Jump to content

  • Log In with Google      Sign In   
  • Create Account

Interested in a FREE copy of HTML5 game maker Construct 2?

We'll be giving away three Personal Edition licences in next Tuesday's GDNet Direct email newsletter!

Sign up from the right-hand sidebar on our homepage and read Tuesday's newsletter for details!


We're also offering banner ads on our site from just $5! 1. Details HERE. 2. GDNet+ Subscriptions HERE. 3. Ad upload HERE.


3DS parsing tutorial


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
17 replies to this topic

#1 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 13 April 2005 - 02:14 AM

Please pm me any corrections and suggestions, and I'll edit this (I'm open to criticism)... Don't be shy. there seems to be a lot of questions, regarding 3ds loading and since Ranger_One has started writing a tutorial on obj loading I thought I might as well do the same. first of all I would recommend wavefront's obj format over 3ds, for several reasons

obj reasons

  1. Its in a human readable format, so you can use a simple text editor to make changes to the file.
  2. Its a lot more logical, compared to the irratic structure of a 3ds file
  3. There is no redundant data.

3ds reasons

  1. Binary format, therefore it is faster to load, and is smaller.
But there are always people who want to prehaps convert a 3ds file, to their own personal format and 3ds is also a VERY popular format (prehaps even more so than max file format!). The first and foremost thing to remember about the 3ds file, is that everything is ordered hierachially like in xml.
<root>
	<materials>
		<brickwall>
			// ...
		</brickwall>
		
		<mutantskin>
			// ...
		</mutantskin>
	</materials>
	
	<meshes>
		<wall>
			<texture name="brickwall">
				// face list
			</texture>
			
			<facelist>
				<face a="10" b="20" c="8" />
				// other faces
			</facelist>
			
			<vertexlist>
				<vertex x="120" y="320" z="2" />
				// other vertices
			</vertexlist>
			
			// other details like uv coords .etc
		</wall>
		
		// other meshes
	</meshes>
</root>
above you will see a highly simplified version of what I mean. in 3ds however the tags are replaced by chunk headers. Everything in 3ds is organized into chunks. In the example above a chunk would be root, meshes, vertexlist .etc A chunk header takes the form
chunk_beginning_adress:
[Chunk ID - 2 bytes]
[next Chunk offset from 'chunk_beginning_adress' - 4 bytes]
	// ... data
second_chunk_beginning_adress:
[Chunk ID - 2 bytes]
[next Chunk offset from 'second_chunk_beginning_adress' - 4 bytes]
	// ... data
not only that, but chunks can be nested.
c00:
[Chunk ID - 2 bytes]
[next Chunk offset from c00 - 4 bytes]

// data before sub-chunks

	c01:
	[Chunk ID - 2 bytes]
	[next Chunk offset from c01 - 4 bytes]
	
	c02:
	[Chunk ID - 2 bytes]
	[next Chunk offset from c02 - 4 bytes]
	
	// ... c02 data, then sub-chunks
c10:
[Chunk ID - 2 bytes]
[next Chunk offset from c10 - 4 bytes]
	// ...
reading chunks is trivial. lets say, we opened the 3ds file, read the first 2 bytes and realized its not the chunk we want to parse? well, we simply read the next 4 bytes and jump c00 + offset (this is why it is important to store your offset in the file before reading the chunk id's). Even better lets say we read the first chunk under c00, and realized that c01 is not a chunk we are parsing? well we simply read the next 4 bytes, and jump c01 + offset, placing us right before c02's chunk id (at the point its good to store your new offset within the file). Chunk ID's are the equivalent to tag names under xml. Also a side note, the data under chunks ALWAYS comes before the sub-chunks. OK, so lets parse a 3ds file :) lets give you the part of the 3ds file you will parse (you will of course need a 3ds file). we are just going to read a few chunk headers.
<main3ds>
	// no data under root chunk
	<version />
	<editor3d />
</main3ds>
we are going to read main3ds chunk, then start reading version sub-chunk, then skip to editor 3d chunk.
#include <fstream>
#include <iostream>
using namespace std;

int main(int argc , char **argv)
{
	if(argc < 2)
		return -1;

	ifstream file(argv[1] , ios::binary);
	short id;	// hopefully short is 2 bytes on your system
	int length;	// and an int is 4 bytes :)
	
	int chunk_beginning;

	// main3ds
	file.read((char*)&id , 2);
	cout << "id = (" << hex << id << ")\n";
	file.read((char*)&length , 4);
	cout << "length = (" << dec << length << ")\n\n";

	// version
	chunk_beginning = file.tellg();
	file.read((char*)&id , 2);
	cout << "id = (" << hex << id << ")\n";
	file.read((char*)&length , 4);
	cout << "length = (" << dec << length << ")\n\n";

	// 3d editor chunk
	cout << "(chunk_beginning + offset) = ("
		<< chunk_beginning + length << ")\n";
	file.seekg(chunk_beginning + length);
	
	file.read((char*)&id , 2);
	cout << "id = (" << hex << id << ")\n";
	file.read((char*)&length , 4);
	cout << "length = (" << dec << length << ")\n";

	return 0;
}
the output is:
genjix@linux:~/media/tmp> ./notniceparser heavy.3ds
id = (4d4d)
length = (37624)

id = (2)
length = (10)

(chunk_beginning + offset) = (16)
id = (3d3d)
length = (37608)
i should mention that I spent a long time, searching for a bug when i ported my parser to win32 - tellg() returns strange results without the ios::binary flag :S some 3ds files may have different output with the above program, so get a different 3ds file (all the 3ds files ive tested so far, have given same result, but they may have a different chunk ordering). No this is all well and good, but its not a nice way to parse a 3ds file :/ Taking our comparison with xml, most xml parsers work like this
<main3ds>
	// no data under root chunk
	<version />
	<editor3d />
</main3ds>
	Xml::Document doc("doc.xml");
	
	Xml::Element root = doc.Child();

	if(root.Name() == "main3ds")
	{
		for(Xml::Element child = root.Child() ;
			child ; child = child.Sibling())
		{
			// i know switches only work on integral types
			// this is semi-pseudo code
			switch(child.Name())
			{
				case("version"):
					cout << "id = (" << child.ID() << ")\n";
					cout << "length = ("
						<< child.Length() << ")\n";
					// do stuff
				break;
			
				case("editor3d"):
					cout << "id = (" << child.ID() << ")\n";
					cout << "length = ("
						<< child.Length() << ")\n";
					// do stuff
				break;
			}
		}
	}
	else
	{
		// parse error!
	}
this is how the above example could be coded in our imaginary world. You may complain how that is longer and more lines, but that sure as hell, is easier to understand. Now imagine that instead of strings and names we had shorts and ID's. That would then become.
	Model3DSFile doc("doc.xml");
	
	Model3DSChunk root = doc.Child();

	if(root.ID() == 0x4d4d)	// main3ds
	{
		for(Model3DSChunk child = root.Child() ;
			child ; child = child.Sibling())
		{
			// i know switches only work on integral types
			// this is semi-pseudo code
			switch(child.ID())
			{
				case(0x0002):	// version
					cout << "id = (" << child.ID() << ")\n";
					cout << "length = ("
						<< child.Length() << ")\n";
					// do stuff
				break;
			
				case(0x3d3d):	// editor3d
					cout << "id = (" << child.ID() << ")\n";
					cout << "length = ("
						<< child.Length() << ")\n";
					// do stuff
				break;
			}
		}
	}
	else
	{
		// parse error!
	}
to keep you happy, here are the header files for Model3DSFile and Model3DSChunk, until my next tutorial.
#ifndef FILE_H
#define FILE_H

#include <fstream>

#include "chunk.h"

/***/
class Model3DSFile
{
    public:
	/***/
	Model3DSFile(const char *src);
	/***/
	~Model3DSFile();

	Model3DSChunk Child();
    private:
	static int FileSize(std::ifstream &file);
	
	std::ifstream file;
};

#endif







#ifndef CHUNK_H
#define CHUNK_H

#include <fstream>
#include <string>

/***/
class Model3DSChunk
{
    public:
	/***/
	Model3DSChunk(std::ifstream &infile , int csend);
	/***/
	Model3DSChunk(const Model3DSChunk &chunk);
	/***/
	~Model3DSChunk();

	/**bug : making 2 seperate file stream chunks = each other*/
	void operator=(const Model3DSChunk &chunk);

	operator bool();

	int ID();

	short Short();
	int Int();
	float Float();
	std::string Str();

	Model3DSChunk Child();
	Model3DSChunk Sibling();
    private:
	std::ifstream &file
	
	int chunkset_end;
	int id , begin , end;
};

#endif







[Edited by - Genjix on April 15, 2005 6:50:55 AM]

Sponsor:

#2 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 14 April 2005 - 05:29 AM

if anyone thinks its worth me finishing this tutorial, tell me otherwise ill just post source code to parser.

#3 _DarkWIng_   Members   -  Reputation: 602

Like
0Likes
Like

Posted 14 April 2005 - 05:46 AM

I belive you should finish it. And some mod should add this (and OBJ) thread to FAQ.

And just one note: There are also upsides compared to .OBJ files. The main one would be the fact that data in 3ds files is stored in binary format and this makes them much smaller and alot faster to load.
You should never let your fears become the boundaries of your dreams.

#4 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 14 April 2005 - 07:36 AM

ok, so here goes then (certainly spurred me on ).

now, when we create a chunk we need to know where the end of the parent is, so we know once we've stopped parsing sibling chunks.


<root>
<teacup />
<nine />
<farah />
</root>


once we reach farah, we also reach the end of root.

so we open up our 3ds file. When we start parsing, we pass a reference of the current ifstream to the root chunk (stored as a const reference), and the filesize in bytes.
The end of the file is also the end of the child chunks - although a 3ds file should only have one root chunk (rather like xml).

the code for this goes something like this

/*open file stream*/
ifstream file(src , ios::in|ios::binary);

/*get child node*/
file.seekg(0);
Chunk c(file , FileSize(file));


so adding creating the file loader (you will notice I've now wrapped it in a namespace)

#ifndef FILE_H
#define FILE_H

#include <fstream>

#include "chunk.h"

namespace Model3DS
{

/***/
class File
{
public:
/***/
File(const char *src);
/***/
~File();

Model3DS::Chunk Child();
private:
static int FileSize(std::ifstream &file);

std::ifstream file;
};

}

#endif


and the source

#include "file.h"
using namespace Model3DS;

#include <iostream>
using namespace std;

File::File(const char *src)
: file(src , ios::in|ios::binary) // open the file stream
{
}
File::~File()
{
file.close(); // close the file stream
}

Chunk File::Child()
{
file.seekg(0); // seek to beginning before parse
return Chunk(file , FileSize(file));// give current fstream and chunk end
}

/*calculates the file size by seeking to end and finding offset from beginning
of the file stream*/
int File::FileSize(ifstream &file)
{
int curr_pos = file.tellg();

file.seekg(0);
int beginning = file.tellg();
file.seekg(0 , ios::end);
int ending = file.tellg();

file.seekg(curr_pos);

return ending - beginning; // ;)
}



#5 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 14 April 2005 - 07:57 AM

now what do we need for a chunk parser?

  • get ID() of current chunk for identifying chunk (rather like name of xml element)

  • get Child() chunk

  • move to Sibling() chunk

  • check whether current chunk is valid (whether reached end of parent)

the last point you may not understand.

<root>
<child />
<child />
<child />
</root>

for(Xml::Element child = root("child") ; child ; child = child.Sibling("child"))
{
// ... do stuff
}

that piece of code will loop 3 times. On the fourth time round the boolean test on child will be false, because there are no more <child /> tags underneath <root />.

<root>
<child />
<child />
<child />
</root> <child />
<root1>


its like if you have the size of the whole <root /> chunk, then at offset of root in file in bytes (p), and size of root in bytes (q), then

p + q = c4 = r1

where c4 is the poisiton of the fourth child chunk and r1 is the position of the next root chunk (<root1 />).

so when we create a sibling, and the test on the sibling is done we must test

return (begin < chunkset_end) && (begin >= 0);

the (begin >= 0), tests if we have reached end of file (as begin will = -1 when on eof and you call tellg()).

here is the watered down header for chunk.h

#ifndef CHUNK_H
#define CHUNK_H

#include <fstream>
#include <string>

namespace Model3DS
{

/***/
class Chunk
{
public:
/***/
Chunk(std::ifstream &infile , int csend);
/***/
Chunk(const Chunk &chunk);
/***/
~Chunk();

/**bug : making 2 seperate file stream chunks = each other*/
void operator=(const Chunk &chunk);

operator bool();

int ID();

Chunk Child();
Chunk Sibling();
private:
std::ifstream &file;

int chunkset_end;
int id , begin , end;
};

}

#endif


everything else, may be safely ignored for time being.

#6 Drew_Benton   Crossbones+   -  Reputation: 1716

Like
0Likes
Like

Posted 14 April 2005 - 08:02 AM

Nice work! I would also suggest to you making a .DOC/.HTML version formated and colored [wink]. I have yet to read though it, but new tutorials are always good. When I get a chance to read it, I'll give you some feedback.

#7 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 14 April 2005 - 08:18 AM

so lets start filling in the methods.

first of all when we create a chunk, we want to know where we are.

Chunk::Chunk(ifstream &infile , int csend)
: file(infile) , chunkset_end(csend)
{
//cout << "Chunk::Chunk()\n";
begin = file.tellg(); // get chunk beginning position

id = 0; // zero out upper bytes
file.read((char*)&id , 2); // read chunk id

file.read((char*)&end , 4); // read chunk end offset
end += begin; // compute absolute position

// debugging info :)
/*cout << "id = (" << hex << id << ")\t" << dec
<< "begin = (" << begin << ")\t"
<< "end = (" << end << ")\t"
<< "chunkset_end = (" << chunkset_end << ")\n";*/
}
Chunk::Chunk(const Chunk &chunk)
: file(chunk.file) ,
chunkset_end(chunk.chunkset_end) ,
id(chunk.id) , begin(chunk.begin) , end(chunk.end)
{
//cout << "Chunk::Chunk(const Chunk &chunk)\n";
}
Chunk::~Chunk()
{
}

they are simple and easy enough to understand. notice we zero out id before filling in the bottom 2 bytes.
for those who may not know much low level stuff, this is what happens

[ 255 ][ 10 ][ 211 ][ 3 ] // before zeroing, it has random garbage
[ 0 ][ 0 ][ 0 ][ 0 ] // zero whole thing out
[ 0 ][ 0 ][ X ][ X ] // read info into bottom 2 bytes

otherwise what will happen is that the first 2 bytes will be filled with garbage.

my void Chunk::operator=(const Chunk &chunk); is not so clean -

void Chunk::operator=(const Chunk &chunk)
{
//cout << "Chunk::operator=(const Chunk &chunk)\n";

/*if chunk.file is opened on a seperate file...*/
file.seekg(chunk.file.tellg());

chunkset_end = chunk.chunkset_end;
id = chunk.id;
begin = chunk.begin;
end = chunk.end;
}

anyone spot the bug? what do you think would happen were you to have open 2 seperate 3ds chunk file streams and called operator=() on them? well since the first file stream wouldn't be changed to the second, you'd end up positioned where the second chunk is in their file stream, in this file stream!
This is ok, and poses no real threat however when we write the parser wrapper (as it opens a the file stream, parses the chunks and when the function exits, Model3DS::File is destroyed and hence the file stream is destroyed.

the test, to see whether whether this chunk, is at the end of the parent chunk (i.e this sibling and all the next siblings are invalid), we saw above

Chunk::operator bool()
{
/*cout << "Chunk::operator bool() == "
<< ((begin < chunkset_end) && (begin >= 0)) << "\n"
<< "(" << begin << " < " << chunkset_end << ")\n";*/
return (begin < chunkset_end) && (begin >= 0);
}


we parsed the id, at the chunk headers and stored it in the variable id, so the only thing to do in the ID() method is to return id.

int Chunk::ID()
{
return id;
}


you remember in the constructor we passed the end of the parent chunk as an absolute position in the constructor above, where it is used in the boolean test (also above)?

Chunk Chunk::Child()
{
//cout << "end = (" << end << ")\n";
return Chunk(file , end); // pass end of this 'father' chunk
}
Chunk Chunk::Sibling()
{
file.seekg(end); // travel to beginning of next chunk
//cout << "seek = (" << end << ")\n";
return Chunk(file , chunkset_end);// pass this chunks fathers end position
}


#8 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 14 April 2005 - 08:51 AM

arlier on I mentioned that data comes before child chunks. "But whats data?" I hear you ask.

<root>
<littleperson>
string name = "jessica simpson\0";
float age = 13.3141592654;
int normal = 2;
float x = 120;
float y = 230;
float z = 480;

<toys />
<toys />
<toys />
</littleperson>

<vertices>
short number_of_vertices = 4;

float x = ...;
float y = ...;
float z = ...;

float x = ...;
float y = ...;
float z = ...;

float x = ...;
float y = ...;
float z = ...;

float x = ...;
float y = ...;
float z = ...;
</vertices>
</root>

under littleperson, the data is name , age , normal , x , y and z;
the size of the data before we get to start parsing the child chunks in this case would be

sizeof(data) = sizeof(name) + sizeof(age) + sizeof(normal) + sizeof(x)
+ sizeof(y) + sizeof(z)
= sizeof(16*sizeof(char)) + sizeof(float) + sizeof(int)
+ sizeof(float) + sizeof(float) + sizeof(float)
= 16 + 4 + 4 + 4 + 4 + 4
= 38

so we have to parse 38 bytes when we begin in littleperson before we start reading the child chunks no matter what. Even if we don't want that info.

in the vertices node we'd read that short, and know to loop 4 times, read 3 sets of floats.

have you also noticed how the string was terminated by \0? strings in a 3ds file are simple c style nul terminated strings.

here is our new header file with our new data getting functions.

#ifndef CHUNK_H
#define CHUNK_H

#include <fstream>
#include <string>

namespace Model3DS
{

/***/
class Chunk
{
public:
/***/
Chunk(std::ifstream &infile , int csend);
/***/
Chunk(const Chunk &chunk);
/***/
~Chunk();

/**bug : making 2 seperate file stream chunks = each other*/
void operator=(const Chunk &chunk);

operator bool();

int ID();

short Short();
int Int();
float Float();
std::string Str();

Chunk Child();
Chunk Sibling();
private:
std::ifstream &file

int chunkset_end;
int id , begin , end;
};

}

#endif

How complicated was that!!!11!1!!11 4 new functions?
Also remember this 2 handy pieces of advice -

  • always parse the data first

  • you need to know how much data to read before parsing the child chunks,
    otherwise things will go haywire because the child chunk you'll be reading
    will be offset backwards incorrectly



short Chunk::Short()
{
short s = 0; // zero all variables if worried about 64 bit
file.read((char*)&s , 2);
return s;
}
int Chunk::Int()
{
int i;
file.read((char*)&i , 4);
return i;
}
float Chunk::Float()
{
float f;
file.read((char*)&f , 4);
return f;
}
string Chunk::Str()
{
string s;
char c;

do
{
file.read(&c , 1);
s += c;
}while(c != '\0');

return s;
}

if you are worried about the sizes of ints and other data types on different machines (64 bit), just do what i have done with the Short() method, otherwise its fine.

[Edited by - Genjix on April 15, 2005 6:51:51 AM]

#9 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 14 April 2005 - 09:00 AM

Time to take a step back, and look at what we have so far.

#ifndef FILE_H
#define FILE_H

#include <fstream>

#include "chunk.h"

namespace Model3DS
{

/***/
class File
{
public:
/***/
File(const char *src);
/***/
~File();

Model3DS::Chunk Child();
private:
static int FileSize(std::ifstream &file);

std::ifstream file;
};

}

#endif




#include "file.h"
using namespace Model3DS;

#include <iostream>
using namespace std;

File::File(const char *src)
: file(src , ios::in|ios::binary)
{
}
File::~File()
{
cout << "File::~File()\n";
file.close();
}

Chunk File::Child()
{
file.seekg(0);
return Chunk(file , FileSize(file));
}

int File::FileSize(ifstream &file)
{
int curr_pos = file.tellg();

file.seekg(0);
int beginning = file.tellg();
file.seekg(0 , ios::end);
int ending = file.tellg();

file.seekg(curr_pos);

return ending - beginning; // ;)
}




#ifndef CHUNK_H
#define CHUNK_H

#include <fstream>
#include <string>

namespace Model3DS
{

/***/
class Chunk
{
public:
/***/
Chunk(std::ifstream &infile , int csend);
/***/
Chunk(const Chunk &chunk);
/***/
~Chunk();

/**bug : making 2 seperate file stream chunks = each other*/
void operator=(const Chunk &chunk);

operator bool();

int ID();

short Short();
int Int();
float Float();
std::string Str();

Chunk Child();
Chunk Sibling();
private:
std::ifstream &file

int chunkset_end;
int id , begin , end;
};

}

#endif




#include "chunk.h"
using namespace Model3DS;

#include <iostream>
using namespace std;

Chunk::Chunk(ifstream &infile , int csend)
: file(infile) , chunkset_end(csend)
{
//cout << "Chunk::Chunk()\n";
begin = file.tellg();

id = 0; // zero out upper bytes
file.read((char*)&id , 2);

file.read((char*)&end , 4);
end += begin; // compute absolute position

/*cout << "id = (" << hex << id << ")\t" << dec
<< "begin = (" << begin << ")\t"
<< "end = (" << end << ")\t"
<< "chunkset_end = (" << chunkset_end << ")\n";*/

}
Chunk::Chunk(const Chunk &chunk)
: file(chunk.file) ,
chunkset_end(chunk.chunkset_end) ,
id(chunk.id) , begin(chunk.begin) , end(chunk.end)
{
//cout << "Chunk::Chunk(const Chunk &chunk)\n";
}
Chunk::~Chunk()
{
}

void Chunk::operator=(const Chunk &chunk)
{
//cout << "Chunk::operator=(const Chunk &chunk)\n";

file.seekg(chunk.file.tellg());

chunkset_end = chunk.chunkset_end;
id = chunk.id;
begin = chunk.begin;
end = chunk.end;
}

Chunk::operator bool()
{
int curr_pos = file.tellg();
/*cout << "Chunk::operator bool() == "
<< ((begin < chunkset_end) && (begin >= 0)) << "\n"
<< "(" << begin << " < " << chunkset_end << ")\n";*/

return (begin < chunkset_end) && (begin >= 0);
}

int Chunk::ID()
{
return id;
}

short Chunk::Short()
{
short s;
file.read((char*)&s , 2);
return s;
}
int Chunk::Int()
{
int i;
file.read((char*)&i , 4);
return i;
}
float Chunk::Float()
{
float f;
file.read((char*)&f , 4);
return f;
}
string Chunk::Str()
{
string s;
char c;

do
{
file.read(&c , 1);
s += c;
}while(c != '\0');

return s;
}

Chunk Chunk::Child()
{
//cout << "end = (" << end << ")\n";
return Chunk(file , end);
}
Chunk Chunk::Sibling()
{
file.seekg(end);// travel to next chunk
//cout << "seek = (" << end << ")\n";
return Chunk(file , chunkset_end);
}




and for copy-pasting
chunk/file.h

#ifndef FILE_H
#define FILE_H

#include <fstream>

#include "chunk.h"

namespace Model3DS
{

/***/
class File
{
public:
/***/
File(const char *src);
/***/
~File();

Model3DS::Chunk Child();
private:
static int FileSize(std::ifstream &file);

std::ifstream file;
};

}

#endif

chunk/file.cpp

#include "file.h"
using namespace Model3DS;

#include <iostream>
using namespace std;

File::File(const char *src)
: file(src , ios::in|ios::binary)
{
}
File::~File()
{
cout << "File::~File()\n";
file.close();
}

Chunk File::Child()
{
file.seekg(0);
return Chunk(file , FileSize(file));
}

int File::FileSize(ifstream &file)
{
int curr_pos = file.tellg();

file.seekg(0);
int beginning = file.tellg();
file.seekg(0 , ios::end);
int ending = file.tellg();

file.seekg(curr_pos);

return ending - beginning; // ;)
}

chunk/chunk.h

#ifndef CHUNK_H
#define CHUNK_H

#include <fstream>
#include <string>

namespace Model3DS
{

/***/
class Chunk
{
public:
/***/
Chunk(std::ifstream &infile , int csend);
/***/
Chunk(const Chunk &chunk);
/***/
~Chunk();

/**bug : making 2 seperate file stream chunks = each other*/
void operator=(const Chunk &chunk);

operator bool();

int ID();

short Short();
int Int();
float Float();
std::string Str();

Chunk Child();
Chunk Sibling();
private:
std::ifstream &file

int chunkset_end;
int id , begin , end;
};

}

#endif

chunk/chunk.cpp

#include "chunk.h"
using namespace Model3DS;

#include <iostream>
using namespace std;

Chunk::Chunk(ifstream &infile , int csend)
: file(infile) , chunkset_end(csend)
{
//cout << "Chunk::Chunk()\n";
begin = file.tellg();

id = 0; // zero out upper bytes
file.read((char*)&id , 2);

file.read((char*)&end , 4);
end += begin; // compute absolute position

/*cout << "id = (" << hex << id << ")\t" << dec
<< "begin = (" << begin << ")\t"
<< "end = (" << end << ")\t"
<< "chunkset_end = (" << chunkset_end << ")\n";*/
}
Chunk::Chunk(const Chunk &chunk)
: file(chunk.file) ,
chunkset_end(chunk.chunkset_end) ,
id(chunk.id) , begin(chunk.begin) , end(chunk.end)
{
//cout << "Chunk::Chunk(const Chunk &chunk)\n";
}
Chunk::~Chunk()
{
}

void Chunk::operator=(const Chunk &chunk)
{
//cout << "Chunk::operator=(const Chunk &chunk)\n";

file.seekg(chunk.file.tellg());

chunkset_end = chunk.chunkset_end;
id = chunk.id;
begin = chunk.begin;
end = chunk.end;
}

Chunk::operator bool()
{
int curr_pos = file.tellg();
/*cout << "Chunk::operator bool() == "
<< ((begin < chunkset_end) && (begin >= 0)) << "\n"
<< "(" << begin << " < " << chunkset_end << ")\n";*/
return (begin < chunkset_end) && (begin >= 0);
}

int Chunk::ID()
{
return id;
}

short Chunk::Short()
{
short s = 0;
file.read((char*)&s , 2);
return s;
}
int Chunk::Int()
{
int i;
file.read((char*)&i , 4);
return i;
}
float Chunk::Float()
{
float f;
file.read((char*)&f , 4);
return f;
}
string Chunk::Str()
{
string s;
char c;

do
{
file.read(&c , 1);
s += c;
}while(c != '\0');

return s;
}

Chunk Chunk::Child()
{
//cout << "end = (" << end << ")\n";
return Chunk(file , end);
}
Chunk Chunk::Sibling()
{
file.seekg(end);// travel to next chunk
//cout << "seek = (" << end << ")\n";
return Chunk(file , chunkset_end);
}


#10 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 14 April 2005 - 09:06 AM

before we get onto the full blown parser lets have a quick test

#include <iostream>
using namespace std;

#include "3ds/3ds.h"

namespace Level5
{

void FacesMaterials(Model3DSChunk c)
{
cout << "\t\t\t\tmaterial name = (" << c.Str() << ")\n";

short n_faces = c.Short();
for(short i = 0 ; i < n_faces ; i++)
{
cout << "\t\t\t\tmaterial assigned to face " << c.Short() << "\n";
}
}

}

namespace Level4
{

void LocalCoordinateSystem(Model3DSChunk c)
{
float matrix[4][3];

for(int i = 0 ; i < 4 ; i++)
{
cout << "\t\t\t(";
for(int j = 0 ; j < 3 ; j++)
{
matrix[i][j] = c.Float();
cout << matrix[i][j];

if(j < 2)
cout << " , ";
}
cout << ")\n";
}
}

void Vertices(Model3DSChunk c)
{
short n_vertices = c.Short();

float x , y , z;

cout << dec << "\t\t\tn_vertices = (" << n_vertices << ")\n";
for(short i = 0 ; i < n_vertices ; i++)
{
x = c.Float();
y = c.Float();
z = c.Float();

cout << "\t\t\tv[" << i << "] = ("
<< x << "," << y << "," << z << ")\n";
}
}

void TextureCoords(Model3DSChunk c)
{
short n_texcoords = c.Short();

float u , v;

cout << dec << "\t\t\tn_texcoords = (" << n_texcoords << ")\n";
for(short i = 0 ; i < n_texcoords ; i++)
{
u = c.Float();
v = c.Float();

cout << "\t\t\ttexcoord[" << i << "] = ("
<< u << "," << v << ")\n";
}
}

void TriangleFaces(Model3DSChunk c)
{
short n_faces = c.Short();

short fa , fb , fc , flag;

cout << dec << "\t\t\tn_faces = (" << n_faces << ")\n";
for(short i = 0 ; i < n_faces ; i++)
{
fa = c.Short();
fb = c.Short();
fc = c.Short();
flag = c.Short();

cout << "\t\t\tface[" << i << "] = ("
<< fa << "," << fb << "," << fc << ",{" << flag << "})\n";
}

for(Model3DSChunk cc = c.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0x4130):
cout << "\t\t\tfaces materials\n";
Level5::FacesMaterials(cc);
break;

default:
cout << "\t\t\terror : unknown chunk "
<< hex << cc.ID() << dec << "\n";
break;
}
}
}

}

namespace Level3
{

void ObjectTriangleMesh(Model3DSChunk c)
{
for(Model3DSChunk cc = c.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0x4160):
cout << "\t\tlocal coordinate system\n";
Level4::LocalCoordinateSystem(cc);
break;

case(0x4110):
cout << "\t\tvertices\n";
Level4::Vertices(cc);
break;

case(0x4140):
cout << "\t\tuv vertices\n";
Level4::TextureCoords(cc);
break;

case(0x4120):
cout << "\t\ttriangles\n";
Level4::TriangleFaces(cc);
break;

default:
cout << "\t\terror : unknown chunk " << cc.ID() << "\n";
break;
}
}
}

}

