Dynamic Spine Attachment Swapping

Does anyone know of an easy way to create a Texture2D from a SpriteFrame. But not the Texture2D the SpriteFrame is on (i.e. a tileset atlas), since that would contain the entire tileset image, but rather a Texture2D to just the single tile that the SpriteFrame is framing.

Alternatively does anyone know of an easy way to dynamically replace the image of a spine RegionAttachment with an image from a SpriteFrame, and/or dynamically create and add a new RegionAttachment to a given slot on the spine animation.

The Spine skin system works here, but will be a major pain to deal with and constantly update, change, and fix. Thus we’d like to be able to dynamically change the spine animation at runtime, since this would make maintaining this spine animation much easier. Both questions I posed are for the same problem, that is that we need to dynamically change/create the attachment for our Spine animation using a SpriteFrame.

Asked on the Discord server, and reposted here at request of @jasmine

Some potentially useful information by Roo101 here.

Attempting to follow those instructions didn’t work for me, as I’m likely doing something wrong in the several functions that Roo101 didn’t provide. As I get a crash on line #908 of SkeletonRenderer.cpp when attempting to swap out the attachment. Experimenting and trying several different things results in the same crash for me.

Any help would be appreciated!

I will ask engineering for help. What version of cocos2d-x are you using?

Thanks @slackmoehrle ! We’re using 3.17.2, the latest supported version for Cocos2d-x (4.0 has build issues according to TravisCI on GitHub)

Awesome. Thank you. And thank Jasmine for having you post :wink:

1 Like

In case you’re not already aware, the Texture2D in the SpriteFrame is not an isolated texture if it came from a texture atlas. It’s still in that atlas, and the SpriteFrame simply has a reference to the large atlas texture, along with coordinates of where that specific frame resides in it.

Now, are you sure it’s a region attachment and not a mesh attachment that you’re trying to replace in Spine? If so, you’ll need to create the code that maps the texture from the atlas to the region attachment, if it can be done. Otherwise, you’ll need to copy that specific frame onto a cocos2d::RenderTexture, then use that RenderTexture to replace the spine attachment, since it’ll no longer be connected to the original atlas (it’ll be an isolated texture).

Other that that, Roo101 is my account on the Spine forums, so all that code posted there is from my application. I can’t give all the code out, but the code you see in that post was probably the hardest bit to figure out. Also, as I mentioned on Discord, you need to convert the Unity/C# code to the cocos2d-x/C++ equivalent, and while it may take some work, you’ll gain an understanding of how everything fits together.

A lot of the code in the Unity/C# code in the Spine repo that I linked on Discord is probably redundant now too, since the Spine run-time had a lot of updates to it, so much of the functionality in the C# extension was moved to the core run-time (for all languages, including C++).

Cocos2d-x v4.0 works fine, but you need to go through the PRs and merge the ones that fix a lot of the issues.

I appreciate your response, but I am aware of this, hence why I said: “[…] but rather a Texture2D to just the single tile that the SpriteFrame is framing”.

I’m sure it’s a region attachment, as I have access to Spine, and the artist who made the spine animation that I’m trying to dynamically swap an attachment texture for :slight_smile:

I’d rather not go the cocos2d::RenderTexture route, because they are horrendously inefficient I’ve found through my personal experiences with Cocos2d-x. Especially on an animation designed to be displayed many times, and with an arbitrary number of attachment texture replacements (i.e. could be over well over 100 cocos2d::RenderTextures such as this).

Again I appreciate your response, but: “Attempting to follow those instructions didn’t work for me, as I’m likely doing something wrong in the several functions that Roo101 didn’t provide. […]”, with the only code you didn’t provide being the code that needs to be converted from “the Unity/C# code to the cocos2d-x/C++ equivalent”. A direct 1-1 replacement of the C# code to the C++ is impossible, as there are several helper methods with no C++ function equivalents, i.e. spine::AtlasUtilities::ToSpineAtlasPage, spine::RegionAttachment::SetRegion, and cocos2d::Material::toSpineAtlasPage, then of course the two that you mentioned: spine::AtlasUtilities::ToAtlasRegion and spine::AttachmentCloneExtensions::GetRemappedClone.

