Obj Mesh Importer

Today I am sharing a bit of code I use to read very basic vertex and texture coordinate information from an obj file. The .obj format is explained in detail here. It is pretty well supported across most 3D-modeling software and is currently my go-to format for 3D mesh information.

In order to keep the vertex information file format agnostic (in case you decide to add other file formats later), I designed an intermediate layer of abstraction between the .obj file and OpenGL.

typedef struct {
	GLfloat* pos;   // (x,y,z)
	GLfloat* tex;   // (u,v)
	GLfloat* norm;  // (x,y,z)
	GLsizeiptr num; // total num of verts
} VertInfo;

typedef struct {
	GLuint* pos;    // 3 pos inds per
	GLuint* tex;    // 3 tex inds per
	GLuint* norm;   // 3 norm inds per
	GLsizeiptr num; // num of triangles
} IndInfo;

This works well for my current needs but there are a few simplifications I have made about the obj. An obj file may contain an optional coordinate (x, y, z [,w]) but I am ignoring that it might exist. Another simplification is that the texture coordinates have two variables, not three. These are reasonable assumptions to make since they basically ignore optional arguments that I won’t use anyways. The bigger assumption is that all the face definitions are triangles. This isn’t the case, even with the files I’ll be using, but OpenGL is really fast with triangles to I’m converting the quadrilaterals to triangles. This current iteration only accepts quadrilaterals but triangle faces would be trivial to add.

Here is the actually importer.

void loadObjFile(VertInfo** verts, IndInfo** inds, const char* filename) {
	if(*verts) {
		free((*verts)->pos);
		(*verts)->pos = NULL;
		free((*verts)->tex);
		(*verts)->tex = NULL;
		free((*verts)->norm);
		(*verts)->norm = NULL;

		(*verts)->num = 0;
	} else *verts = (VertInfo*) calloc(0x01, sizeof(VertInfo));

	if(*inds) {
		free((*inds)->pos);
		(*inds)->pos = NULL;
		free((*inds)->tex);
		(*inds)->tex = NULL;
		free((*inds)->norm);
		(*inds)->norm = NULL;
		
		(*inds)->num = 0;
	} else *inds = (IndInfo*) calloc(0x01, sizeof(IndInfo));

	size_t n = 0;
	char** lines = NULL;

	if(filename==NULL) return;
	FILE* fp = fopen(filename, "r");

	do {
		char c;
		int colInd = 0;

		lines = (char**) realloc(lines, ++n*sizeof(char*));
		lines[n-1] = NULL;

		do {
			fread(&c, sizeof(char), 1, fp);

			// NOTE: strip newlines
			if(c!='n') {
				if(colInd>0) {
					// NOTE: strip extra spaces
					if(!(lines[n-1][colInd-1]==' ' && c==' ')) {
						lines[n-1] = (char*) realloc(lines[n-1], ++colInd*sizeof(char));
						lines[n-1][colInd-1] = c;
					}
				} else {
					lines[n-1] = (char*) realloc(lines[n-1], ++colInd*sizeof(char));
					lines[n-1][colInd-1] = c;
				}
			}
		} while(c!='n');

		// NOTE: end with a null terminator
		lines[n-1] = (char*) realloc(lines[n-1], ++colInd*sizeof(char));
		lines[n-1][colInd-1] = '';

		if(lines[n-1][0]=='#') {
			// NOTE: ignore comments

			free(lines[n-1]);
			lines[n-1] = NULL;

			lines = (char**) realloc(lines, --n*sizeof(char*));
		} else if(lines[n-1][0]=='') {
			// NOTE: ignore blank lines

			free(lines[n-1]);
			lines[n-1] = NULL;

			lines = (char**) realloc(lines, --n*sizeof(char*));
		}
	} while(!feof(fp));

	fclose(fp);
	fp = NULL;

	size_t numPos = 0;
	size_t numTex = 0;

	int i;
	for(i=0; i<n; i++) {
		// NOTE: parse the file

		printf("%sn", lines[i]);

		if(lines[i][0]=='v' && lines[i][1]==' ') {
			// NOTE: parse the vertex and add it to the array

			(*verts)->num++;

			numPos += 3;
			(*verts)->pos = (GLfloat*) realloc((*verts)->pos, numPos*sizeof(GLfloat));

			GLfloat x = 0, y = 0, z = 0;
			sscanf(lines[i], "v %f %f %f", &x, &y, &z);
			printf("[%f, %f, %f]n", x, y, z);

			(*verts)->pos[numPos-3] = x;
			(*verts)->pos[numPos-2] = y;
			(*verts)->pos[numPos-1] = z;

		} else if(lines[i][0]=='v' && lines[i][1]=='t' && lines[i][2]==' ') {
			// NOTE: parse the texture coordinates

			numTex += 2;
			(*verts)->tex = (GLfloat*) realloc((*verts)->tex, numTex*sizeof(GLfloat));

			GLfloat u = 0, v = 0;
			sscanf(lines[i], "vt %f %f", &u, &v);
			printf("[%f, %f]n", u, v);

			(*verts)->tex[numTex-2] = u;
			(*verts)->tex[numTex-1] = v;
			
		} else if(lines[i][0] == 'f' && lines[i][1] == ' ') {
			// NOTE: parse the face elements

			int c, num = 0;
			GLuint* indices = NULL;

			for(c=1; c<strlen(lines[i]); c++) {

				// TODO: will have to handle the case when normals are left out
				// or when texture indices are left out

				// TODO: currently I am assuming 4 indices per face which makes
				// two triangles (i.e. 6 elements)

				if(lines[i][c]==' ') {
					num += 3;
					indices = (GLuint*) realloc(indices, num*sizeof(GLuint));

					GLuint vi = 0, vti = 0, vni = 0;
					sscanf(&lines[i][c], " %d/%d/%d", &vi, &vti, &vni);
					printf("[%d, %d, %d]n", vi, vti, vni);

					indices[num-3] = vi;
					indices[num-2] = vti;
					indices[num-1] = vni;
				}
			}

			if(num==4*3) {

				// TODO: currently only setting the position index but I need
				// to set them all eventually

				(*inds)->num += 6;

				(*inds)->pos = (GLuint*) realloc((*inds)->pos, (*inds)->num*sizeof(GLuint));
				(*inds)->tex = (GLuint*) realloc((*inds)->tex, (*inds)->num*sizeof(GLuint));
				(*inds)->norm = (GLuint*) realloc((*inds)->norm, (*inds)->num*sizeof(GLuint));

				int pos0 = indices[0];
				int pos1 = indices[3];
				int pos2 = indices[6];
				int pos3 = indices[9];

				(*inds)->pos[(*inds)->num-6] = pos0-1;
				(*inds)->pos[(*inds)->num-5] = pos1-1;
				(*inds)->pos[(*inds)->num-4] = pos2-1;
				(*inds)->pos[(*inds)->num-3] = pos2-1;
				(*inds)->pos[(*inds)->num-2] = pos3-1;
				(*inds)->pos[(*inds)->num-1] = pos0-1;
			}

			free(indices);
			indices = NULL;
		}

		printf("n");
	}

	for(i=0; i<n; i++) {
		free(lines[i]);
		lines[i] = NULL;
	}

	free(lines);
	lines = NULL;
}

