Mesh From Template

First… can’t begin to explain how excited I am about this…
Second… let me explain what THIS is!

I’ve been kicking this idea around for a while and it is the basis for how I wanted to implement a vegetation system… however it use goes far beyond that. One of the major requirements for putting together the vegetation system was the ability to use anyone’s models for trees, bushes, grass, plants, rocks, etc. But…loading individual models and placing them is right out. Cloning these models and running GeometryBatchFactory… or using a BatchNode was too memory intensive (only reason being is, they both have to assume that each model in the node being batched has a different set of buffers… so… extract, append, extract, append, etc, etc).

The solution I’ve been playing around with is:

  1. Load a single model
  2. Extract the buffers
  3. Pass them to a custom mesh with a list containing positions, rotations and scaling information
  4. Create final buffers based on the template * mesh info list size
  5. Populated the final buffers using the templates + mesh info
  6. return the custom mesh containing multiple instances of the template model as a single mesh.

Using this as a basis for a vegetation system, the user has complete control over how the vegetation in their world looks, needs to know NOTHING about generating custom meshes, the system takes care of the rest.

Basically, you would:

  1. Load a model
  2. Use the MeshUtils class to extract the buffers as a template.
  3. Either generate a MeshInfo list (or alternatively read it out of some form of atlas image) - consisting of: position, rotation, scale
  4. Create a new instance of the MeshFromTemplate class and pass in the template buffers and mesh info.
  5. Add this is a geom > node, apply a material and done.

Once integrated into the Paging System, literally all you have to do is select a template model(s) and material(s) for each delegator (which can handle multiple templates if so desired) and everything else happens for you.

I’m posting the code for the MeshFromTemplate & MeshInfo classes so people can look through them… these two classes + the utility class will more than likely be part of the paging system once released, as their uses go waaaaaay beyond a vegetation system.

But… first, a vid: It consists of 2 Meshes using shared MeshInfo data. 1) The tree trunk . 2) The leaves. - sharing the MeshInfo data ensures that all objects are placed, rotated and scaled the same.

[video]http://youtu.be/Lmk4RwFDDso[/video]

MeshFromTemplate.java
[java]import com.jme3.asset.AssetManager;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.List;

/**
*

  • @author t0neg0d
    */
    public class MeshFromTemplate extends Mesh {
    AssetManager assetManager;

    FloatBuffer templateVerts;
    FloatBuffer templateCoords;
    IndexBuffer templateIndexes;
    FloatBuffer templateNormals;

    FloatBuffer finVerts;
    FloatBuffer finCoords;
    IntBuffer finIndexes;
    FloatBuffer finNormals;

    Quaternion qR = new Quaternion();

    public MeshFromTemplate(AssetManager assetManager, FloatBuffer templateVerts, FloatBuffer templateCoords, IndexBuffer templateIndexes, FloatBuffer templateNormals) {
    this.assetManager = assetManager;
    this.templateVerts = templateVerts;
    this.templateCoords = templateCoords;
    this.templateIndexes = templateIndexes;
    this.templateNormals = templateNormals;
    }

    public void build(List<MeshInfo> positions) {
    // Create final buffers
    this.finVerts = BufferUtils.createFloatBuffer(templateVerts.capacity()*positions.size());
    this.finCoords = BufferUtils.createFloatBuffer(templateCoords.capacity()*positions.size());
    this.finIndexes = BufferUtils.createIntBuffer(templateIndexes.size()*positions.size());
    this.finNormals = BufferUtils.createFloatBuffer(templateNormals.capacity()*positions.size());

     // Create new vector3f for altering position/rotation of existing buffer data
     Vector3f tempVec = new Vector3f();
     
     int index = 0, index2 = 0, index3 = 0, index4 = 0;
     int indexOffset = 0;
     
     for (int i = 0; i &lt; positions.size(); i++) {
     	templateVerts.rewind();
     	for (int v = 0; v &lt; templateVerts.capacity(); v += 3) {
     		tempVec.set(templateVerts.get(), templateVerts.get(), templateVerts.get());
     		positions.get(i).getRotation().mult(tempVec, tempVec);
     		tempVec.addLocal(positions.get(i).getPosition());
     		tempVec.multLocal(positions.get(i).getScale());
     		finVerts.put(index, tempVec.getX());
     		index++;
     		finVerts.put(index, tempVec.getY());
     		index++;
     		finVerts.put(index, tempVec.getZ());
     		index++;
     	}
     	
     	templateCoords.rewind();
     	for (int v = 0; v &lt; templateCoords.capacity(); v++) {
     		finCoords.put(index2, templateCoords.get());
     		index2++;
     	}
     	
     	for (int v = 0; v &lt; templateIndexes.size(); v++) {
     		finIndexes.put(index3, templateIndexes.get(v)+indexOffset);
     		index3++;
     	}
     	indexOffset += templateVerts.capacity()/3;
     	
     	templateNormals.rewind();
     	for (int v = 0; v &lt; templateNormals.capacity(); v++) {
     		finNormals.put(index4, templateNormals.get());
     		index4++;
     	}
     }
     
     // Help GC
     tempVec = null;
     templateVerts = null;
     templateCoords = null;
     templateIndexes = null;
     templateNormals = null;
     
     // Clear &amp; ssign buffers
     this.clearBuffer(Type.Position);
     this.setBuffer(Type.Position,	3, finVerts);
     this.clearBuffer(Type.TexCoord);
     this.setBuffer(Type.TexCoord,	2, finCoords);
     this.clearBuffer(Type.Index);
     this.setBuffer(Type.Index,		3, finIndexes);
     this.clearBuffer(Type.Normal);
     this.setBuffer(Type.Normal,		3, finNormals);
     this.updateBound();
    

    }
    }[/java]