Obviously my implementation of these is incorrect, as I attempted to implement them based on the C# source code, and that is when I’m getting the crash at line #908 of SkeletonRenderer.cpp after attempting to swap out the attachment.

Correct, it was never going to be a one to one conversion between C# to C++, and if you’re going to go down the path adding this functionality to your app, then it can’t be done without a full understanding of how Spine attachments work.

I understand, and with a debugger I’m sure you’ll pinpoint exactly why the crash happened. Just trace it back from that point.

You can use a RenderTexture, but as an atlas. What you do is copy the new textures onto it, so you’re basically creating a texture atlas dynamically. Fit as many images/frames as you can on it, so it’ll take up 1 draw call (assuming it’s only 1 atlas). You can even save it to disk, and load it later to be used again without having to reconstruct everything.

These may help (many others out there too):

https://github.com/TeamHypersomnia/rectpack2D
https://github.com/villekoskelaorg/RectanglePacking
https://github.com/juj/RectangleBinPack
https://github.com/soimy/maxrects-packer
https://github.com/nickgravelyn/SpriteSheetPacker

You’ll also need to create the PLIST file associated with the atlas, so that all the new frames are listed in it.

I appreciate your response, but no offense this just comes off as condescending and isn’t helpful for me to solve the issue at hand. Why not give me information that the documentation or myself is lacking, rather than just telling me I need to understand them? I have a generalized idea of how they work, and it should be enough to get something as seemingly simple as this working. I’m a paid employee doing this for a salary and cannot afford to spend days or weeks just reading the source code in order to get a “full understanding of how Spine attachments work”, hence why I’m asking here in general rather than just spend all of my free time figuring it out myself. I assumed if someone had already solved this issue that they would be able to provide me with the tools needed to solve it myself.

I’ve already attempted to use the debugger to inspect the stack trace, but it doesn’t back trace to any of the code I’ve created. It only traces back to the mainLoop and draw steps of Cocos. This isn’t very helpful for me, especially since the variables in Xcode appear to be correct at each step in the creation and setting of the custom attachment, but then the Attachment itself seems to lose it’s reference or become cleaned up between then and the first draw call (since I never see the change rendered to the screen before the crash). This doesn’t make any sense to me, and in fact because of the way I set it up I’d instead just expect it to leak as I was going to set up the memory management after I got it working in the first place.

I’m really struggling to see how this is any easier or more simple than just trying to use the tileset atlas that I’ve been attempting to use from the beginning, since it has to be dealt with the same way in both cases, i.e we have to load individual frames from a texture into a spine::RegionAttachment

Thanks again for your response.

The functionality you’re after is not typical usage of Spine, otherwise more Spine users would have requested it, and Esoteric would have a reason to implement it. They’ve done a lot a lot already to help make things easier, but in the end, the developer using Spine is the one to add the bits and pieces missing in order to achieve whatever functionality they’re after.

I’m sorry you feel that way, but take it as you will, as it was a statement of fact. This functionality requires deeper knowledge of the Spine and Cocos2d-x internals. You can’t expect others to provide you with a copy and paste solution, and I’m definitely not the one to provide it, because that is not my job nor my responsibility.

Why you’re asking for it is not relevant, and it is completely unreasonable to expect others to provide you with any solution that they have spent their “free” time working on.

If you have specific questions regarding your implementation, then post them with more information, including why and how you’ve tried to solve problem.

Other that that, you know exactly what you want out of this, so take a step back, and do what any programmer would do, think about the problem, and work your way to solving it. You’ve already been provided with a lot more information than others have had access to in working this out. It’s your choice how you choose to use that information.

The reason I mentioned creating your own atlas dynamically is in case you’re applying shaders to the original textures that you’re using for the attachments. It would bake them into the image so that you don’t need to keep applying the shader each time, along with the fact that you can’t apply different shaders to different Spine attachments, at least not as far as I’m aware. For instance, an attachment like a hat, where the user would choose a color for; you wouldn’t want to provide an image of every single color in existence, so you would use a color changing shader, apply it to the image, and then save that to the dynamically created sprite sheet. You then load it when required, and the only time you update it is when the user changes the color of that item again.

