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++;
}