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
* @namespace PIXI
* @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;
};