diff --git a/spine-csharp/README.md b/spine-csharp/README.md index 3b6f0d1a4..4f6e5acdc 100644 --- a/spine-csharp/README.md +++ b/spine-csharp/README.md @@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f ## Spine version -spine-csharp works with data exported from Spine 3.1.08. Updating spine-csharp to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress. +spine-csharp works with data exported from the latest version of Spine. spine-csharp supports all Spine features. diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index c77e34e05..09a1f72ad 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -209,14 +209,14 @@ namespace Spine { } public class RotateTimeline : CurveTimeline { - protected const int PREV_FRAME_TIME = -2; - protected const int FRAME_VALUE = 1; + internal const int PREV_TIME = -2; + internal const int VALUE = 1; internal int boneIndex; internal float[] frames; public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, ... + public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... public RotateTimeline (int frameCount) : base(frameCount) { @@ -249,13 +249,13 @@ namespace Spine { } // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 2); - float prevFrameValue = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent((frameIndex >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + int frame = Animation.binarySearch(frames, time, 2); + float prevFrameValue = frames[frame - 1]; + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent((frame >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue; + amount = frames[frame + VALUE] - prevFrameValue; while (amount > 180) amount -= 360; while (amount < -180) @@ -270,9 +270,9 @@ namespace Spine { } public class TranslateTimeline : CurveTimeline { - protected const int PREV_FRAME_TIME = -3; - protected const int FRAME_X = 1; - protected const int FRAME_Y = 2; + protected const int PREV_TIME = -3; + protected const int X = 1; + protected const int Y = 2; internal int boneIndex; internal float[] frames; @@ -306,15 +306,15 @@ namespace Spine { } // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 3); - float prevFrameX = frames[frameIndex - 2]; - float prevFrameY = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + int frame = Animation.binarySearch(frames, time, 3); + float prevFrameX = frames[frame - 2]; + float prevFrameY = frames[frame - 1]; + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - bone.x += (bone.data.x + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.x) * alpha; - bone.y += (bone.data.y + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.y) * alpha; + bone.x += (bone.data.x + prevFrameX + (frames[frame + X] - prevFrameX) * percent - bone.x) * alpha; + bone.y += (bone.data.y + prevFrameY + (frames[frame + Y] - prevFrameY) * percent - bone.y) * alpha; } } @@ -335,24 +335,53 @@ namespace Spine { } // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 3); - float prevFrameX = frames[frameIndex - 2]; - float prevFrameY = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + int frame = Animation.binarySearch(frames, time, 3); + float prevFrameX = frames[frame - 2]; + float prevFrameY = frames[frame - 1]; + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent) - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent) - bone.scaleY) * alpha; + bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.scaleY) * alpha; + } + } + + public class ShearTimeline : TranslateTimeline { + public ShearTimeline (int frameCount) + : base (frameCount) { + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.Items[boneIndex]; + if (time >= frames[frames.Length - 3]) { // Time is after last frame. + bone.shearX += (bone.data.shearX + frames[frames.Length - 2] - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + frames[frames.Length - 1] - bone.shearY) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, 3); + float prevFrameX = frames[frame - 2]; + float prevFrameY = frames[frame - 1]; + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + bone.shearX += (bone.data.shearX + (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.shearY) * alpha; } } public class ColorTimeline : CurveTimeline { - protected const int PREV_FRAME_TIME = -5; - protected const int FRAME_R = 1; - protected const int FRAME_G = 2; - protected const int FRAME_B = 3; - protected const int FRAME_A = 4; + protected const int PREV_TIME = -5; + protected const int R = 1; + protected const int G = 2; + protected const int B = 3; + protected const int A = 4; internal int slotIndex; internal float[] frames; @@ -380,8 +409,7 @@ namespace Spine { if (time < frames[0]) return; // Time is before first frame. float r, g, b, a; - if (time >= frames[frames.Length - 5]) { - // Time is after last frame. + if (time >= frames[frames.Length - 5]) { // Time is after last frame. int i = frames.Length - 1; r = frames[i - 3]; g = frames[i - 2]; @@ -389,19 +417,19 @@ namespace Spine { a = frames[i]; } else { // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 5); - float prevFrameR = frames[frameIndex - 4]; - float prevFrameG = frames[frameIndex - 3]; - float prevFrameB = frames[frameIndex - 2]; - float prevFrameA = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + int frame = Animation.binarySearch(frames, time, 5); + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - r = prevFrameR + (frames[frameIndex + FRAME_R] - prevFrameR) * percent; - g = prevFrameG + (frames[frameIndex + FRAME_G] - prevFrameG) * percent; - b = prevFrameB + (frames[frameIndex + FRAME_B] - prevFrameB) * percent; - a = prevFrameA + (frames[frameIndex + FRAME_A] - prevFrameA) * percent; + r = frames[frame - 4]; + g = frames[frame - 3]; + b = frames[frame - 2]; + a = frames[frame - 1]; + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; } Slot slot = skeleton.slots.Items[slotIndex]; if (alpha < 1) { @@ -488,19 +516,19 @@ namespace Spine { return; if (time < frames[0]) return; // Time is before first frame. - int frameIndex; + int frame; if (lastTime < frames[0]) - frameIndex = 0; + frame = 0; else { - frameIndex = Animation.binarySearch(frames, lastTime); - float frame = frames[frameIndex]; - while (frameIndex > 0) { // Fire multiple events with the same frame. - if (frames[frameIndex - 1] != frame) break; - frameIndex--; + frame = Animation.binarySearch(frames, lastTime); + float frameTime = frames[frame]; + while (frame > 0) { // Fire multiple events with the same frame. + if (frames[frame - 1] != frameTime) break; + frame--; } } - for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++) - firedEvents.Add(events[frameIndex]); + for (; frame < frameCount && time >= frames[frame]; frame++) + firedEvents.Add(events[frame]); } } @@ -528,15 +556,15 @@ namespace Spine { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. - int frameIndex; + int frame; if (time >= frames[frames.Length - 1]) // Time is after last frame. - frameIndex = frames.Length - 1; + frame = frames.Length - 1; else - frameIndex = Animation.binarySearch(frames, time) - 1; + frame = Animation.binarySearch(frames, time) - 1; ExposedList drawOrder = skeleton.drawOrder; ExposedList slots = skeleton.slots; - int[] drawOrderToSetupIndex = drawOrders[frameIndex]; + int[] drawOrderToSetupIndex = drawOrders[frame]; if (drawOrderToSetupIndex == null) { drawOrder.Clear(); for (int i = 0, n = slots.Count; i < n; i++) @@ -605,13 +633,13 @@ namespace Spine { } // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time); - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime); - percent = GetCurvePercent(frameIndex - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + int frame = Animation.binarySearch(frames, time); + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame - 1] - frameTime); + percent = GetCurvePercent(frame - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - float[] prevVertices = frameVertices[frameIndex - 1]; - float[] nextVertices = frameVertices[frameIndex]; + float[] prevVertices = frameVertices[frame - 1]; + float[] nextVertices = frameVertices[frame]; if (alpha < 1) { for (int i = 0; i < vertexCount; i++) { @@ -629,10 +657,10 @@ namespace Spine { } public class IkConstraintTimeline : CurveTimeline { - private const int PREV_FRAME_TIME = -3; - private const int PREV_FRAME_MIX = -2; - private const int PREV_FRAME_BEND_DIRECTION = -1; - private const int FRAME_MIX = 1; + private const int PREV_TIME = -3; + private const int PREV_MIX = -2; + private const int PREV_BEND_DIRECTION = -1; + private const int MIX = 1; internal int ikConstraintIndex; internal float[] frames; @@ -644,8 +672,8 @@ namespace Spine { : base(frameCount) { frames = new float[frameCount * 3]; } - - /** Sets the time, mix and bend direction of the specified keyframe. */ + + /// Sets the time, mix and bend direction of the specified keyframe. public void SetFrame (int frameIndex, float time, float mix, int bendDirection) { frameIndex *= 3; frames[frameIndex] = time; @@ -657,24 +685,86 @@ namespace Spine { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. - IkConstraint ikConstraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; if (time >= frames[frames.Length - 3]) { // Time is after last frame. - ikConstraint.mix += (frames[frames.Length - 2] - ikConstraint.mix) * alpha; - ikConstraint.bendDirection = (int)frames[frames.Length - 1]; + constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; + constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; return; } // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 3); - float prevFrameMix = frames[frameIndex + PREV_FRAME_MIX]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + int frame = Animation.binarySearch(frames, time, 3); + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - float mix = prevFrameMix + (frames[frameIndex + FRAME_MIX] - prevFrameMix) * percent; - ikConstraint.mix += (mix - ikConstraint.mix) * alpha; - ikConstraint.bendDirection = (int)frames[frameIndex + PREV_FRAME_BEND_DIRECTION]; + float mix = frames[frame + PREV_MIX]; + constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; + constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + } + } + + public class TransformConstraintTimeline : CurveTimeline { + private const int PREV_TIME = -5; + private const int PREV_ROTATE_MIX = -4; + private const int PREV_TRANSLATE_MIX = -3; + private const int PREV_SCALE_MIX = -2; + private const int PREV_SHEAR_MIX = -1; + private const int ROTATE_MIX = 1; + private const int TRANSLATE_MIX = 2; + private const int SCALE_MIX = 3; + private const int SHEAR_MIX = 4; + + internal int transformConstraintIndex; + internal float[] frames; + + public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... + + public TransformConstraintTimeline (int frameCount) + : base(frameCount) { + frames = new float[frameCount * 5]; + } + + public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { + frameIndex *= 5; + frames[frameIndex] = time; + frames[frameIndex + 1] = rotateMix; + frames[frameIndex + 2] = translateMix; + frames[frameIndex + 3] = scaleMix; + frames[frameIndex + 4] = shearMix; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; + + if (time >= frames[frames.Length - 5]) { // Time is after last frame. + int i = frames.Length - 1; + constraint.rotateMix += (frames[i - 3] - constraint.rotateMix) * alpha; + constraint.translateMix += (frames[i - 2] - constraint.translateMix) * alpha; + constraint.scaleMix += (frames[i - 1] - constraint.scaleMix) * alpha; + constraint.shearMix += (frames[i] - constraint.shearMix) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, 5); + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + float rotate = frames[frame + PREV_ROTATE_MIX]; + float translate = frames[frame + PREV_TRANSLATE_MIX]; + float scale = frames[frame + PREV_SCALE_MIX]; + float shear = frames[frame + PREV_SHEAR_MIX]; + constraint.rotateMix += (rotate + (frames[frame + ROTATE_MIX] - rotate) * percent - constraint.rotateMix) * alpha; + constraint.translateMix += (translate + (frames[frame + TRANSLATE_MIX] - translate) * percent - constraint.translateMix) * alpha; + constraint.scaleMix += (scale + (frames[frame + SCALE_MIX] - scale) * percent - constraint.scaleMix) * alpha; + constraint.shearMix += (shear + (frames[frame + SHEAR_MIX] - shear) * percent - constraint.shearMix) * alpha; } } } diff --git a/spine-csharp/src/Bone.cs b/spine-csharp/src/Bone.cs index d1aeee7e1..01f1efbcb 100644 --- a/spine-csharp/src/Bone.cs +++ b/spine-csharp/src/Bone.cs @@ -40,7 +40,7 @@ namespace Spine { internal Skeleton skeleton; internal Bone parent; internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY; + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; internal float appliedRotation, appliedScaleX, appliedScaleY; internal float a, b, worldX; @@ -62,6 +62,8 @@ namespace Spine { public float AppliedScaleY { get { return appliedScaleY; } set { appliedScaleY = value; } } public float ScaleX { get { return scaleX; } set { scaleX = value; } } public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float ShearX { get { return shearX; } set { shearX = value; } } + public float ShearY { get { return shearY; } set { shearY = value; } } public float A { get { return a; } } public float B { get { return b; } } @@ -88,22 +90,24 @@ namespace Spine { /// Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}. public void Update () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY); + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); } /// Computes the world SRT using the parent bone and this bone's local SRT. public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY); + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); } /// Computes the world SRT using the parent bone and the specified local SRT. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY) { + public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { appliedRotation = rotation; appliedScaleX = scaleX; appliedScaleY = scaleY; - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float la = cos * scaleX, lb = -sin * scaleY, lc = sin * scaleX, ld = cos * scaleY; + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX, ld = MathUtils.SinDeg(rotationY) * scaleY; + Bone parent = this.parent; if (parent == null) { // Root bone. Skeleton skeleton = this.skeleton; @@ -146,8 +150,7 @@ namespace Spine { pc = 0; pd = 1; do { - cos = MathUtils.CosDeg(parent.appliedRotation); - sin = MathUtils.SinDeg(parent.appliedRotation); + float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation); float temp = pa * cos + pb * sin; pb = pa * -sin + pb * cos; pa = temp; @@ -168,9 +171,7 @@ namespace Spine { pc = 0; pd = 1; do { - float r = parent.rotation; - cos = MathUtils.CosDeg(r); - sin = MathUtils.SinDeg(r); + float r = parent.appliedRotation, cos = MathUtils.CosDeg(r), sin = MathUtils.SinDeg(r); float psx = parent.appliedScaleX, psy = parent.appliedScaleY; float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy; float temp = pa * za + pb * zc; @@ -221,6 +222,8 @@ namespace Spine { rotation = data.rotation; scaleX = data.scaleX; scaleY = data.scaleY; + shearX = data.shearX; + shearY = data.shearY; } public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { diff --git a/spine-csharp/src/BoneData.cs b/spine-csharp/src/BoneData.cs index 29214e2f6..21b1ed555 100644 --- a/spine-csharp/src/BoneData.cs +++ b/spine-csharp/src/BoneData.cs @@ -35,7 +35,7 @@ namespace Spine { public class BoneData { internal BoneData parent; internal String name; - internal float length, x, y, rotation, scaleX = 1, scaleY = 1; + internal float length, x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; internal bool inheritScale = true, inheritRotation = true; /// May be null. @@ -47,6 +47,8 @@ namespace Spine { public float Rotation { get { return rotation; } set { rotation = value; } } public float ScaleX { get { return scaleX; } set { scaleX = value; } } public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float ShearX { get { return shearX; } set { shearX = value; } } + public float ShearY { get { return shearY; } set { shearY = value; } } public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } diff --git a/spine-csharp/src/IkConstraint.cs b/spine-csharp/src/IkConstraint.cs index 63112c064..3b4530135 100644 --- a/spine-csharp/src/IkConstraint.cs +++ b/spine-csharp/src/IkConstraint.cs @@ -83,14 +83,17 @@ namespace Spine { /// 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. static public void Apply (Bone bone, float targetX, float targetY, float alpha) { - float parentRotation = bone.parent == null ? 0 : bone.parent.WorldRotationX; - float rotation = bone.rotation; - float rotationIK = MathUtils.Atan2(targetY - bone.worldY, targetX - bone.worldX) * MathUtils.radDeg - parentRotation; - if ((bone.worldSignX != bone.worldSignY) != (bone.skeleton.flipX != (bone.skeleton.flipY != Bone.yDown))) - rotationIK = 360 - rotationIK; - if (rotationIK > 180) rotationIK -= 360; + Bone pp = bone.parent; + float id = 1 / (pp.a * pp.d - pp.b * pp.c); + float x = targetX - pp.worldX, y = targetY - pp.worldY; + float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y; + float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX; + if (bone.scaleX < 0) rotationIK += 180; + 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); + bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + (rotationIK - bone.rotation) * alpha, bone.appliedScaleX, + bone.appliedScaleY, bone.shearX, bone.shearY); } /// Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as @@ -125,22 +128,12 @@ namespace Spine { } else os2 = 0; Bone pp = parent.parent; - float tx, ty, dx, dy; - if (pp == null) { - tx = targetX - px; - ty = targetY - py; - dx = child.worldX - px; - dy = child.worldY - py; - } else { - float a = pp.a, b = pp.b, c = pp.c, d = pp.d, invDet = 1 / (a * d - b * c); - float 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; - } + float ppa = pp.a, ppb = pp.b, ppc = pp.c, ppd = pp.d, id = 1 / (ppa * ppd - ppb * ppc); + float x = targetX - pp.worldX, y = targetY - pp.worldY; + float tx = (x * ppd - y * ppb) * id - px, ty = (y * ppa - x * ppc) * id - py; + x = child.worldX - pp.worldX; + y = child.worldY - pp.worldY; + float dx = (x * ppd - y * ppb) * id - px, dy = (y * ppa - x * ppc) * id - py; float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; if (u) { l2 *= psx; @@ -162,40 +155,41 @@ namespace Spine { float r0 = q / c2, r1 = c0 / q; float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; if (r * r <= dd) { - float y1 = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - MathUtils.Atan2(y1, r); - a2 = MathUtils.Atan2(y1 / psy, (r - l1) / psx); + y = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - MathUtils.Atan2(y, r); + a2 = MathUtils.Atan2(y / psy, (r - l1) / psx); goto outer; } } float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0; float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0; - float x = l1 + a, dist = x * x; - if (dist > maxDist) { + x = l1 + a; + d = x * x; + if (d > maxDist) { maxAngle = 0; - maxDist = dist; + maxDist = d; maxX = x; } x = l1 - a; - dist = x * x; - if (dist < minDist) { + d = x * x; + if (d < minDist) { minAngle = MathUtils.PI; - minDist = dist; + minDist = d; minX = x; } float angle = (float)Math.Acos(-a * l1 / (aa - bb)); x = a * MathUtils.Cos(angle) + l1; - float y = b * MathUtils.Sin(angle); - dist = x * x + y * y; - if (dist < minDist) { + y = b * MathUtils.Sin(angle); + d = x * x + y * y; + if (d < minDist) { minAngle = angle; - minDist = dist; + minDist = d; minX = x; minY = y; } - if (dist > maxDist) { + if (d > maxDist) { maxAngle = angle; - maxDist = dist; + maxDist = d; maxX = x; maxY = y; } @@ -210,15 +204,15 @@ namespace Spine { outer: float os = MathUtils.Atan2(cy, cx) * s2; a1 = (a1 - os) * MathUtils.radDeg + os1; - a2 = (a2 + os) * MathUtils.radDeg * s2 + os2; + a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2; if (a1 > 180) a1 -= 360; else if (a1 < -180) a1 += 360; if (a2 > 180) a2 -= 360; else if (a2 < -180) a2 += 360; float rotation = parent.rotation; - parent.UpdateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY); + parent.UpdateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY, 0, 0); rotation = child.rotation; - child.UpdateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY); + child.UpdateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY, child.shearX, child.shearY); } } } diff --git a/spine-csharp/src/MathUtils.cs b/spine-csharp/src/MathUtils.cs index 225b20f6c..665cf8f06 100644 --- a/spine-csharp/src/MathUtils.cs +++ b/spine-csharp/src/MathUtils.cs @@ -34,6 +34,7 @@ using System; namespace Spine { public static class MathUtils { public const float PI = 3.1415927f; + public const float PI2 = PI * 2; public const float radDeg = 180f / PI; public const float degRad = PI / 180; diff --git a/spine-csharp/src/Skeleton.cs b/spine-csharp/src/Skeleton.cs index a8cad1246..ea88ecd76 100644 --- a/spine-csharp/src/Skeleton.cs +++ b/spine-csharp/src/Skeleton.cs @@ -127,8 +127,7 @@ namespace Spine { for (int i = 0; i < transformConstraintsCount; i++) { TransformConstraint transformConstraint = transformConstraints.Items[i]; for (int ii = updateCache.Count - 1; i >= 0; ii--) { - IUpdatable updateable = updateCache.Items[ii]; - if (updateable == transformConstraint.bone || updateable == transformConstraint.target) { + if (updateCache.Items[ii] == transformConstraint.bone) { updateCache.Insert(ii + 1, transformConstraint); break; } @@ -165,9 +164,11 @@ namespace Spine { ExposedList transformConstraints = this.transformConstraints; for (int i = 0, n = transformConstraints.Count; i < n; i++) { TransformConstraint constraint = transformConstraints.Items[i]; - constraint.translateMix = constraint.data.translateMix; - constraint.x = constraint.data.x; - constraint.y = constraint.data.y; + TransformConstraintData data = constraint.data; + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; + constraint.shearMix = data.shearMix; } } diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs index 830ac5785..d5228f43e 100644 --- a/spine-csharp/src/SkeletonBinary.cs +++ b/spine-csharp/src/SkeletonBinary.cs @@ -29,6 +29,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#if (UNITY_5 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) +#define IS_UNITY +#endif + using System; using System.IO; using System.Collections.Generic; @@ -40,11 +44,12 @@ using Windows.Storage; namespace Spine { public class SkeletonBinary { - public const int TIMELINE_SCALE = 0; - public const int TIMELINE_ROTATE = 1; - public const int TIMELINE_TRANSLATE = 2; - public const int TIMELINE_ATTACHMENT = 3; - public const int TIMELINE_COLOR = 4; + public const int TIMELINE_ROTATE = 0; + public const int TIMELINE_TRANSLATE = 1; + public const int TIMELINE_SCALE = 2; + public const int TIMELINE_SHEAR = 3; + public const int TIMELINE_ATTACHMENT = 4; + public const int TIMELINE_COLOR = 5; public const int CURVE_LINEAR = 0; public const int CURVE_STEPPED = 1; @@ -65,10 +70,8 @@ namespace Spine { this.attachmentLoader = attachmentLoader; Scale = 1; } - - #if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) - #if WINDOWS_STOREAPP - + + #if !ISUNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) { @@ -95,7 +98,6 @@ namespace Spine { } #endif // WINDOWS_STOREAPP - #endif // !(UNITY) public SkeletonData ReadSkeletonData (Stream input) { if (input == null) throw new ArgumentNullException("input"); @@ -121,14 +123,16 @@ namespace Spine { String name = ReadString(input); BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; BoneData boneData = new BoneData(name, parent); + boneData.rotation = ReadFloat(input); boneData.x = ReadFloat(input) * scale; boneData.y = ReadFloat(input) * scale; boneData.scaleX = ReadFloat(input); boneData.scaleY = ReadFloat(input); - boneData.rotation = ReadFloat(input); + boneData.shearX = ReadFloat(input); + boneData.shearY = ReadFloat(input); boneData.length = ReadFloat(input) * scale; - boneData.inheritScale = ReadBoolean(input); boneData.inheritRotation = ReadBoolean(input); + boneData.inheritScale = ReadBoolean(input); if (nonessential) ReadInt(input); // Skip bone color. skeletonData.bones.Add(boneData); } @@ -149,9 +153,16 @@ namespace Spine { TransformConstraintData transformConstraintData = new TransformConstraintData(ReadString(input)); transformConstraintData.bone = skeletonData.bones.Items[ReadVarint(input, true)]; transformConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)]; + transformConstraintData.offsetRotation = ReadFloat(input); + transformConstraintData.offsetX = ReadFloat(input) * scale; + transformConstraintData.offsetY = ReadFloat(input) * scale; + transformConstraintData.offsetScaleX = ReadFloat(input); + transformConstraintData.offsetScaleY = ReadFloat(input); + transformConstraintData.offsetShearY = ReadFloat(input); + transformConstraintData.rotateMix = ReadFloat(input); transformConstraintData.translateMix = ReadFloat(input); - transformConstraintData.x = ReadFloat(input) * scale; - transformConstraintData.y = ReadFloat(input) * scale; + transformConstraintData.scaleMix = ReadFloat(input); + transformConstraintData.shearMix = ReadFloat(input); skeletonData.transformConstraints.Add(transformConstraintData); } @@ -247,11 +258,11 @@ namespace Spine { switch (type) { case AttachmentType.region: { String path = ReadString(input); + float rotation = ReadFloat(input); float x = ReadFloat(input); float y = ReadFloat(input); float scaleX = ReadFloat(input); float scaleY = ReadFloat(input); - float rotation = ReadFloat(input); float width = ReadFloat(input); float height = ReadFloat(input); int color = ReadInt(input); @@ -508,11 +519,14 @@ namespace Spine { break; } case TIMELINE_TRANSLATE: - case TIMELINE_SCALE: { + case TIMELINE_SCALE: + case TIMELINE_SHEAR: { TranslateTimeline timeline; float timelineScale = 1; if (timelineType == TIMELINE_SCALE) timeline = new ScaleTimeline(frameCount); + else if (timelineType == TIMELINE_SHEAR) + timeline = new ShearTimeline(frameCount); else { timeline = new TranslateTimeline(frameCount); timelineScale = scale; @@ -533,10 +547,10 @@ namespace Spine { // IK timelines. for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - IkConstraintData ikConstraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)]; + IkConstraintData constraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)]; int frameCount = ReadVarint(input, true); IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); @@ -545,6 +559,20 @@ namespace Spine { duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); } + // Transform constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) { + TransformConstraintData constraint = skeletonData.transformConstraints.Items[ReadVarint(input, true)]; + int frameCount = ReadVarint(input, true); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); + timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]); + } + // FFD timelines. for (int i = 0, n = ReadVarint(input, true); i < n; i++) { Skin skin = skeletonData.skins.Items[ReadVarint(input, true)]; diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs index 84691e890..feb110231 100644 --- a/spine-csharp/src/SkeletonJson.cs +++ b/spine-csharp/src/SkeletonJson.cs @@ -59,9 +59,7 @@ namespace Spine { Scale = 1; } - #if !(IS_UNITY) - #if WINDOWS_STOREAPP - + #if !(IS_UNITY) && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -88,9 +86,7 @@ namespace Spine { return skeletonData; } } - #endif // WINDOWS_STOREAPP - #endif // !UNITY public SkeletonData ReadSkeletonData (TextReader reader) { if (reader == null) throw new ArgumentNullException("reader cannot be null."); @@ -125,6 +121,8 @@ namespace Spine { boneData.rotation = GetFloat(boneMap, "rotation", 0); boneData.scaleX = GetFloat(boneMap, "scaleX", 1); boneData.scaleY = GetFloat(boneMap, "scaleY", 1); + boneData.shearX = GetFloat(boneMap, "shearX", 1); + boneData.shearY = GetFloat(boneMap, "shearY", 1); boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true); boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true); skeletonData.bones.Add(boneData); @@ -165,9 +163,17 @@ namespace Spine { transformConstraintData.target = skeletonData.FindBone(targetName); if (transformConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); + transformConstraintData.offsetRotation = GetFloat(transformMap, "rotation", 0); + transformConstraintData.offsetX = GetFloat(transformMap, "x", 0) * scale; + transformConstraintData.offsetY = GetFloat(transformMap, "y", 0) * scale; + transformConstraintData.offsetScaleX = GetFloat(transformMap, "scaleX", 0); + transformConstraintData.offsetScaleY = GetFloat(transformMap, "scaleY", 0); + transformConstraintData.offsetShearY = GetFloat(transformMap, "shearY", 0); + + transformConstraintData.rotateMix = GetFloat(transformMap, "rotateMix", 1); transformConstraintData.translateMix = GetFloat(transformMap, "translateMix", 1); - transformConstraintData.x = GetFloat(transformMap, "x", 0) * scale; - transformConstraintData.y = GetFloat(transformMap, "y", 0) * scale; + transformConstraintData.scaleMix = GetFloat(transformMap, "scaleMix", 1); + transformConstraintData.shearMix = GetFloat(transformMap, "shearMix", 1); skeletonData.transformConstraints.Add(transformConstraintData); } @@ -520,11 +526,13 @@ namespace Spine { timelines.Add(timeline); duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); - } else if (timelineName == "translate" || timelineName == "scale") { + } else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") { TranslateTimeline timeline; float timelineScale = 1; if (timelineName == "scale") timeline = new ScaleTimeline(values.Count); + else if (timelineName == "shear") + timeline = new ShearTimeline(values.Count); else { timeline = new TranslateTimeline(values.Count); timelineScale = scale; @@ -534,8 +542,8 @@ namespace Spine { int frameIndex = 0; foreach (Dictionary valueMap in values) { float time = (float)valueMap["time"]; - float x = valueMap.ContainsKey("x") ? (float)valueMap["x"] : 0; - float y = valueMap.ContainsKey("y") ? (float)valueMap["y"] : 0; + float x = GetFloat(valueMap, "x", 0); + float y = GetFloat(valueMap, "y", 0); timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale); ReadCurve(timeline, frameIndex, valueMap); frameIndex++; @@ -549,17 +557,18 @@ namespace Spine { } } + // IK timelines. if (map.ContainsKey("ik")) { - foreach (KeyValuePair ikMap in (Dictionary)map["ik"]) { - IkConstraintData ikConstraint = skeletonData.FindIkConstraint(ikMap.Key); - var values = (List)ikMap.Value; + foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) { + IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); int frameIndex = 0; foreach (Dictionary valueMap in values) { float time = (float)valueMap["time"]; - float mix = valueMap.ContainsKey("mix") ? (float)valueMap["mix"] : 1; - bool bendPositive = valueMap.ContainsKey("bendPositive") ? (bool)valueMap["bendPositive"] : true; + float mix = GetFloat(valueMap, "mix", 1); + bool bendPositive = GetBoolean(valueMap, "bendPositive", true); timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); ReadCurve(timeline, frameIndex, valueMap); frameIndex++; @@ -569,6 +578,30 @@ namespace Spine { } } + // Transform constraint timelines. + if (map.ContainsKey("transform")) { + foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new TransformConstraintTimeline(values.Count); + timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) { + float time = (float)valueMap["time"]; + float rotateMix = GetFloat(valueMap, "rotateMix", 1); + float translateMix = GetFloat(valueMap, "translateMix", 1); + float scaleMix = GetFloat(valueMap, "scaleMix", 1); + float shearMix = GetFloat(valueMap, "shearMix", 1); + timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); + } + } + + // FFD timelines. if (map.ContainsKey("ffd")) { foreach (KeyValuePair ffdMap in (Dictionary)map["ffd"]) { Skin skin = skeletonData.FindSkin(ffdMap.Key); diff --git a/spine-csharp/src/TransformConstraint.cs b/spine-csharp/src/TransformConstraint.cs index d5b03be6e..911aa5075 100644 --- a/spine-csharp/src/TransformConstraint.cs +++ b/spine-csharp/src/TransformConstraint.cs @@ -36,38 +36,94 @@ namespace Spine { public class TransformConstraint : IUpdatable { internal TransformConstraintData data; internal Bone bone, target; - internal float translateMix; - internal float x, y; + internal float rotateMix, translateMix, scaleMix, shearMix; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; public TransformConstraintData Data { get { return data; } } public Bone Bone { get { return bone; } set { bone = value; } } public Bone Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } + + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { if (data == null) throw new ArgumentNullException("data cannot be null."); if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); this.data = data; translateMix = data.translateMix; - x = data.x; - y = data.y; + rotateMix = data.rotateMix; + scaleMix = data.scaleMix; + shearMix = data.shearMix; + offsetX = data.offsetX; + offsetY = data.offsetY; + offsetScaleX = data.offsetScaleX; + offsetScaleY = data.offsetScaleY; + offsetShearY = data.offsetShearY; bone = skeleton.FindBone(data.bone.name); target = skeleton.FindBone(data.target.name); } - public void Update () { - Apply(); + public void Apply () { + Update(); } - public void Apply () { + public void Update () { + Bone bone = this.bone; + Bone target = this.target; + + if (rotateMix > 0) { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(target.c, target.a) - MathUtils.Atan2(c, a) + offsetRotation * MathUtils.degRad; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r); + bone.a = cos * a - sin * c; + bone.b = cos * b - sin * d; + bone.c = sin * a + cos * c; + bone.d = sin * b + cos * d; + } + + if (scaleMix > 0) { + float bs = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + float ts = (float)Math.Sqrt(target.a * target.a + target.c * target.c); + float s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleX) * scaleMix) / bs : 0; + bone.a *= s; + bone.c *= s; + bs = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + ts = (float)Math.Sqrt(target.b * target.b + target.d * target.d); + s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleY) * scaleMix) / bs : 0; + bone.b *= s; + bone.d *= s; + } + + if (shearMix > 0) { + float b = bone.b, d = bone.d; + float by = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(target.d, target.b) - MathUtils.Atan2(target.c, target.a) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r = by + (r + offsetShearY * MathUtils.degRad) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + } + float translateMix = this.translateMix; if (translateMix > 0) { - Bone bone = this.bone; float tx, ty; - target.LocalToWorld(x, y, out tx, out ty); + target.LocalToWorld(offsetX, offsetY, out tx, out ty); bone.worldX += (tx - bone.worldX) * translateMix; bone.worldY += (ty - bone.worldY) * translateMix; } diff --git a/spine-csharp/src/TransformConstraintData.cs b/spine-csharp/src/TransformConstraintData.cs index 907add275..eeee5d21e 100644 --- a/spine-csharp/src/TransformConstraintData.cs +++ b/spine-csharp/src/TransformConstraintData.cs @@ -36,15 +36,23 @@ namespace Spine { public class TransformConstraintData { internal String name; internal BoneData bone, target; - internal float translateMix; - internal float x, y; + internal float rotateMix, translateMix, scaleMix, shearMix; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; public String Name { get { return name; } } public BoneData Bone { get { return bone; } set { bone = value; } } public BoneData Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } + + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } public TransformConstraintData (String name) { if (name == null) throw new ArgumentNullException("name cannot be null."); diff --git a/spine-unity/README.md b/spine-unity/README.md index b52cff4b6..465178b58 100644 --- a/spine-unity/README.md +++ b/spine-unity/README.md @@ -14,7 +14,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f ## Spine version -spine-unity works with data exported from Spine 3.1.08. Updating spine-unity to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress. +spine-unity works with data exported from the latest version of Spine. spine-unity supports all Spine features.