Bläddra i källkod

Merge branch '3.7-beta' into 3.7-beta-cpp

badlogic 6 år sedan
förälder
incheckning
bb1bdd7046
26 ändrade filer med 549 tillägg och 291 borttagningar
  1. 1 0
      .gitignore
  2. 1 1
      spine-cocos2dx/example/Classes/AppDelegate.cpp
  3. 2 2
      spine-cocos2dx/example/Classes/BatchingExample.cpp
  4. 4 4
      spine-cocos2dx/example/Classes/CoinExample.cpp
  5. 1 1
      spine-cocos2dx/example/Classes/GoblinsExample.cpp
  6. 7 7
      spine-cocos2dx/example/Classes/RaptorExample.cpp
  7. 29 29
      spine-cocos2dx/example/Classes/SkeletonRendererSeparatorExample.cpp
  8. 2 2
      spine-cocos2dx/example/Classes/SpineboyExample.cpp
  9. 1 1
      spine-cocos2dx/example/Classes/TankExample.cpp
  10. 120 120
      spine-cocos2dx/src/spine/SkeletonRenderer.h
  11. 1 1
      spine-cocos2dx/src/spine/SkeletonTwoColorBatch.cpp
  12. 2 1
      spine-csharp/src/Bone.cs
  13. 40 36
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java
  14. 2 1
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java
  15. 12 3
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java
  16. 6 6
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java
  17. 118 45
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/TwoColorPolygonBatch.java
  18. 10 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs
  19. 1 0
      spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs
  20. 3 3
      spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs
  21. 16 16
      spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs
  22. 7 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs
  23. 47 11
      spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs
  24. 105 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Shaders/Spine-Special-Skeleton-Grayscale.shader
  25. 9 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Shaders/Spine-Special-Skeleton-Grayscale.shader.meta
  26. 2 1
      spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs

+ 1 - 0
.gitignore

@@ -41,6 +41,7 @@ spine-cocos2dx/example/cocos2d
 spine-cocos2dx/example/proj.win32/spine-cocos2d-x.VC.opendb
 spine-cocos2dx/example/proj.win32/spine-cocos2d-x.VC.opendb
 spine-cocos2dx/example/proj.win32/spine-cocos2d-x.VC.db
 spine-cocos2dx/example/proj.win32/spine-cocos2d-x.VC.db
 xcuserdata/
 xcuserdata/
