• FEATURED

View more

View more

View more

### Image of the Day Submit

IOTD | Top Screenshots

### The latest, straight to your Inbox.

Subscribe to GameDev.net Direct to receive the latest updates and exclusive content.

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

16 replies to this topic

### #1Ranger_One  Members

Posted 09 April 2005 - 10:16 AM

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 */
{
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...

### #2sjf  Members

Posted 09 April 2005 - 01:52 PM

I can't wait. Way to go...

### #3Ranger_One  Members

Posted 10 April 2005 - 02:29 AM

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]

### #4Ranger_One  Members

Posted 10 April 2005 - 02:49 AM

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]

### #5Ranger_One  Members

Posted 10 April 2005 - 03:15 AM

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)...

### #6CRACK123  Members

Posted 10 April 2005 - 05:48 AM

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.

### #7justinrwalsh  Members

Posted 10 April 2005 - 04:08 PM

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


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

#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     //
//                                                                 */
public:

void FreeObj(void);
ObjModel ReturnObj(void);

protected:
string *fileName;
ObjModel *theObj;

};
/*                                                                 //
//-----------------------------------------------------------------*/
#endif




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

/*-----------------------------------------------------------------//
//		  				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;
}
/*                                                                 //
//-----------------------------------------------------------------*/

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

fileName = NULL;
theObj = NULL;
}

FreeObj();
}

if(fileName != NULL) delete fileName;
if(theObj != NULL)   delete theObj;
}

ObjModel ret(*theObj);
return ret;
}

fileName = new string(file);
theObj = new ObjModel();
}

FreeObj();
fileName = new string(file);
theObj = new ObjModel();
}

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>
using namespace std;

int main(void)  {
ObjModel data;

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]

### #8sjhalayka  Members

Posted 10 April 2005 - 04:21 PM

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);	}}

### #9justinrwalsh  Members

Posted 10 April 2005 - 06:53 PM

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.

### #10Ranger_One  Members

Posted 10 April 2005 - 10:45 PM

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 :)

### #11sjf  Members

Posted 11 April 2005 - 12:31 AM

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_Hstruct 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

### #12justinrwalsh  Members

Posted 11 April 2005 - 07:27 AM

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.

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?

### #13Ranger_One  Members

Posted 11 April 2005 - 08:07 AM

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.

### #14/ Genjix   Banned

Posted 11 April 2005 - 09:54 AM

*bump*

### #15justinrwalsh  Members

Posted 11 April 2005 - 10:24 AM

giant error in my source...

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

### #16Starfox  Members

Posted 16 August 2011 - 04:22 PM

Holy crap I started a blog - http://unobvious.typepad.com/

### #17RobTheBloke  Members

Posted 17 August 2011 - 09:59 AM

How would you go about drawing all this shit in OpenGL?

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,