/****************************************************************************** * Spine Runtimes License Agreement * Last updated January 1, 2020. Replaces all prior versions. * * Copyright (c) 2013-2020, Esoteric Software LLC * * Integration of the Spine Runtimes into software or otherwise creating * derivative works of the Spine Runtimes is permitted under the terms and * conditions of Section 2 of the Spine Editor License Agreement: * http://esotericsoftware.com/spine-editor-license * * Otherwise, it is permitted to integrate the Spine Runtimes into software * or otherwise create derivative works of the Spine Runtimes (collectively, * "Products"), provided that each user of the Products must obtain their own * Spine Editor license and redistribution of the Products in any form must * include this license and copyright notice. * * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ #include "spine-creator-support/SkeletonRenderer.h" #include #include "2d/renderer/RenderDrawInfo.h" #include "2d/renderer/RenderEntity.h" #include "MiddlewareMacro.h" #include "SharedBufferManager.h" #include "SkeletonDataMgr.h" #include "base/DeferredReleasePool.h" #include "base/TypeDef.h" #include "base/memory/Memory.h" #include "gfx-base/GFXDef.h" #include "math/Math.h" #include "math/Vec3.h" #include "renderer/core/MaterialInstance.h" #include "spine-creator-support/AttachmentVertices.h" #include "spine-creator-support/spine-cocos2dx.h" USING_NS_MW; // NOLINT(google-build-using-namespace) using namespace spine; // NOLINT(google-build-using-namespace) using namespace cc; // NOLINT(google-build-using-namespace) using namespace cc::gfx; // NOLINT(google-build-using-namespace) using std::max; using std::min; static const std::string TECH_STAGE = "opaque"; static const std::string TEXTURE_KEY = "texture"; static spine::Cocos2dTextureLoader textureLoader; static std::vector _slotTextureSet{}; enum DebugType { NONE = 0, SLOTS, MESH, BONES }; SkeletonRenderer *SkeletonRenderer::create() { return new SkeletonRenderer(); } SkeletonRenderer *SkeletonRenderer::createWithSkeleton(Skeleton *skeleton, bool ownsSkeleton, bool ownsSkeletonData) { return new SkeletonRenderer(skeleton, ownsSkeleton, ownsSkeletonData); } SkeletonRenderer *SkeletonRenderer::createWithData(SkeletonData *skeletonData, bool ownsSkeletonData) { return new SkeletonRenderer(skeletonData, ownsSkeletonData); } SkeletonRenderer *SkeletonRenderer::createWithFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale) { return new SkeletonRenderer(skeletonDataFile, atlasFile, scale); } void SkeletonRenderer::initialize() { if (_clipper == nullptr) { _clipper = new (__FILE__, __LINE__) SkeletonClipping(); } if (_sharedBufferOffset == nullptr) { // store global TypedArray begin and end offset _sharedBufferOffset = new cc::middleware::IOTypedArray(se::Object::TypedArrayType::UINT32, sizeof(uint32_t) * 2); } _skeleton->setToSetupPose(); _skeleton->updateWorldTransform(); } void SkeletonRenderer::beginSchedule() { MiddlewareManager::getInstance()->addTimer(this); } void SkeletonRenderer::onEnable() { beginSchedule(); } void SkeletonRenderer::onDisable() { stopSchedule(); } void SkeletonRenderer::stopSchedule() { MiddlewareManager::getInstance()->removeTimer(this); if (_sharedBufferOffset) { _sharedBufferOffset->reset(); _sharedBufferOffset->clear(); } if (_debugBuffer) { _debugBuffer->reset(); _debugBuffer->clear(); } } void SkeletonRenderer::setSkeletonData(SkeletonData *skeletonData, bool ownsSkeletonData) { _skeleton = new (__FILE__, __LINE__) Skeleton(skeletonData); _ownsSkeletonData = ownsSkeletonData; } SkeletonRenderer::SkeletonRenderer() = default; SkeletonRenderer::SkeletonRenderer(Skeleton *skeleton, bool ownsSkeleton, bool ownsSkeletonData, bool ownsAtlas) { initWithSkeleton(skeleton, ownsSkeleton, ownsSkeletonData, ownsAtlas); } SkeletonRenderer::SkeletonRenderer(SkeletonData *skeletonData, bool ownsSkeletonData) { initWithData(skeletonData, ownsSkeletonData); } SkeletonRenderer::SkeletonRenderer(const std::string &skeletonDataFile, const std::string &atlasFile, float scale) { initWithJsonFile(skeletonDataFile, atlasFile, scale); } SkeletonRenderer::~SkeletonRenderer() { CC_SAFE_RELEASE(_effectDelegate); if (_ownsSkeletonData) delete _skeleton->getData(); if (_ownsSkeleton) delete _skeleton; if (_ownsAtlas && _atlas) delete _atlas; delete _attachmentLoader; delete _clipper; if (_debugBuffer) { delete _debugBuffer; _debugBuffer = nullptr; } if (_sharedBufferOffset) { delete _sharedBufferOffset; _sharedBufferOffset = nullptr; } for (auto *draw : _drawInfoArray) { CC_SAFE_DELETE(draw); } for (auto &item : _materialCaches) { CC_SAFE_DELETE(item.second); } stopSchedule(); } void SkeletonRenderer::initWithUUID(const std::string &uuid) { _ownsSkeleton = true; _uuid = uuid; SkeletonData *skeletonData = SkeletonDataMgr::getInstance()->retainByUUID(uuid); CC_ASSERT(skeletonData); setSkeletonData(skeletonData, false); initialize(); } void SkeletonRenderer::initWithSkeleton(Skeleton *skeleton, bool ownsSkeleton, bool ownsSkeletonData, bool ownsAtlas) { _skeleton = skeleton; _ownsSkeleton = ownsSkeleton; _ownsSkeletonData = ownsSkeletonData; _ownsAtlas = ownsAtlas; initialize(); } void SkeletonRenderer::initWithData(SkeletonData *skeletonData, bool ownsSkeletonData) { _ownsSkeleton = true; setSkeletonData(skeletonData, ownsSkeletonData); initialize(); } void SkeletonRenderer::initWithJsonFile(const std::string &skeletonDataFile, Atlas *atlas, float scale) { _atlas = atlas; _attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas); SkeletonJson json(_attachmentLoader); json.setScale(scale); SkeletonData *skeletonData = json.readSkeletonDataFile(skeletonDataFile.c_str()); CC_ASSERT(skeletonData); // Can use json.getError() to get error message. _ownsSkeleton = true; setSkeletonData(skeletonData, true); initialize(); } void SkeletonRenderer::initWithJsonFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale) { _atlas = new (__FILE__, __LINE__) Atlas(atlasFile.c_str(), &textureLoader); CC_ASSERT(_atlas); _attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas); SkeletonJson json(_attachmentLoader); json.setScale(scale); SkeletonData *skeletonData = json.readSkeletonDataFile(skeletonDataFile.c_str()); CC_ASSERT(skeletonData); // Can use json.getError() to get error message. _ownsSkeleton = true; _ownsAtlas = true; setSkeletonData(skeletonData, true); initialize(); } void SkeletonRenderer::initWithBinaryFile(const std::string &skeletonDataFile, Atlas *atlas, float scale) { _atlas = atlas; _attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas); SkeletonBinary binary(_attachmentLoader); binary.setScale(scale); SkeletonData *skeletonData = binary.readSkeletonDataFile(skeletonDataFile.c_str()); CC_ASSERT(skeletonData); // Can use binary.getError() to get error message. _ownsSkeleton = true; setSkeletonData(skeletonData, true); initialize(); } void SkeletonRenderer::initWithBinaryFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale) { _atlas = new (__FILE__, __LINE__) Atlas(atlasFile.c_str(), &textureLoader); CC_ASSERT(_atlas); _attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas); SkeletonBinary binary(_attachmentLoader); binary.setScale(scale); SkeletonData *skeletonData = binary.readSkeletonDataFile(skeletonDataFile.c_str()); CC_ASSERT(skeletonData); // Can use binary.getError() to get error message. _ownsSkeleton = true; _ownsAtlas = true; setSkeletonData(skeletonData, true); initialize(); } void SkeletonRenderer::render(float /*deltaTime*/) { if (!_skeleton) return; auto *entity = _entity; entity->clearDynamicRenderDrawInfos(); _sharedBufferOffset->reset(); _sharedBufferOffset->clear(); // avoid other place call update. auto *mgr = MiddlewareManager::getInstance(); if (!mgr->isRendering) return; auto *attachMgr = mgr->getAttachInfoMgr(); auto *attachInfo = attachMgr->getBuffer(); if (!attachInfo) return; // store attach info offset _sharedBufferOffset->writeUint32(static_cast(attachInfo->getCurPos()) / sizeof(uint32_t)); // If opacity is 0,then return. if (_skeleton->getColor().a == 0) { return; } auto &nodeWorldMat = entity->getNode()->getWorldMatrix(); // color range is [0.0, 1.0] cc::middleware::Color4F color; cc::middleware::Color4F darkColor; AttachmentVertices *attachmentVertices = nullptr; bool inRange = !(_startSlotIndex != -1 || _endSlotIndex != -1); auto vertexFormat = _useTint ? VF_XYZUVCC : VF_XYZUVC; cc::middleware::MeshBuffer *mb = mgr->getMeshBuffer(vertexFormat); cc::middleware::IOBuffer &vb = mb->getVB(); cc::middleware::IOBuffer &ib = mb->getIB(); // vertex size int bytes with one color unsigned int vbs1 = sizeof(V3F_T2F_C4B); // vertex size in floats with one color unsigned int vs1 = vbs1 / sizeof(float); // vertex size int bytes with two color unsigned int vbs2 = sizeof(V3F_T2F_C4B_C4B); // verex size in floats with two color unsigned int vs2 = vbs2 / sizeof(float); auto vbs = vbs1; if (_useTint) { vbs = vbs2; } unsigned int vbSize = 0; unsigned int ibSize = 0; int curBlendSrc = -1; int curBlendDst = -1; int curBlendMode = -1; int preBlendMode = -1; uint32_t curISegLen = 0; cc::Texture2D *preTexture = nullptr; cc::Texture2D *curTexture = nullptr; RenderDrawInfo *curDrawInfo = nullptr; int materialLen = 0; Slot *slot = nullptr; int isFull = 0; if (_debugSlots || _debugBones || _debugMesh) { // If enable debug draw,then init debug buffer. if (_debugBuffer == nullptr) { _debugBuffer = new cc::middleware::IOTypedArray(se::Object::TypedArrayType::FLOAT32, MAX_DEBUG_BUFFER_SIZE); } _debugBuffer->reset(); } auto flush = [&]() { // fill pre segment indices count field if (curDrawInfo) { curDrawInfo->setIbCount(curISegLen); } curDrawInfo = requestDrawInfo(materialLen); entity->addDynamicRenderDrawInfo(curDrawInfo); // prepare to fill new segment field curBlendMode = slot->getData().getBlendMode(); switch (curBlendMode) { case BlendMode_Additive: curBlendSrc = static_cast(_premultipliedAlpha ? BlendFactor::ONE : BlendFactor::SRC_ALPHA); curBlendDst = static_cast(BlendFactor::ONE); break; case BlendMode_Multiply: curBlendSrc = static_cast(BlendFactor::DST_COLOR); curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_ALPHA); break; case BlendMode_Screen: curBlendSrc = static_cast(BlendFactor::ONE); curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_COLOR); break; default: curBlendSrc = static_cast(_premultipliedAlpha ? BlendFactor::ONE : BlendFactor::SRC_ALPHA); curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_ALPHA); } auto *material = requestMaterial(curBlendSrc, curBlendDst); curDrawInfo->setMaterial(material); gfx::Texture *texture = curTexture->getGFXTexture(); gfx::Sampler *sampler = curTexture->getGFXSampler(); curDrawInfo->setTexture(texture); curDrawInfo->setSampler(sampler); auto *uiMeshBuffer = mb->getUIMeshBuffer(); curDrawInfo->setMeshBuffer(uiMeshBuffer); curDrawInfo->setIndexOffset(static_cast(ib.getCurPos()) / sizeof(uint16_t)); // reset pre blend mode to current preBlendMode = static_cast(slot->getData().getBlendMode()); // reset pre texture index to current preTexture = curTexture; // reset index segmentation count curISegLen = 0; // material length increased materialLen++; }; VertexEffect *effect = nullptr; if (_effectDelegate) { effect = _effectDelegate->getVertexEffect(); } if (effect) { effect->begin(*_skeleton); } auto &drawOrder = _skeleton->getDrawOrder(); for (size_t i = 0, n = drawOrder.size(); i < n; ++i) { isFull = 0; slot = drawOrder[i]; if (slot->getBone().isActive() == false) { continue; } if (_startSlotIndex >= 0 && _startSlotIndex == slot->getData().getIndex()) { inRange = true; } if (!inRange) { _clipper->clipEnd(*slot); continue; } if (_endSlotIndex >= 0 && _endSlotIndex == slot->getData().getIndex()) { inRange = false; } if (!slot->getAttachment()) { _clipper->clipEnd(*slot); continue; } cc::middleware::Triangles triangles; cc::middleware::TwoColorTriangles trianglesTwoColor; if (slot->getAttachment()->getRTTI().isExactly(RegionAttachment::rtti)) { auto *attachment = dynamic_cast(slot->getAttachment()); attachmentVertices = reinterpret_cast(attachment->getRendererObject()); // Early exit if attachment is invisible if (attachment->getColor().a == 0) { _clipper->clipEnd(*slot); continue; } if (!_useTint) { triangles.vertCount = attachmentVertices->_triangles->vertCount; vbSize = triangles.vertCount * sizeof(V3F_T2F_C4B); isFull |= vb.checkSpace(vbSize, true); triangles.verts = reinterpret_cast(vb.getCurBuffer()); memcpy(static_cast(triangles.verts), static_cast(attachmentVertices->_triangles->verts), vbSize); attachment->computeWorldVertices(slot->getBone(), reinterpret_cast(triangles.verts), 0, vs1); triangles.indexCount = attachmentVertices->_triangles->indexCount; ibSize = triangles.indexCount * sizeof(uint16_t); ib.checkSpace(ibSize, true); triangles.indices = reinterpret_cast(ib.getCurBuffer()); memcpy(triangles.indices, attachmentVertices->_triangles->indices, ibSize); } else { trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount; vbSize = trianglesTwoColor.vertCount * sizeof(V3F_T2F_C4B_C4B); isFull |= vb.checkSpace(vbSize, true); trianglesTwoColor.verts = reinterpret_cast(vb.getCurBuffer()); for (int ii = 0; ii < trianglesTwoColor.vertCount; ii++) { trianglesTwoColor.verts[ii].texCoord = attachmentVertices->_triangles->verts[ii].texCoord; } attachment->computeWorldVertices(slot->getBone(), reinterpret_cast(trianglesTwoColor.verts), 0, vs2); trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount; ibSize = trianglesTwoColor.indexCount * sizeof(uint16_t); ib.checkSpace(ibSize, true); trianglesTwoColor.indices = reinterpret_cast(ib.getCurBuffer()); memcpy(trianglesTwoColor.indices, attachmentVertices->_triangles->indices, ibSize); } color.r = attachment->getColor().r; color.g = attachment->getColor().g; color.b = attachment->getColor().b; color.a = attachment->getColor().a; if (_debugSlots) { _debugBuffer->writeFloat32(DebugType::SLOTS); _debugBuffer->writeFloat32(8); float *vertices = _useTint ? reinterpret_cast(trianglesTwoColor.verts) : reinterpret_cast(triangles.verts); unsigned int stride = _useTint ? vs2 : vs1; // Quadrangle has 4 vertex. for (int ii = 0; ii < 4; ii++) { _debugBuffer->writeFloat32(vertices[0]); _debugBuffer->writeFloat32(vertices[1]); vertices += stride; } } } else if (slot->getAttachment()->getRTTI().isExactly(MeshAttachment::rtti)) { auto *attachment = dynamic_cast(slot->getAttachment()); attachmentVertices = static_cast(attachment->getRendererObject()); // Early exit if attachment is invisible if (attachment->getColor().a == 0) { _clipper->clipEnd(*slot); continue; } if (!_useTint) { triangles.vertCount = attachmentVertices->_triangles->vertCount; vbSize = triangles.vertCount * sizeof(V3F_T2F_C4B); isFull |= vb.checkSpace(vbSize, true); triangles.verts = reinterpret_cast(vb.getCurBuffer()); memcpy(static_cast(triangles.verts), static_cast(attachmentVertices->_triangles->verts), vbSize); attachment->computeWorldVertices(*slot, 0, attachment->getWorldVerticesLength(), reinterpret_cast(triangles.verts), 0, vs1); triangles.indexCount = attachmentVertices->_triangles->indexCount; ibSize = triangles.indexCount * sizeof(uint16_t); ib.checkSpace(ibSize, true); triangles.indices = reinterpret_cast(ib.getCurBuffer()); memcpy(triangles.indices, attachmentVertices->_triangles->indices, ibSize); } else { trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount; vbSize = trianglesTwoColor.vertCount * sizeof(V3F_T2F_C4B_C4B); isFull |= vb.checkSpace(vbSize, true); trianglesTwoColor.verts = reinterpret_cast(vb.getCurBuffer()); for (int ii = 0; ii < trianglesTwoColor.vertCount; ii++) { trianglesTwoColor.verts[ii].texCoord = attachmentVertices->_triangles->verts[ii].texCoord; } attachment->computeWorldVertices(*slot, 0, attachment->getWorldVerticesLength(), reinterpret_cast(trianglesTwoColor.verts), 0, vs2); trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount; ibSize = trianglesTwoColor.indexCount * sizeof(uint16_t); ib.checkSpace(ibSize, true); trianglesTwoColor.indices = reinterpret_cast(ib.getCurBuffer()); memcpy(trianglesTwoColor.indices, attachmentVertices->_triangles->indices, ibSize); } color.r = attachment->getColor().r; color.g = attachment->getColor().g; color.b = attachment->getColor().b; color.a = attachment->getColor().a; if (_debugMesh) { int indexCount = _useTint ? trianglesTwoColor.indexCount : triangles.indexCount; uint16_t *indices = _useTint ? trianglesTwoColor.indices : triangles.indices; float *vertices = _useTint ? reinterpret_cast(trianglesTwoColor.verts) : reinterpret_cast(triangles.verts); unsigned int stride = _useTint ? vs2 : vs1; _debugBuffer->writeFloat32(DebugType::MESH); _debugBuffer->writeFloat32(static_cast(indexCount * 2)); for (int ii = 0; ii < indexCount; ii += 3) { unsigned int v1 = indices[ii] * stride; unsigned int v2 = indices[ii + 1] * stride; unsigned int v3 = indices[ii + 2] * stride; _debugBuffer->writeFloat32(vertices[v1]); _debugBuffer->writeFloat32(vertices[v1 + 1]); _debugBuffer->writeFloat32(vertices[v2]); _debugBuffer->writeFloat32(vertices[v2 + 1]); _debugBuffer->writeFloat32(vertices[v3]); _debugBuffer->writeFloat32(vertices[v3 + 1]); } } } else if (slot->getAttachment()->getRTTI().isExactly(ClippingAttachment::rtti)) { auto *clip = dynamic_cast(slot->getAttachment()); _clipper->clipStart(*slot, clip); continue; } else { _clipper->clipEnd(*slot); continue; } color.a = _skeleton->getColor().a * slot->getColor().a * color.a * _nodeColor.a * 255; // skip rendering if the color of this attachment is 0 if (color.a == 0) { _clipper->clipEnd(*slot); continue; } float multiplier = _premultipliedAlpha ? color.a : 255; float red = _nodeColor.r * _skeleton->getColor().r * color.r * multiplier; float green = _nodeColor.g * _skeleton->getColor().g * color.g * multiplier; float blue = _nodeColor.b * _skeleton->getColor().b * color.b * multiplier; color.r = red * slot->getColor().r; color.g = green * slot->getColor().g; color.b = blue * slot->getColor().b; if (slot->hasDarkColor()) { darkColor.r = red * slot->getDarkColor().r; darkColor.g = green * slot->getDarkColor().g; darkColor.b = blue * slot->getDarkColor().b; } else { darkColor.r = 0; darkColor.g = 0; darkColor.b = 0; } darkColor.a = _premultipliedAlpha ? 255 : 0; // One color tint logic if (!_useTint) { // Cliping logic Color4B light; light.r = (uint8_t)color.r; light.g = (uint8_t)color.g; light.b = (uint8_t)color.b; light.a = (uint8_t)color.a; if (_clipper->isClipping()) { _clipper->clipTriangles(reinterpret_cast(&triangles.verts[0].vertex), triangles.indices, triangles.indexCount, reinterpret_cast(&triangles.verts[0].texCoord), vs1); if (_clipper->getClippedTriangles().size() == 0) { _clipper->clipEnd(*slot); continue; } triangles.vertCount = static_cast(_clipper->getClippedVertices().size()) >> 1; vbSize = triangles.vertCount * sizeof(V3F_T2F_C4B); isFull |= vb.checkSpace(vbSize, true); triangles.verts = reinterpret_cast(vb.getCurBuffer()); triangles.indexCount = static_cast(_clipper->getClippedTriangles().size()); ibSize = triangles.indexCount * sizeof(uint16_t); ib.checkSpace(ibSize, true); triangles.indices = reinterpret_cast(ib.getCurBuffer()); memcpy(triangles.indices, _clipper->getClippedTriangles().buffer(), sizeof(uint16_t) * _clipper->getClippedTriangles().size()); float *verts = _clipper->getClippedVertices().buffer(); float *uvs = _clipper->getClippedUVs().buffer(); if (effect) { for (int v = 0, vn = triangles.vertCount, vv = 0; v < vn; ++v, vv += 2) { V3F_T2F_C4B *vertex = triangles.verts + v; vertex->vertex.x = verts[vv]; vertex->vertex.y = verts[vv + 1]; vertex->texCoord.u = uvs[vv]; vertex->texCoord.v = uvs[vv + 1]; effect->transform(vertex->vertex.x, vertex->vertex.y); vertex->color = light; } } else { for (int v = 0, vn = triangles.vertCount, vv = 0; v < vn; ++v, vv += 2) { V3F_T2F_C4B *vertex = triangles.verts + v; vertex->vertex.x = verts[vv]; vertex->vertex.y = verts[vv + 1]; vertex->texCoord.u = uvs[vv]; vertex->texCoord.v = uvs[vv + 1]; vertex->color = light; } } // No cliping logic } else { if (effect) { for (int v = 0, vn = triangles.vertCount; v < vn; ++v) { V3F_T2F_C4B *vertex = triangles.verts + v; effect->transform(vertex->vertex.x, vertex->vertex.y); vertex->color = light; } } else { for (int v = 0, vn = triangles.vertCount; v < vn; ++v) { V3F_T2F_C4B *vertex = triangles.verts + v; vertex->color = light; } } } } // Two color tint logic else { Color4B light; Color4B dark; light.r = (uint8_t)color.r; light.g = (uint8_t)color.g; light.b = (uint8_t)color.b; light.a = (uint8_t)color.a; dark.r = (uint8_t)darkColor.r; dark.g = (uint8_t)darkColor.g; dark.b = (uint8_t)darkColor.b; dark.a = (uint8_t)darkColor.a; if (_clipper->isClipping()) { _clipper->clipTriangles(reinterpret_cast(&trianglesTwoColor.verts[0].vertex), trianglesTwoColor.indices, trianglesTwoColor.indexCount, reinterpret_cast(&trianglesTwoColor.verts[0].texCoord), vs2); if (_clipper->getClippedTriangles().size() == 0) { _clipper->clipEnd(*slot); continue; } trianglesTwoColor.vertCount = static_cast(_clipper->getClippedVertices().size()) >> 1; vbSize = trianglesTwoColor.vertCount * sizeof(V3F_T2F_C4B_C4B); isFull |= vb.checkSpace(vbSize, true); trianglesTwoColor.verts = reinterpret_cast(vb.getCurBuffer()); trianglesTwoColor.indexCount = static_cast(_clipper->getClippedTriangles().size()); ibSize = trianglesTwoColor.indexCount * sizeof(uint16_t); trianglesTwoColor.indices = reinterpret_cast(ib.getCurBuffer()); memcpy(trianglesTwoColor.indices, _clipper->getClippedTriangles().buffer(), sizeof(uint16_t) * _clipper->getClippedTriangles().size()); float *verts = _clipper->getClippedVertices().buffer(); float *uvs = _clipper->getClippedUVs().buffer(); if (effect) { for (int v = 0, vn = trianglesTwoColor.vertCount, vv = 0; v < vn; ++v, vv += 2) { V3F_T2F_C4B_C4B *vertex = trianglesTwoColor.verts + v; vertex->vertex.x = verts[vv]; vertex->vertex.y = verts[vv + 1]; vertex->texCoord.u = uvs[vv]; vertex->texCoord.v = uvs[vv + 1]; effect->transform(vertex->vertex.x, vertex->vertex.y); vertex->color = light; vertex->color2 = dark; } } else { for (int v = 0, vn = trianglesTwoColor.vertCount, vv = 0; v < vn; ++v, vv += 2) { V3F_T2F_C4B_C4B *vertex = trianglesTwoColor.verts + v; vertex->vertex.x = verts[vv]; vertex->vertex.y = verts[vv + 1]; vertex->texCoord.u = uvs[vv]; vertex->texCoord.v = uvs[vv + 1]; vertex->color = light; vertex->color2 = dark; } } } else { if (effect) { for (int v = 0, vn = trianglesTwoColor.vertCount; v < vn; ++v) { V3F_T2F_C4B_C4B *vertex = trianglesTwoColor.verts + v; effect->transform(vertex->vertex.x, vertex->vertex.y); vertex->color = light; vertex->color2 = dark; } } else { for (int v = 0, vn = trianglesTwoColor.vertCount; v < vn; ++v) { V3F_T2F_C4B_C4B *vertex = trianglesTwoColor.verts + v; vertex->color = light; vertex->color2 = dark; } } } } curTexture = (cc::Texture2D *)attachmentVertices->_texture->getRealTexture(); // If texture or blendMode change,will change material. if (preTexture != curTexture || preBlendMode != slot->getData().getBlendMode() || isFull) { flush(); } if (_enableBatch) { uint8_t *vbBuffer = vb.getCurBuffer(); cc::Vec3 *point = nullptr; for (unsigned int ii = 0, nn = vbSize; ii < nn; ii += vbs) { point = reinterpret_cast(vbBuffer + ii); point->z = 0; point->transformMat4(*point, nodeWorldMat); } } auto vertexOffset = vb.getCurPos() / vbs; if (vbSize > 0 && ibSize > 0) { if (vertexOffset > 0) { auto *ibBuffer = reinterpret_cast(ib.getCurBuffer()); for (unsigned int ii = 0, nn = ibSize / sizeof(uint16_t); ii < nn; ii++) { ibBuffer[ii] += vertexOffset; } } vb.move(static_cast(vbSize)); ib.move(static_cast(ibSize)); // Record this turn index segmentation count,it will store in material buffer in the end. curISegLen += ibSize / sizeof(uint16_t); } _clipper->clipEnd(*slot); } // End slot traverse _clipper->clipEnd(); if (effect) effect->end(); if (curDrawInfo) curDrawInfo->setIbCount(curISegLen); if (_useAttach || _debugBones) { auto &bones = _skeleton->getBones(); size_t bonesCount = bones.size(); cc::Mat4 boneMat = cc::Mat4::IDENTITY; if (_debugBones) { _debugBuffer->writeFloat32(DebugType::BONES); _debugBuffer->writeFloat32(static_cast(bonesCount * 4)); } for (size_t i = 0, n = bonesCount; i < n; i++) { Bone *bone = bones[i]; boneMat.m[0] = bone->getA(); boneMat.m[1] = bone->getC(); boneMat.m[4] = bone->getB(); boneMat.m[5] = bone->getD(); boneMat.m[12] = bone->getWorldX(); boneMat.m[13] = bone->getWorldY(); attachInfo->checkSpace(sizeof(boneMat), true); attachInfo->writeBytes(reinterpret_cast(&boneMat), sizeof(boneMat)); if (_debugBones) { float boneLength = bone->getData().getLength(); float x = boneLength * bone->getA() + bone->getWorldX(); float y = boneLength * bone->getC() + bone->getWorldY(); _debugBuffer->writeFloat32(bone->getWorldX()); _debugBuffer->writeFloat32(bone->getWorldY()); _debugBuffer->writeFloat32(x); _debugBuffer->writeFloat32(y); } } } // debug end if (_debugBuffer) { if (_debugBuffer->isOutRange()) { _debugBuffer->reset(); CC_LOG_INFO("Spine debug data is too large, debug buffer has no space to put in it!!!!!!!!!!"); CC_LOG_INFO("You can adjust MAX_DEBUG_BUFFER_SIZE macro"); } _debugBuffer->writeFloat32(DebugType::NONE); } } cc::Rect SkeletonRenderer::getBoundingBox() const { static cc::middleware::IOBuffer buffer(1024); float *worldVertices = nullptr; float minX = 999999.0F; float minY = 999999.0F; float maxX = -999999.0F; float maxY = -999999.0F; for (int i = 0; i < _skeleton->getSlots().size(); ++i) { Slot *slot = _skeleton->getSlots()[i]; if (!slot->getAttachment()) continue; int verticesCount; if (slot->getAttachment()->getRTTI().isExactly(RegionAttachment::rtti)) { auto *attachment = dynamic_cast(slot->getAttachment()); buffer.checkSpace(8 * sizeof(float)); worldVertices = reinterpret_cast(buffer.getCurBuffer()); attachment->computeWorldVertices(slot->getBone(), worldVertices, 0, 2); verticesCount = 8; } else if (slot->getAttachment()->getRTTI().isExactly(MeshAttachment::rtti)) { auto *mesh = dynamic_cast(slot->getAttachment()); buffer.checkSpace(mesh->getWorldVerticesLength() * sizeof(float)); worldVertices = reinterpret_cast(buffer.getCurBuffer()); mesh->computeWorldVertices(*slot, 0, mesh->getWorldVerticesLength(), worldVertices, 0, 2); verticesCount = static_cast(mesh->getWorldVerticesLength()); } else { continue; } for (int ii = 0; ii < verticesCount; ii += 2) { float x = worldVertices[ii]; float y = worldVertices[ii + 1]; minX = min(minX, x); minY = min(minY, y); maxX = max(maxX, x); maxY = max(maxY, y); } } if (minX == 999999.0F) minX = minY = maxX = maxY = 0; return cc::Rect(minX, minY, maxX - minX, maxY - minY); } void SkeletonRenderer::updateWorldTransform() { if (_skeleton) { _skeleton->updateWorldTransform(); } } void SkeletonRenderer::setAttachEnabled(bool enabled) { _useAttach = enabled; } void SkeletonRenderer::setToSetupPose() { if (_skeleton) { _skeleton->setToSetupPose(); } } void SkeletonRenderer::setBonesToSetupPose() { if (_skeleton) { _skeleton->setBonesToSetupPose(); } } void SkeletonRenderer::setSlotsToSetupPose() { if (_skeleton) { _skeleton->setSlotsToSetupPose(); } } spine::Bone *SkeletonRenderer::findBone(const std::string &boneName) const { if (_skeleton) { return _skeleton->findBone(boneName.c_str()); } return nullptr; } spine::Slot *SkeletonRenderer::findSlot(const std::string &slotName) const { if (_skeleton) { return _skeleton->findSlot(slotName.c_str()); } return nullptr; } void SkeletonRenderer::setSkin(const std::string &skinName) { if (_skeleton) { _skeleton->setSkin(skinName.empty() ? nullptr : skinName.c_str()); _skeleton->setSlotsToSetupPose(); } } void SkeletonRenderer::setSkin(const char *skinName) { if (_skeleton) { _skeleton->setSkin(skinName); _skeleton->setSlotsToSetupPose(); } } spine::Attachment *SkeletonRenderer::getAttachment(const std::string &slotName, const std::string &attachmentName) const { if (_skeleton) { return _skeleton->getAttachment(slotName.c_str(), attachmentName.c_str()); } return nullptr; } bool SkeletonRenderer::setAttachment(const std::string &slotName, const std::string &attachmentName) { if (_skeleton) { _skeleton->setAttachment(slotName.c_str(), attachmentName.empty() ? nullptr : attachmentName.c_str()); } return true; } bool SkeletonRenderer::setAttachment(const std::string &slotName, const char *attachmentName) { if (_skeleton) { _skeleton->setAttachment(slotName.c_str(), attachmentName); } return true; } void SkeletonRenderer::setUseTint(bool enabled) { _useTint = enabled; } void SkeletonRenderer::setVertexEffectDelegate(VertexEffectDelegate *effectDelegate) { if (_effectDelegate == effectDelegate) { return; } CC_SAFE_RELEASE(_effectDelegate); _effectDelegate = effectDelegate; CC_SAFE_ADD_REF(_effectDelegate); } void SkeletonRenderer::setSlotsRange(int startSlotIndex, int endSlotIndex) { this->_startSlotIndex = startSlotIndex; this->_endSlotIndex = endSlotIndex; } spine::Skeleton *SkeletonRenderer::getSkeleton() const { return _skeleton; } void SkeletonRenderer::setTimeScale(float scale) { _timeScale = scale; } float SkeletonRenderer::getTimeScale() const { return _timeScale; } void SkeletonRenderer::paused(bool value) { _paused = value; } void SkeletonRenderer::setColor(float r, float g, float b, float a) { _nodeColor.r = r / 255.0F; _nodeColor.g = g / 255.0F; _nodeColor.b = b / 255.0F; _nodeColor.a = a / 255.0F; } void SkeletonRenderer::setBatchEnabled(bool enabled) { if (enabled != _enableBatch) { for (auto &item : _materialCaches) { CC_SAFE_DELETE(item.second); } _materialCaches.clear(); _enableBatch = enabled; } } void SkeletonRenderer::setDebugBonesEnabled(bool enabled) { _debugBones = enabled; } void SkeletonRenderer::setDebugSlotsEnabled(bool enabled) { _debugSlots = enabled; } void SkeletonRenderer::setDebugMeshEnabled(bool enabled) { _debugMesh = enabled; } void SkeletonRenderer::setOpacityModifyRGB(bool value) { _premultipliedAlpha = value; } bool SkeletonRenderer::isOpacityModifyRGB() const { return _premultipliedAlpha; } se_object_ptr SkeletonRenderer::getDebugData() const { if (_debugBuffer) { return _debugBuffer->getTypeArray(); } return nullptr; } se_object_ptr SkeletonRenderer::getSharedBufferOffset() const { if (_sharedBufferOffset) { return _sharedBufferOffset->getTypeArray(); } return nullptr; } void SkeletonRenderer::setRenderEntity(cc::RenderEntity *entity) { _entity = entity; } void SkeletonRenderer::setMaterial(cc::Material *material) { _material = material; for (auto &item : _materialCaches) { CC_SAFE_DELETE(item.second); } _materialCaches.clear(); } cc::RenderDrawInfo *SkeletonRenderer::requestDrawInfo(int idx) { if (_drawInfoArray.size() < idx + 1) { cc::RenderDrawInfo *draw = new cc::RenderDrawInfo(); draw->setDrawInfoType(static_cast(RenderDrawInfoType::MIDDLEWARE)); _drawInfoArray.push_back(draw); } return _drawInfoArray[idx]; } cc::Material *SkeletonRenderer::requestMaterial(uint16_t blendSrc, uint16_t blendDst) { uint32_t key = static_cast(blendSrc) << 16 | static_cast(blendDst); if (_materialCaches.find(key) == _materialCaches.end()) { const IMaterialInstanceInfo info{ (Material *)_material, 0}; MaterialInstance *materialInstance = new MaterialInstance(info); PassOverrides overrides; BlendStateInfo stateInfo; stateInfo.blendColor = gfx::Color{1.0F, 1.0F, 1.0F, 1.0F}; BlendTargetInfo targetInfo; targetInfo.blendEq = gfx::BlendOp::ADD; targetInfo.blendAlphaEq = gfx::BlendOp::ADD; targetInfo.blendSrc = (gfx::BlendFactor)blendSrc; targetInfo.blendDst = (gfx::BlendFactor)blendDst; targetInfo.blendSrcAlpha = (gfx::BlendFactor)blendSrc; targetInfo.blendDstAlpha = (gfx::BlendFactor)blendDst; BlendTargetInfoList targetList{targetInfo}; stateInfo.targets = targetList; overrides.blendState = stateInfo; materialInstance->overridePipelineStates(overrides); const MacroRecord macros{{"TWO_COLORED", _useTint}, {"USE_LOCAL", !_enableBatch}}; materialInstance->recompileShaders(macros); _materialCaches[key] = materialInstance; } return _materialCaches[key]; } void SkeletonRenderer::setSlotTexture(const std::string &slotName, cc::Texture2D *tex2d, bool createAttachment) { if (!_skeleton) return; auto slot = _skeleton->findSlot(slotName.c_str()); if (!slot) return; auto attachment = slot->getAttachment(); if (!attachment) return; auto width = tex2d->getWidth(); auto height = tex2d->getHeight(); if (createAttachment) { attachment = attachment->copy(); slot->setAttachment(attachment); } AttachmentVertices *attachmentVertices = nullptr; if (attachment->getRTTI().isExactly(spine::RegionAttachment::rtti)) { auto region = static_cast(attachment); region->setRegionWidth(width); region->setRegionHeight(height); region->setRegionOriginalWidth(width); region->setRegionOriginalHeight(height); region->setWidth(width); region->setHeight(height); region->setUVs(0, 0, 1.0f, 1.0f, false); region->updateOffset(); attachmentVertices = static_cast(region->getRendererObject()); if (createAttachment) { attachmentVertices = attachmentVertices->copy(); region->setRendererObject(attachmentVertices); } V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts; auto UVs = region->getUVs(); for (int i = 0, ii = 0; i < 4; ++i, ii += 2) { vertices[i].texCoord.u = UVs[ii]; vertices[i].texCoord.v = UVs[ii + 1]; } } else if (attachment->getRTTI().isExactly(spine::MeshAttachment::rtti)) { auto mesh = static_cast(attachment); mesh->setRegionWidth(width); mesh->setRegionHeight(height); mesh->setRegionOriginalWidth(width); mesh->setRegionOriginalHeight(height); mesh->setWidth(width); mesh->setHeight(height); mesh->setRegionU(0); mesh->setRegionV(0); mesh->setRegionU2(1.0f); mesh->setRegionV2(1.0f); mesh->setRegionRotate(true); mesh->setRegionDegrees(0); mesh->updateUVs(); attachmentVertices = static_cast(mesh->getRendererObject()); if (createAttachment) { attachmentVertices = attachmentVertices->copy(); mesh->setRendererObject(attachmentVertices); } V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts; auto UVs = mesh->getUVs(); for (size_t i = 0, ii = 0, nn = mesh->getWorldVerticesLength(); ii < nn; ++i, ii += 2) { vertices[i].texCoord.u = UVs[ii]; vertices[i].texCoord.v = UVs[ii + 1]; } } if (!attachmentVertices) return; middleware::Texture2D *middlewareTexture = nullptr; for (auto &it : _slotTextureSet) { if (it->getRealTexture() == tex2d) { middlewareTexture = it; break; } } if (!middlewareTexture) { middlewareTexture = new middleware::Texture2D(); middlewareTexture->addRef(); middlewareTexture->setRealTexture(tex2d); } if (attachmentVertices->_texture) { attachmentVertices->_texture->release(); } attachmentVertices->_texture = middlewareTexture; }