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:
- Load a single model
- Extract the buffers
- Pass them to a custom mesh with a list containing positions, rotations and scaling information
- Create final buffers based on the template * mesh info list size
- Populated the final buffers using the templates + mesh info
- 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:
- Load a model
- Use the MeshUtils class to extract the buffers as a template.
- Either generate a MeshInfo list (or alternatively read it out of some form of atlas image) - consisting of: position, rotation, scale
- Create a new instance of the MeshFromTemplate class and pass in the template buffers and mesh info.
- 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 < positions.size(); i++) { templateVerts.rewind(); for (int v = 0; v < 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 < templateCoords.capacity(); v++) { finCoords.put(index2, templateCoords.get()); index2++; } for (int v = 0; v < templateIndexes.size(); v++) { finIndexes.put(index3, templateIndexes.get(v)+indexOffset); index3++; } indexOffset += templateVerts.capacity()/3; templateNormals.rewind(); for (int v = 0; v < templateNormals.capacity(); v++) { finNormals.put(index4, templateNormals.get()); index4++; } } // Help GC tempVec = null; templateVerts = null; templateCoords = null; templateIndexes = null; templateNormals = null; // Clear & 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, "Common/MatDefs/Light/Lighting.j3md"); mat.setBoolean("UseMaterialColors", true); mat.setBoolean("HighQuality", true); mat.setFloat("Shininess", 0f); mat.setColor("Ambient", ColorRGBA.White); mat.setColor("Diffuse", ColorRGBA.White); mat.setTexture("DiffuseMap", 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, "Common/MatDefs/Light/Lighting.j3md"); mat2.setBoolean("UseMaterialColors", true); mat2.setBoolean("HighQuality", true); mat2.setFloat("Shininess", 0f); mat2.setColor("Ambient", ColorRGBA.White); mat2.setColor("Diffuse", ColorRGBA.White); mat2.setTexture("DiffuseMap", tex2); mat2.setFloat("AlphaDiscardThreshold", 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 < 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<MeshInfo> positions = createRandomMeshInfo(); mesh.build(positions); mesh2.build(positions); Vector3f loc = new Vector3f((float)Math.random()*250f,0f,(float)Math.random()*250f); // create geometry & node, then apply material Geometry geom = new Geometry("Geom" + i); geom.setMesh(mesh); Node n = new Node("Node" + i); n.attachChild(geom); n.setMaterial(mat); n.setLocalTranslation(loc); // Add the new node to the scene rootNode.attachChild(n); // create geometry & node, then apply material Geometry geom2 = new Geometry("Geom2" + i); geom2.setMesh(mesh2); Node n2 = new Node("Node2" + 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()360f2fFastMath.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]