namespace Level2
{

void MeshVersion(Model3DSChunk c)
{
cout << "\tmesh version = (" << c.Int() << ")\n";
}

void EditMaterial(Model3DSChunk c)
{
string name;
for(Model3DSChunk mat_name = c.Child() ; mat_name ;
mat_name = mat_name.Sibling())
{
switch(mat_name.ID())
{
case(0xa000):
name = mat_name.Str();
cout << "\tmaterial_name = (" << name << ")\n";
break;

default:
cout << "\terror : unknown chunk "
<< mat_name.ID() << "\n";
break;
}
}
}

void EditObject(Model3DSChunk c)
{
cout << "\tobject name = (" << c.Str() << ")\n";

for(Model3DSChunk cc = c.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0x4100):
cout << "\tobject tri mesh\n";
Level3::ObjectTriangleMesh(cc);
break;

case(0x4600):
cout << "\tobject light\n";
break;

case(0x4700):
cout << "\tobject camera\n";
break;

default:
cout << "\terror : unknown object "
<< hex << cc.ID() << dec << "\n";
break;
}
}
}

}

namespace Level1
{

void Version(Model3DSChunk c)
{
cout << "version = (" << c.Int() << ")\n";
}

void Editor3D(Model3DSChunk c)
{
for(Model3DSChunk cc = c.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0x3d3e):
cout << "mesh version\n";
Level2::MeshVersion(cc);
break;

case(0xafff):
cout << "edit material\n";
Level2::EditMaterial(cc);
break;

case(0x4000):
cout << "edit object\n";
Level2::EditObject(cc);
break;

default:
cout << "error : unknown chunk " << cc.ID() << "\n";
break;
}
}
}

}

