diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index 6fa9f2d8c..87e9eba9c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -1295,15 +1295,15 @@ public class Animation { } } - /** Changes an IK constraint's {@link IkConstraint#getMix()}, {@link IkConstraint#getBendDirection()}, and - * {@link IkConstraint#getStretch()}. */ + /** Changes an IK constraint's {@link IkConstraint#getMix()}, {@link IkConstraint#getBendDirection()}, + * {@link IkConstraint#getStretch()}, and {@link IkConstraint#getCompress()}. */ static public class IkConstraintTimeline extends CurveTimeline { - static public final int ENTRIES = 4; - static private final int PREV_TIME = -4, PREV_MIX = -3, PREV_BEND_DIRECTION = -2, PREV_STRETCH = -1; - static private final int MIX = 1, BEND_DIRECTION = 2, STRETCH = 3; + static public final int ENTRIES = 5; + static private final int PREV_TIME = -5, PREV_MIX = -4, PREV_BEND_DIRECTION = -3, PREV_COMPRESS = -2, PREV_STRETCH = -1; + static private final int MIX = 1, BEND_DIRECTION = 2, COMPRESS = 3, STRETCH = 4; int ikConstraintIndex; - private final float[] frames; // time, mix, bendDirection, ... + private final float[] frames; // time, mix, bendDirection, compress, stretch, ... public IkConstraintTimeline (int frameCount) { super(frameCount); @@ -1324,17 +1324,18 @@ public class Animation { return ikConstraintIndex; } - /** The time in seconds, mix, and bend direction for each key frame. */ + /** The time in seconds, mix, bend direction, compress, and stretch for each key frame. */ public float[] getFrames () { return frames; } - /** Sets the time in seconds, mix, and bend direction for the specified key frame. */ - public void setFrame (int frameIndex, float time, float mix, int bendDirection, boolean stretch) { + /** 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, boolean compress, boolean 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; } @@ -1348,11 +1349,13 @@ public class Animation { case setup: constraint.mix = constraint.data.mix; constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; constraint.stretch = constraint.data.stretch; return; case 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; @@ -1363,15 +1366,18 @@ public class Animation { constraint.mix = constraint.data.mix + (frames[frames.length + PREV_MIX] - constraint.data.mix) * alpha; if (direction == 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 == 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; } } @@ -1388,15 +1394,18 @@ public class Animation { constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; if (direction == 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 == 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-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java index 77623cbad..4fbcebab4 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -43,7 +43,7 @@ public class IkConstraint implements Constraint { final Array bones; Bone target; int bendDirection; - boolean stretch; + boolean compress, stretch; float mix = 1; public IkConstraint (IkConstraintData data, Skeleton skeleton) { @@ -52,6 +52,7 @@ public class IkConstraint implements Constraint { this.data = data; mix = data.mix; bendDirection = data.bendDirection; + compress = data.compress; stretch = data.stretch; bones = new Array(data.bones.size); @@ -71,6 +72,7 @@ public class IkConstraint implements Constraint { target = skeleton.bones.get(constraint.target.data.index); mix = constraint.mix; bendDirection = constraint.bendDirection; + compress = constraint.compress; stretch = constraint.stretch; } @@ -84,7 +86,7 @@ public class IkConstraint implements Constraint { Array bones = this.bones; switch (bones.size) { case 1: - apply(bones.first(), target.worldX, target.worldY, stretch, mix); + apply(bones.first(), target.worldX, target.worldY, compress, stretch, data.uniform, mix); break; case 2: apply(bones.first(), bones.get(1), target.worldX, target.worldY, bendDirection, stretch, mix); @@ -128,8 +130,17 @@ public class IkConstraint implements Constraint { this.bendDirection = bendDirection; } - /** 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 local - * nonuniform scale, stretching is not applied. */ + /** When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. */ + public boolean getCompress () { + return compress; + } + + public void setCompress (boolean compress) { + this.compress = compress; + } + + /** When true, if the target is out of range, the parent bone is scaled to reach it. If more than one bone is being constrained + * and the parent bone has local nonuniform scale, stretch is not applied. */ public boolean getStretch () { return stretch; } @@ -148,7 +159,8 @@ public class IkConstraint implements Constraint { } /** Applies 1 bone IK. The target is specified in the world coordinate system. */ - static public void apply (Bone bone, float targetX, float targetY, boolean stretch, float alpha) { + static public void apply (Bone bone, float targetX, float targetY, boolean compress, boolean stretch, boolean uniform, + float alpha) { if (!bone.appliedValid) bone.updateAppliedTransform(); Bone p = bone.parent; float id = 1 / (p.a * p.d - p.b * p.c); @@ -158,14 +170,18 @@ public class IkConstraint implements Constraint { if (bone.ascaleX < 0) rotationIK += 180; if (rotationIK > 180) rotationIK -= 360; - else if (rotationIK < -180) rotationIK += 360; - float sx = bone.ascaleX; - if (stretch) { + else if (rotationIK < -180) // + rotationIK += 360; + 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.ashearY); + bone.updateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, bone.ashearY); } /** Applies 2 bone IK. The target is specified in the world coordinate system. diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraintData.java index 81bceb23b..0ef63a675 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraintData.java @@ -41,7 +41,7 @@ public class IkConstraintData { final Array bones = new Array(); BoneData target; int bendDirection = 1; - boolean stretch; + boolean compress, stretch, uniform; float mix = 1; public IkConstraintData (String name) { @@ -78,25 +78,6 @@ public class IkConstraintData { this.target = target; } - /** Controls the bend direction of the IK bones, either 1 or -1. */ - public int getBendDirection () { - return bendDirection; - } - - public void setBendDirection (int bendDirection) { - this.bendDirection = bendDirection; - } - - /** 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 local - * nonuniform scale, stretching is not applied. */ - public boolean getStretch () { - return stretch; - } - - public void setStretch (boolean stretch) { - this.stretch = stretch; - } - /** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */ public float getMix () { return mix; @@ -106,6 +87,44 @@ public class IkConstraintData { this.mix = mix; } + /** Controls the bend direction of the IK bones, either 1 or -1. */ + public int getBendDirection () { + return bendDirection; + } + + public void setBendDirection (int bendDirection) { + this.bendDirection = bendDirection; + } + + /** When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. */ + public boolean getCompress () { + return compress; + } + + public void setCompress (boolean compress) { + this.compress = compress; + } + + /** When true, if the target is out of range, the parent bone is scaled to reach it. If more than one bone is being constrained + * and the parent bone has local nonuniform scale, stretch is not applied. */ + public boolean getStretch () { + return stretch; + } + + public void setStretch (boolean stretch) { + this.stretch = stretch; + } + + /** When true, only a single bone is being constrained, and {@link #getCompress()} or {@link #getStretch()} is used, the bone + * is scaled on both the X and Y axes. */ + public boolean getUniform () { + return uniform; + } + + public void setUniform (boolean uniform) { + this.uniform = uniform; + } + public String toString () { return name; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java index 1e3168037..498dd54d2 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -394,9 +394,10 @@ public class Skeleton { Array ikConstraints = this.ikConstraints; for (int i = 0, n = ikConstraints.size; i < n; i++) { IkConstraint constraint = ikConstraints.get(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; } Array transformConstraints = this.transformConstraints; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java index 1d5e0959b..3f88d8651 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -232,7 +232,9 @@ public class SkeletonBinary { data.target = skeletonData.bones.get(input.readInt(true)); data.mix = input.readFloat(); data.bendDirection = input.readByte(); + data.compress = input.readBoolean(); data.stretch = input.readBoolean(); + data.uniform = input.readBoolean(); skeletonData.ikConstraints.add(data); } @@ -307,7 +309,7 @@ public class SkeletonBinary { data.intValue = input.readInt(false); data.floatValue = input.readFloat(); data.stringValue = input.readString(); - data.audioPath = input.readString(); + data.audioPath = input.readString(); skeletonData.events.add(data); } @@ -661,7 +663,8 @@ public class SkeletonBinary { IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); timeline.ikConstraintIndex = index; for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readByte(), input.readBoolean()); + timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readByte(), input.readBoolean(), + input.readBoolean()); if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); } timelines.add(timeline); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index 3120595b6..9ec1d11ab 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -185,9 +185,11 @@ public class SkeletonJson { data.target = skeletonData.findBone(targetName); if (data.target == null) throw new SerializationException("IK target bone not found: " + targetName); - data.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1; - data.stretch = constraintMap.getBoolean("stretch", false); data.mix = constraintMap.getFloat("mix", 1); + data.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1; + data.compress = constraintMap.getBoolean("compress", false); + data.stretch = constraintMap.getBoolean("stretch", false); + data.uniform = constraintMap.getBoolean("uniform", false); skeletonData.ikConstraints.add(data); } @@ -569,7 +571,8 @@ public class SkeletonJson { int frameIndex = 0; for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) { timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("mix", 1), - valueMap.getBoolean("bendPositive", true) ? 1 : -1, valueMap.getBoolean("stretch", false)); + valueMap.getBoolean("bendPositive", true) ? 1 : -1, valueMap.getBoolean("compress", false), + valueMap.getBoolean("stretch", false)); readCurve(valueMap, timeline, frameIndex); frameIndex++; }