From 275e702bc8fdd5d4f8c52ea4439ec3d89dd5779e Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 16 Mar 2026 17:14:15 +0100 Subject: [PATCH] [cpp] Port cf45806b, 409bf9d6, 0e16ef9c from libgdx --- .../include/spine/DrawOrderFolderTimeline.h | 74 +++++++++++ spine-cpp/include/spine/Skeleton.h | 2 + spine-cpp/include/spine/SkeletonBinary.h | 2 + spine-cpp/include/spine/SkeletonData.h | 3 + spine-cpp/include/spine/SkeletonJson.h | 2 + spine-cpp/include/spine/SliderData.h | 1 + spine-cpp/include/spine/spine.h | 1 + spine-cpp/src/spine/AnimationState.cpp | 7 +- .../src/spine/DrawOrderFolderTimeline.cpp | 115 ++++++++++++++++++ spine-cpp/src/spine/SkeletonBinary.cpp | 64 ++++++---- spine-cpp/src/spine/SkeletonData.cpp | 12 ++ spine-cpp/src/spine/SkeletonJson.cpp | 115 ++++++++++++++---- 12 files changed, 348 insertions(+), 50 deletions(-) create mode 100644 spine-cpp/include/spine/DrawOrderFolderTimeline.h create mode 100644 spine-cpp/src/spine/DrawOrderFolderTimeline.cpp diff --git a/spine-cpp/include/spine/DrawOrderFolderTimeline.h b/spine-cpp/include/spine/DrawOrderFolderTimeline.h new file mode 100644 index 000000000..34e29dee8 --- /dev/null +++ b/spine-cpp/include/spine/DrawOrderFolderTimeline.h @@ -0,0 +1,74 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_DrawOrderFolderTimeline_h +#define Spine_DrawOrderFolderTimeline_h + +#include + +namespace spine { + /// Changes a subset of a skeleton's Skeleton::getDrawOrder(). + class SP_API DrawOrderFolderTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + DrawOrderFolderTimeline(size_t frameCount, Array &slots, size_t slotCount); + + virtual void apply(Skeleton &skeleton, float lastTime, float time, Array *events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) override; + + size_t getFrameCount(); + + /// The Skeleton::getSlots() indices that this timeline affects, in setup order. + Array &getSlots(); + + /// The draw order for each frame. See setFrame(). + Array> &getDrawOrders(); + + /// Sets the time and draw order for the specified frame. + /// @param frame Between 0 and frameCount, inclusive. + /// @param time The frame time in seconds. + /// @param drawOrder Ordered getSlots() indices, or null to use setup pose order. + void setFrame(size_t frame, float time, Array *drawOrder); + + private: + Array _slots; + Array _inFolder; + Array> _drawOrders; + + void setup(Skeleton &skeleton); + void apply(Skeleton &skeleton, Array &drawOrder); + }; +} + +#endif /* Spine_DrawOrderFolderTimeline_h */ diff --git a/spine-cpp/include/spine/Skeleton.h b/spine-cpp/include/spine/Skeleton.h index a6a9c8bbf..f51f5a4de 100644 --- a/spine-cpp/include/spine/Skeleton.h +++ b/spine-cpp/include/spine/Skeleton.h @@ -88,6 +88,8 @@ namespace spine { friend class DeformTimeline; + friend class DrawOrderFolderTimeline; + friend class DrawOrderTimeline; friend class EventTimeline; diff --git a/spine-cpp/include/spine/SkeletonBinary.h b/spine-cpp/include/spine/SkeletonBinary.h index 920d8b19c..ac48b87bf 100644 --- a/spine-cpp/include/spine/SkeletonBinary.h +++ b/spine-cpp/include/spine/SkeletonBinary.h @@ -257,6 +257,8 @@ namespace spine { void readTimeline(DataInput &input, Array &timelines, BoneTimeline2 &timeline, float scale); + void readDrawOrder(DataInput &input, size_t slotCount, Array &drawOrder); + void setBezier(DataInput &input, CurveTimeline &timeline, int bezier, int frame, int value, float time1, float time2, float value1, float value2, float scale); }; diff --git a/spine-cpp/include/spine/SkeletonData.h b/spine-cpp/include/spine/SkeletonData.h index a5fd92f03..05dd4659c 100644 --- a/spine-cpp/include/spine/SkeletonData.h +++ b/spine-cpp/include/spine/SkeletonData.h @@ -88,6 +88,9 @@ namespace spine { /// @return May be NULL. Animation *findAnimation(const String &animationName); + /// Collects animations used by slider constraints. + Array &findSliderAnimations(Array &animations); + /// The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been /// set. const String &getName(); diff --git a/spine-cpp/include/spine/SkeletonJson.h b/spine-cpp/include/spine/SkeletonJson.h index d004930f6..afdf43fe6 100644 --- a/spine-cpp/include/spine/SkeletonJson.h +++ b/spine-cpp/include/spine/SkeletonJson.h @@ -111,6 +111,8 @@ namespace spine { void readVertices(Json *attachmentMap, VertexAttachment *attachment, size_t verticesLength); + bool readDrawOrder(SkeletonData *skeletonData, Json *keyMap, int slotCount, const Array *folderSlots, Array &drawOrder); + void setError(Json *root, const String &value1, const String &value2); int findSlotIndex(SkeletonData *skeletonData, const String &slotName, Array timelines); diff --git a/spine-cpp/include/spine/SliderData.h b/spine-cpp/include/spine/SliderData.h index 94aec7bfe..7a826cb36 100644 --- a/spine-cpp/include/spine/SliderData.h +++ b/spine-cpp/include/spine/SliderData.h @@ -45,6 +45,7 @@ namespace spine { /// Stores the setup pose for a Slider class SP_API SliderData : public ConstraintDataGeneric { friend class SkeletonBinary; + friend class SkeletonData; friend class SkeletonJson; friend class Slider; friend class SliderMixTimeline; diff --git a/spine-cpp/include/spine/spine.h b/spine-cpp/include/spine/spine.h index 12a28ca42..4b43f782d 100644 --- a/spine-cpp/include/spine/spine.h +++ b/spine-cpp/include/spine/spine.h @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include diff --git a/spine-cpp/src/spine/AnimationState.cpp b/spine-cpp/src/spine/AnimationState.cpp index 566dc2bac..36a058049 100644 --- a/spine-cpp/src/spine/AnimationState.cpp +++ b/spine-cpp/src/spine/AnimationState.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -444,7 +445,8 @@ void EventQueue::drain() { break; case EventType_Event: if (!trackEntry->_listenerObject) - SP_INVOKE_ANIMATION_LISTENER(trackEntry->_listener, state, queueEntry._type, trackEntry, queueEntry._event, trackEntry->_listenerUserData); + SP_INVOKE_ANIMATION_LISTENER(trackEntry->_listener, state, queueEntry._type, trackEntry, queueEntry._event, + trackEntry->_listenerUserData); else trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, queueEntry._event); if (!state._listenerObject) @@ -1203,7 +1205,8 @@ continue_outer: timelineMode[i] = Subsequent; } else { if (to == NULL || timeline->getRTTI().isExactly(AttachmentTimeline::rtti) || timeline->getRTTI().isExactly(DrawOrderTimeline::rtti) || - timeline->getRTTI().isExactly(EventTimeline::rtti) || !to->_animation->hasTimeline(ids)) { + timeline->getRTTI().isExactly(DrawOrderFolderTimeline::rtti) || timeline->getRTTI().isExactly(EventTimeline::rtti) || + !to->_animation->hasTimeline(ids)) { timelineMode[i] = First; } else { for (TrackEntry *next = to->_mixingTo; next != NULL; next = next->_mixingTo) { diff --git a/spine-cpp/src/spine/DrawOrderFolderTimeline.cpp b/spine-cpp/src/spine/DrawOrderFolderTimeline.cpp new file mode 100644 index 000000000..723b226d3 --- /dev/null +++ b/spine-cpp/src/spine/DrawOrderFolderTimeline.cpp @@ -0,0 +1,115 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(DrawOrderFolderTimeline, Timeline) + +DrawOrderFolderTimeline::DrawOrderFolderTimeline(size_t frameCount, Array &slots, size_t slotCount) : Timeline(frameCount, 1) { + PropertyId ids[] = {((PropertyId) Property_DrawOrder << 32)}; + setPropertyIds(ids, 1); + + _slots.addAll(slots); + _drawOrders.ensureCapacity(frameCount); + _inFolder.setSize(slotCount, false); + for (size_t i = 0; i < _slots.size(); ++i) _inFolder[_slots[i]] = true; + for (size_t i = 0; i < frameCount; ++i) { + Array vec; + _drawOrders.add(vec); + } +} + +void DrawOrderFolderTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array *events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { + SP_UNUSED(lastTime); + SP_UNUSED(events); + SP_UNUSED(alpha); + SP_UNUSED(appliedPose); + + if (direction == MixDirection_Out) { + if (blend == MixBlend_Setup) setup(skeleton); + } else if (time < _frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) setup(skeleton); + } else { + Array &drawOrder = _drawOrders[Animation::search(_frames, time)]; + if (drawOrder.size() == 0) + setup(skeleton); + else + apply(skeleton, drawOrder); + } +} + +size_t DrawOrderFolderTimeline::getFrameCount() { + return _frames.size(); +} + +Array &DrawOrderFolderTimeline::getSlots() { + return _slots; +} + +Array> &DrawOrderFolderTimeline::getDrawOrders() { + return _drawOrders; +} + +void DrawOrderFolderTimeline::setFrame(size_t frame, float time, Array *drawOrder) { + _frames[frame] = time; + _drawOrders[frame].clear(); + if (drawOrder != NULL) _drawOrders[frame].addAll(*drawOrder); +} + +void DrawOrderFolderTimeline::setup(Skeleton &skeleton) { + Array &drawOrder = skeleton._drawOrder; + Array &allSlots = skeleton._slots; + for (size_t i = 0, found = 0, done = _slots.size();; ++i) { + if (_inFolder[drawOrder[i]->getData().getIndex()]) { + drawOrder[i] = allSlots[_slots[found]]; + if (++found == done) break; + } + } +} + +void DrawOrderFolderTimeline::apply(Skeleton &skeleton, Array &drawOrderIndices) { + Array &drawOrder = skeleton._drawOrder; + Array &allSlots = skeleton._slots; + for (size_t i = 0, found = 0, done = _slots.size();; ++i) { + if (_inFolder[drawOrder[i]->getData().getIndex()]) { + drawOrder[i] = allSlots[_slots[drawOrderIndices[found]]]; + if (++found == done) break; + } + } +} diff --git a/spine-cpp/src/spine/SkeletonBinary.cpp b/spine-cpp/src/spine/SkeletonBinary.cpp index 6c4613b45..c8cd2b82d 100644 --- a/spine-cpp/src/spine/SkeletonBinary.cpp +++ b/spine-cpp/src/spine/SkeletonBinary.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -1304,33 +1305,33 @@ Animation *SkeletonBinary::readAnimation(DataInput &input, const String &name, S } // Draw order timeline. + size_t slotCount = skeletonData._slots.size(); size_t drawOrderCount = (size_t) input.readInt(true); if (drawOrderCount > 0) { DrawOrderTimeline *timeline = new (__FILE__, __LINE__) DrawOrderTimeline(drawOrderCount); - size_t slotCount = skeletonData._slots.size(); for (size_t i = 0; i < drawOrderCount; ++i) { float time = input.readFloat(); - size_t offsetCount = (size_t) input.readInt(true); Array drawOrder; - drawOrder.setSize(slotCount, 0); - for (int ii = (int) slotCount - 1; ii >= 0; --ii) drawOrder[ii] = -1; - Array unchanged; - unchanged.setSize(slotCount - offsetCount, 0); - size_t originalIndex = 0, unchangedIndex = 0; - for (size_t ii = 0; ii < offsetCount; ++ii) { - size_t slotIndex = (size_t) input.readInt(true); - // Collect unchanged items. - while (originalIndex != slotIndex) unchanged[unchangedIndex++] = (int) originalIndex++; - // Set changed items. - size_t index = originalIndex; - drawOrder[index + (size_t) input.readInt(true)] = (int) originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) unchanged[unchangedIndex++] = (int) originalIndex++; - // Fill in unchanged items. - for (int ii = (int) slotCount - 1; ii >= 0; --ii) - if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; - timeline->setFrame(i, time, &drawOrder); + readDrawOrder(input, slotCount, drawOrder); + timeline->setFrame(i, time, drawOrder.size() == 0 ? NULL : &drawOrder); + } + timelines.add(timeline); + } + + // Draw order folder timelines. + size_t folderCount = (size_t) input.readInt(true); + for (size_t i = 0; i < folderCount; ++i) { + size_t folderSlotCount = (size_t) input.readInt(true); + Array folderSlots; + folderSlots.setSize(folderSlotCount, 0); + for (size_t ii = 0; ii < folderSlotCount; ++ii) folderSlots[ii] = input.readInt(true); + size_t keyCount = (size_t) input.readInt(true); + DrawOrderFolderTimeline *timeline = new (__FILE__, __LINE__) DrawOrderFolderTimeline(keyCount, folderSlots, slotCount); + for (size_t ii = 0; ii < keyCount; ++ii) { + float time = input.readFloat(); + Array drawOrder; + readDrawOrder(input, folderSlotCount, drawOrder); + timeline->setFrame(ii, time, drawOrder.size() == 0 ? NULL : &drawOrder); } timelines.add(timeline); } @@ -1409,6 +1410,27 @@ void SkeletonBinary::readTimeline(DataInput &input, Array &timelines timelines.add(&timeline); } +void SkeletonBinary::readDrawOrder(DataInput &input, size_t slotCount, Array &drawOrder) { + size_t changeCount = (size_t) input.readInt(true); + drawOrder.clear(); + if (changeCount == 0) return; + + drawOrder.setSize(slotCount, 0); + for (int i = (int) slotCount - 1; i >= 0; --i) drawOrder[i] = -1; + Array unchanged; + unchanged.setSize(slotCount - changeCount, 0); + size_t originalIndex = 0, unchangedIndex = 0; + for (size_t i = 0; i < changeCount; ++i) { + size_t slotIndex = (size_t) input.readInt(true); + while (originalIndex != slotIndex) unchanged[unchangedIndex++] = (int) originalIndex++; + size_t index = originalIndex; + drawOrder[index + (size_t) input.readInt(true)] = (int) originalIndex++; + } + while (originalIndex < slotCount) unchanged[unchangedIndex++] = (int) originalIndex++; + for (int i = (int) slotCount - 1; i >= 0; --i) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; +} + void SkeletonBinary::setBezier(DataInput &input, CurveTimeline &timeline, int bezier, int frame, int value, float time1, float time2, float value1, float value2, float scale) { float cx1 = input.readFloat(); diff --git a/spine-cpp/src/spine/SkeletonData.cpp b/spine-cpp/src/spine/SkeletonData.cpp index e37d68909..1c42b90e1 100644 --- a/spine-cpp/src/spine/SkeletonData.cpp +++ b/spine-cpp/src/spine/SkeletonData.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include @@ -84,6 +85,17 @@ Animation *SkeletonData::findAnimation(const String &animationName) { return ArrayUtils::findWithName(_animations, animationName); } +Array &SkeletonData::findSliderAnimations(Array &animations) { + for (size_t i = 0, n = _constraints.size(); i < n; i++) { + ConstraintData *constraint = _constraints[i]; + if (constraint->getRTTI().instanceOf(SliderData::rtti)) { + SliderData *data = static_cast(constraint); + if (data->_animation != NULL) animations.add(data->_animation); + } + } + return animations; +} + const String &SkeletonData::getName() { return _name; } diff --git a/spine-cpp/src/spine/SkeletonJson.cpp b/spine-cpp/src/spine/SkeletonJson.cpp index 98901e2c6..fd7f23496 100644 --- a/spine-cpp/src/spine/SkeletonJson.cpp +++ b/spine-cpp/src/spine/SkeletonJson.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -1242,8 +1243,13 @@ Animation *SkeletonJson::readAnimation(Json *map, SkeletonData *skeletonData) { return NULL; } for (Json *slotMap = attachmentsMap->_child; slotMap; slotMap = slotMap->_next) { - int slotIndex = findSlotIndex(skeletonData, slotMap->_name, timelines); - if (slotIndex == -1) return NULL; + SlotData *slot = skeletonData->findSlot(slotMap->_name); + if (!slot) { + ArrayUtils::deleteElements(timelines); + setError(NULL, "Attachment slot not found: ", slotMap->_name); + return NULL; + } + int slotIndex = slot->getIndex(); for (Json *attachmentMap = slotMap->_child; attachmentMap; attachmentMap = attachmentMap->_next) { Attachment *attachment = skin->getAttachment(slotIndex, attachmentMap->_name); if (!attachment) { @@ -1327,36 +1333,48 @@ Animation *SkeletonJson::readAnimation(Json *map, SkeletonData *skeletonData) { int frame = 0; for (Json *keyMap = drawOrder->_child; keyMap; keyMap = keyMap->_next, ++frame) { Array drawOrder2; - Json *offsets = Json::getItem(keyMap, "offsets"); - if (offsets) { - drawOrder2.setSize(slotCount, 0); - for (int i = slotCount - 1; i >= 0; i--) drawOrder2[i] = -1; - Array unchanged; - unchanged.setSize(slotCount - offsets->_size, 0); - int originalIndex = 0, unchangedIndex = 0; - for (Json *offsetMap = offsets->_child; offsetMap; offsetMap = offsetMap->_next) { - SlotData *slot = skeletonData->findSlot(Json::getString(offsetMap, "slot", 0)); - if (!slot) { - ArrayUtils::deleteElements(timelines); - return NULL; - } - /* Collect unchanged items. */ - while (originalIndex != slot->_index) unchanged[unchangedIndex++] = originalIndex++; - /* Set changed items. */ - int index = originalIndex; - drawOrder2[index + Json::getInt(offsetMap, "offset", 0)] = originalIndex++; - } - /* Collect remaining unchanged items. */ - while (originalIndex < slotCount) unchanged[unchangedIndex++] = originalIndex++; - /* Fill in unchanged items. */ - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder2[i] == -1) drawOrder2[i] = unchanged[--unchangedIndex]; + if (!readDrawOrder(skeletonData, keyMap, slotCount, NULL, drawOrder2)) { + ArrayUtils::deleteElements(timelines); + return NULL; } - timeline->setFrame(frame, Json::getFloat(keyMap, "time", 0), &drawOrder2); + timeline->setFrame(frame, Json::getFloat(keyMap, "time", 0), drawOrder2.size() == 0 ? NULL : &drawOrder2); } timelines.add(timeline); } + // Draw order folder timelines. + Json *drawOrderFolder = Json::getItem(map, "drawOrderFolder"); + if (drawOrderFolder) { + for (Json *timelineMap = drawOrderFolder->_child; timelineMap; timelineMap = timelineMap->_next) { + Json *slotEntry = Json::getItem(timelineMap, "slots"); + Array folderSlots; + folderSlots.setSize(slotEntry ? slotEntry->_size : 0, 0); + int ii = 0; + for (Json *entry = slotEntry ? slotEntry->_child : NULL; entry; entry = entry->_next, ++ii) { + SlotData *slot = skeletonData->findSlot(entry->_valueString); + if (!slot) { + ArrayUtils::deleteElements(timelines); + setError(NULL, "Draw order folder slot not found: ", entry->_valueString); + return NULL; + } + folderSlots[ii] = slot->getIndex(); + } + Json *keyMap = Json::getItem(timelineMap, "keys"); + DrawOrderFolderTimeline *timeline = new (__FILE__, __LINE__) + DrawOrderFolderTimeline(keyMap ? keyMap->_size : 0, folderSlots, skeletonData->_slots.size()); + int frame = 0; + for (Json *entry = keyMap ? keyMap->_child : NULL; entry; entry = entry->_next, ++frame) { + Array folderDrawOrder; + if (!readDrawOrder(skeletonData, entry, (int) folderSlots.size(), &folderSlots, folderDrawOrder)) { + ArrayUtils::deleteElements(timelines); + return NULL; + } + timeline->setFrame(frame, Json::getFloat(entry, "time", 0), folderDrawOrder.size() == 0 ? NULL : &folderDrawOrder); + } + timelines.add(timeline); + } + } + // Event timeline. Json *events = Json::getItem(map, "events"); if (events) { @@ -1462,6 +1480,49 @@ int SkeletonJson::findSlotIndex(SkeletonData *skeletonData, const String &slotNa return slotIndex; } +bool SkeletonJson::readDrawOrder(SkeletonData *skeletonData, Json *keyMap, int slotCount, const Array *folderSlots, Array &drawOrder) { + Json *changes = Json::getItem(keyMap, "offsets"); + drawOrder.clear(); + if (changes == NULL) return true; + + drawOrder.setSize(slotCount, 0); + for (int i = slotCount - 1; i >= 0; i--) drawOrder[i] = -1; + Array unchanged; + unchanged.setSize(slotCount - changes->_size, 0); + int originalIndex = 0, unchangedIndex = 0; + for (Json *offsetMap = changes->_child; offsetMap; offsetMap = offsetMap->_next) { + const char *slotName = Json::getString(offsetMap, "slot", 0); + SlotData *slot = skeletonData->findSlot(slotName); + if (slot == NULL) { + setError(NULL, "Draw order slot not found: ", slotName); + return false; + } + int index; + if (folderSlots == NULL) { + index = slot->getIndex(); + } else { + index = -1; + for (int i = 0; i < slotCount; i++) { + if ((*folderSlots)[i] == slot->getIndex()) { + index = i; + break; + } + } + if (index == -1) { + setError(NULL, "Slot not in folder: ", slotName); + return false; + } + } + while (originalIndex != index) unchanged[unchangedIndex++] = originalIndex++; + int drawOrderIndex = originalIndex; + drawOrder[drawOrderIndex + Json::getInt(offsetMap, "offset", 0)] = originalIndex++; + } + while (originalIndex < slotCount) unchanged[unchangedIndex++] = originalIndex++; + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + return true; +} + void SkeletonJson::setError(Json *root, const String &value1, const String &value2) { _error = String(value1).append(value2); delete root;