• Runtimes
  • How to obtain the bounding box and rendering attachments.

  • 수정됨

Nate My job is very clear. I take a screenshot of the "Default" animation of the existing animation file to make the image strictly centered (having considered "long spear" and such, only centering is required). In fact, I shouldn't or can't make any modifications to the animation file. This is beyond my authority.

Nate There's no Skeleton in spine-sfml/spine-c

I hope to pack a very, very small tool to handle this job, so I hope to carry as few operating environments as possible. spine-sfml/spine-c can't do it,so, what > 3.8/spine-c/spine-c/src/spine/Skeleton.c. Then I will try: spine-sfml/spine-cpp

Nate The only way to fix that is draw, then inspect the pixels.

So this job is necessary, but I still hope to know more about to what extent spine-runtime can achieve.

Nate That you are drawing images with lots of blank space is a separate problem.

So I raised two questions. The first one already has a basic direction for solution, which is the one discussed above.

The second problem is the most important one at present. I have chosen a more extreme example again.

I don't know what happened. The code above me couldn't draw many things, nor could I tell the connection between these missing objects. I don't know how to obtain more information about this problem.

Related Discussions
...

Nate I examined the specific implementation of Skeleton and realized that there was no essential difference from my traversal of SP_ATTACHMENT_REGION and SP_ATTACHMENT_MESH. Therefore, I realized that this work might have gone beyond the scope of spine-runtime. So I adopted the simple Andrew's Monotone Chain algorithm to fulfill my requirements, and it did. (Perhaps I should have done this long ago. ( 。ớ ₃ờ)ھ)

So the first problem has been solved at a certain stage, while there is no idea about the second one.

