One of the most useful skills in creative coding is reading all kinds of data and files. And reading Wavefront .obj files in Processing is definitely one of the more fun applications. There are several libraries that will read .obj files but writing one for ourselves is a good exercise. Especially since we will have more control over what we want to do.
Wavefront .obj files are plain text files describing polygon meshes. The link above describes the entire format comprehensively. But that’s not what we need right now. Let’s start with an example, a simple pyramid (download).
The example file contains everything needed to draw the pyramid: the positions of the 5 corners, the vertices, and how the corners are connected, the faces. There are three different kinds of lines in this example file. The first type is a comment line, starting with #. The other two types describe the essential elements of a mesh: vertices and faces.
Lines starting with v give the position of a point on the mesh. The order in which the vertices are given defines an index. The first vertex index is “1”. Faces, the lines starting with f, are defined by the indices of the vertices they connect in some consistent order. Several faces can share the same vertex. Obj files support faces with 3 vertices, triangles, and faces with 4 vertices, quads. Most files will be triangle-based.
The pyramid file contains five vertices, one top vertex 1, and four base vertices, 2 to 5. The four sides of the pyramid connect the top “1” with each of the lines at the base, “2 3”, “3 4”, “4 5” and “5 1”. The bottom of the pyramid is a square “5 4 3 2”, in the file given as two triangles “5 4 3” and “5 3 2”. Don’t worry about the order, we’re reading files, not writing them (yet).
Reading .obj files in Processing
This reads the file but does nothing with it except
println(line). Handling each line requires us to do three things:
- Determine the type of the line
- If not a comment line, read the vertex or face data
- Add the vertex or face to our collection
Determining the type is a matter of looking at the first part of the line and see if it’s a vertex, a face or another type. Looking at the first character only wouldn’t be sufficient for arbitrary .obj files. The full spec includes the types “vn” and “vt”, which would be indistinguishable from “v”, but we’re not going to use those. Instead, we’re using Processing’s
splitTokens() functionality, cutting each line in parts separated by whitespace (reference).
How to store the vertices and the faces? Each vertex has three coordinates and a face has three or four indices. We create two small classes to hold the data.
Also, we don’t know beforehand how many there will be. We’ll be using two ArrayList to store the vertices and faces while the file is read. Reading the vertex information is straightforward. Each line “v x y z” has been split into 4 parts. We ignore the first part, “v”, and use
float() to convert the text string to a floating point number (reference).
The face data requires an additional step. In our example file, all faces are defined as “f index1 index2 index3”. In general, a face line in an .obj file can hold three indices, “vertexIndex1/textureIndex1/normalIndex1”. We only need the first one.
splitTokens() to the rescue, this time using “/” to split each part of the string into further chunks.
Additionally, we need to correct the vertex index we read from the file. The first index in a Wavefront .obj file is 1. This intuitive, daily life way of counting is called one-based indexing. That vertex is the first that will be added to the ArrayList. Processing uses zero-based indexing, the first element is at index 0. This is very sensible from a computer science point-of-view. To retrieve that first element, we write
vertices.get(0). As a consequence, if the file refers to index 1254, that vertex is stored at ArrayList index 1253. This invites such a common type of error that it has it’s own Wikipedia entry. To compensate for the different indexing schemes, we subtract 1 from every index we read from the file.
Putting it all together
Last task: get Processing to draw the faces of the mesh we loaded. For convenience, we add drawing functions to Vertex en Face. Putting everything together (download):