Blend modes for all runtimes.

This commit is contained in:
NathanSweet 2015-04-02 14:20:20 +02:00
parent 142e770e5b
commit f8a76c6455
38 changed files with 385 additions and 131 deletions

View File

@ -54,7 +54,7 @@
{ "name": "goggles", "bone": "head", "attachment": "goggles" },
{ "name": "front_bracer", "bone": "front_bracer", "attachment": "front_bracer" },
{ "name": "front_fist", "bone": "front_fist", "attachment": "front_fist_closed" },
{ "name": "muzzle", "bone": "gunTip", "additive": true },
{ "name": "muzzle", "bone": "gunTip", "blend": "additive" },
{ "name": "head-bb", "bone": "head" }
],
"skins": {

View File

@ -0,0 +1,46 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.1
*
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to install, execute and perform the Spine Runtimes
* Software (the "Software") solely for internal use. Without the written
* permission of Esoteric Software (typically granted by licensing Spine), you
* may not (a) modify, translate, adapt or otherwise create derivative works,
* improvements of the Software or develop new applications using the Software
* or (b) remove, delete, alter or obscure any trademarks or any copyright,
* trademark, patent or other intellectual property or proprietary rights
* notices on or in the Software, including any copy thereof. Redistributions
* in binary or source form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 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.
*****************************************************************************/
package spine {
public class BlendMode {
public static const normal:BlendMode = new BlendMode(0);
public static const additive:BlendMode = new BlendMode(1);
public static const multiply:BlendMode = new BlendMode(2);
public static const screen:BlendMode = new BlendMode(3);
public var ordinal:int;
public function BlendMode (ordinal:int) {
this.ordinal = ordinal;
}
}
}

View File

@ -145,7 +145,7 @@ public class SkeletonJson {
}
slotData.attachmentName = slotMap["attachment"];
slotData.additiveBlending = slotMap["additive"];
slotData.blendMode = BlendMode[slotMap["blend"] || "normal"];
skeletonData.slots[skeletonData.slots.length] = slotData;
}

View File

