Hi,
i wrote a class to simplify the creation of custom meshes.
Maybe it can be added to JME3!!!
Here it is:
[java]
package com.jme3.scene;
import java.nio.;
import java.util.;
import java.util.Map.Entry;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;
/**
- This helper class simplifies the creation of custom meshes. It is built so you can add vertex by vertex.<br>
- It dynamically initializes the needed buffers and manages them automatically when adding and setting the first vertex.<br>
- By adding a second vertex and not specifying the values for all buffers the values from the previous vertex will be copied automatically.<br>
- When done adding vertexes and before you can use the mesh you need to call the method {@link #finish()}.
*
-
@author Finomosec
*/
public class CustomMesh extends Mesh {
private final int initialSize;
private final int increaseBy = 50;
private int index = 0;
/**
- Dynamically managed buffers
*/
private Map<Type, Buffer> buffers = new HashMap<Type, Buffer>();
/**
- Here we store the values set by the set methods for use in fillValues().
- That way we preserve the prior value and thus can reuse it when the individual value is not changed.
- Example: We want 9 vertexes in blue. Call {@link #setColor(ColorRGBA)} with blue. Call {@link #addVertex(Vector3f)} 9 times with the wanted vertexes.
*/
private Map<Type, Object> values = new HashMap<Type, Object>();
/**
-
@param initialSize
-
number of Vertexes expected (will increase automatically if needed, but with loss of performance)<br />
*/
public CustomMesh(int initialSize) {
this.initialSize = initialSize;
}
private Buffer getBufferFor(Type type) {
Buffer buffer = buffers.get(type);
TypeMeta typeMeta = TypeMeta.getTypeMeta(type);
if (buffer == null) {
buffer = typeMeta.createBuffer(initialSize);
buffers.put(type, buffer);
} else {
if (buffer.remaining() < typeMeta.getNumValues()) {
if (buffer instanceof ShortBuffer) {
buffer = BufferUtils.ensureLargeEnough((ShortBuffer) buffer, typeMeta.getNumValues() * increaseBy);
} else if (buffer instanceof FloatBuffer) {
buffer = BufferUtils.ensureLargeEnough((FloatBuffer) buffer, typeMeta.getNumValues() * increaseBy);
}
buffers.put(type, buffer);
}
}
return buffer;
}
public void addVertex() {
((ShortBuffer) getBufferFor(Type.Index)).put((short) index++);
for (Entry<Type, Object> valueEntry : values.entrySet()) {
Buffer buffer = getBufferFor(valueEntry.getKey());
if (buffer instanceof ShortBuffer) {
((ShortBuffer) buffer).put((short[]) valueEntry.getValue());
} else if (buffer instanceof FloatBuffer) {
((FloatBuffer) buffer).put((float[]) valueEntry.getValue());
} else if (buffer instanceof ByteBuffer) {
((ByteBuffer) buffer).put((byte[]) valueEntry.getValue());
}
}
}
public void addVertex(Vector3f position) {
setPosition(position);
addVertex();
}
public void addVertex(float x, float y, float z) {
setPosition(x, y, z);
addVertex();
}
public void addVertex(Vector3f position, ColorRGBA color) {
setPosition(position);
setColor(color);
addVertex();
}
public void addVertex(Vector3f position, ColorRGBA color, Vector3f normal) {
setPosition(position);
setColor(color);
setNormal(normal);
addVertex();
}
public void setPosition(Vector3f vector) {
setFloatValue(Type.Position, vector.x, vector.y, vector.z);
}
public void setPosition(float x, float y, float z) {
setFloatValue(Type.Position, x, y, z);
}
public void setColor(ColorRGBA color) {
setFloatValue(Type.Color, color.r, color.g, color.b, color.a);
}
public void setColor(float r, float g, float b, float a) {
setFloatValue(Type.Color, r, g, b, a);
}
public void setNormal(Vector3f vector) {
setFloatValue(Type.Normal, vector.x, vector.y, vector.z);
}
public void setNormal(float x, float y, float z) {
setFloatValue(Type.Normal, x, y, z);
}
public void setFloatValue(Type type, float… value) {
TypeMeta typeMeta = TypeMeta.getTypeMeta(type);
if (typeMeta.getBufferType() != FloatBuffer.class) throw new IllegalArgumentException(“This buffer does not take ‘float’ values!”);
if (typeMeta.getNumValues() != value.length) throw new IllegalArgumentException(“This buffer needs " + typeMeta.getNumValues() + " values!”);
values.put(type, value);
}
public void setShortValue(Type type, short… value) {
TypeMeta typeMeta = TypeMeta.getTypeMeta(type);
if (typeMeta.getBufferType() != ShortBuffer.class) throw new IllegalArgumentException(“This buffer does not take ‘short’ values!”);
if (typeMeta.getNumValues() != value.length) throw new IllegalArgumentException(“This buffer needs " + typeMeta.getNumValues() + " values!”);
values.put(type, value);
}
public void setByteValue(Type type, byte… value) {
TypeMeta typeMeta = TypeMeta.getTypeMeta(type);
if (typeMeta.getBufferType() != ByteBuffer.class) throw new IllegalArgumentException(“This buffer does not take ‘byte’ values!”);
if (typeMeta.getNumValues() != value.length) throw new IllegalArgumentException(“This buffer needs " + typeMeta.getNumValues() + " values!”);
values.put(type, value);
}
public void finish() {
for (Entry<Type, Buffer> bufferEntry : buffers.entrySet()) {
Type type = bufferEntry.getKey();
Buffer buffer = bufferEntry.getValue();
buffer.flip();
TypeMeta typeMeta = TypeMeta.getTypeMeta(type);
if (buffer instanceof FloatBuffer) {
setBuffer(type, typeMeta.getNumValues(), (FloatBuffer) buffer);
} else if (buffer instanceof ShortBuffer) {
setBuffer(type, typeMeta.getNumValues(), (ShortBuffer) buffer);
} else if (buffer instanceof ByteBuffer) {
setBuffer(type, typeMeta.getNumValues(), (ByteBuffer) buffer);
}
}
updateBound();
updateCounts();
}
/**
- This helper class specifies the meta data needed to create the respective buffers automatically.<br>
- This whole functionality COULD be integrated into the {@link Type} class to reduce unnecessary overhead!
*
-
@author Finomosec
*
*/
private static class TypeMeta {
private static Map<Type, TypeMeta> mapping;
public static TypeMeta getTypeMeta(Type type) {
if (mapping == null) {
mapping = new HashMap<Type, TypeMeta>();
mapping.put(Type.Index, new TypeMeta(1, ShortBuffer.class));
mapping.put(Type.Position, new TypeMeta(3, FloatBuffer.class));
mapping.put(Type.Normal, new TypeMeta(3, FloatBuffer.class));
mapping.put(Type.Size, new TypeMeta(1, FloatBuffer.class));
mapping.put(Type.Color, new TypeMeta(4, FloatBuffer.class));
// TODO: Complete with other Types!
// Alternatively this data and functionality could be included directly in the Type Enum!!
}
return mapping.get(type);
}
private int numValues;
private Class<? extends Buffer> bufferType;
private TypeMeta(int numValues, Class<? extends Buffer> bufferType) {
this.numValues = numValues;
this.bufferType = bufferType;
}
public int getNumValues() {
return numValues;
}
public Class<? extends Buffer> getBufferType() {
return bufferType;
}
public Buffer createBuffer(int initialSize) {
Buffer buffer = null;
if (getBufferType() == FloatBuffer.class) buffer = BufferUtils.createFloatBuffer(initialSize * numValues);
else if (getBufferType() == ShortBuffer.class) buffer = BufferUtils.createShortBuffer(initialSize * numValues);
else if (getBufferType() == ByteBuffer.class) buffer = BufferUtils.createByteBuffer(initialSize * numValues);
return buffer;
}
}
}
[/java]
Here a simple example how to use it:
[java]
CustomMesh mesh = new CustomMesh(12);
mesh.setMode(Mode.Triangles);
mesh.setColor(ColorRGBA.Blue);
mesh.addVertex(0, 0, -2);
mesh.addVertex(-1, 0, -1);
mesh.addVertex(1, 0, -1);
mesh.setColor(ColorRGBA.Red);
mesh.addVertex(2, 0, 0);
mesh.addVertex(1, 0, -1);
mesh.addVertex(1, 0, 1);
mesh.setColor(ColorRGBA.Green);
mesh.addVertex(0, 0, 2);
mesh.addVertex(1, 0, 1);
mesh.addVertex(-1, 0, 1);
mesh.setColor(ColorRGBA.Yellow);
mesh.addVertex(-2, 0, 0);
mesh.addVertex(-1, 0, 1);
mesh.addVertex(-1, 0, -1);
mesh.finish();
Geometry geo = new Geometry(“Colors”, mesh);
Material mat = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);
mat.setBoolean(“VertexColor”, true);
geo.setMaterial(mat);
rootNode.attachChild(geo);
[/java]
It could even be shortened to this (for each of the 4 blocks):
[java]
mesh.addVertex(new Vector3f(0, 0, -2), ColorRGBA.Blue);
mesh.addVertex(-1, 0, -1);
mesh.addVertex(1, 0, -1);
[/java]
And here is how I used it to create a 3D spiral:
[java]
ColorRGBA glowColor = new ColorRGBA(0.0f, 0.0f, 0.8f, 1f);
ColorRGBA color = new ColorRGBA(0.3f, 0.3f, 1.0f, 1f);
int points = 300;
float rounds = 10;
float maxRadius = 5f;
float maxDepth = 20f;
CustomMesh mesh = new CustomMesh(points * 2);
mesh.setMode(Mode.LineStrip);
ColorRGBA baseColor;
baseColor = new ColorRGBA(0.0f, 0.0f, 0.8f, 1f);
for (int i = points; i > 0; i–) {
float percent = (float) i / (float) points;
float angle = (float) (Math.PI * 2f * rounds * percent);
float xPos = (float) Math.sin(angle) * maxRadius * percent;
float yPos = (float) Math.cos(angle) * maxRadius * percent;
float depth = maxDepth - maxDepth * (float) Math.sqrt(percent);
mesh.setColor(baseColor.mult(wrap(1.8f - percent * 2f, 0f, 1f)));
mesh.addVertex(xPos, yPos, depth);
}
baseColor = new ColorRGBA(0.3f, 0.3f, 1f, 1f);
for (int i = 0; i < points; i++) {
float percent = (float) i / (float) points;
float angle = (float) (Math.PI * 2f * rounds * percent + Math.PI); // 180 degree offset
float xPos = (float) Math.sin(angle) * maxRadius * percent;
float yPos = (float) Math.cos(angle) * maxRadius * percent;
float depth = maxDepth - maxDepth * (float) Math.sqrt(percent);
mesh.setColor(baseColor.mult(wrap(1.8f - percent * 2f, 0f, 1f)));
mesh.addVertex(xPos, yPos, depth);
}
mesh.finish();
Geometry geo = new Geometry(“Portal”, mesh);
Material mat = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);
mat.setBoolean(“VertexColor”, true);
mat.setColor(“GlowColor”, glowColor);
geo.setMaterial(mat);
// geo.move(pos);
// Matrix3f rotationMatrix = new Matrix3f();
// rotationMatrix.fromStartEndVectors(Vector3f.UNIT_Z, pos.normalize());
// geo.setLocalRotation(rotationMatrix);
rootNode.attachChild(geo);
}
private float wrap(float in, float min, float max) {
if (in < min) in = min;
if (in > max) in = max;
return in;
}
[/java]
The basic concept is:
You set all the parameters you need and then call addVertex().
Then you change all parameters that need to CHANGE and leave the others alone.
And finally call addVertex() again.
All unchanged parameters will be copied from the previous vertex.
At the end call finish() and that’s all.
The buffers are all handled automatically.
Greeting Fino;