Using Spritesheet and 9-patch at once

Hi @pspeed,

I using Lemur for a while now and quite sastisfy with the lib overall. However i find it lack the case of using spritesheet and 9-patch at once. Like, the texture is packed in a bigger spritesheet and the coords have to modify to let the Quad know how to render it properly.

As far as I know, I can use TbtQuad to render a scale-to-fit quad, but not be able to offset the texture coords of it. So, I wonder how you pack all the buttons texture in a sheet and then use them as 9-patches after.

A work around is to make another TbtQuad and TbtQuadComponent to support this case but I’d like to know if you gonna add this feature any time soon (if you have some free 3mins) (add more paramaters in TbtQuad constructors). Something like:

public TbtQuad( float width, float height, int x1, int y1, int x2, int y2, int imageWidth, int imageHeight, float imageScale , float offsetX, float offsetY)

The concept is a good one. I simply haven’t implemented it yet because I hadn’t personally needed it and no one asked. But I think it’s a useful thing.

However, I’m not sure TbtQuad stuff is the right place for it. A 9 patch is kind of a specific thing and it’s parameters are geared towards that… not to mention that it creates a 9 quad mesh when all a sprite would need is a regular quad. I mean my guess is that your extra parameters are a way to get rid of the extra border anyway… but I’m not sure what they mean exactly.

So maybe some kind of Sprite or Atlas-specific component would be better?

mhh… for me the TbtQuad is the same as a 9-patch. Your post seems to imply there is a difference for you @pspeed.
I guess what @atomix would like is being able to pick the image needed for a TbtQuad in a sprite sheet or a texture atlas.
offsetX and offsetY would be the coordinates of the lower left corner of the image in the sprite sheet, IMO.

Yeah, it occured to me later that he wanted still the 9-patch functionality with stretching but for an atlas.

…I thought maybe he was just using the fact that it already had texture offsets to get atlas support.

I worry because the parameters for any 9 patch setup are already kind of difficult to understand at first. I keep trying to think if there is a better way. Perhaps adding 4 more parameters is enough… though I think they should all be in pixel units, personally.

For the spritesheet thing, texturepacker is a good atlas tool and a bit off topic but we are not supported by this tool whereas a lot of 2d/3d engines are…A shame !!!

Do you already have made a request to their team?

Hi, @pspeed, @nehon

The point is may be one more constructor need or two more params need in TbtQuad (or what ever is a 9 patch Mesh with a custo m texture coordinates and scale-to-fit a rectangle :slight_smile: ) to support “texture atlas” straight forward.

Let say i want a buttonA with offset (100,100) size(100,100) in the texture atlas which is a 9-patches with (4,4,4,4) insets. Then I want buttonB with offset (200,200) size (100,100) insets (6,6,6,6)…

All of them packed together in one big texture as i optimized my game. The point is I really want all the params at one place (in a contructor method) to change the code automaticly along with my own texture packer. You see, at first I use separate textures for each buttons right straight from Photoshop. Later I run the tool and it auto replace all my reference to the old texture with the new one in the texture atlas. I did this with Nifty and Tonegod so I really want to see if I can do easily with Lemur also.

I have no problem with making my own TbtQuadComponent to support that case but its redundant make me sick :slight_smile:

1 Like

@haze, Support TexturePacker ?

For me it’s a small snippet of code that import a TextureAtlas and procedure a TextureAtlasCell. It seems like you need something like that.

You can guess, may be other have it already :slight_smile: Just ask.

Yeah, as @nehon pointed out, I misunderstood what you were trying to do. I will add something but I’m not sure how soon I will get to it. If you’ve already modified the class locally then posting it might speed up the process… otherwise I’ll just add it when I can either way.

It’s so simple but should be easier if I don’t have to redo both TbtQuad and TbtQuadBackgroundComponent

