/****************************************************************************** * Spine Runtimes Software License * Version 2.3 * * Copyright (c) 2013-2015, Esoteric Software * All rights reserved. * * You are granted a perpetual, non-exclusive, non-sublicensable and * non-transferable license to use, install, execute and perform the Spine * Runtimes Software (the "Software") and derivative works solely for personal * or internal use. Without the written permission of Esoteric Software (see * Section 2 of the Spine Software License Agreement), you may not (a) modify, * translate, adapt or otherwise create derivative works, improvements of the * Software or develop new applications using the Software or (b) remove, * delete, alter or obscure any trademarks or any copyright, trademark, patent * or other intellectual property or proprietary rights notices on or in the * Software, including any copy thereof. Redistributions in binary or source * form must include this license and terms. * * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ var spine = { radDeg: 180 / Math.PI, degRad: Math.PI / 180, Float32Array: typeof(Float32Array) === 'undefined' ? Array : Float32Array, Uint32Array: typeof(Uint32Array) === 'undefined' ? Array : Uint32Array, Uint16Array: typeof(Uint16Array) === 'undefined' ? Array : Uint16Array }; spine.temp = new spine.Float32Array(2); spine.BoneData = function (name, parent) { this.name = name; this.parent = parent; }; spine.BoneData.prototype = { length: 0, x: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1, inheritScale: true, inheritRotation: true }; spine.BlendMode = { normal: 0, additive: 1, multiply: 2, screen: 3 }; spine.SlotData = function (name, boneData) { this.name = name; this.boneData = boneData; }; spine.SlotData.prototype = { r: 1, g: 1, b: 1, a: 1, attachmentName: null, blendMode: spine.BlendMode.normal }; spine.IkConstraintData = function (name) { this.name = name; this.bones = []; }; spine.IkConstraintData.prototype = { target: null, bendDirection: 1, mix: 1 }; spine.TransformConstraintData = function (name) { this.name = name; }; spine.TransformConstraintData.prototype = { bone: null, target: null, translateMix: 1, x: 0, y: 0 }; spine.Bone = function (boneData, skeleton, parent) { this.data = boneData; this.skeleton = skeleton; this.parent = parent; this.setToSetupPose(); }; spine.Bone.yDown = false; spine.Bone.prototype = { x: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1, a: 0, b: 0, worldX: 0, c: 0, d: 0, worldY: 0, worldSignX: 1, worldSignY: 1, update: function () { this.updateWorldTransform(this.x, this.y, this.rotation, this.scaleX, this.scaleY); }, updateWorldTransformWith: function () { this.updateWorldTransform(this.x, this.y, this.rotation, this.scaleX, this.scaleY); }, updateWorldTransform: function (x, y, rotation, scaleX, scaleY) { this.appliedRotation = rotation; this.appliedScaleX = scaleX; this.appliedScaleY = scaleY; rotation *= spine.degRad; var cos = Math.cos(rotation), sin = Math.sin(rotation); var la = cos * scaleX, lb = -sin * scaleY, lc = sin * scaleX, ld = cos * scaleY; var parent = this.parent; if (!parent) { // Root bone. var skeleton = this.skeleton; if (skeleton.flipX) { x = -x; la = -la; lb = -lb; } if (skeleton.flipY != spine.Bone.yDown) { y = -y; lc = -lc; ld = -ld; } this.a = la; this.b = lb; this.c = lc; this.d = ld; this.worldX = x; this.worldY = y; this.worldSignX = scaleX < 0 ? -1 : 1; this.worldSignY = scaleY < 0 ? -1 : 1; return; } var pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; this.worldX = pa * x + pb * y + parent.worldX; this.worldY = pc * x + pd * y + parent.worldY; this.worldSignX = parent.worldSignX * (scaleX < 0 ? -1 : 1); this.worldSignY = parent.worldSignY * (scaleY < 0 ? -1 : 1); if (this.data.inheritRotation && this.data.inheritScale) { this.a = pa * la + pb * lc; this.b = pa * lb + pb * ld; this.c = pc * la + pd * lc; this.d = pc * lb + pd * ld; } else { if (this.data.inheritRotation) { // No scale inheritance. pa = 1; pb = 0; pc = 0; pd = 1; do { rotation = parent.appliedRotation * spine.degRad; cos = Math.cos(rotation); sin = Math.sin(rotation); var temp = pa * cos + pb * sin; pb = pa * -sin + pb * cos; pa = temp; temp = pc * cos + pd * sin; pd = pc * -sin + pd * cos; pc = temp; if (!parent.data.inheritRotation) break; parent = parent.parent; } while (parent); this.a = pa * la + pb * lc; this.b = pa * lb + pb * ld; this.c = pc * la + pd * lc; this.d = pc * lb + pd * ld; } else if (this.data.inheritScale) { // No rotation inheritance. pa = 1; pb = 0; pc = 0; pd = 1; do { rotation = parent.appliedRotation * spine.degRad; cos = Math.cos(rotation); sin = Math.sin(rotation); var psx = parent.appliedScaleX, psy = parent.appliedScaleY; var za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy; var temp = pa * za + pb * zc; pb = pa * zb + pb * zd; pa = temp; temp = pc * za + pd * zc; pd = pc * zb + pd * zd; pc = temp; if (psx < 0) rotation = -rotation; cos = Math.cos(-rotation); sin = Math.sin(-rotation); temp = pa * cos + pb * sin; pb = pa * -sin + pb * cos; pa = temp; temp = pc * cos + pd * sin; pd = pc * -sin + pd * cos; pc = temp; if (!parent.data.inheritScale) break; parent = parent.parent; } while (parent); this.a = pa * la + pb * lc; this.b = pa * lb + pb * ld; this.c = pc * la + pd * lc; this.d = pc * lb + pd * ld; } else { this.a = la; this.b = lb; this.c = lc; this.d = ld; } if (this.skeleton.flipX) { this.a = -this.a; this.b = -this.b; } if (this.skeleton.flipY != spine.Bone.yDown) { this.c = -this.c; this.d = -this.d; } } }, setToSetupPose: function () { var data = this.data; this.x = data.x; this.y = data.y; this.rotation = data.rotation; this.scaleX = data.scaleX; this.scaleY = data.scaleY; }, getWorldRotationX: function () { return Math.atan2(this.c, this.a) * spine.radDeg; }, getWorldRotationY: function () { return Math.atan2(this.d, this.b) * spine.radDeg; }, getWorldScaleX: function () { return Math.sqrt(this.a * this.a + this.b * this.b) * this.worldSignX; }, getWorldScaleY: function () { return Math.sqrt(this.c * this.c + this.d * this.d) * this.worldSignY; }, worldToLocal: function (world) { var x = world[0] - this.worldX, y = world[1] - this.worldY; var a = this.a, b = this.b, c = this.c, d = this.d; var invDet = 1 / (a * d - b * c); world[0] = (x * d * invDet - y * b * invDet); world[1] = (y * a * invDet - x * c * invDet); return world; }, localToWorld: function (local) { var x = local[0], y = local[1]; local[0] = x * this.a + y * this.b + this.worldX; local[1] = x * this.c + y * this.d + this.worldY; return local; } }; spine.Slot = function (slotData, bone) { this.data = slotData; this.bone = bone; this.attachmentVertices = new spine.Float32Array(); this.setToSetupPose(); }; spine.Slot.prototype = { r: 1, g: 1, b: 1, a: 1, _attachmentTime: 0, attachment: null, setAttachment: function (attachment) { if (this.attachment == attachment) return; this.attachment = attachment; this._attachmentTime = this.bone.skeleton.time; this.attachmentVertices.length = 0; }, setAttachmentTime: function (time) { this._attachmentTime = this.bone.skeleton.time - time; }, getAttachmentTime: function () { return this.bone.skeleton.time - this._attachmentTime; }, setToSetupPose: function () { var data = this.data; this.r = data.r; this.g = data.g; this.b = data.b; this.a = data.a; if (!data.attachmentName) this.setAttachment(null); else { var slotDatas = this.bone.skeleton.data.slots; for (var i = 0, n = slotDatas.length; i < n; i++) { if (slotDatas[i] == data) { this.attachment = null; this.setAttachment(this.bone.skeleton.getAttachmentBySlotIndex(i, data.attachmentName)); break; } } } } }; spine.IkConstraint = function (data, skeleton) { this.data = data; this.mix = data.mix; this.bendDirection = data.bendDirection; this.bones = []; for (var i = 0, n = data.bones.length; i < n; i++) this.bones[i] = skeleton.findBone(data.bones[i].name); this.target = skeleton.findBone(data.target.name); }; spine.IkConstraint.prototype = { apply: function () { this.update(); }, update: function () { var target = this.target; var bones = this.bones; switch (bones.length) { case 1: spine.IkConstraint.apply1(bones[0], target.worldX, target.worldY, this.mix); break; case 2: spine.IkConstraint.apply2(bones[0], bones[1], target.worldX, target.worldY, this.bendDirection, this.mix); break; } }, }; /** Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified in the world * coordinate system. */ spine.IkConstraint.apply1 = function (bone, targetX, targetY, alpha) { var parentRotation = !bone.parent ? 0 : bone.parent.getWorldRotationX(); var rotation = bone.rotation; var rotationIK = Math.atan2(targetY - bone.worldY, targetX - bone.worldX) * spine.radDeg - parentRotation; if ((bone.worldSignX != bone.worldSignY) != (bone.skeleton.flipX != (bone.skeleton.flipY != spine.Bone.yDown))) rotationIK = 360 - rotationIK; if (rotationIK > 180) rotationIK -= 360; else if (rotationIK < -180) rotationIK += 360; bone.updateWorldTransform(bone.x, bone.y, rotation + (rotationIK - rotation) * alpha, bone.appliedScaleX, bone.appliedScaleY); }; /** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The * target is specified in the world coordinate system. * @param child A direct descendant of the parent bone. */ spine.IkConstraint.apply2 = function (parent, child, targetX, targetY, bendDir, alpha) { if (alpha == 0) return; var px = parent.x, py = parent.y, psx = parent.appliedScaleX, psy = parent.appliedScaleY; var os1, os2, s2; if (psx < 0) { psx = -psx; os1 = 180; s2 = -1; } else { os1 = 0; s2 = 1; } if (psy < 0) { psy = -psy; s2 = -s2; } var cx = child.x, cy = child.y, csx = child.appliedScaleX; var u = Math.abs(psx - psy) <= 0.0001; if (!u && cy != 0) { child.worldX = parent.a * cx + parent.worldX; child.worldY = parent.c * cx + parent.worldY; cy = 0; } if (csx < 0) { csx = -csx; os2 = 180; } else os2 = 0; var pp = parent.parent; var tx, ty, dx, dy; if (!pp) { tx = targetX - px; ty = targetY - py; dx = child.worldX - px; dy = child.worldY - py; } else { var a = pp.a, b = pp.b, c = pp.c, d = pp.d, invDet = 1 / (a * d - b * c); var wx = pp.worldX, wy = pp.worldY, x = targetX - wx, y = targetY - wy; tx = (x * d - y * b) * invDet - px; ty = (y * a - x * c) * invDet - py; x = child.worldX - wx; y = child.worldY - wy; dx = (x * d - y * b) * invDet - px; dy = (y * a - x * c) * invDet - py; } var l1 = Math.sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; outer: if (u) { l2 *= psx; var cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); if (cos < -1) cos = -1; else if (cos > 1) cos = 1; a2 = Math.acos(cos) * bendDir; var a = l1 + l2 * cos, o = l2 * Math.sin(a2); a1 = Math.atan2(ty * a - tx * o, tx * a + ty * o); } else { var a = psx * l2, b = psy * l2, ta = Math.atan2(ty, tx); var aa = a * a, bb = b * b, ll = l1 * l1, dd = tx * tx + ty * ty; var c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa; var d = c1 * c1 - 4 * c2 * c0; if (d >= 0) { var q = Math.sqrt(d); if (c1 < 0) q = -q; q = -(c1 + q) / 2; var r0 = q / c2, r1 = c0 / q; var r = Math.abs(r0) < Math.abs(r1) ? r0 : r1; if (r * r <= dd) { var y = Math.sqrt(dd - r * r) * bendDir; a1 = ta - Math.atan2(y, r); a2 = Math.atan2(y / psy, (r - l1) / psx); break outer; } } var minAngle = 0, minDist = Number.MAX_VALUE, minX = 0, minY = 0; var maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0; var x = l1 + a, dist = x * x; if (dist > maxDist) { maxAngle = 0; maxDist = dist; maxX = x; } x = l1 - a; dist = x * x; if (dist < minDist) { minAngle = Math.PI; minDist = dist; minX = x; } var angle = Math.acos(-a * l1 / (aa - bb)); x = a * Math.cos(angle) + l1; var y = b * Math.sin(angle); dist = x * x + y * y; if (dist < minDist) { minAngle = angle; minDist = dist; minX = x; minY = y; } if (dist > maxDist) { maxAngle = angle; maxDist = dist; maxX = x; maxY = y; } if (dd <= (minDist + maxDist) / 2) { a1 = ta - Math.atan2(minY * bendDir, minX); a2 = minAngle * bendDir; } else { a1 = ta - Math.atan2(maxY * bendDir, maxX); a2 = maxAngle * bendDir; } } var os = Math.atan2(cy, cx) * s2; a1 = (a1 - os) * spine.radDeg + os1; a2 = (a2 + os) * spine.radDeg * s2 + os2; if (a1 > 180) a1 -= 360; else if (a1 < -180) a1 += 360; if (a2 > 180) a2 -= 360; else if (a2 < -180) a2 += 360; var rotation = parent.rotation; parent.updateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY); rotation = child.rotation; child.updateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY); }; spine.TransformConstraint = function (data, skeleton) { this.data = data; this.translateMix = data.translateMix; this.x = data.x; this.y = data.y; this.bone = skeleton.findBone(data.bone.name); this.target = skeleton.findBone(data.target.name); }; spine.TransformConstraint.prototype = { apply: function () { this.update(); }, update: function () { var translateMix = this.translateMix; if (translateMix > 0) { var temp = spine.temp; temp[0] = x; temp[1] = y; this.target.localToWorld(temp); var bone = this.bone; bone.worldX += (temp[0] - bone.worldX) * translateMix; bone.worldY += (temp[1] - bone.worldY) * translateMix; } }, }; spine.Skin = function (name) { this.name = name; this.attachments = {}; }; spine.Skin.prototype = { addAttachment: function (slotIndex, name, attachment) { this.attachments[slotIndex + ":" + name] = attachment; }, getAttachment: function (slotIndex, name) { return this.attachments[slotIndex + ":" + name]; }, _attachAll: function (skeleton, oldSkin) { for (var key in oldSkin.attachments) { var colon = key.indexOf(":"); var slotIndex = parseInt(key.substring(0, colon)); var name = key.substring(colon + 1); var slot = skeleton.slots[slotIndex]; if (slot.attachment && slot.attachment.name == name) { var attachment = this.getAttachment(slotIndex, name); if (attachment) slot.setAttachment(attachment); } } } }; spine.Animation = function (name, timelines, duration) { this.name = name; this.timelines = timelines; this.duration = duration; }; spine.Animation.prototype = { apply: function (skeleton, lastTime, time, loop, events) { if (loop && this.duration != 0) { time %= this.duration; if (lastTime > 0) lastTime %= this.duration; } var timelines = this.timelines; for (var i = 0, n = timelines.length; i < n; i++) timelines[i].apply(skeleton, lastTime, time, events, 1); }, mix: function (skeleton, lastTime, time, loop, events, alpha) { if (loop && this.duration != 0) { time %= this.duration; if (lastTime > 0) lastTime %= this.duration; } var timelines = this.timelines; for (var i = 0, n = timelines.length; i < n; i++) timelines[i].apply(skeleton, lastTime, time, events, alpha); } }; spine.Animation.binarySearch = function (values, target, step) { var low = 0; var high = Math.floor(values.length / step) - 2; if (!high) return step; var current = high >>> 1; while (true) { if (values[(current + 1) * step] <= target) low = current + 1; else high = current; if (low == high) return (low + 1) * step; current = (low + high) >>> 1; } }; spine.Animation.binarySearch1 = function (values, target) { var low = 0; var high = values.length - 2; if (!high) return 1; var current = high >>> 1; while (true) { if (values[current + 1] <= target) low = current + 1; else high = current; if (low == high) return low + 1; current = (low + high) >>> 1; } }; spine.Animation.linearSearch = function (values, target, step) { for (var i = 0, last = values.length - step; i <= last; i += step) if (values[i] > target) return i; return -1; }; spine.Curves = function (frameCount) { var count = (frameCount - 1) * 19/*BEZIER_SIZE*/; this.curves = new spine.Float32Array(count); // type, x, y, ... this.curves.length = count; }; spine.Curves.prototype = { setLinear: function (frameIndex) { this.curves[frameIndex * 19/*BEZIER_SIZE*/] = 0/*LINEAR*/; }, setStepped: function (frameIndex) { this.curves[frameIndex * 19/*BEZIER_SIZE*/] = 1/*STEPPED*/; }, /** Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. * cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of * the difference between the keyframe's values. */ setCurve: function (frameIndex, cx1, cy1, cx2, cy2) { var subdiv1 = 1 / 10/*BEZIER_SEGMENTS*/, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1; var pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3; var tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1; var dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3; var ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5; var dddfx = tmp2x * pre5, dddfy = tmp2y * pre5; var i = frameIndex * 19/*BEZIER_SIZE*/; var curves = this.curves; curves[i++] = 2/*BEZIER*/; var x = dfx, y = dfy; for (var n = i + 19/*BEZIER_SIZE*/ - 1; i < n; i += 2) { curves[i] = x; curves[i + 1] = y; dfx += ddfx; dfy += ddfy; ddfx += dddfx; ddfy += dddfy; x += dfx; y += dfy; } }, getCurvePercent: function (frameIndex, percent) { percent = percent < 0 ? 0 : (percent > 1 ? 1 : percent); var curves = this.curves; var i = frameIndex * 19/*BEZIER_SIZE*/; var type = curves[i]; if (type === 0/*LINEAR*/) return percent; if (type == 1/*STEPPED*/) return 0; i++; var x = 0; for (var start = i, n = i + 19/*BEZIER_SIZE*/ - 1; i < n; i += 2) { x = curves[i]; if (x >= percent) { var prevX, prevY; if (i == start) { prevX = 0; prevY = 0; } else { prevX = curves[i - 2]; prevY = curves[i - 1]; } return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); } } var y = curves[i - 1]; return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. } }; spine.RotateTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = new spine.Float32Array(frameCount * 2); // time, angle, ... this.frames.length = frameCount * 2; }; spine.RotateTimeline.prototype = { boneIndex: 0, getFrameCount: function () { return this.frames.length / 2; }, setFrame: function (frameIndex, time, angle) { frameIndex *= 2; this.frames[frameIndex] = time; this.frames[frameIndex + 1] = angle; }, apply: function (skeleton, lastTime, time, firedEvents, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. var bone = skeleton.bones[this.boneIndex]; if (time >= frames[frames.length - 2]) { // Time is after last frame. var amount = bone.data.rotation + frames[frames.length - 1] - bone.rotation; while (amount > 180) amount -= 360; while (amount < -180) amount += 360; bone.rotation += amount * alpha; return; } // Interpolate between the previous frame and the current frame. var frameIndex = spine.Animation.binarySearch(frames, time, 2); var prevFrameValue = frames[frameIndex - 1]; var frameTime = frames[frameIndex]; var percent = 1 - (time - frameTime) / (frames[frameIndex - 2/*PREV_FRAME_TIME*/] - frameTime); percent = this.curves.getCurvePercent(frameIndex / 2 - 1, percent); var amount = frames[frameIndex + 1/*FRAME_VALUE*/] - prevFrameValue; while (amount > 180) amount -= 360; while (amount < -180) amount += 360; amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation; while (amount > 180) amount -= 360; while (amount < -180) amount += 360; bone.rotation += amount * alpha; } }; spine.TranslateTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = new spine.Float32Array(frameCount * 3); // time, x, y, ... this.frames.length = frameCount * 3; }; spine.TranslateTimeline.prototype = { boneIndex: 0, getFrameCount: function () { return this.frames.length / 3; }, setFrame: function (frameIndex, time, x, y) { frameIndex *= 3; this.frames[frameIndex] = time; this.frames[frameIndex + 1] = x; this.frames[frameIndex + 2] = y; }, apply: function (skeleton, lastTime, time, firedEvents, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. var bone = skeleton.bones[this.boneIndex]; if (time >= frames[frames.length - 3]) { // Time is after last frame. bone.x += (bone.data.x + frames[frames.length - 2] - bone.x) * alpha; bone.y += (bone.data.y + frames[frames.length - 1] - bone.y) * alpha; return; } // Interpolate between the previous frame and the current frame. var frameIndex = spine.Animation.binarySearch(frames, time, 3); var prevFrameX = frames[frameIndex - 2]; var prevFrameY = frames[frameIndex - 1]; var frameTime = frames[frameIndex]; var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*PREV_FRAME_TIME*/] - frameTime); percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent); bone.x += (bone.data.x + prevFrameX + (frames[frameIndex + 1/*FRAME_X*/] - prevFrameX) * percent - bone.x) * alpha; bone.y += (bone.data.y + prevFrameY + (frames[frameIndex + 2/*FRAME_Y*/] - prevFrameY) * percent - bone.y) * alpha; } }; spine.ScaleTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = new spine.Float32Array(frameCount * 3); // time, x, y, ... this.frames.length = frameCount * 3; }; spine.ScaleTimeline.prototype = { boneIndex: 0, getFrameCount: function () { return this.frames.length / 3; }, setFrame: function (frameIndex, time, x, y) { frameIndex *= 3; this.frames[frameIndex] = time; this.frames[frameIndex + 1] = x; this.frames[frameIndex + 2] = y; }, apply: function (skeleton, lastTime, time, firedEvents, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. var bone = skeleton.bones[this.boneIndex]; if (time >= frames[frames.length - 3]) { // Time is after last frame. bone.scaleX += (bone.data.scaleX * frames[frames.length - 2] - bone.scaleX) * alpha; bone.scaleY += (bone.data.scaleY * frames[frames.length - 1] - bone.scaleY) * alpha; return; } // Interpolate between the previous frame and the current frame. var frameIndex = spine.Animation.binarySearch(frames, time, 3); var prevFrameX = frames[frameIndex - 2]; var prevFrameY = frames[frameIndex - 1]; var frameTime = frames[frameIndex]; var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*PREV_FRAME_TIME*/] - frameTime); percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent); bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frameIndex + 1/*FRAME_X*/] - prevFrameX) * percent) - bone.scaleX) * alpha; bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frameIndex + 2/*FRAME_Y*/] - prevFrameY) * percent) - bone.scaleY) * alpha; } }; spine.ColorTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = new spine.Float32Array(frameCount * 5); // time, r, g, b, a, ... this.frames.length = frameCount * 5; }; spine.ColorTimeline.prototype = { slotIndex: 0, getFrameCount: function () { return this.frames.length / 5; }, setFrame: function (frameIndex, time, r, g, b, a) { frameIndex *= 5; this.frames[frameIndex] = time; this.frames[frameIndex + 1] = r; this.frames[frameIndex + 2] = g; this.frames[frameIndex + 3] = b; this.frames[frameIndex + 4] = a; }, apply: function (skeleton, lastTime, time, firedEvents, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. var r, g, b, a; if (time >= frames[frames.length - 5]) { // Time is after last frame. var i = frames.length - 1; r = frames[i - 3]; g = frames[i - 2]; b = frames[i - 1]; a = frames[i]; } else { // Interpolate between the previous frame and the current frame. var frameIndex = spine.Animation.binarySearch(frames, time, 5); var prevFrameR = frames[frameIndex - 4]; var prevFrameG = frames[frameIndex - 3]; var prevFrameB = frames[frameIndex - 2]; var prevFrameA = frames[frameIndex - 1]; var frameTime = frames[frameIndex]; var percent = 1 - (time - frameTime) / (frames[frameIndex - 5/*PREV_FRAME_TIME*/] - frameTime); percent = this.curves.getCurvePercent(frameIndex / 5 - 1, percent); r = prevFrameR + (frames[frameIndex + 1/*FRAME_R*/] - prevFrameR) * percent; g = prevFrameG + (frames[frameIndex + 2/*FRAME_G*/] - prevFrameG) * percent; b = prevFrameB + (frames[frameIndex + 3/*FRAME_B*/] - prevFrameB) * percent; a = prevFrameA + (frames[frameIndex + 4/*FRAME_A*/] - prevFrameA) * percent; } var slot = skeleton.slots[this.slotIndex]; if (alpha < 1) { slot.r += (r - slot.r) * alpha; slot.g += (g - slot.g) * alpha; slot.b += (b - slot.b) * alpha; slot.a += (a - slot.a) * alpha; } else { slot.r = r; slot.g = g; slot.b = b; slot.a = a; } } }; spine.AttachmentTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = new spine.Float32Array(frameCount); // time, ... this.frames.length = frameCount; this.attachmentNames = []; this.attachmentNames.length = frameCount; }; spine.AttachmentTimeline.prototype = { slotIndex: 0, getFrameCount: function () { return this.frames.length; }, setFrame: function (frameIndex, time, attachmentName) { this.frames[frameIndex] = time; this.attachmentNames[frameIndex] = attachmentName; }, apply: function (skeleton, lastTime, time, firedEvents, alpha) { var frames = this.frames; if (time < frames[0]) { if (lastTime > time) this.apply(skeleton, lastTime, Number.MAX_VALUE, null, 0); return; } else if (lastTime > time) // lastTime = -1; var frameIndex = time >= frames[frames.length - 1] ? frames.length - 1 : spine.Animation.binarySearch1(frames, time) - 1; if (frames[frameIndex] < lastTime) return; var attachmentName = this.attachmentNames[frameIndex]; skeleton.slots[this.slotIndex].setAttachment( !attachmentName ? null : skeleton.getAttachmentBySlotIndex(this.slotIndex, attachmentName)); } }; spine.EventTimeline = function (frameCount) { this.frames = new spine.Float32Array(frameCount); // time, ... this.frames.length = frameCount; this.events = []; this.events.length = frameCount; }; spine.EventTimeline.prototype = { getFrameCount: function () { return this.frames.length; }, setFrame: function (frameIndex, event) { this.frames[frameIndex] = event.time; this.events[frameIndex] = event; }, /** Fires events for frames > lastTime and <= time. */ apply: function (skeleton, lastTime, time, firedEvents, alpha) { if (!firedEvents) return; var frames = this.frames; var frameCount = frames.length; if (lastTime > time) { // Fire events after last time for looped animations. this.apply(skeleton, lastTime, Number.MAX_VALUE, firedEvents, alpha); lastTime = -1; } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. return; if (time < frames[0]) return; // Time is before first frame. var frameIndex; if (lastTime < frames[0]) frameIndex = 0; else { frameIndex = spine.Animation.binarySearch1(frames, lastTime); var frame = frames[frameIndex]; while (frameIndex > 0) { // Fire multiple events with the same frame. if (frames[frameIndex - 1] != frame) break; frameIndex--; } } var events = this.events; for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++) firedEvents[firedEvents.length] = events[frameIndex]; } }; spine.DrawOrderTimeline = function (frameCount) { this.frames = new spine.Float32Array(frameCount); // time, ... this.frames.length = frameCount; this.drawOrders = []; this.drawOrders.length = frameCount; }; spine.DrawOrderTimeline.prototype = { getFrameCount: function () { return this.frames.length; }, setFrame: function (frameIndex, time, drawOrder) { this.frames[frameIndex] = time; this.drawOrders[frameIndex] = drawOrder; }, apply: function (skeleton, lastTime, time, firedEvents, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. var frameIndex; if (time >= frames[frames.length - 1]) // Time is after last frame. frameIndex = frames.length - 1; else frameIndex = spine.Animation.binarySearch1(frames, time) - 1; var drawOrder = skeleton.drawOrder; var slots = skeleton.slots; var drawOrderToSetupIndex = this.drawOrders[frameIndex]; if (!drawOrderToSetupIndex) { for (var i = 0, n = slots.length; i < n; i++) drawOrder[i] = slots[i]; } else { for (var i = 0, n = drawOrderToSetupIndex.length; i < n; i++) drawOrder[i] = skeleton.slots[drawOrderToSetupIndex[i]]; } } }; spine.FfdTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = new spine.Float32Array(frameCount); this.frames.length = frameCount; this.frameVertices = []; this.frameVertices.length = frameCount; }; spine.FfdTimeline.prototype = { slotIndex: 0, attachment: 0, getFrameCount: function () { return this.frames.length; }, setFrame: function (frameIndex, time, vertices) { this.frames[frameIndex] = time; this.frameVertices[frameIndex] = vertices; }, apply: function (skeleton, lastTime, time, firedEvents, alpha) { var slot = skeleton.slots[this.slotIndex]; var slotAttachment = slot.attachment; if (!slotAttachment) return; if (slotAttachment != this.attachment && (!slotAttachment.inheritFFD || slotAttachment.parentMesh != this.attachment)) return; var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. var frameVertices = this.frameVertices; var vertexCount = frameVertices[0].length; var vertices = slot.attachmentVertices; if (vertices.length != vertexCount) { slot.attachmentVertices = vertices = new spine.Float32Array(vertexCount); alpha = 1; } if (time >= frames[frames.length - 1]) { // Time is after last frame. var lastVertices = frameVertices[frames.length - 1]; if (alpha < 1) { for (var i = 0; i < vertexCount; i++) vertices[i] += (lastVertices[i] - vertices[i]) * alpha; } else { for (var i = 0; i < vertexCount; i++) vertices[i] = lastVertices[i]; } return; } // Interpolate between the previous frame and the current frame. var frameIndex = spine.Animation.binarySearch1(frames, time); var frameTime = frames[frameIndex]; var percent = 1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime); percent = this.curves.getCurvePercent(frameIndex - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); var prevVertices = frameVertices[frameIndex - 1]; var nextVertices = frameVertices[frameIndex]; if (alpha < 1) { for (var i = 0; i < vertexCount; i++) { var prev = prevVertices[i]; vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; } } else { for (var i = 0; i < vertexCount; i++) { var prev = prevVertices[i]; vertices[i] = prev + (nextVertices[i] - prev) * percent; } } } }; spine.IkConstraintTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = new spine.Float32Array(frameCount * 3); // time, mix, bendDirection, ... this.frames.length = frameCount * 3; }; spine.IkConstraintTimeline.prototype = { ikConstraintIndex: 0, getFrameCount: function () { return this.frames.length / 3; }, setFrame: function (frameIndex, time, mix, bendDirection) { frameIndex *= 3; this.frames[frameIndex] = time; this.frames[frameIndex + 1] = mix; this.frames[frameIndex + 2] = bendDirection; }, apply: function (skeleton, lastTime, time, firedEvents, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. var ikConstraint = skeleton.ikConstraints[this.ikConstraintIndex]; if (time >= frames[frames.length - 3]) { // Time is after last frame. ikConstraint.mix += (frames[frames.length - 2] - ikConstraint.mix) * alpha; ikConstraint.bendDirection = frames[frames.length - 1]; return; } // Interpolate between the previous frame and the current frame. var frameIndex = spine.Animation.binarySearch(frames, time, 3); var prevFrameMix = frames[frameIndex + -2/*PREV_FRAME_MIX*/]; var frameTime = frames[frameIndex]; var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*PREV_FRAME_TIME*/] - frameTime); percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent); var mix = prevFrameMix + (frames[frameIndex + 1/*FRAME_MIX*/] - prevFrameMix) * percent; ikConstraint.mix += (mix - ikConstraint.mix) * alpha; ikConstraint.bendDirection = frames[frameIndex + -1/*PREV_FRAME_BEND_DIRECTION*/]; } }; spine.SkeletonData = function () { this.bones = []; this.slots = []; this.skins = []; this.events = []; this.animations = []; this.ikConstraints = []; this.transformConstraints = []; }; spine.SkeletonData.prototype = { name: null, defaultSkin: null, width: 0, height: 0, version: null, hash: null, /** @return May be null. */ findBone: function (boneName) { var bones = this.bones; for (var i = 0, n = bones.length; i < n; i++) if (bones[i].name == boneName) return bones[i]; return null; }, /** @return -1 if the bone was not found. */ findBoneIndex: function (boneName) { var bones = this.bones; for (var i = 0, n = bones.length; i < n; i++) if (bones[i].name == boneName) return i; return -1; }, /** @return May be null. */ findSlot: function (slotName) { var slots = this.slots; for (var i = 0, n = slots.length; i < n; i++) { if (slots[i].name == slotName) return slot[i]; } return null; }, /** @return -1 if the bone was not found. */ findSlotIndex: function (slotName) { var slots = this.slots; for (var i = 0, n = slots.length; i < n; i++) if (slots[i].name == slotName) return i; return -1; }, /** @return May be null. */ findSkin: function (skinName) { var skins = this.skins; for (var i = 0, n = skins.length; i < n; i++) if (skins[i].name == skinName) return skins[i]; return null; }, /** @return May be null. */ findEvent: function (eventName) { var events = this.events; for (var i = 0, n = events.length; i < n; i++) if (events[i].name == eventName) return events[i]; return null; }, /** @return May be null. */ findAnimation: function (animationName) { var animations = this.animations; for (var i = 0, n = animations.length; i < n; i++) if (animations[i].name == animationName) return animations[i]; return null; }, /** @return May be null. */ findIkConstraint: function (constraintName) { var ikConstraints = this.ikConstraints; for (var i = 0, n = ikConstraints.length; i < n; i++) if (ikConstraints[i].name == constraintName) return ikConstraints[i]; return null; }, /** @return May be null. */ findTransformConstraints: function (constraintName) { var transformConstraints = this.transformConstraints; for (var i = 0, n = transformConstraints.length; i < n; i++) if (transformConstraints[i].name == constraintName) return transformConstraints[i]; return null; } }; spine.Skeleton = function (skeletonData) { this.data = skeletonData; this.bones = []; for (var i = 0, n = skeletonData.bones.length; i < n; i++) { var boneData = skeletonData.bones[i]; var parent = !boneData.parent ? null : this.bones[skeletonData.bones.indexOf(boneData.parent)]; this.bones[i] = new spine.Bone(boneData, this, parent); } this.slots = []; this.drawOrder = []; for (var i = 0, n = skeletonData.slots.length; i < n; i++) { var slotData = skeletonData.slots[i]; var bone = this.bones[skeletonData.bones.indexOf(slotData.boneData)]; var slot = new spine.Slot(slotData, bone); this.slots[i] = slot; this.drawOrder[i] = slot; } this.ikConstraints = []; for (var i = 0, n = skeletonData.ikConstraints.length; i < n; i++) this.ikConstraints[i] = new spine.IkConstraint(skeletonData.ikConstraints[i], this); this.transformConstraints = []; for (var i = 0, n = skeletonData.transformConstraints.length; i < n; i++) this.transformConstraints[i] = new spine.TransformConstraint(skeletonData.transformConstraints[i], this); this.cache = []; this.updateCache(); }; spine.Skeleton.prototype = { x: 0, y: 0, skin: null, r: 1, g: 1, b: 1, a: 1, time: 0, flipX: false, flipY: false, /** Caches information about bones and constraints. Must be called if bones or constraints are added or removed. */ updateCache: function () { var bones = this.bones; var updateCache = this.cache; var ikConstraints = this.ikConstraints; var transformConstraints = this.transformConstraints; var ikConstraintsCount = ikConstraints.length; var transformConstraintsCount = transformConstraints.length; updateCache.length = 0; for (var i = 0, n = bones.length; i < n; i++) { var bone = bones[i]; updateCache[updateCache.length] = bone; for (var ii = 0; ii < ikConstraintsCount; ii++) { var ikConstraint = ikConstraints[ii]; if (bone == ikConstraint.bones[ikConstraint.bones.length - 1]) { updateCache[updateCache.length] = ikConstraint; break; } } } for (var i = 0; i < transformConstraintsCount; i++) { var transformConstraint = transformConstraints[i]; for (var ii = updateCache.length - 1; ii >= 0; ii--) { var object = updateCache[ii]; if (object == transformConstraint.bone || object == transformConstraint.target) { updateCache.splice(ii + 1, 0, transformConstraint); break; } } } }, /** Updates the world transform for each bone and applies constraints. */ updateWorldTransform: function () { var updateCache = this.cache; for (var i = 0, n = updateCache.length; i < n; i++) updateCache[i].update(); }, /** Sets the bones, constraints, and slots to their setup pose values. */ setToSetupPose: function () { this.setBonesToSetupPose(); this.setSlotsToSetupPose(); }, /** Sets the bones and constraints to their setup pose values. */ setBonesToSetupPose: function () { var bones = this.bones; for (var i = 0, n = bones.length; i < n; i++) bones[i].setToSetupPose(); var ikConstraints = this.ikConstraints; for (var i = 0, n = ikConstraints.length; i < n; i++) { var constraint = ikConstraints[i]; constraint.bendDirection = constraint.data.bendDirection; constraint.mix = constraint.data.mix; } var transformConstraints = this.transformConstraints; for (var i = 0, n = transformConstraints.length; i < n; i++) { var constraint = transformConstraints[i]; constraint.translateMix = constraint.data.translateMix; constraint.x = constraint.data.x; constraint.y = constraint.data.y; } }, setSlotsToSetupPose: function () { var slots = this.slots; var drawOrder = this.drawOrder; for (var i = 0, n = slots.length; i < n; i++) { drawOrder[i] = slots[i]; slots[i].setToSetupPose(i); } }, /** @return May return null. */ getRootBone: function () { return this.bones.length ? this.bones[0] : null; }, /** @return May be null. */ findBone: function (boneName) { var bones = this.bones; for (var i = 0, n = bones.length; i < n; i++) if (bones[i].data.name == boneName) return bones[i]; return null; }, /** @return -1 if the bone was not found. */ findBoneIndex: function (boneName) { var bones = this.bones; for (var i = 0, n = bones.length; i < n; i++) if (bones[i].data.name == boneName) return i; return -1; }, /** @return May be null. */ findSlot: function (slotName) { var slots = this.slots; for (var i = 0, n = slots.length; i < n; i++) if (slots[i].data.name == slotName) return slots[i]; return null; }, /** @return -1 if the bone was not found. */ findSlotIndex: function (slotName) { var slots = this.slots; for (var i = 0, n = slots.length; i < n; i++) if (slots[i].data.name == slotName) return i; return -1; }, setSkinByName: function (skinName) { var skin = this.data.findSkin(skinName); if (!skin) throw "Skin not found: " + skinName; this.setSkin(skin); }, /** Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default skin}. * Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. If there was * no old skin, each slot's setup mode attachment is attached from the new skin. * @param newSkin May be null. */ setSkin: function (newSkin) { if (newSkin) { if (this.skin) newSkin._attachAll(this, this.skin); else { var slots = this.slots; for (var i = 0, n = slots.length; i < n; i++) { var slot = slots[i]; var name = slot.data.attachmentName; if (name) { var attachment = newSkin.getAttachment(i, name); if (attachment) slot.setAttachment(attachment); } } } } this.skin = newSkin; }, /** @return May be null. */ getAttachmentBySlotName: function (slotName, attachmentName) { return this.getAttachmentBySlotIndex(this.data.findSlotIndex(slotName), attachmentName); }, /** @return May be null. */ getAttachmentBySlotIndex: function (slotIndex, attachmentName) { if (this.skin) { var attachment = this.skin.getAttachment(slotIndex, attachmentName); if (attachment) return attachment; } if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName); return null; }, /** @param attachmentName May be null. */ setAttachment: function (slotName, attachmentName) { var slots = this.slots; for (var i = 0, n = slots.length; i < n; i++) { var slot = slots[i]; if (slot.data.name == slotName) { var attachment = null; if (attachmentName) { attachment = this.getAttachmentBySlotIndex(i, attachmentName); if (!attachment) throw "Attachment not found: " + attachmentName + ", for slot: " + slotName; } slot.setAttachment(attachment); return; } } throw "Slot not found: " + slotName; }, /** @return May be null. */ findIkConstraint: function (constraintName) { var ikConstraints = this.ikConstraints; for (var i = 0, n = ikConstraints.length; i < n; i++) if (ikConstraints[i].data.name == constraintName) return ikConstraints[i]; return null; }, /** @return May be null. */ findTransformConstraint: function (constraintName) { var transformConstraints = this.transformConstraints; for (var i = 0, n = transformConstraints.length; i < n; i++) if (transformConstraints[i].data.name == constraintName) return transformConstraints[i]; return null; }, update: function (delta) { this.time += delta; } }; spine.EventData = function (name) { this.name = name; }; spine.EventData.prototype = { intValue: 0, floatValue: 0, stringValue: null }; spine.Event = function (time, data) { this.time = time; this.data = data; }; spine.Event.prototype = { intValue: 0, floatValue: 0, stringValue: null }; spine.AttachmentType = { region: 0, boundingbox: 1, mesh: 2, weightedmesh: 3, linkedmesh: 4, weightedlinkedmesh: 5 }; spine.RegionAttachment = function (name) { this.name = name; this.offset = new spine.Float32Array(8); this.offset.length = 8; this.uvs = new spine.Float32Array(8); this.uvs.length = 8; }; spine.RegionAttachment.prototype = { type: spine.AttachmentType.region, x: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1, width: 0, height: 0, r: 1, g: 1, b: 1, a: 1, path: null, rendererObject: null, regionOffsetX: 0, regionOffsetY: 0, regionWidth: 0, regionHeight: 0, regionOriginalWidth: 0, regionOriginalHeight: 0, setUVs: function (u, v, u2, v2, rotate) { var uvs = this.uvs; if (rotate) { uvs[2/*X2*/] = u; uvs[3/*Y2*/] = v2; uvs[4/*X3*/] = u; uvs[5/*Y3*/] = v; uvs[6/*X4*/] = u2; uvs[7/*Y4*/] = v; uvs[0/*X1*/] = u2; uvs[1/*Y1*/] = v2; } else { uvs[0/*X1*/] = u; uvs[1/*Y1*/] = v2; uvs[2/*X2*/] = u; uvs[3/*Y2*/] = v; uvs[4/*X3*/] = u2; uvs[5/*Y3*/] = v; uvs[6/*X4*/] = u2; uvs[7/*Y4*/] = v2; } }, updateOffset: function () { var regionScaleX = this.width / this.regionOriginalWidth * this.scaleX; var regionScaleY = this.height / this.regionOriginalHeight * this.scaleY; var localX = -this.width / 2 * this.scaleX + this.regionOffsetX * regionScaleX; var localY = -this.height / 2 * this.scaleY + this.regionOffsetY * regionScaleY; var localX2 = localX + this.regionWidth * regionScaleX; var localY2 = localY + this.regionHeight * regionScaleY; var radians = this.rotation * spine.degRad; var cos = Math.cos(radians); var sin = Math.sin(radians); var localXCos = localX * cos + this.x; var localXSin = localX * sin; var localYCos = localY * cos + this.y; var localYSin = localY * sin; var localX2Cos = localX2 * cos + this.x; var localX2Sin = localX2 * sin; var localY2Cos = localY2 * cos + this.y; var localY2Sin = localY2 * sin; var offset = this.offset; offset[0/*X1*/] = localXCos - localYSin; offset[1/*Y1*/] = localYCos + localXSin; offset[2/*X2*/] = localXCos - localY2Sin; offset[3/*Y2*/] = localY2Cos + localXSin; offset[4/*X3*/] = localX2Cos - localY2Sin; offset[5/*Y3*/] = localY2Cos + localX2Sin; offset[6/*X4*/] = localX2Cos - localYSin; offset[7/*Y4*/] = localYCos + localX2Sin; }, computeVertices: function (x, y, bone, vertices) { x += bone.worldX; y += bone.worldY; var m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d; var offset = this.offset; vertices[0/*X1*/] = offset[0/*X1*/] * m00 + offset[1/*Y1*/] * m01 + x; vertices[1/*Y1*/] = offset[0/*X1*/] * m10 + offset[1/*Y1*/] * m11 + y; vertices[2/*X2*/] = offset[2/*X2*/] * m00 + offset[3/*Y2*/] * m01 + x; vertices[3/*Y2*/] = offset[2/*X2*/] * m10 + offset[3/*Y2*/] * m11 + y; vertices[4/*X3*/] = offset[4/*X3*/] * m00 + offset[5/*X3*/] * m01 + x; vertices[5/*X3*/] = offset[4/*X3*/] * m10 + offset[5/*X3*/] * m11 + y; vertices[6/*X4*/] = offset[6/*X4*/] * m00 + offset[7/*Y4*/] * m01 + x; vertices[7/*Y4*/] = offset[6/*X4*/] * m10 + offset[7/*Y4*/] * m11 + y; } }; spine.MeshAttachment = function (name) { this.name = name; }; spine.MeshAttachment.prototype = { type: spine.AttachmentType.mesh, vertices: null, uvs: null, regionUVs: null, triangles: null, hullLength: 0, r: 1, g: 1, b: 1, a: 1, path: null, inheritFFD: true, parentMesh: null, rendererObject: null, regionU: 0, regionV: 0, regionU2: 0, regionV2: 0, regionRotate: false, regionOffsetX: 0, regionOffsetY: 0, regionWidth: 0, regionHeight: 0, regionOriginalWidth: 0, regionOriginalHeight: 0, edges: null, width: 0, height: 0, updateUVs: function () { var width = this.regionU2 - this.regionU, height = this.regionV2 - this.regionV; var n = this.regionUVs.length; if (!this.uvs || this.uvs.length != n) this.uvs = new spine.Float32Array(n); if (this.regionRotate) { for (var i = 0; i < n; i += 2) { this.uvs[i] = this.regionU + this.regionUVs[i + 1] * width; this.uvs[i + 1] = this.regionV + height - this.regionUVs[i] * height; } } else { for (var i = 0; i < n; i += 2) { this.uvs[i] = this.regionU + this.regionUVs[i] * width; this.uvs[i + 1] = this.regionV + this.regionUVs[i + 1] * height; } } }, computeWorldVertices: function (x, y, slot, worldVertices) { var bone = slot.bone; x += bone.worldX; y += bone.worldY; var m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d; var vertices = this.vertices; var verticesCount = vertices.length; if (slot.attachmentVertices.length == verticesCount) vertices = slot.attachmentVertices; for (var i = 0; i < verticesCount; i += 2) { var vx = vertices[i]; var vy = vertices[i + 1]; worldVertices[i] = vx * m00 + vy * m01 + x; worldVertices[i + 1] = vx * m10 + vy * m11 + y; } }, setParentMesh: function (parentMesh) { this.parentMesh = parentMesh; if (parentMesh) { this.vertices = parentMesh.vertices; this.regionUVs = parentMesh.regionUVs; this.triangles = parentMesh.triangles; this.hullLength = parentMesh.hullLength; this.edges = parentMesh.edges; this.width = parentMesh.width; this.height = parentMesh.height; } } }; spine.WeightedMeshAttachment = function (name) { this.name = name; }; spine.WeightedMeshAttachment.prototype = { type: spine.AttachmentType.weightedmesh, bones: null, weights: null, uvs: null, regionUVs: null, triangles: null, hullLength: 0, r: 1, g: 1, b: 1, a: 1, path: null, inheritFFD: true, parentMesh: null, rendererObject: null, regionU: 0, regionV: 0, regionU2: 0, regionV2: 0, regionRotate: false, regionOffsetX: 0, regionOffsetY: 0, regionWidth: 0, regionHeight: 0, regionOriginalWidth: 0, regionOriginalHeight: 0, edges: null, width: 0, height: 0, updateUVs: function (u, v, u2, v2, rotate) { var width = this.regionU2 - this.regionU, height = this.regionV2 - this.regionV; var n = this.regionUVs.length; if (!this.uvs || this.uvs.length != n) this.uvs = new spine.Float32Array(n); if (this.regionRotate) { for (var i = 0; i < n; i += 2) { this.uvs[i] = this.regionU + this.regionUVs[i + 1] * width; this.uvs[i + 1] = this.regionV + height - this.regionUVs[i] * height; } } else { for (var i = 0; i < n; i += 2) { this.uvs[i] = this.regionU + this.regionUVs[i] * width; this.uvs[i + 1] = this.regionV + this.regionUVs[i + 1] * height; } } }, computeWorldVertices: function (x, y, slot, worldVertices) { var skeletonBones = slot.bone.skeleton.bones; var weights = this.weights; var bones = this.bones; var w = 0, v = 0, b = 0, f = 0, n = bones.length, nn; var wx, wy, bone, vx, vy, weight; if (!slot.attachmentVertices.length) { for (; v < n; w += 2) { wx = 0; wy = 0; nn = bones[v++] + v; for (; v < nn; v++, b += 3) { bone = skeletonBones[bones[v]]; vx = weights[b]; vy = weights[b + 1]; weight = weights[b + 2]; wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight; wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight; } worldVertices[w] = wx + x; worldVertices[w + 1] = wy + y; } } else { var ffd = slot.attachmentVertices; for (; v < n; w += 2) { wx = 0; wy = 0; nn = bones[v++] + v; for (; v < nn; v++, b += 3, f += 2) { bone = skeletonBones[bones[v]]; vx = weights[b] + ffd[f]; vy = weights[b + 1] + ffd[f + 1]; weight = weights[b + 2]; wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight; wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight; } worldVertices[w] = wx + x; worldVertices[w + 1] = wy + y; } } }, setParentMesh: function (parentMesh) { this.parentMesh = parentMesh; if (parentMesh) { this.bones = parentMesh.bones; this.weights = parentMesh.weights; this.regionUVs = parentMesh.regionUVs; this.triangles = parentMesh.triangles; this.hullLength = parentMesh.hullLength; this.edges = parentMesh.edges; this.width = parentMesh.width; this.height = parentMesh.height; } } }; spine.BoundingBoxAttachment = function (name) { this.name = name; this.vertices = new spine.Float32Array(); }; spine.BoundingBoxAttachment.prototype = { type: spine.AttachmentType.boundingbox, computeWorldVertices: function (x, y, bone, worldVertices) { x += bone.worldX; y += bone.worldY; var m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d; var vertices = this.vertices; for (var i = 0, n = vertices.length; i < n; i += 2) { var px = vertices[i]; var py = vertices[i + 1]; worldVertices[i] = px * m00 + py * m01 + x; worldVertices[i + 1] = px * m10 + py * m11 + y; } } }; spine.AnimationStateData = function (skeletonData) { this.skeletonData = skeletonData; this.animationToMixTime = {}; }; spine.AnimationStateData.prototype = { defaultMix: 0, setMixByName: function (fromName, toName, duration) { var from = this.skeletonData.findAnimation(fromName); if (!from) throw "Animation not found: " + fromName; var to = this.skeletonData.findAnimation(toName); if (!to) throw "Animation not found: " + toName; this.setMix(from, to, duration); }, setMix: function (from, to, duration) { this.animationToMixTime[from.name + ":" + to.name] = duration; }, getMix: function (from, to) { var key = from.name + ":" + to.name; return this.animationToMixTime.hasOwnProperty(key) ? this.animationToMixTime[key] : this.defaultMix; } }; spine.TrackEntry = function () {}; spine.TrackEntry.prototype = { next: null, previous: null, animation: null, loop: false, delay: 0, time: 0, lastTime: -1, endTime: 0, timeScale: 1, mixTime: 0, mixDuration: 0, mix: 1, onStart: null, onEnd: null, onComplete: null, onEvent: null }; spine.AnimationState = function (stateData) { this.data = stateData; this.tracks = []; this.events = []; }; spine.AnimationState.prototype = { onStart: null, onEnd: null, onComplete: null, onEvent: null, timeScale: 1, update: function (delta) { delta *= this.timeScale; for (var i = 0; i < this.tracks.length; i++) { var current = this.tracks[i]; if (!current) continue; current.time += delta * current.timeScale; if (current.previous) { var previousDelta = delta * current.previous.timeScale; current.previous.time += previousDelta; current.mixTime += previousDelta; } var next = current.next; if (next) { next.time = current.lastTime - next.delay; if (next.time >= 0) this.setCurrent(i, next); } else { // End non-looping animation when it reaches its end time and there is no next entry. if (!current.loop && current.lastTime >= current.endTime) this.clearTrack(i); } } }, apply: function (skeleton) { for (var i = 0; i < this.tracks.length; i++) { var current = this.tracks[i]; if (!current) continue; this.events.length = 0; var time = current.time; var lastTime = current.lastTime; var endTime = current.endTime; var loop = current.loop; if (!loop && time > endTime) time = endTime; var previous = current.previous; if (!previous) { if (current.mix == 1) current.animation.apply(skeleton, current.lastTime, time, loop, this.events); else current.animation.mix(skeleton, current.lastTime, time, loop, this.events, current.mix); } else { var previousTime = previous.time; if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; previous.animation.apply(skeleton, previousTime, previousTime, previous.loop, null); var alpha = current.mixTime / current.mixDuration * current.mix; if (alpha >= 1) { alpha = 1; current.previous = null; } current.animation.mix(skeleton, current.lastTime, time, loop, this.events, alpha); } for (var ii = 0, nn = this.events.length; ii < nn; ii++) { var event = this.events[ii]; if (current.onEvent) current.onEvent(i, event); if (this.onEvent) this.onEvent(i, event); } // Check if completed the animation or a loop iteration. if (loop ? (lastTime % endTime > time % endTime) : (lastTime < endTime && time >= endTime)) { var count = Math.floor(time / endTime); if (current.onComplete) current.onComplete(i, count); if (this.onComplete) this.onComplete(i, count); } current.lastTime = current.time; } }, clearTracks: function () { for (var i = 0, n = this.tracks.length; i < n; i++) this.clearTrack(i); this.tracks.length = 0; }, clearTrack: function (trackIndex) { if (trackIndex >= this.tracks.length) return; var current = this.tracks[trackIndex]; if (!current) return; if (current.onEnd) current.onEnd(trackIndex); if (this.onEnd) this.onEnd(trackIndex); this.tracks[trackIndex] = null; }, _expandToIndex: function (index) { if (index < this.tracks.length) return this.tracks[index]; while (index >= this.tracks.length) this.tracks[this.tracks.length] = null; return null; }, setCurrent: function (index, entry) { var current = this._expandToIndex(index); if (current) { var previous = current.previous; current.previous = null; if (current.onEnd) current.onEnd(index); if (this.onEnd) this.onEnd(index); entry.mixDuration = this.data.getMix(current.animation, entry.animation); if (entry.mixDuration > 0) { entry.mixTime = 0; // If a mix is in progress, mix from the closest animation. if (previous && current.mixTime / current.mixDuration < 0.5) entry.previous = previous; else entry.previous = current; } } this.tracks[index] = entry; if (entry.onStart) entry.onStart(index); if (this.onStart) this.onStart(index); }, setAnimationByName: function (trackIndex, animationName, loop) { var animation = this.data.skeletonData.findAnimation(animationName); if (!animation) throw "Animation not found: " + animationName; return this.setAnimation(trackIndex, animation, loop); }, /** Set the current animation. Any queued animations are cleared. */ setAnimation: function (trackIndex, animation, loop) { var entry = new spine.TrackEntry(); entry.animation = animation; entry.loop = loop; entry.endTime = animation.duration; this.setCurrent(trackIndex, entry); return entry; }, addAnimationByName: function (trackIndex, animationName, loop, delay) { var animation = this.data.skeletonData.findAnimation(animationName); if (!animation) throw "Animation not found: " + animationName; return this.addAnimation(trackIndex, animation, loop, delay); }, /** Adds an animation to be played delay seconds after the current or last queued animation. * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */ addAnimation: function (trackIndex, animation, loop, delay) { var entry = new spine.TrackEntry(); entry.animation = animation; entry.loop = loop; entry.endTime = animation.duration; var last = this._expandToIndex(trackIndex); if (last) { while (last.next) last = last.next; last.next = entry; } else this.tracks[trackIndex] = entry; if (delay <= 0) { if (last) delay += last.endTime - this.data.getMix(last.animation, animation); else delay = 0; } entry.delay = delay; return entry; }, /** May be null. */ getCurrent: function (trackIndex) { if (trackIndex >= this.tracks.length) return null; return this.tracks[trackIndex]; } }; spine.SkeletonJson = function (attachmentLoader) { this.attachmentLoader = attachmentLoader; this.linkedMeshes = []; }; spine.SkeletonJson.prototype = { scale: 1, readSkeletonData: function (root, name) { var skeletonData = new spine.SkeletonData(); skeletonData.name = name; // Skeleton. var skeletonMap = root["skeleton"]; if (skeletonMap) { skeletonData.hash = skeletonMap["hash"]; skeletonData.version = skeletonMap["spine"]; skeletonData.width = skeletonMap["width"] || 0; skeletonData.height = skeletonMap["height"] || 0; } // Bones. var bones = root["bones"]; for (var i = 0, n = bones.length; i < n; i++) { var boneMap = bones[i]; var parent = null; if (boneMap["parent"]) { parent = skeletonData.findBone(boneMap["parent"]); if (!parent) throw "Parent bone not found: " + boneMap["parent"]; } var boneData = new spine.BoneData(boneMap["name"], parent); boneData.length = (boneMap["length"] || 0) * this.scale; boneData.x = (boneMap["x"] || 0) * this.scale; boneData.y = (boneMap["y"] || 0) * this.scale; boneData.rotation = (boneMap["rotation"] || 0); boneData.scaleX = boneMap.hasOwnProperty("scaleX") ? boneMap["scaleX"] : 1; boneData.scaleY = boneMap.hasOwnProperty("scaleY") ? boneMap["scaleY"] : 1; boneData.inheritScale = boneMap.hasOwnProperty("inheritScale") ? boneMap["inheritScale"] : true; boneData.inheritRotation = boneMap.hasOwnProperty("inheritRotation") ? boneMap["inheritRotation"] : true; skeletonData.bones[i] = boneData; } // IK constraints. var ik = root["ik"]; if (ik) { for (var i = 0, n = ik.length; i < n; i++) { var ikMap = ik[i]; var ikConstraintData = new spine.IkConstraintData(ikMap["name"]); var bones = ikMap["bones"]; for (var ii = 0, nn = bones.length; ii < nn; ii++) { var bone = skeletonData.findBone(bones[ii]); if (!bone) throw "IK bone not found: " + bones[ii]; ikConstraintData.bones[ii] = bone; } ikConstraintData.target = skeletonData.findBone(ikMap["target"]); if (!ikConstraintData.target) throw "Target bone not found: " + ikMap["target"]; ikConstraintData.bendDirection = (!ikMap.hasOwnProperty("bendPositive") || ikMap["bendPositive"]) ? 1 : -1; ikConstraintData.mix = ikMap.hasOwnProperty("mix") ? ikMap["mix"] : 1; skeletonData.ikConstraints[i] = ikConstraintData; } } // Transform constraints. var transform = root["transform"]; if (transform) { for (var i = 0, n = transform.length; i < n; i++) { var transformMap = transform[i]; var transformConstraintData = new spine.TransformConstraintData(transformMap["name"]); transformConstraintData.bone = skeletonData.findBone(transformMap["bone"]); if (!transformConstraintData.bone) throw "Bone not found: " + transformMap["bone"]; transformConstraintData.target = skeletonData.findBone(transformMap["target"]); if (!transformConstraintData.target) throw "Target bone not found: " + transformMap["target"]; transformConstraintData.mix = transformMap.hasOwnProperty("translateMix") ? ikMap["translateMix"] : 1; transformConstraintData.x = (transformMap["x"] || 0) * this.scale; transformConstraintData.y = (transformMap["y"] || 0) * this.scale; skeletonData.transformConstraints[i] = transformConstraintData; } } // Slots. var slots = root["slots"]; for (var i = 0, n = slots.length; i < n; i++) { var slotMap = slots[i]; var boneData = skeletonData.findBone(slotMap["bone"]); if (!boneData) throw "Slot bone not found: " + slotMap["bone"]; var slotData = new spine.SlotData(slotMap["name"], boneData); var color = slotMap["color"]; if (color) { slotData.r = this.toColor(color, 0); slotData.g = this.toColor(color, 1); slotData.b = this.toColor(color, 2); slotData.a = this.toColor(color, 3); } slotData.attachmentName = slotMap["attachment"]; slotData.blendMode = spine.BlendMode[slotMap["blend"] || "normal"]; skeletonData.slots[i] = slotData; } // Skins. var skins = root["skins"]; for (var skinName in skins) { if (!skins.hasOwnProperty(skinName)) continue; var skinMap = skins[skinName]; var skin = new spine.Skin(skinName); for (var slotName in skinMap) { if (!skinMap.hasOwnProperty(slotName)) continue; var slotIndex = skeletonData.findSlotIndex(slotName); var slotEntry = skinMap[slotName]; for (var attachmentName in slotEntry) { if (!slotEntry.hasOwnProperty(attachmentName)) continue; var attachment = this.readAttachment(skin, slotIndex, attachmentName, slotEntry[attachmentName]); if (attachment) skin.addAttachment(slotIndex, attachmentName, attachment); } } skeletonData.skins[skeletonData.skins.length] = skin; if (skin.name == "default") skeletonData.defaultSkin = skin; } // Linked meshes. for (var i = 0, n = this.linkedMeshes.length; i < n; i++) { var linkedMesh = this.linkedMeshes[i]; var skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin); if (!skin) throw "Skin not found: " + linkedMesh.skin; var parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent); if (!parent) throw "Parent mesh not found: " + linkedMesh.parent; linkedMesh.mesh.setParentMesh(parent); linkedMesh.mesh.updateUVs(); } this.linkedMeshes.length = 0; // Events. var events = root["events"]; for (var eventName in events) { if (!events.hasOwnProperty(eventName)) continue; var eventMap = events[eventName]; var eventData = new spine.EventData(eventName); eventData.intValue = eventMap["int"] || 0; eventData.floatValue = eventMap["float"] || 0; eventData.stringValue = eventMap["string"] || null; skeletonData.events[skeletonData.events.length] = eventData; } // Animations. var animations = root["animations"]; for (var animationName in animations) { if (!animations.hasOwnProperty(animationName)) continue; this.readAnimation(animationName, animations[animationName], skeletonData); } return skeletonData; }, readAttachment: function (skin, slotIndex, name, map) { name = map["name"] || name; var type = map["type"] || "region"; if (type == "skinnedmesh") type = "weightedmesh"; type = spine.AttachmentType[type]; var path = map["path"] || name; var scale = this.scale; switch (type) { case spine.AttachmentType.region: var region = this.attachmentLoader.newRegionAttachment(skin, name, path); if (!region) return null; region.path = path; region.x = (map["x"] || 0) * scale; region.y = (map["y"] || 0) * scale; region.scaleX = map.hasOwnProperty("scaleX") ? map["scaleX"] : 1; region.scaleY = map.hasOwnProperty("scaleY") ? map["scaleY"] : 1; region.rotation = map["rotation"] || 0; region.width = (map["width"] || 0) * scale; region.height = (map["height"] || 0) * scale; var color = map["color"]; if (color) { region.r = this.toColor(color, 0); region.g = this.toColor(color, 1); region.b = this.toColor(color, 2); region.a = this.toColor(color, 3); } region.updateOffset(); return region; case spine.AttachmentType.mesh: case spine.AttachmentType.linkedmesh: var mesh = this.attachmentLoader.newMeshAttachment(skin, name, path); if (!mesh) return null; mesh.path = path; color = map["color"]; if (color) { mesh.r = this.toColor(color, 0); mesh.g = this.toColor(color, 1); mesh.b = this.toColor(color, 2); mesh.a = this.toColor(color, 3); } mesh.width = (map["width"] || 0) * scale; mesh.height = (map["height"] || 0) * scale; if (!map["parent"]) { mesh.vertices = this.getFloatArray(map, "vertices", scale); mesh.triangles = this.getUint32Array(map, "triangles"); mesh.regionUVs = this.getFloatArray(map, "uvs", 1); mesh.updateUVs(); mesh.hullLength = (map["hull"] || 0) * 2; if (map["edges"]) mesh.edges = this.getUint16Array(map, "edges"); } else { mesh.inheritFFD = map.hasOwnProperty("ffd") ? map["ffd"] : true; this.linkedMeshes[this.linkedMeshes.length] = {mesh: mesh, skin: map["skin"], slotIndex: slotIndex, parent: map["parent"]}; } return mesh; case spine.AttachmentType.weightedmesh: case spine.AttachmentType.weightedlinkedmesh: var mesh = this.attachmentLoader.newWeightedMeshAttachment(skin, name, path); if (!mesh) return null; mesh.path = path; color = map["color"]; if (color) { mesh.r = this.toColor(color, 0); mesh.g = this.toColor(color, 1); mesh.b = this.toColor(color, 2); mesh.a = this.toColor(color, 3); } mesh.width = (map["width"] || 0) * scale; mesh.height = (map["height"] || 0) * scale; if (!map["parent"]) { var uvs = this.getFloatArray(map, "uvs", 1); var vertices = this.getFloatArray(map, "vertices", 1); var weights = new spine.Float32Array(uvs.length * 3 * 3); var bones = new spine.Uint32Array(uvs.length * 3); for (var i = 0, b = 0, w = 0, n = vertices.length; i < n; ) { var boneCount = vertices[i++] | 0; bones[b++] = boneCount; for (var nn = i + boneCount * 4; i < nn; ) { bones[b++] = vertices[i]; weights[w++] = vertices[i + 1] * scale; weights[w++] = vertices[i + 2] * scale; weights[w++] = vertices[i + 3]; i += 4; } } mesh.bones = bones; mesh.weights = weights; mesh.triangles = this.getUint32Array(map, "triangles"); mesh.regionUVs = uvs; mesh.updateUVs(); mesh.hullLength = (map["hull"] || 0) * 2; if (map["edges"]) mesh.edges = this.getUint16Array(map, "edges"); } else { mesh.inheritFFD = map.hasOwnProperty("ffd") ? map["ffd"] : true; this.linkedMeshes[this.linkedMeshes.length] = {mesh: mesh, skin: map["skin"], slotIndex: slotIndex, parent: map["parent"]}; } return mesh; case spine.AttachmentType.boundingbox: var attachment = this.attachmentLoader.newBoundingBoxAttachment(skin, name); var vertices = map["vertices"]; for (var i = 0, n = vertices.length; i < n; i++) attachment.vertices[i] = vertices[i] * scale; return attachment; } throw "Unknown attachment type: " + type; }, readAnimation: function (name, map, skeletonData) { var timelines = []; var duration = 0; var slots = map["slots"]; for (var slotName in slots) { if (!slots.hasOwnProperty(slotName)) continue; var slotMap = slots[slotName]; var slotIndex = skeletonData.findSlotIndex(slotName); for (var timelineName in slotMap) { if (!slotMap.hasOwnProperty(timelineName)) continue; var values = slotMap[timelineName]; if (timelineName == "color") { var timeline = new spine.ColorTimeline(values.length); timeline.slotIndex = slotIndex; var frameIndex = 0; for (var i = 0, n = values.length; i < n; i++) { var valueMap = values[i]; var color = valueMap["color"]; var r = this.toColor(color, 0); var g = this.toColor(color, 1); var b = this.toColor(color, 2); var a = this.toColor(color, 3); timeline.setFrame(frameIndex, valueMap["time"], r, g, b, a); this.readCurve(timeline, frameIndex, valueMap); frameIndex++; } timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 5 - 5]); } else if (timelineName == "attachment") { var timeline = new spine.AttachmentTimeline(values.length); timeline.slotIndex = slotIndex; var frameIndex = 0; for (var i = 0, n = values.length; i < n; i++) { var valueMap = values[i]; timeline.setFrame(frameIndex++, valueMap["time"], valueMap["name"]); } timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); } else throw "Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"; } } var bones = map["bones"]; for (var boneName in bones) { if (!bones.hasOwnProperty(boneName)) continue; var boneIndex = skeletonData.findBoneIndex(boneName); if (boneIndex == -1) throw "Bone not found: " + boneName; var boneMap = bones[boneName]; for (var timelineName in boneMap) { if (!boneMap.hasOwnProperty(timelineName)) continue; var values = boneMap[timelineName]; if (timelineName == "rotate") { var timeline = new spine.RotateTimeline(values.length); timeline.boneIndex = boneIndex; var frameIndex = 0; for (var i = 0, n = values.length; i < n; i++) { var valueMap = values[i]; timeline.setFrame(frameIndex, valueMap["time"], valueMap["angle"]); this.readCurve(timeline, frameIndex, valueMap); frameIndex++; } timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 2 - 2]); } else if (timelineName == "translate" || timelineName == "scale") { var timeline; var timelineScale = 1; if (timelineName == "scale") timeline = new spine.ScaleTimeline(values.length); else { timeline = new spine.TranslateTimeline(values.length); timelineScale = this.scale; } timeline.boneIndex = boneIndex; var frameIndex = 0; for (var i = 0, n = values.length; i < n; i++) { var valueMap = values[i]; var x = (valueMap["x"] || 0) * timelineScale; var y = (valueMap["y"] || 0) * timelineScale; timeline.setFrame(frameIndex, valueMap["time"], x, y); this.readCurve(timeline, frameIndex, valueMap); frameIndex++; } timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 3 - 3]); } else throw "Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"; } } var ikMap = map["ik"]; for (var ikConstraintName in ikMap) { if (!ikMap.hasOwnProperty(ikConstraintName)) continue; var ikConstraint = skeletonData.findIkConstraint(ikConstraintName); var values = ikMap[ikConstraintName]; var timeline = new spine.IkConstraintTimeline(values.length); timeline.ikConstraintIndex = skeletonData.ikConstraints.indexOf(ikConstraint); var frameIndex = 0; for (var i = 0, n = values.length; i < n; i++) { var valueMap = values[i]; var mix = valueMap.hasOwnProperty("mix") ? valueMap["mix"] : 1; var bendDirection = (!valueMap.hasOwnProperty("bendPositive") || valueMap["bendPositive"]) ? 1 : -1; timeline.setFrame(frameIndex, valueMap["time"], mix, bendDirection); this.readCurve(timeline, frameIndex, valueMap); frameIndex++; } timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 3 - 3]); } var ffd = map["ffd"]; for (var skinName in ffd) { var skin = skeletonData.findSkin(skinName); var slotMap = ffd[skinName]; for (slotName in slotMap) { var slotIndex = skeletonData.findSlotIndex(slotName); var meshMap = slotMap[slotName]; for (var meshName in meshMap) { var values = meshMap[meshName]; var timeline = new spine.FfdTimeline(values.length); var attachment = skin.getAttachment(slotIndex, meshName); if (!attachment) throw "FFD attachment not found: " + meshName; timeline.slotIndex = slotIndex; timeline.attachment = attachment; var isMesh = attachment.type == spine.AttachmentType.mesh; var vertexCount; if (isMesh) vertexCount = attachment.vertices.length; else vertexCount = attachment.weights.length / 3 * 2; var frameIndex = 0; for (var i = 0, n = values.length; i < n; i++) { var valueMap = values[i]; var vertices; if (!valueMap["vertices"]) { if (isMesh) vertices = attachment.vertices; else { vertices = new spine.Float32Array(vertexCount); vertices.length = vertexCount; } } else { var verticesValue = valueMap["vertices"]; var vertices = new spine.Float32Array(vertexCount); vertices.length = vertexCount; var start = valueMap["offset"] || 0; var nn = verticesValue.length; if (this.scale == 1) { for (var ii = 0; ii < nn; ii++) vertices[ii + start] = verticesValue[ii]; } else { for (var ii = 0; ii < nn; ii++) vertices[ii + start] = verticesValue[ii] * this.scale; } if (isMesh) { var meshVertices = attachment.vertices; for (var ii = 0, nn = vertices.length; ii < nn; ii++) vertices[ii] += meshVertices[ii]; } } timeline.setFrame(frameIndex, valueMap["time"], vertices); this.readCurve(timeline, frameIndex, valueMap); frameIndex++; } timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); } } } var drawOrderValues = map["drawOrder"]; if (!drawOrderValues) drawOrderValues = map["draworder"]; if (drawOrderValues) { var timeline = new spine.DrawOrderTimeline(drawOrderValues.length); var slotCount = skeletonData.slots.length; var frameIndex = 0; for (var i = 0, n = drawOrderValues.length; i < n; i++) { var drawOrderMap = drawOrderValues[i]; var drawOrder = null; if (drawOrderMap["offsets"]) { drawOrder = new spine.Uint32Array(slotCount); drawOrder.length = slotCount; for (var ii = slotCount - 1; ii >= 0; ii--) drawOrder[ii] = 4294967295; var offsets = drawOrderMap["offsets"]; var unchanged = new spine.Uint32Array(slotCount - offsets.length); unchanged.length = slotCount - offsets.length; var originalIndex = 0, unchangedIndex = 0; for (var ii = 0, nn = offsets.length; ii < nn; ii++) { var offsetMap = offsets[ii]; var slotIndex = skeletonData.findSlotIndex(offsetMap["slot"]); if (slotIndex == -1) throw "Slot not found: " + offsetMap["slot"]; // Collect unchanged items. while (originalIndex != slotIndex) unchanged[unchangedIndex++] = originalIndex++; // Set changed items. drawOrder[originalIndex + offsetMap["offset"]] = originalIndex++; } // Collect remaining unchanged items. while (originalIndex < slotCount) unchanged[unchangedIndex++] = originalIndex++; // Fill in unchanged items. for (var ii = slotCount - 1; ii >= 0; ii--) if (drawOrder[ii] == 4294967295) drawOrder[ii] = unchanged[--unchangedIndex]; } timeline.setFrame(frameIndex++, drawOrderMap["time"], drawOrder); } timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); } var events = map["events"]; if (events) { var timeline = new spine.EventTimeline(events.length); var frameIndex = 0; for (var i = 0, n = events.length; i < n; i++) { var eventMap = events[i]; var eventData = skeletonData.findEvent(eventMap["name"]); if (!eventData) throw "Event not found: " + eventMap["name"]; var event = new spine.Event(eventMap["time"], eventData); event.intValue = eventMap.hasOwnProperty("int") ? eventMap["int"] : eventData.intValue; event.floatValue = eventMap.hasOwnProperty("float") ? eventMap["float"] : eventData.floatValue; event.stringValue = eventMap.hasOwnProperty("string") ? eventMap["string"] : eventData.stringValue; timeline.setFrame(frameIndex++, event); } timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); } skeletonData.animations[skeletonData.animations.length] = new spine.Animation(name, timelines, duration); }, readCurve: function (timeline, frameIndex, valueMap) { var curve = valueMap["curve"]; if (!curve) timeline.curves.setLinear(frameIndex); else if (curve == "stepped") timeline.curves.setStepped(frameIndex); else if (curve instanceof Array) timeline.curves.setCurve(frameIndex, curve[0], curve[1], curve[2], curve[3]); }, toColor: function (hexString, colorIndex) { if (hexString.length != 8) throw "Color hexidecimal length must be 8, recieved: " + hexString; return parseInt(hexString.substring(colorIndex * 2, (colorIndex * 2) + 2), 16) / 255; }, getFloatArray: function (map, name, scale) { var list = map[name]; var values = new spine.Float32Array(list.length); var i = 0, n = list.length; if (scale == 1) { for (; i < n; i++) values[i] = list[i]; } else { for (; i < n; i++) values[i] = list[i] * scale; } return values; }, getUint32Array: function (map, name) { var list = map[name]; var values = new spine.Uint32Array(list.length); for (var i = 0, n = list.length; i < n; i++) values[i] = list[i] | 0; return values; }, getUint16Array: function (map, name) { var list = map[name]; var values = new spine.Uint16Array(list.length); for (var i = 0, n = list.length; i < n; i++) values[i] = list[i] | 0; return values; } }; spine.Atlas = function (atlasText, textureLoader) { this.textureLoader = textureLoader; this.pages = []; this.regions = []; var reader = new spine.AtlasReader(atlasText); var tuple = []; tuple.length = 4; var page = null; while (true) { var line = reader.readLine(); if (line === null) break; line = reader.trim(line); if (!line.length) page = null; else if (!page) { page = new spine.AtlasPage(); page.name = line; if (reader.readTuple(tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. page.width = parseInt(tuple[0]); page.height = parseInt(tuple[1]); reader.readTuple(tuple); } page.format = spine.Atlas.Format[tuple[0]]; reader.readTuple(tuple); page.minFilter = spine.Atlas.TextureFilter[tuple[0]]; page.magFilter = spine.Atlas.TextureFilter[tuple[1]]; var direction = reader.readValue(); page.uWrap = spine.Atlas.TextureWrap.clampToEdge; page.vWrap = spine.Atlas.TextureWrap.clampToEdge; if (direction == "x") page.uWrap = spine.Atlas.TextureWrap.repeat; else if (direction == "y") page.vWrap = spine.Atlas.TextureWrap.repeat; else if (direction == "xy") page.uWrap = page.vWrap = spine.Atlas.TextureWrap.repeat; textureLoader.load(page, line, this); this.pages[this.pages.length] = page; } else { var region = new spine.AtlasRegion(); region.name = line; region.page = page; region.rotate = reader.readValue() == "true"; reader.readTuple(tuple); var x = parseInt(tuple[0]); var y = parseInt(tuple[1]); reader.readTuple(tuple); var width = parseInt(tuple[0]); var height = parseInt(tuple[1]); region.u = x / page.width; region.v = y / page.height; if (region.rotate) { region.u2 = (x + height) / page.width; region.v2 = (y + width) / page.height; } else { region.u2 = (x + width) / page.width; region.v2 = (y + height) / page.height; } region.x = x; region.y = y; region.width = Math.abs(width); region.height = Math.abs(height); if (reader.readTuple(tuple) == 4) { // split is optional region.splits = [parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])]; if (reader.readTuple(tuple) == 4) { // pad is optional, but only present with splits region.pads = [parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])]; reader.readTuple(tuple); } } region.originalWidth = parseInt(tuple[0]); region.originalHeight = parseInt(tuple[1]); reader.readTuple(tuple); region.offsetX = parseInt(tuple[0]); region.offsetY = parseInt(tuple[1]); region.index = parseInt(reader.readValue()); this.regions[this.regions.length] = region; } } }; spine.Atlas.prototype = { findRegion: function (name) { var regions = this.regions; for (var i = 0, n = regions.length; i < n; i++) if (regions[i].name == name) return regions[i]; return null; }, dispose: function () { var pages = this.pages; for (var i = 0, n = pages.length; i < n; i++) this.textureLoader.unload(pages[i].rendererObject); }, updateUVs: function (page) { var regions = this.regions; for (var i = 0, n = regions.length; i < n; i++) { var region = regions[i]; if (region.page != page) continue; region.u = region.x / page.width; region.v = region.y / page.height; if (region.rotate) { region.u2 = (region.x + region.height) / page.width; region.v2 = (region.y + region.width) / page.height; } else { region.u2 = (region.x + region.width) / page.width; region.v2 = (region.y + region.height) / page.height; } } } }; spine.Atlas.Format = { alpha: 0, intensity: 1, luminanceAlpha: 2, rgb565: 3, rgba4444: 4, rgb888: 5, rgba8888: 6 }; spine.Atlas.TextureFilter = { nearest: 0, linear: 1, mipMap: 2, mipMapNearestNearest: 3, mipMapLinearNearest: 4, mipMapNearestLinear: 5, mipMapLinearLinear: 6 }; spine.Atlas.TextureWrap = { mirroredRepeat: 0, clampToEdge: 1, repeat: 2 }; spine.AtlasPage = function () {}; spine.AtlasPage.prototype = { name: null, format: null, minFilter: null, magFilter: null, uWrap: null, vWrap: null, rendererObject: null, width: 0, height: 0 }; spine.AtlasRegion = function () {}; spine.AtlasRegion.prototype = { page: null, name: null, x: 0, y: 0, width: 0, height: 0, u: 0, v: 0, u2: 0, v2: 0, offsetX: 0, offsetY: 0, originalWidth: 0, originalHeight: 0, index: 0, rotate: false, splits: null, pads: null }; spine.AtlasReader = function (text) { this.lines = text.split(/\r\n|\r|\n/); }; spine.AtlasReader.prototype = { index: 0, trim: function (value) { return value.replace(/^\s+|\s+$/g, ""); }, readLine: function () { if (this.index >= this.lines.length) return null; return this.lines[this.index++]; }, readValue: function () { var line = this.readLine(); var colon = line.indexOf(":"); if (colon == -1) throw "Invalid line: " + line; return this.trim(line.substring(colon + 1)); }, /** Returns the number of tuple values read (1, 2 or 4). */ readTuple: function (tuple) { var line = this.readLine(); var colon = line.indexOf(":"); if (colon == -1) throw "Invalid line: " + line; var i = 0, lastMatch = colon + 1; for (; i < 3; i++) { var comma = line.indexOf(",", lastMatch); if (comma == -1) break; tuple[i] = this.trim(line.substr(lastMatch, comma - lastMatch)); lastMatch = comma + 1; } tuple[i] = this.trim(line.substring(lastMatch)); return i + 1; } }; spine.AtlasAttachmentLoader = function (atlas) { this.atlas = atlas; }; spine.AtlasAttachmentLoader.prototype = { newRegionAttachment: function (skin, name, path) { var region = this.atlas.findRegion(path); if (!region) throw "Region not found in atlas: " + path + " (region attachment: " + name + ")"; var attachment = new spine.RegionAttachment(name); attachment.rendererObject = region; attachment.setUVs(region.u, region.v, region.u2, region.v2, region.rotate); attachment.regionOffsetX = region.offsetX; attachment.regionOffsetY = region.offsetY; attachment.regionWidth = region.width; attachment.regionHeight = region.height; attachment.regionOriginalWidth = region.originalWidth; attachment.regionOriginalHeight = region.originalHeight; return attachment; }, newMeshAttachment: function (skin, name, path) { var region = this.atlas.findRegion(path); if (!region) throw "Region not found in atlas: " + path + " (mesh attachment: " + name + ")"; var attachment = new spine.MeshAttachment(name); attachment.rendererObject = region; attachment.regionU = region.u; attachment.regionV = region.v; attachment.regionU2 = region.u2; attachment.regionV2 = region.v2; attachment.regionRotate = region.rotate; attachment.regionOffsetX = region.offsetX; attachment.regionOffsetY = region.offsetY; attachment.regionWidth = region.width; attachment.regionHeight = region.height; attachment.regionOriginalWidth = region.originalWidth; attachment.regionOriginalHeight = region.originalHeight; return attachment; }, newWeightedMeshAttachment: function (skin, name, path) { var region = this.atlas.findRegion(path); if (!region) throw "Region not found in atlas: " + path + " (weighted mesh attachment: " + name + ")"; var attachment = new spine.WeightedMeshAttachment(name); attachment.rendererObject = region; attachment.regionU = region.u; attachment.regionV = region.v; attachment.regionU2 = region.u2; attachment.regionV2 = region.v2; attachment.regionRotate = region.rotate; attachment.regionOffsetX = region.offsetX; attachment.regionOffsetY = region.offsetY; attachment.regionWidth = region.width; attachment.regionHeight = region.height; attachment.regionOriginalWidth = region.originalWidth; attachment.regionOriginalHeight = region.originalHeight; return attachment; }, newBoundingBoxAttachment: function (skin, name) { return new spine.BoundingBoxAttachment(name); } }; spine.SkeletonBounds = function () { this.polygonPool = []; this.polygons = []; this.boundingBoxes = []; }; spine.SkeletonBounds.prototype = { minX: 0, minY: 0, maxX: 0, maxY: 0, update: function (skeleton, updateAabb) { var slots = skeleton.slots; var slotCount = slots.length; var x = skeleton.x, y = skeleton.y; var boundingBoxes = this.boundingBoxes; var polygonPool = this.polygonPool; var polygons = this.polygons; boundingBoxes.length = 0; for (var i = 0, n = polygons.length; i < n; i++) polygonPool[polygonPool.length] = polygons[i]; polygons.length = 0; for (var i = 0; i < slotCount; i++) { var slot = slots[i]; var boundingBox = slot.attachment; if (boundingBox.type != spine.AttachmentType.boundingbox) continue; boundingBoxes[boundingBoxes.length] = boundingBox; var poolCount = polygonPool.length, polygon; if (poolCount > 0) { polygon = polygonPool[poolCount - 1]; polygonPool.splice(poolCount - 1, 1); } else polygon = new spine.Float32Array(); polygons[polygons.length] = polygon; polygon.length = boundingBox.vertices.length; boundingBox.computeWorldVertices(x, y, slot.bone, polygon); } if (updateAabb) this.aabbCompute(); }, aabbCompute: function () { var polygons = this.polygons; var minX = Number.MAX_VALUE, minY = Number.MAX_VALUE, maxX = -Number.MAX_VALUE, maxY = -Number.MAX_VALUE; for (var i = 0, n = polygons.length; i < n; i++) { var vertices = polygons[i]; for (var ii = 0, nn = vertices.length; ii < nn; ii += 2) { var x = vertices[ii]; var y = vertices[ii + 1]; minX = Math.min(minX, x); minY = Math.min(minY, y); maxX = Math.max(maxX, x); maxY = Math.max(maxY, y); } } this.minX = minX; this.minY = minY; this.maxX = maxX; this.maxY = maxY; }, /** Returns true if the axis aligned bounding box contains the point. */ aabbContainsPoint: function (x, y) { return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY; }, /** Returns true if the axis aligned bounding box intersects the line segment. */ aabbIntersectsSegment: function (x1, y1, x2, y2) { var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY)) return false; var m = (y2 - y1) / (x2 - x1); var y = m * (minX - x1) + y1; if (y > minY && y < maxY) return true; y = m * (maxX - x1) + y1; if (y > minY && y < maxY) return true; var x = (minY - y1) / m + x1; if (x > minX && x < maxX) return true; x = (maxY - y1) / m + x1; if (x > minX && x < maxX) return true; return false; }, /** Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. */ aabbIntersectsSkeleton: function (bounds) { return this.minX < bounds.maxX && this.maxX > bounds.minX && this.minY < bounds.maxY && this.maxY > bounds.minY; }, /** Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more * efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true. */ containsPoint: function (x, y) { var polygons = this.polygons; for (var i = 0, n = polygons.length; i < n; i++) if (this.polygonContainsPoint(polygons[i], x, y)) return this.boundingBoxes[i]; return null; }, /** Returns the first bounding box attachment that contains the line segment, or null. When doing many checks, it is usually * more efficient to only call this method if {@link #aabbIntersectsSegment(float, float, float, float)} returns true. */ intersectsSegment: function (x1, y1, x2, y2) { var polygons = this.polygons; for (var i = 0, n = polygons.length; i < n; i++) if (polygons[i].intersectsSegment(x1, y1, x2, y2)) return this.boundingBoxes[i]; return null; }, /** Returns true if the polygon contains the point. */ polygonContainsPoint: function (polygon, x, y) { var nn = polygon.length; var prevIndex = nn - 2; var inside = false; for (var ii = 0; ii < nn; ii += 2) { var vertexY = polygon[ii + 1]; var prevY = polygon[prevIndex + 1]; if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) { var vertexX = polygon[ii]; if (vertexX + (y - vertexY) / (prevY - vertexY) * (polygon[prevIndex] - vertexX) < x) inside = !inside; } prevIndex = ii; } return inside; }, /** Returns true if the polygon contains the line segment. */ polygonIntersectsSegment: function (polygon, x1, y1, x2, y2) { var nn = polygon.length; var width12 = x1 - x2, height12 = y1 - y2; var det1 = x1 * y2 - y1 * x2; var x3 = polygon[nn - 2], y3 = polygon[nn - 1]; for (var ii = 0; ii < nn; ii += 2) { var x4 = polygon[ii], y4 = polygon[ii + 1]; var det2 = x3 * y4 - y3 * x4; var width34 = x3 - x4, height34 = y3 - y4; var det3 = width12 * height34 - height12 * width34; var x = (det1 * width34 - width12 * det2) / det3; if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) { var y = (det1 * height34 - height12 * det2) / det3; if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true; } x3 = x4; y3 = y4; } return false; }, getPolygon: function (attachment) { var index = this.boundingBoxes.indexOf(attachment); return index == -1 ? null : this.polygons[index]; }, getWidth: function () { return this.maxX - this.minX; }, getHeight: function () { return this.maxY - this.minY; } };