OBJ Model Loading (In Detail)

Started by
15 comments, last by RobTheBloke 12 years, 8 months ago
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...
Advertisement
I can't wait. Way to go...
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]
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]
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)...
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.

The more applications I write, more I find out how less I know
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 = copy.NormalArray;

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

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

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

//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 = right.NormalArray;

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

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

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

//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.X
<< " "
<< data.VertexArray.Y
<< " "
<< data.VertexArray.Z
<< "\n";
}
for(i = 0; i < data.NumTriangle; i++) {
cout << "Indacies into faces:\n";
cout << data.TriangleArray.Vertex[0]
<< " "
<< data.TriangleArray.Vertex[1]
<< " "
<< data.TriangleArray.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]
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);	}}
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.
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 :)

This topic is closed to new replies.

Advertisement