public class ExTbtQuad extends Mesh
                     implements Cloneable{

    private Vector2f size;
    private Vector2f imageSize;
    private float[] horzFolds;
    private float[] vertFolds;
    private float[] horzTexCoords;
    private float[] vertTexCoords;
    float offsetX, offsetY;

    public ExTbtQuad(float width, float height) {
        this.size = new Vector2f(width, height);
        this.imageSize = new Vector2f(width, height);
        this.horzFolds = new float[]{width / 3f, 2 * width / 3f};
        this.vertFolds = new float[]{height / 3f, 2 * height / 3f};
        this.horzTexCoords = new float[]{0, 1 / 3f, 2 / 3f, 1};
        this.vertTexCoords = new float[]{0, 1 / 3f, 2 / 3f, 1};
        refreshGeometry();
    }

    public ExTbtQuad(float width, float height, int x1, int y1, int x2, int y2, int imageWidth, int imageHeight, int ox, int oy, float imageScale) {
        this.size = new Vector2f(width, height);

        float iw = imageWidth * imageScale;
        float ih = imageHeight * imageScale;
        this.imageSize = new Vector2f(iw, ih);
        this.horzFolds = new float[]{imageScale * x1, imageScale * x2};
        this.vertFolds = new float[]{imageScale * y1, imageScale * y2};

        // Slide the far end necessary to make the proper width
        // and height
        horzFolds[1] += width - iw;
        vertFolds[1] += height - ih;

//THIS IS WHAT I ADD :)
        this.offsetX = ox / iw;
        this.offsetY = oy / ih;
        this.horzTexCoords = new float[]{0 + offsetX, (float) x1 / imageWidth + offsetX, (float) x2 / imageWidth + offsetX, 1 + offsetX};
        this.vertTexCoords = new float[]{0 + offsetY, (float) y1 / imageHeight + offsetY, (float) y2 / imageHeight + offsetY, 1 + offsetY};
        refreshGeometry();


    }

    public ExTbtQuad clone() {
        ExTbtQuad result = (ExTbtQuad) super.deepClone();
        result.size = size.clone();
        result.imageSize = imageSize.clone();
        result.horzFolds = horzFolds.clone();
        result.vertFolds = vertFolds.clone();
        result.horzTexCoords = horzTexCoords.clone();
        result.vertTexCoords = vertTexCoords.clone();
        return result;
    }

    public Vector2f getSize() {
        return size;
    }

    public void updateSize(float width, float height) {
        if (size.x == width && size.y == height) {
            return;
        }

        // Put back the size adjustment we made in the first place
        horzFolds[1] -= size.x - imageSize.x;
        vertFolds[1] -= size.y - imageSize.y;

        size.set(width, height);

        // Adjust the middle fold for the new size
        horzFolds[1] += size.x - imageSize.x;
        vertFolds[1] += size.y - imageSize.y;
        refreshGeometry();
    }

    protected void refreshGeometry() {

        setBuffer(Type.Index, 3, new short[]{
            0, 1, 12,
            0, 12, 11,
            1, 2, 13,
            1, 13, 12,
            2, 3, 13,
            3, 4, 13,
            13, 4, 5,
            13, 5, 14,
            14, 5, 6,
            14, 6, 7,
            15, 14, 7,
            15, 7, 8,
            10, 15, 9,
            15, 8, 9,
            11, 12, 15,
            11, 15, 10,
            // The center
            12, 13, 14,
            12, 14, 15
        });

        setBuffer(Type.Position, 3, new float[]{
            0, 0, 0,
            horzFolds[0], 0, 0,
            horzFolds[1], 0, 0,
            size.x, 0, 0,
            size.x, vertFolds[0], 0,
            size.x, vertFolds[1], 0,
            size.x, size.y, 0,
            horzFolds[1], size.y, 0,
            horzFolds[0], size.y, 0,
            0, size.y, 0,
            0, vertFolds[1], 0,
            0, vertFolds[0], 0,
            // The center
            horzFolds[0], vertFolds[0], 0,
            horzFolds[1], vertFolds[0], 0,
            horzFolds[1], vertFolds[1], 0,
            horzFolds[0], vertFolds[1], 0
        });
        setBuffer(Type.TexCoord, 2, new float[]{
            horzTexCoords[0], vertTexCoords[0],
            horzTexCoords[1], vertTexCoords[0],
            horzTexCoords[2], vertTexCoords[0],
            horzTexCoords[3], vertTexCoords[0],
            horzTexCoords[3], vertTexCoords[1],
            horzTexCoords[3], vertTexCoords[2],
            horzTexCoords[3], vertTexCoords[3],
            horzTexCoords[2], vertTexCoords[3],
            horzTexCoords[1], vertTexCoords[3],
            horzTexCoords[0], vertTexCoords[3],
            horzTexCoords[0], vertTexCoords[2],
            horzTexCoords[0], vertTexCoords[1],
            // The center
            horzTexCoords[1], vertTexCoords[1],
            horzTexCoords[2], vertTexCoords[1],
            horzTexCoords[2], vertTexCoords[2],
            horzTexCoords[1], vertTexCoords[2]
        });

        setBuffer(Type.Normal, 3, new float[]{
            0, 0, 1,
            0, 0, 1,
            0, 0, 1,
            0, 0, 1,
            0, 0, 1,
            0, 0, 1,
            0, 0, 1,
            0, 0, 1,
            0, 0, 1,
            0, 0, 1,
            0, 0, 1,
            0, 0, 1,
            // The center
            0, 0, 1,
            0, 0, 1,
            0, 0, 1,
            0, 0, 1
        });

        updateBound();
    }
}

