diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index 1288d6dbf..631ebbca2 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -1352,15 +1352,16 @@ namespace Spine { } } - /// Changes an IK constraint's , , - /// , and . + /// Changes an IK constraint's , , + /// , , and . public class IkConstraintTimeline : CurveTimeline { - 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; + public const int ENTRIES = 6; + private const int PREV_TIME = -6, PREV_MIX = -5, PREV_SOFTNESS = -4, PREV_BEND_DIRECTION = -3, PREV_COMPRESS = -2, + PREV_STRETCH = -1; + private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; internal int ikConstraintIndex; - internal float[] frames; // time, mix, bendDirection, compress, stretch, ... + internal float[] frames; // time, mix, softness, bendDirection, compress, stretch, ... public IkConstraintTimeline (int frameCount) : base(frameCount) { @@ -1382,14 +1383,16 @@ namespace Spine { } } - /// The time in seconds, mix, bend direction, compress, and stretch for each key frame. + /// The time in seconds, mix, softness, bend direction, compress, and stretch for each key frame. public float[] Frames { get { return frames; } set { frames = value; } } - /// Sets the time in seconds, mix, bend direction, compress, and stretch for the specified key frame. - public void SetFrame (int frameIndex, float time, float mix, int bendDirection, bool compress, bool stretch) { + /// Sets the time in seconds, mix, softness, bend direction, compress, and stretch for the specified key frame. + public void SetFrame (int frameIndex, float time, float mix, float softness, int bendDirection, bool compress, + bool stretch) { frameIndex *= ENTRIES; frames[frameIndex] = time; frames[frameIndex + MIX] = mix; + frames[frameIndex + SOFTNESS] = softness; frames[frameIndex + BEND_DIRECTION] = bendDirection; frames[frameIndex + COMPRESS] = compress ? 1 : 0; frames[frameIndex + STRETCH] = stretch ? 1 : 0; @@ -1404,12 +1407,14 @@ namespace Spine { switch (blend) { case MixBlend.Setup: constraint.mix = constraint.data.mix; + constraint.softness = constraint.data.softness; 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.softness += (constraint.data.softness - constraint.softness) * alpha; constraint.bendDirection = constraint.data.bendDirection; constraint.compress = constraint.data.compress; constraint.stretch = constraint.data.stretch; @@ -1421,6 +1426,8 @@ namespace Spine { if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. if (blend == MixBlend.Setup) { constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; + constraint.softness = constraint.data.softness + + (frames[frames.Length + PREV_SOFTNESS] - constraint.data.softness) * alpha; if (direction == MixDirection.Out) { constraint.bendDirection = constraint.data.bendDirection; constraint.compress = constraint.data.compress; @@ -1432,6 +1439,7 @@ namespace Spine { } } else { constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; + constraint.softness += (frames[frames.Length + PREV_SOFTNESS] - constraint.softness) * alpha; if (direction == MixDirection.In) { constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0; @@ -1444,11 +1452,14 @@ namespace Spine { // Interpolate between the previous frame and the current frame. int frame = Animation.BinarySearch(frames, time, ENTRIES); float mix = frames[frame + PREV_MIX]; + float softness = frames[frame + PREV_SOFTNESS]; float frameTime = frames[frame]; float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); if (blend == MixBlend.Setup) { constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; + constraint.softness = constraint.data.softness + + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.data.softness) * alpha; if (direction == MixDirection.Out) { constraint.bendDirection = constraint.data.bendDirection; constraint.compress = constraint.data.compress; @@ -1460,6 +1471,7 @@ namespace Spine { } } else { constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; + constraint.softness += (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.softness) * alpha; if (direction == MixDirection.In) { constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; constraint.compress = frames[frame + PREV_COMPRESS] != 0; diff --git a/spine-csharp/src/IkConstraint.cs b/spine-csharp/src/IkConstraint.cs index f30cfdfc0..6c5e0f3a5 100644 --- a/spine-csharp/src/IkConstraint.cs +++ b/spine-csharp/src/IkConstraint.cs @@ -43,7 +43,7 @@ namespace Spine { internal Bone target; internal int bendDirection; internal bool compress, stretch; - internal float mix = 1; + internal float mix = 1, softness; internal bool active; @@ -52,6 +52,7 @@ namespace Spine { if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); this.data = data; mix = data.mix; + softness = data.softness; bendDirection = data.bendDirection; compress = data.compress; stretch = data.stretch; @@ -72,6 +73,7 @@ namespace Spine { bones.Add(skeleton.Bones.Items[bone.data.index]); target = skeleton.Bones.Items[constraint.target.data.index]; mix = constraint.mix; + softness = constraint.softness; bendDirection = constraint.bendDirection; compress = constraint.compress; stretch = constraint.stretch; @@ -90,7 +92,7 @@ namespace Spine { 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); + Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, softness, mix); break; } } @@ -112,6 +114,12 @@ namespace Spine { set { mix = value; } } + ///For two bone IK, the distance from the maximum reach of the bones that rotation will slow. + public float Softness { + get { return softness; } + set { softness = value; } + } + /// Controls the bend direction of the IK bones, either 1 or -1. public int BendDirection { get { return bendDirection; } @@ -174,7 +182,8 @@ namespace Spine { /// Applies 2 bone IK. The target is specified in the world coordinate system. /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float alpha) { + static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float softness, + float alpha) { if (alpha == 0) { child.UpdateWorldTransform(); return; @@ -216,12 +225,29 @@ namespace Spine { b = pp.b; c = pp.c; d = pp.d; - float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; - float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py, dd = tx * tx + ty * ty; - x = cwx - pp.worldX; - y = cwy - pp.worldY; + float id = 1 / (a * d - b * c), x = cwx - pp.worldX, y = cwy - pp.worldY; float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (l1 < 0.0001f) { + Apply(parent, targetX, targetY, false, stretch, false, alpha); + child.UpdateWorldTransform(cx, cy, 0, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + return; + } + x = targetX - pp.worldX; + y = targetY - pp.worldY; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + float dd = tx * tx + ty * ty; + if (softness != 0) { + softness *= psx * (csx + 1) / 2; + float td = (float)Math.Sqrt(dd), sd = td - l1 - l2 * psx + softness; + if (sd > 0) { + float p = Math.Min(1, sd / (softness * 2)) - 1; + p = (sd - softness * (1 - p * p)) / td; + tx -= p * tx; + ty -= p * ty; + dd = tx * tx + ty * ty; + } + } if (u) { l2 *= psx; float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); @@ -229,7 +255,7 @@ namespace Spine { cos = -1; else if (cos > 1) { cos = 1; - if (stretch && l1 + l2 > 0.0001f) sx *= ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; + if (stretch) sx *= ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; } a2 = (float)Math.Acos(cos) * bendDir; a = l1 + l2 * cos; diff --git a/spine-csharp/src/IkConstraintData.cs b/spine-csharp/src/IkConstraintData.cs index 24d9d860b..cd0c4a717 100644 --- a/spine-csharp/src/IkConstraintData.cs +++ b/spine-csharp/src/IkConstraintData.cs @@ -37,7 +37,7 @@ namespace Spine { internal BoneData target; internal int bendDirection = 1; internal bool compress, stretch, uniform; - internal float mix = 1; + internal float mix = 1, softness; public IkConstraintData (string name) : base(name) { } @@ -60,6 +60,12 @@ namespace Spine { set { mix = value; } } + ///For two bone IK, the distance from the maximum reach of the bones that rotation will slow. + public float Softness { + get { return softness; } + set { softness = value; } + } + /// Controls the bend direction of the IK bones, either 1 or -1. public int BendDirection { get { return bendDirection; } diff --git a/spine-csharp/src/Skeleton.cs b/spine-csharp/src/Skeleton.cs index 4dcf13f97..47ae149dd 100644 --- a/spine-csharp/src/Skeleton.cs +++ b/spine-csharp/src/Skeleton.cs @@ -384,6 +384,7 @@ namespace Spine { for (int i = 0, n = ikConstraints.Count; i < n; i++) { IkConstraint constraint = ikConstraintsItems[i]; constraint.mix = constraint.data.mix; + constraint.softness = constraint.data.softness; constraint.bendDirection = constraint.data.bendDirection; constraint.compress = constraint.data.compress; constraint.stretch = constraint.data.stretch; diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs index f5620dca0..eecc4036d 100644 --- a/spine-csharp/src/SkeletonBinary.cs +++ b/spine-csharp/src/SkeletonBinary.cs @@ -210,6 +210,7 @@ namespace Spine { bones[ii] = skeletonData.bones.Items[input.ReadInt(true)]; data.target = skeletonData.bones.Items[input.ReadInt(true)]; data.mix = input.ReadFloat(); + data.softness = input.ReadFloat() * scale; data.bendDirection = input.ReadSByte(); data.compress = input.ReadBoolean(); data.stretch = input.ReadBoolean(); @@ -681,7 +682,7 @@ namespace Spine { ikConstraintIndex = index }; for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadSByte(), input.ReadBoolean(), + timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat() * scale, input.ReadSByte(), input.ReadBoolean(), input.ReadBoolean()); if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); } diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs index f213467bf..fb6649097 100644 --- a/spine-csharp/src/SkeletonJson.cs +++ b/spine-csharp/src/SkeletonJson.cs @@ -188,6 +188,7 @@ namespace Spine { data.target = skeletonData.FindBone(targetName); if (data.target == null) throw new Exception("IK target bone not found: " + targetName); data.mix = GetFloat(constraintMap, "mix", 1); + data.softness = GetFloat(constraintMap, "softness", 0) * scale; data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; data.compress = GetBoolean(constraintMap, "compress", false); data.stretch = GetBoolean(constraintMap, "stretch", false); @@ -648,6 +649,7 @@ namespace Spine { frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "mix", 1), + GetFloat(valueMap, "softness", 0) * scale, GetBoolean(valueMap, "bendPositive", true) ? 1 : -1, GetBoolean(valueMap, "compress", true), GetBoolean(valueMap, "stretch", false) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AnimationMatchModifier/AnimationMatchModifierAsset.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AnimationMatchModifier/AnimationMatchModifierAsset.cs index 0442fc805..bc0d2b9fd 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AnimationMatchModifier/AnimationMatchModifierAsset.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AnimationMatchModifier/AnimationMatchModifierAsset.cs @@ -224,7 +224,7 @@ namespace Spine.Unity.Modules { static IkConstraintTimeline GetFillerTimeline (IkConstraintTimeline timeline, SkeletonData skeletonData) { var t = new IkConstraintTimeline(1); var ikConstraintData = skeletonData.ikConstraints.Items[timeline.ikConstraintIndex]; - t.SetFrame(0, 0, ikConstraintData.mix, ikConstraintData.bendDirection, ikConstraintData.compress, ikConstraintData.stretch); + t.SetFrame(0, 0, ikConstraintData.mix, ikConstraintData.softness, ikConstraintData.bendDirection, ikConstraintData.compress, ikConstraintData.stretch); return t; } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs index 004f14291..17d505a0d 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs @@ -518,6 +518,7 @@ namespace Spine { case TimelineType.IkConstraint: ikc = skeleton.ikConstraints.Items[i]; ikc.mix = ikc.data.mix; + ikc.softness = ikc.data.softness; ikc.bendDirection = ikc.data.bendDirection; ikc.stretch = ikc.data.stretch; break;