int main(int argc , char **argv)
{
if(argc < 2)
return -1;

Model3DSFile file(argv[1]);

Model3DSChunk root = file.Child();

cout << "id = (" << hex << root.ID() << ")\n";

for(Model3DSChunk cc = root.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0x0002):
Level1::Version(cc);
break;

case(0x3d3d):
Level1::Editor3D(cc);
break;

default:
cout << "error : unknown chunk in main\n";
break;
}
}

return 0;
}
please not, I know the above main.cpp is not a good example of coding, but I wrote it in one sitting as a demonstration of the using the above classes to parse a 3ds file

#11 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 15 April 2005 - 11:35 PM

ok so here is the 3ds structure we are going to parse

string - nul terminated byte set
short - 2 byte
int - 4 byte
float - 4 byte

<root>
<version 0x0002>
int version;
</version>

<editor3d 0x3d3d>
<meshversion 0x3d3e>
int meshversion;
</meshversion>

<editmaterial 0xafff>
// look below
</editmaterial>

<editobject 0x4000>
string objectname;
// look below
</editobject>
</editor3d>
</root>


edit material chunk (0xafff)

<editmaterial 0xafff>
<materialname 0xa000>
string materialname;
</materialname>

<shininesspercent 0xa040>
// either a 0x0030 or 0x0031 chunk
<percentint 0x0030>
int percent;
</percentint>
<percentfloat 0x0030>
int percent;
</percentfloat>
</shininesspercent>

