This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

| 3DS File Loader Submitted by

Here's the framework for my 3D Studio File Loader that I wrote over Christmas break. I've seen a couple of these pop up over the past month or so, and none of them have impressed me, so I decided to write my own. Currently, it supports loading the vertex/index list (of course), along with materials, textures, and hierarchy information. It is also OO-designed and very easy to extend. This is only a framework, however; you need to supply the code to input the data into your own buffers and data structures, as each API varies widely, as does each person's preference of how to organize data. At each point where data is read from the file, take that data and feed it into a buffer, or just discard it if you wish.
I ripped this code out of my engine and gutted it down to a framework so I wouldn't be limiting anyone to my particular choice of organizing data. Source to my engine (what I have done, anyway) is available upon request, where you can witness this file loader in all it's wonderful glory. It's my first attempt at a 3D engine though, so go easy on me. Got the inspiration while interning last semester at Verant Interactive (thanks John!).
This code is Open Source. Use as you wish, but don't bitch if it breaks something. If you use this in any project worth mentioning, let me know, I'd love to see what you've done with it. Enjoy!
--------------------
Terry Caton
tcaton@umr.edu
www.umr.edu/~tcaton

Currently browsing [3ds.zip] (85,684 bytes) - [3ds.cpp] - (12,481 bytes)

    /*
    **************************************************************************
    Author: Terry Caton
    Date: January 24th, 2001
    Contact: tcaton@umr.edu
    Open Source. Use as you wish, but don't bitch if it breaks something. If you
    use this in any project worth mentioning, let me know, I'd love to see what
    you've done.

    Here's the framework for my 3D Studio File Loader. Currently, it supports
    loading the vertex/index list (of course), along with materials and hierarchy
    information. This is only a framework, however; you need to supply the code
    to input the data into your own buffers, as each API varies widely, as does
    each persons preference of how to organize data. At each point where data is
    read from the file, take that data and feed it into a buffer, or just discard
    it if you wish.

    The way I did it, and the way the framework here encourages, is to save all
    the data into a 3dsObject class (one for each object in the file), and then at
    the end when everything is done, assemble each into it's compiled vertex array/
    vertex buffer/whatever. Granted, this is my first attempt at a 3D engine, so
    there may be a few fundamental flaws here, but hey, go easy on me.

    If you so desire, contact me at tcaton@umr.edu. Questions, comments, money,
    whatever. Source to my engine is also available upon request, with a working
    example of this file loader in action. Thanks, and enjoy!!

    Special thanks to John Ratcliff and all the guys I worked with, for getting
    me interested in all this.

    **************************************************************************
    */

    #include "3ds.h"
    #include <stdlib.h>
    #include <string.h>

    // Global instance of loader
    Load3ds gLoad3ds;

    Load3ds::Load3ds()
    {
    	mBuffer = 0;
    	mCurrentChunk = 0;
    	mTempChunk = new Chunk;

    	// initialize any other members you choose to add
    }

    Load3ds::~Load3ds()
    {
    	delete mTempChunk;

    }

    void * Load3ds::Create(char * aFilename)
    {
    	int lBytesRead = 0;
    	mCurrentChunk = new Chunk;

    	mFile = fopen(aFilename, "rb");
    	ReadChunk(mCurrentChunk);
    	if (mCurrentChunk->mID != PRIMARY)
    		exit(1107);
    	// Largest possible buffer we'd need is MAX_INDICES * sizeof(triangle)
    	mBuffer = new unsigned char[65536 * 3 * sizeof(float)];

    	// Begin loading objects, by calling recursive function
    	ProcessNextChunk(mCurrentChunk);

    	// All data read, compile them into a friendly format
    	CompileObjects();

    	// Clean up after everything, to prepare for next time
    	CleanUp();

    	return 0;
    }

    int Load3ds::CleanUp()
    {

    	fclose(mFile);
    	delete [] mBuffer;
    	delete mCurrentChunk;

    /*
    Add any cleanup routines you may need between calls to Load3ds::Create(),
    such as deleting temporary vertex/index lists, materials, or whatever
    else you choose to add
    */

    	return 1;
    }

    int Load3ds::ProcessNextChunk(Chunk * aPreviousChunk)
    {
    	mCurrentChunk = new Chunk;

    	while (aPreviousChunk->mBytesRead < aPreviousChunk->mLength)
    	{
    		// Read next chunk
    		ReadChunk(mCurrentChunk);

    		switch (mCurrentChunk->mID)
    		{
    		case VERSION:
    			// Check version (must be 3 or less)
    			mCurrentChunk->mBytesRead += fread(mBuffer, 1, mCurrentChunk->mLength - mCurrentChunk->mBytesRead, mFile);
    			if (*(unsigned short *) mBuffer > 0x03)
    				exit(1107);
    			break;

    		case EDITMATERIAL:
    			// Proceed to material loading function
    			ProcessNextMaterialChunk(mCurrentChunk);
    			break;

    		case EDIT3DS:
    			// Check mesh verion, then proceed to mesh loading function
    			ReadChunk(mTempChunk);
    			mTempChunk->mBytesRead += fread(mBuffer, 1, mTempChunk->mLength - mTempChunk->mBytesRead, mFile);
    			mCurrentChunk->mBytesRead += mTempChunk->mBytesRead;
    			if (mTempChunk->mID != MESHVERSION || *(unsigned short *)mBuffer > 0x03)
    				exit(1107);
    			ProcessNextChunk(mCurrentChunk);
    			break;

    		case EDITOBJECT:
    			mCurrentChunk->mBytesRead += GetString((char *)mBuffer);
    			// mBuffer now contains name of the object to be edited
    			ProcessNextObjectChunk(mCurrentChunk);
    			break;

    		case EDITKEYFRAME:
    			ProcessNextKeyFrameChunk(mCurrentChunk);
    			break;

    		default:  // unrecognized/unsupported chunk
    			mCurrentChunk->mBytesRead += fread(mBuffer, 1, mCurrentChunk->mLength - mCurrentChunk->mBytesRead, mFile);
    			break;
    		}

    	aPreviousChunk->mBytesRead += mCurrentChunk->mBytesRead;
    	}

    	delete mCurrentChunk;
    	mCurrentChunk = aPreviousChunk;

    	return 1;
    }

    int Load3ds::ProcessNextObjectChunk(Chunk * aPreviousChunk)
    {
    	mCurrentChunk = new Chunk;

    	while (aPreviousChunk->mBytesRead < aPreviousChunk->mLength)
    	{
    		ReadChunk(mCurrentChunk);

    		switch (mCurrentChunk->mID)
    		{
    		case OBJTRIMESH:
    			// at this point, mBuffer will contain the name of the object being described
    			ProcessNextObjectChunk(mCurrentChunk);
    			ComputeNormals();

    			break;

    		case TRIVERT:
    			FillVertexBuffer(mCurrentChunk);
    			break;

    		case TRIFACE:
    			FillIndexBuffer(mCurrentChunk);
    			break;

    		case TRIFACEMAT:
    			// your getting a list of triangles that belong to a certain material
    			SortIndicesByMaterial(mCurrentChunk);
    			break;

    		case TRIUV:
    			FillTexCoordBuffer(mCurrentChunk);
    			break;

    		default:  // unrecognized/unsupported chunk
    			mCurrentChunk->mBytesRead += fread(mBuffer, 1, mCurrentChunk->mLength - mCurrentChunk->mBytesRead, mFile);
    			break;
    		}

    	aPreviousChunk->mBytesRead += mCurrentChunk->mBytesRead;
    	}

    	delete mCurrentChunk;
    	mCurrentChunk = aPreviousChunk;

    	return 1;
    }

    int Load3ds::ProcessNextMaterialChunk(Chunk * aPreviousChunk)
    {
    	mCurrentChunk = new Chunk;

    	while (aPreviousChunk->mBytesRead < aPreviousChunk->mLength)
    	{
    		ReadChunk(mCurrentChunk);

    		switch (mCurrentChunk->mID)
    		{
    		case MATNAME:
    			mCurrentChunk->mBytesRead += fread(mBuffer, 1, mCurrentChunk->mLength - mCurrentChunk->mBytesRead, mFile);
    			break;

    		case MATLUMINANCE:
    			ReadColorChunk(mCurrentChunk, (float *) mBuffer);
    			break;

    		case MATDIFFUSE:
    			ReadColorChunk(mCurrentChunk, (float *) mBuffer);
    			break;

    		case MATSPECULAR:
    			ReadColorChunk(mCurrentChunk, (float *) mBuffer);
    			break;

    		case MATSHININESS:
    			ReadColorChunk(mCurrentChunk, (float *) mBuffer);
    			break;

    		case MATMAP:
    			// texture map chunk, proceed
    			ProcessNextMaterialChunk(mCurrentChunk);
    			break;

    		case MATMAPFILE:
    			mCurrentChunk->mBytesRead += fread((char *)mBuffer, 1, mCurrentChunk->mLength - mCurrentChunk->mBytesRead, mFile);
    			// mBuffer now contains the filename of the next texture; load it if you wish
    			break;

    		default:  // unrecognized/unsupported chunk
    			mCurrentChunk->mBytesRead += fread(mBuffer, 1, mCurrentChunk->mLength - mCurrentChunk->mBytesRead, mFile);
    			break;
    		}

    	aPreviousChunk->mBytesRead += mCurrentChunk->mBytesRead;
    	}

    	delete mCurrentChunk;
    	mCurrentChunk = aPreviousChunk;

    	return 1;
    }

    int Load3ds::ProcessNextKeyFrameChunk(Chunk * aPreviousChunk)
    {
    	mCurrentChunk = new Chunk;

    	short int lCurrentID, lCurrentParentID;

    	while (aPreviousChunk->mBytesRead < aPreviousChunk->mLength)
    	{
    		ReadChunk(mCurrentChunk);

    		switch (mCurrentChunk->mID)
    		{
    		case KFMESH:
    			ProcessNextKeyFrameChunk(mCurrentChunk);
    			break;

    		case KFHEIRARCHY:
    			mCurrentChunk->mBytesRead += fread(&lCurrentID, 1, 2, mFile);
    			// lCurrentID now contains the ID of the current object being described
    			//  Save this if you want to support an object hierarchy
    			break;

    		case KFNAME:
    			mCurrentChunk->mBytesRead += GetString((char *)mBuffer);
    			// mBuffer now contains the name of the object whose KF info will
    			//   be described

    			mCurrentChunk->mBytesRead += fread(mBuffer, 1, 4, mFile);  // useless, ignore
    			mCurrentChunk->mBytesRead += fread(&lCurrentParentID, 1, 2, mFile);
    			// lCurrentParentID now contains the ID of the parent of the current object
    			// being described
    			break;

    		default:  // unrecognized/unsupported chunk
    			mCurrentChunk->mBytesRead += fread(mBuffer, 1, mCurrentChunk->mLength - mCurrentChunk->mBytesRead, mFile);
    			break;
    		}

    	aPreviousChunk->mBytesRead += mCurrentChunk->mBytesRead;
    	}

    	delete mCurrentChunk;
    	mCurrentChunk = aPreviousChunk;

    	return 1;
    }

    int Load3ds::GetString(char * aBuffer)
    {
    	unsigned int lBytesRead = 0;
    	int index = 0;

    	fread(aBuffer, 1, 1, mFile);

    	while (*(aBuffer + index) != 0)
    		fread(aBuffer + ++index, 1, 1, mFile);

    	return strlen(aBuffer) + 1;
    }

    int Load3ds::ReadChunk(Chunk * aChunk)
    {
    	aChunk->mBytesRead = fread(&aChunk->mID, 1, 2, mFile);
    	aChunk->mBytesRead += fread(&aChunk->mLength, 1, 4, mFile);

    	return 1;
    }

    int Load3ds::ReadColorChunk(Chunk * aChunk, float * aVector)
    {
    	ReadChunk(mTempChunk);
    	mTempChunk->mBytesRead += fread(mBuffer, 1, mTempChunk->mLength - mTempChunk->mBytesRead, mFile);

    	aChunk->mBytesRead += mTempChunk->mBytesRead;
    	return 1;
    }

    int Load3ds::ReadPercentChunk(Chunk * aChunk, float * aPercent)
    {
    	ReadChunk(mTempChunk);
    	mTempChunk->mBytesRead += fread(mBuffer, 1, mTempChunk->mLength - mTempChunk->mBytesRead, mFile);
    	*aPercent = (float) *((short int *) mBuffer) / 100.0f;

    	aChunk->mBytesRead += mTempChunk->mBytesRead;
    	return 1;
    }

    int Load3ds::FillIndexBuffer(Chunk * aPreviousChunk)
    {
    	short int lNumFaces;
    	aPreviousChunk->mBytesRead += fread(&lNumFaces, 1, 2, mFile);
    	aPreviousChunk->mBytesRead += fread(mBuffer, 1, aPreviousChunk->mLength - aPreviousChunk->mBytesRead, mFile);
    	// mBuffer now contains an array of indices (unsigned short ints)
    	//   Careful, the list consists of 3 vertex indices and then an edge
    	//   flag (safe to ignore, probably
    	//   * bit 0: CA visible, bit 1: BC visible, bit 2: AB visible

    	return 1;
    }

    int Load3ds::SortIndicesByMaterial(Chunk * aPreviousChunk)
    {
    	unsigned short int lNumFaces;
    	aPreviousChunk->mBytesRead += GetString((char *) mBuffer);
    	// mBuffer contains the name of the material that is associated
    	//  with the following triangles (set of 3 indices which index into the vertex list
    	//  of the current object chunk)

    	aPreviousChunk->mBytesRead += fread(&lNumFaces, 1, 2, mFile);

    	aPreviousChunk->mBytesRead += fread(mBuffer, 1, aPreviousChunk->mLength - aPreviousChunk->mBytesRead, mFile);
    	// mBuffer now contains a list of triangles that use the material specified above

    	return 1;
    }

    int Load3ds::FillTexCoordBuffer(Chunk * aPreviousChunk)
    {
    	int lNumTexCoords;
    	aPreviousChunk->mBytesRead += fread(&lNumTexCoords, 1, 2, mFile);
    	aPreviousChunk->mBytesRead += fread(mBuffer, 1, aPreviousChunk->mLength - aPreviousChunk->mBytesRead, mFile);
    	// mBuffer now contains a list of UV coordinates (2 floats)

    	return 1;
    }

    int Load3ds::FillVertexBuffer(Chunk * aPreviousChunk)
    {
    	int lNumVertices;
    	aPreviousChunk->mBytesRead += fread(&lNumVertices, 1, 2, mFile);
    	aPreviousChunk->mBytesRead += fread(mBuffer, 1, aPreviousChunk->mLength - aPreviousChunk->mBytesRead, mFile);
    	// mBuffer now contains a list of vertex coordinates (3 floats)

    	return 1;
    }

    int Load3ds::ComputeNormals()
    {
    /*
    Compute your normals here. Quick way of doing it (no smoothing groups, though):

      for (each vertex in vertex list)
    	{
    	for (each triangle in triangle list)
    	  {
    	  if (vertex is in triangle)
    	    {
    		compute normal of triangle
    		add to total_normal
    		}
    	  }
    	normalize(total_normal)
    	save_to_normal_list(total_normal)
    	total_normal = 0
    	}

    This seemed to work well for me, though it's my first time having to compute
    normals myself. One might think that normalizing the normal right after it's
    computed and added to total_normal would be best, but I've found this not to be
    the case. If this is done, then small triangles have just as much influence on the
    final normal as larger triangles. My way, the model comes out looking much more
    smooth, especially if there's small flaws in the model.
    */

    	return 1;
    }

    int Load3ds::CompileObjects()
    {

    /*
    By now, you should have a collection of objects (or a single object), with
    proper hierarchy information, vertices, indices, and materials. Now's your
    time to assemble these into however you want them to send them down the
    OGL/D3D pipeline.

    */
    	return 1;
    }

    ///////////////////////////////////////////////////////////////////

    Chunk::Chunk()
    {
    	mID = 0;
    	mLength = 0;
    	mBytesRead = 0;
    }

    Chunk::~Chunk()
    {
    }