Thanks for the code. I will try to get to it this weekend as I have some other stuff to catch up on.

Note: as we are circling in another thread, if you aren’t building Lemur from source then it might not help you. Lemur HEAD depends on JME 3.1 right now and I’ve been loathe to branch it just to push 3.0 releases. I could but it’s a lot of work I hoped to avoid since JME 3.1 has been perpetually “any day now” for some time… :confused:

[Should use this new thread]
I’m using default JME jar and lemur, both from update center, the lastest update center i think :smile:

I will try to compile both jme from git source and lemur r1607 from svn if there is any incompatible may cause the labels to be hidden.

;), just meant we are not mentioned on their website whereas libgdx and others are…officially ^^

I means I don’t think they going to name all engines around. Especially we don’t have such official pipeline of packaging spritesheets anyway. But I think other may find interests in using such tool in … JME SDK directly :slight_smile: I used it for ages.

Right :+1:

Just found it, really cool tool, byebye paint x)

Contributing with my own 9-patch quad implementation too (based on TbtQuad). This one takes the offset and size of the atlas inner-region as extra values:

/**
 * @author NemesisMate, based on Paul Speed's TbtQuad.
 * @see com.simsilica.lemur.geom.TbtQuad
 */
public class AutoTbtQuad extends Mesh
        implements Cloneable{

    private Vector2f size;
    private Vector2f imageSize;
    private float[] horzTexCoords;
    private float[] vertTexCoords;

    private float[] horzFolds;
    private float[] vertFolds;

    public AutoTbtQuad(float width, float height) {
        this.size = new Vector2f(width, height);
        this.imageSize = new Vector2f(width, height);

        this.horzFolds = new float[] { width/3f, 2 * width/3f };
        this.vertFolds = new float[] { height/3f, 2 * height/3f };

        this.horzTexCoords = new float[]{0, 1 / 3f, 2 / 3f, 1};
        this.vertTexCoords = new float[]{0, 1 / 3f, 2 / 3f, 1};

        refreshGeometry();
    }

    public AutoTbtQuad(int regionOffsetX, int regionOffsetY, float regionWidth, float regionHeight, int x1, int y1, int x2, int y2, int imageWidth, int imageHeight) {
        this.size = new Vector2f(regionWidth, regionHeight);
        this.imageSize = new Vector2f(imageWidth, imageHeight);

        float offsetX = regionOffsetX / (float) imageWidth;
        float offsetY = regionOffsetY / (float) imageHeight;

        horzFolds = new float[] { x1, x2 };
        vertFolds = new float[] { y1, y2 };

        this.horzTexCoords = new float[]{ offsetX, (float) x1 / imageWidth + offsetX, (float) x2 / imageWidth + offsetX, offsetX + regionWidth / imageWidth };
        this.vertTexCoords = new float[]{ offsetY, (float) y1 / imageHeight + offsetY, (float) y2 / imageHeight + offsetY, offsetY + regionHeight / imageHeight };


        refreshGeometry();


    }

    public AutoTbtQuad clone() {
        AutoTbtQuad result = (AutoTbtQuad) super.deepClone();

        result.size = size.clone();
        result.imageSize = imageSize.clone();
        result.horzTexCoords = horzTexCoords.clone();
        result.vertTexCoords = vertTexCoords.clone();

        return result;
    }

    public Vector2f getSize() {
        return size;
    }

    public void updateSize(float width, float height) {
        if (size.x == width && size.y == height) {
            return;
        }

        size.set(width, height);
        refreshGeometry();
    }

    protected void refreshGeometry() {
        // Vertexes are arranged as:
        //
        //  9 -- 8 -- 7 -- 6
        //  | \  | /  | /  |
        // 10 --15 --14 -- 5
        //  | /  | /  | /  |
        // 11 --12 --13 -- 4
        //  | /  | /  | \  |
        //  0 -- 1 -- 2 -- 3
        //
        // Note: some of the corners are flipped to better support extrusion
        // if the caller desires to pull up the center quad.

        setBuffer(VertexBuffer.Type.Index, 3, new short[]{
                0, 1, 12,
                0, 12, 11,
                1, 2, 13,
                1, 13, 12,
                2, 3, 13,
                3, 4, 13,
                13, 4, 5,
                13, 5, 14,
                14, 5, 6,
                14, 6, 7,
                15, 14, 7,
                15, 7, 8,
                10, 15, 9,
                15, 8, 9,
                11, 12, 15,
                11, 15, 10,

                // The center
                12, 13, 14,
                12, 14, 15
        });

        float x0 = horzFolds[0];
        float x1 = size.x - horzFolds[1];
        float y0 = vertFolds[0];
        float y1 = size.y - vertFolds[1];

        setBuffer(VertexBuffer.Type.Position, 3, new float[] {
                0, 0, 0,
                x0, 0, 0,
                x1, 0, 0,
                size.x, 0, 0,
                size.x, y0, 0,
                size.x, y1, 0,
                size.x, size.y, 0,
                x1, size.y, 0,
                x0, size.y, 0,
                0, size.y, 0,
                0, y1, 0,
                0, y0, 0,
                // The center
                x0, y0, 0,
                x1, y0, 0,
                x1, y1, 0,
                x0, y1, 0
        });
        setBuffer(VertexBuffer.Type.TexCoord, 2, new float[]{
                horzTexCoords[0], vertTexCoords[0],
                horzTexCoords[1], vertTexCoords[0],
                horzTexCoords[2], vertTexCoords[0],
                horzTexCoords[3], vertTexCoords[0],
                horzTexCoords[3], vertTexCoords[1],
                horzTexCoords[3], vertTexCoords[2],
                horzTexCoords[3], vertTexCoords[3],
                horzTexCoords[2], vertTexCoords[3],
                horzTexCoords[1], vertTexCoords[3],
                horzTexCoords[0], vertTexCoords[3],
                horzTexCoords[0], vertTexCoords[2],
                horzTexCoords[0], vertTexCoords[1],
                // The center
                horzTexCoords[1], vertTexCoords[1],
                horzTexCoords[2], vertTexCoords[1],
                horzTexCoords[2], vertTexCoords[2],
                horzTexCoords[1], vertTexCoords[2]
        });

        setBuffer(VertexBuffer.Type.Normal, 3, new float[]{
                0, 0, 1,
                0, 0, 1,
                0, 0, 1,
                0, 0, 1,
                0, 0, 1,
                0, 0, 1,
                0, 0, 1,
                0, 0, 1,
                0, 0, 1,
                0, 0, 1,
                0, 0, 1,
                0, 0, 1,
                // The center
                0, 0, 1,
                0, 0, 1,
                0, 0, 1,
                0, 0, 1
        });

        updateBound();
    }
}
1 Like

Nice, nice… :slight_smile:

It’s a year old thread and someone still find it useful. Thanks for the contribution.