MeshInfo.java
[java]import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;

/**
*

  • @author t0neg0d
    */
    public class MeshInfo {
    Vector3f position;
    Quaternion rotation;
    float scale;
    public MeshInfo(Vector3f position, Quaternion rotation, float scale) {
    this.position = position;
    this.rotation = rotation;
    this.scale = scale;
    }
    public Vector3f getPosition() {
    return this.position;
    }
    public Quaternion getRotation() {
    return this.rotation;
    }
    public float getScale() {
    return this.scale;
    }
    }[/java]

Main.java (usage test)
[java]

import com.jme3.app.SimpleApplication;
import com.jme3.app.state.VideoRecorderAppState;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.texture.Texture;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;

/**

  • test

  • @author t0neg0d
    */
    public class Main extends SimpleApplication implements ActionListener {
    VideoRecorderAppState vrAppState;

    int numMeshes = 1;
    int numBatchedModelsPerMesh = 24;

    String pathModel = "Models/Pine.j3o";
    String pathTexture = "Textures/Bark.jpg";

    String pathModel2 = "Models/Leaves.j3o";
    String pathTexture2 = "Textures/Leaves.png";

    FloatBuffer verts, coords, normals;
    IndexBuffer indexes;
    FloatBuffer verts2, coords2, normals2;
    IndexBuffer indexes2;
    Mesh template, template2;
    Material mat, mat2;
    Texture tex, tex2;

    public static void main(String[] args) {
    Main app = new Main();
    app.start();
    }

    @Override
    public void simpleInitApp() {
    vrAppState = new VideoRecorderAppState();
    vrAppState.setQuality(0.35f);

     setupKeys();
     
     flyCam.setMoveSpeed(100f);
     
     AmbientLight al = new AmbientLight();
     al.setColor(new ColorRGBA(1f, 1f, 1f, 1f));
     rootNode.addLight(al);
     
     DirectionalLight sun = new DirectionalLight();
     sun.setDirection(new Vector3f(0f,-.25f,0f).normalizeLocal());
     sun.setColor(new ColorRGBA(1f,1f,1f,1f));
     rootNode.addLight(sun);
     
     tex = assetManager.loadTexture(pathTexture);
     tex.setMinFilter(Texture.MinFilter.BilinearNearestMipMap);
     tex.setMagFilter(Texture.MagFilter.Bilinear);
     tex.setWrap(Texture.WrapMode.Repeat);
     
     mat = new Material(assetManager, &quot;Common/MatDefs/Light/Lighting.j3md&quot;);
     mat.setBoolean(&quot;UseMaterialColors&quot;, true);
     mat.setBoolean(&quot;HighQuality&quot;, true);
     mat.setFloat(&quot;Shininess&quot;, 0f);
     mat.setColor(&quot;Ambient&quot;, ColorRGBA.White);
     mat.setColor(&quot;Diffuse&quot;, ColorRGBA.White);
     mat.setTexture(&quot;DiffuseMap&quot;, tex);
     mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Back);
     
     tex2 = assetManager.loadTexture(pathTexture2);
     tex2.setMinFilter(Texture.MinFilter.BilinearNearestMipMap);
     tex2.setMagFilter(Texture.MagFilter.Bilinear);
     tex2.setWrap(Texture.WrapMode.Repeat);
     
     mat2 = new Material(assetManager, &quot;Common/MatDefs/Light/Lighting.j3md&quot;);
     mat2.setBoolean(&quot;UseMaterialColors&quot;, true);
     mat2.setBoolean(&quot;HighQuality&quot;, true);
     mat2.setFloat(&quot;Shininess&quot;, 0f);
     mat2.setColor(&quot;Ambient&quot;, ColorRGBA.White);
     mat2.setColor(&quot;Diffuse&quot;, ColorRGBA.White);
     mat2.setTexture(&quot;DiffuseMap&quot;, tex2);
     mat2.setFloat(&quot;AlphaDiscardThreshold&quot;, 0.5f);
     mat2.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Back);
     mat2.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
     
     // Load template model
     template = ((Geometry)((Node)assetManager.loadModel(pathModel)).getChild(0)).getMesh();
     template2 = ((Geometry)((Node)assetManager.loadModel(pathModel2)).getChild(0)).getMesh();
     
     // Extract template buffers
     verts = template.getFloatBuffer(VertexBuffer.Type.Position);
     coords = template.getFloatBuffer(VertexBuffer.Type.TexCoord);
     indexes = template.getIndexBuffer();
     normals = template.getFloatBuffer(VertexBuffer.Type.Normal);
     verts2 = template2.getFloatBuffer(VertexBuffer.Type.Position);
     coords2 = template2.getFloatBuffer(VertexBuffer.Type.TexCoord);
     indexes2 = template2.getIndexBuffer();
     normals2 = template2.getFloatBuffer(VertexBuffer.Type.Normal);
     
     // Create some meshes from the template
     for (int i = 0; i &lt; numMeshes; i++) {
     	// Create a new custom mesh
     	MeshFromTemplate mesh = new MeshFromTemplate(assetManager, verts, coords, indexes, normals);
     	MeshFromTemplate mesh2 = new MeshFromTemplate(assetManager, verts2, coords2, indexes2, normals2);
     	
     	// Create a single mesh containing numBatchedModelsPerMesh instances of the template mesh
     	List&lt;MeshInfo&gt; positions = createRandomMeshInfo();
     	
     	mesh.build(positions);
     	mesh2.build(positions);
     	
     	Vector3f loc = new Vector3f((float)Math.random()*250f,0f,(float)Math.random()*250f);
     	
     	// create geometry &amp; node, then apply material
     	Geometry geom = new Geometry(&quot;Geom&quot; + i);
     	geom.setMesh(mesh);
     	Node n = new Node(&quot;Node&quot; + i);
     	n.attachChild(geom);
     	n.setMaterial(mat);
     	n.setLocalTranslation(loc);
     	// Add the new node to the scene
     	rootNode.attachChild(n);
     	
     	// create geometry &amp; node, then apply material
     	Geometry geom2 = new Geometry(&quot;Geom2&quot; + i);
     	geom2.setMesh(mesh2);
     	Node n2 = new Node(&quot;Node2&quot; + i);
     	n2.attachChild(geom2);
     	n2.setMaterial(mat2);
     	n2.setLocalTranslation(loc);
     	// Add the new node to the scene
     	rootNode.attachChild(n2);
     }
    

    }

    private List<MeshInfo> createRandomMeshInfo() {
    List<MeshInfo> positions = new ArrayList();
    for (int x = 0; x < numBatchedModelsPerMesh; x++) {
    positions.add(
    new MeshInfo(
    new Vector3f( (float)Math.random()*50f, 0f, (float)Math.random()50f ),
    new Quaternion().fromAngleAxis((float)Math.random()360f2f
    FastMath.DEG_TO_RAD, Vector3f.UNIT_Y),
    0.5f+(float)Math.random()
    )
    );
    }
    return positions;
    }

    @Override
    public void simpleUpdate(float tpf) {
    //TODO: add update code
    }

    @Override
    public void simpleRender(RenderManager rm) {
    //TODO: add render code
    }

    private void setupKeys() {
    inputManager.addMapping("F9", new KeyTrigger(KeyInput.KEY_F9));
    inputManager.addListener(this, "F9");
    }

    public void onAction(String binding, boolean value, float tpf) {
    if (binding.equals("F9")) {
    if (!value) {
    if (stateManager.hasState(vrAppState)) {
    System.out.println("Stopping video recorder");
    stateManager.detach(vrAppState);
    } else {
    System.out.println("Starting video recorder");
    stateManager.attach(vrAppState);
    }
    }
    }
    }

}[/java]

