Top ↑

WaveFront Loader CPP file

Here we come to the actual code for the loader. It works well, but could use some small improvements.

Constructor and destructor

Nothing special here. We just include our header .h file, and create an empty constructor/destructor.

#include "wavefrontLoader.h"

WFObject::WFObject()
{

}

WFObject::~WFObject()
{

}

Wrapper function - load()

This is one of two functions (the other being draw()) that are exposed to the rest of the program. It tasked with loading the .obj file and sending it off to be parsed.

int WFObject::load(char *filename)
{
    fstream objFile;
    objFile.open(filename);

    if(objFile.is_open())
    {
        char line[255];

        // Parse object file line by line
        while(objFile.good())
        {
            objFile.getline(line, 255);
            parseLine(line);
        }

        objFile.close();
    }
    else
    {
        cout << "Could not open WFObject file '" << filename << "'\n";
        return false;
    }

    return true;
}

Line parser

This function takes a line read in from the .obj file and works out what type of line it is, and calls the appropriate function to parse it into the dynamic object data arrays.

void WFObject::parseLine(char *line)
{
    if(!strlen(line))       // If empty string, don't do anything with it
    {
        return;
    }

    char *lineType;
    lineType = strtok(strdup(line), " ");

    // Decide what to do
    if(!strcmp(lineType, "v"))          // Vertex
    {
        parseVertex(line);
    }
    else if(!strcmp(lineType, "vn"))    // Normal
    {
        parseNormal(line);
    }
    else if(!strcmp(lineType, "f"))     // Face
    {
        parseFace(line);
    }

    return;
}

Object display function

This is the only other public function made available by the class. It's task is, obviously, to draw the model on the screen. The function below uses OpenGL in immediate mode, which is crap due to being massively inefficient. If you want to make your objects display a lot faster, use VBOs or vertex arrays at the least.

void WFObject::draw()
{
    glBegin(GL_TRIANGLES);

    for(int f = 0; f < faces.size(); f++)
    {
        glNormal3f(normals[faces[f].vn1 - 1].x, normals[faces[f].vn1 - 1].y, normals[faces[f].vn1 - 1].z);
        glVertex3f(vertices[faces[f].v1 - 1].x, vertices[faces[f].v1 - 1].y, vertices[faces[f].v1 - 1].z);

        glNormal3f(normals[faces[f].vn2 - 1].x, normals[faces[f].vn2 - 1].y, normals[faces[f].vn2 - 1].z);
        glVertex3f(vertices[faces[f].v2 - 1].x, vertices[faces[f].v2 - 1].y, vertices[faces[f].v2 - 1].z);

        glNormal3f(normals[faces[f].vn3 - 1].x, normals[faces[f].vn3 - 1].y, normals[faces[f].vn3 - 1].z);
        glVertex3f(vertices[faces[f].v3 - 1].x, vertices[faces[f].v3 - 1].y, vertices[faces[f].v3 - 1].z);
    }

    glEnd();
}

This is pretty simple. We tell OpenGL we want to draw triangles, then loop through the faces array, using it's contents as an index to grap the vertex and normal data, which is passed to glVertex3f() and glNormal3f respectively.

Parsing functions

This simple loader only parses 3 types of line:

  • Vertex v
  • Normal vn
  • Face f

Thus, only 3 functions are needed (all are private:).

Parsing a vertex

A vertex line has the v %f %f %f format. sscanf() becomes very handy here.

void WFObject::parseVertex(char *line)
{
    vertices.push_back(Vector());       // Add a new element to the vertices array

    sscanf(line, "v %f %f %f", &vertices.back().x, &vertices.back().y, &vertices.back().z);

    return;
}

Parsing a normal

Very much the same as parsing a vertex, but has the vn prefix, instead of the v prefix. We also put the parsed data into normals instead of vertices.

void WFObject::parseNormal(char *line)
{
    normals.push_back(Vector());

    sscanf(line, "vn %f %f %f", &normals.back().x, &normals.back().y, &normals.back().z);

    return;
}

Parsing a face

This one becomes a little more complex, as there can be multiple face line formats. This loader only supports triangular meshes, so any polygonal face definitions are going to either be ignored, or cause a crash (I haven't tested them).

void WFObject::parseFace(char *line)
{
    int fill = 0;

    faces.push_back(Face());

    // Read face line. If texture indicies aren't present, don't read them.
    if(sscanf(line, "f %d//%d %d//%d %d//%d", &faces.back().v1,
                                              &faces.back().vn1,
                                              &faces.back().v2,
                                              &faces.back().vn2,
                                              &faces.back().v3,
                                              &faces.back().vn3) <= 1)
    {
        sscanf(line, "f %d/%d/%d %d/%d/%d %d/%d/%d", &faces.back().v1,
                                                     &fill,
                                                     &faces.back().vn1,
                                                     &faces.back().v2,
                                                     &fill,
                                                     &faces.back().vn2,
                                                     &faces.back().v3,
                                                     &fill,
                                                     &faces.back().vn3);
    }

    return;
}

The first sscanf() statement reads a face line that doesn't contain any texture mapping information. If the line does contain texture coordinates, the second sscanf() statement reads only the vertex and normal data. The texcoord data is fed into &fill, and discarded.

Look ma, a model! (Bonus downloads)

Here I have documented only the WaveFront loader code; you'll need to be able to set up your own OpenGL rendering context and call .draw() in your render function, and .load() in your init function. I have provided a small project that demonstrates this object loader. It uses SDL, OpenGL and that's about it.

Complimentary downloads

Righto, now that we have a basic geometry loader, let's move on to something a bit more exciting; materials!