Well I made some minor progress, as the game is no longer crashing (was likely just me using the wrong attachment name upon replacement). With many modifications I’ve managed to get the attachment replaced, however the UVs must be incorrect for the attachment, since it shows a random portion of the texture I’m trying to use to replace the attachment with, and not just the portion of the texture that my SpriteFrame is framing that I’m attempting to replace. @R101 I’m assuming the system you wrote and linked to just assumes the sprite sheets are the same shape and size, with the same number of tiles in the sheet in the same spots as the replaced attachments. Is this assumption correct? If so then I believe that means I need to manually calculate the new UVs based on the position and size of the texture that the SpriteFrame is using.

The number of posts I’ve seen regarding dynamic atlas swapping, on both the Cocos and Spine forums, would seem to disagree. As does the fact that this is apparently supported out of the box in Unity based on everything I’ve read (though I’ve not tested it first hand myself yet).

This is off-topic, passive aggressive, and not helpful. I find it hard to believe that others can’t post a copy and paste solution to this issue, since it’s not a new concept or idea and has obviously been done by people, such as you, in the past. Especially so given the fact that Cocos2d-x is an open source engine, and the Spine Runtimes are also open sourced, meaning all of the code needing to be copy and pasted should be based on open source code. Whether or not someone provides a solution to me is their prerogative, but for something like this which should seemingly be able to be done with a single class and some static methods I would assume that a copy and paste solution would be all that was needed (and that most people who’ve done that already are kind enough to provide their solution here if they are here to help other users).

Again mostly off-topic, passive aggressive, and not helpful. This is an open source game engine, with many people spending their “free” time working on improving the engine and helping the users who use it. So how exactly is it unreasonable to expect someone to help provide a solution to a problem that myself and many others have had in the past? How exactly have I “been provided with a lot more information than others have had access to in working this out”? The only information so far I’ve had to work with has been my own knowledge, experimentation, and the over 2 year old topic you linked to trying to help someone else with the same problem I’m having; meaning I’ve had no more information than anyone else with access to Google.

That make sense for some use-cases (though the hat example could be done in other ways, such as setting the slot color, and/or splitting the attachment into multiple shapes and coloring only the slots you need). Our use-case won’t require custom shaders, or slot specific shaders however, so I don’t think I’ll need to use a solution like this. I appreciate your explanation for the use RenderTexture created Atlas for attachment swapping however.

That’s actually not the case. In the system I have in place, the source of the images isn’t relevant, as it could be an individual image file for a frame, or an image frame from a sprite sheet. The UV coordinates for these are 100% known, since Cocos2d-x loads them, and those coordinates must be known in order to render a sprite correctly.

The issue is figuring out how Spine requires that data.

I got it to work! I’ll post a copy and paste solution here for anyone to use most likely tomorrow :slight_smile:

I’m never passive-aggressive; I’m just blunt.

Information from that “2 year old” topic is what helped that developer solve a particular issue with the implementation they had already been working on. The developer was never asking for a hand-out, just information on a specific issue.

That’s like saying “if you write a game using Cocos2d-x and Spine, then you should also release the code.”, so no, your statement is not accurate. It would depend entirely on the licensing model, and in this case, there is absolutely no requirement for code to be shared for any reason. Also, FYI, the bits of code required in the Spine run-time to help with implementing such functionality were submitted as PRs, and merged in.

Whether you get this to work or not is entirely up to you.

I can assure you @R101 isn’t passive-aggressive. Just efficient in wording. :slight_smile:

Okay, here’s the solution I got, cleaned up and refactored for external use.

Note this solution is probably not the best but it works for us and our uses. This solution will definitely leak a small amount of memory if you don’t call delete XXX on the heap allocated objects once you’re done using them, e.g. AtlasPage, AtlasRegion, and the Attachment itself.

SpriteFrameAttachmentLoader.h

//
//  SpriteFrameAttachmentLoader.h
//  Replaces attachments on a spine skeleton with an arbitrary cocos2d::SpriteFrame.
//
//  Created by Tyelor K. on 9/21/21 for ExceptioNULL Games.
//  Use freely, and modify to your hearts content. Neither Myself, ExceptioNULL Games,
//  nor this code are associated with or affiliated with Cocos2d-x or its license holders.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this code, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of this code, and to permit persons to whom the code is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.