4 Likes

Ugh… just saw that the definition of List class isn’t showing up in the code block due to > and < …

I believe all Lists are List<MeshInfo>

EDIT: Fix this in the posted code. Not exactly sure how that will translate when cut/pasted though

So that’s batching, but by duplicating the same set of data. I can see why it consume less memory indeed.

What makes me uncomfortable about your system is that it’s a workaround to the fact that we are lazy asses and that we didn’t put Instancing in…(actually, it’s already in but there is no API for the user)

What you are doing here could be covered by hardware instancing…but JME does not support it.
Instancing is having a single mesh drawn several time with one draw call. That’s an opengl feature.
The restrictions are that it has to be the same mesh with the same material. That’s the reason why it’s low on the priority scale because we said “hey…what are the odds for that?”
…but I guess you found a valid use case.
You’d have a lot less memory consumption with it because you’d need only your template data to draw a forest.

To be clear, I don’t mean to be deceptive about what you did, it’s great, and you should use it, because right now you have no other way to do it. But make sure it’s not too much embed into your system, and that you can easily replace it with instancing.
I’m gonna bump Instancing priority up. :wink:

EDIT : I realized “deceptive” is badly worded, it just have a complete different meaning in french than in english. I mean that i don’t want to sound discouraging.

2 Likes
@nehon said:So that's batching, but by duplicating the same set of data. I can see why it consume less memory indeed.

