[libgdx] Added Posed and other base classes. Renamed BoneLocal/Pose. Removed skeleton fields, prefer passing.

This commit is contained in:
Nathan Sweet 2025-04-16 23:09:38 -04:00
parent a8c081dbb1
commit fff1606b6d
47 changed files with 793 additions and 1169 deletions

View File

@ -75,7 +75,7 @@ public class BonePlotting {
SkeletonData skeletonData = json.readSkeletonData(new FileHandle("assets/spineboy/spineboy-ess.json")); SkeletonData skeletonData = json.readSkeletonData(new FileHandle("assets/spineboy/spineboy-ess.json"));
Skeleton skeleton = new Skeleton(skeletonData); Skeleton skeleton = new Skeleton(skeletonData);
BoneApplied bone = skeleton.findBone("gun-tip").getAppliedPose(); BonePose bone = skeleton.findBone("gun-tip").getAppliedPose();
// Pose the skeleton at regular intervals throughout each animation. // Pose the skeleton at regular intervals throughout each animation.
float fps = 1 / 15f; float fps = 1 / 15f;

View File

@ -161,7 +161,7 @@ public class Box2DExample extends ApplicationAdapter {
if (!(slot.getAppliedPose().getAttachment() instanceof Box2dAttachment)) continue; if (!(slot.getAppliedPose().getAttachment() instanceof Box2dAttachment)) continue;
Box2dAttachment attachment = (Box2dAttachment)slot.getAppliedPose().getAttachment(); Box2dAttachment attachment = (Box2dAttachment)slot.getAppliedPose().getAttachment();
if (attachment.body == null) continue; if (attachment.body == null) continue;
BoneApplied bone = slot.getBone().getAppliedPose(); BonePose bone = slot.getBone().getAppliedPose();
float x = bone.getWorldX(); float x = bone.getWorldX();
float y = bone.getWorldY(); float y = bone.getWorldY();
float rotation = bone.getWorldRotationX(); float rotation = bone.getWorldRotationX();

View File

@ -47,7 +47,7 @@ public class SkeletonAttachmentTest extends ApplicationAdapter {
Skeleton spineboy, goblin; Skeleton spineboy, goblin;
AnimationState spineboyState, goblinState; AnimationState spineboyState, goblinState;
BoneApplied attachmentBone; BonePose attachmentBone;
public void create () { public void create () {
camera = new OrthographicCamera(); camera = new OrthographicCamera();

View File

@ -544,7 +544,7 @@ public class Animation {
} }
} }
/** Changes a bone's local {@link BonePose#getRotation()}. */ /** Changes a bone's local {@link BoneLocal#getRotation()}. */
static public class RotateTimeline extends CurveTimeline1 implements BoneTimeline { static public class RotateTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex; final int boneIndex;
@ -562,13 +562,13 @@ public class Animation {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) { if (bone.active) {
BonePose pose = appliedPose ? bone.applied : bone.pose; BoneLocal pose = appliedPose ? bone.applied : bone.pose;
pose.rotation = getRelativeValue(time, alpha, blend, pose.rotation, bone.data.setup.rotation); pose.rotation = getRelativeValue(time, alpha, blend, pose.rotation, bone.data.setup.rotation);
} }
} }
} }
/** Changes a bone's local {@link BonePose#getX()} and {@link BonePose#getY()}. */ /** Changes a bone's local {@link BoneLocal#getX()} and {@link BoneLocal#getY()}. */
static public class TranslateTimeline extends CurveTimeline2 implements BoneTimeline { static public class TranslateTimeline extends CurveTimeline2 implements BoneTimeline {
final int boneIndex; final int boneIndex;
@ -588,7 +588,7 @@ public class Animation {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return; if (!bone.active) return;
BonePose pose = appliedPose ? bone.applied : bone.pose, setup = bone.data.setup; BoneLocal pose = appliedPose ? bone.applied : bone.pose, setup = bone.data.setup;
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
@ -641,7 +641,7 @@ public class Animation {
} }
} }
/** Changes a bone's local {@link BonePose#getX()}. */ /** Changes a bone's local {@link BoneLocal#getX()}. */
static public class TranslateXTimeline extends CurveTimeline1 implements BoneTimeline { static public class TranslateXTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex; final int boneIndex;
@ -659,13 +659,13 @@ public class Animation {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) { if (bone.active) {
BonePose pose = appliedPose ? bone.applied : bone.pose; BoneLocal pose = appliedPose ? bone.applied : bone.pose;
pose.x = getRelativeValue(time, alpha, blend, pose.x, bone.data.setup.x); pose.x = getRelativeValue(time, alpha, blend, pose.x, bone.data.setup.x);
} }
} }
} }
/** Changes a bone's local {@link BonePose#getY()}. */ /** Changes a bone's local {@link BoneLocal#getY()}. */
static public class TranslateYTimeline extends CurveTimeline1 implements BoneTimeline { static public class TranslateYTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex; final int boneIndex;
@ -683,13 +683,13 @@ public class Animation {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) { if (bone.active) {
BonePose pose = appliedPose ? bone.applied : bone.pose; BoneLocal pose = appliedPose ? bone.applied : bone.pose;
pose.y = getRelativeValue(time, alpha, blend, pose.y, bone.data.setup.y); pose.y = getRelativeValue(time, alpha, blend, pose.y, bone.data.setup.y);
} }
} }
} }
/** Changes a bone's local {@link BonePose#getScaleX()} and {@link BonePose#getScaleY()}. */ /** Changes a bone's local {@link BoneLocal#getScaleX()} and {@link BoneLocal#getScaleY()}. */
static public class ScaleTimeline extends CurveTimeline2 implements BoneTimeline { static public class ScaleTimeline extends CurveTimeline2 implements BoneTimeline {
final int boneIndex; final int boneIndex;
@ -709,7 +709,7 @@ public class Animation {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return; if (!bone.active) return;
BonePose pose = appliedPose ? bone.applied : bone.pose, setup = bone.data.setup; BoneLocal pose = appliedPose ? bone.applied : bone.pose, setup = bone.data.setup;
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
@ -801,7 +801,7 @@ public class Animation {
} }
} }
/** Changes a bone's local {@link BonePose#getScaleX()}. */ /** Changes a bone's local {@link BoneLocal#getScaleX()}. */
static public class ScaleXTimeline extends CurveTimeline1 implements BoneTimeline { static public class ScaleXTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex; final int boneIndex;
@ -819,13 +819,13 @@ public class Animation {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) { if (bone.active) {
BonePose pose = appliedPose ? bone.applied : bone.pose; BoneLocal pose = appliedPose ? bone.applied : bone.pose;
pose.scaleX = getScaleValue(time, alpha, blend, direction, pose.scaleX, bone.data.setup.scaleX); pose.scaleX = getScaleValue(time, alpha, blend, direction, pose.scaleX, bone.data.setup.scaleX);
} }
} }
} }
/** Changes a bone's local {@link BonePose#getScaleY()}. */ /** Changes a bone's local {@link BoneLocal#getScaleY()}. */
static public class ScaleYTimeline extends CurveTimeline1 implements BoneTimeline { static public class ScaleYTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex; final int boneIndex;
@ -843,13 +843,13 @@ public class Animation {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) { if (bone.active) {
BonePose pose = appliedPose ? bone.applied : bone.pose; BoneLocal pose = appliedPose ? bone.applied : bone.pose;
pose.scaleY = getScaleValue(time, alpha, blend, direction, pose.scaleY, bone.data.setup.scaleY); pose.scaleY = getScaleValue(time, alpha, blend, direction, pose.scaleY, bone.data.setup.scaleY);
} }
} }
} }
/** Changes a bone's local {@link BonePose#getShearX()} and {@link BonePose#getShearY()}. */ /** Changes a bone's local {@link BoneLocal#getShearX()} and {@link BoneLocal#getShearY()}. */
static public class ShearTimeline extends CurveTimeline2 implements BoneTimeline { static public class ShearTimeline extends CurveTimeline2 implements BoneTimeline {
final int boneIndex; final int boneIndex;
@ -869,7 +869,7 @@ public class Animation {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return; if (!bone.active) return;
BonePose pose = appliedPose ? bone.applied : bone.pose, setup = bone.data.setup; BoneLocal pose = appliedPose ? bone.applied : bone.pose, setup = bone.data.setup;
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
@ -922,7 +922,7 @@ public class Animation {
} }
} }
/** Changes a bone's local {@link BonePose#getShearX()}. */ /** Changes a bone's local {@link BoneLocal#getShearX()}. */
static public class ShearXTimeline extends CurveTimeline1 implements BoneTimeline { static public class ShearXTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex; final int boneIndex;
@ -940,13 +940,13 @@ public class Animation {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) { if (bone.active) {
BonePose pose = appliedPose ? bone.applied : bone.pose; BoneLocal pose = appliedPose ? bone.applied : bone.pose;
pose.shearX = getRelativeValue(time, alpha, blend, pose.shearX, bone.data.setup.shearX); pose.shearX = getRelativeValue(time, alpha, blend, pose.shearX, bone.data.setup.shearX);
} }
} }
} }
/** Changes a bone's local {@link BonePose#getShearY()}. */ /** Changes a bone's local {@link BoneLocal#getShearY()}. */
static public class ShearYTimeline extends CurveTimeline1 implements BoneTimeline { static public class ShearYTimeline extends CurveTimeline1 implements BoneTimeline {
final int boneIndex; final int boneIndex;
@ -964,13 +964,13 @@ public class Animation {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (bone.active) { if (bone.active) {
BonePose pose = appliedPose ? bone.applied : bone.pose; BoneLocal pose = appliedPose ? bone.applied : bone.pose;
pose.shearY = getRelativeValue(time, alpha, blend, pose.shearY, bone.data.setup.shearY); pose.shearY = getRelativeValue(time, alpha, blend, pose.shearY, bone.data.setup.shearY);
} }
} }
} }
/** Changes a bone's {@link BonePose#getInherit()}. */ /** Changes a bone's {@link BoneLocal#getInherit()}. */
static public class InheritTimeline extends Timeline implements BoneTimeline { static public class InheritTimeline extends Timeline implements BoneTimeline {
static public final int ENTRIES = 2; static public final int ENTRIES = 2;
static private final int INHERIT = 1; static private final int INHERIT = 1;
@ -1004,7 +1004,7 @@ public class Animation {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return; if (!bone.active) return;
BonePose pose = appliedPose ? bone.applied : bone.pose; BoneLocal pose = appliedPose ? bone.applied : bone.pose;
if (direction == out) { if (direction == out) {
if (blend == setup) pose.inherit = bone.data.setup.inherit; if (blend == setup) pose.inherit = bone.data.setup.inherit;
@ -2572,12 +2572,12 @@ public class Animation {
if (lastTime < frames[0] || time >= frames[search(frames, lastTime) + 1]) { if (lastTime < frames[0] || time >= frames[search(frames, lastTime) + 1]) {
if (constraint != null) if (constraint != null)
constraint.reset(); constraint.reset(skeleton);
else { else {
Object[] constraints = skeleton.physicsConstraints.items; Object[] constraints = skeleton.physicsConstraints.items;
for (int i = 0, n = skeleton.physicsConstraints.size; i < n; i++) { for (int i = 0, n = skeleton.physicsConstraints.size; i < n; i++) {
constraint = (PhysicsConstraint)constraints[i]; constraint = (PhysicsConstraint)constraints[i];
if (constraint.active) constraint.reset(); if (constraint.active) constraint.reset(skeleton);
} }
} }
} }

View File

@ -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.pose, setup = bone.data.setup; BoneLocal 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,67 +38,24 @@ 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 implements Constrained { public class Bone extends PosedActive<BoneData, BoneLocal, BonePose> {
final BoneData data;
final Skeleton skeleton;
@Null final Bone parent; @Null final Bone parent;
final Array<Bone> children = new Array(); final Array<Bone> children = new Array();
final BoneApplied pose = new BoneApplied(this), constrained = new BoneApplied(this); boolean sorted;
BoneApplied applied = pose;
boolean sorted, active;
public Bone (BoneData data, Skeleton skeleton, @Null Bone parent) { public Bone (BoneData data, @Null Bone parent) {
if (data == null) throw new IllegalArgumentException("data cannot be null."); super(data, new BonePose(), new BonePose());
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
this.data = data;
this.skeleton = skeleton;
this.parent = parent; this.parent = parent;
setupPose(); applied.bone = this;
constrained.bone = this;
} }
/** Copy constructor. Does not copy the {@link #getChildren()} bones. */ /** Copy constructor. Does not copy the {@link #getChildren()} bones. */
public Bone (Bone bone, Skeleton skeleton, @Null Bone parent) { public Bone (Bone bone, @Null Bone parent) {
this.data = bone.data; this(bone.data, parent);
this.skeleton = skeleton;
this.parent = parent;
pose.set(bone.pose); pose.set(bone.pose);
} }
/** Sets this bone's local transform to the setup pose. */
public void setupPose () {
pose.set(data.setup);
}
/** The bone's setup pose data. */
public BoneData getData () {
return data;
}
public BonePose getPose () {
return 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;
}
/** The parent bone, or null if this is the root bone. */ /** The parent bone, or null if this is the root bone. */
public @Null Bone getParent () { public @Null Bone getParent () {
return parent; return parent;
@ -108,18 +65,4 @@ public class Bone implements Constrained {
public Array<Bone> getChildren () { public Array<Bone> getChildren () {
return children; return children;
} }
/** Returns false when this bone 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.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see BoneData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return active;
}
public String toString () {
return data.name;
}
} }

View File

@ -1,376 +0,0 @@
package com.esotericsoftware.spine;
import static com.badlogic.gdx.math.Matrix3.*;
import static com.esotericsoftware.spine.utils.SpineUtils.*;
import com.badlogic.gdx.math.Matrix3;
import com.badlogic.gdx.math.Vector2;
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 Update {
final Bone bone;
float a, b, worldX;
float c, d, worldY;
BoneApplied (Bone bone) {
this.bone = bone;
}
/** Computes the world transform using the parent bone and this bone's local applied transform. */
public void updateWorldTransform () {
update(null);
}
/** Computes the world transform using the parent bone and this bone's local transform.
* <p>
* See {@link #updateWorldTransform(float, float, float, float, float, float, float)}. */
/** Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the
* specified local transform. Child bones are not updated.
* <p>
* See <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide. */
public void update (Physics physics) {
if (bone.parent == null) { // Root bone.
Skeleton skeleton = bone.skeleton;
float sx = skeleton.scaleX, sy = skeleton.scaleY;
float rx = (rotation + shearX) * degRad;
float ry = (rotation + 90 + shearY) * degRad;
a = cos(rx) * scaleX * sx;
b = cos(ry) * scaleY * sx;
c = sin(rx) * scaleX * sy;
d = sin(ry) * scaleY * sy;
worldX = x * sx + skeleton.x;
worldY = y * sy + skeleton.y;
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;
switch (inherit) {
case normal -> {
float rx = (rotation + shearX) * degRad;
float ry = (rotation + 90 + shearY) * degRad;
float la = cos(rx) * scaleX;
float lb = cos(ry) * scaleY;
float lc = sin(rx) * scaleX;
float ld = sin(ry) * scaleY;
a = pa * la + pb * lc;
b = pa * lb + pb * ld;
c = pc * la + pd * lc;
d = pc * lb + pd * ld;
return;
}
case onlyTranslation -> {
float rx = (rotation + shearX) * degRad;
float ry = (rotation + 90 + shearY) * degRad;
a = cos(rx) * scaleX;
b = cos(ry) * scaleY;
c = sin(rx) * scaleX;
d = sin(ry) * scaleY;
}
case noRotationOrReflection -> {
float sx = 1 / bone.skeleton.scaleX, sy = 1 / bone.skeleton.scaleY;
pa *= sx;
pc *= sy;
float s = pa * pa + pc * pc, prx;
if (s > 0.0001f) {
s = Math.abs(pa * pd * sy - pb * sx * pc) / s;
pb = pc * s;
pd = pa * s;
prx = atan2Deg(pc, pa);
} else {
pa = 0;
pc = 0;
prx = 90 - atan2Deg(pd, pb);
}
float rx = (rotation + shearX - prx) * degRad;
float ry = (rotation + shearY - prx + 90) * degRad;
float la = cos(rx) * scaleX;
float lb = cos(ry) * scaleY;
float lc = sin(rx) * scaleX;
float ld = sin(ry) * scaleY;
a = pa * la - pb * lc;
b = pa * lb - pb * ld;
c = pc * la + pd * lc;
d = pc * lb + pd * ld;
}
case noScale, noScaleOrReflection -> {
Skeleton skeleton = bone.skeleton;
rotation *= degRad;
float cos = cos(rotation), sin = sin(rotation);
float za = (pa * cos + pb * sin) / skeleton.scaleX;
float zc = (pc * cos + pd * sin) / skeleton.scaleY;
float s = (float)Math.sqrt(za * za + zc * zc);
if (s > 0.00001f) s = 1 / s;
za *= s;
zc *= s;
s = (float)Math.sqrt(za * za + zc * zc);
if (inherit == Inherit.noScale && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s;
rotation = PI / 2 + atan2(zc, za);
float zb = cos(rotation) * s;
float zd = sin(rotation) * s;
shearX *= degRad;
shearY = (90 + shearY) * degRad;
float la = cos(shearX) * scaleX;
float lb = cos(shearY) * scaleY;
float lc = sin(shearX) * scaleX;
float ld = sin(shearY) * scaleY;
a = za * la + zb * lc;
b = za * lb + zb * ld;
c = zc * la + zd * lc;
d = zc * lb + zd * ld;
}
}
Skeleton skeleton = bone.skeleton;
a *= skeleton.scaleX;
b *= skeleton.scaleX;
c *= skeleton.scaleY;
d *= skeleton.scaleY;
}
/** 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 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 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 = bone.parent.applied;
if (parent == null) {
x = worldX - bone.skeleton.x;
y = worldY - bone.skeleton.y;
float a = this.a, b = this.b, c = this.c, d = this.d;
rotation = atan2Deg(c, a);
scaleX = (float)Math.sqrt(a * a + c * c);
scaleY = (float)Math.sqrt(b * b + d * d);
shearX = 0;
shearY = atan2Deg(a * b + c * d, a * d - b * c);
return;
}
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
float pid = 1 / (pa * pd - pb * pc);
float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid;
float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
x = (dx * ia - dy * ib);
y = (dy * id - dx * ic);
float ra, rb, rc, rd;
if (inherit == Inherit.onlyTranslation) {
ra = a;
rb = b;
rc = c;
rd = d;
} else {
Skeleton skeleton = bone.skeleton;
switch (inherit) {
case noRotationOrReflection -> {
float s = Math.abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
pb = -pc * skeleton.scaleX * s / skeleton.scaleY;
pd = pa * skeleton.scaleY * s / skeleton.scaleX;
pid = 1 / (pa * pd - pb * pc);
ia = pd * pid;
ib = pb * pid;
}
case noScale, noScaleOrReflection -> {
float r = rotation * degRad, cos = cos(r), sin = sin(r);
pa = (pa * cos + pb * sin) / skeleton.scaleX;
pc = (pc * cos + pd * sin) / skeleton.scaleY;
float s = (float)Math.sqrt(pa * pa + pc * pc);
if (s > 0.00001f) s = 1 / s;
pa *= s;
pc *= s;
s = (float)Math.sqrt(pa * pa + pc * pc);
if (inherit == Inherit.noScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s;
r = PI / 2 + atan2(pc, pa);
pb = cos(r) * s;
pd = sin(r) * s;
pid = 1 / (pa * pd - pb * pc);
ia = pd * pid;
ib = pb * pid;
ic = pc * pid;
id = pa * pid;
}
}
ra = ia * a - ib * c;
rb = ia * b - ib * d;
rc = id * c - ic * a;
rd = id * d - ic * b;
}
shearX = 0;
scaleX = (float)Math.sqrt(ra * ra + rc * rc);
if (scaleX > 0.0001f) {
float det = ra * rd - rb * rc;
scaleY = det / scaleX;
shearY = -atan2Deg(ra * rb + rc * rd, det);
rotation = atan2Deg(rc, ra);
} else {
scaleX = 0;
scaleY = (float)Math.sqrt(rb * rb + rd * rd);
shearY = 0;
rotation = 90 - atan2Deg(rd, rb);
}
}
/** Part of the world transform matrix for the X axis. If changed, {@link #updateLocalTransform()} should be called. */
public float getA () {
return a;
}
public void setA (float a) {
this.a = a;
}
/** Part of the world transform matrix for the Y axis. If changed, {@link #updateLocalTransform()} should be called. */
public float getB () {
return b;
}
public void setB (float b) {
this.b = b;
}
/** Part of the world transform matrix for the X axis. If changed, {@link #updateLocalTransform()} should be called. */
public float getC () {
return c;
}
public void setC (float c) {
this.c = c;
}
/** Part of the world transform matrix for the Y axis. If changed, {@link #updateLocalTransform()} should be called. */
public float getD () {
return d;
}
public void setD (float d) {
this.d = d;
}
/** The world X position. If changed, {@link #updateLocalTransform()} should be called. */
public float getWorldX () {
return worldX;
}
public void setWorldX (float worldX) {
this.worldX = worldX;
}
/** The world Y position. If changed, {@link #updateLocalTransform()} should be called. */
public float getWorldY () {
return worldY;
}
public void setWorldY (float worldY) {
this.worldY = worldY;
}
/** The world rotation for the X axis, calculated using {@link #a} and {@link #c}. */
public float getWorldRotationX () {
return atan2Deg(c, a);
}
/** The world rotation for the Y axis, calculated using {@link #b} and {@link #d}. */
public float getWorldRotationY () {
return atan2Deg(d, b);
}
/** The magnitude (always positive) of the world scale X, calculated using {@link #a} and {@link #c}. */
public float getWorldScaleX () {
return (float)Math.sqrt(a * a + c * c);
}
/** The magnitude (always positive) of the world scale Y, calculated using {@link #b} and {@link #d}. */
public float getWorldScaleY () {
return (float)Math.sqrt(b * b + d * d);
}
public Matrix3 getWorldTransform (Matrix3 worldTransform) {
if (worldTransform == null) throw new IllegalArgumentException("worldTransform cannot be null.");
float[] val = worldTransform.val;
val[M00] = a;
val[M01] = b;
val[M10] = c;
val[M11] = d;
val[M02] = worldX;
val[M12] = worldY;
val[M20] = 0;
val[M21] = 0;
val[M22] = 1;
return worldTransform;
}
/** Transforms a point from world coordinates to the bone's local coordinates. */
public Vector2 worldToLocal (Vector2 world) {
if (world == null) throw new IllegalArgumentException("world cannot be null.");
float det = a * d - b * c;
float x = world.x - worldX, y = world.y - worldY;
world.x = (x * d - y * b) / det;
world.y = (y * a - x * c) / det;
return world;
}
/** Transforms a point from the bone's local coordinates to world coordinates. */
public Vector2 localToWorld (Vector2 local) {
if (local == null) throw new IllegalArgumentException("local cannot be null.");
float x = local.x, y = local.y;
local.x = x * a + y * b + worldX;
local.y = x * c + y * d + worldY;
return local;
}
/** 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 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 bone.parent == null ? world : bone.parent.applied.localToWorld(world);
}
/** Transforms a world rotation to a local rotation. */
public float worldToLocalRotation (float worldRotation) {
worldRotation *= degRad;
float sin = sin(worldRotation), cos = cos(worldRotation);
return atan2Deg(a * sin - c * cos, d * cos - b * sin) + rotation - shearX;
}
/** Transforms a local rotation to a world rotation. */
public float localToWorldRotation (float localRotation) {
localRotation = (localRotation - rotation - shearX) * degRad;
float sin = sin(localRotation), cos = cos(localRotation);
return atan2Deg(cos * c + sin * d, cos * a + sin * b);
}
/** Rotates the world transform the specified amount.
* <p>
* 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);
float ra = a, rb = b;
a = cos * ra - sin * c;
b = cos * rb - sin * d;
c = sin * ra + cos * c;
d = sin * rb + cos * d;
}
public String toString () {
return bone.data.name;
}
}

View File

@ -33,13 +33,10 @@ import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.Null;
/** The setup pose for a bone. */ /** The setup pose for a bone. */
public class BoneData { public class BoneData extends PosedData<BoneLocal> {
final int index; final int index;
final String name;
@Null final BoneData parent; @Null final BoneData parent;
final BonePose setup = new BonePose();
float length; float length;
boolean skinRequired;
// Nonessential. // Nonessential.
final Color color = new Color(0.61f, 0.61f, 0.61f, 1); // 9b9b9bff final Color color = new Color(0.61f, 0.61f, 0.61f, 1); // 9b9b9bff
@ -47,18 +44,16 @@ public class BoneData {
boolean visible; boolean visible;
public BoneData (int index, String name, @Null BoneData parent) { public BoneData (int index, String name, @Null BoneData parent) {
super(name, new BoneLocal());
if (index < 0) throw new IllegalArgumentException("index must be >= 0."); if (index < 0) throw new IllegalArgumentException("index must be >= 0.");
if (name == null) throw new IllegalArgumentException("name cannot be null."); if (name == null) throw new IllegalArgumentException("name cannot be null.");
this.index = index; this.index = index;
this.name = name;
this.parent = parent; this.parent = parent;
} }
/** Copy constructor. */ /** Copy constructor. */
public BoneData (BoneData data, @Null BoneData parent) { public BoneData (BoneData data, @Null BoneData parent) {
index = data.index; this(data.index, data.name, parent);
name = data.name;
this.parent = parent;
length = data.length; length = data.length;
setup.set(data.setup); setup.set(data.setup);
} }
@ -68,19 +63,10 @@ public class BoneData {
return index; return index;
} }
/** The name of the bone, which is unique across all bones in the skeleton. */
public String getName () {
return name;
}
public @Null BoneData getParent () { public @Null BoneData getParent () {
return parent; return parent;
} }
public BonePose getSetupPose () {
return setup;
}
/** The bone's length. */ /** The bone's length. */
public float getLength () { public float getLength () {
return length; return length;
@ -90,18 +76,6 @@ public class BoneData {
this.length = length; this.length = length;
} }
/** When true, {@link Skeleton#updateWorldTransform(Physics)} only updates this bone if the {@link Skeleton#getSkin()} contains
* this bone.
* <p>
* See {@link Skin#getBones()}. */
public boolean getSkinRequired () {
return skinRequired;
}
public void setSkinRequired (boolean skinRequired) {
this.skinRequired = skinRequired;
}
/** The color of the bone as it was in Spine, or a default color if nonessential data was not exported. Bones are not usually /** The color of the bone as it was in Spine, or a default color if nonessential data was not exported. Bones are not usually
* rendered at runtime. */ * rendered at runtime. */
public Color getColor () { public Color getColor () {
@ -126,10 +100,6 @@ public class BoneData {
this.visible = visible; this.visible = visible;
} }
public String toString () {
return name;
}
/** Determines how a bone inherits world transforms from parent bones. */ /** Determines how a bone inherits world transforms from parent bones. */
static public enum Inherit { static public enum Inherit {
normal, onlyTranslation, noRotationOrReflection, noScale, noScaleOrReflection; normal, onlyTranslation, noRotationOrReflection, noScale, noScaleOrReflection;

View File

@ -0,0 +1,141 @@
/******************************************************************************
* 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 local pose. */
public class BoneLocal implements Pose<BoneLocal> {
float x, y, rotation, scaleX, scaleY, shearX, shearY;
Inherit inherit;
BoneLocal () {
}
public void set (BoneLocal pose) {
if (pose == null) throw new IllegalArgumentException("pose cannot be null.");
x = pose.x;
y = pose.y;
rotation = pose.rotation;
scaleX = pose.scaleX;
scaleY = pose.scaleY;
shearX = pose.shearX;
shearY = pose.shearY;
inherit = pose.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

@ -1,141 +1,371 @@
/******************************************************************************
* 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; package com.esotericsoftware.spine;
import static com.badlogic.gdx.math.Matrix3.*;
import static com.esotericsoftware.spine.utils.SpineUtils.*;
import com.badlogic.gdx.math.Matrix3;
import com.badlogic.gdx.math.Vector2;
import com.esotericsoftware.spine.BoneData.Inherit; import com.esotericsoftware.spine.BoneData.Inherit;
/** Stores a bone's local pose. */ /** The applied pose for a bone. This is the {@link Bone} pose with constraints applied and the world transform computed by
public class BonePose { * {@link Skeleton#updateWorldTransform(Physics)}. */
float x, y, rotation, scaleX, scaleY, shearX, shearY; public class BonePose extends BoneLocal implements Update {
Inherit inherit; Bone bone;
float a, b, worldX;
float c, d, worldY;
BonePose () { BonePose () {
} }
public void set (BonePose pose) { /** Computes the world transform using the parent bone and this bone's local applied transform. */
if (pose == null) throw new IllegalArgumentException("pose cannot be null."); public void updateWorldTransform (Skeleton skeleton) {
x = pose.x; update(skeleton, null);
y = pose.y;
rotation = pose.rotation;
scaleX = pose.scaleX;
scaleY = pose.scaleY;
shearX = pose.shearX;
shearY = pose.shearY;
inherit = pose.inherit;
} }
/** The local x translation. */ /** Computes the world transform using the parent bone and this bone's local transform.
public float getX () { * <p>
return x; * See {@link #updateWorldTransform(float, float, float, float, float, float, float)}. */
/** Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the
* specified local transform. Child bones are not updated.
* <p>
* See <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide. */
public void update (Skeleton skeleton, Physics physics) {
if (bone.parent == null) { // Root bone.
float sx = skeleton.scaleX, sy = skeleton.scaleY;
float rx = (rotation + shearX) * degRad;
float ry = (rotation + 90 + shearY) * degRad;
a = cos(rx) * scaleX * sx;
b = cos(ry) * scaleY * sx;
c = sin(rx) * scaleX * sy;
d = sin(ry) * scaleY * sy;
worldX = x * sx + skeleton.x;
worldY = y * sy + skeleton.y;
return;
}
BonePose 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;
switch (inherit) {
case normal -> {
float rx = (rotation + shearX) * degRad;
float ry = (rotation + 90 + shearY) * degRad;
float la = cos(rx) * scaleX;
float lb = cos(ry) * scaleY;
float lc = sin(rx) * scaleX;
float ld = sin(ry) * scaleY;
a = pa * la + pb * lc;
b = pa * lb + pb * ld;
c = pc * la + pd * lc;
d = pc * lb + pd * ld;
return;
}
case onlyTranslation -> {
float rx = (rotation + shearX) * degRad;
float ry = (rotation + 90 + shearY) * degRad;
a = cos(rx) * scaleX;
b = cos(ry) * scaleY;
c = sin(rx) * scaleX;
d = sin(ry) * scaleY;
}
case noRotationOrReflection -> {
float sx = 1 / skeleton.scaleX, sy = 1 / skeleton.scaleY;
pa *= sx;
pc *= sy;
float s = pa * pa + pc * pc, prx;
if (s > 0.0001f) {
s = Math.abs(pa * pd * sy - pb * sx * pc) / s;
pb = pc * s;
pd = pa * s;
prx = atan2Deg(pc, pa);
} else {
pa = 0;
pc = 0;
prx = 90 - atan2Deg(pd, pb);
}
float rx = (rotation + shearX - prx) * degRad;
float ry = (rotation + shearY - prx + 90) * degRad;
float la = cos(rx) * scaleX;
float lb = cos(ry) * scaleY;
float lc = sin(rx) * scaleX;
float ld = sin(ry) * scaleY;
a = pa * la - pb * lc;
b = pa * lb - pb * ld;
c = pc * la + pd * lc;
d = pc * lb + pd * ld;
}
case noScale, noScaleOrReflection -> {
rotation *= degRad;
float cos = cos(rotation), sin = sin(rotation);
float za = (pa * cos + pb * sin) / skeleton.scaleX;
float zc = (pc * cos + pd * sin) / skeleton.scaleY;
float s = (float)Math.sqrt(za * za + zc * zc);
if (s > 0.00001f) s = 1 / s;
za *= s;
zc *= s;
s = (float)Math.sqrt(za * za + zc * zc);
if (inherit == Inherit.noScale && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s;
rotation = PI / 2 + atan2(zc, za);
float zb = cos(rotation) * s;
float zd = sin(rotation) * s;
shearX *= degRad;
shearY = (90 + shearY) * degRad;
float la = cos(shearX) * scaleX;
float lb = cos(shearY) * scaleY;
float lc = sin(shearX) * scaleX;
float ld = sin(shearY) * scaleY;
a = za * la + zb * lc;
b = za * lb + zb * ld;
c = zc * la + zd * lc;
d = zc * lb + zd * ld;
}
}
a *= skeleton.scaleX;
b *= skeleton.scaleX;
c *= skeleton.scaleY;
d *= skeleton.scaleY;
} }
public void setX (float x) { /** Computes the local transform values from the world transform.
this.x = x; * <p>
* If the world transform is modified (by a constraint, {@link #rotateWorld(float)}, etc) then this method should be called so
* 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 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 (Skeleton skeleton) {
if (bone.parent == null) {
x = worldX - skeleton.x;
y = worldY - skeleton.y;
float a = this.a, b = this.b, c = this.c, d = this.d;
rotation = atan2Deg(c, a);
scaleX = (float)Math.sqrt(a * a + c * c);
scaleY = (float)Math.sqrt(b * b + d * d);
shearX = 0;
shearY = atan2Deg(a * b + c * d, a * d - b * c);
return;
}
BonePose parent = bone.parent.applied;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
float pid = 1 / (pa * pd - pb * pc);
float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid;
float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
x = (dx * ia - dy * ib);
y = (dy * id - dx * ic);
float ra, rb, rc, rd;
if (inherit == Inherit.onlyTranslation) {
ra = a;
rb = b;
rc = c;
rd = d;
} else {
switch (inherit) {
case noRotationOrReflection -> {
float s = Math.abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
pb = -pc * skeleton.scaleX * s / skeleton.scaleY;
pd = pa * skeleton.scaleY * s / skeleton.scaleX;
pid = 1 / (pa * pd - pb * pc);
ia = pd * pid;
ib = pb * pid;
}
case noScale, noScaleOrReflection -> {
float r = rotation * degRad, cos = cos(r), sin = sin(r);
pa = (pa * cos + pb * sin) / skeleton.scaleX;
pc = (pc * cos + pd * sin) / skeleton.scaleY;
float s = (float)Math.sqrt(pa * pa + pc * pc);
if (s > 0.00001f) s = 1 / s;
pa *= s;
pc *= s;
s = (float)Math.sqrt(pa * pa + pc * pc);
if (inherit == Inherit.noScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s;
r = PI / 2 + atan2(pc, pa);
pb = cos(r) * s;
pd = sin(r) * s;
pid = 1 / (pa * pd - pb * pc);
ia = pd * pid;
ib = pb * pid;
ic = pc * pid;
id = pa * pid;
}
}
ra = ia * a - ib * c;
rb = ia * b - ib * d;
rc = id * c - ic * a;
rd = id * d - ic * b;
}
shearX = 0;
scaleX = (float)Math.sqrt(ra * ra + rc * rc);
if (scaleX > 0.0001f) {
float det = ra * rd - rb * rc;
scaleY = det / scaleX;
shearY = -atan2Deg(ra * rb + rc * rd, det);
rotation = atan2Deg(rc, ra);
} else {
scaleX = 0;
scaleY = (float)Math.sqrt(rb * rb + rd * rd);
shearY = 0;
rotation = 90 - atan2Deg(rd, rb);
}
} }
/** The local y translation. */ /** Part of the world transform matrix for the X axis. If changed, {@link #updateLocalTransform(Skeleton)} should be called. */
public float getY () { public float getA () {
return y; return a;
} }
public void setY (float y) { public void setA (float a) {
this.y = y; this.a = a;
} }
public void setPosition (float x, float y) { /** Part of the world transform matrix for the Y axis. If changed, {@link #updateLocalTransform(Skeleton)} should be called. */
this.x = x; public float getB () {
this.y = y; return b;
} }
/** The local rotation in degrees, counter clockwise. */ public void setB (float b) {
public float getRotation () { this.b = b;
return rotation;
} }
public void setRotation (float rotation) { /** Part of the world transform matrix for the X axis. If changed, {@link #updateLocalTransform(Skeleton)} should be called. */
this.rotation = rotation; public float getC () {
return c;
} }
/** The local scaleX. */ public void setC (float c) {
public float getScaleX () { this.c = c;
return scaleX;
} }
public void setScaleX (float scaleX) { /** Part of the world transform matrix for the Y axis. If changed, {@link #updateLocalTransform(Skeleton)} should be called. */
this.scaleX = scaleX; public float getD () {
return d;
} }
/** The local scaleY. */ public void setD (float d) {
public float getScaleY () { this.d = d;
return scaleY;
} }
public void setScaleY (float scaleY) { /** The world X position. If changed, {@link #updateLocalTransform(Skeleton)} should be called. */
this.scaleY = scaleY; public float getWorldX () {
return worldX;
} }
public void setScale (float scaleX, float scaleY) { public void setWorldX (float worldX) {
this.scaleX = scaleX; this.worldX = worldX;
this.scaleY = scaleY;
} }
public void setScale (float scale) { /** The world Y position. If changed, {@link #updateLocalTransform(Skeleton)} should be called. */
scaleX = scale; public float getWorldY () {
scaleY = scale; return worldY;
} }
/** The local shearX. */ public void setWorldY (float worldY) {
public float getShearX () { this.worldY = worldY;
return shearX;
} }
public void setShearX (float shearX) { /** The world rotation for the X axis, calculated using {@link #a} and {@link #c}. */
this.shearX = shearX; public float getWorldRotationX () {
return atan2Deg(c, a);
} }
/** The local shearY. */ /** The world rotation for the Y axis, calculated using {@link #b} and {@link #d}. */
public float getShearY () { public float getWorldRotationY () {
return shearY; return atan2Deg(d, b);
} }
public void setShearY (float shearY) { /** The magnitude (always positive) of the world scale X, calculated using {@link #a} and {@link #c}. */
this.shearY = shearY; public float getWorldScaleX () {
return (float)Math.sqrt(a * a + c * c);
} }
/** Determines how parent world transforms affect this bone. */ /** The magnitude (always positive) of the world scale Y, calculated using {@link #b} and {@link #d}. */
public Inherit getInherit () { public float getWorldScaleY () {
return inherit; return (float)Math.sqrt(b * b + d * d);
} }
public void setInherit (Inherit inherit) { public Matrix3 getWorldTransform (Matrix3 worldTransform) {
if (inherit == null) throw new IllegalArgumentException("inherit cannot be null."); if (worldTransform == null) throw new IllegalArgumentException("worldTransform cannot be null.");
this.inherit = inherit; float[] val = worldTransform.val;
val[M00] = a;
val[M01] = b;
val[M10] = c;
val[M11] = d;
val[M02] = worldX;
val[M12] = worldY;
val[M20] = 0;
val[M21] = 0;
val[M22] = 1;
return worldTransform;
}
/** Transforms a point from world coordinates to the bone's local coordinates. */
public Vector2 worldToLocal (Vector2 world) {
if (world == null) throw new IllegalArgumentException("world cannot be null.");
float det = a * d - b * c;
float x = world.x - worldX, y = world.y - worldY;
world.x = (x * d - y * b) / det;
world.y = (y * a - x * c) / det;
return world;
}
/** Transforms a point from the bone's local coordinates to world coordinates. */
public Vector2 localToWorld (Vector2 local) {
if (local == null) throw new IllegalArgumentException("local cannot be null.");
float x = local.x, y = local.y;
local.x = x * a + y * b + worldX;
local.y = x * c + y * d + worldY;
return local;
}
/** 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 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 bone.parent == null ? world : bone.parent.applied.localToWorld(world);
}
/** Transforms a world rotation to a local rotation. */
public float worldToLocalRotation (float worldRotation) {
worldRotation *= degRad;
float sin = sin(worldRotation), cos = cos(worldRotation);
return atan2Deg(a * sin - c * cos, d * cos - b * sin) + rotation - shearX;
}
/** Transforms a local rotation to a world rotation. */
public float localToWorldRotation (float localRotation) {
localRotation = (localRotation - rotation - shearX) * degRad;
float sin = sin(localRotation), cos = cos(localRotation);
return atan2Deg(cos * c + sin * d, cos * a + sin * b);
}
/** Rotates the world transform the specified amount.
* <p>
* After changes are made to the world transform, {@link #updateLocalTransform(Skeleton)} should be called and
* {@link #update(Skeleton, 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);
float ra = a, rb = b;
a = cos * ra - sin * c;
b = cos * rb - sin * d;
c = sin * ra + cos * c;
d = sin * rb + cos * d;
}
public String toString () {
return bone.data.name;
} }
} }

View File

@ -1,36 +0,0 @@
/******************************************************************************
* 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

@ -0,0 +1,10 @@
package com.esotericsoftware.spine;
abstract public class Constraint<D extends PosedData<P>, P extends Pose> extends PosedActive<D, P, P> implements Update {
public Constraint (D data, P pose, P constrained) {
super(data, pose, constrained);
}
abstract public void sort ();
}

View File

@ -39,26 +39,19 @@ 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 Constrained, Update { public class IkConstraint extends Constraint<IkConstraintData, IkConstraintPose> {
final IkConstraintData data; final Array<BonePose> bones;
final Array<BoneApplied> bones;
Bone target; Bone target;
final IkConstraintPose pose = new IkConstraintPose(), constrained = new IkConstraintPose();
IkConstraintPose applied = pose;
boolean active;
public IkConstraint (IkConstraintData data, Skeleton skeleton) { public IkConstraint (IkConstraintData data, Skeleton skeleton) {
if (data == null) throw new IllegalArgumentException("data cannot be null."); super(data, new IkConstraintPose(), new IkConstraintPose());
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
this.data = data;
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).constrained); bones.add(skeleton.bones.get(boneData.index).constrained);
target = skeleton.bones.get(data.target.index); target = skeleton.bones.get(data.target.index);
setupPose();
} }
/** Copy constructor. */ /** Copy constructor. */
@ -67,26 +60,25 @@ public class IkConstraint implements Constrained, Update {
pose.set(constraint.pose); pose.set(constraint.pose);
} }
public void setupPose () {
pose.set(data.setup);
}
/** Applies the constraint to the constrained bones. */ /** Applies the constraint to the constrained bones. */
public void update (Physics physics) { public void update (Skeleton skeleton, Physics physics) {
IkConstraintPose a = applied; IkConstraintPose a = applied;
if (a.mix == 0) return; if (a.mix == 0) return;
BoneApplied target = this.target.applied; BonePose 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(skeleton, (BonePose)bones[0], target.worldX, target.worldY, a.compress, a.stretch, data.uniform, a.mix);
case 2 -> // case 2 -> apply(skeleton, (BonePose)bones[0], (BonePose)bones[1], target.worldX, target.worldY, a.bendDirection, a.stretch,
apply((BoneApplied)bones[0], (BoneApplied)bones[1], target.worldX, target.worldY, a.bendDirection, a.stretch, data.uniform, a.softness, a.mix);
data.uniform, a.softness, a.mix);
} }
} }
public void sort () {
// BOZO
}
/** The 1 or 2 bones that will be modified by this IK constraint. */ /** The 1 or 2 bones that will be modified by this IK constraint. */
public Array<BoneApplied> getBones () { public Array<BonePose> getBones () {
return bones; return bones;
} }
@ -100,61 +92,20 @@ public class IkConstraint implements Constrained, Update {
this.target = target; this.target = target;
} }
public IkConstraintPose getPose () {
return pose;
}
public IkConstraintPose getAppliedPose () {
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.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see ConstraintData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return active;
}
/** The IK constraint's setup pose data. */
public IkConstraintData getData () {
return data;
}
public String toString () {
return data.name;
}
/** Applies 1 bone IK. The target is specified in the world coordinate system. */ /** Applies 1 bone IK. The target is specified in the world coordinate system. */
static public void apply (BoneApplied bone, float targetX, float targetY, boolean compress, boolean stretch, boolean uniform, static public void apply (Skeleton skeleton, BonePose bone, float targetX, float targetY, boolean compress, boolean stretch,
float alpha) { boolean uniform, 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.bone.parent.applied; BonePose 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) {
case onlyTranslation: case onlyTranslation:
tx = (targetX - bone.worldX) * Math.signum(bone.bone.skeleton.scaleX); tx = (targetX - bone.worldX) * Math.signum(skeleton.scaleX);
ty = (targetY - bone.worldY) * Math.signum(bone.bone.skeleton.scaleY); ty = (targetY - bone.worldY) * Math.signum(skeleton.scaleY);
break; break;
case noRotationOrReflection: case noRotationOrReflection:
float s = Math.abs(pa * pd - pb * pc) / Math.max(0.0001f, pa * pa + pc * pc); float s = Math.abs(pa * pd - pb * pc) / Math.max(0.0001f, pa * pa + pc * pc);
Skeleton skeleton = bone.bone.skeleton;
float sa = pa / skeleton.scaleX; float sa = pa / skeleton.scaleX;
float sc = pc / skeleton.scaleY; float sc = pc / skeleton.scaleY;
pb = -sc * s * skeleton.scaleX; pb = -sc * s * skeleton.scaleX;
@ -196,13 +147,13 @@ public class IkConstraint implements Constrained, Update {
} }
} }
} }
bone.updateWorldTransform(); bone.updateWorldTransform(skeleton);
} }
/** Applies 2 bone IK. The target is specified in the world coordinate system. /** Applies 2 bone IK. The target is specified in the world coordinate system.
* @param child A direct descendant of the parent bone. */ * @param child A direct descendant of the parent bone. */
static public void apply (BoneApplied parent, BoneApplied child, float targetX, float targetY, int bendDir, boolean stretch, static public void apply (Skeleton skeleton, BonePose parent, BonePose child, float targetX, float targetY, int bendDir,
boolean uniform, float softness, float alpha) { boolean stretch, boolean uniform, float softness, float alpha) {
if (parent == null) throw new IllegalArgumentException("parent cannot be null."); if (parent == null) throw new IllegalArgumentException("parent cannot be null.");
if (child == null) throw new IllegalArgumentException("child cannot be null."); if (child == null) throw new IllegalArgumentException("child cannot be null.");
if (parent.inherit != Inherit.normal || child.inherit != Inherit.normal) return; if (parent.inherit != Inherit.normal || child.inherit != Inherit.normal) return;
@ -235,7 +186,7 @@ public class IkConstraint implements Constrained, Update {
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.bone.parent.applied; BonePose pp = parent.bone.parent.applied;
a = pp.a; a = pp.a;
b = pp.b; b = pp.b;
c = pp.c; c = pp.c;
@ -245,9 +196,9 @@ public class IkConstraint implements Constrained, Update {
float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; 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.bone.data.length * csx, a1, a2; float l1 = (float)Math.sqrt(dx * dx + dy * dy), l2 = child.bone.data.length * csx, a1, a2;
if (l1 < 0.0001f) { if (l1 < 0.0001f) {
apply(parent, targetX, targetY, false, stretch, false, alpha); apply(skeleton, parent, targetX, targetY, false, stretch, false, alpha);
child.rotation = 0; child.rotation = 0;
child.updateWorldTransform(); child.updateWorldTransform(skeleton);
return; return;
} }
x = targetX - pp.worldX; x = targetX - pp.worldX;
@ -342,13 +293,13 @@ public class IkConstraint implements Constrained, Update {
else if (a1 < -180) // else if (a1 < -180) //
a1 += 360; a1 += 360;
parent.rotation += a1 * alpha; parent.rotation += a1 * alpha;
parent.updateWorldTransform(); parent.updateWorldTransform(skeleton);
a2 = ((a2 + os) * radDeg - child.shearX) * s2 + os2 - child.rotation; a2 = ((a2 + os) * radDeg - child.shearX) * s2 + os2 - child.rotation;
if (a2 > 180) if (a2 > 180)
a2 -= 360; a2 -= 360;
else if (a2 < -180) // else if (a2 < -180) //
a2 += 360; a2 += 360;
child.rotation += a2 * alpha; child.rotation += a2 * alpha;
child.updateWorldTransform(); child.updateWorldTransform(skeleton);
} }
} }

View File

@ -34,18 +34,13 @@ import com.badlogic.gdx.utils.Array;
/** Stores the setup pose for an {@link IkConstraint}. /** Stores the setup pose for an {@link IkConstraint}.
* <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 IkConstraintData extends ConstraintData { public class IkConstraintData extends PosedData<IkConstraintPose> {
final IkConstraintPose setup = new IkConstraintPose();
final Array<BoneData> bones = new Array(); final Array<BoneData> bones = new Array();
BoneData target; BoneData target;
boolean uniform; boolean uniform;
public IkConstraintData (String name) { public IkConstraintData (String name) {
super(name); super(name, new IkConstraintPose());
}
public IkConstraintPose getSetupPose () {
return setup;
} }
/** The bones that are constrained by this IK constraint. */ /** The bones that are constrained by this IK constraint. */

View File

@ -30,7 +30,7 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
/** Stores the current pose for an IK constraint. */ /** Stores the current pose for an IK constraint. */
public class IkConstraintPose { public class IkConstraintPose implements Pose<IkConstraintPose> {
int bendDirection; int bendDirection;
boolean compress, stretch; boolean compress, stretch;
float mix, softness; float mix, softness;

View File

@ -45,39 +45,26 @@ 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 Constrained, Update { public class PathConstraint extends Constraint<PathConstraintData, PathConstraintPose> {
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 Array<BonePose> bones;
final Array<BoneApplied> bones;
Slot slot; Slot slot;
final PathConstraintPose pose = new PathConstraintPose(), constrained = new PathConstraintPose();
PathConstraintPose applied = pose;
boolean active;
private final FloatArray spaces = new FloatArray(), positions = new FloatArray(); private final FloatArray spaces = new FloatArray(), positions = new FloatArray();
private final FloatArray world = new FloatArray(), curves = new FloatArray(), lengths = new FloatArray(); private final FloatArray world = new FloatArray(), curves = new FloatArray(), lengths = new FloatArray();
private final float[] segments = new float[10]; private final float[] segments = new float[10];
public PathConstraint (PathConstraintData data, Array<BoneApplied> bones, Slot slot) {
this.data = data;
this.bones = bones;
this.slot = slot;
}
public PathConstraint (PathConstraintData data, Skeleton skeleton) { public PathConstraint (PathConstraintData data, Skeleton skeleton) {
if (data == null) throw new IllegalArgumentException("data cannot be null."); super(data, new PathConstraintPose(), new PathConstraintPose());
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
this.data = data;
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).constrained); bones.add(skeleton.bones.get(boneData.index).constrained);
slot = skeleton.slots.get(data.slot.index); slot = skeleton.slots.get(data.slot.index);
setupPose();
} }
/** Copy constructor. */ /** Copy constructor. */
@ -86,12 +73,8 @@ public class PathConstraint implements Constrained, Update {
pose.set(constraint.pose); pose.set(constraint.pose);
} }
public void setupPose () {
pose.set(data.setup);
}
/** Applies the constraint to the constrained bones. */ /** Applies the constraint to the constrained bones. */
public void update (Physics physics) { public void update (Skeleton skeleton, Physics physics) {
if (!(slot.applied.attachment instanceof PathAttachment pathAttachment)) return; if (!(slot.applied.attachment instanceof PathAttachment pathAttachment)) return;
PathConstraintPose pose = applied; PathConstraintPose pose = applied;
@ -109,7 +92,7 @@ public class PathConstraint implements Constrained, Update {
case percent -> { case percent -> {
if (scale) { if (scale) {
for (int i = 0, n = spacesCount - 1; i < n; i++) { for (int i = 0, n = spacesCount - 1; i < n; i++) {
var bone = (BoneApplied)bones[i]; var bone = (BonePose)bones[i];
float setupLength = bone.bone.data.length; float setupLength = bone.bone.data.length;
float x = setupLength * bone.a, y = setupLength * bone.c; float x = setupLength * bone.a, y = setupLength * bone.c;
lengths[i] = (float)Math.sqrt(x * x + y * y); lengths[i] = (float)Math.sqrt(x * x + y * y);
@ -120,7 +103,7 @@ public class PathConstraint implements Constrained, Update {
case proportional -> { case proportional -> {
float sum = 0; float sum = 0;
for (int i = 0, n = spacesCount - 1; i < n;) { for (int i = 0, n = spacesCount - 1; i < n;) {
var bone = (BoneApplied)bones[i]; var bone = (BonePose)bones[i];
float setupLength = bone.bone.data.length; float setupLength = bone.bone.data.length;
if (setupLength < epsilon) { if (setupLength < epsilon) {
if (scale) lengths[i] = 0; if (scale) lengths[i] = 0;
@ -142,7 +125,7 @@ public class PathConstraint implements Constrained, Update {
default -> { default -> {
boolean lengthSpacing = data.spacingMode == SpacingMode.length; boolean lengthSpacing = data.spacingMode == SpacingMode.length;
for (int i = 0, n = spacesCount - 1; i < n;) { for (int i = 0, n = spacesCount - 1; i < n;) {
var bone = (BoneApplied)bones[i]; var bone = (BonePose)bones[i];
float setupLength = bone.bone.data.length; float setupLength = bone.bone.data.length;
if (setupLength < epsilon) { if (setupLength < epsilon) {
if (scale) lengths[i] = 0; if (scale) lengths[i] = 0;
@ -157,18 +140,18 @@ public class PathConstraint implements Constrained, Update {
} }
} }
float[] positions = computeWorldPositions(pathAttachment, spacesCount, tangents); float[] positions = computeWorldPositions(skeleton, pathAttachment, spacesCount, tangents);
float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
boolean tip; boolean tip;
if (offsetRotation == 0) if (offsetRotation == 0)
tip = data.rotateMode == RotateMode.chain; tip = data.rotateMode == RotateMode.chain;
else { else {
tip = false; tip = false;
BoneApplied p = slot.bone.applied; BonePose p = slot.bone.applied;
offsetRotation *= p.a * p.d - p.b * p.c > 0 ? degRad : -degRad; offsetRotation *= p.a * p.d - p.b * p.c > 0 ? degRad : -degRad;
} }
for (int i = 0, p = 3; i < boneCount; i++, p += 3) { for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
var bone = (BoneApplied)bones[i]; var bone = (BonePose)bones[i];
bone.worldX += (boneX - bone.worldX) * mixX; bone.worldX += (boneX - bone.worldX) * mixX;
bone.worldY += (boneY - bone.worldY) * mixY; bone.worldY += (boneY - bone.worldY) * mixY;
float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
@ -211,11 +194,11 @@ public class PathConstraint implements Constrained, Update {
bone.c = sin * a + cos * c; bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d; bone.d = sin * b + cos * d;
} }
bone.updateLocalTransform(); bone.updateLocalTransform(skeleton);
} }
} }
float[] computeWorldPositions (PathAttachment path, int spacesCount, boolean tangents) { float[] computeWorldPositions (Skeleton skeleton, PathAttachment path, int spacesCount, boolean tangents) {
Slot slot = this.slot; Slot slot = this.slot;
float position = applied.position; float position = applied.position;
float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3 + 2), world; float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3 + 2), world;
@ -248,14 +231,14 @@ public class PathConstraint implements Constrained, Update {
} else if (p < 0) { } else if (p < 0) {
if (prevCurve != BEFORE) { if (prevCurve != BEFORE) {
prevCurve = BEFORE; prevCurve = BEFORE;
path.computeWorldVertices(slot, 2, 4, world, 0, 2); path.computeWorldVertices(skeleton, slot, 2, 4, world, 0, 2);
} }
addBeforePosition(p, world, 0, out, o); addBeforePosition(p, world, 0, out, o);
continue; continue;
} else if (p > pathLength) { } else if (p > pathLength) {
if (prevCurve != AFTER) { if (prevCurve != AFTER) {
prevCurve = AFTER; prevCurve = AFTER;
path.computeWorldVertices(slot, verticesLength - 6, 4, world, 0, 2); path.computeWorldVertices(skeleton, slot, verticesLength - 6, 4, world, 0, 2);
} }
addAfterPosition(p - pathLength, world, 0, out, o); addAfterPosition(p - pathLength, world, 0, out, o);
continue; continue;
@ -276,10 +259,10 @@ public class PathConstraint implements Constrained, Update {
if (curve != prevCurve) { if (curve != prevCurve) {
prevCurve = curve; prevCurve = curve;
if (closed && curve == curveCount) { if (closed && curve == curveCount) {
path.computeWorldVertices(slot, verticesLength - 4, 4, world, 0, 2); path.computeWorldVertices(skeleton, slot, verticesLength - 4, 4, world, 0, 2);
path.computeWorldVertices(slot, 0, 4, world, 4, 2); path.computeWorldVertices(skeleton, slot, 0, 4, world, 4, 2);
} else } else
path.computeWorldVertices(slot, curve * 6 + 2, 8, world, 0, 2); path.computeWorldVertices(skeleton, slot, curve * 6 + 2, 8, world, 0, 2);
} }
addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o, addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
tangents || (i > 0 && space < epsilon)); tangents || (i > 0 && space < epsilon));
@ -291,15 +274,15 @@ public class PathConstraint implements Constrained, Update {
if (closed) { if (closed) {
verticesLength += 2; verticesLength += 2;
world = this.world.setSize(verticesLength); world = this.world.setSize(verticesLength);
path.computeWorldVertices(slot, 2, verticesLength - 4, world, 0, 2); path.computeWorldVertices(skeleton, slot, 2, verticesLength - 4, world, 0, 2);
path.computeWorldVertices(slot, 0, 2, world, verticesLength - 4, 2); path.computeWorldVertices(skeleton, slot, 0, 2, world, verticesLength - 4, 2);
world[verticesLength - 2] = world[0]; world[verticesLength - 2] = world[0];
world[verticesLength - 1] = world[1]; world[verticesLength - 1] = world[1];
} else { } else {
curveCount--; curveCount--;
verticesLength -= 4; verticesLength -= 4;
world = this.world.setSize(verticesLength); world = this.world.setSize(verticesLength);
path.computeWorldVertices(slot, 2, verticesLength, world, 0, 2); path.computeWorldVertices(skeleton, slot, 2, verticesLength, world, 0, 2);
} }
// Curve lengths. // Curve lengths.
@ -473,8 +456,11 @@ public class PathConstraint implements Constrained, Update {
} }
} }
public void sort () {
}
/** The bones that will be modified by this path constraint. */ /** The bones that will be modified by this path constraint. */
public Array<BoneApplied> getBones () { public Array<BonePose> getBones () {
return bones; return bones;
} }
@ -487,44 +473,4 @@ public class PathConstraint implements Constrained, Update {
if (slot == null) throw new IllegalArgumentException("slot cannot be null."); if (slot == null) throw new IllegalArgumentException("slot cannot be null.");
this.slot = slot; this.slot = slot;
} }
public PathConstraintPose getPose () {
return pose;
}
public PathConstraintPose getAppliedPose () {
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.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see ConstraintData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return active;
}
/** The path constraint's setup pose data. */
public PathConstraintData getData () {
return data;
}
public String toString () {
return data.name;
}
} }

View File

@ -34,8 +34,7 @@ import com.badlogic.gdx.utils.Array;
/** Stores the setup pose for a {@link PathConstraint}. /** Stores the setup pose for a {@link PathConstraint}.
* <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 PathConstraintData extends ConstraintData { public class PathConstraintData extends PosedData<PathConstraintPose> {
final PathConstraintPose setup = new PathConstraintPose();
final Array<BoneData> bones = new Array(); final Array<BoneData> bones = new Array();
SlotData slot; SlotData slot;
PositionMode positionMode; PositionMode positionMode;
@ -44,11 +43,7 @@ public class PathConstraintData extends ConstraintData {
float offsetRotation; float offsetRotation;
public PathConstraintData (String name) { public PathConstraintData (String name) {
super(name); super(name, new PathConstraintPose());
}
public PathConstraintPose getSetupPose () {
return setup;
} }
/** The bones that will be modified by this path constraint. */ /** The bones that will be modified by this path constraint. */

View File

@ -30,7 +30,7 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
/** Stores a pose for a path constraint. */ /** Stores a pose for a path constraint. */
public class PathConstraintPose { public class PathConstraintPose implements Pose<PathConstraintPose> {
float position, spacing, mixRotate, mixX, mixY; float position, spacing, mixRotate, mixX, mixY;
public void set (PathConstraintPose pose) { public void set (PathConstraintPose pose) {

View File

@ -34,13 +34,8 @@ 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 Constrained, Update { public class PhysicsConstraint extends Constraint<PhysicsConstraintData, PhysicsConstraintPose> {
final PhysicsConstraintData data; BonePose bone;
final Skeleton skeleton;
BoneApplied bone;
final PhysicsConstraintPose pose = new PhysicsConstraintPose(), constrained = new PhysicsConstraintPose();
PhysicsConstraintPose applied = pose;
boolean active;
boolean reset = true; boolean reset = true;
float ux, uy, cx, cy, tx, ty; float ux, uy, cx, cy, tx, ty;
@ -51,14 +46,10 @@ public class PhysicsConstraint implements Constrained, Update {
float remaining, lastTime; float remaining, lastTime;
public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton) { public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton) {
if (data == null) throw new IllegalArgumentException("data cannot be null."); super(data, new PhysicsConstraintPose(), new PhysicsConstraintPose());
if (skeleton == null) throw new IllegalArgumentException("skeleton 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).constrained; bone = skeleton.bones.get(data.bone.index).constrained;
setupPose();
} }
/** Copy constructor. */ /** Copy constructor. */
@ -67,7 +58,7 @@ public class PhysicsConstraint implements Constrained, Update {
pose.set(constraint.pose); pose.set(constraint.pose);
} }
public void reset () { public void reset (Skeleton skeleton) {
remaining = 0; remaining = 0;
lastTime = skeleton.time; lastTime = skeleton.time;
reset = true; reset = true;
@ -81,12 +72,8 @@ public class PhysicsConstraint implements Constrained, Update {
scaleVelocity = 0; scaleVelocity = 0;
} }
public void setupPose () { /** Translates the physics constraint so next {@link #update(Skeleton, Physics)} forces are applied as if the bone moved an
pose.set(data.setup); * additional amount in world space. */
}
/** Translates the physics constraint so next {@link #update(Physics)} forces are applied as if the bone moved an additional
* amount in world space. */
public void translate (float x, float y) { public void translate (float x, float y) {
ux -= x; ux -= x;
uy -= y; uy -= y;
@ -94,8 +81,8 @@ public class PhysicsConstraint implements Constrained, Update {
cy -= y; cy -= y;
} }
/** Rotates the physics constraint so next {@link #update(Physics)} forces are applied as if the bone rotated around the /** Rotates the physics constraint so next {@link #update(Skeleton, Physics)} forces are applied as if the bone rotated around
* specified point in world space. */ * the specified point in world space. */
public void rotate (float x, float y, float degrees) { public void rotate (float x, float y, float degrees) {
float r = degrees * degRad, cos = cos(r), sin = sin(r); float r = degrees * degRad, cos = cos(r), sin = sin(r);
float dx = cx - x, dy = cy - y; float dx = cx - x, dy = cy - y;
@ -103,23 +90,22 @@ public class PhysicsConstraint implements Constrained, Update {
} }
/** Applies the constraint to the constrained bones. */ /** Applies the constraint to the constrained bones. */
public void update (Physics physics) { public void update (Skeleton skeleton, Physics physics) {
PhysicsConstraintPose pose = applied; PhysicsConstraintPose pose = applied;
float mix = pose.mix; float mix = pose.mix;
if (mix == 0) return; if (mix == 0) return;
boolean x = data.x > 0, y = data.y > 0, rotateOrShearX = data.rotate > 0 || data.shearX > 0, scaleX = data.scaleX > 0; boolean x = data.x > 0, y = data.y > 0, rotateOrShearX = data.rotate > 0 || data.shearX > 0, scaleX = data.scaleX > 0;
BoneApplied bone = this.bone; BonePose bone = this.bone;
float l = bone.bone.data.length; float l = bone.bone.data.length;
switch (physics) { switch (physics) {
case none: case none:
return; return;
case reset: case reset:
reset(); reset(skeleton);
// Fall through. // Fall through.
case update: case update:
Skeleton skeleton = this.skeleton;
float delta = Math.max(skeleton.time - lastTime, 0); float delta = Math.max(skeleton.time - lastTime, 0);
remaining += delta; remaining += delta;
lastTime = skeleton.time; lastTime = skeleton.time;
@ -266,59 +252,18 @@ public class PhysicsConstraint implements Constrained, Update {
tx = l * bone.a; tx = l * bone.a;
ty = l * bone.c; ty = l * bone.c;
} }
bone.updateLocalTransform(); bone.updateLocalTransform(skeleton);
} }
/** The physics constraint's setup pose data. */ public void sort () {
public PhysicsConstraintData getData () {
return data;
}
public Skeleton getSkeleton () {
return skeleton;
} }
/** The bone constrained by this physics constraint. */ /** The bone constrained by this physics constraint. */
public BoneApplied getBone () { public BonePose getBone () {
return bone; return bone;
} }
public void setBone (BoneApplied bone) { public void setBone (BonePose bone) {
this.bone = bone; this.bone = bone;
} }
public PhysicsConstraintPose getPose () {
return pose;
}
public PhysicsConstraintPose getAppliedPose () {
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.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see ConstraintData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return active;
}
public String toString () {
return data.name;
}
} }

View File

@ -32,18 +32,13 @@ package com.esotericsoftware.spine;
/** 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 PhysicsConstraintData extends ConstraintData { public class PhysicsConstraintData extends PosedData<PhysicsConstraintPose> {
final PhysicsConstraintPose setup = new PhysicsConstraintPose();
BoneData bone; BoneData bone;
float x, y, rotate, scaleX, shearX, limit, step; float x, y, rotate, scaleX, shearX, limit, step;
boolean inertiaGlobal, strengthGlobal, dampingGlobal, massGlobal, windGlobal, gravityGlobal, mixGlobal; boolean inertiaGlobal, strengthGlobal, dampingGlobal, massGlobal, windGlobal, gravityGlobal, mixGlobal;
public PhysicsConstraintData (String name) { public PhysicsConstraintData (String name) {
super(name); super(name, new PhysicsConstraintPose());
}
public PhysicsConstraintPose getSetupPose () {
return setup;
} }
/** The bone constrained by this physics constraint. */ /** The bone constrained by this physics constraint. */

View File

@ -30,7 +30,7 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
/** Stores a pose for a physics constraint. */ /** Stores a pose for a physics constraint. */
public class PhysicsConstraintPose { public class PhysicsConstraintPose implements Pose<PhysicsConstraintPose> {
float inertia, strength, damping, massInverse, wind, gravity, mix; float inertia, strength, damping, massInverse, wind, gravity, mix;
public void set (PhysicsConstraintPose pose) { public void set (PhysicsConstraintPose pose) {

View File

@ -0,0 +1,6 @@
package com.esotericsoftware.spine;
public interface Pose<P> {
public void set (P pose);
}

View File

@ -0,0 +1,51 @@
package com.esotericsoftware.spine;
abstract public class Posed<D extends PosedData<P>, P extends Pose, A extends P> {
final D data;
final P pose;
final A constrained;
A applied;
public Posed (D data, P pose, A constrained) {
if (data == null) throw new IllegalArgumentException("data cannot be null.");
this.data = data;
this.pose = pose;
this.constrained = constrained;
applied = (A)pose;
setupPose();
}
public void setupPose () {
pose.set(data.setup);
}
/** The constraint's setup pose data. */
public D getData () {
return data;
}
public P getPose () {
return pose;
}
public A getAppliedPose () {
return applied;
}
public A getConstrainedPose () {
return constrained;
}
public void setConstrained (boolean constrained) {
applied = constrained ? this.constrained : (A)pose;
}
public void resetAppliedPose () {
applied.set(pose);
}
public String toString () {
return data.name;
}
}

View File

@ -0,0 +1,21 @@
package com.esotericsoftware.spine;
abstract public class PosedActive<D extends PosedData<P>, P extends Pose, A extends P> extends Posed<D, P, A> {
boolean active;
public PosedActive (D data, P pose, A constrained) {
super(data, pose, constrained);
}
/** 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.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see PosedData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return active;
}
}

View File

@ -29,15 +29,26 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
/** The base class for all constraint datas. */ /** The base class for all constrained datas. */
abstract public class ConstraintData { abstract public class PosedData<P extends Pose> {
final String name; final String name;
int order; final P setup;
boolean skinRequired; boolean skinRequired;
public ConstraintData (String name) { int order; // BOZO - Remove order.
public int getOrder () {
return order;
}
public void setOrder (int order) {
this.order = order;
}
public PosedData (String name, P setup) {
if (name == null) throw new IllegalArgumentException("name cannot be null."); if (name == null) throw new IllegalArgumentException("name cannot be null.");
this.name = name; this.name = name;
this.setup = setup;
} }
/** The constraint's name, which is unique across all constraints in the skeleton of the same type. */ /** The constraint's name, which is unique across all constraints in the skeleton of the same type. */
@ -45,14 +56,8 @@ abstract public class ConstraintData {
return name; return name;
} }
/** The ordinal of this constraint for the order a skeleton's constraints will be applied by public P getSetupPose () {
* {@link Skeleton#updateWorldTransform(Physics)}. */ return setup;
public int getOrder () {
return order;
}
public void setOrder (int order) {
this.order = order;
} }
/** When true, {@link Skeleton#updateWorldTransform(Physics)} only updates this constraint if the {@link Skeleton#getSkin()} /** When true, {@link Skeleton#updateWorldTransform(Physics)} only updates this constraint if the {@link Skeleton#getSkin()}

View File

@ -64,7 +64,7 @@ public class Skeleton {
final Array<PathConstraint> pathConstraints; final Array<PathConstraint> pathConstraints;
final Array<PhysicsConstraint> physicsConstraints; final Array<PhysicsConstraint> physicsConstraints;
final Array updateCache = new Array(); final Array updateCache = new Array();
final Array<Constrained> resetCache = new Array(); final Array<Posed> 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;
@ -78,10 +78,10 @@ public class Skeleton {
for (BoneData boneData : data.bones) { for (BoneData boneData : data.bones) {
Bone bone; Bone bone;
if (boneData.parent == null) if (boneData.parent == null)
bone = new Bone(boneData, this, null); bone = new Bone(boneData, null);
else { else {
var parent = (Bone)bones[boneData.parent.index]; var parent = (Bone)bones[boneData.parent.index];
bone = new Bone(boneData, this, parent); bone = new Bone(boneData, parent);
parent.children.add(bone); parent.children.add(bone);
} }
this.bones.add(bone); this.bones.add(bone);
@ -97,7 +97,7 @@ public class Skeleton {
sliders = new Array(data.sliders.size); sliders = new Array(data.sliders.size);
for (SliderData constraint : data.sliders) for (SliderData constraint : data.sliders)
sliders.add(new Slider(constraint, this)); sliders.add(new Slider(constraint));
ikConstraints = new Array(data.ikConstraints.size); ikConstraints = new Array(data.ikConstraints.size);
for (IkConstraintData constraint : data.ikConstraints) for (IkConstraintData constraint : data.ikConstraints)
@ -129,10 +129,10 @@ public class Skeleton {
for (Bone bone : skeleton.bones) { for (Bone bone : skeleton.bones) {
Bone newBone; Bone newBone;
if (bone.parent == null) if (bone.parent == null)
newBone = new Bone(bone, this, null); newBone = new Bone(bone, null);
else { else {
Bone parent = bones.get(bone.parent.data.index); Bone parent = bones.get(bone.parent.data.index);
newBone = new Bone(bone, this, parent); newBone = new Bone(bone, parent);
parent.children.add(newBone); parent.children.add(newBone);
} }
bones.add(newBone); bones.add(newBone);
@ -150,7 +150,7 @@ public class Skeleton {
sliders = new Array(skeleton.sliders.size); sliders = new Array(skeleton.sliders.size);
for (Slider constraint : skeleton.sliders) for (Slider constraint : skeleton.sliders)
sliders.add(new Slider(constraint, skeleton)); sliders.add(new Slider(constraint));
ikConstraints = new Array(skeleton.ikConstraints.size); ikConstraints = new Array(skeleton.ikConstraints.size);
for (IkConstraint constraint : skeleton.ikConstraints) for (IkConstraint constraint : skeleton.ikConstraints)
@ -276,7 +276,7 @@ public class Skeleton {
sortBone(constraint.target); sortBone(constraint.target);
Array<BoneApplied> constrained = constraint.bones; Array<BonePose> constrained = constraint.bones;
Bone parent = constrained.first().bone; Bone parent = constrained.first().bone;
sortBone(parent); sortBone(parent);
resetCache(parent); resetCache(parent);
@ -306,14 +306,14 @@ public class Skeleton {
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 = ((BonePose)constrained[i]).bone;
resetCache(child); 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++) {
Bone bone = ((BoneApplied)constrained[i]).bone; Bone bone = ((BonePose)constrained[i]).bone;
resetCache(bone); resetCache(bone);
sortBone(bone); sortBone(bone);
} }
@ -322,9 +322,9 @@ public class Skeleton {
updateCache.add(constraint); updateCache.add(constraint);
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
sortReset(((BoneApplied)constrained[i]).bone.children); sortReset(((BonePose)constrained[i]).bone.children);
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
((BoneApplied)constrained[i]).bone.sorted = true; ((BonePose)constrained[i]).bone.sorted = true;
} }
private void sortPathConstraint (PathConstraint constraint) { private void sortPathConstraint (PathConstraint constraint) {
@ -344,7 +344,7 @@ 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++) {
Bone bone = ((BoneApplied)constrained[i]).bone; Bone bone = ((BonePose)constrained[i]).bone;
resetCache(bone); resetCache(bone);
sortBone(bone); sortBone(bone);
} }
@ -352,9 +352,9 @@ public class Skeleton {
updateCache.add(constraint); updateCache.add(constraint);
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
sortReset(((BoneApplied)constrained[i]).bone.children); sortReset(((BonePose)constrained[i]).bone.children);
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
((BoneApplied)constrained[i]).bone.sorted = true; ((BonePose)constrained[i]).bone.sorted = true;
} }
private void sortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { private void sortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
@ -423,7 +423,7 @@ public class Skeleton {
if (timelines[i] instanceof BoneTimeline boneTimeline) sortBone((Bone)bones[boneTimeline.getBoneIndex()]); if (timelines[i] instanceof BoneTimeline boneTimeline) sortBone((Bone)bones[boneTimeline.getBoneIndex()]);
} }
private void resetCache (Constrained object) { private void resetCache (Posed object) {
if (!resetCache.contains(object, true)) { if (!resetCache.contains(object, true)) {
resetCache.add(object); resetCache.add(object);
object.setConstrained(true); object.setConstrained(true);
@ -455,11 +455,11 @@ public class Skeleton {
public void updateWorldTransform (Physics physics) { public void updateWorldTransform (Physics physics) {
Object[] resetCache = this.resetCache.items; Object[] resetCache = this.resetCache.items;
for (int i = 0, n = this.resetCache.size; i < n; i++) for (int i = 0, n = this.resetCache.size; i < n; i++)
((Constrained)resetCache[i]).resetAppliedPose(); ((Posed)resetCache[i]).resetAppliedPose();
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++)
((Update)updateCache[i]).update(physics); ((Update)updateCache[i]).update(this, 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
@ -467,15 +467,15 @@ public class Skeleton {
* <p> * <p>
* 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, BoneApplied parent) { public void updateWorldTransform (Physics physics, BonePose parent) {
if (parent == null) throw new IllegalArgumentException("parent cannot be null."); if (parent == null) throw new IllegalArgumentException("parent cannot be null.");
Object[] resetCache = this.resetCache.items; Object[] resetCache = this.resetCache.items;
for (int i = 0, n = this.resetCache.size; i < n; i++) for (int i = 0, n = this.resetCache.size; i < n; i++)
((Constrained)resetCache[i]).resetAppliedPose(); ((Posed)resetCache[i]).resetAppliedPose();
// 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; BonePose rootBone = getRootBone().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;
rootBone.worldX = pa * x + pb * y + parent.worldX; rootBone.worldX = pa * x + pb * y + parent.worldX;
rootBone.worldY = pc * x + pd * y + parent.worldY; rootBone.worldY = pc * x + pd * y + parent.worldY;
@ -495,7 +495,7 @@ public class Skeleton {
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 = (Update)updateCache[i]; var updatable = (Update)updateCache[i];
if (updatable != rootBone) updatable.update(physics); if (updatable != rootBone) updatable.update(this, physics);
} }
} }
@ -804,10 +804,10 @@ public class Skeleton {
} else if (attachment instanceof MeshAttachment mesh) { } else if (attachment instanceof MeshAttachment mesh) {
verticesLength = mesh.getWorldVerticesLength(); verticesLength = mesh.getWorldVerticesLength();
vertices = temp.setSize(verticesLength); vertices = temp.setSize(verticesLength);
mesh.computeWorldVertices(slot, 0, verticesLength, vertices, 0, 2); mesh.computeWorldVertices(this, slot, 0, verticesLength, vertices, 0, 2);
triangles = mesh.getTriangles(); triangles = mesh.getTriangles();
} else if (attachment instanceof ClippingAttachment clip && clipper != null) { } else if (attachment instanceof ClippingAttachment clip && clipper != null) {
clipper.clipStart(slot, clip); clipper.clipStart(this, slot, clip);
continue; continue;
} }
if (vertices != null) { if (vertices != null) {

View File

@ -217,7 +217,7 @@ public class SkeletonBinary extends SkeletonLoader {
String name = input.readString(); String name = input.readString();
BoneData parent = i == 0 ? null : (BoneData)bones[input.readInt(true)]; BoneData parent = i == 0 ? null : (BoneData)bones[input.readInt(true)];
var data = new BoneData(i, name, parent); var data = new BoneData(i, name, parent);
BonePose setup = data.setup; BoneLocal setup = data.setup;
setup.rotation = input.readFloat(); setup.rotation = input.readFloat();
setup.x = input.readFloat() * scale; setup.x = input.readFloat() * scale;
setup.y = input.readFloat() * scale; setup.y = input.readFloat() * scale;
@ -493,16 +493,16 @@ public class SkeletonBinary extends SkeletonLoader {
items = skeletonData.ikConstraints.items; items = skeletonData.ikConstraints.items;
for (int i = 0, n = input.readInt(true); i < n; i++) for (int i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.add((ConstraintData)items[input.readInt(true)]); skin.constraints.add((PosedData)items[input.readInt(true)]);
items = skeletonData.transformConstraints.items; items = skeletonData.transformConstraints.items;
for (int i = 0, n = input.readInt(true); i < n; i++) for (int i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.add((ConstraintData)items[input.readInt(true)]); skin.constraints.add((PosedData)items[input.readInt(true)]);
items = skeletonData.pathConstraints.items; items = skeletonData.pathConstraints.items;
for (int i = 0, n = input.readInt(true); i < n; i++) for (int i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.add((ConstraintData)items[input.readInt(true)]); skin.constraints.add((PosedData)items[input.readInt(true)]);
items = skeletonData.physicsConstraints.items; items = skeletonData.physicsConstraints.items;
for (int i = 0, n = input.readInt(true); i < n; i++) for (int i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.add((ConstraintData)items[input.readInt(true)]); skin.constraints.add((PosedData)items[input.readInt(true)]);
skin.constraints.shrink(); skin.constraints.shrink();
slotCount = input.readInt(true); slotCount = input.readInt(true);

View File

@ -73,7 +73,7 @@ public class SkeletonBounds {
FloatArray polygon = polygonPool.obtain(); FloatArray polygon = polygonPool.obtain();
polygons.add(polygon); polygons.add(polygon);
boundingBox.computeWorldVertices(slot, 0, boundingBox.getWorldVerticesLength(), boundingBox.computeWorldVertices(skeleton, slot, 0, boundingBox.getWorldVerticesLength(),
polygon.setSize(boundingBox.getWorldVerticesLength()), 0, 2); polygon.setSize(boundingBox.getWorldVerticesLength()), 0, 2);
} }
} }

View File

@ -174,7 +174,7 @@ public class SkeletonJson extends SkeletonLoader {
} }
var data = new BoneData(skeletonData.bones.size, boneMap.getString("name"), parent); var data = new BoneData(skeletonData.bones.size, boneMap.getString("name"), parent);
data.length = boneMap.getFloat("length", 0) * scale; data.length = boneMap.getFloat("length", 0) * scale;
BonePose setup = data.setup; BoneLocal setup = data.setup;
setup.x = boneMap.getFloat("x", 0) * scale; setup.x = boneMap.getFloat("x", 0) * scale;
setup.y = boneMap.getFloat("y", 0) * scale; setup.y = boneMap.getFloat("y", 0) * scale;
setup.rotation = boneMap.getFloat("rotation", 0); setup.rotation = boneMap.getFloat("rotation", 0);

View File

@ -165,14 +165,14 @@ public class SkeletonRenderer {
int count = mesh.getWorldVerticesLength(); int count = mesh.getWorldVerticesLength();
verticesLength = (count >> 1) * 5; verticesLength = (count >> 1) * 5;
vertices = this.vertices.setSize(verticesLength); vertices = this.vertices.setSize(verticesLength);
mesh.computeWorldVertices(slot, 0, count, vertices, 0, 5); mesh.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 5);
triangles = mesh.getTriangles(); triangles = mesh.getTriangles();
texture = mesh.getRegion().getTexture(); texture = mesh.getRegion().getTexture();
uvs = mesh.getUVs(); uvs = mesh.getUVs();
color = mesh.getColor(); color = mesh.getColor();
} else if (attachment instanceof ClippingAttachment clip) { } else if (attachment instanceof ClippingAttachment clip) {
clipper.clipStart(slot, clip); clipper.clipStart(skeleton, slot, clip);
continue; continue;
} else if (attachment instanceof SkeletonAttachment skeletonAttachment) { } else if (attachment instanceof SkeletonAttachment skeletonAttachment) {
@ -260,14 +260,14 @@ public class SkeletonRenderer {
int count = mesh.getWorldVerticesLength(); int count = mesh.getWorldVerticesLength();
verticesLength = count * 3; verticesLength = count * 3;
vertices = this.vertices.setSize(verticesLength); vertices = this.vertices.setSize(verticesLength);
mesh.computeWorldVertices(slot, 0, count, vertices, 0, 6); mesh.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 6);
triangles = mesh.getTriangles(); triangles = mesh.getTriangles();
texture = mesh.getRegion().getTexture(); texture = mesh.getRegion().getTexture();
uvs = mesh.getUVs(); uvs = mesh.getUVs();
color = mesh.getColor(); color = mesh.getColor();
} else if (attachment instanceof ClippingAttachment clip) { } else if (attachment instanceof ClippingAttachment clip) {
clipper.clipStart(slot, clip); clipper.clipStart(skeleton, slot, clip);
continue; continue;
} else if (attachment instanceof SkeletonAttachment skeletonAttachment) { } else if (attachment instanceof SkeletonAttachment skeletonAttachment) {

View File

@ -95,7 +95,7 @@ public class SkeletonRendererDebug {
shapes.setColor(boneOriginColor); shapes.setColor(boneOriginColor);
} else } else
shapes.setColor(boneLineColor); shapes.setColor(boneLineColor);
BoneApplied applied = bone.applied; BonePose applied = bone.applied;
float x = length * applied.a + applied.worldX; float x = length * applied.a + applied.worldX;
float y = length * applied.c + applied.worldY; float y = length * applied.c + applied.worldY;
shapes.rectLine(applied.worldX, applied.worldY, x, y, width * scale); shapes.rectLine(applied.worldX, applied.worldY, x, y, width * scale);
@ -140,7 +140,7 @@ public class SkeletonRendererDebug {
if (!slot.bone.active) continue; if (!slot.bone.active) continue;
if (!(slot.pose.attachment instanceof MeshAttachment mesh)) continue; if (!(slot.pose.attachment instanceof MeshAttachment mesh)) continue;
float[] vertices = this.vertices.setSize(mesh.getWorldVerticesLength()); float[] vertices = this.vertices.setSize(mesh.getWorldVerticesLength());
mesh.computeWorldVertices(slot, 0, mesh.getWorldVerticesLength(), vertices, 0, 2); mesh.computeWorldVertices(skeleton, slot, 0, mesh.getWorldVerticesLength(), vertices, 0, 2);
short[] triangles = mesh.getTriangles(); short[] triangles = mesh.getTriangles();
int hullLength = mesh.getHullLength(); int hullLength = mesh.getHullLength();
if (drawMeshTriangles) { if (drawMeshTriangles) {
@ -187,7 +187,7 @@ public class SkeletonRendererDebug {
if (!(slot.pose.attachment instanceof ClippingAttachment clip)) continue; if (!(slot.pose.attachment instanceof ClippingAttachment clip)) continue;
int nn = clip.getWorldVerticesLength(); int nn = clip.getWorldVerticesLength();
float[] vertices = this.vertices.setSize(nn); float[] vertices = this.vertices.setSize(nn);
clip.computeWorldVertices(slot, 0, nn, vertices, 0, 2); clip.computeWorldVertices(skeleton, slot, 0, nn, vertices, 0, 2);
shapes.setColor(clip.getColor()); shapes.setColor(clip.getColor());
for (int ii = 2; ii < nn; ii += 2) for (int ii = 2; ii < nn; ii += 2)
shapes.line(vertices[ii - 2], vertices[ii - 1], vertices[ii], vertices[ii + 1]); shapes.line(vertices[ii - 2], vertices[ii - 1], vertices[ii], vertices[ii + 1]);
@ -202,7 +202,7 @@ public class SkeletonRendererDebug {
if (!(slot.pose.attachment instanceof PathAttachment path)) continue; if (!(slot.pose.attachment instanceof PathAttachment path)) continue;
int nn = path.getWorldVerticesLength(); int nn = path.getWorldVerticesLength();
float[] vertices = this.vertices.setSize(nn); float[] vertices = this.vertices.setSize(nn);
path.computeWorldVertices(slot, 0, nn, vertices, 0, 2); path.computeWorldVertices(skeleton, slot, 0, nn, vertices, 0, 2);
Color color = path.getColor(); Color color = path.getColor();
float x1 = vertices[2], y1 = vertices[3], x2 = 0, y2 = 0; float x1 = vertices[2], y1 = vertices[3], x2 = 0, y2 = 0;
if (path.getClosed()) { if (path.getClosed()) {

View File

@ -45,7 +45,7 @@ public class Skin {
final String name; final String name;
final OrderedSet<SkinEntry> attachments = new OrderedSet(); final OrderedSet<SkinEntry> attachments = new OrderedSet();
final Array<BoneData> bones = new Array(0); final Array<BoneData> bones = new Array(0);
final Array<ConstraintData> constraints = new Array(0); final Array<PosedData> constraints = new Array(0);
private final SkinEntry lookup = new SkinEntry(0, "", null); private final SkinEntry lookup = new SkinEntry(0, "", null);
// Nonessential. // Nonessential.
@ -71,7 +71,7 @@ public class Skin {
for (BoneData data : skin.bones) for (BoneData data : skin.bones)
if (!bones.contains(data, true)) bones.add(data); if (!bones.contains(data, true)) bones.add(data);
for (ConstraintData data : skin.constraints) for (PosedData data : skin.constraints)
if (!constraints.contains(data, true)) constraints.add(data); if (!constraints.contains(data, true)) constraints.add(data);
for (SkinEntry entry : skin.attachments.orderedItems()) for (SkinEntry entry : skin.attachments.orderedItems())
@ -86,7 +86,7 @@ public class Skin {
for (BoneData data : skin.bones) for (BoneData data : skin.bones)
if (!bones.contains(data, true)) bones.add(data); if (!bones.contains(data, true)) bones.add(data);
for (ConstraintData data : skin.constraints) for (PosedData data : skin.constraints)
if (!constraints.contains(data, true)) constraints.add(data); if (!constraints.contains(data, true)) constraints.add(data);
for (SkinEntry entry : skin.attachments.orderedItems()) { for (SkinEntry entry : skin.attachments.orderedItems()) {
@ -134,7 +134,7 @@ public class Skin {
return bones; return bones;
} }
public Array<ConstraintData> getConstraints () { public Array<PosedData> getConstraints () {
return constraints; return constraints;
} }

View File

@ -35,70 +35,22 @@ 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 Constrained, Update { public class Slider extends Constraint<SliderData, SliderPose> {
final SliderData data; public Slider (SliderData data) {
final Skeleton skeleton; super(data, new SliderPose(), new SliderPose());
final SliderPose pose = new SliderPose(), constrained = new SliderPose();
SliderPose applied = pose;
boolean active;
public Slider (SliderData 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;
setupPose();
} }
/** Copy constructor. */ /** Copy constructor. */
public Slider (Slider slider, Skeleton skeleton) { public Slider (Slider slider) {
this(slider.data, skeleton); this(slider.data);
pose.set(slider.pose); pose.set(slider.pose);
} }
public void update (Physics physics) { public void update (Skeleton skeleton, Physics physics) {
SliderPose pose = applied; SliderPose pose = applied;
data.animation.apply(skeleton, pose.time, pose.time, false, null, pose.mix, MixBlend.replace, MixDirection.in, true); data.animation.apply(skeleton, pose.time, pose.time, false, null, pose.mix, MixBlend.replace, MixDirection.in, true);
} }
public void setupPose () { public void sort () {
pose.set(data.setup);
}
public SliderPose getPose () {
return pose;
}
public SliderPose getAppliedPose () {
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.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see ConstraintData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return active;
}
public String toString () {
return data.name;
} }
} }

View File

@ -32,16 +32,11 @@ package com.esotericsoftware.spine;
/** 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 SliderData extends ConstraintData { public class SliderData extends PosedData<SliderPose> {
final SliderPose setup = new SliderPose();
Animation animation; Animation animation;
public SliderData (String name) { public SliderData (String name) {
super(name); super(name, new SliderPose());
}
public SliderPose getSetupPose () {
return setup;
} }
public Animation getAnimation () { public Animation getAnimation () {

View File

@ -30,7 +30,7 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
/** Stores a pose for a slider. */ /** Stores a pose for a slider. */
public class SliderPose { public class SliderPose implements Pose<SliderPose> {
float time, mix; float time, mix;
public void set (SliderPose pose) { public void set (SliderPose pose) {

View File

@ -34,17 +34,13 @@ 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 implements Constrained { public class Slot extends Posed<SlotData, SlotPose, SlotPose> {
final SlotData data;
final Bone bone; final Bone bone;
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) {
if (data == null) throw new IllegalArgumentException("slot cannot be null."); super(data, new SlotPose(), new SlotPose());
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
this.data = data;
bone = skeleton.bones.get(data.boneData.index); bone = skeleton.bones.get(data.boneData.index);
if (data.setup.darkColor != null) { if (data.setup.darkColor != null) {
pose.darkColor = new Color(); pose.darkColor = new Color();
@ -55,9 +51,7 @@ public class Slot implements Constrained {
/** Copy constructor. */ /** Copy constructor. */
public Slot (Slot slot, Bone bone) { public Slot (Slot slot, Bone bone) {
if (slot == null) throw new IllegalArgumentException("slot cannot be null."); super(slot.data, new SlotPose(), new SlotPose());
if (bone == null) throw new IllegalArgumentException("bone cannot be null.");
data = slot.data;
this.bone = bone; this.bone = bone;
if (data.setup.darkColor != null) { if (data.setup.darkColor != null) {
pose.darkColor = new Color(); pose.darkColor = new Color();
@ -66,48 +60,8 @@ public class Slot implements Constrained {
pose.set(slot.pose); pose.set(slot.pose);
} }
/** Sets this slot to the setup pose. */
public void setupPose () {
pose.set(data.setup);
if (data.attachmentName != null) pose.setAttachment(bone.skeleton.getAttachment(data.index, data.attachmentName));
}
/** The slot's setup pose data. */
public SlotData getData () {
return data;
}
public SlotPose getPose () {
return 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. */ /** The bone this slot belongs to. */
public Bone getBone () { public Bone getBone () {
return bone; return bone;
} }
/** The skeleton this slot belongs to. */
public Skeleton getSkeleton () {
return bone.skeleton;
}
public String toString () {
return data.name;
}
} }

View File

@ -32,11 +32,9 @@ package com.esotericsoftware.spine;
import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.Null;
/** Stores the setup pose for a {@link Slot}. */ /** Stores the setup pose for a {@link Slot}. */
public class SlotData { public class SlotData extends PosedData<SlotPose> {
final int index; final int index;
final String name;
final BoneData boneData; final BoneData boneData;
final SlotPose setup = new SlotPose();
@Null String attachmentName; @Null String attachmentName;
BlendMode blendMode; BlendMode blendMode;
@ -44,11 +42,10 @@ public class SlotData {
boolean visible = true; boolean visible = true;
public SlotData (int index, String name, BoneData boneData) { public SlotData (int index, String name, BoneData boneData) {
super(name, new SlotPose());
if (index < 0) throw new IllegalArgumentException("index must be >= 0."); if (index < 0) throw new IllegalArgumentException("index must be >= 0.");
if (name == null) throw new IllegalArgumentException("name cannot be null.");
if (boneData == null) throw new IllegalArgumentException("boneData cannot be null."); if (boneData == null) throw new IllegalArgumentException("boneData cannot be null.");
this.index = index; this.index = index;
this.name = name;
this.boneData = boneData; this.boneData = boneData;
} }
@ -57,20 +54,11 @@ public class SlotData {
return index; return index;
} }
/** The name of the slot, which is unique across all slots in the skeleton. */
public String getName () {
return name;
}
/** The bone this slot belongs to. */ /** The bone this slot belongs to. */
public BoneData getBoneData () { public BoneData getBoneData () {
return boneData; return boneData;
} }
public SlotPose getSetupPose () {
return setup;
}
public void setAttachmentName (@Null String attachmentName) { public void setAttachmentName (@Null String attachmentName) {
this.attachmentName = attachmentName; this.attachmentName = attachmentName;
} }
@ -98,8 +86,4 @@ public class SlotData {
public void setVisible (boolean visible) { public void setVisible (boolean visible) {
this.visible = visible; this.visible = visible;
} }
public String toString () {
return name;
}
} }

View File

@ -41,7 +41,7 @@ import com.esotericsoftware.spine.attachments.VertexAttachment;
/** Stores a slot's pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store state /** Stores a slot's 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 * for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared across
* multiple skeletons. */ * multiple skeletons. */
public class SlotPose { public class SlotPose implements Pose<SlotPose> {
final Color color = new Color(); final Color color = new Color();
@Null Color darkColor; @Null Color darkColor;
@Null Attachment attachment; // Not used in setup pose. @Null Attachment attachment; // Not used in setup pose.
@ -104,7 +104,8 @@ public class SlotPose {
/** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a /** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a
* weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. * weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions.
* <p> * <p>
* See {@link VertexAttachment#computeWorldVertices(Slot, int, int, float[], int, int)} and {@link DeformTimeline}. */ * See {@link VertexAttachment#computeWorldVertices(Skeleton, Slot, int, int, float[], int, int)} and
* {@link DeformTimeline}. */
public FloatArray getDeform () { public FloatArray getDeform () {
return deform; return deform;
} }

View File

@ -40,32 +40,19 @@ 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 Constrained, Update { public class TransformConstraint extends Constraint<TransformConstraintData, TransformConstraintPose> {
final TransformConstraintData data; final Array<BonePose> bones;
final Array<BoneApplied> bones;
Bone source; Bone source;
final TransformConstraintPose pose = new TransformConstraintPose(), constrained = new TransformConstraintPose();
TransformConstraintPose applied = pose;
boolean active;
public TransformConstraint (TransformConstraintData data, Array<BoneApplied> bones, Bone source) {
this.data = data;
this.bones = bones;
this.source = source;
}
public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { public TransformConstraint (TransformConstraintData data, Skeleton skeleton) {
if (data == null) throw new IllegalArgumentException("data cannot be null."); super(data, new TransformConstraintPose(), new TransformConstraintPose());
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
this.data = data;
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).constrained); bones.add(skeleton.bones.get(boneData.index).constrained);
source = skeleton.bones.get(data.source.index); source = skeleton.bones.get(data.source.index);
setupPose();
} }
/** Copy constructor. */ /** Copy constructor. */
@ -74,24 +61,20 @@ public class TransformConstraint implements Constrained, Update {
pose.set(constraint.pose); pose.set(constraint.pose);
} }
public void setupPose () {
pose.set(data.setup);
}
/** Applies the constraint to the constrained bones. */ /** Applies the constraint to the constrained bones. */
public void update (Physics physics) { public void update (Skeleton skeleton, Physics physics) {
TransformConstraintPose pose = applied; TransformConstraintPose pose = applied;
if (pose.mixRotate == 0 && pose.mixX == 0 && pose.mixY == 0 && pose.mixScaleX == 0 && pose.mixScaleY == 0 if (pose.mixRotate == 0 && pose.mixX == 0 && pose.mixY == 0 && pose.mixScaleX == 0 && pose.mixScaleY == 0
&& pose.mixShearY == 0) return; && pose.mixShearY == 0) return;
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.applied; BonePose 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 = (BonePose)bones[i];
if (bone.bone.applied != bone.bone.constrained) System.out.println(); 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];
@ -112,14 +95,17 @@ public class TransformConstraint implements Constrained, Update {
} }
} }
if (localTarget) if (localTarget)
bone.update(null); bone.update(skeleton, null);
else else
bone.updateLocalTransform(); bone.updateLocalTransform(skeleton);
} }
} }
public void sort () {
}
/** The bones that will be modified by this transform constraint. */ /** The bones that will be modified by this transform constraint. */
public Array<BoneApplied> getBones () { public Array<BonePose> getBones () {
return bones; return bones;
} }
@ -132,44 +118,4 @@ public class TransformConstraint implements Constrained, Update {
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;
} }
public TransformConstraintPose getPose () {
return pose;
}
public TransformConstraintPose getAppliedPose () {
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.
* @see Skin#getBones()
* @see Skin#getConstraints()
* @see ConstraintData#getSkinRequired()
* @see Skeleton#updateCache() */
public boolean isActive () {
return active;
}
/** The transform constraint's setup pose data. */
public TransformConstraintData getData () {
return data;
}
public String toString () {
return data.name;
}
} }

View File

@ -36,8 +36,7 @@ import com.badlogic.gdx.utils.Array;
/** Stores the setup pose for a {@link TransformConstraint}. /** Stores the setup pose for a {@link TransformConstraint}.
* <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 TransformConstraintData extends ConstraintData { public class TransformConstraintData extends PosedData<TransformConstraintPose> {
final TransformConstraintPose setup = new TransformConstraintPose();
final Array<BoneData> bones = new Array(); final Array<BoneData> bones = new Array();
BoneData source; BoneData source;
float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
@ -45,11 +44,7 @@ public class TransformConstraintData extends ConstraintData {
final Array<FromProperty> properties = new Array(); final Array<FromProperty> properties = new Array();
public TransformConstraintData (String name) { public TransformConstraintData (String name) {
super(name); super(name, new TransformConstraintPose());
}
public TransformConstraintPose getSetupPose () {
return setup;
} }
/** The bones that will be modified by this transform constraint. */ /** The bones that will be modified by this transform constraint. */
@ -171,7 +166,7 @@ public class TransformConstraintData extends ConstraintData {
public final Array<ToProperty> to = new Array(); public final Array<ToProperty> to = new Array();
/** Reads this property from the specified bone. */ /** Reads this property from the specified bone. */
abstract public float value (TransformConstraintData data, BoneApplied source, boolean local); abstract public float value (TransformConstraintData data, BonePose source, boolean local);
} }
/** Constrained property for a {@link TransformConstraint}. */ /** Constrained property for a {@link TransformConstraint}. */
@ -189,11 +184,11 @@ public class TransformConstraintData extends ConstraintData {
abstract public float mix (TransformConstraintPose pose); abstract public float mix (TransformConstraintPose pose);
/** Applies the value to this property. */ /** Applies the value to this property. */
abstract public void apply (TransformConstraintPose pose, BoneApplied bone, float value, boolean local, boolean additive); abstract public void apply (TransformConstraintPose pose, BonePose bone, float value, boolean local, boolean additive);
} }
static public class FromRotate extends FromProperty { static public class FromRotate extends FromProperty {
public float value (TransformConstraintData data, BoneApplied source, boolean local) { public float value (TransformConstraintData data, BonePose source, boolean local) {
if (local) return source.rotation + data.offsetRotation; if (local) return source.rotation + data.offsetRotation;
float value = atan2(source.c, source.a) * radDeg float value = atan2(source.c, source.a) * radDeg
+ (source.a * source.d - source.b * source.c > 0 ? data.offsetRotation : -data.offsetRotation); + (source.a * source.d - source.b * source.c > 0 ? data.offsetRotation : -data.offsetRotation);
@ -207,7 +202,7 @@ public class TransformConstraintData extends ConstraintData {
return pose.mixRotate; return pose.mixRotate;
} }
public void apply (TransformConstraintPose pose, BoneApplied bone, float value, boolean local, boolean additive) { public void apply (TransformConstraintPose pose, BonePose bone, float value, boolean local, boolean additive) {
if (local) { if (local) {
if (!additive) value -= bone.rotation; if (!additive) value -= bone.rotation;
bone.rotation += value * pose.mixRotate; bone.rotation += value * pose.mixRotate;
@ -230,7 +225,7 @@ public class TransformConstraintData extends ConstraintData {
} }
static public class FromX extends FromProperty { static public class FromX extends FromProperty {
public float value (TransformConstraintData data, BoneApplied source, boolean local) { public float value (TransformConstraintData data, BonePose source, boolean local) {
return local ? source.x + data.offsetX : data.offsetX * source.a + data.offsetY * source.b + source.worldX; return local ? source.x + data.offsetX : data.offsetX * source.a + data.offsetY * source.b + source.worldX;
} }
} }
@ -240,7 +235,7 @@ public class TransformConstraintData extends ConstraintData {
return pose.mixX; return pose.mixX;
} }
public void apply (TransformConstraintPose pose, BoneApplied bone, float value, boolean local, boolean additive) { public void apply (TransformConstraintPose pose, BonePose bone, float value, boolean local, boolean additive) {
if (local) { if (local) {
if (!additive) value -= bone.x; if (!additive) value -= bone.x;
bone.x += value * pose.mixX; bone.x += value * pose.mixX;
@ -252,7 +247,7 @@ public class TransformConstraintData extends ConstraintData {
} }
static public class FromY extends FromProperty { static public class FromY extends FromProperty {
public float value (TransformConstraintData data, BoneApplied source, boolean local) { public float value (TransformConstraintData data, BonePose source, boolean local) {
return local ? source.y + data.offsetY : data.offsetX * source.c + data.offsetY * source.d + source.worldY; return local ? source.y + data.offsetY : data.offsetX * source.c + data.offsetY * source.d + source.worldY;
} }
} }
@ -262,7 +257,7 @@ public class TransformConstraintData extends ConstraintData {
return pose.mixY; return pose.mixY;
} }
public void apply (TransformConstraintPose pose, BoneApplied bone, float value, boolean local, boolean additive) { public void apply (TransformConstraintPose pose, BonePose bone, float value, boolean local, boolean additive) {
if (local) { if (local) {
if (!additive) value -= bone.y; if (!additive) value -= bone.y;
bone.y += value * pose.mixY; bone.y += value * pose.mixY;
@ -274,7 +269,7 @@ public class TransformConstraintData extends ConstraintData {
} }
static public class FromScaleX extends FromProperty { static public class FromScaleX extends FromProperty {
public float value (TransformConstraintData data, BoneApplied source, boolean local) { public float value (TransformConstraintData data, BonePose source, boolean local) {
return (local ? source.scaleX : (float)Math.sqrt(source.a * source.a + source.c * source.c)) + data.offsetScaleX; return (local ? source.scaleX : (float)Math.sqrt(source.a * source.a + source.c * source.c)) + data.offsetScaleX;
} }
} }
@ -284,7 +279,7 @@ public class TransformConstraintData extends ConstraintData {
return pose.mixScaleX; return pose.mixScaleX;
} }
public void apply (TransformConstraintPose pose, BoneApplied bone, float value, boolean local, boolean additive) { public void apply (TransformConstraintPose pose, BonePose bone, float value, boolean local, boolean additive) {
if (local) { if (local) {
if (additive) if (additive)
bone.scaleX *= 1 + ((value - 1) * pose.mixScaleX); bone.scaleX *= 1 + ((value - 1) * pose.mixScaleX);
@ -305,7 +300,7 @@ public class TransformConstraintData extends ConstraintData {
} }
static public class FromScaleY extends FromProperty { static public class FromScaleY extends FromProperty {
public float value (TransformConstraintData data, BoneApplied source, boolean local) { public float value (TransformConstraintData data, BonePose source, boolean local) {
return (local ? source.scaleY : (float)Math.sqrt(source.b * source.b + source.d * source.d)) + data.offsetScaleY; return (local ? source.scaleY : (float)Math.sqrt(source.b * source.b + source.d * source.d)) + data.offsetScaleY;
} }
} }
@ -315,7 +310,7 @@ public class TransformConstraintData extends ConstraintData {
return pose.mixScaleY; return pose.mixScaleY;
} }
public void apply (TransformConstraintPose pose, BoneApplied bone, float value, boolean local, boolean additive) { public void apply (TransformConstraintPose pose, BonePose bone, float value, boolean local, boolean additive) {
if (local) { if (local) {
if (additive) if (additive)
bone.scaleY *= 1 + ((value - 1) * pose.mixScaleY); bone.scaleY *= 1 + ((value - 1) * pose.mixScaleY);
@ -336,7 +331,7 @@ public class TransformConstraintData extends ConstraintData {
} }
static public class FromShearY extends FromProperty { static public class FromShearY extends FromProperty {
public float value (TransformConstraintData data, BoneApplied source, boolean local) { public float value (TransformConstraintData data, BonePose source, boolean local) {
return (local ? source.shearY : (atan2(source.d, source.b) - atan2(source.c, source.a)) * radDeg - 90) return (local ? source.shearY : (atan2(source.d, source.b) - atan2(source.c, source.a)) * radDeg - 90)
+ data.offsetShearY; + data.offsetShearY;
} }
@ -347,7 +342,7 @@ public class TransformConstraintData extends ConstraintData {
return pose.mixShearY; return pose.mixShearY;
} }
public void apply (TransformConstraintPose pose, BoneApplied bone, float value, boolean local, boolean additive) { public void apply (TransformConstraintPose pose, BonePose bone, float value, boolean local, boolean additive) {
if (local) { if (local) {
if (!additive) value -= bone.shearY; if (!additive) value -= bone.shearY;
bone.shearY += value * pose.mixShearY; bone.shearY += value * pose.mixShearY;

View File

@ -30,7 +30,7 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
/** Stores a pose for a transform constraint. */ /** Stores a pose for a transform constraint. */
public class TransformConstraintPose { public class TransformConstraintPose implements Pose<TransformConstraintPose> {
float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY;
public void set (TransformConstraintPose pose) { public void set (TransformConstraintPose pose) {

View File

@ -32,5 +32,5 @@ 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 Update { 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 (Skeleton skeleton, Physics physics);
} }

View File

@ -36,6 +36,7 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Skeleton;
import com.esotericsoftware.spine.Slot; import com.esotericsoftware.spine.Slot;
/** An attachment that displays a textured mesh. A mesh has hull vertices and internal vertices within the hull. Holes are not /** An attachment that displays a textured mesh. A mesh has hull vertices and internal vertices within the hull. Holes are not
@ -164,9 +165,10 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion
} }
/** If the attachment has a {@link #sequence}, the region may be changed. */ /** If the attachment has a {@link #sequence}, the region may be changed. */
public void computeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride) { public void computeWorldVertices (Skeleton skeleton, Slot slot, int start, int count, float[] worldVertices, int offset,
int stride) {
if (sequence != null) sequence.apply(slot.getAppliedPose(), this); if (sequence != null) sequence.apply(slot.getAppliedPose(), this);
super.computeWorldVertices(slot, start, count, worldVertices, offset, stride); super.computeWorldVertices(skeleton, slot, start, count, worldVertices, offset, stride);
} }
/** Triplets of vertex indices which describe the mesh's triangulation. */ /** Triplets of vertex indices which describe the mesh's triangulation. */

View File

@ -34,7 +34,7 @@ import static com.esotericsoftware.spine.utils.SpineUtils.*;
import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.esotericsoftware.spine.BoneApplied; import com.esotericsoftware.spine.BonePose;
/** An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be /** An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be
* used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a * used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a
@ -90,13 +90,13 @@ public class PointAttachment extends Attachment {
return color; return color;
} }
public Vector2 computeWorldPosition (BoneApplied bone, Vector2 point) { public Vector2 computeWorldPosition (BonePose bone, Vector2 point) {
point.x = x * bone.getA() + y * bone.getB() + bone.getWorldX(); point.x = x * bone.getA() + y * bone.getB() + bone.getWorldX();
point.y = x * bone.getC() + y * bone.getD() + bone.getWorldY(); point.y = x * bone.getC() + y * bone.getD() + bone.getWorldY();
return point; return point;
} }
public float computeWorldRotation (BoneApplied bone) { public float computeWorldRotation (BonePose bone) {
float r = rotation * degRad, cos = cos(r), sin = sin(r); float r = rotation * degRad, cos = cos(r), sin = sin(r);
float x = cos * bone.getA() + sin * bone.getB(); float x = cos * bone.getA() + sin * bone.getB();
float y = cos * bone.getC() + sin * bone.getD(); float y = cos * bone.getC() + sin * bone.getD();

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.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.BoneApplied; import com.esotericsoftware.spine.BonePose;
import com.esotericsoftware.spine.Slot; import com.esotericsoftware.spine.Slot;
/** An attachment that displays a textured quadrilateral. /** An attachment that displays a textured quadrilateral.
@ -179,7 +179,7 @@ public class RegionAttachment extends Attachment implements HasTextureRegion {
if (sequence != null) sequence.apply(slot.getAppliedPose(), this); if (sequence != null) sequence.apply(slot.getAppliedPose(), this);
float[] vertexOffset = this.offset; float[] vertexOffset = this.offset;
BoneApplied bone = slot.getBone().getAppliedPose(); BonePose bone = slot.getBone().getAppliedPose();
float x = bone.getWorldX(), y = bone.getWorldY(); float x = bone.getWorldX(), y = bone.getWorldY();
float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD(); float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD();
float offsetX, offsetY; float offsetX, offsetY;

View File

@ -35,7 +35,7 @@ import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Bone; import com.esotericsoftware.spine.Bone;
import com.esotericsoftware.spine.BoneApplied; import com.esotericsoftware.spine.BonePose;
import com.esotericsoftware.spine.Skeleton; import com.esotericsoftware.spine.Skeleton;
import com.esotericsoftware.spine.Slot; import com.esotericsoftware.spine.Slot;
import com.esotericsoftware.spine.SlotPose; import com.esotericsoftware.spine.SlotPose;
@ -86,14 +86,16 @@ abstract public class VertexAttachment extends Attachment {
* <code>stride</code> / 2. * <code>stride</code> / 2.
* @param offset The <code>worldVertices</code> index to begin writing values. * @param offset The <code>worldVertices</code> index to begin writing values.
* @param stride The number of <code>worldVertices</code> entries between the value pairs written. */ * @param stride The number of <code>worldVertices</code> entries between the value pairs written. */
public void computeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride) { public void computeWorldVertices (Skeleton skeleton, Slot slot, int start, int count, float[] worldVertices, int offset,
int stride) {
count = offset + (count >> 1) * stride; count = offset + (count >> 1) * stride;
FloatArray deformArray = slot.getAppliedPose().getDeform(); FloatArray deformArray = slot.getAppliedPose().getDeform();
float[] vertices = this.vertices; float[] vertices = this.vertices;
int[] bones = this.bones; int[] bones = this.bones;
if (bones == null) { if (bones == null) {
if (deformArray.size > 0) vertices = deformArray.items; if (deformArray.size > 0) vertices = deformArray.items;
BoneApplied bone = slot.getBone().getAppliedPose(); BonePose bone = slot.getBone().getAppliedPose();
float x = bone.getWorldX(), y = bone.getWorldY(); float x = bone.getWorldX(), y = bone.getWorldY();
float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD(); float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD();
for (int v = start, w = offset; w < count; v += 2, w += stride) { for (int v = start, w = offset; w < count; v += 2, w += stride) {
@ -109,14 +111,14 @@ abstract public class VertexAttachment extends Attachment {
v += n + 1; v += n + 1;
skip += n; skip += n;
} }
Object[] skeletonBones = slot.getSkeleton().getBones().items; Object[] skeletonBones = skeleton.getBones().items;
if (deformArray.size == 0) { if (deformArray.size == 0) {
for (int w = offset, b = skip * 3; w < count; w += stride) { for (int w = offset, b = skip * 3; w < count; w += stride) {
float wx = 0, wy = 0; float wx = 0, wy = 0;
int n = bones[v++]; int n = bones[v++];
n += v; n += v;
for (; v < n; v++, b += 3) { for (; v < n; v++, b += 3) {
BoneApplied bone = ((Bone)skeletonBones[bones[v]]).getAppliedPose(); BonePose bone = ((Bone)skeletonBones[bones[v]]).getAppliedPose();
float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2]; float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight; wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight;
wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight; wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight;
@ -131,7 +133,7 @@ abstract public class VertexAttachment extends Attachment {
int n = bones[v++]; int n = bones[v++];
n += v; n += v;
for (; v < n; v++, b += 3, f += 2) { for (; v < n; v++, b += 3, f += 2) {
BoneApplied bone = ((Bone)skeletonBones[bones[v]]).getAppliedPose(); BonePose bone = ((Bone)skeletonBones[bones[v]]).getAppliedPose();
float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2]; float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight; wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight;
wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight; wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight;
@ -166,7 +168,7 @@ abstract public class VertexAttachment extends Attachment {
} }
/** The maximum number of world vertex values that can be output by /** The maximum number of world vertex values that can be output by
* {@link #computeWorldVertices(Slot, int, int, float[], int, int)} using the <code>count</code> parameter. */ * {@link #computeWorldVertices(Skeleton, Slot, int, int, float[], int, int)} using the <code>count</code> parameter. */
public int getWorldVerticesLength () { public int getWorldVerticesLength () {
return worldVerticesLength; return worldVerticesLength;
} }

View File

@ -33,6 +33,7 @@ import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.ShortArray; import com.badlogic.gdx.utils.ShortArray;
import com.esotericsoftware.spine.Skeleton;
import com.esotericsoftware.spine.Slot; import com.esotericsoftware.spine.Slot;
import com.esotericsoftware.spine.attachments.ClippingAttachment; import com.esotericsoftware.spine.attachments.ClippingAttachment;
@ -48,14 +49,14 @@ public class SkeletonClipping {
private ClippingAttachment clipAttachment; private ClippingAttachment clipAttachment;
private Array<FloatArray> clippingPolygons; private Array<FloatArray> clippingPolygons;
public void clipStart (Slot slot, ClippingAttachment clip) { public void clipStart (Skeleton skeleton, Slot slot, ClippingAttachment clip) {
if (clipAttachment != null) return; if (clipAttachment != null) return;
int n = clip.getWorldVerticesLength(); int n = clip.getWorldVerticesLength();
if (n < 6) return; if (n < 6) return;
clipAttachment = clip; clipAttachment = clip;
float[] vertices = clippingPolygon.setSize(n); float[] vertices = clippingPolygon.setSize(n);
clip.computeWorldVertices(slot, 0, n, vertices, 0, 2); clip.computeWorldVertices(skeleton, slot, 0, n, vertices, 0, 2);
makeClockwise(clippingPolygon); makeClockwise(clippingPolygon);
ShortArray triangles = triangulator.triangulate(clippingPolygon); ShortArray triangles = triangulator.triangulate(clippingPolygon);
clippingPolygons = triangulator.decompose(clippingPolygon, triangles); clippingPolygons = triangulator.decompose(clippingPolygon, triangles);