Search code examples
javaopengllwjglwavefront

OBJ loading - weird normals / uvs


So, I'm trying to load (Wavefront) OBJ models in Java. Currently it loads vertex positions properly but texture coords are messed up:

This is what I see in the engine:

enter image description here

This is what I see in blender:

enter image description here

My current loading code is here:

 private void loadOBJ(String filename)
    {
    ArrayList<Vector3f> verts = new ArrayList<Vector3f>();
    ArrayList<Vector3f> norms = new ArrayList<Vector3f>();
    ArrayList<Vector2f> uvs = new ArrayList<Vector2f>();
    ArrayList<Integer> ints = new ArrayList<Integer>(); //Indices
    ArrayList<Integer> nints = new ArrayList<Integer>(); //Normal indices
    ArrayList<Integer> tints = new ArrayList<Integer>(); //Texture coord indices


    try {
        BufferedReader reader = new BufferedReader(new FileReader(filename));
        String line;

        while ((line = reader.readLine()) != null) {
            String[] tokens = line.split(" ");


            if(tokens[0].startsWith("vn"))
            {
                norms.add(new Vector3f(Float.parseFloat(tokens[1]), 
                                       Float.parseFloat(tokens[2]), 
                                       Float.parseFloat(tokens[3])));   
            }
            else if(tokens[0].startsWith("vt"))
            {
                uvs.add(new Vector2f(Float.parseFloat(tokens[1]), 
                                     Float.parseFloat(tokens[2])));
            }
            else if(tokens[0].startsWith("v")) 
            {
                verts.add(new Vector3f(Float.parseFloat(tokens[1]), 
                                       Float.parseFloat(tokens[2]), 
                                       Float.parseFloat(tokens[3])));
            }
            else if(tokens[0].startsWith("f"))
            {
                ints.add(Integer.parseInt(tokens[1].split("/")[0]) - 1);
                ints.add(Integer.parseInt(tokens[2].split("/")[0]) - 1);
                ints.add(Integer.parseInt(tokens[3].split("/")[0]) - 1);

                tints.add(Integer.parseInt(tokens[1].split("/")[1]) - 1);
                tints.add(Integer.parseInt(tokens[2].split("/")[1]) - 1);
                tints.add(Integer.parseInt(tokens[3].split("/")[1]) - 1);

                nints.add(Integer.parseInt(tokens[1].split("/")[2]) - 1);
                nints.add(Integer.parseInt(tokens[2].split("/")[2]) - 1);
                nints.add(Integer.parseInt(tokens[3].split("/")[2]) - 1);

                if(tokens.length > 4) //For quads
                {
                    ints.add(Integer.parseInt(tokens[1].split("/")[0]) - 1);
                    ints.add(Integer.parseInt(tokens[4].split("/")[0]) - 1);
                    ints.add(Integer.parseInt(tokens[2].split("/")[0]) - 1);

                    tints.add(Integer.parseInt(tokens[1].split("/")[1]) - 1);
                    tints.add(Integer.parseInt(tokens[4].split("/")[1]) - 1);
                    tints.add(Integer.parseInt(tokens[2].split("/")[1]) - 1);

                    nints.add(Integer.parseInt(tokens[1].split("/")[2]) - 1);
                    nints.add(Integer.parseInt(tokens[4].split("/")[2]) - 1);
                    nints.add(Integer.parseInt(tokens[2].split("/")[2]) - 1);
                }
            }

        }
        reader.close();


    } catch (IOException e) {
        System.err.println("Could not read file.");
        e.printStackTrace();
        System.exit(-1);
    }

    //Now convert the loaded data to internal format: VertexData[] that con tains positions, uvs, and normals, and int[] that has indices

    vertices = new VertexData[verts.size()];
    indices = new int[ints.size()];

    for(int i = 0; i < ints.size(); i++)
    {
        indices[i] = ints.get(i);
        int j = ints.get(i);
        int k = tints.get(i);
        int q = nints.get(i);

        vertices[j] = new VertexData();

        vertices[j].setXYZ(verts.get(j).x, verts.get(j).y, verts.get(j).z);
        vertices[j].setST(uvs.get(k).x, uvs.get(k).y);
        vertices[j].setNormal(norms.get(q).x, norms.get(q).y, norms.get(q).z);
    }

}

As you can see from the pictures current code failes to properly load uvs, but I can't figure out whats wrong. Help?


Solution

  • Your code does not correctly generate the vertex data suitable for the GL. As you know, in the obj format, there is an separate array for vertex position, normals, texcoords and so on. And faces are formed by independetly indexing into them. In the GL, a vertex is the set of all of its attributes, and you can't use separate indices per attribute (at least not directly; modern GL is flexible enough to allow you to implement that level of indirection in the vertex shader, but that would be another story).

    Your code maybe attempts at fixing this, but the solution is wrong. You simply use the vertex index from the OBJ file as the global index, and write your vertex containing all of the attributes using the other indices no matter, if the same vertex index might be used with a different combination of normals or texcoords - you simply overwrite it with the last such combination.

    THe coorect approach would creating a separate vertex for each unique (vertex,normal,texcoord) combination that occurs. As a result, the number of vertices will of course increase, and the index value can't be directly reused for the element array, either.