[libgdx] Constraint order from appearance in data. updateCache sorting moved to constraints.

This commit is contained in:
Nathan Sweet 2025-04-17 18:04:37 -04:00
parent fff1606b6d
commit d115ca83dd
24 changed files with 843 additions and 1051 deletions

View File

@ -1980,7 +1980,7 @@ public class Animation {
return ENTRIES;
}
/** The index of the IK constraint in {@link Skeleton#getIkConstraints()} that will be changed when this timeline is
/** The index of the IK constraint in {@link Skeleton#getConstraints()} that will be changed when this timeline is
* applied. */
public int getIkConstraintIndex () {
return constraintIndex;
@ -2004,7 +2004,7 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
IkConstraint constraint = skeleton.ikConstraints.get(constraintIndex);
var constraint = (IkConstraint)skeleton.constraints.get(constraintIndex);
if (!constraint.active) return;
IkConstraintPose pose = appliedPose ? constraint.applied : constraint.pose, setup = constraint.data.setup;
@ -2090,8 +2090,8 @@ public class Animation {
return ENTRIES;
}
/** The index of the transform constraint in {@link Skeleton#getTransformConstraints()} that will be changed when this
* timeline is applied. */
/** The index of the transform constraint in {@link Skeleton#getConstraints()} that will be changed when this timeline is
* applied. */
public int getTransformConstraintIndex () {
return constraintIndex;
}
@ -2114,7 +2114,7 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
TransformConstraint constraint = skeleton.transformConstraints.get(constraintIndex);
var constraint = (TransformConstraint)skeleton.constraints.get(constraintIndex);
if (!constraint.active) return;
TransformConstraintPose pose = appliedPose ? constraint.applied : constraint.pose;
@ -2205,7 +2205,7 @@ public class Animation {
constraintIndex = pathConstraintIndex;
}
/** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is
/** The index of the path constraint in {@link Skeleton#getConstraints()} that will be changed when this timeline is
* applied. */
public int getPathConstraintIndex () {
return constraintIndex;
@ -2214,7 +2214,7 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
PathConstraint constraint = skeleton.pathConstraints.get(constraintIndex);
var constraint = (PathConstraint)skeleton.constraints.get(constraintIndex);
if (constraint.active) {
PathConstraintPose pose = appliedPose ? constraint.applied : constraint.pose;
pose.position = getAbsoluteValue(time, alpha, blend, pose.position, constraint.data.setup.position);
@ -2231,7 +2231,7 @@ public class Animation {
constraintIndex = pathConstraintIndex;
}
/** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is
/** The index of the path constraint in {@link Skeleton#getConstraints()} that will be changed when this timeline is
* applied. */
public int getPathConstraintIndex () {
return constraintIndex;
@ -2240,7 +2240,7 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
PathConstraint constraint = skeleton.pathConstraints.get(constraintIndex);
var constraint = (PathConstraint)skeleton.constraints.get(constraintIndex);
if (constraint.active) {
PathConstraintPose pose = appliedPose ? constraint.applied : constraint.pose;
pose.spacing = getAbsoluteValue(time, alpha, blend, pose.spacing, constraint.data.setup.spacing);
@ -2265,7 +2265,7 @@ public class Animation {
return ENTRIES;
}
/** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is
/** The index of the path constraint in {@link Skeleton#getConstraints()} that will be changed when this timeline is
* applied. */
public int getPathConstraintIndex () {
return constraintIndex;
@ -2285,7 +2285,7 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
PathConstraint constraint = skeleton.pathConstraints.get(constraintIndex);
var constraint = (PathConstraint)skeleton.constraints.get(constraintIndex);
if (!constraint.active) return;
PathConstraintPose pose = appliedPose ? constraint.applied : constraint.pose;
@ -2362,20 +2362,19 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction, boolean appliedPose) {
PhysicsConstraint constraint;
if (constraintIndex == -1) {
float value = time >= frames[0] ? getCurveValue(time) : 0;
Object[] constraints = skeleton.physicsConstraints.items;
for (int i = 0, n = skeleton.physicsConstraints.size; i < n; i++) {
constraint = (PhysicsConstraint)constraints[i];
Object[] constraints = skeleton.physics.items;
for (int i = 0, n = skeleton.physics.size; i < n; i++) {
var constraint = (PhysicsConstraint)constraints[i];
if (constraint.active && global(constraint.data)) {
PhysicsConstraintPose pose = appliedPose ? constraint.applied : constraint.pose;
set(pose, getAbsoluteValue(time, alpha, blend, get(pose), get(constraint.data.setup), value));
}
}
} else {
constraint = skeleton.physicsConstraints.get(constraintIndex);
var constraint = (PhysicsConstraint)skeleton.constraints.get(constraintIndex);
if (constraint.active) {
PhysicsConstraintPose pose = appliedPose ? constraint.applied : constraint.pose;
set(pose, getAbsoluteValue(time, alpha, blend, get(pose), get(constraint.data.setup)));
@ -2557,7 +2556,7 @@ public class Animation {
PhysicsConstraint constraint = null;
if (constraintIndex != -1) {
constraint = skeleton.physicsConstraints.get(constraintIndex);
constraint = (PhysicsConstraint)skeleton.constraints.get(constraintIndex);
if (!constraint.active) return;
}
@ -2574,8 +2573,8 @@ public class Animation {
if (constraint != null)
constraint.reset(skeleton);
else {
Object[] constraints = skeleton.physicsConstraints.items;
for (int i = 0, n = skeleton.physicsConstraints.size; i < n; i++) {
Object[] constraints = skeleton.physics.items;
for (int i = 0, n = skeleton.physics.size; i < n; i++) {
constraint = (PhysicsConstraint)constraints[i];
if (constraint.active) constraint.reset(skeleton);
}

View File

@ -1,10 +1,21 @@
package com.esotericsoftware.spine;
abstract public class Constraint<D extends PosedData<P>, P extends Pose> extends PosedActive<D, P, P> implements Update {
abstract public class Constraint< //
T extends Constraint<T, D, P>, //
D extends ConstraintData<T, 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 ();
abstract public T copy (Skeleton skeleton);
abstract void sort (Skeleton skeleton);
boolean isSourceActive () {
return true;
}
}

View File

@ -0,0 +1,14 @@
package com.esotericsoftware.spine;
abstract public class ConstraintData< //
T extends Constraint, //
P extends Pose> //
extends PosedData<P> {
public ConstraintData (String name, P setup) {
super(name, setup);
}
abstract public T create (Skeleton skeleton);
}

View File

@ -39,7 +39,7 @@ import com.esotericsoftware.spine.AnimationState.AnimationStateListener;
* AnimationStateListener {@link AnimationStateListener#event(com.esotericsoftware.spine.AnimationState.TrackEntry, Event)}, and
* <a href="https://esotericsoftware.com/spine-events">Events</a> in the Spine User Guide. */
public class Event {
private final EventData data;
final EventData data;
int intValue;
float floatValue;
String stringValue;

View File

@ -39,7 +39,7 @@ 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 extends Constraint<IkConstraintData, IkConstraintPose> {
public class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkConstraintPose> {
final Array<BonePose> bones;
Bone target;
@ -54,10 +54,10 @@ public class IkConstraint extends Constraint<IkConstraintData, IkConstraintPose>
target = skeleton.bones.get(data.target.index);
}
/** Copy constructor. */
public IkConstraint (IkConstraint constraint, Skeleton skeleton) {
this(constraint.data, skeleton);
pose.set(constraint.pose);
public IkConstraint copy (Skeleton skeleton) {
var copy = new IkConstraint(data, skeleton);
copy.pose.set(pose);
return copy;
}
/** Applies the constraint to the constrained bones. */
@ -73,8 +73,29 @@ public class IkConstraint extends Constraint<IkConstraintData, IkConstraintPose>
}
}
public void sort () {
// BOZO
void sort (Skeleton skeleton) {
skeleton.sortBone(target);
Bone parent = bones.first().bone;
skeleton.sortBone(parent);
skeleton.resetCache(parent);
if (bones.size == 1) {
skeleton.updateCache.add(this);
skeleton.sortReset(parent.children);
} else {
Bone child = bones.peek().bone;
skeleton.resetCache(child);
skeleton.sortBone(child);
skeleton.updateCache.add(this);
skeleton.sortReset(parent.children);
child.sorted = true;
}
}
boolean isSourceActive () {
return target.active;
}
/** The 1 or 2 bones that will be modified by this IK constraint. */

View File

@ -34,7 +34,7 @@ import com.badlogic.gdx.utils.Array;
/** Stores the setup pose for an {@link IkConstraint}.
* <p>
* See <a href="https://esotericsoftware.com/spine-ik-constraints">IK constraints</a> in the Spine User Guide. */
public class IkConstraintData extends PosedData<IkConstraintPose> {
public class IkConstraintData extends ConstraintData<IkConstraint, IkConstraintPose> {
final Array<BoneData> bones = new Array();
BoneData target;
boolean uniform;
@ -43,6 +43,10 @@ public class IkConstraintData extends PosedData<IkConstraintPose> {
super(name, new IkConstraintPose());
}
public IkConstraint create (Skeleton skeleton) {
return new IkConstraint(this, skeleton);
}
/** The bones that are constrained by this IK constraint. */
public Array<BoneData> getBones () {
return bones;

View File

@ -39,13 +39,15 @@ import com.badlogic.gdx.utils.FloatArray;
import com.esotericsoftware.spine.PathConstraintData.PositionMode;
import com.esotericsoftware.spine.PathConstraintData.RotateMode;
import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
import com.esotericsoftware.spine.Skin.SkinEntry;
import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
/** Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the
* 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 extends Constraint<PathConstraintData, PathConstraintPose> {
public class PathConstraint extends Constraint<PathConstraint, PathConstraintData, PathConstraintPose> {
static final int NONE = -1, BEFORE = -2, AFTER = -3;
static final float epsilon = 0.00001f;
@ -67,10 +69,10 @@ public class PathConstraint extends Constraint<PathConstraintData, PathConstrain
slot = skeleton.slots.get(data.slot.index);
}
/** Copy constructor. */
public PathConstraint (PathConstraint constraint, Skeleton skeleton) {
this(constraint.data, skeleton);
pose.set(constraint.pose);
public PathConstraint copy (Skeleton skeleton) {
var copy = new PathConstraint(data, skeleton);
copy.pose.set(pose);
return copy;
}
/** Applies the constraint to the constrained bones. */
@ -456,7 +458,57 @@ public class PathConstraint extends Constraint<PathConstraintData, PathConstrain
}
}
public void sort () {
void sort (Skeleton skeleton) {
int slotIndex = slot.getData().index;
Bone slotBone = slot.bone;
if (skeleton.skin != null) sortPathConstraintAttachment(skeleton, skeleton.skin, slotIndex, slotBone);
if (skeleton.data.defaultSkin != null && skeleton.data.defaultSkin != skeleton.skin)
sortPathConstraintAttachment(skeleton, skeleton.data.defaultSkin, slotIndex, slotBone);
sortPathConstraintAttachment(skeleton, slot.pose.attachment, slotBone);
Object[] bones = this.bones.items;
int boneCount = this.bones.size;
for (int i = 0; i < boneCount; i++) {
Bone bone = ((BonePose)bones[i]).bone;
skeleton.resetCache(bone);
skeleton.sortBone(bone);
}
skeleton.updateCache.add(this);
for (int i = 0; i < boneCount; i++)
skeleton.sortReset(((BonePose)bones[i]).bone.children);
for (int i = 0; i < boneCount; i++)
((BonePose)bones[i]).bone.sorted = true;
}
private void sortPathConstraintAttachment (Skeleton skeleton, Skin skin, int slotIndex, Bone slotBone) {
Object[] entries = skin.attachments.orderedItems().items;
for (int i = 0, n = skin.attachments.size; i < n; i++) {
var entry = (SkinEntry)entries[i];
if (entry.slotIndex == slotIndex) sortPathConstraintAttachment(skeleton, entry.attachment, slotBone);
}
}
private void sortPathConstraintAttachment (Skeleton skeleton, Attachment attachment, Bone slotBone) {
if (!(attachment instanceof PathAttachment pathAttachment)) return;
int[] pathBones = pathAttachment.getBones();
if (pathBones == null)
skeleton.sortBone(slotBone);
else {
Object[] bones = skeleton.bones.items;
for (int i = 0, n = pathBones.length; i < n;) {
int nn = pathBones[i++];
nn += i;
while (i < nn)
skeleton.sortBone((Bone)bones[pathBones[i++]]);
}
}
}
boolean isSourceActive () {
return slot.bone.active;
}
/** The bones that will be modified by this path constraint. */

View File

@ -34,7 +34,7 @@ import com.badlogic.gdx.utils.Array;
/** Stores the setup pose for a {@link PathConstraint}.
* <p>
* See <a href="https://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide. */
public class PathConstraintData extends PosedData<PathConstraintPose> {
public class PathConstraintData extends ConstraintData<PathConstraint, PathConstraintPose> {
final Array<BoneData> bones = new Array();
SlotData slot;
PositionMode positionMode;
@ -46,6 +46,10 @@ public class PathConstraintData extends PosedData<PathConstraintPose> {
super(name, new PathConstraintPose());
}
public PathConstraint create (Skeleton skeleton) {
return new PathConstraint(this, skeleton);
}
/** The bones that will be modified by this path constraint. */
public Array<BoneData> getBones () {
return bones;

View File

@ -34,7 +34,7 @@ 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 extends Constraint<PhysicsConstraintData, PhysicsConstraintPose> {
public class PhysicsConstraint extends Constraint<PhysicsConstraint, PhysicsConstraintData, PhysicsConstraintPose> {
BonePose bone;
boolean reset = true;
@ -52,10 +52,10 @@ public class PhysicsConstraint extends Constraint<PhysicsConstraintData, Physics
bone = skeleton.bones.get(data.bone.index).constrained;
}
/** Copy constructor. */
public PhysicsConstraint (PhysicsConstraint constraint, Skeleton skeleton) {
this(constraint.data, skeleton);
pose.set(constraint.pose);
public PhysicsConstraint copy (Skeleton skeleton) {
var copy = new PhysicsConstraint(data, skeleton);
copy.pose.set(pose);
return copy;
}
public void reset (Skeleton skeleton) {
@ -255,7 +255,19 @@ public class PhysicsConstraint extends Constraint<PhysicsConstraintData, Physics
bone.updateLocalTransform(skeleton);
}
public void sort () {
void sort (Skeleton skeleton) {
Bone bone = this.bone.bone;
skeleton.sortBone(bone);
skeleton.resetCache(bone);
skeleton.updateCache.add(this);
skeleton.sortReset(bone.children);
bone.sorted = true;
}
boolean isSourceActive () {
return bone.bone.active;
}
/** The bone constrained by this physics constraint. */

View File

@ -32,7 +32,7 @@ package com.esotericsoftware.spine;
/** 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 PhysicsConstraintData extends PosedData<PhysicsConstraintPose> {
public class PhysicsConstraintData extends ConstraintData<PhysicsConstraint, PhysicsConstraintPose> {
BoneData bone;
float x, y, rotate, scaleX, shearX, limit, step;
boolean inertiaGlobal, strengthGlobal, dampingGlobal, massGlobal, windGlobal, gravityGlobal, mixGlobal;
@ -41,6 +41,10 @@ public class PhysicsConstraintData extends PosedData<PhysicsConstraintPose> {
super(name, new PhysicsConstraintPose());
}
public PhysicsConstraint create (Skeleton skeleton) {
return new PhysicsConstraint(this, skeleton);
}
/** The bone constrained by this physics constraint. */
public BoneData getBone () {
return bone;

View File

@ -1,7 +1,11 @@
package com.esotericsoftware.spine;
abstract public class Posed<D extends PosedData<P>, P extends Pose, A extends P> {
abstract public class Posed< //
D extends PosedData<P>, //
P extends Pose, //
A extends P> {
final D data;
final P pose;
final A constrained;
@ -13,7 +17,6 @@ abstract public class Posed<D extends PosedData<P>, P extends Pose, A extends P>
this.pose = pose;
this.constrained = constrained;
applied = (A)pose;
setupPose();
}
public void setupPose () {

View File

@ -1,11 +1,17 @@
package com.esotericsoftware.spine;
abstract public class PosedActive<D extends PosedData<P>, P extends Pose, A extends P> extends Posed<D, P, A> {
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);
setupPose();
}
/** Returns false when this constraint won't be updated by

View File

@ -35,16 +35,6 @@ abstract public class PosedData<P extends Pose> {
final P setup;
boolean skinRequired;
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.");
this.name = name;

View File

@ -37,13 +37,9 @@ import com.badlogic.gdx.utils.Array;
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;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.utils.SkeletonClipping;
@ -58,11 +54,8 @@ public class Skeleton {
final Array<Bone> bones;
final Array<Slot> slots;
Array<Slot> drawOrder;
final Array<Slider> sliders;
final Array<IkConstraint> ikConstraints;
final Array<TransformConstraint> transformConstraints;
final Array<PathConstraint> pathConstraints;
final Array<PhysicsConstraint> physicsConstraints;
final Array<Constraint> constraints;
final Array<PhysicsConstraint> physics;
final Array updateCache = new Array();
final Array<Posed> resetCache = new Array();
@Null Skin skin;
@ -95,25 +88,13 @@ public class Skeleton {
drawOrder.add(slot);
}
sliders = new Array(data.sliders.size);
for (SliderData constraint : data.sliders)
sliders.add(new Slider(constraint));
ikConstraints = new Array(data.ikConstraints.size);
for (IkConstraintData constraint : data.ikConstraints)
ikConstraints.add(new IkConstraint(constraint, this));
transformConstraints = new Array(data.transformConstraints.size);
for (TransformConstraintData constraint : data.transformConstraints)
transformConstraints.add(new TransformConstraint(constraint, this));
pathConstraints = new Array(data.pathConstraints.size);
for (PathConstraintData constraint : data.pathConstraints)
pathConstraints.add(new PathConstraint(constraint, this));
physicsConstraints = new Array(data.physicsConstraints.size);
for (PhysicsConstraintData constraint : data.physicsConstraints)
physicsConstraints.add(new PhysicsConstraint(constraint, this));
constraints = new Array(data.constraints.size);
physics = new Array();
for (ConstraintData constraintData : data.constraints) {
Constraint constraint = constraintData.create(this);
if (constraint instanceof PhysicsConstraint physicsConstraint) physics.add(physicsConstraint);
constraints.add(constraint);
}
color = new Color(1, 1, 1, 1);
@ -141,32 +122,20 @@ public class Skeleton {
slots = new Array(skeleton.slots.size);
for (Slot slot : skeleton.slots) {
Bone bone = bones.get(slot.bone.data.index);
slots.add(new Slot(slot, bone));
slots.add(new Slot(slot, bone, this));
}
drawOrder = new Array(slots.size);
for (Slot slot : skeleton.drawOrder)
drawOrder.add(slots.get(slot.data.index));
sliders = new Array(skeleton.sliders.size);
for (Slider constraint : skeleton.sliders)
sliders.add(new Slider(constraint));
ikConstraints = new Array(skeleton.ikConstraints.size);
for (IkConstraint constraint : skeleton.ikConstraints)
ikConstraints.add(new IkConstraint(constraint, skeleton));
transformConstraints = new Array(skeleton.transformConstraints.size);
for (TransformConstraint constraint : skeleton.transformConstraints)
transformConstraints.add(new TransformConstraint(constraint, skeleton));
pathConstraints = new Array(skeleton.pathConstraints.size);
for (PathConstraint constraint : skeleton.pathConstraints)
pathConstraints.add(new PathConstraint(constraint, skeleton));
physicsConstraints = new Array(skeleton.physicsConstraints.size);
for (PhysicsConstraint constraint : skeleton.physicsConstraints)
physicsConstraints.add(new PhysicsConstraint(constraint, skeleton));
physics = new Array(skeleton.physics.size);
constraints = new Array(skeleton.constraints.size);
for (Constraint other : skeleton.constraints) {
Constraint constraint = other.copy(this);
if (constraint instanceof PhysicsConstraint physicsConstraint) physics.add(physicsConstraint);
constraints.add(constraint);
}
skin = skeleton.skin;
color = new Color(skeleton.color);
@ -194,9 +163,9 @@ public class Skeleton {
bone.setConstrained(false);
}
if (skin != null) {
Object[] skinBones = skin.bones.items;
Object[] objects = skin.bones.items;
for (int i = 0, n = skin.bones.size; i < n; i++) {
var bone = (Bone)bones[((BoneData)skinBones[i]).index];
var bone = (Bone)objects[((BoneData)objects[i]).index];
do {
bone.sorted = false;
bone.active = true;
@ -205,232 +174,34 @@ public class Skeleton {
}
}
int sliderCount = sliders.size, ikCount = ikConstraints.size, transformCount = transformConstraints.size,
pathCount = pathConstraints.size, physicsCount = physicsConstraints.size;
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++) {
for (int ii = 0; ii < sliderCount; ii++) {
var constraint = (Slider)sliders[ii];
if (constraint.data.order == i) {
sortSlider(constraint);
continue outer;
}
}
for (int ii = 0; ii < ikCount; ii++) {
var constraint = (IkConstraint)ikConstraints[ii];
if (constraint.data.order == i) {
sortIkConstraint(constraint);
continue outer;
}
}
for (int ii = 0; ii < transformCount; ii++) {
var constraint = (TransformConstraint)transformConstraints[ii];
if (constraint.data.order == i) {
sortTransformConstraint(constraint);
continue outer;
}
}
for (int ii = 0; ii < pathCount; ii++) {
var constraint = (PathConstraint)pathConstraints[ii];
if (constraint.data.order == i) {
sortPathConstraint(constraint);
continue outer;
}
}
for (int ii = 0; ii < physicsCount; ii++) {
var constraint = (PhysicsConstraint)physicsConstraints[ii];
if (constraint.data.order == i) {
sortPhysicsConstraint(constraint);
continue outer;
}
}
Object[] objects = constraints.items;
int n = constraints.size;
for (int i = 0; i < n; i++)
((Constraint)objects[i]).setConstrained(false);
for (int i = 0; i < n; i++) {
var constraint = (Constraint<?, ?, ?>)objects[i];
constraint.active = constraint.isSourceActive()
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)));
if (constraint.active) constraint.sort(this);
}
for (int i = 0; i < boneCount; i++)
sortBone((Bone)bones[i]);
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;
objects = this.updateCache.items;
n = this.updateCache.size;
for (int i = 0; i < n; i++)
if (objects[i] instanceof Bone bone) objects[i] = bone.applied;
}
private void sortIkConstraint (IkConstraint constraint) {
constraint.active = constraint.target.active
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)));
if (!constraint.active) return;
sortBone(constraint.target);
Array<BonePose> 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);
sortReset(parent.children);
child.sorted = true;
}
}
private void sortTransformConstraint (TransformConstraint constraint) {
constraint.active = constraint.source.active
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)));
if (!constraint.active) return;
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 = ((BonePose)constrained[i]).bone;
resetCache(child);
sortBone(child.parent);
sortBone(child);
}
} else {
for (int i = 0; i < boneCount; i++) {
Bone bone = ((BonePose)constrained[i]).bone;
resetCache(bone);
sortBone(bone);
}
}
updateCache.add(constraint);
for (int i = 0; i < boneCount; i++)
sortReset(((BonePose)constrained[i]).bone.children);
for (int i = 0; i < boneCount; i++)
((BonePose)constrained[i]).bone.sorted = true;
}
private void sortPathConstraint (PathConstraint constraint) {
constraint.active = constraint.slot.bone.active
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)));
if (!constraint.active) return;
Slot slot = constraint.slot;
int slotIndex = slot.getData().index;
Bone slotBone = slot.bone;
if (skin != null) sortPathConstraintAttachment(skin, slotIndex, slotBone);
if (data.defaultSkin != null && data.defaultSkin != skin)
sortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone);
sortPathConstraintAttachment(slot.pose.attachment, slotBone);
Object[] constrained = constraint.bones.items;
int boneCount = constraint.bones.size;
for (int i = 0; i < boneCount; i++) {
Bone bone = ((BonePose)constrained[i]).bone;
resetCache(bone);
sortBone(bone);
}
updateCache.add(constraint);
for (int i = 0; i < boneCount; i++)
sortReset(((BonePose)constrained[i]).bone.children);
for (int i = 0; i < boneCount; i++)
((BonePose)constrained[i]).bone.sorted = true;
}
private void sortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
Object[] entries = skin.attachments.orderedItems().items;
for (int i = 0, n = skin.attachments.size; i < n; i++) {
var entry = (SkinEntry)entries[i];
if (entry.slotIndex == slotIndex) sortPathConstraintAttachment(entry.attachment, slotBone);
}
}
private void sortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
if (!(attachment instanceof PathAttachment pathAttachment)) return;
int[] pathBones = pathAttachment.getBones();
if (pathBones == null)
sortBone(slotBone);
else {
Object[] bones = this.bones.items;
for (int i = 0, n = pathBones.length; i < n;) {
int nn = pathBones[i++];
nn += i;
while (i < nn)
sortBone((Bone)bones[pathBones[i++]]);
}
}
}
private void sortPhysicsConstraint (PhysicsConstraint constraint) {
Bone bone = constraint.bone.bone;
constraint.active = bone.active
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)));
if (!constraint.active) return;
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 (Posed object) {
if (!resetCache.contains(object, true)) {
void resetCache (Posed object) {
if (!resetCache.contains(object, true)) { // BOZO
resetCache.add(object);
object.setConstrained(true);
}
}
private void sortBone (Bone bone) {
void sortBone (Bone bone) {
if (bone.sorted) return;
Bone parent = bone.parent;
if (parent != null) sortBone(parent);
@ -438,7 +209,7 @@ public class Skeleton {
updateCache.add(bone);
}
private void sortReset (Array<Bone> bones) {
void sortReset (Array<Bone> bones) {
Object[] items = bones.items;
for (int i = 0, n = bones.size; i < n; i++) {
var bone = (Bone)items[i];
@ -453,13 +224,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[] resetCache = this.resetCache.items;
Object[] objects = this.resetCache.items;
for (int i = 0, n = this.resetCache.size; i < n; i++)
((Posed)resetCache[i]).resetAppliedPose();
((Posed)objects[i]).resetAppliedPose();
Object[] updateCache = this.updateCache.items;
objects = this.updateCache.items;
for (int i = 0, n = this.updateCache.size; i < n; i++)
((Update)updateCache[i]).update(this, physics);
((Update)objects[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
@ -470,9 +241,9 @@ public class Skeleton {
public void updateWorldTransform (Physics physics, BonePose parent) {
if (parent == null) throw new IllegalArgumentException("parent cannot be null.");
Object[] resetCache = this.resetCache.items;
Object[] objects = this.resetCache.items;
for (int i = 0, n = this.resetCache.size; i < n; i++)
((Posed)resetCache[i]).resetAppliedPose();
((Posed)objects[i]).resetAppliedPose();
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
BonePose rootBone = getRootBone().applied;
@ -492,9 +263,9 @@ public class Skeleton {
rootBone.d = (pc * lb + pd * ld) * scaleY;
// Update everything except root bone.
Object[] updateCache = this.updateCache.items;
objects = this.updateCache.items;
for (int i = 0, n = this.updateCache.size; i < n; i++) {
var updatable = (Update)updateCache[i];
var updatable = (Update)objects[i];
if (updatable != rootBone) updatable.update(this, physics);
}
}
@ -507,29 +278,13 @@ public class Skeleton {
/** Sets the bones and constraints to their setup pose values. */
public void setupPoseBones () {
Object[] bones = this.bones.items;
Object[] objects = this.bones.items;
for (int i = 0, n = this.bones.size; i < n; i++)
((Bone)bones[i]).setupPose();
((Bone)objects[i]).setupPose();
Object[] constraints = sliders.items;
for (int i = 0, n = sliders.size; i < n; i++)
((Slider)constraints[i]).setupPose();
constraints = ikConstraints.items;
for (int i = 0, n = ikConstraints.size; i < n; i++)
((IkConstraint)constraints[i]).setupPose();
constraints = transformConstraints.items;
for (int i = 0, n = transformConstraints.size; i < n; i++)
((TransformConstraint)constraints[i]).setupPose();
constraints = pathConstraints.items;
for (int i = 0, n = pathConstraints.size; i < n; i++)
((PathConstraint)constraints[i]).setupPose();
constraints = physicsConstraints.items;
for (int i = 0, n = physicsConstraints.size; i < n; i++)
((PhysicsConstraint)constraints[i]).setupPose();
objects = constraints.items;
for (int i = 0, n = constraints.size; i < n; i++)
((Constraint)objects[i]).setupPose();
}
/** Sets the slots and draw order to their setup pose values. */
@ -684,87 +439,22 @@ public class Skeleton {
slot.pose.setAttachment(attachment);
}
/** The skeleton's sliders. */
public Array<Slider> getSliders () {
return sliders;
/** The skeleton's constraints. */
public Array<Constraint> getConstraints () {
return constraints;
}
/** Finds a slider by comparing each slider's name. It is more efficient to cache the results of this method than to call it
* repeatedly. */
public @Null Slider findSlider (String constraintName) {
if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
Object[] sliders = this.sliders.items;
for (int i = 0, n = this.sliders.size; i < n; i++) {
var slider = (Slider)sliders[i];
if (slider.data.name.equals(constraintName)) return slider;
}
return null;
}
/** The skeleton's IK constraints. */
public Array<IkConstraint> getIkConstraints () {
return ikConstraints;
}
/** Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method
* than to call it repeatedly. */
public @Null IkConstraint findIkConstraint (String constraintName) {
if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
Object[] ikConstraints = this.ikConstraints.items;
for (int i = 0, n = this.ikConstraints.size; i < n; i++) {
var ikConstraint = (IkConstraint)ikConstraints[i];
if (ikConstraint.data.name.equals(constraintName)) return ikConstraint;
}
return null;
}
/** The skeleton's transform constraints. */
public Array<TransformConstraint> getTransformConstraints () {
return transformConstraints;
}
/** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
* this method than to call it repeatedly. */
public @Null TransformConstraint findTransformConstraint (String constraintName) {
if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
Object[] transformConstraints = this.transformConstraints.items;
for (int i = 0, n = this.transformConstraints.size; i < n; i++) {
var constraint = (TransformConstraint)transformConstraints[i];
if (constraint.data.name.equals(constraintName)) return constraint;
}
return null;
}
/** The skeleton's path constraints. */
public Array<PathConstraint> getPathConstraints () {
return pathConstraints;
}
/** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
* than to call it repeatedly. */
public @Null PathConstraint findPathConstraint (String constraintName) {
if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
Object[] pathConstraints = this.pathConstraints.items;
for (int i = 0, n = this.pathConstraints.size; i < n; i++) {
var constraint = (PathConstraint)pathConstraints[i];
if (constraint.data.name.equals(constraintName)) return constraint;
}
return null;
}
/** The skeleton's physics constraints. */
public Array<PhysicsConstraint> getPhysicsConstraints () {
return physicsConstraints;
return physics;
}
/** Finds a physics constraint by comparing each physics constraint's name. It is more efficient to cache the results of this
* method than to call it repeatedly. */
public @Null PhysicsConstraint findPhysicsConstraint (String constraintName) {
public @Null <T extends Constraint> T findConstraint (String constraintName, Class<T> type) {
if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
Object[] physicsConstraints = this.physicsConstraints.items;
for (int i = 0, n = this.physicsConstraints.size; i < n; i++) {
var constraint = (PhysicsConstraint)physicsConstraints[i];
if (constraint.data.name.equals(constraintName)) return constraint;
if (type == null) throw new IllegalArgumentException("type cannot be null.");
Object[] constraints = this.constraints.items;
for (int i = 0, n = this.constraints.size; i < n; i++) {
Object constraint = constraints[i];
if (type.isInstance(constraint) && ((PosedData)constraint).name.equals(constraintName)) return (T)constraint;
}
return null;
}
@ -796,31 +486,33 @@ public class Skeleton {
float[] vertices = null;
short[] triangles = null;
Attachment attachment = slot.pose.attachment;
if (attachment instanceof RegionAttachment region) {
verticesLength = 8;
vertices = temp.setSize(8);
region.computeWorldVertices(slot, vertices, 0, 2);
triangles = quadTriangles;
} else if (attachment instanceof MeshAttachment mesh) {
verticesLength = mesh.getWorldVerticesLength();
vertices = temp.setSize(verticesLength);
mesh.computeWorldVertices(this, slot, 0, verticesLength, vertices, 0, 2);
triangles = mesh.getTriangles();
} else if (attachment instanceof ClippingAttachment clip && clipper != null) {
clipper.clipStart(this, slot, clip);
continue;
}
if (vertices != null) {
if (clipper != null && clipper.isClipping() && clipper.clipTriangles(vertices, triangles, triangles.length)) {
vertices = clipper.getClippedVertices().items;
verticesLength = clipper.getClippedVertices().size;
if (attachment != null) {
if (attachment instanceof RegionAttachment region) {
verticesLength = 8;
vertices = temp.setSize(8);
region.computeWorldVertices(slot, vertices, 0, 2);
triangles = quadTriangles;
} else if (attachment instanceof MeshAttachment mesh) {
verticesLength = mesh.getWorldVerticesLength();
vertices = temp.setSize(verticesLength);
mesh.computeWorldVertices(this, slot, 0, verticesLength, vertices, 0, 2);
triangles = mesh.getTriangles();
} else if (attachment instanceof ClippingAttachment clip && clipper != null) {
clipper.clipStart(this, slot, clip);
continue;
}
for (int ii = 0; ii < verticesLength; ii += 2) {
float x = vertices[ii], y = vertices[ii + 1];
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
if (vertices != null) {
if (clipper != null && clipper.isClipping() && clipper.clipTriangles(vertices, triangles, triangles.length)) {
vertices = clipper.getClippedVertices().items;
verticesLength = clipper.getClippedVertices().size;
}
for (int ii = 0; ii < verticesLength; ii += 2) {
float x = vertices[ii], y = vertices[ii + 1];
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
}
}
if (clipper != null) clipper.clipEnd(slot);
@ -908,15 +600,15 @@ public class Skeleton {
/** Calls {@link PhysicsConstraint#translate(float, float)} for each physics constraint. */
public void physicsTranslate (float x, float y) {
Object[] physicsConstraints = this.physicsConstraints.items;
for (int i = 0, n = this.physicsConstraints.size; i < n; i++)
Object[] physicsConstraints = this.physics.items;
for (int i = 0, n = this.physics.size; i < n; i++)
((PhysicsConstraint)physicsConstraints[i]).translate(x, y);
}
/** Calls {@link PhysicsConstraint#rotate(float, float, float)} for each physics constraint. */
public void physicsRotate (float x, float y, float degrees) {
Object[] physicsConstraints = this.physicsConstraints.items;
for (int i = 0, n = this.physicsConstraints.size; i < n; i++)
Object[] physicsConstraints = this.physics.items;
for (int i = 0, n = this.physics.size; i < n; i++)
((PhysicsConstraint)physicsConstraints[i]).rotate(x, y, degrees);
}

View File

@ -137,6 +137,11 @@ public class SkeletonBinary extends SkeletonLoader {
static public final int SLOT_RGB2 = 4;
static public final int SLOT_ALPHA = 5;
static public final int CONSTRAINT_IK = 0;
static public final int CONSTRAINT_PATH = 1;
static public final int CONSTRAINT_TRANSFORM = 2;
static public final int CONSTRAINT_PHYSICS = 3;
static public final int ATTACHMENT_DEFORM = 0;
static public final int ATTACHMENT_SEQUENCE = 1;
@ -253,164 +258,155 @@ public class SkeletonBinary extends SkeletonLoader {
slots[i] = data;
}
// IK constraints.
o = skeletonData.ikConstraints.setSize(n = input.readInt(true));
// Constraints.
o = skeletonData.constraints.setSize(n = input.readInt(true));
for (int i = 0, nn; i < n; i++) {
var data = new IkConstraintData(input.readString());
data.order = input.readInt(true);
Object[] constraintBones = data.bones.setSize(nn = input.readInt(true));
for (int ii = 0; ii < nn; ii++)
constraintBones[ii] = bones[input.readInt(true)];
data.target = (BoneData)bones[input.readInt(true)];
int flags = input.read();
data.skinRequired = (flags & 1) != 0;
data.uniform = (flags & 2) != 0;
IkConstraintPose setup = data.setup;
setup.bendDirection = (flags & 4) != 0 ? 1 : -1;
setup.compress = (flags & 8) != 0;
setup.stretch = (flags & 16) != 0;
if ((flags & 32) != 0) setup.mix = (flags & 64) != 0 ? input.readFloat() : 1;
if ((flags & 128) != 0) setup.softness = input.readFloat() * scale;
o[i] = data;
}
// Transform constraints.
o = skeletonData.transformConstraints.setSize(n = input.readInt(true));
for (int i = 0, nn; i < n; i++) {
var data = new TransformConstraintData(input.readString());
data.order = input.readInt(true);
Object[] constraintBones = data.bones.setSize(nn = input.readInt(true));
for (int ii = 0; ii < nn; ii++)
constraintBones[ii] = bones[input.readInt(true)];
data.source = (BoneData)bones[input.readInt(true)];
int flags = input.read();
data.skinRequired = (flags & 1) != 0;
data.localSource = (flags & 2) != 0;
data.localTarget = (flags & 4) != 0;
data.additive = (flags & 8) != 0;
data.clamp = (flags & 16) != 0;
Object[] froms = data.properties.setSize(nn = flags >> 5);
for (int ii = 0, tn; ii < nn; ii++) {
float fromScale = 1;
FromProperty from;
switch (input.readByte()) {
case 0 -> from = new FromRotate();
case 1 -> {
from = new FromX();
fromScale = scale;
}
case 2 -> {
from = new FromY();
fromScale = scale;
}
case 3 -> from = new FromScaleX();
case 4 -> from = new FromScaleY();
case 5 -> from = new FromShearY();
default -> from = null;
}
from.offset = input.readFloat() * fromScale;
Object[] tos = from.to.setSize(tn = input.readByte());
for (int t = 0; t < tn; t++) {
float toScale = 1;
ToProperty to;
switch (input.readByte()) {
case CONSTRAINT_IK -> {
var data = new IkConstraintData(input.readString());
Object[] constraintBones = data.bones.setSize(nn = input.readInt(true));
for (int ii = 0; ii < nn; ii++)
constraintBones[ii] = bones[input.readInt(true)];
data.target = (BoneData)bones[input.readInt(true)];
int flags = input.read();
data.skinRequired = (flags & 1) != 0;
data.uniform = (flags & 2) != 0;
IkConstraintPose setup = data.setup;
setup.bendDirection = (flags & 4) != 0 ? 1 : -1;
setup.compress = (flags & 8) != 0;
setup.stretch = (flags & 16) != 0;
if ((flags & 32) != 0) setup.mix = (flags & 64) != 0 ? input.readFloat() : 1;
if ((flags & 128) != 0) setup.softness = input.readFloat() * scale;
o[i] = data;
}
case CONSTRAINT_TRANSFORM -> {
var data = new TransformConstraintData(input.readString());
Object[] constraintBones = data.bones.setSize(nn = input.readInt(true));
for (int ii = 0; ii < nn; ii++)
constraintBones[ii] = bones[input.readInt(true)];
data.source = (BoneData)bones[input.readInt(true)];
int flags = input.read();
data.skinRequired = (flags & 1) != 0;
data.localSource = (flags & 2) != 0;
data.localTarget = (flags & 4) != 0;
data.additive = (flags & 8) != 0;
data.clamp = (flags & 16) != 0;
Object[] froms = data.properties.setSize(nn = flags >> 5);
for (int ii = 0, tn; ii < nn; ii++) {
float fromScale = 1;
FromProperty from;
switch (input.readByte()) {
case 0 -> to = new ToRotate();
case 0 -> from = new FromRotate();
case 1 -> {
to = new ToX();
toScale = scale;
from = new FromX();
fromScale = scale;
}
case 2 -> {
to = new ToY();
toScale = scale;
from = new FromY();
fromScale = scale;
}
case 3 -> to = new ToScaleX();
case 4 -> to = new ToScaleY();
case 5 -> to = new ToShearY();
default -> to = null;
case 3 -> from = new FromScaleX();
case 4 -> from = new FromScaleY();
case 5 -> from = new FromShearY();
default -> from = null;
}
to.offset = input.readFloat() * toScale;
to.max = input.readFloat() * toScale;
to.scale = input.readFloat() * toScale / fromScale;
tos[t] = to;
from.offset = input.readFloat() * fromScale;
Object[] tos = from.to.setSize(tn = input.readByte());
for (int t = 0; t < tn; t++) {
float toScale = 1;
ToProperty to;
switch (input.readByte()) {
case 0 -> to = new ToRotate();
case 1 -> {
to = new ToX();
toScale = scale;
}
case 2 -> {
to = new ToY();
toScale = scale;
}
case 3 -> to = new ToScaleX();
case 4 -> to = new ToScaleY();
case 5 -> to = new ToShearY();
default -> to = null;
}
to.offset = input.readFloat() * toScale;
to.max = input.readFloat() * toScale;
to.scale = input.readFloat() * toScale / fromScale;
tos[t] = to;
}
froms[ii] = from;
}
froms[ii] = from;
flags = input.read();
if ((flags & 1) != 0) data.offsetRotation = input.readFloat();
if ((flags & 2) != 0) data.offsetX = input.readFloat() * scale;
if ((flags & 4) != 0) data.offsetY = input.readFloat() * scale;
if ((flags & 8) != 0) data.offsetScaleX = input.readFloat();
if ((flags & 16) != 0) data.offsetScaleY = input.readFloat();
if ((flags & 32) != 0) data.offsetShearY = input.readFloat();
flags = input.read();
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();
if ((flags & 8) != 0) setup.mixScaleX = input.readFloat();
if ((flags & 16) != 0) setup.mixScaleY = input.readFloat();
if ((flags & 32) != 0) setup.mixShearY = input.readFloat();
o[i] = data;
}
case CONSTRAINT_PATH -> {
var data = new PathConstraintData(input.readString());
Object[] constraintBones = data.bones.setSize(nn = input.readInt(true));
for (int ii = 0; ii < nn; ii++)
constraintBones[ii] = bones[input.readInt(true)];
data.slot = (SlotData)slots[input.readInt(true)];
int flags = input.read();
data.skinRequired = (flags & 1) != 0;
data.positionMode = PositionMode.values[flags & 2];
data.spacingMode = SpacingMode.values[(flags >> 2) & 3];
data.rotateMode = RotateMode.values[(flags >> 4) & 3];
if ((flags & 128) != 0) data.offsetRotation = input.readFloat();
PathConstraintPose setup = data.setup;
setup.position = input.readFloat();
if (data.positionMode == PositionMode.fixed) setup.position *= scale;
setup.spacing = input.readFloat();
if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) setup.spacing *= scale;
setup.mixRotate = input.readFloat();
setup.mixX = input.readFloat();
setup.mixY = input.readFloat();
o[i] = data;
}
case CONSTRAINT_PHYSICS -> {
var data = new PhysicsConstraintData(input.readString());
data.bone = (BoneData)bones[input.readInt(true)];
int flags = input.read();
data.skinRequired = (flags & 1) != 0;
if ((flags & 2) != 0) data.x = input.readFloat();
if ((flags & 4) != 0) data.y = input.readFloat();
if ((flags & 8) != 0) data.rotate = input.readFloat();
if ((flags & 16) != 0) data.scaleX = input.readFloat();
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.setup;
setup.inertia = input.readFloat();
setup.strength = input.readFloat();
setup.damping = input.readFloat();
setup.massInverse = (flags & 128) != 0 ? input.readFloat() : 1;
setup.wind = input.readFloat();
setup.gravity = input.readFloat();
flags = input.read();
if ((flags & 1) != 0) data.inertiaGlobal = true;
if ((flags & 2) != 0) data.strengthGlobal = true;
if ((flags & 4) != 0) data.dampingGlobal = true;
if ((flags & 8) != 0) data.massGlobal = true;
if ((flags & 16) != 0) data.windGlobal = true;
if ((flags & 32) != 0) data.gravityGlobal = true;
if ((flags & 64) != 0) data.mixGlobal = true;
setup.mix = (flags & 128) != 0 ? input.readFloat() : 1;
o[i] = data;
}
}
flags = input.read();
if ((flags & 1) != 0) data.offsetRotation = input.readFloat();
if ((flags & 2) != 0) data.offsetX = input.readFloat() * scale;
if ((flags & 4) != 0) data.offsetY = input.readFloat() * scale;
if ((flags & 8) != 0) data.offsetScaleX = input.readFloat();
if ((flags & 16) != 0) data.offsetScaleY = input.readFloat();
if ((flags & 32) != 0) data.offsetShearY = input.readFloat();
flags = input.read();
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();
if ((flags & 8) != 0) setup.mixScaleX = input.readFloat();
if ((flags & 16) != 0) setup.mixScaleY = input.readFloat();
if ((flags & 32) != 0) setup.mixShearY = input.readFloat();
o[i] = data;
}
// Path constraints.
o = skeletonData.pathConstraints.setSize(n = input.readInt(true));
for (int i = 0, nn; i < n; i++) {
var data = new PathConstraintData(input.readString());
data.order = input.readInt(true);
data.skinRequired = input.readBoolean();
Object[] constraintBones = data.bones.setSize(nn = input.readInt(true));
for (int ii = 0; ii < nn; ii++)
constraintBones[ii] = bones[input.readInt(true)];
data.slot = (SlotData)slots[input.readInt(true)];
int flags = input.read();
data.positionMode = PositionMode.values[flags & 1];
data.spacingMode = SpacingMode.values[(flags >> 1) & 3];
data.rotateMode = RotateMode.values[(flags >> 3) & 3];
if ((flags & 128) != 0) data.offsetRotation = input.readFloat();
PathConstraintPose setup = data.setup;
setup.position = input.readFloat();
if (data.positionMode == PositionMode.fixed) setup.position *= scale;
setup.spacing = input.readFloat();
if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) setup.spacing *= scale;
setup.mixRotate = input.readFloat();
setup.mixX = input.readFloat();
setup.mixY = input.readFloat();
o[i] = data;
}
// Physics constraints.
o = skeletonData.physicsConstraints.setSize(n = input.readInt(true));
for (int i = 0; i < n; i++) {
var data = new PhysicsConstraintData(input.readString());
data.order = input.readInt(true);
data.bone = (BoneData)bones[input.readInt(true)];
int flags = input.read();
data.skinRequired = (flags & 1) != 0;
if ((flags & 2) != 0) data.x = input.readFloat();
if ((flags & 4) != 0) data.y = input.readFloat();
if ((flags & 8) != 0) data.rotate = input.readFloat();
if ((flags & 16) != 0) data.scaleX = input.readFloat();
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.setup;
setup.inertia = input.readFloat();
setup.strength = input.readFloat();
setup.damping = input.readFloat();
setup.massInverse = (flags & 128) != 0 ? input.readFloat() : 1;
setup.wind = input.readFloat();
setup.gravity = input.readFloat();
flags = input.read();
if ((flags & 1) != 0) data.inertiaGlobal = true;
if ((flags & 2) != 0) data.strengthGlobal = true;
if ((flags & 4) != 0) data.dampingGlobal = true;
if ((flags & 8) != 0) data.massGlobal = true;
if ((flags & 16) != 0) data.windGlobal = true;
if ((flags & 32) != 0) data.gravityGlobal = true;
if ((flags & 64) != 0) data.mixGlobal = true;
setup.mix = (flags & 128) != 0 ? input.readFloat() : 1;
o[i] = data;
}
// Default skin.
@ -487,23 +483,15 @@ public class SkeletonBinary extends SkeletonLoader {
if (nonessential) Color.rgba8888ToColor(skin.color, input.readInt());
Object[] bones = skin.bones.setSize(input.readInt(true)), items = skeletonData.bones.items;
for (int i = 0, n = skin.bones.size; i < n; i++)
bones[i] = items[input.readInt(true)];
int n;
Object[] from = skeletonData.bones.items, to = skin.bones.setSize(n = input.readInt(true));
for (int i = 0; i < n; i++)
to[i] = from[input.readInt(true)];
items = skeletonData.ikConstraints.items;
for (int i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.add((PosedData)items[input.readInt(true)]);
items = skeletonData.transformConstraints.items;
for (int i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.add((PosedData)items[input.readInt(true)]);
items = skeletonData.pathConstraints.items;
for (int i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.add((PosedData)items[input.readInt(true)]);
items = skeletonData.physicsConstraints.items;
for (int i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.add((PosedData)items[input.readInt(true)]);
skin.constraints.shrink();
from = skeletonData.constraints.items;
to = skin.constraints.setSize(n = input.readInt(true));
for (int i = 0; i < n; i++)
to[i] = from[input.readInt(true)];
slotCount = input.readInt(true);
}
@ -981,7 +969,7 @@ public class SkeletonBinary extends SkeletonLoader {
// Path constraint timelines.
for (int i = 0, n = input.readInt(true); i < n; i++) {
int index = input.readInt(true);
PathConstraintData data = skeletonData.pathConstraints.get(index);
var data = (PathConstraintData)skeletonData.constraints.get(index);
for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
int type = input.readByte(), frameCount = input.readInt(true), bezierCount = input.readInt(true);
switch (type) {
@ -1158,7 +1146,7 @@ public class SkeletonBinary extends SkeletonLoader {
event.floatValue = input.readFloat();
event.stringValue = input.readString();
if (event.stringValue == null) event.stringValue = eventData.stringValue;
if (event.getData().audioPath != null) {
if (event.data.audioPath != null) {
event.volume = input.readFloat();
event.balance = input.readFloat();
}

View File

@ -44,11 +44,7 @@ public class SkeletonData {
@Null Skin defaultSkin;
final Array<EventData> events = new Array();
final Array<Animation> animations = new Array();
final Array<SliderData> sliders = new Array();
final Array<IkConstraintData> ikConstraints = new Array();
final Array<TransformConstraintData> transformConstraints = new Array();
final Array<PathConstraintData> pathConstraints = new Array();
final Array<PhysicsConstraintData> physicsConstraints = new Array();
final Array<ConstraintData> constraints = new Array();
float x, y, width, height, referenceScale = 100;
@Null String version, hash;
@ -160,97 +156,20 @@ public class SkeletonData {
return null;
}
// --- Sliders
// --- Constraints.
/** The skeleton's sliders. */
public Array<SliderData> getSliders () {
return sliders;
/** The skeleton's constraints. */
public Array<ConstraintData> getConstraints () {
return constraints;
}
/** Finds a slider by comparing each IK constraint's name. It is more efficient to cache the results of this method than to
* call it multiple times. */
public @Null SliderData findSlider (String constraintName) {
public @Null <T extends ConstraintData> T findConstraint (String constraintName, Class<T> type) {
if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
Object[] sliders = this.sliders.items;
for (int i = 0, n = this.sliders.size; i < n; i++) {
var constraint = (SliderData)sliders[i];
if (constraint.name.equals(constraintName)) return constraint;
}
return null;
}
// --- IK constraints
/** The skeleton's IK constraints. */
public Array<IkConstraintData> getIkConstraints () {
return ikConstraints;
}
/** Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method
* than to call it multiple times. */
public @Null IkConstraintData findIkConstraint (String constraintName) {
if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
Object[] ikConstraints = this.ikConstraints.items;
for (int i = 0, n = this.ikConstraints.size; i < n; i++) {
var constraint = (IkConstraintData)ikConstraints[i];
if (constraint.name.equals(constraintName)) return constraint;
}
return null;
}
// --- Transform constraints
/** The skeleton's transform constraints. */
public Array<TransformConstraintData> getTransformConstraints () {
return transformConstraints;
}
/** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
* this method than to call it multiple times. */
public @Null TransformConstraintData findTransformConstraint (String constraintName) {
if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
Object[] transformConstraints = this.transformConstraints.items;
for (int i = 0, n = this.transformConstraints.size; i < n; i++) {
var constraint = (TransformConstraintData)transformConstraints[i];
if (constraint.name.equals(constraintName)) return constraint;
}
return null;
}
// --- Path constraints
/** The skeleton's path constraints. */
public Array<PathConstraintData> getPathConstraints () {
return pathConstraints;
}
/** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
* than to call it multiple times. */
public @Null PathConstraintData findPathConstraint (String constraintName) {
if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
Object[] pathConstraints = this.pathConstraints.items;
for (int i = 0, n = this.pathConstraints.size; i < n; i++) {
var constraint = (PathConstraintData)pathConstraints[i];
if (constraint.name.equals(constraintName)) return constraint;
}
return null;
}
// --- Physics constraints
/** The skeleton's physics constraints. */
public Array<PhysicsConstraintData> getPhysicsConstraints () {
return physicsConstraints;
}
/** Finds a physics constraint by comparing each physics constraint's name. It is more efficient to cache the results of this
* method than to call it multiple times. */
public @Null PhysicsConstraintData findPhysicsConstraint (String constraintName) {
if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
Object[] physicsConstraints = this.physicsConstraints.items;
for (int i = 0, n = this.physicsConstraints.size; i < n; i++) {
var constraint = (PhysicsConstraintData)physicsConstraints[i];
if (constraint.name.equals(constraintName)) return constraint;
if (type == null) throw new IllegalArgumentException("type cannot be null.");
Object[] constraints = this.constraints.items;
for (int i = 0, n = this.constraints.size; i < n; i++) {
Object constraint = constraints[i];
if (type.isInstance(constraint) && ((PosedData)constraint).name.equals(constraintName)) return (T)constraint;
}
return null;
}

View File

@ -215,198 +215,194 @@ public class SkeletonJson extends SkeletonLoader {
skeletonData.slots.add(data);
}
// IK constraints.
for (JsonValue constraintMap = root.getChild("ik"); constraintMap != null; constraintMap = constraintMap.next) {
var data = new IkConstraintData(constraintMap.getString("name"));
data.order = constraintMap.getInt("order", 0);
data.skinRequired = constraintMap.getBoolean("skin", false);
// Constraints.
for (JsonValue constraintMap = root.getChild("constraints"); constraintMap != null; constraintMap = constraintMap.next) {
switch (constraintMap.getString("type")) {
case "ik" -> {
var data = new IkConstraintData(constraintMap.getString("name"));
data.skinRequired = constraintMap.getBoolean("skin", false);
for (JsonValue entry = constraintMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("IK bone not found: " + entry);
data.bones.add(bone);
for (JsonValue entry = constraintMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("IK bone not found: " + entry);
data.bones.add(bone);
}
String targetName = constraintMap.getString("target");
data.target = skeletonData.findBone(targetName);
if (data.target == null) throw new SerializationException("IK target bone not found: " + targetName);
data.uniform = constraintMap.getBoolean("uniform", false);
IkConstraintPose setup = data.setup;
setup.mix = constraintMap.getFloat("mix", 1);
setup.softness = constraintMap.getFloat("softness", 0) * scale;
setup.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1;
setup.compress = constraintMap.getBoolean("compress", false);
setup.stretch = constraintMap.getBoolean("stretch", false);
skeletonData.constraints.add(data);
}
case "transform" -> {
var data = new TransformConstraintData(constraintMap.getString("name"));
data.skinRequired = constraintMap.getBoolean("skin", false);
String targetName = constraintMap.getString("target");
data.target = skeletonData.findBone(targetName);
if (data.target == null) throw new SerializationException("IK target bone not found: " + targetName);
data.uniform = constraintMap.getBoolean("uniform", false);
IkConstraintPose setup = data.setup;
setup.mix = constraintMap.getFloat("mix", 1);
setup.softness = constraintMap.getFloat("softness", 0) * scale;
setup.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1;
setup.compress = constraintMap.getBoolean("compress", false);
setup.stretch = constraintMap.getBoolean("stretch", false);
skeletonData.ikConstraints.add(data);
}
// Transform constraints.
for (JsonValue constraintMap = root.getChild("transform"); constraintMap != null; constraintMap = constraintMap.next) {
var data = new TransformConstraintData(constraintMap.getString("name"));
data.order = constraintMap.getInt("order", 0);
data.skinRequired = constraintMap.getBoolean("skin", false);
for (JsonValue entry = constraintMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("Transform constraint bone not found: " + entry);
data.bones.add(bone);
}
String sourceName = constraintMap.getString("source");
data.source = skeletonData.findBone(sourceName);
if (data.source == null) throw new SerializationException("Transform constraint source bone not found: " + sourceName);
data.localSource = constraintMap.getBoolean("localSource", false);
data.localTarget = constraintMap.getBoolean("localTarget", false);
data.additive = constraintMap.getBoolean("additive", false);
data.clamp = constraintMap.getBoolean("clamp", false);
boolean rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false;
for (JsonValue fromEntry = constraintMap.getChild("properties"); fromEntry != null; fromEntry = fromEntry.next) {
float fromScale = 1;
FromProperty from;
switch (fromEntry.name) {
case "rotate" -> from = new FromRotate();
case "x" -> {
from = new FromX();
fromScale = scale;
for (JsonValue entry = constraintMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("Transform constraint bone not found: " + entry);
data.bones.add(bone);
}
case "y" -> {
from = new FromY();
fromScale = scale;
}
case "scaleX" -> from = new FromScaleX();
case "scaleY" -> from = new FromScaleY();
case "shearY" -> from = new FromShearY();
default -> throw new SerializationException("Invalid transform constraint from property: " + fromEntry.name);
}
from.offset = fromEntry.getFloat("offset", 0) * fromScale;
for (JsonValue toEntry = fromEntry.getChild("to"); toEntry != null; toEntry = toEntry.next) {
float toScale = 1;
ToProperty to;
switch (toEntry.name) {
case "rotate" -> {
rotate = true;
to = new ToRotate();
}
String sourceName = constraintMap.getString("source");
data.source = skeletonData.findBone(sourceName);
if (data.source == null)
throw new SerializationException("Transform constraint source bone not found: " + sourceName);
data.localSource = constraintMap.getBoolean("localSource", false);
data.localTarget = constraintMap.getBoolean("localTarget", false);
data.additive = constraintMap.getBoolean("additive", false);
data.clamp = constraintMap.getBoolean("clamp", false);
boolean rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false;
for (JsonValue fromEntry = constraintMap.getChild("properties"); fromEntry != null; fromEntry = fromEntry.next) {
float fromScale = 1;
FromProperty from;
switch (fromEntry.name) {
case "rotate" -> from = new FromRotate();
case "x" -> {
x = true;
to = new ToX();
toScale = scale;
from = new FromX();
fromScale = scale;
}
case "y" -> {
y = true;
to = new ToY();
toScale = scale;
from = new FromY();
fromScale = scale;
}
case "scaleX" -> {
scaleX = true;
to = new ToScaleX();
case "scaleX" -> from = new FromScaleX();
case "scaleY" -> from = new FromScaleY();
case "shearY" -> from = new FromShearY();
default -> throw new SerializationException("Invalid transform constraint from property: " + fromEntry.name);
}
case "scaleY" -> {
scaleY = true;
to = new ToScaleY();
from.offset = fromEntry.getFloat("offset", 0) * fromScale;
for (JsonValue toEntry = fromEntry.getChild("to"); toEntry != null; toEntry = toEntry.next) {
float toScale = 1;
ToProperty to;
switch (toEntry.name) {
case "rotate" -> {
rotate = true;
to = new ToRotate();
}
case "x" -> {
x = true;
to = new ToX();
toScale = scale;
}
case "y" -> {
y = true;
to = new ToY();
toScale = scale;
}
case "scaleX" -> {
scaleX = true;
to = new ToScaleX();
}
case "scaleY" -> {
scaleY = true;
to = new ToScaleY();
}
case "shearY" -> {
shearY = true;
to = new ToShearY();
}
default -> throw new SerializationException("Invalid transform constraint to property: " + toEntry.name);
}
to.offset = toEntry.getFloat("offset", 0) * toScale;
to.max = toEntry.getFloat("max", 1) * toScale;
to.scale = toEntry.getFloat("scale") * toScale / fromScale;
from.to.add(to);
}
case "shearY" -> {
shearY = true;
to = new ToShearY();
}
default -> throw new SerializationException("Invalid transform constraint to property: " + toEntry.name);
}
to.offset = toEntry.getFloat("offset", 0) * toScale;
to.max = toEntry.getFloat("max", 1) * toScale;
to.scale = toEntry.getFloat("scale") * toScale / fromScale;
from.to.add(to);
if (from.to.notEmpty()) data.properties.add(from);
}
if (from.to.notEmpty()) data.properties.add(from);
data.offsetRotation = constraintMap.getFloat("rotation", 0);
data.offsetX = constraintMap.getFloat("x", 0) * scale;
data.offsetY = constraintMap.getFloat("y", 0) * scale;
data.offsetScaleX = constraintMap.getFloat("scaleX", 0);
data.offsetScaleY = constraintMap.getFloat("scaleY", 0);
data.offsetShearY = constraintMap.getFloat("shearY", 0);
TransformConstraintPose setup = data.setup;
if (rotate) setup.mixRotate = constraintMap.getFloat("mixRotate", 1);
if (x) setup.mixX = constraintMap.getFloat("mixX", 1);
if (y) setup.mixY = constraintMap.getFloat("mixY", setup.mixX);
if (scaleX) setup.mixScaleX = constraintMap.getFloat("mixScaleX", 1);
if (scaleY) setup.mixScaleY = constraintMap.getFloat("mixScaleY", setup.mixScaleX);
if (shearY) setup.mixShearY = constraintMap.getFloat("mixShearY", 1);
skeletonData.constraints.add(data);
}
case "path" -> {
var data = new PathConstraintData(constraintMap.getString("name"));
data.skinRequired = constraintMap.getBoolean("skin", false);
data.offsetRotation = constraintMap.getFloat("rotation", 0);
data.offsetX = constraintMap.getFloat("x", 0) * scale;
data.offsetY = constraintMap.getFloat("y", 0) * scale;
data.offsetScaleX = constraintMap.getFloat("scaleX", 0);
data.offsetScaleY = constraintMap.getFloat("scaleY", 0);
data.offsetShearY = constraintMap.getFloat("shearY", 0);
for (JsonValue entry = constraintMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("Path bone not found: " + entry);
data.bones.add(bone);
}
TransformConstraintPose setup = data.setup;
if (rotate) setup.mixRotate = constraintMap.getFloat("mixRotate", 1);
if (x) setup.mixX = constraintMap.getFloat("mixX", 1);
if (y) setup.mixY = constraintMap.getFloat("mixY", setup.mixX);
if (scaleX) setup.mixScaleX = constraintMap.getFloat("mixScaleX", 1);
if (scaleY) setup.mixScaleY = constraintMap.getFloat("mixScaleY", setup.mixScaleX);
if (shearY) setup.mixShearY = constraintMap.getFloat("mixShearY", 1);
String slotName = constraintMap.getString("slot");
data.slot = skeletonData.findSlot(slotName);
if (data.slot == null) throw new SerializationException("Path slot not found: " + slotName);
skeletonData.transformConstraints.add(data);
}
data.positionMode = PositionMode.valueOf(constraintMap.getString("positionMode", "percent"));
data.spacingMode = SpacingMode.valueOf(constraintMap.getString("spacingMode", "length"));
data.rotateMode = RotateMode.valueOf(constraintMap.getString("rotateMode", "tangent"));
data.offsetRotation = constraintMap.getFloat("rotation", 0);
PathConstraintPose setup = data.setup;
setup.position = constraintMap.getFloat("position", 0);
if (data.positionMode == PositionMode.fixed) setup.position *= scale;
setup.spacing = constraintMap.getFloat("spacing", 0);
if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) setup.spacing *= scale;
setup.mixRotate = constraintMap.getFloat("mixRotate", 1);
setup.mixX = constraintMap.getFloat("mixX", 1);
setup.mixY = constraintMap.getFloat("mixY", 1);
// Path constraints.
for (JsonValue constraintMap = root.getChild("path"); constraintMap != null; constraintMap = constraintMap.next) {
var data = new PathConstraintData(constraintMap.getString("name"));
data.order = constraintMap.getInt("order", 0);
data.skinRequired = constraintMap.getBoolean("skin", false);
for (JsonValue entry = constraintMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("Path bone not found: " + entry);
data.bones.add(bone);
skeletonData.constraints.add(data);
}
case "physics" -> {
var data = new PhysicsConstraintData(constraintMap.getString("name"));
data.skinRequired = constraintMap.getBoolean("skin", false);
String slotName = constraintMap.getString("slot");
data.slot = skeletonData.findSlot(slotName);
if (data.slot == null) throw new SerializationException("Path slot not found: " + slotName);
String boneName = constraintMap.getString("bone");
data.bone = skeletonData.findBone(boneName);
if (data.bone == null) throw new SerializationException("Physics bone not found: " + boneName);
data.positionMode = PositionMode.valueOf(constraintMap.getString("positionMode", "percent"));
data.spacingMode = SpacingMode.valueOf(constraintMap.getString("spacingMode", "length"));
data.rotateMode = RotateMode.valueOf(constraintMap.getString("rotateMode", "tangent"));
data.offsetRotation = constraintMap.getFloat("rotation", 0);
PathConstraintPose setup = data.setup;
setup.position = constraintMap.getFloat("position", 0);
if (data.positionMode == PositionMode.fixed) setup.position *= scale;
setup.spacing = constraintMap.getFloat("spacing", 0);
if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) setup.spacing *= scale;
setup.mixRotate = constraintMap.getFloat("mixRotate", 1);
setup.mixX = constraintMap.getFloat("mixX", 1);
setup.mixY = constraintMap.getFloat("mixY", 1);
data.x = constraintMap.getFloat("x", 0);
data.y = constraintMap.getFloat("y", 0);
data.rotate = constraintMap.getFloat("rotate", 0);
data.scaleX = constraintMap.getFloat("scaleX", 0);
data.shearX = constraintMap.getFloat("shearX", 0);
data.limit = constraintMap.getFloat("limit", 5000) * scale;
data.step = 1f / constraintMap.getInt("fps", 60);
PhysicsConstraintPose setup = data.setup;
setup.inertia = constraintMap.getFloat("inertia", 1);
setup.strength = constraintMap.getFloat("strength", 100);
setup.damping = constraintMap.getFloat("damping", 1);
setup.massInverse = 1 / constraintMap.getFloat("mass", 1);
setup.wind = constraintMap.getFloat("wind", 0);
setup.gravity = constraintMap.getFloat("gravity", 0);
setup.mix = constraintMap.getFloat("mix", 1);
data.inertiaGlobal = constraintMap.getBoolean("inertiaGlobal", false);
data.strengthGlobal = constraintMap.getBoolean("strengthGlobal", false);
data.dampingGlobal = constraintMap.getBoolean("dampingGlobal", false);
data.massGlobal = constraintMap.getBoolean("massGlobal", false);
data.windGlobal = constraintMap.getBoolean("windGlobal", false);
data.gravityGlobal = constraintMap.getBoolean("gravityGlobal", false);
data.mixGlobal = constraintMap.getBoolean("mixGlobal", false);
skeletonData.pathConstraints.add(data);
}
// Physics constraints.
for (JsonValue constraintMap = root.getChild("physics"); constraintMap != null; constraintMap = constraintMap.next) {
var data = new PhysicsConstraintData(constraintMap.getString("name"));
data.order = constraintMap.getInt("order", 0);
data.skinRequired = constraintMap.getBoolean("skin", false);
String boneName = constraintMap.getString("bone");
data.bone = skeletonData.findBone(boneName);
if (data.bone == null) throw new SerializationException("Physics bone not found: " + boneName);
data.x = constraintMap.getFloat("x", 0);
data.y = constraintMap.getFloat("y", 0);
data.rotate = constraintMap.getFloat("rotate", 0);
data.scaleX = constraintMap.getFloat("scaleX", 0);
data.shearX = constraintMap.getFloat("shearX", 0);
data.limit = constraintMap.getFloat("limit", 5000) * scale;
data.step = 1f / constraintMap.getInt("fps", 60);
PhysicsConstraintPose setup = data.setup;
setup.inertia = constraintMap.getFloat("inertia", 1);
setup.strength = constraintMap.getFloat("strength", 100);
setup.damping = constraintMap.getFloat("damping", 1);
setup.massInverse = 1 / constraintMap.getFloat("mass", 1);
setup.wind = constraintMap.getFloat("wind", 0);
setup.gravity = constraintMap.getFloat("gravity", 0);
setup.mix = constraintMap.getFloat("mix", 1);
data.inertiaGlobal = constraintMap.getBoolean("inertiaGlobal", false);
data.strengthGlobal = constraintMap.getBoolean("strengthGlobal", false);
data.dampingGlobal = constraintMap.getBoolean("dampingGlobal", false);
data.massGlobal = constraintMap.getBoolean("massGlobal", false);
data.windGlobal = constraintMap.getBoolean("windGlobal", false);
data.gravityGlobal = constraintMap.getBoolean("gravityGlobal", false);
data.mixGlobal = constraintMap.getBoolean("mixGlobal", false);
skeletonData.physicsConstraints.add(data);
skeletonData.constraints.add(data);
}
// BOZO! - Sliders.
}
}
// Skins.
@ -419,22 +415,22 @@ public class SkeletonJson extends SkeletonLoader {
}
skin.bones.shrink();
for (JsonValue entry = skinMap.getChild("ik"); entry != null; entry = entry.next) {
IkConstraintData constraint = skeletonData.findIkConstraint(entry.asString());
IkConstraintData constraint = skeletonData.findConstraint(entry.asString(), IkConstraintData.class);
if (constraint == null) throw new SerializationException("Skin IK constraint not found: " + entry);
skin.constraints.add(constraint);
}
for (JsonValue entry = skinMap.getChild("transform"); entry != null; entry = entry.next) {
TransformConstraintData constraint = skeletonData.findTransformConstraint(entry.asString());
TransformConstraintData constraint = skeletonData.findConstraint(entry.asString(), TransformConstraintData.class);
if (constraint == null) throw new SerializationException("Skin transform constraint not found: " + entry);
skin.constraints.add(constraint);
}
for (JsonValue entry = skinMap.getChild("path"); entry != null; entry = entry.next) {
PathConstraintData constraint = skeletonData.findPathConstraint(entry.asString());
PathConstraintData constraint = skeletonData.findConstraint(entry.asString(), PathConstraintData.class);
if (constraint == null) throw new SerializationException("Skin path constraint not found: " + entry);
skin.constraints.add(constraint);
}
for (JsonValue entry = skinMap.getChild("physics"); entry != null; entry = entry.next) {
PhysicsConstraintData constraint = skeletonData.findPhysicsConstraint(entry.asString());
PhysicsConstraintData constraint = skeletonData.findConstraint(entry.asString(), PhysicsConstraintData.class);
if (constraint == null) throw new SerializationException("Skin physics constraint not found: " + entry);
skin.constraints.add(constraint);
}
@ -463,7 +459,7 @@ public class SkeletonJson extends SkeletonLoader {
Object[] items = linkedMeshes.items;
for (int i = 0, n = linkedMeshes.size; i < n; i++) {
var linkedMesh = (LinkedMesh)items[i];
Skin skin = linkedMesh.skin == null ? skeletonData.getDefaultSkin() : skeletonData.findSkin(linkedMesh.skin);
Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
if (skin == null) throw new SerializationException("Skin not found: " + linkedMesh.skin);
Attachment parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
@ -501,7 +497,7 @@ public class SkeletonJson extends SkeletonLoader {
skeletonData.skins.shrink();
skeletonData.events.shrink();
skeletonData.animations.shrink();
skeletonData.ikConstraints.shrink();
skeletonData.constraints.shrink();
return skeletonData;
}
@ -896,9 +892,9 @@ public class SkeletonJson extends SkeletonLoader {
for (JsonValue timelineMap = map.getChild("ik"); timelineMap != null; timelineMap = timelineMap.next) {
JsonValue keyMap = timelineMap.child;
if (keyMap == null) continue;
IkConstraintData constraint = skeletonData.findIkConstraint(timelineMap.name);
IkConstraintData constraint = skeletonData.findConstraint(timelineMap.name, IkConstraintData.class);
var timeline = new IkConstraintTimeline(timelineMap.size, timelineMap.size << 1,
skeletonData.getIkConstraints().indexOf(constraint, true));
skeletonData.constraints.indexOf(constraint, true));
float time = keyMap.getFloat("time", 0);
float mix = keyMap.getFloat("mix", 1), softness = keyMap.getFloat("softness", 0) * scale;
for (int frame = 0, bezier = 0;; frame++) {
@ -928,9 +924,9 @@ public class SkeletonJson extends SkeletonLoader {
for (JsonValue timelineMap = map.getChild("transform"); timelineMap != null; timelineMap = timelineMap.next) {
JsonValue keyMap = timelineMap.child;
if (keyMap == null) continue;
TransformConstraintData constraint = skeletonData.findTransformConstraint(timelineMap.name);
TransformConstraintData constraint = skeletonData.findConstraint(timelineMap.name, TransformConstraintData.class);
var timeline = new TransformConstraintTimeline(timelineMap.size, timelineMap.size * 6,
skeletonData.getTransformConstraints().indexOf(constraint, true));
skeletonData.constraints.indexOf(constraint, true));
float time = keyMap.getFloat("time", 0);
float mixRotate = keyMap.getFloat("mixRotate", 1);
float mixX = keyMap.getFloat("mixX", 1), mixY = keyMap.getFloat("mixY", mixX);
@ -971,9 +967,9 @@ public class SkeletonJson extends SkeletonLoader {
// Path constraint timelines.
for (JsonValue constraintMap = map.getChild("path"); constraintMap != null; constraintMap = constraintMap.next) {
PathConstraintData constraint = skeletonData.findPathConstraint(constraintMap.name);
PathConstraintData constraint = skeletonData.findConstraint(constraintMap.name, PathConstraintData.class);
if (constraint == null) throw new SerializationException("Path constraint not found: " + constraintMap.name);
int index = skeletonData.pathConstraints.indexOf(constraint, true);
int index = skeletonData.constraints.indexOf(constraint, true);
for (JsonValue timelineMap = constraintMap.child; timelineMap != null; timelineMap = timelineMap.next) {
JsonValue keyMap = timelineMap.child;
if (keyMap == null) continue;
@ -1026,9 +1022,9 @@ public class SkeletonJson extends SkeletonLoader {
for (JsonValue constraintMap = map.getChild("physics"); constraintMap != null; constraintMap = constraintMap.next) {
int index = -1;
if (!constraintMap.name.isEmpty()) {
PhysicsConstraintData constraint = skeletonData.findPhysicsConstraint(constraintMap.name);
PhysicsConstraintData constraint = skeletonData.findConstraint(constraintMap.name, PhysicsConstraintData.class);
if (constraint == null) throw new SerializationException("Physics constraint not found: " + constraintMap.name);
index = skeletonData.physicsConstraints.indexOf(constraint, true);
index = skeletonData.constraints.indexOf(constraint, true);
}
for (JsonValue timelineMap = constraintMap.child; timelineMap != null; timelineMap = timelineMap.next) {
JsonValue keyMap = timelineMap.child;
@ -1179,7 +1175,7 @@ public class SkeletonJson extends SkeletonLoader {
event.intValue = keyMap.getInt("int", eventData.intValue);
event.floatValue = keyMap.getFloat("float", eventData.floatValue);
event.stringValue = keyMap.getString("string", eventData.stringValue);
if (event.getData().audioPath != null) {
if (event.data.audioPath != null) {
event.volume = keyMap.getFloat("volume", eventData.volume);
event.balance = keyMap.getFloat("balance", eventData.balance);
}

View File

@ -54,7 +54,7 @@ public class SkeletonRenderer {
/** Renders the specified skeleton. If the batch is a PolygonSpriteBatch, {@link #draw(PolygonSpriteBatch, Skeleton)} is
* called. If the batch is a TwoColorPolygonBatch, {@link #draw(TwoColorPolygonBatch, Skeleton)} is called. Otherwise the
* skeleton is rendered without two color tinting and any mesh attachments will throw an exception.
* skeleton is rendered without two color tinting and any mesh or clipping attachments will throw an exception.
* <p>
* This method may change the batch's {@link Batch#setBlendFunctionSeparate(int, int, int, int) blending function}. The
* previous blend function is not restored, since that could result in unnecessary flushes, depending on what is rendered
@ -145,76 +145,75 @@ public class SkeletonRenderer {
Object[] drawOrder = skeleton.drawOrder.items;
for (int i = 0, n = skeleton.drawOrder.size; i < n; i++) {
var slot = (Slot)drawOrder[i];
if (!slot.bone.active) {
clipper.clipEnd(slot);
continue;
}
SlotPose pose = slot.applied;
Texture texture = null;
Attachment attachment = pose.attachment;
if (attachment instanceof RegionAttachment region) {
verticesLength = 20;
vertices = this.vertices.items;
region.computeWorldVertices(slot, vertices, 0, 5);
triangles = quadTriangles;
texture = region.getRegion().getTexture();
uvs = region.getUVs();
color = region.getColor();
if (slot.bone.active) {
SlotPose pose = slot.applied;
Attachment attachment = pose.attachment;
if (attachment != null) {
Texture texture = null;
if (attachment instanceof RegionAttachment region) {
verticesLength = 20;
vertices = this.vertices.items;
region.computeWorldVertices(slot, vertices, 0, 5);
triangles = quadTriangles;
texture = region.getRegion().getTexture();
uvs = region.getUVs();
color = region.getColor();
} else if (attachment instanceof MeshAttachment mesh) {
int count = mesh.getWorldVerticesLength();
verticesLength = (count >> 1) * 5;
vertices = this.vertices.setSize(verticesLength);
mesh.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 5);
triangles = mesh.getTriangles();
texture = mesh.getRegion().getTexture();
uvs = mesh.getUVs();
color = mesh.getColor();
} else if (attachment instanceof MeshAttachment mesh) {
int count = mesh.getWorldVerticesLength();
verticesLength = (count >> 1) * 5;
vertices = this.vertices.setSize(verticesLength);
mesh.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 5);
triangles = mesh.getTriangles();
texture = mesh.getRegion().getTexture();
uvs = mesh.getUVs();
color = mesh.getColor();
} else if (attachment instanceof ClippingAttachment clip) {
clipper.clipStart(skeleton, slot, clip);
continue;
} else if (attachment instanceof ClippingAttachment clip) {
clipper.clipStart(skeleton, slot, clip);
continue;
} else if (attachment instanceof SkeletonAttachment skeletonAttachment) {
Skeleton attachmentSkeleton = skeletonAttachment.getSkeleton();
if (attachmentSkeleton != null) draw(batch, attachmentSkeleton);
}
if (texture != null) {
Color slotColor = pose.getColor();
float alpha = a * slotColor.a * color.a * 255;
float multiplier = pmaColors ? alpha : 255;
BlendMode slotBlendMode = slot.data.getBlendMode();
if (slotBlendMode != blendMode) {
if (slotBlendMode == BlendMode.additive && pmaColors) {
slotBlendMode = BlendMode.normal;
alpha = 0;
} else if (attachment instanceof SkeletonAttachment skeletonAttachment) {
Skeleton attachmentSkeleton = skeletonAttachment.getSkeleton();
if (attachmentSkeleton != null) draw(batch, attachmentSkeleton);
}
blendMode = slotBlendMode;
blendMode.apply(batch, pmaBlendModes);
}
float c = NumberUtils.intToFloatColor((int)alpha << 24 //
| (int)(b * slotColor.b * color.b * multiplier) << 16 //
| (int)(g * slotColor.g * color.g * multiplier) << 8 //
| (int)(r * slotColor.r * color.r * multiplier));
if (texture != null) {
Color slotColor = pose.getColor();
float alpha = a * slotColor.a * color.a * 255;
float multiplier = pmaColors ? alpha : 255;
if (clipper.isClipping() && clipper.clipTriangles(vertices, triangles, triangles.length, uvs, c, 0, false, 5)) {
FloatArray clippedVertices = clipper.getClippedVertices();
ShortArray clippedTriangles = clipper.getClippedTriangles();
batch.draw(texture, clippedVertices.items, 0, clippedVertices.size, clippedTriangles.items, 0,
clippedTriangles.size);
} else {
for (int v = 2, u = 0; v < verticesLength; v += 5, u += 2) {
vertices[v] = c;
vertices[v + 1] = uvs[u];
vertices[v + 2] = uvs[u + 1];
BlendMode slotBlendMode = slot.data.getBlendMode();
if (slotBlendMode != blendMode) {
if (slotBlendMode == BlendMode.additive && pmaColors) {
slotBlendMode = BlendMode.normal;
alpha = 0;
}
blendMode = slotBlendMode;
blendMode.apply(batch, pmaBlendModes);
}
float c = NumberUtils.intToFloatColor((int)alpha << 24 //
| (int)(b * slotColor.b * color.b * multiplier) << 16 //
| (int)(g * slotColor.g * color.g * multiplier) << 8 //
| (int)(r * slotColor.r * color.r * multiplier));
if (clipper.isClipping() && clipper.clipTriangles(vertices, triangles, triangles.length, uvs, c, 0, false, 5)) {
FloatArray clippedVertices = clipper.getClippedVertices();
ShortArray clippedTriangles = clipper.getClippedTriangles();
batch.draw(texture, clippedVertices.items, 0, clippedVertices.size, clippedTriangles.items, 0,
clippedTriangles.size);
} else {
for (int v = 2, u = 0; v < verticesLength; v += 5, u += 2) {
vertices[v] = c;
vertices[v + 1] = uvs[u];
vertices[v + 2] = uvs[u + 1];
}
batch.draw(texture, vertices, 0, verticesLength, triangles, 0, triangles.length);
}
}
batch.draw(texture, vertices, 0, verticesLength, triangles, 0, triangles.length);
}
}
clipper.clipEnd(slot);
}
clipper.clipEnd();
@ -240,85 +239,85 @@ public class SkeletonRenderer {
Object[] drawOrder = skeleton.drawOrder.items;
for (int i = 0, n = skeleton.drawOrder.size; i < n; i++) {
var slot = (Slot)drawOrder[i];
if (!slot.bone.active) {
clipper.clipEnd(slot);
continue;
}
SlotPose pose = slot.applied;
Texture texture = null;
Attachment attachment = pose.attachment;
if (attachment instanceof RegionAttachment region) {
verticesLength = 24;
vertices = this.vertices.items;
region.computeWorldVertices(slot, vertices, 0, 6);
triangles = quadTriangles;
texture = region.getRegion().getTexture();
uvs = region.getUVs();
color = region.getColor();
if (slot.bone.active) {
SlotPose pose = slot.applied;
Attachment attachment = pose.attachment;
if (attachment != null) {
Texture texture = null;
if (attachment instanceof RegionAttachment region) {
verticesLength = 24;
vertices = this.vertices.items;
region.computeWorldVertices(slot, vertices, 0, 6);
triangles = quadTriangles;
texture = region.getRegion().getTexture();
uvs = region.getUVs();
color = region.getColor();
} else if (attachment instanceof MeshAttachment mesh) {
int count = mesh.getWorldVerticesLength();
verticesLength = count * 3;
vertices = this.vertices.setSize(verticesLength);
mesh.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 6);
triangles = mesh.getTriangles();
texture = mesh.getRegion().getTexture();
uvs = mesh.getUVs();
color = mesh.getColor();
} else if (attachment instanceof MeshAttachment mesh) {
int count = mesh.getWorldVerticesLength();
verticesLength = count * 3;
vertices = this.vertices.setSize(verticesLength);
mesh.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 6);
triangles = mesh.getTriangles();
texture = mesh.getRegion().getTexture();
uvs = mesh.getUVs();
color = mesh.getColor();
} else if (attachment instanceof ClippingAttachment clip) {
clipper.clipStart(skeleton, slot, clip);
continue;
} else if (attachment instanceof ClippingAttachment clip) {
clipper.clipStart(skeleton, slot, clip);
continue;
} else if (attachment instanceof SkeletonAttachment skeletonAttachment) {
Skeleton attachmentSkeleton = skeletonAttachment.getSkeleton();
if (attachmentSkeleton != null) draw(batch, attachmentSkeleton);
}
if (texture != null) {
Color lightColor = pose.getColor();
float alpha = a * lightColor.a * color.a * 255;
float multiplier = pmaColors ? alpha : 255;
BlendMode slotBlendMode = slot.data.getBlendMode();
if (slotBlendMode != blendMode) {
if (slotBlendMode == BlendMode.additive && pmaColors) {
slotBlendMode = BlendMode.normal;
alpha = 0;
} else if (attachment instanceof SkeletonAttachment skeletonAttachment) {
Skeleton attachmentSkeleton = skeletonAttachment.getSkeleton();
if (attachmentSkeleton != null) draw(batch, attachmentSkeleton);
}
blendMode = slotBlendMode;
blendMode.apply(batch, pmaBlendModes);
}
float red = r * color.r * multiplier;
float green = g * color.g * multiplier;
float blue = b * color.b * multiplier;
float light = NumberUtils.intToFloatColor((int)alpha << 24 //
| (int)(blue * lightColor.b) << 16 //
| (int)(green * lightColor.g) << 8 //
| (int)(red * lightColor.r));
Color darkColor = pose.getDarkColor();
float dark = darkColor == null ? 0
: NumberUtils.intToFloatColor((int)(blue * darkColor.b) << 16 //
| (int)(green * darkColor.g) << 8 //
| (int)(red * darkColor.r));
if (texture != null) {
Color lightColor = pose.getColor();
float alpha = a * lightColor.a * color.a * 255;
float multiplier = pmaColors ? alpha : 255;
if (clipper.isClipping() && clipper.clipTriangles(vertices, triangles, triangles.length, uvs, light, dark, true, 6)) {
FloatArray clippedVertices = clipper.getClippedVertices();
ShortArray clippedTriangles = clipper.getClippedTriangles();
batch.drawTwoColor(texture, clippedVertices.items, 0, clippedVertices.size, clippedTriangles.items, 0,
clippedTriangles.size);
} else {
for (int v = 2, u = 0; v < verticesLength; v += 6, u += 2) {
vertices[v] = light;
vertices[v + 1] = dark;
vertices[v + 2] = uvs[u];
vertices[v + 3] = uvs[u + 1];
BlendMode slotBlendMode = slot.data.getBlendMode();
if (slotBlendMode != blendMode) {
if (slotBlendMode == BlendMode.additive && pmaColors) {
slotBlendMode = BlendMode.normal;
alpha = 0;
}
blendMode = slotBlendMode;
blendMode.apply(batch, pmaBlendModes);
}
float red = r * color.r * multiplier;
float green = g * color.g * multiplier;
float blue = b * color.b * multiplier;
float light = NumberUtils.intToFloatColor((int)alpha << 24 //
| (int)(blue * lightColor.b) << 16 //
| (int)(green * lightColor.g) << 8 //
| (int)(red * lightColor.r));
Color darkColor = pose.getDarkColor();
float dark = darkColor == null ? 0
: NumberUtils.intToFloatColor((int)(blue * darkColor.b) << 16 //
| (int)(green * darkColor.g) << 8 //
| (int)(red * darkColor.r));
if (clipper.isClipping()
&& clipper.clipTriangles(vertices, triangles, triangles.length, uvs, light, dark, true, 6)) {
FloatArray clippedVertices = clipper.getClippedVertices();
ShortArray clippedTriangles = clipper.getClippedTriangles();
batch.drawTwoColor(texture, clippedVertices.items, 0, clippedVertices.size, clippedTriangles.items, 0,
clippedTriangles.size);
} else {
for (int v = 2, u = 0; v < verticesLength; v += 6, u += 2) {
vertices[v] = light;
vertices[v + 1] = dark;
vertices[v + 2] = uvs[u];
vertices[v + 3] = uvs[u + 1];
}
batch.drawTwoColor(texture, vertices, 0, verticesLength, triangles, 0, triangles.length);
}
}
batch.drawTwoColor(texture, vertices, 0, verticesLength, triangles, 0, triangles.length);
}
}
clipper.clipEnd(slot);
}
clipper.clipEnd();

View File

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

View File

@ -29,21 +29,23 @@
package com.esotericsoftware.spine;
import com.esotericsoftware.spine.Animation.BoneTimeline;
import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection;
import com.esotericsoftware.spine.Animation.Timeline;
/** 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 extends Constraint<SliderData, SliderPose> {
public class Slider extends Constraint<Slider, SliderData, SliderPose> {
public Slider (SliderData data) {
super(data, new SliderPose(), new SliderPose());
}
/** Copy constructor. */
public Slider (Slider slider) {
this(slider.data);
pose.set(slider.pose);
public Slider copy (Skeleton skeleton) {
var copy = new Slider(data);
copy.pose.set(pose);
return copy;
}
public void update (Skeleton skeleton, Physics physics) {
@ -51,6 +53,28 @@ public class Slider extends Constraint<SliderData, SliderPose> {
data.animation.apply(skeleton, pose.time, pose.time, false, null, pose.mix, MixBlend.replace, MixDirection.in, true);
}
public void sort () {
void sort (Skeleton skeleton) {
Object[] timelines = data.animation.timelines.items;
int timelineCount = data.animation.timelines.size;
// BOZO - Sort and reset other timeline types.
Object[] bones = skeleton.bones.items;
for (int i = 0; i < timelineCount; i++) {
var timeline = (Timeline)timelines[i];
if (timeline instanceof BoneTimeline boneTimeline) skeleton.sortBone((Bone)bones[boneTimeline.getBoneIndex()]);
}
skeleton.updateCache.add(this);
for (int i = 0; i < timelineCount; i++) {
if (timelines[i] instanceof BoneTimeline boneTimeline) {
var bone = (Bone)bones[boneTimeline.getBoneIndex()];
skeleton.resetCache(bone);
skeleton.sortReset(bone.children);
bone.sorted = false;
}
}
for (int i = 0; i < timelineCount; i++)
if (timelines[i] instanceof BoneTimeline boneTimeline) skeleton.sortBone((Bone)bones[boneTimeline.getBoneIndex()]);
}
}

View File

@ -32,13 +32,17 @@ package com.esotericsoftware.spine;
/** 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 SliderData extends PosedData<SliderPose> {
public class SliderData extends ConstraintData<Slider, SliderPose> {
Animation animation;
public SliderData (String name) {
super(name, new SliderPose());
}
public Slider create (Skeleton skeleton) {
return new Slider(this);
}
public Animation getAnimation () {
return animation;
}

View File

@ -35,12 +35,14 @@ import com.badlogic.gdx.graphics.Color;
* 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 extends Posed<SlotData, SlotPose, SlotPose> {
final Skeleton skeleton;
final Bone bone;
int attachmentState;
public Slot (SlotData data, Skeleton skeleton) {
super(data, new SlotPose(), new SlotPose());
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
this.skeleton = skeleton;
bone = skeleton.bones.get(data.boneData.index);
if (data.setup.darkColor != null) {
pose.darkColor = new Color();
@ -50,9 +52,12 @@ public class Slot extends Posed<SlotData, SlotPose, SlotPose> {
}
/** Copy constructor. */
public Slot (Slot slot, Bone bone) {
public Slot (Slot slot, Bone bone, Skeleton skeleton) {
super(slot.data, new SlotPose(), new SlotPose());
if (bone == null) throw new IllegalArgumentException("bone cannot be null.");
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
this.bone = bone;
this.skeleton = skeleton;
if (data.setup.darkColor != null) {
pose.darkColor = new Color();
applied.darkColor = new Color();
@ -64,4 +69,16 @@ public class Slot extends Posed<SlotData, SlotPose, SlotPose> {
public Bone getBone () {
return bone;
}
public void setupPose () {
pose.color.set(data.setup.color);
if (pose.darkColor != null) pose.darkColor.set(data.setup.darkColor);
pose.sequenceIndex = pose.sequenceIndex;
if (data.attachmentName == null)
pose.setAttachment(null);
else {
pose.attachment = null;
pose.setAttachment(skeleton.getAttachment(data.index, data.attachmentName));
}
}
}

View File

@ -40,7 +40,7 @@ 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 extends Constraint<TransformConstraintData, TransformConstraintPose> {
public class TransformConstraint extends Constraint<TransformConstraint, TransformConstraintData, TransformConstraintPose> {
final Array<BonePose> bones;
Bone source;
@ -55,10 +55,10 @@ public class TransformConstraint extends Constraint<TransformConstraintData, Tra
source = skeleton.bones.get(data.source.index);
}
/** Copy constructor. */
public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) {
this(constraint.data, skeleton);
pose.set(constraint.pose);
public TransformConstraint copy (Skeleton skeleton) {
var copy = new TransformConstraint(data, skeleton);
copy.pose.set(pose);
return copy;
}
/** Applies the constraint to the constrained bones. */
@ -101,7 +101,36 @@ public class TransformConstraint extends Constraint<TransformConstraintData, Tra
}
}
public void sort () {
void sort (Skeleton skeleton) {
skeleton.sortBone(source);
Object[] bones = this.bones.items;
int boneCount = this.bones.size;
if (data.localSource) {
for (int i = 0; i < boneCount; i++) {
Bone child = ((BonePose)bones[i]).bone;
skeleton.resetCache(child);
skeleton.sortBone(child.parent);
skeleton.sortBone(child);
}
} else {
for (int i = 0; i < boneCount; i++) {
Bone bone = ((BonePose)bones[i]).bone;
skeleton.resetCache(bone);
skeleton.sortBone(bone);
}
}
skeleton.updateCache.add(this);
for (int i = 0; i < boneCount; i++)
skeleton.sortReset(((BonePose)bones[i]).bone.children);
for (int i = 0; i < boneCount; i++)
((BonePose)bones[i]).bone.sorted = true;
}
boolean isSourceActive () {
return source.active;
}
/** The bones that will be modified by this transform constraint. */

View File

@ -36,7 +36,7 @@ import com.badlogic.gdx.utils.Array;
/** Stores the setup pose for a {@link TransformConstraint}.
* <p>
* See <a href="https://esotericsoftware.com/spine-transform-constraints">Transform constraints</a> in the Spine User Guide. */
public class TransformConstraintData extends PosedData<TransformConstraintPose> {
public class TransformConstraintData extends ConstraintData<TransformConstraint, TransformConstraintPose> {
final Array<BoneData> bones = new Array();
BoneData source;
float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
@ -47,6 +47,10 @@ public class TransformConstraintData extends PosedData<TransformConstraintPose>
super(name, new TransformConstraintPose());
}
public TransformConstraint create (Skeleton skeleton) {
return new TransformConstraint(this, skeleton);
}
/** The bones that will be modified by this transform constraint. */
public Array<BoneData> getBones () {
return bones;