mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-09 16:48:43 +08:00
[libgdx] Optimized setting the applied pose.
This commit is contained in:
parent
c8069fa852
commit
a8c081dbb1
@ -278,7 +278,7 @@ public class AnimationState {
|
||||
var slot = (Slot)slots[i];
|
||||
if (slot.attachmentState == setupState) {
|
||||
String attachmentName = slot.data.attachmentName;
|
||||
slot.getPose().setAttachment(attachmentName == null ? null : skeleton.getAttachment(slot.data.index, attachmentName));
|
||||
slot.pose.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slot.data.index, attachmentName));
|
||||
}
|
||||
}
|
||||
unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot.
|
||||
@ -399,7 +399,7 @@ public class AnimationState {
|
||||
}
|
||||
|
||||
private void setAttachment (Skeleton skeleton, Slot slot, String attachmentName, boolean attachments) {
|
||||
slot.getPose().setAttachment(attachmentName == null ? null : skeleton.getAttachment(slot.data.index, attachmentName));
|
||||
slot.pose.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slot.data.index, attachmentName));
|
||||
if (attachments) slot.attachmentState = unkeyedState + CURRENT;
|
||||
}
|
||||
|
||||
@ -417,7 +417,7 @@ public class AnimationState {
|
||||
|
||||
Bone bone = skeleton.bones.get(timeline.boneIndex);
|
||||
if (!bone.active) return;
|
||||
BonePose pose = bone.getPose(), setup = bone.data.setup;
|
||||
BonePose pose = bone.pose, setup = bone.data.setup;
|
||||
float[] frames = timeline.frames;
|
||||
float r1, r2;
|
||||
if (time < frames[0]) { // Time is before first frame.
|
||||
|
||||
@ -38,13 +38,13 @@ import com.badlogic.gdx.utils.Null;
|
||||
* A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a
|
||||
* local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a
|
||||
* constraint or application code modifies the world transform after it was computed from the local transform. */
|
||||
public class Bone {
|
||||
public class Bone implements Constrained {
|
||||
final BoneData data;
|
||||
final Skeleton skeleton;
|
||||
@Null final Bone parent;
|
||||
final Array<Bone> children = new Array();
|
||||
final BonePose pose = new BonePose();
|
||||
final BoneApplied applied = new BoneApplied(this);
|
||||
final BoneApplied pose = new BoneApplied(this), constrained = new BoneApplied(this);
|
||||
BoneApplied applied = pose;
|
||||
boolean sorted, active;
|
||||
|
||||
public Bone (BoneData data, Skeleton skeleton, @Null Bone parent) {
|
||||
@ -74,16 +74,26 @@ public class Bone {
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Returns the bone's pose. */
|
||||
public BonePose getPose () {
|
||||
return pose;
|
||||
}
|
||||
|
||||
/** Returns the bone's applied pose. */
|
||||
public BoneApplied getAppliedPose () {
|
||||
return applied;
|
||||
}
|
||||
|
||||
public BoneApplied getConstrainedPose () {
|
||||
return constrained;
|
||||
}
|
||||
|
||||
public void setConstrained (boolean constrained) {
|
||||
applied = constrained ? this.constrained : pose;
|
||||
}
|
||||
|
||||
public void resetAppliedPose () {
|
||||
applied.set(pose);
|
||||
}
|
||||
|
||||
/** The skeleton this bone belongs to. */
|
||||
public Skeleton getSkeleton () {
|
||||
return skeleton;
|
||||
|
||||
@ -6,21 +6,18 @@ import static com.esotericsoftware.spine.utils.SpineUtils.*;
|
||||
|
||||
import com.badlogic.gdx.math.Matrix3;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
import com.badlogic.gdx.utils.Null;
|
||||
|
||||
import com.esotericsoftware.spine.BoneData.Inherit;
|
||||
|
||||
/** The applied pose for a bone. This is the {@link Bone} pose with constraints applied and the world transform computed by
|
||||
* {@link Skeleton#updateWorldTransform(Physics)}. */
|
||||
public class BoneApplied extends BonePose implements Updatable {
|
||||
public class BoneApplied extends BonePose implements Update {
|
||||
final Bone bone;
|
||||
@Null final BoneApplied parent;
|
||||
float a, b, worldX;
|
||||
float c, d, worldY;
|
||||
|
||||
BoneApplied (Bone bone) {
|
||||
this.bone = bone;
|
||||
parent = bone.parent == null ? null : bone.parent.applied;
|
||||
}
|
||||
|
||||
/** Computes the world transform using the parent bone and this bone's local applied transform. */
|
||||
@ -37,8 +34,7 @@ public class BoneApplied extends BonePose implements Updatable {
|
||||
* See <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
|
||||
* Runtimes Guide. */
|
||||
public void update (Physics physics) {
|
||||
BoneApplied parent = this.parent;
|
||||
if (parent == null) { // Root bone.
|
||||
if (bone.parent == null) { // Root bone.
|
||||
Skeleton skeleton = bone.skeleton;
|
||||
float sx = skeleton.scaleX, sy = skeleton.scaleY;
|
||||
float rx = (rotation + shearX) * degRad;
|
||||
@ -52,6 +48,7 @@ public class BoneApplied extends BonePose implements Updatable {
|
||||
return;
|
||||
}
|
||||
|
||||
BoneApplied parent = bone.parent.applied;
|
||||
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
|
||||
worldX = pa * x + pb * y + parent.worldX;
|
||||
worldY = pc * x + pd * y + parent.worldY;
|
||||
@ -147,7 +144,7 @@ public class BoneApplied extends BonePose implements Updatable {
|
||||
* Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The local transform after
|
||||
* calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. */
|
||||
public void updateLocalTransform () {
|
||||
BoneApplied parent = this.parent;
|
||||
BoneApplied parent = bone.parent.applied;
|
||||
if (parent == null) {
|
||||
x = worldX - bone.skeleton.x;
|
||||
y = worldY - bone.skeleton.y;
|
||||
@ -336,13 +333,13 @@ public class BoneApplied extends BonePose implements Updatable {
|
||||
/** Transforms a point from world coordinates to the parent bone's local coordinates. */
|
||||
public Vector2 worldToParent (Vector2 world) {
|
||||
if (world == null) throw new IllegalArgumentException("world cannot be null.");
|
||||
return parent == null ? world : parent.worldToLocal(world);
|
||||
return bone.parent == null ? world : bone.parent.applied.worldToLocal(world);
|
||||
}
|
||||
|
||||
/** Transforms a point from the parent bone's coordinates to world coordinates. */
|
||||
public Vector2 parentToWorld (Vector2 world) {
|
||||
if (world == null) throw new IllegalArgumentException("world cannot be null.");
|
||||
return parent == null ? world : parent.localToWorld(world);
|
||||
return bone.parent == null ? world : bone.parent.applied.localToWorld(world);
|
||||
}
|
||||
|
||||
/** Transforms a world rotation to a local rotation. */
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package com.esotericsoftware.spine;
|
||||
|
||||
public interface Constrained {
|
||||
public void setConstrained (boolean constrained);
|
||||
|
||||
public void resetAppliedPose ();
|
||||
}
|
||||
@ -39,11 +39,12 @@ import com.esotericsoftware.spine.BoneData.Inherit;
|
||||
* the last bone is as close to the target bone as possible.
|
||||
* <p>
|
||||
* See <a href="https://esotericsoftware.com/spine-ik-constraints">IK constraints</a> in the Spine User Guide. */
|
||||
public class IkConstraint implements Updatable {
|
||||
public class IkConstraint implements Constrained, Update {
|
||||
final IkConstraintData data;
|
||||
final Array<BoneApplied> bones;
|
||||
BoneApplied target;
|
||||
final IkConstraintPose pose = new IkConstraintPose(), applied = new IkConstraintPose();
|
||||
Bone target;
|
||||
final IkConstraintPose pose = new IkConstraintPose(), constrained = new IkConstraintPose();
|
||||
IkConstraintPose applied = pose;
|
||||
boolean active;
|
||||
|
||||
public IkConstraint (IkConstraintData data, Skeleton skeleton) {
|
||||
@ -53,9 +54,9 @@ public class IkConstraint implements Updatable {
|
||||
|
||||
bones = new Array(data.bones.size);
|
||||
for (BoneData boneData : data.bones)
|
||||
bones.add(skeleton.bones.get(boneData.index).applied);
|
||||
bones.add(skeleton.bones.get(boneData.index).constrained);
|
||||
|
||||
target = skeleton.bones.get(data.target.index).applied;
|
||||
target = skeleton.bones.get(data.target.index);
|
||||
|
||||
setupPose();
|
||||
}
|
||||
@ -74,7 +75,7 @@ public class IkConstraint implements Updatable {
|
||||
public void update (Physics physics) {
|
||||
IkConstraintPose a = applied;
|
||||
if (a.mix == 0) return;
|
||||
BoneApplied target = this.target;
|
||||
BoneApplied target = this.target.applied;
|
||||
Object[] bones = this.bones.items;
|
||||
switch (this.bones.size) {
|
||||
case 1 -> apply((BoneApplied)bones[0], target.worldX, target.worldY, a.compress, a.stretch, data.uniform, a.mix);
|
||||
@ -90,11 +91,11 @@ public class IkConstraint implements Updatable {
|
||||
}
|
||||
|
||||
/** The bone that is the IK target. */
|
||||
public BoneApplied getTarget () {
|
||||
public Bone getTarget () {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTarget (BoneApplied target) {
|
||||
public void setTarget (Bone target) {
|
||||
if (target == null) throw new IllegalArgumentException("target cannot be null.");
|
||||
this.target = target;
|
||||
}
|
||||
@ -107,6 +108,18 @@ public class IkConstraint implements Updatable {
|
||||
return applied;
|
||||
}
|
||||
|
||||
public IkConstraintPose getConstrainedPose () {
|
||||
return constrained;
|
||||
}
|
||||
|
||||
public void setConstrained (boolean constrained) {
|
||||
applied = constrained ? this.constrained : pose;
|
||||
}
|
||||
|
||||
public void resetAppliedPose () {
|
||||
applied.set(pose);
|
||||
}
|
||||
|
||||
/** Returns false when this constraint won't be updated by
|
||||
* {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the
|
||||
* {@link Skeleton#getSkin() active skin} does not contain this item.
|
||||
@ -131,7 +144,7 @@ public class IkConstraint implements Updatable {
|
||||
static public void apply (BoneApplied bone, float targetX, float targetY, boolean compress, boolean stretch, boolean uniform,
|
||||
float alpha) {
|
||||
if (bone == null) throw new IllegalArgumentException("bone cannot be null.");
|
||||
BoneApplied p = bone.parent;
|
||||
BoneApplied p = bone.bone.parent.applied;
|
||||
float pa = p.a, pb = p.b, pc = p.c, pd = p.d;
|
||||
float rotationIK = -bone.shearX - bone.rotation, tx, ty;
|
||||
switch (bone.inherit) {
|
||||
@ -222,7 +235,7 @@ public class IkConstraint implements Updatable {
|
||||
cwx = a * child.x + b * child.y + parent.worldX;
|
||||
cwy = c * child.x + d * child.y + parent.worldY;
|
||||
}
|
||||
BoneApplied pp = parent.parent;
|
||||
BoneApplied pp = parent.bone.parent.applied;
|
||||
a = pp.a;
|
||||
b = pp.b;
|
||||
c = pp.c;
|
||||
|
||||
@ -45,14 +45,15 @@ import com.esotericsoftware.spine.attachments.PathAttachment;
|
||||
* constrained bones so they follow a {@link PathAttachment}.
|
||||
* <p>
|
||||
* See <a href="https://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide. */
|
||||
public class PathConstraint implements Updatable {
|
||||
public class PathConstraint implements Constrained, Update {
|
||||
static final int NONE = -1, BEFORE = -2, AFTER = -3;
|
||||
static final float epsilon = 0.00001f;
|
||||
|
||||
final PathConstraintData data;
|
||||
final Array<BoneApplied> bones;
|
||||
Slot slot;
|
||||
final PathConstraintPose pose = new PathConstraintPose(), applied = new PathConstraintPose();
|
||||
final PathConstraintPose pose = new PathConstraintPose(), constrained = new PathConstraintPose();
|
||||
PathConstraintPose applied = pose;
|
||||
boolean active;
|
||||
|
||||
private final FloatArray spaces = new FloatArray(), positions = new FloatArray();
|
||||
@ -72,7 +73,7 @@ public class PathConstraint implements Updatable {
|
||||
|
||||
bones = new Array(data.bones.size);
|
||||
for (BoneData boneData : data.bones)
|
||||
bones.add(skeleton.bones.get(boneData.index).applied);
|
||||
bones.add(skeleton.bones.get(boneData.index).constrained);
|
||||
|
||||
slot = skeleton.slots.get(data.slot.index);
|
||||
|
||||
@ -495,6 +496,18 @@ public class PathConstraint implements Updatable {
|
||||
return applied;
|
||||
}
|
||||
|
||||
public PathConstraintPose getConstrainedPose () {
|
||||
return constrained;
|
||||
}
|
||||
|
||||
public void setConstrained (boolean constrained) {
|
||||
applied = constrained ? this.constrained : pose;
|
||||
}
|
||||
|
||||
public void resetAppliedPose () {
|
||||
applied.set(pose);
|
||||
}
|
||||
|
||||
/** Returns false when this constraint won't be updated by
|
||||
* {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the
|
||||
* {@link Skeleton#getSkin() active skin} does not contain this item.
|
||||
|
||||
@ -34,11 +34,12 @@ import static com.esotericsoftware.spine.utils.SpineUtils.*;
|
||||
/** Stores the current pose for a physics constraint. A physics constraint applies physics to bones.
|
||||
* <p>
|
||||
* See <a href="https://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
|
||||
public class PhysicsConstraint implements Updatable {
|
||||
public class PhysicsConstraint implements Constrained, Update {
|
||||
final PhysicsConstraintData data;
|
||||
final Skeleton skeleton;
|
||||
BoneApplied bone;
|
||||
final PhysicsConstraintPose pose = new PhysicsConstraintPose(), applied = new PhysicsConstraintPose();
|
||||
final PhysicsConstraintPose pose = new PhysicsConstraintPose(), constrained = new PhysicsConstraintPose();
|
||||
PhysicsConstraintPose applied = pose;
|
||||
boolean active;
|
||||
|
||||
boolean reset = true;
|
||||
@ -49,19 +50,13 @@ public class PhysicsConstraint implements Updatable {
|
||||
float scaleOffset, scaleVelocity;
|
||||
float remaining, lastTime;
|
||||
|
||||
private PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton, BoneApplied bone) {
|
||||
this.data = data;
|
||||
this.skeleton = skeleton;
|
||||
this.bone = bone;
|
||||
}
|
||||
|
||||
public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton) {
|
||||
if (data == null) throw new IllegalArgumentException("data cannot be null.");
|
||||
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
|
||||
this.data = data;
|
||||
this.skeleton = skeleton;
|
||||
|
||||
bone = skeleton.bones.get(data.bone.index).applied;
|
||||
bone = skeleton.bones.get(data.bone.index).constrained;
|
||||
|
||||
setupPose();
|
||||
}
|
||||
@ -300,6 +295,18 @@ public class PhysicsConstraint implements Updatable {
|
||||
return applied;
|
||||
}
|
||||
|
||||
public PhysicsConstraintPose getConstrainedPose () {
|
||||
return constrained;
|
||||
}
|
||||
|
||||
public void setConstrained (boolean constrained) {
|
||||
applied = constrained ? this.constrained : pose;
|
||||
}
|
||||
|
||||
public void resetAppliedPose () {
|
||||
applied.set(pose);
|
||||
}
|
||||
|
||||
/** Returns false when this constraint won't be updated by
|
||||
* {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the
|
||||
* {@link Skeleton#getSkin() active skin} does not contain this item.
|
||||
|
||||
@ -38,6 +38,7 @@ import com.badlogic.gdx.utils.FloatArray;
|
||||
import com.badlogic.gdx.utils.Null;
|
||||
|
||||
import com.esotericsoftware.spine.Animation.BoneTimeline;
|
||||
import com.esotericsoftware.spine.Animation.Timeline;
|
||||
import com.esotericsoftware.spine.Skin.SkinEntry;
|
||||
import com.esotericsoftware.spine.attachments.Attachment;
|
||||
import com.esotericsoftware.spine.attachments.ClippingAttachment;
|
||||
@ -52,7 +53,7 @@ import com.esotericsoftware.spine.utils.SkeletonClipping;
|
||||
* Runtimes Guide. */
|
||||
public class Skeleton {
|
||||
static private final short[] quadTriangles = {0, 1, 2, 2, 3, 0};
|
||||
private static final Object IkConstraint = null;
|
||||
|
||||
final SkeletonData data;
|
||||
final Array<Bone> bones;
|
||||
final Array<Slot> slots;
|
||||
@ -62,7 +63,8 @@ public class Skeleton {
|
||||
final Array<TransformConstraint> transformConstraints;
|
||||
final Array<PathConstraint> pathConstraints;
|
||||
final Array<PhysicsConstraint> physicsConstraints;
|
||||
final Array<Updatable> updateCache = new Array();
|
||||
final Array updateCache = new Array();
|
||||
final Array<Constrained> resetCache = new Array();
|
||||
@Null Skin skin;
|
||||
final Color color;
|
||||
float x, y, scaleX = 1, scaleY = 1, time;
|
||||
@ -180,8 +182,8 @@ public class Skeleton {
|
||||
/** Caches information about bones and constraints. Must be called if the {@link #getSkin()} is modified or if bones,
|
||||
* constraints, or weighted path attachments are added or removed. */
|
||||
public void updateCache () {
|
||||
Array<Updatable> updateCache = this.updateCache;
|
||||
updateCache.clear();
|
||||
resetCache.clear();
|
||||
|
||||
int boneCount = bones.size;
|
||||
Object[] bones = this.bones.items;
|
||||
@ -189,6 +191,7 @@ public class Skeleton {
|
||||
var bone = (Bone)bones[i];
|
||||
bone.sorted = bone.data.skinRequired;
|
||||
bone.active = !bone.sorted;
|
||||
bone.setConstrained(false);
|
||||
}
|
||||
if (skin != null) {
|
||||
Object[] skinBones = skin.bones.items;
|
||||
@ -207,6 +210,17 @@ public class Skeleton {
|
||||
Object[] sliders = this.sliders.items, ikConstraints = this.ikConstraints.items,
|
||||
transformConstraints = this.transformConstraints.items, pathConstraints = this.pathConstraints.items,
|
||||
physicsConstraints = this.physicsConstraints.items;
|
||||
for (int ii = 0; ii < sliderCount; ii++)
|
||||
((Slider)sliders[ii]).setConstrained(false);
|
||||
for (int ii = 0; ii < ikCount; ii++)
|
||||
((IkConstraint)ikConstraints[ii]).setConstrained(false);
|
||||
for (int ii = 0; ii < transformCount; ii++)
|
||||
((TransformConstraint)transformConstraints[ii]).setConstrained(false);
|
||||
for (int ii = 0; ii < pathCount; ii++)
|
||||
((PathConstraint)pathConstraints[ii]).setConstrained(false);
|
||||
for (int ii = 0; ii < physicsCount; ii++)
|
||||
((PhysicsConstraint)physicsConstraints[ii]).setConstrained(false);
|
||||
|
||||
int constraintCount = ikCount + transformCount + pathCount + physicsCount;
|
||||
outer:
|
||||
for (int i = 0; i < constraintCount; i++) {
|
||||
@ -249,47 +263,29 @@ public class Skeleton {
|
||||
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
sortBone((Bone)bones[i]);
|
||||
}
|
||||
|
||||
private void sortSlider (Slider constraint) {
|
||||
constraint.active = !constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true));
|
||||
if (!constraint.active) return;
|
||||
|
||||
Object[] timelines = constraint.data.animation.timelines.items;
|
||||
int timelineCount = constraint.data.animation.timelines.size;
|
||||
|
||||
Object[] bones = this.bones.items;
|
||||
for (int i = 0; i < timelineCount; i++)
|
||||
if (timelines[i] instanceof BoneTimeline boneTimeline) sortBone((Bone)bones[boneTimeline.getBoneIndex()]);
|
||||
|
||||
updateCache.add(constraint);
|
||||
|
||||
for (int i = 0; i < timelineCount; i++) {
|
||||
if (timelines[i] instanceof BoneTimeline boneTimeline) {
|
||||
var bone = (Bone)bones[boneTimeline.getBoneIndex()];
|
||||
sortReset(bone.children);
|
||||
bone.sorted = false;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < timelineCount; i++)
|
||||
if (timelines[i] instanceof BoneTimeline boneTimeline) sortBone((Bone)bones[boneTimeline.getBoneIndex()]);
|
||||
Object[] updateCache = this.updateCache.items;
|
||||
for (int i = 0, n = this.updateCache.size; i < n; i++)
|
||||
if (updateCache[i] instanceof Bone bone) updateCache[i] = bone.applied;
|
||||
}
|
||||
|
||||
private void sortIkConstraint (IkConstraint constraint) {
|
||||
constraint.active = constraint.target.bone.active
|
||||
constraint.active = constraint.target.active
|
||||
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)));
|
||||
if (!constraint.active) return;
|
||||
|
||||
sortBone(constraint.target.bone);
|
||||
sortBone(constraint.target);
|
||||
|
||||
Array<BoneApplied> constrained = constraint.bones;
|
||||
Bone parent = constrained.first().bone;
|
||||
sortBone(parent);
|
||||
resetCache(parent);
|
||||
if (constrained.size == 1) {
|
||||
updateCache.add(constraint);
|
||||
sortReset(parent.children);
|
||||
} else {
|
||||
Bone child = constrained.peek().bone;
|
||||
resetCache(child);
|
||||
sortBone(child);
|
||||
|
||||
updateCache.add(constraint);
|
||||
@ -300,23 +296,27 @@ public class Skeleton {
|
||||
}
|
||||
|
||||
private void sortTransformConstraint (TransformConstraint constraint) {
|
||||
constraint.active = constraint.source.bone.active
|
||||
constraint.active = constraint.source.active
|
||||
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)));
|
||||
if (!constraint.active) return;
|
||||
|
||||
sortBone(constraint.source.bone);
|
||||
sortBone(constraint.source);
|
||||
|
||||
Object[] constrained = constraint.bones.items;
|
||||
int boneCount = constraint.bones.size;
|
||||
if (constraint.data.localSource) {
|
||||
for (int i = 0; i < boneCount; i++) {
|
||||
Bone child = ((BoneApplied)constrained[i]).bone;
|
||||
resetCache(child);
|
||||
sortBone(child.parent);
|
||||
sortBone(child);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
sortBone(((BoneApplied)constrained[i]).bone);
|
||||
for (int i = 0; i < boneCount; i++) {
|
||||
Bone bone = ((BoneApplied)constrained[i]).bone;
|
||||
resetCache(bone);
|
||||
sortBone(bone);
|
||||
}
|
||||
}
|
||||
|
||||
updateCache.add(constraint);
|
||||
@ -343,8 +343,11 @@ public class Skeleton {
|
||||
|
||||
Object[] constrained = constraint.bones.items;
|
||||
int boneCount = constraint.bones.size;
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
sortBone(((BoneApplied)constrained[i]).bone);
|
||||
for (int i = 0; i < boneCount; i++) {
|
||||
Bone bone = ((BoneApplied)constrained[i]).bone;
|
||||
resetCache(bone);
|
||||
sortBone(bone);
|
||||
}
|
||||
|
||||
updateCache.add(constraint);
|
||||
|
||||
@ -386,18 +389,53 @@ public class Skeleton {
|
||||
|
||||
sortBone(bone);
|
||||
|
||||
resetCache(bone);
|
||||
updateCache.add(constraint);
|
||||
|
||||
sortReset(bone.children);
|
||||
bone.sorted = true;
|
||||
}
|
||||
|
||||
private void sortSlider (Slider constraint) {
|
||||
constraint.active = !constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true));
|
||||
if (!constraint.active) return;
|
||||
|
||||
Object[] timelines = constraint.data.animation.timelines.items;
|
||||
int timelineCount = constraint.data.animation.timelines.size;
|
||||
|
||||
Object[] bones = this.bones.items;
|
||||
for (int i = 0; i < timelineCount; i++) {
|
||||
var timeline = (Timeline)timelines[i];
|
||||
if (timeline instanceof BoneTimeline boneTimeline) sortBone((Bone)bones[boneTimeline.getBoneIndex()]);
|
||||
}
|
||||
|
||||
updateCache.add(constraint);
|
||||
|
||||
for (int i = 0; i < timelineCount; i++) {
|
||||
if (timelines[i] instanceof BoneTimeline boneTimeline) {
|
||||
var bone = (Bone)bones[boneTimeline.getBoneIndex()];
|
||||
resetCache(bone);
|
||||
sortReset(bone.children);
|
||||
bone.sorted = false;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < timelineCount; i++)
|
||||
if (timelines[i] instanceof BoneTimeline boneTimeline) sortBone((Bone)bones[boneTimeline.getBoneIndex()]);
|
||||
}
|
||||
|
||||
private void resetCache (Constrained object) {
|
||||
if (!resetCache.contains(object, true)) {
|
||||
resetCache.add(object);
|
||||
object.setConstrained(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void sortBone (Bone bone) {
|
||||
if (bone.sorted) return;
|
||||
Bone parent = bone.parent;
|
||||
if (parent != null) sortBone(parent);
|
||||
bone.sorted = true;
|
||||
updateCache.add(bone.applied);
|
||||
updateCache.add(bone);
|
||||
}
|
||||
|
||||
private void sortReset (Array<Bone> bones) {
|
||||
@ -415,45 +453,13 @@ public class Skeleton {
|
||||
* See <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
|
||||
* Runtimes Guide. */
|
||||
public void updateWorldTransform (Physics physics) {
|
||||
Object[] objects = this.bones.items;
|
||||
for (int i = 0, n = this.bones.size; i < n; i++) {
|
||||
var bone = (Bone)objects[i];
|
||||
if (bone.active) bone.applied.set(bone.pose);
|
||||
}
|
||||
objects = this.slots.items;
|
||||
for (int i = 0, n = this.slots.size; i < n; i++) {
|
||||
var slot = (Slot)objects[i];
|
||||
if (slot.bone.active) slot.applied.set(slot.pose);
|
||||
}
|
||||
objects = ikConstraints.items;
|
||||
for (int i = 0, n = ikConstraints.size; i < n; i++) {
|
||||
var constraint = (IkConstraint)objects[i];
|
||||
if (constraint.active) constraint.applied.set(constraint.pose);
|
||||
}
|
||||
objects = pathConstraints.items;
|
||||
for (int i = 0, n = pathConstraints.size; i < n; i++) {
|
||||
var constraint = (PathConstraint)objects[i];
|
||||
if (constraint.active) constraint.applied.set(constraint.pose);
|
||||
}
|
||||
objects = transformConstraints.items;
|
||||
for (int i = 0, n = transformConstraints.size; i < n; i++) {
|
||||
var constraint = (TransformConstraint)objects[i];
|
||||
if (constraint.active) constraint.applied.set(constraint.pose);
|
||||
}
|
||||
objects = physicsConstraints.items;
|
||||
for (int i = 0, n = physicsConstraints.size; i < n; i++) {
|
||||
var constraint = (PhysicsConstraint)objects[i];
|
||||
if (constraint.active) constraint.applied.set(constraint.pose);
|
||||
}
|
||||
objects = sliders.items;
|
||||
for (int i = 0, n = sliders.size; i < n; i++) {
|
||||
var constraint = (Slider)objects[i];
|
||||
if (constraint.active) constraint.applied.set(constraint.pose);
|
||||
}
|
||||
Object[] resetCache = this.resetCache.items;
|
||||
for (int i = 0, n = this.resetCache.size; i < n; i++)
|
||||
((Constrained)resetCache[i]).resetAppliedPose();
|
||||
|
||||
Object[] updateCache = this.updateCache.items;
|
||||
for (int i = 0, n = this.updateCache.size; i < n; i++)
|
||||
((Updatable)updateCache[i]).update(physics);
|
||||
((Update)updateCache[i]).update(physics);
|
||||
}
|
||||
|
||||
/** Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies
|
||||
@ -464,41 +470,9 @@ public class Skeleton {
|
||||
public void updateWorldTransform (Physics physics, BoneApplied parent) {
|
||||
if (parent == null) throw new IllegalArgumentException("parent cannot be null.");
|
||||
|
||||
Object[] objects = this.bones.items;
|
||||
for (int i = 0, n = this.bones.size; i < n; i++) {
|
||||
var bone = (Bone)objects[i];
|
||||
if (bone.active) bone.applied.set(bone.pose);
|
||||
}
|
||||
objects = this.slots.items;
|
||||
for (int i = 0, n = this.slots.size; i < n; i++) {
|
||||
var slot = (Slot)objects[i];
|
||||
if (slot.bone.active) slot.applied.set(slot.pose);
|
||||
}
|
||||
objects = ikConstraints.items;
|
||||
for (int i = 0, n = ikConstraints.size; i < n; i++) {
|
||||
var constraint = (IkConstraint)objects[i];
|
||||
if (constraint.active) constraint.applied.set(constraint.pose);
|
||||
}
|
||||
objects = pathConstraints.items;
|
||||
for (int i = 0, n = pathConstraints.size; i < n; i++) {
|
||||
var constraint = (PathConstraint)objects[i];
|
||||
if (constraint.active) constraint.applied.set(constraint.pose);
|
||||
}
|
||||
objects = transformConstraints.items;
|
||||
for (int i = 0, n = transformConstraints.size; i < n; i++) {
|
||||
var constraint = (TransformConstraint)objects[i];
|
||||
if (constraint.active) constraint.applied.set(constraint.pose);
|
||||
}
|
||||
objects = physicsConstraints.items;
|
||||
for (int i = 0, n = physicsConstraints.size; i < n; i++) {
|
||||
var constraint = (PhysicsConstraint)objects[i];
|
||||
if (constraint.active) constraint.applied.set(constraint.pose);
|
||||
}
|
||||
objects = sliders.items;
|
||||
for (int i = 0, n = sliders.size; i < n; i++) {
|
||||
var constraint = (Slider)objects[i];
|
||||
if (constraint.active) constraint.applied.set(constraint.pose);
|
||||
}
|
||||
Object[] resetCache = this.resetCache.items;
|
||||
for (int i = 0, n = this.resetCache.size; i < n; i++)
|
||||
((Constrained)resetCache[i]).resetAppliedPose();
|
||||
|
||||
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
|
||||
BoneApplied rootBone = getRootBone().applied;
|
||||
@ -520,7 +494,7 @@ public class Skeleton {
|
||||
// Update everything except root bone.
|
||||
Object[] updateCache = this.updateCache.items;
|
||||
for (int i = 0, n = this.updateCache.size; i < n; i++) {
|
||||
var updatable = (Updatable)updateCache[i];
|
||||
var updatable = (Update)updateCache[i];
|
||||
if (updatable != rootBone) updatable.update(physics);
|
||||
}
|
||||
}
|
||||
@ -578,7 +552,7 @@ public class Skeleton {
|
||||
}
|
||||
|
||||
/** The list of bones and constraints, sorted in the order they should be updated, as computed by {@link #updateCache()}. */
|
||||
public Array<Updatable> getUpdateCache () {
|
||||
public Array<Update> getUpdateCache () {
|
||||
return updateCache;
|
||||
}
|
||||
|
||||
|
||||
@ -343,7 +343,7 @@ public class SkeletonBinary extends SkeletonLoader {
|
||||
if ((flags & 16) != 0) data.offsetScaleY = input.readFloat();
|
||||
if ((flags & 32) != 0) data.offsetShearY = input.readFloat();
|
||||
flags = input.read();
|
||||
TransformConstraintPose setup = data.getSetupPose();
|
||||
TransformConstraintPose setup = data.setup;
|
||||
if ((flags & 1) != 0) setup.mixRotate = input.readFloat();
|
||||
if ((flags & 2) != 0) setup.mixX = input.readFloat();
|
||||
if ((flags & 4) != 0) setup.mixY = input.readFloat();
|
||||
@ -394,7 +394,7 @@ public class SkeletonBinary extends SkeletonLoader {
|
||||
if ((flags & 32) != 0) data.shearX = input.readFloat();
|
||||
data.limit = ((flags & 64) != 0 ? input.readFloat() : 5000) * scale;
|
||||
data.step = 1f / input.readUnsignedByte();
|
||||
PhysicsConstraintPose setup = data.getSetupPose();
|
||||
PhysicsConstraintPose setup = data.setup;
|
||||
setup.inertia = input.readFloat();
|
||||
setup.strength = input.readFloat();
|
||||
setup.damping = input.readFloat();
|
||||
|
||||
@ -204,10 +204,10 @@ public class SkeletonJson extends SkeletonLoader {
|
||||
var data = new SlotData(skeletonData.slots.size, slotName, boneData);
|
||||
|
||||
String color = slotMap.getString("color", null);
|
||||
if (color != null) Color.valueOf(color, data.getSetupPose().getColor());
|
||||
if (color != null) Color.valueOf(color, data.setup.getColor());
|
||||
|
||||
String dark = slotMap.getString("dark", null);
|
||||
if (dark != null) Color.valueOf(dark, data.getSetupPose().getDarkColor());
|
||||
if (dark != null) Color.valueOf(dark, data.setup.getDarkColor());
|
||||
|
||||
data.attachmentName = slotMap.getString("attachment", null);
|
||||
data.blendMode = BlendMode.valueOf(slotMap.getString("blend", BlendMode.normal.name()));
|
||||
@ -390,7 +390,7 @@ public class SkeletonJson extends SkeletonLoader {
|
||||
data.shearX = constraintMap.getFloat("shearX", 0);
|
||||
data.limit = constraintMap.getFloat("limit", 5000) * scale;
|
||||
data.step = 1f / constraintMap.getInt("fps", 60);
|
||||
PhysicsConstraintPose setup = data.getSetupPose();
|
||||
PhysicsConstraintPose setup = data.setup;
|
||||
setup.inertia = constraintMap.getFloat("inertia", 1);
|
||||
setup.strength = constraintMap.getFloat("strength", 100);
|
||||
setup.damping = constraintMap.getFloat("damping", 1);
|
||||
|
||||
@ -80,7 +80,7 @@ public class SkeletonRenderer {
|
||||
for (int i = 0, n = skeleton.drawOrder.size; i < n; i++) {
|
||||
var slot = (Slot)drawOrder[i];
|
||||
if (!slot.bone.active) continue;
|
||||
SlotPose pose = slot.getAppliedPose();
|
||||
SlotPose pose = slot.applied;
|
||||
Attachment attachment = pose.attachment;
|
||||
if (attachment instanceof RegionAttachment region) {
|
||||
region.computeWorldVertices(slot, vertices, 0, 5);
|
||||
@ -149,7 +149,7 @@ public class SkeletonRenderer {
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
SlotPose pose = slot.getAppliedPose();
|
||||
SlotPose pose = slot.applied;
|
||||
Texture texture = null;
|
||||
Attachment attachment = pose.attachment;
|
||||
if (attachment instanceof RegionAttachment region) {
|
||||
@ -244,7 +244,7 @@ public class SkeletonRenderer {
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
SlotPose pose = slot.getAppliedPose();
|
||||
SlotPose pose = slot.applied;
|
||||
Texture texture = null;
|
||||
Attachment attachment = pose.attachment;
|
||||
if (attachment instanceof RegionAttachment region) {
|
||||
|
||||
@ -157,7 +157,7 @@ public class Skin {
|
||||
Object[] slots = skeleton.slots.items;
|
||||
for (SkinEntry entry : oldSkin.attachments.orderedItems()) {
|
||||
int slotIndex = entry.slotIndex;
|
||||
SlotPose slot = ((Slot)slots[slotIndex]).getPose();
|
||||
SlotPose slot = ((Slot)slots[slotIndex]).pose;
|
||||
if (slot.attachment == entry.attachment) {
|
||||
Attachment attachment = getAttachment(slotIndex, entry.name);
|
||||
if (attachment != null) slot.setAttachment(attachment);
|
||||
|
||||
@ -35,10 +35,11 @@ import com.esotericsoftware.spine.Animation.MixDirection;
|
||||
/** Stores the setup pose for a {@link PhysicsConstraint}.
|
||||
* <p>
|
||||
* See <a href="https://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
|
||||
public class Slider implements Updatable {
|
||||
public class Slider implements Constrained, Update {
|
||||
final SliderData data;
|
||||
final Skeleton skeleton;
|
||||
final SliderPose pose = new SliderPose(), applied = new SliderPose();
|
||||
final SliderPose pose = new SliderPose(), constrained = new SliderPose();
|
||||
SliderPose applied = pose;
|
||||
|
||||
boolean active;
|
||||
|
||||
@ -54,7 +55,7 @@ public class Slider implements Updatable {
|
||||
/** Copy constructor. */
|
||||
public Slider (Slider slider, Skeleton skeleton) {
|
||||
this(slider.data, skeleton);
|
||||
setupPose();
|
||||
pose.set(slider.pose);
|
||||
}
|
||||
|
||||
public void update (Physics physics) {
|
||||
@ -74,6 +75,18 @@ public class Slider implements Updatable {
|
||||
return applied;
|
||||
}
|
||||
|
||||
public SliderPose getConstrainedPose () {
|
||||
return constrained;
|
||||
}
|
||||
|
||||
public void setConstrained (boolean constrained) {
|
||||
applied = constrained ? this.constrained : pose;
|
||||
}
|
||||
|
||||
public void resetAppliedPose () {
|
||||
applied.set(pose);
|
||||
}
|
||||
|
||||
/** Returns false when this constraint won't be updated by
|
||||
* {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the
|
||||
* {@link Skeleton#getSkin() active skin} does not contain this item.
|
||||
|
||||
@ -38,7 +38,6 @@ public class SliderPose {
|
||||
mix = pose.mix;
|
||||
}
|
||||
|
||||
|
||||
public float getTime () {
|
||||
return time;
|
||||
}
|
||||
|
||||
@ -34,10 +34,11 @@ import com.badlogic.gdx.graphics.Color;
|
||||
/** Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store
|
||||
* state for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared
|
||||
* across multiple skeletons. */
|
||||
public class Slot {
|
||||
public class Slot implements Constrained {
|
||||
final SlotData data;
|
||||
final Bone bone;
|
||||
final SlotPose pose = new SlotPose(), applied = new SlotPose();
|
||||
final SlotPose pose = new SlotPose(), constrained = new SlotPose();
|
||||
SlotPose applied = pose;
|
||||
int attachmentState;
|
||||
|
||||
public Slot (SlotData data, Skeleton skeleton) {
|
||||
@ -76,16 +77,26 @@ public class Slot {
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Returns the slot's pose. */
|
||||
public SlotPose getPose () {
|
||||
return pose;
|
||||
}
|
||||
|
||||
/** Returns the slot's applied pose. */
|
||||
public SlotPose getAppliedPose () {
|
||||
return applied;
|
||||
}
|
||||
|
||||
public SlotPose getConstrainedPose () {
|
||||
return constrained;
|
||||
}
|
||||
|
||||
public void setConstrained (boolean constrained) {
|
||||
applied = constrained ? this.constrained : pose;
|
||||
}
|
||||
|
||||
public void resetAppliedPose () {
|
||||
applied.set(pose);
|
||||
}
|
||||
|
||||
/** The bone this slot belongs to. */
|
||||
public Bone getBone () {
|
||||
return bone;
|
||||
|
||||
@ -40,14 +40,15 @@ import com.esotericsoftware.spine.TransformConstraintData.ToProperty;
|
||||
* bones to match that of the source bone.
|
||||
* <p>
|
||||
* See <a href="https://esotericsoftware.com/spine-transform-constraints">Transform constraints</a> in the Spine User Guide. */
|
||||
public class TransformConstraint implements Updatable {
|
||||
public class TransformConstraint implements Constrained, Update {
|
||||
final TransformConstraintData data;
|
||||
final Array<BoneApplied> bones;
|
||||
BoneApplied source;
|
||||
final TransformConstraintPose pose = new TransformConstraintPose(), applied = new TransformConstraintPose();
|
||||
Bone source;
|
||||
final TransformConstraintPose pose = new TransformConstraintPose(), constrained = new TransformConstraintPose();
|
||||
TransformConstraintPose applied = pose;
|
||||
boolean active;
|
||||
|
||||
public TransformConstraint (TransformConstraintData data, Array<BoneApplied> bones, BoneApplied source) {
|
||||
public TransformConstraint (TransformConstraintData data, Array<BoneApplied> bones, Bone source) {
|
||||
this.data = data;
|
||||
this.bones = bones;
|
||||
this.source = source;
|
||||
@ -60,9 +61,9 @@ public class TransformConstraint implements Updatable {
|
||||
|
||||
bones = new Array(data.bones.size);
|
||||
for (BoneData boneData : data.bones)
|
||||
bones.add(skeleton.bones.get(boneData.index).applied);
|
||||
bones.add(skeleton.bones.get(boneData.index).constrained);
|
||||
|
||||
source = skeleton.bones.get(data.source.index).applied;
|
||||
source = skeleton.bones.get(data.source.index);
|
||||
|
||||
setupPose();
|
||||
}
|
||||
@ -85,12 +86,13 @@ public class TransformConstraint implements Updatable {
|
||||
|
||||
TransformConstraintData data = this.data;
|
||||
boolean localFrom = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp;
|
||||
BoneApplied source = this.source;
|
||||
BoneApplied source = this.source.applied;
|
||||
Object[] fromItems = data.properties.items;
|
||||
int fn = data.properties.size;
|
||||
Object[] bones = this.bones.items;
|
||||
for (int i = 0, n = this.bones.size; i < n; i++) {
|
||||
var bone = (BoneApplied)bones[i];
|
||||
if (bone.bone.applied != bone.bone.constrained) System.out.println();
|
||||
for (int f = 0; f < fn; f++) {
|
||||
var from = (FromProperty)fromItems[f];
|
||||
float value = from.value(data, source, localFrom) - from.offset;
|
||||
@ -122,11 +124,11 @@ public class TransformConstraint implements Updatable {
|
||||
}
|
||||
|
||||
/** The bone whose world transform will be copied to the constrained bones. */
|
||||
public BoneApplied getSource () {
|
||||
public Bone getSource () {
|
||||
return source;
|
||||
}
|
||||
|
||||
public void setSource (BoneApplied source) {
|
||||
public void setSource (Bone source) {
|
||||
if (source == null) throw new IllegalArgumentException("source cannot be null.");
|
||||
this.source = source;
|
||||
}
|
||||
@ -139,6 +141,18 @@ public class TransformConstraint implements Updatable {
|
||||
return applied;
|
||||
}
|
||||
|
||||
public TransformConstraintPose getConstrainedPose () {
|
||||
return constrained;
|
||||
}
|
||||
|
||||
public void setConstrained (boolean constrained) {
|
||||
applied = constrained ? this.constrained : pose;
|
||||
}
|
||||
|
||||
public void resetAppliedPose () {
|
||||
applied.set(pose);
|
||||
}
|
||||
|
||||
/** Returns false when this constraint won't be updated by
|
||||
* {@link Skeleton#updateWorldTransform(com.esotericsoftware.spine.Physics)} because a skin is required and the
|
||||
* {@link Skeleton#getSkin() active skin} does not contain this item.
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
package com.esotericsoftware.spine;
|
||||
|
||||
/** The interface for items updated by {@link Skeleton#updateWorldTransform(Physics)}. */
|
||||
public interface Updatable {
|
||||
public interface Update {
|
||||
/** @param physics Determines how physics and other non-deterministic updates are applied. */
|
||||
public void update (Physics physics);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user