Does JME have the ability to group small objects into on renderable object?

One thing that is needed for a prototype I want to work on, is rendering lots of small cubes in groups. I read somewhere that GPUs don't like drawing lots of small objects. Is there any feature of JME that can be used to group a number of cubes into a single renderable object to increase performance?



Thanks

Not yet, no. But could be of use, yes…

You could do something like create a GroupedTriMesh object that extends TriMesh and has an add method. The add method takes a TriMesh parameter appends it's Vertex, Normal, Color, Texture and Index buffer data to its own buffers.

   

I could but I think that's a big beyond my knowledge right now! :slight_smile:

U could make some method that transforms a cube to the set of the vertex that construct it.

Then compute the final Trimesh from the points.

Fraid you've still lost me. This is what I'm working on so far:


public class GeometryBatch extends TriMesh {
   
    public static GeometryBatch create(TriMesh mesh)
    {
        return new GeometryBatch(mesh);
    }
   
    /** Creates a new instance of GeometryBatch */
    public GeometryBatch(TriMesh mesh) {
        FloatBuffer vertices = mesh.getVertexBuffer();
        FloatBuffer normal = mesh.getNormalBuffer();
        FloatBuffer color = mesh.getColorBuffer();
        FloatBuffer texture = mesh.getTextureBuffer();
        IntBuffer indices = mesh.getIndexBuffer();
       
        reconstruct(vertices, normal, color, texture, indices);
    }
   
    public void addMesh(TriMesh mesh)
    {
        int total = this.getVertexBuffer().capacity() + mesh.getVertexBuffer().capacity();      
        FloatBuffer vertices = FloatBuffer.allocate(total);
        vertices.put(this.getVertexBuffer());
        vertices.put(mesh.getVertexBuffer());
       
        total = this.getNormalBuffer().capacity() + mesh.getNormalBuffer().capacity();      
        FloatBuffer normal = FloatBuffer.allocate(total);
        normal.put(this.getNormalBuffer());
        normal.put(mesh.getNormalBuffer());
       
        total = this.getColorBuffer().capacity() + mesh.getColorBuffer().capacity();         // null pointer exception here
        FloatBuffer color = FloatBuffer.allocate(total);
        color.put(this.getColorBuffer());
        color.put(mesh.getColorBuffer());
       
        total = this.getTextureBuffer().capacity() + mesh.getTextureBuffer().capacity();      
        FloatBuffer texture = FloatBuffer.allocate(total);
        texture.put(this.getTextureBuffer());
        texture.put(mesh.getTextureBuffer());
       
        total = this.getIndexBuffer().capacity() + mesh.getIndexBuffer().capacity();      
        IntBuffer indices = IntBuffer.allocate(total);
        indices.put(this.getIndexBuffer());
        indices.put(mesh.getIndexBuffer());
        reconstruct(vertices, normal, color, texture, indices);
    }
}



Except I get get a NullPointerException.


        Sphere s2 = new Sphere("Sphere", 30, 30, 25);
        s2.setLocalTranslation(new Vector3f(0, 250, 0));
        Sphere s3 = new Sphere("Sphere", 30, 30, 25);
        s3.setLocalTranslation(new Vector3f(50, 250, 50));
        GeometryBatch geom = GeometryBatch.create("Fred", s2);
        geom.addMesh(s3);
        geom.setLocalTranslation(new Vector3f(0, 250, 0));
        geom.setModelBound(new BoundingBox());
        geom.updateModelBound();
        rootNode.attachChild(geom);



this.getColorBuffer() and mesh.getColorBuffer() both return null.

yes, you are going to have to check for null. TriMeshes are not required to have all buffers initialized. Color for instance is not used in the Shape classes.

Ah I see thanks!

What do I need to do if one color FloatBuffer is null? I tried this:


   FloatBuffer color = null;
   if (this.getColorBuffer() == null && mesh.getColorBuffer() == null)
   {
       color = FloatBuffer.allocate(0);
   }
   else if (this.getColorBuffer() == null || mesh.getColorBuffer() == null)
   {
       if (this.getColorBuffer() == null)
       {
      color = this.getColorBuffer();
       }
       else if (mesh.getColorBuffer() == null)
       {
      color = mesh.getColorBuffer();
       }
   }
   else
   {
       total = this.getColorBuffer().capacity() + mesh.getColorBuffer().capacity();
       color = FloatBuffer.allocate(total);
       color.put(this.getColorBuffer());
       color.put(mesh.getColorBuffer());
   }



But that just gives me the following when I use the code below:

