3ds loader [source]

Started by
7 comments, last by Nedelman 16 years, 10 months ago
well.. ive finally finished my loader.. after many irritating hours... if anyone needs a 3ds loader.. feel free to use my code.. however ull need a vector and a texture class to get it working.. comments and suggestions are welcome... it supports multiple objects, materials, bumpmaps, diffusemaps and opicaty maps... more maps can easily be added.. just look at the code.. ive got no comments since its pretty much self explaining... ive tried to make it as simple as possible.. thx to all those thats been helping me out... header


/****************************************************************/
/*********** 3DS LOADER BY DRAGON_STRIKE, ROBERT NAGY ***********/
/****************************************************************/

#ifndef MODEL_3DS_H
#define MODEL_3DS_H

#include "BaseCode/Image.h"
#include "BaseCode/Math/math.h"

#include <cstdio>
#include <cstdlib>
#include <io.h>
#include <vector>
#include <string>

class Model_3DS;

class Object_3DS{

	friend Model_3DS;

	private:

		struct Face{
			unsigned short a,b,c;	
		};

		struct MaterialFace {
			Face* subFaces;	
			int numSubFaces;
			int MatIndex;	
		};

		std::string Name;		    
		
		int faces_amount;
		int vertices_amount;

		Face* Faces;

		vec3* Vertices; 
		vec3* Normals; 
		vec3* Tangents; 
		vec2* TexCoords;

		std::vector<MaterialFace> MatFaces;

	public:

		MaterialFace GetMaterialFace(int i) const
		{ return MatFaces.at(i);}

		int GetNumMatFaces() 
		{ return MatFaces.size();}

		

		const vec3* GetVertices() const
		{ return Vertices; }

		const vec3* GetNormals() const
		{ return Normals; }

		const vec3* GetTangents() const
		{ return Tangents; }

		const vec2* GetTexCoords() const
		{ return TexCoords; }

		vec3 GetVertex(int i)
		{ return Vertices; }

		vec3 GetNormal(int i)
		{ return Normals; }

		vec3 GetTangent(int i)
		{ return Tangents; }

		vec2 GetTexCoord(int i)
		{ return TexCoords; }
		
};

class Material_3DS{

	friend Model_3DS;

	private:
		std::string Name;	
		  
	public:

		CIMAGE TextureMap;	
		CIMAGE BumpMap;	
		CIMAGE OpicatyMap;	

		float Ambient[4];		         
		float Diffuse[4];	           
		float Specular[4];		         
		float Shining;	
		float Transparancy;
		  
		float SpecularMat;              
		float OpacityMat;            
		float ReflectionMat;             
		float BumpMat;  

};

class Model_3DS 
{
	private:
	
		void NormalizeVertices();
		FILE *file;

		std::vector<Object_3DS> m_Object_3DS;
		std::vector<Material_3DS> m_Material_3DS;

		void F_MATERIAL_BLOCK();
		void F_MATERIAL_NAME();
		void F_AMBIENT_COLOR();
		void F_DIFFUSE_COLOR();
		void F_SPECULAR_COLOR();
		void F_SHININESS();
		void F_TRANSPARACY();
		void F_TEXFILE();

		void F_OBJECT_BLOCK();
		void F_VERTICES_LIST();
		void F_FACES_DESCRIPTION();
		void F_FACES_MATERIAL();
		void F_MAPPING_COORDINATES_LIST();

		unsigned int chunk_length;	
		unsigned short chunk_id;

		CIMAGE* CurMap;

	public:

		Object_3DS GetObject_3DS(int i) const
		{ return m_Object_3DS.at(i);}

		Material_3DS GetMaterial_3DS(int i) const
		{ return m_Material_3DS.at(i);}

		int GetNumObjects() 
		{ return m_Object_3DS.size();}

		int GetNumMaterials()
		{ return m_Material_3DS.size();}
		
		Model_3DS();
		~Model_3DS();

		int Load(char* filename);

};

#endif MODEL_3DS_H




body

