0
\$\begingroup\$

Edit

I've rewritten the loader to duplicate data if only one element (v, vt, vn) is different. I get a much better result, but it is still incorrect. The seams on the mesh match what they should look like, but the texture appears a bit deformed. I've checked a list of the texture coords, and there appear to be some missing, but I don't understand how this could happen.

Here is the new code:

int bytesLoaded = 0;
    
    ArrayList<Float> vertices = new ArrayList<>();
    ArrayList<Float> textureCoords = new ArrayList<>();
    ArrayList<Float> normals = new ArrayList<>();
    
    ArrayList<Float> plainVertices = new ArrayList<>();
    ArrayList<Float> plainTextureCoords = new ArrayList<>();
    ArrayList<Float> plainNormals = new ArrayList<>();
    
    ArrayList<Integer> indices = new ArrayList<>();
    ArrayList<String> writtenData = new ArrayList<>();
    
    String line;
    
    try {
        InputStream stream = IO.class.getClassLoader().getResourceAsStream(filePath); //get file off classpath
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); //create file reader
        
        while((line = reader.readLine())!=null) { //repeat until document's end has been reached
            String[] data = line.split(" "); //split line into each data point
            
            if (line.startsWith("v ")) { //add vertices in the 3 data points, excluding the first which is the "v"
                for (int i=1; i<4; i++) {
                    plainVertices.add(Float.parseFloat(data[i]));
                }
                
            } else if (line.startsWith("vt ")) {
                for (int i=1; i<3; i++) {
                    plainTextureCoords.add(Float.parseFloat(data[i])); //add vertex textures in the 2 data points, excluding the first which is the "vt"
                }
                
            } else if (line.startsWith("vn ")) {
                for (int i=1; i<4; i++) {
                    plainNormals.add(Float.parseFloat(data[i])); //add vertices in the 3 data points, excluding the first which is the "vn"
                }
                
            } else if (line.startsWith("f ")) { //indices
                for (int i = 1; i<4; i++) {
                    String[] faceIndices = data[i].split("/"); //split each face into v indice, vt indice and vn indice
                    
                    int vertexIndex = Integer.parseInt(faceIndices[0]) - 1; //get the indices, subtract 1 because OpenGL indices are 0 indexed
                    int textureCoordIndex = Integer.parseInt(faceIndices[1]) - 1;
                    int normalIndex = Integer.parseInt(faceIndices[2]) - 1;
                    
                    StringBuilder tokenBuilder = new StringBuilder(); //generate a unique token to prevent repeats
                    tokenBuilder.append(vertexIndex);
                    tokenBuilder.append("+");
                    tokenBuilder.append(textureCoordIndex);
                    tokenBuilder.append("+");
                    tokenBuilder.append(normalIndex);

                    String token = tokenBuilder.toString(); 
                    System.out.println(token);
                    
                    if (!writtenData.contains(token)) { //add the 3 indices' values to their list
                        writtenData.add(token);
                        
                        vertices.add(plainVertices.get(vertexIndex * 3));
                        vertices.add(plainVertices.get(vertexIndex * 3 + 1));
                        vertices.add(plainVertices.get(vertexIndex * 3 + 2));
                        
                        textureCoords.add(plainTextureCoords.get(textureCoordIndex * 2));
                        textureCoords.add(plainTextureCoords.get(textureCoordIndex * 2 + 1));
                        
                        normals.add(plainNormals.get(normalIndex * 3));
                        normals.add(plainNormals.get(normalIndex * 3 + 1));
                        normals.add(plainNormals.get(normalIndex * 3 + 2));
                    
                    }
                    
                    indices.add(writtenData.indexOf(token)); //generate an index
                }
            }
            bytesLoaded = bytesLoaded + line.getBytes().length; //keep track of bytes loaded
            //System.out.println(bytesLoaded);
        }

Original

I'm trying to create an OBJ file loader for my 3D game library (with Java and LWJGL), but having issues with loading texture coordinates (and normals too but I assume they'll apply the same as texture coords). I should get a Suzanne head looking like this, but instead looks like this.

Here's the code:

int bytesLoaded = 0;
ArrayList<Float> vertices = new ArrayList<>();
ArrayList<Float> textureCoords = new ArrayList<>();
ArrayList<Float> normals = new ArrayList<>();

ArrayList<Float> plainTextureCoords = new ArrayList<>();
ArrayList<Float> plainNormals = new ArrayList<>();

ArrayList<Integer> vertexIndices = new ArrayList<>();
ArrayList<Integer> textureCoordIndices = new ArrayList<>();
ArrayList<Integer> normalIndices = new ArrayList<>();

ArrayList<String> textureCoordsAdded = new ArrayList<>();
ArrayList<String> normalsAdded = new ArrayList<>();

