简体   繁体   English

为什么在调整地图集大小后纹理无法在纹理地图集中找到它的位置?

[英]Why a texture cannot find its position in a texture atlas after the atlas has been resized?

I've got texture atlas from: https://straypixels.net/texture-packing-for-fonts/我的纹理图集来自: https : //straypixels.net/texture-packing-for-fonts/

struct TextureNode
{
    TextureNode(const Vector2<uint32>& origin, const Vector2<uint32>& size)
        : origin(origin), size(size)
    {
    }

    Vector2<uint32> origin = 0;    // Top left of the rectangle this node represents
    Vector2<uint32> size = 0;      // Size of the rectangle this node represents
    bool empty = true;                // true if this node is a leaf and is filled

    std::unique_ptr<TextureNode> left = nullptr;  // Left (or top) subdivision
    std::unique_ptr<TextureNode> right = nullptr; // Right (or bottom) subdivision
};

struct TextureAtlas
{
    TextureAtlas() = default;
    ~TextureAtlas()
    {
        buffer.clear();
    }

    void Create(const Vector2<uint32>& size)
    {
        textureSize = size;
        rootNode = std::make_unique<::TextureNode>(0, size);
        buffer.resize(size.w * size.h);
    }

    TextureNode* Pack(TextureNode* node, const Vector2<uint32>& size)
    {
        if (!node->empty) {
            // The node is filled, not gonna fit anything else here
            assert(!node->left && !node->right);
            return nullptr;
        } else if (node->left && node->right) {
            // Non-leaf, try inserting to the left and then to the right
            TextureNode* retval = Pack(node->left.get(), size);
            if (retval != nullptr) {
                return retval;
            }
            return Pack(node->right.get(), size);
        } else {
            // This is an unfilled leaf - let's see if we can fill it
            Vector2<uint32> realSize = node->size;

            // If we're along a boundary, calculate the actual size
            if (node->origin.x + node->size.x == INT_MAX) {
                realSize.x = textureSize.x - node->origin.x;
            }
            if (node->origin.y + node->size.y == INT_MAX) {
                realSize.y = textureSize.y - node->origin.y;
            }

            if (node->size.x == size.x && node->size.y == size.y) {
                // Perfect size - just pack into this node
                node->empty = false;
                return node;
            } else if (realSize.x < size.x || realSize.y < size.y) {
                // Not big enough
                return nullptr;
            } else {
                // Large enough - split until we get a perfect fit
                TextureNode* left = nullptr;
                TextureNode* right = nullptr;

                // Determine how much space we'll have left if we split each way
                int remainX = realSize.x - size.x;
                int remainY = realSize.y - size.y;

                // Split the way that will leave the most room
                bool verticalSplit = remainX < remainY;
                if (remainX == 0 && remainY == 0) {
                    // Edge case - we are are going to hit the border of
                    // the texture atlas perfectly, split at the border instead
                    if (node->size.x > node->size.y) {
                        verticalSplit = false;
                    } else  verticalSplit = true;
                }

                if (verticalSplit) {
                    // Split vertically (left is top)
                    left = new TextureNode(node->origin, Vector2<uint32>(node->size.x, size.y));
                    right = new TextureNode(Vector2<uint32>(node->origin.x, node->origin.y + size.y), Vector2<uint32>(node->size.x, node->size.y - size.y));
                } else {
                    // Split horizontally
                    left = new TextureNode(node->origin, Vector2<uint32>(size.x, node->size.y));
                    right = new TextureNode(Vector2<uint32>(node->origin.x + size.x, node->origin.y), Vector2<uint32>(node->size.x - size.x, node->size.y));
                }

                node->left.reset(left);
                node->right.reset(right);
                return Pack(node->left.get(), size);
            }
        }

        return nullptr;
    }

    Vector2<uint32> PackTexture(unsigned char* textureBuffer, const Vector2<uint32>& bufferSize)
    {
        TextureNode* node = Pack(rootNode.get(), bufferSize);
        if (node == nullptr) {
            ResizeBuffer(textureSize * 2U);
            node = Pack(rootNode.get(), bufferSize);

            // Note: this assert will be hit if we try to pack a texture larger
            // than the current size of the texture
            assert(node != nullptr);
        }

        assert(bufferSize.x == node->size.x);
        assert(bufferSize.y == node->size.y);

        // Copy the texture to the texture atlas' buffer
        for (uint32 ly = 0; ly < bufferSize.y; ly++) {
            for (uint32 lx = 0; lx < bufferSize.x; lx++) {
                int y = node->origin.y + ly;
                int x = node->origin.x + lx;
                buffer[y * textureSize.x + x] = textureBuffer[ly * bufferSize.x + lx];
            }
        }

        return node->origin;
    }

    void ResizeBuffer(const Vector2<uint32>& newSize)
    {
        vector<uchar> newBuffer;
        newBuffer.resize(newSize.y * newSize.x);
        for (uint32 y = 0; y < textureSize.y; y++) {
            for (uint32 x = 0; x < textureSize.x; x++) {
                newBuffer[y * newSize.x + x] = buffer[y * textureSize.x + x];
            }
        }

        textureSize = newSize;
        buffer = std::move(newBuffer);
        rootNode->size = newSize;
    }

    vector<uchar> buffer;
    Vector2<uint32> textureSize = 512;

    std::unique_ptr<TextureNode> rootNode = nullptr;
}

int main()
{
    TextureAtlas atlas;
    atlas.Create({256, 256};

    Font font;
    //                       size
    font.Create("arial.ttf", 100.0f);

    std::string chars = "ABCDEFGHIJKLMNOPRSTUWXYZabcdefghijklmnoprstuwzyw1234567890";

    for(const auto& c : chars) {
        auto txt = font.LoadGlyph(c);
        atlas.PackTexture(txt.data(), txt.w, txt.h);
    }
}

It works when the atlas space has not been changed.当图集空间没有改变时它起作用。 When there is no space left and atlas needs to be resized, the size changes, but a algorithm cannot find where to put another texture and it hits assert(node != nullptr);当没有剩余空间并且需要调整图集大小时,大小会发生变化,但算法无法找到放置另一个纹理的位置并且它会命中assert(node != nullptr); in PackTexture(..) method.PackTexture(..)方法中。 You can use whatever texture you want.你可以使用任何你想要的纹理。 The result is the same.结果是一样的。 After running through the code by a breakpoint it looks like rootNode changes its size, but left and right still remain the same.通过断点通过代码运行后,它看起来像rootNode改变其大小,但leftright仍保持不变。 Nullptr is returned by this step in Pack(..) method:此步骤在Pack(..)方法中返回Nullptr

if (realSize.x < size.x || realSize.y < size.y) {
    // Not big enough
    return nullptr;
}

Why is that?这是为什么? How to change the algorithm, so it can handle resizing properly?如何更改算法,以便它可以正确处理调整大小?

You haven't set the root's size properly, so the realSize is never tweaked.你没有正确设置根的大小,所以realSize永远不会被调整。

Note that a default constructed TextureAtlas is in an invalid state.请注意,默认构造的TextureAtlas处于无效状态。 Why have a separate Create ?为什么有一个单独的Create

Also your destructor does the same thing as the default destructor.此外,您的析构函数与默认析构函数执行相同的操作。 It's better to omit it.最好省略它。

explicit TextureAtlas(const Vector2<uint32>& size = { 512, 512 })
  : textureSize(size),
    rootNode(std::make_unique<TextureNode>(0, { INT_MAX, INT_MAX })),
    buffer(size.w * size.h)
{
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM