GeometryBatch

Hello,



two questions:

  • It looks like GeometryBatch is not yet in the nightly build. Is this intended (e.g. there is a better way)?
  • Has someone made some investigations for implementing rotations (and not only translations) when adding meshes to GeometryBatch?



    (Yeah, I know: "If you want it, implement it", but I don't really know how to do it…)

Landai:

So far GeometryBatch works fine for me (and fast), so I just tried to make it more flexible. Maybe both approaches can be "merged" (if it makes sense)




The GeometryInstance approach (my code) should be as fast and designed in a flexible way, that was the reason that those classes was added :slight_smile: It is designed to create "static batches", but it is easy to change the attributes of the instances, and recommit the buffers that need to be updated. By "static" I mean they should not be updated every frame …



Chirstius:
What would be interesting is some way to "address" into the batch for a given piece of geometry, modify it, and have the resulting batch displayed.




With the GI classes, it is possible, but they only update one buffer at the time. So if you modify the position (or any other attributes) of one instance, all instances vertices are committed. I think it would be an easy task to update the classes so it is possible to update only the changed instances (I might add that functionality when I have time) :slight_smile:


Would this also allow modification of the texture coordinates to individually map textures (or sections of one large texture?) to pieces of the batch?  That was kinda the route I wanted to go down when I was thinking about how to add that capability.


That should be no problem :)

Snylt said:

Would this also allow modification of the texture coordinates to individually map textures (or sections of one large texture?) to pieces of the batch?
  1. It should get included in the nightly build (i think) :slight_smile:
  2. The rotation is very much implemented… The boxes in TestGeometryInstancing are given a rotation :smiley:

I had wanted to tackle this a while back but never had the chance (yeah, I know).  I also want (and someday hope to try) the ability to support different textures and support changing them dynamically (on an individual piece of the batch - if that can even be done).  I'm not using GeometryBatch for unique pieces of geometry so to speak, I'm creating a SharedMesh and then using that to populate the batch.  Maybe it would work if I used individual instances but when creating a 50x50 (or larger) grid of objects this gets to be unmanagable and slow pretty quickly.



We've been down this road before Landei and I know our general usage of GeometryBatch is fairly similar.  Maybe we can combine forces and get some of this stuff done?

For me the important thing is rotation, so I'll try to get this running…


So, I modified GeometryBatch. I added the addMeshGeometry methods, which read the translation and rotation from the mesh itself (and allow additional Transformations) and I added the possibility to perform an arbitrary "Transformation", which can be "everything" (translation, rotation, scalation and even nonaffine transformation). The code is not really tested, so be careful…



import com.jme.math.Quaternion;
import com.jme.scene.TriMesh;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ByteBuffer;
import com.jme.math.Vector3f;
import com.jme.util.geom.BufferUtils;
import com.jme.bounding.BoundingBox;

/**
 *
 * @author mike
 */
public class GeometryBatch extends TriMesh {
   
    /** Do not use, for internal use only */
    public GeometryBatch() {
        FloatBuffer vertices = BufferUtils.createFloatBuffer(0);
        FloatBuffer normal = BufferUtils.createFloatBuffer(0);
        FloatBuffer color = null;
        FloatBuffer texture = BufferUtils.createFloatBuffer(0);
        IntBuffer indices = BufferUtils.createIntBuffer(0);
        reconstruct(vertices, normal, color, texture, indices);
       
        this.setModelBound(new BoundingBox());
        updateModelBound();
    }
   
    public GeometryBatch(String name) {
        super(name);
       
        FloatBuffer vertices = BufferUtils.createFloatBuffer(0);
        FloatBuffer normal = BufferUtils.createFloatBuffer(0);
        FloatBuffer color = null;
        FloatBuffer texture = BufferUtils.createFloatBuffer(0);
        IntBuffer indices = BufferUtils.createIntBuffer(0);
       
        reconstruct(vertices, normal, color, texture, indices);
       
        this.setModelBound(new BoundingBox());
        updateModelBound();
    }
   
    public static GeometryBatch create(String name, TriMesh mesh) {
        return new GeometryBatch(name, mesh);
    }
   
    /** Creates a new instance of GeometryBatch */
    public GeometryBatch(String name, TriMesh mesh) {
        super(name);
       
        //System.out.println("Adding mesh " + mesh.toString());
       
        FloatBuffer vertices = BufferUtils.clone(mesh.getVertexBuffer(0));
        FloatBuffer normal = BufferUtils.clone(mesh.getNormalBuffer(0));
        FloatBuffer color = BufferUtils.clone(mesh.getColorBuffer(0));
        FloatBuffer texture = BufferUtils.clone(mesh.getTextureBuffer(0,0));
        IntBuffer indices = BufferUtils.clone(mesh.getIndexBuffer(0));
       
        reconstruct(vertices, normal, color, texture, indices);
       
        this.setModelBound(new BoundingBox());
        updateModelBound();
    }
   
    public static void dumpFloatBuffer(FloatBuffer buff, String title) {
        System.out.println("********** Start FloatBuffer " + title);
        int cap = buff.capacity();
        buff.rewind();
       
        System.out.println();
       
        for (int ii=0; ii<cap; ii++) {
            System.out.println(buff.get());
        }
        System.out.println();
        System.out.println("********** End FloatBuffer **********");
       
        buff.rewind();
    }
   
    public static void dumpIntBuffer(IntBuffer buff, String title) {
        System.out.println("********** Start IntBuffer " + title);
        int cap = buff.capacity();
        buff.rewind();
       
        System.out.println();
       
        for (int ii=0; ii<cap; ii++) {
            System.out.println(Integer.toString(ii) + " " + Integer.toString(buff.get()));
        }
        System.out.println();
        System.out.println("********** End IntBuffer **********");
       
        buff.rewind();
    }
 
    public void addMeshGeometry(TriMesh mesh) {
        addGeometry(mesh, Transformation.getRotationTranslation(mesh.getLocalRotation(), mesh.getLocalTranslation()));
    }
   
    public void addMeshGeometry(TriMesh mesh, Vector3f translation) {
        addGeometry(mesh, Transformation.getRotationTranslation(mesh.getLocalRotation(),
                                                                mesh.getLocalTranslation().add(translation)));
    }  
   
    public void addMeshGeometry(TriMesh mesh, Transformation trans) {
        addGeometry(mesh, Transformation.getTransformationChain(
                Transformation.getRotationTranslation(mesh.getLocalRotation(), mesh.getLocalTranslation()), trans));
    }  
   
    public void addGeometry(TriMesh mesh, Vector3f translation) {
        addGeometry(mesh, Transformation.getTranslation(translation));
    }
   
    public void addGeometry(TriMesh mesh, Transformation trans) {
        // Append the indices.
        int total = this.getIndexBuffer(0).capacity() + mesh.getIndexBuffer(0).capacity();
        IntBuffer indices = BufferUtils.createIntBuffer(total);
        IntBuffer buff = this.getIndexBuffer(0);
        buff.rewind();
        indices.put(buff);
       
        int extra = this.getVertexBuffer(0).capacity() / 3;
        buff = mesh.getIndexBuffer(0);
        buff.rewind();
        for (int ii=0; ii<buff.capacity(); ii++) {
            indices.put(buff.get() + extra);
        }
       
        // Append the normals.
        total = this.getNormalBuffer(0).capacity() + mesh.getNormalBuffer(0).capacity();
        FloatBuffer normal = BufferUtils.createFloatBuffer(total);
        FloatBuffer buff2 = this.getNormalBuffer(0);
        buff2.rewind();
        normal.put(buff2);
       
        buff2 = mesh.getNormalBuffer(0);
        buff2.rewind();
        normal.put(buff2);
       
        // Append the texture coords.
        total = this.getTextureBuffer(0,0).capacity() +
                mesh.getTextureBuffer(0,0).capacity();
        FloatBuffer texture = BufferUtils.createFloatBuffer(total);
        buff2 = this.getTextureBuffer(0,0);
        buff2.rewind();
        texture.put(buff2);
       
        buff2 = mesh.getTextureBuffer(0,0);
        buff2.rewind();
        texture.put(buff2);
       
        // Take a temp copy of the vertices and rotate / translate them.
        FloatBuffer temp = BufferUtils.clone(mesh.getVertexBuffer(0));
        int count = temp.capacity() / 3;
        Vector3f tempVec = new Vector3f();
        for (int ii=0; ii < count; ii++) {
            BufferUtils.populateFromBuffer(tempVec, temp, ii);
            trans.transform(tempVec);
            BufferUtils.setInBuffer(tempVec, temp, ii);
        }
        temp.rewind();
       
        // Append the vertices.
        total = this.getVertexBuffer(0).capacity() + mesh.getVertexBuffer(0).capacity();
        FloatBuffer vertices = BufferUtils.createFloatBuffer(total);
        buff2 = this.getVertexBuffer(0);
        buff2.rewind();
       
        vertices.put(buff2);
        vertices.put(temp);
       
        // Append the colour.
        FloatBuffer color = null;
        if (this.getColorBuffer(0) != null && mesh.getColorBuffer(0) == null) {
            color = BufferUtils.clone(this.getColorBuffer(0));
        } else if (mesh.getColorBuffer(0) != null && this.getColorBuffer(0) == null) {
            color = BufferUtils.clone(mesh.getColorBuffer(0));
        } else if (mesh.getColorBuffer(0) != null && this.getColorBuffer(0) != null) {
            total = this.getColorBuffer(0).capacity() + mesh.getColorBuffer(0).capacity();
            color = BufferUtils.createColorBuffer(total);
            buff2 = this.getColorBuffer(0);
            buff2.rewind();
            color.put(buff2);
           
            buff2 = mesh.getColorBuffer(0);
            buff2.rewind();
            color.put(buff2);
        }
       
        reconstruct(vertices, normal, color, texture, indices);
       
        updateModelBound();
    }
   
    public String toString() {
        return super.toString();
    }
}



Use the static methods of this class or implement a subclass:


import com.jme.math.Quaternion;
import com.jme.math.Vector3f;

/**
 *
 * @author DGronau
 */
public abstract class Transformation {

    abstract public void transform(Vector3f vector);
   
    public static Transformation getIdentity() {
        return new Transformation() {
            public void transform(Vector3f vector) {}
        };
    }
   
    public static Transformation getTranslation(final Vector3f translation) {
        return new Transformation() {
            public void transform(Vector3f vector) {
                vector.addLocal(translation);
            }
        };
    }
   
    public static Transformation getRotation(final Quaternion rotation) {
        return new Transformation() {
            public void transform(Vector3f vector) {
                rotation.multLocal(vector);
            }
        };
    }

    public static Transformation getRotationTranslation(final Quaternion rotation, final Vector3f translation) {
        return new Transformation() {
            public void transform(Vector3f vector) {
                rotation.multLocal(vector);
                vector.addLocal(translation);
            }
        };       
    }

    public static Transformation getTranslationRotation(final Vector3f translation, final Quaternion rotation) {
        return new Transformation() {
            public void transform(Vector3f vector) {
                vector.addLocal(translation);
                rotation.multLocal(vector);
            }
        };       
    }

    public static Transformation getScale(final Vector3f scale) {
        return new Transformation() {
            public void transform(Vector3f vector) {
                vector.multLocal(scale);
            }
        };       
    }
   
    public static Transformation getTransformationChain(final Transformation... transformations) {
         return new Transformation() {
            public void transform(Vector3f vector) {
                for (Transformation transformation : transformations) {
                    transformation.transform(vector);
                }
            }
        };       
    }
}

