[libgdx] BonePose/Applied classes.

This commit is contained in:
Nathan Sweet 2025-04-12 22:08:54 -04:00
parent 71ef2d5f98
commit cab6f73396
14 changed files with 327 additions and 256 deletions

View File

@ -544,7 +544,7 @@ public class Animation {
}
}
/** Changes a bone's local {@link Bone#getRotation()}. */
/** Changes a bone's local {@link BonePose#getRotation()}. */
static public class RotateTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex;
@ -560,15 +560,15 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) {
if (appliedPose) bone = bone.applied;
bone.rotation = getRelativeValue(time, alpha, blend, bone.rotation, bone.data.rotation);
Bone pose = skeleton.bones.get(boneIndex);
if (pose.active) {
BonePose bone = appliedPose ? pose.applied : pose;
bone.rotation = getRelativeValue(time, alpha, blend, bone.rotation, pose.data.rotation);
}
}
}
/** Changes a bone's local {@link Bone#getX()} and {@link Bone#getY()}. */
/** Changes a bone's local {@link BonePose#getX()} and {@link BonePose#getY()}. */
static public class TranslateTimeline extends CurveTimeline2 implements BoneTimeline {
final int boneIndex;
@ -586,20 +586,20 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return;
if (appliedPose) bone = bone.applied;
Bone pose = skeleton.bones.get(boneIndex);
if (!pose.active) return;
BonePose bone = appliedPose ? pose.applied : pose;
float[] frames = this.frames;
if (time < frames[0]) {
switch (blend) {
case setup:
bone.x = bone.data.x;
bone.y = bone.data.y;
bone.x = pose.data.x;
bone.y = pose.data.y;
return;
case first:
bone.x += (bone.data.x - bone.x) * alpha;
bone.y += (bone.data.y - bone.y) * alpha;
bone.x += (pose.data.x - bone.x) * alpha;
bone.y += (pose.data.y - bone.y) * alpha;
}
return;
}
@ -626,13 +626,13 @@ public class Animation {
switch (blend) {
case setup:
bone.x = bone.data.x + x * alpha;
bone.y = bone.data.y + y * alpha;
bone.x = pose.data.x + x * alpha;
bone.y = pose.data.y + y * alpha;
break;
case first:
case replace:
bone.x += (bone.data.x + x - bone.x) * alpha;
bone.y += (bone.data.y + y - bone.y) * alpha;
bone.x += (pose.data.x + x - bone.x) * alpha;
bone.y += (pose.data.y + y - bone.y) * alpha;
break;
case add:
bone.x += x * alpha;
@ -641,7 +641,7 @@ public class Animation {
}
}
/** Changes a bone's local {@link Bone#getX()}. */
/** Changes a bone's local {@link BonePose#getX()}. */
static public class TranslateXTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex;
@ -657,15 +657,15 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) {
if (appliedPose) bone = bone.applied;
bone.x = getRelativeValue(time, alpha, blend, bone.x, bone.data.x);
Bone pose = skeleton.bones.get(boneIndex);
if (pose.active) {
BonePose bone = appliedPose ? pose.applied : pose;
bone.x = getRelativeValue(time, alpha, blend, bone.x, pose.data.x);
}
}
}
/** Changes a bone's local {@link Bone#getY()}. */
/** Changes a bone's local {@link BonePose#getY()}. */
static public class TranslateYTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex;
@ -681,15 +681,15 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) {
if (appliedPose) bone = bone.applied;
bone.y = getRelativeValue(time, alpha, blend, bone.y, bone.data.y);
Bone pose = skeleton.bones.get(boneIndex);
if (pose.active) {
BonePose bone = appliedPose ? pose.applied : pose;
bone.y = getRelativeValue(time, alpha, blend, bone.y, pose.data.y);
}
}
}
/** Changes a bone's local {@link Bone#getScaleX()} and {@link Bone#getScaleY()}. */
/** Changes a bone's local {@link BonePose#getScaleX()} and {@link BonePose#getScaleY()}. */
static public class ScaleTimeline extends CurveTimeline2 implements BoneTimeline {
final int boneIndex;
@ -707,20 +707,20 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return;
if (appliedPose) bone = bone.applied;
Bone pose = skeleton.bones.get(boneIndex);
if (!pose.active) return;
BonePose bone = appliedPose ? pose.applied : pose;
float[] frames = this.frames;
if (time < frames[0]) {
switch (blend) {
case setup:
bone.scaleX = bone.data.scaleX;
bone.scaleY = bone.data.scaleY;
bone.scaleX = pose.data.scaleX;
bone.scaleY = pose.data.scaleY;
return;
case first:
bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
bone.scaleX += (pose.data.scaleX - bone.scaleX) * alpha;
bone.scaleY += (pose.data.scaleY - bone.scaleY) * alpha;
}
return;
}
@ -744,13 +744,13 @@ public class Animation {
x = getBezierValue(time, i, VALUE1, curveType - BEZIER);
y = getBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER);
}
x *= bone.data.scaleX;
y *= bone.data.scaleY;
x *= pose.data.scaleX;
y *= pose.data.scaleY;
if (alpha == 1) {
if (blend == add) {
bone.scaleX += x - bone.data.scaleX;
bone.scaleY += y - bone.data.scaleY;
bone.scaleX += x - pose.data.scaleX;
bone.scaleY += y - pose.data.scaleY;
} else {
bone.scaleX = x;
bone.scaleY = y;
@ -761,8 +761,8 @@ public class Animation {
if (direction == out) {
switch (blend) {
case setup:
bx = bone.data.scaleX;
by = bone.data.scaleY;
bx = pose.data.scaleX;
by = pose.data.scaleY;
bone.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bx) * alpha;
bone.scaleY = by + (Math.abs(y) * Math.signum(by) - by) * alpha;
break;
@ -774,14 +774,14 @@ public class Animation {
bone.scaleY = by + (Math.abs(y) * Math.signum(by) - by) * alpha;
break;
case add:
bone.scaleX += (x - bone.data.scaleX) * alpha;
bone.scaleY += (y - bone.data.scaleY) * alpha;
bone.scaleX += (x - pose.data.scaleX) * alpha;
bone.scaleY += (y - pose.data.scaleY) * alpha;
}
} else {
switch (blend) {
case setup:
bx = Math.abs(bone.data.scaleX) * Math.signum(x);
by = Math.abs(bone.data.scaleY) * Math.signum(y);
bx = Math.abs(pose.data.scaleX) * Math.signum(x);
by = Math.abs(pose.data.scaleY) * Math.signum(y);
bone.scaleX = bx + (x - bx) * alpha;
bone.scaleY = by + (y - by) * alpha;
break;
@ -793,15 +793,15 @@ public class Animation {
bone.scaleY = by + (y - by) * alpha;
break;
case add:
bone.scaleX += (x - bone.data.scaleX) * alpha;
bone.scaleY += (y - bone.data.scaleY) * alpha;
bone.scaleX += (x - pose.data.scaleX) * alpha;
bone.scaleY += (y - pose.data.scaleY) * alpha;
}
}
}
}
}
/** Changes a bone's local {@link Bone#getScaleX()}. */
/** Changes a bone's local {@link BonePose#getScaleX()}. */
static public class ScaleXTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex;
@ -817,15 +817,15 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) {
if (appliedPose) bone = bone.applied;
bone.scaleX = getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleX);
Bone pose = skeleton.bones.get(boneIndex);
if (pose.active) {
BonePose bone = appliedPose ? pose.applied : pose;
bone.scaleX = getScaleValue(time, alpha, blend, direction, bone.scaleX, pose.data.scaleX);
}
}
}
/** Changes a bone's local {@link Bone#getScaleY()}. */
/** Changes a bone's local {@link BonePose#getScaleY()}. */
static public class ScaleYTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex;
@ -841,15 +841,15 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) {
if (appliedPose) bone = bone.applied;
bone.scaleY = getScaleValue(time, alpha, blend, direction, bone.scaleY, bone.data.scaleY);
Bone pose = skeleton.bones.get(boneIndex);
if (pose.active) {
BonePose bone = appliedPose ? pose.applied : pose;
bone.scaleY = getScaleValue(time, alpha, blend, direction, bone.scaleY, pose.data.scaleY);
}
}
}
/** Changes a bone's local {@link Bone#getShearX()} and {@link Bone#getShearY()}. */
/** Changes a bone's local {@link BonePose#getShearX()} and {@link BonePose#getShearY()}. */
static public class ShearTimeline extends CurveTimeline2 implements BoneTimeline {
final int boneIndex;
@ -867,20 +867,20 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return;
if (appliedPose) bone = bone.applied;
Bone pose = skeleton.bones.get(boneIndex);
if (!pose.active) return;
BonePose bone = appliedPose ? pose.applied : pose;
float[] frames = this.frames;
if (time < frames[0]) {
switch (blend) {
case setup:
bone.shearX = bone.data.shearX;
bone.shearY = bone.data.shearY;
bone.shearX = pose.data.shearX;
bone.shearY = pose.data.shearY;
return;
case first:
bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
bone.shearX += (pose.data.shearX - bone.shearX) * alpha;
bone.shearY += (pose.data.shearY - bone.shearY) * alpha;
}
return;
}
@ -907,13 +907,13 @@ public class Animation {
switch (blend) {
case setup:
bone.shearX = bone.data.shearX + x * alpha;
bone.shearY = bone.data.shearY + y * alpha;
bone.shearX = pose.data.shearX + x * alpha;
bone.shearY = pose.data.shearY + y * alpha;
break;
case first:
case replace:
bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
bone.shearX += (pose.data.shearX + x - bone.shearX) * alpha;
bone.shearY += (pose.data.shearY + y - bone.shearY) * alpha;
break;
case add:
bone.shearX += x * alpha;
@ -922,7 +922,7 @@ public class Animation {
}
}
/** Changes a bone's local {@link Bone#getShearX()}. */
/** Changes a bone's local {@link BonePose#getShearX()}. */
static public class ShearXTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex;
@ -938,15 +938,15 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) {
if (appliedPose) bone = bone.applied;
bone.shearX = getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearX);
Bone pose = skeleton.bones.get(boneIndex);
if (pose.active) {
BonePose bone = appliedPose ? pose.applied : pose;
bone.shearX = getRelativeValue(time, alpha, blend, bone.shearX, pose.data.shearX);
}
}
}
/** Changes a bone's local {@link Bone#getShearY()}. */
/** Changes a bone's local {@link BonePose#getShearY()}. */
static public class ShearYTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex;
@ -962,15 +962,15 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) {
if (appliedPose) bone = bone.applied;
bone.shearY = getRelativeValue(time, alpha, blend, bone.shearY, bone.data.shearY);
Bone pose = skeleton.bones.get(boneIndex);
if (pose.active) {
BonePose bone = appliedPose ? pose.applied : pose;
bone.shearY = getRelativeValue(time, alpha, blend, bone.shearY, pose.data.shearY);
}
}
}
/** Changes a bone's {@link Bone#getInherit()}. */
/** Changes a bone's {@link BonePose#getInherit()}. */
static public class InheritTimeline extends Timeline implements BoneTimeline {
static public final int ENTRIES = 2;
static private final int INHERIT = 1;
@ -1002,18 +1002,18 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return;
if (appliedPose) bone = bone.applied;
Bone pose = skeleton.bones.get(boneIndex);
if (!pose.active) return;
BonePose bone = appliedPose ? pose.applied : pose;
if (direction == out) {
if (blend == setup) bone.inherit = bone.data.inherit;
if (blend == setup) bone.inherit = pose.data.inherit;
return;
}
float[] frames = this.frames;
if (time < frames[0]) {
if (blend == setup || blend == first) bone.inherit = bone.data.inherit;
if (blend == setup || blend == first) bone.inherit = pose.data.inherit;
return;
}
bone.inherit = Inherit.values[(int)frames[search(frames, time, ENTRIES) + INHERIT]];

View File

@ -32,32 +32,20 @@ package com.esotericsoftware.spine;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.BoneData.Inherit;
/** Stores a bone's current pose.
* <p>
* 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 extends BonePose {
final BoneData data;
final Skeleton skeleton;
@Null final Bone parent;
final Array<Bone> children;
BoneApplied applied;
float x, y, rotation, scaleX, scaleY, shearX, shearY;
Inherit inherit;
final BoneApplied applied = new BoneApplied(this);
boolean sorted, active;
Bone (Bone bone) {
this.data = bone.data;
this.skeleton = bone.skeleton;
this.parent = bone.parent;
this.children = bone.children;
}
public Bone (BoneData data, Skeleton skeleton, @Null Bone parent) {
if (data == null) throw new IllegalArgumentException("data cannot be null.");
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
@ -66,27 +54,16 @@ public class Bone {
this.parent = parent;
children = new Array();
applied = new BoneApplied(this);
setToSetupPose();
}
/** Copy constructor. Does not copy the {@link #getChildren()} bones. */
public Bone (Bone bone, Skeleton skeleton, @Null Bone parent) {
if (bone == null) throw new IllegalArgumentException("bone cannot be null.");
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
super(bone);
this.skeleton = skeleton;
this.parent = parent;
children = new Array();
data = bone.data;
x = bone.x;
y = bone.y;
rotation = bone.rotation;
scaleX = bone.scaleX;
scaleY = bone.scaleY;
shearX = bone.shearX;
shearY = bone.shearY;
inherit = bone.inherit;
}
/** Sets this bone's local transform to the setup pose. */
@ -122,98 +99,17 @@ public class Bone {
return children;
}
/** Returns false when this bone won't be updated by
* {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Skeleton.Physics)} because a skin is required and the
* {@link Skeleton#getSkin() active skin} does not contain this item.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see BoneData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return active;
}
/** The local x translation. */
public float getX () {
return x;
}
public void setX (float x) {
this.x = x;
}
/** The local y translation. */
public float getY () {
return y;
}
public void setY (float y) {
this.y = y;
}
public void setPosition (float x, float y) {
this.x = x;
this.y = y;
}
/** The local rotation in degrees, counter clockwise. */
public float getRotation () {
return rotation;
}
public void setRotation (float rotation) {
this.rotation = rotation;
}
/** The local scaleX. */
public float getScaleX () {
return scaleX;
}
public void setScaleX (float scaleX) {
this.scaleX = scaleX;
}
/** The local scaleY. */
public float getScaleY () {
return scaleY;
}
public void setScaleY (float scaleY) {
this.scaleY = scaleY;
}
public void setScale (float scaleX, float scaleY) {
this.scaleX = scaleX;
this.scaleY = scaleY;
}
public void setScale (float scale) {
scaleX = scale;
scaleY = scale;
}
/** The local shearX. */
public float getShearX () {
return shearX;
}
public void setShearX (float shearX) {
this.shearX = shearX;
}
/** The local shearY. */
public float getShearY () {
return shearY;
}
public void setShearY (float shearY) {
this.shearY = shearY;
}
/** Determines how parent world transforms affect this bone. */
public Inherit getInherit () {
return inherit;
}
public void setInherit (Inherit inherit) {
if (inherit == null) throw new IllegalArgumentException("inherit cannot be null.");
this.inherit = inherit;
}
/** Returns the bone for applied pose. */
public BoneApplied getApplied () {
return applied;

View File

@ -11,21 +11,20 @@ import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.BoneData.Inherit;
import com.esotericsoftware.spine.Skeleton.Physics;
public class BoneApplied extends Bone implements Updatable {
public class BoneApplied extends BonePose implements Updatable {
final Bone pose;
@Null final BoneApplied parentApplied;
@Null final BoneApplied parent;
float a, b, worldX;
float c, d, worldY;
BoneApplied (Bone bone) {
super(bone);
pose = bone;
parentApplied = parent == null ? null : parent.applied;
parent = bone.parent == null ? null : bone.parent.applied;
}
/** Computes the world transform using the parent bone and this bone's local applied transform. */
public void updateWorldTransform () {
updateWorldTransform();
update(null);
}
/** Computes the world transform using the parent bone and this bone's local transform.
@ -37,9 +36,10 @@ public class BoneApplied extends Bone 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 = parentApplied;
Skeleton skeleton = pose.skeleton;
BoneApplied parent = this.parent;
if (parent == null) { // Root bone.
Skeleton skeleton = this.skeleton;
float sx = skeleton.scaleX, sy = skeleton.scaleY;
float rx = (rotation + shearX) * degRad;
float ry = (rotation + 90 + shearY) * degRad;
@ -136,16 +136,18 @@ public class BoneApplied extends Bone implements Updatable {
d *= skeleton.scaleY;
}
/** Computes the applied transform values from the world transform.
/** Computes the local transform values from the world transform.
* <p>
* If the world transform is modified (by a constraint, {@link #rotateWorld(float)}, etc) then this method should be called so
* the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another
* the local transform matches the world transform. The local transform may be needed by other code (eg to apply another
* constraint).
* <p>
* Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied 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. */
public void updateAppliedTransform () {
BoneApplied parent = parentApplied;
public void updateLocalTransform () {
Skeleton skeleton = pose.skeleton;
BoneApplied parent = this.parent;
if (parent == null) {
x = worldX - skeleton.x;
y = worldY - skeleton.y;
@ -222,7 +224,7 @@ public class BoneApplied extends Bone implements Updatable {
}
}
/** Part of the world transform matrix for the X axis. If changed, {@link #updateAppliedTransform()} should be called. */
/** Part of the world transform matrix for the X axis. If changed, {@link #updateLocalTransform()} should be called. */
public float getA () {
return a;
}
@ -231,7 +233,7 @@ public class BoneApplied extends Bone implements Updatable {
this.a = a;
}
/** Part of the world transform matrix for the Y axis. If changed, {@link #updateAppliedTransform()} should be called. */
/** Part of the world transform matrix for the Y axis. If changed, {@link #updateLocalTransform()} should be called. */
public float getB () {
return b;
}
@ -240,7 +242,7 @@ public class BoneApplied extends Bone implements Updatable {
this.b = b;
}
/** Part of the world transform matrix for the X axis. If changed, {@link #updateAppliedTransform()} should be called. */
/** Part of the world transform matrix for the X axis. If changed, {@link #updateLocalTransform()} should be called. */
public float getC () {
return c;
}
@ -249,7 +251,7 @@ public class BoneApplied extends Bone implements Updatable {
this.c = c;
}
/** Part of the world transform matrix for the Y axis. If changed, {@link #updateAppliedTransform()} should be called. */
/** Part of the world transform matrix for the Y axis. If changed, {@link #updateLocalTransform()} should be called. */
public float getD () {
return d;
}
@ -258,7 +260,7 @@ public class BoneApplied extends Bone implements Updatable {
this.d = d;
}
/** The world X position. If changed, {@link #updateAppliedTransform()} should be called. */
/** The world X position. If changed, {@link #updateLocalTransform()} should be called. */
public float getWorldX () {
return worldX;
}
@ -267,7 +269,7 @@ public class BoneApplied extends Bone implements Updatable {
this.worldX = worldX;
}
/** The world Y position. If changed, {@link #updateAppliedTransform()} should be called. */
/** The world Y position. If changed, {@link #updateLocalTransform()} should be called. */
public float getWorldY () {
return worldY;
}
@ -333,13 +335,13 @@ public class BoneApplied extends Bone 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 : parentApplied.worldToLocal(world);
return parent == null ? world : parent.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 : parentApplied.localToWorld(world);
return parent == null ? world : parent.localToWorld(world);
}
/** Transforms a world rotation to a local rotation. */
@ -358,8 +360,8 @@ public class BoneApplied extends Bone implements Updatable {
/** Rotates the world transform the specified amount.
* <p>
* After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and
* {@link #update(Physics)} will need to be called on any child bones, recursively. */
* After changes are made to the world transform, {@link #updateLocalTransform()} should be called and {@link #update(Physics)}
* will need to be called on any child bones, recursively. */
public void rotateWorld (float degrees) {
degrees *= degRad;
float sin = sin(degrees), cos = cos(degrees);

View File

@ -34,7 +34,7 @@ import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Stores the setup pose for a {@link Bone}. */
/** Stores the setup pose for a {@link BonePose}. */
public class BoneData {
final int index;
final String name;

View File

@ -0,0 +1,145 @@
/******************************************************************************
* 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;
import com.esotericsoftware.spine.BoneData.Inherit;
/** Stores a bone's current pose.
* <p>
* 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 BonePose {
float x, y, rotation, scaleX, scaleY, shearX, shearY;
Inherit inherit;
BonePose () {
}
/** Copy constructor. */
public BonePose (BonePose bone) {
x = bone.x;
y = bone.y;
rotation = bone.rotation;
scaleX = bone.scaleX;
scaleY = bone.scaleY;
shearX = bone.shearX;
shearY = bone.shearY;
inherit = bone.inherit;
}
/** The local x translation. */
public float getX () {
return x;
}
public void setX (float x) {
this.x = x;
}
/** The local y translation. */
public float getY () {
return y;
}
public void setY (float y) {
this.y = y;
}
public void setPosition (float x, float y) {
this.x = x;
this.y = y;
}
/** The local rotation in degrees, counter clockwise. */
public float getRotation () {
return rotation;
}
public void setRotation (float rotation) {
this.rotation = rotation;
}
/** The local scaleX. */
public float getScaleX () {
return scaleX;
}
public void setScaleX (float scaleX) {
this.scaleX = scaleX;
}
/** The local scaleY. */
public float getScaleY () {
return scaleY;
}
public void setScaleY (float scaleY) {
this.scaleY = scaleY;
}
public void setScale (float scaleX, float scaleY) {
this.scaleX = scaleX;
this.scaleY = scaleY;
}
public void setScale (float scale) {
scaleX = scale;
scaleY = scale;
}
/** The local shearX. */
public float getShearX () {
return shearX;
}
public void setShearX (float shearX) {
this.shearX = shearX;
}
/** The local shearY. */
public float getShearY () {
return shearY;
}
public void setShearY (float shearY) {
this.shearY = shearY;
}
/** Determines how parent world transforms affect this bone. */
public Inherit getInherit () {
return inherit;
}
public void setInherit (Inherit inherit) {
if (inherit == null) throw new IllegalArgumentException("inherit cannot be null.");
this.inherit = inherit;
}
}

View File

@ -107,7 +107,7 @@ public class IkConstraint implements Updatable {
}
/** The bone that is the IK target. */
public Bone getTarget () {
public BoneApplied getTarget () {
return target;
}
@ -167,6 +167,13 @@ public class IkConstraint implements Updatable {
this.stretch = stretch;
}
/** Returns false when this constraint won't be updated by
* {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Skeleton.Physics)} because a skin is required and the
* {@link Skeleton#getSkin() active skin} does not contain this item.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see ConstraintData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return active;
}
@ -184,20 +191,21 @@ 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.parentApplied;
BoneApplied p = bone.parent;
float pa = p.a, pb = p.b, pc = p.c, pd = p.d;
float rotationIK = -bone.shearX - bone.rotation, tx, ty;
switch (bone.inherit) {
case onlyTranslation:
tx = (targetX - bone.worldX) * Math.signum(bone.skeleton.scaleX);
ty = (targetY - bone.worldY) * Math.signum(bone.skeleton.scaleY);
tx = (targetX - bone.worldX) * Math.signum(bone.pose.skeleton.scaleX);
ty = (targetY - bone.worldY) * Math.signum(bone.pose.skeleton.scaleY);
break;
case noRotationOrReflection:
float s = Math.abs(pa * pd - pb * pc) / Math.max(0.0001f, pa * pa + pc * pc);
float sa = pa / bone.skeleton.scaleX;
float sc = pc / bone.skeleton.scaleY;
pb = -sc * s * bone.skeleton.scaleX;
pd = sa * s * bone.skeleton.scaleY;
Skeleton skeleton = bone.pose.skeleton;
float sa = pa / skeleton.scaleX;
float sc = pc / skeleton.scaleY;
pb = -sc * s * skeleton.scaleX;
pd = sa * s * skeleton.scaleY;
rotationIK += atan2Deg(sc, sa);
// Fall through.
default:
@ -225,7 +233,7 @@ public class IkConstraint implements Updatable {
ty = targetY - bone.worldY;
}
}
float b = bone.data.length * bone.scaleX;
float b = bone.pose.data.length * bone.scaleX;
if (b > 0.0001f) {
float dd = tx * tx + ty * ty;
if ((compress && dd < b * b) || (stretch && dd > b * b)) {
@ -274,7 +282,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.parentApplied;
BoneApplied pp = parent.parent;
a = pp.a;
b = pp.b;
c = pp.c;
@ -282,7 +290,7 @@ public class IkConstraint implements Updatable {
float id = a * d - b * c, x = cwx - pp.worldX, y = cwy - pp.worldY;
id = Math.abs(id) <= 0.0001f ? 0 : 1 / id;
float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
float l1 = (float)Math.sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
float l1 = (float)Math.sqrt(dx * dx + dy * dy), l2 = child.pose.data.length * csx, a1, a2;
if (l1 < 0.0001f) {
apply(parent, targetX, targetY, false, stretch, false, alpha);
child.rotation = 0;

View File

@ -119,7 +119,7 @@ public class PathConstraint implements Updatable {
if (scale) {
for (int i = 0, n = spacesCount - 1; i < n; i++) {
var bone = (BoneApplied)bones[i];
float setupLength = bone.data.length;
float setupLength = bone.pose.data.length;
float x = setupLength * bone.a, y = setupLength * bone.c;
lengths[i] = (float)Math.sqrt(x * x + y * y);
}
@ -130,7 +130,7 @@ public class PathConstraint implements Updatable {
float sum = 0;
for (int i = 0, n = spacesCount - 1; i < n;) {
var bone = (BoneApplied)bones[i];
float setupLength = bone.data.length;
float setupLength = bone.pose.data.length;
if (setupLength < epsilon) {
if (scale) lengths[i] = 0;
spaces[++i] = spacing;
@ -152,7 +152,7 @@ public class PathConstraint implements Updatable {
boolean lengthSpacing = data.spacingMode == SpacingMode.length;
for (int i = 0, n = spacesCount - 1; i < n;) {
var bone = (BoneApplied)bones[i];
float setupLength = bone.data.length;
float setupLength = bone.pose.data.length;
if (setupLength < epsilon) {
if (scale) lengths[i] = 0;
spaces[++i] = spacing;
@ -203,7 +203,7 @@ public class PathConstraint implements Updatable {
if (tip) {
cos = cos(r);
sin = sin(r);
float length = bone.data.length;
float length = bone.pose.data.length;
boneX += (length * (cos * a - sin * c) - dx) * mixRotate;
boneY += (length * (sin * a + cos * c) - dy) * mixRotate;
} else
@ -220,7 +220,7 @@ public class PathConstraint implements Updatable {
bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d;
}
bone.updateAppliedTransform();
bone.updateLocalTransform();
}
}
@ -542,6 +542,13 @@ public class PathConstraint implements Updatable {
this.slot = slot;
}
/** Returns false when this constraint won't be updated by
* {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Skeleton.Physics)} because a skin is required and the
* {@link Skeleton#getSkin() active skin} does not contain this item.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see ConstraintData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return active;
}

View File

@ -127,7 +127,7 @@ public class PhysicsConstraint implements Updatable {
boolean x = data.x > 0, y = data.y > 0, rotateOrShearX = data.rotate > 0 || data.shearX > 0, scaleX = data.scaleX > 0;
BoneApplied bone = this.bone;
float l = bone.data.length;
float l = bone.pose.data.length;
switch (physics) {
case none:
@ -282,7 +282,7 @@ public class PhysicsConstraint implements Updatable {
tx = l * bone.a;
ty = l * bone.c;
}
bone.updateAppliedTransform();
bone.updateLocalTransform();
}
/** The bone constrained by this physics constraint. */
@ -351,6 +351,13 @@ public class PhysicsConstraint implements Updatable {
this.mix = mix;
}
/** Returns false when this constraint won't be updated by
* {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Skeleton.Physics)} because a skin is required and the
* {@link Skeleton#getSkin() active skin} does not contain this item.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see ConstraintData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return active;
}

View File

@ -273,7 +273,7 @@ public class Skeleton {
}
private void sortIkConstraint (IkConstraint constraint) {
constraint.active = constraint.target.active
constraint.active = constraint.target.pose.active
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)));
if (!constraint.active) return;
@ -297,7 +297,7 @@ public class Skeleton {
}
private void sortTransformConstraint (TransformConstraint constraint) {
constraint.active = constraint.source.active
constraint.active = constraint.source.pose.active
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)));
if (!constraint.active) return;
@ -319,7 +319,7 @@ public class Skeleton {
updateCache.add(constraint);
for (int i = 0; i < boneCount; i++)
sortReset(((BoneApplied)constrained[i]).children);
sortReset(((BoneApplied)constrained[i]).pose.children);
for (int i = 0; i < boneCount; i++)
((BoneApplied)constrained[i]).pose.sorted = true;
}

View File

@ -70,8 +70,15 @@ public class Slider implements Updatable {
mix = data.mix;
}
/** Returns false when this constraint won't be updated by
* {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Skeleton.Physics)} because a skin is required and the
* {@link Skeleton#getSkin() active skin} does not contain this item.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see ConstraintData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return true;
return active;
}
public Animation getAnimation () {

View File

@ -122,7 +122,7 @@ public class TransformConstraint implements Updatable {
if (localTarget)
bone.update(null);
else
bone.updateAppliedTransform();
bone.updateLocalTransform();
}
}
@ -195,6 +195,13 @@ public class TransformConstraint implements Updatable {
this.mixShearY = mixShearY;
}
/** Returns false when this constraint won't be updated by
* {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Skeleton.Physics)} because a skin is required and the
* {@link Skeleton#getSkin() active skin} does not contain this item.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see ConstraintData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return active;
}

View File

@ -35,13 +35,4 @@ import com.esotericsoftware.spine.Skeleton.Physics;
public interface Updatable {
/** @param physics Determines how physics and other non-deterministic updates are applied. */
public void update (Physics physics);
/** Returns false when this item won't be updated by
* {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Skeleton.Physics)} because a skin is required and the
* {@link Skeleton#getSkin() active skin} does not contain this item.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see BoneData#getSkinRequired()
* @see ConstraintData#getSkinRequired() */
public boolean isActive ();
}

View File

@ -36,7 +36,7 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Bone;
import com.esotericsoftware.spine.BonePose;
import com.esotericsoftware.spine.BoneApplied;
import com.esotericsoftware.spine.Slot;

View File

@ -34,8 +34,9 @@ import static com.esotericsoftware.spine.utils.SpineUtils.*;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Bone;
import com.esotericsoftware.spine.BonePose;
import com.esotericsoftware.spine.BoneApplied;
import com.esotericsoftware.spine.Bone;
import com.esotericsoftware.spine.Skeleton;
import com.esotericsoftware.spine.Slot;