Răsfoiți Sursa

[c] Implemented two color tinting. We use our own batching/shader, see SkeletonTwoColorBatch. Currently, every two color tinted skeleton is its own batch. Enable two color tinting by SkeletonRenderer::setTwoColorTint(true) for a specific skeleton instance

badlogic 8 ani în urmă
părinte
comite
1be3c1a6b4

+ 25 - 8
spine-c/spine-c/src/spine/SkeletonBinary.c

@@ -187,6 +187,7 @@ static void readColor (_dataInput* input, float *r, float *g, float *b, float *a
 
 #define SLOT_ATTACHMENT 0
 #define SLOT_COLOR 1
+#define SLOT_TWO_COLOR 2
 
 #define PATH_POSITION 0
 #define PATH_SPACING 1
@@ -261,6 +262,20 @@ static spAnimation* _spSkeletonBinary_readAnimation (spSkeletonBinary* self, con
 			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 = readString(input);
+						/* TODO Avoid copying of attachmentName inside */
+						spAttachmentTimeline_setFrame(timeline, frameIndex, time, attachmentName);
+						FREE(attachmentName);
+					}
+					kv_push(spTimeline*, timelines, SUPER(timeline));
+					duration = MAX(duration, timeline->frames[frameCount - 1]);
+					break;
+				}
 				case SLOT_COLOR: {
 					spColorTimeline* timeline = spColorTimeline_create(frameCount);
 					timeline->slotIndex = slotIndex;
@@ -275,18 +290,20 @@ static spAnimation* _spSkeletonBinary_readAnimation (spSkeletonBinary* self, con
 					duration = MAX(duration, timeline->frames[(frameCount - 1) * COLOR_ENTRIES]);
 					break;
 				}
-				case SLOT_ATTACHMENT: {
-					spAttachmentTimeline* timeline = spAttachmentTimeline_create(frameCount);
+				case SLOT_TWO_COLOR: {
+					spTwoColorTimeline* timeline = spTwoColorTimeline_create(frameCount);
 					timeline->slotIndex = slotIndex;
 					for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
 						float time = readFloat(input);
-						const char* attachmentName = readString(input);
-						/* TODO Avoid copying of attachmentName inside */
-						spAttachmentTimeline_setFrame(timeline, frameIndex, time, attachmentName);
-						FREE(attachmentName);
+						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);
 					}
-					kv_push(spTimeline*, timelines, SUPER(timeline));
-					duration = MAX(duration, timeline->frames[frameCount - 1]);
+					kv_push(spTimeline*, timelines, SUPER(SUPER(timeline)));
+					duration = MAX(duration, timeline->frames[(frameCount - 1) * TWOCOLOR_ENTRIES]);
 					break;
 				}
 				default: {

+ 3 - 3
spine-c/spine-c/src/spine/SkeletonJson.c

@@ -93,7 +93,7 @@ static float toColor (const char* value, int index) {
 	char *error;
 	int color;
 
-	if (strlen(value) != 8) return -1;
+	if (strlen(value) / 2 < index) return -1;
 	value += index * 2;
 
 	digits[0] = *value;
@@ -217,8 +217,8 @@ static spAnimation* _spSkeletonJson_readAnimation (spSkeletonJson* self, Json* r
 				timeline->slotIndex = slotIndex;
 
 				for (valueMap = timelineMap->child, frameIndex = 0; valueMap; valueMap = valueMap->next, ++frameIndex) {
-					const char* s = Json_getString(valueMap, "color", 0);
-					const char* ds = Json_getString(valueMap, "color", 0);
+					const char* s = Json_getString(valueMap, "light", 0);
+					const char* ds = Json_getString(valueMap, "dark", 0);
 					spTwoColorTimeline_setFrame(timeline, frameIndex, Json_getFloat(valueMap, "time", 0), toColor(s, 0), toColor(s, 1), toColor(s, 2),
 											 toColor(s, 3), toColor(ds, 0), toColor(ds, 1), toColor(ds, 2));
 					readCurve(valueMap, SUPER(timeline), frameIndex);

+ 8 - 0
spine-cocos2dx/example/proj.ios_mac/spine-cocos2d-x.xcodeproj/project.pbxproj

@@ -46,6 +46,8 @@
 		521A8E6519F0C34300D177D7 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 521A8E6319F0C34300D177D7 /* [email protected] */; };
 		52B47A471A53D09C004E4C60 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52B47A461A53D09B004E4C60 /* Security.framework */; };
 		7602C5551D7DAA1300C7C674 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7602C5541D7DAA1300C7C674 /* CoreText.framework */; };
+		76A45BDE1E64396800745AA1 /* SkeletonTwoColorBatch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 76A45BDC1E64396800745AA1 /* SkeletonTwoColorBatch.cpp */; };
+		76A45BDF1E64396800745AA1 /* SkeletonTwoColorBatch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 76A45BDC1E64396800745AA1 /* SkeletonTwoColorBatch.cpp */; };
 		76AAA3C01D180F7C00C54FCB /* AppDelegate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 76AAA3B31D180F7C00C54FCB /* AppDelegate.cpp */; };
 		76AAA3C11D180F7C00C54FCB /* BatchingExample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 76AAA3B61D180F7C00C54FCB /* BatchingExample.cpp */; };
 		76AAA3C21D180F7C00C54FCB /* GoblinsExample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 76AAA3B81D180F7C00C54FCB /* GoblinsExample.cpp */; };
