New class for custom meshes

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;
7 Likes

O.o This actually looks pretty good - but I see you’re using a ShortBuffer for indices… I’ve had problems with that before when creating larger meshes - such as those generated by using algorithms such as marching cubes. Seems really easy to use it though. I like that the Indices are automatically handled, but that may cause some problems as well… The issue of using a ShortBuffer is only clear when you have around 11,000 triangles per mesh :stuck_out_tongue:

Well i was using the ShortBuffer because i found an example like that somewhere.



I was concerned about that point myself already …



What can we use instead??

Just gonna comment: Wormhole for the win! :smiley:

ShortBuffer should be fine for most, but perhaps an option to specify a custom buffer or a similar solution would be well received

Here it is … i changed it to IntBuffer:

[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 {@link #addVertex()}.
  • 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 FloatBuffer) {

buffer = BufferUtils.ensureLargeEnough((FloatBuffer) buffer, typeMeta.getNumValues() * increaseBy);

} else if (buffer instanceof IntBuffer) {

buffer = ensureLargeEnough((IntBuffer) buffer, typeMeta.getNumValues() * increaseBy);

} else 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;

}



/**

  • Ensures there is at least the <code>required</code> number of entries left after the current position of the
  • buffer. If the buffer is too small a larger one is created and the old one copied to the new buffer.

    *
  • @param buffer
  •        buffer that should be checked/copied (may be null)<br />
    
  • @param required
  •        minimum number of elements that should be remaining in the returned buffer<br />
    
  • @return a buffer large enough to receive at least the <code>required</code> number of entries, same position as
  •     the input buffer, not null<br />
    

*/

public static IntBuffer ensureLargeEnough(IntBuffer buffer, int required) { // Did not exist in BufferUtils so i had to copy & modify it TODO: Add/move to BufferUtils

if (buffer == null || (buffer.remaining() < required)) {

int position = (buffer != null ? buffer.position() : 0);

IntBuffer newVerts = BufferUtils.createIntBuffer(position + required);

if (buffer != null) {

buffer.rewind();

newVerts.put(buffer);

newVerts.position(position);

}

buffer = newVerts;

}

return buffer;

}



public void addVertex() {

((IntBuffer) getBufferFor(Type.Index)).put(index++);

for (Entry<Type, Object> valueEntry : values.entrySet()) {

Buffer buffer = getBufferFor(valueEntry.getKey());

if (buffer instanceof FloatBuffer) {

((FloatBuffer) buffer).put((float[]) valueEntry.getValue());

} else if (buffer instanceof IntBuffer) {

((IntBuffer) buffer).put((int[]) valueEntry.getValue());

} else if (buffer instanceof ShortBuffer) {

((ShortBuffer) buffer).put((short[]) 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 setIntValue(Type type, int… value) {

TypeMeta typeMeta = TypeMeta.getTypeMeta(type);

if (typeMeta.getBufferType() != IntBuffer.class) throw new IllegalArgumentException(“This buffer does not take ‘int’ 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 IntBuffer) {

setBuffer(type, typeMeta.getNumValues(), (IntBuffer) 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, IntBuffer.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! OR Alternatively add this data and functionality 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() == IntBuffer.class) buffer = BufferUtils.createIntBuffer(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]



    p.s. BufferUtils is missing the function “public static IntBuffer ensureLargeEnough(IntBuffer buffer, int required)”.



    Greetings Fino;

So who can add this to JME3???

cool wormhole effect, didnt know you could use other mesh modes other than triangles.

Since you liked it so much i have put up some more …

http://hub.jmonkeyengine.org/groups/user-code-projects/forum/topic/project-genesis-new-wip-1/

Thank you! I’m using this (very simply for now) to create some basic meshes beyond the standard JME3 ones. Maybe I’ll get to the point where I can build something as fancy as your wormholes, but for now its mostly hex tiles ;).



Thanks!

This looks AWESOME!! Is there a way to change the location of a vertex (without making a completely new mesh and replacing it)? Just wondering. It looks great, though! I hope to be able to use this some time…



-NomNom

This looks like exactly what i needed to jump start my custom mesh class. Ill be playing around with it the next couple days and let you know how it goes.

Working well so far. Ive added in some code to handle setting texCoords as well. Great job.

We can add this to jME3, but I would prefer if this was done by a factory or generator class, since the CustomMesh class will be written into J3O files.

1 Like

Looks like a Mesh Builder:

http://en.wikipedia.org/wiki/Builder_pattern



Regards