I have written a simple wavefront parses for Java, which is displayed in the following code section:
public class OBJLoader {
public static GameEntity loadObjModel(String fileName, String texturePath) throws Exception {
double start = System.nanoTime();
List<Vector3f> vertices = null;
List<Vector2f> textures = null;
List<Vector3f> normals = null;
List<Integer> indices = null;
String line;
float[] vertexPosArray = null;
float[] texturesArray = null;
float[] normalsArray = null;
int[] indicesArray = null;
try {
FileReader fr = new FileReader(new File("resources/models/" + fileName + ".obj"));
BufferedReader br = new BufferedReader(fr);
vertices = new ArrayList<>();
textures = new ArrayList<>();
normals = new ArrayList<>();
indices = new ArrayList<>();
//read v, vt and vn
while((line = br.readLine()) != null) {
line = br.readLine();
if (line != null || !line.equals("") || !line.startsWith("#")) {
String[] splitLine = line.split(" ");
switch(splitLine[0]) {
case "v":
Vector3f vertex = new Vector3f(Float.parseFloat(splitLine[1]), Float.parseFloat(splitLine[2]), Float.parseFloat(splitLine[3]));
vertices.add(vertex);
System.out.println("[OBJLoader.loadObjModel]: Vertex " + vertex.toString() + " has been added to vertices from " + fileName);
break;
case "vt":
Vector2f texture = new Vector2f(Float.parseFloat(splitLine[1]), Float.parseFloat(splitLine[2]));
textures.add(texture);
System.out.println("[OBJLoader.loadObjModel]: Texture coordinate [" + texture.x + ", " + texture.y + "] has been added to textures from " + fileName);
break;
case "vn":
Vector3f normal = new Vector3f(Float.parseFloat(splitLine[1]), Float.parseFloat(splitLine[2]), Float.parseFloat(splitLine[3]));
normals.add(normal);
System.out.println("OBJLoader.loadObjModel]: Normal " + normal + " has been added to normals from " + fileName);
break;
}
}
}
int numVertices = vertices.size();
texturesArray = new float[numVertices*2];
normalsArray = new float[numVertices*3];
//read f
while((line = br.readLine()) != null && line.startsWith("f")) {
String[] split = line.split(" ");
String[] v1 = split[1].split("/");
String[] v2 = split[2].split("/");
String[] v3 = split[3].split("/");
processVertex(v1, indices, textures, normals, texturesArray, normalsArray);
processVertex(v2, indices, textures, normals, texturesArray, normalsArray);
processVertex(v3, indices, textures, normals, texturesArray, normalsArray);
line = br.readLine();
}
br.close();
} catch (Exception e) {
System.err.print("[OBJLoader.loadObjModel]: Error loading obj model!");
e.printStackTrace();
}
vertexPosArray = new float[vertices.size()*3];
indicesArray = new int[indices.size()];
int i = 0;
for(Vector3f vertex : vertices) {
vertexPosArray[i++] = vertex.x;
vertexPosArray[i++] = vertex.y;
vertexPosArray[i++] = vertex.z;
}
for(int j = 0; j<indices.size(); j++) {
indicesArray[i] = indices.get(i);
}
double end = System.nanoTime();
double delta = (end - start) / 1000_000;
System.out.println("[OBJLoader.loadObjModel]: Vertices array of " + fileName + ": ");
ArrayUtils.displayFloatArray(vertexPosArray);
System.out.println("[OBJLoader.loadObjModel]: It took " + delta + " milliseconds to load " + fileName);
return new GameEntity(vertexPosArray, indicesArray, texturesArray, texturePath);
}
/**
* The input to this method is vertex data as a String array, which is used to determine how to
* arrange texture coordinate and normal vector data (this data is associated with each vertex position)
* into the correct order in the texture and normals array
* @param vertexData
* @param indices
* @param textrues
* @param normals
* @param textureArray
* @param normalsArray
*/
private static void processVertex(String[] vertexData, List<Integer> indices, List<Vector2f> textures,
List<Vector3f> normals, float[] textureArray, float[] normalsArray) {
int currentVertexPointer = Integer.parseInt(vertexData[0]) - 1;
indices.add(currentVertexPointer);
Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
textureArray[currentVertexPointer*2] = currentTex.x;
textureArray[currentVertexPointer*2 + 1] = 1 - currentTex.y;
Vector3f currentNorm = normals.get(Integer.parseInt(vertexData[2]) - 1);
normalsArray[currentVertexPointer*3] = currentNorm.x;
normalsArray[currentVertexPointer*3 + 1] = currentNorm.y;
normalsArray[currentVertexPointer*3 + 2] = currentNorm.z;
}
}
This is the wavefront obj file that I am trying to read (it represents a cube):
# Blender v2.78 (sub 0) OBJ File: 'cube.blend'
# www.blender.org
o Cube
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 0.2766 0.2633
vt 0.5000 0.4867
vt 0.2766 0.4867
vt 0.7234 0.4867
vt 0.9467 0.2633
vt 0.9467 0.4867
vt 0.0533 0.4867
vt 0.0533 0.2633
vt 0.2766 0.0400
vt 0.5000 0.2633
vt 0.0533 0.7100
vt 0.7234 0.2633
vt 0.0533 0.0400
vt 0.2766 0.7100
vn 0.0000 -1.0000 0.0000
vn 0.0000 1.0000 0.0000
vn 1.0000 -0.0000 0.0000
vn 0.0000 -0.0000 1.0000
vn -1.0000 -0.0000 -0.0000
vn 0.0000 0.0000 -1.0000
s off
f 2/1/1 4/2/1 1/3/1
f 8/4/2 6/5/2 5/6/2
f 5/7/3 2/1/3 1/3/3
f 6/8/4 3/9/4 2/1/4
f 3/10/5 8/4/5 4/2/5
f 1/3/6 8/11/6 5/7/6
f 2/1/1 3/10/1 4/2/1
f 8/4/2 7/12/2 6/5/2
f 5/7/3 6/8/3 2/1/3
f 6/8/4 7/13/4 3/9/4
f 3/10/5 7/12/5 8/4/5
f 1/3/6 4/14/6 8/11/6
This obj parser is supposed to read the contents of an obj file, which is accessed by resources/models/MODELFILENAME.obj file path.
First, it creates a new FileReader based on the file path and then uses that FileReader instance to create a BufferedReader instance, which is used to read each line of the specified file.
Then comes the while loop. If the end of the file is reached, then the while loop is finished (String variable line, which is read by the BufferedReader instance gets the value null at the end of the file).
The while loop goes like this:
line = br.readLine();
if (line != null || !line.equals("") || !line.startsWith("#"))
String[] splitLine = line.split(" ");
splitLine
(which is either v
, vt
, vn
or f
): switch(splitLine[0])
If the line begins with v
, then create a new Vector3f object (a 3 dimensional vector, its constructor is Vector3f(float x, float y, float z)
) out of the numbers which are behind v
. These numbers are accessed as the first, second and third index in the splitLine String array due to how space characters are defined in the .obj file. Prior to being sent to the constructor, they are parsed from Strings to floats. Then at the end, print the string representation of the Vector3f to console
Similarly, the process is repeated for texture coordinates (just that instead of creating a Vector3f, a Vector2f is created) and the normal vectors.
There is no point in explaining the remaining source code, since the problem occurs here already.
The above file parser seems to read only half the data (it "jumps" every second line and fails to process it).
This is the output which shows which lines, beginning with v
have been processed (ie. Vector3f's made out of their data):
[OBJLoader.loadObjModel]: Vertex 1.0 -1.0 -1.0 has been added to vertices from cube
[OBJLoader.loadObjModel]: Vertex -1.0 -1.0 1.0 has been added to vertices from cube
[OBJLoader.loadObjModel]: Vertex 1.0 1.0 -0.999999 has been added to vertices from cube
[OBJLoader.loadObjModel]: Vertex -1.0 1.0 1.0 has been added to vertices from cube
This is the data for v
's in the obj file:
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
If you compare the data it soon becomes obvious that only 4 lines get processed instead of 8. The first line is read, the second is skipped. The third line is read, the fourth is skipped. And so it goes on. Why is the problem occurring? Are there any mistakes in the switch statement which cause the lines to be skipped?
Thank you for the time taken.
The problem is caused by this:
while((line = br.readLine()) != null) {
line = br.readLine();
You are already reading one line of the file with br.readLine()
and a second time as soon as you read the line again in the loop. Just delete the second line = br.readLine();
and you should be fine.
As addition, you do not need to check for if (line != null ||
as this is already checked be the loop condition.