mirror of
https://github.com/smallmain/cocos-enhance-kit.git
synced 2025-01-14 06:51:08 +00:00
1039 lines
37 KiB
C++
1039 lines
37 KiB
C++
/******************************************************************************
|
|
* Spine Runtimes License Agreement
|
|
* Last updated January 1, 2020. Replaces all prior versions.
|
|
*
|
|
* Copyright (c) 2013-2020, 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.
|
|
*****************************************************************************/
|
|
|
|
#ifdef SPINE_UE4
|
|
#include "SpinePluginPrivatePCH.h"
|
|
#endif
|
|
|
|
#include <spine/SkeletonBinary.h>
|
|
|
|
#include <spine/SkeletonData.h>
|
|
#include <spine/Atlas.h>
|
|
#include <spine/AtlasAttachmentLoader.h>
|
|
#include <spine/LinkedMesh.h>
|
|
#include <spine/Skin.h>
|
|
#include <spine/Attachment.h>
|
|
#include <spine/VertexAttachment.h>
|
|
#include <spine/Animation.h>
|
|
#include <spine/CurveTimeline.h>
|
|
|
|
#include <spine/ContainerUtil.h>
|
|
#include <spine/BoneData.h>
|
|
#include <spine/SlotData.h>
|
|
#include <spine/IkConstraintData.h>
|
|
#include <spine/TransformConstraintData.h>
|
|
#include <spine/PathConstraintData.h>
|
|
#include <spine/AttachmentType.h>
|
|
#include <spine/RegionAttachment.h>
|
|
#include <spine/BoundingBoxAttachment.h>
|
|
#include <spine/MeshAttachment.h>
|
|
#include <spine/PathAttachment.h>
|
|
#include <spine/PointAttachment.h>
|
|
#include <spine/ClippingAttachment.h>
|
|
#include <spine/EventData.h>
|
|
#include <spine/AttachmentTimeline.h>
|
|
#include <spine/ColorTimeline.h>
|
|
#include <spine/TwoColorTimeline.h>
|
|
#include <spine/RotateTimeline.h>
|
|
#include <spine/TranslateTimeline.h>
|
|
#include <spine/ScaleTimeline.h>
|
|
#include <spine/ShearTimeline.h>
|
|
#include <spine/IkConstraintTimeline.h>
|
|
#include <spine/TransformConstraintTimeline.h>
|
|
#include <spine/PathConstraintPositionTimeline.h>
|
|
#include <spine/PathConstraintSpacingTimeline.h>
|
|
#include <spine/PathConstraintMixTimeline.h>
|
|
#include <spine/DeformTimeline.h>
|
|
#include <spine/DrawOrderTimeline.h>
|
|
#include <spine/EventTimeline.h>
|
|
#include <spine/Event.h>
|
|
|
|
using namespace spine;
|
|
|
|
const int SkeletonBinary::BONE_ROTATE = 0;
|
|
const int SkeletonBinary::BONE_TRANSLATE = 1;
|
|
const int SkeletonBinary::BONE_SCALE = 2;
|
|
const int SkeletonBinary::BONE_SHEAR = 3;
|
|
|
|
const int SkeletonBinary::SLOT_ATTACHMENT = 0;
|
|
const int SkeletonBinary::SLOT_COLOR = 1;
|
|
const int SkeletonBinary::SLOT_TWO_COLOR = 2;
|
|
|
|
const int SkeletonBinary::PATH_POSITION = 0;
|
|
const int SkeletonBinary::PATH_SPACING = 1;
|
|
const int SkeletonBinary::PATH_MIX = 2;
|
|
|
|
const int SkeletonBinary::CURVE_LINEAR = 0;
|
|
const int SkeletonBinary::CURVE_STEPPED = 1;
|
|
const int SkeletonBinary::CURVE_BEZIER = 2;
|
|
|
|
SkeletonBinary::SkeletonBinary(Atlas *atlasArray) : _attachmentLoader(
|
|
new(__FILE__, __LINE__) AtlasAttachmentLoader(atlasArray)), _error(), _scale(1), _ownsLoader(true) {
|
|
|
|
}
|
|
|
|
SkeletonBinary::SkeletonBinary(AttachmentLoader *attachmentLoader) : _attachmentLoader(attachmentLoader), _error(),
|
|
_scale(1), _ownsLoader(false)
|
|
{
|
|
assert(_attachmentLoader != NULL);
|
|
}
|
|
|
|
SkeletonBinary::~SkeletonBinary() {
|
|
ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes);
|
|
_linkedMeshes.clear();
|
|
|
|
if (_ownsLoader) delete _attachmentLoader;
|
|
}
|
|
|
|
SkeletonData *SkeletonBinary::readSkeletonData(const unsigned char *binary, const int length) {
|
|
bool nonessential;
|
|
SkeletonData *skeletonData;
|
|
|
|
DataInput *input = new(__FILE__, __LINE__) DataInput();
|
|
input->cursor = binary;
|
|
input->end = binary + length;
|
|
|
|
_linkedMeshes.clear();
|
|
|
|
skeletonData = new(__FILE__, __LINE__) SkeletonData();
|
|
|
|
char *skeletonData_hash = readString(input);
|
|
skeletonData->_hash.own(skeletonData_hash);
|
|
|
|
char *skeletonData_version = readString(input);
|
|
skeletonData->_version.own(skeletonData_version);
|
|
if ("3.8.75" == skeletonData->_version) {
|
|
delete input;
|
|
delete skeletonData;
|
|
setError("Unsupported skeleton data, please export with a newer version of Spine.", "");
|
|
return NULL;
|
|
}
|
|
|
|
skeletonData->_x = readFloat(input);
|
|
skeletonData->_y = readFloat(input);
|
|
skeletonData->_width = readFloat(input);
|
|
skeletonData->_height = readFloat(input);
|
|
|
|
nonessential = readBoolean(input);
|
|
|
|
if (nonessential) {
|
|
/* Skip images path, audio path & fps */
|
|
skeletonData->_fps = readFloat(input);
|
|
skeletonData->_imagesPath.own(readString(input));
|
|
skeletonData->_audioPath.own(readString(input));
|
|
}
|
|
|
|
int numStrings = readVarint(input, true);
|
|
for (int i = 0; i < numStrings; i++)
|
|
skeletonData->_strings.add(readString(input));
|
|
|
|
/* Bones. */
|
|
int numBones = readVarint(input, true);
|
|
skeletonData->_bones.setSize(numBones, 0);
|
|
for (int i = 0; i < numBones; ++i) {
|
|
const char *name = readString(input);
|
|
BoneData *parent = i == 0 ? 0 : skeletonData->_bones[readVarint(input, true)];
|
|
BoneData *data = new(__FILE__, __LINE__) BoneData(i, String(name, true), parent);
|
|
data->_rotation = readFloat(input);
|
|
data->_x = readFloat(input) * _scale;
|
|
data->_y = readFloat(input) * _scale;
|
|
data->_scaleX = readFloat(input);
|
|
data->_scaleY = readFloat(input);
|
|
data->_shearX = readFloat(input);
|
|
data->_shearY = readFloat(input);
|
|
data->_length = readFloat(input) * _scale;
|
|
data->_transformMode = static_cast<TransformMode>(readVarint(input, true));
|
|
data->_skinRequired = readBoolean(input);
|
|
if (nonessential) readInt(input); /* Skip bone color. */
|
|
skeletonData->_bones[i] = data;
|
|
}
|
|
|
|
/* Slots. */
|
|
int slotsCount = readVarint(input, true);
|
|
skeletonData->_slots.setSize(slotsCount, 0);
|
|
for (int i = 0; i < slotsCount; ++i) {
|
|
const char *slotName = readString(input);
|
|
BoneData *boneData = skeletonData->_bones[readVarint(input, true)];
|
|
SlotData *slotData = new(__FILE__, __LINE__) SlotData(i, String(slotName, true), *boneData);
|
|
|
|
readColor(input, slotData->getColor());
|
|
unsigned char r = readByte(input);
|
|
unsigned char g = readByte(input);
|
|
unsigned char b = readByte(input);
|
|
unsigned char a = readByte(input);
|
|
if (!(r == 0xff && g == 0xff && b == 0xff && a == 0xff)) {
|
|
slotData->getDarkColor().set(r / 255.0f, g / 255.0f, b / 255.0f, 1);
|
|
slotData->setHasDarkColor(true);
|
|
}
|
|
slotData->_attachmentName = readStringRef(input, skeletonData);
|
|
slotData->_blendMode = static_cast<BlendMode>(readVarint(input, true));
|
|
skeletonData->_slots[i] = slotData;
|
|
}
|
|
|
|
/* IK constraints. */
|
|
int ikConstraintsCount = readVarint(input, true);
|
|
skeletonData->_ikConstraints.setSize(ikConstraintsCount, 0);
|
|
for (int i = 0; i < ikConstraintsCount; ++i) {
|
|
const char *name = readString(input);
|
|
IkConstraintData *data = new(__FILE__, __LINE__) IkConstraintData(String(name, true));
|
|
data->setOrder(readVarint(input, true));
|
|
data->setSkinRequired(readBoolean(input));
|
|
int bonesCount = readVarint(input, true);
|
|
data->_bones.setSize(bonesCount, 0);
|
|
for (int ii = 0; ii < bonesCount; ++ii)
|
|
data->_bones[ii] = skeletonData->_bones[readVarint(input, true)];
|
|
data->_target = skeletonData->_bones[readVarint(input, true)];
|
|
data->_mix = readFloat(input);
|
|
data->_softness = readFloat(input) * _scale;
|
|
data->_bendDirection = readSByte(input);
|
|
data->_compress = readBoolean(input);
|
|
data->_stretch = readBoolean(input);
|
|
data->_uniform = readBoolean(input);
|
|
skeletonData->_ikConstraints[i] = data;
|
|
}
|
|
|
|
/* Transform constraints. */
|
|
int transformConstraintsCount = readVarint(input, true);
|
|
skeletonData->_transformConstraints.setSize(transformConstraintsCount, 0);
|
|
for (int i = 0; i < transformConstraintsCount; ++i) {
|
|
const char *name = readString(input);
|
|
TransformConstraintData *data = new(__FILE__, __LINE__) TransformConstraintData(String(name, true));
|
|
data->setOrder(readVarint(input, true));
|
|
data->setSkinRequired(readBoolean(input));
|
|
int bonesCount = readVarint(input, true);
|
|
data->_bones.setSize(bonesCount, 0);
|
|
for (int ii = 0; ii < bonesCount; ++ii)
|
|
data->_bones[ii] = skeletonData->_bones[readVarint(input, true)];
|
|
data->_target = skeletonData->_bones[readVarint(input, true)];
|
|
data->_local = readBoolean(input);
|
|
data->_relative = readBoolean(input);
|
|
data->_offsetRotation = readFloat(input);
|
|
data->_offsetX = readFloat(input) * _scale;
|
|
data->_offsetY = readFloat(input) * _scale;
|
|
data->_offsetScaleX = readFloat(input);
|
|
data->_offsetScaleY = readFloat(input);
|
|
data->_offsetShearY = readFloat(input);
|
|
data->_rotateMix = readFloat(input);
|
|
data->_translateMix = readFloat(input);
|
|
data->_scaleMix = readFloat(input);
|
|
data->_shearMix = readFloat(input);
|
|
skeletonData->_transformConstraints[i] = data;
|
|
}
|
|
|
|
/* Path constraints */
|
|
int pathConstraintsCount = readVarint(input, true);
|
|
skeletonData->_pathConstraints.setSize(pathConstraintsCount, 0);
|
|
for (int i = 0; i < pathConstraintsCount; ++i) {
|
|
const char *name = readString(input);
|
|
PathConstraintData *data = new(__FILE__, __LINE__) PathConstraintData(String(name, true));
|
|
data->setOrder(readVarint(input, true));
|
|
data->setSkinRequired(readBoolean(input));
|
|
int bonesCount = readVarint(input, true);
|
|
data->_bones.setSize(bonesCount, 0);
|
|
for (int ii = 0; ii < bonesCount; ++ii)
|
|
data->_bones[ii] = skeletonData->_bones[readVarint(input, true)];
|
|
data->_target = skeletonData->_slots[readVarint(input, true)];
|
|
data->_positionMode = static_cast<PositionMode>(readVarint(input, true));
|
|
data->_spacingMode = static_cast<SpacingMode>(readVarint(input, true));
|
|
data->_rotateMode = static_cast<RotateMode>(readVarint(input, true));
|
|
data->_offsetRotation = readFloat(input);
|
|
data->_position = readFloat(input);
|
|
if (data->_positionMode == PositionMode_Fixed) data->_position *= _scale;
|
|
data->_spacing = readFloat(input);
|
|
if (data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed)
|
|
data->_spacing *= _scale;
|
|
data->_rotateMix = readFloat(input);
|
|
data->_translateMix = readFloat(input);
|
|
skeletonData->_pathConstraints[i] = data;
|
|
}
|
|
|
|
/* Default skin. */
|
|
Skin* defaultSkin = readSkin(input, true, skeletonData, nonessential);
|
|
if (defaultSkin) {
|
|
skeletonData->_defaultSkin = defaultSkin;
|
|
skeletonData->_skins.add(defaultSkin);
|
|
}
|
|
|
|
/* Skins. */
|
|
for (size_t i = 0, n = (size_t)readVarint(input, true); i < n; ++i)
|
|
skeletonData->_skins.add(readSkin(input, false, skeletonData, nonessential));
|
|
|
|
/* Linked meshes. */
|
|
for (int i = 0, n = _linkedMeshes.size(); i < n; ++i) {
|
|
LinkedMesh *linkedMesh = _linkedMeshes[i];
|
|
Skin *skin = linkedMesh->_skin.length() == 0 ? skeletonData->getDefaultSkin() : skeletonData->findSkin(
|
|
linkedMesh->_skin);
|
|
if (skin == NULL) {
|
|
delete input;
|
|
delete skeletonData;
|
|
setError("Skin not found: ", linkedMesh->_skin.buffer());
|
|
return NULL;
|
|
}
|
|
Attachment *parent = skin->getAttachment(linkedMesh->_slotIndex, linkedMesh->_parent);
|
|
if (parent == NULL) {
|
|
delete input;
|
|
delete skeletonData;
|
|
setError("Parent mesh not found: ", linkedMesh->_parent.buffer());
|
|
return NULL;
|
|
}
|
|
linkedMesh->_mesh->_deformAttachment = linkedMesh->_inheritDeform ? static_cast<VertexAttachment*>(parent) : linkedMesh->_mesh;
|
|
linkedMesh->_mesh->setParentMesh(static_cast<MeshAttachment *>(parent));
|
|
linkedMesh->_mesh->updateUVs();
|
|
_attachmentLoader->configureAttachment(linkedMesh->_mesh);
|
|
}
|
|
ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes);
|
|
_linkedMeshes.clear();
|
|
|
|
/* Events. */
|
|
int eventsCount = readVarint(input, true);
|
|
skeletonData->_events.setSize(eventsCount, 0);
|
|
for (int i = 0; i < eventsCount; ++i) {
|
|
const char *name = readStringRef(input, skeletonData);
|
|
EventData *eventData = new(__FILE__, __LINE__) EventData(String(name));
|
|
eventData->_intValue = readVarint(input, false);
|
|
eventData->_floatValue = readFloat(input);
|
|
eventData->_stringValue.own(readString(input));
|
|
eventData->_audioPath.own(readString(input)); // skip audio path
|
|
if (!eventData->_audioPath.isEmpty()) {
|
|
eventData->_volume = readFloat(input);
|
|
eventData->_balance = readFloat(input);
|
|
}
|
|
skeletonData->_events[i] = eventData;
|
|
}
|
|
|
|
/* Animations. */
|
|
int animationsCount = readVarint(input, true);
|
|
skeletonData->_animations.setSize(animationsCount, 0);
|
|
for (int i = 0; i < animationsCount; ++i) {
|
|
String name(readString(input), true);
|
|
Animation *animation = readAnimation(name, input, skeletonData);
|
|
if (!animation) {
|
|
delete input;
|
|
delete skeletonData;
|
|
return NULL;
|
|
}
|
|
skeletonData->_animations[i] = animation;
|
|
}
|
|
|
|
delete input;
|
|
return skeletonData;
|
|
}
|
|
|
|
SkeletonData *SkeletonBinary::readSkeletonDataFile(const String &path) {
|
|
int length;
|
|
SkeletonData *skeletonData;
|
|
const char *binary = SpineExtension::readFile(path.buffer(), &length);
|
|
if (length == 0 || !binary) {
|
|
setError("Unable to read skeleton file: ", path.buffer());
|
|
return NULL;
|
|
}
|
|
skeletonData = readSkeletonData((unsigned char *) binary, length);
|
|
SpineExtension::free(binary, __FILE__, __LINE__);
|
|
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);
|
|
}
|
|
|
|
char *SkeletonBinary::readString(DataInput *input) {
|
|
int length = readVarint(input, true);
|
|
char *string;
|
|
if (length == 0) return NULL;
|
|
string = SpineExtension::alloc<char>(length, __FILE__, __LINE__);
|
|
memcpy(string, input->cursor, length - 1);
|
|
input->cursor += length - 1;
|
|
string[length - 1] = '\0';
|
|
return string;
|
|
}
|
|
|
|
char* SkeletonBinary::readStringRef(DataInput* input, SkeletonData* skeletonData) {
|
|
int index = readVarint(input, true);
|
|
return index == 0 ? nullptr : skeletonData->_strings[index - 1];
|
|
}
|
|
|
|
float SkeletonBinary::readFloat(DataInput *input) {
|
|
union {
|
|
int intValue;
|
|
float floatValue;
|
|
} intToFloat;
|
|
intToFloat.intValue = readInt(input);
|
|
return intToFloat.floatValue;
|
|
}
|
|
|
|
unsigned char SkeletonBinary::readByte(DataInput *input) {
|
|
return *input->cursor++;
|
|
}
|
|
|
|
signed char SkeletonBinary::readSByte(DataInput *input) {
|
|
return (signed char) readByte(input);
|
|
}
|
|
|
|
bool SkeletonBinary::readBoolean(DataInput *input) {
|
|
return readByte(input) != 0;
|
|
}
|
|
|
|
int SkeletonBinary::readInt(DataInput *input) {
|
|
int result = readByte(input);
|
|
result <<= 8;
|
|
result |= readByte(input);
|
|
result <<= 8;
|
|
result |= readByte(input);
|
|
result <<= 8;
|
|
result |= readByte(input);
|
|
return result;
|
|
}
|
|
|
|
void SkeletonBinary::readColor(DataInput *input, Color &color) {
|
|
color.r = readByte(input) / 255.0f;
|
|
color.g = readByte(input) / 255.0f;
|
|
color.b = readByte(input) / 255.0f;
|
|
color.a = readByte(input) / 255.0f;
|
|
}
|
|
|
|
int SkeletonBinary::readVarint(DataInput *input, bool optimizePositive) {
|
|
unsigned char b = readByte(input);
|
|
int value = b & 0x7F;
|
|
if (b & 0x80) {
|
|
b = readByte(input);
|
|
value |= (b & 0x7F) << 7;
|
|
if (b & 0x80) {
|
|
b = readByte(input);
|
|
value |= (b & 0x7F) << 14;
|
|
if (b & 0x80) {
|
|
b = readByte(input);
|
|
value |= (b & 0x7F) << 21;
|
|
if (b & 0x80) value |= (readByte(input) & 0x7F) << 28;
|
|
}
|
|
}
|
|
}
|
|
if (!optimizePositive) value = (((unsigned int) value >> 1) ^ -(value & 1));
|
|
return value;
|
|
}
|
|
|
|
Skin *SkeletonBinary::readSkin(DataInput *input, bool defaultSkin, SkeletonData *skeletonData, bool nonessential) {
|
|
Skin *skin;
|
|
int slotCount = 0;
|
|
if (defaultSkin) {
|
|
slotCount = readVarint(input, true);
|
|
if (slotCount == 0) return NULL;
|
|
skin = new(__FILE__, __LINE__) Skin("default");
|
|
} else {
|
|
skin = new(__FILE__, __LINE__) Skin(readStringRef(input, skeletonData));
|
|
for (int i = 0, n = readVarint(input, true); i < n; i++)
|
|
skin->getBones().add(skeletonData->_bones[readVarint(input, true)]);
|
|
|
|
for (int i = 0, n = readVarint(input, true); i < n; i++)
|
|
skin->getConstraints().add(skeletonData->_ikConstraints[readVarint(input, true)]);
|
|
|
|
for (int i = 0, n = readVarint(input, true); i < n; i++)
|
|
skin->getConstraints().add(skeletonData->_transformConstraints[readVarint(input, true)]);
|
|
|
|
for (int i = 0, n = readVarint(input, true); i < n; i++)
|
|
skin->getConstraints().add(skeletonData->_pathConstraints[readVarint(input, true)]);
|
|
slotCount = readVarint(input, true);
|
|
}
|
|
|
|
for (int i = 0; i < slotCount; ++i) {
|
|
int slotIndex = readVarint(input, true);
|
|
for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) {
|
|
String name(readStringRef(input, skeletonData));
|
|
Attachment *attachment = readAttachment(input, skin, slotIndex, name, skeletonData, nonessential);
|
|
if (attachment) skin->setAttachment(slotIndex, String(name), attachment);
|
|
}
|
|
}
|
|
return skin;
|
|
}
|
|
|
|
Attachment *SkeletonBinary::readAttachment(DataInput *input, Skin *skin, int slotIndex, const String &attachmentName,
|
|
SkeletonData *skeletonData, bool nonessential
|
|
) {
|
|
String name(readStringRef(input, skeletonData));
|
|
if (name.isEmpty()) name = attachmentName;
|
|
|
|
AttachmentType type = static_cast<AttachmentType>(readByte(input));
|
|
switch (type) {
|
|
case AttachmentType_Region: {
|
|
String path(readStringRef(input, skeletonData));
|
|
if (path.isEmpty()) path = name;
|
|
RegionAttachment *region = _attachmentLoader->newRegionAttachment(*skin, String(name), String(path));
|
|
if (region == NULL){
|
|
return NULL;
|
|
}
|
|
region->_path = path;
|
|
region->_rotation = readFloat(input);
|
|
region->_x = readFloat(input) * _scale;
|
|
region->_y = readFloat(input) * _scale;
|
|
region->_scaleX = readFloat(input);
|
|
region->_scaleY = readFloat(input);
|
|
region->_width = readFloat(input) * _scale;
|
|
region->_height = readFloat(input) * _scale;
|
|
readColor(input, region->getColor());
|
|
region->updateOffset();
|
|
_attachmentLoader->configureAttachment(region);
|
|
return region;
|
|
}
|
|
case AttachmentType_Boundingbox: {
|
|
int vertexCount = readVarint(input, true);
|
|
BoundingBoxAttachment *box = _attachmentLoader->newBoundingBoxAttachment(*skin, String(name));
|
|
readVertices(input, static_cast<VertexAttachment *>(box), vertexCount);
|
|
if (nonessential) {
|
|
/* Skip color. */
|
|
readInt(input);
|
|
}
|
|
_attachmentLoader->configureAttachment(box);
|
|
return box;
|
|
}
|
|
case AttachmentType_Mesh: {
|
|
int vertexCount;
|
|
MeshAttachment *mesh;
|
|
String path(readStringRef(input, skeletonData));
|
|
if (path.isEmpty()) path = name;
|
|
|
|
mesh = _attachmentLoader->newMeshAttachment(*skin, String(name), String(path));
|
|
mesh->_path = path;
|
|
readColor(input, mesh->getColor());
|
|
vertexCount = readVarint(input, true);
|
|
readFloatArray(input, vertexCount << 1, 1, mesh->getRegionUVs());
|
|
readShortArray(input, mesh->getTriangles());
|
|
readVertices(input, static_cast<VertexAttachment *>(mesh), vertexCount);
|
|
mesh->updateUVs();
|
|
mesh->_hullLength = readVarint(input, true) << 1;
|
|
if (nonessential) {
|
|
readShortArray(input, mesh->getEdges());
|
|
mesh->_width = readFloat(input) * _scale;
|
|
mesh->_height = readFloat(input) * _scale;
|
|
} else {
|
|
mesh->_width = 0;
|
|
mesh->_height = 0;
|
|
}
|
|
_attachmentLoader->configureAttachment(mesh);
|
|
return mesh;
|
|
}
|
|
case AttachmentType_Linkedmesh: {
|
|
String path(readStringRef(input, skeletonData));
|
|
if (path.isEmpty()) path = name;
|
|
|
|
MeshAttachment *mesh = _attachmentLoader->newMeshAttachment(*skin, String(name), String(path));
|
|
mesh->_path = path;
|
|
readColor(input, mesh->getColor());
|
|
String skinName(readStringRef(input, skeletonData));
|
|
String parent(readStringRef(input, skeletonData));
|
|
bool inheritDeform = readBoolean(input);
|
|
if (nonessential) {
|
|
mesh->_width = readFloat(input) * _scale;
|
|
mesh->_height = readFloat(input) * _scale;
|
|
}
|
|
|
|
LinkedMesh *linkedMesh = new(__FILE__, __LINE__) LinkedMesh(mesh, String(skinName), slotIndex,
|
|
String(parent), inheritDeform);
|
|
_linkedMeshes.add(linkedMesh);
|
|
return mesh;
|
|
}
|
|
case AttachmentType_Path: {
|
|
PathAttachment *path = _attachmentLoader->newPathAttachment(*skin, String(name));
|
|
path->_closed = readBoolean(input);
|
|
path->_constantSpeed = readBoolean(input);
|
|
int vertexCount = readVarint(input, true);
|
|
readVertices(input, static_cast<VertexAttachment *>(path), vertexCount);
|
|
int lengthsLength = vertexCount / 3;
|
|
path->_lengths.setSize(lengthsLength, 0);
|
|
for (int i = 0; i < lengthsLength; ++i) {
|
|
path->_lengths[i] = readFloat(input) * _scale;
|
|
}
|
|
if (nonessential) {
|
|
/* Skip color. */
|
|
readInt(input);
|
|
}
|
|
_attachmentLoader->configureAttachment(path);
|
|
return path;
|
|
}
|
|
case AttachmentType_Point: {
|
|
PointAttachment *point = _attachmentLoader->newPointAttachment(*skin, String(name));
|
|
point->_rotation = readFloat(input);
|
|
point->_x = readFloat(input) * _scale;
|
|
point->_y = readFloat(input) * _scale;
|
|
|
|
if (nonessential) {
|
|
/* Skip color. */
|
|
readInt(input);
|
|
}
|
|
_attachmentLoader->configureAttachment(point);
|
|
return point;
|
|
}
|
|
case AttachmentType_Clipping: {
|
|
int endSlotIndex = readVarint(input, true);
|
|
int vertexCount = readVarint(input, true);
|
|
ClippingAttachment *clip = _attachmentLoader->newClippingAttachment(*skin, name);
|
|
readVertices(input, static_cast<VertexAttachment *>(clip), vertexCount);
|
|
clip->_endSlot = skeletonData->_slots[endSlotIndex];
|
|
if (nonessential) {
|
|
/* Skip color. */
|
|
readInt(input);
|
|
}
|
|
_attachmentLoader->configureAttachment(clip);
|
|
return clip;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void SkeletonBinary::readVertices(DataInput *input, VertexAttachment *attachment, int vertexCount) {
|
|
float scale = _scale;
|
|
int verticesLength = vertexCount << 1;
|
|
attachment->setWorldVerticesLength(vertexCount << 1);
|
|
|
|
if (!readBoolean(input)) {
|
|
readFloatArray(input, verticesLength, scale, attachment->getVertices());
|
|
return;
|
|
}
|
|
|
|
Vector<float> &vertices = attachment->getVertices();
|
|
Vector<size_t> &bones = attachment->getBones();
|
|
vertices.ensureCapacity(verticesLength * 3 * 3);
|
|
bones.ensureCapacity(verticesLength * 3);
|
|
|
|
for (int i = 0; i < vertexCount; ++i) {
|
|
int boneCount = readVarint(input, true);
|
|
bones.add(boneCount);
|
|
for (int ii = 0; ii < boneCount; ++ii) {
|
|
bones.add(readVarint(input, true));
|
|
vertices.add(readFloat(input) * scale);
|
|
vertices.add(readFloat(input) * scale);
|
|
vertices.add(readFloat(input));
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkeletonBinary::readFloatArray(DataInput *input, int n, float scale, Vector<float> &array) {
|
|
array.setSize(n, 0);
|
|
|
|
int i;
|
|
if (scale == 1) {
|
|
for (i = 0; i < n; ++i) {
|
|
array[i] = readFloat(input);
|
|
}
|
|
} else {
|
|
for (i = 0; i < n; ++i) {
|
|
array[i] = readFloat(input) * scale;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkeletonBinary::readShortArray(DataInput *input, Vector<unsigned short> &array) {
|
|
int n = readVarint(input, true);
|
|
array.setSize(n, 0);
|
|
|
|
int i;
|
|
for (i = 0; i < n; ++i) {
|
|
array[i] = readByte(input) << 8;
|
|
array[i] |= readByte(input);
|
|
}
|
|
}
|
|
|
|
Animation *SkeletonBinary::readAnimation(const String &name, DataInput *input, SkeletonData *skeletonData) {
|
|
Vector<Timeline *> timelines;
|
|
float scale = _scale;
|
|
float duration = 0;
|
|
|
|
// Slot timelines.
|
|
for (int i = 0, n = readVarint(input, true); i < n; ++i) {
|
|
int slotIndex = readVarint(input, true);
|
|
for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) {
|
|
unsigned char timelineType = readByte(input);
|
|
int frameCount = readVarint(input, true);
|
|
switch (timelineType) {
|
|
case SLOT_ATTACHMENT: {
|
|
AttachmentTimeline *timeline = new(__FILE__, __LINE__) AttachmentTimeline(frameCount);
|
|
timeline->_slotIndex = slotIndex;
|
|
for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
|
|
float time = readFloat(input);
|
|
String attachmentName(readStringRef(input, skeletonData));
|
|
timeline->setFrame(frameIndex, time, attachmentName);
|
|
}
|
|
timelines.add(timeline);
|
|
duration = MathUtil::max(duration, timeline->_frames[frameCount - 1]);
|
|
break;
|
|
}
|
|
case SLOT_COLOR: {
|
|
ColorTimeline *timeline = new(__FILE__, __LINE__) ColorTimeline(frameCount);
|
|
timeline->_slotIndex = slotIndex;
|
|
for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
|
|
float time = readFloat(input);
|
|
int color = readInt(input);
|
|
float r = ((color & 0xff000000) >> 24) / 255.0f;
|
|
float g = ((color & 0x00ff0000) >> 16) / 255.0f;
|
|
float b = ((color & 0x0000ff00) >> 8) / 255.0f;
|
|
float a = ((color & 0x000000ff)) / 255.0f;
|
|
timeline->setFrame(frameIndex, time, r, g, b, a);
|
|
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
|
|
}
|
|
timelines.add(timeline);
|
|
duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * ColorTimeline::ENTRIES]);
|
|
break;
|
|
}
|
|
case SLOT_TWO_COLOR: {
|
|
TwoColorTimeline *timeline = new(__FILE__, __LINE__) TwoColorTimeline(frameCount);
|
|
timeline->_slotIndex = slotIndex;
|
|
for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
|
|
float time = readFloat(input);
|
|
int color = readInt(input);
|
|
float r = ((color & 0xff000000) >> 24) / 255.0f;
|
|
float g = ((color & 0x00ff0000) >> 16) / 255.0f;
|
|
float b = ((color & 0x0000ff00) >> 8) / 255.0f;
|
|
float a = ((color & 0x000000ff)) / 255.0f;
|
|
int color2 = readInt(input); // 0x00rrggbb
|
|
float r2 = ((color2 & 0x00ff0000) >> 16) / 255.0f;
|
|
float g2 = ((color2 & 0x0000ff00) >> 8) / 255.0f;
|
|
float b2 = ((color2 & 0x000000ff)) / 255.0f;
|
|
|
|
timeline->setFrame(frameIndex, time, r, g, b, a, r2, g2, b2);
|
|
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
|
|
}
|
|
timelines.add(timeline);
|
|
duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * TwoColorTimeline::ENTRIES]);
|
|
break;
|
|
}
|
|
default: {
|
|
ContainerUtil::cleanUpVectorOfPointers(timelines);
|
|
setError("Invalid timeline type for a slot: ", skeletonData->_slots[slotIndex]->_name.buffer());
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Bone timelines.
|
|
for (int i = 0, n = readVarint(input, true); i < n; ++i) {
|
|
int boneIndex = readVarint(input, true);
|
|
for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) {
|
|
unsigned char timelineType = readByte(input);
|
|
int frameCount = readVarint(input, true);
|
|
switch (timelineType) {
|
|
case BONE_ROTATE: {
|
|
RotateTimeline *timeline = new(__FILE__, __LINE__) RotateTimeline(frameCount);
|
|
timeline->_boneIndex = boneIndex;
|
|
for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
|
|
float time = readFloat(input);
|
|
float degrees = readFloat(input);
|
|
timeline->setFrame(frameIndex, time, degrees);
|
|
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
|
|
}
|
|
timelines.add(timeline);
|
|
duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * RotateTimeline::ENTRIES]);
|
|
break;
|
|
}
|
|
case BONE_TRANSLATE:
|
|
case BONE_SCALE:
|
|
case BONE_SHEAR: {
|
|
TranslateTimeline *timeline;
|
|
float timelineScale = 1;
|
|
if (timelineType == BONE_SCALE) {
|
|
timeline = new(__FILE__, __LINE__) ScaleTimeline(frameCount);
|
|
} else if (timelineType == BONE_SHEAR) {
|
|
timeline = new(__FILE__, __LINE__) ShearTimeline(frameCount);
|
|
} else {
|
|
timeline = new(__FILE__, __LINE__) TranslateTimeline(frameCount);
|
|
timelineScale = scale;
|
|
}
|
|
timeline->_boneIndex = boneIndex;
|
|
for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
|
|
float time = readFloat(input);
|
|
float x = readFloat(input) * timelineScale;
|
|
float y = readFloat(input) * timelineScale;
|
|
timeline->setFrame(frameIndex, time, x, y);
|
|
if (frameIndex < frameCount - 1) {
|
|
readCurve(input, frameIndex, timeline);
|
|
}
|
|
}
|
|
timelines.add(timeline);
|
|
duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * TranslateTimeline::ENTRIES]);
|
|
break;
|
|
}
|
|
default: {
|
|
ContainerUtil::cleanUpVectorOfPointers(timelines);
|
|
setError("Invalid timeline type for a bone: ", skeletonData->_bones[boneIndex]->_name.buffer());
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// IK timelines.
|
|
for (int i = 0, n = readVarint(input, true); i < n; ++i) {
|
|
int index = readVarint(input, true);
|
|
int frameCount = readVarint(input, true);
|
|
IkConstraintTimeline *timeline = new(__FILE__, __LINE__) IkConstraintTimeline(frameCount);
|
|
timeline->_ikConstraintIndex = index;
|
|
for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
|
|
float time = readFloat(input);
|
|
float mix = readFloat(input);
|
|
float softness = readFloat(input) * _scale;
|
|
signed char bendDirection = readSByte(input);
|
|
bool compress = readBoolean(input);
|
|
bool stretch = readBoolean(input);
|
|
timeline->setFrame(frameIndex, time, mix, softness, bendDirection, compress, stretch);
|
|
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
|
|
}
|
|
timelines.add(timeline);
|
|
duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * IkConstraintTimeline::ENTRIES]);
|
|
}
|
|
|
|
// Transform constraint timelines.
|
|
for (int i = 0, n = readVarint(input, true); i < n; ++i) {
|
|
int index = readVarint(input, true);
|
|
int frameCount = readVarint(input, true);
|
|
TransformConstraintTimeline *timeline = new(__FILE__, __LINE__) TransformConstraintTimeline(frameCount);
|
|
timeline->_transformConstraintIndex = index;
|
|
for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
|
|
float time = readFloat(input);
|
|
float rotateMix = readFloat(input);
|
|
float translateMix = readFloat(input);
|
|
float scaleMix = readFloat(input);
|
|
float shearMix = readFloat(input);
|
|
timeline->setFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix);
|
|
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
|
|
}
|
|
timelines.add(timeline);
|
|
duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * TransformConstraintTimeline::ENTRIES]);
|
|
}
|
|
|
|
// Path constraint timelines.
|
|
for (int i = 0, n = readVarint(input, true); i < n; ++i) {
|
|
int index = readVarint(input, true);
|
|
PathConstraintData *data = skeletonData->_pathConstraints[index];
|
|
for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) {
|
|
int timelineType = readSByte(input);
|
|
int frameCount = readVarint(input, true);
|
|
switch (timelineType) {
|
|
case PATH_POSITION:
|
|
case PATH_SPACING: {
|
|
PathConstraintPositionTimeline *timeline;
|
|
float timelineScale = 1;
|
|
if (timelineType == PATH_SPACING) {
|
|
timeline = new(__FILE__, __LINE__) PathConstraintSpacingTimeline(frameCount);
|
|
|
|
if (data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed) timelineScale = scale;
|
|
} else {
|
|
timeline = new(__FILE__, __LINE__) PathConstraintPositionTimeline(frameCount);
|
|
|
|
if (data->_positionMode == PositionMode_Fixed) timelineScale = scale;
|
|
}
|
|
timeline->_pathConstraintIndex = index;
|
|
for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
|
|
float time = readFloat(input);
|
|
float value = readFloat(input) * timelineScale;
|
|
timeline->setFrame(frameIndex, time, value);
|
|
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
|
|
}
|
|
timelines.add(timeline);
|
|
duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * PathConstraintPositionTimeline::ENTRIES]);
|
|
break;
|
|
}
|
|
case PATH_MIX: {
|
|
PathConstraintMixTimeline *timeline = new(__FILE__, __LINE__) PathConstraintMixTimeline(frameCount);
|
|
|
|
timeline->_pathConstraintIndex = index;
|
|
for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
|
|
float time = readFloat(input);
|
|
float rotateMix = readFloat(input);
|
|
float translateMix = readFloat(input);
|
|
timeline->setFrame(frameIndex, time, rotateMix, translateMix);
|
|
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
|
|
}
|
|
timelines.add(timeline);
|
|
duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * PathConstraintMixTimeline::ENTRIES]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deform timelines.
|
|
for (int i = 0, n = readVarint(input, true); i < n; ++i) {
|
|
Skin *skin = skeletonData->_skins[readVarint(input, true)];
|
|
for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) {
|
|
int slotIndex = readVarint(input, true);
|
|
for (int iii = 0, nnn = readVarint(input, true); iii < nnn; iii++) {
|
|
const char *attachmentName = readStringRef(input, skeletonData);
|
|
Attachment *baseAttachment = skin->getAttachment(slotIndex, String(attachmentName));
|
|
|
|
if (!baseAttachment) {
|
|
ContainerUtil::cleanUpVectorOfPointers(timelines);
|
|
setError("Attachment not found: ", attachmentName);
|
|
return NULL;
|
|
}
|
|
|
|
VertexAttachment *attachment = static_cast<VertexAttachment *>(baseAttachment);
|
|
|
|
bool weighted = attachment->_bones.size() > 0;
|
|
Vector<float> &vertices = attachment->_vertices;
|
|
size_t deformLength = weighted ? vertices.size() / 3 * 2 : vertices.size();
|
|
|
|
size_t frameCount = (size_t)readVarint(input, true);
|
|
|
|
DeformTimeline *timeline = new(__FILE__, __LINE__) DeformTimeline(frameCount);
|
|
timeline->_slotIndex = slotIndex;
|
|
timeline->_attachment = attachment;
|
|
|
|
for (size_t frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
|
|
float time = readFloat(input);
|
|
Vector<float> deform;
|
|
size_t end = (size_t)readVarint(input, true);
|
|
if (end == 0) {
|
|
if (weighted) {
|
|
deform.setSize(deformLength, 0);
|
|
for (size_t iiii = 0; iiii < deformLength; ++iiii)
|
|
deform[iiii] = 0;
|
|
} else {
|
|
deform.clearAndAddAll(vertices);
|
|
}
|
|
} else {
|
|
deform.setSize(deformLength, 0);
|
|
size_t start = (size_t)readVarint(input, true);
|
|
end += start;
|
|
if (scale == 1) {
|
|
for (size_t v = start; v < end; ++v)
|
|
deform[v] = readFloat(input);
|
|
} else {
|
|
for (size_t v = start; v < end; ++v)
|
|
deform[v] = readFloat(input) * scale;
|
|
}
|
|
|
|
if (!weighted) {
|
|
for (size_t v = 0, vn = deform.size(); v < vn; ++v)
|
|
deform[v] += vertices[v];
|
|
}
|
|
}
|
|
|
|
timeline->setFrame(frameIndex, time, deform);
|
|
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
|
|
}
|
|
|
|
timelines.add(timeline);
|
|
duration = MathUtil::max(duration, timeline->_frames[frameCount - 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw order timeline.
|
|
size_t drawOrderCount = (size_t)readVarint(input, 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 = readFloat(input);
|
|
size_t offsetCount = (size_t)readVarint(input, true);
|
|
|
|
Vector<int> drawOrder;
|
|
drawOrder.setSize(slotCount, 0);
|
|
for (int ii = (int)slotCount - 1; ii >= 0; --ii)
|
|
drawOrder[ii] = -1;
|
|
|
|
Vector<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)readVarint(input, true);
|
|
// Collect unchanged items.
|
|
while (originalIndex != slotIndex)
|
|
unchanged[unchangedIndex++] = originalIndex++;
|
|
// Set changed items.
|
|
size_t index = originalIndex;
|
|
drawOrder[index + (size_t)readVarint(input, true)] = originalIndex++;
|
|
}
|
|
|
|
// Collect remaining unchanged items.
|
|
while (originalIndex < slotCount) {
|
|
unchanged[unchangedIndex++] = 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);
|
|
duration = MathUtil::max(duration, timeline->_frames[drawOrderCount - 1]);
|
|
}
|
|
|
|
// Event timeline.
|
|
int eventCount = readVarint(input, true);
|
|
if (eventCount > 0) {
|
|
EventTimeline *timeline = new(__FILE__, __LINE__) EventTimeline(eventCount);
|
|
|
|
for (int i = 0; i < eventCount; ++i) {
|
|
float time = readFloat(input);
|
|
EventData *eventData = skeletonData->_events[readVarint(input, true)];
|
|
Event *event = new(__FILE__, __LINE__) Event(time, *eventData);
|
|
|
|
event->_intValue = readVarint(input, false);
|
|
event->_floatValue = readFloat(input);
|
|
bool freeString = readBoolean(input);
|
|
const char *event_stringValue = freeString ? readString(input) : eventData->_stringValue.buffer();
|
|
event->_stringValue = String(event_stringValue);
|
|
if (freeString) SpineExtension::free(event_stringValue, __FILE__, __LINE__);
|
|
|
|
if (!eventData->_audioPath.isEmpty()) {
|
|
event->_volume = readFloat(input);
|
|
event->_balance = readFloat(input);
|
|
}
|
|
timeline->setFrame(i, event);
|
|
}
|
|
|
|
timelines.add(timeline);
|
|
duration = MathUtil::max(duration, timeline->_frames[eventCount - 1]);
|
|
}
|
|
|
|
return new(__FILE__, __LINE__) Animation(String(name), timelines, duration);
|
|
}
|
|
|
|
void SkeletonBinary::readCurve(DataInput *input, int frameIndex, CurveTimeline *timeline) {
|
|
switch (readByte(input)) {
|
|
case CURVE_STEPPED: {
|
|
timeline->setStepped(frameIndex);
|
|
break;
|
|
}
|
|
case CURVE_BEZIER: {
|
|
float cx1 = readFloat(input);
|
|
float cy1 = readFloat(input);
|
|
float cx2 = readFloat(input);
|
|
float cy2 = readFloat(input);
|
|
timeline->setCurve(frameIndex, cx1, cy1, cx2, cy2);
|
|
break;
|
|
}
|
|
}
|
|
}
|