Custom Mesh Artifacts [Solved]

Hi, new to JME so excuse my noobiness. I’m trying to create a terrain type mesh, programmatically.

In essense, I create a, say, 100 by 100 size mesh with 5000 “quads” (10,000 triangles), connected together in a flat grid along the X and Z axis. Starting from 0,0 and going up in both directions.

It looks as though when the origin of the mesh (0,0,0) is hidden from the camera, parts of the mesh becomes invisible. As though the bounds of the mesh hasn’t been updated?

Here’s a video of it: - YouTube

At the beginning of the video I showcase some of the code. The main method is ‘generate’ which loops through width and length and creates some helper objects which I call “quads” which each contain 4 vertices that make up that given quad.

I add those quads to the “vertices” ArrayList, as well as create indices in the “indices” arraylist.

The other method is the ‘apply’ method, which sets the vertex and indices buffers (and calls updateBound on the mesh).

Any help would be greatly appreciated. Thanks in advance!

If you change a mesh after adding it to a geometry then you will need to call:
http://javadoc.jmonkeyengine.org/com/jme3/scene/Geometry.html#updateModelBound()

Hi. I create the geometry object after I generate the terrain (and call updateBound on the mesh). So I suppose I shouldn’t have to do this.

Well, your problem indicates that your mesh bounds are not correct… or that the geometry bounds are not correct.

Given that we have no code to go on, it will be up to you to figure out why that is.

Right. Here’s the code.

Outside the class, I simply create a new Terrain instance, call “generate” and then create a geometry based on the mesh created.

package mygame;

import com.jme3.bounding.BoundingVolume;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.terrain.noise.basis.Noise;
import com.jme3.util.BufferUtils;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Random;

/**
 *
 * @author petter
 */
public class Terrain {

    /**
     * The width of the terrain.
     */
    public int width = 100;

    /**
     * The length of the terrain.
     */
    public int length = 100;

    /**
     * The size (width and length (x and z)) of each quad in the terrain.
     */
    public int gridSize = 1;

    public Mesh mesh = new Mesh();

    public ArrayList<Quad> quads;
    public ArrayList<Vector3f> vertices;
    public ArrayList<Integer> indices;
    public ArrayList<Vector2f> texCoords;

    public class Quad {
        public int index;
        public Vector3f position;

        public Vector3f[] vertices = new Vector3f[4];

        public Quad(int index, Vector3f position) {
            this.index = index;
            this.position = position;
        }
    }

    /**
     * Create a new terrain instance of the given width, length and gridSize.
     * @param width
     * @param length
     * @param gridSize 
     */
    public Terrain(int width, int length, int gridSize) {
        this.width = width;
        this.length = length;
        this.gridSize = gridSize;
    }

    public float getSize() {
        return width + length / 2f;
    }

    /**
     * Generates quad objects, vertices, indices and texcoords for the given width, length and gridSize.
     * Remember to call the apply method after generating or changing the terrain data.
     */
    public void generate() {
        //initialize verts, indices and texcoords lists
        vertices = new ArrayList<Vector3f>();
        indices = new ArrayList<Integer>();
        texCoords = new ArrayList<Vector2f>();

        //and the quads list
        quads = new ArrayList<Quad>();

        //create the quads and vertices
        int index = 0;
        int x;
        int z;
        int i;
        Quad q;

        for(x = 0; x < width/gridSize; x++) {
            for(z = 0; z < length/gridSize; z++) {
                q = new Quad(index, new Vector3f(x * gridSize, 0, z * gridSize));
                System.out.println("Creating Quad at: " + q.position.x + ", " + q.position.z);
                quads.add(q);

                //create the 4 vertices of this quad
                q.vertices[0] = new Vector3f(q.position.x, 0, q.position.z + gridSize);
                q.vertices[1] = new Vector3f(q.position.x + gridSize, 0, q.position.z + gridSize);
                q.vertices[2] = new Vector3f(q.position.x, 0, q.position.z);
                q.vertices[3] = new Vector3f(q.position.x + gridSize, 0, q.position.z);

                //create the vertices on the mesh itself
                Vector3f vert;
                for(int v = 0; v < q.vertices.length; v++) {
                    vert = q.vertices[v];
                    if (vertices.indexOf(vert) < 0) {
                        System.out.println("Vert: " + vert.x + ", " + vert.z);
                        //create this vertex and texture coordinate
                        vertices.add(vert);
                        texCoords.add(v, new Vector2f(vert.x / width, vert.z / length));
                        System.out.println("texcoord: " + texCoords.get(v).x + "," + texCoords.get(v).y);
                    }
                }

                //create the indices
                indices.add(vertices.indexOf(q.vertices[2]));
                indices.add(vertices.indexOf(q.vertices[0]));
                indices.add(vertices.indexOf(q.vertices[1]));

                System.out.println("Creating triangle: " + q.vertices[2] + ", " + q.vertices[0] + ", " + q.vertices[1]);

                indices.add(vertices.indexOf(q.vertices[1]));
                indices.add(vertices.indexOf(q.vertices[3]));
                indices.add(vertices.indexOf(q.vertices[2]));

                System.out.println("Creating triangle: " + q.vertices[1] + ", " + q.vertices[3] + ", " + q.vertices[2]);

                index++;
            }
        }
    }

