• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
Ranger_One

OBJ Model Loading (In Detail)

16 posts in this topic

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:
/* Return 1 if strings are equal, 0 if not */
/* Make sure we don't ever pass the given end (E) */
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;
}

/* Return 1 if the character is dead space, 0 if not */
int IsNumeric(char A)
{
	if (A == '.') return 1;
	if (A == '-') return 1;
	if ((A >= 0x30) && (A <= 0x39)) return 1;
	return 0;
}


/* Return 1 if the character is dead space, 0 if not */
int IsDeadSpace(char A)
{
    	if (A < 33) return 1;
	 else return 0;
}

/* Return 1 if the character is a newline/linefeed, 0 if not */
int IsEOL(char A)
{
	if (A == 10) return 1;
	 else return 0;
}


Next post: The initial scan...
0

Share this post


Link to post
Share on other sites

ObjModel* ObjLoadModel(char *mem, int sz)
{
char *p, *e;
char b[512];
int c;
ObjModel *ret;

// the returned model struct, allocate and clear
ret = calloc(1,sizeof(ObjModel));

// current position and end location pointers
p = mem;
e = mem + sz;

// first pass, scan the number of vertex, normals, texcoords, and faces
while (p != e)
{
// nibble off one line, ignoring leading dead space
c = 0;
while ((IsDeadSpace(*p)) && (p != e)) p++;
while ((!IsEOL(*p)) && (p != e) && (c < 512)) { b[c++] = *p; p++; }

// ok, b[] contains the current line
if (StrEqual(b,"vn",&b[c],2)) ret->NumNormal++;
else
if (StrEqual(b,"vt",&b[c],2)) ret->NumTexCoord++;
else
if (StrEqual(b,"v",&b[c],1)) ret->NumVertex++;
else
if (StrEqual(b,"f",&b[c],1)) ret->NumTriangle++;
}

// now allocate the arrays
ret->VertexArray = malloc(sizeof(ObjVertex)*ret->NumVertex);
ret->NormalArray = malloc(sizeof(ObjNormal)*ret->NumNormal);
ret->TexCoordArray = malloc(sizeof(ObjTexCoord)*ret->NumTexCoord);
ret->TriangleArray = malloc(sizeof(ObjTriangle)*ret->NumTriangle);




There is the first 2 steps of the function, scan the file for counts and then allocate the arrays. I won't be placing any error checking in this code for clarity, nor will I handle groups. I'll probably post code that does those things later in this thread.

Now all we have left is to read the actual data! (Next post)

Note: The order of the StrEqual()s is important. If the 'v' test was placed first, it would pass for 'vn' and 'vt' as well...

[Edited by - Ranger_One on April 10, 2005 8:29:35 AM]
0

Share this post


Link to post
Share on other sites
Ok, I'm a big fat liar. I got tired after writing my own faulty strtod() function (which would only do approximate values) and surrendered. So I'm linking to: calloc(), malloc(), and sscanf() now. But I digress, and here is the final code:


// finally, go back and scan the values
p = mem;
Vc = Nc = Tc = Fc = 0;

while (p != e)
{
// nibble off one line, ignoring leading dead space
c = 0;
while ((IsDeadSpace(*p)) && (p != e)) p++;
while ((!IsEOL(*p)) && (p != e) && (c < 512)) { b[c++] = *p; p++; }

// ok, b[] contains the current line
if (StrEqual(b,"vn",&b[c],2))
{
sscanf(b,"vn %f %f %f",&ret->NormalArray[Nc].X,&ret->NormalArray[Nc].Y,&ret->NormalArray[Nc].Z);
Nc++;
}
else
if (StrEqual(b,"vt",&b[c],2))
{
sscanf(b,"vt %f %f",&ret->TexCoordArray[Tc].U,&ret->TexCoordArray[Tc].V);
Tc++;
}
else
if (StrEqual(b,"v",&b[c],1))
{
sscanf(b,"v %f %f %f",&ret->VertexArray[Vc].X,&ret->VertexArray[Vc].Y,&ret->VertexArray[Vc].Z);
Vc++;
}
else
if (StrEqual(b,"f",&b[c],1))
{
sscanf(b,"f %d/%d/%d %d/%d/%d %d/%d/%d",
&ret->TriangleArray[Fc].Vertex[0],&ret->TriangleArray[Fc].TexCoord[0],&ret->TriangleArray[Fc].Normal[0],
&ret->TriangleArray[Fc].Vertex[1],&ret->TriangleArray[Fc].TexCoord[1],&ret->TriangleArray[Fc].Normal[1],
&ret->TriangleArray[Fc].Vertex[2],&ret->TriangleArray[Fc].TexCoord[2],&ret->TriangleArray[Fc].Normal[2]);
Fc++;
}
}

return ret;
}