#ifndef SpriteFrameAttachmentLoader_h
#define SpriteFrameAttachmentLoader_h

#include "cocos2d.h"
#include <spine/spine.h>
#include <spine/spine-cocos2dx.h>

namespace spineextensions {

using spString = spine::String;

static unsigned short quadTriangles[6] = { 0, 1, 2, 2, 3, 0 };

class SpriteFrameAttachmentLoader
{
public:
    static void replaceAttachment(spine::SkeletonAnimation* skeleton, const std::string& slotName, cocos2d::SpriteFrame* spriteFrame);
    
private:
    static spine::AtlasRegion* toAtlasRegion(cocos2d::SpriteFrame* spriteFrame);
    static spine::Attachment* getRemappedClone(spine::Attachment* attachment, spine::AtlasRegion* atlasRegion, bool cloneMeshAsLinked = true, bool useOriginalRegionSize = true, float scale = 1.0f);
    static void configureAttachment(spine::Attachment* attachment);
    
    static spine::AtlasPage* toSpineAtlasPage(cocos2d::SpriteFrame* spriteFrame);
    static spine::AtlasPage* toSpineAtlasPage(cocos2d::Texture2D* tex);
    
    static void setAttachmentRegion(spine::RegionAttachment* attachment, spine::AtlasRegion* atlasRegion);
    static void setAttachmentRegion(spine::MeshAttachment* attachment, spine::AtlasRegion* atlasRegion);

    static void setAttachmentVertices(spine::RegionAttachment* attachment);
    static void setAttachmentVertices(spine::MeshAttachment* attachment);
    
    static void deleteAttachmentVertices(void* vertices);
};

} // ns_spineextensions

#endif /* SpriteFrameAttachmentLoader_h */

SpriteFrameAttachmentLoader.cpp

//
//  SpriteFrameAttachmentLoader.cpp
//  Replaces attachments on a spine skeleton with an arbitrary cocos2d::SpriteFrame.
//
//  Created by Tyelor K. on 9/21/21 for ExceptioNULL Games.
//  Use freely, and modify to your hearts content. Neither Myself, ExceptioNULL Games,
//  nor this code are associated with or affiliated with Cocos2d-x or its license holders.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this code, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of this code, and to permit persons to whom the code is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.

#include "SpriteFrameAttachmentLoader.h"

#include <spine/AttachmentVertices.h>
#include <spine/Atlas.h>

USING_NS_CC;
using namespace spine;

using namespace spineextensions;

//
// Usage: spineextensions::SpriteFrameAttachmentLoader::replaceAttachment(skeletonWithAttachmentToReplace, slotNameHoldingAttachmentToReplace, spriteFrameToReplaceAttachmentWith)
//
// Note: This solution modifies the input skeleton animation, and thus requires no return value.
//
void SpriteFrameAttachmentLoader::replaceAttachment(spine::SkeletonAnimation* skeleton, const std::string& slotName, cocos2d::SpriteFrame* spriteFrame)
{
    // Ensure parameters are valid:
    if (skeleton == nullptr || spriteFrame == nullptr || slotName.empty()) {
        return;
    }
    
    // Find the slot for the attachment we're replacing:
    auto slot = skeleton->findSlot(slotName);

    // If slot was found, find it's attachment:
    if (slot != nullptr) {
        auto attachment = slot->getAttachment();

        // If attachment was found, replace it:
        if (attachment != nullptr) {
            auto atlasRegion = toAtlasRegion(spriteFrame);
            auto newAttachment = getRemappedClone(attachment, atlasRegion);

            // Replace the attachment only if the new attachment is not null:
            if (newAttachment != nullptr) {
                slot->setAttachment(newAttachment);
            }
        }
    }
}

