Browse Source

[c][cpp] Spotless formatter

badlogic 4 years ago
parent
commit
87658c7d31
100 changed files with 15850 additions and 15767 deletions
  1. 66 0
      .clang-format
  2. 9 0
      build.gradle
  3. 0 0
      formatters/README.md
  4. 66 0
      formatters/cpp.clang-format
  5. 2633 2673
      spine-c/spine-c/src/spine/Animation.c
  6. 1104 1113
      spine-c/spine-c/src/spine/AnimationState.c
  7. 151 151
      spine-c/spine-c/src/spine/AnimationStateData.c
  8. 2 2
      spine-c/spine-c/src/spine/Array.c
  9. 464 462
      spine-c/spine-c/src/spine/Atlas.c
  10. 99 99
      spine-c/spine-c/src/spine/AtlasAttachmentLoader.c
  11. 65 65
      spine-c/spine-c/src/spine/Attachment.c
  12. 101 102
      spine-c/spine-c/src/spine/AttachmentLoader.c
  13. 294 294
      spine-c/spine-c/src/spine/Bone.c
  14. 47 47
      spine-c/spine-c/src/spine/BoneData.c
  15. 54 54
      spine-c/spine-c/src/spine/BoundingBoxAttachment.c
  16. 56 56
      spine-c/spine-c/src/spine/ClippingAttachment.c
  17. 8 4
      spine-c/spine-c/src/spine/Color.c
  18. 2 3
      spine-c/spine-c/src/spine/Debug.c
  19. 43 43
      spine-c/spine-c/src/spine/Event.c
  20. 44 44
      spine-c/spine-c/src/spine/EventData.c
  21. 297 297
      spine-c/spine-c/src/spine/IkConstraint.c
  22. 48 48
      spine-c/spine-c/src/spine/IkConstraintData.c
  23. 8 7
      spine-c/spine-c/src/spine/Json.c
  24. 210 210
      spine-c/spine-c/src/spine/MeshAttachment.c
  25. 59 59
      spine-c/spine-c/src/spine/PathAttachment.c
  26. 527 527
      spine-c/spine-c/src/spine/PathConstraint.c
  27. 43 43
      spine-c/spine-c/src/spine/PathConstraintData.c
  28. 161 154
      spine-c/spine-c/src/spine/RegionAttachment.c
  29. 649 646
      spine-c/spine-c/src/spine/Skeleton.c
  30. 1465 1466
      spine-c/spine-c/src/spine/SkeletonBinary.c
  31. 210 214
      spine-c/spine-c/src/spine/SkeletonBounds.c
  32. 5 3
      spine-c/spine-c/src/spine/SkeletonClipping.c
  33. 175 175
      spine-c/spine-c/src/spine/SkeletonData.c
  34. 1538 1522
      spine-c/spine-c/src/spine/SkeletonJson.c
  35. 281 283
      spine-c/spine-c/src/spine/Skin.c
  36. 99 101
      spine-c/spine-c/src/spine/Slot.c
  37. 55 55
      spine-c/spine-c/src/spine/SlotData.c
  38. 259 255
      spine-c/spine-c/src/spine/TransformConstraint.c
  39. 43 43
      spine-c/spine-c/src/spine/TransformConstraintData.c
  40. 5 3
      spine-c/spine-c/src/spine/Triangulator.c
  41. 142 143
      spine-c/spine-c/src/spine/VertexAttachment.c
  42. 136 136
      spine-c/spine-c/src/spine/extension.c
  43. 6 6
      spine-cocos2dx/example-v4/proj.android/app/jni/hellocpp/main.cpp
  44. 1 3
      spine-cocos2dx/example-v4/proj.ios_mac/ios/AppController.h
  45. 1 2
      spine-cocos2dx/example-v4/proj.ios_mac/ios/RootViewController.h
  46. 3 4
      spine-cocos2dx/example-v4/proj.ios_mac/mac/main.cpp
  47. 6 7
      spine-cocos2dx/example-v4/proj.linux/main.cpp
  48. 8 9
      spine-cocos2dx/example-v4/proj.win32/main.cpp
  49. 3 3
      spine-cocos2dx/example-v4/proj.win32/main.h
  50. 8 8
      spine-cocos2dx/example-v4/proj.win32/resource.h
  51. 135 135
      spine-cocos2dx/example/Classes/AppDelegate.cpp
  52. 45 45
      spine-cocos2dx/example/Classes/AppDelegate.h
  53. 88 88
      spine-cocos2dx/example/Classes/AppMacros.h
  54. 114 115
      spine-cocos2dx/example/Classes/BatchingExample.cpp
  55. 54 54
      spine-cocos2dx/example/Classes/BatchingExample.h
  56. 66 66
      spine-cocos2dx/example/Classes/CoinExample.cpp
  57. 48 48
      spine-cocos2dx/example/Classes/CoinExample.h
  58. 67 67
      spine-cocos2dx/example/Classes/GoblinsExample.cpp
  59. 48 48
      spine-cocos2dx/example/Classes/GoblinsExample.h
  60. 31 32
      spine-cocos2dx/example/Classes/IKExample.cpp
  61. 6 6
      spine-cocos2dx/example/Classes/IKExample.h
  62. 6 6
      spine-cocos2dx/example/Classes/MixAndMatchExample.cpp
  63. 6 6
      spine-cocos2dx/example/Classes/MixAndMatchExample.h
  64. 86 86
      spine-cocos2dx/example/Classes/RaptorExample.cpp
  65. 51 51
      spine-cocos2dx/example/Classes/RaptorExample.h
  66. 94 94
      spine-cocos2dx/example/Classes/SkeletonRendererSeparatorExample.cpp
  67. 52 53
      spine-cocos2dx/example/Classes/SkeletonRendererSeparatorExample.h
  68. 102 102
      spine-cocos2dx/example/Classes/SpineboyExample.cpp
  69. 50 50
      spine-cocos2dx/example/Classes/SpineboyExample.h
  70. 66 66
      spine-cocos2dx/example/Classes/TankExample.cpp
  71. 48 48
      spine-cocos2dx/example/Classes/TankExample.h
  72. 6 6
      spine-cocos2dx/example/proj.android/jni/hellocpp/main.cpp
  73. 2 3
      spine-cocos2dx/example/proj.ios_mac/ios/AppController.h
  74. 1 2
      spine-cocos2dx/example/proj.ios_mac/ios/RootViewController.h
  75. 3 4
      spine-cocos2dx/example/proj.ios_mac/mac/main.cpp
  76. 6 7
      spine-cocos2dx/example/proj.linux/main.cpp
  77. 8 9
      spine-cocos2dx/example/proj.win32/main.cpp
  78. 3 3
      spine-cocos2dx/example/proj.win32/main.h
  79. 8 8
      spine-cocos2dx/example/proj.win32/resource.h
  80. 53 53
      spine-cocos2dx/src/spine/AttachmentVertices.cpp
  81. 48 48
      spine-cocos2dx/src/spine/AttachmentVertices.h
  82. 327 327
      spine-cocos2dx/src/spine/SkeletonAnimation.cpp
  83. 133 136
      spine-cocos2dx/src/spine/SkeletonAnimation.h
  84. 860 866
      spine-cocos2dx/src/spine/SkeletonRenderer.cpp
  85. 48 49
      spine-cocos2dx/src/spine/SkeletonRenderer.h
  86. 189 189
      spine-cocos2dx/src/spine/spine-cocos2dx.cpp
  87. 78 78
      spine-cocos2dx/src/spine/spine-cocos2dx.h
  88. 95 94
      spine-cocos2dx/src/spine/v3/SkeletonBatch.cpp
  89. 13 13
      spine-cocos2dx/src/spine/v3/SkeletonBatch.h
  90. 262 265
      spine-cocos2dx/src/spine/v3/SkeletonTwoColorBatch.cpp
  91. 35 35
      spine-cocos2dx/src/spine/v3/SkeletonTwoColorBatch.h
  92. 139 140
      spine-cocos2dx/src/spine/v4/SkeletonBatch.cpp
  93. 42 42
      spine-cocos2dx/src/spine/v4/SkeletonBatch.h
  94. 322 335
      spine-cocos2dx/src/spine/v4/SkeletonTwoColorBatch.cpp
  95. 65 65
      spine-cocos2dx/src/spine/v4/SkeletonTwoColorBatch.h
  96. 15 14
      spine-cpp/spine-cpp-unit-tests/src/main.cpp
  97. 7 9
      spine-cpp/spine-cpp/src/spine/Animation.cpp
  98. 54 50
      spine-cpp/spine-cpp/src/spine/AnimationState.cpp
  99. 1 1
      spine-cpp/spine-cpp/src/spine/AnimationStateData.cpp
  100. 5 6
      spine-cpp/spine-cpp/src/spine/Atlas.cpp

+ 66 - 0
.clang-format

@@ -0,0 +1,66 @@
+# Generated from CLion C/C++ Code Style settings
+BasedOnStyle: LLVM
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: None
+AlignOperands: Align
+AllowAllArgumentsOnNextLine: false
+AllowAllConstructorInitializersOnNextLine: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: Always
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: Always
+AllowShortLambdasOnASingleLine: All
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterReturnType: None
+AlwaysBreakTemplateDeclarations: Yes
+BreakBeforeBraces: Custom
+BraceWrapping:
+  AfterCaseLabel: false
+  AfterClass: false
+  AfterControlStatement: Never
+  AfterEnum: false
+  AfterFunction: false
+  AfterNamespace: false
+  AfterUnion: false
+  BeforeCatch: false
+  BeforeElse: false
+  IndentBraces: false
+  SplitEmptyFunction: false
+  SplitEmptyRecord: true
+BreakBeforeBinaryOperators: None
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: BeforeColon
+BreakInheritanceList: BeforeColon
+ColumnLimit: 0
+CompactNamespaces: false
+ContinuationIndentWidth: 8
+IndentCaseLabels: true
+IndentPPDirectives: None
+IndentWidth: 4
+KeepEmptyLinesAtTheStartOfBlocks: true
+MaxEmptyLinesToKeep: 2
+NamespaceIndentation: All
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PointerAlignment: Right
+ReflowComments: false
+SpaceAfterCStyleCast: true
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 0
+SpacesInAngles: false
+SpacesInCStyleCastParentheses: false
+SpacesInContainerLiterals: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+TabWidth: 4
+UseTab: ForContinuationAndIndentation

+ 9 - 0
build.gradle

@@ -11,4 +11,13 @@ spotless {
         target 'spine-libgdx/**/*.java'
         eclipse().configFile('formatters/eclipse-formatter.xml')
     }
+
+    cpp {
+        target 'spine-c/**/*.c', 'spine-c/**/.h',
+               'spine-cpp/**/*.cpp', 'spine-cpp/**/.h',
+               'spine-cocos2dx/**/*.cpp', 'spine-cocos2dx/**/*.h',
+               'spine-sfml/**/*.c', 'spine-sfml/**/*.cpp', 'spine-sfml/**/*.h',
+               'spine-ue4/**/*.cpp', 'spine-ue4/**/*.h'
+        clangFormat('12.0.0').style('file')
+    }
 }

+ 0 - 0
formatters/README.md


+ 66 - 0
formatters/cpp.clang-format

@@ -0,0 +1,66 @@
+# Generated from CLion C/C++ Code Style settings
+BasedOnStyle: LLVM
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: None
+AlignOperands: Align
+AllowAllArgumentsOnNextLine: false
+AllowAllConstructorInitializersOnNextLine: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: Always
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: Always
+AllowShortLambdasOnASingleLine: All
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterReturnType: None
+AlwaysBreakTemplateDeclarations: Yes
+BreakBeforeBraces: Custom
+BraceWrapping:
+  AfterCaseLabel: false
+  AfterClass: false
+  AfterControlStatement: Never
+  AfterEnum: false
+  AfterFunction: false
+  AfterNamespace: false
+  AfterUnion: false
+  BeforeCatch: false
+  BeforeElse: false
+  IndentBraces: false
+  SplitEmptyFunction: false
+  SplitEmptyRecord: true
+BreakBeforeBinaryOperators: None
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: BeforeColon
+BreakInheritanceList: BeforeColon
+ColumnLimit: 0
+CompactNamespaces: false
+ContinuationIndentWidth: 8
+IndentCaseLabels: true
+IndentPPDirectives: None
+IndentWidth: 4
+KeepEmptyLinesAtTheStartOfBlocks: true
+MaxEmptyLinesToKeep: 2
+NamespaceIndentation: All
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PointerAlignment: Right
+ReflowComments: false
+SpaceAfterCStyleCast: true
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 0
+SpacesInAngles: false
+SpacesInCStyleCastParentheses: false
+SpacesInContainerLiterals: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+TabWidth: 4
+UseTab: ForContinuationAndIndentation

+ 2633 - 2673
spine-c/spine-c/src/spine/Animation.c

@@ -1,2673 +1,2633 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/Animation.h>
-#include <spine/IkConstraint.h>
-#include <limits.h>
-#include <spine/extension.h>
-
-_SP_ARRAY_IMPLEMENT_TYPE(spPropertyIdArray, spPropertyId)
-
-_SP_ARRAY_IMPLEMENT_TYPE(spTimelineArray, spTimeline*)
-
-spAnimation *spAnimation_create(const char *name, spTimelineArray *timelines, float duration) {
-	int i, n;
-	spAnimation *self = NEW(spAnimation);
-	MALLOC_STR(self->name, name);
-	self->timelines = timelines != NULL ? timelines : spTimelineArray_create(1);
-	timelines = self->timelines;
-	self->timelineIds = spPropertyIdArray_create(16);
-	for (i = 0, n = timelines->size; i < n; i++) {
-		spPropertyIdArray_addAllValues(self->timelineIds, timelines->items[i]->propertyIds, 0,
-									   timelines->items[i]->propertyIdsCount);
-	}
-	self->duration = duration;
-	return self;
-}
-
-void spAnimation_dispose(spAnimation *self) {
-	int i;
-	for (i = 0; i < self->timelines->size; ++i)
-		spTimeline_dispose(self->timelines->items[i]);
-	spTimelineArray_dispose(self->timelines);
-	spPropertyIdArray_dispose(self->timelineIds);
-	FREE(self->name);
-	FREE(self);
-}
-
-int /*bool*/ spAnimation_hasTimeline(spAnimation *self, spPropertyId *ids, int idsCount) {
-	int i, n, ii;
-	for (i = 0, n = self->timelineIds->size; i < n; i++) {
-		for (ii = 0; ii < idsCount; ii++) {
-			if (self->timelineIds->items[i] == ids[ii]) return 1;
-		}
-	}
-	return 0;
-}
-
-void
-spAnimation_apply(const spAnimation *self, spSkeleton *skeleton, float lastTime, float time, int loop, spEvent **events,
-				  int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction
-) {
-	int i, n = self->timelines->size;
-
-	if (loop && self->duration) {
-		time = FMOD(time, self->duration);
-		if (lastTime > 0) lastTime = FMOD(lastTime, self->duration);
-	}
-
-	for (i = 0; i < n; ++i)
-		spTimeline_apply(self->timelines->items[i], skeleton, lastTime, time, events, eventsCount, alpha, blend,
-						 direction);
-}
-
-static search (spFloatArray
-*values,
-float time
-) {
-int i, n;
-float *items = values->items;
-for (
-i = 1, n = values->size;
-i<n;
-i++)
-if (items[i] > time) return i - 1;
-return values->size - 1;
-}
-
-static search2 (spFloatArray
-*values,
-float time,
-int step
-) {
-int i, n;
-float *items = values->items;
-for (
-i = step, n = values->size;
-i<n;
-i += step)
-if (items[i] > time) return i -
-step;
-return values->size -
-step;
-}
-
-/**/
-
-void _spTimeline_init(spTimeline *self,
-					  int frameCount,
-					  int frameEntries,
-					  spPropertyId *propertyIds,
-					  int propertyIdsCount,
-					  spTimelineType type,
-					  void (*dispose)(spTimeline *self),
-					  void (*apply)(spTimeline *self, spSkeleton *skeleton, float lastTime, float time,
-									spEvent **firedEvents,
-									int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction),
-					  void (*setBezier)(spTimeline *self, int bezier, int frame, float value, float time1, float value1,
-										float cx1, float cy1,
-										float cx2, float cy2, float time2, float value2)
-) {
-	int i;
-	self->frames = spFloatArray_create(frameCount * frameEntries);
-	self->frames->size = frameCount * frameEntries;
-	self->frameCount = frameCount;
-	self->frameEntries = frameEntries;
-
-	for (i = 0; i < propertyIdsCount; i++)
-		self->propertyIds[i] = propertyIds[i];
-	self->propertyIdsCount = propertyIdsCount;
-
-	self->type = type;
-
-	self->vtable.dispose = dispose;
-	self->vtable.apply = apply;
-	self->vtable.setBezier = setBezier;
-}
-
-void spTimeline_dispose(spTimeline *self) {
-	self->vtable.dispose(self);
-	spFloatArray_dispose(self->frames);
-	FREE(self);
-}
-
-void spTimeline_apply(spTimeline *self, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
-					  int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
-	self->vtable.apply(self, skeleton, lastTime, time, firedEvents, eventsCount, alpha, blend, direction);
-}
-
-void spTimeline_setBezier(spTimeline *self, int bezier, int frame, float value, float time1, float value1, float cx1,
-						  float cy1, float cx2, float cy2, float time2, float value2) {
-	if (self->vtable.setBezier)
-		self->vtable.setBezier(self, bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
-}
-
-float spTimeline_getDuration(const spTimeline *self) {
-	return self->frames->items[self->frames->size - self->frameEntries];
-}
-
-/**/
-
-#define CURVE_LINEAR 0
-#define CURVE_STEPPED 1
-#define CURVE_BEZIER 2
-#define BEZIER_SIZE 18
-
-void _spCurveTimeline_init(spCurveTimeline *self,
-						   int frameCount,
-						   int frameEntries,
-						   int bezierCount,
-						   spPropertyId *propertyIds,
-						   int propertyIdsCount,
-						   spTimelineType type,
-						   void (*dispose)(spTimeline *self),
-						   void (*apply)(spTimeline *self, spSkeleton *skeleton, float lastTime, float time,
-										 spEvent **firedEvents,
-										 int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction),
-						   void (*setBezier)(spTimeline *self, int bezier, int frame, float value, float time1,
-											 float value1, float cx1, float cy1,
-											 float cx2, float cy2, float time2, float value2)) {
-	_spTimeline_init(SUPER(self), frameCount, frameEntries, propertyIds, propertyIdsCount, type, dispose, apply,
-					 setBezier);
-	self->curves = spFloatArray_create(frameCount + bezierCount * BEZIER_SIZE);
-	self->curves->size = frameCount + bezierCount * BEZIER_SIZE;
-	self->curves->items[frameCount - 1] = CURVE_STEPPED;
-}
-
-void _spCurveTimeline_dispose(spTimeline *self) {
-	spFloatArray_dispose(SUB_CAST(spCurveTimeline, self)->curves);
-}
-
-void _spCurveTimeline_setBezier(spTimeline *timeline, int bezier, int frame, float value, float time1, float value1,
-								float cx1, float cy1, float cx2, float cy2, float time2, float value2) {
-	spCurveTimeline *self = SUB_CAST(spCurveTimeline, timeline);
-	float tmpx, tmpy, dddx, dddy, ddx, ddy, dx, dy, x, y;
-	int i = self->super.frameCount + bezier * BEZIER_SIZE, n;
-	float *curves = self->curves->items;
-	if (value == 0) curves[frame] = CURVE_BEZIER + i;
-	tmpx = (time1 - cx1 * 2 + cx2) * 0.03;
-	tmpy = (value1 - cy1 * 2 + cy2) * 0.03;
-	dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006;
-	dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006;
-	ddx = tmpx * 2 + dddx;
-	ddy = tmpy * 2 + dddy;
-	dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667;
-	dy = (cy1 - value1) * 0.3 + tmpy + dddy * 0.16666667;
-	x = time1 + dx, y = value1 + dy;
-	for (n = i + BEZIER_SIZE; i < n; i += 2) {
-		curves[i] = x;
-		curves[i + 1] = y;
-		dx += ddx;
-		dy += ddy;
-		ddx += dddx;
-		ddy += dddy;
-		x += dx;
-		y += dy;
-	}
-}
-
-float _spCurveTimeline_getBezierValue(spCurveTimeline *self, float time, int frameIndex, int valueOffset, int i) {
-	float *curves = self->curves->items;
-	float *frames = SUPER(self)->frames->items;
-	float x, y;
-	int n;
-	if (curves[i] > time) {
-		x = frames[frameIndex];
-		y = frames[frameIndex + valueOffset];
-		return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
-	}
-	n = i + BEZIER_SIZE;
-	for (i += 2; i < n; i += 2) {
-		if (curves[i] >= time) {
-			x = curves[i - 2];
-			y = curves[i - 1];
-			return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
-		}
-	}
-	frameIndex += self->super.frameEntries;
-	x = curves[n - 2];
-	y = curves[n - 1];
-	return y + (time - x) / (frames[frameIndex] - x) * (frames[frameIndex + valueOffset] - y);
-}
-
-void spCurveTimeline_setLinear(spCurveTimeline *self, int frame) {
-	self->curves->items[frame] = CURVE_LINEAR;
-}
-
-void spCurveTimeline_setStepped(spCurveTimeline *self, int frame) {
-	self->curves->items[frame] = CURVE_STEPPED;
-}
-
-#define CURVE1_ENTRIES 2
-#define CURVE1_VALUE 1
-
-void spCurveTimeline1_setFrame(spCurveTimeline1 *self, int frame, float time, float value) {
-	float *frames = self->super.frames->items;
-	frame <<= 1;
-	frames[frame] = time;
-	frames[frame + CURVE1_VALUE] = value;
-}
-
-float spCurveTimeline1_getCurveValue(spCurveTimeline1 *self, float time) {
-	float *frames = self->super.frames->items;
-	float *curves = self->curves->items;
-	int i = self->super.frames->size - 2;
-	int ii, curveType;
-	for (ii = 2; ii <= i; ii += 2) {
-		if (frames[ii] > time) {
-			i = ii - 2;
-			break;
-		}
-	}
-
-	curveType = (int) curves[i >> 1];
-	switch (curveType) {
-		case CURVE_LINEAR: {
-			float before = frames[i], value = frames[i + CURVE1_VALUE];
-			return value + (time - before) / (frames[i + CURVE1_ENTRIES] - before) *
-						   (frames[i + CURVE1_ENTRIES + CURVE1_VALUE] - value);
-		}
-		case CURVE_STEPPED:
-			return frames[i + CURVE1_VALUE];
-	}
-	return _spCurveTimeline_getBezierValue(self, time, i, CURVE1_VALUE, curveType - CURVE_BEZIER);
-}
-
-#define CURVE2_ENTRIES 3
-#define CURVE2_VALUE1 1
-#define CURVE2_VALUE2 2
-
-SP_API void spCurveTimeline2_setFrame(spCurveTimeline1 *self, int frame, float time, float value1, float value2) {
-	float *frames = self->super.frames->items;
-	frame *= CURVE2_ENTRIES;
-	frames[frame] = time;
-	frames[frame + CURVE2_VALUE1] = value1;
-	frames[frame + CURVE2_VALUE2] = value2;
-}
-
-/**/
-
-void
-_spRotateTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
-						int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction
-) {
-	spBone *bone;
-	float r;
-	spRotateTimeline *self = SUB_CAST(spRotateTimeline, timeline);
-	float *frames = self->super.super.frames->items;
-
-	bone = skeleton->bones[self->boneIndex];
-	if (!bone->active) return;
-
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				bone->rotation = bone->data->rotation;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				bone->rotation += (bone->data->rotation - bone->rotation) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	r = spCurveTimeline1_getCurveValue(SUPER(self), time);
-	switch (blend) {
-		case SP_MIX_BLEND_SETUP:
-			bone->rotation = bone->data->rotation + r * alpha;
-			break;
-		case SP_MIX_BLEND_FIRST:
-		case SP_MIX_BLEND_REPLACE:
-			r += bone->data->rotation - bone->rotation;
-		case SP_MIX_BLEND_ADD:
-			bone->rotation += r * alpha;
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spRotateTimeline *spRotateTimeline_create(int frameCount, int bezierCount, int boneIndex) {
-	spRotateTimeline *timeline = NEW(spRotateTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_ROTATE << 32) | boneIndex;
-	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_ROTATE,
-						  _spCurveTimeline_dispose, _spRotateTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->boneIndex = boneIndex;
-	return timeline;
-}
-
-void spRotateTimeline_setFrame(spRotateTimeline *self, int frame, float time, float degrees) {
-	spCurveTimeline1_setFrame(SUPER(self), frame, time, degrees);
-}
-
-/**/
-
-void _spTranslateTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-								spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-								spMixDirection direction
-) {
-	spBone *bone;
-	float x, y, t;
-	int i, curveType;
-
-	spTranslateTimeline *self = SUB_CAST(spTranslateTimeline, timeline);
-	float *frames = self->super.super.frames->items;
-	float *curves = self->super.curves->items;
-
-	bone = skeleton->bones[self->boneIndex];
-	if (!bone->active) return;
-
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				bone->x = bone->data->x;
-				bone->y = bone->data->y;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				bone->x += (bone->data->x - bone->x) * alpha;
-				bone->y += (bone->data->y - bone->y) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	i = search2(self->super.super.frames, time, CURVE2_ENTRIES);
-	curveType = (int) curves[i / CURVE2_ENTRIES];
-	switch (curveType) {
-		case CURVE_LINEAR: {
-			float before = frames[i];
-			x = frames[i + CURVE2_VALUE1];
-			y = frames[i + CURVE2_VALUE2];
-			t = (time - before) / (frames[i + CURVE2_ENTRIES] - before);
-			x += (frames[i + CURVE2_ENTRIES + CURVE2_VALUE1] - x) * t;
-			y += (frames[i + CURVE2_ENTRIES + CURVE2_VALUE2] - y) * t;
-			break;
-		}
-		case CURVE_STEPPED: {
-			x = frames[i + CURVE2_VALUE1];
-			y = frames[i + CURVE2_VALUE2];
-			break;
-		}
-		default: {
-			x = _spCurveTimeline_getBezierValue(SUPER(self), time, i, CURVE2_VALUE1, curveType - CURVE_BEZIER);
-			y = _spCurveTimeline_getBezierValue(SUPER(self), time, i, CURVE2_VALUE2,
-												curveType + BEZIER_SIZE - CURVE_BEZIER);
-		}
-	}
-
-	switch (blend) {
-		case SP_MIX_BLEND_SETUP:
-			bone->x = bone->data->x + x * alpha;
-			bone->y = bone->data->y + y * alpha;
-			break;
-		case SP_MIX_BLEND_FIRST:
-		case SP_MIX_BLEND_REPLACE:
-			bone->x += (bone->data->x + x - bone->x) * alpha;
-			bone->y += (bone->data->y + y - bone->y) * alpha;
-			break;
-		case SP_MIX_BLEND_ADD:
-			bone->x += x * alpha;
-			bone->y += y * alpha;
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spTranslateTimeline *spTranslateTimeline_create(int frameCount, int bezierCount, int boneIndex) {
-	spTranslateTimeline *timeline = NEW(spTranslateTimeline);
-	spPropertyId ids[2];
-	ids[0] = ((spPropertyId) SP_PROPERTY_X << 32) | boneIndex;
-	ids[1] = ((spPropertyId) SP_PROPERTY_Y << 32) | boneIndex;
-	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE2_ENTRIES, bezierCount, ids, 2, SP_TIMELINE_TRANSLATE,
-						  _spCurveTimeline_dispose, _spTranslateTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->boneIndex = boneIndex;
-	return timeline;
-}
-
-void spTranslateTimeline_setFrame(spTranslateTimeline *self, int frame, float time, float x, float y) {
-	spCurveTimeline2_setFrame(SUPER(self), frame, time, x, y);
-}
-
-/**/
-
-void _spTranslateXTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-								 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-								 spMixDirection direction
-) {
-	spBone *bone;
-	float x;
-
-	spTranslateXTimeline *self = SUB_CAST(spTranslateXTimeline, timeline);
-	float *frames = self->super.super.frames->items;
-
-	bone = skeleton->bones[self->boneIndex];
-	if (!bone->active) return;
-
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				bone->x = bone->data->x;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				bone->x += (bone->data->x - bone->x) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	x = spCurveTimeline1_getCurveValue(SUPER(self), time);
-	switch (blend) {
-		case SP_MIX_BLEND_SETUP:
-			bone->x = bone->data->x + x * alpha;
-			break;
-		case SP_MIX_BLEND_FIRST:
-		case SP_MIX_BLEND_REPLACE:
-			bone->x += (bone->data->x + x - bone->x) * alpha;
-			break;
-		case SP_MIX_BLEND_ADD:
-			bone->x += x * alpha;
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spTranslateXTimeline *spTranslateXTimeline_create(int frameCount, int bezierCount, int boneIndex) {
-	spTranslateXTimeline *timeline = NEW(spTranslateXTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_X << 32) | boneIndex;
-	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_TRANSLATEX,
-						  _spCurveTimeline_dispose, _spTranslateXTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->boneIndex = boneIndex;
-	return timeline;
-}
-
-void spTranslateXTimeline_setFrame(spTranslateXTimeline *self, int frame, float time, float x) {
-	spCurveTimeline1_setFrame(SUPER(self), frame, time, x);
-}
-
-/**/
-
-void _spTranslateYTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-								 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-								 spMixDirection direction
-) {
-	spBone *bone;
-	float y;
-
-	spTranslateYTimeline *self = SUB_CAST(spTranslateYTimeline, timeline);
-	float *frames = self->super.super.frames->items;
-
-	bone = skeleton->bones[self->boneIndex];
-	if (!bone->active) return;
-
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				bone->y = bone->data->y;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				bone->y += (bone->data->y - bone->y) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	y = spCurveTimeline1_getCurveValue(SUPER(self), time);
-	switch (blend) {
-		case SP_MIX_BLEND_SETUP:
-			bone->y = bone->data->y + y * alpha;
-			break;
-		case SP_MIX_BLEND_FIRST:
-		case SP_MIX_BLEND_REPLACE:
-			bone->y += (bone->data->y + y - bone->y) * alpha;
-			break;
-		case SP_MIX_BLEND_ADD:
-			bone->y += y * alpha;
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spTranslateYTimeline *spTranslateYTimeline_create(int frameCount, int bezierCount, int boneIndex) {
-	spTranslateYTimeline *timeline = NEW(spTranslateYTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_Y << 32) | boneIndex;
-	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_TRANSLATEY,
-						  _spCurveTimeline_dispose, _spTranslateYTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->boneIndex = boneIndex;
-	return timeline;
-}
-
-void spTranslateYTimeline_setFrame(spTranslateYTimeline *self, int frame, float time, float y) {
-	spCurveTimeline1_setFrame(SUPER(self), frame, time, y);
-}
-
-/**/
-
-void
-_spScaleTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
-					   int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction
-) {
-	spBone *bone;
-	int i, curveType;
-	float x, y, t;
-
-	spScaleTimeline *self = SUB_CAST(spScaleTimeline, timeline);
-	float *frames = self->super.super.frames->items;
-	float *curves = self->super.curves->items;
-
-	bone = skeleton->bones[self->boneIndex];
-	if (!bone->active) return;
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				bone->scaleX = bone->data->scaleX;
-				bone->scaleY = bone->data->scaleY;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				bone->scaleX += (bone->data->scaleX - bone->scaleX) * alpha;
-				bone->scaleY += (bone->data->scaleY - bone->scaleY) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	i = search2(self->super.super.frames, time, CURVE2_ENTRIES);
-	curveType = (int) curves[i / CURVE2_ENTRIES];
-	switch (curveType) {
-		case CURVE_LINEAR: {
-			float before = frames[i];
-			x = frames[i + CURVE2_VALUE1];
-			y = frames[i + CURVE2_VALUE2];
-			t = (time - before) / (frames[i + CURVE2_ENTRIES] - before);
-			x += (frames[i + CURVE2_ENTRIES + CURVE2_VALUE1] - x) * t;
-			y += (frames[i + CURVE2_ENTRIES + CURVE2_VALUE2] - y) * t;
-			break;
-		}
-		case CURVE_STEPPED: {
-			x = frames[i + CURVE2_VALUE1];
-			y = frames[i + CURVE2_VALUE2];
-			break;
-		}
-		default: {
-			x = _spCurveTimeline_getBezierValue(SUPER(self), time, i, CURVE2_VALUE1, curveType - CURVE_BEZIER);
-			y = _spCurveTimeline_getBezierValue(SUPER(self), time, i, CURVE2_VALUE2,
-												curveType + BEZIER_SIZE - CURVE_BEZIER);
-		}
-	}
-	x *= bone->data->scaleX;
-	y *= bone->data->scaleY;
-
-	if (alpha == 1) {
-		if (blend == SP_MIX_BLEND_ADD) {
-			bone->scaleX += x - bone->data->scaleX;
-			bone->scaleY += y - bone->data->scaleY;
-		} else {
-			bone->scaleX = x;
-			bone->scaleY = y;
-		}
-	} else {
-		float bx, by;
-		if (direction == SP_MIX_DIRECTION_OUT) {
-			switch (blend) {
-				case SP_MIX_BLEND_SETUP:
-					bx = bone->data->scaleX;
-					by = bone->data->scaleY;
-					bone->scaleX = bx + (ABS(x) * SIGNUM(bx) - bx) * alpha;
-					bone->scaleY = by + (ABS(y) * SIGNUM(by) - by) * alpha;
-					break;
-				case SP_MIX_BLEND_FIRST:
-				case SP_MIX_BLEND_REPLACE:
-					bx = bone->scaleX;
-					by = bone->scaleY;
-					bone->scaleX = bx + (ABS(x) * SIGNUM(bx) - bx) * alpha;
-					bone->scaleY = by + (ABS(y) * SIGNUM(by) - by) * alpha;
-					break;
-				case SP_MIX_BLEND_ADD:
-					bx = bone->scaleX;
-					by = bone->scaleY;
-					bone->scaleX = bx + (ABS(x) * SIGNUM(bx) - bone->data->scaleX) * alpha;
-					bone->scaleY = by + (ABS(y) * SIGNUM(by) - bone->data->scaleY) * alpha;
-			}
-		} else {
-			switch (blend) {
-				case SP_MIX_BLEND_SETUP:
-					bx = ABS(bone->data->scaleX) * SIGNUM(x);
-					by = ABS(bone->data->scaleY) * SIGNUM(y);
-					bone->scaleX = bx + (x - bx) * alpha;
-					bone->scaleY = by + (y - by) * alpha;
-					break;
-				case SP_MIX_BLEND_FIRST:
-				case SP_MIX_BLEND_REPLACE:
-					bx = ABS(bone->scaleX) * SIGNUM(x);
-					by = ABS(bone->scaleY) * SIGNUM(y);
-					bone->scaleX = bx + (x - bx) * alpha;
-					bone->scaleY = by + (y - by) * alpha;
-					break;
-				case SP_MIX_BLEND_ADD:
-					bx = SIGNUM(x);
-					by = SIGNUM(y);
-					bone->scaleX = ABS(bone->scaleX) * bx + (x - ABS(bone->data->scaleX) * bx) * alpha;
-					bone->scaleY = ABS(bone->scaleY) * by + (y - ABS(bone->data->scaleY) * by) * alpha;
-			}
-		}
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-}
-
-spScaleTimeline *spScaleTimeline_create(int frameCount, int bezierCount, int boneIndex) {
-	spScaleTimeline *timeline = NEW(spScaleTimeline);
-	spPropertyId ids[2];
-	ids[0] = ((spPropertyId) SP_PROPERTY_SCALEX << 32) | boneIndex;
-	ids[1] = ((spPropertyId) SP_PROPERTY_SCALEY << 32) | boneIndex;
-	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE2_ENTRIES, bezierCount, ids, 2, SP_TIMELINE_SCALE,
-						  _spCurveTimeline_dispose, _spScaleTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->boneIndex = boneIndex;
-	return timeline;
-}
-
-void spScaleTimeline_setFrame(spScaleTimeline *self, int frame, float time, float x, float y) {
-	spCurveTimeline2_setFrame(SUPER(self), frame, time, x, y);
-}
-
-/**/
-
-void _spScaleXTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-							 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-							 spMixDirection direction
-) {
-	spBone *bone;
-	float x;
-
-	spScaleXTimeline *self = SUB_CAST(spScaleXTimeline, timeline);
-	float *frames = self->super.super.frames->items;
-
-	bone = skeleton->bones[self->boneIndex];
-	if (!bone->active) return;
-
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				bone->scaleX = bone->data->scaleX;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				bone->scaleX += (bone->data->scaleX - bone->scaleX) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	x = spCurveTimeline1_getCurveValue(SUPER(self), time) * bone->data->scaleX;
-	if (alpha == 1) {
-		if (blend == SP_MIX_BLEND_ADD)
-			bone->scaleX += x - bone->data->scaleX;
-		else
-			bone->scaleX = x;
-	} else {
-		/* Mixing out uses sign of setup or current pose, else use sign of key. */
-		float bx;
-		if (direction == SP_MIX_DIRECTION_OUT) {
-			switch (blend) {
-				case SP_MIX_BLEND_SETUP:
-					bx = bone->data->scaleX;
-					bone->scaleX = bx + (ABS(x) * SIGNUM(bx) - bx) * alpha;
-					break;
-				case SP_MIX_BLEND_FIRST:
-				case SP_MIX_BLEND_REPLACE:
-					bx = bone->scaleX;
-					bone->scaleX = bx + (ABS(x) * SIGNUM(bx) - bx) * alpha;
-					break;
-				case SP_MIX_BLEND_ADD:
-					bx = bone->scaleX;
-					bone->scaleX = bx + (ABS(x) * SIGNUM(bx) - bone->data->scaleX) * alpha;
-			}
-		} else {
-			switch (blend) {
-				case SP_MIX_BLEND_SETUP:
-					bx = ABS(bone->data->scaleX) * SIGNUM(x);
-					bone->scaleX = bx + (x - bx) * alpha;
-					break;
-				case SP_MIX_BLEND_FIRST:
-				case SP_MIX_BLEND_REPLACE:
-					bx = ABS(bone->scaleX) * SIGNUM(x);
-					bone->scaleX = bx + (x - bx) * alpha;
-					break;
-				case SP_MIX_BLEND_ADD:
-					bx = SIGNUM(x);
-					bone->scaleX = ABS(bone->scaleX) * bx + (x - ABS(bone->data->scaleX) * bx) * alpha;
-			}
-		}
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-}
-
-spScaleXTimeline *spScaleXTimeline_create(int frameCount, int bezierCount, int boneIndex) {
-	spScaleXTimeline *timeline = NEW(spScaleXTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_SCALEX << 32) | boneIndex;
-	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_SCALEX,
-						  _spCurveTimeline_dispose, _spScaleXTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->boneIndex = boneIndex;
-	return timeline;
-}
-
-void spScaleXTimeline_setFrame(spScaleXTimeline *self, int frame, float time, float y) {
-	spCurveTimeline1_setFrame(SUPER(self), frame, time, y);
-}
-
-/**/
-
-void _spScaleYTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-							 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-							 spMixDirection direction
-) {
-	spBone *bone;
-	float y;
-
-	spScaleYTimeline *self = SUB_CAST(spScaleYTimeline, timeline);
-	float *frames = self->super.super.frames->items;
-
-	bone = skeleton->bones[self->boneIndex];
-	if (!bone->active) return;
-
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				bone->scaleY = bone->data->scaleY;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				bone->scaleY += (bone->data->scaleY - bone->scaleY) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	y = spCurveTimeline1_getCurveValue(SUPER(self), time) * bone->data->scaleY;
-	if (alpha == 1) {
-		if (blend == SP_MIX_BLEND_ADD)
-			bone->scaleY += y - bone->data->scaleY;
-		else
-			bone->scaleY = y;
-	} else {
-		/* Mixing out uses sign of setup or current pose, else use sign of key. */
-		float by = 0;
-		if (direction == SP_MIX_DIRECTION_OUT) {
-			switch (blend) {
-				case SP_MIX_BLEND_SETUP:
-					by = bone->data->scaleY;
-					bone->scaleY = by + (ABS(y) * SIGNUM(by) - by) * alpha;
-					break;
-				case SP_MIX_BLEND_FIRST:
-				case SP_MIX_BLEND_REPLACE:
-					by = bone->scaleY;
-					bone->scaleY = by + (ABS(y) * SIGNUM(by) - by) * alpha;
-					break;
-				case SP_MIX_BLEND_ADD:
-					by = bone->scaleY;
-					bone->scaleY = by + (ABS(y) * SIGNUM(by) - bone->data->scaleY) * alpha;
-			}
-		} else {
-			switch (blend) {
-				case SP_MIX_BLEND_SETUP:
-					by = ABS(bone->data->scaleY) * SIGNUM(y);
-					bone->scaleY = by + (y - by) * alpha;
-					break;
-				case SP_MIX_BLEND_FIRST:
-				case SP_MIX_BLEND_REPLACE:
-					by = ABS(bone->scaleY) * SIGNUM(y);
-					bone->scaleY = by + (y - by) * alpha;
-					break;
-				case SP_MIX_BLEND_ADD:
-					by = SIGNUM(y);
-					bone->scaleY = ABS(bone->scaleY) * by + (y - ABS(bone->data->scaleY) * by) * alpha;
-			}
-		}
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-}
-
-spScaleYTimeline *spScaleYTimeline_create(int frameCount, int bezierCount, int boneIndex) {
-	spScaleYTimeline *timeline = NEW(spScaleYTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_SCALEY << 32) | boneIndex;
-	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_SCALEY,
-						  _spCurveTimeline_dispose, _spScaleYTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->boneIndex = boneIndex;
-	return timeline;
-}
-
-void spScaleYTimeline_setFrame(spScaleYTimeline *self, int frame, float time, float y) {
-	spCurveTimeline1_setFrame(SUPER(self), frame, time, y);
-}
-
-/**/
-
-void
-_spShearTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
-					   int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction
-) {
-	spBone *bone;
-	float x, y, t;
-	int i, curveType;
-
-	spShearTimeline *self = SUB_CAST(spShearTimeline, timeline);
-	float *frames = SUPER(self)->super.frames->items;
-	float *curves = SUPER(self)->curves->items;
-
-	bone = skeleton->bones[self->boneIndex];
-	if (!bone->active) return;
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				bone->shearX = bone->data->shearX;
-				bone->shearY = bone->data->shearY;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				bone->shearX += (bone->data->shearX - bone->shearX) * alpha;
-				bone->shearY += (bone->data->shearY - bone->shearY) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	i = search2(self->super.super.frames, time, CURVE2_ENTRIES);
-	curveType = (int) curves[i / CURVE2_ENTRIES];
-	switch (curveType) {
-		case CURVE_LINEAR: {
-			float before = frames[i];
-			x = frames[i + CURVE2_VALUE1];
-			y = frames[i + CURVE2_VALUE2];
-			t = (time - before) / (frames[i + CURVE2_ENTRIES] - before);
-			x += (frames[i + CURVE2_ENTRIES + CURVE2_VALUE1] - x) * t;
-			y += (frames[i + CURVE2_ENTRIES + CURVE2_VALUE2] - y) * t;
-			break;
-		}
-		case CURVE_STEPPED: {
-			x = frames[i + CURVE2_VALUE1];
-			y = frames[i + CURVE2_VALUE2];
-			break;
-		}
-		default: {
-			x = _spCurveTimeline_getBezierValue(SUPER(self), time, i, CURVE2_VALUE1, curveType - CURVE_BEZIER);
-			y = _spCurveTimeline_getBezierValue(SUPER(self), time, i, CURVE2_VALUE2,
-												curveType + BEZIER_SIZE - CURVE_BEZIER);
-		}
-	}
-
-	switch (blend) {
-		case SP_MIX_BLEND_SETUP:
-			bone->shearX = bone->data->shearX + x * alpha;
-			bone->shearY = bone->data->shearY + y * alpha;
-			break;
-		case SP_MIX_BLEND_FIRST:
-		case SP_MIX_BLEND_REPLACE:
-			bone->shearX += (bone->data->shearX + x - bone->shearX) * alpha;
-			bone->shearY += (bone->data->shearY + y - bone->shearY) * alpha;
-			break;
-		case SP_MIX_BLEND_ADD:
-			bone->shearX += x * alpha;
-			bone->shearY += y * alpha;
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spShearTimeline *spShearTimeline_create(int frameCount, int bezierCount, int boneIndex) {
-	spShearTimeline *timeline = NEW(spShearTimeline);
-	spPropertyId ids[2];
-	ids[0] = ((spPropertyId) SP_PROPERTY_SHEARX << 32) | boneIndex;
-	ids[1] = ((spPropertyId) SP_PROPERTY_SHEARY << 32) | boneIndex;
-	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE2_ENTRIES, bezierCount, ids, 2, SP_TIMELINE_SHEAR,
-						  _spCurveTimeline_dispose, _spShearTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->boneIndex = boneIndex;
-	return timeline;
-}
-
-void spShearTimeline_setFrame(spShearTimeline *self, int frame, float time, float x, float y) {
-	spCurveTimeline2_setFrame(SUPER(self), frame, time, x, y);
-}
-
-/**/
-
-void _spShearXTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-							 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-							 spMixDirection direction
-) {
-	spBone *bone;
-	float x;
-
-	spShearXTimeline *self = SUB_CAST(spShearXTimeline, timeline);
-	float *frames = self->super.super.frames->items;
-
-	bone = skeleton->bones[self->boneIndex];
-	if (!bone->active) return;
-
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				bone->shearX = bone->data->shearX;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				bone->shearX += (bone->data->shearX - bone->shearX) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	x = spCurveTimeline1_getCurveValue(SUPER(self), time);
-	switch (blend) {
-		case SP_MIX_BLEND_SETUP:
-			bone->shearX = bone->data->shearX + x * alpha;
-			break;
-		case SP_MIX_BLEND_FIRST:
-		case SP_MIX_BLEND_REPLACE:
-			bone->shearX += (bone->data->shearX + x - bone->shearX) * alpha;
-			break;
-		case SP_MIX_BLEND_ADD:
-			bone->shearX += x * alpha;
-	}
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spShearXTimeline *spShearXTimeline_create(int frameCount, int bezierCount, int boneIndex) {
-	spShearXTimeline *timeline = NEW(spShearXTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_SHEARX << 32) | boneIndex;
-	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_SHEARX,
-						  _spCurveTimeline_dispose, _spShearXTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->boneIndex = boneIndex;
-	return timeline;
-}
-
-void spShearXTimeline_setFrame(spShearXTimeline *self, int frame, float time, float x) {
-	spCurveTimeline1_setFrame(SUPER(self), frame, time, x);
-}
-
-/**/
-
-void _spShearYTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-							 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-							 spMixDirection direction
-) {
-	spBone *bone;
-	float y;
-
-	spShearYTimeline *self = SUB_CAST(spShearYTimeline, timeline);
-	float *frames = self->super.super.frames->items;
-
-	bone = skeleton->bones[self->boneIndex];
-	if (!bone->active) return;
-
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				bone->shearY = bone->data->shearY;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				bone->shearY += (bone->data->shearY - bone->shearY) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	y = spCurveTimeline1_getCurveValue(SUPER(self), time);
-	switch (blend) {
-		case SP_MIX_BLEND_SETUP:
-			bone->shearY = bone->data->shearY + y * alpha;
-			break;
-		case SP_MIX_BLEND_FIRST:
-		case SP_MIX_BLEND_REPLACE:
-			bone->shearY += (bone->data->shearY + y - bone->shearY) * alpha;
-			break;
-		case SP_MIX_BLEND_ADD:
-			bone->shearY += y * alpha;
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spShearYTimeline *spShearYTimeline_create(int frameCount, int bezierCount, int boneIndex) {
-	spShearYTimeline *timeline = NEW(spShearYTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_SHEARY << 32) | boneIndex;
-	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_SHEARY,
-						  _spCurveTimeline_dispose, _spShearYTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->boneIndex = boneIndex;
-	return timeline;
-}
-
-void spShearYTimeline_setFrame(spShearYTimeline *self, int frame, float time, float y) {
-	spCurveTimeline1_setFrame(SUPER(self), frame, time, y);
-}
-
-/**/
-
-static const int RGBA_ENTRIES = 5, COLOR_R = 1, COLOR_G = 2, COLOR_B = 3, COLOR_A = 4;
-
-void
-_spRGBATimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
-					  int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction
-) {
-	spSlot *slot;
-	int i, curveType;
-	float r, g, b, a, t;
-	spColor *color;
-	spColor *setup;
-	spRGBATimeline *self = (spRGBATimeline *) timeline;
-	float *frames = self->super.super.frames->items;
-	float *curves = self->super.curves->items;
-
-	slot = skeleton->slots[self->slotIndex];
-	if (!slot->bone->active) return;
-
-	if (time < frames[0]) {
-		color = &slot->color;
-		setup = &slot->data->color;
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				spColor_setFromColor(color, setup);
-				return;
-			case SP_MIX_BLEND_FIRST:
-				spColor_addFloats(color, (setup->r - color->r) * alpha, (setup->g - color->g) * alpha,
-								  (setup->b - color->b) * alpha,
-								  (setup->a - color->a) * alpha);
-			default: {
-			}
-		}
-		return;
-	}
-
-	i = search2(self->super.super.frames, time, RGBA_ENTRIES);
-	curveType = (int) curves[i / RGBA_ENTRIES];
-	switch (curveType) {
-		case CURVE_LINEAR: {
-			float before = frames[i];
-			r = frames[i + COLOR_R];
-			g = frames[i + COLOR_G];
-			b = frames[i + COLOR_B];
-			a = frames[i + COLOR_A];
-			t = (time - before) / (frames[i + RGBA_ENTRIES] - before);
-			r += (frames[i + RGBA_ENTRIES + COLOR_R] - r) * t;
-			g += (frames[i + RGBA_ENTRIES + COLOR_G] - g) * t;
-			b += (frames[i + RGBA_ENTRIES + COLOR_B] - b) * t;
-			a += (frames[i + RGBA_ENTRIES + COLOR_A] - a) * t;
-			break;
-		}
-		case CURVE_STEPPED: {
-			r = frames[i + COLOR_R];
-			g = frames[i + COLOR_G];
-			b = frames[i + COLOR_B];
-			a = frames[i + COLOR_A];
-			break;
-		}
-		default: {
-			r = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_R, curveType - CURVE_BEZIER);
-			g = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_G,
-												curveType + BEZIER_SIZE - CURVE_BEZIER);
-			b = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_B,
-												curveType + BEZIER_SIZE * 2 - CURVE_BEZIER);
-			a = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_A,
-												curveType + BEZIER_SIZE * 3 - CURVE_BEZIER);
-		}
-	}
-	color = &slot->color;
-	if (alpha == 1)
-		spColor_setFromFloats(color, r, g, b, a);
-	else {
-		if (blend == SP_MIX_BLEND_SETUP) spColor_setFromColor(color, &slot->data->color);
-		spColor_addFloats(color, (r - color->r) * alpha, (g - color->g) * alpha, (b - color->b) * alpha,
-						  (a - color->a) * alpha);
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spRGBATimeline *spRGBATimeline_create(int framesCount, int bezierCount, int slotIndex) {
-	spRGBATimeline *timeline = NEW(spRGBATimeline);
-	spPropertyId ids[2];
-	ids[0] = ((spPropertyId) SP_PROPERTY_RGB << 32) | slotIndex;
-	ids[1] = ((spPropertyId) SP_PROPERTY_ALPHA << 32) | slotIndex;
-	_spCurveTimeline_init(SUPER(timeline), framesCount, RGBA_ENTRIES, bezierCount, ids, 2, SP_TIMELINE_RGBA,
-						  _spCurveTimeline_dispose, _spRGBATimeline_apply, _spCurveTimeline_setBezier);
-	timeline->slotIndex = slotIndex;
-	return timeline;
-}
-
-void spRGBATimeline_setFrame(spRGBATimeline *self, int frame, float time, float r, float g, float b, float a) {
-	float *frames = self->super.super.frames->items;
-	frame *= RGBA_ENTRIES;
-	frames[frame] = time;
-	frames[frame + COLOR_R] = r;
-	frames[frame + COLOR_G] = g;
-	frames[frame + COLOR_B] = b;
-	frames[frame + COLOR_A] = a;
-}
-
-/**/
-
-#define RGB_ENTRIES 4
-
-void _spRGBTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
-						  int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction
-) {
-	spSlot *slot;
-	int i, curveType;
-	float r, g, b, t;
-	spColor *color;
-	spColor *setup;
-	spRGBTimeline *self = (spRGBTimeline *) timeline;
-	float *frames = self->super.super.frames->items;
-	float *curves = self->super.curves->items;
-
-	slot = skeleton->slots[self->slotIndex];
-	if (!slot->bone->active) return;
-
-	if (time < frames[0]) {
-		color = &slot->color;
-		setup = &slot->data->color;
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				spColor_setFromColor(color, setup);
-				return;
-			case SP_MIX_BLEND_FIRST:
-				spColor_addFloats(color, (setup->r - color->r) * alpha, (setup->g - color->g) * alpha,
-								  (setup->b - color->b) * alpha,
-								  (setup->a - color->a) * alpha);
-			default: {
-			}
-		}
-		return;
-	}
-
-	i = search2(self->super.super.frames, time, RGB_ENTRIES);
-	curveType = (int) curves[i / RGB_ENTRIES];
-	switch (curveType) {
-		case CURVE_LINEAR: {
-			float before = frames[i];
-			r = frames[i + COLOR_R];
-			g = frames[i + COLOR_G];
-			b = frames[i + COLOR_B];
-			t = (time - before) / (frames[i + RGB_ENTRIES] - before);
-			r += (frames[i + RGB_ENTRIES + COLOR_R] - r) * t;
-			g += (frames[i + RGB_ENTRIES + COLOR_G] - g) * t;
-			b += (frames[i + RGB_ENTRIES + COLOR_B] - b) * t;
-			break;
-		}
-		case CURVE_STEPPED: {
-			r = frames[i + COLOR_R];
-			g = frames[i + COLOR_G];
-			b = frames[i + COLOR_B];
-			break;
-		}
-		default: {
-			r = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_R, curveType - CURVE_BEZIER);
-			g = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_G,
-												curveType + BEZIER_SIZE - CURVE_BEZIER);
-			b = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_B,
-												curveType + BEZIER_SIZE * 2 - CURVE_BEZIER);
-		}
-	}
-	color = &slot->color;
-	if (alpha == 1) {
-		color->r = r;
-		color->g = g;
-		color->b = b;
-	} else {
-		if (blend == SP_MIX_BLEND_SETUP) {
-			color->r = slot->data->color.r;
-			color->g = slot->data->color.g;
-			color->b = slot->data->color.b;
-		}
-		color->r += (r - color->r) * alpha;
-		color->g += (g - color->g) * alpha;
-		color->b += (b - color->b) * alpha;
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spRGBTimeline *spRGBTimeline_create(int framesCount, int bezierCount, int slotIndex) {
-	spRGBTimeline *timeline = NEW(spRGBTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_RGB << 32) | slotIndex;
-	_spCurveTimeline_init(SUPER(timeline), framesCount, RGB_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_RGB,
-						  _spCurveTimeline_dispose, _spRGBTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->slotIndex = slotIndex;
-	return timeline;
-}
-
-void spRGBTimeline_setFrame(spRGBTimeline *self, int frame, float time, float r, float g, float b) {
-	float *frames = self->super.super.frames->items;
-	frame *= RGB_ENTRIES;
-	frames[frame] = time;
-	frames[frame + COLOR_R] = r;
-	frames[frame + COLOR_G] = g;
-	frames[frame + COLOR_B] = b;
-}
-
-/**/
-
-void _spAlphaTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-							spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-							spMixDirection direction
-) {
-	spSlot *slot;
-	float a;
-	spColor *color;
-	spColor *setup;
-	spAlphaTimeline *self = (spAlphaTimeline *) timeline;
-	float *frames = self->super.super.frames->items;
-
-	slot = skeleton->slots[self->slotIndex];
-	if (!slot->bone->active) return;
-
-	if (time < frames[0]) { /* Time is before first frame-> */
-		color = &slot->color;
-		setup = &slot->data->color;
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				color->a = setup->a;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				color->a += (setup->a - color->a) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	a = spCurveTimeline1_getCurveValue(SUPER(self), time);
-	if (alpha == 1)
-		slot->color.a = a;
-	else {
-		if (blend == SP_MIX_BLEND_SETUP) slot->color.a = slot->data->color.a;
-		slot->color.a += (a - slot->color.a) * alpha;
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spAlphaTimeline *spAlphaTimeline_create(int frameCount, int bezierCount, int slotIndex) {
-	spAlphaTimeline *timeline = NEW(spAlphaTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_ALPHA << 32) | slotIndex;
-	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_ALPHA,
-						  _spCurveTimeline_dispose, _spAlphaTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->slotIndex = slotIndex;
-	return timeline;
-}
-
-void spAlphaTimeline_setFrame(spAlphaTimeline *self, int frame, float time, float alpha) {
-	spCurveTimeline1_setFrame(SUPER(self), frame, time, alpha);
-}
-
-/**/
-
-static const int RGBA2_ENTRIES = 8, COLOR_R2 = 5, COLOR_G2 = 6, COLOR_B2 = 7;
-
-void
-_spRGBA2Timeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
-					   int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction
-) {
-	spSlot *slot;
-	int i, curveType;
-	float r, g, b, a, r2, g2, b2, t;
-	spColor *light, *setupLight;
-	spColor *dark, *setupDark;
-	spRGBA2Timeline *self = (spRGBA2Timeline *) timeline;
-	float *frames = self->super.super.frames->items;
-	float *curves = self->super.curves->items;
-
-	slot = skeleton->slots[self->slotIndex];
-	if (!slot->bone->active) return;
-
-	if (time < frames[0]) {
-		light = &slot->color;
-		dark = slot->darkColor;
-		setupLight = &slot->data->color;
-		setupDark = slot->data->darkColor;
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				spColor_setFromColor(light, setupLight);
-				spColor_setFromFloats3(dark, setupDark->r, setupDark->g, setupDark->b);
-				return;
-			case SP_MIX_BLEND_FIRST:
-				spColor_addFloats(light, (setupLight->r - light->r) * alpha, (setupLight->g - light->g) * alpha,
-								  (setupLight->b - light->b) * alpha,
-								  (setupLight->a - light->a) * alpha);
-				dark->r += (setupDark->r - dark->r) * alpha;
-				dark->g += (setupDark->g - dark->g) * alpha;
-				dark->b += (setupDark->b - dark->b) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	r = 0, g = 0, b = 0, a = 0, r2 = 0, g2 = 0, b2 = 0;
-	i = search2(self->super.super.frames, time, RGBA2_ENTRIES);
-	curveType = (int) curves[i / RGBA2_ENTRIES];
-	switch (curveType) {
-		case CURVE_LINEAR: {
-			float before = frames[i];
-			r = frames[i + COLOR_R];
-			g = frames[i + COLOR_G];
-			b = frames[i + COLOR_B];
-			a = frames[i + COLOR_A];
-			r2 = frames[i + COLOR_R2];
-			g2 = frames[i + COLOR_G2];
-			b2 = frames[i + COLOR_B2];
-			t = (time - before) / (frames[i + RGBA2_ENTRIES] - before);
-			r += (frames[i + RGBA2_ENTRIES + COLOR_R] - r) * t;
-			g += (frames[i + RGBA2_ENTRIES + COLOR_G] - g) * t;
-			b += (frames[i + RGBA2_ENTRIES + COLOR_B] - b) * t;
-			a += (frames[i + RGBA2_ENTRIES + COLOR_A] - a) * t;
-			r2 += (frames[i + RGBA2_ENTRIES + COLOR_R2] - r2) * t;
-			g2 += (frames[i + RGBA2_ENTRIES + COLOR_G2] - g2) * t;
-			b2 += (frames[i + RGBA2_ENTRIES + COLOR_B2] - b2) * t;
-			break;
-		}
-		case CURVE_STEPPED: {
-			r = frames[i + COLOR_R];
-			g = frames[i + COLOR_G];
-			b = frames[i + COLOR_B];
-			a = frames[i + COLOR_A];
-			r2 = frames[i + COLOR_R2];
-			g2 = frames[i + COLOR_G2];
-			b2 = frames[i + COLOR_B2];
-			break;
-		}
-		default: {
-			r = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_R, curveType - CURVE_BEZIER);
-			g = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_G,
-												curveType + BEZIER_SIZE - CURVE_BEZIER);
-			b = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_B,
-												curveType + BEZIER_SIZE * 2 - CURVE_BEZIER);
-			a = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_A,
-												curveType + BEZIER_SIZE * 3 - CURVE_BEZIER);
-			r2 = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_R2,
-												 curveType + BEZIER_SIZE * 4 - CURVE_BEZIER);
-			g2 = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_G2,
-												 curveType + BEZIER_SIZE * 5 - CURVE_BEZIER);
-			b2 = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_B2,
-												 curveType + BEZIER_SIZE * 6 - CURVE_BEZIER);
-		}
-	}
-
-	light = &slot->color, dark = slot->darkColor;
-	if (alpha == 1) {
-		spColor_setFromFloats(light, r, g, b, a);
-		spColor_setFromFloats3(dark, r2, g2, b2);
-	} else {
-		if (blend == SP_MIX_BLEND_SETUP) {
-			spColor_setFromColor(light, &slot->data->color);
-			spColor_setFromColor(dark, slot->data->darkColor);
-		}
-		spColor_addFloats(light, (r - light->r) * alpha, (g - light->g) * alpha, (b - light->b) * alpha,
-						  (a - light->a) * alpha);
-		dark->r += (r2 - dark->r) * alpha;
-		dark->g += (g2 - dark->g) * alpha;
-		dark->b += (b2 - dark->b) * alpha;
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spRGBA2Timeline *spRGBA2Timeline_create(int framesCount, int bezierCount, int slotIndex) {
-	spRGBA2Timeline *timeline = NEW(spRGBA2Timeline);
-	spPropertyId ids[3];
-	ids[0] = ((spPropertyId) SP_PROPERTY_RGB << 32) | slotIndex;
-	ids[1] = ((spPropertyId) SP_PROPERTY_ALPHA << 32) | slotIndex;
-	ids[2] = ((spPropertyId) SP_PROPERTY_RGB2 << 32) | slotIndex;
-	_spCurveTimeline_init(SUPER(timeline), framesCount, RGBA2_ENTRIES, bezierCount, ids, 3, SP_TIMELINE_RGBA2,
-						  _spCurveTimeline_dispose, _spRGBA2Timeline_apply, _spCurveTimeline_setBezier);
-	timeline->slotIndex = slotIndex;
-	return timeline;
-}
-
-void
-spRGBA2Timeline_setFrame(spRGBA2Timeline *self, int frame, float time, float r, float g, float b, float a, float r2,
-						 float g2, float b2) {
-	float *frames = self->super.super.frames->items;
-	frame *= RGBA2_ENTRIES;
-	frames[frame] = time;
-	frames[frame + COLOR_R] = r;
-	frames[frame + COLOR_G] = g;
-	frames[frame + COLOR_B] = b;
-	frames[frame + COLOR_A] = a;
-	frames[frame + COLOR_R2] = r2;
-	frames[frame + COLOR_G2] = g2;
-	frames[frame + COLOR_B2] = b2;
-}
-
-/**/
-
-static const int RGB2_ENTRIES = 7, COLOR2_R2 = 5, COLOR2_G2 = 6, COLOR2_B2 = 7;
-
-void
-_spRGB2Timeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
-					  int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction
-) {
-	spSlot *slot;
-	int i, curveType;
-	float r, g, b, r2, g2, b2, t;
-	spColor *light, *setupLight;
-	spColor *dark, *setupDark;
-	spRGB2Timeline *self = (spRGB2Timeline *) timeline;
-	float *frames = self->super.super.frames->items;
-	float *curves = self->super.curves->items;
-
-	slot = skeleton->slots[self->slotIndex];
-	if (!slot->bone->active) return;
-
-	if (time < frames[0]) {
-		light = &slot->color;
-		dark = slot->darkColor;
-		setupLight = &slot->data->color;
-		setupDark = slot->data->darkColor;
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				spColor_setFromColor3(light, setupLight);
-				spColor_setFromColor3(dark, setupDark);
-				return;
-			case SP_MIX_BLEND_FIRST:
-				spColor_addFloats3(light, (setupLight->r - light->r) * alpha, (setupLight->g - light->g) * alpha,
-								   (setupLight->b - light->b) * alpha);
-				dark->r += (setupDark->r - dark->r) * alpha;
-				dark->g += (setupDark->g - dark->g) * alpha;
-				dark->b += (setupDark->b - dark->b) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	r = 0, g = 0, b = 0, r2 = 0, g2 = 0, b2 = 0;
-	i = search2(self->super.super.frames, time, RGB2_ENTRIES);
-	curveType = (int) curves[i / RGB2_ENTRIES];
-	switch (curveType) {
-		case CURVE_LINEAR: {
-			float before = frames[i];
-			r = frames[i + COLOR_R];
-			g = frames[i + COLOR_G];
-			b = frames[i + COLOR_B];
-			r2 = frames[i + COLOR2_R2];
-			g2 = frames[i + COLOR2_G2];
-			b2 = frames[i + COLOR2_B2];
-			t = (time - before) / (frames[i + RGB2_ENTRIES] - before);
-			r += (frames[i + RGB2_ENTRIES + COLOR_R] - r) * t;
-			g += (frames[i + RGB2_ENTRIES + COLOR_G] - g) * t;
-			b += (frames[i + RGB2_ENTRIES + COLOR_B] - b) * t;
-			r2 += (frames[i + RGB2_ENTRIES + COLOR2_R2] - r2) * t;
-			g2 += (frames[i + RGB2_ENTRIES + COLOR2_G2] - g2) * t;
-			b2 += (frames[i + RGB2_ENTRIES + COLOR2_B2] - b2) * t;
-			break;
-		}
-		case CURVE_STEPPED: {
-			r = frames[i + COLOR_R];
-			g = frames[i + COLOR_G];
-			b = frames[i + COLOR_B];
-			r2 = frames[i + COLOR2_R2];
-			g2 = frames[i + COLOR2_G2];
-			b2 = frames[i + COLOR2_B2];
-			break;
-		}
-		default: {
-			r = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_R, curveType - CURVE_BEZIER);
-			g = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_G,
-												curveType + BEZIER_SIZE - CURVE_BEZIER);
-			b = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_B,
-												curveType + BEZIER_SIZE * 2 - CURVE_BEZIER);
-			r2 = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR2_R2,
-												 curveType + BEZIER_SIZE * 3 - CURVE_BEZIER);
-			g2 = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR2_G2,
-												 curveType + BEZIER_SIZE * 4 - CURVE_BEZIER);
-			b2 = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR2_B2,
-												 curveType + BEZIER_SIZE * 5 - CURVE_BEZIER);
-		}
-	}
-
-	light = &slot->color, dark = slot->darkColor;
-	if (alpha == 1) {
-		spColor_setFromFloats3(light, r, g, b);
-		spColor_setFromFloats3(dark, r2, g2, b2);
-	} else {
-		if (blend == SP_MIX_BLEND_SETUP) {
-			spColor_setFromColor3(light, &slot->data->color);
-
-			spColor_setFromColor3(dark, slot->data->darkColor);
-		}
-		spColor_addFloats3(light, (r - light->r) * alpha, (g - light->g) * alpha, (b - light->b) * alpha);
-		dark->r += (r2 - dark->r) * alpha;
-		dark->g += (g2 - dark->g) * alpha;
-		dark->b += (b2 - dark->b) * alpha;
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spRGB2Timeline *spRGB2Timeline_create(int framesCount, int bezierCount, int slotIndex) {
-	spRGB2Timeline *timeline = NEW(spRGB2Timeline);
-	spPropertyId ids[2];
-	ids[0] = ((spPropertyId) SP_PROPERTY_RGB << 32) | slotIndex;
-	ids[1] = ((spPropertyId) SP_PROPERTY_RGB2 << 32) | slotIndex;
-	_spCurveTimeline_init(SUPER(timeline), framesCount, RGB2_ENTRIES, bezierCount, ids, 2, SP_TIMELINE_RGB2,
-						  _spCurveTimeline_dispose, _spRGB2Timeline_apply, _spCurveTimeline_setBezier);
-	timeline->slotIndex = slotIndex;
-	return timeline;
-}
-
-void spRGB2Timeline_setFrame(spRGB2Timeline *self, int frame, float time, float r, float g, float b, float r2, float g2,
-							 float b2) {
-	float *frames = self->super.super.frames->items;
-	frame *= RGB2_ENTRIES;
-	frames[frame] = time;
-	frames[frame + COLOR_R] = r;
-	frames[frame + COLOR_G] = g;
-	frames[frame + COLOR_B] = b;
-	frames[frame + COLOR2_R2] = r2;
-	frames[frame + COLOR2_G2] = g2;
-	frames[frame + COLOR2_B2] = b2;
-}
-
-/**/
-
-static void
-_spSetAttachment(spAttachmentTimeline *timeline, spSkeleton *skeleton, spSlot *slot, const char *attachmentName) {
-	slot->attachment =
-			attachmentName == NULL ? NULL : spSkeleton_getAttachmentForSlotIndex(skeleton, timeline->slotIndex,
-																				 attachmentName);
-}
-
-void _spAttachmentTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-								 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-								 spMixDirection direction) {
-	const char *attachmentName;
-	spAttachmentTimeline *self = (spAttachmentTimeline *) timeline;
-	float *frames = self->super.frames->items;
-	spSlot *slot = skeleton->slots[self->slotIndex];
-	if (!slot->bone->active) return;
-
-	if (direction == SP_MIX_DIRECTION_OUT) {
-		if (blend == SP_MIX_BLEND_SETUP) {
-			_spSetAttachment(self, skeleton, slot, slot->data->attachmentName);
-		}
-		return;
-	}
-
-	if (time < frames[0]) {
-		if (blend == SP_MIX_BLEND_SETUP || blend == SP_MIX_BLEND_FIRST) {
-			_spSetAttachment(self, skeleton, slot, slot->data->attachmentName);
-		}
-		return;
-	}
-
-	if (time < frames[0]) {
-		if (blend == SP_MIX_BLEND_SETUP || blend == SP_MIX_BLEND_FIRST)
-			_spSetAttachment(self, skeleton, slot, slot->data->attachmentName);
-		return;
-	}
-
-	attachmentName = self->attachmentNames[search(self->super.frames, time)];
-	_spSetAttachment(self, skeleton, slot, attachmentName);
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(alpha);
-}
-
-void _spAttachmentTimeline_dispose(spTimeline *timeline) {
-	spAttachmentTimeline *self = SUB_CAST(spAttachmentTimeline, timeline);
-	int i;
-	for (i = 0; i < self->super.frames->size; ++i)
-		FREE(self->attachmentNames[i]);
-	FREE(self->attachmentNames);
-}
-
-spAttachmentTimeline *spAttachmentTimeline_create(int framesCount, int slotIndex) {
-	spAttachmentTimeline *self = NEW(spAttachmentTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_ATTACHMENT << 32) | slotIndex;
-	_spTimeline_init(SUPER(self), framesCount, 1, ids, 1, SP_TIMELINE_ATTACHMENT, _spAttachmentTimeline_dispose,
-					 _spAttachmentTimeline_apply, 0);
-	CONST_CAST(char**, self->attachmentNames) = CALLOC(char*, framesCount);
-	self->slotIndex = slotIndex;
-	return self;
-}
-
-void spAttachmentTimeline_setFrame(spAttachmentTimeline *self, int frame, float time, const char *attachmentName) {
-	self->super.frames->items[frame] = time;
-
-	FREE(self->attachmentNames[frame]);
-	if (attachmentName)
-		MALLOC_STR(self->attachmentNames[frame], attachmentName);
-	else
-		self->attachmentNames[frame] = 0;
-}
-
-/**/
-
-void _spDeformTimeline_setBezier(spTimeline *timeline, int bezier, int frame, float value, float time1, float value1,
-								 float cx1, float cy1,
-								 float cx2, float cy2, float time2, float value2) {
-	spDeformTimeline *self = SUB_CAST(spDeformTimeline, timeline);
-	int n, i = self->super.super.frameCount + bezier * BEZIER_SIZE;
-	float *curves = self->super.curves->items;
-	float tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = cy2 * 0.03 - cy1 * 0.06;
-	float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = (cy1 - cy2 + 0.33333333) * 0.018;
-	float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy;
-	float dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = cy1 * 0.3 + tmpy + dddy * 0.16666667;
-	float x = time1 + dx, y = dy;
-	if (value == 0) curves[frame] = CURVE_BEZIER + i;
-	for (n = i + BEZIER_SIZE; i < n; i += 2) {
-		curves[i] = x;
-		curves[i + 1] = y;
-		dx += ddx;
-		dy += ddy;
-		ddx += dddx;
-		ddy += dddy;
-		x += dx;
-		y += dy;
-	}
-
-	UNUSED(value1);
-	UNUSED(value2);
-}
-
-float _spDeformTimeline_getCurvePercent(spDeformTimeline *self, float time, int frame) {
-	float *curves = self->super.curves->items;
-	float *frames = self->super.super.frames->items;
-	int n, i = (int) curves[frame];
-	int frameEntries = self->super.super.frameEntries;
-	float x, y;
-	switch (i) {
-		case CURVE_LINEAR: {
-			x = frames[frame];
-			return (time - x) / (frames[frame + frameEntries] - x);
-		}
-		case CURVE_STEPPED: {
-			return 0;
-		}
-		default: {
-		}
-	}
-	i -= CURVE_BEZIER;
-	if (curves[i] > time) {
-		x = frames[frame];
-		return curves[i + 1] * (time - x) / (curves[i] - x);
-	}
-	n = i + BEZIER_SIZE;
-	for (i += 2; i < n; i += 2) {
-		if (curves[i] >= time) {
-			x = curves[i - 2], y = curves[i - 1];
-			return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
-		}
-	}
-	x = curves[n - 2], y = curves[n - 1];
-	return y + (1 - y) * (time - x) / (frames[frame + frameEntries] - x);
-}
-
-void _spDeformTimeline_apply(
-		spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
-		int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction
-) {
-	int frame, i, vertexCount;
-	float percent, frameTime;
-	const float *prevVertices;
-	const float *nextVertices;
-	float *frames;
-	int framesCount;
-	const float **frameVertices;
-	float *deformArray;
-	spDeformTimeline *self = (spDeformTimeline *) timeline;
-
-	spSlot *slot = skeleton->slots[self->slotIndex];
-	if (!slot->bone->active) return;
-
-	if (!slot->attachment) return;
-	switch (slot->attachment->type) {
-		case SP_ATTACHMENT_BOUNDING_BOX:
-		case SP_ATTACHMENT_CLIPPING:
-		case SP_ATTACHMENT_MESH:
-		case SP_ATTACHMENT_PATH: {
-			spVertexAttachment *vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
-			if (vertexAttachment->deformAttachment != SUB_CAST(spVertexAttachment, self->attachment)) return;
-			break;
-		}
-		default:
-			return;
-	}
-
-	frames = self->super.super.frames->items;
-	framesCount = self->super.super.frames->size;
-	vertexCount = self->frameVerticesCount;
-	if (slot->deformCount < vertexCount) {
-		if (slot->deformCapacity < vertexCount) {
-			FREE(slot->deform);
-			slot->deform = MALLOC(float, vertexCount);
-			slot->deformCapacity = vertexCount;
-		}
-	}
-	if (slot->deformCount == 0) blend = SP_MIX_BLEND_SETUP;
-
-	frameVertices = self->frameVertices;
-	deformArray = slot->deform;
-
-	if (time < frames[0]) { /* Time is before first frame. */
-		spVertexAttachment *vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				slot->deformCount = 0;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				if (alpha == 1) {
-					slot->deformCount = 0;
-					return;
-				}
-				slot->deformCount = vertexCount;
-				if (!vertexAttachment->bones) {
-					float *setupVertices = vertexAttachment->vertices;
-					for (i = 0; i < vertexCount; i++) {
-						deformArray[i] += (setupVertices[i] - deformArray[i]) * alpha;
-					}
-				} else {
-					alpha = 1 - alpha;
-					for (i = 0; i < vertexCount; i++) {
-						deformArray[i] *= alpha;
-					}
-				}
-			case SP_MIX_BLEND_REPLACE:
-			case SP_MIX_BLEND_ADD:; /* to appease compiler */
-		}
-		return;
-	}
-
-	slot->deformCount = vertexCount;
-	if (time >= frames[framesCount - 1]) { /* Time is after last frame. */
-		const float *lastVertices = self->frameVertices[framesCount - 1];
-		if (alpha == 1) {
-			if (blend == SP_MIX_BLEND_ADD) {
-				spVertexAttachment *vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
-				if (!vertexAttachment->bones) {
-					/* Unweighted vertex positions, with alpha. */
-					float *setupVertices = vertexAttachment->vertices;
-					for (i = 0; i < vertexCount; i++) {
-						deformArray[i] += lastVertices[i] - setupVertices[i];
-					}
-				} else {
-					/* Weighted deform offsets, with alpha. */
-					for (i = 0; i < vertexCount; i++)
-						deformArray[i] += lastVertices[i];
-				}
-			} else {
-				/* Vertex positions or deform offsets, no alpha. */
-				memcpy(deformArray, lastVertices, vertexCount * sizeof(float));
-			}
-		} else {
-			spVertexAttachment *vertexAttachment;
-			switch (blend) {
-				case SP_MIX_BLEND_SETUP:
-					vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
-					if (!vertexAttachment->bones) {
-						/* Unweighted vertex positions, with alpha. */
-						float *setupVertices = vertexAttachment->vertices;
-						for (i = 0; i < vertexCount; i++) {
-							float setup = setupVertices[i];
-							deformArray[i] = setup + (lastVertices[i] - setup) * alpha;
-						}
-					} else {
-						/* Weighted deform offsets, with alpha. */
-						for (i = 0; i < vertexCount; i++)
-							deformArray[i] = lastVertices[i] * alpha;
-					}
-					break;
-				case SP_MIX_BLEND_FIRST:
-				case SP_MIX_BLEND_REPLACE:
-					/* Vertex positions or deform offsets, with alpha. */
-					for (i = 0; i < vertexCount; i++)
-						deformArray[i] += (lastVertices[i] - deformArray[i]) * alpha;
-				case SP_MIX_BLEND_ADD:
-					vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
-					if (!vertexAttachment->bones) {
-						/* Unweighted vertex positions, with alpha. */
-						float *setupVertices = vertexAttachment->vertices;
-						for (i = 0; i < vertexCount; i++) {
-							deformArray[i] += (lastVertices[i] - setupVertices[i]) * alpha;
-						}
-					} else {
-						for (i = 0; i < vertexCount; i++)
-							deformArray[i] += lastVertices[i] * alpha;
-					}
-			}
-		}
-		return;
-	}
-
-	/* Interpolate between the previous frame and the current frame. */
-	frame = search(self->super.super.frames, time);
-	percent = _spDeformTimeline_getCurvePercent(self, time, frame);
-	prevVertices = frameVertices[frame];
-	nextVertices = frameVertices[frame + 1];
-	frameTime = frames[frame];
-
-	if (alpha == 1) {
-		if (blend == SP_MIX_BLEND_ADD) {
-			spVertexAttachment *vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
-			if (!vertexAttachment->bones) {
-				float *setupVertices = vertexAttachment->vertices;
-				for (i = 0; i < vertexCount; i++) {
-					float prev = prevVertices[i];
-					deformArray[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i];
-				}
-			} else {
-				for (i = 0; i < vertexCount; i++) {
-					float prev = prevVertices[i];
-					deformArray[i] += prev + (nextVertices[i] - prev) * percent;
-				}
-			}
-		} else {
-			for (i = 0; i < vertexCount; i++) {
-				float prev = prevVertices[i];
-				deformArray[i] = prev + (nextVertices[i] - prev) * percent;
-			}
-		}
-	} else {
-		spVertexAttachment *vertexAttachment;
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
-				if (!vertexAttachment->bones) {
-					float *setupVertices = vertexAttachment->vertices;
-					for (i = 0; i < vertexCount; i++) {
-						float prev = prevVertices[i], setup = setupVertices[i];
-						deformArray[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha;
-					}
-				} else {
-					for (i = 0; i < vertexCount; i++) {
-						float prev = prevVertices[i];
-						deformArray[i] = (prev + (nextVertices[i] - prev) * percent) * alpha;
-					}
-				}
-				break;
-			case SP_MIX_BLEND_FIRST:
-			case SP_MIX_BLEND_REPLACE:
-				for (i = 0; i < vertexCount; i++) {
-					float prev = prevVertices[i];
-					deformArray[i] += (prev + (nextVertices[i] - prev) * percent - deformArray[i]) * alpha;
-				}
-				break;
-			case SP_MIX_BLEND_ADD:
-				vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
-				if (!vertexAttachment->bones) {
-					float *setupVertices = vertexAttachment->vertices;
-					for (i = 0; i < vertexCount; i++) {
-						float prev = prevVertices[i];
-						deformArray[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha;
-					}
-				} else {
-					for (i = 0; i < vertexCount; i++) {
-						float prev = prevVertices[i];
-						deformArray[i] += (prev + (nextVertices[i] - prev) * percent) * alpha;
-					}
-				}
-		}
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-void _spDeformTimeline_dispose(spTimeline *timeline) {
-	spDeformTimeline *self = SUB_CAST(spDeformTimeline, timeline);
-	int i;
-	for (i = 0; i < self->super.super.frames->size; ++i)
-		FREE(self->frameVertices[i]);
-	FREE(self->frameVertices);
-	_spCurveTimeline_dispose(timeline);
-}
-
-spDeformTimeline *spDeformTimeline_create(int framesCount, int frameVerticesCount, int bezierCount, int slotIndex,
-										  spVertexAttachment *attachment) {
-	spDeformTimeline *self = NEW(spDeformTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_DEFORM << 32) | ((slotIndex << 16 | attachment->id) & 0xffffffff);
-	_spCurveTimeline_init(SUPER(self), framesCount, 1, bezierCount, ids, 1, SP_TIMELINE_DEFORM,
-						  _spDeformTimeline_dispose, _spDeformTimeline_apply, _spDeformTimeline_setBezier);
-	CONST_CAST(float**, self->frameVertices) = CALLOC(float*, framesCount);
-	CONST_CAST(int, self->frameVerticesCount) = frameVerticesCount;
-	self->slotIndex = slotIndex;
-	self->attachment = SUPER(attachment);
-	return self;
-}
-
-void spDeformTimeline_setFrame(spDeformTimeline *self, int frame, float time, float *vertices) {
-	self->super.super.frames->items[frame] = time;
-
-	FREE(self->frameVertices[frame]);
-	if (!vertices)
-		self->frameVertices[frame] = 0;
-	else {
-		self->frameVertices[frame] = MALLOC(float, self->frameVerticesCount);
-		memcpy(CONST_CAST(float*, self->frameVertices[frame]), vertices, self->frameVerticesCount * sizeof(float));
-	}
-}
-
-/**/
-
-/** Fires events for frames > lastTime and <= time. */
-void
-_spEventTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
-					   int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction
-) {
-	spEventTimeline *self = (spEventTimeline *) timeline;
-	float *frames = self->super.frames->items;
-	int framesCount = self->super.frames->size;
-	int i;
-	if (!firedEvents) return;
-
-	if (lastTime > time) { /* Fire events after last time for looped animations. */
-		_spEventTimeline_apply(timeline, skeleton, lastTime, (float) INT_MAX, firedEvents, eventsCount, alpha, blend,
-							   direction);
-		lastTime = -1;
-	} else if (lastTime >= frames[framesCount - 1]) {
-		/* Last time is after last i. */
-		return;
-	}
-
-	if (time < frames[0]) return; /* Time is before first i. */
-
-	if (lastTime < frames[0])
-		i = 0;
-	else {
-		float frameTime;
-		i = search(self->super.frames, lastTime) + 1;
-		frameTime = frames[i];
-		while (i > 0) { /* Fire multiple events with the same i. */
-			if (frames[i - 1] != frameTime) break;
-			i--;
-		}
-	}
-	for (; i < framesCount && time >= frames[i]; ++i) {
-		firedEvents[*eventsCount] = self->events[i];
-		(*eventsCount)++;
-	}
-	UNUSED(direction);
-}
-
-void _spEventTimeline_dispose(spTimeline *timeline) {
-	spEventTimeline *self = SUB_CAST(spEventTimeline, timeline);
-	int i;
-
-	for (i = 0; i < self->super.frames->size; ++i)
-		spEvent_dispose(self->events[i]);
-	FREE(self->events);
-}
-
-spEventTimeline *spEventTimeline_create(int framesCount) {
-	spEventTimeline *self = NEW(spEventTimeline);
-	spPropertyId ids[1];
-	ids[0] = (spPropertyId) SP_PROPERTY_EVENT << 32;
-	_spTimeline_init(SUPER(self), framesCount, 1, ids, 1, SP_TIMELINE_EVENT, _spEventTimeline_dispose,
-					 _spEventTimeline_apply, 0);
-	CONST_CAST(spEvent**, self->events) = CALLOC(spEvent*, framesCount);
-	return self;
-}
-
-void spEventTimeline_setFrame(spEventTimeline *self, int frame, spEvent *event) {
-	self->super.frames->items[frame] = event->time;
-
-	FREE(self->events[frame]);
-	self->events[frame] = event;
-}
-
-/**/
-
-void _spDrawOrderTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-								spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-								spMixDirection direction
-) {
-	int i;
-	const int *drawOrderToSetupIndex;
-	spDrawOrderTimeline *self = (spDrawOrderTimeline *) timeline;
-	float *frames = self->super.frames->items;
-
-	if (direction == SP_MIX_DIRECTION_OUT) {
-		if (blend == SP_MIX_BLEND_SETUP)
-			memcpy(skeleton->drawOrder, skeleton->slots, self->slotsCount * sizeof(spSlot *));
-		return;
-	}
-
-	if (time < frames[0]) {
-		if (blend == SP_MIX_BLEND_SETUP || blend == SP_MIX_BLEND_FIRST)
-			memcpy(skeleton->drawOrder, skeleton->slots, self->slotsCount * sizeof(spSlot *));
-		return;
-	}
-
-	drawOrderToSetupIndex = self->drawOrders[search(self->super.frames, time)];
-	if (!drawOrderToSetupIndex)
-		memcpy(skeleton->drawOrder, skeleton->slots, self->slotsCount * sizeof(spSlot *));
-	else {
-		for (i = 0; i < self->slotsCount; ++i)
-			skeleton->drawOrder[i] = skeleton->slots[drawOrderToSetupIndex[i]];
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(alpha);
-}
-
-void _spDrawOrderTimeline_dispose(spTimeline *timeline) {
-	spDrawOrderTimeline *self = SUB_CAST(spDrawOrderTimeline, timeline);
-	int i;
-
-	for (i = 0; i < self->super.frames->size; ++i)
-		FREE(self->drawOrders[i]);
-	FREE(self->drawOrders);
-}
-
-spDrawOrderTimeline *spDrawOrderTimeline_create(int framesCount, int slotsCount) {
-	spDrawOrderTimeline *self = NEW(spDrawOrderTimeline);
-	spPropertyId ids[1];
-	ids[0] = (spPropertyId) SP_PROPERTY_DRAWORDER << 32;
-	_spTimeline_init(SUPER(self), framesCount, 1, ids, 1, SP_TIMELINE_DRAWORDER, _spDrawOrderTimeline_dispose,
-					 _spDrawOrderTimeline_apply, 0);
-
-	CONST_CAST(int**, self->drawOrders) = CALLOC(int*, framesCount);
-	CONST_CAST(int, self->slotsCount) = slotsCount;
-
-	return self;
-}
-
-void spDrawOrderTimeline_setFrame(spDrawOrderTimeline *self, int frame, float time, const int *drawOrder) {
-	self->super.frames->items[frame] = time;
-
-	FREE(self->drawOrders[frame]);
-	if (!drawOrder)
-		self->drawOrders[frame] = 0;
-	else {
-		self->drawOrders[frame] = MALLOC(int, self->slotsCount);
-		memcpy(CONST_CAST(int*, self->drawOrders[frame]), drawOrder, self->slotsCount * sizeof(int));
-	}
-}
-
-/**/
-
-static const int IKCONSTRAINT_ENTRIES = 6;
-static const int IKCONSTRAINT_MIX = 1, IKCONSTRAINT_SOFTNESS = 2, IKCONSTRAINT_BEND_DIRECTION = 3, IKCONSTRAINT_COMPRESS = 4, IKCONSTRAINT_STRETCH = 5;
-
-void _spIkConstraintTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-								   spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-								   spMixDirection direction
-) {
-	int i, curveType;
-	float mix, softness, t;
-	spIkConstraint *constraint;
-	spIkConstraintTimeline *self = (spIkConstraintTimeline *) timeline;
-	float *frames = self->super.super.frames->items;
-	float *curves = self->super.curves->items;
-
-	constraint = skeleton->ikConstraints[self->ikConstraintIndex];
-	if (!constraint->active) return;
-
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				constraint->mix = constraint->data->mix;
-				constraint->softness = constraint->data->softness;
-				constraint->bendDirection = constraint->data->bendDirection;
-				constraint->compress = constraint->data->compress;
-				constraint->stretch = constraint->data->stretch;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				constraint->mix += (constraint->data->mix - constraint->mix) * alpha;
-				constraint->softness += (constraint->data->softness - constraint->softness) * alpha;
-				constraint->bendDirection = constraint->data->bendDirection;
-				constraint->compress = constraint->data->compress;
-				constraint->stretch = constraint->data->stretch;
-				return;
-			default:
-				return;
-		}
-	}
-
-	i = search2(self->super.super.frames, time, IKCONSTRAINT_ENTRIES);
-	curveType = (int) curves[i / IKCONSTRAINT_ENTRIES];
-	switch (curveType) {
-		case CURVE_LINEAR: {
-			float before = frames[i];
-			mix = frames[i + IKCONSTRAINT_MIX];
-			softness = frames[i + IKCONSTRAINT_SOFTNESS];
-			t = (time - before) / (frames[i + IKCONSTRAINT_ENTRIES] - before);
-			mix += (frames[i + IKCONSTRAINT_ENTRIES + IKCONSTRAINT_MIX] - mix) * t;
-			softness += (frames[i + IKCONSTRAINT_ENTRIES + IKCONSTRAINT_SOFTNESS] - softness) * t;
-			break;
-		}
-		case CURVE_STEPPED: {
-			mix = frames[i + IKCONSTRAINT_MIX];
-			softness = frames[i + IKCONSTRAINT_SOFTNESS];
-			break;
-		}
-		default: {
-			mix = _spCurveTimeline_getBezierValue(SUPER(self), time, i, IKCONSTRAINT_MIX, curveType - CURVE_BEZIER);
-			softness = _spCurveTimeline_getBezierValue(SUPER(self), time, i, IKCONSTRAINT_SOFTNESS,
-													   curveType + BEZIER_SIZE - CURVE_BEZIER);
-		}
-	}
-
-	if (blend == SP_MIX_BLEND_SETUP) {
-		constraint->mix = constraint->data->mix + (mix - constraint->data->mix) * alpha;
-		constraint->softness = constraint->data->softness + (softness - constraint->data->softness) * alpha;
-
-		if (direction == SP_MIX_DIRECTION_OUT) {
-			constraint->bendDirection = constraint->data->bendDirection;
-			constraint->compress = constraint->data->compress;
-			constraint->stretch = constraint->data->stretch;
-		} else {
-			constraint->bendDirection = frames[i + IKCONSTRAINT_BEND_DIRECTION];
-			constraint->compress = frames[i + IKCONSTRAINT_COMPRESS] != 0;
-			constraint->stretch = frames[i + IKCONSTRAINT_STRETCH] != 0;
-		}
-	} else {
-		constraint->mix += (mix - constraint->mix) * alpha;
-		constraint->softness += (softness - constraint->softness) * alpha;
-		if (direction == SP_MIX_DIRECTION_IN) {
-			constraint->bendDirection = frames[i + IKCONSTRAINT_BEND_DIRECTION];
-			constraint->compress = frames[i + IKCONSTRAINT_COMPRESS] != 0;
-			constraint->stretch = frames[i + IKCONSTRAINT_STRETCH] != 0;
-		}
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-}
-
-spIkConstraintTimeline *spIkConstraintTimeline_create(int framesCount, int bezierCount, int ikConstraintIndex) {
-	spIkConstraintTimeline *timeline = NEW(spIkConstraintTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_IKCONSTRAINT << 32) | ikConstraintIndex;
-	_spCurveTimeline_init(SUPER(timeline), framesCount, IKCONSTRAINT_ENTRIES, bezierCount, ids, 1,
-						  SP_TIMELINE_IKCONSTRAINT, _spCurveTimeline_dispose, _spIkConstraintTimeline_apply,
-						  _spCurveTimeline_setBezier);
-	timeline->ikConstraintIndex = ikConstraintIndex;
-	return timeline;
-}
-
-void spIkConstraintTimeline_setFrame(spIkConstraintTimeline *self, int frame, float time, float mix, float softness,
-									 int bendDirection, int /*boolean*/ compress, int /*boolean*/ stretch
-) {
-	float *frames = self->super.super.frames->items;
-	frame *= IKCONSTRAINT_ENTRIES;
-	frames[frame] = time;
-	frames[frame + IKCONSTRAINT_MIX] = mix;
-	frames[frame + IKCONSTRAINT_SOFTNESS] = softness;
-	frames[frame + IKCONSTRAINT_BEND_DIRECTION] = (float) bendDirection;
-	frames[frame + IKCONSTRAINT_COMPRESS] = compress ? 1 : 0;
-	frames[frame + IKCONSTRAINT_STRETCH] = stretch ? 1 : 0;
-}
-
-/**/
-static const int TRANSFORMCONSTRAINT_ENTRIES = 7;
-static const int TRANSFORMCONSTRAINT_ROTATE = 1;
-static const int TRANSFORMCONSTRAINT_X = 2;
-static const int TRANSFORMCONSTRAINT_Y = 3;
-static const int TRANSFORMCONSTRAINT_SCALEX = 4;
-static const int TRANSFORMCONSTRAINT_SCALEY = 5;
-static const int TRANSFORMCONSTRAINT_SHEARY = 6;
-
-void _spTransformConstraintTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-										  spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-										  spMixDirection direction
-) {
-	int i, curveType;
-	float rotate, x, y, scaleX, scaleY, shearY, t;
-	spTransformConstraint *constraint;
-	spTransformConstraintTimeline *self = (spTransformConstraintTimeline *) timeline;
-	float *frames;
-	float *curves;
-	spTransformConstraintData *data;
-
-	constraint = skeleton->transformConstraints[self->transformConstraintIndex];
-	if (!constraint->active) return;
-
-	frames = self->super.super.frames->items;
-	curves = self->super.curves->items;
-
-	data = constraint->data;
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				constraint->mixRotate = data->mixRotate;
-				constraint->mixX = data->mixX;
-				constraint->mixY = data->mixY;
-				constraint->mixScaleX = data->mixScaleX;
-				constraint->mixScaleY = data->mixScaleY;
-				constraint->mixShearY = data->mixShearY;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				constraint->mixRotate += (data->mixRotate - constraint->mixRotate) * alpha;
-				constraint->mixX += (data->mixX - constraint->mixX) * alpha;
-				constraint->mixY += (data->mixY - constraint->mixY) * alpha;
-				constraint->mixScaleX += (data->mixScaleX - constraint->mixScaleX) * alpha;
-				constraint->mixScaleY += (data->mixScaleY - constraint->mixScaleY) * alpha;
-				constraint->mixShearY += (data->mixShearY - constraint->mixShearY) * alpha;
-				return;
-			default:
-				return;
-		}
-	}
-
-	i = search2(self->super.super.frames, time, TRANSFORMCONSTRAINT_ENTRIES);
-	curveType = (int) curves[i / TRANSFORMCONSTRAINT_ENTRIES];
-	switch (curveType) {
-		case CURVE_LINEAR: {
-			float before = frames[i];
-			rotate = frames[i + TRANSFORMCONSTRAINT_ROTATE];
-			x = frames[i + TRANSFORMCONSTRAINT_X];
-			y = frames[i + TRANSFORMCONSTRAINT_Y];
-			scaleX = frames[i + TRANSFORMCONSTRAINT_SCALEX];
-			scaleY = frames[i + TRANSFORMCONSTRAINT_SCALEY];
-			shearY = frames[i + TRANSFORMCONSTRAINT_SHEARY];
-			t = (time - before) / (frames[i + TRANSFORMCONSTRAINT_ENTRIES] - before);
-			rotate += (frames[i + TRANSFORMCONSTRAINT_ENTRIES + TRANSFORMCONSTRAINT_ROTATE] - rotate) * t;
-			x += (frames[i + TRANSFORMCONSTRAINT_ENTRIES + TRANSFORMCONSTRAINT_X] - x) * t;
-			y += (frames[i + TRANSFORMCONSTRAINT_ENTRIES + TRANSFORMCONSTRAINT_Y] - y) * t;
-			scaleX += (frames[i + TRANSFORMCONSTRAINT_ENTRIES + TRANSFORMCONSTRAINT_SCALEX] - scaleX) * t;
-			scaleY += (frames[i + TRANSFORMCONSTRAINT_ENTRIES + TRANSFORMCONSTRAINT_SCALEY] - scaleY) * t;
-			shearY += (frames[i + TRANSFORMCONSTRAINT_ENTRIES + TRANSFORMCONSTRAINT_SHEARY] - shearY) * t;
-			break;
-		}
-		case CURVE_STEPPED: {
-			rotate = frames[i + TRANSFORMCONSTRAINT_ROTATE];
-			x = frames[i + TRANSFORMCONSTRAINT_X];
-			y = frames[i + TRANSFORMCONSTRAINT_Y];
-			scaleX = frames[i + TRANSFORMCONSTRAINT_SCALEX];
-			scaleY = frames[i + TRANSFORMCONSTRAINT_SCALEY];
-			shearY = frames[i + TRANSFORMCONSTRAINT_SHEARY];
-			break;
-		}
-		default: {
-			rotate = _spCurveTimeline_getBezierValue(SUPER(self), time, i, TRANSFORMCONSTRAINT_ROTATE,
-													 curveType - CURVE_BEZIER);
-			x = _spCurveTimeline_getBezierValue(SUPER(self), time, i, TRANSFORMCONSTRAINT_X,
-												curveType + BEZIER_SIZE - CURVE_BEZIER);
-			y = _spCurveTimeline_getBezierValue(SUPER(self), time, i, TRANSFORMCONSTRAINT_Y,
-												curveType + BEZIER_SIZE * 2 - CURVE_BEZIER);
-			scaleX = _spCurveTimeline_getBezierValue(SUPER(self), time, i, TRANSFORMCONSTRAINT_SCALEX,
-													 curveType + BEZIER_SIZE * 3 - CURVE_BEZIER);
-			scaleY = _spCurveTimeline_getBezierValue(SUPER(self), time, i, TRANSFORMCONSTRAINT_SCALEY,
-													 curveType + BEZIER_SIZE * 4 - CURVE_BEZIER);
-			shearY = _spCurveTimeline_getBezierValue(SUPER(self), time, i, TRANSFORMCONSTRAINT_SHEARY,
-													 curveType + BEZIER_SIZE * 5 - CURVE_BEZIER);
-		}
-	}
-
-	if (blend == SP_MIX_BLEND_SETUP) {
-		constraint->mixRotate = data->mixRotate + (rotate - data->mixRotate) * alpha;
-		constraint->mixX = data->mixX + (x - data->mixX) * alpha;
-		constraint->mixY = data->mixY + (y - data->mixY) * alpha;
-		constraint->mixScaleX = data->mixScaleX + (scaleX - data->mixScaleX) * alpha;
-		constraint->mixScaleY = data->mixScaleY + (scaleY - data->mixScaleY) * alpha;
-		constraint->mixShearY = data->mixShearY + (shearY - data->mixShearY) * alpha;
-	} else {
-		constraint->mixRotate += (rotate - constraint->mixRotate) * alpha;
-		constraint->mixX += (x - constraint->mixX) * alpha;
-		constraint->mixY += (y - constraint->mixY) * alpha;
-		constraint->mixScaleX += (scaleX - constraint->mixScaleX) * alpha;
-		constraint->mixScaleY += (scaleY - constraint->mixScaleY) * alpha;
-		constraint->mixShearY += (shearY - constraint->mixShearY) * alpha;
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spTransformConstraintTimeline *
-spTransformConstraintTimeline_create(int framesCount, int bezierCount, int transformConstraintIndex) {
-	spTransformConstraintTimeline *timeline = NEW(spTransformConstraintTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_TRANSFORMCONSTRAINT << 32) | transformConstraintIndex;
-	_spCurveTimeline_init(SUPER(timeline), framesCount, TRANSFORMCONSTRAINT_ENTRIES, bezierCount, ids, 1,
-						  SP_TIMELINE_TRANSFORMCONSTRAINT, _spCurveTimeline_dispose,
-						  _spTransformConstraintTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->transformConstraintIndex = transformConstraintIndex;
-	return timeline;
-}
-
-void spTransformConstraintTimeline_setFrame(spTransformConstraintTimeline *self, int frame, float time, float mixRotate,
-											float mixX, float mixY, float mixScaleX, float mixScaleY, float mixShearY) {
-	float *frames = self->super.super.frames->items;
-	frame *= TRANSFORMCONSTRAINT_ENTRIES;
-	frames[frame] = time;
-	frames[frame + TRANSFORMCONSTRAINT_ROTATE] = mixRotate;
-	frames[frame + TRANSFORMCONSTRAINT_X] = mixX;
-	frames[frame + TRANSFORMCONSTRAINT_X] = mixY;
-	frames[frame + TRANSFORMCONSTRAINT_SCALEX] = mixScaleX;
-	frames[frame + TRANSFORMCONSTRAINT_SCALEY] = mixScaleY;
-	frames[frame + TRANSFORMCONSTRAINT_SHEARY] = mixShearY;
-}
-
-/**/
-static const int PATHCONSTRAINTPOSITION_ENTRIES = 2;
-static const int PATHCONSTRAINTPOSITION_VALUE = 1;
-
-void _spPathConstraintPositionTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-											 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-											 spMixDirection direction
-) {
-	float position;
-	spPathConstraint *constraint;
-	spPathConstraintPositionTimeline *self = (spPathConstraintPositionTimeline *) timeline;
-	float *frames;
-
-	constraint = skeleton->pathConstraints[self->pathConstraintIndex];
-	if (!constraint->active) return;
-
-	frames = self->super.super.frames->items;
-
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				constraint->position = constraint->data->position;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				constraint->position += (constraint->data->position - constraint->position) * alpha;
-				return;
-			default:
-				return;
-		}
-	}
-
-	position = spCurveTimeline1_getCurveValue(SUPER(self), time);
-
-	if (blend == SP_MIX_BLEND_SETUP)
-		constraint->position = constraint->data->position + (position - constraint->data->position) * alpha;
-	else
-		constraint->position += (position - constraint->position) * alpha;
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spPathConstraintPositionTimeline *
-spPathConstraintPositionTimeline_create(int framesCount, int bezierCount, int pathConstraintIndex) {
-	spPathConstraintPositionTimeline *timeline = NEW(spPathConstraintPositionTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_PATHCONSTRAINT_POSITION << 32) | pathConstraintIndex;
-	_spCurveTimeline_init(SUPER(timeline), framesCount, PATHCONSTRAINTPOSITION_ENTRIES, bezierCount, ids, 1,
-						  SP_TIMELINE_PATHCONSTRAINTPOSITION, _spCurveTimeline_dispose,
-						  _spPathConstraintPositionTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->pathConstraintIndex = pathConstraintIndex;
-	return timeline;
-}
-
-void
-spPathConstraintPositionTimeline_setFrame(spPathConstraintPositionTimeline *self, int frame, float time, float value) {
-	float *frames = self->super.super.frames->items;
-	frame *= PATHCONSTRAINTPOSITION_ENTRIES;
-	frames[frame] = time;
-	frames[frame + PATHCONSTRAINTPOSITION_VALUE] = value;
-}
-
-/**/
-static const int PATHCONSTRAINTSPACING_ENTRIES = 2;
-static const int PATHCONSTRAINTSPACING_VALUE = 1;
-
-void _spPathConstraintSpacingTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-											spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-											spMixDirection direction
-) {
-	float spacing;
-	spPathConstraint *constraint;
-	spPathConstraintSpacingTimeline *self = (spPathConstraintSpacingTimeline *) timeline;
-	float *frames;
-
-	constraint = skeleton->pathConstraints[self->pathConstraintIndex];
-	if (!constraint->active) return;
-
-	frames = self->super.super.frames->items;
-
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				constraint->spacing = constraint->data->spacing;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				constraint->spacing += (constraint->data->spacing - constraint->spacing) * alpha;
-				return;
-			default:
-				return;
-		}
-	}
-
-	spacing = spCurveTimeline1_getCurveValue(SUPER(self), time);
-
-	if (blend == SP_MIX_BLEND_SETUP)
-		constraint->spacing = constraint->data->spacing + (spacing - constraint->data->spacing) * alpha;
-	else
-		constraint->spacing += (spacing - constraint->spacing) * alpha;
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spPathConstraintSpacingTimeline *
-spPathConstraintSpacingTimeline_create(int framesCount, int bezierCount, int pathConstraintIndex) {
-	spPathConstraintSpacingTimeline *timeline = NEW(spPathConstraintSpacingTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_PATHCONSTRAINT_SPACING << 32) | pathConstraintIndex;
-	_spCurveTimeline_init(SUPER(timeline), framesCount, PATHCONSTRAINTSPACING_ENTRIES, bezierCount, ids, 1,
-						  SP_TIMELINE_PATHCONSTRAINTSPACING, _spCurveTimeline_dispose,
-						  _spPathConstraintSpacingTimeline_apply, _spCurveTimeline_setBezier);
-	timeline->pathConstraintIndex = pathConstraintIndex;
-	return timeline;
-}
-
-void
-spPathConstraintSpacingTimeline_setFrame(spPathConstraintSpacingTimeline *self, int frame, float time, float value) {
-	float *frames = self->super.super.frames->items;
-	frame *= PATHCONSTRAINTSPACING_ENTRIES;
-	frames[frame] = time;
-	frames[frame + PATHCONSTRAINTSPACING_VALUE] = value;
-}
-
-/**/
-
-static const int PATHCONSTRAINTMIX_ENTRIES = 4;
-static const int PATHCONSTRAINTMIX_ROTATE = 1;
-static const int PATHCONSTRAINTMIX_X = 2;
-static const int PATHCONSTRAINTMIX_Y = 3;
-
-void _spPathConstraintMixTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
-										spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
-										spMixDirection direction
-) {
-	int i, curveType;
-	float rotate, x, y, t;
-	spPathConstraint *constraint;
-	spPathConstraintMixTimeline *self = (spPathConstraintMixTimeline *) timeline;
-	float *frames;
-	float *curves;
-
-	constraint = skeleton->pathConstraints[self->pathConstraintIndex];
-	if (!constraint->active) return;
-
-	frames = self->super.super.frames->items;
-	curves = self->super.curves->items;
-
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				constraint->mixRotate = constraint->data->mixRotate;
-				constraint->mixX = constraint->data->mixX;
-				constraint->mixY = constraint->data->mixY;
-				return;
-			case SP_MIX_BLEND_FIRST:
-				constraint->mixRotate += (constraint->data->mixRotate - constraint->mixRotate) * alpha;
-				constraint->mixX += (constraint->data->mixX - constraint->mixX) * alpha;
-				constraint->mixY += (constraint->data->mixY - constraint->mixY) * alpha;
-			default: {
-			}
-		}
-		return;
-	}
-
-	i = search2(self->super.super.frames, time, PATHCONSTRAINTMIX_ENTRIES);
-	curveType = (int) curves[i >> 2];
-	switch (curveType) {
-		case CURVE_LINEAR: {
-			float before = frames[i];
-			rotate = frames[i + PATHCONSTRAINTMIX_ROTATE];
-			x = frames[i + PATHCONSTRAINTMIX_X];
-			y = frames[i + PATHCONSTRAINTMIX_Y];
-			t = (time - before) / (frames[i + PATHCONSTRAINTMIX_ENTRIES] - before);
-			rotate += (frames[i + PATHCONSTRAINTMIX_ENTRIES + PATHCONSTRAINTMIX_ROTATE] - rotate) * t;
-			x += (frames[i + PATHCONSTRAINTMIX_ENTRIES + PATHCONSTRAINTMIX_X] - x) * t;
-			y += (frames[i + PATHCONSTRAINTMIX_ENTRIES + PATHCONSTRAINTMIX_Y] - y) * t;
-			break;
-		}
-		case CURVE_STEPPED: {
-			rotate = frames[i + PATHCONSTRAINTMIX_ROTATE];
-			x = frames[i + PATHCONSTRAINTMIX_X];
-			y = frames[i + PATHCONSTRAINTMIX_Y];
-			break;
-		}
-		default: {
-			rotate = _spCurveTimeline_getBezierValue(SUPER(self), time, i, PATHCONSTRAINTMIX_ROTATE,
-													 curveType - CURVE_BEZIER);
-			x = _spCurveTimeline_getBezierValue(SUPER(self), time, i, PATHCONSTRAINTMIX_X,
-												curveType + BEZIER_SIZE - CURVE_BEZIER);
-			y = _spCurveTimeline_getBezierValue(SUPER(self), time, i, PATHCONSTRAINTMIX_Y,
-												curveType + BEZIER_SIZE * 2 - CURVE_BEZIER);
-		}
-	}
-
-	if (blend == SP_MIX_BLEND_SETUP) {
-		spPathConstraintData *data = constraint->data;
-		constraint->mixRotate = data->mixRotate + (rotate - data->mixRotate) * alpha;
-		constraint->mixX = data->mixX + (x - data->mixX) * alpha;
-		constraint->mixY = data->mixY + (y - data->mixY) * alpha;
-	} else {
-		constraint->mixRotate += (rotate - constraint->mixRotate) * alpha;
-		constraint->mixX += (x - constraint->mixX) * alpha;
-		constraint->mixY += (y - constraint->mixY) * alpha;
-	}
-
-	UNUSED(lastTime);
-	UNUSED(firedEvents);
-	UNUSED(eventsCount);
-	UNUSED(direction);
-}
-
-spPathConstraintMixTimeline *
-spPathConstraintMixTimeline_create(int framesCount, int bezierCount, int pathConstraintIndex) {
-	spPathConstraintMixTimeline *timeline = NEW(spPathConstraintMixTimeline);
-	spPropertyId ids[1];
-	ids[0] = ((spPropertyId) SP_PROPERTY_PATHCONSTRAINT_MIX << 32) | pathConstraintIndex;
-	_spCurveTimeline_init(SUPER(timeline), framesCount, PATHCONSTRAINTMIX_ENTRIES, bezierCount, ids, 1,
-						  SP_TIMELINE_PATHCONSTRAINTMIX, _spCurveTimeline_dispose, _spPathConstraintMixTimeline_apply,
-						  _spCurveTimeline_setBezier);
-	timeline->pathConstraintIndex = pathConstraintIndex;
-	return timeline;
-}
-
-void spPathConstraintMixTimeline_setFrame(spPathConstraintMixTimeline *self, int frame, float time, float mixRotate,
-										  float mixX, float mixY) {
-	float *frames = self->super.super.frames->items;
-	frame *= PATHCONSTRAINTMIX_ENTRIES;
-	frames[frame] = time;
-	frames[frame + PATHCONSTRAINTMIX_ROTATE] = mixRotate;
-	frames[frame + PATHCONSTRAINTMIX_X] = mixX;
-	frames[frame + PATHCONSTRAINTMIX_Y] = mixY;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <limits.h>
+#include <spine/Animation.h>
+#include <spine/IkConstraint.h>
+#include <spine/extension.h>
+
+_SP_ARRAY_IMPLEMENT_TYPE(spPropertyIdArray, spPropertyId)
+
+_SP_ARRAY_IMPLEMENT_TYPE(spTimelineArray, spTimeline *)
+
+spAnimation *spAnimation_create(const char *name, spTimelineArray *timelines, float duration) {
+	int i, n;
+	spAnimation *self = NEW(spAnimation);
+	MALLOC_STR(self->name, name);
+	self->timelines = timelines != NULL ? timelines : spTimelineArray_create(1);
+	timelines = self->timelines;
+	self->timelineIds = spPropertyIdArray_create(16);
+	for (i = 0, n = timelines->size; i < n; i++) {
+		spPropertyIdArray_addAllValues(self->timelineIds, timelines->items[i]->propertyIds, 0,
+									   timelines->items[i]->propertyIdsCount);
+	}
+	self->duration = duration;
+	return self;
+}
+
+void spAnimation_dispose(spAnimation *self) {
+	int i;
+	for (i = 0; i < self->timelines->size; ++i)
+		spTimeline_dispose(self->timelines->items[i]);
+	spTimelineArray_dispose(self->timelines);
+	spPropertyIdArray_dispose(self->timelineIds);
+	FREE(self->name);
+	FREE(self);
+}
+
+int /*bool*/ spAnimation_hasTimeline(spAnimation *self, spPropertyId *ids, int idsCount) {
+	int i, n, ii;
+	for (i = 0, n = self->timelineIds->size; i < n; i++) {
+		for (ii = 0; ii < idsCount; ii++) {
+			if (self->timelineIds->items[i] == ids[ii]) return 1;
+		}
+	}
+	return 0;
+}
+
+void spAnimation_apply(const spAnimation *self, spSkeleton *skeleton, float lastTime, float time, int loop, spEvent **events,
+					   int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
+	int i, n = self->timelines->size;
+
+	if (loop && self->duration) {
+		time = FMOD(time, self->duration);
+		if (lastTime > 0) lastTime = FMOD(lastTime, self->duration);
+	}
+
+	for (i = 0; i < n; ++i)
+		spTimeline_apply(self->timelines->items[i], skeleton, lastTime, time, events, eventsCount, alpha, blend,
+						 direction);
+}
+
+static search(spFloatArray
+					  *values,
+			  float time) {
+	int i, n;
+	float *items = values->items;
+	for (
+			i = 1, n = values->size;
+			i < n;
+			i++)
+		if (items[i] > time) return i - 1;
+	return values->size - 1;
+}
+
+static search2(spFloatArray
+					   *values,
+			   float time,
+			   int step) {
+	int i, n;
+	float *items = values->items;
+	for (
+			i = step, n = values->size;
+			i < n;
+			i += step)
+		if (items[i] > time) return i -
+									step;
+	return values->size -
+		   step;
+}
+
+/**/
+
+void _spTimeline_init(spTimeline *self,
+					  int frameCount,
+					  int frameEntries,
+					  spPropertyId *propertyIds,
+					  int propertyIdsCount,
+					  spTimelineType type,
+					  void (*dispose)(spTimeline *self),
+					  void (*apply)(spTimeline *self, spSkeleton *skeleton, float lastTime, float time,
+									spEvent **firedEvents,
+									int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction),
+					  void (*setBezier)(spTimeline *self, int bezier, int frame, float value, float time1, float value1,
+										float cx1, float cy1,
+										float cx2, float cy2, float time2, float value2)) {
+	int i;
+	self->frames = spFloatArray_create(frameCount * frameEntries);
+	self->frames->size = frameCount * frameEntries;
+	self->frameCount = frameCount;
+	self->frameEntries = frameEntries;
+
+	for (i = 0; i < propertyIdsCount; i++)
+		self->propertyIds[i] = propertyIds[i];
+	self->propertyIdsCount = propertyIdsCount;
+
+	self->type = type;
+
+	self->vtable.dispose = dispose;
+	self->vtable.apply = apply;
+	self->vtable.setBezier = setBezier;
+}
+
+void spTimeline_dispose(spTimeline *self) {
+	self->vtable.dispose(self);
+	spFloatArray_dispose(self->frames);
+	FREE(self);
+}
+
+void spTimeline_apply(spTimeline *self, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
+					  int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
+	self->vtable.apply(self, skeleton, lastTime, time, firedEvents, eventsCount, alpha, blend, direction);
+}
+
+void spTimeline_setBezier(spTimeline *self, int bezier, int frame, float value, float time1, float value1, float cx1,
+						  float cy1, float cx2, float cy2, float time2, float value2) {
+	if (self->vtable.setBezier)
+		self->vtable.setBezier(self, bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
+}
+
+float spTimeline_getDuration(const spTimeline *self) {
+	return self->frames->items[self->frames->size - self->frameEntries];
+}
+
+/**/
+
+#define CURVE_LINEAR 0
+#define CURVE_STEPPED 1
+#define CURVE_BEZIER 2
+#define BEZIER_SIZE 18
+
+void _spCurveTimeline_init(spCurveTimeline *self,
+						   int frameCount,
+						   int frameEntries,
+						   int bezierCount,
+						   spPropertyId *propertyIds,
+						   int propertyIdsCount,
+						   spTimelineType type,
+						   void (*dispose)(spTimeline *self),
+						   void (*apply)(spTimeline *self, spSkeleton *skeleton, float lastTime, float time,
+										 spEvent **firedEvents,
+										 int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction),
+						   void (*setBezier)(spTimeline *self, int bezier, int frame, float value, float time1,
+											 float value1, float cx1, float cy1,
+											 float cx2, float cy2, float time2, float value2)) {
+	_spTimeline_init(SUPER(self), frameCount, frameEntries, propertyIds, propertyIdsCount, type, dispose, apply,
+					 setBezier);
+	self->curves = spFloatArray_create(frameCount + bezierCount * BEZIER_SIZE);
+	self->curves->size = frameCount + bezierCount * BEZIER_SIZE;
+	self->curves->items[frameCount - 1] = CURVE_STEPPED;
+}
+
+void _spCurveTimeline_dispose(spTimeline *self) {
+	spFloatArray_dispose(SUB_CAST(spCurveTimeline, self)->curves);
+}
+
+void _spCurveTimeline_setBezier(spTimeline *timeline, int bezier, int frame, float value, float time1, float value1,
+								float cx1, float cy1, float cx2, float cy2, float time2, float value2) {
+	spCurveTimeline *self = SUB_CAST(spCurveTimeline, timeline);
+	float tmpx, tmpy, dddx, dddy, ddx, ddy, dx, dy, x, y;
+	int i = self->super.frameCount + bezier * BEZIER_SIZE, n;
+	float *curves = self->curves->items;
+	if (value == 0) curves[frame] = CURVE_BEZIER + i;
+	tmpx = (time1 - cx1 * 2 + cx2) * 0.03;
+	tmpy = (value1 - cy1 * 2 + cy2) * 0.03;
+	dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006;
+	dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006;
+	ddx = tmpx * 2 + dddx;
+	ddy = tmpy * 2 + dddy;
+	dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667;
+	dy = (cy1 - value1) * 0.3 + tmpy + dddy * 0.16666667;
+	x = time1 + dx, y = value1 + dy;
+	for (n = i + BEZIER_SIZE; i < n; i += 2) {
+		curves[i] = x;
+		curves[i + 1] = y;
+		dx += ddx;
+		dy += ddy;
+		ddx += dddx;
+		ddy += dddy;
+		x += dx;
+		y += dy;
+	}
+}
+
+float _spCurveTimeline_getBezierValue(spCurveTimeline *self, float time, int frameIndex, int valueOffset, int i) {
+	float *curves = self->curves->items;
+	float *frames = SUPER(self)->frames->items;
+	float x, y;
+	int n;
+	if (curves[i] > time) {
+		x = frames[frameIndex];
+		y = frames[frameIndex + valueOffset];
+		return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
+	}
+	n = i + BEZIER_SIZE;
+	for (i += 2; i < n; i += 2) {
+		if (curves[i] >= time) {
+			x = curves[i - 2];
+			y = curves[i - 1];
+			return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
+		}
+	}
+	frameIndex += self->super.frameEntries;
+	x = curves[n - 2];
+	y = curves[n - 1];
+	return y + (time - x) / (frames[frameIndex] - x) * (frames[frameIndex + valueOffset] - y);
+}
+
+void spCurveTimeline_setLinear(spCurveTimeline *self, int frame) {
+	self->curves->items[frame] = CURVE_LINEAR;
+}
+
+void spCurveTimeline_setStepped(spCurveTimeline *self, int frame) {
+	self->curves->items[frame] = CURVE_STEPPED;
+}
+
+#define CURVE1_ENTRIES 2
+#define CURVE1_VALUE 1
+
+void spCurveTimeline1_setFrame(spCurveTimeline1 *self, int frame, float time, float value) {
+	float *frames = self->super.frames->items;
+	frame <<= 1;
+	frames[frame] = time;
+	frames[frame + CURVE1_VALUE] = value;
+}
+
+float spCurveTimeline1_getCurveValue(spCurveTimeline1 *self, float time) {
+	float *frames = self->super.frames->items;
+	float *curves = self->curves->items;
+	int i = self->super.frames->size - 2;
+	int ii, curveType;
+	for (ii = 2; ii <= i; ii += 2) {
+		if (frames[ii] > time) {
+			i = ii - 2;
+			break;
+		}
+	}
+
+	curveType = (int) curves[i >> 1];
+	switch (curveType) {
+		case CURVE_LINEAR: {
+			float before = frames[i], value = frames[i + CURVE1_VALUE];
+			return value + (time - before) / (frames[i + CURVE1_ENTRIES] - before) *
+								   (frames[i + CURVE1_ENTRIES + CURVE1_VALUE] - value);
+		}
+		case CURVE_STEPPED:
+			return frames[i + CURVE1_VALUE];
+	}
+	return _spCurveTimeline_getBezierValue(self, time, i, CURVE1_VALUE, curveType - CURVE_BEZIER);
+}
+
+#define CURVE2_ENTRIES 3
+#define CURVE2_VALUE1 1
+#define CURVE2_VALUE2 2
+
+SP_API void spCurveTimeline2_setFrame(spCurveTimeline1 *self, int frame, float time, float value1, float value2) {
+	float *frames = self->super.frames->items;
+	frame *= CURVE2_ENTRIES;
+	frames[frame] = time;
+	frames[frame + CURVE2_VALUE1] = value1;
+	frames[frame + CURVE2_VALUE2] = value2;
+}
+
+/**/
+
+void _spRotateTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
+							 int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
+	spBone *bone;
+	float r;
+	spRotateTimeline *self = SUB_CAST(spRotateTimeline, timeline);
+	float *frames = self->super.super.frames->items;
+
+	bone = skeleton->bones[self->boneIndex];
+	if (!bone->active) return;
+
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				bone->rotation = bone->data->rotation;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				bone->rotation += (bone->data->rotation - bone->rotation) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	r = spCurveTimeline1_getCurveValue(SUPER(self), time);
+	switch (blend) {
+		case SP_MIX_BLEND_SETUP:
+			bone->rotation = bone->data->rotation + r * alpha;
+			break;
+		case SP_MIX_BLEND_FIRST:
+		case SP_MIX_BLEND_REPLACE:
+			r += bone->data->rotation - bone->rotation;
+		case SP_MIX_BLEND_ADD:
+			bone->rotation += r * alpha;
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spRotateTimeline *spRotateTimeline_create(int frameCount, int bezierCount, int boneIndex) {
+	spRotateTimeline *timeline = NEW(spRotateTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_ROTATE << 32) | boneIndex;
+	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_ROTATE,
+						  _spCurveTimeline_dispose, _spRotateTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->boneIndex = boneIndex;
+	return timeline;
+}
+
+void spRotateTimeline_setFrame(spRotateTimeline *self, int frame, float time, float degrees) {
+	spCurveTimeline1_setFrame(SUPER(self), frame, time, degrees);
+}
+
+/**/
+
+void _spTranslateTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+								spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+								spMixDirection direction) {
+	spBone *bone;
+	float x, y, t;
+	int i, curveType;
+
+	spTranslateTimeline *self = SUB_CAST(spTranslateTimeline, timeline);
+	float *frames = self->super.super.frames->items;
+	float *curves = self->super.curves->items;
+
+	bone = skeleton->bones[self->boneIndex];
+	if (!bone->active) return;
+
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				bone->x = bone->data->x;
+				bone->y = bone->data->y;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				bone->x += (bone->data->x - bone->x) * alpha;
+				bone->y += (bone->data->y - bone->y) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	i = search2(self->super.super.frames, time, CURVE2_ENTRIES);
+	curveType = (int) curves[i / CURVE2_ENTRIES];
+	switch (curveType) {
+		case CURVE_LINEAR: {
+			float before = frames[i];
+			x = frames[i + CURVE2_VALUE1];
+			y = frames[i + CURVE2_VALUE2];
+			t = (time - before) / (frames[i + CURVE2_ENTRIES] - before);
+			x += (frames[i + CURVE2_ENTRIES + CURVE2_VALUE1] - x) * t;
+			y += (frames[i + CURVE2_ENTRIES + CURVE2_VALUE2] - y) * t;
+			break;
+		}
+		case CURVE_STEPPED: {
+			x = frames[i + CURVE2_VALUE1];
+			y = frames[i + CURVE2_VALUE2];
+			break;
+		}
+		default: {
+			x = _spCurveTimeline_getBezierValue(SUPER(self), time, i, CURVE2_VALUE1, curveType - CURVE_BEZIER);
+			y = _spCurveTimeline_getBezierValue(SUPER(self), time, i, CURVE2_VALUE2,
+												curveType + BEZIER_SIZE - CURVE_BEZIER);
+		}
+	}
+
+	switch (blend) {
+		case SP_MIX_BLEND_SETUP:
+			bone->x = bone->data->x + x * alpha;
+			bone->y = bone->data->y + y * alpha;
+			break;
+		case SP_MIX_BLEND_FIRST:
+		case SP_MIX_BLEND_REPLACE:
+			bone->x += (bone->data->x + x - bone->x) * alpha;
+			bone->y += (bone->data->y + y - bone->y) * alpha;
+			break;
+		case SP_MIX_BLEND_ADD:
+			bone->x += x * alpha;
+			bone->y += y * alpha;
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spTranslateTimeline *spTranslateTimeline_create(int frameCount, int bezierCount, int boneIndex) {
+	spTranslateTimeline *timeline = NEW(spTranslateTimeline);
+	spPropertyId ids[2];
+	ids[0] = ((spPropertyId) SP_PROPERTY_X << 32) | boneIndex;
+	ids[1] = ((spPropertyId) SP_PROPERTY_Y << 32) | boneIndex;
+	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE2_ENTRIES, bezierCount, ids, 2, SP_TIMELINE_TRANSLATE,
+						  _spCurveTimeline_dispose, _spTranslateTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->boneIndex = boneIndex;
+	return timeline;
+}
+
+void spTranslateTimeline_setFrame(spTranslateTimeline *self, int frame, float time, float x, float y) {
+	spCurveTimeline2_setFrame(SUPER(self), frame, time, x, y);
+}
+
+/**/
+
+void _spTranslateXTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+								 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+								 spMixDirection direction) {
+	spBone *bone;
+	float x;
+
+	spTranslateXTimeline *self = SUB_CAST(spTranslateXTimeline, timeline);
+	float *frames = self->super.super.frames->items;
+
+	bone = skeleton->bones[self->boneIndex];
+	if (!bone->active) return;
+
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				bone->x = bone->data->x;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				bone->x += (bone->data->x - bone->x) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	x = spCurveTimeline1_getCurveValue(SUPER(self), time);
+	switch (blend) {
+		case SP_MIX_BLEND_SETUP:
+			bone->x = bone->data->x + x * alpha;
+			break;
+		case SP_MIX_BLEND_FIRST:
+		case SP_MIX_BLEND_REPLACE:
+			bone->x += (bone->data->x + x - bone->x) * alpha;
+			break;
+		case SP_MIX_BLEND_ADD:
+			bone->x += x * alpha;
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spTranslateXTimeline *spTranslateXTimeline_create(int frameCount, int bezierCount, int boneIndex) {
+	spTranslateXTimeline *timeline = NEW(spTranslateXTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_X << 32) | boneIndex;
+	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_TRANSLATEX,
+						  _spCurveTimeline_dispose, _spTranslateXTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->boneIndex = boneIndex;
+	return timeline;
+}
+
+void spTranslateXTimeline_setFrame(spTranslateXTimeline *self, int frame, float time, float x) {
+	spCurveTimeline1_setFrame(SUPER(self), frame, time, x);
+}
+
+/**/
+
+void _spTranslateYTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+								 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+								 spMixDirection direction) {
+	spBone *bone;
+	float y;
+
+	spTranslateYTimeline *self = SUB_CAST(spTranslateYTimeline, timeline);
+	float *frames = self->super.super.frames->items;
+
+	bone = skeleton->bones[self->boneIndex];
+	if (!bone->active) return;
+
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				bone->y = bone->data->y;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				bone->y += (bone->data->y - bone->y) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	y = spCurveTimeline1_getCurveValue(SUPER(self), time);
+	switch (blend) {
+		case SP_MIX_BLEND_SETUP:
+			bone->y = bone->data->y + y * alpha;
+			break;
+		case SP_MIX_BLEND_FIRST:
+		case SP_MIX_BLEND_REPLACE:
+			bone->y += (bone->data->y + y - bone->y) * alpha;
+			break;
+		case SP_MIX_BLEND_ADD:
+			bone->y += y * alpha;
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spTranslateYTimeline *spTranslateYTimeline_create(int frameCount, int bezierCount, int boneIndex) {
+	spTranslateYTimeline *timeline = NEW(spTranslateYTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_Y << 32) | boneIndex;
+	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_TRANSLATEY,
+						  _spCurveTimeline_dispose, _spTranslateYTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->boneIndex = boneIndex;
+	return timeline;
+}
+
+void spTranslateYTimeline_setFrame(spTranslateYTimeline *self, int frame, float time, float y) {
+	spCurveTimeline1_setFrame(SUPER(self), frame, time, y);
+}
+
+/**/
+
+void _spScaleTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
+							int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
+	spBone *bone;
+	int i, curveType;
+	float x, y, t;
+
+	spScaleTimeline *self = SUB_CAST(spScaleTimeline, timeline);
+	float *frames = self->super.super.frames->items;
+	float *curves = self->super.curves->items;
+
+	bone = skeleton->bones[self->boneIndex];
+	if (!bone->active) return;
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				bone->scaleX = bone->data->scaleX;
+				bone->scaleY = bone->data->scaleY;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				bone->scaleX += (bone->data->scaleX - bone->scaleX) * alpha;
+				bone->scaleY += (bone->data->scaleY - bone->scaleY) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	i = search2(self->super.super.frames, time, CURVE2_ENTRIES);
+	curveType = (int) curves[i / CURVE2_ENTRIES];
+	switch (curveType) {
+		case CURVE_LINEAR: {
+			float before = frames[i];
+			x = frames[i + CURVE2_VALUE1];
+			y = frames[i + CURVE2_VALUE2];
+			t = (time - before) / (frames[i + CURVE2_ENTRIES] - before);
+			x += (frames[i + CURVE2_ENTRIES + CURVE2_VALUE1] - x) * t;
+			y += (frames[i + CURVE2_ENTRIES + CURVE2_VALUE2] - y) * t;
+			break;
+		}
+		case CURVE_STEPPED: {
+			x = frames[i + CURVE2_VALUE1];
+			y = frames[i + CURVE2_VALUE2];
+			break;
+		}
+		default: {
+			x = _spCurveTimeline_getBezierValue(SUPER(self), time, i, CURVE2_VALUE1, curveType - CURVE_BEZIER);
+			y = _spCurveTimeline_getBezierValue(SUPER(self), time, i, CURVE2_VALUE2,
+												curveType + BEZIER_SIZE - CURVE_BEZIER);
+		}
+	}
+	x *= bone->data->scaleX;
+	y *= bone->data->scaleY;
+
+	if (alpha == 1) {
+		if (blend == SP_MIX_BLEND_ADD) {
+			bone->scaleX += x - bone->data->scaleX;
+			bone->scaleY += y - bone->data->scaleY;
+		} else {
+			bone->scaleX = x;
+			bone->scaleY = y;
+		}
+	} else {
+		float bx, by;
+		if (direction == SP_MIX_DIRECTION_OUT) {
+			switch (blend) {
+				case SP_MIX_BLEND_SETUP:
+					bx = bone->data->scaleX;
+					by = bone->data->scaleY;
+					bone->scaleX = bx + (ABS(x) * SIGNUM(bx) - bx) * alpha;
+					bone->scaleY = by + (ABS(y) * SIGNUM(by) - by) * alpha;
+					break;
+				case SP_MIX_BLEND_FIRST:
+				case SP_MIX_BLEND_REPLACE:
+					bx = bone->scaleX;
+					by = bone->scaleY;
+					bone->scaleX = bx + (ABS(x) * SIGNUM(bx) - bx) * alpha;
+					bone->scaleY = by + (ABS(y) * SIGNUM(by) - by) * alpha;
+					break;
+				case SP_MIX_BLEND_ADD:
+					bx = bone->scaleX;
+					by = bone->scaleY;
+					bone->scaleX = bx + (ABS(x) * SIGNUM(bx) - bone->data->scaleX) * alpha;
+					bone->scaleY = by + (ABS(y) * SIGNUM(by) - bone->data->scaleY) * alpha;
+			}
+		} else {
+			switch (blend) {
+				case SP_MIX_BLEND_SETUP:
+					bx = ABS(bone->data->scaleX) * SIGNUM(x);
+					by = ABS(bone->data->scaleY) * SIGNUM(y);
+					bone->scaleX = bx + (x - bx) * alpha;
+					bone->scaleY = by + (y - by) * alpha;
+					break;
+				case SP_MIX_BLEND_FIRST:
+				case SP_MIX_BLEND_REPLACE:
+					bx = ABS(bone->scaleX) * SIGNUM(x);
+					by = ABS(bone->scaleY) * SIGNUM(y);
+					bone->scaleX = bx + (x - bx) * alpha;
+					bone->scaleY = by + (y - by) * alpha;
+					break;
+				case SP_MIX_BLEND_ADD:
+					bx = SIGNUM(x);
+					by = SIGNUM(y);
+					bone->scaleX = ABS(bone->scaleX) * bx + (x - ABS(bone->data->scaleX) * bx) * alpha;
+					bone->scaleY = ABS(bone->scaleY) * by + (y - ABS(bone->data->scaleY) * by) * alpha;
+			}
+		}
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+}
+
+spScaleTimeline *spScaleTimeline_create(int frameCount, int bezierCount, int boneIndex) {
+	spScaleTimeline *timeline = NEW(spScaleTimeline);
+	spPropertyId ids[2];
+	ids[0] = ((spPropertyId) SP_PROPERTY_SCALEX << 32) | boneIndex;
+	ids[1] = ((spPropertyId) SP_PROPERTY_SCALEY << 32) | boneIndex;
+	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE2_ENTRIES, bezierCount, ids, 2, SP_TIMELINE_SCALE,
+						  _spCurveTimeline_dispose, _spScaleTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->boneIndex = boneIndex;
+	return timeline;
+}
+
+void spScaleTimeline_setFrame(spScaleTimeline *self, int frame, float time, float x, float y) {
+	spCurveTimeline2_setFrame(SUPER(self), frame, time, x, y);
+}
+
+/**/
+
+void _spScaleXTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+							 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+							 spMixDirection direction) {
+	spBone *bone;
+	float x;
+
+	spScaleXTimeline *self = SUB_CAST(spScaleXTimeline, timeline);
+	float *frames = self->super.super.frames->items;
+
+	bone = skeleton->bones[self->boneIndex];
+	if (!bone->active) return;
+
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				bone->scaleX = bone->data->scaleX;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				bone->scaleX += (bone->data->scaleX - bone->scaleX) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	x = spCurveTimeline1_getCurveValue(SUPER(self), time) * bone->data->scaleX;
+	if (alpha == 1) {
+		if (blend == SP_MIX_BLEND_ADD)
+			bone->scaleX += x - bone->data->scaleX;
+		else
+			bone->scaleX = x;
+	} else {
+		/* Mixing out uses sign of setup or current pose, else use sign of key. */
+		float bx;
+		if (direction == SP_MIX_DIRECTION_OUT) {
+			switch (blend) {
+				case SP_MIX_BLEND_SETUP:
+					bx = bone->data->scaleX;
+					bone->scaleX = bx + (ABS(x) * SIGNUM(bx) - bx) * alpha;
+					break;
+				case SP_MIX_BLEND_FIRST:
+				case SP_MIX_BLEND_REPLACE:
+					bx = bone->scaleX;
+					bone->scaleX = bx + (ABS(x) * SIGNUM(bx) - bx) * alpha;
+					break;
+				case SP_MIX_BLEND_ADD:
+					bx = bone->scaleX;
+					bone->scaleX = bx + (ABS(x) * SIGNUM(bx) - bone->data->scaleX) * alpha;
+			}
+		} else {
+			switch (blend) {
+				case SP_MIX_BLEND_SETUP:
+					bx = ABS(bone->data->scaleX) * SIGNUM(x);
+					bone->scaleX = bx + (x - bx) * alpha;
+					break;
+				case SP_MIX_BLEND_FIRST:
+				case SP_MIX_BLEND_REPLACE:
+					bx = ABS(bone->scaleX) * SIGNUM(x);
+					bone->scaleX = bx + (x - bx) * alpha;
+					break;
+				case SP_MIX_BLEND_ADD:
+					bx = SIGNUM(x);
+					bone->scaleX = ABS(bone->scaleX) * bx + (x - ABS(bone->data->scaleX) * bx) * alpha;
+			}
+		}
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+}
+
+spScaleXTimeline *spScaleXTimeline_create(int frameCount, int bezierCount, int boneIndex) {
+	spScaleXTimeline *timeline = NEW(spScaleXTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_SCALEX << 32) | boneIndex;
+	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_SCALEX,
+						  _spCurveTimeline_dispose, _spScaleXTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->boneIndex = boneIndex;
+	return timeline;
+}
+
+void spScaleXTimeline_setFrame(spScaleXTimeline *self, int frame, float time, float y) {
+	spCurveTimeline1_setFrame(SUPER(self), frame, time, y);
+}
+
+/**/
+
+void _spScaleYTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+							 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+							 spMixDirection direction) {
+	spBone *bone;
+	float y;
+
+	spScaleYTimeline *self = SUB_CAST(spScaleYTimeline, timeline);
+	float *frames = self->super.super.frames->items;
+
+	bone = skeleton->bones[self->boneIndex];
+	if (!bone->active) return;
+
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				bone->scaleY = bone->data->scaleY;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				bone->scaleY += (bone->data->scaleY - bone->scaleY) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	y = spCurveTimeline1_getCurveValue(SUPER(self), time) * bone->data->scaleY;
+	if (alpha == 1) {
+		if (blend == SP_MIX_BLEND_ADD)
+			bone->scaleY += y - bone->data->scaleY;
+		else
+			bone->scaleY = y;
+	} else {
+		/* Mixing out uses sign of setup or current pose, else use sign of key. */
+		float by = 0;
+		if (direction == SP_MIX_DIRECTION_OUT) {
+			switch (blend) {
+				case SP_MIX_BLEND_SETUP:
+					by = bone->data->scaleY;
+					bone->scaleY = by + (ABS(y) * SIGNUM(by) - by) * alpha;
+					break;
+				case SP_MIX_BLEND_FIRST:
+				case SP_MIX_BLEND_REPLACE:
+					by = bone->scaleY;
+					bone->scaleY = by + (ABS(y) * SIGNUM(by) - by) * alpha;
+					break;
+				case SP_MIX_BLEND_ADD:
+					by = bone->scaleY;
+					bone->scaleY = by + (ABS(y) * SIGNUM(by) - bone->data->scaleY) * alpha;
+			}
+		} else {
+			switch (blend) {
+				case SP_MIX_BLEND_SETUP:
+					by = ABS(bone->data->scaleY) * SIGNUM(y);
+					bone->scaleY = by + (y - by) * alpha;
+					break;
+				case SP_MIX_BLEND_FIRST:
+				case SP_MIX_BLEND_REPLACE:
+					by = ABS(bone->scaleY) * SIGNUM(y);
+					bone->scaleY = by + (y - by) * alpha;
+					break;
+				case SP_MIX_BLEND_ADD:
+					by = SIGNUM(y);
+					bone->scaleY = ABS(bone->scaleY) * by + (y - ABS(bone->data->scaleY) * by) * alpha;
+			}
+		}
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+}
+
+spScaleYTimeline *spScaleYTimeline_create(int frameCount, int bezierCount, int boneIndex) {
+	spScaleYTimeline *timeline = NEW(spScaleYTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_SCALEY << 32) | boneIndex;
+	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_SCALEY,
+						  _spCurveTimeline_dispose, _spScaleYTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->boneIndex = boneIndex;
+	return timeline;
+}
+
+void spScaleYTimeline_setFrame(spScaleYTimeline *self, int frame, float time, float y) {
+	spCurveTimeline1_setFrame(SUPER(self), frame, time, y);
+}
+
+/**/
+
+void _spShearTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
+							int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
+	spBone *bone;
+	float x, y, t;
+	int i, curveType;
+
+	spShearTimeline *self = SUB_CAST(spShearTimeline, timeline);
+	float *frames = SUPER(self)->super.frames->items;
+	float *curves = SUPER(self)->curves->items;
+
+	bone = skeleton->bones[self->boneIndex];
+	if (!bone->active) return;
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				bone->shearX = bone->data->shearX;
+				bone->shearY = bone->data->shearY;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				bone->shearX += (bone->data->shearX - bone->shearX) * alpha;
+				bone->shearY += (bone->data->shearY - bone->shearY) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	i = search2(self->super.super.frames, time, CURVE2_ENTRIES);
+	curveType = (int) curves[i / CURVE2_ENTRIES];
+	switch (curveType) {
+		case CURVE_LINEAR: {
+			float before = frames[i];
+			x = frames[i + CURVE2_VALUE1];
+			y = frames[i + CURVE2_VALUE2];
+			t = (time - before) / (frames[i + CURVE2_ENTRIES] - before);
+			x += (frames[i + CURVE2_ENTRIES + CURVE2_VALUE1] - x) * t;
+			y += (frames[i + CURVE2_ENTRIES + CURVE2_VALUE2] - y) * t;
+			break;
+		}
+		case CURVE_STEPPED: {
+			x = frames[i + CURVE2_VALUE1];
+			y = frames[i + CURVE2_VALUE2];
+			break;
+		}
+		default: {
+			x = _spCurveTimeline_getBezierValue(SUPER(self), time, i, CURVE2_VALUE1, curveType - CURVE_BEZIER);
+			y = _spCurveTimeline_getBezierValue(SUPER(self), time, i, CURVE2_VALUE2,
+												curveType + BEZIER_SIZE - CURVE_BEZIER);
+		}
+	}
+
+	switch (blend) {
+		case SP_MIX_BLEND_SETUP:
+			bone->shearX = bone->data->shearX + x * alpha;
+			bone->shearY = bone->data->shearY + y * alpha;
+			break;
+		case SP_MIX_BLEND_FIRST:
+		case SP_MIX_BLEND_REPLACE:
+			bone->shearX += (bone->data->shearX + x - bone->shearX) * alpha;
+			bone->shearY += (bone->data->shearY + y - bone->shearY) * alpha;
+			break;
+		case SP_MIX_BLEND_ADD:
+			bone->shearX += x * alpha;
+			bone->shearY += y * alpha;
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spShearTimeline *spShearTimeline_create(int frameCount, int bezierCount, int boneIndex) {
+	spShearTimeline *timeline = NEW(spShearTimeline);
+	spPropertyId ids[2];
+	ids[0] = ((spPropertyId) SP_PROPERTY_SHEARX << 32) | boneIndex;
+	ids[1] = ((spPropertyId) SP_PROPERTY_SHEARY << 32) | boneIndex;
+	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE2_ENTRIES, bezierCount, ids, 2, SP_TIMELINE_SHEAR,
+						  _spCurveTimeline_dispose, _spShearTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->boneIndex = boneIndex;
+	return timeline;
+}
+
+void spShearTimeline_setFrame(spShearTimeline *self, int frame, float time, float x, float y) {
+	spCurveTimeline2_setFrame(SUPER(self), frame, time, x, y);
+}
+
+/**/
+
+void _spShearXTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+							 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+							 spMixDirection direction) {
+	spBone *bone;
+	float x;
+
+	spShearXTimeline *self = SUB_CAST(spShearXTimeline, timeline);
+	float *frames = self->super.super.frames->items;
+
+	bone = skeleton->bones[self->boneIndex];
+	if (!bone->active) return;
+
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				bone->shearX = bone->data->shearX;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				bone->shearX += (bone->data->shearX - bone->shearX) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	x = spCurveTimeline1_getCurveValue(SUPER(self), time);
+	switch (blend) {
+		case SP_MIX_BLEND_SETUP:
+			bone->shearX = bone->data->shearX + x * alpha;
+			break;
+		case SP_MIX_BLEND_FIRST:
+		case SP_MIX_BLEND_REPLACE:
+			bone->shearX += (bone->data->shearX + x - bone->shearX) * alpha;
+			break;
+		case SP_MIX_BLEND_ADD:
+			bone->shearX += x * alpha;
+	}
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spShearXTimeline *spShearXTimeline_create(int frameCount, int bezierCount, int boneIndex) {
+	spShearXTimeline *timeline = NEW(spShearXTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_SHEARX << 32) | boneIndex;
+	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_SHEARX,
+						  _spCurveTimeline_dispose, _spShearXTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->boneIndex = boneIndex;
+	return timeline;
+}
+
+void spShearXTimeline_setFrame(spShearXTimeline *self, int frame, float time, float x) {
+	spCurveTimeline1_setFrame(SUPER(self), frame, time, x);
+}
+
+/**/
+
+void _spShearYTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+							 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+							 spMixDirection direction) {
+	spBone *bone;
+	float y;
+
+	spShearYTimeline *self = SUB_CAST(spShearYTimeline, timeline);
+	float *frames = self->super.super.frames->items;
+
+	bone = skeleton->bones[self->boneIndex];
+	if (!bone->active) return;
+
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				bone->shearY = bone->data->shearY;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				bone->shearY += (bone->data->shearY - bone->shearY) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	y = spCurveTimeline1_getCurveValue(SUPER(self), time);
+	switch (blend) {
+		case SP_MIX_BLEND_SETUP:
+			bone->shearY = bone->data->shearY + y * alpha;
+			break;
+		case SP_MIX_BLEND_FIRST:
+		case SP_MIX_BLEND_REPLACE:
+			bone->shearY += (bone->data->shearY + y - bone->shearY) * alpha;
+			break;
+		case SP_MIX_BLEND_ADD:
+			bone->shearY += y * alpha;
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spShearYTimeline *spShearYTimeline_create(int frameCount, int bezierCount, int boneIndex) {
+	spShearYTimeline *timeline = NEW(spShearYTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_SHEARY << 32) | boneIndex;
+	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_SHEARY,
+						  _spCurveTimeline_dispose, _spShearYTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->boneIndex = boneIndex;
+	return timeline;
+}
+
+void spShearYTimeline_setFrame(spShearYTimeline *self, int frame, float time, float y) {
+	spCurveTimeline1_setFrame(SUPER(self), frame, time, y);
+}
+
+/**/
+
+static const int RGBA_ENTRIES = 5, COLOR_R = 1, COLOR_G = 2, COLOR_B = 3, COLOR_A = 4;
+
+void _spRGBATimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
+						   int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
+	spSlot *slot;
+	int i, curveType;
+	float r, g, b, a, t;
+	spColor *color;
+	spColor *setup;
+	spRGBATimeline *self = (spRGBATimeline *) timeline;
+	float *frames = self->super.super.frames->items;
+	float *curves = self->super.curves->items;
+
+	slot = skeleton->slots[self->slotIndex];
+	if (!slot->bone->active) return;
+
+	if (time < frames[0]) {
+		color = &slot->color;
+		setup = &slot->data->color;
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				spColor_setFromColor(color, setup);
+				return;
+			case SP_MIX_BLEND_FIRST:
+				spColor_addFloats(color, (setup->r - color->r) * alpha, (setup->g - color->g) * alpha,
+								  (setup->b - color->b) * alpha,
+								  (setup->a - color->a) * alpha);
+			default: {
+			}
+		}
+		return;
+	}
+
+	i = search2(self->super.super.frames, time, RGBA_ENTRIES);
+	curveType = (int) curves[i / RGBA_ENTRIES];
+	switch (curveType) {
+		case CURVE_LINEAR: {
+			float before = frames[i];
+			r = frames[i + COLOR_R];
+			g = frames[i + COLOR_G];
+			b = frames[i + COLOR_B];
+			a = frames[i + COLOR_A];
+			t = (time - before) / (frames[i + RGBA_ENTRIES] - before);
+			r += (frames[i + RGBA_ENTRIES + COLOR_R] - r) * t;
+			g += (frames[i + RGBA_ENTRIES + COLOR_G] - g) * t;
+			b += (frames[i + RGBA_ENTRIES + COLOR_B] - b) * t;
+			a += (frames[i + RGBA_ENTRIES + COLOR_A] - a) * t;
+			break;
+		}
+		case CURVE_STEPPED: {
+			r = frames[i + COLOR_R];
+			g = frames[i + COLOR_G];
+			b = frames[i + COLOR_B];
+			a = frames[i + COLOR_A];
+			break;
+		}
+		default: {
+			r = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_R, curveType - CURVE_BEZIER);
+			g = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_G,
+												curveType + BEZIER_SIZE - CURVE_BEZIER);
+			b = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_B,
+												curveType + BEZIER_SIZE * 2 - CURVE_BEZIER);
+			a = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_A,
+												curveType + BEZIER_SIZE * 3 - CURVE_BEZIER);
+		}
+	}
+	color = &slot->color;
+	if (alpha == 1)
+		spColor_setFromFloats(color, r, g, b, a);
+	else {
+		if (blend == SP_MIX_BLEND_SETUP) spColor_setFromColor(color, &slot->data->color);
+		spColor_addFloats(color, (r - color->r) * alpha, (g - color->g) * alpha, (b - color->b) * alpha,
+						  (a - color->a) * alpha);
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spRGBATimeline *spRGBATimeline_create(int framesCount, int bezierCount, int slotIndex) {
+	spRGBATimeline *timeline = NEW(spRGBATimeline);
+	spPropertyId ids[2];
+	ids[0] = ((spPropertyId) SP_PROPERTY_RGB << 32) | slotIndex;
+	ids[1] = ((spPropertyId) SP_PROPERTY_ALPHA << 32) | slotIndex;
+	_spCurveTimeline_init(SUPER(timeline), framesCount, RGBA_ENTRIES, bezierCount, ids, 2, SP_TIMELINE_RGBA,
+						  _spCurveTimeline_dispose, _spRGBATimeline_apply, _spCurveTimeline_setBezier);
+	timeline->slotIndex = slotIndex;
+	return timeline;
+}
+
+void spRGBATimeline_setFrame(spRGBATimeline *self, int frame, float time, float r, float g, float b, float a) {
+	float *frames = self->super.super.frames->items;
+	frame *= RGBA_ENTRIES;
+	frames[frame] = time;
+	frames[frame + COLOR_R] = r;
+	frames[frame + COLOR_G] = g;
+	frames[frame + COLOR_B] = b;
+	frames[frame + COLOR_A] = a;
+}
+
+/**/
+
+#define RGB_ENTRIES 4
+
+void _spRGBTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
+						  int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
+	spSlot *slot;
+	int i, curveType;
+	float r, g, b, t;
+	spColor *color;
+	spColor *setup;
+	spRGBTimeline *self = (spRGBTimeline *) timeline;
+	float *frames = self->super.super.frames->items;
+	float *curves = self->super.curves->items;
+
+	slot = skeleton->slots[self->slotIndex];
+	if (!slot->bone->active) return;
+
+	if (time < frames[0]) {
+		color = &slot->color;
+		setup = &slot->data->color;
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				spColor_setFromColor(color, setup);
+				return;
+			case SP_MIX_BLEND_FIRST:
+				spColor_addFloats(color, (setup->r - color->r) * alpha, (setup->g - color->g) * alpha,
+								  (setup->b - color->b) * alpha,
+								  (setup->a - color->a) * alpha);
+			default: {
+			}
+		}
+		return;
+	}
+
+	i = search2(self->super.super.frames, time, RGB_ENTRIES);
+	curveType = (int) curves[i / RGB_ENTRIES];
+	switch (curveType) {
+		case CURVE_LINEAR: {
+			float before = frames[i];
+			r = frames[i + COLOR_R];
+			g = frames[i + COLOR_G];
+			b = frames[i + COLOR_B];
+			t = (time - before) / (frames[i + RGB_ENTRIES] - before);
+			r += (frames[i + RGB_ENTRIES + COLOR_R] - r) * t;
+			g += (frames[i + RGB_ENTRIES + COLOR_G] - g) * t;
+			b += (frames[i + RGB_ENTRIES + COLOR_B] - b) * t;
+			break;
+		}
+		case CURVE_STEPPED: {
+			r = frames[i + COLOR_R];
+			g = frames[i + COLOR_G];
+			b = frames[i + COLOR_B];
+			break;
+		}
+		default: {
+			r = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_R, curveType - CURVE_BEZIER);
+			g = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_G,
+												curveType + BEZIER_SIZE - CURVE_BEZIER);
+			b = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_B,
+												curveType + BEZIER_SIZE * 2 - CURVE_BEZIER);
+		}
+	}
+	color = &slot->color;
+	if (alpha == 1) {
+		color->r = r;
+		color->g = g;
+		color->b = b;
+	} else {
+		if (blend == SP_MIX_BLEND_SETUP) {
+			color->r = slot->data->color.r;
+			color->g = slot->data->color.g;
+			color->b = slot->data->color.b;
+		}
+		color->r += (r - color->r) * alpha;
+		color->g += (g - color->g) * alpha;
+		color->b += (b - color->b) * alpha;
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spRGBTimeline *spRGBTimeline_create(int framesCount, int bezierCount, int slotIndex) {
+	spRGBTimeline *timeline = NEW(spRGBTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_RGB << 32) | slotIndex;
+	_spCurveTimeline_init(SUPER(timeline), framesCount, RGB_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_RGB,
+						  _spCurveTimeline_dispose, _spRGBTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->slotIndex = slotIndex;
+	return timeline;
+}
+
+void spRGBTimeline_setFrame(spRGBTimeline *self, int frame, float time, float r, float g, float b) {
+	float *frames = self->super.super.frames->items;
+	frame *= RGB_ENTRIES;
+	frames[frame] = time;
+	frames[frame + COLOR_R] = r;
+	frames[frame + COLOR_G] = g;
+	frames[frame + COLOR_B] = b;
+}
+
+/**/
+
+void _spAlphaTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+							spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+							spMixDirection direction) {
+	spSlot *slot;
+	float a;
+	spColor *color;
+	spColor *setup;
+	spAlphaTimeline *self = (spAlphaTimeline *) timeline;
+	float *frames = self->super.super.frames->items;
+
+	slot = skeleton->slots[self->slotIndex];
+	if (!slot->bone->active) return;
+
+	if (time < frames[0]) { /* Time is before first frame-> */
+		color = &slot->color;
+		setup = &slot->data->color;
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				color->a = setup->a;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				color->a += (setup->a - color->a) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	a = spCurveTimeline1_getCurveValue(SUPER(self), time);
+	if (alpha == 1)
+		slot->color.a = a;
+	else {
+		if (blend == SP_MIX_BLEND_SETUP) slot->color.a = slot->data->color.a;
+		slot->color.a += (a - slot->color.a) * alpha;
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spAlphaTimeline *spAlphaTimeline_create(int frameCount, int bezierCount, int slotIndex) {
+	spAlphaTimeline *timeline = NEW(spAlphaTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_ALPHA << 32) | slotIndex;
+	_spCurveTimeline_init(SUPER(timeline), frameCount, CURVE1_ENTRIES, bezierCount, ids, 1, SP_TIMELINE_ALPHA,
+						  _spCurveTimeline_dispose, _spAlphaTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->slotIndex = slotIndex;
+	return timeline;
+}
+
+void spAlphaTimeline_setFrame(spAlphaTimeline *self, int frame, float time, float alpha) {
+	spCurveTimeline1_setFrame(SUPER(self), frame, time, alpha);
+}
+
+/**/
+
+static const int RGBA2_ENTRIES = 8, COLOR_R2 = 5, COLOR_G2 = 6, COLOR_B2 = 7;
+
+void _spRGBA2Timeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
+							int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
+	spSlot *slot;
+	int i, curveType;
+	float r, g, b, a, r2, g2, b2, t;
+	spColor *light, *setupLight;
+	spColor *dark, *setupDark;
+	spRGBA2Timeline *self = (spRGBA2Timeline *) timeline;
+	float *frames = self->super.super.frames->items;
+	float *curves = self->super.curves->items;
+
+	slot = skeleton->slots[self->slotIndex];
+	if (!slot->bone->active) return;
+
+	if (time < frames[0]) {
+		light = &slot->color;
+		dark = slot->darkColor;
+		setupLight = &slot->data->color;
+		setupDark = slot->data->darkColor;
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				spColor_setFromColor(light, setupLight);
+				spColor_setFromFloats3(dark, setupDark->r, setupDark->g, setupDark->b);
+				return;
+			case SP_MIX_BLEND_FIRST:
+				spColor_addFloats(light, (setupLight->r - light->r) * alpha, (setupLight->g - light->g) * alpha,
+								  (setupLight->b - light->b) * alpha,
+								  (setupLight->a - light->a) * alpha);
+				dark->r += (setupDark->r - dark->r) * alpha;
+				dark->g += (setupDark->g - dark->g) * alpha;
+				dark->b += (setupDark->b - dark->b) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	r = 0, g = 0, b = 0, a = 0, r2 = 0, g2 = 0, b2 = 0;
+	i = search2(self->super.super.frames, time, RGBA2_ENTRIES);
+	curveType = (int) curves[i / RGBA2_ENTRIES];
+	switch (curveType) {
+		case CURVE_LINEAR: {
+			float before = frames[i];
+			r = frames[i + COLOR_R];
+			g = frames[i + COLOR_G];
+			b = frames[i + COLOR_B];
+			a = frames[i + COLOR_A];
+			r2 = frames[i + COLOR_R2];
+			g2 = frames[i + COLOR_G2];
+			b2 = frames[i + COLOR_B2];
+			t = (time - before) / (frames[i + RGBA2_ENTRIES] - before);
+			r += (frames[i + RGBA2_ENTRIES + COLOR_R] - r) * t;
+			g += (frames[i + RGBA2_ENTRIES + COLOR_G] - g) * t;
+			b += (frames[i + RGBA2_ENTRIES + COLOR_B] - b) * t;
+			a += (frames[i + RGBA2_ENTRIES + COLOR_A] - a) * t;
+			r2 += (frames[i + RGBA2_ENTRIES + COLOR_R2] - r2) * t;
+			g2 += (frames[i + RGBA2_ENTRIES + COLOR_G2] - g2) * t;
+			b2 += (frames[i + RGBA2_ENTRIES + COLOR_B2] - b2) * t;
+			break;
+		}
+		case CURVE_STEPPED: {
+			r = frames[i + COLOR_R];
+			g = frames[i + COLOR_G];
+			b = frames[i + COLOR_B];
+			a = frames[i + COLOR_A];
+			r2 = frames[i + COLOR_R2];
+			g2 = frames[i + COLOR_G2];
+			b2 = frames[i + COLOR_B2];
+			break;
+		}
+		default: {
+			r = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_R, curveType - CURVE_BEZIER);
+			g = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_G,
+												curveType + BEZIER_SIZE - CURVE_BEZIER);
+			b = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_B,
+												curveType + BEZIER_SIZE * 2 - CURVE_BEZIER);
+			a = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_A,
+												curveType + BEZIER_SIZE * 3 - CURVE_BEZIER);
+			r2 = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_R2,
+												 curveType + BEZIER_SIZE * 4 - CURVE_BEZIER);
+			g2 = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_G2,
+												 curveType + BEZIER_SIZE * 5 - CURVE_BEZIER);
+			b2 = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_B2,
+												 curveType + BEZIER_SIZE * 6 - CURVE_BEZIER);
+		}
+	}
+
+	light = &slot->color, dark = slot->darkColor;
+	if (alpha == 1) {
+		spColor_setFromFloats(light, r, g, b, a);
+		spColor_setFromFloats3(dark, r2, g2, b2);
+	} else {
+		if (blend == SP_MIX_BLEND_SETUP) {
+			spColor_setFromColor(light, &slot->data->color);
+			spColor_setFromColor(dark, slot->data->darkColor);
+		}
+		spColor_addFloats(light, (r - light->r) * alpha, (g - light->g) * alpha, (b - light->b) * alpha,
+						  (a - light->a) * alpha);
+		dark->r += (r2 - dark->r) * alpha;
+		dark->g += (g2 - dark->g) * alpha;
+		dark->b += (b2 - dark->b) * alpha;
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spRGBA2Timeline *spRGBA2Timeline_create(int framesCount, int bezierCount, int slotIndex) {
+	spRGBA2Timeline *timeline = NEW(spRGBA2Timeline);
+	spPropertyId ids[3];
+	ids[0] = ((spPropertyId) SP_PROPERTY_RGB << 32) | slotIndex;
+	ids[1] = ((spPropertyId) SP_PROPERTY_ALPHA << 32) | slotIndex;
+	ids[2] = ((spPropertyId) SP_PROPERTY_RGB2 << 32) | slotIndex;
+	_spCurveTimeline_init(SUPER(timeline), framesCount, RGBA2_ENTRIES, bezierCount, ids, 3, SP_TIMELINE_RGBA2,
+						  _spCurveTimeline_dispose, _spRGBA2Timeline_apply, _spCurveTimeline_setBezier);
+	timeline->slotIndex = slotIndex;
+	return timeline;
+}
+
+void spRGBA2Timeline_setFrame(spRGBA2Timeline *self, int frame, float time, float r, float g, float b, float a, float r2,
+							  float g2, float b2) {
+	float *frames = self->super.super.frames->items;
+	frame *= RGBA2_ENTRIES;
+	frames[frame] = time;
+	frames[frame + COLOR_R] = r;
+	frames[frame + COLOR_G] = g;
+	frames[frame + COLOR_B] = b;
+	frames[frame + COLOR_A] = a;
+	frames[frame + COLOR_R2] = r2;
+	frames[frame + COLOR_G2] = g2;
+	frames[frame + COLOR_B2] = b2;
+}
+
+/**/
+
+static const int RGB2_ENTRIES = 7, COLOR2_R2 = 5, COLOR2_G2 = 6, COLOR2_B2 = 7;
+
+void _spRGB2Timeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
+						   int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
+	spSlot *slot;
+	int i, curveType;
+	float r, g, b, r2, g2, b2, t;
+	spColor *light, *setupLight;
+	spColor *dark, *setupDark;
+	spRGB2Timeline *self = (spRGB2Timeline *) timeline;
+	float *frames = self->super.super.frames->items;
+	float *curves = self->super.curves->items;
+
+	slot = skeleton->slots[self->slotIndex];
+	if (!slot->bone->active) return;
+
+	if (time < frames[0]) {
+		light = &slot->color;
+		dark = slot->darkColor;
+		setupLight = &slot->data->color;
+		setupDark = slot->data->darkColor;
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				spColor_setFromColor3(light, setupLight);
+				spColor_setFromColor3(dark, setupDark);
+				return;
+			case SP_MIX_BLEND_FIRST:
+				spColor_addFloats3(light, (setupLight->r - light->r) * alpha, (setupLight->g - light->g) * alpha,
+								   (setupLight->b - light->b) * alpha);
+				dark->r += (setupDark->r - dark->r) * alpha;
+				dark->g += (setupDark->g - dark->g) * alpha;
+				dark->b += (setupDark->b - dark->b) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	r = 0, g = 0, b = 0, r2 = 0, g2 = 0, b2 = 0;
+	i = search2(self->super.super.frames, time, RGB2_ENTRIES);
+	curveType = (int) curves[i / RGB2_ENTRIES];
+	switch (curveType) {
+		case CURVE_LINEAR: {
+			float before = frames[i];
+			r = frames[i + COLOR_R];
+			g = frames[i + COLOR_G];
+			b = frames[i + COLOR_B];
+			r2 = frames[i + COLOR2_R2];
+			g2 = frames[i + COLOR2_G2];
+			b2 = frames[i + COLOR2_B2];
+			t = (time - before) / (frames[i + RGB2_ENTRIES] - before);
+			r += (frames[i + RGB2_ENTRIES + COLOR_R] - r) * t;
+			g += (frames[i + RGB2_ENTRIES + COLOR_G] - g) * t;
+			b += (frames[i + RGB2_ENTRIES + COLOR_B] - b) * t;
+			r2 += (frames[i + RGB2_ENTRIES + COLOR2_R2] - r2) * t;
+			g2 += (frames[i + RGB2_ENTRIES + COLOR2_G2] - g2) * t;
+			b2 += (frames[i + RGB2_ENTRIES + COLOR2_B2] - b2) * t;
+			break;
+		}
+		case CURVE_STEPPED: {
+			r = frames[i + COLOR_R];
+			g = frames[i + COLOR_G];
+			b = frames[i + COLOR_B];
+			r2 = frames[i + COLOR2_R2];
+			g2 = frames[i + COLOR2_G2];
+			b2 = frames[i + COLOR2_B2];
+			break;
+		}
+		default: {
+			r = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_R, curveType - CURVE_BEZIER);
+			g = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_G,
+												curveType + BEZIER_SIZE - CURVE_BEZIER);
+			b = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR_B,
+												curveType + BEZIER_SIZE * 2 - CURVE_BEZIER);
+			r2 = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR2_R2,
+												 curveType + BEZIER_SIZE * 3 - CURVE_BEZIER);
+			g2 = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR2_G2,
+												 curveType + BEZIER_SIZE * 4 - CURVE_BEZIER);
+			b2 = _spCurveTimeline_getBezierValue(SUPER(self), time, i, COLOR2_B2,
+												 curveType + BEZIER_SIZE * 5 - CURVE_BEZIER);
+		}
+	}
+
+	light = &slot->color, dark = slot->darkColor;
+	if (alpha == 1) {
+		spColor_setFromFloats3(light, r, g, b);
+		spColor_setFromFloats3(dark, r2, g2, b2);
+	} else {
+		if (blend == SP_MIX_BLEND_SETUP) {
+			spColor_setFromColor3(light, &slot->data->color);
+
+			spColor_setFromColor3(dark, slot->data->darkColor);
+		}
+		spColor_addFloats3(light, (r - light->r) * alpha, (g - light->g) * alpha, (b - light->b) * alpha);
+		dark->r += (r2 - dark->r) * alpha;
+		dark->g += (g2 - dark->g) * alpha;
+		dark->b += (b2 - dark->b) * alpha;
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spRGB2Timeline *spRGB2Timeline_create(int framesCount, int bezierCount, int slotIndex) {
+	spRGB2Timeline *timeline = NEW(spRGB2Timeline);
+	spPropertyId ids[2];
+	ids[0] = ((spPropertyId) SP_PROPERTY_RGB << 32) | slotIndex;
+	ids[1] = ((spPropertyId) SP_PROPERTY_RGB2 << 32) | slotIndex;
+	_spCurveTimeline_init(SUPER(timeline), framesCount, RGB2_ENTRIES, bezierCount, ids, 2, SP_TIMELINE_RGB2,
+						  _spCurveTimeline_dispose, _spRGB2Timeline_apply, _spCurveTimeline_setBezier);
+	timeline->slotIndex = slotIndex;
+	return timeline;
+}
+
+void spRGB2Timeline_setFrame(spRGB2Timeline *self, int frame, float time, float r, float g, float b, float r2, float g2,
+							 float b2) {
+	float *frames = self->super.super.frames->items;
+	frame *= RGB2_ENTRIES;
+	frames[frame] = time;
+	frames[frame + COLOR_R] = r;
+	frames[frame + COLOR_G] = g;
+	frames[frame + COLOR_B] = b;
+	frames[frame + COLOR2_R2] = r2;
+	frames[frame + COLOR2_G2] = g2;
+	frames[frame + COLOR2_B2] = b2;
+}
+
+/**/
+
+static void
+_spSetAttachment(spAttachmentTimeline *timeline, spSkeleton *skeleton, spSlot *slot, const char *attachmentName) {
+	slot->attachment =
+			attachmentName == NULL ? NULL : spSkeleton_getAttachmentForSlotIndex(skeleton, timeline->slotIndex, attachmentName);
+}
+
+void _spAttachmentTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+								 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+								 spMixDirection direction) {
+	const char *attachmentName;
+	spAttachmentTimeline *self = (spAttachmentTimeline *) timeline;
+	float *frames = self->super.frames->items;
+	spSlot *slot = skeleton->slots[self->slotIndex];
+	if (!slot->bone->active) return;
+
+	if (direction == SP_MIX_DIRECTION_OUT) {
+		if (blend == SP_MIX_BLEND_SETUP) {
+			_spSetAttachment(self, skeleton, slot, slot->data->attachmentName);
+		}
+		return;
+	}
+
+	if (time < frames[0]) {
+		if (blend == SP_MIX_BLEND_SETUP || blend == SP_MIX_BLEND_FIRST) {
+			_spSetAttachment(self, skeleton, slot, slot->data->attachmentName);
+		}
+		return;
+	}
+
+	if (time < frames[0]) {
+		if (blend == SP_MIX_BLEND_SETUP || blend == SP_MIX_BLEND_FIRST)
+			_spSetAttachment(self, skeleton, slot, slot->data->attachmentName);
+		return;
+	}
+
+	attachmentName = self->attachmentNames[search(self->super.frames, time)];
+	_spSetAttachment(self, skeleton, slot, attachmentName);
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(alpha);
+}
+
+void _spAttachmentTimeline_dispose(spTimeline *timeline) {
+	spAttachmentTimeline *self = SUB_CAST(spAttachmentTimeline, timeline);
+	int i;
+	for (i = 0; i < self->super.frames->size; ++i)
+		FREE(self->attachmentNames[i]);
+	FREE(self->attachmentNames);
+}
+
+spAttachmentTimeline *spAttachmentTimeline_create(int framesCount, int slotIndex) {
+	spAttachmentTimeline *self = NEW(spAttachmentTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_ATTACHMENT << 32) | slotIndex;
+	_spTimeline_init(SUPER(self), framesCount, 1, ids, 1, SP_TIMELINE_ATTACHMENT, _spAttachmentTimeline_dispose,
+					 _spAttachmentTimeline_apply, 0);
+	CONST_CAST(char **, self->attachmentNames) = CALLOC(char *, framesCount);
+	self->slotIndex = slotIndex;
+	return self;
+}
+
+void spAttachmentTimeline_setFrame(spAttachmentTimeline *self, int frame, float time, const char *attachmentName) {
+	self->super.frames->items[frame] = time;
+
+	FREE(self->attachmentNames[frame]);
+	if (attachmentName)
+		MALLOC_STR(self->attachmentNames[frame], attachmentName);
+	else
+		self->attachmentNames[frame] = 0;
+}
+
+/**/
+
+void _spDeformTimeline_setBezier(spTimeline *timeline, int bezier, int frame, float value, float time1, float value1,
+								 float cx1, float cy1,
+								 float cx2, float cy2, float time2, float value2) {
+	spDeformTimeline *self = SUB_CAST(spDeformTimeline, timeline);
+	int n, i = self->super.super.frameCount + bezier * BEZIER_SIZE;
+	float *curves = self->super.curves->items;
+	float tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = cy2 * 0.03 - cy1 * 0.06;
+	float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = (cy1 - cy2 + 0.33333333) * 0.018;
+	float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy;
+	float dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = cy1 * 0.3 + tmpy + dddy * 0.16666667;
+	float x = time1 + dx, y = dy;
+	if (value == 0) curves[frame] = CURVE_BEZIER + i;
+	for (n = i + BEZIER_SIZE; i < n; i += 2) {
+		curves[i] = x;
+		curves[i + 1] = y;
+		dx += ddx;
+		dy += ddy;
+		ddx += dddx;
+		ddy += dddy;
+		x += dx;
+		y += dy;
+	}
+
+	UNUSED(value1);
+	UNUSED(value2);
+}
+
+float _spDeformTimeline_getCurvePercent(spDeformTimeline *self, float time, int frame) {
+	float *curves = self->super.curves->items;
+	float *frames = self->super.super.frames->items;
+	int n, i = (int) curves[frame];
+	int frameEntries = self->super.super.frameEntries;
+	float x, y;
+	switch (i) {
+		case CURVE_LINEAR: {
+			x = frames[frame];
+			return (time - x) / (frames[frame + frameEntries] - x);
+		}
+		case CURVE_STEPPED: {
+			return 0;
+		}
+		default: {
+		}
+	}
+	i -= CURVE_BEZIER;
+	if (curves[i] > time) {
+		x = frames[frame];
+		return curves[i + 1] * (time - x) / (curves[i] - x);
+	}
+	n = i + BEZIER_SIZE;
+	for (i += 2; i < n; i += 2) {
+		if (curves[i] >= time) {
+			x = curves[i - 2], y = curves[i - 1];
+			return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
+		}
+	}
+	x = curves[n - 2], y = curves[n - 1];
+	return y + (1 - y) * (time - x) / (frames[frame + frameEntries] - x);
+}
+
+void _spDeformTimeline_apply(
+		spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
+		int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
+	int frame, i, vertexCount;
+	float percent, frameTime;
+	const float *prevVertices;
+	const float *nextVertices;
+	float *frames;
+	int framesCount;
+	const float **frameVertices;
+	float *deformArray;
+	spDeformTimeline *self = (spDeformTimeline *) timeline;
+
+	spSlot *slot = skeleton->slots[self->slotIndex];
+	if (!slot->bone->active) return;
+
+	if (!slot->attachment) return;
+	switch (slot->attachment->type) {
+		case SP_ATTACHMENT_BOUNDING_BOX:
+		case SP_ATTACHMENT_CLIPPING:
+		case SP_ATTACHMENT_MESH:
+		case SP_ATTACHMENT_PATH: {
+			spVertexAttachment *vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
+			if (vertexAttachment->deformAttachment != SUB_CAST(spVertexAttachment, self->attachment)) return;
+			break;
+		}
+		default:
+			return;
+	}
+
+	frames = self->super.super.frames->items;
+	framesCount = self->super.super.frames->size;
+	vertexCount = self->frameVerticesCount;
+	if (slot->deformCount < vertexCount) {
+		if (slot->deformCapacity < vertexCount) {
+			FREE(slot->deform);
+			slot->deform = MALLOC(float, vertexCount);
+			slot->deformCapacity = vertexCount;
+		}
+	}
+	if (slot->deformCount == 0) blend = SP_MIX_BLEND_SETUP;
+
+	frameVertices = self->frameVertices;
+	deformArray = slot->deform;
+
+	if (time < frames[0]) { /* Time is before first frame. */
+		spVertexAttachment *vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				slot->deformCount = 0;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				if (alpha == 1) {
+					slot->deformCount = 0;
+					return;
+				}
+				slot->deformCount = vertexCount;
+				if (!vertexAttachment->bones) {
+					float *setupVertices = vertexAttachment->vertices;
+					for (i = 0; i < vertexCount; i++) {
+						deformArray[i] += (setupVertices[i] - deformArray[i]) * alpha;
+					}
+				} else {
+					alpha = 1 - alpha;
+					for (i = 0; i < vertexCount; i++) {
+						deformArray[i] *= alpha;
+					}
+				}
+			case SP_MIX_BLEND_REPLACE:
+			case SP_MIX_BLEND_ADD:; /* to appease compiler */
+		}
+		return;
+	}
+
+	slot->deformCount = vertexCount;
+	if (time >= frames[framesCount - 1]) { /* Time is after last frame. */
+		const float *lastVertices = self->frameVertices[framesCount - 1];
+		if (alpha == 1) {
+			if (blend == SP_MIX_BLEND_ADD) {
+				spVertexAttachment *vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
+				if (!vertexAttachment->bones) {
+					/* Unweighted vertex positions, with alpha. */
+					float *setupVertices = vertexAttachment->vertices;
+					for (i = 0; i < vertexCount; i++) {
+						deformArray[i] += lastVertices[i] - setupVertices[i];
+					}
+				} else {
+					/* Weighted deform offsets, with alpha. */
+					for (i = 0; i < vertexCount; i++)
+						deformArray[i] += lastVertices[i];
+				}
+			} else {
+				/* Vertex positions or deform offsets, no alpha. */
+				memcpy(deformArray, lastVertices, vertexCount * sizeof(float));
+			}
+		} else {
+			spVertexAttachment *vertexAttachment;
+			switch (blend) {
+				case SP_MIX_BLEND_SETUP:
+					vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
+					if (!vertexAttachment->bones) {
+						/* Unweighted vertex positions, with alpha. */
+						float *setupVertices = vertexAttachment->vertices;
+						for (i = 0; i < vertexCount; i++) {
+							float setup = setupVertices[i];
+							deformArray[i] = setup + (lastVertices[i] - setup) * alpha;
+						}
+					} else {
+						/* Weighted deform offsets, with alpha. */
+						for (i = 0; i < vertexCount; i++)
+							deformArray[i] = lastVertices[i] * alpha;
+					}
+					break;
+				case SP_MIX_BLEND_FIRST:
+				case SP_MIX_BLEND_REPLACE:
+					/* Vertex positions or deform offsets, with alpha. */
+					for (i = 0; i < vertexCount; i++)
+						deformArray[i] += (lastVertices[i] - deformArray[i]) * alpha;
+				case SP_MIX_BLEND_ADD:
+					vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
+					if (!vertexAttachment->bones) {
+						/* Unweighted vertex positions, with alpha. */
+						float *setupVertices = vertexAttachment->vertices;
+						for (i = 0; i < vertexCount; i++) {
+							deformArray[i] += (lastVertices[i] - setupVertices[i]) * alpha;
+						}
+					} else {
+						for (i = 0; i < vertexCount; i++)
+							deformArray[i] += lastVertices[i] * alpha;
+					}
+			}
+		}
+		return;
+	}
+
+	/* Interpolate between the previous frame and the current frame. */
+	frame = search(self->super.super.frames, time);
+	percent = _spDeformTimeline_getCurvePercent(self, time, frame);
+	prevVertices = frameVertices[frame];
+	nextVertices = frameVertices[frame + 1];
+	frameTime = frames[frame];
+
+	if (alpha == 1) {
+		if (blend == SP_MIX_BLEND_ADD) {
+			spVertexAttachment *vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
+			if (!vertexAttachment->bones) {
+				float *setupVertices = vertexAttachment->vertices;
+				for (i = 0; i < vertexCount; i++) {
+					float prev = prevVertices[i];
+					deformArray[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i];
+				}
+			} else {
+				for (i = 0; i < vertexCount; i++) {
+					float prev = prevVertices[i];
+					deformArray[i] += prev + (nextVertices[i] - prev) * percent;
+				}
+			}
+		} else {
+			for (i = 0; i < vertexCount; i++) {
+				float prev = prevVertices[i];
+				deformArray[i] = prev + (nextVertices[i] - prev) * percent;
+			}
+		}
+	} else {
+		spVertexAttachment *vertexAttachment;
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
+				if (!vertexAttachment->bones) {
+					float *setupVertices = vertexAttachment->vertices;
+					for (i = 0; i < vertexCount; i++) {
+						float prev = prevVertices[i], setup = setupVertices[i];
+						deformArray[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha;
+					}
+				} else {
+					for (i = 0; i < vertexCount; i++) {
+						float prev = prevVertices[i];
+						deformArray[i] = (prev + (nextVertices[i] - prev) * percent) * alpha;
+					}
+				}
+				break;
+			case SP_MIX_BLEND_FIRST:
+			case SP_MIX_BLEND_REPLACE:
+				for (i = 0; i < vertexCount; i++) {
+					float prev = prevVertices[i];
+					deformArray[i] += (prev + (nextVertices[i] - prev) * percent - deformArray[i]) * alpha;
+				}
+				break;
+			case SP_MIX_BLEND_ADD:
+				vertexAttachment = SUB_CAST(spVertexAttachment, slot->attachment);
+				if (!vertexAttachment->bones) {
+					float *setupVertices = vertexAttachment->vertices;
+					for (i = 0; i < vertexCount; i++) {
+						float prev = prevVertices[i];
+						deformArray[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha;
+					}
+				} else {
+					for (i = 0; i < vertexCount; i++) {
+						float prev = prevVertices[i];
+						deformArray[i] += (prev + (nextVertices[i] - prev) * percent) * alpha;
+					}
+				}
+		}
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+void _spDeformTimeline_dispose(spTimeline *timeline) {
+	spDeformTimeline *self = SUB_CAST(spDeformTimeline, timeline);
+	int i;
+	for (i = 0; i < self->super.super.frames->size; ++i)
+		FREE(self->frameVertices[i]);
+	FREE(self->frameVertices);
+	_spCurveTimeline_dispose(timeline);
+}
+
+spDeformTimeline *spDeformTimeline_create(int framesCount, int frameVerticesCount, int bezierCount, int slotIndex,
+										  spVertexAttachment *attachment) {
+	spDeformTimeline *self = NEW(spDeformTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_DEFORM << 32) | ((slotIndex << 16 | attachment->id) & 0xffffffff);
+	_spCurveTimeline_init(SUPER(self), framesCount, 1, bezierCount, ids, 1, SP_TIMELINE_DEFORM,
+						  _spDeformTimeline_dispose, _spDeformTimeline_apply, _spDeformTimeline_setBezier);
+	CONST_CAST(float **, self->frameVertices) = CALLOC(float *, framesCount);
+	CONST_CAST(int, self->frameVerticesCount) = frameVerticesCount;
+	self->slotIndex = slotIndex;
+	self->attachment = SUPER(attachment);
+	return self;
+}
+
+void spDeformTimeline_setFrame(spDeformTimeline *self, int frame, float time, float *vertices) {
+	self->super.super.frames->items[frame] = time;
+
+	FREE(self->frameVertices[frame]);
+	if (!vertices)
+		self->frameVertices[frame] = 0;
+	else {
+		self->frameVertices[frame] = MALLOC(float, self->frameVerticesCount);
+		memcpy(CONST_CAST(float *, self->frameVertices[frame]), vertices, self->frameVerticesCount * sizeof(float));
+	}
+}
+
+/**/
+
+/** Fires events for frames > lastTime and <= time. */
+void _spEventTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time, spEvent **firedEvents,
+							int *eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
+	spEventTimeline *self = (spEventTimeline *) timeline;
+	float *frames = self->super.frames->items;
+	int framesCount = self->super.frames->size;
+	int i;
+	if (!firedEvents) return;
+
+	if (lastTime > time) { /* Fire events after last time for looped animations. */
+		_spEventTimeline_apply(timeline, skeleton, lastTime, (float) INT_MAX, firedEvents, eventsCount, alpha, blend,
+							   direction);
+		lastTime = -1;
+	} else if (lastTime >= frames[framesCount - 1]) {
+		/* Last time is after last i. */
+		return;
+	}
+
+	if (time < frames[0]) return; /* Time is before first i. */
+
+	if (lastTime < frames[0])
+		i = 0;
+	else {
+		float frameTime;
+		i = search(self->super.frames, lastTime) + 1;
+		frameTime = frames[i];
+		while (i > 0) { /* Fire multiple events with the same i. */
+			if (frames[i - 1] != frameTime) break;
+			i--;
+		}
+	}
+	for (; i < framesCount && time >= frames[i]; ++i) {
+		firedEvents[*eventsCount] = self->events[i];
+		(*eventsCount)++;
+	}
+	UNUSED(direction);
+}
+
+void _spEventTimeline_dispose(spTimeline *timeline) {
+	spEventTimeline *self = SUB_CAST(spEventTimeline, timeline);
+	int i;
+
+	for (i = 0; i < self->super.frames->size; ++i)
+		spEvent_dispose(self->events[i]);
+	FREE(self->events);
+}
+
+spEventTimeline *spEventTimeline_create(int framesCount) {
+	spEventTimeline *self = NEW(spEventTimeline);
+	spPropertyId ids[1];
+	ids[0] = (spPropertyId) SP_PROPERTY_EVENT << 32;
+	_spTimeline_init(SUPER(self), framesCount, 1, ids, 1, SP_TIMELINE_EVENT, _spEventTimeline_dispose,
+					 _spEventTimeline_apply, 0);
+	CONST_CAST(spEvent **, self->events) = CALLOC(spEvent *, framesCount);
+	return self;
+}
+
+void spEventTimeline_setFrame(spEventTimeline *self, int frame, spEvent *event) {
+	self->super.frames->items[frame] = event->time;
+
+	FREE(self->events[frame]);
+	self->events[frame] = event;
+}
+
+/**/
+
+void _spDrawOrderTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+								spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+								spMixDirection direction) {
+	int i;
+	const int *drawOrderToSetupIndex;
+	spDrawOrderTimeline *self = (spDrawOrderTimeline *) timeline;
+	float *frames = self->super.frames->items;
+
+	if (direction == SP_MIX_DIRECTION_OUT) {
+		if (blend == SP_MIX_BLEND_SETUP)
+			memcpy(skeleton->drawOrder, skeleton->slots, self->slotsCount * sizeof(spSlot *));
+		return;
+	}
+
+	if (time < frames[0]) {
+		if (blend == SP_MIX_BLEND_SETUP || blend == SP_MIX_BLEND_FIRST)
+			memcpy(skeleton->drawOrder, skeleton->slots, self->slotsCount * sizeof(spSlot *));
+		return;
+	}
+
+	drawOrderToSetupIndex = self->drawOrders[search(self->super.frames, time)];
+	if (!drawOrderToSetupIndex)
+		memcpy(skeleton->drawOrder, skeleton->slots, self->slotsCount * sizeof(spSlot *));
+	else {
+		for (i = 0; i < self->slotsCount; ++i)
+			skeleton->drawOrder[i] = skeleton->slots[drawOrderToSetupIndex[i]];
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(alpha);
+}
+
+void _spDrawOrderTimeline_dispose(spTimeline *timeline) {
+	spDrawOrderTimeline *self = SUB_CAST(spDrawOrderTimeline, timeline);
+	int i;
+
+	for (i = 0; i < self->super.frames->size; ++i)
+		FREE(self->drawOrders[i]);
+	FREE(self->drawOrders);
+}
+
+spDrawOrderTimeline *spDrawOrderTimeline_create(int framesCount, int slotsCount) {
+	spDrawOrderTimeline *self = NEW(spDrawOrderTimeline);
+	spPropertyId ids[1];
+	ids[0] = (spPropertyId) SP_PROPERTY_DRAWORDER << 32;
+	_spTimeline_init(SUPER(self), framesCount, 1, ids, 1, SP_TIMELINE_DRAWORDER, _spDrawOrderTimeline_dispose,
+					 _spDrawOrderTimeline_apply, 0);
+
+	CONST_CAST(int **, self->drawOrders) = CALLOC(int *, framesCount);
+	CONST_CAST(int, self->slotsCount) = slotsCount;
+
+	return self;
+}
+
+void spDrawOrderTimeline_setFrame(spDrawOrderTimeline *self, int frame, float time, const int *drawOrder) {
+	self->super.frames->items[frame] = time;
+
+	FREE(self->drawOrders[frame]);
+	if (!drawOrder)
+		self->drawOrders[frame] = 0;
+	else {
+		self->drawOrders[frame] = MALLOC(int, self->slotsCount);
+		memcpy(CONST_CAST(int *, self->drawOrders[frame]), drawOrder, self->slotsCount * sizeof(int));
+	}
+}
+
+/**/
+
+static const int IKCONSTRAINT_ENTRIES = 6;
+static const int IKCONSTRAINT_MIX = 1, IKCONSTRAINT_SOFTNESS = 2, IKCONSTRAINT_BEND_DIRECTION = 3, IKCONSTRAINT_COMPRESS = 4, IKCONSTRAINT_STRETCH = 5;
+
+void _spIkConstraintTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+								   spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+								   spMixDirection direction) {
+	int i, curveType;
+	float mix, softness, t;
+	spIkConstraint *constraint;
+	spIkConstraintTimeline *self = (spIkConstraintTimeline *) timeline;
+	float *frames = self->super.super.frames->items;
+	float *curves = self->super.curves->items;
+
+	constraint = skeleton->ikConstraints[self->ikConstraintIndex];
+	if (!constraint->active) return;
+
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				constraint->mix = constraint->data->mix;
+				constraint->softness = constraint->data->softness;
+				constraint->bendDirection = constraint->data->bendDirection;
+				constraint->compress = constraint->data->compress;
+				constraint->stretch = constraint->data->stretch;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				constraint->mix += (constraint->data->mix - constraint->mix) * alpha;
+				constraint->softness += (constraint->data->softness - constraint->softness) * alpha;
+				constraint->bendDirection = constraint->data->bendDirection;
+				constraint->compress = constraint->data->compress;
+				constraint->stretch = constraint->data->stretch;
+				return;
+			default:
+				return;
+		}
+	}
+
+	i = search2(self->super.super.frames, time, IKCONSTRAINT_ENTRIES);
+	curveType = (int) curves[i / IKCONSTRAINT_ENTRIES];
+	switch (curveType) {
+		case CURVE_LINEAR: {
+			float before = frames[i];
+			mix = frames[i + IKCONSTRAINT_MIX];
+			softness = frames[i + IKCONSTRAINT_SOFTNESS];
+			t = (time - before) / (frames[i + IKCONSTRAINT_ENTRIES] - before);
+			mix += (frames[i + IKCONSTRAINT_ENTRIES + IKCONSTRAINT_MIX] - mix) * t;
+			softness += (frames[i + IKCONSTRAINT_ENTRIES + IKCONSTRAINT_SOFTNESS] - softness) * t;
+			break;
+		}
+		case CURVE_STEPPED: {
+			mix = frames[i + IKCONSTRAINT_MIX];
+			softness = frames[i + IKCONSTRAINT_SOFTNESS];
+			break;
+		}
+		default: {
+			mix = _spCurveTimeline_getBezierValue(SUPER(self), time, i, IKCONSTRAINT_MIX, curveType - CURVE_BEZIER);
+			softness = _spCurveTimeline_getBezierValue(SUPER(self), time, i, IKCONSTRAINT_SOFTNESS,
+													   curveType + BEZIER_SIZE - CURVE_BEZIER);
+		}
+	}
+
+	if (blend == SP_MIX_BLEND_SETUP) {
+		constraint->mix = constraint->data->mix + (mix - constraint->data->mix) * alpha;
+		constraint->softness = constraint->data->softness + (softness - constraint->data->softness) * alpha;
+
+		if (direction == SP_MIX_DIRECTION_OUT) {
+			constraint->bendDirection = constraint->data->bendDirection;
+			constraint->compress = constraint->data->compress;
+			constraint->stretch = constraint->data->stretch;
+		} else {
+			constraint->bendDirection = frames[i + IKCONSTRAINT_BEND_DIRECTION];
+			constraint->compress = frames[i + IKCONSTRAINT_COMPRESS] != 0;
+			constraint->stretch = frames[i + IKCONSTRAINT_STRETCH] != 0;
+		}
+	} else {
+		constraint->mix += (mix - constraint->mix) * alpha;
+		constraint->softness += (softness - constraint->softness) * alpha;
+		if (direction == SP_MIX_DIRECTION_IN) {
+			constraint->bendDirection = frames[i + IKCONSTRAINT_BEND_DIRECTION];
+			constraint->compress = frames[i + IKCONSTRAINT_COMPRESS] != 0;
+			constraint->stretch = frames[i + IKCONSTRAINT_STRETCH] != 0;
+		}
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+}
+
+spIkConstraintTimeline *spIkConstraintTimeline_create(int framesCount, int bezierCount, int ikConstraintIndex) {
+	spIkConstraintTimeline *timeline = NEW(spIkConstraintTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_IKCONSTRAINT << 32) | ikConstraintIndex;
+	_spCurveTimeline_init(SUPER(timeline), framesCount, IKCONSTRAINT_ENTRIES, bezierCount, ids, 1,
+						  SP_TIMELINE_IKCONSTRAINT, _spCurveTimeline_dispose, _spIkConstraintTimeline_apply,
+						  _spCurveTimeline_setBezier);
+	timeline->ikConstraintIndex = ikConstraintIndex;
+	return timeline;
+}
+
+void spIkConstraintTimeline_setFrame(spIkConstraintTimeline *self, int frame, float time, float mix, float softness,
+									 int bendDirection, int /*boolean*/ compress, int /*boolean*/ stretch) {
+	float *frames = self->super.super.frames->items;
+	frame *= IKCONSTRAINT_ENTRIES;
+	frames[frame] = time;
+	frames[frame + IKCONSTRAINT_MIX] = mix;
+	frames[frame + IKCONSTRAINT_SOFTNESS] = softness;
+	frames[frame + IKCONSTRAINT_BEND_DIRECTION] = (float) bendDirection;
+	frames[frame + IKCONSTRAINT_COMPRESS] = compress ? 1 : 0;
+	frames[frame + IKCONSTRAINT_STRETCH] = stretch ? 1 : 0;
+}
+
+/**/
+static const int TRANSFORMCONSTRAINT_ENTRIES = 7;
+static const int TRANSFORMCONSTRAINT_ROTATE = 1;
+static const int TRANSFORMCONSTRAINT_X = 2;
+static const int TRANSFORMCONSTRAINT_Y = 3;
+static const int TRANSFORMCONSTRAINT_SCALEX = 4;
+static const int TRANSFORMCONSTRAINT_SCALEY = 5;
+static const int TRANSFORMCONSTRAINT_SHEARY = 6;
+
+void _spTransformConstraintTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+										  spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+										  spMixDirection direction) {
+	int i, curveType;
+	float rotate, x, y, scaleX, scaleY, shearY, t;
+	spTransformConstraint *constraint;
+	spTransformConstraintTimeline *self = (spTransformConstraintTimeline *) timeline;
+	float *frames;
+	float *curves;
+	spTransformConstraintData *data;
+
+	constraint = skeleton->transformConstraints[self->transformConstraintIndex];
+	if (!constraint->active) return;
+
+	frames = self->super.super.frames->items;
+	curves = self->super.curves->items;
+
+	data = constraint->data;
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				constraint->mixRotate = data->mixRotate;
+				constraint->mixX = data->mixX;
+				constraint->mixY = data->mixY;
+				constraint->mixScaleX = data->mixScaleX;
+				constraint->mixScaleY = data->mixScaleY;
+				constraint->mixShearY = data->mixShearY;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				constraint->mixRotate += (data->mixRotate - constraint->mixRotate) * alpha;
+				constraint->mixX += (data->mixX - constraint->mixX) * alpha;
+				constraint->mixY += (data->mixY - constraint->mixY) * alpha;
+				constraint->mixScaleX += (data->mixScaleX - constraint->mixScaleX) * alpha;
+				constraint->mixScaleY += (data->mixScaleY - constraint->mixScaleY) * alpha;
+				constraint->mixShearY += (data->mixShearY - constraint->mixShearY) * alpha;
+				return;
+			default:
+				return;
+		}
+	}
+
+	i = search2(self->super.super.frames, time, TRANSFORMCONSTRAINT_ENTRIES);
+	curveType = (int) curves[i / TRANSFORMCONSTRAINT_ENTRIES];
+	switch (curveType) {
+		case CURVE_LINEAR: {
+			float before = frames[i];
+			rotate = frames[i + TRANSFORMCONSTRAINT_ROTATE];
+			x = frames[i + TRANSFORMCONSTRAINT_X];
+			y = frames[i + TRANSFORMCONSTRAINT_Y];
+			scaleX = frames[i + TRANSFORMCONSTRAINT_SCALEX];
+			scaleY = frames[i + TRANSFORMCONSTRAINT_SCALEY];
+			shearY = frames[i + TRANSFORMCONSTRAINT_SHEARY];
+			t = (time - before) / (frames[i + TRANSFORMCONSTRAINT_ENTRIES] - before);
+			rotate += (frames[i + TRANSFORMCONSTRAINT_ENTRIES + TRANSFORMCONSTRAINT_ROTATE] - rotate) * t;
+			x += (frames[i + TRANSFORMCONSTRAINT_ENTRIES + TRANSFORMCONSTRAINT_X] - x) * t;
+			y += (frames[i + TRANSFORMCONSTRAINT_ENTRIES + TRANSFORMCONSTRAINT_Y] - y) * t;
+			scaleX += (frames[i + TRANSFORMCONSTRAINT_ENTRIES + TRANSFORMCONSTRAINT_SCALEX] - scaleX) * t;
+			scaleY += (frames[i + TRANSFORMCONSTRAINT_ENTRIES + TRANSFORMCONSTRAINT_SCALEY] - scaleY) * t;
+			shearY += (frames[i + TRANSFORMCONSTRAINT_ENTRIES + TRANSFORMCONSTRAINT_SHEARY] - shearY) * t;
+			break;
+		}
+		case CURVE_STEPPED: {
+			rotate = frames[i + TRANSFORMCONSTRAINT_ROTATE];
+			x = frames[i + TRANSFORMCONSTRAINT_X];
+			y = frames[i + TRANSFORMCONSTRAINT_Y];
+			scaleX = frames[i + TRANSFORMCONSTRAINT_SCALEX];
+			scaleY = frames[i + TRANSFORMCONSTRAINT_SCALEY];
+			shearY = frames[i + TRANSFORMCONSTRAINT_SHEARY];
+			break;
+		}
+		default: {
+			rotate = _spCurveTimeline_getBezierValue(SUPER(self), time, i, TRANSFORMCONSTRAINT_ROTATE,
+													 curveType - CURVE_BEZIER);
+			x = _spCurveTimeline_getBezierValue(SUPER(self), time, i, TRANSFORMCONSTRAINT_X,
+												curveType + BEZIER_SIZE - CURVE_BEZIER);
+			y = _spCurveTimeline_getBezierValue(SUPER(self), time, i, TRANSFORMCONSTRAINT_Y,
+												curveType + BEZIER_SIZE * 2 - CURVE_BEZIER);
+			scaleX = _spCurveTimeline_getBezierValue(SUPER(self), time, i, TRANSFORMCONSTRAINT_SCALEX,
+													 curveType + BEZIER_SIZE * 3 - CURVE_BEZIER);
+			scaleY = _spCurveTimeline_getBezierValue(SUPER(self), time, i, TRANSFORMCONSTRAINT_SCALEY,
+													 curveType + BEZIER_SIZE * 4 - CURVE_BEZIER);
+			shearY = _spCurveTimeline_getBezierValue(SUPER(self), time, i, TRANSFORMCONSTRAINT_SHEARY,
+													 curveType + BEZIER_SIZE * 5 - CURVE_BEZIER);
+		}
+	}
+
+	if (blend == SP_MIX_BLEND_SETUP) {
+		constraint->mixRotate = data->mixRotate + (rotate - data->mixRotate) * alpha;
+		constraint->mixX = data->mixX + (x - data->mixX) * alpha;
+		constraint->mixY = data->mixY + (y - data->mixY) * alpha;
+		constraint->mixScaleX = data->mixScaleX + (scaleX - data->mixScaleX) * alpha;
+		constraint->mixScaleY = data->mixScaleY + (scaleY - data->mixScaleY) * alpha;
+		constraint->mixShearY = data->mixShearY + (shearY - data->mixShearY) * alpha;
+	} else {
+		constraint->mixRotate += (rotate - constraint->mixRotate) * alpha;
+		constraint->mixX += (x - constraint->mixX) * alpha;
+		constraint->mixY += (y - constraint->mixY) * alpha;
+		constraint->mixScaleX += (scaleX - constraint->mixScaleX) * alpha;
+		constraint->mixScaleY += (scaleY - constraint->mixScaleY) * alpha;
+		constraint->mixShearY += (shearY - constraint->mixShearY) * alpha;
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spTransformConstraintTimeline *
+spTransformConstraintTimeline_create(int framesCount, int bezierCount, int transformConstraintIndex) {
+	spTransformConstraintTimeline *timeline = NEW(spTransformConstraintTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_TRANSFORMCONSTRAINT << 32) | transformConstraintIndex;
+	_spCurveTimeline_init(SUPER(timeline), framesCount, TRANSFORMCONSTRAINT_ENTRIES, bezierCount, ids, 1,
+						  SP_TIMELINE_TRANSFORMCONSTRAINT, _spCurveTimeline_dispose,
+						  _spTransformConstraintTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->transformConstraintIndex = transformConstraintIndex;
+	return timeline;
+}
+
+void spTransformConstraintTimeline_setFrame(spTransformConstraintTimeline *self, int frame, float time, float mixRotate,
+											float mixX, float mixY, float mixScaleX, float mixScaleY, float mixShearY) {
+	float *frames = self->super.super.frames->items;
+	frame *= TRANSFORMCONSTRAINT_ENTRIES;
+	frames[frame] = time;
+	frames[frame + TRANSFORMCONSTRAINT_ROTATE] = mixRotate;
+	frames[frame + TRANSFORMCONSTRAINT_X] = mixX;
+	frames[frame + TRANSFORMCONSTRAINT_X] = mixY;
+	frames[frame + TRANSFORMCONSTRAINT_SCALEX] = mixScaleX;
+	frames[frame + TRANSFORMCONSTRAINT_SCALEY] = mixScaleY;
+	frames[frame + TRANSFORMCONSTRAINT_SHEARY] = mixShearY;
+}
+
+/**/
+static const int PATHCONSTRAINTPOSITION_ENTRIES = 2;
+static const int PATHCONSTRAINTPOSITION_VALUE = 1;
+
+void _spPathConstraintPositionTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+											 spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+											 spMixDirection direction) {
+	float position;
+	spPathConstraint *constraint;
+	spPathConstraintPositionTimeline *self = (spPathConstraintPositionTimeline *) timeline;
+	float *frames;
+
+	constraint = skeleton->pathConstraints[self->pathConstraintIndex];
+	if (!constraint->active) return;
+
+	frames = self->super.super.frames->items;
+
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				constraint->position = constraint->data->position;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				constraint->position += (constraint->data->position - constraint->position) * alpha;
+				return;
+			default:
+				return;
+		}
+	}
+
+	position = spCurveTimeline1_getCurveValue(SUPER(self), time);
+
+	if (blend == SP_MIX_BLEND_SETUP)
+		constraint->position = constraint->data->position + (position - constraint->data->position) * alpha;
+	else
+		constraint->position += (position - constraint->position) * alpha;
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spPathConstraintPositionTimeline *
+spPathConstraintPositionTimeline_create(int framesCount, int bezierCount, int pathConstraintIndex) {
+	spPathConstraintPositionTimeline *timeline = NEW(spPathConstraintPositionTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_PATHCONSTRAINT_POSITION << 32) | pathConstraintIndex;
+	_spCurveTimeline_init(SUPER(timeline), framesCount, PATHCONSTRAINTPOSITION_ENTRIES, bezierCount, ids, 1,
+						  SP_TIMELINE_PATHCONSTRAINTPOSITION, _spCurveTimeline_dispose,
+						  _spPathConstraintPositionTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->pathConstraintIndex = pathConstraintIndex;
+	return timeline;
+}
+
+void spPathConstraintPositionTimeline_setFrame(spPathConstraintPositionTimeline *self, int frame, float time, float value) {
+	float *frames = self->super.super.frames->items;
+	frame *= PATHCONSTRAINTPOSITION_ENTRIES;
+	frames[frame] = time;
+	frames[frame + PATHCONSTRAINTPOSITION_VALUE] = value;
+}
+
+/**/
+static const int PATHCONSTRAINTSPACING_ENTRIES = 2;
+static const int PATHCONSTRAINTSPACING_VALUE = 1;
+
+void _spPathConstraintSpacingTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+											spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+											spMixDirection direction) {
+	float spacing;
+	spPathConstraint *constraint;
+	spPathConstraintSpacingTimeline *self = (spPathConstraintSpacingTimeline *) timeline;
+	float *frames;
+
+	constraint = skeleton->pathConstraints[self->pathConstraintIndex];
+	if (!constraint->active) return;
+
+	frames = self->super.super.frames->items;
+
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				constraint->spacing = constraint->data->spacing;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				constraint->spacing += (constraint->data->spacing - constraint->spacing) * alpha;
+				return;
+			default:
+				return;
+		}
+	}
+
+	spacing = spCurveTimeline1_getCurveValue(SUPER(self), time);
+
+	if (blend == SP_MIX_BLEND_SETUP)
+		constraint->spacing = constraint->data->spacing + (spacing - constraint->data->spacing) * alpha;
+	else
+		constraint->spacing += (spacing - constraint->spacing) * alpha;
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spPathConstraintSpacingTimeline *
+spPathConstraintSpacingTimeline_create(int framesCount, int bezierCount, int pathConstraintIndex) {
+	spPathConstraintSpacingTimeline *timeline = NEW(spPathConstraintSpacingTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_PATHCONSTRAINT_SPACING << 32) | pathConstraintIndex;
+	_spCurveTimeline_init(SUPER(timeline), framesCount, PATHCONSTRAINTSPACING_ENTRIES, bezierCount, ids, 1,
+						  SP_TIMELINE_PATHCONSTRAINTSPACING, _spCurveTimeline_dispose,
+						  _spPathConstraintSpacingTimeline_apply, _spCurveTimeline_setBezier);
+	timeline->pathConstraintIndex = pathConstraintIndex;
+	return timeline;
+}
+
+void spPathConstraintSpacingTimeline_setFrame(spPathConstraintSpacingTimeline *self, int frame, float time, float value) {
+	float *frames = self->super.super.frames->items;
+	frame *= PATHCONSTRAINTSPACING_ENTRIES;
+	frames[frame] = time;
+	frames[frame + PATHCONSTRAINTSPACING_VALUE] = value;
+}
+
+/**/
+
+static const int PATHCONSTRAINTMIX_ENTRIES = 4;
+static const int PATHCONSTRAINTMIX_ROTATE = 1;
+static const int PATHCONSTRAINTMIX_X = 2;
+static const int PATHCONSTRAINTMIX_Y = 3;
+
+void _spPathConstraintMixTimeline_apply(spTimeline *timeline, spSkeleton *skeleton, float lastTime, float time,
+										spEvent **firedEvents, int *eventsCount, float alpha, spMixBlend blend,
+										spMixDirection direction) {
+	int i, curveType;
+	float rotate, x, y, t;
+	spPathConstraint *constraint;
+	spPathConstraintMixTimeline *self = (spPathConstraintMixTimeline *) timeline;
+	float *frames;
+	float *curves;
+
+	constraint = skeleton->pathConstraints[self->pathConstraintIndex];
+	if (!constraint->active) return;
+
+	frames = self->super.super.frames->items;
+	curves = self->super.curves->items;
+
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				constraint->mixRotate = constraint->data->mixRotate;
+				constraint->mixX = constraint->data->mixX;
+				constraint->mixY = constraint->data->mixY;
+				return;
+			case SP_MIX_BLEND_FIRST:
+				constraint->mixRotate += (constraint->data->mixRotate - constraint->mixRotate) * alpha;
+				constraint->mixX += (constraint->data->mixX - constraint->mixX) * alpha;
+				constraint->mixY += (constraint->data->mixY - constraint->mixY) * alpha;
+			default: {
+			}
+		}
+		return;
+	}
+
+	i = search2(self->super.super.frames, time, PATHCONSTRAINTMIX_ENTRIES);
+	curveType = (int) curves[i >> 2];
+	switch (curveType) {
+		case CURVE_LINEAR: {
+			float before = frames[i];
+			rotate = frames[i + PATHCONSTRAINTMIX_ROTATE];
+			x = frames[i + PATHCONSTRAINTMIX_X];
+			y = frames[i + PATHCONSTRAINTMIX_Y];
+			t = (time - before) / (frames[i + PATHCONSTRAINTMIX_ENTRIES] - before);
+			rotate += (frames[i + PATHCONSTRAINTMIX_ENTRIES + PATHCONSTRAINTMIX_ROTATE] - rotate) * t;
+			x += (frames[i + PATHCONSTRAINTMIX_ENTRIES + PATHCONSTRAINTMIX_X] - x) * t;
+			y += (frames[i + PATHCONSTRAINTMIX_ENTRIES + PATHCONSTRAINTMIX_Y] - y) * t;
+			break;
+		}
+		case CURVE_STEPPED: {
+			rotate = frames[i + PATHCONSTRAINTMIX_ROTATE];
+			x = frames[i + PATHCONSTRAINTMIX_X];
+			y = frames[i + PATHCONSTRAINTMIX_Y];
+			break;
+		}
+		default: {
+			rotate = _spCurveTimeline_getBezierValue(SUPER(self), time, i, PATHCONSTRAINTMIX_ROTATE,
+													 curveType - CURVE_BEZIER);
+			x = _spCurveTimeline_getBezierValue(SUPER(self), time, i, PATHCONSTRAINTMIX_X,
+												curveType + BEZIER_SIZE - CURVE_BEZIER);
+			y = _spCurveTimeline_getBezierValue(SUPER(self), time, i, PATHCONSTRAINTMIX_Y,
+												curveType + BEZIER_SIZE * 2 - CURVE_BEZIER);
+		}
+	}
+
+	if (blend == SP_MIX_BLEND_SETUP) {
+		spPathConstraintData *data = constraint->data;
+		constraint->mixRotate = data->mixRotate + (rotate - data->mixRotate) * alpha;
+		constraint->mixX = data->mixX + (x - data->mixX) * alpha;
+		constraint->mixY = data->mixY + (y - data->mixY) * alpha;
+	} else {
+		constraint->mixRotate += (rotate - constraint->mixRotate) * alpha;
+		constraint->mixX += (x - constraint->mixX) * alpha;
+		constraint->mixY += (y - constraint->mixY) * alpha;
+	}
+
+	UNUSED(lastTime);
+	UNUSED(firedEvents);
+	UNUSED(eventsCount);
+	UNUSED(direction);
+}
+
+spPathConstraintMixTimeline *
+spPathConstraintMixTimeline_create(int framesCount, int bezierCount, int pathConstraintIndex) {
+	spPathConstraintMixTimeline *timeline = NEW(spPathConstraintMixTimeline);
+	spPropertyId ids[1];
+	ids[0] = ((spPropertyId) SP_PROPERTY_PATHCONSTRAINT_MIX << 32) | pathConstraintIndex;
+	_spCurveTimeline_init(SUPER(timeline), framesCount, PATHCONSTRAINTMIX_ENTRIES, bezierCount, ids, 1,
+						  SP_TIMELINE_PATHCONSTRAINTMIX, _spCurveTimeline_dispose, _spPathConstraintMixTimeline_apply,
+						  _spCurveTimeline_setBezier);
+	timeline->pathConstraintIndex = pathConstraintIndex;
+	return timeline;
+}
+
+void spPathConstraintMixTimeline_setFrame(spPathConstraintMixTimeline *self, int frame, float time, float mixRotate,
+										  float mixX, float mixY) {
+	float *frames = self->super.super.frames->items;
+	frame *= PATHCONSTRAINTMIX_ENTRIES;
+	frames[frame] = time;
+	frames[frame + PATHCONSTRAINTMIX_ROTATE] = mixRotate;
+	frames[frame + PATHCONSTRAINTMIX_X] = mixX;
+	frames[frame + PATHCONSTRAINTMIX_Y] = mixY;
+}

+ 1104 - 1113
spine-c/spine-c/src/spine/AnimationState.c

@@ -1,1113 +1,1104 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/AnimationState.h>
-#include <spine/extension.h>
-#include <limits.h>
-
-#define SUBSEQUENT 0
-#define FIRST 1
-#define HOLD_SUBSEQUENT 2
-#define HOLD_FIRST 3
-#define HOLD_MIX 4
-
-#define SETUP 1
-#define CURRENT 2
-
-_SP_ARRAY_IMPLEMENT_TYPE(spTrackEntryArray, spTrackEntry*)
-
-static spAnimation *SP_EMPTY_ANIMATION = 0;
-
-void spAnimationState_disposeStatics() {
-	if (SP_EMPTY_ANIMATION) spAnimation_dispose(SP_EMPTY_ANIMATION);
-	SP_EMPTY_ANIMATION = 0;
-}
-
-/* Forward declaration of some "private" functions so we can keep
- the same function order in C as we have method order in Java. */
-void _spAnimationState_disposeTrackEntry(spTrackEntry *entry);
-
-void _spAnimationState_disposeTrackEntries(spAnimationState *state, spTrackEntry *entry);
-
-int /*boolean*/ _spAnimationState_updateMixingFrom(spAnimationState *self, spTrackEntry *entry, float delta);
-
-float _spAnimationState_applyMixingFrom(spAnimationState *self, spTrackEntry *entry, spSkeleton *skeleton,
-										spMixBlend currentBlend);
-
-void
-_spAnimationState_applyRotateTimeline(spAnimationState *self, spTimeline *timeline, spSkeleton *skeleton, float time,
-									  float alpha, spMixBlend blend, float *timelinesRotation, int i,
-									  int /*boolean*/ firstFrame);
-
-void _spAnimationState_applyAttachmentTimeline(spAnimationState *self, spTimeline *timeline, spSkeleton *skeleton,
-											   float animationTime, spMixBlend blend, int /*bool*/ firstFrame);
-
-void _spAnimationState_queueEvents(spAnimationState *self, spTrackEntry *entry, float animationTime);
-
-void _spAnimationState_setCurrent(spAnimationState *self, int index, spTrackEntry *current, int /*boolean*/ interrupt);
-
-spTrackEntry *_spAnimationState_expandToIndex(spAnimationState *self, int index);
-
-spTrackEntry *
-_spAnimationState_trackEntry(spAnimationState *self, int trackIndex, spAnimation *animation, int /*boolean*/ loop,
-							 spTrackEntry *last);
-
-void _spAnimationState_animationsChanged(spAnimationState *self);
-
-float *_spAnimationState_resizeTimelinesRotation(spTrackEntry *entry, int newSize);
-
-void _spAnimationState_ensureCapacityPropertyIDs(spAnimationState *self, int capacity);
-
-int _spAnimationState_addPropertyID(spAnimationState *self, spPropertyId id);
-
-void _spTrackEntry_computeHold(spTrackEntry *self, spAnimationState *state);
-
-_spEventQueue *_spEventQueue_create(_spAnimationState *state) {
-	_spEventQueue *self = CALLOC(_spEventQueue, 1);
-	self->state = state;
-	self->objectsCount = 0;
-	self->objectsCapacity = 16;
-	self->objects = CALLOC(_spEventQueueItem, self->objectsCapacity);
-	self->drainDisabled = 0;
-	return self;
-}
-
-void _spEventQueue_free(_spEventQueue *self) {
-	FREE(self->objects);
-	FREE(self);
-}
-
-void _spEventQueue_ensureCapacity(_spEventQueue *self, int newElements) {
-	if (self->objectsCount + newElements > self->objectsCapacity) {
-		_spEventQueueItem *newObjects;
-		self->objectsCapacity <<= 1;
-		newObjects = CALLOC(_spEventQueueItem, self->objectsCapacity);
-		memcpy(newObjects, self->objects, sizeof(_spEventQueueItem) * self->objectsCount);
-		FREE(self->objects);
-		self->objects = newObjects;
-	}
-}
-
-void _spEventQueue_addType(_spEventQueue *self, spEventType type) {
-	_spEventQueue_ensureCapacity(self, 1);
-	self->objects[self->objectsCount++].type = type;
-}
-
-void _spEventQueue_addEntry(_spEventQueue *self, spTrackEntry *entry) {
-	_spEventQueue_ensureCapacity(self, 1);
-	self->objects[self->objectsCount++].entry = entry;
-}
-
-void _spEventQueue_addEvent(_spEventQueue *self, spEvent *event) {
-	_spEventQueue_ensureCapacity(self, 1);
-	self->objects[self->objectsCount++].event = event;
-}
-
-void _spEventQueue_start(_spEventQueue *self, spTrackEntry *entry) {
-	_spEventQueue_addType(self, SP_ANIMATION_START);
-	_spEventQueue_addEntry(self, entry);
-	self->state->animationsChanged = 1;
-}
-
-void _spEventQueue_interrupt(_spEventQueue *self, spTrackEntry *entry) {
-	_spEventQueue_addType(self, SP_ANIMATION_INTERRUPT);
-	_spEventQueue_addEntry(self, entry);
-}
-
-void _spEventQueue_end(_spEventQueue *self, spTrackEntry *entry) {
-	_spEventQueue_addType(self, SP_ANIMATION_END);
-	_spEventQueue_addEntry(self, entry);
-	self->state->animationsChanged = 1;
-}
-
-void _spEventQueue_dispose(_spEventQueue *self, spTrackEntry *entry) {
-	_spEventQueue_addType(self, SP_ANIMATION_DISPOSE);
-	_spEventQueue_addEntry(self, entry);
-}
-
-void _spEventQueue_complete(_spEventQueue *self, spTrackEntry *entry) {
-	_spEventQueue_addType(self, SP_ANIMATION_COMPLETE);
-	_spEventQueue_addEntry(self, entry);
-}
-
-void _spEventQueue_event(_spEventQueue *self, spTrackEntry *entry, spEvent *event) {
-	_spEventQueue_addType(self, SP_ANIMATION_EVENT);
-	_spEventQueue_addEntry(self, entry);
-	_spEventQueue_addEvent(self, event);
-}
-
-void _spEventQueue_clear(_spEventQueue *self) {
-	self->objectsCount = 0;
-}
-
-void _spEventQueue_drain(_spEventQueue *self) {
-	int i;
-	if (self->drainDisabled) return;
-	self->drainDisabled = 1;
-	for (i = 0; i < self->objectsCount; i += 2) {
-		spEventType type = (spEventType) self->objects[i].type;
-		spTrackEntry *entry = self->objects[i + 1].entry;
-		spEvent *event;
-		switch (type) {
-			case SP_ANIMATION_START:
-			case SP_ANIMATION_INTERRUPT:
-			case SP_ANIMATION_COMPLETE:
-				if (entry->listener) entry->listener(SUPER(self->state), type, entry, 0);
-				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, 0);
-				break;
-			case SP_ANIMATION_END:
-				if (entry->listener) entry->listener(SUPER(self->state), type, entry, 0);
-				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, 0);
-				/* Fall through. */
-			case SP_ANIMATION_DISPOSE:
-				if (entry->listener) entry->listener(SUPER(self->state), SP_ANIMATION_DISPOSE, entry, 0);
-				if (self->state->super.listener)
-					self->state->super.listener(SUPER(self->state), SP_ANIMATION_DISPOSE, entry, 0);
-				_spAnimationState_disposeTrackEntry(entry);
-				break;
-			case SP_ANIMATION_EVENT:
-				event = self->objects[i + 2].event;
-				if (entry->listener) entry->listener(SUPER(self->state), type, entry, event);
-				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, event);
-				i++;
-				break;
-		}
-	}
-	_spEventQueue_clear(self);
-
-	self->drainDisabled = 0;
-}
-
-/* These two functions are needed in the UE4 runtime, see #1037 */
-void _spAnimationState_enableQueue(spAnimationState *self) {
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	internal->queue->drainDisabled = 0;
-}
-
-void _spAnimationState_disableQueue(spAnimationState *self) {
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	internal->queue->drainDisabled = 1;
-}
-
-void _spAnimationState_disposeTrackEntry(spTrackEntry *entry) {
-	spIntArray_dispose(entry->timelineMode);
-	spTrackEntryArray_dispose(entry->timelineHoldMix);
-	FREE(entry->timelinesRotation);
-	FREE(entry);
-}
-
-void _spAnimationState_disposeTrackEntries(spAnimationState *state, spTrackEntry *entry) {
-	while (entry) {
-		spTrackEntry *next = entry->next;
-		spTrackEntry *from = entry->mixingFrom;
-		while (from) {
-			spTrackEntry *nextFrom = from->mixingFrom;
-			if (entry->listener) entry->listener(state, SP_ANIMATION_DISPOSE, from, 0);
-			if (state->listener) state->listener(state, SP_ANIMATION_DISPOSE, from, 0);
-			_spAnimationState_disposeTrackEntry(from);
-			from = nextFrom;
-		}
-		if (entry->listener) entry->listener(state, SP_ANIMATION_DISPOSE, entry, 0);
-		if (state->listener) state->listener(state, SP_ANIMATION_DISPOSE, entry, 0);
-		_spAnimationState_disposeTrackEntry(entry);
-		entry = next;
-	}
-}
-
-spAnimationState *spAnimationState_create(spAnimationStateData *data) {
-	_spAnimationState *internal;
-	spAnimationState *self;
-
-	if (!SP_EMPTY_ANIMATION) {
-		SP_EMPTY_ANIMATION = (spAnimation *) 1; /* dirty trick so we can recursively call spAnimation_create */
-		SP_EMPTY_ANIMATION = spAnimation_create("<empty>", NULL, 0);
-	}
-
-	internal = NEW(_spAnimationState);
-	self = SUPER(internal);
-
-	CONST_CAST(spAnimationStateData*, self->data) = data;
-	self->timeScale = 1;
-
-	internal->queue = _spEventQueue_create(internal);
-	internal->events = CALLOC(spEvent*, 128);
-
-	internal->propertyIDs = CALLOC(spPropertyId, 128);
-	internal->propertyIDsCapacity = 128;
-
-	return self;
-}
-
-void spAnimationState_dispose(spAnimationState *self) {
-	int i;
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	for (i = 0; i < self->tracksCount; i++)
-		_spAnimationState_disposeTrackEntries(self, self->tracks[i]);
-	FREE(self->tracks);
-	_spEventQueue_free(internal->queue);
-	FREE(internal->events);
-	FREE(internal->propertyIDs);
-	FREE(internal);
-}
-
-void spAnimationState_update(spAnimationState *self, float delta) {
-	int i, n;
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	delta *= self->timeScale;
-	for (i = 0, n = self->tracksCount; i < n; i++) {
-		float currentDelta;
-		spTrackEntry *current = self->tracks[i];
-		spTrackEntry *next;
-		if (!current) continue;
-
-		current->animationLast = current->nextAnimationLast;
-		current->trackLast = current->nextTrackLast;
-
-		currentDelta = delta * current->timeScale;
-
-		if (current->delay > 0) {
-			current->delay -= currentDelta;
-			if (current->delay > 0) continue;
-			currentDelta = -current->delay;
-			current->delay = 0;
-		}
-
-		next = current->next;
-		if (next) {
-			/* When the next entry's delay is passed, change to the next entry, preserving leftover time. */
-			float nextTime = current->trackLast - next->delay;
-			if (nextTime >= 0) {
-				next->delay = 0;
-				next->trackTime +=
-						current->timeScale == 0 ? 0 : (nextTime / current->timeScale + delta) * next->timeScale;
-				current->trackTime += currentDelta;
-				_spAnimationState_setCurrent(self, i, next, 1);
-				while (next->mixingFrom) {
-					next->mixTime += delta;
-					next = next->mixingFrom;
-				}
-				continue;
-			}
-		} else {
-			/* Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom. */
-			if (current->trackLast >= current->trackEnd && current->mixingFrom == 0) {
-				self->tracks[i] = 0;
-				_spEventQueue_end(internal->queue, current);
-				spAnimationState_clearNext(self, current);
-				continue;
-			}
-		}
-		if (current->mixingFrom != 0 && _spAnimationState_updateMixingFrom(self, current, delta)) {
-			/* End mixing from entries once all have completed. */
-			spTrackEntry *from = current->mixingFrom;
-			current->mixingFrom = 0;
-			if (from != 0) from->mixingTo = 0;
-			while (from != 0) {
-				_spEventQueue_end(internal->queue, from);
-				from = from->mixingFrom;
-			}
-		}
-
-		current->trackTime += currentDelta;
-	}
-
-	_spEventQueue_drain(internal->queue);
-}
-
-int /*boolean*/ _spAnimationState_updateMixingFrom(spAnimationState *self, spTrackEntry *to, float delta) {
-	spTrackEntry *from = to->mixingFrom;
-	int finished;
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	if (!from) return -1;
-
-	finished = _spAnimationState_updateMixingFrom(self, from, delta);
-
-	from->animationLast = from->nextAnimationLast;
-	from->trackLast = from->nextTrackLast;
-
-	/* Require mixTime > 0 to ensure the mixing from entry was applied at least once. */
-	if (to->mixTime > 0 && to->mixTime >= to->mixDuration) {
-		/* Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). */
-		if (from->totalAlpha == 0 || to->mixDuration == 0) {
-			to->mixingFrom = from->mixingFrom;
-			if (from->mixingFrom != 0) from->mixingFrom->mixingTo = to;
-			to->interruptAlpha = from->interruptAlpha;
-			_spEventQueue_end(internal->queue, from);
-		}
-		return finished;
-	}
-
-	from->trackTime += delta * from->timeScale;
-	to->mixTime += delta;
-	return 0;
-}
-
-int spAnimationState_apply(spAnimationState *self, spSkeleton *skeleton) {
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	spTrackEntry *current;
-	int i, ii, n;
-	float animationLast, animationTime;
-	int timelineCount;
-	spTimeline **timelines;
-	int /*boolean*/ firstFrame;
-	float *timelinesRotation;
-	spTimeline *timeline;
-	int applied = 0;
-	spMixBlend blend;
-	spMixBlend timelineBlend;
-	int setupState = 0;
-	spSlot **slots = NULL;
-	spSlot *slot = NULL;
-	const char *attachmentName = NULL;
-	spEvent **applyEvents = NULL;
-	float applyTime;
-
-	if (internal->animationsChanged) _spAnimationState_animationsChanged(self);
-
-	for (i = 0, n = self->tracksCount; i < n; i++) {
-		float mix;
-		current = self->tracks[i];
-		if (!current || current->delay > 0) continue;
-		applied = -1;
-		blend = i == 0 ? SP_MIX_BLEND_FIRST : current->mixBlend;
-
-		/* Apply mixing from entries first. */
-		mix = current->alpha;
-		if (current->mixingFrom)
-			mix *= _spAnimationState_applyMixingFrom(self, current, skeleton, blend);
-		else if (current->trackTime >= current->trackEnd && current->next == 0)
-			mix = 0;
-
-		/* Apply current entry. */
-		animationLast = current->animationLast;
-		animationTime = spTrackEntry_getAnimationTime(current);
-		timelineCount = current->animation->timelines->size;
-		applyEvents = internal->events;
-		applyTime = animationTime;
-		if (current->reverse) {
-			applyTime = current->animation->duration - applyTime;
-			applyEvents = NULL;
-		}
-		timelines = current->animation->timelines->items;
-		if ((i == 0 && mix == 1) || blend == SP_MIX_BLEND_ADD) {
-			for (ii = 0; ii < timelineCount; ii++) {
-				timeline = timelines[ii];
-				if (timeline->propertyIds[0] == SP_PROPERTY_ATTACHMENT) {
-					_spAnimationState_applyAttachmentTimeline(self, timeline, skeleton, applyTime, blend, -1);
-				} else {
-					spTimeline_apply(timelines[ii], skeleton, animationLast, applyTime, applyEvents,
-									 &internal->eventsCount, mix, blend, SP_MIX_DIRECTION_IN);
-				}
-			}
-		} else {
-			spIntArray *timelineMode = current->timelineMode;
-
-			firstFrame = current->timelinesRotationCount != timelineCount << 1;
-			if (firstFrame) _spAnimationState_resizeTimelinesRotation(current, timelineCount << 1);
-			timelinesRotation = current->timelinesRotation;
-
-			for (ii = 0; ii < timelineCount; ii++) {
-				timeline = timelines[ii];
-				timelineBlend = timelineMode->items[ii] == SUBSEQUENT ? blend : SP_MIX_BLEND_SETUP;
-				if (timeline->propertyIds[0] == SP_PROPERTY_ROTATE)
-					_spAnimationState_applyRotateTimeline(self, timeline, skeleton, applyTime, mix, timelineBlend,
-														  timelinesRotation, ii << 1, firstFrame);
-				else if (timeline->propertyIds[0] == SP_PROPERTY_ATTACHMENT)
-					_spAnimationState_applyAttachmentTimeline(self, timeline, skeleton, applyTime, timelineBlend, -1);
-				else
-					spTimeline_apply(timeline, skeleton, animationLast, applyTime, applyEvents, &internal->eventsCount,
-									 mix, timelineBlend, SP_MIX_DIRECTION_IN);
-			}
-		}
-		_spAnimationState_queueEvents(self, current, animationTime);
-		internal->eventsCount = 0;
-		current->nextAnimationLast = animationTime;
-		current->nextTrackLast = current->trackTime;
-	}
-
-	setupState = self->unkeyedState + SETUP;
-	slots = skeleton->slots;
-	for (i = 0, n = skeleton->slotsCount; i < n; i++) {
-		slot = slots[i];
-		if (slot->attachmentState == setupState) {
-			attachmentName = slot->data->attachmentName;
-			slot->attachment =
-					attachmentName == NULL ? NULL : spSkeleton_getAttachmentForSlotIndex(skeleton, slot->data->index,
-																						 attachmentName);
-		}
-	}
-	self->unkeyedState += 2;
-
-	_spEventQueue_drain(internal->queue);
-	return applied;
-}
-
-float
-_spAnimationState_applyMixingFrom(spAnimationState *self, spTrackEntry *to, spSkeleton *skeleton, spMixBlend blend) {
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	float mix;
-	spEvent **events;
-	int /*boolean*/ attachments;
-	int /*boolean*/ drawOrder;
-	float animationLast;
-	float animationTime;
-	int timelineCount;
-	spTimeline **timelines;
-	spIntArray *timelineMode;
-	spTrackEntryArray *timelineHoldMix;
-	spMixBlend timelineBlend;
-	float alphaHold;
-	float alphaMix;
-	float alpha;
-	int /*boolean*/ firstFrame;
-	float *timelinesRotation;
-	int i;
-	spTrackEntry *holdMix;
-	float applyTime;
-
-	spTrackEntry *from = to->mixingFrom;
-	if (from->mixingFrom) _spAnimationState_applyMixingFrom(self, from, skeleton, blend);
-
-	if (to->mixDuration == 0) { /* Single frame mix to undo mixingFrom changes. */
-		mix = 1;
-		if (blend == SP_MIX_BLEND_FIRST) blend = SP_MIX_BLEND_SETUP;
-	} else {
-		mix = to->mixTime / to->mixDuration;
-		if (mix > 1) mix = 1;
-		if (blend != SP_MIX_BLEND_FIRST) blend = from->mixBlend;
-	}
-
-	attachments = mix < from->attachmentThreshold;
-	drawOrder = mix < from->drawOrderThreshold;
-	timelineCount = from->animation->timelines->size;
-	timelines = from->animation->timelines->items;
-	alphaHold = from->alpha * to->interruptAlpha;
-	alphaMix = alphaHold * (1 - mix);
-	animationLast = from->animationLast;
-	animationTime = spTrackEntry_getAnimationTime(from);
-	applyTime = animationTime;
-	events = NULL;
-	if (from->reverse) {
-		applyTime = from->animation->duration - applyTime;
-	} else {
-		if (mix < from->eventThreshold) events = internal->events;
-	}
-
-	if (blend == SP_MIX_BLEND_ADD) {
-		for (i = 0; i < timelineCount; i++) {
-			spTimeline *timeline = timelines[i];
-			spTimeline_apply(timeline, skeleton, animationLast, applyTime, events, &internal->eventsCount, alphaMix,
-							 blend, SP_MIX_DIRECTION_OUT);
-		}
-	} else {
-		timelineMode = from->timelineMode;
-		timelineHoldMix = from->timelineHoldMix;
-
-		firstFrame = from->timelinesRotationCount != timelineCount << 1;
-		if (firstFrame) _spAnimationState_resizeTimelinesRotation(from, timelineCount << 1);
-		timelinesRotation = from->timelinesRotation;
-
-		from->totalAlpha = 0;
-		for (i = 0; i < timelineCount; i++) {
-			spMixDirection direction = SP_MIX_DIRECTION_OUT;
-			spTimeline *timeline = timelines[i];
-
-			switch (timelineMode->items[i]) {
-				case SUBSEQUENT:
-					if (!drawOrder && timeline->propertyIds[0] == SP_PROPERTY_DRAWORDER) continue;
-					timelineBlend = blend;
-					alpha = alphaMix;
-					break;
-				case FIRST:
-					timelineBlend = SP_MIX_BLEND_SETUP;
-					alpha = alphaMix;
-					break;
-				case HOLD_SUBSEQUENT:
-					timelineBlend = blend;
-					alpha = alphaHold;
-					break;
-				case HOLD_FIRST:
-					timelineBlend = SP_MIX_BLEND_SETUP;
-					alpha = alphaHold;
-					break;
-				default:
-					timelineBlend = SP_MIX_BLEND_SETUP;
-					holdMix = timelineHoldMix->items[i];
-					alpha = alphaHold * MAX(0, 1 - holdMix->mixTime / holdMix->mixDuration);
-					break;
-			}
-			from->totalAlpha += alpha;
-			if (timeline->propertyIds[0] == SP_PROPERTY_ROTATE)
-				_spAnimationState_applyRotateTimeline(self, timeline, skeleton, applyTime, alpha, timelineBlend,
-													  timelinesRotation, i << 1, firstFrame);
-			else if (timeline->propertyIds[0] == SP_PROPERTY_ATTACHMENT)
-				_spAnimationState_applyAttachmentTimeline(self, timeline, skeleton, applyTime, timelineBlend,
-														  attachments);
-			else {
-				if (drawOrder && timeline->propertyIds[0] == SP_PROPERTY_DRAWORDER &&
-					timelineBlend == SP_MIX_BLEND_SETUP)
-					direction = SP_MIX_DIRECTION_IN;
-				spTimeline_apply(timeline, skeleton, animationLast, applyTime, events, &internal->eventsCount,
-								 alpha, timelineBlend, direction);
-			}
-		}
-	}
-
-
-	if (to->mixDuration > 0) _spAnimationState_queueEvents(self, from, animationTime);
-	internal->eventsCount = 0;
-	from->nextAnimationLast = animationTime;
-	from->nextTrackLast = from->trackTime;
-
-	return mix;
-}
-
-static void
-_spAnimationState_setAttachment(spAnimationState *self, spSkeleton *skeleton, spSlot *slot, const char *attachmentName,
-								int /*bool*/ attachments) {
-	slot->attachment = attachmentName == NULL ? NULL : spSkeleton_getAttachmentForSlotIndex(skeleton, slot->data->index,
-																							attachmentName);
-	if (attachments) slot->attachmentState = self->unkeyedState + CURRENT;
-}
-
-/* @param target After the first and before the last entry. */
-static int binarySearch1(float *values, int valuesLength, float target) {
-	int low = 0, current;
-	int high = valuesLength - 2;
-	if (high == 0) return 1;
-	current = high >> 1;
-	while (1) {
-		if (values[(current + 1)] <= target)
-			low = current + 1;
-		else
-			high = current;
-		if (low == high) return low + 1;
-		current = (low + high) >> 1;
-	}
-	return 0;
-}
-
-void _spAnimationState_applyAttachmentTimeline(spAnimationState *self, spTimeline *timeline, spSkeleton *skeleton,
-											   float time, spMixBlend blend, int /*bool*/ attachments) {
-	spAttachmentTimeline *attachmentTimeline;
-	spSlot *slot;
-	float *frames;
-
-	attachmentTimeline = SUB_CAST(spAttachmentTimeline, timeline);
-	slot = skeleton->slots[attachmentTimeline->slotIndex];
-	if (!slot->bone->active) return;
-
-	frames = attachmentTimeline->super.frames->items;
-	if (time < frames[0]) {
-		if (blend == SP_MIX_BLEND_SETUP || blend == SP_MIX_BLEND_FIRST)
-			_spAnimationState_setAttachment(self, skeleton, slot, slot->data->attachmentName, attachments);
-	} else {
-		_spAnimationState_setAttachment(self, skeleton, slot, attachmentTimeline->attachmentNames[binarySearch1(frames,
-																												attachmentTimeline->super.frames->size,
-																												time)],
-										attachments);
-	}
-
-	/* If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.*/
-	if (slot->attachmentState <= self->unkeyedState) slot->attachmentState = self->unkeyedState + SETUP;
-}
-
-void
-_spAnimationState_applyRotateTimeline(spAnimationState *self, spTimeline *timeline, spSkeleton *skeleton, float time,
-									  float alpha, spMixBlend blend, float *timelinesRotation, int i,
-									  int /*boolean*/ firstFrame
-) {
-	spRotateTimeline *rotateTimeline;
-	float *frames;
-	spBone *bone;
-	float r1, r2;
-	float total, diff;
-	int /*boolean*/ current, dir;
-	UNUSED(self);
-
-	if (firstFrame) timelinesRotation[i] = 0;
-
-	if (alpha == 1) {
-		spTimeline_apply(timeline, skeleton, 0, time, 0, 0, 1, blend, SP_MIX_DIRECTION_IN);
-		return;
-	}
-
-	rotateTimeline = SUB_CAST(spRotateTimeline, timeline);
-	frames = rotateTimeline->super.super.frames->items;
-	bone = skeleton->bones[rotateTimeline->boneIndex];
-	if (!bone->active) return;
-	if (time < frames[0]) {
-		switch (blend) {
-			case SP_MIX_BLEND_SETUP:
-				bone->rotation = bone->data->rotation;
-			default:
-				return;
-			case SP_MIX_BLEND_FIRST:
-				r1 = bone->rotation;
-				r2 = bone->data->rotation;
-		}
-	} else {
-		r1 = blend == SP_MIX_BLEND_SETUP ? bone->data->rotation : bone->rotation;
-		r2 = bone->data->rotation + spCurveTimeline1_getCurveValue(&rotateTimeline->super, time);
-	}
-
-	/* Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. */
-	diff = r2 - r1;
-	diff -= (16384 - (int) (16384.499999999996 - diff / 360)) * 360;
-	if (diff == 0) {
-		total = timelinesRotation[i];
-	} else {
-		float lastTotal, lastDiff;
-		if (firstFrame) {
-			lastTotal = 0;
-			lastDiff = diff;
-		} else {
-			lastTotal = timelinesRotation[i]; /* Angle and direction of mix, including loops. */
-			lastDiff = timelinesRotation[i + 1]; /* Difference between bones. */
-		}
-		current = diff > 0;
-		dir = lastTotal >= 0;
-		/* Detect cross at 0 (not 180). */
-		if (SIGNUM(lastDiff) != SIGNUM(diff) && ABS(lastDiff) <= 90) {
-			/* A cross after a 360 rotation is a loop. */
-			if (ABS(lastTotal) > 180) lastTotal += 360 * SIGNUM(lastTotal);
-			dir = current;
-		}
-		total = diff + lastTotal - FMOD(lastTotal, 360); /* Store loops as part of lastTotal. */
-		if (dir != current) total += 360 * SIGNUM(lastTotal);
-		timelinesRotation[i] = total;
-	}
-	timelinesRotation[i + 1] = diff;
-	bone->rotation = r1 + total * alpha;
-}
-
-void _spAnimationState_queueEvents(spAnimationState *self, spTrackEntry *entry, float animationTime) {
-	spEvent **events;
-	spEvent *event;
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	int i, n, complete;
-	float animationStart = entry->animationStart, animationEnd = entry->animationEnd;
-	float duration = animationEnd - animationStart;
-	float trackLastWrapped = FMOD(entry->trackLast, duration);
-
-	/* Queue events before complete. */
-	events = internal->events;
-	for (i = 0, n = internal->eventsCount; i < n; i++) {
-		event = events[i];
-		if (event->time < trackLastWrapped) break;
-		if (event->time > animationEnd) continue; /* Discard events outside animation start/end. */
-		_spEventQueue_event(internal->queue, entry, event);
-	}
-
-	/* Queue complete if completed a loop iteration or the animation. */
-	if (entry->loop)
-		complete = duration == 0 || (trackLastWrapped > FMOD(entry->trackTime, duration));
-	else
-		complete = (animationTime >= animationEnd && entry->animationLast < animationEnd);
-	if (complete) _spEventQueue_complete(internal->queue, entry);
-
-	/* Queue events after complete. */
-	for (; i < n; i++) {
-		event = events[i];
-		if (event->time < animationStart) continue; /* Discard events outside animation start/end. */
-		_spEventQueue_event(internal->queue, entry, event);
-	}
-}
-
-void spAnimationState_clearTracks(spAnimationState *self) {
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	int i, n, oldDrainDisabled;
-	oldDrainDisabled = internal->queue->drainDisabled;
-	internal->queue->drainDisabled = 1;
-	for (i = 0, n = self->tracksCount; i < n; i++)
-		spAnimationState_clearTrack(self, i);
-	self->tracksCount = 0;
-	internal->queue->drainDisabled = oldDrainDisabled;
-	_spEventQueue_drain(internal->queue);
-}
-
-void spAnimationState_clearTrack(spAnimationState *self, int trackIndex) {
-	spTrackEntry *current;
-	spTrackEntry *entry;
-	spTrackEntry *from;
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-
-	if (trackIndex >= self->tracksCount) return;
-	current = self->tracks[trackIndex];
-	if (!current) return;
-
-	_spEventQueue_end(internal->queue, current);
-
-    spAnimationState_clearNext(self, current);
-
-	entry = current;
-	while (1) {
-		from = entry->mixingFrom;
-		if (!from) break;
-		_spEventQueue_end(internal->queue, from);
-		entry->mixingFrom = 0;
-		entry->mixingTo = 0;
-		entry = from;
-	}
-
-	self->tracks[current->trackIndex] = 0;
-	_spEventQueue_drain(internal->queue);
-}
-
-void _spAnimationState_setCurrent(spAnimationState *self, int index, spTrackEntry *current, int /*boolean*/ interrupt) {
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	spTrackEntry *from = _spAnimationState_expandToIndex(self, index);
-	self->tracks[index] = current;
-	current->previous = NULL;
-
-	if (from) {
-		if (interrupt) _spEventQueue_interrupt(internal->queue, from);
-		current->mixingFrom = from;
-		from->mixingTo = current;
-		current->mixTime = 0;
-
-		/* Store the interrupted mix percentage. */
-		if (from->mixingFrom != 0 && from->mixDuration > 0)
-			current->interruptAlpha *= MIN(1, from->mixTime / from->mixDuration);
-
-		from->timelinesRotationCount = 0;
-	}
-
-	_spEventQueue_start(internal->queue, current);
-}
-
-/** Set the current animation. Any queued animations are cleared. */
-spTrackEntry *spAnimationState_setAnimationByName(spAnimationState *self, int trackIndex, const char *animationName,
-												  int/*bool*/loop) {
-	spAnimation *animation = spSkeletonData_findAnimation(self->data->skeletonData, animationName);
-	return spAnimationState_setAnimation(self, trackIndex, animation, loop);
-}
-
-spTrackEntry *
-spAnimationState_setAnimation(spAnimationState *self, int trackIndex, spAnimation *animation, int/*bool*/loop) {
-	spTrackEntry *entry;
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	int interrupt = 1;
-	spTrackEntry *current = _spAnimationState_expandToIndex(self, trackIndex);
-	if (current) {
-		if (current->nextTrackLast == -1) {
-			/* Don't mix from an entry that was never applied. */
-			self->tracks[trackIndex] = current->mixingFrom;
-			_spEventQueue_interrupt(internal->queue, current);
-			_spEventQueue_end(internal->queue, current);
-            spAnimationState_clearNext(self, current);
-			current = current->mixingFrom;
-			interrupt = 0;
-		} else
-            spAnimationState_clearNext(self, current);
-	}
-	entry = _spAnimationState_trackEntry(self, trackIndex, animation, loop, current);
-	_spAnimationState_setCurrent(self, trackIndex, entry, interrupt);
-	_spEventQueue_drain(internal->queue);
-	return entry;
-}
-
-/** Adds an animation to be played delay seconds after the current or last queued animation, taking into account any mix
- * duration. */
-spTrackEntry *spAnimationState_addAnimationByName(spAnimationState *self, int trackIndex, const char *animationName,
-												  int/*bool*/loop, float delay
-) {
-	spAnimation *animation = spSkeletonData_findAnimation(self->data->skeletonData, animationName);
-	return spAnimationState_addAnimation(self, trackIndex, animation, loop, delay);
-}
-
-spTrackEntry *
-spAnimationState_addAnimation(spAnimationState *self, int trackIndex, spAnimation *animation, int/*bool*/loop,
-							  float delay) {
-	spTrackEntry *entry;
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	spTrackEntry *last = _spAnimationState_expandToIndex(self, trackIndex);
-	if (last) {
-		while (last->next)
-			last = last->next;
-	}
-
-	entry = _spAnimationState_trackEntry(self, trackIndex, animation, loop, last);
-
-	if (!last) {
-		_spAnimationState_setCurrent(self, trackIndex, entry, 1);
-		_spEventQueue_drain(internal->queue);
-	} else {
-		last->next = entry;
-		entry->previous = last;
-		if (delay <= 0) delay += spTrackEntry_getTrackComplete(last) - entry->mixDuration;
-	}
-
-	entry->delay = delay;
-	return entry;
-}
-
-spTrackEntry *spAnimationState_setEmptyAnimation(spAnimationState *self, int trackIndex, float mixDuration) {
-	spTrackEntry *entry = spAnimationState_setAnimation(self, trackIndex, SP_EMPTY_ANIMATION, 0);
-	entry->mixDuration = mixDuration;
-	entry->trackEnd = mixDuration;
-	return entry;
-}
-
-spTrackEntry *
-spAnimationState_addEmptyAnimation(spAnimationState *self, int trackIndex, float mixDuration, float delay) {
-	spTrackEntry *entry;
-	entry = spAnimationState_addAnimation(self, trackIndex, SP_EMPTY_ANIMATION, 0, delay <= 0 ? 1 : delay);
-	entry->mixDuration = mixDuration;
-	entry->trackEnd = mixDuration;
-	if (delay <= 0 && entry->previous != NULL)
-		entry->delay = spTrackEntry_getTrackComplete(entry->previous) - entry->mixDuration + delay;
-	return entry;
-}
-
-void spAnimationState_setEmptyAnimations(spAnimationState *self, float mixDuration) {
-	int i, n, oldDrainDisabled;
-	spTrackEntry *current;
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	oldDrainDisabled = internal->queue->drainDisabled;
-	internal->queue->drainDisabled = 1;
-	for (i = 0, n = self->tracksCount; i < n; i++) {
-		current = self->tracks[i];
-		if (current) spAnimationState_setEmptyAnimation(self, current->trackIndex, mixDuration);
-	}
-	internal->queue->drainDisabled = oldDrainDisabled;
-	_spEventQueue_drain(internal->queue);
-}
-
-spTrackEntry *_spAnimationState_expandToIndex(spAnimationState *self, int index) {
-	spTrackEntry **newTracks;
-	if (index < self->tracksCount) return self->tracks[index];
-	newTracks = CALLOC(spTrackEntry*, index + 1);
-	memcpy(newTracks, self->tracks, self->tracksCount * sizeof(spTrackEntry *));
-	FREE(self->tracks);
-	self->tracks = newTracks;
-	self->tracksCount = index + 1;
-	return 0;
-}
-
-spTrackEntry *
-_spAnimationState_trackEntry(spAnimationState *self, int trackIndex, spAnimation *animation, int /*boolean*/ loop,
-							 spTrackEntry *last) {
-	spTrackEntry *entry = NEW(spTrackEntry);
-	entry->trackIndex = trackIndex;
-	entry->animation = animation;
-	entry->loop = loop;
-	entry->holdPrevious = 0;
-	entry->reverse = 0;
-	entry->previous = 0;
-	entry->next = 0;
-
-	entry->eventThreshold = 0;
-	entry->attachmentThreshold = 0;
-	entry->drawOrderThreshold = 0;
-
-	entry->animationStart = 0;
-	entry->animationEnd = animation->duration;
-	entry->animationLast = -1;
-	entry->nextAnimationLast = -1;
-
-	entry->delay = 0;
-	entry->trackTime = 0;
-	entry->trackLast = -1;
-	entry->nextTrackLast = -1;
-	entry->trackEnd = (float) INT_MAX;
-	entry->timeScale = 1;
-
-	entry->alpha = 1;
-	entry->interruptAlpha = 1;
-	entry->mixTime = 0;
-	entry->mixDuration = !last ? 0 : spAnimationStateData_getMix(self->data, last->animation, animation);
-	entry->mixBlend = SP_MIX_BLEND_REPLACE;
-
-	entry->timelineMode = spIntArray_create(16);
-	entry->timelineHoldMix = spTrackEntryArray_create(16);
-
-	return entry;
-}
-
-void spAnimationState_clearNext(spAnimationState *self, spTrackEntry *entry) {
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	spTrackEntry *next = entry->next;
-	while (next) {
-		_spEventQueue_dispose(internal->queue, next);
-		next = next->next;
-	}
-	entry->next = 0;
-}
-
-void _spAnimationState_animationsChanged(spAnimationState *self) {
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	int i, n;
-	spTrackEntry *entry;
-	internal->animationsChanged = 0;
-
-	internal->propertyIDsCount = 0;
-	i = 0;
-	n = self->tracksCount;
-
-	for (; i < n; i++) {
-		entry = self->tracks[i];
-		if (!entry) continue;
-		while (entry->mixingFrom != 0)
-			entry = entry->mixingFrom;
-		do {
-			if (entry->mixingTo == 0 || entry->mixBlend != SP_MIX_BLEND_ADD) _spTrackEntry_computeHold(entry, self);
-			entry = entry->mixingTo;
-		} while (entry != 0);
-	}
-}
-
-float *_spAnimationState_resizeTimelinesRotation(spTrackEntry *entry, int newSize) {
-	if (entry->timelinesRotationCount != newSize) {
-		float *newTimelinesRotation = CALLOC(float, newSize);
-		FREE(entry->timelinesRotation);
-		entry->timelinesRotation = newTimelinesRotation;
-		entry->timelinesRotationCount = newSize;
-	}
-	return entry->timelinesRotation;
-}
-
-void _spAnimationState_ensureCapacityPropertyIDs(spAnimationState *self, int capacity) {
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	if (internal->propertyIDsCapacity < capacity) {
-		spPropertyId *newPropertyIDs = CALLOC(spPropertyId, capacity << 1);
-		memcpy(newPropertyIDs, internal->propertyIDs, sizeof(spPropertyId) * internal->propertyIDsCount);
-		FREE(internal->propertyIDs);
-		internal->propertyIDs = newPropertyIDs;
-		internal->propertyIDsCapacity = capacity << 1;
-	}
-}
-
-int _spAnimationState_addPropertyID(spAnimationState *self, spPropertyId id) {
-	int i, n;
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-
-	for (i = 0, n = internal->propertyIDsCount; i < n; i++) {
-		if (internal->propertyIDs[i] == id) return 0;
-	}
-
-	_spAnimationState_ensureCapacityPropertyIDs(self, internal->propertyIDsCount + 1);
-	internal->propertyIDs[internal->propertyIDsCount] = id;
-	internal->propertyIDsCount++;
-	return 1;
-}
-
-int _spAnimationState_addPropertyIDs(spAnimationState *self, spPropertyId *ids, int numIds) {
-	int i, n;
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	int oldSize = internal->propertyIDsCount;
-
-	for (i = 0, n = numIds; i < n; i++) {
-		_spAnimationState_addPropertyID(self, ids[i]);
-	}
-
-	return internal->propertyIDsCount != oldSize;
-}
-
-spTrackEntry *spAnimationState_getCurrent(spAnimationState *self, int trackIndex) {
-	if (trackIndex >= self->tracksCount) return 0;
-	return self->tracks[trackIndex];
-}
-
-void spAnimationState_clearListenerNotifications(spAnimationState *self) {
-	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
-	_spEventQueue_clear(internal->queue);
-}
-
-float spTrackEntry_getAnimationTime(spTrackEntry *entry) {
-	if (entry->loop) {
-		float duration = entry->animationEnd - entry->animationStart;
-		if (duration == 0) return entry->animationStart;
-		return FMOD(entry->trackTime, duration) + entry->animationStart;
-	}
-	return MIN(entry->trackTime + entry->animationStart, entry->animationEnd);
-}
-
-float spTrackEntry_getTrackComplete(spTrackEntry *entry) {
-	float duration = entry->animationEnd - entry->animationStart;
-	if (duration != 0) {
-		if (entry->loop) return duration * (1 + (int) (entry->trackTime / duration)); /* Completion of next loop. */
-		if (entry->trackTime < duration) return duration; /* Before duration. */
-	}
-	return entry->trackTime; /* Next update. */
-}
-
-void _spTrackEntry_computeHold(spTrackEntry *entry, spAnimationState *state) {
-	spTrackEntry *to;
-	spTimeline **timelines;
-	int timelinesCount;
-	int *timelineMode;
-	spTrackEntry **timelineHoldMix;
-	spTrackEntry *next;
-	int i;
-
-	to = entry->mixingTo;
-	timelines = entry->animation->timelines->items;
-	timelinesCount = entry->animation->timelines->size;
-	timelineMode = spIntArray_setSize(entry->timelineMode, timelinesCount)->items;
-	spTrackEntryArray_clear(entry->timelineHoldMix);
-	timelineHoldMix = spTrackEntryArray_setSize(entry->timelineHoldMix, timelinesCount)->items;
-
-	if (to != 0 && to->holdPrevious) {
-		for (i = 0; i < timelinesCount; i++) {
-			spPropertyId *ids = timelines[i]->propertyIds;
-			int numIds = timelines[i]->propertyIdsCount;
-			timelineMode[i] = _spAnimationState_addPropertyIDs(state, ids, numIds) ? HOLD_FIRST : HOLD_SUBSEQUENT;
-		}
-		return;
-	}
-
-	i = 0;
-	continue_outer:
-	for (; i < timelinesCount; i++) {
-		spTimeline *timeline = timelines[i];
-		spPropertyId *ids = timeline->propertyIds;
-		int numIds = timeline->propertyIdsCount;
-		if (!_spAnimationState_addPropertyIDs(state, ids, numIds))
-			timelineMode[i] = SUBSEQUENT;
-		else if (to == 0 || timeline->propertyIds[0] == SP_PROPERTY_ATTACHMENT ||
-				 timeline->propertyIds[0] == SP_PROPERTY_DRAWORDER ||
-				 timeline->propertyIds[0] == SP_PROPERTY_EVENT ||
-				 !spAnimation_hasTimeline(to->animation, ids, numIds)) {
-			timelineMode[i] = FIRST;
-		} else {
-			for (next = to->mixingTo; next != 0; next = next->mixingTo) {
-				if (spAnimation_hasTimeline(next->animation, ids, numIds)) continue;
-				if (next->mixDuration > 0) {
-					timelineMode[i] = HOLD_MIX;
-					timelineHoldMix[i] = next;
-					i++;
-					goto continue_outer;
-				}
-				break;
-			}
-			timelineMode[i] = HOLD_FIRST;
-		}
-	}
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <limits.h>
+#include <spine/AnimationState.h>
+#include <spine/extension.h>
+
+#define SUBSEQUENT 0
+#define FIRST 1
+#define HOLD_SUBSEQUENT 2
+#define HOLD_FIRST 3
+#define HOLD_MIX 4
+
+#define SETUP 1
+#define CURRENT 2
+
+_SP_ARRAY_IMPLEMENT_TYPE(spTrackEntryArray, spTrackEntry *)
+
+static spAnimation *SP_EMPTY_ANIMATION = 0;
+
+void spAnimationState_disposeStatics() {
+	if (SP_EMPTY_ANIMATION) spAnimation_dispose(SP_EMPTY_ANIMATION);
+	SP_EMPTY_ANIMATION = 0;
+}
+
+/* Forward declaration of some "private" functions so we can keep
+ the same function order in C as we have method order in Java. */
+void _spAnimationState_disposeTrackEntry(spTrackEntry *entry);
+
+void _spAnimationState_disposeTrackEntries(spAnimationState *state, spTrackEntry *entry);
+
+int /*boolean*/ _spAnimationState_updateMixingFrom(spAnimationState *self, spTrackEntry *entry, float delta);
+
+float _spAnimationState_applyMixingFrom(spAnimationState *self, spTrackEntry *entry, spSkeleton *skeleton,
+										spMixBlend currentBlend);
+
+void _spAnimationState_applyRotateTimeline(spAnimationState *self, spTimeline *timeline, spSkeleton *skeleton, float time,
+										   float alpha, spMixBlend blend, float *timelinesRotation, int i,
+										   int /*boolean*/ firstFrame);
+
+void _spAnimationState_applyAttachmentTimeline(spAnimationState *self, spTimeline *timeline, spSkeleton *skeleton,
+											   float animationTime, spMixBlend blend, int /*bool*/ firstFrame);
+
+void _spAnimationState_queueEvents(spAnimationState *self, spTrackEntry *entry, float animationTime);
+
+void _spAnimationState_setCurrent(spAnimationState *self, int index, spTrackEntry *current, int /*boolean*/ interrupt);
+
+spTrackEntry *_spAnimationState_expandToIndex(spAnimationState *self, int index);
+
+spTrackEntry *
+_spAnimationState_trackEntry(spAnimationState *self, int trackIndex, spAnimation *animation, int /*boolean*/ loop,
+							 spTrackEntry *last);
+
+void _spAnimationState_animationsChanged(spAnimationState *self);
+
+float *_spAnimationState_resizeTimelinesRotation(spTrackEntry *entry, int newSize);
+
+void _spAnimationState_ensureCapacityPropertyIDs(spAnimationState *self, int capacity);
+
+int _spAnimationState_addPropertyID(spAnimationState *self, spPropertyId id);
+
+void _spTrackEntry_computeHold(spTrackEntry *self, spAnimationState *state);
+
+_spEventQueue *_spEventQueue_create(_spAnimationState *state) {
+	_spEventQueue *self = CALLOC(_spEventQueue, 1);
+	self->state = state;
+	self->objectsCount = 0;
+	self->objectsCapacity = 16;
+	self->objects = CALLOC(_spEventQueueItem, self->objectsCapacity);
+	self->drainDisabled = 0;
+	return self;
+}
+
+void _spEventQueue_free(_spEventQueue *self) {
+	FREE(self->objects);
+	FREE(self);
+}
+
+void _spEventQueue_ensureCapacity(_spEventQueue *self, int newElements) {
+	if (self->objectsCount + newElements > self->objectsCapacity) {
+		_spEventQueueItem *newObjects;
+		self->objectsCapacity <<= 1;
+		newObjects = CALLOC(_spEventQueueItem, self->objectsCapacity);
+		memcpy(newObjects, self->objects, sizeof(_spEventQueueItem) * self->objectsCount);
+		FREE(self->objects);
+		self->objects = newObjects;
+	}
+}
+
+void _spEventQueue_addType(_spEventQueue *self, spEventType type) {
+	_spEventQueue_ensureCapacity(self, 1);
+	self->objects[self->objectsCount++].type = type;
+}
+
+void _spEventQueue_addEntry(_spEventQueue *self, spTrackEntry *entry) {
+	_spEventQueue_ensureCapacity(self, 1);
+	self->objects[self->objectsCount++].entry = entry;
+}
+
+void _spEventQueue_addEvent(_spEventQueue *self, spEvent *event) {
+	_spEventQueue_ensureCapacity(self, 1);
+	self->objects[self->objectsCount++].event = event;
+}
+
+void _spEventQueue_start(_spEventQueue *self, spTrackEntry *entry) {
+	_spEventQueue_addType(self, SP_ANIMATION_START);
+	_spEventQueue_addEntry(self, entry);
+	self->state->animationsChanged = 1;
+}
+
+void _spEventQueue_interrupt(_spEventQueue *self, spTrackEntry *entry) {
+	_spEventQueue_addType(self, SP_ANIMATION_INTERRUPT);
+	_spEventQueue_addEntry(self, entry);
+}
+
+void _spEventQueue_end(_spEventQueue *self, spTrackEntry *entry) {
+	_spEventQueue_addType(self, SP_ANIMATION_END);
+	_spEventQueue_addEntry(self, entry);
+	self->state->animationsChanged = 1;
+}
+
+void _spEventQueue_dispose(_spEventQueue *self, spTrackEntry *entry) {
+	_spEventQueue_addType(self, SP_ANIMATION_DISPOSE);
+	_spEventQueue_addEntry(self, entry);
+}
+
+void _spEventQueue_complete(_spEventQueue *self, spTrackEntry *entry) {
+	_spEventQueue_addType(self, SP_ANIMATION_COMPLETE);
+	_spEventQueue_addEntry(self, entry);
+}
+
+void _spEventQueue_event(_spEventQueue *self, spTrackEntry *entry, spEvent *event) {
+	_spEventQueue_addType(self, SP_ANIMATION_EVENT);
+	_spEventQueue_addEntry(self, entry);
+	_spEventQueue_addEvent(self, event);
+}
+
+void _spEventQueue_clear(_spEventQueue *self) {
+	self->objectsCount = 0;
+}
+
+void _spEventQueue_drain(_spEventQueue *self) {
+	int i;
+	if (self->drainDisabled) return;
+	self->drainDisabled = 1;
+	for (i = 0; i < self->objectsCount; i += 2) {
+		spEventType type = (spEventType) self->objects[i].type;
+		spTrackEntry *entry = self->objects[i + 1].entry;
+		spEvent *event;
+		switch (type) {
+			case SP_ANIMATION_START:
+			case SP_ANIMATION_INTERRUPT:
+			case SP_ANIMATION_COMPLETE:
+				if (entry->listener) entry->listener(SUPER(self->state), type, entry, 0);
+				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, 0);
+				break;
+			case SP_ANIMATION_END:
+				if (entry->listener) entry->listener(SUPER(self->state), type, entry, 0);
+				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, 0);
+				/* Fall through. */
+			case SP_ANIMATION_DISPOSE:
+				if (entry->listener) entry->listener(SUPER(self->state), SP_ANIMATION_DISPOSE, entry, 0);
+				if (self->state->super.listener)
+					self->state->super.listener(SUPER(self->state), SP_ANIMATION_DISPOSE, entry, 0);
+				_spAnimationState_disposeTrackEntry(entry);
+				break;
+			case SP_ANIMATION_EVENT:
+				event = self->objects[i + 2].event;
+				if (entry->listener) entry->listener(SUPER(self->state), type, entry, event);
+				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, event);
+				i++;
+				break;
+		}
+	}
+	_spEventQueue_clear(self);
+
+	self->drainDisabled = 0;
+}
+
+/* These two functions are needed in the UE4 runtime, see #1037 */
+void _spAnimationState_enableQueue(spAnimationState *self) {
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	internal->queue->drainDisabled = 0;
+}
+
+void _spAnimationState_disableQueue(spAnimationState *self) {
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	internal->queue->drainDisabled = 1;
+}
+
+void _spAnimationState_disposeTrackEntry(spTrackEntry *entry) {
+	spIntArray_dispose(entry->timelineMode);
+	spTrackEntryArray_dispose(entry->timelineHoldMix);
+	FREE(entry->timelinesRotation);
+	FREE(entry);
+}
+
+void _spAnimationState_disposeTrackEntries(spAnimationState *state, spTrackEntry *entry) {
+	while (entry) {
+		spTrackEntry *next = entry->next;
+		spTrackEntry *from = entry->mixingFrom;
+		while (from) {
+			spTrackEntry *nextFrom = from->mixingFrom;
+			if (entry->listener) entry->listener(state, SP_ANIMATION_DISPOSE, from, 0);
+			if (state->listener) state->listener(state, SP_ANIMATION_DISPOSE, from, 0);
+			_spAnimationState_disposeTrackEntry(from);
+			from = nextFrom;
+		}
+		if (entry->listener) entry->listener(state, SP_ANIMATION_DISPOSE, entry, 0);
+		if (state->listener) state->listener(state, SP_ANIMATION_DISPOSE, entry, 0);
+		_spAnimationState_disposeTrackEntry(entry);
+		entry = next;
+	}
+}
+
+spAnimationState *spAnimationState_create(spAnimationStateData *data) {
+	_spAnimationState *internal;
+	spAnimationState *self;
+
+	if (!SP_EMPTY_ANIMATION) {
+		SP_EMPTY_ANIMATION = (spAnimation *) 1; /* dirty trick so we can recursively call spAnimation_create */
+		SP_EMPTY_ANIMATION = spAnimation_create("<empty>", NULL, 0);
+	}
+
+	internal = NEW(_spAnimationState);
+	self = SUPER(internal);
+
+	CONST_CAST(spAnimationStateData *, self->data) = data;
+	self->timeScale = 1;
+
+	internal->queue = _spEventQueue_create(internal);
+	internal->events = CALLOC(spEvent *, 128);
+
+	internal->propertyIDs = CALLOC(spPropertyId, 128);
+	internal->propertyIDsCapacity = 128;
+
+	return self;
+}
+
+void spAnimationState_dispose(spAnimationState *self) {
+	int i;
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	for (i = 0; i < self->tracksCount; i++)
+		_spAnimationState_disposeTrackEntries(self, self->tracks[i]);
+	FREE(self->tracks);
+	_spEventQueue_free(internal->queue);
+	FREE(internal->events);
+	FREE(internal->propertyIDs);
+	FREE(internal);
+}
+
+void spAnimationState_update(spAnimationState *self, float delta) {
+	int i, n;
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	delta *= self->timeScale;
+	for (i = 0, n = self->tracksCount; i < n; i++) {
+		float currentDelta;
+		spTrackEntry *current = self->tracks[i];
+		spTrackEntry *next;
+		if (!current) continue;
+
+		current->animationLast = current->nextAnimationLast;
+		current->trackLast = current->nextTrackLast;
+
+		currentDelta = delta * current->timeScale;
+
+		if (current->delay > 0) {
+			current->delay -= currentDelta;
+			if (current->delay > 0) continue;
+			currentDelta = -current->delay;
+			current->delay = 0;
+		}
+
+		next = current->next;
+		if (next) {
+			/* When the next entry's delay is passed, change to the next entry, preserving leftover time. */
+			float nextTime = current->trackLast - next->delay;
+			if (nextTime >= 0) {
+				next->delay = 0;
+				next->trackTime +=
+						current->timeScale == 0 ? 0 : (nextTime / current->timeScale + delta) * next->timeScale;
+				current->trackTime += currentDelta;
+				_spAnimationState_setCurrent(self, i, next, 1);
+				while (next->mixingFrom) {
+					next->mixTime += delta;
+					next = next->mixingFrom;
+				}
+				continue;
+			}
+		} else {
+			/* Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom. */
+			if (current->trackLast >= current->trackEnd && current->mixingFrom == 0) {
+				self->tracks[i] = 0;
+				_spEventQueue_end(internal->queue, current);
+				spAnimationState_clearNext(self, current);
+				continue;
+			}
+		}
+		if (current->mixingFrom != 0 && _spAnimationState_updateMixingFrom(self, current, delta)) {
+			/* End mixing from entries once all have completed. */
+			spTrackEntry *from = current->mixingFrom;
+			current->mixingFrom = 0;
+			if (from != 0) from->mixingTo = 0;
+			while (from != 0) {
+				_spEventQueue_end(internal->queue, from);
+				from = from->mixingFrom;
+			}
+		}
+
+		current->trackTime += currentDelta;
+	}
+
+	_spEventQueue_drain(internal->queue);
+}
+
+int /*boolean*/ _spAnimationState_updateMixingFrom(spAnimationState *self, spTrackEntry *to, float delta) {
+	spTrackEntry *from = to->mixingFrom;
+	int finished;
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	if (!from) return -1;
+
+	finished = _spAnimationState_updateMixingFrom(self, from, delta);
+
+	from->animationLast = from->nextAnimationLast;
+	from->trackLast = from->nextTrackLast;
+
+	/* Require mixTime > 0 to ensure the mixing from entry was applied at least once. */
+	if (to->mixTime > 0 && to->mixTime >= to->mixDuration) {
+		/* Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). */
+		if (from->totalAlpha == 0 || to->mixDuration == 0) {
+			to->mixingFrom = from->mixingFrom;
+			if (from->mixingFrom != 0) from->mixingFrom->mixingTo = to;
+			to->interruptAlpha = from->interruptAlpha;
+			_spEventQueue_end(internal->queue, from);
+		}
+		return finished;
+	}
+
+	from->trackTime += delta * from->timeScale;
+	to->mixTime += delta;
+	return 0;
+}
+
+int spAnimationState_apply(spAnimationState *self, spSkeleton *skeleton) {
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	spTrackEntry *current;
+	int i, ii, n;
+	float animationLast, animationTime;
+	int timelineCount;
+	spTimeline **timelines;
+	int /*boolean*/ firstFrame;
+	float *timelinesRotation;
+	spTimeline *timeline;
+	int applied = 0;
+	spMixBlend blend;
+	spMixBlend timelineBlend;
+	int setupState = 0;
+	spSlot **slots = NULL;
+	spSlot *slot = NULL;
+	const char *attachmentName = NULL;
+	spEvent **applyEvents = NULL;
+	float applyTime;
+
+	if (internal->animationsChanged) _spAnimationState_animationsChanged(self);
+
+	for (i = 0, n = self->tracksCount; i < n; i++) {
+		float mix;
+		current = self->tracks[i];
+		if (!current || current->delay > 0) continue;
+		applied = -1;
+		blend = i == 0 ? SP_MIX_BLEND_FIRST : current->mixBlend;
+
+		/* Apply mixing from entries first. */
+		mix = current->alpha;
+		if (current->mixingFrom)
+			mix *= _spAnimationState_applyMixingFrom(self, current, skeleton, blend);
+		else if (current->trackTime >= current->trackEnd && current->next == 0)
+			mix = 0;
+
+		/* Apply current entry. */
+		animationLast = current->animationLast;
+		animationTime = spTrackEntry_getAnimationTime(current);
+		timelineCount = current->animation->timelines->size;
+		applyEvents = internal->events;
+		applyTime = animationTime;
+		if (current->reverse) {
+			applyTime = current->animation->duration - applyTime;
+			applyEvents = NULL;
+		}
+		timelines = current->animation->timelines->items;
+		if ((i == 0 && mix == 1) || blend == SP_MIX_BLEND_ADD) {
+			for (ii = 0; ii < timelineCount; ii++) {
+				timeline = timelines[ii];
+				if (timeline->propertyIds[0] == SP_PROPERTY_ATTACHMENT) {
+					_spAnimationState_applyAttachmentTimeline(self, timeline, skeleton, applyTime, blend, -1);
+				} else {
+					spTimeline_apply(timelines[ii], skeleton, animationLast, applyTime, applyEvents,
+									 &internal->eventsCount, mix, blend, SP_MIX_DIRECTION_IN);
+				}
+			}
+		} else {
+			spIntArray *timelineMode = current->timelineMode;
+
+			firstFrame = current->timelinesRotationCount != timelineCount << 1;
+			if (firstFrame) _spAnimationState_resizeTimelinesRotation(current, timelineCount << 1);
+			timelinesRotation = current->timelinesRotation;
+
+			for (ii = 0; ii < timelineCount; ii++) {
+				timeline = timelines[ii];
+				timelineBlend = timelineMode->items[ii] == SUBSEQUENT ? blend : SP_MIX_BLEND_SETUP;
+				if (timeline->propertyIds[0] == SP_PROPERTY_ROTATE)
+					_spAnimationState_applyRotateTimeline(self, timeline, skeleton, applyTime, mix, timelineBlend,
+														  timelinesRotation, ii << 1, firstFrame);
+				else if (timeline->propertyIds[0] == SP_PROPERTY_ATTACHMENT)
+					_spAnimationState_applyAttachmentTimeline(self, timeline, skeleton, applyTime, timelineBlend, -1);
+				else
+					spTimeline_apply(timeline, skeleton, animationLast, applyTime, applyEvents, &internal->eventsCount,
+									 mix, timelineBlend, SP_MIX_DIRECTION_IN);
+			}
+		}
+		_spAnimationState_queueEvents(self, current, animationTime);
+		internal->eventsCount = 0;
+		current->nextAnimationLast = animationTime;
+		current->nextTrackLast = current->trackTime;
+	}
+
+	setupState = self->unkeyedState + SETUP;
+	slots = skeleton->slots;
+	for (i = 0, n = skeleton->slotsCount; i < n; i++) {
+		slot = slots[i];
+		if (slot->attachmentState == setupState) {
+			attachmentName = slot->data->attachmentName;
+			slot->attachment =
+					attachmentName == NULL ? NULL : spSkeleton_getAttachmentForSlotIndex(skeleton, slot->data->index, attachmentName);
+		}
+	}
+	self->unkeyedState += 2;
+
+	_spEventQueue_drain(internal->queue);
+	return applied;
+}
+
+float _spAnimationState_applyMixingFrom(spAnimationState *self, spTrackEntry *to, spSkeleton *skeleton, spMixBlend blend) {
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	float mix;
+	spEvent **events;
+	int /*boolean*/ attachments;
+	int /*boolean*/ drawOrder;
+	float animationLast;
+	float animationTime;
+	int timelineCount;
+	spTimeline **timelines;
+	spIntArray *timelineMode;
+	spTrackEntryArray *timelineHoldMix;
+	spMixBlend timelineBlend;
+	float alphaHold;
+	float alphaMix;
+	float alpha;
+	int /*boolean*/ firstFrame;
+	float *timelinesRotation;
+	int i;
+	spTrackEntry *holdMix;
+	float applyTime;
+
+	spTrackEntry *from = to->mixingFrom;
+	if (from->mixingFrom) _spAnimationState_applyMixingFrom(self, from, skeleton, blend);
+
+	if (to->mixDuration == 0) { /* Single frame mix to undo mixingFrom changes. */
+		mix = 1;
+		if (blend == SP_MIX_BLEND_FIRST) blend = SP_MIX_BLEND_SETUP;
+	} else {
+		mix = to->mixTime / to->mixDuration;
+		if (mix > 1) mix = 1;
+		if (blend != SP_MIX_BLEND_FIRST) blend = from->mixBlend;
+	}
+
+	attachments = mix < from->attachmentThreshold;
+	drawOrder = mix < from->drawOrderThreshold;
+	timelineCount = from->animation->timelines->size;
+	timelines = from->animation->timelines->items;
+	alphaHold = from->alpha * to->interruptAlpha;
+	alphaMix = alphaHold * (1 - mix);
+	animationLast = from->animationLast;
+	animationTime = spTrackEntry_getAnimationTime(from);
+	applyTime = animationTime;
+	events = NULL;
+	if (from->reverse) {
+		applyTime = from->animation->duration - applyTime;
+	} else {
+		if (mix < from->eventThreshold) events = internal->events;
+	}
+
+	if (blend == SP_MIX_BLEND_ADD) {
+		for (i = 0; i < timelineCount; i++) {
+			spTimeline *timeline = timelines[i];
+			spTimeline_apply(timeline, skeleton, animationLast, applyTime, events, &internal->eventsCount, alphaMix,
+							 blend, SP_MIX_DIRECTION_OUT);
+		}
+	} else {
+		timelineMode = from->timelineMode;
+		timelineHoldMix = from->timelineHoldMix;
+
+		firstFrame = from->timelinesRotationCount != timelineCount << 1;
+		if (firstFrame) _spAnimationState_resizeTimelinesRotation(from, timelineCount << 1);
+		timelinesRotation = from->timelinesRotation;
+
+		from->totalAlpha = 0;
+		for (i = 0; i < timelineCount; i++) {
+			spMixDirection direction = SP_MIX_DIRECTION_OUT;
+			spTimeline *timeline = timelines[i];
+
+			switch (timelineMode->items[i]) {
+				case SUBSEQUENT:
+					if (!drawOrder && timeline->propertyIds[0] == SP_PROPERTY_DRAWORDER) continue;
+					timelineBlend = blend;
+					alpha = alphaMix;
+					break;
+				case FIRST:
+					timelineBlend = SP_MIX_BLEND_SETUP;
+					alpha = alphaMix;
+					break;
+				case HOLD_SUBSEQUENT:
+					timelineBlend = blend;
+					alpha = alphaHold;
+					break;
+				case HOLD_FIRST:
+					timelineBlend = SP_MIX_BLEND_SETUP;
+					alpha = alphaHold;
+					break;
+				default:
+					timelineBlend = SP_MIX_BLEND_SETUP;
+					holdMix = timelineHoldMix->items[i];
+					alpha = alphaHold * MAX(0, 1 - holdMix->mixTime / holdMix->mixDuration);
+					break;
+			}
+			from->totalAlpha += alpha;
+			if (timeline->propertyIds[0] == SP_PROPERTY_ROTATE)
+				_spAnimationState_applyRotateTimeline(self, timeline, skeleton, applyTime, alpha, timelineBlend,
+													  timelinesRotation, i << 1, firstFrame);
+			else if (timeline->propertyIds[0] == SP_PROPERTY_ATTACHMENT)
+				_spAnimationState_applyAttachmentTimeline(self, timeline, skeleton, applyTime, timelineBlend,
+														  attachments);
+			else {
+				if (drawOrder && timeline->propertyIds[0] == SP_PROPERTY_DRAWORDER &&
+					timelineBlend == SP_MIX_BLEND_SETUP)
+					direction = SP_MIX_DIRECTION_IN;
+				spTimeline_apply(timeline, skeleton, animationLast, applyTime, events, &internal->eventsCount,
+								 alpha, timelineBlend, direction);
+			}
+		}
+	}
+
+
+	if (to->mixDuration > 0) _spAnimationState_queueEvents(self, from, animationTime);
+	internal->eventsCount = 0;
+	from->nextAnimationLast = animationTime;
+	from->nextTrackLast = from->trackTime;
+
+	return mix;
+}
+
+static void
+_spAnimationState_setAttachment(spAnimationState *self, spSkeleton *skeleton, spSlot *slot, const char *attachmentName,
+								int /*bool*/ attachments) {
+	slot->attachment = attachmentName == NULL ? NULL : spSkeleton_getAttachmentForSlotIndex(skeleton, slot->data->index, attachmentName);
+	if (attachments) slot->attachmentState = self->unkeyedState + CURRENT;
+}
+
+/* @param target After the first and before the last entry. */
+static int binarySearch1(float *values, int valuesLength, float target) {
+	int low = 0, current;
+	int high = valuesLength - 2;
+	if (high == 0) return 1;
+	current = high >> 1;
+	while (1) {
+		if (values[(current + 1)] <= target)
+			low = current + 1;
+		else
+			high = current;
+		if (low == high) return low + 1;
+		current = (low + high) >> 1;
+	}
+	return 0;
+}
+
+void _spAnimationState_applyAttachmentTimeline(spAnimationState *self, spTimeline *timeline, spSkeleton *skeleton,
+											   float time, spMixBlend blend, int /*bool*/ attachments) {
+	spAttachmentTimeline *attachmentTimeline;
+	spSlot *slot;
+	float *frames;
+
+	attachmentTimeline = SUB_CAST(spAttachmentTimeline, timeline);
+	slot = skeleton->slots[attachmentTimeline->slotIndex];
+	if (!slot->bone->active) return;
+
+	frames = attachmentTimeline->super.frames->items;
+	if (time < frames[0]) {
+		if (blend == SP_MIX_BLEND_SETUP || blend == SP_MIX_BLEND_FIRST)
+			_spAnimationState_setAttachment(self, skeleton, slot, slot->data->attachmentName, attachments);
+	} else {
+		_spAnimationState_setAttachment(self, skeleton, slot, attachmentTimeline->attachmentNames[binarySearch1(frames, attachmentTimeline->super.frames->size, time)],
+										attachments);
+	}
+
+	/* If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.*/
+	if (slot->attachmentState <= self->unkeyedState) slot->attachmentState = self->unkeyedState + SETUP;
+}
+
+void _spAnimationState_applyRotateTimeline(spAnimationState *self, spTimeline *timeline, spSkeleton *skeleton, float time,
+										   float alpha, spMixBlend blend, float *timelinesRotation, int i,
+										   int /*boolean*/ firstFrame) {
+	spRotateTimeline *rotateTimeline;
+	float *frames;
+	spBone *bone;
+	float r1, r2;
+	float total, diff;
+	int /*boolean*/ current, dir;
+	UNUSED(self);
+
+	if (firstFrame) timelinesRotation[i] = 0;
+
+	if (alpha == 1) {
+		spTimeline_apply(timeline, skeleton, 0, time, 0, 0, 1, blend, SP_MIX_DIRECTION_IN);
+		return;
+	}
+
+	rotateTimeline = SUB_CAST(spRotateTimeline, timeline);
+	frames = rotateTimeline->super.super.frames->items;
+	bone = skeleton->bones[rotateTimeline->boneIndex];
+	if (!bone->active) return;
+	if (time < frames[0]) {
+		switch (blend) {
+			case SP_MIX_BLEND_SETUP:
+				bone->rotation = bone->data->rotation;
+			default:
+				return;
+			case SP_MIX_BLEND_FIRST:
+				r1 = bone->rotation;
+				r2 = bone->data->rotation;
+		}
+	} else {
+		r1 = blend == SP_MIX_BLEND_SETUP ? bone->data->rotation : bone->rotation;
+		r2 = bone->data->rotation + spCurveTimeline1_getCurveValue(&rotateTimeline->super, time);
+	}
+
+	/* Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. */
+	diff = r2 - r1;
+	diff -= (16384 - (int) (16384.499999999996 - diff / 360)) * 360;
+	if (diff == 0) {
+		total = timelinesRotation[i];
+	} else {
+		float lastTotal, lastDiff;
+		if (firstFrame) {
+			lastTotal = 0;
+			lastDiff = diff;
+		} else {
+			lastTotal = timelinesRotation[i];    /* Angle and direction of mix, including loops. */
+			lastDiff = timelinesRotation[i + 1]; /* Difference between bones. */
+		}
+		current = diff > 0;
+		dir = lastTotal >= 0;
+		/* Detect cross at 0 (not 180). */
+		if (SIGNUM(lastDiff) != SIGNUM(diff) && ABS(lastDiff) <= 90) {
+			/* A cross after a 360 rotation is a loop. */
+			if (ABS(lastTotal) > 180) lastTotal += 360 * SIGNUM(lastTotal);
+			dir = current;
+		}
+		total = diff + lastTotal - FMOD(lastTotal, 360); /* Store loops as part of lastTotal. */
+		if (dir != current) total += 360 * SIGNUM(lastTotal);
+		timelinesRotation[i] = total;
+	}
+	timelinesRotation[i + 1] = diff;
+	bone->rotation = r1 + total * alpha;
+}
+
+void _spAnimationState_queueEvents(spAnimationState *self, spTrackEntry *entry, float animationTime) {
+	spEvent **events;
+	spEvent *event;
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	int i, n, complete;
+	float animationStart = entry->animationStart, animationEnd = entry->animationEnd;
+	float duration = animationEnd - animationStart;
+	float trackLastWrapped = FMOD(entry->trackLast, duration);
+
+	/* Queue events before complete. */
+	events = internal->events;
+	for (i = 0, n = internal->eventsCount; i < n; i++) {
+		event = events[i];
+		if (event->time < trackLastWrapped) break;
+		if (event->time > animationEnd) continue; /* Discard events outside animation start/end. */
+		_spEventQueue_event(internal->queue, entry, event);
+	}
+
+	/* Queue complete if completed a loop iteration or the animation. */
+	if (entry->loop)
+		complete = duration == 0 || (trackLastWrapped > FMOD(entry->trackTime, duration));
+	else
+		complete = (animationTime >= animationEnd && entry->animationLast < animationEnd);
+	if (complete) _spEventQueue_complete(internal->queue, entry);
+
+	/* Queue events after complete. */
+	for (; i < n; i++) {
+		event = events[i];
+		if (event->time < animationStart) continue; /* Discard events outside animation start/end. */
+		_spEventQueue_event(internal->queue, entry, event);
+	}
+}
+
+void spAnimationState_clearTracks(spAnimationState *self) {
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	int i, n, oldDrainDisabled;
+	oldDrainDisabled = internal->queue->drainDisabled;
+	internal->queue->drainDisabled = 1;
+	for (i = 0, n = self->tracksCount; i < n; i++)
+		spAnimationState_clearTrack(self, i);
+	self->tracksCount = 0;
+	internal->queue->drainDisabled = oldDrainDisabled;
+	_spEventQueue_drain(internal->queue);
+}
+
+void spAnimationState_clearTrack(spAnimationState *self, int trackIndex) {
+	spTrackEntry *current;
+	spTrackEntry *entry;
+	spTrackEntry *from;
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+
+	if (trackIndex >= self->tracksCount) return;
+	current = self->tracks[trackIndex];
+	if (!current) return;
+
+	_spEventQueue_end(internal->queue, current);
+
+	spAnimationState_clearNext(self, current);
+
+	entry = current;
+	while (1) {
+		from = entry->mixingFrom;
+		if (!from) break;
+		_spEventQueue_end(internal->queue, from);
+		entry->mixingFrom = 0;
+		entry->mixingTo = 0;
+		entry = from;
+	}
+
+	self->tracks[current->trackIndex] = 0;
+	_spEventQueue_drain(internal->queue);
+}
+
+void _spAnimationState_setCurrent(spAnimationState *self, int index, spTrackEntry *current, int /*boolean*/ interrupt) {
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	spTrackEntry *from = _spAnimationState_expandToIndex(self, index);
+	self->tracks[index] = current;
+	current->previous = NULL;
+
+	if (from) {
+		if (interrupt) _spEventQueue_interrupt(internal->queue, from);
+		current->mixingFrom = from;
+		from->mixingTo = current;
+		current->mixTime = 0;
+
+		/* Store the interrupted mix percentage. */
+		if (from->mixingFrom != 0 && from->mixDuration > 0)
+			current->interruptAlpha *= MIN(1, from->mixTime / from->mixDuration);
+
+		from->timelinesRotationCount = 0;
+	}
+
+	_spEventQueue_start(internal->queue, current);
+}
+
+/** Set the current animation. Any queued animations are cleared. */
+spTrackEntry *spAnimationState_setAnimationByName(spAnimationState *self, int trackIndex, const char *animationName,
+												  int /*bool*/ loop) {
+	spAnimation *animation = spSkeletonData_findAnimation(self->data->skeletonData, animationName);
+	return spAnimationState_setAnimation(self, trackIndex, animation, loop);
+}
+
+spTrackEntry *
+spAnimationState_setAnimation(spAnimationState *self, int trackIndex, spAnimation *animation, int /*bool*/ loop) {
+	spTrackEntry *entry;
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	int interrupt = 1;
+	spTrackEntry *current = _spAnimationState_expandToIndex(self, trackIndex);
+	if (current) {
+		if (current->nextTrackLast == -1) {
+			/* Don't mix from an entry that was never applied. */
+			self->tracks[trackIndex] = current->mixingFrom;
+			_spEventQueue_interrupt(internal->queue, current);
+			_spEventQueue_end(internal->queue, current);
+			spAnimationState_clearNext(self, current);
+			current = current->mixingFrom;
+			interrupt = 0;
+		} else
+			spAnimationState_clearNext(self, current);
+	}
+	entry = _spAnimationState_trackEntry(self, trackIndex, animation, loop, current);
+	_spAnimationState_setCurrent(self, trackIndex, entry, interrupt);
+	_spEventQueue_drain(internal->queue);
+	return entry;
+}
+
+/** Adds an animation to be played delay seconds after the current or last queued animation, taking into account any mix
+ * duration. */
+spTrackEntry *spAnimationState_addAnimationByName(spAnimationState *self, int trackIndex, const char *animationName,
+												  int /*bool*/ loop, float delay) {
+	spAnimation *animation = spSkeletonData_findAnimation(self->data->skeletonData, animationName);
+	return spAnimationState_addAnimation(self, trackIndex, animation, loop, delay);
+}
+
+spTrackEntry *
+spAnimationState_addAnimation(spAnimationState *self, int trackIndex, spAnimation *animation, int /*bool*/ loop,
+							  float delay) {
+	spTrackEntry *entry;
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	spTrackEntry *last = _spAnimationState_expandToIndex(self, trackIndex);
+	if (last) {
+		while (last->next)
+			last = last->next;
+	}
+
+	entry = _spAnimationState_trackEntry(self, trackIndex, animation, loop, last);
+
+	if (!last) {
+		_spAnimationState_setCurrent(self, trackIndex, entry, 1);
+		_spEventQueue_drain(internal->queue);
+	} else {
+		last->next = entry;
+		entry->previous = last;
+		if (delay <= 0) delay += spTrackEntry_getTrackComplete(last) - entry->mixDuration;
+	}
+
+	entry->delay = delay;
+	return entry;
+}
+
+spTrackEntry *spAnimationState_setEmptyAnimation(spAnimationState *self, int trackIndex, float mixDuration) {
+	spTrackEntry *entry = spAnimationState_setAnimation(self, trackIndex, SP_EMPTY_ANIMATION, 0);
+	entry->mixDuration = mixDuration;
+	entry->trackEnd = mixDuration;
+	return entry;
+}
+
+spTrackEntry *
+spAnimationState_addEmptyAnimation(spAnimationState *self, int trackIndex, float mixDuration, float delay) {
+	spTrackEntry *entry;
+	entry = spAnimationState_addAnimation(self, trackIndex, SP_EMPTY_ANIMATION, 0, delay <= 0 ? 1 : delay);
+	entry->mixDuration = mixDuration;
+	entry->trackEnd = mixDuration;
+	if (delay <= 0 && entry->previous != NULL)
+		entry->delay = spTrackEntry_getTrackComplete(entry->previous) - entry->mixDuration + delay;
+	return entry;
+}
+
+void spAnimationState_setEmptyAnimations(spAnimationState *self, float mixDuration) {
+	int i, n, oldDrainDisabled;
+	spTrackEntry *current;
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	oldDrainDisabled = internal->queue->drainDisabled;
+	internal->queue->drainDisabled = 1;
+	for (i = 0, n = self->tracksCount; i < n; i++) {
+		current = self->tracks[i];
+		if (current) spAnimationState_setEmptyAnimation(self, current->trackIndex, mixDuration);
+	}
+	internal->queue->drainDisabled = oldDrainDisabled;
+	_spEventQueue_drain(internal->queue);
+}
+
+spTrackEntry *_spAnimationState_expandToIndex(spAnimationState *self, int index) {
+	spTrackEntry **newTracks;
+	if (index < self->tracksCount) return self->tracks[index];
+	newTracks = CALLOC(spTrackEntry *, index + 1);
+	memcpy(newTracks, self->tracks, self->tracksCount * sizeof(spTrackEntry *));
+	FREE(self->tracks);
+	self->tracks = newTracks;
+	self->tracksCount = index + 1;
+	return 0;
+}
+
+spTrackEntry *
+_spAnimationState_trackEntry(spAnimationState *self, int trackIndex, spAnimation *animation, int /*boolean*/ loop,
+							 spTrackEntry *last) {
+	spTrackEntry *entry = NEW(spTrackEntry);
+	entry->trackIndex = trackIndex;
+	entry->animation = animation;
+	entry->loop = loop;
+	entry->holdPrevious = 0;
+	entry->reverse = 0;
+	entry->previous = 0;
+	entry->next = 0;
+
+	entry->eventThreshold = 0;
+	entry->attachmentThreshold = 0;
+	entry->drawOrderThreshold = 0;
+
+	entry->animationStart = 0;
+	entry->animationEnd = animation->duration;
+	entry->animationLast = -1;
+	entry->nextAnimationLast = -1;
+
+	entry->delay = 0;
+	entry->trackTime = 0;
+	entry->trackLast = -1;
+	entry->nextTrackLast = -1;
+	entry->trackEnd = (float) INT_MAX;
+	entry->timeScale = 1;
+
+	entry->alpha = 1;
+	entry->interruptAlpha = 1;
+	entry->mixTime = 0;
+	entry->mixDuration = !last ? 0 : spAnimationStateData_getMix(self->data, last->animation, animation);
+	entry->mixBlend = SP_MIX_BLEND_REPLACE;
+
+	entry->timelineMode = spIntArray_create(16);
+	entry->timelineHoldMix = spTrackEntryArray_create(16);
+
+	return entry;
+}
+
+void spAnimationState_clearNext(spAnimationState *self, spTrackEntry *entry) {
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	spTrackEntry *next = entry->next;
+	while (next) {
+		_spEventQueue_dispose(internal->queue, next);
+		next = next->next;
+	}
+	entry->next = 0;
+}
+
+void _spAnimationState_animationsChanged(spAnimationState *self) {
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	int i, n;
+	spTrackEntry *entry;
+	internal->animationsChanged = 0;
+
+	internal->propertyIDsCount = 0;
+	i = 0;
+	n = self->tracksCount;
+
+	for (; i < n; i++) {
+		entry = self->tracks[i];
+		if (!entry) continue;
+		while (entry->mixingFrom != 0)
+			entry = entry->mixingFrom;
+		do {
+			if (entry->mixingTo == 0 || entry->mixBlend != SP_MIX_BLEND_ADD) _spTrackEntry_computeHold(entry, self);
+			entry = entry->mixingTo;
+		} while (entry != 0);
+	}
+}
+
+float *_spAnimationState_resizeTimelinesRotation(spTrackEntry *entry, int newSize) {
+	if (entry->timelinesRotationCount != newSize) {
+		float *newTimelinesRotation = CALLOC(float, newSize);
+		FREE(entry->timelinesRotation);
+		entry->timelinesRotation = newTimelinesRotation;
+		entry->timelinesRotationCount = newSize;
+	}
+	return entry->timelinesRotation;
+}
+
+void _spAnimationState_ensureCapacityPropertyIDs(spAnimationState *self, int capacity) {
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	if (internal->propertyIDsCapacity < capacity) {
+		spPropertyId *newPropertyIDs = CALLOC(spPropertyId, capacity << 1);
+		memcpy(newPropertyIDs, internal->propertyIDs, sizeof(spPropertyId) * internal->propertyIDsCount);
+		FREE(internal->propertyIDs);
+		internal->propertyIDs = newPropertyIDs;
+		internal->propertyIDsCapacity = capacity << 1;
+	}
+}
+
+int _spAnimationState_addPropertyID(spAnimationState *self, spPropertyId id) {
+	int i, n;
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+
+	for (i = 0, n = internal->propertyIDsCount; i < n; i++) {
+		if (internal->propertyIDs[i] == id) return 0;
+	}
+
+	_spAnimationState_ensureCapacityPropertyIDs(self, internal->propertyIDsCount + 1);
+	internal->propertyIDs[internal->propertyIDsCount] = id;
+	internal->propertyIDsCount++;
+	return 1;
+}
+
+int _spAnimationState_addPropertyIDs(spAnimationState *self, spPropertyId *ids, int numIds) {
+	int i, n;
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	int oldSize = internal->propertyIDsCount;
+
+	for (i = 0, n = numIds; i < n; i++) {
+		_spAnimationState_addPropertyID(self, ids[i]);
+	}
+
+	return internal->propertyIDsCount != oldSize;
+}
+
+spTrackEntry *spAnimationState_getCurrent(spAnimationState *self, int trackIndex) {
+	if (trackIndex >= self->tracksCount) return 0;
+	return self->tracks[trackIndex];
+}
+
+void spAnimationState_clearListenerNotifications(spAnimationState *self) {
+	_spAnimationState *internal = SUB_CAST(_spAnimationState, self);
+	_spEventQueue_clear(internal->queue);
+}
+
+float spTrackEntry_getAnimationTime(spTrackEntry *entry) {
+	if (entry->loop) {
+		float duration = entry->animationEnd - entry->animationStart;
+		if (duration == 0) return entry->animationStart;
+		return FMOD(entry->trackTime, duration) + entry->animationStart;
+	}
+	return MIN(entry->trackTime + entry->animationStart, entry->animationEnd);
+}
+
+float spTrackEntry_getTrackComplete(spTrackEntry *entry) {
+	float duration = entry->animationEnd - entry->animationStart;
+	if (duration != 0) {
+		if (entry->loop) return duration * (1 + (int) (entry->trackTime / duration)); /* Completion of next loop. */
+		if (entry->trackTime < duration) return duration;                             /* Before duration. */
+	}
+	return entry->trackTime; /* Next update. */
+}
+
+void _spTrackEntry_computeHold(spTrackEntry *entry, spAnimationState *state) {
+	spTrackEntry *to;
+	spTimeline **timelines;
+	int timelinesCount;
+	int *timelineMode;
+	spTrackEntry **timelineHoldMix;
+	spTrackEntry *next;
+	int i;
+
+	to = entry->mixingTo;
+	timelines = entry->animation->timelines->items;
+	timelinesCount = entry->animation->timelines->size;
+	timelineMode = spIntArray_setSize(entry->timelineMode, timelinesCount)->items;
+	spTrackEntryArray_clear(entry->timelineHoldMix);
+	timelineHoldMix = spTrackEntryArray_setSize(entry->timelineHoldMix, timelinesCount)->items;
+
+	if (to != 0 && to->holdPrevious) {
+		for (i = 0; i < timelinesCount; i++) {
+			spPropertyId *ids = timelines[i]->propertyIds;
+			int numIds = timelines[i]->propertyIdsCount;
+			timelineMode[i] = _spAnimationState_addPropertyIDs(state, ids, numIds) ? HOLD_FIRST : HOLD_SUBSEQUENT;
+		}
+		return;
+	}
+
+	i = 0;
+continue_outer:
+	for (; i < timelinesCount; i++) {
+		spTimeline *timeline = timelines[i];
+		spPropertyId *ids = timeline->propertyIds;
+		int numIds = timeline->propertyIdsCount;
+		if (!_spAnimationState_addPropertyIDs(state, ids, numIds))
+			timelineMode[i] = SUBSEQUENT;
+		else if (to == 0 || timeline->propertyIds[0] == SP_PROPERTY_ATTACHMENT ||
+				 timeline->propertyIds[0] == SP_PROPERTY_DRAWORDER ||
+				 timeline->propertyIds[0] == SP_PROPERTY_EVENT ||
+				 !spAnimation_hasTimeline(to->animation, ids, numIds)) {
+			timelineMode[i] = FIRST;
+		} else {
+			for (next = to->mixingTo; next != 0; next = next->mixingTo) {
+				if (spAnimation_hasTimeline(next->animation, ids, numIds)) continue;
+				if (next->mixDuration > 0) {
+					timelineMode[i] = HOLD_MIX;
+					timelineHoldMix[i] = next;
+					i++;
+					goto continue_outer;
+				}
+				break;
+			}
+			timelineMode[i] = HOLD_FIRST;
+		}
+	}
+}

+ 151 - 151
spine-c/spine-c/src/spine/AnimationStateData.c

@@ -1,151 +1,151 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/AnimationStateData.h>
-#include <spine/extension.h>
-
-typedef struct _ToEntry _ToEntry;
-struct _ToEntry {
-	spAnimation *animation;
-	float duration;
-	_ToEntry *next;
-};
-
-_ToEntry *_ToEntry_create(spAnimation *to, float duration) {
-	_ToEntry *self = NEW(_ToEntry);
-	self->animation = to;
-	self->duration = duration;
-	return self;
-}
-
-void _ToEntry_dispose(_ToEntry *self) {
-	FREE(self);
-}
-
-/**/
-
-typedef struct _FromEntry _FromEntry;
-struct _FromEntry {
-	spAnimation *animation;
-	_ToEntry *toEntries;
-	_FromEntry *next;
-};
-
-_FromEntry *_FromEntry_create(spAnimation *from) {
-	_FromEntry *self = NEW(_FromEntry);
-	self->animation = from;
-	return self;
-}
-
-void _FromEntry_dispose(_FromEntry *self) {
-	FREE(self);
-}
-
-/**/
-
-spAnimationStateData *spAnimationStateData_create(spSkeletonData *skeletonData) {
-	spAnimationStateData *self = NEW(spAnimationStateData);
-	CONST_CAST(spSkeletonData*, self->skeletonData) = skeletonData;
-	return self;
-}
-
-void spAnimationStateData_dispose(spAnimationStateData *self) {
-	_ToEntry *toEntry;
-	_ToEntry *nextToEntry;
-	_FromEntry *nextFromEntry;
-
-	_FromEntry *fromEntry = (_FromEntry *) self->entries;
-	while (fromEntry) {
-		toEntry = fromEntry->toEntries;
-		while (toEntry) {
-			nextToEntry = toEntry->next;
-			_ToEntry_dispose(toEntry);
-			toEntry = nextToEntry;
-		}
-		nextFromEntry = fromEntry->next;
-		_FromEntry_dispose(fromEntry);
-		fromEntry = nextFromEntry;
-	}
-
-	FREE(self);
-}
-
-void spAnimationStateData_setMixByName(spAnimationStateData *self, const char *fromName, const char *toName,
-									   float duration) {
-	spAnimation *to;
-	spAnimation *from = spSkeletonData_findAnimation(self->skeletonData, fromName);
-	if (!from) return;
-	to = spSkeletonData_findAnimation(self->skeletonData, toName);
-	if (!to) return;
-	spAnimationStateData_setMix(self, from, to, duration);
-}
-
-void spAnimationStateData_setMix(spAnimationStateData *self, spAnimation *from, spAnimation *to, float duration) {
-	/* Find existing FromEntry. */
-	_ToEntry *toEntry;
-	_FromEntry *fromEntry = (_FromEntry *) self->entries;
-	while (fromEntry) {
-		if (fromEntry->animation == from) {
-			/* Find existing ToEntry. */
-			toEntry = fromEntry->toEntries;
-			while (toEntry) {
-				if (toEntry->animation == to) {
-					toEntry->duration = duration;
-					return;
-				}
-				toEntry = toEntry->next;
-			}
-			break; /* Add new ToEntry to the existing FromEntry. */
-		}
-		fromEntry = fromEntry->next;
-	}
-	if (!fromEntry) {
-		fromEntry = _FromEntry_create(from);
-		fromEntry->next = (_FromEntry *) self->entries;
-		CONST_CAST(_FromEntry*, self->entries) = fromEntry;
-	}
-	toEntry = _ToEntry_create(to, duration);
-	toEntry->next = fromEntry->toEntries;
-	fromEntry->toEntries = toEntry;
-}
-
-float spAnimationStateData_getMix(spAnimationStateData *self, spAnimation *from, spAnimation *to) {
-	_FromEntry *fromEntry = (_FromEntry *) self->entries;
-	while (fromEntry) {
-		if (fromEntry->animation == from) {
-			_ToEntry *toEntry = fromEntry->toEntries;
-			while (toEntry) {
-				if (toEntry->animation == to) return toEntry->duration;
-				toEntry = toEntry->next;
-			}
-		}
-		fromEntry = fromEntry->next;
-	}
-	return self->defaultMix;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/AnimationStateData.h>
+#include <spine/extension.h>
+
+typedef struct _ToEntry _ToEntry;
+struct _ToEntry {
+	spAnimation *animation;
+	float duration;
+	_ToEntry *next;
+};
+
+_ToEntry *_ToEntry_create(spAnimation *to, float duration) {
+	_ToEntry *self = NEW(_ToEntry);
+	self->animation = to;
+	self->duration = duration;
+	return self;
+}
+
+void _ToEntry_dispose(_ToEntry *self) {
+	FREE(self);
+}
+
+/**/
+
+typedef struct _FromEntry _FromEntry;
+struct _FromEntry {
+	spAnimation *animation;
+	_ToEntry *toEntries;
+	_FromEntry *next;
+};
+
+_FromEntry *_FromEntry_create(spAnimation *from) {
+	_FromEntry *self = NEW(_FromEntry);
+	self->animation = from;
+	return self;
+}
+
+void _FromEntry_dispose(_FromEntry *self) {
+	FREE(self);
+}
+
+/**/
+
+spAnimationStateData *spAnimationStateData_create(spSkeletonData *skeletonData) {
+	spAnimationStateData *self = NEW(spAnimationStateData);
+	CONST_CAST(spSkeletonData *, self->skeletonData) = skeletonData;
+	return self;
+}
+
+void spAnimationStateData_dispose(spAnimationStateData *self) {
+	_ToEntry *toEntry;
+	_ToEntry *nextToEntry;
+	_FromEntry *nextFromEntry;
+
+	_FromEntry *fromEntry = (_FromEntry *) self->entries;
+	while (fromEntry) {
+		toEntry = fromEntry->toEntries;
+		while (toEntry) {
+			nextToEntry = toEntry->next;
+			_ToEntry_dispose(toEntry);
+			toEntry = nextToEntry;
+		}
+		nextFromEntry = fromEntry->next;
+		_FromEntry_dispose(fromEntry);
+		fromEntry = nextFromEntry;
+	}
+
+	FREE(self);
+}
+
+void spAnimationStateData_setMixByName(spAnimationStateData *self, const char *fromName, const char *toName,
+									   float duration) {
+	spAnimation *to;
+	spAnimation *from = spSkeletonData_findAnimation(self->skeletonData, fromName);
+	if (!from) return;
+	to = spSkeletonData_findAnimation(self->skeletonData, toName);
+	if (!to) return;
+	spAnimationStateData_setMix(self, from, to, duration);
+}
+
+void spAnimationStateData_setMix(spAnimationStateData *self, spAnimation *from, spAnimation *to, float duration) {
+	/* Find existing FromEntry. */
+	_ToEntry *toEntry;
+	_FromEntry *fromEntry = (_FromEntry *) self->entries;
+	while (fromEntry) {
+		if (fromEntry->animation == from) {
+			/* Find existing ToEntry. */
+			toEntry = fromEntry->toEntries;
+			while (toEntry) {
+				if (toEntry->animation == to) {
+					toEntry->duration = duration;
+					return;
+				}
+				toEntry = toEntry->next;
+			}
+			break; /* Add new ToEntry to the existing FromEntry. */
+		}
+		fromEntry = fromEntry->next;
+	}
+	if (!fromEntry) {
+		fromEntry = _FromEntry_create(from);
+		fromEntry->next = (_FromEntry *) self->entries;
+		CONST_CAST(_FromEntry *, self->entries) = fromEntry;
+	}
+	toEntry = _ToEntry_create(to, duration);
+	toEntry->next = fromEntry->toEntries;
+	fromEntry->toEntries = toEntry;
+}
+
+float spAnimationStateData_getMix(spAnimationStateData *self, spAnimation *from, spAnimation *to) {
+	_FromEntry *fromEntry = (_FromEntry *) self->entries;
+	while (fromEntry) {
+		if (fromEntry->animation == from) {
+			_ToEntry *toEntry = fromEntry->toEntries;
+			while (toEntry) {
+				if (toEntry->animation == to) return toEntry->duration;
+				toEntry = toEntry->next;
+			}
+		}
+		fromEntry = fromEntry->next;
+	}
+	return self->defaultMix;
+}

+ 2 - 2
spine-c/spine-c/src/spine/Array.c

@@ -38,6 +38,6 @@ _SP_ARRAY_IMPLEMENT_TYPE(spShortArray, short)
 
 _SP_ARRAY_IMPLEMENT_TYPE(spUnsignedShortArray, unsigned short)
 
-_SP_ARRAY_IMPLEMENT_TYPE(spArrayFloatArray, spFloatArray*)
+_SP_ARRAY_IMPLEMENT_TYPE(spArrayFloatArray, spFloatArray *)
 
-_SP_ARRAY_IMPLEMENT_TYPE(spArrayShortArray, spShortArray*)
+_SP_ARRAY_IMPLEMENT_TYPE(spArrayShortArray, spShortArray *)

+ 464 - 462
spine-c/spine-c/src/spine/Atlas.c

@@ -1,462 +1,464 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/Atlas.h>
-#include <ctype.h>
-#include <spine/extension.h>
-
-spKeyValueArray *spKeyValueArray_create(int initialCapacity) {
-	spKeyValueArray *array = ((spKeyValueArray *) _spCalloc(1, sizeof(spKeyValueArray), "_file_name_", 39));
-	array->size = 0;
-	array->capacity = initialCapacity;
-	array->items = ((spKeyValue *) _spCalloc(initialCapacity, sizeof(spKeyValue), "_file_name_", 39));
-	return array;
-}
-
-void spKeyValueArray_dispose(spKeyValueArray *self) {
-	_spFree((void *) self->items);
-	_spFree((void *) self);
-}
-
-void spKeyValueArray_clear(spKeyValueArray *self) { self->size = 0; }
-
-spKeyValueArray *spKeyValueArray_setSize(spKeyValueArray *self, int newSize) {
-	self->size = newSize;
-	if (self->capacity < newSize) {
-		self->capacity = ((8) > ((int) (self->size * 1.75f)) ? (8) : ((int) (self->size * 1.75f)));
-		self->items = ((spKeyValue *) _spRealloc(self->items, sizeof(spKeyValue) * (self->capacity)));
-	}
-	return self;
-}
-
-void spKeyValueArray_ensureCapacity(spKeyValueArray *self, int newCapacity) {
-	if (self->capacity >= newCapacity)return;
-	self->capacity = newCapacity;
-	self->items = ((spKeyValue *) _spRealloc(self->items, sizeof(spKeyValue) * (self->capacity)));
-}
-
-void spKeyValueArray_add(spKeyValueArray *self, spKeyValue value) {
-	if (self->size == self->capacity) {
-		self->capacity = ((8) > ((int) (self->size * 1.75f)) ? (8) : ((int) (self->size * 1.75f)));
-		self->items = ((spKeyValue *) _spRealloc(self->items, sizeof(spKeyValue) * (self->capacity)));
-	}
-	self->items[self->size++] = value;
-}
-
-void spKeyValueArray_addAll(spKeyValueArray *self, spKeyValueArray *other) {
-	int i = 0;
-	for (; i < other->size; i++) { spKeyValueArray_add(self, other->items[i]); }
-}
-
-void spKeyValueArray_addAllValues(spKeyValueArray *self, spKeyValue *values, int offset, int count) {
-	int i = offset, n = offset + count;
-	for (; i < n; i++) { spKeyValueArray_add(self, values[i]); }
-}
-
-int spKeyValueArray_contains(spKeyValueArray *self, spKeyValue value) {
-	spKeyValue *items = self->items;
-	int i, n;
-	for (i = 0, n = self->size; i < n; i++) { if (!strcmp(items[i].name, value.name))return -1; }
-	return 0;
-}
-
-spKeyValue spKeyValueArray_pop(spKeyValueArray *self) {
-	spKeyValue item = self->items[--self->size];
-	return item;
-}
-
-spKeyValue spKeyValueArray_peek(spKeyValueArray *self) { return self->items[self->size - 1]; }
-
-spAtlasPage *spAtlasPage_create(spAtlas *atlas, const char *name) {
-	spAtlasPage *self = NEW(spAtlasPage);
-	CONST_CAST(spAtlas*, self->atlas) = atlas;
-	MALLOC_STR(self->name, name);
-	return self;
-}
-
-void spAtlasPage_dispose(spAtlasPage *self) {
-	_spAtlasPage_disposeTexture(self);
-	FREE(self->name);
-	FREE(self);
-}
-
-/**/
-
-spAtlasRegion *spAtlasRegion_create() {
-	spAtlasRegion *region = NEW(spAtlasRegion);
-	region->keyValues = spKeyValueArray_create(2);
-	return region;
-}
-
-void spAtlasRegion_dispose(spAtlasRegion *self) {
-	int i, n;
-	FREE(self->name);
-	FREE(self->splits);
-	FREE(self->pads);
-	for (i = 0, n = self->keyValues->size; i < n; i++) {
-		FREE(self->keyValues->items[i].name);
-	}
-	spKeyValueArray_dispose(self->keyValues);
-	FREE(self);
-}
-
-/**/
-
-typedef struct SimpleString {
-	char *start;
-	char *end;
-	int length;
-} SimpleString;
-
-static SimpleString *ss_trim(SimpleString *self) {
-	while (isspace((unsigned char) *self->start) && self->start < self->end)
-		self->start++;
-	if (self->start == self->end) return self;
-	self->end--;
-	while (((unsigned char) *self->end == '\r') && self->end >= self->start)
-		self->end--;
-	self->end++;
-	self->length = self->end - self->start;
-	return self;
-}
-
-static int ss_indexOf(SimpleString *self, char needle) {
-	char *c = self->start;
-	while (c < self->end) {
-		if (*c == needle) return c - self->start;
-		c++;
-	}
-	return -1;
-}
-
-static int ss_indexOf2(SimpleString *self, char needle, int at) {
-	char *c = self->start + at;
-	while (c < self->end) {
-		if (*c == needle) return c - self->start;
-		c++;
-	}
-	return -1;
-}
-
-static SimpleString ss_substr(SimpleString *self, int s, int e) {
-	SimpleString result;
-	e = s + e;
-	result.start = self->start + s;
-	result.end = self->start + e;
-	result.length = e - s;
-	return result;
-}
-
-static SimpleString ss_substr2(SimpleString *self, int s) {
-	SimpleString result;
-	result.start = self->start + s;
-	result.end = self->end;
-	result.length = result.end - result.start;
-	return result;
-}
-
-static int /*boolean*/ ss_equals(SimpleString *self, const char *str) {
-	int i;
-	int otherLen = strlen(str);
-	if (self->length != otherLen) return 0;
-	for (i = 0; i < self->length; i++) {
-		if (self->start[i] != str[i]) return 0;
-	}
-	return -1;
-}
-
-static char *ss_copy(SimpleString *self) {
-	char *string = CALLOC(char, self->length + 1);
-	memcpy(string, self->start, self->length);
-	string[self->length] = '\0';
-	return string;
-}
-
-static int ss_toInt(SimpleString *self) {
-	return (int) strtol(self->start, &self->end, 10);
-}
-
-typedef struct AtlasInput {
-	const char *start;
-	const char *end;
-	char *index;
-	int length;
-	SimpleString line;
-} AtlasInput;
-
-static SimpleString *ai_readLine(AtlasInput *self) {
-	if (self->index >= self->end) return 0;
-	self->line.start = self->index;
-	while (self->index < self->end && *self->index != '\n')
-		self->index++;
-	self->line.end = self->index;
-	if (self->index != self->end) self->index++;
-	self->line = *ss_trim(&self->line);
-	self->line.length = self->end - self->start;
-	return &self->line;
-}
-
-static int ai_readEntry(SimpleString entry[5], SimpleString *line) {
-	int colon, i, lastMatch;
-	SimpleString substr;
-	if (line == NULL) return 0;
-	ss_trim(line);
-	if (line->length == 0) return 0;
-
-	colon = ss_indexOf(line, ':');
-	if (colon == -1) return 0;
-	substr = ss_substr(line, 0, colon);
-	entry[0] = *ss_trim(&substr);
-	for (i = 1, lastMatch = colon + 1;; i++) {
-		int comma = ss_indexOf2(line, ',', lastMatch);
-		if (comma == -1) {
-			substr = ss_substr2(line, lastMatch);
-			entry[i] = *ss_trim(&substr);
-			return i;
-		}
-		substr = ss_substr(line, lastMatch, comma - lastMatch);
-		entry[i] = *ss_trim(&substr);
-		lastMatch = comma + 1;
-		if (i == 4) return 4;
-	}
-}
-
-static const char *formatNames[] = {"", "Alpha", "Intensity", "LuminanceAlpha", "RGB565", "RGBA4444", "RGB888",
-									"RGBA8888"};
-static const char *textureFilterNames[] = {"", "Nearest", "Linear", "MipMap", "MipMapNearestNearest",
-										   "MipMapLinearNearest",
-										   "MipMapNearestLinear", "MipMapLinearLinear"};
-
-int indexOf(const char **array, int count, SimpleString *str) {
-	int i;
-	for (i = 0; i < count; i++)
-		if (ss_equals(str, array[i])) return i;
-	return 0;
-}
-
-spAtlas *spAtlas_create(const char *begin, int length, const char *dir, void *rendererObject) {
-	spAtlas *self;
-	AtlasInput reader;
-	SimpleString *line;
-	SimpleString entry[5];
-	spAtlasPage *page = NULL;
-	spAtlasPage *lastPage = NULL;
-	spAtlasRegion *lastRegion = NULL;
-
-	int count;
-	int dirLength = (int) strlen(dir);
-	int needsSlash = dirLength > 0 && dir[dirLength - 1] != '/' && dir[dirLength - 1] != '\\';
-
-	self = NEW(spAtlas);
-	self->rendererObject = rendererObject;
-
-	reader.start = begin;
-	reader.end = begin + length;
-	reader.index = (char *) begin;
-	reader.length = length;
-
-	line = ai_readLine(&reader);
-	while (line != NULL && line->length == 0)
-		line = ai_readLine(&reader);
-
-	while (-1) {
-		if (line == NULL || line->length == 0) break;
-		if (ai_readEntry(entry, line) == 0) break;
-		line = ai_readLine(&reader);
-	}
-
-	while (-1) {
-		if (line == NULL) break;
-		if (ss_trim(line)->length == 0) {
-			page = NULL;
-			line = ai_readLine(&reader);
-		} else if (page == NULL) {
-			char *name = ss_copy(line);
-			char *path = CALLOC(char, dirLength + needsSlash + strlen(name) + 1);
-			memcpy(path, dir, dirLength);
-			if (needsSlash) path[dirLength] = '/';
-			strcpy(path + dirLength + needsSlash, name);
-			page = spAtlasPage_create(self, name);
-			FREE(name);
-
-			if (lastPage)
-				lastPage->next = page;
-			else
-				self->pages = page;
-			lastPage = page;
-
-			while (-1) {
-				line = ai_readLine(&reader);
-				if (ai_readEntry(entry, line) == 0) break;
-				if (ss_equals(&entry[0], "size")) {
-					page->width = ss_toInt(&entry[1]);
-					page->height = ss_toInt(&entry[2]);
-				} else if (ss_equals(&entry[0], "format")) {
-					page->format = (spAtlasFormat) indexOf(formatNames, 8, &entry[1]);
-				} else if (ss_equals(&entry[0], "filter")) {
-					page->minFilter = (spAtlasFilter) indexOf(textureFilterNames, 8, &entry[1]);
-					page->magFilter = (spAtlasFilter) indexOf(textureFilterNames, 8, &entry[2]);
-				} else if (ss_equals(&entry[0], "repeat")) {
-					page->uWrap = SP_ATLAS_CLAMPTOEDGE;
-					page->vWrap = SP_ATLAS_CLAMPTOEDGE;
-					if (ss_indexOf(&entry[1], 'x') != -1) page->uWrap = SP_ATLAS_REPEAT;
-					if (ss_indexOf(&entry[1], 'y') != -1) page->vWrap = SP_ATLAS_REPEAT;
-				} else if (ss_equals(&entry[0], "pma")) {
-					page->pma = ss_equals(&entry[1], "true");
-				}
-			}
-
-			_spAtlasPage_createTexture(page, path);
-			FREE(path);
-		} else {
-			spAtlasRegion *region = spAtlasRegion_create();
-			if (lastRegion)
-				lastRegion->next = region;
-			else
-				self->regions = region;
-			lastRegion = region;
-			region->page = page;
-			region->name = ss_copy(line);
-			while (-1) {
-				line = ai_readLine(&reader);
-				count = ai_readEntry(entry, line);
-				if (count == 0) break;
-				if (ss_equals(&entry[0], "xy")) {
-					region->x = ss_toInt(&entry[1]);
-					region->y = ss_toInt(&entry[2]);
-				} else if (ss_equals(&entry[0], "size")) {
-					region->width = ss_toInt(&entry[1]);
-					region->height = ss_toInt(&entry[2]);
-				} else if (ss_equals(&entry[0], "bounds")) {
-					region->x = ss_toInt(&entry[1]);
-					region->y = ss_toInt(&entry[2]);
-					region->width = ss_toInt(&entry[3]);
-					region->height = ss_toInt(&entry[4]);
-				} else if (ss_equals(&entry[0], "offset")) {
-					region->offsetX = ss_toInt(&entry[1]);
-					region->offsetY = ss_toInt(&entry[2]);
-				} else if (ss_equals(&entry[0], "orig")) {
-					region->originalWidth = ss_toInt(&entry[1]);
-					region->originalHeight = ss_toInt(&entry[2]);
-				} else if (ss_equals(&entry[0], "offsets")) {
-					region->offsetX = ss_toInt(&entry[1]);
-					region->offsetY = ss_toInt(&entry[2]);
-					region->originalWidth = ss_toInt(&entry[3]);
-					region->originalHeight = ss_toInt(&entry[4]);
-				} else if (ss_equals(&entry[0], "rotate")) {
-					if (ss_equals(&entry[1], "true")) {
-						region->degrees = 90;
-					} else if (!ss_equals(&entry[1], "false")) {
-						region->degrees = ss_toInt(&entry[1]);
-					}
-				} else if (ss_equals(&entry[0], "index")) {
-					region->index = ss_toInt(&entry[1]);
-				} else {
-					int i = 0;
-					spKeyValue keyValue;
-					keyValue.name = ss_copy(&entry[0]);
-					for (i = 0; i < count; i++) {
-						keyValue.values[i] = ss_toInt(&entry[i + 1]);
-					}
-					spKeyValueArray_add(region->keyValues, keyValue);
-				}
-			}
-			if (region->originalWidth == 0 && region->originalHeight == 0) {
-				region->originalWidth = region->width;
-				region->originalHeight = region->height;
-			}
-
-			region->u = (float) region->x / page->width;
-			region->v = (float) region->y / page->height;
-			if (region->degrees == 90) {
-				region->u2 = (float) (region->x + region->height) / page->width;
-				region->v2 = (float) (region->y + region->width) / page->height;
-			} else {
-				region->u2 = (float) (region->x + region->width) / page->width;
-				region->v2 = (float) (region->y + region->height) / page->height;
-			}
-		}
-	}
-
-	return self;
-}
-
-spAtlas *spAtlas_createFromFile(const char *path, void *rendererObject) {
-	int dirLength;
-	char *dir;
-	int length;
-	const char *data;
-
-	spAtlas *atlas = 0;
-
-	/* Get directory from atlas path. */
-	const char *lastForwardSlash = strrchr(path, '/');
-	const char *lastBackwardSlash = strrchr(path, '\\');
-	const char *lastSlash = lastForwardSlash > lastBackwardSlash ? lastForwardSlash : lastBackwardSlash;
-	if (lastSlash == path) lastSlash++; /* Never drop starting slash. */
-	dirLength = (int) (lastSlash ? lastSlash - path : 0);
-	dir = MALLOC(char, dirLength + 1);
-	memcpy(dir, path, dirLength);
-	dir[dirLength] = '\0';
-
-	data = _spUtil_readFile(path, &length);
-	if (data) atlas = spAtlas_create(data, length, dir, rendererObject);
-
-	FREE(data);
-	FREE(dir);
-	return atlas;
-}
-
-void spAtlas_dispose(spAtlas *self) {
-	spAtlasRegion *region, *nextRegion;
-	spAtlasPage *page = self->pages;
-	while (page) {
-		spAtlasPage *nextPage = page->next;
-		spAtlasPage_dispose(page);
-		page = nextPage;
-	}
-
-	region = self->regions;
-	while (region) {
-		nextRegion = region->next;
-		spAtlasRegion_dispose(region);
-		region = nextRegion;
-	}
-
-	FREE(self);
-}
-
-spAtlasRegion *spAtlas_findRegion(const spAtlas *self, const char *name) {
-	spAtlasRegion *region = self->regions;
-	while (region) {
-		if (strcmp(region->name, name) == 0) return region;
-		region = region->next;
-	}
-	return 0;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <ctype.h>
+#include <spine/Atlas.h>
+#include <spine/extension.h>
+
+spKeyValueArray *spKeyValueArray_create(int initialCapacity) {
+	spKeyValueArray *array = ((spKeyValueArray *) _spCalloc(1, sizeof(spKeyValueArray), "_file_name_", 39));
+	array->size = 0;
+	array->capacity = initialCapacity;
+	array->items = ((spKeyValue *) _spCalloc(initialCapacity, sizeof(spKeyValue), "_file_name_", 39));
+	return array;
+}
+
+void spKeyValueArray_dispose(spKeyValueArray *self) {
+	_spFree((void *) self->items);
+	_spFree((void *) self);
+}
+
+void spKeyValueArray_clear(spKeyValueArray *self) { self->size = 0; }
+
+spKeyValueArray *spKeyValueArray_setSize(spKeyValueArray *self, int newSize) {
+	self->size = newSize;
+	if (self->capacity < newSize) {
+		self->capacity = ((8) > ((int) (self->size * 1.75f)) ? (8) : ((int) (self->size * 1.75f)));
+		self->items = ((spKeyValue *) _spRealloc(self->items, sizeof(spKeyValue) * (self->capacity)));
+	}
+	return self;
+}
+
+void spKeyValueArray_ensureCapacity(spKeyValueArray *self, int newCapacity) {
+	if (self->capacity >= newCapacity) return;
+	self->capacity = newCapacity;
+	self->items = ((spKeyValue *) _spRealloc(self->items, sizeof(spKeyValue) * (self->capacity)));
+}
+
+void spKeyValueArray_add(spKeyValueArray *self, spKeyValue value) {
+	if (self->size == self->capacity) {
+		self->capacity = ((8) > ((int) (self->size * 1.75f)) ? (8) : ((int) (self->size * 1.75f)));
+		self->items = ((spKeyValue *) _spRealloc(self->items, sizeof(spKeyValue) * (self->capacity)));
+	}
+	self->items[self->size++] = value;
+}
+
+void spKeyValueArray_addAll(spKeyValueArray *self, spKeyValueArray *other) {
+	int i = 0;
+	for (; i < other->size; i++) { spKeyValueArray_add(self, other->items[i]); }
+}
+
+void spKeyValueArray_addAllValues(spKeyValueArray *self, spKeyValue *values, int offset, int count) {
+	int i = offset, n = offset + count;
+	for (; i < n; i++) { spKeyValueArray_add(self, values[i]); }
+}
+
+int spKeyValueArray_contains(spKeyValueArray *self, spKeyValue value) {
+	spKeyValue *items = self->items;
+	int i, n;
+	for (i = 0, n = self->size; i < n; i++) {
+		if (!strcmp(items[i].name, value.name)) return -1;
+	}
+	return 0;
+}
+
+spKeyValue spKeyValueArray_pop(spKeyValueArray *self) {
+	spKeyValue item = self->items[--self->size];
+	return item;
+}
+
+spKeyValue spKeyValueArray_peek(spKeyValueArray *self) { return self->items[self->size - 1]; }
+
+spAtlasPage *spAtlasPage_create(spAtlas *atlas, const char *name) {
+	spAtlasPage *self = NEW(spAtlasPage);
+	CONST_CAST(spAtlas *, self->atlas) = atlas;
+	MALLOC_STR(self->name, name);
+	return self;
+}
+
+void spAtlasPage_dispose(spAtlasPage *self) {
+	_spAtlasPage_disposeTexture(self);
+	FREE(self->name);
+	FREE(self);
+}
+
+/**/
+
+spAtlasRegion *spAtlasRegion_create() {
+	spAtlasRegion *region = NEW(spAtlasRegion);
+	region->keyValues = spKeyValueArray_create(2);
+	return region;
+}
+
+void spAtlasRegion_dispose(spAtlasRegion *self) {
+	int i, n;
+	FREE(self->name);
+	FREE(self->splits);
+	FREE(self->pads);
+	for (i = 0, n = self->keyValues->size; i < n; i++) {
+		FREE(self->keyValues->items[i].name);
+	}
+	spKeyValueArray_dispose(self->keyValues);
+	FREE(self);
+}
+
+/**/
+
+typedef struct SimpleString {
+	char *start;
+	char *end;
+	int length;
+} SimpleString;
+
+static SimpleString *ss_trim(SimpleString *self) {
+	while (isspace((unsigned char) *self->start) && self->start < self->end)
+		self->start++;
+	if (self->start == self->end) return self;
+	self->end--;
+	while (((unsigned char) *self->end == '\r') && self->end >= self->start)
+		self->end--;
+	self->end++;
+	self->length = self->end - self->start;
+	return self;
+}
+
+static int ss_indexOf(SimpleString *self, char needle) {
+	char *c = self->start;
+	while (c < self->end) {
+		if (*c == needle) return c - self->start;
+		c++;
+	}
+	return -1;
+}
+
+static int ss_indexOf2(SimpleString *self, char needle, int at) {
+	char *c = self->start + at;
+	while (c < self->end) {
+		if (*c == needle) return c - self->start;
+		c++;
+	}
+	return -1;
+}
+
+static SimpleString ss_substr(SimpleString *self, int s, int e) {
+	SimpleString result;
+	e = s + e;
+	result.start = self->start + s;
+	result.end = self->start + e;
+	result.length = e - s;
+	return result;
+}
+
+static SimpleString ss_substr2(SimpleString *self, int s) {
+	SimpleString result;
+	result.start = self->start + s;
+	result.end = self->end;
+	result.length = result.end - result.start;
+	return result;
+}
+
+static int /*boolean*/ ss_equals(SimpleString *self, const char *str) {
+	int i;
+	int otherLen = strlen(str);
+	if (self->length != otherLen) return 0;
+	for (i = 0; i < self->length; i++) {
+		if (self->start[i] != str[i]) return 0;
+	}
+	return -1;
+}
+
+static char *ss_copy(SimpleString *self) {
+	char *string = CALLOC(char, self->length + 1);
+	memcpy(string, self->start, self->length);
+	string[self->length] = '\0';
+	return string;
+}
+
+static int ss_toInt(SimpleString *self) {
+	return (int) strtol(self->start, &self->end, 10);
+}
+
+typedef struct AtlasInput {
+	const char *start;
+	const char *end;
+	char *index;
+	int length;
+	SimpleString line;
+} AtlasInput;
+
+static SimpleString *ai_readLine(AtlasInput *self) {
+	if (self->index >= self->end) return 0;
+	self->line.start = self->index;
+	while (self->index < self->end && *self->index != '\n')
+		self->index++;
+	self->line.end = self->index;
+	if (self->index != self->end) self->index++;
+	self->line = *ss_trim(&self->line);
+	self->line.length = self->end - self->start;
+	return &self->line;
+}
+
+static int ai_readEntry(SimpleString entry[5], SimpleString *line) {
+	int colon, i, lastMatch;
+	SimpleString substr;
+	if (line == NULL) return 0;
+	ss_trim(line);
+	if (line->length == 0) return 0;
+
+	colon = ss_indexOf(line, ':');
+	if (colon == -1) return 0;
+	substr = ss_substr(line, 0, colon);
+	entry[0] = *ss_trim(&substr);
+	for (i = 1, lastMatch = colon + 1;; i++) {
+		int comma = ss_indexOf2(line, ',', lastMatch);
+		if (comma == -1) {
+			substr = ss_substr2(line, lastMatch);
+			entry[i] = *ss_trim(&substr);
+			return i;
+		}
+		substr = ss_substr(line, lastMatch, comma - lastMatch);
+		entry[i] = *ss_trim(&substr);
+		lastMatch = comma + 1;
+		if (i == 4) return 4;
+	}
+}
+
+static const char *formatNames[] = {"", "Alpha", "Intensity", "LuminanceAlpha", "RGB565", "RGBA4444", "RGB888",
+									"RGBA8888"};
+static const char *textureFilterNames[] = {"", "Nearest", "Linear", "MipMap", "MipMapNearestNearest",
+										   "MipMapLinearNearest",
+										   "MipMapNearestLinear", "MipMapLinearLinear"};
+
+int indexOf(const char **array, int count, SimpleString *str) {
+	int i;
+	for (i = 0; i < count; i++)
+		if (ss_equals(str, array[i])) return i;
+	return 0;
+}
+
+spAtlas *spAtlas_create(const char *begin, int length, const char *dir, void *rendererObject) {
+	spAtlas *self;
+	AtlasInput reader;
+	SimpleString *line;
+	SimpleString entry[5];
+	spAtlasPage *page = NULL;
+	spAtlasPage *lastPage = NULL;
+	spAtlasRegion *lastRegion = NULL;
+
+	int count;
+	int dirLength = (int) strlen(dir);
+	int needsSlash = dirLength > 0 && dir[dirLength - 1] != '/' && dir[dirLength - 1] != '\\';
+
+	self = NEW(spAtlas);
+	self->rendererObject = rendererObject;
+
+	reader.start = begin;
+	reader.end = begin + length;
+	reader.index = (char *) begin;
+	reader.length = length;
+
+	line = ai_readLine(&reader);
+	while (line != NULL && line->length == 0)
+		line = ai_readLine(&reader);
+
+	while (-1) {
+		if (line == NULL || line->length == 0) break;
+		if (ai_readEntry(entry, line) == 0) break;
+		line = ai_readLine(&reader);
+	}
+
+	while (-1) {
+		if (line == NULL) break;
+		if (ss_trim(line)->length == 0) {
+			page = NULL;
+			line = ai_readLine(&reader);
+		} else if (page == NULL) {
+			char *name = ss_copy(line);
+			char *path = CALLOC(char, dirLength + needsSlash + strlen(name) + 1);
+			memcpy(path, dir, dirLength);
+			if (needsSlash) path[dirLength] = '/';
+			strcpy(path + dirLength + needsSlash, name);
+			page = spAtlasPage_create(self, name);
+			FREE(name);
+
+			if (lastPage)
+				lastPage->next = page;
+			else
+				self->pages = page;
+			lastPage = page;
+
+			while (-1) {
+				line = ai_readLine(&reader);
+				if (ai_readEntry(entry, line) == 0) break;
+				if (ss_equals(&entry[0], "size")) {
+					page->width = ss_toInt(&entry[1]);
+					page->height = ss_toInt(&entry[2]);
+				} else if (ss_equals(&entry[0], "format")) {
+					page->format = (spAtlasFormat) indexOf(formatNames, 8, &entry[1]);
+				} else if (ss_equals(&entry[0], "filter")) {
+					page->minFilter = (spAtlasFilter) indexOf(textureFilterNames, 8, &entry[1]);
+					page->magFilter = (spAtlasFilter) indexOf(textureFilterNames, 8, &entry[2]);
+				} else if (ss_equals(&entry[0], "repeat")) {
+					page->uWrap = SP_ATLAS_CLAMPTOEDGE;
+					page->vWrap = SP_ATLAS_CLAMPTOEDGE;
+					if (ss_indexOf(&entry[1], 'x') != -1) page->uWrap = SP_ATLAS_REPEAT;
+					if (ss_indexOf(&entry[1], 'y') != -1) page->vWrap = SP_ATLAS_REPEAT;
+				} else if (ss_equals(&entry[0], "pma")) {
+					page->pma = ss_equals(&entry[1], "true");
+				}
+			}
+
+			_spAtlasPage_createTexture(page, path);
+			FREE(path);
+		} else {
+			spAtlasRegion *region = spAtlasRegion_create();
+			if (lastRegion)
+				lastRegion->next = region;
+			else
+				self->regions = region;
+			lastRegion = region;
+			region->page = page;
+			region->name = ss_copy(line);
+			while (-1) {
+				line = ai_readLine(&reader);
+				count = ai_readEntry(entry, line);
+				if (count == 0) break;
+				if (ss_equals(&entry[0], "xy")) {
+					region->x = ss_toInt(&entry[1]);
+					region->y = ss_toInt(&entry[2]);
+				} else if (ss_equals(&entry[0], "size")) {
+					region->width = ss_toInt(&entry[1]);
+					region->height = ss_toInt(&entry[2]);
+				} else if (ss_equals(&entry[0], "bounds")) {
+					region->x = ss_toInt(&entry[1]);
+					region->y = ss_toInt(&entry[2]);
+					region->width = ss_toInt(&entry[3]);
+					region->height = ss_toInt(&entry[4]);
+				} else if (ss_equals(&entry[0], "offset")) {
+					region->offsetX = ss_toInt(&entry[1]);
+					region->offsetY = ss_toInt(&entry[2]);
+				} else if (ss_equals(&entry[0], "orig")) {
+					region->originalWidth = ss_toInt(&entry[1]);
+					region->originalHeight = ss_toInt(&entry[2]);
+				} else if (ss_equals(&entry[0], "offsets")) {
+					region->offsetX = ss_toInt(&entry[1]);
+					region->offsetY = ss_toInt(&entry[2]);
+					region->originalWidth = ss_toInt(&entry[3]);
+					region->originalHeight = ss_toInt(&entry[4]);
+				} else if (ss_equals(&entry[0], "rotate")) {
+					if (ss_equals(&entry[1], "true")) {
+						region->degrees = 90;
+					} else if (!ss_equals(&entry[1], "false")) {
+						region->degrees = ss_toInt(&entry[1]);
+					}
+				} else if (ss_equals(&entry[0], "index")) {
+					region->index = ss_toInt(&entry[1]);
+				} else {
+					int i = 0;
+					spKeyValue keyValue;
+					keyValue.name = ss_copy(&entry[0]);
+					for (i = 0; i < count; i++) {
+						keyValue.values[i] = ss_toInt(&entry[i + 1]);
+					}
+					spKeyValueArray_add(region->keyValues, keyValue);
+				}
+			}
+			if (region->originalWidth == 0 && region->originalHeight == 0) {
+				region->originalWidth = region->width;
+				region->originalHeight = region->height;
+			}
+
+			region->u = (float) region->x / page->width;
+			region->v = (float) region->y / page->height;
+			if (region->degrees == 90) {
+				region->u2 = (float) (region->x + region->height) / page->width;
+				region->v2 = (float) (region->y + region->width) / page->height;
+			} else {
+				region->u2 = (float) (region->x + region->width) / page->width;
+				region->v2 = (float) (region->y + region->height) / page->height;
+			}
+		}
+	}
+
+	return self;
+}
+
+spAtlas *spAtlas_createFromFile(const char *path, void *rendererObject) {
+	int dirLength;
+	char *dir;
+	int length;
+	const char *data;
+
+	spAtlas *atlas = 0;
+
+	/* Get directory from atlas path. */
+	const char *lastForwardSlash = strrchr(path, '/');
+	const char *lastBackwardSlash = strrchr(path, '\\');
+	const char *lastSlash = lastForwardSlash > lastBackwardSlash ? lastForwardSlash : lastBackwardSlash;
+	if (lastSlash == path) lastSlash++; /* Never drop starting slash. */
+	dirLength = (int) (lastSlash ? lastSlash - path : 0);
+	dir = MALLOC(char, dirLength + 1);
+	memcpy(dir, path, dirLength);
+	dir[dirLength] = '\0';
+
+	data = _spUtil_readFile(path, &length);
+	if (data) atlas = spAtlas_create(data, length, dir, rendererObject);
+
+	FREE(data);
+	FREE(dir);
+	return atlas;
+}
+
+void spAtlas_dispose(spAtlas *self) {
+	spAtlasRegion *region, *nextRegion;
+	spAtlasPage *page = self->pages;
+	while (page) {
+		spAtlasPage *nextPage = page->next;
+		spAtlasPage_dispose(page);
+		page = nextPage;
+	}
+
+	region = self->regions;
+	while (region) {
+		nextRegion = region->next;
+		spAtlasRegion_dispose(region);
+		region = nextRegion;
+	}
+
+	FREE(self);
+}
+
+spAtlasRegion *spAtlas_findRegion(const spAtlas *self, const char *name) {
+	spAtlasRegion *region = self->regions;
+	while (region) {
+		if (strcmp(region->name, name) == 0) return region;
+		region = region->next;
+	}
+	return 0;
+}

+ 99 - 99
spine-c/spine-c/src/spine/AtlasAttachmentLoader.c

@@ -1,99 +1,99 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/AtlasAttachmentLoader.h>
-#include <spine/extension.h>
-
-spAttachment *_spAtlasAttachmentLoader_createAttachment(spAttachmentLoader *loader, spSkin *skin, spAttachmentType type,
-														const char *name, const char *path) {
-	spAtlasAttachmentLoader *self = SUB_CAST(spAtlasAttachmentLoader, loader);
-	switch (type) {
-		case SP_ATTACHMENT_REGION: {
-			spRegionAttachment *attachment;
-			spAtlasRegion *region = spAtlas_findRegion(self->atlas, path);
-			if (!region) {
-				_spAttachmentLoader_setError(loader, "Region not found: ", path);
-				return 0;
-			}
-			attachment = spRegionAttachment_create(name);
-			attachment->rendererObject = region;
-			spRegionAttachment_setUVs(attachment, region->u, region->v, region->u2, region->v2, region->degrees);
-			attachment->regionOffsetX = region->offsetX;
-			attachment->regionOffsetY = region->offsetY;
-			attachment->regionWidth = region->width;
-			attachment->regionHeight = region->height;
-			attachment->regionOriginalWidth = region->originalWidth;
-			attachment->regionOriginalHeight = region->originalHeight;
-			return SUPER(attachment);
-		}
-		case SP_ATTACHMENT_MESH:
-		case SP_ATTACHMENT_LINKED_MESH: {
-			spMeshAttachment *attachment;
-			spAtlasRegion *region = spAtlas_findRegion(self->atlas, path);
-			if (!region) {
-				_spAttachmentLoader_setError(loader, "Region not found: ", path);
-				return 0;
-			}
-			attachment = spMeshAttachment_create(name);
-			attachment->rendererObject = region;
-			attachment->regionU = region->u;
-			attachment->regionV = region->v;
-			attachment->regionU2 = region->u2;
-			attachment->regionV2 = region->v2;
-			attachment->regionDegrees = region->degrees;
-			attachment->regionOffsetX = region->offsetX;
-			attachment->regionOffsetY = region->offsetY;
-			attachment->regionWidth = region->width;
-			attachment->regionHeight = region->height;
-			attachment->regionOriginalWidth = region->originalWidth;
-			attachment->regionOriginalHeight = region->originalHeight;
-			return SUPER(SUPER(attachment));
-		}
-		case SP_ATTACHMENT_BOUNDING_BOX:
-			return SUPER(SUPER(spBoundingBoxAttachment_create(name)));
-		case SP_ATTACHMENT_PATH:
-			return SUPER(SUPER(spPathAttachment_create(name)));
-		case SP_ATTACHMENT_POINT:
-			return SUPER(spPointAttachment_create(name));
-		case SP_ATTACHMENT_CLIPPING:
-			return SUPER(SUPER(spClippingAttachment_create(name)));
-		default:
-			_spAttachmentLoader_setUnknownTypeError(loader, type);
-			return 0;
-	}
-
-	UNUSED(skin);
-}
-
-spAtlasAttachmentLoader *spAtlasAttachmentLoader_create(spAtlas *atlas) {
-	spAtlasAttachmentLoader *self = NEW(spAtlasAttachmentLoader);
-	_spAttachmentLoader_init(SUPER(self), _spAttachmentLoader_deinit, _spAtlasAttachmentLoader_createAttachment, 0, 0);
-	self->atlas = atlas;
-	return self;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/AtlasAttachmentLoader.h>
+#include <spine/extension.h>
+
+spAttachment *_spAtlasAttachmentLoader_createAttachment(spAttachmentLoader *loader, spSkin *skin, spAttachmentType type,
+														const char *name, const char *path) {
+	spAtlasAttachmentLoader *self = SUB_CAST(spAtlasAttachmentLoader, loader);
+	switch (type) {
+		case SP_ATTACHMENT_REGION: {
+			spRegionAttachment *attachment;
+			spAtlasRegion *region = spAtlas_findRegion(self->atlas, path);
+			if (!region) {
+				_spAttachmentLoader_setError(loader, "Region not found: ", path);
+				return 0;
+			}
+			attachment = spRegionAttachment_create(name);
+			attachment->rendererObject = region;
+			spRegionAttachment_setUVs(attachment, region->u, region->v, region->u2, region->v2, region->degrees);
+			attachment->regionOffsetX = region->offsetX;
+			attachment->regionOffsetY = region->offsetY;
+			attachment->regionWidth = region->width;
+			attachment->regionHeight = region->height;
+			attachment->regionOriginalWidth = region->originalWidth;
+			attachment->regionOriginalHeight = region->originalHeight;
+			return SUPER(attachment);
+		}
+		case SP_ATTACHMENT_MESH:
+		case SP_ATTACHMENT_LINKED_MESH: {
+			spMeshAttachment *attachment;
+			spAtlasRegion *region = spAtlas_findRegion(self->atlas, path);
+			if (!region) {
+				_spAttachmentLoader_setError(loader, "Region not found: ", path);
+				return 0;
+			}
+			attachment = spMeshAttachment_create(name);
+			attachment->rendererObject = region;
+			attachment->regionU = region->u;
+			attachment->regionV = region->v;
+			attachment->regionU2 = region->u2;
+			attachment->regionV2 = region->v2;
+			attachment->regionDegrees = region->degrees;
+			attachment->regionOffsetX = region->offsetX;
+			attachment->regionOffsetY = region->offsetY;
+			attachment->regionWidth = region->width;
+			attachment->regionHeight = region->height;
+			attachment->regionOriginalWidth = region->originalWidth;
+			attachment->regionOriginalHeight = region->originalHeight;
+			return SUPER(SUPER(attachment));
+		}
+		case SP_ATTACHMENT_BOUNDING_BOX:
+			return SUPER(SUPER(spBoundingBoxAttachment_create(name)));
+		case SP_ATTACHMENT_PATH:
+			return SUPER(SUPER(spPathAttachment_create(name)));
+		case SP_ATTACHMENT_POINT:
+			return SUPER(spPointAttachment_create(name));
+		case SP_ATTACHMENT_CLIPPING:
+			return SUPER(SUPER(spClippingAttachment_create(name)));
+		default:
+			_spAttachmentLoader_setUnknownTypeError(loader, type);
+			return 0;
+	}
+
+	UNUSED(skin);
+}
+
+spAtlasAttachmentLoader *spAtlasAttachmentLoader_create(spAtlas *atlas) {
+	spAtlasAttachmentLoader *self = NEW(spAtlasAttachmentLoader);
+	_spAttachmentLoader_init(SUPER(self), _spAttachmentLoader_deinit, _spAtlasAttachmentLoader_createAttachment, 0, 0);
+	self->atlas = atlas;
+	return self;
+}

+ 65 - 65
spine-c/spine-c/src/spine/Attachment.c

@@ -1,65 +1,65 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/Attachment.h>
-#include <spine/extension.h>
-#include <spine/Slot.h>
-
-typedef struct _spAttachmentVtable {
-	void (*dispose)(spAttachment *self);
-
-	spAttachment *(*copy)(spAttachment *self);
-} _spAttachmentVtable;
-
-void _spAttachment_init(spAttachment *self, const char *name, spAttachmentType type, /**/
-						void (*dispose)(spAttachment *self), spAttachment *(*copy)(spAttachment *self)) {
-
-	CONST_CAST(_spAttachmentVtable*, self->vtable) = NEW(_spAttachmentVtable);
-	VTABLE(spAttachment, self)->dispose = dispose;
-	VTABLE(spAttachment, self)->copy = copy;
-
-	MALLOC_STR(self->name, name);
-	CONST_CAST(spAttachmentType, self->type) = type;
-}
-
-void _spAttachment_deinit(spAttachment *self) {
-	if (self->attachmentLoader) spAttachmentLoader_disposeAttachment(self->attachmentLoader, self);
-	FREE(self->vtable);
-	FREE(self->name);
-}
-
-spAttachment *spAttachment_copy(spAttachment *self) {
-	return VTABLE(spAttachment, self)->copy(self);
-}
-
-void spAttachment_dispose(spAttachment *self) {
-	self->refCount--;
-	if (self->refCount <= 0)
-		VTABLE(spAttachment, self)->dispose(self);
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/Attachment.h>
+#include <spine/Slot.h>
+#include <spine/extension.h>
+
+typedef struct _spAttachmentVtable {
+	void (*dispose)(spAttachment *self);
+
+	spAttachment *(*copy)(spAttachment *self);
+} _spAttachmentVtable;
+
+void _spAttachment_init(spAttachment *self, const char *name, spAttachmentType type, /**/
+						void (*dispose)(spAttachment *self), spAttachment *(*copy)(spAttachment *self)) {
+
+	CONST_CAST(_spAttachmentVtable *, self->vtable) = NEW(_spAttachmentVtable);
+	VTABLE(spAttachment, self)->dispose = dispose;
+	VTABLE(spAttachment, self)->copy = copy;
+
+	MALLOC_STR(self->name, name);
+	CONST_CAST(spAttachmentType, self->type) = type;
+}
+
+void _spAttachment_deinit(spAttachment *self) {
+	if (self->attachmentLoader) spAttachmentLoader_disposeAttachment(self->attachmentLoader, self);
+	FREE(self->vtable);
+	FREE(self->name);
+}
+
+spAttachment *spAttachment_copy(spAttachment *self) {
+	return VTABLE(spAttachment, self)->copy(self);
+}
+
+void spAttachment_dispose(spAttachment *self) {
+	self->refCount--;
+	if (self->refCount <= 0)
+		VTABLE(spAttachment, self)->dispose(self);
+}

+ 101 - 102
spine-c/spine-c/src/spine/AttachmentLoader.c

@@ -1,102 +1,101 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/AttachmentLoader.h>
-#include <stdio.h>
-#include <spine/extension.h>
-
-typedef struct _spAttachmentLoaderVtable {
-	spAttachment *(*createAttachment)(spAttachmentLoader *self, spSkin *skin, spAttachmentType type, const char *name,
-									  const char *path);
-
-	void (*configureAttachment)(spAttachmentLoader *self, spAttachment *);
-
-	void (*disposeAttachment)(spAttachmentLoader *self, spAttachment *);
-
-	void (*dispose)(spAttachmentLoader *self);
-} _spAttachmentLoaderVtable;
-
-void _spAttachmentLoader_init(spAttachmentLoader *self,
-							  void (*dispose)(spAttachmentLoader *self),
-							  spAttachment *(*createAttachment)(spAttachmentLoader *self, spSkin *skin,
-																spAttachmentType type, const char *name,
-																const char *path),
-							  void (*configureAttachment)(spAttachmentLoader *self, spAttachment *),
-							  void (*disposeAttachment)(spAttachmentLoader *self, spAttachment *)
-) {
-	CONST_CAST(_spAttachmentLoaderVtable*, self->vtable) = NEW(_spAttachmentLoaderVtable);
-	VTABLE(spAttachmentLoader, self)->dispose = dispose;
-	VTABLE(spAttachmentLoader, self)->createAttachment = createAttachment;
-	VTABLE(spAttachmentLoader, self)->configureAttachment = configureAttachment;
-	VTABLE(spAttachmentLoader, self)->disposeAttachment = disposeAttachment;
-}
-
-void _spAttachmentLoader_deinit(spAttachmentLoader *self) {
-	FREE(self->vtable);
-	FREE(self->error1);
-	FREE(self->error2);
-}
-
-void spAttachmentLoader_dispose(spAttachmentLoader *self) {
-	VTABLE(spAttachmentLoader, self)->dispose(self);
-	FREE(self);
-}
-
-spAttachment *
-spAttachmentLoader_createAttachment(spAttachmentLoader *self, spSkin *skin, spAttachmentType type, const char *name,
-									const char *path) {
-	FREE(self->error1);
-	FREE(self->error2);
-	self->error1 = 0;
-	self->error2 = 0;
-	return VTABLE(spAttachmentLoader, self)->createAttachment(self, skin, type, name, path);
-}
-
-void spAttachmentLoader_configureAttachment(spAttachmentLoader *self, spAttachment *attachment) {
-	if (!VTABLE(spAttachmentLoader, self)->configureAttachment) return;
-	VTABLE(spAttachmentLoader, self)->configureAttachment(self, attachment);
-}
-
-void spAttachmentLoader_disposeAttachment(spAttachmentLoader *self, spAttachment *attachment) {
-	if (!VTABLE(spAttachmentLoader, self)->disposeAttachment) return;
-	VTABLE(spAttachmentLoader, self)->disposeAttachment(self, attachment);
-}
-
-void _spAttachmentLoader_setError(spAttachmentLoader *self, const char *error1, const char *error2) {
-	FREE(self->error1);
-	FREE(self->error2);
-	MALLOC_STR(self->error1, error1);
-	MALLOC_STR(self->error2, error2);
-}
-
-void _spAttachmentLoader_setUnknownTypeError(spAttachmentLoader *self, spAttachmentType type) {
-	char buffer[16];
-	sprintf(buffer, "%d", type);
-	_spAttachmentLoader_setError(self, "Unknown attachment type: ", buffer);
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/AttachmentLoader.h>
+#include <spine/extension.h>
+#include <stdio.h>
+
+typedef struct _spAttachmentLoaderVtable {
+	spAttachment *(*createAttachment)(spAttachmentLoader *self, spSkin *skin, spAttachmentType type, const char *name,
+									  const char *path);
+
+	void (*configureAttachment)(spAttachmentLoader *self, spAttachment *);
+
+	void (*disposeAttachment)(spAttachmentLoader *self, spAttachment *);
+
+	void (*dispose)(spAttachmentLoader *self);
+} _spAttachmentLoaderVtable;
+
+void _spAttachmentLoader_init(spAttachmentLoader *self,
+							  void (*dispose)(spAttachmentLoader *self),
+							  spAttachment *(*createAttachment)(spAttachmentLoader *self, spSkin *skin,
+																spAttachmentType type, const char *name,
+																const char *path),
+							  void (*configureAttachment)(spAttachmentLoader *self, spAttachment *),
+							  void (*disposeAttachment)(spAttachmentLoader *self, spAttachment *)) {
+	CONST_CAST(_spAttachmentLoaderVtable *, self->vtable) = NEW(_spAttachmentLoaderVtable);
+	VTABLE(spAttachmentLoader, self)->dispose = dispose;
+	VTABLE(spAttachmentLoader, self)->createAttachment = createAttachment;
+	VTABLE(spAttachmentLoader, self)->configureAttachment = configureAttachment;
+	VTABLE(spAttachmentLoader, self)->disposeAttachment = disposeAttachment;
+}
+
+void _spAttachmentLoader_deinit(spAttachmentLoader *self) {
+	FREE(self->vtable);
+	FREE(self->error1);
+	FREE(self->error2);
+}
+
+void spAttachmentLoader_dispose(spAttachmentLoader *self) {
+	VTABLE(spAttachmentLoader, self)->dispose(self);
+	FREE(self);
+}
+
+spAttachment *
+spAttachmentLoader_createAttachment(spAttachmentLoader *self, spSkin *skin, spAttachmentType type, const char *name,
+									const char *path) {
+	FREE(self->error1);
+	FREE(self->error2);
+	self->error1 = 0;
+	self->error2 = 0;
+	return VTABLE(spAttachmentLoader, self)->createAttachment(self, skin, type, name, path);
+}
+
+void spAttachmentLoader_configureAttachment(spAttachmentLoader *self, spAttachment *attachment) {
+	if (!VTABLE(spAttachmentLoader, self)->configureAttachment) return;
+	VTABLE(spAttachmentLoader, self)->configureAttachment(self, attachment);
+}
+
+void spAttachmentLoader_disposeAttachment(spAttachmentLoader *self, spAttachment *attachment) {
+	if (!VTABLE(spAttachmentLoader, self)->disposeAttachment) return;
+	VTABLE(spAttachmentLoader, self)->disposeAttachment(self, attachment);
+}
+
+void _spAttachmentLoader_setError(spAttachmentLoader *self, const char *error1, const char *error2) {
+	FREE(self->error1);
+	FREE(self->error2);
+	MALLOC_STR(self->error1, error1);
+	MALLOC_STR(self->error2, error2);
+}
+
+void _spAttachmentLoader_setUnknownTypeError(spAttachmentLoader *self, spAttachmentType type) {
+	char buffer[16];
+	sprintf(buffer, "%d", type);
+	_spAttachmentLoader_setError(self, "Unknown attachment type: ", buffer);
+}

+ 294 - 294
spine-c/spine-c/src/spine/Bone.c

@@ -1,294 +1,294 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/Bone.h>
-#include <spine/extension.h>
-#include <stdio.h>
-
-static int yDown;
-
-void spBone_setYDown(int value) {
-	yDown = value;
-}
-
-int spBone_isYDown() {
-	return yDown;
-}
-
-spBone *spBone_create(spBoneData *data, spSkeleton *skeleton, spBone *parent) {
-	spBone *self = NEW(spBone);
-	CONST_CAST(spBoneData*, self->data) = data;
-	CONST_CAST(spSkeleton*, self->skeleton) = skeleton;
-	CONST_CAST(spBone*, self->parent) = parent;
-	CONST_CAST(float, self->a) = 1.0f;
-	CONST_CAST(float, self->d) = 1.0f;
-	spBone_setToSetupPose(self);
-	return self;
-}
-
-void spBone_dispose(spBone *self) {
-	FREE(self->children);
-	FREE(self);
-}
-
-void spBone_update(spBone *self) {
-	spBone_updateWorldTransformWith(self, self->ax, self->ay, self->arotation, self->ascaleX, self->ascaleY, self->ashearX, self->ashearY);
-}
-
-void spBone_updateWorldTransform(spBone *self) {
-	spBone_updateWorldTransformWith(self, self->x, self->y, self->rotation, self->scaleX, self->scaleY, self->shearX,
-									self->shearY);
-}
-
-void spBone_updateWorldTransformWith(spBone *self, float x, float y, float rotation, float scaleX, float scaleY,
-									 float shearX, float shearY) {
-	float cosine, sine;
-	float pa, pb, pc, pd;
-	spBone *parent = self->parent;
-	float sx = self->skeleton->scaleX;
-	float sy = self->skeleton->scaleY * (spBone_isYDown() ? -1 : 1);
-
-	self->ax = x;
-	self->ay = y;
-	self->arotation = rotation;
-	self->ascaleX = scaleX;
-	self->ascaleY = scaleY;
-	self->ashearX = shearX;
-	self->ashearY = shearY;
-
-	if (!parent) { /* Root bone. */
-		float rotationY = rotation + 90 + shearY;
-		CONST_CAST(float, self->a) = COS_DEG(rotation + shearX) * scaleX * sx;
-		CONST_CAST(float, self->b) = COS_DEG(rotationY) * scaleY * sx;
-		CONST_CAST(float, self->c) = SIN_DEG(rotation + shearX) * scaleX * sy;
-		CONST_CAST(float, self->d) = SIN_DEG(rotationY) * scaleY * sy;
-		CONST_CAST(float, self->worldX) = x * sx + self->skeleton->x;
-		CONST_CAST(float, self->worldY) = y * sy + self->skeleton->y;
-		return;
-	}
-
-	pa = parent->a;
-	pb = parent->b;
-	pc = parent->c;
-	pd = parent->d;
-
-	CONST_CAST(float, self->worldX) = pa * x + pb * y + parent->worldX;
-	CONST_CAST(float, self->worldY) = pc * x + pd * y + parent->worldY;
-
-	switch (self->data->transformMode) {
-		case SP_TRANSFORMMODE_NORMAL: {
-			float rotationY = rotation + 90 + shearY;
-			float la = COS_DEG(rotation + shearX) * scaleX;
-			float lb = COS_DEG(rotationY) * scaleY;
-			float lc = SIN_DEG(rotation + shearX) * scaleX;
-			float ld = SIN_DEG(rotationY) * scaleY;
-			CONST_CAST(float, self->a) = pa * la + pb * lc;
-			CONST_CAST(float, self->b) = pa * lb + pb * ld;
-			CONST_CAST(float, self->c) = pc * la + pd * lc;
-			CONST_CAST(float, self->d) = pc * lb + pd * ld;
-			return;
-		}
-		case SP_TRANSFORMMODE_ONLYTRANSLATION: {
-			float rotationY = rotation + 90 + shearY;
-			CONST_CAST(float, self->a) = COS_DEG(rotation + shearX) * scaleX;
-			CONST_CAST(float, self->b) = COS_DEG(rotationY) * scaleY;
-			CONST_CAST(float, self->c) = SIN_DEG(rotation + shearX) * scaleX;
-			CONST_CAST(float, self->d) = SIN_DEG(rotationY) * scaleY;
-			break;
-		}
-		case SP_TRANSFORMMODE_NOROTATIONORREFLECTION: {
-			float s = pa * pa + pc * pc;
-			float prx, rx, ry, la, lb, lc, ld;
-			if (s > 0.0001f) {
-				s = ABS(pa * pd - pb * pc) / s;
-				pa /= self->skeleton->scaleX;
-				pc /= self->skeleton->scaleY;
-				pb = pc * s;
-				pd = pa * s;
-				prx = ATAN2(pc, pa) * RAD_DEG;
-			} else {
-				pa = 0;
-				pc = 0;
-				prx = 90 - ATAN2(pd, pb) * RAD_DEG;
-			}
-			rx = rotation + shearX - prx;
-			ry = rotation + shearY - prx + 90;
-			la = COS_DEG(rx) * scaleX;
-			lb = COS_DEG(ry) * scaleY;
-			lc = SIN_DEG(rx) * scaleX;
-			ld = SIN_DEG(ry) * scaleY;
-			CONST_CAST(float, self->a) = pa * la - pb * lc;
-			CONST_CAST(float, self->b) = pa * lb - pb * ld;
-			CONST_CAST(float, self->c) = pc * la + pd * lc;
-			CONST_CAST(float, self->d) = pc * lb + pd * ld;
-			break;
-		}
-		case SP_TRANSFORMMODE_NOSCALE:
-		case SP_TRANSFORMMODE_NOSCALEORREFLECTION: {
-			float za, zc, s;
-			float r, zb, zd, la, lb, lc, ld;
-			cosine = COS_DEG(rotation);
-			sine = SIN_DEG(rotation);
-			za = (pa * cosine + pb * sine) / sx;
-			zc = (pc * cosine + pd * sine) / sy;
-			s = SQRT(za * za + zc * zc);
-			if (s > 0.00001f) s = 1 / s;
-			za *= s;
-			zc *= s;
-			s = SQRT(za * za + zc * zc);
-			if (self->data->transformMode == SP_TRANSFORMMODE_NOSCALE && (pa * pd - pb * pc < 0) != (sx < 0 != sy < 0))
-				s = -s;
-			r = PI / 2 + ATAN2(zc, za);
-			zb = COS(r) * s;
-			zd = SIN(r) * s;
-			la = COS_DEG(shearX) * scaleX;
-			lb = COS_DEG(90 + shearY) * scaleY;
-			lc = SIN_DEG(shearX) * scaleX;
-			ld = SIN_DEG(90 + shearY) * scaleY;
-			CONST_CAST(float, self->a) = za * la + zb * lc;
-			CONST_CAST(float, self->b) = za * lb + zb * ld;
-			CONST_CAST(float, self->c) = zc * la + zd * lc;
-			CONST_CAST(float, self->d) = zc * lb + zd * ld;
-			break;
-		}
-	}
-
-	CONST_CAST(float, self->a) *= sx;
-	CONST_CAST(float, self->b) *= sx;
-	CONST_CAST(float, self->c) *= sy;
-	CONST_CAST(float, self->d) *= sy;
-}
-
-void spBone_setToSetupPose(spBone *self) {
-	self->x = self->data->x;
-	self->y = self->data->y;
-	self->rotation = self->data->rotation;
-	self->scaleX = self->data->scaleX;
-	self->scaleY = self->data->scaleY;
-	self->shearX = self->data->shearX;
-	self->shearY = self->data->shearY;
-}
-
-float spBone_getWorldRotationX(spBone *self) {
-	return ATAN2(self->c, self->a) * RAD_DEG;
-}
-
-float spBone_getWorldRotationY(spBone *self) {
-	return ATAN2(self->d, self->b) * RAD_DEG;
-}
-
-float spBone_getWorldScaleX(spBone *self) {
-	return SQRT(self->a * self->a + self->c * self->c);
-}
-
-float spBone_getWorldScaleY(spBone *self) {
-	return SQRT(self->b * self->b + self->d * self->d);
-}
-
-/** Computes the individual applied transform values from the world transform. This can be useful to perform processing using
- * the applied transform after the world transform has been modified directly (eg, by a constraint).
- * <p>
- * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. */
-void spBone_updateAppliedTransform(spBone *self) {
-	spBone *parent = self->parent;
-	if (!parent) {
-		self->ax = self->worldX;
-		self->ay = self->worldY;
-		self->arotation = ATAN2(self->c, self->a) * RAD_DEG;
-		self->ascaleX = SQRT(self->a * self->a + self->c * self->c);
-		self->ascaleY = SQRT(self->b * self->b + self->d * self->d);
-		self->ashearX = 0;
-		self->ashearY = ATAN2(self->a * self->b + self->c * self->d, self->a * self->d - self->b * self->c) * RAD_DEG;
-	} else {
-		float pa = parent->a, pb = parent->b, pc = parent->c, pd = parent->d;
-		float pid = 1 / (pa * pd - pb * pc);
-		float dx = self->worldX - parent->worldX, dy = self->worldY - parent->worldY;
-		float ia = pid * pd;
-		float id = pid * pa;
-		float ib = pid * pb;
-		float ic = pid * pc;
-		float ra = ia * self->a - ib * self->c;
-		float rb = ia * self->b - ib * self->d;
-		float rc = id * self->c - ic * self->a;
-		float rd = id * self->d - ic * self->b;
-		self->ax = (dx * pd * pid - dy * pb * pid);
-		self->ay = (dy * pa * pid - dx * pc * pid);
-		self->ashearX = 0;
-		self->ascaleX = SQRT(ra * ra + rc * rc);
-		if (self->ascaleX > 0.0001f) {
-			float det = ra * rd - rb * rc;
-			self->ascaleY = det / self->ascaleX;
-			self->ashearY = ATAN2(ra * rb + rc * rd, det) * RAD_DEG;
-			self->arotation = ATAN2(rc, ra) * RAD_DEG;
-		} else {
-			self->ascaleX = 0;
-			self->ascaleY = SQRT(rb * rb + rd * rd);
-			self->ashearY = 0;
-			self->arotation = 90 - ATAN2(rd, rb) * RAD_DEG;
-		}
-	}
-}
-
-void spBone_worldToLocal(spBone *self, float worldX, float worldY, float *localX, float *localY) {
-	float invDet = 1 / (self->a * self->d - self->b * self->c);
-	float x = worldX - self->worldX, y = worldY - self->worldY;
-	*localX = (x * self->d * invDet - y * self->b * invDet);
-	*localY = (y * self->a * invDet - x * self->c * invDet);
-}
-
-void spBone_localToWorld(spBone *self, float localX, float localY, float *worldX, float *worldY) {
-	float x = localX, y = localY;
-	*worldX = x * self->a + y * self->b + self->worldX;
-	*worldY = x * self->c + y * self->d + self->worldY;
-}
-
-float spBone_worldToLocalRotation(spBone *self, float worldRotation) {
-	float sine, cosine;
-	sine = SIN_DEG(worldRotation);
-	cosine = COS_DEG(worldRotation);
-	return ATAN2(self->a * sine - self->c * cosine, self->d * cosine - self->b * sine) * RAD_DEG + self->rotation -
-		   self->shearX;
-}
-
-float spBone_localToWorldRotation(spBone *self, float localRotation) {
-	float sine, cosine;
-	localRotation -= self->rotation - self->shearX;
-	sine = SIN_DEG(localRotation);
-	cosine = COS_DEG(localRotation);
-	return ATAN2(cosine * self->c + sine * self->d, cosine * self->a + sine * self->b) * RAD_DEG;
-}
-
-void spBone_rotateWorld(spBone *self, float degrees) {
-	float a = self->a, b = self->b, c = self->c, d = self->d;
-	float cosine = COS_DEG(degrees), sine = SIN_DEG(degrees);
-	CONST_CAST(float, self->a) = cosine * a - sine * c;
-	CONST_CAST(float, self->b) = cosine * b - sine * d;
-	CONST_CAST(float, self->c) = sine * a + cosine * c;
-	CONST_CAST(float, self->d) = sine * b + cosine * d;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/Bone.h>
+#include <spine/extension.h>
+#include <stdio.h>
+
+static int yDown;
+
+void spBone_setYDown(int value) {
+	yDown = value;
+}
+
+int spBone_isYDown() {
+	return yDown;
+}
+
+spBone *spBone_create(spBoneData *data, spSkeleton *skeleton, spBone *parent) {
+	spBone *self = NEW(spBone);
+	CONST_CAST(spBoneData *, self->data) = data;
+	CONST_CAST(spSkeleton *, self->skeleton) = skeleton;
+	CONST_CAST(spBone *, self->parent) = parent;
+	CONST_CAST(float, self->a) = 1.0f;
+	CONST_CAST(float, self->d) = 1.0f;
+	spBone_setToSetupPose(self);
+	return self;
+}
+
+void spBone_dispose(spBone *self) {
+	FREE(self->children);
+	FREE(self);
+}
+
+void spBone_update(spBone *self) {
+	spBone_updateWorldTransformWith(self, self->ax, self->ay, self->arotation, self->ascaleX, self->ascaleY, self->ashearX, self->ashearY);
+}
+
+void spBone_updateWorldTransform(spBone *self) {
+	spBone_updateWorldTransformWith(self, self->x, self->y, self->rotation, self->scaleX, self->scaleY, self->shearX,
+									self->shearY);
+}
+
+void spBone_updateWorldTransformWith(spBone *self, float x, float y, float rotation, float scaleX, float scaleY,
+									 float shearX, float shearY) {
+	float cosine, sine;
+	float pa, pb, pc, pd;
+	spBone *parent = self->parent;
+	float sx = self->skeleton->scaleX;
+	float sy = self->skeleton->scaleY * (spBone_isYDown() ? -1 : 1);
+
+	self->ax = x;
+	self->ay = y;
+	self->arotation = rotation;
+	self->ascaleX = scaleX;
+	self->ascaleY = scaleY;
+	self->ashearX = shearX;
+	self->ashearY = shearY;
+
+	if (!parent) { /* Root bone. */
+		float rotationY = rotation + 90 + shearY;
+		CONST_CAST(float, self->a) = COS_DEG(rotation + shearX) * scaleX * sx;
+		CONST_CAST(float, self->b) = COS_DEG(rotationY) * scaleY * sx;
+		CONST_CAST(float, self->c) = SIN_DEG(rotation + shearX) * scaleX * sy;
+		CONST_CAST(float, self->d) = SIN_DEG(rotationY) * scaleY * sy;
+		CONST_CAST(float, self->worldX) = x * sx + self->skeleton->x;
+		CONST_CAST(float, self->worldY) = y * sy + self->skeleton->y;
+		return;
+	}
+
+	pa = parent->a;
+	pb = parent->b;
+	pc = parent->c;
+	pd = parent->d;
+
+	CONST_CAST(float, self->worldX) = pa * x + pb * y + parent->worldX;
+	CONST_CAST(float, self->worldY) = pc * x + pd * y + parent->worldY;
+
+	switch (self->data->transformMode) {
+		case SP_TRANSFORMMODE_NORMAL: {
+			float rotationY = rotation + 90 + shearY;
+			float la = COS_DEG(rotation + shearX) * scaleX;
+			float lb = COS_DEG(rotationY) * scaleY;
+			float lc = SIN_DEG(rotation + shearX) * scaleX;
+			float ld = SIN_DEG(rotationY) * scaleY;
+			CONST_CAST(float, self->a) = pa * la + pb * lc;
+			CONST_CAST(float, self->b) = pa * lb + pb * ld;
+			CONST_CAST(float, self->c) = pc * la + pd * lc;
+			CONST_CAST(float, self->d) = pc * lb + pd * ld;
+			return;
+		}
+		case SP_TRANSFORMMODE_ONLYTRANSLATION: {
+			float rotationY = rotation + 90 + shearY;
+			CONST_CAST(float, self->a) = COS_DEG(rotation + shearX) * scaleX;
+			CONST_CAST(float, self->b) = COS_DEG(rotationY) * scaleY;
+			CONST_CAST(float, self->c) = SIN_DEG(rotation + shearX) * scaleX;
+			CONST_CAST(float, self->d) = SIN_DEG(rotationY) * scaleY;
+			break;
+		}
+		case SP_TRANSFORMMODE_NOROTATIONORREFLECTION: {
+			float s = pa * pa + pc * pc;
+			float prx, rx, ry, la, lb, lc, ld;
+			if (s > 0.0001f) {
+				s = ABS(pa * pd - pb * pc) / s;
+				pa /= self->skeleton->scaleX;
+				pc /= self->skeleton->scaleY;
+				pb = pc * s;
+				pd = pa * s;
+				prx = ATAN2(pc, pa) * RAD_DEG;
+			} else {
+				pa = 0;
+				pc = 0;
+				prx = 90 - ATAN2(pd, pb) * RAD_DEG;
+			}
+			rx = rotation + shearX - prx;
+			ry = rotation + shearY - prx + 90;
+			la = COS_DEG(rx) * scaleX;
+			lb = COS_DEG(ry) * scaleY;
+			lc = SIN_DEG(rx) * scaleX;
+			ld = SIN_DEG(ry) * scaleY;
+			CONST_CAST(float, self->a) = pa * la - pb * lc;
+			CONST_CAST(float, self->b) = pa * lb - pb * ld;
+			CONST_CAST(float, self->c) = pc * la + pd * lc;
+			CONST_CAST(float, self->d) = pc * lb + pd * ld;
+			break;
+		}
+		case SP_TRANSFORMMODE_NOSCALE:
+		case SP_TRANSFORMMODE_NOSCALEORREFLECTION: {
+			float za, zc, s;
+			float r, zb, zd, la, lb, lc, ld;
+			cosine = COS_DEG(rotation);
+			sine = SIN_DEG(rotation);
+			za = (pa * cosine + pb * sine) / sx;
+			zc = (pc * cosine + pd * sine) / sy;
+			s = SQRT(za * za + zc * zc);
+			if (s > 0.00001f) s = 1 / s;
+			za *= s;
+			zc *= s;
+			s = SQRT(za * za + zc * zc);
+			if (self->data->transformMode == SP_TRANSFORMMODE_NOSCALE && (pa * pd - pb * pc < 0) != (sx < 0 != sy < 0))
+				s = -s;
+			r = PI / 2 + ATAN2(zc, za);
+			zb = COS(r) * s;
+			zd = SIN(r) * s;
+			la = COS_DEG(shearX) * scaleX;
+			lb = COS_DEG(90 + shearY) * scaleY;
+			lc = SIN_DEG(shearX) * scaleX;
+			ld = SIN_DEG(90 + shearY) * scaleY;
+			CONST_CAST(float, self->a) = za * la + zb * lc;
+			CONST_CAST(float, self->b) = za * lb + zb * ld;
+			CONST_CAST(float, self->c) = zc * la + zd * lc;
+			CONST_CAST(float, self->d) = zc * lb + zd * ld;
+			break;
+		}
+	}
+
+	CONST_CAST(float, self->a) *= sx;
+	CONST_CAST(float, self->b) *= sx;
+	CONST_CAST(float, self->c) *= sy;
+	CONST_CAST(float, self->d) *= sy;
+}
+
+void spBone_setToSetupPose(spBone *self) {
+	self->x = self->data->x;
+	self->y = self->data->y;
+	self->rotation = self->data->rotation;
+	self->scaleX = self->data->scaleX;
+	self->scaleY = self->data->scaleY;
+	self->shearX = self->data->shearX;
+	self->shearY = self->data->shearY;
+}
+
+float spBone_getWorldRotationX(spBone *self) {
+	return ATAN2(self->c, self->a) * RAD_DEG;
+}
+
+float spBone_getWorldRotationY(spBone *self) {
+	return ATAN2(self->d, self->b) * RAD_DEG;
+}
+
+float spBone_getWorldScaleX(spBone *self) {
+	return SQRT(self->a * self->a + self->c * self->c);
+}
+
+float spBone_getWorldScaleY(spBone *self) {
+	return SQRT(self->b * self->b + self->d * self->d);
+}
+
+/** Computes the individual applied transform values from the world transform. This can be useful to perform processing using
+ * the applied transform after the world transform has been modified directly (eg, by a constraint).
+ * <p>
+ * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. */
+void spBone_updateAppliedTransform(spBone *self) {
+	spBone *parent = self->parent;
+	if (!parent) {
+		self->ax = self->worldX;
+		self->ay = self->worldY;
+		self->arotation = ATAN2(self->c, self->a) * RAD_DEG;
+		self->ascaleX = SQRT(self->a * self->a + self->c * self->c);
+		self->ascaleY = SQRT(self->b * self->b + self->d * self->d);
+		self->ashearX = 0;
+		self->ashearY = ATAN2(self->a * self->b + self->c * self->d, self->a * self->d - self->b * self->c) * RAD_DEG;
+	} else {
+		float pa = parent->a, pb = parent->b, pc = parent->c, pd = parent->d;
+		float pid = 1 / (pa * pd - pb * pc);
+		float dx = self->worldX - parent->worldX, dy = self->worldY - parent->worldY;
+		float ia = pid * pd;
+		float id = pid * pa;
+		float ib = pid * pb;
+		float ic = pid * pc;
+		float ra = ia * self->a - ib * self->c;
+		float rb = ia * self->b - ib * self->d;
+		float rc = id * self->c - ic * self->a;
+		float rd = id * self->d - ic * self->b;
+		self->ax = (dx * pd * pid - dy * pb * pid);
+		self->ay = (dy * pa * pid - dx * pc * pid);
+		self->ashearX = 0;
+		self->ascaleX = SQRT(ra * ra + rc * rc);
+		if (self->ascaleX > 0.0001f) {
+			float det = ra * rd - rb * rc;
+			self->ascaleY = det / self->ascaleX;
+			self->ashearY = ATAN2(ra * rb + rc * rd, det) * RAD_DEG;
+			self->arotation = ATAN2(rc, ra) * RAD_DEG;
+		} else {
+			self->ascaleX = 0;
+			self->ascaleY = SQRT(rb * rb + rd * rd);
+			self->ashearY = 0;
+			self->arotation = 90 - ATAN2(rd, rb) * RAD_DEG;
+		}
+	}
+}
+
+void spBone_worldToLocal(spBone *self, float worldX, float worldY, float *localX, float *localY) {
+	float invDet = 1 / (self->a * self->d - self->b * self->c);
+	float x = worldX - self->worldX, y = worldY - self->worldY;
+	*localX = (x * self->d * invDet - y * self->b * invDet);
+	*localY = (y * self->a * invDet - x * self->c * invDet);
+}
+
+void spBone_localToWorld(spBone *self, float localX, float localY, float *worldX, float *worldY) {
+	float x = localX, y = localY;
+	*worldX = x * self->a + y * self->b + self->worldX;
+	*worldY = x * self->c + y * self->d + self->worldY;
+}
+
+float spBone_worldToLocalRotation(spBone *self, float worldRotation) {
+	float sine, cosine;
+	sine = SIN_DEG(worldRotation);
+	cosine = COS_DEG(worldRotation);
+	return ATAN2(self->a * sine - self->c * cosine, self->d * cosine - self->b * sine) * RAD_DEG + self->rotation -
+		   self->shearX;
+}
+
+float spBone_localToWorldRotation(spBone *self, float localRotation) {
+	float sine, cosine;
+	localRotation -= self->rotation - self->shearX;
+	sine = SIN_DEG(localRotation);
+	cosine = COS_DEG(localRotation);
+	return ATAN2(cosine * self->c + sine * self->d, cosine * self->a + sine * self->b) * RAD_DEG;
+}
+
+void spBone_rotateWorld(spBone *self, float degrees) {
+	float a = self->a, b = self->b, c = self->c, d = self->d;
+	float cosine = COS_DEG(degrees), sine = SIN_DEG(degrees);
+	CONST_CAST(float, self->a) = cosine * a - sine * c;
+	CONST_CAST(float, self->b) = cosine * b - sine * d;
+	CONST_CAST(float, self->c) = sine * a + cosine * c;
+	CONST_CAST(float, self->d) = sine * b + cosine * d;
+}

+ 47 - 47
spine-c/spine-c/src/spine/BoneData.c

@@ -1,47 +1,47 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/BoneData.h>
-#include <spine/extension.h>
-
-spBoneData *spBoneData_create(int index, const char *name, spBoneData *parent) {
-	spBoneData *self = NEW(spBoneData);
-	CONST_CAST(int, self->index) = index;
-	MALLOC_STR(self->name, name);
-	CONST_CAST(spBoneData*, self->parent) = parent;
-	self->scaleX = 1;
-	self->scaleY = 1;
-	self->transformMode = SP_TRANSFORMMODE_NORMAL;
-	return self;
-}
-
-void spBoneData_dispose(spBoneData *self) {
-	FREE(self->name);
-	FREE(self);
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/BoneData.h>
+#include <spine/extension.h>
+
+spBoneData *spBoneData_create(int index, const char *name, spBoneData *parent) {
+	spBoneData *self = NEW(spBoneData);
+	CONST_CAST(int, self->index) = index;
+	MALLOC_STR(self->name, name);
+	CONST_CAST(spBoneData *, self->parent) = parent;
+	self->scaleX = 1;
+	self->scaleY = 1;
+	self->transformMode = SP_TRANSFORMMODE_NORMAL;
+	return self;
+}
+
+void spBoneData_dispose(spBoneData *self) {
+	FREE(self->name);
+	FREE(self);
+}

+ 54 - 54
spine-c/spine-c/src/spine/BoundingBoxAttachment.c

@@ -1,54 +1,54 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/BoundingBoxAttachment.h>
-#include <spine/extension.h>
-
-void _spBoundingBoxAttachment_dispose(spAttachment *attachment) {
-	spBoundingBoxAttachment *self = SUB_CAST(spBoundingBoxAttachment, attachment);
-
-	_spVertexAttachment_deinit(SUPER(self));
-
-	FREE(self);
-}
-
-spAttachment *_spBoundingBoxAttachment_copy(spAttachment *attachment) {
-	spBoundingBoxAttachment *copy = spBoundingBoxAttachment_create(attachment->name);
-	spBoundingBoxAttachment *self = SUB_CAST(spBoundingBoxAttachment, attachment);
-	spVertexAttachment_copyTo(SUPER(self), SUPER(copy));
-	return SUPER(SUPER(copy));
-}
-
-spBoundingBoxAttachment *spBoundingBoxAttachment_create(const char *name) {
-	spBoundingBoxAttachment *self = NEW(spBoundingBoxAttachment);
-	_spVertexAttachment_init(SUPER(self));
-	_spAttachment_init(SUPER(SUPER(self)), name, SP_ATTACHMENT_BOUNDING_BOX, _spBoundingBoxAttachment_dispose,
-					   _spBoundingBoxAttachment_copy);
-	return self;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/BoundingBoxAttachment.h>
+#include <spine/extension.h>
+
+void _spBoundingBoxAttachment_dispose(spAttachment *attachment) {
+	spBoundingBoxAttachment *self = SUB_CAST(spBoundingBoxAttachment, attachment);
+
+	_spVertexAttachment_deinit(SUPER(self));
+
+	FREE(self);
+}
+
+spAttachment *_spBoundingBoxAttachment_copy(spAttachment *attachment) {
+	spBoundingBoxAttachment *copy = spBoundingBoxAttachment_create(attachment->name);
+	spBoundingBoxAttachment *self = SUB_CAST(spBoundingBoxAttachment, attachment);
+	spVertexAttachment_copyTo(SUPER(self), SUPER(copy));
+	return SUPER(SUPER(copy));
+}
+
+spBoundingBoxAttachment *spBoundingBoxAttachment_create(const char *name) {
+	spBoundingBoxAttachment *self = NEW(spBoundingBoxAttachment);
+	_spVertexAttachment_init(SUPER(self));
+	_spAttachment_init(SUPER(SUPER(self)), name, SP_ATTACHMENT_BOUNDING_BOX, _spBoundingBoxAttachment_dispose,
+					   _spBoundingBoxAttachment_copy);
+	return self;
+}

+ 56 - 56
spine-c/spine-c/src/spine/ClippingAttachment.c

@@ -1,56 +1,56 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/ClippingAttachment.h>
-#include <spine/extension.h>
-
-void _spClippingAttachment_dispose(spAttachment *attachment) {
-	spClippingAttachment *self = SUB_CAST(spClippingAttachment, attachment);
-
-	_spVertexAttachment_deinit(SUPER(self));
-
-	FREE(self);
-}
-
-spAttachment *_spClippingAttachment_copy(spAttachment *attachment) {
-	spClippingAttachment *copy = spClippingAttachment_create(attachment->name);
-	spClippingAttachment *self = SUB_CAST(spClippingAttachment, attachment);
-	spVertexAttachment_copyTo(SUPER(self), SUPER(copy));
-	copy->endSlot = self->endSlot;
-	return SUPER(SUPER(copy));
-}
-
-spClippingAttachment *spClippingAttachment_create(const char *name) {
-	spClippingAttachment *self = NEW(spClippingAttachment);
-	_spVertexAttachment_init(SUPER(self));
-	_spAttachment_init(SUPER(SUPER(self)), name, SP_ATTACHMENT_CLIPPING, _spClippingAttachment_dispose,
-					   _spClippingAttachment_copy);
-	self->endSlot = 0;
-	return self;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/ClippingAttachment.h>
+#include <spine/extension.h>
+
+void _spClippingAttachment_dispose(spAttachment *attachment) {
+	spClippingAttachment *self = SUB_CAST(spClippingAttachment, attachment);
+
+	_spVertexAttachment_deinit(SUPER(self));
+
+	FREE(self);
+}
+
+spAttachment *_spClippingAttachment_copy(spAttachment *attachment) {
+	spClippingAttachment *copy = spClippingAttachment_create(attachment->name);
+	spClippingAttachment *self = SUB_CAST(spClippingAttachment, attachment);
+	spVertexAttachment_copyTo(SUPER(self), SUPER(copy));
+	copy->endSlot = self->endSlot;
+	return SUPER(SUPER(copy));
+}
+
+spClippingAttachment *spClippingAttachment_create(const char *name) {
+	spClippingAttachment *self = NEW(spClippingAttachment);
+	_spVertexAttachment_init(SUPER(self));
+	_spAttachment_init(SUPER(SUPER(self)), name, SP_ATTACHMENT_CLIPPING, _spClippingAttachment_dispose,
+					   _spClippingAttachment_copy);
+	self->endSlot = 0;
+	return self;
+}

+ 8 - 4
spine-c/spine-c/src/spine/Color.c

@@ -91,14 +91,18 @@ void spColor_addFloats3(spColor *self, float r, float g, float b) {
 
 void spColor_clamp(spColor *self) {
 	if (self->r < 0) self->r = 0;
-	else if (self->r > 1) self->r = 1;
+	else if (self->r > 1)
+		self->r = 1;
 
 	if (self->g < 0) self->g = 0;
-	else if (self->g > 1) self->g = 1;
+	else if (self->g > 1)
+		self->g = 1;
 
 	if (self->b < 0) self->b = 0;
-	else if (self->b > 1) self->b = 1;
+	else if (self->b > 1)
+		self->b = 1;
 
 	if (self->a < 0) self->a = 0;
-	else if (self->a > 1) self->a = 1;
+	else if (self->a > 1)
+		self->a = 1;
 }

+ 2 - 3
spine-c/spine-c/src/spine/Debug.c

@@ -27,8 +27,8 @@
  * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
-#include <spine/Debug.h>
 #include <spine/Animation.h>
+#include <spine/Debug.h>
 
 #include <stdio.h>
 
@@ -56,8 +56,7 @@ static const char *_spTimelineTypeNames[] = {
 		"Rgb",
 		"TransformConstraint",
 		"DrawOrder",
-		"Event"
-};
+		"Event"};
 
 void spDebug_printSkeletonData(spSkeletonData *skeletonData) {
 	int i, n;

+ 43 - 43
spine-c/spine-c/src/spine/Event.c

@@ -1,43 +1,43 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/Event.h>
-#include <spine/extension.h>
-
-spEvent *spEvent_create(float time, spEventData *data) {
-	spEvent *self = NEW(spEvent);
-	CONST_CAST(spEventData*, self->data) = data;
-	CONST_CAST(float, self->time) = time;
-	return self;
-}
-
-void spEvent_dispose(spEvent *self) {
-	FREE(self->stringValue);
-	FREE(self);
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/Event.h>
+#include <spine/extension.h>
+
+spEvent *spEvent_create(float time, spEventData *data) {
+	spEvent *self = NEW(spEvent);
+	CONST_CAST(spEventData *, self->data) = data;
+	CONST_CAST(float, self->time) = time;
+	return self;
+}
+
+void spEvent_dispose(spEvent *self) {
+	FREE(self->stringValue);
+	FREE(self);
+}

+ 44 - 44
spine-c/spine-c/src/spine/EventData.c

@@ -1,44 +1,44 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/EventData.h>
-#include <spine/extension.h>
-
-spEventData *spEventData_create(const char *name) {
-	spEventData *self = NEW(spEventData);
-	MALLOC_STR(self->name, name);
-	return self;
-}
-
-void spEventData_dispose(spEventData *self) {
-	FREE(self->audioPath);
-	FREE(self->stringValue);
-	FREE(self->name);
-	FREE(self);
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/EventData.h>
+#include <spine/extension.h>
+
+spEventData *spEventData_create(const char *name) {
+	spEventData *self = NEW(spEventData);
+	MALLOC_STR(self->name, name);
+	return self;
+}
+
+void spEventData_dispose(spEventData *self) {
+	FREE(self->audioPath);
+	FREE(self->stringValue);
+	FREE(self->name);
+	FREE(self);
+}

+ 297 - 297
spine-c/spine-c/src/spine/IkConstraint.c

@@ -1,297 +1,297 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/IkConstraint.h>
-#include <spine/Skeleton.h>
-#include <spine/extension.h>
-#include <float.h>
-
-spIkConstraint *spIkConstraint_create(spIkConstraintData *data, const spSkeleton *skeleton) {
-	int i;
-
-	spIkConstraint *self = NEW(spIkConstraint);
-	CONST_CAST(spIkConstraintData*, self->data) = data;
-	self->bendDirection = data->bendDirection;
-	self->compress = data->compress;
-	self->stretch = data->stretch;
-	self->mix = data->mix;
-	self->softness = data->softness;
-
-	self->bonesCount = self->data->bonesCount;
-	self->bones = MALLOC(spBone*, self->bonesCount);
-	for (i = 0; i < self->bonesCount; ++i)
-		self->bones[i] = spSkeleton_findBone(skeleton, self->data->bones[i]->name);
-	self->target = spSkeleton_findBone(skeleton, self->data->target->name);
-
-	return self;
-}
-
-void spIkConstraint_dispose(spIkConstraint *self) {
-	FREE(self->bones);
-	FREE(self);
-}
-
-void spIkConstraint_update(spIkConstraint *self) {
-	if (self->mix == 0) return;
-	switch (self->bonesCount) {
-		case 1:
-			spIkConstraint_apply1(self->bones[0], self->target->worldX, self->target->worldY, self->compress,
-								  self->stretch, self->data->uniform, self->mix);
-			break;
-		case 2:
-			spIkConstraint_apply2(self->bones[0], self->bones[1], self->target->worldX, self->target->worldY,
-								  self->bendDirection, self->stretch, self->data->uniform, self->softness, self->mix);
-			break;
-	}
-}
-
-void
-spIkConstraint_apply1(spBone *bone, float targetX, float targetY, int /*boolean*/ compress, int /*boolean*/ stretch,
-					  int /*boolean*/ uniform, float alpha) {
-	spBone *p = bone->parent;
-	float pa = p->a, pb = p->b, pc = p->c, pd = p->d;
-	float rotationIK = -bone->ashearX - bone->arotation;
-	float tx = 0, ty = 0, sx = 0, sy = 0, s = 0, sa = 0, sc = 0;
-
-	switch (bone->data->transformMode) {
-		case SP_TRANSFORMMODE_ONLYTRANSLATION:
-			tx = targetX - bone->worldX;
-			ty = targetY - bone->worldY;
-			break;
-		case SP_TRANSFORMMODE_NOROTATIONORREFLECTION: {
-			s = ABS(pa * pd - pb * pc) / (pa * pa + pc * pc);
-			sa = pa / bone->skeleton->scaleX;
-			sc = pc / bone->skeleton->scaleY;
-			pb = -sc * s * bone->skeleton->scaleX;
-			pd = sa * s * bone->skeleton->scaleY;
-			rotationIK += ATAN2(sc, sa) * RAD_DEG;
-		}
-		default: {
-			float x = targetX - p->worldX, y = targetY - p->worldY;
-			float d = pa * pd - pb * pc;
-			tx = (x * pd - y * pb) / d - bone->ax;
-			ty = (y * pa - x * pc) / d - bone->ay;
-		}
-	}
-	rotationIK += ATAN2(ty, tx) * RAD_DEG;
-
-	if (bone->ascaleX < 0) rotationIK += 180;
-	if (rotationIK > 180) rotationIK -= 360;
-	else if (rotationIK < -180) rotationIK += 360;
-	sx = bone->ascaleX;
-	sy = bone->ascaleY;
-	if (compress || stretch) {
-		float b, dd;
-		switch (bone->data->transformMode) {
-			case SP_TRANSFORMMODE_NOSCALE:
-			case SP_TRANSFORMMODE_NOSCALEORREFLECTION:
-				tx = targetX - bone->worldX;
-				ty = targetY - bone->worldY;
-			default:;
-		}
-		b = bone->data->length * sx, dd = SQRT(tx * tx + ty * ty);
-		if ((compress && dd < b) || ((stretch && dd > b) && (b > 0.0001f))) {
-			s = (dd / b - 1) * alpha + 1;
-			sx *= s;
-			if (uniform) sy *= s;
-		}
-	}
-	spBone_updateWorldTransformWith(bone, bone->ax, bone->ay, bone->arotation + rotationIK * alpha, sx,
-									sy, bone->ashearX, bone->ashearY);
-}
-
-void
-spIkConstraint_apply2(spBone *parent, spBone *child, float targetX, float targetY, int bendDir, int /*boolean*/ stretch,
-					  int /*boolean*/ uniform, float softness, float alpha) {
-	float a, b, c, d;
-	float px, py, psx, psy, sx, sy;
-	float cx, cy, csx, cwx, cwy;
-	int o1, o2, s2, u;
-	spBone *pp = parent->parent;
-	float tx, ty, dd, dx, dy, l1, l2, a1, a2, r, td, sd, p;
-	float id, x, y;
-	float aa, bb, ll, ta, c0, c1, c2;
-
-	px = parent->ax;
-	py = parent->ay;
-	psx = parent->ascaleX;
-	psy = parent->ascaleY;
-	sx = psx;
-	sy = psy;
-	csx = child->ascaleX;
-	if (psx < 0) {
-		psx = -psx;
-		o1 = 180;
-		s2 = -1;
-	} else {
-		o1 = 0;
-		s2 = 1;
-	}
-	if (psy < 0) {
-		psy = -psy;
-		s2 = -s2;
-	}
-	if (csx < 0) {
-		csx = -csx;
-		o2 = 180;
-	} else
-		o2 = 0;
-	r = psx - psy;
-	cx = child->ax;
-	u = (r < 0 ? -r : r) <= 0.0001f;
-	if (!u || stretch) {
-		cy = 0;
-		cwx = parent->a * cx + parent->worldX;
-		cwy = parent->c * cx + parent->worldY;
-	} else {
-		cy = child->ay;
-		cwx = parent->a * cx + parent->b * cy + parent->worldX;
-		cwy = parent->c * cx + parent->d * cy + parent->worldY;
-	}
-	a = pp->a;
-	b = pp->b;
-	c = pp->c;
-	d = pp->d;
-	id = 1 / (a * d - b * c);
-	x = cwx - pp->worldX;
-	y = cwy - pp->worldY;
-	dx = (x * d - y * b) * id - px;
-	dy = (y * a - x * c) * id - py;
-	l1 = SQRT(dx * dx + dy * dy);
-	l2 = child->data->length * csx;
-	if (l1 < 0.0001) {
-		spIkConstraint_apply1(parent, targetX, targetY, 0, stretch, 0, alpha);
-		spBone_updateWorldTransformWith(child, cx, cy, 0, child->ascaleX, child->ascaleY, child->ashearX,
-										child->ashearY);
-		return;
-	}
-	x = targetX - pp->worldX;
-	y = targetY - pp->worldY;
-	tx = (x * d - y * b) * id - px;
-	ty = (y * a - x * c) * id - py;
-	dd = tx * tx + ty * ty;
-	if (softness != 0) {
-		softness *= psx * (csx + 1) * 0.5f;
-		td = SQRT(dd);
-		sd = td - l1 - l2 * psx + softness;
-		if (sd > 0) {
-			p = MIN(1, sd / (softness * 2)) - 1;
-			p = (sd - softness * (1 - p * p)) / td;
-			tx -= p * tx;
-			ty -= p * ty;
-			dd = tx * tx + ty * ty;
-		}
-	}
-	if (u) {
-		float cosine;
-		l2 *= psx;
-		cosine = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
-		if (cosine < -1) {
-			cosine = -1;
-			a2 = PI * bendDir;
-		} else if (cosine > 1) {
-			cosine = 1;
-			a2 = 0;
-			if (stretch) {
-				a = (SQRT(dd) / (l1 + l2) - 1) * alpha + 1;
-				sx *= a;
-				if (uniform) sy *= a;
-			}
-		} else
-			a2 = ACOS(cosine) * bendDir;
-		a = l1 + l2 * cosine;
-		b = l2 * SIN(a2);
-		a1 = ATAN2(ty * a - tx * b, tx * a + ty * b);
-	} else {
-		a = psx * l2;
-		b = psy * l2;
-		aa = a * a, bb = b * b, ll = l1 * l1, ta = ATAN2(ty, tx);
-		c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa;
-		d = c1 * c1 - 4 * c2 * c0;
-		if (d >= 0) {
-			float q = SQRT(d), r0, r1;
-			if (c1 < 0) q = -q;
-			q = -(c1 + q) * 0.5f;
-			r0 = q / c2;
-			r1 = c0 / q;
-			r = ABS(r0) < ABS(r1) ? r0 : r1;
-			if (r * r <= dd) {
-				y = SQRT(dd - r * r) * bendDir;
-				a1 = ta - ATAN2(y, r);
-				a2 = ATAN2(y / psy, (r - l1) / psx);
-				goto break_outer;
-			}
-		}
-		{
-			float minAngle = PI, minX = l1 - a, minDist = minX * minX, minY = 0;
-			float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0;
-			c0 = -a * l1 / (aa - bb);
-			if (c0 >= -1 && c0 <= 1) {
-				c0 = ACOS(c0);
-				x = a * COS(c0) + l1;
-				y = b * SIN(c0);
-				d = x * x + y * y;
-				if (d < minDist) {
-					minAngle = c0;
-					minDist = d;
-					minX = x;
-					minY = y;
-				}
-				if (d > maxDist) {
-					maxAngle = c0;
-					maxDist = d;
-					maxX = x;
-					maxY = y;
-				}
-			}
-			if (dd <= (minDist + maxDist) * 0.5f) {
-				a1 = ta - ATAN2(minY * bendDir, minX);
-				a2 = minAngle * bendDir;
-			} else {
-				a1 = ta - ATAN2(maxY * bendDir, maxX);
-				a2 = maxAngle * bendDir;
-			}
-		}
-	}
-	break_outer:
-	{
-		float os = ATAN2(cy, cx) * s2;
-		float rotation = parent->arotation;
-		a1 = (a1 - os) * RAD_DEG + o1 -rotation;
-		if (a1 > 180) a1 -= 360;
-		else if (a1 < -180) a1 += 360;
-		spBone_updateWorldTransformWith(parent, px, py, rotation + a1 * alpha, sx, sy, 0, 0);
-		rotation = child->arotation;
-		a2 = ((a2 + os) * RAD_DEG - child->ashearX) * s2 + o2 - rotation;
-		if (a2 > 180) a2 -= 360;
-		else if (a2 < -180) a2 += 360;
-		spBone_updateWorldTransformWith(child, cx, cy, rotation + a2 * alpha, child->ascaleX, child->ascaleY,
-										child->ashearX, child->ashearY);
-	}
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <float.h>
+#include <spine/IkConstraint.h>
+#include <spine/Skeleton.h>
+#include <spine/extension.h>
+
+spIkConstraint *spIkConstraint_create(spIkConstraintData *data, const spSkeleton *skeleton) {
+	int i;
+
+	spIkConstraint *self = NEW(spIkConstraint);
+	CONST_CAST(spIkConstraintData *, self->data) = data;
+	self->bendDirection = data->bendDirection;
+	self->compress = data->compress;
+	self->stretch = data->stretch;
+	self->mix = data->mix;
+	self->softness = data->softness;
+
+	self->bonesCount = self->data->bonesCount;
+	self->bones = MALLOC(spBone *, self->bonesCount);
+	for (i = 0; i < self->bonesCount; ++i)
+		self->bones[i] = spSkeleton_findBone(skeleton, self->data->bones[i]->name);
+	self->target = spSkeleton_findBone(skeleton, self->data->target->name);
+
+	return self;
+}
+
+void spIkConstraint_dispose(spIkConstraint *self) {
+	FREE(self->bones);
+	FREE(self);
+}
+
+void spIkConstraint_update(spIkConstraint *self) {
+	if (self->mix == 0) return;
+	switch (self->bonesCount) {
+		case 1:
+			spIkConstraint_apply1(self->bones[0], self->target->worldX, self->target->worldY, self->compress,
+								  self->stretch, self->data->uniform, self->mix);
+			break;
+		case 2:
+			spIkConstraint_apply2(self->bones[0], self->bones[1], self->target->worldX, self->target->worldY,
+								  self->bendDirection, self->stretch, self->data->uniform, self->softness, self->mix);
+			break;
+	}
+}
+
+void spIkConstraint_apply1(spBone *bone, float targetX, float targetY, int /*boolean*/ compress, int /*boolean*/ stretch,
+						   int /*boolean*/ uniform, float alpha) {
+	spBone *p = bone->parent;
+	float pa = p->a, pb = p->b, pc = p->c, pd = p->d;
+	float rotationIK = -bone->ashearX - bone->arotation;
+	float tx = 0, ty = 0, sx = 0, sy = 0, s = 0, sa = 0, sc = 0;
+
+	switch (bone->data->transformMode) {
+		case SP_TRANSFORMMODE_ONLYTRANSLATION:
+			tx = targetX - bone->worldX;
+			ty = targetY - bone->worldY;
+			break;
+		case SP_TRANSFORMMODE_NOROTATIONORREFLECTION: {
+			s = ABS(pa * pd - pb * pc) / (pa * pa + pc * pc);
+			sa = pa / bone->skeleton->scaleX;
+			sc = pc / bone->skeleton->scaleY;
+			pb = -sc * s * bone->skeleton->scaleX;
+			pd = sa * s * bone->skeleton->scaleY;
+			rotationIK += ATAN2(sc, sa) * RAD_DEG;
+		}
+		default: {
+			float x = targetX - p->worldX, y = targetY - p->worldY;
+			float d = pa * pd - pb * pc;
+			tx = (x * pd - y * pb) / d - bone->ax;
+			ty = (y * pa - x * pc) / d - bone->ay;
+		}
+	}
+	rotationIK += ATAN2(ty, tx) * RAD_DEG;
+
+	if (bone->ascaleX < 0) rotationIK += 180;
+	if (rotationIK > 180) rotationIK -= 360;
+	else if (rotationIK < -180)
+		rotationIK += 360;
+	sx = bone->ascaleX;
+	sy = bone->ascaleY;
+	if (compress || stretch) {
+		float b, dd;
+		switch (bone->data->transformMode) {
+			case SP_TRANSFORMMODE_NOSCALE:
+			case SP_TRANSFORMMODE_NOSCALEORREFLECTION:
+				tx = targetX - bone->worldX;
+				ty = targetY - bone->worldY;
+			default:;
+		}
+		b = bone->data->length * sx, dd = SQRT(tx * tx + ty * ty);
+		if ((compress && dd < b) || ((stretch && dd > b) && (b > 0.0001f))) {
+			s = (dd / b - 1) * alpha + 1;
+			sx *= s;
+			if (uniform) sy *= s;
+		}
+	}
+	spBone_updateWorldTransformWith(bone, bone->ax, bone->ay, bone->arotation + rotationIK * alpha, sx,
+									sy, bone->ashearX, bone->ashearY);
+}
+
+void spIkConstraint_apply2(spBone *parent, spBone *child, float targetX, float targetY, int bendDir, int /*boolean*/ stretch,
+						   int /*boolean*/ uniform, float softness, float alpha) {
+	float a, b, c, d;
+	float px, py, psx, psy, sx, sy;
+	float cx, cy, csx, cwx, cwy;
+	int o1, o2, s2, u;
+	spBone *pp = parent->parent;
+	float tx, ty, dd, dx, dy, l1, l2, a1, a2, r, td, sd, p;
+	float id, x, y;
+	float aa, bb, ll, ta, c0, c1, c2;
+
+	px = parent->ax;
+	py = parent->ay;
+	psx = parent->ascaleX;
+	psy = parent->ascaleY;
+	sx = psx;
+	sy = psy;
+	csx = child->ascaleX;
+	if (psx < 0) {
+		psx = -psx;
+		o1 = 180;
+		s2 = -1;
+	} else {
+		o1 = 0;
+		s2 = 1;
+	}
+	if (psy < 0) {
+		psy = -psy;
+		s2 = -s2;
+	}
+	if (csx < 0) {
+		csx = -csx;
+		o2 = 180;
+	} else
+		o2 = 0;
+	r = psx - psy;
+	cx = child->ax;
+	u = (r < 0 ? -r : r) <= 0.0001f;
+	if (!u || stretch) {
+		cy = 0;
+		cwx = parent->a * cx + parent->worldX;
+		cwy = parent->c * cx + parent->worldY;
+	} else {
+		cy = child->ay;
+		cwx = parent->a * cx + parent->b * cy + parent->worldX;
+		cwy = parent->c * cx + parent->d * cy + parent->worldY;
+	}
+	a = pp->a;
+	b = pp->b;
+	c = pp->c;
+	d = pp->d;
+	id = 1 / (a * d - b * c);
+	x = cwx - pp->worldX;
+	y = cwy - pp->worldY;
+	dx = (x * d - y * b) * id - px;
+	dy = (y * a - x * c) * id - py;
+	l1 = SQRT(dx * dx + dy * dy);
+	l2 = child->data->length * csx;
+	if (l1 < 0.0001) {
+		spIkConstraint_apply1(parent, targetX, targetY, 0, stretch, 0, alpha);
+		spBone_updateWorldTransformWith(child, cx, cy, 0, child->ascaleX, child->ascaleY, child->ashearX,
+										child->ashearY);
+		return;
+	}
+	x = targetX - pp->worldX;
+	y = targetY - pp->worldY;
+	tx = (x * d - y * b) * id - px;
+	ty = (y * a - x * c) * id - py;
+	dd = tx * tx + ty * ty;
+	if (softness != 0) {
+		softness *= psx * (csx + 1) * 0.5f;
+		td = SQRT(dd);
+		sd = td - l1 - l2 * psx + softness;
+		if (sd > 0) {
+			p = MIN(1, sd / (softness * 2)) - 1;
+			p = (sd - softness * (1 - p * p)) / td;
+			tx -= p * tx;
+			ty -= p * ty;
+			dd = tx * tx + ty * ty;
+		}
+	}
+	if (u) {
+		float cosine;
+		l2 *= psx;
+		cosine = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
+		if (cosine < -1) {
+			cosine = -1;
+			a2 = PI * bendDir;
+		} else if (cosine > 1) {
+			cosine = 1;
+			a2 = 0;
+			if (stretch) {
+				a = (SQRT(dd) / (l1 + l2) - 1) * alpha + 1;
+				sx *= a;
+				if (uniform) sy *= a;
+			}
+		} else
+			a2 = ACOS(cosine) * bendDir;
+		a = l1 + l2 * cosine;
+		b = l2 * SIN(a2);
+		a1 = ATAN2(ty * a - tx * b, tx * a + ty * b);
+	} else {
+		a = psx * l2;
+		b = psy * l2;
+		aa = a * a, bb = b * b, ll = l1 * l1, ta = ATAN2(ty, tx);
+		c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa;
+		d = c1 * c1 - 4 * c2 * c0;
+		if (d >= 0) {
+			float q = SQRT(d), r0, r1;
+			if (c1 < 0) q = -q;
+			q = -(c1 + q) * 0.5f;
+			r0 = q / c2;
+			r1 = c0 / q;
+			r = ABS(r0) < ABS(r1) ? r0 : r1;
+			if (r * r <= dd) {
+				y = SQRT(dd - r * r) * bendDir;
+				a1 = ta - ATAN2(y, r);
+				a2 = ATAN2(y / psy, (r - l1) / psx);
+				goto break_outer;
+			}
+		}
+		{
+			float minAngle = PI, minX = l1 - a, minDist = minX * minX, minY = 0;
+			float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0;
+			c0 = -a * l1 / (aa - bb);
+			if (c0 >= -1 && c0 <= 1) {
+				c0 = ACOS(c0);
+				x = a * COS(c0) + l1;
+				y = b * SIN(c0);
+				d = x * x + y * y;
+				if (d < minDist) {
+					minAngle = c0;
+					minDist = d;
+					minX = x;
+					minY = y;
+				}
+				if (d > maxDist) {
+					maxAngle = c0;
+					maxDist = d;
+					maxX = x;
+					maxY = y;
+				}
+			}
+			if (dd <= (minDist + maxDist) * 0.5f) {
+				a1 = ta - ATAN2(minY * bendDir, minX);
+				a2 = minAngle * bendDir;
+			} else {
+				a1 = ta - ATAN2(maxY * bendDir, maxX);
+				a2 = maxAngle * bendDir;
+			}
+		}
+	}
+break_outer : {
+	float os = ATAN2(cy, cx) * s2;
+	float rotation = parent->arotation;
+	a1 = (a1 - os) * RAD_DEG + o1 - rotation;
+	if (a1 > 180) a1 -= 360;
+	else if (a1 < -180)
+		a1 += 360;
+	spBone_updateWorldTransformWith(parent, px, py, rotation + a1 * alpha, sx, sy, 0, 0);
+	rotation = child->arotation;
+	a2 = ((a2 + os) * RAD_DEG - child->ashearX) * s2 + o2 - rotation;
+	if (a2 > 180) a2 -= 360;
+	else if (a2 < -180)
+		a2 += 360;
+	spBone_updateWorldTransformWith(child, cx, cy, rotation + a2 * alpha, child->ascaleX, child->ascaleY,
+									child->ashearX, child->ashearY);
+}
+}

+ 48 - 48
spine-c/spine-c/src/spine/IkConstraintData.c

@@ -1,48 +1,48 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/IkConstraintData.h>
-#include <spine/extension.h>
-
-spIkConstraintData *spIkConstraintData_create(const char *name) {
-	spIkConstraintData *self = NEW(spIkConstraintData);
-	MALLOC_STR(self->name, name);
-	self->bendDirection = 1;
-	self->compress = 0;
-	self->stretch = 0;
-	self->uniform = 0;
-	self->mix = 1;
-	return self;
-}
-
-void spIkConstraintData_dispose(spIkConstraintData *self) {
-	FREE(self->name);
-	FREE(self->bones);
-	FREE(self);
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/IkConstraintData.h>
+#include <spine/extension.h>
+
+spIkConstraintData *spIkConstraintData_create(const char *name) {
+	spIkConstraintData *self = NEW(spIkConstraintData);
+	MALLOC_STR(self->name, name);
+	self->bendDirection = 1;
+	self->compress = 0;
+	self->stretch = 0;
+	self->uniform = 0;
+	self->mix = 1;
+	return self;
+}
+
+void spIkConstraintData_dispose(spIkConstraintData *self) {
+	FREE(self->name);
+	FREE(self->bones);
+	FREE(self);
+}

+ 8 - 7
spine-c/spine-c/src/spine/Json.c

@@ -35,11 +35,11 @@ THE SOFTWARE.
 #endif
 
 #include "Json.h"
-#include <stdio.h>
 #include <ctype.h>
+#include <spine/extension.h>
+#include <stdio.h>
 #include <stdlib.h> /* strtod (C89), strtof (C99) */
 #include <string.h> /* strcasecmp (4.4BSD - compatibility), _stricmp (_WIN32) */
-#include <spine/extension.h>
 
 #ifndef SPINE_JSON_DEBUG
 /* Define this to do extra NULL and expected-character checking */
@@ -221,7 +221,8 @@ static const char *parse_string(Json *item, const char *str) {
 						len = 1;
 					else if (uc < 0x800)
 						len = 2;
-					else if (uc < 0x10000) len = 3;
+					else if (uc < 0x10000)
+						len = 3;
 					ptr2 += len;
 
 					switch (len) {
@@ -292,7 +293,7 @@ Json *Json_create(const char *value) {
 static const char *parse_value(Json *item, const char *value) {
 	/* Referenced by Json_create(), parse_array(), and parse_object(). */
 	/* Always called with the result of skip(). */
-#if SPINE_JSON_DEBUG /* Checked at entry to graph, Json_create, and after every parse_ call. */
+#if SPINE_JSON_DEBUG      /* Checked at entry to graph, Json_create, and after every parse_ call. */
 	if (!value) return 0; /* Fail on null. */
 #endif
 
@@ -362,7 +363,7 @@ static const char *parse_array(Json *item, const char *value) {
 	if (*value == ']') return value + 1; /* empty array. */
 
 	item->child = child = Json_new();
-	if (!item->child) return 0; /* memory fail */
+	if (!item->child) return 0;                    /* memory fail */
 	value = skip(parse_value(child, skip(value))); /* skip any spacing, get the value. */
 	if (!value) return 0;
 	item->size = 1;
@@ -409,7 +410,7 @@ static const char *parse_object(Json *item, const char *value) {
 	if (*value != ':') {
 		ep = value;
 		return 0;
-	} /* fail! */
+	}                                                  /* fail! */
 	value = skip(parse_value(child, skip(value + 1))); /* skip any spacing, get the value. */
 	if (!value) return 0;
 	item->size = 1;
@@ -429,7 +430,7 @@ static const char *parse_object(Json *item, const char *value) {
 		if (*value != ':') {
 			ep = value;
 			return 0;
-		} /* fail! */
+		}                                                  /* fail! */
 		value = skip(parse_value(child, skip(value + 1))); /* skip any spacing, get the value. */
 		if (!value) return 0;
 		item->size++;

+ 210 - 210
spine-c/spine-c/src/spine/MeshAttachment.c

@@ -1,210 +1,210 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/MeshAttachment.h>
-#include <spine/extension.h>
-#include <stdio.h>
-
-void _spMeshAttachment_dispose(spAttachment *attachment) {
-	spMeshAttachment *self = SUB_CAST(spMeshAttachment, attachment);
-	FREE(self->path);
-	FREE(self->uvs);
-	if (!self->parentMesh) {
-		_spVertexAttachment_deinit(SUPER(self));
-		FREE(self->regionUVs);
-		FREE(self->triangles);
-		FREE(self->edges);
-	} else
-		_spAttachment_deinit(attachment);
-	FREE(self);
-}
-
-spAttachment *_spMeshAttachment_copy(spAttachment *attachment) {
-	spMeshAttachment *copy;
-	spMeshAttachment *self = SUB_CAST(spMeshAttachment, attachment);
-	if (self->parentMesh)
-		return SUPER(SUPER(spMeshAttachment_newLinkedMesh(self)));
-	copy = spMeshAttachment_create(attachment->name);
-	copy->rendererObject = self->rendererObject;
-	copy->regionU = self->regionU;
-	copy->regionV = self->regionV;
-	copy->regionU2 = self->regionU2;
-	copy->regionV2 = self->regionV2;
-	copy->regionDegrees = self->regionDegrees;
-	copy->regionOffsetX = self->regionOffsetX;
-	copy->regionOffsetY = self->regionOffsetY;
-	copy->regionWidth = self->regionWidth;
-	copy->regionHeight = self->regionHeight;
-	copy->regionOriginalWidth = self->regionOriginalWidth;
-	copy->regionOriginalHeight = self->regionOriginalHeight;
-	MALLOC_STR(copy->path, self->path);
-	spColor_setFromColor(&copy->color, &self->color);
-
-	spVertexAttachment_copyTo(SUPER(self), SUPER(copy));
-	copy->regionUVs = MALLOC(float, SUPER(self)->worldVerticesLength);
-	memcpy(copy->regionUVs, self->regionUVs, SUPER(self)->worldVerticesLength * sizeof(float));
-	copy->uvs = MALLOC(float, SUPER(self)->worldVerticesLength);
-	memcpy(copy->uvs, self->uvs, SUPER(self)->worldVerticesLength * sizeof(float));
-	copy->trianglesCount = self->trianglesCount;
-	copy->triangles = MALLOC(unsigned short, self->trianglesCount);
-	memcpy(copy->triangles, self->triangles, self->trianglesCount * sizeof(short));
-	copy->hullLength = self->hullLength;
-	if (self->edgesCount > 0) {
-		copy->edgesCount = self->edgesCount;
-		copy->edges = MALLOC(int, self->edgesCount);
-		memcpy(copy->edges, self->edges, self->edgesCount * sizeof(int));
-	}
-	copy->width = self->width;
-	copy->height = self->height;
-
-	return SUPER(SUPER(copy));
-}
-
-spMeshAttachment *spMeshAttachment_newLinkedMesh(spMeshAttachment *self) {
-	spMeshAttachment *copy = spMeshAttachment_create(self->super.super.name);
-
-	copy->rendererObject = self->rendererObject;
-	copy->regionU = self->regionU;
-	copy->regionV = self->regionV;
-	copy->regionU2 = self->regionU2;
-	copy->regionV2 = self->regionV2;
-	copy->regionDegrees = self->regionDegrees;
-	copy->regionOffsetX = self->regionOffsetX;
-	copy->regionOffsetY = self->regionOffsetY;
-	copy->regionWidth = self->regionWidth;
-	copy->regionHeight = self->regionHeight;
-	copy->regionOriginalWidth = self->regionOriginalWidth;
-	copy->regionOriginalHeight = self->regionOriginalHeight;
-	MALLOC_STR(copy->path, self->path);
-	spColor_setFromColor(&copy->color, &self->color);
-	copy->super.deformAttachment = self->super.deformAttachment;
-	spMeshAttachment_setParentMesh(copy, self->parentMesh ? self->parentMesh : self);
-	spMeshAttachment_updateUVs(copy);
-	return copy;
-}
-
-spMeshAttachment *spMeshAttachment_create(const char *name) {
-	spMeshAttachment *self = NEW(spMeshAttachment);
-	_spVertexAttachment_init(SUPER(self));
-	spColor_setFromFloats(&self->color, 1, 1, 1, 1);
-	_spAttachment_init(SUPER(SUPER(self)), name, SP_ATTACHMENT_MESH, _spMeshAttachment_dispose, _spMeshAttachment_copy);
-	return self;
-}
-
-void spMeshAttachment_updateUVs(spMeshAttachment *self) {
-	int i, n;
-	float *uvs;
-	float u, v, width, height;
-	int verticesLength = SUPER(self)->worldVerticesLength;
-	FREE(self->uvs);
-	uvs = self->uvs = MALLOC(float, verticesLength);
-	n = verticesLength;
-	u = self->regionU;
-	v = self->regionV;
-
-	switch (self->regionDegrees) {
-		case 90: {
-			float textureWidth = self->regionHeight / (self->regionU2 - self->regionU);
-			float textureHeight = self->regionWidth / (self->regionV2 - self->regionV);
-			u -= (self->regionOriginalHeight - self->regionOffsetY - self->regionHeight) / textureWidth;
-			v -= (self->regionOriginalWidth - self->regionOffsetX - self->regionWidth) / textureHeight;
-			width = self->regionOriginalHeight / textureWidth;
-			height = self->regionOriginalWidth / textureHeight;
-			for (i = 0; i < n; i += 2) {
-				uvs[i] = u + self->regionUVs[i + 1] * width;
-				uvs[i + 1] = v + (1 - self->regionUVs[i]) * height;
-			}
-			return;
-		}
-		case 180: {
-			float textureWidth = self->regionWidth / (self->regionU2 - self->regionU);
-			float textureHeight = self->regionHeight / (self->regionV2 - self->regionV);
-			u -= (self->regionOriginalWidth - self->regionOffsetX - self->regionWidth) / textureWidth;
-			v -= self->regionOffsetY / textureHeight;
-			width = self->regionOriginalWidth / textureWidth;
-			height = self->regionOriginalHeight / textureHeight;
-			for (i = 0; i < n; i += 2) {
-				uvs[i] = u + (1 - self->regionUVs[i]) * width;
-				uvs[i + 1] = v + (1 - self->regionUVs[i + 1]) * height;
-			}
-			return;
-		}
-		case 270: {
-			float textureHeight = self->regionHeight / (self->regionV2 - self->regionV);
-			float textureWidth = self->regionWidth / (self->regionU2 - self->regionU);
-			u -= self->regionOffsetY / textureWidth;
-			v -= self->regionOffsetX / textureHeight;
-			width = self->regionOriginalHeight / textureWidth;
-			height = self->regionOriginalWidth / textureHeight;
-			for (i = 0; i < n; i += 2) {
-				uvs[i] = u + (1 - self->regionUVs[i + 1]) * width;
-				uvs[i + 1] = v + self->regionUVs[i] * height;
-			}
-			return;
-		}
-		default: {
-			float textureWidth = self->regionWidth / (self->regionU2 - self->regionU);
-			float textureHeight = self->regionHeight / (self->regionV2 - self->regionV);
-			u -= self->regionOffsetX / textureWidth;
-			v -= (self->regionOriginalHeight - self->regionOffsetY - self->regionHeight) / textureHeight;
-			width = self->regionOriginalWidth / textureWidth;
-			height = self->regionOriginalHeight / textureHeight;
-			for (i = 0; i < n; i += 2) {
-				uvs[i] = u + self->regionUVs[i] * width;
-				uvs[i + 1] = v + self->regionUVs[i + 1] * height;
-			}
-		}
-	}
-}
-
-void spMeshAttachment_setParentMesh(spMeshAttachment *self, spMeshAttachment *parentMesh) {
-	CONST_CAST(spMeshAttachment*, self->parentMesh) = parentMesh;
-	if (parentMesh) {
-		self->super.bones = parentMesh->super.bones;
-		self->super.bonesCount = parentMesh->super.bonesCount;
-
-		self->super.vertices = parentMesh->super.vertices;
-		self->super.verticesCount = parentMesh->super.verticesCount;
-
-		self->regionUVs = parentMesh->regionUVs;
-
-		self->triangles = parentMesh->triangles;
-		self->trianglesCount = parentMesh->trianglesCount;
-
-		self->hullLength = parentMesh->hullLength;
-
-		self->super.worldVerticesLength = parentMesh->super.worldVerticesLength;
-
-		self->edges = parentMesh->edges;
-		self->edgesCount = parentMesh->edgesCount;
-
-		self->width = parentMesh->width;
-		self->height = parentMesh->height;
-	}
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/MeshAttachment.h>
+#include <spine/extension.h>
+#include <stdio.h>
+
+void _spMeshAttachment_dispose(spAttachment *attachment) {
+	spMeshAttachment *self = SUB_CAST(spMeshAttachment, attachment);
+	FREE(self->path);
+	FREE(self->uvs);
+	if (!self->parentMesh) {
+		_spVertexAttachment_deinit(SUPER(self));
+		FREE(self->regionUVs);
+		FREE(self->triangles);
+		FREE(self->edges);
+	} else
+		_spAttachment_deinit(attachment);
+	FREE(self);
+}
+
+spAttachment *_spMeshAttachment_copy(spAttachment *attachment) {
+	spMeshAttachment *copy;
+	spMeshAttachment *self = SUB_CAST(spMeshAttachment, attachment);
+	if (self->parentMesh)
+		return SUPER(SUPER(spMeshAttachment_newLinkedMesh(self)));
+	copy = spMeshAttachment_create(attachment->name);
+	copy->rendererObject = self->rendererObject;
+	copy->regionU = self->regionU;
+	copy->regionV = self->regionV;
+	copy->regionU2 = self->regionU2;
+	copy->regionV2 = self->regionV2;
+	copy->regionDegrees = self->regionDegrees;
+	copy->regionOffsetX = self->regionOffsetX;
+	copy->regionOffsetY = self->regionOffsetY;
+	copy->regionWidth = self->regionWidth;
+	copy->regionHeight = self->regionHeight;
+	copy->regionOriginalWidth = self->regionOriginalWidth;
+	copy->regionOriginalHeight = self->regionOriginalHeight;
+	MALLOC_STR(copy->path, self->path);
+	spColor_setFromColor(&copy->color, &self->color);
+
+	spVertexAttachment_copyTo(SUPER(self), SUPER(copy));
+	copy->regionUVs = MALLOC(float, SUPER(self)->worldVerticesLength);
+	memcpy(copy->regionUVs, self->regionUVs, SUPER(self)->worldVerticesLength * sizeof(float));
+	copy->uvs = MALLOC(float, SUPER(self)->worldVerticesLength);
+	memcpy(copy->uvs, self->uvs, SUPER(self)->worldVerticesLength * sizeof(float));
+	copy->trianglesCount = self->trianglesCount;
+	copy->triangles = MALLOC(unsigned short, self->trianglesCount);
+	memcpy(copy->triangles, self->triangles, self->trianglesCount * sizeof(short));
+	copy->hullLength = self->hullLength;
+	if (self->edgesCount > 0) {
+		copy->edgesCount = self->edgesCount;
+		copy->edges = MALLOC(int, self->edgesCount);
+		memcpy(copy->edges, self->edges, self->edgesCount * sizeof(int));
+	}
+	copy->width = self->width;
+	copy->height = self->height;
+
+	return SUPER(SUPER(copy));
+}
+
+spMeshAttachment *spMeshAttachment_newLinkedMesh(spMeshAttachment *self) {
+	spMeshAttachment *copy = spMeshAttachment_create(self->super.super.name);
+
+	copy->rendererObject = self->rendererObject;
+	copy->regionU = self->regionU;
+	copy->regionV = self->regionV;
+	copy->regionU2 = self->regionU2;
+	copy->regionV2 = self->regionV2;
+	copy->regionDegrees = self->regionDegrees;
+	copy->regionOffsetX = self->regionOffsetX;
+	copy->regionOffsetY = self->regionOffsetY;
+	copy->regionWidth = self->regionWidth;
+	copy->regionHeight = self->regionHeight;
+	copy->regionOriginalWidth = self->regionOriginalWidth;
+	copy->regionOriginalHeight = self->regionOriginalHeight;
+	MALLOC_STR(copy->path, self->path);
+	spColor_setFromColor(&copy->color, &self->color);
+	copy->super.deformAttachment = self->super.deformAttachment;
+	spMeshAttachment_setParentMesh(copy, self->parentMesh ? self->parentMesh : self);
+	spMeshAttachment_updateUVs(copy);
+	return copy;
+}
+
+spMeshAttachment *spMeshAttachment_create(const char *name) {
+	spMeshAttachment *self = NEW(spMeshAttachment);
+	_spVertexAttachment_init(SUPER(self));
+	spColor_setFromFloats(&self->color, 1, 1, 1, 1);
+	_spAttachment_init(SUPER(SUPER(self)), name, SP_ATTACHMENT_MESH, _spMeshAttachment_dispose, _spMeshAttachment_copy);
+	return self;
+}
+
+void spMeshAttachment_updateUVs(spMeshAttachment *self) {
+	int i, n;
+	float *uvs;
+	float u, v, width, height;
+	int verticesLength = SUPER(self)->worldVerticesLength;
+	FREE(self->uvs);
+	uvs = self->uvs = MALLOC(float, verticesLength);
+	n = verticesLength;
+	u = self->regionU;
+	v = self->regionV;
+
+	switch (self->regionDegrees) {
+		case 90: {
+			float textureWidth = self->regionHeight / (self->regionU2 - self->regionU);
+			float textureHeight = self->regionWidth / (self->regionV2 - self->regionV);
+			u -= (self->regionOriginalHeight - self->regionOffsetY - self->regionHeight) / textureWidth;
+			v -= (self->regionOriginalWidth - self->regionOffsetX - self->regionWidth) / textureHeight;
+			width = self->regionOriginalHeight / textureWidth;
+			height = self->regionOriginalWidth / textureHeight;
+			for (i = 0; i < n; i += 2) {
+				uvs[i] = u + self->regionUVs[i + 1] * width;
+				uvs[i + 1] = v + (1 - self->regionUVs[i]) * height;
+			}
+			return;
+		}
+		case 180: {
+			float textureWidth = self->regionWidth / (self->regionU2 - self->regionU);
+			float textureHeight = self->regionHeight / (self->regionV2 - self->regionV);
+			u -= (self->regionOriginalWidth - self->regionOffsetX - self->regionWidth) / textureWidth;
+			v -= self->regionOffsetY / textureHeight;
+			width = self->regionOriginalWidth / textureWidth;
+			height = self->regionOriginalHeight / textureHeight;
+			for (i = 0; i < n; i += 2) {
+				uvs[i] = u + (1 - self->regionUVs[i]) * width;
+				uvs[i + 1] = v + (1 - self->regionUVs[i + 1]) * height;
+			}
+			return;
+		}
+		case 270: {
+			float textureHeight = self->regionHeight / (self->regionV2 - self->regionV);
+			float textureWidth = self->regionWidth / (self->regionU2 - self->regionU);
+			u -= self->regionOffsetY / textureWidth;
+			v -= self->regionOffsetX / textureHeight;
+			width = self->regionOriginalHeight / textureWidth;
+			height = self->regionOriginalWidth / textureHeight;
+			for (i = 0; i < n; i += 2) {
+				uvs[i] = u + (1 - self->regionUVs[i + 1]) * width;
+				uvs[i + 1] = v + self->regionUVs[i] * height;
+			}
+			return;
+		}
+		default: {
+			float textureWidth = self->regionWidth / (self->regionU2 - self->regionU);
+			float textureHeight = self->regionHeight / (self->regionV2 - self->regionV);
+			u -= self->regionOffsetX / textureWidth;
+			v -= (self->regionOriginalHeight - self->regionOffsetY - self->regionHeight) / textureHeight;
+			width = self->regionOriginalWidth / textureWidth;
+			height = self->regionOriginalHeight / textureHeight;
+			for (i = 0; i < n; i += 2) {
+				uvs[i] = u + self->regionUVs[i] * width;
+				uvs[i + 1] = v + self->regionUVs[i + 1] * height;
+			}
+		}
+	}
+}
+
+void spMeshAttachment_setParentMesh(spMeshAttachment *self, spMeshAttachment *parentMesh) {
+	CONST_CAST(spMeshAttachment *, self->parentMesh) = parentMesh;
+	if (parentMesh) {
+		self->super.bones = parentMesh->super.bones;
+		self->super.bonesCount = parentMesh->super.bonesCount;
+
+		self->super.vertices = parentMesh->super.vertices;
+		self->super.verticesCount = parentMesh->super.verticesCount;
+
+		self->regionUVs = parentMesh->regionUVs;
+
+		self->triangles = parentMesh->triangles;
+		self->trianglesCount = parentMesh->trianglesCount;
+
+		self->hullLength = parentMesh->hullLength;
+
+		self->super.worldVerticesLength = parentMesh->super.worldVerticesLength;
+
+		self->edges = parentMesh->edges;
+		self->edgesCount = parentMesh->edgesCount;
+
+		self->width = parentMesh->width;
+		self->height = parentMesh->height;
+	}
+}

+ 59 - 59
spine-c/spine-c/src/spine/PathAttachment.c

@@ -1,59 +1,59 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/PathAttachment.h>
-#include <spine/extension.h>
-
-void _spPathAttachment_dispose(spAttachment *attachment) {
-	spPathAttachment *self = SUB_CAST(spPathAttachment, attachment);
-
-	_spVertexAttachment_deinit(SUPER(self));
-
-	FREE(self->lengths);
-	FREE(self);
-}
-
-spAttachment *_spPathAttachment_copy(spAttachment *attachment) {
-	spPathAttachment *copy = spPathAttachment_create(attachment->name);
-	spPathAttachment *self = SUB_CAST(spPathAttachment, attachment);
-	spVertexAttachment_copyTo(SUPER(self), SUPER(copy));
-	copy->lengthsLength = self->lengthsLength;
-	copy->lengths = MALLOC(float, self->lengthsLength);
-	memcpy(copy->lengths, self->lengths, self->lengthsLength * sizeof(float));
-	copy->closed = self->closed;
-	copy->constantSpeed = self->constantSpeed;
-	return SUPER(SUPER(copy));
-}
-
-spPathAttachment *spPathAttachment_create(const char *name) {
-	spPathAttachment *self = NEW(spPathAttachment);
-	_spVertexAttachment_init(SUPER(self));
-	_spAttachment_init(SUPER(SUPER(self)), name, SP_ATTACHMENT_PATH, _spPathAttachment_dispose, _spPathAttachment_copy);
-	return self;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/PathAttachment.h>
+#include <spine/extension.h>
+
+void _spPathAttachment_dispose(spAttachment *attachment) {
+	spPathAttachment *self = SUB_CAST(spPathAttachment, attachment);
+
+	_spVertexAttachment_deinit(SUPER(self));
+
+	FREE(self->lengths);
+	FREE(self);
+}
+
+spAttachment *_spPathAttachment_copy(spAttachment *attachment) {
+	spPathAttachment *copy = spPathAttachment_create(attachment->name);
+	spPathAttachment *self = SUB_CAST(spPathAttachment, attachment);
+	spVertexAttachment_copyTo(SUPER(self), SUPER(copy));
+	copy->lengthsLength = self->lengthsLength;
+	copy->lengths = MALLOC(float, self->lengthsLength);
+	memcpy(copy->lengths, self->lengths, self->lengthsLength * sizeof(float));
+	copy->closed = self->closed;
+	copy->constantSpeed = self->constantSpeed;
+	return SUPER(SUPER(copy));
+}
+
+spPathAttachment *spPathAttachment_create(const char *name) {
+	spPathAttachment *self = NEW(spPathAttachment);
+	_spVertexAttachment_init(SUPER(self));
+	_spAttachment_init(SUPER(SUPER(self)), name, SP_ATTACHMENT_PATH, _spPathAttachment_dispose, _spPathAttachment_copy);
+	return self;
+}

+ 527 - 527
spine-c/spine-c/src/spine/PathConstraint.c

@@ -1,527 +1,527 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/PathConstraint.h>
-#include <spine/Skeleton.h>
-#include <spine/extension.h>
-
-#define PATHCONSTRAINT_NONE -1
-#define PATHCONSTRAINT_BEFORE -2
-#define PATHCONSTRAINT_AFTER -3
-#define EPSILON 0.00001f
-
-spPathConstraint *spPathConstraint_create(spPathConstraintData *data, const spSkeleton *skeleton) {
-	int i;
-	spPathConstraint *self = NEW(spPathConstraint);
-	CONST_CAST(spPathConstraintData*, self->data) = data;
-	self->bonesCount = data->bonesCount;
-	CONST_CAST(spBone**, self->bones) = MALLOC(spBone*, self->bonesCount);
-	for (i = 0; i < self->bonesCount; ++i)
-		self->bones[i] = spSkeleton_findBone(skeleton, self->data->bones[i]->name);
-	self->target = spSkeleton_findSlot(skeleton, self->data->target->name);
-	self->position = data->position;
-	self->spacing = data->spacing;
-	self->mixRotate = data->mixRotate;
-	self->mixX = data->mixX;
-	self->mixY = data->mixY;
-	self->spacesCount = 0;
-	self->spaces = 0;
-	self->positionsCount = 0;
-	self->positions = 0;
-	self->worldCount = 0;
-	self->world = 0;
-	self->curvesCount = 0;
-	self->curves = 0;
-	self->lengthsCount = 0;
-	self->lengths = 0;
-	return self;
-}
-
-void spPathConstraint_dispose(spPathConstraint *self) {
-	FREE(self->bones);
-	FREE(self->spaces);
-	if (self->positions) FREE(self->positions);
-	if (self->world) FREE(self->world);
-	if (self->curves) FREE(self->curves);
-	if (self->lengths) FREE(self->lengths);
-	FREE(self);
-}
-
-void spPathConstraint_update(spPathConstraint *self) {
-	int i, p, n;
-	float length, setupLength, x, y, dx, dy, s, sum;
-	float *spaces, *lengths, *positions;
-	float spacing;
-	float boneX, boneY, offsetRotation;
-	int/*bool*/tip;
-	float mixRotate = self->mixRotate, mixX = self->mixX, mixY = self->mixY;
-	int lengthSpacing;
-	spPathAttachment *attachment = (spPathAttachment *) self->target->attachment;
-	spPathConstraintData *data = self->data;
-	int tangents = data->rotateMode == SP_ROTATE_MODE_TANGENT, scale = data->rotateMode == SP_ROTATE_MODE_CHAIN_SCALE;
-	int boneCount = self->bonesCount, spacesCount = tangents ? boneCount : boneCount + 1;
-	spBone **bones = self->bones;
-	spBone *pa;
-
-	if (mixRotate == 0 && mixX == 0 && mixY == 0) return;
-	if ((attachment == 0) || (attachment->super.super.type != SP_ATTACHMENT_PATH)) return;
-
-	if (self->spacesCount != spacesCount) {
-		if (self->spaces) FREE(self->spaces);
-		self->spaces = MALLOC(float, spacesCount);
-		self->spacesCount = spacesCount;
-	}
-	spaces = self->spaces;
-	spaces[0] = 0;
-	lengths = 0;
-	spacing = self->spacing;
-
-	if (scale) {
-		if (self->lengthsCount != boneCount) {
-			if (self->lengths) FREE(self->lengths);
-			self->lengths = MALLOC(float, boneCount);
-			self->lengthsCount = boneCount;
-		}
-		lengths = self->lengths;
-	}
-
-	switch (data->spacingMode) {
-		case SP_SPACING_MODE_PERCENT:
-			if (scale) {
-				for (i = 0, n = spacesCount - 1; i < n; i++) {
-					spBone *bone = bones[i];
-					setupLength = bone->data->length;
-					if (setupLength < EPSILON)
-						lengths[i] = 0;
-					else {
-						x = setupLength * bone->a;
-						y = setupLength * bone->c;
-						lengths[i] = SQRT(x * x + y * y);
-					}
-				}
-			}
-			for (i = 1, n = spacesCount; i < n; i++) spaces[i] = spacing;
-			break;
-		case SP_SPACING_MODE_PROPORTIONAL:
-			sum = 0;
-			for (i = 0; i < boneCount;) {
-				spBone *bone = bones[i];
-				setupLength = bone->data->length;
-				if (setupLength < EPSILON) {
-					if (scale) lengths[i] = 0;
-					spaces[++i] = spacing;
-				} else {
-					x = setupLength * bone->a, y = setupLength * bone->c;
-					length = SQRT(x * x + y * y);
-					if (scale) lengths[i] = length;
-					spaces[++i] = length;
-					sum += length;
-				}
-			}
-			if (sum > 0) {
-				sum = spacesCount / sum * spacing;
-				for (i = 1; i < spacesCount; i++)
-					spaces[i] *= sum;
-			}
-			break;
-		default:
-			lengthSpacing = data->spacingMode == SP_SPACING_MODE_LENGTH;
-			for (i = 0, n = spacesCount - 1; i < n;) {
-				spBone *bone = bones[i];
-				setupLength = bone->data->length;
-				if (setupLength < EPSILON) {
-					if (scale) lengths[i] = 0;
-					spaces[++i] = spacing;
-				} else {
-					x = setupLength * bone->a, y = setupLength * bone->c;
-					length = SQRT(x * x + y * y);
-					if (scale) lengths[i] = length;
-					spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
-				}
-			}
-	}
-
-	positions = spPathConstraint_computeWorldPositions(self, attachment, spacesCount, tangents);
-	boneX = positions[0], boneY = positions[1], offsetRotation = self->data->offsetRotation;
-	tip = 0;
-	if (offsetRotation == 0)
-		tip = data->rotateMode == SP_ROTATE_MODE_CHAIN;
-	else {
-		tip = 0;
-		pa = self->target->bone;
-		offsetRotation *= pa->a * pa->d - pa->b * pa->c > 0 ? DEG_RAD : -DEG_RAD;
-	}
-	for (i = 0, p = 3; i < boneCount; i++, p += 3) {
-		spBone *bone = bones[i];
-		CONST_CAST(float, bone->worldX) += (boneX - bone->worldX) * mixX;
-		CONST_CAST(float, bone->worldY) += (boneY - bone->worldY) * mixY;
-		x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
-		if (scale) {
-			length = lengths[i];
-			if (length != 0) {
-				s = (SQRT(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
-				CONST_CAST(float, bone->a) *= s;
-				CONST_CAST(float, bone->c) *= s;
-			}
-		}
-		boneX = x;
-		boneY = y;
-		if (mixRotate > 0) {
-			float a = bone->a, b = bone->b, c = bone->c, d = bone->d, r, cosine, sine;
-			if (tangents)
-				r = positions[p - 1];
-			else if (spaces[i + 1] == 0)
-				r = positions[p + 2];
-			else
-				r = ATAN2(dy, dx);
-			r -= ATAN2(c, a) - offsetRotation * DEG_RAD;
-			if (tip) {
-				cosine = COS(r);
-				sine = SIN(r);
-				length = bone->data->length;
-				boneX += (length * (cosine * a - sine * c) - dx) * mixRotate;
-				boneY += (length * (sine * a + cosine * c) - dy) * mixRotate;
-			} else
-				r += offsetRotation;
-			if (r > PI)
-				r -= PI2;
-			else if (r < -PI)
-				r += PI2;
-			r *= mixRotate;
-			cosine = COS(r);
-			sine = SIN(r);
-			CONST_CAST(float, bone->a) = cosine * a - sine * c;
-			CONST_CAST(float, bone->b) = cosine * b - sine * d;
-			CONST_CAST(float, bone->c) = sine * a + cosine * c;
-			CONST_CAST(float, bone->d) = sine * b + cosine * d;
-		}
-		spBone_updateAppliedTransform(bone);
-	}
-}
-
-static void _addBeforePosition(float p, float *temp, int i, float *out, int o) {
-	float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = ATAN2(dy, dx);
-	out[o] = x1 + p * COS(r);
-	out[o + 1] = y1 + p * SIN(r);
-	out[o + 2] = r;
-}
-
-static void _addAfterPosition(float p, float *temp, int i, float *out, int o) {
-	float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = ATAN2(dy, dx);
-	out[o] = x1 + p * COS(r);
-	out[o + 1] = y1 + p * SIN(r);
-	out[o + 2] = r;
-}
-
-static void
-_addCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
-				  float *out, int o, int/*bool*/tangents) {
-	float tt, ttt, u, uu, uuu;
-	float ut, ut3, uut3, utt3;
-	float x, y;
-	if (p == 0 || ISNAN(p)) {
-		out[o] = x1;
-		out[o + 1] = y1;
-		out[o + 2] = ATAN2(cy1 - y1, cx1 - x1);
-		return;
-	}
-	tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
-	ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
-	x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
-	out[o] = x;
-	out[o + 1] = y;
-	if (tangents) {
-		if (p < 0.001)
-			out[o + 2] = ATAN2(cy1 - y1, cx1 - x1);
-		else
-			out[o + 2] = ATAN2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
-	}
-}
-
-float *spPathConstraint_computeWorldPositions(spPathConstraint *self, spPathAttachment *path, int spacesCount,
-											  int/*bool*/ tangents) {
-	int i, o, w, curve, segment, /*bool*/closed, verticesLength, curveCount, prevCurve;
-	float *out, *curves, *segments;
-	float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy, pathLength, curveLength, p;
-	float x1, y1, cx1, cy1, cx2, cy2, x2, y2, multiplier;
-	spSlot *target = self->target;
-	float position = self->position;
-	float *spaces = self->spaces, *world = 0;
-	if (self->positionsCount != spacesCount * 3 + 2) {
-		if (self->positions) FREE(self->positions);
-		self->positions = MALLOC(float, spacesCount * 3 + 2);
-		self->positionsCount = spacesCount * 3 + 2;
-	}
-	out = self->positions;
-	closed = path->closed;
-	verticesLength = path->super.worldVerticesLength, curveCount = verticesLength / 6, prevCurve = PATHCONSTRAINT_NONE;
-
-	if (!path->constantSpeed) {
-		float *lengths = path->lengths;
-		curveCount -= closed ? 1 : 2;
-		pathLength = lengths[curveCount];
-		if (self->data->positionMode == SP_POSITION_MODE_PERCENT) position += pathLength;
-		switch (self->data->spacingMode) {
-			case SP_SPACING_MODE_PERCENT:
-				multiplier = pathLength;
-				break;
-			case SP_SPACING_MODE_PROPORTIONAL:
-				multiplier = pathLength / spacesCount;
-				break;
-			default:
-				multiplier = 1;
-		}
-
-		if (self->worldCount != 8) {
-			if (self->world) FREE(self->world);
-			self->world = MALLOC(float, 8);
-			self->worldCount = 8;
-		}
-		world = self->world;
-		for (i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
-			float space = spaces[i] * multiplier;
-			position += space;
-			p = position;
-
-			if (closed) {
-				p = FMOD(p, pathLength);
-				if (p < 0) p += pathLength;
-				curve = 0;
-			} else if (p < 0) {
-				if (prevCurve != PATHCONSTRAINT_BEFORE) {
-					prevCurve = PATHCONSTRAINT_BEFORE;
-					spVertexAttachment_computeWorldVertices(SUPER(path), target, 2, 4, world, 0, 2);
-				}
-				_addBeforePosition(p, world, 0, out, o);
-				continue;
-			} else if (p > pathLength) {
-				if (prevCurve != PATHCONSTRAINT_AFTER) {
-					prevCurve = PATHCONSTRAINT_AFTER;
-					spVertexAttachment_computeWorldVertices(SUPER(path), target, verticesLength - 6, 4, world, 0, 2);
-				}
-				_addAfterPosition(p - pathLength, world, 0, out, o);
-				continue;
-			}
-
-			/* Determine curve containing position. */
-			for (;; curve++) {
-				float length = lengths[curve];
-				if (p > length) continue;
-				if (curve == 0)
-					p /= length;
-				else {
-					float prev = lengths[curve - 1];
-					p = (p - prev) / (length - prev);
-				}
-				break;
-			}
-			if (curve != prevCurve) {
-				prevCurve = curve;
-				if (closed && curve == curveCount) {
-					spVertexAttachment_computeWorldVertices(SUPER(path), target, verticesLength - 4, 4, world, 0, 2);
-					spVertexAttachment_computeWorldVertices(SUPER(path), target, 0, 4, world, 4, 2);
-				} else
-					spVertexAttachment_computeWorldVertices(SUPER(path), target, curve * 6 + 2, 8, world, 0, 2);
-			}
-			_addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
-							  tangents || (i > 0 && space == 0));
-		}
-		return out;
-	}
-
-	/* World vertices. */
-	if (closed) {
-		verticesLength += 2;
-		if (self->worldCount != verticesLength) {
-			if (self->world) FREE(self->world);
-			self->world = MALLOC(float, verticesLength);
-			self->worldCount = verticesLength;
-		}
-		world = self->world;
-		spVertexAttachment_computeWorldVertices(SUPER(path), target, 2, verticesLength - 4, world, 0, 2);
-		spVertexAttachment_computeWorldVertices(SUPER(path), target, 0, 2, world, verticesLength - 4, 2);
-		world[verticesLength - 2] = world[0];
-		world[verticesLength - 1] = world[1];
-	} else {
-		curveCount--;
-		verticesLength -= 4;
-		if (self->worldCount != verticesLength) {
-			if (self->world) FREE(self->world);
-			self->world = MALLOC(float, verticesLength);
-			self->worldCount = verticesLength;
-		}
-		world = self->world;
-		spVertexAttachment_computeWorldVertices(SUPER(path), target, 2, verticesLength, world, 0, 2);
-	}
-
-	/* Curve lengths. */
-	if (self->curvesCount != curveCount) {
-		if (self->curves) FREE(self->curves);
-		self->curves = MALLOC(float, curveCount);
-		self->curvesCount = curveCount;
-	}
-	curves = self->curves;
-	pathLength = 0;
-	x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
-	for (i = 0, w = 2; i < curveCount; i++, w += 6) {
-		cx1 = world[w];
-		cy1 = world[w + 1];
-		cx2 = world[w + 2];
-		cy2 = world[w + 3];
-		x2 = world[w + 4];
-		y2 = world[w + 5];
-		tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f;
-		tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f;
-		dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f;
-		dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f;
-		ddfx = tmpx * 2 + dddfx;
-		ddfy = tmpy * 2 + dddfy;
-		dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f;
-		dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f;
-		pathLength += SQRT(dfx * dfx + dfy * dfy);
-		dfx += ddfx;
-		dfy += ddfy;
-		ddfx += dddfx;
-		ddfy += dddfy;
-		pathLength += SQRT(dfx * dfx + dfy * dfy);
-		dfx += ddfx;
-		dfy += ddfy;
-		pathLength += SQRT(dfx * dfx + dfy * dfy);
-		dfx += ddfx + dddfx;
-		dfy += ddfy + dddfy;
-		pathLength += SQRT(dfx * dfx + dfy * dfy);
-		curves[i] = pathLength;
-		x1 = x2;
-		y1 = y2;
-	}
-
-	if (self->data->positionMode == SP_POSITION_MODE_PERCENT) position *= pathLength;
-
-	switch (self->data->spacingMode) {
-		case SP_SPACING_MODE_PERCENT:
-			multiplier = pathLength;
-			break;
-		case SP_SPACING_MODE_PROPORTIONAL:
-			multiplier = pathLength / spacesCount;
-			break;
-		default:
-			multiplier = 1;
-	}
-
-	segments = self->segments;
-	curveLength = 0;
-	for (i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
-		float space = spaces[i] * multiplier;
-		position += space;
-		p = position;
-
-		if (closed) {
-			p = FMOD(p, pathLength);
-			if (p < 0) p += pathLength;
-			curve = 0;
-		} else if (p < 0) {
-			_addBeforePosition(p, world, 0, out, o);
-			continue;
-		} else if (p > pathLength) {
-			_addAfterPosition(p - pathLength, world, verticesLength - 4, out, o);
-			continue;
-		}
-
-		/* Determine curve containing position. */
-		for (;; curve++) {
-			float length = curves[curve];
-			if (p > length) continue;
-			if (curve == 0)
-				p /= length;
-			else {
-				float prev = curves[curve - 1];
-				p = (p - prev) / (length - prev);
-			}
-			break;
-		}
-
-		/* Curve segment lengths. */
-		if (curve != prevCurve) {
-			int ii;
-			prevCurve = curve;
-			ii = curve * 6;
-			x1 = world[ii];
-			y1 = world[ii + 1];
-			cx1 = world[ii + 2];
-			cy1 = world[ii + 3];
-			cx2 = world[ii + 4];
-			cy2 = world[ii + 5];
-			x2 = world[ii + 6];
-			y2 = world[ii + 7];
-			tmpx = (x1 - cx1 * 2 + cx2) * 0.03f;
-			tmpy = (y1 - cy1 * 2 + cy2) * 0.03f;
-			dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f;
-			dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f;
-			ddfx = tmpx * 2 + dddfx;
-			ddfy = tmpy * 2 + dddfy;
-			dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f;
-			dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f;
-			curveLength = SQRT(dfx * dfx + dfy * dfy);
-			segments[0] = curveLength;
-			for (ii = 1; ii < 8; ii++) {
-				dfx += ddfx;
-				dfy += ddfy;
-				ddfx += dddfx;
-				ddfy += dddfy;
-				curveLength += SQRT(dfx * dfx + dfy * dfy);
-				segments[ii] = curveLength;
-			}
-			dfx += ddfx;
-			dfy += ddfy;
-			curveLength += SQRT(dfx * dfx + dfy * dfy);
-			segments[8] = curveLength;
-			dfx += ddfx + dddfx;
-			dfy += ddfy + dddfy;
-			curveLength += SQRT(dfx * dfx + dfy * dfy);
-			segments[9] = curveLength;
-			segment = 0;
-		}
-
-		/* Weight by segment length. */
-		p *= curveLength;
-		for (;; segment++) {
-			float length = segments[segment];
-			if (p > length) continue;
-			if (segment == 0)
-				p /= length;
-			else {
-				float prev = segments[segment - 1];
-				p = segment + (p - prev) / (length - prev);
-			}
-			break;
-		}
-		_addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0));
-	}
-	return out;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/PathConstraint.h>
+#include <spine/Skeleton.h>
+#include <spine/extension.h>
+
+#define PATHCONSTRAINT_NONE -1
+#define PATHCONSTRAINT_BEFORE -2
+#define PATHCONSTRAINT_AFTER -3
+#define EPSILON 0.00001f
+
+spPathConstraint *spPathConstraint_create(spPathConstraintData *data, const spSkeleton *skeleton) {
+	int i;
+	spPathConstraint *self = NEW(spPathConstraint);
+	CONST_CAST(spPathConstraintData *, self->data) = data;
+	self->bonesCount = data->bonesCount;
+	CONST_CAST(spBone **, self->bones) = MALLOC(spBone *, self->bonesCount);
+	for (i = 0; i < self->bonesCount; ++i)
+		self->bones[i] = spSkeleton_findBone(skeleton, self->data->bones[i]->name);
+	self->target = spSkeleton_findSlot(skeleton, self->data->target->name);
+	self->position = data->position;
+	self->spacing = data->spacing;
+	self->mixRotate = data->mixRotate;
+	self->mixX = data->mixX;
+	self->mixY = data->mixY;
+	self->spacesCount = 0;
+	self->spaces = 0;
+	self->positionsCount = 0;
+	self->positions = 0;
+	self->worldCount = 0;
+	self->world = 0;
+	self->curvesCount = 0;
+	self->curves = 0;
+	self->lengthsCount = 0;
+	self->lengths = 0;
+	return self;
+}
+
+void spPathConstraint_dispose(spPathConstraint *self) {
+	FREE(self->bones);
+	FREE(self->spaces);
+	if (self->positions) FREE(self->positions);
+	if (self->world) FREE(self->world);
+	if (self->curves) FREE(self->curves);
+	if (self->lengths) FREE(self->lengths);
+	FREE(self);
+}
+
+void spPathConstraint_update(spPathConstraint *self) {
+	int i, p, n;
+	float length, setupLength, x, y, dx, dy, s, sum;
+	float *spaces, *lengths, *positions;
+	float spacing;
+	float boneX, boneY, offsetRotation;
+	int /*bool*/ tip;
+	float mixRotate = self->mixRotate, mixX = self->mixX, mixY = self->mixY;
+	int lengthSpacing;
+	spPathAttachment *attachment = (spPathAttachment *) self->target->attachment;
+	spPathConstraintData *data = self->data;
+	int tangents = data->rotateMode == SP_ROTATE_MODE_TANGENT, scale = data->rotateMode == SP_ROTATE_MODE_CHAIN_SCALE;
+	int boneCount = self->bonesCount, spacesCount = tangents ? boneCount : boneCount + 1;
+	spBone **bones = self->bones;
+	spBone *pa;
+
+	if (mixRotate == 0 && mixX == 0 && mixY == 0) return;
+	if ((attachment == 0) || (attachment->super.super.type != SP_ATTACHMENT_PATH)) return;
+
+	if (self->spacesCount != spacesCount) {
+		if (self->spaces) FREE(self->spaces);
+		self->spaces = MALLOC(float, spacesCount);
+		self->spacesCount = spacesCount;
+	}
+	spaces = self->spaces;
+	spaces[0] = 0;
+	lengths = 0;
+	spacing = self->spacing;
+
+	if (scale) {
+		if (self->lengthsCount != boneCount) {
+			if (self->lengths) FREE(self->lengths);
+			self->lengths = MALLOC(float, boneCount);
+			self->lengthsCount = boneCount;
+		}
+		lengths = self->lengths;
+	}
+
+	switch (data->spacingMode) {
+		case SP_SPACING_MODE_PERCENT:
+			if (scale) {
+				for (i = 0, n = spacesCount - 1; i < n; i++) {
+					spBone *bone = bones[i];
+					setupLength = bone->data->length;
+					if (setupLength < EPSILON)
+						lengths[i] = 0;
+					else {
+						x = setupLength * bone->a;
+						y = setupLength * bone->c;
+						lengths[i] = SQRT(x * x + y * y);
+					}
+				}
+			}
+			for (i = 1, n = spacesCount; i < n; i++) spaces[i] = spacing;
+			break;
+		case SP_SPACING_MODE_PROPORTIONAL:
+			sum = 0;
+			for (i = 0; i < boneCount;) {
+				spBone *bone = bones[i];
+				setupLength = bone->data->length;
+				if (setupLength < EPSILON) {
+					if (scale) lengths[i] = 0;
+					spaces[++i] = spacing;
+				} else {
+					x = setupLength * bone->a, y = setupLength * bone->c;
+					length = SQRT(x * x + y * y);
+					if (scale) lengths[i] = length;
+					spaces[++i] = length;
+					sum += length;
+				}
+			}
+			if (sum > 0) {
+				sum = spacesCount / sum * spacing;
+				for (i = 1; i < spacesCount; i++)
+					spaces[i] *= sum;
+			}
+			break;
+		default:
+			lengthSpacing = data->spacingMode == SP_SPACING_MODE_LENGTH;
+			for (i = 0, n = spacesCount - 1; i < n;) {
+				spBone *bone = bones[i];
+				setupLength = bone->data->length;
+				if (setupLength < EPSILON) {
+					if (scale) lengths[i] = 0;
+					spaces[++i] = spacing;
+				} else {
+					x = setupLength * bone->a, y = setupLength * bone->c;
+					length = SQRT(x * x + y * y);
+					if (scale) lengths[i] = length;
+					spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
+				}
+			}
+	}
+
+	positions = spPathConstraint_computeWorldPositions(self, attachment, spacesCount, tangents);
+	boneX = positions[0], boneY = positions[1], offsetRotation = self->data->offsetRotation;
+	tip = 0;
+	if (offsetRotation == 0)
+		tip = data->rotateMode == SP_ROTATE_MODE_CHAIN;
+	else {
+		tip = 0;
+		pa = self->target->bone;
+		offsetRotation *= pa->a * pa->d - pa->b * pa->c > 0 ? DEG_RAD : -DEG_RAD;
+	}
+	for (i = 0, p = 3; i < boneCount; i++, p += 3) {
+		spBone *bone = bones[i];
+		CONST_CAST(float, bone->worldX) += (boneX - bone->worldX) * mixX;
+		CONST_CAST(float, bone->worldY) += (boneY - bone->worldY) * mixY;
+		x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
+		if (scale) {
+			length = lengths[i];
+			if (length != 0) {
+				s = (SQRT(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
+				CONST_CAST(float, bone->a) *= s;
+				CONST_CAST(float, bone->c) *= s;
+			}
+		}
+		boneX = x;
+		boneY = y;
+		if (mixRotate > 0) {
+			float a = bone->a, b = bone->b, c = bone->c, d = bone->d, r, cosine, sine;
+			if (tangents)
+				r = positions[p - 1];
+			else if (spaces[i + 1] == 0)
+				r = positions[p + 2];
+			else
+				r = ATAN2(dy, dx);
+			r -= ATAN2(c, a) - offsetRotation * DEG_RAD;
+			if (tip) {
+				cosine = COS(r);
+				sine = SIN(r);
+				length = bone->data->length;
+				boneX += (length * (cosine * a - sine * c) - dx) * mixRotate;
+				boneY += (length * (sine * a + cosine * c) - dy) * mixRotate;
+			} else
+				r += offsetRotation;
+			if (r > PI)
+				r -= PI2;
+			else if (r < -PI)
+				r += PI2;
+			r *= mixRotate;
+			cosine = COS(r);
+			sine = SIN(r);
+			CONST_CAST(float, bone->a) = cosine * a - sine * c;
+			CONST_CAST(float, bone->b) = cosine * b - sine * d;
+			CONST_CAST(float, bone->c) = sine * a + cosine * c;
+			CONST_CAST(float, bone->d) = sine * b + cosine * d;
+		}
+		spBone_updateAppliedTransform(bone);
+	}
+}
+
+static void _addBeforePosition(float p, float *temp, int i, float *out, int o) {
+	float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = ATAN2(dy, dx);
+	out[o] = x1 + p * COS(r);
+	out[o + 1] = y1 + p * SIN(r);
+	out[o + 2] = r;
+}
+
+static void _addAfterPosition(float p, float *temp, int i, float *out, int o) {
+	float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = ATAN2(dy, dx);
+	out[o] = x1 + p * COS(r);
+	out[o + 1] = y1 + p * SIN(r);
+	out[o + 2] = r;
+}
+
+static void
+_addCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
+				  float *out, int o, int /*bool*/ tangents) {
+	float tt, ttt, u, uu, uuu;
+	float ut, ut3, uut3, utt3;
+	float x, y;
+	if (p == 0 || ISNAN(p)) {
+		out[o] = x1;
+		out[o + 1] = y1;
+		out[o + 2] = ATAN2(cy1 - y1, cx1 - x1);
+		return;
+	}
+	tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
+	ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
+	x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
+	out[o] = x;
+	out[o + 1] = y;
+	if (tangents) {
+		if (p < 0.001)
+			out[o + 2] = ATAN2(cy1 - y1, cx1 - x1);
+		else
+			out[o + 2] = ATAN2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
+	}
+}
+
+float *spPathConstraint_computeWorldPositions(spPathConstraint *self, spPathAttachment *path, int spacesCount,
+											  int /*bool*/ tangents) {
+	int i, o, w, curve, segment, /*bool*/ closed, verticesLength, curveCount, prevCurve;
+	float *out, *curves, *segments;
+	float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy, pathLength, curveLength, p;
+	float x1, y1, cx1, cy1, cx2, cy2, x2, y2, multiplier;
+	spSlot *target = self->target;
+	float position = self->position;
+	float *spaces = self->spaces, *world = 0;
+	if (self->positionsCount != spacesCount * 3 + 2) {
+		if (self->positions) FREE(self->positions);
+		self->positions = MALLOC(float, spacesCount * 3 + 2);
+		self->positionsCount = spacesCount * 3 + 2;
+	}
+	out = self->positions;
+	closed = path->closed;
+	verticesLength = path->super.worldVerticesLength, curveCount = verticesLength / 6, prevCurve = PATHCONSTRAINT_NONE;
+
+	if (!path->constantSpeed) {
+		float *lengths = path->lengths;
+		curveCount -= closed ? 1 : 2;
+		pathLength = lengths[curveCount];
+		if (self->data->positionMode == SP_POSITION_MODE_PERCENT) position += pathLength;
+		switch (self->data->spacingMode) {
+			case SP_SPACING_MODE_PERCENT:
+				multiplier = pathLength;
+				break;
+			case SP_SPACING_MODE_PROPORTIONAL:
+				multiplier = pathLength / spacesCount;
+				break;
+			default:
+				multiplier = 1;
+		}
+
+		if (self->worldCount != 8) {
+			if (self->world) FREE(self->world);
+			self->world = MALLOC(float, 8);
+			self->worldCount = 8;
+		}
+		world = self->world;
+		for (i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
+			float space = spaces[i] * multiplier;
+			position += space;
+			p = position;
+
+			if (closed) {
+				p = FMOD(p, pathLength);
+				if (p < 0) p += pathLength;
+				curve = 0;
+			} else if (p < 0) {
+				if (prevCurve != PATHCONSTRAINT_BEFORE) {
+					prevCurve = PATHCONSTRAINT_BEFORE;
+					spVertexAttachment_computeWorldVertices(SUPER(path), target, 2, 4, world, 0, 2);
+				}
+				_addBeforePosition(p, world, 0, out, o);
+				continue;
+			} else if (p > pathLength) {
+				if (prevCurve != PATHCONSTRAINT_AFTER) {
+					prevCurve = PATHCONSTRAINT_AFTER;
+					spVertexAttachment_computeWorldVertices(SUPER(path), target, verticesLength - 6, 4, world, 0, 2);
+				}
+				_addAfterPosition(p - pathLength, world, 0, out, o);
+				continue;
+			}
+
+			/* Determine curve containing position. */
+			for (;; curve++) {
+				float length = lengths[curve];
+				if (p > length) continue;
+				if (curve == 0)
+					p /= length;
+				else {
+					float prev = lengths[curve - 1];
+					p = (p - prev) / (length - prev);
+				}
+				break;
+			}
+			if (curve != prevCurve) {
+				prevCurve = curve;
+				if (closed && curve == curveCount) {
+					spVertexAttachment_computeWorldVertices(SUPER(path), target, verticesLength - 4, 4, world, 0, 2);
+					spVertexAttachment_computeWorldVertices(SUPER(path), target, 0, 4, world, 4, 2);
+				} else
+					spVertexAttachment_computeWorldVertices(SUPER(path), target, curve * 6 + 2, 8, world, 0, 2);
+			}
+			_addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
+							  tangents || (i > 0 && space == 0));
+		}
+		return out;
+	}
+
+	/* World vertices. */
+	if (closed) {
+		verticesLength += 2;
+		if (self->worldCount != verticesLength) {
+			if (self->world) FREE(self->world);
+			self->world = MALLOC(float, verticesLength);
+			self->worldCount = verticesLength;
+		}
+		world = self->world;
+		spVertexAttachment_computeWorldVertices(SUPER(path), target, 2, verticesLength - 4, world, 0, 2);
+		spVertexAttachment_computeWorldVertices(SUPER(path), target, 0, 2, world, verticesLength - 4, 2);
+		world[verticesLength - 2] = world[0];
+		world[verticesLength - 1] = world[1];
+	} else {
+		curveCount--;
+		verticesLength -= 4;
+		if (self->worldCount != verticesLength) {
+			if (self->world) FREE(self->world);
+			self->world = MALLOC(float, verticesLength);
+			self->worldCount = verticesLength;
+		}
+		world = self->world;
+		spVertexAttachment_computeWorldVertices(SUPER(path), target, 2, verticesLength, world, 0, 2);
+	}
+
+	/* Curve lengths. */
+	if (self->curvesCount != curveCount) {
+		if (self->curves) FREE(self->curves);
+		self->curves = MALLOC(float, curveCount);
+		self->curvesCount = curveCount;
+	}
+	curves = self->curves;
+	pathLength = 0;
+	x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
+	for (i = 0, w = 2; i < curveCount; i++, w += 6) {
+		cx1 = world[w];
+		cy1 = world[w + 1];
+		cx2 = world[w + 2];
+		cy2 = world[w + 3];
+		x2 = world[w + 4];
+		y2 = world[w + 5];
+		tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f;
+		tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f;
+		dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f;
+		dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f;
+		ddfx = tmpx * 2 + dddfx;
+		ddfy = tmpy * 2 + dddfy;
+		dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f;
+		dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f;
+		pathLength += SQRT(dfx * dfx + dfy * dfy);
+		dfx += ddfx;
+		dfy += ddfy;
+		ddfx += dddfx;
+		ddfy += dddfy;
+		pathLength += SQRT(dfx * dfx + dfy * dfy);
+		dfx += ddfx;
+		dfy += ddfy;
+		pathLength += SQRT(dfx * dfx + dfy * dfy);
+		dfx += ddfx + dddfx;
+		dfy += ddfy + dddfy;
+		pathLength += SQRT(dfx * dfx + dfy * dfy);
+		curves[i] = pathLength;
+		x1 = x2;
+		y1 = y2;
+	}
+
+	if (self->data->positionMode == SP_POSITION_MODE_PERCENT) position *= pathLength;
+
+	switch (self->data->spacingMode) {
+		case SP_SPACING_MODE_PERCENT:
+			multiplier = pathLength;
+			break;
+		case SP_SPACING_MODE_PROPORTIONAL:
+			multiplier = pathLength / spacesCount;
+			break;
+		default:
+			multiplier = 1;
+	}
+
+	segments = self->segments;
+	curveLength = 0;
+	for (i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
+		float space = spaces[i] * multiplier;
+		position += space;
+		p = position;
+
+		if (closed) {
+			p = FMOD(p, pathLength);
+			if (p < 0) p += pathLength;
+			curve = 0;
+		} else if (p < 0) {
+			_addBeforePosition(p, world, 0, out, o);
+			continue;
+		} else if (p > pathLength) {
+			_addAfterPosition(p - pathLength, world, verticesLength - 4, out, o);
+			continue;
+		}
+
+		/* Determine curve containing position. */
+		for (;; curve++) {
+			float length = curves[curve];
+			if (p > length) continue;
+			if (curve == 0)
+				p /= length;
+			else {
+				float prev = curves[curve - 1];
+				p = (p - prev) / (length - prev);
+			}
+			break;
+		}
+
+		/* Curve segment lengths. */
+		if (curve != prevCurve) {
+			int ii;
+			prevCurve = curve;
+			ii = curve * 6;
+			x1 = world[ii];
+			y1 = world[ii + 1];
+			cx1 = world[ii + 2];
+			cy1 = world[ii + 3];
+			cx2 = world[ii + 4];
+			cy2 = world[ii + 5];
+			x2 = world[ii + 6];
+			y2 = world[ii + 7];
+			tmpx = (x1 - cx1 * 2 + cx2) * 0.03f;
+			tmpy = (y1 - cy1 * 2 + cy2) * 0.03f;
+			dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f;
+			dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f;
+			ddfx = tmpx * 2 + dddfx;
+			ddfy = tmpy * 2 + dddfy;
+			dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f;
+			dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f;
+			curveLength = SQRT(dfx * dfx + dfy * dfy);
+			segments[0] = curveLength;
+			for (ii = 1; ii < 8; ii++) {
+				dfx += ddfx;
+				dfy += ddfy;
+				ddfx += dddfx;
+				ddfy += dddfy;
+				curveLength += SQRT(dfx * dfx + dfy * dfy);
+				segments[ii] = curveLength;
+			}
+			dfx += ddfx;
+			dfy += ddfy;
+			curveLength += SQRT(dfx * dfx + dfy * dfy);
+			segments[8] = curveLength;
+			dfx += ddfx + dddfx;
+			dfy += ddfy + dddfy;
+			curveLength += SQRT(dfx * dfx + dfy * dfy);
+			segments[9] = curveLength;
+			segment = 0;
+		}
+
+		/* Weight by segment length. */
+		p *= curveLength;
+		for (;; segment++) {
+			float length = segments[segment];
+			if (p > length) continue;
+			if (segment == 0)
+				p /= length;
+			else {
+				float prev = segments[segment - 1];
+				p = segment + (p - prev) / (length - prev);
+			}
+			break;
+		}
+		_addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0));
+	}
+	return out;
+}

+ 43 - 43
spine-c/spine-c/src/spine/PathConstraintData.c

@@ -1,43 +1,43 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/PathConstraintData.h>
-#include <spine/extension.h>
-
-spPathConstraintData *spPathConstraintData_create(const char *name) {
-	spPathConstraintData *self = NEW(spPathConstraintData);
-	MALLOC_STR(self->name, name);
-	return self;
-}
-
-void spPathConstraintData_dispose(spPathConstraintData *self) {
-	FREE(self->name);
-	FREE(self->bones);
-	FREE(self);
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/PathConstraintData.h>
+#include <spine/extension.h>
+
+spPathConstraintData *spPathConstraintData_create(const char *name) {
+	spPathConstraintData *self = NEW(spPathConstraintData);
+	MALLOC_STR(self->name, name);
+	return self;
+}
+
+void spPathConstraintData_dispose(spPathConstraintData *self) {
+	FREE(self->name);
+	FREE(self->bones);
+	FREE(self);
+}

+ 161 - 154
spine-c/spine-c/src/spine/RegionAttachment.c

@@ -1,154 +1,161 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/RegionAttachment.h>
-#include <spine/extension.h>
-
-typedef enum {
-	BLX = 0, BLY, ULX, ULY, URX, URY, BRX, BRY
-} spVertexIndex;
-
-void _spRegionAttachment_dispose(spAttachment *attachment) {
-	spRegionAttachment *self = SUB_CAST(spRegionAttachment, attachment);
-	_spAttachment_deinit(attachment);
-	FREE(self->path);
-	FREE(self);
-}
-
-spAttachment *_spRegionAttachment_copy(spAttachment *attachment) {
-	spRegionAttachment *self = SUB_CAST(spRegionAttachment, attachment);
-	spRegionAttachment *copy = spRegionAttachment_create(attachment->name);
-	copy->regionWidth = self->regionWidth;
-	copy->regionHeight = self->regionHeight;
-	copy->regionOffsetX = self->regionOffsetX;
-	copy->regionOffsetY = self->regionOffsetY;
-	copy->regionOriginalWidth = self->regionOriginalWidth;
-	copy->regionOriginalHeight = self->regionOriginalHeight;
-	copy->rendererObject = self->rendererObject;
-	MALLOC_STR(copy->path, self->path);
-	copy->x = self->x;
-	copy->y = self->y;
-	copy->scaleX = self->scaleX;
-	copy->scaleY = self->scaleY;
-	copy->rotation = self->rotation;
-	copy->width = self->width;
-	copy->height = self->height;
-	memcpy(copy->uvs, self->uvs, sizeof(float) * 8);
-	memcpy(copy->offset, self->offset, sizeof(float) * 8);
-	spColor_setFromColor(&copy->color, &self->color);
-	return SUPER(copy);
-}
-
-spRegionAttachment *spRegionAttachment_create(const char *name) {
-	spRegionAttachment *self = NEW(spRegionAttachment);
-	self->scaleX = 1;
-	self->scaleY = 1;
-	spColor_setFromFloats(&self->color, 1, 1, 1, 1);
-	_spAttachment_init(SUPER(self), name, SP_ATTACHMENT_REGION, _spRegionAttachment_dispose, _spRegionAttachment_copy);
-	return self;
-}
-
-void spRegionAttachment_setUVs(spRegionAttachment *self, float u, float v, float u2, float v2, float degrees) {
-	if (degrees == 90) {
-		self->uvs[URX] = u;
-		self->uvs[URY] = v2;
-		self->uvs[BRX] = u;
-		self->uvs[BRY] = v;
-		self->uvs[BLX] = u2;
-		self->uvs[BLY] = v;
-		self->uvs[ULX] = u2;
-		self->uvs[ULY] = v2;
-	} else {
-		self->uvs[ULX] = u;
-		self->uvs[ULY] = v2;
-		self->uvs[URX] = u;
-		self->uvs[URY] = v;
-		self->uvs[BRX] = u2;
-		self->uvs[BRY] = v;
-		self->uvs[BLX] = u2;
-		self->uvs[BLY] = v2;
-	}
-}
-
-void spRegionAttachment_updateOffset(spRegionAttachment *self) {
-	float regionScaleX = self->width / self->regionOriginalWidth * self->scaleX;
-	float regionScaleY = self->height / self->regionOriginalHeight * self->scaleY;
-	float localX = -self->width / 2 * self->scaleX + self->regionOffsetX * regionScaleX;
-	float localY = -self->height / 2 * self->scaleY + self->regionOffsetY * regionScaleY;
-	float localX2 = localX + self->regionWidth * regionScaleX;
-	float localY2 = localY + self->regionHeight * regionScaleY;
-	float radians = self->rotation * DEG_RAD;
-	float cosine = COS(radians), sine = SIN(radians);
-	float localXCos = localX * cosine + self->x;
-	float localXSin = localX * sine;
-	float localYCos = localY * cosine + self->y;
-	float localYSin = localY * sine;
-	float localX2Cos = localX2 * cosine + self->x;
-	float localX2Sin = localX2 * sine;
-	float localY2Cos = localY2 * cosine + self->y;
-	float localY2Sin = localY2 * sine;
-	self->offset[BLX] = localXCos - localYSin;
-	self->offset[BLY] = localYCos + localXSin;
-	self->offset[ULX] = localXCos - localY2Sin;
-	self->offset[ULY] = localY2Cos + localXSin;
-	self->offset[URX] = localX2Cos - localY2Sin;
-	self->offset[URY] = localY2Cos + localX2Sin;
-	self->offset[BRX] = localX2Cos - localYSin;
-	self->offset[BRY] = localYCos + localX2Sin;
-}
-
-void spRegionAttachment_computeWorldVertices(spRegionAttachment *self, spBone *bone, float *vertices, int offset,
-											 int stride) {
-	const float *offsets = self->offset;
-	float x = bone->worldX, y = bone->worldY;
-	float offsetX, offsetY;
-
-	offsetX = offsets[BRX];
-	offsetY = offsets[BRY];
-	vertices[offset] = offsetX * bone->a + offsetY * bone->b + x; /* br */
-	vertices[offset + 1] = offsetX * bone->c + offsetY * bone->d + y;
-	offset += stride;
-
-	offsetX = offsets[BLX];
-	offsetY = offsets[BLY];
-	vertices[offset] = offsetX * bone->a + offsetY * bone->b + x; /* bl */
-	vertices[offset + 1] = offsetX * bone->c + offsetY * bone->d + y;
-	offset += stride;
-
-	offsetX = offsets[ULX];
-	offsetY = offsets[ULY];
-	vertices[offset] = offsetX * bone->a + offsetY * bone->b + x; /* ul */
-	vertices[offset + 1] = offsetX * bone->c + offsetY * bone->d + y;
-	offset += stride;
-
-	offsetX = offsets[URX];
-	offsetY = offsets[URY];
-	vertices[offset] = offsetX * bone->a + offsetY * bone->b + x; /* ur */
-	vertices[offset + 1] = offsetX * bone->c + offsetY * bone->d + y;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/RegionAttachment.h>
+#include <spine/extension.h>
+
+typedef enum {
+	BLX = 0,
+	BLY,
+	ULX,
+	ULY,
+	URX,
+	URY,
+	BRX,
+	BRY
+} spVertexIndex;
+
+void _spRegionAttachment_dispose(spAttachment *attachment) {
+	spRegionAttachment *self = SUB_CAST(spRegionAttachment, attachment);
+	_spAttachment_deinit(attachment);
+	FREE(self->path);
+	FREE(self);
+}
+
+spAttachment *_spRegionAttachment_copy(spAttachment *attachment) {
+	spRegionAttachment *self = SUB_CAST(spRegionAttachment, attachment);
+	spRegionAttachment *copy = spRegionAttachment_create(attachment->name);
+	copy->regionWidth = self->regionWidth;
+	copy->regionHeight = self->regionHeight;
+	copy->regionOffsetX = self->regionOffsetX;
+	copy->regionOffsetY = self->regionOffsetY;
+	copy->regionOriginalWidth = self->regionOriginalWidth;
+	copy->regionOriginalHeight = self->regionOriginalHeight;
+	copy->rendererObject = self->rendererObject;
+	MALLOC_STR(copy->path, self->path);
+	copy->x = self->x;
+	copy->y = self->y;
+	copy->scaleX = self->scaleX;
+	copy->scaleY = self->scaleY;
+	copy->rotation = self->rotation;
+	copy->width = self->width;
+	copy->height = self->height;
+	memcpy(copy->uvs, self->uvs, sizeof(float) * 8);
+	memcpy(copy->offset, self->offset, sizeof(float) * 8);
+	spColor_setFromColor(&copy->color, &self->color);
+	return SUPER(copy);
+}
+
+spRegionAttachment *spRegionAttachment_create(const char *name) {
+	spRegionAttachment *self = NEW(spRegionAttachment);
+	self->scaleX = 1;
+	self->scaleY = 1;
+	spColor_setFromFloats(&self->color, 1, 1, 1, 1);
+	_spAttachment_init(SUPER(self), name, SP_ATTACHMENT_REGION, _spRegionAttachment_dispose, _spRegionAttachment_copy);
+	return self;
+}
+
+void spRegionAttachment_setUVs(spRegionAttachment *self, float u, float v, float u2, float v2, float degrees) {
+	if (degrees == 90) {
+		self->uvs[URX] = u;
+		self->uvs[URY] = v2;
+		self->uvs[BRX] = u;
+		self->uvs[BRY] = v;
+		self->uvs[BLX] = u2;
+		self->uvs[BLY] = v;
+		self->uvs[ULX] = u2;
+		self->uvs[ULY] = v2;
+	} else {
+		self->uvs[ULX] = u;
+		self->uvs[ULY] = v2;
+		self->uvs[URX] = u;
+		self->uvs[URY] = v;
+		self->uvs[BRX] = u2;
+		self->uvs[BRY] = v;
+		self->uvs[BLX] = u2;
+		self->uvs[BLY] = v2;
+	}
+}
+
+void spRegionAttachment_updateOffset(spRegionAttachment *self) {
+	float regionScaleX = self->width / self->regionOriginalWidth * self->scaleX;
+	float regionScaleY = self->height / self->regionOriginalHeight * self->scaleY;
+	float localX = -self->width / 2 * self->scaleX + self->regionOffsetX * regionScaleX;
+	float localY = -self->height / 2 * self->scaleY + self->regionOffsetY * regionScaleY;
+	float localX2 = localX + self->regionWidth * regionScaleX;
+	float localY2 = localY + self->regionHeight * regionScaleY;
+	float radians = self->rotation * DEG_RAD;
+	float cosine = COS(radians), sine = SIN(radians);
+	float localXCos = localX * cosine + self->x;
+	float localXSin = localX * sine;
+	float localYCos = localY * cosine + self->y;
+	float localYSin = localY * sine;
+	float localX2Cos = localX2 * cosine + self->x;
+	float localX2Sin = localX2 * sine;
+	float localY2Cos = localY2 * cosine + self->y;
+	float localY2Sin = localY2 * sine;
+	self->offset[BLX] = localXCos - localYSin;
+	self->offset[BLY] = localYCos + localXSin;
+	self->offset[ULX] = localXCos - localY2Sin;
+	self->offset[ULY] = localY2Cos + localXSin;
+	self->offset[URX] = localX2Cos - localY2Sin;
+	self->offset[URY] = localY2Cos + localX2Sin;
+	self->offset[BRX] = localX2Cos - localYSin;
+	self->offset[BRY] = localYCos + localX2Sin;
+}
+
+void spRegionAttachment_computeWorldVertices(spRegionAttachment *self, spBone *bone, float *vertices, int offset,
+											 int stride) {
+	const float *offsets = self->offset;
+	float x = bone->worldX, y = bone->worldY;
+	float offsetX, offsetY;
+
+	offsetX = offsets[BRX];
+	offsetY = offsets[BRY];
+	vertices[offset] = offsetX * bone->a + offsetY * bone->b + x; /* br */
+	vertices[offset + 1] = offsetX * bone->c + offsetY * bone->d + y;
+	offset += stride;
+
+	offsetX = offsets[BLX];
+	offsetY = offsets[BLY];
+	vertices[offset] = offsetX * bone->a + offsetY * bone->b + x; /* bl */
+	vertices[offset + 1] = offsetX * bone->c + offsetY * bone->d + y;
+	offset += stride;
+
+	offsetX = offsets[ULX];
+	offsetY = offsets[ULY];
+	vertices[offset] = offsetX * bone->a + offsetY * bone->b + x; /* ul */
+	vertices[offset + 1] = offsetX * bone->c + offsetY * bone->d + y;
+	offset += stride;
+
+	offsetX = offsets[URX];
+	offsetY = offsets[URY];
+	vertices[offset] = offsetX * bone->a + offsetY * bone->b + x; /* ur */
+	vertices[offset + 1] = offsetX * bone->c + offsetY * bone->d + y;
+}

+ 649 - 646
spine-c/spine-c/src/spine/Skeleton.c

@@ -1,646 +1,649 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/Skeleton.h>
-#include <stdlib.h>
-#include <string.h>
-#include <spine/extension.h>
-
-typedef enum {
-	SP_UPDATE_BONE, SP_UPDATE_IK_CONSTRAINT, SP_UPDATE_PATH_CONSTRAINT, SP_UPDATE_TRANSFORM_CONSTRAINT
-} _spUpdateType;
-
-typedef struct {
-	_spUpdateType type;
-	void *object;
-} _spUpdate;
-
-typedef struct {
-	spSkeleton super;
-
-	int updateCacheCount;
-	int updateCacheCapacity;
-	_spUpdate *updateCache;
-} _spSkeleton;
-
-spSkeleton *spSkeleton_create(spSkeletonData *data) {
-	int i;
-	int *childrenCounts;
-
-	_spSkeleton *internal = NEW(_spSkeleton);
-	spSkeleton *self = SUPER(internal);
-	CONST_CAST(spSkeletonData*, self->data) = data;
-
-	self->bonesCount = self->data->bonesCount;
-	self->bones = MALLOC(spBone*, self->bonesCount);
-	childrenCounts = CALLOC(int, self->bonesCount);
-
-	for (i = 0; i < self->bonesCount; ++i) {
-		spBoneData *boneData = self->data->bones[i];
-		spBone *newBone;
-		if (!boneData->parent)
-			newBone = spBone_create(boneData, self, 0);
-		else {
-			spBone *parent = self->bones[boneData->parent->index];
-			newBone = spBone_create(boneData, self, parent);
-			++childrenCounts[boneData->parent->index];
-		}
-		self->bones[i] = newBone;
-	}
-	for (i = 0; i < self->bonesCount; ++i) {
-		spBoneData *boneData = self->data->bones[i];
-		spBone *bone = self->bones[i];
-		CONST_CAST(spBone**, bone->children) = MALLOC(spBone*, childrenCounts[boneData->index]);
-	}
-	for (i = 0; i < self->bonesCount; ++i) {
-		spBone *bone = self->bones[i];
-		spBone *parent = bone->parent;
-		if (parent)
-			parent->children[parent->childrenCount++] = bone;
-	}
-	CONST_CAST(spBone*, self->root) = (self->bonesCount > 0 ? self->bones[0] : NULL);
-
-	self->slotsCount = data->slotsCount;
-	self->slots = MALLOC(spSlot*, self->slotsCount);
-	for (i = 0; i < self->slotsCount; ++i) {
-		spSlotData *slotData = data->slots[i];
-		spBone *bone = self->bones[slotData->boneData->index];
-		self->slots[i] = spSlot_create(slotData, bone);
-	}
-
-	self->drawOrder = MALLOC(spSlot*, self->slotsCount);
-	memcpy(self->drawOrder, self->slots, sizeof(spSlot *) * self->slotsCount);
-
-	self->ikConstraintsCount = data->ikConstraintsCount;
-	self->ikConstraints = MALLOC(spIkConstraint*, self->ikConstraintsCount);
-	for (i = 0; i < self->data->ikConstraintsCount; ++i)
-		self->ikConstraints[i] = spIkConstraint_create(self->data->ikConstraints[i], self);
-
-	self->transformConstraintsCount = data->transformConstraintsCount;
-	self->transformConstraints = MALLOC(spTransformConstraint*, self->transformConstraintsCount);
-	for (i = 0; i < self->data->transformConstraintsCount; ++i)
-		self->transformConstraints[i] = spTransformConstraint_create(self->data->transformConstraints[i], self);
-
-	self->pathConstraintsCount = data->pathConstraintsCount;
-	self->pathConstraints = MALLOC(spPathConstraint*, self->pathConstraintsCount);
-	for (i = 0; i < self->data->pathConstraintsCount; i++)
-		self->pathConstraints[i] = spPathConstraint_create(self->data->pathConstraints[i], self);
-
-	spColor_setFromFloats(&self->color, 1, 1, 1, 1);
-
-	self->scaleX = 1;
-	self->scaleY = 1;
-
-	spSkeleton_updateCache(self);
-
-	FREE(childrenCounts);
-
-	return self;
-}
-
-void spSkeleton_dispose(spSkeleton *self) {
-	int i;
-	_spSkeleton *internal = SUB_CAST(_spSkeleton, self);
-
-	FREE(internal->updateCache);
-
-	for (i = 0; i < self->bonesCount; ++i)
-		spBone_dispose(self->bones[i]);
-	FREE(self->bones);
-
-	for (i = 0; i < self->slotsCount; ++i)
-		spSlot_dispose(self->slots[i]);
-	FREE(self->slots);
-
-	for (i = 0; i < self->ikConstraintsCount; ++i)
-		spIkConstraint_dispose(self->ikConstraints[i]);
-	FREE(self->ikConstraints);
-
-	for (i = 0; i < self->transformConstraintsCount; ++i)
-		spTransformConstraint_dispose(self->transformConstraints[i]);
-	FREE(self->transformConstraints);
-
-	for (i = 0; i < self->pathConstraintsCount; i++)
-		spPathConstraint_dispose(self->pathConstraints[i]);
-	FREE(self->pathConstraints);
-
-	FREE(self->drawOrder);
-	FREE(self);
-}
-
-static void _addToUpdateCache(_spSkeleton *const internal, _spUpdateType type, void *object) {
-	_spUpdate *update;
-	if (internal->updateCacheCount == internal->updateCacheCapacity) {
-		internal->updateCacheCapacity *= 2;
-		internal->updateCache = (_spUpdate *) realloc(internal->updateCache,
-													  sizeof(_spUpdate) * internal->updateCacheCapacity);
-	}
-	update = internal->updateCache + internal->updateCacheCount;
-	update->type = type;
-	update->object = object;
-	++internal->updateCacheCount;
-}
-
-static void _sortBone(_spSkeleton *const internal, spBone *bone) {
-	if (bone->sorted) return;
-	if (bone->parent) _sortBone(internal, bone->parent);
-	bone->sorted = 1;
-	_addToUpdateCache(internal, SP_UPDATE_BONE, bone);
-}
-
-static void
-_sortPathConstraintAttachmentBones(_spSkeleton *const internal, spAttachment *attachment, spBone *slotBone) {
-	spPathAttachment *pathAttachment = (spPathAttachment *) attachment;
-	int *pathBones;
-	int pathBonesCount;
-	if (pathAttachment->super.super.type != SP_ATTACHMENT_PATH) return;
-	pathBones = pathAttachment->super.bones;
-	pathBonesCount = pathAttachment->super.bonesCount;
-	if (pathBones == 0)
-		_sortBone(internal, slotBone);
-	else {
-		spBone **bones = internal->super.bones;
-		int i = 0, n;
-
-		for (i = 0, n = pathBonesCount; i < n;) {
-			int nn = pathBones[i++];
-			nn += i;
-			while (i < nn)
-				_sortBone(internal, bones[pathBones[i++]]);
-		}
-	}
-}
-
-static void _sortPathConstraintAttachment(_spSkeleton *const internal, spSkin *skin, int slotIndex, spBone *slotBone) {
-	_Entry *entry = SUB_CAST(_spSkin, skin)->entries;
-	while (entry) {
-		if (entry->slotIndex == slotIndex) _sortPathConstraintAttachmentBones(internal, entry->attachment, slotBone);
-		entry = entry->next;
-	}
-}
-
-static void _sortReset(spBone **bones, int bonesCount) {
-	int i;
-	for (i = 0; i < bonesCount; ++i) {
-		spBone *bone = bones[i];
-		if (!bone->active) continue;
-		if (bone->sorted) _sortReset(bone->children, bone->childrenCount);
-		bone->sorted = 0;
-	}
-}
-
-static void _sortIkConstraint(_spSkeleton *const internal, spIkConstraint *constraint) {
-	spBone *target = constraint->target;
-	spBone **constrained;
-	spBone *parent;
-
-	constraint->active = constraint->target->active && (!constraint->data->skinRequired || (internal->super.skin != 0 &&
-																							spIkConstraintDataArray_contains(
-																									internal->super.skin->ikConstraints,
-																									constraint->data)));
-	if (!constraint->active) return;
-
-	_sortBone(internal, target);
-
-	constrained = constraint->bones;
-	parent = constrained[0];
-	_sortBone(internal, parent);
-
-	if (constraint->bonesCount == 1) {
-		_addToUpdateCache(internal, SP_UPDATE_IK_CONSTRAINT, constraint);
-		_sortReset(parent->children, parent->childrenCount);
-	} else {
-		spBone *child = constrained[constraint->bonesCount - 1];
-		_sortBone(internal, child);
-
-		_addToUpdateCache(internal, SP_UPDATE_IK_CONSTRAINT, constraint);
-
-		_sortReset(parent->children, parent->childrenCount);
-		child->sorted = 1;
-	}
-}
-
-static void _sortPathConstraint(_spSkeleton *const internal, spPathConstraint *constraint) {
-	spSlot *slot = constraint->target;
-	int slotIndex = slot->data->index;
-	spBone *slotBone = slot->bone;
-	int i, n, boneCount;
-	spAttachment *attachment;
-	spBone **constrained;
-	spSkeleton *skeleton = SUPER_CAST(spSkeleton, internal);
-
-	constraint->active = constraint->target->bone->active && (!constraint->data->skinRequired ||
-															  (internal->super.skin != 0 &&
-															   spPathConstraintDataArray_contains(
-																	   internal->super.skin->pathConstraints,
-																	   constraint->data)));
-	if (!constraint->active) return;
-
-	if (skeleton->skin) _sortPathConstraintAttachment(internal, skeleton->skin, slotIndex, slotBone);
-	if (skeleton->data->defaultSkin && skeleton->data->defaultSkin != skeleton->skin)
-		_sortPathConstraintAttachment(internal, skeleton->data->defaultSkin, slotIndex, slotBone);
-	for (i = 0, n = skeleton->data->skinsCount; i < n; i++)
-		_sortPathConstraintAttachment(internal, skeleton->data->skins[i], slotIndex, slotBone);
-
-	attachment = slot->attachment;
-	if (attachment && attachment->type == SP_ATTACHMENT_PATH)
-		_sortPathConstraintAttachmentBones(internal, attachment, slotBone);
-
-	constrained = constraint->bones;
-	boneCount = constraint->bonesCount;
-	for (i = 0; i < boneCount; i++)
-		_sortBone(internal, constrained[i]);
-
-	_addToUpdateCache(internal, SP_UPDATE_PATH_CONSTRAINT, constraint);
-
-	for (i = 0; i < boneCount; i++)
-		_sortReset(constrained[i]->children, constrained[i]->childrenCount);
-	for (i = 0; i < boneCount; i++)
-		constrained[i]->sorted = 1;
-}
-
-static void _sortTransformConstraint(_spSkeleton *const internal, spTransformConstraint *constraint) {
-	int i, boneCount;
-	spBone **constrained;
-	spBone *child;
-
-	constraint->active = constraint->target->active && (!constraint->data->skinRequired || (internal->super.skin != 0 &&
-																							spTransformConstraintDataArray_contains(
-																									internal->super.skin->transformConstraints,
-																									constraint->data)));
-	if (!constraint->active) return;
-
-	_sortBone(internal, constraint->target);
-
-	constrained = constraint->bones;
-	boneCount = constraint->bonesCount;
-	if (constraint->data->local) {
-		for (i = 0; i < boneCount; i++) {
-			child = constrained[i];
-			_sortBone(internal, child->parent);
-			_sortBone(internal, child);
-		}
-	} else {
-		for (i = 0; i < boneCount; i++)
-			_sortBone(internal, constrained[i]);
-	}
-
-	_addToUpdateCache(internal, SP_UPDATE_TRANSFORM_CONSTRAINT, constraint);
-
-	for (i = 0; i < boneCount; i++)
-		_sortReset(constrained[i]->children, constrained[i]->childrenCount);
-	for (i = 0; i < boneCount; i++)
-		constrained[i]->sorted = 1;
-}
-
-void spSkeleton_updateCache(spSkeleton *self) {
-	int i, ii;
-	spBone **bones;
-	spIkConstraint **ikConstraints;
-	spPathConstraint **pathConstraints;
-	spTransformConstraint **transformConstraints;
-	int ikCount, transformCount, pathCount, constraintCount;
-	_spSkeleton *internal = SUB_CAST(_spSkeleton, self);
-
-	internal->updateCacheCapacity =
-			self->bonesCount + self->ikConstraintsCount + self->transformConstraintsCount + self->pathConstraintsCount;
-	FREE(internal->updateCache);
-	internal->updateCache = MALLOC(_spUpdate, internal->updateCacheCapacity);
-	internal->updateCacheCount = 0;
-
-	bones = self->bones;
-	for (i = 0; i < self->bonesCount; ++i) {
-		spBone *bone = bones[i];
-		bone->sorted = bone->data->skinRequired;
-		bone->active = !bone->sorted;
-	}
-
-	if (self->skin) {
-		spBoneDataArray *skinBones = self->skin->bones;
-		for (i = 0; i < skinBones->size; i++) {
-			spBone *bone = self->bones[skinBones->items[i]->index];
-			do {
-				bone->sorted = 0;
-				bone->active = -1;
-				bone = bone->parent;
-			} while (bone != 0);
-		}
-	}
-
-	/* IK first, lowest hierarchy depth first. */
-	ikConstraints = self->ikConstraints;
-	transformConstraints = self->transformConstraints;
-	pathConstraints = self->pathConstraints;
-	ikCount = self->ikConstraintsCount;
-	transformCount = self->transformConstraintsCount;
-	pathCount = self->pathConstraintsCount;
-	constraintCount = ikCount + transformCount + pathCount;
-
-	i = 0;
-	continue_outer:
-	for (; i < constraintCount; i++) {
-		for (ii = 0; ii < ikCount; ii++) {
-			spIkConstraint *ikConstraint = ikConstraints[ii];
-			if (ikConstraint->data->order == i) {
-				_sortIkConstraint(internal, ikConstraint);
-				i++;
-				goto continue_outer;
-			}
-		}
-
-		for (ii = 0; ii < transformCount; ii++) {
-			spTransformConstraint *transformConstraint = transformConstraints[ii];
-			if (transformConstraint->data->order == i) {
-				_sortTransformConstraint(internal, transformConstraint);
-				i++;
-				goto continue_outer;
-			}
-		}
-
-		for (ii = 0; ii < pathCount; ii++) {
-			spPathConstraint *pathConstraint = pathConstraints[ii];
-			if (pathConstraint->data->order == i) {
-				_sortPathConstraint(internal, pathConstraint);
-				i++;
-				goto continue_outer;
-			}
-		}
-	}
-
-	for (i = 0; i < self->bonesCount; ++i)
-		_sortBone(internal, self->bones[i]);
-}
-
-void spSkeleton_updateWorldTransform(const spSkeleton *self) {
-	int i, n;
-	_spSkeleton *internal = SUB_CAST(_spSkeleton, self);
-
-	for (i = 0, n = self->bonesCount; i < n; i++) {
-		spBone *bone = self->bones[i];
-		bone->ax = bone->x;
-		bone->ay = bone->y;
-		bone->arotation = bone->rotation;
-		bone->ascaleX = bone->scaleX;
-		bone->ascaleY = bone->scaleY;
-		bone->ashearX = bone->shearX;
-		bone->ashearY = bone->shearY;
-	}
-	
-	for (i = 0; i < internal->updateCacheCount; ++i) {
-		_spUpdate *update = internal->updateCache + i;
-		switch (update->type) {
-			case SP_UPDATE_BONE:
-				spBone_update((spBone *) update->object);
-				break;
-			case SP_UPDATE_IK_CONSTRAINT:
-				spIkConstraint_update((spIkConstraint *) update->object);
-				break;
-			case SP_UPDATE_TRANSFORM_CONSTRAINT:
-				spTransformConstraint_update((spTransformConstraint *) update->object);
-				break;
-			case SP_UPDATE_PATH_CONSTRAINT:
-				spPathConstraint_update((spPathConstraint *) update->object);
-				break;
-		}
-	}
-}
-
-void spSkeleton_updateWorldTransformWith(const spSkeleton *self, const spBone *parent) {
-/* Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. */
-	int i;
-	float rotationY, la, lb, lc, ld;
-	_spUpdate *updateCache;
-	_spSkeleton *internal = SUB_CAST(_spSkeleton, self);
-	spBone *rootBone = self->root;
-	float pa = parent->a, pb = parent->b, pc = parent->c, pd = parent->d;
-	CONST_CAST(float, rootBone->worldX) = pa * self->x + pb * self->y + parent->worldX;
-	CONST_CAST(float, rootBone->worldY) = pc * self->x + pd * self->y + parent->worldY;
-
-	rotationY = rootBone->rotation + 90 + rootBone->shearY;
-	la = COS_DEG(rootBone->rotation + rootBone->shearX) * rootBone->scaleX;
-	lb = COS_DEG(rotationY) * rootBone->scaleY;
-	lc = SIN_DEG(rootBone->rotation + rootBone->shearX) * rootBone->scaleX;
-	ld = SIN_DEG(rotationY) * rootBone->scaleY;
-	CONST_CAST(float, rootBone->a) = (pa * la + pb * lc) * self->scaleX;
-	CONST_CAST(float, rootBone->b) = (pa * lb + pb * ld) * self->scaleX;
-	CONST_CAST(float, rootBone->c) = (pc * la + pd * lc) * self->scaleY;
-	CONST_CAST(float, rootBone->d) = (pc * lb + pd * ld) * self->scaleY;
-
-	/* Update everything except root bone. */
-	updateCache = internal->updateCache;
-	for (i = 0; i < internal->updateCacheCount; ++i) {
-		_spUpdate *update = internal->updateCache + i;
-		switch (update->type) {
-			case SP_UPDATE_BONE:
-				if ((spBone *) update->object != rootBone) spBone_updateWorldTransform((spBone *) update->object);
-				break;
-			case SP_UPDATE_IK_CONSTRAINT:
-				spIkConstraint_update((spIkConstraint *) update->object);
-				break;
-			case SP_UPDATE_TRANSFORM_CONSTRAINT:
-				spTransformConstraint_update((spTransformConstraint *) update->object);
-				break;
-			case SP_UPDATE_PATH_CONSTRAINT:
-				spPathConstraint_update((spPathConstraint *) update->object);
-				break;
-		}
-	}
-}
-
-void spSkeleton_setToSetupPose(const spSkeleton *self) {
-	spSkeleton_setBonesToSetupPose(self);
-	spSkeleton_setSlotsToSetupPose(self);
-}
-
-void spSkeleton_setBonesToSetupPose(const spSkeleton *self) {
-	int i;
-	for (i = 0; i < self->bonesCount; ++i)
-		spBone_setToSetupPose(self->bones[i]);
-
-	for (i = 0; i < self->ikConstraintsCount; ++i) {
-		spIkConstraint *ikConstraint = self->ikConstraints[i];
-		ikConstraint->bendDirection = ikConstraint->data->bendDirection;
-		ikConstraint->compress = ikConstraint->data->compress;
-		ikConstraint->stretch = ikConstraint->data->stretch;
-		ikConstraint->softness = ikConstraint->data->softness;
-		ikConstraint->mix = ikConstraint->data->mix;
-	}
-
-	for (i = 0; i < self->transformConstraintsCount; ++i) {
-		spTransformConstraint *constraint = self->transformConstraints[i];
-		spTransformConstraintData *data = constraint->data;
-		constraint->mixRotate = data->mixRotate;
-		constraint->mixX = data->mixX;
-		constraint->mixY = data->mixY;
-		constraint->mixScaleX = data->mixScaleX;
-		constraint->mixScaleY = data->mixScaleY;
-		constraint->mixShearY = data->mixShearY;
-	}
-
-	for (i = 0; i < self->pathConstraintsCount; ++i) {
-		spPathConstraint *constraint = self->pathConstraints[i];
-		spPathConstraintData *data = constraint->data;
-		constraint->position = data->position;
-		constraint->spacing = data->spacing;
-		constraint->mixRotate = data->mixRotate;
-		constraint->mixX = data->mixX;
-		constraint->mixY = data->mixY;
-	}
-}
-
-void spSkeleton_setSlotsToSetupPose(const spSkeleton *self) {
-	int i;
-	memcpy(self->drawOrder, self->slots, self->slotsCount * sizeof(spSlot *));
-	for (i = 0; i < self->slotsCount; ++i)
-		spSlot_setToSetupPose(self->slots[i]);
-}
-
-spBone *spSkeleton_findBone(const spSkeleton *self, const char *boneName) {
-	int i;
-	for (i = 0; i < self->bonesCount; ++i)
-		if (strcmp(self->data->bones[i]->name, boneName) == 0) return self->bones[i];
-	return 0;
-}
-
-int spSkeleton_findBoneIndex(const spSkeleton *self, const char *boneName) {
-	int i;
-	for (i = 0; i < self->bonesCount; ++i)
-		if (strcmp(self->data->bones[i]->name, boneName) == 0) return i;
-	return -1;
-}
-
-spSlot *spSkeleton_findSlot(const spSkeleton *self, const char *slotName) {
-	int i;
-	for (i = 0; i < self->slotsCount; ++i)
-		if (strcmp(self->data->slots[i]->name, slotName) == 0) return self->slots[i];
-	return 0;
-}
-
-int spSkeleton_findSlotIndex(const spSkeleton *self, const char *slotName) {
-	int i;
-	for (i = 0; i < self->slotsCount; ++i)
-		if (strcmp(self->data->slots[i]->name, slotName) == 0) return i;
-	return -1;
-}
-
-int spSkeleton_setSkinByName(spSkeleton *self, const char *skinName) {
-	spSkin *skin;
-	if (!skinName) {
-		spSkeleton_setSkin(self, 0);
-		return 1;
-	}
-	skin = spSkeletonData_findSkin(self->data, skinName);
-	if (!skin) return 0;
-	spSkeleton_setSkin(self, skin);
-	return 1;
-}
-
-void spSkeleton_setSkin(spSkeleton *self, spSkin *newSkin) {
-	if (self->skin == newSkin) return;
-	if (newSkin) {
-		if (self->skin)
-			spSkin_attachAll(newSkin, self, self->skin);
-		else {
-			/* No previous skin, attach setup pose attachments. */
-			int i;
-			for (i = 0; i < self->slotsCount; ++i) {
-				spSlot *slot = self->slots[i];
-				if (slot->data->attachmentName) {
-					spAttachment *attachment = spSkin_getAttachment(newSkin, i, slot->data->attachmentName);
-					if (attachment) spSlot_setAttachment(slot, attachment);
-				}
-			}
-		}
-	}
-	CONST_CAST(spSkin*, self->skin) = newSkin;
-	spSkeleton_updateCache(self);
-}
-
-spAttachment *
-spSkeleton_getAttachmentForSlotName(const spSkeleton *self, const char *slotName, const char *attachmentName) {
-	int slotIndex = spSkeletonData_findSlotIndex(self->data, slotName);
-	return spSkeleton_getAttachmentForSlotIndex(self, slotIndex, attachmentName);
-}
-
-spAttachment *spSkeleton_getAttachmentForSlotIndex(const spSkeleton *self, int slotIndex, const char *attachmentName) {
-	if (slotIndex == -1) return 0;
-	if (self->skin) {
-		spAttachment *attachment = spSkin_getAttachment(self->skin, slotIndex, attachmentName);
-		if (attachment) return attachment;
-	}
-	if (self->data->defaultSkin) {
-		spAttachment *attachment = spSkin_getAttachment(self->data->defaultSkin, slotIndex, attachmentName);
-		if (attachment) return attachment;
-	}
-	return 0;
-}
-
-int spSkeleton_setAttachment(spSkeleton *self, const char *slotName, const char *attachmentName) {
-	int i;
-	for (i = 0; i < self->slotsCount; ++i) {
-		spSlot *slot = self->slots[i];
-		if (strcmp(slot->data->name, slotName) == 0) {
-			if (!attachmentName)
-				spSlot_setAttachment(slot, 0);
-			else {
-				spAttachment *attachment = spSkeleton_getAttachmentForSlotIndex(self, i, attachmentName);
-				if (!attachment) return 0;
-				spSlot_setAttachment(slot, attachment);
-			}
-			return 1;
-		}
-	}
-	return 0;
-}
-
-spIkConstraint *spSkeleton_findIkConstraint(const spSkeleton *self, const char *constraintName) {
-	int i;
-	for (i = 0; i < self->ikConstraintsCount; ++i)
-		if (strcmp(self->ikConstraints[i]->data->name, constraintName) == 0) return self->ikConstraints[i];
-	return 0;
-}
-
-spTransformConstraint *spSkeleton_findTransformConstraint(const spSkeleton *self, const char *constraintName) {
-	int i;
-	for (i = 0; i < self->transformConstraintsCount; ++i)
-		if (strcmp(self->transformConstraints[i]->data->name, constraintName) == 0)
-			return self->transformConstraints[i];
-	return 0;
-}
-
-spPathConstraint *spSkeleton_findPathConstraint(const spSkeleton *self, const char *constraintName) {
-	int i;
-	for (i = 0; i < self->pathConstraintsCount; ++i)
-		if (strcmp(self->pathConstraints[i]->data->name, constraintName) == 0) return self->pathConstraints[i];
-	return 0;
-}
-
-void spSkeleton_update(spSkeleton *self, float deltaTime) {
-	self->time += deltaTime;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/Skeleton.h>
+#include <spine/extension.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef enum {
+	SP_UPDATE_BONE,
+	SP_UPDATE_IK_CONSTRAINT,
+	SP_UPDATE_PATH_CONSTRAINT,
+	SP_UPDATE_TRANSFORM_CONSTRAINT
+} _spUpdateType;
+
+typedef struct {
+	_spUpdateType type;
+	void *object;
+} _spUpdate;
+
+typedef struct {
+	spSkeleton super;
+
+	int updateCacheCount;
+	int updateCacheCapacity;
+	_spUpdate *updateCache;
+} _spSkeleton;
+
+spSkeleton *spSkeleton_create(spSkeletonData *data) {
+	int i;
+	int *childrenCounts;
+
+	_spSkeleton *internal = NEW(_spSkeleton);
+	spSkeleton *self = SUPER(internal);
+	CONST_CAST(spSkeletonData *, self->data) = data;
+
+	self->bonesCount = self->data->bonesCount;
+	self->bones = MALLOC(spBone *, self->bonesCount);
+	childrenCounts = CALLOC(int, self->bonesCount);
+
+	for (i = 0; i < self->bonesCount; ++i) {
+		spBoneData *boneData = self->data->bones[i];
+		spBone *newBone;
+		if (!boneData->parent)
+			newBone = spBone_create(boneData, self, 0);
+		else {
+			spBone *parent = self->bones[boneData->parent->index];
+			newBone = spBone_create(boneData, self, parent);
+			++childrenCounts[boneData->parent->index];
+		}
+		self->bones[i] = newBone;
+	}
+	for (i = 0; i < self->bonesCount; ++i) {
+		spBoneData *boneData = self->data->bones[i];
+		spBone *bone = self->bones[i];
+		CONST_CAST(spBone **, bone->children) = MALLOC(spBone *, childrenCounts[boneData->index]);
+	}
+	for (i = 0; i < self->bonesCount; ++i) {
+		spBone *bone = self->bones[i];
+		spBone *parent = bone->parent;
+		if (parent)
+			parent->children[parent->childrenCount++] = bone;
+	}
+	CONST_CAST(spBone *, self->root) = (self->bonesCount > 0 ? self->bones[0] : NULL);
+
+	self->slotsCount = data->slotsCount;
+	self->slots = MALLOC(spSlot *, self->slotsCount);
+	for (i = 0; i < self->slotsCount; ++i) {
+		spSlotData *slotData = data->slots[i];
+		spBone *bone = self->bones[slotData->boneData->index];
+		self->slots[i] = spSlot_create(slotData, bone);
+	}
+
+	self->drawOrder = MALLOC(spSlot *, self->slotsCount);
+	memcpy(self->drawOrder, self->slots, sizeof(spSlot *) * self->slotsCount);
+
+	self->ikConstraintsCount = data->ikConstraintsCount;
+	self->ikConstraints = MALLOC(spIkConstraint *, self->ikConstraintsCount);
+	for (i = 0; i < self->data->ikConstraintsCount; ++i)
+		self->ikConstraints[i] = spIkConstraint_create(self->data->ikConstraints[i], self);
+
+	self->transformConstraintsCount = data->transformConstraintsCount;
+	self->transformConstraints = MALLOC(spTransformConstraint *, self->transformConstraintsCount);
+	for (i = 0; i < self->data->transformConstraintsCount; ++i)
+		self->transformConstraints[i] = spTransformConstraint_create(self->data->transformConstraints[i], self);
+
+	self->pathConstraintsCount = data->pathConstraintsCount;
+	self->pathConstraints = MALLOC(spPathConstraint *, self->pathConstraintsCount);
+	for (i = 0; i < self->data->pathConstraintsCount; i++)
+		self->pathConstraints[i] = spPathConstraint_create(self->data->pathConstraints[i], self);
+
+	spColor_setFromFloats(&self->color, 1, 1, 1, 1);
+
+	self->scaleX = 1;
+	self->scaleY = 1;
+
+	spSkeleton_updateCache(self);
+
+	FREE(childrenCounts);
+
+	return self;
+}
+
+void spSkeleton_dispose(spSkeleton *self) {
+	int i;
+	_spSkeleton *internal = SUB_CAST(_spSkeleton, self);
+
+	FREE(internal->updateCache);
+
+	for (i = 0; i < self->bonesCount; ++i)
+		spBone_dispose(self->bones[i]);
+	FREE(self->bones);
+
+	for (i = 0; i < self->slotsCount; ++i)
+		spSlot_dispose(self->slots[i]);
+	FREE(self->slots);
+
+	for (i = 0; i < self->ikConstraintsCount; ++i)
+		spIkConstraint_dispose(self->ikConstraints[i]);
+	FREE(self->ikConstraints);
+
+	for (i = 0; i < self->transformConstraintsCount; ++i)
+		spTransformConstraint_dispose(self->transformConstraints[i]);
+	FREE(self->transformConstraints);
+
+	for (i = 0; i < self->pathConstraintsCount; i++)
+		spPathConstraint_dispose(self->pathConstraints[i]);
+	FREE(self->pathConstraints);
+
+	FREE(self->drawOrder);
+	FREE(self);
+}
+
+static void _addToUpdateCache(_spSkeleton *const internal, _spUpdateType type, void *object) {
+	_spUpdate *update;
+	if (internal->updateCacheCount == internal->updateCacheCapacity) {
+		internal->updateCacheCapacity *= 2;
+		internal->updateCache = (_spUpdate *) realloc(internal->updateCache,
+													  sizeof(_spUpdate) * internal->updateCacheCapacity);
+	}
+	update = internal->updateCache + internal->updateCacheCount;
+	update->type = type;
+	update->object = object;
+	++internal->updateCacheCount;
+}
+
+static void _sortBone(_spSkeleton *const internal, spBone *bone) {
+	if (bone->sorted) return;
+	if (bone->parent) _sortBone(internal, bone->parent);
+	bone->sorted = 1;
+	_addToUpdateCache(internal, SP_UPDATE_BONE, bone);
+}
+
+static void
+_sortPathConstraintAttachmentBones(_spSkeleton *const internal, spAttachment *attachment, spBone *slotBone) {
+	spPathAttachment *pathAttachment = (spPathAttachment *) attachment;
+	int *pathBones;
+	int pathBonesCount;
+	if (pathAttachment->super.super.type != SP_ATTACHMENT_PATH) return;
+	pathBones = pathAttachment->super.bones;
+	pathBonesCount = pathAttachment->super.bonesCount;
+	if (pathBones == 0)
+		_sortBone(internal, slotBone);
+	else {
+		spBone **bones = internal->super.bones;
+		int i = 0, n;
+
+		for (i = 0, n = pathBonesCount; i < n;) {
+			int nn = pathBones[i++];
+			nn += i;
+			while (i < nn)
+				_sortBone(internal, bones[pathBones[i++]]);
+		}
+	}
+}
+
+static void _sortPathConstraintAttachment(_spSkeleton *const internal, spSkin *skin, int slotIndex, spBone *slotBone) {
+	_Entry *entry = SUB_CAST(_spSkin, skin)->entries;
+	while (entry) {
+		if (entry->slotIndex == slotIndex) _sortPathConstraintAttachmentBones(internal, entry->attachment, slotBone);
+		entry = entry->next;
+	}
+}
+
+static void _sortReset(spBone **bones, int bonesCount) {
+	int i;
+	for (i = 0; i < bonesCount; ++i) {
+		spBone *bone = bones[i];
+		if (!bone->active) continue;
+		if (bone->sorted) _sortReset(bone->children, bone->childrenCount);
+		bone->sorted = 0;
+	}
+}
+
+static void _sortIkConstraint(_spSkeleton *const internal, spIkConstraint *constraint) {
+	spBone *target = constraint->target;
+	spBone **constrained;
+	spBone *parent;
+
+	constraint->active = constraint->target->active && (!constraint->data->skinRequired || (internal->super.skin != 0 &&
+																							spIkConstraintDataArray_contains(
+																									internal->super.skin->ikConstraints,
+																									constraint->data)));
+	if (!constraint->active) return;
+
+	_sortBone(internal, target);
+
+	constrained = constraint->bones;
+	parent = constrained[0];
+	_sortBone(internal, parent);
+
+	if (constraint->bonesCount == 1) {
+		_addToUpdateCache(internal, SP_UPDATE_IK_CONSTRAINT, constraint);
+		_sortReset(parent->children, parent->childrenCount);
+	} else {
+		spBone *child = constrained[constraint->bonesCount - 1];
+		_sortBone(internal, child);
+
+		_addToUpdateCache(internal, SP_UPDATE_IK_CONSTRAINT, constraint);
+
+		_sortReset(parent->children, parent->childrenCount);
+		child->sorted = 1;
+	}
+}
+
+static void _sortPathConstraint(_spSkeleton *const internal, spPathConstraint *constraint) {
+	spSlot *slot = constraint->target;
+	int slotIndex = slot->data->index;
+	spBone *slotBone = slot->bone;
+	int i, n, boneCount;
+	spAttachment *attachment;
+	spBone **constrained;
+	spSkeleton *skeleton = SUPER_CAST(spSkeleton, internal);
+
+	constraint->active = constraint->target->bone->active && (!constraint->data->skinRequired ||
+															  (internal->super.skin != 0 &&
+															   spPathConstraintDataArray_contains(
+																	   internal->super.skin->pathConstraints,
+																	   constraint->data)));
+	if (!constraint->active) return;
+
+	if (skeleton->skin) _sortPathConstraintAttachment(internal, skeleton->skin, slotIndex, slotBone);
+	if (skeleton->data->defaultSkin && skeleton->data->defaultSkin != skeleton->skin)
+		_sortPathConstraintAttachment(internal, skeleton->data->defaultSkin, slotIndex, slotBone);
+	for (i = 0, n = skeleton->data->skinsCount; i < n; i++)
+		_sortPathConstraintAttachment(internal, skeleton->data->skins[i], slotIndex, slotBone);
+
+	attachment = slot->attachment;
+	if (attachment && attachment->type == SP_ATTACHMENT_PATH)
+		_sortPathConstraintAttachmentBones(internal, attachment, slotBone);
+
+	constrained = constraint->bones;
+	boneCount = constraint->bonesCount;
+	for (i = 0; i < boneCount; i++)
+		_sortBone(internal, constrained[i]);
+
+	_addToUpdateCache(internal, SP_UPDATE_PATH_CONSTRAINT, constraint);
+
+	for (i = 0; i < boneCount; i++)
+		_sortReset(constrained[i]->children, constrained[i]->childrenCount);
+	for (i = 0; i < boneCount; i++)
+		constrained[i]->sorted = 1;
+}
+
+static void _sortTransformConstraint(_spSkeleton *const internal, spTransformConstraint *constraint) {
+	int i, boneCount;
+	spBone **constrained;
+	spBone *child;
+
+	constraint->active = constraint->target->active && (!constraint->data->skinRequired || (internal->super.skin != 0 &&
+																							spTransformConstraintDataArray_contains(
+																									internal->super.skin->transformConstraints,
+																									constraint->data)));
+	if (!constraint->active) return;
+
+	_sortBone(internal, constraint->target);
+
+	constrained = constraint->bones;
+	boneCount = constraint->bonesCount;
+	if (constraint->data->local) {
+		for (i = 0; i < boneCount; i++) {
+			child = constrained[i];
+			_sortBone(internal, child->parent);
+			_sortBone(internal, child);
+		}
+	} else {
+		for (i = 0; i < boneCount; i++)
+			_sortBone(internal, constrained[i]);
+	}
+
+	_addToUpdateCache(internal, SP_UPDATE_TRANSFORM_CONSTRAINT, constraint);
+
+	for (i = 0; i < boneCount; i++)
+		_sortReset(constrained[i]->children, constrained[i]->childrenCount);
+	for (i = 0; i < boneCount; i++)
+		constrained[i]->sorted = 1;
+}
+
+void spSkeleton_updateCache(spSkeleton *self) {
+	int i, ii;
+	spBone **bones;
+	spIkConstraint **ikConstraints;
+	spPathConstraint **pathConstraints;
+	spTransformConstraint **transformConstraints;
+	int ikCount, transformCount, pathCount, constraintCount;
+	_spSkeleton *internal = SUB_CAST(_spSkeleton, self);
+
+	internal->updateCacheCapacity =
+			self->bonesCount + self->ikConstraintsCount + self->transformConstraintsCount + self->pathConstraintsCount;
+	FREE(internal->updateCache);
+	internal->updateCache = MALLOC(_spUpdate, internal->updateCacheCapacity);
+	internal->updateCacheCount = 0;
+
+	bones = self->bones;
+	for (i = 0; i < self->bonesCount; ++i) {
+		spBone *bone = bones[i];
+		bone->sorted = bone->data->skinRequired;
+		bone->active = !bone->sorted;
+	}
+
+	if (self->skin) {
+		spBoneDataArray *skinBones = self->skin->bones;
+		for (i = 0; i < skinBones->size; i++) {
+			spBone *bone = self->bones[skinBones->items[i]->index];
+			do {
+				bone->sorted = 0;
+				bone->active = -1;
+				bone = bone->parent;
+			} while (bone != 0);
+		}
+	}
+
+	/* IK first, lowest hierarchy depth first. */
+	ikConstraints = self->ikConstraints;
+	transformConstraints = self->transformConstraints;
+	pathConstraints = self->pathConstraints;
+	ikCount = self->ikConstraintsCount;
+	transformCount = self->transformConstraintsCount;
+	pathCount = self->pathConstraintsCount;
+	constraintCount = ikCount + transformCount + pathCount;
+
+	i = 0;
+continue_outer:
+	for (; i < constraintCount; i++) {
+		for (ii = 0; ii < ikCount; ii++) {
+			spIkConstraint *ikConstraint = ikConstraints[ii];
+			if (ikConstraint->data->order == i) {
+				_sortIkConstraint(internal, ikConstraint);
+				i++;
+				goto continue_outer;
+			}
+		}
+
+		for (ii = 0; ii < transformCount; ii++) {
+			spTransformConstraint *transformConstraint = transformConstraints[ii];
+			if (transformConstraint->data->order == i) {
+				_sortTransformConstraint(internal, transformConstraint);
+				i++;
+				goto continue_outer;
+			}
+		}
+
+		for (ii = 0; ii < pathCount; ii++) {
+			spPathConstraint *pathConstraint = pathConstraints[ii];
+			if (pathConstraint->data->order == i) {
+				_sortPathConstraint(internal, pathConstraint);
+				i++;
+				goto continue_outer;
+			}
+		}
+	}
+
+	for (i = 0; i < self->bonesCount; ++i)
+		_sortBone(internal, self->bones[i]);
+}
+
+void spSkeleton_updateWorldTransform(const spSkeleton *self) {
+	int i, n;
+	_spSkeleton *internal = SUB_CAST(_spSkeleton, self);
+
+	for (i = 0, n = self->bonesCount; i < n; i++) {
+		spBone *bone = self->bones[i];
+		bone->ax = bone->x;
+		bone->ay = bone->y;
+		bone->arotation = bone->rotation;
+		bone->ascaleX = bone->scaleX;
+		bone->ascaleY = bone->scaleY;
+		bone->ashearX = bone->shearX;
+		bone->ashearY = bone->shearY;
+	}
+
+	for (i = 0; i < internal->updateCacheCount; ++i) {
+		_spUpdate *update = internal->updateCache + i;
+		switch (update->type) {
+			case SP_UPDATE_BONE:
+				spBone_update((spBone *) update->object);
+				break;
+			case SP_UPDATE_IK_CONSTRAINT:
+				spIkConstraint_update((spIkConstraint *) update->object);
+				break;
+			case SP_UPDATE_TRANSFORM_CONSTRAINT:
+				spTransformConstraint_update((spTransformConstraint *) update->object);
+				break;
+			case SP_UPDATE_PATH_CONSTRAINT:
+				spPathConstraint_update((spPathConstraint *) update->object);
+				break;
+		}
+	}
+}
+
+void spSkeleton_updateWorldTransformWith(const spSkeleton *self, const spBone *parent) {
+	/* Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. */
+	int i;
+	float rotationY, la, lb, lc, ld;
+	_spUpdate *updateCache;
+	_spSkeleton *internal = SUB_CAST(_spSkeleton, self);
+	spBone *rootBone = self->root;
+	float pa = parent->a, pb = parent->b, pc = parent->c, pd = parent->d;
+	CONST_CAST(float, rootBone->worldX) = pa * self->x + pb * self->y + parent->worldX;
+	CONST_CAST(float, rootBone->worldY) = pc * self->x + pd * self->y + parent->worldY;
+
+	rotationY = rootBone->rotation + 90 + rootBone->shearY;
+	la = COS_DEG(rootBone->rotation + rootBone->shearX) * rootBone->scaleX;
+	lb = COS_DEG(rotationY) * rootBone->scaleY;
+	lc = SIN_DEG(rootBone->rotation + rootBone->shearX) * rootBone->scaleX;
+	ld = SIN_DEG(rotationY) * rootBone->scaleY;
+	CONST_CAST(float, rootBone->a) = (pa * la + pb * lc) * self->scaleX;
+	CONST_CAST(float, rootBone->b) = (pa * lb + pb * ld) * self->scaleX;
+	CONST_CAST(float, rootBone->c) = (pc * la + pd * lc) * self->scaleY;
+	CONST_CAST(float, rootBone->d) = (pc * lb + pd * ld) * self->scaleY;
+
+	/* Update everything except root bone. */
+	updateCache = internal->updateCache;
+	for (i = 0; i < internal->updateCacheCount; ++i) {
+		_spUpdate *update = internal->updateCache + i;
+		switch (update->type) {
+			case SP_UPDATE_BONE:
+				if ((spBone *) update->object != rootBone) spBone_updateWorldTransform((spBone *) update->object);
+				break;
+			case SP_UPDATE_IK_CONSTRAINT:
+				spIkConstraint_update((spIkConstraint *) update->object);
+				break;
+			case SP_UPDATE_TRANSFORM_CONSTRAINT:
+				spTransformConstraint_update((spTransformConstraint *) update->object);
+				break;
+			case SP_UPDATE_PATH_CONSTRAINT:
+				spPathConstraint_update((spPathConstraint *) update->object);
+				break;
+		}
+	}
+}
+
+void spSkeleton_setToSetupPose(const spSkeleton *self) {
+	spSkeleton_setBonesToSetupPose(self);
+	spSkeleton_setSlotsToSetupPose(self);
+}
+
+void spSkeleton_setBonesToSetupPose(const spSkeleton *self) {
+	int i;
+	for (i = 0; i < self->bonesCount; ++i)
+		spBone_setToSetupPose(self->bones[i]);
+
+	for (i = 0; i < self->ikConstraintsCount; ++i) {
+		spIkConstraint *ikConstraint = self->ikConstraints[i];
+		ikConstraint->bendDirection = ikConstraint->data->bendDirection;
+		ikConstraint->compress = ikConstraint->data->compress;
+		ikConstraint->stretch = ikConstraint->data->stretch;
+		ikConstraint->softness = ikConstraint->data->softness;
+		ikConstraint->mix = ikConstraint->data->mix;
+	}
+
+	for (i = 0; i < self->transformConstraintsCount; ++i) {
+		spTransformConstraint *constraint = self->transformConstraints[i];
+		spTransformConstraintData *data = constraint->data;
+		constraint->mixRotate = data->mixRotate;
+		constraint->mixX = data->mixX;
+		constraint->mixY = data->mixY;
+		constraint->mixScaleX = data->mixScaleX;
+		constraint->mixScaleY = data->mixScaleY;
+		constraint->mixShearY = data->mixShearY;
+	}
+
+	for (i = 0; i < self->pathConstraintsCount; ++i) {
+		spPathConstraint *constraint = self->pathConstraints[i];
+		spPathConstraintData *data = constraint->data;
+		constraint->position = data->position;
+		constraint->spacing = data->spacing;
+		constraint->mixRotate = data->mixRotate;
+		constraint->mixX = data->mixX;
+		constraint->mixY = data->mixY;
+	}
+}
+
+void spSkeleton_setSlotsToSetupPose(const spSkeleton *self) {
+	int i;
+	memcpy(self->drawOrder, self->slots, self->slotsCount * sizeof(spSlot *));
+	for (i = 0; i < self->slotsCount; ++i)
+		spSlot_setToSetupPose(self->slots[i]);
+}
+
+spBone *spSkeleton_findBone(const spSkeleton *self, const char *boneName) {
+	int i;
+	for (i = 0; i < self->bonesCount; ++i)
+		if (strcmp(self->data->bones[i]->name, boneName) == 0) return self->bones[i];
+	return 0;
+}
+
+int spSkeleton_findBoneIndex(const spSkeleton *self, const char *boneName) {
+	int i;
+	for (i = 0; i < self->bonesCount; ++i)
+		if (strcmp(self->data->bones[i]->name, boneName) == 0) return i;
+	return -1;
+}
+
+spSlot *spSkeleton_findSlot(const spSkeleton *self, const char *slotName) {
+	int i;
+	for (i = 0; i < self->slotsCount; ++i)
+		if (strcmp(self->data->slots[i]->name, slotName) == 0) return self->slots[i];
+	return 0;
+}
+
+int spSkeleton_findSlotIndex(const spSkeleton *self, const char *slotName) {
+	int i;
+	for (i = 0; i < self->slotsCount; ++i)
+		if (strcmp(self->data->slots[i]->name, slotName) == 0) return i;
+	return -1;
+}
+
+int spSkeleton_setSkinByName(spSkeleton *self, const char *skinName) {
+	spSkin *skin;
+	if (!skinName) {
+		spSkeleton_setSkin(self, 0);
+		return 1;
+	}
+	skin = spSkeletonData_findSkin(self->data, skinName);
+	if (!skin) return 0;
+	spSkeleton_setSkin(self, skin);
+	return 1;
+}
+
+void spSkeleton_setSkin(spSkeleton *self, spSkin *newSkin) {
+	if (self->skin == newSkin) return;
+	if (newSkin) {
+		if (self->skin)
+			spSkin_attachAll(newSkin, self, self->skin);
+		else {
+			/* No previous skin, attach setup pose attachments. */
+			int i;
+			for (i = 0; i < self->slotsCount; ++i) {
+				spSlot *slot = self->slots[i];
+				if (slot->data->attachmentName) {
+					spAttachment *attachment = spSkin_getAttachment(newSkin, i, slot->data->attachmentName);
+					if (attachment) spSlot_setAttachment(slot, attachment);
+				}
+			}
+		}
+	}
+	CONST_CAST(spSkin *, self->skin) = newSkin;
+	spSkeleton_updateCache(self);
+}
+
+spAttachment *
+spSkeleton_getAttachmentForSlotName(const spSkeleton *self, const char *slotName, const char *attachmentName) {
+	int slotIndex = spSkeletonData_findSlotIndex(self->data, slotName);
+	return spSkeleton_getAttachmentForSlotIndex(self, slotIndex, attachmentName);
+}
+
+spAttachment *spSkeleton_getAttachmentForSlotIndex(const spSkeleton *self, int slotIndex, const char *attachmentName) {
+	if (slotIndex == -1) return 0;
+	if (self->skin) {
+		spAttachment *attachment = spSkin_getAttachment(self->skin, slotIndex, attachmentName);
+		if (attachment) return attachment;
+	}
+	if (self->data->defaultSkin) {
+		spAttachment *attachment = spSkin_getAttachment(self->data->defaultSkin, slotIndex, attachmentName);
+		if (attachment) return attachment;
+	}
+	return 0;
+}
+
+int spSkeleton_setAttachment(spSkeleton *self, const char *slotName, const char *attachmentName) {
+	int i;
+	for (i = 0; i < self->slotsCount; ++i) {
+		spSlot *slot = self->slots[i];
+		if (strcmp(slot->data->name, slotName) == 0) {
+			if (!attachmentName)
+				spSlot_setAttachment(slot, 0);
+			else {
+				spAttachment *attachment = spSkeleton_getAttachmentForSlotIndex(self, i, attachmentName);
+				if (!attachment) return 0;
+				spSlot_setAttachment(slot, attachment);
+			}
+			return 1;
+		}
+	}
+	return 0;
+}
+
+spIkConstraint *spSkeleton_findIkConstraint(const spSkeleton *self, const char *constraintName) {
+	int i;
+	for (i = 0; i < self->ikConstraintsCount; ++i)
+		if (strcmp(self->ikConstraints[i]->data->name, constraintName) == 0) return self->ikConstraints[i];
+	return 0;
+}
+
+spTransformConstraint *spSkeleton_findTransformConstraint(const spSkeleton *self, const char *constraintName) {
+	int i;
+	for (i = 0; i < self->transformConstraintsCount; ++i)
+		if (strcmp(self->transformConstraints[i]->data->name, constraintName) == 0)
+			return self->transformConstraints[i];
+	return 0;
+}
+
+spPathConstraint *spSkeleton_findPathConstraint(const spSkeleton *self, const char *constraintName) {
+	int i;
+	for (i = 0; i < self->pathConstraintsCount; ++i)
+		if (strcmp(self->pathConstraints[i]->data->name, constraintName) == 0) return self->pathConstraints[i];
+	return 0;
+}
+
+void spSkeleton_update(spSkeleton *self, float deltaTime) {
+	self->time += deltaTime;
+}

+ 1465 - 1466
spine-c/spine-c/src/spine/SkeletonBinary.c

@@ -1,1466 +1,1465 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/SkeletonBinary.h>
-#include <stdio.h>
-#include <spine/extension.h>
-#include <spine/AtlasAttachmentLoader.h>
-#include <spine/Animation.h>
-#include <spine/Array.h>
-
-typedef struct {
-	const unsigned char *cursor;
-	const unsigned char *end;
-} _dataInput;
-
-typedef struct {
-	const char *parent;
-	const char *skin;
-	int slotIndex;
-	spMeshAttachment *mesh;
-	int inheritDeform;
-} _spLinkedMesh;
-
-typedef struct {
-	spSkeletonBinary super;
-	int ownsLoader;
-
-	int linkedMeshCount;
-	int linkedMeshCapacity;
-	_spLinkedMesh *linkedMeshes;
-} _spSkeletonBinary;
-
-spSkeletonBinary *spSkeletonBinary_createWithLoader(spAttachmentLoader *attachmentLoader) {
-	spSkeletonBinary *self = SUPER(NEW(_spSkeletonBinary));
-	self->scale = 1;
-	self->attachmentLoader = attachmentLoader;
-	return self;
-}
-
-spSkeletonBinary *spSkeletonBinary_create(spAtlas *atlas) {
-	spAtlasAttachmentLoader *attachmentLoader = spAtlasAttachmentLoader_create(atlas);
-	spSkeletonBinary *self = spSkeletonBinary_createWithLoader(SUPER(attachmentLoader));
-	SUB_CAST(_spSkeletonBinary, self)->ownsLoader = 1;
-	return self;
-}
-
-void spSkeletonBinary_dispose(spSkeletonBinary *self) {
-	_spSkeletonBinary *internal = SUB_CAST(_spSkeletonBinary, self);
-	if (internal->ownsLoader) spAttachmentLoader_dispose(self->attachmentLoader);
-	FREE(internal->linkedMeshes);
-	FREE(self->error);
-	FREE(self);
-}
-
-void _spSkeletonBinary_setError(spSkeletonBinary *self, 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);
-}
-
-static unsigned char readByte(_dataInput *input) {
-	return *input->cursor++;
-}
-
-static signed char readSByte(_dataInput *input) {
-	return (signed char) readByte(input);
-}
-
-static int readBoolean(_dataInput *input) {
-	return readByte(input) != 0;
-}
-
-static int readInt(_dataInput *input) {
-	int result = readByte(input);
-	result <<= 8;
-	result |= readByte(input);
-	result <<= 8;
-	result |= readByte(input);
-	result <<= 8;
-	result |= readByte(input);
-	return result;
-}
-
-static int readVarint(_dataInput *input, int/*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;
-}
-
-float readFloat(_dataInput *input) {
-	union {
-		int intValue;
-		float floatValue;
-	} intToFloat;
-	intToFloat.intValue = readInt(input);
-	return intToFloat.floatValue;
-}
-
-char *readString(_dataInput *input) {
-	int length = readVarint(input, 1);
-	char *string;
-	if (length == 0) {
-		return 0;
-	}
-	string = MALLOC(char, length);
-	memcpy(string, input->cursor, length - 1);
-	input->cursor += length - 1;
-	string[length - 1] = '\0';
-	return string;
-}
-
-static char *readStringRef(_dataInput *input, spSkeletonData *skeletonData) {
-	int index = readVarint(input, 1);
-	return index == 0 ? 0 : skeletonData->strings[index - 1];
-}
-
-static void readColor(_dataInput *input, float *r, float *g, float *b, float *a) {
-	*r = readByte(input) / 255.0f;
-	*g = readByte(input) / 255.0f;
-	*b = readByte(input) / 255.0f;
-	*a = readByte(input) / 255.0f;
-}
-
-#define ATTACHMENT_REGION 0
-#define ATTACHMENT_BOUNDING_BOX 1
-#define ATTACHMENT_MESH 2
-#define ATTACHMENT_LINKED_MESH 3
-#define ATTACHMENT_PATH 4
-
-#define BLEND_MODE_NORMAL 0
-#define BLEND_MODE_ADDITIVE 1
-#define BLEND_MODE_MULTIPLY 2
-#define BLEND_MODE_SCREEN 3
-
-#define BONE_ROTATE 0
-#define BONE_TRANSLATE 1
-#define BONE_TRANSLATEX 2
-#define BONE_TRANSLATEY 3
-#define BONE_SCALE 4
-#define BONE_SCALEX 5
-#define BONE_SCALEY 6
-#define BONE_SHEAR 7
-#define BONE_SHEARX 8
-#define BONE_SHEARY 9
-
-#define SLOT_ATTACHMENT 0
-#define SLOT_RGBA 1
-#define SLOT_RGB 2
-#define SLOT_RGBA2 3
-#define SLOT_RGB2 4
-#define SLOT_ALPHA 5
-
-#define PATH_POSITION 0
-#define PATH_SPACING 1
-#define PATH_MIX 2
-
-#define CURVE_LINEAR 0
-#define CURVE_STEPPED 1
-#define CURVE_BEZIER 2
-
-#define PATH_POSITION_FIXED 0
-#define PATH_POSITION_PERCENT 1
-
-#define PATH_SPACING_LENGTH 0
-#define PATH_SPACING_FIXED 1
-#define PATH_SPACING_PERCENT 2
-
-#define PATH_ROTATE_TANGENT 0
-#define PATH_ROTATE_CHAIN 1
-#define PATH_ROTATE_CHAIN_SCALE 2
-
-static void
-setBezier(_dataInput *input, spTimeline *timeline, int bezier, int frame, int value, float time1, float time2,
-		  float value1, float value2, float scale) {
-	float cx1 = readFloat(input);
-	float cy1 = readFloat(input);
-	float cx2 = readFloat(input);
-	float cy2 = readFloat(input);
-	spTimeline_setBezier(timeline, bezier, frame, value, time1, value1, cx1, cy1 * scale, cx2, cy2 * scale, time2,
-						 value2);
-}
-
-static spTimeline *readTimeline(_dataInput *input, spCurveTimeline1 *timeline, float scale) {
-	int frame, bezier, frameLast;
-	float time2, value2;
-	float time = readFloat(input);
-	float value = readFloat(input) * scale;
-	for (frame = 0, bezier = 0, frameLast = timeline->super.frameCount - 1;; frame++) {
-		spCurveTimeline1_setFrame(timeline, frame, time, value);
-		if (frame == frameLast) break;
-		time2 = readFloat(input);
-		value2 = readFloat(input) * scale;
-		switch (readSByte(input)) {
-			case CURVE_STEPPED:
-				spCurveTimeline_setStepped(timeline, frame);
-				break;
-			case CURVE_BEZIER:
-				setBezier(input, SUPER(timeline), bezier++, frame, 0, time, time2, value, value2, 1);
-		}
-		time = time2;
-		value = value2;
-	}
-	return SUPER(timeline);
-}
-
-static spTimeline *readTimeline2(_dataInput *input, spCurveTimeline2 *timeline, float scale) {
-	int frame, bezier, frameLast;
-	float time2, nvalue1, nvalue2;
-	float time = readFloat(input);
-	float value1 = readFloat(input) * scale;
-	float value2 = readFloat(input) * scale;
-	for (frame = 0, bezier = 0, frameLast = timeline->super.frameCount - 1;; frame++) {
-		spCurveTimeline2_setFrame(timeline, frame, time, value1, value2);
-		if (frame == frameLast) break;
-		time2 = readFloat(input);
-		nvalue1 = readFloat(input) * scale;
-		nvalue2 = readFloat(input) * scale;
-		switch (readSByte(input)) {
-			case CURVE_STEPPED:
-				spCurveTimeline_setStepped(timeline, frame);
-				break;
-			case CURVE_BEZIER:
-				setBezier(input, SUPER(timeline), bezier++, frame, 0, time, time2, value1, nvalue1, scale);
-				setBezier(input, SUPER(timeline), bezier++, frame, 1, time, time2, value2, nvalue2, scale);
-		}
-		time = time2;
-		value1 = nvalue1;
-		value2 = nvalue2;
-	}
-	return SUPER(timeline);
-}
-
-static void _spSkeletonBinary_addLinkedMesh(spSkeletonBinary *self, spMeshAttachment *mesh,
-											const char *skin, int slotIndex, const char *parent, int inheritDeform) {
-	_spLinkedMesh *linkedMesh;
-	_spSkeletonBinary *internal = SUB_CAST(_spSkeletonBinary, self);
-
-	if (internal->linkedMeshCount == internal->linkedMeshCapacity) {
-		_spLinkedMesh *linkedMeshes;
-		internal->linkedMeshCapacity *= 2;
-		if (internal->linkedMeshCapacity < 8) internal->linkedMeshCapacity = 8;
-		/* TODO Why not realloc? */
-		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->inheritDeform = inheritDeform;
-}
-
-static spAnimation *_spSkeletonBinary_readAnimation(spSkeletonBinary *self, const char *name,
-													_dataInput *input, spSkeletonData *skeletonData) {
-	spTimelineArray *timelines = spTimelineArray_create(18);
-	float duration = 0;
-	int i, n, ii, nn, iii, nnn;
-	int frame, bezier;
-	int drawOrderCount, eventCount;
-	spAnimation *animation;
-	float scale = self->scale;
-
-	int numTimelines = readVarint(input, 1);
-	UNUSED(numTimelines);
-
-	/* Slot timelines. */
-	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
-		int slotIndex = readVarint(input, 1);
-		for (ii = 0, nn = readVarint(input, 1); ii < nn; ++ii) {
-			unsigned char timelineType = readByte(input);
-			int frameCount = readVarint(input, 1);
-			int frameLast = frameCount - 1;
-			switch (timelineType) {
-				case SLOT_ATTACHMENT: {
-					spAttachmentTimeline *timeline = spAttachmentTimeline_create(frameCount, slotIndex);
-					for (frame = 0; frame < frameCount; ++frame) {
-						float time = readFloat(input);
-						const char *attachmentName = readStringRef(input, skeletonData);
-						spAttachmentTimeline_setFrame(timeline, frame, time, attachmentName);
-					}
-					spTimelineArray_add(timelines, SUPER(timeline));
-					break;
-				}
-				case SLOT_RGBA: {
-					int bezierCount = readVarint(input, 1);
-					spRGBATimeline *timeline = spRGBATimeline_create(frameCount, bezierCount, slotIndex);
-
-					float time = readFloat(input);
-					float r = readByte(input) / 255.0;
-					float g = readByte(input) / 255.0;
-					float b = readByte(input) / 255.0;
-					float a = readByte(input) / 255.0;
-
-					for (frame = 0, bezier = 0;; frame++) {
-						float time2, r2, g2, b2, a2;
-						spRGBATimeline_setFrame(timeline, frame, time, r, g, b, a);
-						if (frame == frameLast) break;
-
-						time2 = readFloat(input);
-						r2 = readByte(input) / 255.0;
-						g2 = readByte(input) / 255.0;
-						b2 = readByte(input) / 255.0;
-						a2 = readByte(input) / 255.0;
-
-						switch (readSByte(input)) {
-							case CURVE_STEPPED:
-								spCurveTimeline_setStepped(SUPER(timeline), frame);
-								break;
-							case CURVE_BEZIER:
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, r, r2, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, g, g2, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 2, time, time2, b, b2, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 3, time, time2, a, a2, 1);
-						}
-						time = time2;
-						r = r2;
-						g = g2;
-						b = b2;
-						a = a2;
-					}
-					spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
-					break;
-				}
-				case SLOT_RGB: {
-					int bezierCount = readVarint(input, 1);
-					spRGBTimeline *timeline = spRGBTimeline_create(frameCount, bezierCount, slotIndex);
-
-					float time = readFloat(input);
-					float r = readByte(input) / 255.0;
-					float g = readByte(input) / 255.0;
-					float b = readByte(input) / 255.0;
-
-					for (frame = 0, bezier = 0;; frame++) {
-						float time2, r2, g2, b2;
-						spRGBTimeline_setFrame(timeline, frame, time, r, g, b);
-						if (frame == frameLast) break;
-
-						time2 = readFloat(input);
-						r2 = readByte(input) / 255.0;
-						g2 = readByte(input) / 255.0;
-						b2 = readByte(input) / 255.0;
-
-						switch (readSByte(input)) {
-							case CURVE_STEPPED:
-								spCurveTimeline_setStepped(SUPER(timeline), frame);
-								break;
-							case CURVE_BEZIER:
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, r, r2, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, g, g2, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 2, time, time2, b, b2, 1);
-						}
-						time = time2;
-						r = r2;
-						g = g2;
-						b = b2;
-					}
-					spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
-					break;
-				}
-				case SLOT_RGBA2: {
-					int bezierCount = readVarint(input, 1);
-					spRGBA2Timeline *timeline = spRGBA2Timeline_create(frameCount, bezierCount, slotIndex);
-
-					float time = readFloat(input);
-					float r = readByte(input) / 255.0;
-					float g = readByte(input) / 255.0;
-					float b = readByte(input) / 255.0;
-					float a = readByte(input) / 255.0;
-					float r2 = readByte(input) / 255.0;
-					float g2 = readByte(input) / 255.0;
-					float b2 = readByte(input) / 255.0;
-
-					for (frame = 0, bezier = 0;; frame++) {
-						float time2, nr, ng, nb, na, nr2, ng2, nb2;
-						spRGBA2Timeline_setFrame(timeline, frame, time, r, g, b, a, r2, g2, b2);
-						if (frame == frameLast) break;
-						time2 = readFloat(input);
-						nr = readByte(input) / 255.0;
-						ng = readByte(input) / 255.0;
-						nb = readByte(input) / 255.0;
-						na = readByte(input) / 255.0;
-						nr2 = readByte(input) / 255.0;
-						ng2 = readByte(input) / 255.0;
-						nb2 = readByte(input) / 255.0;
-
-						switch (readSByte(input)) {
-							case CURVE_STEPPED:
-								spCurveTimeline_setStepped(SUPER(timeline), frame);
-								break;
-							case CURVE_BEZIER:
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, r, nr, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, g, ng, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 2, time, time2, b, nb, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 3, time, time2, a, na, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 4, time, time2, r2, nr2, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 5, time, time2, g2, ng2, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 6, time, time2, b2, nb2, 1);
-						}
-						time = time2;
-						r = nr;
-						g = ng;
-						b = nb;
-						a = na;
-						r2 = nr2;
-						g2 = ng2;
-						b2 = nb2;
-					}
-					spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
-					break;
-				}
-				case SLOT_RGB2: {
-					int bezierCount = readVarint(input, 1);
-					spRGB2Timeline *timeline = spRGB2Timeline_create(frameCount, bezierCount, slotIndex);
-
-					float time = readFloat(input);
-					float r = readByte(input) / 255.0;
-					float g = readByte(input) / 255.0;
-					float b = readByte(input) / 255.0;
-					float r2 = readByte(input) / 255.0;
-					float g2 = readByte(input) / 255.0;
-					float b2 = readByte(input) / 255.0;
-
-					for (frame = 0, bezier = 0;; frame++) {
-						float time2, nr, ng, nb, nr2, ng2, nb2;
-						spRGB2Timeline_setFrame(timeline, frame, time, r, g, b, r2, g2, b2);
-						if (frame == frameLast) break;
-						time2 = readFloat(input);
-						nr = readByte(input) / 255.0;
-						ng = readByte(input) / 255.0;
-						nb = readByte(input) / 255.0;
-						nr2 = readByte(input) / 255.0;
-						ng2 = readByte(input) / 255.0;
-						nb2 = readByte(input) / 255.0;
-
-						switch (readSByte(input)) {
-							case CURVE_STEPPED:
-								spCurveTimeline_setStepped(SUPER(timeline), frame);
-								break;
-							case CURVE_BEZIER:
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, r, nr, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, g, ng, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 2, time, time2, b, nb, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 3, time, time2, r2, nr2, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 4, time, time2, g2, ng2, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 5, time, time2, b2, nb2, 1);
-						}
-						time = time2;
-						r = nr;
-						g = ng;
-						b = nb;
-						r2 = nr2;
-						g2 = ng2;
-						b2 = nb2;
-					}
-					spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
-					break;
-				}
-				case SLOT_ALPHA: {
-					int bezierCount = readVarint(input, 1);
-					spAlphaTimeline *timeline = spAlphaTimeline_create(frameCount, bezierCount, slotIndex);
-					float time = readFloat(input);
-					float a = readByte(input) / 255.0;
-					for (frame = 0, bezier = 0;; frame++) {
-						float time2, a2;
-						spAlphaTimeline_setFrame(timeline, frame, time, a);
-						if (frame == frameLast) break;
-						time2 = readFloat(input);
-						a2 = readByte(input) / 255;
-						switch (readSByte(input)) {
-							case CURVE_STEPPED:
-								spCurveTimeline_setStepped(SUPER(timeline), frame);
-								break;
-							case CURVE_BEZIER:
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, a, a2, 1);
-						}
-						time = time2;
-						a = a2;
-					}
-					spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
-					break;
-				}
-				default: {
-					return NULL;
-				}
-			}
-		}
-	}
-
-	/* Bone timelines. */
-	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
-		int boneIndex = readVarint(input, 1);
-		for (ii = 0, nn = readVarint(input, 1); ii < nn; ++ii) {
-			unsigned char timelineType = readByte(input);
-			int frameCount = readVarint(input, 1);
-			int bezierCount = readVarint(input, 1);
-			spTimeline *timeline = NULL;
-			switch (timelineType) {
-				case BONE_ROTATE:
-					timeline = readTimeline(input, SUPER(spRotateTimeline_create(frameCount, bezierCount, boneIndex)),
-											1);
-					break;
-				case BONE_TRANSLATE:
-					timeline = readTimeline2(input,
-											 SUPER(spTranslateTimeline_create(frameCount, bezierCount, boneIndex)),
-											 scale);
-					break;
-				case BONE_TRANSLATEX:
-					timeline = readTimeline(input,
-											SUPER(spTranslateXTimeline_create(frameCount, bezierCount, boneIndex)),
-											scale);
-					break;
-				case BONE_TRANSLATEY:
-					timeline = readTimeline(input,
-											SUPER(spTranslateYTimeline_create(frameCount, bezierCount, boneIndex)),
-											scale);
-					break;
-				case BONE_SCALE:
-					timeline = readTimeline2(input, SUPER(spScaleTimeline_create(frameCount, bezierCount, boneIndex)),
-											 1);
-					break;
-				case BONE_SCALEX:
-					timeline = readTimeline(input, SUPER(spScaleXTimeline_create(frameCount, bezierCount, boneIndex)),
-											1);
-					break;
-				case BONE_SCALEY:
-					timeline = readTimeline(input, SUPER(spScaleYTimeline_create(frameCount, bezierCount, boneIndex)),
-											1);
-					break;
-				case BONE_SHEAR:
-					timeline = readTimeline2(input, SUPER(spShearTimeline_create(frameCount, bezierCount, boneIndex)),
-											 1);
-					break;
-				case BONE_SHEARX:
-					timeline = readTimeline(input, SUPER(spShearXTimeline_create(frameCount, bezierCount, boneIndex)),
-											1);
-					break;
-				case BONE_SHEARY:
-					timeline = readTimeline(input, SUPER(spShearYTimeline_create(frameCount, bezierCount, boneIndex)),
-											1);
-					break;
-				default: {
-					for (iii = 0; iii < timelines->size; ++iii)
-						spTimeline_dispose(timelines->items[iii]);
-					spTimelineArray_dispose(timelines);
-					_spSkeletonBinary_setError(self, "Invalid timeline type for a bone: ",
-											   skeletonData->bones[boneIndex]->name);
-					return NULL;
-				}
-			}
-			spTimelineArray_add(timelines, timeline);
-		}
-	}
-
-	/* IK constraint timelines. */
-	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
-		int index = readVarint(input, 1);
-		int frameCount = readVarint(input, 1);
-		int frameLast = frameCount - 1;
-		int bezierCount = readVarint(input, 1);
-		spIkConstraintTimeline *timeline = spIkConstraintTimeline_create(frameCount, bezierCount, index);
-		float time = readFloat(input);
-		float mix = readFloat(input);
-		float softness = readFloat(input) * scale;
-		for (frame = 0, bezier = 0;; frame++) {
-			float time2, mix2, softness2;
-			int bendDirection = readSByte(input);
-			int compress = readBoolean(input);
-			int stretch = readBoolean(input);
-			spIkConstraintTimeline_setFrame(timeline, frame, time, mix, softness, bendDirection, compress, stretch);
-			if (frame == frameLast) break;
-			time2 = readFloat(input);
-			mix2 = readFloat(input);
-			softness2 = readFloat(input) * scale;
-			switch (readSByte(input)) {
-				case CURVE_STEPPED:
-					spCurveTimeline_setStepped(SUPER(timeline), frame);
-					break;
-				case CURVE_BEZIER:
-					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, mix, mix2, 1);
-					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, softness, softness2,
-							  scale);
-			}
-			time = time2;
-			mix = mix2;
-			softness = softness2;
-		}
-		spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
-	}
-
-	/* Transform constraint timelines. */
-	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
-		int index = readVarint(input, 1);
-		int frameCount = readVarint(input, 1);
-		int frameLast = frameCount - 1;
-		int bezierCount = readVarint(input, 1);
-		spTransformConstraintTimeline *timeline = spTransformConstraintTimeline_create(frameCount, bezierCount, index);
-		float time = readFloat(input);
-		float mixRotate = readFloat(input);
-		float mixX = readFloat(input);
-		float mixY = readFloat(input);
-		float mixScaleX = readFloat(input);
-		float mixScaleY = readFloat(input);
-		float mixShearY = readFloat(input);
-		for (frame = 0, bezier = 0;; frame++) {
-			float time2, mixRotate2, mixX2, mixY2, mixScaleX2, mixScaleY2, mixShearY2;
-			spTransformConstraintTimeline_setFrame(timeline, frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY,
-												   mixShearY);
-			if (frame == frameLast) break;
-			time2 = readFloat(input);
-			mixRotate2 = readFloat(input);
-			mixX2 = readFloat(input);
-			mixY2 = readFloat(input);
-			mixScaleX2 = readFloat(input);
-			mixScaleY2 = readFloat(input);
-			mixShearY2 = readFloat(input);
-			switch (readSByte(input)) {
-				case CURVE_STEPPED:
-					spCurveTimeline_setStepped(SUPER(timeline), frame);
-					break;
-				case CURVE_BEZIER:
-					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1);
-					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, mixX, mixX2, 1);
-					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 2, time, time2, mixY, mixY2, 1);
-					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 3, time, time2, mixScaleX, mixScaleX2, 1);
-					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 4, time, time2, mixScaleY, mixScaleY2, 1);
-					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 5, time, time2, mixShearY, mixShearY2, 1);
-			}
-			time = time2;
-			mixRotate = mixRotate2;
-			mixX = mixX2;
-			mixY = mixY2;
-			mixScaleX = mixScaleX2;
-			mixScaleY = mixScaleY2;
-			mixShearY = mixShearY2;
-		}
-		spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
-	}
-
-	/* Path constraint timelines. */
-	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
-		int index = readVarint(input, 1);
-		spPathConstraintData *data = skeletonData->pathConstraints[index];
-		for (ii = 0, nn = readVarint(input, 1); ii < nn; ++ii) {
-			int type = readSByte(input);
-			int frameCount = readVarint(input, 1);
-			int bezierCount = readVarint(input, 1);
-			switch (type) {
-				case PATH_POSITION: {
-					spTimelineArray_add(timelines, readTimeline(input, SUPER(spPathConstraintPositionTimeline_create(
-							frameCount, bezierCount, index)),
-																data->positionMode == SP_POSITION_MODE_FIXED ? scale
-																											 : 1));
-					break;
-				}
-				case PATH_SPACING: {
-					spTimelineArray_add(timelines, readTimeline(input,
-																SUPER(spPathConstraintSpacingTimeline_create(frameCount,
-																											 bezierCount,
-																											 index)),
-																data->spacingMode == SP_SPACING_MODE_LENGTH ||
-																data->spacingMode == SP_SPACING_MODE_FIXED ? scale
-																										   : 1));
-					break;
-				}
-				case PATH_MIX: {
-					float time, mixRotate, mixX, mixY;
-					int frameLast;
-					spPathConstraintMixTimeline *timeline = spPathConstraintMixTimeline_create(frameCount, bezierCount,
-																							   index);
-					time = readFloat(input);
-					mixRotate = readFloat(input);
-					mixX = readFloat(input);
-					mixY = readFloat(input);
-					for (frame = 0, bezier = 0, frameLast = timeline->super.super.frameCount - 1;; frame++) {
-						float time2, mixRotate2, mixX2, mixY2;
-						spPathConstraintMixTimeline_setFrame(timeline, frame, time, mixRotate, mixX, mixY);
-						if (frame == frameLast) break;
-						time2 = readFloat(input);
-						mixRotate2 = readFloat(input);
-						mixX2 = readFloat(input);
-						mixY2 = readFloat(input);
-						switch (readSByte(input)) {
-							case CURVE_STEPPED:
-								spCurveTimeline_setStepped(SUPER(timeline), frame);
-								break;
-							case CURVE_BEZIER:
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, mixRotate,
-										  mixRotate2, 1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, mixX, mixX2,
-										  1);
-								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 2, time, time2, mixY, mixY2,
-										  1);
-
-						}
-						time = time2;
-						mixRotate = mixRotate2;
-						mixX = mixX2;
-						mixY = mixY2;
-					}
-					spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
-				}
-			}
-		}
-	}
-
-	/* Deform timelines. */
-	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
-		spSkin *skin = skeletonData->skins[readVarint(input, 1)];
-		for (ii = 0, nn = readVarint(input, 1); ii < nn; ++ii) {
-			int slotIndex = readVarint(input, 1);
-			for (iii = 0, nnn = readVarint(input, 1); iii < nnn; ++iii) {
-				float *tempDeform;
-				spDeformTimeline *timeline;
-				int weighted, deformLength;
-				const char *attachmentName = readStringRef(input, skeletonData);
-				int frameCount, frameLast, bezierCount;
-				float time, time2;
-
-				spVertexAttachment *attachment = SUB_CAST(spVertexAttachment,
-														  spSkin_getAttachment(skin, slotIndex, attachmentName));
-				if (!attachment) {
-					for (i = 0; i < timelines->size; ++i)
-						spTimeline_dispose(timelines->items[i]);
-					spTimelineArray_dispose(timelines);
-					_spSkeletonBinary_setError(self, "Attachment not found: ", attachmentName);
-					return 0;
-				}
-
-				weighted = attachment->bones != 0;
-				deformLength = weighted ? attachment->verticesCount / 3 * 2 : attachment->verticesCount;
-				tempDeform = MALLOC(float, deformLength);
-
-				frameCount = readVarint(input, 1);
-				frameLast = frameCount - 1;
-				bezierCount = readVarint(input, 1);
-				timeline = spDeformTimeline_create(frameCount, deformLength, bezierCount, slotIndex, attachment);
-
-				time = readFloat(input);
-				for (frame = 0, bezier = 0;; ++frame) {
-					float *deform;
-					int end = readVarint(input, 1);
-					if (!end) {
-						if (weighted) {
-							deform = tempDeform;
-							memset(deform, 0, sizeof(float) * deformLength);
-						} else
-							deform = attachment->vertices;
-					} else {
-						int v, start = readVarint(input, 1);
-						deform = tempDeform;
-						memset(deform, 0, sizeof(float) * start);
-						end += start;
-						if (self->scale == 1) {
-							for (v = start; v < end; ++v)
-								deform[v] = readFloat(input);
-						} else {
-							for (v = start; v < end; ++v)
-								deform[v] = readFloat(input) * self->scale;
-						}
-						memset(deform + v, 0, sizeof(float) * (deformLength - v));
-						if (!weighted) {
-							float *vertices = attachment->vertices;
-							for (v = 0; v < deformLength; ++v)
-								deform[v] += vertices[v];
-						}
-					}
-					spDeformTimeline_setFrame(timeline, frame, time, deform);
-					if (frame == frameLast) break;
-					time2 = readFloat(input);
-					switch (readSByte(input)) {
-						case CURVE_STEPPED:
-							spCurveTimeline_setStepped(SUPER(timeline), frame);
-							break;
-						case CURVE_BEZIER:
-							setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, 0, 1, 1);
-					}
-					time = time2;
-				}
-				FREE(tempDeform);
-
-				spTimelineArray_add(timelines, (spTimeline *) timeline);
-			}
-		}
-	}
-
-	/* Draw order timeline. */
-	drawOrderCount = readVarint(input, 1);
-	if (drawOrderCount) {
-		spDrawOrderTimeline *timeline = spDrawOrderTimeline_create(drawOrderCount, skeletonData->slotsCount);
-		for (i = 0; i < drawOrderCount; ++i) {
-			float time = readFloat(input);
-			int offsetCount = readVarint(input, 1);
-			int *drawOrder = MALLOC(int, skeletonData->slotsCount);
-			int *unchanged = MALLOC(int, skeletonData->slotsCount - offsetCount);
-			int originalIndex = 0, unchangedIndex = 0;
-			memset(drawOrder, -1, sizeof(int) * skeletonData->slotsCount);
-			for (ii = 0; ii < offsetCount; ++ii) {
-				int slotIndex = readVarint(input, 1);
-				/* Collect unchanged items. */
-				while (originalIndex != slotIndex)
-					unchanged[unchangedIndex++] = originalIndex++;
-				/* Set changed items. */
-				drawOrder[originalIndex + readVarint(input, 1)] = 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);
-			/* TODO Avoid copying of drawOrder inside */
-			spDrawOrderTimeline_setFrame(timeline, i, time, drawOrder);
-			FREE(drawOrder);
-		}
-		spTimelineArray_add(timelines, (spTimeline *) timeline);
-	}
-
-	/* Event timeline. */
-	eventCount = readVarint(input, 1);
-	if (eventCount) {
-		spEventTimeline *timeline = spEventTimeline_create(eventCount);
-		for (i = 0; i < eventCount; ++i) {
-			float time = readFloat(input);
-			spEventData *eventData = skeletonData->events[readVarint(input, 1)];
-			spEvent *event = spEvent_create(time, eventData);
-			event->intValue = readVarint(input, 0);
-			event->floatValue = readFloat(input);
-			if (readBoolean(input))
-				event->stringValue = readString(input);
-			else
-				MALLOC_STR(event->stringValue, eventData->stringValue);
-			if (eventData->audioPath) {
-				event->volume = readFloat(input);
-				event->balance = readFloat(input);
-			}
-			spEventTimeline_setFrame(timeline, i, event);
-		}
-		spTimelineArray_add(timelines, (spTimeline *) timeline);
-	}
-
-	duration = 0;
-	for (i = 0, n = timelines->size; i < n; i++) {
-		duration = MAX(duration, spTimeline_getDuration(timelines->items[i]));
-	}
-	animation = spAnimation_create(name, timelines, duration);
-	return animation;
-}
-
-static float *_readFloatArray(_dataInput *input, int n, float scale) {
-	float *array = MALLOC(float, n);
-	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;
-	return array;
-}
-
-static short *_readShortArray(_dataInput *input, int *length) {
-	int n = readVarint(input, 1);
-	short *array = MALLOC(short, n);
-	int i;
-	*length = n;
-	for (i = 0; i < n; ++i) {
-		array[i] = readByte(input) << 8;
-		array[i] |= readByte(input);
-	}
-	return array;
-}
-
-static void _readVertices(spSkeletonBinary *self, _dataInput *input, spVertexAttachment *attachment,
-						  int vertexCount) {
-	int i, ii;
-	int verticesLength = vertexCount << 1;
-	spFloatArray *weights = spFloatArray_create(8);
-	spIntArray *bones = spIntArray_create(8);
-
-	attachment->worldVerticesLength = verticesLength;
-
-	if (!readBoolean(input)) {
-		attachment->verticesCount = verticesLength;
-		attachment->vertices = _readFloatArray(input, verticesLength, self->scale);
-		attachment->bonesCount = 0;
-		attachment->bones = 0;
-		spFloatArray_dispose(weights);
-		spIntArray_dispose(bones);
-		return;
-	}
-
-	spFloatArray_ensureCapacity(weights, verticesLength * 3 * 3);
-	spIntArray_ensureCapacity(bones, verticesLength * 3);
-
-	for (i = 0; i < vertexCount; ++i) {
-		int boneCount = readVarint(input, 1);
-		spIntArray_add(bones, boneCount);
-		for (ii = 0; ii < boneCount; ++ii) {
-			spIntArray_add(bones, readVarint(input, 1));
-			spFloatArray_add(weights, readFloat(input) * self->scale);
-			spFloatArray_add(weights, readFloat(input) * self->scale);
-			spFloatArray_add(weights, readFloat(input));
-		}
-	}
-
-	attachment->verticesCount = weights->size;
-	attachment->vertices = weights->items;
-	FREE(weights);
-
-	attachment->bonesCount = bones->size;
-	attachment->bones = bones->items;
-	FREE(bones);
-}
-
-spAttachment *spSkeletonBinary_readAttachment(spSkeletonBinary *self, _dataInput *input,
-											  spSkin *skin, int slotIndex, const char *attachmentName,
-											  spSkeletonData *skeletonData, int/*bool*/ nonessential) {
-	int i;
-	spAttachmentType type;
-	const char *name = readStringRef(input, skeletonData);
-	if (!name) name = attachmentName;
-
-	type = (spAttachmentType) readByte(input);
-
-	switch (type) {
-		case SP_ATTACHMENT_REGION: {
-			const char *path = readStringRef(input, skeletonData);
-			spAttachment *attachment;
-			spRegionAttachment *region;
-			if (!path) MALLOC_STR(path, name);
-			else {
-				const char *tmp = 0;
-				MALLOC_STR(tmp, path);
-				path = tmp;
-			}
-			attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, path);
-			region = SUB_CAST(spRegionAttachment, attachment);
-			region->path = path;
-			region->rotation = readFloat(input);
-			region->x = readFloat(input) * self->scale;
-			region->y = readFloat(input) * self->scale;
-			region->scaleX = readFloat(input);
-			region->scaleY = readFloat(input);
-			region->width = readFloat(input) * self->scale;
-			region->height = readFloat(input) * self->scale;
-			readColor(input, &region->color.r, &region->color.g, &region->color.b, &region->color.a);
-			spRegionAttachment_updateOffset(region);
-			spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
-			return attachment;
-		}
-		case SP_ATTACHMENT_BOUNDING_BOX: {
-			int vertexCount = readVarint(input, 1);
-			spAttachment *attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, 0);
-			_readVertices(self, input, SUB_CAST(spVertexAttachment, attachment), vertexCount);
-			if (nonessential) {
-				spBoundingBoxAttachment *bbox = SUB_CAST(spBoundingBoxAttachment, attachment);
-				readColor(input, &bbox->color.r, &bbox->color.g, &bbox->color.b, &bbox->color.a);
-			}
-			spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
-			return attachment;
-		}
-		case SP_ATTACHMENT_MESH: {
-			int vertexCount;
-			spAttachment *attachment;
-			spMeshAttachment *mesh;
-			const char *path = readStringRef(input, skeletonData);
-			if (!path) MALLOC_STR(path, name);
-			else {
-				const char *tmp = 0;
-				MALLOC_STR(tmp, path);
-				path = tmp;
-			}
-			attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, path);
-			mesh = SUB_CAST(spMeshAttachment, attachment);
-			mesh->path = path;
-			readColor(input, &mesh->color.r, &mesh->color.g, &mesh->color.b, &mesh->color.a);
-			vertexCount = readVarint(input, 1);
-			mesh->regionUVs = _readFloatArray(input, vertexCount << 1, 1);
-			mesh->triangles = (unsigned short *) _readShortArray(input, &mesh->trianglesCount);
-			_readVertices(self, input, SUPER(mesh), vertexCount);
-			spMeshAttachment_updateUVs(mesh);
-			mesh->hullLength = readVarint(input, 1) << 1;
-			if (nonessential) {
-				mesh->edges = (int *) _readShortArray(input, &mesh->edgesCount);
-				mesh->width = readFloat(input) * self->scale;
-				mesh->height = readFloat(input) * self->scale;
-			} else {
-				mesh->edges = 0;
-				mesh->width = 0;
-				mesh->height = 0;
-			}
-			spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
-			return attachment;
-		}
-		case SP_ATTACHMENT_LINKED_MESH: {
-			const char *skinName;
-			const char *parent;
-			spAttachment *attachment;
-			spMeshAttachment *mesh;
-			int inheritDeform;
-			const char *path = readStringRef(input, skeletonData);
-			if (!path) MALLOC_STR(path, name);
-			else {
-				const char *tmp = 0;
-				MALLOC_STR(tmp, path);
-				path = tmp;
-			}
-			attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, path);
-			mesh = SUB_CAST(spMeshAttachment, attachment);
-			mesh->path = path;
-			readColor(input, &mesh->color.r, &mesh->color.g, &mesh->color.b, &mesh->color.a);
-			skinName = readStringRef(input, skeletonData);
-			parent = readStringRef(input, skeletonData);
-			inheritDeform = readBoolean(input);
-			if (nonessential) {
-				mesh->width = readFloat(input) * self->scale;
-				mesh->height = readFloat(input) * self->scale;
-			}
-			_spSkeletonBinary_addLinkedMesh(self, mesh, skinName, slotIndex, parent, inheritDeform);
-			return attachment;
-		}
-		case SP_ATTACHMENT_PATH: {
-			spAttachment *attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, 0);
-			spPathAttachment *path = SUB_CAST(spPathAttachment, attachment);
-			int vertexCount = 0;
-			path->closed = readBoolean(input);
-			path->constantSpeed = readBoolean(input);
-			vertexCount = readVarint(input, 1);
-			_readVertices(self, input, SUPER(path), vertexCount);
-			path->lengthsLength = vertexCount / 3;
-			path->lengths = MALLOC(float, path->lengthsLength);
-			for (i = 0; i < path->lengthsLength; ++i) {
-				path->lengths[i] = readFloat(input) * self->scale;
-			}
-			if (nonessential) {
-				readColor(input, &path->color.r, &path->color.g, &path->color.b, &path->color.a);
-			}
-			spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
-			return attachment;
-		}
-		case SP_ATTACHMENT_POINT: {
-			spAttachment *attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, 0);
-			spPointAttachment *point = SUB_CAST(spPointAttachment, attachment);
-			point->rotation = readFloat(input);
-			point->x = readFloat(input) * self->scale;
-			point->y = readFloat(input) * self->scale;
-
-			if (nonessential) {
-				readColor(input, &point->color.r, &point->color.g, &point->color.b, &point->color.a);
-			}
-			spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
-			return attachment;
-		}
-		case SP_ATTACHMENT_CLIPPING: {
-			int endSlotIndex = readVarint(input, 1);
-			int vertexCount = readVarint(input, 1);
-			spAttachment *attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, 0);
-			spClippingAttachment *clip = SUB_CAST(spClippingAttachment, attachment);
-			_readVertices(self, input, SUB_CAST(spVertexAttachment, attachment), vertexCount);
-			if (nonessential) {
-				readColor(input, &clip->color.r, &clip->color.g, &clip->color.b, &clip->color.a);
-			}
-			clip->endSlot = skeletonData->slots[endSlotIndex];
-			spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
-			return attachment;
-		}
-	}
-
-	return 0;
-}
-
-spSkin *spSkeletonBinary_readSkin(spSkeletonBinary *self, _dataInput *input, int/*bool*/ defaultSkin,
-								  spSkeletonData *skeletonData, int/*bool*/ nonessential) {
-	spSkin *skin;
-	int i, n, ii, nn, slotCount;
-
-	if (defaultSkin) {
-		slotCount = readVarint(input, 1);
-		if (slotCount == 0) return 0;
-		skin = spSkin_create("default");
-	} else {
-		skin = spSkin_create(readStringRef(input, skeletonData));
-		for (i = 0, n = readVarint(input, 1); i < n; i++)
-			spBoneDataArray_add(skin->bones, skeletonData->bones[readVarint(input, 1)]);
-
-		for (i = 0, n = readVarint(input, 1); i < n; i++)
-			spIkConstraintDataArray_add(skin->ikConstraints, skeletonData->ikConstraints[readVarint(input, 1)]);
-
-		for (i = 0, n = readVarint(input, 1); i < n; i++)
-			spTransformConstraintDataArray_add(skin->transformConstraints,
-											   skeletonData->transformConstraints[readVarint(input, 1)]);
-
-		for (i = 0, n = readVarint(input, 1); i < n; i++)
-			spPathConstraintDataArray_add(skin->pathConstraints, skeletonData->pathConstraints[readVarint(input, 1)]);
-
-		slotCount = readVarint(input, 1);
-	}
-
-	for (i = 0; i < slotCount; ++i) {
-		int slotIndex = readVarint(input, 1);
-		for (ii = 0, nn = readVarint(input, 1); ii < nn; ++ii) {
-			const char *name = readStringRef(input, skeletonData);
-			spAttachment *attachment = spSkeletonBinary_readAttachment(self, input, skin, slotIndex, name, skeletonData,
-																	   nonessential);
-			if (attachment) spSkin_setAttachment(skin, slotIndex, name, attachment);
-		}
-	}
-	return skin;
-}
-
-spSkeletonData *spSkeletonBinary_readSkeletonDataFile(spSkeletonBinary *self, const char *path) {
-	int length;
-	spSkeletonData *skeletonData;
-	const char *binary = _spUtil_readFile(path, &length);
-	if (length == 0 || !binary) {
-		_spSkeletonBinary_setError(self, "Unable to read skeleton file: ", path);
-		return 0;
-	}
-	skeletonData = spSkeletonBinary_readSkeletonData(self, (unsigned char *) binary, length);
-	FREE(binary);
-	return skeletonData;
-}
-
-spSkeletonData *spSkeletonBinary_readSkeletonData(spSkeletonBinary *self, const unsigned char *binary,
-												  const int length) {
-	int i, n, ii, nonessential;
-	char buffer[32];
-	int lowHash, highHash;
-	spSkeletonData *skeletonData;
-	_spSkeletonBinary *internal = SUB_CAST(_spSkeletonBinary, self);
-
-	_dataInput *input = NEW(_dataInput);
-	input->cursor = binary;
-	input->end = binary + length;
-
-	FREE(self->error);
-	CONST_CAST(char*, self->error) = 0;
-	internal->linkedMeshCount = 0;
-
-	skeletonData = spSkeletonData_create();
-	lowHash = readInt(input);
-	highHash = readInt(input);
-	sprintf(buffer, "%x%x", highHash, lowHash);
-	buffer[31] = 0;
-	skeletonData->hash = strdup(buffer);
-
-	skeletonData->version = readString(input);
-	if (!strlen(skeletonData->version)) {
-		FREE(skeletonData->version);
-		skeletonData->version = 0;
-	}
-
-	skeletonData->x = readFloat(input);
-	skeletonData->y = readFloat(input);
-	skeletonData->width = readFloat(input);
-	skeletonData->height = readFloat(input);
-
-	nonessential = readBoolean(input);
-
-	if (nonessential) {
-		skeletonData->fps = readFloat(input);
-		skeletonData->imagesPath = readString(input);
-		if (!strlen(skeletonData->imagesPath)) {
-			FREE(skeletonData->imagesPath);
-			skeletonData->imagesPath = 0;
-		}
-		skeletonData->audioPath = readString(input);
-		if (!strlen(skeletonData->audioPath)) {
-			FREE(skeletonData->audioPath);
-			skeletonData->audioPath = 0;
-		}
-	}
-
-	skeletonData->stringsCount = n = readVarint(input, 1);
-	skeletonData->strings = MALLOC(char*, skeletonData->stringsCount);
-	for (i = 0; i < n; i++) {
-		skeletonData->strings[i] = readString(input);
-	}
-
-	/* Bones. */
-	skeletonData->bonesCount = readVarint(input, 1);
-	skeletonData->bones = MALLOC(spBoneData*, skeletonData->bonesCount);
-	for (i = 0; i < skeletonData->bonesCount; ++i) {
-		spBoneData *data;
-		int mode;
-		const char *name = readString(input);
-		spBoneData *parent = i == 0 ? 0 : skeletonData->bones[readVarint(input, 1)];
-		/* TODO Avoid copying of name */
-		data = spBoneData_create(i, name, parent);
-		FREE(name);
-		data->rotation = readFloat(input);
-		data->x = readFloat(input) * self->scale;
-		data->y = readFloat(input) * self->scale;
-		data->scaleX = readFloat(input);
-		data->scaleY = readFloat(input);
-		data->shearX = readFloat(input);
-		data->shearY = readFloat(input);
-		data->length = readFloat(input) * self->scale;
-		mode = readVarint(input, 1);
-		switch (mode) {
-			case 0:
-				data->transformMode = SP_TRANSFORMMODE_NORMAL;
-				break;
-			case 1:
-				data->transformMode = SP_TRANSFORMMODE_ONLYTRANSLATION;
-				break;
-			case 2:
-				data->transformMode = SP_TRANSFORMMODE_NOROTATIONORREFLECTION;
-				break;
-			case 3:
-				data->transformMode = SP_TRANSFORMMODE_NOSCALE;
-				break;
-			case 4:
-				data->transformMode = SP_TRANSFORMMODE_NOSCALEORREFLECTION;
-				break;
-		}
-		data->skinRequired = readBoolean(input);
-		if (nonessential) {
-			readColor(input, &data->color.r, &data->color.g, &data->color.b, &data->color.a);
-		}
-		skeletonData->bones[i] = data;
-	}
-
-	/* Slots. */
-	skeletonData->slotsCount = readVarint(input, 1);
-	skeletonData->slots = MALLOC(spSlotData*, skeletonData->slotsCount);
-	for (i = 0; i < skeletonData->slotsCount; ++i) {
-		int r, g, b, a;
-		const char *attachmentName;
-		const char *slotName = readString(input);
-		spBoneData *boneData = skeletonData->bones[readVarint(input, 1)];
-		/* TODO Avoid copying of slotName */
-		spSlotData *slotData = spSlotData_create(i, slotName, boneData);
-		FREE(slotName);
-		readColor(input, &slotData->color.r, &slotData->color.g, &slotData->color.b, &slotData->color.a);
-		a = readByte(input);
-		r = readByte(input);
-		g = readByte(input);
-		b = readByte(input);
-		if (!(r == 0xff && g == 0xff && b == 0xff && a == 0xff)) {
-			slotData->darkColor = spColor_create();
-			spColor_setFromFloats(slotData->darkColor, r / 255.0f, g / 255.0f, b / 255.0f, 1);
-		}
-		attachmentName = readStringRef(input, skeletonData);
-		if (attachmentName) MALLOC_STR(slotData->attachmentName, attachmentName);
-		else slotData->attachmentName = 0;
-		slotData->blendMode = (spBlendMode) readVarint(input, 1);
-		skeletonData->slots[i] = slotData;
-	}
-
-	/* IK constraints. */
-	skeletonData->ikConstraintsCount = readVarint(input, 1);
-	skeletonData->ikConstraints = MALLOC(spIkConstraintData*, skeletonData->ikConstraintsCount);
-	for (i = 0; i < skeletonData->ikConstraintsCount; ++i) {
-		const char *name = readString(input);
-		/* TODO Avoid copying of name */
-		spIkConstraintData *data = spIkConstraintData_create(name);
-		data->order = readVarint(input, 1);
-		data->skinRequired = readBoolean(input);
-		FREE(name);
-		data->bonesCount = readVarint(input, 1);
-		data->bones = MALLOC(spBoneData*, data->bonesCount);
-		for (ii = 0; ii < data->bonesCount; ++ii)
-			data->bones[ii] = skeletonData->bones[readVarint(input, 1)];
-		data->target = skeletonData->bones[readVarint(input, 1)];
-		data->mix = readFloat(input);
-		data->softness = readFloat(input);
-		data->bendDirection = readSByte(input);
-		data->compress = readBoolean(input);
-		data->stretch = readBoolean(input);
-		data->uniform = readBoolean(input);
-		skeletonData->ikConstraints[i] = data;
-	}
-
-	/* Transform constraints. */
-	skeletonData->transformConstraintsCount = readVarint(input, 1);
-	skeletonData->transformConstraints = MALLOC(
-			spTransformConstraintData*, skeletonData->transformConstraintsCount);
-	for (i = 0; i < skeletonData->transformConstraintsCount; ++i) {
-		const char *name = readString(input);
-		/* TODO Avoid copying of name */
-		spTransformConstraintData *data = spTransformConstraintData_create(name);
-		data->order = readVarint(input, 1);
-		data->skinRequired = readBoolean(input);
-		FREE(name);
-		data->bonesCount = readVarint(input, 1);
-		CONST_CAST(spBoneData**, data->bones) = MALLOC(spBoneData*, data->bonesCount);
-		for (ii = 0; ii < data->bonesCount; ++ii)
-			data->bones[ii] = skeletonData->bones[readVarint(input, 1)];
-		data->target = skeletonData->bones[readVarint(input, 1)];
-		data->local = readBoolean(input);
-		data->relative = readBoolean(input);
-		data->offsetRotation = readFloat(input);
-		data->offsetX = readFloat(input) * self->scale;
-		data->offsetY = readFloat(input) * self->scale;
-		data->offsetScaleX = readFloat(input);
-		data->offsetScaleY = readFloat(input);
-		data->offsetShearY = readFloat(input);
-		data->mixRotate = readFloat(input);
-		data->mixX = readFloat(input);
-		data->mixY = readFloat(input);
-		data->mixScaleX = readFloat(input);
-		data->mixScaleY = readFloat(input);
-		data->mixShearY = readFloat(input);
-		skeletonData->transformConstraints[i] = data;
-	}
-
-	/* Path constraints */
-	skeletonData->pathConstraintsCount = readVarint(input, 1);
-	skeletonData->pathConstraints = MALLOC(spPathConstraintData*, skeletonData->pathConstraintsCount);
-	for (i = 0; i < skeletonData->pathConstraintsCount; ++i) {
-		const char *name = readString(input);
-		/* TODO Avoid copying of name */
-		spPathConstraintData *data = spPathConstraintData_create(name);
-		data->order = readVarint(input, 1);
-		data->skinRequired = readBoolean(input);
-		FREE(name);
-		data->bonesCount = readVarint(input, 1);
-		CONST_CAST(spBoneData**, data->bones) = MALLOC(spBoneData*, data->bonesCount);
-		for (ii = 0; ii < data->bonesCount; ++ii)
-			data->bones[ii] = skeletonData->bones[readVarint(input, 1)];
-		data->target = skeletonData->slots[readVarint(input, 1)];
-		data->positionMode = (spPositionMode) readVarint(input, 1);
-		data->spacingMode = (spSpacingMode) readVarint(input, 1);
-		data->rotateMode = (spRotateMode) readVarint(input, 1);
-		data->offsetRotation = readFloat(input);
-		data->position = readFloat(input);
-		if (data->positionMode == SP_POSITION_MODE_FIXED) data->position *= self->scale;
-		data->spacing = readFloat(input);
-		if (data->spacingMode == SP_SPACING_MODE_LENGTH || data->spacingMode == SP_SPACING_MODE_FIXED)
-			data->spacing *= self->scale;
-		data->mixRotate = readFloat(input);
-		data->mixX = readFloat(input);
-		data->mixY = readFloat(input);
-		skeletonData->pathConstraints[i] = data;
-	}
-
-	/* Default skin. */
-	skeletonData->defaultSkin = spSkeletonBinary_readSkin(self, input, -1, skeletonData, nonessential);
-	skeletonData->skinsCount = readVarint(input, 1);
-
-	if (skeletonData->defaultSkin)
-		++skeletonData->skinsCount;
-
-	skeletonData->skins = MALLOC(spSkin*, skeletonData->skinsCount);
-
-	if (skeletonData->defaultSkin)
-		skeletonData->skins[0] = skeletonData->defaultSkin;
-
-	/* Skins. */
-	for (i = skeletonData->defaultSkin ? 1 : 0; i < skeletonData->skinsCount; ++i) {
-		skeletonData->skins[i] = spSkeletonBinary_readSkin(self, input, 0, skeletonData, nonessential);
-	}
-
-	/* Linked meshes. */
-	for (i = 0; i < internal->linkedMeshCount; ++i) {
-		_spLinkedMesh *linkedMesh = internal->linkedMeshes + i;
-		spSkin *skin = !linkedMesh->skin ? skeletonData->defaultSkin : spSkeletonData_findSkin(skeletonData,
-																							   linkedMesh->skin);
-		spAttachment *parent;
-		if (!skin) {
-			FREE(input);
-			spSkeletonData_dispose(skeletonData);
-			_spSkeletonBinary_setError(self, "Skin not found: ", linkedMesh->skin);
-			return 0;
-		}
-		parent = spSkin_getAttachment(skin, linkedMesh->slotIndex, linkedMesh->parent);
-		if (!parent) {
-			FREE(input);
-			spSkeletonData_dispose(skeletonData);
-			_spSkeletonBinary_setError(self, "Parent mesh not found: ", linkedMesh->parent);
-			return 0;
-		}
-		linkedMesh->mesh->super.deformAttachment = linkedMesh->inheritDeform ? SUB_CAST(spVertexAttachment, parent)
-																			 : SUB_CAST(spVertexAttachment,
-																						linkedMesh->mesh);
-		spMeshAttachment_setParentMesh(linkedMesh->mesh, SUB_CAST(spMeshAttachment, parent));
-		spMeshAttachment_updateUVs(linkedMesh->mesh);
-		spAttachmentLoader_configureAttachment(self->attachmentLoader, SUPER(SUPER(linkedMesh->mesh)));
-	}
-
-	/* Events. */
-	skeletonData->eventsCount = readVarint(input, 1);
-	skeletonData->events = MALLOC(spEventData*, skeletonData->eventsCount);
-	for (i = 0; i < skeletonData->eventsCount; ++i) {
-		const char *name = readStringRef(input, skeletonData);
-		spEventData *eventData = spEventData_create(name);
-		eventData->intValue = readVarint(input, 0);
-		eventData->floatValue = readFloat(input);
-		eventData->stringValue = readString(input);
-		eventData->audioPath = readString(input);
-		if (eventData->audioPath) {
-			eventData->volume = readFloat(input);
-			eventData->balance = readFloat(input);
-		}
-		skeletonData->events[i] = eventData;
-	}
-
-	/* Animations. */
-	skeletonData->animationsCount = readVarint(input, 1);
-	skeletonData->animations = MALLOC(spAnimation*, skeletonData->animationsCount);
-	for (i = 0; i < skeletonData->animationsCount; ++i) {
-		const char *name = readString(input);
-		spAnimation *animation = _spSkeletonBinary_readAnimation(self, name, input, skeletonData);
-		FREE(name);
-		if (!animation) {
-			FREE(input);
-			spSkeletonData_dispose(skeletonData);
-			return 0;
-		}
-		skeletonData->animations[i] = animation;
-	}
-
-	FREE(input);
-	return skeletonData;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/Animation.h>
+#include <spine/Array.h>
+#include <spine/AtlasAttachmentLoader.h>
+#include <spine/SkeletonBinary.h>
+#include <spine/extension.h>
+#include <stdio.h>
+
+typedef struct {
+	const unsigned char *cursor;
+	const unsigned char *end;
+} _dataInput;
+
+typedef struct {
+	const char *parent;
+	const char *skin;
+	int slotIndex;
+	spMeshAttachment *mesh;
+	int inheritDeform;
+} _spLinkedMesh;
+
+typedef struct {
+	spSkeletonBinary super;
+	int ownsLoader;
+
+	int linkedMeshCount;
+	int linkedMeshCapacity;
+	_spLinkedMesh *linkedMeshes;
+} _spSkeletonBinary;
+
+spSkeletonBinary *spSkeletonBinary_createWithLoader(spAttachmentLoader *attachmentLoader) {
+	spSkeletonBinary *self = SUPER(NEW(_spSkeletonBinary));
+	self->scale = 1;
+	self->attachmentLoader = attachmentLoader;
+	return self;
+}
+
+spSkeletonBinary *spSkeletonBinary_create(spAtlas *atlas) {
+	spAtlasAttachmentLoader *attachmentLoader = spAtlasAttachmentLoader_create(atlas);
+	spSkeletonBinary *self = spSkeletonBinary_createWithLoader(SUPER(attachmentLoader));
+	SUB_CAST(_spSkeletonBinary, self)->ownsLoader = 1;
+	return self;
+}
+
+void spSkeletonBinary_dispose(spSkeletonBinary *self) {
+	_spSkeletonBinary *internal = SUB_CAST(_spSkeletonBinary, self);
+	if (internal->ownsLoader) spAttachmentLoader_dispose(self->attachmentLoader);
+	FREE(internal->linkedMeshes);
+	FREE(self->error);
+	FREE(self);
+}
+
+void _spSkeletonBinary_setError(spSkeletonBinary *self, 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);
+}
+
+static unsigned char readByte(_dataInput *input) {
+	return *input->cursor++;
+}
+
+static signed char readSByte(_dataInput *input) {
+	return (signed char) readByte(input);
+}
+
+static int readBoolean(_dataInput *input) {
+	return readByte(input) != 0;
+}
+
+static int readInt(_dataInput *input) {
+	int result = readByte(input);
+	result <<= 8;
+	result |= readByte(input);
+	result <<= 8;
+	result |= readByte(input);
+	result <<= 8;
+	result |= readByte(input);
+	return result;
+}
+
+static int readVarint(_dataInput *input, int /*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;
+}
+
+float readFloat(_dataInput *input) {
+	union {
+		int intValue;
+		float floatValue;
+	} intToFloat;
+	intToFloat.intValue = readInt(input);
+	return intToFloat.floatValue;
+}
+
+char *readString(_dataInput *input) {
+	int length = readVarint(input, 1);
+	char *string;
+	if (length == 0) {
+		return 0;
+	}
+	string = MALLOC(char, length);
+	memcpy(string, input->cursor, length - 1);
+	input->cursor += length - 1;
+	string[length - 1] = '\0';
+	return string;
+}
+
+static char *readStringRef(_dataInput *input, spSkeletonData *skeletonData) {
+	int index = readVarint(input, 1);
+	return index == 0 ? 0 : skeletonData->strings[index - 1];
+}
+
+static void readColor(_dataInput *input, float *r, float *g, float *b, float *a) {
+	*r = readByte(input) / 255.0f;
+	*g = readByte(input) / 255.0f;
+	*b = readByte(input) / 255.0f;
+	*a = readByte(input) / 255.0f;
+}
+
+#define ATTACHMENT_REGION 0
+#define ATTACHMENT_BOUNDING_BOX 1
+#define ATTACHMENT_MESH 2
+#define ATTACHMENT_LINKED_MESH 3
+#define ATTACHMENT_PATH 4
+
+#define BLEND_MODE_NORMAL 0
+#define BLEND_MODE_ADDITIVE 1
+#define BLEND_MODE_MULTIPLY 2
+#define BLEND_MODE_SCREEN 3
+
+#define BONE_ROTATE 0
+#define BONE_TRANSLATE 1
+#define BONE_TRANSLATEX 2
+#define BONE_TRANSLATEY 3
+#define BONE_SCALE 4
+#define BONE_SCALEX 5
+#define BONE_SCALEY 6
+#define BONE_SHEAR 7
+#define BONE_SHEARX 8
+#define BONE_SHEARY 9
+
+#define SLOT_ATTACHMENT 0
+#define SLOT_RGBA 1
+#define SLOT_RGB 2
+#define SLOT_RGBA2 3
+#define SLOT_RGB2 4
+#define SLOT_ALPHA 5
+
+#define PATH_POSITION 0
+#define PATH_SPACING 1
+#define PATH_MIX 2
+
+#define CURVE_LINEAR 0
+#define CURVE_STEPPED 1
+#define CURVE_BEZIER 2
+
+#define PATH_POSITION_FIXED 0
+#define PATH_POSITION_PERCENT 1
+
+#define PATH_SPACING_LENGTH 0
+#define PATH_SPACING_FIXED 1
+#define PATH_SPACING_PERCENT 2
+
+#define PATH_ROTATE_TANGENT 0
+#define PATH_ROTATE_CHAIN 1
+#define PATH_ROTATE_CHAIN_SCALE 2
+
+static void
+setBezier(_dataInput *input, spTimeline *timeline, int bezier, int frame, int value, float time1, float time2,
+		  float value1, float value2, float scale) {
+	float cx1 = readFloat(input);
+	float cy1 = readFloat(input);
+	float cx2 = readFloat(input);
+	float cy2 = readFloat(input);
+	spTimeline_setBezier(timeline, bezier, frame, value, time1, value1, cx1, cy1 * scale, cx2, cy2 * scale, time2,
+						 value2);
+}
+
+static spTimeline *readTimeline(_dataInput *input, spCurveTimeline1 *timeline, float scale) {
+	int frame, bezier, frameLast;
+	float time2, value2;
+	float time = readFloat(input);
+	float value = readFloat(input) * scale;
+	for (frame = 0, bezier = 0, frameLast = timeline->super.frameCount - 1;; frame++) {
+		spCurveTimeline1_setFrame(timeline, frame, time, value);
+		if (frame == frameLast) break;
+		time2 = readFloat(input);
+		value2 = readFloat(input) * scale;
+		switch (readSByte(input)) {
+			case CURVE_STEPPED:
+				spCurveTimeline_setStepped(timeline, frame);
+				break;
+			case CURVE_BEZIER:
+				setBezier(input, SUPER(timeline), bezier++, frame, 0, time, time2, value, value2, 1);
+		}
+		time = time2;
+		value = value2;
+	}
+	return SUPER(timeline);
+}
+
+static spTimeline *readTimeline2(_dataInput *input, spCurveTimeline2 *timeline, float scale) {
+	int frame, bezier, frameLast;
+	float time2, nvalue1, nvalue2;
+	float time = readFloat(input);
+	float value1 = readFloat(input) * scale;
+	float value2 = readFloat(input) * scale;
+	for (frame = 0, bezier = 0, frameLast = timeline->super.frameCount - 1;; frame++) {
+		spCurveTimeline2_setFrame(timeline, frame, time, value1, value2);
+		if (frame == frameLast) break;
+		time2 = readFloat(input);
+		nvalue1 = readFloat(input) * scale;
+		nvalue2 = readFloat(input) * scale;
+		switch (readSByte(input)) {
+			case CURVE_STEPPED:
+				spCurveTimeline_setStepped(timeline, frame);
+				break;
+			case CURVE_BEZIER:
+				setBezier(input, SUPER(timeline), bezier++, frame, 0, time, time2, value1, nvalue1, scale);
+				setBezier(input, SUPER(timeline), bezier++, frame, 1, time, time2, value2, nvalue2, scale);
+		}
+		time = time2;
+		value1 = nvalue1;
+		value2 = nvalue2;
+	}
+	return SUPER(timeline);
+}
+
+static void _spSkeletonBinary_addLinkedMesh(spSkeletonBinary *self, spMeshAttachment *mesh,
+											const char *skin, int slotIndex, const char *parent, int inheritDeform) {
+	_spLinkedMesh *linkedMesh;
+	_spSkeletonBinary *internal = SUB_CAST(_spSkeletonBinary, self);
+
+	if (internal->linkedMeshCount == internal->linkedMeshCapacity) {
+		_spLinkedMesh *linkedMeshes;
+		internal->linkedMeshCapacity *= 2;
+		if (internal->linkedMeshCapacity < 8) internal->linkedMeshCapacity = 8;
+		/* TODO Why not realloc? */
+		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->inheritDeform = inheritDeform;
+}
+
+static spAnimation *_spSkeletonBinary_readAnimation(spSkeletonBinary *self, const char *name,
+													_dataInput *input, spSkeletonData *skeletonData) {
+	spTimelineArray *timelines = spTimelineArray_create(18);
+	float duration = 0;
+	int i, n, ii, nn, iii, nnn;
+	int frame, bezier;
+	int drawOrderCount, eventCount;
+	spAnimation *animation;
+	float scale = self->scale;
+
+	int numTimelines = readVarint(input, 1);
+	UNUSED(numTimelines);
+
+	/* Slot timelines. */
+	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
+		int slotIndex = readVarint(input, 1);
+		for (ii = 0, nn = readVarint(input, 1); ii < nn; ++ii) {
+			unsigned char timelineType = readByte(input);
+			int frameCount = readVarint(input, 1);
+			int frameLast = frameCount - 1;
+			switch (timelineType) {
+				case SLOT_ATTACHMENT: {
+					spAttachmentTimeline *timeline = spAttachmentTimeline_create(frameCount, slotIndex);
+					for (frame = 0; frame < frameCount; ++frame) {
+						float time = readFloat(input);
+						const char *attachmentName = readStringRef(input, skeletonData);
+						spAttachmentTimeline_setFrame(timeline, frame, time, attachmentName);
+					}
+					spTimelineArray_add(timelines, SUPER(timeline));
+					break;
+				}
+				case SLOT_RGBA: {
+					int bezierCount = readVarint(input, 1);
+					spRGBATimeline *timeline = spRGBATimeline_create(frameCount, bezierCount, slotIndex);
+
+					float time = readFloat(input);
+					float r = readByte(input) / 255.0;
+					float g = readByte(input) / 255.0;
+					float b = readByte(input) / 255.0;
+					float a = readByte(input) / 255.0;
+
+					for (frame = 0, bezier = 0;; frame++) {
+						float time2, r2, g2, b2, a2;
+						spRGBATimeline_setFrame(timeline, frame, time, r, g, b, a);
+						if (frame == frameLast) break;
+
+						time2 = readFloat(input);
+						r2 = readByte(input) / 255.0;
+						g2 = readByte(input) / 255.0;
+						b2 = readByte(input) / 255.0;
+						a2 = readByte(input) / 255.0;
+
+						switch (readSByte(input)) {
+							case CURVE_STEPPED:
+								spCurveTimeline_setStepped(SUPER(timeline), frame);
+								break;
+							case CURVE_BEZIER:
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, r, r2, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, g, g2, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 2, time, time2, b, b2, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 3, time, time2, a, a2, 1);
+						}
+						time = time2;
+						r = r2;
+						g = g2;
+						b = b2;
+						a = a2;
+					}
+					spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
+					break;
+				}
+				case SLOT_RGB: {
+					int bezierCount = readVarint(input, 1);
+					spRGBTimeline *timeline = spRGBTimeline_create(frameCount, bezierCount, slotIndex);
+
+					float time = readFloat(input);
+					float r = readByte(input) / 255.0;
+					float g = readByte(input) / 255.0;
+					float b = readByte(input) / 255.0;
+
+					for (frame = 0, bezier = 0;; frame++) {
+						float time2, r2, g2, b2;
+						spRGBTimeline_setFrame(timeline, frame, time, r, g, b);
+						if (frame == frameLast) break;
+
+						time2 = readFloat(input);
+						r2 = readByte(input) / 255.0;
+						g2 = readByte(input) / 255.0;
+						b2 = readByte(input) / 255.0;
+
+						switch (readSByte(input)) {
+							case CURVE_STEPPED:
+								spCurveTimeline_setStepped(SUPER(timeline), frame);
+								break;
+							case CURVE_BEZIER:
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, r, r2, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, g, g2, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 2, time, time2, b, b2, 1);
+						}
+						time = time2;
+						r = r2;
+						g = g2;
+						b = b2;
+					}
+					spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
+					break;
+				}
+				case SLOT_RGBA2: {
+					int bezierCount = readVarint(input, 1);
+					spRGBA2Timeline *timeline = spRGBA2Timeline_create(frameCount, bezierCount, slotIndex);
+
+					float time = readFloat(input);
+					float r = readByte(input) / 255.0;
+					float g = readByte(input) / 255.0;
+					float b = readByte(input) / 255.0;
+					float a = readByte(input) / 255.0;
+					float r2 = readByte(input) / 255.0;
+					float g2 = readByte(input) / 255.0;
+					float b2 = readByte(input) / 255.0;
+
+					for (frame = 0, bezier = 0;; frame++) {
+						float time2, nr, ng, nb, na, nr2, ng2, nb2;
+						spRGBA2Timeline_setFrame(timeline, frame, time, r, g, b, a, r2, g2, b2);
+						if (frame == frameLast) break;
+						time2 = readFloat(input);
+						nr = readByte(input) / 255.0;
+						ng = readByte(input) / 255.0;
+						nb = readByte(input) / 255.0;
+						na = readByte(input) / 255.0;
+						nr2 = readByte(input) / 255.0;
+						ng2 = readByte(input) / 255.0;
+						nb2 = readByte(input) / 255.0;
+
+						switch (readSByte(input)) {
+							case CURVE_STEPPED:
+								spCurveTimeline_setStepped(SUPER(timeline), frame);
+								break;
+							case CURVE_BEZIER:
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, r, nr, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, g, ng, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 2, time, time2, b, nb, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 3, time, time2, a, na, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 4, time, time2, r2, nr2, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 5, time, time2, g2, ng2, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 6, time, time2, b2, nb2, 1);
+						}
+						time = time2;
+						r = nr;
+						g = ng;
+						b = nb;
+						a = na;
+						r2 = nr2;
+						g2 = ng2;
+						b2 = nb2;
+					}
+					spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
+					break;
+				}
+				case SLOT_RGB2: {
+					int bezierCount = readVarint(input, 1);
+					spRGB2Timeline *timeline = spRGB2Timeline_create(frameCount, bezierCount, slotIndex);
+
+					float time = readFloat(input);
+					float r = readByte(input) / 255.0;
+					float g = readByte(input) / 255.0;
+					float b = readByte(input) / 255.0;
+					float r2 = readByte(input) / 255.0;
+					float g2 = readByte(input) / 255.0;
+					float b2 = readByte(input) / 255.0;
+
+					for (frame = 0, bezier = 0;; frame++) {
+						float time2, nr, ng, nb, nr2, ng2, nb2;
+						spRGB2Timeline_setFrame(timeline, frame, time, r, g, b, r2, g2, b2);
+						if (frame == frameLast) break;
+						time2 = readFloat(input);
+						nr = readByte(input) / 255.0;
+						ng = readByte(input) / 255.0;
+						nb = readByte(input) / 255.0;
+						nr2 = readByte(input) / 255.0;
+						ng2 = readByte(input) / 255.0;
+						nb2 = readByte(input) / 255.0;
+
+						switch (readSByte(input)) {
+							case CURVE_STEPPED:
+								spCurveTimeline_setStepped(SUPER(timeline), frame);
+								break;
+							case CURVE_BEZIER:
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, r, nr, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, g, ng, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 2, time, time2, b, nb, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 3, time, time2, r2, nr2, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 4, time, time2, g2, ng2, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 5, time, time2, b2, nb2, 1);
+						}
+						time = time2;
+						r = nr;
+						g = ng;
+						b = nb;
+						r2 = nr2;
+						g2 = ng2;
+						b2 = nb2;
+					}
+					spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
+					break;
+				}
+				case SLOT_ALPHA: {
+					int bezierCount = readVarint(input, 1);
+					spAlphaTimeline *timeline = spAlphaTimeline_create(frameCount, bezierCount, slotIndex);
+					float time = readFloat(input);
+					float a = readByte(input) / 255.0;
+					for (frame = 0, bezier = 0;; frame++) {
+						float time2, a2;
+						spAlphaTimeline_setFrame(timeline, frame, time, a);
+						if (frame == frameLast) break;
+						time2 = readFloat(input);
+						a2 = readByte(input) / 255;
+						switch (readSByte(input)) {
+							case CURVE_STEPPED:
+								spCurveTimeline_setStepped(SUPER(timeline), frame);
+								break;
+							case CURVE_BEZIER:
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, a, a2, 1);
+						}
+						time = time2;
+						a = a2;
+					}
+					spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
+					break;
+				}
+				default: {
+					return NULL;
+				}
+			}
+		}
+	}
+
+	/* Bone timelines. */
+	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
+		int boneIndex = readVarint(input, 1);
+		for (ii = 0, nn = readVarint(input, 1); ii < nn; ++ii) {
+			unsigned char timelineType = readByte(input);
+			int frameCount = readVarint(input, 1);
+			int bezierCount = readVarint(input, 1);
+			spTimeline *timeline = NULL;
+			switch (timelineType) {
+				case BONE_ROTATE:
+					timeline = readTimeline(input, SUPER(spRotateTimeline_create(frameCount, bezierCount, boneIndex)),
+											1);
+					break;
+				case BONE_TRANSLATE:
+					timeline = readTimeline2(input,
+											 SUPER(spTranslateTimeline_create(frameCount, bezierCount, boneIndex)),
+											 scale);
+					break;
+				case BONE_TRANSLATEX:
+					timeline = readTimeline(input,
+											SUPER(spTranslateXTimeline_create(frameCount, bezierCount, boneIndex)),
+											scale);
+					break;
+				case BONE_TRANSLATEY:
+					timeline = readTimeline(input,
+											SUPER(spTranslateYTimeline_create(frameCount, bezierCount, boneIndex)),
+											scale);
+					break;
+				case BONE_SCALE:
+					timeline = readTimeline2(input, SUPER(spScaleTimeline_create(frameCount, bezierCount, boneIndex)),
+											 1);
+					break;
+				case BONE_SCALEX:
+					timeline = readTimeline(input, SUPER(spScaleXTimeline_create(frameCount, bezierCount, boneIndex)),
+											1);
+					break;
+				case BONE_SCALEY:
+					timeline = readTimeline(input, SUPER(spScaleYTimeline_create(frameCount, bezierCount, boneIndex)),
+											1);
+					break;
+				case BONE_SHEAR:
+					timeline = readTimeline2(input, SUPER(spShearTimeline_create(frameCount, bezierCount, boneIndex)),
+											 1);
+					break;
+				case BONE_SHEARX:
+					timeline = readTimeline(input, SUPER(spShearXTimeline_create(frameCount, bezierCount, boneIndex)),
+											1);
+					break;
+				case BONE_SHEARY:
+					timeline = readTimeline(input, SUPER(spShearYTimeline_create(frameCount, bezierCount, boneIndex)),
+											1);
+					break;
+				default: {
+					for (iii = 0; iii < timelines->size; ++iii)
+						spTimeline_dispose(timelines->items[iii]);
+					spTimelineArray_dispose(timelines);
+					_spSkeletonBinary_setError(self, "Invalid timeline type for a bone: ",
+											   skeletonData->bones[boneIndex]->name);
+					return NULL;
+				}
+			}
+			spTimelineArray_add(timelines, timeline);
+		}
+	}
+
+	/* IK constraint timelines. */
+	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
+		int index = readVarint(input, 1);
+		int frameCount = readVarint(input, 1);
+		int frameLast = frameCount - 1;
+		int bezierCount = readVarint(input, 1);
+		spIkConstraintTimeline *timeline = spIkConstraintTimeline_create(frameCount, bezierCount, index);
+		float time = readFloat(input);
+		float mix = readFloat(input);
+		float softness = readFloat(input) * scale;
+		for (frame = 0, bezier = 0;; frame++) {
+			float time2, mix2, softness2;
+			int bendDirection = readSByte(input);
+			int compress = readBoolean(input);
+			int stretch = readBoolean(input);
+			spIkConstraintTimeline_setFrame(timeline, frame, time, mix, softness, bendDirection, compress, stretch);
+			if (frame == frameLast) break;
+			time2 = readFloat(input);
+			mix2 = readFloat(input);
+			softness2 = readFloat(input) * scale;
+			switch (readSByte(input)) {
+				case CURVE_STEPPED:
+					spCurveTimeline_setStepped(SUPER(timeline), frame);
+					break;
+				case CURVE_BEZIER:
+					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, mix, mix2, 1);
+					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, softness, softness2,
+							  scale);
+			}
+			time = time2;
+			mix = mix2;
+			softness = softness2;
+		}
+		spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
+	}
+
+	/* Transform constraint timelines. */
+	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
+		int index = readVarint(input, 1);
+		int frameCount = readVarint(input, 1);
+		int frameLast = frameCount - 1;
+		int bezierCount = readVarint(input, 1);
+		spTransformConstraintTimeline *timeline = spTransformConstraintTimeline_create(frameCount, bezierCount, index);
+		float time = readFloat(input);
+		float mixRotate = readFloat(input);
+		float mixX = readFloat(input);
+		float mixY = readFloat(input);
+		float mixScaleX = readFloat(input);
+		float mixScaleY = readFloat(input);
+		float mixShearY = readFloat(input);
+		for (frame = 0, bezier = 0;; frame++) {
+			float time2, mixRotate2, mixX2, mixY2, mixScaleX2, mixScaleY2, mixShearY2;
+			spTransformConstraintTimeline_setFrame(timeline, frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY,
+												   mixShearY);
+			if (frame == frameLast) break;
+			time2 = readFloat(input);
+			mixRotate2 = readFloat(input);
+			mixX2 = readFloat(input);
+			mixY2 = readFloat(input);
+			mixScaleX2 = readFloat(input);
+			mixScaleY2 = readFloat(input);
+			mixShearY2 = readFloat(input);
+			switch (readSByte(input)) {
+				case CURVE_STEPPED:
+					spCurveTimeline_setStepped(SUPER(timeline), frame);
+					break;
+				case CURVE_BEZIER:
+					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1);
+					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, mixX, mixX2, 1);
+					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 2, time, time2, mixY, mixY2, 1);
+					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 3, time, time2, mixScaleX, mixScaleX2, 1);
+					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 4, time, time2, mixScaleY, mixScaleY2, 1);
+					setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 5, time, time2, mixShearY, mixShearY2, 1);
+			}
+			time = time2;
+			mixRotate = mixRotate2;
+			mixX = mixX2;
+			mixY = mixY2;
+			mixScaleX = mixScaleX2;
+			mixScaleY = mixScaleY2;
+			mixShearY = mixShearY2;
+		}
+		spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
+	}
+
+	/* Path constraint timelines. */
+	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
+		int index = readVarint(input, 1);
+		spPathConstraintData *data = skeletonData->pathConstraints[index];
+		for (ii = 0, nn = readVarint(input, 1); ii < nn; ++ii) {
+			int type = readSByte(input);
+			int frameCount = readVarint(input, 1);
+			int bezierCount = readVarint(input, 1);
+			switch (type) {
+				case PATH_POSITION: {
+					spTimelineArray_add(timelines, readTimeline(input, SUPER(spPathConstraintPositionTimeline_create(frameCount, bezierCount, index)),
+																data->positionMode == SP_POSITION_MODE_FIXED ? scale
+																											 : 1));
+					break;
+				}
+				case PATH_SPACING: {
+					spTimelineArray_add(timelines, readTimeline(input,
+																SUPER(spPathConstraintSpacingTimeline_create(frameCount,
+																											 bezierCount,
+																											 index)),
+																data->spacingMode == SP_SPACING_MODE_LENGTH ||
+																				data->spacingMode == SP_SPACING_MODE_FIXED
+																		? scale
+																		: 1));
+					break;
+				}
+				case PATH_MIX: {
+					float time, mixRotate, mixX, mixY;
+					int frameLast;
+					spPathConstraintMixTimeline *timeline = spPathConstraintMixTimeline_create(frameCount, bezierCount,
+																							   index);
+					time = readFloat(input);
+					mixRotate = readFloat(input);
+					mixX = readFloat(input);
+					mixY = readFloat(input);
+					for (frame = 0, bezier = 0, frameLast = timeline->super.super.frameCount - 1;; frame++) {
+						float time2, mixRotate2, mixX2, mixY2;
+						spPathConstraintMixTimeline_setFrame(timeline, frame, time, mixRotate, mixX, mixY);
+						if (frame == frameLast) break;
+						time2 = readFloat(input);
+						mixRotate2 = readFloat(input);
+						mixX2 = readFloat(input);
+						mixY2 = readFloat(input);
+						switch (readSByte(input)) {
+							case CURVE_STEPPED:
+								spCurveTimeline_setStepped(SUPER(timeline), frame);
+								break;
+							case CURVE_BEZIER:
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, mixRotate,
+										  mixRotate2, 1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 1, time, time2, mixX, mixX2,
+										  1);
+								setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 2, time, time2, mixY, mixY2,
+										  1);
+						}
+						time = time2;
+						mixRotate = mixRotate2;
+						mixX = mixX2;
+						mixY = mixY2;
+					}
+					spTimelineArray_add(timelines, SUPER(SUPER(timeline)));
+				}
+			}
+		}
+	}
+
+	/* Deform timelines. */
+	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
+		spSkin *skin = skeletonData->skins[readVarint(input, 1)];
+		for (ii = 0, nn = readVarint(input, 1); ii < nn; ++ii) {
+			int slotIndex = readVarint(input, 1);
+			for (iii = 0, nnn = readVarint(input, 1); iii < nnn; ++iii) {
+				float *tempDeform;
+				spDeformTimeline *timeline;
+				int weighted, deformLength;
+				const char *attachmentName = readStringRef(input, skeletonData);
+				int frameCount, frameLast, bezierCount;
+				float time, time2;
+
+				spVertexAttachment *attachment = SUB_CAST(spVertexAttachment,
+														  spSkin_getAttachment(skin, slotIndex, attachmentName));
+				if (!attachment) {
+					for (i = 0; i < timelines->size; ++i)
+						spTimeline_dispose(timelines->items[i]);
+					spTimelineArray_dispose(timelines);
+					_spSkeletonBinary_setError(self, "Attachment not found: ", attachmentName);
+					return 0;
+				}
+
+				weighted = attachment->bones != 0;
+				deformLength = weighted ? attachment->verticesCount / 3 * 2 : attachment->verticesCount;
+				tempDeform = MALLOC(float, deformLength);
+
+				frameCount = readVarint(input, 1);
+				frameLast = frameCount - 1;
+				bezierCount = readVarint(input, 1);
+				timeline = spDeformTimeline_create(frameCount, deformLength, bezierCount, slotIndex, attachment);
+
+				time = readFloat(input);
+				for (frame = 0, bezier = 0;; ++frame) {
+					float *deform;
+					int end = readVarint(input, 1);
+					if (!end) {
+						if (weighted) {
+							deform = tempDeform;
+							memset(deform, 0, sizeof(float) * deformLength);
+						} else
+							deform = attachment->vertices;
+					} else {
+						int v, start = readVarint(input, 1);
+						deform = tempDeform;
+						memset(deform, 0, sizeof(float) * start);
+						end += start;
+						if (self->scale == 1) {
+							for (v = start; v < end; ++v)
+								deform[v] = readFloat(input);
+						} else {
+							for (v = start; v < end; ++v)
+								deform[v] = readFloat(input) * self->scale;
+						}
+						memset(deform + v, 0, sizeof(float) * (deformLength - v));
+						if (!weighted) {
+							float *vertices = attachment->vertices;
+							for (v = 0; v < deformLength; ++v)
+								deform[v] += vertices[v];
+						}
+					}
+					spDeformTimeline_setFrame(timeline, frame, time, deform);
+					if (frame == frameLast) break;
+					time2 = readFloat(input);
+					switch (readSByte(input)) {
+						case CURVE_STEPPED:
+							spCurveTimeline_setStepped(SUPER(timeline), frame);
+							break;
+						case CURVE_BEZIER:
+							setBezier(input, SUPER(SUPER(timeline)), bezier++, frame, 0, time, time2, 0, 1, 1);
+					}
+					time = time2;
+				}
+				FREE(tempDeform);
+
+				spTimelineArray_add(timelines, (spTimeline *) timeline);
+			}
+		}
+	}
+
+	/* Draw order timeline. */
+	drawOrderCount = readVarint(input, 1);
+	if (drawOrderCount) {
+		spDrawOrderTimeline *timeline = spDrawOrderTimeline_create(drawOrderCount, skeletonData->slotsCount);
+		for (i = 0; i < drawOrderCount; ++i) {
+			float time = readFloat(input);
+			int offsetCount = readVarint(input, 1);
+			int *drawOrder = MALLOC(int, skeletonData->slotsCount);
+			int *unchanged = MALLOC(int, skeletonData->slotsCount - offsetCount);
+			int originalIndex = 0, unchangedIndex = 0;
+			memset(drawOrder, -1, sizeof(int) * skeletonData->slotsCount);
+			for (ii = 0; ii < offsetCount; ++ii) {
+				int slotIndex = readVarint(input, 1);
+				/* Collect unchanged items. */
+				while (originalIndex != slotIndex)
+					unchanged[unchangedIndex++] = originalIndex++;
+				/* Set changed items. */
+				drawOrder[originalIndex + readVarint(input, 1)] = 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);
+			/* TODO Avoid copying of drawOrder inside */
+			spDrawOrderTimeline_setFrame(timeline, i, time, drawOrder);
+			FREE(drawOrder);
+		}
+		spTimelineArray_add(timelines, (spTimeline *) timeline);
+	}
+
+	/* Event timeline. */
+	eventCount = readVarint(input, 1);
+	if (eventCount) {
+		spEventTimeline *timeline = spEventTimeline_create(eventCount);
+		for (i = 0; i < eventCount; ++i) {
+			float time = readFloat(input);
+			spEventData *eventData = skeletonData->events[readVarint(input, 1)];
+			spEvent *event = spEvent_create(time, eventData);
+			event->intValue = readVarint(input, 0);
+			event->floatValue = readFloat(input);
+			if (readBoolean(input))
+				event->stringValue = readString(input);
+			else
+				MALLOC_STR(event->stringValue, eventData->stringValue);
+			if (eventData->audioPath) {
+				event->volume = readFloat(input);
+				event->balance = readFloat(input);
+			}
+			spEventTimeline_setFrame(timeline, i, event);
+		}
+		spTimelineArray_add(timelines, (spTimeline *) timeline);
+	}
+
+	duration = 0;
+	for (i = 0, n = timelines->size; i < n; i++) {
+		duration = MAX(duration, spTimeline_getDuration(timelines->items[i]));
+	}
+	animation = spAnimation_create(name, timelines, duration);
+	return animation;
+}
+
+static float *_readFloatArray(_dataInput *input, int n, float scale) {
+	float *array = MALLOC(float, n);
+	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;
+	return array;
+}
+
+static short *_readShortArray(_dataInput *input, int *length) {
+	int n = readVarint(input, 1);
+	short *array = MALLOC(short, n);
+	int i;
+	*length = n;
+	for (i = 0; i < n; ++i) {
+		array[i] = readByte(input) << 8;
+		array[i] |= readByte(input);
+	}
+	return array;
+}
+
+static void _readVertices(spSkeletonBinary *self, _dataInput *input, spVertexAttachment *attachment,
+						  int vertexCount) {
+	int i, ii;
+	int verticesLength = vertexCount << 1;
+	spFloatArray *weights = spFloatArray_create(8);
+	spIntArray *bones = spIntArray_create(8);
+
+	attachment->worldVerticesLength = verticesLength;
+
+	if (!readBoolean(input)) {
+		attachment->verticesCount = verticesLength;
+		attachment->vertices = _readFloatArray(input, verticesLength, self->scale);
+		attachment->bonesCount = 0;
+		attachment->bones = 0;
+		spFloatArray_dispose(weights);
+		spIntArray_dispose(bones);
+		return;
+	}
+
+	spFloatArray_ensureCapacity(weights, verticesLength * 3 * 3);
+	spIntArray_ensureCapacity(bones, verticesLength * 3);
+
+	for (i = 0; i < vertexCount; ++i) {
+		int boneCount = readVarint(input, 1);
+		spIntArray_add(bones, boneCount);
+		for (ii = 0; ii < boneCount; ++ii) {
+			spIntArray_add(bones, readVarint(input, 1));
+			spFloatArray_add(weights, readFloat(input) * self->scale);
+			spFloatArray_add(weights, readFloat(input) * self->scale);
+			spFloatArray_add(weights, readFloat(input));
+		}
+	}
+
+	attachment->verticesCount = weights->size;
+	attachment->vertices = weights->items;
+	FREE(weights);
+
+	attachment->bonesCount = bones->size;
+	attachment->bones = bones->items;
+	FREE(bones);
+}
+
+spAttachment *spSkeletonBinary_readAttachment(spSkeletonBinary *self, _dataInput *input,
+											  spSkin *skin, int slotIndex, const char *attachmentName,
+											  spSkeletonData *skeletonData, int /*bool*/ nonessential) {
+	int i;
+	spAttachmentType type;
+	const char *name = readStringRef(input, skeletonData);
+	if (!name) name = attachmentName;
+
+	type = (spAttachmentType) readByte(input);
+
+	switch (type) {
+		case SP_ATTACHMENT_REGION: {
+			const char *path = readStringRef(input, skeletonData);
+			spAttachment *attachment;
+			spRegionAttachment *region;
+			if (!path) MALLOC_STR(path, name);
+			else {
+				const char *tmp = 0;
+				MALLOC_STR(tmp, path);
+				path = tmp;
+			}
+			attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, path);
+			region = SUB_CAST(spRegionAttachment, attachment);
+			region->path = path;
+			region->rotation = readFloat(input);
+			region->x = readFloat(input) * self->scale;
+			region->y = readFloat(input) * self->scale;
+			region->scaleX = readFloat(input);
+			region->scaleY = readFloat(input);
+			region->width = readFloat(input) * self->scale;
+			region->height = readFloat(input) * self->scale;
+			readColor(input, &region->color.r, &region->color.g, &region->color.b, &region->color.a);
+			spRegionAttachment_updateOffset(region);
+			spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
+			return attachment;
+		}
+		case SP_ATTACHMENT_BOUNDING_BOX: {
+			int vertexCount = readVarint(input, 1);
+			spAttachment *attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, 0);
+			_readVertices(self, input, SUB_CAST(spVertexAttachment, attachment), vertexCount);
+			if (nonessential) {
+				spBoundingBoxAttachment *bbox = SUB_CAST(spBoundingBoxAttachment, attachment);
+				readColor(input, &bbox->color.r, &bbox->color.g, &bbox->color.b, &bbox->color.a);
+			}
+			spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
+			return attachment;
+		}
+		case SP_ATTACHMENT_MESH: {
+			int vertexCount;
+			spAttachment *attachment;
+			spMeshAttachment *mesh;
+			const char *path = readStringRef(input, skeletonData);
+			if (!path) MALLOC_STR(path, name);
+			else {
+				const char *tmp = 0;
+				MALLOC_STR(tmp, path);
+				path = tmp;
+			}
+			attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, path);
+			mesh = SUB_CAST(spMeshAttachment, attachment);
+			mesh->path = path;
+			readColor(input, &mesh->color.r, &mesh->color.g, &mesh->color.b, &mesh->color.a);
+			vertexCount = readVarint(input, 1);
+			mesh->regionUVs = _readFloatArray(input, vertexCount << 1, 1);
+			mesh->triangles = (unsigned short *) _readShortArray(input, &mesh->trianglesCount);
+			_readVertices(self, input, SUPER(mesh), vertexCount);
+			spMeshAttachment_updateUVs(mesh);
+			mesh->hullLength = readVarint(input, 1) << 1;
+			if (nonessential) {
+				mesh->edges = (int *) _readShortArray(input, &mesh->edgesCount);
+				mesh->width = readFloat(input) * self->scale;
+				mesh->height = readFloat(input) * self->scale;
+			} else {
+				mesh->edges = 0;
+				mesh->width = 0;
+				mesh->height = 0;
+			}
+			spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
+			return attachment;
+		}
+		case SP_ATTACHMENT_LINKED_MESH: {
+			const char *skinName;
+			const char *parent;
+			spAttachment *attachment;
+			spMeshAttachment *mesh;
+			int inheritDeform;
+			const char *path = readStringRef(input, skeletonData);
+			if (!path) MALLOC_STR(path, name);
+			else {
+				const char *tmp = 0;
+				MALLOC_STR(tmp, path);
+				path = tmp;
+			}
+			attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, path);
+			mesh = SUB_CAST(spMeshAttachment, attachment);
+			mesh->path = path;
+			readColor(input, &mesh->color.r, &mesh->color.g, &mesh->color.b, &mesh->color.a);
+			skinName = readStringRef(input, skeletonData);
+			parent = readStringRef(input, skeletonData);
+			inheritDeform = readBoolean(input);
+			if (nonessential) {
+				mesh->width = readFloat(input) * self->scale;
+				mesh->height = readFloat(input) * self->scale;
+			}
+			_spSkeletonBinary_addLinkedMesh(self, mesh, skinName, slotIndex, parent, inheritDeform);
+			return attachment;
+		}
+		case SP_ATTACHMENT_PATH: {
+			spAttachment *attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, 0);
+			spPathAttachment *path = SUB_CAST(spPathAttachment, attachment);
+			int vertexCount = 0;
+			path->closed = readBoolean(input);
+			path->constantSpeed = readBoolean(input);
+			vertexCount = readVarint(input, 1);
+			_readVertices(self, input, SUPER(path), vertexCount);
+			path->lengthsLength = vertexCount / 3;
+			path->lengths = MALLOC(float, path->lengthsLength);
+			for (i = 0; i < path->lengthsLength; ++i) {
+				path->lengths[i] = readFloat(input) * self->scale;
+			}
+			if (nonessential) {
+				readColor(input, &path->color.r, &path->color.g, &path->color.b, &path->color.a);
+			}
+			spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
+			return attachment;
+		}
+		case SP_ATTACHMENT_POINT: {
+			spAttachment *attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, 0);
+			spPointAttachment *point = SUB_CAST(spPointAttachment, attachment);
+			point->rotation = readFloat(input);
+			point->x = readFloat(input) * self->scale;
+			point->y = readFloat(input) * self->scale;
+
+			if (nonessential) {
+				readColor(input, &point->color.r, &point->color.g, &point->color.b, &point->color.a);
+			}
+			spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
+			return attachment;
+		}
+		case SP_ATTACHMENT_CLIPPING: {
+			int endSlotIndex = readVarint(input, 1);
+			int vertexCount = readVarint(input, 1);
+			spAttachment *attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, name, 0);
+			spClippingAttachment *clip = SUB_CAST(spClippingAttachment, attachment);
+			_readVertices(self, input, SUB_CAST(spVertexAttachment, attachment), vertexCount);
+			if (nonessential) {
+				readColor(input, &clip->color.r, &clip->color.g, &clip->color.b, &clip->color.a);
+			}
+			clip->endSlot = skeletonData->slots[endSlotIndex];
+			spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
+			return attachment;
+		}
+	}
+
+	return 0;
+}
+
+spSkin *spSkeletonBinary_readSkin(spSkeletonBinary *self, _dataInput *input, int /*bool*/ defaultSkin,
+								  spSkeletonData *skeletonData, int /*bool*/ nonessential) {
+	spSkin *skin;
+	int i, n, ii, nn, slotCount;
+
+	if (defaultSkin) {
+		slotCount = readVarint(input, 1);
+		if (slotCount == 0) return 0;
+		skin = spSkin_create("default");
+	} else {
+		skin = spSkin_create(readStringRef(input, skeletonData));
+		for (i = 0, n = readVarint(input, 1); i < n; i++)
+			spBoneDataArray_add(skin->bones, skeletonData->bones[readVarint(input, 1)]);
+
+		for (i = 0, n = readVarint(input, 1); i < n; i++)
+			spIkConstraintDataArray_add(skin->ikConstraints, skeletonData->ikConstraints[readVarint(input, 1)]);
+
+		for (i = 0, n = readVarint(input, 1); i < n; i++)
+			spTransformConstraintDataArray_add(skin->transformConstraints,
+											   skeletonData->transformConstraints[readVarint(input, 1)]);
+
+		for (i = 0, n = readVarint(input, 1); i < n; i++)
+			spPathConstraintDataArray_add(skin->pathConstraints, skeletonData->pathConstraints[readVarint(input, 1)]);
+
+		slotCount = readVarint(input, 1);
+	}
+
+	for (i = 0; i < slotCount; ++i) {
+		int slotIndex = readVarint(input, 1);
+		for (ii = 0, nn = readVarint(input, 1); ii < nn; ++ii) {
+			const char *name = readStringRef(input, skeletonData);
+			spAttachment *attachment = spSkeletonBinary_readAttachment(self, input, skin, slotIndex, name, skeletonData,
+																	   nonessential);
+			if (attachment) spSkin_setAttachment(skin, slotIndex, name, attachment);
+		}
+	}
+	return skin;
+}
+
+spSkeletonData *spSkeletonBinary_readSkeletonDataFile(spSkeletonBinary *self, const char *path) {
+	int length;
+	spSkeletonData *skeletonData;
+	const char *binary = _spUtil_readFile(path, &length);
+	if (length == 0 || !binary) {
+		_spSkeletonBinary_setError(self, "Unable to read skeleton file: ", path);
+		return 0;
+	}
+	skeletonData = spSkeletonBinary_readSkeletonData(self, (unsigned char *) binary, length);
+	FREE(binary);
+	return skeletonData;
+}
+
+spSkeletonData *spSkeletonBinary_readSkeletonData(spSkeletonBinary *self, const unsigned char *binary,
+												  const int length) {
+	int i, n, ii, nonessential;
+	char buffer[32];
+	int lowHash, highHash;
+	spSkeletonData *skeletonData;
+	_spSkeletonBinary *internal = SUB_CAST(_spSkeletonBinary, self);
+
+	_dataInput *input = NEW(_dataInput);
+	input->cursor = binary;
+	input->end = binary + length;
+
+	FREE(self->error);
+	CONST_CAST(char *, self->error) = 0;
+	internal->linkedMeshCount = 0;
+
+	skeletonData = spSkeletonData_create();
+	lowHash = readInt(input);
+	highHash = readInt(input);
+	sprintf(buffer, "%x%x", highHash, lowHash);
+	buffer[31] = 0;
+	skeletonData->hash = strdup(buffer);
+
+	skeletonData->version = readString(input);
+	if (!strlen(skeletonData->version)) {
+		FREE(skeletonData->version);
+		skeletonData->version = 0;
+	}
+
+	skeletonData->x = readFloat(input);
+	skeletonData->y = readFloat(input);
+	skeletonData->width = readFloat(input);
+	skeletonData->height = readFloat(input);
+
+	nonessential = readBoolean(input);
+
+	if (nonessential) {
+		skeletonData->fps = readFloat(input);
+		skeletonData->imagesPath = readString(input);
+		if (!strlen(skeletonData->imagesPath)) {
+			FREE(skeletonData->imagesPath);
+			skeletonData->imagesPath = 0;
+		}
+		skeletonData->audioPath = readString(input);
+		if (!strlen(skeletonData->audioPath)) {
+			FREE(skeletonData->audioPath);
+			skeletonData->audioPath = 0;
+		}
+	}
+
+	skeletonData->stringsCount = n = readVarint(input, 1);
+	skeletonData->strings = MALLOC(char *, skeletonData->stringsCount);
+	for (i = 0; i < n; i++) {
+		skeletonData->strings[i] = readString(input);
+	}
+
+	/* Bones. */
+	skeletonData->bonesCount = readVarint(input, 1);
+	skeletonData->bones = MALLOC(spBoneData *, skeletonData->bonesCount);
+	for (i = 0; i < skeletonData->bonesCount; ++i) {
+		spBoneData *data;
+		int mode;
+		const char *name = readString(input);
+		spBoneData *parent = i == 0 ? 0 : skeletonData->bones[readVarint(input, 1)];
+		/* TODO Avoid copying of name */
+		data = spBoneData_create(i, name, parent);
+		FREE(name);
+		data->rotation = readFloat(input);
+		data->x = readFloat(input) * self->scale;
+		data->y = readFloat(input) * self->scale;
+		data->scaleX = readFloat(input);
+		data->scaleY = readFloat(input);
+		data->shearX = readFloat(input);
+		data->shearY = readFloat(input);
+		data->length = readFloat(input) * self->scale;
+		mode = readVarint(input, 1);
+		switch (mode) {
+			case 0:
+				data->transformMode = SP_TRANSFORMMODE_NORMAL;
+				break;
+			case 1:
+				data->transformMode = SP_TRANSFORMMODE_ONLYTRANSLATION;
+				break;
+			case 2:
+				data->transformMode = SP_TRANSFORMMODE_NOROTATIONORREFLECTION;
+				break;
+			case 3:
+				data->transformMode = SP_TRANSFORMMODE_NOSCALE;
+				break;
+			case 4:
+				data->transformMode = SP_TRANSFORMMODE_NOSCALEORREFLECTION;
+				break;
+		}
+		data->skinRequired = readBoolean(input);
+		if (nonessential) {
+			readColor(input, &data->color.r, &data->color.g, &data->color.b, &data->color.a);
+		}
+		skeletonData->bones[i] = data;
+	}
+
+	/* Slots. */
+	skeletonData->slotsCount = readVarint(input, 1);
+	skeletonData->slots = MALLOC(spSlotData *, skeletonData->slotsCount);
+	for (i = 0; i < skeletonData->slotsCount; ++i) {
+		int r, g, b, a;
+		const char *attachmentName;
+		const char *slotName = readString(input);
+		spBoneData *boneData = skeletonData->bones[readVarint(input, 1)];
+		/* TODO Avoid copying of slotName */
+		spSlotData *slotData = spSlotData_create(i, slotName, boneData);
+		FREE(slotName);
+		readColor(input, &slotData->color.r, &slotData->color.g, &slotData->color.b, &slotData->color.a);
+		a = readByte(input);
+		r = readByte(input);
+		g = readByte(input);
+		b = readByte(input);
+		if (!(r == 0xff && g == 0xff && b == 0xff && a == 0xff)) {
+			slotData->darkColor = spColor_create();
+			spColor_setFromFloats(slotData->darkColor, r / 255.0f, g / 255.0f, b / 255.0f, 1);
+		}
+		attachmentName = readStringRef(input, skeletonData);
+		if (attachmentName) MALLOC_STR(slotData->attachmentName, attachmentName);
+		else
+			slotData->attachmentName = 0;
+		slotData->blendMode = (spBlendMode) readVarint(input, 1);
+		skeletonData->slots[i] = slotData;
+	}
+
+	/* IK constraints. */
+	skeletonData->ikConstraintsCount = readVarint(input, 1);
+	skeletonData->ikConstraints = MALLOC(spIkConstraintData *, skeletonData->ikConstraintsCount);
+	for (i = 0; i < skeletonData->ikConstraintsCount; ++i) {
+		const char *name = readString(input);
+		/* TODO Avoid copying of name */
+		spIkConstraintData *data = spIkConstraintData_create(name);
+		data->order = readVarint(input, 1);
+		data->skinRequired = readBoolean(input);
+		FREE(name);
+		data->bonesCount = readVarint(input, 1);
+		data->bones = MALLOC(spBoneData *, data->bonesCount);
+		for (ii = 0; ii < data->bonesCount; ++ii)
+			data->bones[ii] = skeletonData->bones[readVarint(input, 1)];
+		data->target = skeletonData->bones[readVarint(input, 1)];
+		data->mix = readFloat(input);
+		data->softness = readFloat(input);
+		data->bendDirection = readSByte(input);
+		data->compress = readBoolean(input);
+		data->stretch = readBoolean(input);
+		data->uniform = readBoolean(input);
+		skeletonData->ikConstraints[i] = data;
+	}
+
+	/* Transform constraints. */
+	skeletonData->transformConstraintsCount = readVarint(input, 1);
+	skeletonData->transformConstraints = MALLOC(
+			spTransformConstraintData *, skeletonData->transformConstraintsCount);
+	for (i = 0; i < skeletonData->transformConstraintsCount; ++i) {
+		const char *name = readString(input);
+		/* TODO Avoid copying of name */
+		spTransformConstraintData *data = spTransformConstraintData_create(name);
+		data->order = readVarint(input, 1);
+		data->skinRequired = readBoolean(input);
+		FREE(name);
+		data->bonesCount = readVarint(input, 1);
+		CONST_CAST(spBoneData **, data->bones) = MALLOC(spBoneData *, data->bonesCount);
+		for (ii = 0; ii < data->bonesCount; ++ii)
+			data->bones[ii] = skeletonData->bones[readVarint(input, 1)];
+		data->target = skeletonData->bones[readVarint(input, 1)];
+		data->local = readBoolean(input);
+		data->relative = readBoolean(input);
+		data->offsetRotation = readFloat(input);
+		data->offsetX = readFloat(input) * self->scale;
+		data->offsetY = readFloat(input) * self->scale;
+		data->offsetScaleX = readFloat(input);
+		data->offsetScaleY = readFloat(input);
+		data->offsetShearY = readFloat(input);
+		data->mixRotate = readFloat(input);
+		data->mixX = readFloat(input);
+		data->mixY = readFloat(input);
+		data->mixScaleX = readFloat(input);
+		data->mixScaleY = readFloat(input);
+		data->mixShearY = readFloat(input);
+		skeletonData->transformConstraints[i] = data;
+	}
+
+	/* Path constraints */
+	skeletonData->pathConstraintsCount = readVarint(input, 1);
+	skeletonData->pathConstraints = MALLOC(spPathConstraintData *, skeletonData->pathConstraintsCount);
+	for (i = 0; i < skeletonData->pathConstraintsCount; ++i) {
+		const char *name = readString(input);
+		/* TODO Avoid copying of name */
+		spPathConstraintData *data = spPathConstraintData_create(name);
+		data->order = readVarint(input, 1);
+		data->skinRequired = readBoolean(input);
+		FREE(name);
+		data->bonesCount = readVarint(input, 1);
+		CONST_CAST(spBoneData **, data->bones) = MALLOC(spBoneData *, data->bonesCount);
+		for (ii = 0; ii < data->bonesCount; ++ii)
+			data->bones[ii] = skeletonData->bones[readVarint(input, 1)];
+		data->target = skeletonData->slots[readVarint(input, 1)];
+		data->positionMode = (spPositionMode) readVarint(input, 1);
+		data->spacingMode = (spSpacingMode) readVarint(input, 1);
+		data->rotateMode = (spRotateMode) readVarint(input, 1);
+		data->offsetRotation = readFloat(input);
+		data->position = readFloat(input);
+		if (data->positionMode == SP_POSITION_MODE_FIXED) data->position *= self->scale;
+		data->spacing = readFloat(input);
+		if (data->spacingMode == SP_SPACING_MODE_LENGTH || data->spacingMode == SP_SPACING_MODE_FIXED)
+			data->spacing *= self->scale;
+		data->mixRotate = readFloat(input);
+		data->mixX = readFloat(input);
+		data->mixY = readFloat(input);
+		skeletonData->pathConstraints[i] = data;
+	}
+
+	/* Default skin. */
+	skeletonData->defaultSkin = spSkeletonBinary_readSkin(self, input, -1, skeletonData, nonessential);
+	skeletonData->skinsCount = readVarint(input, 1);
+
+	if (skeletonData->defaultSkin)
+		++skeletonData->skinsCount;
+
+	skeletonData->skins = MALLOC(spSkin *, skeletonData->skinsCount);
+
+	if (skeletonData->defaultSkin)
+		skeletonData->skins[0] = skeletonData->defaultSkin;
+
+	/* Skins. */
+	for (i = skeletonData->defaultSkin ? 1 : 0; i < skeletonData->skinsCount; ++i) {
+		skeletonData->skins[i] = spSkeletonBinary_readSkin(self, input, 0, skeletonData, nonessential);
+	}
+
+	/* Linked meshes. */
+	for (i = 0; i < internal->linkedMeshCount; ++i) {
+		_spLinkedMesh *linkedMesh = internal->linkedMeshes + i;
+		spSkin *skin = !linkedMesh->skin ? skeletonData->defaultSkin : spSkeletonData_findSkin(skeletonData, linkedMesh->skin);
+		spAttachment *parent;
+		if (!skin) {
+			FREE(input);
+			spSkeletonData_dispose(skeletonData);
+			_spSkeletonBinary_setError(self, "Skin not found: ", linkedMesh->skin);
+			return 0;
+		}
+		parent = spSkin_getAttachment(skin, linkedMesh->slotIndex, linkedMesh->parent);
+		if (!parent) {
+			FREE(input);
+			spSkeletonData_dispose(skeletonData);
+			_spSkeletonBinary_setError(self, "Parent mesh not found: ", linkedMesh->parent);
+			return 0;
+		}
+		linkedMesh->mesh->super.deformAttachment = linkedMesh->inheritDeform ? SUB_CAST(spVertexAttachment, parent)
+																			 : SUB_CAST(spVertexAttachment,
+																						linkedMesh->mesh);
+		spMeshAttachment_setParentMesh(linkedMesh->mesh, SUB_CAST(spMeshAttachment, parent));
+		spMeshAttachment_updateUVs(linkedMesh->mesh);
+		spAttachmentLoader_configureAttachment(self->attachmentLoader, SUPER(SUPER(linkedMesh->mesh)));
+	}
+
+	/* Events. */
+	skeletonData->eventsCount = readVarint(input, 1);
+	skeletonData->events = MALLOC(spEventData *, skeletonData->eventsCount);
+	for (i = 0; i < skeletonData->eventsCount; ++i) {
+		const char *name = readStringRef(input, skeletonData);
+		spEventData *eventData = spEventData_create(name);
+		eventData->intValue = readVarint(input, 0);
+		eventData->floatValue = readFloat(input);
+		eventData->stringValue = readString(input);
+		eventData->audioPath = readString(input);
+		if (eventData->audioPath) {
+			eventData->volume = readFloat(input);
+			eventData->balance = readFloat(input);
+		}
+		skeletonData->events[i] = eventData;
+	}
+
+	/* Animations. */
+	skeletonData->animationsCount = readVarint(input, 1);
+	skeletonData->animations = MALLOC(spAnimation *, skeletonData->animationsCount);
+	for (i = 0; i < skeletonData->animationsCount; ++i) {
+		const char *name = readString(input);
+		spAnimation *animation = _spSkeletonBinary_readAnimation(self, name, input, skeletonData);
+		FREE(name);
+		if (!animation) {
+			FREE(input);
+			spSkeletonData_dispose(skeletonData);
+			return 0;
+		}
+		skeletonData->animations[i] = animation;
+	}
+
+	FREE(input);
+	return skeletonData;
+}

+ 210 - 214
spine-c/spine-c/src/spine/SkeletonBounds.c

@@ -1,214 +1,210 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/SkeletonBounds.h>
-#include <limits.h>
-#include <spine/extension.h>
-
-spPolygon *spPolygon_create(int capacity) {
-	spPolygon *self = NEW(spPolygon);
-	self->capacity = capacity;
-	CONST_CAST(float*, self->vertices) = MALLOC(float, capacity);
-	return self;
-}
-
-void spPolygon_dispose(spPolygon *self) {
-	FREE(self->vertices);
-	FREE(self);
-}
-
-int/*bool*/spPolygon_containsPoint(spPolygon *self, float x, float y) {
-	int prevIndex = self->count - 2;
-	int inside = 0;
-	int i;
-	for (i = 0; i < self->count; i += 2) {
-		float vertexY = self->vertices[i + 1];
-		float prevY = self->vertices[prevIndex + 1];
-		if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
-			float vertexX = self->vertices[i];
-			if (vertexX + (y - vertexY) / (prevY - vertexY) * (self->vertices[prevIndex] - vertexX) < x)
-				inside = !inside;
-		}
-		prevIndex = i;
-	}
-	return inside;
-}
-
-int/*bool*/spPolygon_intersectsSegment(spPolygon *self, float x1, float y1, float x2, float y2) {
-	float width12 = x1 - x2, height12 = y1 - y2;
-	float det1 = x1 * y2 - y1 * x2;
-	float x3 = self->vertices[self->count - 2], y3 = self->vertices[self->count - 1];
-	int i;
-	for (i = 0; i < self->count; i += 2) {
-		float x4 = self->vertices[i], y4 = self->vertices[i + 1];
-		float det2 = x3 * y4 - y3 * x4;
-		float width34 = x3 - x4, height34 = y3 - y4;
-		float det3 = width12 * height34 - height12 * width34;
-		float x = (det1 * width34 - width12 * det2) / det3;
-		if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
-			float y = (det1 * height34 - height12 * det2) / det3;
-			if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1)))
-				return 1;
-		}
-		x3 = x4;
-		y3 = y4;
-	}
-	return 0;
-}
-
-/**/
-
-typedef struct {
-	spSkeletonBounds super;
-	int capacity;
-} _spSkeletonBounds;
-
-spSkeletonBounds *spSkeletonBounds_create() {
-	return SUPER(NEW(_spSkeletonBounds));
-}
-
-void spSkeletonBounds_dispose(spSkeletonBounds *self) {
-	int i;
-	for (i = 0; i < SUB_CAST(_spSkeletonBounds, self)->capacity; ++i)
-		if (self->polygons[i]) spPolygon_dispose(self->polygons[i]);
-	FREE(self->polygons);
-	FREE(self->boundingBoxes);
-	FREE(self);
-}
-
-void spSkeletonBounds_update(spSkeletonBounds *self, spSkeleton *skeleton, int/*bool*/updateAabb) {
-	int i;
-
-	_spSkeletonBounds *internal = SUB_CAST(_spSkeletonBounds, self);
-	if (internal->capacity < skeleton->slotsCount) {
-		spPolygon **newPolygons;
-
-		FREE(self->boundingBoxes);
-		self->boundingBoxes = MALLOC(spBoundingBoxAttachment*, skeleton->slotsCount);
-
-		newPolygons = CALLOC(spPolygon*, skeleton->slotsCount);
-		memcpy(newPolygons, self->polygons, sizeof(spPolygon *) * internal->capacity);
-		FREE(self->polygons);
-		self->polygons = newPolygons;
-
-		internal->capacity = skeleton->slotsCount;
-	}
-
-	self->minX = (float) INT_MAX;
-	self->minY = (float) INT_MAX;
-	self->maxX = (float) INT_MIN;
-	self->maxY = (float) INT_MIN;
-
-	self->count = 0;
-	for (i = 0; i < skeleton->slotsCount; ++i) {
-		spPolygon *polygon;
-		spBoundingBoxAttachment *boundingBox;
-		spAttachment *attachment;
-
-		spSlot *slot = skeleton->slots[i];
-		if (!slot->bone->active) continue;
-		attachment = slot->attachment;
-		if (!attachment || attachment->type != SP_ATTACHMENT_BOUNDING_BOX) continue;
-		boundingBox = (spBoundingBoxAttachment *) attachment;
-		self->boundingBoxes[self->count] = boundingBox;
-
-		polygon = self->polygons[self->count];
-		if (!polygon || polygon->capacity < boundingBox->super.worldVerticesLength) {
-			if (polygon) spPolygon_dispose(polygon);
-			self->polygons[self->count] = polygon = spPolygon_create(boundingBox->super.worldVerticesLength);
-		}
-		polygon->count = boundingBox->super.worldVerticesLength;
-		spVertexAttachment_computeWorldVertices(SUPER(boundingBox), slot, 0, polygon->count, polygon->vertices, 0, 2);
-
-		if (updateAabb) {
-			int ii = 0;
-			for (; ii < polygon->count; ii += 2) {
-				float x = polygon->vertices[ii];
-				float y = polygon->vertices[ii + 1];
-				if (x < self->minX) self->minX = x;
-				if (y < self->minY) self->minY = y;
-				if (x > self->maxX) self->maxX = x;
-				if (y > self->maxY) self->maxY = y;
-			}
-		}
-
-		self->count++;
-	}
-}
-
-int/*bool*/spSkeletonBounds_aabbContainsPoint(spSkeletonBounds *self, float x, float y) {
-	return x >= self->minX && x <= self->maxX && y >= self->minY && y <= self->maxY;
-}
-
-int/*bool*/spSkeletonBounds_aabbIntersectsSegment(spSkeletonBounds *self, float x1, float y1, float x2, float y2) {
-	float m, x, y;
-	if ((x1 <= self->minX && x2 <= self->minX)
-		|| (y1 <= self->minY && y2 <= self->minY)
-		|| (x1 >= self->maxX && x2 >= self->maxX)
-		|| (y1 >= self->maxY && y2 >= self->maxY)
-			)
-		return 0;
-	m = (y2 - y1) / (x2 - x1);
-	y = m * (self->minX - x1) + y1;
-	if (y > self->minY && y < self->maxY) return 1;
-	y = m * (self->maxX - x1) + y1;
-	if (y > self->minY && y < self->maxY) return 1;
-	x = (self->minY - y1) / m + x1;
-	if (x > self->minX && x < self->maxX) return 1;
-	x = (self->maxY - y1) / m + x1;
-	if (x > self->minX && x < self->maxX) return 1;
-	return 0;
-}
-
-int/*bool*/spSkeletonBounds_aabbIntersectsSkeleton(spSkeletonBounds *self, spSkeletonBounds *bounds) {
-	return self->minX < bounds->maxX && self->maxX > bounds->minX && self->minY < bounds->maxY &&
-		   self->maxY > bounds->minY;
-}
-
-spBoundingBoxAttachment *spSkeletonBounds_containsPoint(spSkeletonBounds *self, float x, float y) {
-	int i;
-	for (i = 0; i < self->count; ++i)
-		if (spPolygon_containsPoint(self->polygons[i], x, y)) return self->boundingBoxes[i];
-	return 0;
-}
-
-spBoundingBoxAttachment *
-spSkeletonBounds_intersectsSegment(spSkeletonBounds *self, float x1, float y1, float x2, float y2) {
-	int i;
-	for (i = 0; i < self->count; ++i)
-		if (spPolygon_intersectsSegment(self->polygons[i], x1, y1, x2, y2)) return self->boundingBoxes[i];
-	return 0;
-}
-
-spPolygon *spSkeletonBounds_getPolygon(spSkeletonBounds *self, spBoundingBoxAttachment *boundingBox) {
-	int i;
-	for (i = 0; i < self->count; ++i)
-		if (self->boundingBoxes[i] == boundingBox) return self->polygons[i];
-	return 0;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <limits.h>
+#include <spine/SkeletonBounds.h>
+#include <spine/extension.h>
+
+spPolygon *spPolygon_create(int capacity) {
+	spPolygon *self = NEW(spPolygon);
+	self->capacity = capacity;
+	CONST_CAST(float *, self->vertices) = MALLOC(float, capacity);
+	return self;
+}
+
+void spPolygon_dispose(spPolygon *self) {
+	FREE(self->vertices);
+	FREE(self);
+}
+
+int /*bool*/ spPolygon_containsPoint(spPolygon *self, float x, float y) {
+	int prevIndex = self->count - 2;
+	int inside = 0;
+	int i;
+	for (i = 0; i < self->count; i += 2) {
+		float vertexY = self->vertices[i + 1];
+		float prevY = self->vertices[prevIndex + 1];
+		if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
+			float vertexX = self->vertices[i];
+			if (vertexX + (y - vertexY) / (prevY - vertexY) * (self->vertices[prevIndex] - vertexX) < x)
+				inside = !inside;
+		}
+		prevIndex = i;
+	}
+	return inside;
+}
+
+int /*bool*/ spPolygon_intersectsSegment(spPolygon *self, float x1, float y1, float x2, float y2) {
+	float width12 = x1 - x2, height12 = y1 - y2;
+	float det1 = x1 * y2 - y1 * x2;
+	float x3 = self->vertices[self->count - 2], y3 = self->vertices[self->count - 1];
+	int i;
+	for (i = 0; i < self->count; i += 2) {
+		float x4 = self->vertices[i], y4 = self->vertices[i + 1];
+		float det2 = x3 * y4 - y3 * x4;
+		float width34 = x3 - x4, height34 = y3 - y4;
+		float det3 = width12 * height34 - height12 * width34;
+		float x = (det1 * width34 - width12 * det2) / det3;
+		if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
+			float y = (det1 * height34 - height12 * det2) / det3;
+			if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1)))
+				return 1;
+		}
+		x3 = x4;
+		y3 = y4;
+	}
+	return 0;
+}
+
+/**/
+
+typedef struct {
+	spSkeletonBounds super;
+	int capacity;
+} _spSkeletonBounds;
+
+spSkeletonBounds *spSkeletonBounds_create() {
+	return SUPER(NEW(_spSkeletonBounds));
+}
+
+void spSkeletonBounds_dispose(spSkeletonBounds *self) {
+	int i;
+	for (i = 0; i < SUB_CAST(_spSkeletonBounds, self)->capacity; ++i)
+		if (self->polygons[i]) spPolygon_dispose(self->polygons[i]);
+	FREE(self->polygons);
+	FREE(self->boundingBoxes);
+	FREE(self);
+}
+
+void spSkeletonBounds_update(spSkeletonBounds *self, spSkeleton *skeleton, int /*bool*/ updateAabb) {
+	int i;
+
+	_spSkeletonBounds *internal = SUB_CAST(_spSkeletonBounds, self);
+	if (internal->capacity < skeleton->slotsCount) {
+		spPolygon **newPolygons;
+
+		FREE(self->boundingBoxes);
+		self->boundingBoxes = MALLOC(spBoundingBoxAttachment *, skeleton->slotsCount);
+
+		newPolygons = CALLOC(spPolygon *, skeleton->slotsCount);
+		memcpy(newPolygons, self->polygons, sizeof(spPolygon *) * internal->capacity);
+		FREE(self->polygons);
+		self->polygons = newPolygons;
+
+		internal->capacity = skeleton->slotsCount;
+	}
+
+	self->minX = (float) INT_MAX;
+	self->minY = (float) INT_MAX;
+	self->maxX = (float) INT_MIN;
+	self->maxY = (float) INT_MIN;
+
+	self->count = 0;
+	for (i = 0; i < skeleton->slotsCount; ++i) {
+		spPolygon *polygon;
+		spBoundingBoxAttachment *boundingBox;
+		spAttachment *attachment;
+
+		spSlot *slot = skeleton->slots[i];
+		if (!slot->bone->active) continue;
+		attachment = slot->attachment;
+		if (!attachment || attachment->type != SP_ATTACHMENT_BOUNDING_BOX) continue;
+		boundingBox = (spBoundingBoxAttachment *) attachment;
+		self->boundingBoxes[self->count] = boundingBox;
+
+		polygon = self->polygons[self->count];
+		if (!polygon || polygon->capacity < boundingBox->super.worldVerticesLength) {
+			if (polygon) spPolygon_dispose(polygon);
+			self->polygons[self->count] = polygon = spPolygon_create(boundingBox->super.worldVerticesLength);
+		}
+		polygon->count = boundingBox->super.worldVerticesLength;
+		spVertexAttachment_computeWorldVertices(SUPER(boundingBox), slot, 0, polygon->count, polygon->vertices, 0, 2);
+
+		if (updateAabb) {
+			int ii = 0;
+			for (; ii < polygon->count; ii += 2) {
+				float x = polygon->vertices[ii];
+				float y = polygon->vertices[ii + 1];
+				if (x < self->minX) self->minX = x;
+				if (y < self->minY) self->minY = y;
+				if (x > self->maxX) self->maxX = x;
+				if (y > self->maxY) self->maxY = y;
+			}
+		}
+
+		self->count++;
+	}
+}
+
+int /*bool*/ spSkeletonBounds_aabbContainsPoint(spSkeletonBounds *self, float x, float y) {
+	return x >= self->minX && x <= self->maxX && y >= self->minY && y <= self->maxY;
+}
+
+int /*bool*/ spSkeletonBounds_aabbIntersectsSegment(spSkeletonBounds *self, float x1, float y1, float x2, float y2) {
+	float m, x, y;
+	if ((x1 <= self->minX && x2 <= self->minX) || (y1 <= self->minY && y2 <= self->minY) || (x1 >= self->maxX && x2 >= self->maxX) || (y1 >= self->maxY && y2 >= self->maxY))
+		return 0;
+	m = (y2 - y1) / (x2 - x1);
+	y = m * (self->minX - x1) + y1;
+	if (y > self->minY && y < self->maxY) return 1;
+	y = m * (self->maxX - x1) + y1;
+	if (y > self->minY && y < self->maxY) return 1;
+	x = (self->minY - y1) / m + x1;
+	if (x > self->minX && x < self->maxX) return 1;
+	x = (self->maxY - y1) / m + x1;
+	if (x > self->minX && x < self->maxX) return 1;
+	return 0;
+}
+
+int /*bool*/ spSkeletonBounds_aabbIntersectsSkeleton(spSkeletonBounds *self, spSkeletonBounds *bounds) {
+	return self->minX < bounds->maxX && self->maxX > bounds->minX && self->minY < bounds->maxY &&
+		   self->maxY > bounds->minY;
+}
+
+spBoundingBoxAttachment *spSkeletonBounds_containsPoint(spSkeletonBounds *self, float x, float y) {
+	int i;
+	for (i = 0; i < self->count; ++i)
+		if (spPolygon_containsPoint(self->polygons[i], x, y)) return self->boundingBoxes[i];
+	return 0;
+}
+
+spBoundingBoxAttachment *
+spSkeletonBounds_intersectsSegment(spSkeletonBounds *self, float x1, float y1, float x2, float y2) {
+	int i;
+	for (i = 0; i < self->count; ++i)
+		if (spPolygon_intersectsSegment(self->polygons[i], x1, y1, x2, y2)) return self->boundingBoxes[i];
+	return 0;
+}
+
+spPolygon *spSkeletonBounds_getPolygon(spSkeletonBounds *self, spBoundingBoxAttachment *boundingBox) {
+	int i;
+	for (i = 0; i < self->count; ++i)
+		if (self->boundingBoxes[i] == boundingBox) return self->polygons[i];
+	return 0;
+}

+ 5 - 3
spine-c/spine-c/src/spine/SkeletonClipping.c

@@ -61,7 +61,8 @@ static void _makeClockwise(spFloatArray *polygon) {
 	int verticeslength = polygon->size;
 
 	float area =
-			vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y;
+				  vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1],
+		  p1x, p1y, p2x, p2y;
 	for (i = 0, n = verticeslength - 3; i < n; i += 2) {
 		p1x = vertices[i];
 		p1y = vertices[i + 1];
@@ -237,7 +238,7 @@ void spSkeletonClipping_clipTriangles(spSkeletonClipping *self, float *vertices,
 	spFloatArray_clear(clippedUVs);
 	spUnsignedShortArray_clear(clippedTriangles);
 	i = 0;
-	continue_outer:
+continue_outer:
 	for (; i < trianglesLength; i += 3) {
 		int p;
 		int vertexOffset = triangles[i] * stride;
@@ -297,7 +298,8 @@ void spSkeletonClipping_clipTriangles(spSkeletonClipping *self, float *vertices,
 
 				s = clippedTriangles->size;
 				clippedTrianglesItems = spUnsignedShortArray_setSize(clippedTriangles,
-																	 s + 3 * (clipOutputCount - 2))->items;
+																	 s + 3 * (clipOutputCount - 2))
+												->items;
 				clipOutputCount--;
 				for (ii = 1; ii < clipOutputCount; ii++) {
 					clippedTrianglesItems[s] = index;

+ 175 - 175
spine-c/spine-c/src/spine/SkeletonData.c

@@ -1,175 +1,175 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/SkeletonData.h>
-#include <string.h>
-#include <spine/extension.h>
-
-spSkeletonData *spSkeletonData_create() {
-	return NEW(spSkeletonData);
-}
-
-void spSkeletonData_dispose(spSkeletonData *self) {
-	int i;
-
-	for (i = 0; i < self->stringsCount; ++i)
-		FREE(self->strings[i]);
-	FREE(self->strings);
-
-	for (i = 0; i < self->bonesCount; ++i)
-		spBoneData_dispose(self->bones[i]);
-	FREE(self->bones);
-
-	for (i = 0; i < self->slotsCount; ++i)
-		spSlotData_dispose(self->slots[i]);
-	FREE(self->slots);
-
-	for (i = 0; i < self->skinsCount; ++i)
-		spSkin_dispose(self->skins[i]);
-	FREE(self->skins);
-
-	for (i = 0; i < self->eventsCount; ++i)
-		spEventData_dispose(self->events[i]);
-	FREE(self->events);
-
-	for (i = 0; i < self->animationsCount; ++i)
-		spAnimation_dispose(self->animations[i]);
-	FREE(self->animations);
-
-	for (i = 0; i < self->ikConstraintsCount; ++i)
-		spIkConstraintData_dispose(self->ikConstraints[i]);
-	FREE(self->ikConstraints);
-
-	for (i = 0; i < self->transformConstraintsCount; ++i)
-		spTransformConstraintData_dispose(self->transformConstraints[i]);
-	FREE(self->transformConstraints);
-
-	for (i = 0; i < self->pathConstraintsCount; i++)
-		spPathConstraintData_dispose(self->pathConstraints[i]);
-	FREE(self->pathConstraints);
-
-	FREE(self->hash);
-	FREE(self->version);
-	FREE(self->imagesPath);
-	FREE(self->audioPath);
-
-	FREE(self);
-}
-
-spBoneData *spSkeletonData_findBone(const spSkeletonData *self, const char *boneName) {
-	int i;
-	for (i = 0; i < self->bonesCount; ++i)
-		if (strcmp(self->bones[i]->name, boneName) == 0) return self->bones[i];
-	return 0;
-}
-
-int spSkeletonData_findBoneIndex(const spSkeletonData *self, const char *boneName) {
-	int i;
-	for (i = 0; i < self->bonesCount; ++i)
-		if (strcmp(self->bones[i]->name, boneName) == 0) return i;
-	return -1;
-}
-
-spSlotData *spSkeletonData_findSlot(const spSkeletonData *self, const char *slotName) {
-	int i;
-	for (i = 0; i < self->slotsCount; ++i)
-		if (strcmp(self->slots[i]->name, slotName) == 0) return self->slots[i];
-	return 0;
-}
-
-int spSkeletonData_findSlotIndex(const spSkeletonData *self, const char *slotName) {
-	int i;
-	for (i = 0; i < self->slotsCount; ++i)
-		if (strcmp(self->slots[i]->name, slotName) == 0) return i;
-	return -1;
-}
-
-spSkin *spSkeletonData_findSkin(const spSkeletonData *self, const char *skinName) {
-	int i;
-	for (i = 0; i < self->skinsCount; ++i)
-		if (strcmp(self->skins[i]->name, skinName) == 0) return self->skins[i];
-	return 0;
-}
-
-spEventData *spSkeletonData_findEvent(const spSkeletonData *self, const char *eventName) {
-	int i;
-	for (i = 0; i < self->eventsCount; ++i)
-		if (strcmp(self->events[i]->name, eventName) == 0) return self->events[i];
-	return 0;
-}
-
-spAnimation *spSkeletonData_findAnimation(const spSkeletonData *self, const char *animationName) {
-	int i;
-	for (i = 0; i < self->animationsCount; ++i)
-		if (strcmp(self->animations[i]->name, animationName) == 0) return self->animations[i];
-	return 0;
-}
-
-spIkConstraintData *spSkeletonData_findIkConstraint(const spSkeletonData *self, const char *constraintName) {
-	int i;
-	for (i = 0; i < self->ikConstraintsCount; ++i)
-		if (strcmp(self->ikConstraints[i]->name, constraintName) == 0) return self->ikConstraints[i];
-	return 0;
-}
-
-int spSkeletonData_findIkConstraintIndex(const spSkeletonData *self, const char *constraintName) {
-	int i;
-	for (i = 0; i < self->ikConstraintsCount; ++i)
-		if (strcmp(self->ikConstraints[i]->name, constraintName) == 0) return i;
-	return -1;
-}
-
-spTransformConstraintData *
-spSkeletonData_findTransformConstraint(const spSkeletonData *self, const char *constraintName) {
-	int i;
-	for (i = 0; i < self->transformConstraintsCount; ++i)
-		if (strcmp(self->transformConstraints[i]->name, constraintName) == 0) return self->transformConstraints[i];
-	return 0;
-}
-
-int spSkeletonData_findTransformConstraintIndex(const spSkeletonData *self, const char *constraintName) {
-	int i;
-	for (i = 0; i < self->transformConstraintsCount; ++i)
-		if (strcmp(self->transformConstraints[i]->name, constraintName) == 0) return i;
-	return -1;
-}
-
-spPathConstraintData *spSkeletonData_findPathConstraint(const spSkeletonData *self, const char *constraintName) {
-	int i;
-	for (i = 0; i < self->pathConstraintsCount; ++i)
-		if (strcmp(self->pathConstraints[i]->name, constraintName) == 0) return self->pathConstraints[i];
-	return 0;
-}
-
-int spSkeletonData_findPathConstraintIndex(const spSkeletonData *self, const char *constraintName) {
-	int i;
-	for (i = 0; i < self->pathConstraintsCount; ++i)
-		if (strcmp(self->pathConstraints[i]->name, constraintName) == 0) return i;
-	return -1;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/SkeletonData.h>
+#include <spine/extension.h>
+#include <string.h>
+
+spSkeletonData *spSkeletonData_create() {
+	return NEW(spSkeletonData);
+}
+
+void spSkeletonData_dispose(spSkeletonData *self) {
+	int i;
+
+	for (i = 0; i < self->stringsCount; ++i)
+		FREE(self->strings[i]);
+	FREE(self->strings);
+
+	for (i = 0; i < self->bonesCount; ++i)
+		spBoneData_dispose(self->bones[i]);
+	FREE(self->bones);
+
+	for (i = 0; i < self->slotsCount; ++i)
+		spSlotData_dispose(self->slots[i]);
+	FREE(self->slots);
+
+	for (i = 0; i < self->skinsCount; ++i)
+		spSkin_dispose(self->skins[i]);
+	FREE(self->skins);
+
+	for (i = 0; i < self->eventsCount; ++i)
+		spEventData_dispose(self->events[i]);
+	FREE(self->events);
+
+	for (i = 0; i < self->animationsCount; ++i)
+		spAnimation_dispose(self->animations[i]);
+	FREE(self->animations);
+
+	for (i = 0; i < self->ikConstraintsCount; ++i)
+		spIkConstraintData_dispose(self->ikConstraints[i]);
+	FREE(self->ikConstraints);
+
+	for (i = 0; i < self->transformConstraintsCount; ++i)
+		spTransformConstraintData_dispose(self->transformConstraints[i]);
+	FREE(self->transformConstraints);
+
+	for (i = 0; i < self->pathConstraintsCount; i++)
+		spPathConstraintData_dispose(self->pathConstraints[i]);
+	FREE(self->pathConstraints);
+
+	FREE(self->hash);
+	FREE(self->version);
+	FREE(self->imagesPath);
+	FREE(self->audioPath);
+
+	FREE(self);
+}
+
+spBoneData *spSkeletonData_findBone(const spSkeletonData *self, const char *boneName) {
+	int i;
+	for (i = 0; i < self->bonesCount; ++i)
+		if (strcmp(self->bones[i]->name, boneName) == 0) return self->bones[i];
+	return 0;
+}
+
+int spSkeletonData_findBoneIndex(const spSkeletonData *self, const char *boneName) {
+	int i;
+	for (i = 0; i < self->bonesCount; ++i)
+		if (strcmp(self->bones[i]->name, boneName) == 0) return i;
+	return -1;
+}
+
+spSlotData *spSkeletonData_findSlot(const spSkeletonData *self, const char *slotName) {
+	int i;
+	for (i = 0; i < self->slotsCount; ++i)
+		if (strcmp(self->slots[i]->name, slotName) == 0) return self->slots[i];
+	return 0;
+}
+
+int spSkeletonData_findSlotIndex(const spSkeletonData *self, const char *slotName) {
+	int i;
+	for (i = 0; i < self->slotsCount; ++i)
+		if (strcmp(self->slots[i]->name, slotName) == 0) return i;
+	return -1;
+}
+
+spSkin *spSkeletonData_findSkin(const spSkeletonData *self, const char *skinName) {
+	int i;
+	for (i = 0; i < self->skinsCount; ++i)
+		if (strcmp(self->skins[i]->name, skinName) == 0) return self->skins[i];
+	return 0;
+}
+
+spEventData *spSkeletonData_findEvent(const spSkeletonData *self, const char *eventName) {
+	int i;
+	for (i = 0; i < self->eventsCount; ++i)
+		if (strcmp(self->events[i]->name, eventName) == 0) return self->events[i];
+	return 0;
+}
+
+spAnimation *spSkeletonData_findAnimation(const spSkeletonData *self, const char *animationName) {
+	int i;
+	for (i = 0; i < self->animationsCount; ++i)
+		if (strcmp(self->animations[i]->name, animationName) == 0) return self->animations[i];
+	return 0;
+}
+
+spIkConstraintData *spSkeletonData_findIkConstraint(const spSkeletonData *self, const char *constraintName) {
+	int i;
+	for (i = 0; i < self->ikConstraintsCount; ++i)
+		if (strcmp(self->ikConstraints[i]->name, constraintName) == 0) return self->ikConstraints[i];
+	return 0;
+}
+
+int spSkeletonData_findIkConstraintIndex(const spSkeletonData *self, const char *constraintName) {
+	int i;
+	for (i = 0; i < self->ikConstraintsCount; ++i)
+		if (strcmp(self->ikConstraints[i]->name, constraintName) == 0) return i;
+	return -1;
+}
+
+spTransformConstraintData *
+spSkeletonData_findTransformConstraint(const spSkeletonData *self, const char *constraintName) {
+	int i;
+	for (i = 0; i < self->transformConstraintsCount; ++i)
+		if (strcmp(self->transformConstraints[i]->name, constraintName) == 0) return self->transformConstraints[i];
+	return 0;
+}
+
+int spSkeletonData_findTransformConstraintIndex(const spSkeletonData *self, const char *constraintName) {
+	int i;
+	for (i = 0; i < self->transformConstraintsCount; ++i)
+		if (strcmp(self->transformConstraints[i]->name, constraintName) == 0) return i;
+	return -1;
+}
+
+spPathConstraintData *spSkeletonData_findPathConstraint(const spSkeletonData *self, const char *constraintName) {
+	int i;
+	for (i = 0; i < self->pathConstraintsCount; ++i)
+		if (strcmp(self->pathConstraints[i]->name, constraintName) == 0) return self->pathConstraints[i];
+	return 0;
+}
+
+int spSkeletonData_findPathConstraintIndex(const spSkeletonData *self, const char *constraintName) {
+	int i;
+	for (i = 0; i < self->pathConstraintsCount; ++i)
+		if (strcmp(self->pathConstraints[i]->name, constraintName) == 0) return i;
+	return -1;
+}

+ 1538 - 1522
spine-c/spine-c/src/spine/SkeletonJson.c

@@ -1,1522 +1,1538 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/SkeletonJson.h>
-#include <stdio.h>
-#include "Json.h"
-#include <spine/extension.h>
-#include <spine/AtlasAttachmentLoader.h>
-#include <spine/Array.h>
-
-#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
-#define strdup _strdup
-#endif
-
-typedef struct {
-	const char *parent;
-	const char *skin;
-	int slotIndex;
-	spMeshAttachment *mesh;
-	int inheritDeform;
-} _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);
-}
-
-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) {
-		if (value != 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 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->inheritDeform = 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 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 *deformJson = Json_getItem(root, "deform");
-	Json *drawOrderJson = Json_getItem(root, "drawOrder");
-	Json *events = Json_getItem(root, "events");
-	Json *boneMap, *slotMap, *constraintMap, *keyMap, *nextMap, *curve, *timelineMap;
-	int frame, bezier, i, n;
-	spColor color, color2, newColor, newColor2;
-
-	/* Slot timelines. */
-	for (slotMap = slots ? slots->child : 0; slotMap; slotMap = slotMap->next) {
-		int slotIndex = spSkeletonData_findSlotIndex(skeletonData, slotMap->name);
-		if (slotIndex == -1) {
-			cleanUpTimelines(timelines);
-			_spSkeletonJson_setError(self, NULL, "Slot not found: ", slotMap->name);
-			return NULL;
-		}
-
-		for (timelineMap = slotMap->child; timelineMap; timelineMap = timelineMap->next) {
-			if (strcmp(timelineMap->name, "attachment") == 0) {
-				int frameCount = timelineMap->size;
-				spAttachmentTimeline *timeline = spAttachmentTimeline_create(frameCount, 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")->valueString);
-				}
-				spTimelineArray_add(timelines, SUPER(timeline));
-
-			} else if (strcmp(timelineMap->name, "rgba") == 0) {
-				float time;
-				int frameCount = timelineMap->size;
-				int bezierCount = frameCount << 2;
-				spRGBATimeline *timeline = spRGBATimeline_create(frameCount, bezierCount, 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;
-				int frameCount = timelineMap->size;
-				int bezierCount = frameCount * 3;
-				spRGBTimeline *timeline = spRGBTimeline_create(frameCount, bezierCount, 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(timelineMap->size,
-																						 timelineMap->size, slotIndex)),
-															0, 1));
-			} else if (strcmp(timelineMap->name, "rgba2") == 0) {
-				float time;
-				int frameCount = timelineMap->size;
-				int bezierCount = frameCount * 7;
-				spRGBA2Timeline *timeline = spRGBA2Timeline_create(frameCount, bezierCount, 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;
-				int frameCount = timelineMap->size;
-				int bezierCount = frameCount * 7;
-				spRGBA2Timeline *timeline = spRGBA2Timeline_create(frameCount, bezierCount, 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 = spSkeletonData_findBoneIndex(skeletonData, boneMap->name);
-		if (boneIndex == -1) {
-			cleanUpTimelines(timelines);
-			_spSkeletonJson_setError(self, NULL, "Bone not found: ", boneMap->name);
-			return NULL;
-		}
-
-		for (timelineMap = boneMap->child; timelineMap; timelineMap = timelineMap->next) {
-			if (timelineMap->size == 0) continue;
-
-			if (strcmp(timelineMap->name, "rotate") == 0) {
-				spTimelineArray_add(timelines, readTimeline(timelineMap->child,
-															SUPER(spRotateTimeline_create(timelineMap->size,
-																						  timelineMap->size,
-																						  boneIndex)), 0, 1));
-			} else if (strcmp(timelineMap->name, "translate") == 0) {
-				spTranslateTimeline *timeline = spTranslateTimeline_create(timelineMap->size, timelineMap->size << 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(timelineMap->size, timelineMap->size,
-																			 boneIndex);
-				spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 0, scale));
-			} else if (strcmp(timelineMap->name, "translatey") == 0) {
-				spTranslateYTimeline *timeline = spTranslateYTimeline_create(timelineMap->size, timelineMap->size,
-																			 boneIndex);
-				spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 0, scale));
-			} else if (strcmp(timelineMap->name, "scale") == 0) {
-				spScaleTimeline *timeline = spScaleTimeline_create(timelineMap->size, timelineMap->size << 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(timelineMap->size, timelineMap->size, boneIndex);
-				spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 1, 1));
-			} else if (strcmp(timelineMap->name, "scaley") == 0) {
-				spScaleYTimeline *timeline = spScaleYTimeline_create(timelineMap->size, timelineMap->size, boneIndex);
-				spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 1, 1));
-			} else if (strcmp(timelineMap->name, "shear") == 0) {
-				spShearTimeline *timeline = spShearTimeline_create(timelineMap->size, timelineMap->size << 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(timelineMap->size, timelineMap->size, boneIndex);
-				spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 0, 1));
-			} else if (strcmp(timelineMap->name, "sheary") == 0) {
-				spShearYTimeline *timeline = spShearYTimeline_create(timelineMap->size, timelineMap->size, boneIndex);
-				spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 0, 1));
-			} 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 = spSkeletonData_findIkConstraintIndex(skeletonData, constraint->name);
-		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 = spSkeletonData_findTransformConstraintIndex(skeletonData, constraint->name);
-		timeline = spTransformConstraintTimeline_create(constraintMap->size, constraintMap->size << 2, 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 *data = spSkeletonData_findPathConstraint(skeletonData, constraintMap->name);
-		int index;
-		if (!data) {
-			cleanUpTimelines(timelines);
-			_spSkeletonJson_setError(self, NULL, "Path constraint not found: ", constraintMap->name);
-			return NULL;
-		}
-		index = spSkeletonData_findPathConstraintIndex(skeletonData, data->name);
-		for (timelineMap = constraintMap->child; timelineMap; timelineMap = timelineMap->next) {
-			const char *timelineName;
-			keyMap = timelineMap->child;
-			if (keyMap == NULL) continue;
-			timelineName = timelineMap->name;
-			if (strcmp(timelineName, "position") == 0) {
-				spPathConstraintPositionTimeline *timeline = spPathConstraintPositionTimeline_create(timelineMap->size,
-																									 timelineMap->size,
-																									 index);
-				spTimelineArray_add(timelines, readTimeline(keyMap, SUPER(timeline), 0,
-															data->positionMode == SP_POSITION_MODE_FIXED ? scale : 1));
-			} else if (strcmp(timelineName, "spacing") == 0) {
-				spCurveTimeline1 *timeline = SUPER(
-						spPathConstraintSpacingTimeline_create(timelineMap->size, timelineMap->size, index));
-				spTimelineArray_add(timelines, readTimeline(keyMap, timeline, 0,
-															data->spacingMode == SP_SPACING_MODE_LENGTH ||
-															data->spacingMode == SP_SPACING_MODE_FIXED ? scale : 1));
-			} else if (strcmp(timelineName, "mix") == 0) {
-				spPathConstraintMixTimeline *timeline = spPathConstraintMixTimeline_create(timelineMap->size,
-																						   timelineMap->size * 3,
-																						   index);
-				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)));
-			}
-		}
-	}
-
-	/* Deform timelines. */
-	for (constraintMap = deformJson ? deformJson->child : 0; constraintMap; constraintMap = constraintMap->next) {
-		spSkin *skin = spSkeletonData_findSkin(skeletonData, constraintMap->name);
-		for (slotMap = constraintMap->child; slotMap; slotMap = slotMap->next) {
-			int slotIndex = spSkeletonData_findSlotIndex(skeletonData, slotMap->name);
-
-			for (timelineMap = slotMap->child; timelineMap; timelineMap = timelineMap->next) {
-				float *tempDeform;
-				spVertexAttachment *attachment;
-				int weighted, deformLength;
-				spDeformTimeline *timeline;
-				float time;
-				keyMap = timelineMap->child;
-				if (keyMap == NULL) continue;
-
-				attachment = SUB_CAST(spVertexAttachment, spSkin_getAttachment(skin, slotIndex, timelineMap->name));
-				if (!attachment) {
-					cleanUpTimelines(timelines);
-					_spSkeletonJson_setError(self, 0, "Attachment not found: ", timelineMap->name);
-					return 0;
-				}
-				weighted = attachment->bones != 0;
-				deformLength = weighted ? attachment->verticesCount / 3 * 2 : attachment->verticesCount;
-				tempDeform = MALLOC(float, deformLength);
-				timeline = spDeformTimeline_create(timelineMap->size, deformLength, timelineMap->size, slotIndex,
-												   attachment);
-
-				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 = attachment->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 = attachment->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)));
-			}
-		}
-	}
-
-	/* 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 = spSkeletonData_findSlotIndex(skeletonData, Json_getString(offsetMap, "slot", 0));
-					if (slotIndex == -1) {
-						cleanUpTimelines(timelines);
-						_spSkeletonJson_setError(self, 0, "Slot not found: ", Json_getString(offsetMap, "slot", 0));
-						return 0;
-					}
-					/* 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 0;
-			}
-			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 0;
-	}
-	skeletonData = spSkeletonJson_readSkeletonData(self, json);
-	FREE(json);
-	return skeletonData;
-}
-
-spSkeletonData *spSkeletonJson_readSkeletonData(spSkeletonJson *self, const char *json) {
-	int i, ii;
-	spSkeletonData *skeletonData;
-	Json *root, *skeleton, *bones, *boneMap, *ik, *transform, *pathJson, *slots, *skins, *animations, *events;
-	_spSkeletonJson *internal = SUB_CAST(_spSkeletonJson, self);
-
-	FREE(self->error);
-	CONST_CAST(char*, self->error) = 0;
-	internal->linkedMeshCount = 0;
-
-	root = Json_create(json);
-
-	if (!root) {
-		_spSkeletonJson_setError(self, 0, "Invalid skeleton JSON: ", Json_getError());
-		return 0;
-	}
-
-	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));
-		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->fps = Json_getFloat(skeleton, "fps", 30);
-		skeletonData->imagesPath = Json_getString(skeleton, "images", 0);
-		if (skeletonData->imagesPath) skeletonData->imagesPath = strdup(skeletonData->imagesPath);
-		skeletonData->audioPath = Json_getString(skeleton, "audio", 0);
-		if (skeletonData->audioPath) skeletonData->audioPath = strdup(skeletonData->audioPath);
-	}
-
-	/* 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 *transformMode;
-		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 0;
-			}
-		}
-
-		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);
-		transformMode = Json_getString(boneMap, "transform", "normal");
-		data->transformMode = SP_TRANSFORMMODE_NORMAL;
-		if (strcmp(transformMode, "normal") == 0) data->transformMode = SP_TRANSFORMMODE_NORMAL;
-		else if (strcmp(transformMode, "onlyTranslation") == 0) data->transformMode = SP_TRANSFORMMODE_ONLYTRANSLATION;
-		else if (strcmp(transformMode, "noRotationOrReflection") == 0)
-			data->transformMode = SP_TRANSFORMMODE_NOROTATIONORREFLECTION;
-		else if (strcmp(transformMode, "noScale") == 0) data->transformMode = SP_TRANSFORMMODE_NOSCALE;
-		else if (strcmp(transformMode, "noScaleOrReflection") == 0)
-			data->transformMode = SP_TRANSFORMMODE_NOSCALEORREFLECTION;
-		data->skinRequired = Json_getInt(boneMap, "skin", 0) ? 1 : 0;
-
-		color = Json_getString(boneMap, "color", 0);
-		if (color) toColor2(&data->color, color, -1);
-
-		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 0;
-			}
-
-			data = spSlotData_create(i, Json_getString(slotMap, "name", 0), 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),
-									  toColor(dark, 3));
-			}
-
-			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;
-			}
-
-			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 0;
-				}
-			}
-
-			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 0;
-			}
-
-			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;
-			CONST_CAST(spBoneData**, 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 0;
-				}
-			}
-
-			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 0;
-			}
-
-			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->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;
-			CONST_CAST(spBoneData**, 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 0;
-				}
-			}
-
-			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 0;
-			}
-
-			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;
-
-			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;
-		}
-	}
-
-	/* 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 0;
-					}
-					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 0;
-					}
-					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 0;
-					}
-					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 0;
-					}
-					spTransformConstraintDataArray_add(skin->transformConstraints, constraint);
-				}
-			}
-
-			skeletonData->skins[skeletonData->skinsCount++] = skin;
-			if (strcmp(skin->name, "default") == 0) skeletonData->defaultSkin = skin;
-
-			for (attachmentsMap = Json_getItem(skinMap,
-											   "attachments")->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;
-
-					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 0;
-					}
-
-					attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, attachmentName,
-																	 path);
-					if (!attachment) {
-						if (self->attachmentLoader->error1) {
-							spSkeletonData_dispose(skeletonData);
-							_spSkeletonJson_setError(self, root, self->attachmentLoader->error1,
-													 self->attachmentLoader->error2);
-							return 0;
-						}
-						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;
-
-							color = Json_getString(attachmentMap, "color", 0);
-							if (color) {
-								spColor_setFromFloats(&region->color,
-													  toColor(color, 0),
-													  toColor(color, 1),
-													  toColor(color, 2),
-													  toColor(color, 3));
-							}
-
-							spRegionAttachment_updateOffset(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;
-
-							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);
-
-								spMeshAttachment_updateUVs(mesh);
-
-								mesh->hullLength = Json_getInt(attachmentMap, "hull", 0);
-
-								entry = Json_getItem(attachmentMap, "edges");
-								if (entry) {
-									mesh->edgesCount = entry->size;
-									mesh->edges = MALLOC(int, entry->size);
-									for (entry = entry->child, ii = 0; entry; entry = entry->next, ++ii)
-										mesh->edges[ii] = entry->valueInt;
-								}
-
-								spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
-							} else {
-								int inheritDeform = Json_getInt(attachmentMap, "deform", 1);
-								_spSkeletonJson_addLinkedMesh(self, SUB_CAST(spMeshAttachment, attachment),
-															  Json_getString(attachmentMap, "skin", 0), slot->index,
-															  entry->valueString, inheritDeform);
-							}
-							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 0;
-		}
-		parent = spSkin_getAttachment(skin, linkedMesh->slotIndex, linkedMesh->parent);
-		if (!parent) {
-			spSkeletonData_dispose(skeletonData);
-			_spSkeletonJson_setError(self, 0, "Parent mesh not found: ", linkedMesh->parent);
-			return 0;
-		}
-		linkedMesh->mesh->super.deformAttachment = linkedMesh->inheritDeform ? SUB_CAST(spVertexAttachment, parent)
-																			 : SUB_CAST(spVertexAttachment,
-																						linkedMesh->mesh);
-		spMeshAttachment_setParentMesh(linkedMesh->mesh, SUB_CAST(spMeshAttachment, parent));
-		spMeshAttachment_updateUVs(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 0;
-			}
-			skeletonData->animations[skeletonData->animationsCount++] = animation;
-		}
-	}
-
-	Json_dispose(root);
-	return skeletonData;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "Json.h"
+#include <spine/Array.h>
+#include <spine/AtlasAttachmentLoader.h>
+#include <spine/SkeletonJson.h>
+#include <spine/extension.h>
+#include <stdio.h>
+
+#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
+#define strdup _strdup
+#endif
+
+typedef struct {
+	const char *parent;
+	const char *skin;
+	int slotIndex;
+	spMeshAttachment *mesh;
+	int inheritDeform;
+} _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);
+}
+
+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) {
+		if (value != 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 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->inheritDeform = 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 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 *deformJson = Json_getItem(root, "deform");
+	Json *drawOrderJson = Json_getItem(root, "drawOrder");
+	Json *events = Json_getItem(root, "events");
+	Json *boneMap, *slotMap, *constraintMap, *keyMap, *nextMap, *curve, *timelineMap;
+	int frame, bezier, i, n;
+	spColor color, color2, newColor, newColor2;
+
+	/* Slot timelines. */
+	for (slotMap = slots ? slots->child : 0; slotMap; slotMap = slotMap->next) {
+		int slotIndex = spSkeletonData_findSlotIndex(skeletonData, slotMap->name);
+		if (slotIndex == -1) {
+			cleanUpTimelines(timelines);
+			_spSkeletonJson_setError(self, NULL, "Slot not found: ", slotMap->name);
+			return NULL;
+		}
+
+		for (timelineMap = slotMap->child; timelineMap; timelineMap = timelineMap->next) {
+			if (strcmp(timelineMap->name, "attachment") == 0) {
+				int frameCount = timelineMap->size;
+				spAttachmentTimeline *timeline = spAttachmentTimeline_create(frameCount, 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")->valueString);
+				}
+				spTimelineArray_add(timelines, SUPER(timeline));
+
+			} else if (strcmp(timelineMap->name, "rgba") == 0) {
+				float time;
+				int frameCount = timelineMap->size;
+				int bezierCount = frameCount << 2;
+				spRGBATimeline *timeline = spRGBATimeline_create(frameCount, bezierCount, 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;
+				int frameCount = timelineMap->size;
+				int bezierCount = frameCount * 3;
+				spRGBTimeline *timeline = spRGBTimeline_create(frameCount, bezierCount, 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(timelineMap->size,
+																						 timelineMap->size, slotIndex)),
+															0, 1));
+			} else if (strcmp(timelineMap->name, "rgba2") == 0) {
+				float time;
+				int frameCount = timelineMap->size;
+				int bezierCount = frameCount * 7;
+				spRGBA2Timeline *timeline = spRGBA2Timeline_create(frameCount, bezierCount, 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;
+				int frameCount = timelineMap->size;
+				int bezierCount = frameCount * 7;
+				spRGBA2Timeline *timeline = spRGBA2Timeline_create(frameCount, bezierCount, 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 = spSkeletonData_findBoneIndex(skeletonData, boneMap->name);
+		if (boneIndex == -1) {
+			cleanUpTimelines(timelines);
+			_spSkeletonJson_setError(self, NULL, "Bone not found: ", boneMap->name);
+			return NULL;
+		}
+
+		for (timelineMap = boneMap->child; timelineMap; timelineMap = timelineMap->next) {
+			if (timelineMap->size == 0) continue;
+
+			if (strcmp(timelineMap->name, "rotate") == 0) {
+				spTimelineArray_add(timelines, readTimeline(timelineMap->child,
+															SUPER(spRotateTimeline_create(timelineMap->size,
+																						  timelineMap->size,
+																						  boneIndex)),
+															0, 1));
+			} else if (strcmp(timelineMap->name, "translate") == 0) {
+				spTranslateTimeline *timeline = spTranslateTimeline_create(timelineMap->size, timelineMap->size << 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(timelineMap->size, timelineMap->size,
+																			 boneIndex);
+				spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 0, scale));
+			} else if (strcmp(timelineMap->name, "translatey") == 0) {
+				spTranslateYTimeline *timeline = spTranslateYTimeline_create(timelineMap->size, timelineMap->size,
+																			 boneIndex);
+				spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 0, scale));
+			} else if (strcmp(timelineMap->name, "scale") == 0) {
+				spScaleTimeline *timeline = spScaleTimeline_create(timelineMap->size, timelineMap->size << 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(timelineMap->size, timelineMap->size, boneIndex);
+				spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 1, 1));
+			} else if (strcmp(timelineMap->name, "scaley") == 0) {
+				spScaleYTimeline *timeline = spScaleYTimeline_create(timelineMap->size, timelineMap->size, boneIndex);
+				spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 1, 1));
+			} else if (strcmp(timelineMap->name, "shear") == 0) {
+				spShearTimeline *timeline = spShearTimeline_create(timelineMap->size, timelineMap->size << 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(timelineMap->size, timelineMap->size, boneIndex);
+				spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 0, 1));
+			} else if (strcmp(timelineMap->name, "sheary") == 0) {
+				spShearYTimeline *timeline = spShearYTimeline_create(timelineMap->size, timelineMap->size, boneIndex);
+				spTimelineArray_add(timelines, readTimeline(timelineMap->child, SUPER(timeline), 0, 1));
+			} 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 = spSkeletonData_findIkConstraintIndex(skeletonData, constraint->name);
+		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 = spSkeletonData_findTransformConstraintIndex(skeletonData, constraint->name);
+		timeline = spTransformConstraintTimeline_create(constraintMap->size, constraintMap->size << 2, 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 *data = spSkeletonData_findPathConstraint(skeletonData, constraintMap->name);
+		int index;
+		if (!data) {
+			cleanUpTimelines(timelines);
+			_spSkeletonJson_setError(self, NULL, "Path constraint not found: ", constraintMap->name);
+			return NULL;
+		}
+		index = spSkeletonData_findPathConstraintIndex(skeletonData, data->name);
+		for (timelineMap = constraintMap->child; timelineMap; timelineMap = timelineMap->next) {
+			const char *timelineName;
+			keyMap = timelineMap->child;
+			if (keyMap == NULL) continue;
+			timelineName = timelineMap->name;
+			if (strcmp(timelineName, "position") == 0) {
+				spPathConstraintPositionTimeline *timeline = spPathConstraintPositionTimeline_create(timelineMap->size,
+																									 timelineMap->size,
+																									 index);
+				spTimelineArray_add(timelines, readTimeline(keyMap, SUPER(timeline), 0,
+															data->positionMode == SP_POSITION_MODE_FIXED ? scale : 1));
+			} else if (strcmp(timelineName, "spacing") == 0) {
+				spCurveTimeline1 *timeline = SUPER(
+						spPathConstraintSpacingTimeline_create(timelineMap->size, timelineMap->size, index));
+				spTimelineArray_add(timelines, readTimeline(keyMap, timeline, 0,
+															data->spacingMode == SP_SPACING_MODE_LENGTH ||
+																			data->spacingMode == SP_SPACING_MODE_FIXED
+																	? scale
+																	: 1));
+			} else if (strcmp(timelineName, "mix") == 0) {
+				spPathConstraintMixTimeline *timeline = spPathConstraintMixTimeline_create(timelineMap->size,
+																						   timelineMap->size * 3,
+																						   index);
+				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)));
+			}
+		}
+	}
+
+	/* Deform timelines. */
+	for (constraintMap = deformJson ? deformJson->child : 0; constraintMap; constraintMap = constraintMap->next) {
+		spSkin *skin = spSkeletonData_findSkin(skeletonData, constraintMap->name);
+		for (slotMap = constraintMap->child; slotMap; slotMap = slotMap->next) {
+			int slotIndex = spSkeletonData_findSlotIndex(skeletonData, slotMap->name);
+
+			for (timelineMap = slotMap->child; timelineMap; timelineMap = timelineMap->next) {
+				float *tempDeform;
+				spVertexAttachment *attachment;
+				int weighted, deformLength;
+				spDeformTimeline *timeline;
+				float time;
+				keyMap = timelineMap->child;
+				if (keyMap == NULL) continue;
+
+				attachment = SUB_CAST(spVertexAttachment, spSkin_getAttachment(skin, slotIndex, timelineMap->name));
+				if (!attachment) {
+					cleanUpTimelines(timelines);
+					_spSkeletonJson_setError(self, 0, "Attachment not found: ", timelineMap->name);
+					return 0;
+				}
+				weighted = attachment->bones != 0;
+				deformLength = weighted ? attachment->verticesCount / 3 * 2 : attachment->verticesCount;
+				tempDeform = MALLOC(float, deformLength);
+				timeline = spDeformTimeline_create(timelineMap->size, deformLength, timelineMap->size, slotIndex,
+												   attachment);
+
+				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 = attachment->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 = attachment->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)));
+			}
+		}
+	}
+
+	/* 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 = spSkeletonData_findSlotIndex(skeletonData, Json_getString(offsetMap, "slot", 0));
+					if (slotIndex == -1) {
+						cleanUpTimelines(timelines);
+						_spSkeletonJson_setError(self, 0, "Slot not found: ", Json_getString(offsetMap, "slot", 0));
+						return 0;
+					}
+					/* 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 0;
+			}
+			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 0;
+	}
+	skeletonData = spSkeletonJson_readSkeletonData(self, json);
+	FREE(json);
+	return skeletonData;
+}
+
+spSkeletonData *spSkeletonJson_readSkeletonData(spSkeletonJson *self, const char *json) {
+	int i, ii;
+	spSkeletonData *skeletonData;
+	Json *root, *skeleton, *bones, *boneMap, *ik, *transform, *pathJson, *slots, *skins, *animations, *events;
+	_spSkeletonJson *internal = SUB_CAST(_spSkeletonJson, self);
+
+	FREE(self->error);
+	CONST_CAST(char *, self->error) = 0;
+	internal->linkedMeshCount = 0;
+
+	root = Json_create(json);
+
+	if (!root) {
+		_spSkeletonJson_setError(self, 0, "Invalid skeleton JSON: ", Json_getError());
+		return 0;
+	}
+
+	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));
+		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->fps = Json_getFloat(skeleton, "fps", 30);
+		skeletonData->imagesPath = Json_getString(skeleton, "images", 0);
+		if (skeletonData->imagesPath) skeletonData->imagesPath = strdup(skeletonData->imagesPath);
+		skeletonData->audioPath = Json_getString(skeleton, "audio", 0);
+		if (skeletonData->audioPath) skeletonData->audioPath = strdup(skeletonData->audioPath);
+	}
+
+	/* 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 *transformMode;
+		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 0;
+			}
+		}
+
+		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);
+		transformMode = Json_getString(boneMap, "transform", "normal");
+		data->transformMode = SP_TRANSFORMMODE_NORMAL;
+		if (strcmp(transformMode, "normal") == 0) data->transformMode = SP_TRANSFORMMODE_NORMAL;
+		else if (strcmp(transformMode, "onlyTranslation") == 0)
+			data->transformMode = SP_TRANSFORMMODE_ONLYTRANSLATION;
+		else if (strcmp(transformMode, "noRotationOrReflection") == 0)
+			data->transformMode = SP_TRANSFORMMODE_NOROTATIONORREFLECTION;
+		else if (strcmp(transformMode, "noScale") == 0)
+			data->transformMode = SP_TRANSFORMMODE_NOSCALE;
+		else if (strcmp(transformMode, "noScaleOrReflection") == 0)
+			data->transformMode = SP_TRANSFORMMODE_NOSCALEORREFLECTION;
+		data->skinRequired = Json_getInt(boneMap, "skin", 0) ? 1 : 0;
+
+		color = Json_getString(boneMap, "color", 0);
+		if (color) toColor2(&data->color, color, -1);
+
+		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 0;
+			}
+
+			data = spSlotData_create(i, Json_getString(slotMap, "name", 0), 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),
+									  toColor(dark, 3));
+			}
+
+			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;
+			}
+
+			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 0;
+				}
+			}
+
+			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 0;
+			}
+
+			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;
+			CONST_CAST(spBoneData **, 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 0;
+				}
+			}
+
+			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 0;
+			}
+
+			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->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;
+			CONST_CAST(spBoneData **, 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 0;
+				}
+			}
+
+			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 0;
+			}
+
+			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;
+
+			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;
+		}
+	}
+
+	/* 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 0;
+					}
+					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 0;
+					}
+					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 0;
+					}
+					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 0;
+					}
+					spTransformConstraintDataArray_add(skin->transformConstraints, constraint);
+				}
+			}
+
+			skeletonData->skins[skeletonData->skinsCount++] = skin;
+			if (strcmp(skin->name, "default") == 0) skeletonData->defaultSkin = skin;
+
+			for (attachmentsMap = Json_getItem(skinMap,
+											   "attachments")
+										  ->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;
+
+					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 0;
+					}
+
+					attachment = spAttachmentLoader_createAttachment(self->attachmentLoader, skin, type, attachmentName,
+																	 path);
+					if (!attachment) {
+						if (self->attachmentLoader->error1) {
+							spSkeletonData_dispose(skeletonData);
+							_spSkeletonJson_setError(self, root, self->attachmentLoader->error1,
+													 self->attachmentLoader->error2);
+							return 0;
+						}
+						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;
+
+							color = Json_getString(attachmentMap, "color", 0);
+							if (color) {
+								spColor_setFromFloats(&region->color,
+													  toColor(color, 0),
+													  toColor(color, 1),
+													  toColor(color, 2),
+													  toColor(color, 3));
+							}
+
+							spRegionAttachment_updateOffset(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;
+
+							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);
+
+								spMeshAttachment_updateUVs(mesh);
+
+								mesh->hullLength = Json_getInt(attachmentMap, "hull", 0);
+
+								entry = Json_getItem(attachmentMap, "edges");
+								if (entry) {
+									mesh->edgesCount = entry->size;
+									mesh->edges = MALLOC(int, entry->size);
+									for (entry = entry->child, ii = 0; entry; entry = entry->next, ++ii)
+										mesh->edges[ii] = entry->valueInt;
+								}
+
+								spAttachmentLoader_configureAttachment(self->attachmentLoader, attachment);
+							} else {
+								int inheritDeform = Json_getInt(attachmentMap, "deform", 1);
+								_spSkeletonJson_addLinkedMesh(self, SUB_CAST(spMeshAttachment, attachment),
+															  Json_getString(attachmentMap, "skin", 0), slot->index,
+															  entry->valueString, inheritDeform);
+							}
+							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 0;
+		}
+		parent = spSkin_getAttachment(skin, linkedMesh->slotIndex, linkedMesh->parent);
+		if (!parent) {
+			spSkeletonData_dispose(skeletonData);
+			_spSkeletonJson_setError(self, 0, "Parent mesh not found: ", linkedMesh->parent);
+			return 0;
+		}
+		linkedMesh->mesh->super.deformAttachment = linkedMesh->inheritDeform ? SUB_CAST(spVertexAttachment, parent)
+																			 : SUB_CAST(spVertexAttachment,
+																						linkedMesh->mesh);
+		spMeshAttachment_setParentMesh(linkedMesh->mesh, SUB_CAST(spMeshAttachment, parent));
+		spMeshAttachment_updateUVs(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 0;
+			}
+			skeletonData->animations[skeletonData->animationsCount++] = animation;
+		}
+	}
+
+	Json_dispose(root);
+	return skeletonData;
+}

+ 281 - 283
spine-c/spine-c/src/spine/Skin.c

@@ -1,283 +1,281 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/Skin.h>
-#include <spine/extension.h>
-#include <stdio.h>
-
-_SP_ARRAY_IMPLEMENT_TYPE(spBoneDataArray, spBoneData*)
-
-_SP_ARRAY_IMPLEMENT_TYPE(spIkConstraintDataArray, spIkConstraintData*)
-
-_SP_ARRAY_IMPLEMENT_TYPE(spTransformConstraintDataArray, spTransformConstraintData*)
-
-_SP_ARRAY_IMPLEMENT_TYPE(spPathConstraintDataArray, spPathConstraintData*)
-
-_Entry *_Entry_create(int slotIndex, const char *name, spAttachment *attachment) {
-	_Entry *self = NEW(_Entry);
-	self->slotIndex = slotIndex;
-	MALLOC_STR(self->name, name);
-	self->attachment = attachment;
-	return self;
-}
-
-void _Entry_dispose(_Entry *self) {
-	spAttachment_dispose(self->attachment);
-	FREE(self->name);
-	FREE(self);
-}
-
-static _SkinHashTableEntry *_SkinHashTableEntry_create(_Entry *entry) {
-	_SkinHashTableEntry *self = NEW(_SkinHashTableEntry);
-	self->entry = entry;
-	return self;
-}
-
-static void _SkinHashTableEntry_dispose(_SkinHashTableEntry *self) {
-	FREE(self);
-}
-
-/**/
-
-spSkin *spSkin_create(const char *name) {
-	spSkin *self = SUPER(NEW(_spSkin));
-	MALLOC_STR(self->name, name);
-	self->bones = spBoneDataArray_create(4);
-	self->ikConstraints = spIkConstraintDataArray_create(4);
-	self->transformConstraints = spTransformConstraintDataArray_create(4);
-	self->pathConstraints = spPathConstraintDataArray_create(4);
-	return self;
-}
-
-void spSkin_dispose(spSkin *self) {
-	_Entry *entry = SUB_CAST(_spSkin, self)->entries;
-
-	while (entry) {
-		_Entry *nextEntry = entry->next;
-		_Entry_dispose(entry);
-		entry = nextEntry;
-	}
-
-	{
-		_SkinHashTableEntry **currentHashtableEntry = SUB_CAST(_spSkin, self)->entriesHashTable;
-		int i;
-
-		for (i = 0; i < SKIN_ENTRIES_HASH_TABLE_SIZE; ++i, ++currentHashtableEntry) {
-			_SkinHashTableEntry *hashtableEntry = *currentHashtableEntry;
-
-			while (hashtableEntry) {
-				_SkinHashTableEntry *nextEntry = hashtableEntry->next;
-				_SkinHashTableEntry_dispose(hashtableEntry);
-				hashtableEntry = nextEntry;
-			}
-		}
-	}
-
-	spBoneDataArray_dispose(self->bones);
-	spIkConstraintDataArray_dispose(self->ikConstraints);
-	spTransformConstraintDataArray_dispose(self->transformConstraints);
-	spPathConstraintDataArray_dispose(self->pathConstraints);
-	FREE(self->name);
-	FREE(self);
-}
-
-void spSkin_setAttachment(spSkin *self, int slotIndex, const char *name, spAttachment *attachment) {
-	_SkinHashTableEntry *existingEntry = 0;
-	_SkinHashTableEntry *hashEntry = SUB_CAST(_spSkin, self)->entriesHashTable[(unsigned int) slotIndex %
-																			   SKIN_ENTRIES_HASH_TABLE_SIZE];
-	while (hashEntry) {
-		if (hashEntry->entry->slotIndex == slotIndex && strcmp(hashEntry->entry->name, name) == 0) {
-			existingEntry = hashEntry;
-			break;
-		}
-		hashEntry = hashEntry->next;
-	}
-
-	if (attachment) attachment->refCount++;
-
-	if (existingEntry) {
-		if (hashEntry->entry->attachment) spAttachment_dispose(hashEntry->entry->attachment);
-		hashEntry->entry->attachment = attachment;
-	} else {
-		_Entry *newEntry = _Entry_create(slotIndex, name, attachment);
-		newEntry->next = SUB_CAST(_spSkin, self)->entries;
-		SUB_CAST(_spSkin, self)->entries = newEntry;
-		{
-			unsigned int hashTableIndex = (unsigned int) slotIndex % SKIN_ENTRIES_HASH_TABLE_SIZE;
-			_SkinHashTableEntry **hashTable = SUB_CAST(_spSkin, self)->entriesHashTable;
-
-			_SkinHashTableEntry *newHashEntry = _SkinHashTableEntry_create(newEntry);
-			newHashEntry->next = hashTable[hashTableIndex];
-			SUB_CAST(_spSkin, self)->entriesHashTable[hashTableIndex] = newHashEntry;
-		}
-	}
-}
-
-spAttachment *spSkin_getAttachment(const spSkin *self, int slotIndex, const char *name) {
-	const _SkinHashTableEntry *hashEntry = SUB_CAST(_spSkin, self)->entriesHashTable[(unsigned int) slotIndex %
-																					 SKIN_ENTRIES_HASH_TABLE_SIZE];
-	while (hashEntry) {
-		if (hashEntry->entry->slotIndex == slotIndex && strcmp(hashEntry->entry->name, name) == 0)
-			return hashEntry->entry->attachment;
-		hashEntry = hashEntry->next;
-	}
-	return 0;
-}
-
-const char *spSkin_getAttachmentName(const spSkin *self, int slotIndex, int attachmentIndex) {
-	const _Entry *entry = SUB_CAST(_spSkin, self)->entries;
-	int i = 0;
-	while (entry) {
-		if (entry->slotIndex == slotIndex) {
-			if (i == attachmentIndex) return entry->name;
-			i++;
-		}
-		entry = entry->next;
-	}
-	return 0;
-}
-
-void spSkin_attachAll(const spSkin *self, spSkeleton *skeleton, const spSkin *oldSkin) {
-	const _Entry *entry = SUB_CAST(_spSkin, oldSkin)->entries;
-	while (entry) {
-		spSlot *slot = skeleton->slots[entry->slotIndex];
-		if (slot->attachment == entry->attachment) {
-			spAttachment *attachment = spSkin_getAttachment(self, entry->slotIndex, entry->name);
-			if (attachment) spSlot_setAttachment(slot, attachment);
-		}
-		entry = entry->next;
-	}
-}
-
-void spSkin_addSkin(spSkin *self, const spSkin *other) {
-	int i = 0;
-	spSkinEntry *entry;
-
-	for (i = 0; i < other->bones->size; i++) {
-		if (!spBoneDataArray_contains(self->bones, other->bones->items[i]))
-			spBoneDataArray_add(self->bones, other->bones->items[i]);
-	}
-
-	for (i = 0; i < other->ikConstraints->size; i++) {
-		if (!spIkConstraintDataArray_contains(self->ikConstraints, other->ikConstraints->items[i]))
-			spIkConstraintDataArray_add(self->ikConstraints, other->ikConstraints->items[i]);
-	}
-
-	for (i = 0; i < other->transformConstraints->size; i++) {
-		if (!spTransformConstraintDataArray_contains(self->transformConstraints, other->transformConstraints->items[i]))
-			spTransformConstraintDataArray_add(self->transformConstraints, other->transformConstraints->items[i]);
-	}
-
-	for (i = 0; i < other->pathConstraints->size; i++) {
-		if (!spPathConstraintDataArray_contains(self->pathConstraints, other->pathConstraints->items[i]))
-			spPathConstraintDataArray_add(self->pathConstraints, other->pathConstraints->items[i]);
-	}
-
-	entry = spSkin_getAttachments(other);
-	while (entry) {
-		spSkin_setAttachment(self, entry->slotIndex, entry->name, entry->attachment);
-		entry = entry->next;
-	}
-}
-
-void spSkin_copySkin(spSkin *self, const spSkin *other) {
-	int i = 0;
-	spSkinEntry *entry;
-
-	for (i = 0; i < other->bones->size; i++) {
-		if (!spBoneDataArray_contains(self->bones, other->bones->items[i]))
-			spBoneDataArray_add(self->bones, other->bones->items[i]);
-	}
-
-	for (i = 0; i < other->ikConstraints->size; i++) {
-		if (!spIkConstraintDataArray_contains(self->ikConstraints, other->ikConstraints->items[i]))
-			spIkConstraintDataArray_add(self->ikConstraints, other->ikConstraints->items[i]);
-	}
-
-	for (i = 0; i < other->transformConstraints->size; i++) {
-		if (!spTransformConstraintDataArray_contains(self->transformConstraints, other->transformConstraints->items[i]))
-			spTransformConstraintDataArray_add(self->transformConstraints, other->transformConstraints->items[i]);
-	}
-
-	for (i = 0; i < other->pathConstraints->size; i++) {
-		if (!spPathConstraintDataArray_contains(self->pathConstraints, other->pathConstraints->items[i]))
-			spPathConstraintDataArray_add(self->pathConstraints, other->pathConstraints->items[i]);
-	}
-
-	entry = spSkin_getAttachments(other);
-	while (entry) {
-		if (entry->attachment->type == SP_ATTACHMENT_MESH) {
-			spMeshAttachment *attachment = spMeshAttachment_newLinkedMesh(
-					SUB_CAST(spMeshAttachment, entry->attachment));
-			spSkin_setAttachment(self, entry->slotIndex, entry->name, SUPER(SUPER(attachment)));
-		} else {
-			spAttachment *attachment = entry->attachment ? spAttachment_copy(entry->attachment) : 0;
-			spSkin_setAttachment(self, entry->slotIndex, entry->name, attachment);
-		}
-		entry = entry->next;
-	}
-}
-
-spSkinEntry *spSkin_getAttachments(const spSkin *self) {
-	return SUB_CAST(_spSkin, self)->entries;
-}
-
-void spSkin_clear(spSkin *self) {
-	_Entry *entry = SUB_CAST(_spSkin, self)->entries;
-
-	while (entry) {
-		_Entry *nextEntry = entry->next;
-		_Entry_dispose(entry);
-		entry = nextEntry;
-	}
-
-	SUB_CAST(_spSkin, self)->entries = 0;
-
-	{
-		_SkinHashTableEntry **currentHashtableEntry = SUB_CAST(_spSkin, self)->entriesHashTable;
-		int i;
-
-		for (i = 0; i < SKIN_ENTRIES_HASH_TABLE_SIZE; ++i, ++currentHashtableEntry) {
-			_SkinHashTableEntry *hashtableEntry = *currentHashtableEntry;
-
-			while (hashtableEntry) {
-				_SkinHashTableEntry *nextEntry = hashtableEntry->next;
-				_SkinHashTableEntry_dispose(hashtableEntry);
-				hashtableEntry = nextEntry;
-			}
-
-			SUB_CAST(_spSkin, self)->entriesHashTable[i] = 0;
-		}
-	}
-
-	spBoneDataArray_clear(self->bones);
-	spIkConstraintDataArray_clear(self->ikConstraints);
-	spTransformConstraintDataArray_clear(self->transformConstraints);
-	spPathConstraintDataArray_clear(self->pathConstraints);
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/Skin.h>
+#include <spine/extension.h>
+#include <stdio.h>
+
+_SP_ARRAY_IMPLEMENT_TYPE(spBoneDataArray, spBoneData *)
+
+_SP_ARRAY_IMPLEMENT_TYPE(spIkConstraintDataArray, spIkConstraintData *)
+
+_SP_ARRAY_IMPLEMENT_TYPE(spTransformConstraintDataArray, spTransformConstraintData *)
+
+_SP_ARRAY_IMPLEMENT_TYPE(spPathConstraintDataArray, spPathConstraintData *)
+
+_Entry *_Entry_create(int slotIndex, const char *name, spAttachment *attachment) {
+	_Entry *self = NEW(_Entry);
+	self->slotIndex = slotIndex;
+	MALLOC_STR(self->name, name);
+	self->attachment = attachment;
+	return self;
+}
+
+void _Entry_dispose(_Entry *self) {
+	spAttachment_dispose(self->attachment);
+	FREE(self->name);
+	FREE(self);
+}
+
+static _SkinHashTableEntry *_SkinHashTableEntry_create(_Entry *entry) {
+	_SkinHashTableEntry *self = NEW(_SkinHashTableEntry);
+	self->entry = entry;
+	return self;
+}
+
+static void _SkinHashTableEntry_dispose(_SkinHashTableEntry *self) {
+	FREE(self);
+}
+
+/**/
+
+spSkin *spSkin_create(const char *name) {
+	spSkin *self = SUPER(NEW(_spSkin));
+	MALLOC_STR(self->name, name);
+	self->bones = spBoneDataArray_create(4);
+	self->ikConstraints = spIkConstraintDataArray_create(4);
+	self->transformConstraints = spTransformConstraintDataArray_create(4);
+	self->pathConstraints = spPathConstraintDataArray_create(4);
+	return self;
+}
+
+void spSkin_dispose(spSkin *self) {
+	_Entry *entry = SUB_CAST(_spSkin, self)->entries;
+
+	while (entry) {
+		_Entry *nextEntry = entry->next;
+		_Entry_dispose(entry);
+		entry = nextEntry;
+	}
+
+	{
+		_SkinHashTableEntry **currentHashtableEntry = SUB_CAST(_spSkin, self)->entriesHashTable;
+		int i;
+
+		for (i = 0; i < SKIN_ENTRIES_HASH_TABLE_SIZE; ++i, ++currentHashtableEntry) {
+			_SkinHashTableEntry *hashtableEntry = *currentHashtableEntry;
+
+			while (hashtableEntry) {
+				_SkinHashTableEntry *nextEntry = hashtableEntry->next;
+				_SkinHashTableEntry_dispose(hashtableEntry);
+				hashtableEntry = nextEntry;
+			}
+		}
+	}
+
+	spBoneDataArray_dispose(self->bones);
+	spIkConstraintDataArray_dispose(self->ikConstraints);
+	spTransformConstraintDataArray_dispose(self->transformConstraints);
+	spPathConstraintDataArray_dispose(self->pathConstraints);
+	FREE(self->name);
+	FREE(self);
+}
+
+void spSkin_setAttachment(spSkin *self, int slotIndex, const char *name, spAttachment *attachment) {
+	_SkinHashTableEntry *existingEntry = 0;
+	_SkinHashTableEntry *hashEntry = SUB_CAST(_spSkin, self)->entriesHashTable[(unsigned int) slotIndex % SKIN_ENTRIES_HASH_TABLE_SIZE];
+	while (hashEntry) {
+		if (hashEntry->entry->slotIndex == slotIndex && strcmp(hashEntry->entry->name, name) == 0) {
+			existingEntry = hashEntry;
+			break;
+		}
+		hashEntry = hashEntry->next;
+	}
+
+	if (attachment) attachment->refCount++;
+
+	if (existingEntry) {
+		if (hashEntry->entry->attachment) spAttachment_dispose(hashEntry->entry->attachment);
+		hashEntry->entry->attachment = attachment;
+	} else {
+		_Entry *newEntry = _Entry_create(slotIndex, name, attachment);
+		newEntry->next = SUB_CAST(_spSkin, self)->entries;
+		SUB_CAST(_spSkin, self)->entries = newEntry;
+		{
+			unsigned int hashTableIndex = (unsigned int) slotIndex % SKIN_ENTRIES_HASH_TABLE_SIZE;
+			_SkinHashTableEntry **hashTable = SUB_CAST(_spSkin, self)->entriesHashTable;
+
+			_SkinHashTableEntry *newHashEntry = _SkinHashTableEntry_create(newEntry);
+			newHashEntry->next = hashTable[hashTableIndex];
+			SUB_CAST(_spSkin, self)->entriesHashTable[hashTableIndex] = newHashEntry;
+		}
+	}
+}
+
+spAttachment *spSkin_getAttachment(const spSkin *self, int slotIndex, const char *name) {
+	const _SkinHashTableEntry *hashEntry = SUB_CAST(_spSkin, self)->entriesHashTable[(unsigned int) slotIndex % SKIN_ENTRIES_HASH_TABLE_SIZE];
+	while (hashEntry) {
+		if (hashEntry->entry->slotIndex == slotIndex && strcmp(hashEntry->entry->name, name) == 0)
+			return hashEntry->entry->attachment;
+		hashEntry = hashEntry->next;
+	}
+	return 0;
+}
+
+const char *spSkin_getAttachmentName(const spSkin *self, int slotIndex, int attachmentIndex) {
+	const _Entry *entry = SUB_CAST(_spSkin, self)->entries;
+	int i = 0;
+	while (entry) {
+		if (entry->slotIndex == slotIndex) {
+			if (i == attachmentIndex) return entry->name;
+			i++;
+		}
+		entry = entry->next;
+	}
+	return 0;
+}
+
+void spSkin_attachAll(const spSkin *self, spSkeleton *skeleton, const spSkin *oldSkin) {
+	const _Entry *entry = SUB_CAST(_spSkin, oldSkin)->entries;
+	while (entry) {
+		spSlot *slot = skeleton->slots[entry->slotIndex];
+		if (slot->attachment == entry->attachment) {
+			spAttachment *attachment = spSkin_getAttachment(self, entry->slotIndex, entry->name);
+			if (attachment) spSlot_setAttachment(slot, attachment);
+		}
+		entry = entry->next;
+	}
+}
+
+void spSkin_addSkin(spSkin *self, const spSkin *other) {
+	int i = 0;
+	spSkinEntry *entry;
+
+	for (i = 0; i < other->bones->size; i++) {
+		if (!spBoneDataArray_contains(self->bones, other->bones->items[i]))
+			spBoneDataArray_add(self->bones, other->bones->items[i]);
+	}
+
+	for (i = 0; i < other->ikConstraints->size; i++) {
+		if (!spIkConstraintDataArray_contains(self->ikConstraints, other->ikConstraints->items[i]))
+			spIkConstraintDataArray_add(self->ikConstraints, other->ikConstraints->items[i]);
+	}
+
+	for (i = 0; i < other->transformConstraints->size; i++) {
+		if (!spTransformConstraintDataArray_contains(self->transformConstraints, other->transformConstraints->items[i]))
+			spTransformConstraintDataArray_add(self->transformConstraints, other->transformConstraints->items[i]);
+	}
+
+	for (i = 0; i < other->pathConstraints->size; i++) {
+		if (!spPathConstraintDataArray_contains(self->pathConstraints, other->pathConstraints->items[i]))
+			spPathConstraintDataArray_add(self->pathConstraints, other->pathConstraints->items[i]);
+	}
+
+	entry = spSkin_getAttachments(other);
+	while (entry) {
+		spSkin_setAttachment(self, entry->slotIndex, entry->name, entry->attachment);
+		entry = entry->next;
+	}
+}
+
+void spSkin_copySkin(spSkin *self, const spSkin *other) {
+	int i = 0;
+	spSkinEntry *entry;
+
+	for (i = 0; i < other->bones->size; i++) {
+		if (!spBoneDataArray_contains(self->bones, other->bones->items[i]))
+			spBoneDataArray_add(self->bones, other->bones->items[i]);
+	}
+
+	for (i = 0; i < other->ikConstraints->size; i++) {
+		if (!spIkConstraintDataArray_contains(self->ikConstraints, other->ikConstraints->items[i]))
+			spIkConstraintDataArray_add(self->ikConstraints, other->ikConstraints->items[i]);
+	}
+
+	for (i = 0; i < other->transformConstraints->size; i++) {
+		if (!spTransformConstraintDataArray_contains(self->transformConstraints, other->transformConstraints->items[i]))
+			spTransformConstraintDataArray_add(self->transformConstraints, other->transformConstraints->items[i]);
+	}
+
+	for (i = 0; i < other->pathConstraints->size; i++) {
+		if (!spPathConstraintDataArray_contains(self->pathConstraints, other->pathConstraints->items[i]))
+			spPathConstraintDataArray_add(self->pathConstraints, other->pathConstraints->items[i]);
+	}
+
+	entry = spSkin_getAttachments(other);
+	while (entry) {
+		if (entry->attachment->type == SP_ATTACHMENT_MESH) {
+			spMeshAttachment *attachment = spMeshAttachment_newLinkedMesh(
+					SUB_CAST(spMeshAttachment, entry->attachment));
+			spSkin_setAttachment(self, entry->slotIndex, entry->name, SUPER(SUPER(attachment)));
+		} else {
+			spAttachment *attachment = entry->attachment ? spAttachment_copy(entry->attachment) : 0;
+			spSkin_setAttachment(self, entry->slotIndex, entry->name, attachment);
+		}
+		entry = entry->next;
+	}
+}
+
+spSkinEntry *spSkin_getAttachments(const spSkin *self) {
+	return SUB_CAST(_spSkin, self)->entries;
+}
+
+void spSkin_clear(spSkin *self) {
+	_Entry *entry = SUB_CAST(_spSkin, self)->entries;
+
+	while (entry) {
+		_Entry *nextEntry = entry->next;
+		_Entry_dispose(entry);
+		entry = nextEntry;
+	}
+
+	SUB_CAST(_spSkin, self)->entries = 0;
+
+	{
+		_SkinHashTableEntry **currentHashtableEntry = SUB_CAST(_spSkin, self)->entriesHashTable;
+		int i;
+
+		for (i = 0; i < SKIN_ENTRIES_HASH_TABLE_SIZE; ++i, ++currentHashtableEntry) {
+			_SkinHashTableEntry *hashtableEntry = *currentHashtableEntry;
+
+			while (hashtableEntry) {
+				_SkinHashTableEntry *nextEntry = hashtableEntry->next;
+				_SkinHashTableEntry_dispose(hashtableEntry);
+				hashtableEntry = nextEntry;
+			}
+
+			SUB_CAST(_spSkin, self)->entriesHashTable[i] = 0;
+		}
+	}
+
+	spBoneDataArray_clear(self->bones);
+	spIkConstraintDataArray_clear(self->ikConstraints);
+	spTransformConstraintDataArray_clear(self->transformConstraints);
+	spPathConstraintDataArray_clear(self->pathConstraints);
+}

+ 99 - 101
spine-c/spine-c/src/spine/Slot.c

@@ -1,101 +1,99 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/Slot.h>
-#include <spine/extension.h>
-
-typedef struct {
-	spSlot super;
-	float attachmentTime;
-} _spSlot;
-
-spSlot *spSlot_create(spSlotData *data, spBone *bone) {
-	spSlot *self = SUPER(NEW(_spSlot));
-	CONST_CAST(spSlotData*, self->data) = data;
-	CONST_CAST(spBone*, self->bone) = bone;
-	spColor_setFromFloats(&self->color, 1, 1, 1, 1);
-	self->darkColor = data->darkColor == 0 ? 0 : spColor_create();
-	spSlot_setToSetupPose(self);
-	return self;
-}
-
-void spSlot_dispose(spSlot *self) {
-	FREE(self->deform);
-	FREE(self->darkColor);
-	FREE(self);
-}
-
-static int isVertexAttachment(spAttachment *attachment) {
-	if (attachment == NULL) return 0;
-	switch (attachment->type) {
-		case SP_ATTACHMENT_BOUNDING_BOX:
-		case SP_ATTACHMENT_CLIPPING:
-		case SP_ATTACHMENT_MESH:
-		case SP_ATTACHMENT_PATH:
-			return -1;
-		default:
-			return 0;
-	}
-}
-
-void spSlot_setAttachment(spSlot *self, spAttachment *attachment) {
-	if (attachment == self->attachment) return;
-
-	if (!isVertexAttachment(attachment) ||
-		!isVertexAttachment(self->attachment)
-		|| (SUB_CAST(spVertexAttachment, attachment)->deformAttachment !=
-			SUB_CAST(spVertexAttachment, self->attachment)->deformAttachment)) {
-		self->deformCount = 0;
-	}
-
-	CONST_CAST(spAttachment*, self->attachment) = attachment;
-	SUB_CAST(_spSlot, self)->attachmentTime = self->bone->skeleton->time;
-}
-
-void spSlot_setAttachmentTime(spSlot *self, float time) {
-	SUB_CAST(_spSlot, self)->attachmentTime = self->bone->skeleton->time - time;
-}
-
-float spSlot_getAttachmentTime(const spSlot *self) {
-	return self->bone->skeleton->time - SUB_CAST(_spSlot, self)->attachmentTime;
-}
-
-void spSlot_setToSetupPose(spSlot *self) {
-	spColor_setFromColor(&self->color, &self->data->color);
-	if (self->darkColor) spColor_setFromColor(self->darkColor, self->data->darkColor);
-
-	if (!self->data->attachmentName)
-		spSlot_setAttachment(self, 0);
-	else {
-		spAttachment *attachment = spSkeleton_getAttachmentForSlotIndex(
-				self->bone->skeleton, self->data->index, self->data->attachmentName);
-		CONST_CAST(spAttachment*, self->attachment) = 0;
-		spSlot_setAttachment(self, attachment);
-	}
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/Slot.h>
+#include <spine/extension.h>
+
+typedef struct {
+	spSlot super;
+	float attachmentTime;
+} _spSlot;
+
+spSlot *spSlot_create(spSlotData *data, spBone *bone) {
+	spSlot *self = SUPER(NEW(_spSlot));
+	CONST_CAST(spSlotData *, self->data) = data;
+	CONST_CAST(spBone *, self->bone) = bone;
+	spColor_setFromFloats(&self->color, 1, 1, 1, 1);
+	self->darkColor = data->darkColor == 0 ? 0 : spColor_create();
+	spSlot_setToSetupPose(self);
+	return self;
+}
+
+void spSlot_dispose(spSlot *self) {
+	FREE(self->deform);
+	FREE(self->darkColor);
+	FREE(self);
+}
+
+static int isVertexAttachment(spAttachment *attachment) {
+	if (attachment == NULL) return 0;
+	switch (attachment->type) {
+		case SP_ATTACHMENT_BOUNDING_BOX:
+		case SP_ATTACHMENT_CLIPPING:
+		case SP_ATTACHMENT_MESH:
+		case SP_ATTACHMENT_PATH:
+			return -1;
+		default:
+			return 0;
+	}
+}
+
+void spSlot_setAttachment(spSlot *self, spAttachment *attachment) {
+	if (attachment == self->attachment) return;
+
+	if (!isVertexAttachment(attachment) ||
+		!isVertexAttachment(self->attachment) || (SUB_CAST(spVertexAttachment, attachment)->deformAttachment != SUB_CAST(spVertexAttachment, self->attachment)->deformAttachment)) {
+		self->deformCount = 0;
+	}
+
+	CONST_CAST(spAttachment *, self->attachment) = attachment;
+	SUB_CAST(_spSlot, self)->attachmentTime = self->bone->skeleton->time;
+}
+
+void spSlot_setAttachmentTime(spSlot *self, float time) {
+	SUB_CAST(_spSlot, self)->attachmentTime = self->bone->skeleton->time - time;
+}
+
+float spSlot_getAttachmentTime(const spSlot *self) {
+	return self->bone->skeleton->time - SUB_CAST(_spSlot, self)->attachmentTime;
+}
+
+void spSlot_setToSetupPose(spSlot *self) {
+	spColor_setFromColor(&self->color, &self->data->color);
+	if (self->darkColor) spColor_setFromColor(self->darkColor, self->data->darkColor);
+
+	if (!self->data->attachmentName)
+		spSlot_setAttachment(self, 0);
+	else {
+		spAttachment *attachment = spSkeleton_getAttachmentForSlotIndex(
+				self->bone->skeleton, self->data->index, self->data->attachmentName);
+		CONST_CAST(spAttachment *, self->attachment) = 0;
+		spSlot_setAttachment(self, attachment);
+	}
+}

+ 55 - 55
spine-c/spine-c/src/spine/SlotData.c

@@ -1,55 +1,55 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/SlotData.h>
-#include <spine/extension.h>
-
-spSlotData *spSlotData_create(const int index, const char *name, spBoneData *boneData) {
-	spSlotData *self = NEW(spSlotData);
-	CONST_CAST(int, self->index) = index;
-	MALLOC_STR(self->name, name);
-	CONST_CAST(spBoneData*, self->boneData) = boneData;
-	spColor_setFromFloats(&self->color, 1, 1, 1, 1);
-	return self;
-}
-
-void spSlotData_dispose(spSlotData *self) {
-	FREE(self->name);
-	FREE(self->attachmentName);
-	FREE(self->darkColor);
-	FREE(self);
-}
-
-void spSlotData_setAttachmentName(spSlotData *self, const char *attachmentName) {
-	FREE(self->attachmentName);
-	if (attachmentName)
-		MALLOC_STR(self->attachmentName, attachmentName);
-	else
-		CONST_CAST(char*, self->attachmentName) = 0;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/SlotData.h>
+#include <spine/extension.h>
+
+spSlotData *spSlotData_create(const int index, const char *name, spBoneData *boneData) {
+	spSlotData *self = NEW(spSlotData);
+	CONST_CAST(int, self->index) = index;
+	MALLOC_STR(self->name, name);
+	CONST_CAST(spBoneData *, self->boneData) = boneData;
+	spColor_setFromFloats(&self->color, 1, 1, 1, 1);
+	return self;
+}
+
+void spSlotData_dispose(spSlotData *self) {
+	FREE(self->name);
+	FREE(self->attachmentName);
+	FREE(self->darkColor);
+	FREE(self);
+}
+
+void spSlotData_setAttachmentName(spSlotData *self, const char *attachmentName) {
+	FREE(self->attachmentName);
+	if (attachmentName)
+		MALLOC_STR(self->attachmentName, attachmentName);
+	else
+		CONST_CAST(char *, self->attachmentName) = 0;
+}

+ 259 - 255
spine-c/spine-c/src/spine/TransformConstraint.c

@@ -1,255 +1,259 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/TransformConstraint.h>
-#include <spine/Skeleton.h>
-#include <spine/extension.h>
-
-spTransformConstraint *spTransformConstraint_create(spTransformConstraintData *data, const spSkeleton *skeleton) {
-	int i;
-	spTransformConstraint *self = NEW(spTransformConstraint);
-	CONST_CAST(spTransformConstraintData*, self->data) = data;
-	self->mixRotate = data->mixRotate;
-	self->mixX = data->mixX;
-	self->mixY = data->mixY;
-	self->mixScaleX = data->mixScaleX;
-	self->mixScaleY = data->mixScaleY;
-	self->mixShearY = data->mixShearY;
-	self->bonesCount = data->bonesCount;
-	CONST_CAST(spBone**, self->bones) = MALLOC(spBone*, self->bonesCount);
-	for (i = 0; i < self->bonesCount; ++i)
-		self->bones[i] = spSkeleton_findBone(skeleton, self->data->bones[i]->name);
-	self->target = spSkeleton_findBone(skeleton, self->data->target->name);
-	return self;
-}
-
-void spTransformConstraint_dispose(spTransformConstraint *self) {
-	FREE(self->bones);
-	FREE(self);
-}
-
-void _spTransformConstraint_applyAbsoluteWorld(spTransformConstraint *self) {
-	float mixRotate = self->mixRotate, mixX = self->mixX, mixY = self->mixY, mixScaleX = self->mixScaleX,
-			mixScaleY = self->mixScaleY, mixShearY = self->mixShearY;
-	int /*bool*/ translate = mixX != 0 || mixY != 0;
-	spBone *target = self->target;
-	float ta = target->a, tb = target->b, tc = target->c, td = target->d;
-	float degRadReflect = ta * td - tb * tc > 0 ? DEG_RAD : -DEG_RAD;
-	float offsetRotation = self->data->offsetRotation * degRadReflect, offsetShearY =
-			self->data->offsetShearY * degRadReflect;
-	int i;
-	float a, b, c, d, r, cosine, sine, x, y, s, by;
-	for (i = 0; i < self->bonesCount; ++i) {
-		spBone *bone = self->bones[i];
-
-		if (mixRotate != 0) {
-			a = bone->a, b = bone->b, c = bone->c, d = bone->d;
-			r = ATAN2(tc, ta) - ATAN2(c, a) + offsetRotation;
-			if (r > PI) r -= PI2;
-			else if (r < -PI) r += PI2;
-			r *= mixRotate;
-			cosine = COS(r);
-			sine = SIN(r);
-			CONST_CAST(float, bone->a) = cosine * a - sine * c;
-			CONST_CAST(float, bone->b) = cosine * b - sine * d;
-			CONST_CAST(float, bone->c) = sine * a + cosine * c;
-			CONST_CAST(float, bone->d) = sine * b + cosine * d;
-		}
-
-		if (translate) {
-			spBone_localToWorld(target, self->data->offsetX, self->data->offsetY, &x, &y);
-			CONST_CAST(float, bone->worldX) += (x - bone->worldX) * mixX;
-			CONST_CAST(float, bone->worldY) += (y - bone->worldY) * mixY;
-		}
-
-		if (mixScaleX > 0) {
-			s = SQRT(bone->a * bone->a + bone->c * bone->c);
-			if (s != 0) s = (s + (SQRT(ta * ta + tc * tc) - s + self->data->offsetScaleX) * mixScaleX) / s;
-			CONST_CAST(float, bone->a) *= s;
-			CONST_CAST(float, bone->c) *= s;
-		}
-		if (mixScaleY != 0) {
-			s = SQRT(bone->b * bone->b + bone->d * bone->d);
-			if (s != 0) s = (s + (SQRT(tb * tb + td * td) - s + self->data->offsetScaleY) * mixScaleY) / s;
-			CONST_CAST(float, bone->b) *= s;
-			CONST_CAST(float, bone->d) *= s;
-		}
-
-		if (mixShearY > 0) {
-			b = bone->b, d = bone->d;
-			by = ATAN2(d, b);
-			r = ATAN2(td, tb) - ATAN2(tc, ta) - (by - ATAN2(bone->c, bone->a));
-			s = SQRT(b * b + d * d);
-			if (r > PI) r -= PI2;
-			else if (r < -PI) r += PI2;
-			r = by + (r + offsetShearY) * mixShearY;
-			CONST_CAST(float, bone->b) = COS(r) * s;
-			CONST_CAST(float, bone->d) = SIN(r) * s;
-		}
-		spBone_updateAppliedTransform(bone);
-	}
-}
-
-void _spTransformConstraint_applyRelativeWorld(spTransformConstraint *self) {
-	float mixRotate = self->mixRotate, mixX = self->mixX, mixY = self->mixY, mixScaleX = self->mixScaleX,
-			mixScaleY = self->mixScaleY, mixShearY = self->mixShearY;
-	int /*bool*/ translate = mixX != 0 || mixY != 0;
-	spBone *target = self->target;
-	float ta = target->a, tb = target->b, tc = target->c, td = target->d;
-	float degRadReflect = ta * td - tb * tc > 0 ? DEG_RAD : -DEG_RAD;
-	float offsetRotation = self->data->offsetRotation * degRadReflect, offsetShearY =
-			self->data->offsetShearY * degRadReflect;
-	int i;
-	float a, b, c, d, r, cosine, sine, x, y, s;
-	for (i = 0; i < self->bonesCount; ++i) {
-		spBone *bone = self->bones[i];
-
-		if (mixRotate != 0) {
-			a = bone->a, b = bone->b, c = bone->c, d = bone->d;
-			r = ATAN2(tc, ta) + offsetRotation;
-			if (r > PI) r -= PI2;
-			else if (r < -PI) r += PI2;
-			r *= mixRotate;
-			cosine = COS(r);
-			sine = SIN(r);
-			CONST_CAST(float, bone->a) = cosine * a - sine * c;
-			CONST_CAST(float, bone->b) = cosine * b - sine * d;
-			CONST_CAST(float, bone->c) = sine * a + cosine * c;
-			CONST_CAST(float, bone->d) = sine * b + cosine * d;
-		}
-
-		if (translate != 0) {
-			spBone_localToWorld(target, self->data->offsetX, self->data->offsetY, &x, &y);
-			CONST_CAST(float, bone->worldX) += (x * mixX);
-			CONST_CAST(float, bone->worldY) += (y * mixY);
-		}
-
-		if (mixScaleX != 0) {
-			s = (SQRT(ta * ta + tc * tc) - 1 + self->data->offsetScaleX) * mixScaleX + 1;
-			CONST_CAST(float, bone->a) *= s;
-			CONST_CAST(float, bone->c) *= s;
-		}
-		if (mixScaleY > 0) {
-			s = (SQRT(tb * tb + td * td) - 1 + self->data->offsetScaleY) * mixScaleY + 1;
-			CONST_CAST(float, bone->b) *= s;
-			CONST_CAST(float, bone->d) *= s;
-		}
-
-		if (mixShearY > 0) {
-			r = ATAN2(td, tb) - ATAN2(tc, ta);
-			if (r > PI) r -= PI2;
-			else if (r < -PI) r += PI2;
-			b = bone->b, d = bone->d;
-			r = ATAN2(d, b) + (r - PI / 2 + offsetShearY) * mixShearY;
-			s = SQRT(b * b + d * d);
-			CONST_CAST(float, bone->b) = COS(r) * s;
-			CONST_CAST(float, bone->d) = SIN(r) * s;
-		}
-
-		spBone_updateAppliedTransform(bone);
-	}
-}
-
-void _spTransformConstraint_applyAbsoluteLocal(spTransformConstraint *self) {
-	float mixRotate = self->mixRotate, mixX = self->mixX, mixY = self->mixY, mixScaleX = self->mixScaleX,
-			mixScaleY = self->mixScaleY, mixShearY = self->mixShearY;
-	spBone *target = self->target;
-	int i;
-	float rotation, r, x, y, scaleX, scaleY, shearY;
-
-	for (i = 0; i < self->bonesCount; ++i) {
-		spBone *bone = self->bones[i];
-
-		rotation = bone->arotation;
-		if (mixRotate != 0) {
-			r = target->arotation - rotation + self->data->offsetRotation;
-			r -= (16384 - (int) (16384.499999999996 - r / 360)) * 360;
-			rotation += r * mixRotate;
-		}
-
-		x = bone->ax, y = bone->ay;
-		x += (target->ax - x + self->data->offsetX) * mixX;
-		y += (target->ay - y + self->data->offsetY) * mixY;
-
-		scaleX = bone->ascaleX, scaleY = bone->ascaleY;
-		if (mixScaleX != 0 && scaleX != 0)
-			scaleX = (scaleX + (target->ascaleX - scaleX + self->data->offsetScaleX) * mixScaleX) / scaleX;
-		if (mixScaleY != 0 && scaleY != 0)
-			scaleY = (scaleY + (target->ascaleY - scaleY + self->data->offsetScaleY) * mixScaleY) / scaleY;
-
-		shearY = bone->ashearY;
-		if (mixShearY != 0) {
-			r = target->ashearY - shearY + self->data->offsetShearY;
-			r -= (16384 - (int) (16384.499999999996 - r / 360)) * 360;
-			shearY += r * mixShearY;
-		}
-
-		spBone_updateWorldTransformWith(bone, x, y, rotation, scaleX, scaleY, bone->ashearX, shearY);
-	}
-}
-
-void _spTransformConstraint_applyRelativeLocal(spTransformConstraint *self) {
-	float mixRotate = self->mixRotate, mixX = self->mixX, mixY = self->mixY, mixScaleX = self->mixScaleX,
-			mixScaleY = self->mixScaleY, mixShearY = self->mixShearY;
-	spBone *target = self->target;
-	int i;
-	float rotation, x, y, scaleX, scaleY, shearY;
-
-	for (i = 0; i < self->bonesCount; ++i) {
-		spBone *bone = self->bones[i];
-
-		rotation = bone->arotation + (target->arotation + self->data->offsetRotation) * mixRotate;
-		x = bone->ax + (target->ax + self->data->offsetX) * mixX;
-		y = bone->ay + (target->ay + self->data->offsetY) * mixY;
-		scaleX = (bone->ascaleX * ((target->ascaleX - 1 + self->data->offsetScaleX) * mixScaleX) + 1);
-		scaleY = (bone->ascaleY * ((target->ascaleY - 1 + self->data->offsetScaleY) * mixScaleY) + 1);
-		shearY = bone->ashearY + (target->ashearY + self->data->offsetShearY) * mixShearY;
-
-		spBone_updateWorldTransformWith(bone, x, y, rotation, scaleX, scaleY, bone->ashearX, shearY);
-	}
-}
-
-void spTransformConstraint_update(spTransformConstraint *self) {
-	if (self->mixRotate == 0 && self->mixX == 0 && self->mixY == 0 && self->mixScaleX == 0 && self->mixScaleX == 0 &&
-		self->mixShearY == 0)
-		return;
-
-	if (self->data->local) {
-		if (self->data->relative)
-			_spTransformConstraint_applyRelativeLocal(self);
-		else
-			_spTransformConstraint_applyAbsoluteLocal(self);
-
-	} else {
-		if (self->data->relative)
-			_spTransformConstraint_applyRelativeWorld(self);
-		else
-			_spTransformConstraint_applyAbsoluteWorld(self);
-	}
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/Skeleton.h>
+#include <spine/TransformConstraint.h>
+#include <spine/extension.h>
+
+spTransformConstraint *spTransformConstraint_create(spTransformConstraintData *data, const spSkeleton *skeleton) {
+	int i;
+	spTransformConstraint *self = NEW(spTransformConstraint);
+	CONST_CAST(spTransformConstraintData *, self->data) = data;
+	self->mixRotate = data->mixRotate;
+	self->mixX = data->mixX;
+	self->mixY = data->mixY;
+	self->mixScaleX = data->mixScaleX;
+	self->mixScaleY = data->mixScaleY;
+	self->mixShearY = data->mixShearY;
+	self->bonesCount = data->bonesCount;
+	CONST_CAST(spBone **, self->bones) = MALLOC(spBone *, self->bonesCount);
+	for (i = 0; i < self->bonesCount; ++i)
+		self->bones[i] = spSkeleton_findBone(skeleton, self->data->bones[i]->name);
+	self->target = spSkeleton_findBone(skeleton, self->data->target->name);
+	return self;
+}
+
+void spTransformConstraint_dispose(spTransformConstraint *self) {
+	FREE(self->bones);
+	FREE(self);
+}
+
+void _spTransformConstraint_applyAbsoluteWorld(spTransformConstraint *self) {
+	float mixRotate = self->mixRotate, mixX = self->mixX, mixY = self->mixY, mixScaleX = self->mixScaleX,
+		  mixScaleY = self->mixScaleY, mixShearY = self->mixShearY;
+	int /*bool*/ translate = mixX != 0 || mixY != 0;
+	spBone *target = self->target;
+	float ta = target->a, tb = target->b, tc = target->c, td = target->d;
+	float degRadReflect = ta * td - tb * tc > 0 ? DEG_RAD : -DEG_RAD;
+	float offsetRotation = self->data->offsetRotation * degRadReflect, offsetShearY =
+																			   self->data->offsetShearY * degRadReflect;
+	int i;
+	float a, b, c, d, r, cosine, sine, x, y, s, by;
+	for (i = 0; i < self->bonesCount; ++i) {
+		spBone *bone = self->bones[i];
+
+		if (mixRotate != 0) {
+			a = bone->a, b = bone->b, c = bone->c, d = bone->d;
+			r = ATAN2(tc, ta) - ATAN2(c, a) + offsetRotation;
+			if (r > PI) r -= PI2;
+			else if (r < -PI)
+				r += PI2;
+			r *= mixRotate;
+			cosine = COS(r);
+			sine = SIN(r);
+			CONST_CAST(float, bone->a) = cosine * a - sine * c;
+			CONST_CAST(float, bone->b) = cosine * b - sine * d;
+			CONST_CAST(float, bone->c) = sine * a + cosine * c;
+			CONST_CAST(float, bone->d) = sine * b + cosine * d;
+		}
+
+		if (translate) {
+			spBone_localToWorld(target, self->data->offsetX, self->data->offsetY, &x, &y);
+			CONST_CAST(float, bone->worldX) += (x - bone->worldX) * mixX;
+			CONST_CAST(float, bone->worldY) += (y - bone->worldY) * mixY;
+		}
+
+		if (mixScaleX > 0) {
+			s = SQRT(bone->a * bone->a + bone->c * bone->c);
+			if (s != 0) s = (s + (SQRT(ta * ta + tc * tc) - s + self->data->offsetScaleX) * mixScaleX) / s;
+			CONST_CAST(float, bone->a) *= s;
+			CONST_CAST(float, bone->c) *= s;
+		}
+		if (mixScaleY != 0) {
+			s = SQRT(bone->b * bone->b + bone->d * bone->d);
+			if (s != 0) s = (s + (SQRT(tb * tb + td * td) - s + self->data->offsetScaleY) * mixScaleY) / s;
+			CONST_CAST(float, bone->b) *= s;
+			CONST_CAST(float, bone->d) *= s;
+		}
+
+		if (mixShearY > 0) {
+			b = bone->b, d = bone->d;
+			by = ATAN2(d, b);
+			r = ATAN2(td, tb) - ATAN2(tc, ta) - (by - ATAN2(bone->c, bone->a));
+			s = SQRT(b * b + d * d);
+			if (r > PI) r -= PI2;
+			else if (r < -PI)
+				r += PI2;
+			r = by + (r + offsetShearY) * mixShearY;
+			CONST_CAST(float, bone->b) = COS(r) * s;
+			CONST_CAST(float, bone->d) = SIN(r) * s;
+		}
+		spBone_updateAppliedTransform(bone);
+	}
+}
+
+void _spTransformConstraint_applyRelativeWorld(spTransformConstraint *self) {
+	float mixRotate = self->mixRotate, mixX = self->mixX, mixY = self->mixY, mixScaleX = self->mixScaleX,
+		  mixScaleY = self->mixScaleY, mixShearY = self->mixShearY;
+	int /*bool*/ translate = mixX != 0 || mixY != 0;
+	spBone *target = self->target;
+	float ta = target->a, tb = target->b, tc = target->c, td = target->d;
+	float degRadReflect = ta * td - tb * tc > 0 ? DEG_RAD : -DEG_RAD;
+	float offsetRotation = self->data->offsetRotation * degRadReflect, offsetShearY =
+																			   self->data->offsetShearY * degRadReflect;
+	int i;
+	float a, b, c, d, r, cosine, sine, x, y, s;
+	for (i = 0; i < self->bonesCount; ++i) {
+		spBone *bone = self->bones[i];
+
+		if (mixRotate != 0) {
+			a = bone->a, b = bone->b, c = bone->c, d = bone->d;
+			r = ATAN2(tc, ta) + offsetRotation;
+			if (r > PI) r -= PI2;
+			else if (r < -PI)
+				r += PI2;
+			r *= mixRotate;
+			cosine = COS(r);
+			sine = SIN(r);
+			CONST_CAST(float, bone->a) = cosine * a - sine * c;
+			CONST_CAST(float, bone->b) = cosine * b - sine * d;
+			CONST_CAST(float, bone->c) = sine * a + cosine * c;
+			CONST_CAST(float, bone->d) = sine * b + cosine * d;
+		}
+
+		if (translate != 0) {
+			spBone_localToWorld(target, self->data->offsetX, self->data->offsetY, &x, &y);
+			CONST_CAST(float, bone->worldX) += (x * mixX);
+			CONST_CAST(float, bone->worldY) += (y * mixY);
+		}
+
+		if (mixScaleX != 0) {
+			s = (SQRT(ta * ta + tc * tc) - 1 + self->data->offsetScaleX) * mixScaleX + 1;
+			CONST_CAST(float, bone->a) *= s;
+			CONST_CAST(float, bone->c) *= s;
+		}
+		if (mixScaleY > 0) {
+			s = (SQRT(tb * tb + td * td) - 1 + self->data->offsetScaleY) * mixScaleY + 1;
+			CONST_CAST(float, bone->b) *= s;
+			CONST_CAST(float, bone->d) *= s;
+		}
+
+		if (mixShearY > 0) {
+			r = ATAN2(td, tb) - ATAN2(tc, ta);
+			if (r > PI) r -= PI2;
+			else if (r < -PI)
+				r += PI2;
+			b = bone->b, d = bone->d;
+			r = ATAN2(d, b) + (r - PI / 2 + offsetShearY) * mixShearY;
+			s = SQRT(b * b + d * d);
+			CONST_CAST(float, bone->b) = COS(r) * s;
+			CONST_CAST(float, bone->d) = SIN(r) * s;
+		}
+
+		spBone_updateAppliedTransform(bone);
+	}
+}
+
+void _spTransformConstraint_applyAbsoluteLocal(spTransformConstraint *self) {
+	float mixRotate = self->mixRotate, mixX = self->mixX, mixY = self->mixY, mixScaleX = self->mixScaleX,
+		  mixScaleY = self->mixScaleY, mixShearY = self->mixShearY;
+	spBone *target = self->target;
+	int i;
+	float rotation, r, x, y, scaleX, scaleY, shearY;
+
+	for (i = 0; i < self->bonesCount; ++i) {
+		spBone *bone = self->bones[i];
+
+		rotation = bone->arotation;
+		if (mixRotate != 0) {
+			r = target->arotation - rotation + self->data->offsetRotation;
+			r -= (16384 - (int) (16384.499999999996 - r / 360)) * 360;
+			rotation += r * mixRotate;
+		}
+
+		x = bone->ax, y = bone->ay;
+		x += (target->ax - x + self->data->offsetX) * mixX;
+		y += (target->ay - y + self->data->offsetY) * mixY;
+
+		scaleX = bone->ascaleX, scaleY = bone->ascaleY;
+		if (mixScaleX != 0 && scaleX != 0)
+			scaleX = (scaleX + (target->ascaleX - scaleX + self->data->offsetScaleX) * mixScaleX) / scaleX;
+		if (mixScaleY != 0 && scaleY != 0)
+			scaleY = (scaleY + (target->ascaleY - scaleY + self->data->offsetScaleY) * mixScaleY) / scaleY;
+
+		shearY = bone->ashearY;
+		if (mixShearY != 0) {
+			r = target->ashearY - shearY + self->data->offsetShearY;
+			r -= (16384 - (int) (16384.499999999996 - r / 360)) * 360;
+			shearY += r * mixShearY;
+		}
+
+		spBone_updateWorldTransformWith(bone, x, y, rotation, scaleX, scaleY, bone->ashearX, shearY);
+	}
+}
+
+void _spTransformConstraint_applyRelativeLocal(spTransformConstraint *self) {
+	float mixRotate = self->mixRotate, mixX = self->mixX, mixY = self->mixY, mixScaleX = self->mixScaleX,
+		  mixScaleY = self->mixScaleY, mixShearY = self->mixShearY;
+	spBone *target = self->target;
+	int i;
+	float rotation, x, y, scaleX, scaleY, shearY;
+
+	for (i = 0; i < self->bonesCount; ++i) {
+		spBone *bone = self->bones[i];
+
+		rotation = bone->arotation + (target->arotation + self->data->offsetRotation) * mixRotate;
+		x = bone->ax + (target->ax + self->data->offsetX) * mixX;
+		y = bone->ay + (target->ay + self->data->offsetY) * mixY;
+		scaleX = (bone->ascaleX * ((target->ascaleX - 1 + self->data->offsetScaleX) * mixScaleX) + 1);
+		scaleY = (bone->ascaleY * ((target->ascaleY - 1 + self->data->offsetScaleY) * mixScaleY) + 1);
+		shearY = bone->ashearY + (target->ashearY + self->data->offsetShearY) * mixShearY;
+
+		spBone_updateWorldTransformWith(bone, x, y, rotation, scaleX, scaleY, bone->ashearX, shearY);
+	}
+}
+
+void spTransformConstraint_update(spTransformConstraint *self) {
+	if (self->mixRotate == 0 && self->mixX == 0 && self->mixY == 0 && self->mixScaleX == 0 && self->mixScaleX == 0 &&
+		self->mixShearY == 0)
+		return;
+
+	if (self->data->local) {
+		if (self->data->relative)
+			_spTransformConstraint_applyRelativeLocal(self);
+		else
+			_spTransformConstraint_applyAbsoluteLocal(self);
+
+	} else {
+		if (self->data->relative)
+			_spTransformConstraint_applyRelativeWorld(self);
+		else
+			_spTransformConstraint_applyAbsoluteWorld(self);
+	}
+}

+ 43 - 43
spine-c/spine-c/src/spine/TransformConstraintData.c

@@ -1,43 +1,43 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/TransformConstraintData.h>
-#include <spine/extension.h>
-
-spTransformConstraintData *spTransformConstraintData_create(const char *name) {
-	spTransformConstraintData *self = NEW(spTransformConstraintData);
-	MALLOC_STR(self->name, name);
-	return self;
-}
-
-void spTransformConstraintData_dispose(spTransformConstraintData *self) {
-	FREE(self->name);
-	FREE(self->bones);
-	FREE(self);
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/TransformConstraintData.h>
+#include <spine/extension.h>
+
+spTransformConstraintData *spTransformConstraintData_create(const char *name) {
+	spTransformConstraintData *self = NEW(spTransformConstraintData);
+	MALLOC_STR(self->name, name);
+	return self;
+}
+
+void spTransformConstraintData_dispose(spTransformConstraintData *self) {
+	FREE(self->name);
+	FREE(self->bones);
+	FREE(self);
+}

+ 5 - 3
spine-c/spine-c/src/spine/Triangulator.c

@@ -77,7 +77,8 @@ void spTriangulator_dispose(spTriangulator *self) {
 
 static spFloatArray *_obtainPolygon(spTriangulator *self) {
 	if (self->polygonPool->size == 0) return spFloatArray_create(16);
-	else return spArrayFloatArray_pop(self->polygonPool);
+	else
+		return spArrayFloatArray_pop(self->polygonPool);
 }
 
 static void _freePolygon(spTriangulator *self, spFloatArray *polygon) {
@@ -93,7 +94,8 @@ static void _freeAllPolygons(spTriangulator *self, spArrayFloatArray *polygons)
 
 static spShortArray *_obtainPolygonIndices(spTriangulator *self) {
 	if (self->polygonIndicesPool->size == 0) return spShortArray_create(16);
-	else return spArrayShortArray_pop(self->polygonIndicesPool);
+	else
+		return spArrayShortArray_pop(self->polygonIndicesPool);
 }
 
 static void _freePolygonIndices(spTriangulator *self, spShortArray *indices) {
@@ -175,7 +177,7 @@ spShortArray *spTriangulator_triangulate(spTriangulator *self, spFloatArray *ver
 				}
 				break;
 			}
-			break_outer:
+		break_outer:
 
 			if (next == 0) {
 				do {

+ 142 - 143
spine-c/spine-c/src/spine/VertexAttachment.c

@@ -1,143 +1,142 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/VertexAttachment.h>
-#include <spine/extension.h>
-
-/* FIXME this is not thread-safe */
-static int nextID = 0;
-
-void _spVertexAttachment_init(spVertexAttachment *attachment) {
-	attachment->id = nextID++;
-	attachment->deformAttachment = attachment;
-}
-
-void _spVertexAttachment_deinit(spVertexAttachment *attachment) {
-	_spAttachment_deinit(SUPER(attachment));
-	FREE(attachment->bones);
-	FREE(attachment->vertices);
-}
-
-void spVertexAttachment_computeWorldVertices(spVertexAttachment *self, spSlot *slot, int start, int count,
-											 float *worldVertices, int offset, int stride) {
-	spSkeleton *skeleton;
-	int deformLength;
-	float *deformArray;
-	float *vertices;
-	int *bones;
-
-	count = offset + (count >> 1) * stride;
-	skeleton = slot->bone->skeleton;
-	deformLength = slot->deformCount;
-	deformArray = slot->deform;
-	vertices = self->vertices;
-	bones = self->bones;
-	if (!bones) {
-		spBone *bone;
-		int v, w;
-		float x, y;
-		if (deformLength > 0) vertices = deformArray;
-		bone = slot->bone;
-		x = bone->worldX;
-		y = bone->worldY;
-		for (v = start, w = offset; w < count; v += 2, w += stride) {
-			float vx = vertices[v], vy = vertices[v + 1];
-			worldVertices[w] = vx * bone->a + vy * bone->b + x;
-			worldVertices[w + 1] = vx * bone->c + vy * bone->d + y;
-		}
-	} else {
-		int v = 0, skip = 0, i;
-		spBone **skeletonBones;
-		for (i = 0; i < start; i += 2) {
-			int n = bones[v];
-			v += n + 1;
-			skip += n;
-		}
-		skeletonBones = skeleton->bones;
-		if (deformLength == 0) {
-			int w, b;
-			for (w = offset, b = skip * 3; w < count; w += stride) {
-				float wx = 0, wy = 0;
-				int n = bones[v++];
-				n += v;
-				for (; v < n; v++, b += 3) {
-					spBone *bone = skeletonBones[bones[v]];
-					float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
-					wx += (vx * bone->a + vy * bone->b + bone->worldX) * weight;
-					wy += (vx * bone->c + vy * bone->d + bone->worldY) * weight;
-				}
-				worldVertices[w] = wx;
-				worldVertices[w + 1] = wy;
-			}
-		} else {
-			int w, b, f;
-			for (w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) {
-				float wx = 0, wy = 0;
-				int n = bones[v++];
-				n += v;
-				for (; v < n; v++, b += 3, f += 2) {
-					spBone *bone = skeletonBones[bones[v]];
-					float vx = vertices[b] + deformArray[f], vy =
-							vertices[b + 1] + deformArray[f + 1], weight = vertices[b + 2];
-					wx += (vx * bone->a + vy * bone->b + bone->worldX) * weight;
-					wy += (vx * bone->c + vy * bone->d + bone->worldY) * weight;
-				}
-				worldVertices[w] = wx;
-				worldVertices[w + 1] = wy;
-			}
-		}
-	}
-}
-
-void spVertexAttachment_copyTo(spVertexAttachment *from, spVertexAttachment *to) {
-	if (from->bonesCount) {
-		to->bonesCount = from->bonesCount;
-		to->bones = MALLOC(int, from->bonesCount);
-		memcpy(to->bones, from->bones, from->bonesCount * sizeof(int));
-	} else {
-		to->bonesCount = 0;
-		if (to->bones) {
-			FREE(to->bones);
-			to->bones = 0;
-		}
-	}
-
-	if (from->verticesCount) {
-		to->verticesCount = from->verticesCount;
-		to->vertices = MALLOC(float, from->verticesCount);
-		memcpy(to->vertices, from->vertices, from->verticesCount * sizeof(float));
-	} else {
-		to->verticesCount = 0;
-		if (to->vertices) {
-			FREE(to->vertices);
-			to->vertices = 0;
-		}
-	}
-	to->worldVerticesLength = from->worldVerticesLength;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/VertexAttachment.h>
+#include <spine/extension.h>
+
+/* FIXME this is not thread-safe */
+static int nextID = 0;
+
+void _spVertexAttachment_init(spVertexAttachment *attachment) {
+	attachment->id = nextID++;
+	attachment->deformAttachment = attachment;
+}
+
+void _spVertexAttachment_deinit(spVertexAttachment *attachment) {
+	_spAttachment_deinit(SUPER(attachment));
+	FREE(attachment->bones);
+	FREE(attachment->vertices);
+}
+
+void spVertexAttachment_computeWorldVertices(spVertexAttachment *self, spSlot *slot, int start, int count,
+											 float *worldVertices, int offset, int stride) {
+	spSkeleton *skeleton;
+	int deformLength;
+	float *deformArray;
+	float *vertices;
+	int *bones;
+
+	count = offset + (count >> 1) * stride;
+	skeleton = slot->bone->skeleton;
+	deformLength = slot->deformCount;
+	deformArray = slot->deform;
+	vertices = self->vertices;
+	bones = self->bones;
+	if (!bones) {
+		spBone *bone;
+		int v, w;
+		float x, y;
+		if (deformLength > 0) vertices = deformArray;
+		bone = slot->bone;
+		x = bone->worldX;
+		y = bone->worldY;
+		for (v = start, w = offset; w < count; v += 2, w += stride) {
+			float vx = vertices[v], vy = vertices[v + 1];
+			worldVertices[w] = vx * bone->a + vy * bone->b + x;
+			worldVertices[w + 1] = vx * bone->c + vy * bone->d + y;
+		}
+	} else {
+		int v = 0, skip = 0, i;
+		spBone **skeletonBones;
+		for (i = 0; i < start; i += 2) {
+			int n = bones[v];
+			v += n + 1;
+			skip += n;
+		}
+		skeletonBones = skeleton->bones;
+		if (deformLength == 0) {
+			int w, b;
+			for (w = offset, b = skip * 3; w < count; w += stride) {
+				float wx = 0, wy = 0;
+				int n = bones[v++];
+				n += v;
+				for (; v < n; v++, b += 3) {
+					spBone *bone = skeletonBones[bones[v]];
+					float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
+					wx += (vx * bone->a + vy * bone->b + bone->worldX) * weight;
+					wy += (vx * bone->c + vy * bone->d + bone->worldY) * weight;
+				}
+				worldVertices[w] = wx;
+				worldVertices[w + 1] = wy;
+			}
+		} else {
+			int w, b, f;
+			for (w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) {
+				float wx = 0, wy = 0;
+				int n = bones[v++];
+				n += v;
+				for (; v < n; v++, b += 3, f += 2) {
+					spBone *bone = skeletonBones[bones[v]];
+					float vx = vertices[b] + deformArray[f], vy = vertices[b + 1] + deformArray[f + 1], weight = vertices[b + 2];
+					wx += (vx * bone->a + vy * bone->b + bone->worldX) * weight;
+					wy += (vx * bone->c + vy * bone->d + bone->worldY) * weight;
+				}
+				worldVertices[w] = wx;
+				worldVertices[w + 1] = wy;
+			}
+		}
+	}
+}
+
+void spVertexAttachment_copyTo(spVertexAttachment *from, spVertexAttachment *to) {
+	if (from->bonesCount) {
+		to->bonesCount = from->bonesCount;
+		to->bones = MALLOC(int, from->bonesCount);
+		memcpy(to->bones, from->bones, from->bonesCount * sizeof(int));
+	} else {
+		to->bonesCount = 0;
+		if (to->bones) {
+			FREE(to->bones);
+			to->bones = 0;
+		}
+	}
+
+	if (from->verticesCount) {
+		to->verticesCount = from->verticesCount;
+		to->vertices = MALLOC(float, from->verticesCount);
+		memcpy(to->vertices, from->vertices, from->verticesCount * sizeof(float));
+	} else {
+		to->verticesCount = 0;
+		if (to->vertices) {
+			FREE(to->vertices);
+			to->vertices = 0;
+		}
+	}
+	to->worldVerticesLength = from->worldVerticesLength;
+}

+ 136 - 136
spine-c/spine-c/src/spine/extension.c

@@ -1,136 +1,136 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/extension.h>
-#include <stdio.h>
-
-float _spInternalRandom() {
-	return rand() / (float) RAND_MAX;
-}
-
-static void *(*mallocFunc)(size_t size) = malloc;
-
-static void *(*reallocFunc)(void *ptr, size_t size) = realloc;
-
-static void *(*debugMallocFunc)(size_t size, const char *file, int line) = NULL;
-
-static void (*freeFunc)(void *ptr) = free;
-
-static float (*randomFunc)() = _spInternalRandom;
-
-void *_spMalloc(size_t size, const char *file, int line) {
-	if (debugMallocFunc)
-		return debugMallocFunc(size, file, line);
-
-	return mallocFunc(size);
-}
-
-void *_spCalloc(size_t num, size_t size, const char *file, int line) {
-	void *ptr = _spMalloc(num * size, file, line);
-	if (ptr) memset(ptr, 0, num * size);
-	return ptr;
-}
-
-void *_spRealloc(void *ptr, size_t size) {
-	return reallocFunc(ptr, size);
-}
-
-void _spFree(void *ptr) {
-	freeFunc(ptr);
-}
-
-float _spRandom() {
-	return randomFunc();
-}
-
-void _spSetDebugMalloc(void *(*malloc)(size_t size, const char *file, int line)) {
-	debugMallocFunc = malloc;
-}
-
-void _spSetMalloc(void *(*malloc)(size_t size)) {
-	mallocFunc = malloc;
-}
-
-void _spSetRealloc(void *(*realloc)(void *ptr, size_t size)) {
-	reallocFunc = realloc;
-}
-
-void _spSetFree(void (*free)(void *ptr)) {
-	freeFunc = free;
-}
-
-void _spSetRandom(float (*random)()) {
-	randomFunc = random;
-}
-
-char *_spReadFile(const char *path, int *length) {
-	char *data;
-	size_t result;
-	FILE *file = fopen(path, "rb");
-	if (!file) return 0;
-
-	fseek(file, 0, SEEK_END);
-	*length = (int) ftell(file);
-	fseek(file, 0, SEEK_SET);
-
-	data = MALLOC(char, *length);
-	result = fread(data, 1, *length, file);
-	UNUSED(result);
-	fclose(file);
-
-	return data;
-}
-
-float _spMath_random(float min, float max) {
-	return min + (max - min) * _spRandom();
-}
-
-float _spMath_randomTriangular(float min, float max) {
-	return _spMath_randomTriangularWith(min, max, (min + max) * 0.5f);
-}
-
-float _spMath_randomTriangularWith(float min, float max, float mode) {
-	float u = _spRandom();
-	float d = max - min;
-	if (u <= (mode - min) / d) return min + SQRT(u * d * (mode - min));
-	return max - SQRT((1 - u) * d * (max - mode));
-}
-
-float _spMath_interpolate(float (*apply)(float a), float start, float end, float a) {
-	return start + (end - start) * apply(a);
-}
-
-float _spMath_pow2_apply(float a) {
-	if (a <= 0.5) return POW(a * 2, 2) / 2;
-	return POW((a - 1) * 2, 2) / -2 + 1;
-}
-
-float _spMath_pow2out_apply(float a) {
-	return POW(a - 1, 2) * -1 + 1;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/extension.h>
+#include <stdio.h>
+
+float _spInternalRandom() {
+	return rand() / (float) RAND_MAX;
+}
+
+static void *(*mallocFunc)(size_t size) = malloc;
+
+static void *(*reallocFunc)(void *ptr, size_t size) = realloc;
+
+static void *(*debugMallocFunc)(size_t size, const char *file, int line) = NULL;
+
+static void (*freeFunc)(void *ptr) = free;
+
+static float (*randomFunc)() = _spInternalRandom;
+
+void *_spMalloc(size_t size, const char *file, int line) {
+	if (debugMallocFunc)
+		return debugMallocFunc(size, file, line);
+
+	return mallocFunc(size);
+}
+
+void *_spCalloc(size_t num, size_t size, const char *file, int line) {
+	void *ptr = _spMalloc(num * size, file, line);
+	if (ptr) memset(ptr, 0, num * size);
+	return ptr;
+}
+
+void *_spRealloc(void *ptr, size_t size) {
+	return reallocFunc(ptr, size);
+}
+
+void _spFree(void *ptr) {
+	freeFunc(ptr);
+}
+
+float _spRandom() {
+	return randomFunc();
+}
+
+void _spSetDebugMalloc(void *(*malloc)(size_t size, const char *file, int line)) {
+	debugMallocFunc = malloc;
+}
+
+void _spSetMalloc(void *(*malloc)(size_t size)) {
+	mallocFunc = malloc;
+}
+
+void _spSetRealloc(void *(*realloc)(void *ptr, size_t size)) {
+	reallocFunc = realloc;
+}
+
+void _spSetFree(void (*free)(void *ptr)) {
+	freeFunc = free;
+}
+
+void _spSetRandom(float (*random)()) {
+	randomFunc = random;
+}
+
+char *_spReadFile(const char *path, int *length) {
+	char *data;
+	size_t result;
+	FILE *file = fopen(path, "rb");
+	if (!file) return 0;
+
+	fseek(file, 0, SEEK_END);
+	*length = (int) ftell(file);
+	fseek(file, 0, SEEK_SET);
+
+	data = MALLOC(char, *length);
+	result = fread(data, 1, *length, file);
+	UNUSED(result);
+	fclose(file);
+
+	return data;
+}
+
+float _spMath_random(float min, float max) {
+	return min + (max - min) * _spRandom();
+}
+
+float _spMath_randomTriangular(float min, float max) {
+	return _spMath_randomTriangularWith(min, max, (min + max) * 0.5f);
+}
+
+float _spMath_randomTriangularWith(float min, float max, float mode) {
+	float u = _spRandom();
+	float d = max - min;
+	if (u <= (mode - min) / d) return min + SQRT(u * d * (mode - min));
+	return max - SQRT((1 - u) * d * (max - mode));
+}
+
+float _spMath_interpolate(float (*apply)(float a), float start, float end, float a) {
+	return start + (end - start) * apply(a);
+}
+
+float _spMath_pow2_apply(float a) {
+	if (a <= 0.5) return POW(a * 2, 2) / 2;
+	return POW((a - 1) * 2, 2) / -2 + 1;
+}
+
+float _spMath_pow2out_apply(float a) {
+	return POW(a - 1, 2) * -1 + 1;
+}

+ 6 - 6
spine-cocos2dx/example-v4/proj.android/app/jni/hellocpp/main.cpp

@@ -29,14 +29,14 @@
 
 #include "AppDelegate.h"
 
-#define  LOG_TAG    "main"
-#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
+#define LOG_TAG "main"
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
 
 namespace {
-std::unique_ptr<AppDelegate> appDelegate;
+	std::unique_ptr<AppDelegate> appDelegate;
 }
 
-void cocos_android_app_init(JNIEnv* env) {
-    LOGD("cocos_android_app_init");
-    appDelegate.reset(new AppDelegate());
+void cocos_android_app_init(JNIEnv *env) {
+	LOGD("cocos_android_app_init");
+	appDelegate.reset(new AppDelegate());
 }

+ 1 - 3
spine-cocos2dx/example-v4/proj.ios_mac/ios/AppController.h

@@ -29,10 +29,8 @@
 @class RootViewController;
 
 @interface AppController : NSObject <UIApplicationDelegate> {
-
 }
 
-@property(nonatomic, readonly) RootViewController* viewController;
+@property(nonatomic, readonly) RootViewController *viewController;
 
 @end
-

+ 1 - 2
spine-cocos2dx/example-v4/proj.ios_mac/ios/RootViewController.h

@@ -28,8 +28,7 @@
 
 
 @interface RootViewController : UIViewController {
-
 }
-- (BOOL) prefersStatusBarHidden;
+- (BOOL)prefersStatusBarHidden;
 
 @end

+ 3 - 4
spine-cocos2dx/example-v4/proj.ios_mac/mac/main.cpp

@@ -28,8 +28,7 @@
 
 USING_NS_CC;
 
-int main(int argc, char *argv[])
-{
-    AppDelegate app;
-    return Application::getInstance()->run();
+int main(int argc, char *argv[]) {
+	AppDelegate app;
+	return Application::getInstance()->run();
 }

+ 6 - 7
spine-cocos2dx/example-v4/proj.linux/main.cpp

@@ -24,16 +24,15 @@
 
 #include "../Classes/AppDelegate.h"
 
-#include <stdlib.h>
 #include <stdio.h>
-#include <unistd.h>
+#include <stdlib.h>
 #include <string>
+#include <unistd.h>
 
 USING_NS_CC;
 
-int main(int argc, char **argv)
-{
-    // create the application instance
-    AppDelegate app;
-    return Application::getInstance()->run();
+int main(int argc, char **argv) {
+	// create the application instance
+	AppDelegate app;
+	return Application::getInstance()->run();
 }

+ 8 - 9
spine-cocos2dx/example-v4/proj.win32/main.cpp

@@ -29,14 +29,13 @@
 USING_NS_CC;
 
 int WINAPI _tWinMain(HINSTANCE hInstance,
-                       HINSTANCE hPrevInstance,
-                       LPTSTR    lpCmdLine,
-                       int       nCmdShow)
-{
-    UNREFERENCED_PARAMETER(hPrevInstance);
-    UNREFERENCED_PARAMETER(lpCmdLine);
+					 HINSTANCE hPrevInstance,
+					 LPTSTR lpCmdLine,
+					 int nCmdShow) {
+	UNREFERENCED_PARAMETER(hPrevInstance);
+	UNREFERENCED_PARAMETER(lpCmdLine);
 
-    // create the application instance
-    AppDelegate app;
-    return Application::getInstance()->run();
+	// create the application instance
+	AppDelegate app;
+	return Application::getInstance()->run();
 }

+ 3 - 3
spine-cocos2dx/example-v4/proj.win32/main.h

@@ -25,13 +25,13 @@
 #ifndef __MAIN_H__
 #define __MAIN_H__
 
-#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
+#define WIN32_LEAN_AND_MEAN// Exclude rarely-used stuff from Windows headers
 
 // Windows Header Files:
-#include <windows.h>
 #include <tchar.h>
+#include <windows.h>
 
 // C RunTime Header Files
 #include "platform/CCStdC.h"
 
-#endif    // __MAIN_H__
+#endif// __MAIN_H__

+ 8 - 8
spine-cocos2dx/example-v4/proj.win32/resource.h

@@ -27,18 +27,18 @@
 // Used by game.RC
 //
 
-#define IDS_PROJNAME                100
-#define IDR_TESTJS    100
+#define IDS_PROJNAME 100
+#define IDR_TESTJS 100
 
-#define ID_FILE_NEW_WINDOW            32771
+#define ID_FILE_NEW_WINDOW 32771
 
 // Next default values for new objects
-// 
+//
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
-#define _APS_NEXT_RESOURCE_VALUE    201
-#define _APS_NEXT_CONTROL_VALUE        1000
-#define _APS_NEXT_SYMED_VALUE        101
-#define _APS_NEXT_COMMAND_VALUE        32775
+#define _APS_NEXT_RESOURCE_VALUE 201
+#define _APS_NEXT_CONTROL_VALUE 1000
+#define _APS_NEXT_SYMED_VALUE 101
+#define _APS_NEXT_COMMAND_VALUE 32775
 #endif
 #endif

+ 135 - 135
spine-cocos2dx/example/Classes/AppDelegate.cpp

@@ -1,135 +1,135 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include "AppDelegate.h"
-
-#include <vector>
-#include <string>
-
-#include "IKExample.h"
-#include <spine/spine-cocos2dx.h>
-#include <spine/Debug.h>
-#include "AppMacros.h"
-
-USING_NS_CC;
-using namespace std;
-
-using namespace spine;
-
-DebugExtension debugExtension(SpineExtension::getInstance());
-
-AppDelegate::AppDelegate () {
-}
-
-AppDelegate::~AppDelegate () {
-	SkeletonBatch::destroyInstance();
-	SkeletonTwoColorBatch::destroyInstance();
-	debugExtension.reportLeaks();
-}
-
-bool AppDelegate::applicationDidFinishLaunching () {
-	// initialize director
-	auto director = Director::getInstance();
-	auto glview = director->getOpenGLView();
-	if (!glview) {
-		GLContextAttrs attrs = { 8, 8, 8, 8, 0, 0 };
-		GLView::setGLContextAttrs(attrs);
-		glview = GLViewImpl::create("Spine Example");
-		director->setOpenGLView(glview);
-	}
-
-	// Set the design resolution
-#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8)
-	// a bug in DirectX 11 level9-x on the device prevents ResolutionPolicy::NO_BORDER from working correctly
-	glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::SHOW_ALL);
-#else
-	glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
-#endif
-
-	cocos2d::Size frameSize = glview->getFrameSize();
-	
-	vector<string> searchPath;
-
-	// In this demo, we select resource according to the frame's height.
-	// If the resource size is different from design resolution size, you need to set contentScaleFactor.
-	// We use the ratio of resource's height to the height of design resolution,
-	// this can make sure that the resource's height could fit for the height of design resolution.
-	if (frameSize.height > mediumResource.size.height) {
-		// if the frame's height is larger than the height of medium resource size, select large resource.
-		searchPath.push_back(largeResource.directory);
-		director->setContentScaleFactor(MIN(largeResource.size.height/designResolutionSize.height, largeResource.size.width/designResolutionSize.width));
-	} else if (frameSize.height > smallResource.size.height) {
-		// if the frame's height is larger than the height of small resource size, select medium resource.
-		searchPath.push_back(mediumResource.directory);
-		director->setContentScaleFactor(MIN(mediumResource.size.height/designResolutionSize.height, mediumResource.size.width/designResolutionSize.width));
-	} else {
-		// if the frame's height is smaller than the height of medium resource size, select small resource.
-		searchPath.push_back(smallResource.directory);
-		director->setContentScaleFactor(MIN(smallResource.size.height/designResolutionSize.height, smallResource.size.width/designResolutionSize.width));
-	}
-	
-	searchPath.push_back("common");
-	 
-	// set search path
-	FileUtils::getInstance()->setSearchPaths(searchPath);
-	
-	// turn on display FPS
-	director->setDisplayStats(true);
-
-	// set FPS. the default value is 1.0/60 if you don't call this
-	director->setAnimationInterval(1.0f / 60);
-
-	// Set the Debug wrapper extension so we know about memory leaks.
-	SpineExtension::setInstance(&debugExtension);
-	
-	// create a scene. it's an autorelease object
-	//auto scene = RaptorExample::scene();
-	auto scene = IKExample::scene();
-
-	// run
-	director->runWithScene(scene);
-
-	return true;
-}
-
-// This function will be called when the app is inactive. When comes a phone call,it's be invoked too
-void AppDelegate::applicationDidEnterBackground () {
-	Director::getInstance()->stopAnimation();
-
-	// if you use SimpleAudioEngine, it must be paused
-	// SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();
-}
-
-// this function will be called when the app is active again
-void AppDelegate::applicationWillEnterForeground () {
-	Director::getInstance()->startAnimation();
-
-	// if you use SimpleAudioEngine, it must resume here
-	// SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic();
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "AppDelegate.h"
+
+#include <string>
+#include <vector>
+
+#include "AppMacros.h"
+#include "IKExample.h"
+#include <spine/Debug.h>
+#include <spine/spine-cocos2dx.h>
+
+USING_NS_CC;
+using namespace std;
+
+using namespace spine;
+
+DebugExtension debugExtension(SpineExtension::getInstance());
+
+AppDelegate::AppDelegate() {
+}
+
+AppDelegate::~AppDelegate() {
+	SkeletonBatch::destroyInstance();
+	SkeletonTwoColorBatch::destroyInstance();
+	debugExtension.reportLeaks();
+}
+
+bool AppDelegate::applicationDidFinishLaunching() {
+	// initialize director
+	auto director = Director::getInstance();
+	auto glview = director->getOpenGLView();
+	if (!glview) {
+		GLContextAttrs attrs = {8, 8, 8, 8, 0, 0};
+		GLView::setGLContextAttrs(attrs);
+		glview = GLViewImpl::create("Spine Example");
+		director->setOpenGLView(glview);
+	}
+
+	// Set the design resolution
+#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8)
+	// a bug in DirectX 11 level9-x on the device prevents ResolutionPolicy::NO_BORDER from working correctly
+	glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::SHOW_ALL);
+#else
+	glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
+#endif
+
+	cocos2d::Size frameSize = glview->getFrameSize();
+
+	vector<string> searchPath;
+
+	// In this demo, we select resource according to the frame's height.
+	// If the resource size is different from design resolution size, you need to set contentScaleFactor.
+	// We use the ratio of resource's height to the height of design resolution,
+	// this can make sure that the resource's height could fit for the height of design resolution.
+	if (frameSize.height > mediumResource.size.height) {
+		// if the frame's height is larger than the height of medium resource size, select large resource.
+		searchPath.push_back(largeResource.directory);
+		director->setContentScaleFactor(MIN(largeResource.size.height / designResolutionSize.height, largeResource.size.width / designResolutionSize.width));
+	} else if (frameSize.height > smallResource.size.height) {
+		// if the frame's height is larger than the height of small resource size, select medium resource.
+		searchPath.push_back(mediumResource.directory);
+		director->setContentScaleFactor(MIN(mediumResource.size.height / designResolutionSize.height, mediumResource.size.width / designResolutionSize.width));
+	} else {
+		// if the frame's height is smaller than the height of medium resource size, select small resource.
+		searchPath.push_back(smallResource.directory);
+		director->setContentScaleFactor(MIN(smallResource.size.height / designResolutionSize.height, smallResource.size.width / designResolutionSize.width));
+	}
+
+	searchPath.push_back("common");
+
+	// set search path
+	FileUtils::getInstance()->setSearchPaths(searchPath);
+
+	// turn on display FPS
+	director->setDisplayStats(true);
+
+	// set FPS. the default value is 1.0/60 if you don't call this
+	director->setAnimationInterval(1.0f / 60);
+
+	// Set the Debug wrapper extension so we know about memory leaks.
+	SpineExtension::setInstance(&debugExtension);
+
+	// create a scene. it's an autorelease object
+	//auto scene = RaptorExample::scene();
+	auto scene = IKExample::scene();
+
+	// run
+	director->runWithScene(scene);
+
+	return true;
+}
+
+// This function will be called when the app is inactive. When comes a phone call,it's be invoked too
+void AppDelegate::applicationDidEnterBackground() {
+	Director::getInstance()->stopAnimation();
+
+	// if you use SimpleAudioEngine, it must be paused
+	// SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();
+}
+
+// this function will be called when the app is active again
+void AppDelegate::applicationWillEnterForeground() {
+	Director::getInstance()->startAnimation();
+
+	// if you use SimpleAudioEngine, it must resume here
+	// SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic();
+}

+ 45 - 45
spine-cocos2dx/example/Classes/AppDelegate.h

@@ -1,45 +1,45 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#ifndef  _APPDELEGATE_H_
-#define  _APPDELEGATE_H_
-
-#include "cocos2d.h"
-
-class AppDelegate: private cocos2d::Application {
-public:
-	AppDelegate ();
-	virtual ~AppDelegate ();
-
-	virtual bool applicationDidFinishLaunching ();
-	virtual void applicationDidEnterBackground ();
-	virtual void applicationWillEnterForeground ();    
-};
-
-#endif // _APPDELEGATE_H_
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#ifndef _APPDELEGATE_H_
+#define _APPDELEGATE_H_
+
+#include "cocos2d.h"
+
+class AppDelegate : private cocos2d::Application {
+public:
+	AppDelegate();
+	virtual ~AppDelegate();
+
+	virtual bool applicationDidFinishLaunching();
+	virtual void applicationDidEnterBackground();
+	virtual void applicationWillEnterForeground();
+};
+
+#endif// _APPDELEGATE_H_

+ 88 - 88
spine-cocos2dx/example/Classes/AppMacros.h

@@ -1,88 +1,88 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#ifndef _APPMACROS_H_
-#define _APPMACROS_H_
-
-#include "cocos2d.h"
-
-/* For demonstrating using one design resolution to match different resources,
- or one resource to match different design resolutions.
-
- [Situation 1] Using one design resolution to match different resources.
- Please look into Appdelegate::applicationDidFinishLaunching.
- We check current device frame size to decide which resource need to be selected.
- So if you want to test this situation which said in title '[Situation 1]',
- you should change ios simulator to different device(e.g. iphone, iphone-retina3.5, iphone-retina4.0, ipad, ipad-retina),
- or change the window size in "proj.XXX/main.cpp" by "CCEGLView::setFrameSize" if you are using win32 or linux plaform
- and modify "proj.mac/AppController.mm" by changing the window rectangle.
-
- [Situation 2] Using one resource to match different design resolutions.
- The coordinates in your codes is based on your current design resolution rather than resource size.
- Therefore, your design resolution could be very large and your resource size could be small.
- To test this, just define the marco 'TARGET_DESIGN_RESOLUTION_SIZE' to 'DESIGN_RESOLUTION_2048X1536'
- and open iphone simulator or create a window of 480x320 size.
-
- [Note] Normally, developer just need to define one design resolution(e.g. 960x640) with one or more resources.
- */
-
-#define DESIGN_RESOLUTION_480X320    0
-#define DESIGN_RESOLUTION_960x640    1
-#define DESIGN_RESOLUTION_1024X768   2
-#define DESIGN_RESOLUTION_2048X1536  3
-
-/* If you want to switch design resolution, change next line */
-#define TARGET_DESIGN_RESOLUTION_SIZE  DESIGN_RESOLUTION_960x640
-
-typedef struct tagResource {
-	cocos2d::Size size;
-	char directory[100];
-} Resource;
-
-static Resource smallResource = {cocos2d::Size(480, 320), "iphone"};
-static Resource mediumResource = {cocos2d::Size(960, 640), "iphone-retina"};
-static Resource largeResource = {cocos2d::Size(1024, 768), "ipad"};
-static Resource extralargeResource = {cocos2d::Size(2048, 1536), "ipad-retina"};
-
-#if (TARGET_DESIGN_RESOLUTION_SIZE == DESIGN_RESOLUTION_480X320)
-static cocos2d::Size designResolutionSize = cocos2d::Size(480, 320);
-#elif (TARGET_DESIGN_RESOLUTION_SIZE == DESIGN_RESOLUTION_960x640)
-static cocos2d::Size designResolutionSize = cocos2d::Size(960, 640);
-#elif (TARGET_DESIGN_RESOLUTION_SIZE == DESIGN_RESOLUTION_1024X768)
-static cocos2d::Size designResolutionSize = cocos2d::Size(1024, 768);
-#elif (TARGET_DESIGN_RESOLUTION_SIZE == DESIGN_RESOLUTION_2048X1536)
-static cocos2d::Size designResolutionSize = cocos2d::Size(2048, 1536);
-#else
-#error unknown target design resolution!
-#endif
-
-// The font size 24 is designed for small resolution, so we should change it to fit for current design resolution
-#define TITLE_FONT_SIZE (cocos2d::Director::getInstance()->getOpenGLView()->getDesignResolutionSize().width / smallResource.size.width * 24)
-
-#endif /* _APPMACROS_H_ */
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#ifndef _APPMACROS_H_
+#define _APPMACROS_H_
+
+#include "cocos2d.h"
+
+/* For demonstrating using one design resolution to match different resources,
+ or one resource to match different design resolutions.
+
+ [Situation 1] Using one design resolution to match different resources.
+ Please look into Appdelegate::applicationDidFinishLaunching.
+ We check current device frame size to decide which resource need to be selected.
+ So if you want to test this situation which said in title '[Situation 1]',
+ you should change ios simulator to different device(e.g. iphone, iphone-retina3.5, iphone-retina4.0, ipad, ipad-retina),
+ or change the window size in "proj.XXX/main.cpp" by "CCEGLView::setFrameSize" if you are using win32 or linux plaform
+ and modify "proj.mac/AppController.mm" by changing the window rectangle.
+
+ [Situation 2] Using one resource to match different design resolutions.
+ The coordinates in your codes is based on your current design resolution rather than resource size.
+ Therefore, your design resolution could be very large and your resource size could be small.
+ To test this, just define the marco 'TARGET_DESIGN_RESOLUTION_SIZE' to 'DESIGN_RESOLUTION_2048X1536'
+ and open iphone simulator or create a window of 480x320 size.
+
+ [Note] Normally, developer just need to define one design resolution(e.g. 960x640) with one or more resources.
+ */
+
+#define DESIGN_RESOLUTION_480X320 0
+#define DESIGN_RESOLUTION_960x640 1
+#define DESIGN_RESOLUTION_1024X768 2
+#define DESIGN_RESOLUTION_2048X1536 3
+
+/* If you want to switch design resolution, change next line */
+#define TARGET_DESIGN_RESOLUTION_SIZE DESIGN_RESOLUTION_960x640
+
+typedef struct tagResource {
+	cocos2d::Size size;
+	char directory[100];
+} Resource;
+
+static Resource smallResource = {cocos2d::Size(480, 320), "iphone"};
+static Resource mediumResource = {cocos2d::Size(960, 640), "iphone-retina"};
+static Resource largeResource = {cocos2d::Size(1024, 768), "ipad"};
+static Resource extralargeResource = {cocos2d::Size(2048, 1536), "ipad-retina"};
+
+#if (TARGET_DESIGN_RESOLUTION_SIZE == DESIGN_RESOLUTION_480X320)
+static cocos2d::Size designResolutionSize = cocos2d::Size(480, 320);
+#elif (TARGET_DESIGN_RESOLUTION_SIZE == DESIGN_RESOLUTION_960x640)
+static cocos2d::Size designResolutionSize = cocos2d::Size(960, 640);
+#elif (TARGET_DESIGN_RESOLUTION_SIZE == DESIGN_RESOLUTION_1024X768)
+static cocos2d::Size designResolutionSize = cocos2d::Size(1024, 768);
+#elif (TARGET_DESIGN_RESOLUTION_SIZE == DESIGN_RESOLUTION_2048X1536)
+static cocos2d::Size designResolutionSize = cocos2d::Size(2048, 1536);
+#else
+#error unknown target design resolution!
+#endif
+
+// The font size 24 is designed for small resolution, so we should change it to fit for current design resolution
+#define TITLE_FONT_SIZE (cocos2d::Director::getInstance()->getOpenGLView()->getDesignResolutionSize().width / smallResource.size.width * 24)
+
+#endif /* _APPMACROS_H_ */

+ 114 - 115
spine-cocos2dx/example/Classes/BatchingExample.cpp

@@ -1,115 +1,114 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include "BatchingExample.h"
-#include "IKExample.h"
-
-USING_NS_CC;
-using namespace spine;
-
-#define NUM_SKELETONS 50
-
-Cocos2dTextureLoader textureLoader;
-
-Scene* BatchingExample::scene () {
-	Scene *scene = Scene::create();
-	scene->addChild(BatchingExample::create());
-	return scene;
-}
-
-bool BatchingExample::init () {
-	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
-
-	// Load the texture atlas. Note that the texture loader has to live
-	// as long as the Atlas, as the Atlas destructor will call TextureLoader::unload.
-	_atlas = new (__FILE__, __LINE__) Atlas("spineboy.atlas", &textureLoader, true);
-	CCASSERT(_atlas, "Error reading atlas file.");
-
-	// This attachment loader configures attachments with data needed for cocos2d-x rendering.
-	// Do not dispose the attachment loader until the skeleton data is disposed!
-	_attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas);
-
-	// Load the skeleton data.
-	SkeletonJson* json = new (__FILE__, __LINE__) SkeletonJson(_attachmentLoader);
-	json->setScale(0.6f); // Resizes skeleton data to 60% of the size it was in Spine.
-	_skeletonData = json->readSkeletonDataFile("spineboy-pro.json");
-	CCASSERT(_skeletonData, json->getError().isEmpty() ? json->getError().buffer() : "Error reading skeleton data file.");
-	delete json;
-
-	// Setup mix times.
-	_stateData = new (__FILE__, __LINE__) AnimationStateData(_skeletonData);
-	_stateData->setMix("walk", "jump", 0.2f);
-	_stateData->setMix("jump", "run", 0.2f);
-
-	int xMin = _contentSize.width * 0.10f, xMax = _contentSize.width * 0.90f;
-	int yMin = 0, yMax = _contentSize.height * 0.7f;
-	for (int i = 0; i < NUM_SKELETONS; i++) {
-		// Each skeleton node shares the same atlas, skeleton data, and mix times.
-		SkeletonAnimation* skeletonNode = SkeletonAnimation::createWithData(_skeletonData, false);
-		skeletonNode->setAnimationStateData(_stateData);
-
-		skeletonNode->setAnimation(0, "walk", true);
-		skeletonNode->addAnimation(0, "jump", true, RandomHelper::random_int(0, 300) / 100.0f);
-		skeletonNode->addAnimation(0, "run", true);
-
-		// alternative setting two color tint for groups of 10 skeletons
-		// should end up with #skeletons / 10 batches
-		// if (j++ < 10)
-//			skeletonNode->setTwoColorTint(true);
-//		if (j == 20) j = 0;
-		// skeletonNode->setTwoColorTint(true);
-
-		skeletonNode->setPosition(Vec2(
-			RandomHelper::random_int(xMin, xMax),
-			RandomHelper::random_int(yMin, yMax)
-		));
-		addChild(skeletonNode);
-	}
-
-	scheduleUpdate();
-
-	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
-	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
-		Director::getInstance()->replaceScene(IKExample::scene());
-		return true;
-	};
-	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
-
-	return true;
-}
-
-BatchingExample::~BatchingExample () {
-	// SkeletonAnimation instances are cocos2d-x nodes and are disposed of automatically as normal, but the data created
-	// manually to be shared across multiple SkeletonAnimations needs to be disposed of manually.
-
-	delete _skeletonData;
-	delete _stateData;
-	delete _attachmentLoader;
-	delete _atlas;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "BatchingExample.h"
+#include "IKExample.h"
+
+USING_NS_CC;
+using namespace spine;
+
+#define NUM_SKELETONS 50
+
+Cocos2dTextureLoader textureLoader;
+
+Scene *BatchingExample::scene() {
+	Scene *scene = Scene::create();
+	scene->addChild(BatchingExample::create());
+	return scene;
+}
+
+bool BatchingExample::init() {
+	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
+
+	// Load the texture atlas. Note that the texture loader has to live
+	// as long as the Atlas, as the Atlas destructor will call TextureLoader::unload.
+	_atlas = new (__FILE__, __LINE__) Atlas("spineboy.atlas", &textureLoader, true);
+	CCASSERT(_atlas, "Error reading atlas file.");
+
+	// This attachment loader configures attachments with data needed for cocos2d-x rendering.
+	// Do not dispose the attachment loader until the skeleton data is disposed!
+	_attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas);
+
+	// Load the skeleton data.
+	SkeletonJson *json = new (__FILE__, __LINE__) SkeletonJson(_attachmentLoader);
+	json->setScale(0.6f);// Resizes skeleton data to 60% of the size it was in Spine.
+	_skeletonData = json->readSkeletonDataFile("spineboy-pro.json");
+	CCASSERT(_skeletonData, json->getError().isEmpty() ? json->getError().buffer() : "Error reading skeleton data file.");
+	delete json;
+
+	// Setup mix times.
+	_stateData = new (__FILE__, __LINE__) AnimationStateData(_skeletonData);
+	_stateData->setMix("walk", "jump", 0.2f);
+	_stateData->setMix("jump", "run", 0.2f);
+
+	int xMin = _contentSize.width * 0.10f, xMax = _contentSize.width * 0.90f;
+	int yMin = 0, yMax = _contentSize.height * 0.7f;
+	for (int i = 0; i < NUM_SKELETONS; i++) {
+		// Each skeleton node shares the same atlas, skeleton data, and mix times.
+		SkeletonAnimation *skeletonNode = SkeletonAnimation::createWithData(_skeletonData, false);
+		skeletonNode->setAnimationStateData(_stateData);
+
+		skeletonNode->setAnimation(0, "walk", true);
+		skeletonNode->addAnimation(0, "jump", true, RandomHelper::random_int(0, 300) / 100.0f);
+		skeletonNode->addAnimation(0, "run", true);
+
+		// alternative setting two color tint for groups of 10 skeletons
+		// should end up with #skeletons / 10 batches
+		// if (j++ < 10)
+		//			skeletonNode->setTwoColorTint(true);
+		//		if (j == 20) j = 0;
+		// skeletonNode->setTwoColorTint(true);
+
+		skeletonNode->setPosition(Vec2(
+				RandomHelper::random_int(xMin, xMax),
+				RandomHelper::random_int(yMin, yMax)));
+		addChild(skeletonNode);
+	}
+
+	scheduleUpdate();
+
+	EventListenerTouchOneByOne *listener = EventListenerTouchOneByOne::create();
+	listener->onTouchBegan = [this](Touch *touch, cocos2d::Event *event) -> bool {
+		Director::getInstance()->replaceScene(IKExample::scene());
+		return true;
+	};
+	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
+
+	return true;
+}
+
+BatchingExample::~BatchingExample() {
+	// SkeletonAnimation instances are cocos2d-x nodes and are disposed of automatically as normal, but the data created
+	// manually to be shared across multiple SkeletonAnimations needs to be disposed of manually.
+
+	delete _skeletonData;
+	delete _stateData;
+	delete _attachmentLoader;
+	delete _atlas;
+}

+ 54 - 54
spine-cocos2dx/example/Classes/BatchingExample.h

@@ -1,54 +1,54 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#ifndef _BATCHINGEXAMPLE_H_
-#define _BATCHINGEXAMPLE_H_
-
-#include "cocos2d.h"
-#include <spine/spine-cocos2dx.h>
-
-using namespace spine;
-
-class BatchingExample : public cocos2d::LayerColor {
-public:
-	static cocos2d::Scene* scene ();
-
-	CREATE_FUNC(BatchingExample);
-	~BatchingExample ();
-
-	virtual bool init ();
-
-protected:
-	Atlas* _atlas;
-	AttachmentLoader* _attachmentLoader;
-	SkeletonData* _skeletonData;
-	AnimationStateData* _stateData;
-};
-
-#endif // _BATCHINGEXAMPLE_H_
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#ifndef _BATCHINGEXAMPLE_H_
+#define _BATCHINGEXAMPLE_H_
+
+#include "cocos2d.h"
+#include <spine/spine-cocos2dx.h>
+
+using namespace spine;
+
+class BatchingExample : public cocos2d::LayerColor {
+public:
+	static cocos2d::Scene *scene();
+
+	CREATE_FUNC(BatchingExample);
+	~BatchingExample();
+
+	virtual bool init();
+
+protected:
+	Atlas *_atlas;
+	AttachmentLoader *_attachmentLoader;
+	SkeletonData *_skeletonData;
+	AnimationStateData *_stateData;
+};
+
+#endif// _BATCHINGEXAMPLE_H_

+ 66 - 66
spine-cocos2dx/example/Classes/CoinExample.cpp

@@ -1,66 +1,66 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include "CoinExample.h"
-#include "MixAndMatchExample.h"
-
-USING_NS_CC;
-using namespace spine;
-
-Scene* CoinExample::scene () {
-	Scene *scene = Scene::create();
-	scene->addChild(CoinExample::create());
-	return scene;
-}
-
-bool CoinExample::init () {
-	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
-
-	skeletonNode = SkeletonAnimation::createWithBinaryFile("coin-pro.skel", "coin.atlas", 1);
-	skeletonNode->setAnimation(0, "animation", true);
-
-	skeletonNode->setPosition(Vec2(_contentSize.width / 2, _contentSize.height / 2));
-	addChild(skeletonNode);
-
-	scheduleUpdate();
-
-	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
-	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
-		if (!skeletonNode->getDebugBonesEnabled())
-			skeletonNode->setDebugBonesEnabled(true);
-		else if (skeletonNode->getTimeScale() == 1)
-			skeletonNode->setTimeScale(0.3f);
-		else
-			Director::getInstance()->replaceScene(MixAndMatchExample::scene());
-		return true;
-	};
-	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
-
-	return true;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "CoinExample.h"
+#include "MixAndMatchExample.h"
+
+USING_NS_CC;
+using namespace spine;
+
+Scene *CoinExample::scene() {
+	Scene *scene = Scene::create();
+	scene->addChild(CoinExample::create());
+	return scene;
+}
+
+bool CoinExample::init() {
+	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
+
+	skeletonNode = SkeletonAnimation::createWithBinaryFile("coin-pro.skel", "coin.atlas", 1);
+	skeletonNode->setAnimation(0, "animation", true);
+
+	skeletonNode->setPosition(Vec2(_contentSize.width / 2, _contentSize.height / 2));
+	addChild(skeletonNode);
+
+	scheduleUpdate();
+
+	EventListenerTouchOneByOne *listener = EventListenerTouchOneByOne::create();
+	listener->onTouchBegan = [this](Touch *touch, cocos2d::Event *event) -> bool {
+		if (!skeletonNode->getDebugBonesEnabled())
+			skeletonNode->setDebugBonesEnabled(true);
+		else if (skeletonNode->getTimeScale() == 1)
+			skeletonNode->setTimeScale(0.3f);
+		else
+			Director::getInstance()->replaceScene(MixAndMatchExample::scene());
+		return true;
+	};
+	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
+
+	return true;
+}

+ 48 - 48
spine-cocos2dx/example/Classes/CoinExample.h

@@ -1,48 +1,48 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#ifndef _COINEXAMPLE_H_
-#define _COINEXAMPLE_H_
-
-#include "cocos2d.h"
-#include <spine/spine-cocos2dx.h>
-
-class CoinExample : public cocos2d::LayerColor {
-public:
-	static cocos2d::Scene* scene ();
-
-	CREATE_FUNC(CoinExample);
-
-	virtual bool init ();
-
-private:
-	spine::SkeletonAnimation* skeletonNode;
-};
-
-#endif // _COINXAMPLE_H_
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#ifndef _COINEXAMPLE_H_
+#define _COINEXAMPLE_H_
+
+#include "cocos2d.h"
+#include <spine/spine-cocos2dx.h>
+
+class CoinExample : public cocos2d::LayerColor {
+public:
+	static cocos2d::Scene *scene();
+
+	CREATE_FUNC(CoinExample);
+
+	virtual bool init();
+
+private:
+	spine::SkeletonAnimation *skeletonNode;
+};
+
+#endif// _COINXAMPLE_H_

+ 67 - 67
spine-cocos2dx/example/Classes/GoblinsExample.cpp

@@ -1,67 +1,67 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include "GoblinsExample.h"
-#include "RaptorExample.h"
-
-USING_NS_CC;
-using namespace spine;
-
-Scene* GoblinsExample::scene () {
-	Scene *scene = Scene::create();
-	scene->addChild(GoblinsExample::create());
-	return scene;
-}
-
-bool GoblinsExample::init () {
-	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
-
-	skeletonNode = SkeletonAnimation::createWithJsonFile("goblins-pro.json", "goblins.atlas", 1.5f);
-	skeletonNode->setAnimation(0, "walk", true);
-	skeletonNode->setSkin("goblin");
-
-	skeletonNode->setPosition(Vec2(_contentSize.width / 2, 20));
-	addChild(skeletonNode);
-
-	scheduleUpdate();
-
-	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
-	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
-		if (!skeletonNode->getDebugBonesEnabled())
-			skeletonNode->setDebugBonesEnabled(true);
-		else if (skeletonNode->getTimeScale() == 1)
-			skeletonNode->setTimeScale(0.3f);
-		else
-			Director::getInstance()->replaceScene(RaptorExample::scene());
-		return true;
-	};
-	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
-
-	return true;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "GoblinsExample.h"
+#include "RaptorExample.h"
+
+USING_NS_CC;
+using namespace spine;
+
+Scene *GoblinsExample::scene() {
+	Scene *scene = Scene::create();
+	scene->addChild(GoblinsExample::create());
+	return scene;
+}
+
+bool GoblinsExample::init() {
+	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
+
+	skeletonNode = SkeletonAnimation::createWithJsonFile("goblins-pro.json", "goblins.atlas", 1.5f);
+	skeletonNode->setAnimation(0, "walk", true);
+	skeletonNode->setSkin("goblin");
+
+	skeletonNode->setPosition(Vec2(_contentSize.width / 2, 20));
+	addChild(skeletonNode);
+
+	scheduleUpdate();
+
+	EventListenerTouchOneByOne *listener = EventListenerTouchOneByOne::create();
+	listener->onTouchBegan = [this](Touch *touch, cocos2d::Event *event) -> bool {
+		if (!skeletonNode->getDebugBonesEnabled())
+			skeletonNode->setDebugBonesEnabled(true);
+		else if (skeletonNode->getTimeScale() == 1)
+			skeletonNode->setTimeScale(0.3f);
+		else
+			Director::getInstance()->replaceScene(RaptorExample::scene());
+		return true;
+	};
+	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
+
+	return true;
+}

+ 48 - 48
spine-cocos2dx/example/Classes/GoblinsExample.h

@@ -1,48 +1,48 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#ifndef _GOBLINSEXAMPLE_H_
-#define _GOBLINSEXAMPLE_H_
-
-#include "cocos2d.h"
-#include <spine/spine-cocos2dx.h>
-
-class GoblinsExample : public cocos2d::LayerColor {
-public:
-	static cocos2d::Scene* scene ();
-
-	CREATE_FUNC(GoblinsExample);
-
-	virtual bool init ();
-
-private:
-	spine::SkeletonAnimation* skeletonNode;
-};
-
-#endif // _GOBLINSEXAMPLE_H_
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#ifndef _GOBLINSEXAMPLE_H_
+#define _GOBLINSEXAMPLE_H_
+
+#include "cocos2d.h"
+#include <spine/spine-cocos2dx.h>
+
+class GoblinsExample : public cocos2d::LayerColor {
+public:
+	static cocos2d::Scene *scene();
+
+	CREATE_FUNC(GoblinsExample);
+
+	virtual bool init();
+
+private:
+	spine::SkeletonAnimation *skeletonNode;
+};
+
+#endif// _GOBLINSEXAMPLE_H_

+ 31 - 32
spine-cocos2dx/example/Classes/IKExample.cpp

@@ -37,24 +37,24 @@ using namespace spine;
 // of a bone based on the touch position, which in
 // turn will make an IK chain follow that bone
 // smoothly.
-Scene* IKExample::scene () {
-    Scene *scene = Scene::create();
-    scene->addChild(IKExample::create());
-    return scene;
+Scene *IKExample::scene() {
+	Scene *scene = Scene::create();
+	scene->addChild(IKExample::create());
+	return scene;
 }
 
-bool IKExample::init () {
-    if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
-	
+bool IKExample::init() {
+	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
+
 	// Load the Spineboy skeleton and create a SkeletonAnimation node from it
 	// centered on the screen.
-    skeletonNode = SkeletonAnimation::createWithJsonFile("spineboy-pro.json", "spineboy.atlas", 0.6f);
-    skeletonNode->setPosition(Vec2(_contentSize.width / 2, 20));
-    addChild(skeletonNode);
-    
+	skeletonNode = SkeletonAnimation::createWithJsonFile("spineboy-pro.json", "spineboy.atlas", 0.6f);
+	skeletonNode->setPosition(Vec2(_contentSize.width / 2, 20));
+	addChild(skeletonNode);
+
 	// Queue the "walk" animation on the first track.
 	skeletonNode->setAnimation(0, "walk", true);
-	
+
 	// Queue the "aim" animation on a higher track.
 	// It consists of a single frame that positions
 	// the back arm and gun such that they point at
@@ -69,15 +69,15 @@ bool IKExample::init () {
 	// Next we setup a listener that receives and stores
 	// the current mouse location. The location is converted
 	// to the skeleton's coordinate system.
-	EventListenerMouse* mouseListener = EventListenerMouse::create();
-	mouseListener->onMouseMove = [this] (cocos2d::Event* event) -> void {
+	EventListenerMouse *mouseListener = EventListenerMouse::create();
+	mouseListener->onMouseMove = [this](cocos2d::Event *event) -> void {
 		// convert the mosue location to the skeleton's coordinate space
 		// and store it.
-		EventMouse* mouseEvent = dynamic_cast<EventMouse*>(event);
+		EventMouse *mouseEvent = dynamic_cast<EventMouse *>(event);
 		position = skeletonNode->convertToNodeSpace(mouseEvent->getLocationInView());
 	};
 	_eventDispatcher->addEventListenerWithSceneGraphPriority(mouseListener, this);
-	
+
 	// Position the "crosshair" bone at the mouse
 	// location.
 	//
@@ -91,30 +91,29 @@ bool IKExample::init () {
 	// converted mouse location, we call updateWorldTransforms()
 	// again so the change of the IK target position is
 	// applied to the rest of the skeleton.
-	skeletonNode->setPostUpdateWorldTransformsListener([this] (SkeletonAnimation* node) -> void {
-		Bone* crosshair = node->findBone("crosshair"); // The bone should be cached
+	skeletonNode->setPostUpdateWorldTransformsListener([this](SkeletonAnimation *node) -> void {
+		Bone *crosshair = node->findBone("crosshair");// The bone should be cached
 		float localX = 0, localY = 0;
 		crosshair->getParent()->worldToLocal(position.x, position.y, localX, localY);
 		crosshair->setX(localX);
 		crosshair->setY(localY);
 		crosshair->setAppliedValid(false);
-		
+
 		node->getSkeleton()->updateWorldTransform();
 	});
-	
-	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
-	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
-        Director::getInstance()->replaceScene(SpineboyExample::scene());
-        return true;
-    };
-	
-    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
-	
-    scheduleUpdate();
 
-    return true;
+	EventListenerTouchOneByOne *listener = EventListenerTouchOneByOne::create();
+	listener->onTouchBegan = [this](Touch *touch, cocos2d::Event *event) -> bool {
+		Director::getInstance()->replaceScene(SpineboyExample::scene());
+		return true;
+	};
+
+	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
+
+	scheduleUpdate();
+
+	return true;
 }
 
-void IKExample::update (float deltaTime) {
-    
+void IKExample::update(float deltaTime) {
 }

+ 6 - 6
spine-cocos2dx/example/Classes/IKExample.h

@@ -35,17 +35,17 @@
 
 class IKExample : public cocos2d::LayerColor {
 public:
-    static cocos2d::Scene* scene ();
+	static cocos2d::Scene *scene();
 
-    CREATE_FUNC (IKExample);
+	CREATE_FUNC(IKExample);
 
-    virtual bool init ();
+	virtual bool init();
 
-    virtual void update (float deltaTime);
+	virtual void update(float deltaTime);
 
 private:
-    spine::SkeletonAnimation* skeletonNode;
+	spine::SkeletonAnimation *skeletonNode;
 	cocos2d::Vec2 position;
 };
 
-#endif // _IKEXAMPLE_H_
+#endif// _IKEXAMPLE_H_

+ 6 - 6
spine-cocos2dx/example/Classes/MixAndMatchExample.cpp

@@ -33,7 +33,7 @@
 USING_NS_CC;
 using namespace spine;
 
-Scene* MixAndMatchExample::scene () {
+Scene *MixAndMatchExample::scene() {
 	Scene *scene = Scene::create();
 	scene->addChild(MixAndMatchExample::create());
 	return scene;
@@ -43,17 +43,17 @@ MixAndMatchExample::~MixAndMatchExample() {
 	delete skin;
 }
 
-bool MixAndMatchExample::init () {
+bool MixAndMatchExample::init() {
 	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
 
 	skeletonNode = SkeletonAnimation::createWithBinaryFile("mix-and-match-pro.skel", "mix-and-match.atlas", 0.5);
 	skeletonNode->setAnimation(0, "dance", true);
-	
+
 	// Create a new skin, by mixing and matching other skins
 	// that fit together. Items making up the girl are individual
 	// skins. Using the skin API, a new skin is created which is
 	// a combination of all these individual item skins.
-	SkeletonData* skeletonData = skeletonNode->getSkeleton()->getData();
+	SkeletonData *skeletonData = skeletonNode->getSkeleton()->getData();
 	skin = new (__FILE__, __LINE__) Skin("mix-and-match");
 	skin->addSkin(skeletonData->findSkin("skin-base"));
 	skin->addSkin(skeletonData->findSkin("nose/short"));
@@ -71,8 +71,8 @@ bool MixAndMatchExample::init () {
 
 	scheduleUpdate();
 
-	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
-	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
+	EventListenerTouchOneByOne *listener = EventListenerTouchOneByOne::create();
+	listener->onTouchBegan = [this](Touch *touch, cocos2d::Event *event) -> bool {
 		if (!skeletonNode->getDebugBonesEnabled())
 			skeletonNode->setDebugBonesEnabled(true);
 		else if (skeletonNode->getTimeScale() == 1)

+ 6 - 6
spine-cocos2dx/example/Classes/MixAndMatchExample.h

@@ -35,17 +35,17 @@
 
 class MixAndMatchExample : public cocos2d::LayerColor {
 public:
-	static cocos2d::Scene* scene ();
+	static cocos2d::Scene *scene();
 
 	CREATE_FUNC(MixAndMatchExample);
 
-	virtual bool init ();
-	
+	virtual bool init();
+
 	virtual ~MixAndMatchExample();
 
 private:
-	spine::SkeletonAnimation* skeletonNode;
-	spine::Skin* skin;
+	spine::SkeletonAnimation *skeletonNode;
+	spine::Skin *skin;
 };
 
-#endif // _MIXANDMATCHXAMPLE_H_
+#endif// _MIXANDMATCHXAMPLE_H_

+ 86 - 86
spine-cocos2dx/example/Classes/RaptorExample.cpp

@@ -1,86 +1,86 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include "RaptorExample.h"
-#include "TankExample.h"
-#include <spine/Extension.h>
-
-USING_NS_CC;
-using namespace spine;
-
-PowInterpolation pow2(2);
-PowOutInterpolation powOut2(2);
-SwirlVertexEffect effect(400, powOut2);
-
-Scene* RaptorExample::scene () {
-	Scene *scene = Scene::create();
-	scene->addChild(RaptorExample::create());
-	return scene;
-}
-
-bool RaptorExample::init () {
-	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
-
-	skeletonNode = SkeletonAnimation::createWithJsonFile("raptor-pro.json", "raptor.atlas", 0.5f);
-	skeletonNode->setAnimation(0, "walk", true);
-	skeletonNode->addAnimation(1, "gun-grab", false, 2);
-	skeletonNode->setTwoColorTint(true);
-	
-	effect.setCenterY(200);
-	swirlTime = 0;
-	
-	skeletonNode->setVertexEffect(&effect);
-
-	skeletonNode->setPosition(Vec2(_contentSize.width / 2, 20));
-	addChild(skeletonNode);
-
-	scheduleUpdate();
-	
-	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
-	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
-		if (!skeletonNode->getDebugBonesEnabled()) {
-			skeletonNode->setDebugBonesEnabled(true);
-			skeletonNode->setDebugMeshesEnabled(true);			
-		} else if (skeletonNode->getTimeScale() == 1)
-			skeletonNode->setTimeScale(0.3f);
-		else
-			Director::getInstance()->replaceScene(TankExample::scene());
-		return true;
-	};
-	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
-
-	return true;
-}
-
-void RaptorExample::update(float fDelta) {
-	swirlTime += fDelta;
-	float percent = spine::MathUtil::fmod(swirlTime, 2);
-	if (percent > 1) percent = 1 - (percent - 1);
-	effect.setAngle(pow2.interpolate(-60.0f, 60.0f, percent));
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "RaptorExample.h"
+#include "TankExample.h"
+#include <spine/Extension.h>
+
+USING_NS_CC;
+using namespace spine;
+
+PowInterpolation pow2(2);
+PowOutInterpolation powOut2(2);
+SwirlVertexEffect effect(400, powOut2);
+
+Scene *RaptorExample::scene() {
+	Scene *scene = Scene::create();
+	scene->addChild(RaptorExample::create());
+	return scene;
+}
+
+bool RaptorExample::init() {
+	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
+
+	skeletonNode = SkeletonAnimation::createWithJsonFile("raptor-pro.json", "raptor.atlas", 0.5f);
+	skeletonNode->setAnimation(0, "walk", true);
+	skeletonNode->addAnimation(1, "gun-grab", false, 2);
+	skeletonNode->setTwoColorTint(true);
+
+	effect.setCenterY(200);
+	swirlTime = 0;
+
+	skeletonNode->setVertexEffect(&effect);
+
+	skeletonNode->setPosition(Vec2(_contentSize.width / 2, 20));
+	addChild(skeletonNode);
+
+	scheduleUpdate();
+
+	EventListenerTouchOneByOne *listener = EventListenerTouchOneByOne::create();
+	listener->onTouchBegan = [this](Touch *touch, cocos2d::Event *event) -> bool {
+		if (!skeletonNode->getDebugBonesEnabled()) {
+			skeletonNode->setDebugBonesEnabled(true);
+			skeletonNode->setDebugMeshesEnabled(true);
+		} else if (skeletonNode->getTimeScale() == 1)
+			skeletonNode->setTimeScale(0.3f);
+		else
+			Director::getInstance()->replaceScene(TankExample::scene());
+		return true;
+	};
+	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
+
+	return true;
+}
+
+void RaptorExample::update(float fDelta) {
+	swirlTime += fDelta;
+	float percent = spine::MathUtil::fmod(swirlTime, 2);
+	if (percent > 1) percent = 1 - (percent - 1);
+	effect.setAngle(pow2.interpolate(-60.0f, 60.0f, percent));
+}

+ 51 - 51
spine-cocos2dx/example/Classes/RaptorExample.h

@@ -1,51 +1,51 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#ifndef _RAPTOREXAMPLE_H_
-#define _RAPTOREXAMPLE_H_
-
-#include "cocos2d.h"
-#include <spine/spine-cocos2dx.h>
-
-class RaptorExample : public cocos2d::LayerColor {
-public:
-	static cocos2d::Scene* scene ();
-
-	CREATE_FUNC(RaptorExample);
-
-	virtual bool init ();
-	
-	virtual void update(float fDelta);
-
-private:
-	spine::SkeletonAnimation* skeletonNode;
-	float swirlTime;
-};
-
-#endif // _RAPTOREXAMPLE_H_
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#ifndef _RAPTOREXAMPLE_H_
+#define _RAPTOREXAMPLE_H_
+
+#include "cocos2d.h"
+#include <spine/spine-cocos2dx.h>
+
+class RaptorExample : public cocos2d::LayerColor {
+public:
+	static cocos2d::Scene *scene();
+
+	CREATE_FUNC(RaptorExample);
+
+	virtual bool init();
+
+	virtual void update(float fDelta);
+
+private:
+	spine::SkeletonAnimation *skeletonNode;
+	float swirlTime;
+};
+
+#endif// _RAPTOREXAMPLE_H_

+ 94 - 94
spine-cocos2dx/example/Classes/SkeletonRendererSeparatorExample.cpp

@@ -1,94 +1,94 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include "SkeletonRendererSeparatorExample.h"
-#include "GoblinsExample.h"
-
-USING_NS_CC;
-using namespace spine;
-
-Scene* SkeletonRendererSeparatorExample::scene () {
-	Scene *scene = Scene::create();
-	scene->addChild(SkeletonRendererSeparatorExample::create());
-	return scene;
-}
-
-bool SkeletonRendererSeparatorExample::init () {
-	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
-
-	// Spineboy's back, which will manage the animation and GPU resources
-	// will render only the front slots of Spineboy
-	backNode = SkeletonAnimation::createWithJsonFile("spineboy-pro.json", "spineboy.atlas", 0.6f);
-	backNode->setMix("walk", "jump", 0.4);
-	backNode->setAnimation(0, "walk", true);
-	backNode->setSlotsRange(backNode->findSlot("rear-upper-arm")->getData().getIndex(), backNode->findSlot("rear-shin")->getData().getIndex());
-	backNode->setPosition(Vec2(_contentSize.width / 2, 20));
-
-	// A simple rectangle to go between the front and back slots of Spineboy
-	betweenNode = DrawNode::create();
-	Vec2 rect[4];
-	rect[0] = Vec2(0, 0);
-	rect[1] = Vec2(40, 0);
-	rect[2] = Vec2(40, 200);
-	rect[3] = Vec2(0, 200);
-	betweenNode->drawPolygon(rect, 4, Color4F(1, 0, 0, 1), 1, Color4F(1, 0, 0, 1));
-	betweenNode->setPosition(Vec2(_contentSize.width / 2 + 30, 20));
-	// Spineboy's front, doesn't manage any skeleton, animation or GPU resources, but simply
-	// renders the back slots of Spineboy. The skeleton, animatio state and GPU resources
-	// are shared with the front node!
-	frontNode = SkeletonRenderer::createWithSkeleton(backNode->getSkeleton());
-	frontNode->setSlotsRange(frontNode->findSlot("neck")->getData().getIndex(), -1);
-	frontNode->setPosition(Vec2(_contentSize.width / 2, 20));
-
-	// Add the front, between and back node in the correct order to this scene
-	addChild(backNode);
-	addChild(betweenNode);
-	addChild(frontNode);
-
-	scheduleUpdate();
-
-	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
-	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
-		if (!backNode->getDebugBonesEnabled())
-			backNode->setDebugBonesEnabled(true);
-		else if (backNode->getTimeScale() == 1)
-			backNode->setTimeScale(0.3f);
-		else
-			Director::getInstance()->replaceScene(GoblinsExample::scene());
-		return true;
-	};
-	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
-
-	return true;
-}
-
-void SkeletonRendererSeparatorExample::update (float deltaTime) {
-	// Test releasing memory.
-	// Director::getInstance()->replaceScene(SpineboyExample::scene());
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "SkeletonRendererSeparatorExample.h"
+#include "GoblinsExample.h"
+
+USING_NS_CC;
+using namespace spine;
+
+Scene *SkeletonRendererSeparatorExample::scene() {
+	Scene *scene = Scene::create();
+	scene->addChild(SkeletonRendererSeparatorExample::create());
+	return scene;
+}
+
+bool SkeletonRendererSeparatorExample::init() {
+	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
+
+	// Spineboy's back, which will manage the animation and GPU resources
+	// will render only the front slots of Spineboy
+	backNode = SkeletonAnimation::createWithJsonFile("spineboy-pro.json", "spineboy.atlas", 0.6f);
+	backNode->setMix("walk", "jump", 0.4);
+	backNode->setAnimation(0, "walk", true);
+	backNode->setSlotsRange(backNode->findSlot("rear-upper-arm")->getData().getIndex(), backNode->findSlot("rear-shin")->getData().getIndex());
+	backNode->setPosition(Vec2(_contentSize.width / 2, 20));
+
+	// A simple rectangle to go between the front and back slots of Spineboy
+	betweenNode = DrawNode::create();
+	Vec2 rect[4];
+	rect[0] = Vec2(0, 0);
+	rect[1] = Vec2(40, 0);
+	rect[2] = Vec2(40, 200);
+	rect[3] = Vec2(0, 200);
+	betweenNode->drawPolygon(rect, 4, Color4F(1, 0, 0, 1), 1, Color4F(1, 0, 0, 1));
+	betweenNode->setPosition(Vec2(_contentSize.width / 2 + 30, 20));
+	// Spineboy's front, doesn't manage any skeleton, animation or GPU resources, but simply
+	// renders the back slots of Spineboy. The skeleton, animatio state and GPU resources
+	// are shared with the front node!
+	frontNode = SkeletonRenderer::createWithSkeleton(backNode->getSkeleton());
+	frontNode->setSlotsRange(frontNode->findSlot("neck")->getData().getIndex(), -1);
+	frontNode->setPosition(Vec2(_contentSize.width / 2, 20));
+
+	// Add the front, between and back node in the correct order to this scene
+	addChild(backNode);
+	addChild(betweenNode);
+	addChild(frontNode);
+
+	scheduleUpdate();
+
+	EventListenerTouchOneByOne *listener = EventListenerTouchOneByOne::create();
+	listener->onTouchBegan = [this](Touch *touch, cocos2d::Event *event) -> bool {
+		if (!backNode->getDebugBonesEnabled())
+			backNode->setDebugBonesEnabled(true);
+		else if (backNode->getTimeScale() == 1)
+			backNode->setTimeScale(0.3f);
+		else
+			Director::getInstance()->replaceScene(GoblinsExample::scene());
+		return true;
+	};
+	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
+
+	return true;
+}
+
+void SkeletonRendererSeparatorExample::update(float deltaTime) {
+	// Test releasing memory.
+	// Director::getInstance()->replaceScene(SpineboyExample::scene());
+}

+ 52 - 53
spine-cocos2dx/example/Classes/SkeletonRendererSeparatorExample.h

@@ -1,53 +1,52 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#ifndef _SKELETONRENDERERSEPARATOREXAMPLE_H_
-#define _SKELETONRENDERERSEPARATOREXAMPLE_H_
-
-#include "cocos2d.h"
-#include <spine/spine-cocos2dx.h>
-
-class SkeletonRendererSeparatorExample : public cocos2d::LayerColor {
-public:
-	static cocos2d::Scene* scene ();
-
-	CREATE_FUNC (SkeletonRendererSeparatorExample);
-
-	virtual bool init ();
-
-	virtual void update (float deltaTime);
-
-private:
-	spine::SkeletonAnimation* backNode;
-	spine::SkeletonRenderer* frontNode;
-	cocos2d::DrawNode* betweenNode;
-	
-};
-
-#endif // _SKELETONRENDERERSEPARATOREXAMPLE_H_
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#ifndef _SKELETONRENDERERSEPARATOREXAMPLE_H_
+#define _SKELETONRENDERERSEPARATOREXAMPLE_H_
+
+#include "cocos2d.h"
+#include <spine/spine-cocos2dx.h>
+
+class SkeletonRendererSeparatorExample : public cocos2d::LayerColor {
+public:
+	static cocos2d::Scene *scene();
+
+	CREATE_FUNC(SkeletonRendererSeparatorExample);
+
+	virtual bool init();
+
+	virtual void update(float deltaTime);
+
+private:
+	spine::SkeletonAnimation *backNode;
+	spine::SkeletonRenderer *frontNode;
+	cocos2d::DrawNode *betweenNode;
+};
+
+#endif// _SKELETONRENDERERSEPARATOREXAMPLE_H_

+ 102 - 102
spine-cocos2dx/example/Classes/SpineboyExample.cpp

@@ -1,102 +1,102 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include "SpineboyExample.h"
-#include "SkeletonRendererSeparatorExample.h"
-
-USING_NS_CC;
-using namespace spine;
-
-Scene* SpineboyExample::scene () {
-	Scene *scene = Scene::create();
-	scene->addChild(SpineboyExample::create());
-	return scene;
-}
-
-bool SpineboyExample::init () {
-	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
-
-	skeletonNode = SkeletonAnimation::createWithJsonFile("spineboy-pro.json", "spineboy.atlas", 0.6f);
-
-    skeletonNode->setStartListener( [] (TrackEntry* entry) {
-		log("%d start: %s", entry->getTrackIndex(), entry->getAnimation()->getName().buffer());
-	});
-    skeletonNode->setInterruptListener( [] (TrackEntry* entry) {
-        log("%d interrupt", entry->getTrackIndex());
-    });
-	skeletonNode->setEndListener( [] (TrackEntry* entry) {
-		log("%d end", entry->getTrackIndex());
-	});
-	skeletonNode->setCompleteListener( [] (TrackEntry* entry) {
-		log("%d complete", entry->getTrackIndex());
-	});
-    skeletonNode->setDisposeListener( [] (TrackEntry* entry) {
-        log("%d dispose", entry->getTrackIndex());
-    });
-	skeletonNode->setEventListener( [] (TrackEntry* entry, spine::Event* event) {
-		log("%d event: %s, %d, %f, %s", entry->getTrackIndex(), event->getData().getName().buffer(), event->getIntValue(), event->getFloatValue(), event->getStringValue().buffer());
-	});
-
-	skeletonNode->setMix("walk", "jump", 0.4);
-	skeletonNode->setMix("jump", "run", 0.4);
-	skeletonNode->setAnimation(0, "walk", true);
-	TrackEntry* jumpEntry = skeletonNode->addAnimation(0, "jump", false, 1);
-	skeletonNode->addAnimation(0, "run", true);
-
-	skeletonNode->setTrackStartListener(jumpEntry, [] (TrackEntry* entry) {
-		log("jumped!");
-	});
-
-	// skeletonNode->addAnimation(1, "test", true);
-	// skeletonNode->runAction(RepeatForever::create(Sequence::create(FadeOut::create(1), FadeIn::create(1), DelayTime::create(5), NULL)));
-
-	skeletonNode->setPosition(Vec2(_contentSize.width / 2, 20));
-	addChild(skeletonNode);
-
-	scheduleUpdate();
-
-	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
-	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
-		if (!skeletonNode->getDebugBonesEnabled())
-			skeletonNode->setDebugBonesEnabled(true);
-		else if (skeletonNode->getTimeScale() == 1)
-			skeletonNode->setTimeScale(0.3f);
-		else
-			Director::getInstance()->replaceScene(SkeletonRendererSeparatorExample::scene());
-		return true;
-	};
-	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
-
-	return true;
-}
-
-void SpineboyExample::update (float deltaTime) {
-	// Test releasing memory.
-	// Director::getInstance()->replaceScene(SpineboyExample::scene());
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "SpineboyExample.h"
+#include "SkeletonRendererSeparatorExample.h"
+
+USING_NS_CC;
+using namespace spine;
+
+Scene *SpineboyExample::scene() {
+	Scene *scene = Scene::create();
+	scene->addChild(SpineboyExample::create());
+	return scene;
+}
+
+bool SpineboyExample::init() {
+	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
+
+	skeletonNode = SkeletonAnimation::createWithJsonFile("spineboy-pro.json", "spineboy.atlas", 0.6f);
+
+	skeletonNode->setStartListener([](TrackEntry *entry) {
+		log("%d start: %s", entry->getTrackIndex(), entry->getAnimation()->getName().buffer());
+	});
+	skeletonNode->setInterruptListener([](TrackEntry *entry) {
+		log("%d interrupt", entry->getTrackIndex());
+	});
+	skeletonNode->setEndListener([](TrackEntry *entry) {
+		log("%d end", entry->getTrackIndex());
+	});
+	skeletonNode->setCompleteListener([](TrackEntry *entry) {
+		log("%d complete", entry->getTrackIndex());
+	});
+	skeletonNode->setDisposeListener([](TrackEntry *entry) {
+		log("%d dispose", entry->getTrackIndex());
+	});
+	skeletonNode->setEventListener([](TrackEntry *entry, spine::Event *event) {
+		log("%d event: %s, %d, %f, %s", entry->getTrackIndex(), event->getData().getName().buffer(), event->getIntValue(), event->getFloatValue(), event->getStringValue().buffer());
+	});
+
+	skeletonNode->setMix("walk", "jump", 0.4);
+	skeletonNode->setMix("jump", "run", 0.4);
+	skeletonNode->setAnimation(0, "walk", true);
+	TrackEntry *jumpEntry = skeletonNode->addAnimation(0, "jump", false, 1);
+	skeletonNode->addAnimation(0, "run", true);
+
+	skeletonNode->setTrackStartListener(jumpEntry, [](TrackEntry *entry) {
+		log("jumped!");
+	});
+
+	// skeletonNode->addAnimation(1, "test", true);
+	// skeletonNode->runAction(RepeatForever::create(Sequence::create(FadeOut::create(1), FadeIn::create(1), DelayTime::create(5), NULL)));
+
+	skeletonNode->setPosition(Vec2(_contentSize.width / 2, 20));
+	addChild(skeletonNode);
+
+	scheduleUpdate();
+
+	EventListenerTouchOneByOne *listener = EventListenerTouchOneByOne::create();
+	listener->onTouchBegan = [this](Touch *touch, cocos2d::Event *event) -> bool {
+		if (!skeletonNode->getDebugBonesEnabled())
+			skeletonNode->setDebugBonesEnabled(true);
+		else if (skeletonNode->getTimeScale() == 1)
+			skeletonNode->setTimeScale(0.3f);
+		else
+			Director::getInstance()->replaceScene(SkeletonRendererSeparatorExample::scene());
+		return true;
+	};
+	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
+
+	return true;
+}
+
+void SpineboyExample::update(float deltaTime) {
+	// Test releasing memory.
+	// Director::getInstance()->replaceScene(SpineboyExample::scene());
+}

+ 50 - 50
spine-cocos2dx/example/Classes/SpineboyExample.h

@@ -1,50 +1,50 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#ifndef _SPINEBOYEXAMPLE_H_
-#define _SPINEBOYEXAMPLE_H_
-
-#include "cocos2d.h"
-#include <spine/spine-cocos2dx.h>
-
-class SpineboyExample : public cocos2d::LayerColor {
-public:
-	static cocos2d::Scene* scene ();
-
-	CREATE_FUNC (SpineboyExample);
-
-	virtual bool init ();
-
-	virtual void update (float deltaTime);
-
-private:
-	spine::SkeletonAnimation* skeletonNode;
-};
-
-#endif // _SPINEBOYEXAMPLE_H_
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#ifndef _SPINEBOYEXAMPLE_H_
+#define _SPINEBOYEXAMPLE_H_
+
+#include "cocos2d.h"
+#include <spine/spine-cocos2dx.h>
+
+class SpineboyExample : public cocos2d::LayerColor {
+public:
+	static cocos2d::Scene *scene();
+
+	CREATE_FUNC(SpineboyExample);
+
+	virtual bool init();
+
+	virtual void update(float deltaTime);
+
+private:
+	spine::SkeletonAnimation *skeletonNode;
+};
+
+#endif// _SPINEBOYEXAMPLE_H_

+ 66 - 66
spine-cocos2dx/example/Classes/TankExample.cpp

@@ -1,66 +1,66 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include "TankExample.h"
-#include "CoinExample.h"
-
-USING_NS_CC;
-using namespace spine;
-
-Scene* TankExample::scene () {
-	Scene *scene = Scene::create();
-	scene->addChild(TankExample::create());
-	return scene;
-}
-
-bool TankExample::init () {
-	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
-
-	skeletonNode = SkeletonAnimation::createWithBinaryFile("tank-pro.skel", "tank.atlas", 0.5f);
-	skeletonNode->setAnimation(0, "shoot", true);
-
-	skeletonNode->setPosition(Vec2(_contentSize.width / 2 + 400, 20));
-	addChild(skeletonNode);
-
-	scheduleUpdate();
-
-	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
-	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
-		if (!skeletonNode->getDebugBonesEnabled())
-			skeletonNode->setDebugBonesEnabled(true);
-		else if (skeletonNode->getTimeScale() == 1)
-			skeletonNode->setTimeScale(0.3f);
-		else
-			Director::getInstance()->replaceScene(CoinExample::scene());
-		return true;
-	};
-	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
-
-	return true;
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "TankExample.h"
+#include "CoinExample.h"
+
+USING_NS_CC;
+using namespace spine;
+
+Scene *TankExample::scene() {
+	Scene *scene = Scene::create();
+	scene->addChild(TankExample::create());
+	return scene;
+}
+
+bool TankExample::init() {
+	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
+
+	skeletonNode = SkeletonAnimation::createWithBinaryFile("tank-pro.skel", "tank.atlas", 0.5f);
+	skeletonNode->setAnimation(0, "shoot", true);
+
+	skeletonNode->setPosition(Vec2(_contentSize.width / 2 + 400, 20));
+	addChild(skeletonNode);
+
+	scheduleUpdate();
+
+	EventListenerTouchOneByOne *listener = EventListenerTouchOneByOne::create();
+	listener->onTouchBegan = [this](Touch *touch, cocos2d::Event *event) -> bool {
+		if (!skeletonNode->getDebugBonesEnabled())
+			skeletonNode->setDebugBonesEnabled(true);
+		else if (skeletonNode->getTimeScale() == 1)
+			skeletonNode->setTimeScale(0.3f);
+		else
+			Director::getInstance()->replaceScene(CoinExample::scene());
+		return true;
+	};
+	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
+
+	return true;
+}

+ 48 - 48
spine-cocos2dx/example/Classes/TankExample.h

@@ -1,48 +1,48 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#ifndef _TANKEXAMPLE_H_
-#define _TANKEXAMPLE_H_
-
-#include "cocos2d.h"
-#include <spine/spine-cocos2dx.h>
-
-class TankExample : public cocos2d::LayerColor {
-public:
-	static cocos2d::Scene* scene ();
-
-	CREATE_FUNC(TankExample);
-
-	virtual bool init ();
-
-private:
-	spine::SkeletonAnimation* skeletonNode;
-};
-
-#endif // _TANKEXAMPLE_H_
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#ifndef _TANKEXAMPLE_H_
+#define _TANKEXAMPLE_H_
+
+#include "cocos2d.h"
+#include <spine/spine-cocos2dx.h>
+
+class TankExample : public cocos2d::LayerColor {
+public:
+	static cocos2d::Scene *scene();
+
+	CREATE_FUNC(TankExample);
+
+	virtual bool init();
+
+private:
+	spine::SkeletonAnimation *skeletonNode;
+};
+
+#endif// _TANKEXAMPLE_H_

+ 6 - 6
spine-cocos2dx/example/proj.android/jni/hellocpp/main.cpp

@@ -1,15 +1,15 @@
 #include "AppDelegate.h"
 #include "cocos2d.h"
 #include "platform/android/jni/JniHelper.h"
-#include <jni.h>
 #include <android/log.h>
+#include <jni.h>
 
-#define  LOG_TAG    "main"
-#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
+#define LOG_TAG "main"
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
 
 using namespace cocos2d;
 
-void cocos_android_app_init (JNIEnv* env) {
-    LOGD("cocos_android_app_init");
-    AppDelegate *pAppDelegate = new AppDelegate();
+void cocos_android_app_init(JNIEnv *env) {
+	LOGD("cocos_android_app_init");
+	AppDelegate *pAppDelegate = new AppDelegate();
 }

+ 2 - 3
spine-cocos2dx/example/proj.ios_mac/ios/AppController.h

@@ -3,10 +3,9 @@
 @class RootViewController;
 
 @interface AppController : NSObject <UIApplicationDelegate> {
-    UIWindow *window;
+	UIWindow *window;
 }
 
-@property(nonatomic, readonly) RootViewController* viewController;
+@property(nonatomic, readonly) RootViewController *viewController;
 
 @end
-

+ 1 - 2
spine-cocos2dx/example/proj.ios_mac/ios/RootViewController.h

@@ -27,8 +27,7 @@
 
 
 @interface RootViewController : UIViewController {
-
 }
-- (BOOL) prefersStatusBarHidden;
+- (BOOL)prefersStatusBarHidden;
 
 @end

+ 3 - 4
spine-cocos2dx/example/proj.ios_mac/mac/main.cpp

@@ -27,8 +27,7 @@
 
 USING_NS_CC;
 
-int main(int argc, char *argv[])
-{
-    AppDelegate app;
-    return Application::getInstance()->run();
+int main(int argc, char *argv[]) {
+	AppDelegate app;
+	return Application::getInstance()->run();
 }

+ 6 - 7
spine-cocos2dx/example/proj.linux/main.cpp

@@ -1,15 +1,14 @@
 #include "../Classes/AppDelegate.h"
 
-#include <stdlib.h>
 #include <stdio.h>
-#include <unistd.h>
+#include <stdlib.h>
 #include <string>
+#include <unistd.h>
 
 USING_NS_CC;
 
-int main(int argc, char **argv)
-{
-    // create the application instance
-    AppDelegate app;
-    return Application::getInstance()->run();
+int main(int argc, char **argv) {
+	// create the application instance
+	AppDelegate app;
+	return Application::getInstance()->run();
 }

+ 8 - 9
spine-cocos2dx/example/proj.win32/main.cpp

@@ -3,14 +3,13 @@
 #include "cocos2d.h"
 
 int wWinMain(HINSTANCE hInstance,
-                       HINSTANCE hPrevInstance,
-                       LPTSTR    lpCmdLine,
-                       int       nCmdShow)
-{
-    UNREFERENCED_PARAMETER(hPrevInstance);
-    UNREFERENCED_PARAMETER(lpCmdLine);
+			 HINSTANCE hPrevInstance,
+			 LPTSTR lpCmdLine,
+			 int nCmdShow) {
+	UNREFERENCED_PARAMETER(hPrevInstance);
+	UNREFERENCED_PARAMETER(lpCmdLine);
 
-    // create the application instance
-    AppDelegate app;
-    return cocos2d::Application::getInstance()->run();
+	// create the application instance
+	AppDelegate app;
+	return cocos2d::Application::getInstance()->run();
 }

+ 3 - 3
spine-cocos2dx/example/proj.win32/main.h

@@ -1,13 +1,13 @@
 #ifndef __MAIN_H__
 #define __MAIN_H__
 
-#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
+#define WIN32_LEAN_AND_MEAN// Exclude rarely-used stuff from Windows headers
 
 // Windows Header Files:
-#include <windows.h>
 #include <tchar.h>
+#include <windows.h>
 
 // C RunTime Header Files
 #include "platform/CCStdC.h"
 
-#endif    // __MAIN_H__
+#endif// __MAIN_H__

+ 8 - 8
spine-cocos2dx/example/proj.win32/resource.h

@@ -3,18 +3,18 @@
 // Used by game.RC
 //
 
-#define IDS_PROJNAME                100
-#define IDR_TESTJS    100
+#define IDS_PROJNAME 100
+#define IDR_TESTJS 100
 
-#define ID_FILE_NEW_WINDOW            32771
+#define ID_FILE_NEW_WINDOW 32771
 
 // Next default values for new objects
-// 
+//
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
-#define _APS_NEXT_RESOURCE_VALUE    201
-#define _APS_NEXT_CONTROL_VALUE        1000
-#define _APS_NEXT_SYMED_VALUE        101
-#define _APS_NEXT_COMMAND_VALUE        32775
+#define _APS_NEXT_RESOURCE_VALUE 201
+#define _APS_NEXT_CONTROL_VALUE 1000
+#define _APS_NEXT_SYMED_VALUE 101
+#define _APS_NEXT_COMMAND_VALUE 32775
 #endif
 #endif

+ 53 - 53
spine-cocos2dx/src/spine/AttachmentVertices.cpp

@@ -1,53 +1,53 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/AttachmentVertices.h>
-
-USING_NS_CC;
-
-namespace spine {
-
-AttachmentVertices::AttachmentVertices (Texture2D* texture, int verticesCount, unsigned short* triangles, int trianglesCount) {
-	_texture = texture;
-	if (_texture) _texture->retain();
-
-	_triangles = new TrianglesCommand::Triangles();
-	_triangles->verts = new V3F_C4B_T2F[verticesCount];
-	_triangles->vertCount = verticesCount;
-	_triangles->indices = triangles;
-	_triangles->indexCount = trianglesCount;
-}
-
-AttachmentVertices::~AttachmentVertices () {
-	delete [] _triangles->verts;
-	delete _triangles;
-	if (_texture) _texture->release();
-}
-
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/AttachmentVertices.h>
+
+USING_NS_CC;
+
+namespace spine {
+
+	AttachmentVertices::AttachmentVertices(Texture2D *texture, int verticesCount, unsigned short *triangles, int trianglesCount) {
+		_texture = texture;
+		if (_texture) _texture->retain();
+
+		_triangles = new TrianglesCommand::Triangles();
+		_triangles->verts = new V3F_C4B_T2F[verticesCount];
+		_triangles->vertCount = verticesCount;
+		_triangles->indices = triangles;
+		_triangles->indexCount = trianglesCount;
+	}
+
+	AttachmentVertices::~AttachmentVertices() {
+		delete[] _triangles->verts;
+		delete _triangles;
+		if (_texture) _texture->release();
+	}
+
+}// namespace spine

+ 48 - 48
spine-cocos2dx/src/spine/AttachmentVertices.h

@@ -1,48 +1,48 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#ifndef SPINE_ATTACHMENTVERTICES_H_
-#define SPINE_ATTACHMENTVERTICES_H_
-
-#include "cocos2d.h"
-
-namespace spine {
-
-class AttachmentVertices {
-public:
-	AttachmentVertices (cocos2d::Texture2D* texture, int verticesCount, unsigned short* triangles, int trianglesCount);
-	virtual ~AttachmentVertices ();
-
-	cocos2d::Texture2D* _texture;
-	cocos2d::TrianglesCommand::Triangles* _triangles;
-};
-
-}
-
-#endif /* SPINE_ATTACHMENTVERTICES_H_ */
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#ifndef SPINE_ATTACHMENTVERTICES_H_
+#define SPINE_ATTACHMENTVERTICES_H_
+
+#include "cocos2d.h"
+
+namespace spine {
+
+	class AttachmentVertices {
+	public:
+		AttachmentVertices(cocos2d::Texture2D *texture, int verticesCount, unsigned short *triangles, int trianglesCount);
+		virtual ~AttachmentVertices();
+
+		cocos2d::Texture2D *_texture;
+		cocos2d::TrianglesCommand::Triangles *_triangles;
+	};
+
+}// namespace spine
+
+#endif /* SPINE_ATTACHMENTVERTICES_H_ */

+ 327 - 327
spine-cocos2dx/src/spine/SkeletonAnimation.cpp

@@ -1,327 +1,327 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/SkeletonAnimation.h>
-#include <spine/spine-cocos2dx.h>
-#include <spine/Extension.h>
-#include <algorithm>
-
-USING_NS_CC;
-using std::min;
-using std::max;
-using std::vector;
-
-namespace spine {
-
-typedef struct _TrackEntryListeners {
-	StartListener startListener;
-	InterruptListener interruptListener;
-	EndListener endListener;
-	DisposeListener disposeListener;
-	CompleteListener completeListener;
-	EventListener eventListener;
-} _TrackEntryListeners;
-
-void animationCallback (AnimationState* state, EventType type, TrackEntry* entry, Event* event) {
-	((SkeletonAnimation*)state->getRendererObject())->onAnimationStateEvent(entry, type, event);
-}
-
-void trackEntryCallback (AnimationState* state, EventType type, TrackEntry* entry, Event* event) {
-	((SkeletonAnimation*)state->getRendererObject())->onTrackEntryEvent(entry, type, event);
-	if (type == EventType_Dispose) {
-		if (entry->getRendererObject()) {
-			delete (spine::_TrackEntryListeners*)entry->getRendererObject();
-			entry->setRendererObject(NULL);
-		}
-	}
-}
-
-static _TrackEntryListeners* getListeners (TrackEntry* entry) {
-	if (!entry->getRendererObject()) {
-		entry->setRendererObject(new spine::_TrackEntryListeners());
-		entry->setListener(trackEntryCallback);
-	}
-	return (_TrackEntryListeners*)entry->getRendererObject();
-}
-
-//
-
-SkeletonAnimation* SkeletonAnimation::createWithData (SkeletonData* skeletonData, bool ownsSkeletonData) {
-	SkeletonAnimation* node = new SkeletonAnimation();
-	node->initWithData(skeletonData, ownsSkeletonData);
-	node->autorelease();
-	return node;
-}
-
-SkeletonAnimation* SkeletonAnimation::createWithJsonFile (const std::string& skeletonJsonFile, Atlas* atlas, float scale) {
-	SkeletonAnimation* node = new SkeletonAnimation();
-	node->initWithJsonFile(skeletonJsonFile, atlas, scale);
-	node->autorelease();
-	return node;
-}
-
-SkeletonAnimation* SkeletonAnimation::createWithJsonFile (const std::string& skeletonJsonFile, const std::string& atlasFile, float scale) {
-	SkeletonAnimation* node = new SkeletonAnimation();
-	node->initWithJsonFile(skeletonJsonFile, atlasFile, scale);
-	node->autorelease();
-	return node;
-}
-
-SkeletonAnimation* SkeletonAnimation::createWithBinaryFile (const std::string& skeletonBinaryFile, Atlas* atlas, float scale) {
-	SkeletonAnimation* node = new SkeletonAnimation();
-	node->initWithBinaryFile(skeletonBinaryFile, atlas, scale);
-	node->autorelease();
-	return node;
-}
-
-SkeletonAnimation* SkeletonAnimation::createWithBinaryFile (const std::string& skeletonBinaryFile, const std::string& atlasFile, float scale) {
-	SkeletonAnimation* node = new SkeletonAnimation();
-	node->initWithBinaryFile(skeletonBinaryFile, atlasFile, scale);
-	node->autorelease();
-	return node;
-}
-
-
-void SkeletonAnimation::initialize () {
-	super::initialize();
-
-	_ownsAnimationStateData = true;
-	_updateOnlyIfVisible = false;
-	_state = new (__FILE__, __LINE__) AnimationState(new (__FILE__, __LINE__) AnimationStateData(_skeleton->getData()));
-	_state->setRendererObject(this);
-	_state->setListener(animationCallback);
-
-	_firstDraw = true;
-}
-
-SkeletonAnimation::SkeletonAnimation ()
-		: SkeletonRenderer() {
-}
-
-SkeletonAnimation::~SkeletonAnimation () {
-	if (_ownsAnimationStateData) delete _state->getData();
-	delete _state;
-}
-
-void SkeletonAnimation::update (float deltaTime) {
-	if (_updateOnlyIfVisible && !isVisible()) return;
-
-	super::update(deltaTime);
-
-	deltaTime *= _timeScale;
-	if (_preUpdateListener) _preUpdateListener(this);
-	_state->update(deltaTime);
-	_state->apply(*_skeleton);
-	_skeleton->updateWorldTransform();
-	if (_postUpdateListener) _postUpdateListener(this);
-}
-
-void SkeletonAnimation::draw(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t transformFlags) {
-	if (_firstDraw) {
-		_firstDraw = false;
-		update(0);
-	}
-	super::draw(renderer, transform, transformFlags);
-}
-
-void SkeletonAnimation::setAnimationStateData (AnimationStateData* stateData) {
-	CCASSERT(stateData, "stateData cannot be null.");
-
-	if (_ownsAnimationStateData) delete _state->getData();
-	delete _state;
-
-	_ownsAnimationStateData = false;
-	_state = new (__FILE__, __LINE__) AnimationState(stateData);
-	_state->setRendererObject(this);
-	_state->setListener(animationCallback);
-}
-
-void SkeletonAnimation::setMix (const std::string& fromAnimation, const std::string& toAnimation, float duration) {
-	_state->getData()->setMix(fromAnimation.c_str(), toAnimation.c_str(), duration);
-}
-
-TrackEntry* SkeletonAnimation::setAnimation (int trackIndex, const std::string& name, bool loop) {
-	Animation* animation = _skeleton->getData()->findAnimation(name.c_str());
-	if (!animation) {
-		log("Spine: Animation not found: %s", name.c_str());
-		return 0;
-	}
-	return _state->setAnimation(trackIndex, animation, loop);
-}
-
-TrackEntry* SkeletonAnimation::addAnimation (int trackIndex, const std::string& name, bool loop, float delay) {
-	Animation* animation = _skeleton->getData()->findAnimation(name.c_str());
-	if (!animation) {
-		log("Spine: Animation not found: %s", name.c_str());
-		return 0;
-	}
-	return _state->addAnimation(trackIndex, animation, loop, delay);
-}
-
-TrackEntry* SkeletonAnimation::setEmptyAnimation (int trackIndex, float mixDuration) {
-	return _state->setEmptyAnimation(trackIndex, mixDuration);
-}
-
-void SkeletonAnimation::setEmptyAnimations (float mixDuration) {
-	_state->setEmptyAnimations(mixDuration);
-}
-
-TrackEntry* SkeletonAnimation::addEmptyAnimation (int trackIndex, float mixDuration, float delay) {
-	return _state->addEmptyAnimation(trackIndex, mixDuration, delay);
-}
-
-Animation* SkeletonAnimation::findAnimation(const std::string& name) const {
-	return _skeleton->getData()->findAnimation(name.c_str());
-}
-
-TrackEntry* SkeletonAnimation::getCurrent (int trackIndex) {
-	return _state->getCurrent(trackIndex);
-}
-
-void SkeletonAnimation::clearTracks () {
-	_state->clearTracks();
-}
-
-void SkeletonAnimation::clearTrack (int trackIndex) {
-	_state->clearTrack(trackIndex);
-}
-
-void SkeletonAnimation::onAnimationStateEvent (TrackEntry* entry, EventType type, Event* event) {
-	switch (type) {
-	case EventType_Start:
-		if (_startListener) _startListener(entry);
-		break;
-	case EventType_Interrupt:
-		if (_interruptListener) _interruptListener(entry);
-		break;
-	case EventType_End:
-		if (_endListener) _endListener(entry);
-		break;
-	case EventType_Dispose:
-		if (_disposeListener) _disposeListener(entry);
-		break;
-	case EventType_Complete:
-		if (_completeListener) _completeListener(entry);
-		break;
-	case EventType_Event:
-		if (_eventListener) _eventListener(entry, event);
-		break;
-	}
-}
-
-void SkeletonAnimation::onTrackEntryEvent (TrackEntry* entry, EventType type, Event* event) {
-	if (!entry->getRendererObject()) return;
-	_TrackEntryListeners* listeners = (_TrackEntryListeners*)entry->getRendererObject();
-	switch (type) {
-	case EventType_Start:
-		if (listeners->startListener) listeners->startListener(entry);
-		break;
-	case EventType_Interrupt:
-		if (listeners->interruptListener) listeners->interruptListener(entry);
-		break;
-	case EventType_End:
-		if (listeners->endListener) listeners->endListener(entry);
-		break;
-	case EventType_Dispose:
-		if (listeners->disposeListener) listeners->disposeListener(entry);
-		break;
-	case EventType_Complete:
-		if (listeners->completeListener) listeners->completeListener(entry);
-		break;
-	case EventType_Event:
-		if (listeners->eventListener) listeners->eventListener(entry, event);
-		break;
-	}
-}
-
-void SkeletonAnimation::setStartListener (const StartListener& listener) {
-	_startListener = listener;
-}
-
-void SkeletonAnimation::setInterruptListener (const InterruptListener& listener) {
-	_interruptListener = listener;
-}
-
-void SkeletonAnimation::setEndListener (const EndListener& listener) {
-	_endListener = listener;
-}
-
-void SkeletonAnimation::setDisposeListener (const DisposeListener& listener) {
-	_disposeListener = listener;
-}
-
-void SkeletonAnimation::setCompleteListener (const CompleteListener& listener) {
-	_completeListener = listener;
-}
-
-void SkeletonAnimation::setEventListener (const EventListener& listener) {
-	_eventListener = listener;
-}
-
-void SkeletonAnimation::setPreUpdateWorldTransformsListener(const UpdateWorldTransformsListener &listener) {
-	_preUpdateListener = listener;
-}
-
-void SkeletonAnimation::setPostUpdateWorldTransformsListener(const UpdateWorldTransformsListener &listener) {
-	_postUpdateListener = listener;
-}
-
-void SkeletonAnimation::setTrackStartListener (TrackEntry* entry, const StartListener& listener) {
-	getListeners(entry)->startListener = listener;
-}
-
-void SkeletonAnimation::setTrackInterruptListener (TrackEntry* entry, const InterruptListener& listener) {
-	getListeners(entry)->interruptListener = listener;
-}
-
-void SkeletonAnimation::setTrackEndListener (TrackEntry* entry, const EndListener& listener) {
-	getListeners(entry)->endListener = listener;
-}
-
-void SkeletonAnimation::setTrackDisposeListener (TrackEntry* entry, const DisposeListener& listener) {
-	getListeners(entry)->disposeListener = listener;
-}
-
-void SkeletonAnimation::setTrackCompleteListener (TrackEntry* entry, const CompleteListener& listener) {
-	getListeners(entry)->completeListener = listener;
-}
-
-void SkeletonAnimation::setTrackEventListener (TrackEntry* entry, const EventListener& listener) {
-	getListeners(entry)->eventListener = listener;
-}
-
-AnimationState* SkeletonAnimation::getState() const {
-	return _state;
-}
-
-void SkeletonAnimation::setUpdateOnlyIfVisible(bool status) {
-	_updateOnlyIfVisible = status;
-}
-
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <algorithm>
+#include <spine/Extension.h>
+#include <spine/SkeletonAnimation.h>
+#include <spine/spine-cocos2dx.h>
+
+USING_NS_CC;
+using std::max;
+using std::min;
+using std::vector;
+
+namespace spine {
+
+	typedef struct _TrackEntryListeners {
+		StartListener startListener;
+		InterruptListener interruptListener;
+		EndListener endListener;
+		DisposeListener disposeListener;
+		CompleteListener completeListener;
+		EventListener eventListener;
+	} _TrackEntryListeners;
+
+	void animationCallback(AnimationState *state, EventType type, TrackEntry *entry, Event *event) {
+		((SkeletonAnimation *) state->getRendererObject())->onAnimationStateEvent(entry, type, event);
+	}
+
+	void trackEntryCallback(AnimationState *state, EventType type, TrackEntry *entry, Event *event) {
+		((SkeletonAnimation *) state->getRendererObject())->onTrackEntryEvent(entry, type, event);
+		if (type == EventType_Dispose) {
+			if (entry->getRendererObject()) {
+				delete (spine::_TrackEntryListeners *) entry->getRendererObject();
+				entry->setRendererObject(NULL);
+			}
+		}
+	}
+
+	static _TrackEntryListeners *getListeners(TrackEntry *entry) {
+		if (!entry->getRendererObject()) {
+			entry->setRendererObject(new spine::_TrackEntryListeners());
+			entry->setListener(trackEntryCallback);
+		}
+		return (_TrackEntryListeners *) entry->getRendererObject();
+	}
+
+	//
+
+	SkeletonAnimation *SkeletonAnimation::createWithData(SkeletonData *skeletonData, bool ownsSkeletonData) {
+		SkeletonAnimation *node = new SkeletonAnimation();
+		node->initWithData(skeletonData, ownsSkeletonData);
+		node->autorelease();
+		return node;
+	}
+
+	SkeletonAnimation *SkeletonAnimation::createWithJsonFile(const std::string &skeletonJsonFile, Atlas *atlas, float scale) {
+		SkeletonAnimation *node = new SkeletonAnimation();
+		node->initWithJsonFile(skeletonJsonFile, atlas, scale);
+		node->autorelease();
+		return node;
+	}
+
+	SkeletonAnimation *SkeletonAnimation::createWithJsonFile(const std::string &skeletonJsonFile, const std::string &atlasFile, float scale) {
+		SkeletonAnimation *node = new SkeletonAnimation();
+		node->initWithJsonFile(skeletonJsonFile, atlasFile, scale);
+		node->autorelease();
+		return node;
+	}
+
+	SkeletonAnimation *SkeletonAnimation::createWithBinaryFile(const std::string &skeletonBinaryFile, Atlas *atlas, float scale) {
+		SkeletonAnimation *node = new SkeletonAnimation();
+		node->initWithBinaryFile(skeletonBinaryFile, atlas, scale);
+		node->autorelease();
+		return node;
+	}
+
+	SkeletonAnimation *SkeletonAnimation::createWithBinaryFile(const std::string &skeletonBinaryFile, const std::string &atlasFile, float scale) {
+		SkeletonAnimation *node = new SkeletonAnimation();
+		node->initWithBinaryFile(skeletonBinaryFile, atlasFile, scale);
+		node->autorelease();
+		return node;
+	}
+
+
+	void SkeletonAnimation::initialize() {
+		super::initialize();
+
+		_ownsAnimationStateData = true;
+		_updateOnlyIfVisible = false;
+		_state = new (__FILE__, __LINE__) AnimationState(new (__FILE__, __LINE__) AnimationStateData(_skeleton->getData()));
+		_state->setRendererObject(this);
+		_state->setListener(animationCallback);
+
+		_firstDraw = true;
+	}
+
+	SkeletonAnimation::SkeletonAnimation()
+		: SkeletonRenderer() {
+	}
+
+	SkeletonAnimation::~SkeletonAnimation() {
+		if (_ownsAnimationStateData) delete _state->getData();
+		delete _state;
+	}
+
+	void SkeletonAnimation::update(float deltaTime) {
+		if (_updateOnlyIfVisible && !isVisible()) return;
+
+		super::update(deltaTime);
+
+		deltaTime *= _timeScale;
+		if (_preUpdateListener) _preUpdateListener(this);
+		_state->update(deltaTime);
+		_state->apply(*_skeleton);
+		_skeleton->updateWorldTransform();
+		if (_postUpdateListener) _postUpdateListener(this);
+	}
+
+	void SkeletonAnimation::draw(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t transformFlags) {
+		if (_firstDraw) {
+			_firstDraw = false;
+			update(0);
+		}
+		super::draw(renderer, transform, transformFlags);
+	}
+
+	void SkeletonAnimation::setAnimationStateData(AnimationStateData *stateData) {
+		CCASSERT(stateData, "stateData cannot be null.");
+
+		if (_ownsAnimationStateData) delete _state->getData();
+		delete _state;
+
+		_ownsAnimationStateData = false;
+		_state = new (__FILE__, __LINE__) AnimationState(stateData);
+		_state->setRendererObject(this);
+		_state->setListener(animationCallback);
+	}
+
+	void SkeletonAnimation::setMix(const std::string &fromAnimation, const std::string &toAnimation, float duration) {
+		_state->getData()->setMix(fromAnimation.c_str(), toAnimation.c_str(), duration);
+	}
+
+	TrackEntry *SkeletonAnimation::setAnimation(int trackIndex, const std::string &name, bool loop) {
+		Animation *animation = _skeleton->getData()->findAnimation(name.c_str());
+		if (!animation) {
+			log("Spine: Animation not found: %s", name.c_str());
+			return 0;
+		}
+		return _state->setAnimation(trackIndex, animation, loop);
+	}
+
+	TrackEntry *SkeletonAnimation::addAnimation(int trackIndex, const std::string &name, bool loop, float delay) {
+		Animation *animation = _skeleton->getData()->findAnimation(name.c_str());
+		if (!animation) {
+			log("Spine: Animation not found: %s", name.c_str());
+			return 0;
+		}
+		return _state->addAnimation(trackIndex, animation, loop, delay);
+	}
+
+	TrackEntry *SkeletonAnimation::setEmptyAnimation(int trackIndex, float mixDuration) {
+		return _state->setEmptyAnimation(trackIndex, mixDuration);
+	}
+
+	void SkeletonAnimation::setEmptyAnimations(float mixDuration) {
+		_state->setEmptyAnimations(mixDuration);
+	}
+
+	TrackEntry *SkeletonAnimation::addEmptyAnimation(int trackIndex, float mixDuration, float delay) {
+		return _state->addEmptyAnimation(trackIndex, mixDuration, delay);
+	}
+
+	Animation *SkeletonAnimation::findAnimation(const std::string &name) const {
+		return _skeleton->getData()->findAnimation(name.c_str());
+	}
+
+	TrackEntry *SkeletonAnimation::getCurrent(int trackIndex) {
+		return _state->getCurrent(trackIndex);
+	}
+
+	void SkeletonAnimation::clearTracks() {
+		_state->clearTracks();
+	}
+
+	void SkeletonAnimation::clearTrack(int trackIndex) {
+		_state->clearTrack(trackIndex);
+	}
+
+	void SkeletonAnimation::onAnimationStateEvent(TrackEntry *entry, EventType type, Event *event) {
+		switch (type) {
+			case EventType_Start:
+				if (_startListener) _startListener(entry);
+				break;
+			case EventType_Interrupt:
+				if (_interruptListener) _interruptListener(entry);
+				break;
+			case EventType_End:
+				if (_endListener) _endListener(entry);
+				break;
+			case EventType_Dispose:
+				if (_disposeListener) _disposeListener(entry);
+				break;
+			case EventType_Complete:
+				if (_completeListener) _completeListener(entry);
+				break;
+			case EventType_Event:
+				if (_eventListener) _eventListener(entry, event);
+				break;
+		}
+	}
+
+	void SkeletonAnimation::onTrackEntryEvent(TrackEntry *entry, EventType type, Event *event) {
+		if (!entry->getRendererObject()) return;
+		_TrackEntryListeners *listeners = (_TrackEntryListeners *) entry->getRendererObject();
+		switch (type) {
+			case EventType_Start:
+				if (listeners->startListener) listeners->startListener(entry);
+				break;
+			case EventType_Interrupt:
+				if (listeners->interruptListener) listeners->interruptListener(entry);
+				break;
+			case EventType_End:
+				if (listeners->endListener) listeners->endListener(entry);
+				break;
+			case EventType_Dispose:
+				if (listeners->disposeListener) listeners->disposeListener(entry);
+				break;
+			case EventType_Complete:
+				if (listeners->completeListener) listeners->completeListener(entry);
+				break;
+			case EventType_Event:
+				if (listeners->eventListener) listeners->eventListener(entry, event);
+				break;
+		}
+	}
+
+	void SkeletonAnimation::setStartListener(const StartListener &listener) {
+		_startListener = listener;
+	}
+
+	void SkeletonAnimation::setInterruptListener(const InterruptListener &listener) {
+		_interruptListener = listener;
+	}
+
+	void SkeletonAnimation::setEndListener(const EndListener &listener) {
+		_endListener = listener;
+	}
+
+	void SkeletonAnimation::setDisposeListener(const DisposeListener &listener) {
+		_disposeListener = listener;
+	}
+
+	void SkeletonAnimation::setCompleteListener(const CompleteListener &listener) {
+		_completeListener = listener;
+	}
+
+	void SkeletonAnimation::setEventListener(const EventListener &listener) {
+		_eventListener = listener;
+	}
+
+	void SkeletonAnimation::setPreUpdateWorldTransformsListener(const UpdateWorldTransformsListener &listener) {
+		_preUpdateListener = listener;
+	}
+
+	void SkeletonAnimation::setPostUpdateWorldTransformsListener(const UpdateWorldTransformsListener &listener) {
+		_postUpdateListener = listener;
+	}
+
+	void SkeletonAnimation::setTrackStartListener(TrackEntry *entry, const StartListener &listener) {
+		getListeners(entry)->startListener = listener;
+	}
+
+	void SkeletonAnimation::setTrackInterruptListener(TrackEntry *entry, const InterruptListener &listener) {
+		getListeners(entry)->interruptListener = listener;
+	}
+
+	void SkeletonAnimation::setTrackEndListener(TrackEntry *entry, const EndListener &listener) {
+		getListeners(entry)->endListener = listener;
+	}
+
+	void SkeletonAnimation::setTrackDisposeListener(TrackEntry *entry, const DisposeListener &listener) {
+		getListeners(entry)->disposeListener = listener;
+	}
+
+	void SkeletonAnimation::setTrackCompleteListener(TrackEntry *entry, const CompleteListener &listener) {
+		getListeners(entry)->completeListener = listener;
+	}
+
+	void SkeletonAnimation::setTrackEventListener(TrackEntry *entry, const EventListener &listener) {
+		getListeners(entry)->eventListener = listener;
+	}
+
+	AnimationState *SkeletonAnimation::getState() const {
+		return _state;
+	}
+
+	void SkeletonAnimation::setUpdateOnlyIfVisible(bool status) {
+		_updateOnlyIfVisible = status;
+	}
+
+}// namespace spine

+ 133 - 136
spine-cocos2dx/src/spine/SkeletonAnimation.h

@@ -1,136 +1,133 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#ifndef SPINE_SKELETONANIMATION_H_
-#define SPINE_SKELETONANIMATION_H_
-
-#include <spine/spine.h>
-#include <spine/spine-cocos2dx.h>
-#include "cocos2d.h"
-
-namespace spine {
-
-class SkeletonAnimation;
-
-typedef std::function<void(TrackEntry* entry)> StartListener;
-typedef std::function<void(TrackEntry* entry)> InterruptListener;
-typedef std::function<void(TrackEntry* entry)> EndListener;
-typedef std::function<void(TrackEntry* entry)> DisposeListener;
-typedef std::function<void(TrackEntry* entry)> CompleteListener;
-typedef std::function<void(TrackEntry* entry, Event* event)> EventListener;
-typedef std::function<void(SkeletonAnimation* node)> UpdateWorldTransformsListener;
-
-/** Draws an animated skeleton, providing an AnimationState for applying one or more animations and queuing animations to be
-  * played later. */
-class SkeletonAnimation: public SkeletonRenderer {
-public:
-	CREATE_FUNC(SkeletonAnimation);
-	static SkeletonAnimation* createWithData (SkeletonData* skeletonData, bool ownsSkeletonData = false);
-	static SkeletonAnimation* createWithJsonFile (const std::string& skeletonJsonFile, Atlas* atlas, float scale = 1);
-	static SkeletonAnimation* createWithJsonFile (const std::string& skeletonJsonFile, const std::string& atlasFile, float scale = 1);
-	static SkeletonAnimation* createWithBinaryFile (const std::string& skeletonBinaryFile, Atlas* atlas, float scale = 1);
-	static SkeletonAnimation* createWithBinaryFile (const std::string& skeletonBinaryFile, const std::string& atlasFile, float scale = 1);
-
-	// Use createWithJsonFile instead
-	CC_DEPRECATED_ATTRIBUTE static SkeletonAnimation* createWithFile (const std::string& skeletonJsonFile, Atlas* atlas, float scale = 1)
-	{
-		return SkeletonAnimation::createWithJsonFile(skeletonJsonFile, atlas, scale);
-	}
-	// Use createWithJsonFile instead
-	CC_DEPRECATED_ATTRIBUTE static SkeletonAnimation* createWithFile (const std::string& skeletonJsonFile, const std::string& atlasFile, float scale = 1)
-	{
-		return SkeletonAnimation::createWithJsonFile(skeletonJsonFile, atlasFile, scale);
-	}
-
-	virtual void update (float deltaTime) override;
-	virtual void draw (cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t transformFlags) override;
-
-	void setAnimationStateData (AnimationStateData* stateData);
-	void setMix (const std::string& fromAnimation, const std::string& toAnimation, float duration);
-
-	TrackEntry* setAnimation (int trackIndex, const std::string& name, bool loop);
-	TrackEntry* addAnimation (int trackIndex, const std::string& name, bool loop, float delay = 0);
-	TrackEntry* setEmptyAnimation (int trackIndex, float mixDuration);
-	void setEmptyAnimations (float mixDuration);
-	TrackEntry* addEmptyAnimation (int trackIndex, float mixDuration, float delay = 0);
-	Animation* findAnimation(const std::string& name) const;
-	TrackEntry* getCurrent (int trackIndex = 0);
-	void clearTracks ();
-	void clearTrack (int trackIndex = 0);
-
-	void setStartListener (const StartListener& listener);
-	void setInterruptListener (const InterruptListener& listener);
-	void setEndListener (const EndListener& listener);
-	void setDisposeListener (const DisposeListener& listener);
-	void setCompleteListener (const CompleteListener& listener);
-	void setEventListener (const EventListener& listener);
-	void setPreUpdateWorldTransformsListener(const UpdateWorldTransformsListener& listener);
-	void setPostUpdateWorldTransformsListener(const UpdateWorldTransformsListener& listener);
-
-	void setTrackStartListener (TrackEntry* entry, const StartListener& listener);
-	void setTrackInterruptListener (TrackEntry* entry, const InterruptListener& listener);
-	void setTrackEndListener (TrackEntry* entry, const EndListener& listener);
-	void setTrackDisposeListener (TrackEntry* entry, const DisposeListener& listener);
-	void setTrackCompleteListener (TrackEntry* entry, const CompleteListener& listener);
-	void setTrackEventListener (TrackEntry* entry, const EventListener& listener);
-
-	virtual void onAnimationStateEvent (TrackEntry* entry, EventType type, Event* event);
-	virtual void onTrackEntryEvent (TrackEntry* entry, EventType type, Event* event);
-
-	AnimationState* getState() const;
-	void setUpdateOnlyIfVisible(bool status);
-
-CC_CONSTRUCTOR_ACCESS:
-	SkeletonAnimation ();
-	virtual ~SkeletonAnimation ();
-	virtual void initialize () override;
-
-protected:
-	AnimationState* _state;
-
-	bool _ownsAnimationStateData;
-	bool _updateOnlyIfVisible;
-	bool _firstDraw;
-
-	StartListener _startListener;
-	InterruptListener _interruptListener;
-	EndListener _endListener;
-	DisposeListener _disposeListener;
-	CompleteListener _completeListener;
-	EventListener _eventListener;
-	UpdateWorldTransformsListener _preUpdateListener;
-	UpdateWorldTransformsListener _postUpdateListener;
-
-private:
-	typedef SkeletonRenderer super;
-};
-
-}
-
-#endif /* SPINE_SKELETONANIMATION_H_ */
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#ifndef SPINE_SKELETONANIMATION_H_
+#define SPINE_SKELETONANIMATION_H_
+
+#include "cocos2d.h"
+#include <spine/spine-cocos2dx.h>
+#include <spine/spine.h>
+
+namespace spine {
+
+	class SkeletonAnimation;
+
+	typedef std::function<void(TrackEntry *entry)> StartListener;
+	typedef std::function<void(TrackEntry *entry)> InterruptListener;
+	typedef std::function<void(TrackEntry *entry)> EndListener;
+	typedef std::function<void(TrackEntry *entry)> DisposeListener;
+	typedef std::function<void(TrackEntry *entry)> CompleteListener;
+	typedef std::function<void(TrackEntry *entry, Event *event)> EventListener;
+	typedef std::function<void(SkeletonAnimation *node)> UpdateWorldTransformsListener;
+
+	/** Draws an animated skeleton, providing an AnimationState for applying one or more animations and queuing animations to be
+  * played later. */
+	class SkeletonAnimation : public SkeletonRenderer {
+	public:
+		CREATE_FUNC(SkeletonAnimation);
+		static SkeletonAnimation *createWithData(SkeletonData *skeletonData, bool ownsSkeletonData = false);
+		static SkeletonAnimation *createWithJsonFile(const std::string &skeletonJsonFile, Atlas *atlas, float scale = 1);
+		static SkeletonAnimation *createWithJsonFile(const std::string &skeletonJsonFile, const std::string &atlasFile, float scale = 1);
+		static SkeletonAnimation *createWithBinaryFile(const std::string &skeletonBinaryFile, Atlas *atlas, float scale = 1);
+		static SkeletonAnimation *createWithBinaryFile(const std::string &skeletonBinaryFile, const std::string &atlasFile, float scale = 1);
+
+		// Use createWithJsonFile instead
+		CC_DEPRECATED_ATTRIBUTE static SkeletonAnimation *createWithFile(const std::string &skeletonJsonFile, Atlas *atlas, float scale = 1) {
+			return SkeletonAnimation::createWithJsonFile(skeletonJsonFile, atlas, scale);
+		}
+		// Use createWithJsonFile instead
+		CC_DEPRECATED_ATTRIBUTE static SkeletonAnimation *createWithFile(const std::string &skeletonJsonFile, const std::string &atlasFile, float scale = 1) {
+			return SkeletonAnimation::createWithJsonFile(skeletonJsonFile, atlasFile, scale);
+		}
+
+		virtual void update(float deltaTime) override;
+		virtual void draw(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t transformFlags) override;
+
+		void setAnimationStateData(AnimationStateData *stateData);
+		void setMix(const std::string &fromAnimation, const std::string &toAnimation, float duration);
+
+		TrackEntry *setAnimation(int trackIndex, const std::string &name, bool loop);
+		TrackEntry *addAnimation(int trackIndex, const std::string &name, bool loop, float delay = 0);
+		TrackEntry *setEmptyAnimation(int trackIndex, float mixDuration);
+		void setEmptyAnimations(float mixDuration);
+		TrackEntry *addEmptyAnimation(int trackIndex, float mixDuration, float delay = 0);
+		Animation *findAnimation(const std::string &name) const;
+		TrackEntry *getCurrent(int trackIndex = 0);
+		void clearTracks();
+		void clearTrack(int trackIndex = 0);
+
+		void setStartListener(const StartListener &listener);
+		void setInterruptListener(const InterruptListener &listener);
+		void setEndListener(const EndListener &listener);
+		void setDisposeListener(const DisposeListener &listener);
+		void setCompleteListener(const CompleteListener &listener);
+		void setEventListener(const EventListener &listener);
+		void setPreUpdateWorldTransformsListener(const UpdateWorldTransformsListener &listener);
+		void setPostUpdateWorldTransformsListener(const UpdateWorldTransformsListener &listener);
+
+		void setTrackStartListener(TrackEntry *entry, const StartListener &listener);
+		void setTrackInterruptListener(TrackEntry *entry, const InterruptListener &listener);
+		void setTrackEndListener(TrackEntry *entry, const EndListener &listener);
+		void setTrackDisposeListener(TrackEntry *entry, const DisposeListener &listener);
+		void setTrackCompleteListener(TrackEntry *entry, const CompleteListener &listener);
+		void setTrackEventListener(TrackEntry *entry, const EventListener &listener);
+
+		virtual void onAnimationStateEvent(TrackEntry *entry, EventType type, Event *event);
+		virtual void onTrackEntryEvent(TrackEntry *entry, EventType type, Event *event);
+
+		AnimationState *getState() const;
+		void setUpdateOnlyIfVisible(bool status);
+
+		CC_CONSTRUCTOR_ACCESS : SkeletonAnimation();
+		virtual ~SkeletonAnimation();
+		virtual void initialize() override;
+
+	protected:
+		AnimationState *_state;
+
+		bool _ownsAnimationStateData;
+		bool _updateOnlyIfVisible;
+		bool _firstDraw;
+
+		StartListener _startListener;
+		InterruptListener _interruptListener;
+		EndListener _endListener;
+		DisposeListener _disposeListener;
+		CompleteListener _completeListener;
+		EventListener _eventListener;
+		UpdateWorldTransformsListener _preUpdateListener;
+		UpdateWorldTransformsListener _postUpdateListener;
+
+	private:
+		typedef SkeletonRenderer super;
+	};
+
+}// namespace spine
+
+#endif /* SPINE_SKELETONANIMATION_H_ */

+ 860 - 866
spine-cocos2dx/src/spine/SkeletonRenderer.cpp

@@ -27,1048 +27,1042 @@
  * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
-#include <spine/spine-cocos2dx.h>
-#include <spine/Extension.h>
-#include <spine/AttachmentVertices.h>
 #include <algorithm>
+#include <spine/AttachmentVertices.h>
+#include <spine/Extension.h>
+#include <spine/spine-cocos2dx.h>
 
 USING_NS_CC;
 
 namespace spine {
 
-namespace {
-	Cocos2dTextureLoader textureLoader;
+	namespace {
+		Cocos2dTextureLoader textureLoader;
 
-	int computeTotalCoordCount(Skeleton& skeleton, int startSlotIndex, int endSlotIndex);
-	cocos2d::Rect computeBoundingRect(const float* coords, int vertexCount);
-	void interleaveCoordinates(float* dst, const float* src, int vertexCount, int dstStride);
-	BlendFunc makeBlendFunc(BlendMode blendMode, bool premultipliedAlpha);
-	void transformWorldVertices(float* dstCoord, int coordCount, Skeleton& skeleton, int startSlotIndex, int endSlotIndex);
-	bool cullRectangle(Renderer* renderer, const Mat4& transform, const cocos2d::Rect& rect);
-		Color4B ColorToColor4B(const Color& color);
-	bool slotIsOutRange(Slot& slot, int startSlotIndex, int endSlotIndex);
-	bool nothingToDraw(Slot& slot, int startSlotIndex, int endSlotIndex);
-}
+		int computeTotalCoordCount(Skeleton &skeleton, int startSlotIndex, int endSlotIndex);
+		cocos2d::Rect computeBoundingRect(const float *coords, int vertexCount);
+		void interleaveCoordinates(float *dst, const float *src, int vertexCount, int dstStride);
+		BlendFunc makeBlendFunc(BlendMode blendMode, bool premultipliedAlpha);
+		void transformWorldVertices(float *dstCoord, int coordCount, Skeleton &skeleton, int startSlotIndex, int endSlotIndex);
+		bool cullRectangle(Renderer *renderer, const Mat4 &transform, const cocos2d::Rect &rect);
+		Color4B ColorToColor4B(const Color &color);
+		bool slotIsOutRange(Slot &slot, int startSlotIndex, int endSlotIndex);
+		bool nothingToDraw(Slot &slot, int startSlotIndex, int endSlotIndex);
+	}// namespace
 
 // C Variable length array
 #ifdef _MSC_VER
 // VLA not supported, use _malloca
 #define VLA(type, arr, count) \
-	type* arr = static_cast<type*>( _malloca(sizeof(type) * count) )
-#define VLA_FREE(arr) do { _freea(arr); } while(false)
+	type *arr = static_cast<type *>(_malloca(sizeof(type) * count))
+#define VLA_FREE(arr) \
+	do { _freea(arr); } while (false)
 #else
 #define VLA(type, arr, count) \
 	type arr[count]
 #define VLA_FREE(arr)
 #endif
 
-SkeletonRenderer* SkeletonRenderer::createWithSkeleton(Skeleton* skeleton, bool ownsSkeleton, bool ownsSkeletonData) {
-	SkeletonRenderer* node = new SkeletonRenderer(skeleton, ownsSkeleton, ownsSkeletonData);
-	node->autorelease();
-	return node;
-}
+	SkeletonRenderer *SkeletonRenderer::createWithSkeleton(Skeleton *skeleton, bool ownsSkeleton, bool ownsSkeletonData) {
+		SkeletonRenderer *node = new SkeletonRenderer(skeleton, ownsSkeleton, ownsSkeletonData);
+		node->autorelease();
+		return node;
+	}
 
-SkeletonRenderer* SkeletonRenderer::createWithData (SkeletonData* skeletonData, bool ownsSkeletonData) {
-	SkeletonRenderer* node = new SkeletonRenderer(skeletonData, ownsSkeletonData);
-	node->autorelease();
-	return node;
-}
+	SkeletonRenderer *SkeletonRenderer::createWithData(SkeletonData *skeletonData, bool ownsSkeletonData) {
+		SkeletonRenderer *node = new SkeletonRenderer(skeletonData, ownsSkeletonData);
+		node->autorelease();
+		return node;
+	}
 
-SkeletonRenderer* SkeletonRenderer::createWithFile (const std::string& skeletonDataFile, Atlas* atlas, float scale) {
-	SkeletonRenderer* node = new SkeletonRenderer(skeletonDataFile, atlas, scale);
-	node->autorelease();
-	return node;
-}
+	SkeletonRenderer *SkeletonRenderer::createWithFile(const std::string &skeletonDataFile, Atlas *atlas, float scale) {
+		SkeletonRenderer *node = new SkeletonRenderer(skeletonDataFile, atlas, scale);
+		node->autorelease();
+		return node;
+	}
 
-SkeletonRenderer* SkeletonRenderer::createWithFile (const std::string& skeletonDataFile, const std::string& atlasFile, float scale) {
-	SkeletonRenderer* node = new SkeletonRenderer(skeletonDataFile, atlasFile, scale);
-	node->autorelease();
-	return node;
-}
+	SkeletonRenderer *SkeletonRenderer::createWithFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale) {
+		SkeletonRenderer *node = new SkeletonRenderer(skeletonDataFile, atlasFile, scale);
+		node->autorelease();
+		return node;
+	}
 
-void SkeletonRenderer::initialize () {
-	_clipper = new (__FILE__, __LINE__) SkeletonClipping();
+	void SkeletonRenderer::initialize() {
+		_clipper = new (__FILE__, __LINE__) SkeletonClipping();
 
-	_blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;
-	setOpacityModifyRGB(true);
+		_blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;
+		setOpacityModifyRGB(true);
 
-	setTwoColorTint(false);
+		setTwoColorTint(false);
 
-	_skeleton->setToSetupPose();
-	_skeleton->updateWorldTransform();
-}
+		_skeleton->setToSetupPose();
+		_skeleton->updateWorldTransform();
+	}
 
-void SkeletonRenderer::setupGLProgramState (bool twoColorTintEnabled) {
-	if (twoColorTintEnabled) {
+	void SkeletonRenderer::setupGLProgramState(bool twoColorTintEnabled) {
+		if (twoColorTintEnabled) {
 #if COCOS2D_VERSION < 0x00040000
-		setGLProgramState(SkeletonTwoColorBatch::getInstance()->getTwoColorTintProgramState());
+			setGLProgramState(SkeletonTwoColorBatch::getInstance()->getTwoColorTintProgramState());
 #endif
-		return;
-	}
-
-	Texture2D *texture = nullptr;
-	for (int i = 0, n = _skeleton->getSlots().size(); i < n; i++) {
-		Slot* slot = _skeleton->getDrawOrder()[i];
-		Attachment* const attachment = slot->getAttachment();
-		if (!attachment) continue;
-		if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
-			RegionAttachment* regionAttachment = static_cast<RegionAttachment*>(attachment);
-			texture = static_cast<AttachmentVertices*>(regionAttachment->getRendererObject())->_texture;
-		} else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
-			MeshAttachment* meshAttachment = static_cast<MeshAttachment*>(attachment);
-			texture = static_cast<AttachmentVertices*>(meshAttachment->getRendererObject())->_texture;
-		} else {
-			continue;
+			return;
 		}
 
-		if (texture != nullptr) {
-			break;
+		Texture2D *texture = nullptr;
+		for (int i = 0, n = _skeleton->getSlots().size(); i < n; i++) {
+			Slot *slot = _skeleton->getDrawOrder()[i];
+			Attachment *const attachment = slot->getAttachment();
+			if (!attachment) continue;
+			if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
+				RegionAttachment *regionAttachment = static_cast<RegionAttachment *>(attachment);
+				texture = static_cast<AttachmentVertices *>(regionAttachment->getRendererObject())->_texture;
+			} else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
+				MeshAttachment *meshAttachment = static_cast<MeshAttachment *>(attachment);
+				texture = static_cast<AttachmentVertices *>(meshAttachment->getRendererObject())->_texture;
+			} else {
+				continue;
+			}
+
+			if (texture != nullptr) {
+				break;
+			}
 		}
-	}
 #if COCOS2D_VERSION < 0x00040000
-	setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP, texture));
+		setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP, texture));
 #endif
-}
-
-void SkeletonRenderer::setSkeletonData (SkeletonData *skeletonData, bool ownsSkeletonData) {
-	_skeleton = new (__FILE__, __LINE__) Skeleton(skeletonData);
-	_ownsSkeletonData = ownsSkeletonData;
-}
-
-SkeletonRenderer::SkeletonRenderer ()
-	: _atlas(nullptr), _attachmentLoader(nullptr), _timeScale(1), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits<int>::max()) {
-}
-
-SkeletonRenderer::SkeletonRenderer(Skeleton* skeleton, bool ownsSkeleton, bool ownsSkeletonData, bool ownsAtlas)
-	: _atlas(nullptr), _attachmentLoader(nullptr), _timeScale(1), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits<int>::max()) {
-	initWithSkeleton(skeleton, ownsSkeleton, ownsSkeletonData, ownsAtlas);
-}
-
-SkeletonRenderer::SkeletonRenderer (SkeletonData *skeletonData, bool ownsSkeletonData)
-	: _atlas(nullptr), _attachmentLoader(nullptr), _timeScale(1), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits<int>::max()) {
-	initWithData(skeletonData, ownsSkeletonData);
-}
-
-SkeletonRenderer::SkeletonRenderer (const std::string& skeletonDataFile, Atlas* atlas, float scale)
-	: _atlas(nullptr), _attachmentLoader(nullptr), _timeScale(1), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits<int>::max()) {
-	initWithJsonFile(skeletonDataFile, atlas, scale);
-}
-
-SkeletonRenderer::SkeletonRenderer (const std::string& skeletonDataFile, const std::string& atlasFile, float scale)
-	: _atlas(nullptr), _attachmentLoader(nullptr), _timeScale(1), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits<int>::max()) {
-	initWithJsonFile(skeletonDataFile, atlasFile, scale);
-}
-
-SkeletonRenderer::~SkeletonRenderer () {
-	if (_ownsSkeletonData) delete _skeleton->getData();
-	if (_ownsSkeleton) delete _skeleton;
-	if (_ownsAtlas && _atlas) delete _atlas;
-	if (_attachmentLoader) delete _attachmentLoader;
-	delete _clipper;
-}
-
-void SkeletonRenderer::initWithSkeleton(Skeleton* skeleton, bool ownsSkeleton, bool ownsSkeletonData, bool ownsAtlas) {
-	_skeleton = skeleton;
-	_ownsSkeleton = ownsSkeleton;
-	_ownsSkeletonData = ownsSkeletonData;
-	_ownsAtlas = ownsAtlas;
-	initialize();
-}
-
-void SkeletonRenderer::initWithData (SkeletonData* skeletonData, bool ownsSkeletonData) {
-	_ownsSkeleton = true;
-	setSkeletonData(skeletonData, ownsSkeletonData);
-	initialize();
-}
-
-void SkeletonRenderer::initWithJsonFile (const std::string& skeletonDataFile, Atlas* atlas, float scale) {
-	_atlas = atlas;
-	_attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas);
-
-	SkeletonJson json(_attachmentLoader);
-	json.setScale(scale);
-	SkeletonData* skeletonData = json.readSkeletonDataFile(skeletonDataFile.c_str());
-	CCASSERT(skeletonData, !json.getError().isEmpty() ? json.getError().buffer() : "Error reading skeleton data.");
-
-	_ownsSkeleton = true;
-	setSkeletonData(skeletonData, true);
-
-	initialize();
-}
-
-void SkeletonRenderer::initWithJsonFile (const std::string& skeletonDataFile, const std::string& atlasFile, float scale) {
-	_atlas = new (__FILE__, __LINE__) Atlas(atlasFile.c_str(), &textureLoader, true);
-	CCASSERT(_atlas, "Error reading atlas file.");
-
-	_attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas);
-
-	SkeletonJson json(_attachmentLoader);
-	json.setScale(scale);
-	SkeletonData* skeletonData = json.readSkeletonDataFile(skeletonDataFile.c_str());
-	CCASSERT(skeletonData, !json.getError().isEmpty() ? json.getError().buffer() : "Error reading skeleton data.");
-
-	_ownsSkeleton = true;
-	_ownsAtlas = true;
-	setSkeletonData(skeletonData, true);
-
-	initialize();
-}
-
-void SkeletonRenderer::initWithBinaryFile (const std::string& skeletonDataFile, Atlas* atlas, float scale) {
-	_atlas = atlas;
-	_attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas);
-
-	SkeletonBinary binary(_attachmentLoader);
-	binary.setScale(scale);
-	SkeletonData* skeletonData = binary.readSkeletonDataFile(skeletonDataFile.c_str());
-	CCASSERT(skeletonData, !binary.getError().isEmpty() ? binary.getError().buffer() : "Error reading skeleton data.");
-	_ownsSkeleton = true;
-	setSkeletonData(skeletonData, true);
-
-	initialize();
-}
-
-void SkeletonRenderer::initWithBinaryFile (const std::string& skeletonDataFile, const std::string& atlasFile, float scale) {
-	_atlas = new (__FILE__, __LINE__) Atlas(atlasFile.c_str(), &textureLoader, true);
-	CCASSERT(_atlas, "Error reading atlas file.");
-
-	_attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas);
-
-	SkeletonBinary binary(_attachmentLoader);
-	binary.setScale(scale);
-	SkeletonData* skeletonData = binary.readSkeletonDataFile(skeletonDataFile.c_str());
-	CCASSERT(skeletonData, !binary.getError().isEmpty() ? binary.getError().buffer() : "Error reading skeleton data.");
-	_ownsSkeleton = true;
-	_ownsAtlas = true;
-	setSkeletonData(skeletonData, true);
-
-	initialize();
-}
-
-
-void SkeletonRenderer::update (float deltaTime) {
-	Node::update(deltaTime);
-	if (_ownsSkeleton) _skeleton->update(deltaTime * _timeScale);
-}
-
-void SkeletonRenderer::draw (Renderer* renderer, const Mat4& transform, uint32_t transformFlags) {
-	// Early exit if the skeleton is invisible.
-	if (getDisplayedOpacity() == 0 || _skeleton->getColor().a == 0) {
-		return;
 	}
 
-	const int coordCount = computeTotalCoordCount(*_skeleton, _startSlotIndex, _endSlotIndex);
-	if (coordCount == 0) {
-		return;
+	void SkeletonRenderer::setSkeletonData(SkeletonData *skeletonData, bool ownsSkeletonData) {
+		_skeleton = new (__FILE__, __LINE__) Skeleton(skeletonData);
+		_ownsSkeletonData = ownsSkeletonData;
 	}
-	assert(coordCount % 2 == 0);
 
-	VLA(float, worldCoords, coordCount);
-	transformWorldVertices(worldCoords, coordCount, *_skeleton, _startSlotIndex, _endSlotIndex);
+	SkeletonRenderer::SkeletonRenderer()
+		: _atlas(nullptr), _attachmentLoader(nullptr), _timeScale(1), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits<int>::max()) {
+	}
 
-#if CC_USE_CULLING
-	const cocos2d::Rect bb = computeBoundingRect(worldCoords, coordCount / 2);
+	SkeletonRenderer::SkeletonRenderer(Skeleton *skeleton, bool ownsSkeleton, bool ownsSkeletonData, bool ownsAtlas)
+		: _atlas(nullptr), _attachmentLoader(nullptr), _timeScale(1), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits<int>::max()) {
+		initWithSkeleton(skeleton, ownsSkeleton, ownsSkeletonData, ownsAtlas);
+	}
 
-	if (cullRectangle(renderer, transform, bb)) {
-		VLA_FREE(worldCoords);
-		return;
+	SkeletonRenderer::SkeletonRenderer(SkeletonData *skeletonData, bool ownsSkeletonData)
+		: _atlas(nullptr), _attachmentLoader(nullptr), _timeScale(1), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits<int>::max()) {
+		initWithData(skeletonData, ownsSkeletonData);
 	}
-#endif
 
-	const float* worldCoordPtr = worldCoords;
-	SkeletonBatch* batch = SkeletonBatch::getInstance();
-	SkeletonTwoColorBatch* twoColorBatch = SkeletonTwoColorBatch::getInstance();
-	const bool hasSingleTint = (isTwoColorTint() == false);
+	SkeletonRenderer::SkeletonRenderer(const std::string &skeletonDataFile, Atlas *atlas, float scale)
+		: _atlas(nullptr), _attachmentLoader(nullptr), _timeScale(1), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits<int>::max()) {
+		initWithJsonFile(skeletonDataFile, atlas, scale);
+	}
 
-	if (_effect) {
-		_effect->begin(*_skeleton);
+	SkeletonRenderer::SkeletonRenderer(const std::string &skeletonDataFile, const std::string &atlasFile, float scale)
+		: _atlas(nullptr), _attachmentLoader(nullptr), _timeScale(1), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits<int>::max()) {
+		initWithJsonFile(skeletonDataFile, atlasFile, scale);
 	}
 
-	const Color3B displayedColor = getDisplayedColor();
-	Color nodeColor;
-	nodeColor.r = displayedColor.r / 255.f;
-	nodeColor.g = displayedColor.g / 255.f;
-	nodeColor.b = displayedColor.b / 255.f;
-	nodeColor.a = getDisplayedOpacity() / 255.f;
-
-	Color color;
-	Color darkColor;
-	const float darkPremultipliedAlpha = _premultipliedAlpha ? 1.f : 0;
-	AttachmentVertices* attachmentVertices = nullptr;
-	TwoColorTrianglesCommand* lastTwoColorTrianglesCommand = nullptr;
-	for (int i = 0, n = _skeleton->getSlots().size(); i < n; ++i) {
-		Slot* slot = _skeleton->getDrawOrder()[i];;
-
-		if (nothingToDraw(*slot, _startSlotIndex, _endSlotIndex)) {
-			_clipper->clipEnd(*slot);
-			continue;
-		}
+	SkeletonRenderer::~SkeletonRenderer() {
+		if (_ownsSkeletonData) delete _skeleton->getData();
+		if (_ownsSkeleton) delete _skeleton;
+		if (_ownsAtlas && _atlas) delete _atlas;
+		if (_attachmentLoader) delete _attachmentLoader;
+		delete _clipper;
+	}
 
-		cocos2d::TrianglesCommand::Triangles triangles;
-		TwoColorTriangles trianglesTwoColor;
+	void SkeletonRenderer::initWithSkeleton(Skeleton *skeleton, bool ownsSkeleton, bool ownsSkeletonData, bool ownsAtlas) {
+		_skeleton = skeleton;
+		_ownsSkeleton = ownsSkeleton;
+		_ownsSkeletonData = ownsSkeletonData;
+		_ownsAtlas = ownsAtlas;
+		initialize();
+	}
 
-		if (slot->getAttachment()->getRTTI().isExactly(RegionAttachment::rtti)) {
-			RegionAttachment* attachment = static_cast<RegionAttachment*>(slot->getAttachment());
-			attachmentVertices = static_cast<AttachmentVertices*>(attachment->getRendererObject());
+	void SkeletonRenderer::initWithData(SkeletonData *skeletonData, bool ownsSkeletonData) {
+		_ownsSkeleton = true;
+		setSkeletonData(skeletonData, ownsSkeletonData);
+		initialize();
+	}
 
-			float* dstTriangleVertices = nullptr;
-			int dstStride = 0; // in floats
-			if (hasSingleTint) {
-				triangles.indices = attachmentVertices->_triangles->indices;
-				triangles.indexCount = attachmentVertices->_triangles->indexCount;
-				triangles.verts = batch->allocateVertices(attachmentVertices->_triangles->vertCount);
-				triangles.vertCount = attachmentVertices->_triangles->vertCount;
-				assert(triangles.vertCount == 4);
-				memcpy(triangles.verts, attachmentVertices->_triangles->verts, sizeof(cocos2d::V3F_C4B_T2F) * attachmentVertices->_triangles->vertCount);
-				dstStride = sizeof(V3F_C4B_T2F) / sizeof(float);
-				dstTriangleVertices = reinterpret_cast<float*>(triangles.verts);
-			} else {
-				trianglesTwoColor.indices = attachmentVertices->_triangles->indices;
-				trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount;
-				trianglesTwoColor.verts = twoColorBatch->allocateVertices(attachmentVertices->_triangles->vertCount);
-				trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount;
-				assert(trianglesTwoColor.vertCount == 4);
-				for (int v = 0; v < trianglesTwoColor.vertCount; v++) {
-					trianglesTwoColor.verts[v].texCoords = attachmentVertices->_triangles->verts[v].texCoords;
-				}
-				dstTriangleVertices = reinterpret_cast<float*>(trianglesTwoColor.verts);
-				dstStride = sizeof(V3F_C4B_C4B_T2F) / sizeof(float);
-			}
-			// Copy world vertices to triangle vertices.
-			interleaveCoordinates(dstTriangleVertices, worldCoordPtr, 4, dstStride);
-			worldCoordPtr += 8;
+	void SkeletonRenderer::initWithJsonFile(const std::string &skeletonDataFile, Atlas *atlas, float scale) {
+		_atlas = atlas;
+		_attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas);
 
-			color = attachment->getColor();
-		}
-		else if (slot->getAttachment()->getRTTI().isExactly(MeshAttachment::rtti)) {
-			MeshAttachment* attachment = (MeshAttachment*)slot->getAttachment();
-			attachmentVertices = (AttachmentVertices*)attachment->getRendererObject();
+		SkeletonJson json(_attachmentLoader);
+		json.setScale(scale);
+		SkeletonData *skeletonData = json.readSkeletonDataFile(skeletonDataFile.c_str());
+		CCASSERT(skeletonData, !json.getError().isEmpty() ? json.getError().buffer() : "Error reading skeleton data.");
 
-			float* dstTriangleVertices = nullptr;
-			int dstStride = 0; // in floats
-			int dstVertexCount = 0;
-			if (hasSingleTint) {
-				triangles.indices = attachmentVertices->_triangles->indices;
-				triangles.indexCount = attachmentVertices->_triangles->indexCount;
-				triangles.verts = batch->allocateVertices(attachmentVertices->_triangles->vertCount);
-				triangles.vertCount = attachmentVertices->_triangles->vertCount;
-				memcpy(triangles.verts, attachmentVertices->_triangles->verts, sizeof(cocos2d::V3F_C4B_T2F) * attachmentVertices->_triangles->vertCount);
-				dstTriangleVertices = (float*)triangles.verts;
-				dstStride = sizeof(V3F_C4B_T2F) / sizeof(float);
-				dstVertexCount = triangles.vertCount;
-			} else {
-				trianglesTwoColor.indices = attachmentVertices->_triangles->indices;
-				trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount;
-				trianglesTwoColor.verts = twoColorBatch->allocateVertices(attachmentVertices->_triangles->vertCount);
-				trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount;
-				for (int v = 0; v < trianglesTwoColor.vertCount; v++) {
-					trianglesTwoColor.verts[v].texCoords = attachmentVertices->_triangles->verts[v].texCoords;
-				}
-				dstTriangleVertices = (float*)trianglesTwoColor.verts;
-				dstStride = sizeof(V3F_C4B_C4B_T2F) / sizeof(float);
-				dstVertexCount = trianglesTwoColor.vertCount;
-			}
+		_ownsSkeleton = true;
+		setSkeletonData(skeletonData, true);
+
+		initialize();
+	}
 
-			// Copy world vertices to triangle vertices.
-			//assert(dstVertexCount * 2 == attachment->super.worldVerticesLength);
-			interleaveCoordinates(dstTriangleVertices, worldCoordPtr, dstVertexCount, dstStride);
-			worldCoordPtr += dstVertexCount * 2;
+	void SkeletonRenderer::initWithJsonFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale) {
+		_atlas = new (__FILE__, __LINE__) Atlas(atlasFile.c_str(), &textureLoader, true);
+		CCASSERT(_atlas, "Error reading atlas file.");
 
-			color = attachment->getColor();
-		}
-		else if (slot->getAttachment()->getRTTI().isExactly(ClippingAttachment::rtti)) {
-			ClippingAttachment* clip = (ClippingAttachment*)slot->getAttachment();
-			_clipper->clipStart(*slot, clip);
-			continue;
-		} else {
-			_clipper->clipEnd(*slot);
-			continue;
+		_attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas);
+
+		SkeletonJson json(_attachmentLoader);
+		json.setScale(scale);
+		SkeletonData *skeletonData = json.readSkeletonDataFile(skeletonDataFile.c_str());
+		CCASSERT(skeletonData, !json.getError().isEmpty() ? json.getError().buffer() : "Error reading skeleton data.");
+
+		_ownsSkeleton = true;
+		_ownsAtlas = true;
+		setSkeletonData(skeletonData, true);
+
+		initialize();
+	}
+
+	void SkeletonRenderer::initWithBinaryFile(const std::string &skeletonDataFile, Atlas *atlas, float scale) {
+		_atlas = atlas;
+		_attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas);
+
+		SkeletonBinary binary(_attachmentLoader);
+		binary.setScale(scale);
+		SkeletonData *skeletonData = binary.readSkeletonDataFile(skeletonDataFile.c_str());
+		CCASSERT(skeletonData, !binary.getError().isEmpty() ? binary.getError().buffer() : "Error reading skeleton data.");
+		_ownsSkeleton = true;
+		setSkeletonData(skeletonData, true);
+
+		initialize();
+	}
+
+	void SkeletonRenderer::initWithBinaryFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale) {
+		_atlas = new (__FILE__, __LINE__) Atlas(atlasFile.c_str(), &textureLoader, true);
+		CCASSERT(_atlas, "Error reading atlas file.");
+
+		_attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas);
+
+		SkeletonBinary binary(_attachmentLoader);
+		binary.setScale(scale);
+		SkeletonData *skeletonData = binary.readSkeletonDataFile(skeletonDataFile.c_str());
+		CCASSERT(skeletonData, !binary.getError().isEmpty() ? binary.getError().buffer() : "Error reading skeleton data.");
+		_ownsSkeleton = true;
+		_ownsAtlas = true;
+		setSkeletonData(skeletonData, true);
+
+		initialize();
+	}
+
+
+	void SkeletonRenderer::update(float deltaTime) {
+		Node::update(deltaTime);
+		if (_ownsSkeleton) _skeleton->update(deltaTime * _timeScale);
+	}
+
+	void SkeletonRenderer::draw(Renderer *renderer, const Mat4 &transform, uint32_t transformFlags) {
+		// Early exit if the skeleton is invisible.
+		if (getDisplayedOpacity() == 0 || _skeleton->getColor().a == 0) {
+			return;
 		}
 
-		if (slot->hasDarkColor()) {
-			darkColor = slot->getDarkColor();
-		} else {
-			darkColor.r = 0;
-			darkColor.g = 0;
-			darkColor.b = 0;
+		const int coordCount = computeTotalCoordCount(*_skeleton, _startSlotIndex, _endSlotIndex);
+		if (coordCount == 0) {
+			return;
 		}
-		darkColor.a = darkPremultipliedAlpha;
+		assert(coordCount % 2 == 0);
 
-		color.a *= nodeColor.a * _skeleton->getColor().a * slot->getColor().a;
-		if (color.a == 0) {
-			_clipper->clipEnd(*slot);
-			continue;
+		VLA(float, worldCoords, coordCount);
+		transformWorldVertices(worldCoords, coordCount, *_skeleton, _startSlotIndex, _endSlotIndex);
+
+#if CC_USE_CULLING
+		const cocos2d::Rect bb = computeBoundingRect(worldCoords, coordCount / 2);
+
+		if (cullRectangle(renderer, transform, bb)) {
+			VLA_FREE(worldCoords);
+			return;
 		}
-		color.r *= nodeColor.r * _skeleton->getColor().r * slot->getColor().r;
-		color.g *= nodeColor.g * _skeleton->getColor().g * slot->getColor().g;
-		color.b *= nodeColor.b * _skeleton->getColor().b * slot->getColor().b;
-		if (_premultipliedAlpha) {
-			color.r *= color.a;
-			color.g *= color.a;
-			color.b *= color.a;
+#endif
+
+		const float *worldCoordPtr = worldCoords;
+		SkeletonBatch *batch = SkeletonBatch::getInstance();
+		SkeletonTwoColorBatch *twoColorBatch = SkeletonTwoColorBatch::getInstance();
+		const bool hasSingleTint = (isTwoColorTint() == false);
+
+		if (_effect) {
+			_effect->begin(*_skeleton);
 		}
 
-		const cocos2d::Color4B color4B = ColorToColor4B(color);
-		const cocos2d::Color4B darkColor4B = ColorToColor4B(darkColor);
-		const BlendFunc blendFunc = makeBlendFunc(slot->getData().getBlendMode(), attachmentVertices->_texture->hasPremultipliedAlpha());
-		_blendFunc = blendFunc;
+		const Color3B displayedColor = getDisplayedColor();
+		Color nodeColor;
+		nodeColor.r = displayedColor.r / 255.f;
+		nodeColor.g = displayedColor.g / 255.f;
+		nodeColor.b = displayedColor.b / 255.f;
+		nodeColor.a = getDisplayedOpacity() / 255.f;
+
+		Color color;
+		Color darkColor;
+		const float darkPremultipliedAlpha = _premultipliedAlpha ? 1.f : 0;
+		AttachmentVertices *attachmentVertices = nullptr;
+		TwoColorTrianglesCommand *lastTwoColorTrianglesCommand = nullptr;
+		for (int i = 0, n = _skeleton->getSlots().size(); i < n; ++i) {
+			Slot *slot = _skeleton->getDrawOrder()[i];
+			;
 
-		if (hasSingleTint) {
-			if (_clipper->isClipping()) {
-				_clipper->clipTriangles((float*)&triangles.verts[0].vertices, triangles.indices, triangles.indexCount, (float*)&triangles.verts[0].texCoords, sizeof(cocos2d::V3F_C4B_T2F) / 4);
-				batch->deallocateVertices(triangles.vertCount);
+			if (nothingToDraw(*slot, _startSlotIndex, _endSlotIndex)) {
+				_clipper->clipEnd(*slot);
+				continue;
+			}
 
-				if (_clipper->getClippedTriangles().size() == 0) {
-					_clipper->clipEnd(*slot);
-					continue;
+			cocos2d::TrianglesCommand::Triangles triangles;
+			TwoColorTriangles trianglesTwoColor;
+
+			if (slot->getAttachment()->getRTTI().isExactly(RegionAttachment::rtti)) {
+				RegionAttachment *attachment = static_cast<RegionAttachment *>(slot->getAttachment());
+				attachmentVertices = static_cast<AttachmentVertices *>(attachment->getRendererObject());
+
+				float *dstTriangleVertices = nullptr;
+				int dstStride = 0;// in floats
+				if (hasSingleTint) {
+					triangles.indices = attachmentVertices->_triangles->indices;
+					triangles.indexCount = attachmentVertices->_triangles->indexCount;
+					triangles.verts = batch->allocateVertices(attachmentVertices->_triangles->vertCount);
+					triangles.vertCount = attachmentVertices->_triangles->vertCount;
+					assert(triangles.vertCount == 4);
+					memcpy(triangles.verts, attachmentVertices->_triangles->verts, sizeof(cocos2d::V3F_C4B_T2F) * attachmentVertices->_triangles->vertCount);
+					dstStride = sizeof(V3F_C4B_T2F) / sizeof(float);
+					dstTriangleVertices = reinterpret_cast<float *>(triangles.verts);
+				} else {
+					trianglesTwoColor.indices = attachmentVertices->_triangles->indices;
+					trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount;
+					trianglesTwoColor.verts = twoColorBatch->allocateVertices(attachmentVertices->_triangles->vertCount);
+					trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount;
+					assert(trianglesTwoColor.vertCount == 4);
+					for (int v = 0; v < trianglesTwoColor.vertCount; v++) {
+						trianglesTwoColor.verts[v].texCoords = attachmentVertices->_triangles->verts[v].texCoords;
+					}
+					dstTriangleVertices = reinterpret_cast<float *>(trianglesTwoColor.verts);
+					dstStride = sizeof(V3F_C4B_C4B_T2F) / sizeof(float);
+				}
+				// Copy world vertices to triangle vertices.
+				interleaveCoordinates(dstTriangleVertices, worldCoordPtr, 4, dstStride);
+				worldCoordPtr += 8;
+
+				color = attachment->getColor();
+			} else if (slot->getAttachment()->getRTTI().isExactly(MeshAttachment::rtti)) {
+				MeshAttachment *attachment = (MeshAttachment *) slot->getAttachment();
+				attachmentVertices = (AttachmentVertices *) attachment->getRendererObject();
+
+				float *dstTriangleVertices = nullptr;
+				int dstStride = 0;// in floats
+				int dstVertexCount = 0;
+				if (hasSingleTint) {
+					triangles.indices = attachmentVertices->_triangles->indices;
+					triangles.indexCount = attachmentVertices->_triangles->indexCount;
+					triangles.verts = batch->allocateVertices(attachmentVertices->_triangles->vertCount);
+					triangles.vertCount = attachmentVertices->_triangles->vertCount;
+					memcpy(triangles.verts, attachmentVertices->_triangles->verts, sizeof(cocos2d::V3F_C4B_T2F) * attachmentVertices->_triangles->vertCount);
+					dstTriangleVertices = (float *) triangles.verts;
+					dstStride = sizeof(V3F_C4B_T2F) / sizeof(float);
+					dstVertexCount = triangles.vertCount;
+				} else {
+					trianglesTwoColor.indices = attachmentVertices->_triangles->indices;
+					trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount;
+					trianglesTwoColor.verts = twoColorBatch->allocateVertices(attachmentVertices->_triangles->vertCount);
+					trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount;
+					for (int v = 0; v < trianglesTwoColor.vertCount; v++) {
+						trianglesTwoColor.verts[v].texCoords = attachmentVertices->_triangles->verts[v].texCoords;
+					}
+					dstTriangleVertices = (float *) trianglesTwoColor.verts;
+					dstStride = sizeof(V3F_C4B_C4B_T2F) / sizeof(float);
+					dstVertexCount = trianglesTwoColor.vertCount;
 				}
 
-				triangles.vertCount = _clipper->getClippedVertices().size() / 2;
-				triangles.verts = batch->allocateVertices(triangles.vertCount);
-				triangles.indexCount = _clipper->getClippedTriangles().size();
-				triangles.indices =
-				batch->allocateIndices(triangles.indexCount);
-				memcpy(triangles.indices, _clipper->getClippedTriangles().buffer(), sizeof(unsigned short) * _clipper->getClippedTriangles().size());
+				// Copy world vertices to triangle vertices.
+				//assert(dstVertexCount * 2 == attachment->super.worldVerticesLength);
+				interleaveCoordinates(dstTriangleVertices, worldCoordPtr, dstVertexCount, dstStride);
+				worldCoordPtr += dstVertexCount * 2;
+
+				color = attachment->getColor();
+			} else if (slot->getAttachment()->getRTTI().isExactly(ClippingAttachment::rtti)) {
+				ClippingAttachment *clip = (ClippingAttachment *) slot->getAttachment();
+				_clipper->clipStart(*slot, clip);
+				continue;
+			} else {
+				_clipper->clipEnd(*slot);
+				continue;
+			}
+
+			if (slot->hasDarkColor()) {
+				darkColor = slot->getDarkColor();
+			} else {
+				darkColor.r = 0;
+				darkColor.g = 0;
+				darkColor.b = 0;
+			}
+			darkColor.a = darkPremultipliedAlpha;
+
+			color.a *= nodeColor.a * _skeleton->getColor().a * slot->getColor().a;
+			if (color.a == 0) {
+				_clipper->clipEnd(*slot);
+				continue;
+			}
+			color.r *= nodeColor.r * _skeleton->getColor().r * slot->getColor().r;
+			color.g *= nodeColor.g * _skeleton->getColor().g * slot->getColor().g;
+			color.b *= nodeColor.b * _skeleton->getColor().b * slot->getColor().b;
+			if (_premultipliedAlpha) {
+				color.r *= color.a;
+				color.g *= color.a;
+				color.b *= color.a;
+			}
+
+			const cocos2d::Color4B color4B = ColorToColor4B(color);
+			const cocos2d::Color4B darkColor4B = ColorToColor4B(darkColor);
+			const BlendFunc blendFunc = makeBlendFunc(slot->getData().getBlendMode(), attachmentVertices->_texture->hasPremultipliedAlpha());
+			_blendFunc = blendFunc;
+
+			if (hasSingleTint) {
+				if (_clipper->isClipping()) {
+					_clipper->clipTriangles((float *) &triangles.verts[0].vertices, triangles.indices, triangles.indexCount, (float *) &triangles.verts[0].texCoords, sizeof(cocos2d::V3F_C4B_T2F) / 4);
+					batch->deallocateVertices(triangles.vertCount);
+
+					if (_clipper->getClippedTriangles().size() == 0) {
+						_clipper->clipEnd(*slot);
+						continue;
+					}
+
+					triangles.vertCount = _clipper->getClippedVertices().size() / 2;
+					triangles.verts = batch->allocateVertices(triangles.vertCount);
+					triangles.indexCount = _clipper->getClippedTriangles().size();
+					triangles.indices =
+							batch->allocateIndices(triangles.indexCount);
+					memcpy(triangles.indices, _clipper->getClippedTriangles().buffer(), sizeof(unsigned short) * _clipper->getClippedTriangles().size());
 
 #if COCOS2D_VERSION < 0x00040000
-				cocos2d::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _glProgramState, blendFunc, triangles, transform, transformFlags);
+					cocos2d::TrianglesCommand *batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _glProgramState, blendFunc, triangles, transform, transformFlags);
 #else
-				cocos2d::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _programState, blendFunc, triangles, transform, transformFlags);
+					cocos2d::TrianglesCommand *batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _programState, blendFunc, triangles, transform, transformFlags);
 #endif
 
-				const float* verts = _clipper->getClippedVertices().buffer();
-				const float* uvs = _clipper->getClippedUVs().buffer();
-				if (_effect) {
-					V3F_C4B_T2F* vertex = batchedTriangles->getTriangles().verts;
-					Color darkTmp;
-					for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv+=2, ++vertex) {
-						Color lightCopy = color;
-						vertex->vertices.x = verts[vv];
-						vertex->vertices.y = verts[vv + 1];
-						vertex->texCoords.u = uvs[vv];
-						vertex->texCoords.v = uvs[vv + 1];
-						_effect->transform(vertex->vertices.x, vertex->vertices.y, vertex->texCoords.u, vertex->texCoords.v, lightCopy, darkTmp);
-						vertex->colors = ColorToColor4B(lightCopy);
+					const float *verts = _clipper->getClippedVertices().buffer();
+					const float *uvs = _clipper->getClippedUVs().buffer();
+					if (_effect) {
+						V3F_C4B_T2F *vertex = batchedTriangles->getTriangles().verts;
+						Color darkTmp;
+						for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv += 2, ++vertex) {
+							Color lightCopy = color;
+							vertex->vertices.x = verts[vv];
+							vertex->vertices.y = verts[vv + 1];
+							vertex->texCoords.u = uvs[vv];
+							vertex->texCoords.v = uvs[vv + 1];
+							_effect->transform(vertex->vertices.x, vertex->vertices.y, vertex->texCoords.u, vertex->texCoords.v, lightCopy, darkTmp);
+							vertex->colors = ColorToColor4B(lightCopy);
+						}
+					} else {
+						V3F_C4B_T2F *vertex = batchedTriangles->getTriangles().verts;
+						for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv += 2, ++vertex) {
+							vertex->vertices.x = verts[vv];
+							vertex->vertices.y = verts[vv + 1];
+							vertex->texCoords.u = uvs[vv];
+							vertex->texCoords.v = uvs[vv + 1];
+							vertex->colors = color4B;
+						}
 					}
 				} else {
-					V3F_C4B_T2F* vertex = batchedTriangles->getTriangles().verts;
-					for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv+=2, ++vertex) {
-						vertex->vertices.x = verts[vv];
-						vertex->vertices.y = verts[vv + 1];
-						vertex->texCoords.u = uvs[vv];
-						vertex->texCoords.v = uvs[vv + 1];
-						vertex->colors = color4B;
-					}
-				}
-			} else {
-				// Not clipping.
+					// Not clipping.
 #if COCOS2D_VERSION < 0x00040000
-				cocos2d::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _glProgramState, blendFunc, triangles, transform, transformFlags);
+					cocos2d::TrianglesCommand *batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _glProgramState, blendFunc, triangles, transform, transformFlags);
 #else
-				cocos2d::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _programState, blendFunc, triangles, transform, transformFlags);
+					cocos2d::TrianglesCommand *batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _programState, blendFunc, triangles, transform, transformFlags);
 #endif
 
-				if (_effect) {
-					V3F_C4B_T2F* vertex = batchedTriangles->getTriangles().verts;
-					Color darkTmp;
-					for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) {
-						Color lightCopy = color;
-						_effect->transform(vertex->vertices.x, vertex->vertices.y, vertex->texCoords.u, vertex->texCoords.v, lightCopy, darkTmp);
-						vertex->colors = ColorToColor4B(lightCopy);
-					}
-				} else {
-					V3F_C4B_T2F* vertex = batchedTriangles->getTriangles().verts;
-					for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) {
-						vertex->colors = color4B;
+					if (_effect) {
+						V3F_C4B_T2F *vertex = batchedTriangles->getTriangles().verts;
+						Color darkTmp;
+						for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) {
+							Color lightCopy = color;
+							_effect->transform(vertex->vertices.x, vertex->vertices.y, vertex->texCoords.u, vertex->texCoords.v, lightCopy, darkTmp);
+							vertex->colors = ColorToColor4B(lightCopy);
+						}
+					} else {
+						V3F_C4B_T2F *vertex = batchedTriangles->getTriangles().verts;
+						for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) {
+							vertex->colors = color4B;
+						}
 					}
 				}
-			}
-		} else {
-			// Two color tinting.
+			} else {
+				// Two color tinting.
 
-			if (_clipper->isClipping()) {
-				_clipper->clipTriangles((float*)&trianglesTwoColor.verts[0].position, trianglesTwoColor.indices, trianglesTwoColor.indexCount, (float*)&trianglesTwoColor.verts[0].texCoords, sizeof(V3F_C4B_C4B_T2F) / 4);
-				twoColorBatch->deallocateVertices(trianglesTwoColor.vertCount);
+				if (_clipper->isClipping()) {
+					_clipper->clipTriangles((float *) &trianglesTwoColor.verts[0].position, trianglesTwoColor.indices, trianglesTwoColor.indexCount, (float *) &trianglesTwoColor.verts[0].texCoords, sizeof(V3F_C4B_C4B_T2F) / 4);
+					twoColorBatch->deallocateVertices(trianglesTwoColor.vertCount);
 
-				if (_clipper->getClippedTriangles().size() == 0) {
-					_clipper->clipEnd(*slot);
-					continue;
-				}
+					if (_clipper->getClippedTriangles().size() == 0) {
+						_clipper->clipEnd(*slot);
+						continue;
+					}
 
-				trianglesTwoColor.vertCount = _clipper->getClippedVertices().size() / 2;
-				trianglesTwoColor.verts = twoColorBatch->allocateVertices(trianglesTwoColor.vertCount);
-				trianglesTwoColor.indexCount = _clipper->getClippedTriangles().size();
-				trianglesTwoColor.indices = twoColorBatch->allocateIndices(trianglesTwoColor.indexCount);
-				memcpy(trianglesTwoColor.indices, _clipper->getClippedTriangles().buffer(), sizeof(unsigned short) * _clipper->getClippedTriangles().size());
+					trianglesTwoColor.vertCount = _clipper->getClippedVertices().size() / 2;
+					trianglesTwoColor.verts = twoColorBatch->allocateVertices(trianglesTwoColor.vertCount);
+					trianglesTwoColor.indexCount = _clipper->getClippedTriangles().size();
+					trianglesTwoColor.indices = twoColorBatch->allocateIndices(trianglesTwoColor.indexCount);
+					memcpy(trianglesTwoColor.indices, _clipper->getClippedTriangles().buffer(), sizeof(unsigned short) * _clipper->getClippedTriangles().size());
 
 #if COCOS2D_VERSION < 0x00040000
-				TwoColorTrianglesCommand* batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture->getName(), _glProgramState, blendFunc, trianglesTwoColor, transform, transformFlags);
+					TwoColorTrianglesCommand *batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture->getName(), _glProgramState, blendFunc, trianglesTwoColor, transform, transformFlags);
 #else
-				TwoColorTrianglesCommand* batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _programState, blendFunc, trianglesTwoColor, transform, transformFlags);
+					TwoColorTrianglesCommand *batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _programState, blendFunc, trianglesTwoColor, transform, transformFlags);
 #endif
 
-				const float* verts = _clipper->getClippedVertices().buffer();
-				const float* uvs = _clipper->getClippedUVs().buffer();
-
-				if (_effect) {
-					V3F_C4B_C4B_T2F* vertex = batchedTriangles->getTriangles().verts;
-					for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv += 2, ++vertex) {
-						Color lightCopy = color;
-						Color darkCopy = darkColor;
-						vertex->position.x = verts[vv];
-						vertex->position.y = verts[vv + 1];
-						vertex->texCoords.u = uvs[vv];
-						vertex->texCoords.v = uvs[vv + 1];
-						_effect->transform(vertex->position.x, vertex->position.y, vertex->texCoords.u, vertex->texCoords.v, lightCopy, darkCopy);
-						vertex->color = ColorToColor4B(lightCopy);
-						vertex->color2 = ColorToColor4B(darkCopy);
+					const float *verts = _clipper->getClippedVertices().buffer();
+					const float *uvs = _clipper->getClippedUVs().buffer();
+
+					if (_effect) {
+						V3F_C4B_C4B_T2F *vertex = batchedTriangles->getTriangles().verts;
+						for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv += 2, ++vertex) {
+							Color lightCopy = color;
+							Color darkCopy = darkColor;
+							vertex->position.x = verts[vv];
+							vertex->position.y = verts[vv + 1];
+							vertex->texCoords.u = uvs[vv];
+							vertex->texCoords.v = uvs[vv + 1];
+							_effect->transform(vertex->position.x, vertex->position.y, vertex->texCoords.u, vertex->texCoords.v, lightCopy, darkCopy);
+							vertex->color = ColorToColor4B(lightCopy);
+							vertex->color2 = ColorToColor4B(darkCopy);
+						}
+					} else {
+						V3F_C4B_C4B_T2F *vertex = batchedTriangles->getTriangles().verts;
+						for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv += 2, ++vertex) {
+							vertex->position.x = verts[vv];
+							vertex->position.y = verts[vv + 1];
+							vertex->texCoords.u = uvs[vv];
+							vertex->texCoords.v = uvs[vv + 1];
+							vertex->color = color4B;
+							vertex->color2 = darkColor4B;
+						}
 					}
 				} else {
-					V3F_C4B_C4B_T2F* vertex = batchedTriangles->getTriangles().verts;
-					for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv += 2, ++vertex) {
-						vertex->position.x = verts[vv];
-						vertex->position.y = verts[vv + 1];
-						vertex->texCoords.u = uvs[vv];
-						vertex->texCoords.v = uvs[vv + 1];
-						vertex->color = color4B;
-						vertex->color2 = darkColor4B;
-					}
-				}
-			} else {
 
 #if COCOS2D_VERSION < 0x00040000
-				TwoColorTrianglesCommand* batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture->getName(), _glProgramState, blendFunc, trianglesTwoColor, transform, transformFlags);
+					TwoColorTrianglesCommand *batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture->getName(), _glProgramState, blendFunc, trianglesTwoColor, transform, transformFlags);
 #else
-				TwoColorTrianglesCommand* batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _programState, blendFunc, trianglesTwoColor, transform, transformFlags);
+					TwoColorTrianglesCommand *batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _programState, blendFunc, trianglesTwoColor, transform, transformFlags);
 #endif
 
-				if (_effect) {
-					V3F_C4B_C4B_T2F* vertex = batchedTriangles->getTriangles().verts;
-					for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) {
-						Color lightCopy = color;
-						Color darkCopy = darkColor;
-						_effect->transform(vertex->position.x, vertex->position.y, vertex->texCoords.u, vertex->texCoords.v, lightCopy, darkCopy);
-						vertex->color = ColorToColor4B(lightCopy);
-						vertex->color2 = ColorToColor4B(darkCopy);
-					}
-				} else {
-					V3F_C4B_C4B_T2F* vertex = batchedTriangles->getTriangles().verts;
-					for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) {
-						vertex->color = color4B;
-						vertex->color2 = darkColor4B;
+					if (_effect) {
+						V3F_C4B_C4B_T2F *vertex = batchedTriangles->getTriangles().verts;
+						for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) {
+							Color lightCopy = color;
+							Color darkCopy = darkColor;
+							_effect->transform(vertex->position.x, vertex->position.y, vertex->texCoords.u, vertex->texCoords.v, lightCopy, darkCopy);
+							vertex->color = ColorToColor4B(lightCopy);
+							vertex->color2 = ColorToColor4B(darkCopy);
+						}
+					} else {
+						V3F_C4B_C4B_T2F *vertex = batchedTriangles->getTriangles().verts;
+						for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) {
+							vertex->color = color4B;
+							vertex->color2 = darkColor4B;
+						}
 					}
 				}
 			}
+			_clipper->clipEnd(*slot);
 		}
-		_clipper->clipEnd(*slot);
-	}
-	_clipper->clipEnd();
-
-	if (lastTwoColorTrianglesCommand) {
-		Node* parent = this->getParent();
-
-		// We need to decide if we can postpone flushing the current batch. We can postpone if the next sibling node is a two color
-		// tinted skeleton with the same global-z.
-		// The parent->getChildrenCount() > 100 check is a hack as checking for a sibling is an O(n) operation, and if all children
-		// of this nodes parent are skeletons, we are in O(n2) territory.
-		if (!parent || parent->getChildrenCount() > 100 || getChildrenCount() != 0) {
-			lastTwoColorTrianglesCommand->setForceFlush(true);
-		} else {
-			const cocos2d::Vector<Node*>& children = parent->getChildren();
-			Node* sibling = nullptr;
-			for (ssize_t i = 0; i < children.size(); i++) {
-				if (children.at(i) == this) {
-					if (i < children.size() - 1) {
-						sibling = children.at(i+1);
-						break;
-					}
-				}
-			}
-			if (!sibling) {
+		_clipper->clipEnd();
+
+		if (lastTwoColorTrianglesCommand) {
+			Node *parent = this->getParent();
+
+			// We need to decide if we can postpone flushing the current batch. We can postpone if the next sibling node is a two color
+			// tinted skeleton with the same global-z.
+			// The parent->getChildrenCount() > 100 check is a hack as checking for a sibling is an O(n) operation, and if all children
+			// of this nodes parent are skeletons, we are in O(n2) territory.
+			if (!parent || parent->getChildrenCount() > 100 || getChildrenCount() != 0) {
 				lastTwoColorTrianglesCommand->setForceFlush(true);
 			} else {
-				SkeletonRenderer* siblingSkeleton = dynamic_cast<SkeletonRenderer*>(sibling);
-				if (!siblingSkeleton || // flush is next sibling isn't a SkeletonRenderer
-					!siblingSkeleton->isTwoColorTint() || // flush if next sibling isn't two color tinted
-					!siblingSkeleton->isVisible() || // flush if next sibling is two color tinted but not visible
-					(siblingSkeleton->getGlobalZOrder() != this->getGlobalZOrder())) { // flush if next sibling is two color tinted but z-order differs
+				const cocos2d::Vector<Node *> &children = parent->getChildren();
+				Node *sibling = nullptr;
+				for (ssize_t i = 0; i < children.size(); i++) {
+					if (children.at(i) == this) {
+						if (i < children.size() - 1) {
+							sibling = children.at(i + 1);
+							break;
+						}
+					}
+				}
+				if (!sibling) {
 					lastTwoColorTrianglesCommand->setForceFlush(true);
+				} else {
+					SkeletonRenderer *siblingSkeleton = dynamic_cast<SkeletonRenderer *>(sibling);
+					if (!siblingSkeleton ||                                               // flush is next sibling isn't a SkeletonRenderer
+						!siblingSkeleton->isTwoColorTint() ||                             // flush if next sibling isn't two color tinted
+						!siblingSkeleton->isVisible() ||                                  // flush if next sibling is two color tinted but not visible
+						(siblingSkeleton->getGlobalZOrder() != this->getGlobalZOrder())) {// flush if next sibling is two color tinted but z-order differs
+						lastTwoColorTrianglesCommand->setForceFlush(true);
+					}
 				}
 			}
 		}
-	}
 
-	if (_effect) _effect->end();
+		if (_effect) _effect->end();
 
-	if (_debugBoundingRect || _debugSlots || _debugBones || _debugMeshes) {
-		drawDebug(renderer, transform, transformFlags);
-	}
+		if (_debugBoundingRect || _debugSlots || _debugBones || _debugMeshes) {
+			drawDebug(renderer, transform, transformFlags);
+		}
 
-	VLA_FREE(worldCoords);
-}
+		VLA_FREE(worldCoords);
+	}
 
 
-void SkeletonRenderer::drawDebug (Renderer* renderer, const Mat4 &transform, uint32_t transformFlags) {
+	void SkeletonRenderer::drawDebug(Renderer *renderer, const Mat4 &transform, uint32_t transformFlags) {
 
 #if !defined(USE_MATRIX_STACK_PROJECTION_ONLY)
-	Director* director = Director::getInstance();
-	director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
-	director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, transform);
+		Director *director = Director::getInstance();
+		director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
+		director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, transform);
 #endif
 
-	DrawNode* drawNode = DrawNode::create();
-	drawNode->setGlobalZOrder(getGlobalZOrder());
+		DrawNode *drawNode = DrawNode::create();
+		drawNode->setGlobalZOrder(getGlobalZOrder());
 
-	// Draw bounding rectangle
-	if (_debugBoundingRect) {
+		// Draw bounding rectangle
+		if (_debugBoundingRect) {
 #if COCOS2D_VERSION < 0x00040000
-		glLineWidth(2);
+			glLineWidth(2);
 #else
-		drawNode->setLineWidth(2.0f);
+			drawNode->setLineWidth(2.0f);
 #endif
-		const cocos2d::Rect brect = getBoundingBox();
-		const Vec2 points[4] =
-		{
-			brect.origin,
-			{ brect.origin.x + brect.size.width, brect.origin.y },
-			{ brect.origin.x + brect.size.width, brect.origin.y + brect.size.height },
-			{ brect.origin.x, brect.origin.y + brect.size.height }
-		};
-		drawNode->drawPoly(points, 4, true, Color4F::GREEN);
-	}
+			const cocos2d::Rect brect = getBoundingBox();
+			const Vec2 points[4] =
+					{
+							brect.origin,
+							{brect.origin.x + brect.size.width, brect.origin.y},
+							{brect.origin.x + brect.size.width, brect.origin.y + brect.size.height},
+							{brect.origin.x, brect.origin.y + brect.size.height}};
+			drawNode->drawPoly(points, 4, true, Color4F::GREEN);
+		}
 
-	if (_debugSlots) {
-		// Slots.
-		// DrawPrimitives::setDrawColor4B(0, 0, 255, 255);
+		if (_debugSlots) {
+			// Slots.
+			// DrawPrimitives::setDrawColor4B(0, 0, 255, 255);
 #if COCOS2D_VERSION < 0x00040000
-		glLineWidth(2);
+			glLineWidth(2);
 #else
-		drawNode->setLineWidth(2.0f);
+			drawNode->setLineWidth(2.0f);
 #endif
-		V3F_C4B_T2F_Quad quad;
-		for (int i = 0, n = _skeleton->getSlots().size(); i < n; i++) {
-			Slot* slot = _skeleton->getDrawOrder()[i];
+			V3F_C4B_T2F_Quad quad;
+			for (int i = 0, n = _skeleton->getSlots().size(); i < n; i++) {
+				Slot *slot = _skeleton->getDrawOrder()[i];
 
-			if (!slot->getBone().isActive()) continue;
-			if (!slot->getAttachment() || !slot->getAttachment()->getRTTI().isExactly(RegionAttachment::rtti)) continue;
+				if (!slot->getBone().isActive()) continue;
+				if (!slot->getAttachment() || !slot->getAttachment()->getRTTI().isExactly(RegionAttachment::rtti)) continue;
 
-			if (slotIsOutRange(*slot, _startSlotIndex, _endSlotIndex)) {
-				continue;
-			}
+				if (slotIsOutRange(*slot, _startSlotIndex, _endSlotIndex)) {
+					continue;
+				}
 
-			RegionAttachment* attachment = (RegionAttachment*)slot->getAttachment();
-			float worldVertices[8];
-			attachment->computeWorldVertices(slot->getBone(), worldVertices, 0, 2);
-			const Vec2 points[4] =
-			{
-				{ worldVertices[0], worldVertices[1] },
-				{ worldVertices[2], worldVertices[3] },
-				{ worldVertices[4], worldVertices[5] },
-				{ worldVertices[6], worldVertices[7] }
-			};
-			drawNode->drawPoly(points, 4, true, Color4F::BLUE);
+				RegionAttachment *attachment = (RegionAttachment *) slot->getAttachment();
+				float worldVertices[8];
+				attachment->computeWorldVertices(slot->getBone(), worldVertices, 0, 2);
+				const Vec2 points[4] =
+						{
+								{worldVertices[0], worldVertices[1]},
+								{worldVertices[2], worldVertices[3]},
+								{worldVertices[4], worldVertices[5]},
+								{worldVertices[6], worldVertices[7]}};
+				drawNode->drawPoly(points, 4, true, Color4F::BLUE);
+			}
 		}
-	}
 
-	if (_debugBones) {
-		// Bone lengths.
+		if (_debugBones) {
+			// Bone lengths.
 #if COCOS2D_VERSION < 0x00040000
-		glLineWidth(2);
+			glLineWidth(2);
 #else
-		drawNode->setLineWidth(2.0f);
+			drawNode->setLineWidth(2.0f);
 #endif
-		for (int i = 0, n = _skeleton->getBones().size(); i < n; i++) {
-			Bone *bone = _skeleton->getBones()[i];
-			if (!bone->isActive()) continue;
-			float x = bone->getData().getLength() * bone->getA() + bone->getWorldX();
-			float y = bone->getData().getLength() * bone->getC() + bone->getWorldY();
-			drawNode->drawLine(Vec2(bone->getWorldX(), bone->getWorldY()), Vec2(x, y), Color4F::RED);
-		}
-		// Bone origins.
-		auto color = Color4F::BLUE; // Root bone is blue.
-		for (int i = 0, n = _skeleton->getBones().size(); i < n; i++) {
-			Bone *bone = _skeleton->getBones()[i];
-			if (!bone->isActive()) continue;
-			drawNode->drawPoint(Vec2(bone->getWorldX(), bone->getWorldY()), 4, color);
-			if (i == 0) color = Color4F::GREEN;
+			for (int i = 0, n = _skeleton->getBones().size(); i < n; i++) {
+				Bone *bone = _skeleton->getBones()[i];
+				if (!bone->isActive()) continue;
+				float x = bone->getData().getLength() * bone->getA() + bone->getWorldX();
+				float y = bone->getData().getLength() * bone->getC() + bone->getWorldY();
+				drawNode->drawLine(Vec2(bone->getWorldX(), bone->getWorldY()), Vec2(x, y), Color4F::RED);
+			}
+			// Bone origins.
+			auto color = Color4F::BLUE;// Root bone is blue.
+			for (int i = 0, n = _skeleton->getBones().size(); i < n; i++) {
+				Bone *bone = _skeleton->getBones()[i];
+				if (!bone->isActive()) continue;
+				drawNode->drawPoint(Vec2(bone->getWorldX(), bone->getWorldY()), 4, color);
+				if (i == 0) color = Color4F::GREEN;
+			}
 		}
-	}
 
-	if (_debugMeshes) {
-		// Meshes.
+		if (_debugMeshes) {
+			// Meshes.
 #if COCOS2D_VERSION < 0x00040000
-		glLineWidth(2);
+			glLineWidth(2);
 #else
-		drawNode->setLineWidth(2.0f);
+			drawNode->setLineWidth(2.0f);
 #endif
-		for (int i = 0, n = _skeleton->getSlots().size(); i < n; ++i) {
-			Slot* slot = _skeleton->getDrawOrder()[i];
-			if (!slot->getBone().isActive()) continue;
-			if (!slot->getAttachment() || !slot->getAttachment()->getRTTI().isExactly(MeshAttachment::rtti)) continue;
-			MeshAttachment* const mesh = static_cast<MeshAttachment*>(slot->getAttachment());
-			VLA(float, worldCoord, mesh->getWorldVerticesLength());
-			mesh->computeWorldVertices(*slot, 0, mesh->getWorldVerticesLength(), worldCoord, 0, 2);
-			for (size_t t = 0; t < mesh->getTriangles().size(); t += 3) {
-				// Fetch triangle indices
-				const int idx0 = mesh->getTriangles()[t + 0];
-				const int idx1 = mesh->getTriangles()[t + 1];
-				const int idx2 = mesh->getTriangles()[t + 2];
-				const Vec2 v[3] =
-				{
-					worldCoord + (idx0 * 2),
-					worldCoord + (idx1 * 2),
-					worldCoord + (idx2 * 2)
-				};
-				drawNode->drawPoly(v, 3, true, Color4F::YELLOW);
+			for (int i = 0, n = _skeleton->getSlots().size(); i < n; ++i) {
+				Slot *slot = _skeleton->getDrawOrder()[i];
+				if (!slot->getBone().isActive()) continue;
+				if (!slot->getAttachment() || !slot->getAttachment()->getRTTI().isExactly(MeshAttachment::rtti)) continue;
+				MeshAttachment *const mesh = static_cast<MeshAttachment *>(slot->getAttachment());
+				VLA(float, worldCoord, mesh->getWorldVerticesLength());
+				mesh->computeWorldVertices(*slot, 0, mesh->getWorldVerticesLength(), worldCoord, 0, 2);
+				for (size_t t = 0; t < mesh->getTriangles().size(); t += 3) {
+					// Fetch triangle indices
+					const int idx0 = mesh->getTriangles()[t + 0];
+					const int idx1 = mesh->getTriangles()[t + 1];
+					const int idx2 = mesh->getTriangles()[t + 2];
+					const Vec2 v[3] =
+							{
+									worldCoord + (idx0 * 2),
+									worldCoord + (idx1 * 2),
+									worldCoord + (idx2 * 2)};
+					drawNode->drawPoly(v, 3, true, Color4F::YELLOW);
+				}
+				VLA_FREE(worldCoord);
 			}
-			VLA_FREE(worldCoord);
 		}
-	}
 
-	drawNode->draw(renderer, transform, transformFlags);
+		drawNode->draw(renderer, transform, transformFlags);
 #if !defined(USE_MATRIX_STACK_PROJECTION_ONLY)
-	director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
+		director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
 #endif
-}
-
-cocos2d::Rect SkeletonRenderer::getBoundingBox () const {
-	const int coordCount = computeTotalCoordCount(*_skeleton, _startSlotIndex, _endSlotIndex);
-	if (coordCount == 0) return { 0, 0, 0, 0 };
-	VLA(float, worldCoords, coordCount);
-	transformWorldVertices(worldCoords, coordCount, *_skeleton, _startSlotIndex, _endSlotIndex);
-	const cocos2d::Rect bb = computeBoundingRect(worldCoords, coordCount / 2);
-	VLA_FREE(worldCoords);
-	return bb;
-}
-
-// --- Convenience methods for Skeleton_* functions.
-
-void SkeletonRenderer::updateWorldTransform() {
-	_skeleton->updateWorldTransform();
-}
-
-void SkeletonRenderer::setToSetupPose () {
-	_skeleton->setToSetupPose();
-}
-void SkeletonRenderer::setBonesToSetupPose () {
-	_skeleton->setBonesToSetupPose();
-}
-void SkeletonRenderer::setSlotsToSetupPose () {
-	_skeleton->setSlotsToSetupPose();
-}
-
-Bone* SkeletonRenderer::findBone (const std::string& boneName) const {
-	return _skeleton->findBone(boneName.c_str());
-}
-
-Slot* SkeletonRenderer::findSlot (const std::string& slotName) const {
-	return _skeleton->findSlot( slotName.c_str());
-}
-
-void SkeletonRenderer::setSkin (const std::string& skinName) {
-	_skeleton->setSkin(skinName.empty() ? 0 : skinName.c_str());
-}
-void SkeletonRenderer::setSkin (const char* skinName) {
-	_skeleton->setSkin(skinName);
-}
-
-Attachment* SkeletonRenderer::getAttachment (const std::string& slotName, const std::string& attachmentName) const {
-	return _skeleton->getAttachment(slotName.c_str(), attachmentName.c_str());
-}
-bool SkeletonRenderer::setAttachment (const std::string& slotName, const std::string& attachmentName) {
-	bool result = _skeleton->getAttachment(slotName.c_str(), attachmentName.empty() ? 0 : attachmentName.c_str()) ? true : false;
-	_skeleton->setAttachment(slotName.c_str(), attachmentName.empty() ? 0 : attachmentName.c_str());
-	return result;
-}
-bool SkeletonRenderer::setAttachment (const std::string& slotName, const char* attachmentName) {
-	bool result = _skeleton->getAttachment(slotName.c_str(), attachmentName) ? true : false;
-	_skeleton->setAttachment(slotName.c_str(), attachmentName);
-	return result;
-}
-
-void SkeletonRenderer::setTwoColorTint(bool enabled) {
+	}
+
+	cocos2d::Rect SkeletonRenderer::getBoundingBox() const {
+		const int coordCount = computeTotalCoordCount(*_skeleton, _startSlotIndex, _endSlotIndex);
+		if (coordCount == 0) return {0, 0, 0, 0};
+		VLA(float, worldCoords, coordCount);
+		transformWorldVertices(worldCoords, coordCount, *_skeleton, _startSlotIndex, _endSlotIndex);
+		const cocos2d::Rect bb = computeBoundingRect(worldCoords, coordCount / 2);
+		VLA_FREE(worldCoords);
+		return bb;
+	}
+
+	// --- Convenience methods for Skeleton_* functions.
+
+	void SkeletonRenderer::updateWorldTransform() {
+		_skeleton->updateWorldTransform();
+	}
+
+	void SkeletonRenderer::setToSetupPose() {
+		_skeleton->setToSetupPose();
+	}
+	void SkeletonRenderer::setBonesToSetupPose() {
+		_skeleton->setBonesToSetupPose();
+	}
+	void SkeletonRenderer::setSlotsToSetupPose() {
+		_skeleton->setSlotsToSetupPose();
+	}
+
+	Bone *SkeletonRenderer::findBone(const std::string &boneName) const {
+		return _skeleton->findBone(boneName.c_str());
+	}
+
+	Slot *SkeletonRenderer::findSlot(const std::string &slotName) const {
+		return _skeleton->findSlot(slotName.c_str());
+	}
+
+	void SkeletonRenderer::setSkin(const std::string &skinName) {
+		_skeleton->setSkin(skinName.empty() ? 0 : skinName.c_str());
+	}
+	void SkeletonRenderer::setSkin(const char *skinName) {
+		_skeleton->setSkin(skinName);
+	}
+
+	Attachment *SkeletonRenderer::getAttachment(const std::string &slotName, const std::string &attachmentName) const {
+		return _skeleton->getAttachment(slotName.c_str(), attachmentName.c_str());
+	}
+	bool SkeletonRenderer::setAttachment(const std::string &slotName, const std::string &attachmentName) {
+		bool result = _skeleton->getAttachment(slotName.c_str(), attachmentName.empty() ? 0 : attachmentName.c_str()) ? true : false;
+		_skeleton->setAttachment(slotName.c_str(), attachmentName.empty() ? 0 : attachmentName.c_str());
+		return result;
+	}
+	bool SkeletonRenderer::setAttachment(const std::string &slotName, const char *attachmentName) {
+		bool result = _skeleton->getAttachment(slotName.c_str(), attachmentName) ? true : false;
+		_skeleton->setAttachment(slotName.c_str(), attachmentName);
+		return result;
+	}
+
+	void SkeletonRenderer::setTwoColorTint(bool enabled) {
 #if COCOS2D_VERSION >= 0x00040000
-	_twoColorTint = enabled;
+		_twoColorTint = enabled;
 #endif
-	setupGLProgramState(enabled);
-}
+		setupGLProgramState(enabled);
+	}
 
-bool SkeletonRenderer::isTwoColorTint() {
+	bool SkeletonRenderer::isTwoColorTint() {
 #if COCOS2D_VERSION < 0x00040000
-	return getGLProgramState() == SkeletonTwoColorBatch::getInstance()->getTwoColorTintProgramState();
+		return getGLProgramState() == SkeletonTwoColorBatch::getInstance()->getTwoColorTintProgramState();
 #else
-	return _twoColorTint;
+		return _twoColorTint;
 #endif
-}
-
-void SkeletonRenderer::setVertexEffect(VertexEffect *effect) {
-	this->_effect = effect;
-}
-
-void SkeletonRenderer::setSlotsRange(int startSlotIndex, int endSlotIndex) {
-	_startSlotIndex = startSlotIndex == -1 ? 0 : startSlotIndex;
-	_endSlotIndex = endSlotIndex == -1 ? std::numeric_limits<int>::max() : endSlotIndex;
-}
-
-Skeleton* SkeletonRenderer::getSkeleton () const {
-	return _skeleton;
-}
-
-void SkeletonRenderer::setTimeScale (float scale) {
-	_timeScale = scale;
-}
-float SkeletonRenderer::getTimeScale () const {
-	return _timeScale;
-}
-
-void SkeletonRenderer::setDebugSlotsEnabled (bool enabled) {
-	_debugSlots = enabled;
-}
-bool SkeletonRenderer::getDebugSlotsEnabled () const {
-	return _debugSlots;
-}
-
-void SkeletonRenderer::setDebugBonesEnabled (bool enabled) {
-	_debugBones = enabled;
-}
-bool SkeletonRenderer::getDebugBonesEnabled () const {
-	return _debugBones;
-}
-
-void SkeletonRenderer::setDebugMeshesEnabled (bool enabled) {
-	_debugMeshes = enabled;
-}
-bool SkeletonRenderer::getDebugMeshesEnabled () const {
-	return _debugMeshes;
-}
-
-void SkeletonRenderer::setDebugBoundingRectEnabled(bool enabled) {
-	_debugBoundingRect = enabled;
-}
-
-bool SkeletonRenderer::getDebugBoundingRectEnabled() const {
-	return _debugBoundingRect;
-}
-
-void SkeletonRenderer::onEnter () {
+	}
+
+	void SkeletonRenderer::setVertexEffect(VertexEffect *effect) {
+		this->_effect = effect;
+	}
+
+	void SkeletonRenderer::setSlotsRange(int startSlotIndex, int endSlotIndex) {
+		_startSlotIndex = startSlotIndex == -1 ? 0 : startSlotIndex;
+		_endSlotIndex = endSlotIndex == -1 ? std::numeric_limits<int>::max() : endSlotIndex;
+	}
+
+	Skeleton *SkeletonRenderer::getSkeleton() const {
+		return _skeleton;
+	}
+
+	void SkeletonRenderer::setTimeScale(float scale) {
+		_timeScale = scale;
+	}
+	float SkeletonRenderer::getTimeScale() const {
+		return _timeScale;
+	}
+
+	void SkeletonRenderer::setDebugSlotsEnabled(bool enabled) {
+		_debugSlots = enabled;
+	}
+	bool SkeletonRenderer::getDebugSlotsEnabled() const {
+		return _debugSlots;
+	}
+
+	void SkeletonRenderer::setDebugBonesEnabled(bool enabled) {
+		_debugBones = enabled;
+	}
+	bool SkeletonRenderer::getDebugBonesEnabled() const {
+		return _debugBones;
+	}
+
+	void SkeletonRenderer::setDebugMeshesEnabled(bool enabled) {
+		_debugMeshes = enabled;
+	}
+	bool SkeletonRenderer::getDebugMeshesEnabled() const {
+		return _debugMeshes;
+	}
+
+	void SkeletonRenderer::setDebugBoundingRectEnabled(bool enabled) {
+		_debugBoundingRect = enabled;
+	}
+
+	bool SkeletonRenderer::getDebugBoundingRectEnabled() const {
+		return _debugBoundingRect;
+	}
+
+	void SkeletonRenderer::onEnter() {
 #if CC_ENABLE_SCRIPT_BINDING && COCOS2D_VERSION < 0x00040000
-	if (_scriptType == kScriptTypeJavascript && ScriptEngineManager::sendNodeEventToJSExtended(this, kNodeOnEnter)) return;
+		if (_scriptType == kScriptTypeJavascript && ScriptEngineManager::sendNodeEventToJSExtended(this, kNodeOnEnter)) return;
 #endif
-	Node::onEnter();
-	scheduleUpdate();
-}
+		Node::onEnter();
+		scheduleUpdate();
+	}
 
-void SkeletonRenderer::onExit () {
+	void SkeletonRenderer::onExit() {
 #if CC_ENABLE_SCRIPT_BINDING && COCOS2D_VERSION < 0x00040000
-	if (_scriptType == kScriptTypeJavascript && ScriptEngineManager::sendNodeEventToJSExtended(this, kNodeOnExit)) return;
+		if (_scriptType == kScriptTypeJavascript && ScriptEngineManager::sendNodeEventToJSExtended(this, kNodeOnExit)) return;
 #endif
-	Node::onExit();
-	unscheduleUpdate();
-}
-
-// --- CCBlendProtocol
-
-const BlendFunc& SkeletonRenderer::getBlendFunc () const {
-	return _blendFunc;
-}
-
-void SkeletonRenderer::setBlendFunc (const BlendFunc &blendFunc) {
-	_blendFunc = blendFunc;
-}
-
-void SkeletonRenderer::setOpacityModifyRGB (bool value) {
-	_premultipliedAlpha = value;
-}
-
-bool SkeletonRenderer::isOpacityModifyRGB () const {
-	return _premultipliedAlpha;
-}
-
-namespace {
-	cocos2d::Rect computeBoundingRect(const float* coords, int vertexCount) {
-		assert(coords);
-		assert(vertexCount > 0);
-
-		const float* v = coords;
-		float minX = v[0];
-		float minY = v[1];
-		float maxX = minX;
-		float maxY = minY;
-		for (int i = 1; i < vertexCount; ++i) {
-			v += 2;
-			float x = v[0];
-			float y = v[1];
-			minX = std::min(minX, x);
-			minY = std::min(minY, y);
-			maxX = std::max(maxX, x);
-			maxY = std::max(maxY, y);
-		}
-		return { minX, minY, maxX - minX, maxY - minY };
+		Node::onExit();
+		unscheduleUpdate();
 	}
 
-	bool slotIsOutRange(Slot& slot, int startSlotIndex, int endSlotIndex) {
-		const int index = slot.getData().getIndex();
-		return startSlotIndex > index || endSlotIndex < index;
+	// --- CCBlendProtocol
+
+	const BlendFunc &SkeletonRenderer::getBlendFunc() const {
+		return _blendFunc;
 	}
 
-	bool nothingToDraw(Slot& slot, int startSlotIndex, int endSlotIndex) {
-		Attachment *attachment = slot.getAttachment();
-		if (!attachment ||
-			slotIsOutRange(slot, startSlotIndex, endSlotIndex) ||
-			!slot.getBone().isActive() ||
-			slot.getColor().a == 0)
-			return true;
-		if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
-			if (static_cast<RegionAttachment*>(attachment)->getColor().a == 0)
-				return true;
-		}
-		else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
-			if (static_cast<MeshAttachment*>(attachment)->getColor().a == 0)
-				return true;
-		}
-		return false;
+	void SkeletonRenderer::setBlendFunc(const BlendFunc &blendFunc) {
+		_blendFunc = blendFunc;
 	}
 
-	int computeTotalCoordCount(Skeleton& skeleton, int startSlotIndex, int endSlotIndex) {
-		int coordCount = 0;
-		for (size_t i = 0; i < skeleton.getSlots().size(); ++i) {
-			Slot& slot = *skeleton.getSlots()[i];
-			if (nothingToDraw(slot, startSlotIndex, endSlotIndex)) {
-				continue;
+	void SkeletonRenderer::setOpacityModifyRGB(bool value) {
+		_premultipliedAlpha = value;
+	}
+
+	bool SkeletonRenderer::isOpacityModifyRGB() const {
+		return _premultipliedAlpha;
+	}
+
+	namespace {
+		cocos2d::Rect computeBoundingRect(const float *coords, int vertexCount) {
+			assert(coords);
+			assert(vertexCount > 0);
+
+			const float *v = coords;
+			float minX = v[0];
+			float minY = v[1];
+			float maxX = minX;
+			float maxY = minY;
+			for (int i = 1; i < vertexCount; ++i) {
+				v += 2;
+				float x = v[0];
+				float y = v[1];
+				minX = std::min(minX, x);
+				minY = std::min(minY, y);
+				maxX = std::max(maxX, x);
+				maxY = std::max(maxY, y);
 			}
-			Attachment* const attachment = slot.getAttachment();
+			return {minX, minY, maxX - minX, maxY - minY};
+		}
+
+		bool slotIsOutRange(Slot &slot, int startSlotIndex, int endSlotIndex) {
+			const int index = slot.getData().getIndex();
+			return startSlotIndex > index || endSlotIndex < index;
+		}
+
+		bool nothingToDraw(Slot &slot, int startSlotIndex, int endSlotIndex) {
+			Attachment *attachment = slot.getAttachment();
+			if (!attachment ||
+				slotIsOutRange(slot, startSlotIndex, endSlotIndex) ||
+				!slot.getBone().isActive() ||
+				slot.getColor().a == 0)
+				return true;
 			if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
-				coordCount += 8;
+				if (static_cast<RegionAttachment *>(attachment)->getColor().a == 0)
+					return true;
+			} else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
+				if (static_cast<MeshAttachment *>(attachment)->getColor().a == 0)
+					return true;
 			}
-			else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
-				MeshAttachment* const mesh = static_cast<MeshAttachment*>(attachment);
-				coordCount += mesh->getWorldVerticesLength();
+			return false;
+		}
+
+		int computeTotalCoordCount(Skeleton &skeleton, int startSlotIndex, int endSlotIndex) {
+			int coordCount = 0;
+			for (size_t i = 0; i < skeleton.getSlots().size(); ++i) {
+				Slot &slot = *skeleton.getSlots()[i];
+				if (nothingToDraw(slot, startSlotIndex, endSlotIndex)) {
+					continue;
+				}
+				Attachment *const attachment = slot.getAttachment();
+				if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
+					coordCount += 8;
+				} else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
+					MeshAttachment *const mesh = static_cast<MeshAttachment *>(attachment);
+					coordCount += mesh->getWorldVerticesLength();
+				}
 			}
+			return coordCount;
 		}
-		return coordCount;
-	}
 
 
-	void transformWorldVertices(float* dstCoord, int coordCount, Skeleton& skeleton, int startSlotIndex, int endSlotIndex) {
-		float* dstPtr = dstCoord;
+		void transformWorldVertices(float *dstCoord, int coordCount, Skeleton &skeleton, int startSlotIndex, int endSlotIndex) {
+			float *dstPtr = dstCoord;
 #ifndef NDEBUG
-		float* const dstEnd = dstCoord + coordCount;
+			float *const dstEnd = dstCoord + coordCount;
 #endif
-		for (size_t i = 0; i < skeleton.getSlots().size(); ++i) {
-			/*const*/ Slot& slot = *skeleton.getDrawOrder()[i]; // match the draw order of SkeletonRenderer::Draw
-			if (nothingToDraw(slot, startSlotIndex, endSlotIndex)) {
-				continue;
-			}
-			Attachment* const attachment = slot.getAttachment();
-			if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
-				RegionAttachment* const regionAttachment = static_cast<RegionAttachment*>(attachment);
-				assert(dstPtr + 8 <= dstEnd);
-				regionAttachment->computeWorldVertices(slot.getBone(), dstPtr, 0, 2);
-				dstPtr += 8;
-			} else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
-				MeshAttachment* const mesh = static_cast<MeshAttachment*>(attachment);
-				assert(dstPtr + mesh->getWorldVerticesLength() <= dstEnd);
-				mesh->computeWorldVertices(slot, 0, mesh->getWorldVerticesLength(), dstPtr, 0, 2);
-				dstPtr += mesh->getWorldVerticesLength();
+			for (size_t i = 0; i < skeleton.getSlots().size(); ++i) {
+				/*const*/ Slot &slot = *skeleton.getDrawOrder()[i];// match the draw order of SkeletonRenderer::Draw
+				if (nothingToDraw(slot, startSlotIndex, endSlotIndex)) {
+					continue;
+				}
+				Attachment *const attachment = slot.getAttachment();
+				if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
+					RegionAttachment *const regionAttachment = static_cast<RegionAttachment *>(attachment);
+					assert(dstPtr + 8 <= dstEnd);
+					regionAttachment->computeWorldVertices(slot.getBone(), dstPtr, 0, 2);
+					dstPtr += 8;
+				} else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
+					MeshAttachment *const mesh = static_cast<MeshAttachment *>(attachment);
+					assert(dstPtr + mesh->getWorldVerticesLength() <= dstEnd);
+					mesh->computeWorldVertices(slot, 0, mesh->getWorldVerticesLength(), dstPtr, 0, 2);
+					dstPtr += mesh->getWorldVerticesLength();
+				}
 			}
+			assert(dstPtr == dstEnd);
 		}
-		assert(dstPtr == dstEnd);
-	}
 
-	void interleaveCoordinates(float* dst, const float* src, int count, int dstStride) {
-		if (dstStride == 2) {
-			memcpy(dst, src, sizeof(float) * count * 2);
-		} else {
-			for (int i = 0; i < count; ++i) {
-				dst[0] = src[0];
-				dst[1] = src[1];
-				dst += dstStride;
-				src += 2;
+		void interleaveCoordinates(float *dst, const float *src, int count, int dstStride) {
+			if (dstStride == 2) {
+				memcpy(dst, src, sizeof(float) * count * 2);
+			} else {
+				for (int i = 0; i < count; ++i) {
+					dst[0] = src[0];
+					dst[1] = src[1];
+					dst += dstStride;
+					src += 2;
+				}
 			}
 		}
 
-	}
-
-	BlendFunc makeBlendFunc(BlendMode blendMode, bool premultipliedAlpha) {
-		BlendFunc blendFunc;
+		BlendFunc makeBlendFunc(BlendMode blendMode, bool premultipliedAlpha) {
+			BlendFunc blendFunc;
 
 #if COCOS2D_VERSION < 0x00040000
-		switch (blendMode) {
-		case BlendMode_Additive:
-			blendFunc.src = premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA;
-			blendFunc.dst = GL_ONE;
-			break;
-		case BlendMode_Multiply:
-			blendFunc.src = GL_DST_COLOR;
-			blendFunc.dst = GL_ONE_MINUS_SRC_ALPHA;
-			break;
-		case BlendMode_Screen:
-			blendFunc.src = GL_ONE;
-			blendFunc.dst = GL_ONE_MINUS_SRC_COLOR;
-			break;
-		default:
-			blendFunc.src = premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA;
-			blendFunc.dst = GL_ONE_MINUS_SRC_ALPHA;
-			break;
-		}
+			switch (blendMode) {
+				case BlendMode_Additive:
+					blendFunc.src = premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA;
+					blendFunc.dst = GL_ONE;
+					break;
+				case BlendMode_Multiply:
+					blendFunc.src = GL_DST_COLOR;
+					blendFunc.dst = GL_ONE_MINUS_SRC_ALPHA;
+					break;
+				case BlendMode_Screen:
+					blendFunc.src = GL_ONE;
+					blendFunc.dst = GL_ONE_MINUS_SRC_COLOR;
+					break;
+				default:
+					blendFunc.src = premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA;
+					blendFunc.dst = GL_ONE_MINUS_SRC_ALPHA;
+					break;
+			}
 #else
-		switch (blendMode) {
-			case BlendMode_Additive:
-				blendFunc.src = premultipliedAlpha ? backend::BlendFactor::ONE : backend::BlendFactor::SRC_ALPHA;
-				blendFunc.dst = backend::BlendFactor::ONE;
-				break;
-			case BlendMode_Multiply:
-				blendFunc.src = backend::BlendFactor::DST_COLOR;
-				blendFunc.dst = backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
-				break;
-			case BlendMode_Screen:
-				blendFunc.src = backend::BlendFactor::ONE;
-				blendFunc.dst = backend::BlendFactor::ONE_MINUS_SRC_COLOR;
-				break;
-			default:
-				blendFunc.src = premultipliedAlpha ? backend::BlendFactor::ONE : backend::BlendFactor::SRC_ALPHA;
-				blendFunc.dst = backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
-		}
+			switch (blendMode) {
+				case BlendMode_Additive:
+					blendFunc.src = premultipliedAlpha ? backend::BlendFactor::ONE : backend::BlendFactor::SRC_ALPHA;
+					blendFunc.dst = backend::BlendFactor::ONE;
+					break;
+				case BlendMode_Multiply:
+					blendFunc.src = backend::BlendFactor::DST_COLOR;
+					blendFunc.dst = backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
+					break;
+				case BlendMode_Screen:
+					blendFunc.src = backend::BlendFactor::ONE;
+					blendFunc.dst = backend::BlendFactor::ONE_MINUS_SRC_COLOR;
+					break;
+				default:
+					blendFunc.src = premultipliedAlpha ? backend::BlendFactor::ONE : backend::BlendFactor::SRC_ALPHA;
+					blendFunc.dst = backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
+			}
 #endif
-		return blendFunc;
-	}
+			return blendFunc;
+		}
 
 
-	bool cullRectangle(Renderer* renderer, const Mat4& transform, const cocos2d::Rect& rect) {
-		if (Camera::getVisitingCamera() == nullptr)
-			return false;
+		bool cullRectangle(Renderer *renderer, const Mat4 &transform, const cocos2d::Rect &rect) {
+			if (Camera::getVisitingCamera() == nullptr)
+				return false;
 
-		auto director = Director::getInstance();
-		auto scene = director->getRunningScene();
+			auto director = Director::getInstance();
+			auto scene = director->getRunningScene();
 
-		if (!scene || (scene && Camera::getDefaultCamera() != Camera::getVisitingCamera()))
-			return false;
+			if (!scene || (scene && Camera::getDefaultCamera() != Camera::getVisitingCamera()))
+				return false;
 
-		Rect visibleRect(director->getVisibleOrigin(), director->getVisibleSize());
-
-		// transform center point to screen space
-		float hSizeX = rect.size.width/2;
-		float hSizeY = rect.size.height/2;
-		Vec3 v3p(rect.origin.x + hSizeX, rect.origin.y + hSizeY, 0);
-		transform.transformPoint(&v3p);
-		Vec2 v2p = Camera::getVisitingCamera()->projectGL(v3p);
-
-		// convert content size to world coordinates
-		float wshw = std::max(fabsf(hSizeX * transform.m[0] + hSizeY * transform.m[4]), fabsf(hSizeX * transform.m[0] - hSizeY * transform.m[4]));
-		float wshh = std::max(fabsf(hSizeX * transform.m[1] + hSizeY * transform.m[5]), fabsf(hSizeX * transform.m[1] - hSizeY * transform.m[5]));
-
-		// enlarge visible rect half size in screen coord
-		visibleRect.origin.x -= wshw;
-		visibleRect.origin.y -= wshh;
-		visibleRect.size.width += wshw * 2;
-		visibleRect.size.height += wshh * 2;
-		return !visibleRect.containsPoint(v2p);
-	}
+			Rect visibleRect(director->getVisibleOrigin(), director->getVisibleSize());
 
+			// transform center point to screen space
+			float hSizeX = rect.size.width / 2;
+			float hSizeY = rect.size.height / 2;
+			Vec3 v3p(rect.origin.x + hSizeX, rect.origin.y + hSizeY, 0);
+			transform.transformPoint(&v3p);
+			Vec2 v2p = Camera::getVisitingCamera()->projectGL(v3p);
 
-	Color4B ColorToColor4B(const Color& color) {
-		return { (uint8_t)(color.r * 255.f), (uint8_t)(color.g * 255.f), (uint8_t)(color.b * 255.f), (uint8_t)(color.a * 255.f) };
-	}
-}
+			// convert content size to world coordinates
+			float wshw = std::max(fabsf(hSizeX * transform.m[0] + hSizeY * transform.m[4]), fabsf(hSizeX * transform.m[0] - hSizeY * transform.m[4]));
+			float wshh = std::max(fabsf(hSizeX * transform.m[1] + hSizeY * transform.m[5]), fabsf(hSizeX * transform.m[1] - hSizeY * transform.m[5]));
+
+			// enlarge visible rect half size in screen coord
+			visibleRect.origin.x -= wshw;
+			visibleRect.origin.y -= wshh;
+			visibleRect.size.width += wshw * 2;
+			visibleRect.size.height += wshh * 2;
+			return !visibleRect.containsPoint(v2p);
+		}
+
+
+		Color4B ColorToColor4B(const Color &color) {
+			return {(uint8_t) (color.r * 255.f), (uint8_t) (color.g * 255.f), (uint8_t) (color.b * 255.f), (uint8_t) (color.a * 255.f)};
+		}
+	}// namespace
 
-}
+}// namespace spine

+ 48 - 49
spine-cocos2dx/src/spine/SkeletonRenderer.h

@@ -38,21 +38,21 @@ namespace spine {
 	class AttachmentVertices;
 
 	/* Draws a skeleton. */
-	class SkeletonRenderer: public cocos2d::Node, public cocos2d::BlendProtocol {
+	class SkeletonRenderer : public cocos2d::Node, public cocos2d::BlendProtocol {
 	public:
 		CREATE_FUNC(SkeletonRenderer);
-		static SkeletonRenderer* createWithSkeleton(Skeleton* skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false);
-		static SkeletonRenderer* createWithData (SkeletonData* skeletonData, bool ownsSkeletonData = false);
-		static SkeletonRenderer* createWithFile (const std::string& skeletonDataFile, Atlas* atlas, float scale = 1);
-		static SkeletonRenderer* createWithFile (const std::string& skeletonDataFile, const std::string& atlasFile, float scale = 1);
+		static SkeletonRenderer *createWithSkeleton(Skeleton *skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false);
+		static SkeletonRenderer *createWithData(SkeletonData *skeletonData, bool ownsSkeletonData = false);
+		static SkeletonRenderer *createWithFile(const std::string &skeletonDataFile, Atlas *atlas, float scale = 1);
+		static SkeletonRenderer *createWithFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale = 1);
 
-		void update (float deltaTime) override;
-		void draw (cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t transformFlags) override;
-		cocos2d::Rect getBoundingBox () const override;
-		void onEnter () override;
-		void onExit () override;
+		void update(float deltaTime) override;
+		void draw(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t transformFlags) override;
+		cocos2d::Rect getBoundingBox() const override;
+		void onEnter() override;
+		void onExit() override;
 
-		Skeleton* getSkeleton() const;
+		Skeleton *getSkeleton() const;
 
 		void setTimeScale(float scale);
 		float getTimeScale() const;
@@ -71,31 +71,31 @@ namespace spine {
 		bool getDebugBoundingRectEnabled() const;
 
 		// --- Convenience methods for common Skeleton_* functions.
-		void updateWorldTransform ();
+		void updateWorldTransform();
 
-		void setToSetupPose ();
-		void setBonesToSetupPose ();
-		void setSlotsToSetupPose ();
+		void setToSetupPose();
+		void setBonesToSetupPose();
+		void setSlotsToSetupPose();
 
 		/* Returns 0 if the bone was not found. */
-		Bone* findBone (const std::string& boneName) const;
+		Bone *findBone(const std::string &boneName) const;
 		/* Returns 0 if the slot was not found. */
-		Slot* findSlot (const std::string& slotName) const;
+		Slot *findSlot(const std::string &slotName) const;
 
 		/* Sets the skin used to look up attachments not found in the SkeletonData defaultSkin. Attachments from the new skin are
 		 * attached if the corresponding attachment from the old skin was attached.
 		 * @param skin May be empty string ("") for no skin.*/
-		void setSkin (const std::string& skinName);
+		void setSkin(const std::string &skinName);
 		/** @param skin May be 0 for no skin.*/
-		void setSkin (const char* skinName);
+		void setSkin(const char *skinName);
 
 		/* Returns 0 if the slot or attachment was not found. */
-		Attachment* getAttachment (const std::string& slotName, const std::string& attachmentName) const;
+		Attachment *getAttachment(const std::string &slotName, const std::string &attachmentName) const;
 		/* Returns false if the slot or attachment was not found.
 		 * @param attachmentName May be empty string ("") for no attachment. */
-		bool setAttachment (const std::string& slotName, const std::string& attachmentName);
+		bool setAttachment(const std::string &slotName, const std::string &attachmentName);
 		/* @param attachmentName May be 0 for no attachment. */
-		bool setAttachment (const std::string& slotName, const char* attachmentName);
+		bool setAttachment(const std::string &slotName, const char *attachmentName);
 
 		/* Enables/disables two color tinting for this instance. May break batching */
 		void setTwoColorTint(bool enabled);
@@ -103,56 +103,55 @@ namespace spine {
 		bool isTwoColorTint();
 
 		/* Sets the vertex effect to be used, set to 0 to disable vertex effects */
-		void setVertexEffect(VertexEffect* effect);
+		void setVertexEffect(VertexEffect *effect);
 
 		/* Sets the range of slots that should be rendered. Use -1, -1 to clear the range */
 		void setSlotsRange(int startSlotIndex, int endSlotIndex);
 
 		// --- BlendProtocol
-		void setBlendFunc (const cocos2d::BlendFunc& blendFunc)override;
-		const cocos2d::BlendFunc& getBlendFunc () const override;
-		void setOpacityModifyRGB (bool value) override;
-		bool isOpacityModifyRGB () const override;
+		void setBlendFunc(const cocos2d::BlendFunc &blendFunc) override;
+		const cocos2d::BlendFunc &getBlendFunc() const override;
+		void setOpacityModifyRGB(bool value) override;
+		bool isOpacityModifyRGB() const override;
 
-	CC_CONSTRUCTOR_ACCESS:
-		SkeletonRenderer ();
-		SkeletonRenderer(Skeleton* skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false, bool ownsAtlas = false);
-		SkeletonRenderer (SkeletonData* skeletonData, bool ownsSkeletonData = false);
-		SkeletonRenderer (const std::string& skeletonDataFile, Atlas* atlas, float scale = 1);
-		SkeletonRenderer (const std::string& skeletonDataFile, const std::string& atlasFile, float scale = 1);
+		CC_CONSTRUCTOR_ACCESS : SkeletonRenderer();
+		SkeletonRenderer(Skeleton *skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false, bool ownsAtlas = false);
+		SkeletonRenderer(SkeletonData *skeletonData, bool ownsSkeletonData = false);
+		SkeletonRenderer(const std::string &skeletonDataFile, Atlas *atlas, float scale = 1);
+		SkeletonRenderer(const std::string &skeletonDataFile, const std::string &atlasFile, float scale = 1);
 
-		virtual ~SkeletonRenderer ();
+		virtual ~SkeletonRenderer();
 
-		void initWithSkeleton(Skeleton* skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false, bool ownsAtlas = false);
-		void initWithData (SkeletonData* skeletonData, bool ownsSkeletonData = false);
-		void initWithJsonFile (const std::string& skeletonDataFile, Atlas* atlas, float scale = 1);
-		void initWithJsonFile (const std::string& skeletonDataFile, const std::string& atlasFile, float scale = 1);
-		void initWithBinaryFile (const std::string& skeletonDataFile, Atlas* atlas, float scale = 1);
-		void initWithBinaryFile (const std::string& skeletonDataFile, const std::string& atlasFile, float scale = 1);
+		void initWithSkeleton(Skeleton *skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false, bool ownsAtlas = false);
+		void initWithData(SkeletonData *skeletonData, bool ownsSkeletonData = false);
+		void initWithJsonFile(const std::string &skeletonDataFile, Atlas *atlas, float scale = 1);
+		void initWithJsonFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale = 1);
+		void initWithBinaryFile(const std::string &skeletonDataFile, Atlas *atlas, float scale = 1);
+		void initWithBinaryFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale = 1);
 
-		virtual void initialize ();
+		virtual void initialize();
 
 	protected:
-		void setSkeletonData (SkeletonData* skeletonData, bool ownsSkeletonData);
+		void setSkeletonData(SkeletonData *skeletonData, bool ownsSkeletonData);
 		void setupGLProgramState(bool twoColorTintEnabled);
-		virtual void drawDebug (cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t transformFlags);
+		virtual void drawDebug(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t transformFlags);
 
 		bool _ownsSkeletonData;
 		bool _ownsSkeleton;
 		bool _ownsAtlas = false;
-		Atlas* _atlas;
-		AttachmentLoader* _attachmentLoader;
+		Atlas *_atlas;
+		AttachmentLoader *_attachmentLoader;
 		cocos2d::CustomCommand _debugCommand;
 		cocos2d::BlendFunc _blendFunc;
 		bool _premultipliedAlpha;
-		Skeleton* _skeleton;
+		Skeleton *_skeleton;
 		float _timeScale;
 		bool _debugSlots;
 		bool _debugBones;
 		bool _debugMeshes;
 		bool _debugBoundingRect;
-		SkeletonClipping* _clipper;
-		VertexEffect* _effect;
+		SkeletonClipping *_clipper;
+		VertexEffect *_effect;
 		cocos2d::Rect _boundingRect;
 
 		int _startSlotIndex;
@@ -160,6 +159,6 @@ namespace spine {
 		bool _twoColorTint;
 	};
 
-}
+}// namespace spine
 
 #endif /* SPINE_SKELETONRENDERER_H_ */

+ 189 - 189
spine-cocos2dx/src/spine/spine-cocos2dx.cpp

@@ -1,189 +1,189 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#include <spine/spine-cocos2dx.h>
-#include <spine/Extension.h>
-#include <spine/AttachmentVertices.h>
-
-USING_NS_CC;
-using namespace spine;
-
-static void deleteAttachmentVertices (void* vertices) {
-	delete (AttachmentVertices *) vertices;
-}
-
-static unsigned short quadTriangles[6] = {0, 1, 2, 2, 3, 0};
-
-static void setAttachmentVertices(RegionAttachment* attachment) {
-	AtlasRegion* region = (AtlasRegion*)attachment->getRendererObject();
-	AttachmentVertices* attachmentVertices = new AttachmentVertices((Texture2D*)region->page->getRendererObject(), 4, quadTriangles, 6);
-	V3F_C4B_T2F* vertices = attachmentVertices->_triangles->verts;
-	for (int i = 0, ii = 0; i < 4; ++i, ii += 2) {
-		vertices[i].texCoords.u = attachment->getUVs()[ii];
-		vertices[i].texCoords.v = attachment->getUVs()[ii + 1];
-	}
-	attachment->setRendererObject(attachmentVertices, deleteAttachmentVertices);
-}
-
-static void setAttachmentVertices(MeshAttachment* attachment) {
-	AtlasRegion* region = (AtlasRegion*)attachment->getRendererObject();
-	AttachmentVertices* attachmentVertices = new AttachmentVertices((Texture2D*)region->page->getRendererObject(),
-																	attachment->getWorldVerticesLength() >> 1, attachment->getTriangles().buffer(), attachment->getTriangles().size());
-	V3F_C4B_T2F* vertices = attachmentVertices->_triangles->verts;
-	for (int i = 0, ii = 0, nn = attachment->getWorldVerticesLength(); ii < nn; ++i, ii += 2) {
-		vertices[i].texCoords.u = attachment->getUVs()[ii];
-		vertices[i].texCoords.v = attachment->getUVs()[ii + 1];
-	}
-	attachment->setRendererObject(attachmentVertices, deleteAttachmentVertices);
-}
-
-Cocos2dAtlasAttachmentLoader::Cocos2dAtlasAttachmentLoader(Atlas* atlas): AtlasAttachmentLoader(atlas) {
-}
-
-Cocos2dAtlasAttachmentLoader::~Cocos2dAtlasAttachmentLoader() { }
-
-void Cocos2dAtlasAttachmentLoader::configureAttachment(Attachment* attachment) {
-	if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
-		setAttachmentVertices((RegionAttachment*)attachment);
-	} else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
-		setAttachmentVertices((MeshAttachment*)attachment);
-	}
-}
-
-#if COCOS2D_VERSION >= 0x0040000
-
-backend::SamplerAddressMode wrap (TextureWrap wrap) {
-	return wrap ==  TextureWrap_ClampToEdge ? backend::SamplerAddressMode::CLAMP_TO_EDGE : backend::SamplerAddressMode::REPEAT;
-}
-
-backend::SamplerFilter filter (TextureFilter filter) {
-	switch (filter) {
-	case TextureFilter_Unknown:
-		break;
-	case TextureFilter_Nearest:
-		return backend::SamplerFilter::NEAREST;
-	case TextureFilter_Linear:
-		return backend::SamplerFilter::LINEAR;
-	case TextureFilter_MipMap:
-		return backend::SamplerFilter::LINEAR;
-	case TextureFilter_MipMapNearestNearest:
-		return backend::SamplerFilter::NEAREST;
-	case TextureFilter_MipMapLinearNearest:
-        return backend::SamplerFilter::NEAREST;
-	case TextureFilter_MipMapNearestLinear:
-        return backend::SamplerFilter::LINEAR;
-	case TextureFilter_MipMapLinearLinear:
-        return backend::SamplerFilter::LINEAR;
-	}
-	return backend::SamplerFilter::LINEAR;
-}
-
-#else
-
-GLuint wrap (TextureWrap wrap) {
-	return wrap ==  TextureWrap_ClampToEdge ? GL_CLAMP_TO_EDGE : GL_REPEAT;
-}
-
-GLuint filter (TextureFilter filter) {
-	switch (filter) {
-	case TextureFilter_Unknown:
-		break;
-	case TextureFilter_Nearest:
-		return GL_NEAREST;
-	case TextureFilter_Linear:
-		return GL_LINEAR;
-	case TextureFilter_MipMap:
-		return GL_LINEAR_MIPMAP_LINEAR;
-	case TextureFilter_MipMapNearestNearest:
-		return GL_NEAREST_MIPMAP_NEAREST;
-	case TextureFilter_MipMapLinearNearest:
-		return GL_LINEAR_MIPMAP_NEAREST;
-	case TextureFilter_MipMapNearestLinear:
-		return GL_NEAREST_MIPMAP_LINEAR;
-	case TextureFilter_MipMapLinearLinear:
-		return GL_LINEAR_MIPMAP_LINEAR;
-	}
-	return GL_LINEAR;
-}
-
-#endif
-
-Cocos2dTextureLoader::Cocos2dTextureLoader() : TextureLoader() { }
-Cocos2dTextureLoader::~Cocos2dTextureLoader() { }
-
-void Cocos2dTextureLoader::load(AtlasPage& page, const spine::String& path) {
-	Texture2D* texture = Director::getInstance()->getTextureCache()->addImage(path.buffer());
-	CCASSERT(texture != nullptr, "Invalid image");
-	if (texture) {
-		texture->retain();
-#if COCOS2D_VERSION >= 0x0040000
-		Texture2D::TexParams textureParams(filter(page.minFilter), filter(page.magFilter), wrap(page.uWrap), wrap(page.vWrap));
-#else
-		Texture2D::TexParams textureParams = {filter(page.minFilter), filter(page.magFilter), wrap(page.uWrap), wrap(page.vWrap)};
-#endif
-		texture->setTexParameters(textureParams);
-
-		page.setRendererObject(texture);
-		page.width = texture->getPixelsWide();
-		page.height = texture->getPixelsHigh();
-	}
-}
-
-void Cocos2dTextureLoader::unload(void* texture) {
-	if (texture) {
-		((Texture2D*)texture)->release();
-	}
-}
-
-
-Cocos2dExtension::Cocos2dExtension() : DefaultSpineExtension() { }
-
-Cocos2dExtension::~Cocos2dExtension() { }
-
-char *Cocos2dExtension::_readFile(const spine::String &path, int *length) {
-    Data data = FileUtils::getInstance()->getDataFromFile(path.buffer());
-	if (data.isNull()) return nullptr;
-
-	// avoid buffer overflow (int is shorter than ssize_t in certain platforms)
-#if COCOS2D_VERSION >= 0x00031200
-	ssize_t tmpLen;
-	char *ret = (char*)data.takeBuffer(&tmpLen);
-	*length = static_cast<int>(tmpLen);
-	return ret;
-#else
-	*length = static_cast<int>(data.getSize());
-    auto bytes = SpineExtension::alloc<char>(*length, __FILE__, __LINE__);
-	memcpy(bytes, data.getBytes(), *length);
-	return bytes;
-#endif
-}
-
-SpineExtension *spine::getDefaultExtension () {
-	return new Cocos2dExtension();
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include <spine/AttachmentVertices.h>
+#include <spine/Extension.h>
+#include <spine/spine-cocos2dx.h>
+
+USING_NS_CC;
+using namespace spine;
+
+static void deleteAttachmentVertices(void *vertices) {
+	delete (AttachmentVertices *) vertices;
+}
+
+static unsigned short quadTriangles[6] = {0, 1, 2, 2, 3, 0};
+
+static void setAttachmentVertices(RegionAttachment *attachment) {
+	AtlasRegion *region = (AtlasRegion *) attachment->getRendererObject();
+	AttachmentVertices *attachmentVertices = new AttachmentVertices((Texture2D *) region->page->getRendererObject(), 4, quadTriangles, 6);
+	V3F_C4B_T2F *vertices = attachmentVertices->_triangles->verts;
+	for (int i = 0, ii = 0; i < 4; ++i, ii += 2) {
+		vertices[i].texCoords.u = attachment->getUVs()[ii];
+		vertices[i].texCoords.v = attachment->getUVs()[ii + 1];
+	}
+	attachment->setRendererObject(attachmentVertices, deleteAttachmentVertices);
+}
+
+static void setAttachmentVertices(MeshAttachment *attachment) {
+	AtlasRegion *region = (AtlasRegion *) attachment->getRendererObject();
+	AttachmentVertices *attachmentVertices = new AttachmentVertices((Texture2D *) region->page->getRendererObject(),
+																	attachment->getWorldVerticesLength() >> 1, attachment->getTriangles().buffer(), attachment->getTriangles().size());
+	V3F_C4B_T2F *vertices = attachmentVertices->_triangles->verts;
+	for (int i = 0, ii = 0, nn = attachment->getWorldVerticesLength(); ii < nn; ++i, ii += 2) {
+		vertices[i].texCoords.u = attachment->getUVs()[ii];
+		vertices[i].texCoords.v = attachment->getUVs()[ii + 1];
+	}
+	attachment->setRendererObject(attachmentVertices, deleteAttachmentVertices);
+}
+
+Cocos2dAtlasAttachmentLoader::Cocos2dAtlasAttachmentLoader(Atlas *atlas) : AtlasAttachmentLoader(atlas) {
+}
+
+Cocos2dAtlasAttachmentLoader::~Cocos2dAtlasAttachmentLoader() {}
+
+void Cocos2dAtlasAttachmentLoader::configureAttachment(Attachment *attachment) {
+	if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
+		setAttachmentVertices((RegionAttachment *) attachment);
+	} else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
+		setAttachmentVertices((MeshAttachment *) attachment);
+	}
+}
+
+#if COCOS2D_VERSION >= 0x0040000
+
+backend::SamplerAddressMode wrap(TextureWrap wrap) {
+	return wrap == TextureWrap_ClampToEdge ? backend::SamplerAddressMode::CLAMP_TO_EDGE : backend::SamplerAddressMode::REPEAT;
+}
+
+backend::SamplerFilter filter(TextureFilter filter) {
+	switch (filter) {
+		case TextureFilter_Unknown:
+			break;
+		case TextureFilter_Nearest:
+			return backend::SamplerFilter::NEAREST;
+		case TextureFilter_Linear:
+			return backend::SamplerFilter::LINEAR;
+		case TextureFilter_MipMap:
+			return backend::SamplerFilter::LINEAR;
+		case TextureFilter_MipMapNearestNearest:
+			return backend::SamplerFilter::NEAREST;
+		case TextureFilter_MipMapLinearNearest:
+			return backend::SamplerFilter::NEAREST;
+		case TextureFilter_MipMapNearestLinear:
+			return backend::SamplerFilter::LINEAR;
+		case TextureFilter_MipMapLinearLinear:
+			return backend::SamplerFilter::LINEAR;
+	}
+	return backend::SamplerFilter::LINEAR;
+}
+
+#else
+
+GLuint wrap(TextureWrap wrap) {
+	return wrap == TextureWrap_ClampToEdge ? GL_CLAMP_TO_EDGE : GL_REPEAT;
+}
+
+GLuint filter(TextureFilter filter) {
+	switch (filter) {
+		case TextureFilter_Unknown:
+			break;
+		case TextureFilter_Nearest:
+			return GL_NEAREST;
+		case TextureFilter_Linear:
+			return GL_LINEAR;
+		case TextureFilter_MipMap:
+			return GL_LINEAR_MIPMAP_LINEAR;
+		case TextureFilter_MipMapNearestNearest:
+			return GL_NEAREST_MIPMAP_NEAREST;
+		case TextureFilter_MipMapLinearNearest:
+			return GL_LINEAR_MIPMAP_NEAREST;
+		case TextureFilter_MipMapNearestLinear:
+			return GL_NEAREST_MIPMAP_LINEAR;
+		case TextureFilter_MipMapLinearLinear:
+			return GL_LINEAR_MIPMAP_LINEAR;
+	}
+	return GL_LINEAR;
+}
+
+#endif
+
+Cocos2dTextureLoader::Cocos2dTextureLoader() : TextureLoader() {}
+Cocos2dTextureLoader::~Cocos2dTextureLoader() {}
+
+void Cocos2dTextureLoader::load(AtlasPage &page, const spine::String &path) {
+	Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(path.buffer());
+	CCASSERT(texture != nullptr, "Invalid image");
+	if (texture) {
+		texture->retain();
+#if COCOS2D_VERSION >= 0x0040000
+		Texture2D::TexParams textureParams(filter(page.minFilter), filter(page.magFilter), wrap(page.uWrap), wrap(page.vWrap));
+#else
+		Texture2D::TexParams textureParams = {filter(page.minFilter), filter(page.magFilter), wrap(page.uWrap), wrap(page.vWrap)};
+#endif
+		texture->setTexParameters(textureParams);
+
+		page.setRendererObject(texture);
+		page.width = texture->getPixelsWide();
+		page.height = texture->getPixelsHigh();
+	}
+}
+
+void Cocos2dTextureLoader::unload(void *texture) {
+	if (texture) {
+		((Texture2D *) texture)->release();
+	}
+}
+
+
+Cocos2dExtension::Cocos2dExtension() : DefaultSpineExtension() {}
+
+Cocos2dExtension::~Cocos2dExtension() {}
+
+char *Cocos2dExtension::_readFile(const spine::String &path, int *length) {
+	Data data = FileUtils::getInstance()->getDataFromFile(path.buffer());
+	if (data.isNull()) return nullptr;
+
+		// avoid buffer overflow (int is shorter than ssize_t in certain platforms)
+#if COCOS2D_VERSION >= 0x00031200
+	ssize_t tmpLen;
+	char *ret = (char *) data.takeBuffer(&tmpLen);
+	*length = static_cast<int>(tmpLen);
+	return ret;
+#else
+	*length = static_cast<int>(data.getSize());
+	auto bytes = SpineExtension::alloc<char>(*length, __FILE__, __LINE__);
+	memcpy(bytes, data.getBytes(), *length);
+	return bytes;
+#endif
+}
+
+SpineExtension *spine::getDefaultExtension() {
+	return new Cocos2dExtension();
+}

+ 78 - 78
spine-cocos2dx/src/spine/spine-cocos2dx.h

@@ -1,78 +1,78 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#ifndef SPINE_COCOS2DX_H_
-#define SPINE_COCOS2DX_H_
-
-#include <spine/spine.h>
-#include "cocos2d.h"
-
-#include <spine/SkeletonRenderer.h>
-
-#if COCOS2D_VERSION < 0x00040000
-#include <spine/v3/SkeletonBatch.h>
-#include <spine/v3/SkeletonTwoColorBatch.h>
-#else
-#include <spine/v4/SkeletonBatch.h>
-#include <spine/v4/SkeletonTwoColorBatch.h>
-#endif
-
-#include <spine/SkeletonAnimation.h>
-
-namespace spine {
-	class Cocos2dAtlasAttachmentLoader: public AtlasAttachmentLoader {
-	public:
-		Cocos2dAtlasAttachmentLoader(Atlas* atlas);
-		virtual ~Cocos2dAtlasAttachmentLoader();
-		virtual void configureAttachment(Attachment* attachment);
-	};
-
-	class Cocos2dTextureLoader: public TextureLoader {
-	public:
-		Cocos2dTextureLoader();
-		
-		virtual ~Cocos2dTextureLoader();
-		
-		virtual void load(AtlasPage& page, const String& path);
-
-		virtual void unload(void* texture);
-	};
-
-	class Cocos2dExtension: public DefaultSpineExtension {
-	public:
-		Cocos2dExtension();
-		
-		virtual ~Cocos2dExtension();
-		
-	protected:
-		virtual char *_readFile(const String &path, int *length);
-	};
-}
-
-#endif /* SPINE_COCOS2DX_H_ */
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#ifndef SPINE_COCOS2DX_H_
+#define SPINE_COCOS2DX_H_
+
+#include "cocos2d.h"
+#include <spine/spine.h>
+
+#include <spine/SkeletonRenderer.h>
+
+#if COCOS2D_VERSION < 0x00040000
+#include <spine/v3/SkeletonBatch.h>
+#include <spine/v3/SkeletonTwoColorBatch.h>
+#else
+#include <spine/v4/SkeletonBatch.h>
+#include <spine/v4/SkeletonTwoColorBatch.h>
+#endif
+
+#include <spine/SkeletonAnimation.h>
+
+namespace spine {
+	class Cocos2dAtlasAttachmentLoader : public AtlasAttachmentLoader {
+	public:
+		Cocos2dAtlasAttachmentLoader(Atlas *atlas);
+		virtual ~Cocos2dAtlasAttachmentLoader();
+		virtual void configureAttachment(Attachment *attachment);
+	};
+
+	class Cocos2dTextureLoader : public TextureLoader {
+	public:
+		Cocos2dTextureLoader();
+
+		virtual ~Cocos2dTextureLoader();
+
+		virtual void load(AtlasPage &page, const String &path);
+
+		virtual void unload(void *texture);
+	};
+
+	class Cocos2dExtension : public DefaultSpineExtension {
+	public:
+		Cocos2dExtension();
+
+		virtual ~Cocos2dExtension();
+
+	protected:
+		virtual char *_readFile(const String &path, int *length);
+	};
+}// namespace spine
+
+#endif /* SPINE_COCOS2DX_H_ */

+ 95 - 94
spine-cocos2dx/src/spine/v3/SkeletonBatch.cpp

@@ -30,8 +30,8 @@
 #include <spine/spine-cocos2dx.h>
 #if COCOS2D_VERSION < 0x00040000
 
-#include <spine/Extension.h>
 #include <algorithm>
+#include <spine/Extension.h>
 
 USING_NS_CC;
 #define EVENT_AFTER_DRAW_RESET_POSITION "director_after_draw"
@@ -40,116 +40,117 @@ using std::max;
 
 namespace spine {
 
-static SkeletonBatch* instance = nullptr;
+	static SkeletonBatch *instance = nullptr;
 
-SkeletonBatch* SkeletonBatch::getInstance () {
-	if (!instance) instance = new SkeletonBatch();
-	return instance;
-}
-
-void SkeletonBatch::destroyInstance () {
-	if (instance) {
-		delete instance;
-		instance = nullptr;
+	SkeletonBatch *SkeletonBatch::getInstance() {
+		if (!instance) instance = new SkeletonBatch();
+		return instance;
 	}
-}
 
-SkeletonBatch::SkeletonBatch () {
-	for (unsigned int i = 0; i < INITIAL_SIZE; i++) {
-		_commandsPool.push_back(new TrianglesCommand());
+	void SkeletonBatch::destroyInstance() {
+		if (instance) {
+			delete instance;
+			instance = nullptr;
+		}
 	}
 
-	reset ();
+	SkeletonBatch::SkeletonBatch() {
+		for (unsigned int i = 0; i < INITIAL_SIZE; i++) {
+			_commandsPool.push_back(new TrianglesCommand());
+		}
+
+		reset();
 
-	// callback after drawing is finished so we can clear out the batch state
-	// for the next frame
-	Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_AFTER_DRAW_RESET_POSITION, [this](EventCustom* eventCustom){
-		this->update(0);
-	});;
-}
+		// callback after drawing is finished so we can clear out the batch state
+		// for the next frame
+		Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_AFTER_DRAW_RESET_POSITION, [this](EventCustom *eventCustom) {
+			this->update(0);
+		});
+		;
+	}
+
+	SkeletonBatch::~SkeletonBatch() {
+		Director::getInstance()->getEventDispatcher()->removeCustomEventListeners(EVENT_AFTER_DRAW_RESET_POSITION);
 
-SkeletonBatch::~SkeletonBatch () {
-	Director::getInstance()->getEventDispatcher()->removeCustomEventListeners(EVENT_AFTER_DRAW_RESET_POSITION);
+		for (unsigned int i = 0; i < _commandsPool.size(); i++) {
+			delete _commandsPool[i];
+			_commandsPool[i] = nullptr;
+		}
+	}
 
-	for (unsigned int i = 0; i < _commandsPool.size(); i++) {
-		delete _commandsPool[i];
-		_commandsPool[i] = nullptr;
+	void SkeletonBatch::update(float delta) {
+		reset();
 	}
-}
-
-void SkeletonBatch::update (float delta) {
-	reset();
-}
-
-cocos2d::V3F_C4B_T2F* SkeletonBatch::allocateVertices(uint32_t numVertices) {
-	if (_vertices.size() - _numVertices < numVertices) {
-		cocos2d::V3F_C4B_T2F* oldData = _vertices.data();
-		_vertices.resize((_vertices.size() + numVertices) * 2 + 1);
-		cocos2d::V3F_C4B_T2F* newData = _vertices.data();
-		for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
-			TrianglesCommand* command = _commandsPool[i];
-			cocos2d::TrianglesCommand::Triangles& triangles = (cocos2d::TrianglesCommand::Triangles&)command->getTriangles();
-			triangles.verts = newData + (triangles.verts - oldData);
+
+	cocos2d::V3F_C4B_T2F *SkeletonBatch::allocateVertices(uint32_t numVertices) {
+		if (_vertices.size() - _numVertices < numVertices) {
+			cocos2d::V3F_C4B_T2F *oldData = _vertices.data();
+			_vertices.resize((_vertices.size() + numVertices) * 2 + 1);
+			cocos2d::V3F_C4B_T2F *newData = _vertices.data();
+			for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
+				TrianglesCommand *command = _commandsPool[i];
+				cocos2d::TrianglesCommand::Triangles &triangles = (cocos2d::TrianglesCommand::Triangles &) command->getTriangles();
+				triangles.verts = newData + (triangles.verts - oldData);
+			}
 		}
+
+		cocos2d::V3F_C4B_T2F *vertices = _vertices.data() + _numVertices;
+		_numVertices += numVertices;
+		return vertices;
 	}
 
-	cocos2d::V3F_C4B_T2F* vertices = _vertices.data() + _numVertices;
-	_numVertices += numVertices;
-	return vertices;
-}
-
-void SkeletonBatch::deallocateVertices(uint32_t numVertices) {
-	_numVertices -= numVertices;
-}
-
-
-unsigned short* SkeletonBatch::allocateIndices(uint32_t numIndices) {
-	if (_indices.getCapacity() - _indices.size() < numIndices) {
-		unsigned short* oldData = _indices.buffer();
-		int oldSize = _indices.size();
-		_indices.ensureCapacity(_indices.size() + numIndices);
-		unsigned short* newData = _indices.buffer();
-		for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
-			TrianglesCommand* command = _commandsPool[i];
-			cocos2d::TrianglesCommand::Triangles& triangles = (cocos2d::TrianglesCommand::Triangles&)command->getTriangles();
-			if (triangles.indices >= oldData && triangles.indices < oldData + oldSize) {
-				triangles.indices = newData + (triangles.indices - oldData);
+	void SkeletonBatch::deallocateVertices(uint32_t numVertices) {
+		_numVertices -= numVertices;
+	}
+
+
+	unsigned short *SkeletonBatch::allocateIndices(uint32_t numIndices) {
+		if (_indices.getCapacity() - _indices.size() < numIndices) {
+			unsigned short *oldData = _indices.buffer();
+			int oldSize = _indices.size();
+			_indices.ensureCapacity(_indices.size() + numIndices);
+			unsigned short *newData = _indices.buffer();
+			for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
+				TrianglesCommand *command = _commandsPool[i];
+				cocos2d::TrianglesCommand::Triangles &triangles = (cocos2d::TrianglesCommand::Triangles &) command->getTriangles();
+				if (triangles.indices >= oldData && triangles.indices < oldData + oldSize) {
+					triangles.indices = newData + (triangles.indices - oldData);
+				}
 			}
 		}
+
+		unsigned short *indices = _indices.buffer() + _indices.size();
+		_indices.setSize(_indices.size() + numIndices, 0);
+		return indices;
 	}
 
-	unsigned short* indices = _indices.buffer() + _indices.size();
-	_indices.setSize(_indices.size() + numIndices, 0);
-	return indices;
-}
-
-void SkeletonBatch::deallocateIndices(uint32_t numIndices) {
-	_indices.setSize(_indices.size() - numIndices, 0);
-}
-
-
-cocos2d::TrianglesCommand* SkeletonBatch::addCommand(cocos2d::Renderer* renderer, float globalOrder, cocos2d::Texture2D* texture, cocos2d::GLProgramState* glProgramState, cocos2d::BlendFunc blendType, const cocos2d::TrianglesCommand::Triangles& triangles, const cocos2d::Mat4& mv, uint32_t flags) {
-	TrianglesCommand* command = nextFreeCommand();
-	command->init(globalOrder, texture, glProgramState, blendType, triangles, mv, flags);
-	renderer->addCommand(command);
-	return command;
-}
-
-void SkeletonBatch::reset() {
-	_nextFreeCommand = 0;
-	_numVertices = 0;
-	_indices.setSize(0, 0);
-}
-
-cocos2d::TrianglesCommand* SkeletonBatch::nextFreeCommand() {
-	if (_commandsPool.size() <= _nextFreeCommand) {
-		unsigned int newSize = _commandsPool.size() * 2 + 1;
-		for (int i = _commandsPool.size(); i < newSize; i++) {
-			_commandsPool.push_back(new TrianglesCommand());
+	void SkeletonBatch::deallocateIndices(uint32_t numIndices) {
+		_indices.setSize(_indices.size() - numIndices, 0);
+	}
+
+
+	cocos2d::TrianglesCommand *SkeletonBatch::addCommand(cocos2d::Renderer *renderer, float globalOrder, cocos2d::Texture2D *texture, cocos2d::GLProgramState *glProgramState, cocos2d::BlendFunc blendType, const cocos2d::TrianglesCommand::Triangles &triangles, const cocos2d::Mat4 &mv, uint32_t flags) {
+		TrianglesCommand *command = nextFreeCommand();
+		command->init(globalOrder, texture, glProgramState, blendType, triangles, mv, flags);
+		renderer->addCommand(command);
+		return command;
+	}
+
+	void SkeletonBatch::reset() {
+		_nextFreeCommand = 0;
+		_numVertices = 0;
+		_indices.setSize(0, 0);
+	}
+
+	cocos2d::TrianglesCommand *SkeletonBatch::nextFreeCommand() {
+		if (_commandsPool.size() <= _nextFreeCommand) {
+			unsigned int newSize = _commandsPool.size() * 2 + 1;
+			for (int i = _commandsPool.size(); i < newSize; i++) {
+				_commandsPool.push_back(new TrianglesCommand());
+			}
 		}
+		return _commandsPool[_nextFreeCommand++];
 	}
-	return _commandsPool[_nextFreeCommand++];
-}
-}
+}// namespace spine
 
 #endif

+ 13 - 13
spine-cocos2dx/src/spine/v3/SkeletonBatch.h

@@ -40,28 +40,28 @@ namespace spine {
 
 	class SkeletonBatch {
 	public:
-		static SkeletonBatch* getInstance ();
+		static SkeletonBatch *getInstance();
 
-		static void destroyInstance ();
+		static void destroyInstance();
 
-		void update (float delta);
+		void update(float delta);
 
-		cocos2d::V3F_C4B_T2F* allocateVertices(uint32_t numVertices);
+		cocos2d::V3F_C4B_T2F *allocateVertices(uint32_t numVertices);
 		void deallocateVertices(uint32_t numVertices);
-		unsigned short* allocateIndices(uint32_t numIndices);
+		unsigned short *allocateIndices(uint32_t numIndices);
 		void deallocateIndices(uint32_t numVertices);
-		cocos2d::TrianglesCommand* addCommand(cocos2d::Renderer* renderer, float globalOrder, cocos2d::Texture2D* texture, cocos2d::GLProgramState* glProgramState, cocos2d::BlendFunc blendType, const cocos2d::TrianglesCommand::Triangles& triangles, const cocos2d::Mat4& mv, uint32_t flags);
+		cocos2d::TrianglesCommand *addCommand(cocos2d::Renderer *renderer, float globalOrder, cocos2d::Texture2D *texture, cocos2d::GLProgramState *glProgramState, cocos2d::BlendFunc blendType, const cocos2d::TrianglesCommand::Triangles &triangles, const cocos2d::Mat4 &mv, uint32_t flags);
 
 	protected:
-		SkeletonBatch ();
-		virtual ~SkeletonBatch ();
+		SkeletonBatch();
+		virtual ~SkeletonBatch();
 
-		void reset ();
+		void reset();
 
-		cocos2d::TrianglesCommand* nextFreeCommand ();
+		cocos2d::TrianglesCommand *nextFreeCommand();
 
 		// pool of commands
-		std::vector<cocos2d::TrianglesCommand*> _commandsPool;
+		std::vector<cocos2d::TrianglesCommand *> _commandsPool;
 		uint32_t _nextFreeCommand;
 
 		// pool of vertices
@@ -72,8 +72,8 @@ namespace spine {
 		Vector<unsigned short> _indices;
 	};
 
-}
+}// namespace spine
 
 #endif
 
-#endif // SPINE_SKELETONBATCH_H_
+#endif// SPINE_SKELETONBATCH_H_

+ 262 - 265
spine-cocos2dx/src/spine/v3/SkeletonTwoColorBatch.cpp

@@ -30,8 +30,8 @@
 #include <spine/spine-cocos2dx.h>
 #if COCOS2D_VERSION < 0x00040000
 
-#include <spine/Extension.h>
 #include <algorithm>
+#include <spine/Extension.h>
 
 USING_NS_CC;
 #define EVENT_AFTER_DRAW_RESET_POSITION "director_after_draw"
@@ -40,312 +40,309 @@ using std::max;
 #define MAX_VERTICES 64000
 #define MAX_INDICES 64000
 
-#define STRINGIFY(A)  #A
+#define STRINGIFY(A) #A
 
 namespace spine {
 
-TwoColorTrianglesCommand::TwoColorTrianglesCommand() :_materialID(0), _textureID(0), _glProgramState(nullptr), _glProgram(nullptr), _blendType(BlendFunc::DISABLE), _alphaTextureID(0) {
-	_type = RenderCommand::Type::CUSTOM_COMMAND;
-	func = [this]() { draw(); };
-}
+	TwoColorTrianglesCommand::TwoColorTrianglesCommand() : _materialID(0), _textureID(0), _glProgramState(nullptr), _glProgram(nullptr), _blendType(BlendFunc::DISABLE), _alphaTextureID(0) {
+		_type = RenderCommand::Type::CUSTOM_COMMAND;
+		func = [this]() { draw(); };
+	}
+
+	void TwoColorTrianglesCommand::init(float globalOrder, GLuint textureID, GLProgramState *glProgramState, BlendFunc blendType, const TwoColorTriangles &triangles, const Mat4 &mv, uint32_t flags) {
+		CCASSERT(glProgramState, "Invalid GLProgramState");
+		CCASSERT(glProgramState->getVertexAttribsFlags() == 0, "No custom attributes are supported in QuadCommand");
+
+		RenderCommand::init(globalOrder, mv, flags);
 
-void TwoColorTrianglesCommand::init(float globalOrder, GLuint textureID, GLProgramState* glProgramState, BlendFunc blendType, const TwoColorTriangles& triangles, const Mat4& mv, uint32_t flags) {
-	CCASSERT(glProgramState, "Invalid GLProgramState");
-	CCASSERT(glProgramState->getVertexAttribsFlags() == 0, "No custom attributes are supported in QuadCommand");
+		_triangles = triangles;
+		if (_triangles.indexCount % 3 != 0) {
+			int count = _triangles.indexCount;
+			_triangles.indexCount = count / 3 * 3;
+			CCLOGERROR("Resize indexCount from %d to %d, size must be multiple times of 3", count, _triangles.indexCount);
+		}
+		_mv = mv;
 
-	RenderCommand::init(globalOrder, mv, flags);
+		if (_textureID != textureID || _blendType.src != blendType.src || _blendType.dst != blendType.dst ||
+			_glProgramState != glProgramState ||
+			_glProgram != glProgramState->getGLProgram()) {
+			_textureID = textureID;
+			_blendType = blendType;
+			_glProgramState = glProgramState;
+			_glProgram = glProgramState->getGLProgram();
 
-	_triangles = triangles;
-	if(_triangles.indexCount % 3 != 0) {
-	int count = _triangles.indexCount;
-		_triangles.indexCount = count / 3 * 3;
-		CCLOGERROR("Resize indexCount from %d to %d, size must be multiple times of 3", count, _triangles.indexCount);
+			generateMaterialID();
+		}
 	}
-	_mv = mv;
 
-	if( _textureID != textureID || _blendType.src != blendType.src || _blendType.dst != blendType.dst ||
-		_glProgramState != glProgramState ||
-		_glProgram != glProgramState->getGLProgram()) {
-		_textureID = textureID;
-		_blendType = blendType;
-		_glProgramState = glProgramState;
-		_glProgram = glProgramState->getGLProgram();
+	TwoColorTrianglesCommand::~TwoColorTrianglesCommand() {
+	}
 
-		generateMaterialID();
+	void TwoColorTrianglesCommand::generateMaterialID() {
+		// do not batch if using custom uniforms (since we cannot batch) it
+		if (_glProgramState->getUniformCount() > 0) {
+			_materialID = Renderer::MATERIAL_ID_DO_NOT_BATCH;
+			setSkipBatching(true);
+		} else {
+			int glProgram = (int) _glProgram->getProgram();
+			_materialID = glProgram + (int) _textureID + (int) _blendType.src + (int) _blendType.dst;
+		}
 	}
-}
 
-TwoColorTrianglesCommand::~TwoColorTrianglesCommand() {
-}
+	void TwoColorTrianglesCommand::useMaterial() const {
+		//Set texture
+		GL::bindTexture2D(_textureID);
 
-void TwoColorTrianglesCommand::generateMaterialID() {
-	// do not batch if using custom uniforms (since we cannot batch) it
-	if(_glProgramState->getUniformCount() > 0) {
-		_materialID = Renderer::MATERIAL_ID_DO_NOT_BATCH;
-		setSkipBatching(true);
+		if (_alphaTextureID > 0) {
+			// ANDROID ETC1 ALPHA supports.
+			GL::bindTexture2DN(1, _alphaTextureID);
+		}
+		//set blend mode
+		GL::blendFunc(_blendType.src, _blendType.dst);
+
+		_glProgramState->apply(_mv);
 	}
-	else {
-		int glProgram = (int)_glProgram->getProgram();
-		_materialID = glProgram + (int)_textureID + (int)_blendType.src + (int)_blendType.dst;
+
+	void TwoColorTrianglesCommand::draw() {
+		SkeletonTwoColorBatch::getInstance()->batch(this);
 	}
-}
 
-void TwoColorTrianglesCommand::useMaterial() const {
-	//Set texture
-	GL::bindTexture2D(_textureID);
+	const char *TWO_COLOR_TINT_VERTEX_SHADER = STRINGIFY(
+			attribute vec4 a_position;
+			attribute vec4 a_color;
+			attribute vec4 a_color2;
+			attribute vec2 a_texCoords;
+
+\n #ifdef GL_ES\n
+					varying lowp vec4 v_light;
+			varying lowp vec4 v_dark;
+			varying mediump vec2 v_texCoord;
+\n #else \n
+					varying vec4 v_light;
+			varying vec4 v_dark;
+			varying vec2 v_texCoord;
+
+\n #endif \n
+
+			void main() {
+				v_light = a_color;
+				v_dark = a_color2;
+				v_texCoord = a_texCoords;
+				gl_Position = CC_PMatrix * a_position;
+			});
+
+	const char *TWO_COLOR_TINT_FRAGMENT_SHADER = STRINGIFY(
+\n #ifdef GL_ES\n
+					precision lowp float;
+\n #endif \n
+
+							varying vec4 v_light;
+			varying vec4 v_dark;
+			varying vec2 v_texCoord;
+
+			void main() {
+				vec4 texColor = texture2D(CC_Texture0, v_texCoord);
+				float alpha = texColor.a * v_light.a;
+				gl_FragColor.a = alpha;
+				gl_FragColor.rgb = ((texColor.a - 1.0) * v_dark.a + 1.0 - texColor.rgb) * v_dark.rgb + texColor.rgb * v_light.rgb;
+			});
+
+
+	static SkeletonTwoColorBatch *instance = nullptr;
+
+	SkeletonTwoColorBatch *SkeletonTwoColorBatch::getInstance() {
+		if (!instance) instance = new SkeletonTwoColorBatch();
+		return instance;
+	}
 
-	if (_alphaTextureID > 0) {
-		// ANDROID ETC1 ALPHA supports.
-		GL::bindTexture2DN(1, _alphaTextureID);
+	void SkeletonTwoColorBatch::destroyInstance() {
+		if (instance) {
+			delete instance;
+			instance = nullptr;
+		}
 	}
-	//set blend mode
-	GL::blendFunc(_blendType.src, _blendType.dst);
-
-	_glProgramState->apply(_mv);
-}
-
-void TwoColorTrianglesCommand::draw() {
-	SkeletonTwoColorBatch::getInstance()->batch(this);
-}
-
-const char* TWO_COLOR_TINT_VERTEX_SHADER = STRINGIFY(
-attribute vec4 a_position;
-attribute vec4 a_color;
-attribute vec4 a_color2;
-attribute vec2 a_texCoords;
-
-\n#ifdef GL_ES\n
-varying lowp vec4 v_light;
-varying lowp vec4 v_dark;
-varying mediump vec2 v_texCoord;
-\n#else\n
-varying vec4 v_light;
-varying vec4 v_dark;
-varying vec2 v_texCoord;
-
-\n#endif\n
-
-void main() {
-	v_light = a_color;
-	v_dark = a_color2;
-	v_texCoord = a_texCoords;
-	gl_Position = CC_PMatrix * a_position;
-}
-);
-
-const char* TWO_COLOR_TINT_FRAGMENT_SHADER = STRINGIFY(
-\n#ifdef GL_ES\n
-precision lowp float;
-\n#endif\n
-
-varying vec4 v_light;
-varying vec4 v_dark;
-varying vec2 v_texCoord;
-
-void main() {
-	vec4 texColor = texture2D(CC_Texture0, v_texCoord);
-	float alpha = texColor.a * v_light.a;
-	gl_FragColor.a = alpha;
-	gl_FragColor.rgb = ((texColor.a - 1.0) * v_dark.a + 1.0 - texColor.rgb) * v_dark.rgb + texColor.rgb * v_light.rgb;
-}
-);
-
-
-static SkeletonTwoColorBatch* instance = nullptr;
-
-SkeletonTwoColorBatch* SkeletonTwoColorBatch::getInstance () {
-	if (!instance) instance = new SkeletonTwoColorBatch();
-	return instance;
-}
-
-void SkeletonTwoColorBatch::destroyInstance () {
-	if (instance) {
-		delete instance;
-		instance = nullptr;
+
+	SkeletonTwoColorBatch::SkeletonTwoColorBatch() : _vertexBuffer(0), _indexBuffer(0) {
+		for (unsigned int i = 0; i < INITIAL_SIZE; i++) {
+			_commandsPool.push_back(new TwoColorTrianglesCommand());
+		}
+
+		reset();
+
+		// callback after drawing is finished so we can clear out the batch state
+		// for the next frame
+		Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_AFTER_DRAW_RESET_POSITION, [this](EventCustom *eventCustom) {
+			this->update(0);
+		});
+
+		_twoColorTintShader = cocos2d::GLProgram::createWithByteArrays(TWO_COLOR_TINT_VERTEX_SHADER, TWO_COLOR_TINT_FRAGMENT_SHADER);
+		_twoColorTintShaderState = GLProgramState::getOrCreateWithGLProgram(_twoColorTintShader);
+		_twoColorTintShaderState->retain();
+
+		glGenBuffers(1, &_vertexBufferHandle);
+		_vertexBuffer = new V3F_C4B_C4B_T2F[MAX_VERTICES];
+		glGenBuffers(1, &_indexBufferHandle);
+		_indexBuffer = new unsigned short[MAX_INDICES];
+		_positionAttributeLocation = _twoColorTintShader->getAttribLocation("a_position");
+		_colorAttributeLocation = _twoColorTintShader->getAttribLocation("a_color");
+		_color2AttributeLocation = _twoColorTintShader->getAttribLocation("a_color2");
+		_texCoordsAttributeLocation = _twoColorTintShader->getAttribLocation("a_texCoords");
 	}
-}
 
-SkeletonTwoColorBatch::SkeletonTwoColorBatch () : _vertexBuffer(0), _indexBuffer(0) {
-	for (unsigned int i = 0; i < INITIAL_SIZE; i++) {
-		_commandsPool.push_back(new TwoColorTrianglesCommand());
+	SkeletonTwoColorBatch::~SkeletonTwoColorBatch() {
+		Director::getInstance()->getEventDispatcher()->removeCustomEventListeners(EVENT_AFTER_DRAW_RESET_POSITION);
+
+		for (unsigned int i = 0; i < _commandsPool.size(); i++) {
+			delete _commandsPool[i];
+			_commandsPool[i] = nullptr;
+		}
+		_twoColorTintShader->release();
+		delete[] _vertexBuffer;
+		delete[] _indexBuffer;
 	}
 
-	reset ();
-
-	// callback after drawing is finished so we can clear out the batch state
-	// for the next frame
-	Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_AFTER_DRAW_RESET_POSITION, [this](EventCustom* eventCustom){
-		this->update(0);
-	});
-
-	_twoColorTintShader = cocos2d::GLProgram::createWithByteArrays(TWO_COLOR_TINT_VERTEX_SHADER, TWO_COLOR_TINT_FRAGMENT_SHADER);
-	_twoColorTintShaderState = GLProgramState::getOrCreateWithGLProgram(_twoColorTintShader);
-	_twoColorTintShaderState->retain();
-
-	glGenBuffers(1, &_vertexBufferHandle);
-	_vertexBuffer = new V3F_C4B_C4B_T2F[MAX_VERTICES];
-	glGenBuffers(1, &_indexBufferHandle);
-	_indexBuffer = new unsigned short[MAX_INDICES];
-	_positionAttributeLocation = _twoColorTintShader->getAttribLocation("a_position");
-	_colorAttributeLocation = _twoColorTintShader->getAttribLocation("a_color");
-	_color2AttributeLocation = _twoColorTintShader->getAttribLocation("a_color2");
-	_texCoordsAttributeLocation = _twoColorTintShader->getAttribLocation("a_texCoords");
-}
-
-SkeletonTwoColorBatch::~SkeletonTwoColorBatch () {
-	Director::getInstance()->getEventDispatcher()->removeCustomEventListeners(EVENT_AFTER_DRAW_RESET_POSITION);
-
-	for (unsigned int i = 0; i < _commandsPool.size(); i++) {
-		delete _commandsPool[i];
-		_commandsPool[i] = nullptr;
+	void SkeletonTwoColorBatch::update(float delta) {
+		reset();
 	}
-	_twoColorTintShader->release();
-	delete[] _vertexBuffer;
-	delete[] _indexBuffer;
-}
-
-void SkeletonTwoColorBatch::update (float delta) {
-	reset();
-}
-
-V3F_C4B_C4B_T2F* SkeletonTwoColorBatch::allocateVertices(uint32_t numVertices) {
-	if (_vertices.size() - _numVertices < numVertices) {
-		V3F_C4B_C4B_T2F* oldData = _vertices.data();
-		_vertices.resize((_vertices.size() + numVertices) * 2 + 1);
-		V3F_C4B_C4B_T2F* newData = _vertices.data();
-		for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
-			TwoColorTrianglesCommand* command = _commandsPool[i];
-			TwoColorTriangles& triangles = (TwoColorTriangles&)command->getTriangles();
-			triangles.verts = newData + (triangles.verts - oldData);
+
+	V3F_C4B_C4B_T2F *SkeletonTwoColorBatch::allocateVertices(uint32_t numVertices) {
+		if (_vertices.size() - _numVertices < numVertices) {
+			V3F_C4B_C4B_T2F *oldData = _vertices.data();
+			_vertices.resize((_vertices.size() + numVertices) * 2 + 1);
+			V3F_C4B_C4B_T2F *newData = _vertices.data();
+			for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
+				TwoColorTrianglesCommand *command = _commandsPool[i];
+				TwoColorTriangles &triangles = (TwoColorTriangles &) command->getTriangles();
+				triangles.verts = newData + (triangles.verts - oldData);
+			}
 		}
-	}
 
-	V3F_C4B_C4B_T2F* vertices = _vertices.data() + _numVertices;
-	_numVertices += numVertices;
-	return vertices;
-}
+		V3F_C4B_C4B_T2F *vertices = _vertices.data() + _numVertices;
+		_numVertices += numVertices;
+		return vertices;
+	}
 
 
-void SkeletonTwoColorBatch::deallocateVertices(uint32_t numVertices) {
-	_numVertices -= numVertices;
-}
+	void SkeletonTwoColorBatch::deallocateVertices(uint32_t numVertices) {
+		_numVertices -= numVertices;
+	}
 
 
-unsigned short* SkeletonTwoColorBatch::allocateIndices(uint32_t numIndices) {
-	if (_indices.getCapacity() - _indices.size() < numIndices) {
-		unsigned short* oldData = _indices.buffer();
-		int oldSize =_indices.size();
-		_indices.ensureCapacity(_indices.size() + numIndices);
-		unsigned short* newData = _indices.buffer();
-		for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
-			TwoColorTrianglesCommand* command = _commandsPool[i];
-			TwoColorTriangles& triangles = (TwoColorTriangles&)command->getTriangles();
-			if (triangles.indices >= oldData && triangles.indices < oldData + oldSize) {
-				triangles.indices = newData + (triangles.indices - oldData);
+	unsigned short *SkeletonTwoColorBatch::allocateIndices(uint32_t numIndices) {
+		if (_indices.getCapacity() - _indices.size() < numIndices) {
+			unsigned short *oldData = _indices.buffer();
+			int oldSize = _indices.size();
+			_indices.ensureCapacity(_indices.size() + numIndices);
+			unsigned short *newData = _indices.buffer();
+			for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
+				TwoColorTrianglesCommand *command = _commandsPool[i];
+				TwoColorTriangles &triangles = (TwoColorTriangles &) command->getTriangles();
+				if (triangles.indices >= oldData && triangles.indices < oldData + oldSize) {
+					triangles.indices = newData + (triangles.indices - oldData);
+				}
 			}
 		}
-	}
 
-	unsigned short* indices = _indices.buffer() + _indices.size();
-	_indices.setSize(_indices.size() + numIndices, 0);
-	return indices;
-}
-
-void SkeletonTwoColorBatch::deallocateIndices(uint32_t numIndices) {
-	_indices.setSize(_indices.size() - numIndices, 0);
-}
-
-TwoColorTrianglesCommand* SkeletonTwoColorBatch::addCommand(cocos2d::Renderer* renderer, float globalOrder, GLuint textureID, cocos2d::GLProgramState* glProgramState, cocos2d::BlendFunc blendType, const TwoColorTriangles& triangles, const cocos2d::Mat4& mv, uint32_t flags) {
-	TwoColorTrianglesCommand* command = nextFreeCommand();
-	command->init(globalOrder, textureID, glProgramState, blendType, triangles, mv, flags);
-	renderer->addCommand(command);
-	return command;
-}
-
-void SkeletonTwoColorBatch::batch (TwoColorTrianglesCommand* command) {
-	if (_numVerticesBuffer + command->getTriangles().vertCount >= MAX_VERTICES || _numIndicesBuffer + command->getTriangles().indexCount >= MAX_INDICES) {
-		flush(_lastCommand);
+		unsigned short *indices = _indices.buffer() + _indices.size();
+		_indices.setSize(_indices.size() + numIndices, 0);
+		return indices;
 	}
 
-	uint32_t materialID = command->getMaterialID();
-	if (_lastCommand && _lastCommand->getMaterialID() != materialID) {
-		flush(_lastCommand);
+	void SkeletonTwoColorBatch::deallocateIndices(uint32_t numIndices) {
+		_indices.setSize(_indices.size() - numIndices, 0);
 	}
 
-	memcpy(_vertexBuffer + _numVerticesBuffer, command->getTriangles().verts, sizeof(V3F_C4B_C4B_T2F) * command->getTriangles().vertCount);
-	const Mat4& modelView = command->getModelView();
-	for (int i = _numVerticesBuffer; i < _numVerticesBuffer + command->getTriangles().vertCount; i++) {
-		modelView.transformPoint(&_vertexBuffer[i].position);
+	TwoColorTrianglesCommand *SkeletonTwoColorBatch::addCommand(cocos2d::Renderer *renderer, float globalOrder, GLuint textureID, cocos2d::GLProgramState *glProgramState, cocos2d::BlendFunc blendType, const TwoColorTriangles &triangles, const cocos2d::Mat4 &mv, uint32_t flags) {
+		TwoColorTrianglesCommand *command = nextFreeCommand();
+		command->init(globalOrder, textureID, glProgramState, blendType, triangles, mv, flags);
+		renderer->addCommand(command);
+		return command;
 	}
 
-	unsigned short vertexOffset = (unsigned short)_numVerticesBuffer;
-	unsigned short* indices = command->getTriangles().indices;
-	for (int i = 0, j = _numIndicesBuffer; i < command->getTriangles().indexCount; i++, j++) {
-		_indexBuffer[j] = indices[i] + vertexOffset;
+	void SkeletonTwoColorBatch::batch(TwoColorTrianglesCommand *command) {
+		if (_numVerticesBuffer + command->getTriangles().vertCount >= MAX_VERTICES || _numIndicesBuffer + command->getTriangles().indexCount >= MAX_INDICES) {
+			flush(_lastCommand);
+		}
+
+		uint32_t materialID = command->getMaterialID();
+		if (_lastCommand && _lastCommand->getMaterialID() != materialID) {
+			flush(_lastCommand);
+		}
+
+		memcpy(_vertexBuffer + _numVerticesBuffer, command->getTriangles().verts, sizeof(V3F_C4B_C4B_T2F) * command->getTriangles().vertCount);
+		const Mat4 &modelView = command->getModelView();
+		for (int i = _numVerticesBuffer; i < _numVerticesBuffer + command->getTriangles().vertCount; i++) {
+			modelView.transformPoint(&_vertexBuffer[i].position);
+		}
+
+		unsigned short vertexOffset = (unsigned short) _numVerticesBuffer;
+		unsigned short *indices = command->getTriangles().indices;
+		for (int i = 0, j = _numIndicesBuffer; i < command->getTriangles().indexCount; i++, j++) {
+			_indexBuffer[j] = indices[i] + vertexOffset;
+		}
+
+		_numVerticesBuffer += command->getTriangles().vertCount;
+		_numIndicesBuffer += command->getTriangles().indexCount;
+
+		if (command->isForceFlush()) {
+			flush(command);
+		}
+		_lastCommand = command;
 	}
 
-	_numVerticesBuffer += command->getTriangles().vertCount;
-	_numIndicesBuffer += command->getTriangles().indexCount;
+	void SkeletonTwoColorBatch::flush(TwoColorTrianglesCommand *materialCommand) {
+		if (!materialCommand)
+			return;
 
-	if (command->isForceFlush()) {
-		flush(command);
+		materialCommand->useMaterial();
+
+		glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferHandle);
+		glBufferData(GL_ARRAY_BUFFER, sizeof(V3F_C4B_C4B_T2F) * _numVerticesBuffer, _vertexBuffer, GL_DYNAMIC_DRAW);
+
+		glEnableVertexAttribArray(_positionAttributeLocation);
+		glEnableVertexAttribArray(_colorAttributeLocation);
+		glEnableVertexAttribArray(_color2AttributeLocation);
+		glEnableVertexAttribArray(_texCoordsAttributeLocation);
+
+		glVertexAttribPointer(_positionAttributeLocation, 3, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_C4B_T2F), (GLvoid *) offsetof(V3F_C4B_C4B_T2F, position));
+		glVertexAttribPointer(_colorAttributeLocation, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_C4B_T2F), (GLvoid *) offsetof(V3F_C4B_C4B_T2F, color));
+		glVertexAttribPointer(_color2AttributeLocation, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_C4B_T2F), (GLvoid *) offsetof(V3F_C4B_C4B_T2F, color2));
+		glVertexAttribPointer(_texCoordsAttributeLocation, 2, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_C4B_T2F), (GLvoid *) offsetof(V3F_C4B_C4B_T2F, texCoords));
+
+		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferHandle);
+		glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * _numIndicesBuffer, _indexBuffer, GL_STATIC_DRAW);
+
+		glDrawElements(GL_TRIANGLES, (GLsizei) _numIndicesBuffer, GL_UNSIGNED_SHORT, 0);
+
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+		_numVerticesBuffer = 0;
+		_numIndicesBuffer = 0;
+		_numBatches++;
 	}
-	_lastCommand = command;
-}
-
-void SkeletonTwoColorBatch::flush (TwoColorTrianglesCommand* materialCommand) {
-	if (!materialCommand)
-		return;
-
-	materialCommand->useMaterial();
-
-	glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferHandle);
-	glBufferData(GL_ARRAY_BUFFER, sizeof(V3F_C4B_C4B_T2F) * _numVerticesBuffer , _vertexBuffer, GL_DYNAMIC_DRAW);
-
-	glEnableVertexAttribArray(_positionAttributeLocation);
-	glEnableVertexAttribArray(_colorAttributeLocation);
-	glEnableVertexAttribArray(_color2AttributeLocation);
-	glEnableVertexAttribArray(_texCoordsAttributeLocation);
-
-	glVertexAttribPointer(_positionAttributeLocation, 3, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_C4B_T2F), (GLvoid*)offsetof(V3F_C4B_C4B_T2F, position));
-	glVertexAttribPointer(_colorAttributeLocation, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_C4B_T2F), (GLvoid*)offsetof(V3F_C4B_C4B_T2F, color));
-	glVertexAttribPointer(_color2AttributeLocation, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_C4B_T2F), (GLvoid*)offsetof(V3F_C4B_C4B_T2F, color2));
-	glVertexAttribPointer(_texCoordsAttributeLocation, 2, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_C4B_T2F), (GLvoid*)offsetof(V3F_C4B_C4B_T2F, texCoords));
-
-	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferHandle);
-	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * _numIndicesBuffer, _indexBuffer, GL_STATIC_DRAW);
-
-	glDrawElements(GL_TRIANGLES, (GLsizei)_numIndicesBuffer, GL_UNSIGNED_SHORT, 0);
-
-	glBindBuffer(GL_ARRAY_BUFFER, 0);
-	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
-
-	_numVerticesBuffer = 0;
-	_numIndicesBuffer = 0;
-	_numBatches++;
-}
-
-void SkeletonTwoColorBatch::reset() {
-	_nextFreeCommand = 0;
-	_numVertices = 0;
-	_indices.setSize(0, 0);
-	_numVerticesBuffer = 0;
-	_numIndicesBuffer = 0;
-	_lastCommand = nullptr;
-	_numBatches = 0;
-}
-
-TwoColorTrianglesCommand* SkeletonTwoColorBatch::nextFreeCommand() {
-	if (_commandsPool.size() <= _nextFreeCommand) {
-		unsigned int newSize = _commandsPool.size() * 2 + 1;
-		for (int i = _commandsPool.size();  i < newSize; i++) {
-			_commandsPool.push_back(new TwoColorTrianglesCommand());
+
+	void SkeletonTwoColorBatch::reset() {
+		_nextFreeCommand = 0;
+		_numVertices = 0;
+		_indices.setSize(0, 0);
+		_numVerticesBuffer = 0;
+		_numIndicesBuffer = 0;
+		_lastCommand = nullptr;
+		_numBatches = 0;
+	}
+
+	TwoColorTrianglesCommand *SkeletonTwoColorBatch::nextFreeCommand() {
+		if (_commandsPool.size() <= _nextFreeCommand) {
+			unsigned int newSize = _commandsPool.size() * 2 + 1;
+			for (int i = _commandsPool.size(); i < newSize; i++) {
+				_commandsPool.push_back(new TwoColorTrianglesCommand());
+			}
 		}
+		TwoColorTrianglesCommand *command = _commandsPool[_nextFreeCommand++];
+		command->setForceFlush(false);
+		return command;
 	}
-	TwoColorTrianglesCommand* command = _commandsPool[_nextFreeCommand++];
-	command->setForceFlush(false);
-	return command;
-}
-}
+}// namespace spine
 
 #endif

+ 35 - 35
spine-cocos2dx/src/spine/v3/SkeletonTwoColorBatch.h

@@ -45,8 +45,8 @@ namespace spine {
 	};
 
 	struct TwoColorTriangles {
-		V3F_C4B_C4B_T2F* verts;
-		unsigned short* indices;
+		V3F_C4B_C4B_T2F *verts;
+		unsigned short *indices;
 		int vertCount;
 		int indexCount;
 	};
@@ -57,7 +57,7 @@ namespace spine {
 
 		~TwoColorTrianglesCommand();
 
-		void init(float globalOrder, GLuint textureID, cocos2d::GLProgramState* glProgramState, cocos2d::BlendFunc blendType, const TwoColorTriangles& triangles, const cocos2d::Mat4& mv, uint32_t flags);
+		void init(float globalOrder, GLuint textureID, cocos2d::GLProgramState *glProgramState, cocos2d::BlendFunc blendType, const TwoColorTriangles &triangles, const cocos2d::Mat4 &mv, uint32_t flags);
 
 		void useMaterial() const;
 
@@ -65,34 +65,34 @@ namespace spine {
 
 		inline GLuint getTextureID() const { return _textureID; }
 
-		inline const TwoColorTriangles& getTriangles() const { return _triangles; }
+		inline const TwoColorTriangles &getTriangles() const { return _triangles; }
 
 		inline ssize_t getVertexCount() const { return _triangles.vertCount; }
 
 		inline ssize_t getIndexCount() const { return _triangles.indexCount; }
 
-		inline const V3F_C4B_C4B_T2F* getVertices() const { return _triangles.verts; }
+		inline const V3F_C4B_C4B_T2F *getVertices() const { return _triangles.verts; }
 
-		inline const unsigned short* getIndices() const { return _triangles.indices; }
+		inline const unsigned short *getIndices() const { return _triangles.indices; }
 
-		inline cocos2d::GLProgramState* getGLProgramState() const { return _glProgramState; }
+		inline cocos2d::GLProgramState *getGLProgramState() const { return _glProgramState; }
 
 		inline cocos2d::BlendFunc getBlendType() const { return _blendType; }
 
-		inline const cocos2d::Mat4& getModelView() const { return _mv; }
+		inline const cocos2d::Mat4 &getModelView() const { return _mv; }
 
-		void draw ();
+		void draw();
 
-		void setForceFlush (bool forceFlush) { _forceFlush = forceFlush; }
+		void setForceFlush(bool forceFlush) { _forceFlush = forceFlush; }
 
-		bool isForceFlush () { return _forceFlush; };
+		bool isForceFlush() { return _forceFlush; };
 
 	protected:
 		void generateMaterialID();
 		uint32_t _materialID;
 		GLuint _textureID;
-		cocos2d::GLProgramState* _glProgramState;
-		cocos2d::GLProgram* _glProgram;
+		cocos2d::GLProgramState *_glProgramState;
+		cocos2d::GLProgram *_glProgram;
 		cocos2d::BlendFunc _blendType;
 		TwoColorTriangles _triangles;
 		cocos2d::Mat4 _mv;
@@ -102,38 +102,38 @@ namespace spine {
 
 	class SkeletonTwoColorBatch {
 	public:
-		static SkeletonTwoColorBatch* getInstance ();
+		static SkeletonTwoColorBatch *getInstance();
 
-		static void destroyInstance ();
+		static void destroyInstance();
 
-		void update (float delta);
+		void update(float delta);
 
-		V3F_C4B_C4B_T2F* allocateVertices(uint32_t numVertices);
+		V3F_C4B_C4B_T2F *allocateVertices(uint32_t numVertices);
 		void deallocateVertices(uint32_t numVertices);
 
-		unsigned short* allocateIndices(uint32_t numIndices);
+		unsigned short *allocateIndices(uint32_t numIndices);
 		void deallocateIndices(uint32_t numIndices);
 
-		TwoColorTrianglesCommand* addCommand(cocos2d::Renderer* renderer, float globalOrder, GLuint textureID, cocos2d::GLProgramState* glProgramState, cocos2d::BlendFunc blendType, const TwoColorTriangles& triangles, const cocos2d::Mat4& mv, uint32_t flags);
+		TwoColorTrianglesCommand *addCommand(cocos2d::Renderer *renderer, float globalOrder, GLuint textureID, cocos2d::GLProgramState *glProgramState, cocos2d::BlendFunc blendType, const TwoColorTriangles &triangles, const cocos2d::Mat4 &mv, uint32_t flags);
 
-		cocos2d::GLProgramState* getTwoColorTintProgramState () { return _twoColorTintShaderState; }
+		cocos2d::GLProgramState *getTwoColorTintProgramState() { return _twoColorTintShaderState; }
 
-		void batch (TwoColorTrianglesCommand* command);
+		void batch(TwoColorTrianglesCommand *command);
 
-		void flush (TwoColorTrianglesCommand* materialCommand);
+		void flush(TwoColorTrianglesCommand *materialCommand);
 
-		uint32_t getNumBatches () { return _numBatches; };
+		uint32_t getNumBatches() { return _numBatches; };
 
 	protected:
-		SkeletonTwoColorBatch ();
-		virtual ~SkeletonTwoColorBatch ();
+		SkeletonTwoColorBatch();
+		virtual ~SkeletonTwoColorBatch();
 
-		void reset ();
+		void reset();
 
-		TwoColorTrianglesCommand* nextFreeCommand ();
+		TwoColorTrianglesCommand *nextFreeCommand();
 
 		// pool of commands
-		std::vector<TwoColorTrianglesCommand*> _commandsPool;
+		std::vector<TwoColorTrianglesCommand *> _commandsPool;
 		uint32_t _nextFreeCommand;
 
 		// pool of vertices
@@ -144,29 +144,29 @@ namespace spine {
 		Vector<unsigned short> _indices;
 
 		// two color tint shader and state
-		cocos2d::GLProgram* _twoColorTintShader;
-		cocos2d::GLProgramState* _twoColorTintShaderState;
+		cocos2d::GLProgram *_twoColorTintShader;
+		cocos2d::GLProgramState *_twoColorTintShaderState;
 
 		// VBO handles & attribute locations
 		GLuint _vertexBufferHandle;
-		V3F_C4B_C4B_T2F* _vertexBuffer;
+		V3F_C4B_C4B_T2F *_vertexBuffer;
 		uint32_t _numVerticesBuffer;
 		GLuint _indexBufferHandle;
 		uint32_t _numIndicesBuffer;
-		unsigned short* _indexBuffer;
+		unsigned short *_indexBuffer;
 		GLint _positionAttributeLocation;
 		GLint _colorAttributeLocation;
 		GLint _color2AttributeLocation;
 		GLint _texCoordsAttributeLocation;
 
 		// last batched command, needed for flushing to set material
-		TwoColorTrianglesCommand* _lastCommand;
+		TwoColorTrianglesCommand *_lastCommand;
 
 		// number of batches in the last frame
 		uint32_t _numBatches;
 	};
-}
+}// namespace spine
 
 #endif
 
-#endif // SPINE_SKELETONTWOCOLORBATCH_H_
+#endif// SPINE_SKELETONTWOCOLORBATCH_H_

+ 139 - 140
spine-cocos2dx/src/spine/v4/SkeletonBatch.cpp

@@ -30,176 +30,175 @@
 #include <spine/spine-cocos2dx.h>
 #if COCOS2D_VERSION >= 0x00040000
 
-#include <spine/Extension.h>
 #include <algorithm>
+#include <spine/Extension.h>
 
 USING_NS_CC;
 #define EVENT_AFTER_DRAW_RESET_POSITION "director_after_draw"
 using std::max;
 #define INITIAL_SIZE (10000)
 
-#include "renderer/ccShaders.h"
 #include "renderer/backend/Device.h"
+#include "renderer/ccShaders.h"
 
 namespace spine {
 
-static SkeletonBatch* instance = nullptr;
+	static SkeletonBatch *instance = nullptr;
+
+	SkeletonBatch *SkeletonBatch::getInstance() {
+		if (!instance) instance = new SkeletonBatch();
+		return instance;
+	}
+
+	void SkeletonBatch::destroyInstance() {
+		if (instance) {
+			delete instance;
+			instance = nullptr;
+		}
+	}
+
+	SkeletonBatch::SkeletonBatch() {
+
+		auto program = backend::Program::getBuiltinProgram(backend::ProgramType::POSITION_TEXTURE_COLOR);
+		_programState = new backend::ProgramState(program);// new default program state
+		updateProgramStateLayout(_programState);
+		for (unsigned int i = 0; i < INITIAL_SIZE; i++) {
+			_commandsPool.push_back(createNewTrianglesCommand());
+		}
+		reset();
+		// callback after drawing is finished so we can clear out the batch state
+		// for the next frame
+		Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_AFTER_DRAW_RESET_POSITION, [this](EventCustom *eventCustom) {
+			this->update(0);
+		});
+		;
+	}
+
+	SkeletonBatch::~SkeletonBatch() {
+		Director::getInstance()->getEventDispatcher()->removeCustomEventListeners(EVENT_AFTER_DRAW_RESET_POSITION);
+
+		for (unsigned int i = 0; i < _commandsPool.size(); i++) {
+			CC_SAFE_RELEASE(_commandsPool[i]->getPipelineDescriptor().programState);
+			delete _commandsPool[i];
+			_commandsPool[i] = nullptr;
+		}
+
+		CC_SAFE_RELEASE(_programState);
+	}
+
+	void SkeletonBatch::updateProgramStateLayout(cocos2d::backend::ProgramState *programState) {
+		auto vertexLayout = programState->getVertexLayout();
 
-SkeletonBatch* SkeletonBatch::getInstance () {
-	if (!instance) instance = new SkeletonBatch();
-	return instance;
-}
+		auto locPosition = programState->getAttributeLocation("a_position");
+		auto locTexcoord = programState->getAttributeLocation("a_texCoord");
+		auto locColor = programState->getAttributeLocation("a_color");
+		vertexLayout->setAttribute("a_position", locPosition, backend::VertexFormat::FLOAT3, offsetof(V3F_C4B_T2F, vertices), false);
+		vertexLayout->setAttribute("a_color", locColor, backend::VertexFormat::UBYTE4, offsetof(V3F_C4B_T2F, colors), true);
+		vertexLayout->setAttribute("a_texCoord", locTexcoord, backend::VertexFormat::FLOAT2, offsetof(V3F_C4B_T2F, texCoords), false);
+		vertexLayout->setLayout(sizeof(_vertices[0]));
 
-void SkeletonBatch::destroyInstance () {
-	if (instance) {
-		delete instance;
-		instance = nullptr;
+
+		_locMVP = programState->getUniformLocation("u_MVPMatrix");
+		_locTexture = programState->getUniformLocation("u_texture");
 	}
-}
-
-SkeletonBatch::SkeletonBatch () {
-
-    auto program = backend::Program::getBuiltinProgram(backend::ProgramType::POSITION_TEXTURE_COLOR);
-	_programState = new backend::ProgramState(program); // new default program state
-	updateProgramStateLayout(_programState);
-    for (unsigned int i = 0; i < INITIAL_SIZE; i++) {
-        _commandsPool.push_back(createNewTrianglesCommand());
-    }
-    reset();
-    // callback after drawing is finished so we can clear out the batch state
-    // for the next frame
-    Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_AFTER_DRAW_RESET_POSITION, [this](EventCustom* eventCustom) {
-        this->update(0);
-        });;
-}
-
-SkeletonBatch::~SkeletonBatch () {
-	Director::getInstance()->getEventDispatcher()->removeCustomEventListeners(EVENT_AFTER_DRAW_RESET_POSITION);
-
-	for (unsigned int i = 0; i < _commandsPool.size(); i++) {
-        CC_SAFE_RELEASE(_commandsPool[i]->getPipelineDescriptor().programState);
-		delete _commandsPool[i];
-		_commandsPool[i] = nullptr;
+
+	void SkeletonBatch::update(float delta) {
+		reset();
 	}
 
-	CC_SAFE_RELEASE(_programState);
-}
-
-void SkeletonBatch::updateProgramStateLayout(cocos2d::backend::ProgramState* programState)
-{
-	auto vertexLayout = programState->getVertexLayout();
-
-	auto locPosition = programState->getAttributeLocation("a_position");
-	auto locTexcoord = programState->getAttributeLocation("a_texCoord");
-	auto locColor = programState->getAttributeLocation("a_color");
-	vertexLayout->setAttribute("a_position", locPosition, backend::VertexFormat::FLOAT3, offsetof(V3F_C4B_T2F, vertices), false);
-	vertexLayout->setAttribute("a_color", locColor, backend::VertexFormat::UBYTE4, offsetof(V3F_C4B_T2F, colors), true);
-	vertexLayout->setAttribute("a_texCoord", locTexcoord, backend::VertexFormat::FLOAT2, offsetof(V3F_C4B_T2F, texCoords), false);
-	vertexLayout->setLayout(sizeof(_vertices[0]));
-
-
-	_locMVP = programState->getUniformLocation("u_MVPMatrix");
-	_locTexture = programState->getUniformLocation("u_texture");
-}
-
-void SkeletonBatch::update (float delta) {
-	reset();
-}
-
-cocos2d::V3F_C4B_T2F* SkeletonBatch::allocateVertices(uint32_t numVertices) {
-	if (_vertices.size() - _numVertices < numVertices) {
-		cocos2d::V3F_C4B_T2F* oldData = _vertices.data();
-		_vertices.resize((_vertices.size() + numVertices) * 2 + 1);
-		cocos2d::V3F_C4B_T2F* newData = _vertices.data();
-		for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
-			TrianglesCommand* command = _commandsPool[i];
-			cocos2d::TrianglesCommand::Triangles& triangles = (cocos2d::TrianglesCommand::Triangles&)command->getTriangles();
-			triangles.verts = newData + (triangles.verts - oldData);
+	cocos2d::V3F_C4B_T2F *SkeletonBatch::allocateVertices(uint32_t numVertices) {
+		if (_vertices.size() - _numVertices < numVertices) {
+			cocos2d::V3F_C4B_T2F *oldData = _vertices.data();
+			_vertices.resize((_vertices.size() + numVertices) * 2 + 1);
+			cocos2d::V3F_C4B_T2F *newData = _vertices.data();
+			for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
+				TrianglesCommand *command = _commandsPool[i];
+				cocos2d::TrianglesCommand::Triangles &triangles = (cocos2d::TrianglesCommand::Triangles &) command->getTriangles();
+				triangles.verts = newData + (triangles.verts - oldData);
+			}
 		}
+
+		cocos2d::V3F_C4B_T2F *vertices = _vertices.data() + _numVertices;
+		_numVertices += numVertices;
+		return vertices;
+	}
+
+	void SkeletonBatch::deallocateVertices(uint32_t numVertices) {
+		_numVertices -= numVertices;
 	}
 
-	cocos2d::V3F_C4B_T2F* vertices = _vertices.data() + _numVertices;
-	_numVertices += numVertices;
-	return vertices;
-}
-
-void SkeletonBatch::deallocateVertices(uint32_t numVertices) {
-	_numVertices -= numVertices;
-}
-
-
-unsigned short* SkeletonBatch::allocateIndices(uint32_t numIndices) {
-	if (_indices.getCapacity() - _indices.size() < numIndices) {
-		unsigned short* oldData = _indices.buffer();
-		int oldSize = _indices.size();
-		_indices.ensureCapacity(_indices.size() + numIndices);
-		unsigned short* newData = _indices.buffer();
-		for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
-			TrianglesCommand* command = _commandsPool[i];
-			cocos2d::TrianglesCommand::Triangles& triangles = (cocos2d::TrianglesCommand::Triangles&)command->getTriangles();
-			if (triangles.indices >= oldData && triangles.indices < oldData + oldSize) {
-				triangles.indices = newData + (triangles.indices - oldData);
+
+	unsigned short *SkeletonBatch::allocateIndices(uint32_t numIndices) {
+		if (_indices.getCapacity() - _indices.size() < numIndices) {
+			unsigned short *oldData = _indices.buffer();
+			int oldSize = _indices.size();
+			_indices.ensureCapacity(_indices.size() + numIndices);
+			unsigned short *newData = _indices.buffer();
+			for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
+				TrianglesCommand *command = _commandsPool[i];
+				cocos2d::TrianglesCommand::Triangles &triangles = (cocos2d::TrianglesCommand::Triangles &) command->getTriangles();
+				if (triangles.indices >= oldData && triangles.indices < oldData + oldSize) {
+					triangles.indices = newData + (triangles.indices - oldData);
+				}
 			}
 		}
+
+		unsigned short *indices = _indices.buffer() + _indices.size();
+		_indices.setSize(_indices.size() + numIndices, 0);
+		return indices;
 	}
 
-	unsigned short* indices = _indices.buffer() + _indices.size();
-	_indices.setSize(_indices.size() + numIndices, 0);
-	return indices;
-}
+	void SkeletonBatch::deallocateIndices(uint32_t numIndices) {
+		_indices.setSize(_indices.size() - numIndices, 0);
+	}
 
-void SkeletonBatch::deallocateIndices(uint32_t numIndices) {
-	_indices.setSize(_indices.size() - numIndices, 0);
-}
 
+	cocos2d::TrianglesCommand *SkeletonBatch::addCommand(cocos2d::Renderer *renderer, float globalOrder, cocos2d::Texture2D *texture, backend::ProgramState *programState, cocos2d::BlendFunc blendType, const cocos2d::TrianglesCommand::Triangles &triangles, const cocos2d::Mat4 &mv, uint32_t flags) {
+		TrianglesCommand *command = nextFreeCommand();
+		const cocos2d::Mat4 &projectionMat = Director::getInstance()->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
 
-cocos2d::TrianglesCommand* SkeletonBatch::addCommand(cocos2d::Renderer* renderer, float globalOrder, cocos2d::Texture2D* texture, backend::ProgramState* programState, cocos2d::BlendFunc blendType, const cocos2d::TrianglesCommand::Triangles& triangles, const cocos2d::Mat4& mv, uint32_t flags) {
-	TrianglesCommand* command = nextFreeCommand();
-    const cocos2d::Mat4& projectionMat = Director::getInstance()->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);    
+		if (programState == nullptr)
+			programState = _programState;
 
-	if (programState == nullptr)
-		programState = _programState;
+		CCASSERT(programState, "programState should not be null");
 
-	CCASSERT(programState, "programState should not be null");
+		auto &pipelinePS = command->getPipelineDescriptor().programState;
+		if (pipelinePS == nullptr || pipelinePS->getProgram() != programState->getProgram()) {
+			CC_SAFE_RELEASE(pipelinePS);
+			pipelinePS = programState->clone();
 
-	auto& pipelinePS = command->getPipelineDescriptor().programState;
-	if (pipelinePS == nullptr || pipelinePS->getProgram() != programState->getProgram()) 
-	{
-		CC_SAFE_RELEASE(pipelinePS);
-		pipelinePS = programState->clone();
+			updateProgramStateLayout(pipelinePS);
+		}
+
+		pipelinePS->setUniform(_locMVP, projectionMat.m, sizeof(projectionMat.m));
+		pipelinePS->setTexture(_locTexture, 0, texture->getBackendTexture());
+
+		command->init(globalOrder, texture, blendType, triangles, mv, flags);
+		renderer->addCommand(command);
+		return command;
+	}
+
+	void SkeletonBatch::reset() {
+		_nextFreeCommand = 0;
+		_numVertices = 0;
+		_indices.setSize(0, 0);
+	}
+
+	cocos2d::TrianglesCommand *SkeletonBatch::nextFreeCommand() {
+		if (_commandsPool.size() <= _nextFreeCommand) {
+			unsigned int newSize = _commandsPool.size() * 2 + 1;
+			for (int i = _commandsPool.size(); i < newSize; i++) {
+				_commandsPool.push_back(createNewTrianglesCommand());
+			}
+		}
+		auto *command = _commandsPool[_nextFreeCommand++];
+		return command;
+	}
 
-		updateProgramStateLayout(pipelinePS);
+	cocos2d::TrianglesCommand *SkeletonBatch::createNewTrianglesCommand() {
+		auto *command = new TrianglesCommand();
+		return command;
 	}
-	
-	pipelinePS->setUniform(_locMVP, projectionMat.m, sizeof(projectionMat.m));
-	pipelinePS->setTexture(_locTexture, 0, texture->getBackendTexture());
-
-	command->init(globalOrder, texture, blendType, triangles, mv, flags);
-	renderer->addCommand(command);
-	return command;
-}
-
-void SkeletonBatch::reset() {
-	_nextFreeCommand = 0;
-	_numVertices = 0;
-	_indices.setSize(0, 0);
-}
-
-cocos2d::TrianglesCommand* SkeletonBatch::nextFreeCommand() {
-    if (_commandsPool.size() <= _nextFreeCommand) {
-        unsigned int newSize = _commandsPool.size() * 2 + 1;
-        for (int i = _commandsPool.size(); i < newSize; i++) {
-            _commandsPool.push_back(createNewTrianglesCommand());
-        }
-    }
-    auto* command = _commandsPool[_nextFreeCommand++];
-    return command;
-}
-
-cocos2d::TrianglesCommand *SkeletonBatch::createNewTrianglesCommand() {
-    auto* command = new TrianglesCommand();
-    return command;
-}
-}
+}// namespace spine
 
 #endif

+ 42 - 42
spine-cocos2dx/src/spine/v4/SkeletonBatch.h

@@ -33,57 +33,57 @@
 #include "cocos2d.h"
 #if COCOS2D_VERSION >= 0x00040000
 
+#include "renderer/backend/ProgramState.h"
 #include <spine/spine.h>
 #include <vector>
-#include "renderer/backend/ProgramState.h"
 
 namespace spine {
-    
-    class SkeletonBatch {
-    public:
-        static SkeletonBatch* getInstance ();
-        
-        static void destroyInstance ();
-        
-        void update (float delta);
-		
-		cocos2d::V3F_C4B_T2F* allocateVertices(uint32_t numVertices);
+
+	class SkeletonBatch {
+	public:
+		static SkeletonBatch *getInstance();
+
+		static void destroyInstance();
+
+		void update(float delta);
+
+		cocos2d::V3F_C4B_T2F *allocateVertices(uint32_t numVertices);
 		void deallocateVertices(uint32_t numVertices);
-		unsigned short* allocateIndices(uint32_t numIndices);
+		unsigned short *allocateIndices(uint32_t numIndices);
 		void deallocateIndices(uint32_t numVertices);
-		cocos2d::TrianglesCommand* addCommand(cocos2d::Renderer* renderer, float globalOrder, cocos2d::Texture2D* texture, cocos2d::backend::ProgramState* programState, cocos2d::BlendFunc blendType, const cocos2d::TrianglesCommand::Triangles& triangles, const cocos2d::Mat4& mv, uint32_t flags);
-        
-		void updateProgramStateLayout(cocos2d::backend::ProgramState* programState);
-
-    protected:
-        SkeletonBatch ();
-        virtual ~SkeletonBatch ();
-		
-		void reset ();
-		
-		cocos2d::TrianglesCommand* nextFreeCommand ();
-
-        cocos2d::TrianglesCommand* createNewTrianglesCommand();
-
-        // the default program state for batch draw
-        cocos2d::backend::ProgramState*                     _programState = nullptr;
-        cocos2d::backend::UniformLocation                   _locMVP;
-        cocos2d::backend::UniformLocation                   _locTexture;
-		
+		cocos2d::TrianglesCommand *addCommand(cocos2d::Renderer *renderer, float globalOrder, cocos2d::Texture2D *texture, cocos2d::backend::ProgramState *programState, cocos2d::BlendFunc blendType, const cocos2d::TrianglesCommand::Triangles &triangles, const cocos2d::Mat4 &mv, uint32_t flags);
+
+		void updateProgramStateLayout(cocos2d::backend::ProgramState *programState);
+
+	protected:
+		SkeletonBatch();
+		virtual ~SkeletonBatch();
+
+		void reset();
+
+		cocos2d::TrianglesCommand *nextFreeCommand();
+
+		cocos2d::TrianglesCommand *createNewTrianglesCommand();
+
+		// the default program state for batch draw
+		cocos2d::backend::ProgramState *_programState = nullptr;
+		cocos2d::backend::UniformLocation _locMVP;
+		cocos2d::backend::UniformLocation _locTexture;
+
 		// pool of commands
-		std::vector<cocos2d::TrianglesCommand*>             _commandsPool;
-		uint32_t                                            _nextFreeCommand;
-		
+		std::vector<cocos2d::TrianglesCommand *> _commandsPool;
+		uint32_t _nextFreeCommand;
+
 		// pool of vertices
-		std::vector<cocos2d::V3F_C4B_T2F>                   _vertices;
-		uint32_t                                            _numVertices;
-		
+		std::vector<cocos2d::V3F_C4B_T2F> _vertices;
+		uint32_t _numVertices;
+
 		// pool of indices
-		Vector<unsigned short>                              _indices;
-    };
-	
-}
+		Vector<unsigned short> _indices;
+	};
+
+}// namespace spine
 
 #endif
 
-#endif // SPINE_SKELETONBATCH_H_
+#endif// SPINE_SKELETONBATCH_H_

+ 322 - 335
spine-cocos2dx/src/spine/v4/SkeletonTwoColorBatch.cpp

@@ -30,15 +30,15 @@
 #include <spine/spine-cocos2dx.h>
 #if COCOS2D_VERSION >= 0x00040000
 
-#include <spine/Extension.h>
-#include <algorithm>
-#include <stddef.h> // offsetof
 #include "base/ccTypes.h"
 #include "base/ccUtils.h"
+#include <algorithm>
+#include <spine/Extension.h>
+#include <stddef.h>// offsetof
 
-#include "xxhash.h"
-#include "renderer/ccShaders.h"
 #include "renderer/backend/Device.h"
+#include "renderer/ccShaders.h"
+#include "xxhash.h"
 
 USING_NS_CC;
 #define EVENT_AFTER_DRAW_RESET_POSITION "director_after_draw"
@@ -47,387 +47,374 @@ using std::max;
 #define MAX_VERTICES 64000
 #define MAX_INDICES 64000
 
-#define STRINGIFY(A)  #A
+#define STRINGIFY(A) #A
 
 namespace {
 
-    const char* TWO_COLOR_TINT_VERTEX_SHADER = STRINGIFY(
-    uniform mat4 u_PMatrix;
-    attribute vec4 a_position;
-    attribute vec4 a_color;
-    attribute vec4 a_color2;
-    attribute vec2 a_texCoords;
-
-    \n#ifdef GL_ES\n
-        varying lowp vec4 v_light;
-    varying lowp vec4 v_dark;
-    varying mediump vec2 v_texCoord;
-    \n#else\n
-        varying vec4 v_light;
-    varying vec4 v_dark;
-    varying vec2 v_texCoord;
-
-    \n#endif\n
-
-        void main() {
-        v_light = a_color;
-        v_dark = a_color2;
-        v_texCoord = a_texCoords;
-        gl_Position = u_PMatrix * a_position;
-    }
-    );
-
-    const char* TWO_COLOR_TINT_FRAGMENT_SHADER = STRINGIFY(
-        \n#ifdef GL_ES\n
-        precision lowp float;
-    \n#endif\n
-        uniform sampler2D u_texture;
-    varying vec4 v_light;
-    varying vec4 v_dark;
-    varying vec2 v_texCoord;
-
-    void main() {
-        vec4 texColor = texture2D(u_texture, v_texCoord);
-        float alpha = texColor.a * v_light.a;
-        gl_FragColor.a = alpha;
-        gl_FragColor.rgb = ((texColor.a - 1.0) * v_dark.a + 1.0 - texColor.rgb) * v_dark.rgb + texColor.rgb * v_light.rgb;
-    }
-    );
-
-
-    std::shared_ptr<backend::ProgramState>  __twoColorProgramState = nullptr;
-    backend::UniformLocation                __locPMatrix;
-    backend::UniformLocation                __locTexture;
-
-    static void updateProgramStateLayout(backend::ProgramState* programState) {
-        __locPMatrix = programState->getUniformLocation("u_PMatrix");
-        __locTexture = programState->getUniformLocation("u_texture");
-
-        auto layout = programState->getVertexLayout();
-
-        auto locPosition = programState->getAttributeLocation("a_position");
-        auto locTexcoord = programState->getAttributeLocation("a_texCoords");
-        auto locColor = programState->getAttributeLocation("a_color");
-        auto locColor2 = programState->getAttributeLocation("a_color2");
-
-        layout->setAttribute("a_position", locPosition, backend::VertexFormat::FLOAT3, offsetof(spine::V3F_C4B_C4B_T2F, position), false);
-        layout->setAttribute("a_color", locColor, backend::VertexFormat::UBYTE4, offsetof(spine::V3F_C4B_C4B_T2F, color), true);
-        layout->setAttribute("a_color2", locColor2, backend::VertexFormat::UBYTE4, offsetof(spine::V3F_C4B_C4B_T2F, color2), true);
-        layout->setAttribute("a_texCoords", locTexcoord, backend::VertexFormat::FLOAT2, offsetof(spine::V3F_C4B_C4B_T2F, texCoords), false);
-        layout->setLayout(sizeof(spine::V3F_C4B_C4B_T2F));
-    }
-
-    static void initTwoColorProgramState()
-    {
-        if (__twoColorProgramState)
-        {
-            return;
-        }
-        auto program = backend::Device::getInstance()->newProgram(TWO_COLOR_TINT_VERTEX_SHADER, TWO_COLOR_TINT_FRAGMENT_SHADER);
-        auto* programState = new backend::ProgramState(program);
-        program->release();
-
-        updateProgramStateLayout(programState);
-
-        __twoColorProgramState = std::shared_ptr<backend::ProgramState>(programState);
-    }
-
-}
+	const char *TWO_COLOR_TINT_VERTEX_SHADER = STRINGIFY(
+			uniform mat4 u_PMatrix;
+			attribute vec4 a_position;
+			attribute vec4 a_color;
+			attribute vec4 a_color2;
+			attribute vec2 a_texCoords;
+
+    \n #ifdef GL_ES\n
+					varying lowp vec4 v_light;
+			varying lowp vec4 v_dark;
+			varying mediump vec2 v_texCoord;
+    \n #else \n
+					varying vec4 v_light;
+			varying vec4 v_dark;
+			varying vec2 v_texCoord;
+
+    \n #endif \n
+
+			void main() {
+				v_light = a_color;
+				v_dark = a_color2;
+				v_texCoord = a_texCoords;
+				gl_Position = u_PMatrix * a_position;
+			});
+
+	const char *TWO_COLOR_TINT_FRAGMENT_SHADER = STRINGIFY(
+        \n #ifdef GL_ES\n
+					precision lowp float;
+    \n #endif \n
+							uniform sampler2D u_texture;
+			varying vec4 v_light;
+			varying vec4 v_dark;
+			varying vec2 v_texCoord;
+
+			void main() {
+				vec4 texColor = texture2D(u_texture, v_texCoord);
+				float alpha = texColor.a * v_light.a;
+				gl_FragColor.a = alpha;
+				gl_FragColor.rgb = ((texColor.a - 1.0) * v_dark.a + 1.0 - texColor.rgb) * v_dark.rgb + texColor.rgb * v_light.rgb;
+			});
+
+
+	std::shared_ptr<backend::ProgramState> __twoColorProgramState = nullptr;
+	backend::UniformLocation __locPMatrix;
+	backend::UniformLocation __locTexture;
+
+	static void updateProgramStateLayout(backend::ProgramState *programState) {
+		__locPMatrix = programState->getUniformLocation("u_PMatrix");
+		__locTexture = programState->getUniformLocation("u_texture");
+
+		auto layout = programState->getVertexLayout();
+
+		auto locPosition = programState->getAttributeLocation("a_position");
+		auto locTexcoord = programState->getAttributeLocation("a_texCoords");
+		auto locColor = programState->getAttributeLocation("a_color");
+		auto locColor2 = programState->getAttributeLocation("a_color2");
+
+		layout->setAttribute("a_position", locPosition, backend::VertexFormat::FLOAT3, offsetof(spine::V3F_C4B_C4B_T2F, position), false);
+		layout->setAttribute("a_color", locColor, backend::VertexFormat::UBYTE4, offsetof(spine::V3F_C4B_C4B_T2F, color), true);
+		layout->setAttribute("a_color2", locColor2, backend::VertexFormat::UBYTE4, offsetof(spine::V3F_C4B_C4B_T2F, color2), true);
+		layout->setAttribute("a_texCoords", locTexcoord, backend::VertexFormat::FLOAT2, offsetof(spine::V3F_C4B_C4B_T2F, texCoords), false);
+		layout->setLayout(sizeof(spine::V3F_C4B_C4B_T2F));
+	}
+
+	static void initTwoColorProgramState() {
+		if (__twoColorProgramState) {
+			return;
+		}
+		auto program = backend::Device::getInstance()->newProgram(TWO_COLOR_TINT_VERTEX_SHADER, TWO_COLOR_TINT_FRAGMENT_SHADER);
+		auto *programState = new backend::ProgramState(program);
+		program->release();
+
+		updateProgramStateLayout(programState);
+
+		__twoColorProgramState = std::shared_ptr<backend::ProgramState>(programState);
+	}
+
+}// namespace
 
 namespace spine {
 
-TwoColorTrianglesCommand::TwoColorTrianglesCommand() :_materialID(0), _texture(nullptr), _blendType(BlendFunc::DISABLE) {
-	_type = RenderCommand::Type::CUSTOM_COMMAND;
-}
+	TwoColorTrianglesCommand::TwoColorTrianglesCommand() : _materialID(0), _texture(nullptr), _blendType(BlendFunc::DISABLE) {
+		_type = RenderCommand::Type::CUSTOM_COMMAND;
+	}
 
-void TwoColorTrianglesCommand::init(float globalOrder, cocos2d::Texture2D *texture, cocos2d::backend::ProgramState* programState, BlendFunc blendType, const TwoColorTriangles& triangles, const Mat4& mv, uint32_t flags) {
+	void TwoColorTrianglesCommand::init(float globalOrder, cocos2d::Texture2D *texture, cocos2d::backend::ProgramState *programState, BlendFunc blendType, const TwoColorTriangles &triangles, const Mat4 &mv, uint32_t flags) {
 
-    updateCommandPipelineDescriptor(programState);
-    const cocos2d::Mat4& projectionMat = Director::getInstance()->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
+		updateCommandPipelineDescriptor(programState);
+		const cocos2d::Mat4 &projectionMat = Director::getInstance()->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
 
-    auto finalMatrix = projectionMat * mv;
+		auto finalMatrix = projectionMat * mv;
 
-    _programState->setUniform(_locPMatrix, finalMatrix.m, sizeof(finalMatrix.m));
-    _programState->setTexture(_locTexture, 0, texture->getBackendTexture());
+		_programState->setUniform(_locPMatrix, finalMatrix.m, sizeof(finalMatrix.m));
+		_programState->setTexture(_locTexture, 0, texture->getBackendTexture());
 
 
-    RenderCommand::init(globalOrder, mv, flags);
+		RenderCommand::init(globalOrder, mv, flags);
 
-    _triangles = triangles;
-    if (_triangles.indexCount % 3 != 0) {
-        int count = _triangles.indexCount;
-        _triangles.indexCount = count / 3 * 3;
-        CCLOGERROR("Resize indexCount from %d to %d, size must be multiple times of 3", count, _triangles.indexCount);
-    }
+		_triangles = triangles;
+		if (_triangles.indexCount % 3 != 0) {
+			int count = _triangles.indexCount;
+			_triangles.indexCount = count / 3 * 3;
+			CCLOGERROR("Resize indexCount from %d to %d, size must be multiple times of 3", count, _triangles.indexCount);
+		}
 
-    _mv = mv;
+		_mv = mv;
 
-    if (_blendType.src != blendType.src || _blendType.dst != blendType.dst ||
-        _texture != texture->getBackendTexture() || _pipelineDescriptor.programState != _programState)
-    {
-		_texture = texture->getBackendTexture();
-		_blendType = blendType;
+		if (_blendType.src != blendType.src || _blendType.dst != blendType.dst ||
+			_texture != texture->getBackendTexture() || _pipelineDescriptor.programState != _programState) {
+			_texture = texture->getBackendTexture();
+			_blendType = blendType;
 
-        _prog = _programState->getProgram();
+			_prog = _programState->getProgram();
 
-        auto& blendDescriptor = _pipelineDescriptor.blendDescriptor;
-        blendDescriptor.blendEnabled = true;
-        blendDescriptor.sourceRGBBlendFactor = blendDescriptor.sourceAlphaBlendFactor = blendType.src;
-        blendDescriptor.destinationRGBBlendFactor = blendDescriptor.destinationAlphaBlendFactor = blendType.dst;
+			auto &blendDescriptor = _pipelineDescriptor.blendDescriptor;
+			blendDescriptor.blendEnabled = true;
+			blendDescriptor.sourceRGBBlendFactor = blendDescriptor.sourceAlphaBlendFactor = blendType.src;
+			blendDescriptor.destinationRGBBlendFactor = blendDescriptor.destinationAlphaBlendFactor = blendType.dst;
 
-		generateMaterialID();
+			generateMaterialID();
+		}
 	}
-}
-
-
-
-void TwoColorTrianglesCommand::updateCommandPipelineDescriptor(cocos2d::backend::ProgramState* programState)
-{
-    // OPTIMIZE ME: all commands belong a same Node should share a same programState like SkeletonBatch
-    if (!__twoColorProgramState)
-    {
-        initTwoColorProgramState();
-    }
-
-    bool needsUpdateStateLayout = false;
-    auto& pipelinePS = _pipelineDescriptor.programState;
-    if (programState != nullptr)
-    {
-        if (_programState != programState) {
-            CC_SAFE_RELEASE(_programState);
-            _programState = programState; // Because the programState belong to Node, so no need to clone
-            CC_SAFE_RETAIN(_programState);
-            needsUpdateStateLayout = true;
-        }
-    }
-    else {
-        needsUpdateStateLayout = _programState != nullptr && _programState->getProgram() != __twoColorProgramState->getProgram();
-        CC_SAFE_RELEASE(_programState);
-        _programState = __twoColorProgramState->clone();
-    }
-
-    CCASSERT(_programState, "programState should not be null");
-    pipelinePS = _programState;
-
-    if (needsUpdateStateLayout)
-        updateProgramStateLayout(pipelinePS);
-
-    _locPMatrix = __locPMatrix;
-    _locTexture = __locTexture;
-}
-
-TwoColorTrianglesCommand::~TwoColorTrianglesCommand()
-{
-    CC_SAFE_RELEASE_NULL(_programState);
-}
-
-void TwoColorTrianglesCommand::generateMaterialID() {
-	// do not batch if using custom uniforms (since we cannot batch) it
-
-
-    struct
-    {
-        void* texture;
-        void* prog;
-        backend::BlendFactor src;
-        backend::BlendFactor dst;
-    }hashMe;
-
-    // NOTE: Initialize hashMe struct to make the value of padding bytes be filled with zero.
-    // It's important since XXH32 below will also consider the padding bytes which probably
-    // are set to random values by different compilers.
-    memset(&hashMe, 0, sizeof(hashMe));
-
-    hashMe.texture = _texture;
-    hashMe.src = _blendType.src;
-    hashMe.dst = _blendType.dst;
-    hashMe.prog = _prog;
-    _materialID = XXH32((const void*)&hashMe, sizeof(hashMe), 0);
-}
-
-
-void TwoColorTrianglesCommand::draw(Renderer *r) {
-	SkeletonTwoColorBatch::getInstance()->batch(r, this);
-}
-
-void TwoColorTrianglesCommand::updateVertexAndIndexBuffer(Renderer *r, V3F_C4B_C4B_T2F *vertices, int verticesSize, uint16_t *indices, int indicesSize)
-{
-    if(verticesSize != _vertexCapacity)
-        createVertexBuffer(sizeof(V3F_C4B_C4B_T2F), verticesSize, CustomCommand::BufferUsage::DYNAMIC);
-    if(indicesSize != _indexCapacity)
-        createIndexBuffer(CustomCommand::IndexFormat::U_SHORT, indicesSize, CustomCommand::BufferUsage::DYNAMIC);
-
-    updateVertexBuffer(vertices, sizeof(V3F_C4B_C4B_T2F) * verticesSize);
-    updateIndexBuffer(indices, sizeof(uint16_t) * indicesSize);
-}
-
-
-static SkeletonTwoColorBatch* instance = nullptr;
-
-SkeletonTwoColorBatch* SkeletonTwoColorBatch::getInstance () {
-	if (!instance) instance = new SkeletonTwoColorBatch();
-	return instance;
-}
-
-void SkeletonTwoColorBatch::destroyInstance () {
-	if (instance) {
-		delete instance;
-		instance = nullptr;
+
+
+	void TwoColorTrianglesCommand::updateCommandPipelineDescriptor(cocos2d::backend::ProgramState *programState) {
+		// OPTIMIZE ME: all commands belong a same Node should share a same programState like SkeletonBatch
+		if (!__twoColorProgramState) {
+			initTwoColorProgramState();
+		}
+
+		bool needsUpdateStateLayout = false;
+		auto &pipelinePS = _pipelineDescriptor.programState;
+		if (programState != nullptr) {
+			if (_programState != programState) {
+				CC_SAFE_RELEASE(_programState);
+				_programState = programState;// Because the programState belong to Node, so no need to clone
+				CC_SAFE_RETAIN(_programState);
+				needsUpdateStateLayout = true;
+			}
+		} else {
+			needsUpdateStateLayout = _programState != nullptr && _programState->getProgram() != __twoColorProgramState->getProgram();
+			CC_SAFE_RELEASE(_programState);
+			_programState = __twoColorProgramState->clone();
+		}
+
+		CCASSERT(_programState, "programState should not be null");
+		pipelinePS = _programState;
+
+		if (needsUpdateStateLayout)
+			updateProgramStateLayout(pipelinePS);
+
+		_locPMatrix = __locPMatrix;
+		_locTexture = __locTexture;
 	}
-}
 
-SkeletonTwoColorBatch::SkeletonTwoColorBatch () : _vertexBuffer(0), _indexBuffer(0) {
-    _commandsPool.reserve(INITIAL_SIZE);
-	for (unsigned int i = 0; i < INITIAL_SIZE; i++) {
-		_commandsPool.push_back(new TwoColorTrianglesCommand());
+	TwoColorTrianglesCommand::~TwoColorTrianglesCommand() {
+		CC_SAFE_RELEASE_NULL(_programState);
 	}
 
-	reset ();
+	void TwoColorTrianglesCommand::generateMaterialID() {
+		// do not batch if using custom uniforms (since we cannot batch) it
 
-	// callback after drawing is finished so we can clear out the batch state
-	// for the next frame
-	Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_AFTER_DRAW_RESET_POSITION, [this](EventCustom* eventCustom){
-		this->update(0);
-	});
 
-}
+		struct
+		{
+			void *texture;
+			void *prog;
+			backend::BlendFactor src;
+			backend::BlendFactor dst;
+		} hashMe;
 
-SkeletonTwoColorBatch::~SkeletonTwoColorBatch () {
-	Director::getInstance()->getEventDispatcher()->removeCustomEventListeners(EVENT_AFTER_DRAW_RESET_POSITION);
+		// NOTE: Initialize hashMe struct to make the value of padding bytes be filled with zero.
+		// It's important since XXH32 below will also consider the padding bytes which probably
+		// are set to random values by different compilers.
+		memset(&hashMe, 0, sizeof(hashMe));
 
-	for (unsigned int i = 0; i < _commandsPool.size(); i++) {
-		delete _commandsPool[i];
-		_commandsPool[i] = nullptr;
+		hashMe.texture = _texture;
+		hashMe.src = _blendType.src;
+		hashMe.dst = _blendType.dst;
+		hashMe.prog = _prog;
+		_materialID = XXH32((const void *) &hashMe, sizeof(hashMe), 0);
 	}
 
-	delete[] _vertexBuffer;
-	delete[] _indexBuffer;
-}
-
-void SkeletonTwoColorBatch::update (float delta) {
-	reset();
-}
-
-V3F_C4B_C4B_T2F* SkeletonTwoColorBatch::allocateVertices(uint32_t numVertices) {
-	if (_vertices.size() - _numVertices < numVertices) {
-		V3F_C4B_C4B_T2F* oldData = _vertices.data();
-		_vertices.resize((_vertices.size() + numVertices) * 2 + 1);
-		V3F_C4B_C4B_T2F* newData = _vertices.data();
-		for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
-			TwoColorTrianglesCommand* command = _commandsPool[i];
-			TwoColorTriangles& triangles = (TwoColorTriangles&)command->getTriangles();
-			triangles.verts = newData + (triangles.verts - oldData);
+
+	void TwoColorTrianglesCommand::draw(Renderer *r) {
+		SkeletonTwoColorBatch::getInstance()->batch(r, this);
+	}
+
+	void TwoColorTrianglesCommand::updateVertexAndIndexBuffer(Renderer *r, V3F_C4B_C4B_T2F *vertices, int verticesSize, uint16_t *indices, int indicesSize) {
+		if (verticesSize != _vertexCapacity)
+			createVertexBuffer(sizeof(V3F_C4B_C4B_T2F), verticesSize, CustomCommand::BufferUsage::DYNAMIC);
+		if (indicesSize != _indexCapacity)
+			createIndexBuffer(CustomCommand::IndexFormat::U_SHORT, indicesSize, CustomCommand::BufferUsage::DYNAMIC);
+
+		updateVertexBuffer(vertices, sizeof(V3F_C4B_C4B_T2F) * verticesSize);
+		updateIndexBuffer(indices, sizeof(uint16_t) * indicesSize);
+	}
+
+
+	static SkeletonTwoColorBatch *instance = nullptr;
+
+	SkeletonTwoColorBatch *SkeletonTwoColorBatch::getInstance() {
+		if (!instance) instance = new SkeletonTwoColorBatch();
+		return instance;
+	}
+
+	void SkeletonTwoColorBatch::destroyInstance() {
+		if (instance) {
+			delete instance;
+			instance = nullptr;
 		}
 	}
 
-	V3F_C4B_C4B_T2F* vertices = _vertices.data() + _numVertices;
-	_numVertices += numVertices;
-	return vertices;
-}
+	SkeletonTwoColorBatch::SkeletonTwoColorBatch() : _vertexBuffer(0), _indexBuffer(0) {
+		_commandsPool.reserve(INITIAL_SIZE);
+		for (unsigned int i = 0; i < INITIAL_SIZE; i++) {
+			_commandsPool.push_back(new TwoColorTrianglesCommand());
+		}
 
+		reset();
+
+		// callback after drawing is finished so we can clear out the batch state
+		// for the next frame
+		Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_AFTER_DRAW_RESET_POSITION, [this](EventCustom *eventCustom) {
+			this->update(0);
+		});
+	}
 
-void SkeletonTwoColorBatch::deallocateVertices(uint32_t numVertices) {
-	_numVertices -= numVertices;
-}
+	SkeletonTwoColorBatch::~SkeletonTwoColorBatch() {
+		Director::getInstance()->getEventDispatcher()->removeCustomEventListeners(EVENT_AFTER_DRAW_RESET_POSITION);
+
+		for (unsigned int i = 0; i < _commandsPool.size(); i++) {
+			delete _commandsPool[i];
+			_commandsPool[i] = nullptr;
+		}
+
+		delete[] _vertexBuffer;
+		delete[] _indexBuffer;
+	}
 
+	void SkeletonTwoColorBatch::update(float delta) {
+		reset();
+	}
 
-unsigned short* SkeletonTwoColorBatch::allocateIndices(uint32_t numIndices) {
-	if (_indices.getCapacity() - _indices.size() < numIndices) {
-		unsigned short* oldData = _indices.buffer();
-		int oldSize =_indices.size();
-		_indices.ensureCapacity(_indices.size() + numIndices);
-		unsigned short* newData = _indices.buffer();
-		for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
-			TwoColorTrianglesCommand* command = _commandsPool[i];
-			TwoColorTriangles& triangles = (TwoColorTriangles&)command->getTriangles();
-			if (triangles.indices >= oldData && triangles.indices < oldData + oldSize) {
-				triangles.indices = newData + (triangles.indices - oldData);
+	V3F_C4B_C4B_T2F *SkeletonTwoColorBatch::allocateVertices(uint32_t numVertices) {
+		if (_vertices.size() - _numVertices < numVertices) {
+			V3F_C4B_C4B_T2F *oldData = _vertices.data();
+			_vertices.resize((_vertices.size() + numVertices) * 2 + 1);
+			V3F_C4B_C4B_T2F *newData = _vertices.data();
+			for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
+				TwoColorTrianglesCommand *command = _commandsPool[i];
+				TwoColorTriangles &triangles = (TwoColorTriangles &) command->getTriangles();
+				triangles.verts = newData + (triangles.verts - oldData);
 			}
 		}
+
+		V3F_C4B_C4B_T2F *vertices = _vertices.data() + _numVertices;
+		_numVertices += numVertices;
+		return vertices;
 	}
 
-	unsigned short* indices = _indices.buffer() + _indices.size();
-	_indices.setSize(_indices.size() + numIndices, 0);
-	return indices;
-}
-
-void SkeletonTwoColorBatch::deallocateIndices(uint32_t numIndices) {
-	_indices.setSize(_indices.size() - numIndices, 0);
-}
-
-TwoColorTrianglesCommand* SkeletonTwoColorBatch::addCommand(cocos2d::Renderer* renderer, float globalOrder, cocos2d::Texture2D* texture, backend::ProgramState* programState, cocos2d::BlendFunc blendType, const TwoColorTriangles& triangles, const cocos2d::Mat4& mv, uint32_t flags) {
-	TwoColorTrianglesCommand* command = nextFreeCommand();
-	command->init(globalOrder, texture, programState, blendType, triangles, mv, flags);
-    command->updateVertexAndIndexBuffer(renderer, triangles.verts, triangles.vertCount, triangles.indices, triangles.indexCount);
-	renderer->addCommand(command);
-	return command;
-}
-
-void SkeletonTwoColorBatch::batch (cocos2d::Renderer *renderer, TwoColorTrianglesCommand* command) {
-	if (_numVerticesBuffer + command->getTriangles().vertCount >= MAX_VERTICES || _numIndicesBuffer + command->getTriangles().indexCount >= MAX_INDICES) {
-		flush(renderer, _lastCommand);
+
+	void SkeletonTwoColorBatch::deallocateVertices(uint32_t numVertices) {
+		_numVertices -= numVertices;
 	}
 
-	uint32_t materialID = command->getMaterialID();
-	if (_lastCommand && _lastCommand->getMaterialID() != materialID) {
-		flush(renderer, _lastCommand);
+
+	unsigned short *SkeletonTwoColorBatch::allocateIndices(uint32_t numIndices) {
+		if (_indices.getCapacity() - _indices.size() < numIndices) {
+			unsigned short *oldData = _indices.buffer();
+			int oldSize = _indices.size();
+			_indices.ensureCapacity(_indices.size() + numIndices);
+			unsigned short *newData = _indices.buffer();
+			for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
+				TwoColorTrianglesCommand *command = _commandsPool[i];
+				TwoColorTriangles &triangles = (TwoColorTriangles &) command->getTriangles();
+				if (triangles.indices >= oldData && triangles.indices < oldData + oldSize) {
+					triangles.indices = newData + (triangles.indices - oldData);
+				}
+			}
+		}
+
+		unsigned short *indices = _indices.buffer() + _indices.size();
+		_indices.setSize(_indices.size() + numIndices, 0);
+		return indices;
 	}
 
-	memcpy(_vertexBuffer + _numVerticesBuffer, command->getTriangles().verts, sizeof(V3F_C4B_C4B_T2F) * command->getTriangles().vertCount);
-	const Mat4& modelView = command->getModelView();
-	for (int i = _numVerticesBuffer; i < _numVerticesBuffer + command->getTriangles().vertCount; i++) {
-		modelView.transformPoint(&_vertexBuffer[i].position);
+	void SkeletonTwoColorBatch::deallocateIndices(uint32_t numIndices) {
+		_indices.setSize(_indices.size() - numIndices, 0);
 	}
 
-	unsigned short vertexOffset = (unsigned short)_numVerticesBuffer;
-	unsigned short* indices = command->getTriangles().indices;
-	for (int i = 0, j = _numIndicesBuffer; i < command->getTriangles().indexCount; i++, j++) {
-		_indexBuffer[j] = indices[i] + vertexOffset;
+	TwoColorTrianglesCommand *SkeletonTwoColorBatch::addCommand(cocos2d::Renderer *renderer, float globalOrder, cocos2d::Texture2D *texture, backend::ProgramState *programState, cocos2d::BlendFunc blendType, const TwoColorTriangles &triangles, const cocos2d::Mat4 &mv, uint32_t flags) {
+		TwoColorTrianglesCommand *command = nextFreeCommand();
+		command->init(globalOrder, texture, programState, blendType, triangles, mv, flags);
+		command->updateVertexAndIndexBuffer(renderer, triangles.verts, triangles.vertCount, triangles.indices, triangles.indexCount);
+		renderer->addCommand(command);
+		return command;
 	}
 
-	_numVerticesBuffer += command->getTriangles().vertCount;
-	_numIndicesBuffer += command->getTriangles().indexCount;
+	void SkeletonTwoColorBatch::batch(cocos2d::Renderer *renderer, TwoColorTrianglesCommand *command) {
+		if (_numVerticesBuffer + command->getTriangles().vertCount >= MAX_VERTICES || _numIndicesBuffer + command->getTriangles().indexCount >= MAX_INDICES) {
+			flush(renderer, _lastCommand);
+		}
 
-	if (command->isForceFlush()) {
-		flush(renderer, command);
+		uint32_t materialID = command->getMaterialID();
+		if (_lastCommand && _lastCommand->getMaterialID() != materialID) {
+			flush(renderer, _lastCommand);
+		}
+
+		memcpy(_vertexBuffer + _numVerticesBuffer, command->getTriangles().verts, sizeof(V3F_C4B_C4B_T2F) * command->getTriangles().vertCount);
+		const Mat4 &modelView = command->getModelView();
+		for (int i = _numVerticesBuffer; i < _numVerticesBuffer + command->getTriangles().vertCount; i++) {
+			modelView.transformPoint(&_vertexBuffer[i].position);
+		}
+
+		unsigned short vertexOffset = (unsigned short) _numVerticesBuffer;
+		unsigned short *indices = command->getTriangles().indices;
+		for (int i = 0, j = _numIndicesBuffer; i < command->getTriangles().indexCount; i++, j++) {
+			_indexBuffer[j] = indices[i] + vertexOffset;
+		}
+
+		_numVerticesBuffer += command->getTriangles().vertCount;
+		_numIndicesBuffer += command->getTriangles().indexCount;
+
+		if (command->isForceFlush()) {
+			flush(renderer, command);
+		}
+		_lastCommand = command;
 	}
-	_lastCommand = command;
-}
-
-void SkeletonTwoColorBatch::flush (cocos2d::Renderer *renderer, TwoColorTrianglesCommand* materialCommand) {
-	if (!materialCommand)
-		return;
-
-    materialCommand->updateVertexAndIndexBuffer(renderer, _vertexBuffer, _numVerticesBuffer, _indexBuffer, _numIndicesBuffer);
-
-    renderer->addCommand(materialCommand);
-
-    _numVerticesBuffer = 0;
-	_numIndicesBuffer = 0;
-	_numBatches++;
-}
-
-void SkeletonTwoColorBatch::reset() {
-	_nextFreeCommand = 0;
-	_numVertices = 0;
-	_indices.setSize(0, 0);
-	_numVerticesBuffer = 0;
-	_numIndicesBuffer = 0;
-	_lastCommand = nullptr;
-	_numBatches = 0;
-}
-
-TwoColorTrianglesCommand* SkeletonTwoColorBatch::nextFreeCommand() {
-	if (_commandsPool.size() <= _nextFreeCommand) {
-		unsigned int newSize = _commandsPool.size() * 2 + 1;
-		for (int i = _commandsPool.size();  i < newSize; i++) {
-			_commandsPool.push_back(new TwoColorTrianglesCommand());
+
+	void SkeletonTwoColorBatch::flush(cocos2d::Renderer *renderer, TwoColorTrianglesCommand *materialCommand) {
+		if (!materialCommand)
+			return;
+
+		materialCommand->updateVertexAndIndexBuffer(renderer, _vertexBuffer, _numVerticesBuffer, _indexBuffer, _numIndicesBuffer);
+
+		renderer->addCommand(materialCommand);
+
+		_numVerticesBuffer = 0;
+		_numIndicesBuffer = 0;
+		_numBatches++;
+	}
+
+	void SkeletonTwoColorBatch::reset() {
+		_nextFreeCommand = 0;
+		_numVertices = 0;
+		_indices.setSize(0, 0);
+		_numVerticesBuffer = 0;
+		_numIndicesBuffer = 0;
+		_lastCommand = nullptr;
+		_numBatches = 0;
+	}
+
+	TwoColorTrianglesCommand *SkeletonTwoColorBatch::nextFreeCommand() {
+		if (_commandsPool.size() <= _nextFreeCommand) {
+			unsigned int newSize = _commandsPool.size() * 2 + 1;
+			for (int i = _commandsPool.size(); i < newSize; i++) {
+				_commandsPool.push_back(new TwoColorTrianglesCommand());
+			}
 		}
+		TwoColorTrianglesCommand *command = _commandsPool[_nextFreeCommand++];
+		command->setForceFlush(false);
+		return command;
 	}
-	TwoColorTrianglesCommand* command = _commandsPool[_nextFreeCommand++];
-	command->setForceFlush(false);
-	return command;
-}
-}
+}// namespace spine
 
 #endif

+ 65 - 65
spine-cocos2dx/src/spine/v4/SkeletonTwoColorBatch.h

@@ -33,9 +33,9 @@
 #include "cocos2d.h"
 #if COCOS2D_VERSION >= 0x00040000
 
+#include "renderer/backend/ProgramState.h"
 #include <spine/spine.h>
 #include <vector>
-#include "renderer/backend/ProgramState.h"
 
 namespace spine {
 	struct V3F_C4B_C4B_T2F {
@@ -44,123 +44,123 @@ namespace spine {
 		cocos2d::Color4B color2;
 		cocos2d::Tex2F texCoords;
 	};
-	
+
 	struct TwoColorTriangles {
-		V3F_C4B_C4B_T2F* verts;
-		unsigned short* indices;
+		V3F_C4B_C4B_T2F *verts;
+		unsigned short *indices;
 		int vertCount;
 		int indexCount;
 	};
-	
+
 	class TwoColorTrianglesCommand : public cocos2d::CustomCommand {
 	public:
 		TwoColorTrianglesCommand();
-		
+
 		~TwoColorTrianglesCommand();
 
-        void init(float globalOrder, cocos2d::Texture2D* texture, cocos2d::backend::ProgramState* programState, cocos2d::BlendFunc blendType, const TwoColorTriangles& triangles, const cocos2d::Mat4& mv, uint32_t flags);
+		void init(float globalOrder, cocos2d::Texture2D *texture, cocos2d::backend::ProgramState *programState, cocos2d::BlendFunc blendType, const TwoColorTriangles &triangles, const cocos2d::Mat4 &mv, uint32_t flags);
+
+		void updateCommandPipelineDescriptor(cocos2d::backend::ProgramState *programState);
 
-        void updateCommandPipelineDescriptor(cocos2d::backend::ProgramState* programState);
+		inline cocos2d::backend::TextureBackend *getTexture() const { return _texture; }
 
-        inline cocos2d::backend::TextureBackend* getTexture() const { return _texture; }
+		void draw(cocos2d::Renderer *renderer);
 
-        void draw(cocos2d::Renderer *renderer);
+		void updateVertexAndIndexBuffer(cocos2d::Renderer *renderer, V3F_C4B_C4B_T2F *vertices, int verticesSize, uint16_t *indices, int indicesSize);
 
-        void updateVertexAndIndexBuffer(cocos2d::Renderer *renderer, V3F_C4B_C4B_T2F *vertices, int verticesSize, uint16_t *indices, int indicesSize);
-		
 		inline uint32_t getMaterialID() const { return _materialID; }
-		
-		inline const TwoColorTriangles& getTriangles() const { return _triangles; }
-		
+
+		inline const TwoColorTriangles &getTriangles() const { return _triangles; }
+
 		inline ssize_t getVertexCount() const { return _triangles.vertCount; }
-		
+
 		inline ssize_t getIndexCount() const { return _triangles.indexCount; }
-		
-		inline const V3F_C4B_C4B_T2F* getVertices() const { return _triangles.verts; }
-		
-		inline const unsigned short* getIndices() const { return _triangles.indices; }
-		
+
+		inline const V3F_C4B_C4B_T2F *getVertices() const { return _triangles.verts; }
+
+		inline const unsigned short *getIndices() const { return _triangles.indices; }
+
 		inline cocos2d::BlendFunc getBlendType() const { return _blendType; }
-		
-		inline const cocos2d::Mat4& getModelView() const { return _mv; }
-		
-		void setForceFlush (bool forceFlush) { _forceFlush = forceFlush; }
-		
-		bool isForceFlush () { return _forceFlush; };
-		
+
+		inline const cocos2d::Mat4 &getModelView() const { return _mv; }
+
+		void setForceFlush(bool forceFlush) { _forceFlush = forceFlush; }
+
+		bool isForceFlush() { return _forceFlush; };
+
 	protected:
 		void generateMaterialID();
 		uint32_t _materialID;
 
 
-        void *_prog = nullptr;
-        cocos2d::backend::TextureBackend    *_texture       = nullptr;
-        cocos2d::backend::ProgramState      *_programState  = nullptr;
-        cocos2d::backend::UniformLocation   _locPMatrix;
-        cocos2d::backend::UniformLocation   _locTexture;
+		void *_prog = nullptr;
+		cocos2d::backend::TextureBackend *_texture = nullptr;
+		cocos2d::backend::ProgramState *_programState = nullptr;
+		cocos2d::backend::UniformLocation _locPMatrix;
+		cocos2d::backend::UniformLocation _locTexture;
 
-		cocos2d::BlendFunc  _blendType;
-		TwoColorTriangles   _triangles;
-		cocos2d::Mat4       _mv;
-		bool                _forceFlush;
+		cocos2d::BlendFunc _blendType;
+		TwoColorTriangles _triangles;
+		cocos2d::Mat4 _mv;
+		bool _forceFlush;
 	};
 
-    class SkeletonTwoColorBatch {
-    public:
-        static SkeletonTwoColorBatch* getInstance ();
+	class SkeletonTwoColorBatch {
+	public:
+		static SkeletonTwoColorBatch *getInstance();
 
-        static void destroyInstance ();
+		static void destroyInstance();
 
-        void update (float delta);
+		void update(float delta);
 
-		V3F_C4B_C4B_T2F* allocateVertices(uint32_t numVertices);
+		V3F_C4B_C4B_T2F *allocateVertices(uint32_t numVertices);
 		void deallocateVertices(uint32_t numVertices);
-		
-		unsigned short* allocateIndices(uint32_t numIndices);
+
+		unsigned short *allocateIndices(uint32_t numIndices);
 		void deallocateIndices(uint32_t numIndices);
 
-        TwoColorTrianglesCommand* addCommand(cocos2d::Renderer* renderer, float globalOrder, cocos2d::Texture2D* texture, cocos2d::backend::ProgramState* programState, cocos2d::BlendFunc blendType, const TwoColorTriangles& triangles, const cocos2d::Mat4& mv, uint32_t flags);
+		TwoColorTrianglesCommand *addCommand(cocos2d::Renderer *renderer, float globalOrder, cocos2d::Texture2D *texture, cocos2d::backend::ProgramState *programState, cocos2d::BlendFunc blendType, const TwoColorTriangles &triangles, const cocos2d::Mat4 &mv, uint32_t flags);
 
-        void batch(cocos2d::Renderer* renderer, TwoColorTrianglesCommand* command);
+		void batch(cocos2d::Renderer *renderer, TwoColorTrianglesCommand *command);
 
-        void flush(cocos2d::Renderer* renderer, TwoColorTrianglesCommand* materialCommand);
+		void flush(cocos2d::Renderer *renderer, TwoColorTrianglesCommand *materialCommand);
 
-		uint32_t getNumBatches () { return _numBatches; };
-		
-    protected:
-        SkeletonTwoColorBatch ();
-        virtual ~SkeletonTwoColorBatch ();
+		uint32_t getNumBatches() { return _numBatches; };
 
-		void reset ();
+	protected:
+		SkeletonTwoColorBatch();
+		virtual ~SkeletonTwoColorBatch();
+
+		void reset();
 
-		TwoColorTrianglesCommand* nextFreeCommand ();
+		TwoColorTrianglesCommand *nextFreeCommand();
 
 		// pool of commands
-		std::vector<TwoColorTrianglesCommand*> _commandsPool;
+		std::vector<TwoColorTrianglesCommand *> _commandsPool;
 		uint32_t _nextFreeCommand;
 
 		// pool of vertices
 		std::vector<V3F_C4B_C4B_T2F> _vertices;
 		uint32_t _numVertices;
-		
+
 		// pool of indices
 		Vector<unsigned short> _indices;
-		
-		
+
+
 		// VBO handles & attribute locations
-		V3F_C4B_C4B_T2F* _vertexBuffer;
+		V3F_C4B_C4B_T2F *_vertexBuffer;
 		uint32_t _numVerticesBuffer;
-        uint32_t _numIndicesBuffer;
-        unsigned short* _indexBuffer;
+		uint32_t _numIndicesBuffer;
+		unsigned short *_indexBuffer;
 
 		// last batched command, needed for flushing to set material
-		TwoColorTrianglesCommand* _lastCommand = nullptr;
+		TwoColorTrianglesCommand *_lastCommand = nullptr;
 
 		// number of batches in the last frame
 		uint32_t _numBatches;
 	};
-}
+}// namespace spine
 
 #endif
 
-#endif // SPINE_SKELETONTWOCOLORBATCH_H_
+#endif// SPINE_SKELETONTWOCOLORBATCH_H_

+ 15 - 14
spine-cpp/spine-cpp-unit-tests/src/main.cpp

@@ -1,47 +1,47 @@
-#include <stdio.h>
-#include <spine/spine.h>
 #include <spine/Debug.h>
+#include <spine/spine.h>
+#include <stdio.h>
 
-#pragma warning ( disable : 4710 )
+#pragma warning(disable : 4710)
 
 using namespace spine;
 
 void loadBinary(const String &binaryFile, const String &atlasFile, Atlas *&atlas, SkeletonData *&skeletonData,
 				AnimationStateData *&stateData, Skeleton *&skeleton, AnimationState *&state) {
-	atlas = new(__FILE__, __LINE__) Atlas(atlasFile, NULL);
+	atlas = new (__FILE__, __LINE__) Atlas(atlasFile, NULL);
 	assert(atlas != NULL);
 
 	SkeletonBinary binary(atlas);
 	skeletonData = binary.readSkeletonDataFile(binaryFile);
 	assert(skeletonData);
 
-	skeleton = new(__FILE__, __LINE__) Skeleton(skeletonData);
+	skeleton = new (__FILE__, __LINE__) Skeleton(skeletonData);
 	assert(skeleton != NULL);
 
-	stateData = new(__FILE__, __LINE__) AnimationStateData(skeletonData);
+	stateData = new (__FILE__, __LINE__) AnimationStateData(skeletonData);
 	assert(stateData != NULL);
 	stateData->setDefaultMix(0.4f);
 
-	state = new(__FILE__, __LINE__) AnimationState(stateData);
+	state = new (__FILE__, __LINE__) AnimationState(stateData);
 }
 
 void loadJson(const String &jsonFile, const String &atlasFile, Atlas *&atlas, SkeletonData *&skeletonData,
 			  AnimationStateData *&stateData, Skeleton *&skeleton, AnimationState *&state) {
-	atlas = new(__FILE__, __LINE__) Atlas(atlasFile, NULL);
+	atlas = new (__FILE__, __LINE__) Atlas(atlasFile, NULL);
 	assert(atlas != NULL);
 
 	SkeletonJson json(atlas);
 	skeletonData = json.readSkeletonDataFile(jsonFile);
 	assert(skeletonData);
 
-	skeleton = new(__FILE__, __LINE__) Skeleton(skeletonData);
+	skeleton = new (__FILE__, __LINE__) Skeleton(skeletonData);
 	assert(skeleton != NULL);
 
-	stateData = new(__FILE__, __LINE__) AnimationStateData(skeletonData);
+	stateData = new (__FILE__, __LINE__) AnimationStateData(skeletonData);
 	assert(stateData != NULL);
 	stateData->setDefaultMix(0.4f);
 
-	state = new(__FILE__, __LINE__) AnimationState(stateData);
+	state = new (__FILE__, __LINE__) AnimationState(stateData);
 }
 
 void dispose(Atlas *atlas, SkeletonData *skeletonData, AnimationStateData *stateData, Skeleton *skeleton,
@@ -55,7 +55,8 @@ void dispose(Atlas *atlas, SkeletonData *skeletonData, AnimationStateData *state
 
 struct TestData {
 	TestData(const String &jsonSkeleton, const String &binarySkeleton, const String &atlas) : _jsonSkeleton(
-			jsonSkeleton), _binarySkeleton(binarySkeleton), _atlas(atlas) {}
+																									  jsonSkeleton),
+																							  _binarySkeleton(binarySkeleton), _atlas(atlas) {}
 
 	String _jsonSkeleton;
 	String _binarySkeleton;
@@ -65,7 +66,7 @@ struct TestData {
 void testLoading() {
 	Vector<TestData> testData;
 	testData.add(TestData("testdata/coin/coin-pro.json", "testdata/coin/coin-pro.skel", "testdata/coin/coin.atlas"));
-/*testData.add(TestData("testdata/goblins/goblins-pro.json", "testdata/goblins/goblins-pro.skel",
+	/*testData.add(TestData("testdata/goblins/goblins-pro.json", "testdata/goblins/goblins-pro.skel",
 						  "testdata/goblins/goblins.atlas"));
 	testData.add(TestData("testdata/raptor/raptor-pro.json", "testdata/raptor/raptor-pro.skel",
 						  "testdata/raptor/raptor.atlas"));
@@ -97,7 +98,7 @@ namespace spine {
 	SpineExtension *getDefaultExtension() {
 		return new DefaultSpineExtension();
 	}
-}
+}// namespace spine
 
 int main(int argc, char **argv) {
 	DebugExtension debug(SpineExtension::getInstance());

+ 7 - 9
spine-cpp/spine-cpp/src/spine/Animation.cpp

@@ -32,9 +32,9 @@
 #endif
 
 #include <spine/Animation.h>
-#include <spine/Timeline.h>
-#include <spine/Skeleton.h>
 #include <spine/Event.h>
+#include <spine/Skeleton.h>
+#include <spine/Timeline.h>
 
 #include <spine/ContainerUtil.h>
 
@@ -42,11 +42,10 @@
 
 using namespace spine;
 
-Animation::Animation(const String &name, Vector<Timeline *> &timelines, float duration) :
-		_timelines(timelines),
-		_timelineIds(),
-		_duration(duration),
-		_name(name) {
+Animation::Animation(const String &name, Vector<Timeline *> &timelines, float duration) : _timelines(timelines),
+																						  _timelineIds(),
+																						  _duration(duration),
+																						  _name(name) {
 	assert(_name.length() > 0);
 	for (size_t i = 0; i < timelines.size(); i++) {
 		Vector<PropertyId> propertyIds = timelines[i]->getPropertyIds();
@@ -67,8 +66,7 @@ Animation::~Animation() {
 }
 
 void Animation::apply(Skeleton &skeleton, float lastTime, float time, bool loop, Vector<Event *> *pEvents, float alpha,
-					  MixBlend blend, MixDirection direction
-) {
+					  MixBlend blend, MixDirection direction) {
 	if (loop && _duration != 0) {
 		time = MathUtil::fmod(time, _duration);
 		if (lastTime > 0) {

+ 54 - 50
spine-cpp/spine-cpp/src/spine/AnimationState.cpp

@@ -31,18 +31,18 @@
 #include "SpinePluginPrivatePCH.h"
 #endif
 
-#include <spine/AnimationState.h>
 #include <spine/Animation.h>
-#include <spine/Event.h>
+#include <spine/AnimationState.h>
 #include <spine/AnimationStateData.h>
-#include <spine/Skeleton.h>
-#include <spine/RotateTimeline.h>
-#include <spine/SkeletonData.h>
+#include <spine/AttachmentTimeline.h>
 #include <spine/Bone.h>
 #include <spine/BoneData.h>
-#include <spine/AttachmentTimeline.h>
 #include <spine/DrawOrderTimeline.h>
+#include <spine/Event.h>
 #include <spine/EventTimeline.h>
+#include <spine/RotateTimeline.h>
+#include <spine/Skeleton.h>
+#include <spine/SkeletonData.h>
 #include <spine/Slot.h>
 #include <spine/SlotData.h>
 
@@ -199,20 +199,19 @@ void TrackEntry::reset() {
 float TrackEntry::getTrackComplete() {
 	float duration = _animationEnd - _animationStart;
 	if (duration != 0) {
-		if (_loop) return duration * (1 + (int) (_trackTime / duration)); // Completion of next loop.
-		if (_trackTime < duration) return duration; // Before duration.
+		if (_loop) return duration * (1 + (int) (_trackTime / duration));// Completion of next loop.
+		if (_trackTime < duration) return duration;                      // Before duration.
 	}
-	return _trackTime; // Next update.
+	return _trackTime;// Next update.
 }
 
-EventQueueEntry::EventQueueEntry(EventType eventType, TrackEntry *trackEntry, Event *event) :
-		_type(eventType),
-		_entry(trackEntry),
-		_event(event) {
+EventQueueEntry::EventQueueEntry(EventType eventType, TrackEntry *trackEntry, Event *event) : _type(eventType),
+																							  _entry(trackEntry),
+																							  _event(event) {
 }
 
 EventQueue *EventQueue::newEventQueue(AnimationState &state, Pool<TrackEntry> &trackEntryPool) {
-	return new(__FILE__, __LINE__) EventQueue(state, trackEntryPool);
+	return new (__FILE__, __LINE__) EventQueue(state, trackEntryPool);
 }
 
 EventQueueEntry EventQueue::newEventQueueEntry(EventType eventType, TrackEntry *entry, Event *event) {
@@ -273,21 +272,27 @@ void EventQueue::drain() {
 			case EventType_Interrupt:
 			case EventType_Complete:
 				if (!trackEntry->_listenerObject) trackEntry->_listener(&state, queueEntry._type, trackEntry, NULL);
-				else trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, NULL);
+				else
+					trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, NULL);
 				if (!state._listenerObject) state._listener(&state, queueEntry._type, trackEntry, NULL);
-				else state._listenerObject->callback(&state, queueEntry._type, trackEntry, NULL);
+				else
+					state._listenerObject->callback(&state, queueEntry._type, trackEntry, NULL);
 				break;
 			case EventType_End:
 				if (!trackEntry->_listenerObject) trackEntry->_listener(&state, queueEntry._type, trackEntry, NULL);
-				else trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, NULL);
+				else
+					trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, NULL);
 				if (!state._listenerObject) state._listener(&state, queueEntry._type, trackEntry, NULL);
-				else state._listenerObject->callback(&state, queueEntry._type, trackEntry, NULL);
+				else
+					state._listenerObject->callback(&state, queueEntry._type, trackEntry, NULL);
 				/* Fall through. */
 			case EventType_Dispose:
 				if (!trackEntry->_listenerObject) trackEntry->_listener(&state, EventType_Dispose, trackEntry, NULL);
-				else trackEntry->_listenerObject->callback(&state, EventType_Dispose, trackEntry, NULL);
+				else
+					trackEntry->_listenerObject->callback(&state, EventType_Dispose, trackEntry, NULL);
 				if (!state._listenerObject) state._listener(&state, EventType_Dispose, trackEntry, NULL);
-				else state._listenerObject->callback(&state, EventType_Dispose, trackEntry, NULL);
+				else
+					state._listenerObject->callback(&state, EventType_Dispose, trackEntry, NULL);
 
 				trackEntry->reset();
 				_trackEntryPool.free(trackEntry);
@@ -295,9 +300,11 @@ void EventQueue::drain() {
 			case EventType_Event:
 				if (!trackEntry->_listenerObject)
 					trackEntry->_listener(&state, queueEntry._type, trackEntry, queueEntry._event);
-				else trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, queueEntry._event);
+				else
+					trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, queueEntry._event);
 				if (!state._listenerObject) state._listener(&state, queueEntry._type, trackEntry, queueEntry._event);
-				else state._listenerObject->callback(&state, queueEntry._type, trackEntry, queueEntry._event);
+				else
+					state._listenerObject->callback(&state, queueEntry._type, trackEntry, queueEntry._event);
 				break;
 		}
 	}
@@ -306,14 +313,13 @@ void EventQueue::drain() {
 	_drainDisabled = false;
 }
 
-AnimationState::AnimationState(AnimationStateData *data) :
-		_data(data),
-		_queue(EventQueue::newEventQueue(*this, _trackEntryPool)),
-		_animationsChanged(false),
-		_listener(dummyOnAnimationEventFunc),
-		_listenerObject(NULL),
-		_unkeyedState(0),
-		_timeScale(1) {
+AnimationState::AnimationState(AnimationStateData *data) : _data(data),
+														   _queue(EventQueue::newEventQueue(*this, _trackEntryPool)),
+														   _animationsChanged(false),
+														   _listener(dummyOnAnimationEventFunc),
+														   _listenerObject(NULL),
+														   _unkeyedState(0),
+														   _timeScale(1) {
 }
 
 AnimationState::~AnimationState() {
@@ -426,7 +432,7 @@ bool AnimationState::apply(Skeleton &skeleton) {
 		if (current._mixingFrom != NULL) {
 			mix *= applyMixingFrom(currentP, skeleton, blend);
 		} else if (current._trackTime >= current._trackEnd && current._next == NULL) {
-			mix = 0; // Set to setup pose the last time the entry will be applied.
+			mix = 0;// Set to setup pose the last time the entry will be applied.
 		}
 
 		// apply current entry.
@@ -485,8 +491,7 @@ bool AnimationState::apply(Skeleton &skeleton) {
 		Slot *slot = slots[i];
 		if (slot->getAttachmentState() == setupState) {
 			const String &attachmentName = slot->getData().getAttachmentName();
-			slot->setAttachment(attachmentName.isEmpty() ? NULL : skeleton.getAttachment(slot->getData().getIndex(),
-																						 attachmentName));
+			slot->setAttachment(attachmentName.isEmpty() ? NULL : skeleton.getAttachment(slot->getData().getIndex(), attachmentName));
 		}
 	}
 	_unkeyedState += 2;
@@ -513,7 +518,7 @@ void AnimationState::clearTrack(size_t trackIndex) {
 
 	_queue->end(current);
 
-    clearNext(current);
+	clearNext(current);
 
 	TrackEntry *entry = current;
 	while (true) {
@@ -548,11 +553,11 @@ TrackEntry *AnimationState::setAnimation(size_t trackIndex, Animation *animation
 			_tracks[trackIndex] = current->_mixingFrom;
 			_queue->interrupt(current);
 			_queue->end(current);
-            clearNext(current);
+			clearNext(current);
 			current = current->_mixingFrom;
 			interrupt = false;
 		} else {
-            clearNext(current);
+			clearNext(current);
 		}
 	}
 
@@ -686,8 +691,7 @@ void AnimationState::applyAttachmentTimeline(AttachmentTimeline *attachmentTimel
 
 
 void AnimationState::applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleton &skeleton, float time, float alpha,
-										 MixBlend blend, Vector<float> &timelinesRotation, size_t i, bool firstFrame
-) {
+										 MixBlend blend, Vector<float> &timelinesRotation, size_t i, bool firstFrame) {
 	if (firstFrame) timelinesRotation[i] = 0;
 
 	if (alpha == 1) {
@@ -725,8 +729,8 @@ void AnimationState::applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleto
 			lastTotal = 0;
 			lastDiff = diff;
 		} else {
-			lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops.
-			lastDiff = timelinesRotation[i + 1]; // Difference between bones.
+			lastTotal = timelinesRotation[i];   // Angle and direction of mix, including loops.
+			lastDiff = timelinesRotation[i + 1];// Difference between bones.
 		}
 
 		bool current = diff > 0, dir = lastTotal >= 0;
@@ -737,7 +741,7 @@ void AnimationState::applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleto
 			dir = current;
 		}
 
-		total = diff + lastTotal - MathUtil::fmod(lastTotal, 360); // Store loops as part of lastTotal.
+		total = diff + lastTotal - MathUtil::fmod(lastTotal, 360);// Store loops as part of lastTotal.
 		if (dir != current) {
 			total += 360 * MathUtil::sign(lastTotal);
 		}
@@ -891,7 +895,7 @@ void AnimationState::queueEvents(TrackEntry *entry, float animationTime) {
 	for (; i < n; ++i) {
 		Event *e = _events[i];
 		if (e->_time < trackLastWrapped) break;
-		if (e->_time > animationEnd) continue; // Discard events outside animation start/end.
+		if (e->_time > animationEnd) continue;// Discard events outside animation start/end.
 		_queue->event(entry, e);
 	}
 
@@ -906,7 +910,7 @@ void AnimationState::queueEvents(TrackEntry *entry, float animationTime) {
 	// Queue events after complete.
 	for (; i < n; ++i) {
 		Event *e = _events[i];
-		if (e->_time < animationStart) continue; // Discard events outside animation start/end.
+		if (e->_time < animationStart) continue;// Discard events outside animation start/end.
 		_queue->event(entry, e);
 	}
 }
@@ -928,10 +932,10 @@ void AnimationState::setCurrent(size_t index, TrackEntry *current, bool interrup
 			current->_interruptAlpha *= MathUtil::min(1.0f, from->_mixTime / from->_mixDuration);
 		}
 
-		from->_timelinesRotation.clear(); // Reset rotation for mixing out, in case entry was mixed in.
+		from->_timelinesRotation.clear();// Reset rotation for mixing out, in case entry was mixed in.
 	}
 
-	_queue->start(current); // triggers animationsChanged
+	_queue->start(current);// triggers animationsChanged
 }
 
 TrackEntry *AnimationState::expandToIndex(size_t index) {
@@ -942,7 +946,7 @@ TrackEntry *AnimationState::expandToIndex(size_t index) {
 }
 
 TrackEntry *AnimationState::newTrackEntry(size_t trackIndex, Animation *animation, bool loop, TrackEntry *last) {
-	TrackEntry *entryP = _trackEntryPool.obtain(); // Pooling
+	TrackEntry *entryP = _trackEntryPool.obtain();// Pooling
 	TrackEntry &entry = *entryP;
 
 	entry._trackIndex = trackIndex;
@@ -962,8 +966,8 @@ TrackEntry *AnimationState::newTrackEntry(size_t trackIndex, Animation *animatio
 	entry._delay = 0;
 	entry._trackTime = 0;
 	entry._trackLast = -1;
-	entry._nextTrackLast = -1; // nextTrackLast == -1 signifies a TrackEntry that wasn't applied yet.
-	entry._trackEnd = FLT_MAX; // loop ? float.MaxValue : animation.Duration;
+	entry._nextTrackLast = -1;// nextTrackLast == -1 signifies a TrackEntry that wasn't applied yet.
+	entry._trackEnd = FLT_MAX;// loop ? float.MaxValue : animation.Duration;
 	entry._timeScale = 1;
 
 	entry._alpha = 1;
@@ -1021,7 +1025,7 @@ void AnimationState::computeHold(TrackEntry *entry) {
 
 	// outer:
 	size_t i = 0;
-	continue_outer:
+continue_outer:
 	for (; i < timelinesCount; ++i) {
 		Timeline *timeline = timelines[i];
 		Vector<PropertyId> &ids = timeline->getPropertyIds();
@@ -1039,7 +1043,7 @@ void AnimationState::computeHold(TrackEntry *entry) {
 						timelineMode[i] = HoldMix;
 						timelineHoldMix[i] = next;
 						i++;
-						goto continue_outer; // continue outer;
+						goto continue_outer;// continue outer;
 					}
 					break;
 				}

+ 1 - 1
spine-cpp/spine-cpp/src/spine/AnimationStateData.cpp

@@ -31,9 +31,9 @@
 #include "SpinePluginPrivatePCH.h"
 #endif
 
+#include <spine/Animation.h>
 #include <spine/AnimationStateData.h>
 #include <spine/SkeletonData.h>
-#include <spine/Animation.h>
 
 using namespace spine;
 

+ 5 - 6
spine-cpp/spine-cpp/src/spine/Atlas.cpp

@@ -32,8 +32,8 @@
 #endif
 
 #include <spine/Atlas.h>
-#include <spine/TextureLoader.h>
 #include <spine/ContainerUtil.h>
+#include <spine/TextureLoader.h>
 
 #include <ctype.h>
 
@@ -65,8 +65,8 @@ Atlas::Atlas(const String &path, TextureLoader *textureLoader, bool createTextur
 }
 
 Atlas::Atlas(const char *data, int length, const char *dir, TextureLoader *textureLoader, bool createTexture)
-		: _textureLoader(
-		textureLoader) {
+	: _textureLoader(
+			  textureLoader) {
 	load(data, length, dir, createTexture);
 }
 
@@ -258,7 +258,7 @@ void Atlas::load(const char *begin, int length, const char *dir, bool createText
 			memcpy(path, dir, dirLength);
 			if (needsSlash) path[dirLength] = '/';
 			strcpy(path + dirLength + needsSlash, name);
-			page = new(__FILE__, __LINE__) AtlasPage(String(name, true));
+			page = new (__FILE__, __LINE__) AtlasPage(String(name, true));
 
 			while (true) {
 				line = reader.readLine();
@@ -289,7 +289,7 @@ void Atlas::load(const char *begin, int length, const char *dir, bool createText
 			}
 			_pages.add(page);
 		} else {
-			AtlasRegion *region = new(__FILE__, __LINE__) AtlasRegion();
+			AtlasRegion *region = new (__FILE__, __LINE__) AtlasRegion();
 			region->page = page;
 			region->name = String(line->copy(), true);
 			while (true) {
@@ -351,4 +351,3 @@ void Atlas::load(const char *begin, int length, const char *dir, bool createText
 		}
 	}
 }
-

Some files were not shown because too many files changed in this diff