diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java index d33a44081..8cdb8fe2e 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java @@ -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"); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index d58b873e3..5ff6de7c2 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -1735,19 +1735,19 @@ public class Animation { public void apply (Skeleton skeleton, float lastTime, float time, @Null Array 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 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; } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/DrawOrder.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/DrawOrder.java new file mode 100644 index 000000000..2ee4166a5 --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/DrawOrder.java @@ -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 setupPose, pose, constrainedPose; + Array appliedPose; + + DrawOrder (Array 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 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 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); + } +} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java index b9875d22f..0f2af71a9 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -53,7 +53,7 @@ public class Skeleton { final SkeletonData data; final Array bones; final Array slots; - Array drawOrder; + final DrawOrder drawOrder; final Array constraints; final Array 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 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 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 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; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java index 71ca81ec7..a61d51356 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -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 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 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 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; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java index 63accf5db..71e682fea 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java @@ -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 { 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++)