From da1b28027519d45a60289d950ad9e5704f398169 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sun, 15 Nov 2020 17:10:14 -0800 Subject: [PATCH] [libgdx] Added separate X and Y sliders for path constraint translate mix. --- .../com/esotericsoftware/spine/Animation.java | 62 ++++++++++++++----- .../spine/PathConstraint.java | 38 +++++++----- .../spine/PathConstraintData.java | 21 +++++-- .../com/esotericsoftware/spine/Skeleton.java | 3 +- .../spine/SkeletonBinary.java | 28 ++++++++- .../esotericsoftware/spine/SkeletonJson.java | 32 +++++++++- 6 files changed, 140 insertions(+), 44 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 70bc5a541..2b7289986 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -2312,8 +2312,12 @@ public class Animation { } } - /** Changes a transform constraint's {@link PathConstraint#getMixRotate()} and {@link PathConstraint#getMixTranslate()}. */ - static public class PathConstraintMixTimeline extends CurveTimeline2 { + /** Changes a transform constraint's {@link PathConstraint#getMixRotate()}, {@link PathConstraint#getMixX()}, and + * {@link PathConstraint#getMixY()}. */ + static public class PathConstraintMixTimeline extends CurveTimeline { + static public final int ENTRIES = 4; + static private final int ROTATE = 1, X = 2, Y = 3; + final int pathConstraintIndex; public PathConstraintMixTimeline (int frameCount, int bezierCount, int pathConstraintIndex) { @@ -2321,12 +2325,27 @@ public class Animation { this.pathConstraintIndex = pathConstraintIndex; } + public int getFrameEntries () { + return ENTRIES; + } + /** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed when this timeline * is applied. */ public int getPathConstraintIndex () { return pathConstraintIndex; } + /** Sets the time and color for the specified frame. + * @param frame Between 0 and frameCount, inclusive. + * @param time The frame time in seconds. */ + public void setFrame (int frame, float time, float mixRotate, float mixX, float mixY) { + frame <<= 2; + frames[frame] = time; + frames[frame + ROTATE] = mixRotate; + frames[frame + X] = mixX; + frames[frame + Y] = mixY; + } + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, MixDirection direction) { @@ -2338,41 +2357,50 @@ public class Animation { switch (blend) { case setup: constraint.mixRotate = constraint.data.mixRotate; - constraint.mixTranslate = constraint.data.mixTranslate; + constraint.mixX = constraint.data.mixX; + constraint.mixY = constraint.data.mixY; return; case first: constraint.mixRotate += (constraint.data.mixRotate - constraint.mixRotate) * alpha; - constraint.mixTranslate += (constraint.data.mixTranslate - constraint.mixTranslate) * alpha; + constraint.mixX += (constraint.data.mixX - constraint.mixX) * alpha; + constraint.mixY += (constraint.data.mixY - constraint.mixY) * alpha; } return; } - float rotate, translate; - int i = search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + float rotate, x, y; + int i = search(frames, time, ENTRIES), curveType = (int)curves[i >> 2]; switch (curveType) { case LINEAR: float before = frames[i]; - rotate = frames[i + VALUE1]; - translate = frames[i + VALUE2]; + rotate = frames[i + ROTATE]; + x = frames[i + X]; + y = frames[i + Y]; float t = (time - before) / (frames[i + ENTRIES] - before); - rotate += (frames[i + ENTRIES + VALUE1] - rotate) * t; - translate += (frames[i + ENTRIES + VALUE2] - translate) * t; + rotate += (frames[i + ENTRIES + ROTATE] - rotate) * t; + x += (frames[i + ENTRIES + X] - x) * t; + y += (frames[i + ENTRIES + Y] - y) * t; break; case STEPPED: - rotate = frames[i + VALUE1]; - translate = frames[i + VALUE2]; + rotate = frames[i + ROTATE]; + x = frames[i + X]; + y = frames[i + Y]; break; default: - rotate = getBezierValue(time, i, VALUE1, curveType - BEZIER); - translate = getBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + rotate = getBezierValue(time, i, ROTATE, curveType - BEZIER); + x = getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER); + y = getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER); } if (blend == setup) { - constraint.mixRotate = constraint.data.mixRotate + (rotate - constraint.data.mixRotate) * alpha; - constraint.mixTranslate = constraint.data.mixTranslate + (translate - constraint.data.mixTranslate) * alpha; + PathConstraintData data = constraint.data; + constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha; + constraint.mixX = data.mixX + (x - data.mixX) * alpha; + constraint.mixY = data.mixY + (y - data.mixY) * alpha; } else { constraint.mixRotate += (rotate - constraint.mixRotate) * alpha; - constraint.mixTranslate += (translate - constraint.mixTranslate) * alpha; + constraint.mixX += (x - constraint.mixX) * alpha; + constraint.mixY += (y - constraint.mixY) * alpha; } } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index b5cb5eab8..23f27d74a 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -50,7 +50,7 @@ public class PathConstraint implements Updatable { final PathConstraintData data; final Array bones; Slot target; - float position, spacing, mixRotate, mixTranslate; + float position, spacing, mixRotate, mixX, mixY; boolean active; @@ -69,7 +69,8 @@ public class PathConstraint implements Updatable { position = data.position; spacing = data.spacing; mixRotate = data.mixRotate; - mixTranslate = data.mixTranslate; + mixX = data.mixX; + mixY = data.mixY; } /** Copy constructor. */ @@ -84,7 +85,8 @@ public class PathConstraint implements Updatable { position = constraint.position; spacing = constraint.spacing; mixRotate = constraint.mixRotate; - mixTranslate = constraint.mixTranslate; + mixX = constraint.mixX; + mixY = constraint.mixY; } /** Applies the constraint to the constrained bones. */ @@ -92,9 +94,8 @@ public class PathConstraint implements Updatable { Attachment attachment = target.attachment; if (!(attachment instanceof PathAttachment)) return; - float mixRotate = this.mixRotate, mixTranslate = this.mixTranslate; - boolean translate = mixTranslate > 0, rotate = mixRotate > 0; - if (!translate && !rotate) return; + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY; + if (mixRotate == 0 && mixX == 0 && mixY == 0) return; PathConstraintData data = this.data; boolean percentSpacing = data.spacingMode == SpacingMode.percent; @@ -145,8 +146,8 @@ public class PathConstraint implements Updatable { } for (int i = 0, p = 3; i < boneCount; i++, p += 3) { Bone bone = (Bone)bones[i]; - bone.worldX += (boneX - bone.worldX) * mixTranslate; - bone.worldY += (boneY - bone.worldY) * mixTranslate; + bone.worldX += (boneX - bone.worldX) * mixX; + bone.worldY += (boneY - bone.worldY) * mixY; float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; if (scale) { float length = lengths[i]; @@ -158,7 +159,7 @@ public class PathConstraint implements Updatable { } boneX = x; boneY = y; - if (rotate) { + if (mixRotate > 0) { float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin; if (tangents) r = positions[p - 1]; @@ -473,13 +474,22 @@ public class PathConstraint implements Updatable { this.mixRotate = mixRotate; } - /** A percentage (0-1) that controls the mix between the constrained and unconstrained translation. */ - public float getMixTranslate () { - return mixTranslate; + /** A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. */ + public float getMixX () { + return mixX; } - public void setMixTranslate (float mixTranslate) { - this.mixTranslate = mixTranslate; + public void setMixX (float mixX) { + this.mixX = mixX; + } + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. */ + public float getMixY () { + return mixY; + } + + public void setMixY (float mixY) { + this.mixY = mixY; } /** The bones that will be modified by this path constraint. */ diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java index 54e6e5a5c..a24bd4087 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java @@ -41,7 +41,7 @@ public class PathConstraintData extends ConstraintData { SpacingMode spacingMode; RotateMode rotateMode; float offsetRotation; - float position, spacing, mixRotate, mixTranslate; + float position, spacing, mixRotate, mixX, mixY; public PathConstraintData (String name) { super(name); @@ -128,13 +128,22 @@ public class PathConstraintData extends ConstraintData { this.mixRotate = mixRotate; } - /** A percentage (0-1) that controls the mix between the constrained and unconstrained translation. */ - public float getMixTranslate () { - return mixTranslate; + /** A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. */ + public float getMixX () { + return mixX; } - public void setMixTranslate (float mixTranslate) { - this.mixTranslate = mixTranslate; + public void setMixX (float mixX) { + this.mixX = mixX; + } + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. */ + public float getMixY () { + return mixY; + } + + public void setMixY (float mixY) { + this.mixY = mixY; } /** Controls how the first bone is positioned along the path. 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 12240d101..5aa107e9c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -420,7 +420,8 @@ public class Skeleton { constraint.position = data.position; constraint.spacing = data.spacing; constraint.mixRotate = data.mixRotate; - constraint.mixTranslate = data.mixTranslate; + constraint.mixX = data.mixX; + constraint.mixY = data.mixY; } } 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 df2b246ab..3decfe576 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -267,7 +267,8 @@ public class SkeletonBinary extends SkeletonLoader { data.spacing = input.readFloat(); if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) data.spacing *= scale; data.mixRotate = input.readFloat(); - data.mixTranslate = input.readFloat(); + data.mixX = input.readFloat(); + data.mixY = input.readFloat(); o[i] = data; } @@ -859,8 +860,29 @@ public class SkeletonBinary extends SkeletonLoader { data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed ? scale : 1)); break; case PATH_MIX: - timelines - .add(readTimeline(input, new PathConstraintMixTimeline(input.readInt(true), input.readInt(true), index), 1)); + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(input.readInt(true), input.readInt(true), + index); + float time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat(); + for (int frame = 0, bezier = 0, frameLast = nn - 1;; frame++) { + timeline.setFrame(frame, time, mixRotate, mixX, mixY); + if (frame == frameLast) break; + float time2 = input.readFloat(), mixRotate2 = input.readFloat(), mixX2 = input.readFloat(), + mixY2 = input.readFloat(); + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + } + 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 c37408fd8..1dcd9c41c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -261,7 +261,8 @@ public class SkeletonJson extends SkeletonLoader { data.spacing = constraintMap.getFloat("spacing", 0); if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) data.spacing *= scale; data.mixRotate = constraintMap.getFloat("mixRotate", 1); - data.mixTranslate = constraintMap.getFloat("mixTranslate", 1); + data.mixX = constraintMap.getFloat("mixX", 1); + data.mixY = constraintMap.getFloat("mixY", 1); skeletonData.pathConstraints.add(data); } @@ -819,8 +820,33 @@ public class SkeletonJson extends SkeletonLoader { timelines.add(readTimeline(keyMap, timeline, 0, data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed ? scale : 1)); } else if (timelineName.equals("mix")) { - CurveTimeline2 timeline = new PathConstraintMixTimeline(timelineMap.size, timelineMap.size << 1, index); - timelines.add(readTimeline(keyMap, timeline, "mixRotate", "mixTranslate", 1, 1)); + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(timelineMap.size, timelineMap.size * 3, index); + float time = keyMap.getFloat("time", 0); + float mixRotate = keyMap.getFloat("mixRotate", 1); + float mixX = keyMap.getFloat("mixX", 1), mixY = keyMap.getFloat("mixY", mixX); + for (int frame = 0, bezier = 0;; frame++) { + timeline.setFrame(frame, time, mixRotate, mixX, mixY); + JsonValue nextMap = keyMap.next; + if (nextMap == null) { + timeline.shrink(bezier); + break; + } + float time2 = nextMap.getFloat("time", 0); + float mixRotate2 = nextMap.getFloat("mixRotate", 1); + float mixX2 = nextMap.getFloat("mixX", 1), mixY2 = nextMap.getFloat("mixY", mixX2); + JsonValue curve = keyMap.get("curve"); + if (curve != null) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + keyMap = nextMap; + } + timelines.add(timeline); } } }