From 15b4c5488810a7d5ac0e008696a401b6230878f2 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Mon, 17 Jun 2019 19:00:50 +0200 Subject: [PATCH] [libgdx] Added IK softness. --- .../com/esotericsoftware/spine/Animation.java | 30 ++++++++++----- .../esotericsoftware/spine/IkConstraint.java | 37 +++++++++++++++---- .../spine/IkConstraintData.java | 11 +++++- .../com/esotericsoftware/spine/Skeleton.java | 1 + .../spine/SkeletonBinary.java | 5 ++- .../esotericsoftware/spine/SkeletonJson.java | 5 ++- 6 files changed, 68 insertions(+), 21 deletions(-) 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 d3d9ffafe..ac1698547 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -1326,15 +1326,16 @@ public class Animation { } } - /** Changes an IK constraint's {@link IkConstraint#getMix()}, {@link IkConstraint#getBendDirection()}, - * {@link IkConstraint#getStretch()}, and {@link IkConstraint#getCompress()}. */ + /** Changes an IK constraint's {@link IkConstraint#getMix()},{@link IkConstraint#getSoftness()}, + * {@link IkConstraint#getBendDirection()}, {@link IkConstraint#getStretch()}, and {@link IkConstraint#getCompress()}. */ static public class IkConstraintTimeline extends CurveTimeline { - 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; + static public final int ENTRIES = 6; + static private final int PREV_TIME = -6, PREV_MIX = -5, PREV_SOFTNESS = -4, PREV_BEND_DIRECTION = -3, PREV_COMPRESS = -2, + PREV_STRETCH = -1; + static private final int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; int ikConstraintIndex; - private final float[] frames; // time, mix, bendDirection, compress, stretch, ... + private final float[] frames; // time, mix, softness, bendDirection, compress, stretch, ... public IkConstraintTimeline (int frameCount) { super(frameCount); @@ -1355,16 +1356,18 @@ public class Animation { return ikConstraintIndex; } - /** 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[] getFrames () { return frames; } - /** 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) { + /** 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, boolean compress, + boolean 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; @@ -1380,12 +1383,14 @@ public class Animation { switch (blend) { case 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 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; @@ -1396,6 +1401,8 @@ public class Animation { if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. if (blend == 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 == out) { constraint.bendDirection = constraint.data.bendDirection; constraint.compress = constraint.data.compress; @@ -1407,6 +1414,7 @@ public class Animation { } } else { constraint.mix += (frames[frames.length + PREV_MIX] - constraint.mix) * alpha; + constraint.softness += (frames[frames.length + PREV_SOFTNESS] - constraint.softness) * alpha; if (direction == in) { constraint.bendDirection = (int)frames[frames.length + PREV_BEND_DIRECTION]; constraint.compress = frames[frames.length + PREV_COMPRESS] != 0; @@ -1419,11 +1427,14 @@ public class Animation { // Interpolate between the previous frame and the current frame. int frame = 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 == 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 == out) { constraint.bendDirection = constraint.data.bendDirection; constraint.compress = constraint.data.compress; @@ -1435,6 +1446,7 @@ public class Animation { } } 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 == in) { constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; constraint.compress = frames[frame + PREV_COMPRESS] != 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 5c26ee50e..9fa6337aa 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 Updatable { Bone target; int bendDirection; boolean compress, stretch; - float mix = 1; + float mix = 1, softness; boolean active; @@ -52,6 +52,7 @@ public class IkConstraint implements Updatable { if (skeleton == null) throw new IllegalArgumentException("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 @@ public class IkConstraint implements Updatable { bones.add(skeleton.bones.get(bone.data.index)); target = skeleton.bones.get(constraint.target.data.index); mix = constraint.mix; + softness = constraint.softness; bendDirection = constraint.bendDirection; compress = constraint.compress; stretch = constraint.stretch; @@ -90,7 +92,7 @@ public class IkConstraint implements Updatable { 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); + apply(bones.first(), bones.get(1), target.worldX, target.worldY, bendDirection, stretch, softness, mix); break; } } @@ -119,6 +121,15 @@ public class IkConstraint implements Updatable { this.mix = mix; } + /** For two bone IK, the distance from the maximum reach of the bones that rotation will slow. */ + public float getSoftness () { + return softness; + } + + public void setSoftness (float softness) { + this.softness = softness; + } + /** Controls the bend direction of the IK bones, either 1 or -1. */ public int getBendDirection () { return bendDirection; @@ -189,7 +200,8 @@ public class IkConstraint implements Updatable { /** Applies 2 bone IK. The target is specified in the world coordinate system. * @param child A direct descendant of the parent bone. */ - static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, boolean stretch, float alpha) { + static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, boolean stretch, float softness, + float alpha) { if (parent == null) throw new IllegalArgumentException("parent cannot be null."); if (child == null) throw new IllegalArgumentException("child cannot be null."); if (alpha == 0) { @@ -233,12 +245,23 @@ public class IkConstraint implements Updatable { 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; + 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) { + float td = (float)Math.sqrt(dd), sd = td - l1 - l2 + 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; + } + } outer: if (u) { l2 *= psx; 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 1d8c2c027..36c6db76e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraintData.java @@ -39,7 +39,7 @@ public class IkConstraintData extends ConstraintData { BoneData target; int bendDirection = 1; boolean compress, stretch, uniform; - float mix = 1; + float mix = 1, softness; public IkConstraintData (String name) { super(name); @@ -69,6 +69,15 @@ public class IkConstraintData extends ConstraintData { this.mix = mix; } + /** For two bone IK, the distance from the maximum reach of the bones that rotation will slow. */ + public float getSoftness () { + return softness; + } + + public void setSoftness (float softness) { + this.softness = softness; + } + /** Controls the bend direction of the IK bones, either 1 or -1. */ public int getBendDirection () { return bendDirection; 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 c1b87885e..0c4732c9b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -419,6 +419,7 @@ public class Skeleton { for (int i = 0, n = ikConstraints.size; i < n; i++) { IkConstraint constraint = ikConstraints.get(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-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java index d58ba5a67..24e7cd42f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -212,6 +212,7 @@ public class SkeletonBinary { bones[ii] = skeletonData.bones.get(input.readInt(true)); data.target = skeletonData.bones.get(input.readInt(true)); data.mix = input.readFloat(); + data.softness = input.readFloat(); data.bendDirection = input.readByte(); data.compress = input.readBoolean(); data.stretch = input.readBoolean(); @@ -668,8 +669,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(), - input.readBoolean()); + timeline.setFrame(frameIndex, input.readFloat(), 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 19d886557..4d2acf199 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -197,6 +197,7 @@ public class SkeletonJson { if (data.target == null) throw new SerializationException("IK target bone not found: " + targetName); data.mix = constraintMap.getFloat("mix", 1); + data.softness = constraintMap.getFloat("softness", 0); data.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1; data.compress = constraintMap.getBoolean("compress", false); data.stretch = constraintMap.getBoolean("stretch", false); @@ -608,8 +609,8 @@ public class SkeletonJson { int frameIndex = 0; for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) { timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), valueMap.getFloat("mix", 1), - valueMap.getBoolean("bendPositive", true) ? 1 : -1, valueMap.getBoolean("compress", false), - valueMap.getBoolean("stretch", false)); + valueMap.getFloat("softness", 0), valueMap.getBoolean("bendPositive", true) ? 1 : -1, + valueMap.getBoolean("compress", false), valueMap.getBoolean("stretch", false)); readCurve(valueMap, timeline, frameIndex); frameIndex++; }