Strange results with custom mesh

I’m trying to make a custom mesh and used
custom mesh tutorial
as starting point. In print out it looks correct, but when I try to render my mesh it’s all messed up.

import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.math.FastMath;
import com.jme3.scene.VertexBuffer;
import com.jme3.util.BufferUtils;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Asteroth
 */
public class Cone extends Mesh{
    
    private Vector3f [] vertices;
    private float [] textureCoords;
    private int [] indexes;
    private float height, radius;
    
    
    /*
    TODO - add parameter for multiple rings along the height and noising.
    */
    public Cone(int verticeCount, float heightin, float radin)
    {
        this.radius = radin;
        this.height = heightin;
        vertices = new Vector3f[verticeCount+1];
        textureCoords = new float[verticeCount+1];
        float stepsize = FastMath.TWO_PI/(float)verticeCount;
        System.err.println(String.format("computed stepsize = %f", FastMath.RAD_TO_DEG*stepsize));
        
        
        Vector3f basepos = new Vector3f(0, 0, -height/2); 
        vertices[0] = basepos;
        for(int i = 1; i < verticeCount+1; i++)
        {
            float angle = (i-1) * stepsize;
            vertices[i] = new Vector3f(FastMath.cos(angle), FastMath.sin(angle), height/2).mult(radius);
            textureCoords[i] = (float)i /verticeCount;
        }
        List<Integer> indexHelper = new ArrayList<Integer>();
        int current = 1;
        for(int i = 0; i<vertices.length*3; i++)
        {
            if(i%3==0)
            {
                indexHelper.add(0);
                current --;
            }
            else
            {
                current ++;
                indexHelper.add(current);
            }
        }
        indexes = new int[indexHelper.size()];
        /*
        why is there no built in method to turn list<Integer> to int array...*/
        for(int i = 0; i< indexHelper.size(); i++)
        {
            indexes[i] = indexHelper.get(i);
        }
        
        setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
        setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(textureCoords));
        setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(indexes)); //EDIT - was missing indexing, still missing some triangles thogh


        updateBound();
        setStatic();
    }
    
    public void print()
    {
        System.out.println(String.format("printing cone of %s height with %s vertices", height, vertices.length));
        for(int i = 0; i < vertices.length; i++)
        {
            System.out.println("["+i+"] " + vertices[i]);
        }
        System.out.println("--------------------------------------------%\nVertex connections:");
        for(int i = 0; i<indexes.length; i++)
        {
            if(i%3==0)
                System.out.println();
            System.out.print(indexes[i] + ", ");
        }
    }
}

For example, when I use

Cone island = new Cone(8, 3f, 1f);
island.print();
Geometry testCone = new Geometry("island test", island);
Material testMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
testMat.getAdditionalRenderState().setWireframe(true);
testMat.setColor("Color", ColorRGBA.Red);
testCone.setMaterial(testMat);
testCone.setLocalTranslation(0, 0, 3f);
rootNode.attachChild(testCone);

I see from the print that the connection by index should be correct, yet I only get 3 triangles, with weird angles and wrong dimensions. What did I mess up?

EDIT - one missing line found, still some indexing problems though - last triangle is missing

Hello, @asteroth.

I would like to help. The following will tell what’s wrong.

Debug

It’s always good to create a debug xyz-Axis when making custom mesh. It is easier to find problems with a reference.

Step 1. Add xyz-axis

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.debug.Arrow;

/**
 * Debug for 
 * 
 * <a href="https://hub.jmonkeyengine.org/t/strange-results-with-custom-mesh/40630">
 * Strange results with custom mesh
 * </a>
 * 
 * @author yanmaoyuan
 *
 */
public class Example extends SimpleApplication {

    @Override
    public void simpleInitApp() {
        setupCamera();
        
        createAxis();
        
        createCone();
    }

    // create cone
    private void createCone() {
        Cone island = new Cone(8, 3f, 1f);
        island.print();
        Geometry testCone = new Geometry("island test", island);
        Material testMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        testMat.getAdditionalRenderState().setWireframe(true);
        testMat.setColor("Color", ColorRGBA.Red);
        testCone.setLocalTranslation(0, 0, 3f);
        testCone.setMaterial(testMat);
        rootNode.attachChild(testCone);
    }
    
    // setup camemra
    private void setupCamera() {
        cam.setLocation(new Vector3f(8, 8, 16));
        cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);