<transparencypercent 0xa050>
// either a 0x0030 or 0x0031 chunk
<percentint 0x0030>
int percent;
</percentint>
<percentfloat 0x0030>
int percent;
</percentfloat>
</transparencypercent>

<texturemap1 0xa200>
<texturesource 0xa300>
string imagesrc;
</texturesource>
</texturemap1>

<texturemap2 0xa200>
<texturesource 0xa300>
string imagesrc;
</texturesource>
</texturemap2>
</editmaterial>


edit object chunk (0x4000)

<editobject 0x4000>
string objectname;

// these child chunks, decide what this object is
<trianglemesh 0x4100>
// objects transform matrix (4x3 - last row is 0 0 0 1)
<localcoordinatesystemmatrix 0x4160>
// 12 floats - go figure
float x00;
float x01;
...
</localcoordinatesystemmatrix>

<vertexlist 0x4110>
short numberofvertices;

float x;
float y;
float z;
// ... repeat numberofvertices times;
</vertexlist>

<texturecoordlist>
short numberoftexcoords;

float u;
float v;
// ... repeat numberoftexcoords times
</texturecoordlist>

<facelist>
short numberoffaces;

float va; // index to vertex array
float vb;
float vc;
// ... repeat numberoffaces times

<facesmaterials 0x4130>
string materialname;
short numberoffaces;

