Mario Zechner de6b411007 Formatting
2024-04-16 17:21:36 +02:00

1827 lines
72 KiB
C

/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 "Json.h"
#include <spine/Version.h>
#include <spine/Array.h>
#include <spine/AtlasAttachmentLoader.h>
#include <spine/SkeletonJson.h>
#include <spine/extension.h>
#include <stdio.h>
typedef struct {
const char *parent;
const char *skin;
int slotIndex;
spMeshAttachment *mesh;
int inheritTimeline;
} _spLinkedMesh;
typedef struct {
spSkeletonJson super;
int ownsLoader;
int linkedMeshCount;
int linkedMeshCapacity;
_spLinkedMesh *linkedMeshes;
} _spSkeletonJson;
spSkeletonJson *spSkeletonJson_createWithLoader(spAttachmentLoader *attachmentLoader) {
spSkeletonJson *self = SUPER(NEW(_spSkeletonJson));
self->scale = 1;
self->attachmentLoader = attachmentLoader;
return self;
}
spSkeletonJson *spSkeletonJson_create(spAtlas *atlas) {
spAtlasAttachmentLoader *attachmentLoader = spAtlasAttachmentLoader_create(atlas);
spSkeletonJson *self = spSkeletonJson_createWithLoader(SUPER(attachmentLoader));
SUB_CAST(_spSkeletonJson, self)->ownsLoader = 1;
return self;
}
void spSkeletonJson_dispose(spSkeletonJson *self) {
_spSkeletonJson *internal = SUB_CAST(_spSkeletonJson, self);
if (internal->ownsLoader) spAttachmentLoader_dispose(self->attachmentLoader);
FREE(internal->linkedMeshes);
FREE(self->error);
FREE(self);
}
void _spSkeletonJson_setError(spSkeletonJson *self, Json *root, const char *value1, const char *value2) {
char message[256];
int length;
FREE(self->error);
strcpy(message, value1);
length = (int) strlen(value1);
if (value2) strncat(message + length, value2, 255 - length);
MALLOC_STR(self->error, message);
if (root) Json_dispose(root);
}
static float toColor(const char *value, int index) {
char digits[3];
char *error;
int color;
if ((size_t) index >= strlen(value) / 2) return -1;
value += index * 2;
digits[0] = *value;
digits[1] = *(value + 1);
digits[2] = '\0';
color = (int) strtoul(digits, &error, 16);
if (*error != 0) return -1;
return color / (float) 255;
}
static void toColor2(spColor *color, const char *value, int /*bool*/ hasAlpha) {
color->r = toColor(value, 0);
color->g = toColor(value, 1);
color->b = toColor(value, 2);
if (hasAlpha) color->a = toColor(value, 3);
else
color->a = 1.0f;
}
static void
setBezier(spCurveTimeline *timeline, int frame, int value, int bezier, float time1, float value1, float cx1, float cy1,
float cx2, float cy2, float time2, float value2) {
spTimeline_setBezier(SUPER(timeline), bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
}
static int readCurve(Json *curve, spCurveTimeline *timeline, int bezier, int frame, int value, float time1, float time2,
float value1, float value2, float scale) {
float cx1, cy1, cx2, cy2;
if (curve->type == Json_String && strcmp(curve->valueString, "stepped") == 0) {
spCurveTimeline_setStepped(timeline, frame);
return bezier;
}
curve = Json_getItemAtIndex(curve, value << 2);
cx1 = curve->valueFloat;
curve = curve->next;
cy1 = curve->valueFloat * scale;
curve = curve->next;
cx2 = curve->valueFloat;
curve = curve->next;
cy2 = curve->valueFloat * scale;
setBezier(timeline, frame, value, bezier, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
return bezier + 1;
}
static spTimeline *readTimeline(Json *keyMap, spCurveTimeline1 *timeline, float defaultValue, float scale) {
float time = Json_getFloat(keyMap, "time", 0);
float value = Json_getFloat(keyMap, "value", defaultValue) * scale;
int frame, bezier = 0;
for (frame = 0;; ++frame) {
Json *nextMap, *curve;
float time2, value2;
spCurveTimeline1_setFrame(timeline, frame, time, value);
nextMap = keyMap->next;
if (nextMap == NULL) break;
time2 = Json_getFloat(nextMap, "time", 0);
value2 = Json_getFloat(nextMap, "value", defaultValue) * scale;
curve = Json_getItem(keyMap, "curve");
if (curve != NULL) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale);
time = time2;
value = value2;
keyMap = nextMap;
}
/* timeline.shrink(); // BOZO */
return SUPER(timeline);
}
static spTimeline *
readTimeline2(Json *keyMap, spCurveTimeline2 *timeline, const char *name1, const char *name2, float defaultValue,
float scale) {
float time = Json_getFloat(keyMap, "time", 0);
float value1 = Json_getFloat(keyMap, name1, defaultValue) * scale;
float value2 = Json_getFloat(keyMap, name2, defaultValue) * scale;
int frame, bezier = 0;
for (frame = 0;; ++frame) {
Json *nextMap, *curve;
float time2, nvalue1, nvalue2;
spCurveTimeline2_setFrame(timeline, frame, time, value1, value2);
nextMap = keyMap->next;
if (nextMap == NULL) break;
time2 = Json_getFloat(nextMap, "time", 0);
nvalue1 = Json_getFloat(nextMap, name1, defaultValue) * scale;
nvalue2 = Json_getFloat(nextMap, name2, defaultValue) * scale;
curve = Json_getItem(keyMap, "curve");
if (curve != NULL) {
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale);
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale);
}
time = time2;
value1 = nvalue1;
value2 = nvalue2;
keyMap = nextMap;
}
/* timeline.shrink(); // BOZO */
return SUPER(timeline);
}
static spSequence *readSequence(Json *item) {
spSequence *sequence;
if (item == NULL) return NULL;
sequence = spSequence_create(Json_getInt(item, "count", 0));
sequence->start = Json_getInt(item, "start", 1);
sequence->digits = Json_getInt(item, "digits", 0);
sequence->setupIndex = Json_getInt(item, "setupIndex", 0);
return sequence;
}
static void _spSkeletonJson_addLinkedMesh(spSkeletonJson *self, spMeshAttachment *mesh, const char *skin, int slotIndex,
const char *parent, int inheritDeform) {
_spLinkedMesh *linkedMesh;
_spSkeletonJson *internal = SUB_CAST(_spSkeletonJson, self);
if (internal->linkedMeshCount == internal->linkedMeshCapacity) {
_spLinkedMesh *linkedMeshes;
internal->linkedMeshCapacity *= 2;
if (internal->linkedMeshCapacity < 8) internal->linkedMeshCapacity = 8;
linkedMeshes = MALLOC(_spLinkedMesh, internal->linkedMeshCapacity);
memcpy(linkedMeshes, internal->linkedMeshes, sizeof(_spLinkedMesh) * internal->linkedMeshCount);
FREE(internal->linkedMeshes);
internal->linkedMeshes = linkedMeshes;
}
linkedMesh = internal->linkedMeshes + internal->linkedMeshCount++;
linkedMesh->mesh = mesh;
linkedMesh->skin = skin;
linkedMesh->slotIndex = slotIndex;
linkedMesh->parent = parent;
linkedMesh->inheritTimeline = inheritDeform;
}
static void cleanUpTimelines(spTimelineArray *timelines) {
int i, n;
for (i = 0, n = timelines->size; i < n; ++i)
spTimeline_dispose(timelines->items[i]);
spTimelineArray_dispose(timelines);
}
static int findSlotIndex(spSkeletonJson *json, const spSkeletonData *skeletonData, const char *slotName, spTimelineArray *timelines) {
spSlotData *slot = spSkeletonData_findSlot(skeletonData, slotName);
if (slot) return slot->index;
cleanUpTimelines(timelines);
_spSkeletonJson_setError(json, NULL, "Slot not found: ", slotName);
return -1;
}
int findIkConstraintIndex(spSkeletonJson *json, const spSkeletonData *skeletonData, const spIkConstraintData *constraint, spTimelineArray *timelines) {
if (constraint) {
int i;
for (i = 0; i < skeletonData->ikConstraintsCount; ++i)
if (skeletonData->ikConstraints[i] == constraint) return i;
}
cleanUpTimelines(timelines);
_spSkeletonJson_setError(json, NULL, "IK constraint not found: ", constraint->name);
return -1;
}
int findTransformConstraintIndex(spSkeletonJson *json, const spSkeletonData *skeletonData, const spTransformConstraintData *constraint, spTimelineArray *timelines) {
if (constraint) {
int i;
for (i = 0; i < skeletonData->transformConstraintsCount; ++i)
if (skeletonData->transformConstraints[i] == constraint) return i;
}
cleanUpTimelines(timelines);
_spSkeletonJson_setError(json, NULL, "Transform constraint not found: ", constraint->name);
return -1;
}
int findPathConstraintIndex(spSkeletonJson *json, const spSkeletonData *skeletonData, const spPathConstraintData *constraint, spTimelineArray *timelines) {
if (constraint) {
int i;
for (i = 0; i < skeletonData->pathConstraintsCount; ++i)
if (skeletonData->pathConstraints[i] == constraint) return i;
}
cleanUpTimelines(timelines);
_spSkeletonJson_setError(json, NULL, "Path constraint not found: ", constraint->name);
return -1;
}
int findPhysicsConstraintIndex(spSkeletonJson *json, const spSkeletonData *skeletonData, const spPhysicsConstraintData *constraint, spTimelineArray *timelines) {
if (constraint) {
int i;
for (i = 0; i < skeletonData->physicsConstraintsCount; ++i)
if (skeletonData->physicsConstraints[i] == constraint) return i;
}
cleanUpTimelines(timelines);
_spSkeletonJson_setError(json, NULL, "Physics constraint not found: ", constraint->name);
return -1;
}
static spAnimation *_spSkeletonJson_readAnimation(spSkeletonJson *self, Json *root, spSkeletonData *skeletonData) {
spTimelineArray *timelines = spTimelineArray_create(8);
float scale = self->scale, duration;
Json *bones = Json_getItem(root, "bones");
Json *slots = Json_getItem(root, "slots");
Json *ik = Json_getItem(root, "ik");
Json *transform = Json_getItem(root, "transform");
Json *paths = Json_getItem(root, "path");
Json *physics = Json_getItem(root, "physics");
Json *attachmentsJson = Json_getItem(root, "attachments");
Json *drawOrderJson = Json_getItem(root, "drawOrder");
Json *events = Json_getItem(root, "events");
Json *boneMap, *slotMap, *keyMap, *nextMap, *curve, *timelineMap;
Json *attachmentsMap, *constraintMap;
int frame, bezier, i, n;
spColor color, color2, newColor, newColor2;
/* Slot timelines. */
for (slotMap = slots ? slots->child : 0; slotMap; slotMap = slotMap->next) {
int slotIndex = findSlotIndex(self, skeletonData, slotMap->name, timelines);
if (slotIndex == -1) return NULL;
for (timelineMap = slotMap->child; timelineMap; timelineMap = timelineMap->next) {
int frames = timelineMap->size;
if (strcmp(timelineMap->name, "attachment") == 0) {
spAttachmentTimeline *timeline = spAttachmentTimeline_create(frames, slotIndex);
for (keyMap = timelineMap->child, frame = 0; keyMap; keyMap = keyMap->next, ++frame) {
spAttachmentTimeline_setFrame(timeline, frame, Json_getFloat(keyMap, "time", 0),
Json_getItem(keyMap, "name") ? Json_getItem(keyMap, "name")->valueString : NULL);
}
spTimelineArray_add(timelines, SUPER(timeline));
} else if (strcmp(timelineMap->name, "rgba") == 0) {
float time;
spRGBATimeline *timeline = spRGBATimeline_create(frames, frames << 2, slotIndex);
keyMap = timelineMap->child;
time = Json_getFloat(keyMap, "time", 0);
toColor2(&color, Json_getString(keyMap, "color", 0), 1);
for (frame = 0, bezier = 0;; ++frame) {
float time2;
spRGBATimeline_setFrame(timeline, frame, time, color.r, color.g, color.b, color.a);
nextMap = keyMap->next;
if (!nextMap) {
/* timeline.shrink(); // BOZO */
break;
}
time2 = Json_getFloat(nextMap, "time", 0);
toColor2(&newColor, Json_getString(nextMap, "color", 0), 1);
curve = Json_getItem(keyMap, "curve");
if (curve) {
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 0, time, time2, color.r, newColor.r,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 1, time, time2, color.g, newColor.g,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 2, time, time2, color.b, newColor.b,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 3, time, time2, color.a, newColor.a,
1);
}
time = time2;
color = newColor;
keyMap = nextMap;
}
spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
} else if (strcmp(timelineMap->name, "rgb") == 0) {
float time;
spRGBTimeline *timeline = spRGBTimeline_create(frames, frames * 3, slotIndex);
keyMap = timelineMap->child;
time = Json_getFloat(keyMap, "time", 0);
toColor2(&color, Json_getString(keyMap, "color", 0), 1);
for (frame = 0, bezier = 0;; ++frame) {
float time2;
spRGBTimeline_setFrame(timeline, frame, time, color.r, color.g, color.b);
nextMap = keyMap->next;
if (!nextMap) {
/* timeline.shrink(); // BOZO */
break;
}
time2 = Json_getFloat(nextMap, "time", 0);
toColor2(&newColor, Json_getString(nextMap, "color", 0), 1);
curve = Json_getItem(keyMap, "curve");
if (curve) {
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 0, time, time2, color.r, newColor.r,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 1, time, time2, color.g, newColor.g,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 2, time, time2, color.b, newColor.b,
1);
}
time = time2;
color = newColor;
keyMap = nextMap;
}
spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
} else if (strcmp(timelineMap->name, "alpha") == 0) {
spTimelineArray_add(timelines, readTimeline(timelineMap->child,
SUPER(spAlphaTimeline_create(frames,
frames, slotIndex)),
0, 1));
} else if (strcmp(timelineMap->name, "rgba2") == 0) {
float time;
spRGBA2Timeline *timeline = spRGBA2Timeline_create(frames, frames * 7, slotIndex);
keyMap = timelineMap->child;
time = Json_getFloat(keyMap, "time", 0);
toColor2(&color, Json_getString(keyMap, "light", 0), 1);
toColor2(&color2, Json_getString(keyMap, "dark", 0), 0);
for (frame = 0, bezier = 0;; ++frame) {
float time2;
spRGBA2Timeline_setFrame(timeline, frame, time, color.r, color.g, color.b, color.a, color2.g,
color2.g, color2.b);
nextMap = keyMap->next;
if (!nextMap) {
/* timeline.shrink(); // BOZO */
break;
}
time2 = Json_getFloat(nextMap, "time", 0);
toColor2(&newColor, Json_getString(nextMap, "light", 0), 1);
toColor2(&newColor2, Json_getString(nextMap, "dark", 0), 0);
curve = Json_getItem(keyMap, "curve");
if (curve) {
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 0, time, time2, color.r, newColor.r,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 1, time, time2, color.g, newColor.g,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 2, time, time2, color.b, newColor.b,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 3, time, time2, color.a, newColor.a,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 4, time, time2, color2.r, newColor2.r,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 5, time, time2, color2.g, newColor2.g,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 6, time, time2, color2.b, newColor2.b,
1);
}
time = time2;
color = newColor;
color2 = newColor2;
keyMap = nextMap;
}
spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
} else if (strcmp(timelineMap->name, "rgb2") == 0) {
float time;
spRGBA2Timeline *timeline = spRGBA2Timeline_create(frames, frames * 6, slotIndex);
keyMap = timelineMap->child;
time = Json_getFloat(keyMap, "time", 0);
toColor2(&color, Json_getString(keyMap, "light", 0), 0);
toColor2(&color2, Json_getString(keyMap, "dark", 0), 0);
for (frame = 0, bezier = 0;; ++frame) {
float time2;
spRGBA2Timeline_setFrame(timeline, frame, time, color.r, color.g, color.b, color.a, color2.r,
color2.g, color2.b);
nextMap = keyMap->next;
if (!nextMap) {
/* timeline.shrink(); // BOZO */
break;
}
time2 = Json_getFloat(nextMap, "time", 0);
toColor2(&newColor, Json_getString(nextMap, "light", 0), 0);
toColor2(&newColor2, Json_getString(nextMap, "dark", 0), 0);
curve = Json_getItem(keyMap, "curve");
if (curve) {
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 0, time, time2, color.r, newColor.r,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 1, time, time2, color.g, newColor.g,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 2, time, time2, color.b, newColor.b,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 3, time, time2, color2.r, newColor2.r,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 4, time, time2, color2.g, newColor2.g,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 5, time, time2, color2.b, newColor2.b,
1);
}
time = time2;
color = newColor;
color2 = newColor2;
keyMap = nextMap;
}
spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
} else {
cleanUpTimelines(timelines);
_spSkeletonJson_setError(self, NULL, "Invalid timeline type for a slot: ", timelineMap->name);
return NULL;
}
}
}
/* Bone timelines. */
for (boneMap = bones ? bones->child : 0; boneMap; boneMap = boneMap->next) {
int boneIndex = -1;
for (i = 0; i < skeletonData->bonesCount; ++i) {
if (strcmp(skeletonData->bones[i]->name, boneMap->name) == 0) {
boneIndex = i;
break;
}
}
if (boneIndex == -1) {
cleanUpTimelines(timelines);
_spSkeletonJson_setError(self, NULL, "Bone not found: ", boneMap->name);
return NULL;
}
for (timelineMap = boneMap->child; timelineMap; timelineMap = timelineMap->next) {
int frames = timelineMap->size;
if (frames == 0) continue;
if (strcmp(timelineMap->name, "rotate") == 0) {
spTimelineArray_add(timelines, readTimeline(timelineMap->child,
SUPER(spRotateTimeline_create(frames,
frames,
boneIndex)),
0, 1));
} else if (strcmp(timelineMap->name, "translate") == 0) {
spTranslateTimeline *timeline = spTranslateTimeline_create(frames, frames << 1,
boneIndex);
spTimelineArray_add(timelines, readTimeline2(timelineMap->child, SUPER(timeline), "x", "y", 0, scale));
} else if (strcmp(timelineMap->name, "translatex") == 0) {
spTranslateXTimeline *timeline = spTranslateXTimeline_create(frames, frames,
boneIndex);
spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 0, scale));
} else if (strcmp(timelineMap->name, "translatey") == 0) {
spTranslateYTimeline *timeline = spTranslateYTimeline_create(frames, frames,
boneIndex);
spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 0, scale));
} else if (strcmp(timelineMap->name, "scale") == 0) {
spScaleTimeline *timeline = spScaleTimeline_create(frames, frames << 1,
boneIndex);
spTimelineArray_add(timelines, readTimeline2(timelineMap->child, SUPER(timeline), "x", "y", 1, 1));
} else if (strcmp(timelineMap->name, "scalex") == 0) {
spScaleXTimeline *timeline = spScaleXTimeline_create(frames, frames, boneIndex);
spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 1, 1));
} else if (strcmp(timelineMap->name, "scaley") == 0) {
spScaleYTimeline *timeline = spScaleYTimeline_create(frames, frames, boneIndex);
spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 1, 1));
} else if (strcmp(timelineMap->name, "shear") == 0) {
spShearTimeline *timeline = spShearTimeline_create(frames, frames << 1,
boneIndex);
spTimelineArray_add(timelines, readTimeline2(timelineMap->child, SUPER(timeline), "x", "y", 0, 1));
} else if (strcmp(timelineMap->name, "shearx") == 0) {
spShearXTimeline *timeline = spShearXTimeline_create(frames, frames, boneIndex);
spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 0, 1));
} else if (strcmp(timelineMap->name, "sheary") == 0) {
spShearYTimeline *timeline = spShearYTimeline_create(frames, frames, boneIndex);
spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 0, 1));
} else if (strcmp(timelineMap->name, "inherit") == 0) {
spInheritTimeline *timeline = spInheritTimeline_create(frames, boneIndex);
keyMap = timelineMap->child;
for (frame = 0;; frame++) {
float time = Json_getFloat(keyMap, "time", 0);
const char *value = Json_getString(keyMap, "value", "normal");
spInherit inherit = SP_INHERIT_NORMAL;
if (strcmp(value, "normal") == 0) inherit = SP_INHERIT_NORMAL;
else if (strcmp(value, "onlyTranslation") == 0)
inherit = SP_INHERIT_ONLYTRANSLATION;
else if (strcmp(value, "noRotationOrReflection") == 0)
inherit = SP_INHERIT_NOROTATIONORREFLECTION;
else if (strcmp(value, "noScale") == 0)
inherit = SP_INHERIT_NOSCALE;
else if (strcmp(value, "noScaleOrReflection") == 0)
inherit = SP_INHERIT_NOSCALEORREFLECTION;
spInheritTimeline_setFrame(timeline, frame, time, inherit);
nextMap = keyMap->next;
if (!nextMap) break;
}
spTimelineArray_add(timelines, SUPER(timeline));
} else {
cleanUpTimelines(timelines);
_spSkeletonJson_setError(self, NULL, "Invalid timeline type for a bone: ", timelineMap->name);
return NULL;
}
}
}
/* IK constraint timelines. */
for (constraintMap = ik ? ik->child : 0; constraintMap; constraintMap = constraintMap->next) {
spIkConstraintData *constraint;
spIkConstraintTimeline *timeline;
int constraintIndex;
float time, mix, softness;
keyMap = constraintMap->child;
if (keyMap == NULL) continue;
constraint = spSkeletonData_findIkConstraint(skeletonData, constraintMap->name);
constraintIndex = findIkConstraintIndex(self, skeletonData, constraint, timelines);
if (constraintIndex == -1) return NULL;
timeline = spIkConstraintTimeline_create(constraintMap->size, constraintMap->size << 1, constraintIndex);
time = Json_getFloat(keyMap, "time", 0);
mix = Json_getFloat(keyMap, "mix", 1);
softness = Json_getFloat(keyMap, "softness", 0) * scale;
for (frame = 0, bezier = 0;; ++frame) {
float time2, mix2, softness2;
int bendDirection = Json_getInt(keyMap, "bendPositive", 1) ? 1 : -1;
spIkConstraintTimeline_setFrame(timeline, frame, time, mix, softness, bendDirection,
Json_getInt(keyMap, "compress", 0) ? 1 : 0,
Json_getInt(keyMap, "stretch", 0) ? 1 : 0);
nextMap = keyMap->next;
if (!nextMap) {
/* timeline.shrink(); // BOZO */
break;
}
time2 = Json_getFloat(nextMap, "time", 0);
mix2 = Json_getFloat(nextMap, "mix", 1);
softness2 = Json_getFloat(nextMap, "softness", 0) * scale;
curve = Json_getItem(keyMap, "curve");
if (curve) {
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 0, time, time2, mix, mix2, 1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 1, time, time2, softness, softness2, scale);
}
time = time2;
mix = mix2;
softness = softness2;
keyMap = nextMap;
}
spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
}
/* Transform constraint timelines. */
for (constraintMap = transform ? transform->child : 0; constraintMap; constraintMap = constraintMap->next) {
spTransformConstraintData *constraint;
spTransformConstraintTimeline *timeline;
int constraintIndex;
float time, mixRotate, mixShearY, mixX, mixY, mixScaleX, mixScaleY;
keyMap = constraintMap->child;
if (keyMap == NULL) continue;
constraint = spSkeletonData_findTransformConstraint(skeletonData, constraintMap->name);
constraintIndex = findTransformConstraintIndex(self, skeletonData, constraint, timelines);
if (constraintIndex == -1) return NULL;
timeline = spTransformConstraintTimeline_create(constraintMap->size, constraintMap->size * 6, constraintIndex);
time = Json_getFloat(keyMap, "time", 0);
mixRotate = Json_getFloat(keyMap, "mixRotate", 1);
mixShearY = Json_getFloat(keyMap, "mixShearY", 1);
mixX = Json_getFloat(keyMap, "mixX", 1);
mixY = Json_getFloat(keyMap, "mixY", mixX);
mixScaleX = Json_getFloat(keyMap, "mixScaleX", 1);
mixScaleY = Json_getFloat(keyMap, "mixScaleY", mixScaleX);
for (frame = 0, bezier = 0;; ++frame) {
float time2, mixRotate2, mixShearY2, mixX2, mixY2, mixScaleX2, mixScaleY2;
spTransformConstraintTimeline_setFrame(timeline, frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY,
mixShearY);
nextMap = keyMap->next;
if (!nextMap) {
/* timeline.shrink(); // BOZO */
break;
}
time2 = Json_getFloat(nextMap, "time", 0);
mixRotate2 = Json_getFloat(nextMap, "mixRotate", 1);
mixShearY2 = Json_getFloat(nextMap, "mixShearY", 1);
mixX2 = Json_getFloat(nextMap, "mixX", 1);
mixY2 = Json_getFloat(nextMap, "mixY", mixX2);
mixScaleX2 = Json_getFloat(nextMap, "mixScaleX", 1);
mixScaleY2 = Json_getFloat(nextMap, "mixScaleY", mixScaleX2);
curve = Json_getItem(keyMap, "curve");
if (curve) {
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 1, time, time2, mixX, mixX2, 1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 2, time, time2, mixY, mixY2, 1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 3, time, time2, mixScaleX, mixScaleX2, 1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 4, time, time2, mixScaleY, mixScaleY2, 1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 5, time, time2, mixShearY, mixShearY2, 1);
}
time = time2;
mixRotate = mixRotate2;
mixX = mixX2;
mixY = mixY2;
mixScaleX = mixScaleX2;
mixScaleY = mixScaleY2;
mixScaleX = mixScaleX2;
keyMap = nextMap;
}
spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
}
/** Path constraint timelines. */
for (constraintMap = paths ? paths->child : 0; constraintMap; constraintMap = constraintMap->next) {
spPathConstraintData *constraint = spSkeletonData_findPathConstraint(skeletonData, constraintMap->name);
int constraintIndex = findPathConstraintIndex(self, skeletonData, constraint, timelines);
if (constraintIndex == -1) return NULL;
for (timelineMap = constraintMap->child; timelineMap; timelineMap = timelineMap->next) {
const char *timelineName;
int frames;
keyMap = timelineMap->child;
if (keyMap == NULL) continue;
frames = timelineMap->size;
timelineName = timelineMap->name;
if (strcmp(timelineName, "position") == 0) {
spPathConstraintPositionTimeline *timeline = spPathConstraintPositionTimeline_create(frames,
frames,
constraintIndex);
spTimelineArray_add(timelines, readTimeline(keyMap, SUPER(timeline), 0,
constraint->positionMode == SP_POSITION_MODE_FIXED ? scale : 1));
} else if (strcmp(timelineName, "spacing") == 0) {
spCurveTimeline1 *timeline = SUPER(
spPathConstraintSpacingTimeline_create(frames, frames, constraintIndex));
spTimelineArray_add(timelines, readTimeline(keyMap, timeline, 0,
constraint->spacingMode == SP_SPACING_MODE_LENGTH ||
constraint->spacingMode == SP_SPACING_MODE_FIXED
? scale
: 1));
} else if (strcmp(timelineName, "mix") == 0) {
spPathConstraintMixTimeline *timeline = spPathConstraintMixTimeline_create(frames,
frames * 3,
constraintIndex);
float time = Json_getFloat(keyMap, "time", 0);
float mixRotate = Json_getFloat(keyMap, "mixRotate", 1);
float mixX = Json_getFloat(keyMap, "mixX", 1);
float mixY = Json_getFloat(keyMap, "mixY", mixX);
for (frame = 0, bezier = 0;; ++frame) {
float time2, mixRotate2, mixX2, mixY2;
spPathConstraintMixTimeline_setFrame(timeline, frame, time, mixRotate, mixX, mixY);
nextMap = keyMap->next;
if (!nextMap) {
/* timeline.shrink(); // BOZO */
break;
}
time2 = Json_getFloat(nextMap, "time", 0);
mixRotate2 = Json_getFloat(nextMap, "mixRotate", 1);
mixX2 = Json_getFloat(nextMap, "mixX", 1);
mixY2 = Json_getFloat(nextMap, "mixY", mixX2);
curve = Json_getItem(keyMap, "curve");
if (curve != NULL) {
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 0, time, time2, mixRotate, mixRotate2,
1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 1, time, time2, mixX, mixX2, 1);
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 2, time, time2, mixY, mixY2, 1);
}
time = time2;
mixRotate = mixRotate2;
mixX = mixX2;
mixY = mixY2;
keyMap = nextMap;
}
spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
}
}
}
/** Physics constraint timelines. */
for (constraintMap = physics ? physics->child : 0; constraintMap; constraintMap = constraintMap->next) {
int index = -1;
if (constraintMap->name && strlen(constraintMap->name) > 0) {
spPhysicsConstraintData *constraint = spSkeletonData_findPhysicsConstraint(skeletonData, constraintMap->name);
index = findPhysicsConstraintIndex(self, skeletonData, constraint, timelines);
if (index == -1) return NULL;
}
for (timelineMap = constraintMap->child; timelineMap; timelineMap = timelineMap->next) {
keyMap = timelineMap->child;
if (keyMap == NULL) continue;
const char *timelineName = timelineMap->name;
int frames = timelineMap->size;
if (strcmp(timelineName, "reset") == 0) {
spPhysicsConstraintResetTimeline *timeline = spPhysicsConstraintResetTimeline_create(frames, index);
for (frame = 0; keyMap != NULL; keyMap = keyMap->next, frame++) {
spPhysicsConstraintResetTimeline_setFrame(timeline, frame, Json_getFloat(keyMap, "time", 0));
}
spTimelineArray_add(timelines, SUPER(timeline));
continue;
}
spPhysicsConstraintTimeline *timeline = NULL;
if (strcmp(timelineName, "inertia") == 0) {
timeline = spPhysicsConstraintTimeline_create(frames, frames, index, SP_TIMELINE_PHYSICSCONSTRAINT_INERTIA);
} else if (strcmp(timelineName, "strength") == 0) {
timeline = spPhysicsConstraintTimeline_create(frames, frames, index, SP_TIMELINE_PHYSICSCONSTRAINT_STRENGTH);
} else if (strcmp(timelineName, "damping") == 0) {
timeline = spPhysicsConstraintTimeline_create(frames, frames, index, SP_TIMELINE_PHYSICSCONSTRAINT_DAMPING);
} else if (strcmp(timelineName, "mass") == 0) {
timeline = spPhysicsConstraintTimeline_create(frames, frames, index, SP_TIMELINE_PHYSICSCONSTRAINT_MASS);
} else if (strcmp(timelineName, "wind") == 0) {
timeline = spPhysicsConstraintTimeline_create(frames, frames, index, SP_TIMELINE_PHYSICSCONSTRAINT_WIND);
} else if (strcmp(timelineName, "gravity") == 0) {
timeline = spPhysicsConstraintTimeline_create(frames, frames, index, SP_TIMELINE_PHYSICSCONSTRAINT_GRAVITY);
} else if (strcmp(timelineName, "mix") == 0) {
timeline = spPhysicsConstraintTimeline_create(frames, frames, index, SP_TIMELINE_PHYSICSCONSTRAINT_MIX);
} else {
continue;
}
spTimelineArray_add(timelines, readTimeline(keyMap, SUPER(timeline), 0, 1));
}
}
/* Attachment timelines. */
for (attachmentsMap = attachmentsJson ? attachmentsJson->child : 0; attachmentsMap; attachmentsMap = attachmentsMap->next) {
spSkin *skin = spSkeletonData_findSkin(skeletonData, attachmentsMap->name);
for (slotMap = attachmentsMap->child; slotMap; slotMap = slotMap->next) {
Json *attachmentMap;
int slotIndex = findSlotIndex(self, skeletonData, slotMap->name, timelines);
if (slotIndex == -1) return NULL;
for (attachmentMap = slotMap->child; attachmentMap; attachmentMap = attachmentMap->next) {
spAttachment *baseAttachment = spSkin_getAttachment(skin, slotIndex, attachmentMap->name);
if (!baseAttachment) {
cleanUpTimelines(timelines);
_spSkeletonJson_setError(self, 0, "Attachment not found: ", attachmentMap->name);
return NULL;
}
for (timelineMap = attachmentMap->child; timelineMap; timelineMap = timelineMap->next) {
int frames;
const char *timelineName;
keyMap = timelineMap->child;
if (keyMap == NULL) continue;
frames = timelineMap->size;
timelineName = timelineMap->name;
if (!strcmp("deform", timelineName)) {
float *tempDeform;
spVertexAttachment *vertexAttachment;
int weighted, deformLength;
spDeformTimeline *timeline;
float time;
vertexAttachment = SUB_CAST(spVertexAttachment, baseAttachment);
weighted = vertexAttachment->bones != 0;
deformLength = weighted ? vertexAttachment->verticesCount / 3 * 2 : vertexAttachment->verticesCount;
tempDeform = MALLOC(float, deformLength);
timeline = spDeformTimeline_create(timelineMap->size, deformLength, timelineMap->size,
slotIndex,
vertexAttachment);
time = Json_getFloat(keyMap, "time", 0);
for (frame = 0, bezier = 0;; ++frame) {
Json *vertices = Json_getItem(keyMap, "vertices");
float *deform;
float time2;
if (!vertices) {
if (weighted) {
deform = tempDeform;
memset(deform, 0, sizeof(float) * deformLength);
} else
deform = vertexAttachment->vertices;
} else {
int v, start = Json_getInt(keyMap, "offset", 0);
Json *vertex;
deform = tempDeform;
memset(deform, 0, sizeof(float) * start);
if (self->scale == 1) {
for (vertex = vertices->child, v = start; vertex; vertex = vertex->next, ++v)
deform[v] = vertex->valueFloat;
} else {
for (vertex = vertices->child, v = start; vertex; vertex = vertex->next, ++v)
deform[v] = vertex->valueFloat * self->scale;
}
memset(deform + v, 0, sizeof(float) * (deformLength - v));
if (!weighted) {
float *verticesValues = vertexAttachment->vertices;
for (v = 0; v < deformLength; ++v)
deform[v] += verticesValues[v];
}
}
spDeformTimeline_setFrame(timeline, frame, time, deform);
nextMap = keyMap->next;
if (!nextMap) {
/* timeline.shrink(); // BOZO */
break;
}
time2 = Json_getFloat(nextMap, "time", 0);
curve = Json_getItem(keyMap, "curve");
if (curve) {
bezier = readCurve(curve, SUPER(timeline), bezier, frame, 0, time, time2, 0, 1, 1);
}
time = time2;
keyMap = nextMap;
}
FREE(tempDeform);
spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
} else if (!strcmp(timelineName, "sequence")) {
spSequenceTimeline *timeline = spSequenceTimeline_create(frames, slotIndex, baseAttachment);
float lastDelay = 0;
for (frame = 0; keyMap != NULL; keyMap = keyMap->next, frame++) {
float delay = Json_getFloat(keyMap, "delay", lastDelay);
float time = Json_getFloat(keyMap, "time", 0);
const char *modeString = Json_getString(keyMap, "mode", "hold");
int index = Json_getInt(keyMap, "index", 0);
int mode = SP_SEQUENCE_MODE_HOLD;
if (!strcmp(modeString, "once")) mode = SP_SEQUENCE_MODE_ONCE;
if (!strcmp(modeString, "loop")) mode = SP_SEQUENCE_MODE_LOOP;
if (!strcmp(modeString, "pingpong")) mode = SP_SEQUENCE_MODE_PINGPONG;
if (!strcmp(modeString, "onceReverse")) mode = SP_SEQUENCE_MODE_ONCEREVERSE;
if (!strcmp(modeString, "loopReverse")) mode = SP_SEQUENCE_MODE_LOOPREVERSE;
if (!strcmp(modeString, "pingpongReverse")) mode = SP_SEQUENCE_MODE_PINGPONGREVERSE;
spSequenceTimeline_setFrame(timeline, frame, time, mode, index, delay);
lastDelay = delay;
}
spTimelineArray_add(timelines, SUPER(timeline));
}
}
}
}
}
/* Draw order timeline. */
if (drawOrderJson) {
spDrawOrderTimeline *timeline = spDrawOrderTimeline_create(drawOrderJson->size, skeletonData->slotsCount);
for (keyMap = drawOrderJson->child, frame = 0; keyMap; keyMap = keyMap->next, ++frame) {
int ii;
int *drawOrder = 0;
Json *offsets = Json_getItem(keyMap, "offsets");
if (offsets) {
Json *offsetMap;
int *unchanged = MALLOC(int, skeletonData->slotsCount - offsets->size);
int originalIndex = 0, unchangedIndex = 0;
drawOrder = MALLOC(int, skeletonData->slotsCount);
for (ii = skeletonData->slotsCount - 1; ii >= 0; --ii)
drawOrder[ii] = -1;
for (offsetMap = offsets->child; offsetMap; offsetMap = offsetMap->next) {
int slotIndex = findSlotIndex(self, skeletonData, Json_getString(offsetMap, "slot", 0), timelines);
if (slotIndex == -1) return NULL;
/* Collect unchanged items. */
while (originalIndex != slotIndex)
unchanged[unchangedIndex++] = originalIndex++;
/* Set changed items. */
drawOrder[originalIndex + Json_getInt(offsetMap, "offset", 0)] = originalIndex;
originalIndex++;
}
/* Collect remaining unchanged items. */
while (originalIndex < skeletonData->slotsCount)
unchanged[unchangedIndex++] = originalIndex++;
/* Fill in unchanged items. */
for (ii = skeletonData->slotsCount - 1; ii >= 0; ii--)
if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
FREE(unchanged);
}
spDrawOrderTimeline_setFrame(timeline, frame, Json_getFloat(keyMap, "time", 0), drawOrder);
FREE(drawOrder);
}
spTimelineArray_add(timelines, SUPER(timeline));
}
/* Event timeline. */
if (events) {
spEventTimeline *timeline = spEventTimeline_create(events->size);
for (keyMap = events->child, frame = 0; keyMap; keyMap = keyMap->next, ++frame) {
spEvent *event;
const char *stringValue;
spEventData *eventData = spSkeletonData_findEvent(skeletonData, Json_getString(keyMap, "name", 0));
if (!eventData) {
cleanUpTimelines(timelines);
_spSkeletonJson_setError(self, 0, "Event not found: ", Json_getString(keyMap, "name", 0));
return NULL;
}
event = spEvent_create(Json_getFloat(keyMap, "time", 0), eventData);
event->intValue = Json_getInt(keyMap, "int", eventData->intValue);
event->floatValue = Json_getFloat(keyMap, "float", eventData->floatValue);
stringValue = Json_getString(keyMap, "string", eventData->stringValue);
if (stringValue) MALLOC_STR(event->stringValue, stringValue);
if (eventData->audioPath) {
event->volume = Json_getFloat(keyMap, "volume", 1);
event->balance = Json_getFloat(keyMap, "volume", 0);
}
spEventTimeline_setFrame(timeline, frame, event);
}
spTimelineArray_add(timelines, SUPER(timeline));
}
duration = 0;
for (i = 0, n = timelines->size; i < n; ++i)
duration = MAX(duration, spTimeline_getDuration(timelines->items[i]));
return spAnimation_create(root->name, timelines, duration);
}
static void
_readVertices(spSkeletonJson *self, Json *attachmentMap, spVertexAttachment *attachment, int verticesLength) {
Json *entry;
float *vertices;
int i, n, nn, entrySize;
spFloatArray *weights;
spIntArray *bones;
attachment->worldVerticesLength = verticesLength;
entry = Json_getItem(attachmentMap, "vertices");
entrySize = entry->size;
vertices = MALLOC(float, entrySize);
for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
vertices[i] = entry->valueFloat;
if (verticesLength == entrySize) {
if (self->scale != 1)
for (i = 0; i < entrySize; ++i)
vertices[i] *= self->scale;
attachment->verticesCount = verticesLength;
attachment->vertices = vertices;
attachment->bonesCount = 0;
attachment->bones = 0;
return;
}
weights = spFloatArray_create(verticesLength * 3 * 3);
bones = spIntArray_create(verticesLength * 3);
for (i = 0, n = entrySize; i < n;) {
int boneCount = (int) vertices[i++];
spIntArray_add(bones, boneCount);
for (nn = i + boneCount * 4; i < nn; i += 4) {
spIntArray_add(bones, (int) vertices[i]);
spFloatArray_add(weights, vertices[i + 1] * self->scale);
spFloatArray_add(weights, vertices[i + 2] * self->scale);
spFloatArray_add(weights, vertices[i + 3]);
}
}
attachment->verticesCount = weights->size;
attachment->vertices = weights->items;
FREE(weights);
attachment->bonesCount = bones->size;
attachment->bones = bones->items;
FREE(bones);
FREE(vertices);
}
spSkeletonData *spSkeletonJson_readSkeletonDataFile(spSkeletonJson *self, const char *path) {
int length;
spSkeletonData *skeletonData;
const char *json = _spUtil_readFile(path, &length);
if (length == 0 || !json) {
_spSkeletonJson_setError(self, 0, "Unable to read skeleton file: ", path);
return NULL;
}
skeletonData = spSkeletonJson_readSkeletonData(self, json);
FREE(json);
return skeletonData;
}
static int string_starts_with(const char *str, const char *needle) {
int lenStr, lenNeedle, i;
if (!str) return 0;
lenStr = (int) strlen(str);
lenNeedle = (int) strlen(needle);
if (lenStr < lenNeedle) return 0;
for (i = 0; i < lenNeedle; i++) {
if (str[i] != needle[i]) return 0;
}
return -1;
}
static int string_lastIndexOf(const char *str, char needle) {
if (!str) return -1;
int lastIndex = -1;
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] == needle) {
lastIndex = i;
}
}
return lastIndex;
}
static char *string_substring(const char *str, int start, int end) {
if (str == NULL || start > end || start < 0) {
return NULL;
}
int len = end - start;
char *substr = MALLOC(char, len + 1);
if (substr == NULL) {
return NULL;
}
strncpy(substr, str + start, len);
substr[len] = '\0';
return substr;
}
spSkeletonData *spSkeletonJson_readSkeletonData(spSkeletonJson *self, const char *json) {
int i, ii;
spSkeletonData *skeletonData;
Json *root, *skeleton, *bones, *boneMap, *ik, *transform, *pathJson, *physics, *slots, *skins, *animations, *events;
_spSkeletonJson *internal = SUB_CAST(_spSkeletonJson, self);
FREE(self->error);
self->error = 0;
internal->linkedMeshCount = 0;
root = Json_create(json);
if (!root) {
_spSkeletonJson_setError(self, 0, "Invalid skeleton JSON: ", Json_getError());
return NULL;
}
skeletonData = spSkeletonData_create();
skeleton = Json_getItem(root, "skeleton");
if (skeleton) {
MALLOC_STR(skeletonData->hash, Json_getString(skeleton, "hash", "0"));
MALLOC_STR(skeletonData->version, Json_getString(skeleton, "spine", "0"));
if (!string_starts_with(skeletonData->version, SPINE_VERSION_STRING)) {
char errorMsg[255];
snprintf(errorMsg, 255, "Skeleton version %s does not match runtime version %s", skeletonData->version, SPINE_VERSION_STRING);
_spSkeletonJson_setError(self, 0, errorMsg, NULL);
return NULL;
}
skeletonData->x = Json_getFloat(skeleton, "x", 0);
skeletonData->y = Json_getFloat(skeleton, "y", 0);
skeletonData->width = Json_getFloat(skeleton, "width", 0);
skeletonData->height = Json_getFloat(skeleton, "height", 0);
skeletonData->referenceScale = Json_getFloat(skeleton, "referenceScale", 100) * self->scale;
skeletonData->fps = Json_getFloat(skeleton, "fps", 30);
skeletonData->imagesPath = Json_getString(skeleton, "images", 0);
if (skeletonData->imagesPath) {
char *tmp = NULL;
MALLOC_STR(tmp, skeletonData->imagesPath);
skeletonData->imagesPath = tmp;
}
skeletonData->audioPath = Json_getString(skeleton, "audio", 0);
if (skeletonData->audioPath) {
char *tmp = NULL;
MALLOC_STR(tmp, skeletonData->audioPath);
skeletonData->audioPath = tmp;
}
}
/* Bones. */
bones = Json_getItem(root, "bones");
skeletonData->bones = MALLOC(spBoneData *, bones->size);
for (boneMap = bones->child, i = 0; boneMap; boneMap = boneMap->next, ++i) {
spBoneData *data;
const char *inherit;
const char *color;
spBoneData *parent = 0;
const char *parentName = Json_getString(boneMap, "parent", 0);
if (parentName) {
parent = spSkeletonData_findBone(skeletonData, parentName);
if (!parent) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Parent bone not found: ", parentName);
return NULL;
}
}
data = spBoneData_create(skeletonData->bonesCount, Json_getString(boneMap, "name", 0), parent);
data->length = Json_getFloat(boneMap, "length", 0) * self->scale;
data->x = Json_getFloat(boneMap, "x", 0) * self->scale;
data->y = Json_getFloat(boneMap, "y", 0) * self->scale;
data->rotation = Json_getFloat(boneMap, "rotation", 0);
data->scaleX = Json_getFloat(boneMap, "scaleX", 1);
data->scaleY = Json_getFloat(boneMap, "scaleY", 1);
data->shearX = Json_getFloat(boneMap, "shearX", 0);
data->shearY = Json_getFloat(boneMap, "shearY", 0);
inherit = Json_getString(boneMap, "inherit", "normal");
data->inherit = SP_INHERIT_NORMAL;
if (strcmp(inherit, "normal") == 0) data->inherit = SP_INHERIT_NORMAL;
else if (strcmp(inherit, "onlyTranslation") == 0)
data->inherit = SP_INHERIT_ONLYTRANSLATION;
else if (strcmp(inherit, "noRotationOrReflection") == 0)
data->inherit = SP_INHERIT_NOROTATIONORREFLECTION;
else if (strcmp(inherit, "noScale") == 0)
data->inherit = SP_INHERIT_NOSCALE;
else if (strcmp(inherit, "noScaleOrReflection") == 0)
data->inherit = SP_INHERIT_NOSCALEORREFLECTION;
data->skinRequired = Json_getInt(boneMap, "skin", 0) ? 1 : 0;
color = Json_getString(boneMap, "color", 0);
if (color) toColor2(&data->color, color, -1);
data->icon = Json_getString(boneMap, "icon", "");
if (data->icon) {
char *tmp = NULL;
MALLOC_STR(tmp, data->icon);
data->icon = tmp;
}
data->visible = Json_getInt(boneMap, "visible", -1) ? -1 : 0;
skeletonData->bones[i] = data;
skeletonData->bonesCount++;
}
/* Slots. */
slots = Json_getItem(root, "slots");
if (slots) {
Json *slotMap;
skeletonData->slotsCount = slots->size;
skeletonData->slots = MALLOC(spSlotData *, slots->size);
for (slotMap = slots->child, i = 0; slotMap; slotMap = slotMap->next, ++i) {
spSlotData *data;
const char *color;
const char *dark;
Json *item;
const char *boneName = Json_getString(slotMap, "bone", 0);
spBoneData *boneData = spSkeletonData_findBone(skeletonData, boneName);
if (!boneData) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Slot bone not found: ", boneName);
return NULL;
}
char *pathName = NULL;
char *slotName = (char *) Json_getString(slotMap, "name", NULL);
int slash = string_lastIndexOf(slotName, '/');
if (slash != -1) {
pathName = string_substring(slotName, 0, slash);
slotName = string_substring(slotName, slash + 1, strlen(slotName));
}
data = spSlotData_create(i, slotName, boneData);
color = Json_getString(slotMap, "color", 0);
if (color) {
spColor_setFromFloats(&data->color,
toColor(color, 0),
toColor(color, 1),
toColor(color, 2),
toColor(color, 3));
}
dark = Json_getString(slotMap, "dark", 0);
if (dark) {
data->darkColor = spColor_create();
spColor_setFromFloats(data->darkColor,
toColor(dark, 0),
toColor(dark, 1),
toColor(dark, 2),
1.0f);
}
item = Json_getItem(slotMap, "attachment");
if (item) spSlotData_setAttachmentName(data, item->valueString);
item = Json_getItem(slotMap, "blend");
if (item) {
if (strcmp(item->valueString, "additive") == 0)
data->blendMode = SP_BLEND_MODE_ADDITIVE;
else if (strcmp(item->valueString, "multiply") == 0)
data->blendMode = SP_BLEND_MODE_MULTIPLY;
else if (strcmp(item->valueString, "screen") == 0)
data->blendMode = SP_BLEND_MODE_SCREEN;
}
data->visible = Json_getInt(slotMap, "visible", -1);
data->path = pathName;
skeletonData->slots[i] = data;
}
}
/* IK constraints. */
ik = Json_getItem(root, "ik");
if (ik) {
Json *constraintMap;
skeletonData->ikConstraintsCount = ik->size;
skeletonData->ikConstraints = MALLOC(spIkConstraintData *, ik->size);
for (constraintMap = ik->child, i = 0; constraintMap; constraintMap = constraintMap->next, ++i) {
const char *targetName;
spIkConstraintData *data = spIkConstraintData_create(Json_getString(constraintMap, "name", 0));
data->order = Json_getInt(constraintMap, "order", 0);
data->skinRequired = Json_getInt(constraintMap, "skin", 0) ? 1 : 0;
boneMap = Json_getItem(constraintMap, "bones");
data->bonesCount = boneMap->size;
data->bones = MALLOC(spBoneData *, boneMap->size);
for (boneMap = boneMap->child, ii = 0; boneMap; boneMap = boneMap->next, ++ii) {
data->bones[ii] = spSkeletonData_findBone(skeletonData, boneMap->valueString);
if (!data->bones[ii]) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "IK bone not found: ", boneMap->valueString);
return NULL;
}
}
targetName = Json_getString(constraintMap, "target", 0);
data->target = spSkeletonData_findBone(skeletonData, targetName);
if (!data->target) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Target bone not found: ", targetName);
return NULL;
}
data->bendDirection = Json_getInt(constraintMap, "bendPositive", 1) ? 1 : -1;
data->compress = Json_getInt(constraintMap, "compress", 0) ? 1 : 0;
data->stretch = Json_getInt(constraintMap, "stretch", 0) ? 1 : 0;
data->uniform = Json_getInt(constraintMap, "uniform", 0) ? 1 : 0;
data->mix = Json_getFloat(constraintMap, "mix", 1);
data->softness = Json_getFloat(constraintMap, "softness", 0) * self->scale;
skeletonData->ikConstraints[i] = data;
}
}
/* Transform constraints. */
transform = Json_getItem(root, "transform");
if (transform) {
Json *constraintMap;
skeletonData->transformConstraintsCount = transform->size;
skeletonData->transformConstraints = MALLOC(spTransformConstraintData *, transform->size);
for (constraintMap = transform->child, i = 0; constraintMap; constraintMap = constraintMap->next, ++i) {
const char *name;
spTransformConstraintData *data = spTransformConstraintData_create(
Json_getString(constraintMap, "name", 0));
data->order = Json_getInt(constraintMap, "order", 0);
data->skinRequired = Json_getInt(constraintMap, "skin", 0) ? 1 : 0;
boneMap = Json_getItem(constraintMap, "bones");
data->bonesCount = boneMap->size;
data->bones = MALLOC(spBoneData *, boneMap->size);
for (boneMap = boneMap->child, ii = 0; boneMap; boneMap = boneMap->next, ++ii) {
data->bones[ii] = spSkeletonData_findBone(skeletonData, boneMap->valueString);
if (!data->bones[ii]) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Transform bone not found: ", boneMap->valueString);
return NULL;
}
}
name = Json_getString(constraintMap, "target", 0);
data->target = spSkeletonData_findBone(skeletonData, name);
if (!data->target) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Target bone not found: ", name);
return NULL;
}
data->local = Json_getInt(constraintMap, "local", 0);
data->relative = Json_getInt(constraintMap, "relative", 0);
data->offsetRotation = Json_getFloat(constraintMap, "rotation", 0);
data->offsetX = Json_getFloat(constraintMap, "x", 0) * self->scale;
data->offsetY = Json_getFloat(constraintMap, "y", 0) * self->scale;
data->offsetScaleX = Json_getFloat(constraintMap, "scaleX", 0);
data->offsetScaleY = Json_getFloat(constraintMap, "scaleY", 0);
data->offsetShearY = Json_getFloat(constraintMap, "shearY", 0);
data->mixRotate = Json_getFloat(constraintMap, "mixRotate", 1);
data->mixX = Json_getFloat(constraintMap, "mixX", 1);
data->mixY = Json_getFloat(constraintMap, "mixY", data->mixX);
data->mixScaleX = Json_getFloat(constraintMap, "mixScaleX", 1);
data->mixScaleY = Json_getFloat(constraintMap, "mixScaleY", data->mixScaleX);
data->mixShearY = Json_getFloat(constraintMap, "mixShearY", 1);
skeletonData->transformConstraints[i] = data;
}
}
/* Path constraints */
pathJson = Json_getItem(root, "path");
if (pathJson) {
Json *constraintMap;
skeletonData->pathConstraintsCount = pathJson->size;
skeletonData->pathConstraints = MALLOC(spPathConstraintData *, pathJson->size);
for (constraintMap = pathJson->child, i = 0; constraintMap; constraintMap = constraintMap->next, ++i) {
const char *name;
const char *item;
spPathConstraintData *data = spPathConstraintData_create(Json_getString(constraintMap, "name", 0));
data->order = Json_getInt(constraintMap, "order", 0);
data->skinRequired = Json_getInt(constraintMap, "skin", 0) ? 1 : 0;
boneMap = Json_getItem(constraintMap, "bones");
data->bonesCount = boneMap->size;
data->bones = MALLOC(spBoneData *, boneMap->size);
for (boneMap = boneMap->child, ii = 0; boneMap; boneMap = boneMap->next, ++ii) {
data->bones[ii] = spSkeletonData_findBone(skeletonData, boneMap->valueString);
if (!data->bones[ii]) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Path bone not found: ", boneMap->valueString);
return NULL;
}
}
name = Json_getString(constraintMap, "target", 0);
data->target = spSkeletonData_findSlot(skeletonData, name);
if (!data->target) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Target slot not found: ", name);
return NULL;
}
item = Json_getString(constraintMap, "positionMode", "percent");
if (strcmp(item, "fixed") == 0) data->positionMode = SP_POSITION_MODE_FIXED;
else if (strcmp(item, "percent") == 0)
data->positionMode = SP_POSITION_MODE_PERCENT;
item = Json_getString(constraintMap, "spacingMode", "length");
if (strcmp(item, "length") == 0) data->spacingMode = SP_SPACING_MODE_LENGTH;
else if (strcmp(item, "fixed") == 0)
data->spacingMode = SP_SPACING_MODE_FIXED;
else if (strcmp(item, "percent") == 0)
data->spacingMode = SP_SPACING_MODE_PERCENT;
else
data->spacingMode = SP_SPACING_MODE_PROPORTIONAL;
item = Json_getString(constraintMap, "rotateMode", "tangent");
if (strcmp(item, "tangent") == 0) data->rotateMode = SP_ROTATE_MODE_TANGENT;
else if (strcmp(item, "chain") == 0)
data->rotateMode = SP_ROTATE_MODE_CHAIN;
else if (strcmp(item, "chainScale") == 0)
data->rotateMode = SP_ROTATE_MODE_CHAIN_SCALE;
data->offsetRotation = Json_getFloat(constraintMap, "rotation", 0);
data->position = Json_getFloat(constraintMap, "position", 0);
if (data->positionMode == SP_POSITION_MODE_FIXED) data->position *= self->scale;
data->spacing = Json_getFloat(constraintMap, "spacing", 0);
if (data->spacingMode == SP_SPACING_MODE_LENGTH || data->spacingMode == SP_SPACING_MODE_FIXED)
data->spacing *= self->scale;
data->mixRotate = Json_getFloat(constraintMap, "mixRotate", 1);
data->mixX = Json_getFloat(constraintMap, "mixX", 1);
data->mixY = Json_getFloat(constraintMap, "mixY", data->mixX);
skeletonData->pathConstraints[i] = data;
}
}
/* Physics constraints */
physics = Json_getItem(root, "physics");
if (physics) {
Json *constraintMap;
skeletonData->physicsConstraintsCount = physics->size;
skeletonData->physicsConstraints = MALLOC(spPhysicsConstraintData *, physics->size);
for (constraintMap = physics->child, i = 0; constraintMap; constraintMap = constraintMap->next, ++i) {
const char *name;
spPhysicsConstraintData *data = spPhysicsConstraintData_create(
Json_getString(constraintMap, "name", 0));
data->order = Json_getInt(constraintMap, "order", 0);
data->skinRequired = Json_getInt(constraintMap, "skin", 0);
name = Json_getString(constraintMap, "bone", 0);
data->bone = spSkeletonData_findBone(skeletonData, name);
if (!data->bone) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Physics bone not found: ", name);
return NULL;
}
data->x = Json_getFloat(constraintMap, "x", 0);
data->y = Json_getFloat(constraintMap, "y", 0);
data->rotate = Json_getFloat(constraintMap, "rotate", 0);
data->scaleX = Json_getFloat(constraintMap, "scaleX", 0);
data->shearX = Json_getFloat(constraintMap, "shearX", 0);
data->limit = Json_getFloat(constraintMap, "limit", 5000) * self->scale;
data->step = 1.0f / Json_getInt(constraintMap, "fps", 60);
data->inertia = Json_getFloat(constraintMap, "inertia", 1);
data->strength = Json_getFloat(constraintMap, "strength", 100);
data->damping = Json_getFloat(constraintMap, "damping", 1);
data->massInverse = 1.0f / Json_getFloat(constraintMap, "mass", 1);
data->wind = Json_getFloat(constraintMap, "wind", 0);
data->gravity = Json_getFloat(constraintMap, "gravity", 0);
data->mix = Json_getFloat(constraintMap, "mix", 1);
data->inertiaGlobal = Json_getInt(constraintMap, "inertiaGlobal", 0);
data->strengthGlobal = Json_getInt(constraintMap, "strengthGlobal", 0);
data->dampingGlobal = Json_getInt(constraintMap, "dampingGlobal", 0);
data->massGlobal = Json_getInt(constraintMap, "massGlobal", 0);
data->windGlobal = Json_getInt(constraintMap, "windGlobal", 0);
data->gravityGlobal = Json_getInt(constraintMap, "gravityGlobal", 0);
data->mixGlobal = Json_getInt(constraintMap, "mixGlobal", 0);
skeletonData->physicsConstraints[i] = data;
}
}
/* Skins. */
skins = Json_getItem(root, "skins");
if (skins) {
Json *skinMap;
skeletonData->skins = MALLOC(spSkin *, skins->size);
for (skinMap = skins->child, i = 0; skinMap; skinMap = skinMap->next, ++i) {
Json *attachmentsMap;
Json *curves;
Json *skinPart;
spSkin *skin = spSkin_create(Json_getString(skinMap, "name", ""));
skinPart = Json_getItem(skinMap, "bones");
if (skinPart) {
for (skinPart = skinPart->child; skinPart; skinPart = skinPart->next) {
spBoneData *bone = spSkeletonData_findBone(skeletonData, skinPart->valueString);
if (!bone) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Skin bone constraint not found: ", skinPart->valueString);
return NULL;
}
spBoneDataArray_add(skin->bones, bone);
}
}
skinPart = Json_getItem(skinMap, "ik");
if (skinPart) {
for (skinPart = skinPart->child; skinPart; skinPart = skinPart->next) {
spIkConstraintData *constraint = spSkeletonData_findIkConstraint(skeletonData,
skinPart->valueString);
if (!constraint) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Skin IK constraint not found: ", skinPart->valueString);
return NULL;
}
spIkConstraintDataArray_add(skin->ikConstraints, constraint);
}
}
skinPart = Json_getItem(skinMap, "path");
if (skinPart) {
for (skinPart = skinPart->child; skinPart; skinPart = skinPart->next) {
spPathConstraintData *constraint = spSkeletonData_findPathConstraint(skeletonData,
skinPart->valueString);
if (!constraint) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Skin path constraint not found: ", skinPart->valueString);
return NULL;
}
spPathConstraintDataArray_add(skin->pathConstraints, constraint);
}
}
skinPart = Json_getItem(skinMap, "transform");
if (skinPart) {
for (skinPart = skinPart->child; skinPart; skinPart = skinPart->next) {
spTransformConstraintData *constraint = spSkeletonData_findTransformConstraint(skeletonData,
skinPart->valueString);
if (!constraint) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Skin transform constraint not found: ",
skinPart->valueString);
return NULL;
}
spTransformConstraintDataArray_add(skin->transformConstraints, constraint);
}
}
skinPart = Json_getItem(skinMap, "physics");
if (skinPart) {
for (skinPart = skinPart->child; skinPart; skinPart = skinPart->next) {
spPhysicsConstraintData *constraint = spSkeletonData_findPhysicsConstraint(skeletonData,
skinPart->valueString);
if (!constraint) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Skin physics constraint not found: ", skinPart->valueString);
return NULL;
}
spPhysicsConstraintDataArray_add(skin->physicsConstraints, constraint);
}
}
skeletonData->skins[skeletonData->skinsCount++] = skin;
if (strcmp(skin->name, "default") == 0) skeletonData->defaultSkin = skin;
skinPart = Json_getItem(skinMap, "attachments");
if (skinPart) {
for (attachmentsMap = skinPart->child; attachmentsMap; attachmentsMap = attachmentsMap->next) {
spSlotData *slot = spSkeletonData_findSlot(skeletonData, attachmentsMap->name);
Json *attachmentMap;
for (attachmentMap = attachmentsMap->child; attachmentMap; attachmentMap = attachmentMap->next) {
spAttachment *attachment;
const char *skinAttachmentName = attachmentMap->name;
const char *attachmentName = Json_getString(attachmentMap, "name", skinAttachmentName);
const char *path = Json_getString(attachmentMap, "path", attachmentName);
const char *color;
Json *entry;
spSequence *sequence;
const char *typeString = Json_getString(attachmentMap, "type", "region");
spAttachmentType type;
if (strcmp(typeString, "region") == 0) type = SP_ATTACHMENT_REGION;
else if (strcmp(typeString, "mesh") == 0)
type = SP_ATTACHMENT_MESH;
else if (strcmp(typeString, "linkedmesh") == 0)
type = SP_ATTACHMENT_LINKED_MESH;
else if (strcmp(typeString, "boundingbox") == 0)
type = SP_ATTACHMENT_BOUNDING_BOX;
else if (strcmp(typeString, "path") == 0)
type = SP_ATTACHMENT_PATH;
else if (strcmp(typeString, "clipping") == 0)
type = SP_ATTACHMENT_CLIPPING;
else if (strcmp(typeString, "point") == 0)
type = SP_ATTACHMENT_POINT;
else {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, "Unknown attachment type: ", typeString);
return NULL;
}
sequence = readSequence(Json_getItem(attachmentMap, "sequence"));
attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type,
attachmentName,
path, sequence);
if (!attachment) {
if (self->attachmentLoader->error1) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, root, self->attachmentLoader->error1,
self->attachmentLoader->error2);
return NULL;
}
continue;
}
switch (attachment->type) {
case SP_ATTACHMENT_REGION: {
spRegionAttachment *region = SUB_CAST(spRegionAttachment, attachment);
if (path) MALLOC_STR(region->path, path);
region->x = Json_getFloat(attachmentMap, "x", 0) * self->scale;
region->y = Json_getFloat(attachmentMap, "y", 0) * self->scale;
region->scaleX = Json_getFloat(attachmentMap, "scaleX", 1);
region->scaleY = Json_getFloat(attachmentMap, "scaleY", 1);
region->rotation = Json_getFloat(attachmentMap, "rotation", 0);
region->width = Json_getFloat(attachmentMap, "width", 32) * self->scale;
region->height = Json_getFloat(attachmentMap, "height", 32) * self->scale;
region->sequence = sequence;
color = Json_getString(attachmentMap, "color", 0);
if (color) {
spColor_setFromFloats(&region->color,
toColor(color, 0),
toColor(color, 1),
toColor(color, 2),
toColor(color, 3));
}
if (region->region != NULL) spRegionAttachment_updateRegion(region);
spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
break;
}
case SP_ATTACHMENT_MESH:
case SP_ATTACHMENT_LINKED_MESH: {
spMeshAttachment *mesh = SUB_CAST(spMeshAttachment, attachment);
MALLOC_STR(mesh->path, path);
color = Json_getString(attachmentMap, "color", 0);
if (color) {
spColor_setFromFloats(&mesh->color,
toColor(color, 0),
toColor(color, 1),
toColor(color, 2),
toColor(color, 3));
}
mesh->width = Json_getFloat(attachmentMap, "width", 32) * self->scale;
mesh->height = Json_getFloat(attachmentMap, "height", 32) * self->scale;
mesh->sequence = sequence;
entry = Json_getItem(attachmentMap, "parent");
if (!entry) {
int verticesLength;
entry = Json_getItem(attachmentMap, "triangles");
mesh->trianglesCount = entry->size;
mesh->triangles = MALLOC(unsigned short, entry->size);
for (entry = entry->child, ii = 0; entry; entry = entry->next, ++ii)
mesh->triangles[ii] = (unsigned short) entry->valueInt;
entry = Json_getItem(attachmentMap, "uvs");
verticesLength = entry->size;
mesh->regionUVs = MALLOC(float, verticesLength);
for (entry = entry->child, ii = 0; entry; entry = entry->next, ++ii)
mesh->regionUVs[ii] = entry->valueFloat;
_readVertices(self, attachmentMap, SUPER(mesh), verticesLength);
if (mesh->region != NULL) spMeshAttachment_updateRegion(mesh);
mesh->hullLength = Json_getInt(attachmentMap, "hull", 0);
entry = Json_getItem(attachmentMap, "edges");
if (entry) {
mesh->edgesCount = entry->size;
mesh->edges = MALLOC(unsigned short, entry->size);
for (entry = entry->child, ii = 0; entry; entry = entry->next, ++ii)
mesh->edges[ii] = (unsigned short) entry->valueInt;
}
spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
} else {
int inheritTimelines = Json_getInt(attachmentMap, "timelines", 1);
_spSkeletonJson_addLinkedMesh(self, SUB_CAST(spMeshAttachment, attachment),
Json_getString(attachmentMap, "skin", 0), slot->index,
entry->valueString, inheritTimelines);
}
break;
}
case SP_ATTACHMENT_BOUNDING_BOX: {
spBoundingBoxAttachment *box = SUB_CAST(spBoundingBoxAttachment, attachment);
int vertexCount = Json_getInt(attachmentMap, "vertexCount", 0) << 1;
_readVertices(self, attachmentMap, SUPER(box), vertexCount);
box->super.verticesCount = vertexCount;
color = Json_getString(attachmentMap, "color", 0);
if (color) {
spColor_setFromFloats(&box->color,
toColor(color, 0),
toColor(color, 1),
toColor(color, 2),
toColor(color, 3));
}
spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
break;
}
case SP_ATTACHMENT_PATH: {
spPathAttachment *pathAttachment = SUB_CAST(spPathAttachment, attachment);
int vertexCount = 0;
pathAttachment->closed = Json_getInt(attachmentMap, "closed", 0);
pathAttachment->constantSpeed = Json_getInt(attachmentMap, "constantSpeed", 1);
vertexCount = Json_getInt(attachmentMap, "vertexCount", 0);
_readVertices(self, attachmentMap, SUPER(pathAttachment), vertexCount << 1);
pathAttachment->lengthsLength = vertexCount / 3;
pathAttachment->lengths = MALLOC(float, pathAttachment->lengthsLength);
curves = Json_getItem(attachmentMap, "lengths");
for (curves = curves->child, ii = 0; curves; curves = curves->next, ++ii)
pathAttachment->lengths[ii] = curves->valueFloat * self->scale;
color = Json_getString(attachmentMap, "color", 0);
if (color) {
spColor_setFromFloats(&pathAttachment->color,
toColor(color, 0),
toColor(color, 1),
toColor(color, 2),
toColor(color, 3));
}
break;
}
case SP_ATTACHMENT_POINT: {
spPointAttachment *point = SUB_CAST(spPointAttachment, attachment);
point->x = Json_getFloat(attachmentMap, "x", 0) * self->scale;
point->y = Json_getFloat(attachmentMap, "y", 0) * self->scale;
point->rotation = Json_getFloat(attachmentMap, "rotation", 0);
color = Json_getString(attachmentMap, "color", 0);
if (color) {
spColor_setFromFloats(&point->color,
toColor(color, 0),
toColor(color, 1),
toColor(color, 2),
toColor(color, 3));
}
break;
}
case SP_ATTACHMENT_CLIPPING: {
spClippingAttachment *clip = SUB_CAST(spClippingAttachment, attachment);
int vertexCount = 0;
const char *end = Json_getString(attachmentMap, "end", 0);
if (end) {
spSlotData *endSlot = spSkeletonData_findSlot(skeletonData, end);
clip->endSlot = endSlot;
}
vertexCount = Json_getInt(attachmentMap, "vertexCount", 0) << 1;
_readVertices(self, attachmentMap, SUPER(clip), vertexCount);
color = Json_getString(attachmentMap, "color", 0);
if (color) {
spColor_setFromFloats(&clip->color,
toColor(color, 0),
toColor(color, 1),
toColor(color, 2),
toColor(color, 3));
}
spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
break;
}
}
spSkin_setAttachment(skin, slot->index, skinAttachmentName, attachment);
}
}
}
}
}
/* Linked meshes. */
for (i = 0; i < internal->linkedMeshCount; ++i) {
spAttachment *parent;
_spLinkedMesh *linkedMesh = internal->linkedMeshes + i;
spSkin *skin = !linkedMesh->skin ? skeletonData->defaultSkin : spSkeletonData_findSkin(skeletonData, linkedMesh->skin);
if (!skin) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, 0, "Skin not found: ", linkedMesh->skin);
return NULL;
}
parent = spSkin_getAttachment(skin, linkedMesh->slotIndex, linkedMesh->parent);
if (!parent) {
spSkeletonData_dispose(skeletonData);
_spSkeletonJson_setError(self, 0, "Parent mesh not found: ", linkedMesh->parent);
return NULL;
}
linkedMesh->mesh->super.timelineAttachment = linkedMesh->inheritTimeline ? parent
: SUPER(SUPER(linkedMesh->mesh));
spMeshAttachment_setParentMesh(linkedMesh->mesh, SUB_CAST(spMeshAttachment, parent));
if (linkedMesh->mesh->region != NULL) spMeshAttachment_updateRegion(linkedMesh->mesh);
spAttachmentLoader_configureAttachment(self->attachmentLoader, SUPER(SUPER(linkedMesh->mesh)));
}
/* Events. */
events = Json_getItem(root, "events");
if (events) {
Json *eventMap;
const char *stringValue;
const char *audioPath;
skeletonData->eventsCount = events->size;
skeletonData->events = MALLOC(spEventData *, events->size);
for (eventMap = events->child, i = 0; eventMap; eventMap = eventMap->next, ++i) {
spEventData *eventData = spEventData_create(eventMap->name);
eventData->intValue = Json_getInt(eventMap, "int", 0);
eventData->floatValue = Json_getFloat(eventMap, "float", 0);
stringValue = Json_getString(eventMap, "string", 0);
if (stringValue) MALLOC_STR(eventData->stringValue, stringValue);
audioPath = Json_getString(eventMap, "audio", 0);
if (audioPath) {
MALLOC_STR(eventData->audioPath, audioPath);
eventData->volume = Json_getFloat(eventMap, "volume", 1);
eventData->balance = Json_getFloat(eventMap, "balance", 0);
}
skeletonData->events[i] = eventData;
}
}
/* Animations. */
animations = Json_getItem(root, "animations");
if (animations) {
Json *animationMap;
skeletonData->animations = MALLOC(spAnimation *, animations->size);
for (animationMap = animations->child; animationMap; animationMap = animationMap->next) {
spAnimation *animation = _spSkeletonJson_readAnimation(self, animationMap, skeletonData);
if (!animation) {
spSkeletonData_dispose(skeletonData);
return NULL;
}
skeletonData->animations[skeletonData->animationsCount++] = animation;
}
}
Json_dispose(root);
return skeletonData;
}