        flyCam.setMoveSpeed(10);
    }

    // create xyz-axis
    private void createAxis() {
        createArrow(new Vector3f(5, 0, 0), ColorRGBA.Green);
        createArrow(new Vector3f(0, 5, 0), ColorRGBA.Red);
        createArrow(new Vector3f(0, 0, 5), ColorRGBA.Blue);
    }
    
    private void createArrow(Vector3f vec3, ColorRGBA color) {
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", color);

        Geometry geom = new Geometry("arrow", new Arrow(vec3));
        geom.setMaterial(mat);

        rootNode.attachChild(geom);
    }

    public static void main(String[] args) {
        Example app = new Example();
        app.start();
    }

}

The result of step1

With the image above,it seems that the Front and Back side of your triangles are wrong.

Step 2. What’s wrong?

To make is easier to figure out what’s wrong, I turned off the face culling. Which means opengl will draw all the triangles.

// create cone
private void createCone() {
    Cone island = new Cone(8, 3f, 1f);
    island.print();
    Geometry testCone = new Geometry("island test", island);
    Material testMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    testMat.getAdditionalRenderState().setWireframe(true);
    testMat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);// <--- Turn off face culling
    testMat.setColor("Color", ColorRGBA.Red);
    testCone.setLocalTranslation(0, 0, 3f);
    testCone.setMaterial(testMat);
    rootNode.attachChild(testCone);
}

Run the example again.

See? Some hidden triangles are shown right now.

Why those triangles were culled? The reason for this problem maybe:

  • A. The index order is wrong.
  • B. The normal directions are wrong.

As you don’t create normals for the Cone, the reason must be A.

Step 3. What’s wrong with the index order?

Remember, in OpenGL world counter-clockwise means Front side.

Let’s look deep into your code. This is the part where create the vertices.

    Vector3f basepos = new Vector3f(0, 0, -height/2); 
    vertices[0] = basepos;
    for(int i = 1; i < verticeCount+1; i++)
    {
        float angle = (i-1) * stepsize;
        vertices[i] = new Vector3f(FastMath.cos(angle), FastMath.sin(angle), height/2).mult(radius);
        textureCoords[i] = (float)i /verticeCount;
    }

And this is the output from print() method.

computed stepsize = 45.000000
printing cone of 3.0 height with 9 vertices
[0] (0.0, 0.0, -1.5)
[1] (1.0, 0.0, 1.5)
[2] (0.70710677, 0.70710677, 1.5)
[3] (-4.371139E-8, 1.0, 1.5)
[4] (-0.70710677, 0.70710677, 1.5)
[5] (-1.0, -8.742278E-8, 1.5)
[6] (-0.70710665, -0.7071069, 1.5)
[7] (1.1924881E-8, -1.0, 1.5)
[8] (0.707107, -0.70710653, 1.5)
--------------------------------------------%
Vertex connections:

0, 1, 2, 
0, 2, 3, 
0, 3, 4, 
0, 4, 5, 
0, 5, 6, 
0, 6, 7, 
0, 7, 8, 
0, 8, 9,  // <--- you don't have index 9
0, 9, 10, // <--- you don't have index 9 and 10

The index is:

The right vertex connections should be:

0,2,1,
0,3,2,
0,4,3,
0,5,4,
0,6,5,
0,7,6,
0,8,7,

Or

0,8,7,
0,7,6,
0,6,5,
0,5,4,
0,4,3,
0,3,2,
0,2,1,

Step 4. last triangle is missing?

In fact, it’s not missing, just connect 0, 1 and 8.

0,8,7,
0,7,6,
0,6,5,
0,5,4,
0,4,3,
0,3,2,
0,2,1,
0,1,8, //<-- last triangle

There is a trick for the last triangle: copy vertex 1 as the vertex 9, then the index will be:

0,9,8, //<-- last triangle
0,8,7,
0,7,6,
0,6,5,
0,5,4,
0,4,3,
0,3,2,
0,2,1,

Finally

After all, this is the modified code.

