mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 01:36:02 +08:00
1419 lines
54 KiB
C++
1419 lines
54 KiB
C++
/******************************************************************************
|
|
* 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 <spine/SkeletonBinary.h>
|
|
|
|
#include <spine/Animation.h>
|
|
#include <spine/Atlas.h>
|
|
#include <spine/AtlasAttachmentLoader.h>
|
|
#include <spine/Attachment.h>
|
|
#include <spine/CurveTimeline.h>
|
|
#include <spine/LinkedMesh.h>
|
|
#include <spine/SkeletonData.h>
|
|
#include <spine/Skin.h>
|
|
#include <spine/VertexAttachment.h>
|
|
|
|
#include <spine/AttachmentTimeline.h>
|
|
#include <spine/AttachmentType.h>
|
|
#include <spine/BoneData.h>
|
|
#include <spine/BoundingBoxAttachment.h>
|
|
#include <spine/ClippingAttachment.h>
|
|
#include <spine/ColorTimeline.h>
|
|
#include <spine/ArrayUtils.h>
|
|
#include <spine/DeformTimeline.h>
|
|
#include <spine/DrawOrderTimeline.h>
|
|
#include <spine/Event.h>
|
|
#include <spine/EventData.h>
|
|
#include <spine/EventTimeline.h>
|
|
#include <spine/IkConstraintData.h>
|
|
#include <spine/IkConstraintTimeline.h>
|
|
#include <spine/InheritTimeline.h>
|
|
#include <spine/MeshAttachment.h>
|
|
#include <spine/PathAttachment.h>
|
|
#include <spine/PathConstraintData.h>
|
|
#include <spine/PathConstraintMixTimeline.h>
|
|
#include <spine/PathConstraintPositionTimeline.h>
|
|
#include <spine/PathConstraintSpacingTimeline.h>
|
|
#include <spine/PhysicsConstraintData.h>
|
|
#include <spine/PhysicsConstraintTimeline.h>
|
|
#include <spine/PointAttachment.h>
|
|
#include <spine/RegionAttachment.h>
|
|
#include <spine/RotateTimeline.h>
|
|
#include <spine/ScaleTimeline.h>
|
|
#include <spine/ShearTimeline.h>
|
|
#include <spine/SlotData.h>
|
|
#include <spine/TransformConstraintData.h>
|
|
#include <spine/TransformConstraintTimeline.h>
|
|
#include <spine/TranslateTimeline.h>
|
|
#include <spine/SequenceTimeline.h>
|
|
#include <spine/SliderData.h>
|
|
#include <spine/SliderTimeline.h>
|
|
#include <spine/SliderMixTimeline.h>
|
|
#include <spine/Version.h>
|
|
|
|
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<char *> &strings = skeletonData->_strings.setSize(n, NULL);
|
|
for (int i = 0; i < n; i++) strings[i] = input.readString();
|
|
|
|
/* Bones. */
|
|
Array<BoneData *> &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<Inherit>(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<SlotData *> &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<BlendMode>(input.readInt(true));
|
|
if (nonessential) data->_visible = input.readBoolean();
|
|
slots[i] = data;
|
|
}
|
|
|
|
/* Constraints. */
|
|
int constraintCount = input.readInt(true);
|
|
Array<ConstraintData *> &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<BoneData *> &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<BoneData *> &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<FromProperty *> &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<ToProperty *> &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<BoneData *> &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<Skin *> &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<LinkedMesh *> &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<VertexAttachment *>(parent) : linkedMesh->_mesh;
|
|
linkedMesh->_mesh->setParentMesh(static_cast<MeshAttachment *>(parent));
|
|
if (linkedMesh->_mesh->getSequence() == NULL) linkedMesh->_mesh->updateRegion();
|
|
}
|
|
ArrayUtils::deleteElements(_linkedMeshes);
|
|
_linkedMeshes.clear();
|
|
|
|
/* Events. */
|
|
int eventsCount = input.readInt(true);
|
|
Array<EventData *> &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<Animation *> &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<SliderData *>(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<BoneData *> &from = skeletonData._bones;
|
|
Array<BoneData *> &bones = skin->getBones().setSize(n = input.readInt(true), NULL);
|
|
for (int i = 0; i < n; i++) bones[i] = from[input.readInt(true)];
|
|
|
|
Array<ConstraintData *> &fromConstraints = skeletonData._constraints;
|
|
Array<ConstraintData *> &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<AttachmentType>(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<float> vertices;
|
|
Array<int> 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<float> vertices;
|
|
Array<int> bones;
|
|
int verticesLength = readVertices(input, vertices, bones, (flags & 128) != 0);
|
|
Array<float> uvs;
|
|
readFloatArray(input, verticesLength, 1, uvs);
|
|
Array<unsigned short> triangles;
|
|
readUnsignedShortArray(input, triangles, (verticesLength - hullLength - 2) * 3);
|
|
|
|
Array<unsigned short> 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<float> vertices;
|
|
Array<int> bones;
|
|
int verticesLength = readVertices(input, vertices, bones, (flags & 64) != 0);
|
|
Array<float> 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<float> vertices;
|
|
Array<int> 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<float> &vertices, Array<int> &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<float> &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<unsigned short> &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<Timeline *> 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<PathConstraintData *>(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<VertexAttachment *>(attachment);
|
|
bool weighted = vertexAttachment->_bones.size() > 0;
|
|
Array<float> &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<float> 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<int> drawOrder;
|
|
drawOrder.setSize(slotCount, 0);
|
|
for (int ii = (int) slotCount - 1; ii >= 0; --ii) drawOrder[ii] = -1;
|
|
Array<int> 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<Timeline *> &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<Timeline *> &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);
|
|
} |