11

I have the following code snippet that generates a 3D cube:

ModelBuilder modelBuilder = new ModelBuilder();

box = modelBuilder.createBox(2f, 2f, 2f,
                new Material(TextureAttribute.createDiffuse(AssetLoader.tr[0])),
                VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates
        );

So far so good. The problem is that all faces of the cube uses the same texture, whereas what I want is Assetloader.tr[], which is an array with 6 individual textures to appear on each face respectively.

I have tried

box.nodes.get(0).parts.get(0).material.set(new Material(TextureAttribute.createDiffuse(AssetLoader.tr[0])));
box.nodes.get(0).parts.get(1).material.set(new Material(TextureAttribute.createDiffuse(AssetLoader.tr[1])));
...

but somehow the documentation doesn't give me any hints for how to do it properly. I'm a bit stuck here atm.

Scalarr
  • 746
  • 7
  • 26

1 Answers1

18

There are several considerations to keep in mind. First of all make sure to carefully read: https://github.com/libgdx/libgdx/wiki/ModelBuilder%2C-MeshBuilder-and-MeshPartBuilder.

Secondly, try to avoid the ModelBuilder#createXXX methods. They are just a shortcut for debugging and testing purposes. If you look at the code behind it, you'll see that's very straightforward:

modelBuilder.begin();
modelBuilder.part("box", GL20.GL_TRIANGLES, 
        VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates,
        new Material(TextureAttribute.createDiffuse(AssetLoader.tr[0])))
    .box(2f, 2f, 2f);
box = modelBuilder.end();

As you can see this creates a single part for the entire box, so trying to access a second part (as you do in your example) will not work. But because you want to use a different material for each face, you'll need to create a part for each face.

int attr = VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates;
modelBuilder.begin();
modelBuilder.part("front", GL20.GL_TRIANGLES, attr, new Material(TextureAttribute.createDiffuse(AssetLoader.tr[0])))
    .rect(-2f,-2f,-2f, -2f,2f,-2f,  2f,2f,-2, 2f,-2f,-2f, 0,0,-1);
modelBuilder.part("back", GL20.GL_TRIANGLES, attr, new Material(TextureAttribute.createDiffuse(AssetLoader.tr[1])))
    .rect(-2f,2f,2f, -2f,-2f,2f,  2f,-2f,2f, 2f,2f,2f, 0,0,1);
modelBuilder.part("bottom", GL20.GL_TRIANGLES, attr, new Material(TextureAttribute.createDiffuse(AssetLoader.tr[2])))
    .rect(-2f,-2f,2f, -2f,-2f,-2f,  2f,-2f,-2f, 2f,-2f,2f, 0,-1,0);
modelBuilder.part("top", GL20.GL_TRIANGLES, attr, new Material(TextureAttribute.createDiffuse(AssetLoader.tr[3])))
    .rect(-2f,2f,-2f, -2f,2f,2f,  2f,2f,2f, 2f,2f,-2f, 0,1,0);
modelBuilder.part("left", GL20.GL_TRIANGLES, attr, new Material(TextureAttribute.createDiffuse(AssetLoader.tr[4])))
    .rect(-2f,-2f,2f, -2f,2f,2f,  -2f,2f,-2f, -2f,-2f,-2f, -1,0,0);
modelBuilder.part("right", GL20.GL_TRIANGLES, attr, new Material(TextureAttribute.createDiffuse(AssetLoader.tr[5])))
    .rect(2f,-2f,-2f, 2f,2f,-2f,  2f,2f,2f, 2f,-2f,2f, 1,0,0);
box = modelBuilder.end();

However, having a part for each face does imply a render call for each face. It is more performant to make sure that all TextureRegions share the same texture and use that instead:

int attr = VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates;
modelBuilder.begin();
MeshPartBuilder mpb = modelBuilder.part("box", GL20.GL_TRIANGLES, attr, new Material(TextureAttribute.createDiffuse(AssetLoader.tr[0].getTexture())));
mpb.setUVRange(AssetLoader.tr[0]);
mpb.rect(-2f,-2f,-2f, -2f,2f,-2f,  2f,2f,-2, 2f,-2f,-2f, 0,0,-1);
mpb.setUVRange(AssetLoader.tr[1]);
mpb.rect(-2f,2f,2f, -2f,-2f,2f,  2f,-2f,2f, 2f,2f,2f, 0,0,1);
mpb.setUVRange(AssetLoader.tr[2]);
mpb.rect(-2f,-2f,2f, -2f,-2f,-2f,  2f,-2f,-2f, 2f,-2f,2f, 0,-1,0);
mpb.setUVRange(AssetLoader.tr[3]);
mpb.rect(-2f,2f,-2f, -2f,2f,2f,  2f,2f,2f, 2f,2f,-2f, 0,1,0);
mpb.setUVRange(AssetLoader.tr[4]);
mpb.rect(-2f,-2f,2f, -2f,2f,2f,  -2f,2f,-2f, -2f,-2f,-2f, -1,0,0);
mpb.setUVRange(AssetLoader.tr[5]);
mpb.rect(2f,-2f,-2f, 2f,2f,-2f,  2f,2f,2f, 2f,-2f,2f, 1,0,0);
box = modelBuilder.end();

While this might help you do you want, you should really reconsider your approach. As you can see, creating a model by code can get messy really fast. And, moreover, creating a single model for a box is in most cases far from optimal unless your goal is to only render a single box and nothing more than a box. Instead use a modeling application to create your models. Have a look at my blog at http://blog.xoppa.com/ for more info. If you really want to modify parts yourself, then make sure to read at least up to and including the "behind the scenes" tutorials.

Xoppa
  • 7,983
  • 1
  • 23
  • 34
  • Thank you - It works! Yes, I had a look at the code and thought that there gotta be some simpler and better way that I couldn't figure out. Anyway, it does seem like this feature of being able to attach different materials to different sides should somehow be included in these simple generated primitives of modelBuilder. I don't agree that it should only be for testing and debugging. Many simple yet awesome things can be made with these :) Thanks Again! – Scalarr Dec 16 '14 at 00:45
  • 1
    What if you were planning on having several boxes that you wanted to treat as objects that you can interact with? For example a box that has properties that could affect a character's fighting attributes. P.S. Thank you for you blog. – Ishmael Jun 12 '15 at 18:06
  • Hello @Xoppa , i am creating 2d sprites using models(one model for each) and it has poor performance, can i use the MeshPartBuider for each sprite? using ModelCache is also not helping as TextureRigions are considered as different. Please suggest best way to draw 2d Sprites in 3D i will create the framework based on your suggestion (should i use decals, createRect, ModelCache, single model with many parts, single model meshpart with rect or something else), i like decalBatch should i modify it for building shape more then 4 vertices? Thanks in advance – Diljeet Dec 20 '15 at 14:04
  • 1
    @Xoppa this is a great guide, i did read about these things earlier but current they are more organized and i think i can modify them for 3d framework and models, i will update you about my progress. Currently its a bit over my head because its like thinking of creating a whole new framework(i can use decal instead of sprite), but i will make one, thanks for your help. – Diljeet Dec 20 '15 at 14:45
  • 1
    @Xoppa i did it, yes i really made it work, i made a Sprite3D that can be rendered in ModelBatch. ---- I created a MyModelBatch class which extends ModelBatch, and it can render a object of interface VerticesRenderable(methods getTexture(), getVertices(), getIndices()) and the Sprite3D implements the VerticesRenderable. Here the plus point is that we can render Sprites and models in the MyModelBatch all together, and the script of creating mesh of grouped sprites is in MyModelBatch. I also managed to make 3D Label for texts using these sprites for rendering texts in 3D. – Diljeet Dec 22 '15 at 08:13
  • @Xoppa i mean now we can render texts in 3d, with 3d rotation, scale and position. Rotating of Label/Bitmap Font was not possible before in Libgdx in 2d, i can also make a class for 2d label, which can have rotation. - I want to show you my code and you can test it, and if the code is upto the mark, then you can add it to libgdx, many have struggles for 3D Sprites and many for BitmapFont rotation in 2D and BitmapFont implementation in 3D. One more thing, i removed this code meshBuilder.setVertexTransform(card.transform); and pre-calculated vertices for avoiding these trasnforms every frame. – Diljeet Dec 22 '15 at 08:22
  • You're a lifesaver! I've unsuccessfully googled how to texture a plane for hours now. – xjcl Aug 24 '20 at 09:08