public Cone(int verticeCount, float heightin, float radin)
{
    this.radius = radin;
    this.height = heightin;
    vertices = new Vector3f[verticeCount+2];// +1 for vertex[9]
    textureCoords = new float[verticeCount+2];// +1 for vertex[9]
    float stepsize = FastMath.TWO_PI/(float)verticeCount;
    System.err.println(String.format("computed stepsize = %f", FastMath.RAD_TO_DEG*stepsize));
    
    
    Vector3f basepos = new Vector3f(0, 0, -height/2); 
    vertices[0] = basepos;
    for(int i = 1; i < verticeCount+1; i++)
    {
        float angle = (i-1) * stepsize;
        vertices[i] = new Vector3f(FastMath.cos(angle), FastMath.sin(angle), height/2).mult(radius);
        textureCoords[i] = (float)i /verticeCount;
    }
    vertices[verticeCount+1] = vertices[1];// <-------------- copy vertex[1] as the vertex[9]
    textureCoords[verticeCount+1] = textureCoords[1];//<--------  copy vertex[1] as the vertex[9]
    
    // build index
    // modified by yanmaoyuan
    indexes = new int[verticeCount*3];
    for(int i=0; i<verticeCount; i++) {
        indexes[i*3] = 0;
        indexes[i*3+1] = verticeCount - i + 1;
        indexes[i*3+2] = verticeCount - i;
    }
    
    setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
    setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(textureCoords));
    setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(indexes)); //EDIT - was missing indexing, still missing some triangles thogh


    updateBound();
    setStatic();
}

Run the exmaple.

Turn on face culling.

Turn off face culling.

Fin.

2 Likes

Thanks! It did solve the problem but I have soem question:
Does it mean I can’t reuse 8th vertice to create [0 2 1] and [0 1 8]?
Was thinking it is possible to link a last element of the shape with first to save up on one object, not sure if it would give visible performance boost on a large scale or not though.

You can use [0 1 8] as your wish. Solutions are many, I just show one of them.

However, one or two vertex does not save too much performance. The number of triangles has a greater impact on performance.

So while I technically CAN, the performance gain isn’t worth reducing code clarity. Thanks!

I modified the code to add “lid” for the cone and some noise to make it more irregular but when I tried to texture it, the texture is applied to half of the cone and stretched most of the time. Must be texture coordinates problem, obviously I don’t understand the concept of texture coordinates.

islandtextured

New code:

import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.math.FastMath;
import com.jme3.scene.VertexBuffer;
import com.jme3.util.BufferUtils;


/**
 *
 * @author Asteroth
 */
public class Cone extends Mesh{
    
    private Vector3f [] vertices;
    private float [] textureCoords;
    private int [] indexes;
    private float height, radius;
    private float noise = 0.1f;
    
    
    public Cone(int verticeCount, float heightin, float radin, float noise)
    {
        this.radius = radin;
        this.height = heightin;
        this.noise = noise;
        vertices = new Vector3f[verticeCount+3];
        textureCoords = new float[verticeCount+3];
        float stepsize = FastMath.TWO_PI/(float)verticeCount;
        System.err.println(String.format("computed stepsize = %f", FastMath.RAD_TO_DEG*stepsize));
        
        
        Vector3f basepos = noise(new Vector3f(0, -height/2, 0)),
                toppos = noise(new Vector3f(0, height/2, 0)); 
        
        vertices[0] = basepos;
        vertices[vertices.length-1] = toppos;
        for(int i = 1; i < verticeCount+1; i++)
        {
            float angle = (i-1) * stepsize;
            vertices[i] = noise(new Vector3f(FastMath.cos(angle), height/2, FastMath.sin(angle)).mult(radius));
            textureCoords[i] = (float)i /verticeCount;
        }
        
        /*copy first vertex as last one. */
        vertices[verticeCount + 1] = vertices[1];
        textureCoords[verticeCount + 1] = textureCoords[1];


        indexes = new int[verticeCount*3*2];
        //draw cone
        
        for (int i=0; i<verticeCount; i++)
        {
            indexes[i*3] = verticeCount -i;
            indexes[i*3+1] = verticeCount - i +1;
            indexes[i*3+2] = 0;
        }
        //draw lid:
        for (int i = 0; i < verticeCount; i++)
        {
            indexes[((i + verticeCount)*3)] =   vertices.length -1;
            indexes[((i + verticeCount)*3+1)] = verticeCount - i +1;
            indexes[((i + verticeCount)*3+2)] = verticeCount - i;
        }
        

        
        setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
        setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(textureCoords));
        setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(indexes));
        
        updateBound();
        setStatic();
    }
    
    
    
    private Vector3f noise(Vector3f pure)
    {
        Vector3f noised = 
                 pure.add(  noise*FastMath.nextRandomFloat()-noise/2f,
                            noise*FastMath.nextRandomFloat()-noise/2f,
                            noise*FastMath.nextRandomFloat()-noise/2f);
        
        System.err.println("noise " + noise + " applied to " + pure + " produced " + noised +
                " example noise result ");
        return noised;
    }
    
    public void print()
    {
        System.out.println(String.format("printing cone of %s height with %s vertices", height, vertices.length));
        for(int i = 0; i < vertices.length; i++)
        {
            System.out.println("["+i+"] " + vertices[i]);
        }
        System.out.println("--------------------------------------------%\nVertex connections:");
        for(int i = 0; i<indexes.length; i++)
        {
            if(i%3==0)
                System.out.println();
            System.out.print(indexes[i] + ", ");
        }
    }
}