@@ -251,6 +253,8 @@
 		521A8E6319F0C34300D177D7 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
 		52B47A461A53D09B004E4C60 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.1.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; };
 		7602C5541D7DAA1300C7C674 /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/CoreText.framework; sourceTree = DEVELOPER_DIR; };
+		76A45BDC1E64396800745AA1 /* SkeletonTwoColorBatch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SkeletonTwoColorBatch.cpp; path = ../../src/spine/SkeletonTwoColorBatch.cpp; sourceTree = "<group>"; };
+		76A45BDD1E64396800745AA1 /* SkeletonTwoColorBatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SkeletonTwoColorBatch.h; path = ../../src/spine/SkeletonTwoColorBatch.h; sourceTree = "<group>"; };
 		76AAA3B31D180F7C00C54FCB /* AppDelegate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AppDelegate.cpp; sourceTree = "<group>"; };
 		76AAA3B41D180F7C00C54FCB /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
 		76AAA3B51D180F7C00C54FCB /* AppMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppMacros.h; sourceTree = "<group>"; };
@@ -573,6 +577,8 @@
 		76AAA3FF1D18102C00C54FCB /* spine-cocos2dx */ = {
 			isa = PBXGroup;
 			children = (
+				76A45BDC1E64396800745AA1 /* SkeletonTwoColorBatch.cpp */,
+				76A45BDD1E64396800745AA1 /* SkeletonTwoColorBatch.h */,
 				76AAA4001D18106000C54FCB /* AttachmentVertices.cpp */,
 				76AAA4011D18106000C54FCB /* AttachmentVertices.h */,
 				76AAA4021D18106000C54FCB /* Cocos2dAttachmentLoader.cpp */,
@@ -775,6 +781,7 @@
 				503AE10217EB989F00D1A890 /* RootViewController.mm in Sources */,
 				503AE10117EB989F00D1A890 /* main.m in Sources */,
 				76F28CCB1DEC7EBB00CDE54D /* Skin.c in Sources */,
+				76A45BDE1E64396800745AA1 /* SkeletonTwoColorBatch.cpp in Sources */,
 				76F28CBF1DEC7EBB00CDE54D /* IkConstraintData.c in Sources */,
 				76F28CC61DEC7EBB00CDE54D /* Skeleton.c in Sources */,
 				76AAA4101D18106000C54FCB /* SkeletonRenderer.cpp in Sources */,
@@ -840,6 +847,7 @@
 				76AAA4121D18119F00C54FCB /* AttachmentVertices.cpp in Sources */,
 				76AAA4131D18119F00C54FCB /* AttachmentVertices.h in Sources */,
 				76AAA4141D18119F00C54FCB /* Cocos2dAttachmentLoader.cpp in Sources */,
+				76A45BDF1E64396800745AA1 /* SkeletonTwoColorBatch.cpp in Sources */,
 				76AAA4151D18119F00C54FCB /* Cocos2dAttachmentLoader.h in Sources */,
 				76AAA4161D18119F00C54FCB /* SkeletonAnimation.cpp in Sources */,
 				76AAA4171D18119F00C54FCB /* SkeletonAnimation.h in Sources */,

+ 2 - 2
spine-cocos2dx/src/spine/SkeletonBatch.cpp

@@ -97,11 +97,11 @@ cocos2d::V3F_C4B_T2F* SkeletonBatch::allocateVertices(uint32_t numVertices) {
 	return vertices;
 }
 	
-const cocos2d::TrianglesCommand::Triangles& SkeletonBatch::addCommand(cocos2d::Renderer* renderer, float globalOrder, GLuint textureID, cocos2d::GLProgramState* glProgramState, cocos2d::BlendFunc blendType, const cocos2d::TrianglesCommand::Triangles& triangles, const cocos2d::Mat4& mv, uint32_t flags) {
+cocos2d::TrianglesCommand* SkeletonBatch::addCommand(cocos2d::Renderer* renderer, float globalOrder, GLuint textureID, cocos2d::GLProgramState* glProgramState, cocos2d::BlendFunc blendType, const cocos2d::TrianglesCommand::Triangles& triangles, const cocos2d::Mat4& mv, uint32_t flags) {
 	TrianglesCommand* command = nextFreeCommand();
 	command->init(globalOrder, textureID, glProgramState, blendType, triangles, mv, flags);
 	renderer->addCommand(command);
-	return command->getTriangles();
+	return command;
 }
 
 void SkeletonBatch::reset() {

+ 1 - 1
spine-cocos2dx/src/spine/SkeletonBatch.h

@@ -46,7 +46,7 @@ namespace spine {
         void update (float delta);
 		
 		cocos2d::V3F_C4B_T2F* allocateVertices(uint32_t numVertices);
-		const cocos2d::TrianglesCommand::Triangles& addCommand(cocos2d::Renderer* renderer, float globalOrder, GLuint textureID, cocos2d::GLProgramState* glProgramState, cocos2d::BlendFunc blendType, const cocos2d::TrianglesCommand::Triangles& triangles, const cocos2d::Mat4& mv, uint32_t flags);
+		cocos2d::TrianglesCommand* addCommand(cocos2d::Renderer* renderer, float globalOrder, GLuint textureID, cocos2d::GLProgramState* glProgramState, cocos2d::BlendFunc blendType, const cocos2d::TrianglesCommand::Triangles& triangles, const cocos2d::Mat4& mv, uint32_t flags);
         
     protected:
         SkeletonBatch ();

+ 103 - 21
spine-cocos2dx/src/spine/SkeletonRenderer.cpp

@@ -31,6 +31,7 @@
 #include <spine/SkeletonRenderer.h>
 #include <spine/extension.h>
 #include <spine/SkeletonBatch.h>
+#include <spine/SkeletonTwoColorBatch.h>
 #include <spine/AttachmentVertices.h>
 #include <spine/Cocos2dAttachmentLoader.h>
 #include <algorithm>
@@ -66,6 +67,7 @@ void SkeletonRenderer::initialize () {
 	setOpacityModifyRGB(true);
 
 	setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP));
+	setTwoColorTint(true);
 }
 
 void SkeletonRenderer::setSkeletonData (spSkeletonData *skeletonData, bool ownsSkeletonData) {
@@ -177,6 +179,8 @@ void SkeletonRenderer::update (float deltaTime) {
 
 void SkeletonRenderer::draw (Renderer* renderer, const Mat4& transform, uint32_t transformFlags) {
 	SkeletonBatch* batch = SkeletonBatch::getInstance();
+	SkeletonTwoColorBatch* twoColorBatch = SkeletonTwoColorBatch::getInstance();
+	bool isTwoColorTint = this->isTwoColorTint();
 
 	Color3B nodeColor = getColor();
 	_skeleton->color.r = nodeColor.r / (float)255;
@@ -185,42 +189,91 @@ void SkeletonRenderer::draw (Renderer* renderer, const Mat4& transform, uint32_t
 	_skeleton->color.a = getDisplayedOpacity() / (float)255;
     
     Color4F color;
+	Color4F darkColor;
 	AttachmentVertices* attachmentVertices = nullptr;
+	TwoColorTrianglesCommand* lastTwoColorTrianglesCommand = nullptr;
 	for (int i = 0, n = _skeleton->slotsCount; i < n; ++i) {
 		spSlot* slot = _skeleton->drawOrder[i];
 		if (!slot->attachment) continue;
 		
 		cocos2d::TrianglesCommand::Triangles triangles;
+		TwoColorTriangles trianglesTwoColor;
 		
 		switch (slot->attachment->type) {
 		case SP_ATTACHMENT_REGION: {
 			spRegionAttachment* attachment = (spRegionAttachment*)slot->attachment;
 			attachmentVertices = getAttachmentVertices(attachment);
-			triangles.indices = attachmentVertices->_triangles->indices;
-			triangles.indexCount = attachmentVertices->_triangles->indexCount;
-			triangles.verts = batch->allocateVertices(attachmentVertices->_triangles->vertCount);
-			triangles.vertCount = attachmentVertices->_triangles->vertCount;
-			memcpy(triangles.verts, attachmentVertices->_triangles->verts, sizeof(cocos2d::V3F_C4B_T2F) * attachmentVertices->_triangles->vertCount);
-			spRegionAttachment_computeWorldVertices(attachment, slot->bone, (float*)triangles.verts, 0, 6);
+			
+			if (!isTwoColorTint) {
+				triangles.indices = attachmentVertices->_triangles->indices;
+				triangles.indexCount = attachmentVertices->_triangles->indexCount;
+				triangles.verts = batch->allocateVertices(attachmentVertices->_triangles->vertCount);
+				triangles.vertCount = attachmentVertices->_triangles->vertCount;
+				memcpy(triangles.verts, attachmentVertices->_triangles->verts, sizeof(cocos2d::V3F_C4B_T2F) * attachmentVertices->_triangles->vertCount);
+				spRegionAttachment_computeWorldVertices(attachment, slot->bone, (float*)triangles.verts, 0, 6);
+			} else {
+				trianglesTwoColor.indices = attachmentVertices->_triangles->indices;
+				trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount;
+				trianglesTwoColor.verts = twoColorBatch->allocateVertices(attachmentVertices->_triangles->vertCount);
+				trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount;
+				for (int i = 0; i < trianglesTwoColor.vertCount; i++) {
+					trianglesTwoColor.verts[i].texCoords = attachmentVertices->_triangles->verts[i].texCoords;
+				}
+				spRegionAttachment_computeWorldVertices(attachment, slot->bone, (float*)trianglesTwoColor.verts, 0, 7);
+			}
+			
             color.r = attachment->color.r;
 			color.g = attachment->color.g;
 			color.b = attachment->color.b;
 			color.a = attachment->color.a;
+			
+			if (slot->darkColor) {
+				darkColor.r = slot->darkColor->r * 255;
+				darkColor.g = slot->darkColor->g * 255;
+				darkColor.b = slot->darkColor->b * 255;
+			} else {
+				darkColor.r = 0;
+				darkColor.g = 0;
+				darkColor.b = 0;
+			}
 			break;
 		}
 		case SP_ATTACHMENT_MESH: {
 			spMeshAttachment* attachment = (spMeshAttachment*)slot->attachment;
 			attachmentVertices = getAttachmentVertices(attachment);
-			triangles.indices = attachmentVertices->_triangles->indices;
-			triangles.indexCount = attachmentVertices->_triangles->indexCount;
-			triangles.verts = batch->allocateVertices(attachmentVertices->_triangles->vertCount);
-			triangles.vertCount = attachmentVertices->_triangles->vertCount;
-			memcpy(triangles.verts, attachmentVertices->_triangles->verts, sizeof(cocos2d::V3F_C4B_T2F) * attachmentVertices->_triangles->vertCount);
-			spVertexAttachment_computeWorldVertices(SUPER(attachment), slot, 0, triangles.vertCount * sizeof(cocos2d::V3F_C4B_T2F) / 4, (float*)triangles.verts, 0, 6);
+			
+			if (!isTwoColorTint) {
+				triangles.indices = attachmentVertices->_triangles->indices;
+				triangles.indexCount = attachmentVertices->_triangles->indexCount;
+				triangles.verts = batch->allocateVertices(attachmentVertices->_triangles->vertCount);
+				triangles.vertCount = attachmentVertices->_triangles->vertCount;
+				memcpy(triangles.verts, attachmentVertices->_triangles->verts, sizeof(cocos2d::V3F_C4B_T2F) * attachmentVertices->_triangles->vertCount);
+				spVertexAttachment_computeWorldVertices(SUPER(attachment), slot, 0, triangles.vertCount * sizeof(cocos2d::V3F_C4B_T2F) / 4, (float*)triangles.verts, 0, 6);
+			} else {
+				trianglesTwoColor.indices = attachmentVertices->_triangles->indices;
+				trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount;
+				trianglesTwoColor.verts = twoColorBatch->allocateVertices(attachmentVertices->_triangles->vertCount);
+				trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount;
+				for (int i = 0; i < trianglesTwoColor.vertCount; i++) {
+					trianglesTwoColor.verts[i].texCoords = attachmentVertices->_triangles->verts[i].texCoords;
+				}
+				spVertexAttachment_computeWorldVertices(SUPER(attachment), slot, 0, trianglesTwoColor.vertCount * sizeof(V3F_C4B_C4B_T2F) / 4, (float*)trianglesTwoColor.verts, 0, 7);
+			}
+			
             color.r = attachment->color.r;
             color.g = attachment->color.g;
             color.b = attachment->color.b;
-            color.a = attachment->color.a;
+			color.a = attachment->color.a;
+			
+			if (slot->darkColor) {
+				darkColor.r = slot->darkColor->r * 255;
+				darkColor.g = slot->darkColor->g * 255;
+				darkColor.b = slot->darkColor->b * 255;
+			} else {
+				darkColor.r = 0;
+				darkColor.g = 0;
+				darkColor.b = 0;
+			}
 			break;
 		}
 		default:
@@ -252,16 +305,34 @@ void SkeletonRenderer::draw (Renderer* renderer, const Mat4& transform, uint32_t
 				blendFunc.dst = GL_ONE_MINUS_SRC_ALPHA;
 		}
 		
-		const cocos2d::TrianglesCommand::Triangles& batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture->getName(), _glProgramState, blendFunc, triangles, transform, transformFlags);
-        
-		for (int v = 0, vn = batchedTriangles.vertCount; v < vn; ++v) {
-			V3F_C4B_T2F* vertex = batchedTriangles.verts + v;
-            vertex->colors.r = (GLubyte)color.r;
-            vertex->colors.g = (GLubyte)color.g;
-            vertex->colors.b = (GLubyte)color.b;
-            vertex->colors.a = (GLubyte)color.a;
+		if (!isTwoColorTint) {
+			cocos2d::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture->getName(), _glProgramState, blendFunc, triangles, transform, transformFlags);
+			
+			for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v) {
+				V3F_C4B_T2F* vertex = batchedTriangles->getTriangles().verts + v;
+				vertex->colors.r = (GLubyte)color.r;
+				vertex->colors.g = (GLubyte)color.g;
+				vertex->colors.b = (GLubyte)color.b;
+				vertex->colors.a = (GLubyte)color.a;
+			}
+		} else {
+			TwoColorTrianglesCommand* batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture->getName(), _glProgramState, blendFunc, trianglesTwoColor, transform, transformFlags);
+			
+			for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v) {
+				V3F_C4B_C4B_T2F* vertex = batchedTriangles->getTriangles().verts + v;
+				vertex->color.r = (GLubyte)color.r;
+				vertex->color.g = (GLubyte)color.g;
+				vertex->color.b = (GLubyte)color.b;
+				vertex->color.a = (GLubyte)color.a;
+				vertex->color2.r = (GLubyte)darkColor.r;
+				vertex->color2.g = (GLubyte)darkColor.g;
+				vertex->color2.b = (GLubyte)darkColor.b;
+				vertex->color2.a = 1;
+			}
 		}
 	}
+	
+	if (lastTwoColorTrianglesCommand) lastTwoColorTrianglesCommand->setForceFlush(true);
 
 	if (_debugSlots || _debugBones) {
         drawDebug(renderer, transform, transformFlags);
@@ -394,6 +465,17 @@ bool SkeletonRenderer::setAttachment (const std::string& slotName, const std::st
 bool SkeletonRenderer::setAttachment (const std::string& slotName, const char* attachmentName) {
 	return spSkeleton_setAttachment(_skeleton, slotName.c_str(), attachmentName) ? true : false;
 }
+	
+void SkeletonRenderer::setTwoColorTint(bool enabled) {
+	if (enabled)
+		setGLProgramState(SkeletonTwoColorBatch::getInstance()->getTwoColorTintProgramState());
+	else
+		setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP));
+}
+
+bool SkeletonRenderer::isTwoColorTint() {
+	return getGLProgramState() == SkeletonTwoColorBatch::getInstance()->getTwoColorTintProgramState();
+}
 
 spSkeleton* SkeletonRenderer::getSkeleton () {
 	return _skeleton;

+ 5 - 0
spine-cocos2dx/src/spine/SkeletonRenderer.h

@@ -91,6 +91,11 @@ public:
 	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();
 
     // --- BlendProtocol
     virtual void setBlendFunc (const cocos2d::BlendFunc& blendFunc)override;

+ 324 - 0
spine-cocos2dx/src/spine/SkeletonTwoColorBatch.cpp

@@ -0,0 +1,324 @@
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, Esoteric Software
+ * All rights reserved.
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable, and
+ * non-transferable license to use, install, execute, and perform the Spine
+ * Runtimes software and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes 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 SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+#include <spine/SkeletonTwoColorBatch.h>
+#include "external/xxhash/xxhash.h"
+#include <spine/extension.h>
+#include <algorithm>
+
+USING_NS_CC;
+#define EVENT_AFTER_DRAW_RESET_POSITION "director_after_draw"
+using std::max;
+#define INITIAL_SIZE (10000)
+#define MAX_VERTICES 64000
+#define MAX_INDICES 64000
+
+#define STRINGIFY(A)  #A
+
+namespace spine {
+
+TwoColorTrianglesCommand::TwoColorTrianglesCommand()
+:_materialID(0)
+,_textureID(0)
+,_glProgramState(nullptr)
+,_glProgram(nullptr)
+,_blendType(BlendFunc::DISABLE)
+,_alphaTextureID(0) {
+	_type = RenderCommand::Type::CUSTOM_COMMAND;
+	func = [this]() { draw(); };
+}
+
+void TwoColorTrianglesCommand::init(float globalOrder, GLuint textureID, GLProgramState* glProgramState, BlendFunc blendType, const TwoColorTriangles& triangles, const Mat4& mv, uint32_t flags) {
+	CCASSERT(glProgramState, "Invalid GLProgramState");
+	CCASSERT(glProgramState->getVertexAttribsFlags() == 0, "No custom attributes are supported in QuadCommand");
+
+	RenderCommand::init(globalOrder, mv, flags);
+
+	_triangles = triangles;
+	if(_triangles.indexCount % 3 != 0) {
+	int count = _triangles.indexCount;
+		_triangles.indexCount = count / 3 * 3;
+		CCLOGERROR("Resize indexCount from %zd to %zd, size must be multiple times of 3", count, _triangles.indexCount);
+	}
+	_mv = mv;
+
+	if( _textureID != textureID || _blendType.src != blendType.src || _blendType.dst != blendType.dst ||
+		_glProgramState != glProgramState ||
+		_glProgram != glProgramState->getGLProgram()) {
+		_textureID = textureID;
+		_blendType = blendType;
+		_glProgramState = glProgramState;
+		_glProgram = glProgramState->getGLProgram();
+
+		generateMaterialID();
+	}
+}
+
+TwoColorTrianglesCommand::~TwoColorTrianglesCommand() {
+}
+
+void TwoColorTrianglesCommand::generateMaterialID()
+{
+	// do not batch if using custom uniforms (since we cannot batch) it
+	if(_glProgramState->getUniformCount() > 0) {
+		_materialID = Renderer::MATERIAL_ID_DO_NOT_BATCH;
+		setSkipBatching(true);
+	}
+	else {
+		int glProgram = (int)_glProgram->getProgram();
+		int intArray[4] = { glProgram, (int)_textureID, (int)_blendType.src, (int)_blendType.dst};
+		_materialID = XXH32((const void*)intArray, sizeof(intArray), 0);
+	}
+}
+
+void TwoColorTrianglesCommand::useMaterial() const
+{
+	//Set texture
+	GL::bindTexture2D(_textureID);
+	
+	if (_alphaTextureID > 0) {
+		// ANDROID ETC1 ALPHA supports.
+		GL::bindTexture2DN(1, _alphaTextureID);
+	}
+	//set blend mode
+	GL::blendFunc(_blendType.src, _blendType.dst);
+	
+	_glProgramState->apply(_mv);
+}
+	
+void TwoColorTrianglesCommand::draw() {
+	SkeletonTwoColorBatch::getInstance()->batch(this);
+}
+
+const char* TWO_COLOR_TINT_VERTEX_SHADER = STRINGIFY(
+	attribute vec4 a_position;
+	attribute vec4 a_color;
+	attribute vec4 a_color2;
+	attribute vec2 a_texCoords;
+
+	\n#ifdef GL_ES\n
+	varying lowp vec4 v_light;
+	varying lowp vec4 v_dark;
+	varying mediump vec2 v_texCoord;
+	\n#else\n
+	varying vec4 v_light;
+	varying vec4 v_dark;
+	varying vec2 v_texCoord;
+
+	\n#endif\n
+
+	void main() {
+		v_light = a_color;
+		v_dark = a_color2;
+		v_texCoord = a_texCoords;
+		gl_Position = CC_PMatrix * a_position;
+	}
+);
+
+const char* TWO_COLOR_TINT_FRAGMENT_SHADER = STRINGIFY(
+\n#ifdef GL_ES\n
+precision lowp float;
+\n#endif\n
+
+varying vec4 v_light;
+varying vec4 v_dark;
+varying vec2 v_texCoord;
+
+void main() {
+	vec4 texColor = texture2D(CC_Texture0, v_texCoord);
+	float alpha = texColor.a * v_light.a;
+	gl_FragColor.a = alpha;
+	gl_FragColor.rgb = (1.0 - texColor.rgb) * v_dark.rgb * alpha + texColor.rgb * v_light.rgb;	
+}
+);
+
+
+static SkeletonTwoColorBatch* instance = nullptr;
+
+SkeletonTwoColorBatch* SkeletonTwoColorBatch::getInstance () {
+	if (!instance) instance = new SkeletonTwoColorBatch();
+	return instance;
+}
+
+void SkeletonTwoColorBatch::destroyInstance () {
+	if (instance) {
+		delete instance;
+		instance = nullptr;
+	}
+}
+
+SkeletonTwoColorBatch::SkeletonTwoColorBatch () {
+	for (unsigned int i = 0; i < INITIAL_SIZE; i++) {
+		_commandsPool.push_back(new TwoColorTrianglesCommand());
+	}
+	
+	reset ();
+	
+	// callback after drawing is finished so we can clear out the batch state
+	// for the next frame
+	Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_AFTER_DRAW_RESET_POSITION, [this](EventCustom* eventCustom){
+		this->update(0);
+	});;
+	
+	_twoColorTintShader = cocos2d::GLProgram::createWithByteArrays(TWO_COLOR_TINT_VERTEX_SHADER, TWO_COLOR_TINT_FRAGMENT_SHADER);
+	_twoColorTintShaderState = GLProgramState::getOrCreateWithGLProgram(_twoColorTintShader);
+	_twoColorTintShaderState->retain();
+	
+	glGenBuffers(1, &_vertexBufferHandle);
+	_vertexBuffer = new V3F_C4B_C4B_T2F[MAX_VERTICES];
+	glGenBuffers(1, &_indexBufferHandle);
+	_indexBuffer = new unsigned short[MAX_INDICES];
+	_positionAttributeLocation = _twoColorTintShader->getAttribLocation("a_position");
+	_colorAttributeLocation = _twoColorTintShader->getAttribLocation("a_color");
+	_color2AttributeLocation = _twoColorTintShader->getAttribLocation("a_color2");
+	_texCoordsAttributeLocation = _twoColorTintShader->getAttribLocation("a_texCoords");
+}
+
+SkeletonTwoColorBatch::~SkeletonTwoColorBatch () {
+	Director::getInstance()->getEventDispatcher()->removeCustomEventListeners(EVENT_AFTER_DRAW_RESET_POSITION);
+	
+	for (unsigned int i = 0; i < _commandsPool.size(); i++) {
+		delete _commandsPool[i];
+		_commandsPool[i] = nullptr;
+	}
+	_twoColorTintShader->release();
+	delete _vertexBuffer;
+	delete _indexBuffer;
+}
+
+void SkeletonTwoColorBatch::update (float delta) {
+	printf("Num batches: %i\n", _numBatches);
+	reset();
+}
+
+V3F_C4B_C4B_T2F* SkeletonTwoColorBatch::allocateVertices(uint32_t numVertices) {
+	if (_vertices.size() - _numVertices < numVertices) {
+		V3F_C4B_C4B_T2F* oldData = _vertices.data();
+		_vertices.resize((_vertices.size() + numVertices) * 2 + 1);
+		V3F_C4B_C4B_T2F* newData = _vertices.data();
+		for (uint32_t i = 0; i < this->_nextFreeCommand; i++) {
+			TwoColorTrianglesCommand* command = _commandsPool[i];
+			TwoColorTriangles& triangles = (TwoColorTriangles&)command->getTriangles();
+			triangles.verts = newData + (triangles.verts - oldData);
+		}
+	}
+	
+	V3F_C4B_C4B_T2F* vertices = _vertices.data() + _numVertices;
+	_numVertices += numVertices;
+	return vertices;
+}
+
+TwoColorTrianglesCommand* SkeletonTwoColorBatch::addCommand(cocos2d::Renderer* renderer, float globalOrder, GLuint textureID, cocos2d::GLProgramState* glProgramState, cocos2d::BlendFunc blendType, const TwoColorTriangles& triangles, const cocos2d::Mat4& mv, uint32_t flags) {
+	TwoColorTrianglesCommand* command = nextFreeCommand();
+	command->init(globalOrder, textureID, glProgramState, blendType, triangles, mv, flags);
+	renderer->addCommand(command);
+	return command;
+}
+	
+void SkeletonTwoColorBatch::batch (TwoColorTrianglesCommand* command) {
+	if (_numVerticesBuffer + command->getTriangles().vertCount >= MAX_VERTICES || _numIndicesBuffer + command->getTriangles().indexCount >= MAX_INDICES) {
+		flush(_lastCommand);
+	}
+	
+	memcpy(_vertexBuffer + _numVerticesBuffer, command->getTriangles().verts, sizeof(V3F_C4B_C4B_T2F) * command->getTriangles().vertCount);
+	const Mat4& modelView = command->getModelView();
+	for (int i = _numVerticesBuffer; i < _numVerticesBuffer + command->getTriangles().vertCount; i++) {
+		modelView.transformPoint(&_vertexBuffer[i].position);
+	}
+	
+	unsigned short vertexOffset = (unsigned short)_numVerticesBuffer;
+	unsigned short* indices = command->getTriangles().indices;
+	for (int i = 0, j = _numIndicesBuffer; i < command->getTriangles().indexCount; i++, j++) {
+		_indexBuffer[j] = indices[i] + vertexOffset;
+	}
+	
+	_numVerticesBuffer += command->getTriangles().vertCount;
+	_numIndicesBuffer += command->getTriangles().indexCount;
+	
+	uint32_t materialID = command->getMaterialID();
+	
+	if ((_lastCommand && _lastCommand->getMaterialID() != materialID) || command->isForceFlush()) {
+		flush(_lastCommand);
+	}
+	_lastCommand = command;
+}
+	
+void SkeletonTwoColorBatch::flush (TwoColorTrianglesCommand* materialCommand) {
+	if (!materialCommand)
+		return;
+	
+	materialCommand->useMaterial();
+	
+	glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferHandle);
+	glBufferData(GL_ARRAY_BUFFER, sizeof(V3F_C4B_C4B_T2F) * _numVerticesBuffer , _vertexBuffer, GL_DYNAMIC_DRAW);
+	
+	glEnableVertexAttribArray(_positionAttributeLocation);
+	glEnableVertexAttribArray(_colorAttributeLocation);
+	glEnableVertexAttribArray(_color2AttributeLocation);
+	glEnableVertexAttribArray(_texCoordsAttributeLocation);
+	
+	glVertexAttribPointer(_positionAttributeLocation, 3, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_C4B_T2F), (GLvoid*)offsetof(V3F_C4B_C4B_T2F, position));
+	glVertexAttribPointer(_colorAttributeLocation, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_C4B_T2F), (GLvoid*)offsetof(V3F_C4B_C4B_T2F, color));
+	glVertexAttribPointer(_color2AttributeLocation, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_C4B_T2F), (GLvoid*)offsetof(V3F_C4B_C4B_T2F, color2));
+	glVertexAttribPointer(_texCoordsAttributeLocation, 2, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_C4B_T2F), (GLvoid*)offsetof(V3F_C4B_C4B_T2F, texCoords));
+	
+	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferHandle);
+	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * _numIndicesBuffer, _indexBuffer, GL_STATIC_DRAW);
+	
+	glDrawElements(GL_TRIANGLES, (GLsizei)_numIndicesBuffer, GL_UNSIGNED_SHORT, 0);
+	
+	glBindBuffer(GL_ARRAY_BUFFER, 0);
+	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+	
+	_numVerticesBuffer = 0;
+	_numIndicesBuffer = 0;
+	_numBatches++;
+}
+
+void SkeletonTwoColorBatch::reset() {
+	_nextFreeCommand = 0;
+	_numVertices = 0;
+	_numVerticesBuffer = 0;
+	_numIndicesBuffer = 0;
+	_lastCommand = nullptr;
+	_numBatches = 0;
+}
+
+TwoColorTrianglesCommand* SkeletonTwoColorBatch::nextFreeCommand() {
+	if (_commandsPool.size() <= _nextFreeCommand) {
+		unsigned int newSize = _commandsPool.size() * 2 + 1;
+		for (int i = _commandsPool.size();  i < newSize; i++) {
+			_commandsPool.push_back(new TwoColorTrianglesCommand());
+		}
+	}
+	TwoColorTrianglesCommand* command = _commandsPool[_nextFreeCommand++];
+	command->setForceFlush(false);
+	return command;
+}
+}