OK, a little test for Transformator:







public class GeometryBatchTest extends SimpleGame {

Looks sweet!  A little texturing there and you've almost have a Microsoft Windows logo  :wink:

chirstius said:

Looks sweet!  A little texturing there and you've almost have a Microsoft Windows logo  ;)


Boy you know how to let the air out of a cool screenshot.  :P

…but he is right :slight_smile:



So here is another one:







made with



Awesome.  Great work Landei.

Thanks!



I think the Transformation class could be useful for other things as well (e.g. to distort models or terrains).



Playing around with this is like Xmas… Oh look, a star! What could it mean? :smiley:





Can you animate those transformations in realtime?

Looks exactly like the GeometryInstancing demo in jME (TestGeometryInstancing), is that the idea?  :smiley:

@ chirstius: No, it's static, and I'm not sure that it could be animated (at least not in case of GeometryBatches)



@ Snylt: That looks quite similar. I haven't tried this out yet. So far GeometryBatch works fine for me (and fast), so I just tried to make it more flexible. Maybe both approaches can be "merged" (if it makes sense)

Landei said:

@ chirstius: No, it's static, and I'm not sure that it could be animated (at least not in case of GeometryBatches)


So, if you unlock() the batch, and edit the vertex data will it not show up?  Like, once a batch is displayed, that's it?

What would be interesting is some way to "address" into the batch for a given piece of geometry, modify it, and have the resulting batch displayed.  This seems possible to me but maybe I am missing something.  Is there a way to keep an ID for the geometry added and then index into the batch based on that?

Like store an incrementing ID (or allow one to be passed in) for each add, and tag it internally with the starting (and possibly ending?) index for all of the buffers for that geometry.  Then have a modifyGeometry(int ID, Transformation trans) (or whatever the ID may be doesn't need to be an int) that indexes into the appropriate buffers based on the ID, applies the transformation to those values and then does a reconstruct()?  It's probably not quite that simple and I'm pulling this out of thin air here but doesn't something like that seem possible?

Would this also allow modification of the texture coordinates to individually map textures (or sections of one large texture?) to pieces of the batch?  That was kinda the route I wanted to go down when I was thinking about how to add that capability.  I have a feeling I have no f**king clue what I'm talking about here though.  Feel free to laugh.

Personally I can recommend using, extending and further improving on the new classes under the geometryinstancing package(contributed by snylt) because:

  • it can do what you ask for and more
  • it's clean and extensible(a nice structure)
  • it's allready in jME and it would be great to get your improvements on it instead of just a duplicate



    so I think you should find out in what ways it lacks compared to the GeometryBatch mentioned and try to fill those holes :slight_smile:
For my use, I'm creating a gameboard (hex tiles) and I'd like to be able to alter the texture on individual hexes (selected, highlighted, pathing, etc.) and possibly transform them (raise them slightly above the board, or sink them below) this wouldn't be an "all the time" thing more like an on-click or on on-key event so performance doesn't need to be blistering (updates wouldn't happen every frame).  When you say this should be easy do you mean in the current implementation or one like I was suggesting?  If you can offer any guidance I'd be very interested in trying to implement it into GeometryBatch.

This is already implemented in the GeometryInstance classes. I use them to create a forest scene. The forest is updated when the camera has moved a distance. More about the grass / trees in this thread: http://www.jmonkeyengine.com/jmeforum/index.php?topic=4623.0

As an example of what you want to do, check out the TestGeometryInstancing demo (in the jME source). If you have any questions about it, I'll be glad to help.

@mrcoder - Fair enough.  I just didn't have experience with those classes and had been working with GeometryBatch for a while now.



@snylt - I will certainly give this a look.  From what you say you may have already solved most of the major issues I was running up against with GeometryBatch as it is/was.  So long as the performance is in line I have no major issues switching over.



Thanks all.