and finally the way I call the island:

        Cone island = new Cone(32, 1f, 1f, 0.1f);
        island.print();
        Geometry testCone = new Geometry("island test", island);
        Material testMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
//        testMat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
//        testMat.getAdditionalRenderState().setWireframe(true);
//        testMat.setColor("Color", ColorRGBA.Red);
        
        TextureKey key2 = new TextureKey("Textures/Terrain/splat/road.jpg");
        key2.setGenerateMips(true);
        Texture tex2 = assetManager.loadTexture(key2);
        testMat.setTexture("ColorMap", tex2);

Sadly, the tutorial I was using uses a square, where texture coordinates are trivial and give me no idea how should it be done in case of a 3d shape with arbitrary number of vertices/faces

https://learnopengl.com/Getting-started/Textures

This may help you

I’ll have to read it twice more probably, but I’m starting to understand. If I want to create a texture that covers the entire island to make it look like a huge rock, I need to apply texture coordinates larger than 1 when individual faces are big, to avoid stretching them like an old sock. Plus I need to take into account which side of the triangle is “base” so that corresponding vertices would have horizontal coordinate for example ± 10, while the top part would have 0 to indicate center?

It’s not that easy to make an island with custom mesh and one ColorMap. The custom mesh tutorial is telling us the data structure of jme3, but it’s far far away from 3d modeling.

If your purpose is to creating terrain, I think it’s time to read Tutorial (10) - Hello Terrain
.

If you have more interest in procedure generating, maybe you can play with World Machine first.

That looks amazing but high poly. How can you use this?

You can store the details into normal map and output the hightmap to a low resolution map.

First of all, I’m trying to learn some basics - on the long run I want to be able to generate objects procedurally, and/or modify meshes with noise to make for example sculptures, decor or walls less repetitive without creating a ton of different versions in advance.

For this particular one, I want to generate small floating islands - the overall idea is that the islands float up, while the character falls down forcing the player to keep jumping from one island to another until they reach the goal, while junk falling down crashes onto the islands tipping them off balance, weighting them down to make them fall, etc.
I figured islands will look much better than flat platforms.

Finally, once I’m more comfortable with this, I’m planning to subdivide the mesh so that the base and the top can both be made more irregular

You are describing buoyancy, and that is quite complicated. I dont know if maybe using some kind of physics joint at the center of your island might be somewhere near what you want for tilting with weight. As for moving them up and down based on the total weight, I guess you’ll be able to calculate that based on the initial negative gravity vs the total weight of the objects on the island. Note that this is sounding quite complicated. I’m not sure how you’ll determine what is affecting it easily other than collision detection (is the object on my island?) - and that sounds expensive if your islands are quite large or the items are highly numeric and changing frequently.

My head hurts trying to figure this out. You’ve given yourself quite the task here for a beginner…

I was planning of simply assigning separate gravity vector for each island so they “fall” upwards, while everything else goes with default gravity. Rest resolved with collision detection to find objects that would weight the island down.
I’ve done some testing and seemed like collision of some object with platform (used as island replacement for now) does the trick, albeit the platform went pretty much heywire and floated away while spinning. I’ll have to determine if I can make it gentle enough to give player time to jump to another island, or if there should be some kind of warning about impeding object.

the weight of the island will determine whether or not it “spins” like a windmill or slowly like a huge stone. E.g. a small weight of a man will only affect a large rotatable mass relatively little when he/she jumps on it.