    /**
     * Updates the mesh. This must be called after generating, or modifying the vertices manually.
     */
    public void apply() {
        //vertices
        Vector3f[] verticesArray = new Vector3f[vertices.size()];
        for(int i = 0; i < vertices.size(); i++) {
            verticesArray[i] = vertices.get(i);
        }
        mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(verticesArray));

        //texture coords
        Vector2f[] texCoordsArray = new Vector2f[texCoords.size()];
        for(int i = 0; i < texCoords.size(); i++) {
            texCoordsArray[i] = texCoords.get(i);
        }
        mesh.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoordsArray));


        //indices
        int[] indicesArray = new int[indices.size()];
        for(int i = 0; i < indices.size(); i++) {
            indicesArray[i] = indices.get(i);
        }
        mesh.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(indicesArray));

        //update bounds
        mesh.updateBound();
        BoundingVolume v = mesh.getBound();
        System.out.println(v.contains(new Vector3f(99f, 0, 99f)));// false (ought to be true)
    }

}

…this is an odd test. Why not just print the bounding volume?

Beware of debug code that already makes too many assumptions.

Also, we’ll need to see the code where the geometry is created.

Here’s the code that creates the geometry:

    Terrain terrain = new Terrain(100, 100, 2);
    terrain.generate();
    terrain.apply();
    
    Geometry terrainGeom = new Geometry("Terrain", terrain.mesh);
    
    Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
    //mat.getAdditionalRenderState().setWireframe(true);
    mat.setTexture("DiffuseMap", assetManager.loadTexture("Textures/Terrain/sand.jpg"));
    mat.setBoolean("UseMaterialColors", true);
    mat.setColor("Diffuse", ColorRGBA.White);
    terrainGeom.setMaterial(mat);
    
    rootNode.attachChild(terrainGeom);

…except where is the code that calls generate() and/or apply() which you’ve assured is done before the geometry is created.

My previous post, line 2 and 3 of the code snippet calls the generate and apply methods.

I swear that wasn’t there before… weird.

Anyway, change to actually print the real bounding volume as I suggested instead of layering assumptions into the println.

Hah, there was indeed an issue when I copy-pasted the code so I had to edit it quickly :smiley:

Right, I’ll try that.

I added the line

System.out.println(terrain.mesh.getBound());

right after calling terrain.apply(), and this is what I get:

BoundingBox [Center: (50.0, 0.0, 50.0)  xExtent: 50.0  yExtent: 0.0  zExtent: 50.0]

I’m not sure if I read these values correctly, but if the three “extent” values are distance from the center, then I suppose it’s correct.

Yep, and your test that the comment says it returns false should have returned true.

Sure.

Well, I have no idea. I’ve re-written the code several times now and I still get the same issue. I’m clueless :smiley:

edit: apparently I just time-traveled and replied to your post before you posted it.

True. It has 0 height so it doesn’t contain anything. So I guess that test is right to return false… and I was right to steer you away from early assumptions. :smile:

Doesn’t explain the frustum culling issue, though.

FINALLY! I solved it after so many hours. The mesh needs tangents/normals! geez!

Thanks for help though pspeed!

The mesh only needs tangents if you are doing bump mapping.

And yeah, lit meshes need normals or the light doesn’t know what direction the surface is. Note: the Quad source code is like two clicks away and sets everything needed for Lighting.j3md (without normalmaps or bumpmaps).

Yeah I learned that.

I tried adding normals for each vertex, by doing this:

for(Vector3f v : vertices) {
    normals.add(Vector3f.UNIT_Y);
}

//and later, this:
Vector3f[] normalsArray = normals.toArray(new Vector3f[normals.size()]);
mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normalsArray));

Just to test it, as I understand it, that should be “correct” for any flat “plane”-like mesh.

But the result isn’t very nice:

Your triangles look bad. Normals don’t have anything to do with that.

Switch to the Unshaded material if you are wondering… just give it a solid “Color”. You can also set wire frame to see what your actual triangles are.

What the… I swear it looked OK earlier… with the unshaded material…

Hm. Does it matter in what order I define the vertices?.. It seemed to me that the only requirement is that the indices (triangles) connect the right vertices (obviously :smiley: ) and that they’re connected in a counter-clockwise fashion. Which I am doing.