[libgdx] Added mapping a bone property to a slider.

This commit is contained in:
Nathan Sweet 2025-04-27 22:53:51 -04:00
parent 6da9ac59b7
commit bbc6c2e37e
6 changed files with 186 additions and 86 deletions

View File

@ -303,42 +303,40 @@ public class SkeletonBinary extends SkeletonLoader {
FromProperty[] froms = data.properties.setSize(nn = flags >> 5); FromProperty[] froms = data.properties.setSize(nn = flags >> 5);
for (int ii = 0, tn; ii < nn; ii++) { for (int ii = 0, tn; ii < nn; ii++) {
float fromScale = 1; float fromScale = 1;
FromProperty from; FromProperty from = switch (input.readByte()) {
switch (input.readByte()) { case 0 -> new FromRotate();
case 0 -> from = new FromRotate();
case 1 -> { case 1 -> {
from = new FromX();
fromScale = scale; fromScale = scale;
yield new FromX();
} }
case 2 -> { case 2 -> {
from = new FromY();
fromScale = scale; fromScale = scale;
yield new FromY();
} }
case 3 -> from = new FromScaleX(); case 3 -> new FromScaleX();
case 4 -> from = new FromScaleY(); case 4 -> new FromScaleY();
case 5 -> from = new FromShearY(); case 5 -> new FromShearY();
default -> from = null; default -> null;
} };
from.offset = input.readFloat() * fromScale; from.offset = input.readFloat() * fromScale;
ToProperty[] tos = from.to.setSize(tn = input.readByte()); ToProperty[] tos = from.to.setSize(tn = input.readByte());
for (int t = 0; t < tn; t++) { for (int t = 0; t < tn; t++) {
float toScale = 1; float toScale = 1;
ToProperty to; ToProperty to = switch (input.readByte()) {
switch (input.readByte()) { case 0 -> new ToRotate();
case 0 -> to = new ToRotate();
case 1 -> { case 1 -> {
to = new ToX();
toScale = scale; toScale = scale;
yield new ToX();
} }
case 2 -> { case 2 -> {
to = new ToY();
toScale = scale; toScale = scale;
yield new ToY();
} }
case 3 -> to = new ToScaleX(); case 3 -> new ToScaleX();
case 4 -> to = new ToScaleY(); case 4 -> new ToScaleY();
case 5 -> to = new ToShearY(); case 5 -> new ToShearY();
default -> to = null; default -> null;
} };
to.offset = input.readFloat() * toScale; to.offset = input.readFloat() * toScale;
to.max = input.readFloat() * toScale; to.max = input.readFloat() * toScale;
to.scale = input.readFloat() * toScale / fromScale; to.scale = input.readFloat() * toScale / fromScale;
@ -347,12 +345,12 @@ public class SkeletonBinary extends SkeletonLoader {
froms[ii] = from; froms[ii] = from;
} }
flags = input.read(); flags = input.read();
if ((flags & 1) != 0) data.offsetRotation = input.readFloat(); if ((flags & 1) != 0) data.offsets[0] = input.readFloat();
if ((flags & 2) != 0) data.offsetX = input.readFloat() * scale; if ((flags & 2) != 0) data.offsets[1] = input.readFloat() * scale;
if ((flags & 4) != 0) data.offsetY = input.readFloat() * scale; if ((flags & 4) != 0) data.offsets[2] = input.readFloat() * scale;
if ((flags & 8) != 0) data.offsetScaleX = input.readFloat(); if ((flags & 8) != 0) data.offsets[3] = input.readFloat();
if ((flags & 16) != 0) data.offsetScaleY = input.readFloat(); if ((flags & 16) != 0) data.offsets[4] = input.readFloat();
if ((flags & 32) != 0) data.offsetShearY = input.readFloat(); if ((flags & 32) != 0) data.offsets[5] = input.readFloat();
flags = input.read(); flags = input.read();
TransformConstraintPose setup = data.setup; TransformConstraintPose setup = data.setup;
if ((flags & 1) != 0) setup.mixRotate = input.readFloat(); if ((flags & 1) != 0) setup.mixRotate = input.readFloat();
@ -422,6 +420,28 @@ public class SkeletonBinary extends SkeletonLoader {
data.additive = (nn & 4) != 0; data.additive = (nn & 4) != 0;
if ((nn & 8) != 0) data.setup.time = input.readFloat(); if ((nn & 8) != 0) data.setup.time = input.readFloat();
if ((nn & 16) != 0) data.setup.mix = (nn & 32) != 0 ? input.readFloat() : 1; if ((nn & 16) != 0) data.setup.mix = (nn & 32) != 0 ? input.readFloat() : 1;
if ((nn & 64) != 0) {
data.local = (nn & 128) != 0;
data.bone = bones[input.readInt(true)];
float offset = input.readFloat();
data.property = switch (input.readByte()) {
case 0 -> new FromRotate();
case 1 -> {
offset *= scale;
yield new FromX();
}
case 2 -> {
offset *= scale;
yield new FromY();
}
case 3 -> new FromScaleX();
case 4 -> new FromScaleY();
case 5 -> new FromShearY();
default -> null;
};
data.property.offset = offset;
data.scale = input.readFloat();
}
constraints[i] = data; constraints[i] = data;
} }
} }

View File

@ -268,23 +268,8 @@ public class SkeletonJson extends SkeletonLoader {
boolean rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false; boolean rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false;
for (JsonValue fromEntry = constraintMap.getChild("properties"); fromEntry != null; fromEntry = fromEntry.next) { for (JsonValue fromEntry = constraintMap.getChild("properties"); fromEntry != null; fromEntry = fromEntry.next) {
float fromScale = 1; FromProperty from = fromProperty(fromEntry.name);
FromProperty from; float fromScale = propertyScale(fromEntry.name, scale);
switch (fromEntry.name) {
case "rotate" -> from = new FromRotate();
case "x" -> {
from = new FromX();
fromScale = scale;
}
case "y" -> {
from = new FromY();
fromScale = scale;
}
case "scaleX" -> from = new FromScaleX();
case "scaleY" -> from = new FromScaleY();
case "shearY" -> from = new FromShearY();
default -> throw new SerializationException("Invalid transform constraint from property: " + fromEntry.name);
}
from.offset = fromEntry.getFloat("offset", 0) * fromScale; from.offset = fromEntry.getFloat("offset", 0) * fromScale;
for (JsonValue toEntry = fromEntry.getChild("to"); toEntry != null; toEntry = toEntry.next) { for (JsonValue toEntry = fromEntry.getChild("to"); toEntry != null; toEntry = toEntry.next) {
float toScale = 1; float toScale = 1;
@ -326,12 +311,12 @@ public class SkeletonJson extends SkeletonLoader {
if (from.to.notEmpty()) data.properties.add(from); if (from.to.notEmpty()) data.properties.add(from);
} }
data.offsetRotation = constraintMap.getFloat("rotation", 0); data.offsets[0] = constraintMap.getFloat("rotation", 0);
data.offsetX = constraintMap.getFloat("x", 0) * scale; data.offsets[1] = constraintMap.getFloat("x", 0) * scale;
data.offsetY = constraintMap.getFloat("y", 0) * scale; data.offsets[2] = constraintMap.getFloat("y", 0) * scale;
data.offsetScaleX = constraintMap.getFloat("scaleX", 0); data.offsets[3] = constraintMap.getFloat("scaleX", 0);
data.offsetScaleY = constraintMap.getFloat("scaleY", 0); data.offsets[4] = constraintMap.getFloat("scaleY", 0);
data.offsetShearY = constraintMap.getFloat("shearY", 0); data.offsets[5] = constraintMap.getFloat("shearY", 0);
TransformConstraintPose setup = data.setup; TransformConstraintPose setup = data.setup;
if (rotate) setup.mixRotate = constraintMap.getFloat("mixRotate", 1); if (rotate) setup.mixRotate = constraintMap.getFloat("mixRotate", 1);
@ -408,10 +393,22 @@ public class SkeletonJson extends SkeletonLoader {
case "slider" -> { case "slider" -> {
var data = new SliderData(name); var data = new SliderData(name);
data.skinRequired = skinRequired; data.skinRequired = skinRequired;
data.loop = constraintMap.getBoolean("loop", false);
data.additive = constraintMap.getBoolean("additive", false); data.additive = constraintMap.getBoolean("additive", false);
data.loop = constraintMap.getBoolean("loop", false);
data.setup.time = constraintMap.getFloat("time", 0); data.setup.time = constraintMap.getFloat("time", 0);
data.setup.mix = constraintMap.getFloat("mix", 1); data.setup.mix = constraintMap.getFloat("mix", 1);
String boneName = constraintMap.getString("bone", null);
if (boneName != null) {
data.bone = skeletonData.findBone(boneName);
if (data.bone == null) throw new SerializationException("Slider bone not found: " + boneName);
String property = constraintMap.getString("property");
data.property = fromProperty(property);
data.property.offset = constraintMap.getFloat("offset", 0) * propertyScale(property, scale);
data.scale = constraintMap.getFloat("scale");
data.local = constraintMap.getBoolean("local", false);
}
skeletonData.constraints.add(data); skeletonData.constraints.add(data);
} }
} }
@ -523,6 +520,25 @@ public class SkeletonJson extends SkeletonLoader {
return skeletonData; return skeletonData;
} }
private FromProperty fromProperty (String type) {
return switch (type) {
case "rotate" -> new FromRotate();
case "x" -> new FromX();
case "y" -> new FromY();
case "scaleX" -> new FromScaleX();
case "scaleY" -> new FromScaleY();
case "shearY" -> new FromShearY();
default -> throw new SerializationException("Invalid from property: " + type);
};
}
private float propertyScale (String type, float scale) {
return switch (type) {
case "x", "y" -> scale;
default -> 1;
};
}
private Attachment readAttachment (JsonValue map, Skin skin, int slotIndex, String name, SkeletonData skeletonData) { private Attachment readAttachment (JsonValue map, Skin skin, int slotIndex, String name, SkeletonData skeletonData) {
float scale = this.scale; float scale = this.scale;
name = map.getString("name", name); name = map.getString("name", name);

View File

@ -41,12 +41,19 @@ import com.esotericsoftware.spine.Animation.Timeline;
* <p> * <p>
* See <a href="https://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */ * See <a href="https://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
public class Slider extends Constraint<Slider, SliderData, SliderPose> { public class Slider extends Constraint<Slider, SliderData, SliderPose> {
public Slider (SliderData data) { static private final float[] offsets = new float[6];
Bone bone;
public Slider (SliderData data, Skeleton skeleton) {
super(data, new SliderPose(), new SliderPose()); super(data, new SliderPose(), new SliderPose());
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
if (data.bone != null) bone = skeleton.bones.items[data.bone.index];
} }
public Slider copy (Skeleton skeleton) { public Slider copy (Skeleton skeleton) {
var copy = new Slider(data); var copy = new Slider(data, skeleton);
copy.pose.set(pose); copy.pose.set(pose);
return copy; return copy;
} }
@ -54,9 +61,18 @@ public class Slider extends Constraint<Slider, SliderData, SliderPose> {
public void update (Skeleton skeleton, Physics physics) { public void update (Skeleton skeleton, Physics physics) {
if (pose.mix == 0) return; if (pose.mix == 0) return;
Animation animation = data.animation;
if (bone != null && bone.active) {
pose.time = (data.property.value(bone.applied, data.local, offsets) - data.property.offset) * data.scale;
if (data.loop)
pose.time = animation.duration + (pose.time % animation.duration);
else
pose.time = Math.max(0, pose.time);
}
SliderData data = this.data; SliderData data = this.data;
Timeline[] timelines = data.animation.timelines.items; Timeline[] timelines = animation.timelines.items;
int timelineCount = data.animation.timelines.size; int timelineCount = animation.timelines.size;
Bone[] bones = skeleton.bones.items; Bone[] bones = skeleton.bones.items;
if (pose.mix == 1) { if (pose.mix == 1) {
for (int i = 0; i < timelineCount; i++) for (int i = 0; i < timelineCount; i++)
@ -72,8 +88,8 @@ public class Slider extends Constraint<Slider, SliderData, SliderPose> {
} }
SliderPose pose = applied; SliderPose pose = applied;
data.animation.apply(skeleton, pose.time, pose.time, data.loop, null, pose.mix, animation.apply(skeleton, pose.time, pose.time, data.loop, null, pose.mix, data.additive ? MixBlend.add : MixBlend.replace,
data.additive ? MixBlend.add : MixBlend.replace, MixDirection.in, true); MixDirection.in, true);
} }
void sort (Skeleton skeleton) { void sort (Skeleton skeleton) {
@ -108,4 +124,12 @@ public class Slider extends Constraint<Slider, SliderData, SliderPose> {
skeleton.resetCache(constraints[timeline.getConstraintIndex()]); skeleton.resetCache(constraints[timeline.getConstraintIndex()]);
} }
} }
public Bone getBone () {
return bone;
}
public void setBone (Bone bone) {
this.bone = bone;
}
} }

View File

@ -29,19 +29,27 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.TransformConstraintData.FromProperty;
/** Stores the setup pose for a {@link PhysicsConstraint}. /** Stores the setup pose for a {@link PhysicsConstraint}.
* <p> * <p>
* See <a href="https://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */ * See <a href="https://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
public class SliderData extends ConstraintData<Slider, SliderPose> { public class SliderData extends ConstraintData<Slider, SliderPose> {
Animation animation; Animation animation;
boolean additive, loop; boolean additive, loop;
@Null BoneData bone;
@Null FromProperty property;
float scale;
boolean local;
public SliderData (String name) { public SliderData (String name) {
super(name, new SliderPose()); super(name, new SliderPose());
} }
public Slider create (Skeleton skeleton) { public Slider create (Skeleton skeleton) {
return new Slider(this); return new Slider(this, skeleton);
} }
public Animation getAnimation () { public Animation getAnimation () {
@ -67,4 +75,36 @@ public class SliderData extends ConstraintData<Slider, SliderPose> {
public void setLoop (boolean loop) { public void setLoop (boolean loop) {
this.loop = loop; this.loop = loop;
} }
public @Null BoneData getBone () {
return bone;
}
public void setBone (@Null BoneData bone) {
this.bone = bone;
}
public @Null FromProperty getProperty () {
return property;
}
public void setProperty (@Null FromProperty property) {
this.property = property;
}
public float getScale () {
return scale;
}
public void setScale (float scale) {
this.scale = scale;
}
public boolean getLocal () {
return local;
}
public void setLocal (boolean local) {
this.local = local;
}
} }

View File

@ -69,6 +69,7 @@ public class TransformConstraint extends Constraint<TransformConstraint, Transfo
TransformConstraintData data = this.data; TransformConstraintData data = this.data;
boolean localSource = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp; boolean localSource = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp;
float[] offsets = data.offsets;
BonePose source = this.source.applied; BonePose source = this.source.applied;
int update = skeleton.update; int update = skeleton.update;
if (localSource && source.local == skeleton.update) source.updateLocalTransform(skeleton); if (localSource && source.local == skeleton.update) source.updateLocalTransform(skeleton);
@ -80,7 +81,7 @@ public class TransformConstraint extends Constraint<TransformConstraint, Transfo
if (localTarget && bone.local == update) bone.updateLocalTransform(skeleton); if (localTarget && bone.local == update) bone.updateLocalTransform(skeleton);
for (int f = 0; f < fn; f++) { for (int f = 0; f < fn; f++) {
FromProperty from = fromItems[f]; FromProperty from = fromItems[f];
float value = from.value(data, source, localSource) - from.offset; float value = from.value(source, localSource, offsets) - from.offset;
ToProperty[] toItems = from.to.items; ToProperty[] toItems = from.to.items;
for (int t = 0, tn = from.to.size; t < tn; t++) { for (int t = 0, tn = from.to.size; t < tn; t++) {
ToProperty to = toItems[t]; ToProperty to = toItems[t];

View File

@ -39,7 +39,7 @@ import com.badlogic.gdx.utils.Array;
public class TransformConstraintData extends ConstraintData<TransformConstraint, TransformConstraintPose> { public class TransformConstraintData extends ConstraintData<TransformConstraint, TransformConstraintPose> {
final Array<BoneData> bones = new Array(true, 0, BoneData[]::new); final Array<BoneData> bones = new Array(true, 0, BoneData[]::new);
BoneData source; BoneData source;
float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; float[] offsets = new float[6];
boolean localSource, localTarget, additive, clamp; boolean localSource, localTarget, additive, clamp;
final Array<FromProperty> properties = new Array(true, 1, FromProperty[]::new); final Array<FromProperty> properties = new Array(true, 1, FromProperty[]::new);
@ -68,56 +68,56 @@ public class TransformConstraintData extends ConstraintData<TransformConstraint,
/** An offset added to the constrained bone rotation. */ /** An offset added to the constrained bone rotation. */
public float getOffsetRotation () { public float getOffsetRotation () {
return offsetRotation; return offsets[0];
} }
public void setOffsetRotation (float offsetRotation) { public void setOffsetRotation (float offsetRotation) {
this.offsetRotation = offsetRotation; offsets[0] = offsetRotation;
} }
/** An offset added to the constrained bone X translation. */ /** An offset added to the constrained bone X translation. */
public float getOffsetX () { public float getOffsetX () {
return offsetX; return offsets[1];
} }
public void setOffsetX (float offsetX) { public void setOffsetX (float offsetX) {
this.offsetX = offsetX; offsets[1] = offsetX;
} }
/** An offset added to the constrained bone Y translation. */ /** An offset added to the constrained bone Y translation. */
public float getOffsetY () { public float getOffsetY () {
return offsetY; return offsets[2];
} }
public void setOffsetY (float offsetY) { public void setOffsetY (float offsetY) {
this.offsetY = offsetY; offsets[2] = offsetY;
} }
/** An offset added to the constrained bone scaleX. */ /** An offset added to the constrained bone scaleX. */
public float getOffsetScaleX () { public float getOffsetScaleX () {
return offsetScaleX; return offsets[3];
} }
public void setOffsetScaleX (float offsetScaleX) { public void setOffsetScaleX (float offsetScaleX) {
this.offsetScaleX = offsetScaleX; offsets[3] = offsetScaleX;
} }
/** An offset added to the constrained bone scaleY. */ /** An offset added to the constrained bone scaleY. */
public float getOffsetScaleY () { public float getOffsetScaleY () {
return offsetScaleY; return offsets[4];
} }
public void setOffsetScaleY (float offsetScaleY) { public void setOffsetScaleY (float offsetScaleY) {
this.offsetScaleY = offsetScaleY; offsets[4] = offsetScaleY;
} }
/** An offset added to the constrained bone shearY. */ /** An offset added to the constrained bone shearY. */
public float getOffsetShearY () { public float getOffsetShearY () {
return offsetShearY; return offsets[5];
} }
public void setOffsetShearY (float offsetShearY) { public void setOffsetShearY (float offsetShearY) {
this.offsetShearY = offsetShearY; offsets[5] = offsetShearY;
} }
/** Reads the source bone's local transform instead of its world transform. */ /** Reads the source bone's local transform instead of its world transform. */
@ -170,7 +170,7 @@ public class TransformConstraintData extends ConstraintData<TransformConstraint,
public final Array<ToProperty> to = new Array(true, 1, ToProperty[]::new); public final Array<ToProperty> to = new Array(true, 1, ToProperty[]::new);
/** Reads this property from the specified bone. */ /** Reads this property from the specified bone. */
abstract public float value (TransformConstraintData data, BonePose source, boolean local); abstract public float value (BonePose source, boolean local, float[] offsets);
} }
/** Constrained property for a {@link TransformConstraint}. */ /** Constrained property for a {@link TransformConstraint}. */
@ -192,10 +192,10 @@ public class TransformConstraintData extends ConstraintData<TransformConstraint,
} }
static public class FromRotate extends FromProperty { static public class FromRotate extends FromProperty {
public float value (TransformConstraintData data, BonePose source, boolean local) { public float value (BonePose source, boolean local, float[] offsets) {
if (local) return source.rotation + data.offsetRotation; if (local) return source.rotation + offsets[0];
float value = atan2(source.c, source.a) * radDeg float value = atan2(source.c, source.a) * radDeg
+ (source.a * source.d - source.b * source.c > 0 ? data.offsetRotation : -data.offsetRotation); + (source.a * source.d - source.b * source.c > 0 ? offsets[0] : -offsets[0]);
if (value < 0) value += 360; if (value < 0) value += 360;
return value; return value;
} }
@ -229,8 +229,8 @@ public class TransformConstraintData extends ConstraintData<TransformConstraint,
} }
static public class FromX extends FromProperty { static public class FromX extends FromProperty {
public float value (TransformConstraintData data, BonePose source, boolean local) { public float value (BonePose source, boolean local, float[] offsets) {
return local ? source.x + data.offsetX : data.offsetX * source.a + data.offsetY * source.b + source.worldX; return local ? source.x + offsets[1] : offsets[1] * source.a + offsets[2] * source.b + source.worldX;
} }
} }
@ -251,8 +251,8 @@ public class TransformConstraintData extends ConstraintData<TransformConstraint,
} }
static public class FromY extends FromProperty { static public class FromY extends FromProperty {
public float value (TransformConstraintData data, BonePose source, boolean local) { public float value (BonePose source, boolean local, float[] offsets) {
return local ? source.y + data.offsetY : data.offsetX * source.c + data.offsetY * source.d + source.worldY; return local ? source.y + offsets[2] : offsets[1] * source.c + offsets[2] * source.d + source.worldY;
} }
} }
@ -273,8 +273,8 @@ public class TransformConstraintData extends ConstraintData<TransformConstraint,
} }
static public class FromScaleX extends FromProperty { static public class FromScaleX extends FromProperty {
public float value (TransformConstraintData data, BonePose source, boolean local) { public float value (BonePose source, boolean local, float[] offsets) {
return (local ? source.scaleX : (float)Math.sqrt(source.a * source.a + source.c * source.c)) + data.offsetScaleX; return (local ? source.scaleX : (float)Math.sqrt(source.a * source.a + source.c * source.c)) + offsets[3];
} }
} }
@ -304,8 +304,8 @@ public class TransformConstraintData extends ConstraintData<TransformConstraint,
} }
static public class FromScaleY extends FromProperty { static public class FromScaleY extends FromProperty {
public float value (TransformConstraintData data, BonePose source, boolean local) { public float value (BonePose source, boolean local, float[] offsets) {
return (local ? source.scaleY : (float)Math.sqrt(source.b * source.b + source.d * source.d)) + data.offsetScaleY; return (local ? source.scaleY : (float)Math.sqrt(source.b * source.b + source.d * source.d)) + offsets[4];
} }
} }
@ -335,9 +335,8 @@ public class TransformConstraintData extends ConstraintData<TransformConstraint,
} }
static public class FromShearY extends FromProperty { static public class FromShearY extends FromProperty {
public float value (TransformConstraintData data, BonePose source, boolean local) { public float value (BonePose source, boolean local, float[] offsets) {
return (local ? source.shearY : (atan2(source.d, source.b) - atan2(source.c, source.a)) * radDeg - 90) return (local ? source.shearY : (atan2(source.d, source.b) - atan2(source.c, source.a)) * radDeg - 90) + offsets[5];
+ data.offsetShearY;
} }
} }