mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
[libgdx] Added Posed and other base classes. Renamed BoneLocal/Pose. Removed skeleton fields, prefer passing.
This commit is contained in:
parent
a8c081dbb1
commit
fff1606b6d
@ -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;
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 ();
|
|
||||||
}
|
|
||||||
@ -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 ();
|
||||||
|
}
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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. */
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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. */
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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. */
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
package com.esotericsoftware.spine;
|
||||||
|
|
||||||
|
public interface Pose<P> {
|
||||||
|
public void set (P pose);
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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()}
|
||||||
@ -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) {
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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()) {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 () {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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. */
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user