spine::AtlasRegion* SpriteFrameAttachmentLoader::toAtlasRegion(cocos2d::SpriteFrame* spriteFrame)
{
    // Ensure parameters are valid:
    if (spriteFrame == nullptr) {
        return nullptr;
    }
    
    auto page = toSpineAtlasPage(spriteFrame);
    
    auto width = page->width;
    auto height = page->height;
    
    auto region = new(__FILE__, __LINE__) AtlasRegion();
    region->name = std::to_string(spriteFrame->getTexture()->getName()).c_str();
    region->index = -1;
    region->rotate = spriteFrame->isRotated();

    // World space units
    Vec2 boundsMin = Vec2::ZERO;
    Vec2 boundsMax = Vec2(width, height);

    auto frameRect = spriteFrame->getRect();
    
    // Texture space/pixel units
    region->width = (int)frameRect.size.width;
    region->originalWidth = (int)spriteFrame->getOriginalSizeInPixels().width;
    region->height = (int)frameRect.size.height;
    region->originalHeight = (int)spriteFrame->getOriginalSizeInPixels().height;
    region->offsetX = 0.0f;
    region->offsetY = 0.0f;

    // Get UVs based on the SpriteFrame:
    region->u = frameRect.origin.x / width;
    region->v = frameRect.origin.y / height;
    region->u2 = (frameRect.origin.x + frameRect.size.width) / width;
    region->v2 = (frameRect.origin.y + frameRect.size.height) / height;
    region->x = 0.0f;
    region->y = 0.0f;

    region->page = page;

    return region;
}

Attachment* SpriteFrameAttachmentLoader::getRemappedClone(spine::Attachment* attachment, spine::AtlasRegion* atlasRegion, bool cloneMeshAsLinked, bool useOriginalRegionSize, float scale)
{
    // Ensure parameters are valid:
    if (attachment == nullptr || atlasRegion == nullptr) {
        return nullptr;
    }
    
    auto regionAttachment = dynamic_cast<RegionAttachment*>(attachment);
    if (regionAttachment != nullptr) {
        RegionAttachment* newAttachment = static_cast<RegionAttachment*>(regionAttachment->copy());
        
        setAttachmentRegion(newAttachment, atlasRegion);
        
        configureAttachment(newAttachment);
        
        if (!useOriginalRegionSize) {
            newAttachment->setRegionWidth(atlasRegion->width * scale);
            newAttachment->setRegionHeight(atlasRegion->height * scale);
        }
        
        newAttachment->updateOffset();
        
        return newAttachment;
    }
    
    auto meshAttachment = dynamic_cast<MeshAttachment*>(attachment);
    if (meshAttachment != nullptr) {
        MeshAttachment* newAttachment = cloneMeshAsLinked ? meshAttachment->newLinkedMesh() : (MeshAttachment*)meshAttachment->copy();
        
        setAttachmentRegion(newAttachment, atlasRegion);
        
        configureAttachment(newAttachment);
        
        return newAttachment;
    }

    // Non-renderable Attachments will return as normal cloned attachments.
    return attachment->copy();
}

spine::AtlasPage* SpriteFrameAttachmentLoader::toSpineAtlasPage(cocos2d::SpriteFrame* spriteFrame)
{
    // Ensure parameters are valid:
    if (spriteFrame == nullptr) {
        return nullptr;
    }
    
    return toSpineAtlasPage(spriteFrame->getTexture());
}

AtlasPage* SpriteFrameAttachmentLoader::toSpineAtlasPage(Texture2D* texture)
{
    // Ensure parameters are valid:
    if (texture == nullptr) {
        return nullptr;
    }
    
    auto newPage = new(__FILE__, __LINE__) AtlasPage(std::to_string(texture->getName()).c_str());
    
    newPage->setRendererObject(texture);
    newPage->texturePath = texture->getPath().c_str();
    newPage->width = texture->getPixelsWide();
    newPage->height = texture->getPixelsHigh();

    return newPage;
}

void SpriteFrameAttachmentLoader::configureAttachment(Attachment* attachment)
{
    auto regionAttachment = dynamic_cast<RegionAttachment*>(attachment);
    if (regionAttachment != nullptr) {
        setAttachmentVertices(regionAttachment);
        
        return;
    }
    
    auto meshAttachment = dynamic_cast<MeshAttachment*>(attachment);
    if (meshAttachment != nullptr) {
        setAttachmentVertices(meshAttachment);
        
        return;
    }
}

