Browse Source

Merge pull request #6 from mtannel/Spine_Runtime_Update

Updated Spine runtime version to 3.8.
Peter Robinson 5 years ago
parent
commit
e1de61f39e
100 changed files with 11763 additions and 3932 deletions
  1. 1 1
      .gitignore
  2. BIN
      engine/compilers/VisualStudio 2017/.vs/Torque 2D/v15/Solution.VC.db-shm
  3. 48 11
      engine/compilers/VisualStudio 2017/Torque 2D.vcxproj
  4. 140 29
      engine/compilers/VisualStudio 2017/Torque 2D.vcxproj.filters
  5. 1 1
      engine/compilers/VisualStudio 2017/libogg.vcxproj
  6. 1 1
      engine/compilers/VisualStudio 2017/libvorbis.vcxproj
  7. 1 1
      engine/compilers/VisualStudio 2017/ljpeg.vcxproj
  8. 1 1
      engine/compilers/VisualStudio 2017/lpng.vcxproj
  9. 1 1
      engine/compilers/VisualStudio 2017/zlib.vcxproj
  10. 0 386
      engine/source/2d/assets/SkeletonAsset.cc
  11. 0 101
      engine/source/2d/assets/SkeletonAsset.h
  12. 434 0
      engine/source/2d/assets/SpineAsset.cc
  13. 107 0
      engine/source/2d/assets/SpineAsset.h
  14. 16 16
      engine/source/2d/assets/SpineAsset_ScriptBinding.h
  15. 18 38
      engine/source/2d/core/BatchRender.cc
  16. 9 7
      engine/source/2d/core/BatchRender.h
  17. 10 0
      engine/source/2d/core/CoreMath.h
  18. 66 6
      engine/source/2d/core/ImageFrameProviderCore.cc
  19. 24 2
      engine/source/2d/core/ImageFrameProviderCore.h
  20. 32 24
      engine/source/2d/core/SpriteBatch.cc
  21. 3 1
      engine/source/2d/core/SpriteBatch.h
  22. 64 23
      engine/source/2d/core/SpriteBatchItem.cc
  23. 31 4
      engine/source/2d/core/SpriteBatchItem.h
  24. 1 1
      engine/source/2d/scene/DebugDraw.h
  25. 1 1
      engine/source/2d/scene/SceneRenderRequest.h
  26. 1 1
      engine/source/2d/sceneobject/SceneObject.cc
  27. 0 472
      engine/source/2d/sceneobject/SkeletonObject.cc
  28. 0 161
      engine/source/2d/sceneobject/SkeletonObject.h
  29. 0 311
      engine/source/2d/sceneobject/SkeletonObject_ScriptBinding.h
  30. 28 0
      engine/source/2d/sceneobject/SpineCollisionProxy.cc
  31. 44 0
      engine/source/2d/sceneobject/SpineCollisionProxy.h
  32. 1239 0
      engine/source/2d/sceneobject/SpineObject.cc
  33. 293 0
      engine/source/2d/sceneobject/SpineObject.h
  34. 688 0
      engine/source/2d/sceneobject/SpineObject_ScriptBinding.h
  35. 1 1
      engine/source/bitmapFont/BitmapFontCharacterInfo.h
  36. 1 1
      engine/source/graphics/dgl.cc
  37. 1 1
      engine/source/graphics/gBitmap.h
  38. 2 2
      engine/source/graphics/gColor.cc
  39. 664 664
      engine/source/graphics/gColor.h
  40. 116 116
      engine/source/graphics/gColor_ScriptBinding.h
  41. 1 1
      engine/source/graphics/gPalette.h
  42. 1 1
      engine/source/graphics/splineUtil.h
  43. 1 1
      engine/source/gui/guiControl.h
  44. 1 1
      engine/source/gui/guiDefaultControlRender.cc
  45. 1 1
      engine/source/gui/guiTextCtrl.cc
  46. 1 1
      engine/source/gui/guiTypes.h
  47. 1 1
      engine/source/io/nStream.cc
  48. 1 1
      engine/source/persistence/taml/tamlCustom.h
  49. 652 262
      engine/source/spine/Animation.c
  50. 412 73
      engine/source/spine/Animation.h
  51. 948 173
      engine/source/spine/AnimationState.c
  52. 122 46
      engine/source/spine/AnimationState.h
  53. 23 22
      engine/source/spine/AnimationStateData.c
  54. 38 28
      engine/source/spine/AnimationStateData.h
  55. 38 0
      engine/source/spine/Array.c
  56. 132 0
      engine/source/spine/Array.h
  57. 116 93
      engine/source/spine/Atlas.c
  58. 81 43
      engine/source/spine/Atlas.h
  59. 63 30
      engine/source/spine/AtlasAttachmentLoader.c
  60. 26 24
      engine/source/spine/AtlasAttachmentLoader.h
  61. 36 26
      engine/source/spine/Attachment.c
  62. 53 29
      engine/source/spine/Attachment.h
  63. 49 29
      engine/source/spine/AttachmentLoader.c
  64. 41 32
      engine/source/spine/AttachmentLoader.h
  65. 252 62
      engine/source/spine/Bone.c
  66. 87 35
      engine/source/spine/Bone.h
  67. 26 25
      engine/source/spine/BoneData.c
  68. 54 28
      engine/source/spine/BoneData.h
  69. 33 40
      engine/source/spine/BoundingBoxAttachment.c
  70. 29 31
      engine/source/spine/BoundingBoxAttachment.h
  71. 55 0
      engine/source/spine/ClippingAttachment.c
  72. 60 0
      engine/source/spine/ClippingAttachment.h
  73. 84 0
      engine/source/spine/Color.c
  74. 77 0
      engine/source/spine/Color.h
  75. 25 23
      engine/source/spine/Event.c
  76. 43 27
      engine/source/spine/Event.h
  77. 24 22
      engine/source/spine/EventData.c
  78. 44 27
      engine/source/spine/EventData.h
  79. 276 0
      engine/source/spine/IkConstraint.c
  80. 94 0
      engine/source/spine/IkConstraint.h
  81. 48 0
      engine/source/spine/IkConstraintData.c
  82. 86 0
      engine/source/spine/IkConstraintData.h
  83. 187 84
      engine/source/spine/Json.c
  84. 10 3
      engine/source/spine/Json.h
  85. 211 0
      engine/source/spine/MeshAttachment.c
  86. 91 0
      engine/source/spine/MeshAttachment.h
  87. 59 0
      engine/source/spine/PathAttachment.c
  88. 62 0
      engine/source/spine/PathAttachment.h
  89. 492 0
      engine/source/spine/PathConstraint.c
  90. 115 0
      engine/source/spine/PathConstraint.h
  91. 43 0
      engine/source/spine/PathConstraintData.c
  92. 99 0
      engine/source/spine/PathConstraintData.h
  93. 67 0
      engine/source/spine/PointAttachment.c
  94. 64 0
      engine/source/spine/PointAttachment.h
  95. 107 69
      engine/source/spine/RegionAttachment.c
  96. 32 34
      engine/source/spine/RegionAttachment.h
  97. 481 70
      engine/source/spine/Skeleton.c
  98. 102 48
      engine/source/spine/Skeleton.h
  99. 1147 0
      engine/source/spine/SkeletonBinary.c
  100. 71 0
      engine/source/spine/SkeletonBinary.h

+ 1 - 1
.gitignore

@@ -27,7 +27,7 @@ Torque2D_DEBUG.exe
 Torque2DGame.app
 Torque2DGame.app
 Torque2DGame_Debug.app
 Torque2DGame_Debug.app
 linkmap.txt
 linkmap.txt
-.vs/
+**/.vs/**
 
 
 # Compiled source #
 # Compiled source #
 ###################
 ###################

BIN
engine/compilers/VisualStudio 2017/.vs/Torque 2D/v15/Solution.VC.db-shm


+ 48 - 11
engine/compilers/VisualStudio 2017/Torque 2D.vcxproj

@@ -18,7 +18,7 @@
     <ProjectGuid>{1564A07D-230E-4C90-AEE6-52AC9A58D6C9}</ProjectGuid>
     <ProjectGuid>{1564A07D-230E-4C90-AEE6-52AC9A58D6C9}</ProjectGuid>
     <RootNamespace>TorqueGame</RootNamespace>
     <RootNamespace>TorqueGame</RootNamespace>
     <ProjectName>Torque2D</ProjectName>
     <ProjectName>Torque2D</ProjectName>
-    <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
   </PropertyGroup>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
@@ -247,7 +247,7 @@
     <ClCompile Include="..\..\source\2d\assets\ParticleAssetEmitter.cc" />
     <ClCompile Include="..\..\source\2d\assets\ParticleAssetEmitter.cc" />
     <ClCompile Include="..\..\source\2d\assets\ParticleAssetField.cc" />
     <ClCompile Include="..\..\source\2d\assets\ParticleAssetField.cc" />
     <ClCompile Include="..\..\source\2d\assets\ParticleAssetFieldCollection.cc" />
     <ClCompile Include="..\..\source\2d\assets\ParticleAssetFieldCollection.cc" />
-    <ClCompile Include="..\..\source\2d\assets\SkeletonAsset.cc" />
+    <ClCompile Include="..\..\source\2d\assets\SpineAsset.cc" />
     <ClCompile Include="..\..\source\2d\controllers\AmbientForceController.cc" />
     <ClCompile Include="..\..\source\2d\controllers\AmbientForceController.cc" />
     <ClCompile Include="..\..\source\2d\controllers\core\GroupedSceneController.cc" />
     <ClCompile Include="..\..\source\2d\controllers\core\GroupedSceneController.cc" />
     <ClCompile Include="..\..\source\2d\controllers\core\PickingSceneController.cc" />
     <ClCompile Include="..\..\source\2d\controllers\core\PickingSceneController.cc" />
@@ -279,7 +279,8 @@
     <ClCompile Include="..\..\source\2d\sceneobject\Scroller.cc" />
     <ClCompile Include="..\..\source\2d\sceneobject\Scroller.cc" />
     <ClCompile Include="..\..\source\2d\sceneobject\LightObject.cc" />
     <ClCompile Include="..\..\source\2d\sceneobject\LightObject.cc" />
     <ClCompile Include="..\..\source\2d\sceneobject\ShapeVector.cc" />
     <ClCompile Include="..\..\source\2d\sceneobject\ShapeVector.cc" />
-    <ClCompile Include="..\..\source\2d\sceneobject\SkeletonObject.cc" />
+    <ClCompile Include="..\..\source\2d\sceneobject\SpineCollisionProxy.cc" />
+    <ClCompile Include="..\..\source\2d\sceneobject\SpineObject.cc" />
     <ClCompile Include="..\..\source\2d\sceneobject\Sprite.cc" />
     <ClCompile Include="..\..\source\2d\sceneobject\Sprite.cc" />
     <ClCompile Include="..\..\source\2d\sceneobject\TextSprite.cc" />
     <ClCompile Include="..\..\source\2d\sceneobject\TextSprite.cc" />
     <ClCompile Include="..\..\source\2d\sceneobject\Trigger.cc" />
     <ClCompile Include="..\..\source\2d\sceneobject\Trigger.cc" />
@@ -381,7 +382,7 @@
     <ClCompile Include="..\..\source\graphics\bitmapBmp.cc" />
     <ClCompile Include="..\..\source\graphics\bitmapBmp.cc" />
     <ClCompile Include="..\..\source\graphics\bitmapJpeg.cc" />
     <ClCompile Include="..\..\source\graphics\bitmapJpeg.cc" />
     <ClCompile Include="..\..\source\graphics\bitmapPng.cc" />
     <ClCompile Include="..\..\source\graphics\bitmapPng.cc" />
-    <ClCompile Include="..\..\source\graphics\color.cc" />
+    <ClCompile Include="..\..\source\graphics\gColor.cc" />
     <ClCompile Include="..\..\source\graphics\dgl.cc" />
     <ClCompile Include="..\..\source\graphics\dgl.cc" />
     <ClCompile Include="..\..\source\graphics\dglMatrix.cc" />
     <ClCompile Include="..\..\source\graphics\dglMatrix.cc" />
     <ClCompile Include="..\..\source\graphics\DynamicTexture.cc" />
     <ClCompile Include="..\..\source\graphics\DynamicTexture.cc" />
@@ -599,6 +600,7 @@
     <ClCompile Include="..\..\source\spine\Animation.c" />
     <ClCompile Include="..\..\source\spine\Animation.c" />
     <ClCompile Include="..\..\source\spine\AnimationState.c" />
     <ClCompile Include="..\..\source\spine\AnimationState.c" />
     <ClCompile Include="..\..\source\spine\AnimationStateData.c" />
     <ClCompile Include="..\..\source\spine\AnimationStateData.c" />
+    <ClCompile Include="..\..\source\spine\Array.c" />
     <ClCompile Include="..\..\source\spine\Atlas.c" />
     <ClCompile Include="..\..\source\spine\Atlas.c" />
     <ClCompile Include="..\..\source\spine\AtlasAttachmentLoader.c" />
     <ClCompile Include="..\..\source\spine\AtlasAttachmentLoader.c" />
     <ClCompile Include="..\..\source\spine\Attachment.c" />
     <ClCompile Include="..\..\source\spine\Attachment.c" />
@@ -606,18 +608,34 @@
     <ClCompile Include="..\..\source\spine\Bone.c" />
     <ClCompile Include="..\..\source\spine\Bone.c" />
     <ClCompile Include="..\..\source\spine\BoneData.c" />
     <ClCompile Include="..\..\source\spine\BoneData.c" />
     <ClCompile Include="..\..\source\spine\BoundingBoxAttachment.c" />
     <ClCompile Include="..\..\source\spine\BoundingBoxAttachment.c" />
+    <ClCompile Include="..\..\source\spine\ClippingAttachment.c" />
+    <ClCompile Include="..\..\source\spine\Color.c" />
     <ClCompile Include="..\..\source\spine\Event.c" />
     <ClCompile Include="..\..\source\spine\Event.c" />
     <ClCompile Include="..\..\source\spine\EventData.c" />
     <ClCompile Include="..\..\source\spine\EventData.c" />
     <ClCompile Include="..\..\source\spine\extension.c" />
     <ClCompile Include="..\..\source\spine\extension.c" />
+    <ClCompile Include="..\..\source\spine\IkConstraint.c" />
+    <ClCompile Include="..\..\source\spine\IkConstraintData.c" />
     <ClCompile Include="..\..\source\spine\Json.c" />
     <ClCompile Include="..\..\source\spine\Json.c" />
+    <ClCompile Include="..\..\source\spine\MeshAttachment.c" />
+    <ClCompile Include="..\..\source\spine\PathAttachment.c" />
+    <ClCompile Include="..\..\source\spine\PathConstraint.c" />
+    <ClCompile Include="..\..\source\spine\PathConstraintData.c" />
+    <ClCompile Include="..\..\source\spine\PointAttachment.c" />
     <ClCompile Include="..\..\source\spine\RegionAttachment.c" />
     <ClCompile Include="..\..\source\spine\RegionAttachment.c" />
     <ClCompile Include="..\..\source\spine\Skeleton.c" />
     <ClCompile Include="..\..\source\spine\Skeleton.c" />
+    <ClCompile Include="..\..\source\spine\SkeletonBinary.c" />
     <ClCompile Include="..\..\source\spine\SkeletonBounds.c" />
     <ClCompile Include="..\..\source\spine\SkeletonBounds.c" />
+    <ClCompile Include="..\..\source\spine\SkeletonClipping.c" />
     <ClCompile Include="..\..\source\spine\SkeletonData.c" />
     <ClCompile Include="..\..\source\spine\SkeletonData.c" />
     <ClCompile Include="..\..\source\spine\SkeletonJson.c" />
     <ClCompile Include="..\..\source\spine\SkeletonJson.c" />
     <ClCompile Include="..\..\source\spine\Skin.c" />
     <ClCompile Include="..\..\source\spine\Skin.c" />
     <ClCompile Include="..\..\source\spine\Slot.c" />
     <ClCompile Include="..\..\source\spine\Slot.c" />
     <ClCompile Include="..\..\source\spine\SlotData.c" />
     <ClCompile Include="..\..\source\spine\SlotData.c" />
+    <ClCompile Include="..\..\source\spine\TransformConstraint.c" />
+    <ClCompile Include="..\..\source\spine\TransformConstraintData.c" />
+    <ClCompile Include="..\..\source\spine\Triangulator.c" />
+    <ClCompile Include="..\..\source\spine\VertexAttachment.c" />
+    <ClCompile Include="..\..\source\spine\VertexEffect.c" />
     <ClCompile Include="..\..\source\string\findMatch.cc" />
     <ClCompile Include="..\..\source\string\findMatch.cc" />
     <ClCompile Include="..\..\source\string\stringBuffer.cc" />
     <ClCompile Include="..\..\source\string\stringBuffer.cc" />
     <ClCompile Include="..\..\source\string\stringStack.cc" />
     <ClCompile Include="..\..\source\string\stringStack.cc" />
@@ -661,8 +679,8 @@
     <ClInclude Include="..\..\source\2d\assets\ParticleAssetField.h" />
     <ClInclude Include="..\..\source\2d\assets\ParticleAssetField.h" />
     <ClInclude Include="..\..\source\2d\assets\ParticleAssetFieldCollection.h" />
     <ClInclude Include="..\..\source\2d\assets\ParticleAssetFieldCollection.h" />
     <ClInclude Include="..\..\source\2d\assets\ParticleAsset_ScriptBinding.h" />
     <ClInclude Include="..\..\source\2d\assets\ParticleAsset_ScriptBinding.h" />
-    <ClInclude Include="..\..\source\2d\assets\SkeletonAsset.h" />
-    <ClInclude Include="..\..\source\2d\assets\SkeletonAsset_ScriptBinding.h" />
+    <ClInclude Include="..\..\source\2d\assets\SpineAsset.h" />
+    <ClInclude Include="..\..\source\2d\assets\SpineAsset_ScriptBinding.h" />
     <ClInclude Include="..\..\source\2d\controllers\AmbientForceController.h" />
     <ClInclude Include="..\..\source\2d\controllers\AmbientForceController.h" />
     <ClInclude Include="..\..\source\2d\controllers\AmbientForceController_ScriptBinding.h" />
     <ClInclude Include="..\..\source\2d\controllers\AmbientForceController_ScriptBinding.h" />
     <ClInclude Include="..\..\source\2d\controllers\core\GroupedSceneController.h" />
     <ClInclude Include="..\..\source\2d\controllers\core\GroupedSceneController.h" />
@@ -719,8 +737,9 @@
     <ClInclude Include="..\..\source\2d\sceneobject\LightObject_ScriptBinding.h" />
     <ClInclude Include="..\..\source\2d\sceneobject\LightObject_ScriptBinding.h" />
     <ClInclude Include="..\..\source\2d\sceneobject\ShapeVector.h" />
     <ClInclude Include="..\..\source\2d\sceneobject\ShapeVector.h" />
     <ClInclude Include="..\..\source\2d\sceneobject\ShapeVector_ScriptBinding.h" />
     <ClInclude Include="..\..\source\2d\sceneobject\ShapeVector_ScriptBinding.h" />
-    <ClInclude Include="..\..\source\2d\sceneobject\SkeletonObject.h" />
-    <ClInclude Include="..\..\source\2d\sceneobject\SkeletonObject_ScriptBinding.h" />
+    <ClInclude Include="..\..\source\2d\sceneobject\SpineCollisionProxy.h" />
+    <ClInclude Include="..\..\source\2d\sceneobject\SpineObject.h" />
+    <ClInclude Include="..\..\source\2d\sceneobject\SpineObject_ScriptBinding.h" />
     <ClInclude Include="..\..\source\2d\sceneobject\Sprite.h" />
     <ClInclude Include="..\..\source\2d\sceneobject\Sprite.h" />
     <ClInclude Include="..\..\source\2d\sceneobject\Sprite_ScriptBinding.h" />
     <ClInclude Include="..\..\source\2d\sceneobject\Sprite_ScriptBinding.h" />
     <ClInclude Include="..\..\source\2d\sceneobject\TextSprite.h" />
     <ClInclude Include="..\..\source\2d\sceneobject\TextSprite.h" />
@@ -885,8 +904,8 @@
     <ClInclude Include="..\..\source\game\gameInterface.h" />
     <ClInclude Include="..\..\source\game\gameInterface.h" />
     <ClInclude Include="..\..\source\game\gameInterface_ScriptBinding.h" />
     <ClInclude Include="..\..\source\game\gameInterface_ScriptBinding.h" />
     <ClInclude Include="..\..\source\game\version_ScriptBinding.h" />
     <ClInclude Include="..\..\source\game\version_ScriptBinding.h" />
-    <ClInclude Include="..\..\source\graphics\color.h" />
-    <ClInclude Include="..\..\source\graphics\color_ScriptBinding.h" />
+    <ClInclude Include="..\..\source\graphics\gColor.h" />
+    <ClInclude Include="..\..\source\graphics\gColor_ScriptBinding.h" />
     <ClInclude Include="..\..\source\graphics\dgl.h" />
     <ClInclude Include="..\..\source\graphics\dgl.h" />
     <ClInclude Include="..\..\source\graphics\dglMac_Scriptbinding.h" />
     <ClInclude Include="..\..\source\graphics\dglMac_Scriptbinding.h" />
     <ClInclude Include="..\..\source\graphics\dgl_ScriptBinding.h" />
     <ClInclude Include="..\..\source\graphics\dgl_ScriptBinding.h" />
@@ -1194,6 +1213,7 @@
     <ClInclude Include="..\..\source\spine\Animation.h" />
     <ClInclude Include="..\..\source\spine\Animation.h" />
     <ClInclude Include="..\..\source\spine\AnimationState.h" />
     <ClInclude Include="..\..\source\spine\AnimationState.h" />
     <ClInclude Include="..\..\source\spine\AnimationStateData.h" />
     <ClInclude Include="..\..\source\spine\AnimationStateData.h" />
+    <ClInclude Include="..\..\source\spine\Array.h" />
     <ClInclude Include="..\..\source\spine\Atlas.h" />
     <ClInclude Include="..\..\source\spine\Atlas.h" />
     <ClInclude Include="..\..\source\spine\AtlasAttachmentLoader.h" />
     <ClInclude Include="..\..\source\spine\AtlasAttachmentLoader.h" />
     <ClInclude Include="..\..\source\spine\Attachment.h" />
     <ClInclude Include="..\..\source\spine\Attachment.h" />
@@ -1201,19 +1221,36 @@
     <ClInclude Include="..\..\source\spine\Bone.h" />
     <ClInclude Include="..\..\source\spine\Bone.h" />
     <ClInclude Include="..\..\source\spine\BoneData.h" />
     <ClInclude Include="..\..\source\spine\BoneData.h" />
     <ClInclude Include="..\..\source\spine\BoundingBoxAttachment.h" />
     <ClInclude Include="..\..\source\spine\BoundingBoxAttachment.h" />
+    <ClInclude Include="..\..\source\spine\ClippingAttachment.h" />
+    <ClInclude Include="..\..\source\spine\Color.h" />
+    <ClInclude Include="..\..\source\spine\dll.h" />
     <ClInclude Include="..\..\source\spine\Event.h" />
     <ClInclude Include="..\..\source\spine\Event.h" />
     <ClInclude Include="..\..\source\spine\EventData.h" />
     <ClInclude Include="..\..\source\spine\EventData.h" />
     <ClInclude Include="..\..\source\spine\extension.h" />
     <ClInclude Include="..\..\source\spine\extension.h" />
+    <ClInclude Include="..\..\source\spine\IkConstraint.h" />
+    <ClInclude Include="..\..\source\spine\IkConstraintData.h" />
     <ClInclude Include="..\..\source\spine\Json.h" />
     <ClInclude Include="..\..\source\spine\Json.h" />
+    <ClInclude Include="..\..\source\spine\MeshAttachment.h" />
+    <ClInclude Include="..\..\source\spine\PathAttachment.h" />
+    <ClInclude Include="..\..\source\spine\PathConstraint.h" />
+    <ClInclude Include="..\..\source\spine\PathConstraintData.h" />
+    <ClInclude Include="..\..\source\spine\PointAttachment.h" />
     <ClInclude Include="..\..\source\spine\RegionAttachment.h" />
     <ClInclude Include="..\..\source\spine\RegionAttachment.h" />
     <ClInclude Include="..\..\source\spine\Skeleton.h" />
     <ClInclude Include="..\..\source\spine\Skeleton.h" />
+    <ClInclude Include="..\..\source\spine\SkeletonBinary.h" />
     <ClInclude Include="..\..\source\spine\SkeletonBounds.h" />
     <ClInclude Include="..\..\source\spine\SkeletonBounds.h" />
+    <ClInclude Include="..\..\source\spine\SkeletonClipping.h" />
     <ClInclude Include="..\..\source\spine\SkeletonData.h" />
     <ClInclude Include="..\..\source\spine\SkeletonData.h" />
     <ClInclude Include="..\..\source\spine\SkeletonJson.h" />
     <ClInclude Include="..\..\source\spine\SkeletonJson.h" />
     <ClInclude Include="..\..\source\spine\Skin.h" />
     <ClInclude Include="..\..\source\spine\Skin.h" />
     <ClInclude Include="..\..\source\spine\Slot.h" />
     <ClInclude Include="..\..\source\spine\Slot.h" />
     <ClInclude Include="..\..\source\spine\SlotData.h" />
     <ClInclude Include="..\..\source\spine\SlotData.h" />
     <ClInclude Include="..\..\source\spine\spine.h" />
     <ClInclude Include="..\..\source\spine\spine.h" />
+    <ClInclude Include="..\..\source\spine\TransformConstraint.h" />
+    <ClInclude Include="..\..\source\spine\TransformConstraintData.h" />
+    <ClInclude Include="..\..\source\spine\Triangulator.h" />
+    <ClInclude Include="..\..\source\spine\VertexAttachment.h" />
+    <ClInclude Include="..\..\source\spine\VertexEffect.h" />
     <ClInclude Include="..\..\source\string\findMatch.h" />
     <ClInclude Include="..\..\source\string\findMatch.h" />
     <ClInclude Include="..\..\source\string\stringBuffer.h" />
     <ClInclude Include="..\..\source\string\stringBuffer.h" />
     <ClInclude Include="..\..\source\string\stringBuffer_ScriptBinding.h" />
     <ClInclude Include="..\..\source\string\stringBuffer_ScriptBinding.h" />
@@ -1318,4 +1355,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
   </ImportGroup>
-</Project>
+</Project>

+ 140 - 29
engine/compilers/VisualStudio 2017/Torque 2D.vcxproj.filters

@@ -1176,9 +1176,6 @@
     <ClCompile Include="..\..\source\platformWin32\nativeDialogs\win32FileDialog.cc">
     <ClCompile Include="..\..\source\platformWin32\nativeDialogs\win32FileDialog.cc">
       <Filter>platformWin32\nativeDialogs</Filter>
       <Filter>platformWin32\nativeDialogs</Filter>
     </ClCompile>
     </ClCompile>
-    <ClCompile Include="..\..\source\graphics\color.cc">
-      <Filter>graphics</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\source\2d\assets\ParticleAsset.cc">
     <ClCompile Include="..\..\source\2d\assets\ParticleAsset.cc">
       <Filter>2d\assets</Filter>
       <Filter>2d\assets</Filter>
     </ClCompile>
     </ClCompile>
@@ -1353,12 +1350,6 @@
     <ClCompile Include="..\..\source\spine\SlotData.c">
     <ClCompile Include="..\..\source\spine\SlotData.c">
       <Filter>spine</Filter>
       <Filter>spine</Filter>
     </ClCompile>
     </ClCompile>
-    <ClCompile Include="..\..\source\2d\assets\SkeletonAsset.cc">
-      <Filter>2d\assets</Filter>
-    </ClCompile>
-    <ClCompile Include="..\..\source\2d\sceneobject\SkeletonObject.cc">
-      <Filter>2d\sceneobject</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\source\audio\vorbisStreamSource.cc">
     <ClCompile Include="..\..\source\audio\vorbisStreamSource.cc">
       <Filter>audio</Filter>
       <Filter>audio</Filter>
     </ClCompile>
     </ClCompile>
@@ -1386,7 +1377,7 @@
     </ClCompile>
     </ClCompile>
     <ClCompile Include="..\..\source\gui\guiTextCtrl.cc">
     <ClCompile Include="..\..\source\gui\guiTextCtrl.cc">
       <Filter>gui</Filter>
       <Filter>gui</Filter>
-	</ClCompile>
+    </ClCompile>
     <ClCompile Include="..\..\source\Box2D\Particle\b2Particle.cpp">
     <ClCompile Include="..\..\source\Box2D\Particle\b2Particle.cpp">
       <Filter>Box2D\Particle</Filter>
       <Filter>Box2D\Particle</Filter>
     </ClCompile>
     </ClCompile>
@@ -1417,6 +1408,69 @@
     <ClCompile Include="..\..\source\2d\sceneobject\Path.cc">
     <ClCompile Include="..\..\source\2d\sceneobject\Path.cc">
       <Filter>2d\sceneobject</Filter>
       <Filter>2d\sceneobject</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="..\..\source\spine\Array.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\ClippingAttachment.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\Color.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\IkConstraint.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\IkConstraintData.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\MeshAttachment.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\PathAttachment.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\PathConstraint.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\PathConstraintData.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\PointAttachment.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\SkeletonBinary.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\SkeletonClipping.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\TransformConstraint.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\TransformConstraintData.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\Triangulator.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\VertexAttachment.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\spine\VertexEffect.c">
+      <Filter>spine</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\graphics\gColor.cc">
+      <Filter>graphics</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\2d\sceneobject\SpineObject.cc">
+      <Filter>2d\sceneobject</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\2d\assets\SpineAsset.cc">
+      <Filter>2d\assets</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\source\2d\sceneobject\SpineCollisionProxy.cc">
+      <Filter>2d\sceneobject</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\source\audio\audio.h">
     <ClInclude Include="..\..\source\audio\audio.h">
@@ -2211,9 +2265,6 @@
     <ClInclude Include="..\..\source\algorithm\hashFunction.h">
     <ClInclude Include="..\..\source\algorithm\hashFunction.h">
       <Filter>algorithm</Filter>
       <Filter>algorithm</Filter>
     </ClInclude>
     </ClInclude>
-    <ClInclude Include="..\..\source\graphics\color.h">
-      <Filter>graphics</Filter>
-    </ClInclude>
     <ClInclude Include="..\..\source\collection\bitMatrix.h">
     <ClInclude Include="..\..\source\collection\bitMatrix.h">
       <Filter>collection</Filter>
       <Filter>collection</Filter>
     </ClInclude>
     </ClInclude>
@@ -2802,9 +2853,6 @@
     <ClInclude Include="..\..\source\console\expando_ScriptBinding.h">
     <ClInclude Include="..\..\source\console\expando_ScriptBinding.h">
       <Filter>console</Filter>
       <Filter>console</Filter>
     </ClInclude>
     </ClInclude>
-    <ClInclude Include="..\..\source\graphics\color_ScriptBinding.h">
-      <Filter>graphics</Filter>
-    </ClInclude>
     <ClInclude Include="..\..\source\sim\simObject_ScriptBinding.h">
     <ClInclude Include="..\..\source\sim\simObject_ScriptBinding.h">
       <Filter>sim</Filter>
       <Filter>sim</Filter>
     </ClInclude>
     </ClInclude>
@@ -3064,18 +3112,6 @@
     <ClInclude Include="..\..\source\spine\spine.h">
     <ClInclude Include="..\..\source\spine\spine.h">
       <Filter>spine</Filter>
       <Filter>spine</Filter>
     </ClInclude>
     </ClInclude>
-    <ClInclude Include="..\..\source\2d\assets\SkeletonAsset.h">
-      <Filter>2d\assets</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\source\2d\assets\SkeletonAsset_ScriptBinding.h">
-      <Filter>2d\assets</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\source\2d\sceneobject\SkeletonObject_ScriptBinding.h">
-      <Filter>2d\sceneobject</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\source\2d\sceneobject\SkeletonObject.h">
-      <Filter>2d\sceneobject</Filter>
-    </ClInclude>
     <ClInclude Include="..\..\source\audio\vorbisStreamSource.h">
     <ClInclude Include="..\..\source\audio\vorbisStreamSource.h">
       <Filter>audio</Filter>
       <Filter>audio</Filter>
     </ClInclude>
     </ClInclude>
@@ -3184,6 +3220,81 @@
     <ClInclude Include="..\..\source\2d\sceneobject\LightObject_ScriptBinding.h">
     <ClInclude Include="..\..\source\2d\sceneobject\LightObject_ScriptBinding.h">
       <Filter>2d\sceneobject</Filter>
       <Filter>2d\sceneobject</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="..\..\source\spine\Array.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\ClippingAttachment.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\Color.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\dll.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\IkConstraint.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\IkConstraintData.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\MeshAttachment.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\PathAttachment.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\PathConstraint.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\PathConstraintData.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\PointAttachment.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\SkeletonBinary.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\SkeletonClipping.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\TransformConstraint.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\TransformConstraintData.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\Triangulator.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\VertexAttachment.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\spine\VertexEffect.h">
+      <Filter>spine</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\graphics\gColor.h">
+      <Filter>graphics</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\graphics\gColor_ScriptBinding.h">
+      <Filter>graphics</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\2d\sceneobject\SpineObject.h">
+      <Filter>2d\sceneobject</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\2d\sceneobject\SpineObject_ScriptBinding.h">
+      <Filter>2d\sceneobject</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\2d\assets\SpineAsset.h">
+      <Filter>2d\assets</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\2d\assets\SpineAsset_ScriptBinding.h">
+      <Filter>2d\assets</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\2d\sceneobject\SpineCollisionProxy.h">
+      <Filter>2d\sceneobject</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <CustomBuild Include="..\..\source\math\mMath_ASM.asm">
     <CustomBuild Include="..\..\source\math\mMath_ASM.asm">
@@ -3210,4 +3321,4 @@
       <Filter>Box2D\Particle</Filter>
       <Filter>Box2D\Particle</Filter>
     </None>
     </None>
   </ItemGroup>
   </ItemGroup>
-</Project>
+</Project>

+ 1 - 1
engine/compilers/VisualStudio 2017/libogg.vcxproj

@@ -30,7 +30,7 @@
     <ProjectGuid>{15CBFEFF-7965-41F5-B4E2-21E8795C9159}</ProjectGuid>
     <ProjectGuid>{15CBFEFF-7965-41F5-B4E2-21E8795C9159}</ProjectGuid>
     <RootNamespace>libogg</RootNamespace>
     <RootNamespace>libogg</RootNamespace>
     <Keyword>Win32Proj</Keyword>
     <Keyword>Win32Proj</Keyword>
-    <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
   </PropertyGroup>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">

+ 1 - 1
engine/compilers/VisualStudio 2017/libvorbis.vcxproj

@@ -22,7 +22,7 @@
     <ProjectGuid>{3A214E06-B95E-4D61-A291-1F8DF2EC10FD}</ProjectGuid>
     <ProjectGuid>{3A214E06-B95E-4D61-A291-1F8DF2EC10FD}</ProjectGuid>
     <RootNamespace>libvorbis</RootNamespace>
     <RootNamespace>libvorbis</RootNamespace>
     <Keyword>Win32Proj</Keyword>
     <Keyword>Win32Proj</Keyword>
-    <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
   </PropertyGroup>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">

+ 1 - 1
engine/compilers/VisualStudio 2017/ljpeg.vcxproj

@@ -16,7 +16,7 @@
   </ItemGroup>
   </ItemGroup>
   <PropertyGroup Label="Globals">
   <PropertyGroup Label="Globals">
     <ProjectGuid>{0B07BA94-AA53-4FD4-ADB4-79EC2DA53B36}</ProjectGuid>
     <ProjectGuid>{0B07BA94-AA53-4FD4-ADB4-79EC2DA53B36}</ProjectGuid>
-    <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
   </PropertyGroup>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">

+ 1 - 1
engine/compilers/VisualStudio 2017/lpng.vcxproj

@@ -16,7 +16,7 @@
   </ItemGroup>
   </ItemGroup>
   <PropertyGroup Label="Globals">
   <PropertyGroup Label="Globals">
     <ProjectGuid>{AF1179E3-A838-46A3-A427-1E62AA4C52F4}</ProjectGuid>
     <ProjectGuid>{AF1179E3-A838-46A3-A427-1E62AA4C52F4}</ProjectGuid>
-    <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
   </PropertyGroup>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">

+ 1 - 1
engine/compilers/VisualStudio 2017/zlib.vcxproj

@@ -46,7 +46,7 @@
   </ItemGroup>
   </ItemGroup>
   <PropertyGroup Label="Globals">
   <PropertyGroup Label="Globals">
     <ProjectGuid>{86CB2525-0CF3-40D3-BF42-A0A95035EE8C}</ProjectGuid>
     <ProjectGuid>{86CB2525-0CF3-40D3-BF42-A0A95035EE8C}</ProjectGuid>
-    <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
   </PropertyGroup>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">

+ 0 - 386
engine/source/2d/assets/SkeletonAsset.cc

@@ -1,386 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2013 GarageGames, LLC
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//-----------------------------------------------------------------------------
-
-#ifndef _CONSOLE_H_
-#include "console/console.h"
-#endif
-
-#ifndef _CONSOLEINTERNAL_H_
-#include "console/consoleInternal.h"
-#endif
-
-#ifndef _GBITMAP_H_
-#include "graphics/gBitmap.h"
-#endif
-
-#ifndef _UTILITY_H_
-#include "2d/core/Utility.h"
-#endif
-
-#ifndef _SCENE_OBJECT_H_
-#include "2d/sceneobject/SceneObject.h"
-#endif
-
-#ifndef _SKELETON_ASSET_H_
-#include "2d/assets/SkeletonAsset.h"
-#endif
-
-// Script bindings.
-#include "SkeletonAsset_ScriptBinding.h"
-
-//------------------------------------------------------------------------------
-
-IMPLEMENT_CONOBJECT(SkeletonAsset);
-
-//------------------------------------------------------------------------------
-
-ConsoleType( skeletonAssetPtr, TypeSkeletonAssetPtr, sizeof(AssetPtr<SkeletonAsset>), ASSET_ID_FIELD_PREFIX )
-
-//-----------------------------------------------------------------------------
-
-ConsoleGetType( TypeSkeletonAssetPtr )
-{
-    // Fetch asset Id.
-    return (*((AssetPtr<SkeletonAsset>*)dptr)).getAssetId();
-}
-
-//-----------------------------------------------------------------------------
-
-ConsoleSetType( TypeSkeletonAssetPtr )
-{
-    // Was a single argument specified?
-    if( argc == 1 )
-    {
-        // Yes, so fetch field value.
-        const char* pFieldValue = argv[0];
-
-        // Fetch asset pointer.
-        AssetPtr<SkeletonAsset>* pAssetPtr = dynamic_cast<AssetPtr<SkeletonAsset>*>((AssetPtrBase*)(dptr));
-
-        // Is the asset pointer the correct type?
-        if (pAssetPtr == NULL )
-        {
-            // No, so fail.
-            Con::warnf( "(TypeSkeletonAssetPtr) - Failed to set asset Id '%d'.", pFieldValue );
-            return;
-        }
-
-        // Set asset.
-        pAssetPtr->setAssetId( pFieldValue );
-
-        return;
-   }
-
-    // Warn.
-    Con::warnf( "(TypeSkeletonAssetPtr) - Cannot set multiple args to a single asset." );
-}
-
-
-//------------------------------------------------------------------------------
-
-SkeletonAsset::SkeletonAsset() :    mSkeletonFile(StringTable->EmptyString),
-                                    mAtlasFile(StringTable->EmptyString),
-                                    mAtlasDirty(true),
-                                    mAtlas(NULL),
-                                    mSkeletonData(NULL),
-                                    mStateData(NULL)
-{
-}
-
-//------------------------------------------------------------------------------
-
-SkeletonAsset::~SkeletonAsset()
-{
-    spAnimationStateData_dispose(mStateData);
-    spSkeletonData_dispose(mSkeletonData);
-    spAtlas_dispose(mAtlas);
-}
-
-//------------------------------------------------------------------------------
-
-void SkeletonAsset::initPersistFields()
-{
-    // Call parent.
-    Parent::initPersistFields();
-
-    // Fields.
-    addProtectedField("AtlasFile", TypeAssetLooseFilePath, Offset(mAtlasFile, SkeletonAsset), &setAtlasFile, &defaultProtectedGetFn, &writeAtlasFile, "The loose file pointing to the .atlas file used for skinning");
-    addProtectedField("SkeletonFile", TypeAssetLooseFilePath, Offset(mSkeletonFile, SkeletonAsset), &setSkeletonFile, &defaultProtectedGetFn, &writeSkeletonFile, "The loose file produced by the editor, which is fed into this asset");
-}
-
-//------------------------------------------------------------------------------
-
-bool SkeletonAsset::onAdd()
-{
-    // Call Parent.
-    if (!Parent::onAdd())
-       return false;
-
-    return true;
-}
-
-//------------------------------------------------------------------------------
-
-void SkeletonAsset::onRemove()
-{
-    // Call Parent.
-    Parent::onRemove();
-}
-
-//------------------------------------------------------------------------------
-
-void SkeletonAsset::setSkeletonFile( const char* pSkeletonFile )
-{
-    // Sanity!
-    AssertFatal( pSkeletonFile != NULL, "Cannot use a NULL skeleton file." );
-
-    // Fetch skeleton file.
-    pSkeletonFile = StringTable->insert( pSkeletonFile );
-
-    // Ignore no change.
-    if (pSkeletonFile == mSkeletonFile )
-        return;
-
-    // Update.
-    mSkeletonFile = getOwned() ? expandAssetFilePath( pSkeletonFile ) : StringTable->insert( pSkeletonFile );
-
-    // Refresh the asset.
-    refreshAsset();
-}
-
-//------------------------------------------------------------------------------
-
-void SkeletonAsset::setAtlasFile( const char* pAtlasFile )
-{
-    // Sanity!
-    AssertFatal( pAtlasFile != NULL, "Cannot use a NULL atlas file." );
-
-    // Fetch atlas file.
-    pAtlasFile = StringTable->insert( pAtlasFile );
-
-    // Ignore no change.
-    if (pAtlasFile == mAtlasFile )
-        return;
-
-    // Update.
-    mAtlasFile = getOwned() ? expandAssetFilePath( pAtlasFile ) : StringTable->insert( pAtlasFile );
-    mAtlasDirty = true;
-
-    // Refresh the asset.
-    refreshAsset();
-}
-
-//------------------------------------------------------------------------------
-
-void SkeletonAsset::copyTo(SimObject* object)
-{
-    // Call to parent.
-    Parent::copyTo(object);
-
-    // Cast to asset.
-    SkeletonAsset* pAsset = static_cast<SkeletonAsset*>(object);
-
-    // Sanity!
-    AssertFatal(pAsset != NULL, "SkeletonAsset::copyTo() - Object is not the correct type.");
-
-    // Copy state.
-    pAsset->setAtlasFile( getAtlasFile() );
-    pAsset->setSkeletonFile( getSkeletonFile() );
-}
-
-//------------------------------------------------------------------------------
-
-void SkeletonAsset::initializeAsset( void )
-{
-    // Call parent.
-    Parent::initializeAsset();
-
-    // Ensure the skeleton file is expanded.
-    mSkeletonFile = expandAssetFilePath( mSkeletonFile );
-
-    // Ensure the skeleton file is expanded.
-    mAtlasFile = expandAssetFilePath( mAtlasFile );
-
-    // Build the atlas data
-    if (mAtlasDirty)
-        buildAtlasData();
-
-    // Build the skeleton data
-    buildSkeletonData();
-}
-
-//------------------------------------------------------------------------------
-
-void SkeletonAsset::onAssetRefresh( void )
-{
-    // Ignore if not yet added to the sim.
-    if (!isProperlyAdded() )
-        return;
-
-    // Call parent.
-    Parent::onAssetRefresh();
-
-    // Reset any states or data
-    if (mAtlasDirty)
-        buildAtlasData();
-
-    buildSkeletonData();
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonAsset::buildAtlasData( void )
-{
-    // If the atlas data was previously created, need to release it
-    if (mAtlas)
-        spAtlas_dispose(mAtlas);
-
-    // If we are using a .atlas file
-    if (mAtlasFile != StringTable->EmptyString)
-        mAtlas = spAtlas_readAtlasFile(mAtlasFile);
-
-    // Atlas load failure
-    AssertFatal(mAtlas != NULL, "SkeletonAsset::buildAtlasData() - Atlas was not loaded.");
-
-    spAtlasPage* currentPage = mAtlas->pages;
-
-    while (currentPage != NULL)
-    {
-        // Allocate a new ImageAsset. If we have multiple atlases, we would loop this multiple times
-        ImageAsset* pImageAsset = new ImageAsset();
-
-        const char* imageFilePath = expandAssetFilePath(currentPage->name);
-        
-        // Point to the raw file (png or jpg)
-        pImageAsset->setImageFile( imageFilePath);        
-
-        // Enable Explicit Mode so we can use region coordinates
-        pImageAsset->setExplicitMode( true );
-
-        spAtlasRegion* currentRegion = mAtlas->regions;
-
-        // Add it to the AssetDatabase, making it accessible everywhere
-        mImageAsset = AssetDatabase.addPrivateAsset( pImageAsset );
-
-        // Loop through the Atlas information to create cell regions
-        while (currentRegion != NULL)
-        {
-            pImageAsset->addExplicitCell( currentRegion->x, currentRegion->y, currentRegion->width, currentRegion->height, currentRegion->name );
-
-            currentRegion = currentRegion->next;
-        }
-
-        mImageAsset->forceCalculation();
-        currentPage = currentPage->next;
-    }
-
-    mAtlasDirty = false;
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonAsset::buildSkeletonData( void )
-{
-    // Atlas load failure
-    AssertFatal(mAtlas != NULL, "SkeletonAsset::buildSkeletonData() - Atlas was not loaded.");
-    
-    // Clear state data
-    if (mStateData)
-        spAnimationStateData_dispose(mStateData);
-
-    // Clear skeleton data
-    if (mSkeletonData)
-        spSkeletonData_dispose(mSkeletonData);
-    
-    spSkeletonJson* json = spSkeletonJson_create(mAtlas);
-    mSkeletonData = spSkeletonJson_readSkeletonDataFile(json, mSkeletonFile);
-
-    if (!mSkeletonData)
-    {
-        spAtlas_dispose(mAtlas);
-        mAtlas = 0;
-
-        // Report json->error message
-        AssertFatal(mSkeletonData != NULL, "SkeletonAsset::buildSkeletonData() - Skeleton data was not valid.");
-    }
-
-    spSkeletonJson_dispose(json);
-
-    mStateData = spAnimationStateData_create(mSkeletonData);
-}
-
-//-----------------------------------------------------------------------------
-
-bool SkeletonAsset::isAssetValid( void ) const
-{
-    return ((mAtlas != NULL) && (mSkeletonData != NULL) && (mStateData != NULL) && mImageAsset.notNull());
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonAsset::onTamlPreWrite( void )
-{
-    // Call parent.
-    Parent::onTamlPreWrite();
-
-    // Ensure the skeleton file is collapsed.
-    mSkeletonFile = collapseAssetFilePath( mSkeletonFile );
-
-    // Ensure the atlas file is collapsed.
-    mAtlasFile = collapseAssetFilePath( mAtlasFile );
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonAsset::onTamlPostWrite( void )
-{
-    // Call parent.
-    Parent::onTamlPostWrite();
-
-    // Ensure the skeleton file is expanded.
-    mSkeletonFile = expandAssetFilePath( mSkeletonFile );
-
-    // Ensure the atlas file is expanded.
-    mAtlasFile = expandAssetFilePath( mAtlasFile );
-}
-
-//------------------------------------------------------------------------------
-
-void SkeletonAsset::onTamlCustomWrite( TamlCustomNodes& customNodes )
-{
-    // Debug Profiling.
-    PROFILE_SCOPE(SkeletonAsset_OnTamlCustomWrite);
-
-    // Call parent.
-    Parent::onTamlCustomWrite( customNodes );
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonAsset::onTamlCustomRead( const TamlCustomNodes& customNodes )
-{
-    // Debug Profiling.
-    PROFILE_SCOPE(SkeletonAsset_OnTamlCustomRead);
-
-    // Call parent.
-    Parent::onTamlCustomRead( customNodes );
-}

+ 0 - 101
engine/source/2d/assets/SkeletonAsset.h

@@ -1,101 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2013 GarageGames, LLC
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//-----------------------------------------------------------------------------
-
-#ifndef _SKELETON_ASSET_H_
-#define _SKELETON_ASSET_H_
-
-#ifndef _ASSET_PTR_H_
-#include "assets/assetPtr.h"
-#endif
-
-#ifndef _IMAGE_ASSET_H_
-#include "2d/assets/ImageAsset.h"
-#endif
-
-#ifndef SPINE_SPINE_H_
-#include "spine/spine.h"
-#endif
-
-//-----------------------------------------------------------------------------
-
-DefineConsoleType( TypeSkeletonAssetPtr )
-
-//-----------------------------------------------------------------------------
-
-class SkeletonAsset : public AssetBase
-{
-private:
-    typedef AssetBase Parent;
-    bool                            mAtlasDirty;
-
-public:
-    StringTableEntry                mSkeletonFile;
-    StringTableEntry                mAtlasFile;
-    AssetPtr<ImageAsset>            mImageAsset;
-    spAtlas*                        mAtlas;
-    spSkeletonData*                 mSkeletonData;
-    spAnimationStateData*           mStateData;
-
-public:
-    SkeletonAsset();
-    virtual ~SkeletonAsset();
-
-    /// Core.
-    static void initPersistFields();
-    virtual bool onAdd();
-    virtual void onRemove();
-    virtual void copyTo(SimObject* object);
-
-    void                    setSkeletonFile( const char* pSkeletonFile );
-    inline StringTableEntry getSkeletonFile( void ) const                   { return mSkeletonFile; }
-
-    void                    setAtlasFile( const char* pAtlasFile );
-    inline StringTableEntry getAtlasFile( void ) const                      { return mAtlasFile; }
-    
-    virtual bool            isAssetValid( void ) const;
-
-    /// Declare Console Object.
-    DECLARE_CONOBJECT(SkeletonAsset);
-
-private:
-    void buildAtlasData( void );
-    void buildSkeletonData( void );
-
-protected:
-    virtual void initializeAsset( void );
-    virtual void onAssetRefresh( void );
-
-    /// Taml callbacks.
-    virtual void onTamlPreWrite( void );
-    virtual void onTamlPostWrite( void );
-    virtual void onTamlCustomWrite( TamlCustomNodes& customNodes );
-    virtual void onTamlCustomRead( const TamlCustomNodes& customNodes );
-
-
-protected:
-    static bool setSkeletonFile( void* obj, const char* data )              { static_cast<SkeletonAsset*>(obj)->setSkeletonFile(data); return false; }
-    static bool writeSkeletonFile( void* obj, StringTableEntry pFieldName ) { return static_cast<SkeletonAsset*>(obj)->getSkeletonFile() != StringTable->EmptyString; }
-    static bool setAtlasFile( void* obj, const char* data )                 { static_cast<SkeletonAsset*>(obj)->setAtlasFile(data); return false; }
-    static bool writeAtlasFile( void* obj, StringTableEntry pFieldName )    { return static_cast<SkeletonAsset*>(obj)->getAtlasFile() != StringTable->EmptyString; }
-};
-
-#endif // _SKELETON_ASSET_H_

+ 434 - 0
engine/source/2d/assets/SpineAsset.cc

@@ -0,0 +1,434 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _CONSOLE_H_
+#include "console/console.h"
+#endif
+
+#ifndef _CONSOLEINTERNAL_H_
+#include "console/consoleInternal.h"
+#endif
+
+#ifndef _GBITMAP_H_
+#include "graphics/gBitmap.h"
+#endif
+
+#ifndef _UTILITY_H_
+#include "2d/core/Utility.h"
+#endif
+
+#ifndef _SCENE_OBJECT_H_
+#include "2d/sceneobject/SceneObject.h"
+#endif
+
+#ifndef _SPINE_ASSET_H_
+#include "2d/assets/SpineAsset.h"
+#endif
+
+// Script bindings.
+#include "SpineAsset_ScriptBinding.h"
+
+#include "spine/extension.h"
+
+//------------------------------------------------------------------------------
+
+IMPLEMENT_CONOBJECT(SpineAsset);
+
+//------------------------------------------------------------------------------
+
+ConsoleType(spineAssetPtr, TypeSpineAssetPtr, sizeof(AssetPtr<SpineAsset>), ASSET_ID_FIELD_PREFIX)
+
+//-----------------------------------------------------------------------------
+
+ConsoleGetType(TypeSpineAssetPtr)
+{
+	// Fetch asset Id.
+	return (*((AssetPtr<SpineAsset>*)dptr)).getAssetId();
+}
+
+//-----------------------------------------------------------------------------
+
+ConsoleSetType(TypeSpineAssetPtr)
+{
+	// Was a single argument specified?
+	if (argc == 1)
+	{
+		// Yes, so fetch field value.
+		const char* pFieldValue = argv[0];
+
+		// Fetch asset pointer.
+		AssetPtr<SpineAsset>* pAssetPtr = dynamic_cast<AssetPtr<SpineAsset>*>((AssetPtrBase*)(dptr));
+
+		// Is the asset pointer the correct type?
+		if (pAssetPtr == NULL)
+		{
+			// No, so fail.
+			Con::warnf("(TypeSpineAssetPtr) - Failed to set asset Id '%d'.", pFieldValue);
+			return;
+		}
+
+		// Set asset.
+		pAssetPtr->setAssetId(pFieldValue);
+
+		return;
+	}
+
+	// Warn.
+	Con::warnf("(TypeSpineAssetPtr) - Cannot set multiple args to a single asset.");
+}
+
+
+//-----------------------------------------------------------------------------
+// 
+// The following three methods are hooks required to be defined by the spine runtime.
+//
+//-----------------------------------------------------------------------------
+void _spAtlasPage_createTexture(spAtlasPage* self, const char* path) {
+
+	// Allocate a new ImageAsset. 
+	ImageAsset* pImageAsset = new ImageAsset();
+
+	SpineAsset *pSpine = (SpineAsset *)self->atlas->rendererObject;
+	const char* imageFilePath = pSpine->expandAssetFilePath(self->name);
+
+	// Point to the raw file (png or jpg)
+	pImageAsset->setImageFile(imageFilePath);
+
+	// Enable Explicit Mode so we can use region coordinates
+	pImageAsset->setExplicitMode(true);
+
+	// Add it to the AssetDatabase, making it accessible everywhere
+	pSpine->mImageAsset = AssetDatabase.addPrivateAsset(pImageAsset);
+
+	// Attach texture info for the page.
+	self->rendererObject = pSpine->mImageAsset;
+	self->width = pImageAsset->getImageWidth();
+	self->height = pImageAsset->getImageHeight();
+}
+
+void _spAtlasPage_disposeTexture(spAtlasPage* self) {
+}
+
+char* _spUtil_readFile(const char* path, int* length) {
+	return _spReadFile(path, length);
+}
+
+//------------------------------------------------------------------------------
+
+SpineAsset::SpineAsset() : mSpineFile(StringTable->EmptyString),
+mAtlasFile(StringTable->EmptyString),
+mAtlasDirty(true),
+mAtlas(NULL),
+mSkeletonData(NULL),
+mAnimationStateData(NULL),
+mPreMultipliedAlpha(false)
+
+{
+}
+
+//------------------------------------------------------------------------------
+
+SpineAsset::~SpineAsset()
+{
+	spAnimationStateData_dispose(mAnimationStateData);
+	spSkeletonData_dispose(mSkeletonData);
+	spAtlas_dispose(mAtlas);
+}
+
+//------------------------------------------------------------------------------
+
+void SpineAsset::initPersistFields()
+{
+	// Call parent.
+	Parent::initPersistFields();
+
+	// Fields.
+	addProtectedField("AtlasFile", TypeAssetLooseFilePath, Offset(mAtlasFile, SpineAsset), &setAtlasFile, &defaultProtectedGetFn, &writeAtlasFile, "The loose file pointing to the .atlas file used for skinning");
+	addProtectedField("SpineFile", TypeAssetLooseFilePath, Offset(mSpineFile, SpineAsset), &setSpineFile, &defaultProtectedGetFn, &writeSpineFile, "The loose file produced by the editor, which is fed into this asset");
+	addProtectedField("PreMultipliedAlpha", TypeBool, Offset(mPreMultipliedAlpha, SpineAsset), &setPreMultipliedAlpha, &defaultProtectedGetFn, &writePreMultipliedAlpha, "Whether texture is built with pre-multiplied alpha values.");
+}
+
+//------------------------------------------------------------------------------
+
+bool SpineAsset::onAdd()
+{
+	// Call Parent.
+	if (!Parent::onAdd())
+		return false;
+
+	return true;
+}
+
+//------------------------------------------------------------------------------
+
+void SpineAsset::onRemove()
+{
+	// Call Parent.
+	Parent::onRemove();
+}
+
+//------------------------------------------------------------------------------
+
+void SpineAsset::setSpineFile(const char* pSpineFile)
+{
+	// Sanity!
+	AssertFatal(pSpineFile != NULL, "Cannot use a NULL spine file.");
+
+	// Fetch spine file.
+	pSpineFile = StringTable->insert(pSpineFile);
+
+	// Ignore no change.
+	if (pSpineFile == mSpineFile)
+		return;
+
+	// Update.
+	mSpineFile = getOwned() ? expandAssetFilePath(pSpineFile) : StringTable->insert(pSpineFile);
+
+	// Refresh the asset.
+	refreshAsset();
+}
+
+//------------------------------------------------------------------------------
+
+void SpineAsset::setAtlasFile(const char* pAtlasFile)
+{
+	// Sanity!
+	AssertFatal(pAtlasFile != NULL, "Cannot use a NULL atlas file.");
+
+	// Fetch atlas file.
+	pAtlasFile = StringTable->insert(pAtlasFile);
+
+	// Ignore no change.
+	if (pAtlasFile == mAtlasFile)
+		return;
+
+	// Update.
+	mAtlasFile = getOwned() ? expandAssetFilePath(pAtlasFile) : StringTable->insert(pAtlasFile);
+	mAtlasDirty = true;
+
+	// Refresh the asset.
+	refreshAsset();
+}
+
+//------------------------------------------------------------------------------
+
+void SpineAsset::copyTo(SimObject* object)
+{
+	// Call to parent.
+	Parent::copyTo(object);
+
+	// Cast to asset.
+	SpineAsset* pAsset = static_cast<SpineAsset*>(object);
+
+	// Sanity!
+	AssertFatal(pAsset != NULL, "SpineAsset::copyTo() - Object is not the correct type.");
+
+	// Copy state.
+	pAsset->setAtlasFile(getAtlasFile());
+	pAsset->setSpineFile(getSpineFile());
+}
+
+//------------------------------------------------------------------------------
+
+void SpineAsset::initializeAsset(void)
+{
+	// Call parent.
+	Parent::initializeAsset();
+
+	// Ensure the spine file is expanded.
+	mSpineFile = expandAssetFilePath(mSpineFile);
+
+	// Ensure the spine file is expanded.
+	mAtlasFile = expandAssetFilePath(mAtlasFile);
+
+	// Build the atlas data
+	if (mAtlasDirty)
+		buildAtlasData();
+
+	// Build the spine data
+	buildSpineData();
+}
+
+//------------------------------------------------------------------------------
+
+void SpineAsset::onAssetRefresh(void)
+{
+	// Ignore if not yet added to the sim.
+	if (!isProperlyAdded())
+		return;
+
+	// Call parent.
+	Parent::onAssetRefresh();
+
+	// Reset any states or data
+	if (mAtlasDirty)
+		buildAtlasData();
+
+	buildSpineData();
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineAsset::buildAtlasData(void)
+{
+	// If the atlas data was previously created, need to release it
+	if (mAtlas)
+		spAtlas_dispose(mAtlas);
+
+	// If we are using a .atlas file
+	if (mAtlasFile != StringTable->EmptyString)
+		mAtlas = spAtlas_createFromFile(mAtlasFile, this);
+
+	// Atlas load failure
+	AssertFatal(mAtlas != NULL, "SpineAsset::buildAtlasData() - Atlas was not loaded.");
+
+	spAtlasPage* currentPage = mAtlas->pages;
+
+	while (currentPage != NULL)
+	{
+		ImageAsset* pImageAsset = (ImageAsset *)currentPage->rendererObject;
+
+		spAtlasRegion* currentRegion = mAtlas->regions;
+
+		// Loop through the Atlas information to create cell regions
+		while (currentRegion != NULL)
+		{
+			if (currentRegion->rotate) {
+				pImageAsset->addExplicitCell(currentRegion->x, currentRegion->y, currentRegion->height, currentRegion->width, currentRegion->name);
+			}
+			else {
+				pImageAsset->addExplicitCell(currentRegion->x, currentRegion->y, currentRegion->width, currentRegion->height, currentRegion->name);
+			}
+
+			currentRegion = currentRegion->next;
+		}
+
+		mImageAsset->forceCalculation();
+		currentPage = currentPage->next;
+	}
+
+	mAtlasDirty = false;
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineAsset::buildSpineData(void)
+{
+	// Atlas load failure
+	AssertFatal(mAtlas != NULL, "SpineAsset::buildSpineData() - Atlas was not loaded.");
+
+	// Clear state data
+	if (mAnimationStateData)
+		spAnimationStateData_dispose(mAnimationStateData);
+
+	// Clear skeleton data
+	if (mSkeletonData)
+		spSkeletonData_dispose(mSkeletonData);
+
+	// Determine if we have a json or binary file to process.
+	AssertFatal(mSpineFile != NULL, "SpineAsset::buildSpineData() - Spine data file name is not defined.");
+
+	if (Platform::hasExtension(mSpineFile, "skel")) {
+		spSkeletonBinary* skel = spSkeletonBinary_create(mAtlas);
+		skel->scale = 0.01f; // Adapt to box2d coordinate size - where 1 unit = 1 meter.
+		mSkeletonData = spSkeletonBinary_readSkeletonDataFile(skel, mSpineFile);
+		spSkeletonBinary_dispose(skel);
+	}
+	else if (Platform::hasExtension(mSpineFile, "json")) {
+		spSkeletonJson* json = spSkeletonJson_create(mAtlas);
+		json->scale = 0.01f;
+		mSkeletonData = spSkeletonJson_readSkeletonDataFile(json, mSpineFile);
+		spSkeletonJson_dispose(json);
+	}
+	else {
+		AssertFatal(mSkeletonData != NULL,
+			"SpineAsset::buildSpineData: Unrecognized spine data file extension recieved.  Expecting either '.json' or '.skel'.");
+	}
+
+	if (!mSkeletonData)
+	{
+		spAtlas_dispose(mAtlas);
+		mAtlas = 0;
+
+		// Report json->error message
+		AssertFatal(mSkeletonData != NULL, "SpineAsset::buildSpineData() - Spine data was not valid.");
+	}
+
+	mAnimationStateData = spAnimationStateData_create(mSkeletonData);
+}
+
+//-----------------------------------------------------------------------------
+
+bool SpineAsset::isAssetValid(void) const
+{
+	return ((mAtlas != NULL) && (mSkeletonData != NULL) && (mAnimationStateData != NULL) && mImageAsset.notNull());
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineAsset::onTamlPreWrite(void)
+{
+	// Call parent.
+	Parent::onTamlPreWrite();
+
+	// Ensure the spine file is collapsed.
+	mSpineFile = collapseAssetFilePath(mSpineFile);
+
+	// Ensure the atlas file is collapsed.
+	mAtlasFile = collapseAssetFilePath(mAtlasFile);
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineAsset::onTamlPostWrite(void)
+{
+	// Call parent.
+	Parent::onTamlPostWrite();
+
+	// Ensure the spine file is expanded.
+	mSpineFile = expandAssetFilePath(mSpineFile);
+
+	// Ensure the atlas file is expanded.
+	mAtlasFile = expandAssetFilePath(mAtlasFile);
+}
+
+//------------------------------------------------------------------------------
+
+void SpineAsset::onTamlCustomWrite(TamlCustomNodes& customNodes)
+{
+	// Debug Profiling.
+	PROFILE_SCOPE(SpineAsset_OnTamlCustomWrite);
+
+	// Call parent.
+	Parent::onTamlCustomWrite(customNodes);
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineAsset::onTamlCustomRead(const TamlCustomNodes& customNodes)
+{
+	// Debug Profiling.
+	PROFILE_SCOPE(SpineAsset_OnTamlCustomRead);
+
+	// Call parent.
+	Parent::onTamlCustomRead(customNodes);
+}

+ 107 - 0
engine/source/2d/assets/SpineAsset.h

@@ -0,0 +1,107 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _SPINE_ASSET_H_
+#define _SPINE_ASSET_H_
+
+#ifndef _ASSET_PTR_H_
+#include "assets/assetPtr.h"
+#endif
+
+#ifndef _IMAGE_ASSET_H_
+#include "2d/assets/ImageAsset.h"
+#endif
+
+#ifndef SPINE_SPINE_H_
+#include "spine/spine.h"
+#endif
+
+//-----------------------------------------------------------------------------
+
+DefineConsoleType(TypeSpineAssetPtr)
+
+//-----------------------------------------------------------------------------
+
+class SpineAsset : public AssetBase
+{
+private:
+	typedef AssetBase Parent;
+	bool                            mAtlasDirty;
+	bool							mPreMultipliedAlpha;
+
+public:
+	StringTableEntry                mSpineFile;
+	StringTableEntry                mAtlasFile;
+	AssetPtr<ImageAsset>            mImageAsset;
+	spAtlas*                        mAtlas;
+	spSkeletonData*                 mSkeletonData;
+	spAnimationStateData*           mAnimationStateData;
+
+public:
+	SpineAsset();
+	virtual ~SpineAsset();
+
+	/// Core.
+	static void initPersistFields();
+	virtual bool onAdd();
+	virtual void onRemove();
+	virtual void copyTo(SimObject* object);
+
+	void                    setSpineFile(const char* pSpineFile);
+	inline StringTableEntry getSpineFile(void) const { return mSpineFile; }
+
+	void                    setAtlasFile(const char* pAtlasFile);
+	inline StringTableEntry getAtlasFile(void) const { return mAtlasFile; }
+
+	virtual bool            isAssetValid(void) const;
+
+	inline void				setPreMultipliedAlpha(const bool usePMA) { mPreMultipliedAlpha = usePMA; }
+	inline bool				getPreMultipliedAlpha(void) const { return mPreMultipliedAlpha; }
+
+	/// Declare Console Object.
+	DECLARE_CONOBJECT(SpineAsset);
+
+private:
+	void buildAtlasData(void);
+	void buildSpineData(void);
+
+protected:
+	virtual void initializeAsset(void);
+	virtual void onAssetRefresh(void);
+
+	/// Taml callbacks.
+	virtual void onTamlPreWrite(void);
+	virtual void onTamlPostWrite(void);
+	virtual void onTamlCustomWrite(TamlCustomNodes& customNodes);
+	virtual void onTamlCustomRead(const TamlCustomNodes& customNodes);
+
+
+protected:
+	static bool setSpineFile(void* obj, const char* data) { static_cast<SpineAsset*>(obj)->setSpineFile(data); return false; }
+	static bool writeSpineFile(void* obj, StringTableEntry pFieldName) { return static_cast<SpineAsset*>(obj)->getSpineFile() != StringTable->EmptyString; }
+	static bool setAtlasFile(void* obj, const char* data) { static_cast<SpineAsset*>(obj)->setAtlasFile(data); return false; }
+	static bool writeAtlasFile(void* obj, StringTableEntry pFieldName) { return static_cast<SpineAsset*>(obj)->getAtlasFile() != StringTable->EmptyString; }
+	static bool setPreMultipliedAlpha(void* obj, const char* data) { static_cast<SpineAsset*>(obj)->setPreMultipliedAlpha(dAtob(data)); return false; }
+	static bool writePreMultipliedAlpha(void* obj, StringTableEntry pFieldName) { return static_cast<SpineAsset*>(obj)->getPreMultipliedAlpha(); }
+};
+
+#endif // _SPINE_ASSET_H_

+ 16 - 16
engine/source/2d/assets/SkeletonAsset_ScriptBinding.h → engine/source/2d/assets/SpineAsset_ScriptBinding.h

@@ -20,46 +20,46 @@
 // IN THE SOFTWARE.
 // IN THE SOFTWARE.
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 
-ConsoleMethodGroupBeginWithDocs(SkeletonAsset, AssetBase)
+ConsoleMethodGroupBeginWithDocs(SpineAsset, AssetBase)
 
 
 /*! Sets the atlas file.
 /*! Sets the atlas file.
-    @return No return value.
+	@return No return value.
 */
 */
-ConsoleMethodWithDocs(SkeletonAsset, setAtlasFile, ConsoleVoid, 3, 3, (AtlasFile))
+ConsoleMethodWithDocs(SpineAsset, setAtlasFile, ConsoleVoid, 3, 3, (AtlasFile))
 {
 {
-    object->setAtlasFile( argv[2] );
+	object->setAtlasFile(argv[2]);
 }
 }
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 
 /*! Gets the atlas file.
 /*! Gets the atlas file.
-    @return Returns the atlas file.
+	@return Returns the atlas file.
 */
 */
-ConsoleMethodWithDocs(SkeletonAsset, getAtlasFile, ConsoleString, 2, 2, ())
+ConsoleMethodWithDocs(SpineAsset, getAtlasFile, ConsoleString, 2, 2, ())
 {
 {
-    return object->getAtlasFile();
+	return object->getAtlasFile();
 }
 }
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 
 
-/*! Sets the skeleton file.
-    @return No return value.
+/*! Sets the spine file.
+	@return No return value.
 */
 */
-ConsoleMethodWithDocs(SkeletonAsset, setSkeletonFile, ConsoleVoid, 3, 3, (SkeletonFile))
+ConsoleMethodWithDocs(SpineAsset, setSpineFile, ConsoleVoid, 3, 3, (SpineFile))
 {
 {
-    object->setSkeletonFile( argv[2] );
+	object->setSpineFile(argv[2]);
 }
 }
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 
-/*! Gets the skeleton file.
-    @return Returns the skeleton file.
+/*! Gets the spine file.
+	@return Returns the spine file.
 */
 */
-ConsoleMethodWithDocs(SkeletonAsset, getSkeletonFile, ConsoleString, 2, 2, ())
+ConsoleMethodWithDocs(SpineAsset, getSpineFile, ConsoleString, 2, 2, ())
 {
 {
-    return object->getSkeletonFile();
+	return object->getSpineFile();
 }
 }
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 
 
-ConsoleMethodGroupEndWithDocs(SkeletonAsset)
+ConsoleMethodGroupEndWithDocs(SpineAsset)

+ 18 - 38
engine/source/2d/core/BatchRender.cc

@@ -103,8 +103,8 @@ void BatchRender::SubmitTriangles(
         const U32 vertexCount,
         const U32 vertexCount,
         const Vector2* pVertexArray,
         const Vector2* pVertexArray,
         const Vector2* pTextureArray,
         const Vector2* pTextureArray,
-        TextureHandle& texture,
-        const ColorF& color )
+		const ColorF*  pColorArray,
+		TextureHandle& texture)
 {
 {
     // Debug Profiling.
     // Debug Profiling.
     PROFILE_SCOPE(BatchRender_SubmitTriangles);
     PROFILE_SCOPE(BatchRender_SubmitTriangles);
@@ -117,28 +117,17 @@ void BatchRender::SubmitTriangles(
     // Calculate triangle count.
     // Calculate triangle count.
     const U32 triangleCount = vertexCount / 3;
     const U32 triangleCount = vertexCount / 3;
 
 
-    // Would we exceed the triangle buffer size?
     if ( (mTriangleCount + triangleCount) > BATCHRENDER_MAXTRIANGLES )
     if ( (mTriangleCount + triangleCount) > BATCHRENDER_MAXTRIANGLES )
     {
     {
-        // Yes, so flush.
+        // No room in the batch for the incoming request, so flush the current batch contents.
         flush( mpDebugStats->batchBufferFullFlush );
         flush( mpDebugStats->batchBufferFullFlush );
     }
     }
-    // Do we have anything batched?
-    else if ( mTriangleCount > 0 )
-    {
-        // Yes, so do we have any existing colors?
-        if ( mColorCount == 0 )
-        {
-            // No, so flush if color is specified.
-            if ( color != NoColor  )
-                flush( mpDebugStats->batchColorStateFlush );
-        }
-        else
-        {
-            // Yes, so flush if color is not specified.
-            if ( color == NoColor  )
-                flush( mpDebugStats->batchColorStateFlush );
-        }
+    else if ( mTriangleCount > 0 && mColorCount == 0 )
+	{
+		// We have a batch entry without explicit color definition.  This can happen via the other
+		// render methods which do not require color definition.  If so, we have to flush the batch to
+		// prepare for the current run.
+		flush( mpDebugStats->batchColorStateFlush );
     }
     }
 
 
     // Strict order mode?
     // Strict order mode?
@@ -171,28 +160,19 @@ void BatchRender::SubmitTriangles(
         findTextureBatch( texture )->push_back( TriangleRun( TriangleRun::TRIANGLE, triangleCount, mVertexCount ) );
         findTextureBatch( texture )->push_back( TriangleRun( TriangleRun::TRIANGLE, triangleCount, mVertexCount ) );
     }
     }
 
 
-    // Is a color specified?
-    if ( color != NoColor )
-    {
-        // Yes, so add colors.
-        for( U32 n = 0; n < triangleCount; ++n )
-        {
-            mColorBuffer[mColorCount++] = color;
-            mColorBuffer[mColorCount++] = color;
-            mColorBuffer[mColorCount++] = color;
-        }
-    }
-
-    // Add textured vertices.
+    // Load vertex info into batch buffers
     for( U32 n = 0; n < triangleCount; ++n )
     for( U32 n = 0; n < triangleCount; ++n )
     {
     {
-        mVertexBuffer[mVertexCount++]   = *(pVertexArray++);
-        mVertexBuffer[mVertexCount++]   = *(pVertexArray++);
-        mVertexBuffer[mVertexCount++]   = *(pVertexArray++);
+		mColorBuffer[mColorCount++] = *(pColorArray++);
+		mColorBuffer[mColorCount++] = *(pColorArray++);
+		mColorBuffer[mColorCount++] = *(pColorArray++);
+		mVertexBuffer[mVertexCount++] = *(pVertexArray++);
+		mVertexBuffer[mVertexCount++] = *(pVertexArray++);
+		mVertexBuffer[mVertexCount++] = *(pVertexArray++);
+		mTextureBuffer[mTextureCoordCount++] = *(pTextureArray++);
         mTextureBuffer[mTextureCoordCount++] = *(pTextureArray++);
         mTextureBuffer[mTextureCoordCount++] = *(pTextureArray++);
         mTextureBuffer[mTextureCoordCount++] = *(pTextureArray++);
         mTextureBuffer[mTextureCoordCount++] = *(pTextureArray++);
-        mTextureBuffer[mTextureCoordCount++] = *(pTextureArray++);
-    }
+	}
 
 
     // Stats.
     // Stats.
     mpDebugStats->batchTrianglesSubmitted += triangleCount;
     mpDebugStats->batchTrianglesSubmitted += triangleCount;

+ 9 - 7
engine/source/2d/core/BatchRender.h

@@ -44,7 +44,7 @@
 #endif
 #endif
 
 
 #ifndef _COLOR_H_
 #ifndef _COLOR_H_
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #endif
 #endif
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
@@ -116,6 +116,8 @@ public:
     BatchRender();
     BatchRender();
     virtual ~BatchRender();
     virtual ~BatchRender();
 
 
+	static const U32 maxVertexCount = BATCHRENDER_BUFFERSIZE; 
+
     /// Set the strict order mode.
     /// Set the strict order mode.
     inline void setStrictOrderMode( const bool strictOrder, const bool forceFlush = false )
     inline void setStrictOrderMode( const bool strictOrder, const bool forceFlush = false )
     {
     {
@@ -225,12 +227,12 @@ public:
     ///   |\      |\
     ///   |\      |\
     ///   | \     | \
     ///   | \     | \
     ///  0| _\1  3| _\4
     ///  0| _\1  3| _\4
-    void SubmitTriangles(
-            const U32 vertexCount,
-            const Vector2* pVertexArray,
-            const Vector2* pTextureArray,
-            TextureHandle& texture,
-            const ColorF& color = ColorF(-1.0f, -1.0f, -1.0f) );
+	void SubmitTriangles(
+		const U32 vertexCount,
+		const Vector2* pVertexArray,
+		const Vector2* pTextureArray,
+		const ColorF*  pColorArray,
+		TextureHandle& texture);
 
 
     /// Submit a quad for batching.
     /// Submit a quad for batching.
     /// Vertex and textures are indexed as:
     /// Vertex and textures are indexed as:

+ 10 - 0
engine/source/2d/core/CoreMath.h

@@ -170,6 +170,16 @@ bool mPointInRectangle( const Vector2& point, const Vector2& rectMin, const Vect
 /// Calculate line/rectangle intersection.
 /// Calculate line/rectangle intersection.
 bool mLineRectangleIntersect( const Vector2& startPoint, const Vector2& endPoint, const Vector2& rectMin, const Vector2& rectMax, F32* pTime = NULL );
 bool mLineRectangleIntersect( const Vector2& startPoint, const Vector2& endPoint, const Vector2& rectMin, const Vector2& rectMax, F32* pTime = NULL );
 
 
+/// Transform the incoming point 'v' by rotating it about the point (and by the amount) specified in the transform 'T'.
+/// NOTE: b2Math has a method called b2Mul() that is similar, but doesn't take into account the offset in T. So I made this.
+inline b2Vec2 mRotateAboutArbitraryPoint(const b2Transform& T, const b2Vec2& v)
+{
+	float32 x = (T.q.c * (v.x - T.p.x) - T.q.s * (v.y - T.p.y)) + T.p.x;
+	float32 y = (T.q.s * (v.x - T.p.x) + T.q.c * (v.y - T.p.y)) + T.p.y;
+
+	return b2Vec2(x, y);
+}
+
 } // Namespace CoreMath.
 } // Namespace CoreMath.
 
 
 #endif // _CORE_UTILITY_H_
 #endif // _CORE_UTILITY_H_

+ 66 - 6
engine/source/2d/core/ImageFrameProviderCore.cc

@@ -224,7 +224,7 @@ void ImageFrameProviderCore::render(
     const Vector2& vertexPos1,
     const Vector2& vertexPos1,
     const Vector2& vertexPos2,
     const Vector2& vertexPos2,
     const Vector2& vertexPos3,
     const Vector2& vertexPos3,
-    BatchRender* pBatchRenderer ) const
+	BatchRender* pBatchRenderer ) const
 {
 {
     // Finish if we can't render.
     // Finish if we can't render.
     if ( !validRender() )
     if ( !validRender() )
@@ -240,21 +240,81 @@ void ImageFrameProviderCore::render(
     const Vector2& texLower = texelArea.mTexelLower;
     const Vector2& texLower = texelArea.mTexelLower;
     const Vector2& texUpper = texelArea.mTexelUpper;
     const Vector2& texUpper = texelArea.mTexelUpper;
     
     
+	Vector2 texturePos0, texturePos1, texturePos2, texturePos3;
+
+	texturePos0 = Vector2(texLower.x, texUpper.y);
+	texturePos1 = Vector2(texUpper.x, texUpper.y);
+	texturePos2 = Vector2(texUpper.x, texLower.y);
+	texturePos3 = Vector2(texLower.x, texLower.y);
+
     // Submit batched quad.
     // Submit batched quad.
     pBatchRenderer->SubmitQuad(
     pBatchRenderer->SubmitQuad(
         vertexPos0,
         vertexPos0,
         vertexPos1,
         vertexPos1,
         vertexPos2,
         vertexPos2,
         vertexPos3,
         vertexPos3,
-        Vector2( texLower.x, texUpper.y ),
-        Vector2( texUpper.x, texUpper.y ),
-        Vector2( texUpper.x, texLower.y ),
-        Vector2( texLower.x, texLower.y ),
-        getProviderTexture() );
+		texturePos0,
+		texturePos1,
+		texturePos2,
+		texturePos3,
+		getProviderTexture() );
+}
+
+//------------------------------------------------------------------------------
+
+void ImageFrameProviderCore::render(
+	const bool flipX,
+	const bool flipY,
+	const Vector2& vertexPos0,
+	const Vector2& vertexPos1,
+	const Vector2& vertexPos2,
+	const Vector2& vertexPos3,
+	const Vector2& uvPos0,
+	const Vector2& uvPos1,
+	const Vector2& uvPos2,
+	const Vector2& uvPos3,
+	BatchRender* pBatchRenderer) const
+{
+	// Finish if we can't render.
+	if (!validRender())
+		return;
+
+	// Submit batched quad.
+	pBatchRenderer->SubmitQuad(
+		vertexPos0,
+		vertexPos1,
+		vertexPos2,
+		vertexPos3,
+		uvPos0,
+		uvPos1,
+		uvPos2,
+		uvPos3,
+		getProviderTexture());
 }
 }
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 
+void ImageFrameProviderCore::render(
+		const U32 vertexCount,
+		const Vector2 *vertexArray,
+		const Vector2 *textureArray,
+		const ColorF *colorArray,
+		BatchRender* pBatchRenderer) const
+{
+	// Finish if we can't render.
+	if (!validRender() || !vertexCount)
+		return;
+
+	// Submit mesh list
+	pBatchRenderer->SubmitTriangles(
+		vertexCount,
+		vertexArray,
+		textureArray,
+		colorArray,
+		getProviderTexture());
+}
+
+//------------------------------------------------------------------------------
 void ImageFrameProviderCore::renderGui( GuiControl& owner, Point2I offset, const RectI &updateRect ) const
 void ImageFrameProviderCore::renderGui( GuiControl& owner, Point2I offset, const RectI &updateRect ) const
 {
 {
     // Validate frame provider.
     // Validate frame provider.

+ 24 - 2
engine/source/2d/core/ImageFrameProviderCore.h

@@ -110,7 +110,29 @@ public:
         const Vector2& vertexPos3,
         const Vector2& vertexPos3,
         BatchRender* pBatchRenderer ) const;
         BatchRender* pBatchRenderer ) const;
 
 
-    void renderGui( GuiControl& owner, Point2I offset, const RectI &updateRect ) const;
+	// Use uv coordinates explicitly given.
+	virtual void render(
+		const bool flipX,
+		const bool flipY,
+		const Vector2& vertexPos0,
+		const Vector2& vertexPos1,
+		const Vector2& vertexPos2,
+		const Vector2& vertexPos3,
+		const Vector2& uvPos0,
+		const Vector2& uvPos1,
+		const Vector2& uvPos2,
+		const Vector2& uvPos3,
+		BatchRender* pBatchRenderer) const;
+
+	// Render the given list of vertex information directly.
+	void ImageFrameProviderCore::render(
+		const U32 vertexCount,
+		const Vector2 *vertexArray,
+		const Vector2 *textureArray,
+		const ColorF *colorArray,
+		BatchRender* pBatchRenderer) const;
+
+	void renderGui( GuiControl& owner, Point2I offset, const RectI &updateRect ) const;
 
 
     /// Static-Image Frame.
     /// Static-Image Frame.
     inline bool setImage( const char* pImageAssetId ) { return setImage( pImageAssetId, mImageFrame ); }
     inline bool setImage( const char* pImageAssetId ) { return setImage( pImageAssetId, mImageFrame ); }
@@ -121,7 +143,7 @@ public:
     inline U32 getImageFrame( void ) const { return mImageFrame; }
     inline U32 getImageFrame( void ) const { return mImageFrame; }
     virtual bool setNamedImageFrame( const char* frame );
     virtual bool setNamedImageFrame( const char* frame );
     inline StringTableEntry getNamedImageFrame( void ) const { return mNamedImageFrame; }
     inline StringTableEntry getNamedImageFrame( void ) const { return mNamedImageFrame; }
-
+	
     /// Animated-Image Frame.
     /// Animated-Image Frame.
     virtual bool setAnimation( const char* pAnimationAssetId );
     virtual bool setAnimation( const char* pAnimationAssetId );
     inline StringTableEntry getAnimation( void ) const { return mpAnimationAsset->getAssetId(); }
     inline StringTableEntry getAnimation( void ) const { return mpAnimationAsset->getAssetId(); }

+ 32 - 24
engine/source/2d/core/SpriteBatch.cc

@@ -54,7 +54,9 @@ SpriteBatch::SpriteBatch() :
     mBatchTransformId = 0;
     mBatchTransformId = 0;
 
 
     // Reset local extents.
     // Reset local extents.
-    mLocalExtents.SetZero();
+	mLocalAABB.lowerBound.SetZero();
+	mLocalAABB.upperBound.SetZero();
+	mLocalExtents.SetZero();
     mLocalExtentsDirty = true;
     mLocalExtentsDirty = true;
 }
 }
 
 
@@ -96,9 +98,6 @@ void SpriteBatch::prepareRender( SceneRenderObject* pSceneRenderObject, const Sc
     // Set the sort mode.
     // Set the sort mode.
     pSceneRenderQueue->setSortMode( getBatchSortMode() );
     pSceneRenderQueue->setSortMode( getBatchSortMode() );
 
 
-    // Calculate local AABB.
-    const b2AABB localAABB = calculateLocalAABB( pSceneRenderState->mRenderAABB );
-
     // Do we have a sprite batch query?
     // Do we have a sprite batch query?
     if ( mpSpriteBatchQuery != NULL )
     if ( mpSpriteBatchQuery != NULL )
     {
     {
@@ -108,8 +107,11 @@ void SpriteBatch::prepareRender( SceneRenderObject* pSceneRenderObject, const Sc
         // Yes, so fetch sprite batch query and clear results.
         // Yes, so fetch sprite batch query and clear results.
         SpriteBatchQuery* pSpriteBatchQuery = getSpriteBatchQuery( true );
         SpriteBatchQuery* pSpriteBatchQuery = getSpriteBatchQuery( true );
 
 
-        // Perform query.
-        pSpriteBatchQuery->queryArea( localAABB, false );
+		// Calculate local AABB.
+		const b2AABB localAABB = calculateLocalAABB(pSceneRenderState->mRenderAABB);
+
+		// Perform query.
+		pSpriteBatchQuery->queryArea( localAABB, false );
 
 
         // Debug Profiling.
         // Debug Profiling.
         PROFILE_END(); // SpriteBatch_PrepareRenderQuery
         PROFILE_END(); // SpriteBatch_PrepareRenderQuery
@@ -1262,12 +1264,22 @@ void SpriteBatch::setBatchTransform( const b2Transform& batchTransform )
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 
 
-void SpriteBatch::updateLocalExtents( void )
+void SpriteBatch::updateLocalExtents(const b2AABB *precalculatedLocalAABB)
 {
 {
     // Debug Profiling.
     // Debug Profiling.
     PROFILE_SCOPE(SpriteBatch_UpdateLocalExtents);
     PROFILE_SCOPE(SpriteBatch_UpdateLocalExtents);
 
 
-    // Finish if the local extents are not dirty.
+	if (precalculatedLocalAABB && precalculatedLocalAABB->IsValid()) {
+		// We are being given our AABB.  Nothing further is needed.
+		mLocalAABB = *precalculatedLocalAABB;
+		mLocalExtents = mLocalAABB.upperBound - mLocalAABB.lowerBound;
+
+		mLocalExtentsDirty = false;
+
+		return;
+	}
+
+	// Finish if the local extents are not dirty.
     if ( !mLocalExtentsDirty )
     if ( !mLocalExtentsDirty )
         return;
         return;
 
 
@@ -1287,26 +1299,22 @@ void SpriteBatch::updateLocalExtents( void )
     typeSpriteBatchHash::iterator spriteItr = mSprites.begin();
     typeSpriteBatchHash::iterator spriteItr = mSprites.begin();
 
 
     // Set render AABB to this sprite.
     // Set render AABB to this sprite.
-    b2AABB localAABB = spriteItr->value->getLocalAABB();
+    mLocalAABB = spriteItr->value->getLocalAABB();
 
 
-    // Combine with the rest of the sprites.
+	// Combine with the rest of the sprites.
     for( ; spriteItr != mSprites.end(); ++spriteItr )
     for( ; spriteItr != mSprites.end(); ++spriteItr )
     {
     {
-        localAABB.Combine( spriteItr->value->getLocalAABB() );
+		mLocalAABB.Combine( spriteItr->value->getLocalAABB() );
     }
     }
-
-    // Fetch local render extents.
-    const b2Vec2& localLowerExtent = localAABB.lowerBound;
-    const b2Vec2& localUpperExtent = localAABB.upperBound;
-
-    // Calculate maximum extents.
-    const F32 lowerExtentX = mFabs(localLowerExtent.x);
-    const F32 lowerExtentY = mFabs(localLowerExtent.y);
-    const F32 upperExtentX = mFabs(localUpperExtent.x);
-    const F32 upperExtentY = mFabs(localUpperExtent.y);
-
-    // Calculate local extents.
-    mLocalExtents.Set( mFabs(lowerExtentX > upperExtentX ? lowerExtentX : upperExtentX) * 2.0f, mFabs(lowerExtentY > upperExtentY ? lowerExtentY : upperExtentY) * 2.0f );
+	
+	float xSize = mLocalAABB.upperBound.x > mLocalAABB.lowerBound.x
+		? mLocalAABB.upperBound.x - mLocalAABB.lowerBound.x
+		: mLocalAABB.lowerBound.x - mLocalAABB.upperBound.x;
+	float ySize = mLocalAABB.upperBound.y > mLocalAABB.lowerBound.y
+		? mLocalAABB.upperBound.y - mLocalAABB.lowerBound.y
+		: mLocalAABB.lowerBound.y - mLocalAABB.upperBound.y;
+
+	mLocalExtents.Set(xSize, ySize);
 }
 }
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------

+ 3 - 1
engine/source/2d/core/SpriteBatch.h

@@ -65,6 +65,7 @@ private:
     bool                            mBatchTransformDirty;
     bool                            mBatchTransformDirty;
     U32                             mBatchTransformId;
     U32                             mBatchTransformId;
 
 
+	b2AABB							mLocalAABB;
     Vector2                         mLocalExtents;
     Vector2                         mLocalExtents;
     bool                            mLocalExtentsDirty;
     bool                            mLocalExtentsDirty;
 
 
@@ -86,6 +87,7 @@ public:
     inline void setLocalExtentsDirty( void ) { mLocalExtentsDirty = true; }
     inline void setLocalExtentsDirty( void ) { mLocalExtentsDirty = true; }
     inline bool getLocalExtentsDirty( void ) const { return mLocalExtentsDirty; }
     inline bool getLocalExtentsDirty( void ) const { return mLocalExtentsDirty; }
     inline const Vector2& getLocalExtents( void ) { if ( getLocalExtentsDirty() ) updateLocalExtents(); return mLocalExtents; }
     inline const Vector2& getLocalExtents( void ) { if ( getLocalExtentsDirty() ) updateLocalExtents(); return mLocalExtents; }
+	inline const b2AABB& getLocalAABB(void) { if (getLocalExtentsDirty()) updateLocalExtents(); return mLocalAABB; }
 
 
     void createQueryProxy( SpriteBatchItem* pSpriteBatchItem );
     void createQueryProxy( SpriteBatchItem* pSpriteBatchItem );
     void destroyQueryProxy( SpriteBatchItem* pSpriteBatchItem );
     void destroyQueryProxy( SpriteBatchItem* pSpriteBatchItem );
@@ -201,7 +203,7 @@ protected:
     void integrateSprites(const F32 totalTime, const F32 elapsedTime, DebugStats* pDebugStats);
     void integrateSprites(const F32 totalTime, const F32 elapsedTime, DebugStats* pDebugStats);
 
 
     void setBatchTransform( const b2Transform& batchTransform );
     void setBatchTransform( const b2Transform& batchTransform );
-    void updateLocalExtents( void );
+    void updateLocalExtents(const b2AABB *precalculatedLocalAABB = NULL);
 
 
     void createSpriteBatchQuery( void );
     void createSpriteBatchQuery( void );
     void destroySpriteBatchQuery( void );
     void destroySpriteBatchQuery( void );

+ 64 - 23
engine/source/2d/core/SpriteBatchItem.cc

@@ -97,6 +97,7 @@ void SpriteBatchItem::resetState( void )
 
 
     mVisible = true;
     mVisible = true;
     mExplicitMode = false;
     mExplicitMode = false;
+	mTriangleRun = false;
 
 
     mLocalPosition.SetZero();
     mLocalPosition.SetZero();
     for (U32 i = 0; i < 4; i++)
     for (U32 i = 0; i < 4; i++)
@@ -222,24 +223,64 @@ void SpriteBatchItem::render( BatchRender* pBatchRenderer, const SceneRenderRequ
     pBatchRenderer->setAlphaTestMode( pSceneRenderRequest );
     pBatchRenderer->setAlphaTestMode( pSceneRenderRequest );
 
 
     // Render.
     // Render.
-    Parent::render( mFlipX, mFlipY,
-                    mRenderOOBB[0],
-                    mRenderOOBB[1],
-                    mRenderOOBB[2],
-                    mRenderOOBB[3],
-                    pBatchRenderer );
+	if (mTriangleRun) {
+		Parent::render(
+			mDrawData.vertexCount,
+			mDrawData.vertexArray.data(),
+			mDrawData.textureArray.data(),
+			mDrawData.colorArray.data(),
+			pBatchRenderer
+		);
+	}
+	else if (mExplicitMode) {
+		Parent::render(mFlipX, mFlipY,
+			mRenderOOBB[0],
+			mRenderOOBB[1],
+			mRenderOOBB[2],
+			mRenderOOBB[3],
+			mExplicitUVs[0],
+			mExplicitUVs[1],
+			mExplicitUVs[2],
+			mExplicitUVs[3],
+			pBatchRenderer);
+	}
+	else {
+		Parent::render(mFlipX, mFlipY,
+			mRenderOOBB[0],
+			mRenderOOBB[1],
+			mRenderOOBB[2],
+			mRenderOOBB[3],
+			pBatchRenderer);
+	}
 }
 }
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 
 
-void SpriteBatchItem::setExplicitVertices( const Vector2* explicitVertices )
+// When something else (like an animation runtime) is controlling the vertices and uv's
+// just past them through to the render stage.
+void SpriteBatchItem::setExplicitVertices( const F32* vertices, const F32* uvs)
 {
 {
-    mExplicitMode = true;
-
-    mExplicitVerts[0] = explicitVertices[0];
-    mExplicitVerts[1] = explicitVertices[1];
-    mExplicitVerts[2] = explicitVertices[2];
-    mExplicitVerts[3] = explicitVertices[3];
+	mExplicitMode = true;
+
+	mExplicitVerts[0].x = vertices[0];
+	mExplicitVerts[0].y = vertices[1];
+	mExplicitVerts[1].x = vertices[2];
+	mExplicitVerts[1].y = vertices[3];
+	mExplicitVerts[2].x = vertices[4];
+	mExplicitVerts[2].y = vertices[5];
+	mExplicitVerts[3].x = vertices[6];
+	mExplicitVerts[3].y = vertices[7];
+
+	if (uvs) {
+		mExplicitUVs[0].x = uvs[0];
+		mExplicitUVs[0].y = uvs[1];
+		mExplicitUVs[1].x = uvs[2];
+		mExplicitUVs[1].y = uvs[3];
+		mExplicitUVs[2].x = uvs[4];
+		mExplicitUVs[2].y = uvs[5];
+		mExplicitUVs[3].x = uvs[6];
+		mExplicitUVs[3].y = uvs[7];
+	}
 }
 }
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
@@ -256,19 +297,19 @@ void SpriteBatchItem::updateLocalTransform( void )
     if ( !mLocalTransformDirty )
     if ( !mLocalTransformDirty )
         return;
         return;
 
 
-    // Set local transform.
-    b2Transform localTransform;
-    localTransform.p = mLocalPosition;
-    localTransform.q.Set( mLocalAngle );
-
-    // Calculate half size.
-    const F32 halfWidth = mSize.x * 0.5f;
-    const F32 halfHeight = mSize.y * 0.5f;
+	// Set local transform.
+	b2Transform localTransform;
+	localTransform.p = mLocalPosition;
+	localTransform.q.Set(mLocalAngle);
 
 
-    // Set local size vertices.
+	// Set local size vertices.
     if (!mExplicitMode)
     if (!mExplicitMode)
     {
     {
-        mLocalOOBB[0].Set( -halfWidth, -halfHeight );
+		// Calculate half size.
+		const F32 halfWidth = mSize.x * 0.5f;
+		const F32 halfHeight = mSize.y * 0.5f;
+
+		mLocalOOBB[0].Set( -halfWidth, -halfHeight );
         mLocalOOBB[1].Set( +halfWidth, -halfHeight );
         mLocalOOBB[1].Set( +halfWidth, -halfHeight );
         mLocalOOBB[2].Set( +halfWidth, +halfHeight );
         mLocalOOBB[2].Set( +halfWidth, +halfHeight );
         mLocalOOBB[3].Set( -halfWidth, +halfHeight );
         mLocalOOBB[3].Set( -halfWidth, +halfHeight );

+ 31 - 4
engine/source/2d/core/SpriteBatchItem.h

@@ -45,7 +45,25 @@ class SpriteBatchItem : public ImageFrameProvider
     typedef ImageFrameProvider Parent;
     typedef ImageFrameProvider Parent;
 
 
 public:
 public:
-    // Represents a logical position.
+	
+	// Holds items required by call to SubmitTriangles.  Currently, this is only used when
+	// mTriangleRun is true due to processing of Spine's mesh attachments.
+	typedef struct
+	{
+		U32 vertexCount;
+		vector<Vector2> vertexArray;
+		vector<Vector2> textureArray;
+		vector<ColorF>  colorArray;
+
+		inline void size(U32 newSize) {
+			vertexCount = newSize;
+			vertexArray.resize(newSize);
+			textureArray.resize(newSize);
+			colorArray.resize(newSize);
+		}
+	} drawData;
+
+	// Represents a logical position.
     struct LogicalPosition : public IFactoryObjectReset
     struct LogicalPosition : public IFactoryObjectReset
     {
     {
         const static S32 MAX_ARGUMENTS = 6;
         const static S32 MAX_ARGUMENTS = 6;
@@ -199,7 +217,12 @@ protected:
 
 
     Vector2             mLocalPosition;
     Vector2             mLocalPosition;
     Vector2             mExplicitVerts[4];
     Vector2             mExplicitVerts[4];
-    F32                 mLocalAngle;
+	Vector2             mExplicitUVs[4];
+
+	bool				mTriangleRun;
+	drawData			mDrawData;
+
+	F32                 mLocalAngle;
     Vector2             mSize;
     Vector2             mSize;
     F32                 mDepth;
     F32                 mDepth;
     bool                mFlipX;
     bool                mFlipX;
@@ -249,10 +272,14 @@ public:
     inline void setExplicitMode( const bool explicitMode ) { mExplicitMode = explicitMode; }
     inline void setExplicitMode( const bool explicitMode ) { mExplicitMode = explicitMode; }
     inline bool getExplicitMode( void ) const { return mExplicitMode; }
     inline bool getExplicitMode( void ) const { return mExplicitMode; }
 
 
-    inline void setLocalPosition( const Vector2& localPosition ) { mLocalPosition = localPosition; mLocalTransformDirty = true; }
+	inline void setTriangleRun(const bool usesTriangles) { mTriangleRun = usesTriangles; }
+	inline bool getTriangleRun(void) const { return mTriangleRun; }
+	inline drawData *getDrawData(void) { return &mDrawData; }
+
+	inline void setLocalPosition( const Vector2& localPosition ) { mLocalPosition = localPosition; mLocalTransformDirty = true; }
     inline Vector2 getLocalPosition( void ) const { return mLocalPosition; }
     inline Vector2 getLocalPosition( void ) const { return mLocalPosition; }
 
 
-    void setExplicitVertices( const Vector2* explicitVertices );
+    void setExplicitVertices( const F32* vertices, const F32* uvs = 0 );
 
 
     inline void setLocalAngle( const F32 localAngle ) { mLocalAngle = localAngle; mLocalTransformDirty = true; }
     inline void setLocalAngle( const F32 localAngle ) { mLocalAngle = localAngle; mLocalTransformDirty = true; }
     inline F32 getLocalAngle( void ) const { return mLocalAngle; }
     inline F32 getLocalAngle( void ) const { return mLocalAngle; }

+ 1 - 1
engine/source/2d/scene/DebugDraw.h

@@ -32,7 +32,7 @@
 #endif
 #endif
 
 
 #ifndef _COLOR_H_
 #ifndef _COLOR_H_
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #endif
 #endif
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------

+ 1 - 1
engine/source/2d/scene/SceneRenderRequest.h

@@ -32,7 +32,7 @@
 #endif
 #endif
 
 
 #ifndef _COLOR_H_
 #ifndef _COLOR_H_
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #endif
 #endif
 
 
 // Debug Profiling.
 // Debug Profiling.

+ 1 - 1
engine/source/2d/sceneobject/SceneObject.cc

@@ -29,7 +29,7 @@
 #endif
 #endif
 
 
 #ifndef _COLOR_H_
 #ifndef _COLOR_H_
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #endif
 #endif
 
 
 #ifndef _BITSTREAM_H_
 #ifndef _BITSTREAM_H_

+ 0 - 472
engine/source/2d/sceneobject/SkeletonObject.cc

@@ -1,472 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2013 GarageGames, LLC
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//-----------------------------------------------------------------------------
-
-#ifndef _SKELETON_OBJECT_H_
-#include "2d/sceneobject/SkeletonObject.h"
-#endif
-
-#include "spine/extension.h"
-
-// Script bindings.
-#include "2d/sceneobject/SkeletonObject_ScriptBinding.h"
-
-//-----------------------------------------------------------------------------
-
-/*void _spAtlasPage_createTexture (spAtlasPage* self, const char* path) {
-}
-void _spAtlasPage_disposeTexture (spAtlasPage* self) {
-}
-
-char* _spUtil_readFile (const char* path, int* length) {
-    return _readFile(path, length);
-}*/
-
-//-----------------------------------------------------------------------------
-
-IMPLEMENT_CONOBJECT(SkeletonObject);
-
-//------------------------------------------------------------------------------
-
-SkeletonObject::SkeletonObject() :  mPreTickTime( 0.0f ),
-                                    mPostTickTime( 0.0f ),
-                                    mTimeScale(1),
-                                    mLastFrameTime(0),
-                                    mTotalAnimationTime(0),
-                                    mSkeleton(NULL),
-                                    mState(NULL),
-                                    mAnimationCycle(false),
-                                    mAnimationFinished(true),
-                                    mAnimationDuration(0.0),
-                                    mFlipX(false),
-                                    mFlipY(false)
-{
-    mCurrentAnimation = StringTable->insert("");
-    mSkeletonScale.SetZero();
-    mSkeletonOffset.SetZero();
-}
-
-//------------------------------------------------------------------------------
-
-SkeletonObject::~SkeletonObject()
-{
-    if (mSkeleton) {
-        spSkeleton_dispose(mSkeleton);
-        mSkeleton = NULL;
-    }
-    if (mState) {
-        spAnimationState_dispose(mState);
-        mState = NULL;
-    }
-}
-
-//------------------------------------------------------------------------------
-
-void SkeletonObject::initPersistFields()
-{
-    // Call parent.
-    Parent::initPersistFields();
-    
-    addProtectedField("Asset", TypeSkeletonAssetPtr, Offset(mSkeletonAsset, SkeletonObject), &setSkeletonAsset, &getSkeletonAsset, &writeSkeletonAsset, "The skeleton asset ID used for the skeleton.");
-    addProtectedField("AnimationName", TypeString, Offset(mCurrentAnimation, SkeletonObject), &setAnimationName, &getAnimationName, &writeAnimationName, "The animation name to play.");
-    addProtectedField("Skin", TypeString, Offset(mCurrentSkin, SkeletonObject), &setCurrentSkin, &getCurrentSkin, &writeCurrentSkin, "The skin to use.");
-    addProtectedField("RootBoneScale", TypeVector2, 0, &setRootBoneScale, &getRootBoneScale, &writeRootBoneScale, "Scaling of the skeleton's root bone");
-    addProtectedField("RootBoneOffset", TypeVector2, 0, &setRootBoneOffset, &getRootBoneOffset, &writeRootBoneOffset, "X/Y offset of the skeleton's root bone");
-    addProtectedField("AnimationCycle", TypeBool, Offset(mAnimationCycle, SkeletonObject), &setAnimationCycle, &defaultProtectedGetFn, &writeAnimationCycle, "Whether the animation loops or not");
-    addField("FlipX", TypeBool, Offset(mFlipX, SkeletonObject), &writeFlipX, "");
-    addField("FlipY", TypeBool, Offset(mFlipY, SkeletonObject), &writeFlipY, "");
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonObject::preIntegrate( const F32 totalTime, const F32 elapsedTime, DebugStats* pDebugStats )
-{
-    // Note tick times.
-    mPreTickTime = mPostTickTime;
-    mPostTickTime = totalTime;
-    
-    // Update composition at pre-tick time.
-    updateComposition( mPreTickTime );
-    
-    // Are the spatials dirty?
-    if ( getSpatialDirty() )
-    {
-        // Yes, so update the world transform.
-        setBatchTransform( getRenderTransform() );
-    }
-    
-    // Are the render extents dirty?
-    if ( getLocalExtentsDirty() )
-    {
-        // Yes, so set size as local extents.
-        setSize( getLocalExtents() );
-    }
-    
-    // Call parent.
-    Parent::preIntegrate( totalTime, elapsedTime, pDebugStats );
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonObject::integrateObject( const F32 totalTime, const F32 elapsedTime, DebugStats* pDebugStats )
-{
-    // Call Parent.
-    Parent::integrateObject( totalTime, elapsedTime, pDebugStats );
-    
-    // Finish if the spatials are NOT dirty.
-    if ( !getSpatialDirty() )
-        return;
-    
-    // Update the batch world transform.
-    setBatchTransform( getRenderTransform() );
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonObject::interpolateObject( const F32 timeDelta )
-{
-    // Call parent.
-    Parent::interpolateObject( timeDelta );
-    
-    // Update composition time (interpolated).
-    updateComposition( (timeDelta * mPreTickTime) + ((1.0f-timeDelta) * mPostTickTime) );
-    
-    // Finish if the spatials are NOT dirty.
-    if ( !getSpatialDirty() )
-        return;
-    
-    // Update the batch world transform.
-    setBatchTransform( getRenderTransform() );
-}
-
-//------------------------------------------------------------------------------
-
-void SkeletonObject::copyTo(SimObject* object)
-{
-    // Call to parent.
-    Parent::copyTo(object);
-    
-    // Fetch object.
-    SkeletonObject* pComposite = dynamic_cast<SkeletonObject*>(object);
-    
-    // Sanity!
-    AssertFatal(pComposite != NULL, "SkeletonObject::copyTo() - Object is not the correct type.");
-    
-    // Copy state.
-    pComposite->setSkeletonAsset( getSkeletonAsset() );
-    pComposite->setAnimationName( getAnimationName(), getAnimationCycle() );
-    pComposite->setCurrentSkin( getCurrentSkin() );
-    pComposite->setRootBoneScale( getRootBoneScale() );
-    pComposite->setRootBoneOffset( getRootBoneOffset() );
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonObject::scenePrepareRender( const SceneRenderState* pSceneRenderState, SceneRenderQueue* pSceneRenderQueue )
-{
-    // Prepare render.
-    SpriteBatch::prepareRender( this, pSceneRenderState, pSceneRenderQueue );
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonObject::sceneRender( const SceneRenderState* pSceneRenderState, const SceneRenderRequest* pSceneRenderRequest, BatchRender* pBatchRenderer )
-{
-    // Render.
-    SpriteBatch::render( pSceneRenderState, pSceneRenderRequest, pBatchRenderer );
-    
-}
-
-//-----------------------------------------------------------------------------
-
-bool SkeletonObject::setSkeletonAsset( const char* pSkeletonAssetId )
-{
-    // Sanity!
-    AssertFatal( pSkeletonAssetId != NULL, "Cannot use a NULL asset Id." );
-    
-    // Fetch the asset Id.
-    mSkeletonAsset = pSkeletonAssetId;
-    
-    // Generate composition.
-    generateComposition();
-    
-    return true;
-}
-
-//-----------------------------------------------------------------------------
-
-bool SkeletonObject::setAnimationName( const char* pAnimation, const bool isLooping )
-{
-    // Make sure an asset was loaded.
-    if (mSkeletonAsset.isNull())
-        return false;
-    
-    // Set the animation.
-    mCurrentAnimation = StringTable->insert(pAnimation);
-    
-    mAnimationCycle = isLooping;
-    
-    // Generate composition.
-    generateComposition();
-    
-    return true;
-}
-
-//-----------------------------------------------------------------------------
-
-bool SkeletonObject::setMix( const char* pFromAnimation, const char* pToAnimation, float time)
-{
-    if (mSkeletonAsset.isNull())
-    {
-        Con::warnf("SkeletonObject::setMix() - Cannot mix. No asset assigned");
-        return false;
-    }
-    
-    // Check for valid animation state data
-    AssertFatal( mSkeletonAsset->mStateData != NULL, "SkeletonObject::setMix() - Animation state data invalid" );
-    
-    // Check to see if the "from animation" is valid
-    spAnimation* from = spSkeletonData_findAnimation(mSkeleton->data, pFromAnimation);
-    
-    if (!from)
-    {
-        Con::warnf("SkeletonObject::setMix() - Animation %s does not exist.", pFromAnimation);
-        return false;
-    }
-    
-    // Check to see if the "to animation" is valid
-    spAnimation* to = spSkeletonData_findAnimation(mSkeleton->data, pToAnimation);
-    
-    if (!to)
-    {
-        Con::warnf("SkeletonObject::setMix() - Animation %s does not exist.", pToAnimation);
-        return false;
-    }
-    
-    // Check to see if a valid mix time was passsed
-    if (time < 0.0f)
-    {
-        Con::warnf("SkeletonObject::setMix() - Invalid time set, %f", time);
-        return false;
-    }
-    
-    spAnimationStateData_setMixByName(mSkeletonAsset->mStateData, pFromAnimation, pToAnimation, time);
-    return true;
-}
-
-//-----------------------------------------------------------------------------
-
-bool SkeletonObject::setCurrentSkin( const char* pSkin )
-{
-    if (mSkeletonAsset.isNull() || !mSkeleton)
-    {
-        Con::errorf("SkeletonObject::setCurrentSkin() - Skeleton Asset was null or skeleton was not built");
-        return false;
-    }
-    
-    S32 result = spSkeleton_setSkinByName(mSkeleton, pSkin);
-    
-    if (result)
-    {
-        spSkeleton_setSlotsToSetupPose(mSkeleton);
-        return true;
-    }
-    else
-    {
-        Con::errorf("SkeletonObject::setCurrentSkin() - Skin %s not found", pSkin);
-        return false;
-    }
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonObject::setRootBoneScale(const Vector2& scale)
-{
-    mSkeletonScale = scale;
-    
-    if (!mSkeleton)
-        return;
-    
-    if (mSkeletonScale.notZero())
-    {
-        spBone* rootBone = mSkeleton->root;
-        rootBone->scaleX = mSkeletonScale.x;
-        rootBone->scaleY = mSkeletonScale.y;
-    }
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonObject::setRootBoneOffset(const Vector2& offset)
-{
-    mSkeletonOffset = offset;
-    
-    if (!mSkeleton)
-        return;
-    
-    if (mSkeletonOffset.notZero())
-    {
-        spBone* rootBone = mSkeleton->root;
-        rootBone->x = mSkeletonOffset.x;
-        rootBone->y = mSkeletonOffset.y;
-    }
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonObject::generateComposition( void )
-{
-    // Clear existing visualization
-    clearSprites();
-    mSkeletonSprites.clear();
-    
-    // Finish if skeleton asset isn't available.
-    if ( mSkeletonAsset.isNull() )
-        return;
-    
-    // Generate visualization.
-    if ((*mSkeletonAsset).mImageAsset.isNull())
-    {
-        Con::warnf( "SkeletonObject::generateComposition() - Image asset was NULL, so nothing can be added to the composition.");
-        return;
-    }
-    
-    if (!mSkeleton)
-        mSkeleton = spSkeleton_create(mSkeletonAsset->mSkeletonData);
-    
-    if (!mState)
-        mState = spAnimationState_create(mSkeletonAsset->mStateData);
-    
-    if (mCurrentAnimation != StringTable->EmptyString)
-    {
-        spAnimationState_setAnimationByName(mState, 0, mCurrentAnimation, mAnimationCycle);
-        mAnimationDuration = mState->tracks[0]->animation->duration;
-        mAnimationFinished = false;
-        mTotalAnimationTime = mLastFrameTime + mAnimationDuration;
-    }
-    
-    if (mSkeletonScale.notZero())
-    {
-        spBone* rootBone = mSkeleton->root;
-        rootBone->scaleX = mSkeletonScale.x;
-        rootBone->scaleY = mSkeletonScale.y;
-    }
-    
-    if (mSkeletonOffset.notZero())
-    {
-        spBone* rootBone = mSkeleton->root;
-        rootBone->x = mSkeletonOffset.x;
-        rootBone->y = mSkeletonOffset.y;
-    }
-}
-
-//-----------------------------------------------------------------------------
-
-void SkeletonObject::updateComposition( const F32 time )
-{
-    // Update position/orientation/state of visualization
-    float delta = (time - mLastFrameTime) * mTimeScale;
-    mLastFrameTime = time;
-    
-    spSkeleton_update(mSkeleton, delta);
-    
-    if (!mAnimationFinished)
-    {
-        spAnimationState_update(mState, delta);
-        spAnimationState_apply(mState, mSkeleton);
-    }
-    
-    spSkeleton_updateWorldTransform(mSkeleton);
-    
-    // Get the ImageAsset used by the sprites
-    StringTableEntry assetId = (*mSkeletonAsset).mImageAsset.getAssetId();
-    
-    clearSprites();
-    
-    Vector2 vertices[4];
-    
-    F32 vertexPositions[8];
-    for (int i = 0; i < mSkeleton->slotCount; ++i)
-    {
-        spSlot* slot = mSkeleton->slots[i];
-        spAttachment* attachment = slot->attachment;
-        
-        if (!attachment || attachment->type != ATTACHMENT_REGION)
-            continue;
-        
-        spRegionAttachment* regionAttachment = (spRegionAttachment*)attachment;
-        spRegionAttachment_computeWorldVertices(regionAttachment, slot->skeleton->x, slot->skeleton->y, slot->bone, vertexPositions);
-        
-        SpriteBatchItem* pSprite = SpriteBatch::createSprite();
-
-        pSprite->setDepth(mSceneLayerDepth);
-        
-        pSprite->setSrcBlendFactor(mSrcBlendFactor);
-        pSprite->setDstBlendFactor(mDstBlendFactor);
-        
-        mSkeleton->r = mBlendColor.red;
-        mSkeleton->g = mBlendColor.green;
-        mSkeleton->b = mBlendColor.blue;
-        mSkeleton->a = mBlendColor.alpha;
-        
-        F32 alpha = mSkeleton->a * slot->a;
-        pSprite->setBlendColor(ColorF(
-            mSkeleton->r * slot->r * alpha,
-            mSkeleton->g * slot->g * alpha,
-            mSkeleton->b * slot->b * alpha,
-            alpha
-        ));
-        
-        mSkeleton->flipX = getFlipX();
-        mSkeleton->flipY = getFlipY();
-        
-        vertices[0].x = vertexPositions[VERTEX_X1];
-        vertices[0].y = vertexPositions[VERTEX_Y1];
-        vertices[1].x = vertexPositions[VERTEX_X4];
-        vertices[1].y = vertexPositions[VERTEX_Y4];
-        vertices[2].x = vertexPositions[VERTEX_X3];
-        vertices[2].y = vertexPositions[VERTEX_Y3];
-        vertices[3].x = vertexPositions[VERTEX_X2];
-        vertices[3].y = vertexPositions[VERTEX_Y2];
-        pSprite->setExplicitVertices(vertices);
-        
-        pSprite->setImage(assetId);
-        pSprite->setNamedImageFrame(attachment->name);
-    }
-    
-    if (mLastFrameTime >= mTotalAnimationTime)
-        mAnimationFinished = true;
-    
-    if (mAnimationFinished && !mAnimationCycle)
-    {
-        onAnimationFinished();
-    }
-    else
-    {
-        mAnimationFinished = false;
-    }
-}
-
-void SkeletonObject::onAnimationFinished()
-{
-    // Do script callback.
-    Con::executef( this, 2, "onAnimationFinished", mCurrentAnimation );
-}

+ 0 - 161
engine/source/2d/sceneobject/SkeletonObject.h

@@ -1,161 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2013 GarageGames, LLC
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//-----------------------------------------------------------------------------
-
-#ifndef _SKELETON_OBJECT_H_
-#define _SKELETON_OBJECT_H_
-
-#ifndef _SPRITE_BATCH_H_
-#include "2d/core/SpriteBatch.h"
-#endif
-
-#ifndef _SCENE_OBJECT_H_
-#include "2d/sceneobject/SceneObject.h"
-#endif
-
-#ifndef _SKELETON_ASSET_H_
-#include "2d/assets/SkeletonAsset.h"
-#endif
-
-//------------------------------------------------------------------------------
-
-class SkeletonObject : public SceneObject, public SpriteBatch
-{
-protected:
-    typedef SceneObject Parent;
-    
-private:
-    typedef Vector<SpriteBatchItem*> typeSkeletonSpritesVector;
-    typeSkeletonSpritesVector   mSkeletonSprites;
-    
-    AssetPtr<SkeletonAsset>     mSkeletonAsset;
-    spSkeleton*                 mSkeleton;
-    spAnimationState*           mState;
-    
-    F32                         mPreTickTime;
-    F32                         mPostTickTime;
-    F32                         mTimeScale;
-    F32                         mLastFrameTime;
-    F32                         mAnimationDuration;
-    F32                         mTotalAnimationTime;
-    
-    bool                        mAnimationFinished;
-    bool                        mAnimationCycle;
-    Vector2                     mSkeletonScale;
-    Vector2                     mSkeletonOffset;
-    
-    StringTableEntry            mCurrentAnimation;
-    StringTableEntry            mCurrentSkin;
-    
-    bool                        mFlipX;
-    bool                        mFlipY;
-    
-    
-    
-public:
-    SkeletonObject();
-    virtual ~SkeletonObject();
-    
-    static void initPersistFields();
-    
-    virtual void preIntegrate( const F32 totalTime, const F32 elapsedTime, DebugStats* pDebugStats );
-    virtual void integrateObject( const F32 totalTime, const F32 elapsedTime, DebugStats* pDebugStats );
-    virtual void interpolateObject( const F32 timeDelta );
-    
-    virtual void copyTo( SimObject* object );
-    
-    virtual bool canPrepareRender( void ) const { return true; }
-    virtual bool validRender( void ) const { return mSkeletonAsset.notNull(); }
-    virtual bool shouldRender( void ) const { return true; }
-    virtual void scenePrepareRender( const SceneRenderState* pSceneRenderState, SceneRenderQueue* pSceneRenderQueue );
-    virtual void sceneRender( const SceneRenderState* pSceneRenderState, const SceneRenderRequest* pSceneRenderRequest, BatchRender* pBatchRenderer );
-    
-    /// Render flipping.
-    void setFlip( const bool flipX, const bool flipY )  { mFlipX = flipX; mFlipY = flipY; }
-    void setFlipX( const bool flipX )                   { setFlip( flipX, mFlipY ); }
-    void setFlipY( const bool flipY )                   { setFlip( mFlipX, flipY ); }
-    inline bool getFlipX( void ) const                  { return mFlipX; }
-    inline bool getFlipY( void ) const                  { return mFlipY; }
-    
-    bool setSkeletonAsset( const char* pSkeletonAssetId );
-    inline StringTableEntry getSkeletonAsset( void ) const { return mSkeletonAsset.getAssetId(); }
-    
-    inline bool setAnimationName( const char* pAnimation ) { return setAnimationName( pAnimation, mAnimationCycle ); }
-    bool setAnimationName( const char* pAnimation, const bool isLooping = false);
-    inline StringTableEntry getAnimationName( void ) const { return mCurrentAnimation; }
-    
-    bool setMix( const char* pFromAnimation, const char* pToAnimation, float time);
-    
-    bool setCurrentSkin( const char* pSkin );
-    inline StringTableEntry getCurrentSkin( void ) const { return mCurrentSkin; }
-    
-    void setRootBoneScale( const Vector2& scale );
-    inline void setRootBoneScale( const F32 x, const F32 y ){ setRootBoneScale( Vector2(x, y) ); }
-    inline Vector2 getRootBoneScale( void ) const { return mSkeletonScale; }
-    
-    void setRootBoneOffset( const Vector2& scale );
-    inline void setRootBoneOffset( const F32 x, const F32 y ){ setRootBoneOffset( Vector2(x, y) ); }
-    inline Vector2 getRootBoneOffset( void ) const { return mSkeletonOffset; }
-    
-    inline F32 getAnimationDuration( void ) const { return mAnimationDuration; }
-    inline bool isAnimationFinished( void ) const { return mAnimationFinished; };
-    
-    inline void setAnimationCycle( const bool isLooping ) { mAnimationCycle = isLooping; }
-    inline bool getAnimationCycle( void ) const {return mAnimationCycle; };
-    
-    void onAnimationFinished();
-    
-    /// Declare Console Object.
-    DECLARE_CONOBJECT( SkeletonObject );
-    
-protected:
-    void generateComposition( void );
-    void updateComposition( const F32 time );
-    
-protected:
-    static bool setSkeletonAsset( void* obj, const char* data )                  { static_cast<SkeletonObject*>(obj)->setSkeletonAsset(data); return false; }
-    static const char* getSkeletonAsset(void* obj, const char* data)             { return static_cast<SkeletonObject*>(obj)->getSkeletonAsset(); }
-    static bool writeSkeletonAsset( void* obj, StringTableEntry pFieldName )     { return static_cast<SkeletonObject*>(obj)->mSkeletonAsset.notNull(); }
-    
-    static bool setAnimationName( void* obj, const char* data )                  { static_cast<SkeletonObject*>(obj)->setAnimationName(data, static_cast<SkeletonObject*>(obj)->getAnimationCycle()); return false; }
-    static const char* getAnimationName(void* obj, const char* data)             { return static_cast<SkeletonObject*>(obj)->getAnimationName(); }
-    static bool writeAnimationName( void*obj, StringTableEntry pAnimation )      { return static_cast<SkeletonObject*>(obj)->getAnimationName() != StringTable->EmptyString; }
-    
-    static bool setCurrentSkin( void* obj, const char* data )                    { static_cast<SkeletonObject*>(obj)->setCurrentSkin(data); return false; }
-    static const char* getCurrentSkin(void* obj, const char* data)               { return static_cast<SkeletonObject*>(obj)->getCurrentSkin(); }
-    static bool writeCurrentSkin( void*obj, StringTableEntry pSkin )             { return static_cast<SkeletonObject*>(obj)->getCurrentSkin() != StringTable->EmptyString; }
-    
-    static bool setRootBoneScale(void* obj, const char* data)                    { static_cast<SkeletonObject*>(obj)->setRootBoneScale(Vector2(data)); return false; }
-    static const char* getRootBoneScale(void* obj, const char* data)             { return static_cast<SkeletonObject*>(obj)->getRootBoneScale().scriptThis(); }
-    static bool writeRootBoneScale( void* obj, StringTableEntry pFieldName )     { return static_cast<SkeletonObject*>(obj)->getRootBoneScale().notZero(); }
-    
-    static bool setRootBoneOffset(void* obj, const char* data)                   { static_cast<SkeletonObject*>(obj)->setRootBoneOffset(Vector2(data)); return false; }
-    static const char* getRootBoneOffset(void* obj, const char* data)            { return static_cast<SkeletonObject*>(obj)->getRootBoneOffset().scriptThis(); }
-    static bool writeRootBoneOffset( void* obj, StringTableEntry pFieldName )    { return static_cast<SkeletonObject*>(obj)->getRootBoneOffset().notZero(); }
-    
-    static bool setAnimationCycle( void* obj, const char* data )                 { static_cast<SkeletonObject*>(obj)->setAnimationCycle( dAtob(data) ); return false; }
-    static bool writeAnimationCycle( void* obj, StringTableEntry pFieldName )    { return static_cast<SkeletonObject*>(obj)->getAnimationCycle() == false; }
-    
-    static bool writeFlipX( void* obj, StringTableEntry pFieldName )             { return static_cast<SkeletonObject*>(obj)->getFlipX() == true; }
-    static bool writeFlipY( void* obj, StringTableEntry pFieldName )             { return static_cast<SkeletonObject*>(obj)->getFlipY() == true; }
-};
-
-#endif // _SKELETON_OBJECT_H_

+ 0 - 311
engine/source/2d/sceneobject/SkeletonObject_ScriptBinding.h

@@ -1,311 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2013 GarageGames, LLC
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//-----------------------------------------------------------------------------
-
-ConsoleMethodGroupBeginWithDocs(SkeletonObject, SceneObject)
-
-/*! Sets the skeleton asset Id to use.
-    @param skeletonAssetId The skeleton asset Id to use.
-    @return No return value.
-*/
-ConsoleMethodWithDocs(SkeletonObject, setSkeletonAsset, ConsoleVoid, 3, 3, (skeletonAssetId?))
-{
-    object->setSkeletonAsset( argv[2] );
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets the skeleton asset Id.
-    @return The skeleton asset Id.
-*/
-ConsoleMethodWithDocs(SkeletonObject, getSkeletonAsset, ConsoleString, 2, 2, ())
-{
-    return object->getSkeletonAsset();
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Sets the animation for the object.
-    @param animationName String containing animation name.
-    @param cycle Optional bool to determine whether the animation should loop.
-    @return Returns true on success."
-*/
-ConsoleMethodWithDocs(SkeletonObject, setAnimationName, ConsoleBool, 3, 4, (animationName, [cycle]))
-{
-    // Determine looping
-    bool shouldLoop = argc >= 4 ? dAtob(argv[3]) : false;
-    
-    return object->setAnimationName(argv[2], shouldLoop);
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets the name of the current animation.
-    @return String containing the animation name.
-*/
-ConsoleMethodWithDocs(SkeletonObject, getAnimationName, ConsoleString, 2, 2, ())
-{
-    return object->getAnimationName();
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Sets the skin for the skeleton.
-    @return No return value.
-*/
-ConsoleMethodWithDocs(SkeletonObject, setSkin, ConsoleVoid, 3, 3, (skinName))
-{
-    object->setCurrentSkin(argv[2]);
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets the name of the current skin.
-    @return String containing the skin name.
-*/
-ConsoleMethodWithDocs(SkeletonObject, getSkin, ConsoleString, 2, 2, ())
-{
-    return object->getCurrentSkin();
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Sets scaling of the skeleton's root bone.
-    @param scaleX Base x coordinate scale.
-    @param scaleY Base y coordinate scale.
-    @return No return value.
-*/
-ConsoleMethodWithDocs(SkeletonObject, setRootBoneScale, ConsoleVoid, 3, 4, (float scaleX, float scaleY))
-{
-    F32 scaleX, scaleY;
-    
-    const U32 elementCount = Utility::mGetStringElementCount(argv[2]);
-    
-    // ("width height")
-    if ((elementCount == 2) && (argc == 3))
-    {
-        scaleX = dAtof(Utility::mGetStringElement(argv[2], 0));
-        scaleY = dAtof(Utility::mGetStringElement(argv[2], 1));
-    }
-    
-    // (width, [height])
-    else if (elementCount == 1)
-    {
-        scaleX = dAtof(argv[2]);
-        
-        if (argc > 3)
-            scaleY = dAtof(argv[3]);
-        else
-            scaleY = scaleX;
-    }
-    
-    // Invalid
-    else
-    {
-        Con::warnf("SkeletonObject::setRootBoneScale() - Invalid number of parameters!");
-        return;
-    }
-    
-    // Set Scale.
-    object->setRootBoneScale(Vector2(scaleX, scaleY));
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets the skeleton's root bone scale.
-    @return (float x/y height) The x and y scale of the object's root bone.
-*/
-ConsoleMethodWithDocs(SkeletonObject, getRootBoneScale, ConsoleString, 2, 2, ())
-{
-    return object->getRootBoneScale().scriptThis();
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Sets local offset of the skeleton's root bone.
-    @param x Base x coordinate.
-    @param y Base y coordinate.
-    @return No return value.
-*/
-ConsoleMethodWithDocs(SkeletonObject, setRootBoneOffset, ConsoleVoid, 3, 4, (float x, float y))
-{
-    F32 x, y;
-    
-    const U32 elementCount = Utility::mGetStringElementCount(argv[2]);
-    
-    // ("x y")
-    if ((elementCount == 2) && (argc == 3))
-    {
-        x = dAtof(Utility::mGetStringElement(argv[2], 0));
-        y = dAtof(Utility::mGetStringElement(argv[2], 1));
-    }
-    
-    // (x, [y])
-    else if (elementCount == 1)
-    {
-        x = dAtof(argv[2]);
-        
-        if (argc > 3)
-            y = dAtof(argv[3]);
-        else
-            y = x;
-    }
-    
-    // Invalid
-    else
-    {
-        Con::warnf("SkeletonObject::setRootBoneOffset() - Invalid number of parameters!");
-        return;
-    }
-    
-    // Set Size.
-    object->setRootBoneOffset(Vector2(x, y));
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets the skeleton's root bone offset.
-    @return (float x/y) The x and y offset of the object's root bone.
-*/
-ConsoleMethodWithDocs(SkeletonObject, getRootBoneOffset, ConsoleString, 2, 2, ())
-{
-    return object->getRootBoneOffset().scriptThis();
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Sets whether the animation cycles or not.
-    @param cycle Bool to determine whether the animation should loop.
-    @return No return value.
-*/
-ConsoleMethodWithDocs(SkeletonObject, setAnimationCycle, ConsoleVoid, 3, 3, (bool cycle))
-{
-    object->setAnimationCycle( dAtob(argv[2] ) );
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets whether the animation cycles or not.
-    @return Whether the animation cycles or not.
-*/
-ConsoleMethodWithDocs(SkeletonObject, getAnimationCycle, ConsoleBool, 2, 2, ())
-{
-    return object->getAnimationCycle();
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Sets the sprite texture flipping for each axis.
-    @param flipX Whether or not to flip the texture along the x (horizontal) axis.
-    @param flipY Whether or not to flip the texture along the y (vertical) axis.
-    @return No return value.
-*/
-ConsoleMethodWithDocs(SkeletonObject, setFlip, ConsoleVoid, 4, 4, (bool flipX, bool flipY))
-{
-    // Set Flip.
-    object->setFlip( dAtob(argv[2]), dAtob(argv[3]) );
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets the flip for each axis.
-    @return (bool flipX/bool flipY) Whether or not the texture is flipped along the x and y axis.
-*/
-ConsoleMethodWithDocs(SkeletonObject, getFlip, ConsoleString, 2, 2, ())
-{
-    // Create Returnable Buffer.
-    char* pBuffer = Con::getReturnBuffer(32);
-    
-    // Format Buffer.
-    dSprintf(pBuffer, 32, "%d %d", object->getFlipX(), object->getFlipY());
-    
-    // Return Buffer.
-    return pBuffer;
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Sets whether or not the texture is flipped horizontally.
-    @param flipX Whether or not to flip the texture along the x (horizontal) axis.
-    @return No return value.
-*/
-ConsoleMethodWithDocs(SkeletonObject, setFlipX, ConsoleVoid, 3, 3, (bool flipX))
-{
-    // Set Flip.
-    object->setFlipX( dAtob(argv[2]) );
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Sets whether or not the texture is flipped vertically.
-    @param flipY Whether or not to flip the texture along the y (vertical) axis.
-    @return No return value.
-*/
-ConsoleMethodWithDocs(SkeletonObject, setFlipY, ConsoleVoid, 3, 3, (bool flipY))
-{
-    // Set Flip.
-    object->setFlipY( dAtob(argv[2]) );
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets whether or not the texture is flipped horizontally.
-    @return (bool flipX) Whether or not the texture is flipped along the x axis.
-*/
-ConsoleMethodWithDocs(SkeletonObject, getFlipX, ConsoleBool, 2, 2, ())
-{
-    return object->getFlipX();
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets whether or not the texture is flipped vertically.
-    @return (bool flipY) Whether or not the texture is flipped along the y axis.
-*/
-ConsoleMethodWithDocs(SkeletonObject, getFlipY, ConsoleBool, 2, 2, ())
-{
-    return object->getFlipY();
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets the duration of the current animation.
-    @return Duration of the animation in seconds.
-*/
-ConsoleMethodWithDocs(SkeletonObject, getAnimationDuration, ConsoleFloat, 2, 2, ())
-{
-    return object->getAnimationDuration();
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Mixes the current animation with another.
-    @param animation The name of the animation to mix.
-    @param time The time to start mixing.
-*/
-ConsoleMethodWithDocs(SkeletonObject, setMix, ConsoleBool, 5, 5, (fromAnimation, toAnimation, time))
-{
-    Con::printf("Mixing %s with %s at %f", argv[2], argv[3], dAtof(argv[4]));
-    
-    return object->setMix(argv[2], argv[3], dAtof(argv[4]));
-}
-
-ConsoleMethodGroupEndWithDocs(SkeletonObject)

+ 28 - 0
engine/source/2d/sceneobject/SpineCollisionProxy.cc

@@ -0,0 +1,28 @@
+// Author: Mike Tannel - 7/21/2020
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _SPINE_COLLISION_PROXY_H_
+#include "SpineCollisionProxy.h"
+#endif
+
+//------------------------------------------------------------------------------
+// Want these proxies usable from torque script
+IMPLEMENT_CONOBJECT(SpineCollisionProxy);
+
+//------------------------------------------------------------------------------
+
+SpineCollisionProxy::SpineCollisionProxy() :
+	mActive{true},
+	mRotation{0.0f}
+{}
+
+//------------------------------------------------------------------------------
+

+ 44 - 0
engine/source/2d/sceneobject/SpineCollisionProxy.h

@@ -0,0 +1,44 @@
+// Author: Mike Tannel - 7/21/2020
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+#ifndef _SPINE_COLLISION_PROXY_H_
+#define _SPINE_COLLISION_PROXY_H_
+
+#ifndef _SCENE_OBJECT_H_
+#include "2d/sceneobject/SceneObject.h"
+#endif
+
+#ifndef SPINE_SPINE_H_
+#include "spine/spine.h"
+#endif
+
+class SpineCollisionProxy :
+	public SceneObject
+{
+protected:
+	// For console object
+	typedef SceneObject Parent;
+
+public:
+	SpineCollisionProxy();
+
+	bool mActive;
+
+	F32	 mRotation;
+
+	/// Declare Console Object.
+	DECLARE_CONOBJECT(SpineCollisionProxy);
+
+public:
+	inline void deActivate(void) { if (!mActive) return;  mActive = false; setActive(false); }
+	inline void activate(void) { if (mActive) return;  mActive = true; setActive(true); }
+};
+
+#endif // _SPINE_COLLISION_PROXY_H_

+ 1239 - 0
engine/source/2d/sceneobject/SpineObject.cc

@@ -0,0 +1,1239 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _SPINE_OBJECT_H_
+#include "2d/sceneobject/SpineObject.h"
+#endif
+
+// Script bindings.
+#include "2d/sceneobject/SpineObject_ScriptBinding.h"
+
+//------------------------------------------------------------------------------
+
+void spineAnimationEventCallbackHandler(spAnimationState* state, spEventType type, spTrackEntry* entry, spEvent* event) {
+
+	SpineObject *targetSpineObject = (SpineObject *)state->userData;
+	if (!targetSpineObject) {
+		Con::warnf("spineAnimationEventCallbackHandler - Event ('%s') received with no spine object in spAnimationState->userData.  Discarding event.",
+			event->data->name);
+	}
+
+	const char* animationName = StringTable->insert(entry && entry->animation ? entry->animation->name : 0, true);
+
+	switch (type) {
+	case SP_ANIMATION_START:
+		Con::executef(targetSpineObject, 3, "onAnimationStart", animationName, Con::getIntArg(entry->trackIndex));
+		break;
+	case SP_ANIMATION_INTERRUPT:
+		Con::executef(targetSpineObject, 3, "onAnimationInterrupt", animationName, Con::getIntArg(entry->trackIndex));
+		break;
+	case SP_ANIMATION_END:
+		Con::executef(targetSpineObject, 3, "onAnimationEnd", animationName, Con::getIntArg(entry->trackIndex));
+		break;
+	case SP_ANIMATION_COMPLETE:
+		Con::executef(targetSpineObject, 3, "onAnimationComplete", animationName, Con::getIntArg(entry->trackIndex));
+		break;
+	case SP_ANIMATION_DISPOSE:
+		Con::executef(targetSpineObject, 3, "onAnimationDispose", animationName, Con::getIntArg(entry->trackIndex));
+		break;
+	case SP_ANIMATION_EVENT:
+		Con::executef(targetSpineObject, 10
+			, "onAnimationEvent"
+			, animationName
+			, Con::getIntArg(entry->trackIndex)
+			, event->data->name ? event->data->name : ""
+			, Con::getIntArg(event->intValue)
+			, Con::getFloatArg(event->floatValue)
+			, event->stringValue ? event->stringValue : ""
+			, Con::getFloatArg(event->time)
+			, Con::getFloatArg(event->volume)
+			, Con::getFloatArg(event->balance));
+		break;
+	}
+}
+
+//------------------------------------------------------------------------------
+
+IMPLEMENT_CONOBJECT(SpineObject);
+
+//------------------------------------------------------------------------------
+
+// Spine vertex effect support
+static EnumTable::Enums vertexEffectLookup[] =
+{
+	{ SpineObject::NONE,     "None"   },
+	{ SpineObject::JITTER,   "Jitter" },
+	{ SpineObject::SWIRL,    "Swirl"  }
+};
+
+static EnumTable VertexEffectTable(3, &vertexEffectLookup[0]);
+
+//------------------------------------------------------------------------------
+
+SpineObject::VertexEffect SpineObject::getVertexEffectTypeEnum(const char* label)
+{
+	// Search for Mnemonic.
+	for (U32 i = 0; i < (sizeof(vertexEffectLookup) / sizeof(EnumTable::Enums)); i++)
+		if (dStricmp(vertexEffectLookup[i].label, label) == 0)
+			return((SpineObject::VertexEffect)vertexEffectLookup[i].index);
+
+	// Warn.
+	Con::warnf("SpineObject::getVertexEffectTypeEnum() - Invalid vertex effect type '%s'.", label);
+
+	return VertexEffect::INVALID_VERTEX_EFFECT;
+}
+
+//-----------------------------------------------------------------------------
+
+const char* SpineObject::getVertexEffectTypeDescription(const VertexEffect vertexEffectType)
+{
+	// Search for Mnemonic.
+	for (U32 i = 0; i < (sizeof(vertexEffectLookup) / sizeof(EnumTable::Enums)); i++)
+	{
+		if (vertexEffectLookup[i].index == (S32)vertexEffectType)
+			return vertexEffectLookup[i].label;
+	}
+
+	// Warn.
+	Con::warnf("SpineObject::getVertexEffectTypeDescription() - Invalid vertex effect type.");
+
+	return StringTable->EmptyString;
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineObject::initPersistFields()
+{
+	// Call parent.
+	Parent::initPersistFields();
+
+	addProtectedField("Asset", TypeSpineAssetPtr, Offset(mSpineAsset, SpineObject), &setSpineAsset, &defaultProtectedGetFn, &writeSpineAsset, "The spine asset ID used for the spine.");
+	addProtectedField("Skin", TypeString, 0, &setSkin, &getSkinName, &writeCurrentSkin, "The skin to use.");
+	addProtectedField("Scale", TypeVector2, 0, &setScale, &getScale, &writeScale, "Scaling of the skeleton geometry.");
+	addProtectedField("AnimationCycle", TypeBool, Offset(mShouldLoop, SpineObject), &setShouldLoop, &defaultProtectedGetFn, &writeAnimationData, "Whether the startup animation should loop or not.");
+	addProtectedField("AnimationName", TypeString, 0, &setAnimation, &getAnimationName, &writeAnimationData, "The animation name to play at startup.");
+	addProtectedField("TimeScale", TypeF32, 0, &setTimeScale, &getTimeScale, &writeTimeScale, "Time scale (animation speed) adjustment factor.");
+	addProtectedField("FlipX", TypeBool, Offset(mFlipX, SpineObject), &setFlipX, &defaultProtectedGetFn, &writeFlipX, "Whether to invert the image horizontally.");
+	addProtectedField("FlipY", TypeBool, Offset(mFlipY, SpineObject), &setFlipY, &defaultProtectedGetFn, &writeFlipY, "Whether image should be inverted vertically.");
+	addGroup("Vertex Effects");
+	addProtectedField("ActiveEffect", TypeEnum, Offset(mActiveEffect, SpineObject), &setActiveEffectType, &defaultProtectedGetFn, &writeActiveEffectType, 1, &VertexEffectTable, "The name of the vertex effect assigned, or None.");
+	addProtectedField("JitterX", TypeF32, 0, &setJitterX, &getJitterX, &writeJitterEffectValues, "A 'Jitter' vertex special effect setting.  Note: Play around with the various settings.  They can be modified on the fly to vary the effect the displayed item.");
+	addProtectedField("JitterY", TypeF32, 0, &setJitterY, &getJitterY, &writeJitterEffectValues, "A 'Jitter' vertex special effect setting.");
+	addProtectedField("SwirlX", TypeF32, 0, &setSwirlX, &getSwirlX, &writeSwirlEffectValues, "A 'Swirl' vertex special effect setting.");
+	addProtectedField("SwirlY", TypeF32, 0, &setSwirlY, &getSwirlY, &writeSwirlEffectValues, "A 'Swirl' vertex special effect setting.");
+	addProtectedField("SwirlRadius", TypeF32, 0, &setSwirlRadius, &getSwirlRadius, &writeSwirlEffectValues, "A 'Swirl' vertex special effect setting.");
+	addProtectedField("SwirlAngle", TypeF32, 0, &setSwirlAngle, &getSwirlAngle, &writeSwirlEffectValues, "A 'Swirl' vertex special effect setting.");
+	endGroup("Vertex Effects");
+	addProtectedField("EventCallbacksEnabled", TypeBool, 0, &setEventCallbacksEnabled, &defaultProtectedGetFn, &writeEventCallbacksEnabled, "Whether the SpineObject should receive spine animation event callbacks.");
+
+}
+
+//------------------------------------------------------------------------------
+
+void SpineObject::resetState() {
+
+	mPreTickTime = 0.0f;
+	mPostTickTime = 0.0f;
+	mLastFrameTime = 0.0f;
+	mFlipX = mFlipY = false;
+
+	mSkeleton.reset();
+	mAnimationState.reset();
+	mSkeletonClipping.reset();
+	mSkeletonBounds.reset();
+
+	mAutoCenterOffset.SetZero();
+
+	// Don't want b2DynamicTree supported search (for culling reasons) being performed 
+	// on our couple-o-sprites lashup.
+	setBatchCulling(false);
+
+	mVertexEffect = NULL;
+	mActiveEffect = VertexEffect::NONE;
+
+	mPriorRootBoneWorldX = 0.0f;
+	mPriorRootBoneWorldY = 0.0f;
+	mPriorFlipX = mFlipX;
+	mPriorFlipY = mFlipY;
+
+	mShouldLoop = false;
+
+	mCollisionProxies.clear();
+}
+
+//------------------------------------------------------------------------------
+
+// NOTE: Update this if new state members are added to the object.
+void SpineObject::copyTo(SimObject* object)
+{
+	// Call to parent.
+	Parent::copyTo(object);
+
+	// Fetch object.
+	SpineObject* pSpine = dynamic_cast<SpineObject*>(object);
+
+	// Sanity!
+	AssertFatal(pSpine != NULL, "SpineObject::copyTo() - Object is not the correct type.");
+
+	// Copy state.
+	pSpine->setSpineAsset(getSpineAsset());
+	pSpine->setFlipX(getFlipX());
+	pSpine->setFlipY(getFlipY());
+	pSpine->setPosition(getPosition());
+	pSpine->setAngle(getAngle());
+	pSpine->setAnimation(getAnimationName(), getIsLooping());
+	pSpine->setSkin(getSkinName());
+	pSpine->setScale(getScale());
+	pSpine->mAutoCenterOffset = mAutoCenterOffset;
+
+	copyCollisionShapes(pSpine);
+}
+
+//-----------------------------------------------------------------------------
+
+bool SpineObject::setSpineAsset(const char* pSpineAssetId)
+{
+	// Sanity!
+	if (pSpineAssetId == NULL)
+	{
+		Con::errorf("SpineObject::setSpineAsset() - Cannot use a NULL asset Id.");
+		return false;
+	}
+
+	// Fetch the asset.
+	AssetPtr<SpineAsset> temp = StringTable->insert(pSpineAssetId, true);
+
+	if (temp->mImageAsset.isNull())
+	{
+		Con::errorf("SpineObject::setSpineAsset() - Image asset is undefined.");
+		return false;
+	}
+
+	// Clear away existing. 
+	clearSprites();
+	resetState();
+
+	// Reflect asset definition
+	mSpineAsset = std::move(temp);
+	mSkeleton = skeleton_ptr(spSkeleton_create(mSpineAsset->mSkeletonData));
+	mAnimationState = animationState_ptr(spAnimationState_create(mSpineAsset->mAnimationStateData));
+	mSkeletonBounds = skeletonBounds_ptr(spSkeletonBounds_create());
+	mSkeletonClipping = skeletonClipping_ptr(spSkeletonClipping_create());
+
+	spSkeleton_setToSetupPose(mSkeleton.get());
+
+	// Needed by the events system to route callbacks.
+	mAnimationState->userData = this;
+
+	// Prepare for animation.
+	updateSpine(0.0f);
+
+	return true;
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineObject::setFlip(const bool flipX, const bool flipY) {
+
+	mFlipX = flipX;
+	mSkeleton->scaleX = flipX
+		? mSkeleton->scaleX < 0 ? mSkeleton->scaleX : -mSkeleton->scaleX
+		: mSkeleton->scaleX >= 0 ? mSkeleton->scaleX : -mSkeleton->scaleX;
+
+	mFlipY = flipY;
+	mSkeleton->scaleY = flipY
+		? mSkeleton->scaleY < 0 ? mSkeleton->scaleY : -mSkeleton->scaleY
+		: mSkeleton->scaleY >= 0 ? mSkeleton->scaleY : -mSkeleton->scaleY;
+}
+
+//-----------------------------------------------------------------------------
+
+F32 SpineObject::setTimeScale(const F32 timeScale) {
+	if (!mAnimationState)
+		return 0.0f;
+
+	F32 previousValue = mAnimationState->timeScale;
+
+	mAnimationState->timeScale = timeScale;
+
+	return previousValue;
+}
+
+//-----------------------------------------------------------------------------
+
+bool SpineObject::setAnimation(const char* pName, const int track, const bool shouldLoop, const F32 mixDuration)
+{
+	if (!mAnimationState)
+		return false;
+
+	// Check to see if the requested animation is valid
+	auto animation = spSkeletonData_findAnimation(mSkeleton->data, StringTable->insert(pName, true));
+
+	if (!animation)
+	{
+		Con::warnf("SpineObject::setAnimation() - Animation '%s' does not exist.", StringTable->insert(pName, true));
+		return false;
+	}
+
+	// Set the animation.
+	spTrackEntry* entry = spAnimationState_setAnimation(mAnimationState.get(), track, animation, shouldLoop);
+
+	if (mixDuration < 0.0f) {
+		// Use default mix duration
+		return entry != NULL;
+	}
+	else {
+		// Use given mix duration.
+		entry->mixDuration = mixDuration;
+		return true;
+	}
+}
+
+//-----------------------------------------------------------------------------
+
+bool SpineObject::setEmptyAnimation(const int track, const F32 mixDuration) {
+	if (!mAnimationState)
+		return false;
+
+	return spAnimationState_setEmptyAnimation(mAnimationState.get(), track, mixDuration) != NULL;
+}
+
+//-----------------------------------------------------------------------------
+
+bool SpineObject::queueAnimation(const char* pName, const int track, const bool shouldLoop, const F32 mixDuration, const F32 delay)
+{
+	if (!mAnimationState)
+		return false;
+
+	// Check to see if the requested animation is valid
+	auto animation = spSkeletonData_findAnimation(mSkeleton->data, StringTable->insert(pName, true));
+
+	if (!animation)
+	{
+		Con::warnf("SpineObject::queueAnimation() - Animation '%s' does not exist.", StringTable->insert(pName, true));
+		return false;
+	}
+
+	// Set the animation.
+	spTrackEntry* entry = spAnimationState_addAnimation(mAnimationState.get(), track, animation, shouldLoop, delay);
+
+	if (mixDuration < 0.0f) {
+		// Use default mix duration
+		return entry != NULL;
+	}
+	else {
+		// Use given mix duration.
+		entry->mixDuration = mixDuration;
+		return true;
+	}
+}
+
+//-----------------------------------------------------------------------------
+
+bool SpineObject::queueEmptyAnimation(const int track, const F32 mixDuration, const F32 delay) {
+	if (!mAnimationState)
+		return false;
+
+	return spAnimationState_addEmptyAnimation(mAnimationState.get(), track, mixDuration, delay) != NULL;
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineObject::clearAnimations(const int track, const bool mixToSetupPose, const F32 mixDuration) {
+	if (!mAnimationState)
+		return;
+
+	spAnimationState_clearTrack(mAnimationState.get(), track);
+
+	if (mixToSetupPose)
+		setEmptyAnimation(track, mixDuration);
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineObject::clearAllAnimations(const bool mixToSetupPose, const F32 mixDuration) {
+	if (!mAnimationState)
+		return;
+
+	spAnimationState_clearTracks(mAnimationState.get());
+
+	if (mixToSetupPose)
+		spAnimationState_setEmptyAnimations(mAnimationState.get(), mixDuration);
+}
+
+//-----------------------------------------------------------------------------
+
+StringTableEntry SpineObject::getAnimationName(const int track) const {
+	if (!mAnimationState)
+		return StringTable->EmptyString;
+
+	spTrackEntry* entry = spAnimationState_getCurrent(mAnimationState.get(), track);
+
+	if (!entry)
+		return StringTable->EmptyString;
+
+	return StringTable->insert(entry->animation->name, true);
+}
+
+//-----------------------------------------------------------------------------
+
+bool SpineObject::getIsLooping(const int track) const {
+	if (!mAnimationState)
+		return StringTable->EmptyString;
+
+	spTrackEntry* entry = spAnimationState_getCurrent(mAnimationState.get(), track);
+
+	if (!entry)
+		return StringTable->EmptyString;
+
+	return entry->loop;
+}
+
+//-----------------------------------------------------------------------------
+
+bool SpineObject::setMix(const char* pFromAnimation, const char* pToAnimation, const F32 time)
+{
+	if (mSpineAsset.isNull())
+	{
+		Con::warnf("SpineObject::setMix() - Cannot mix. No asset assigned");
+		return false;
+	}
+
+	// Check for valid animation state data
+	AssertFatal(mSpineAsset->mAnimationStateData != NULL, "SpineObject::setMix() - Animation state data invalid");
+
+	// Check to see if the "from animation" is valid
+	auto from = spSkeletonData_findAnimation(mSkeleton->data, StringTable->insert(pFromAnimation, true));
+
+	if (!from)
+	{
+		Con::warnf("SpineObject::setMix() - Animation '%s' does not exist.", StringTable->insert(pFromAnimation, true));
+		return false;
+	}
+
+	// Check to see if the "to animation" is valid
+	auto to = spSkeletonData_findAnimation(mSkeleton->data, StringTable->insert(pToAnimation, true));
+
+	if (!to)
+	{
+		Con::warnf("SpineObject::setMix() - Animation '%s' does not exist.", StringTable->insert(pToAnimation, true));
+		return false;
+	}
+
+	// Check to see if a valid mix time was passsed
+	if (time < 0.0f)
+	{
+		Con::warnf("SpineObject::setMix() - Invalid time set, '%f'", time);
+		return false;
+	}
+
+	spAnimationStateData_setMix(mSpineAsset->mAnimationStateData, from, to, time);
+	return true;
+}
+
+//-----------------------------------------------------------------------------
+
+bool SpineObject::setSkin(const char* pSkin)
+{
+	if (mSpineAsset.isNull() || !mSkeleton)
+	{
+		Con::errorf("SpineObject::setSkin() - Spine asset was null or skeleton was not built.");
+		return false;
+	}
+
+	auto to = spSkeletonData_findSkin(mSkeleton->data, StringTable->insert(pSkin, true));
+	if (!to)
+	{
+		Con::warnf("SpineObject::setSkin() - Skin '%s' does not exist.", StringTable->insert(pSkin, true));
+		return false;
+	}
+
+	spSkeleton_setSkin(mSkeleton.get(), to);
+	spSkeleton_setSlotsToSetupPose(mSkeleton.get());
+	spAnimationState_apply(mAnimationState.get(), mSkeleton.get());
+
+	return true;
+}
+
+//-----------------------------------------------------------------------------
+
+inline StringTableEntry SpineObject::getSkinName(void) const {
+	if (!mAnimationState || !mSkeleton)
+		return StringTable->EmptyString;
+
+	spSkin* pSkin = mSkeleton->skin;
+
+	if (!pSkin) {
+		// Using default skin.
+		return StringTable->insert(mSkeleton->data->defaultSkin->name, true);
+	}
+	else {
+		return StringTable->insert(pSkin->name, true);
+	}
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineObject::setScale(const Vector2& scale)
+{
+	if (!mSkeleton)
+		return;
+
+	// Set scale, but keep orientation.  This is used by spine to flip the character
+	// as well as set its size.
+	mSkeleton->scaleX = mSkeleton->scaleX < 0.0f ? -scale.x : scale.x;
+	mSkeleton->scaleY = mSkeleton->scaleY < 0.0f ? -scale.y : scale.y;
+}
+
+//------------------------------------------------------------------------------
+
+void SpineObject::setActiveEffect(const VertexEffect requestedEffect) {
+	if (mActiveEffect == requestedEffect)
+		return;
+
+	switch (requestedEffect) {
+	case VertexEffect::JITTER:
+		if (!mJitterControl) {
+			mJitterControl = jitterEffect_ptr(spJitterVertexEffect_create(0, 0));
+		}
+		mVertexEffect = &mJitterControl->super;
+		mActiveEffect = VertexEffect::JITTER;
+		break;
+	case VertexEffect::SWIRL:
+		if (!mSwirlControl) {
+			mSwirlControl = swirlEffect_ptr(spSwirlVertexEffect_create(0));
+		}
+		mVertexEffect = &mSwirlControl->super;
+		mActiveEffect = VertexEffect::SWIRL;
+		break;
+	default:
+		Con::warnf("SpineObject::setActiveEffect - Unrecognized vertex special effect requested: '%s'.",
+			getVertexEffectTypeDescription(requestedEffect));
+	}
+}
+
+//------------------------------------------------------------------------------
+
+void SpineObject::enableJitter(const F32 x, const F32 y) {
+	if (mJitterControl) {
+		mJitterControl->jitterX = x;
+		mJitterControl->jitterY = y;
+	}
+	else {
+		mJitterControl = jitterEffect_ptr(spJitterVertexEffect_create(x, y));
+	}
+	mVertexEffect = &mJitterControl->super;
+	mActiveEffect = VertexEffect::JITTER;
+}
+
+//------------------------------------------------------------------------------
+
+void SpineObject::disableJitter() {
+	if (mActiveEffect == VertexEffect::JITTER) {
+		mVertexEffect = NULL;
+		mActiveEffect = VertexEffect::NONE;
+	}
+}
+
+//------------------------------------------------------------------------------
+
+void SpineObject::enableSwirl(const F32 radius) {
+	if (mSwirlControl) {
+		mSwirlControl->radius = radius;
+	}
+	else {
+		mSwirlControl = swirlEffect_ptr(spSwirlVertexEffect_create(radius));
+	}
+	mVertexEffect = &mSwirlControl->super;
+	mActiveEffect = VertexEffect::SWIRL;
+}
+
+//------------------------------------------------------------------------------
+
+void SpineObject::disableSwirl() {
+	if (mActiveEffect == VertexEffect::SWIRL) {
+		mVertexEffect = NULL;
+		mActiveEffect = VertexEffect::NONE;
+	}
+}
+
+//------------------------------------------------------------------------------
+
+void SpineObject::enableEventCallbacks(void) {
+	// No animation state
+	if (!mAnimationState)
+		return;
+
+	// Already listening
+	if (mAnimationState->listener)
+		return;
+
+	mAnimationState->listener = spineAnimationEventCallbackHandler;
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineObject::disableEventCallbacks(void) {
+	// No animation state
+	if (!mAnimationState)
+		return;
+
+	// Not listening anyway.
+	if (!mAnimationState->listener)
+		return;
+
+	mAnimationState->listener = NULL;
+}
+
+//------------------------------------------------------------------------------
+
+void SpineObject::preIntegrate(const F32 totalTime, const F32 elapsedTime, DebugStats* pDebugStats)
+{
+	Parent::preIntegrate(totalTime, elapsedTime, pDebugStats);
+
+	// Note tick times.
+	mPreTickTime = mPostTickTime;
+	mPostTickTime = totalTime;
+
+	// Update at pre-tick time.
+	updateSpine(mPreTickTime);
+	prepareSpineForRender();
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineObject::interpolateObject(const F32 timeDelta)
+{
+	Parent::interpolateObject(timeDelta);
+
+	// Update time (interpolated).
+	F32 timeInterp = (timeDelta * mPreTickTime) + ((1.0f - timeDelta) * mPostTickTime);
+	updateSpine(timeInterp);
+	prepareSpineForRender();
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineObject::scenePrepareRender(const SceneRenderState* pSceneRenderState, SceneRenderQueue* pSceneRenderQueue)
+{
+	// Set batch transform to identity because the skeleton is responsible for 
+	// its geometry's position
+	setBatchTransform(B2_IDENTITY_TRANSFORM);
+
+	SpriteBatch::prepareRender(this, pSceneRenderState, pSceneRenderQueue);
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineObject::sceneRender(const SceneRenderState* pSceneRenderState, const SceneRenderRequest* pSceneRenderRequest, BatchRender* pBatchRenderer)
+{
+	// Render.
+	SpriteBatch::render(pSceneRenderState, pSceneRenderRequest, pBatchRenderer);
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineObject::updateSpine(const F32 time)
+{
+	// Update the skeleton's position/rotation.
+	Vector2 p = getPosition();
+	mSkeleton->x = p.x - mAutoCenterOffset.x;
+	mSkeleton->y = p.y - mAutoCenterOffset.y;
+	mSkeleton->root->rotation = setPerFlipState(mRadToDeg(getAngle()));
+
+	// Advance time
+	F32 delta = (time - mLastFrameTime);
+	mLastFrameTime = time;
+
+	spSkeleton_update(mSkeleton.get(), delta);
+	spAnimationState_update(mAnimationState.get(), delta);
+	spAnimationState_apply(mAnimationState.get(), mSkeleton.get());
+	spSkeleton_updateWorldTransform(mSkeleton.get());
+}
+
+//-----------------------------------------------------------------------------
+
+void SpineObject::prepareSpineForRender()
+{
+	// Early out if skeleton is invisible
+	if (mSkeleton->color.a == 0) {
+		for (const auto& i : mCollisionProxies) {
+			i.value->deActivate();
+		}
+		return;
+	}
+
+	if (mVertexEffect)
+		mVertexEffect->begin(mVertexEffect, mSkeleton.get());
+
+	// Get the ImageAsset used by the sprites
+	StringTableEntry assetId = mSpineAsset->mImageAsset.getAssetId();
+
+	clearSprites();
+
+	b2AABB spriteAABB;
+	U16 quadIndices[6] = { 0, 1, 2, 2, 3, 0 };
+	FrameTemp<F32> VertexBuffer(BatchRender::maxVertexCount);
+
+	vector<Vector2> pointSoup;
+
+	for (int i = 0; i < mSkeleton->slotsCount; ++i)
+	{
+		auto slot = mSkeleton->drawOrder[i];
+		if (!slot)
+			continue;
+
+		auto attachment = slot->attachment;
+		if (!attachment)
+			continue;
+
+		SpineCollisionProxy* pCollisionProxy = NULL;
+		auto itr = mCollisionProxies.find(attachment);
+		if (itr != mCollisionProxies.end()) {
+			pCollisionProxy = itr->value;
+		}
+
+		if (slot->color.a == 0 || !slot->bone->active) {
+			spSkeletonClipping_clipEnd(mSkeletonClipping.get(), slot);
+
+			if (pCollisionProxy && pCollisionProxy->mActive) {
+				pCollisionProxy->deActivate();
+			}
+			continue;
+		}
+
+		F32	*uvs = 0;
+		U16	*indices = 0;
+		int	indicesCount = 0;
+		F32 *vertices = VertexBuffer; // It will be redirected to a different buffer if clipping is performed.
+		int	verticesCount = 0;
+		spColor* attachmentColor = NULL;
+		string attachmentName;
+
+		if (attachment->type == SP_ATTACHMENT_REGION) {
+			auto regionAttachment = (spRegionAttachment*)attachment;
+			attachmentName = StringTable->insert(regionAttachment->path ? regionAttachment->path : attachment->name, true);
+			attachmentColor = &regionAttachment->color;
+
+			// Is slot invisible?
+			if (attachmentColor->a == 0) {
+				spSkeletonClipping_clipEnd(mSkeletonClipping.get(), slot);
+
+				if (pCollisionProxy && pCollisionProxy->mActive) {
+					pCollisionProxy->deActivate();
+				}
+				continue;
+			}
+
+			if (pCollisionProxy && !pCollisionProxy->mActive) {
+				pCollisionProxy->activate();
+			}
+
+			auto currentRegion = (spAtlasRegion *)regionAttachment->rendererObject;
+
+			spRegionAttachment_updateOffset(regionAttachment);
+			spRegionAttachment_setUVs(regionAttachment, currentRegion->u, currentRegion->v, currentRegion->u2, currentRegion->v2, currentRegion->rotate);
+			spRegionAttachment_computeWorldVertices(regionAttachment, slot->bone, vertices, 0, 2);
+
+			verticesCount = 4;
+			uvs = regionAttachment->uvs;
+			indices = quadIndices;
+			indicesCount = 6;
+		}
+		else if (attachment->type == SP_ATTACHMENT_MESH) {
+			auto meshAttachment = (spMeshAttachment*)attachment;
+			attachmentName = StringTable->insert(meshAttachment->path ? meshAttachment->path : attachment->name, true);
+
+			attachmentColor = &meshAttachment->color;
+
+			// Is slot invisible?
+			if (attachmentColor->a == 0) {
+				spSkeletonClipping_clipEnd(mSkeletonClipping.get(), slot);
+
+				if (pCollisionProxy && pCollisionProxy->mActive) {
+					pCollisionProxy->deActivate();
+				}
+				continue;
+			}
+
+			// Vertex buffer overrun?
+			if (meshAttachment->super.worldVerticesLength > BatchRender::maxVertexCount) {
+				Con::warnf("Mesh attachment '%s' exceeded vertex buffer size. Mesh count was %d. Buffer size is %d.\n",
+					attachmentName, meshAttachment->super.worldVerticesLength, BatchRender::maxVertexCount);
+				continue;
+			}
+
+			if (pCollisionProxy && !pCollisionProxy->mActive) {
+				pCollisionProxy->activate();
+			}
+
+			// Compute updated render info.
+			spVertexAttachment_computeWorldVertices(&meshAttachment->super, slot, 0, meshAttachment->super.worldVerticesLength, vertices, 0, 2);
+
+			verticesCount = meshAttachment->super.worldVerticesLength >> 1;
+			uvs = meshAttachment->uvs;
+			indices = meshAttachment->triangles;
+			indicesCount = meshAttachment->trianglesCount;
+		}
+		else if (attachment->type == SP_ATTACHMENT_CLIPPING) {
+			auto clippingAttachment = (spClippingAttachment*)attachment;
+			spSkeletonClipping_clipStart(mSkeletonClipping.get(), slot, clippingAttachment);
+			continue;
+		}
+		else continue;
+
+		// Perform clipping if active
+		if (spSkeletonClipping_isClipping(mSkeletonClipping.get())) {
+			spSkeletonClipping_clipTriangles(mSkeletonClipping.get(), vertices, verticesCount << 1, indices, indicesCount, uvs, 2);
+			vertices = mSkeletonClipping->clippedVertices->items;
+			verticesCount = mSkeletonClipping->clippedVertices->size >> 1;
+			uvs = mSkeletonClipping->clippedUVs->items;
+			indices = mSkeletonClipping->clippedTriangles->items;
+			indicesCount = mSkeletonClipping->clippedTriangles->size;
+		}
+
+		if (!verticesCount) {
+			// No geometry to display.
+			if (pCollisionProxy && pCollisionProxy->mActive) {
+				pCollisionProxy->deActivate();
+			}
+			continue;
+		}
+
+		if (!mSpineAsset->getPreMultipliedAlpha()) {
+			// Not using Premultiplied Alpha
+			switch (slot->data->blendMode) {
+			case SP_BLEND_MODE_ADDITIVE:
+				setSrcBlendFactor(GL_SRC_ALPHA);
+				setDstBlendFactor(GL_ONE);
+				break;
+			case SP_BLEND_MODE_MULTIPLY:
+				setSrcBlendFactor(GL_DST_COLOR);
+				setDstBlendFactor(GL_ONE_MINUS_SRC_ALPHA);
+				break;
+			case SP_BLEND_MODE_SCREEN:
+				setSrcBlendFactor(GL_ONE);
+				setDstBlendFactor(GL_ONE_MINUS_SRC_COLOR);
+				break;
+			case SP_BLEND_MODE_NORMAL:
+			default:
+				setSrcBlendFactor(GL_SRC_ALPHA);
+				setDstBlendFactor(GL_ONE_MINUS_SRC_ALPHA);
+				break;
+			}
+		}
+		else {
+			// Setup for Premultiplied Alpha
+			switch (slot->data->blendMode) {
+			case SP_BLEND_MODE_ADDITIVE:
+				setSrcBlendFactor(GL_ONE);
+				setDstBlendFactor(GL_ONE);
+				break;
+			case SP_BLEND_MODE_MULTIPLY:
+				setSrcBlendFactor(GL_DST_COLOR);
+				setDstBlendFactor(GL_ONE_MINUS_SRC_ALPHA);
+				break;
+			case SP_BLEND_MODE_SCREEN:
+				setSrcBlendFactor(GL_ONE);
+				setDstBlendFactor(GL_ONE_MINUS_SRC_COLOR);
+				break;
+			case SP_BLEND_MODE_NORMAL:
+			default:
+				setSrcBlendFactor(GL_ONE);
+				setDstBlendFactor(GL_ONE_MINUS_SRC_ALPHA);
+				break;
+			}
+		}
+
+		// Define sprite carrier object.  NOTE: This isn't a Sprite. It's a SpriteBatchItem, which is completely different
+		// than a Sprite.  It's more like a render request headed for the batch renderer.
+		SpriteBatchItem* pSprite = SpriteBatch::createSprite();
+
+		pSprite->setDepth(mSceneLayerDepth);
+
+		pSprite->setSrcBlendFactor(mSrcBlendFactor);
+		pSprite->setDstBlendFactor(mDstBlendFactor);
+
+		F32 r = mSkeleton->color.r * slot->color.r * attachmentColor->r;
+		F32 g = mSkeleton->color.g * slot->color.g * attachmentColor->g;
+		F32 b = mSkeleton->color.b * slot->color.b * attachmentColor->b;
+		F32 a = mSkeleton->color.a * slot->color.a * attachmentColor->a;
+
+		spColor light;
+		light.r = r; light.g = g; light.b = b; light.a = a;
+
+		SpriteBatchItem::drawData *pDrawData = pSprite->getDrawData();
+		pDrawData->size(indicesCount);
+
+		spriteAABB.lowerBound.x = spriteAABB.upperBound.x = vertices[0];
+		spriteAABB.lowerBound.y = spriteAABB.upperBound.y = vertices[1];
+		if (mVertexEffect != NULL) {
+			vector<F32> vertexEffectUVs;
+			vector<spColor> vertexEffectColors;
+			for (int j = 0; j < verticesCount; j++) {
+				spColor vertexColor = light;
+				spColor dark;
+				dark.r = dark.g = dark.b = dark.a = 0;
+				int index = j << 1;
+				float x = vertices[index];
+				float y = vertices[index + 1];
+				float u = uvs[index];
+				float v = uvs[index + 1];
+				mVertexEffect->transform(mVertexEffect, &x, &y, &u, &v, &vertexColor, &dark);
+				vertices[index] = x;
+				vertices[index + 1] = y;
+				vertexEffectUVs.push_back(u);
+				vertexEffectUVs.push_back(v);
+				vertexEffectColors.push_back(vertexColor);
+			}
+
+			for (int j = 0; j < indicesCount; ++j) {
+				int index = indices[j] << 1;
+				F32 x = vertices[index];
+				F32 y = vertices[index + 1];
+				spColor vertexColor = vertexEffectColors[index >> 1];
+
+				pDrawData->vertexArray[j].Set(x, y);
+				pDrawData->textureArray[j].Set(vertexEffectUVs[index], vertexEffectUVs[index + 1]);
+				pDrawData->colorArray[j].red = vertexColor.r;
+				pDrawData->colorArray[j].green = vertexColor.g;
+				pDrawData->colorArray[j].blue = vertexColor.b;
+				pDrawData->colorArray[j].alpha = vertexColor.a;
+
+				spriteAABB.lowerBound.x = x < spriteAABB.lowerBound.x ? x : spriteAABB.lowerBound.x;
+				spriteAABB.lowerBound.y = y < spriteAABB.lowerBound.y ? y : spriteAABB.lowerBound.y;
+				spriteAABB.upperBound.x = x > spriteAABB.upperBound.x ? x : spriteAABB.upperBound.x;
+				spriteAABB.upperBound.y = y > spriteAABB.upperBound.y ? y : spriteAABB.upperBound.y;
+			}
+		}
+		else {
+			for (int j = 0; j < indicesCount; ++j) {
+				int index = indices[j] << 1;
+				F32 x = vertices[index];
+				F32 y = vertices[index + 1];
+
+				pDrawData->vertexArray[j].Set(vertices[index], vertices[index + 1]);
+				pDrawData->textureArray[j].Set(uvs[index], uvs[index + 1]);
+				pDrawData->colorArray[j].red = r;
+				pDrawData->colorArray[j].green = g;
+				pDrawData->colorArray[j].blue = b;
+				pDrawData->colorArray[j].alpha = a;
+
+				spriteAABB.lowerBound.x = x < spriteAABB.lowerBound.x ? x : spriteAABB.lowerBound.x;
+				spriteAABB.lowerBound.y = y < spriteAABB.lowerBound.y ? y : spriteAABB.lowerBound.y;
+				spriteAABB.upperBound.x = x > spriteAABB.upperBound.x ? x : spriteAABB.upperBound.x;
+				spriteAABB.upperBound.y = y > spriteAABB.upperBound.y ? y : spriteAABB.upperBound.y;
+			}
+		}
+
+		// Save the sprite's aabb on the sprite.
+		F32 ev[]{
+			spriteAABB.upperBound.x, spriteAABB.upperBound.y,	//LL
+			spriteAABB.lowerBound.x, spriteAABB.upperBound.y,	//LR
+			spriteAABB.lowerBound.x, spriteAABB.lowerBound.y,	//UR
+			spriteAABB.upperBound.x, spriteAABB.lowerBound.y };	//UL
+		pSprite->setExplicitVertices(ev);
+
+		pSprite->setTriangleRun(true);
+		pSprite->setImage(assetId, attachmentName.c_str());
+
+		spSkeletonClipping_clipEnd(mSkeletonClipping.get(), slot);
+
+		if (pCollisionProxy) {
+			// Center collision box on attachment.
+			pCollisionProxy->setPosition(
+				Vector2((spriteAABB.lowerBound.x + spriteAABB.upperBound.x) * 0.5f,
+				(spriteAABB.lowerBound.y + spriteAABB.upperBound.y) * 0.5f)
+			);
+			pCollisionProxy->setAngle(
+				mDegToRad(spBone_localToWorldRotation(slot->bone, slot->bone->rotation + pCollisionProxy->mRotation))
+			);
+		}
+
+		// Capture this sprite's vertices for OOBB calculation later.
+		pointSoup.insert(pointSoup.end(), pDrawData->vertexArray.begin(), pDrawData->vertexArray.end());
+	}
+
+	spSkeletonClipping_clipEnd2(mSkeletonClipping.get());
+
+	if (mVertexEffect)
+		mVertexEffect->end(mVertexEffect);
+
+	calculateSpineOOBB(pointSoup);
+}
+
+
+//-----------------------------------------------------------------------------
+// This spins through all of the spine object's drawn points and calculates its OOBB.
+// It then updates the location of the SceneObject to center it over the spine object.
+void SpineObject::calculateSpineOOBB(const vector<Vector2> pointSoup) {
+
+	if (!pointSoup.size()) {
+		setSize(Vector2(0.0f, 0.0f));
+		setLocalExtentsDirty();
+		updateLocalExtents();
+		return;
+	}
+
+	// First step is to get the AABB.
+	b2AABB oobb = getLocalAABB();
+
+	if (getAngle() == 0.0f) {
+		// The OOBB coincides with the AABB so we are done.
+		setSize(oobb.upperBound - oobb.lowerBound);
+	}
+	else {
+		// Second step is to rotate the soup about its center to axis align it and
+		// grab its AABB.
+		b2Rot rotation(-getAngle());
+		b2Transform t(oobb.GetCenter(), rotation);
+
+		b2Vec2 pp = CoreMath::mRotateAboutArbitraryPoint(t, pointSoup[0]);
+		oobb.lowerBound.Set(pp.x, pp.y);
+		oobb.upperBound.Set(pp.x, pp.y);
+		for (const auto& p : pointSoup) {
+			pp = CoreMath::mRotateAboutArbitraryPoint(t, p);
+			oobb.lowerBound.x = pp.x < oobb.lowerBound.x ? pp.x : oobb.lowerBound.x;
+			oobb.lowerBound.y = pp.y < oobb.lowerBound.y ? pp.y : oobb.lowerBound.y;
+			oobb.upperBound.x = pp.x > oobb.upperBound.x ? pp.x : oobb.upperBound.x;
+			oobb.upperBound.y = pp.y > oobb.upperBound.y ? pp.y : oobb.upperBound.y;
+		}
+
+		// We now have the updated AABB.  This is what the SceneObject needs, so 
+		// pass it in.
+		updateLocalExtents(&oobb);
+		setSize(oobb.upperBound - oobb.lowerBound);
+
+		// Third step is to complete calculation of the OOBB by aligning it back to 
+		// the spine object.
+		t.q.Set(getAngle());
+		oobb.lowerBound = CoreMath::mRotateAboutArbitraryPoint(t, oobb.lowerBound);
+		oobb.upperBound = CoreMath::mRotateAboutArbitraryPoint(t, oobb.upperBound);
+	}
+
+	// Reposition the SceneObject so that it is centered over the spine object.
+	Vector2 actualCenter = oobb.GetCenter();
+	Vector2 sceneObjectCenter = getPosition();
+
+	Vector2 delta = actualCenter - sceneObjectCenter;
+
+	// Nullify the reposition of the SceneObject by incorporating its inverse 
+	// into the spine object's position.
+	mAutoCenterOffset.x += delta.x;
+	mAutoCenterOffset.y += delta.y;
+
+	// Handle special case of the spine object being 'flipped' in either axis.  When
+	// that happens, we want to keep the location of the root bone at roughly the
+	// same position on screen as it was prior to the flip.  This helps to reduce 
+	// a jarring reposition of the root bone.
+	bool doFlipRecurse = false;
+
+	if (mPriorFlipX == mFlipX) {
+		mPriorRootBoneWorldX = mSkeleton->root->worldX;
+	}
+	else {
+		// Compensate for the flip in X (about the y axis).  This is an approximation
+		// that will fail if the velocity of the spine object is large.  If that 
+		// becomes a problem, incorporate the velocity delta into the 'prior world x'
+		// to have the compensated position reflect the underlying movement.
+		mPriorFlipX = mFlipX;
+		doFlipRecurse = true;
+
+		delta.x += mPriorRootBoneWorldX - mSkeleton->root->worldX;
+	}
+	if (mPriorFlipY == mFlipY) {
+		mPriorRootBoneWorldY = mSkeleton->root->worldY;
+	}
+	else {
+		mPriorFlipY = mFlipY;
+		doFlipRecurse = true;
+
+		delta.y += mPriorRootBoneWorldY - mSkeleton->root->worldY;
+	}
+
+	setPosition(sceneObjectCenter + delta);
+
+	if (doFlipRecurse) {
+		// Trash the current render data and recalculate it.  This avoids a flash
+		// artifact due to possibly large changes coming from the flip.
+		this->clearSprites();
+		updateSpine(mLastFrameTime);
+		prepareSpineForRender();
+	}
+}
+
+//-----------------------------------------------------------------------------
+
+const SpineCollisionProxy* SpineObject::getCollisionProxy(
+	const char* anAttachmentName,
+	const char* aSlotName,
+	const char* aSkinName,
+	const F32 sizerWidth,
+	const F32 sizerHeight,
+	const char* objectName) {
+
+	// First, locate the requested attachment.
+	spAttachment* pAttachment = NULL;
+	auto attachmentName = StringTable->insert(anAttachmentName, true);
+
+	auto slotName = StringTable->insert(aSlotName, true);
+	auto pSlotData = spSkeletonData_findSlot(mSkeleton->data, slotName);
+	if (!pSlotData) {
+		Con::warnf("SpineObject::getCollisionProxy - Slot not found: '%s'.", slotName);
+		return NULL;
+	}
+
+	auto skinName = StringTable->insert(aSkinName, true);
+	if (skinName == StringTable->EmptyString || dStricmp(skinName, "default") == 0) {
+		// Look in the currently active skin and then the default skin if needed.
+		pAttachment = spSkeleton_getAttachmentForSlotIndex(mSkeleton.get(), pSlotData->index, attachmentName);
+		if (!pAttachment) {
+			Con::warnf("SpineObject::getCollisionProxy - Attachment not found: '%s'.", attachmentName);
+			return NULL;
+		}
+	}
+	else {
+		// Attachment must exist in the skin name given or else it's an error.
+		auto pSkin = spSkeletonData_findSkin(mSkeleton->data, skinName);
+		if (!pSkin) {
+			Con::warnf("SpineObject::getCollisionProxy - Skin not found: '%s'.", skinName);
+			return NULL;
+		}
+
+		pAttachment = spSkin_getAttachment(pSkin, pSlotData->index, attachmentName);
+		if (!pAttachment) {
+			Con::warnf("SpineObject::getCollisionProxy - Attachment not found in requested skin.\nskin: '%s'.  attachment: '%s'.", skinName, attachmentName);
+			return NULL;
+		}
+	}
+
+	// Check that the attachment is a supported type.
+	if (pAttachment->type != spAttachmentType::SP_ATTACHMENT_REGION && pAttachment->type != spAttachmentType::SP_ATTACHMENT_MESH) {
+		Con::warnf("SpineObject::getCollisionProxy - Attachment requested is of an unsupported type.  Only Region and Mesh attachments are supported.");
+		return NULL;
+	}
+
+	// objectName is not considered here.  Multiple proxies for the same attachment is 
+	// not supported.
+	auto itr = mCollisionProxies.find(pAttachment);
+	if (itr != mCollisionProxies.end()) {
+		return itr->value;
+	}
+
+	auto pBone = spSkeleton_findBone(mSkeleton.get(), pSlotData->boneData->name);
+	if (!pBone) {
+		Con::warnf("SpineObject::getCollisionProxy - Unable to find bone required by slot data: '%s'.", pSlotData->boneData->name);
+		return NULL;
+	}
+
+	// Create new proxy object.
+	auto pProxy = new SpineCollisionProxy();
+
+	// Add to the internal collection
+	mCollisionProxies.insert(pAttachment, pProxy);
+
+	// Initialize the proxy
+	F32 width, height = 0.0f;
+	if (pAttachment->type == SP_ATTACHMENT_REGION) {
+		auto regionAttachment = (spRegionAttachment*)pAttachment;
+
+		width = regionAttachment->width * regionAttachment->scaleX * spBone_getWorldScaleX(pBone) * sizerWidth;
+		height = regionAttachment->height * regionAttachment->scaleY * spBone_getWorldScaleY(pBone) * sizerHeight;
+
+		pProxy->mRotation = regionAttachment->rotation;
+	}
+	else {
+		auto meshAttachment = (spMeshAttachment*)pAttachment;
+
+		width = meshAttachment->width * spBone_getWorldScaleX(pBone) * sizerWidth;
+		height = meshAttachment->height * spBone_getWorldScaleY(pBone) * sizerHeight;
+
+		auto currentRegion = (spAtlasRegion *)meshAttachment->rendererObject;
+		pProxy->mRotation = currentRegion->rotate ? 90.0f : 0.0f;
+	}
+
+	// Set box location to a galaxy far far away... 
+	// NOTE: The box is auto-centered over the attachment at render.  
+	pProxy->setPosition(Vector2(5000, CoreMath::mGetRandomF(-5000.0, 5000.0)));
+
+	// Set layer and group membership to that of the owning spine object.
+	pProxy->setSceneGroup(getSceneGroup());
+	pProxy->setSceneLayer(getSceneLayer());
+
+	// Register the proxy object.  This adds it to the runtime environment.
+	if (objectName) {
+		if (!pProxy->registerObject(objectName)) {
+			return NULL;
+		}
+	}
+	else {
+		if (!pProxy->registerObject()) {
+			return NULL;
+		}
+	}
+
+	// Create collision fixture and enable collision callback support.
+	auto fixtureIndex = pProxy->createPolygonBoxCollisionShape(width * mSkeleton->scaleX, height * mSkeleton->scaleY);
+	pProxy->setCollisionShapeIsSensor(fixtureIndex, true);
+	pProxy->setCollisionCallback(true);
+
+	pProxy->setSleepingAllowed(false);
+
+	// Disable collision box from colliding against other collision boxes in the spine object's scene group.
+	// The intent is to stop a character's own collision boxes from setting each other off.  Different characters
+	// should be in different groups.  That's the idea.  Maybe, should just let all the contacts go through and let
+	// them be handled/ignored on the script side.
+	auto groupMask = pProxy->getCollisionGroupMask();
+	groupMask &= ~(1 << getSceneGroup()); // Clears bit in mask corresponding to the spine object's group.
+	pProxy->setCollisionGroupMask(groupMask);
+
+	// Deactivate.  If should be active, it will be made so at render time.
+	pProxy->deActivate();
+
+	getScene()->addToScene(pProxy);
+
+	return pProxy;
+}
+
+//-----------------------------------------------------------------------------
+
+bool SpineObject::deleteCollisionProxy(const char *proxyId) {
+
+	AssertFatal(proxyId != NULL, "SpineObject::deleteCollisionProxy() - Recieved NULL collision proxy object id.");
+
+	SimObjectId idToDelete = dAtoi(proxyId);
+
+	// Locate the requested object
+	for (const auto& i : mCollisionProxies) {
+		if (i.value->getId() == idToDelete) {
+			// Delete it
+			getScene()->removeFromScene(i.value);
+			i.value->deleteObject();
+
+			mCollisionProxies.erase(i.key);
+			return true;
+		}
+	}
+
+	Con::warnf("SpineObject::deleteCollisionProxy() - Unable to locate proxy with given id: '%s'.", proxyId);
+	return false;
+}

+ 293 - 0
engine/source/2d/sceneobject/SpineObject.h

@@ -0,0 +1,293 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _SPINE_OBJECT_H_
+#define _SPINE_OBJECT_H_
+
+#ifndef _FRAMEALLOCATOR_H_
+#include "memory/frameAllocator.h"
+#endif
+
+#ifndef _SPRITE_BATCH_H_
+#include "2d/core/SpriteBatch.h"
+#endif
+
+#ifndef _SCENE_OBJECT_H_
+#include "2d/sceneobject/SceneObject.h"
+#endif
+
+#ifndef _SPINE_ASSET_H_
+#include "2d/assets/SpineAsset.h"
+#endif
+
+#ifndef _SPINE_COLLISION_PROXY_H_
+#include "2d/sceneobject/SpineCollisionProxy.h"
+#endif
+
+//------------------------------------------------------------------------------
+// Spine events support
+static void spineAnimationEventCallbackHandler(spAnimationState* state, spEventType type, spTrackEntry* entry, spEvent* event);
+
+//------------------------------------------------------------------------------
+
+static const b2Transform B2_IDENTITY_TRANSFORM = b2Transform(b2Vec2(0.0f, 0.0f), b2Rot(0.0f));
+
+class SpineObject : public SceneObject, public SpriteBatch
+{
+protected:
+	typedef SceneObject Parent;
+
+public:
+	// Smart pointer support
+	struct SkeletonDeleter { void operator()(spSkeleton* p) { spSkeleton_dispose(p); } };
+	struct AnimationStateDeleter { void operator()(spAnimationState* p) { p->listener = NULL; spAnimationState_disposeStatics(); spAnimationState_dispose(p); } };
+	struct SkeletonClippingDeleter { void operator()(spSkeletonClipping* p) { spSkeletonClipping_dispose(p); } };
+	struct SkeletonBoundsDeleter { void operator()(spSkeletonBounds* p) { spSkeletonBounds_dispose(p); } };
+	struct JitterVertexDeleter { void operator()(spJitterVertexEffect* p) { spJitterVertexEffect_dispose(p); } };
+	struct SwirlVertexDeleter { void operator()(spSwirlVertexEffect* p) { spSwirlVertexEffect_dispose(p); } };
+
+	using skeleton_ptr = unique_ptr<spSkeleton, SkeletonDeleter>;
+	using animationState_ptr = unique_ptr<spAnimationState, AnimationStateDeleter>;
+	using skeletonClipping_ptr = unique_ptr<spSkeletonClipping, SkeletonClippingDeleter>;
+	using skeletonBounds_ptr = unique_ptr<spSkeletonBounds, SkeletonBoundsDeleter>;
+	using jitterEffect_ptr = unique_ptr<spJitterVertexEffect, JitterVertexDeleter>;
+	using swirlEffect_ptr = unique_ptr<spSwirlVertexEffect, SwirlVertexDeleter>;
+
+	// Can't use smart pointers for this because HashTable calls delete on the value members in its destructor.
+	using SpineCollisionProxyMapType = HashMap<spAttachment*, SpineCollisionProxy*>;
+
+	enum VertexEffect {
+		INVALID_VERTEX_EFFECT,
+
+		NONE,
+		JITTER,
+		SWIRL
+	};
+private:
+	AssetPtr<SpineAsset>			mSpineAsset; /* SpineObject holds instance data, SpineAsset holds template data. */
+
+	skeleton_ptr					mSkeleton;
+	animationState_ptr				mAnimationState;
+	skeletonClipping_ptr			mSkeletonClipping;
+	skeletonBounds_ptr				mSkeletonBounds;
+
+	// Special vertex effects provided by Spine
+	spVertexEffect					*mVertexEffect;  // Only one effect can be active at a time. This points to it. Null if none active.
+	VertexEffect					mActiveEffect;
+	jitterEffect_ptr				mJitterControl;
+	swirlEffect_ptr					mSwirlControl;
+
+	F32               mPreTickTime;
+	F32               mPostTickTime;
+	F32               mLastFrameTime;
+
+	bool              mFlipX;
+	bool              mFlipY;
+
+	// OOBB calculation support
+	F32				  mPriorRootBoneWorldX;
+	F32				  mPriorRootBoneWorldY;
+	bool			  mPriorFlipX;
+	bool			  mPriorFlipY;
+
+	// Auto-Center support
+	Vector2			  mAutoCenterOffset;
+
+	// Collision support
+	SpineCollisionProxyMapType mCollisionProxies;
+
+public:
+	SpineObject() { resetState(); };
+
+	// Use CopyTo() if need to replicate.
+	SpineObject(const SpineObject&) = delete;
+	SpineObject& SpineObject::operator=(const SpineObject&) = delete;
+
+	// Add 'move' support if/when needed.
+	SpineObject(SpineObject&& other) = delete;
+	SpineObject& SpineObject::operator=(SpineObject&&) = delete;
+
+	virtual void copyTo(SimObject* object);
+
+	bool setSpineAsset(const char* pSpineAssetId);
+	inline StringTableEntry getSpineAsset(void) const { return mSpineAsset.getAssetId(); }
+
+	// Render flipping.
+	void setFlip(const bool flipX, const bool flipY);
+	void setFlipX(const bool flipX) { setFlip(flipX, mFlipY); }
+	void setFlipY(const bool flipY) { setFlip(mFlipX, flipY); }
+	inline bool getFlipX(void) const { return mFlipX; }
+	inline bool getFlipY(void) const { return mFlipY; }
+
+	// Render special effects
+	inline VertexEffect getActiveEffect(void) const { return mActiveEffect; }
+
+	// -- Jitter
+	void enableJitter(const F32 x, const F32 y);
+	void disableJitter(void);
+	// -- These can be used by script at run time to vary the effect.
+	inline F32 getJitterX(void) const { return mJitterControl ? mJitterControl->jitterX : 0.0f; }
+	inline void setJitterX(const F32 x) { if (mJitterControl) mJitterControl->jitterX = x; }
+	inline F32 getJitterY(void) const { return mJitterControl ? mJitterControl->jitterY : 0.0f; }
+	inline void setJitterY(const F32 y) { if (mJitterControl) mJitterControl->jitterY = y; }
+
+	// -- Swirl
+	void enableSwirl(const F32 radius);
+	void disableSwirl(void);
+	// -- These can be used by script at run time to vary the effect.
+	inline F32 getSwirlX(void) const { return mSwirlControl ? mSwirlControl->centerX : 0.0f; }
+	inline void setSwirlX(const F32 x) { if (mSwirlControl) mSwirlControl->centerX = x; }
+	inline F32 getSwirlY(void) const { return mSwirlControl ? mSwirlControl->centerY : 0.0f; }
+	inline void setSwirlY(const F32 y) { if (mSwirlControl) mSwirlControl->centerY = y; }
+	inline F32 getSwirlRadius(void) const { return mSwirlControl ? mSwirlControl->radius : 0.0f; }
+	inline void setSwirlRadius(const F32 r) { if (mSwirlControl) mSwirlControl->radius = r; }
+	inline F32 getSwirlAngle(void) const { return mSwirlControl ? mSwirlControl->angle : 0.0f; }
+	inline void setSwirlAngle(const F32 a) { if (mSwirlControl) mSwirlControl->angle = a; }
+
+	// Appearance
+	void setScale(const Vector2& scale);
+	inline void setScale(const F32 x, const F32 y) { setScale(Vector2(x, y)); }
+	inline Vector2 getScale(void) const { return mSkeleton ? Vector2(mSkeleton->scaleX, mSkeleton->scaleY) : Vector2(0.0f, 0.0f); }
+
+	bool setSkin(const char* pSkin);
+	inline StringTableEntry getSkinName(void) const;
+
+	// Animation
+	inline F32 setTimeScale(const F32 timeScale);
+	inline F32 getTimeScale(void) const { return mAnimationState ? mAnimationState->timeScale : 0.0f; }
+
+	bool setAnimation(const char* pName, const int track = 0, const bool shouldLoop = false, const F32 mixDuration = -1.0f);
+	bool setEmptyAnimation(const int track = 0, const F32 mixDuration = 0.0f);
+	bool queueAnimation(const char* pName, const int track = 0, const bool shouldLoop = false, const F32 mixDuration = -1.0f, const F32 delay = 0.0f);
+	bool queueEmptyAnimation(const int track = 0, const F32 mixDuration = 0.0f, const F32 delay = 0.0f);
+	void clearAnimations(const int track, const bool mixToSetupPose = false, const F32 mixDuration = 0.0f);
+	void clearAllAnimations(const bool mixToSetupPose = false, const F32 mixDuration = 0.0f);
+	StringTableEntry getAnimationName(const int track = 0) const;
+	bool getIsLooping(const int track = 0) const;
+	bool setMix(const char* pFromName, const char* pToName, const F32 mixDuration);
+
+	// Events
+	void SpineObject::enableEventCallbacks(void);
+	void SpineObject::disableEventCallbacks(void);
+
+	// Collision Support
+	const SpineCollisionProxy* getCollisionProxy(
+		const char* anAttachmentName,
+		const char* aSlotName,
+		const char* aSkinName = "default",
+		const F32 sizerWidth = 1.0f,
+		const F32 sizerHeight = 1.0f,
+		const char* objectName = NULL);
+	bool deleteCollisionProxy(const char *proxyId);
+
+	// This needs to be available to the Console for use during object instantiation.
+	static void initPersistFields();
+
+	/// Declare Console Object.
+	DECLARE_CONOBJECT(SpineObject);
+
+protected:
+	// Render suport
+	void updateSpine(const F32 time);
+	void prepareSpineForRender();
+	void calculateSpineOOBB(const vector<Vector2> pointSoup);
+
+	virtual void preIntegrate(const F32 totalTime, const F32 elapsedTime, DebugStats* pDebugStats);
+	virtual void interpolateObject(const F32 timeDelta);
+
+	virtual bool canPrepareRender(void) const { return true; }
+	virtual bool validRender(void) const { return mSpineAsset.notNull(); }
+	virtual bool shouldRender(void) const { return true; }
+	virtual void scenePrepareRender(const SceneRenderState* pSceneRenderState, SceneRenderQueue* pSceneRenderQueue);
+	virtual void sceneRender(const SceneRenderState* pSceneRenderState, const SceneRenderRequest* pSceneRenderRequest, BatchRender* pBatchRenderer);
+
+	// Render special effects - support
+	// Set indirectly via enable methods.  However, need this to support TAML reading of object.
+	void setActiveEffect(const VertexEffect e);
+
+	static VertexEffect getVertexEffectTypeEnum(const char* label);
+	static const char* getVertexEffectTypeDescription(const VertexEffect vertexEffectType);
+
+	// Internal management
+	void SpineObject::resetState();
+
+private:
+	// Utility 
+
+	// Cope with spine reflecting about an axis to accomplish a flip, while the SceneObject doesn't support such flipping.
+	inline F32 setPerFlipState(const F32 value) { return mFlipY ? (mFlipX ? value : -value) : (mFlipX ? -value : value); }
+
+	// This is only used during TAML loading. It buffers the value needed when setting the requested animation.
+	bool mShouldLoop;
+
+protected:
+	static bool setSpineAsset(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setSpineAsset(data); return false; }
+	static bool writeSpineAsset(void* obj, StringTableEntry pFieldName) { return static_cast<SpineObject*>(obj)->mSpineAsset.notNull(); }
+
+	static bool setShouldLoop(void* obj, const char* data) { static_cast<SpineObject*>(obj)->mShouldLoop = dAtob(data); return false; }
+	static bool setAnimation(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setAnimation(data, static_cast<SpineObject*>(obj)->mShouldLoop); return false; }
+	static const char* getAnimationName(void* obj, const char* data) { return static_cast<SpineObject*>(obj)->getAnimationName(); }
+	static bool writeAnimationData(void*obj, const char* data) { return static_cast<SpineObject*>(obj)->getAnimationName() != StringTable->EmptyString; }
+
+	static bool setSkin(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setSkin(data); return false; }
+	static const char* getSkinName(void* obj, const char* data) { return static_cast<SpineObject*>(obj)->getSkinName(); }
+	static bool writeCurrentSkin(void*obj, StringTableEntry pSkin) { return true; }
+
+	static bool setScale(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setScale(Vector2(data)); return false; }
+	static const char* getScale(void* obj, const char* data) { return static_cast<SpineObject*>(obj)->getScale().scriptThis(); }
+	static bool writeScale(void* obj, StringTableEntry pFieldName) { return static_cast<SpineObject*>(obj)->getScale().notZero(); }
+
+	static bool setFlipX(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setFlipX(dAtob(data)); return false; }
+	static bool writeFlipX(void* obj, StringTableEntry pFieldName) { return static_cast<SpineObject*>(obj)->getFlipX() == true; }
+
+	static bool setFlipY(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setFlipY(dAtob(data)); return false; }
+	static bool writeFlipY(void* obj, StringTableEntry pFieldName) { return static_cast<SpineObject*>(obj)->getFlipY() == true; }
+
+	static bool setTimeScale(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setTimeScale(dAtof(data)); return false; }
+	static const char* getTimeScale(void* obj, const char* data) { return Con::getFloatArg(static_cast<SpineObject*>(obj)->getTimeScale()); }
+	static bool writeTimeScale(void* obj, StringTableEntry pFieldName) { return static_cast<SpineObject*>(obj)->getTimeScale() != 1.0f; }
+
+	static bool setActiveEffectType(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setActiveEffect(getVertexEffectTypeEnum(data)); return false; }
+	static bool writeActiveEffectType(void* obj, StringTableEntry pFieldName) { VertexEffect ve = static_cast<SpineObject*>(obj)->getActiveEffect(); return ve != NONE && ve != INVALID_VERTEX_EFFECT; }
+
+	static bool setJitterX(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setJitterX(dAtof(data)); return false; }
+	static const char* getJitterX(void* obj, const char* data) { return Con::getFloatArg(static_cast<SpineObject*>(obj)->getJitterX()); }
+	static bool setJitterY(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setJitterY(dAtof(data)); return false; }
+	static const char* getJitterY(void* obj, const char* data) { return Con::getFloatArg(static_cast<SpineObject*>(obj)->getJitterY()); }
+	static bool writeJitterEffectValues(void* obj, StringTableEntry pFieldName) { return static_cast<SpineObject*>(obj)->getActiveEffect() == VertexEffect::JITTER; }
+
+	static bool setSwirlX(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setSwirlX(dAtof(data)); return false; }
+	static const char* getSwirlX(void* obj, const char* data) { return Con::getFloatArg(static_cast<SpineObject*>(obj)->getSwirlX()); }
+	static bool setSwirlY(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setSwirlY(dAtof(data)); return false; }
+	static const char* getSwirlY(void* obj, const char* data) { return Con::getFloatArg(static_cast<SpineObject*>(obj)->getSwirlY()); }
+	static bool setSwirlRadius(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setSwirlRadius(dAtof(data)); return false; }
+	static const char* getSwirlRadius(void* obj, const char* data) { return Con::getFloatArg(static_cast<SpineObject*>(obj)->getSwirlRadius()); }
+	static bool setSwirlAngle(void* obj, const char* data) { static_cast<SpineObject*>(obj)->setSwirlAngle(dAtof(data)); return false; }
+	static const char* getSwirlAngle(void* obj, const char* data) { return Con::getFloatArg(static_cast<SpineObject*>(obj)->getSwirlAngle()); }
+	static bool writeSwirlEffectValues(void* obj, StringTableEntry pFieldName) { return static_cast<SpineObject*>(obj)->getActiveEffect() == VertexEffect::SWIRL; }
+
+	static bool setEventCallbacksEnabled(void* obj, const char* data) { if (dAtob(data)) { static_cast<SpineObject*>(obj)->enableEventCallbacks(); } else { static_cast<SpineObject*>(obj)->disableEventCallbacks(); } return false; }
+	static bool writeEventCallbacksEnabled(void* obj, StringTableEntry pFieldName) { SpineObject *me = static_cast<SpineObject*>(obj); return me->mAnimationState && me->mAnimationState->listener; }
+
+};
+
+#endif // _SPINE_OBJECT_H_

+ 688 - 0
engine/source/2d/sceneobject/SpineObject_ScriptBinding.h

@@ -0,0 +1,688 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+ConsoleMethodGroupBeginWithDocs(SpineObject, SceneObject)
+
+/*! Sets the spine asset Id to use.
+	@param spineAssetId Integer - The spine asset Id to use.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, setSpineAsset, ConsoleVoid, 3, 3, (int spineAssetId))
+{
+	object->setSpineAsset(argv[2]);
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Gets the spine asset Id.
+	@return Integer - The spine asset Id.
+*/
+ConsoleMethodWithDocs(SpineObject, getSpineAsset, ConsoleString, 2, 2, ())
+{
+	return object->getSpineAsset();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Sets the animation time scale factor.
+	@param timeScale Float - The factor by which to multiply the base time scale as
+	set in the animation.
+	@return Float - The previous value of the time scale.
+*/
+ConsoleMethodWithDocs(SpineObject, setTimeScale, ConsoleFloat, 3, 3, (float timeScale))
+{
+	return object->setTimeScale(dAtof(argv[2]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Gets the animation time scale.
+	@return Float - The current animation time factor.
+*/
+ConsoleMethodWithDocs(SpineObject, getTimeScale, ConsoleFloat, 2, 2, ())
+{
+	return object->getTimeScale();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Sets the animation for the object.
+	@param animationName String - containing animation name to run.
+	@param track Int - Optional. Track to run animation in.  Defaults to zero.
+	@param loop Bool - Optional.  Determines whether the animation should loop. Defaults to false.
+	@param mixDuration Float - Optional.  The amount of time in seconds to transition (or mix) from the
+	previously running animation to this one being set.  If not set, it defaults to the value set
+	using the setMix() method.  If nothing was defined via setMix, then it defaults to zero, which means
+	no transition, an abrupt change.
+	@return Return Bool - True on success.
+*/
+ConsoleMethodWithDocs(SpineObject, setAnimation, ConsoleBool, 3, 6, (char *animationName, [int track, bool loop, float mixDuration]))
+{
+	// Determine looping
+	int track = argc >= 4 ? dAtoi(argv[3]) : 0;
+	bool shouldLoop = argc >= 5 ? dAtob(argv[4]) : false;
+	F32 mixDuration = argc >= 6 ? dAtof(argv[5]) : -1.0f;
+	return object->setAnimation(argv[2], track, shouldLoop, mixDuration);
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Sets the empty animation on the object.  This is mainly used to fade into and out of the pose
+	position, but can be used for whatever.
+	@param track Int - Optional. Track to run animation in.  Defaults to zero.
+	@param mixDuration Float - Optional.  The amount of time in seconds to transition from the
+	currently running animation to this one.
+	@return Return Bool - True on success.
+*/
+ConsoleMethodWithDocs(SpineObject, setEmptyAnimation, ConsoleBool, 2, 4, ([int track, float mixDuration]))
+{
+	int track = argc >= 3 ? dAtoi(argv[2]) : 0;
+	F32 mixDuration = argc >= 4 ? dAtof(argv[3]) : 0.0f;
+	return object->setEmptyAnimation(track, mixDuration);
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Queues the animation for the object.  The queued animation will play after the currently
+	playing	animation completes.
+	@param animationName String - Contains the name of the animation to queue.
+	@param track Int - Optional. Track to run in.  Defaults to zero.  Animations can be layered in
+	different tracks.
+	@param loop Boolean - Optional. Indicates if animation should loop once it's processed.
+	@param mixDuration Float - Optional.  The amount of time in seconds to transition from the
+	currently running animation to this one.
+	@param delay Float - Optional.  Indicates a lag (leading or trailing) between this and
+	the currently running animation.  In seconds.
+	@return Returns Bool - True on success.
+*/
+ConsoleMethodWithDocs(SpineObject, queueAnimation, ConsoleBool, 3, 7, (char *animationName, [int track, bool loop, float mixDuration, float delay]))
+{
+	int track = argc >= 4 ? dAtoi(argv[3]) : 0;
+	bool shouldLoop = argc >= 5 ? dAtob(argv[4]) : false;
+	F32 mixDuration = argc >= 6 ? dAtof(argv[5]) : -1.0f;
+	F32 delay = argc >= 7 ? dAtof(argv[6]) : 0.0f;
+	return object->queueAnimation(argv[2], track, shouldLoop, mixDuration, delay);
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Queues the empty animation on the object.  Useful for fading and transitions.
+	@param track Int - Optional. Track to run animation in.  Defaults to zero.
+	@param mixDuration Float - Optional.  The amount of time in seconds to transition from the
+	currently running animation to this one.
+	@param delay Float - Optional.  Indicates a lag (leading or trailing) between this and
+	the currently running animation.  In seconds.  Defaults to zero.
+	@return Return Bool - True on success.
+*/
+ConsoleMethodWithDocs(SpineObject, queueEmptyAnimation, ConsoleBool, 2, 5, ([int track, float mixDuration, float delay]))
+{
+	int track = argc >= 3 ? dAtoi(argv[2]) : 0;
+	F32 mixDuration = argc >= 4 ? dAtof(argv[3]) : 0.0f;
+	F32 delay = argc >= 5 ? dAtof(argv[4]) : 0.0f;
+	return object->queueEmptyAnimation(track, mixDuration, delay);
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Clears animations (running and queued) from the given track.
+	@param track Int - Optional. Track to clear animations from.  Defaults to zero.
+	@param mixToSetup Bool - Optional. If true the track will fade back to the setup pose
+	over the number of seconds specified in the mixDuration argument.
+	@param mixDuration Float - Optional.  The amount of time in seconds to transition back to
+	the setup pose.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, clearAnimations, ConsoleVoid, 2, 5, ([int track, bool mixToSetup, float mixDuration]))
+{
+	int track = argc >= 3 ? dAtoi(argv[2]) : 0;
+	bool mixToSetup = argc >= 4 ? dAtob(argv[3]) : false;
+	F32 mixDuration = argc >= 5 ? dAtof(argv[4]) : 0.0f;
+	object->clearAnimations(track, mixToSetup, mixDuration);
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Clears animations (running and queued) from all tracks.
+	@param mixToSetup Bool - Optional. If true the character will fade back to the setup pose
+	over the number of seconds specified in the mixDuration argument.
+	@param mixDuration Float - Optional.  The amount of time in seconds to transition back to
+	the setup pose.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, clearAllAnimations, ConsoleVoid, 2, 4, ([bool mixToSetup, float mixDuration]))
+{
+	bool mixToSetup = argc >= 3 ? dAtob(argv[2]) : false;
+	F32 mixDuration = argc >= 4 ? dAtof(argv[3]) : 0.0f;
+	object->clearAllAnimations(mixToSetup, mixDuration);
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Gets the name of the animation currently running in the given track.
+	@param track Int - Optional. Track to get name from.  Defaults to zero.
+	@return String - Contains the animation name.
+*/
+ConsoleMethodWithDocs(SpineObject, getAnimationName, ConsoleString, 2, 3, ([int track]))
+{
+	int track = argc >= 3 ? dAtoi(argv[2]) : 0;
+	return object->getAnimationName(track);
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Check if the animation currently running on the given track is looping.
+	@param track Int - Optional. Track to check.  Defaults to zero.
+	@return Bool - True if current animation is looping.  False if there is no animation
+	running or there is but it is not looping.
+*/
+ConsoleMethodWithDocs(SpineObject, getIsLooping, ConsoleBool, 2, 3, ([int track]))
+{
+	int track = argc >= 3 ? dAtoi(argv[2]) : 0;
+	return object->getIsLooping(track);
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Sets the animation mix time to be used when transitioning between two
+	animations.  This is the amount of time that will be spent morphing
+	between the from and to animations.
+	@param fromAnimation String - The name of the animation transitioning from.
+	@param toAnimation String - The name of the animation transitioning to.
+	@param mixDuration Float - The number of seconds to spend in the transition.
+	@return Returns Bool - True on success.
+*/
+ConsoleMethodWithDocs(SpineObject, setMix, ConsoleBool, 5, 5, (char *fromAnimation, char *toAnimation, float mixDuration))
+{
+	Con::printf("Mixing %s with %s at %f", argv[2], argv[3], dAtof(argv[4]));
+
+	return object->setMix(argv[2], argv[3], dAtof(argv[4]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Sets the skin for the spine object.
+	@param skinName String - Name of the skin (as defined in the animation data) to apply.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, setSkin, ConsoleVoid, 3, 3, (char *skinName))
+{
+	object->setSkin(argv[2]);
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Gets the name of the skin currently applied to the spine object.
+	@return String - The currently applied skin name.
+*/
+ConsoleMethodWithDocs(SpineObject, getSkinName, ConsoleString, 2, 2, ())
+{
+	return object->getSkinName();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Sets scaling of the spine object's skeleton.
+	@param scaleX Float - X factor.
+	@param scaleY Float - Y factor.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, setScale, ConsoleVoid, 3, 4, (float scaleX, float scaleY))
+{
+	F32 scaleX, scaleY;
+
+	const U32 elementCount = Utility::mGetStringElementCount(argv[2]);
+
+	// ("width height")
+	if ((elementCount == 2) && (argc == 3))
+	{
+		scaleX = dAtof(Utility::mGetStringElement(argv[2], 0));
+		scaleY = dAtof(Utility::mGetStringElement(argv[2], 1));
+	}
+
+	// (width, [height])
+	else if (elementCount == 1)
+	{
+		scaleX = dAtof(argv[2]);
+
+		if (argc > 3)
+			scaleY = dAtof(argv[3]);
+		else
+			scaleY = scaleX;
+	}
+
+	// Invalid
+	else
+	{
+		Con::warnf("SpineObject::setScale() - Invalid number of parameters!");
+		return;
+	}
+
+	// Set Scale.
+	object->setScale(Vector2(scaleX, scaleY));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Gets the spine object's skeleton scale.
+	@return (float x/y height) The x and y scale of the object's root bone.
+*/
+ConsoleMethodWithDocs(SpineObject, getScale, ConsoleString, 2, 2, ())
+{
+	return object->getScale().scriptThis();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Sets the sprite texture flipping for each axis.
+	@param flipX Bool - Whether or not to flip the texture along the x (horizontal) axis.
+	@param flipY Bool - Whether or not to flip the texture along the y (vertical) axis.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, setFlip, ConsoleVoid, 4, 4, (bool flipX, bool flipY))
+{
+	// Set Flip.
+	object->setFlip(dAtob(argv[2]), dAtob(argv[3]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Gets the flip for each axis.
+	@return (bool flipX/bool flipY) Whether or not the texture is flipped along the x and y axes.
+*/
+ConsoleMethodWithDocs(SpineObject, getFlip, ConsoleString, 2, 2, ())
+{
+	// Create Returnable Buffer.
+	char* pBuffer = Con::getReturnBuffer(32);
+
+	// Format Buffer.
+	dSprintf(pBuffer, 32, "%d %d", object->getFlipX(), object->getFlipY());
+
+	// Return Buffer.
+	return pBuffer;
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Sets whether or not the texture is flipped horizontally.
+	@param flipX Bool - Whether or not to flip the texture along the x (horizontal) axis.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, setFlipX, ConsoleVoid, 3, 3, (bool flipX))
+{
+	// Set Flip.
+	object->setFlipX(dAtob(argv[2]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Sets whether or not the texture is flipped vertically.
+	@param flipY Bool - Whether or not to flip the texture along the y (vertical) axis.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, setFlipY, ConsoleVoid, 3, 3, (bool flipY))
+{
+	// Set Flip.
+	object->setFlipY(dAtob(argv[2]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Gets whether or not the texture is flipped horizontally.
+	@return (bool flipX) Whether or not the texture is flipped along the x axis.
+*/
+ConsoleMethodWithDocs(SpineObject, getFlipX, ConsoleBool, 2, 2, ())
+{
+	return object->getFlipX();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Gets whether or not the texture is flipped vertically.
+	@return (bool flipY) Whether or not the texture is flipped along the y axis.
+*/
+ConsoleMethodWithDocs(SpineObject, getFlipY, ConsoleBool, 2, 2, ())
+{
+	return object->getFlipY();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Enables the Jitter special effect
+	@param JitterX Float - A special effect control parameter.
+	@param JitterY Float - A special effect control parameter.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, enableJitter, ConsoleVoid, 4, 4, (float x, float y))
+{
+	// Enable the special effect
+	object->enableJitter(dAtof(argv[2]), dAtof(argv[3]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Set the Jitter X control value.
+	@param X - Float A special effect control parameter.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, setJitterX, ConsoleVoid, 3, 3, (float x))
+{
+	// Enable the special effect
+	object->setJitterX(dAtof(argv[2]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Get the Jitter X control value.
+	@return The current Jitter effect X control value.  If the Jitter effect is not or
+	has not been enabled, it will return zero.
+*/
+ConsoleMethodWithDocs(SpineObject, getJitterX, ConsoleFloat, 2, 2, ())
+{
+	// Enable the special effect
+	return object->getJitterX();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Set the Jitter Y control value.
+	@param Y Float - A special effect control parameter.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, setJitterY, ConsoleVoid, 3, 3, (float y))
+{
+	// Enable the special effect
+	object->setJitterY(dAtof(argv[2]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Get the Jitter Y control value.
+	@return The current Jitter effect Y control value.  If the Jitter effect is not
+	or has not been enabled, it will return zero.
+*/
+ConsoleMethodWithDocs(SpineObject, getJitterY, ConsoleFloat, 2, 2, ())
+{
+	// Enable the special effect
+	return object->getJitterY();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Disables the Jitter special effect.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, disableJitter, ConsoleVoid, 2, 2, ())
+{
+	return object->disableJitter();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Enables the Swirl special effect
+	@param Radius Float - In degrees.  A special effect control parameter.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, enableSwirl, ConsoleVoid, 3, 3, (float radius))
+{
+	// Enable the special effect
+	object->enableSwirl(dAtof(argv[2]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Set the Swirl X control value.
+	@param X Float - A special effect control parameter.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, setSwirlX, ConsoleVoid, 3, 3, (float x))
+{
+	// Enable the special effect
+	object->setSwirlX(dAtof(argv[2]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Get the Swirl X control value.
+	@return The current Swirl effect X control value.  If the Swirl effect is not
+	or has not been enabled, it will return zero.
+*/
+ConsoleMethodWithDocs(SpineObject, getSwirlX, ConsoleFloat, 2, 2, ())
+{
+	// Enable the special effect
+	return object->getSwirlX();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Set the Swirl Y control value.
+	@param Y Float - A special effect control parameter.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, setSwirlY, ConsoleVoid, 3, 3, (float y))
+{
+	// Enable the special effect
+	object->setSwirlY(dAtof(argv[2]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Get the Swirl Y control value.
+	@return The current Swirl effect Y control value.  If the Swirl effect is not
+	or has not been enabled, it will return zero.
+*/
+ConsoleMethodWithDocs(SpineObject, getSwirlY, ConsoleFloat, 2, 2, ())
+{
+	// Enable the special effect
+	return object->getSwirlY();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Set the Swirl Radius control value.
+	@param Radius Float - A special effect control parameter.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, setSwirlRadius, ConsoleVoid, 3, 3, (float radius))
+{
+	// Enable the special effect
+	object->setSwirlRadius(dAtof(argv[2]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Get the Swirl Radius control value.
+	@return The current Swirl effect Radius control value.  If the Swirl effect is not
+	or has not been enabled, it will return zero.
+*/
+ConsoleMethodWithDocs(SpineObject, getSwirlRadius, ConsoleFloat, 2, 2, ())
+{
+	// Enable the special effect
+	return object->getSwirlRadius();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Set the Swirl Angle control value.
+	@param Angle Float - In degrees.  A special effect control parameter.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, setSwirlAngle, ConsoleVoid, 3, 3, (float radius))
+{
+	// Enable the special effect
+	object->setSwirlAngle(dAtof(argv[2]));
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Get the Swirl Angle control value.
+	@return The current Swirl effect Angle control value.  If the Swirl effect is not
+	or has not been enabled, it will return zero.
+*/
+ConsoleMethodWithDocs(SpineObject, getSwirlAngle, ConsoleFloat, 2, 2, ())
+{
+	// Enable the special effect
+	return object->getSwirlAngle();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Disables the Swirl special effect.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, disableSwirl, ConsoleVoid, 2, 2, ())
+{
+	return object->disableSwirl();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Enables animation event callbacks.
+	After calling this method, your SpineObject instance will begin to receive animation lifecycle callbacks.
+	This includes events defined within the animation when it was created.  Those embedded events will arrive
+	in the 'onAnimationEvent' callback, where '%eventName' will indicate which particular event occurred.
+
+@note The onAnimationEvent callback comes with a large number of arguments.  These correspond in large part to
+	  the attributes that can be defined for the event inside Spine's animation editor.  It is safe to ignore
+	  them if not needed.
+
+	The following code block shows every available event callback method.  It is okay to define handlers for only
+	those events of interest, though enabling callbacks and not defining any would just be a waste.  More likely
+	would be to remove the last callback at some future time and forget to disable them.
+
+	@code
+	function SpineObject::onAnimationStart(%this, %animationName, %animationTrack){
+		echo("SpineObject received onAnimationStart: "@%animationName);
+	}
+
+	function SpineObject::onAnimationInterrupt(%this, %animationName, %animationTrack){
+		echo("SpineObject received onAnimationInterrupt: "@%animationName);
+	}
+
+	function SpineObject::onAnimationEnd(%this, %animationName, %animationTrack){
+		echo("SpineObject received onAnimationEnd: "@%animationName);
+	}
+
+	function SpineObject::onAnimationComplete(%this, %animationName, %animationTrack){
+		echo("SpineObject received onAnimationComplete: "@%animationName);
+	}
+
+	function SpineObject::onAnimationDispose(%this, %animationName, %animationTrack){
+		echo("SpineObject received onAnimationDispose: "@%animationName);
+	}
+
+	function SpineObject::onAnimationEvent(%this, %animationName, %animationTrack, %eventName, %intArg, %floatArg, %stringArg, %time, %volume, %balance){
+		echo("SpineObject received onAnimationEvent:" SPC %animationName SPC %eventName);
+		if(%eventName $= "footstep"){
+			alxPlay("GameAssets:KnightFootstep");
+		}
+	}
+	@endcode
+
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, enableAnimationEventCallbacks, ConsoleVoid, 2, 2, ())
+{
+	object->enableEventCallbacks();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Disables animation event callbacks.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(SpineObject, disableAnimationEventCallbacks, ConsoleVoid, 2, 2, ())
+{
+	object->disableEventCallbacks();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Retrieve a collision proxy object.  This is a scene object that wraps a collsion box centered on
+	a spine attachment.  Since it's a SceneObject, it can then be manipulated/used for collision
+	processing in onCollision etc... This first checks if the attachment requested already has a proxy
+	and returns that if so.  If not, a new proxy is created and returned.
+	@note
+	This method should be called after the owning spine object has been added to the scene. The
+	proxy will be automatically added to the scene in that case.  If the proxy is created prior to the
+	spine object being added to the scene, the proxy will have to be added to the scene manually. It
+	won't be automatically added later when the spine object is added.
+
+	@note
+	The collision objects never have any velocity.  They are continually positioned (warped) to match
+	the location of the attachment they were created on.  This means that they are only useful as
+	sensors.  Even if they are not explicitly set to be sensors, since they have no velocity, they
+	don't trigger an impulse or other response.
+
+	@note
+	The generated collision proxy is assigned to the same group and layer as the parent spine object.
+	In addition, its collision mask is configured to disable collisions with all other collision boxes
+	in that same group.  The intent is to prevent collisions between attachements on the same spine
+	object.  Therefore, for different spine objects to collide, they should be assigned to different
+	scene groups.  However, if needed, the settings on the returned proxy may be changed by making the
+	appropriate	calls.
+
+	@param AttachmentName String - The spine attachment name that the new proxy should wrap.
+	@param SlotName String - The slot name that the new proxy should wrap.  The slot and attachment
+	names form a compound key.  Both are required to identify the attachment to get a collision
+	box for.
+	@param SkinName String Optional - The skin in which to search for the specified attachment name.
+	If given, then only that skin will be searched for the attachment, and creation will fail if it is not
+	found.  If this parameter is not given (or is set to "default"), then the current skin will be searched
+	first and if not found, the default skin will be searched.  Use "default" if you want the default
+	behavior while needing to define further optional parameters.
+	@param SizerWidth Float Optional - A factor that can be used to arbitrarily shrink or grow the generated
+	box's width.  The box remains centered on the attachment.
+	@param SizerHeight Float Optional - A factor that can be used to arbitrarily shrink or grow the generated
+	box's height.
+	@param ObjectName String Optional - Used to set the name of the object returned to the caller.
+
+	@return The requested SpineCollisionProxy object, or nothing if creation failed.
+*/
+ConsoleMethodWithDocs(SpineObject, getCollisionProxy, ConsoleString, 4, 8, (char *attachmentName, char *slotName, [char *skinName, float sizerWidth, float sizerHeight, char *objectName]))
+{
+	const char* skinName = argc >= 5 ? argv[4] : "default";
+	const F32 sizerWidth = argc >= 6 ? dAtof(argv[5]) : 1.0f;
+	const F32 sizerHeight = argc >= 7 ? dAtof(argv[6]) : 1.0f;
+	const char* objectName = argc >= 8 ? argv[7] : NULL;
+
+	// Get the collision proxy for the requested spine attachment.
+	const SceneObject* pProxy = object->getCollisionProxy(argv[2], argv[3], skinName, sizerWidth, sizerHeight, objectName);
+
+	return pProxy ? pProxy->getIdString() : StringTable->EmptyString;
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Delete the given collision proxy.
+	Proxies will be deleted automatically at cleanup.  However, this can be used
+	to delete one sooner.
+	@param proxy SpineCollisionProxy - The proxy to delete.
+	@return bool - True if the object was deleted, false otherwise.
+*/
+ConsoleMethodWithDocs(SpineObject, deleteCollisionProxy, ConsoleBool, 3, 3, (SpineCollsionProxy proxy))
+{
+	// Enable the special effect
+	return object->deleteCollisionProxy(argv[2]);
+}
+
+
+ConsoleMethodGroupEndWithDocs(SpineObject)

+ 1 - 1
engine/source/bitmapFont/BitmapFontCharacterInfo.h

@@ -28,7 +28,7 @@
 #endif
 #endif
 
 
 #ifndef _COLOR_H_
 #ifndef _COLOR_H_
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #endif
 #endif
 
 
 namespace font
 namespace font

+ 1 - 1
engine/source/graphics/dgl.cc

@@ -23,7 +23,7 @@
 #include "math/mPoint.h"
 #include "math/mPoint.h"
 #include "graphics/TextureManager.h"
 #include "graphics/TextureManager.h"
 #include "graphics/dgl.h"
 #include "graphics/dgl.h"
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #include "math/mPoint.h"
 #include "math/mPoint.h"
 #include "math/mRect.h"
 #include "math/mRect.h"
 #include "graphics/gFont.h"
 #include "graphics/gFont.h"

+ 1 - 1
engine/source/graphics/gBitmap.h

@@ -31,7 +31,7 @@
 #include "io/resource/resourceManager.h"
 #include "io/resource/resourceManager.h"
 #endif
 #endif
 #ifndef _COLOR_H_
 #ifndef _COLOR_H_
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #endif
 #endif
 
 
 //-------------------------------------- Forward decls.
 //-------------------------------------- Forward decls.

+ 2 - 2
engine/source/graphics/color.cc → engine/source/graphics/gColor.cc

@@ -20,11 +20,11 @@
 // IN THE SOFTWARE.
 // IN THE SOFTWARE.
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #include "console/console.h"
 #include "console/console.h"
 #include "console/consoleTypes.h"
 #include "console/consoleTypes.h"
 
 
-#include "color_ScriptBinding.h"
+#include "gColor_ScriptBinding.h"
 
 
 #ifndef _STRINGUNIT_H_
 #ifndef _STRINGUNIT_H_
 #include "string/stringUnit.h"
 #include "string/stringUnit.h"

+ 664 - 664
engine/source/graphics/color.h → engine/source/graphics/gColor.h

@@ -1,664 +1,664 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2013 GarageGames, LLC
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//-----------------------------------------------------------------------------
-
-#ifndef _COLOR_H_
-#define _COLOR_H_
-
-#ifndef _PLATFORM_H_
-#include "platform/platform.h"
-#endif
-
-#ifndef _CONSOLE_BASE_TYPE_H_
-#include "console/consoleBaseType.h"
-#endif
-
-//-----------------------------------------------------------------------------
-
-DefineConsoleType( TypeColorI )
-DefineConsoleType( TypeColorF )
-
-
-//-----------------------------------------------------------------------------
-
-class ColorI;
-
-//-----------------------------------------------------------------------------
-
-class ColorF
-{
-  public:
-   F32 red;
-   F32 green;
-   F32 blue;
-   F32 alpha;
-
-  public:
-   ColorF() { }
-   ColorF(const ColorF& in_rCopy);
-   ColorF(const F32 in_r,
-          const F32 in_g,
-          const F32 in_b,
-          const F32 in_a = 1.0f);
-
-   ColorF( const char* pStockColorName );
-
-   void set(const F32 in_r,
-            const F32 in_g,
-            const F32 in_b,
-            const F32 in_a = 1.0f);
-
-   void set( const char* pStockColorName );
-
-   static const ColorF& StockColor( const char* pStockColorName );
-   StringTableEntry StockColor( void );
-
-   ColorF& operator*=(const ColorF& in_mul);       // Can be useful for lighting
-   ColorF  operator*(const ColorF& in_mul) const;
-   ColorF& operator+=(const ColorF& in_rAdd);
-   ColorF  operator+(const ColorF& in_rAdd) const;
-   ColorF& operator-=(const ColorF& in_rSub);
-   ColorF  operator-(const ColorF& in_rSub) const;
-
-   ColorF& operator*=(const F32 in_mul);
-   ColorF  operator*(const F32 in_mul) const;
-   ColorF& operator/=(const F32 in_div);
-   ColorF  operator/(const F32 in_div) const;
-
-   ColorF  operator-() const;
-
-   // This should be as unique as possible as it is used for hashing.
-   operator const U32() const { return getRGBAPack(); }
-
-   // Value equality check for hashing.
-   bool operator==(const ColorF&) const;
-
-   // Value inequality check.
-   bool operator!=(const ColorF&) const;
-
-   const F32* address( void ) const { return &red; }
-
-   U32 getARGBPack() const;
-   U32 getRGBAPack() const;
-   U32 getBGRAPack() const;
-
-   operator ColorI() const;
-
-   void interpolate(const ColorF& in_rC1,
-                    const ColorF& in_rC2,
-                    const F32 in_factor);
-
-   bool isValidColor() const { return (red   >= 0.0f && red   <= 1.0f) &&
-                                      (green >= 0.0f && green <= 1.0f) &&
-                                      (blue  >= 0.0f && blue  <= 1.0f) &&
-                                      (alpha >= 0.0f && alpha <= 1.0f); }
-   void clamp();
-
-   inline StringTableEntry stringThis(void) const   { char buffer[64]; dSprintf(buffer, 64, "%f %f %f %f", red, green, blue, alpha ); return StringTable->insert(buffer); }
-   inline const char* scriptThis(void) const        { char* pBuffer = Con::getReturnBuffer(64); dSprintf(pBuffer, 32, "%.5f %.5f %.5f %.5f", red, green, blue, alpha ); return pBuffer; }
-};
-
-//-----------------------------------------------------------------------------
-
-class ColorI
-{
-  public:
-   U8 red;
-   U8 green;
-   U8 blue;
-   U8 alpha;
-
-  public:
-   ColorI() { }
-   ColorI(const ColorI& in_rCopy);
-   ColorI(const U8 in_r,
-          const U8 in_g,
-          const U8 in_b,
-          const U8 in_a = U8(255));
-
-   ColorI( const char* pStockColorName );
-
-   void set(const U8 in_r,
-            const U8 in_g,
-            const U8 in_b,
-            const U8 in_a = U8(255));
-
-   void set( const char* pStockColorName );
-
-   static const ColorI& StockColor( const char* pStockColorName );
-   StringTableEntry StockColor( void );
-
-   ColorI& operator*=(const F32 in_mul);
-   ColorI  operator*(const F32 in_mul) const;
-
-   operator ColorF() const;
-
-   // This should be as unique as possible as it is used for hashing.
-   operator const U32() const { return getRGBAPack(); }
-
-   // Value equality check for hashing.
-   bool operator==(const ColorI&) const;
-
-   // Value inequality check.
-   bool operator!=(const ColorI&) const;
-
-   const U8* address( void ) const { return &red; }
-
-   void interpolate(const ColorI& in_rC1,
-                    const ColorI& in_rC2,
-                    const F32  in_factor);
-
-   U32 getARGBPack() const;
-   U32 getRGBAPack() const;
-   U32 getABGRPack() const;
-
-   U32 getBGRPack() const;
-   U32 getRGBPack() const;
-
-   U32 getRGBEndian() const;
-   U32 getARGBEndian() const;
-
-   U16 get565()  const;
-   U16 get4444() const;
-
-   inline StringTableEntry stringThis(void) const   { char buffer[64]; dSprintf(buffer, 64, "%d %d %d %d", red, green, blue, alpha ); return StringTable->insert(buffer); }
-   inline const char* scriptThis(void) const        { char* pBuffer = Con::getReturnBuffer(64); dSprintf(pBuffer, 32, "%d %d %d %d", red, green, blue, alpha ); return pBuffer; }
-};
-
-
-//-----------------------------------------------------------------------------
-
-class StockColorItem
-{
-private:
-    StockColorItem() {}
-
-public:
-    StockColorItem( const char* pName, const U8 red, const U8 green, const U8 blue, const U8 alpha = 255 )
-    {
-        // Sanity!
-        AssertFatal( pName != NULL, "Stock color name cannot be NULL." );
-
-        // Set stock color.
-        // NOTE:-   We'll use the char pointer here.  We can yet use the string-table unfortunately.
-        mColorName = pName;
-        mColorI.set( red, green, blue, alpha );
-        mColorF = mColorI;
-    }
-
-    inline const char*      getColorName( void ) const { return mColorName; }
-    inline const ColorF&    getColorF( void ) const { return mColorF; }
-    inline const ColorI&    getColorI( void ) const { return mColorI; }
-
-    const char*         mColorName;
-    ColorF              mColorF;
-    ColorI              mColorI;
-};
-
-//-----------------------------------------------------------------------------
-
-class StockColor
-{
-public:
-    static bool isColor( const char* pStockColorName );
-    static const ColorF& colorF( const char* pStockColorName );
-    static const ColorI& colorI( const char* pStockColorName );
-    static StringTableEntry name( const ColorF& color );
-    static StringTableEntry name( const ColorI& color );
-
-    static S32 getCount( void );
-    static const StockColorItem* getColorItem( const S32 index );
-
-    static void create( void );
-    static void destroy( void );
-};
-
-//-----------------------------------------------------------------------------
-
-inline void ColorF::set(const F32 in_r,
-            const F32 in_g,
-            const F32 in_b,
-            const F32 in_a)
-{
-   red   = in_r;
-   green = in_g;
-   blue  = in_b;
-   alpha = in_a;
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF::ColorF(const ColorF& in_rCopy)
-{
-   red   = in_rCopy.red;
-   green = in_rCopy.green;
-   blue  = in_rCopy.blue;
-   alpha = in_rCopy.alpha;
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF::ColorF(const F32 in_r,
-               const F32 in_g,
-               const F32 in_b,
-               const F32 in_a)
-{
-   set(in_r, in_g, in_b, in_a);
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF& ColorF::operator*=(const ColorF& in_mul)
-{
-   red   *= in_mul.red;
-   green *= in_mul.green;
-   blue  *= in_mul.blue;
-   alpha *= in_mul.alpha;
-
-   return *this;
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF ColorF::operator*(const ColorF& in_mul) const
-{
-   return ColorF(red   * in_mul.red,
-                 green * in_mul.green,
-                 blue  * in_mul.blue,
-                 alpha * in_mul.alpha);
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF& ColorF::operator+=(const ColorF& in_rAdd)
-{
-   red   += in_rAdd.red;
-   green += in_rAdd.green;
-   blue  += in_rAdd.blue;
-   alpha += in_rAdd.alpha;
-
-   return *this;
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF ColorF::operator+(const ColorF& in_rAdd) const
-{
-   return ColorF(red   + in_rAdd.red,
-                  green + in_rAdd.green,
-                  blue  + in_rAdd.blue,
-                  alpha + in_rAdd.alpha);
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF& ColorF::operator-=(const ColorF& in_rSub)
-{
-   red   -= in_rSub.red;
-   green -= in_rSub.green;
-   blue  -= in_rSub.blue;
-   alpha -= in_rSub.alpha;
-
-   return *this;
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF ColorF::operator-(const ColorF& in_rSub) const
-{
-   return ColorF(red   - in_rSub.red,
-                 green - in_rSub.green,
-                 blue  - in_rSub.blue,
-                 alpha - in_rSub.alpha);
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF& ColorF::operator*=(const F32 in_mul)
-{
-   red   *= in_mul;
-   green *= in_mul;
-   blue  *= in_mul;
-   alpha *= in_mul;
-
-   return *this;
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF ColorF::operator*(const F32 in_mul) const
-{
-   return ColorF(red   * in_mul,
-                  green * in_mul,
-                  blue  * in_mul,
-                  alpha * in_mul);
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF& ColorF::operator/=(const F32 in_div)
-{
-   AssertFatal(in_div != 0.0f, "Error, div by zero...");
-   F32 inv = 1.0f / in_div;
-
-   red   *= inv;
-   green *= inv;
-   blue  *= inv;
-   alpha *= inv;
-
-   return *this;
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF ColorF::operator/(const F32 in_div) const
-{
-   AssertFatal(in_div != 0.0f, "Error, div by zero...");
-   F32 inv = 1.0f / in_div;
-
-   return ColorF(red * inv,
-                  green * inv,
-                  blue  * inv,
-                  alpha * inv);
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF ColorF::operator-() const
-{
-   return ColorF(-red, -green, -blue, -alpha);
-}
-
-//-----------------------------------------------------------------------------
-
-inline bool ColorF::operator==(const ColorF& in_Cmp) const
-{
-   return ( mIsEqual(red, in_Cmp.red) && mIsEqual(green, in_Cmp.green) && mIsEqual(blue, in_Cmp.blue) && mIsEqual(alpha, in_Cmp.alpha) );
-}
-
-//-----------------------------------------------------------------------------
-
-inline bool ColorF::operator!=(const ColorF& in_Cmp) const
-{
-   return ( mNotEqual(red, in_Cmp.red) || mNotEqual(green, in_Cmp.green) || mNotEqual(blue, in_Cmp.blue) || mNotEqual(alpha, in_Cmp.alpha) );
-}
-
-//-----------------------------------------------------------------------------
-
-inline U32 ColorF::getARGBPack() const
-{
-   return (U32(alpha * 255.0f + 0.5) << 24) |
-          (U32(red   * 255.0f + 0.5) << 16) |
-          (U32(green * 255.0f + 0.5) <<  8) |
-          (U32(blue  * 255.0f + 0.5) <<  0);
-}
-
-//-----------------------------------------------------------------------------
-
-inline U32 ColorF::getRGBAPack() const
-{
-   return (U32(alpha * 255.0f + 0.5) <<  0) |
-          (U32(red   * 255.0f + 0.5) << 24) |
-          (U32(green * 255.0f + 0.5) << 16) |
-          (U32(blue  * 255.0f + 0.5) <<  8);
-}
-
-//-----------------------------------------------------------------------------
-
-inline U32 ColorF::getBGRAPack() const
-{
-   return (U32(alpha * 255.0f + 0.5) <<  0) |
-          (U32(red   * 255.0f + 0.5) <<  8) |
-          (U32(green * 255.0f + 0.5) << 16) |
-          (U32(blue  * 255.0f + 0.5) << 24);
-}
-
-//-----------------------------------------------------------------------------
-
-inline void ColorF::interpolate(const ColorF& in_rC1,
-                    const ColorF& in_rC2,
-                    const F32  in_factor)
-{
-   F32 f2 = 1.0f - in_factor;
-   red   = (in_rC1.red   * f2) + (in_rC2.red   * in_factor);
-   green = (in_rC1.green * f2) + (in_rC2.green * in_factor);
-   blue  = (in_rC1.blue  * f2) + (in_rC2.blue  * in_factor);
-   alpha = (in_rC1.alpha * f2) + (in_rC2.alpha * in_factor);
-}
-
-//-----------------------------------------------------------------------------
-
-inline void ColorF::clamp()
-{
-   if (red > 1.0f)
-      red = 1.0f;
-   else if (red < 0.0f)
-      red = 0.0f;
-
-   if (green > 1.0f)
-      green = 1.0f;
-   else if (green < 0.0f)
-      green = 0.0f;
-
-   if (blue > 1.0f)
-      blue = 1.0f;
-   else if (blue < 0.0f)
-      blue = 0.0f;
-
-   if (alpha > 1.0f)
-      alpha = 1.0f;
-   else if (alpha < 0.0f)
-      alpha = 0.0f;
-}
-
-//-----------------------------------------------------------------------------
-
-inline void ColorI::set(const U8 in_r,
-            const U8 in_g,
-            const U8 in_b,
-            const U8 in_a)
-{
-   red   = in_r;
-   green = in_g;
-   blue  = in_b;
-   alpha = in_a;
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorI::ColorI(const ColorI& in_rCopy)
-{
-   red   = in_rCopy.red;
-   green = in_rCopy.green;
-   blue  = in_rCopy.blue;
-   alpha = in_rCopy.alpha;
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorI::ColorI(const U8 in_r,
-               const U8 in_g,
-               const U8 in_b,
-               const U8 in_a)
-{
-   set(in_r, in_g, in_b, in_a);
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorI& ColorI::operator*=(const F32 in_mul)
-{
-   red   = U8((F32(red)   * in_mul) + 0.5f);
-   green = U8((F32(green) * in_mul) + 0.5f);
-   blue  = U8((F32(blue)  * in_mul) + 0.5f);
-   alpha = U8((F32(alpha) * in_mul) + 0.5f);
-
-   return *this;
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorI ColorI::operator*(const F32 in_mul) const
-{
-   ColorI temp(*this);
-   temp *= in_mul;
-   return temp;
-}
-
-//-----------------------------------------------------------------------------
-
-inline bool ColorI::operator==(const ColorI& in_Cmp) const
-{
-   return (red == in_Cmp.red && green == in_Cmp.green && blue == in_Cmp.blue && alpha == in_Cmp.alpha);
-}
-
-//-----------------------------------------------------------------------------
-
-inline bool ColorI::operator!=(const ColorI& in_Cmp) const
-{
-   return (red != in_Cmp.red || green != in_Cmp.green || blue != in_Cmp.blue || alpha != in_Cmp.alpha);
-}
-
-//-----------------------------------------------------------------------------
-
-inline void ColorI::interpolate(const ColorI& in_rC1,
-                    const ColorI& in_rC2,
-                    const F32  in_factor)
-{
-   F32 f2= 1.0f - in_factor;
-   red   = U8(((F32(in_rC1.red)   * f2) + (F32(in_rC2.red)   * in_factor)) + 0.5f);
-   green = U8(((F32(in_rC1.green) * f2) + (F32(in_rC2.green) * in_factor)) + 0.5f);
-   blue  = U8(((F32(in_rC1.blue)  * f2) + (F32(in_rC2.blue)  * in_factor)) + 0.5f);
-   alpha = U8(((F32(in_rC1.alpha) * f2) + (F32(in_rC2.alpha) * in_factor)) + 0.5f);
-}
-
-//-----------------------------------------------------------------------------
-
-inline U32 ColorI::getARGBPack() const
-{
-   return (U32(alpha) << 24) |
-          (U32(red)   << 16) |
-          (U32(green) <<  8) |
-          (U32(blue)  <<  0);
-}
-
-//-----------------------------------------------------------------------------
-
-inline U32 ColorI::getRGBAPack() const
-{
-   return (U32(alpha) <<  0) |
-          (U32(red)   << 24) |
-          (U32(green) << 16) |
-          (U32(blue)  <<  8);
-}
-
-//-----------------------------------------------------------------------------
-
-inline U32 ColorI::getABGRPack() const
-{
-   return (U32(alpha) << 24) |
-          (U32(red)   << 16) |
-          (U32(green) <<  8) |
-          (U32(blue)  <<  0);
-}
-
-//-----------------------------------------------------------------------------
-
-inline U32 ColorI::getBGRPack() const
-{
-   return (U32(blue)  << 16) |
-          (U32(green) <<  8) |
-          (U32(red)   <<  0);
-}
-
-//-----------------------------------------------------------------------------
-
-inline U32 ColorI::getRGBPack() const
-{
-   return (U32(red)   << 16) |
-          (U32(green) <<  8) |
-          (U32(blue)  <<  0);
-}
-
-//-----------------------------------------------------------------------------
-
-inline U32 ColorI::getRGBEndian() const
-{
-#if defined(TORQUE_BIG_ENDIAN)
-      return(getRGBPack());
-#else
-      return(getBGRPack());
-#endif
-}
-
-//-----------------------------------------------------------------------------
-
-inline U32 ColorI::getARGBEndian() const
-{
-#if defined(TORQUE_BIG_ENDIAN)
-   return(getABGRPack());
-#else
-   return(getARGBPack());
-#endif
-}
-
-//-----------------------------------------------------------------------------
-
-inline U16 ColorI::get565() const
-{
-   return U16((U16(red   >> 3) << 11) |
-              (U16(green >> 2) <<  5) |
-              (U16(blue  >> 3) <<  0));
-}
-
-//-----------------------------------------------------------------------------
-
-inline U16 ColorI::get4444() const
-{
-   return U16(U16(U16(alpha >> 4) << 12) |
-              U16(U16(red   >> 4) <<  8) |
-              U16(U16(green >> 4) <<  4) |
-              U16(U16(blue  >> 4) <<  0));
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorF::operator ColorI() const
-{
-   return ColorI(U8(red   * 255.0f + 0.5),
-                  U8(green * 255.0f + 0.5),
-                  U8(blue  * 255.0f + 0.5),
-                  U8(alpha * 255.0f + 0.5));
-}
-
-//-----------------------------------------------------------------------------
-
-inline ColorI::operator ColorF() const
-{
-   const F32 inv255 = 1.0f / 255.0f;
-
-   return ColorF(F32(red)   * inv255,
-                 F32(green) * inv255,
-                 F32(blue)  * inv255,
-                 F32(alpha) * inv255);
-}
-
-#endif //_COLOR_H_
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _COLOR_H_
+#define _COLOR_H_
+
+#ifndef _PLATFORM_H_
+#include "platform/platform.h"
+#endif
+
+#ifndef _CONSOLE_BASE_TYPE_H_
+#include "console/consoleBaseType.h"
+#endif
+
+//-----------------------------------------------------------------------------
+
+DefineConsoleType( TypeColorI )
+DefineConsoleType( TypeColorF )
+
+
+//-----------------------------------------------------------------------------
+
+class ColorI;
+
+//-----------------------------------------------------------------------------
+
+class ColorF
+{
+  public:
+   F32 red;
+   F32 green;
+   F32 blue;
+   F32 alpha;
+
+  public:
+   ColorF() { }
+   ColorF(const ColorF& in_rCopy);
+   ColorF(const F32 in_r,
+          const F32 in_g,
+          const F32 in_b,
+          const F32 in_a = 1.0f);
+
+   ColorF( const char* pStockColorName );
+
+   void set(const F32 in_r,
+            const F32 in_g,
+            const F32 in_b,
+            const F32 in_a = 1.0f);
+
+   void set( const char* pStockColorName );
+
+   static const ColorF& StockColor( const char* pStockColorName );
+   StringTableEntry StockColor( void );
+
+   ColorF& operator*=(const ColorF& in_mul);       // Can be useful for lighting
+   ColorF  operator*(const ColorF& in_mul) const;
+   ColorF& operator+=(const ColorF& in_rAdd);
+   ColorF  operator+(const ColorF& in_rAdd) const;
+   ColorF& operator-=(const ColorF& in_rSub);
+   ColorF  operator-(const ColorF& in_rSub) const;
+
+   ColorF& operator*=(const F32 in_mul);
+   ColorF  operator*(const F32 in_mul) const;
+   ColorF& operator/=(const F32 in_div);
+   ColorF  operator/(const F32 in_div) const;
+
+   ColorF  operator-() const;
+
+   // This should be as unique as possible as it is used for hashing.
+   operator const U32() const { return getRGBAPack(); }
+
+   // Value equality check for hashing.
+   bool operator==(const ColorF&) const;
+
+   // Value inequality check.
+   bool operator!=(const ColorF&) const;
+
+   const F32* address( void ) const { return &red; }
+
+   U32 getARGBPack() const;
+   U32 getRGBAPack() const;
+   U32 getBGRAPack() const;
+
+   operator ColorI() const;
+
+   void interpolate(const ColorF& in_rC1,
+                    const ColorF& in_rC2,
+                    const F32 in_factor);
+
+   bool isValidColor() const { return (red   >= 0.0f && red   <= 1.0f) &&
+                                      (green >= 0.0f && green <= 1.0f) &&
+                                      (blue  >= 0.0f && blue  <= 1.0f) &&
+                                      (alpha >= 0.0f && alpha <= 1.0f); }
+   void clamp();
+
+   inline StringTableEntry stringThis(void) const   { char buffer[64]; dSprintf(buffer, 64, "%f %f %f %f", red, green, blue, alpha ); return StringTable->insert(buffer); }
+   inline const char* scriptThis(void) const        { char* pBuffer = Con::getReturnBuffer(64); dSprintf(pBuffer, 32, "%.5f %.5f %.5f %.5f", red, green, blue, alpha ); return pBuffer; }
+};
+
+//-----------------------------------------------------------------------------
+
+class ColorI
+{
+  public:
+   U8 red;
+   U8 green;
+   U8 blue;
+   U8 alpha;
+
+  public:
+   ColorI() { }
+   ColorI(const ColorI& in_rCopy);
+   ColorI(const U8 in_r,
+          const U8 in_g,
+          const U8 in_b,
+          const U8 in_a = U8(255));
+
+   ColorI( const char* pStockColorName );
+
+   void set(const U8 in_r,
+            const U8 in_g,
+            const U8 in_b,
+            const U8 in_a = U8(255));
+
+   void set( const char* pStockColorName );
+
+   static const ColorI& StockColor( const char* pStockColorName );
+   StringTableEntry StockColor( void );
+
+   ColorI& operator*=(const F32 in_mul);
+   ColorI  operator*(const F32 in_mul) const;
+
+   operator ColorF() const;
+
+   // This should be as unique as possible as it is used for hashing.
+   operator const U32() const { return getRGBAPack(); }
+
+   // Value equality check for hashing.
+   bool operator==(const ColorI&) const;
+
+   // Value inequality check.
+   bool operator!=(const ColorI&) const;
+
+   const U8* address( void ) const { return &red; }
+
+   void interpolate(const ColorI& in_rC1,
+                    const ColorI& in_rC2,
+                    const F32  in_factor);
+
+   U32 getARGBPack() const;
+   U32 getRGBAPack() const;
+   U32 getABGRPack() const;
+
+   U32 getBGRPack() const;
+   U32 getRGBPack() const;
+
+   U32 getRGBEndian() const;
+   U32 getARGBEndian() const;
+
+   U16 get565()  const;
+   U16 get4444() const;
+
+   inline StringTableEntry stringThis(void) const   { char buffer[64]; dSprintf(buffer, 64, "%d %d %d %d", red, green, blue, alpha ); return StringTable->insert(buffer); }
+   inline const char* scriptThis(void) const        { char* pBuffer = Con::getReturnBuffer(64); dSprintf(pBuffer, 32, "%d %d %d %d", red, green, blue, alpha ); return pBuffer; }
+};
+
+
+//-----------------------------------------------------------------------------
+
+class StockColorItem
+{
+private:
+    StockColorItem() {}
+
+public:
+    StockColorItem( const char* pName, const U8 red, const U8 green, const U8 blue, const U8 alpha = 255 )
+    {
+        // Sanity!
+        AssertFatal( pName != NULL, "Stock color name cannot be NULL." );
+
+        // Set stock color.
+        // NOTE:-   We'll use the char pointer here.  We can yet use the string-table unfortunately.
+        mColorName = pName;
+        mColorI.set( red, green, blue, alpha );
+        mColorF = mColorI;
+    }
+
+    inline const char*      getColorName( void ) const { return mColorName; }
+    inline const ColorF&    getColorF( void ) const { return mColorF; }
+    inline const ColorI&    getColorI( void ) const { return mColorI; }
+
+    const char*         mColorName;
+    ColorF              mColorF;
+    ColorI              mColorI;
+};
+
+//-----------------------------------------------------------------------------
+
+class StockColor
+{
+public:
+    static bool isColor( const char* pStockColorName );
+    static const ColorF& colorF( const char* pStockColorName );
+    static const ColorI& colorI( const char* pStockColorName );
+    static StringTableEntry name( const ColorF& color );
+    static StringTableEntry name( const ColorI& color );
+
+    static S32 getCount( void );
+    static const StockColorItem* getColorItem( const S32 index );
+
+    static void create( void );
+    static void destroy( void );
+};
+
+//-----------------------------------------------------------------------------
+
+inline void ColorF::set(const F32 in_r,
+            const F32 in_g,
+            const F32 in_b,
+            const F32 in_a)
+{
+   red   = in_r;
+   green = in_g;
+   blue  = in_b;
+   alpha = in_a;
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF::ColorF(const ColorF& in_rCopy)
+{
+   red   = in_rCopy.red;
+   green = in_rCopy.green;
+   blue  = in_rCopy.blue;
+   alpha = in_rCopy.alpha;
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF::ColorF(const F32 in_r,
+               const F32 in_g,
+               const F32 in_b,
+               const F32 in_a)
+{
+   set(in_r, in_g, in_b, in_a);
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF& ColorF::operator*=(const ColorF& in_mul)
+{
+   red   *= in_mul.red;
+   green *= in_mul.green;
+   blue  *= in_mul.blue;
+   alpha *= in_mul.alpha;
+
+   return *this;
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF ColorF::operator*(const ColorF& in_mul) const
+{
+   return ColorF(red   * in_mul.red,
+                 green * in_mul.green,
+                 blue  * in_mul.blue,
+                 alpha * in_mul.alpha);
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF& ColorF::operator+=(const ColorF& in_rAdd)
+{
+   red   += in_rAdd.red;
+   green += in_rAdd.green;
+   blue  += in_rAdd.blue;
+   alpha += in_rAdd.alpha;
+
+   return *this;
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF ColorF::operator+(const ColorF& in_rAdd) const
+{
+   return ColorF(red   + in_rAdd.red,
+                  green + in_rAdd.green,
+                  blue  + in_rAdd.blue,
+                  alpha + in_rAdd.alpha);
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF& ColorF::operator-=(const ColorF& in_rSub)
+{
+   red   -= in_rSub.red;
+   green -= in_rSub.green;
+   blue  -= in_rSub.blue;
+   alpha -= in_rSub.alpha;
+
+   return *this;
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF ColorF::operator-(const ColorF& in_rSub) const
+{
+   return ColorF(red   - in_rSub.red,
+                 green - in_rSub.green,
+                 blue  - in_rSub.blue,
+                 alpha - in_rSub.alpha);
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF& ColorF::operator*=(const F32 in_mul)
+{
+   red   *= in_mul;
+   green *= in_mul;
+   blue  *= in_mul;
+   alpha *= in_mul;
+
+   return *this;
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF ColorF::operator*(const F32 in_mul) const
+{
+   return ColorF(red   * in_mul,
+                  green * in_mul,
+                  blue  * in_mul,
+                  alpha * in_mul);
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF& ColorF::operator/=(const F32 in_div)
+{
+   AssertFatal(in_div != 0.0f, "Error, div by zero...");
+   F32 inv = 1.0f / in_div;
+
+   red   *= inv;
+   green *= inv;
+   blue  *= inv;
+   alpha *= inv;
+
+   return *this;
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF ColorF::operator/(const F32 in_div) const
+{
+   AssertFatal(in_div != 0.0f, "Error, div by zero...");
+   F32 inv = 1.0f / in_div;
+
+   return ColorF(red * inv,
+                  green * inv,
+                  blue  * inv,
+                  alpha * inv);
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF ColorF::operator-() const
+{
+   return ColorF(-red, -green, -blue, -alpha);
+}
+
+//-----------------------------------------------------------------------------
+
+inline bool ColorF::operator==(const ColorF& in_Cmp) const
+{
+   return ( mIsEqual(red, in_Cmp.red) && mIsEqual(green, in_Cmp.green) && mIsEqual(blue, in_Cmp.blue) && mIsEqual(alpha, in_Cmp.alpha) );
+}
+
+//-----------------------------------------------------------------------------
+
+inline bool ColorF::operator!=(const ColorF& in_Cmp) const
+{
+   return ( mNotEqual(red, in_Cmp.red) || mNotEqual(green, in_Cmp.green) || mNotEqual(blue, in_Cmp.blue) || mNotEqual(alpha, in_Cmp.alpha) );
+}
+
+//-----------------------------------------------------------------------------
+
+inline U32 ColorF::getARGBPack() const
+{
+   return (U32(alpha * 255.0f + 0.5) << 24) |
+          (U32(red   * 255.0f + 0.5) << 16) |
+          (U32(green * 255.0f + 0.5) <<  8) |
+          (U32(blue  * 255.0f + 0.5) <<  0);
+}
+
+//-----------------------------------------------------------------------------
+
+inline U32 ColorF::getRGBAPack() const
+{
+   return (U32(alpha * 255.0f + 0.5) <<  0) |
+          (U32(red   * 255.0f + 0.5) << 24) |
+          (U32(green * 255.0f + 0.5) << 16) |
+          (U32(blue  * 255.0f + 0.5) <<  8);
+}
+
+//-----------------------------------------------------------------------------
+
+inline U32 ColorF::getBGRAPack() const
+{
+   return (U32(alpha * 255.0f + 0.5) <<  0) |
+          (U32(red   * 255.0f + 0.5) <<  8) |
+          (U32(green * 255.0f + 0.5) << 16) |
+          (U32(blue  * 255.0f + 0.5) << 24);
+}
+
+//-----------------------------------------------------------------------------
+
+inline void ColorF::interpolate(const ColorF& in_rC1,
+                    const ColorF& in_rC2,
+                    const F32  in_factor)
+{
+   F32 f2 = 1.0f - in_factor;
+   red   = (in_rC1.red   * f2) + (in_rC2.red   * in_factor);
+   green = (in_rC1.green * f2) + (in_rC2.green * in_factor);
+   blue  = (in_rC1.blue  * f2) + (in_rC2.blue  * in_factor);
+   alpha = (in_rC1.alpha * f2) + (in_rC2.alpha * in_factor);
+}
+
+//-----------------------------------------------------------------------------
+
+inline void ColorF::clamp()
+{
+   if (red > 1.0f)
+      red = 1.0f;
+   else if (red < 0.0f)
+      red = 0.0f;
+
+   if (green > 1.0f)
+      green = 1.0f;
+   else if (green < 0.0f)
+      green = 0.0f;
+
+   if (blue > 1.0f)
+      blue = 1.0f;
+   else if (blue < 0.0f)
+      blue = 0.0f;
+
+   if (alpha > 1.0f)
+      alpha = 1.0f;
+   else if (alpha < 0.0f)
+      alpha = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+
+inline void ColorI::set(const U8 in_r,
+            const U8 in_g,
+            const U8 in_b,
+            const U8 in_a)
+{
+   red   = in_r;
+   green = in_g;
+   blue  = in_b;
+   alpha = in_a;
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorI::ColorI(const ColorI& in_rCopy)
+{
+   red   = in_rCopy.red;
+   green = in_rCopy.green;
+   blue  = in_rCopy.blue;
+   alpha = in_rCopy.alpha;
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorI::ColorI(const U8 in_r,
+               const U8 in_g,
+               const U8 in_b,
+               const U8 in_a)
+{
+   set(in_r, in_g, in_b, in_a);
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorI& ColorI::operator*=(const F32 in_mul)
+{
+   red   = U8((F32(red)   * in_mul) + 0.5f);
+   green = U8((F32(green) * in_mul) + 0.5f);
+   blue  = U8((F32(blue)  * in_mul) + 0.5f);
+   alpha = U8((F32(alpha) * in_mul) + 0.5f);
+
+   return *this;
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorI ColorI::operator*(const F32 in_mul) const
+{
+   ColorI temp(*this);
+   temp *= in_mul;
+   return temp;
+}
+
+//-----------------------------------------------------------------------------
+
+inline bool ColorI::operator==(const ColorI& in_Cmp) const
+{
+   return (red == in_Cmp.red && green == in_Cmp.green && blue == in_Cmp.blue && alpha == in_Cmp.alpha);
+}
+
+//-----------------------------------------------------------------------------
+
+inline bool ColorI::operator!=(const ColorI& in_Cmp) const
+{
+   return (red != in_Cmp.red || green != in_Cmp.green || blue != in_Cmp.blue || alpha != in_Cmp.alpha);
+}
+
+//-----------------------------------------------------------------------------
+
+inline void ColorI::interpolate(const ColorI& in_rC1,
+                    const ColorI& in_rC2,
+                    const F32  in_factor)
+{
+   F32 f2= 1.0f - in_factor;
+   red   = U8(((F32(in_rC1.red)   * f2) + (F32(in_rC2.red)   * in_factor)) + 0.5f);
+   green = U8(((F32(in_rC1.green) * f2) + (F32(in_rC2.green) * in_factor)) + 0.5f);
+   blue  = U8(((F32(in_rC1.blue)  * f2) + (F32(in_rC2.blue)  * in_factor)) + 0.5f);
+   alpha = U8(((F32(in_rC1.alpha) * f2) + (F32(in_rC2.alpha) * in_factor)) + 0.5f);
+}
+
+//-----------------------------------------------------------------------------
+
+inline U32 ColorI::getARGBPack() const
+{
+   return (U32(alpha) << 24) |
+          (U32(red)   << 16) |
+          (U32(green) <<  8) |
+          (U32(blue)  <<  0);
+}
+
+//-----------------------------------------------------------------------------
+
+inline U32 ColorI::getRGBAPack() const
+{
+   return (U32(alpha) <<  0) |
+          (U32(red)   << 24) |
+          (U32(green) << 16) |
+          (U32(blue)  <<  8);
+}
+
+//-----------------------------------------------------------------------------
+
+inline U32 ColorI::getABGRPack() const
+{
+   return (U32(alpha) << 24) |
+          (U32(red)   << 16) |
+          (U32(green) <<  8) |
+          (U32(blue)  <<  0);
+}
+
+//-----------------------------------------------------------------------------
+
+inline U32 ColorI::getBGRPack() const
+{
+   return (U32(blue)  << 16) |
+          (U32(green) <<  8) |
+          (U32(red)   <<  0);
+}
+
+//-----------------------------------------------------------------------------
+
+inline U32 ColorI::getRGBPack() const
+{
+   return (U32(red)   << 16) |
+          (U32(green) <<  8) |
+          (U32(blue)  <<  0);
+}
+
+//-----------------------------------------------------------------------------
+
+inline U32 ColorI::getRGBEndian() const
+{
+#if defined(TORQUE_BIG_ENDIAN)
+      return(getRGBPack());
+#else
+      return(getBGRPack());
+#endif
+}
+
+//-----------------------------------------------------------------------------
+
+inline U32 ColorI::getARGBEndian() const
+{
+#if defined(TORQUE_BIG_ENDIAN)
+   return(getABGRPack());
+#else
+   return(getARGBPack());
+#endif
+}
+
+//-----------------------------------------------------------------------------
+
+inline U16 ColorI::get565() const
+{
+   return U16((U16(red   >> 3) << 11) |
+              (U16(green >> 2) <<  5) |
+              (U16(blue  >> 3) <<  0));
+}
+
+//-----------------------------------------------------------------------------
+
+inline U16 ColorI::get4444() const
+{
+   return U16(U16(U16(alpha >> 4) << 12) |
+              U16(U16(red   >> 4) <<  8) |
+              U16(U16(green >> 4) <<  4) |
+              U16(U16(blue  >> 4) <<  0));
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorF::operator ColorI() const
+{
+   return ColorI(U8(red   * 255.0f + 0.5),
+                  U8(green * 255.0f + 0.5),
+                  U8(blue  * 255.0f + 0.5),
+                  U8(alpha * 255.0f + 0.5));
+}
+
+//-----------------------------------------------------------------------------
+
+inline ColorI::operator ColorF() const
+{
+   const F32 inv255 = 1.0f / 255.0f;
+
+   return ColorF(F32(red)   * inv255,
+                 F32(green) * inv255,
+                 F32(blue)  * inv255,
+                 F32(alpha) * inv255);
+}
+
+#endif //_COLOR_H_

+ 116 - 116
engine/source/graphics/color_ScriptBinding.h → engine/source/graphics/gColor_ScriptBinding.h

@@ -1,116 +1,116 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2013 GarageGames, LLC
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//-----------------------------------------------------------------------------
-
-/*! @defgroup ColorFunctions Color
-	@ingroup TorqueScriptFunctions
-	@{
-*/
-
-/*! Gets a count of available stock colors.
-    @return A count of available stock colors.
-*/
-ConsoleFunctionWithDocs( getStockColorCount, ConsoleInt, 1, 1, ())
-{
-    return StockColor::getCount();
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets the stock color name at the specified index.
-    @param stockColorIndex The zero-based index of the stock color name to retrieve.
-    @return The stock color name at the specified index or nothing if the string is invalid.
-*/
-ConsoleFunctionWithDocs( getStockColorName, ConsoleString, 2, 2, (stockColorIndex))
-{
-    // Fetch stock color index.
-    const S32 stockColorIndex = dAtoi(argv[1]);
-
-    // Fetch the color item.
-    const StockColorItem* pColorItem = StockColor::getColorItem( stockColorIndex );
-
-    return pColorItem == NULL ? NULL : pColorItem->getColorName();
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets whether the specified name is a stock color or not.
-    @param stockColorName - The stock color name to test for.
-    @return Whether the specified name is a stock color or not.
-*/
-ConsoleFunctionWithDocs( isStockColor, ConsoleBool, 2, 2, (stockColorName))
-{
-    // Fetch stock color name.
-    const char* pStockColorName = argv[1];
-
-    // Return whether this is a stock color name or not.
-    return StockColor::isColor( pStockColorName );
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets a floating-point-based stock color by name.
-    @param stockColorName - The stock color name to retrieve.
-    @return The stock color that matches the specified color name.  Returns nothing if the color name is not found.
-*/
-ConsoleFunctionWithDocs( getStockColorF, ConsoleString, 2, 2, (stockColorName))
-{
-    // Fetch stock color name.
-    const char* pStockColorName = argv[1];
-
-    // Return nothing if stock color name is invalid.
-    if ( !StockColor::isColor( pStockColorName ) )
-        return StringTable->EmptyString;
-
-    // Fetch stock color.
-    const ColorF& color = StockColor::colorF( pStockColorName );
-
-    // Format stock color.
-    char* returnBuffer = Con::getReturnBuffer(256);
-    dSprintf(returnBuffer, 256, "%g %g %g %g", color.red, color.green, color.blue, color.alpha);
-    return(returnBuffer);
-}
-
-//-----------------------------------------------------------------------------
-
-/*! Gets a byte-based stock color by name.
-    @param stockColorName - The stock color name to retrieve.
-    @return The stock color that matches the specified color name.  Returns nothing if the color name is not found.
-*/
-ConsoleFunctionWithDocs( getStockColorI, ConsoleString, 2, 2, (stockColorName))
-{
-    // Fetch stock color name.
-    const char* pStockColorName = argv[1];
-
-    // Return nothing if stock color name is invalid.
-    if ( !StockColor::isColor( pStockColorName ) )
-        return StringTable->EmptyString;
-
-    // Fetch stock color.
-    const ColorI& color = StockColor::colorI( pStockColorName );
-
-    // Format stock color.
-    char* returnBuffer = Con::getReturnBuffer(256);
-    dSprintf(returnBuffer, 256, "%d %d %d %d", color.red, color.green, color.blue, color.alpha);
-    return(returnBuffer);
-}
-
-/*! @} */ // group ColorFunctions
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+/*! @defgroup ColorFunctions Color
+	@ingroup TorqueScriptFunctions
+	@{
+*/
+
+/*! Gets a count of available stock colors.
+    @return A count of available stock colors.
+*/
+ConsoleFunctionWithDocs( getStockColorCount, ConsoleInt, 1, 1, ())
+{
+    return StockColor::getCount();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Gets the stock color name at the specified index.
+    @param stockColorIndex The zero-based index of the stock color name to retrieve.
+    @return The stock color name at the specified index or nothing if the string is invalid.
+*/
+ConsoleFunctionWithDocs( getStockColorName, ConsoleString, 2, 2, (stockColorIndex))
+{
+    // Fetch stock color index.
+    const S32 stockColorIndex = dAtoi(argv[1]);
+
+    // Fetch the color item.
+    const StockColorItem* pColorItem = StockColor::getColorItem( stockColorIndex );
+
+    return pColorItem == NULL ? NULL : pColorItem->getColorName();
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Gets whether the specified name is a stock color or not.
+    @param stockColorName - The stock color name to test for.
+    @return Whether the specified name is a stock color or not.
+*/
+ConsoleFunctionWithDocs( isStockColor, ConsoleBool, 2, 2, (stockColorName))
+{
+    // Fetch stock color name.
+    const char* pStockColorName = argv[1];
+
+    // Return whether this is a stock color name or not.
+    return StockColor::isColor( pStockColorName );
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Gets a floating-point-based stock color by name.
+    @param stockColorName - The stock color name to retrieve.
+    @return The stock color that matches the specified color name.  Returns nothing if the color name is not found.
+*/
+ConsoleFunctionWithDocs( getStockColorF, ConsoleString, 2, 2, (stockColorName))
+{
+    // Fetch stock color name.
+    const char* pStockColorName = argv[1];
+
+    // Return nothing if stock color name is invalid.
+    if ( !StockColor::isColor( pStockColorName ) )
+        return StringTable->EmptyString;
+
+    // Fetch stock color.
+    const ColorF& color = StockColor::colorF( pStockColorName );
+
+    // Format stock color.
+    char* returnBuffer = Con::getReturnBuffer(256);
+    dSprintf(returnBuffer, 256, "%g %g %g %g", color.red, color.green, color.blue, color.alpha);
+    return(returnBuffer);
+}
+
+//-----------------------------------------------------------------------------
+
+/*! Gets a byte-based stock color by name.
+    @param stockColorName - The stock color name to retrieve.
+    @return The stock color that matches the specified color name.  Returns nothing if the color name is not found.
+*/
+ConsoleFunctionWithDocs( getStockColorI, ConsoleString, 2, 2, (stockColorName))
+{
+    // Fetch stock color name.
+    const char* pStockColorName = argv[1];
+
+    // Return nothing if stock color name is invalid.
+    if ( !StockColor::isColor( pStockColorName ) )
+        return StringTable->EmptyString;
+
+    // Fetch stock color.
+    const ColorI& color = StockColor::colorI( pStockColorName );
+
+    // Format stock color.
+    char* returnBuffer = Con::getReturnBuffer(256);
+    dSprintf(returnBuffer, 256, "%d %d %d %d", color.red, color.green, color.blue, color.alpha);
+    return(returnBuffer);
+}
+
+/*! @} */ // group ColorFunctions

+ 1 - 1
engine/source/graphics/gPalette.h

@@ -28,7 +28,7 @@
 #include "platform/platform.h"
 #include "platform/platform.h"
 #endif
 #endif
 #ifndef _COLOR_H_
 #ifndef _COLOR_H_
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #endif
 #endif
 
 
 //-------------------------------------- Forward decls.
 //-------------------------------------- Forward decls.

+ 1 - 1
engine/source/graphics/splineUtil.h

@@ -33,7 +33,7 @@
 #include "math/mSplinePatch.h"
 #include "math/mSplinePatch.h"
 #endif
 #endif
 #ifndef _COLOR_H_
 #ifndef _COLOR_H_
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #endif
 #endif
 
 
 /// Spline utility namespace.  This is used for generating pretty splines so you can get nice curved surfaces.
 /// Spline utility namespace.  This is used for generating pretty splines so you can get nice curved surfaces.

+ 1 - 1
engine/source/gui/guiControl.h

@@ -33,7 +33,7 @@
 #include "math/mRect.h"
 #include "math/mRect.h"
 #endif
 #endif
 #ifndef _COLOR_H_
 #ifndef _COLOR_H_
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #endif
 #endif
 #ifndef _SIMBASE_H_
 #ifndef _SIMBASE_H_
 #include "sim/simBase.h"
 #include "sim/simBase.h"

+ 1 - 1
engine/source/gui/guiDefaultControlRender.cc

@@ -23,7 +23,7 @@
 #include "graphics/dgl.h"
 #include "graphics/dgl.h"
 #include "gui/guiDefaultControlRender.h"
 #include "gui/guiDefaultControlRender.h"
 #include "gui/guiTypes.h"
 #include "gui/guiTypes.h"
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #include "math/mRect.h"
 #include "math/mRect.h"
 
 
 void renderBorderedRect(RectI &bounds, GuiControlProfile *profile, GuiControlState state )
 void renderBorderedRect(RectI &bounds, GuiControlProfile *profile, GuiControlState state )

+ 1 - 1
engine/source/gui/guiTextCtrl.cc

@@ -22,7 +22,7 @@
 
 
 #include "console/consoleTypes.h"
 #include "console/consoleTypes.h"
 #include "console/console.h"
 #include "console/console.h"
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #include "gui/guiTextCtrl.h"
 #include "gui/guiTextCtrl.h"
 #include "graphics/dgl.h"
 #include "graphics/dgl.h"
 #include "gui/language/lang.h"
 #include "gui/language/lang.h"

+ 1 - 1
engine/source/gui/guiTypes.h

@@ -32,7 +32,7 @@
 #endif
 #endif
 
 
 #ifndef _COLOR_H_
 #ifndef _COLOR_H_
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #endif
 #endif
 
 
 #ifndef _SIMBASE_H_
 #ifndef _SIMBASE_H_

+ 1 - 1
engine/source/io/nStream.cc

@@ -23,7 +23,7 @@
 #include "platform/platform.h"
 #include "platform/platform.h"
 #include "stream.h"
 #include "stream.h"
 #include "string/stringTable.h"
 #include "string/stringTable.h"
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #include "io/rawData.h"
 #include "io/rawData.h"
 #include "io/byteBuffer.h"
 #include "io/byteBuffer.h"
 
 

+ 1 - 1
engine/source/persistence/taml/tamlCustom.h

@@ -40,7 +40,7 @@
 #endif
 #endif
 
 
 #ifndef _COLOR_H_
 #ifndef _COLOR_H_
-#include "graphics/color.h"
+#include "graphics/gColor.h"
 #endif
 #endif
 
 
 #ifndef _SIMBASE_H_
 #ifndef _SIMBASE_H_

File diff suppressed because it is too large
+ 652 - 262
engine/source/spine/Animation.c


+ 412 - 73
engine/source/spine/Animation.h

@@ -1,35 +1,38 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_ANIMATION_H_
 #ifndef SPINE_ANIMATION_H_
 #define SPINE_ANIMATION_H_
 #define SPINE_ANIMATION_H_
 
 
+#include <spine/dll.h>
 #include <spine/Event.h>
 #include <spine/Event.h>
+#include <spine/Attachment.h>
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
@@ -38,75 +41,123 @@ extern "C" {
 typedef struct spTimeline spTimeline;
 typedef struct spTimeline spTimeline;
 struct spSkeleton;
 struct spSkeleton;
 
 
-typedef struct {
+typedef struct spAnimation {
 	const char* const name;
 	const char* const name;
 	float duration;
 	float duration;
 
 
-	int timelineCount;
+	int timelinesCount;
 	spTimeline** timelines;
 	spTimeline** timelines;
+
+#ifdef __cplusplus
+	spAnimation() :
+		name(0),
+		duration(0),
+		timelinesCount(0),
+		timelines(0) {
+	}
+#endif
 } spAnimation;
 } spAnimation;
 
 
-spAnimation* spAnimation_create (const char* name, int timelineCount);
-void spAnimation_dispose (spAnimation* self);
+typedef enum {
+	SP_MIX_BLEND_SETUP,
+	SP_MIX_BLEND_FIRST,
+	SP_MIX_BLEND_REPLACE,
+	SP_MIX_BLEND_ADD
+} spMixBlend;
 
 
-/** Poses the skeleton at the specified time for this animation.
- * @param lastTime The last time the animation was applied.
- * @param events Any triggered events are added. */
-void spAnimation_apply (const spAnimation* self, struct spSkeleton* skeleton, float lastTime, float time, int loop,
-		spEvent** events, int* eventCount);
+typedef enum {
+	SP_MIX_DIRECTION_IN,
+	SP_MIX_DIRECTION_OUT
+} spMixDirection;
 
 
-/** Poses the skeleton at the specified time for this animation mixed with the current pose.
+SP_API spAnimation* spAnimation_create (const char* name, int timelinesCount);
+SP_API void spAnimation_dispose (spAnimation* self);
+
+/** Poses the skeleton at the specified time for this animation.
  * @param lastTime The last time the animation was applied.
  * @param lastTime The last time the animation was applied.
- * @param events Any triggered events are added.
- * @param alpha The amount of this animation that affects the current pose. */
-void spAnimation_mix (const spAnimation* self, struct spSkeleton* skeleton, float lastTime, float time, int loop,
-		spEvent** events, int* eventCount, float alpha);
+ * @param events Any triggered events are added. May be null.*/
+SP_API void spAnimation_apply (const spAnimation* self, struct spSkeleton* skeleton, float lastTime, float time, int loop,
+		spEvent** events, int* eventsCount, float alpha, spMixBlend blend, spMixDirection direction);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spAnimation Animation;
 typedef spAnimation Animation;
 #define Animation_create(...) spAnimation_create(__VA_ARGS__)
 #define Animation_create(...) spAnimation_create(__VA_ARGS__)
 #define Animation_dispose(...) spAnimation_dispose(__VA_ARGS__)
 #define Animation_dispose(...) spAnimation_dispose(__VA_ARGS__)
 #define Animation_apply(...) spAnimation_apply(__VA_ARGS__)
 #define Animation_apply(...) spAnimation_apply(__VA_ARGS__)
-#define Animation_mix(...) spAnimation_mix(__VA_ARGS__)
 #endif
 #endif
 
 
 /**/
 /**/
 
 
 typedef enum {
 typedef enum {
-	TIMELINE_SCALE, TIMELINE_ROTATE, TIMELINE_TRANLATE, TIMELINE_COLOR, TIMELINE_ATTACHMENT, TIMELINE_EVENT, TIMELINE_DRAWORDER
+	SP_TIMELINE_ROTATE,
+	SP_TIMELINE_TRANSLATE,
+	SP_TIMELINE_SCALE,
+	SP_TIMELINE_SHEAR,
+	SP_TIMELINE_ATTACHMENT,
+	SP_TIMELINE_COLOR,
+	SP_TIMELINE_DEFORM,
+	SP_TIMELINE_EVENT,
+	SP_TIMELINE_DRAWORDER,
+	SP_TIMELINE_IKCONSTRAINT,
+	SP_TIMELINE_TRANSFORMCONSTRAINT,
+	SP_TIMELINE_PATHCONSTRAINTPOSITION,
+	SP_TIMELINE_PATHCONSTRAINTSPACING,
+	SP_TIMELINE_PATHCONSTRAINTMIX,
+	SP_TIMELINE_TWOCOLOR
 } spTimelineType;
 } spTimelineType;
 
 
 struct spTimeline {
 struct spTimeline {
 	const spTimelineType type;
 	const spTimelineType type;
-
 	const void* const vtable;
 	const void* const vtable;
+
+#ifdef __cplusplus
+	spTimeline() :
+		type(SP_TIMELINE_SCALE),
+		vtable(0) {
+	}
+#endif
 };
 };
 
 
-void spTimeline_dispose (spTimeline* self);
-void spTimeline_apply (const spTimeline* self, struct spSkeleton* skeleton, float lastTime, float time, spEvent** firedEvents,
-		int* eventCount, float alpha);
+SP_API void spTimeline_dispose (spTimeline* self);
+SP_API void spTimeline_apply (const spTimeline* self, struct spSkeleton* skeleton, float lastTime, float time, spEvent** firedEvents,
+		int* eventsCount, float alpha, spMixBlend blend, spMixDirection direction);
+SP_API int spTimeline_getPropertyId (const spTimeline* self);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spTimeline Timeline;
 typedef spTimeline Timeline;
+#define TIMELINE_SCALE SP_TIMELINE_SCALE
+#define TIMELINE_ROTATE SP_TIMELINE_ROTATE
+#define TIMELINE_TRANSLATE SP_TIMELINE_TRANSLATE
+#define TIMELINE_COLOR SP_TIMELINE_COLOR
+#define TIMELINE_ATTACHMENT SP_TIMELINE_ATTACHMENT
+#define TIMELINE_EVENT SP_TIMELINE_EVENT
+#define TIMELINE_DRAWORDER SP_TIMELINE_DRAWORDER
 #define Timeline_dispose(...) spTimeline_dispose(__VA_ARGS__)
 #define Timeline_dispose(...) spTimeline_dispose(__VA_ARGS__)
 #define Timeline_apply(...) spTimeline_apply(__VA_ARGS__)
 #define Timeline_apply(...) spTimeline_apply(__VA_ARGS__)
 #endif
 #endif
 
 
 /**/
 /**/
 
 
-typedef struct {
+typedef struct spCurveTimeline {
 	spTimeline super;
 	spTimeline super;
-	float* curves; /* dfx, dfy, ddfx, ddfy, dddfx, dddfy, ... */
+	float* curves; /* type, x, y, ... */
+
+#ifdef __cplusplus
+	spCurveTimeline() :
+		super(),
+		curves(0) {
+	}
+#endif
 } spCurveTimeline;
 } spCurveTimeline;
 
 
-void spCurveTimeline_setLinear (spCurveTimeline* self, int frameIndex);
-void spCurveTimeline_setStepped (spCurveTimeline* self, int frameIndex);
+SP_API void spCurveTimeline_setLinear (spCurveTimeline* self, int frameIndex);
+SP_API void spCurveTimeline_setStepped (spCurveTimeline* self, int frameIndex);
 
 
 /* Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
 /* Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
  * cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
  * cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
  * the difference between the keyframe's values. */
  * the difference between the keyframe's values. */
-void spCurveTimeline_setCurve (spCurveTimeline* self, int frameIndex, float cx1, float cy1, float cx2, float cy2);
-float spCurveTimeline_getCurvePercent (const spCurveTimeline* self, int frameIndex, float percent);
+SP_API void spCurveTimeline_setCurve (spCurveTimeline* self, int frameIndex, float cx1, float cy1, float cx2, float cy2);
+SP_API float spCurveTimeline_getCurvePercent (const spCurveTimeline* self, int frameIndex, float percent);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spCurveTimeline CurveTimeline;
 typedef spCurveTimeline CurveTimeline;
@@ -120,14 +171,31 @@ typedef spCurveTimeline CurveTimeline;
 
 
 typedef struct spBaseTimeline {
 typedef struct spBaseTimeline {
 	spCurveTimeline super;
 	spCurveTimeline super;
-	int const framesLength;
+	int const framesCount;
 	float* const frames; /* time, angle, ... for rotate. time, x, y, ... for translate and scale. */
 	float* const frames; /* time, angle, ... for rotate. time, x, y, ... for translate and scale. */
 	int boneIndex;
 	int boneIndex;
-} spRotateTimeline;
 
 
-spRotateTimeline* spRotateTimeline_create (int frameCount);
+#ifdef __cplusplus
+	spBaseTimeline() :
+		super(),
+		framesCount(0),
+		frames(0),
+		boneIndex(0) {
+	}
+#endif
+} spBaseTimeline;
+
+/**/
+
+static const int ROTATE_PREV_TIME = -2, ROTATE_PREV_ROTATION = -1;
+static const int ROTATE_ROTATION = 1;
+static const int ROTATE_ENTRIES = 2;
+
+typedef struct spBaseTimeline spRotateTimeline;
+
+SP_API spRotateTimeline* spRotateTimeline_create (int framesCount);
 
 
-void spRotateTimeline_setFrame (spRotateTimeline* self, int frameIndex, float time, float angle);
+SP_API void spRotateTimeline_setFrame (spRotateTimeline* self, int frameIndex, float time, float angle);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spRotateTimeline RotateTimeline;
 typedef spRotateTimeline RotateTimeline;
@@ -137,11 +205,13 @@ typedef spRotateTimeline RotateTimeline;
 
 
 /**/
 /**/
 
 
+static const int TRANSLATE_ENTRIES = 3;
+
 typedef struct spBaseTimeline spTranslateTimeline;
 typedef struct spBaseTimeline spTranslateTimeline;
 
 
-spTranslateTimeline* spTranslateTimeline_create (int frameCount);
+SP_API spTranslateTimeline* spTranslateTimeline_create (int framesCount);
 
 
-void spTranslateTimeline_setFrame (spTranslateTimeline* self, int frameIndex, float time, float x, float y);
+SP_API void spTranslateTimeline_setFrame (spTranslateTimeline* self, int frameIndex, float time, float x, float y);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spTranslateTimeline TranslateTimeline;
 typedef spTranslateTimeline TranslateTimeline;
@@ -153,9 +223,9 @@ typedef spTranslateTimeline TranslateTimeline;
 
 
 typedef struct spBaseTimeline spScaleTimeline;
 typedef struct spBaseTimeline spScaleTimeline;
 
 
-spScaleTimeline* spScaleTimeline_create (int frameCount);
+SP_API spScaleTimeline* spScaleTimeline_create (int framesCount);
 
 
-void spScaleTimeline_setFrame (spScaleTimeline* self, int frameIndex, float time, float x, float y);
+SP_API void spScaleTimeline_setFrame (spScaleTimeline* self, int frameIndex, float time, float x, float y);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spScaleTimeline ScaleTimeline;
 typedef spScaleTimeline ScaleTimeline;
@@ -165,16 +235,41 @@ typedef spScaleTimeline ScaleTimeline;
 
 
 /**/
 /**/
 
 
-typedef struct {
+typedef struct spBaseTimeline spShearTimeline;
+
+SP_API spShearTimeline* spShearTimeline_create (int framesCount);
+
+SP_API void spShearTimeline_setFrame (spShearTimeline* self, int frameIndex, float time, float x, float y);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spShearTimeline ShearTimeline;
+#define ShearTimeline_create(...) spShearTimeline_create(__VA_ARGS__)
+#define ShearTimeline_setFrame(...) spShearTimeline_setFrame(__VA_ARGS__)
+#endif
+
+/**/
+
+static const int COLOR_ENTRIES = 5;
+
+typedef struct spColorTimeline {
 	spCurveTimeline super;
 	spCurveTimeline super;
-	int const framesLength;
+	int const framesCount;
 	float* const frames; /* time, r, g, b, a, ... */
 	float* const frames; /* time, r, g, b, a, ... */
 	int slotIndex;
 	int slotIndex;
+
+#ifdef __cplusplus
+	spColorTimeline() :
+		super(),
+		framesCount(0),
+		frames(0),
+		slotIndex(0) {
+	}
+#endif
 } spColorTimeline;
 } spColorTimeline;
 
 
-spColorTimeline* spColorTimeline_create (int frameCount);
+SP_API spColorTimeline* spColorTimeline_create (int framesCount);
 
 
-void spColorTimeline_setFrame (spColorTimeline* self, int frameIndex, float time, float r, float g, float b, float a);
+SP_API void spColorTimeline_setFrame (spColorTimeline* self, int frameIndex, float time, float r, float g, float b, float a);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spColorTimeline ColorTimeline;
 typedef spColorTimeline ColorTimeline;
@@ -184,18 +279,58 @@ typedef spColorTimeline ColorTimeline;
 
 
 /**/
 /**/
 
 
-typedef struct {
+static const int TWOCOLOR_ENTRIES = 8;
+
+typedef struct spTwoColorTimeline {
+	spCurveTimeline super;
+	int const framesCount;
+	float* const frames; /* time, r, g, b, a, ... */
+	int slotIndex;
+
+#ifdef __cplusplus
+	spTwoColorTimeline() :
+		super(),
+		framesCount(0),
+		frames(0),
+		slotIndex(0) {
+	}
+#endif
+} spTwoColorTimeline;
+
+SP_API spTwoColorTimeline* spTwoColorTimeline_create (int framesCount);
+
+SP_API void spTwoColorTimeline_setFrame (spTwoColorTimeline* self, int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spTwoColorTimeline TwoColorTimeline;
+#define TwoColorTimeline_create(...) spTwoColorTimeline_create(__VA_ARGS__)
+#define TwoColorTimeline_setFrame(...) spTwoColorTimeline_setFrame(__VA_ARGS__)
+#endif
+
+/**/
+
+typedef struct spAttachmentTimeline {
 	spTimeline super;
 	spTimeline super;
-	int const framesLength;
+	int const framesCount;
 	float* const frames; /* time, ... */
 	float* const frames; /* time, ... */
 	int slotIndex;
 	int slotIndex;
 	const char** const attachmentNames;
 	const char** const attachmentNames;
+
+#ifdef __cplusplus
+	spAttachmentTimeline() :
+		super(),
+		framesCount(0),
+		frames(0),
+		slotIndex(0),
+		attachmentNames(0) {
+	}
+#endif
 } spAttachmentTimeline;
 } spAttachmentTimeline;
 
 
-spAttachmentTimeline* spAttachmentTimeline_create (int frameCount);
+SP_API spAttachmentTimeline* spAttachmentTimeline_create (int framesCount);
 
 
 /* @param attachmentName May be 0. */
 /* @param attachmentName May be 0. */
-void spAttachmentTimeline_setFrame (spAttachmentTimeline* self, int frameIndex, float time, const char* attachmentName);
+SP_API void spAttachmentTimeline_setFrame (spAttachmentTimeline* self, int frameIndex, float time, const char* attachmentName);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spAttachmentTimeline AttachmentTimeline;
 typedef spAttachmentTimeline AttachmentTimeline;
@@ -205,16 +340,25 @@ typedef spAttachmentTimeline AttachmentTimeline;
 
 
 /**/
 /**/
 
 
-typedef struct {
+typedef struct spEventTimeline {
 	spTimeline super;
 	spTimeline super;
-	int const framesLength;
+	int const framesCount;
 	float* const frames; /* time, ... */
 	float* const frames; /* time, ... */
 	spEvent** const events;
 	spEvent** const events;
+
+#ifdef __cplusplus
+	spEventTimeline() :
+		super(),
+		framesCount(0),
+		frames(0),
+		events(0) {
+	}
+#endif
 } spEventTimeline;
 } spEventTimeline;
 
 
-spEventTimeline* spEventTimeline_create (int frameCount);
+SP_API spEventTimeline* spEventTimeline_create (int framesCount);
 
 
-void spEventTimeline_setFrame (spEventTimeline* self, int frameIndex, float time, spEvent* event);
+SP_API void spEventTimeline_setFrame (spEventTimeline* self, int frameIndex, spEvent* event);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spEventTimeline EventTimeline;
 typedef spEventTimeline EventTimeline;
@@ -224,17 +368,27 @@ typedef spEventTimeline EventTimeline;
 
 
 /**/
 /**/
 
 
-typedef struct {
+typedef struct spDrawOrderTimeline {
 	spTimeline super;
 	spTimeline super;
-	int const framesLength;
+	int const framesCount;
 	float* const frames; /* time, ... */
 	float* const frames; /* time, ... */
 	const int** const drawOrders;
 	const int** const drawOrders;
-	int const slotCount;
+	int const slotsCount;
+
+#ifdef __cplusplus
+	spDrawOrderTimeline() :
+		super(),
+		framesCount(0),
+		frames(0),
+		drawOrders(0),
+		slotsCount(0) {
+	}
+#endif
 } spDrawOrderTimeline;
 } spDrawOrderTimeline;
 
 
-spDrawOrderTimeline* spDrawOrderTimeline_create (int frameCount, int slotCount);
+SP_API spDrawOrderTimeline* spDrawOrderTimeline_create (int framesCount, int slotsCount);
 
 
-void spDrawOrderTimeline_setFrame (spDrawOrderTimeline* self, int frameIndex, float time, const int* drawOrder);
+SP_API void spDrawOrderTimeline_setFrame (spDrawOrderTimeline* self, int frameIndex, float time, const int* drawOrder);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spDrawOrderTimeline DrawOrderTimeline;
 typedef spDrawOrderTimeline DrawOrderTimeline;
@@ -242,6 +396,191 @@ typedef spDrawOrderTimeline DrawOrderTimeline;
 #define DrawOrderTimeline_setFrame(...) spDrawOrderTimeline_setFrame(__VA_ARGS__)
 #define DrawOrderTimeline_setFrame(...) spDrawOrderTimeline_setFrame(__VA_ARGS__)
 #endif
 #endif
 
 
+/**/
+
+typedef struct spDeformTimeline {
+	spCurveTimeline super;
+	int const framesCount;
+	float* const frames; /* time, ... */
+	int const frameVerticesCount;
+	const float** const frameVertices;
+	int slotIndex;
+	spAttachment* attachment;
+
+#ifdef __cplusplus
+	spDeformTimeline() :
+		super(),
+		framesCount(0),
+		frames(0),
+		frameVerticesCount(0),
+		frameVertices(0),
+		slotIndex(0) {
+	}
+#endif
+} spDeformTimeline;
+
+SP_API spDeformTimeline* spDeformTimeline_create (int framesCount, int frameVerticesCount);
+
+SP_API void spDeformTimeline_setFrame (spDeformTimeline* self, int frameIndex, float time, float* vertices);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spDeformTimeline DeformTimeline;
+#define DeformTimeline_create(...) spDeformTimeline_create(__VA_ARGS__)
+#define DeformTimeline_setFrame(...) spDeformTimeline_setFrame(__VA_ARGS__)
+#endif
+
+/**/
+
+static const int IKCONSTRAINT_ENTRIES = 6;
+
+typedef struct spIkConstraintTimeline {
+	spCurveTimeline super;
+	int const framesCount;
+	float* const frames; /* time, mix, bendDirection, ... */
+	int ikConstraintIndex;
+
+#ifdef __cplusplus
+	spIkConstraintTimeline() :
+		super(),
+		framesCount(0),
+		frames(0),
+		ikConstraintIndex(0) {
+	}
+#endif
+} spIkConstraintTimeline;
+
+SP_API spIkConstraintTimeline* spIkConstraintTimeline_create (int framesCount);
+
+SP_API void spIkConstraintTimeline_setFrame (spIkConstraintTimeline* self, int frameIndex, float time, float mix, float softness, int bendDirection, int /*boolean*/ compress, int /**boolean**/ stretch);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spIkConstraintTimeline IkConstraintTimeline;
+#define IkConstraintTimeline_create(...) spIkConstraintTimeline_create(__VA_ARGS__)
+#define IkConstraintTimeline_setFrame(...) spIkConstraintTimeline_setFrame(__VA_ARGS__)
+#endif
+
+/**/
+
+static const int TRANSFORMCONSTRAINT_ENTRIES = 5;
+
+typedef struct spTransformConstraintTimeline {
+	spCurveTimeline super;
+	int const framesCount;
+	float* const frames; /* time, rotate mix, translate mix, scale mix, shear mix, ... */
+	int transformConstraintIndex;
+
+#ifdef __cplusplus
+	spTransformConstraintTimeline() :
+		super(),
+		framesCount(0),
+		frames(0),
+		transformConstraintIndex(0) {
+	}
+#endif
+} spTransformConstraintTimeline;
+
+SP_API spTransformConstraintTimeline* spTransformConstraintTimeline_create (int framesCount);
+
+SP_API void spTransformConstraintTimeline_setFrame (spTransformConstraintTimeline* self, int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spTransformConstraintTimeline TransformConstraintTimeline;
+#define TransformConstraintTimeline_create(...) spTransformConstraintTimeline_create(__VA_ARGS__)
+#define TransformConstraintTimeline_setFrame(...) spTransformConstraintTimeline_setFrame(__VA_ARGS__)
+#endif
+
+/**/
+
+static const int PATHCONSTRAINTPOSITION_ENTRIES = 2;
+
+typedef struct spPathConstraintPositionTimeline {
+	spCurveTimeline super;
+	int const framesCount;
+	float* const frames; /* time, rotate mix, translate mix, scale mix, shear mix, ... */
+	int pathConstraintIndex;
+
+#ifdef __cplusplus
+	spPathConstraintPositionTimeline() :
+		super(),
+		framesCount(0),
+		frames(0),
+		pathConstraintIndex(0) {
+	}
+#endif
+} spPathConstraintPositionTimeline;
+
+SP_API spPathConstraintPositionTimeline* spPathConstraintPositionTimeline_create (int framesCount);
+
+SP_API void spPathConstraintPositionTimeline_setFrame (spPathConstraintPositionTimeline* self, int frameIndex, float time, float value);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spPathConstraintPositionTimeline PathConstraintPositionTimeline;
+#define PathConstraintPositionTimeline_create(...) spPathConstraintPositionTimeline_create(__VA_ARGS__)
+#define PathConstraintPositionTimeline_setFrame(...) spPathConstraintPositionTimeline_setFrame(__VA_ARGS__)
+#endif
+
+/**/
+
+static const int PATHCONSTRAINTSPACING_ENTRIES = 2;
+
+typedef struct spPathConstraintSpacingTimeline {
+	spCurveTimeline super;
+	int const framesCount;
+	float* const frames; /* time, rotate mix, translate mix, scale mix, shear mix, ... */
+	int pathConstraintIndex;
+
+#ifdef __cplusplus
+	spPathConstraintSpacingTimeline() :
+		super(),
+		framesCount(0),
+		frames(0),
+		pathConstraintIndex(0) {
+	}
+#endif
+} spPathConstraintSpacingTimeline;
+
+SP_API spPathConstraintSpacingTimeline* spPathConstraintSpacingTimeline_create (int framesCount);
+
+SP_API void spPathConstraintSpacingTimeline_setFrame (spPathConstraintSpacingTimeline* self, int frameIndex, float time, float value);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spPathConstraintSpacingTimeline PathConstraintSpacingTimeline;
+#define PathConstraintSpacingTimeline_create(...) spPathConstraintSpacingTimeline_create(__VA_ARGS__)
+#define PathConstraintSpacingTimeline_setFrame(...) spPathConstraintSpacingTimeline_setFrame(__VA_ARGS__)
+#endif
+
+/**/
+
+static const int PATHCONSTRAINTMIX_ENTRIES = 3;
+
+typedef struct spPathConstraintMixTimeline {
+	spCurveTimeline super;
+	int const framesCount;
+	float* const frames; /* time, rotate mix, translate mix, scale mix, shear mix, ... */
+	int pathConstraintIndex;
+
+#ifdef __cplusplus
+	spPathConstraintMixTimeline() :
+		super(),
+		framesCount(0),
+		frames(0),
+		pathConstraintIndex(0) {
+	}
+#endif
+} spPathConstraintMixTimeline;
+
+SP_API spPathConstraintMixTimeline* spPathConstraintMixTimeline_create (int framesCount);
+
+SP_API void spPathConstraintMixTimeline_setFrame (spPathConstraintMixTimeline* self, int frameIndex, float time, float rotateMix, float translateMix);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spPathConstraintMixTimeline PathConstraintMixTimeline;
+#define PathConstraintMixTimeline_create(...) spPathConstraintMixTimeline_create(__VA_ARGS__)
+#define PathConstraintMixTimeline_setFrame(...) spPathConstraintMixTimeline_setFrame(__VA_ARGS__)
+#endif
+
+/**/
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
 #endif
 #endif

+ 948 - 173
engine/source/spine/AnimationState.c

@@ -1,282 +1,1057 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
-#include <spine/Animation.h>
 #include <spine/AnimationState.h>
 #include <spine/AnimationState.h>
-#include <spine/AnimationStateData.h>
-#include <spine/Event.h>
 #include <spine/extension.h>
 #include <spine/extension.h>
-#include <spine/Skeleton.h>
-#include <spine/SkeletonData.h>
-#include <string.h>
+#include <limits.h>
 
 
-spTrackEntry* _spTrackEntry_create () {
-	spTrackEntry* entry = NEW(spTrackEntry);
-	entry->timeScale = 1;
-	entry->lastTime = -1;
-	return entry;
+#define SUBSEQUENT 0
+#define FIRST 1
+#define HOLD 2
+#define HOLD_MIX 3
+
+#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_disposeNext (spAnimationState* self, spTrackEntry* entry);
+void _spAnimationState_animationsChanged (spAnimationState* self);
+float* _spAnimationState_resizeTimelinesRotation(spTrackEntry* entry, int newSize);
+int* _spAnimationState_resizeTimelinesFirst(spTrackEntry* entry, int newSize);
+void _spAnimationState_ensureCapacityPropertyIDs(spAnimationState* self, int capacity);
+int _spAnimationState_addPropertyID(spAnimationState* self, int 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 _spTrackEntry_dispose (spTrackEntry* 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);
 	FREE(entry);
 }
 }
 
 
-void _spTrackEntry_disposeAll (spTrackEntry* entry) {
+void _spAnimationState_disposeTrackEntries (spAnimationState* state, spTrackEntry* entry) {
 	while (entry) {
 	while (entry) {
 		spTrackEntry* next = entry->next;
 		spTrackEntry* next = entry->next;
-		_spTrackEntry_dispose(entry);
+		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;
 		entry = next;
 	}
 	}
 }
 }
 
 
-/**/
+spAnimationState* spAnimationState_create (spAnimationStateData* data) {
+	_spAnimationState* internal;
+	spAnimationState* self;
 
 
-typedef struct {
-	spAnimationState super;
-	spEvent** events;
-} _spAnimationState;
+	if (!SP_EMPTY_ANIMATION) {
+		SP_EMPTY_ANIMATION = (spAnimation*)1; /* dirty trick so we can recursively call spAnimation_create */
+		SP_EMPTY_ANIMATION = spAnimation_create("<empty>", 0);
+	}
 
 
-void _spAnimationState_setCurrent (spAnimationState* self, int index, spTrackEntry* entry);
+	internal = NEW(_spAnimationState);
+	self = SUPER(internal);
 
 
-spAnimationState* spAnimationState_create (spAnimationStateData* data) {
-	_spAnimationState* internal = NEW(_spAnimationState);
-	spAnimationState* self = SUPER(internal);
-	internal->events = MALLOC(spEvent*, 64);
-	self->timeScale = 1;
 	CONST_CAST(spAnimationStateData*, self->data) = data;
 	CONST_CAST(spAnimationStateData*, self->data) = data;
+	self->timeScale = 1;
+
+	internal->queue = _spEventQueue_create(internal);
+	internal->events = CALLOC(spEvent*, 128);
+
+	internal->propertyIDs = CALLOC(int, 128);
+	internal->propertyIDsCapacity = 128;
+
 	return self;
 	return self;
 }
 }
 
 
 void spAnimationState_dispose (spAnimationState* self) {
 void spAnimationState_dispose (spAnimationState* self) {
 	int i;
 	int i;
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
-	FREE(internal->events);
-	for (i = 0; i < self->trackCount; i++)
-		_spTrackEntry_disposeAll(self->tracks[i]);
+	for (i = 0; i < self->tracksCount; i++)
+		_spAnimationState_disposeTrackEntries(self, self->tracks[i]);
 	FREE(self->tracks);
 	FREE(self->tracks);
-	FREE(self);
+	_spEventQueue_free(internal->queue);
+	FREE(internal->events);
+	FREE(internal->propertyIDs);
+	FREE(internal);
 }
 }
 
 
 void spAnimationState_update (spAnimationState* self, float delta) {
 void spAnimationState_update (spAnimationState* self, float delta) {
-	int i;
-	float trackDelta;
+	int i, n;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
 	delta *= self->timeScale;
 	delta *= self->timeScale;
-	for (i = 0; i < self->trackCount; i++) {
+	for (i = 0, n = self->tracksCount; i < n; i++) {
+		float currentDelta;
 		spTrackEntry* current = self->tracks[i];
 		spTrackEntry* current = self->tracks[i];
+		spTrackEntry* next;
 		if (!current) continue;
 		if (!current) continue;
 
 
-		trackDelta = delta * current->timeScale;
-		current->time += trackDelta;
-		if (current->previous) {
-			current->previous->time += trackDelta;
-			current->mixTime += trackDelta;
+		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;
 		}
 		}
 
 
-		if (current->next) {
-			if (current->lastTime >= current->next->delay) _spAnimationState_setCurrent(self, i, current->next);
+		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 {
 		} else {
-			/* End non-looping animation when it reaches its end time and there is no next entry. */
-			if (!current->loop && current->lastTime >= current->endTime) spAnimationState_clearTrack(self, i);
+			/* 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_disposeNext(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);
 }
 }
 
 
-void spAnimationState_apply (spAnimationState* self, spSkeleton* skeleton) {
+int /*boolean*/ _spAnimationState_updateMixingFrom (spAnimationState* self, spTrackEntry* to, float delta) {
+	spTrackEntry* from = to->mixingFrom;
+	int finished;
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	if (!from) return -1;
 
 
-	int i, ii;
-	int eventCount;
-	float time;
-	spTrackEntry* previous;
-	for (i = 0; i < self->trackCount; i++) {
-		spTrackEntry* current = self->tracks[i];
-		if (!current) continue;
+	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;
 
 
-		eventCount = 0;
+	if (internal->animationsChanged) _spAnimationState_animationsChanged(self);
 
 
-		time = current->time;
-		if (!current->loop && time > current->endTime) time = current->endTime;
+	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;
 
 
-		previous = current->previous;
-		if (!previous) {
-			spAnimation_apply(current->animation, skeleton, current->lastTime, time, current->loop, internal->events, &eventCount);
+		/* 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->timelinesCount;
+		timelines = current->animation->timelines;
+		if ((i == 0 && mix == 1) || blend == SP_MIX_BLEND_ADD) {
+			for (ii = 0; ii < timelineCount; ii++) {
+                timeline = timelines[ii];
+			    if (timeline->type == SP_TIMELINE_ATTACHMENT) {
+                    _spAnimationState_applyAttachmentTimeline(self, timeline, skeleton, animationTime, blend, -1);
+			    } else {
+                    spTimeline_apply(timelines[ii], skeleton, animationLast, animationTime, internal->events,
+                                     &internal->eventsCount, mix, blend, SP_MIX_DIRECTION_IN);
+                }
+            }
 		} else {
 		} else {
-			float alpha = current->mixTime / current->mixDuration;
+			spIntArray* timelineMode = current->timelineMode;
 
 
-			float previousTime = previous->time;
-			if (!previous->loop && previousTime > previous->endTime) previousTime = previous->endTime;
-			spAnimation_apply(previous->animation, skeleton, previousTime, previousTime, previous->loop, 0, 0);
+			firstFrame = current->timelinesRotationCount == 0;
+			if (firstFrame) _spAnimationState_resizeTimelinesRotation(current, timelineCount << 1);
+			timelinesRotation = current->timelinesRotation;
 
 
-			if (alpha >= 1) {
-				alpha = 1;
-				_spTrackEntry_dispose(current->previous);
-				current->previous = 0;
+			for (ii = 0; ii < timelineCount; ii++) {
+				timeline = timelines[ii];
+				timelineBlend = timelineMode->items[ii] == SUBSEQUENT ? blend : SP_MIX_BLEND_SETUP;
+				if (timeline->type == SP_TIMELINE_ROTATE)
+					_spAnimationState_applyRotateTimeline(self, timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
+				else if (timeline->type == SP_TIMELINE_ATTACHMENT)
+				    _spAnimationState_applyAttachmentTimeline(self, timeline, skeleton, animationTime, timelineBlend, -1);
+				else
+					spTimeline_apply(timeline, skeleton, animationLast, animationTime, internal->events, &internal->eventsCount, mix, timelineBlend, SP_MIX_DIRECTION_IN);
 			}
 			}
-			spAnimation_mix(current->animation, skeleton, current->lastTime, time, current->loop, internal->events, &eventCount,
-					alpha);
 		}
 		}
+		_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;
+
+	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;
+	}
 
 
-		for (ii = 0; ii < eventCount; ii++) {
-			spEvent* event = internal->events[ii];
-			if (current->listener) current->listener(self, i, ANIMATION_EVENT, event, 0);
-			if (self->listener) self->listener(self, i, ANIMATION_EVENT, event, 0);
+	events = mix < from->eventThreshold ? internal->events : 0;
+	attachments = mix < from->attachmentThreshold;
+	drawOrder = mix < from->drawOrderThreshold;
+	animationLast = from->animationLast;
+	animationTime = spTrackEntry_getAnimationTime(from);
+	timelineCount = from->animation->timelinesCount;
+	timelines = from->animation->timelines;
+	alphaHold = from->alpha * to->interruptAlpha; alphaMix = alphaHold * (1 - mix);
+	if (blend == SP_MIX_BLEND_ADD) {
+		for (i = 0; i < timelineCount; i++) {
+			spTimeline *timeline = timelines[i];
+			spTimeline_apply(timeline, skeleton, animationLast, animationTime, events, &internal->eventsCount, alphaMix, blend, SP_MIX_DIRECTION_OUT);
 		}
 		}
+	} else {
+		timelineMode = from->timelineMode;
+		timelineHoldMix = from->timelineHoldMix;
 
 
-		/* Check if completed the animation or a loop iteration. */
-		if (current->loop ? (FMOD(current->lastTime, current->endTime) > FMOD(time, current->endTime))
-				: (current->lastTime < current->endTime && time >= current->endTime)) {
-			int count = (int)(time / current->endTime);
-			if (current->listener) current->listener(self, i, ANIMATION_COMPLETE, 0, count);
-			if (self->listener) self->listener(self, i, ANIMATION_COMPLETE, 0, count);
-			if (i >= self->trackCount || self->tracks[i] != current) continue;
+		firstFrame = from->timelinesRotationCount == 0;
+		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->type == SP_TIMELINE_DRAWORDER) continue;
+                    timelineBlend = blend;
+					alpha = alphaMix;
+					break;
+				case FIRST:
+					timelineBlend = SP_MIX_BLEND_SETUP;
+					alpha = alphaMix;
+					break;
+				case HOLD:
+					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->type == SP_TIMELINE_ROTATE)
+				_spAnimationState_applyRotateTimeline(self, timeline, skeleton, animationTime, alpha, timelineBlend,
+					timelinesRotation, i << 1, firstFrame);
+			else if (timeline->type == SP_TIMELINE_ATTACHMENT)
+			    _spAnimationState_applyAttachmentTimeline(self, timeline, skeleton, animationTime, timelineBlend, attachments);
+			else {
+                if (drawOrder && timeline->type == SP_TIMELINE_DRAWORDER && timelineBlend == SP_MIX_BLEND_SETUP)
+                    direction = SP_MIX_DIRECTION_IN;
+				spTimeline_apply(timeline, skeleton, animationLast, animationTime, 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;
+    int frameIndex;
+    float* frames;
+
+    attachmentTimeline = SUB_CAST(spAttachmentTimeline, timeline);
+    slot = skeleton->slots[attachmentTimeline->slotIndex];
+    if (!slot->bone->active) return;
+
+    frames = attachmentTimeline->frames;
+    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 {
+        if (time >= frames[attachmentTimeline->framesCount - 1])
+            frameIndex = attachmentTimeline->framesCount - 1;
+        else
+            frameIndex = binarySearch1(frames, attachmentTimeline->framesCount, time) - 1;
+        _spAnimationState_setAttachment(self, skeleton, slot, attachmentTimeline->attachmentNames[frameIndex], attachments);
+    }
 
 
-		if (i >= self->trackCount || self->tracks[i] != current) continue;
-		current->lastTime = current->time;
+    /* 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;
+	int frame;
+	float prevRotation;
+	float frameTime;
+	float percent;
+	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->frames;
+	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;
+		if (time >= frames[rotateTimeline->framesCount - ROTATE_ENTRIES]) /* Time is after last frame. */
+			r2 = bone->data->rotation + frames[rotateTimeline->framesCount + ROTATE_PREV_ROTATION];
+		else {
+			/* Interpolate between the previous frame and the current frame. */
+			frame = _spCurveTimeline_binarySearch(frames, rotateTimeline->framesCount, time, ROTATE_ENTRIES);
+			prevRotation = frames[frame + ROTATE_PREV_ROTATION];
+			frameTime = frames[frame];
+			percent = spCurveTimeline_getCurvePercent(SUPER(rotateTimeline), (frame >> 1) - 1,
+				1 - (time - frameTime) / (frames[frame + ROTATE_PREV_TIME] - frameTime));
+
+			r2 = frames[frame + ROTATE_ROTATION] - prevRotation;
+			r2 -= (16384 - (int) (16384.499999999996 - r2 / 360)) * 360;
+			r2 = prevRotation + r2 * percent + bone->data->rotation;
+			r2 -= (16384 - (int) (16384.499999999996 - r2 / 360)) * 360;
+		}
+	}
+
+	/* 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;
+	r1 += total * alpha;
+	bone->rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360;
+}
+
+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) {
 void spAnimationState_clearTracks (spAnimationState* self) {
-	int i;
-	for (i = 0; i < self->trackCount; i++)
+	_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);
 		spAnimationState_clearTrack(self, i);
-	self->trackCount = 0;
+	self->tracksCount = 0;
+	internal->queue->drainDisabled = oldDrainDisabled;
+	_spEventQueue_drain(internal->queue);
 }
 }
 
 
 void spAnimationState_clearTrack (spAnimationState* self, int trackIndex) {
 void spAnimationState_clearTrack (spAnimationState* self, int trackIndex) {
 	spTrackEntry* current;
 	spTrackEntry* current;
-	if (trackIndex >= self->trackCount) return;
+	spTrackEntry* entry;
+	spTrackEntry* from;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+
+	if (trackIndex >= self->tracksCount) return;
 	current = self->tracks[trackIndex];
 	current = self->tracks[trackIndex];
 	if (!current) return;
 	if (!current) return;
 
 
-	if (current->listener) current->listener(self, trackIndex, ANIMATION_END, 0, 0);
-	if (self->listener) self->listener(self, trackIndex, ANIMATION_END, 0, 0);
+	_spEventQueue_end(internal->queue, current);
 
 
-	self->tracks[trackIndex] = 0;
-	if (current->previous) _spTrackEntry_dispose(current->previous);
-	_spTrackEntry_disposeAll(current);
-}
+	_spAnimationState_disposeNext(self, current);
 
 
-spTrackEntry* _spAnimationState_expandToIndex (spAnimationState* self, int index) {
-	spTrackEntry** newTracks;
-	if (index < self->trackCount) return self->tracks[index];
-	newTracks = CALLOC(spTrackEntry*, index + 1);
-	memcpy(newTracks, self->tracks, self->trackCount * sizeof(spTrackEntry*));
-	FREE(self->tracks);
-	self->tracks = newTracks;
-	self->trackCount = index + 1;
-	return 0;
+	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* entry) {
-	spTrackEntry* current = _spAnimationState_expandToIndex(self, index);
-	if (current) {
-		spTrackEntry* previous = current->previous;
-		current->previous = 0;
-
-		if (current->listener) current->listener(self, index, ANIMATION_END, 0, 0);
-		if (self->listener) self->listener(self, index, ANIMATION_END, 0, 0);
-
-		entry->mixDuration = spAnimationStateData_getMix(self->data, current->animation, entry->animation);
-		if (entry->mixDuration > 0) {
-			entry->mixTime = 0;
-			/* If a mix is in progress, mix from the closest animation. */
-			if (previous && current->mixTime / current->mixDuration < 0.5f) {
-				entry->previous = previous;
-				previous = current;
-			} else
-				entry->previous = current;
-		} else
-			_spTrackEntry_dispose(current);
+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;
 
 
-		if (previous) _spTrackEntry_dispose(previous);
-	}
+	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);
 
 
-	self->tracks[index] = entry;
+		from->timelinesRotationCount = 0;
+	}
 
 
-	if (entry->listener) entry->listener(self, index, ANIMATION_START, 0, 0);
-	if (self->listener) self->listener(self, index, ANIMATION_START, 0, 0);
+	_spEventQueue_start(internal->queue, current);
 }
 }
 
 
-spTrackEntry* spAnimationState_setAnimationByName (spAnimationState* self, int trackIndex, const char* animationName,
-		int/*bool*/loop) {
+/** 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);
 	spAnimation* animation = spSkeletonData_findAnimation(self->data->skeletonData, animationName);
 	return spAnimationState_setAnimation(self, trackIndex, animation, loop);
 	return spAnimationState_setAnimation(self, trackIndex, animation, loop);
 }
 }
 
 
 spTrackEntry* spAnimationState_setAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop) {
 spTrackEntry* spAnimationState_setAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop) {
 	spTrackEntry* entry;
 	spTrackEntry* entry;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	int interrupt = 1;
 	spTrackEntry* current = _spAnimationState_expandToIndex(self, trackIndex);
 	spTrackEntry* current = _spAnimationState_expandToIndex(self, trackIndex);
-	if (current) _spTrackEntry_disposeAll(current->next);
-
-	entry = _spTrackEntry_create();
-	entry->animation = animation;
-	entry->loop = loop;
-	entry->endTime = animation->duration;
-	_spAnimationState_setCurrent(self, trackIndex, entry);
+	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_disposeNext(self, current);
+			current = current->mixingFrom;
+			interrupt = 0;
+		} else
+			_spAnimationState_disposeNext(self, current);
+	}
+	entry = _spAnimationState_trackEntry(self, trackIndex, animation, loop, current);
+	_spAnimationState_setCurrent(self, trackIndex, entry, interrupt);
+	_spEventQueue_drain(internal->queue);
 	return entry;
 	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,
 spTrackEntry* spAnimationState_addAnimationByName (spAnimationState* self, int trackIndex, const char* animationName,
-		int/*bool*/loop, float delay) {
+	int/*bool*/loop, float delay
+) {
 	spAnimation* animation = spSkeletonData_findAnimation(self->data->skeletonData, animationName);
 	spAnimation* animation = spSkeletonData_findAnimation(self->data->skeletonData, animationName);
 	return spAnimationState_addAnimation(self, trackIndex, animation, loop, delay);
 	return spAnimationState_addAnimation(self, trackIndex, animation, loop, delay);
 }
 }
 
 
-spTrackEntry* spAnimationState_addAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop,
-		float delay) {
-	spTrackEntry* last;
-
-	spTrackEntry* entry = _spTrackEntry_create();
-	entry->animation = animation;
-	entry->loop = loop;
-	entry->endTime = animation->duration;
-
-	last = _spAnimationState_expandToIndex(self, trackIndex);
+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) {
 	if (last) {
 		while (last->next)
 		while (last->next)
 			last = 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;
 		last->next = entry;
-	} else
-		self->tracks[trackIndex] = entry;
-
-	if (delay <= 0) {
-		if (last)
-			delay += last->endTime - spAnimationStateData_getMix(self->data, last->animation, animation);
-		else
-			delay = 0;
+		if (delay <= 0) {
+			float duration = last->animationEnd - last->animationStart;
+			if (duration != 0) {
+				if (last->loop) {
+					delay += duration * (1 + (int) (last->trackTime / duration));
+				} else {
+					delay += MAX(duration, last->trackTime);
+				}
+				delay -= spAnimationStateData_getMix(self->data, last->animation, animation);
+			} else
+				delay = last->trackTime;
+		}
 	}
 	}
+
 	entry->delay = delay;
 	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;
+	if (delay <= 0) delay -= mixDuration;
+	entry = spAnimationState_addAnimation(self, trackIndex, SP_EMPTY_ANIMATION, 0, delay);
+	entry->mixDuration = mixDuration;
+	entry->trackEnd = mixDuration;
 	return entry;
 	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->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_disposeNext (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) {
+		int *newPropertyIDs = CALLOC(int, capacity << 1);
+		memcpy(newPropertyIDs, internal->propertyIDs, sizeof(int) * internal->propertyIDsCount);
+		FREE(internal->propertyIDs);
+		internal->propertyIDs = newPropertyIDs;
+		internal->propertyIDsCapacity = capacity << 1;
+	}
+}
+
+int _spAnimationState_addPropertyID(spAnimationState* self, int 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;
+}
+
 spTrackEntry* spAnimationState_getCurrent (spAnimationState* self, int trackIndex) {
 spTrackEntry* spAnimationState_getCurrent (spAnimationState* self, int trackIndex) {
-	if (trackIndex >= self->trackCount) return 0;
+	if (trackIndex >= self->tracksCount) return 0;
 	return self->tracks[trackIndex];
 	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);
+}
+
+int /*boolean*/ _spTrackEntry_hasTimeline(spTrackEntry* self, int id) {
+	spTimeline** timelines = self->animation->timelines;
+	int i, n;
+	for (i = 0, n = self->animation->timelinesCount; i < n; i++)
+		if (spTimeline_getPropertyId(timelines[i]) == id) return 1;
+	return 0;
+}
+
+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;
+	timelinesCount = entry->animation->timelinesCount;
+	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++) {
+			int id = spTimeline_getPropertyId(timelines[i]);
+			_spAnimationState_addPropertyID(state, id);
+			timelineMode[i] = HOLD;
+		}
+		return;
+	}
+
+	i = 0;
+	continue_outer:
+	for (; i < timelinesCount; i++) {
+		spTimeline* timeline = timelines[i];
+		int id = spTimeline_getPropertyId(timeline);
+		if (!_spAnimationState_addPropertyID(state, id))
+			timelineMode[i] = SUBSEQUENT;
+		else if (to == 0 || timeline->type == SP_TIMELINE_ATTACHMENT || timeline->type == SP_TIMELINE_DRAWORDER ||
+				timeline->type == SP_TIMELINE_EVENT || !_spTrackEntry_hasTimeline(to, id)) {
+			timelineMode[i] = FIRST;
+		} else {
+			for (next = to->mixingTo; next != 0; next = next->mixingTo) {
+				if (_spTrackEntry_hasTimeline(next, id)) continue;
+				if (next->mixDuration > 0) {
+					timelineMode[i] = HOLD_MIX;
+					timelineHoldMix[i] = next;
+					i++;
+					goto continue_outer;
+				}
+				break;
+			}
+			timelineMode[i] = HOLD;
+		}
+	}
+}

+ 122 - 46
engine/source/spine/AnimationState.h

@@ -1,98 +1,170 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_ANIMATIONSTATE_H_
 #ifndef SPINE_ANIMATIONSTATE_H_
 #define SPINE_ANIMATIONSTATE_H_
 #define SPINE_ANIMATIONSTATE_H_
 
 
+#include <spine/dll.h>
 #include <spine/Animation.h>
 #include <spine/Animation.h>
 #include <spine/AnimationStateData.h>
 #include <spine/AnimationStateData.h>
 #include <spine/Event.h>
 #include <spine/Event.h>
+#include <spine/Array.h>
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
 typedef enum {
 typedef enum {
-	ANIMATION_START, ANIMATION_END, ANIMATION_COMPLETE, ANIMATION_EVENT
+	SP_ANIMATION_START, SP_ANIMATION_INTERRUPT, SP_ANIMATION_END, SP_ANIMATION_COMPLETE, SP_ANIMATION_DISPOSE, SP_ANIMATION_EVENT
 } spEventType;
 } spEventType;
 
 
 typedef struct spAnimationState spAnimationState;
 typedef struct spAnimationState spAnimationState;
+typedef struct spTrackEntry spTrackEntry;
 
 
-typedef void (*spAnimationStateListener) (spAnimationState* state, int trackIndex, spEventType type, spEvent* event,
-		int loopCount);
+typedef void (*spAnimationStateListener) (spAnimationState* state, spEventType type, spTrackEntry* entry, spEvent* event);
+
+_SP_ARRAY_DECLARE_TYPE(spTrackEntryArray, spTrackEntry*)
 
 
-typedef struct spTrackEntry spTrackEntry;
 struct spTrackEntry {
 struct spTrackEntry {
-	spTrackEntry* next;
-	spTrackEntry* previous;
 	spAnimation* animation;
 	spAnimation* animation;
-	int/*bool*/loop;
-	float delay, time, lastTime, endTime, timeScale;
+	spTrackEntry* next;
+	spTrackEntry* mixingFrom;
+	spTrackEntry* mixingTo;
 	spAnimationStateListener listener;
 	spAnimationStateListener listener;
-	float mixTime, mixDuration;
+	int trackIndex;
+	int /*boolean*/ loop;
+	int /*boolean*/ holdPrevious;
+	float eventThreshold, attachmentThreshold, drawOrderThreshold;
+	float animationStart, animationEnd, animationLast, nextAnimationLast;
+	float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale;
+	float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha;
+	spMixBlend mixBlend;
+	spIntArray* timelineMode;
+	spTrackEntryArray* timelineHoldMix;
+	float* timelinesRotation;
+	int timelinesRotationCount;
+	void* rendererObject;
+	void* userData;
+
+#ifdef __cplusplus
+	spTrackEntry() :
+		animation(0),
+		next(0), mixingFrom(0), mixingTo(0),
+		listener(0),
+		trackIndex(0),
+		loop(0),
+		holdPrevious(0),
+		eventThreshold(0), attachmentThreshold(0), drawOrderThreshold(0),
+		animationStart(0), animationEnd(0), animationLast(0), nextAnimationLast(0),
+		delay(0), trackTime(0), trackLast(0), nextTrackLast(0), trackEnd(0), timeScale(0),
+		alpha(0), mixTime(0), mixDuration(0), interruptAlpha(0), totalAlpha(0),
+		mixBlend(SP_MIX_BLEND_REPLACE),
+		timelineMode(0),
+		timelineHoldMix(0),
+		timelinesRotation(0),
+		timelinesRotationCount(0),
+		rendererObject(0), userData(0) {
+	}
+#endif
 };
 };
 
 
 struct spAnimationState {
 struct spAnimationState {
 	spAnimationStateData* const data;
 	spAnimationStateData* const data;
-	float timeScale;
-	spAnimationStateListener listener;
-	void* context;
 
 
-	int trackCount;
+	int tracksCount;
 	spTrackEntry** tracks;
 	spTrackEntry** tracks;
+
+	spAnimationStateListener listener;
+
+	float timeScale;
+
+	void* rendererObject;
+	void* userData;
+
+    int unkeyedState;
+
+#ifdef __cplusplus
+	spAnimationState() :
+		data(0),
+		tracksCount(0),
+		tracks(0),
+		listener(0),
+		timeScale(0),
+		rendererObject(0),
+		userData(0),
+		unkeyedState(0) {
+	}
+#endif
 };
 };
 
 
 /* @param data May be 0 for no mixing. */
 /* @param data May be 0 for no mixing. */
-spAnimationState* spAnimationState_create (spAnimationStateData* data);
-void spAnimationState_dispose (spAnimationState* self);
+SP_API spAnimationState* spAnimationState_create (spAnimationStateData* data);
+SP_API void spAnimationState_dispose (spAnimationState* self);
 
 
-void spAnimationState_update (spAnimationState* self, float delta);
-void spAnimationState_apply (spAnimationState* self, struct spSkeleton* skeleton);
+SP_API void spAnimationState_update (spAnimationState* self, float delta);
+SP_API int /**bool**/ spAnimationState_apply (spAnimationState* self, struct spSkeleton* skeleton);
 
 
-void spAnimationState_clearTracks (spAnimationState* self);
-void spAnimationState_clearTrack (spAnimationState* self, int trackIndex);
+SP_API void spAnimationState_clearTracks (spAnimationState* self);
+SP_API void spAnimationState_clearTrack (spAnimationState* self, int trackIndex);
 
 
 /** Set the current animation. Any queued animations are cleared. */
 /** Set the current animation. Any queued animations are cleared. */
-spTrackEntry* spAnimationState_setAnimationByName (spAnimationState* self, int trackIndex, const char* animationName,
+SP_API spTrackEntry* spAnimationState_setAnimationByName (spAnimationState* self, int trackIndex, const char* animationName,
 		int/*bool*/loop);
 		int/*bool*/loop);
-spTrackEntry* spAnimationState_setAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop);
+SP_API spTrackEntry* spAnimationState_setAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop);
 
 
 /** Adds an animation to be played delay seconds after the current or last queued animation, taking into account any mix
 /** Adds an animation to be played delay seconds after the current or last queued animation, taking into account any mix
  * duration. */
  * duration. */
-spTrackEntry* spAnimationState_addAnimationByName (spAnimationState* self, int trackIndex, const char* animationName,
+SP_API spTrackEntry* spAnimationState_addAnimationByName (spAnimationState* self, int trackIndex, const char* animationName,
 		int/*bool*/loop, float delay);
 		int/*bool*/loop, float delay);
-spTrackEntry* spAnimationState_addAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop,
+SP_API spTrackEntry* spAnimationState_addAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop,
 		float delay);
 		float delay);
+SP_API spTrackEntry* spAnimationState_setEmptyAnimation(spAnimationState* self, int trackIndex, float mixDuration);
+SP_API spTrackEntry* spAnimationState_addEmptyAnimation(spAnimationState* self, int trackIndex, float mixDuration, float delay);
+SP_API void spAnimationState_setEmptyAnimations(spAnimationState* self, float mixDuration);
+
+SP_API spTrackEntry* spAnimationState_getCurrent (spAnimationState* self, int trackIndex);
+
+SP_API void spAnimationState_clearListenerNotifications(spAnimationState* self);
+
+SP_API float spTrackEntry_getAnimationTime (spTrackEntry* entry);
 
 
-spTrackEntry* spAnimationState_getCurrent (spAnimationState* self, int trackIndex);
+/** Use this to dispose static memory before your app exits to appease your memory leak detector*/
+SP_API void spAnimationState_disposeStatics ();
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spEventType EventType;
 typedef spEventType EventType;
+#define ANIMATION_START SP_ANIMATION_START
+#define ANIMATION_INTERRUPT SP_ANIMATION_INTERRUPT
+#define ANIMATION_END SP_ANIMATION_END
+#define ANIMATION_COMPLETE SP_ANIMATION_COMPLETE
+#define ANIMATION_DISPOSE SP_ANIMATION_DISPOSE
+#define ANIMATION_EVENT SP_ANIMATION_EVENT
 typedef spAnimationStateListener AnimationStateListener;
 typedef spAnimationStateListener AnimationStateListener;
 typedef spTrackEntry TrackEntry;
 typedef spTrackEntry TrackEntry;
 typedef spAnimationState AnimationState;
 typedef spAnimationState AnimationState;
@@ -106,7 +178,11 @@ typedef spAnimationState AnimationState;
 #define AnimationState_setAnimation(...) spAnimationState_setAnimation(__VA_ARGS__)
 #define AnimationState_setAnimation(...) spAnimationState_setAnimation(__VA_ARGS__)
 #define AnimationState_addAnimationByName(...) spAnimationState_addAnimationByName(__VA_ARGS__)
 #define AnimationState_addAnimationByName(...) spAnimationState_addAnimationByName(__VA_ARGS__)
 #define AnimationState_addAnimation(...) spAnimationState_addAnimation(__VA_ARGS__)
 #define AnimationState_addAnimation(...) spAnimationState_addAnimation(__VA_ARGS__)
+#define AnimationState_setEmptyAnimation(...) spAnimationState_setEmptyAnimation(__VA_ARGS__)
+#define AnimationState_addEmptyAnimation(...) spAnimationState_addEmptyAnimation(__VA_ARGS__)
+#define AnimationState_setEmptyAnimations(...) spAnimationState_setEmptyAnimations(__VA_ARGS__)
 #define AnimationState_getCurrent(...) spAnimationState_getCurrent(__VA_ARGS__)
 #define AnimationState_getCurrent(...) spAnimationState_getCurrent(__VA_ARGS__)
+#define AnimationState_clearListenerNotifications(...) spAnimationState_clearListenerNotifications(__VA_ARGS__)
 #endif
 #endif
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus

+ 23 - 22
engine/source/spine/AnimationStateData.c

@@ -1,29 +1,30 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include <spine/AnimationStateData.h>
 #include <spine/AnimationStateData.h>

+ 38 - 28
engine/source/spine/AnimationStateData.h

@@ -1,34 +1,36 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_ANIMATIONSTATEDATA_H_
 #ifndef SPINE_ANIMATIONSTATEDATA_H_
 #define SPINE_ANIMATIONSTATEDATA_H_
 #define SPINE_ANIMATIONSTATEDATA_H_
 
 
+#include <spine/dll.h>
 #include <spine/Animation.h>
 #include <spine/Animation.h>
 #include <spine/SkeletonData.h>
 #include <spine/SkeletonData.h>
 
 
@@ -36,19 +38,27 @@
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-typedef struct {
+typedef struct spAnimationStateData {
 	spSkeletonData* const skeletonData;
 	spSkeletonData* const skeletonData;
 	float defaultMix;
 	float defaultMix;
 	const void* const entries;
 	const void* const entries;
+
+#ifdef __cplusplus
+	spAnimationStateData() :
+		skeletonData(0),
+		defaultMix(0),
+		entries(0) {
+	}
+#endif
 } spAnimationStateData;
 } spAnimationStateData;
 
 
-spAnimationStateData* spAnimationStateData_create (spSkeletonData* skeletonData);
-void spAnimationStateData_dispose (spAnimationStateData* self);
+SP_API spAnimationStateData* spAnimationStateData_create (spSkeletonData* skeletonData);
+SP_API void spAnimationStateData_dispose (spAnimationStateData* self);
 
 
-void spAnimationStateData_setMixByName (spAnimationStateData* self, const char* fromName, const char* toName, float duration);
-void spAnimationStateData_setMix (spAnimationStateData* self, spAnimation* from, spAnimation* to, float duration);
+SP_API void spAnimationStateData_setMixByName (spAnimationStateData* self, const char* fromName, const char* toName, float duration);
+SP_API void spAnimationStateData_setMix (spAnimationStateData* self, spAnimation* from, spAnimation* to, float duration);
 /* Returns 0 if there is no mixing between the animations. */
 /* Returns 0 if there is no mixing between the animations. */
-float spAnimationStateData_getMix (spAnimationStateData* self, spAnimation* from, spAnimation* to);
+SP_API float spAnimationStateData_getMix (spAnimationStateData* self, spAnimation* from, spAnimation* to);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spAnimationStateData AnimationStateData;
 typedef spAnimationStateData AnimationStateData;

+ 38 - 0
engine/source/spine/Array.c

@@ -0,0 +1,38 @@
+/******************************************************************************
+ * 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/Array.h>
+#include <spine/extension.h>
+
+_SP_ARRAY_IMPLEMENT_TYPE(spFloatArray, float)
+_SP_ARRAY_IMPLEMENT_TYPE(spIntArray, int)
+_SP_ARRAY_IMPLEMENT_TYPE(spShortArray, short)
+_SP_ARRAY_IMPLEMENT_TYPE(spUnsignedShortArray, unsigned short)
+_SP_ARRAY_IMPLEMENT_TYPE(spArrayFloatArray, spFloatArray*)
+_SP_ARRAY_IMPLEMENT_TYPE(spArrayShortArray, spShortArray*)

+ 132 - 0
engine/source/spine/Array.h

@@ -0,0 +1,132 @@
+/******************************************************************************
+ * 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_ARRAY_H
+#define SPINE_ARRAY_H
+
+#include <spine/dll.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define _SP_ARRAY_DECLARE_TYPE(name, itemType) \
+	typedef struct name { int size; int capacity; itemType* items; } name; \
+	SP_API name* name##_create(int initialCapacity); \
+	SP_API void name##_dispose(name* self); \
+	SP_API void name##_clear(name* self); \
+	SP_API name* name##_setSize(name* self, int newSize); \
+	SP_API void name##_ensureCapacity(name* self, int newCapacity); \
+	SP_API void name##_add(name* self, itemType value); \
+	SP_API void name##_addAll(name* self, name* other); \
+	SP_API void name##_addAllValues(name* self, itemType* values, int offset, int count); \
+	SP_API void name##_removeAt(name* self, int index); \
+	SP_API int name##_contains(name* self, itemType value); \
+	SP_API itemType name##_pop(name* self); \
+	SP_API itemType name##_peek(name* self);
+
+#define _SP_ARRAY_IMPLEMENT_TYPE(name, itemType) \
+	name* name##_create(int initialCapacity) { \
+		name* array = CALLOC(name, 1); \
+		array->size = 0; \
+		array->capacity = initialCapacity; \
+		array->items = CALLOC(itemType, initialCapacity); \
+		return array; \
+	} \
+	void name##_dispose(name* self) { \
+		FREE(self->items); \
+		FREE(self); \
+	} \
+	void name##_clear(name* self) { \
+		self->size = 0; \
+	} \
+	name* name##_setSize(name* self, int newSize) { \
+		self->size = newSize; \
+		if (self->capacity < newSize) { \
+			self->capacity = MAX(8, (int)(self->size * 1.75f)); \
+			self->items = REALLOC(self->items, itemType, self->capacity); \
+		} \
+		return self; \
+	} \
+	void name##_ensureCapacity(name* self, int newCapacity) { \
+		if (self->capacity >= newCapacity) return; \
+		self->capacity = newCapacity; \
+		self->items = REALLOC(self->items, itemType, self->capacity); \
+	} \
+	void name##_add(name* self, itemType value) { \
+		if (self->size == self->capacity) { \
+			self->capacity = MAX(8, (int)(self->size * 1.75f)); \
+			self->items = REALLOC(self->items, itemType, self->capacity); \
+		} \
+		self->items[self->size++] = value; \
+	} \
+	void name##_addAll(name* self, name* other) { \
+		int i = 0; \
+		for (; i < other->size; i++) { \
+			name##_add(self, other->items[i]); \
+		} \
+	} \
+	void name##_addAllValues(name* self, itemType* values, int offset, int count) { \
+		int i = offset, n = offset + count; \
+		for (; i < n; i++) { \
+			name##_add(self, values[i]); \
+		} \
+	} \
+	void name##_removeAt(name* self, int index) { \
+		self->size--; \
+		memmove(self->items + index, self->items + index + 1, sizeof(itemType) * (self->size - index)); \
+	} \
+	int name##_contains(name* self, itemType value) { \
+		itemType* items = self->items; \
+		int i, n; \
+		for (i = 0, n = self->size; i < n; i++) { \
+			if (items[i] == value) return -1; \
+		} \
+		return 0; \
+	} \
+	itemType name##_pop(name* self) { \
+		itemType item = self->items[--self->size]; \
+		return item; \
+	} \
+	itemType name##_peek(name* self) { \
+		return self->items[self->size - 1]; \
+	}
+
+_SP_ARRAY_DECLARE_TYPE(spFloatArray, float)
+_SP_ARRAY_DECLARE_TYPE(spIntArray, int)
+_SP_ARRAY_DECLARE_TYPE(spShortArray, short)
+_SP_ARRAY_DECLARE_TYPE(spUnsignedShortArray, unsigned short)
+_SP_ARRAY_DECLARE_TYPE(spArrayFloatArray, spFloatArray*)
+_SP_ARRAY_DECLARE_TYPE(spArrayShortArray, spShortArray*)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPINE_ARRAY_H */

+ 116 - 93
engine/source/spine/Atlas.c

@@ -1,42 +1,44 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include <spine/Atlas.h>
 #include <spine/Atlas.h>
 #include <ctype.h>
 #include <ctype.h>
 #include <spine/extension.h>
 #include <spine/extension.h>
 
 
-spAtlasPage* spAtlasPage_create (const char* name) {
+spAtlasPage* spAtlasPage_create(spAtlas* atlas, const char* name) {
 	spAtlasPage* self = NEW(spAtlasPage);
 	spAtlasPage* self = NEW(spAtlasPage);
+	CONST_CAST(spAtlas*, self->atlas) = atlas;
 	MALLOC_STR(self->name, name);
 	MALLOC_STR(self->name, name);
 	return self;
 	return self;
 }
 }
 
 
-void spAtlasPage_dispose (spAtlasPage* self) {
+void spAtlasPage_dispose(spAtlasPage* self) {
 	_spAtlasPage_disposeTexture(self);
 	_spAtlasPage_disposeTexture(self);
 	FREE(self->name);
 	FREE(self->name);
 	FREE(self);
 	FREE(self);
@@ -44,11 +46,11 @@ void spAtlasPage_dispose (spAtlasPage* self) {
 
 
 /**/
 /**/
 
 
-spAtlasRegion* spAtlasRegion_create () {
-	return NEW(spAtlasRegion) ;
+spAtlasRegion* spAtlasRegion_create() {
+	return NEW(spAtlasRegion);
 }
 }
 
 
-void spAtlasRegion_dispose (spAtlasRegion* self) {
+void spAtlasRegion_dispose(spAtlasRegion* self) {
 	FREE(self->name);
 	FREE(self->name);
 	FREE(self->splits);
 	FREE(self->splits);
 	FREE(self->pads);
 	FREE(self->pads);
@@ -62,39 +64,34 @@ typedef struct {
 	const char* end;
 	const char* end;
 } Str;
 } Str;
 
 
-static void trim (Str* str) {
-	while (isspace(*str->begin) && str->begin < str->end)
+static void trim(Str* str) {
+	while (isspace((unsigned char)*str->begin) && str->begin < str->end)
 		(str->begin)++;
 		(str->begin)++;
 	if (str->begin == str->end) return;
 	if (str->begin == str->end) return;
 	str->end--;
 	str->end--;
-	while (isspace(*str->end) && str->end >= str->begin)
+	while (((unsigned char)*str->end == '\r') && str->end >= str->begin)
 		str->end--;
 		str->end--;
 	str->end++;
 	str->end++;
 }
 }
 
 
 /* Tokenize string without modification. Returns 0 on failure. */
 /* Tokenize string without modification. Returns 0 on failure. */
-static int readLine (const char* begin, const char* end, Str* str) {
-	static const char* nextStart;
-	if (begin) {
-		nextStart = begin;
-		return 1;
-	}
-	if (nextStart == end) return 0;
-	str->begin = nextStart;
+static int readLine(const char** begin, const char* end, Str* str) {
+	if (*begin == end) return 0;
+	str->begin = *begin;
 
 
 	/* Find next delimiter. */
 	/* Find next delimiter. */
-	while (nextStart != end && *nextStart != '\n')
-		nextStart++;
+	while (*begin != end && **begin != '\n')
+		(*begin)++;
 
 
-	str->end = nextStart;
+	str->end = *begin;
 	trim(str);
 	trim(str);
 
 
-	if (nextStart != end) nextStart++;
+	if (*begin != end) (*begin)++;
 	return 1;
 	return 1;
 }
 }
 
 
 /* Moves str->begin past the first occurence of c. Returns 0 on failure. */
 /* Moves str->begin past the first occurence of c. Returns 0 on failure. */
-static int beginPast (Str* str, char c) {
+static int beginPast(Str* str, char c) {
 	const char* begin = str->begin;
 	const char* begin = str->begin;
 	while (1) {
 	while (1) {
 		char lastSkippedChar = *begin;
 		char lastSkippedChar = *begin;
@@ -107,26 +104,23 @@ static int beginPast (Str* str, char c) {
 }
 }
 
 
 /* Returns 0 on failure. */
 /* Returns 0 on failure. */
-static int readValue (const char* end, Str* str) {
-	readLine(0, end, str);
+static int readValue(const char** begin, const char* end, Str* str) {
+	readLine(begin, end, str);
 	if (!beginPast(str, ':')) return 0;
 	if (!beginPast(str, ':')) return 0;
 	trim(str);
 	trim(str);
 	return 1;
 	return 1;
 }
 }
 
 
-/* Returns the number of tuple values read (2, 4, or 0 for failure). */
-static int readTuple (const char* end, Str tuple[]) {
+/* Returns the number of tuple values read (1, 2, 4, or 0 for failure). */
+static int readTuple(const char** begin, const char* end, Str tuple[]) {
 	int i;
 	int i;
-	Str str;
-	readLine(0, end, &str);
+	Str str = { NULL, NULL };
+	readLine(begin, end, &str);
 	if (!beginPast(&str, ':')) return 0;
 	if (!beginPast(&str, ':')) return 0;
 
 
 	for (i = 0; i < 3; ++i) {
 	for (i = 0; i < 3; ++i) {
 		tuple[i].begin = str.begin;
 		tuple[i].begin = str.begin;
-		if (!beginPast(&str, ',')) {
-			if (i == 0) return 0;
-			break;
-		}
+		if (!beginPast(&str, ',')) break;
 		tuple[i].end = str.begin - 2;
 		tuple[i].end = str.begin - 2;
 		trim(&tuple[i]);
 		trim(&tuple[i]);
 	}
 	}
@@ -136,7 +130,7 @@ static int readTuple (const char* end, Str tuple[]) {
 	return i + 1;
 	return i + 1;
 }
 }
 
 
-static char* mallocString (Str* str) {
+static char* mallocString(Str* str) {
 	int length = (int)(str->end - str->begin);
 	int length = (int)(str->end - str->begin);
 	char* string = MALLOC(char, length + 1);
 	char* string = MALLOC(char, length + 1);
 	memcpy(string, str->begin, length);
 	memcpy(string, str->begin, length);
@@ -144,56 +138,59 @@ static char* mallocString (Str* str) {
 	return string;
 	return string;
 }
 }
 
 
-static int indexOf (const char** array, int count, Str* str) {
+static int indexOf(const char** array, int count, Str* str) {
 	int length = (int)(str->end - str->begin);
 	int length = (int)(str->end - str->begin);
 	int i;
 	int i;
 	for (i = count - 1; i >= 0; i--)
 	for (i = count - 1; i >= 0; i--)
 		if (strncmp(array[i], str->begin, length) == 0) return i;
 		if (strncmp(array[i], str->begin, length) == 0) return i;
-	return -1;
+	return 0;
 }
 }
 
 
-static int equals (Str* str, const char* other) {
+static int equals(Str* str, const char* other) {
 	return strncmp(other, str->begin, str->end - str->begin) == 0;
 	return strncmp(other, str->begin, str->end - str->begin) == 0;
 }
 }
 
 
-static int toInt (Str* str) {
-	return strtol(str->begin, (char**)&str->end, 10);
+static int toInt(Str* str) {
+	return (int)strtol(str->begin, (char**)&str->end, 10);
 }
 }
 
 
-static spAtlas* abortAtlas (spAtlas* self) {
+static spAtlas* abortAtlas(spAtlas* self) {
 	spAtlas_dispose(self);
 	spAtlas_dispose(self);
 	return 0;
 	return 0;
 }
 }
 
 
-static const char* formatNames[] = {"Alpha", "Intensity", "LuminanceAlpha", "RGB565", "RGBA4444", "RGB888", "RGBA8888"};
-static const char* textureFilterNames[] = {"Nearest", "Linear", "MipMap", "MipMapNearestNearest", "MipMapLinearNearest",
-		"MipMapNearestLinear", "MipMapLinearLinear"};
+static const char* formatNames[] = { "", "Alpha", "Intensity", "LuminanceAlpha", "RGB565", "RGBA4444", "RGB888", "RGBA8888" };
+static const char* textureFilterNames[] = { "", "Nearest", "Linear", "MipMap", "MipMapNearestNearest", "MipMapLinearNearest",
+"MipMapNearestLinear", "MipMapLinearLinear" };
+
+spAtlas* spAtlas_create(const char* begin, int length, const char* dir, void* rendererObject) {
+	spAtlas* self;
 
 
-spAtlas* spAtlas_readAtlas (const char* begin, int length, const char* dir) {
 	int count;
 	int count;
 	const char* end = begin + length;
 	const char* end = begin + length;
 	int dirLength = (int)strlen(dir);
 	int dirLength = (int)strlen(dir);
 	int needsSlash = dirLength > 0 && dir[dirLength - 1] != '/' && dir[dirLength - 1] != '\\';
 	int needsSlash = dirLength > 0 && dir[dirLength - 1] != '/' && dir[dirLength - 1] != '\\';
 
 
-	spAtlas* self = NEW(spAtlas);
-
 	spAtlasPage *page = 0;
 	spAtlasPage *page = 0;
 	spAtlasPage *lastPage = 0;
 	spAtlasPage *lastPage = 0;
 	spAtlasRegion *lastRegion = 0;
 	spAtlasRegion *lastRegion = 0;
 	Str str;
 	Str str;
 	Str tuple[4];
 	Str tuple[4];
-	readLine(begin, 0, 0);
-	while (readLine(0, end, &str)) {
-		if (str.end - str.begin == 0) {
+
+	self = NEW(spAtlas);
+	self->rendererObject = rendererObject;
+
+	while (readLine(&begin, end, &str)) {
+		if (str.end - str.begin == 0)
 			page = 0;
 			page = 0;
-		} else if (!page) {
+		else if (!page) {
 			char* name = mallocString(&str);
 			char* name = mallocString(&str);
 			char* path = MALLOC(char, dirLength + needsSlash + strlen(name) + 1);
 			char* path = MALLOC(char, dirLength + needsSlash + strlen(name) + 1);
 			memcpy(path, dir, dirLength);
 			memcpy(path, dir, dirLength);
 			if (needsSlash) path[dirLength] = '/';
 			if (needsSlash) path[dirLength] = '/';
 			strcpy(path + dirLength + needsSlash, name);
 			strcpy(path + dirLength + needsSlash, name);
 
 
-			page = spAtlasPage_create(name);
+			page = spAtlasPage_create(self, name);
 			FREE(name);
 			FREE(name);
 			if (lastPage)
 			if (lastPage)
 				lastPage->next = page;
 				lastPage->next = page;
@@ -201,17 +198,35 @@ spAtlas* spAtlas_readAtlas (const char* begin, int length, const char* dir) {
 				self->pages = page;
 				self->pages = page;
 			lastPage = page;
 			lastPage = page;
 
 
-			if (!readValue(end, &str)) return abortAtlas(self);
-			page->format = (spAtlasFormat)indexOf(formatNames, 7, &str);
+			switch (readTuple(&begin, end, tuple)) {
+			case 0:
+				return abortAtlas(self);
+			case 2: /* size is only optional for an atlas packed with an old TexturePacker. */
+				page->width = toInt(tuple);
+				page->height = toInt(tuple + 1);
+				if (!readTuple(&begin, end, tuple)) return abortAtlas(self);
+			}
+			page->format = (spAtlasFormat)indexOf(formatNames, 8, tuple);
+
+			if (!readTuple(&begin, end, tuple)) return abortAtlas(self);
+			page->minFilter = (spAtlasFilter)indexOf(textureFilterNames, 8, tuple);
+			page->magFilter = (spAtlasFilter)indexOf(textureFilterNames, 8, tuple + 1);
 
 
-			if (!readTuple(end, tuple)) return abortAtlas(self);
-			page->minFilter = (spAtlasFilter)indexOf(textureFilterNames, 7, tuple);
-			page->magFilter = (spAtlasFilter)indexOf(textureFilterNames, 7, tuple + 1);
+			if (!readValue(&begin, end, &str)) return abortAtlas(self);
 
 
-			if (!readValue(end, &str)) return abortAtlas(self);
+			page->uWrap = SP_ATLAS_CLAMPTOEDGE;
+			page->vWrap = SP_ATLAS_CLAMPTOEDGE;
 			if (!equals(&str, "none")) {
 			if (!equals(&str, "none")) {
-				page->uWrap = *str.begin == 'x' ? ATLAS_REPEAT : (*str.begin == 'y' ? ATLAS_CLAMPTOEDGE : ATLAS_REPEAT);
-				page->vWrap = *str.begin == 'x' ? ATLAS_CLAMPTOEDGE : (*str.begin == 'y' ? ATLAS_REPEAT : ATLAS_REPEAT);
+				if (str.end - str.begin == 1) {
+					if (*str.begin == 'x')
+						page->uWrap = SP_ATLAS_REPEAT;
+					else if (*str.begin == 'y')
+						page->vWrap = SP_ATLAS_REPEAT;
+				}
+				else if (equals(&str, "xy")) {
+					page->uWrap = SP_ATLAS_REPEAT;
+					page->vWrap = SP_ATLAS_REPEAT;
+				}
 			}
 			}
 
 
 			_spAtlasPage_createTexture(page, path);
 			_spAtlasPage_createTexture(page, path);
@@ -227,14 +242,20 @@ spAtlas* spAtlas_readAtlas (const char* begin, int length, const char* dir) {
 			region->page = page;
 			region->page = page;
 			region->name = mallocString(&str);
 			region->name = mallocString(&str);
 
 
-			if (!readValue(end, &str)) return abortAtlas(self);
-			region->rotate = equals(&str, "true");
+			if (!readValue(&begin, end, &str)) return abortAtlas(self);
+			if (equals(&str, "true"))
+				region->degrees = 90;
+			else if (equals(&str, "false"))
+				region->degrees = 0;
+			else
+				region->degrees = toInt(&str);
+			region->rotate = region->degrees == 90;
 
 
-			if (readTuple(end, tuple) != 2) return abortAtlas(self);
+			if (readTuple(&begin, end, tuple) != 2) return abortAtlas(self);
 			region->x = toInt(tuple);
 			region->x = toInt(tuple);
 			region->y = toInt(tuple + 1);
 			region->y = toInt(tuple + 1);
 
 
-			if (readTuple(end, tuple) != 2) return abortAtlas(self);
+			if (readTuple(&begin, end, tuple) != 2) return abortAtlas(self);
 			region->width = toInt(tuple);
 			region->width = toInt(tuple);
 			region->height = toInt(tuple + 1);
 			region->height = toInt(tuple + 1);
 
 
@@ -248,7 +269,8 @@ spAtlas* spAtlas_readAtlas (const char* begin, int length, const char* dir) {
 				region->v2 = (region->y + region->height) / (float)page->height;
 				region->v2 = (region->y + region->height) / (float)page->height;
 			}
 			}
 
 
-			if (!(count = readTuple(end, tuple))) return abortAtlas(self);
+			count = readTuple(&begin, end, tuple);
+			if (!count) return abortAtlas(self);
 			if (count == 4) { /* split is optional */
 			if (count == 4) { /* split is optional */
 				region->splits = MALLOC(int, 4);
 				region->splits = MALLOC(int, 4);
 				region->splits[0] = toInt(tuple);
 				region->splits[0] = toInt(tuple);
@@ -256,7 +278,8 @@ spAtlas* spAtlas_readAtlas (const char* begin, int length, const char* dir) {
 				region->splits[2] = toInt(tuple + 2);
 				region->splits[2] = toInt(tuple + 2);
 				region->splits[3] = toInt(tuple + 3);
 				region->splits[3] = toInt(tuple + 3);
 
 
-				if (!(count = readTuple(end, tuple))) return abortAtlas(self);
+				count = readTuple(&begin, end, tuple);
+				if (!count) return abortAtlas(self);
 				if (count == 4) { /* pad is optional, but only present with splits */
 				if (count == 4) { /* pad is optional, but only present with splits */
 					region->pads = MALLOC(int, 4);
 					region->pads = MALLOC(int, 4);
 					region->pads[0] = toInt(tuple);
 					region->pads[0] = toInt(tuple);
@@ -264,18 +287,18 @@ spAtlas* spAtlas_readAtlas (const char* begin, int length, const char* dir) {
 					region->pads[2] = toInt(tuple + 2);
 					region->pads[2] = toInt(tuple + 2);
 					region->pads[3] = toInt(tuple + 3);
 					region->pads[3] = toInt(tuple + 3);
 
 
-					if (!readTuple(end, tuple)) return abortAtlas(self);
+					if (!readTuple(&begin, end, tuple)) return abortAtlas(self);
 				}
 				}
 			}
 			}
 
 
 			region->originalWidth = toInt(tuple);
 			region->originalWidth = toInt(tuple);
 			region->originalHeight = toInt(tuple + 1);
 			region->originalHeight = toInt(tuple + 1);
 
 
-			readTuple(end, tuple);
+			readTuple(&begin, end, tuple);
 			region->offsetX = toInt(tuple);
 			region->offsetX = toInt(tuple);
 			region->offsetY = toInt(tuple + 1);
 			region->offsetY = toInt(tuple + 1);
 
 
-			if (!readValue(end, &str)) return abortAtlas(self);
+			if (!readValue(&begin, end, &str)) return abortAtlas(self);
 			region->index = toInt(&str);
 			region->index = toInt(&str);
 		}
 		}
 	}
 	}
@@ -283,7 +306,7 @@ spAtlas* spAtlas_readAtlas (const char* begin, int length, const char* dir) {
 	return self;
 	return self;
 }
 }
 
 
-spAtlas* spAtlas_readAtlasFile (const char* path) {
+spAtlas* spAtlas_createFromFile(const char* path, void* rendererObject) {
 	int dirLength;
 	int dirLength;
 	char *dir;
 	char *dir;
 	int length;
 	int length;
@@ -296,20 +319,20 @@ spAtlas* spAtlas_readAtlasFile (const char* path) {
 	const char* lastBackwardSlash = strrchr(path, '\\');
 	const char* lastBackwardSlash = strrchr(path, '\\');
 	const char* lastSlash = lastForwardSlash > lastBackwardSlash ? lastForwardSlash : lastBackwardSlash;
 	const char* lastSlash = lastForwardSlash > lastBackwardSlash ? lastForwardSlash : lastBackwardSlash;
 	if (lastSlash == path) lastSlash++; /* Never drop starting slash. */
 	if (lastSlash == path) lastSlash++; /* Never drop starting slash. */
-	dirLength = lastSlash ? lastSlash - path : 0;
+	dirLength = (int)(lastSlash ? lastSlash - path : 0);
 	dir = MALLOC(char, dirLength + 1);
 	dir = MALLOC(char, dirLength + 1);
 	memcpy(dir, path, dirLength);
 	memcpy(dir, path, dirLength);
 	dir[dirLength] = '\0';
 	dir[dirLength] = '\0';
 
 
 	data = _spUtil_readFile(path, &length);
 	data = _spUtil_readFile(path, &length);
-	if (data) atlas = spAtlas_readAtlas(data, length, dir);
+	if (data) atlas = spAtlas_create(data, length, dir, rendererObject);
 
 
 	FREE(data);
 	FREE(data);
 	FREE(dir);
 	FREE(dir);
 	return atlas;
 	return atlas;
 }
 }
 
 
-void spAtlas_dispose (spAtlas* self) {
+void spAtlas_dispose(spAtlas* self) {
 	spAtlasRegion* region, *nextRegion;
 	spAtlasRegion* region, *nextRegion;
 	spAtlasPage* page = self->pages;
 	spAtlasPage* page = self->pages;
 	while (page) {
 	while (page) {
@@ -328,7 +351,7 @@ void spAtlas_dispose (spAtlas* self) {
 	FREE(self);
 	FREE(self);
 }
 }
 
 
-spAtlasRegion* spAtlas_findRegion (const spAtlas* self, const char* name) {
+spAtlasRegion* spAtlas_findRegion(const spAtlas* self, const char* name) {
 	spAtlasRegion* region = self->regions;
 	spAtlasRegion* region = self->regions;
 	while (region) {
 	while (region) {
 		if (strcmp(region->name, name) == 0) return region;
 		if (strcmp(region->name, name) == 0) return region;

+ 81 - 43
engine/source/spine/Atlas.h

@@ -1,58 +1,74 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_ATLAS_H_
 #ifndef SPINE_ATLAS_H_
 #define SPINE_ATLAS_H_
 #define SPINE_ATLAS_H_
 
 
+#include <spine/dll.h>
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
+typedef struct spAtlas spAtlas;
+
 typedef enum {
 typedef enum {
-	ATLAS_ALPHA, ATLAS_INTENSITY, ATLAS_LUMINANCE_ALPHA, ATLAS_RGB565, ATLAS_RGBA4444, ATLAS_RGB888, ATLAS_RGBA8888
+	SP_ATLAS_UNKNOWN_FORMAT,
+	SP_ATLAS_ALPHA,
+	SP_ATLAS_INTENSITY,
+	SP_ATLAS_LUMINANCE_ALPHA,
+	SP_ATLAS_RGB565,
+	SP_ATLAS_RGBA4444,
+	SP_ATLAS_RGB888,
+	SP_ATLAS_RGBA8888
 } spAtlasFormat;
 } spAtlasFormat;
 
 
 typedef enum {
 typedef enum {
-	ATLAS_NEAREST,
-	ATLAS_LINEAR,
-	ATLAS_MIPMAP,
-	ATLAS_MIPMAP_NEAREST_NEAREST,
-	ATLAS_MIPMAP_LINEAR_NEAREST,
-	ATLAS_MIPMAP_NEAREST_LINEAR,
-	ATLAS_MIPMAP_LINEAR_LINEAR
+	SP_ATLAS_UNKNOWN_FILTER,
+	SP_ATLAS_NEAREST,
+	SP_ATLAS_LINEAR,
+	SP_ATLAS_MIPMAP,
+	SP_ATLAS_MIPMAP_NEAREST_NEAREST,
+	SP_ATLAS_MIPMAP_LINEAR_NEAREST,
+	SP_ATLAS_MIPMAP_NEAREST_LINEAR,
+	SP_ATLAS_MIPMAP_LINEAR_LINEAR
 } spAtlasFilter;
 } spAtlasFilter;
 
 
 typedef enum {
 typedef enum {
-	ATLAS_MIRROREDREPEAT, ATLAS_CLAMPTOEDGE, ATLAS_REPEAT
+	SP_ATLAS_MIRROREDREPEAT,
+	SP_ATLAS_CLAMPTOEDGE,
+	SP_ATLAS_REPEAT
 } spAtlasWrap;
 } spAtlasWrap;
 
 
 typedef struct spAtlasPage spAtlasPage;
 typedef struct spAtlasPage spAtlasPage;
 struct spAtlasPage {
 struct spAtlasPage {
+	const spAtlas* atlas;
 	const char* name;
 	const char* name;
 	spAtlasFormat format;
 	spAtlasFormat format;
 	spAtlasFilter minFilter, magFilter;
 	spAtlasFilter minFilter, magFilter;
@@ -64,13 +80,32 @@ struct spAtlasPage {
 	spAtlasPage* next;
 	spAtlasPage* next;
 };
 };
 
 
-spAtlasPage* spAtlasPage_create (const char* name);
-void spAtlasPage_dispose (spAtlasPage* self);
+SP_API spAtlasPage* spAtlasPage_create (spAtlas* atlas, const char* name);
+SP_API void spAtlasPage_dispose (spAtlasPage* self);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spAtlasFormat AtlasFormat;
 typedef spAtlasFormat AtlasFormat;
+#define ATLAS_UNKNOWN_FORMAT SP_ATLAS_UNKNOWN_FORMAT
+#define ATLAS_ALPHA SP_ATLAS_ALPHA
+#define ATLAS_INTENSITY SP_ATLAS_INTENSITY
+#define ATLAS_LUMINANCE_ALPHA SP_ATLAS_LUMINANCE_ALPHA
+#define ATLAS_RGB565 SP_ATLAS_RGB565
+#define ATLAS_RGBA4444 SP_ATLAS_RGBA4444
+#define ATLAS_RGB888 SP_ATLAS_RGB888
+#define ATLAS_RGBA8888 SP_ATLAS_RGBA8888
 typedef spAtlasFilter AtlasFilter;
 typedef spAtlasFilter AtlasFilter;
+#define ATLAS_UNKNOWN_FILTER SP_ATLAS_UNKNOWN_FILTER
+#define ATLAS_NEAREST SP_ATLAS_NEAREST
+#define ATLAS_LINEAR SP_ATLAS_LINEAR
+#define ATLAS_MIPMAP SP_ATLAS_MIPMAP
+#define ATLAS_MIPMAP_NEAREST_NEAREST SP_ATLAS_MIPMAP_NEAREST_NEAREST
+#define ATLAS_MIPMAP_LINEAR_NEAREST SP_ATLAS_MIPMAP_LINEAR_NEAREST
+#define ATLAS_MIPMAP_NEAREST_LINEAR SP_ATLAS_MIPMAP_NEAREST_LINEAR
+#define ATLAS_MIPMAP_LINEAR_LINEAR SP_ATLAS_MIPMAP_LINEAR_LINEAR
 typedef spAtlasWrap AtlasWrap;
 typedef spAtlasWrap AtlasWrap;
+#define ATLAS_MIRROREDREPEAT SP_ATLAS_MIRROREDREPEAT
+#define ATLAS_CLAMPTOEDGE SP_ATLAS_CLAMPTOEDGE
+#define ATLAS_REPEAT SP_ATLAS_REPEAT
 typedef spAtlasPage AtlasPage;
 typedef spAtlasPage AtlasPage;
 #define AtlasPage_create(...) spAtlasPage_create(__VA_ARGS__)
 #define AtlasPage_create(...) spAtlasPage_create(__VA_ARGS__)
 #define AtlasPage_dispose(...) spAtlasPage_dispose(__VA_ARGS__)
 #define AtlasPage_dispose(...) spAtlasPage_dispose(__VA_ARGS__)
@@ -87,6 +122,7 @@ struct spAtlasRegion {
 	int originalWidth, originalHeight;
 	int originalWidth, originalHeight;
 	int index;
 	int index;
 	int/*bool*/rotate;
 	int/*bool*/rotate;
+	int degrees;
 	int/*bool*/flip;
 	int/*bool*/flip;
 	int* splits;
 	int* splits;
 	int* pads;
 	int* pads;
@@ -96,8 +132,8 @@ struct spAtlasRegion {
 	spAtlasRegion* next;
 	spAtlasRegion* next;
 };
 };
 
 
-spAtlasRegion* spAtlasRegion_create ();
-void spAtlasRegion_dispose (spAtlasRegion* self);
+SP_API spAtlasRegion* spAtlasRegion_create ();
+SP_API void spAtlasRegion_dispose (spAtlasRegion* self);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spAtlasRegion AtlasRegion;
 typedef spAtlasRegion AtlasRegion;
@@ -107,24 +143,26 @@ typedef spAtlasRegion AtlasRegion;
 
 
 /**/
 /**/
 
 
-typedef struct {
+struct spAtlas {
 	spAtlasPage* pages;
 	spAtlasPage* pages;
 	spAtlasRegion* regions;
 	spAtlasRegion* regions;
-} spAtlas;
+
+	void* rendererObject;
+};
 
 
 /* Image files referenced in the atlas file will be prefixed with dir. */
 /* Image files referenced in the atlas file will be prefixed with dir. */
-spAtlas* spAtlas_readAtlas (const char* data, int length, const char* dir);
+SP_API spAtlas* spAtlas_create (const char* data, int length, const char* dir, void* rendererObject);
 /* Image files referenced in the atlas file will be prefixed with the directory containing the atlas file. */
 /* Image files referenced in the atlas file will be prefixed with the directory containing the atlas file. */
-spAtlas* spAtlas_readAtlasFile (const char* path);
-void spAtlas_dispose (spAtlas* atlas);
+SP_API spAtlas* spAtlas_createFromFile (const char* path, void* rendererObject);
+SP_API void spAtlas_dispose (spAtlas* atlas);
 
 
 /* Returns 0 if the region was not found. */
 /* Returns 0 if the region was not found. */
-spAtlasRegion* spAtlas_findRegion (const spAtlas* self, const char* name);
+SP_API spAtlasRegion* spAtlas_findRegion (const spAtlas* self, const char* name);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spAtlas Atlas;
 typedef spAtlas Atlas;
-#define Atlas_readAtlas(...) spAtlas_readAtlas(__VA_ARGS__)
-#define Atlas_readAtlasFile(...) spAtlas_readAtlasFile(__VA_ARGS__)
+#define Atlas_create(...) spAtlas_create(__VA_ARGS__)
+#define Atlas_createFromFile(...) spAtlas_createFromFile(__VA_ARGS__)
 #define Atlas_dispose(...) spAtlas_dispose(__VA_ARGS__)
 #define Atlas_dispose(...) spAtlas_dispose(__VA_ARGS__)
 #define Atlas_findRegion(...) spAtlas_findRegion(__VA_ARGS__)
 #define Atlas_findRegion(...) spAtlas_findRegion(__VA_ARGS__)
 #endif
 #endif

+ 63 - 30
engine/source/spine/AtlasAttachmentLoader.c

@@ -1,43 +1,44 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include <spine/AtlasAttachmentLoader.h>
 #include <spine/AtlasAttachmentLoader.h>
 #include <spine/extension.h>
 #include <spine/extension.h>
 
 
-spAttachment* _spAtlasAttachmentLoader_newAttachment (spAttachmentLoader* loader, spSkin* skin, spAttachmentType type,
-		const char* name) {
+spAttachment* _spAtlasAttachmentLoader_createAttachment (spAttachmentLoader* loader, spSkin* skin, spAttachmentType type,
+		const char* name, const char* path) {
 	spAtlasAttachmentLoader* self = SUB_CAST(spAtlasAttachmentLoader, loader);
 	spAtlasAttachmentLoader* self = SUB_CAST(spAtlasAttachmentLoader, loader);
 	switch (type) {
 	switch (type) {
-	case ATTACHMENT_REGION: {
+	case SP_ATTACHMENT_REGION: {
 		spRegionAttachment* attachment;
 		spRegionAttachment* attachment;
-		spAtlasRegion* region = spAtlas_findRegion(self->atlas, name);
+		spAtlasRegion* region = spAtlas_findRegion(self->atlas, path);
 		if (!region) {
 		if (!region) {
-			_spAttachmentLoader_setError(loader, "Region not found: ", name);
+			_spAttachmentLoader_setError(loader, "Region not found: ", path);
 			return 0;
 			return 0;
 		}
 		}
 		attachment = spRegionAttachment_create(name);
 		attachment = spRegionAttachment_create(name);
@@ -51,17 +52,49 @@ spAttachment* _spAtlasAttachmentLoader_newAttachment (spAttachmentLoader* loader
 		attachment->regionOriginalHeight = region->originalHeight;
 		attachment->regionOriginalHeight = region->originalHeight;
 		return SUPER(attachment);
 		return SUPER(attachment);
 	}
 	}
-	case ATTACHMENT_BOUNDING_BOX:
-		return SUPER(spBoundingBoxAttachment_create(name));
+	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->regionRotate = region->rotate;
+		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:
 	default:
 		_spAttachmentLoader_setUnknownTypeError(loader, type);
 		_spAttachmentLoader_setUnknownTypeError(loader, type);
 		return 0;
 		return 0;
 	}
 	}
+
+	UNUSED(skin);
 }
 }
 
 
 spAtlasAttachmentLoader* spAtlasAttachmentLoader_create (spAtlas* atlas) {
 spAtlasAttachmentLoader* spAtlasAttachmentLoader_create (spAtlas* atlas) {
 	spAtlasAttachmentLoader* self = NEW(spAtlasAttachmentLoader);
 	spAtlasAttachmentLoader* self = NEW(spAtlasAttachmentLoader);
-	_spAttachmentLoader_init(SUPER(self), _spAttachmentLoader_deinit, _spAtlasAttachmentLoader_newAttachment);
+	_spAttachmentLoader_init(SUPER(self), _spAttachmentLoader_deinit, _spAtlasAttachmentLoader_createAttachment, 0, 0);
 	self->atlas = atlas;
 	self->atlas = atlas;
 	return self;
 	return self;
 }
 }

+ 26 - 24
engine/source/spine/AtlasAttachmentLoader.h

@@ -1,34 +1,36 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_ATLASATTACHMENTLOADER_H_
 #ifndef SPINE_ATLASATTACHMENTLOADER_H_
 #define SPINE_ATLASATTACHMENTLOADER_H_
 #define SPINE_ATLASATTACHMENTLOADER_H_
 
 
+#include <spine/dll.h>
 #include <spine/AttachmentLoader.h>
 #include <spine/AttachmentLoader.h>
 #include <spine/Atlas.h>
 #include <spine/Atlas.h>
 
 
@@ -36,12 +38,12 @@
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-typedef struct {
+typedef struct spAtlasAttachmentLoader {
 	spAttachmentLoader super;
 	spAttachmentLoader super;
 	spAtlas* atlas;
 	spAtlas* atlas;
 } spAtlasAttachmentLoader;
 } spAtlasAttachmentLoader;
 
 
-spAtlasAttachmentLoader* spAtlasAttachmentLoader_create (spAtlas* atlas);
+SP_API spAtlasAttachmentLoader* spAtlasAttachmentLoader_create (spAtlas* atlas);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spAtlasAttachmentLoader AtlasAttachmentLoader;
 typedef spAtlasAttachmentLoader AtlasAttachmentLoader;

+ 36 - 26
engine/source/spine/Attachment.c

@@ -1,29 +1,30 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include <spine/Attachment.h>
 #include <spine/Attachment.h>
@@ -32,23 +33,32 @@
 
 
 typedef struct _spAttachmentVtable {
 typedef struct _spAttachmentVtable {
 	void (*dispose) (spAttachment* self);
 	void (*dispose) (spAttachment* self);
+	spAttachment* (*copy) (spAttachment* self);
 } _spAttachmentVtable;
 } _spAttachmentVtable;
 
 
 void _spAttachment_init (spAttachment* self, const char* name, spAttachmentType type, /**/
 void _spAttachment_init (spAttachment* self, const char* name, spAttachmentType type, /**/
-		void (*dispose) (spAttachment* self)) {
+		void (*dispose) (spAttachment* self), spAttachment* (*copy) (spAttachment* self)) {
 
 
 	CONST_CAST(_spAttachmentVtable*, self->vtable) = NEW(_spAttachmentVtable);
 	CONST_CAST(_spAttachmentVtable*, self->vtable) = NEW(_spAttachmentVtable);
-	VTABLE(spAttachment, self) ->dispose = dispose;
+	VTABLE(spAttachment, self)->dispose = dispose;
+	VTABLE(spAttachment, self)->copy = copy;
 
 
 	MALLOC_STR(self->name, name);
 	MALLOC_STR(self->name, name);
-	self->type = type;
+	CONST_CAST(spAttachmentType, self->type) = type;
 }
 }
 
 
 void _spAttachment_deinit (spAttachment* self) {
 void _spAttachment_deinit (spAttachment* self) {
+	if (self->attachmentLoader) spAttachmentLoader_disposeAttachment(self->attachmentLoader, self);
 	FREE(self->vtable);
 	FREE(self->vtable);
 	FREE(self->name);
 	FREE(self->name);
 }
 }
 
 
+spAttachment* spAttachment_copy (spAttachment* self) {
+	return VTABLE(spAttachment, self) ->copy(self);
+}
+
 void spAttachment_dispose (spAttachment* self) {
 void spAttachment_dispose (spAttachment* self) {
-	VTABLE(spAttachment, self) ->dispose(self);
+	self->refCount--;
+	if (self->refCount <= 0)
+		VTABLE(spAttachment, self) ->dispose(self);
 }
 }

+ 53 - 29
engine/source/spine/Attachment.h

@@ -1,56 +1,80 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_ATTACHMENT_H_
 #ifndef SPINE_ATTACHMENT_H_
 #define SPINE_ATTACHMENT_H_
 #define SPINE_ATTACHMENT_H_
 
 
+#include <spine/dll.h>
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-struct spSlot;
+struct spAttachmentLoader;
 
 
 typedef enum {
 typedef enum {
-	ATTACHMENT_REGION, ATTACHMENT_REGION_SEQUENCE, ATTACHMENT_BOUNDING_BOX
+	SP_ATTACHMENT_REGION,
+	SP_ATTACHMENT_BOUNDING_BOX,
+	SP_ATTACHMENT_MESH,
+	SP_ATTACHMENT_LINKED_MESH,
+	SP_ATTACHMENT_PATH,
+	SP_ATTACHMENT_POINT,
+	SP_ATTACHMENT_CLIPPING
 } spAttachmentType;
 } spAttachmentType;
 
 
-typedef struct spAttachment spAttachment;
-struct spAttachment {
+typedef struct spAttachment {
 	const char* const name;
 	const char* const name;
-	spAttachmentType type;
-
+	const spAttachmentType type;
 	const void* const vtable;
 	const void* const vtable;
-};
+	int refCount;
+	struct spAttachmentLoader* attachmentLoader;
+
+#ifdef __cplusplus
+	spAttachment() :
+		name(0),
+		type(SP_ATTACHMENT_REGION),
+		vtable(0),
+		refCount(0) {
+	}
+#endif
+} spAttachment;
 
 
 void spAttachment_dispose (spAttachment* self);
 void spAttachment_dispose (spAttachment* self);
 
 
+spAttachment* spAttachment_copy (spAttachment* self);
+
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spAttachmentType AttachmentType;
 typedef spAttachmentType AttachmentType;
+#define ATTACHMENT_REGION SP_ATTACHMENT_REGION
+#define ATTACHMENT_BOUNDING_BOX SP_ATTACHMENT_BOUNDING_BOX
+#define ATTACHMENT_MESH SP_ATTACHMENT_MESH
+#define ATTACHMENT_LINKED_MESH SP_ATTACHMENT_LINKED_MESH
 typedef spAttachment Attachment;
 typedef spAttachment Attachment;
 #define Attachment_dispose(...) spAttachment_dispose(__VA_ARGS__)
 #define Attachment_dispose(...) spAttachment_dispose(__VA_ARGS__)
 #endif
 #endif

+ 49 - 29
engine/source/spine/AttachmentLoader.c

@@ -1,29 +1,30 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include <spine/AttachmentLoader.h>
 #include <spine/AttachmentLoader.h>
@@ -31,16 +32,24 @@
 #include <spine/extension.h>
 #include <spine/extension.h>
 
 
 typedef struct _spAttachmentLoaderVtable {
 typedef struct _spAttachmentLoaderVtable {
-	spAttachment* (*newAttachment) (spAttachmentLoader* self, spSkin* skin, spAttachmentType type, const char* name);
+	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);
 	void (*dispose) (spAttachmentLoader* self);
 } _spAttachmentLoaderVtable;
 } _spAttachmentLoaderVtable;
 
 
-void _spAttachmentLoader_init (spAttachmentLoader* self, /**/
-void (*dispose) (spAttachmentLoader* self), /**/
-spAttachment* (*newAttachment) (spAttachmentLoader* self, spSkin* skin, spAttachmentType type, const char* name)) {
+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);
 	CONST_CAST(_spAttachmentLoaderVtable*, self->vtable) = NEW(_spAttachmentLoaderVtable);
 	VTABLE(spAttachmentLoader, self)->dispose = dispose;
 	VTABLE(spAttachmentLoader, self)->dispose = dispose;
-	VTABLE(spAttachmentLoader, self)->newAttachment = newAttachment;
+	VTABLE(spAttachmentLoader, self)->createAttachment = createAttachment;
+	VTABLE(spAttachmentLoader, self)->configureAttachment = configureAttachment;
+	VTABLE(spAttachmentLoader, self)->disposeAttachment = disposeAttachment;
 }
 }
 
 
 void _spAttachmentLoader_deinit (spAttachmentLoader* self) {
 void _spAttachmentLoader_deinit (spAttachmentLoader* self) {
@@ -54,12 +63,23 @@ void spAttachmentLoader_dispose (spAttachmentLoader* self) {
 	FREE(self);
 	FREE(self);
 }
 }
 
 
-spAttachment* spAttachmentLoader_newAttachment (spAttachmentLoader* self, spSkin* skin, spAttachmentType type, const char* name) {
+spAttachment* spAttachmentLoader_createAttachment (spAttachmentLoader* self, spSkin* skin, spAttachmentType type, const char* name,
+		const char* path) {
 	FREE(self->error1);
 	FREE(self->error1);
 	FREE(self->error2);
 	FREE(self->error2);
 	self->error1 = 0;
 	self->error1 = 0;
 	self->error2 = 0;
 	self->error2 = 0;
-	return VTABLE(spAttachmentLoader, self)->newAttachment(self, skin, type, name);
+	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) {
 void _spAttachmentLoader_setError (spAttachmentLoader* self, const char* error1, const char* error2) {

+ 41 - 32
engine/source/spine/AttachmentLoader.h

@@ -1,34 +1,36 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_ATTACHMENTLOADER_H_
 #ifndef SPINE_ATTACHMENTLOADER_H_
 #define SPINE_ATTACHMENTLOADER_H_
 #define SPINE_ATTACHMENTLOADER_H_
 
 
+#include <spine/dll.h>
 #include <spine/Attachment.h>
 #include <spine/Attachment.h>
 #include <spine/Skin.h>
 #include <spine/Skin.h>
 
 
@@ -36,30 +38,37 @@
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-typedef struct spAttachmentLoader spAttachmentLoader;
-struct spAttachmentLoader {
+typedef struct spAttachmentLoader {
 	const char* error1;
 	const char* error1;
 	const char* error2;
 	const char* error2;
 
 
 	const void* const vtable;
 	const void* const vtable;
 #ifdef __cplusplus
 #ifdef __cplusplus
 	spAttachmentLoader () :
 	spAttachmentLoader () :
-					error1(0),
-					error2(0),
-					vtable(0) {
+		error1(0),
+		error2(0),
+		vtable(0) {
 	}
 	}
 #endif
 #endif
-};
+} spAttachmentLoader;
 
 
-void spAttachmentLoader_dispose (spAttachmentLoader* self);
+SP_API void spAttachmentLoader_dispose (spAttachmentLoader* self);
 
 
-/* Returns 0 to not load an attachment. If 0 is returned and spAttachmentLoader.error1 is set, an error occurred. */
-spAttachment* spAttachmentLoader_newAttachment (spAttachmentLoader* self, spSkin* skin, spAttachmentType type, const char* name);
+/* Called to create each attachment. Returns 0 to not load an attachment. If 0 is returned and _spAttachmentLoader_setError was
+ * called, an error occurred. */
+SP_API spAttachment* spAttachmentLoader_createAttachment (spAttachmentLoader* self, spSkin* skin, spAttachmentType type, const char* name,
+		const char* path);
+/* Called after the attachment has been fully configured. */
+SP_API void spAttachmentLoader_configureAttachment (spAttachmentLoader* self, spAttachment* attachment);
+/* Called just before the attachment is disposed. This can release allocations made in spAttachmentLoader_configureAttachment. */
+SP_API void spAttachmentLoader_disposeAttachment (spAttachmentLoader* self, spAttachment* attachment);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spAttachmentLoader AttachmentLoader;
 typedef spAttachmentLoader AttachmentLoader;
 #define AttachmentLoader_dispose(...) spAttachmentLoader_dispose(__VA_ARGS__)
 #define AttachmentLoader_dispose(...) spAttachmentLoader_dispose(__VA_ARGS__)
-#define AttachmentLoader_newAttachment(...) spAttachmentLoader_newAttachment(__VA_ARGS__)
+#define AttachmentLoader_createAttachment(...) spAttachmentLoader_createAttachment(__VA_ARGS__)
+#define AttachmentLoader_configureAttachment(...) spAttachmentLoader_configureAttachment(__VA_ARGS__)
+#define AttachmentLoader_disposeAttachment(...) spAttachmentLoader_disposeAttachment(__VA_ARGS__)
 #endif
 #endif
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus

+ 252 - 62
engine/source/spine/Bone.c

@@ -1,99 +1,289 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include <spine/Bone.h>
 #include <spine/Bone.h>
 #include <spine/extension.h>
 #include <spine/extension.h>
-
+#include <stdio.h>
 static int yDown;
 static int yDown;
 
 
 void spBone_setYDown (int value) {
 void spBone_setYDown (int value) {
 	yDown = value;
 	yDown = value;
 }
 }
 
 
-spBone* spBone_create (spBoneData* data, spBone* parent) {
+int spBone_isYDown () {
+	return yDown;
+}
+
+spBone* spBone_create (spBoneData* data, spSkeleton* skeleton, spBone* parent) {
 	spBone* self = NEW(spBone);
 	spBone* self = NEW(spBone);
 	CONST_CAST(spBoneData*, self->data) = data;
 	CONST_CAST(spBoneData*, self->data) = data;
+	CONST_CAST(spSkeleton*, self->skeleton) = skeleton;
 	CONST_CAST(spBone*, self->parent) = parent;
 	CONST_CAST(spBone*, self->parent) = parent;
+	CONST_CAST(float, self->a) = 1.0f;
+	CONST_CAST(float, self->d) = 1.0f;
 	spBone_setToSetupPose(self);
 	spBone_setToSetupPose(self);
 	return self;
 	return self;
 }
 }
 
 
 void spBone_dispose (spBone* self) {
 void spBone_dispose (spBone* self) {
+	FREE(self->children);
 	FREE(self);
 	FREE(self);
 }
 }
 
 
+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;
+	self->appliedValid = 1;
+
+	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) {
 void spBone_setToSetupPose (spBone* self) {
 	self->x = self->data->x;
 	self->x = self->data->x;
 	self->y = self->data->y;
 	self->y = self->data->y;
 	self->rotation = self->data->rotation;
 	self->rotation = self->data->rotation;
 	self->scaleX = self->data->scaleX;
 	self->scaleX = self->data->scaleX;
 	self->scaleY = self->data->scaleY;
 	self->scaleY = self->data->scaleY;
+	self->shearX = self->data->shearX;
+	self->shearY = self->data->shearY;
 }
 }
 
 
-void spBone_updateWorldTransform (spBone* self, int flipX, int flipY) {
-	float radians, cosine, sine;
-	if (self->parent) {
-		CONST_CAST(float, self->worldX) = self->x * self->parent->m00 + self->y * self->parent->m01 + self->parent->worldX;
-		CONST_CAST(float, self->worldY) = self->x * self->parent->m10 + self->y * self->parent->m11 + self->parent->worldY;
-		if (self->data->inheritScale) {
-			CONST_CAST(float, self->worldScaleX) = self->parent->worldScaleX * self->scaleX;
-			CONST_CAST(float, self->worldScaleY) = self->parent->worldScaleY * self->scaleY;
+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;
+	self->appliedValid = 1;
+	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 {
 		} else {
-			CONST_CAST(float, self->worldScaleX) = self->scaleX;
-			CONST_CAST(float, self->worldScaleY) = self->scaleY;
+			self->ascaleX = 0;
+			self->ascaleY = SQRT(rb * rb + rd * rd);
+			self->ashearY = 0;
+			self->arotation = 90 - ATAN2(rd, rb) * RAD_DEG;
 		}
 		}
-		CONST_CAST(float, self->worldRotation) =
-				self->data->inheritRotation ? self->parent->worldRotation + self->rotation : self->rotation;
-	} else {
-		CONST_CAST(float, self->worldX) = flipX ? -self->x : self->x;
-		CONST_CAST(float, self->worldY) = flipY != yDown ? -self->y : self->y;
-		CONST_CAST(float, self->worldScaleX) = self->scaleX;
-		CONST_CAST(float, self->worldScaleY) = self->scaleY;
-		CONST_CAST(float, self->worldRotation) = self->rotation;
-	}
-	radians = (float)(self->worldRotation * 3.1415926535897932385 / 180);
-#ifdef __STDC_VERSION__
-	cosine = cosf(radians);
-	sine = sinf(radians);
-#else
-	cosine = (float)cos(radians);
-	sine = (float)sin(radians);
-#endif
-	CONST_CAST(float, self->m00) = cosine * self->worldScaleX;
-	CONST_CAST(float, self->m10) = sine * self->worldScaleX;
-	CONST_CAST(float, self->m01) = -sine * self->worldScaleY;
-	CONST_CAST(float, self->m11) = cosine * self->worldScaleY;
-	if (flipX) {
-		CONST_CAST(float, self->m00) = -self->m00;
-		CONST_CAST(float, self->m01) = -self->m01;
-	}
-	if (flipY != yDown) {
-		CONST_CAST(float, self->m10) = -self->m10;
-		CONST_CAST(float, self->m11) = -self->m11;
 	}
 	}
 }
 }
+
+void spBone_worldToLocal (spBone* self, float worldX, float worldY, float* localX, float* localY) {
+	float a = self->a, b = self->b, c = self->c, d = self->d;
+	float invDet = 1 / (a * d - b * c);
+	float x = worldX - self->worldX, y = worldY - self->worldY;
+	*localX = (x * d * invDet - y * b * invDet);
+	*localY = (y * a * invDet - x * 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;
+	CONST_CAST(int, self->appliedValid) = 0;
+}

+ 87 - 35
engine/source/spine/Bone.h

@@ -1,71 +1,123 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_BONE_H_
 #ifndef SPINE_BONE_H_
 #define SPINE_BONE_H_
 #define SPINE_BONE_H_
 
 
+#include <spine/dll.h>
 #include <spine/BoneData.h>
 #include <spine/BoneData.h>
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
+struct spSkeleton;
+
 typedef struct spBone spBone;
 typedef struct spBone spBone;
 struct spBone {
 struct spBone {
 	spBoneData* const data;
 	spBoneData* const data;
+	struct spSkeleton* const skeleton;
 	spBone* const parent;
 	spBone* const parent;
-	float x, y;
-	float rotation;
-	float scaleX, scaleY;
-
-	float const m00, m01, worldX; /* a b x */
-	float const m10, m11, worldY; /* c d y */
-	float const worldRotation;
-	float const worldScaleX, worldScaleY;
+	int childrenCount;
+	spBone** const children;
+	float x, y, rotation, scaleX, scaleY, shearX, shearY;
+	float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY;
+	int /*bool*/ appliedValid;
+
+	float const a, b, worldX;
+	float const c, d, worldY;
+
+	int/*bool*/ sorted;
+	int/*bool*/ active;
+
+#ifdef __cplusplus
+	spBone() :
+		data(0),
+		skeleton(0),
+		parent(0),
+		childrenCount(0), children(0),
+		x(0), y(0), rotation(0), scaleX(0), scaleY(0),
+		ax(0), ay(0), arotation(0), ascaleX(0), ascaleY(0), ashearX(0), ashearY(0),
+		appliedValid(0),
+
+		a(0), b(0), worldX(0),
+		c(0), d(0), worldY(0),
+
+		sorted(0), active(0) {
+	}
+#endif
 };
 };
 
 
-void spBone_setYDown (int/*bool*/yDown);
+SP_API void spBone_setYDown (int/*bool*/yDown);
+SP_API int/*bool*/spBone_isYDown ();
 
 
 /* @param parent May be 0. */
 /* @param parent May be 0. */
-spBone* spBone_create (spBoneData* data, spBone* parent);
-void spBone_dispose (spBone* self);
+SP_API spBone* spBone_create (spBoneData* data, struct spSkeleton* skeleton, spBone* parent);
+SP_API void spBone_dispose (spBone* self);
+
+SP_API void spBone_setToSetupPose (spBone* self);
+
+SP_API void spBone_updateWorldTransform (spBone* self);
+SP_API void spBone_updateWorldTransformWith (spBone* self, float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY);
+
+SP_API float spBone_getWorldRotationX (spBone* self);
+SP_API float spBone_getWorldRotationY (spBone* self);
+SP_API float spBone_getWorldScaleX (spBone* self);
+SP_API float spBone_getWorldScaleY (spBone* self);
 
 
-void spBone_setToSetupPose (spBone* self);
+SP_API void spBone_updateAppliedTransform (spBone* self);
 
 
-void spBone_updateWorldTransform (spBone* self, int/*bool*/flipX, int/*bool*/flipY);
+SP_API void spBone_worldToLocal (spBone* self, float worldX, float worldY, float* localX, float* localY);
+SP_API void spBone_localToWorld (spBone* self, float localX, float localY, float* worldX, float* worldY);
+SP_API float spBone_worldToLocalRotation (spBone* self, float worldRotation);
+SP_API float spBone_localToWorldRotation (spBone* self, float localRotation);
+SP_API void spBone_rotateWorld (spBone* self, float degrees);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spBone Bone;
 typedef spBone Bone;
 #define Bone_setYDown(...) spBone_setYDown(__VA_ARGS__)
 #define Bone_setYDown(...) spBone_setYDown(__VA_ARGS__)
+#define Bone_isYDown() spBone_isYDown()
 #define Bone_create(...) spBone_create(__VA_ARGS__)
 #define Bone_create(...) spBone_create(__VA_ARGS__)
 #define Bone_dispose(...) spBone_dispose(__VA_ARGS__)
 #define Bone_dispose(...) spBone_dispose(__VA_ARGS__)
 #define Bone_setToSetupPose(...) spBone_setToSetupPose(__VA_ARGS__)
 #define Bone_setToSetupPose(...) spBone_setToSetupPose(__VA_ARGS__)
 #define Bone_updateWorldTransform(...) spBone_updateWorldTransform(__VA_ARGS__)
 #define Bone_updateWorldTransform(...) spBone_updateWorldTransform(__VA_ARGS__)
+#define Bone_updateWorldTransformWith(...) spBone_updateWorldTransformWith(__VA_ARGS__)
+#define Bone_getWorldRotationX(...) spBone_getWorldRotationX(__VA_ARGS__)
+#define Bone_getWorldRotationY(...) spBone_getWorldRotationY(__VA_ARGS__)
+#define Bone_getWorldScaleX(...) spBone_getWorldScaleX(__VA_ARGS__)
+#define Bone_getWorldScaleY(...) spBone_getWorldScaleY(__VA_ARGS__)
+#define Bone_updateAppliedTransform(...) spBone_updateAppliedTransform(__VA_ARGS__)
+#define Bone_worldToLocal(...) spBone_worldToLocal(__VA_ARGS__)
+#define Bone_localToWorld(...) spBone_localToWorld(__VA_ARGS__)
+#define Bone_worldToLocalRotation(...) spBone_worldToLocalRotation(__VA_ARGS__)
+#define Bone_localToWorldRotation(...) spBone_localToWorldRotation(__VA_ARGS__)
+#define Bone_rotateWorld(...) spBone_rotateWorld(__VA_ARGS__)
 #endif
 #endif
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus

+ 26 - 25
engine/source/spine/BoneData.c

@@ -1,42 +1,43 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include <spine/BoneData.h>
 #include <spine/BoneData.h>
 #include <spine/extension.h>
 #include <spine/extension.h>
 
 
-spBoneData* spBoneData_create (const char* name, spBoneData* parent) {
+spBoneData* spBoneData_create (int index, const char* name, spBoneData* parent) {
 	spBoneData* self = NEW(spBoneData);
 	spBoneData* self = NEW(spBoneData);
+	CONST_CAST(int, self->index) = index;
 	MALLOC_STR(self->name, name);
 	MALLOC_STR(self->name, name);
 	CONST_CAST(spBoneData*, self->parent) = parent;
 	CONST_CAST(spBoneData*, self->parent) = parent;
 	self->scaleX = 1;
 	self->scaleX = 1;
 	self->scaleY = 1;
 	self->scaleY = 1;
-	self->inheritScale = 1;
-	self->inheritRotation = 1;
+	self->transformMode = SP_TRANSFORMMODE_NORMAL;
 	return self;
 	return self;
 }
 }
 
 

+ 54 - 28
engine/source/spine/BoneData.h

@@ -1,51 +1,77 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_BONEDATA_H_
 #ifndef SPINE_BONEDATA_H_
 #define SPINE_BONEDATA_H_
 #define SPINE_BONEDATA_H_
 
 
+#include <spine/dll.h>
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
+typedef enum {
+	SP_TRANSFORMMODE_NORMAL,
+	SP_TRANSFORMMODE_ONLYTRANSLATION,
+	SP_TRANSFORMMODE_NOROTATIONORREFLECTION,
+	SP_TRANSFORMMODE_NOSCALE,
+	SP_TRANSFORMMODE_NOSCALEORREFLECTION
+} spTransformMode;
+
 typedef struct spBoneData spBoneData;
 typedef struct spBoneData spBoneData;
 struct spBoneData {
 struct spBoneData {
+	const int index;
 	const char* const name;
 	const char* const name;
 	spBoneData* const parent;
 	spBoneData* const parent;
 	float length;
 	float length;
-	float x, y;
-	float rotation;
-	float scaleX, scaleY;
-	int/*bool*/inheritScale, inheritRotation;
+	float x, y, rotation, scaleX, scaleY, shearX, shearY;
+	spTransformMode transformMode;
+	int/*bool*/ skinRequired;
+
+#ifdef __cplusplus
+	spBoneData() :
+		index(0),
+		name(0),
+		parent(0),
+		length(0),
+		x(0), y(0),
+		rotation(0),
+		scaleX(0), scaleY(0),
+		shearX(0), shearY(0),
+		transformMode(SP_TRANSFORMMODE_NORMAL),
+		skinRequired(0) {
+	}
+#endif
 };
 };
 
 
-spBoneData* spBoneData_create (const char* name, spBoneData* parent);
-void spBoneData_dispose (spBoneData* self);
+SP_API spBoneData* spBoneData_create (int index, const char* name, spBoneData* parent);
+SP_API void spBoneData_dispose (spBoneData* self);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spBoneData BoneData;
 typedef spBoneData BoneData;

+ 33 - 40
engine/source/spine/BoundingBoxAttachment.c

@@ -1,29 +1,30 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include <spine/BoundingBoxAttachment.h>
 #include <spine/BoundingBoxAttachment.h>
@@ -32,29 +33,21 @@
 void _spBoundingBoxAttachment_dispose (spAttachment* attachment) {
 void _spBoundingBoxAttachment_dispose (spAttachment* attachment) {
 	spBoundingBoxAttachment* self = SUB_CAST(spBoundingBoxAttachment, attachment);
 	spBoundingBoxAttachment* self = SUB_CAST(spBoundingBoxAttachment, attachment);
 
 
-	_spAttachment_deinit(attachment);
+	_spVertexAttachment_deinit(SUPER(self));
 
 
-	FREE(self->vertices);
 	FREE(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* spBoundingBoxAttachment_create (const char* name) {
 	spBoundingBoxAttachment* self = NEW(spBoundingBoxAttachment);
 	spBoundingBoxAttachment* self = NEW(spBoundingBoxAttachment);
-	_spAttachment_init(SUPER(self), name, ATTACHMENT_BOUNDING_BOX, _spBoundingBoxAttachment_dispose);
+	_spVertexAttachment_init(SUPER(self));
+	_spAttachment_init(SUPER(SUPER(self)), name, SP_ATTACHMENT_BOUNDING_BOX, _spBoundingBoxAttachment_dispose, _spBoundingBoxAttachment_copy);
 	return self;
 	return self;
 }
 }
-
-void spBoundingBoxAttachment_computeWorldVertices (spBoundingBoxAttachment* self, float x, float y, spBone* bone, float* worldVertices) {
-	int i;
-	float px, py;
-	float* vertices = self->vertices;
-
-	x += bone->worldX;
-	y += bone->worldY;
-	for (i = 0; i < self->verticesCount; i += 2) {
-		px = vertices[i];
-		py = vertices[i + 1];
-		worldVertices[i] = px * bone->m00 + py * bone->m01 + x;
-		worldVertices[i + 1] = px * bone->m10 + py * bone->m11 + y;
-	}
-}

+ 29 - 31
engine/source/spine/BoundingBoxAttachment.h

@@ -1,35 +1,38 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_BOUNDINGBOXATTACHMENT_H_
 #ifndef SPINE_BOUNDINGBOXATTACHMENT_H_
 #define SPINE_BOUNDINGBOXATTACHMENT_H_
 #define SPINE_BOUNDINGBOXATTACHMENT_H_
 
 
+#include <spine/dll.h>
 #include <spine/Attachment.h>
 #include <spine/Attachment.h>
+#include <spine/VertexAttachment.h>
 #include <spine/Atlas.h>
 #include <spine/Atlas.h>
 #include <spine/Slot.h>
 #include <spine/Slot.h>
 
 
@@ -37,20 +40,15 @@
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-typedef struct spBoundingBoxAttachment spBoundingBoxAttachment;
-struct spBoundingBoxAttachment {
-	spAttachment super;
-	int verticesCount;
-	float* vertices;
-};
+typedef struct spBoundingBoxAttachment {
+	spVertexAttachment super;
+} spBoundingBoxAttachment;
 
 
-spBoundingBoxAttachment* spBoundingBoxAttachment_create (const char* name);
-void spBoundingBoxAttachment_computeWorldVertices (spBoundingBoxAttachment* self, float x, float y, spBone* bone, float* vertices);
+SP_API spBoundingBoxAttachment* spBoundingBoxAttachment_create (const char* name);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spBoundingBoxAttachment BoundingBoxAttachment;
 typedef spBoundingBoxAttachment BoundingBoxAttachment;
 #define BoundingBoxAttachment_create(...) spBoundingBoxAttachment_create(__VA_ARGS__)
 #define BoundingBoxAttachment_create(...) spBoundingBoxAttachment_create(__VA_ARGS__)
-#define BoundingBoxAttachment_computeWorldVertices(...) spBoundingBoxAttachment_computeWorldVertices(__VA_ARGS__)
 #endif
 #endif
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus

+ 55 - 0
engine/source/spine/ClippingAttachment.c

@@ -0,0 +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/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;
+}

+ 60 - 0
engine/source/spine/ClippingAttachment.h

@@ -0,0 +1,60 @@
+/******************************************************************************
+ * 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_CLIPPINGATTACHMENT_H_
+#define SPINE_CLIPPINGATTACHMENT_H_
+
+#include <spine/dll.h>
+#include <spine/Attachment.h>
+#include <spine/VertexAttachment.h>
+#include <spine/Atlas.h>
+#include <spine/Slot.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct spClippingAttachment {
+	spVertexAttachment super;
+	spSlotData* endSlot;
+} spClippingAttachment;
+
+SP_API void _spClippingAttachment_dispose(spAttachment* self);
+SP_API spClippingAttachment* spClippingAttachment_create (const char* name);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spClippingAttachment ClippingAttachment;
+#define ClippingAttachment_create(...) spClippingAttachment_create(__VA_ARGS__)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPINE_CLIPPINGATTACHMENT_H_ */

+ 84 - 0
engine/source/spine/Color.c

@@ -0,0 +1,84 @@
+/******************************************************************************
+ * 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/Color.h>
+#include <spine/extension.h>
+
+spColor* spColor_create() {
+	return MALLOC(spColor, 1);
+}
+
+void spColor_dispose(spColor* self) {
+	if (self) FREE(self);
+}
+
+void spColor_setFromFloats(spColor* self, float r, float g, float b, float a) {
+	self->r = r;
+	self->g = g;
+	self->b = b;
+	self->a = a;
+	spColor_clamp(self);
+}
+
+void spColor_setFromColor(spColor* self, spColor* otherColor) {
+	self->r = otherColor->r;
+	self->g = otherColor->g;
+	self->b = otherColor->b;
+	self->a = otherColor->a;
+}
+
+void spColor_addColor(spColor* self, spColor* otherColor) {
+	self->r += otherColor->r;
+	self->g += otherColor->g;
+	self->b += otherColor->b;
+	self->a += otherColor->a;
+	spColor_clamp(self);
+}
+
+void spColor_addFloats(spColor* self, float r, float g, float b, float a) {
+	self->r += r;
+	self->g += g;
+	self->b += b;
+	self->a += a;
+	spColor_clamp(self);
+}
+
+void spColor_clamp(spColor* self) {
+	if (self->r < 0) self->r = 0;
+	else if (self->r > 1) self->r = 1;
+
+	if (self->g < 0) self->g = 0;
+	else if (self->g > 1) self->g = 1;
+
+	if (self->b < 0) self->b = 0;
+	else if (self->b > 1) self->b = 1;
+
+	if (self->a < 0) self->a = 0;
+	else if (self->a > 1) self->a = 1;
+}

+ 77 - 0
engine/source/spine/Color.h

@@ -0,0 +1,77 @@
+/******************************************************************************
+ * 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_COLOR_H_
+#define SPINE_COLOR_H_
+
+#include <spine/dll.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct spColor {
+	float r, g, b, a;
+
+#ifdef __cplusplus
+	spColor() :
+		r(0), g(0), b(0), a(0) {
+	}
+
+	bool operator==(const spColor& rhs) {
+		return r == rhs.r && g == rhs.g && b == rhs.b && a == rhs.a;
+	}
+#endif
+} spColor;
+
+/* @param attachmentName May be 0 for no setup pose attachment. */
+SP_API spColor* spColor_create();
+SP_API void spColor_dispose(spColor* self);
+SP_API void spColor_setFromFloats(spColor* color, float r, float g, float b, float a);
+SP_API void spColor_setFromColor(spColor* color, spColor* otherColor);
+SP_API void spColor_addFloats(spColor* color, float r, float g, float b, float a);
+SP_API void spColor_addColor(spColor* color, spColor* otherColor);
+SP_API void spColor_clamp(spColor* color);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spColor color;
+#define Color_create() spColor_create()
+#define Color_dispose(...) spColor_dispose(__VA_ARGS__)
+#define Color_setFromFloats(...) spColor_setFromFloats(__VA_ARGS__)
+#define Color_setFromColor(...) spColor_setFromColor(__VA_ARGS__)
+#define Color_addColor(...) spColor_addColor(__VA_ARGS__)
+#define Color_addFloats(...) spColor_addFloats(__VA_ARGS__)
+#define Color_clamp(...) spColor_clamp(__VA_ARGS__)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPINE_COLOR_H_ */

+ 25 - 23
engine/source/spine/Event.c

@@ -1,37 +1,39 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include <spine/Event.h>
 #include <spine/Event.h>
 #include <spine/extension.h>
 #include <spine/extension.h>
 
 
-spEvent* spEvent_create (spEventData* data) {
+spEvent* spEvent_create (float time, spEventData* data) {
 	spEvent* self = NEW(spEvent);
 	spEvent* self = NEW(spEvent);
 	CONST_CAST(spEventData*, self->data) = data;
 	CONST_CAST(spEventData*, self->data) = data;
+	CONST_CAST(float, self->time) = time;
 	return self;
 	return self;
 }
 }
 
 

+ 43 - 27
engine/source/spine/Event.h

@@ -1,50 +1,66 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_EVENT_H_
 #ifndef SPINE_EVENT_H_
 #define SPINE_EVENT_H_
 #define SPINE_EVENT_H_
 
 
+#include <spine/dll.h>
 #include <spine/EventData.h>
 #include <spine/EventData.h>
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-typedef struct spEvent spEvent;
-struct spEvent {
+typedef struct spEvent {
 	spEventData* const data;
 	spEventData* const data;
+	float const time;
 	int intValue;
 	int intValue;
 	float floatValue;
 	float floatValue;
 	const char* stringValue;
 	const char* stringValue;
-};
+	float volume;
+	float balance;
 
 
-spEvent* spEvent_create (spEventData* data);
-void spEvent_dispose (spEvent* self);
+#ifdef __cplusplus
+	spEvent() :
+		data(0),
+		time(0),
+		intValue(0),
+		floatValue(0),
+		stringValue(0),
+		volume(0),
+		balance(0) {
+	}
+#endif
+} spEvent;
+
+SP_API spEvent* spEvent_create (float time, spEventData* data);
+SP_API void spEvent_dispose (spEvent* self);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spEvent Event;
 typedef spEvent Event;

+ 24 - 22
engine/source/spine/EventData.c

@@ -1,29 +1,30 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include <spine/EventData.h>
 #include <spine/EventData.h>
@@ -36,6 +37,7 @@ spEventData* spEventData_create (const char* name) {
 }
 }
 
 
 void spEventData_dispose (spEventData* self) {
 void spEventData_dispose (spEventData* self) {
+	FREE(self->audioPath);
 	FREE(self->stringValue);
 	FREE(self->stringValue);
 	FREE(self->name);
 	FREE(self->name);
 	FREE(self);
 	FREE(self);

+ 44 - 27
engine/source/spine/EventData.h

@@ -1,48 +1,65 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_EVENTDATA_H_
 #ifndef SPINE_EVENTDATA_H_
 #define SPINE_EVENTDATA_H_
 #define SPINE_EVENTDATA_H_
 
 
+#include <spine/dll.h>
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-typedef struct spEventData spEventData;
-struct spEventData {
+typedef struct spEventData {
 	const char* const name;
 	const char* const name;
 	int intValue;
 	int intValue;
 	float floatValue;
 	float floatValue;
 	const char* stringValue;
 	const char* stringValue;
-};
+	const char* audioPath;
+	float volume;
+	float balance;
+
+#ifdef __cplusplus
+	spEventData() :
+		name(0),
+		intValue(0),
+		floatValue(0),
+		stringValue(0),
+		audioPath(0),
+		volume(0),
+		balance(0) {
+	}
+#endif
+} spEventData;
 
 
-spEventData* spEventData_create (const char* name);
-void spEventData_dispose (spEventData* self);
+SP_API spEventData* spEventData_create (const char* name);
+SP_API void spEventData_dispose (spEventData* self);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spEventData EventData;
 typedef spEventData EventData;

+ 276 - 0
engine/source/spine/IkConstraint.c

@@ -0,0 +1,276 @@
+/******************************************************************************
+ * 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_apply(spIkConstraint *self) {
+	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->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;
+	if (!bone->appliedValid) spBone_updateAppliedTransform(bone);
+
+    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, float softness, float alpha) {
+	float a, b, c, d;
+	float px, py, psx, sx, psy;
+	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;
+	if (alpha == 0) {
+		spBone_updateWorldTransform(child);
+		return;
+	}
+	if (!parent->appliedValid) spBone_updateAppliedTransform(parent);
+	if (!child->appliedValid) spBone_updateAppliedTransform(child);
+	px = parent->ax; py = parent->ay; psx = parent->ascaleX; sx = psx; psy = parent->ascaleY; 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) {
+		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) / 2;
+		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;
+		else if (cosine > 1) {
+			cosine = 1;
+			if (stretch) sx *= (SQRT(dd) / (l1 + l2) - 1) * alpha + 1;
+		}
+		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) / 2;
+			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) / 2) {
+				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;
+		a1 = (a1 - os) * RAD_DEG + o1 - parent->arotation;
+		if (a1 > 180) a1 -= 360;
+		else if (a1 < -180) a1 += 360;
+		spBone_updateWorldTransformWith(parent, px, py, parent->rotation + a1 * alpha, sx, parent->ascaleY, 0, 0);
+		a2 = ((a2 + os) * RAD_DEG - child->ashearX) * s2 + o2 - child->arotation;
+		if (a2 > 180) a2 -= 360;
+		else if (a2 < -180) a2 += 360;
+		spBone_updateWorldTransformWith(child, cx, cy, child->arotation + a2 * alpha, child->ascaleX, child->ascaleY, child->ashearX, child->ashearY);
+	}
+}

+ 94 - 0
engine/source/spine/IkConstraint.h

@@ -0,0 +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.
+ *****************************************************************************/
+
+#ifndef SPINE_IKCONSTRAINT_H_
+#define SPINE_IKCONSTRAINT_H_
+
+#include <spine/dll.h>
+#include <spine/IkConstraintData.h>
+#include <spine/Bone.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct spSkeleton;
+
+typedef struct spIkConstraint {
+	spIkConstraintData* const data;
+
+	int bonesCount;
+	spBone** bones;
+
+	spBone* target;
+	int bendDirection;
+	int /*boolean*/ compress;
+	int /*boolean*/ stretch;
+	float mix;
+	float softness;
+
+	int /*boolean*/ active;
+
+#ifdef __cplusplus
+	spIkConstraint() :
+		data(0),
+		bonesCount(0),
+		bones(0),
+		target(0),
+		bendDirection(0),
+		stretch(0),
+		mix(0),
+		softness(0),
+		active(0) {
+	}
+#endif
+} spIkConstraint;
+
+SP_API spIkConstraint* spIkConstraint_create (spIkConstraintData* data, const struct spSkeleton* skeleton);
+SP_API void spIkConstraint_dispose (spIkConstraint* self);
+
+SP_API void spIkConstraint_apply (spIkConstraint* self);
+
+SP_API void spIkConstraint_apply1 (spBone* bone, float targetX, float targetY, int /*boolean*/ compress, int /*boolean*/ stretch, int /*boolean*/ uniform, float alpha);
+SP_API void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float targetY, int bendDirection, int /*boolean*/ stretch, float softness, float alpha);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spIkConstraint IkConstraint;
+#define IkConstraint_create(...) spIkConstraint_create(__VA_ARGS__)
+#define IkConstraint_dispose(...) spIkConstraint_dispose(__VA_ARGS__)
+#define IkConstraint_apply(...) spIkConstraint_apply(__VA_ARGS__)
+#define IkConstraint_apply1(...) spIkConstraint_apply1(__VA_ARGS__)
+#define IkConstraint_apply2(...) spIkConstraint_apply2(__VA_ARGS__)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPINE_IKCONSTRAINT_H_ */

+ 48 - 0
engine/source/spine/IkConstraintData.c

@@ -0,0 +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);
+}

+ 86 - 0
engine/source/spine/IkConstraintData.h

@@ -0,0 +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.
+ *****************************************************************************/
+
+#ifndef SPINE_IKCONSTRAINTDATA_H_
+#define SPINE_IKCONSTRAINTDATA_H_
+
+#include <spine/dll.h>
+#include <spine/BoneData.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct spIkConstraintData {
+	const char* const name;
+	int order;
+	int /*boolean*/ skinRequired;
+	int bonesCount;
+	spBoneData** bones;
+
+	spBoneData* target;
+	int bendDirection;
+	int /*boolean*/ compress;
+	int /*boolean*/ stretch;
+	int /*boolean*/ uniform;
+	float mix;
+	float softness;
+
+#ifdef __cplusplus
+	spIkConstraintData() :
+		name(0),
+		order(0),
+		skinRequired(0),
+		bonesCount(0),
+		bones(0),
+		target(0),
+		bendDirection(0),
+		compress(0),
+		stretch(0),
+		uniform(0),
+		mix(0),
+		softness(0) {
+	}
+#endif
+} spIkConstraintData;
+
+SP_API spIkConstraintData* spIkConstraintData_create (const char* name);
+SP_API void spIkConstraintData_dispose (spIkConstraintData* self);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spIkConstraintData IkConstraintData;
+#define IkConstraintData_create(...) spIkConstraintData_create(__VA_ARGS__)
+#define IkConstraintData_dispose(...) spIkConstraintData_dispose(__VA_ARGS__)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPINE_IKCONSTRAINTDATA_H_ */

+ 187 - 84
engine/source/spine/Json.c

@@ -1,33 +1,51 @@
 /*
 /*
- Copyright (c) 2009 Dave Gamble
-
- Permission is hereby granted, dispose of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- */
+Copyright (c) 2009, Dave Gamble
+Copyright (c) 2013, Esoteric Software
+
+Permission is hereby granted, dispose of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
 
 
 /* Json */
 /* Json */
 /* JSON parser in C. */
 /* JSON parser in C. */
 
 
+#ifndef _DEFAULT_SOURCE
+/* Bring strings.h definitions into string.h, where appropriate */
+#define _DEFAULT_SOURCE
+#endif
+
+#ifndef _BSD_SOURCE
+/* Bring strings.h definitions into string.h, where appropriate */
+#define _BSD_SOURCE
+#endif
+
 #include "Json.h"
 #include "Json.h"
 #include <stdio.h>
 #include <stdio.h>
 #include <ctype.h>
 #include <ctype.h>
+#include <stdlib.h> /* strtod (C89), strtof (C99) */
+#include <string.h> /* strcasecmp (4.4BSD - compatibility), _stricmp (_WIN32) */
 #include <spine/extension.h>
 #include <spine/extension.h>
 
 
+#ifndef SPINE_JSON_DEBUG
+/* Define this to do extra NULL and expected-character checking */
+#define SPINE_JSON_DEBUG 0
+#endif
+
 static const char* ep;
 static const char* ep;
 
 
 const char* Json_getError (void) {
 const char* Json_getError (void) {
@@ -35,11 +53,22 @@ const char* Json_getError (void) {
 }
 }
 
 
 static int Json_strcasecmp (const char* s1, const char* s2) {
 static int Json_strcasecmp (const char* s1, const char* s2) {
-	if (!s1) return (s1 == s2) ? 0 : 1;
-	if (!s2) return 1;
-	for (; tolower(*s1) == tolower(*s2); ++s1, ++s2)
-		if (*s1 == 0) return 0;
-	return tolower(*(const unsigned char*)s1) - tolower(*(const unsigned char*)s2);
+	/* TODO we may be able to elide these NULL checks if we can prove
+	the graph and input (only callsite is Json_getItem) should not have NULLs */
+	if (s1 && s2) {
+#if defined(_WIN32)
+		return _stricmp(s1, s2);
+#else
+		return strcasecmp( s1, s2 );
+#endif
+	} else {
+		if (s1 < s2)
+			return -1; /* s1 is null, s2 is not */
+		else if (s1 == s2)
+			return 0; /* both are null */
+		else
+			return 1; /* s2 is nul	s1 is not */
+	}
 }
 }
 
 
 /* Internal constructor. */
 /* Internal constructor. */
@@ -62,37 +91,70 @@ void Json_dispose (Json *c) {
 
 
 /* Parse the input text to generate a number, and populate the result into item. */
 /* Parse the input text to generate a number, and populate the result into item. */
 static const char* parse_number (Json *item, const char* num) {
 static const char* parse_number (Json *item, const char* num) {
-	float n = 0, sign = 1, scale = 0;
-	int subscale = 0, signsubscale = 1;
-
-	/* Could use sscanf for this? */
-	if (*num == '-') sign = -1, num++; /* Has sign? */
-	if (*num == '0') num++; /* is zero */
-	if (*num >= '1' && *num <= '9') do
-		n = (n * 10.0f) + (*num++ - '0');
-	while (*num >= '0' && *num <= '9'); /* Number? */
-	if (*num == '.' && num[1] >= '0' && num[1] <= '9') {
-		num++;
-		do
-			n = (n * 10.0f) + (*num++ - '0'), scale--;
-		while (*num >= '0' && *num <= '9');
-	} /* Fractional part? */
-	if (*num == 'e' || *num == 'E') /* Exponent? */
-	{
-		num++;
-		if (*num == '+')
-			num++;
-		else if (*num == '-') signsubscale = -1, num++; /* With sign? */
-		while (*num >= '0' && *num <= '9')
-			subscale = (subscale * 10) + (*num++ - '0'); /* Number? */
+	double result = 0.0;
+	int negative = 0;
+	char* ptr = (char*)num;
+
+	if (*ptr == '-') {
+		negative = -1;
+		++ptr;
+	}
+
+	while (*ptr >= '0' && *ptr <= '9') {
+		result = result * 10.0 + (*ptr - '0');
+		++ptr;
 	}
 	}
 
 
-	n = sign * n * (float)pow(10.0f, (scale + subscale * signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */
+	if (*ptr == '.') {
+		double fraction = 0.0;
+		int n = 0;
+		++ptr;
+
+		while (*ptr >= '0' && *ptr <= '9') {
+			fraction = (fraction * 10.0) + (*ptr - '0');
+			++ptr;
+			++n;
+		}
+		result += fraction / POW(10.0, n);
+	}
+	if (negative) result = -result;
+
+	if (*ptr == 'e' || *ptr == 'E') {
+		double exponent = 0;
+		int expNegative = 0;
+		int n = 0;
+		++ptr;
+
+		if (*ptr == '-') {
+			expNegative = -1;
+			++ptr;
+		} else if (*ptr == '+') {
+			++ptr;
+		}
+
+		while (*ptr >= '0' && *ptr <= '9') {
+			exponent = (exponent * 10.0) + (*ptr - '0');
+			++ptr;
+			++n;
+		}
+
+		if (expNegative)
+			result = result / POW(10, exponent);
+		else
+			result = result * POW(10, exponent);
+	}
 
 
-	item->valueFloat = n;
-	item->valueInt = (int)n;
-	item->type = Json_Number;
-	return num;
+	if (ptr != num) {
+		/* Parse success, number found. */
+		item->valueFloat = (float)result;
+		item->valueInt = (int)result;
+		item->type = Json_Number;
+		return ptr;
+	} else {
+		/* Parse failure, ep is set. */
+		ep = num;
+		return 0;
+	}
 }
 }
 
 
 /* Parse the input text into an unescaped cstring, and populate item. */
 /* Parse the input text into an unescaped cstring, and populate item. */
@@ -103,7 +165,7 @@ static const char* parse_string (Json *item, const char* str) {
 	char* out;
 	char* out;
 	int len = 0;
 	int len = 0;
 	unsigned uc, uc2;
 	unsigned uc, uc2;
-	if (*str != '\"') {
+	if (*str != '\"') { /* TODO: don't need this check when called from parse_value, but do need from parse_object */
 		ep = str;
 		ep = str;
 		return 0;
 		return 0;
 	} /* not a string! */
 	} /* not a string! */
@@ -111,7 +173,7 @@ static const char* parse_string (Json *item, const char* str) {
 	while (*ptr != '\"' && *ptr && ++len)
 	while (*ptr != '\"' && *ptr && ++len)
 		if (*ptr++ == '\\') ptr++; /* Skip escaped quotes. */
 		if (*ptr++ == '\\') ptr++; /* Skip escaped quotes. */
 
 
-	out = (char*)malloc(len + 1); /* This is how long we need for the string, roughly. */
+	out = MALLOC(char, len + 1); /* The length needed for the string, roughly. */
 	if (!out) return 0;
 	if (!out) return 0;
 
 
 	ptr = str + 1;
 	ptr = str + 1;
@@ -143,6 +205,7 @@ static const char* parse_string (Json *item, const char* str) {
 
 
 				if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) break; /* check for invalid.	*/
 				if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) break; /* check for invalid.	*/
 
 
+				/* TODO provide an option to ignore surrogates, use unicode replacement character? */
 				if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs.	*/
 				if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs.	*/
 				{
 				{
 					if (ptr[1] != '\\' || ptr[2] != 'u') break; /* missing second-half of surrogate.	*/
 					if (ptr[1] != '\\' || ptr[2] != 'u') break; /* missing second-half of surrogate.	*/
@@ -164,12 +227,15 @@ static const char* parse_string (Json *item, const char* str) {
 				case 4:
 				case 4:
 					*--ptr2 = ((uc | 0x80) & 0xBF);
 					*--ptr2 = ((uc | 0x80) & 0xBF);
 					uc >>= 6;
 					uc >>= 6;
+					/* fallthrough */
 				case 3:
 				case 3:
 					*--ptr2 = ((uc | 0x80) & 0xBF);
 					*--ptr2 = ((uc | 0x80) & 0xBF);
 					uc >>= 6;
 					uc >>= 6;
+					/* fallthrough */
 				case 2:
 				case 2:
 					*--ptr2 = ((uc | 0x80) & 0xBF);
 					*--ptr2 = ((uc | 0x80) & 0xBF);
 					uc >>= 6;
 					uc >>= 6;
+					/* fallthrough */
 				case 1:
 				case 1:
 					*--ptr2 = (uc | firstByteMark[len]);
 					*--ptr2 = (uc | firstByteMark[len]);
 				}
 				}
@@ -183,7 +249,7 @@ static const char* parse_string (Json *item, const char* str) {
 		}
 		}
 	}
 	}
 	*ptr2 = 0;
 	*ptr2 = 0;
-	if (*ptr == '\"') ptr++;
+	if (*ptr == '\"') ptr++; /* TODO error handling if not \" or \0 ? */
 	item->valueString = out;
 	item->valueString = out;
 	item->type = Json_String;
 	item->type = Json_String;
 	return ptr;
 	return ptr;
@@ -196,20 +262,22 @@ static const char* parse_object (Json *item, const char* value);
 
 
 /* Utility to jump whitespace and cr/lf */
 /* Utility to jump whitespace and cr/lf */
 static const char* skip (const char* in) {
 static const char* skip (const char* in) {
-	while (in && *in && (unsigned char)*in <= 32)
+	if (!in) return 0; /* must propagate NULL since it's often called in skip(f(...)) form */
+	while (*in && (unsigned char)*in <= 32)
 		in++;
 		in++;
 	return in;
 	return in;
 }
 }
 
 
 /* Parse an object - create a new root, and populate. */
 /* Parse an object - create a new root, and populate. */
 Json *Json_create (const char* value) {
 Json *Json_create (const char* value) {
-	const char* end = 0;
-	Json *c = Json_new();
+	Json *c;
 	ep = 0;
 	ep = 0;
+	if (!value) return 0; /* only place we check for NULL other than skip() */
+	c = Json_new();
 	if (!c) return 0; /* memory fail */
 	if (!c) return 0; /* memory fail */
 
 
-	end = parse_value(c, skip(value));
-	if (!end) {
+	value = parse_value(c, skip(value));
+	if (!value) {
 		Json_dispose(c);
 		Json_dispose(c);
 		return 0;
 		return 0;
 	} /* parse failure. ep is set. */
 	} /* parse failure. ep is set. */
@@ -219,31 +287,56 @@ Json *Json_create (const char* value) {
 
 
 /* Parser core - when encountering text, process appropriately. */
 /* Parser core - when encountering text, process appropriately. */
 static const char* parse_value (Json *item, 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 (!value) return 0; /* Fail on null. */
 	if (!value) return 0; /* Fail on null. */
-	if (!strncmp(value, "null", 4)) {
-		item->type = Json_NULL;
-		return value + 4;
+#endif
+
+	switch (*value) {
+	case 'n': {
+		if (!strncmp(value + 1, "ull", 3)) {
+			item->type = Json_NULL;
+			return value + 4;
+		}
+		break;
 	}
 	}
-	if (!strncmp(value, "false", 5)) {
-		item->type = Json_False;
-		return value + 5;
+	case 'f': {
+		if (!strncmp(value + 1, "alse", 4)) {
+			item->type = Json_False;
+			/* calloc prevents us needing item->type = Json_False or valueInt = 0 here */
+			return value + 5;
+		}
+		break;
 	}
 	}
-	if (!strncmp(value, "true", 4)) {
-		item->type = Json_True;
-		item->valueInt = 1;
-		return value + 4;
+	case 't': {
+		if (!strncmp(value + 1, "rue", 3)) {
+			item->type = Json_True;
+			item->valueInt = 1;
+			return value + 4;
+		}
+		break;
 	}
 	}
-	if (*value == '\"') {
+	case '\"':
 		return parse_string(item, value);
 		return parse_string(item, value);
-	}
-	if (*value == '-' || (*value >= '0' && *value <= '9')) {
-		return parse_number(item, value);
-	}
-	if (*value == '[') {
+	case '[':
 		return parse_array(item, value);
 		return parse_array(item, value);
-	}
-	if (*value == '{') {
+	case '{':
 		return parse_object(item, value);
 		return parse_object(item, value);
+	case '-': /* fallthrough */
+	case '0': /* fallthrough */
+	case '1': /* fallthrough */
+	case '2': /* fallthrough */
+	case '3': /* fallthrough */
+	case '4': /* fallthrough */
+	case '5': /* fallthrough */
+	case '6': /* fallthrough */
+	case '7': /* fallthrough */
+	case '8': /* fallthrough */
+	case '9':
+		return parse_number(item, value);
+	default:
+		break;
 	}
 	}
 
 
 	ep = value;
 	ep = value;
@@ -253,10 +346,13 @@ static const char* parse_value (Json *item, const char* value) {
 /* Build an array from input text. */
 /* Build an array from input text. */
 static const char* parse_array (Json *item, const char* value) {
 static const char* parse_array (Json *item, const char* value) {
 	Json *child;
 	Json *child;
+
+#if SPINE_JSON_DEBUG /* unnecessary, only callsite (parse_value) verifies this */
 	if (*value != '[') {
 	if (*value != '[') {
 		ep = value;
 		ep = value;
 		return 0;
 		return 0;
 	} /* not an array! */
 	} /* not an array! */
+#endif
 
 
 	item->type = Json_Array;
 	item->type = Json_Array;
 	value = skip(value + 1);
 	value = skip(value + 1);
@@ -269,13 +365,15 @@ static const char* parse_array (Json *item, const char* value) {
 	item->size = 1;
 	item->size = 1;
 
 
 	while (*value == ',') {
 	while (*value == ',') {
-		Json *new_item;
-		if (!(new_item = Json_new())) return 0; /* memory fail */
+		Json *new_item = Json_new();
+		if (!new_item) return 0; /* memory fail */
 		child->next = new_item;
 		child->next = new_item;
+#if SPINE_JSON_HAVE_PREV
 		new_item->prev = child;
 		new_item->prev = child;
+#endif
 		child = new_item;
 		child = new_item;
 		value = skip(parse_value(child, skip(value + 1)));
 		value = skip(parse_value(child, skip(value + 1)));
-		if (!value) return 0; /* memory fail */
+		if (!value) return 0; /* parse fail */
 		item->size++;
 		item->size++;
 	}
 	}
 
 
@@ -287,10 +385,13 @@ static const char* parse_array (Json *item, const char* value) {
 /* Build an object from the text. */
 /* Build an object from the text. */
 static const char* parse_object (Json *item, const char* value) {
 static const char* parse_object (Json *item, const char* value) {
 	Json *child;
 	Json *child;
+
+#if SPINE_JSON_DEBUG /* unnecessary, only callsite (parse_value) verifies this */
 	if (*value != '{') {
 	if (*value != '{') {
 		ep = value;
 		ep = value;
 		return 0;
 		return 0;
 	} /* not an object! */
 	} /* not an object! */
+#endif
 
 
 	item->type = Json_Object;
 	item->type = Json_Object;
 	value = skip(value + 1);
 	value = skip(value + 1);
@@ -311,10 +412,12 @@ static const char* parse_object (Json *item, const char* value) {
 	item->size = 1;
 	item->size = 1;
 
 
 	while (*value == ',') {
 	while (*value == ',') {
-		Json *new_item;
-		if (!(new_item = Json_new())) return 0; /* memory fail */
+		Json *new_item = Json_new();
+		if (!new_item) return 0; /* memory fail */
 		child->next = new_item;
 		child->next = new_item;
+#if SPINE_JSON_HAVE_PREV
 		new_item->prev = child;
 		new_item->prev = child;
+#endif
 		child = new_item;
 		child = new_item;
 		value = skip(parse_string(child, skip(value + 1)));
 		value = skip(parse_string(child, skip(value + 1)));
 		if (!value) return 0;
 		if (!value) return 0;

+ 10 - 3
engine/source/spine/Json.h

@@ -1,16 +1,16 @@
 /*
 /*
  Copyright (c) 2009 Dave Gamble
  Copyright (c) 2009 Dave Gamble
- 
+
  Permission is hereby granted, dispose of charge, to any person obtaining a copy
  Permission is hereby granted, dispose of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:
  furnished to do so, subject to the following conditions:
- 
+
  The above copyright notice and this permission notice shall be included in
  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.
  all copies or substantial portions of the Software.
- 
+
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -38,10 +38,17 @@ extern "C" {
 #define Json_Array 5
 #define Json_Array 5
 #define Json_Object 6
 #define Json_Object 6
 
 
+#ifndef SPINE_JSON_HAVE_PREV
+/* Spine doesn't use the "prev" link in the Json sibling lists. */
+#define SPINE_JSON_HAVE_PREV 0
+#endif
+
 /* The Json structure: */
 /* The Json structure: */
 typedef struct Json {
 typedef struct Json {
 	struct Json* next;
 	struct Json* next;
+#if SPINE_JSON_HAVE_PREV
 	struct Json* prev; /* next/prev allow you to walk array/object chains. Alternatively, use getSize/getItem */
 	struct Json* prev; /* next/prev allow you to walk array/object chains. Alternatively, use getSize/getItem */
+#endif
 	struct Json* child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
 	struct Json* child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
 
 
 	int type; /* The type of the item, as above. */
 	int type; /* The type of the item, as above. */

+ 211 - 0
engine/source/spine/MeshAttachment.c

@@ -0,0 +1,211 @@
+/******************************************************************************
+ * 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->regionRotate = self->regionRotate;
+	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->regionRotate = self->regionRotate;
+	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;
+	}
+}

+ 91 - 0
engine/source/spine/MeshAttachment.h

@@ -0,0 +1,91 @@
+/******************************************************************************
+ * 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_MESHATTACHMENT_H_
+#define SPINE_MESHATTACHMENT_H_
+
+#include <spine/dll.h>
+#include <spine/Attachment.h>
+#include <spine/VertexAttachment.h>
+#include <spine/Atlas.h>
+#include <spine/Slot.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct spMeshAttachment spMeshAttachment;
+struct spMeshAttachment {
+	spVertexAttachment super;
+
+	void* rendererObject;
+	int regionOffsetX, regionOffsetY; /* Pixels stripped from the bottom left, unrotated. */
+	int regionWidth, regionHeight; /* Unrotated, stripped pixel size. */
+	int regionOriginalWidth, regionOriginalHeight; /* Unrotated, unstripped pixel size. */
+	float regionU, regionV, regionU2, regionV2;
+	int/*bool*/regionRotate;
+	int regionDegrees;
+
+	const char* path;
+
+	float* regionUVs;
+	float* uvs;
+
+	int trianglesCount;
+	unsigned short* triangles;
+
+	spColor color;
+
+	int hullLength;
+
+	spMeshAttachment* const parentMesh;
+
+	/* Nonessential. */
+	int edgesCount;
+	int* edges;
+	float width, height;
+};
+
+SP_API spMeshAttachment* spMeshAttachment_create (const char* name);
+SP_API void spMeshAttachment_updateUVs (spMeshAttachment* self);
+SP_API void spMeshAttachment_setParentMesh (spMeshAttachment* self, spMeshAttachment* parentMesh);
+SP_API spMeshAttachment* spMeshAttachment_newLinkedMesh (spMeshAttachment* self);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spMeshAttachment MeshAttachment;
+#define MeshAttachment_create(...) spMeshAttachment_create(__VA_ARGS__)
+#define MeshAttachment_updateUVs(...) spMeshAttachment_updateUVs(__VA_ARGS__)
+#define MeshAttachment_setParentMesh(...) spMeshAttachment_setParentMesh(__VA_ARGS__)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPINE_MESHATTACHMENT_H_ */

+ 59 - 0
engine/source/spine/PathAttachment.c

@@ -0,0 +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;
+}

+ 62 - 0
engine/source/spine/PathAttachment.h

@@ -0,0 +1,62 @@
+/******************************************************************************
+ * 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_PATHATTACHMENT_H_
+#define SPINE_PATHATTACHMENT_H_
+
+#include <spine/dll.h>
+#include <spine/Attachment.h>
+#include <spine/VertexAttachment.h>
+#include <spine/Atlas.h>
+#include <spine/Slot.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct spPathAttachment {
+	spVertexAttachment super;
+	int lengthsLength;
+	float* lengths;
+	int/*bool*/ closed, constantSpeed;
+} spPathAttachment;
+
+SP_API spPathAttachment* spPathAttachment_create (const char* name);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spPathAttachment PathAttachment;
+#define PathAttachment_create(...) spPathAttachment_create(__VA_ARGS__)
+#define PathAttachment_computeWorldVertices(...) spPathAttachment_computeWorldVertices(__VA_ARGS__)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPINE_PATHATTACHMENT_H_ */

+ 492 - 0
engine/source/spine/PathConstraint.c

@@ -0,0 +1,492 @@
+/******************************************************************************
+ * 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->rotateMix = data->rotateMix;
+	self->translateMix = data->translateMix;
+	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_apply (spPathConstraint* self) {
+	int i, p, n;
+	float length, setupLength, x, y, dx, dy, s;
+	float* spaces, *lengths, *positions;
+	float spacing;
+	float boneX, boneY, offsetRotation;
+	int/*bool*/tip;
+	float rotateMix = self->rotateMix, translateMix = self->translateMix;
+	int/*bool*/ translate = translateMix > 0, rotate = rotateMix > 0;
+	int lengthSpacing;
+	spPathAttachment* attachment = (spPathAttachment*)self->target->attachment;
+	spPathConstraintData* data = self->data;
+	int percentSpacing = data->spacingMode == SP_SPACING_MODE_PERCENT;
+	spRotateMode rotateMode = data->rotateMode;
+	int tangents = rotateMode == SP_ROTATE_MODE_TANGENT, scale = rotateMode == SP_ROTATE_MODE_CHAIN_SCALE;
+	int boneCount = self->bonesCount, spacesCount = tangents ? boneCount : boneCount + 1;
+	spBone** bones = self->bones;
+	spBone* pa;
+
+	if (!translate && !rotate) 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 || !percentSpacing) {
+		if (scale) {
+			if (self->lengthsCount != boneCount) {
+				if (self->lengths) FREE(self->lengths);
+				self->lengths = MALLOC(float, boneCount);
+				self->lengthsCount = boneCount;
+			}
+			lengths = self->lengths;
+		}
+		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] = 0;
+			} else if (percentSpacing) {
+				if (scale) {
+					x = setupLength * bone->a, y = setupLength * bone->c;
+					length = SQRT(x * x + y * y);
+					lengths[i] = length;
+				}
+				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;
+			}
+		}
+	} else {
+		for (i = 1; i < spacesCount; i++) {
+			spaces[i] = spacing;
+		}
+	}
+
+	positions = spPathConstraint_computeWorldPositions(self, attachment, spacesCount, tangents,
+		data->positionMode == SP_POSITION_MODE_PERCENT, percentSpacing);
+	boneX = positions[0], boneY = positions[1], offsetRotation = self->data->offsetRotation;
+	tip = 0;
+	if (offsetRotation == 0)
+		tip = 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) * translateMix;
+		CONST_CAST(float, bone->worldY) += (boneY - bone->worldY) * translateMix;
+		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) * rotateMix + 1;
+				CONST_CAST(float, bone->a) *= s;
+				CONST_CAST(float, bone->c) *= s;
+			}
+		}
+		boneX = x;
+		boneY = y;
+		if (rotate) {
+			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) * rotateMix;
+				boneY += (length * (sine * a + cosine * c) - dy) * rotateMix;
+			} else
+				r += offsetRotation;
+			if (r > PI)
+				r -= PI2;
+			else if (r < -PI)
+				r += PI2;
+			r *= rotateMix;
+			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;
+		}
+		CONST_CAST(int, bone->appliedValid) = -1;
+	}
+}
+
+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;
+}
+
+/* Need to pass 0 as an argument, so VC++ doesn't error with C2124 */
+static int _isNan(float value, float zero) {
+	float _nan = (float)0.0 / zero;
+	return 0 == memcmp((void*)&value, (void*)&_nan, sizeof(value));
+}
+
+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, 0)) {
+		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/*bool*/percentPosition, int/**/percentSpacing) {
+	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;
+	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 (percentPosition) position *= pathLength;
+		if (percentSpacing) {
+			for (i = 1; i < spacesCount; i++)
+				spaces[i] *= pathLength;
+		}
+		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];
+			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 (percentPosition)
+		position *= pathLength;
+	else
+		position *= pathLength / path->lengths[curveCount - 1];
+	if (percentSpacing) {
+		for (i = 1; i < spacesCount; i++)
+			spaces[i] *= pathLength;
+	}
+
+	segments = self->segments;
+	curveLength = 0;
+	for (i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
+		float space = spaces[i];
+		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;
+}

+ 115 - 0
engine/source/spine/PathConstraint.h

@@ -0,0 +1,115 @@
+/******************************************************************************
+ * 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_PATHCONSTRAINT_H_
+#define SPINE_PATHCONSTRAINT_H_
+
+#include <spine/dll.h>
+#include <spine/PathConstraintData.h>
+#include <spine/Bone.h>
+#include <spine/Slot.h>
+#include "PathAttachment.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct spSkeleton;
+
+typedef struct spPathConstraint {
+	spPathConstraintData* const data;
+	int bonesCount;
+	spBone** const bones;
+	spSlot* target;
+	float position, spacing, rotateMix, translateMix;
+
+	int spacesCount;
+	float* spaces;
+
+	int positionsCount;
+	float* positions;
+
+	int worldCount;
+	float* world;
+
+	int curvesCount;
+	float* curves;
+
+	int lengthsCount;
+	float* lengths;
+
+	float segments[10];
+
+	int /*boolean*/ active;
+
+#ifdef __cplusplus
+	spPathConstraint() :
+		data(0),
+		bonesCount(0),
+		bones(0),
+		target(0),
+		position(0),
+		spacing(0),
+		rotateMix(0),
+		translateMix(0),
+		spacesCount(0),
+		spaces(0),
+		positionsCount(0),
+		positions(0),
+		worldCount(0),
+		world(0),
+		curvesCount(0),
+		curves(0),
+		lengthsCount(0),
+		lengths(0),
+		active(0) {
+	}
+#endif
+} spPathConstraint;
+
+#define SP_PATHCONSTRAINT_
+
+SP_API spPathConstraint* spPathConstraint_create (spPathConstraintData* data, const struct spSkeleton* skeleton);
+SP_API void spPathConstraint_dispose (spPathConstraint* self);
+
+SP_API void spPathConstraint_apply (spPathConstraint* self);
+SP_API float* spPathConstraint_computeWorldPositions(spPathConstraint* self, spPathAttachment* path, int spacesCount, int/*bool*/ tangents, int/*bool*/percentPosition, int/**/percentSpacing);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spPathConstraint PathConstraint;
+#define PathConstraint_create(...) spPathConstraint_create(__VA_ARGS__)
+#define PathConstraint_dispose(...) spPathConstraint_dispose(__VA_ARGS__)
+#define PathConstraint_apply(...) spPathConstraint_apply(__VA_ARGS__)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPINE_PATHCONSTRAINT_H_ */

+ 43 - 0
engine/source/spine/PathConstraintData.c

@@ -0,0 +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);
+}

+ 99 - 0
engine/source/spine/PathConstraintData.h

@@ -0,0 +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.
+ *****************************************************************************/
+
+#ifndef SPINE_PATHCONSTRAINTDATA_H_
+#define SPINE_PATHCONSTRAINTDATA_H_
+
+#include <spine/dll.h>
+#include <spine/BoneData.h>
+#include <spine/SlotData.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+	SP_POSITION_MODE_FIXED, SP_POSITION_MODE_PERCENT
+} spPositionMode;
+
+typedef enum {
+	SP_SPACING_MODE_LENGTH, SP_SPACING_MODE_FIXED, SP_SPACING_MODE_PERCENT
+} spSpacingMode;
+
+typedef enum {
+	SP_ROTATE_MODE_TANGENT, SP_ROTATE_MODE_CHAIN, SP_ROTATE_MODE_CHAIN_SCALE
+} spRotateMode;
+
+typedef struct spPathConstraintData {
+	const char* const name;
+	int order;
+	int/*bool*/ skinRequired;
+	int bonesCount;
+	spBoneData** const bones;
+	spSlotData* target;
+	spPositionMode positionMode;
+	spSpacingMode spacingMode;
+	spRotateMode rotateMode;
+	float offsetRotation;
+	float position, spacing, rotateMix, translateMix;
+
+#ifdef __cplusplus
+	spPathConstraintData() :
+		name(0),
+		order(0),
+		skinRequired(0),
+		bonesCount(0),
+		bones(0),
+		target(0),
+		positionMode(SP_POSITION_MODE_FIXED),
+		spacingMode(SP_SPACING_MODE_LENGTH),
+		rotateMode(SP_ROTATE_MODE_TANGENT),
+		offsetRotation(0),
+		position(0),
+		spacing(0),
+		rotateMix(0),
+		translateMix(0) {
+	}
+#endif
+} spPathConstraintData;
+
+SP_API spPathConstraintData* spPathConstraintData_create (const char* name);
+SP_API void spPathConstraintData_dispose (spPathConstraintData* self);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spPathConstraintData PathConstraintData;
+#define PathConstraintData_create(...) spPathConstraintData_create(__VA_ARGS__)
+#define PathConstraintData_dispose(...) spPathConstraintData_dispose(__VA_ARGS__)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPINE_PATHCONSTRAINTDATA_H_ */

+ 67 - 0
engine/source/spine/PointAttachment.c

@@ -0,0 +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 <spine/PointAttachment.h>
+#include <spine/extension.h>
+
+void _spPointAttachment_dispose (spAttachment* attachment) {
+	spPointAttachment* self = SUB_CAST(spPointAttachment, attachment);
+	_spAttachment_deinit(attachment);
+	FREE(self);
+}
+
+spAttachment* _spPointAttachment_copy (spAttachment* attachment) {
+	spPointAttachment* self = SUB_CAST(spPointAttachment, attachment);
+	spPointAttachment* copy = spPointAttachment_create(attachment->name);
+	copy->x = self->x;
+	copy->y = self->y;
+	copy->rotation = self->rotation;
+	spColor_setFromColor(&copy->color, &self->color);
+	return SUPER(copy);
+}
+
+spPointAttachment* spPointAttachment_create (const char* name) {
+	spPointAttachment* self = NEW(spPointAttachment);
+	_spAttachment_init(SUPER(self), name, SP_ATTACHMENT_POINT, _spPointAttachment_dispose, _spPointAttachment_copy);
+	return self;
+}
+
+void spPointAttachment_computeWorldPosition (spPointAttachment* self, spBone* bone, float* x, float* y) {
+	*x = self->x * bone->a + self->y * bone->b + bone->worldX;
+	*y = self->x * bone->c + self->y * bone->d + bone->worldY;
+}
+
+float spPointAttachment_computeWorldRotation (spPointAttachment* self, spBone* bone) {
+	float cosine, sine, x, y;
+	cosine = COS_DEG(self->rotation);
+	sine = SIN_DEG(self->rotation);
+	x = cosine * bone->a + sine * bone->b;
+	y = cosine * bone->c + sine * bone->d;
+	return ATAN2(y, x) * RAD_DEG;
+}

+ 64 - 0
engine/source/spine/PointAttachment.h

@@ -0,0 +1,64 @@
+/******************************************************************************
+ * 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_POINTATTACHMENT_H_
+#define SPINE_POINTATTACHMENT_H_
+
+#include <spine/dll.h>
+#include <spine/Attachment.h>
+#include <spine/VertexAttachment.h>
+#include <spine/Atlas.h>
+#include <spine/Slot.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct spPointAttachment {
+	spAttachment super;
+	float x, y, rotation;
+	spColor color;
+} spPointAttachment;
+
+SP_API spPointAttachment* spPointAttachment_create (const char* name);
+SP_API void spPointAttachment_computeWorldPosition (spPointAttachment* self, spBone* bone, float* x, float* y);
+SP_API float spPointAttachment_computeWorldRotation (spPointAttachment* self, spBone* bone);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spPointAttachment PointAttachment;
+#define PointAttachment_create(...) spPointAttachment_create(__VA_ARGS__)
+#define PointAttachment_computeWorldPosition(...) spPointAttachment_computeWorldPosition(__VA_ARGS__)
+#define PointAttachment_computeWorldRotation(...) spPointAttachment_computeWorldRotation(__VA_ARGS__)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPINE_POINTATTACHMENT_H_ */

+ 107 - 69
engine/source/spine/RegionAttachment.c

@@ -1,69 +1,98 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include <spine/RegionAttachment.h>
 #include <spine/RegionAttachment.h>
 #include <spine/extension.h>
 #include <spine/extension.h>
 
 
+typedef enum {
+	BLX = 0, BLY, ULX, ULY, URX, URY, BRX, BRY
+} spVertexIndex;
+
 void _spRegionAttachment_dispose (spAttachment* attachment) {
 void _spRegionAttachment_dispose (spAttachment* attachment) {
 	spRegionAttachment* self = SUB_CAST(spRegionAttachment, attachment);
 	spRegionAttachment* self = SUB_CAST(spRegionAttachment, attachment);
-
 	_spAttachment_deinit(attachment);
 	_spAttachment_deinit(attachment);
-
+	FREE(self->path);
 	FREE(self);
 	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* spRegionAttachment_create (const char* name) {
 	spRegionAttachment* self = NEW(spRegionAttachment);
 	spRegionAttachment* self = NEW(spRegionAttachment);
 	self->scaleX = 1;
 	self->scaleX = 1;
 	self->scaleY = 1;
 	self->scaleY = 1;
-	_spAttachment_init(SUPER(self), name, ATTACHMENT_REGION, _spRegionAttachment_dispose);
+	spColor_setFromFloats(&self->color, 1, 1, 1, 1);
+	_spAttachment_init(SUPER(self), name, SP_ATTACHMENT_REGION, _spRegionAttachment_dispose, _spRegionAttachment_copy);
 	return self;
 	return self;
 }
 }
 
 
 void spRegionAttachment_setUVs (spRegionAttachment* self, float u, float v, float u2, float v2, int/*bool*/rotate) {
 void spRegionAttachment_setUVs (spRegionAttachment* self, float u, float v, float u2, float v2, int/*bool*/rotate) {
 	if (rotate) {
 	if (rotate) {
-		self->uvs[VERTEX_X2] = u;
-		self->uvs[VERTEX_Y2] = v2;
-		self->uvs[VERTEX_X3] = u;
-		self->uvs[VERTEX_Y3] = v;
-		self->uvs[VERTEX_X4] = u2;
-		self->uvs[VERTEX_Y4] = v;
-		self->uvs[VERTEX_X1] = u2;
-		self->uvs[VERTEX_Y1] = v2;
+		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 {
 	} else {
-		self->uvs[VERTEX_X1] = u;
-		self->uvs[VERTEX_Y1] = v2;
-		self->uvs[VERTEX_X2] = u;
-		self->uvs[VERTEX_Y2] = v;
-		self->uvs[VERTEX_X3] = u2;
-		self->uvs[VERTEX_Y3] = v;
-		self->uvs[VERTEX_X4] = u2;
-		self->uvs[VERTEX_Y4] = v2;
+		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;
 	}
 	}
 }
 }
 
 
@@ -74,14 +103,8 @@ void spRegionAttachment_updateOffset (spRegionAttachment* self) {
 	float localY = -self->height / 2 * self->scaleY + self->regionOffsetY * regionScaleY;
 	float localY = -self->height / 2 * self->scaleY + self->regionOffsetY * regionScaleY;
 	float localX2 = localX + self->regionWidth * regionScaleX;
 	float localX2 = localX + self->regionWidth * regionScaleX;
 	float localY2 = localY + self->regionHeight * regionScaleY;
 	float localY2 = localY + self->regionHeight * regionScaleY;
-	float radians = (float)(self->rotation * 3.1415926535897932385 / 180);
-#ifdef __STDC_VERSION__
-	float cosine = cosf(radians);
-	float sine = sinf(radians);
-#else
-	float cosine = (float)cos(radians);
-	float sine = (float)sin(radians);
-#endif
+	float radians = self->rotation * DEG_RAD;
+	float cosine = COS(radians), sine = SIN(radians);
 	float localXCos = localX * cosine + self->x;
 	float localXCos = localX * cosine + self->x;
 	float localXSin = localX * sine;
 	float localXSin = localX * sine;
 	float localYCos = localY * cosine + self->y;
 	float localYCos = localY * cosine + self->y;
@@ -90,26 +113,41 @@ void spRegionAttachment_updateOffset (spRegionAttachment* self) {
 	float localX2Sin = localX2 * sine;
 	float localX2Sin = localX2 * sine;
 	float localY2Cos = localY2 * cosine + self->y;
 	float localY2Cos = localY2 * cosine + self->y;
 	float localY2Sin = localY2 * sine;
 	float localY2Sin = localY2 * sine;
-	self->offset[VERTEX_X1] = localXCos - localYSin;
-	self->offset[VERTEX_Y1] = localYCos + localXSin;
-	self->offset[VERTEX_X2] = localXCos - localY2Sin;
-	self->offset[VERTEX_Y2] = localY2Cos + localXSin;
-	self->offset[VERTEX_X3] = localX2Cos - localY2Sin;
-	self->offset[VERTEX_Y3] = localY2Cos + localX2Sin;
-	self->offset[VERTEX_X4] = localX2Cos - localYSin;
-	self->offset[VERTEX_Y4] = localYCos + localX2Sin;
+	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, float x, float y, spBone* bone, float* vertices) {
-	float* offset = self->offset;
-	x += bone->worldX;
-	y += bone->worldY;
-	vertices[VERTEX_X1] = offset[VERTEX_X1] * bone->m00 + offset[VERTEX_Y1] * bone->m01 + x;
-	vertices[VERTEX_Y1] = offset[VERTEX_X1] * bone->m10 + offset[VERTEX_Y1] * bone->m11 + y;
-	vertices[VERTEX_X2] = offset[VERTEX_X2] * bone->m00 + offset[VERTEX_Y2] * bone->m01 + x;
-	vertices[VERTEX_Y2] = offset[VERTEX_X2] * bone->m10 + offset[VERTEX_Y2] * bone->m11 + y;
-	vertices[VERTEX_X3] = offset[VERTEX_X3] * bone->m00 + offset[VERTEX_Y3] * bone->m01 + x;
-	vertices[VERTEX_Y3] = offset[VERTEX_X3] * bone->m10 + offset[VERTEX_Y3] * bone->m11 + y;
-	vertices[VERTEX_X4] = offset[VERTEX_X4] * bone->m00 + offset[VERTEX_Y4] * bone->m01 + x;
-	vertices[VERTEX_Y4] = offset[VERTEX_X4] * bone->m10 + offset[VERTEX_Y4] * bone->m11 + y;
+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;
 }
 }

+ 32 - 34
engine/source/spine/RegionAttachment.h

@@ -1,34 +1,36 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_REGIONATTACHMENT_H_
 #ifndef SPINE_REGIONATTACHMENT_H_
 #define SPINE_REGIONATTACHMENT_H_
 #define SPINE_REGIONATTACHMENT_H_
 
 
+#include <spine/dll.h>
 #include <spine/Attachment.h>
 #include <spine/Attachment.h>
 #include <spine/Atlas.h>
 #include <spine/Atlas.h>
 #include <spine/Slot.h>
 #include <spine/Slot.h>
@@ -37,14 +39,11 @@
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-typedef enum {
-	VERTEX_X1 = 0, VERTEX_Y1, VERTEX_X2, VERTEX_Y2, VERTEX_X3, VERTEX_Y3, VERTEX_X4, VERTEX_Y4
-} spVertexIndex;
-
-typedef struct spRegionAttachment spRegionAttachment;
-struct spRegionAttachment {
+typedef struct spRegionAttachment {
 	spAttachment super;
 	spAttachment super;
+	const char* path;
 	float x, y, scaleX, scaleY, rotation, width, height;
 	float x, y, scaleX, scaleY, rotation, width, height;
+	spColor color;
 
 
 	void* rendererObject;
 	void* rendererObject;
 	int regionOffsetX, regionOffsetY; /* Pixels stripped from the bottom left, unrotated. */
 	int regionOffsetX, regionOffsetY; /* Pixels stripped from the bottom left, unrotated. */
@@ -53,15 +52,14 @@ struct spRegionAttachment {
 
 
 	float offset[8];
 	float offset[8];
 	float uvs[8];
 	float uvs[8];
-};
+} spRegionAttachment;
 
 
-spRegionAttachment* spRegionAttachment_create (const char* name);
-void spRegionAttachment_setUVs (spRegionAttachment* self, float u, float v, float u2, float v2, int/*bool*/rotate);
-void spRegionAttachment_updateOffset (spRegionAttachment* self);
-void spRegionAttachment_computeWorldVertices (spRegionAttachment* self, float x, float y, spBone* bone, float* vertices);
+SP_API spRegionAttachment* spRegionAttachment_create (const char* name);
+SP_API void spRegionAttachment_setUVs (spRegionAttachment* self, float u, float v, float u2, float v2, int/*bool*/rotate);
+SP_API void spRegionAttachment_updateOffset (spRegionAttachment* self);
+SP_API void spRegionAttachment_computeWorldVertices (spRegionAttachment* self, spBone* bone, float* vertices, int offset, int stride);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
-typedef spVertexIndex VertexIndex;
 typedef spRegionAttachment RegionAttachment;
 typedef spRegionAttachment RegionAttachment;
 #define RegionAttachment_create(...) spRegionAttachment_create(__VA_ARGS__)
 #define RegionAttachment_create(...) spRegionAttachment_create(__VA_ARGS__)
 #define RegionAttachment_setUVs(...) spRegionAttachment_setUVs(__VA_ARGS__)
 #define RegionAttachment_setUVs(...) spRegionAttachment_setUVs(__VA_ARGS__)

+ 481 - 70
engine/source/spine/Skeleton.c

@@ -1,105 +1,452 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include <spine/Skeleton.h>
 #include <spine/Skeleton.h>
+#include <stdlib.h>
 #include <string.h>
 #include <string.h>
 #include <spine/extension.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;
+
+	int updateCacheResetCount;
+	int updateCacheResetCapacity;
+	spBone** updateCacheReset;
+} _spSkeleton;
+
 spSkeleton* spSkeleton_create (spSkeletonData* data) {
 spSkeleton* spSkeleton_create (spSkeletonData* data) {
-	int i, ii;
+	int i;
+	int* childrenCounts;
 
 
-	spSkeleton* self = NEW(spSkeleton);
+	_spSkeleton* internal = NEW(_spSkeleton);
+	spSkeleton* self = SUPER(internal);
 	CONST_CAST(spSkeletonData*, self->data) = data;
 	CONST_CAST(spSkeletonData*, self->data) = data;
 
 
-	self->boneCount = self->data->boneCount;
-	self->bones = MALLOC(spBone*, self->boneCount);
+	self->bonesCount = self->data->bonesCount;
+	self->bones = MALLOC(spBone*, self->bonesCount);
+	childrenCounts = CALLOC(int, self->bonesCount);
 
 
-	for (i = 0; i < self->boneCount; ++i) {
+	for (i = 0; i < self->bonesCount; ++i) {
 		spBoneData* boneData = self->data->bones[i];
 		spBoneData* boneData = self->data->bones[i];
-		spBone* parent = 0;
-		if (boneData->parent) {
-			/* Find parent bone. */
-			for (ii = 0; ii < self->boneCount; ++ii) {
-				if (data->bones[ii] == boneData->parent) {
-					parent = self->bones[ii];
-					break;
-				}
-			}
+		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] = spBone_create(boneData, parent);
+		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->bones[0];
+	CONST_CAST(spBone*, self->root) = (self->bonesCount > 0 ? self->bones[0] : NULL);
 
 
-	self->slotCount = data->slotCount;
-	self->slots = MALLOC(spSlot*, self->slotCount);
-	for (i = 0; i < self->slotCount; ++i) {
+	self->slotsCount = data->slotsCount;
+	self->slots = MALLOC(spSlot*, self->slotsCount);
+	for (i = 0; i < self->slotsCount; ++i) {
 		spSlotData *slotData = data->slots[i];
 		spSlotData *slotData = data->slots[i];
-
-		/* Find bone for the slotData's boneData. */
-		spBone* bone = 0;
-		for (ii = 0; ii < self->boneCount; ++ii) {
-			if (data->bones[ii] == slotData->boneData) {
-				bone = self->bones[ii];
-				break;
-			}
-		}
-		self->slots[i] = spSlot_create(slotData, self, bone);
+		spBone* bone = self->bones[slotData->boneData->index];
+		self->slots[i] = spSlot_create(slotData, bone);
 	}
 	}
 
 
-	self->drawOrder = MALLOC(spSlot*, self->slotCount);
-	memcpy(self->drawOrder, self->slots, sizeof(spSlot*) * self->slotCount);
+	self->drawOrder = MALLOC(spSlot*, self->slotsCount);
+	memcpy(self->drawOrder, self->slots, sizeof(spSlot*) * self->slotsCount);
 
 
-	self->r = 1;
-	self->g = 1;
-	self->b = 1;
-	self->a = 1;
+	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;
 	return self;
 }
 }
 
 
 void spSkeleton_dispose (spSkeleton* self) {
 void spSkeleton_dispose (spSkeleton* self) {
 	int i;
 	int i;
-	for (i = 0; i < self->boneCount; ++i)
+	_spSkeleton* internal = SUB_CAST(_spSkeleton, self);
+
+	FREE(internal->updateCache);
+	FREE(internal->updateCacheReset);
+
+	for (i = 0; i < self->bonesCount; ++i)
 		spBone_dispose(self->bones[i]);
 		spBone_dispose(self->bones[i]);
 	FREE(self->bones);
 	FREE(self->bones);
 
 
-	for (i = 0; i < self->slotCount; ++i)
+	for (i = 0; i < self->slotsCount; ++i)
 		spSlot_dispose(self->slots[i]);
 		spSlot_dispose(self->slots[i]);
 	FREE(self->slots);
 	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->drawOrder);
 	FREE(self);
 	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 _addToUpdateCacheReset(_spSkeleton* const internal, spBone* bone) {
+	if (internal->updateCacheResetCount == internal->updateCacheResetCapacity) {
+		internal->updateCacheResetCapacity *= 2;
+		internal->updateCacheReset = (spBone**)realloc(internal->updateCacheReset, sizeof(spBone*) * internal->updateCacheResetCapacity);
+	}
+	internal->updateCacheReset[internal->updateCacheResetCount] = bone;
+	++internal->updateCacheResetCount;
+}
+
+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;
+		while (i < pathBonesCount) {
+			int boneCount = pathBones[i++];
+			for (n = i + boneCount; i < n; i++)
+				_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) {
+	int /*bool*/ contains = 0;
+	int i;
+	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) {
+		spBone* child = constrained[constraint->bonesCount - 1];
+		contains = 0;
+		for (i = 0; i < internal->updateCacheCount; i++) {
+			_spUpdate update = internal->updateCache[i];
+			if (update.object == child) {
+				contains = -1;
+				break;
+			}
+		}
+		if (!contains) _addToUpdateCacheReset(internal, child);
+	}
+
+	_addToUpdateCache(internal, SP_UPDATE_IK_CONSTRAINT, constraint);
+
+	_sortReset(parent->children, parent->childrenCount);
+	constrained[constraint->bonesCount-1]->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;
+	int /*boolean*/ contains = 0;
+
+	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);
+			contains = 0;
+			for (i = 0; i < internal->updateCacheCount; i++) {
+				_spUpdate update = internal->updateCache[i];
+				if (update.object == child) {
+					contains = -1;
+					break;
+				}
+			}
+			if (!contains) _addToUpdateCacheReset(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;
+
+	internal->updateCacheResetCapacity = self->bonesCount;
+	FREE(internal->updateCacheReset);
+	internal->updateCacheReset = MALLOC(spBone*, internal->updateCacheResetCapacity);
+	internal->updateCacheResetCount = 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) {
 void spSkeleton_updateWorldTransform (const spSkeleton* self) {
 	int i;
 	int i;
-	for (i = 0; i < self->boneCount; ++i)
-		spBone_updateWorldTransform(self->bones[i], self->flipX, self->flipY);
+	_spSkeleton* internal = SUB_CAST(_spSkeleton, self);
+	spBone** updateCacheReset = internal->updateCacheReset;
+	for (i = 0; i < internal->updateCacheResetCount; i++) {
+		spBone* bone = updateCacheReset[i];
+		CONST_CAST(float, bone->ax) = bone->x;
+		CONST_CAST(float, bone->ay) = bone->y;
+		CONST_CAST(float, bone->arotation) = bone->rotation;
+		CONST_CAST(float, bone->ascaleX) = bone->scaleX;
+		CONST_CAST(float, bone->ascaleY) = bone->scaleY;
+		CONST_CAST(float, bone->ashearX) = bone->shearX;
+		CONST_CAST(float, bone->ashearY) = bone->shearY;
+		CONST_CAST(int, bone->appliedValid) = 1;
+	}
+
+	for (i = 0; i < internal->updateCacheCount; ++i) {
+		_spUpdate* update = internal->updateCache + i;
+		switch (update->type) {
+		case SP_UPDATE_BONE:
+			spBone_updateWorldTransform((spBone*)update->object);
+			break;
+		case SP_UPDATE_IK_CONSTRAINT:
+			spIkConstraint_apply((spIkConstraint*)update->object);
+			break;
+		case SP_UPDATE_TRANSFORM_CONSTRAINT:
+			spTransformConstraint_apply((spTransformConstraint*)update->object);
+			break;
+		case SP_UPDATE_PATH_CONSTRAINT:
+			spPathConstraint_apply((spPathConstraint*)update->object);
+			break;
+		}
+	}
 }
 }
 
 
 void spSkeleton_setToSetupPose (const spSkeleton* self) {
 void spSkeleton_setToSetupPose (const spSkeleton* self) {
@@ -109,41 +456,68 @@ void spSkeleton_setToSetupPose (const spSkeleton* self) {
 
 
 void spSkeleton_setBonesToSetupPose (const spSkeleton* self) {
 void spSkeleton_setBonesToSetupPose (const spSkeleton* self) {
 	int i;
 	int i;
-	for (i = 0; i < self->boneCount; ++i)
+	for (i = 0; i < self->bonesCount; ++i)
 		spBone_setToSetupPose(self->bones[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->rotateMix = data->rotateMix;
+		constraint->translateMix = data->translateMix;
+		constraint->scaleMix = data->scaleMix;
+		constraint->shearMix = data->shearMix;
+	}
+
+	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->rotateMix = data->rotateMix;
+		constraint->translateMix = data->translateMix;
+	}
 }
 }
 
 
 void spSkeleton_setSlotsToSetupPose (const spSkeleton* self) {
 void spSkeleton_setSlotsToSetupPose (const spSkeleton* self) {
 	int i;
 	int i;
-	memcpy(self->drawOrder, self->slots, self->slotCount * sizeof(spSlot*));
-	for (i = 0; i < self->slotCount; ++i)
+	memcpy(self->drawOrder, self->slots, self->slotsCount * sizeof(spSlot*));
+	for (i = 0; i < self->slotsCount; ++i)
 		spSlot_setToSetupPose(self->slots[i]);
 		spSlot_setToSetupPose(self->slots[i]);
 }
 }
 
 
 spBone* spSkeleton_findBone (const spSkeleton* self, const char* boneName) {
 spBone* spSkeleton_findBone (const spSkeleton* self, const char* boneName) {
 	int i;
 	int i;
-	for (i = 0; i < self->boneCount; ++i)
+	for (i = 0; i < self->bonesCount; ++i)
 		if (strcmp(self->data->bones[i]->name, boneName) == 0) return self->bones[i];
 		if (strcmp(self->data->bones[i]->name, boneName) == 0) return self->bones[i];
 	return 0;
 	return 0;
 }
 }
 
 
 int spSkeleton_findBoneIndex (const spSkeleton* self, const char* boneName) {
 int spSkeleton_findBoneIndex (const spSkeleton* self, const char* boneName) {
 	int i;
 	int i;
-	for (i = 0; i < self->boneCount; ++i)
+	for (i = 0; i < self->bonesCount; ++i)
 		if (strcmp(self->data->bones[i]->name, boneName) == 0) return i;
 		if (strcmp(self->data->bones[i]->name, boneName) == 0) return i;
 	return -1;
 	return -1;
 }
 }
 
 
 spSlot* spSkeleton_findSlot (const spSkeleton* self, const char* slotName) {
 spSlot* spSkeleton_findSlot (const spSkeleton* self, const char* slotName) {
 	int i;
 	int i;
-	for (i = 0; i < self->slotCount; ++i)
+	for (i = 0; i < self->slotsCount; ++i)
 		if (strcmp(self->data->slots[i]->name, slotName) == 0) return self->slots[i];
 		if (strcmp(self->data->slots[i]->name, slotName) == 0) return self->slots[i];
 	return 0;
 	return 0;
 }
 }
 
 
 int spSkeleton_findSlotIndex (const spSkeleton* self, const char* slotName) {
 int spSkeleton_findSlotIndex (const spSkeleton* self, const char* slotName) {
 	int i;
 	int i;
-	for (i = 0; i < self->slotCount; ++i)
+	for (i = 0; i < self->slotsCount; ++i)
 		if (strcmp(self->data->slots[i]->name, slotName) == 0) return i;
 		if (strcmp(self->data->slots[i]->name, slotName) == 0) return i;
 	return -1;
 	return -1;
 }
 }
@@ -161,8 +535,24 @@ int spSkeleton_setSkinByName (spSkeleton* self, const char* skinName) {
 }
 }
 
 
 void spSkeleton_setSkin (spSkeleton* self, spSkin* newSkin) {
 void spSkeleton_setSkin (spSkeleton* self, spSkin* newSkin) {
-	if (self->skin && newSkin) spSkin_attachAll(newSkin, self, self->skin);
+	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;
 	CONST_CAST(spSkin*, self->skin) = newSkin;
+	spSkeleton_updateCache(self);
 }
 }
 
 
 spAttachment* spSkeleton_getAttachmentForSlotName (const spSkeleton* self, const char* slotName, const char* attachmentName) {
 spAttachment* spSkeleton_getAttachmentForSlotName (const spSkeleton* self, const char* slotName, const char* attachmentName) {
@@ -185,7 +575,7 @@ spAttachment* spSkeleton_getAttachmentForSlotIndex (const spSkeleton* self, int
 
 
 int spSkeleton_setAttachment (spSkeleton* self, const char* slotName, const char* attachmentName) {
 int spSkeleton_setAttachment (spSkeleton* self, const char* slotName, const char* attachmentName) {
 	int i;
 	int i;
-	for (i = 0; i < self->slotCount; ++i) {
+	for (i = 0; i < self->slotsCount; ++i) {
 		spSlot *slot = self->slots[i];
 		spSlot *slot = self->slots[i];
 		if (strcmp(slot->data->name, slotName) == 0) {
 		if (strcmp(slot->data->name, slotName) == 0) {
 			if (!attachmentName)
 			if (!attachmentName)
@@ -201,6 +591,27 @@ int spSkeleton_setAttachment (spSkeleton* self, const char* slotName, const char
 	return 0;
 	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) {
 void spSkeleton_update (spSkeleton* self, float deltaTime) {
 	self->time += deltaTime;
 	self->time += deltaTime;
 }
 }

+ 102 - 48
engine/source/spine/Skeleton.h

@@ -1,96 +1,150 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License
- * Version 2
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software, you may not (a) modify, translate, adapt or
- * otherwise create derivative works, improvements of the Software or develop
- * new applications using the Software or (b) remove, delete, alter or obscure
- * any trademarks or any copyright, trademark, patent or other intellectual
- * property or proprietary rights notices on or in the Software, including
- * any copy thereof. Redistributions in binary or source form must include
- * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
- * "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 SOFTARE BE LIABLE FOR ANY
+ * 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
  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * (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
  * 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
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
 #ifndef SPINE_SKELETON_H_
 #ifndef SPINE_SKELETON_H_
 #define SPINE_SKELETON_H_
 #define SPINE_SKELETON_H_
 
 
+#include <spine/dll.h>
 #include <spine/SkeletonData.h>
 #include <spine/SkeletonData.h>
 #include <spine/Slot.h>
 #include <spine/Slot.h>
 #include <spine/Skin.h>
 #include <spine/Skin.h>
+#include <spine/IkConstraint.h>
+#include <spine/TransformConstraint.h>
+#include <spine/PathConstraint.h>
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-typedef struct spSkeleton spSkeleton;
-struct spSkeleton {
+typedef struct spSkeleton {
 	spSkeletonData* const data;
 	spSkeletonData* const data;
 
 
-	int boneCount;
+	int bonesCount;
 	spBone** bones;
 	spBone** bones;
 	spBone* const root;
 	spBone* const root;
 
 
-	int slotCount;
+	int slotsCount;
 	spSlot** slots;
 	spSlot** slots;
 	spSlot** drawOrder;
 	spSlot** drawOrder;
 
 
+	int ikConstraintsCount;
+	spIkConstraint** ikConstraints;
+
+	int transformConstraintsCount;
+	spTransformConstraint** transformConstraints;
+
+	int pathConstraintsCount;
+	spPathConstraint** pathConstraints;
+
 	spSkin* const skin;
 	spSkin* const skin;
-	float r, g, b, a;
+	spColor color;
 	float time;
 	float time;
-	int/*bool*/flipX, flipY;
+	float scaleX, scaleY;
 	float x, y;
 	float x, y;
-};
 
 
-spSkeleton* spSkeleton_create (spSkeletonData* data);
-void spSkeleton_dispose (spSkeleton* self);
+#ifdef __cplusplus
+	spSkeleton() :
+		data(0),
+		bonesCount(0),
+		bones(0),
+		root(0),
+		slotsCount(0),
+		slots(0),
+		drawOrder(0),
+
+		ikConstraintsCount(0),
+		ikConstraints(0),
+
+		transformConstraintsCount(0),
+		transformConstraints(0),
+
+		skin(0),
+		color(),
+		time(0),
+		scaleX(1),
+		scaleY(1),
+		x(0), y(0) {
+	}
+#endif
+} spSkeleton;
+
+SP_API spSkeleton* spSkeleton_create (spSkeletonData* data);
+SP_API void spSkeleton_dispose (spSkeleton* self);
 
 
-void spSkeleton_updateWorldTransform (const spSkeleton* self);
+/* Caches information about bones and constraints. Must be called if bones or constraints, or weighted path attachments
+ * are added or removed. */
+SP_API void spSkeleton_updateCache (spSkeleton* self);
+SP_API void spSkeleton_updateWorldTransform (const spSkeleton* self);
 
 
-void spSkeleton_setToSetupPose (const spSkeleton* self);
-void spSkeleton_setBonesToSetupPose (const spSkeleton* self);
-void spSkeleton_setSlotsToSetupPose (const spSkeleton* self);
+/* Sets the bones, constraints, and slots to their setup pose values. */
+SP_API void spSkeleton_setToSetupPose (const spSkeleton* self);
+/* Sets the bones and constraints to their setup pose values. */
+SP_API void spSkeleton_setBonesToSetupPose (const spSkeleton* self);
+SP_API void spSkeleton_setSlotsToSetupPose (const spSkeleton* self);
 
 
 /* Returns 0 if the bone was not found. */
 /* Returns 0 if the bone was not found. */
-spBone* spSkeleton_findBone (const spSkeleton* self, const char* boneName);
+SP_API spBone* spSkeleton_findBone (const spSkeleton* self, const char* boneName);
 /* Returns -1 if the bone was not found. */
 /* Returns -1 if the bone was not found. */
-int spSkeleton_findBoneIndex (const spSkeleton* self, const char* boneName);
+SP_API int spSkeleton_findBoneIndex (const spSkeleton* self, const char* boneName);
 
 
 /* Returns 0 if the slot was not found. */
 /* Returns 0 if the slot was not found. */
-spSlot* spSkeleton_findSlot (const spSkeleton* self, const char* slotName);
+SP_API spSlot* spSkeleton_findSlot (const spSkeleton* self, const char* slotName);
 /* Returns -1 if the slot was not found. */
 /* Returns -1 if the slot was not found. */
-int spSkeleton_findSlotIndex (const spSkeleton* self, const char* slotName);
+SP_API int spSkeleton_findSlotIndex (const spSkeleton* self, const char* slotName);
 
 
-/* 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.
+/* Sets the skin used to look up attachments before looking in the SkeletonData defaultSkin. Attachments from the new skin are
+ * attached if the corresponding attachment from the old skin was attached. If there was no old skin, each slot's setup mode
+ * attachment is attached from the new skin.
  * @param skin May be 0.*/
  * @param skin May be 0.*/
-void spSkeleton_setSkin (spSkeleton* self, spSkin* skin);
+SP_API void spSkeleton_setSkin (spSkeleton* self, spSkin* skin);
 /* Returns 0 if the skin was not found. See spSkeleton_setSkin.
 /* Returns 0 if the skin was not found. See spSkeleton_setSkin.
  * @param skinName May be 0. */
  * @param skinName May be 0. */
-int spSkeleton_setSkinByName (spSkeleton* self, const char* skinName);
+SP_API int spSkeleton_setSkinByName (spSkeleton* self, const char* skinName);
 
 
 /* Returns 0 if the slot or attachment was not found. */
 /* Returns 0 if the slot or attachment was not found. */
-spAttachment* spSkeleton_getAttachmentForSlotName (const spSkeleton* self, const char* slotName, const char* attachmentName);
-/* Returns 0 if the slot or attachment was not found. */
-spAttachment* spSkeleton_getAttachmentForSlotIndex (const spSkeleton* self, int slotIndex, const char* attachmentName);
+SP_API spAttachment* spSkeleton_getAttachmentForSlotName (const spSkeleton* self, const char* slotName, const char* attachmentName);
 /* Returns 0 if the slot or attachment was not found. */
 /* Returns 0 if the slot or attachment was not found. */
-int spSkeleton_setAttachment (spSkeleton* self, const char* slotName, const char* attachmentName);
+SP_API spAttachment* spSkeleton_getAttachmentForSlotIndex (const spSkeleton* self, int slotIndex, const char* attachmentName);
+/* Returns 0 if the slot or attachment was not found.
+ * @param attachmentName May be 0. */
+SP_API int spSkeleton_setAttachment (spSkeleton* self, const char* slotName, const char* attachmentName);
+
+/* Returns 0 if the IK constraint was not found. */
+SP_API spIkConstraint* spSkeleton_findIkConstraint (const spSkeleton* self, const char* constraintName);
+
+/* Returns 0 if the transform constraint was not found. */
+SP_API spTransformConstraint* spSkeleton_findTransformConstraint (const spSkeleton* self, const char* constraintName);
+
+/* Returns 0 if the path constraint was not found. */
+SP_API spPathConstraint* spSkeleton_findPathConstraint (const spSkeleton* self, const char* constraintName);
 
 
-void spSkeleton_update (spSkeleton* self, float deltaTime);
+SP_API void spSkeleton_update (spSkeleton* self, float deltaTime);
 
 
 #ifdef SPINE_SHORT_NAMES
 #ifdef SPINE_SHORT_NAMES
 typedef spSkeleton Skeleton;
 typedef spSkeleton Skeleton;

+ 1147 - 0
engine/source/spine/SkeletonBinary.c

@@ -0,0 +1,1147 @@
+/******************************************************************************
+ * 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 CURVE_LINEAR 0
+#define CURVE_STEPPED 1
+#define CURVE_BEZIER 2
+
+#define BONE_ROTATE 0
+#define BONE_TRANSLATE 1
+#define BONE_SCALE 2
+#define BONE_SHEAR 3
+
+#define SLOT_ATTACHMENT 0
+#define SLOT_COLOR 1
+#define SLOT_TWO_COLOR 2
+
+#define PATH_POSITION 0
+#define PATH_SPACING 1
+#define PATH_MIX 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 readCurve (_dataInput* input, spCurveTimeline* timeline, int frameIndex) {
+	switch (readByte(input)) {
+	case CURVE_STEPPED: {
+		spCurveTimeline_setStepped(timeline, frameIndex);
+		break;
+	}
+	case CURVE_BEZIER: {
+		float cx1 = readFloat(input);
+		float cy1 = readFloat(input);
+		float cx2 = readFloat(input);
+		float cy2 = readFloat(input);
+		spCurveTimeline_setCurve(timeline, frameIndex, cx1, cy1, cx2, cy2);
+		break;
+	}
+	}
+}
+
+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;
+}
+
+_SP_ARRAY_DECLARE_TYPE(spTimelineArray, spTimeline*)
+_SP_ARRAY_IMPLEMENT_TYPE(spTimelineArray, spTimeline*)
+
+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 frameIndex;
+	int drawOrderCount, eventCount;
+	spAnimation* animation;
+
+	/* 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);
+			switch (timelineType) {
+			case SLOT_ATTACHMENT: {
+				spAttachmentTimeline* timeline = spAttachmentTimeline_create(frameCount);
+				timeline->slotIndex = slotIndex;
+				for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+					float time = readFloat(input);
+					const char* attachmentName = readStringRef(input, skeletonData);
+					/* TODO Avoid copying of attachmentName inside */
+					spAttachmentTimeline_setFrame(timeline, frameIndex, time, attachmentName);
+				}
+				spTimelineArray_add(timelines, (spTimeline*)timeline);
+				duration = MAX(duration, timeline->frames[frameCount - 1]);
+				break;
+			}
+			case SLOT_COLOR: {
+				spColorTimeline* timeline = spColorTimeline_create(frameCount);
+				timeline->slotIndex = slotIndex;
+				for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+					float time = readFloat(input);
+					float r, g, b, a;
+					readColor(input, &r, &g, &b, &a);
+					spColorTimeline_setFrame(timeline, frameIndex, time, r, g, b, a);
+					if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex);
+				}
+				spTimelineArray_add(timelines, (spTimeline*)timeline);
+				duration = MAX(duration, timeline->frames[(frameCount - 1) * COLOR_ENTRIES]);
+				break;
+			}
+			case SLOT_TWO_COLOR: {
+				spTwoColorTimeline* timeline = spTwoColorTimeline_create(frameCount);
+				timeline->slotIndex = slotIndex;
+				for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+					float time = readFloat(input);
+					float r, g, b, a;
+					float r2, g2, b2, a2;
+					readColor(input, &r, &g, &b, &a);
+					readColor(input, &a2, &r2, &g2, &b2);
+					spTwoColorTimeline_setFrame(timeline, frameIndex, time, r, g, b, a, r2, g2, b2);
+					if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex);
+				}
+				spTimelineArray_add(timelines, (spTimeline*)timeline);
+				duration = MAX(duration, timeline->frames[(frameCount - 1) * TWOCOLOR_ENTRIES]);
+				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 slot: ", skeletonData->slots[slotIndex]->name);
+				return 0;
+			}
+			}
+		}
+	}
+
+	/* 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);
+			switch (timelineType) {
+			case BONE_ROTATE: {
+				spRotateTimeline *timeline = spRotateTimeline_create(frameCount);
+				timeline->boneIndex = boneIndex;
+				for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+					float time = readFloat(input);
+					float degrees = readFloat(input);
+					spRotateTimeline_setFrame(timeline, frameIndex, time, degrees);
+					if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex);
+				}
+				spTimelineArray_add(timelines, (spTimeline*)timeline);
+				duration = MAX(duration, timeline->frames[(frameCount - 1) * ROTATE_ENTRIES]);
+				break;
+			}
+			case BONE_TRANSLATE:
+			case BONE_SCALE:
+			case BONE_SHEAR: {
+				float timelineScale = 1;
+				spTranslateTimeline *timeline = 0;
+				switch (timelineType) {
+					case BONE_SCALE:
+						timeline = spScaleTimeline_create(frameCount);
+						break;
+					case BONE_SHEAR:
+						timeline = spShearTimeline_create(frameCount);
+						break;
+					case BONE_TRANSLATE:
+						timeline = spTranslateTimeline_create(frameCount);
+						timelineScale = self->scale;
+						break;
+					default:
+						break;
+				}
+				timeline->boneIndex = boneIndex;
+				for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+					float time = readFloat(input);
+					float x = readFloat(input) * timelineScale;
+					float y = readFloat(input) * timelineScale;
+					spTranslateTimeline_setFrame(timeline, frameIndex, time, x, y);
+					if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex);
+				}
+				spTimelineArray_add(timelines, (spTimeline*)timeline);
+				duration = MAX(duration, timeline->frames[(frameCount - 1) * TRANSLATE_ENTRIES]);
+				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 0;
+			}
+			}
+		}
+	}
+
+	/* IK constraint timelines. */
+	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
+		int index = readVarint(input, 1);
+		int frameCount = readVarint(input, 1);
+		spIkConstraintTimeline* timeline = spIkConstraintTimeline_create(frameCount);
+		timeline->ikConstraintIndex = index;
+		for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+			float time = readFloat(input);
+			float mix = readFloat(input);
+			float softness = readFloat(input);
+			signed char bendDirection = readSByte(input);
+			int compress = readBoolean(input);
+			int stretch = readBoolean(input);
+			spIkConstraintTimeline_setFrame(timeline, frameIndex, time, mix, softness, bendDirection, compress, stretch);
+			if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex);
+		}
+		spTimelineArray_add(timelines, (spTimeline*)timeline);
+		duration = MAX(duration, timeline->frames[(frameCount - 1) * IKCONSTRAINT_ENTRIES]);
+	}
+
+	/* Transform constraint timelines. */
+	for (i = 0, n = readVarint(input, 1); i < n; ++i) {
+		int index = readVarint(input, 1);
+		int frameCount = readVarint(input, 1);
+		spTransformConstraintTimeline* timeline = spTransformConstraintTimeline_create(frameCount);
+		timeline->transformConstraintIndex = index;
+		for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+			float time = readFloat(input);
+			float rotateMix = readFloat(input);
+			float translateMix = readFloat(input);
+			float scaleMix = readFloat(input);
+			float shearMix = readFloat(input);
+			spTransformConstraintTimeline_setFrame(timeline, frameIndex, time, rotateMix, translateMix,
+				scaleMix, shearMix);
+			if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex);
+		}
+		spTimelineArray_add(timelines, (spTimeline*)timeline);
+		duration = MAX(duration, timeline->frames[(frameCount - 1) * TRANSFORMCONSTRAINT_ENTRIES]);
+	}
+
+	/* 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) {
+			unsigned char timelineType = readByte(input);
+			int frameCount = readVarint(input, 1);
+			switch (timelineType) {
+			case PATH_POSITION:
+			case PATH_SPACING: {
+				spPathConstraintPositionTimeline* timeline = 0;
+				float timelineScale = 1;
+				if (timelineType == PATH_SPACING) {
+					timeline = (spPathConstraintPositionTimeline*)spPathConstraintSpacingTimeline_create(frameCount);
+					if (data->spacingMode == SP_SPACING_MODE_LENGTH || data->spacingMode == SP_SPACING_MODE_FIXED)
+						timelineScale = self->scale;
+				} else {
+					timeline = spPathConstraintPositionTimeline_create(frameCount);
+					if (data->positionMode == SP_POSITION_MODE_FIXED)
+						timelineScale = self->scale;
+				}
+				timeline->pathConstraintIndex = index;
+				for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+					float time = readFloat(input);
+					float value = readFloat(input) * timelineScale;
+					spPathConstraintPositionTimeline_setFrame(timeline, frameIndex, time, value);
+					if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex);
+				}
+				spTimelineArray_add(timelines, (spTimeline*)timeline);
+				duration = MAX(duration, timeline->frames[(frameCount - 1) * PATHCONSTRAINTPOSITION_ENTRIES]);
+				break;
+			}
+			case PATH_MIX: {
+				spPathConstraintMixTimeline* timeline = spPathConstraintMixTimeline_create(frameCount);
+				timeline->pathConstraintIndex = index;
+				for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+					float time = readFloat(input);
+					float rotateMix = readFloat(input);
+					float translateMix = readFloat(input);
+					spPathConstraintMixTimeline_setFrame(timeline, frameIndex, time, rotateMix, translateMix);
+					if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex);
+				}
+				spTimelineArray_add(timelines, (spTimeline*)timeline);
+				duration = MAX(duration, timeline->frames[(frameCount - 1) * PATHCONSTRAINTMIX_ENTRIES]);
+			}
+			}
+		}
+	}
+
+	/* 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;
+
+				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);
+				timeline = spDeformTimeline_create(frameCount, deformLength);
+				timeline->slotIndex = slotIndex;
+				timeline->attachment = SUPER(attachment);
+
+				for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+					float time = readFloat(input);
+					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, frameIndex, time, deform);
+					if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex);
+				}
+				FREE(tempDeform);
+
+				spTimelineArray_add(timelines, (spTimeline*)timeline);
+				duration = MAX(duration, timeline->frames[frameCount - 1]);
+			}
+		}
+	}
+
+	/* 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);
+		duration = MAX(duration, timeline->frames[drawOrderCount - 1]);
+	}
+
+	/* 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 = MAX(duration, timeline->frames[eventCount - 1]);
+	}
+
+	animation = spAnimation_create(name, 0);
+	FREE(animation->timelines);
+	animation->duration = duration;
+	animation->timelinesCount = timelines->size;
+	animation->timelines = timelines->items;
+	FREE(timelines);
+	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) readInt(input); /* Skip color. */
+		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) readInt(input); /* Skip color. */
+		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) readInt(input); /* Skip color. */
+		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;
+	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();
+
+	skeletonData->hash = readString(input);
+	if (!strlen(skeletonData->hash)) {
+		FREE(skeletonData->hash);
+		skeletonData->hash = 0;
+	}
+
+	skeletonData->version = readString(input);
+	if (!strlen(skeletonData->version)) {
+		FREE(skeletonData->version);
+		skeletonData->version = 0;
+	}
+    if (strcmp(skeletonData->version, "3.8.75") == 0) {
+        FREE(input);
+        spSkeletonData_dispose(skeletonData);
+        _spSkeletonBinary_setError(self, "Unsupported skeleton data, please export with a newer version of Spine.", "");
+        return 0;
+    }
+
+	skeletonData->x = readFloat(input);
+	skeletonData->y = readFloat(input);
+	skeletonData->width = readFloat(input);
+	skeletonData->height = readFloat(input);
+
+	nonessential = readBoolean(input);
+
+	if (nonessential) {
+		/* Skip images path & fps */
+		readFloat(input);
+		FREE(readString(input));
+		FREE(readString(input));
+	}
+
+	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) readInt(input); /* Skip bone color. */
+		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->rotateMix = readFloat(input);
+		data->translateMix = readFloat(input);
+		data->scaleMix = readFloat(input);
+		data->shearMix = 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->rotateMix = readFloat(input);
+		data->translateMix = 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;
+}

+ 71 - 0
engine/source/spine/SkeletonBinary.h

@@ -0,0 +1,71 @@
+/******************************************************************************
+ * 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_SKELETONBINARY_H_
+#define SPINE_SKELETONBINARY_H_
+
+#include <spine/dll.h>
+#include <spine/Attachment.h>
+#include <spine/AttachmentLoader.h>
+#include <spine/SkeletonData.h>
+#include <spine/Atlas.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct spAtlasAttachmentLoader;
+
+typedef struct spSkeletonBinary {
+	float scale;
+	spAttachmentLoader* attachmentLoader;
+	const char* const error;
+} spSkeletonBinary;
+
+SP_API spSkeletonBinary* spSkeletonBinary_createWithLoader (spAttachmentLoader* attachmentLoader);
+SP_API spSkeletonBinary* spSkeletonBinary_create (spAtlas* atlas);
+SP_API void spSkeletonBinary_dispose (spSkeletonBinary* self);
+
+SP_API spSkeletonData* spSkeletonBinary_readSkeletonData (spSkeletonBinary* self, const unsigned char* binary, const int length);
+SP_API spSkeletonData* spSkeletonBinary_readSkeletonDataFile (spSkeletonBinary* self, const char* path);
+
+#ifdef SPINE_SHORT_NAMES
+typedef spSkeletonBinary SkeletonBinary;
+#define SkeletonBinary_createWithLoader(...) spSkeletonBinary_createWithLoader(__VA_ARGS__)
+#define SkeletonBinary_create(...) spSkeletonBinary_create(__VA_ARGS__)
+#define SkeletonBinary_dispose(...) spSkeletonBinary_dispose(__VA_ARGS__)
+#define SkeletonBinary_readSkeletonData(...) spSkeletonBinary_readSkeletonData(__VA_ARGS__)
+#define SkeletonBinary_readSkeletonDataFile(...) spSkeletonBinary_readSkeletonDataFile(__VA_ARGS__)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPINE_SKELETONBINARY_H_ */

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