TerraMonkey Cave Code

Hi,

I am trying to change the code of LODGeomap as follows:

  • I set a threshold value for y
  • If in a generated triangle the y value of one point is below the threshold and the y value of another point is above the threshold, then remove this triangle from the mesh.
  • The way a triangle is removed from the mesh is by changing the index of the one point that is on the other side of the threshold than the 2 other points to an index of one of the 2 other points. (Thus the triangle becomes a line).

The reason why I want to do this is that this way there can be discontinuities in the height of the terrain. 2 different terrains can be combined to form a cave as follows:

  • There is one terrain with highs and lows and discontinuities between them
  • There is another terrain with the height above the lows of the other terrain and below the highs of the other terrain.

I am testing my code changes with TerrainTestAdvanced.java (which allows to see a wireframe)

First I change the following in GeoMap.java (just to have a clear distinction between the lows and highs in the test file):


public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean center) {

    if (store!=null){
        if (store.remaining() < width*height*3)
            throw new BufferUnderflowException();
    }else{
        store = BufferUtils.createFloatBuffer(width*height*3);
    }

    assert hdata.length == height*width;

    Vector3f offset = new Vector3f(-getWidth() * scale.x * 0.5f,
                                   0,
                                   -getWidth() * scale.z * 0.5f);
    if (!center)
        offset.zero();

    int i = 0;
    for (int z = 0; z < height; z++){
        for (int x = 0; x < width; x++){
            store.put( (float)x*scale.x + offset.x );

float y = hdata[i];
if (y < 70f) {
store.put( (float)57.5fscale.y );
} else {
store.put( (float)y
scale.y);
}
i++;
// store.put( (float)hdata[i++]scale.y );
// store.put( (float)57.5f
scale.y );
store.put( (float)z*scale.z + offset.z );
}
}

Then I try to remove the triangles in the mesh where there are points on both side of the threshold as follows in LODGeomap:


public Mesh createMesh(Vector3f scale, Vector2f tcScale, Vector2f tcOffset, float offsetAmount, int totalSize, boolean center, int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod) {
FloatBuffer pb = writeVertexArray(null, scale, center);
FloatBuffer texb = writeTexCoordArray(null, tcOffset, tcScale, offsetAmount, totalSize);
FloatBuffer nb = writeNormalArray(null, scale);
Buffer ib;
IndexBuffer idxB = writeIndexArrayLodDiff(lod, rightLod, topLod, leftLod, bottomLod, totalSize);
//TODO patch idxB
pb.position(0);
float[] pbFloats = new float[pb.limit()];
pb.get(pbFloats);
boolean[] bottom = new boolean[pbFloats.length/3];
for (int i = 0; i < bottom.length; i++) {
int index = 3i + 1;
bottom[i] = (pbFloats[index] == 57.5f
scale.y);
}
if (idxB.getBuffer() instanceof IntBuffer) {
IntBuffer temp = (IntBuffer)idxB.getBuffer();
int limit = temp.limit();
for (int i = 0; i < limit/3; i++) {
temp.position(3i);
int co1 = temp.get();
temp.position(3
i + 1);
int co2 = temp.get();
temp.position(3i + 2);
int co3 = temp.get();
if (bottom[co1]) {
if (bottom[co2]) { //co1 & co2
if (!bottom[co3]) {
temp.position(3
i + 2);
temp.put(co1);
}
} else { //co1 & !co2
if (bottom[co3]) {
temp.position(3i + 1);
temp.put(co1);
} else {
temp.position(3
i);
temp.put(co2);
}
}
} else {
if (!bottom[co2]) { //!co1 & !co2
if (bottom[co3]) {
temp.position(3i + 2);
temp.put(co1);
}
} else { //!co1 & co2
if (!bottom[co3]) {
temp.position(3
i + 1);
temp.put(co1);
} else {
temp.position(3i);
temp.put(co2);
}
}
}
}
} else {
ShortBuffer temp = (ShortBuffer)idxB.getBuffer();
int limit = temp.limit();
for (int i = 0; i < limit/3; i++) {
temp.position(3
i);
short co1 = temp.get();
temp.position(3i + 1);
short co2 = temp.get();
temp.position(3
i + 2);
short co3 = temp.get();
if (bottom[co1]) {
if (bottom[co2]) { //co1 & co2
if (!bottom[co3]) {
temp.position(3i + 2);
temp.put(co1);
}
} else { //co1 & !co2
if (bottom[co3]) {
temp.position(3
i + 1);
temp.put(co1);
} else {
temp.position(3i);
temp.put(co2);
}
}
} else {
if (!bottom[co2]) { //!co1 & !co2
if (bottom[co3]) {
temp.position(3
i + 2);
temp.put(co1);
}
} else { //!co1 & co2
if (!bottom[co3]) {
temp.position(3i + 1);
temp.put(co1);
} else {
temp.position(3
i);
temp.put(co2);
}
}
}
}
}

    if (idxB.getBuffer() instanceof IntBuffer)
        ib = (IntBuffer)idxB.getBuffer();
    else
        ib = (ShortBuffer)idxB.getBuffer();
    FloatBuffer bb = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3);
    FloatBuffer tanb = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3);
    writeTangentArray(nb, tanb, bb, texb, scale);
    Mesh m = new Mesh();
    m.setMode(Mode.TriangleStrip);
    m.setBuffer(Type.Position, 3, pb);
    m.setBuffer(Type.Normal, 3, nb);
    m.setBuffer(Type.Tangent, 3, tanb);
    m.setBuffer(Type.Binormal, 3, bb);
    m.setBuffer(Type.TexCoord, 2, texb);
    if (ib instanceof IntBuffer)
        m.setBuffer(Type.Index, 3, (IntBuffer)ib);
    else if (ib instanceof ShortBuffer)
        m.setBuffer(Type.Index, 3, (ShortBuffer)ib);
    m.setStatic();
    m.updateBound();
    return m;
}