java.lang.IllegalArgumentException: FloatBuffer is not direct
        at org.lwjgl.BufferChecks.checkDirect(BufferChecks.java:143)
        at org.lwjgl.opengl.GL11.glVertexPointer(GL11.java:2037)
        at com.jme.renderer.lwjgl.LWJGLRenderer.predrawGeometry(Unknown Source)
        at com.jme.renderer.lwjgl.LWJGLRenderer.draw(Unknown Source)
        at com.jme.scene.TriMesh.draw(Unknown Source)
        at com.jme.renderer.RenderQueue.renderOpaqueBucket(Unknown Source)
        at com.jme.renderer.RenderQueue.renderBuckets(Unknown Source)
        at com.jme.renderer.Renderer.renderQueue(Unknown Source)
        at com.jme.renderer.lwjgl.LWJGLRenderer.displayBackBuffer(Unknown Source)
        at com.jme.app.BaseGame.start(Unknown Source)
        at OldLevelLoader.main(OldLevelLoader.java:73)



        Sphere s2 = new Sphere("Sphere1", 30, 30, 25);
        s2.setLocalTranslation(new Vector3f(0, 0, 0));
        s2.setModelBound(new BoundingBox());
        s2.updateModelBound();

        Sphere s3 = new Sphere("Sphere2", 30, 30, 25);
        s3.setLocalTranslation(new Vector3f(50, 0, 50));
        s3.setModelBound(new BoundingBox());
        s3.updateModelBound();
       
        GeometryBatch geom = GeometryBatch.create("Fred", s2);
        geom.addMesh(s3);
        geom.setLocalTranslation(new Vector3f(0, 250, 0));
        geom.setModelBound(new BoundingBox());
        geom.updateModelBound();
        rootNode.attachChild(geom);

You can't allocate a FloatBuffer directly, you have to allocate a direct ByteBuffer using ByteBuffer.allocateDirect and then use asFloatBuffer. This is so platform native code (OpenGL and LWJGL) can read the buffer.



You can also use com.jme.util.geom.BufferUtils for this.

Gotcha! I see now.



Ok so I have:


/*
 * GeometryBatch.java
 *
 * Created on October 30, 2005, 7:20 PM
 *
 * To change this template, choose Tools | Options and locate the template under
 * the Source Creation and Management node. Right-click the template and choose
 * Open. You can then make changes to the template in the Source Editor.
 */
import com.jme.scene.TriMesh;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ByteBuffer;

/**
 *
 * @author mike
 */
public class GeometryBatch extends TriMesh {
   
    /** Creates a new instance of GeometryBatch */
    public GeometryBatch() {
        FloatBuffer vertices;
        FloatBuffer normal;
        FloatBuffer color;
        FloatBuffer texture;
        IntBuffer indices;

        vertices = FloatBuffer.allocate(0);
        normal = FloatBuffer.allocate(0);
        color = FloatBuffer.allocate(0);
        texture = FloatBuffer.allocate(0);
        indices = IntBuffer.allocate(0);
        reconstruct(vertices, normal, color, texture, indices);
    }
   
    public static GeometryBatch create(String name, TriMesh mesh)
    {
   System.out.println("GeometryBatch::create");
   System.out.println(mesh.toString());
        return new GeometryBatch(name, mesh);
    }
   
    /** Creates a new instance of GeometryBatch */
    public GeometryBatch(String name, TriMesh mesh) {
   super(name);

   System.out.println("GeometryBatch::GeometryBatch");
   System.out.println(mesh.toString());
        FloatBuffer vertices = mesh.getVertexBuffer();
        FloatBuffer normal = mesh.getNormalBuffer();
        FloatBuffer color = mesh.getColorBuffer();
        FloatBuffer texture = mesh.getTextureBuffer();
        IntBuffer indices = mesh.getIndexBuffer();
       
        reconstruct(vertices, normal, color, texture, indices);
    }
   
    private static FloatBuffer createFloatBuffer(int size)
    {
   // 4 bytes per float.
   ByteBuffer buffer = ByteBuffer.allocateDirect (size * 4);
   return buffer.asFloatBuffer();
    }
   
    private static IntBuffer createIntBuffer(int size)
    {
   // 4 bytes per int.
   ByteBuffer buffer = ByteBuffer.allocateDirect (size * 4);
   return buffer.asIntBuffer();
    }
   
