From 7183e3b0bdbf023ad4791c8a68905e133cb2e709 Mon Sep 17 00:00:00 2001 From: sp-stefano-lanza <35223005+sp-stefano-lanza@users.noreply.github.com> Date: Tue, 16 Oct 2018 15:46:49 +0200 Subject: [PATCH 01/17] Feature/new skeleton culling (#1196) * Fix culling of skeleton and clean up code * Optionally draw the bounding rectangle of skeletons * Add Visual Studio compiler support * Fix indentation * Fix indentation * Fix indentation * Revert some indentation changes --- .../example/Classes/AppDelegate.cpp | 2 +- .../example/Classes/BatchingExample.cpp | 4 +- .../example/Classes/CoinExample.cpp | 6 +- .../example/Classes/GoblinsExample.cpp | 6 +- .../example/Classes/RaptorExample.cpp | 3 +- .../SkeletonRendererSeparatorExample.cpp | 11 +- .../example/Classes/SpineboyExample.cpp | 6 +- .../example/Classes/TankExample.cpp | 7 +- spine-cocos2dx/src/spine/SkeletonRenderer.cpp | 759 +++++++++++------- spine-cocos2dx/src/spine/SkeletonRenderer.h | 12 +- .../src/spine/SkeletonTwoColorBatch.cpp | 2 +- 11 files changed, 505 insertions(+), 313 deletions(-) diff --git a/spine-cocos2dx/example/Classes/AppDelegate.cpp b/spine-cocos2dx/example/Classes/AppDelegate.cpp index 3cad127b2..4c2222736 100644 --- a/spine-cocos2dx/example/Classes/AppDelegate.cpp +++ b/spine-cocos2dx/example/Classes/AppDelegate.cpp @@ -65,7 +65,7 @@ bool AppDelegate::applicationDidFinishLaunching () { glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER); #endif - Size frameSize = glview->getFrameSize(); + cocos2d::Size frameSize = glview->getFrameSize(); vector searchPath; diff --git a/spine-cocos2dx/example/Classes/BatchingExample.cpp b/spine-cocos2dx/example/Classes/BatchingExample.cpp index 4ba7ac497..4062144f7 100644 --- a/spine-cocos2dx/example/Classes/BatchingExample.cpp +++ b/spine-cocos2dx/example/Classes/BatchingExample.cpp @@ -67,14 +67,14 @@ bool BatchingExample::init () { int xMin = _contentSize.width * 0.10f, xMax = _contentSize.width * 0.90f; 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. SkeletonAnimation* skeletonNode = SkeletonAnimation::createWithData(_skeletonData, false); skeletonNode->setAnimationStateData(_stateData); skeletonNode->setAnimation(0, "walk", true); skeletonNode->addAnimation(0, "jump", true, RandomHelper::random_int(0, 300) / 100.0f); - skeletonNode->addAnimation(0, "run", true); + skeletonNode->addAnimation(0, "run", true); // alternative setting two color tint for groups of 10 skeletons // should end up with #skeletons / 10 batches diff --git a/spine-cocos2dx/example/Classes/CoinExample.cpp b/spine-cocos2dx/example/Classes/CoinExample.cpp index 0ec89b805..1a29ea92c 100644 --- a/spine-cocos2dx/example/Classes/CoinExample.cpp +++ b/spine-cocos2dx/example/Classes/CoinExample.cpp @@ -53,8 +53,10 @@ bool CoinExample::init () { EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create(); listener->onTouchBegan = [this] (Touch* touch, Event* event) -> bool { - if (!skeletonNode->getDebugBonesEnabled()) - skeletonNode->setDebugBonesEnabled(true); + if (!skeletonNode->getDebugBonesEnabled()) { + skeletonNode->setDebugBonesEnabled(true); + skeletonNode->setDebugBoundingRectEnabled(true); + } else if (skeletonNode->getTimeScale() == 1) skeletonNode->setTimeScale(0.3f); else diff --git a/spine-cocos2dx/example/Classes/GoblinsExample.cpp b/spine-cocos2dx/example/Classes/GoblinsExample.cpp index 47648c007..a5af16b92 100644 --- a/spine-cocos2dx/example/Classes/GoblinsExample.cpp +++ b/spine-cocos2dx/example/Classes/GoblinsExample.cpp @@ -54,8 +54,10 @@ bool GoblinsExample::init () { EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create(); listener->onTouchBegan = [this] (Touch* touch, Event* event) -> bool { - if (!skeletonNode->getDebugBonesEnabled()) - skeletonNode->setDebugBonesEnabled(true); + if (!skeletonNode->getDebugBonesEnabled()) { + skeletonNode->setDebugBonesEnabled(true); + skeletonNode->setDebugBoundingRectEnabled(true); + } else if (skeletonNode->getTimeScale() == 1) skeletonNode->setTimeScale(0.3f); else diff --git a/spine-cocos2dx/example/Classes/RaptorExample.cpp b/spine-cocos2dx/example/Classes/RaptorExample.cpp index cfd610a88..d224b5594 100644 --- a/spine-cocos2dx/example/Classes/RaptorExample.cpp +++ b/spine-cocos2dx/example/Classes/RaptorExample.cpp @@ -66,7 +66,8 @@ bool RaptorExample::init () { listener->onTouchBegan = [this] (Touch* touch, Event* event) -> bool { if (!skeletonNode->getDebugBonesEnabled()) { skeletonNode->setDebugBonesEnabled(true); - skeletonNode->setDebugMeshesEnabled(true); + skeletonNode->setDebugMeshesEnabled(true); + skeletonNode->setDebugBoundingRectEnabled(true); } else if (skeletonNode->getTimeScale() == 1) skeletonNode->setTimeScale(0.3f); else diff --git a/spine-cocos2dx/example/Classes/SkeletonRendererSeparatorExample.cpp b/spine-cocos2dx/example/Classes/SkeletonRendererSeparatorExample.cpp index 5ebdfc4eb..99459b36a 100644 --- a/spine-cocos2dx/example/Classes/SkeletonRendererSeparatorExample.cpp +++ b/spine-cocos2dx/example/Classes/SkeletonRendererSeparatorExample.cpp @@ -77,8 +77,15 @@ bool SkeletonRendererSeparatorExample::init () { EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create(); listener->onTouchBegan = [this] (Touch* touch, Event* event) -> bool { - if (!backNode->getDebugBonesEnabled()) - backNode->setDebugBonesEnabled(true); + if (!backNode->getDebugBonesEnabled()) + { + backNode->setDebugBonesEnabled(true); + backNode->setDebugSlotsEnabled(true); + backNode->setDebugBoundingRectEnabled(true); + frontNode->setDebugBonesEnabled(true); + frontNode->setDebugSlotsEnabled(true); + frontNode->setDebugBoundingRectEnabled(true); + } else if (backNode->getTimeScale() == 1) backNode->setTimeScale(0.3f); else diff --git a/spine-cocos2dx/example/Classes/SpineboyExample.cpp b/spine-cocos2dx/example/Classes/SpineboyExample.cpp index 3c8966f6a..8993ed8d5 100644 --- a/spine-cocos2dx/example/Classes/SpineboyExample.cpp +++ b/spine-cocos2dx/example/Classes/SpineboyExample.cpp @@ -84,8 +84,10 @@ bool SpineboyExample::init () { EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create(); listener->onTouchBegan = [this] (Touch* touch, Event* event) -> bool { - if (!skeletonNode->getDebugBonesEnabled()) - skeletonNode->setDebugBonesEnabled(true); + if (!skeletonNode->getDebugBonesEnabled()) { + skeletonNode->setDebugBonesEnabled(true); + skeletonNode->setDebugBoundingRectEnabled(true); + } else if (skeletonNode->getTimeScale() == 1) skeletonNode->setTimeScale(0.3f); else diff --git a/spine-cocos2dx/example/Classes/TankExample.cpp b/spine-cocos2dx/example/Classes/TankExample.cpp index b6fa26130..c1444fbff 100644 --- a/spine-cocos2dx/example/Classes/TankExample.cpp +++ b/spine-cocos2dx/example/Classes/TankExample.cpp @@ -53,8 +53,11 @@ bool TankExample::init () { EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create(); listener->onTouchBegan = [this] (Touch* touch, Event* event) -> bool { - if (!skeletonNode->getDebugBonesEnabled()) - skeletonNode->setDebugBonesEnabled(true); + if (!skeletonNode->getDebugBonesEnabled()) { + skeletonNode->setDebugBonesEnabled(true); + skeletonNode->setDebugSlotsEnabled(true); + skeletonNode->setDebugBoundingRectEnabled(true); + } else if (skeletonNode->getTimeScale() == 1) skeletonNode->setTimeScale(0.3f); else diff --git a/spine-cocos2dx/src/spine/SkeletonRenderer.cpp b/spine-cocos2dx/src/spine/SkeletonRenderer.cpp index d11e572b0..f8ca7353b 100644 --- a/spine-cocos2dx/src/spine/SkeletonRenderer.cpp +++ b/spine-cocos2dx/src/spine/SkeletonRenderer.cpp @@ -36,34 +36,32 @@ #include #include -#define INITIAL_WORLD_VERTICES_LENGTH 1000 -// Used for transforming attachments for bounding boxes & debug rendering -static float* worldVertices = nullptr; -static size_t worldVerticesLength = 0; - -void ensureWorldVerticesCapacity(size_t capacity) { - if (worldVerticesLength < capacity) { - float* newWorldVertices = new float[capacity]; - memcpy(newWorldVertices, worldVertices, capacity * sizeof(float)); - delete[] worldVertices; - worldVertices = newWorldVertices; - worldVerticesLength = capacity; - } -} - USING_NS_CC; -using std::min; -using std::max; + namespace spine { -void SkeletonRenderer::destroyScratchBuffers() { - if (worldVertices) { - delete[] worldVertices; - worldVertices = nullptr; - worldVerticesLength = 0; - } -} + +int computeTotalCoordCount(const spSkeleton& skeleton, int startSlotIndex, int endSlotIndex); +cocos2d::Rect computeBoundingRect(const float* coords, int vertexCount); +void interleaveCoordinates(float* dst, const float* src, int vertexCount, int dstStride); +BlendFunc makeBlendFunc(int blendMode, bool premultipliedAlpha); +void transformWorldVertices(float* dstCoord, int coordCount, const spSkeleton& skeleton, int startSlotIndex, int endSlotIndex); +bool cullRectangle(const Mat4 &transform, const cocos2d::Rect& rect, const Camera& camera); +Color4B spColorToColor4B(const spColor& color); +bool slotIsOutRange(const spSlot& slot, int startSlotIndex, int endSlotIndex); + + +// C Variable length array +#ifdef _MSC_VER + // VLA not supported, use _alloca + #define VLA(type, arr, count) \ + type* arr = static_cast( _alloca(sizeof(type) * count) ) +#else + #define VLA(type, arr, count) \ + type arr[count] +#endif + SkeletonRenderer* SkeletonRenderer::createWithSkeleton(spSkeleton* skeleton, bool ownsSkeleton, bool ownsSkeletonData) { SkeletonRenderer* node = new SkeletonRenderer(skeleton, ownsSkeleton, ownsSkeletonData); @@ -90,11 +88,6 @@ SkeletonRenderer* SkeletonRenderer::createWithFile (const std::string& skeletonD } void SkeletonRenderer::initialize () { - if (!worldVertices) { - worldVertices = new float[INITIAL_WORLD_VERTICES_LENGTH]; - worldVerticesLength = INITIAL_WORLD_VERTICES_LENGTH; - } - _clipper = spSkeletonClipping_create(); _blendFunc = BlendFunc::ALPHA_PREMULTIPLIED; @@ -144,26 +137,26 @@ void SkeletonRenderer::setSkeletonData (spSkeletonData *skeletonData, bool ownsS } SkeletonRenderer::SkeletonRenderer () - : _atlas(nullptr), _attachmentLoader(nullptr), _debugSlots(false), _debugBones(false), _debugMeshes(false), _timeScale(1), _effect(nullptr), _startSlotIndex(-1), _endSlotIndex(-1) { + : _atlas(nullptr), _attachmentLoader(nullptr), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _timeScale(1), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits::max()) { } SkeletonRenderer::SkeletonRenderer(spSkeleton* skeleton, bool ownsSkeleton, bool ownsSkeletonData) - : _atlas(nullptr), _attachmentLoader(nullptr), _debugSlots(false), _debugBones(false), _debugMeshes(false), _timeScale(1), _effect(nullptr), _startSlotIndex(-1), _endSlotIndex(-1) { + : _atlas(nullptr), _attachmentLoader(nullptr), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _timeScale(1), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits::max()) { initWithSkeleton(skeleton, ownsSkeleton, ownsSkeletonData); } SkeletonRenderer::SkeletonRenderer (spSkeletonData *skeletonData, bool ownsSkeletonData) - : _atlas(nullptr), _attachmentLoader(nullptr), _debugSlots(false), _debugBones(false), _debugMeshes(false), _timeScale(1), _effect(nullptr), _startSlotIndex(-1), _endSlotIndex(-1) { + : _atlas(nullptr), _attachmentLoader(nullptr), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _timeScale(1), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits::max()) { initWithData(skeletonData, ownsSkeletonData); } SkeletonRenderer::SkeletonRenderer (const std::string& skeletonDataFile, spAtlas* atlas, float scale) - : _atlas(nullptr), _attachmentLoader(nullptr), _debugSlots(false), _debugBones(false), _debugMeshes(false), _timeScale(1), _effect(nullptr), _startSlotIndex(-1), _endSlotIndex(-1) { + : _atlas(nullptr), _attachmentLoader(nullptr), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _timeScale(1), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits::max()) { initWithJsonFile(skeletonDataFile, atlas, scale); } SkeletonRenderer::SkeletonRenderer (const std::string& skeletonDataFile, const std::string& atlasFile, float scale) - : _atlas(nullptr), _attachmentLoader(nullptr), _debugSlots(false), _debugBones(false), _debugMeshes(false), _timeScale(1), _effect(nullptr), _startSlotIndex(-1), _endSlotIndex(-1) { + : _atlas(nullptr), _attachmentLoader(nullptr), _debugSlots(false), _debugBones(false), _debugMeshes(false), _debugBoundingRect(false), _timeScale(1), _effect(nullptr), _startSlotIndex(0), _endSlotIndex(std::numeric_limits::max()) { initWithJsonFile(skeletonDataFile, atlasFile, scale); } @@ -262,53 +255,68 @@ 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(); + assert(_skeleton); // Early exit if the skeleton is invisible if (getDisplayedOpacity() == 0 || _skeleton->color.a == 0){ return; } - if (_effect) _effect->begin(_effect, _skeleton); + const int coordCount = computeTotalCoordCount(*_skeleton, _startSlotIndex, _endSlotIndex); + if (coordCount == 0) + { + return; + } + assert(coordCount % 2 == 0); - Color4F nodeColor; - nodeColor.r = getDisplayedColor().r / (float)255; - nodeColor.g = getDisplayedColor().g / (float)255; - nodeColor.b = getDisplayedColor().b / (float)255; - nodeColor.a = getDisplayedOpacity() / (float)255; + VLA(float, worldCoords, coordCount); + transformWorldVertices(worldCoords, coordCount, *_skeleton, _startSlotIndex, _endSlotIndex); - Color4F color; - Color4F darkColor; - float darkPremultipliedAlpha = _premultipliedAlpha ? 255 : 0; + #if CC_USE_CULLING + const Camera* camera = Camera::getVisitingCamera(); + const cocos2d::Rect brect = computeBoundingRect(worldCoords, coordCount / 2); + _boundingRect = brect; + if (camera && cullRectangle(transform, brect, *camera)) + { + return; + } + #endif + + const float* worldCoordPtr = worldCoords; + SkeletonBatch* batch = SkeletonBatch::getInstance(); + SkeletonTwoColorBatch* twoColorBatch = SkeletonTwoColorBatch::getInstance(); + const bool hasSingleTint = (isTwoColorTint() == false); + + if (_effect) { + _effect->begin(_effect, _skeleton); + } + + const Color3B displayedColor = getDisplayedColor(); + spColor nodeColor; + nodeColor.r = displayedColor.r / 255.f; + nodeColor.g = displayedColor.g / 255.f; + nodeColor.b = displayedColor.b / 255.f; + nodeColor.a = getDisplayedOpacity() / 255.f; + + spColor color; + spColor darkColor; + const float darkPremultipliedAlpha = _premultipliedAlpha ? 1.f : 0; AttachmentVertices* attachmentVertices = nullptr; TwoColorTrianglesCommand* lastTwoColorTrianglesCommand = nullptr; - bool inRange = _startSlotIndex != -1 || _endSlotIndex != -1 ? false : true; for (int i = 0, n = _skeleton->slotsCount; i < n; ++i) { spSlot* slot = _skeleton->drawOrder[i]; - if (_startSlotIndex >= 0 && _startSlotIndex == slot->data->index) { - inRange = true; - } - - if (!inRange) { - spSkeletonClipping_clipEnd(_clipper, slot); - continue; - } - - if (_endSlotIndex >= 0 && _endSlotIndex == slot->data->index) { - inRange = false; - } - if (!slot->attachment) { spSkeletonClipping_clipEnd(_clipper, slot); continue; } + if (slotIsOutRange(*slot, _startSlotIndex, _endSlotIndex)) { + spSkeletonClipping_clipEnd(_clipper, slot); + continue; + } // Early exit if slot is invisible if (slot->color.a == 0) { - spSkeletonClipping_clipEnd(_clipper, slot); continue; } @@ -317,58 +325,66 @@ void SkeletonRenderer::draw (Renderer* renderer, const Mat4& transform, uint32_t switch (slot->attachment->type) { case SP_ATTACHMENT_REGION: { - spRegionAttachment* attachment = (spRegionAttachment*)slot->attachment; - attachmentVertices = getAttachmentVertices(attachment); - + spRegionAttachment* attachment = reinterpret_cast(slot->attachment); // Early exit if attachment is invisible if (attachment->color.a == 0) { spSkeletonClipping_clipEnd(_clipper, slot); continue; } + attachmentVertices = getAttachmentVertices(attachment); - if (!isTwoColorTint) { + float* dstTriangleVertices = nullptr; + int dstStride = 0; // in floats + if (hasSingleTint) { triangles.indices = attachmentVertices->_triangles->indices; triangles.indexCount = attachmentVertices->_triangles->indexCount; triangles.verts = batch->allocateVertices(attachmentVertices->_triangles->vertCount); triangles.vertCount = attachmentVertices->_triangles->vertCount; - memcpy(triangles.verts, attachmentVertices->_triangles->verts, sizeof(cocos2d::V3F_C4B_T2F) * attachmentVertices->_triangles->vertCount); - spRegionAttachment_computeWorldVertices(attachment, slot->bone, (float*)triangles.verts, 0, 6); + assert(triangles.vertCount == 4); + std::memcpy(triangles.verts, attachmentVertices->_triangles->verts, sizeof(cocos2d::V3F_C4B_T2F) * attachmentVertices->_triangles->vertCount); + dstStride = sizeof(V3F_C4B_T2F) / sizeof(float); + dstTriangleVertices = reinterpret_cast(triangles.verts); } else { trianglesTwoColor.indices = attachmentVertices->_triangles->indices; trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount; trianglesTwoColor.verts = twoColorBatch->allocateVertices(attachmentVertices->_triangles->vertCount); trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount; + assert(trianglesTwoColor.vertCount == 4); for (int 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); + dstTriangleVertices = reinterpret_cast(trianglesTwoColor.verts); + dstStride = sizeof(V3F_C4B_C4B_T2F) / sizeof(float); } + // Copy world vertices to triangle vertices + interleaveCoordinates(dstTriangleVertices, worldCoordPtr, 4, dstStride); + worldCoordPtr += 8; - color.r = attachment->color.r; - color.g = attachment->color.g; - color.b = attachment->color.b; - color.a = attachment->color.a; + color = attachment->color; break; } case SP_ATTACHMENT_MESH: { - spMeshAttachment* attachment = (spMeshAttachment*)slot->attachment; - attachmentVertices = getAttachmentVertices(attachment); - + spMeshAttachment* attachment = reinterpret_cast(slot->attachment); // Early exit if attachment is invisible if (attachment->color.a == 0) { spSkeletonClipping_clipEnd(_clipper, slot); continue; } + attachmentVertices = getAttachmentVertices(attachment); - if (!isTwoColorTint) { + float* dstTriangleVertices = nullptr; + int dstStride = 0; // in floats + int dstVertexCount = 0; + if (hasSingleTint) { triangles.indices = attachmentVertices->_triangles->indices; triangles.indexCount = attachmentVertices->_triangles->indexCount; triangles.verts = batch->allocateVertices(attachmentVertices->_triangles->vertCount); triangles.vertCount = attachmentVertices->_triangles->vertCount; - memcpy(triangles.verts, attachmentVertices->_triangles->verts, sizeof(cocos2d::V3F_C4B_T2F) * attachmentVertices->_triangles->vertCount); - int vertexSizeInFloats = sizeof(cocos2d::V3F_C4B_T2F) / sizeof(float); - spVertexAttachment_computeWorldVertices(SUPER(attachment), slot, 0, attachment->super.worldVerticesLength, (float*)triangles.verts, 0, vertexSizeInFloats); + std::memcpy(triangles.verts, attachmentVertices->_triangles->verts, sizeof(cocos2d::V3F_C4B_T2F) * attachmentVertices->_triangles->vertCount); + dstTriangleVertices = (float*)triangles.verts; + dstStride = sizeof(V3F_C4B_T2F) / sizeof(float); + dstVertexCount = triangles.vertCount; } else { trianglesTwoColor.indices = attachmentVertices->_triangles->indices; trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount; @@ -377,19 +393,21 @@ void SkeletonRenderer::draw (Renderer* renderer, const Mat4& transform, uint32_t for (int i = 0; i < trianglesTwoColor.vertCount; i++) { trianglesTwoColor.verts[i].texCoords = attachmentVertices->_triangles->verts[i].texCoords; } - int vertexSizeInFloats = sizeof(V3F_C4B_C4B_T2F) / sizeof(float); - spVertexAttachment_computeWorldVertices(SUPER(attachment), slot, 0, attachment->super.worldVerticesLength, (float*)trianglesTwoColor.verts, 0, vertexSizeInFloats); + dstTriangleVertices = (float*)trianglesTwoColor.verts; + dstStride = sizeof(V3F_C4B_C4B_T2F) / sizeof(float); + dstVertexCount = trianglesTwoColor.vertCount; } - color.r = attachment->color.r; - color.g = attachment->color.g; - color.b = attachment->color.b; - color.a = attachment->color.a; + // Copy world vertices to triangle vertices + assert(dstVertexCount * 2 == attachment->super.worldVerticesLength); + interleaveCoordinates(dstTriangleVertices, worldCoordPtr, dstVertexCount, dstStride); + worldCoordPtr += dstVertexCount * 2; + color = attachment->color; break; } case SP_ATTACHMENT_CLIPPING: { - spClippingAttachment* clip = (spClippingAttachment*)slot->attachment; + spClippingAttachment* clip = reinterpret_cast(slot->attachment); spSkeletonClipping_clipStart(_clipper, slot, clip); continue; } @@ -398,54 +416,35 @@ void SkeletonRenderer::draw (Renderer* renderer, const Mat4& transform, uint32_t continue; } - float alpha = nodeColor.a * _skeleton->color.a * slot->color.a * color.a * 255; - // skip rendering if the color of this attachment is 0 - if (alpha == 0){ - spSkeletonClipping_clipEnd(_clipper, slot); - continue; - } - float multiplier = _premultipliedAlpha ? alpha : 255; - - float red = nodeColor.r * _skeleton->color.r * slot->color.r * multiplier; - float green = nodeColor.g * _skeleton->color.g * slot->color.g * multiplier; - float blue = nodeColor.b * _skeleton->color.b * slot->color.b * multiplier; - - color.r = red * color.r; - color.g = green * color.g; - color.b = blue * color.b; - color.a = alpha; - if (slot->darkColor) { - darkColor.r = red * slot->darkColor->r; - darkColor.g = green * slot->darkColor->g; - darkColor.b = blue * slot->darkColor->b; + darkColor = *slot->darkColor; } else { darkColor.r = 0; darkColor.g = 0; darkColor.b = 0; } darkColor.a = darkPremultipliedAlpha; - - BlendFunc blendFunc; - switch (slot->data->blendMode) { - case SP_BLEND_MODE_ADDITIVE: - blendFunc.src = _premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA; - blendFunc.dst = GL_ONE; - break; - case SP_BLEND_MODE_MULTIPLY: - blendFunc.src = GL_DST_COLOR; - blendFunc.dst = GL_ONE_MINUS_SRC_ALPHA; - break; - case SP_BLEND_MODE_SCREEN: - blendFunc.src = GL_ONE; - blendFunc.dst = GL_ONE_MINUS_SRC_COLOR; - break; - default: - blendFunc.src = _premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA; - blendFunc.dst = GL_ONE_MINUS_SRC_ALPHA; + color.a *= nodeColor.a * _skeleton->color.a * slot->color.a; + // skip rendering if the color of this attachment is 0 + if (color.a == 0){ + spSkeletonClipping_clipEnd(_clipper, slot); + continue; + } + color.r *= nodeColor.r * _skeleton->color.r * slot->color.r; + color.g *= nodeColor.g * _skeleton->color.g * slot->color.g; + color.b *= nodeColor.b * _skeleton->color.b * slot->color.b; + if (_premultipliedAlpha) + { + color.r *= color.a; + color.g *= color.a; + color.b *= color.a; } - if (!isTwoColorTint) { + const cocos2d::Color4B color4B = spColorToColor4B(color); + const cocos2d::Color4B darkColor4B = spColorToColor4B(darkColor); + const BlendFunc blendFunc = makeBlendFunc(slot->data->blendMode, _premultipliedAlpha); + + if (hasSingleTint) { if (spSkeletonClipping_isClipping(_clipper)) { spSkeletonClipping_clipTriangles(_clipper, (float*)&triangles.verts[0].vertices, triangles.vertCount * sizeof(cocos2d::V3F_C4B_T2F) / 4, triangles.indices, triangles.indexCount, (float*)&triangles.verts[0].texCoords, 6); batch->deallocateVertices(triangles.vertCount); @@ -458,80 +457,60 @@ void SkeletonRenderer::draw (Renderer* renderer, const Mat4& transform, uint32_t triangles.vertCount = _clipper->clippedVertices->size >> 1; triangles.verts = batch->allocateVertices(triangles.vertCount); triangles.indexCount = _clipper->clippedTriangles->size; - triangles.indices = batch->allocateIndices(triangles.indexCount); - memcpy(triangles.indices, _clipper->clippedTriangles->items, sizeof(unsigned short) * _clipper->clippedTriangles->size); + triangles.indices = + batch->allocateIndices(triangles.indexCount); + std::memcpy(triangles.indices, _clipper->clippedTriangles->items, sizeof(unsigned short) * _clipper->clippedTriangles->size); cocos2d::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _glProgramState, blendFunc, triangles, transform, transformFlags); - float* verts = _clipper->clippedVertices->items; - float* uvs = _clipper->clippedUVs->items; + const float* verts = _clipper->clippedVertices->items; + const float* uvs = _clipper->clippedUVs->items; if (_effect) { - spColor light; - spColor dark; - light.r = color.r / 255.0f; - light.g = color.g / 255.0f; - light.b = color.b / 255.0f; - light.a = color.a / 255.0f; - dark.r = dark.g = dark.b = dark.a = 0; - for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv+=2) { - V3F_C4B_T2F* vertex = batchedTriangles->getTriangles().verts + v; - spColor lightCopy = light; - spColor darkCopy = dark; + V3F_C4B_T2F* vertex = batchedTriangles->getTriangles().verts; + spColor darkTmp; + for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv+=2, ++vertex) { + spColor lightCopy = color; vertex->vertices.x = verts[vv]; vertex->vertices.y = verts[vv + 1]; vertex->texCoords.u = uvs[vv]; vertex->texCoords.v = uvs[vv + 1]; - _effect->transform(_effect, &vertex->vertices.x, &vertex->vertices.y, &vertex->texCoords.u, &vertex->texCoords.v, &lightCopy, &darkCopy); - vertex->colors.r = (GLubyte)(lightCopy.r * 255); - vertex->colors.g = (GLubyte)(lightCopy.g * 255); - vertex->colors.b = (GLubyte)(lightCopy.b * 255); - vertex->colors.a = (GLubyte)(lightCopy.a * 255); + _effect->transform(_effect, &vertex->vertices.x, &vertex->vertices.y, &vertex->texCoords.u, &vertex->texCoords.v, &lightCopy, &darkTmp); + vertex->colors = spColorToColor4B(lightCopy); } } else { - for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv+=2) { - V3F_C4B_T2F* vertex = batchedTriangles->getTriangles().verts + v; + const cocos2d::Color4B color4B = spColorToColor4B(color); + V3F_C4B_T2F* vertex = batchedTriangles->getTriangles().verts; + for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv+=2, ++vertex) { vertex->vertices.x = verts[vv]; vertex->vertices.y = verts[vv + 1]; vertex->texCoords.u = uvs[vv]; vertex->texCoords.v = uvs[vv + 1]; - vertex->colors.r = (GLubyte)color.r; - vertex->colors.g = (GLubyte)color.g; - vertex->colors.b = (GLubyte)color.b; - vertex->colors.a = (GLubyte)color.a; + vertex->colors = color4B; } } } else { + // Not clipping + cocos2d::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _glProgramState, blendFunc, triangles, transform, transformFlags); if (_effect) { - spColor light; - spColor dark; - light.r = color.r / 255.0f; - light.g = color.g / 255.0f; - light.b = color.b / 255.0f; - light.a = color.a / 255.0f; - dark.r = dark.g = dark.b = dark.a = 0; - for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v) { - V3F_C4B_T2F* vertex = batchedTriangles->getTriangles().verts + v; - spColor lightCopy = light; - spColor darkCopy = dark; - _effect->transform(_effect, &vertex->vertices.x, &vertex->vertices.y, &vertex->texCoords.u, &vertex->texCoords.v, &lightCopy, &darkCopy); - vertex->colors.r = (GLubyte)(lightCopy.r * 255); - vertex->colors.g = (GLubyte)(lightCopy.g * 255); - vertex->colors.b = (GLubyte)(lightCopy.b * 255); - vertex->colors.a = (GLubyte)(lightCopy.a * 255); + V3F_C4B_T2F* vertex = batchedTriangles->getTriangles().verts; + spColor darkTmp; + for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) { + spColor lightCopy = color; + _effect->transform(_effect, &vertex->vertices.x, &vertex->vertices.y, &vertex->texCoords.u, &vertex->texCoords.v, &lightCopy, &darkTmp); + vertex->colors = spColorToColor4B(lightCopy); } } else { - 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; + V3F_C4B_T2F* vertex = batchedTriangles->getTriangles().verts; + for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) { + vertex->colors = color4B; } } } } else { + // Two tints + if (spSkeletonClipping_isClipping(_clipper)) { spSkeletonClipping_clipTriangles(_clipper, (float*)&trianglesTwoColor.verts[0].position, trianglesTwoColor.vertCount * sizeof(V3F_C4B_C4B_T2F) / 4, trianglesTwoColor.indices, trianglesTwoColor.indexCount, (float*)&trianglesTwoColor.verts[0].texCoords, 7); twoColorBatch->deallocateVertices(trianglesTwoColor.vertCount); @@ -545,99 +524,54 @@ void SkeletonRenderer::draw (Renderer* renderer, const Mat4& transform, uint32_t trianglesTwoColor.verts = twoColorBatch->allocateVertices(trianglesTwoColor.vertCount); trianglesTwoColor.indexCount = _clipper->clippedTriangles->size; trianglesTwoColor.indices = twoColorBatch->allocateIndices(trianglesTwoColor.indexCount); - memcpy(trianglesTwoColor.indices, _clipper->clippedTriangles->items, sizeof(unsigned short) * _clipper->clippedTriangles->size); + std::memcpy(trianglesTwoColor.indices, _clipper->clippedTriangles->items, sizeof(unsigned short) * _clipper->clippedTriangles->size); TwoColorTrianglesCommand* batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture->getName(), _glProgramState, blendFunc, trianglesTwoColor, transform, transformFlags); - float* verts = _clipper->clippedVertices->items; - float* uvs = _clipper->clippedUVs->items; + const float* verts = _clipper->clippedVertices->items; + const float* uvs = _clipper->clippedUVs->items; if (_effect) { - spColor light; - spColor dark; - light.r = color.r / 255.0f; - light.g = color.g / 255.0f; - light.b = color.b / 255.0f; - light.a = color.a / 255.0f; - dark.r = darkColor.r / 255.0f; - dark.g = darkColor.g / 255.0f; - dark.b = darkColor.b / 255.0f; - dark.a = darkColor.a / 255.0f; - for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv += 2) { - V3F_C4B_C4B_T2F* vertex = batchedTriangles->getTriangles().verts + v; - spColor lightCopy = light; - spColor darkCopy = dark; + V3F_C4B_C4B_T2F* vertex = batchedTriangles->getTriangles().verts; + for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv += 2, ++vertex) { + spColor lightCopy = color; + spColor darkCopy = darkColor; vertex->position.x = verts[vv]; vertex->position.y = verts[vv + 1]; vertex->texCoords.u = uvs[vv]; vertex->texCoords.v = uvs[vv + 1]; _effect->transform(_effect, &vertex->position.x, &vertex->position.y, &vertex->texCoords.u, &vertex->texCoords.v, &lightCopy, &darkCopy); - vertex->color.r = (GLubyte)(lightCopy.r * 255); - vertex->color.g = (GLubyte)(lightCopy.g * 255); - vertex->color.b = (GLubyte)(lightCopy.b * 255); - vertex->color.a = (GLubyte)(lightCopy.a * 255); - vertex->color2.r = (GLubyte)(darkCopy.r * 255); - vertex->color2.g = (GLubyte)(darkCopy.g * 255); - vertex->color2.b = (GLubyte)(darkCopy.b * 255); - vertex->color2.a = (GLubyte)darkColor.a; + vertex->color = spColorToColor4B(lightCopy); + vertex->color2 = spColorToColor4B(darkCopy); } } else { - for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv += 2) { - V3F_C4B_C4B_T2F* vertex = batchedTriangles->getTriangles().verts + v; + V3F_C4B_C4B_T2F* vertex = batchedTriangles->getTriangles().verts; + for (int v = 0, vn = batchedTriangles->getTriangles().vertCount, vv = 0; v < vn; ++v, vv += 2, ++vertex) { vertex->position.x = verts[vv]; vertex->position.y = verts[vv + 1]; vertex->texCoords.u = uvs[vv]; vertex->texCoords.v = uvs[vv + 1]; - vertex->color.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 = (GLubyte)darkColor.a; + vertex->color = color4B; + vertex->color2 = darkColor4B; } } } else { TwoColorTrianglesCommand* batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture->getName(), _glProgramState, blendFunc, trianglesTwoColor, transform, transformFlags); if (_effect) { - spColor light; - spColor dark; - light.r = color.r / 255.0f; - light.g = color.g / 255.0f; - light.b = color.b / 255.0f; - light.a = color.a / 255.0f; - dark.r = darkColor.r / 255.0f; - dark.g = darkColor.g / 255.0f; - dark.b = darkColor.b / 255.0f; - dark.a = darkColor.a / 255.0f; - - for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v) { - V3F_C4B_C4B_T2F* vertex = batchedTriangles->getTriangles().verts + v; - spColor lightCopy = light; - spColor darkCopy = dark; + V3F_C4B_C4B_T2F* vertex = batchedTriangles->getTriangles().verts; + for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) { + spColor lightCopy = color; + spColor darkCopy = darkColor; _effect->transform(_effect, &vertex->position.x, &vertex->position.y, &vertex->texCoords.u, &vertex->texCoords.v, &lightCopy, &darkCopy); - vertex->color.r = (GLubyte)(lightCopy.r * 255); - vertex->color.g = (GLubyte)(lightCopy.g * 255); - vertex->color.b = (GLubyte)(lightCopy.b * 255); - vertex->color.a = (GLubyte)(lightCopy.a * 255); - vertex->color2.r = (GLubyte)(darkCopy.r * 255); - vertex->color2.g = (GLubyte)(darkCopy.g * 255); - vertex->color2.b = (GLubyte)(darkCopy.b * 255); - vertex->color2.a = (GLubyte)darkColor.a; + vertex->color = spColorToColor4B(lightCopy); + vertex->color2 = spColorToColor4B(darkCopy); } } else { - 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 = (GLubyte)darkColor.a; + V3F_C4B_C4B_T2F* vertex = batchedTriangles->getTriangles().verts; + for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) { + vertex->color = color4B; + vertex->color2 = darkColor4B; } } } @@ -683,10 +617,12 @@ void SkeletonRenderer::draw (Renderer* renderer, const Mat4& transform, uint32_t } } - if (_effect) _effect->end(_effect); + if (_effect) { + _effect->end(_effect); + } - if (_debugSlots || _debugBones || _debugMeshes) { - drawDebug(renderer, transform, transformFlags); + if (_debugBoundingRect || _debugSlots || _debugBones || _debugMeshes) { + drawDebug(renderer, transform, transformFlags); } } @@ -698,29 +634,52 @@ void SkeletonRenderer::drawDebug (Renderer* renderer, const Mat4 &transform, uin DrawNode* drawNode = DrawNode::create(); + // Draw bounding rectangle + if (_debugBoundingRect) { + glLineWidth(2); + const cocos2d::Rect brect = getBoundingBox(); + const Vec2 points[4] = + { + brect.origin, + { brect.origin.x + brect.size.width, brect.origin.y }, + { brect.origin.x + brect.size.width, brect.origin.y + brect.size.height }, + { brect.origin.x, brect.origin.y + brect.size.height } + }; + drawNode->drawPoly(points, 4, true, Color4F::GREEN); + } + if (_debugSlots) { // Slots. // DrawPrimitives::setDrawColor4B(0, 0, 255, 255); glLineWidth(1); - Vec2 points[4]; V3F_C4B_T2F_Quad quad; for (int i = 0, n = _skeleton->slotsCount; i < n; i++) { spSlot* slot = _skeleton->drawOrder[i]; - if (!slot->attachment || slot->attachment->type != SP_ATTACHMENT_REGION) continue; + if (!slot->attachment || slot->attachment->type != SP_ATTACHMENT_REGION) { + continue; + } + if (slotIsOutRange(*slot, _startSlotIndex, _endSlotIndex)) { + continue; + } spRegionAttachment* attachment = (spRegionAttachment*)slot->attachment; + float worldVertices[8]; spRegionAttachment_computeWorldVertices(attachment, slot->bone, worldVertices, 0, 2); - points[0] = Vec2(worldVertices[0], worldVertices[1]); - points[1] = Vec2(worldVertices[2], worldVertices[3]); - points[2] = Vec2(worldVertices[4], worldVertices[5]); - points[3] = Vec2(worldVertices[6], worldVertices[7]); + const Vec2 points[4] = + { + { worldVertices[0], worldVertices[1] }, + { worldVertices[2], worldVertices[3] }, + { worldVertices[4], worldVertices[5] }, + { worldVertices[6], worldVertices[7] } + }; drawNode->drawPoly(points, 4, true, Color4F::BLUE); } } + if (_debugBones) { // Bone lengths. glLineWidth(2); for (int i = 0, n = _skeleton->bonesCount; i < n; i++) { - spBone *bone = _skeleton->bones[i]; + const spBone *bone = _skeleton->bones[i]; float x = bone->data->length * bone->a + bone->worldX; float y = bone->data->length * bone->c + bone->worldY; drawNode->drawLine(Vec2(bone->worldX, bone->worldY), Vec2(x, y), Color4F::RED); @@ -728,7 +687,7 @@ void SkeletonRenderer::drawDebug (Renderer* renderer, const Mat4 &transform, uin // Bone origins. auto color = Color4F::BLUE; // Root bone is blue. for (int i = 0, n = _skeleton->bonesCount; i < n; i++) { - spBone *bone = _skeleton->bones[i]; + const spBone *bone = _skeleton->bones[i]; drawNode->drawPoint(Vec2(bone->worldX, bone->worldY), 4, color); if (i == 0) color = Color4F::GREEN; } @@ -741,15 +700,20 @@ void SkeletonRenderer::drawDebug (Renderer* renderer, const Mat4 &transform, uin spSlot* slot = _skeleton->drawOrder[i]; if (!slot->attachment || slot->attachment->type != SP_ATTACHMENT_MESH) continue; spMeshAttachment* attachment = (spMeshAttachment*)slot->attachment; - ensureWorldVerticesCapacity(attachment->super.worldVerticesLength); - spVertexAttachment_computeWorldVertices(SUPER(attachment), slot, 0, attachment->super.worldVerticesLength, worldVertices, 0, 2); - for (int ii = 0; ii < attachment->trianglesCount;) { - Vec2 v1(worldVertices + (attachment->triangles[ii++] * 2)); - Vec2 v2(worldVertices + (attachment->triangles[ii++] * 2)); - Vec2 v3(worldVertices + (attachment->triangles[ii++] * 2)); - drawNode->drawLine(v1, v2, Color4F::YELLOW); - drawNode->drawLine(v2, v3, Color4F::YELLOW); - drawNode->drawLine(v3, v1, Color4F::YELLOW); + VLA(float, worldCoord, attachment->super.worldVerticesLength); + spVertexAttachment_computeWorldVertices(SUPER(attachment), slot, 0, attachment->super.worldVerticesLength, worldCoord, 0, 2); + for (int t = 0; t < attachment->trianglesCount; t += 3) { + // Fetch triangle indices + const int idx0 = attachment->triangles[t + 0]; + const int idx1 = attachment->triangles[t + 1]; + const int idx2 = attachment->triangles[t + 2]; + const Vec2 v[3] = + { + worldCoord + (idx0 * 2), + worldCoord + (idx1 * 2), + worldCoord + (idx2 * 2) + }; + drawNode->drawPoly(v, 3, true, Color4F::YELLOW); } } @@ -767,35 +731,8 @@ AttachmentVertices* SkeletonRenderer::getAttachmentVertices (spMeshAttachment* a return (AttachmentVertices*)attachment->rendererObject; } -Rect SkeletonRenderer::getBoundingBox () const { - float minX = FLT_MAX, minY = FLT_MAX, maxX = -FLT_MAX, maxY = -FLT_MAX; - float scaleX = getScaleX(), scaleY = getScaleY(); - for (int i = 0; i < _skeleton->slotsCount; ++i) { - spSlot* slot = _skeleton->slots[i]; - if (!slot->attachment) continue; - int verticesCount; - if (slot->attachment->type == SP_ATTACHMENT_REGION) { - spRegionAttachment* attachment = (spRegionAttachment*)slot->attachment; - spRegionAttachment_computeWorldVertices(attachment, slot->bone, worldVertices, 0, 2); - verticesCount = 8; - } else if (slot->attachment->type == SP_ATTACHMENT_MESH) { - spMeshAttachment* mesh = (spMeshAttachment*)slot->attachment; - ensureWorldVerticesCapacity(mesh->super.worldVerticesLength); - spVertexAttachment_computeWorldVertices(SUPER(mesh), slot, 0, mesh->super.worldVerticesLength, worldVertices, 0, 2); - verticesCount = mesh->super.worldVerticesLength; - } else - continue; - for (int ii = 0; ii < verticesCount; ii += 2) { - float x = worldVertices[ii] * scaleX, y = worldVertices[ii + 1] * scaleY; - minX = min(minX, x); - minY = min(minY, y); - maxX = max(maxX, x); - maxY = max(maxY, y); - } - } - Vec2 position = getPosition(); - if (minX == FLT_MAX) minX = minY = maxX = maxY = 0; - return Rect(position.x + minX, position.y + minY, maxX - minX, maxY - minY); +cocos2d::Rect SkeletonRenderer::getBoundingBox () const { + return _boundingRect; } // --- Convenience methods for Skeleton_* functions. @@ -852,11 +789,11 @@ void SkeletonRenderer::setVertexEffect(spVertexEffect *effect) { } void SkeletonRenderer::setSlotsRange(int startSlotIndex, int endSlotIndex) { - this->_startSlotIndex = startSlotIndex; - this->_endSlotIndex = endSlotIndex; + _startSlotIndex = startSlotIndex == -1 ? 0 : startSlotIndex; + _endSlotIndex = endSlotIndex == -1 ? std::numeric_limits::max() : endSlotIndex; } -spSkeleton* SkeletonRenderer::getSkeleton () { +spSkeleton* SkeletonRenderer::getSkeleton () const { return _skeleton; } @@ -888,6 +825,14 @@ bool SkeletonRenderer::getDebugMeshesEnabled () const { return _debugMeshes; } +void SkeletonRenderer::setDebugBoundingRectEnabled(bool enabled) { + _debugBoundingRect = enabled; +} + +bool SkeletonRenderer::getDebugBoundingRectEnabled() const { + return _debugBoundingRect; +} + void SkeletonRenderer::onEnter () { #if CC_ENABLE_SCRIPT_BINDING if (_scriptType == kScriptTypeJavascript && ScriptEngineManager::sendNodeEventToJSExtended(this, kNodeOnEnter)) return; @@ -922,4 +867,232 @@ bool SkeletonRenderer::isOpacityModifyRGB () const { return _premultipliedAlpha; } + + cocos2d::Rect computeBoundingRect(const float* coords, int vertexCount) + { + assert(coords); + assert(vertexCount > 0); + + const float* v = coords; + float minX = v[0]; + float minY = v[1]; + float maxX = minX; + float maxY = minY; + for (int i = 1; i < vertexCount; ++i) + { + v += 2; + float x = v[0]; + float y = v[1]; + minX = std::min(minX, x); + minY = std::min(minY, y); + maxX = std::max(maxX, x); + maxY = std::max(maxY, y); + } + return { minX, minY, maxX - minX, maxY - minY }; + } + + bool slotIsOutRange(const spSlot& slot, int startSlotIndex, int endSlotIndex) + { + return startSlotIndex > slot.data->index || endSlotIndex < slot.data->index; + } + + int computeTotalCoordCount(const spSkeleton& skeleton, int startSlotIndex, int endSlotIndex) + { + int coordCount = 0; + for (int i = 0; i < skeleton.slotsCount; ++i) + { + const spSlot& slot = *skeleton.slots[i]; + if (!slot.attachment) + { + continue; + } + if (slotIsOutRange(slot, startSlotIndex, endSlotIndex)) + { + continue; + } + // Early exit if slot is invisible + if (slot.color.a == 0) { + continue; + } + if (slot.attachment->type == SP_ATTACHMENT_REGION) + { + // Early exit if attachment is invisible + spRegionAttachment* attachment = reinterpret_cast(slot.attachment); + if (attachment->color.a == 0) { + continue; + } + coordCount += 8; + } + else if (slot.attachment->type == SP_ATTACHMENT_MESH) + { + const spMeshAttachment* mesh = reinterpret_cast(slot.attachment); + // Early exit if attachment is invisible + if (mesh->color.a == 0) { + continue; + } + coordCount += mesh->super.worldVerticesLength; + } + } + return coordCount; + } + + + void transformWorldVertices(float* dstCoord, int coordCount, const spSkeleton& skeleton, int startSlotIndex, int endSlotIndex) + { + float* dstPtr = dstCoord; +#ifndef NDEBUG + float* const dstEnd = dstCoord + coordCount; +#endif + for (int i = 0; i < skeleton.slotsCount; ++i) + { + /*const*/ spSlot& slot = *skeleton.drawOrder[i]; // match the draw order of SkeletonRenderer::Draw + if (!slot.attachment) + { + continue; + } + if (slotIsOutRange(slot, startSlotIndex, endSlotIndex)) + { + continue; + } + // Early exit if slot is invisible + if (slot.color.a == 0) { + continue; + } + if (slot.attachment->type == SP_ATTACHMENT_REGION) + { + spRegionAttachment* attachment = reinterpret_cast(slot.attachment); + // Early exit if attachment is invisible + if (attachment->color.a == 0) { + continue; + } + assert(dstPtr + 8 <= dstEnd); + spRegionAttachment_computeWorldVertices(attachment, slot.bone, dstPtr, 0, 2); + dstPtr += 8; + } + else if (slot.attachment->type == SP_ATTACHMENT_MESH) + { + spMeshAttachment* mesh = reinterpret_cast(slot.attachment); + // Early exit if attachment is invisible + if (mesh->color.a == 0) { + continue; + } + assert(dstPtr + mesh->super.worldVerticesLength <= dstEnd); + spVertexAttachment_computeWorldVertices(SUPER(mesh), &slot, 0, mesh->super.worldVerticesLength, dstPtr, 0, 2); + dstPtr += mesh->super.worldVerticesLength; + } + } + assert(dstPtr == dstEnd); + } + + void interleaveCoordinates(float* __restrict dst, const float* __restrict src, int count, int dstStride) + { + if (dstStride == 2) + { + std::memcpy(dst, src, sizeof(float) * count * 2); + } + else + { + for (int i = 0; i < count; ++i) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst += dstStride; + src += 2; + } + } + + } + + BlendFunc makeBlendFunc(int blendMode, bool premultipliedAlpha) + { + BlendFunc blendFunc; + switch (blendMode) { + case SP_BLEND_MODE_ADDITIVE: + blendFunc.src = premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA; + blendFunc.dst = GL_ONE; + break; + case SP_BLEND_MODE_MULTIPLY: + blendFunc.src = GL_DST_COLOR; + blendFunc.dst = GL_ONE_MINUS_SRC_ALPHA; + break; + case SP_BLEND_MODE_SCREEN: + blendFunc.src = GL_ONE; + blendFunc.dst = GL_ONE_MINUS_SRC_COLOR; + break; + default: + blendFunc.src = premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA; + blendFunc.dst = GL_ONE_MINUS_SRC_ALPHA; + break; + } + return blendFunc; + } + + + bool cullRectangle(const Mat4 &transform, const cocos2d::Rect& rect, const Camera& camera) + { + // Compute rectangle center and half extents in local space + // TODO: Pass the bounding rectangle with this representation directly + const float halfRectWidth = rect.size.width * 0.5f; + const float halfRectHeight = rect.size.height * 0.5f; + const float l_cx = rect.origin.x + halfRectWidth; + const float l_cy = rect.origin.y + halfRectHeight; + + // Transform rectangle center to world space + const float w_cx = (l_cx * transform.m[0] + l_cy * transform.m[4]) + transform.m[12]; + const float w_cy = (l_cx * transform.m[1] + l_cy * transform.m[5]) + transform.m[13]; + + // Compute rectangle half extents in world space + const float w_ex = std::abs(halfRectWidth * transform.m[0]) + std::abs(halfRectHeight * transform.m[4]); + const float w_ey = std::abs(halfRectWidth * transform.m[1]) + std::abs(halfRectHeight * transform.m[5]); + + // Transform rectangle to clip space + const Mat4& viewMatrix = camera.getViewMatrix(); + const Mat4& projectionMatrix = camera.getProjectionMatrix(); + const float c_cx = (w_cx + viewMatrix.m[12]) * projectionMatrix.m[0]; + const float c_cy = (w_cy + viewMatrix.m[13]) * projectionMatrix.m[5]; + const float c_ex = w_ex * projectionMatrix.m[0]; + const float c_ey = w_ey * projectionMatrix.m[5]; + // The rectangle has z == 0 in world space + // cw = projectionMatrix[11] * vz = -vz = wz -viewMatrix.m[14] = -viewMatrix.m[14] + const float c_w = -viewMatrix.m[14]; // w in clip space + + // For each edge, test the rectangle corner closest to it + // If its distance to the edge is negative, the whole rectangle is outside the screen + // Note: the test is conservative and can return false positives in some cases + // The test is done in clip space [-1, +1] + // e.g. left culling <==> (c_cx + c_ex) / cw < -1 <==> (c_cx + c_ex) < -cw + + // Left + if (c_cx + c_ex < -c_w) + { + return true; + } + + // Right + if (c_cx - c_ex > c_w) + { + return true; + } + + // Bottom + if (c_cy + c_ey < -c_w) + { + return true; + } + + // Top + if (c_cy - c_ey > c_w) + { + return true; + } + + return false; + } + + + Color4B spColorToColor4B(const spColor& color) + { + return { (GLubyte)(color.r * 255.f), (GLubyte)(color.g * 255.f), (GLubyte)(color.b * 255.f), (GLubyte)(color.a * 255.f) }; + } + } diff --git a/spine-cocos2dx/src/spine/SkeletonRenderer.h b/spine-cocos2dx/src/spine/SkeletonRenderer.h index 3f41e4a5d..f261daceb 100644 --- a/spine-cocos2dx/src/spine/SkeletonRenderer.h +++ b/spine-cocos2dx/src/spine/SkeletonRenderer.h @@ -49,12 +49,11 @@ public: 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; - spSkeleton* getSkeleton(); + spSkeleton* getSkeleton() const; void setTimeScale(float scale); float getTimeScale() const; @@ -68,6 +67,9 @@ public: void setDebugMeshesEnabled(bool enabled); bool getDebugMeshesEnabled() const; + + void setDebugBoundingRectEnabled(bool enabled); + bool getDebugBoundingRectEnabled() const; // --- Convenience methods for common Skeleton_* functions. void updateWorldTransform (); @@ -113,9 +115,6 @@ public: 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(spSkeleton* skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false); @@ -139,6 +138,7 @@ protected: virtual AttachmentVertices* getAttachmentVertices (spRegionAttachment* attachment) const; virtual AttachmentVertices* getAttachmentVertices (spMeshAttachment* attachment) const; void setupGLProgramState(bool twoColorTintEnabled); + virtual void drawDebug (cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t transformFlags); bool _ownsSkeletonData; bool _ownsSkeleton; @@ -152,8 +152,10 @@ protected: bool _debugSlots; bool _debugBones; bool _debugMeshes; + bool _debugBoundingRect; spSkeletonClipping* _clipper; spVertexEffect* _effect; + cocos2d::Rect _boundingRect; int _startSlotIndex; int _endSlotIndex; diff --git a/spine-cocos2dx/src/spine/SkeletonTwoColorBatch.cpp b/spine-cocos2dx/src/spine/SkeletonTwoColorBatch.cpp index 41b323494..99b45ed84 100644 --- a/spine-cocos2dx/src/spine/SkeletonTwoColorBatch.cpp +++ b/spine-cocos2dx/src/spine/SkeletonTwoColorBatch.cpp @@ -57,7 +57,7 @@ void TwoColorTrianglesCommand::init(float globalOrder, GLuint textureID, GLProgr 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); + CCLOGERROR("Resize indexCount from %d to %d, size must be multiple times of 3", count, _triangles.indexCount); } _mv = mv; From d0d22bb344989d0166ff03e32aedbd50f3279f0b Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 17 Oct 2018 11:23:44 +0200 Subject: [PATCH 02/17] [libgdx] AnimationState javadoc. --- .../spine/AnimationState.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index 2d4a1ec6d..76d1a1daa 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -534,9 +534,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 * 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 delay. 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 delay (ie the mix ends at (delay = 0) or + * before (delay < 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 * after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */ public TrackEntry addAnimation (int trackIndex, Animation animation, boolean loop, float delay) { @@ -598,9 +599,10 @@ public class AnimationState { * {@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 delay. 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 delay (ie the mix ends at (delay = 0) or + * before (delay < 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 * after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */ public TrackEntry addEmptyAnimation (int trackIndex, float mixDuration, float delay) { @@ -859,9 +861,10 @@ public class AnimationState { this.loop = loop; } - /** Seconds to postpone playing the animation. When a track entry is the current track entry, delay postpones - * incrementing the {@link #getTrackTime()}. When a track entry is queued, delay 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, delay + * postpones incrementing the {@link #getTrackTime()}. When this track entry is queued, delay 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 delay). */ public float getDelay () { return delay; } From 9be30c44cd5fd8665f2727632e04ad4de80ab6de Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 17 Oct 2018 14:52:15 +0200 Subject: [PATCH 03/17] [libgdx] Fixed tangents at position 0. Calculating the tangent requires more precision than the position. http://esotericsoftware.com/forum/Glitch-with-the-bones-constrained-to-the-path-10969 Repro: http://n4te.com/x/173-path-tangent-precision.spine --- .../esotericsoftware/spine/PathConstraint.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 377f034b3..e759d75e3 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/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, 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 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; out[o] = x; 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 () { From 554c5f0cf52c3e0a54a5b198a58c660fe994987a Mon Sep 17 00:00:00 2001 From: pharan Date: Wed, 17 Oct 2018 22:17:07 +0800 Subject: [PATCH 04/17] [unity] Spine/Special/Skeleton Grayscale shader --- .../Spine-Special-Skeleton-Grayscale.shader | 105 ++++++++++++++++++ ...ine-Special-Skeleton-Grayscale.shader.meta | 9 ++ 2 files changed, 114 insertions(+) create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Shaders/Spine-Special-Skeleton-Grayscale.shader create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Shaders/Spine-Special-Skeleton-Grayscale.shader.meta diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Shaders/Spine-Special-Skeleton-Grayscale.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Shaders/Spine-Special-Skeleton-Grayscale.shader new file mode 100644 index 000000000..112c46ebb --- /dev/null +++ b/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" +} \ No newline at end of file diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Shaders/Spine-Special-Skeleton-Grayscale.shader.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Shaders/Spine-Special-Skeleton-Grayscale.shader.meta new file mode 100644 index 000000000..6db4577e4 --- /dev/null +++ b/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: From 6dec3d37fac6812a2ceb7e9e9243cbf5305e4dfc Mon Sep 17 00:00:00 2001 From: pharan Date: Wed, 17 Oct 2018 22:18:06 +0800 Subject: [PATCH 05/17] [unity] Fix MeshGenerator unnecessarily replacing buffer objects. --- .../Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs index 9b76040c5..99a85748a 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs @@ -457,10 +457,10 @@ namespace Spine.Unity { if (submeshes.Count - 1 < submeshIndex) { submeshes.Resize(submeshIndex + 1); - if (submeshes.Items[submeshIndex] == null) - submeshes.Items[submeshIndex] = new ExposedList(); } var submesh = submeshes.Items[submeshIndex]; + if (submesh == null) + submeshes.Items[submeshIndex] = submesh = new ExposedList(); submesh.Clear(false); var skeleton = instruction.skeleton; From fac440c434fad82c7c96d4f774176e4dd4e14a4c Mon Sep 17 00:00:00 2001 From: pharan Date: Wed, 17 Oct 2018 22:18:53 +0800 Subject: [PATCH 06/17] [unity] Allow user to preallocate MeshGenerator buffer size. --- .../Components/SkeletonRenderer.cs | 7 ++++ .../spine-unity/Mesh Generation/SpineMesh.cs | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs index 6e70f270c..53bbc5a3f 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs @@ -172,6 +172,13 @@ namespace Spine.Unity { if (skeleton != null) skeleton.SetToSetupPose(); } + ///

+ /// Sets a minimum buffer size for the internal MeshGenerator to prevent excess allocations during animation. + /// + public void EnsureMeshGeneratorCapacity (int minimumVertexCount) { + meshGenerator.EnsureVertexCapacity(minimumVertexCount); + } + /// /// Initialize this component. Attempts to load the SkeletonData and creates the internal Skeleton object and buffers. /// If set to true, it will overwrite internal objects if they were already generated. Otherwise, the initialized component will ignore subsequent calls to initialize. diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs index 99a85748a..c750a78e6 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs @@ -1057,6 +1057,38 @@ namespace Spine.Unity { } #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(minimumVertexCount); + uv3 = new ExposedList(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 () { vertexBuffer.TrimExcess(); uvBuffer.TrimExcess(); From 9867c975ab03b5c9aa6d8f345e2b5b43857c414e Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 17 Oct 2018 21:57:10 +0200 Subject: [PATCH 07/17] [libgdx] More javadoc improvements. --- .../spine/AnimationState.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index 76d1a1daa..cada4633f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -424,10 +424,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. *

* 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 () { boolean oldDrainDisabled = queue.drainDisabled; queue.drainDisabled = true; @@ -438,10 +438,10 @@ public class AnimationState { 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. *

* 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) { if (trackIndex >= tracks.size) return; TrackEntry current = tracks.get(trackIndex); @@ -973,11 +973,11 @@ public class AnimationState { 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. *

- * 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 () { return alpha; } @@ -987,7 +987,7 @@ public class AnimationState { } /** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the - * eventThreshold, event timelines for the animation being mixed out will be applied. Defaults to 0, so event + * eventThreshold, 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. */ public float getEventThreshold () { return eventThreshold; @@ -998,8 +998,8 @@ public class AnimationState { } /** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the - * attachmentThreshold, 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. */ + * attachmentThreshold, 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 () { return attachmentThreshold; } @@ -1009,7 +1009,7 @@ public class AnimationState { } /** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the - * drawOrderThreshold, draw order timelines for the animation being mixed out will be applied. Defaults to 0, + * drawOrderThreshold, 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. */ public float getDrawOrderThreshold () { return drawOrderThreshold; From 84ae36f90f578fd7c15833721e7445ad72adf565 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 17 Oct 2018 22:51:51 +0200 Subject: [PATCH 08/17] [libgdx] Improved fix for timeScale not returning to the setup pose. We should complete a mix frozen by timeScale 0 only when `to` has been mixed out. However, it would probably be better for TrackEntry#timeScale to not affect the mix time at all. #1194 --- .../com/esotericsoftware/spine/AnimationState.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index cada4633f..3166ce1e7 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -172,6 +172,12 @@ public class AnimationState { // Require mixTime > 0 to ensure the mixing from entry was applied at least once. if (to.mixTime > 0 && to.mixTime >= to.mixDuration) { + if (from.timeScale == 0) { + // from has 0 timeScale and has been mixed out, remove its mix and apply it one more time to return to the setup pose. + from.timeScale = 1; + from.mixTime = 0; + from.mixDuration = 0; + } // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). if (from.totalAlpha == 0 || to.mixDuration == 0) { to.mixingFrom = from.mixingFrom; @@ -182,13 +188,6 @@ public class AnimationState { 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; to.mixTime += delta * to.timeScale; return false; From f95465ff964c8ee24ac4a5ab215092ed66500825 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 18 Oct 2018 00:57:58 +0200 Subject: [PATCH 09/17] [libgdx] Mix time is no longer affected by TrackEntry#timeScale. Also contains a fix for the leftover time when the next track entry's delay is reached. The remaining time is converted from `current` back to AnimationState time, then to `next` time. --- .../spine/AnimationState.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index 3166ce1e7..3d309ce4f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -127,11 +127,11 @@ public class AnimationState { float nextTime = current.trackLast - next.delay; if (nextTime >= 0) { next.delay = 0; - next.trackTime = nextTime + delta * next.timeScale; + next.trackTime = (nextTime / current.timeScale + delta) * next.timeScale; current.trackTime += currentDelta; setCurrent(i, next, true); while (next.mixingFrom != null) { - next.mixTime += currentDelta; + next.mixTime += delta; next = next.mixingFrom; } continue; @@ -172,12 +172,6 @@ public class AnimationState { // Require mixTime > 0 to ensure the mixing from entry was applied at least once. if (to.mixTime > 0 && to.mixTime >= to.mixDuration) { - if (from.timeScale == 0) { - // from has 0 timeScale and has been mixed out, remove its mix and apply it one more time to return to the setup pose. - from.timeScale = 1; - from.mixTime = 0; - from.mixDuration = 0; - } // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). if (from.totalAlpha == 0 || to.mixDuration == 0) { to.mixingFrom = from.mixingFrom; @@ -189,7 +183,7 @@ public class AnimationState { } from.trackTime += delta * from.timeScale; - to.mixTime += delta * to.timeScale; + to.mixTime += delta; return false; } @@ -534,9 +528,9 @@ 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 * equivalent to calling {@link #setAnimation(int, Animation, boolean)}. * @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 delay (ie the mix ends at (delay = 0) or - * before (delay < 0) the previous track entry duration). If the previous entry is looping, its next - * loop completion is used instead of its duration. + * minus any mix duration (from the {@link AnimationStateData}) plus the specified delay (ie the mix + * ends at (delay = 0) or before (delay < 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 * after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */ public TrackEntry addAnimation (int trackIndex, Animation animation, boolean loop, float delay) { @@ -765,8 +759,8 @@ public class AnimationState { 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. *

* See TrackEntry {@link TrackEntry#getTimeScale()} for affecting a single animation. */ public float getTimeScale () { @@ -863,7 +857,9 @@ public class AnimationState { /** Seconds to postpone playing the animation. When this track entry is the current track entry, delay * postpones incrementing the {@link #getTrackTime()}. When this track entry is queued, delay 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 delay). */ + * track entry {@link TrackEntry#getTrackTime()} >= this track entry's delay). + *

+ * {@link #getTimeScale()} affects the delay. */ public float getDelay () { return delay; } @@ -945,10 +941,15 @@ public class AnimationState { 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. *

- * If timeScale 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. + *

+ * When using {@link AnimationState#addAnimation(int, Animation, boolean, float)} with a delay <= 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. *

* See AnimationState {@link AnimationState#getTimeScale()} for affecting all animations. */ public float getTimeScale () { @@ -1048,7 +1049,8 @@ public class AnimationState { * track entry only before {@link AnimationState#update(float)} is first called. *

* When using {@link AnimationState#addAnimation(int, Animation, boolean, float)} with a delay <= 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 () { return mixDuration; } From 3554fece0966d45c09983fb0fdeff277bc71099c Mon Sep 17 00:00:00 2001 From: pharan Date: Thu, 18 Oct 2018 21:34:11 +0800 Subject: [PATCH 10/17] [unity] Default loop preference for editor instantiation. --- .../Editor/spine-unity/Editor/SpineEditorUtilities.cs | 10 ++++++++++ .../SkeletonGraphic/Editor/SkeletonGraphicInspector.cs | 1 + 2 files changed, 11 insertions(+) diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs index 282ffdfdd..00cc87aa5 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs +++ b/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"; 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 string SHOW_HIERARCHY_ICONS_KEY = "SPINE_SHOW_HIERARCHY_ICONS"; public static bool showHierarchyIcons = DEFAULT_SHOW_HIERARCHY_ICONS; @@ -327,6 +331,11 @@ namespace Spine.Unity.Editor { if (EditorGUI.EndChangeCheck()) 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.LabelField("Handles and Gizmos", EditorStyles.boldLabel); EditorGUI.BeginChangeCheck(); @@ -1246,6 +1255,7 @@ namespace Spine.Unity.Editor { throw e; } + newSkeletonAnimation.loop = SpineEditorUtilities.Preferences.defaultInstantiateLoop; newSkeletonAnimation.skeleton.Update(0); newSkeletonAnimation.state.Update(0); newSkeletonAnimation.state.Apply(newSkeletonAnimation.skeleton); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs index 573051889..29a31dfdf 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs +++ b/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]; graphic.MeshGenerator.settings.zSpacing = SpineEditorUtilities.Preferences.defaultZSpacing; + graphic.startingLoop = SpineEditorUtilities.Preferences.defaultInstantiateLoop; graphic.Initialize(false); if (skin != null) graphic.Skeleton.SetSkin(skin); graphic.initialSkinName = skin.Name; From 62769c3e7197da20668ce1bdbb38f807ed6391f7 Mon Sep 17 00:00:00 2001 From: pharan Date: Thu, 18 Oct 2018 21:34:36 +0800 Subject: [PATCH 11/17] [unity] Cleanup old renaming. --- .../Editor/SkeletonUtilityBoneInspector.cs | 6 ++-- .../Editor/SkeletonUtilityInspector.cs | 32 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs index e1b798f52..4f339a7e2 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs +++ b/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); SkeletonUtilityBone[] newUtilityBones = go.GetComponentsInChildren(); foreach (SkeletonUtilityBone utilBone in newUtilityBones) - SkeletonGameObjectsInspector.AttachIcon(utilBone); + SkeletonUtilityInspector.AttachIcon(utilBone); } } else { var bone = (Bone)obj; GameObject go = skeletonUtility.SpawnBone(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale); - SkeletonGameObjectsInspector.AttachIcon(go.GetComponent()); + SkeletonUtilityInspector.AttachIcon(go.GetComponent()); Selection.activeGameObject = go; EditorGUIUtility.PingObject(go); } @@ -272,7 +272,7 @@ namespace Spine.Unity.Editor { void SpawnOverride () { GameObject go = skeletonUtility.SpawnBone(utilityBone.bone, utilityBone.transform.parent, SkeletonUtilityBone.Mode.Override, utilityBone.position, utilityBone.rotation, utilityBone.scale); go.name = go.name + " [Override]"; - SkeletonGameObjectsInspector.AttachIcon(go.GetComponent()); + SkeletonUtilityInspector.AttachIcon(go.GetComponent()); Selection.activeGameObject = go; EditorGUIUtility.PingObject(go); } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs index 4ccc85a6e..5734a7a7c 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs @@ -41,17 +41,17 @@ namespace Spine.Unity.Editor { using Icons = SpineEditorUtilities.Icons; [CustomEditor(typeof(SkeletonUtility))] - public class SkeletonGameObjectsInspector : UnityEditor.Editor { + public class SkeletonUtilityInspector : UnityEditor.Editor { - SkeletonUtility skeletonGameObjects; + SkeletonUtility skeletonUtility; Skeleton skeleton; SkeletonRenderer skeletonRenderer; bool isPrefab; readonly GUIContent SpawnHierarchyButtonLabel = new GUIContent("Spawn Hierarchy", Icons.skeleton); void OnEnable () { - skeletonGameObjects = (SkeletonUtility)target; - skeletonRenderer = skeletonGameObjects.GetComponent(); + skeletonUtility = (SkeletonUtility)target; + skeletonRenderer = skeletonUtility.GetComponent(); skeleton = skeletonRenderer.Skeleton; if (skeleton == null) { @@ -78,7 +78,7 @@ namespace Spine.Unity.Editor { EditorGUILayout.PropertyField(serializedObject.FindProperty("boneRoot"), SpineInspectorUtility.TempContent("Skeleton Root")); - bool hasRootBone = skeletonGameObjects.boneRoot != null; + bool hasRootBone = skeletonUtility.boneRoot != null; if (!hasRootBone) 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 (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 () { - 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 () { - 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 () { - 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 () { - 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); } } From d78b2fd024141caf74850894b485f11170806d44 Mon Sep 17 00:00:00 2001 From: pharan Date: Thu, 18 Oct 2018 21:35:33 +0800 Subject: [PATCH 12/17] [unity] Use EnsureCapacity for predetermined list sizes. --- .../Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs | 4 ++-- .../Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs index c750a78e6..e779113fc 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs @@ -1469,13 +1469,13 @@ namespace Spine.Unity { this.hasActiveClipping = other.hasActiveClipping; this.rawVertexCount = other.rawVertexCount; this.attachments.Clear(false); - this.attachments.GrowIfNeeded(other.attachments.Capacity); + this.attachments.EnsureCapacity(other.attachments.Capacity); this.attachments.Count = other.attachments.Count; other.attachments.CopyTo(this.attachments.Items); #endif this.submeshInstructions.Clear(false); - this.submeshInstructions.GrowIfNeeded(other.submeshInstructions.Capacity); + this.submeshInstructions.EnsureCapacity(other.submeshInstructions.Capacity); this.submeshInstructions.Count = other.submeshInstructions.Count; other.submeshInstructions.CopyTo(this.submeshInstructions.Items); } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs index 48460214f..bd4286a51 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs @@ -557,7 +557,8 @@ namespace Spine { var drawOrder = skeleton.drawOrder; drawOrder.Clear(false); - drawOrder.GrowIfNeeded(n); + drawOrder.EnsureCapacity(n); + drawOrder.Count = n; System.Array.Copy(slotsItems, drawOrder.Items, n); } From 8d74ef06ac4b3ca7aa211e1d019ef33b34053d3d Mon Sep 17 00:00:00 2001 From: pharan Date: Thu, 18 Oct 2018 21:36:22 +0800 Subject: [PATCH 13/17] [unity] Prospective resizing for MeshGenerator AddSubmesh branch. --- .../Runtime/spine-unity/Mesh Generation/SpineMesh.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs index e779113fc..dbe8b6148 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs @@ -570,10 +570,13 @@ namespace Spine.Unity { // Add data to vertex buffers { 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; } From 27828048fb25ce2f639d1b4cf9b5b9b584f70b31 Mon Sep 17 00:00:00 2001 From: pharan Date: Thu, 18 Oct 2018 21:37:24 +0800 Subject: [PATCH 14/17] [unity] Fix MeshGenerator unnecessarily disposing buffer objects. --- .../Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs index dbe8b6148..ea080b115 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs @@ -455,9 +455,10 @@ namespace Spine.Unity { public void AddSubmesh (SubmeshInstruction instruction, bool updateTriangles = true) { var settings = this.settings; - if (submeshes.Count - 1 < submeshIndex) { - submeshes.Resize(submeshIndex + 1); - } + int newSubmeshCount = submeshIndex + 1; + if (submeshes.Items.Length < newSubmeshCount) + submeshes.Resize(newSubmeshCount); + submeshes.Count = newSubmeshCount; var submesh = submeshes.Items[submeshIndex]; if (submesh == null) submeshes.Items[submeshIndex] = submesh = new ExposedList(); From 81af7309e8324ec032d4cea161b8c44c7babdc1b Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 19 Oct 2018 00:33:22 +0200 Subject: [PATCH 15/17] Batch and PolygonBatch draw methods accept float[] vertex data in the Batch format (5 attribute/vertex), new methods are provided to accept float[] vertex data containing two colors. closes #1121 --- .../spine/SkeletonRenderer.java | 12 +- .../spine/utils/TwoColorPolygonBatch.java | 163 +++++++++++++----- 2 files changed, 124 insertions(+), 51 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java index 19a9de0db..59159f4e8 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/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 * next. */ 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); return; + } else if (batch instanceof PolygonSpriteBatch) { + draw((PolygonSpriteBatch)batch, skeleton); + return; } VertexEffect vertexEffect = this.vertexEffect; @@ -357,7 +357,7 @@ public class SkeletonRenderer { FloatArray clippedVertices = clipper.getClippedVertices(); ShortArray clippedTriangles = clipper.getClippedTriangles(); 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); } else { if (vertexEffect != null) { @@ -386,7 +386,7 @@ public class SkeletonRenderer { 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); } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/TwoColorPolygonBatch.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/TwoColorPolygonBatch.java index c1d8cf652..dd0de951c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/TwoColorPolygonBatch.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/TwoColorPolygonBatch.java @@ -31,10 +31,15 @@ package com.esotericsoftware.spine.utils; 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.Texture; +import com.badlogic.gdx.graphics.VertexAttribute; import com.badlogic.gdx.graphics.VertexAttributes.Usage; 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.TextureRegion; 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.utils.NumberUtils; -public class TwoColorPolygonBatch implements Batch { +/** A batch that renders polygons and performs tinting using a light and dark color. + *

+ * 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 SPRITE_SIZE = 4 * VERTEX_SIZE; private final Mesh mesh; private final float[] vertices; - private final float[] tempSpriteVertices = new float[SPRITE_SIZE]; private final short[] triangles; private final Matrix4 transformMatrix = new Matrix4(); private final Matrix4 projectionMatrix = new Matrix4(); @@ -313,30 +323,6 @@ public class TwoColorPolygonBatch implements Batch { 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 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) { @@ -746,28 +732,74 @@ public class TwoColorPolygonBatch implements Batch { 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. */ - @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."); - // 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 > 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."); + + 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 float[] vertices = this.vertices; @@ -794,6 +826,47 @@ public class TwoColorPolygonBatch implements Batch { 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 public void draw (TextureRegion region, float x, float y) { draw(region, x, y, region.getRegionWidth(), region.getRegionHeight()); From 3851e201d80b2e4d2195b38c7980ea3cf9928c44 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sat, 20 Oct 2018 18:01:46 +0200 Subject: [PATCH 16/17] [libgdx] Fixed worldToLocalRotation and localToWorldRotation to account for local rotation and shearX. --- .../spine-libgdx/src/com/esotericsoftware/spine/Bone.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java index 1994c6911..77955c4ff 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/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. */ public float worldToLocalRotation (float 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. */ public float localToWorldRotation (float localRotation) { + localRotation -= rotation - shearX; float sin = sinDeg(localRotation), cos = cosDeg(localRotation); return atan2(cos * c + sin * d, cos * a + sin * b) * radDeg; } From 8c55b1b5d0db4cd5ed2c50be2e02a5124fd70177 Mon Sep 17 00:00:00 2001 From: pharan Date: Wed, 24 Oct 2018 23:57:49 +0800 Subject: [PATCH 17/17] [csharp] Fix world-to-local and local-to-world rotation functions to account for local rotation and shearX. based on : https://github.com/EsotericSoftware/spine-runtimes/commit/3851e201d80b2e4d2195b38c7980ea3cf9928c44 --- spine-csharp/src/Bone.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spine-csharp/src/Bone.cs b/spine-csharp/src/Bone.cs index 21b471894..acc1a42ed 100644 --- a/spine-csharp/src/Bone.cs +++ b/spine-csharp/src/Bone.cs @@ -329,10 +329,11 @@ namespace Spine { public float WorldToLocalRotation (float 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) { + localRotation -= rotation - shearX; float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; }