#define MAIN_CHUNK 0x4D4D
    #define EDITOR_CHUNK 0x3D3D
      #define OBJECT_BLOCK 0x4000
         #define TRIANGULAR_MESH 0x4100
            #define VERTICES_LIST 0x4110
            #define FACES_DESCRIPTION 0x4120
               #define FACES_MATERIAL 0x4130
            #define MAPPING_COORDINATES_LIST 0x4140
               #define SMOOTHING_GROUP_LIST 0x4150
            #define LOCAL_COORDINATES_SYSTEM 0x4160
      #define MATERIAL_BLOCK 0xAFFF
			#define MATERIAL_NAME 0xA000
			#define AMBIENT_COLOR 0xA010
			#define DIFFUSE_COLOR 0xA020
			#define SPECULAR_COLOR 0xA030
			#define SHININESS		0xA040
			#define TRANSPARACY		0xA050
			#define TEXTURE			0xA200
			#define SPECULARMAT		0xA204
			#define OPACITYMAT		0xA210
			#define REFLECTIONMAT	0xA220
			#define BUMPMAT			0xA230
			#define TEXFILE			0xA300
            #define MAPPING_FILENAME 0xA300
            #define MAPPING_PARAMETERS 0xA351


#include "Model_3DS.h"

Model_3DS::Model_3DS()
{

}

Model_3DS::~Model_3DS()
{
}

void Model_3DS::NormalizeVertices()
{
		for (int i = 0; i < (int)m_Object_3DS.size(); i++)
		{
			for (int k = 0; k < m_Object_3DS.at(i).vertices_amount; k++)
			{
					m_Object_3DS.at(i).Normals[k] = normalize(m_Object_3DS.at(i).Normals[k]);
					m_Object_3DS.at(i).Tangents[k] = normalize(m_Object_3DS.at(i).Tangents[k]);
			}
		}
}

int Model_3DS::Load(char* filename)
{
   	if (!(file=fopen (filename, "rb"))) 
		return 0; 


	while (ftell (file) < filelength(fileno (file))) //Loop to scan the whole file 
	{

		fread (&chunk_id, 2, 1, file); //Read the chunk header
		fread (&chunk_length, 4, 1, file); //Read the lenght of the chunk

		switch (chunk_id)
        {
			case MAIN_CHUNK: 
			break;    

				case EDITOR_CHUNK:
				break;
					case MATERIAL_BLOCK:					
						F_MATERIAL_BLOCK();
					break;

						case MATERIAL_NAME:							
							F_MATERIAL_NAME();
						break;            
				            			
							case AMBIENT_COLOR:
								F_AMBIENT_COLOR();
							break;

							case DIFFUSE_COLOR:
								F_DIFFUSE_COLOR();
							break;
				                        
							case SPECULAR_COLOR:
								F_SPECULAR_COLOR();
							break; 
				                     
							case SHININESS:
								F_SHININESS();
							break;  
				          
							case TRANSPARACY:
								F_TRANSPARACY();
							break;

							case OPACITYMAT:
								CurMap = &m_Material_3DS.back().OpicatyMap;
							break;

							case BUMPMAT:
								CurMap = &m_Material_3DS.back().BumpMap;
							break;
				            
							case TEXTURE:								
								CurMap = &m_Material_3DS.back().TextureMap;
							break; 
					        
							case TEXFILE:
								F_TEXFILE();
							break;


					case OBJECT_BLOCK: 
						F_OBJECT_BLOCK();
					break;

						case TRIANGULAR_MESH:
						break;

							case VERTICES_LIST: 
								F_VERTICES_LIST();
							break;
					
							case FACES_DESCRIPTION:
								F_FACES_DESCRIPTION();                                
							break;

							case FACES_MATERIAL:
								F_FACES_MATERIAL();
							break;    

							case MAPPING_COORDINATES_LIST:
								F_MAPPING_COORDINATES_LIST();	
							break;



			default:
				 fseek(file, chunk_length-6, SEEK_CUR);
        } 
	}

	fclose (file); 	
	
	NormalizeVertices();
	
	return 1;
    
}


void Model_3DS::F_MATERIAL_BLOCK()
{
	Material_3DS tempMaterial_3DS;
	
	tempMaterial_3DS.Name = "";

	for (int n = 0; n < 4; n++)
	{
		tempMaterial_3DS.Ambient[n] = 0.0f;
		tempMaterial_3DS.Diffuse[n] = 0.0f;
		tempMaterial_3DS.Specular[n] = 0.0f;							
	}

	tempMaterial_3DS.Shining = 0.0f;
	tempMaterial_3DS.SpecularMat = 0.0f;
	tempMaterial_3DS.OpacityMat = 0.0f;
	tempMaterial_3DS.ReflectionMat = 0.0f;
	tempMaterial_3DS.BumpMat = 0.0f;	

	m_Material_3DS.push_back(tempMaterial_3DS);
}

