[libgdx] Draw order now uses pose/appliedPose.

This commit is contained in:
Nathan Sweet 2026-03-25 23:58:31 -04:00
parent b06c14b80e
commit bfbc7567a5
6 changed files with 148 additions and 63 deletions

View File

@ -3091,11 +3091,16 @@ public class SkeletonSerializer {
}
json.writeArrayEnd();
json.writeName("drawOrder");
json.writeName("drawOrderPose");
json.writeArrayStart();
for (Slot item : obj.getDrawOrder()) {
for (Slot item : obj.getDrawOrder().getPose())
writeSlot(item);
json.writeArrayEnd();
json.writeName("drawOrderApplied");
json.writeArrayStart();
for (Slot item : obj.getDrawOrder().getAppliedPose())
writeSlot(item);
}
json.writeArrayEnd();
json.writeName("skin");

View File

@ -1735,19 +1735,19 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, boolean fromSetup,
boolean add, boolean out, boolean appliedPose) {
Slot[] pose = (appliedPose ? skeleton.drawOrder.appliedPose : skeleton.drawOrder.pose).items;
Slot[] setup = skeleton.slots.items;
if (out || time < frames[0]) {
if (fromSetup) arraycopy(skeleton.slots.items, 0, skeleton.drawOrder.items, 0, skeleton.slots.size);
if (fromSetup) arraycopy(setup, 0, pose, 0, skeleton.slots.size);
return;
}
int[] drawOrderToSetupIndex = drawOrders[search(frames, time)];
if (drawOrderToSetupIndex == null)
arraycopy(skeleton.slots.items, 0, skeleton.drawOrder.items, 0, skeleton.slots.size);
int[] order = drawOrders[search(frames, time)];
if (order == null)
arraycopy(setup, 0, pose, 0, skeleton.slots.size);
else {
Slot[] slots = skeleton.slots.items;
Slot[] drawOrder = skeleton.drawOrder.items;
for (int i = 0, n = drawOrderToSetupIndex.length; i < n; i++)
drawOrder[i] = slots[drawOrderToSetupIndex[i]];
for (int i = 0, n = order.length; i < n; i++)
pose[i] = setup[order[i]];
}
}
}
@ -1803,36 +1803,33 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, boolean fromSetup,
boolean add, boolean out, boolean appliedPose) {
Slot[] pose = (appliedPose ? skeleton.drawOrder.appliedPose : skeleton.drawOrder.pose).items;
Slot[] setup = skeleton.slots.items;
if (out || time < frames[0]) {
if (fromSetup) setup(skeleton);
if (fromSetup) setup(pose, setup);
} else {
int[] order = drawOrders[search(frames, time)];
if (order == null)
setup(skeleton);
else
apply(skeleton, order);
}
}
private void setup (Skeleton skeleton) {
boolean[] inFolder = this.inFolder;
Slot[] drawOrder = skeleton.drawOrder.items, allSlots = skeleton.slots.items;
int[] slots = this.slots;
for (int i = 0, found = 0, done = slots.length;; i++) {
if (inFolder[drawOrder[i].data.index]) {
drawOrder[i] = allSlots[slots[found]];
if (++found == done) break;
setup(pose, setup);
else {
boolean[] inFolder = this.inFolder;
int[] slots = this.slots;
for (int i = 0, found = 0, done = slots.length;; i++) {
if (inFolder[pose[i].data.index]) {
pose[i] = setup[slots[order[found]]];
if (++found == done) break;
}
}
}
}
}
private void apply (Skeleton skeleton, int[] order) {
private void setup (Slot[] pose, Slot[] setup) {
boolean[] inFolder = this.inFolder;
Slot[] drawOrder = skeleton.drawOrder.items, allSlots = skeleton.slots.items;
int[] slots = this.slots;
for (int i = 0, found = 0, done = slots.length;; i++) {
if (inFolder[drawOrder[i].data.index]) {
drawOrder[i] = allSlots[slots[order[found]]];
if (inFolder[pose[i].data.index]) {
pose[i] = setup[slots[found]];
if (++found == done) break;
}
}

View File

@ -0,0 +1,78 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2025, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine;
import static com.esotericsoftware.spine.utils.SpineUtils.*;
import com.badlogic.gdx.utils.Array;
/** Stores the skeleton's draw order, which is the order that each slot's attachment is rendered. */
public class DrawOrder {
final Array<Slot> setupPose, pose, constrainedPose;
Array<Slot> appliedPose;
DrawOrder (Array<Slot> setupPose) {
this.setupPose = setupPose;
pose = new Array(setupPose);
constrainedPose = new Array(true, 0, Slot[]::new);
appliedPose = pose;
}
/** Sets the unconstrained draw order to the setup pose order. */
public void setupPose () {
arraycopy(setupPose.items, 0, pose.setSize(setupPose.size), 0, setupPose.size);
}
/** The unconstrained draw order, set by animations and application code. */
public Array<Slot> getPose () {
return pose;
}
/** The constrained draw order for rendering. If no constraints modify the draw order, this is the same as {@link #pose}.
* Otherwise it is a copy of {@link #pose} modified by constraints. */
public Array<Slot> getAppliedPose () {
return appliedPose;
}
/** Sets the applied pose to the unconstrained pose, for when no constraints will modify the draw order. */
void pose () {
appliedPose = pose;
}
/** Sets the applied pose to the constrained pose, in anticipation of the applied pose being modified by constraints. */
void constrained () {
appliedPose = constrainedPose;
}
/** Copies the unconstrained pose to the constrained pose, as a starting point for constraints to be applied. */
void reset () { // Port: resetConstrained
arraycopy(pose.items, 0, constrainedPose.setSize(pose.size), 0, pose.size);
}
}

View File

@ -53,7 +53,7 @@ public class Skeleton {
final SkeletonData data;
final Array<Bone> bones;
final Array<Slot> slots;
Array<Slot> drawOrder;
final DrawOrder drawOrder;
final Array<Constraint> constraints;
final Array<PhysicsConstraint> physics;
final Array updateCache = new Array();
@ -82,12 +82,9 @@ public class Skeleton {
}
slots = new Array(true, data.slots.size, Slot[]::new);
drawOrder = new Array(true, data.slots.size, Slot[]::new);
for (SlotData slotData : data.slots) {
var slot = new Slot(slotData, this);
slots.add(slot);
drawOrder.add(slot);
}
for (SlotData slotData : data.slots)
slots.add(new Slot(slotData, this));
drawOrder = new DrawOrder(slots);
physics = new Array(true, 8, PhysicsConstraint[]::new);
constraints = new Array(true, data.constraints.size, Constraint[]::new);
@ -125,9 +122,10 @@ public class Skeleton {
for (Slot slot : skeleton.slots)
slots.add(new Slot(slot, bones.items[slot.bone.data.index], this));
drawOrder = new Array(true, slots.size, Slot[]::new);
for (Slot slot : skeleton.drawOrder)
drawOrder.add(slots.items[slot.data.index]);
drawOrder = new DrawOrder(slots);
drawOrder.pose.clear();
for (Slot slot : skeleton.drawOrder.pose)
drawOrder.pose.add(slots.items[slot.data.index]);
physics = new Array(true, skeleton.physics.size, PhysicsConstraint[]::new);
constraints = new Array(true, skeleton.constraints.size, Constraint[]::new);
@ -154,6 +152,7 @@ public class Skeleton {
updateCache.clear();
resetCache.clear();
drawOrder.pose();
Slot[] slots = this.slots.items;
for (int i = 0, n = this.slots.size; i < n; i++)
slots[i].pose();
@ -231,6 +230,7 @@ public class Skeleton {
public void updateWorldTransform (Physics physics) {
update++;
drawOrder.reset();
Posed[] resetCache = this.resetCache.items;
for (int i = 0, n = this.resetCache.size; i < n; i++)
resetCache[i].reset();
@ -250,6 +250,7 @@ public class Skeleton {
update++;
drawOrder.reset();
Posed[] resetCache = this.resetCache.items;
for (int i = 0, n = this.resetCache.size; i < n; i++)
resetCache[i].reset();
@ -298,10 +299,9 @@ public class Skeleton {
/** Sets the slots and draw order to their setup pose values. */
public void setupPoseSlots () {
drawOrder.setupPose();
Slot[] slots = this.slots.items;
int n = this.slots.size;
arraycopy(slots, 0, drawOrder.items, 0, n);
for (int i = 0; i < n; i++)
for (int i = 0, n = this.slots.size; i < n; i++)
slots[i].setupPose();
}
@ -350,16 +350,12 @@ public class Skeleton {
return null;
}
/** The skeleton's slots in the order they should be drawn. The returned list may be modified to change the draw order. */
public Array<Slot> getDrawOrder () {
/** The skeleton's draw order. Use {@link DrawOrder#appliedPose} for rendering and {@link DrawOrder#pose} for changing the draw
* order. */
public DrawOrder getDrawOrder () {
return drawOrder;
}
public void setDrawOrder (Array<Slot> drawOrder) {
if (drawOrder == null) throw new IllegalArgumentException("drawOrder cannot be null.");
this.drawOrder = drawOrder;
}
/** The skeleton's current skin. */
public @Null Skin getSkin () {
return skin;
@ -485,15 +481,16 @@ public class Skeleton {
if (offset == null) throw new IllegalArgumentException("offset cannot be null.");
if (size == null) throw new IllegalArgumentException("size cannot be null.");
if (temp == null) throw new IllegalArgumentException("temp cannot be null.");
Slot[] drawOrder = this.drawOrder.items;
Array<Slot> drawOrder = this.drawOrder.appliedPose;
Slot[] slots = drawOrder.items;
float minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE;
for (int i = 0, n = this.drawOrder.size; i < n; i++) {
Slot slot = drawOrder[i];
for (int i = 0, n = drawOrder.size; i < n; i++) {
Slot slot = slots[i];
if (!slot.bone.active) continue;
int verticesLength = 0;
float[] vertices = null;
short[] triangles = null;
Attachment attachment = slot.pose.attachment;
Attachment attachment = slot.appliedPose.attachment;
if (attachment != null) {
if (attachment instanceof RegionAttachment region) {
verticesLength = 8;

View File

@ -33,6 +33,7 @@ import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.NumberUtils;
import com.badlogic.gdx.utils.ShortArray;
@ -77,9 +78,10 @@ public class SkeletonRenderer {
float[] vertices = this.vertices.items;
Color skeletonColor = skeleton.color;
float r = skeletonColor.r, g = skeletonColor.g, b = skeletonColor.b, a = skeletonColor.a;
Slot[] drawOrder = skeleton.drawOrder.items;
for (int i = 0, n = skeleton.drawOrder.size; i < n; i++) {
Slot slot = drawOrder[i];
Array<Slot> drawOrder = skeleton.drawOrder.appliedPose;
Slot[] slots = drawOrder.items;
for (int i = 0, n = drawOrder.size; i < n; i++) {
Slot slot = slots[i];
if (!slot.bone.active) continue;
SlotPose pose = slot.appliedPose;
Attachment attachment = pose.attachment;
@ -146,9 +148,10 @@ public class SkeletonRenderer {
short[] triangles = null;
Color color = null, skeletonColor = skeleton.color;
float r = skeletonColor.r, g = skeletonColor.g, b = skeletonColor.b, a = skeletonColor.a;
Slot[] drawOrder = skeleton.drawOrder.items;
for (int i = 0, n = skeleton.drawOrder.size; i < n; i++) {
Slot slot = drawOrder[i];
Array<Slot> drawOrder = skeleton.drawOrder.appliedPose;
Slot[] slots = drawOrder.items;
for (int i = 0, n = drawOrder.size; i < n; i++) {
Slot slot = slots[i];
if (slot.bone.active) {
SlotPose pose = slot.appliedPose;
Attachment attachment = pose.attachment;
@ -245,9 +248,10 @@ public class SkeletonRenderer {
short[] triangles = null;
Color color = null, skeletonColor = skeleton.color;
float r = skeletonColor.r, g = skeletonColor.g, b = skeletonColor.b, a = skeletonColor.a;
Slot[] drawOrder = skeleton.drawOrder.items;
for (int i = 0, n = skeleton.drawOrder.size; i < n; i++) {
Slot slot = drawOrder[i];
Array<Slot> drawOrder = skeleton.drawOrder.appliedPose;
Slot[] slots = drawOrder.items;
for (int i = 0, n = drawOrder.size; i < n; i++) {
Slot slot = slots[i];
if (slot.bone.active) {
SlotPose pose = slot.appliedPose;
Attachment attachment = pose.attachment;

View File

@ -30,6 +30,8 @@
package com.esotericsoftware.spine;
import com.esotericsoftware.spine.Animation.ConstraintTimeline;
import com.esotericsoftware.spine.Animation.DrawOrderFolderTimeline;
import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
import com.esotericsoftware.spine.Animation.PhysicsConstraintTimeline;
import com.esotericsoftware.spine.Animation.SlotTimeline;
import com.esotericsoftware.spine.Animation.Timeline;
@ -101,6 +103,8 @@ public class Slider extends Constraint<Slider, SliderData, SliderPose> {
Timeline t = timelines[i];
if (t instanceof SlotTimeline timeline)
skeleton.constrained(slots[timeline.getSlotIndex()]);
else if (t instanceof DrawOrderTimeline || t instanceof DrawOrderFolderTimeline)
skeleton.drawOrder.constrained();
else if (t instanceof PhysicsConstraintTimeline timeline) {
if (timeline.constraintIndex == -1) {
for (int ii = 0; ii < physicsCount; ii++)