What makes me uncomfortable about your system is that it’s a workaround to the fact that we are lazy asses and that we didn’t put Instancing in…(actually, it’s already in but there is no API for the user)

What you are doing here could be covered by hardware instancing…but JME does not support it.
Instancing is having a single mesh drawn several time with one draw call. That’s an opengl feature.
The restrictions are that it has to be the same mesh with the same material. That’s the reason why it’s low on the priority scale because we said “hey…what are the odds for that?”
…but I guess you found a valid use case.
You’d have a lot less memory consumption with it because you’d need only your template data to draw a forest.

To be clear, I don’t mean to be deceptive about what you did, it’s great, and you should use it, because right now you have no other way to do it. But make sure it’s not too much embed into your system, and that you can easily replace it with instancing.
I’m gonna bump Instancing priority up. :wink:

EDIT : I realized “deceptive” is badly worded, it just have a complete different meaning in french than in english. I mean that i don’t want to sound discouraging.

I would gladly ditch this in a heart beat for instancing! This is still a resource pig once used for paging tiles. It’s just a slightly faster resource pig >.<

They way I'm incorporating this is totally dependent on the user implementing it in custom delegators. It's just an alternative… and by the sounds of things… hopefully a very short lived one!

Also… I ran into a problem using .get() instead of .get(index) when you have multiple threads accessing the same template buffers >.< I meant to mention this as I realized it about 20 minutes after posting the code.

Anyways… just a heads up in case (for some odd reason) someone tried to use this in a multi-threaded application.

Though, @zarch is correct… if you are only accessing the buffers once at any given time, .get() was faster… noticeably with large meshes.