void Model_3DS::F_MATERIAL_NAME()
{
	char nchar;
	m_Material_3DS.back().Name = "";
	int i = 0;
	do {
		fread (&nchar, 1, 1, file);
		m_Material_3DS.back().Name+=nchar;
		i++;
	} while(nchar != '\0');  
}

void Model_3DS::F_AMBIENT_COLOR()
{
	fread (&m_Material_3DS.back().Ambient[0], sizeof(int), 3, file);
	for (int x = 0; x < 3; x++) 
	{
		m_Material_3DS.back().Ambient [x]= (255.0f - m_Material_3DS.back().Ambient [x]) / 255.0f;
	}
	m_Material_3DS.back().Ambient [3]=1.0f;
	chunk_length = chunk_length-4*3;
	fseek(file, chunk_length-6, SEEK_CUR);
}
 
void Model_3DS::F_DIFFUSE_COLOR()
{
	fread (&m_Material_3DS.back().Diffuse[0], sizeof(int), 3, file);
	for (int x = 0; x < 3; x++) 
	{
		m_Material_3DS.back().Diffuse[x]= (255.0f - m_Material_3DS.back().Diffuse[x]) / 255.0f;
	}

	m_Material_3DS.back().Diffuse[3]=1.0f;

	chunk_length=chunk_length-4*3;
	fseek(file, chunk_length-6, SEEK_CUR);
}

void Model_3DS::F_SPECULAR_COLOR()
{
	fread (&m_Material_3DS.back().Specular[0], sizeof(int), 3, file);
	for (int x = 0; x < 3; x++)
	{
		m_Material_3DS.back().Specular[x]= (255.0f - m_Material_3DS.back().Specular[x]) / 255.0f;
	}
	m_Material_3DS.back().Specular[3]=1.0f;
	chunk_length=chunk_length-4*3;
	fseek(file, chunk_length-6, SEEK_CUR);
}

void Model_3DS::F_SHININESS()
{
	fread (&m_Material_3DS.back().Shining, sizeof(unsigned short), 1, file);
	m_Material_3DS.back().Shining/= 100.0f;
	chunk_length-=2;
	fseek(file, chunk_length-6, SEEK_CUR); 
}

void Model_3DS::F_TRANSPARACY()
{
	fread (&m_Material_3DS.back().Transparancy, sizeof(unsigned short), 1, file);
	m_Material_3DS.back().Transparancy/= 100.0f;
	chunk_length-=2;
	fseek(file, chunk_length-6, SEEK_CUR); 
}

void Model_3DS::F_TEXFILE()
{
	char name[20];
	char nchar;
	int i = 0;
	do {
		fread (&nchar, 1, 1, file);
		name=nchar;
		i++;
	} while(nchar != '\0' && i < 20);      

	CurMap->Load(name);
}

void Model_3DS::F_OBJECT_BLOCK()
{
	Object_3DS tempObject_3DS;

	tempObject_3DS.Name = "";
	tempObject_3DS.vertices_amount = 0;
	tempObject_3DS.faces_amount = 0;

	tempObject_3DS.Vertices = 0;
	tempObject_3DS.Normals = 0; 
	tempObject_3DS.Tangents = 0;

	tempObject_3DS.Faces = 0;
	tempObject_3DS.TexCoords = 0;		

	char nchar;
	int i=0;
	do {
		fread (&nchar, 1, 1, file);
		tempObject_3DS.Name+=nchar;
		i++;
	} while(nchar != '\0');  

	m_Object_3DS.push_back(tempObject_3DS);
}

void Model_3DS::F_VERTICES_LIST()
{
	unsigned short amount = 0;

	fread (&amount, sizeof (unsigned short), 1, file);
	m_Object_3DS.back().vertices_amount = amount;
	m_Object_3DS.back().Vertices = new vec3[amount];
	m_Object_3DS.back().Normals = new vec3[amount];
	m_Object_3DS.back().Tangents = new vec3[amount];

	for (int i=0; i<amount; i++)
	{
		fread (&m_Object_3DS.back().Vertices, sizeof(float), 3, file);			

		float temp = m_Object_3DS.back().Vertices.y;
		m_Object_3DS.back().Vertices.y = m_Object_3DS.back().Vertices.z;
		m_Object_3DS.back().Vertices.z = -temp;

		m_Object_3DS.back().Normals = 0.0f;
		m_Object_3DS.back().Tangents = 0.0f;
	}
}

