[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]; var slot = (Slot)slots[i];
if (slot.attachmentState == setupState) { if (slot.attachmentState == setupState) {
String attachmentName = slot.data.attachmentName; 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. 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) { 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; if (attachments) slot.attachmentState = unkeyedState + CURRENT;
} }
@ -417,7 +417,7 @@ public class AnimationState {
Bone bone = skeleton.bones.get(timeline.boneIndex); Bone bone = skeleton.bones.get(timeline.boneIndex);
if (!bone.active) return; 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[] frames = timeline.frames;
float r1, r2; float r1, r2;
if (time < frames[0]) { // Time is before first frame. 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 * 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 * 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. */ * 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 BoneData data;
final Skeleton skeleton; final Skeleton skeleton;
@Null final Bone parent; @Null final Bone parent;
final Array<Bone> children = new Array(); final Array<Bone> children = new Array();
final BonePose pose = new BonePose(); final BoneApplied pose = new BoneApplied(this), constrained = new BoneApplied(this);
final BoneApplied applied = new BoneApplied(this); BoneApplied applied = pose;
boolean sorted, active; boolean sorted, active;
public Bone (BoneData data, Skeleton skeleton, @Null Bone parent) { public Bone (BoneData data, Skeleton skeleton, @Null Bone parent) {
@ -74,16 +74,26 @@ public class Bone {
return data; return data;
} }
/** Returns the bone's pose. */
public BonePose getPose () { public BonePose getPose () {
return pose; return pose;
} }
/** Returns the bone's applied pose. */
public BoneApplied getAppliedPose () { public BoneApplied getAppliedPose () {
return applied; 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. */ /** The skeleton this bone belongs to. */
public Skeleton getSkeleton () { public Skeleton getSkeleton () {
return skeleton; 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.Matrix3;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.BoneData.Inherit; 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 /** 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)}. */ * {@link Skeleton#updateWorldTransform(Physics)}. */
public class BoneApplied extends BonePose implements Updatable { public class BoneApplied extends BonePose implements Update {
final Bone bone; final Bone bone;
@Null final BoneApplied parent;
float a, b, worldX; float a, b, worldX;
float c, d, worldY; float c, d, worldY;
BoneApplied (Bone bone) { BoneApplied (Bone bone) {
this.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. */ /** 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 * See <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide. */ * Runtimes Guide. */
public void update (Physics physics) { public void update (Physics physics) {
BoneApplied parent = this.parent; if (bone.parent == null) { // Root bone.
if (parent == null) { // Root bone.
Skeleton skeleton = bone.skeleton; Skeleton skeleton = bone.skeleton;
float sx = skeleton.scaleX, sy = skeleton.scaleY; float sx = skeleton.scaleX, sy = skeleton.scaleY;
float rx = (rotation + shearX) * degRad; float rx = (rotation + shearX) * degRad;
@ -52,6 +48,7 @@ public class BoneApplied extends BonePose implements Updatable {
return; return;
} }
BoneApplied parent = bone.parent.applied;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
worldX = pa * x + pb * y + parent.worldX; worldX = pa * x + pb * y + parent.worldX;
worldY = pc * x + pd * y + parent.worldY; 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 * 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. */ * calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. */
public void updateLocalTransform () { public void updateLocalTransform () {
BoneApplied parent = this.parent; BoneApplied parent = bone.parent.applied;
if (parent == null) { if (parent == null) {
x = worldX - bone.skeleton.x; x = worldX - bone.skeleton.x;
y = worldY - bone.skeleton.y; 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. */ /** Transforms a point from world coordinates to the parent bone's local coordinates. */
public Vector2 worldToParent (Vector2 world) { public Vector2 worldToParent (Vector2 world) {
if (world == null) throw new IllegalArgumentException("world cannot be null."); 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. */ /** Transforms a point from the parent bone's coordinates to world coordinates. */
public Vector2 parentToWorld (Vector2 world) { public Vector2 parentToWorld (Vector2 world) {
if (world == null) throw new IllegalArgumentException("world cannot be null."); 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. */ /** 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. * the last bone is as close to the target bone as possible.
* <p> * <p>
* See <a href="https://esotericsoftware.com/spine-ik-constraints">IK constraints</a> in the Spine User Guide. */ * 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 IkConstraintData data;
final Array<BoneApplied> bones; final Array<BoneApplied> bones;
BoneApplied target; Bone target;
final IkConstraintPose pose = new IkConstraintPose(), applied = new IkConstraintPose(); final IkConstraintPose pose = new IkConstraintPose(), constrained = new IkConstraintPose();
IkConstraintPose applied = pose;
boolean active; boolean active;
public IkConstraint (IkConstraintData data, Skeleton skeleton) { public IkConstraint (IkConstraintData data, Skeleton skeleton) {
@ -53,9 +54,9 @@ public class IkConstraint implements Updatable {
bones = new Array(data.bones.size); bones = new Array(data.bones.size);
for (BoneData boneData : data.bones) 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(); setupPose();
} }
@ -74,7 +75,7 @@ public class IkConstraint implements Updatable {
public void update (Physics physics) { public void update (Physics physics) {
IkConstraintPose a = applied; IkConstraintPose a = applied;
if (a.mix == 0) return; if (a.mix == 0) return;
BoneApplied target = this.target; BoneApplied target = this.target.applied;
Object[] bones = this.bones.items; Object[] bones = this.bones.items;
switch (this.bones.size) { switch (this.bones.size) {
case 1 -> apply((BoneApplied)bones[0], target.worldX, target.worldY, a.compress, a.stretch, data.uniform, a.mix); 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. */ /** The bone that is the IK target. */
public BoneApplied getTarget () { public Bone getTarget () {
return target; return target;
} }
public void setTarget (BoneApplied target) { public void setTarget (Bone target) {
if (target == null) throw new IllegalArgumentException("target cannot be null."); if (target == null) throw new IllegalArgumentException("target cannot be null.");
this.target = target; this.target = target;
} }
@ -107,6 +108,18 @@ public class IkConstraint implements Updatable {
return applied; 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 /** 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#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the
* {@link Skeleton#getSkin() active skin} does not contain this item. * {@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, static public void apply (BoneApplied bone, float targetX, float targetY, boolean compress, boolean stretch, boolean uniform,
float alpha) { float alpha) {
if (bone == null) throw new IllegalArgumentException("bone cannot be null."); 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 pa = p.a, pb = p.b, pc = p.c, pd = p.d;
float rotationIK = -bone.shearX - bone.rotation, tx, ty; float rotationIK = -bone.shearX - bone.rotation, tx, ty;
switch (bone.inherit) { switch (bone.inherit) {
@ -222,7 +235,7 @@ public class IkConstraint implements Updatable {
cwx = a * child.x + b * child.y + parent.worldX; cwx = a * child.x + b * child.y + parent.worldX;
cwy = c * child.x + d * child.y + parent.worldY; cwy = c * child.x + d * child.y + parent.worldY;
} }
BoneApplied pp = parent.parent; BoneApplied pp = parent.bone.parent.applied;
a = pp.a; a = pp.a;
b = pp.b; b = pp.b;
c = pp.c; c = pp.c;

View File

@ -45,14 +45,15 @@ import com.esotericsoftware.spine.attachments.PathAttachment;
* constrained bones so they follow a {@link PathAttachment}. * constrained bones so they follow a {@link PathAttachment}.
* <p> * <p>
* See <a href="https://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide. */ * 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 int NONE = -1, BEFORE = -2, AFTER = -3;
static final float epsilon = 0.00001f; static final float epsilon = 0.00001f;
final PathConstraintData data; final PathConstraintData data;
final Array<BoneApplied> bones; final Array<BoneApplied> bones;
Slot slot; Slot slot;
final PathConstraintPose pose = new PathConstraintPose(), applied = new PathConstraintPose(); final PathConstraintPose pose = new PathConstraintPose(), constrained = new PathConstraintPose();
PathConstraintPose applied = pose;
boolean active; boolean active;
private final FloatArray spaces = new FloatArray(), positions = new FloatArray(); private final FloatArray spaces = new FloatArray(), positions = new FloatArray();
@ -72,7 +73,7 @@ public class PathConstraint implements Updatable {
bones = new Array(data.bones.size); bones = new Array(data.bones.size);
for (BoneData boneData : data.bones) 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); slot = skeleton.slots.get(data.slot.index);
@ -495,6 +496,18 @@ public class PathConstraint implements Updatable {
return applied; 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 /** 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#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the
* {@link Skeleton#getSkin() active skin} does not contain this item. * {@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. /** Stores the current pose for a physics constraint. A physics constraint applies physics to bones.
* <p> * <p>
* See <a href="https://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */ * 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 PhysicsConstraintData data;
final Skeleton skeleton; final Skeleton skeleton;
BoneApplied bone; BoneApplied bone;
final PhysicsConstraintPose pose = new PhysicsConstraintPose(), applied = new PhysicsConstraintPose(); final PhysicsConstraintPose pose = new PhysicsConstraintPose(), constrained = new PhysicsConstraintPose();
PhysicsConstraintPose applied = pose;
boolean active; boolean active;
boolean reset = true; boolean reset = true;
@ -49,19 +50,13 @@ public class PhysicsConstraint implements Updatable {
float scaleOffset, scaleVelocity; float scaleOffset, scaleVelocity;
float remaining, lastTime; 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) { public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton) {
if (data == null) throw new IllegalArgumentException("data cannot be null."); if (data == null) throw new IllegalArgumentException("data cannot be null.");
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
this.data = data; this.data = data;
this.skeleton = skeleton; this.skeleton = skeleton;
bone = skeleton.bones.get(data.bone.index).applied; bone = skeleton.bones.get(data.bone.index).constrained;
setupPose(); setupPose();
} }
@ -300,6 +295,18 @@ public class PhysicsConstraint implements Updatable {
return applied; 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 /** 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#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the
* {@link Skeleton#getSkin() active skin} does not contain this item. * {@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.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Animation.BoneTimeline; import com.esotericsoftware.spine.Animation.BoneTimeline;
import com.esotericsoftware.spine.Animation.Timeline;
import com.esotericsoftware.spine.Skin.SkinEntry; import com.esotericsoftware.spine.Skin.SkinEntry;
import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.ClippingAttachment; import com.esotericsoftware.spine.attachments.ClippingAttachment;
@ -52,7 +53,7 @@ import com.esotericsoftware.spine.utils.SkeletonClipping;
* Runtimes Guide. */ * Runtimes Guide. */
public class Skeleton { public class Skeleton {
static private final short[] quadTriangles = {0, 1, 2, 2, 3, 0}; static private final short[] quadTriangles = {0, 1, 2, 2, 3, 0};
private static final Object IkConstraint = null;
final SkeletonData data; final SkeletonData data;
final Array<Bone> bones; final Array<Bone> bones;
final Array<Slot> slots; final Array<Slot> slots;
@ -62,7 +63,8 @@ public class Skeleton {
final Array<TransformConstraint> transformConstraints; final Array<TransformConstraint> transformConstraints;
final Array<PathConstraint> pathConstraints; final Array<PathConstraint> pathConstraints;
final Array<PhysicsConstraint> physicsConstraints; final Array<PhysicsConstraint> physicsConstraints;
final Array<Updatable> updateCache = new Array(); final Array updateCache = new Array();
final Array<Constrained> resetCache = new Array();
@Null Skin skin; @Null Skin skin;
final Color color; final Color color;
float x, y, scaleX = 1, scaleY = 1, time; 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, /** 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. */ * constraints, or weighted path attachments are added or removed. */
public void updateCache () { public void updateCache () {
Array<Updatable> updateCache = this.updateCache;
updateCache.clear(); updateCache.clear();
resetCache.clear();
int boneCount = bones.size; int boneCount = bones.size;
Object[] bones = this.bones.items; Object[] bones = this.bones.items;
@ -189,6 +191,7 @@ public class Skeleton {
var bone = (Bone)bones[i]; var bone = (Bone)bones[i];
bone.sorted = bone.data.skinRequired; bone.sorted = bone.data.skinRequired;
bone.active = !bone.sorted; bone.active = !bone.sorted;
bone.setConstrained(false);
} }
if (skin != null) { if (skin != null) {
Object[] skinBones = skin.bones.items; Object[] skinBones = skin.bones.items;
@ -207,6 +210,17 @@ public class Skeleton {
Object[] sliders = this.sliders.items, ikConstraints = this.ikConstraints.items, Object[] sliders = this.sliders.items, ikConstraints = this.ikConstraints.items,
transformConstraints = this.transformConstraints.items, pathConstraints = this.pathConstraints.items, transformConstraints = this.transformConstraints.items, pathConstraints = this.pathConstraints.items,
physicsConstraints = this.physicsConstraints.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; int constraintCount = ikCount + transformCount + pathCount + physicsCount;
outer: outer:
for (int i = 0; i < constraintCount; i++) { for (int i = 0; i < constraintCount; i++) {
@ -249,47 +263,29 @@ public class Skeleton {
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
sortBone((Bone)bones[i]); sortBone((Bone)bones[i]);
}
private void sortSlider (Slider constraint) { Object[] updateCache = this.updateCache.items;
constraint.active = !constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)); for (int i = 0, n = this.updateCache.size; i < n; i++)
if (!constraint.active) return; if (updateCache[i] instanceof Bone bone) updateCache[i] = bone.applied;
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()]);
} }
private void sortIkConstraint (IkConstraint constraint) { 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))); && (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)));
if (!constraint.active) return; if (!constraint.active) return;
sortBone(constraint.target.bone); sortBone(constraint.target);
Array<BoneApplied> constrained = constraint.bones; Array<BoneApplied> constrained = constraint.bones;
Bone parent = constrained.first().bone; Bone parent = constrained.first().bone;
sortBone(parent); sortBone(parent);
resetCache(parent);
if (constrained.size == 1) { if (constrained.size == 1) {
updateCache.add(constraint); updateCache.add(constraint);
sortReset(parent.children); sortReset(parent.children);
} else { } else {
Bone child = constrained.peek().bone; Bone child = constrained.peek().bone;
resetCache(child);
sortBone(child); sortBone(child);
updateCache.add(constraint); updateCache.add(constraint);
@ -300,23 +296,27 @@ public class Skeleton {
} }
private void sortTransformConstraint (TransformConstraint constraint) { 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))); && (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)));
if (!constraint.active) return; if (!constraint.active) return;
sortBone(constraint.source.bone); sortBone(constraint.source);
Object[] constrained = constraint.bones.items; Object[] constrained = constraint.bones.items;
int boneCount = constraint.bones.size; int boneCount = constraint.bones.size;
if (constraint.data.localSource) { if (constraint.data.localSource) {
for (int i = 0; i < boneCount; i++) { for (int i = 0; i < boneCount; i++) {
Bone child = ((BoneApplied)constrained[i]).bone; Bone child = ((BoneApplied)constrained[i]).bone;
resetCache(child);
sortBone(child.parent); sortBone(child.parent);
sortBone(child); sortBone(child);
} }
} else { } else {
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++) {
sortBone(((BoneApplied)constrained[i]).bone); Bone bone = ((BoneApplied)constrained[i]).bone;
resetCache(bone);
sortBone(bone);
}
} }
updateCache.add(constraint); updateCache.add(constraint);
@ -343,8 +343,11 @@ public class Skeleton {
Object[] constrained = constraint.bones.items; Object[] constrained = constraint.bones.items;
int boneCount = constraint.bones.size; int boneCount = constraint.bones.size;
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++) {
sortBone(((BoneApplied)constrained[i]).bone); Bone bone = ((BoneApplied)constrained[i]).bone;
resetCache(bone);
sortBone(bone);
}
updateCache.add(constraint); updateCache.add(constraint);
@ -386,18 +389,53 @@ public class Skeleton {
sortBone(bone); sortBone(bone);
resetCache(bone);
updateCache.add(constraint); updateCache.add(constraint);
sortReset(bone.children); sortReset(bone.children);
bone.sorted = true; 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) { private void sortBone (Bone bone) {
if (bone.sorted) return; if (bone.sorted) return;
Bone parent = bone.parent; Bone parent = bone.parent;
if (parent != null) sortBone(parent); if (parent != null) sortBone(parent);
bone.sorted = true; bone.sorted = true;
updateCache.add(bone.applied); updateCache.add(bone);
} }
private void sortReset (Array<Bone> bones) { 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 * See <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide. */ * Runtimes Guide. */
public void updateWorldTransform (Physics physics) { public void updateWorldTransform (Physics physics) {
Object[] objects = this.bones.items; Object[] resetCache = this.resetCache.items;
for (int i = 0, n = this.bones.size; i < n; i++) { for (int i = 0, n = this.resetCache.size; i < n; i++)
var bone = (Bone)objects[i]; ((Constrained)resetCache[i]).resetAppliedPose();
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[] updateCache = this.updateCache.items; Object[] updateCache = this.updateCache.items;
for (int i = 0, n = this.updateCache.size; i < n; i++) 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 /** 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) { public void updateWorldTransform (Physics physics, BoneApplied parent) {
if (parent == null) throw new IllegalArgumentException("parent cannot be null."); if (parent == null) throw new IllegalArgumentException("parent cannot be null.");
Object[] objects = this.bones.items; Object[] resetCache = this.resetCache.items;
for (int i = 0, n = this.bones.size; i < n; i++) { for (int i = 0, n = this.resetCache.size; i < n; i++)
var bone = (Bone)objects[i]; ((Constrained)resetCache[i]).resetAppliedPose();
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);
}
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
BoneApplied rootBone = getRootBone().applied; BoneApplied rootBone = getRootBone().applied;
@ -520,7 +494,7 @@ public class Skeleton {
// Update everything except root bone. // Update everything except root bone.
Object[] updateCache = this.updateCache.items; Object[] updateCache = this.updateCache.items;
for (int i = 0, n = this.updateCache.size; i < n; i++) { 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); 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()}. */ /** 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; return updateCache;
} }

View File

@ -343,7 +343,7 @@ public class SkeletonBinary extends SkeletonLoader {
if ((flags & 16) != 0) data.offsetScaleY = input.readFloat(); if ((flags & 16) != 0) data.offsetScaleY = input.readFloat();
if ((flags & 32) != 0) data.offsetShearY = input.readFloat(); if ((flags & 32) != 0) data.offsetShearY = input.readFloat();
flags = input.read(); flags = input.read();
TransformConstraintPose setup = data.getSetupPose(); TransformConstraintPose setup = data.setup;
if ((flags & 1) != 0) setup.mixRotate = input.readFloat(); if ((flags & 1) != 0) setup.mixRotate = input.readFloat();
if ((flags & 2) != 0) setup.mixX = input.readFloat(); if ((flags & 2) != 0) setup.mixX = input.readFloat();
if ((flags & 4) != 0) setup.mixY = 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(); if ((flags & 32) != 0) data.shearX = input.readFloat();
data.limit = ((flags & 64) != 0 ? input.readFloat() : 5000) * scale; data.limit = ((flags & 64) != 0 ? input.readFloat() : 5000) * scale;
data.step = 1f / input.readUnsignedByte(); data.step = 1f / input.readUnsignedByte();
PhysicsConstraintPose setup = data.getSetupPose(); PhysicsConstraintPose setup = data.setup;
setup.inertia = input.readFloat(); setup.inertia = input.readFloat();
setup.strength = input.readFloat(); setup.strength = input.readFloat();
setup.damping = 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); var data = new SlotData(skeletonData.slots.size, slotName, boneData);
String color = slotMap.getString("color", null); 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); 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.attachmentName = slotMap.getString("attachment", null);
data.blendMode = BlendMode.valueOf(slotMap.getString("blend", BlendMode.normal.name())); 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.shearX = constraintMap.getFloat("shearX", 0);
data.limit = constraintMap.getFloat("limit", 5000) * scale; data.limit = constraintMap.getFloat("limit", 5000) * scale;
data.step = 1f / constraintMap.getInt("fps", 60); data.step = 1f / constraintMap.getInt("fps", 60);
PhysicsConstraintPose setup = data.getSetupPose(); PhysicsConstraintPose setup = data.setup;
setup.inertia = constraintMap.getFloat("inertia", 1); setup.inertia = constraintMap.getFloat("inertia", 1);
setup.strength = constraintMap.getFloat("strength", 100); setup.strength = constraintMap.getFloat("strength", 100);
setup.damping = constraintMap.getFloat("damping", 1); 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++) { for (int i = 0, n = skeleton.drawOrder.size; i < n; i++) {
var slot = (Slot)drawOrder[i]; var slot = (Slot)drawOrder[i];
if (!slot.bone.active) continue; if (!slot.bone.active) continue;
SlotPose pose = slot.getAppliedPose(); SlotPose pose = slot.applied;
Attachment attachment = pose.attachment; Attachment attachment = pose.attachment;
if (attachment instanceof RegionAttachment region) { if (attachment instanceof RegionAttachment region) {
region.computeWorldVertices(slot, vertices, 0, 5); region.computeWorldVertices(slot, vertices, 0, 5);
@ -149,7 +149,7 @@ public class SkeletonRenderer {
clipper.clipEnd(slot); clipper.clipEnd(slot);
continue; continue;
} }
SlotPose pose = slot.getAppliedPose(); SlotPose pose = slot.applied;
Texture texture = null; Texture texture = null;
Attachment attachment = pose.attachment; Attachment attachment = pose.attachment;
if (attachment instanceof RegionAttachment region) { if (attachment instanceof RegionAttachment region) {
@ -244,7 +244,7 @@ public class SkeletonRenderer {
clipper.clipEnd(slot); clipper.clipEnd(slot);
continue; continue;
} }
SlotPose pose = slot.getAppliedPose(); SlotPose pose = slot.applied;
Texture texture = null; Texture texture = null;
Attachment attachment = pose.attachment; Attachment attachment = pose.attachment;
if (attachment instanceof RegionAttachment region) { if (attachment instanceof RegionAttachment region) {

View File

@ -157,7 +157,7 @@ public class Skin {
Object[] slots = skeleton.slots.items; Object[] slots = skeleton.slots.items;
for (SkinEntry entry : oldSkin.attachments.orderedItems()) { for (SkinEntry entry : oldSkin.attachments.orderedItems()) {
int slotIndex = entry.slotIndex; int slotIndex = entry.slotIndex;
SlotPose slot = ((Slot)slots[slotIndex]).getPose(); SlotPose slot = ((Slot)slots[slotIndex]).pose;
if (slot.attachment == entry.attachment) { if (slot.attachment == entry.attachment) {
Attachment attachment = getAttachment(slotIndex, entry.name); Attachment attachment = getAttachment(slotIndex, entry.name);
if (attachment != null) slot.setAttachment(attachment); 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}. /** Stores the setup pose for a {@link PhysicsConstraint}.
* <p> * <p>
* See <a href="https://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */ * 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 SliderData data;
final Skeleton skeleton; final Skeleton skeleton;
final SliderPose pose = new SliderPose(), applied = new SliderPose(); final SliderPose pose = new SliderPose(), constrained = new SliderPose();
SliderPose applied = pose;
boolean active; boolean active;
@ -54,7 +55,7 @@ public class Slider implements Updatable {
/** Copy constructor. */ /** Copy constructor. */
public Slider (Slider slider, Skeleton skeleton) { public Slider (Slider slider, Skeleton skeleton) {
this(slider.data, skeleton); this(slider.data, skeleton);
setupPose(); pose.set(slider.pose);
} }
public void update (Physics physics) { public void update (Physics physics) {
@ -74,6 +75,18 @@ public class Slider implements Updatable {
return applied; 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 /** 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#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the
* {@link Skeleton#getSkin() active skin} does not contain this item. * {@link Skeleton#getSkin() active skin} does not contain this item.

View File

@ -38,7 +38,6 @@ public class SliderPose {
mix = pose.mix; mix = pose.mix;
} }
public float getTime () { public float getTime () {
return time; 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 /** 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 * state for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared
* across multiple skeletons. */ * across multiple skeletons. */
public class Slot { public class Slot implements Constrained {
final SlotData data; final SlotData data;
final Bone bone; final Bone bone;
final SlotPose pose = new SlotPose(), applied = new SlotPose(); final SlotPose pose = new SlotPose(), constrained = new SlotPose();
SlotPose applied = pose;
int attachmentState; int attachmentState;
public Slot (SlotData data, Skeleton skeleton) { public Slot (SlotData data, Skeleton skeleton) {
@ -76,16 +77,26 @@ public class Slot {
return data; return data;
} }
/** Returns the slot's pose. */
public SlotPose getPose () { public SlotPose getPose () {
return pose; return pose;
} }
/** Returns the slot's applied pose. */
public SlotPose getAppliedPose () { public SlotPose getAppliedPose () {
return applied; 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. */ /** The bone this slot belongs to. */
public Bone getBone () { public Bone getBone () {
return bone; return bone;

View File

@ -40,14 +40,15 @@ import com.esotericsoftware.spine.TransformConstraintData.ToProperty;
* bones to match that of the source bone. * bones to match that of the source bone.
* <p> * <p>
* See <a href="https://esotericsoftware.com/spine-transform-constraints">Transform constraints</a> in the Spine User Guide. */ * 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 TransformConstraintData data;
final Array<BoneApplied> bones; final Array<BoneApplied> bones;
BoneApplied source; Bone source;
final TransformConstraintPose pose = new TransformConstraintPose(), applied = new TransformConstraintPose(); final TransformConstraintPose pose = new TransformConstraintPose(), constrained = new TransformConstraintPose();
TransformConstraintPose applied = pose;
boolean active; boolean active;
public TransformConstraint (TransformConstraintData data, Array<BoneApplied> bones, BoneApplied source) { public TransformConstraint (TransformConstraintData data, Array<BoneApplied> bones, Bone source) {
this.data = data; this.data = data;
this.bones = bones; this.bones = bones;
this.source = source; this.source = source;
@ -60,9 +61,9 @@ public class TransformConstraint implements Updatable {
bones = new Array(data.bones.size); bones = new Array(data.bones.size);
for (BoneData boneData : data.bones) 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(); setupPose();
} }
@ -85,12 +86,13 @@ public class TransformConstraint implements Updatable {
TransformConstraintData data = this.data; TransformConstraintData data = this.data;
boolean localFrom = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp; 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; Object[] fromItems = data.properties.items;
int fn = data.properties.size; int fn = data.properties.size;
Object[] bones = this.bones.items; Object[] bones = this.bones.items;
for (int i = 0, n = this.bones.size; i < n; i++) { for (int i = 0, n = this.bones.size; i < n; i++) {
var bone = (BoneApplied)bones[i]; var bone = (BoneApplied)bones[i];
if (bone.bone.applied != bone.bone.constrained) System.out.println();
for (int f = 0; f < fn; f++) { for (int f = 0; f < fn; f++) {
var from = (FromProperty)fromItems[f]; var from = (FromProperty)fromItems[f];
float value = from.value(data, source, localFrom) - from.offset; 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. */ /** The bone whose world transform will be copied to the constrained bones. */
public BoneApplied getSource () { public Bone getSource () {
return source; return source;
} }
public void setSource (BoneApplied source) { public void setSource (Bone source) {
if (source == null) throw new IllegalArgumentException("source cannot be null."); if (source == null) throw new IllegalArgumentException("source cannot be null.");
this.source = source; this.source = source;
} }
@ -139,6 +141,18 @@ public class TransformConstraint implements Updatable {
return applied; 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 /** 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#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the
* {@link Skeleton#getSkin() active skin} does not contain this item. * {@link Skeleton#getSkin() active skin} does not contain this item.

View File

@ -30,7 +30,7 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
/** The interface for items updated by {@link Skeleton#updateWorldTransform(Physics)}. */ /** 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. */ /** @param physics Determines how physics and other non-deterministic updates are applied. */
public void update (Physics physics); public void update (Physics physics);
} }