Use the Mesh class to create custom shapes that go beyond Quad, Box, Cylinder, and Sphere, even procedural shapes are possible. Thank you to KayTrance for providing the sample code!
In this tutorial, we (re)create a very simple rectangular mesh, and we have a look at different ways of coloring it. A flat rectangle may not look useful because it's exactly the same as a com.jme3.scene.shape.Quad
. We choose this simple example in order to show you how to build any shape out of triangles – without the distractions of more complex shapes.
Polygon meshes are made up of triangles. The corners of the triangles are vertices. So, when ever you create a new shape, you break it down into triangles.
Let's look at a cube. A cube is made up of 6 rectangles. Each rectangle can be broken down into two triangles. This means you need 12 triangles to create a cube mesh. You also need to know the 8 corner coordinates (vertices). The trick is that you have to specify the vertices in a certain order: Each triangle separately, counter-clockwise.
Sounds worse than it is – here is an example:
Okay, we want to create a Quad. A quad has four vertices, and is made up of two triangles.
The base class for creating meshes is com.jme3.scene.Mesh
.
Mesh m = new Mesh();
To define your own shape, determine its vertex positions in space. Store them in an array using com.jme3.math.Vector3f. For a Quad, we need four vertices: Bottom left, bottom right, top left, top right. We name the array vertices[]
.
Vector3f [] vertices = new Vector3f[4]; vertices[0] = new Vector3f(0,0,0); vertices[1] = new Vector3f(3,0,0); vertices[2] = new Vector3f(0,3,0); vertices[3] = new Vector3f(3,3,0);
Next, define the Quad's 2D texture coordinates for each vertex, in the same order: Bottom left, bottom right, top left, top right. We name this array texCoord[]
Vector2f[] texCoord = new Vector2f[4]; texCoord[0] = new Vector2f(0,0); texCoord[1] = new Vector2f(1,0); texCoord[2] = new Vector2f(0,1); texCoord[3] = new Vector2f(1,1);
Next we turn the unrelated coordinates into triangles – We define the order in which the mesh is constructed. Think of these indexes as coming in groups of three. Each group of indexes describes one triangle. Note that you must specify the vertices counter-clockwise!
int [] indexes = { 2,0,1, 1,3,2 };
2\2--3 | \ | Counter-clockwise | \ | 0--1\1
The Mesh data is stored in a buffer.
com.jme3.util.BufferUtils
, we create three buffers for the three types of information we have:com.jme3.scene.VertexBuffer.Type
.m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices)); m.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoord)); m.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(indexes)); m.updateBound();
Our Mesh is ready! Now we want to see it.
We create a com.jme3.scene.Geometry
, apply a simple color material to it, and attach it to the rootNode to make it appear in the scene.
Geometry geom = new Geometry("OurMesh", m); Material mat = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md"); mat.setColor("Color", ColorRGBA.Blue); geom.setMaterial(mat); rootNode.attachChild(geom);
Ta-daa!
There are more vertex buffers in a Mesh than the three shown above. For an overview, see also Meshes and Models.
Vertex coloring is a simple way of coloring meshes. Instead of just assigning one solid color, each vertex (corner) has a color assigned. The faces between the vertices are then colored with a gradient.
We will use the same mesh m
as defined above, but with a special VertexColor material.
Geometry coloredMesh = new Geometry ("ColoredMesh", m); Material matVC = new Material(assetManager, "Common/MatDefs/Misc/VertexColor.j3md");
We create a float array color buffer.
int colorIndex = 0;
float[] colorArray = new float[4*4];
float[] colorArray = new float[yourVertexCount * 4]
We loop over the colorArray buffer to quickly set some RGBA value for each vertex. As usual, RGBA color values range from 0.0f to 1.0f. Note that the values we use here are arbitrarily chosen! It's just a quick loop to give every vertex a different RGBA value (a purplish gray, purple, a greenish gray, green, see screenshot), without writing too much code. For your own mesh, you'd assign values for the color buffer depending on which color you want your mesh to have.
for(int i = 0; i < 4; i++){ // Red value (is increased by .2 on each next vertex here) colorArray[colorIndex++]= 0.1f+(.2f*i); // Green value (is reduced by .2 on each next vertex) colorArray[colorIndex++]= 0.9f-(0.2f*i); // Blue value (remains the same in our case) colorArray[colorIndex++]= 0.5f; // Alpha value (no transparency set here) colorArray[colorIndex++]= 1.0f; }
Next, set the color buffer. An RGBA color value contains four float components, thus the parameter 4
.
m.setBuffer(Type.Color, 4, colorArray); coloredMesh.setMaterial(matVC);
Now you see a gradient color extending from each vertex.
Alternatively, you can show the vertices as colored points instead of coloring the faces.
Geometry coloredMesh = new Geometry ("ColoredMesh", cMesh); ... m.setMode(Mesh.Mode.Points); m.setPointSize(10f); m.updateBound(); m.setStatic(); Geometry points = new Geometry("Points", m); points.setMaterial(mat); rootNode.attachChild(points); rootNode.attachChild(coloredMesh);
This will result in a 10 px dot being rendered for each of the four vertices. The dot has the vertex color you specified above. The Quad's faces are not rendered at all. This can be used for a special debugging or editing mode.
By default, jME3 optimizes a scene by culling all backfaces. It determines which side the front or backface of a mesh is by the order of the vertices. The frontface is the one where the vertices are specified counter-clockwise.
This means your mesh, as created above, is invisible when seen from "behind". This may not be a problem and is often even intended. If you use the custom meshes to form a polyhedron, or flat wallpaper-like object, rendering the backfaces (the inside of the polyhedron) would indeed be a waste of resources.
In case that your use case requires the backfaces to be visible, you have two options:
mat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off));
int[] indexes = { 2,0,1, 1,3,2, 2,3,1, 1,0,2 };