short face;
// ... repeat numberoffaces times
</facesmaterials>
</facelist>
</trianglemesh>

<light 0x4600>
float x;
float y;
float z;

/* finding this means this is a spotlight
not an omni */
<spotlight 0x4610>
float targetx;
float targety;
float targetz;
</spotlight>
</light>

<camera 0x4700>
// i am going to ignore this
</camera>
</editobject>


also note that multiple objects exist as multiple editobject chunks, not multiple trianglemesh chunks (which is why objectname is in editobject chunk).

These are the things we will be parsing.
So, here in a list as well -

  • lights

  • materials

  • meshes

  • (im considering doing fog support)


And things it will not support

  • camera's

  • keyframing info



also note that the parser stores scene info internally using stl as it is a parser NOT a renderer (the info is meant to be read into the scenegraph, not used in the scenegraph).

[Edited by - Genjix on April 16, 2005 12:35:49 PM]

#12 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 16 April 2005 - 06:15 AM


#ifndef SCENE3DS_H
#define SCENE3DS_H

#include <string>

#include "light/light3ds.h"
#include "material/material3ds.h"
#include "mesh/mesh3ds.h"

/***/
class Scene3DS : public Light3DS , public Material3DS , public Mesh3DS
{
public:
/***/
Scene3DS(const char *src);
/***/
Scene3DS(const std::string &src);
/***/
~Scene3DS();

void operator=(const char *src);
private:

void ParseEditor3D(Model3DSChunk c);
void ParseEditObject(Model3DSChunk c);
};

#endif



#include "scene3ds.h"

#include <iostream>
using namespace std;

#include "chunk/file.h"
#include "chunk/chunk.h"

Scene3DS::Scene3DS(const char *src)
{
operator=(src);
}
Scene3DS::Scene3DS(const string &src)
{
operator=(src.c_str());
}
Scene3DS::~Scene3DS()
{
}

void Scene3DS::operator=(const char *src)
{
Model3DSFile file(src);

Model3DSChunk root = file.Child();

for(Model3DSChunk cc = root.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0x0002):
// parse int which has version info
break;

case(0x3d3d):
ParseEditor3D(cc);
break;

default:
break;
}
}
}

void Scene3DS::ParseEditor3D(Model3DSChunk c)
{
for(Model3DSChunk cc = c.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0x3d3e):
// mesh version
break;

case(0xafff):
Material3DS::Parse(cc);
break;

case(0x4000):
ParseEditObject(cc);
break;

default:
break;
}
}
}

void Scene3DS::ParseEditObject(Model3DSChunk c)
{
string obj_name = c.Str();

for(Model3DSChunk cc = c.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0x4100):
Mesh3DS::Parse(obj_name , cc);
break;

case(0x4600):
Light3DS::Parse(obj_name , cc);
break;

case(0x4700):
cout << "\tobject camera\n";
break;

default:
cout << "\terror : unknown object "
<< hex << cc.ID() << dec << "\n";
break;
}
}
}


this basically parses the main scene, making relevant calls from its inherited bases.

class Scene3DS : public Light3DS , public Material3DS , public Mesh3DS



#13 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 16 April 2005 - 06:17 AM


#ifndef MESH3DS_H
#define MESH3DS_H

#include <map>
#include <string>

#include "../chunk/chunk.h"

#include "mesh3dsobject.h"

/***/
class Mesh3DS
{
public:
/***/
Mesh3DS();
/***/
~Mesh3DS();

void Parse(const std::string &name , Model3DSChunk c);

const std::map<std::string , Mesh3DSObject> &Meshes();
private:

std::map<std::string , Mesh3DSObject> meshes;
};

#endif



#include "mesh3ds.h"

#include <iostream>
using namespace std;

Mesh3DS::Mesh3DS()
{
}
Mesh3DS::~Mesh3DS()
{
}

void Mesh3DS::Parse(const string &name , Model3DSChunk c)
{
cout << "Mesh3DS::Parse(" << name << ",c)\n";

Mesh3DSObject mesh(c);
meshes[name] = mesh;
}

const std::map<std::string , Mesh3DSObject> &Mesh3DS::Meshes()
{
return meshes;
}



and the Mesh3DSObject itself

#ifndef MESH3DSOBJECT_H
#define MESH3DSOBJECT_H

#include <map>
#include <string>
#include <vector>

#include "../chunk/chunk.h"

