EsotericSoftware / spine-runtimes

2D skeletal animation runtimes for Spine.
http://esotericsoftware.com/
Other
4.42k stars 2.92k forks source link

[spine-cpp] SkeletonRenderer does not start a mask if clipping attachment attached to the slot with 0 alpha. #2635

Closed Retro52 closed 2 months ago

Retro52 commented 2 months ago

Hi,

Our team uses the spine-cpp runtime in our engine. Recently, we've updated our runtime from spine-c version 3.8.99 to spine-cpp version 4.2.36. While porting the code, it became clear that it would be easier to rewrite it from scratch.

For the most part, I used the spine-sfml C++ implementation as a reference. However, since we use custom rendering code, we cannot use SkeletonRenderer directly, but I used it as a guide for porting the rendering logic. To my surprise, after testing some animations re-exported for version 4.2, the new implementation had an issue with clipping: some masks were completely skipped. After some investigation, it became clear that the problem was along these lines:

/// for each slot in draw order
Slot &slot = *skeleton.getDrawOrder()[i];
Attachment *attachment = slot.getAttachment();
if (!attachment) {
    clipper.clipEnd(slot);
    continue;
}

// Early out if the slot color is 0 or the bone is not active
if (slot.getColor().a == 0 || !slot.getBone().isActive()) {
    clipper.clipEnd(slot);
    continue;
}

Some of our clipping attachments were attached to slots with an alpha value equal to 0, so they were skipped due to the early-out condition, and hence clipping did not work properly. I added a temporary workaround for this issue by checking the attachment type before continuing to the next attachment, but it would be nice to see a standardized solution, especially for those who use SkeletonRenderer as their main renderer.

...
auto& draw_order = m_skeleton->getDrawOrder();
for (size_t i = 0; i < m_skeleton->getSlots().size(); i++)
{
    auto& slot = *draw_order[i];
    auto* attachment = slot.getAttachment();
    auto attachment_type = GetSlotAttachmentType(slot); // Returns 1 out of 4: eNull, eRegion, eMesh or eClipping

    if ((!attachment || cpp::is_equal(slot.getColor().a, 0.0F) || !slot.getBone().isActive())
        && attachment_type != AttachmentType::eClipping)
    {
        m_clipping.clipEnd(slot);
        continue;
    }
....
}
...

I assume that my workaround is correct because it mimics the Spine editor behavior for the animation. Unfortunately, I cannot provide an example animation, as it was created within the studio, and I am not allowed to publish it.

badlogic commented 2 months ago

Oh wow, you are the first person to run into this after years of this early out existing! I'll have to hunt this down in all implementations now.

Thank you for reporting even though you are not using SkeletonRenderer yourself!