/****************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace spine; SkeletonBinary::SkeletonBinary(Atlas &atlas) : _attachmentLoader(new(__FILE__, __LINE__) AtlasAttachmentLoader(atlas)), _error(), _scale(1), _ownsLoader(true) { } SkeletonBinary::SkeletonBinary(AttachmentLoader &attachmentLoader, bool ownsLoader) : _attachmentLoader(&attachmentLoader), _error(), _scale(1), _ownsLoader(ownsLoader) { } SkeletonBinary::~SkeletonBinary() { ArrayUtils::deleteElements(_linkedMeshes); _linkedMeshes.clear(); if (_ownsLoader) delete _attachmentLoader; } SkeletonData *SkeletonBinary::readSkeletonDataFile(const String &path) { int length; const char *binary = SpineExtension::readFile(path.buffer(), &length); if (length == 0 || !binary) { setError("Unable to read skeleton file: ", path.buffer()); return NULL; } SkeletonData *skeletonData = readSkeletonData((unsigned char *) binary, length); SpineExtension::free(binary, __FILE__, __LINE__); if (skeletonData) { // Extract filename without extension const char *lastSlash = strrchr(path.buffer(), '/'); const char *lastBackslash = strrchr(path.buffer(), '\\'); const char *filename = path.buffer(); if (lastSlash) filename = lastSlash + 1; if (lastBackslash && lastBackslash > filename) filename = lastBackslash + 1; String nameWithoutExtension(filename); const char *lastDot = strrchr(nameWithoutExtension.buffer(), '.'); if (lastDot) { int length = (int) (lastDot - nameWithoutExtension.buffer()); nameWithoutExtension = nameWithoutExtension.substring(0, length); } skeletonData->_name = nameWithoutExtension; } return skeletonData; } SkeletonData *SkeletonBinary::readSkeletonData(const unsigned char *binary, const int length) { if (binary == NULL || length == 0) { setError("Unable to read skeleton file: ", ""); return NULL; } ArrayUtils::deleteElements(_linkedMeshes); _linkedMeshes.clear(); SkeletonData *skeletonData = new (__FILE__, __LINE__) SkeletonData(); DataInput input(skeletonData, binary, length); String version; {// try block in Java long long hash = input.readLong(); if (hash == 0) { skeletonData->_hash = ""; } else { char buffer[32]; snprintf(buffer, 32, "%lld", hash); skeletonData->_hash = String(buffer); } skeletonData->_version.own(input.readString()); if (skeletonData->_version.isEmpty()) skeletonData->_version = ""; version = skeletonData->_version; if (!skeletonData->_version.startsWith(SPINE_VERSION_STRING)) { String errorMsg = "Skeleton version "; errorMsg.append(skeletonData->_version); errorMsg.append(" does not match runtime version "); errorMsg.append(SPINE_VERSION_STRING); setError(errorMsg.buffer(), ""); delete skeletonData; return NULL; } skeletonData->_x = input.readFloat(); skeletonData->_y = input.readFloat(); skeletonData->_width = input.readFloat(); skeletonData->_height = input.readFloat(); skeletonData->_referenceScale = input.readFloat() * this->_scale; bool nonessential = input.readBoolean(); if (nonessential) { skeletonData->_fps = input.readFloat(); skeletonData->_imagesPath.own(input.readString()); skeletonData->_audioPath.own(input.readString()); } int n = input.readInt(true); Array &strings = skeletonData->_strings.setSize(n, NULL); for (int i = 0; i < n; i++) strings[i] = input.readString(); /* Bones. */ Array &bones = skeletonData->_bones.setSize(input.readInt(true), NULL); for (int i = 0; i < (int) bones.size(); ++i) { const char *name = input.readString(); BoneData *parent = i == 0 ? 0 : bones[input.readInt(true)]; BoneData *data = new (__FILE__, __LINE__) BoneData(i, String(name, true), parent); BoneLocal &setup = data->_setup; setup._rotation = input.readFloat(); setup._x = input.readFloat() * _scale; setup._y = input.readFloat() * _scale; setup._scaleX = input.readFloat(); setup._scaleY = input.readFloat(); setup._shearX = input.readFloat(); setup._shearY = input.readFloat(); setup._inherit = static_cast(input.readByte()); data->_length = input.readFloat() * _scale; data->_skinRequired = input.readBoolean(); if (nonessential) { Color::rgba8888ToColor(data->getColor(), input.readInt()); data->_icon.own(input.readString()); data->_visible = input.readBoolean(); } bones[i] = data; } /* Slots. */ Array &slots = skeletonData->_slots.setSize(input.readInt(true), NULL); for (int i = 0; i < (int) slots.size(); ++i) { String slotName = String(input.readString(), true); BoneData *boneData = bones[input.readInt(true)]; SlotData *data = new (__FILE__, __LINE__) SlotData(i, slotName, *boneData); Color::rgba8888ToColor(data->_setup._color, input.readInt()); int darkColor = input.readInt(); if (darkColor != -1) { Color::rgb888ToColor(data->_setup._darkColor, darkColor); data->_setup._hasDarkColor = true; } data->_attachmentName = input.readStringRef(); data->_blendMode = static_cast(input.readInt(true)); if (nonessential) data->_visible = input.readBoolean(); slots[i] = data; } /* Constraints. */ int constraintCount = input.readInt(true); Array &constraints = skeletonData->_constraints.setSize(constraintCount, NULL); for (int i = 0; i < constraintCount; i++) { String name(input.readString(), true); int nn; switch (input.readByte()) { case CONSTRAINT_IK: { IkConstraintData *data = new (__FILE__, __LINE__) IkConstraintData(name); Array &constraintBones = data->_bones.setSize(nn = input.readInt(true), NULL); for (int ii = 0; ii < nn; ii++) constraintBones[ii] = bones[input.readInt(true)]; data->_target = 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; constraints[i] = data; break; } case CONSTRAINT_TRANSFORM: { TransformConstraintData *data = new (__FILE__, __LINE__) TransformConstraintData(name); Array &constraintBones = data->_bones.setSize(nn = input.readInt(true), NULL); for (int ii = 0; ii < nn; ii++) constraintBones[ii] = bones[input.readInt(true)]; data->_source = 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; Array &froms = data->_properties.setSize(nn = flags >> 5, NULL); for (int ii = 0, tn; ii < nn; ii++) { float fromScale = 1; FromProperty *from = NULL; switch (input.readByte()) { case 0: from = new (__FILE__, __LINE__) FromRotate(); break; case 1: fromScale = _scale; from = new (__FILE__, __LINE__) FromX(); break; case 2: fromScale = _scale; from = new (__FILE__, __LINE__) FromY(); break; case 3: from = new (__FILE__, __LINE__) FromScaleX(); break; case 4: from = new (__FILE__, __LINE__) FromScaleY(); break; case 5: from = new (__FILE__, __LINE__) FromShearY(); break; } from->_offset = input.readFloat() * fromScale; Array &tos = from->_to.setSize(tn = input.readByte(), NULL); for (int t = 0; t < tn; t++) { float toScale = 1; ToProperty *to = NULL; switch (input.readByte()) { case 0: to = new (__FILE__, __LINE__) ToRotate(); break; case 1: toScale = _scale; to = new (__FILE__, __LINE__) ToX(); break; case 2: toScale = _scale; to = new (__FILE__, __LINE__) ToY(); break; case 3: to = new (__FILE__, __LINE__) ToScaleX(); break; case 4: to = new (__FILE__, __LINE__) ToScaleY(); break; case 5: to = new (__FILE__, __LINE__) ToShearY(); break; } to->_offset = input.readFloat() * toScale; to->_max = input.readFloat() * toScale; to->_scale = input.readFloat() * toScale / fromScale; tos[t] = to; } froms[ii] = from; } flags = input.read(); if ((flags & 1) != 0) data->_offsets[TransformConstraintData::ROTATION] = input.readFloat(); if ((flags & 2) != 0) data->_offsets[TransformConstraintData::X] = input.readFloat() * _scale; if ((flags & 4) != 0) data->_offsets[TransformConstraintData::Y] = input.readFloat() * _scale; if ((flags & 8) != 0) data->_offsets[TransformConstraintData::SCALEX] = input.readFloat(); if ((flags & 16) != 0) data->_offsets[TransformConstraintData::SCALEY] = input.readFloat(); if ((flags & 32) != 0) data->_offsets[TransformConstraintData::SHEARY] = 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(); constraints[i] = data; break; } case CONSTRAINT_PATH: { PathConstraintData *data = new (__FILE__, __LINE__) PathConstraintData(name); Array &constraintBones = data->_bones.setSize(nn = input.readInt(true), NULL); for (int ii = 0; ii < nn; ii++) constraintBones[ii] = bones[input.readInt(true)]; data->_slot = slots[input.readInt(true)]; int flags = input.read(); data->_skinRequired = (flags & 1) != 0; data->_positionMode = (PositionMode) ((flags >> 1) & 2); data->_spacingMode = (SpacingMode) ((flags >> 2) & 3); data->_rotateMode = (RotateMode) ((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(); constraints[i] = data; break; } case CONSTRAINT_PHYSICS: { PhysicsConstraintData *data = new (__FILE__, __LINE__) PhysicsConstraintData(name); data->_bone = 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 = 1.f / input.readUnsignedByte(); PhysicsConstraintPose &setup = data->getSetupPose(); 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; constraints[i] = data; break; } case CONSTRAINT_SLIDER: { SliderData *data = new (__FILE__, __LINE__) SliderData(name); int flags = input.read(); data->_skinRequired = (flags & 1) != 0; data->_loop = (flags & 2) != 0; data->_additive = (flags & 4) != 0; SliderPose &setup = data->_setup; if ((flags & 8) != 0) setup._time = input.readFloat(); if ((flags & 16) != 0) setup._mix = (flags & 32) != 0 ? input.readFloat() : 1; if ((flags & 64) != 0) { data->_local = (flags & 128) != 0; data->_bone = bones[input.readInt(true)]; float offset = input.readFloat(); float propertyScale = 1; switch (input.readByte()) { case 0: data->_property = new (__FILE__, __LINE__) FromRotate(); break; case 1: propertyScale = _scale; data->_property = new (__FILE__, __LINE__) FromX(); break; case 2: propertyScale = _scale; data->_property = new (__FILE__, __LINE__) FromY(); break; case 3: data->_property = new (__FILE__, __LINE__) FromScaleX(); break; case 4: data->_property = new (__FILE__, __LINE__) FromScaleY(); break; case 5: data->_property = new (__FILE__, __LINE__) FromShearY(); break; default: data->_property = NULL; break; } if (data->_property) data->_property->_offset = offset * propertyScale; data->_offset = input.readFloat(); data->_scale = input.readFloat() / propertyScale; } constraints[i] = data; break; } } } /* Default skin. */ Skin *defaultSkin = readSkin(input, *skeletonData, true, nonessential); if (defaultSkin) { skeletonData->_defaultSkin = defaultSkin; skeletonData->_skins.add(defaultSkin); } if (!this->getError().isEmpty()) { delete skeletonData; return NULL; } /* Skins. */ { int i = (int) skeletonData->_skins.size(); Array &skins = skeletonData->_skins.setSize(n = i + input.readInt(true), NULL); for (; i < n; i++) { Skin *skin = readSkin(input, *skeletonData, false, nonessential); if (skin) skins[i] = skin; else { delete skeletonData; return NULL; } } } /* Linked meshes. */ Array &items = _linkedMeshes; for (int i = 0, n = (int) items.size(); i < n; i++) { LinkedMesh *linkedMesh = items[i]; Skin *skin = skeletonData->_skins[linkedMesh->_skinIndex]; Attachment *parent = skin->getAttachment(linkedMesh->_slotIndex, linkedMesh->_parent); if (parent == NULL) { delete skeletonData; setError("Parent mesh not found: ", linkedMesh->_parent.buffer()); return NULL; } linkedMesh->_mesh->_timelineAttachment = linkedMesh->_inheritTimelines ? static_cast(parent) : linkedMesh->_mesh; linkedMesh->_mesh->setParentMesh(static_cast(parent)); if (linkedMesh->_mesh->getSequence() == NULL) linkedMesh->_mesh->updateRegion(); } ArrayUtils::deleteElements(_linkedMeshes); _linkedMeshes.clear(); /* Events. */ int eventsCount = input.readInt(true); Array &events = skeletonData->_events.setSize(eventsCount, NULL); for (int i = 0; i < eventsCount; ++i) { EventData *eventData = new (__FILE__, __LINE__) EventData(String(input.readString(), true)); eventData->_intValue = input.readInt(false); eventData->_floatValue = input.readFloat(); eventData->_stringValue.own(input.readString()); eventData->_audioPath.own(input.readString()); if (!eventData->_audioPath.isEmpty()) { eventData->_volume = input.readFloat(); eventData->_balance = input.readFloat(); } events[i] = eventData; } /* Animations. */ int animationsCount = input.readInt(true); Array &animations = skeletonData->_animations.setSize(animationsCount, NULL); for (int i = 0; i < animationsCount; ++i) { Animation *animation = readAnimation(input, String(input.readString(), true), *skeletonData); if (!animation) { delete skeletonData; setError("Error reading animation: ", input.readString()); return NULL; } animations[i] = animation; } for (int i = 0; i < constraintCount; i++) { if (constraints[i]->getRTTI().instanceOf(SliderData::rtti)) { SliderData *data = static_cast(constraints[i]); data->setAnimation(*animations[input.readInt(true)]); } } } return skeletonData; } void SkeletonBinary::setError(const char *value1, const char *value2) { char message[256]; int length; strcpy(message, value1); length = (int) strlen(value1); if (value2) strncat(message + length, value2, 255 - length); _error = String(message); } Skin *SkeletonBinary::readSkin(DataInput &input, SkeletonData &skeletonData, bool defaultSkin, bool nonessential) { Skin *skin; int slotCount = 0; if (defaultSkin) { slotCount = input.readInt(true); if (slotCount == 0) return NULL; skin = new (__FILE__, __LINE__) Skin("default"); } else { skin = new (__FILE__, __LINE__) Skin(String(input.readString(), true)); if (nonessential) Color::rgba8888ToColor(skin->getColor(), input.readInt()); int n; Array &from = skeletonData._bones; Array &bones = skin->getBones().setSize(n = input.readInt(true), NULL); for (int i = 0; i < n; i++) bones[i] = from[input.readInt(true)]; Array &fromConstraints = skeletonData._constraints; Array &constraints = skin->getConstraints().setSize(n = input.readInt(true), NULL); for (int i = 0; i < n; i++) constraints[i] = fromConstraints[input.readInt(true)]; slotCount = input.readInt(true); } for (int i = 0; i < slotCount; ++i) { int slotIndex = input.readInt(true); for (int ii = 0, nn = input.readInt(true); ii < nn; ++ii) { String name(input.readStringRef()); Attachment *attachment = readAttachment(input, *skin, slotIndex, name, skeletonData, nonessential); if (attachment) skin->setAttachment(slotIndex, name, *attachment); else { setError("Error reading attachment: ", name.buffer()); delete skin; return NULL; } } } return skin; } Attachment *SkeletonBinary::readAttachment(DataInput &input, Skin &skin, int slotIndex, const String &attachmentName, SkeletonData &skeletonData, bool nonessential) { float scale = _scale; int flags = input.readByte(); String name = (flags & 8) != 0 ? input.readStringRef() : attachmentName; AttachmentType type = static_cast(flags & 0x7); switch (type) { case AttachmentType_Region: { String path = (flags & 16) != 0 ? input.readStringRef() : name; int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff; Sequence *sequence = (flags & 64) != 0 ? readSequence(input) : nullptr; float rotation = (flags & 128) != 0 ? input.readFloat() : 0; float x = input.readFloat(); float y = input.readFloat(); float scaleX = input.readFloat(); float scaleY = input.readFloat(); float width = input.readFloat(); float height = input.readFloat(); RegionAttachment *region = _attachmentLoader->newRegionAttachment(skin, name, path, sequence); if (!region) return NULL; region->setPath(path); region->setX(x * scale); region->setY(y * scale); region->setScaleX(scaleX); region->setScaleY(scaleY); region->setRotation(rotation); region->setWidth(width * scale); region->setHeight(height * scale); Color::rgba8888ToColor(region->getColor(), color); region->setSequence(sequence); if (sequence == NULL) region->updateRegion(); return region; } case AttachmentType_Boundingbox: { Array vertices; Array bones; int verticesLength = readVertices(input, vertices, bones, (flags & 16) != 0); int color = nonessential ? input.readInt() : 0; BoundingBoxAttachment *box = _attachmentLoader->newBoundingBoxAttachment(skin, name); if (!box) return NULL; box->setWorldVerticesLength(verticesLength); box->setVertices(vertices); box->setBones(bones); if (nonessential) Color::rgba8888ToColor(box->getColor(), color); return box; } case AttachmentType_Mesh: { String path = (flags & 16) != 0 ? input.readStringRef() : name; int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff; Sequence *sequence = (flags & 64) != 0 ? readSequence(input) : nullptr; int hullLength = input.readInt(true); Array vertices; Array bones; int verticesLength = readVertices(input, vertices, bones, (flags & 128) != 0); Array uvs; readFloatArray(input, verticesLength, 1, uvs); Array triangles; readUnsignedShortArray(input, triangles, (verticesLength - hullLength - 2) * 3); Array edges; float width = 0, height = 0; if (nonessential) { readUnsignedShortArray(input, edges, input.readInt(true)); width = input.readFloat(); height = input.readFloat(); } MeshAttachment *mesh = _attachmentLoader->newMeshAttachment(skin, name, path, sequence); if (!mesh) return NULL; mesh->setPath(path); Color::rgba8888ToColor(mesh->getColor(), color); mesh->setBones(bones); mesh->setVertices(vertices); mesh->setWorldVerticesLength(verticesLength); mesh->setTriangles(triangles); mesh->setRegionUVs(uvs); if (sequence == NULL) mesh->updateRegion(); mesh->setHullLength(hullLength << 1); mesh->setSequence(sequence); if (nonessential) { mesh->setEdges(edges); mesh->setWidth(width * scale); mesh->setHeight(height * scale); } return mesh; } case AttachmentType_Linkedmesh: { String path = (flags & 16) != 0 ? input.readStringRef() : name; int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff; Sequence *sequence = (flags & 64) != 0 ? readSequence(input) : nullptr; bool inheritTimelines = (flags & 128) != 0; int skinIndex = input.readInt(true); String parent = input.readStringRef(); float width = 0, height = 0; if (nonessential) { width = input.readFloat(); height = input.readFloat(); } MeshAttachment *mesh = _attachmentLoader->newMeshAttachment(skin, name, path, sequence); if (!mesh) return NULL; mesh->setPath(path); Color::rgba8888ToColor(mesh->getColor(), color); mesh->setSequence(sequence); if (nonessential) { mesh->setWidth(width * scale); mesh->setHeight(height * scale); } _linkedMeshes.add(new (__FILE__, __LINE__) LinkedMesh(*mesh, skinIndex, slotIndex, parent, inheritTimelines)); return mesh; } case AttachmentType_Path: { bool closed = (flags & 16) != 0; bool constantSpeed = (flags & 32) != 0; Array vertices; Array bones; int verticesLength = readVertices(input, vertices, bones, (flags & 64) != 0); Array lengths; lengths.setSize(verticesLength / 6, 0); for (int i = 0, n = (int) lengths.size(); i < n; i++) lengths[i] = input.readFloat() * scale; int color = nonessential ? input.readInt() : 0; PathAttachment *path = _attachmentLoader->newPathAttachment(skin, name); if (!path) return NULL; path->setClosed(closed); path->setConstantSpeed(constantSpeed); path->setWorldVerticesLength(verticesLength); path->setVertices(vertices); path->setBones(bones); path->setLengths(lengths); if (nonessential) Color::rgba8888ToColor(path->getColor(), color); return path; } case AttachmentType_Point: { float rotation = input.readFloat(); float x = input.readFloat(); float y = input.readFloat(); int color = nonessential ? input.readInt() : 0; PointAttachment *point = _attachmentLoader->newPointAttachment(skin, name); if (!point) return NULL; point->setX(x * scale); point->setY(y * scale); point->setRotation(rotation); if (nonessential) Color::rgba8888ToColor(point->getColor(), color); return point; } case AttachmentType_Clipping: { int endSlotIndex = input.readInt(true); Array vertices; Array bones; int verticesLength = readVertices(input, vertices, bones, (flags & 16) != 0); int color = nonessential ? input.readInt() : 0; ClippingAttachment *clip = _attachmentLoader->newClippingAttachment(skin, name); if (!clip) return NULL; clip->setEndSlot(skeletonData._slots[endSlotIndex]); clip->setWorldVerticesLength(verticesLength); clip->setVertices(vertices); clip->setBones(bones); if (nonessential) Color::rgba8888ToColor(clip->getColor(), color); return clip; } } return NULL; } Sequence *SkeletonBinary::readSequence(DataInput &input) { Sequence *sequence = new (__FILE__, __LINE__) Sequence(input.readInt(true)); sequence->setStart(input.readInt(true)); sequence->setDigits(input.readInt(true)); sequence->setSetupIndex(input.readInt(true)); return sequence; } int SkeletonBinary::readVertices(DataInput &input, Array &vertices, Array &bones, bool weighted) { float scale = _scale; int vertexCount = input.readInt(true); int verticesLength = vertexCount << 1; if (!weighted) { readFloatArray(input, verticesLength, scale, vertices.setSize(verticesLength, 0)); return verticesLength; } vertices.ensureCapacity(verticesLength * 3 * 3); bones.ensureCapacity(verticesLength * 3); for (int i = 0; i < vertexCount; ++i) { int boneCount = input.readInt(true); bones.add(boneCount); for (int ii = 0; ii < boneCount; ++ii) { bones.add(input.readInt(true)); vertices.add(input.readFloat() * scale); vertices.add(input.readFloat() * scale); vertices.add(input.readFloat()); } } return verticesLength; } void SkeletonBinary::readFloatArray(DataInput &input, int n, float scale, Array &array) { array.setSize(n, 0); int i; if (scale == 1) { for (i = 0; i < n; ++i) { array[i] = input.readFloat(); } } else { for (i = 0; i < n; ++i) { array[i] = input.readFloat() * scale; } } } void SkeletonBinary::readUnsignedShortArray(DataInput &input, Array &array, int n) { array.setSize(n, 0); for (int i = 0; i < n; ++i) { array[i] = (unsigned short) input.readInt(true); } } Animation *SkeletonBinary::readAnimation(DataInput &input, const String &name, SkeletonData &skeletonData) { Array timelines; timelines.ensureCapacity(input.readInt(true)); float scale = _scale; // Slot timelines. for (int i = 0, n = input.readInt(true); i < n; ++i) { int slotIndex = input.readInt(true); for (int ii = 0, nn = input.readInt(true); ii < nn; ++ii) { int timelineType = input.readByte(), frameCount = input.readInt(true), frameLast = frameCount - 1; switch (timelineType) { case SLOT_ATTACHMENT: { AttachmentTimeline *timeline = new (__FILE__, __LINE__) AttachmentTimeline(frameCount, slotIndex); for (int frame = 0; frame < frameCount; ++frame) { float time = input.readFloat(); char *attachmentName = input.readStringRef(); timeline->setFrame(frame, time, attachmentName); } timelines.add(timeline); break; } case SLOT_RGBA: { RGBATimeline *timeline = new (__FILE__, __LINE__) RGBATimeline(frameCount, input.readInt(true), slotIndex); float time = input.readFloat(); float r = input.read() / 255.0f, g = input.read() / 255.0f; float b = input.read() / 255.0f, a = input.read() / 255.0f; for (int frame = 0, bezier = 0;; frame++) { timeline->setFrame(frame, time, r, g, b, a); if (frame == frameLast) break; float time2 = input.readFloat(); float r2 = input.read() / 255.0f, g2 = input.read() / 255.0f; float b2 = input.read() / 255.0f, a2 = input.read() / 255.0f; int curveType = input.readByte(); switch (curveType) { case CURVE_STEPPED: timeline->setStepped(frame); break; case CURVE_BEZIER: setBezier(input, *timeline, bezier++, frame, 0, time, time2, r, r2, 1); setBezier(input, *timeline, bezier++, frame, 1, time, time2, g, g2, 1); setBezier(input, *timeline, bezier++, frame, 2, time, time2, b, b2, 1); setBezier(input, *timeline, bezier++, frame, 3, time, time2, a, a2, 1); break; } time = time2; r = r2; g = g2; b = b2; a = a2; } timelines.add(timeline); break; } case SLOT_RGB: { RGBTimeline *timeline = new (__FILE__, __LINE__) RGBTimeline(frameCount, input.readInt(true), slotIndex); float time = input.readFloat(); float r = input.read() / 255.0f, g = input.read() / 255.0f, b = input.read() / 255.0f; for (int frame = 0, bezier = 0;; frame++) { timeline->setFrame(frame, time, r, g, b); if (frame == frameLast) break; float time2 = input.readFloat(); float r2 = input.read() / 255.0f, g2 = input.read() / 255.0f, b2 = input.read() / 255.0f; int curveType = input.readByte(); switch (curveType) { case CURVE_STEPPED: timeline->setStepped(frame); break; case CURVE_BEZIER: setBezier(input, *timeline, bezier++, frame, 0, time, time2, r, r2, 1); setBezier(input, *timeline, bezier++, frame, 1, time, time2, g, g2, 1); setBezier(input, *timeline, bezier++, frame, 2, time, time2, b, b2, 1); break; } time = time2; r = r2; g = g2; b = b2; } timelines.add(timeline); break; } case SLOT_RGBA2: { RGBA2Timeline *timeline = new (__FILE__, __LINE__) RGBA2Timeline(frameCount, input.readInt(true), slotIndex); float time = input.readFloat(); float r = input.read() / 255.0f, g = input.read() / 255.0f; float b = input.read() / 255.0f, a = input.read() / 255.0f; float r2 = input.read() / 255.0f, g2 = input.read() / 255.0f, b2 = input.read() / 255.0f; for (int frame = 0, bezier = 0;; frame++) { timeline->setFrame(frame, time, r, g, b, a, r2, g2, b2); if (frame == frameLast) break; float time2 = input.readFloat(); float nr = input.read() / 255.0f, ng = input.read() / 255.0f; float nb = input.read() / 255.0f, na = input.read() / 255.0f; float nr2 = input.read() / 255.0f, ng2 = input.read() / 255.0f, nb2 = input.read() / 255.0f; int curveType = input.readByte(); switch (curveType) { case CURVE_STEPPED: timeline->setStepped(frame); break; case CURVE_BEZIER: setBezier(input, *timeline, bezier++, frame, 0, time, time2, r, nr, 1); setBezier(input, *timeline, bezier++, frame, 1, time, time2, g, ng, 1); setBezier(input, *timeline, bezier++, frame, 2, time, time2, b, nb, 1); setBezier(input, *timeline, bezier++, frame, 3, time, time2, a, na, 1); setBezier(input, *timeline, bezier++, frame, 4, time, time2, r2, nr2, 1); setBezier(input, *timeline, bezier++, frame, 5, time, time2, g2, ng2, 1); setBezier(input, *timeline, bezier++, frame, 6, time, time2, b2, nb2, 1); break; } time = time2; r = nr; g = ng; b = nb; a = na; r2 = nr2; g2 = ng2; b2 = nb2; } timelines.add(timeline); break; } case SLOT_RGB2: { RGB2Timeline *timeline = new (__FILE__, __LINE__) RGB2Timeline(frameCount, input.readInt(true), slotIndex); float time = input.readFloat(); float r = input.read() / 255.0f, g = input.read() / 255.0f, b = input.read() / 255.0f; float r2 = input.read() / 255.0f, g2 = input.read() / 255.0f, b2 = input.read() / 255.0f; for (int frame = 0, bezier = 0;; frame++) { timeline->setFrame(frame, time, r, g, b, r2, g2, b2); if (frame == frameLast) break; float time2 = input.readFloat(); float nr = input.read() / 255.0f, ng = input.read() / 255.0f, nb = input.read() / 255.0f; float nr2 = input.read() / 255.0f, ng2 = input.read() / 255.0f, nb2 = input.read() / 255.0f; int curveType = input.readByte(); switch (curveType) { case CURVE_STEPPED: timeline->setStepped(frame); break; case CURVE_BEZIER: setBezier(input, *timeline, bezier++, frame, 0, time, time2, r, nr, 1); setBezier(input, *timeline, bezier++, frame, 1, time, time2, g, ng, 1); setBezier(input, *timeline, bezier++, frame, 2, time, time2, b, nb, 1); setBezier(input, *timeline, bezier++, frame, 3, time, time2, r2, nr2, 1); setBezier(input, *timeline, bezier++, frame, 4, time, time2, g2, ng2, 1); setBezier(input, *timeline, bezier++, frame, 5, time, time2, b2, nb2, 1); break; } time = time2; r = nr; g = ng; b = nb; r2 = nr2; g2 = ng2; b2 = nb2; } timelines.add(timeline); break; } case SLOT_ALPHA: { AlphaTimeline *timeline = new (__FILE__, __LINE__) AlphaTimeline(frameCount, input.readInt(true), slotIndex); float time = input.readFloat(), a = input.read() / 255.0f; for (int frame = 0, bezier = 0;; frame++) { timeline->setFrame(frame, time, a); if (frame == frameLast) break; float time2 = input.readFloat(); float a2 = input.read() / 255.0f; int curveType = input.readByte(); switch (curveType) { case CURVE_STEPPED: timeline->setStepped(frame); break; case CURVE_BEZIER: setBezier(input, *timeline, bezier++, frame, 0, time, time2, a, a2, 1); break; } time = time2; a = a2; } timelines.add(timeline); break; } default: { ArrayUtils::deleteElements(timelines); setError("Invalid slot timeline type: ", String().append(timelineType).buffer()); return NULL; } } } } // Bone timelines. for (int i = 0, n = input.readInt(true); i < n; ++i) { int boneIndex = input.readInt(true); for (int ii = 0, nn = input.readInt(true); ii < nn; ++ii) { int timelineType = input.readByte(), frameCount = input.readInt(true); if (timelineType == BONE_INHERIT) { InheritTimeline *timeline = new (__FILE__, __LINE__) InheritTimeline(frameCount, boneIndex); for (int frame = 0; frame < frameCount; frame++) { float time = input.readFloat(); Inherit inherit = (Inherit) input.readByte(); timeline->setFrame(frame, time, inherit); } timelines.add(timeline); continue; } int bezierCount = input.readInt(true); switch (timelineType) { case BONE_ROTATE: readTimeline(input, timelines, *(new (__FILE__, __LINE__) RotateTimeline(frameCount, bezierCount, boneIndex)), 1); break; case BONE_TRANSLATE: readTimeline(input, timelines, *(new (__FILE__, __LINE__) TranslateTimeline(frameCount, bezierCount, boneIndex)), scale); break; case BONE_TRANSLATEX: readTimeline(input, timelines, *(new (__FILE__, __LINE__) TranslateXTimeline(frameCount, bezierCount, boneIndex)), scale); break; case BONE_TRANSLATEY: readTimeline(input, timelines, *(new (__FILE__, __LINE__) TranslateYTimeline(frameCount, bezierCount, boneIndex)), scale); break; case BONE_SCALE: readTimeline(input, timelines, *(new (__FILE__, __LINE__) ScaleTimeline(frameCount, bezierCount, boneIndex)), 1); break; case BONE_SCALEX: readTimeline(input, timelines, *(new (__FILE__, __LINE__) ScaleXTimeline(frameCount, bezierCount, boneIndex)), 1); break; case BONE_SCALEY: readTimeline(input, timelines, *(new (__FILE__, __LINE__) ScaleYTimeline(frameCount, bezierCount, boneIndex)), 1); break; case BONE_SHEAR: readTimeline(input, timelines, *(new (__FILE__, __LINE__) ShearTimeline(frameCount, bezierCount, boneIndex)), 1); break; case BONE_SHEARX: readTimeline(input, timelines, *(new (__FILE__, __LINE__) ShearXTimeline(frameCount, bezierCount, boneIndex)), 1); break; case BONE_SHEARY: readTimeline(input, timelines, *(new (__FILE__, __LINE__) ShearYTimeline(frameCount, bezierCount, boneIndex)), 1); break; default: { ArrayUtils::deleteElements(timelines); setError("Invalid bone timeline type: ", String().append(timelineType).buffer()); return NULL; } } } } // IK constraint timelines. for (int i = 0, n = input.readInt(true); i < n; i++) { int index = input.readInt(true), frameCount = input.readInt(true), frameLast = frameCount - 1; IkConstraintTimeline *timeline = new (__FILE__, __LINE__) IkConstraintTimeline(frameCount, input.readInt(true), index); int flags = input.read(); float time = input.readFloat(), mix = (flags & 1) != 0 ? ((flags & 2) != 0 ? input.readFloat() : 1) : 0; float softness = (flags & 4) != 0 ? input.readFloat() * scale : 0; for (int frame = 0, bezier = 0;; frame++) { timeline->setFrame(frame, time, mix, softness, (flags & 8) != 0 ? 1 : -1, (flags & 16) != 0, (flags & 32) != 0); if (frame == frameLast) break; flags = input.read(); float time2 = input.readFloat(), mix2 = (flags & 1) != 0 ? ((flags & 2) != 0 ? input.readFloat() : 1) : 0; float softness2 = (flags & 4) != 0 ? input.readFloat() * scale : 0; if ((flags & 64) != 0) timeline->setStepped(frame); else if ((flags & 128) != 0) { setBezier(input, *timeline, bezier++, frame, 0, time, time2, mix, mix2, 1); setBezier(input, *timeline, bezier++, frame, 1, time, time2, softness, softness2, scale); } time = time2; mix = mix2; softness = softness2; } timelines.add(timeline); } // Transform constraint timelines. for (int i = 0, n = input.readInt(true); i < n; ++i) { int index = input.readInt(true), frameCount = input.readInt(true), frameLast = frameCount - 1; TransformConstraintTimeline *timeline = new (__FILE__, __LINE__) TransformConstraintTimeline(frameCount, input.readInt(true), index); float time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat(), mixScaleX = input.readFloat(), mixScaleY = input.readFloat(), mixShearY = input.readFloat(); for (int frame = 0, bezier = 0;; frame++) { timeline->setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); if (frame == frameLast) break; float time2 = input.readFloat(), mixRotate2 = input.readFloat(), mixX2 = input.readFloat(), mixY2 = input.readFloat(), mixScaleX2 = input.readFloat(), mixScaleY2 = input.readFloat(), mixShearY2 = input.readFloat(); int curveType = input.readByte(); switch (curveType) { case CURVE_STEPPED: timeline->setStepped(frame); break; case CURVE_BEZIER: setBezier(input, *timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1); setBezier(input, *timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1); setBezier(input, *timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1); setBezier(input, *timeline, bezier++, frame, 3, time, time2, mixScaleX, mixScaleX2, 1); setBezier(input, *timeline, bezier++, frame, 4, time, time2, mixScaleY, mixScaleY2, 1); setBezier(input, *timeline, bezier++, frame, 5, time, time2, mixShearY, mixShearY2, 1); break; } time = time2; mixRotate = mixRotate2; mixX = mixX2; mixY = mixY2; mixScaleX = mixScaleX2; mixScaleY = mixScaleY2; mixShearY = mixShearY2; } timelines.add(timeline); } // Path constraint timelines. for (int i = 0, n = input.readInt(true); i < n; ++i) { int index = input.readInt(true); PathConstraintData *data = static_cast(skeletonData._constraints[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) { case PATH_POSITION: { readTimeline(input, timelines, *(new (__FILE__, __LINE__) PathConstraintPositionTimeline(frameCount, bezierCount, index)), data->_positionMode == PositionMode_Fixed ? scale : 1); break; } case PATH_SPACING: { readTimeline(input, timelines, *(new (__FILE__, __LINE__) PathConstraintSpacingTimeline(frameCount, bezierCount, index)), data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed ? scale : 1); break; } case PATH_MIX: { PathConstraintMixTimeline *timeline = new (__FILE__, __LINE__) PathConstraintMixTimeline(frameCount, bezierCount, index); float time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat(); for (int frame = 0, bezier = 0, frameLast = (int) timeline->getFrameCount() - 1;; frame++) { timeline->setFrame(frame, time, mixRotate, mixX, mixY); if (frame == frameLast) break; float time2 = input.readFloat(), mixRotate2 = input.readFloat(), mixX2 = input.readFloat(), mixY2 = input.readFloat(); switch (input.readByte()) { case CURVE_STEPPED: timeline->setStepped(frame); break; case CURVE_BEZIER: setBezier(input, *timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1); setBezier(input, *timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1); setBezier(input, *timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1); break; } time = time2; mixRotate = mixRotate2; mixX = mixX2; mixY = mixY2; } timelines.add(timeline); break; } default: { ArrayUtils::deleteElements(timelines); setError("Invalid path constraint timeline type: ", String().append(type).buffer()); return NULL; } } } } // Physics timelines. for (int i = 0, n = input.readInt(true); i < n; i++) { int index = input.readInt(true) - 1; for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) { int type = input.readByte(), frameCount = input.readInt(true); if (type == PHYSICS_RESET) { PhysicsConstraintResetTimeline *timeline = new (__FILE__, __LINE__) PhysicsConstraintResetTimeline(frameCount, index); for (int frame = 0; frame < frameCount; frame++) timeline->setFrame(frame, input.readFloat()); timelines.add(timeline); continue; } int bezierCount = input.readInt(true); switch (type) { case PHYSICS_INERTIA: readTimeline(input, timelines, *(new (__FILE__, __LINE__) PhysicsConstraintInertiaTimeline(frameCount, bezierCount, index)), 1); break; case PHYSICS_STRENGTH: readTimeline(input, timelines, *(new (__FILE__, __LINE__) PhysicsConstraintStrengthTimeline(frameCount, bezierCount, index)), 1); break; case PHYSICS_DAMPING: readTimeline(input, timelines, *(new (__FILE__, __LINE__) PhysicsConstraintDampingTimeline(frameCount, bezierCount, index)), 1); break; case PHYSICS_MASS: readTimeline(input, timelines, *(new (__FILE__, __LINE__) PhysicsConstraintMassTimeline(frameCount, bezierCount, index)), 1); break; case PHYSICS_WIND: readTimeline(input, timelines, *(new (__FILE__, __LINE__) PhysicsConstraintWindTimeline(frameCount, bezierCount, index)), 1); break; case PHYSICS_GRAVITY: readTimeline(input, timelines, *(new (__FILE__, __LINE__) PhysicsConstraintGravityTimeline(frameCount, bezierCount, index)), 1); break; case PHYSICS_MIX: readTimeline(input, timelines, *(new (__FILE__, __LINE__) PhysicsConstraintMixTimeline(frameCount, bezierCount, index)), 1); break; default: { ArrayUtils::deleteElements(timelines); setError("Invalid physics constraint timeline type: ", String().append(type).buffer()); return NULL; } } } } // Slider timelines. for (int i = 0, n = input.readInt(true); i < n; i++) { int index = input.readInt(true); 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) { case SLIDER_TIME: readTimeline(input, timelines, *(new (__FILE__, __LINE__) SliderTimeline(frameCount, bezierCount, index)), 1); break; case SLIDER_MIX: readTimeline(input, timelines, *(new (__FILE__, __LINE__) SliderMixTimeline(frameCount, bezierCount, index)), 1); break; default: { ArrayUtils::deleteElements(timelines); setError("Invalid slider timeline type: ", String().append(type).buffer()); return NULL; } } } } // Attachment timelines. for (int i = 0, n = input.readInt(true); i < n; ++i) { Skin *skin = skeletonData._skins[input.readInt(true)]; for (int ii = 0, nn = input.readInt(true); ii < nn; ++ii) { int slotIndex = input.readInt(true); for (int iii = 0, nnn = input.readInt(true); iii < nnn; iii++) { const char *attachmentName = input.readStringRef(); Attachment *attachment = skin->getAttachment(slotIndex, String(attachmentName)); if (!attachment) { ArrayUtils::deleteElements(timelines); setError("Timeline attachment not found: ", attachmentName); return NULL; } int timelineType = input.readByte(), frameCount = input.readInt(true), frameLast = frameCount - 1; switch (timelineType) { case ATTACHMENT_DEFORM: { VertexAttachment *vertexAttachment = static_cast(attachment); bool weighted = vertexAttachment->_bones.size() > 0; Array &vertices = vertexAttachment->_vertices; int deformLength = weighted ? (int) vertices.size() / 3 * 2 : (int) vertices.size(); DeformTimeline *timeline = new (__FILE__, __LINE__) DeformTimeline(frameCount, input.readInt(true), slotIndex, *vertexAttachment); float time = input.readFloat(); for (int frame = 0, bezier = 0;; ++frame) { Array deform; size_t end = (size_t) input.readInt(true); if (end == 0) { if (weighted) { deform.setSize(deformLength, 0); for (int i = 0; i < deformLength; ++i) deform[i] = 0; } else { deform.clearAndAddAll(vertices); } } else { deform.setSize(deformLength, 0); size_t start = (size_t) input.readInt(true); end += start; if (scale == 1) { for (size_t v = start; v < end; ++v) deform[v] = input.readFloat(); } else { for (size_t v = start; v < end; ++v) deform[v] = input.readFloat() * scale; } if (!weighted) { for (size_t v = 0, vn = deform.size(); v < vn; ++v) deform[v] += vertices[v]; } } timeline->setFrame(frame, time, deform); if (frame == frameLast) break; float time2 = input.readFloat(); switch (input.readByte()) { case CURVE_STEPPED: timeline->setStepped(frame); break; case CURVE_BEZIER: setBezier(input, *timeline, bezier++, frame, 0, time, time2, 0, 1, 1); break; } time = time2; } timelines.add(timeline); break; } case ATTACHMENT_SEQUENCE: { SequenceTimeline *timeline = new (__FILE__, __LINE__) SequenceTimeline(frameCount, slotIndex, *attachment); for (int frame = 0; frame < frameCount; frame++) { float time = input.readFloat(); int modeAndIndex = input.readInt(); float delay = input.readFloat(); timeline->setFrame(frame, time, (SequenceMode) (modeAndIndex & 0xf), modeAndIndex >> 4, delay); } timelines.add(timeline); break; } default: { ArrayUtils::deleteElements(timelines); setError("Invalid attachment timeline type: ", String().append(timelineType).buffer()); return NULL; } } } } } // Draw order timeline. 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); } timelines.add(timeline); } // Event timeline. int eventCount = input.readInt(true); if (eventCount > 0) { EventTimeline *timeline = new (__FILE__, __LINE__) EventTimeline(eventCount); for (int i = 0; i < eventCount; ++i) { float time = input.readFloat(); EventData *eventData = skeletonData._events[input.readInt(true)]; Event *event = new (__FILE__, __LINE__) Event(time, *eventData); event->_intValue = input.readInt(false); event->_floatValue = input.readFloat(); const char *stringValue = input.readString(); if (stringValue == NULL) event->_stringValue = eventData->_stringValue; else event->_stringValue.own(stringValue); if (!eventData->_audioPath.isEmpty()) { event->_volume = input.readFloat(); event->_balance = input.readFloat(); } timeline->setFrame(i, *event); } timelines.add(timeline); } float duration = 0; for (int i = 0, n = (int) timelines.size(); i < n; i++) { duration = MathUtil::max(duration, (timelines[i])->getDuration()); } return new (__FILE__, __LINE__) Animation(String(name), timelines, duration); } void SkeletonBinary::readTimeline(DataInput &input, Array &timelines, CurveTimeline1 &timeline, float scale) { float time = input.readFloat(), value = input.readFloat() * scale; for (int frame = 0, bezier = 0, frameLast = (int) timeline.getFrameCount() - 1;; frame++) { timeline.setFrame(frame, time, value); if (frame == frameLast) break; float time2 = input.readFloat(), value2 = input.readFloat() * scale; switch (input.readByte()) { case CURVE_STEPPED: timeline.setStepped(frame); break; case CURVE_BEZIER: setBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale); break; } time = time2; value = value2; } timelines.add(&timeline); } void SkeletonBinary::readTimeline(DataInput &input, Array &timelines, BoneTimeline2 &timeline, float scale) { float time = input.readFloat(), value1 = input.readFloat() * scale, value2 = input.readFloat() * scale; for (int frame = 0, bezier = 0, frameLast = (int) timeline.getFrameCount() - 1;; frame++) { timeline.setFrame(frame, time, value1, value2); if (frame == frameLast) break; float time2 = input.readFloat(), nvalue1 = input.readFloat() * scale, nvalue2 = input.readFloat() * scale; switch (input.readByte()) { case CURVE_STEPPED: timeline.setStepped(frame); break; case CURVE_BEZIER: setBezier(input, timeline, bezier++, frame, 0, time, time2, value1, nvalue1, scale); setBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2, scale); break; } time = time2; value1 = nvalue1; value2 = nvalue2; } timelines.add(&timeline); } 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(); float cy1 = input.readFloat(); float cx2 = input.readFloat(); float cy2 = input.readFloat(); timeline.setBezier(bezier, frame, value, time1, value1, cx1, cy1 * scale, cx2, cy2 * scale, time2, value2); }