struct Mesh3DSVertex
{
float x , y , z;
};
struct Mesh3DSTextureCoord
{
float u , v;
};
struct Mesh3DSFace
{
int a , b , c;
};
struct Mesh3DSMatrix
{
float m[4][4];
};

/***/
class Mesh3DSObject
{
public:
/***/
Mesh3DSObject();
/***/
Mesh3DSObject(Model3DSChunk c);
/***/
Mesh3DSObject(const Mesh3DSObject &mesh);
/***/
~Mesh3DSObject();

void operator=(const Mesh3DSObject &mesh);

Mesh3DSMatrix Matrix(); // adds last row (0,0,0,1)

const std::vector<Mesh3DSVertex> &Vertices();
const std::vector<Mesh3DSTextureCoord> &TextureCoords();
const std::vector<Mesh3DSFace> &Faces();
const std::map<std::string , std::vector<int> > &Materials();

private:

void ParseLocalCoordinateSystem(Model3DSChunk c);
void ParseVertices(Model3DSChunk c);
void ParseTextureCoords(Model3DSChunk c);
void ParseFaces(Model3DSChunk c);

void ParseFacesMaterials(Model3DSChunk c);

Mesh3DSMatrix matrix;

std::vector<Mesh3DSVertex> vertices;
std::vector<Mesh3DSTextureCoord> texturecoords;
std::vector<Mesh3DSFace> faces;
std::map<std::string , std::vector<int> > material_faces;
};

#endif



#include "mesh3dsobject.h"

#include <iostream>
using namespace std;

Mesh3DSObject::Mesh3DSObject()
{
}
Mesh3DSObject::Mesh3DSObject(Model3DSChunk c)
{
for(Model3DSChunk cc = c.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0x4160):
ParseLocalCoordinateSystem(cc);
break;

case(0x4110):
ParseVertices(cc);
break;

case(0x4140):
ParseTextureCoords(cc);
break;

case(0x4120):
ParseFaces(cc);
break;

default:
cout << "\t\terror : unknown chunk " << cc.ID() << "\n";
break;
}
}
}
Mesh3DSObject::Mesh3DSObject(const Mesh3DSObject &mesh)
{
operator=(mesh);
}
Mesh3DSObject::~Mesh3DSObject()
{
}

void Mesh3DSObject::operator=(const Mesh3DSObject &mesh)
{
matrix = mesh.matrix;
vertices = mesh.vertices;
texturecoords = mesh.texturecoords;
faces = mesh.faces;
material_faces = mesh.material_faces;
}

void Mesh3DSObject::ParseLocalCoordinateSystem(Model3DSChunk c)
{
// bottom row should be (0 , 0 , 0 , 1)

// populate matrix
for(int i = 0 ; i < 4 ; i++)
{
for(int j = 0 ; j < 3 ; j++)
{
matrix.m[i][j] = c.Float();
}
}

// output matrix to the screen
for(int j = 0 ; j < 3 ; j++)
{
cout << "\t\t\t(";
for(int i = 0 ; i < 4 ; i++)
{
cout << matrix.m[i][j];

if(i < 3)
cout << " , ";
}
cout << ")\n";
}

matrix.m[0][3] = 0;
matrix.m[1][3] = 0;
matrix.m[2][3] = 0;
matrix.m[3][3] = 1;
cout << "\t\t\t(0 , 0 , 0 , 1)\n";
}
void Mesh3DSObject::ParseVertices(Model3DSChunk c)
{
int n_vertices = c.Short();

Mesh3DSVertex v;

cout << dec << "\t\t\tn_vertices = (" << n_vertices << ")\n";
for(int i = 0 ; i < n_vertices ; i++)
{
v.x = c.Float();
v.y = c.Float();
v.z = c.Float();

vertices.push_back(v);

cout << "\t\t\tv[" << i << "] = ("
<< v.x << "," << v.y << "," << v.z << ")\n";
}
}
void Mesh3DSObject::ParseTextureCoords(Model3DSChunk c)
{
int n_texcoords = c.Short();

Mesh3DSTextureCoord texcoord;

cout << dec << "\t\t\tn_texcoords = (" << n_texcoords << ")\n";
for(int i = 0 ; i < n_texcoords ; i++)
{
texcoord.u = c.Float();
texcoord.v = c.Float();

texturecoords.push_back(texcoord);

cout << "\t\t\ttexcoord[" << i << "] = ("
<< texcoord.u << "," << texcoord.v << ")\n";
}
}
void Mesh3DSObject::ParseFaces(Model3DSChunk c)
{
int n_faces = c.Short();

Mesh3DSFace face;

cout << dec << "\t\t\tn_faces = (" << n_faces << ")\n";
for(int i = 0 ; i < n_faces ; i++)
{
face.a = c.Short();
face.b = c.Short();
face.c = c.Short();
c.Short(); // read the crappy flag

faces.push_back(face);

cout << "\t\t\tface[" << i << "] = ("
<< face.a << "," << face.b << "," << face.c << ")\n";
}

for(Model3DSChunk cc = c.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0x4130):
ParseFacesMaterials(cc);
break;

default:
break;
}
}
}

void Mesh3DSObject::ParseFacesMaterials(Model3DSChunk c)
{
string material_name = c.Str();
vector<int> faces_applied;

cout << "\t\t\t\tmaterial name = (" << material_name << ")\n";

int n_faces = c.Short();
for(int i = 0 ; i < n_faces ; i++)
{
int f = c.Short();

faces_applied.push_back(f);

cout << "\t\t\t\tmaterial assigned to face " << f << "\n";
}

material_faces[material_name] = faces_applied;
}

Mesh3DSMatrix Mesh3DSObject::Matrix()
{
return matrix;
}

const std::vector<Mesh3DSVertex> &Mesh3DSObject::Vertices()
{
return vertices;
}
const std::vector<Mesh3DSTextureCoord> &Mesh3DSObject::TextureCoords()
{
return texturecoords;
}
const std::vector<Mesh3DSFace> &Mesh3DSObject::Faces()
{
return faces;
}
const std::map<std::string , std::vector<int> > &Mesh3DSObject::Materials()
{
return material_faces;
}



note here that most of the object loading is done by the object itself

[Edited by - Genjix on April 16, 2005 1:17:20 PM]

#14 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 16 April 2005 - 06:19 AM


#ifndef MATERIAL3DS_H
#define MATERIAL3DS_H

#include <map>
#include <string>

#include "../chunk/chunk.h"

#include "material3dsobject.h"

/***/
class Material3DS
{
public:
/***/
Material3DS();
/***/
~Material3DS();

void Parse(Model3DSChunk c);

const std::map<std::string , Material3DSObject> &Materials();
private:

std::string MaterialMap(Model3DSChunk c);

std::map<std::string , Material3DSObject> materials;
};