+ 162 - 0
spine-cocos2dx/src/spine/SkeletonTwoColorBatch.h

@@ -0,0 +1,162 @@
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, Esoteric Software
+ * All rights reserved.
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable, and
+ * non-transferable license to use, install, execute, and perform the Spine
+ * Runtimes software and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes 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 SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#ifndef SPINE_SKELETONTWOCOLORBATCH_H_
+#define SPINE_SKELETONTWOCOLORBATCH_H_
+
+#include <spine/spine.h>
+#include "cocos2d.h"
+#include <vector>
+
+namespace spine {
+	struct V3F_C4B_C4B_T2F {
+		cocos2d::Vec3 position;
+		cocos2d::Color4B color;
+		cocos2d::Color4B color2;
+		cocos2d::Tex2F texCoords;
+	};
+	
+	struct TwoColorTriangles {
+		V3F_C4B_C4B_T2F* verts;
+		unsigned short* indices;
+		int vertCount;
+		int indexCount;
+	};
+	
+	class TwoColorTrianglesCommand : public cocos2d::CustomCommand {
+	public:
+		TwoColorTrianglesCommand();
+		
+		~TwoColorTrianglesCommand();
+	
+		void init(float globalOrder, GLuint textureID, cocos2d::GLProgramState* glProgramState, cocos2d::BlendFunc blendType, const TwoColorTriangles& triangles, const cocos2d::Mat4& mv, uint32_t flags);
+		
+		void useMaterial() const;
+		
+		inline uint32_t getMaterialID() const { return _materialID; }
+		
+		inline GLuint getTextureID() const { return _textureID; }
+		
+		inline const TwoColorTriangles& getTriangles() const { return _triangles; }
+		
+		inline ssize_t getVertexCount() const { return _triangles.vertCount; }
+		
+		inline ssize_t getIndexCount() const { return _triangles.indexCount; }
+		
+		inline const V3F_C4B_C4B_T2F* getVertices() const { return _triangles.verts; }
+		
+		inline const unsigned short* getIndices() const { return _triangles.indices; }
+		
+		inline cocos2d::GLProgramState* getGLProgramState() const { return _glProgramState; }
+		
+		inline cocos2d::BlendFunc getBlendType() const { return _blendType; }
+		
+		inline const cocos2d::Mat4& getModelView() const { return _mv; }
+		
+		void draw ();
+		
+		void setForceFlush (bool forceFlush) { _forceFlush = forceFlush; }
+		
+		bool isForceFlush () { return _forceFlush; };
+		
+	protected:
+		void generateMaterialID();
+		uint32_t _materialID;
+		GLuint _textureID;
+		cocos2d::GLProgramState* _glProgramState;
+		cocos2d::GLProgram* _glProgram;
+		cocos2d::BlendFunc _blendType;
+		TwoColorTriangles _triangles;
+		cocos2d::Mat4 _mv;
+		GLuint _alphaTextureID;
+		bool _forceFlush;
+	};
+
+    class SkeletonTwoColorBatch {
+    public:
+        static SkeletonTwoColorBatch* getInstance ();
+
+        static void destroyInstance ();
+
+        void update (float delta);
+
+		V3F_C4B_C4B_T2F* allocateVertices(uint32_t numVertices);
+
+		TwoColorTrianglesCommand* addCommand(cocos2d::Renderer* renderer, float globalOrder, GLuint textureID, cocos2d::GLProgramState* glProgramState, cocos2d::BlendFunc blendType, const TwoColorTriangles& triangles, const cocos2d::Mat4& mv, uint32_t flags);
+
+		cocos2d::GLProgramState* getTwoColorTintProgramState () { return _twoColorTintShaderState; }
+		
+		void batch (TwoColorTrianglesCommand* command);
+		
+		void flush (TwoColorTrianglesCommand* materialCommand);
+		
+		uint32_t getNumBatches () { return _numBatches; };
+		
+    protected:
+        SkeletonTwoColorBatch ();
+        virtual ~SkeletonTwoColorBatch ();
+
+		void reset ();
+
+		TwoColorTrianglesCommand* nextFreeCommand ();
+
+		// pool of commands
+		std::vector<TwoColorTrianglesCommand*> _commandsPool;
+		uint32_t _nextFreeCommand;
+
+		// pool of vertices
+		std::vector<V3F_C4B_C4B_T2F> _vertices;
+		uint32_t _numVertices;
+		
+		// two color tint shader and state
+		cocos2d::GLProgram* _twoColorTintShader;
+		cocos2d::GLProgramState* _twoColorTintShaderState;
+		
+		// VBO handles & attribute locations
+		GLuint _vertexBufferHandle;
+		V3F_C4B_C4B_T2F* _vertexBuffer;
+		uint32_t _numVerticesBuffer;
+		GLuint _indexBufferHandle;
+		uint32_t _numIndicesBuffer;
+		unsigned short* _indexBuffer;
+		GLint _positionAttributeLocation;
+		GLint _colorAttributeLocation;
+		GLint _color2AttributeLocation;
+		GLint _texCoordsAttributeLocation;
+		
+		// last batched command, needed for flushing to set material
+		TwoColorTrianglesCommand* _lastCommand;
+		
+		// number of batches in the last frame
+		uint32_t _numBatches;
+	};
+}
+
+#endif // SPINE_SKELETONTWOCOLORBATCH_H_