I added printf() outputs for debugging purposes. This function will build the structs and set the pointers so that we can then work on this abstraction layer to build the information OpenGL needs to render the object.

To create the structs just place the following calls.

IndInfo* inds = NULL;
VertInfo* verts = NULL;

loadObjFile(&verts, &inds, "test.obj");

The neat thing about this current setup is that the index information can be passed directly to OpenGL as an element buffer.

// NOTE: allocate a GPU buffer for the element data
GLuint eleBuffer;
glGenBuffers(1, &eleBuffer);

// NOTE: bind to the element buffer so that we may send our data to the GPU
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eleBuffer);

// NOTE: send our element data to the GPU and set as STAIC
glBufferData(GL_ELEMENT_ARRAY_BUFFER, inds->num*sizeof(GLuint), inds->pos, GL_STATIC_DRAW);

On the other hand, the OpenGL vertex information is highly specific to the type of effects you plan to render with your objects. Here I’m just using the position and texture information but if you plan to add lighting or some other type of rendering effect you’ll have to customize the OpenGL vertex information to fit your needs.

GLfloat glVerts[5*verts->num];
memset(glVerts, 0x00, 5*verts->num*sizeof(GLfloat));

int tempInd = 0;
for(; tempInd<verts->num; tempInd++) {
	glVerts[5*tempInd+0] = verts->pos[3*tempInd+0];
	glVerts[5*tempInd+1] = verts->pos[3*tempInd+1];
	glVerts[5*tempInd+2] = verts->pos[3*tempInd+2];

	glVerts[5*tempInd+3] = verts->tex[2*tempInd+0];
	glVerts[5*tempInd+4] = verts->tex[2*tempInd+1];
}

// NOTE: allocate an array buffer on the GPU
GLuint verBuffer;
glGenBuffers(1, &verBuffer);

// NOTE: bind to the array buffer so that we may send our data to the GPU
glBindBuffer(GL_ARRAY_BUFFER, verBuffer);

// NOTE: send our vertex data to the GPU and set as STAIC
glBufferData(GL_ARRAY_BUFFER, 5*verts->num*sizeof(GLfloat), glVerts, GL_STATIC_DRAW);

And that is pretty much it! Now you can create complicated meshes using something like Anim8or or Blender and render them with your own OpenGL implementation.