A further version that adds simple error handling, and a utility to buffer an entire file into memory is forthcoming.

[Edited by - Ranger_One on April 10, 2005 9:49:02 AM]
0

Share this post


Link to post
Share on other sites
Here is a website with the files and a test run of the code:

OBJ Public Domain Code

Still missing:

Error Handling
Group Handling
Support for Faces with more than one triangle (quads, etc.)
Advanced OBJ File Features (probably never will have them)

If there is enough clamoring, I can add the first three. Otherwise, that's up to the code's end user.

NOTE: The returned indexes inside the TriangleArray[] are one higher than they should be, since OBJ files index from 1 and not 0. You could adjust them all down one, at the end of the function, or do it as needed (if you don't care about speed)...
0

Share this post


Link to post
Share on other sites
I would suggest to give more importance to loading obj files than writing c standard libs. The standard libs should be portable to any OS plus they are very well tested, so there is no need to write your own.

0

Share this post


Link to post
Share on other sites
I am working on a c++ version of your code...
Making classes out of the functions. Onlything i'm stuck on is what is the proper c++ way to replace the sscanf() calls on the buffers? cause my buffers are strings...

But i will post the almost C++ compliant version of your code, with some added features.


>>>>><<<<<
>> EDIT <<
>>>>><<<<<

Well hear it is in its incomplete form...
Only error im stuck on is my ObjModel delete [] stuff. causes assertion failure

ObjLoader.h

/*-----------------------------------------------------------------//
// April 10, 2005 //
// Copyright (C) 2005 Justin Walsh //
// //
// This code is Released Under the GNU GPL //
// http://www.gnu.org/copyleft/gpl.html //
//-----------------------------------------------------------------*/


#ifndef OBJLOADER_H
#define OBJLOADER_H

#include <fstream>
#include <string>
#include <sstream>

using namespace std;

/*-----------------------------------------------------------------//
//Here we have our data types our loader class will need and use...//
// */

struct ObjVertex{
float X, Y, Z;
};

struct ObjNormal{
float X, Y, Z;
};

struct ObjTexCoord{
float U, V;
};

struct ObjTriangle{
int Vertex[3];
int Normal[3];
int TexCoord[3];
};

class ObjModel {
public:
ObjModel();
~ObjModel();

ObjModel(const ObjModel& copy);
ObjModel& operator=(const ObjModel& right);


int NumVertex, NumNormal, NumTexCoord, NumTriangle;

ObjVertex *VertexArray;
ObjNormal *NormalArray;
ObjTexCoord *TexCoordArray;

ObjTriangle *TriangleArray;
};
/* //
//-----------------------------------------------------------------*/




/*-----------------------------------------------------------------//
// The meat of the sandwitch, the class to load .obj files //
// */

class ObjLoader {
public:
ObjLoader();
~ObjLoader();

ObjLoader(string file);
void LoadObj(string file);
void FreeObj(void);
ObjModel ReturnObj(void);

protected:
string *fileName;
ObjModel *theObj;

void ReadData(void);
};
/* //
//-----------------------------------------------------------------*/

#endif




ObjLoader.cpp

/*-----------------------------------------------------------------//
// April 10, 2005 //
// Copyright (C) 2005 Justin Walsh //
// //
// This code is Released Under the GNU GPL //
// http://www.gnu.org/copyleft/gpl.html //
//-----------------------------------------------------------------*/


#include "ObjLoader.h"


/*-----------------------------------------------------------------//
// ObjModel Class //
// */

ObjModel::ObjModel() {
//Set everything to zero and empty...
ObjModel::NumNormal = 0;
ObjModel::NumTexCoord = 0;
ObjModel::NumTriangle = 0;
ObjModel::NumVertex = 0;
ObjModel::NormalArray = NULL;
ObjModel::TexCoordArray = NULL;
ObjModel::TriangleArray = NULL;
ObjModel::VertexArray = NULL;
}

ObjModel::~ObjModel() {
//free up our data
delete [] NormalArray;
delete [] TexCoordArray;
delete [] TriangleArray;
delete [] VertexArray;
}

ObjModel::ObjModel(const ObjModel &copy) {
//make room for the new data
NormalArray = new ObjNormal[copy.NumNormal];
TexCoordArray = new ObjTexCoord[copy.NumTexCoord];
TriangleArray = new ObjTriangle[copy.NumTriangle];
VertexArray = new ObjVertex[copy.NumVertex];
NumNormal = copy.NumNormal;
NumTexCoord = copy.NumTexCoord;
NumTriangle = copy.NumTriangle;
NumVertex = copy.NumVertex;


//Grab data from the copy to the current class
for(int i = 0; i < NumNormal; i++)
NormalArray[i] = copy.NormalArray[i];

for(i = 0; i < NumTexCoord; i++)
TexCoordArray[i] = copy.TexCoordArray[i];

for(i = 0; i < NumTriangle; i++)
TriangleArray[i] = copy.TriangleArray[i];

for(i = 0; i < NumVertex; i++)
VertexArray[i] = copy.VertexArray[i];

//good to go...
}

ObjModel& ObjModel::operator=(const ObjModel &right) {
//free current data if we have it
delete [] NormalArray;
delete [] TexCoordArray;
delete [] TriangleArray;
delete [] VertexArray;

//make room for the new data
NormalArray = new ObjNormal[right.NumNormal];
TexCoordArray = new ObjTexCoord[right.NumTexCoord];
TriangleArray = new ObjTriangle[right.NumTriangle];
VertexArray = new ObjVertex[right.NumVertex];
NumNormal = right.NumNormal;
NumTexCoord = right.NumTexCoord;
NumTriangle = right.NumTriangle;
NumVertex = right.NumVertex;


//Grab data from the copy to the current class
for(int i = 0; i < NumNormal; i++)
NormalArray[i] = right.NormalArray[i];

for(i = 0; i < NumTexCoord; i++)
TexCoordArray[i] = right.TexCoordArray[i];

for(i = 0; i < NumTriangle; i++)
TriangleArray[i] = right.TriangleArray[i];

for(i = 0; i < NumVertex; i++)
VertexArray[i] = right.VertexArray[i];

//good to go...
return *this;
}
/* //
//-----------------------------------------------------------------*/




/*-----------------------------------------------------------------//
// ObjLoader Class //
// */


ObjLoader::ObjLoader() {
fileName = NULL;
theObj = NULL;
}

ObjLoader::~ObjLoader() {
FreeObj();
}

void ObjLoader::FreeObj(void) {
if(fileName != NULL) delete fileName;
if(theObj != NULL) delete theObj;
}

ObjModel ObjLoader::ReturnObj(void) {
ObjModel ret(*theObj);
return ret;
}

ObjLoader::ObjLoader(string file) {
fileName = new string(file);
theObj = new ObjModel();
ReadData();
}

void ObjLoader::LoadObj(string file) {
FreeObj();
fileName = new string(file);
theObj = new ObjModel();
ReadData();
}

void ObjLoader::ReadData(void) {
ifstream input(fileName->c_str());
string buffer;

//make sure we got the file opened up ok...
if( !input.is_open() )
return;

//Setup our ObjModel arrays...
//read one line at a time of the file...
while( !input.eof() ) {
getline(input, buffer);

if(buffer.substr(0,2) == "vn")
theObj->NumNormal++;
else if(buffer.substr(0,2) == "vt")
theObj->NumTexCoord++;
else if(buffer.substr(0,1) == "v")
theObj->NumVertex++;
else if(buffer.substr(0,1) == "f")
theObj->NumTriangle++;

}

//Make the arrays the right size...
theObj->NormalArray = new ObjNormal[theObj->NumNormal];
theObj->TexCoordArray = new ObjTexCoord[theObj->NumTexCoord];
theObj->TriangleArray = new ObjTriangle[theObj->NumTriangle];
theObj->VertexArray = new ObjVertex[theObj->NumVertex];

//close the file...
input.close();

//reopen it...
input.open(fileName->c_str());

input.clear();

//make sure we got the file opened up ok...
if( !input.is_open() )
return;

//hear are the counters...
int nC, vC, tC, fC;
nC = vC = tC = fC = 0;

//get the hard data, and load up our arrays...
//read one line at a time of the file...
while( !input.eof() ) {
getline(input, buffer);
istringstream line(buffer);
string temp;
string f1, f2, f3;

if(buffer.substr(0,2) == "vn") {
line >> temp >> f1 >> f2 >> f3;
theObj->NormalArray[vC].X = atof(f1.c_str());
theObj->NormalArray[vC].Y = atof(f2.c_str());
theObj->NormalArray[vC].Z = atof(f3.c_str());
//sscanf(buffer.c_str(), "vn %f %f %f", theObj->NormalArray[nC].X,
// theObj->NormalArray[nC].Y, theObj->NormalArray[nC].Z);
nC++;
}
else if(buffer.substr(0,2) == "vt") {
line >> temp >> f1 >> f2;
theObj->TexCoordArray[tC].U = atof(f1.c_str());
theObj->TexCoordArray[tC].V = atof(f2.c_str());
//sscanf(buffer.c_str(), "vt %f %f", theObj->TexCoordArray[tC].U,
// theObj->TexCoordArray[tC].V);
tC++;
}
else if(buffer.substr(0,1) == "v") {
line >> temp >> f1 >> f2 >> f3;
theObj->VertexArray[vC].X = atof(f1.c_str());
theObj->VertexArray[vC].Y = atof(f2.c_str());
theObj->VertexArray[vC].Z = atof(f3.c_str());
//sscanf(buffer.c_str(), "v %f %f %f", theObj->VertexArray[vC].X,
// theObj->VertexArray[vC].Y, theObj->VertexArray[vC].Z);
vC++;
}
else if(buffer.substr(0,1) == "f") {
line >> temp >> f1 >> f2 >> f3;

int sPos = 0;
int ePos = sPos;
string temp;
ePos = f1.find_first_of("/");
//we have a line with the format of "f %d/%d/%d %d/%d/%d %d/%d/%d"
if(ePos != string::npos) {
temp = f1.substr(sPos, ePos - sPos);
theObj->TriangleArray[fC].Vertex[0] = atoi(temp.c_str()) - 1;

sPos = ePos+1;
ePos = f1.find("/", sPos);
temp = f1.substr(sPos, ePos - sPos);
theObj->TriangleArray[fC].Vertex[1] = atoi(temp.c_str()) - 1;

sPos = ePos+1;
ePos = f1.length();
temp = f1.substr(sPos, ePos - sPos);
theObj->TriangleArray[fC].Vertex[2] = atoi(temp.c_str()) - 1;
}

sPos = 0;
ePos = f2.find_first_of("/");
//we have a line with the format of "f %d/%d/%d %d/%d/%d %d/%d/%d"
if(ePos != string::npos) {
temp = f2.substr(sPos, ePos - sPos);
theObj->TriangleArray[fC].TexCoord[0] = atoi(temp.c_str()) - 1;

sPos = ePos + 1;
ePos = f2.find("/", sPos+1);
temp = f2.substr(sPos, ePos - sPos);
theObj->TriangleArray[fC].TexCoord[1] = atoi(temp.c_str()) - 1;

sPos = ePos + 1;
ePos = f2.length();
temp = f2.substr(sPos, ePos - sPos);
theObj->TriangleArray[fC].TexCoord[2] = atoi(temp.c_str()) - 1;
}

sPos = 0;
ePos = f3.find_first_of("/");
//we have a line with the format of "f %d/%d/%d %d/%d/%d %d/%d/%d"
if(ePos != string::npos) {
temp = f3.substr(sPos, ePos - sPos);
theObj->TriangleArray[fC].Normal[0] = atoi(temp.c_str()) - 1;

sPos = ePos + 1;
ePos = f3.find("/", sPos+1);
temp = f3.substr(sPos, ePos - sPos);
theObj->TriangleArray[fC].Normal[1] = atoi(temp.c_str()) - 1;

sPos = ePos + 1;
ePos = f3.length();
temp = f3.substr(sPos, ePos - sPos);
theObj->TriangleArray[fC].Normal[2] = atoi(temp.c_str()) - 1;
}
//sscanf(buffer.c_str(), "f %d/%d/%d %d/%d/%d %d/%d/%d",
// theObj->TriangleArray[fC].Vertex[0], theObj->TriangleArray[fC].TexCoord[0], theObj->TriangleArray[fC].Normal[0],
// theObj->TriangleArray[fC].Vertex[1], theObj->TriangleArray[fC].TexCoord[1], theObj->TriangleArray[fC].Normal[1],
// theObj->TriangleArray[fC].Vertex[2], theObj->TriangleArray[fC].TexCoord[2], theObj->TriangleArray[fC].Normal[2]);

fC++;
}
}
//all should be good
}


/* //
//-----------------------------------------------------------------*/





main.cpp test program

/*-----------------------------------------------------------------//
// April 10, 2005 //
// Copyright (C) 2005 Justin Walsh //
// //
// This code is Released Under the GNU GPL //
// http://www.gnu.org/copyleft/gpl.html //
//-----------------------------------------------------------------*/


#include <iostream>
#include "ObjLoader.h"
using namespace std;

int main(void) {
ObjModel data;
ObjLoader LoaderClass;

LoaderClass.LoadObj("test.obj");
data = LoaderClass.ReturnObj();

cout << "Object Model has: " << data.NumTriangle << " faces! \n";
for(int i = 0; i < data.NumVertex; i++) {
cout << data.VertexArray[i].X
<< " "
<< data.VertexArray[i].Y
<< " "
<< data.VertexArray[i].Z
<< "\n";
}
for(i = 0; i < data.NumTriangle; i++) {
cout << "Indacies into faces:\n";
cout << data.TriangleArray[i].Vertex[0]
<< " "
<< data.TriangleArray[i].Vertex[1]
<< " "
<< data.TriangleArray[i].Vertex[2]
<< "\n";
}

cout << "Vertex from triangle info]:\n";
cout << data.VertexArray[data.TriangleArray[1].Vertex[0]].X
<< " "
<< data.VertexArray[data.TriangleArray[1].Vertex[0]].Y
<< " "
<< data.VertexArray[data.TriangleArray[1].Vertex[0]].Z
<< "\n"
<< data.VertexArray[data.TriangleArray[1].Vertex[1]].X
<< " "
<< data.VertexArray[data.TriangleArray[1].Vertex[1]].Y
<< " "
<< data.VertexArray[data.TriangleArray[1].Vertex[1]].Z
<< "\n"
<< data.VertexArray[data.TriangleArray[1].Vertex[2]].X
<< " "
<< data.VertexArray[data.TriangleArray[1].Vertex[2]].Y
<< " "
<< data.VertexArray[data.TriangleArray[1].Vertex[2]].Z
<< "\n";


system("PAUSE");
return 0;
}






Sorry to hijack the thread, but if we get this working in a C flavor and C++ flavor that will be awesome.

[Edited by - justinrwalsh on April 11, 2005 12:08:05 AM]
0

Share this post


Link to post
Share on other sites
justinrwalsh, I used an istringstream to break each line up into tokens (mind you, no UV coordinate storage).


void ParseObjLine(const string &line)
{
if(line == "")
return;

istringstream in(lower_string(line));

long unsigned int token_index = 0;
string token("");

#define PROCESS_VERTEX 0
#define PROCESS_VERTEX_NORMAL 1
#define PROCESS_TRIANGLE 2
unsigned char processing_type = 0;

vertex temp_vertex;
vertex temp_vertex_normal;
triangle temp_triangle;

while(in >> token && token_index < 4)
{
if(token_index == 0)
{
if(token == "v")
processing_type = PROCESS_VERTEX;
else if(token == "vn")
processing_type = PROCESS_VERTEX_NORMAL;
else if(token == "f")
processing_type = PROCESS_TRIANGLE;
else
return;
}
else if(token_index == 1)
{
if(processing_type == PROCESS_VERTEX)
{
temp_vertex.x = atof(token.c_str());
}
else if(processing_type == PROCESS_VERTEX_NORMAL)
{
temp_vertex_normal.x = atof(token.c_str());
}
else if(processing_type == PROCESS_TRIANGLE)
{
size_t pos = token.find_first_of("/");

if(pos != string::npos)
token == token.substr(0, pos);

istringstream local_in;
local_in.str(token);

local_in >> temp_triangle.vertex_indices[0];
temp_triangle.vertex_normal_indices[0] = temp_triangle.vertex_indices[0];
}
}
else if(token_index == 2)
{
if(processing_type == PROCESS_VERTEX)
{
temp_vertex.y = atof(token.c_str());
}
else if(processing_type == PROCESS_VERTEX_NORMAL)
{
temp_vertex_normal.y = atof(token.c_str());
}
else if(processing_type == PROCESS_TRIANGLE)
{
size_t pos = token.find_first_of("/");

if(pos != string::npos)
token == token.substr(0, pos);

istringstream local_in;
local_in.str(token);

local_in >> temp_triangle.vertex_indices[1];
temp_triangle.vertex_normal_indices[1] = temp_triangle.vertex_indices[1];
}
}
else if(token_index == 3)
{
if(processing_type == PROCESS_VERTEX)
{
temp_vertex.z = atof(token.c_str());
}
else if(processing_type == PROCESS_VERTEX_NORMAL)
{
temp_vertex_normal.z = atof(token.c_str());
}
else if(processing_type == PROCESS_TRIANGLE)
{
size_t pos = token.find_first_of("/");

if(pos != string::npos)
token == token.substr(0, pos);

istringstream local_in;
local_in.str(token);

local_in >> temp_triangle.vertex_indices[2];
temp_triangle.vertex_normal_indices[2] = temp_triangle.vertex_indices[2];
}
}

token_index++;
}

if(token_index != 4)
return;

if(processing_type == PROCESS_VERTEX)
{
vertices.push_back(temp_vertex);
}
else if(processing_type == PROCESS_VERTEX_NORMAL)
{
vertex_normals.push_back(temp_vertex_normal);
}
else if(processing_type == PROCESS_TRIANGLE)
{
temp_triangle.vertex_indices[0]--;
temp_triangle.vertex_indices[1]--;
temp_triangle.vertex_indices[2]--;

temp_triangle.vertex_normal_indices[0]--;
temp_triangle.vertex_normal_indices[1]--;
temp_triangle.vertex_normal_indices[2]--;

triangles.push_back(temp_triangle);
}
}



0

Share this post


Link to post
Share on other sites
everything works except the objModel destructor...
let me what simple mistake i made in that code...
then i will upload a final version all zipped up.
0

Share this post


Link to post
Share on other sites
you may want to make each delete[] call:

if (Array) delete[] Array;

I don't think calling delete[] on a NULL array is valid.

Oh, and the the C++ conversion looks good :)
0