However I see no difference when patching the method of LODGeomap. Does someone know why my changes to LODGeomap seem not to have the intended effect?

(The patch is temporarily, once I know how to remove the triangles from the mesh, I will refactor the code so it is cleaner.)

Regards,

Toon

1 Like

I guess the following line


        m.setMode(Mode.TriangleStrip);

makes it hard to create discontinuities in the Mesh.

@Toon said: I guess the following line

        m.setMode(Mode.TriangleStrip);

makes it hard to create discontinuities in the Mesh.

I think you can but you’d somehow have to insert degenerate triangles to create holes or whatever. I didn’t read in detail what you are trying to attempt so I don’t know if that’s helpful.

Yep you can create degenerate triangles for holes using just the index bufer. The terrain LOD routine does this already.
This is cool @Toon. If it works out I will add it to core. One addition to it would be a way to specify actual world coordinates where a cave entrance would be as not all caves are at the lowest point in the terrain. Although those points could just be sunk really, really low.
A requirement for it to be in core is for it to work with physics. Right now terrain uses a heightfield for Bullet, but it could change to use a regular collision mesh; they are apparently quite fast too now.

You are right it should work with degenerate triangles.

I have it more or less working with the following code. The only changes I have to make to the existing code is to change


        geomap = new MyLODGeomap(size, heightMap);

in TerrainPatch twice.

The code is:


public class MyLODGeomap extends LODGeomap {
    //TerrainTest.java
    public static final float CUT_OFF = 180f;
    public static final float BOTTOM = 115f;
    //TerrainTestAdvanced.java
//    public static final float CUT_OFF = 70f;
//    public static final float BOTTOM = 57.5f;
    
    public MyLODGeomap(int size, float[] heightMap) {
        super(size, heightMap);
    }
    
    @Override
    public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean center) {
        FloatBuffer parentBuffer = super.writeVertexArray(store, scale, center);
        store = BufferUtils.createFloatBuffer(parentBuffer.limit());
        parentBuffer.position(0);
        
        for (int i = 0; i < parentBuffer.limit(); i++) {
            if ((i%3 == 1) && (hdata[i/3] < CUT_OFF)) { //reset y value
                parentBuffer.get();
                store.put(BOTTOM);
            } else {
                store.put(parentBuffer.get());
            }
        }

        return store;
    }
    
    @Override
    public IndexBuffer writeIndexArrayLodDiff(int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod, int totalSize) {
        return createDegenerateTriangles(super.writeIndexArrayLodDiff(lod, rightLod, topLod, leftLod, bottomLod, totalSize));
    }
    
    @Override
    public IndexBuffer writeIndexArrayLodVariable(int lod, int rightLod, int topLod, int leftLod, int bottomLod, int totalSize) {
        return createDegenerateTriangles(super.writeIndexArrayLodVariable(lod, rightLod, topLod, leftLod, bottomLod, totalSize));
    }
    
    private IndexBuffer createDegenerateTriangles(IndexBuffer indexBuffer) {
        boolean[] bottom = new boolean[hdata.length];
        for (int i = 0; i < hdata.length; i++) {
            bottom[i] = hdata[i] < CUT_OFF;
        }
        
        Buffer ibOld = (indexBuffer.getBuffer() instanceof IntBuffer) ? (IntBuffer)indexBuffer.getBuffer() : (ShortBuffer)indexBuffer.getBuffer();
        
        int[] oldIndicesInt = new int[ibOld.limit()];
        short[] oldIndicesShort = new short[ibOld.limit()];
        ibOld.position(0);
        if (ibOld instanceof IntBuffer) {
            ((IntBuffer)ibOld).get(oldIndicesInt);
        }
        else {
            ((ShortBuffer)ibOld).get(oldIndicesShort);
            for (int i = 0; i < oldIndicesShort.length; i++) {
                oldIndicesInt[i] = oldIndicesShort[i];
            }
        }
        
        int first = oldIndicesInt[0];
        int second = oldIndicesInt[1];
        //*2 to potentially convert each single triangle in 2 degenerate triangles, except for the first 2 points
        IntBuffer ibInt = BufferUtils.createIntBuffer(2*ibOld.limit() - 2);
        ibInt.put(first);
        ibInt.put(second);
        for (int i = 2; i < oldIndicesInt.length; i++) {
            int third = oldIndicesInt[i];
            // create 2 degenerate triangle if not already a degenerate triangle!
            if (((bottom[third] != bottom[first]) || (bottom[third] != bottom[second]))
                && (third != second) && (second != first) && (first != third)) {
                ibInt.put(second);
            }
            ibInt.put(third);
            first = second;
            second = third;
        }
        
        return IndexBuffer.wrapIndexBuffer(ibInt);
    }
}

