Hi. I’ve converted the Geometry Instancing classes from JME2 to JME3. They’re suitable for creating batches of low poly geometry like trees or grass to keep the object count down.
package world.geometryinstancing.instance;
[java]/**
- <code>GeometryBatchCreator</code> is a container class for
- <code>GeometryInstances</code>.
*
-
@author Patrik Lindegrén
*/
public class GeometryBatchCreator {
protected ArrayList<GeometryInstance> instances;
private int nVerts;
private int nIndices;
public GeometryBatchCreator() {
instances = new ArrayList<GeometryInstance>(1);
nVerts = 0;
nIndices = 0;
}
public void clearInstances() {
instances.clear();
nVerts = 0;
nIndices = 0;
}
public void addInstance(GeometryInstance geometryInstance) {
if (geometryInstance == null) {
return;
}
instances.add(geometryInstance);
nIndices += geometryInstance.getNumIndices();
nVerts += geometryInstance.getNumVerts();
}
public void removeInstance(GeometryInstance geometryInstance) {
if (instances.remove(geometryInstance)) {
nIndices -= geometryInstance.getNumIndices();
nVerts -= geometryInstance.getNumVerts();
}
}
public int getNumVertices() {
return nVerts;
}
public int getNumIndices() {
return nIndices;
}
public ArrayList<GeometryInstance> getInstances() {
return instances;
}
public void commit(Mesh batch) {
for (GeometryInstance instance : instances) {
instance.commit(batch);
}
}
}[/java]
package world.geometryinstancing.instance;
[java]/**
- <code>GeometryInstance</code> uses a <code>GeometryInstanceAttributes</code>
- to define an instance of object in world space.
*
-
@author Patrik Lindegrén
*/
public abstract class GeometryInstance<T extends GeometryInstanceAttributes> {
protected T attributes;
public abstract void commit(Mesh batch);
public abstract int getNumIndices();
public abstract int getNumVerts();
public GeometryInstance(T attributes) {
this.attributes = attributes;
}
public T getAttributes() {
return attributes;
}
}[/java]
package world.geometryinstancing.instance;
[java]/**
- <code>GeometryInstanceAttributes</code> specifies the attributes for a
- <code>GeometryInstance</code>.
*
-
@author Patrik Lindegrén
*/
public class GeometryInstanceAttributes {
protected Vector3f translation; // Translation
protected Vector3f scale; // Scale
protected Quaternion rotation; // Rotation
protected Matrix4f mtNormal; // Normal matrix (scale, rotation)
protected Matrix4f mtWorld; // Local to world matrix (scale, rotation, translation)
public GeometryInstanceAttributes(Vector3f translation, Vector3f scale,
Quaternion rotation) {
this.scale = scale;
this.rotation = rotation;
this.translation = translation;
mtWorld = new Matrix4f();
mtNormal = new Matrix4f();
buildMatrices();
}
/**
- Vector used to store and calculate rotation in degrees Not needed when
- radian rotation is implemented in Matrix4f
/
private Vector3f rotationDegrees = new Vector3f();
/* <code>buildMatrices</code> updates the world and rotation matrix */
public void buildMatrices() {
// Scale (temporarily use mtWorld as storage)
mtWorld.loadIdentity();
mtWorld.m00 = scale.x;
mtWorld.m11 = scale.y;
mtWorld.m22 = scale.z;
// Build rotation matrix (temporarily use mtNormal as storage)
// rotationDegrees.set(rotation).multLocal(FastMath.RAD_TO_DEG);
mtNormal.loadIdentity();
mtNormal.setRotationQuaternion(rotation);
// mtNormal.angleRotation(rotationDegrees);
//mtNormal.radianRotation(rotation); // Add a radian rotation function to Matrix4f (requested feature)
// Build normal matrix (scale * rotation)
mtNormal.multLocal(mtWorld);
// Build world matrix (scale * rotation + translation)
mtWorld.set(mtNormal);
mtWorld.setTranslation(translation);
}
public Vector3f getScale() {
return scale;
}
/**
- After using the <code>setScale</code> function, user needs to call the
- <code>buildMatrices</code> function
*
-
@param scale
*/
public void setScale(Vector3f scale) {
this.scale = scale;
}
public Vector3f getTranslation() {
return translation;
}
/**
- After using the <code>setTranslation</code> function, user needs to call
- the <code>buildMatrices</code> function
*
-
@param translation
*/
public void setTranslation(Vector3f translation) {
this.translation = translation;
}
public Quaternion getRotation() {
return rotation;
}
/**
- After using the <code>setRotation</code> function, user needs to call the
- <code>buildMatrices</code> function
*
-
@param rotation
*/
public void setRotation(Quaternion rotation) {
this.rotation = rotation;
}
public Matrix4f getWorldMatrix() {
return mtWorld;
}
public Matrix4f getNormalMatrix() {
return mtNormal;
}
}[/java]
package world.geometryinstancing;
[java]/**
- <code>GeometryBatchInstance</code> uses a <code>GeometryBatchInstanceAttributes</code>
- to define an instance of object in world space. Uses TriMesh as source
- data for the instance, instead of GeomBatch which does not have an index
- buffer.
*
-
@author Patrik Lindegrén
/
public class GeometryBatchInstance
extends GeometryInstance<GeometryBatchInstanceAttributes> {
public Mesh instanceMesh;
public GeometryBatchInstance(Mesh sourceBatch,
GeometryBatchInstanceAttributes attributes) {
super(attributes);
this.instanceMesh = sourceBatch;
}
/* Vector used to store and calculate world transformations */
Vector3f worldVector = new Vector3f();
/**
- Uses the instanceAttributes to transform the instanceBatch into world
- coordinates. The transformed instance mesh is added to the mesh.
*
-
@param mesh
*/
public void commit(Mesh mesh) {
if (mesh == null || instanceMesh == null || getNumVerts() <= 0) {
return;
}
int nVerts = 0;
// Texture buffers
//for (int i = 0; i < instanceMesh.getNumLodLevels(); i++) {
FloatBuffer texBufSrc = instanceMesh.getFloatBuffer(Type.TexCoord);
FloatBuffer texBufDst = mesh.getFloatBuffer(Type.TexCoord);
if (texBufSrc != null && texBufDst != null) {
texBufSrc.rewind();
texBufDst.put(texBufSrc);
}
//}
// Vertex buffer
FloatBuffer vertBufSrc = instanceMesh.getFloatBuffer(Type.Position);
FloatBuffer vertBufDst = mesh.getFloatBuffer(Type.Position);
if (vertBufSrc != null && vertBufDst != null) {
vertBufSrc.rewind();
nVerts = vertBufDst.position() / 3;
for (int i = 0; i < instanceMesh.getVertexCount(); i++) {
worldVector.set(vertBufSrc.get(), vertBufSrc.get(),
vertBufSrc.get());
attributes.getWorldMatrix().mult(worldVector, worldVector);
vertBufDst.put(worldVector.x);
vertBufDst.put(worldVector.y);
vertBufDst.put(worldVector.z);
}
}
// Color buffer
FloatBuffer colorBufSrc = instanceMesh.getFloatBuffer(Type.Color);
FloatBuffer colorBufDst = mesh.getFloatBuffer(Type.Color);
if (colorBufSrc != null && colorBufDst != null) {
colorBufSrc.rewind();
for (int i = 0; i < instanceMesh.getVertexCount(); i++) {
colorBufDst.put(colorBufSrc.get() * attributes.getColor().r);
colorBufDst.put(colorBufSrc.get() * attributes.getColor().g);
colorBufDst.put(colorBufSrc.get() * attributes.getColor().b);
colorBufDst.put(colorBufSrc.get() * attributes.getColor().a);
}
} else if (colorBufDst != null) {
for (int i = 0; i < instanceMesh.getVertexCount(); i++) {
colorBufDst.put(attributes.getColor().r);
colorBufDst.put(attributes.getColor().g);
colorBufDst.put(attributes.getColor().b);
colorBufDst.put(attributes.getColor().a);
}
}
// Normal buffer
FloatBuffer normalBufSrc = instanceMesh.getFloatBuffer(Type.Normal);
FloatBuffer normalBufDst = mesh.getFloatBuffer(Type.Normal);
if (normalBufSrc != null && normalBufDst != null) {
normalBufSrc.rewind();
for (int i = 0; i < instanceMesh.getVertexCount(); i++) {
worldVector.set(normalBufSrc.get(), normalBufSrc.get(),
normalBufSrc.get());
attributes.getNormalMatrix().mult(worldVector, worldVector);
worldVector.normalizeLocal();
normalBufDst.put(worldVector.x);
normalBufDst.put(worldVector.y);
normalBufDst.put(worldVector.z);
}
}
// Index buffer
IndexBuffer indexBufSrc = instanceMesh.getIndexBuffer();
IndexBuffer indexBufDst = mesh.getIndexBuffer();
if (indexBufSrc != null && indexBufDst != null) {
indexBufSrc.getBuffer().rewind();
for (int i = 0; i < instanceMesh.getIndexBuffer().getBuffer().limit(); i++) {
indexBufDst.put(indexBufDst.getBuffer().position(), nVerts + indexBufSrc.get(i));
indexBufDst.getBuffer().position(indexBufDst.getBuffer().position()+1);
}
}
instanceMesh.setBound(new BoundingBox() );
}
public int getNumIndices() {
if (instanceMesh == null) {
return 0;
}
return instanceMesh.getIndexBuffer().size();
}
public int getNumVerts() {
if (instanceMesh == null) {
return 0;
}
return instanceMesh.getVertexCount();
}
}[/java]
package world.geometryinstancing;
[java]/**
- <code>GeometryBatchInstanceAttributes</code> specifies the attributes for a
- <code>GeometryBatchInstance</code>
*
-
@author Patrik Lindegrén
/
public class GeometryBatchInstanceAttributes
extends GeometryInstanceAttributes {
protected ColorRGBA color;
public GeometryBatchInstanceAttributes(Vector3f translation, Vector3f scale,
Quaternion rotation, ColorRGBA color) {
super(translation, scale, rotation);
this.color = color;
}
/* <code>buildMatrices</code> updates the world and rotation matrix */
public ColorRGBA getColor() {
return color;
}
public void setColor(ColorRGBA color) {
this.color = color;
}
}[/java]
Lastly, you’ll need to put this static method somewhere:
[java]public static void createBatchBuffers(Mesh output, GeometryBatchCreator batch)
{
output.setBuffer(VertexBuffer.Type.Index, 1, BufferUtils.createIntBuffer( batch.getNumIndices() ) );
output.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer( batch.getNumVertices() * 3 ) );
output.setBuffer(VertexBuffer.Type.Normal, 3, BufferUtils.createFloatBuffer( batch.getNumVertices() * 3 ) );
output.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer( batch.getNumVertices() * 2 ) );
output.setBuffer(VertexBuffer.Type.Color, 4, BufferUtils.createFloatBuffer( batch.getNumVertices() * 4 ) );
output.updateCounts();
// Commit the instances to the mesh batch
batch.commit(output);
}[/java]
Enjoy.
Edit: cleaned up imports, changed topic name to ‘Geometry Batching’