From f4f22cd5a9346198798ac340481db6b5b8c7e182 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sat, 23 Mar 2024 11:14:38 -0400 Subject: [PATCH] [libgdx] Added bone transform inheritance timeline. * Renamed TransformMode to Inherit. * Inherit has setup pose and skeleton instance values. * Skip 2-bone IK for non-normal inheritance. --- .../com/esotericsoftware/spine/Animation.java | 47 ++++++++++++++++++- .../src/com/esotericsoftware/spine/Bone.java | 26 +++++++--- .../com/esotericsoftware/spine/BoneData.java | 18 +++---- .../esotericsoftware/spine/IkConstraint.java | 6 ++- .../spine/SkeletonBinary.java | 16 +++++-- .../esotericsoftware/spine/SkeletonJson.java | 14 ++++-- 6 files changed, 102 insertions(+), 25 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 de21089a54..4b216ba0eb 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -39,6 +39,7 @@ import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.ObjectSet; +import com.esotericsoftware.spine.BoneData.Inherit; import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.HasTextureRegion; import com.esotericsoftware.spine.attachments.Sequence; @@ -173,7 +174,7 @@ static public enum MixDirection { } static private enum Property { - rotate, x, y, scaleX, scaleY, shearX, shearY, // + rotate, x, y, scaleX, scaleY, shearX, shearY, inherit, // rgb, alpha, rgb2, // attachment, deform, // event, drawOrder, // @@ -944,6 +945,50 @@ public void apply (Skeleton skeleton, float lastTime, float time, @Null ArrayframeCount, inclusive. + * @param time The frame time in seconds. */ + public void setFrame (int frame, float time, Inherit inherit) { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + INHERIT] = inherit.ordinal(); + } + + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, + MixDirection direction) { + + Bone bone = skeleton.bones.get(boneIndex); + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) { + if (blend == setup || blend == first) bone.inherit = bone.data.inherit; + return; + } + bone.inherit = Inherit.values[(int)frames[search(frames, time, ENTRIES) + INHERIT]]; + } + } + /** Changes a slot's {@link Slot#getColor()}. */ static public class RGBATimeline extends CurveTimeline implements SlotTimeline { static public final int ENTRIES = 5; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java index c84dded411..ed6546b440 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -37,7 +37,7 @@ import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Null; -import com.esotericsoftware.spine.BoneData.TransformMode; +import com.esotericsoftware.spine.BoneData.Inherit; import com.esotericsoftware.spine.Skeleton.Physics; /** Stores a bone's current pose. @@ -54,6 +54,7 @@ public class Bone implements Updatable { float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; float a, b, worldX; float c, d, worldY; + Inherit inherit; boolean sorted, active; @@ -80,6 +81,7 @@ public Bone (Bone bone, Skeleton skeleton, @Null Bone parent) { scaleY = bone.scaleY; shearX = bone.shearX; shearY = bone.shearY; + inherit = bone.inherit; } /** Computes the world transform using the parent bone and this bone's local applied transform. */ @@ -127,7 +129,7 @@ public void updateWorldTransform (float x, float y, float rotation, float scaleX worldX = pa * x + pb * y + parent.worldX; worldY = pc * x + pd * y + parent.worldY; - switch (data.transformMode) { + switch (inherit) { case normal: { float rx = (rotation + shearX) * degRad; float ry = (rotation + 90 + shearY) * degRad; @@ -187,8 +189,7 @@ public void updateWorldTransform (float x, float y, float rotation, float scaleX za *= s; zc *= s; s = (float)Math.sqrt(za * za + zc * zc); - if (data.transformMode == TransformMode.noScale - && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s; + if (inherit == Inherit.noScale && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s; rotation = PI / 2 + atan2(zc, za); float zb = cos(rotation) * s; float zd = sin(rotation) * s; @@ -219,6 +220,7 @@ public void setToSetupPose () { scaleY = data.scaleY; shearX = data.shearX; shearY = data.shearY; + inherit = data.inherit; } /** The bone's setup pose data. */ @@ -325,6 +327,16 @@ public void setShearY (float shearY) { this.shearY = shearY; } + /** Controls how parent world transforms affect this bone. */ + public Inherit getInherit () { + return inherit; + } + + public void setInherit (Inherit inherit) { + if (inherit == null) throw new IllegalArgumentException("inherit cannot be null."); + this.inherit = inherit; + } + // -- Applied transform /** The applied local x translation. */ @@ -420,13 +432,13 @@ public void updateAppliedTransform () { ay = (dy * id - dx * ic); float ra, rb, rc, rd; - if (data.transformMode == TransformMode.onlyTranslation) { + if (inherit == Inherit.onlyTranslation) { ra = a; rb = b; rc = c; rd = d; } else { - switch (data.transformMode) { + switch (inherit) { case noRotationOrReflection: { float s = Math.abs(pa * pd - pb * pc) / (pa * pa + pc * pc); float sa = pa / skeleton.scaleX; @@ -448,7 +460,7 @@ public void updateAppliedTransform () { pa *= s; pc *= s; s = (float)Math.sqrt(pa * pa + pc * pc); - if (data.transformMode == TransformMode.noScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s; + if (inherit == Inherit.noScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s; r = PI / 2 + atan2(pc, pa); pb = cos(r) * s; pd = sin(r) * s; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java index 497a02eeb5..a55727bb2d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java @@ -41,7 +41,7 @@ public class BoneData { @Null final BoneData parent; float length; float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - TransformMode transformMode = TransformMode.normal; + Inherit inherit = Inherit.normal; boolean skinRequired; // Nonessential. @@ -169,14 +169,14 @@ public void setShearY (float shearY) { this.shearY = shearY; } - /** The transform mode for how parent world transforms affect this bone. */ - public TransformMode getTransformMode () { - return transformMode; + /** Determines how parent world transforms affect this bone. */ + public Inherit getInherit () { + return inherit; } - public void setTransformMode (TransformMode transformMode) { - if (transformMode == null) throw new IllegalArgumentException("transformMode cannot be null."); - this.transformMode = transformMode; + public void setInherit (Inherit inherit) { + if (inherit == null) throw new IllegalArgumentException("inherit cannot be null."); + this.inherit = inherit; } /** When true, {@link Skeleton#updateWorldTransform(Physics)} only updates this bone if the {@link Skeleton#getSkin()} contains @@ -220,9 +220,9 @@ public String toString () { } /** Determines how a bone inherits world transforms from parent bones. */ - static public enum TransformMode { + static public enum Inherit { normal, onlyTranslation, noRotationOrReflection, noScale, noScaleOrReflection; - static public final TransformMode[] values = TransformMode.values(); + static public final Inherit[] values = Inherit.values(); } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java index c4d6be61f8..8e0d7408d9 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -33,6 +33,7 @@ import com.badlogic.gdx.utils.Array; +import com.esotericsoftware.spine.BoneData.Inherit; import com.esotericsoftware.spine.Skeleton.Physics; /** Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of @@ -189,7 +190,7 @@ static public void apply (Bone bone, float targetX, float targetY, boolean compr Bone p = bone.parent; float pa = p.a, pb = p.b, pc = p.c, pd = p.d; float rotationIK = -bone.ashearX - bone.arotation, tx, ty; - switch (bone.data.transformMode) { + switch (bone.inherit) { case onlyTranslation: tx = (targetX - bone.worldX) * Math.signum(bone.skeleton.scaleX); ty = (targetY - bone.worldY) * Math.signum(bone.skeleton.scaleY); @@ -221,7 +222,7 @@ else if (rotationIK < -180) // rotationIK += 360; float sx = bone.ascaleX, sy = bone.ascaleY; if (compress || stretch) { - switch (bone.data.transformMode) { + switch (bone.inherit) { case noScale: case noScaleOrReflection: tx = targetX - bone.worldX; @@ -246,6 +247,7 @@ static public void apply (Bone parent, Bone child, float targetX, float targetY, 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 (parent.inherit != Inherit.normal || child.inherit != Inherit.normal) return; float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX; int os1, os2, s2; if (psx < 0) { 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 2d2e2b748b..5f83563637 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -52,6 +52,7 @@ import com.esotericsoftware.spine.Animation.DrawOrderTimeline; import com.esotericsoftware.spine.Animation.EventTimeline; import com.esotericsoftware.spine.Animation.IkConstraintTimeline; +import com.esotericsoftware.spine.Animation.InheritTimeline; import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline; import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline; import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline; @@ -80,7 +81,7 @@ import com.esotericsoftware.spine.Animation.TranslateTimeline; import com.esotericsoftware.spine.Animation.TranslateXTimeline; import com.esotericsoftware.spine.Animation.TranslateYTimeline; -import com.esotericsoftware.spine.BoneData.TransformMode; +import com.esotericsoftware.spine.BoneData.Inherit; import com.esotericsoftware.spine.PathConstraintData.PositionMode; import com.esotericsoftware.spine.PathConstraintData.RotateMode; import com.esotericsoftware.spine.PathConstraintData.SpacingMode; @@ -113,6 +114,7 @@ public class SkeletonBinary extends SkeletonLoader { static public final int BONE_SHEAR = 7; static public final int BONE_SHEARX = 8; static public final int BONE_SHEARY = 9; + static public final int BONE_INHERIT = 10; static public final int SLOT_ATTACHMENT = 0; static public final int SLOT_RGBA = 1; @@ -209,7 +211,7 @@ public SkeletonData readSkeletonData (InputStream dataInput) { data.shearX = input.readFloat(); data.shearY = input.readFloat(); data.length = input.readFloat() * scale; - data.transformMode = TransformMode.values[input.readInt(true)]; + data.inherit = Inherit.values[input.readByte()]; data.skinRequired = input.readBoolean(); if (nonessential) { Color.rgba8888ToColor(data.color, input.readInt()); @@ -844,7 +846,15 @@ private Animation readAnimation (SkeletonInput input, String name, SkeletonData for (int i = 0, n = input.readInt(true); i < n; i++) { int boneIndex = input.readInt(true); for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) { - int type = input.readByte(), frameCount = input.readInt(true), bezierCount = input.readInt(true); + int type = input.readByte(), frameCount = input.readInt(true); + if (type == BONE_INHERIT) { + InheritTimeline timeline = new InheritTimeline(frameCount, boneIndex); + for (int frame = 0; frame < frameCount; frame++) + timeline.setFrame(frame, input.readFloat(), Inherit.values[input.readByte()]); + timelines.add(timeline); + continue; + } + int bezierCount = input.readInt(true); switch (type) { case BONE_ROTATE: readTimeline(input, timelines, new RotateTimeline(frameCount, bezierCount, boneIndex), 1); 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 56e266eb2f..3ce055693c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -53,6 +53,7 @@ import com.esotericsoftware.spine.Animation.DrawOrderTimeline; import com.esotericsoftware.spine.Animation.EventTimeline; import com.esotericsoftware.spine.Animation.IkConstraintTimeline; +import com.esotericsoftware.spine.Animation.InheritTimeline; import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline; import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline; import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline; @@ -81,7 +82,7 @@ import com.esotericsoftware.spine.Animation.TranslateTimeline; import com.esotericsoftware.spine.Animation.TranslateXTimeline; import com.esotericsoftware.spine.Animation.TranslateYTimeline; -import com.esotericsoftware.spine.BoneData.TransformMode; +import com.esotericsoftware.spine.BoneData.Inherit; import com.esotericsoftware.spine.PathConstraintData.PositionMode; import com.esotericsoftware.spine.PathConstraintData.RotateMode; import com.esotericsoftware.spine.PathConstraintData.SpacingMode; @@ -166,7 +167,7 @@ public SkeletonData readSkeletonData (JsonValue root) { data.scaleY = boneMap.getFloat("scaleY", 1); data.shearX = boneMap.getFloat("shearX", 0); data.shearY = boneMap.getFloat("shearY", 0); - data.transformMode = TransformMode.valueOf(boneMap.getString("transform", TransformMode.normal.name())); + data.inherit = Inherit.valueOf(boneMap.getString("inherit", Inherit.normal.name())); data.skinRequired = boneMap.getBoolean("skin", false); String color = boneMap.getString("color", null); @@ -812,7 +813,14 @@ else if (timelineName.equals("shear")) { timelines.add(readTimeline(keyMap, new ShearXTimeline(frames, frames, bone.index), 0, 1)); else if (timelineName.equals("sheary")) timelines.add(readTimeline(keyMap, new ShearYTimeline(frames, frames, bone.index), 0, 1)); - else + else if (timelineName.equals("inherit")) { + InheritTimeline timeline = new InheritTimeline(frames, bone.index); + for (int frame = 0; keyMap != null; keyMap = keyMap.next, frame++) { + float time = keyMap.getFloat("time", 0); + timeline.setFrame(frame, time, Inherit.valueOf(keyMap.getString("inherit", Inherit.normal.name()))); + } + timelines.add(timeline); + } else throw new RuntimeException("Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name + ")"); } }