This code separates the heights above the cut off from the heights below the cut off. The heights below the cut off are flattened. It is possible to hover under a mountain peak without colliding with the mesh. A cave could be made by adding a second terrain layer that intersects with the mountain peaks. Then a third terrain could be used for the ceiling inside the caves.

The only thing that seems weird if you test the code is that some rectangles in the height map are fully blacked out. I am not sure why.

1 Like

The blacked out rectangles appear because they have the opposite orientation after an extra degenerate triangle has been added. If I add 2 degenerate triangles instead of 1, they have again their original orientation.

Here is the code which has the intended effect:


public class MyLODGeomap extends LODGeomap {
    //TerrainTest.java
    public static final float CUT_OFF = 180f;
    public static final float BOTTOM = 115f;
    //TerrainTestAdvanced.java
//    public static final float CUT_OFF = 70f;
//    public static final float BOTTOM = 57.5f;
    
    public MyLODGeomap(int size, float[] heightMap) {
        super(size, heightMap);
    }
    
    @Override
    public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean center) {
        FloatBuffer parentBuffer = super.writeVertexArray(store, scale, center);
        store = BufferUtils.createFloatBuffer(parentBuffer.limit());
        parentBuffer.position(0);
        
        for (int i = 0; i < parentBuffer.limit(); i++) {
            if ((i%3 == 1) && (hdata[i/3] < CUT_OFF)) { //reset y value
                parentBuffer.get();
                store.put(BOTTOM);
            } else {
                store.put(parentBuffer.get());
            }
        }

        return store;
    }
    
    @Override
    public IndexBuffer writeIndexArrayLodDiff(int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod, int totalSize) {
        return createDegenerateTriangles(super.writeIndexArrayLodDiff(lod, rightLod, topLod, leftLod, bottomLod, totalSize));
    }
    
    @Override
    public IndexBuffer writeIndexArrayLodVariable(int lod, int rightLod, int topLod, int leftLod, int bottomLod, int totalSize) {
        return createDegenerateTriangles(super.writeIndexArrayLodVariable(lod, rightLod, topLod, leftLod, bottomLod, totalSize));
    }
    
    private IndexBuffer createDegenerateTriangles(IndexBuffer indexBuffer) {
        boolean[] bottom = new boolean[hdata.length];
        for (int i = 0; i < hdata.length; i++) {
            bottom[i] = hdata[i] < CUT_OFF;
        }
        
        Buffer ibOld = (indexBuffer.getBuffer() instanceof IntBuffer) ? (IntBuffer)indexBuffer.getBuffer() : (ShortBuffer)indexBuffer.getBuffer();
        
        int[] oldIndicesInt = new int[ibOld.limit()];
        short[] oldIndicesShort = new short[ibOld.limit()];
        ibOld.position(0);
        if (ibOld instanceof IntBuffer) {
            ((IntBuffer)ibOld).get(oldIndicesInt);
        }
        else {
            ((ShortBuffer)ibOld).get(oldIndicesShort);
            for (int i = 0; i < oldIndicesShort.length; i++) {
                oldIndicesInt[i] = oldIndicesShort[i];
            }
        }
        
        int first = oldIndicesInt[0];
        int second = oldIndicesInt[1];
        //*3 to potentially convert each single triangle in 3 degenerate triangles, except for the first 2 points
        IntBuffer ibInt = BufferUtils.createIntBuffer(3*ibOld.limit() - 4);
        ibInt.put(first);
        ibInt.put(second);
        for (int i = 2; i < oldIndicesInt.length; i++) {
            int third = oldIndicesInt[i];
            // create 2 degenerate triangle if not already a degenerate triangle!
            if (((bottom[third] != bottom[first]) || (bottom[third] != bottom[second]))
                && (third != second) && (second != first) && (first != third)) {
                ibInt.put(second);
                ibInt.put(second);
            }
            ibInt.put(third);
            first = second;
            second = third;
        }
        
        return IndexBuffer.wrapIndexBuffer(ibInt);
    }
}