3DS parsing tutorial

Started by
16 comments, last by Genjix 19 years ago
ok so here is the 3ds structure we are going to parse
string - nul terminated byte setshort  - 2 byteint    - 4 bytefloat  - 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]
Advertisement
#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

#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[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[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]
#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]
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(c);	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.
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]
pm me questions and I will answer them here!

FAQ


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.

This topic is closed to new replies.

Advertisement