diff --git a/spine-js/README.md b/spine-js/README.md index 8f8002420..06077d3f9 100644 --- a/spine-js/README.md +++ b/spine-js/README.md @@ -4,7 +4,7 @@ The spine-js runtime provides functionality to load and manipulate [Spine](http: # spine-canvas -The spine-canvas runtime extends spine-js to perform rendering using an HTML5 canvas. Because it renders rectangular images, nonuniform scaling and mesh attachments are not supported. +The spine-canvas runtime extends spine-js and is a basic example of how to perform rendering using an HTML5 canvas. Because spine-canvas renders rectangular images, nonuniform scaling and mesh attachments are not supported. ## Licensing @@ -14,9 +14,9 @@ The Spine Runtimes are developed with the intent to be used with data exported f ## Spine version -spine-js works with data exported from Spine 2.1.27. Updating spine-js to [v3.0](https://trello.com/c/tF8UykBM/72-update-runtimes-to-support-v3-0-skewing-scale) and [v3.1](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes) is in progress. +spine-js works with data exported from the latest version of Spine. -spine-js supports all Spine features. +spine-js supports all Spine features. spine-canvas does not support mesh attachments or nonuniform scaling.~ spine-js does not yet support loading the binary format. @@ -32,3 +32,4 @@ spine-js does not yet support loading the binary format. ## Runtimes Extending spine-js - [spine-turbulenz](https://github.com/EsotericSoftware/spine-runtimes/blob/master/spine-turbulenz) +- [spine-threejs](https://github.com/EsotericSoftware/spine-runtimes/blob/master/spine-threejs) diff --git a/spine-js/spine-canvas.js b/spine-js/spine-canvas.js index cef4beb1b..1a8c8c3e8 100644 --- a/spine-js/spine-canvas.js +++ b/spine-js/spine-canvas.js @@ -83,11 +83,10 @@ spine.SkeletonRenderer.prototype = { var attachment = slot.attachment; if (!(attachment instanceof spine.RegionAttachment)) continue; var bone = slot.bone; - - var x = bone.worldX + attachment.x * bone.m00 + attachment.y * bone.m01; - var y = bone.worldY + attachment.x * bone.m10 + attachment.y * bone.m11; - var rotation = -(bone.worldRotation + attachment.rotation) * Math.PI / 180; - var w = attachment.width * bone.worldScaleX, h = attachment.height * bone.worldScaleY; + var x = attachment.x * bone.a + attachment.y * bone.b + bone.worldX; + var y = attachment.x * bone.c + attachment.y * bone.d + bone.worldY; + var rotation = (bone.getWorldRotationX() - attachment.rotation) * Math.PI / 180; + var w = attachment.width * bone.getWorldScaleX(), h = attachment.height * bone.getWorldScaleY(); context.translate(x, y); context.rotate(rotation); context.drawImage(attachment.rendererObject, -w / 2, -h / 2, w, h); @@ -116,4 +115,4 @@ spine.SkeletonRenderer.prototype = { }; renderFrame(); } -}; +}; \ No newline at end of file diff --git a/spine-js/spine.js b/spine-js/spine.js index ded11325b..06499bc56 100644 --- a/spine-js/spine.js +++ b/spine-js/spine.js @@ -32,10 +32,11 @@ var spine = { radDeg: 180 / Math.PI, degRad: Math.PI / 180, - temp: [], - Float32Array: (typeof(Float32Array) === 'undefined') ? Array : Float32Array, - Uint16Array: (typeof(Uint16Array) === 'undefined') ? Array : Uint16Array + 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; @@ -47,8 +48,7 @@ spine.BoneData.prototype = { rotation: 0, scaleX: 1, scaleY: 1, inheritScale: true, - inheritRotation: true, - flipX: false, flipY: false + inheritRotation: true }; spine.BlendMode = { @@ -78,6 +78,16 @@ spine.IkConstraintData.prototype = { 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; @@ -86,56 +96,132 @@ spine.Bone = function (boneData, skeleton, parent) { }; spine.Bone.yDown = false; spine.Bone.prototype = { - x: 0, y: 0, - rotation: 0, rotationIK: 0, - scaleX: 1, scaleY: 1, - flipX: false, flipY: false, - m00: 0, m01: 0, worldX: 0, // a b x - m10: 0, m11: 0, worldY: 0, // c d y - worldRotation: 0, - worldScaleX: 1, worldScaleY: 1, - worldFlipX: false, worldFlipY: false, - updateWorldTransform: function () { + 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) { - this.worldX = this.x * parent.m00 + this.y * parent.m01 + parent.worldX; - this.worldY = this.x * parent.m10 + this.y * parent.m11 + parent.worldY; - if (this.data.inheritScale) { - this.worldScaleX = parent.worldScaleX * this.scaleX; - this.worldScaleY = parent.worldScaleY * this.scaleY; - } else { - this.worldScaleX = this.scaleX; - this.worldScaleY = this.scaleY; + if (!parent) { // Root bone. + var skeleton = this.skeleton; + if (skeleton.flipX) { + x = -x; + la = -la; + lb = -lb; } - this.worldRotation = this.data.inheritRotation ? (parent.worldRotation + this.rotationIK) : this.rotationIK; - this.worldFlipX = parent.worldFlipX != this.flipX; - this.worldFlipY = parent.worldFlipY != this.flipY; - } else { - var skeletonFlipX = this.skeleton.flipX, skeletonFlipY = this.skeleton.flipY; - this.worldX = skeletonFlipX ? -this.x : this.x; - this.worldY = (skeletonFlipY != spine.Bone.yDown) ? -this.y : this.y; - this.worldScaleX = this.scaleX; - this.worldScaleY = this.scaleY; - this.worldRotation = this.rotationIK; - this.worldFlipX = skeletonFlipX != this.flipX; - this.worldFlipY = skeletonFlipY != this.flipY; + 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 radians = this.worldRotation * spine.degRad; - var cos = Math.cos(radians); - var sin = Math.sin(radians); - if (this.worldFlipX) { - this.m00 = -cos * this.worldScaleX; - this.m01 = sin * this.worldScaleY; + + 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 { - this.m00 = cos * this.worldScaleX; - this.m01 = -sin * this.worldScaleY; - } - if (this.worldFlipY != spine.Bone.yDown) { - this.m10 = -sin * this.worldScaleX; - this.m11 = -cos * this.worldScaleY; - } else { - this.m10 = sin * this.worldScaleX; - this.m11 = cos * this.worldScaleY; + 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 () { @@ -143,40 +229,47 @@ spine.Bone.prototype = { this.x = data.x; this.y = data.y; this.rotation = data.rotation; - this.rotationIK = this.rotation; this.scaleX = data.scaleX; this.scaleY = data.scaleY; - this.flipX = data.flipX; - this.flipY = data.flipY; + }, + 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 dx = world[0] - this.worldX, dy = world[1] - this.worldY; - var m00 = this.m00, m10 = this.m10, m01 = this.m01, m11 = this.m11; - if (this.worldFlipX != (this.worldFlipY != spine.Bone.yDown)) { - m00 = -m00; - m11 = -m11; - } - var invDet = 1 / (m00 * m11 - m01 * m10); - world[0] = dx * m00 * invDet - dy * m01 * invDet; - world[1] = dy * m11 * invDet - dx * m10 * invDet; + 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 localX = local[0], localY = local[1]; - local[0] = localX * this.m00 + localY * this.m01 + this.worldX; - local[1] = localX * this.m10 + localY * this.m11 + this.worldY; + 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, - attachmentVertices: [], setAttachment: function (attachment) { if (this.attachment == attachment) return; this.attachment = attachment; @@ -218,11 +311,14 @@ spine.IkConstraint = function (data, skeleton) { this.bones = []; for (var i = 0, n = data.bones.length; i < n; i++) - this.bones.push(skeleton.findBone(data.bones[i].name)); + 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) { @@ -233,80 +329,172 @@ spine.IkConstraint.prototype = { 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.data.inheritRotation || !bone.parent) ? 0 : bone.parent.worldRotation; + var parentRotation = !bone.parent ? 0 : bone.parent.getWorldRotationX(); var rotation = bone.rotation; - var rotationIK = Math.atan2(targetY - bone.worldY, targetX - bone.worldX) * spine.radDeg; - if (bone.worldFlipX != (bone.worldFlipY != spine.Bone.yDown)) rotationIK = -rotationIK; - rotationIK -= parentRotation; - bone.rotationIK = rotation + (rotationIK - rotation) * alpha; + 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 Any descendant bone of the parent. */ -spine.IkConstraint.apply2 = function (parent, child, targetX, targetY, bendDirection, alpha) { - var childRotation = child.rotation, parentRotation = parent.rotation; - if (!alpha) { - child.rotationIK = childRotation; - parent.rotationIK = parentRotation; - return; - } - var positionX, positionY, tempPosition = spine.temp; - var parentParent = parent.parent; - if (parentParent) { - tempPosition[0] = targetX; - tempPosition[1] = targetY; - parentParent.worldToLocal(tempPosition); - targetX = (tempPosition[0] - parent.x) * parentParent.worldScaleX; - targetY = (tempPosition[1] - parent.y) * parentParent.worldScaleY; + * @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 { - targetX -= parent.x; - targetY -= parent.y; + os1 = 0; + s2 = 1; } - if (child.parent == parent) { - positionX = child.x; - positionY = child.y; + 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 { - tempPosition[0] = child.x; - tempPosition[1] = child.y; - child.parent.localToWorld(tempPosition); - parent.worldToLocal(tempPosition); - positionX = tempPosition[0]; - positionY = tempPosition[1]; + 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 childX = positionX * parent.worldScaleX, childY = positionY * parent.worldScaleY; - var offset = Math.atan2(childY, childX); - var len1 = Math.sqrt(childX * childX + childY * childY), len2 = child.data.length * child.worldScaleX; - // Based on code by Ryan Juckett with permission: Copyright (c) 2008-2009 Ryan Juckett, http://www.ryanjuckett.com/ - var cosDenom = 2 * len1 * len2; - if (cosDenom < 0.0001) { - child.rotationIK = childRotation + (Math.atan2(targetY, targetX) * spine.radDeg - parentRotation - childRotation) * alpha; - return; + 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 * 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 cos = (targetX * targetX + targetY * targetY - len1 * len1 - len2 * len2) / cosDenom; - if (cos < -1) - cos = -1; - else if (cos > 1) - cos = 1; - var childAngle = Math.acos(cos) * bendDirection; - var adjacent = len1 + len2 * cos, opposite = len2 * Math.sin(childAngle); - var parentAngle = Math.atan2(targetY * adjacent - targetX * opposite, targetX * adjacent + targetY * opposite); - var rotation = (parentAngle - offset) * spine.radDeg - parentRotation; - if (rotation > 180) - rotation -= 360; - else if (rotation < -180) // - rotation += 360; - parent.rotationIK = parentRotation + rotation * alpha; - rotation = (childAngle + offset) * spine.radDeg - childRotation; - if (rotation > 180) - rotation -= 360; - else if (rotation < -180) // - rotation += 360; - child.rotationIK = childRotation + (rotation + parent.worldRotation - child.parent.worldRotation) * alpha; + var os = Math.atan2(cy, cx) * s2; + a1 = (a1 - os) * radDeg + os1; + a2 = (a2 + os) * 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) { @@ -343,7 +531,7 @@ spine.Animation.prototype = { apply: function (skeleton, lastTime, time, loop, events) { if (loop && this.duration != 0) { time %= this.duration; - lastTime %= this.duration; + if (lastTime > 0) lastTime %= this.duration; } var timelines = this.timelines; for (var i = 0, n = timelines.length; i < n; i++) @@ -352,7 +540,7 @@ spine.Animation.prototype = { mix: function (skeleton, lastTime, time, loop, events, alpha) { if (loop && this.duration != 0) { time %= this.duration; - lastTime %= this.duration; + if (lastTime > 0) lastTime %= this.duration; } var timelines = this.timelines; for (var i = 0, n = timelines.length; i < n; i++) @@ -394,8 +582,9 @@ spine.Animation.linearSearch = function (values, target, step) { }; spine.Curves = function (frameCount) { - this.curves = []; // type, x, y, ... - //this.curves.length = (frameCount - 1) * 19/*BEZIER_SIZE*/; + 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) { @@ -418,7 +607,7 @@ spine.Curves.prototype = { 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; @@ -461,7 +650,7 @@ spine.Curves.prototype = { spine.RotateTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); - this.frames = []; // time, angle, ... + this.frames = new spine.Float32Array(frameCount * 2); // time, angle, ... this.frames.length = frameCount * 2; }; spine.RotateTimeline.prototype = { @@ -513,7 +702,7 @@ spine.RotateTimeline.prototype = { spine.TranslateTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); - this.frames = []; // time, x, y, ... + this.frames = new spine.Float32Array(frameCount * 3); // time, x, y, ... this.frames.length = frameCount * 3; }; spine.TranslateTimeline.prototype = { @@ -554,7 +743,7 @@ spine.TranslateTimeline.prototype = { spine.ScaleTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); - this.frames = []; // time, x, y, ... + this.frames = new spine.Float32Array(frameCount * 3); // time, x, y, ... this.frames.length = frameCount * 3; }; spine.ScaleTimeline.prototype = { @@ -595,7 +784,7 @@ spine.ScaleTimeline.prototype = { spine.ColorTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); - this.frames = []; // time, r, g, b, a, ... + this.frames = new spine.Float32Array(frameCount * 5); // time, r, g, b, a, ... this.frames.length = frameCount * 5; }; spine.ColorTimeline.prototype = { @@ -656,7 +845,7 @@ spine.ColorTimeline.prototype = { spine.AttachmentTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); - this.frames = []; // time, ... + this.frames = new spine.Float32Array(frameCount); // time, ... this.frames.length = frameCount; this.attachmentNames = []; this.attachmentNames.length = frameCount; @@ -688,7 +877,7 @@ spine.AttachmentTimeline.prototype = { }; spine.EventTimeline = function (frameCount) { - this.frames = []; // time, ... + this.frames = new spine.Float32Array(frameCount); // time, ... this.frames.length = frameCount; this.events = []; this.events.length = frameCount; @@ -697,8 +886,8 @@ spine.EventTimeline.prototype = { getFrameCount: function () { return this.frames.length; }, - setFrame: function (frameIndex, time, event) { - this.frames[frameIndex] = time; + setFrame: function (frameIndex, event) { + this.frames[frameIndex] = event.time; this.events[frameIndex] = event; }, /** Fires events for frames > lastTime and <= time. */ @@ -728,12 +917,12 @@ spine.EventTimeline.prototype = { } var events = this.events; for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++) - firedEvents.push(events[frameIndex]); + firedEvents[firedEvents.length] = events[frameIndex]; } }; spine.DrawOrderTimeline = function (frameCount) { - this.frames = []; // time, ... + this.frames = new spine.Float32Array(frameCount); // time, ... this.frames.length = frameCount; this.drawOrders = []; this.drawOrders.length = frameCount; @@ -772,7 +961,7 @@ spine.DrawOrderTimeline.prototype = { spine.FfdTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); - this.frames = []; + this.frames = new spine.Float32Array(frameCount); this.frames.length = frameCount; this.frameVertices = []; this.frameVertices.length = frameCount; @@ -789,7 +978,8 @@ spine.FfdTimeline.prototype = { }, apply: function (skeleton, lastTime, time, firedEvents, alpha) { var slot = skeleton.slots[this.slotIndex]; - if (slot.attachment != this.attachment) return; + var slotAttachment = slot.attachment; + if (slotAttachment != this.attachment && (!slotAttachment.inheritFFD || slotAttachment.parentMesh != sourceAttachment)) return; var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -838,7 +1028,7 @@ spine.FfdTimeline.prototype = { spine.IkConstraintTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); - this.frames = []; // time, mix, bendDirection, ... + this.frames = new spine.Float32Array(frameCount * 3); // time, mix, bendDirection, ... this.frames.length = frameCount * 3; }; spine.IkConstraintTimeline.prototype = { @@ -877,62 +1067,6 @@ spine.IkConstraintTimeline.prototype = { } }; -spine.FlipXTimeline = function (frameCount) { - this.curves = new spine.Curves(frameCount); - this.frames = []; // time, flip, ... - this.frames.length = frameCount * 2; -}; -spine.FlipXTimeline.prototype = { - boneIndex: 0, - getFrameCount: function () { - return this.frames.length / 2; - }, - setFrame: function (frameIndex, time, flip) { - frameIndex *= 2; - this.frames[frameIndex] = time; - this.frames[frameIndex + 1] = flip ? 1 : 0; - }, - 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 - 2] ? frames.length : spine.Animation.binarySearch(frames, time, 2)) - 2; - if (frames[frameIndex] < lastTime) return; - skeleton.bones[this.boneIndex].flipX = frames[frameIndex + 1] != 0; - } -}; - -spine.FlipYTimeline = function (frameCount) { - this.curves = new spine.Curves(frameCount); - this.frames = []; // time, flip, ... - this.frames.length = frameCount * 2; -}; -spine.FlipYTimeline.prototype = { - boneIndex: 0, - getFrameCount: function () { - return this.frames.length / 2; - }, - setFrame: function (frameIndex, time, flip) { - frameIndex *= 2; - this.frames[frameIndex] = time; - this.frames[frameIndex + 1] = flip ? 1 : 0; - }, - 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 - 2] ? frames.length : spine.Animation.binarySearch(frames, time, 2)) - 2; - if (frames[frameIndex] < lastTime) return; - skeleton.bones[this.boneIndex].flipY = frames[frameIndex + 1] != 0; - } -}; - spine.SkeletonData = function () { this.bones = []; this.slots = []; @@ -940,6 +1074,7 @@ spine.SkeletonData = function () { this.events = []; this.animations = []; this.ikConstraints = []; + this.transformConstraints = []; }; spine.SkeletonData.prototype = { name: null, @@ -997,10 +1132,17 @@ spine.SkeletonData.prototype = { return null; }, /** @return May be null. */ - findIkConstraint: function (ikConstraintName) { + findIkConstraint: function (constraintName) { var ikConstraints = this.ikConstraints; for (var i = 0, n = ikConstraints.length; i < n; i++) - if (ikConstraints[i].name == ikConstraintName) return ikConstraints[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; } }; @@ -1012,7 +1154,7 @@ spine.Skeleton = function (skeletonData) { 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.push(new spine.Bone(boneData, this, parent)); + this.bones[i] = new spine.Bone(boneData, this, parent); } this.slots = []; @@ -1021,15 +1163,19 @@ spine.Skeleton = function (skeletonData) { var slotData = skeletonData.slots[i]; var bone = this.bones[skeletonData.bones.indexOf(slotData.boneData)]; var slot = new spine.Slot(slotData, bone); - this.slots.push(slot); - this.drawOrder.push(slot); + this.slots[i] = slot; + this.drawOrder[i] = slot; } - + this.ikConstraints = []; for (var i = 0, n = skeletonData.ikConstraints.length; i < n; i++) - this.ikConstraints.push(new spine.IkConstraint(skeletonData.ikConstraints[i], this)); + this.ikConstraints[i] = new spine.IkConstraint(skeletonData.ikConstraints[i], this); - this.boneCache = []; + 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 = { @@ -1038,68 +1184,51 @@ spine.Skeleton.prototype = { r: 1, g: 1, b: 1, a: 1, time: 0, flipX: false, flipY: false, - /** Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or removed. */ + /** 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; - var arrayCount = ikConstraintsCount + 1; - var boneCache = this.boneCache; - if (boneCache.length > arrayCount) boneCache.length = arrayCount; - for (var i = 0, n = boneCache.length; i < n; i++) - boneCache[i].length = 0; - while (boneCache.length < arrayCount) - boneCache[boneCache.length] = []; - - var nonIkBones = boneCache[0]; - var bones = this.bones; - - outer: for (var i = 0, n = bones.length; i < n; i++) { var bone = bones[i]; - var current = bone; - do { - for (var ii = 0; ii < ikConstraintsCount; ii++) { - var ikConstraint = ikConstraints[ii]; - var parent = ikConstraint.bones[0]; - var child= ikConstraint.bones[ikConstraint.bones.length - 1]; - while (true) { - if (current == child) { - boneCache[ii].push(bone); - boneCache[ii + 1].push(bone); - continue outer; - } - if (child == parent) break; - child = child.parent; - } + 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; } - current = current.parent; - } while (current); - nonIkBones[nonIkBones.length] = bone; + } + } + + 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. */ + /** Updates the world transform for each bone and applies constraints. */ updateWorldTransform: function () { - var bones = this.bones; - for (var i = 0, n = bones.length; i < n; i++) { - var bone = bones[i]; - bone.rotationIK = bone.rotation; - } - var i = 0, last = this.boneCache.length - 1; - while (true) { - var cacheBones = this.boneCache[i]; - for (var ii = 0, nn = cacheBones.length; ii < nn; ii++) - cacheBones[ii].updateWorldTransform(); - if (i == last) break; - this.ikConstraints[i].apply(); - i++; - } + var updateCache = this.cache; + for (var i = 0, n = updateCache.length; i < n; i++) + updateCache[i].update(); }, - /** Sets the bones and slots to their setup pose values. */ + /** 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++) @@ -1107,9 +1236,17 @@ spine.Skeleton.prototype = { var ikConstraints = this.ikConstraints; for (var i = 0, n = ikConstraints.length; i < n; i++) { - var ikConstraint = ikConstraints[i]; - ikConstraint.bendDirection = ikConstraint.data.bendDirection; - ikConstraint.mix = ikConstraint.data.mix; + 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 () { @@ -1210,10 +1347,17 @@ spine.Skeleton.prototype = { throw "Slot not found: " + slotName; }, /** @return May be null. */ - findIkConstraint: function (ikConstraintName) { + findIkConstraint: function (constraintName) { var ikConstraints = this.ikConstraints; for (var i = 0, n = ikConstraints.length; i < n; i++) - if (ikConstraints[i].data.name == ikConstraintName) return ikConstraints[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) { @@ -1230,7 +1374,8 @@ spine.EventData.prototype = { stringValue: null }; -spine.Event = function (data) { +spine.Event = function (time, data) { + this.time = time; this.data = data; }; spine.Event.prototype = { @@ -1243,14 +1388,16 @@ spine.AttachmentType = { region: 0, boundingbox: 1, mesh: 2, - skinnedmesh: 3 + weightedmesh: 3, + linkedmesh: 4, + weightedlinkedmesh: 5 }; spine.RegionAttachment = function (name) { this.name = name; - this.offset = []; + this.offset = new spine.Float32Array(8); this.offset.length = 8; - this.uvs = []; + this.uvs = new spine.Float32Array(8); this.uvs.length = 8; }; spine.RegionAttachment.prototype = { @@ -1318,7 +1465,7 @@ spine.RegionAttachment.prototype = { computeVertices: function (x, y, bone, vertices) { x += bone.worldX; y += bone.worldY; - var m00 = bone.m00, m01 = bone.m01, m10 = bone.m10, m11 = bone.m11; + 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; @@ -1343,6 +1490,8 @@ spine.MeshAttachment.prototype = { 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, @@ -1353,18 +1502,16 @@ spine.MeshAttachment.prototype = { 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.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; + 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; + this.uvs[i] = this.regionU + this.regionUVs[i] * width; + this.uvs[i + 1] = this.regionV + this.regionUVs[i + 1] * height; } } }, @@ -1372,7 +1519,7 @@ spine.MeshAttachment.prototype = { var bone = slot.bone; x += bone.worldX; y += bone.worldY; - var m00 = bone.m00, m01 = bone.m01, m10 = bone.m10, m11 = bone.m11; + 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; @@ -1382,14 +1529,26 @@ spine.MeshAttachment.prototype = { 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.SkinnedMeshAttachment = function (name) { +spine.WeightedMeshAttachment = function (name) { this.name = name; }; -spine.SkinnedMeshAttachment.prototype = { - type: spine.AttachmentType.skinnedmesh, +spine.WeightedMeshAttachment.prototype = { + type: spine.AttachmentType.weightedmesh, bones: null, weights: null, uvs: null, @@ -1398,6 +1557,8 @@ spine.SkinnedMeshAttachment.prototype = { 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, @@ -1408,18 +1569,16 @@ spine.SkinnedMeshAttachment.prototype = { 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.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; + 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; + this.uvs[i] = this.regionU + this.regionUVs[i] * width; + this.uvs[i + 1] = this.regionV + this.regionUVs[i + 1] * height; } } }, @@ -1440,8 +1599,8 @@ spine.SkinnedMeshAttachment.prototype = { vx = weights[b]; vy = weights[b + 1]; weight = weights[b + 2]; - wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight; - wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight; + 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; @@ -1457,26 +1616,39 @@ spine.SkinnedMeshAttachment.prototype = { vx = weights[b] + ffd[f]; vy = weights[b + 1] + ffd[f + 1]; weight = weights[b + 2]; - wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight; - wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight; + 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 = []; + 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.m00, m01 = bone.m01, m10 = bone.m10, m11 = bone.m11; + 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]; @@ -1620,7 +1792,7 @@ spine.AnimationState.prototype = { _expandToIndex: function (index) { if (index < this.tracks.length) return this.tracks[index]; while (index >= this.tracks.length) - this.tracks.push(null); + this.tracks[this.tracks.length] = null; return null; }, setCurrent: function (index, entry) { @@ -1702,6 +1874,7 @@ spine.AnimationState.prototype = { spine.SkeletonJson = function (attachmentLoader) { this.attachmentLoader = attachmentLoader; + this.linkedMeshes = []; }; spine.SkeletonJson.prototype = { scale: 1, @@ -1736,7 +1909,7 @@ spine.SkeletonJson.prototype = { 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.push(boneData); + skeletonData.bones[i] = boneData; } // IK constraints. @@ -1750,7 +1923,7 @@ spine.SkeletonJson.prototype = { 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.push(bone); + ikConstraintData.bones[ii] = bone; } ikConstraintData.target = skeletonData.findBone(ikMap["target"]); @@ -1759,7 +1932,28 @@ spine.SkeletonJson.prototype = { ikConstraintData.bendDirection = (!ikMap.hasOwnProperty("bendPositive") || ikMap["bendPositive"]) ? 1 : -1; ikConstraintData.mix = ikMap.hasOwnProperty("mix") ? ikMap["mix"] : 1; - skeletonData.ikConstraints.push(ikConstraintData); + 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; } } @@ -1782,7 +1976,7 @@ spine.SkeletonJson.prototype = { slotData.attachmentName = slotMap["attachment"]; slotData.blendMode = spine.BlendMode[slotMap["blend"] || "normal"]; - skeletonData.slots.push(slotData); + skeletonData.slots[i] = slotData; } // Skins. @@ -1797,14 +1991,26 @@ spine.SkeletonJson.prototype = { var slotEntry = skinMap[slotName]; for (var attachmentName in slotEntry) { if (!slotEntry.hasOwnProperty(attachmentName)) continue; - var attachment = this.readAttachment(skin, attachmentName, slotEntry[attachmentName]); + var attachment = this.readAttachment(skin, slotIndex, attachmentName, slotEntry[attachmentName]); if (attachment) skin.addAttachment(slotIndex, attachmentName, attachment); } } - skeletonData.skins.push(skin); + 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) { @@ -1814,7 +2020,7 @@ spine.SkeletonJson.prototype = { eventData.intValue = eventMap["int"] || 0; eventData.floatValue = eventMap["float"] || 0; eventData.stringValue = eventMap["string"] || null; - skeletonData.events.push(eventData); + skeletonData.events[skeletonData.events.length] = eventData; } // Animations. @@ -1826,14 +2032,17 @@ spine.SkeletonJson.prototype = { return skeletonData; }, - readAttachment: function (skin, name, map) { + readAttachment: function (skin, slotIndex, name, map) { name = map["name"] || name; - var type = spine.AttachmentType[map["type"] || "region"]; + var type = map["type"] || "region"; + if (type == "skinnedmesh") type = "weightedmesh"; + type = spine.AttachmentType[type]; var path = map["path"] || name; - + var scale = this.scale; - if (type == spine.AttachmentType.region) { + switch (type) { + case spine.AttachmentType.region: var region = this.attachmentLoader.newRegionAttachment(skin, name, path); if (!region) return null; region.path = path; @@ -1855,14 +2064,11 @@ spine.SkeletonJson.prototype = { region.updateOffset(); return region; - } else if (type == spine.AttachmentType.mesh) { + case spine.AttachmentType.mesh: + case spine.AttachmentType.linkedmesh: var mesh = this.attachmentLoader.newMeshAttachment(skin, name, path); if (!mesh) return null; mesh.path = path; - mesh.vertices = this.getFloatArray(map, "vertices", scale); - mesh.triangles = this.getIntArray(map, "triangles"); - mesh.regionUVs = this.getFloatArray(map, "uvs", 1); - mesh.updateUVs(); color = map["color"]; if (color) { @@ -1872,37 +2078,28 @@ spine.SkeletonJson.prototype = { mesh.a = this.toColor(color, 3); } - mesh.hullLength = (map["hull"] || 0) * 2; - if (map["edges"]) mesh.edges = this.getIntArray(map, "edges"); 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; - } else if (type == spine.AttachmentType.skinnedmesh) { - var mesh = this.attachmentLoader.newSkinnedMeshAttachment(skin, name, path); + case spine.AttachmentType.weightedmesh: + case spine.AttachmentType.weightedlinkedmesh: + var mesh = this.attachmentLoader.newWeightedMeshAttachment(skin, name, path); if (!mesh) return null; mesh.path = path; - var uvs = this.getFloatArray(map, "uvs", 1); - var vertices = this.getFloatArray(map, "vertices", 1); - var weights = []; - var bones = []; - for (var i = 0, n = vertices.length; i < n; ) { - var boneCount = vertices[i++] | 0; - bones[bones.length] = boneCount; - for (var nn = i + boneCount * 4; i < nn; ) { - bones[bones.length] = vertices[i]; - weights[weights.length] = vertices[i + 1] * scale; - weights[weights.length] = vertices[i + 2] * scale; - weights[weights.length] = vertices[i + 3]; - i += 4; - } - } - mesh.bones = bones; - mesh.weights = weights; - mesh.triangles = this.getIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.updateUVs(); - color = map["color"]; if (color) { mesh.r = this.toColor(color, 0); @@ -1910,17 +2107,44 @@ spine.SkeletonJson.prototype = { mesh.b = this.toColor(color, 2); mesh.a = this.toColor(color, 3); } - - mesh.hullLength = (map["hull"] || 0) * 2; - if (map["edges"]) mesh.edges = this.getIntArray(map, "edges"); + 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, n = vertices.length; i < n; ) { + var boneCount = vertices[i++] | 0; + bones[bones.length] = boneCount; + for (var nn = i + boneCount * 4; i < nn; ) { + bones[bones.length] = vertices[i]; + weights[weights.length] = vertices[i + 1] * scale; + weights[weights.length] = vertices[i + 2] * scale; + weights[weights.length] = 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; - } else if (type == spine.AttachmentType.boundingbox) { + 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.push(vertices[i] * scale); + attachment.vertices[i] = vertices[i] * scale; return attachment; } throw "Unknown attachment type: " + type; @@ -1954,7 +2178,7 @@ spine.SkeletonJson.prototype = { this.readCurve(timeline, frameIndex, valueMap); frameIndex++; } - timelines.push(timeline); + timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 5 - 5]); } else if (timelineName == "attachment") { @@ -1966,7 +2190,7 @@ spine.SkeletonJson.prototype = { var valueMap = values[i]; timeline.setFrame(frameIndex++, valueMap["time"], valueMap["name"]); } - timelines.push(timeline); + timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); } else @@ -1995,7 +2219,7 @@ spine.SkeletonJson.prototype = { this.readCurve(timeline, frameIndex, valueMap); frameIndex++; } - timelines.push(timeline); + timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 2 - 2]); } else if (timelineName == "translate" || timelineName == "scale") { @@ -2018,23 +2242,9 @@ spine.SkeletonJson.prototype = { this.readCurve(timeline, frameIndex, valueMap); frameIndex++; } - timelines.push(timeline); + timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 3 - 3]); - } else if (timelineName == "flipX" || timelineName == "flipY") { - var x = timelineName == "flipX"; - var timeline = x ? new spine.FlipXTimeline(values.length) : new spine.FlipYTimeline(values.length); - timeline.boneIndex = boneIndex; - - var field = x ? "x" : "y"; - var frameIndex = 0; - for (var i = 0, n = values.length; i < n; i++) { - var valueMap = values[i]; - timeline.setFrame(frameIndex, valueMap["time"], valueMap[field] || false); - frameIndex++; - } - timelines.push(timeline); - duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 2 - 2]); } else throw "Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"; } @@ -2056,7 +2266,7 @@ spine.SkeletonJson.prototype = { this.readCurve(timeline, frameIndex, valueMap); frameIndex++; } - timelines.push(timeline); + timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 3 - 3]); } @@ -2074,7 +2284,7 @@ spine.SkeletonJson.prototype = { 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) @@ -2090,12 +2300,12 @@ spine.SkeletonJson.prototype = { if (isMesh) vertices = attachment.vertices; else { - vertices = []; + vertices = new spine.Float32Array(vertexCount); vertices.length = vertexCount; } } else { var verticesValue = valueMap["vertices"]; - var vertices = []; + var vertices = new spine.Float32Array(vertexCount); vertices.length = vertexCount; var start = valueMap["offset"] || 0; var nn = verticesValue.length; @@ -2112,7 +2322,7 @@ spine.SkeletonJson.prototype = { vertices[ii] += meshVertices[ii]; } } - + timeline.setFrame(frameIndex, valueMap["time"], vertices); this.readCurve(timeline, frameIndex, valueMap); frameIndex++; @@ -2133,12 +2343,12 @@ spine.SkeletonJson.prototype = { var drawOrderMap = drawOrderValues[i]; var drawOrder = null; if (drawOrderMap["offsets"]) { - drawOrder = []; + drawOrder = new spine.Uint32Array(slotCount); drawOrder.length = slotCount; for (var ii = slotCount - 1; ii >= 0; ii--) drawOrder[ii] = -1; var offsets = drawOrderMap["offsets"]; - var unchanged = []; + 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++) { @@ -2160,7 +2370,7 @@ spine.SkeletonJson.prototype = { } timeline.setFrame(frameIndex++, drawOrderMap["time"], drawOrder); } - timelines.push(timeline); + timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); } @@ -2172,17 +2382,17 @@ spine.SkeletonJson.prototype = { var eventMap = events[i]; var eventData = skeletonData.findEvent(eventMap["name"]); if (!eventData) throw "Event not found: " + eventMap["name"]; - var event = new spine.Event(eventData); + 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++, eventMap["time"], event); + timeline.setFrame(frameIndex++, event); } - timelines.push(timeline); + timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); } - skeletonData.animations.push(new spine.Animation(name, timelines, duration)); + skeletonData.animations[skeletonData.animations.length] = new spine.Animation(name, timelines, duration); }, readCurve: function (timeline, frameIndex, valueMap) { var curve = valueMap["curve"]; @@ -2210,7 +2420,14 @@ spine.SkeletonJson.prototype = { } return values; }, - getIntArray: function (map, name) { + 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++) @@ -2261,7 +2478,7 @@ spine.Atlas = function (atlasText, textureLoader) { textureLoader.load(page, line, this); - this.pages.push(page); + this.pages[this.pages.length] = page; } else { var region = new spine.AtlasRegion(); @@ -2311,7 +2528,7 @@ spine.Atlas = function (atlasText, textureLoader) { region.index = parseInt(reader.readValue()); - this.regions.push(region); + this.regions[this.regions.length] = region; } } }; @@ -2470,10 +2687,10 @@ spine.AtlasAttachmentLoader.prototype = { attachment.regionOriginalHeight = region.originalHeight; return attachment; }, - newSkinnedMeshAttachment: function (skin, name, path) { + newWeightedMeshAttachment: function (skin, name, path) { var region = this.atlas.findRegion(path); - if (!region) throw "Region not found in atlas: " + path + " (skinned mesh attachment: " + name + ")"; - var attachment = new spine.SkinnedMeshAttachment(name); + 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; @@ -2510,22 +2727,22 @@ spine.SkeletonBounds.prototype = { boundingBoxes.length = 0; for (var i = 0, n = polygons.length; i < n; i++) - polygonPool.push(polygons[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.push(boundingBox); + boundingBoxes[boundingBoxes.length] = boundingBox; var poolCount = polygonPool.length, polygon; if (poolCount > 0) { polygon = polygonPool[poolCount - 1]; polygonPool.splice(poolCount - 1, 1); } else - polygon = []; - polygons.push(polygon); + polygon = new spine.Float32Array(); + polygons[polygons.length] = polygon; polygon.length = boundingBox.vertices.length; boundingBox.computeWorldVertices(x, y, slot.bone, polygon); @@ -2639,4 +2856,4 @@ spine.SkeletonBounds.prototype = { getHeight: function () { return this.maxY - this.minY; } -}; +}; \ No newline at end of file diff --git a/spine-threejs/README.md b/spine-threejs/README.md index 4fc556109..de48d2f0b 100644 --- a/spine-threejs/README.md +++ b/spine-threejs/README.md @@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f ## Spine version -spine-threejs works with data exported from Spine 2.1.27. Updating spine-threejs to [v3.0](https://trello.com/c/tF8UykBM/72-update-runtimes-to-support-v3-0-skewing-scale) and [v3.1](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes) is in progress. +spine-threejs works with data exported from the latest version of Spine. spine-threejs supports all Spine features except for rendering meshes. diff --git a/spine-threejs/example/index.html b/spine-threejs/example/index.html index 3d4531649..ecbb46ff6 100644 --- a/spine-threejs/example/index.html +++ b/spine-threejs/example/index.html @@ -237,8 +237,8 @@ (dz || 0.1) * Z++ ); - matrix.elements[0] = slot.bone.m00; matrix.elements[4] = slot.bone.m01; - matrix.elements[1] = slot.bone.m10; matrix.elements[5] = slot.bone.m11; + matrix.elements[0] = slot.bone.a; matrix.elements[4] = slot.bone.b; + matrix.elements[1] = slot.bone.c; matrix.elements[5] = slot.bone.d; mesh.matrix.copy(matrix); @@ -342,7 +342,8 @@ function load (name, scale) { function init() { scene = new THREE.Scene(); - camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000); + var width = 640, height = 480; + camera = new THREE.PerspectiveCamera(75, width / height, 1, 3000); camera.position.z = 400; geometry = new THREE.BoxGeometry(200, 200, 200); @@ -354,11 +355,12 @@ function init() { load('spineboy', 0.4); renderer = new THREE.WebGLRenderer(); - renderer.setSize(window.innerWidth, window.innerHeight); + renderer.setSize(width, height); document.body.appendChild(renderer.domElement); } +var lastTime = Date.now(); function animate() { requestAnimationFrame(animate); @@ -369,7 +371,8 @@ function animate() { mesh.rotation.x = a * Math.PI * 0.2; mesh.rotation.y = b * Math.PI * 0.4; - anim.update(); + anim.update((t - lastTime) / 1000); + lastTime = t; renderer.render(scene, camera); } @@ -378,11 +381,10 @@ init(); animate(); -
+

-     Click to change the animation or skin. diff --git a/spine-turbulenz/README.md b/spine-turbulenz/README.md index 49598c729..10d101133 100644 --- a/spine-turbulenz/README.md +++ b/spine-turbulenz/README.md @@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f ## Spine version -spine-turbulenz works with data exported from Spine 2.1.27. Updating spine-turbulenz to [v3.0](https://trello.com/c/tF8UykBM/72-update-runtimes-to-support-v3-0-skewing-scale) and [v3.1](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes) is in progress. +spine-turbulenz works with data exported from the latest version of Spine. spine-turbulenz supports all Spine features except for rendering meshes.