[SOLVED] Flipped texture UV's on my custom Path mesh

Hi all,
I am building a Path mesh creator tool into my new scene editor which gives the user of the tool the ability to construct a path of any sorts, such as roads/rivers/etc.

I got the Path mesh to work kindof except when I slapped a texture onto it.
Now it looks like every second quad with a set of 2 triangles gets it’s UV’s flipped.

Like this screenshot:

Here is the code with a test, if there is anyone willing to help me, please?

Path class:

public class Path extends Mesh {

    private float width;
    private List<Spatial> controlPoints;

    private int[] vertexIndices;
    private Vector3f[] vertexPositions;
    private Vector3f[] normalPositions;
    private Vector2f[] vertexTexCoordinates;

    /**
     * Serialization only. Do not use.
     */
    public Path() {
    }

    /**
     * Create a path from a list of spatial points. The spatial's position,
     * rotation and scale is then used to create the path strip.
     *
     * @param points
     */
    public Path(List<Spatial> points) {

        this.width = 1;
        this.controlPoints = points;
        this.generateVertices();
        this.generateIndices();
        this.buildMesh();

    }

    public void rebuild() {
        this.generateVertices();
        this.generateIndices();
        buildMesh();
    }

    private void buildMesh() {
        setMode(Mode.Triangles);
        setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(vertexIndices));
        setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(vertexPositions));
        setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(vertexTexCoordinates));
        setBuffer(VertexBuffer.Type.Normal, 3, BufferUtils.createFloatBuffer(normalPositions));
        updateCounts();
        updateBound();
        setStatic();
    }

    private void generateIndices() {
        // Set the index buffer for the mesh (each pair of vertices defines a quad)
        vertexIndices = new int[(controlPoints.size() - 1) * 6];

        for (int i = 0; i < controlPoints.size() - 1; i++) {
            if (i % 2 == 0) {
                vertexIndices[i * 6 + 0] = i * 2 + 0;
                vertexIndices[i * 6 + 1] = i * 2 + 1;
                vertexIndices[i * 6 + 2] = i * 2 + 2;
                vertexIndices[i * 6 + 3] = i * 2 + 1;
                vertexIndices[i * 6 + 4] = i * 2 + 3;
                vertexIndices[i * 6 + 5] = i * 2 + 2;

            } else {
                //Need to flip it
                //NB: TODO: STRUGGLING WITH THIS PIECE OF CODE.
                //CHANGING THE INDICES DOES NOT SEEM TO WORK.
                vertexIndices[i * 6 + 0] = i * 2 + 0;
                vertexIndices[i * 6 + 1] = i * 2 + 1;
                vertexIndices[i * 6 + 2] = i * 2 + 2;
                vertexIndices[i * 6 + 3] = i * 2 + 1;
                vertexIndices[i * 6 + 4] = i * 2 + 3;
                vertexIndices[i * 6 + 5] = i * 2 + 2;
            }

        }

    }

    private void generateVertices() {

        this.vertexPositions = new Vector3f[controlPoints.size() * 2];
        this.normalPositions = new Vector3f[controlPoints.size() * 2];
        this.vertexTexCoordinates = new Vector2f[controlPoints.size() * 2];

        Spatial current = null;
        Vector3f left = null;
        Vector3f right = null;

        for (int i = 0; i < controlPoints.size(); i++) {
            // Calculate the position of the point along the road            
            current = controlPoints.get(i);
            width = current.getLocalScale().x;

            left = new Vector3f(-width * 0.5f, 0, 0);
            left = current.getLocalRotation().mult(left);
            left = current.getLocalTranslation().add(left);

            right = new Vector3f(width * 0.5f, 0, 0);
            right = current.getLocalRotation().mult(right);
            right = current.getLocalTranslation().add(right);

            vertexPositions[i * 2] = new Vector3f(left.x, left.y, left.z);
            vertexPositions[i * 2 + 1] = new Vector3f(right.x, right.y, right.z);

            // Calculate the UV coordinates for the left and right vertices
            if (i % 2 == 0) {
                vertexTexCoordinates[i * 2] = new Vector2f(0, 0);
                vertexTexCoordinates[i * 2 + 1] = new Vector2f(1, 0);

            } else {
                vertexTexCoordinates[i * 2] = new Vector2f(0, 1);
                vertexTexCoordinates[i * 2 + 1] = new Vector2f(1, 1);

            }

            normalPositions[i * 2] = new Vector3f(0, 1, 0);
            normalPositions[i * 2 + 1] = new Vector3f(0, 1, 0);

        }

    }

    public List<Spatial> getControlPoints() {
        return controlPoints;
    }

    public void setControlPoints(List<Spatial> controlPoints) {
        this.controlPoints = controlPoints;
    }

    @Override
    public void read(JmeImporter importer) throws IOException {
        super.read(importer);
        InputCapsule capsule = importer.getCapsule(this);
        controlPoints = capsule.readSavableArrayList("controlPoints", null);

        this.generateVertices();
        this.generateIndices();
        this.buildMesh();

    }

    @Override
    public void write(JmeExporter e) throws IOException {
        super.write(e);
        e.getCapsule(this).writeSavableArrayList(new ArrayList(controlPoints), "controlPoints", null);
    }
}