void SpriteFrameAttachmentLoader::setAttachmentRegion(RegionAttachment* attachment, AtlasRegion* atlasRegion)
{
    // Ensure parameters are valid:
    if (attachment == nullptr || atlasRegion == nullptr) {
        return;
    }
    
    attachment->setRendererObject(atlasRegion);

    attachment->setUVs(atlasRegion->u, atlasRegion->v, atlasRegion->u2, atlasRegion->v2, atlasRegion->rotate);
    
    attachment->setRegionWidth(atlasRegion->width);
    attachment->setRegionHeight(atlasRegion->height);
    attachment->setRegionOffsetX(atlasRegion->offsetX);
    attachment->setRegionOffsetY(atlasRegion->offsetY);
    attachment->setRegionOriginalWidth(atlasRegion->originalWidth);
    attachment->setRegionOriginalHeight(atlasRegion->originalHeight);
}

void SpriteFrameAttachmentLoader::setAttachmentRegion(MeshAttachment* attachment, AtlasRegion* atlasRegion)
{
    // Ensure parameters are valid:
    if (attachment == nullptr || atlasRegion == nullptr) {
        return;
    }
    
    attachment->setRendererObject(atlasRegion);
    
    attachment->setRegionU(atlasRegion->u);
    attachment->setRegionV(atlasRegion->v);
    attachment->setRegionU2(atlasRegion->u2);
    attachment->setRegionV2(atlasRegion->v2);
    attachment->setRegionRotate(atlasRegion->rotate);
    
    attachment->setRegionWidth(atlasRegion->width);
    attachment->setRegionHeight(atlasRegion->height);
    attachment->setRegionOffsetX(atlasRegion->offsetX);
    attachment->setRegionOffsetY(atlasRegion->offsetY);
    attachment->setRegionOriginalWidth(atlasRegion->originalWidth);
    attachment->setRegionOriginalHeight(atlasRegion->originalHeight);
}

void SpriteFrameAttachmentLoader::setAttachmentVertices(RegionAttachment* attachment)
{
    // Ensure parameters are valid:
    if (attachment == nullptr) {
        return;
    }
    
    AtlasRegion* region = (AtlasRegion*)attachment->getRendererObject();
    auto renderObject = (region == nullptr || region->page == nullptr) ? nullptr : (Texture2D*)region->page->getRendererObject();
    
    AttachmentVertices* attachmentVertices = new AttachmentVertices(renderObject, 4, quadTriangles, 6);
    V3F_C4B_T2F* vertices = attachmentVertices->_triangles->verts;
    
    for (int i = 0, ii = 0; i < 4; ++i, ii += 2) {
        vertices[i].texCoords.u = attachment->getUVs()[ii];
        vertices[i].texCoords.v = attachment->getUVs()[ii + 1];
    }
    
    attachment->setRendererObject(attachmentVertices, deleteAttachmentVertices);
}

void SpriteFrameAttachmentLoader::setAttachmentVertices(MeshAttachment* attachment)
{
    // Ensure parameters are valid:
    if (attachment == nullptr) {
        return;
    }
    
    AtlasRegion* region = (AtlasRegion*)attachment->getRendererObject();
    auto renderObject = (region == nullptr || region->page == nullptr) ? nullptr : (Texture2D*)region->page->getRendererObject();
    
    AttachmentVertices* attachmentVertices = new AttachmentVertices(renderObject, attachment->getWorldVerticesLength() >> 1, attachment->getTriangles().buffer(), attachment->getTriangles().size());
    V3F_C4B_T2F* vertices = attachmentVertices->_triangles->verts;
    
    for (int i = 0, ii = 0, nn = attachment->getWorldVerticesLength(); ii < nn; ++i, ii += 2) {
        vertices[i].texCoords.u = attachment->getUVs()[ii];
        vertices[i].texCoords.v = attachment->getUVs()[ii + 1];
    }
    
    attachment->setRendererObject(attachmentVertices, deleteAttachmentVertices);
}

void SpriteFrameAttachmentLoader::deleteAttachmentVertices(void* vertices)
{
    // Ensure parameters are valid:
    if (vertices != nullptr) {
        delete (AttachmentVertices*)vertices;
    }
}

I hope this helps anyone who had a similar need for such a feature in Cocos2d-x and Spine! Feel free to ask questions on, improve upon, or critique the code :grinning_face_with_smiling_eyes:

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.