4

Trying get my head around the texture wrapper in (the awesome) LibGDX framework and I need help.

I would like to bind a texture (according to Mesh, Color & Texture) that is extracted from a TextureAtlas packed with the TexturePacker. The texture is binding to a rectangular mesh.

I want the texture (instance of Texture) to basically be extracted from a packed file.

Is it doable to use a the createsprite or findregion methods and somehow skip the filehandle step?

Additionally: Anything special one should keep in mind when combining the above method with the AssetManager?

Thanks for sorting me out!

James Skemp
  • 8,018
  • 9
  • 64
  • 107
user1450458
  • 67
  • 1
  • 6

2 Answers2

10

Create the TextureRegion

First, you create a TextureAtlas object by pointing at the text file that describes your atlas (the tool that creates the atlas will create two files: an image and a text file describing its contents):

TextureAtlas myTextures = new TextureAtlas("images/packed.txt");

Then you can look up a TextureRegion in that atlas (that is, a specific sub-texture of the atlas). The region should have the basename of the original file that was used (there are more details and options if you follow some of the special naming conventions to create arrays of texture elements, but leave that off for now):

TextureRegion region = myTextures.findRegion(fname);

Configure a textured Mesh

To draw this texture region on a mesh, you'll need to initialize the Mesh with support for texture coordinates:

Mesh myMesh = new Mesh(...,
                       new VertexAttribute(Usage.TextureCoordinates, 2, "y"));

The new VertexAttribute(Usage.TextureCoordinates, 2, ...) tells libGDX that this mesh will have two texture coordinates per vertex (traditionally, the two texture coordinates are called u and v). You can have a bunch of different attributes per vertex, but I'm going to assume the only other attribute is a 3-valued Usage.Position for the x,y,z spatial coordinates.

Now, in the array of floats that defines your mesh (the array you pass to setVertices) you need to set x, y, and z spatial coordinates plus u, and v texture coordinates for each vertex:

final int floatsPerVertex = 5; // 3 spatial +  2 texture
float[] meshData = new float[numVerticies * floatsPerVertex];
for (int i = 0; i < numVerticies; i++) {
   meshData[(i * floatsPerVertex) + 0] = ... ; // x coordinate of i'th vertex
   meshData[(i * floatsPerVertex) + 1] = ... ; // y coordinate of i'th vertex
   meshData[(i * floatsPerVertex) + 2] = ... ; // z coordinate of i'th vertex
   meshData[(i * floatsPerVertex) + 3] = ... ; // u texture coordinate of i'th vertex
   meshData[(i * floatsPerVertex) + 4] = ... ; // v texture coordinate of i'th vertex
}
myMesh.setVertices(meshData);

You can compute the right u and v for a specific TextureRegion using the getU, getV, getU2, and getV2 methods. Note that texture coordinates have the origin (u1, v1) in the top-left, and the y-axis points "down" (screen and spatial coordinates in OpenGL usually have the origin in the bottom-left and the y-axis points "up"). Its a little complicated, but very flexible in that you can flip or stretch or distort the texture as it maps onto your mesh.

Since the texture is large (say 512x512) and the specific region is a small subset of that (say 20x20 at 128x128), you'll actually end up giving your mesh texture coordinates that utilize just the 20x20 subset of the entire 512x512 image.

Render the textured Mesh

Finally, when you render you need to bind the image, and enable texturing before rendering:

region.getTexture().bind();
Gdx.graphics.getGL10().glEnable(GL10.GL_TEXTURE_2D);
myMesh.render();
Gdx.graphics.getGL10().glDisable(GL10.GL_TEXTURE_2D);

Note that this is much less efficient than it should be. Part of the benefit of a texture atlas is that it should contain a lot of regions that can be rendered together, so you only need to bind one texture, and then render a lot of different textured meshes from that one bound texture.

SpriteBatch supports sprites defined with a TextureRegion and the AssetManager supports loading and finding a TextureAtlas as a first-class element.

P.T.
  • 24,557
  • 7
  • 64
  • 95
  • Appreciate your help. Thanks! – user1450458 Jun 21 '12 at 18:49
  • @user1450458 Glad you found the answer useful. If this satisfactorily answers your question, please "accept" the answer (click the outline of a checkmark on the left so its green). See the answering section of: http://stackoverflow.com/faq#howtoask – P.T. Jun 21 '12 at 18:53
  • I didn't realize the accept-answer tag, but thanks. Followup: I cannot find the setTexureCoordinates method for the Mesh class or any other method handling a TextureRegion or an AtlasRegion. – user1450458 Jun 25 '12 at 07:51
  • @user1450458 Oops, I botched the `setTextureCoordinates` thing .. that's my own method. I'll update the answer to include the right details. – P.T. Jun 25 '12 at 18:34
  • I finally have time again to look through this and I cant make one thing work: region. getImage() method does not exist. and additionally if I would use getTexture() methcod then I will get the whole packed texturepackeed page in return. Any solutions? – user1450458 Sep 10 '12 at 11:23
  • Oops, sorry about that! You should use the `getTexture` method on the region and bind that. (So its now `region.getTexture().bind()`). Binding the entire texture is correct. The idea is that you call bind once (which is expensive -- think of it as uploading the texture to your video card) with a very large texture, and then you 'draw' lots of (u, v) sub-regions from that one texture. – P.T. Sep 10 '12 at 16:53
1

Got it to work using the above explanation.

I can't believe so few are people are running asking this question as it seems like something that others would want to do.

From the accepted solution I created a function that also does the math for the new UV position.

Tested, and it works for me but please review as i'm not a java developer.

public Mesh RebuildMeshUVtoTextureRegion(Mesh ObjectMesh, TextureRegion UVMapPos)
{
    int numFloats = ObjectMesh.getNumVertices() * ObjectMesh.getVertexSize() / 4;
    float[] vertices = new float[numFloats];
    ObjectMesh.getVertices(vertices);

    int numIndices = ObjectMesh.getNumIndices();
    short SourceIndices[] = new short[numIndices];
    ObjectMesh.getIndices(SourceIndices);

    final int floatsPerVertex = 5;
    int TimesToLoop = ((vertices.length) /floatsPerVertex); 

    float previousU;
    float previousV;

    float FullMapHeight = UVMapPos.getTexture().getHeight();
    float FullMapWidth  = UVMapPos.getTexture().getWidth();
    float NewMapWidth = UVMapPos.getRegionWidth();
    float NewMapHeight = UVMapPos.getRegionHeight();

    float FullMapUPercent;
    float FullMapVPercent;

    for (int i = 0; i < TimesToLoop; i++) 
    {   
        previousU = (vertices[(i * floatsPerVertex) + 3]);
        previousV = (vertices[(i * floatsPerVertex) + 4]);
        FullMapUPercent = previousU / FullMapWidth;
        FullMapVPercent = previousV / FullMapHeight;
        vertices[(i * floatsPerVertex) + 3] = (NewMapWidth * FullMapUPercent) + UVMapPos.getU(); //New U
        vertices[(i * floatsPerVertex) + 4] = (NewMapHeight * FullMapVPercent) + UVMapPos.getV();//New V
    }

    ObjectMesh.setVertices(vertices);
    ObjectMesh.setIndices(SourceIndices);

    return ObjectMesh;
}
Paradox
  • 11
  • 2