String line;
try {
    InputStream stream = IO.class.getClassLoader().getResourceAsStream(filePath); //get file off classpath
    BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); //create file reader
    while((line = reader.readLine())!=null) { //repeat until document's end has been reached
        String[] data = line.split(" "); //split line into each data point
        
        textureCoords.add(0.0f); //filler
        normals.add(0.0f);
        
        if (line.startsWith("v ")) { //add vertices in the 3 data points, exculding the first which is the "v"
            for (int i=1; i<4; i++) {
                vertices.add(Float.parseFloat(data[i]));
            }
            
        } else if (line.startsWith("vt ")) {
            for (int i=1; i<3; i++) {
                plainTextureCoords.add(Float.parseFloat(data[i])); //add vertex textures in the 2 data points, excluding the first which is the "vt"
            }
            
        } else if (line.startsWith("vn ")) {
            for (int i=1; i<4; i++) {
                plainNormals.add(Float.parseFloat(data[i])); //add vertices in the 3 data points, excluding the first which is the "vn"
            }
            
        } else if (line.startsWith("f ")) { //indices
            for (int i = 1; i<4; i++) {
                String[] faceIndices = data[i].split("/"); //split each face into v indice, vt indice and vn indice
                for (int j = 0; j<3; j++) {
                    switch (j){
                    case 0:
                        vertexIndices.add(Integer.parseInt(faceIndices[j]) - 1); //minus one as OpenGL indices are 0 indexed, not 1
                        break;
                    case 1:
                        textureCoordIndices.add(Integer.parseInt(faceIndices[j]));
                        break;
                    case 2:
                        normalIndices.add(Integer.parseInt(faceIndices[j]));
                        break;
                    }
                }
            }
        }
        bytesLoaded = bytesLoaded + line.getBytes().length; //just keep track of bytes loaded
    }
    
    for (int i = 0; i<textureCoordIndices.size(); i++) { //process texture coords
        int index = textureCoordIndices.get(i),
                pos =  index * 2 - 2; //times 2 to get the location (2 vt's) and minus 2 to be 0 indexed
            float targetCoord1 = plainTextureCoords.get(pos), //get the texture coords
            targetCoord2 = plainTextureCoords.get(pos + 1);
            String combined = String.valueOf(targetCoord1).concat("+").concat(String.valueOf(targetCoord2)); //Generate an 'ID' for the 2 vertex coords
                if (!textureCoordsAdded.contains(combined)) { //Check if that ID has already been processed
                    textureCoordsAdded.add(combined); //Add that ID to the list of processed indices
                    
                    int controlIndice = vertexIndices.get(pos); //Align the position of the texture coord with the vertices indices (OpenGL only uses vertex indices)
                    textureCoords.add(controlIndice, targetCoord1); //Add the texture coords to the correct position
                    textureCoords.add(controlIndice + 1, targetCoord2);
                }
        }
    
    for (int i = 0; i<normalIndices.size(); i++) { //process normals
        int index = normalIndices.get(i),
                pos =  index * 3 - 3; //times 3 to get the location 32 vn's) and minus 3 to be 0 indexed
                    float targetNorm1 = plainNormals.get(pos), //get the normals
                    targetNorm2 = plainNormals.get(pos + 1),
                    targetNorm3 = plainNormals.get(pos + 2);
                String combined = String.valueOf(targetNorm1).concat("+").concat(String.valueOf(targetNorm2).concat("+").concat(String.valueOf(targetNorm3))); //Generate an 'ID' for the 3 vertex normals
            if (!normalsAdded.contains(combined)) { //Check if that ID has already been processed
                    normalsAdded.add(combined);//Add that ID to the list of processed indices
                
                    int controlIndice = vertexIndices.get(pos);  //Align the position of the normals with the vertices indices (OpenGL only uses vertex indices)
                    normals.add(controlIndice, targetNorm1);
                    normals.add(controlIndice + 1, targetNorm2); //Add the normals to the correct position
                    normals.add(controlIndice + 2, targetNorm3);

            }
    } 
} catch (IOException|NullPointerException e) {
    System.err.println("File " + filePath + " not found!");
    Manager.terminate();
}

I think this might be to do with my understanding of the obj file format, rather than the implementation, but I cannot be sure.

\$\endgroup\$
6
  • \$\begingroup\$ how have you tried to troubleshoot so far? \$\endgroup\$ Commented Jul 27, 2021 at 10:30
  • \$\begingroup\$ @user253751 I’ve had a look at the texture coords list, but the order of the values appears to be gargled, so I definitely think this is to with my implementation and understanding rather than a bug. \$\endgroup\$ Commented Jul 28, 2021 at 15:43
  • \$\begingroup\$ Can you explain how the texture coords list should be built? I'm not quite sure why your code is written the way it is. I don't understand the purpose of controlIndice for example. \$\endgroup\$ Commented Jul 28, 2021 at 15:49
  • \$\begingroup\$ @user253751 Sorry, the naming of my variables are terrible and my code is a bit unclear. Anyways… OpenGL use 1 set of indices to index vertices (in my case I use vertex normals as the ‘master’ indices), texturecords and normals. I need to place the texture coordinates so the vertex indices refer to them in the same way the texture normals refer to them. Sorry, I’m terrible at explaining, but control indice is the final destination for the texture coords in reference to this. \$\endgroup\$ Commented Jul 28, 2021 at 17:13
  • \$\begingroup\$ It seems like you're assuming that if the position is the same, then the texture coordinate is also the same. Hence you only use one texture coordinate for each position. Is that correct? \$\endgroup\$ Commented Jul 28, 2021 at 17:15

0

You must log in to answer this question.

Browse other questions tagged .