/* Esoteric Software SPINE wrapper for pixi.js */ spine.Bone.yDown = true; PIXI.AnimCache = {}; /** * Supporting class to load images from spine atlases as per spine spec. * * @class SpineTextureLoader * @uses EventTarget * @constructor * @param basePath {String} Tha base path where to look for the images to be loaded * @param crossorigin {Boolean} Whether requests should be treated as crossorigin */ PIXI.SpineTextureLoader = function(basePath, crossorigin) { PIXI.EventTarget.call(this); this.basePath = basePath; this.crossorigin = crossorigin; this.loadingCount = 0; }; /* constructor */ PIXI.SpineTextureLoader.prototype = PIXI.SpineTextureLoader; /** * Starts loading a base texture as per spine specification * * @method load * @param page {spine.AtlasPage} Atlas page to which texture belongs * @param file {String} The file to load, this is just the file path relative to the base path configured in the constructor */ PIXI.SpineTextureLoader.prototype.load = function(page, file) { page.rendererObject = PIXI.BaseTexture.fromImage(this.basePath + '/' + file, this.crossorigin); if (!page.rendererObject.hasLoaded) { var scope = this; ++scope.loadingCount; page.rendererObject.addEventListener('loaded', function(){ --scope.loadingCount; scope.dispatchEvent({ type: 'loadedBaseTexture', content: scope }); }); } }; /** * Unloads a previously loaded texture as per spine specification * * @method unload * @param texture {BaseTexture} Texture object to destroy */ PIXI.SpineTextureLoader.prototype.unload = function(texture) { texture.destroy(true); }; /** * A class that enables the you to import and run your spine animations in pixi. * Spine animation data needs to be loaded using the PIXI.AssetLoader or PIXI.SpineLoader before it can be used by this class * See example 12 (http://www.goodboydigital.com/pixijs/examples/12/) to see a working example and check out the source * * @class Spine * @extends DisplayObjectContainer * @constructor * @param url {String} The url of the spine anim file to be used */ PIXI.Spine = function (url) { PIXI.DisplayObjectContainer.call(this); this.spineData = PIXI.AnimCache[url]; if (!this.spineData) { throw new Error('Spine data must be preloaded using PIXI.SpineLoader or PIXI.AssetLoader: ' + url); } this.skeleton = new spine.Skeleton(this.spineData); this.skeleton.updateWorldTransform(); this.stateData = new spine.AnimationStateData(this.spineData); this.state = new spine.AnimationState(this.stateData); this.slotContainers = []; for (var i = 0, n = this.skeleton.drawOrder.length; i < n; i++) { var slot = this.skeleton.drawOrder[i]; var attachment = slot.attachment; var slotContainer = new PIXI.DisplayObjectContainer(); this.slotContainers.push(slotContainer); this.addChild(slotContainer); if (attachment instanceof spine.RegionAttachment) { var spriteName = attachment.rendererObject.name; var sprite = this.createSprite(slot, attachment); slot.currentSprite = sprite; slot.currentSpriteName = spriteName; slotContainer.addChild(sprite); } else if (attachment instanceof spine.MeshAttachment) { var mesh = this.createMesh(slot, attachment); slot.currentMesh = mesh; slot.currentMeshName = attachment.name; slotContainer.addChild(mesh); } else { continue; } } this.autoUpdate = true; }; PIXI.Spine.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); PIXI.Spine.prototype.constructor = PIXI.Spine; /** * If this flag is set to true, the spine animation will be autoupdated every time * the object id drawn. The down side of this approach is that the delta time is * automatically calculated and you could miss out on cool effects like slow motion, * pause, skip ahead and the sorts. Most of these effects can be achieved even with * autoupdate enabled but are harder to achieve. * * @property autoUpdate * @type { Boolean } * @default true */ Object.defineProperty(PIXI.Spine.prototype, 'autoUpdate', { get: function() { return (this.updateTransform === PIXI.Spine.prototype.autoUpdateTransform); }, set: function(value) { this.updateTransform = value ? PIXI.Spine.prototype.autoUpdateTransform : PIXI.DisplayObjectContainer.prototype.updateTransform; } }); /** * Update the spine skeleton and its animations by delta time (dt) * * @method update * @param dt {Number} Delta time. Time by which the animation should be updated */ PIXI.Spine.prototype.update = function(dt) { this.state.update(dt); this.state.apply(this.skeleton); this.skeleton.updateWorldTransform(); var drawOrder = this.skeleton.drawOrder; for (var i = 0, n = drawOrder.length; i < n; i++) { var slot = drawOrder[i]; var attachment = slot.attachment; var slotContainer = this.slotContainers[i]; var type = attachment.type; if (type === spine.AttachmentType.region) { if (attachment.rendererObject) { if (!slot.currentSpriteName || slot.currentSpriteName !== attachment.name) { var spriteName = attachment.rendererObject.name; if (slot.currentSprite !== undefined) { slot.currentSprite.visible = false; } slot.sprites = slot.sprites || {}; if (slot.sprites[spriteName] !== undefined) { slot.sprites[spriteName].visible = true; } else { var sprite = this.createSprite(slot, attachment); slotContainer.addChild(sprite); } slot.currentSprite = slot.sprites[spriteName]; slot.currentSpriteName = spriteName; } } var bone = slot.bone; slotContainer.position.x = bone.worldX + attachment.x * bone.m00 + attachment.y * bone.m01; slotContainer.position.y = bone.worldY + attachment.x * bone.m10 + attachment.y * bone.m11; slotContainer.scale.x = bone.worldScaleX; slotContainer.scale.y = bone.worldScaleY; slotContainer.rotation = -(slot.bone.worldRotation * spine.degRad); slot.currentSprite.tint = PIXI.rgb2hex([slot.r,slot.g,slot.b]); } else if (type === spine.AttachmentType.skinnedmesh) { if (!slot.currentMeshName || slot.currentMeshName !== attachment.name) { var meshName = attachment.name; if (slot.currentMesh !== undefined) { slot.currentMesh.visible = false; } slot.meshes = slot.meshes || {}; if (slot.meshes[meshName] !== undefined) { slot.meshes[meshName].visible = true; } else { var mesh = this.createMesh(slot, attachment); slotContainer.addChild(mesh); } slot.currentMesh = slot.meshes[meshName]; slot.currentMeshName = meshName; } attachment.computeWorldVertices(slot.bone.skeleton.x, slot.bone.skeleton.y, slot, slot.currentMesh.vertices); } else { slotContainer.visible = false; continue; } slotContainer.visible = true; slotContainer.alpha = slot.a; } }; /** * When autoupdate is set to yes this function is used as pixi's updateTransform function * * @method autoUpdateTransform * @private */ PIXI.Spine.prototype.autoUpdateTransform = function () { this.lastTime = this.lastTime || Date.now(); var timeDelta = (Date.now() - this.lastTime) * 0.001; this.lastTime = Date.now(); this.update(timeDelta); PIXI.DisplayObjectContainer.prototype.updateTransform.call(this); }; /** * Create a new sprite to be used with spine.RegionAttachment * * @method createSprite * @param slot {spine.Slot} The slot to which the attachment is parented * @param attachment {spine.RegionAttachment} The attachment that the sprite will represent * @private */ PIXI.Spine.prototype.createSprite = function (slot, attachment) { var descriptor = attachment.rendererObject; var baseTexture = descriptor.page.rendererObject; var spriteRect = new PIXI.Rectangle(descriptor.x, descriptor.y, descriptor.rotate ? descriptor.height : descriptor.width, descriptor.rotate ? descriptor.width : descriptor.height); var spriteTexture = new PIXI.Texture(baseTexture, spriteRect); var sprite = new PIXI.Sprite(spriteTexture); var baseRotation = descriptor.rotate ? Math.PI * 0.5 : 0.0; sprite.scale.set(descriptor.width / descriptor.originalWidth, descriptor.height / descriptor.originalHeight); sprite.rotation = baseRotation - (attachment.rotation * spine.degRad); sprite.anchor.x = sprite.anchor.y = 0.5; slot.sprites = slot.sprites || {}; slot.sprites[descriptor.name] = sprite; return sprite; }; PIXI.Spine.prototype.createMesh = function (slot, attachment) { var descriptor = attachment.rendererObject; var baseTexture = descriptor.page.rendererObject; var texture = new PIXI.Texture(baseTexture); var strip = new PIXI.Strip(texture); strip.drawMode = PIXI.Strip.DrawModes.TRIANGLES; strip.padding = 5; strip.vertices = new PIXI.Float32Array(attachment.uvs.length); strip.uvs = attachment.uvs; strip.indices = attachment.triangles; slot.meshes[attachment.name] = strip; return strip; };