    public void addMesh(TriMesh mesh)
    {
        System.out.println("Adding mesh " + indexBuffer.toString());

        int total = this.getVertexBuffer().capacity() + mesh.getVertexBuffer().capacity();      
        FloatBuffer vertices = GeometryBatch.createFloatBuffer(total);
        vertices.put(this.getVertexBuffer());
        vertices.put(mesh.getVertexBuffer());
       
        total = this.getNormalBuffer().capacity() + mesh.getNormalBuffer().capacity();
        FloatBuffer normal = GeometryBatch.createFloatBuffer(total);
        normal.put(this.getNormalBuffer());
        normal.put(mesh.getNormalBuffer());
       
   FloatBuffer color = null;
   if (this.getColorBuffer() == null && mesh.getColorBuffer() == null)
   {
       color = GeometryBatch.createFloatBuffer(0);
   }
   else if (this.getColorBuffer() == null || mesh.getColorBuffer() == null)
   {
       if (this.getColorBuffer() == null)
       {
      color = this.getColorBuffer();
       }
       else if (mesh.getColorBuffer() == null)
       {
      color = mesh.getColorBuffer();
       }
   }
   else
   {
       total = this.getColorBuffer().capacity() + mesh.getColorBuffer().capacity();
       color = GeometryBatch.createFloatBuffer(total);
       color.put(this.getColorBuffer());
       color.put(mesh.getColorBuffer());
   }
       
        total = this.getTextureBuffer().capacity() + mesh.getTextureBuffer().capacity();      
        FloatBuffer texture = GeometryBatch.createFloatBuffer(total);
        texture.put(this.getTextureBuffer());
        texture.put(mesh.getTextureBuffer());
       
        total = this.getIndexBuffer().capacity() + mesh.getIndexBuffer().capacity();      
        IntBuffer indices = GeometryBatch.createIntBuffer(total);
        indices.put(this.getIndexBuffer());
        indices.put(mesh.getIndexBuffer());
       
        reconstruct(vertices, normal, color, texture, indices);
   
   System.out.println("addMesh");
   this.toString();
    }
}



        Sphere s2 = new Sphere("Sphere1", 30, 30, 25);
        s2.setLocalTranslation(new Vector3f(0, 0, 0));
        s2.setModelBound(new BoundingBox());
        s2.updateModelBound();

        Sphere s3 = new Sphere("Sphere2", 30, 30, 25);
        s3.setLocalTranslation(new Vector3f(50, 0, 50));
        s3.setModelBound(new BoundingBox());
        s3.updateModelBound();
       
        GeometryBatch geom = GeometryBatch.create("Fred", s2);
        geom.addMesh(s3);
        geom.setLocalTranslation(new Vector3f(0, 250, 0));
        geom.setModelBound(new BoundingBox());
        geom.updateModelBound();
        rootNode.attachChild(geom);



If I remove the geom.addMesh(s3) I get one sphere. With it I get nothing. One step closer!

Sorry for the long posts.

Well to start with, your spheres are identical, so even if it "works" as you think it would, you'll only see one sphere. The local translation is send to OpenGL when rendering an object. It does not influence any of the vertices' data.

Oh



scratches head

with the Sphere(String name, Vector3f center, int zSamples, int radialSamples, float radius) constructor you can make two spheres with a different center. If you merge those you should see two Spheres.



However you can't merge the two objects just by appending all the buffers. For example the Index buffer points to positions in the Vertex buffer, so again you'll be drawing the same Sphere twice. You'll need to update all the values in the second Index Buffer by the amount of vertices from the first Vertex buffer.



However, you saw nothing at all. I think that's because you need to rewind() the buffers from the Spheres you're putting in. If you don't know how (Byte)Buffers work exactly, you can look in the JavaDoc of the SDK or try to find an example on the internet (I'd try the Sun java site).


You are going to need to add an index offset. Remember that the index buffer is referencing the index in the array of vertices, but since you are appending vertices, the indices are going to be affected. So index 'i' becomes index i+numberOfVerticesAlreadyIn.



Edit: Doh, Llama beat me to it.

It all seems so obvious now!!!  :lol:



Thanks again

Is this right - a Box has:



New vertex count = 72

New normal count = 72

New texture count = 48

New indices count = 36

Yes



indices: 6 sides to the box, 2 triangles per side, 3 indices per triangle = 36

textures: 8 points to a box, each point used for 3 sides, 2 floats for a Vector2f = 48

vers and normals: 8 points to a box, each point used for 3 sides, 3 floats for a Vector3f = 72

Cool then I'm one more step closer!



Thanks again. You really need an "I'm not worthy" emoticon for the forums.  :smiley:

I must be using the FoatBuffers wrong as I still don't see anything. Oh well, keep plugging away!