@ -38,7 +38,7 @@ public class SlotData {
public var b:Number = 1;
public var a:Number = 1;
public var attachmentName:String;
public var additiveBlending:Boolean;
public var blendMode:BlendMode;
public function SlotData (name:String, boneData:BoneData) {
if (name == null) throw new ArgumentError("name cannot be null.");

View File

@ -52,6 +52,8 @@ import spine.attachments.RegionAttachment;
public class SkeletonSprite extends Sprite {
static private var tempPoint:Point = new Point();
static private var tempMatrix:Matrix = new Matrix();
static private var blendModes:Vector.<String> = new <String>[
BlendMode.NORMAL, BlendMode.ADD, BlendMode.MULTIPLY, BlendMode.SCREEN];
private var _skeleton:Skeleton;
public var timeScale:Number = 1;
@ -119,7 +121,7 @@ public class SkeletonSprite extends Sprite {
regionAttachment["wrapper"] = wrapper;
}
wrapper.blendMode = slot.data.additiveBlending ? BlendMode.ADD : BlendMode.NORMAL;
wrapper.blendMode = blendModes[slot.data.blendMode.ordinal];
var colorTransform:ColorTransform = wrapper.transform.colorTransform;
colorTransform.redMultiplier = skeleton.r * slot.r * regionAttachment.r;

View File

@ -53,7 +53,7 @@ typedef struct spSlot {
spSlot() :
data(0),
bone(0),
r(0), b(0), g(0), a(0),
r(0), g(0), b(0), a(0),
attachment(0),
attachmentVerticesCapacity(0),
attachmentVerticesCount(0),

View File

@ -37,12 +37,16 @@
extern "C" {
#endif
typedef enum {
SP_BLEND_MODE_NORMAL, SP_BLEND_MODE_ADDITIVE, SP_BLEND_MODE_MULTIPLY, SP_BLEND_MODE_SCREEN
} spBlendMode;
typedef struct spSlotData {
const char* const name;
const spBoneData* const boneData;
const char* attachmentName;
float r, g, b, a;
int/*bool*/additiveBlending;
spBlendMode blendMode;
#ifdef __cplusplus
spSlotData() :
@ -50,7 +54,7 @@ typedef struct spSlotData {
boneData(0),
attachmentName(0),
r(0), g(0), b(0), a(0),
additiveBlending(0) {
blendMode(SP_BLEND_MODE_NORMAL) {
}
#endif
} spSlotData;
@ -62,6 +66,11 @@ void spSlotData_dispose (spSlotData* self);
void spSlotData_setAttachmentName (spSlotData* self, const char* attachmentName);
#ifdef SPINE_SHORT_NAMES
typedef spBlendMode BlendMode;
#define BLEND_MODE_NORMAL SP_BLEND_MODE_NORMAL
#define BLEND_MODE_ADDITIVE SP_BLEND_MODE_ADDITIVE
#define BLEND_MODE_MULTIPLY SP_BLEND_MODE_MULTIPLY
#define BLEND_MODE_SCREEN SP_BLEND_MODE_SCREEN
typedef spSlotData SlotData;
#define SlotData_create(...) spSlotData_create(__VA_ARGS__)
#define SlotData_dispose(...) spSlotData_dispose(__VA_ARGS__)

View File

@ -519,7 +519,7 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
for (slotMap = slots->child, i = 0; slotMap; slotMap = slotMap->next, ++i) {
spSlotData* slotData;
const char* color;
Json *attachmentItem;
Json *item;
const char* boneName = Json_getString(slotMap, "bone", 0);
spBoneData* boneData = spSkeletonData_findBone(skeletonData, boneName);
@ -539,10 +539,18 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
slotData->a = toColor(color, 3);
}
attachmentItem = Json_getItem(slotMap, "attachment");
if (attachmentItem) spSlotData_setAttachmentName(slotData, attachmentItem->valueString);
item = Json_getItem(slotMap, "attachment");
if (item) spSlotData_setAttachmentName(slotData, item->valueString);
slotData->additiveBlending = Json_getInt(slotMap, "additive", 0);
item = Json_getItem(slotMap, "blend");
if (item) {
if (strcmp(item->valueString, "additive") == 0)
slotData->blendMode = SP_BLEND_MODE_ADDITIVE;
else if (strcmp(item->valueString, "multiply") == 0)
slotData->blendMode = SP_BLEND_MODE_MULTIPLY;
else if (strcmp(item->valueString, "screen") == 0)
slotData->blendMode = SP_BLEND_MODE_SCREEN;
}
skeletonData->slots[i] = slotData;
}

View File

@ -142,7 +142,7 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
_skeleton->b = nodeColor.b / (float)255;
_skeleton->a = self.opacity / (float)255;
int additive = -1;
int blendMode = -1;
ccColor4B color;
const float* uvs = 0;
int verticesCount = 0;
@ -199,10 +199,22 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
default: ;
}
if (texture) {
if (slot->data->additiveBlending != additive) {
if (slot->data->blendMode != blendMode) {
[batch flush];
ccGLBlendFunc(_blendFunc.src, slot->data->additiveBlending ? GL_ONE : _blendFunc.dst);
additive = slot->data->additiveBlending;
blendMode = slot->data->blendMode;
switch (slot->data->blendMode) {
case SP_BLEND_MODE_ADDITIVE:
ccGLBlendFunc(_premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA, GL_ONE);
break;
case SP_BLEND_MODE_MULTIPLY:
ccGLBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA);
break;
case SP_BLEND_MODE_SCREEN:
ccGLBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
break;
default:
ccGLBlendFunc(_premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
}
color.a = _skeleton->a * slot->a * a * 255;
float multiplier = _premultipliedAlpha ? color.a : 255;

View File

@ -39,10 +39,11 @@
bool _debugBones;
bool _premultipliedAlpha;
ccBlendFunc _blendFunc;
CCDrawNode *_drawNode;
CCDrawNode* _drawNode;
bool _ownsSkeletonData;
spAtlas* _atlas;
float* worldVertices;
float* _worldVertices;
CCBlendMode* screenMode;
}
+ (id) skeletonWithData:(spSkeletonData*)skeletonData ownsSkeletonData:(bool)ownsSkeletonData;

View File

@ -62,7 +62,7 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
- (void) initialize:(spSkeletonData*)skeletonData ownsSkeletonData:(bool)ownsSkeletonData {
_ownsSkeletonData = ownsSkeletonData;
worldVertices = MALLOC(float, 1000); // Max number of vertices per mesh.
_worldVertices = MALLOC(float, 1000); // Max number of vertices per mesh.
_skeleton = spSkeleton_create(skeletonData);
_rootBone = _skeleton->bones[0];
@ -74,6 +74,12 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
[self addChild:_drawNode];
[self setShader:[CCShader positionTextureColorShader]];
_premultipliedAlpha = true;
screenMode = [CCBlendMode blendModeWithOptions:@{
CCBlendFuncSrcColor: @(GL_ONE),
CCBlendFuncDstColor: @(GL_ONE_MINUS_SRC_COLOR)}
];
}
- (id) initWithData:(spSkeletonData*)skeletonData ownsSkeletonData:(bool)ownsSkeletonData {
@ -127,7 +133,7 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
if (_ownsSkeletonData) spSkeletonData_dispose(_skeleton->data);
if (_atlas) spAtlas_dispose(_atlas);
spSkeleton_dispose(_skeleton);
FREE(worldVertices);
FREE(_worldVertices);
[super dealloc];
}
@ -138,7 +144,7 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
_skeleton->b = nodeColor.blue;
_skeleton->a = self.displayedOpacity;
int additive = -1;
int blendMode = -1;
const float* uvs = 0;
int verticesCount = 0;
const int* triangles = 0;
@ -151,7 +157,7 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
switch (slot->attachment->type) {
case SP_ATTACHMENT_REGION: {
spRegionAttachment* attachment = (spRegionAttachment*)slot->attachment;
spRegionAttachment_computeWorldVertices(attachment, slot->bone, worldVertices);
spRegionAttachment_computeWorldVertices(attachment, slot->bone, _worldVertices);
texture = [self getTextureForRegion:attachment];
uvs = attachment->uvs;
verticesCount = 8;
@ -165,7 +171,7 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
}
case SP_ATTACHMENT_MESH: {
spMeshAttachment* attachment = (spMeshAttachment*)slot->attachment;
spMeshAttachment_computeWorldVertices(attachment, slot, worldVertices);
spMeshAttachment_computeWorldVertices(attachment, slot, _worldVertices);
texture = [self getTextureForMesh:attachment];
uvs = attachment->uvs;
verticesCount = attachment->verticesCount;
@ -179,7 +185,7 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
}
case SP_ATTACHMENT_SKINNED_MESH: {
spSkinnedMeshAttachment* attachment = (spSkinnedMeshAttachment*)slot->attachment;
spSkinnedMeshAttachment_computeWorldVertices(attachment, slot, worldVertices);
spSkinnedMeshAttachment_computeWorldVertices(attachment, slot, _worldVertices);
texture = [self getTextureForSkinnedMesh:attachment];
uvs = attachment->uvs;
verticesCount = attachment->uvsCount;
@ -194,22 +200,34 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
default: ;
}
if (texture) {
if (slot->data->additiveBlending != additive) {
[self setBlendMode:[CCBlendMode blendModeWithOptions:@{CCBlendFuncSrcColor: @(_blendFunc.src),CCBlendFuncDstColor: @(slot->data->additiveBlending ? GL_ONE : _blendFunc.dst)}]];
additive = slot->data->additiveBlending;
if (slot->data->blendMode != blendMode) {
blendMode = slot->data->blendMode;
switch (slot->data->blendMode) {
case SP_BLEND_MODE_ADDITIVE:
[self setBlendMode:[CCBlendMode addMode]];
break;
case SP_BLEND_MODE_MULTIPLY:
[self setBlendMode:[CCBlendMode multiplyMode]];
break;
case SP_BLEND_MODE_SCREEN:
[self setBlendMode:screenMode];
break;
default:
[self setBlendMode:_premultipliedAlpha ? [CCBlendMode premultipliedAlphaMode] : [CCBlendMode alphaMode]];
}
}
if (_premultipliedAlpha) {
a *= _skeleton->a * slot->a;
r *= _skeleton->r * slot->r * a;
g *= _skeleton->g * slot->g * a;
b *= _skeleton->b * slot->b * a;
} else {
a *= _skeleton->a * slot->a;
r *= _skeleton->r * slot->r;
g *= _skeleton->g * slot->g;
b *= _skeleton->b * slot->b;
a *= _skeleton->a * slot->a;
r *= _skeleton->r * slot->r * a;
g *= _skeleton->g * slot->g * a;
b *= _skeleton->b * slot->b * a;
} else {
a *= _skeleton->a * slot->a;
r *= _skeleton->r * slot->r;
g *= _skeleton->g * slot->g;
b *= _skeleton->b * slot->b;
}
self.texture = texture;
self.texture = texture;
CGSize size = texture.contentSize;
GLKVector2 center = GLKVector2Make(size.width / 2.0, size.height / 2.0);
GLKVector2 extents = GLKVector2Make(size.width / 2.0, size.height / 2.0);
@ -217,7 +235,7 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
CCRenderBuffer buffer = [renderer enqueueTriangles:(trianglesCount / 3) andVertexes:verticesCount withState:self.renderState globalSortOrder:0];
for (int i = 0; i * 2 < verticesCount; ++i) {
CCVertex vertex;
vertex.position = GLKVector4Make(worldVertices[i * 2], worldVertices[i * 2 + 1], 0.0, 1.0);
vertex.position = GLKVector4Make(_worldVertices[i * 2], _worldVertices[i * 2 + 1], 0.0, 1.0);
vertex.color = GLKVector4Make(r, g, b, a);
vertex.texCoord1 = GLKVector2Make(uvs[i * 2], 1 - uvs[i * 2 + 1]);
CCRenderBufferSetVertex(buffer, i, CCVertexApplyTransform(vertex, transform));
@ -236,11 +254,11 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
spSlot* slot = _skeleton->drawOrder[i];
if (!slot->attachment || slot->attachment->type != SP_ATTACHMENT_REGION) continue;
spRegionAttachment* attachment = (spRegionAttachment*)slot->attachment;
spRegionAttachment_computeWorldVertices(attachment, slot->bone, worldVertices);
points[0] = ccp(worldVertices[0], worldVertices[1]);
points[1] = ccp(worldVertices[2], worldVertices[3]);
points[2] = ccp(worldVertices[4], worldVertices[5]);
points[3] = ccp(worldVertices[6], worldVertices[7]);
spRegionAttachment_computeWorldVertices(attachment, slot->bone, _worldVertices);
points[0] = ccp(_worldVertices[0], _worldVertices[1]);
points[1] = ccp(_worldVertices[2], _worldVertices[3]);
points[2] = ccp(_worldVertices[4], _worldVertices[5]);
points[3] = ccp(_worldVertices[6], _worldVertices[7]);
[_drawNode drawPolyWithVerts:points count:4 fillColor:[CCColor clearColor] borderWidth:1 borderColor:[CCColor blueColor]];
}
}
@ -283,20 +301,20 @@ static const int quadTriangles[6] = {0, 1, 2, 2, 3, 0};
int verticesCount;
if (slot->attachment->type == SP_ATTACHMENT_REGION) {
spRegionAttachment* attachment = (spRegionAttachment*)slot->attachment;
spRegionAttachment_computeWorldVertices(attachment, slot->bone, worldVertices);
spRegionAttachment_computeWorldVertices(attachment, slot->bone, _worldVertices);
verticesCount = 8;
} else if (slot->attachment->type == SP_ATTACHMENT_MESH) {
spMeshAttachment* mesh = (spMeshAttachment*)slot->attachment;
spMeshAttachment_computeWorldVertices(mesh, slot, worldVertices);
spMeshAttachment_computeWorldVertices(mesh, slot, _worldVertices);
verticesCount = mesh->verticesCount;
} else if (slot->attachment->type == SP_ATTACHMENT_SKINNED_MESH) {
spSkinnedMeshAttachment* mesh = (spSkinnedMeshAttachment*)slot->attachment;
spSkinnedMeshAttachment_computeWorldVertices(mesh, slot, worldVertices);
spSkinnedMeshAttachment_computeWorldVertices(mesh, slot, _worldVertices);
verticesCount = mesh->uvsCount;
} else
continue;
for (int ii = 0; ii < verticesCount; ii += 2) {
float x = worldVertices[ii] * scaleX, y = worldVertices[ii + 1] * scaleY;
float x = _worldVertices[ii] * scaleX, y = _worldVertices[ii + 1] * scaleY;
minX = fmin(minX, x);
minY = fmin(minY, y);
maxX = fmax(maxX, x);

View File

@ -144,7 +144,7 @@ void SkeletonRenderer::draw () {
skeleton->b = nodeColor.b / (float)255;
skeleton->a = getDisplayedOpacity() / (float)255;
int additive = -1;
int blendMode = -1;
ccColor4B color;
const float* uvs = nullptr;
int verticesCount = 0;
@ -200,10 +200,22 @@ void SkeletonRenderer::draw () {
}
}
if (texture) {
if (slot->data->additiveBlending != additive) {
if (slot->data->blendMode != blendMode) {
batch->flush();
ccGLBlendFunc(blendFunc.src, slot->data->additiveBlending ? GL_ONE : blendFunc.dst);
additive = slot->data->additiveBlending;
blendMode = slot->data->blendMode;
switch (slot->data->blendMode) {
case SP_BLEND_MODE_ADDITIVE:
ccGLBlendFunc(premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA, GL_ONE);
break;
case SP_BLEND_MODE_MULTIPLY:
ccGLBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA);
break;
case SP_BLEND_MODE_SCREEN:
ccGLBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
break;
default:
ccGLBlendFunc(blendFunc.src, blendFunc.dst);
}
}
color.a = skeleton->a * slot->a * a * 255;
float multiplier = premultipliedAlpha ? color.a : 255;

View File

@ -158,7 +158,7 @@ void SkeletonRenderer::drawSkeleton (const Mat4 &transform, uint32_t transformFl
_skeleton->b = nodeColor.b / (float)255;
_skeleton->a = getDisplayedOpacity() / (float)255;
int additive = -1;
int blendMode = -1;
Color4B color;
const float* uvs = nullptr;
int verticesCount = 0;
@ -215,10 +215,22 @@ void SkeletonRenderer::drawSkeleton (const Mat4 &transform, uint32_t transformFl
default: ;
}
if (texture) {
if (slot->data->additiveBlending != additive) {
if (slot->data->blendMode != blendMode) {
_batch->flush();
GL::blendFunc(_blendFunc.src, slot->data->additiveBlending ? GL_ONE : _blendFunc.dst);
additive = slot->data->additiveBlending;
blendMode = slot->data->blendMode;
switch (slot->data->blendMode) {
case SP_BLEND_MODE_ADDITIVE:
GL::blendFunc(_premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA, GL_ONE);
break;
case SP_BLEND_MODE_MULTIPLY:
GL::blendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA);
break;
case SP_BLEND_MODE_SCREEN:
GL::blendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
break;
default:
GL::blendFunc(_blendFunc.src, _blendFunc.dst);
}
}
color.a = _skeleton->a * slot->a * a * 255;
float multiplier = _premultipliedAlpha ? color.a : 255;

View File

@ -52,6 +52,7 @@ spine.AnimationState = require "spine-lua.AnimationState"
spine.EventData = require "spine-lua.EventData"
spine.Event = require "spine-lua.Event"
spine.SkeletonBounds = require "spine-lua.SkeletonBounds"
spine.BlendMode = require "spine-lua.BlendMode"
spine.utils.readFile = function (fileName, base)
if not base then base = system.ResourceDirectory end
@ -125,7 +126,15 @@ function spine.Skeleton.new (skeletonData, group)
print("Error creating image: " .. attachment.name)
image = spine.Skeleton.failed
end
if slot.data.additiveBlending then image.blendMode = "add" end
if slot.data.blendMode == spine.BlendMode.normal then
image.blendMode = "normal"
elseif slot.data.blendMode == spine.BlendMode.additive then
image.blendMode = "add"
elseif slot.data.blendMode == spine.BlendMode.multiply then
image.blendMode = "multiply"
elseif slot.data.blendMode == spine.BlendMode.screen then
image.blendMode = "screen"
end
images[slot] = image
end
-- Position image based on attachment and bone.

View File

@ -64,6 +64,7 @@
<Compile Include="src\Attachments\MeshAttachment.cs" />
<Compile Include="src\Attachments\RegionAttachment.cs" />
<Compile Include="src\Attachments\SkinnedMeshAttachment.cs" />
<Compile Include="src\BlendMode.cs" />
<Compile Include="src\Bone.cs" />
<Compile Include="src\BoneData.cs" />
<Compile Include="src\AnimationState.cs" />

View File

@ -103,6 +103,7 @@
<Compile Include="src\Attachments\AttachmentType.cs" />
<Compile Include="src\Attachments\BoundingBoxAttachment.cs" />
<Compile Include="src\Attachments\RegionAttachment.cs" />
<Compile Include="src\BlendMode.cs" />
<Compile Include="src\Bone.cs" />
<Compile Include="src\BoneData.cs" />
<Compile Include="src\IkConstraint.cs" />

View File

@ -0,0 +1,35 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.1
*
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to install, execute and perform the Spine Runtimes
* Software (the "Software") solely for internal use. Without the written
* permission of Esoteric Software (typically granted by licensing Spine), you
* may not (a) modify, translate, adapt or otherwise create derivative works,
* improvements of the Software or develop new applications using the Software
* or (b) remove, delete, alter or obscure any trademarks or any copyright,
* trademark, patent or other intellectual property or proprietary rights
* notices on or in the Software, including any copy thereof. Redistributions
* in binary or source form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 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.
*****************************************************************************/
namespace Spine {
public enum BlendMode {
normal, additive, multiply, screen
}
}

View File

@ -156,7 +156,7 @@ namespace Spine {
slotData.b = ((color & 0x0000ff00) >> 8) / 255f;
slotData.a = ((color & 0x000000ff)) / 255f;
slotData.attachmentName = ReadString(input);
slotData.additiveBlending = ReadBoolean(input);
slotData.blendMode = (BlendMode)ReadInt(input, true);
skeletonData.slots.Add(slotData);
}

View File

@ -163,8 +163,10 @@ namespace Spine {
if (slotMap.ContainsKey("attachment"))
slotData.attachmentName = (String)slotMap["attachment"];
if (slotMap.ContainsKey("additive"))
slotData.additiveBlending = (bool)slotMap["additive"];
if (slotMap.ContainsKey("blend"))
slotData.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false);
else
slotData.blendMode = BlendMode.normal;
skeletonData.slots.Add(slotData);
}

View File

@ -36,7 +36,7 @@ namespace Spine {
internal BoneData boneData;
internal float r = 1, g = 1, b = 1, a = 1;
internal String attachmentName;
internal bool additiveBlending;
internal BlendMode blendMode;
public String Name { get { return name; } }
public BoneData BoneData { get { return boneData; } }
@ -46,7 +46,7 @@ namespace Spine {
public float A { get { return a; } set { a = value; } }
/// <summary>May be null.</summary>
public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } }
public bool AdditiveBlending { get { return additiveBlending; } set { additiveBlending = value; } }
public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } }
public SlotData (String name, BoneData boneData) {
if (name == null) throw new ArgumentNullException("name cannot be null.");

View File

@ -50,6 +50,13 @@ spine.BoneData.prototype = {
flipX: false, flipY: false
};
spine.BlendMode = {
normal: 0,
additive: 1,
multiply: 2,
screen: 3
};
spine.SlotData = function (name, boneData) {
this.name = name;
this.boneData = boneData;
@ -57,7 +64,7 @@ spine.SlotData = function (name, boneData) {
spine.SlotData.prototype = {
r: 1, g: 1, b: 1, a: 1,
attachmentName: null,
additiveBlending: false
blendMode: spine.BlendMode.normal
};
spine.IkConstraintData = function (name) {
@ -1766,7 +1773,7 @@ spine.SkeletonJson.prototype = {
}
slotData.attachmentName = slotMap["attachment"];
slotData.additiveBlending = slotMap["additive"] && slotMap["additive"] == "true";
slotData.blendMode = spine.AttachmentType[slotMap["blend"] || "normal"];
skeletonData.slots.push(slotData);
}

View File

@ -0,0 +1,30 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.graphics.GL20;
public enum BlendMode {
normal(GL20.GL_SRC_ALPHA, GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA), //
additive(GL20.GL_SRC_ALPHA, GL20.GL_ONE, GL20.GL_ONE), //
multiply(GL20.GL_DST_COLOR, GL20.GL_DST_COLOR, GL20.GL_ONE_MINUS_SRC_ALPHA), //
screen(GL20.GL_ONE, GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_COLOR), //
;
int source, sourcePMA, dest;
BlendMode (int source, int sourcePremultipledAlpha, int dest) {
this.source = source;
this.sourcePMA = sourcePremultipledAlpha;
this.dest = dest;
}
public int getSource (boolean premultipliedAlpha) {
return premultipliedAlpha ? sourcePMA : source;
}
public int getDest () {
return dest;
}
static public BlendMode[] values = values();
}

View File

@ -160,7 +160,7 @@ public class SkeletonBinary {
SlotData slotData = new SlotData(slotName, boneData);
Color.rgba8888ToColor(slotData.color, input.readInt());
slotData.attachmentName = input.readString();
slotData.additiveBlending = input.readBoolean();
slotData.blendMode = BlendMode.values[input.readInt(true)];
skeletonData.slots.add(slotData);
}
@ -227,7 +227,7 @@ public class SkeletonBinary {
String name = input.readString();
if (name == null) name = attachmentName;
switch (AttachmentType.values()[input.readByte()]) {
switch (AttachmentType.values[input.readByte()]) {
case region: {
String path = input.readString();
if (path == null) path = name;

View File

@ -161,9 +161,7 @@ public class SkeletonJson {
if (color != null) slotData.getColor().set(Color.valueOf(color));
slotData.attachmentName = slotMap.getString("attachment", null);
slotData.additiveBlending = slotMap.getBoolean("additive", false);
slotData.blendMode = BlendMode.valueOf(slotMap.getString("blend", BlendMode.normal.name()));
skeletonData.slots.add(slotData);
}

View File

@ -30,18 +30,16 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.SkeletonAttachment;
import com.esotericsoftware.spine.attachments.SkinnedMeshAttachment;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.utils.Array;
public class SkeletonRenderer {
static private final short[] quadTriangles = {0, 1, 2, 2, 3, 0};
@ -50,10 +48,7 @@ public class SkeletonRenderer {
@SuppressWarnings("null")
public void draw (PolygonSpriteBatch batch, Skeleton skeleton) {
boolean premultipliedAlpha = this.premultipliedAlpha;
int srcFunc = premultipliedAlpha ? GL20.GL_ONE : GL20.GL_SRC_ALPHA;
batch.setBlendFunction(srcFunc, GL20.GL_ONE_MINUS_SRC_ALPHA);
boolean additive = false;
BlendMode blendMode = null;
float[] vertices = null;
short[] triangles = null;
@ -106,12 +101,10 @@ public class SkeletonRenderer {
}
if (texture != null) {
if (slot.data.getAdditiveBlending() != additive) {
additive = !additive;
if (additive)
batch.setBlendFunction(srcFunc, GL20.GL_ONE);
else
batch.setBlendFunction(srcFunc, GL20.GL_ONE_MINUS_SRC_ALPHA);
BlendMode slotBlendMode = slot.data.getBlendMode();
if (slotBlendMode != blendMode) {
blendMode = slotBlendMode;
batch.setBlendFunction(blendMode.getSource(premultipliedAlpha), blendMode.getDest());
}
batch.draw(texture, vertices, 0, vertices.length, triangles, 0, triangles.length);
}
@ -120,10 +113,7 @@ public class SkeletonRenderer {
public void draw (Batch batch, Skeleton skeleton) {
boolean premultipliedAlpha = this.premultipliedAlpha;
int srcFunc = premultipliedAlpha ? GL20.GL_ONE : GL20.GL_SRC_ALPHA;
batch.setBlendFunction(srcFunc, GL20.GL_ONE_MINUS_SRC_ALPHA);
boolean additive = false;
BlendMode blendMode = null;
Array<Slot> drawOrder = skeleton.drawOrder;
for (int i = 0, n = drawOrder.size; i < n; i++) {
@ -133,12 +123,10 @@ public class SkeletonRenderer {
RegionAttachment regionAttachment = (RegionAttachment)attachment;
regionAttachment.updateWorldVertices(slot, premultipliedAlpha);
float[] vertices = regionAttachment.getWorldVertices();
if (slot.data.getAdditiveBlending() != additive) {
additive = !additive;
if (additive)
batch.setBlendFunction(srcFunc, GL20.GL_ONE);
else
batch.setBlendFunction(srcFunc, GL20.GL_ONE_MINUS_SRC_ALPHA);
BlendMode slotBlendMode = slot.data.getBlendMode();
if (slotBlendMode != blendMode) {
blendMode = slotBlendMode;
batch.setBlendFunction(blendMode.getSource(premultipliedAlpha), blendMode.getDest());
}
batch.draw(regionAttachment.getRegion().getTexture(), vertices, 0, 20);

View File

@ -37,7 +37,7 @@ public class SlotData {
final BoneData boneData;
final Color color = new Color(1, 1, 1, 1);
String attachmentName;
boolean additiveBlending;
BlendMode blendMode;
SlotData () {
name = null;
@ -73,12 +73,12 @@ public class SlotData {
return attachmentName;
}
public boolean getAdditiveBlending () {
return additiveBlending;
public BlendMode getBlendMode () {
return blendMode;
}
public void setAdditiveBlending (boolean additiveBlending) {
this.additiveBlending = additiveBlending;
public void setBlendMode (BlendMode blendMode) {
this.blendMode = blendMode;
}
public String toString () {

View File

@ -31,5 +31,7 @@
package com.esotericsoftware.spine.attachments;
public enum AttachmentType {
region, boundingbox, mesh, skinnedmesh
region, boundingbox, mesh, skinnedmesh;
static public AttachmentType[] values = values();
}

View File

@ -52,6 +52,7 @@ spine.AnimationState = require "spine-lua.AnimationState"
spine.EventData = require "spine-lua.EventData"
spine.Event = require "spine-lua.Event"
spine.SkeletonBounds = require "spine-lua.SkeletonBounds"
spine.BlendMode = require "spine-lua.BlendMode"
spine.utils.readFile = function (fileName, base)
local path = fileName
@ -139,10 +140,14 @@ function spine.Skeleton.new (skeletonData, group)
rotation = -rotation
end
love.graphics.setColor(r * slot.r, g * slot.g, b * slot.b, a * slot.a)
if slot.data.additiveBlending then
love.graphics.setBlendMode("additive")
else
if slot.data.blendMode == spine.BlendMode.normal then
love.graphics.setBlendMode("alpha")
elseif slot.data.blendMode == spine.BlendMode.additive then
love.graphics.setBlendMode("additive")
elseif slot.data.blendMode == spine.BlendMode.multiply then
love.graphics.setBlendMode("multiply")
elseif slot.data.blendMode == spine.BlendMode.screen then
love.graphics.setBlendMode("screen")
end
love.graphics.draw(image,
self.x + x,

37
spine-lua/BlendMode.lua Normal file
View File

@ -0,0 +1,37 @@
-------------------------------------------------------------------------------
-- Spine Runtimes Software License
-- Version 2.1
--
-- Copyright (c) 2013, Esoteric Software
-- All rights reserved.
--
-- You are granted a perpetual, non-exclusive, non-sublicensable and
-- non-transferable license to install, execute and perform the Spine Runtimes
-- Software (the "Software") solely for internal use. Without the written
-- permission of Esoteric Software (typically granted by licensing Spine), you
-- may not (a) modify, translate, adapt or otherwise create derivative works,
-- improvements of the Software or develop new applications using the Software
-- or (b) remove, delete, alter or obscure any trademarks or any copyright,
-- trademark, patent or other intellectual property or proprietary rights
-- notices on or in the Software, including any copy thereof. Redistributions
-- in binary or source form must include this license and terms.
--
-- THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
-- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-- EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-- OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 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.
-------------------------------------------------------------------------------
local BlendMode = {
normal = 0,
additive = 1,
multiply = 2,
screen = 3
}
return BlendMode

View File

@ -39,6 +39,7 @@ local IkConstraint = require "spine-lua.IkConstraint"
local EventData = require "spine-lua.EventData"
local Event = require "spine-lua.Event"
local AttachmentType = require "spine-lua.AttachmentType"
local BlendMode = require "spine-lua.BlendMode"
local SkeletonJson = {}
function SkeletonJson.new (attachmentLoader)
@ -154,7 +155,7 @@ function SkeletonJson.new (attachmentLoader)
end
slotData.attachmentName = slotMap["attachment"]
slotData.additiveBlending = slotMap["additive"]
slotData.blendMode = BlendMode[slotMap["blend"] or "normal"]
table.insert(skeletonData.slots, slotData)
skeletonData.slotNameIndices[slotData.name] = #skeletonData.slots

View File

@ -28,6 +28,8 @@
-- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
local BlendMode = require "spine-lua.BlendMode"
local SlotData = {}
function SlotData.new (name, boneData)
if not name then error("name cannot be nil", 2) end
@ -38,7 +40,7 @@ function SlotData.new (name, boneData)
boneData = boneData,
r = 1, g = 1, b = 1, a = 1,
attachmentName = nil,
additiveBlending = false
blendMode = BlendMode.normal
}
function self:setColor (r, g, b, a)

View File

@ -95,7 +95,6 @@ void SkeletonDrawable::update (float deltaTime) {
void SkeletonDrawable::draw (RenderTarget& target, RenderStates states) const {
vertexArray->clear();
states.blendMode = BlendAlpha;
sf::Vertex vertices[4];
sf::Vertex vertex;
@ -103,6 +102,25 @@ void SkeletonDrawable::draw (RenderTarget& target, RenderStates states) const {
Slot* slot = skeleton->drawOrder[i];
Attachment* attachment = slot->attachment;
if (!attachment) continue;
sf::BlendMode blend;
switch (slot->data->blendMode) {
case BLEND_MODE_ADDITIVE:
blend = BlendAdd;
break;
case BLEND_MODE_MULTIPLY:
blend = BlendMultiply;
break;
case BLEND_MODE_SCREEN: // Unsupported, fall through.
default:
blend = BlendAlpha;
}
if (states.blendMode != blend) {
target.draw(*vertexArray, states);
vertexArray->clear();
states.blendMode = blend;
}
Texture* texture = 0;
if (attachment->type == ATTACHMENT_REGION) {
RegionAttachment* regionAttachment = (RegionAttachment*)attachment;
@ -212,13 +230,6 @@ void SkeletonDrawable::draw (RenderTarget& target, RenderStates states) const {
if (texture) {
// SMFL doesn't handle batching for us, so we'll just force a single texture per skeleton.
states.texture = texture;
BlendMode blend = slot->data->additiveBlending ? BlendAdd : BlendAlpha;
if (states.blendMode != blend) {
target.draw(*vertexArray, states);
vertexArray->clear();
states.blendMode = blend;
}
}
}
target.draw(*vertexArray, states);

View File

@ -41,6 +41,9 @@ import flash.geom.Matrix;
import flash.geom.Point;
import flash.utils.Dictionary;
import spine.BlendMode;
import spine.flash.SkeletonSprite;
import starling.core.RenderSupport;
import starling.core.Starling;
import starling.display.BlendMode;
@ -61,8 +64,8 @@ internal class PolygonBatch {
private var _texture:Texture;
private var _support:RenderSupport;
private var _programBits:uint;
private var _blendMode:String;
private var _additive:Boolean;
private var _blendModeNormal:String;
private var _blendMode:spine.BlendMode;
private var _alpha:Number;
private var _verticesCount:int;
@ -88,11 +91,11 @@ internal class PolygonBatch {
_support = support;
_alpha = alpha;
_programBits = 0xffffffff;
_additive = false;
_blendMode = null;
support.finishQuadBatch();
support.blendMode = blendMode;
_blendMode = support.blendMode;
_blendModeNormal = support.blendMode;
var context:Context3D = Starling.context;
context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 1, support.mvpMatrix3D, true);
@ -115,11 +118,14 @@ internal class PolygonBatch {
}
public function add (texture:Texture, vertices:Vector.<Number>, vl:int, uvs:Vector.<Number>, triangles:Vector.<uint>,
r:Number, g:Number, b:Number, a:Number, additive:Boolean, matrix:Matrix) : void {
if (additive != _additive) {
_additive = additive;
r:Number, g:Number, b:Number, a:Number, blendMode:spine.BlendMode, matrix:Matrix) : void {
if (blendMode != _blendMode) {
_blendMode = blendMode;
flush();
_support.blendMode = additive ? BlendMode.ADD : _blendMode;
if (blendMode == spine.BlendMode.normal)
_support.blendMode = _blendModeNormal;
else
_support.blendMode = spine.starling.SkeletonSprite.blendModes[blendMode.ordinal];
_support.applyBlendMode(true);
}

View File

@ -58,6 +58,8 @@ public class SkeletonSprite extends DisplayObject {
static private var _tempMatrix:Matrix = new Matrix();
static private var _tempVertices:Vector.<Number> = new Vector.<Number>(8);
static private var _quadTriangles:Vector.<uint> = new <uint>[0, 1, 2, 2, 3, 0];
static internal var blendModes:Vector.<String> = new <String>[
BlendMode.NORMAL, BlendMode.ADD, BlendMode.MULTIPLY, BlendMode.SCREEN];
private var _skeleton:Skeleton;
private var _polygonBatch:PolygonBatch;
@ -165,7 +167,7 @@ public class SkeletonSprite extends DisplayObject {
r *= skeletonR * slot.r * a;
g *= skeletonG * slot.g * a;
b *= skeletonB * slot.b * a;
polygonBatch.add(image.texture, worldVertices, verticesLength, uvs, triangles, r, g, b, a, slot.data.additiveBlending, matrix);
polygonBatch.add(image.texture, worldVertices, verticesLength, uvs, triangles, r, g, b, a, slot.data.blendMode, matrix);
}
}
}
@ -206,7 +208,7 @@ public class SkeletonSprite extends DisplayObject {
vertexData.setColorAndAlpha(3, rgb, a);
image.updateVertices();
support.blendMode = slot.data.additiveBlending ? BlendMode.ADD : blendMode;
support.blendMode = blendModes[slot.data.blendMode.ordinal];
support.batchQuad(image, alpha, image.texture, _smoothing);
}
}

View File

@ -191,7 +191,7 @@ function drawSkeleton (batch, skeleton) {
if (!(attachment instanceof spine.RegionAttachment)) continue;
attachment.computeVertices(skeleton.x, skeleton.y, slot.bone, vertices);
var blendMode = slot.data.additiveBlending ? draw2D.blend.additive : draw2D.blend.alpha;
var blendMode = slot.data.blendMode == spine.BlendMode.additive ? draw2D.blend.additive : draw2D.blend.alpha;
if (batch.blendMode != blendMode) {
batch.end();
batch.begin(blendMode);

View File

@ -283,8 +283,7 @@ public class SkeletonRenderer : MonoBehaviour {
color.r = (byte)(r * slot.r * regionAttachment.r * color.a);
color.g = (byte)(g * slot.g * regionAttachment.g * color.a);
color.b = (byte)(b * slot.b * regionAttachment.b * color.a);
if (slot.data.additiveBlending)
color.a = 0;
if (slot.data.blendMode == BlendMode.additive) color.a = 0;
colors[vertexIndex] = color;
colors[vertexIndex + 1] = color;
colors[vertexIndex + 2] = color;
@ -311,8 +310,7 @@ public class SkeletonRenderer : MonoBehaviour {
color.r = (byte)(r * slot.r * meshAttachment.r * color.a);
color.g = (byte)(g * slot.g * meshAttachment.g * color.a);
color.b = (byte)(b * slot.b * meshAttachment.b * color.a);
if (slot.data.additiveBlending)
color.a = 0;
if (slot.data.blendMode == BlendMode.additive) color.a = 0;
float[] meshUVs = meshAttachment.uvs;
float z = i * zSpacing;
@ -332,8 +330,7 @@ public class SkeletonRenderer : MonoBehaviour {
color.r = (byte)(r * slot.r * meshAttachment.r * color.a);
color.g = (byte)(g * slot.g * meshAttachment.g * color.a);
color.b = (byte)(b * slot.b * meshAttachment.b * color.a);
if (slot.data.additiveBlending)
color.a = 0;
if (slot.data.blendMode == BlendMode.additive) color.a = 0;
float[] meshUVs = meshAttachment.uvs;
float z = i * zSpacing;

View File

@ -96,7 +96,7 @@ namespace Spine {
Attachment attachment = slot.Attachment;
if (attachment is RegionAttachment) {
RegionAttachment regionAttachment = (RegionAttachment)attachment;
BlendState blend = slot.Data.AdditiveBlending ? BlendState.Additive : defaultBlendState;
BlendState blend = slot.Data.BlendMode == BlendMode.additive ? BlendState.Additive : defaultBlendState;
if (device.BlendState != blend) {
End();
device.BlendState = blend;

View File

@ -88,7 +88,7 @@ namespace Spine {
Slot slot = drawOrder[i];
RegionAttachment regionAttachment = slot.Attachment as RegionAttachment;
if (regionAttachment != null) {
BlendState blend = slot.Data.AdditiveBlending ? BlendState.Additive : defaultBlendState;
BlendState blend = slot.Data.BlendMode == BlendMode.additive ? BlendState.Additive : defaultBlendState;
if (device.BlendState != blend) {
End();
device.BlendState = blend;