+xcshareddata/
 
 
 spine-cocos2d-objc/cocos2d/*
 spine-cocos2d-objc/cocos2d/*
 spine-cocos2d-objc/spine-cocos2d-iphone-objc.xcodeproj/project.xcworkspace/xcshareddata/
 spine-cocos2d-objc/spine-cocos2d-iphone-objc.xcodeproj/project.xcworkspace/xcshareddata/

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

@@ -74,7 +74,7 @@ bool AppDelegate::applicationDidFinishLaunching () {
 	glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
 	glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
 #endif
 #endif
 
 
-	Size frameSize = glview->getFrameSize();
+	cocos2d::Size frameSize = glview->getFrameSize();
 	
 	
 	vector<string> searchPath;
 	vector<string> searchPath;
 
 

+ 2 - 2
spine-cocos2dx/example/Classes/BatchingExample.cpp

@@ -70,14 +70,14 @@ bool BatchingExample::init () {
 
 
 	int xMin = _contentSize.width * 0.10f, xMax = _contentSize.width * 0.90f;
 	int xMin = _contentSize.width * 0.10f, xMax = _contentSize.width * 0.90f;
 	int yMin = 0, yMax = _contentSize.height * 0.7f;
 	int yMin = 0, yMax = _contentSize.height * 0.7f;
-	for (int i = 0, j = 0; i < NUM_SKELETONS; i++) {
+	for (int i = 0; i < NUM_SKELETONS; i++) {
 		// Each skeleton node shares the same atlas, skeleton data, and mix times.
 		// Each skeleton node shares the same atlas, skeleton data, and mix times.
 		SkeletonAnimation* skeletonNode = SkeletonAnimation::createWithData(_skeletonData, false);
 		SkeletonAnimation* skeletonNode = SkeletonAnimation::createWithData(_skeletonData, false);
 		skeletonNode->setAnimationStateData(_stateData);
 		skeletonNode->setAnimationStateData(_stateData);
 
 
 		skeletonNode->setAnimation(0, "walk", true);
 		skeletonNode->setAnimation(0, "walk", true);
 		skeletonNode->addAnimation(0, "jump", true, RandomHelper::random_int(0, 300) / 100.0f);
 		skeletonNode->addAnimation(0, "jump", true, RandomHelper::random_int(0, 300) / 100.0f);
-		skeletonNode->addAnimation(0, "run", true);
+		skeletonNode->addAnimation(0, "run", true);
 		
 		
 		// alternative setting two color tint for groups of 10 skeletons
 		// alternative setting two color tint for groups of 10 skeletons
 		// should end up with #skeletons / 10 batches
 		// should end up with #skeletons / 10 batches

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

@@ -44,14 +44,14 @@ bool CoinExample::init () {
 	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
 	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
 
 
 	skeletonNode = SkeletonAnimation::createWithBinaryFile("coin-pro.skel", "coin.atlas", 1);
 	skeletonNode = SkeletonAnimation::createWithBinaryFile("coin-pro.skel", "coin.atlas", 1);
-	skeletonNode->setAnimation(0, "rotate", true);
-	// skeletonNode->setTwoColorTint(true);
-	
+	skeletonNode->setAnimation(0, "rotate", true);
+	// skeletonNode->setTwoColorTint(true);
+
 	skeletonNode->setPosition(Vec2(_contentSize.width / 2, 100));
 	skeletonNode->setPosition(Vec2(_contentSize.width / 2, 100));
 	addChild(skeletonNode);
 	addChild(skeletonNode);
 
 
 	scheduleUpdate();
 	scheduleUpdate();
-	
+
 	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
 	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
 		if (!skeletonNode->getDebugBonesEnabled())
 		if (!skeletonNode->getDebugBonesEnabled())

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

@@ -51,7 +51,7 @@ bool GoblinsExample::init () {
 	addChild(skeletonNode);
 	addChild(skeletonNode);
 
 
 	scheduleUpdate();
 	scheduleUpdate();
-	
+
 	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
 	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
 		if (!skeletonNode->getDebugBonesEnabled())
 		if (!skeletonNode->getDebugBonesEnabled())

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

@@ -34,9 +34,9 @@
 
 
 USING_NS_CC;
 USING_NS_CC;
 using namespace spine;
 using namespace spine;
-
-PowInterpolation pow2(2);
-PowOutInterpolation powOut2(2);
+
+PowInterpolation pow2(2);
+PowOutInterpolation powOut2(2);
 SwirlVertexEffect effect(400, powOut2);
 SwirlVertexEffect effect(400, powOut2);
 
 
 Scene* RaptorExample::scene () {
 Scene* RaptorExample::scene () {
@@ -67,7 +67,7 @@ bool RaptorExample::init () {
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
 		if (!skeletonNode->getDebugBonesEnabled()) {
 		if (!skeletonNode->getDebugBonesEnabled()) {
 			skeletonNode->setDebugBonesEnabled(true);
 			skeletonNode->setDebugBonesEnabled(true);
-			skeletonNode->setDebugMeshesEnabled(true);
+			skeletonNode->setDebugMeshesEnabled(true);			
 		} else if (skeletonNode->getTimeScale() == 1)
 		} else if (skeletonNode->getTimeScale() == 1)
 			skeletonNode->setTimeScale(0.3f);
 			skeletonNode->setTimeScale(0.3f);
 		else
 		else
@@ -80,8 +80,8 @@ bool RaptorExample::init () {
 }
 }
 
 
 void RaptorExample::update(float fDelta) {
 void RaptorExample::update(float fDelta) {
-	swirlTime += fDelta;
-	float percent = spine::MathUtil::fmod(swirlTime, 2);
-	if (percent > 1) percent = 1 - (percent - 1);
+	swirlTime += fDelta;
+	float percent = spine::MathUtil::fmod(swirlTime, 2);
+	if (percent > 1) percent = 1 - (percent - 1);
 	effect.setAngle(pow2.interpolate(-60.0f, 60.0f, percent));
 	effect.setAngle(pow2.interpolate(-60.0f, 60.0f, percent));
 }
 }

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

@@ -42,39 +42,39 @@ Scene* SkeletonRendererSeparatorExample::scene () {
 
 
 bool SkeletonRendererSeparatorExample::init () {
 bool SkeletonRendererSeparatorExample::init () {
 	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
 	if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
-
-	// Spineboy's back, which will manage the animation and GPU resources
+
+	// Spineboy's back, which will manage the animation and GPU resources
 	// will render only the front slots of Spineboy
 	// will render only the front slots of Spineboy
-	backNode = SkeletonAnimation::createWithJsonFile("spineboy-ess.json", "spineboy.atlas", 0.6f);
-	backNode->setMix("walk", "jump", 0.4);
-	backNode->setAnimation(0, "walk", true);
-	backNode->setSlotsRange(backNode->findSlot("rear-upper-arm")->getData().getIndex(), backNode->findSlot("rear-shin")->getData().getIndex());
-	backNode->setPosition(Vec2(_contentSize.width / 2, 20));
-	
-	// A simple rectangle to go between the front and back slots of Spineboy
-	betweenNode = DrawNode::create();
-	Vec2 rect[4];
-	rect[0] = Vec2(0, 0);
-	rect[1] = Vec2(40, 0);
-	rect[2] = Vec2(40, 200);
-	rect[3] = Vec2(0, 200);
-	betweenNode->drawPolygon(rect, 4, Color4F(1, 0, 0, 1), 1, Color4F(1, 0, 0, 1));
-	betweenNode->setPosition(Vec2(_contentSize.width / 2 + 30, 20));
-	
-	// Spineboy's front, doesn't manage any skeleton, animation or GPU resources, but simply
-	// renders the back slots of Spineboy. The skeleton, animatio state and GPU resources
-	// are shared with the front node!
-	frontNode = SkeletonRenderer::createWithSkeleton(backNode->getSkeleton());
-	frontNode->setSlotsRange(frontNode->findSlot("neck")->getData().getIndex(), -1);
-	frontNode->setPosition(Vec2(_contentSize.width / 2, 20));
-	
-	// Add the front, between and back node in the correct order to this scene
-	addChild(backNode);
-	addChild(betweenNode);
+	backNode = SkeletonAnimation::createWithJsonFile("spineboy-ess.json", "spineboy.atlas", 0.6f);
+	backNode->setMix("walk", "jump", 0.4);
+	backNode->setAnimation(0, "walk", true);
+	backNode->setSlotsRange(backNode->findSlot("rear-upper-arm")->getData().getIndex(), backNode->findSlot("rear-shin")->getData().getIndex());
+	backNode->setPosition(Vec2(_contentSize.width / 2, 20));
+
+	// A simple rectangle to go between the front and back slots of Spineboy
+	betweenNode = DrawNode::create();
+	Vec2 rect[4];
+	rect[0] = Vec2(0, 0);
+	rect[1] = Vec2(40, 0);
+	rect[2] = Vec2(40, 200);
+	rect[3] = Vec2(0, 200);
+	betweenNode->drawPolygon(rect, 4, Color4F(1, 0, 0, 1), 1, Color4F(1, 0, 0, 1));
+	betweenNode->setPosition(Vec2(_contentSize.width / 2 + 30, 20));
+
+	// Spineboy's front, doesn't manage any skeleton, animation or GPU resources, but simply
+	// renders the back slots of Spineboy. The skeleton, animatio state and GPU resources
+	// are shared with the front node!
+	frontNode = SkeletonRenderer::createWithSkeleton(backNode->getSkeleton());
+	frontNode->setSlotsRange(frontNode->findSlot("neck")->getData().getIndex(), -1);
+	frontNode->setPosition(Vec2(_contentSize.width / 2, 20));
+
+	// Add the front, between and back node in the correct order to this scene
+	addChild(backNode);
+	addChild(betweenNode);
 	addChild(frontNode);
 	addChild(frontNode);
 
 
 	scheduleUpdate();
 	scheduleUpdate();
-	
+
 	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
 	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
 		if (!backNode->getDebugBonesEnabled())
 		if (!backNode->getDebugBonesEnabled())

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

@@ -68,7 +68,7 @@ bool SpineboyExample::init () {
 	skeletonNode->setMix("jump", "run", 0.4);
 	skeletonNode->setMix("jump", "run", 0.4);
 	skeletonNode->setAnimation(0, "walk", true);
 	skeletonNode->setAnimation(0, "walk", true);
 	TrackEntry* jumpEntry = skeletonNode->addAnimation(0, "jump", false, 1);
 	TrackEntry* jumpEntry = skeletonNode->addAnimation(0, "jump", false, 1);
-	skeletonNode->addAnimation(0, "run", true);    
+	skeletonNode->addAnimation(0, "run", true);
 
 
 	skeletonNode->setTrackStartListener(jumpEntry, [] (TrackEntry* entry) {
 	skeletonNode->setTrackStartListener(jumpEntry, [] (TrackEntry* entry) {
 		log("jumped!");
 		log("jumped!");
@@ -81,7 +81,7 @@ bool SpineboyExample::init () {
 	addChild(skeletonNode);
 	addChild(skeletonNode);
 
 
 	scheduleUpdate();
 	scheduleUpdate();
-	
+
 	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
 	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
 		if (!skeletonNode->getDebugBonesEnabled())
 		if (!skeletonNode->getDebugBonesEnabled())

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

@@ -50,7 +50,7 @@ bool TankExample::init () {
 	addChild(skeletonNode);
 	addChild(skeletonNode);
 
 
 	scheduleUpdate();
 	scheduleUpdate();
-	
+
 	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
 	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
 		if (!skeletonNode->getDebugBonesEnabled())
 		if (!skeletonNode->getDebugBonesEnabled())

+ 120 - 120
spine-cocos2dx/src/spine/SkeletonRenderer.h

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

+ 1 - 1
spine-cocos2dx/src/spine/SkeletonTwoColorBatch.cpp

@@ -57,7 +57,7 @@ void TwoColorTrianglesCommand::init(float globalOrder, GLuint textureID, GLProgr
 	if(_triangles.indexCount % 3 != 0) {
 	if(_triangles.indexCount % 3 != 0) {
 	int count = _triangles.indexCount;
 	int count = _triangles.indexCount;
 		_triangles.indexCount = count / 3 * 3;
 		_triangles.indexCount = count / 3 * 3;
-		CCLOGERROR("Resize indexCount from %zd to %zd, size must be multiple times of 3", count, _triangles.indexCount);
+		CCLOGERROR("Resize indexCount from %d to %d, size must be multiple times of 3", count, _triangles.indexCount);
 	}
 	}
 	_mv = mv;
 	_mv = mv;
 
 

+ 2 - 1
spine-csharp/src/Bone.cs

@@ -329,10 +329,11 @@ namespace Spine {
 
 
 		public float WorldToLocalRotation (float worldRotation) {
 		public float WorldToLocalRotation (float worldRotation) {
 			float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation);
 			float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation);
-			return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg;
+			return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX;
 		}
 		}
 
 
 		public float LocalToWorldRotation (float localRotation) {
 		public float LocalToWorldRotation (float localRotation) {
+			localRotation -= rotation - shearX;
 			float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation);
 			float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation);
 			return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg;
 			return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg;
 		}
 		}

+ 40 - 36
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java

@@ -127,11 +127,11 @@ public class AnimationState {
 				float nextTime = current.trackLast - next.delay;
 				float nextTime = current.trackLast - next.delay;
 				if (nextTime >= 0) {
 				if (nextTime >= 0) {
 					next.delay = 0;
 					next.delay = 0;
-					next.trackTime = nextTime + delta * next.timeScale;
+					next.trackTime = (nextTime / current.timeScale + delta) * next.timeScale;
 					current.trackTime += currentDelta;
 					current.trackTime += currentDelta;
 					setCurrent(i, next, true);
 					setCurrent(i, next, true);
 					while (next.mixingFrom != null) {
 					while (next.mixingFrom != null) {
-						next.mixTime += currentDelta;
+						next.mixTime += delta;
 						next = next.mixingFrom;
 						next = next.mixingFrom;
 					}
 					}
 					continue;
 					continue;
@@ -182,15 +182,8 @@ public class AnimationState {
 			return finished;
 			return finished;
 		}
 		}
 
 
-		// If to has 0 timeScale and is not the first entry, remove the mix and apply it one more time to return to the setup pose.
-		if (to.timeScale == 0 && to.mixingTo != null) {
-			to.timeScale = 1;
-			to.mixTime = 0;
-			to.mixDuration = 0;
-		}
-
 		from.trackTime += delta * from.timeScale;
 		from.trackTime += delta * from.timeScale;
-		to.mixTime += delta * to.timeScale;
+		to.mixTime += delta;
 		return false;
 		return false;
 	}
 	}
 
 
@@ -424,10 +417,10 @@ public class AnimationState {
 		}
 		}
 	}
 	}
 
 
-	/** Removes all animations from all tracks, leaving skeletons in their previous pose.
+	/** Removes all animations from all tracks, leaving skeletons in their current pose.
 	 * <p>
 	 * <p>
 	 * It may be desired to use {@link AnimationState#setEmptyAnimations(float)} to mix the skeletons back to the setup pose,
 	 * It may be desired to use {@link AnimationState#setEmptyAnimations(float)} to mix the skeletons back to the setup pose,
-	 * rather than leaving them in their previous pose. */
+	 * rather than leaving them in their current pose. */
 	public void clearTracks () {
 	public void clearTracks () {
 		boolean oldDrainDisabled = queue.drainDisabled;
 		boolean oldDrainDisabled = queue.drainDisabled;
 		queue.drainDisabled = true;
 		queue.drainDisabled = true;
@@ -438,10 +431,10 @@ public class AnimationState {
 		queue.drain();
 		queue.drain();
 	}
 	}
 
 
-	/** Removes all animations from the track, leaving skeletons in their previous pose.
+	/** Removes all animations from the track, leaving skeletons in their current pose.
 	 * <p>
 	 * <p>
 	 * It may be desired to use {@link AnimationState#setEmptyAnimation(int, float)} to mix the skeletons back to the setup pose,
 	 * It may be desired to use {@link AnimationState#setEmptyAnimation(int, float)} to mix the skeletons back to the setup pose,
-	 * rather than leaving them in their previous pose. */
+	 * rather than leaving them in their current pose. */
 	public void clearTrack (int trackIndex) {
 	public void clearTrack (int trackIndex) {
 		if (trackIndex >= tracks.size) return;
 		if (trackIndex >= tracks.size) return;
 		TrackEntry current = tracks.get(trackIndex);
 		TrackEntry current = tracks.get(trackIndex);
@@ -534,9 +527,10 @@ public class AnimationState {
 
 
 	/** Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is
 	/** Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is
 	 * equivalent to calling {@link #setAnimation(int, Animation, boolean)}.
 	 * equivalent to calling {@link #setAnimation(int, Animation, boolean)}.
-	 * @param delay Seconds to begin this animation after the start of the previous animation. If <= 0, uses the duration of the
-	 *           previous track entry minus any mix duration plus the specified <code>delay</code>. If the previous entry is
-	 *           looping, its next loop completion is used instead of the duration.
+	 * @param delay If > 0, sets {@link TrackEntry#getDelay()}. If <= 0, the delay set is the duration of the previous track entry
+	 *           minus any mix duration (from the {@link AnimationStateData}) plus the specified <code>delay</code> (ie the mix
+	 *           ends at (<code>delay</code> = 0) or before (<code>delay</code> < 0) the previous track entry duration). If the
+	 *           previous entry is looping, its next loop completion is used instead of its duration.
 	 * @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
 	 * @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
 	 *         after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */
 	 *         after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */
 	public TrackEntry addAnimation (int trackIndex, Animation animation, boolean loop, float delay) {
 	public TrackEntry addAnimation (int trackIndex, Animation animation, boolean loop, float delay) {
@@ -598,9 +592,10 @@ public class AnimationState {
 	 * {@link #setEmptyAnimation(int, float)}.
 	 * {@link #setEmptyAnimation(int, float)}.
 	 * <p>
 	 * <p>
 	 * See {@link #setEmptyAnimation(int, float)}.
 	 * See {@link #setEmptyAnimation(int, float)}.
-	 * @param delay Seconds to begin this animation after the start of the previous animation. If <= 0, uses the duration of the
-	 *           previous track entry minus any mix duration plus the specified <code>delay</code>. If the previous entry is
-	 *           looping, its next loop completion is used instead of the duration.
+	 * @param delay If > 0, sets {@link TrackEntry#getDelay()}. If <= 0, the delay set is the duration of the previous track entry
+	 *           minus any mix duration plus the specified <code>delay</code> (ie the mix ends at (<code>delay</code> = 0) or
+	 *           before (<code>delay</code> < 0) the previous track entry duration). If the previous entry is looping, its next
+	 *           loop completion is used instead of its duration.
 	 * @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
 	 * @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
 	 *         after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */
 	 *         after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */
 	public TrackEntry addEmptyAnimation (int trackIndex, float mixDuration, float delay) {
 	public TrackEntry addEmptyAnimation (int trackIndex, float mixDuration, float delay) {
@@ -764,8 +759,8 @@ public class AnimationState {
 		queue.clear();
 		queue.clear();
 	}
 	}
 
 
-	/** Multiplier for the delta time when the animation state is updated, causing time for all animations to play slower or
-	 * faster. Defaults to 1.
+	/** Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower
+	 * or faster. Defaults to 1.
 	 * <p>
 	 * <p>
 	 * See TrackEntry {@link TrackEntry#getTimeScale()} for affecting a single animation. */
 	 * See TrackEntry {@link TrackEntry#getTimeScale()} for affecting a single animation. */
 	public float getTimeScale () {
 	public float getTimeScale () {
@@ -859,9 +854,12 @@ public class AnimationState {
 			this.loop = loop;
 			this.loop = loop;
 		}
 		}
 
 
-		/** Seconds to postpone playing the animation. When a track entry is the current track entry, <code>delay</code> postpones
-		 * incrementing the {@link #getTrackTime()}. When a track entry is queued, <code>delay</code> is the time from the start of
-		 * the previous animation to when the track entry will become the current track entry. */
+		/** Seconds to postpone playing the animation. When this track entry is the current track entry, <code>delay</code>
+		 * postpones incrementing the {@link #getTrackTime()}. When this track entry is queued, <code>delay</code> is the time from
+		 * the start of the previous animation to when this track entry will become the current track entry (ie when the previous
+		 * track entry {@link TrackEntry#getTrackTime()} >= this track entry's <code>delay</code>).
+		 * <p>
+		 * {@link #getTimeScale()} affects the delay. */
 		public float getDelay () {
 		public float getDelay () {
 			return delay;
 			return delay;
 		}
 		}
@@ -943,10 +941,15 @@ public class AnimationState {
 			return Math.min(trackTime + animationStart, animationEnd);
 			return Math.min(trackTime + animationStart, animationEnd);
 		}
 		}
 
 
-		/** Multiplier for the delta time when the animation state is updated, causing time for this animation to pass slower or
+		/** Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or
 		 * faster. Defaults to 1.
 		 * faster. Defaults to 1.
 		 * <p>
 		 * <p>
-		 * If <code>timeScale</code> is 0, any {@link #getMixDuration()} will be ignored.
+		 * {@link #getMixTime()} is not affected by track entry time scale, so {@link #getMixDuration()} may need to be adjusted to
+		 * match the animation speed.
+		 * <p>
+		 * When using {@link AnimationState#addAnimation(int, Animation, boolean, float)} with a <code>delay</code> <= 0, note the
+		 * {@link #getDelay()} is set using the mix duration from the {@link AnimationStateData}, assuming time scale to be 1. If
+		 * the time scale is not 1, the delay may need to be adjusted.
 		 * <p>
 		 * <p>
 		 * See AnimationState {@link AnimationState#getTimeScale()} for affecting all animations. */
 		 * See AnimationState {@link AnimationState#getTimeScale()} for affecting all animations. */
 		public float getTimeScale () {
 		public float getTimeScale () {
@@ -970,11 +973,11 @@ public class AnimationState {
 			this.listener = listener;
 			this.listener = listener;
 		}
 		}
 
 
-		/** Values < 1 mix this animation with the setup pose or the skeleton's previous pose. Defaults to 1, which overwrites the
-		 * skeleton's previous pose with this animation.
+		/** Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults
+		 * to 1, which overwrites the skeleton's current pose with this animation.
 		 * <p>
 		 * <p>
-		 * Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. It doesn't make sense
-		 * to use alpha on track 0 if the skeleton pose is from the last frame render. */
+		 * Typically track 0 is used to completely pose the skeleton, then alpha is used on higher tracks. It doesn't make sense to
+		 * use alpha on track 0 if the skeleton pose is from the last frame render. */
 		public float getAlpha () {
 		public float getAlpha () {
 			return alpha;
 			return alpha;
 		}
 		}
@@ -984,7 +987,7 @@ public class AnimationState {
 		}
 		}
 
 
 		/** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the
 		/** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the
-		 * <code>eventThreshold</code>, event timelines for the animation being mixed out will be applied. Defaults to 0, so event
+		 * <code>eventThreshold</code>, event timelines are applied while this animation is being mixed out. Defaults to 0, so event
 		 * timelines are not applied for an animation being mixed out. */
 		 * timelines are not applied for an animation being mixed out. */
 		public float getEventThreshold () {
 		public float getEventThreshold () {
 			return eventThreshold;
 			return eventThreshold;
@@ -995,8 +998,8 @@ public class AnimationState {
 		}
 		}
 
 
 		/** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the
 		/** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the
-		 * <code>attachmentThreshold</code>, attachment timelines for the animation being mixed out will be applied. Defaults to 0,
-		 * so attachment timelines are not applied for an animation being mixed out. */
+		 * <code>attachmentThreshold</code>, attachment timelines are applied while this animation is being mixed out. Defaults to
+		 * 0, so attachment timelines are not applied for an animation being mixed out. */
 		public float getAttachmentThreshold () {
 		public float getAttachmentThreshold () {
 			return attachmentThreshold;
 			return attachmentThreshold;
 		}
 		}
@@ -1006,7 +1009,7 @@ public class AnimationState {
 		}
 		}
 
 
 		/** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the
 		/** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the
-		 * <code>drawOrderThreshold</code>, draw order timelines for the animation being mixed out will be applied. Defaults to 0,
+		 * <code>drawOrderThreshold</code>, draw order timelines are applied while this animation is being mixed out. Defaults to 0,
 		 * so draw order timelines are not applied for an animation being mixed out. */
 		 * so draw order timelines are not applied for an animation being mixed out. */
 		public float getDrawOrderThreshold () {
 		public float getDrawOrderThreshold () {
 			return drawOrderThreshold;
 			return drawOrderThreshold;
@@ -1046,7 +1049,8 @@ public class AnimationState {
 		 * track entry only before {@link AnimationState#update(float)} is first called.
 		 * track entry only before {@link AnimationState#update(float)} is first called.
 		 * <p>
 		 * <p>
 		 * When using {@link AnimationState#addAnimation(int, Animation, boolean, float)} with a <code>delay</code> <= 0, note the
 		 * When using {@link AnimationState#addAnimation(int, Animation, boolean, float)} with a <code>delay</code> <= 0, note the
-		 * {@link #getDelay()} is set using the mix duration from the {@link AnimationStateData}. */
+		 * {@link #getDelay()} is set using the mix duration from the {@link AnimationStateData}, not a mix duration set
+		 * afterward. */
 		public float getMixDuration () {
 		public float getMixDuration () {
 			return mixDuration;
 			return mixDuration;
 		}
 		}

+ 2 - 1
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java

@@ -547,11 +547,12 @@ public class Bone implements Updatable {
 	/** Transforms a world rotation to a local rotation. */
 	/** Transforms a world rotation to a local rotation. */
 	public float worldToLocalRotation (float worldRotation) {
 	public float worldToLocalRotation (float worldRotation) {
 		float sin = sinDeg(worldRotation), cos = cosDeg(worldRotation);
 		float sin = sinDeg(worldRotation), cos = cosDeg(worldRotation);
-		return atan2(a * sin - c * cos, d * cos - b * sin) * radDeg;
+		return atan2(a * sin - c * cos, d * cos - b * sin) * radDeg + rotation - shearX;
 	}
 	}
 
 
 	/** Transforms a local rotation to a world rotation. */
 	/** Transforms a local rotation to a world rotation. */
 	public float localToWorldRotation (float localRotation) {
 	public float localToWorldRotation (float localRotation) {
+		localRotation -= rotation - shearX;
 		float sin = sinDeg(localRotation), cos = cosDeg(localRotation);
 		float sin = sinDeg(localRotation), cos = cosDeg(localRotation);
 		return atan2(cos * c + sin * d, cos * a + sin * b) * radDeg;
 		return atan2(cos * c + sin * d, cos * a + sin * b) * radDeg;
 	}
 	}

+ 12 - 3
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java

@@ -430,14 +430,23 @@ public class PathConstraint implements Constraint {
 
 
 	private void addCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
 	private void addCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
 		float[] out, int o, boolean tangents) {
 		float[] out, int o, boolean tangents) {
-		if (p < epsilon || Float.isNaN(p)) p = epsilon;
+		if (p < epsilon || Float.isNaN(p)) {
+			out[o] = x1;
+			out[o + 1] = y1;
+			out[o + 2] = (float)Math.atan2(cy1 - y1, cx1 - x1);
+			return;
+		}
 		float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
 		float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
 		float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
 		float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
 		float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
 		float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
 		out[o] = x;
 		out[o] = x;
 		out[o + 1] = y;
 		out[o + 1] = y;
-		if (tangents)
-			out[o + 2] = (float)Math.atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
+		if (tangents) {
+			if (p < 0.001f)
+				out[o + 2] = (float)Math.atan2(cy1 - y1, cx1 - x1);
+			else
+				out[o + 2] = (float)Math.atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
+		}
 	}
 	}
 
 
 	public int getOrder () {
 	public int getOrder () {

+ 6 - 6
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java

@@ -70,12 +70,12 @@ public class SkeletonRenderer {
 	 * previous blend function is not restored, since that could result in unnecessary flushes, depending on what is rendered
 	 * previous blend function is not restored, since that could result in unnecessary flushes, depending on what is rendered
 	 * next. */
 	 * next. */
 	public void draw (Batch batch, Skeleton skeleton) {
 	public void draw (Batch batch, Skeleton skeleton) {
-		if (batch instanceof PolygonSpriteBatch) {
-			draw((PolygonSpriteBatch)batch, skeleton);
-			return;
-		} else if (batch instanceof TwoColorPolygonBatch) {
+		if (batch instanceof TwoColorPolygonBatch) {
 			draw((TwoColorPolygonBatch)batch, skeleton);
 			draw((TwoColorPolygonBatch)batch, skeleton);
 			return;
 			return;
+		} else if (batch instanceof PolygonSpriteBatch) {
+			draw((PolygonSpriteBatch)batch, skeleton);
+			return;
 		}
 		}
 
 
 		VertexEffect vertexEffect = this.vertexEffect;
 		VertexEffect vertexEffect = this.vertexEffect;
@@ -357,7 +357,7 @@ public class SkeletonRenderer {
 					FloatArray clippedVertices = clipper.getClippedVertices();
 					FloatArray clippedVertices = clipper.getClippedVertices();
 					ShortArray clippedTriangles = clipper.getClippedTriangles();
 					ShortArray clippedTriangles = clipper.getClippedTriangles();
 					if (vertexEffect != null) applyVertexEffect(clippedVertices.items, clippedVertices.size, 6, light, dark);
 					if (vertexEffect != null) applyVertexEffect(clippedVertices.items, clippedVertices.size, 6, light, dark);
-					batch.draw(texture, clippedVertices.items, 0, clippedVertices.size, clippedTriangles.items, 0,
+					batch.drawTwoColor(texture, clippedVertices.items, 0, clippedVertices.size, clippedTriangles.items, 0,
 						clippedTriangles.size);
 						clippedTriangles.size);
 				} else {
 				} else {
 					if (vertexEffect != null) {
 					if (vertexEffect != null) {
@@ -386,7 +386,7 @@ public class SkeletonRenderer {
 							vertices[v + 3] = uvs[u + 1];
 							vertices[v + 3] = uvs[u + 1];
 						}
 						}
 					}
 					}
-					batch.draw(texture, vertices, 0, verticesLength, triangles, 0, triangles.length);
+					batch.drawTwoColor(texture, vertices, 0, verticesLength, triangles, 0, triangles.length);
 				}
 				}
 			}
 			}
 
 

+ 118 - 45
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/TwoColorPolygonBatch.java

@@ -31,10 +31,15 @@
 package com.esotericsoftware.spine.utils;
 package com.esotericsoftware.spine.utils;
 
 
 import com.badlogic.gdx.Gdx;
 import com.badlogic.gdx.Gdx;
-import com.badlogic.gdx.graphics.*;
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.GL20;
+import com.badlogic.gdx.graphics.Mesh;
 import com.badlogic.gdx.graphics.Mesh.VertexDataType;
 import com.badlogic.gdx.graphics.Mesh.VertexDataType;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.VertexAttribute;
 import com.badlogic.gdx.graphics.VertexAttributes.Usage;
 import com.badlogic.gdx.graphics.VertexAttributes.Usage;
 import com.badlogic.gdx.graphics.g2d.Batch;
 import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.PolygonBatch;
 import com.badlogic.gdx.graphics.g2d.PolygonRegion;
 import com.badlogic.gdx.graphics.g2d.PolygonRegion;
 import com.badlogic.gdx.graphics.g2d.TextureRegion;
 import com.badlogic.gdx.graphics.g2d.TextureRegion;
 import com.badlogic.gdx.graphics.glutils.ShaderProgram;
 import com.badlogic.gdx.graphics.glutils.ShaderProgram;
@@ -43,13 +48,18 @@ import com.badlogic.gdx.math.MathUtils;
 import com.badlogic.gdx.math.Matrix4;
 import com.badlogic.gdx.math.Matrix4;
 import com.badlogic.gdx.utils.NumberUtils;
 import com.badlogic.gdx.utils.NumberUtils;
 
 
-public class TwoColorPolygonBatch implements Batch {
+/** A batch that renders polygons and performs tinting using a light and dark color.
+ * <p>
+ * Because an additional vertex attribute is used, the {@link Batch} and {@link PolygonBatch} methods that accept float[] vertex
+ * data do not perform two color tinting. {@link #drawTwoColor(Texture, float[], int, int)} and
+ * {@link #drawTwoColor(Texture, float[], int, int, short[], int, int)} are provided to accept float[] vertex data that contains
+ * two colors per vertex. */
+public class TwoColorPolygonBatch implements PolygonBatch {
 	static final int VERTEX_SIZE = 2 + 1 + 1 + 2;
 	static final int VERTEX_SIZE = 2 + 1 + 1 + 2;
 	static final int SPRITE_SIZE = 4 * VERTEX_SIZE;
 	static final int SPRITE_SIZE = 4 * VERTEX_SIZE;
 
 
 	private final Mesh mesh;
 	private final Mesh mesh;
 	private final float[] vertices;
 	private final float[] vertices;
-	private final float[] tempSpriteVertices = new float[SPRITE_SIZE];
 	private final short[] triangles;
 	private final short[] triangles;
 	private final Matrix4 transformMatrix = new Matrix4();
 	private final Matrix4 transformMatrix = new Matrix4();
 	private final Matrix4 projectionMatrix = new Matrix4();
 	private final Matrix4 projectionMatrix = new Matrix4();
@@ -313,30 +323,6 @@ public class TwoColorPolygonBatch implements Batch {
 		this.vertexIndex = vertexIndex;
 		this.vertexIndex = vertexIndex;
 	}
 	}
 
 
-	public void draw (Texture texture, float[] polygonVertices, int verticesOffset, int verticesCount, short[] polygonTriangles,
-		int trianglesOffset, int trianglesCount) {
-		if (!drawing) throw new IllegalStateException("begin must be called before draw.");
-
-		final short[] triangles = this.triangles;
-		final float[] vertices = this.vertices;
-
-		if (texture != lastTexture) {
-			switchTexture(texture);
-		} else if (triangleIndex + trianglesCount > triangles.length || vertexIndex + verticesCount > vertices.length) //
-			flush();
-
-		int triangleIndex = this.triangleIndex;
-		final int vertexIndex = this.vertexIndex;
-		final int startVertex = vertexIndex / 6;
-
-		for (int i = trianglesOffset, n = i + trianglesCount; i < n; i++)
-			triangles[triangleIndex++] = (short)(polygonTriangles[i] + startVertex);
-		this.triangleIndex = triangleIndex;
-
-		System.arraycopy(polygonVertices, verticesOffset, vertices, vertexIndex, verticesCount);
-		this.vertexIndex += verticesCount;
-	}
-
 	@Override
 	@Override
 	public void draw (Texture texture, float x, float y, float originX, float originY, float width, float height, float scaleX,
 	public void draw (Texture texture, float x, float y, float originX, float originY, float width, float height, float scaleX,
 		float scaleY, float rotation, int srcX, int srcY, int srcWidth, int srcHeight, boolean flipX, boolean flipY) {
 		float scaleY, float rotation, int srcX, int srcY, int srcWidth, int srcHeight, boolean flipX, boolean flipY) {
@@ -746,28 +732,74 @@ public class TwoColorPolygonBatch implements Batch {
 		this.vertexIndex = idx;
 		this.vertexIndex = idx;
 	}
 	}
 
 
-	/** Draws a rectangle using the given vertices. There must be 4 vertices, each made up of 6 elements in this order: x, y,
-	 * lightColor, darkColor, u, v. The {@link #getColor()} and {@link #getDarkColor()} from the TwoColorPolygonBatch is not
+	/** Draws polygons using the given vertices and triangles. There must be 4 vertices, each made up of 6 elements in this order:
+	 * x, y, lightColor, darkColor, u, v. The {@link #getColor()} and {@link #getDarkColor()} from the TwoColorPolygonBatch is not
 	 * applied. */
 	 * applied. */
-	@Override
-	public void draw (Texture texture, float[] spriteVertices, int offset, int count) {
+	public void drawTwoColor (Texture texture, float[] polygonVertices, int verticesOffset, int verticesCount,
+		short[] polygonTriangles, int trianglesOffset, int trianglesCount) {
+		if (!drawing) throw new IllegalStateException("begin must be called before draw.");
+
+		final short[] triangles = this.triangles;
+		final float[] vertices = this.vertices;
+
+		if (texture != lastTexture) {
+			switchTexture(texture);
+		} else if (triangleIndex + trianglesCount > triangles.length || vertexIndex + verticesCount > vertices.length) //
+			flush();
+
+		int triangleIndex = this.triangleIndex;
+		final int vertexIndex = this.vertexIndex;
+		final int startVertex = vertexIndex / 6;
+
+		for (int i = trianglesOffset, n = i + trianglesCount; i < n; i++)
+			triangles[triangleIndex++] = (short)(polygonTriangles[i] + startVertex);
+		this.triangleIndex = triangleIndex;
+
+		System.arraycopy(polygonVertices, verticesOffset, vertices, vertexIndex, verticesCount);
+		this.vertexIndex += verticesCount;
+	}
+
+	/** Draws polygons using the given vertices and triangles in the {@link PolygonBatch} format. There must be 4 vertices, each
+	 * made up of 5 elements in this order: x, y, color, u, v. The {@link #getColor()} and {@link #getDarkColor()} from the
+	 * TwoColorPolygonBatch is not applied. */
+	public void draw (Texture texture, float[] polygonVertices, int verticesOffset, int verticesCount, short[] polygonTriangles,
+		int trianglesOffset, int trianglesCount) {
 		if (!drawing) throw new IllegalStateException("begin must be called before draw.");
 		if (!drawing) throw new IllegalStateException("begin must be called before draw.");
 
 
-		// odds are this is a sprite, we meed to convert it
-		if (spriteVertices.length == 20 && offset == 0 && count == 20) {
-			final float[] vertices = tempSpriteVertices;
-			int idx = 0;
-			for (int i = 0; i < 20; i += 5) {
-				vertices[idx++] = spriteVertices[i];
-				vertices[idx++] = spriteVertices[i + 1];
-				vertices[idx++] = spriteVertices[i + 2];
-				vertices[idx++] = 0; // dark
-				vertices[idx++] = spriteVertices[i + 3];
-				vertices[idx++] = spriteVertices[i + 4];
-			}
-			spriteVertices = vertices;
-			count = SPRITE_SIZE;
+		final short[] triangles = this.triangles;
+		final float[] vertices = this.vertices;
+
+		if (texture != lastTexture) {
+			switchTexture(texture);
+		} else if (triangleIndex + trianglesCount > triangles.length || vertexIndex + verticesCount / 5 * 6 > vertices.length) //
+			flush();
+
+		int triangleIndex = this.triangleIndex;
+		final int vertexIndex = this.vertexIndex;
+		final int startVertex = vertexIndex / 6;
+
+		for (int i = trianglesOffset, n = i + trianglesCount; i < n; i++)
+			triangles[triangleIndex++] = (short)(polygonTriangles[i] + startVertex);
+		this.triangleIndex = triangleIndex;
+
+		int idx = this.vertexIndex;
+		for (int i = verticesOffset, n = verticesOffset + verticesCount; i < n; i += 5) {
+			vertices[idx++] = polygonVertices[i];
+			vertices[idx++] = polygonVertices[i + 1];
+			vertices[idx++] = polygonVertices[i + 2];
+			vertices[idx++] = 0; // dark
+			vertices[idx++] = polygonVertices[i + 3];
+			vertices[idx++] = polygonVertices[i + 4];
 		}
 		}
+		this.vertexIndex = idx;
+	}
+
+	/** Draws rectangles using the given vertices. There must be 4 vertices, each made up of 6 elements in this order: x, y,
+	 * lightColor, darkColor, u, v. The {@link #getColor()} and {@link #getDarkColor()} from the TwoColorPolygonBatch is not
+	 * applied. */
+	public void drawTwoColor (Texture texture, float[] spriteVertices, int offset, int count) {
+		if (!drawing) throw new IllegalStateException("begin must be called before draw.");
+
 		final short[] triangles = this.triangles;
 		final short[] triangles = this.triangles;
 		final float[] vertices = this.vertices;
 		final float[] vertices = this.vertices;
 
 
@@ -794,6 +826,47 @@ public class TwoColorPolygonBatch implements Batch {
 		this.vertexIndex += count;
 		this.vertexIndex += count;
 	}
 	}
 
 
+	/** Draws rectangles using the given vertices in the {@link Batch} format. There must be 4 vertices, each made up of 5 elements
+	 * in this order: x, y, color, u, v. The {@link #getColor()} and {@link #getDarkColor()} from the TwoColorPolygonBatch is not
+	 * applied. */
+	@Override
+	public void draw (Texture texture, float[] spriteVertices, int offset, int count) {
+		if (!drawing) throw new IllegalStateException("begin must be called before draw.");
+
+		final short[] triangles = this.triangles;
+		final float[] vertices = this.vertices;
+
+		final int triangleCount = count / 20 * 6;
+		if (texture != lastTexture)
+			switchTexture(texture);
+		else if (triangleIndex + triangleCount > triangles.length || vertexIndex + count / 5 * 6 > vertices.length) //
+			flush();
+
+		final int vertexIndex = this.vertexIndex;
+		int triangleIndex = this.triangleIndex;
+		short vertex = (short)(vertexIndex / VERTEX_SIZE);
+		for (int n = triangleIndex + triangleCount; triangleIndex < n; triangleIndex += 6, vertex += 4) {
+			triangles[triangleIndex] = vertex;
+			triangles[triangleIndex + 1] = (short)(vertex + 1);
+			triangles[triangleIndex + 2] = (short)(vertex + 2);
+			triangles[triangleIndex + 3] = (short)(vertex + 2);
+			triangles[triangleIndex + 4] = (short)(vertex + 3);
+			triangles[triangleIndex + 5] = vertex;
+		}
+		this.triangleIndex = triangleIndex;
+
+		int idx = this.vertexIndex;
+		for (int i = offset, n = offset + count; i < n; i += 5) {
+			vertices[idx++] = spriteVertices[i];
+			vertices[idx++] = spriteVertices[i + 1];
+			vertices[idx++] = spriteVertices[i + 2];
+			vertices[idx++] = 0; // dark
+			vertices[idx++] = spriteVertices[i + 3];
+			vertices[idx++] = spriteVertices[i + 4];
+		}
+		this.vertexIndex = idx;
+	}
+
 	@Override
 	@Override
 	public void draw (TextureRegion region, float x, float y) {
 	public void draw (TextureRegion region, float x, float y) {
 		draw(region, x, y, region.getRegionWidth(), region.getRegionHeight());
 		draw(region, x, y, region.getRegionWidth(), region.getRegionHeight());

+ 10 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs

@@ -244,6 +244,10 @@ namespace Spine.Unity.Editor {
 			const string DEFAULT_ZSPACING_KEY = "SPINE_DEFAULT_ZSPACING";
 			const string DEFAULT_ZSPACING_KEY = "SPINE_DEFAULT_ZSPACING";
 			public static float defaultZSpacing = DEFAULT_DEFAULT_ZSPACING;
 			public static float defaultZSpacing = DEFAULT_DEFAULT_ZSPACING;
 
 
+			const bool DEFAULT_DEFAULT_INSTANTIATE_LOOP = true;
+			const string DEFAULT_INSTANTIATE_LOOP_KEY = "SPINE_DEFAULT_INSTANTIATE_LOOP";
+			public static bool defaultInstantiateLoop = DEFAULT_DEFAULT_INSTANTIATE_LOOP;
+
 			const bool DEFAULT_SHOW_HIERARCHY_ICONS = true;
 			const bool DEFAULT_SHOW_HIERARCHY_ICONS = true;
 			const string SHOW_HIERARCHY_ICONS_KEY = "SPINE_SHOW_HIERARCHY_ICONS";
 			const string SHOW_HIERARCHY_ICONS_KEY = "SPINE_SHOW_HIERARCHY_ICONS";
 			public static bool showHierarchyIcons = DEFAULT_SHOW_HIERARCHY_ICONS;
 			public static bool showHierarchyIcons = DEFAULT_SHOW_HIERARCHY_ICONS;
@@ -327,6 +331,11 @@ namespace Spine.Unity.Editor {
 				if (EditorGUI.EndChangeCheck())
 				if (EditorGUI.EndChangeCheck())
 					EditorPrefs.SetFloat(DEFAULT_ZSPACING_KEY, defaultZSpacing);
 					EditorPrefs.SetFloat(DEFAULT_ZSPACING_KEY, defaultZSpacing);
 
 
+				EditorGUI.BeginChangeCheck();
+				defaultInstantiateLoop = EditorGUILayout.Toggle(new GUIContent("Default Loop", "Spawn Spine GameObjects with loop enabled."), defaultInstantiateLoop);
+				if (EditorGUI.EndChangeCheck())
+					EditorPrefs.SetBool(DEFAULT_INSTANTIATE_LOOP_KEY, defaultInstantiateLoop);
+
 				EditorGUILayout.Space();
 				EditorGUILayout.Space();
 				EditorGUILayout.LabelField("Handles and Gizmos", EditorStyles.boldLabel);
 				EditorGUILayout.LabelField("Handles and Gizmos", EditorStyles.boldLabel);
 				EditorGUI.BeginChangeCheck();
 				EditorGUI.BeginChangeCheck();
@@ -1246,6 +1255,7 @@ namespace Spine.Unity.Editor {
 					throw e;
 					throw e;
 				}
 				}
 
 
+				newSkeletonAnimation.loop = SpineEditorUtilities.Preferences.defaultInstantiateLoop;
 				newSkeletonAnimation.skeleton.Update(0);
 				newSkeletonAnimation.skeleton.Update(0);
 				newSkeletonAnimation.state.Update(0);
 				newSkeletonAnimation.state.Update(0);
 				newSkeletonAnimation.state.Apply(newSkeletonAnimation.skeleton);
 				newSkeletonAnimation.state.Apply(newSkeletonAnimation.skeleton);

+ 1 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs

@@ -195,6 +195,7 @@ namespace Spine.Unity.Editor {
 			skin = skin ?? data.DefaultSkin ?? data.Skins.Items[0];
 			skin = skin ?? data.DefaultSkin ?? data.Skins.Items[0];
 			graphic.MeshGenerator.settings.zSpacing = SpineEditorUtilities.Preferences.defaultZSpacing;
 			graphic.MeshGenerator.settings.zSpacing = SpineEditorUtilities.Preferences.defaultZSpacing;
 
 
+			graphic.startingLoop = SpineEditorUtilities.Preferences.defaultInstantiateLoop;
 			graphic.Initialize(false);
 			graphic.Initialize(false);
 			if (skin != null) graphic.Skeleton.SetSkin(skin);
 			if (skin != null) graphic.Skeleton.SetSkin(skin);
 			graphic.initialSkinName = skin.Name;
 			graphic.initialSkinName = skin.Name;

+ 3 - 3
spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs

@@ -258,12 +258,12 @@ namespace Spine.Unity.Editor {
 					GameObject go = skeletonUtility.SpawnBoneRecursively(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
 					GameObject go = skeletonUtility.SpawnBoneRecursively(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
 					SkeletonUtilityBone[] newUtilityBones = go.GetComponentsInChildren<SkeletonUtilityBone>();
 					SkeletonUtilityBone[] newUtilityBones = go.GetComponentsInChildren<SkeletonUtilityBone>();
 					foreach (SkeletonUtilityBone utilBone in newUtilityBones)
 					foreach (SkeletonUtilityBone utilBone in newUtilityBones)
-						SkeletonGameObjectsInspector.AttachIcon(utilBone);
+						SkeletonUtilityInspector.AttachIcon(utilBone);
 				}
 				}
 			} else {
 			} else {
 				var bone = (Bone)obj;
 				var bone = (Bone)obj;
 				GameObject go = skeletonUtility.SpawnBone(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
 				GameObject go = skeletonUtility.SpawnBone(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
-				SkeletonGameObjectsInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>());
+				SkeletonUtilityInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>());
 				Selection.activeGameObject = go;
 				Selection.activeGameObject = go;
 				EditorGUIUtility.PingObject(go);
 				EditorGUIUtility.PingObject(go);
 			}
 			}
@@ -272,7 +272,7 @@ namespace Spine.Unity.Editor {
 		void SpawnOverride () {
 		void SpawnOverride () {
 			GameObject go = skeletonUtility.SpawnBone(utilityBone.bone, utilityBone.transform.parent, SkeletonUtilityBone.Mode.Override, utilityBone.position, utilityBone.rotation, utilityBone.scale);
 			GameObject go = skeletonUtility.SpawnBone(utilityBone.bone, utilityBone.transform.parent, SkeletonUtilityBone.Mode.Override, utilityBone.position, utilityBone.rotation, utilityBone.scale);
 			go.name = go.name + " [Override]";
 			go.name = go.name + " [Override]";
-			SkeletonGameObjectsInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>());
+			SkeletonUtilityInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>());
 			Selection.activeGameObject = go;
 			Selection.activeGameObject = go;
 			EditorGUIUtility.PingObject(go);
 			EditorGUIUtility.PingObject(go);
 		}
 		}

+ 16 - 16
spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs

@@ -41,17 +41,17 @@ namespace Spine.Unity.Editor {
 	using Icons = SpineEditorUtilities.Icons;
 	using Icons = SpineEditorUtilities.Icons;
 
 
 	[CustomEditor(typeof(SkeletonUtility))]
 	[CustomEditor(typeof(SkeletonUtility))]
-	public class SkeletonGameObjectsInspector : UnityEditor.Editor {
+	public class SkeletonUtilityInspector : UnityEditor.Editor {
 
 
-		SkeletonUtility skeletonGameObjects;
+		SkeletonUtility skeletonUtility;
 		Skeleton skeleton;
 		Skeleton skeleton;
 		SkeletonRenderer skeletonRenderer;
 		SkeletonRenderer skeletonRenderer;
 		bool isPrefab;
 		bool isPrefab;
 		readonly GUIContent SpawnHierarchyButtonLabel = new GUIContent("Spawn Hierarchy", Icons.skeleton);
 		readonly GUIContent SpawnHierarchyButtonLabel = new GUIContent("Spawn Hierarchy", Icons.skeleton);
 
 
 		void OnEnable () {
 		void OnEnable () {
-			skeletonGameObjects = (SkeletonUtility)target;
-			skeletonRenderer = skeletonGameObjects.GetComponent<SkeletonRenderer>();
+			skeletonUtility = (SkeletonUtility)target;
+			skeletonRenderer = skeletonUtility.GetComponent<SkeletonRenderer>();
 			skeleton = skeletonRenderer.Skeleton;
 			skeleton = skeletonRenderer.Skeleton;
 
 
 			if (skeleton == null) {
 			if (skeleton == null) {
@@ -78,7 +78,7 @@ namespace Spine.Unity.Editor {
 
 
 			EditorGUILayout.PropertyField(serializedObject.FindProperty("boneRoot"), SpineInspectorUtility.TempContent("Skeleton Root"));
 			EditorGUILayout.PropertyField(serializedObject.FindProperty("boneRoot"), SpineInspectorUtility.TempContent("Skeleton Root"));
 
 
-			bool hasRootBone = skeletonGameObjects.boneRoot != null;
+			bool hasRootBone = skeletonUtility.boneRoot != null;
 
 
 			if (!hasRootBone)
 			if (!hasRootBone)
 				EditorGUILayout.HelpBox("No hierarchy found. Use Spawn Hierarchy to generate GameObjects for bones.", MessageType.Info);
 				EditorGUILayout.HelpBox("No hierarchy found. Use Spawn Hierarchy to generate GameObjects for bones.", MessageType.Info);
@@ -90,9 +90,9 @@ namespace Spine.Unity.Editor {
 
 
 			if (hasRootBone) {
 			if (hasRootBone) {
 				if (SpineInspectorUtility.CenteredButton(new GUIContent("Remove Hierarchy"))) {
 				if (SpineInspectorUtility.CenteredButton(new GUIContent("Remove Hierarchy"))) {
-					Undo.RegisterCompleteObjectUndo(skeletonGameObjects, "Remove Hierarchy");
-					Undo.DestroyObjectImmediate(skeletonGameObjects.boneRoot.gameObject);
-					skeletonGameObjects.boneRoot = null;
+					Undo.RegisterCompleteObjectUndo(skeletonUtility, "Remove Hierarchy");
+					Undo.DestroyObjectImmediate(skeletonUtility.boneRoot.gameObject);
+					skeletonUtility.boneRoot = null;
 				}
 				}
 			}
 			}
 		}
 		}
@@ -134,23 +134,23 @@ namespace Spine.Unity.Editor {
 		}
 		}
 
 
 		void SpawnFollowHierarchy () {
 		void SpawnFollowHierarchy () {
-			Selection.activeGameObject = skeletonGameObjects.SpawnHierarchy(SkeletonUtilityBone.Mode.Follow, true, true, true);
-			AttachIconsToChildren(skeletonGameObjects.boneRoot);
+			Selection.activeGameObject = skeletonUtility.SpawnHierarchy(SkeletonUtilityBone.Mode.Follow, true, true, true);
+			AttachIconsToChildren(skeletonUtility.boneRoot);
 		}
 		}
 
 
 		void SpawnFollowHierarchyRootOnly () {
 		void SpawnFollowHierarchyRootOnly () {
-			Selection.activeGameObject = skeletonGameObjects.SpawnRoot(SkeletonUtilityBone.Mode.Follow, true, true, true);
-			AttachIconsToChildren(skeletonGameObjects.boneRoot);
+			Selection.activeGameObject = skeletonUtility.SpawnRoot(SkeletonUtilityBone.Mode.Follow, true, true, true);
+			AttachIconsToChildren(skeletonUtility.boneRoot);
 		}
 		}
 
 
 		void SpawnOverrideHierarchy () {
 		void SpawnOverrideHierarchy () {
-			Selection.activeGameObject = skeletonGameObjects.SpawnHierarchy(SkeletonUtilityBone.Mode.Override, true, true, true);
-			AttachIconsToChildren(skeletonGameObjects.boneRoot);
+			Selection.activeGameObject = skeletonUtility.SpawnHierarchy(SkeletonUtilityBone.Mode.Override, true, true, true);
+			AttachIconsToChildren(skeletonUtility.boneRoot);
 		}
 		}
 
 
 		void SpawnOverrideHierarchyRootOnly () {
 		void SpawnOverrideHierarchyRootOnly () {
-			Selection.activeGameObject = skeletonGameObjects.SpawnRoot(SkeletonUtilityBone.Mode.Override, true, true, true);
-			AttachIconsToChildren(skeletonGameObjects.boneRoot);
+			Selection.activeGameObject = skeletonUtility.SpawnRoot(SkeletonUtilityBone.Mode.Override, true, true, true);
+			AttachIconsToChildren(skeletonUtility.boneRoot);
 		}
 		}
 	}
 	}
 
 

+ 7 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs

@@ -172,6 +172,13 @@ namespace Spine.Unity {
 			if (skeleton != null) skeleton.SetToSetupPose();
 			if (skeleton != null) skeleton.SetToSetupPose();
 		}
 		}
 
 
+		/// <summary>
+		/// Sets a minimum buffer size for the internal MeshGenerator to prevent excess allocations during animation.
+		/// </summary>
+		public void EnsureMeshGeneratorCapacity (int minimumVertexCount) {
+			meshGenerator.EnsureVertexCapacity(minimumVertexCount);
+		}
+
 		/// <summary>
 		/// <summary>
 		/// Initialize this component. Attempts to load the SkeletonData and creates the internal Skeleton object and buffers.</summary>
 		/// Initialize this component. Attempts to load the SkeletonData and creates the internal Skeleton object and buffers.</summary>
 		/// <param name="overwrite">If set to <c>true</c>, it will overwrite internal objects if they were already generated. Otherwise, the initialized component will ignore subsequent calls to initialize.</param>
 		/// <param name="overwrite">If set to <c>true</c>, it will overwrite internal objects if they were already generated. Otherwise, the initialized component will ignore subsequent calls to initialize.</param>

+ 47 - 11
spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs

@@ -455,12 +455,13 @@ namespace Spine.Unity {
 		public void AddSubmesh (SubmeshInstruction instruction, bool updateTriangles = true) {
 		public void AddSubmesh (SubmeshInstruction instruction, bool updateTriangles = true) {
 			var settings = this.settings;
 			var settings = this.settings;
 
 
-			if (submeshes.Count - 1 < submeshIndex) {
-				submeshes.Resize(submeshIndex + 1);
-				if (submeshes.Items[submeshIndex] == null)
-					submeshes.Items[submeshIndex] = new ExposedList<int>();
-			}
+			int newSubmeshCount = submeshIndex + 1;
+			if (submeshes.Items.Length < newSubmeshCount)
+				submeshes.Resize(newSubmeshCount);
+			submeshes.Count = newSubmeshCount;
 			var submesh = submeshes.Items[submeshIndex];
 			var submesh = submeshes.Items[submeshIndex];
+			if (submesh == null)
+				submeshes.Items[submeshIndex] = submesh = new ExposedList<int>();
 			submesh.Clear(false);
 			submesh.Clear(false);
 
 
 			var skeleton = instruction.skeleton;
 			var skeleton = instruction.skeleton;
@@ -570,10 +571,13 @@ namespace Spine.Unity {
 					// Add data to vertex buffers
 					// Add data to vertex buffers
 					{
 					{
 						int newVertexCount = ovc + attachmentVertexCount;
 						int newVertexCount = ovc + attachmentVertexCount;
-						if (newVertexCount > vertexBuffer.Items.Length) { // Manual ExposedList.Resize()
-							Array.Resize(ref vertexBuffer.Items, newVertexCount);
-							Array.Resize(ref uvBuffer.Items, newVertexCount);
-							Array.Resize(ref colorBuffer.Items, newVertexCount);
+						int oldArraySize = vertexBuffer.Items.Length;
+						if (newVertexCount > oldArraySize) {
+							int newArraySize = (int)(oldArraySize * 1.3f);
+							if (newArraySize < newVertexCount) newArraySize = newVertexCount;
+							Array.Resize(ref vertexBuffer.Items, newArraySize);
+							Array.Resize(ref uvBuffer.Items, newArraySize);
+							Array.Resize(ref colorBuffer.Items, newArraySize);
 						}
 						}
 						vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = newVertexCount;
 						vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = newVertexCount;
 					}
 					}
@@ -1057,6 +1061,38 @@ namespace Spine.Unity {
 		}
 		}
 		#endregion
 		#endregion
 
 
+		public void EnsureVertexCapacity (int minimumVertexCount, bool inlcudeTintBlack = false, bool includeTangents = false, bool includeNormals = false) {
+			if (minimumVertexCount > vertexBuffer.Items.Length) {
+				Array.Resize(ref vertexBuffer.Items, minimumVertexCount);
+				Array.Resize(ref uvBuffer.Items, minimumVertexCount);
+				Array.Resize(ref colorBuffer.Items, minimumVertexCount);
+
+				if (inlcudeTintBlack) {
+					if (uv2 == null) {
+						uv2 = new ExposedList<Vector2>(minimumVertexCount);
+						uv3 = new ExposedList<Vector2>(minimumVertexCount);
+					}
+					uv2.Resize(minimumVertexCount);
+					uv3.Resize(minimumVertexCount);
+				}
+
+				if (includeNormals) {
+					if (normals == null)
+						normals = new Vector3[minimumVertexCount];
+					else
+						Array.Resize(ref normals, minimumVertexCount);
+
+				}
+
+				if (includeTangents) {
+					if (tangents == null)
+						tangents = new Vector4[minimumVertexCount];
+					else
+						Array.Resize(ref tangents, minimumVertexCount);
+				}
+			}
+		}
+
 		public void TrimExcess () {
 		public void TrimExcess () {
 			vertexBuffer.TrimExcess();
 			vertexBuffer.TrimExcess();
 			uvBuffer.TrimExcess();
 			uvBuffer.TrimExcess();
@@ -1437,13 +1473,13 @@ namespace Spine.Unity {
 			this.hasActiveClipping = other.hasActiveClipping;
 			this.hasActiveClipping = other.hasActiveClipping;
 			this.rawVertexCount = other.rawVertexCount;
 			this.rawVertexCount = other.rawVertexCount;
 			this.attachments.Clear(false);
 			this.attachments.Clear(false);
-			this.attachments.GrowIfNeeded(other.attachments.Capacity);
+			this.attachments.EnsureCapacity(other.attachments.Capacity);
 			this.attachments.Count = other.attachments.Count;
 			this.attachments.Count = other.attachments.Count;
 			other.attachments.CopyTo(this.attachments.Items);
 			other.attachments.CopyTo(this.attachments.Items);
 			#endif
 			#endif
 
 
 			this.submeshInstructions.Clear(false);
 			this.submeshInstructions.Clear(false);
-			this.submeshInstructions.GrowIfNeeded(other.submeshInstructions.Capacity);
+			this.submeshInstructions.EnsureCapacity(other.submeshInstructions.Capacity);
 			this.submeshInstructions.Count = other.submeshInstructions.Count;
 			this.submeshInstructions.Count = other.submeshInstructions.Count;
 			other.submeshInstructions.CopyTo(this.submeshInstructions.Items);
 			other.submeshInstructions.CopyTo(this.submeshInstructions.Items);
 		}
 		}

+ 105 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Shaders/Spine-Special-Skeleton-Grayscale.shader

@@ -0,0 +1,105 @@
+// - Unlit
+// - Premultiplied Alpha Blending (Optional straight alpha input)
+// - Double-sided, no depth
+
+Shader "Spine/Special/Skeleton Grayscale" {
+	Properties {
+		_GrayPhase ("Phase", Range(0, 1)) = 1
+		[NoScaleOffset] _MainTex ("MainTex", 2D) = "white" {}
+		_Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
+		[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
+	}
+	SubShader {
+		Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
+		Blend One OneMinusSrcAlpha
+		Cull Off
+		ZWrite Off
+		Lighting Off
+
+		Pass {
+			CGPROGRAM
+			#pragma shader_feature _ _STRAIGHT_ALPHA_INPUT
+			#pragma vertex vert
+			#pragma fragment frag
+			#include "UnityCG.cginc"
+			sampler2D _MainTex;
+			float _GrayPhase;
+
+			struct VertexInput {
+				float4 vertex : POSITION;
+				float2 uv : TEXCOORD0;
+				float4 vertexColor : COLOR;
+			};
+
+			struct VertexOutput {
+				float4 pos : SV_POSITION;
+				float2 uv : TEXCOORD0;
+				float4 vertexColor : COLOR;
+			};
+
+			VertexOutput vert (VertexInput v) {
+				VertexOutput o = (VertexOutput)0;
+				o.uv = v.uv;
+				o.vertexColor = v.vertexColor;
+				o.pos = UnityObjectToClipPos(v.vertex);
+				return o;
+			}
+
+			float4 frag (VertexOutput i) : COLOR {
+				float4 rawColor = tex2D(_MainTex,i.uv);
+				float finalAlpha = (rawColor.a * i.vertexColor.a);
+
+				#if defined(_STRAIGHT_ALPHA_INPUT)
+				rawColor.rgb *= rawColor.a;
+				#endif
+
+				rawColor.rgb *= i.vertexColor.rgb;
+
+				float3 finalColor = lerp(rawColor.rgb, dot(rawColor.rgb, float3(0.3, 0.59, 0.11)), _GrayPhase);
+				return fixed4(finalColor, finalAlpha);
+			}
+			ENDCG
+		}
+
+		Pass {
+			Name "Caster"
+			Tags { "LightMode"="ShadowCaster" }
+			Offset 1, 1
+			ZWrite On
+			ZTest LEqual
+
+			Fog { Mode Off }
+			Cull Off
+			Lighting Off
+
+			CGPROGRAM
+			#pragma vertex vert
+			#pragma fragment frag
+			#pragma multi_compile_shadowcaster
+			#pragma fragmentoption ARB_precision_hint_fastest
+			#include "UnityCG.cginc"
+			sampler2D _MainTex;
+			fixed _Cutoff;
+
+			struct VertexOutput { 
+				V2F_SHADOW_CASTER;
+				float2 uv : TEXCOORD1;
+			};
+
+			VertexOutput vert (appdata_base v) {
+				VertexOutput o;
+				o.uv = v.texcoord;
+				TRANSFER_SHADOW_CASTER(o)
+				return o;
+			}
+
+			float4 frag (VertexOutput i) : COLOR {
+				fixed4 texcol = tex2D(_MainTex, i.uv);
+				clip(texcol.a - _Cutoff);
+				SHADOW_CASTER_FRAGMENT(i)
+			}
+			ENDCG
+		}
+	}
+	FallBack "Diffuse"
+}

+ 9 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Shaders/Spine-Special-Skeleton-Grayscale.shader.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: ea7e7c05f36541b4bb280f98ebda8ba1
+timeCreated: 1492385797
+licenseType: Free
+ShaderImporter:
+  defaultTextures: []
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 2 - 1
spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs

@@ -557,7 +557,8 @@ namespace Spine {
 
 
 			var drawOrder = skeleton.drawOrder;
 			var drawOrder = skeleton.drawOrder;
 			drawOrder.Clear(false);
 			drawOrder.Clear(false);
-			drawOrder.GrowIfNeeded(n);
+			drawOrder.EnsureCapacity(n);
+			drawOrder.Count = n;
 			System.Array.Copy(slotsItems, drawOrder.Items, n);
 			System.Array.Copy(slotsItems, drawOrder.Items, n);
 		}
 		}