void Model_3DS::F_FACES_DESCRIPTION()
{
	unsigned short amount = 0;
	unsigned short face_flags = 0;

	fread (&amount, sizeof (unsigned short), 1, file);
	m_Object_3DS.back().faces_amount = amount;

	m_Object_3DS.back().Faces = new Object_3DS::Face[amount];					
	for (int i=0; i<amount; i++)
	{
		fread (&m_Object_3DS.back().Faces, sizeof (unsigned short), 3, file);

		fread (&face_flags, sizeof (unsigned short), 1, file);

		vec3 N = CalculateNormal(m_Object_3DS.back().Vertices[m_Object_3DS.back().Faces.a],
									m_Object_3DS.back().Vertices[m_Object_3DS.back().Faces.b],
									m_Object_3DS.back().Vertices[m_Object_3DS.back().Faces.c]);

		m_Object_3DS.back().Normals[m_Object_3DS.back().Faces.a] += N;
		m_Object_3DS.back().Normals[m_Object_3DS.back().Faces.b] += N;
		m_Object_3DS.back().Normals[m_Object_3DS.back().Faces.c] += N;

		vec3 T = CalculateTangent(m_Object_3DS.back().Vertices[m_Object_3DS.back().Faces.a],
									m_Object_3DS.back().Vertices[m_Object_3DS.back().Faces.b],
									m_Object_3DS.back().Vertices[m_Object_3DS.back().Faces.c],
									m_Object_3DS.back().TexCoords[m_Object_3DS.back().Faces.a],
									m_Object_3DS.back().TexCoords[m_Object_3DS.back().Faces.b],
									m_Object_3DS.back().TexCoords[m_Object_3DS.back().Faces.c]);

		m_Object_3DS.back().Tangents[m_Object_3DS.back().Faces.a] += T;
		m_Object_3DS.back().Tangents[m_Object_3DS.back().Faces.b] += T;
		m_Object_3DS.back().Tangents[m_Object_3DS.back().Faces.c] += T;
	}
}

void Model_3DS::F_FACES_MATERIAL()
{
	char nchar;
	std::string texname="";
	do {
		fread (&nchar, 1, 1, file);
		texname+=nchar;
	} while(nchar != '\0'); 
				            
	int index=0;
	for(int j = 0; j < (int)m_Material_3DS.size(); j++) {
		if (texname==m_Material_3DS.at(j).Name) 
		index=j;
	}

	Object_3DS::MaterialFace tempMaterialFace;
	tempMaterialFace.MatIndex = index;

	unsigned short amount = 0;
	unsigned short face_flags = 0;

	fread (&amount, sizeof (unsigned short), 1, file);

	tempMaterialFace.subFaces = new Object_3DS::Face[amount];
	tempMaterialFace.numSubFaces = amount;								

	for (int j = 0; j < amount; j++)
	{
		fread(&face_flags,sizeof(face_flags),1,file);
		tempMaterialFace.subFaces[j] = m_Object_3DS.back().Faces[face_flags];
	}
	m_Object_3DS.back().MatFaces.push_back(tempMaterialFace);
		
}

void Model_3DS::F_MAPPING_COORDINATES_LIST()
{
	unsigned short amount = 0;

	fread (&amount, sizeof (unsigned short), 1, file);
	m_Object_3DS.back().TexCoords = new vec2[amount];
	
	for (int i=0; i<amount; i++)
	{
		fread (&m_Object_3DS.back().TexCoords, sizeof (float), 2, file);									
	}
}
 




[Edited by - Dragon_Strike on May 18, 2007 11:53:50 AM]
Advertisement
Here are my suggestions to improve the design, usability, and overall quality of your code:

0) The general concept: a loader should, ideally, not involve actually drawing the geometry (nor should something that draws geometry be concerned with loading it). File formats are designed for storage purposes, not rendering purposes, so they're often arranged in a way that's actually not very suitable for rendering. A loader should simply slurp up the data and present it in a uniform format so that rendering subsystems can take it and do what they need to do with it; this decreases coupling between systems. Furthermore, by having a different style of rendering for each file format you can load, you decrease maintainability. I understand the appeal of a "model class" that both loads and draws geometry to a beginner, but this doesn't scale well at all.

1) Your headers: this is C++ code, the proper way to include the old C standard library headers is #include <cstudio> and #include <cstdlib>. Note the c prefix and no .h suffix.

2) More headers: io.h is nonstandard.

3) Namespaces: "using namespace std;" in a header is terrible, unforgivable practice; you have now completely destroyed namespaces for everybody who uses your code.