Share this post


Link to post
Share on other sites
Ranger,

Your source had a couple of bugs (DL version). And a couple a nit-picks of mine.
I cleaned up the code for improved readability. Changed a few naming conventions to std notation. Increased speed (very minor in the read / scan loops) (just make sure the file ends with a blank line!!!). Reduced code (with use of std lib's). I also added free to release the objects. Hope you don't take offence and rain on your parade.

main.cpp

#include <stdlib.h>
#include <stdio.h>
#include "obj.h"

int main()
{
char* memory = NULL;
size_t bytes = ObjLoadFile("tank.obj", &memory);

ObjModel* model = ObjLoadModel(memory, bytes);

printf("Object Model has: %d faces!\n", model->nTriangle);

free(model->NormalArray);
free(model->TexCoordArray);
free(model->TriangleArray);
free(model->VertexArray);
free(model);

system("PAUSE");

return 0;
}




obj.cpp

#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include "obj.h"

ObjModel* ObjLoadModel(char* memory, size_t size)
{
char* p = NULL, * e = NULL;
ObjModel* ret = (ObjModel*) calloc(1, sizeof(ObjModel));
memset(ret, 0, sizeof(ObjModel));

p = memory;
e = memory + size;

while (p != e)
{
if (memcmp(p, "vn", 2) == 0) ret->nNormal++;
else if (memcmp(p, "vt", 2) == 0) ret->nTexCoord++;
else if (memcmp(p, "v", 1) == 0) ret->nVertex++;
else if (memcmp(p, "f", 1) == 0) ret->nTriangle++;

while (*p++ != (char) 0x0A);
}

ret->VertexArray = (ObjVertex*) malloc(sizeof(ObjVertex) * ret->nVertex);
ret->NormalArray = (ObjNormal*) malloc(sizeof(ObjNormal) * ret->nNormal);
ret->TexCoordArray = (ObjTexCoord*) malloc(sizeof(ObjTexCoord) * ret->nTexCoord);
ret->TriangleArray = (ObjTriangle*) malloc(sizeof(ObjTriangle) * ret->nTriangle);

p = memory;

int nV = 0, nN = 0, nT = 0, nF = 0;

while (p != e)
{
if (memcmp(p, "vn", 2) == 0)
{
sscanf(p, "vn %f %f %f", &ret->NormalArray[nN].x,
&ret->NormalArray[nN].y,
&ret->NormalArray[nN].z);
nN++;
}
else if (memcmp(p, "vt", 2) == 0)
{
sscanf(p, "vt %f %f", &ret->TexCoordArray[nT].u,
&ret->TexCoordArray[nT].v);
nT++;
}
else if (memcmp(p, "v", 1) == 0) /* or *p == 'v' */
{
sscanf(p, "v %f %f %f", &ret->VertexArray[nV].x,
&ret->VertexArray[nV].y,
&ret->VertexArray[nV].z);
nV++;
}
else if (memcmp(p, "f", 1) == 0) /* or *p == 'f' */
{
sscanf(p, "f %d/%d/%d %d/%d/%d %d/%d/%d", &ret->TriangleArray[nF].Vertex[0],
&ret->TriangleArray[nF].TexCoord[0],
&ret->TriangleArray[nF].Normal[0],
&ret->TriangleArray[nF].Vertex[1],
&ret->TriangleArray[nF].TexCoord[1],
&ret->TriangleArray[nF].Normal[1],
&ret->TriangleArray[nF].Vertex[2],
&ret->TriangleArray[nF].TexCoord[2],
&ret->TriangleArray[nF].Normal[2]);
nF++;
}

while (*p++ != (char) 0x0A);
}

return ret;
}

size_t ObjLoadFile(char* szFileName, char** memory)
{
size_t bytes = 0;
FILE* file = fopen(szFileName, "rt");

if (file != NULL)
{
fseek(file, 0, SEEK_END);
size_t end = ftell(file);
fseek(file, 0, SEEK_SET);

*memory = (char*) malloc(end);
bytes = fread(*memory, sizeof(char), end, file);

fclose(file);
}

return bytes;
}




obj.h

#ifndef OBJ_H
#define OBJ_H

struct ObjVertex
{
float x, y, z;
};

typedef ObjVertex ObjNormal;

struct ObjTexCoord
{
float u, v;
};

struct ObjTriangle
{
int Vertex[3];
int Normal[3];
int TexCoord[3];
};

struct ObjModel
{
int nVertex, nNormal, nTexCoord, nTriangle;

ObjVertex* VertexArray;
ObjNormal* NormalArray;
ObjTexCoord* TexCoordArray;
ObjTriangle* TriangleArray;
};

ObjModel* ObjLoadModel(char*, size_t);
size_t ObjLoadFile(char*, char**);

#endif



0

Share this post


Link to post
Share on other sites
Ok the C++ version of the code is complete...
sjf i haven't reviewed your code yet to see what improvements need to be made.

There are somethings i noticed about the obj format that i think we should include...

Support for faces that have more than 3 vertex/textureCoord/normal sets.
I modeled something in blender and it hade a few with like 6 f v/vt/vn data sets in a row.

Well here is the link to what I got.. it has been zipped for ease of downloading, i'll include the VC.NET Project files as well.

objLoaderTest.zip

and while i'm at it... how about you just look at my simple un updated webpage...
Justin Walsh's Homepage

thanks to all who help and have helped.

Now one last question...
How would you go about drawing all this shit in OpenGL?
can i just traingle strip it up from the first face in the array, to the last?
0

Share this post


Link to post
Share on other sites
sjf,

I take no offense :) I did however write the code for clarity, and not for speed. I wanted to make sure you could see the process going on easily.

