From bbc6c2e37efe2f55fa2cee3b33accb3c7c416055 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sun, 27 Apr 2025 22:53:51 -0400 Subject: [PATCH] [libgdx] Added mapping a bone property to a slider. --- .../spine/SkeletonBinary.java | 72 ++++++++++++------- .../esotericsoftware/spine/SkeletonJson.java | 64 ++++++++++------- .../com/esotericsoftware/spine/Slider.java | 36 ++++++++-- .../esotericsoftware/spine/SliderData.java | 42 ++++++++++- .../spine/TransformConstraint.java | 3 +- .../spine/TransformConstraintData.java | 55 +++++++------- 6 files changed, 186 insertions(+), 86 deletions(-) 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 eb619c648..cd13d1300 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -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; } } 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 150434e73..acdb91ed5 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -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); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java index aaa51b5f8..9e6d1de7a 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java @@ -41,12 +41,19 @@ import com.esotericsoftware.spine.Animation.Timeline; *

* See Physics constraints in the Spine User Guide. */ public class Slider extends Constraint { - 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 { 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 { } 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 { skeleton.resetCache(constraints[timeline.getConstraintIndex()]); } } + + public Bone getBone () { + return bone; + } + + public void setBone (Bone bone) { + this.bone = bone; + } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SliderData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SliderData.java index d62815774..c3506c91e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SliderData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SliderData.java @@ -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}. *

* See Physics constraints in the Spine User Guide. */ public class SliderData extends ConstraintData { 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 { 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; + } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java index aa6ce3b17..c7428e009 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java @@ -69,6 +69,7 @@ public class TransformConstraint extends Constraint { final Array 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 properties = new Array(true, 1, FromProperty[]::new); @@ -68,56 +68,56 @@ public class TransformConstraintData extends ConstraintData 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 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