4) Encapsulation: some of those simple structs are probably useless on their own; they should be private nested structs of whatever larger object uses them, or otherwise hidden from the view of the end-user of your code.

5) Naming: "Object" is too generic a name. Either choose a more specific name or place your code in your own namespace so you don't cause clashes with people using your code.

6) Encapsulation: your objects are just naked structs, with everything public. This allows you to rather easily destabilize the entire structure. If you follow my suggestion of stripping render functionality (or at least splitting it), you should be able to wrap those data structures up nicer. All those naked pointers are especially dangerous.

7) Organization: The load function is one giant monolithic function; this is ugly, split things out to improve readability.
damn.. and here i thought i was finished...

thx... alot for the suggestions... very helpful... ill look into it later 2 night
the code in the first post have been updated...


ok ive tried to solve the problems u mentioned...

1) solved

2) im unsure what i should replace it with...

3) solved

4) im a bit unsure what u mean... but i put the smaller structures into the private part of the class thats using them...

5) i renamed it to Object_3DS

6) im unsure if it was what u meant.. but ive put the variables in private structures now..

7) im a bit unsure how i would solve this the best wy.. right now i just separated it from the switch function into own function bodies..
ive been searching through the 3ds documentation but there is one thing i dont find.. what is the hex code for the displacement map?
Great looking 3ds loader. Its really straightforward.

In terms of suggestions you may want to add some helper functions in there for places in the code that are replicated.

  // Retrieves the name of the object, material, whatever  char* Model_3DS::getName() { /* insert code */ }  // Retrieves color information  ColorRGBA Model_3DS::getColor() { /* insert code */ }  // etc... etc...  // Or so you know how many bytes were read  int Model_3DS::getName(char* name_) { /* insert code */ }  int Model_3DS::getColor(ColorRGBA& color_) { /* insert code */ }


You could make the code more hierarchical to mirror the structure of the file. All that would do for you is cut back the size of that switch statement. Very optional suggestion.

  void Model_3DS::F_OBJECT_CHUNK() { /* insert code */ }  void Model_3DS::F_MATERIAL_CHUNK() { /* insert code */ }


One thing I had a question about has to do with reading in colors. I'm not understanding the statement

  m_Material_3DS.back().Specular[x]= (255.0f - m_Material_3DS.back().Specular[x]) / 255.0f;


You're reading in the RGB values as ints which are 4 bytes. That means the values can be from 0 to 2^32 - 1. I'm not seeing how this math gets you a number from 0.0 to 1.0.

And finally. Any chances we'll see this class with keyframing info?
Quote:char* Model_3DS::getName()


This is C++ we don't return char*. We return std::string or better yet a reference to it if possible.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

Quote:Original post by Mike2343
Quote:char* Model_3DS::getName()


This is C++ we don't return char*. We return std::string or better yet a reference to it if possible.


My mistake since I saw all the freads I was thinking in C rather than C++. If we want to get picky all those freads should be using ifstream and getName should look like.

int Model_3DS::getName(std::string& name){	name = "";	char nChar;	do	{		file.read(reinterpret_cast<char*>(nChar), sizeof(char));		name += nChar;	} while (nchar != '\0');	// Return the number of bytes read including the	// terminating char	return(sizeof(char) * ((int)name.size() + 1));}

But thats if we want to get picky.
In addition to some of the things jpetrie mentioned:

1)You have memory leaks all over the place. I see a lot of 'new' allocations, but no corresponding 'delete's. You should use std::vector for your mesh components to fix this, or at least add destructors that free the data you allocate.
2)When you load names, the repeated character concatenations are excessive. Since 3DS file format has limits on name lengths, you should load into a char array instead, then copy that into the name string object when the name loading has completed.
3)You automatically convert the meshes so that they have Y axis as the up axis. That's not a good idea since many people, such as myself, prefer to use Z up. You should make the conversion an option.
4)Your vertex normal calculations don't take the face smoothing groups into account. This is a problem since even simple objects, such as a cube, will not be lit properly.
5)No camera or light support. These are extremely useful to have in a scene importer.

I've also written a 3DS importer myself, along with a viewer, which you can find at http://www.gameprojects.com/project/?id=6c3eea10eb . It's written in C# for XNA, but I think you'll find the code easy to follow if you'd like to use it as a reference.
www.gameprojects.com - Share Your Games and Other Projects

This topic is closed to new replies.

Advertisement