#endif




#include "material3ds.h"

#include <iostream>
#include <string>
using namespace std;

Material3DS::Material3DS()
{
}
Material3DS::~Material3DS()
{
}

void Material3DS::Parse(Model3DSChunk c)
{
string mat_name , texture_name;
float shininess_percent , transparency_percent;

for(Model3DSChunk cc = c.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0xa000):
mat_name = cc.Str();
break;

case(0xa040):
shininess_percent = 1.0;
break;

case(0xa050):
transparency_percent = 1.0;
break;

case(0xa200):
case(0xa33a):
texture_name = MaterialMap(cc);
break;

default:
break;
}
}

cout << "texture(" << mat_name << "," << texture_name << ")\t"
<< "shininess = (" << shininess_percent << ")\t"
<< "transparency = (" << transparency_percent << ")\n";

Material3DSObject material(texture_name ,
shininess_percent , transparency_percent);
materials[mat_name] = material;
}

std::string Material3DS::MaterialMap(Model3DSChunk c)
{
for(Model3DSChunk cc = c.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0xa300):
return cc.Str();
break;

default:
break;
}
}
}

const std::map<std::string , Material3DSObject> &Material3DS::Materials()
{
return materials;
}



and the Material3DSObject

#ifndef MATERIAL3DSOBJECT_H
#define MATERIAL3DSOBJECT_H

#include <string>

#include "../chunk/chunk.h"

/***/
class Material3DSObject
{
public:
/***/
Material3DSObject();
/***/
Material3DSObject(const std::string &texturename ,
float shininess , float transparency);
/***/
Material3DSObject(const Material3DSObject &material);
/***/
~Material3DSObject();

void operator=(const Material3DSObject &material);

const std::string &Texture();
const float Shininess();
const float Transparency();
private:
std::string texture;
float shininess_percent , transparency_percent;
};

#endif




#include "material3dsobject.h"

using namespace std;

Material3DSObject::Material3DSObject()
{
shininess_percent = 0.0;
transparency_percent = 0.0;
}
Material3DSObject::Material3DSObject(const std::string &texturename ,
float shininess , float transparency)
: texture(texturename) ,
shininess_percent(shininess) , transparency_percent(transparency)
{
}
Material3DSObject::Material3DSObject(const Material3DSObject &material)
{
operator=(material);
}
Material3DSObject::~Material3DSObject()
{
}

void Material3DSObject::operator=(const Material3DSObject &material)
{
texture = material.texture;
shininess_percent = material.shininess_percent;
transparency_percent = material.transparency_percent;
}

const std::string &Material3DSObject::Texture()
{
return texture;
}
const float Material3DSObject::Shininess()
{
return shininess_percent;
}
const float Material3DSObject::Transparency()
{
return transparency_percent;
}




remember above that most of the loading was done in the mesh object itself? well here we do the opposite. this is because, because we're adding the material to a hash array, we need to know its name. meaning we have to start parsing the 3ds file for the materials name. We also need to therefore extract the needed info from the other chunks.
This is a limitation of the way 3ds files uses chunks - a workaround can be made, but it's long and arduous (not worth it).

[Edited by - Genjix on April 16, 2005 1:19:08 PM]

#15 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 16 April 2005 - 08:06 AM

this is probably the simplest of all.

#ifndef LIGHT3DS_H
#define LIGHT3DS_H

#include <map>
#include <string>

#include "../chunk/chunk.h"

#include "light3dsobject.h"

/***/
class Light3DS
{
public:
/***/
Light3DS();
/***/
~Light3DS();

void Parse(const std::string &name , Model3DSChunk c);

const std::map<std::string , Light3DSObject> &Lights();

private:

std::map<std::string , Light3DSObject> lights;
};

#endif



#include "light3ds.h"

using namespace std;

Light3DS::Light3DS()
{
}
Light3DS::~Light3DS()
{
}

void Light3DS::Parse(const string &name , Model3DSChunk c)
{
Light3DSObject light©;
lights[name] = light;
}

const std::map<std::string , Light3DSObject> &Light3DS::Lights()
{
return lights;
}



and the Light3DSObject

#ifndef LIGHT3DSOBJECT_H
#define LIGHT3DSOBJECT_H

#include "../chunk/chunk.h"

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

/***/
class Light3DSObject
{
public:
/***/
Light3DSObject();
/***/
Light3DSObject(Model3DSChunk c);
/***/
Light3DSObject(const Light3DSObject &light);
/***/
~Light3DSObject();

/***/
void operator=(const Light3DSObject &light);

bool SpotLight(); ///< is this a spot light?
const Light3DSVertex &Position();
const Light3DSVertex &Target();

private:

Light3DSVertex position;

bool spot_light;
Light3DSVertex target;
};

#endif



#include "light3dsobject.h"

Light3DSObject::Light3DSObject()
: spot_light(0)
{
}
Light3DSObject::Light3DSObject(Model3DSChunk c)
: spot_light(0)
{
position.x = c.Float();
position.y = c.Float();
position.z = c.Float();

for(Model3DSChunk cc = c.Child() ; cc ; cc = cc.Sibling())
{
switch(cc.ID())
{
case(0x4610):
spot_light = 1;

target.x = cc.Float();
target.y = cc.Float();
target.z = cc.Float();
break;

default:
break;
}
}
}
Light3DSObject::Light3DSObject(const Light3DSObject &light)
: spot_light(0)
{
operator=(light);
}
Light3DSObject::~Light3DSObject()
{
}

void Light3DSObject::operator=(const Light3DSObject &light)
{
}

bool Light3DSObject::SpotLight()
{
return spot_light;
}
const Light3DSVertex &Light3DSObject::Position()
{
return position;
}
const Light3DSVertex &Light3DSObject::Target()
{
return target;
}


notice how the light object defaults to an omni, and only becomes a spotlight when the 0x4610 chunk is encountered.

#16 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 16 April 2005 - 08:12 AM

thats it!

the whole mess can be found here

things to do

  • namespace some items

  • fog support

  • expand on some of lighting features


I may show you some of these features when I get time and if enough people request them ;)

[Edited by - Genjix on December 1, 2005 9:12:05 AM]

#17 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 16 April 2005 - 08:14 AM

pm me questions and I will answer them here!

FAQ




#18 Genjix   Banned   -  Reputation: 100

Like
0Likes
Like

Posted 16 April 2005 - 09:35 AM

btw: about making a nice html version of this tutorial. I emailed GDNet articles section asking if they wanted to let me host this tutorial 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