And a test SimpleApplication. Just add your own texture.

public class TestRoad extends SimpleApplication {

    private Path path;
    private Geometry pathGeom;

    public static void main(String[] args) {

        TestRoad app = new TestRoad();
        AppSettings settings = new AppSettings(true);
        settings.setWidth(1280);
        settings.setHeight(720);
        app.setSettings(settings);
        app.start(JmeContext.Type.Display);

    }

    @Override
    public void simpleInitApp() {
        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
        loadScene();
        loadCamera();

    }

    private void loadScene() {
        List<Spatial> points = new ArrayList<>();

        for (int i = 0; i < 5; i++) {
            Node point = new Node("point");
            point.setLocalTranslation(0, 0, -i);
            points.add(point);
                        
        }
        
        path = new Path(points);
        pathGeom = new Geometry("path", path);
        addMaterial(pathGeom, ColorRGBA.White, "Textures/proto.png");
        
        rootNode.attachChild(pathGeom);

    }

    private void loadCamera() {
        cam.setLocation(new Vector3f(-3, 3, 2));
        cam.lookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y);
        flyCam.setMoveSpeed(10);
    }

    private Material addMaterial(Spatial spatial, ColorRGBA colorRGBA, String textureStr) {
        Material material = null;
        material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        material.setColor("Color", colorRGBA);
        
        Texture texture = assetManager.loadTexture(textureStr);
        texture.setWrap(Texture.WrapMode.Repeat);
        material.setTexture("ColorMap", texture);
        spatial.setMaterial(material);
        return material;
    }

}

Based on your comment here:

It sounds like your ordering of the indices is correct, so if the texture is still appearing flipped then I think you’d want to adjust the code where you are assigning the uv coordinates to the texCoord buffer here:

I think doing something like this might fix the issue, since texCoords can go above 1.0 and it will treat a texCoord value of 1.25 as if its wrapped back back at 0.25.

            int xCoord = i / 2; //important that this is basic int math so it doesn't round 1/2 up to 1
            vertexTexCoordinates[i + 0] = new Vector2f(xCoord , 0);
            vertexTexCoordinates[i + 1] = new Vector2f(xCoord + 1, 0);

            vertexTexCoordinates[i + 2] = new Vector2f(xCoord , 1);
            vertexTexCoordinates[i + 3] = new Vector2f(xCoord  + 1, 1);

        }

The way you were doing it was oscillating the length component (i think the y value in your case) of the tex coords from 0 > 1 > 0 > 1 for each block, and when it goes from 1 back to 0 on every other block, that is where you are getting the flipping.

You really want the length component of your texCoords to be ordered 0-1-2-3-4 so it wraps smoothly and is never going in reverse order. And if you don’t like having texCoord values over the 0-1 range you can do a remainder to get it back in that range of 0-1.

But I did not get a chance to copy your code and test it myself since I’m just about to go to bed, so my apologies if I’m overlooking a different source of the issue or if am not 100% accurate with my solution, but I hope that at least points you in the right direction.

Thanks @yaRnMcDonuts ,
It did help me in the correct direction.
Here is the Path code for my final solution.

public class Path extends Mesh {

    private float width;
    private List<Spatial> controlPoints;

    private int[] vertexIndices;
    private Vector3f[] vertexPositions;
    private Vector3f[] normalPositions;
    private Vector2f[] vertexTexCoordinates;

    /**
     * Serialization only. Do not use.
     */
    public Path() {
    }

    /**
     * Create a path from a list of spatial points. The spatial's position,
     * rotation and scale is then used to create the path strip.
     *
     * @param points
     */
    public Path(List<Spatial> points) {

        this.width = 1;
        this.controlPoints = points;
        this.generateVertices();
        this.generateIndices();
        this.buildMesh();

    }

