[libgdx] Optimized setting the applied pose.

This commit is contained in:
Nathan Sweet 2025-04-15 23:45:34 -04:00
parent c8069fa852
commit a8c081dbb1
17 changed files with 261 additions and 174 deletions

View File

@ -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.

View File

@ -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<Bone> 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;

View File

@ -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 <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> 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. */

View File

@ -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 ();
}

View File

@ -39,11 +39,12 @@ import com.esotericsoftware.spine.BoneData.Inherit;
* the last bone is as close to the target bone as possible.
* <p>
* See <a href="https://esotericsoftware.com/spine-ik-constraints">IK constraints</a> in the Spine User Guide. */
public class IkConstraint implements Updatable {
public class IkConstraint implements Constrained, Update {
final IkConstraintData data;
final Array<BoneApplied> 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;

View File

@ -45,14 +45,15 @@ import com.esotericsoftware.spine.attachments.PathAttachment;
* constrained bones so they follow a {@link PathAttachment}.
* <p>
* See <a href="https://esotericsoftware.com/spine-path-constraints">Path constraints</a> 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<BoneApplied> 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.

View File

@ -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.
* <p>
* See <a href="https://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> 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.

View File

@ -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<Bone> bones;
final Array<Slot> slots;
@ -62,7 +63,8 @@ public class Skeleton {
final Array<TransformConstraint> transformConstraints;
final Array<PathConstraint> pathConstraints;
final Array<PhysicsConstraint> physicsConstraints;
final Array<Updatable> updateCache = new Array();
final Array updateCache = new Array();
final Array<Constrained> 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<Updatable> 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<BoneApplied> 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<Bone> bones) {
@ -415,45 +453,13 @@ public class Skeleton {
* See <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> 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<Updatable> getUpdateCache () {
public Array<Update> getUpdateCache () {
return updateCache;
}

View File

@ -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();

View File

@ -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);

View File

@ -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) {

View File

@ -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);

View File

@ -35,10 +35,11 @@ import com.esotericsoftware.spine.Animation.MixDirection;
/** 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 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.

View File

@ -38,7 +38,6 @@ public class SliderPose {
mix = pose.mix;
}
public float getTime () {
return time;
}

View File

@ -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;

View File

@ -40,14 +40,15 @@ import com.esotericsoftware.spine.TransformConstraintData.ToProperty;
* bones to match that of the source bone.
* <p>
* See <a href="https://esotericsoftware.com/spine-transform-constraints">Transform constraints</a> in the Spine User Guide. */
public class TransformConstraint implements Updatable {
public class TransformConstraint implements Constrained, Update {
final TransformConstraintData data;
final Array<BoneApplied> 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<BoneApplied> bones, BoneApplied source) {
public TransformConstraint (TransformConstraintData data, Array<BoneApplied> 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.

View File

@ -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);
}