diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index b994172c0..45f420081 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -1210,15 +1210,15 @@ namespace Spine { } public class IkConstraintTimeline : CurveTimeline { - public const int ENTRIES = 4; - private const int PREV_TIME = -4, PREV_MIX = -3, PREV_BEND_DIRECTION = -2, PREV_STRETCH = -1; - private const int MIX = 1, BEND_DIRECTION = 2, STRETCH = 3; + public const int ENTRIES = 5; + private const int PREV_TIME = -5, PREV_MIX = -4, PREV_BEND_DIRECTION = -3, PREV_COMPRESS = -2, PREV_STRETCH = -1; + private const int MIX = 1, BEND_DIRECTION = 2, COMPRESS = 3, STRETCH = 4; internal int ikConstraintIndex; internal float[] frames; public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... + public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, compress, stretch ... override public int PropertyId { get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } @@ -1229,12 +1229,13 @@ namespace Spine { frames = new float[frameCount * ENTRIES]; } - /// Sets the time, mix and bend direction of the specified keyframe. - public void SetFrame (int frameIndex, float time, float mix, int bendDirection, bool stretch) { + /// Sets the time, mix, bend direction, compress and stretch of the specified keyframe. + public void SetFrame (int frameIndex, float time, float mix, int bendDirection, bool compress, bool stretch) { frameIndex *= ENTRIES; frames[frameIndex] = time; frames[frameIndex + MIX] = mix; frames[frameIndex + BEND_DIRECTION] = bendDirection; + frames[frameIndex + COMPRESS] = compress ? 1 : 0; frames[frameIndex + STRETCH] = stretch ? 1 : 0; } @@ -1246,11 +1247,13 @@ namespace Spine { case MixBlend.Setup: constraint.mix = constraint.data.mix; constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; constraint.stretch = constraint.data.stretch; return; case MixBlend.First: constraint.mix += (constraint.data.mix - constraint.mix) * alpha; constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; constraint.stretch = constraint.data.stretch; return; } @@ -1262,15 +1265,18 @@ namespace Spine { constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; if (direction == MixDirection.Out) { constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; constraint.stretch = constraint.data.stretch; } else { constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; + constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0; constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0; } } else { constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; if (direction == MixDirection.In) { constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; + constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0; constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0; } } @@ -1285,10 +1291,22 @@ namespace Spine { if (blend == MixBlend.Setup) { constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; - constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; + if (direction == MixDirection.Out) { + constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; + constraint.stretch = constraint.data.stretch; + } else { + constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + constraint.compress = frames[frame + PREV_COMPRESS] != 0; + constraint.stretch = frames[frame + PREV_STRETCH] != 0; + } } else { constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; - if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + if (direction == MixDirection.In) { + constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + constraint.compress = frames[frame + PREV_COMPRESS] != 0; + constraint.stretch = frames[frame + PREV_STRETCH] != 0; + } } } } diff --git a/spine-csharp/src/IkConstraint.cs b/spine-csharp/src/IkConstraint.cs index 127b51a42..9b70a6a96 100644 --- a/spine-csharp/src/IkConstraint.cs +++ b/spine-csharp/src/IkConstraint.cs @@ -36,7 +36,7 @@ namespace Spine { internal ExposedList bones = new ExposedList(); internal Bone target; internal int bendDirection; - internal bool stretch; + internal bool compress, stretch; internal float mix; public IkConstraintData Data { get { return data; } } @@ -59,9 +59,17 @@ namespace Spine { set { bendDirection = value; } } + /// + /// When true and only a single bone is being constrained, + /// if the target is too close, the bone is scaled to reach it. + public bool Compress { + get { return compress; } + set { compress = value; } + } + /// /// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it. - /// IF the parent bone has nonuniform scale, stretching is not applied. + /// If the parent bone has nonuniform scale, stretching is not applied. public bool Stretch { get { return stretch; } set { stretch = value; } @@ -79,6 +87,7 @@ namespace Spine { this.data = data; mix = data.mix; bendDirection = data.bendDirection; + compress = data.compress; stretch = data.stretch; bones = new ExposedList(data.bones.Count); @@ -97,7 +106,7 @@ namespace Spine { ExposedList bones = this.bones; switch (bones.Count) { case 1: - Apply(bones.Items[0], target.worldX, target.worldY, stretch, mix); + Apply(bones.Items[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); break; case 2: Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, mix); @@ -109,9 +118,8 @@ namespace Spine { return data.name; } - /// 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, bool stretch, float alpha) { + /// Applies 1 bone IK. The target is specified in the world coordinate system. + static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha) { if (!bone.appliedValid) bone.UpdateAppliedTransform(); Bone p = bone.parent; float id = 1 / (p.a * p.d - p.b * p.c); @@ -121,14 +129,18 @@ namespace Spine { if (bone.ascaleX < 0) rotationIK += 180; if (rotationIK > 180) rotationIK -= 360; - else if (rotationIK < -180) + else if (rotationIK < -180) // rotationIK += 360; - float sx = bone.ascaleX; - if (stretch) { + float sx = bone.ascaleX, sy = bone.ascaleY; + if (compress || stretch) { float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); - if (dd > b && b > 0.0001f) sx *= (dd / b - 1) * alpha + 1; + if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) { + float s = (dd / b - 1) * alpha + 1; + sx *= s; + if (uniform) sy *= s; + } } - bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, bone.ascaleY, bone.ashearX, + bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, bone.ashearY); } diff --git a/spine-csharp/src/IkConstraintData.cs b/spine-csharp/src/IkConstraintData.cs index e1b25a3d2..e71c57f5f 100644 --- a/spine-csharp/src/IkConstraintData.cs +++ b/spine-csharp/src/IkConstraintData.cs @@ -39,7 +39,7 @@ namespace Spine { internal List bones = new List(); internal BoneData target; internal int bendDirection = 1; - internal bool stretch; + internal bool compress, stretch, uniform; internal float mix = 1; /// The IK constraint's name, which is unique within the skeleton. @@ -63,12 +63,27 @@ namespace Spine { set { target = value; } } + /// + /// A percentage (0-1) that controls the mix between the constraint and unconstrained rotations. + public float Mix { + get { return mix; } + set { mix = value; } + } + /// Controls the bend direction of the IK bones, either 1 or -1. public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + /// + /// When true, and only a single bone is being constrained, + /// if the target is too close, the bone is scaled to reach it. + public bool Compress { + get { return compress; } + set { compress = value; } + } + /// /// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it. /// If the bone has local nonuniform scale, stretching is not applied. @@ -77,7 +92,13 @@ namespace Spine { set { stretch = value; } } - public float Mix { get { return mix; } set { mix = value; } } + /// + /// When true, only a single bone is being constrained and Compress or Stretch is used, + /// the bone is scaled both on the X and Y axes. + public bool Uniform { + get { return uniform; } + set { uniform = value; } + } public IkConstraintData (string name) { if (name == null) throw new ArgumentNullException("name", "name cannot be null."); diff --git a/spine-csharp/src/Skeleton.cs b/spine-csharp/src/Skeleton.cs index 2ce1b0df4..27ffab499 100644 --- a/spine-csharp/src/Skeleton.cs +++ b/spine-csharp/src/Skeleton.cs @@ -308,9 +308,10 @@ namespace Spine { var ikConstraintsItems = this.ikConstraints.Items; for (int i = 0, n = ikConstraints.Count; i < n; i++) { IkConstraint constraint = ikConstraintsItems[i]; - constraint.bendDirection = constraint.data.bendDirection; - constraint.stretch = constraint.data.stretch; constraint.mix = constraint.data.mix; + constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; + constraint.stretch = constraint.data.stretch; } var transformConstraintsItems = this.transformConstraints.Items; diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs index fc46572e2..a2ed6aca5 100644 --- a/spine-csharp/src/SkeletonBinary.cs +++ b/spine-csharp/src/SkeletonBinary.cs @@ -210,7 +210,9 @@ namespace Spine { data.target = skeletonData.bones.Items[ReadVarint(input, true)]; data.mix = ReadFloat(input); data.bendDirection = ReadSByte(input); + data.compress = ReadBoolean(input); data.stretch = ReadBoolean(input); + data.uniform = ReadBoolean(input); skeletonData.ikConstraints.Add(data); } @@ -646,10 +648,11 @@ namespace Spine { for (int i = 0, n = ReadVarint(input, true); i < n; i++) { int index = ReadVarint(input, true); int frameCount = ReadVarint(input, true); - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); - timeline.ikConstraintIndex = index; + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) { + ikConstraintIndex = index + }; for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input), ReadBoolean(input)); + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input), ReadBoolean(input), ReadBoolean(input)); if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); } timelines.Add(timeline); diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs index 5ba575a80..04a740f29 100644 --- a/spine-csharp/src/SkeletonJson.cs +++ b/spine-csharp/src/SkeletonJson.cs @@ -180,10 +180,11 @@ namespace Spine { string targetName = (string)constraintMap["target"]; data.target = skeletonData.FindBone(targetName); if (data.target == null) throw new Exception("Target bone not found: " + targetName); - - data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; - data.stretch = GetBoolean(constraintMap, "stretch", false); data.mix = GetFloat(constraintMap, "mix", 1); + data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + data.compress = GetBoolean(constraintMap, "compress", false); + data.stretch = GetBoolean(constraintMap, "stretch", false); + data.uniform = GetBoolean(constraintMap, "uniform", false); skeletonData.ikConstraints.Add(data); } @@ -595,11 +596,14 @@ namespace Spine { timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); int frameIndex = 0; foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float mix = GetFloat(valueMap, "mix", 1); - bool bendPositive = GetBoolean(valueMap, "bendPositive", true); - bool stretch = GetBoolean(valueMap, "stretch", false); - timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1, stretch); + timeline.SetFrame( + frameIndex, + (float)valueMap["time"], + GetFloat(valueMap, "mix", 1), + GetBoolean(valueMap, "bendPositive", true) ? 1 : -1, + GetBoolean(valueMap, "compress", true), + GetBoolean(valueMap, "stretch", false) + ); ReadCurve(valueMap, timeline, frameIndex); frameIndex++; }