var core = require('../core'), spine = require('./SpineRuntime'); /* Esoteric Software SPINE wrapper for pixi.js */ spine.Bone.yDown = 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 AssetLoader or 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 * @extends Container * @memberof PIXI.spine * @param spineData {object} The spine data loaded from a spine atlas. */ function Spine(spineData) { core.Container.call(this); if (!spineData) { throw new Error('The spineData param is required.'); } this.spineData = spineData; this.skeleton = new spine.Skeleton(spineData); this.skeleton.updateWorldTransform(); this.stateData = new spine.AnimationStateData(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 core.Container(); 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; } Spine.prototype = Object.create(core.Container.prototype); Spine.prototype.constructor = Spine; module.exports = Spine; Object.defineProperties(Spine.prototype, { /** * 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. * * @member {boolean} * @memberof Spine# * @default true */ autoUpdate: { get: function () { return (this.updateTransform === Spine.prototype.autoUpdateTransform); }, set: function (value) { this.updateTransform = value ? Spine.prototype.autoUpdateTransform : core.Container.prototype.updateTransform; } } }); /** * Update the spine skeleton and its animations by delta time (dt) * * @param dt {number} Delta time. Time by which the animation should be updated */ 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]; if (!attachment) { slotContainer.visible = false; continue; } var type = attachment.type; if (type === spine.AttachmentType.region) { if (attachment.rendererObject) { if (!slot.currentSpriteName || slot.currentSpriteName !== attachment.rendererObject.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 = core.utils.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 * * @private */ 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); core.Container.prototype.updateTransform.call(this); }; /** * Create a new sprite to be used with spine.RegionAttachment * * @param slot {spine.Slot} The slot to which the attachment is parented * @param attachment {spine.RegionAttachment} The attachment that the sprite will represent * @private */ Spine.prototype.createSprite = function (slot, attachment) { var descriptor = attachment.rendererObject; var baseTexture = descriptor.page.rendererObject; var spriteRect = new core.math.Rectangle(descriptor.x, descriptor.y, descriptor.rotate ? descriptor.height : descriptor.width, descriptor.rotate ? descriptor.width : descriptor.height); var spriteTexture = new core.Texture(baseTexture, spriteRect); var sprite = new core.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; }; /** * * @param slot {spine.Slot} The slot to which the attachment is parented * @param attachment {spine.RegionAttachment} The attachment that the sprite will represent * @private */ Spine.prototype.createMesh = function (slot, attachment) { var descriptor = attachment.rendererObject; var baseTexture = descriptor.page.rendererObject; var texture = new core.Texture(baseTexture); var strip = new core.Strip(texture); strip.drawMode = core.Strip.DrawModes.TRIANGLES; strip.canvasPadding = 1.5; strip.vertices = new Float32Array(attachment.uvs.length); strip.uvs = attachment.uvs; strip.indices = attachment.triangles; slot.meshes = slot.meshes || {}; slot.meshes[attachment.name] = strip; return strip; };