Browse Source

[libgdx] Added IK softness.

NathanSweet 6 years ago
parent
commit
15b4c54888

+ 21 - 9
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;

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

+ 10 - 1
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;

+ 1 - 0
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;

+ 3 - 2
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);

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