    public void rebuild() {
        this.generateVertices();
        this.generateIndices();
        buildMesh();
    }

    private void buildMesh() {
        setMode(Mode.Triangles);
        setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(vertexIndices));
        setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(vertexPositions));
        setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(vertexTexCoordinates));
        setBuffer(VertexBuffer.Type.Normal, 3, BufferUtils.createFloatBuffer(normalPositions));
        updateCounts();
        updateBound();
        setStatic();
    }

    private void generateIndices() {
        // Set the index buffer for the mesh (each pair of vertices defines a quad)
        vertexIndices = new int[(controlPoints.size() - 1) * 6];

        for (int i = 0; i < controlPoints.size() - 1; i++) {
            if (i % 2 == 0) {
                vertexIndices[i * 6 + 0] = i * 2 + 0;
                vertexIndices[i * 6 + 1] = i * 2 + 1;
                vertexIndices[i * 6 + 2] = i * 2 + 2;
                vertexIndices[i * 6 + 3] = i * 2 + 1;
                vertexIndices[i * 6 + 4] = i * 2 + 3;
                vertexIndices[i * 6 + 5] = i * 2 + 2;

            } else {
                //Need to flip it
                //NB: TODO: STRUGGLING WITH THIS PIECE OF CODE.
                //CHANGING THE INDICES DOES NOT SEEM TO WORK.
                vertexIndices[i * 6 + 0] = i * 2 + 0;
                vertexIndices[i * 6 + 1] = i * 2 + 1;
                vertexIndices[i * 6 + 2] = i * 2 + 2;
                vertexIndices[i * 6 + 3] = i * 2 + 1;
                vertexIndices[i * 6 + 4] = i * 2 + 3;
                vertexIndices[i * 6 + 5] = i * 2 + 2;
            }

        }

    }

    private void generateVertices() {

        this.vertexPositions = new Vector3f[controlPoints.size() * 2];
        this.normalPositions = new Vector3f[controlPoints.size() * 2];
        this.vertexTexCoordinates = new Vector2f[controlPoints.size() * 2];

        Spatial current = null;
        Vector3f left = null;
        Vector3f right = null;

        for (int i = 0; i < controlPoints.size(); i++) {
            // Calculate the position of the point along the road            
            current = controlPoints.get(i);
            width = current.getLocalScale().x;

            left = new Vector3f(-width * 0.5f, 0, 0);
            left = current.getLocalRotation().mult(left);
            left = current.getLocalTranslation().add(left);

            right = new Vector3f(width * 0.5f, 0, 0);
            right = current.getLocalRotation().mult(right);
            right = current.getLocalTranslation().add(right);

            vertexPositions[i * 2] = new Vector3f(left.x, left.y, left.z);
            vertexPositions[i * 2 + 1] = new Vector3f(right.x, right.y, right.z);

            // Calculate the UV coordinates for the left and right vertices
            int xCoord = i;
            
            if (i % 2 == 0) {
                System.out.println("xCoord: " + xCoord);
                vertexTexCoordinates[i * 2] = new Vector2f(0, xCoord);
                vertexTexCoordinates[i * 2 + 1] = new Vector2f(1, xCoord);

            } else {
                System.out.println("xCoord2: " + xCoord);
                vertexTexCoordinates[i * 2] = new Vector2f(0, xCoord);
                vertexTexCoordinates[i * 2 + 1] = new Vector2f(1, xCoord);

            }

            normalPositions[i * 2] = new Vector3f(0, 1, 0);
            normalPositions[i * 2 + 1] = new Vector3f(0, 1, 0);

        }

    }

    public List<Spatial> getControlPoints() {
        return controlPoints;
    }

    public void setControlPoints(List<Spatial> controlPoints) {
        this.controlPoints = controlPoints;
    }

    @Override
    public void read(JmeImporter importer) throws IOException {
        super.read(importer);
        InputCapsule capsule = importer.getCapsule(this);
        controlPoints = capsule.readSavableArrayList("controlPoints", null);

        this.generateVertices();
        this.generateIndices();
        this.buildMesh();

    }

    @Override
    public void write(JmeExporter e) throws IOException {
        super.write(e);
        e.getCapsule(this).writeSavableArrayList(new ArrayList(controlPoints), "controlPoints", null);
    }
}

Now I am abled to add things like roads and rivers.

5 Likes