View Single Post
  #1 (permalink)  
Old 03-16-2008, 06:48 AM
MrPickle's Avatar
MrPickle MrPickle is offline
Sr. Programmer
Join Date: Nov 2007
Location: England, Lincolnshire
Posts: 303
iTrader: (0)
MrPickle is on a distinguished roadMrPickle is on a distinguished roadMrPickle is on a distinguished road
How to make a basic .obj loader

First I'll start with the basic layout of an .obj file. If you don't have some basic knowledge of OpenGL it's best that you get some, you'll only really need knowledge on how to draw basic polygons. You should also know how to setup a basic OpenGL window. You'll also need the glut libary which can be found here: http://www.xmission.com/~nate/glut.html

# a comment
Normally at the top of the file there's a comment saying which exporter made the file and what not, we just ignore these because well they're comments.

v x y z
This tells us that the following information is a vertex, one point of our face. If it's the first vertex in our file, it's vertex 1, if it's the 10th vertex in the file, it's vertex 10. We'll see why this is relevant later on.

vt u v [w]
This tells us that the following information is a texture coordinate, I haven't tried to get these working as I haven't needed them so you'll have to use your own brains to work them out. Again there position corresponds to what vertex they effect.

vn x y z
This tells us that the following information is a vertex normal. A normal is a vector perpendicular to another vector. These are needed for correct lighting. Again if it's the first normal in our file it's the normal of vertex 1, if it's the 100th normal, it's the normal of vertex 100.

f v1 v2 v3 or f v1//vn1 v2//vn1 v3//vn3 or f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3
This tells us information about the face, this is what brings everything together, I will only be covering f v1 v2 v3 because well I haven't worked out a way to use the others yet (hehe). It's basically a whole number saying which vertex to use, eg: f 5 2 6 is telling us that one corner of our triangle is vertex 5 another corner is vertex 2 and the last is vertex 6. (Remember our vertices are just points, when we specify 3 points together in OpenGL after glBegin(GL_TRIANGLES) it connects them up to make a triangle.)

Here's a basic example of a .obj file, a simple cube:
Code:
# Blender3D v245 OBJ File: 
# www.blender3d.org

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 -1.000000
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
f 5 1 4
f 5 4 8
f 3 7 8
f 3 8 4
f 2 6 3
f 6 7 3
f 1 5 2
f 5 6 2
f 5 8 6
f 8 7 6
f 1 2 3
f 1 3 4
Now for some code!

First we need a way to get the information from the line, there's other ways, but I prefer using this (Thanks to Bench):

Code:
#include <vector>
#include <sstream>

using namespace std;

string getWord(string Line, int Pos)
{
    vector<string> Vec;
    stringstream ss(Line);
    string Word, a;
    while(ss >> Word)
    Vec.push_back(Word);

    if(Pos > Vec.size()-1) //Check whether it's in range
        return "";

    else
    {
        try { a = Vec.at(Pos); } //Just to be safe
        catch(...) { a = ""; } //I'm not sure what to catch
        return a; //Return the word
    }
}
But that returns a string, we need to convert that to a float or int!

Code:
int convertToInt(string Num)
{
    istringstream i(Num);
    int x;

    if(!(i >> x))
        throw 0;

    return x;
}

float convertToFloat(string Num)
{
    istringstream i(Num);
    float x;

    if(!(i >> x))
        throw 0;

    return x;
}
Now we want to open our file, but wait, you don't have any .obj files? I took the liberty of uploading you some, here and here. Now that's sorted we can finally start. I am going to assume that you already have all the code to setup your basic window, if not you can download it here because I'm nice :D I forgot to put "using namespace std;" in, just place it before "bool Grid"

First we need a way to store all the information about our class!

We'll need something to hold our x, y and z coordinates!

Code:
struct Vector {
    float x, y, z;
};
That'll do nicely!
Now we need something to hold our Vertex information, remember we have the vertex position and then the normal!

Code:
struct Vertex {
    Vector Pos;
    Vector Normal;
};
What do we need now? Something to hold the face information did I here you say?

Code:
struct Triangle {
    int v1, v2, v3; //One for each of our corners!
};
Well, that was easy, now we have a way to store the infromation! But how do we bring this all together, I used a class, you can use whatever you feel like.

