Jump to content

  • Log In with Google      Sign In   
  • Create Account


OBJ Model Loading (In Detail)


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.

  • You cannot reply to this topic
16 replies to this topic

#1 Ranger_One   Members   -  Reputation: 300

Like
0Likes
Like

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

Sponsor:

#2 sjf   Members   -  Reputation: 130

Like
0Likes
Like

Posted 09 April 2005 - 01:52 PM

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

#3 Ranger_One   Members   -  Reputation: 300

Like
0Likes
Like

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]

#4 Ranger_One   Members   -  Reputation: 300

Like
0Likes
Like

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]

#5 Ranger_One   Members   -  Reputation: 300

Like
0Likes
Like

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

#6 CRACK123   Members   -  Reputation: 235

Like
0Likes
Like

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.



#7 justinrwalsh   Members   -  Reputation: 171

Like
0Likes
Like

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

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]

#8 taby   Members   -  Reputation: 336

Like
0Likes
Like

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





#9 justinrwalsh   Members   -  Reputation: 171

Like
0Likes
Like

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.

#10 Ranger_One   Members   -  Reputation: 300

Like
0Likes
Like

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

#11 sjf   Members   -  Reputation: 130

Like
0Likes
Like

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





#12 justinrwalsh   Members   -  Reputation: 171

Like
0Likes
Like

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.

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?

#13 Ranger_One   Members   -  Reputation: 300

Like
0Likes
Like

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   -  Reputation: 100

Like
0Likes
Like

Posted 11 April 2005 - 09:54 AM

*bump*

#15 justinrwalsh   Members   -  Reputation: 171

Like
-1Likes
Like

Posted 11 April 2005 - 10:24 AM

giant error in my source...

Uploading fixed code...

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

#16 Starfox   Members   -  Reputation: 504

Like
0Likes
Like

Posted 16 August 2011 - 04:22 PM

What's the license for this?
Holy crap I started a blog - http://unobvious.typepad.com/

#17 RobTheBloke   Crossbones+   -  Reputation: 2340

Like
0Likes
Like

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,
http://nccastaff.bou...e/ObjLoader.zip

basic C++ library (with beziers, materials, groups and lots of other stuff)
http://nccastaff.bou.../www/libObj.zip

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




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.



PARTNERS