The for loop in MeshFromTemplate would change to this to be thread safe:

[java]
for (int i = 0; i < positions.size(); i++) {
for (int v = 0; v < templateVerts.capacity(); v += 3) {
tempVec.set(templateVerts.get(v), templateVerts.get(v+1), templateVerts.get(v+2));
positions.get(i).getRotation().mult(tempVec, tempVec);
tempVec.addLocal(positions.get(i).getPosition());
tempVec.multLocal(positions.get(i).getScale());
finVerts.put(index, tempVec.getX());
index++;
finVerts.put(index, tempVec.getY());
index++;
finVerts.put(index, tempVec.getZ());
index++;
}

        for (int v = 0; v &lt; templateCoords.capacity(); v++) {
            finCoords.put(index2, templateCoords.get(v));
            index2++;
        }

        for (int v = 0; v &lt; templateIndexes.size(); v++) {
            finIndexes.put(index3, templateIndexes.get(v)+indexOffset);
            index3++;
        }
        indexOffset += templateVerts.capacity()/3;

        for (int v = 0; v &lt; templateNormals.capacity(); v++) {
            finNormals.put(index4, templateNormals.get(v));
            index4++;
        }
    }

[/java]

Just by reading the title of this thread I realized that this would be a great use case for GPU-instancing =) I’m happy to hear that a core dude picked that up to! Keep up the good work, all of you!

@nehon said:
That's the reason why it's low on the priority scale because we said "hey....what are the odds for that?"

Yeah, I laughed when I heard that the first time. Considering like every paper on terrain rendering recommends it for the stuff on the terrain: rocks, trees, grass, bushes… :slight_smile: “What are the odds someone would want terrain in their game?” :slight_smile:

What abstracting the meshes in jme more could do in general… :o xD :stuck_out_tongue_winking_eye:

this is interesting.
i havnt actually tested this yet, but i have 2 questions:

  1. could your resulting MeshFromTemplate objects (or their nodes) be used with the CollisionShapeFactory to make a MeshCollisionShape?

  2. could this final mesh support animations?
    i would imagine 2 schools of thought…
    one way would be animation of the mesh as a whole by changing the location, rotation, scale of the instances each frame (updating your MeshInfo list and re-building the geom each frame)
    and the second way i imagine is having each instance within the mesh playing a different anim sequence from the original template’s skeletal anims?
    upon combining both ways, it almost seems like a kind of particle system.
    thats brings me to another question:
    does the current particle system use any kind of instancing, or does the ParticleEmitter create lots of separate Particle objects?

@Decoy said:this is interesting. i havnt actually tested this yet, but i have 2 questions:
  1. could your resulting MeshFromTemplate objects (or their nodes) be used with the CollisionShapeFactory to make a MeshCollisionShape?

  2. could this final mesh support animations?
    i would imagine 2 schools of thought…
    one way would be animation of the mesh as a whole by changing the location, rotation, scale of the instances each frame (updating your MeshInfo list and re-building the geom each frame)
    and the second way i imagine is having each instance within the mesh playing a different anim sequence from the original template’s skeletal anims?
    upon combining both ways, it almost seems like a kind of particle system.
    thats brings me to another question:
    does the current particle system use any kind of instancing, or does the ParticleEmitter create lots of separate Particle objects?

  1. There is no problem doing that with the resulting mesh.
  2. BatchNode is way better suited for individual animations… though, if you talking about vertex deformations on GPU, this would do just fine (i.e. having the leaves and branches sway in the wind-type of thing). For this type of animation, do a search on GPUAnimationFactory. I need to update these shaders to the latest Lighting shaders… but if you go through the thread, there are a ton of videos that show what it does with vertex & texture deforms on the GPU side of things)
  3. I’m guessing on this one, but I would imagine that the particles are a single mesh that stores indexes of each visible quad and modifies the single mesh… but like I said, this is a guess. I’ve never watched the object count when using a ParticleEmitter.
@Decoy said:
does the current particle system use any kind of instancing, or does the ParticleEmitter create lots of separate Particle objects?
Our ParticleEmitter has one mesh and all the deformations occur on the mesh level.
1 Like