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

View File

@ -268,23 +268,8 @@ public class SkeletonJson extends SkeletonLoader {
boolean rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false;
for (JsonValue fromEntry = constraintMap.getChild("properties"); fromEntry != null; fromEntry = fromEntry.next) {
float fromScale = 1;
FromProperty from;
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);
}
FromProperty from = fromProperty(fromEntry.name);
float fromScale = propertyScale(fromEntry.name, scale);
from.offset = fromEntry.getFloat("offset", 0) * fromScale;
for (JsonValue toEntry = fromEntry.getChild("to"); toEntry != null; toEntry = toEntry.next) {
float toScale = 1;
@ -326,12 +311,12 @@ public class SkeletonJson extends SkeletonLoader {
if (from.to.notEmpty()) data.properties.add(from);
}
data.offsetRotation = constraintMap.getFloat("rotation", 0);
data.offsetX = constraintMap.getFloat("x", 0) * scale;
data.offsetY = constraintMap.getFloat("y", 0) * scale;
data.offsetScaleX = constraintMap.getFloat("scaleX", 0);
data.offsetScaleY = constraintMap.getFloat("scaleY", 0);
data.offsetShearY = constraintMap.getFloat("shearY", 0);
data.offsets[0] = constraintMap.getFloat("rotation", 0);
data.offsets[1] = constraintMap.getFloat("x", 0) * scale;
data.offsets[2] = constraintMap.getFloat("y", 0) * scale;
data.offsets[3] = constraintMap.getFloat("scaleX", 0);
data.offsets[4] = constraintMap.getFloat("scaleY", 0);
data.offsets[5] = constraintMap.getFloat("shearY", 0);
TransformConstraintPose setup = data.setup;
if (rotate) setup.mixRotate = constraintMap.getFloat("mixRotate", 1);
@ -408,10 +393,22 @@ public class SkeletonJson extends SkeletonLoader {
case "slider" -> {
var data = new SliderData(name);
data.skinRequired = skinRequired;
data.loop = constraintMap.getBoolean("loop", false);
data.additive = constraintMap.getBoolean("additive", false);
data.loop = constraintMap.getBoolean("loop", false);
data.setup.time = constraintMap.getFloat("time", 0);
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);
}
}
@ -523,6 +520,25 @@ public class SkeletonJson extends SkeletonLoader {
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) {
float scale = this.scale;
name = map.getString("name", name);

View File

@ -41,12 +41,19 @@ import com.esotericsoftware.spine.Animation.Timeline;
* <p>
* 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 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());
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) {
var copy = new Slider(data);
var copy = new Slider(data, skeleton);
copy.pose.set(pose);
return copy;
}
@ -54,9 +61,18 @@ public class Slider extends Constraint<Slider, SliderData, SliderPose> {
public void update (Skeleton skeleton, Physics physics) {
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;
Timeline[] timelines = data.animation.timelines.items;
int timelineCount = data.animation.timelines.size;
Timeline[] timelines = animation.timelines.items;
int timelineCount = animation.timelines.size;
Bone[] bones = skeleton.bones.items;
if (pose.mix == 1) {
for (int i = 0; i < timelineCount; i++)
@ -72,8 +88,8 @@ public class Slider extends Constraint<Slider, SliderData, SliderPose> {
}
SliderPose pose = applied;
data.animation.apply(skeleton, pose.time, pose.time, data.loop, null, pose.mix,
data.additive ? MixBlend.add : MixBlend.replace, MixDirection.in, true);
animation.apply(skeleton, pose.time, pose.time, data.loop, null, pose.mix, data.additive ? MixBlend.add : MixBlend.replace,
MixDirection.in, true);
}
void sort (Skeleton skeleton) {
@ -108,4 +124,12 @@ public class Slider extends Constraint<Slider, SliderData, SliderPose> {
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;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.TransformConstraintData.FromProperty;
/** Stores the setup pose for a {@link PhysicsConstraint}.
* <p>
* See <a href="https://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
public class SliderData extends ConstraintData<Slider, SliderPose> {
Animation animation;
boolean additive, loop;
@Null BoneData bone;
@Null FromProperty property;
float scale;
boolean local;
public SliderData (String name) {
super(name, new SliderPose());
}
public Slider create (Skeleton skeleton) {
return new Slider(this);
return new Slider(this, skeleton);
}
public Animation getAnimation () {
@ -67,4 +75,36 @@ public class SliderData extends ConstraintData<Slider, SliderPose> {
public void setLoop (boolean 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;
boolean localSource = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp;
float[] offsets = data.offsets;
BonePose source = this.source.applied;
int update = skeleton.update;
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);
for (int f = 0; f < fn; 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;
for (int t = 0, tn = from.to.size; t < tn; t++) {
ToProperty to = toItems[t];

View File

@ -39,7 +39,7 @@ import com.badlogic.gdx.utils.Array;
public class TransformConstraintData extends ConstraintData<TransformConstraint, TransformConstraintPose> {
final Array<BoneData> bones = new Array(true, 0, BoneData[]::new);
BoneData source;
float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
float[] offsets = new float[6];
boolean localSource, localTarget, additive, clamp;
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. */
public float getOffsetRotation () {
return offsetRotation;
return offsets[0];
}
public void setOffsetRotation (float offsetRotation) {
this.offsetRotation = offsetRotation;
offsets[0] = offsetRotation;
}
/** An offset added to the constrained bone X translation. */
public float getOffsetX () {
return offsetX;
return offsets[1];
}
public void setOffsetX (float offsetX) {
this.offsetX = offsetX;
offsets[1] = offsetX;
}
/** An offset added to the constrained bone Y translation. */
public float getOffsetY () {
return offsetY;
return offsets[2];
}
public void setOffsetY (float offsetY) {
this.offsetY = offsetY;
offsets[2] = offsetY;
}
/** An offset added to the constrained bone scaleX. */
public float getOffsetScaleX () {
return offsetScaleX;
return offsets[3];
}
public void setOffsetScaleX (float offsetScaleX) {
this.offsetScaleX = offsetScaleX;
offsets[3] = offsetScaleX;
}
/** An offset added to the constrained bone scaleY. */
public float getOffsetScaleY () {
return offsetScaleY;
return offsets[4];
}
public void setOffsetScaleY (float offsetScaleY) {
this.offsetScaleY = offsetScaleY;
offsets[4] = offsetScaleY;
}
/** An offset added to the constrained bone shearY. */
public float getOffsetShearY () {
return offsetShearY;
return offsets[5];
}
public void setOffsetShearY (float offsetShearY) {
this.offsetShearY = offsetShearY;
offsets[5] = offsetShearY;
}
/** 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);
/** 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}. */
@ -192,10 +192,10 @@ public class TransformConstraintData extends ConstraintData<TransformConstraint,
}
static public class FromRotate extends FromProperty {
public float value (TransformConstraintData data, BonePose source, boolean local) {
if (local) return source.rotation + data.offsetRotation;
public float value (BonePose source, boolean local, float[] offsets) {
if (local) return source.rotation + offsets[0];
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;
return value;
}
@ -229,8 +229,8 @@ public class TransformConstraintData extends ConstraintData<TransformConstraint,
}
static public class FromX extends FromProperty {
public float value (TransformConstraintData data, BonePose source, boolean local) {
return local ? source.x + data.offsetX : data.offsetX * source.a + data.offsetY * source.b + source.worldX;
public float value (BonePose source, boolean local, float[] offsets) {
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 {
public float value (TransformConstraintData data, BonePose source, boolean local) {
return local ? source.y + data.offsetY : data.offsetX * source.c + data.offsetY * source.d + source.worldY;
public float value (BonePose source, boolean local, float[] offsets) {
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 {
public float value (TransformConstraintData data, BonePose source, boolean local) {
return (local ? source.scaleX : (float)Math.sqrt(source.a * source.a + source.c * source.c)) + data.offsetScaleX;
public float value (BonePose source, boolean local, float[] offsets) {
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 {
public float value (TransformConstraintData data, BonePose source, boolean local) {
return (local ? source.scaleY : (float)Math.sqrt(source.b * source.b + source.d * source.d)) + data.offsetScaleY;
public float value (BonePose source, boolean local, float[] offsets) {
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 {
public float value (TransformConstraintData data, BonePose source, boolean local) {
return (local ? source.shearY : (atan2(source.d, source.b) - atan2(source.c, source.a)) * radDeg - 90)
+ data.offsetShearY;
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) + offsets[5];
}
}