First we declare our class
Code:
class Model
{
Now, the variables, we don't want everything to be able to edit them so we'll make them private
Code:
    private: //So we can't change them willy nilly
        Triangle Triangles[50000]; //As .obj files don't tell us how many faces
                                   //we have, we have to take a guess, feel free to change the amount for
                                  //bigger models, the most I've come across is 130k
        Vertex Verticies[50000];  //Same as above
        int TriangleCount; //Keep track of the amount of triangles we have
        int VertexCount; //Keep track of the amount of Vertices we have
        int NormalCount; //Keep track of the amount of normals we have
        bool Loaded; //So we know whether it's been loaded of not
Great! Now for the functions!

Code:
public: //So we can access the functions
        Model(); //Constructor
        int Load(const char* Filename);//The function to load, it's int so we can exit the function easily if there's an error!
        void Draw(void); //The function to draw
};
There! Now we've got a nice way to store the information about or model, lets get to the actual code now!

Code:
Model::Model()
{
    VertexCount = 1; //Start on 1
    TriangleCount = 1; //Start on 1
    NormalCount = 1; //Start on 1
    Loaded = false; //We haven't loaded a model yet!
}
That's our constructor, we start on one so that our first vertex isn't vertex 0 but vertex 1 as the faces indicate vertex 1 as vertex 1 not vertex 0. Strange really, everything else in the world of coding starts on 0, so why should they be special :D

Now for the loading! It's really quite simple so don't run away now!
Code:
int Model::Load(const char *Filename)
{
First declare our function, now to declare the variables

Code:
    ifstream F; //Our file
    string Line; //The current line of the file
Nothing special there, just some everyday variables.
Next, we open our file and check if it opened!

Code:
    F.open(Filename, ios::in);
    if(F.fail()) //Has it failed to open
    {
        cout << "Error opening \"" << Filename << "\"" << endl; //Send an error message
        return 0; //Exit the function
    }
Again, nothing to special.
Next we check if we've already got a model loaded, if so, we reset the Triangle, Vertex and Normal count so we don't have both models loaded at once! You can remove this line and see what happens if you want, it won't break, promise!

Code:
if(Loaded) //If loaded is true
    {
        VertexCount = 1; //Reset
        TriangleCount = 1; //Reset
        NormalCount = 1; //Reset
    }
Put your pitchforks away, we're finally here, time to read the file!
First, we start our while loop, standard stuff really.

Code:
    while(!F.eof())
    {
Next we get the current line

Code:
getline(F, Line);
Now to check what we're dealing with, a vertex, a normal, a face?
Thanks to the functions I mentioned earlier we can do that easily!

Code:
if(getWord(Line, 0) == "v") //If the first word is v
        {
Now we know we're dealing with a vertex, we can assign this information to out Verticies variable!

Code:
            if(VertexCount > 50000) break; //If we're exceeding our 50000 we'd better stop
            Verticies[VertexCount].Pos.x = convertToFloat(getWord(Line, 1)); //Set our x location to the second word of the line.
            Verticies[VertexCount].Pos.y = convertToFloat(getWord(Line, 2)); //Set our y variable to the thrid word of the line.
            Verticies[VertexCount].Pos.z = convertToFloat(getWord(Line, 3)); //Set our z variable to the fourth word of the line.
            VertexCount++; //Next vertex please!
It's that simple! We're not done yet mind you! Next we check for normals or faces! If we do have a face or normal we basically do the same!

Code:
        }
        else if(getWord(Line, 0) == "vn") //If it wasn't v is it vn?
        {
            if(NormalCount > 50000) break; //Are we about to exceed the limit?
            Verticies[NormalCount].Normal.x = convertToFloat(getWord(Line, 1));
            Verticies[NormalCount].Normal.y = convertToFloat(getWord(Line, 2));
            Verticies[NormalCount].Normal.z = convertToFloat(getWord(Line, 3));
            NormalCount++; //Next Normal Please!
        }
        else if(getWord(Line, 0) == "f") //If it wasn't v or vn then is it a face?
        {
            if(TriangleCount > 50000) break; //Check to see if we're exceeding limit again
            Triangles[TriangleCount].v1 = convertToInt(getWord(Line, 1));
            Triangles[TriangleCount].v2 = convertToInt(getWord(Line, 2));
            Triangles[TriangleCount].v3 = convertToInt(getWord(Line, 3));
            TriangleCount++; //Next triangle please!
        }
} //Close Loop!
Simple really huh! Next we close the file and set loaded to true!
Code:
    F.close();
    Loaded = true;
    cout << "Triangles: " << TriangleCount-1 << endl;
    cout << "Vertecies: " << VertexCount-1 << endl;
    cout << "Normals: " << NormalCount-1 << endl;
}
Drawing function in next post, it was too long.

__________________

Digg this Post! Del.Icio.Us this Post! Technorati this Post! Furl this Post! Mister Wong this Post! Newsvine this Post! Spurl this Post! Reddit this Post! Netscape this Post!

Last edited by MrPickle : 03-16-2008 at 07:21 AM.
Reply With Quote