As promised in a recent thread, here I will describe code that reads ascii OBJ files of 3d models. I will use simple C source that calls no libraries (except malloc/free, since I need memory control). Here we go...
OBJ File Overview
An OBJ file contains a static 3d model, and possibly some high-level curves/surfaces. Here I make no attempt to support any high-level command found in the standard. The information I used to design this simple loading code can be found here:
Paul Bourke's OBJ Format Page.
Each OBJ file is a collection of ascii text lines, each delimited by the standard characters. I use code that tests for character 10, the newline/linefeed, to determine the breaks. I've yet to encounter any problems with this method, both Unix and Windows include a newline character to delimit the end of line.
The data contained in OBJ files is: Vertex coords, Vertex Normals, Vertex Texcoords, Faces, and Groups. There can be more information, though this is what we are interested in. All of the Vertex information becomes arrays which the Faces index. So, we have reached our first step. We need a structure to hold the OBJ file data after it is read. Here is what I use in the following code:
typedef struct _ObjVertex {
float X, Y, Z;
} ObjVertex;
typedef _ObjVertex _ObjNormal;
typedef struct _ObjTexCoord {
float U, V;
} ObjTexCoord;
typedef struct _ObjTriangle {
int Vertex[3];
int Normal[3];
int TexCoord[3];
} ObjTriangle;
typedef struct _ObjModel {
int NumVertex, NumNormal, NumTexCoord, NumTriangle;
ObjVertex *VertexArray;
ObjNormal *NormalArray;
ObjTexCoord *TexCoordArray;
ObjTriangle *TriangleArray;
} ObjModel;
So, we have 5 components here. Counts of each array. 3 arrays for vertex information, and 1 more for the face indexes. I'm not going to spend more time explaining this. I assume you have some idea of how a 3d model is stored and rendered.
Pitfalls when reading the file...
Since the OBJ standard supplies no header, we encounter our first pitfall. There is no easy way to determine how many entries we need in each array. To overcome this pitfall we can take one of two approaches: waste memory, waste CPU. If we waste memory, we allocate and reallocate the arrays as we read the file. We'll end up with wasted memory that has to be recouped. If we waste CPU, we read the file twice. First scanning the number of entries needed, then allocating the arrays, and finally reading the data into them.
I choose to waste CPU, since it feels like a more elegant solution to the problem. The code provided does the scan, allocate, and read approach. Here is the exported function from this code:
ObjModel* ObjLoadModel(char *mem, int sz);
It reads the model from a chunk of memory, presumably read verbatim from an .OBJ file. We also need some utility code, we is used internally to make everything clean. I could have used some of the std C library functions, but hey- I'm on a no C std lib kick. Here they are:
int StrEqual(char *A, char *B, char *E, int count);
int IsNumeric(char A);
int IsDeadSpace(char A);
int IsEOL(char A);
We'll supply the C code of these utilities first:
int StrEqual(char *A, char *B, char *E, int count)
{
int c;
c = 0;
while ((c < count) && (A != E))
{
if (A[c] != B[c]) return 0;
c++;
}
if (A == E) return 0;
else return 1;
}
int IsNumeric(char A)
{
if (A == '.') return 1;
if (A == '-') return 1;
if ((A >= 0x30) && (A <= 0x39)) return 1;
return 0;
}
int IsDeadSpace(char A)
{
if (A < 33) return 1;
else return 0;
}
int IsEOL(char A)
{
if (A == 10) return 1;
else return 0;
}
Next post: The initial scan...