From a8c081dbb14d02128a9ffcd31651c0f5c5aadea3 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Tue, 15 Apr 2025 23:45:34 -0400 Subject: [PATCH] [libgdx] Optimized setting the applied pose. --- .../spine/AnimationState.java | 6 +- .../src/com/esotericsoftware/spine/Bone.java | 20 +- .../esotericsoftware/spine/BoneApplied.java | 15 +- .../esotericsoftware/spine/Constrained.java | 36 ++++ .../esotericsoftware/spine/IkConstraint.java | 33 ++- .../spine/PathConstraint.java | 19 +- .../spine/PhysicsConstraint.java | 25 ++- .../com/esotericsoftware/spine/Skeleton.java | 190 ++++++++---------- .../spine/SkeletonBinary.java | 4 +- .../esotericsoftware/spine/SkeletonJson.java | 6 +- .../spine/SkeletonRenderer.java | 6 +- .../src/com/esotericsoftware/spine/Skin.java | 2 +- .../com/esotericsoftware/spine/Slider.java | 19 +- .../esotericsoftware/spine/SliderPose.java | 1 - .../src/com/esotericsoftware/spine/Slot.java | 19 +- .../spine/TransformConstraint.java | 32 ++- .../spine/{Updatable.java => Update.java} | 2 +- 17 files changed, 261 insertions(+), 174 deletions(-) create mode 100644 spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Constrained.java rename spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/{Updatable.java => Update.java} (98%) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index 0097bdc77..8ca3d121c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -278,7 +278,7 @@ public class AnimationState { var slot = (Slot)slots[i]; if (slot.attachmentState == setupState) { String attachmentName = slot.data.attachmentName; - slot.getPose().setAttachment(attachmentName == null ? null : skeleton.getAttachment(slot.data.index, attachmentName)); + slot.pose.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slot.data.index, attachmentName)); } } unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot. @@ -399,7 +399,7 @@ public class AnimationState { } private void setAttachment (Skeleton skeleton, Slot slot, String attachmentName, boolean attachments) { - slot.getPose().setAttachment(attachmentName == null ? null : skeleton.getAttachment(slot.data.index, attachmentName)); + slot.pose.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slot.data.index, attachmentName)); if (attachments) slot.attachmentState = unkeyedState + CURRENT; } @@ -417,7 +417,7 @@ public class AnimationState { Bone bone = skeleton.bones.get(timeline.boneIndex); if (!bone.active) return; - BonePose pose = bone.getPose(), setup = bone.data.setup; + BonePose pose = bone.pose, setup = bone.data.setup; float[] frames = timeline.frames; float r1, r2; if (time < frames[0]) { // Time is before first frame. 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 9fcc49a6c..3c1acf9a0 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -38,13 +38,13 @@ import com.badlogic.gdx.utils.Null; * A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a * local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a * constraint or application code modifies the world transform after it was computed from the local transform. */ -public class Bone { +public class Bone implements Constrained { final BoneData data; final Skeleton skeleton; @Null final Bone parent; final Array children = new Array(); - final BonePose pose = new BonePose(); - final BoneApplied applied = new BoneApplied(this); + final BoneApplied pose = new BoneApplied(this), constrained = new BoneApplied(this); + BoneApplied applied = pose; boolean sorted, active; public Bone (BoneData data, Skeleton skeleton, @Null Bone parent) { @@ -74,16 +74,26 @@ public class Bone { return data; } - /** Returns the bone's pose. */ public BonePose getPose () { return pose; } - /** Returns the bone's applied pose. */ public BoneApplied getAppliedPose () { return applied; } + public BoneApplied getConstrainedPose () { + return constrained; + } + + public void setConstrained (boolean constrained) { + applied = constrained ? this.constrained : pose; + } + + public void resetAppliedPose () { + applied.set(pose); + } + /** The skeleton this bone belongs to. */ public Skeleton getSkeleton () { return skeleton; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneApplied.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneApplied.java index f86947504..e3b8efdb6 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneApplied.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneApplied.java @@ -6,21 +6,18 @@ import static com.esotericsoftware.spine.utils.SpineUtils.*; import com.badlogic.gdx.math.Matrix3; import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.utils.Null; import com.esotericsoftware.spine.BoneData.Inherit; /** The applied pose for a bone. This is the {@link Bone} pose with constraints applied and the world transform computed by * {@link Skeleton#updateWorldTransform(Physics)}. */ -public class BoneApplied extends BonePose implements Updatable { +public class BoneApplied extends BonePose implements Update { final Bone bone; - @Null final BoneApplied parent; float a, b, worldX; float c, d, worldY; BoneApplied (Bone bone) { this.bone = bone; - parent = bone.parent == null ? null : bone.parent.applied; } /** Computes the world transform using the parent bone and this bone's local applied transform. */ @@ -37,8 +34,7 @@ public class BoneApplied extends BonePose implements Updatable { * See World transforms in the Spine * Runtimes Guide. */ public void update (Physics physics) { - BoneApplied parent = this.parent; - if (parent == null) { // Root bone. + if (bone.parent == null) { // Root bone. Skeleton skeleton = bone.skeleton; float sx = skeleton.scaleX, sy = skeleton.scaleY; float rx = (rotation + shearX) * degRad; @@ -52,6 +48,7 @@ public class BoneApplied extends BonePose implements Updatable { return; } + BoneApplied parent = bone.parent.applied; float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; worldX = pa * x + pb * y + parent.worldX; worldY = pc * x + pd * y + parent.worldY; @@ -147,7 +144,7 @@ public class BoneApplied extends BonePose implements Updatable { * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The local transform after * calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. */ public void updateLocalTransform () { - BoneApplied parent = this.parent; + BoneApplied parent = bone.parent.applied; if (parent == null) { x = worldX - bone.skeleton.x; y = worldY - bone.skeleton.y; @@ -336,13 +333,13 @@ public class BoneApplied extends BonePose implements Updatable { /** Transforms a point from world coordinates to the parent bone's local coordinates. */ public Vector2 worldToParent (Vector2 world) { if (world == null) throw new IllegalArgumentException("world cannot be null."); - return parent == null ? world : parent.worldToLocal(world); + return bone.parent == null ? world : bone.parent.applied.worldToLocal(world); } /** Transforms a point from the parent bone's coordinates to world coordinates. */ public Vector2 parentToWorld (Vector2 world) { if (world == null) throw new IllegalArgumentException("world cannot be null."); - return parent == null ? world : parent.localToWorld(world); + return bone.parent == null ? world : bone.parent.applied.localToWorld(world); } /** Transforms a world rotation to a local rotation. */ diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Constrained.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Constrained.java new file mode 100644 index 000000000..ac8e8100d --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Constrained.java @@ -0,0 +1,36 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +package com.esotericsoftware.spine; + +public interface Constrained { + public void setConstrained (boolean constrained); + + public void resetAppliedPose (); +} 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 41c6eb707..4e4f843a6 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -39,11 +39,12 @@ import com.esotericsoftware.spine.BoneData.Inherit; * the last bone is as close to the target bone as possible. *

* See IK constraints in the Spine User Guide. */ -public class IkConstraint implements Updatable { +public class IkConstraint implements Constrained, Update { final IkConstraintData data; final Array bones; - BoneApplied target; - final IkConstraintPose pose = new IkConstraintPose(), applied = new IkConstraintPose(); + Bone target; + final IkConstraintPose pose = new IkConstraintPose(), constrained = new IkConstraintPose(); + IkConstraintPose applied = pose; boolean active; public IkConstraint (IkConstraintData data, Skeleton skeleton) { @@ -53,9 +54,9 @@ public class IkConstraint implements Updatable { bones = new Array(data.bones.size); for (BoneData boneData : data.bones) - bones.add(skeleton.bones.get(boneData.index).applied); + bones.add(skeleton.bones.get(boneData.index).constrained); - target = skeleton.bones.get(data.target.index).applied; + target = skeleton.bones.get(data.target.index); setupPose(); } @@ -74,7 +75,7 @@ public class IkConstraint implements Updatable { public void update (Physics physics) { IkConstraintPose a = applied; if (a.mix == 0) return; - BoneApplied target = this.target; + BoneApplied target = this.target.applied; Object[] bones = this.bones.items; switch (this.bones.size) { case 1 -> apply((BoneApplied)bones[0], target.worldX, target.worldY, a.compress, a.stretch, data.uniform, a.mix); @@ -90,11 +91,11 @@ public class IkConstraint implements Updatable { } /** The bone that is the IK target. */ - public BoneApplied getTarget () { + public Bone getTarget () { return target; } - public void setTarget (BoneApplied target) { + public void setTarget (Bone target) { if (target == null) throw new IllegalArgumentException("target cannot be null."); this.target = target; } @@ -107,6 +108,18 @@ public class IkConstraint implements Updatable { return applied; } + public IkConstraintPose getConstrainedPose () { + return constrained; + } + + public void setConstrained (boolean constrained) { + applied = constrained ? this.constrained : pose; + } + + public void resetAppliedPose () { + applied.set(pose); + } + /** Returns false when this constraint won't be updated by * {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the * {@link Skeleton#getSkin() active skin} does not contain this item. @@ -131,7 +144,7 @@ public class IkConstraint implements Updatable { static public void apply (BoneApplied bone, float targetX, float targetY, boolean compress, boolean stretch, boolean uniform, float alpha) { if (bone == null) throw new IllegalArgumentException("bone cannot be null."); - BoneApplied p = bone.parent; + BoneApplied p = bone.bone.parent.applied; float pa = p.a, pb = p.b, pc = p.c, pd = p.d; float rotationIK = -bone.shearX - bone.rotation, tx, ty; switch (bone.inherit) { @@ -222,7 +235,7 @@ public class IkConstraint implements Updatable { cwx = a * child.x + b * child.y + parent.worldX; cwy = c * child.x + d * child.y + parent.worldY; } - BoneApplied pp = parent.parent; + BoneApplied pp = parent.bone.parent.applied; a = pp.a; b = pp.b; c = pp.c; 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 aa0b7e33f..f2120ec89 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -45,14 +45,15 @@ import com.esotericsoftware.spine.attachments.PathAttachment; * constrained bones so they follow a {@link PathAttachment}. *

* See Path constraints in the Spine User Guide. */ -public class PathConstraint implements Updatable { +public class PathConstraint implements Constrained, Update { static final int NONE = -1, BEFORE = -2, AFTER = -3; static final float epsilon = 0.00001f; final PathConstraintData data; final Array bones; Slot slot; - final PathConstraintPose pose = new PathConstraintPose(), applied = new PathConstraintPose(); + final PathConstraintPose pose = new PathConstraintPose(), constrained = new PathConstraintPose(); + PathConstraintPose applied = pose; boolean active; private final FloatArray spaces = new FloatArray(), positions = new FloatArray(); @@ -72,7 +73,7 @@ public class PathConstraint implements Updatable { bones = new Array(data.bones.size); for (BoneData boneData : data.bones) - bones.add(skeleton.bones.get(boneData.index).applied); + bones.add(skeleton.bones.get(boneData.index).constrained); slot = skeleton.slots.get(data.slot.index); @@ -495,6 +496,18 @@ public class PathConstraint implements Updatable { return applied; } + public PathConstraintPose getConstrainedPose () { + return constrained; + } + + public void setConstrained (boolean constrained) { + applied = constrained ? this.constrained : pose; + } + + public void resetAppliedPose () { + applied.set(pose); + } + /** Returns false when this constraint won't be updated by * {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the * {@link Skeleton#getSkin() active skin} does not contain this item. diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java index 8fa8b0cbc..e5b7ccba4 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java @@ -34,11 +34,12 @@ import static com.esotericsoftware.spine.utils.SpineUtils.*; /** Stores the current pose for a physics constraint. A physics constraint applies physics to bones. *

* See Physics constraints in the Spine User Guide. */ -public class PhysicsConstraint implements Updatable { +public class PhysicsConstraint implements Constrained, Update { final PhysicsConstraintData data; final Skeleton skeleton; BoneApplied bone; - final PhysicsConstraintPose pose = new PhysicsConstraintPose(), applied = new PhysicsConstraintPose(); + final PhysicsConstraintPose pose = new PhysicsConstraintPose(), constrained = new PhysicsConstraintPose(); + PhysicsConstraintPose applied = pose; boolean active; boolean reset = true; @@ -49,19 +50,13 @@ public class PhysicsConstraint implements Updatable { float scaleOffset, scaleVelocity; float remaining, lastTime; - private PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton, BoneApplied bone) { - this.data = data; - this.skeleton = skeleton; - this.bone = bone; - } - public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton) { if (data == null) throw new IllegalArgumentException("data cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); this.data = data; this.skeleton = skeleton; - bone = skeleton.bones.get(data.bone.index).applied; + bone = skeleton.bones.get(data.bone.index).constrained; setupPose(); } @@ -300,6 +295,18 @@ public class PhysicsConstraint implements Updatable { return applied; } + public PhysicsConstraintPose getConstrainedPose () { + return constrained; + } + + public void setConstrained (boolean constrained) { + applied = constrained ? this.constrained : pose; + } + + public void resetAppliedPose () { + applied.set(pose); + } + /** Returns false when this constraint won't be updated by * {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the * {@link Skeleton#getSkin() active skin} does not contain this item. 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 9e6c63040..795bcebd8 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -38,6 +38,7 @@ import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.Null; import com.esotericsoftware.spine.Animation.BoneTimeline; +import com.esotericsoftware.spine.Animation.Timeline; import com.esotericsoftware.spine.Skin.SkinEntry; import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.ClippingAttachment; @@ -52,7 +53,7 @@ import com.esotericsoftware.spine.utils.SkeletonClipping; * Runtimes Guide. */ public class Skeleton { static private final short[] quadTriangles = {0, 1, 2, 2, 3, 0}; - private static final Object IkConstraint = null; + final SkeletonData data; final Array bones; final Array slots; @@ -62,7 +63,8 @@ public class Skeleton { final Array transformConstraints; final Array pathConstraints; final Array physicsConstraints; - final Array updateCache = new Array(); + final Array updateCache = new Array(); + final Array resetCache = new Array(); @Null Skin skin; final Color color; float x, y, scaleX = 1, scaleY = 1, time; @@ -180,8 +182,8 @@ public class Skeleton { /** Caches information about bones and constraints. Must be called if the {@link #getSkin()} is modified or if bones, * constraints, or weighted path attachments are added or removed. */ public void updateCache () { - Array updateCache = this.updateCache; updateCache.clear(); + resetCache.clear(); int boneCount = bones.size; Object[] bones = this.bones.items; @@ -189,6 +191,7 @@ public class Skeleton { var bone = (Bone)bones[i]; bone.sorted = bone.data.skinRequired; bone.active = !bone.sorted; + bone.setConstrained(false); } if (skin != null) { Object[] skinBones = skin.bones.items; @@ -207,6 +210,17 @@ public class Skeleton { Object[] sliders = this.sliders.items, ikConstraints = this.ikConstraints.items, transformConstraints = this.transformConstraints.items, pathConstraints = this.pathConstraints.items, physicsConstraints = this.physicsConstraints.items; + for (int ii = 0; ii < sliderCount; ii++) + ((Slider)sliders[ii]).setConstrained(false); + for (int ii = 0; ii < ikCount; ii++) + ((IkConstraint)ikConstraints[ii]).setConstrained(false); + for (int ii = 0; ii < transformCount; ii++) + ((TransformConstraint)transformConstraints[ii]).setConstrained(false); + for (int ii = 0; ii < pathCount; ii++) + ((PathConstraint)pathConstraints[ii]).setConstrained(false); + for (int ii = 0; ii < physicsCount; ii++) + ((PhysicsConstraint)physicsConstraints[ii]).setConstrained(false); + int constraintCount = ikCount + transformCount + pathCount + physicsCount; outer: for (int i = 0; i < constraintCount; i++) { @@ -249,47 +263,29 @@ public class Skeleton { for (int i = 0; i < boneCount; i++) sortBone((Bone)bones[i]); - } - private void sortSlider (Slider constraint) { - constraint.active = !constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)); - if (!constraint.active) return; - - Object[] timelines = constraint.data.animation.timelines.items; - int timelineCount = constraint.data.animation.timelines.size; - - Object[] bones = this.bones.items; - for (int i = 0; i < timelineCount; i++) - if (timelines[i] instanceof BoneTimeline boneTimeline) sortBone((Bone)bones[boneTimeline.getBoneIndex()]); - - updateCache.add(constraint); - - for (int i = 0; i < timelineCount; i++) { - if (timelines[i] instanceof BoneTimeline boneTimeline) { - var bone = (Bone)bones[boneTimeline.getBoneIndex()]; - sortReset(bone.children); - bone.sorted = false; - } - } - for (int i = 0; i < timelineCount; i++) - if (timelines[i] instanceof BoneTimeline boneTimeline) sortBone((Bone)bones[boneTimeline.getBoneIndex()]); + Object[] updateCache = this.updateCache.items; + for (int i = 0, n = this.updateCache.size; i < n; i++) + if (updateCache[i] instanceof Bone bone) updateCache[i] = bone.applied; } private void sortIkConstraint (IkConstraint constraint) { - constraint.active = constraint.target.bone.active + constraint.active = constraint.target.active && (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true))); if (!constraint.active) return; - sortBone(constraint.target.bone); + sortBone(constraint.target); Array constrained = constraint.bones; Bone parent = constrained.first().bone; sortBone(parent); + resetCache(parent); if (constrained.size == 1) { updateCache.add(constraint); sortReset(parent.children); } else { Bone child = constrained.peek().bone; + resetCache(child); sortBone(child); updateCache.add(constraint); @@ -300,23 +296,27 @@ public class Skeleton { } private void sortTransformConstraint (TransformConstraint constraint) { - constraint.active = constraint.source.bone.active + constraint.active = constraint.source.active && (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true))); if (!constraint.active) return; - sortBone(constraint.source.bone); + sortBone(constraint.source); Object[] constrained = constraint.bones.items; int boneCount = constraint.bones.size; if (constraint.data.localSource) { for (int i = 0; i < boneCount; i++) { Bone child = ((BoneApplied)constrained[i]).bone; + resetCache(child); sortBone(child.parent); sortBone(child); } } else { - for (int i = 0; i < boneCount; i++) - sortBone(((BoneApplied)constrained[i]).bone); + for (int i = 0; i < boneCount; i++) { + Bone bone = ((BoneApplied)constrained[i]).bone; + resetCache(bone); + sortBone(bone); + } } updateCache.add(constraint); @@ -343,8 +343,11 @@ public class Skeleton { Object[] constrained = constraint.bones.items; int boneCount = constraint.bones.size; - for (int i = 0; i < boneCount; i++) - sortBone(((BoneApplied)constrained[i]).bone); + for (int i = 0; i < boneCount; i++) { + Bone bone = ((BoneApplied)constrained[i]).bone; + resetCache(bone); + sortBone(bone); + } updateCache.add(constraint); @@ -386,18 +389,53 @@ public class Skeleton { sortBone(bone); + resetCache(bone); updateCache.add(constraint); sortReset(bone.children); bone.sorted = true; } + private void sortSlider (Slider constraint) { + constraint.active = !constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)); + if (!constraint.active) return; + + Object[] timelines = constraint.data.animation.timelines.items; + int timelineCount = constraint.data.animation.timelines.size; + + Object[] bones = this.bones.items; + for (int i = 0; i < timelineCount; i++) { + var timeline = (Timeline)timelines[i]; + if (timeline instanceof BoneTimeline boneTimeline) sortBone((Bone)bones[boneTimeline.getBoneIndex()]); + } + + updateCache.add(constraint); + + for (int i = 0; i < timelineCount; i++) { + if (timelines[i] instanceof BoneTimeline boneTimeline) { + var bone = (Bone)bones[boneTimeline.getBoneIndex()]; + resetCache(bone); + sortReset(bone.children); + bone.sorted = false; + } + } + for (int i = 0; i < timelineCount; i++) + if (timelines[i] instanceof BoneTimeline boneTimeline) sortBone((Bone)bones[boneTimeline.getBoneIndex()]); + } + + private void resetCache (Constrained object) { + if (!resetCache.contains(object, true)) { + resetCache.add(object); + object.setConstrained(true); + } + } + private void sortBone (Bone bone) { if (bone.sorted) return; Bone parent = bone.parent; if (parent != null) sortBone(parent); bone.sorted = true; - updateCache.add(bone.applied); + updateCache.add(bone); } private void sortReset (Array bones) { @@ -415,45 +453,13 @@ public class Skeleton { * See World transforms in the Spine * Runtimes Guide. */ public void updateWorldTransform (Physics physics) { - Object[] objects = this.bones.items; - for (int i = 0, n = this.bones.size; i < n; i++) { - var bone = (Bone)objects[i]; - if (bone.active) bone.applied.set(bone.pose); - } - objects = this.slots.items; - for (int i = 0, n = this.slots.size; i < n; i++) { - var slot = (Slot)objects[i]; - if (slot.bone.active) slot.applied.set(slot.pose); - } - objects = ikConstraints.items; - for (int i = 0, n = ikConstraints.size; i < n; i++) { - var constraint = (IkConstraint)objects[i]; - if (constraint.active) constraint.applied.set(constraint.pose); - } - objects = pathConstraints.items; - for (int i = 0, n = pathConstraints.size; i < n; i++) { - var constraint = (PathConstraint)objects[i]; - if (constraint.active) constraint.applied.set(constraint.pose); - } - objects = transformConstraints.items; - for (int i = 0, n = transformConstraints.size; i < n; i++) { - var constraint = (TransformConstraint)objects[i]; - if (constraint.active) constraint.applied.set(constraint.pose); - } - objects = physicsConstraints.items; - for (int i = 0, n = physicsConstraints.size; i < n; i++) { - var constraint = (PhysicsConstraint)objects[i]; - if (constraint.active) constraint.applied.set(constraint.pose); - } - objects = sliders.items; - for (int i = 0, n = sliders.size; i < n; i++) { - var constraint = (Slider)objects[i]; - if (constraint.active) constraint.applied.set(constraint.pose); - } + Object[] resetCache = this.resetCache.items; + for (int i = 0, n = this.resetCache.size; i < n; i++) + ((Constrained)resetCache[i]).resetAppliedPose(); Object[] updateCache = this.updateCache.items; for (int i = 0, n = this.updateCache.size; i < n; i++) - ((Updatable)updateCache[i]).update(physics); + ((Update)updateCache[i]).update(physics); } /** Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies @@ -464,41 +470,9 @@ public class Skeleton { public void updateWorldTransform (Physics physics, BoneApplied parent) { if (parent == null) throw new IllegalArgumentException("parent cannot be null."); - Object[] objects = this.bones.items; - for (int i = 0, n = this.bones.size; i < n; i++) { - var bone = (Bone)objects[i]; - if (bone.active) bone.applied.set(bone.pose); - } - objects = this.slots.items; - for (int i = 0, n = this.slots.size; i < n; i++) { - var slot = (Slot)objects[i]; - if (slot.bone.active) slot.applied.set(slot.pose); - } - objects = ikConstraints.items; - for (int i = 0, n = ikConstraints.size; i < n; i++) { - var constraint = (IkConstraint)objects[i]; - if (constraint.active) constraint.applied.set(constraint.pose); - } - objects = pathConstraints.items; - for (int i = 0, n = pathConstraints.size; i < n; i++) { - var constraint = (PathConstraint)objects[i]; - if (constraint.active) constraint.applied.set(constraint.pose); - } - objects = transformConstraints.items; - for (int i = 0, n = transformConstraints.size; i < n; i++) { - var constraint = (TransformConstraint)objects[i]; - if (constraint.active) constraint.applied.set(constraint.pose); - } - objects = physicsConstraints.items; - for (int i = 0, n = physicsConstraints.size; i < n; i++) { - var constraint = (PhysicsConstraint)objects[i]; - if (constraint.active) constraint.applied.set(constraint.pose); - } - objects = sliders.items; - for (int i = 0, n = sliders.size; i < n; i++) { - var constraint = (Slider)objects[i]; - if (constraint.active) constraint.applied.set(constraint.pose); - } + Object[] resetCache = this.resetCache.items; + for (int i = 0, n = this.resetCache.size; i < n; i++) + ((Constrained)resetCache[i]).resetAppliedPose(); // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. BoneApplied rootBone = getRootBone().applied; @@ -520,7 +494,7 @@ public class Skeleton { // Update everything except root bone. Object[] updateCache = this.updateCache.items; for (int i = 0, n = this.updateCache.size; i < n; i++) { - var updatable = (Updatable)updateCache[i]; + var updatable = (Update)updateCache[i]; if (updatable != rootBone) updatable.update(physics); } } @@ -578,7 +552,7 @@ public class Skeleton { } /** The list of bones and constraints, sorted in the order they should be updated, as computed by {@link #updateCache()}. */ - public Array getUpdateCache () { + public Array getUpdateCache () { return updateCache; } 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 ac72e6884..bf055ba6b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -343,7 +343,7 @@ public class SkeletonBinary extends SkeletonLoader { if ((flags & 16) != 0) data.offsetScaleY = input.readFloat(); if ((flags & 32) != 0) data.offsetShearY = input.readFloat(); flags = input.read(); - TransformConstraintPose setup = data.getSetupPose(); + TransformConstraintPose setup = data.setup; if ((flags & 1) != 0) setup.mixRotate = input.readFloat(); if ((flags & 2) != 0) setup.mixX = input.readFloat(); if ((flags & 4) != 0) setup.mixY = input.readFloat(); @@ -394,7 +394,7 @@ public class SkeletonBinary extends SkeletonLoader { if ((flags & 32) != 0) data.shearX = input.readFloat(); data.limit = ((flags & 64) != 0 ? input.readFloat() : 5000) * scale; data.step = 1f / input.readUnsignedByte(); - PhysicsConstraintPose setup = data.getSetupPose(); + PhysicsConstraintPose setup = data.setup; setup.inertia = input.readFloat(); setup.strength = input.readFloat(); setup.damping = input.readFloat(); 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 e944bcb55..ca14c5e06 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -204,10 +204,10 @@ public class SkeletonJson extends SkeletonLoader { var data = new SlotData(skeletonData.slots.size, slotName, boneData); String color = slotMap.getString("color", null); - if (color != null) Color.valueOf(color, data.getSetupPose().getColor()); + if (color != null) Color.valueOf(color, data.setup.getColor()); String dark = slotMap.getString("dark", null); - if (dark != null) Color.valueOf(dark, data.getSetupPose().getDarkColor()); + if (dark != null) Color.valueOf(dark, data.setup.getDarkColor()); data.attachmentName = slotMap.getString("attachment", null); data.blendMode = BlendMode.valueOf(slotMap.getString("blend", BlendMode.normal.name())); @@ -390,7 +390,7 @@ public class SkeletonJson extends SkeletonLoader { data.shearX = constraintMap.getFloat("shearX", 0); data.limit = constraintMap.getFloat("limit", 5000) * scale; data.step = 1f / constraintMap.getInt("fps", 60); - PhysicsConstraintPose setup = data.getSetupPose(); + PhysicsConstraintPose setup = data.setup; setup.inertia = constraintMap.getFloat("inertia", 1); setup.strength = constraintMap.getFloat("strength", 100); setup.damping = constraintMap.getFloat("damping", 1); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java index 8dd6eacd8..4c5c73143 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -80,7 +80,7 @@ public class SkeletonRenderer { for (int i = 0, n = skeleton.drawOrder.size; i < n; i++) { var slot = (Slot)drawOrder[i]; if (!slot.bone.active) continue; - SlotPose pose = slot.getAppliedPose(); + SlotPose pose = slot.applied; Attachment attachment = pose.attachment; if (attachment instanceof RegionAttachment region) { region.computeWorldVertices(slot, vertices, 0, 5); @@ -149,7 +149,7 @@ public class SkeletonRenderer { clipper.clipEnd(slot); continue; } - SlotPose pose = slot.getAppliedPose(); + SlotPose pose = slot.applied; Texture texture = null; Attachment attachment = pose.attachment; if (attachment instanceof RegionAttachment region) { @@ -244,7 +244,7 @@ public class SkeletonRenderer { clipper.clipEnd(slot); continue; } - SlotPose pose = slot.getAppliedPose(); + SlotPose pose = slot.applied; Texture texture = null; Attachment attachment = pose.attachment; if (attachment instanceof RegionAttachment region) { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skin.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skin.java index e49c80c95..a4c6f2d49 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skin.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skin.java @@ -157,7 +157,7 @@ public class Skin { Object[] slots = skeleton.slots.items; for (SkinEntry entry : oldSkin.attachments.orderedItems()) { int slotIndex = entry.slotIndex; - SlotPose slot = ((Slot)slots[slotIndex]).getPose(); + SlotPose slot = ((Slot)slots[slotIndex]).pose; if (slot.attachment == entry.attachment) { Attachment attachment = getAttachment(slotIndex, entry.name); if (attachment != null) slot.setAttachment(attachment); 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 0dfcbdf31..f3ca5dd2b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java @@ -35,10 +35,11 @@ import com.esotericsoftware.spine.Animation.MixDirection; /** Stores the setup pose for a {@link PhysicsConstraint}. *

* See Physics constraints in the Spine User Guide. */ -public class Slider implements Updatable { +public class Slider implements Constrained, Update { final SliderData data; final Skeleton skeleton; - final SliderPose pose = new SliderPose(), applied = new SliderPose(); + final SliderPose pose = new SliderPose(), constrained = new SliderPose(); + SliderPose applied = pose; boolean active; @@ -54,7 +55,7 @@ public class Slider implements Updatable { /** Copy constructor. */ public Slider (Slider slider, Skeleton skeleton) { this(slider.data, skeleton); - setupPose(); + pose.set(slider.pose); } public void update (Physics physics) { @@ -74,6 +75,18 @@ public class Slider implements Updatable { return applied; } + public SliderPose getConstrainedPose () { + return constrained; + } + + public void setConstrained (boolean constrained) { + applied = constrained ? this.constrained : pose; + } + + public void resetAppliedPose () { + applied.set(pose); + } + /** Returns false when this constraint won't be updated by * {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the * {@link Skeleton#getSkin() active skin} does not contain this item. diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SliderPose.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SliderPose.java index b613f6d3c..039029584 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SliderPose.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SliderPose.java @@ -38,7 +38,6 @@ public class SliderPose { mix = pose.mix; } - public float getTime () { return time; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java index 14893dc37..168a40236 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java @@ -34,10 +34,11 @@ import com.badlogic.gdx.graphics.Color; /** Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store * state for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared * across multiple skeletons. */ -public class Slot { +public class Slot implements Constrained { final SlotData data; final Bone bone; - final SlotPose pose = new SlotPose(), applied = new SlotPose(); + final SlotPose pose = new SlotPose(), constrained = new SlotPose(); + SlotPose applied = pose; int attachmentState; public Slot (SlotData data, Skeleton skeleton) { @@ -76,16 +77,26 @@ public class Slot { return data; } - /** Returns the slot's pose. */ public SlotPose getPose () { return pose; } - /** Returns the slot's applied pose. */ public SlotPose getAppliedPose () { return applied; } + public SlotPose getConstrainedPose () { + return constrained; + } + + public void setConstrained (boolean constrained) { + applied = constrained ? this.constrained : pose; + } + + public void resetAppliedPose () { + applied.set(pose); + } + /** The bone this slot belongs to. */ public Bone getBone () { return bone; 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 6e2692e8e..54cb02596 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java @@ -40,14 +40,15 @@ import com.esotericsoftware.spine.TransformConstraintData.ToProperty; * bones to match that of the source bone. *

* See Transform constraints in the Spine User Guide. */ -public class TransformConstraint implements Updatable { +public class TransformConstraint implements Constrained, Update { final TransformConstraintData data; final Array bones; - BoneApplied source; - final TransformConstraintPose pose = new TransformConstraintPose(), applied = new TransformConstraintPose(); + Bone source; + final TransformConstraintPose pose = new TransformConstraintPose(), constrained = new TransformConstraintPose(); + TransformConstraintPose applied = pose; boolean active; - public TransformConstraint (TransformConstraintData data, Array bones, BoneApplied source) { + public TransformConstraint (TransformConstraintData data, Array bones, Bone source) { this.data = data; this.bones = bones; this.source = source; @@ -60,9 +61,9 @@ public class TransformConstraint implements Updatable { bones = new Array(data.bones.size); for (BoneData boneData : data.bones) - bones.add(skeleton.bones.get(boneData.index).applied); + bones.add(skeleton.bones.get(boneData.index).constrained); - source = skeleton.bones.get(data.source.index).applied; + source = skeleton.bones.get(data.source.index); setupPose(); } @@ -85,12 +86,13 @@ public class TransformConstraint implements Updatable { TransformConstraintData data = this.data; boolean localFrom = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp; - BoneApplied source = this.source; + BoneApplied source = this.source.applied; Object[] fromItems = data.properties.items; int fn = data.properties.size; Object[] bones = this.bones.items; for (int i = 0, n = this.bones.size; i < n; i++) { var bone = (BoneApplied)bones[i]; + if (bone.bone.applied != bone.bone.constrained) System.out.println(); for (int f = 0; f < fn; f++) { var from = (FromProperty)fromItems[f]; float value = from.value(data, source, localFrom) - from.offset; @@ -122,11 +124,11 @@ public class TransformConstraint implements Updatable { } /** The bone whose world transform will be copied to the constrained bones. */ - public BoneApplied getSource () { + public Bone getSource () { return source; } - public void setSource (BoneApplied source) { + public void setSource (Bone source) { if (source == null) throw new IllegalArgumentException("source cannot be null."); this.source = source; } @@ -139,6 +141,18 @@ public class TransformConstraint implements Updatable { return applied; } + public TransformConstraintPose getConstrainedPose () { + return constrained; + } + + public void setConstrained (boolean constrained) { + applied = constrained ? this.constrained : pose; + } + + public void resetAppliedPose () { + applied.set(pose); + } + /** Returns false when this constraint won't be updated by * {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the * {@link Skeleton#getSkin() active skin} does not contain this item. diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Updatable.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Update.java similarity index 98% rename from spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Updatable.java rename to spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Update.java index 0864002f2..f2e933e3a 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Updatable.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Update.java @@ -30,7 +30,7 @@ package com.esotericsoftware.spine; /** The interface for items updated by {@link Skeleton#updateWorldTransform(Physics)}. */ -public interface Updatable { +public interface Update { /** @param physics Determines how physics and other non-deterministic updates are applied. */ public void update (Physics physics); }