Justinrwalsh,

Yes, we need support for polygon faces. It would be quite simple to triangulate them, and we could use a couple different approaches to do so. Otherwise you could tell your modeller to triangulate the model before saving it (assuming that can be done). Groups are pretty useful too, and easy to add. All this would have made the code more unclear, and clarity was my prime objective.

Drawing the model is as simple as:

glBegin(GL_TRIANGLES);
... Iterate over faces, drawing each triangle.
glEnd()

Thanks! I appreciate the help.
0

Share this post


Link to post
Share on other sites
giant error in my source...

Uploading fixed code...

[Edited by - justinrwalsh on April 11, 2005 4:24:36 PM]
-1

Share this post


Link to post
Share on other sites
[quote]How would you go about drawing all this shit in OpenGL?[/quote]

Step by step guide to obj files in C (Triangulates inputs meshes [in a simplistic way that can fail in some cases] with VertexArray and TBN generation). Starts simple, gets very complicated towards the end. All of this written in the days before shaders though, so the Dot3 example may want converting to GLSL instead,
[url="http://nccastaff.bournemouth.ac.uk/jmacey/RobTheBloke/www/source/ObjLoader.zip"]http://nccastaff.bou...e/ObjLoader.zip[/url]

basic C++ library (with beziers, materials, groups and lots of other stuff)
[url="http://nccastaff.bournemouth.ac.uk/jmacey/RobTheBloke/www/libObj.zip"]http://nccastaff.bou.../www/libObj.zip[/url]

\edit.
Wow. That's a very old thread you dug up there ;)
0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0