Currently browsing [3ds.zip] (85,684 bytes) - [3ds.h] - (3,478 bytes)

    /*
    **************************************************************************
    Author: Terry Caton
    Date: January 24th, 2001
    Contact: tcaton@umr.edu
    Open Source. Use as you wish, but don't bitch if it breaks something. If you
    use this in any project worth mentioning, let me know, I'd love to see what
    you've done.

    Here's the framework for my 3D Studio File Loader. Currently, it supports
    loading the vertex/index list (of course), along with materials and hierarchy
    information. This is only a framework, however; you need to supply the code
    to input the data into your own buffers, as each API varies widely, as does
    each persons preference of how to organize data. At each point where data is
    read from the file, take that data and feed it into a buffer, or just discard
    it if you wish.

    The way I did it, and the way the framework here encourages, is to save all
    the data into a 3dsObject class (one for each object in the file), and then at
    the end when everything is done, assemble each into it's compiled vertex array/
    vertex buffer/whatever. Granted, this is my first attempt at a 3D engine, so
    there may be a few fundamental flaws here, but hey, go easy on me.

    If you so desire, contact me at tcaton@umr.edu. Questions, comments, money,
    whatever. Source to my engine is also available upon request, with a working
    example of this file loader in action. Thanks, and enjoy!!

    **************************************************************************
    */

    #ifndef _3DS_H
    #define _3DS_H

    #include <stdio.h>

    //>------ Primary Chunk, at the beginning of each file
    #define PRIMARY       0x4D4D

    //>------ Main Chunks
    #define EDIT3DS       0x3D3D
    #define VERSION       0x0002
    #define MESHVERSION   0x3D3E
    #define EDITKEYFRAME  0xB000

    //>------ sub defines of EDIT3DS
    #define EDITMATERIAL  0xAFFF
    #define EDITOBJECT    0x4000

    //>------ sub defines of EDITMATERIAL
    #define MATNAME       0xA000
    #define MATLUMINANCE  0xA010
    #define MATDIFFUSE    0xA020
    #define MATSPECULAR   0xA030
    #define MATSHININESS  0xA040
    #define MATMAP        0xA200
    #define MATMAPFILE    0xA300

    #define OBJTRIMESH    0x4100

    //>------ sub defines of OBJTRIMESH
    #define TRIVERT       0x4110
    #define TRIFACE       0x4120
    #define TRIFACEMAT    0x4130
    #define TRIUV         0x4140
    #define TRISMOOTH     0x4150
    #define TRILOCAL      0x4160

    //>------ sub defines of EIDTKEYFRAME
    #define KFMESH        0xB002
    #define KFHEIRARCHY   0xB030
    #define KFNAME        0xB010

    //>>------  these define the different color chunk types
    #define RGBF   0x0010
    #define RGB24  0x0011

    class Chunk;

    class Load3ds
    {
    public:
    	Load3ds();
    	~Load3ds();
    	void * Create(char *);

    private:
    	int GetString(char *);
    	int ReadChunk(Chunk *);
    	int ReadColorChunk(Chunk *, float *);
    	int ReadPercentChunk(Chunk *, float *);

    	int ProcessNextChunk(Chunk *);
    	int ProcessNextObjectChunk(Chunk *);
    	int ProcessNextMaterialChunk(Chunk *);
    	int ProcessNextKeyFrameChunk(Chunk *);
    	int FillVertexBuffer(Chunk *);
    	int FillIndexBuffer(Chunk *);
    	int FillTexCoordBuffer(Chunk *);
    	int SortIndicesByMaterial(Chunk *);

    	int ComputeNormals();
    	int CompileObjects();
    	int CleanUp();

    	FILE * mFile;
    	void * mBuffer;

    	Chunk * mCurrentChunk;
    	Chunk * mTempChunk;

    };

    class Chunk
    {
    public:
    	Chunk();
    	~Chunk();

    	unsigned short int mID;
    	unsigned int mLength;
    	unsigned int mBytesRead;
    };

    #endif

Currently browsing [3ds.zip] (85,684 bytes) - [main.cpp] - (148 bytes)

    #include "3ds.h"
    #include <list>
    #include <iostream>

    extern Load3ds gLoad3ds;

    void main()
    {
    	gLoad3ds.Create("teapot.3ds");

    	return;
    }

The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.