The monotone chain algorithm (aka Andrew's algorithm) is for computing a convex hull. You can reference an efficient, non-allocating version of it that I wrote here. How do you use this algorithm? Do you apply it to the pixels in each mesh? This seems quite complicated. The easiest approach is to render to an in-memory buffer then read the pixels from each edge. Reduce the size of the image for each row or column that is fully transparent. When you encounter a pixel that is not fully transparent, stop. Code for that for texture packing can be seen here. Of course you will need to adapt to your game toolkit API. Since this involves rendering, you are dependent on the game toolkit API. It's not functionality the Spine Runtimes provide, so changing runtimes won't help much. Changing game toolkits may help. Doing it with libgdx would be easy, but you may not want your tool to require or package Java.

For your rendering problem, as I mentioned before: get it working by rendering to the screen first. Once that works, then render to a texture. The rendering code path should be the same, but it's easier to work on getting the screen rendering correct. It's not possible to give more help from only screenshots of incorrect rendering. I can only guess at what is wrong, but it seems as if some batched geometry was not flushed at the end of your rendering.

  • ETO님이 이에 답장했습니다.

    Nate Andrew's Monotone Chain. Here I have used the following more common writing method. I analyzed your writing method and used the quick sort algorithm, while I used std::sort, which combines quick sort and heap sort. The time complexity of both is the same O(n log n). Compared with your maintenance of the index, I don't have the corresponding requirements, so my constant factor is smaller. Therefore, I think it is efficient enough.
    //(Andrew's Monotone Chain)
    static int cross(const sf::Vector2i& O, const sf::Vector2i& A, const sf::Vector2i& B) {
    return (A.x - O.x) * (B.y - O.y) - (A.y - O.y) * (B.x - O.x);
    }
    static std::vector<sf::Vector2i> convexHull(std::vector<sf::Vector2i> P) {
    int n = P.size(), k = 0;
    if (n <= 1) return P;
    std::sort(P.begin(), P.end(), [](auto& a, auto& b) {
    return a.x < b.x || (a.x == b.x && a.y < b.y);
    });
    std::vector<sf::Vector2i> H(2 * n);
    for (int i = 0; i < n; ++i) {
    while (k >= 2 && cross(H[k - 2], H[k - 1], P[i]) <= 0) k--;
    H[k++] = P[i];
    }
    for (int i = n - 2, t = k + 1; i >= 0; --i) {
    while (k >= t && cross(H[k - 2], H[k - 1], P[i]) <= 0) k--;
    H[k++] = P[i];
    }
    H.resize(k - 1);
    return H;
    }

    Regarding the acquisition of the point set, as you said, I don't have a very good method. I traversed all the pixels. This is indeed a bottleneck. But as you said, I don't want to introduce more things, and the time consumption is actually within the acceptable range. Overall, this has met my needs. Thank you for your suggestion.

    Regarding the rendering issue, I have made more attempts below and should be able to provide more details.

    This is the complete process I attempted. Meanwhile, I tried to output the duration of the animation, and unsurprisingly, the result was 0.000000.

    Next, I will talk about the differences under different conditions.

    The difference between the upper left corner and the lower left corner lies in whether this is enabled or not. I found that if the attachment is not explicitly set, even if I do not set an exit condition when counting the bounding box, the unrendered attachments will not be recorded.

    for (int i = 0; i < skeleton->slotsCount; ++i) {
    spSlot* slot = skeleton->drawOrder[i];
    if (!slot->attachment && slot->data->attachmentName) {
    spAttachment* att = Skeleton_getAttachmentForSlotName(skeleton, slot->data->name, slot->data->attachmentName);
    if (att) slot->attachment = att;

    From the difference between the upper left and the right, I learned that not all attachments need to be forcibly opened. There might be a judgment condition here, and I don't know how to handle it.

    Then let's look at another example. The layout is consistent with the previous one, but we can see that after enabling all attachments, there is no difference. At the same time, just like above, it is not included in the statistics of the bounding box.

    As you said, I have rendered it on the screen. In fact, there is no difference between it and when I output it to PNG (always as an example in the upper left corner).


    Is there any extra processing in the skeletonViewer rendering process, the latter example still has no idea for me.

    Nate I can only guess at what is wrong, but it seems as if some batched geometry was not flushed at the end of your rendering.

    For this issue, I only performed such rendering once throughout the full text and there was no cross-thread operation. I don't think it matters much here. According to the previous examples, the location where the error occurred should be at or before the earlier traversal.

    Nate

    I ran through all my model libraries and found that there was still model return 4 but no return 3. This indicates that the model calculated the bounding box, but all of them were set to transparent. Then I made an attempt based on this.

    However, what emerged were the elements that should have been properly hidden, while the elements that should have been drawn still did not appear. This might provide some clues.

    Your convexHull function looks good!

    I traversed all the pixels.

    I still contend the fastest and simplest solution is:

    1. Compute the AABB using mesh and region vertices.
    2. Create a buffer using that AABB size and render the skeleton to it.
    3. Discard fully transparent rows/columns at the 4 edges. You now have the tightly fitting AABB.

    If you know the maximum size of your skeletons, you can skip step 1 and in step 2 just create a large buffer, eg 4096 x 4096.

    You can see an example here. That code poses a skeleton with an animation on frame 0 (using the timeline API, effectively the same as your use of AnimationState), then renders it to a buffer -- very similar to what you want (but in Java with libgdx). Next you'd read the pixels to discard fully transparent rows/columns at the edges (like I showed in the texture packer in my earlier post). Game toolkits like libgdx are so much nicer than those that force you to use a scene graph. 🙁

    Your code that iterates slots should be based on the spine-sfml rendering code, here:
    EsotericSoftware/spine-runtimesblob/4db43da67888eb71ed706046cb13735d96286048/spine-sfml/c/src/spine/spine-sfml.cpp#L159-L317

    Copy paste that, then remove the parts that do rendering. In the for (int j = 0; j < indicesCount; ++j) { loop use the vertices to find the AABB. The reason to do this is because you must do exactly what the spine-sfml rendering code does, except you calculate AABB instead of rendering. If you do something else, then your AABB may not match rendering. For example, it appears your code doesn't apply clipping correctly.

    That code does not use Skeleton_getAttachmentForSlotName and you should not need it. By default the skeleton has the setup pose attachments visible. You don't need to set attachment visibility. The spine-sfml renderer does not. The FboTest linked above does not. You should not change the slot color either.

    I tried to output the duration of the animation, and unsurprisingly, the result was 0.000000.

    0 is correct if the animation has only keys on frame 0.

    As you said, I have rendered it on the screen. In fact, there is no difference between it and when I output it to PNG

    OK, good -- it will be easier to debug when rendering to the screen. Are you rendering directly to the screen though? The code you've show seems to be rendering to the texture. Don't render to your texture and then render the texture to the screen. Render directly to the screen to ensure your rendering code is correct, only then make it more complex by rendering to a texture.

    Render directly to the screen, like the example:

    sf::RenderWindow window(...);
    SkeletonDrawable *drawable = ...;
    ...
    window.clear();
    window.draw(*drawable);
    window.display();

    Is there any extra processing in the skeletonViewer rendering process

    No extra processing should be needed. The SkeletonViewer code is available. The FboTest above is simpler. You just load the skeleton and render it.

    I ran through all my model libraries and found that there was still model return 4 but no return 3. This indicates that the model calculated the bounding box, but all of them were set to transparent.

    This could be correct. A skeleton could have all attachments hidden in the setup pose, eg maybe they are only shown in animations. Or, when a skeleton uses skins it is common to not have any attachments at all in the default skin. When shown in Skeleton Viewer, you won't see anything until you choose a skin.

    At this point you should probably start with the spine-sfml example code. Run it and see spineboy renders. Change the skeleton to one of yours, see it renders correctly. Modify the example to render to a texture, then render the texture to the screen to see rendering was correct. If you need an AABB to render to a texture, base it on the spine-sfml rendering code. Finally, convert the texture to an image and remove fully transparent rows/columns from the 4 edges.

    Give that a try (don't skip any steps!) and if you still have trouble, post or email your full code. contact@esotericsoftware.com

    • ETO님이 이에 답장했습니다.

      Nate

      Nate start with the spine-sfml example code. Run it and see spineboy renders. Change the skeleton to one of yours, see it renders correctly.

      I did do so (Based on this), but the result I got was that spineboy was completely normal, while this part of my model did not display properly.

      Nate Render directly to the screen, like the example:

      The window in the upper left corner above me is done this way. I explained it later, but it might not be conspicuous enough.

      Nate Or, when a skeleton uses skins it is common to not have any attachments at all in the default skin.

      I have considered this aspect, but the fact is that my model only has the default skin.

      Nate Discard fully transparent rows/columns at the 4 edges

      This is indeed a good method. Through some algorithmic techniques, a fitting bounding box can be found efficiently. But in my practice, just like the spear you mentioned above, there is a cigarette placed a bit far away in my model library (the graphic designer is really bad QWQ). So later on, I hope to achieve the "center of gravity" effect. I will design a function to display the more important parts at a slightly central position, so I hope to obtain more information through traversal.

      Nate For example, it appears your code doesn't apply clipping correctly

      I made the modifications according to the code you provided above. The reason why clipping was not applied earlier is that I didn't find the usage requirements in the model. Since it was mentioned, I'll add it as well.

      I don't know if my understanding is correct. I made such a modification, but there was no improvement shown in the end.

      I just sent my code, which includes example.cpp and main.cpp. The former is a simplified test based on the sample code, and the latter is the code I am currently using. It also includes three groups of problematic models. These three should almost cover all the situations I have encountered.

      We got your email, thanks! We'll take a look soon. It should render using the 3.8.99 spine-sfml, as it does in the 3.8.99 Skeleton Viewer.

      Note your skeleton data is 3.8.99, so it's important you use and base your code on the 3.8.99 spine-sfml.

      • ETO님이 이에 답장했습니다.

        Nate I'm very sorry. I didn't notice such details before. I don't know how to obtain the spine-sfml of 3.8.99. What I got here is "download zip" under the 3.8 branch because I noticed that releases are no longer there. I did download it before, but it should be 3.8.95 (releases), and it probably won't work very well either.

        The latest in the 3.8 branch is compatible with exports from 3.8.xx editor versions, so it sounds like you have the right runtime code.

        • ETO님이 이에 답장했습니다.

          Nate I noticed that you are somewhat interested in my implementation of the bounding box. This is my final implementation. Although the annotations are all in Chinese, in implementation, it is simply to obtain the center of gravity by dividing the total torque by the total mass, and then move the closely surrounding side to center the center of gravity. This does indeed require traversal. (If it is no longer necessary, we can stop discussing the issue in this direction. This has fully met my needs.)


          Meanwhile, when I was checking just now, I found that I hadn't implemented your suggestions well before.

          Nate Are you rendering directly to the screen though? The code you've show seems to be rendering to the texture.

          I just corrected the code.
          make window.draw(finalSprite); to window.draw(*drawable);
          But in the end, it still failed.

          Cool, that is a neat solution!

          Hopefully on Monday we'll solve your rendering.

          • ETO님이 이에 답장했습니다.

            Nate That's great, thank you for your continuous help all along ~ ଘ(੭ˊ꒳​ˋ)੭ ​​​

            • Nate 님이 이 게시물을 좋아합니다..

            Nate I have received the relevant push notifications and noticed your fixes for spine-sfml and spine-cpp. Please excuse me for not being able to conduct the test immediately (due to the 12-hour time difference). I checked the newly fixed part and noticed that it corresponded the logic of cpp to c and changed the logical judgment. This also confirmed my previous conjecture. I will perform batch operations on my model library again later to verify if there are any other problems. All in all, thank you for your help!

            • Nate 님이 이 게시물을 좋아합니다..

            We ported your example to cpp and it worked, so it was a bug in c. Should be fixed now!

            • ETO님이 이에 답장했습니다.

              Nate Thank you very much for your work. With my additions, the functions of this tool of mine have now been fully implemented. I might make more attempts later, but I believe the life cycle of this post has come to an end. Thanks again ♪(· ω ·)ノ

              • Nate 님이 이 게시물을 좋아합니다..