Switching MS3D from glBegin/end to Vertex arrays

Okay, this issue has been daunting me for 4 months or so now. This issue has caused me to give up 3d programming, but I’m back baby!

Now who can tell me how to make a centralized renderer? No, but seriously…

Okay, jokes aside, I know all the concepts, I know how everything works, I know exactly how my ms3d loading code works (though not exactly everything it does), I know exactly how a centralized renderer works.

But I suck at coding! No seriously, I can think really well, but coding is hard for me!

Okay, let me show you the uber code, so you know where I’m coming from.

MS3D.h

#ifndef MS3D_H
#define MS3D_H
#include <windows.h>																						
#include <gl\glaux.h>						
#include <vector>


class MS3DModel
{
public:

	MS3DModel();

	virtual ~MS3DModel();


	struct Vertex
	{
		char BoneID;
		float Location[3];
	};


	int NumVertices;
	Vertex * Vertices;

	struct Triangle
	{
		float VertexNormals[3][3];
		float Textures1[3], Textures2[3];
		int VertexIndices[3];
	};

	int NumTriangles;
	Triangle *Triangles;

	struct Mesh
	{
		int MaterialIndex;
		int NumTriangles;
		int *TriangleIndices;
	};

	int NumMeshes;
	Mesh *Meshes;

	struct Material
	{
		float Ambient[4], Diffuse[4], Specular[4], Emissive[4];
		float Shininess;
		GLuint Texture;
		char * TextureFilename;
	};

	int NumMaterials;
	Material *Materials;

	bool Load( const std::string & name );
	void ReloadTextures();
	void Draw();
};

#endif

Okay, that is my ms3d model and the data structures it uses. I’ll continue with more code, then do alot of explaining.

MS3D.cpp

#include <windows.h>		// Header File For Windows
#include <fstream>
#include "MS3D.h"
#include <iostream>
#include "TextureManager.h"

using namespace std;
MS3DModel::MS3DModel()
{
	
	NumMeshes = 0;
	Meshes = NULL;
	NumMaterials = 0;
	Materials = NULL;
	NumTriangles = 0;
	Triangles = NULL;
	
	NumVertices = 0;
	Vertices = NULL;
}

MS3DModel::~MS3DModel()
{
	
	int i;
	for ( i = 0; i < NumMeshes; i++ )
		delete[] Meshes[i].TriangleIndices;
	for ( i = 0; i < NumMaterials; i++ )
		delete[] Materials[i].TextureFilename;

	NumMeshes = 0;//
	if ( Meshes != NULL )
	{
		delete[] Meshes;
		Meshes = NULL;
	}

	NumMaterials = 0;
	if ( Materials != NULL )
	{
		delete[] Materials;
		Materials = NULL;
	}

	NumTriangles = 0;
	if ( Triangles != NULL )
	{
		delete[] Triangles;
		Triangles = NULL;
	}
	
	NumVertices = 0;
	if ( Vertices != NULL )
	{
		delete[] Vertices;
		Vertices = NULL;
	}
}

/* 
	MS3D STRUCTURES 
*/

// byte-align structures
#ifdef _MSC_VER
#	pragma pack( push, packing )
#	pragma pack( 1 )
#	define PACK_STRUCT
#elif defined( __GNUC__ )
#	define PACK_STRUCT	__attribute__((packed))
#else
#	error you must byte-align these structures with the appropriate compiler directives
#endif

typedef unsigned char byte;
typedef unsigned short word;

// File header
struct MS3DHeader
{
	char m_ID[10];
	int m_version;
} PACK_STRUCT;

// Vertex information
struct MS3DVertex
{
	byte m_flags;
	float m_vertex[3];
	char m_boneID;
	byte m_refCount;
} PACK_STRUCT;

// Triangle information
struct MS3DTriangle
{
	word m_flags;
	word m_vertexIndices[3];
	float m_vertexNormals[3][3];
	float m_s[3], m_t[3];
	byte m_smoothingGroup;
	byte m_groupIndex;
} PACK_STRUCT;

// Material information
struct MS3DMaterial
{
    char m_name[32];
    float m_ambient[4];
    float m_diffuse[4];
    float m_specular[4];
    float m_emissive[4];
    float m_shininess;	// 0.0f - 128.0f
    float m_transparency;	// 0.0f - 1.0f
    byte m_mode;	// 0, 1, 2 is unused now
    char m_texture[128];
    char m_alphamap[128];
} PACK_STRUCT;

//	Joint information
struct MS3DJoint
{
	byte m_flags;
	char m_name[32];
	char m_parentName[32];
	float m_rotation[3];
	float m_translation[3];
	word m_numRotationKeyframes;
	word m_numTranslationKeyframes;
} PACK_STRUCT;

// Keyframe data
struct MS3DKeyframe
{
	float m_time;
	float m_parameter[3];
} PACK_STRUCT;

// Default alignment
#ifdef _MSC_VER
#	pragma pack( pop, packing )
#endif

#undef PACK_STRUCT

bool MS3DModel::Load(const std::string & name)
{
	const char *ptr1 = 0;
	ptr1= name.data ( );

	ifstream inputFile( ptr1, ios::in | ios::binary);
	if ( inputFile.fail())
	{
		MessageBox(NULL,"Model file not found.","Model Error",MB_OK);
		return false;	// "Couldn't open the model file."
	}

	inputFile.seekg( 0, ios::end );
	long fileSize = inputFile.tellg();
	inputFile.seekg( 0, ios::beg );

	byte *pBuffer = new byte[fileSize];
	inputFile.read((char *)pBuffer, fileSize );
	inputFile.close();

	const byte *pPtr = pBuffer;
	MS3DHeader *pHeader = ( MS3DHeader* )pPtr;
	pPtr += sizeof( MS3DHeader );

	if ( strncmp( pHeader->m_ID, "MS3D000000", 10 ) != 0 )
	{
		MessageBox(NULL,"Not a valid Milkshape3D model file.", "Model Error",MB_OK);
		return false; // "Not a valid Milkshape3D model file."
	}

	if ( pHeader->m_version < 3 &#0124;&#0124; pHeader->m_version > 7 )
	{
		MessageBox(NULL,"Not a valid Milkshape3D file version.","Model Error",MB_OK);
		return false; // "Unhandled file version.  Milkshape3D Version 1.3 through 1.7 is supported." :)
	}

	int nVertices = *( word* )pPtr; 
	NumVertices = nVertices;
	Vertices = new Vertex[nVertices];
	pPtr += sizeof( word );

	int i;
	for ( i = 0; i < nVertices; i++ )
	{
		MS3DVertex *pVertex = ( MS3DVertex* )pPtr;
		Vertices[i].BoneID = pVertex->m_boneID;
		memcpy( Vertices[i].Location, pVertex->m_vertex, sizeof( float )*3 );
		pPtr += sizeof( MS3DVertex );
	}


	int nTriangles = *( word* )pPtr;
	NumTriangles = nTriangles;
	Triangles = new Triangle[nTriangles];
	pPtr += sizeof( word );

	for ( i = 0; i < nTriangles; i++ )
	{
		MS3DTriangle *pTriangle = ( MS3DTriangle* )pPtr;
		int vertexIndices[3] = { pTriangle->m_vertexIndices[0], pTriangle->m_vertexIndices[1], pTriangle->m_vertexIndices[2] };
		float t[3] = { 1.0f-pTriangle->m_t[0], 1.0f-pTriangle->m_t[1], 1.0f-pTriangle->m_t[2] };
		memcpy( Triangles[i].VertexNormals, pTriangle->m_vertexNormals, sizeof( float )*3*3 );
		memcpy( Triangles[i].Textures1, pTriangle->m_s, sizeof( float )*3 );
		memcpy( Triangles[i].Textures2, t, sizeof( float )*3 );
		memcpy( Triangles[i].VertexIndices, vertexIndices, sizeof( int )*3 );
		pPtr += sizeof( MS3DTriangle );
	}

	int nGroups = *( word* )pPtr;
	NumMeshes = nGroups;
	Meshes = new Mesh[nGroups];
	pPtr += sizeof( word );
	for ( i = 0; i < nGroups; i++ )
	{
		pPtr += sizeof( byte );	// flags
		pPtr += 32;				// name

		word nTriangles = *( word* )pPtr;
		pPtr += sizeof( word );
		int *pTriangleIndices = new int[nTriangles];
		for ( int j = 0; j < nTriangles; j++ )
		{
			pTriangleIndices[j] = *( word* )pPtr;
			pPtr += sizeof( word );
		}

		char materialIndex = *( char* )pPtr;
		pPtr += sizeof( char );
	
		Meshes[i].MaterialIndex = materialIndex;
		Meshes[i].NumTriangles = nTriangles;
		Meshes[i].TriangleIndices = pTriangleIndices;
	}

	int nMaterials = *( word* )pPtr;
	NumMaterials = nMaterials;
	Materials = new Material[nMaterials];
	pPtr += sizeof( word );
	for ( i = 0; i < nMaterials; i++ )
	{
		MS3DMaterial *pMaterial = ( MS3DMaterial* )pPtr;
		memcpy( Materials[i].Ambient, pMaterial->m_ambient, sizeof( float )*4 );
		memcpy( Materials[i].Diffuse, pMaterial->m_diffuse, sizeof( float )*4 );
		memcpy( Materials[i].Specular, pMaterial->m_specular, sizeof( float )*4 );
		memcpy( Materials[i].Emissive, pMaterial->m_emissive, sizeof( float )*4 );
		Materials[i].Shininess = pMaterial->m_shininess;
		Materials[i].TextureFilename = new char[strlen( pMaterial->m_texture )+1];
		strcpy(Materials[i].TextureFilename, pMaterial->m_texture);
		pPtr += sizeof( MS3DMaterial );
	}

	ReloadTextures();

	delete[] pBuffer;

	return true;
}

void MS3DModel::ReloadTextures()
{
	for ( int i = 0; i < NumMaterials; i++ )
		if ( strlen(Materials[i].TextureFilename) > 0 )
		{
			std::string TextureFilename = Materials[i].TextureFilename;
			int cutoff = TextureFilename.find_last_of('//');
			TextureFilename = TextureFilename.substr(cutoff + 1);
			Materials[i].Texture = LoadGLTexture( TextureFilename.c_str() );
		}
		else
			Materials[i].Texture = 0;
}

void MS3DModel::Draw()
{

	GLboolean texEnabled = glIsEnabled( GL_TEXTURE_2D );
 
	// Draw by group
	for ( int i = 0; i < NumMeshes; i++ )
	{

		const int materialIndex = Meshes[i].MaterialIndex;
		if ( materialIndex >= 0 )
		{
			glMaterialfv( GL_FRONT, GL_AMBIENT, Materials[materialIndex].Ambient );
			glMaterialfv( GL_FRONT, GL_DIFFUSE, Materials[materialIndex].Diffuse );
			glMaterialfv( GL_FRONT, GL_SPECULAR, Materials[materialIndex].Specular );
			glMaterialfv( GL_FRONT, GL_EMISSION, Materials[materialIndex].Emissive );
			glMaterialf( GL_FRONT, GL_SHININESS, Materials[materialIndex].Shininess );
 
			if ( Materials[materialIndex].Texture > 0 )
			{
				glBindTexture( GL_TEXTURE_2D, Materials[materialIndex].Texture );
				glEnable( GL_TEXTURE_2D );
			}
			else
				glDisable( GL_TEXTURE_2D );
		}
		else
		{
			// Material properties?
			glDisable( GL_TEXTURE_2D );
		}
 
		glBegin( GL_TRIANGLES );
		{
			for ( int j = 0; j < Meshes[i].NumTriangles; j++ )
			{
				const int triangleIndex = Meshes[i].TriangleIndices[j];
				const MS3DModel::Triangle * pTri = &(Triangles[triangleIndex]);
 
				for ( int k = 0; k < 3; k++ )
				{
					const int index = pTri->VertexIndices[k];
 
					glNormal3fv( pTri->VertexNormals[k] );
					glTexCoord2f( pTri->Textures1[k], pTri->Textures2[k] );
					glVertex3fv( Vertices[index].Location );
				}
			}
		}
		glEnd();
	}
 
	if ( texEnabled )
		glEnable( GL_TEXTURE_2D );
	else
		glDisable( GL_TEXTURE_2D );
}

Okay, now, turn that code into something that uses a single vertex array.

GO!!!

No, but seriously, lemme try to explain what I’m trying to do… And after I do that you’ll be like, jeeze why don’t you just do it yourself?

I’M ADD THATS WHY!!!

Okay, lets look at the header file first, this is our class for the ms3d object, it is an ms3d model, an ms3d model it is, it is simply that, and nothing more. See how it can draw itself? Well that is what I’m out to change, last time I checked humans don’t animate themselves, they are created in a mommy, with some help from a daddy. Those two words are euphanisms here, the daddy would be our big bad logic engine while the mommy would be our rendering engine… The logic engine is all like, gimme a baby! And the rendering engine spits it out like wildfire.

This leads me to want to make it so I can Load this object type into a universal format. Think of it this way… I want my mommy to be able to make mexican babies, asian babies, white babies, and black babies (for lack ofa better term?). Well, right now the way the model is loaded, the mommy would have to be tailored to support the ms3d model only! (or have a big state machine inside the renderer, and that would be silly!)… We wanna set it up so the ms3d model loads into a universal format

Are you following me? Well, I think this is the best I’ve ever explained this before in my life!

So, lets get more detailed here…

A code snippet

	struct Vertex
	{
		char BoneID;
		float Location[3];
	};


	int NumVertices;
	Vertex * Vertices;

	struct Triangle
	{
		float VertexNormals[3][3];
		float Textures1[3], Textures2[3];
		int VertexIndices[3];
	};

	int NumTriangles;
	Triangle *Triangles;

	struct Mesh
	{
		int MaterialIndex;
		int NumTriangles;
		int *TriangleIndices;
	};

	int NumMeshes;
	Mesh *Meshes;

	struct Material
	{
		float Ambient[4], Diffuse[4], Specular[4], Emissive[4];
		float Shininess;
		GLuint Texture;
		char * TextureFilename;
	};

	int NumMaterials;
	Material *Materials;

That was taken from MS3D.h, this is how we store our information as is, this needs to be changed so we can send a single, count it ONE, dynamically allocated array to a renderer. We don’t wanna draw by triangle anymore, we wanna draw with an index, we wanna draw pure vertex information.

That right above there, is a HUGE MESS, theres like 4 differents structs in a single data type, BAH! I want one freakin indexed array, (or 2 or 3), in ONE structure, so I can send the ms3d model to the renderer and have it, say something like…

Okay, lets see these vertices, BAM DRAW!!! Now lets do normals kiddies, BAM draw normals, mmk, what else do we need, color? COLORFIED! Oh, tex coords you say?! Texture applied! BAM!

Does it sound too good to be true?! Well so far for me it is and that makes me VERY SAD!!! BECAUSE I CAN’T FIGURE IT OUT!!! GOD!

OMG I started programming for 3 days and already it is driving me mad again!

I can’t believe this post isn’t over yet!!! Okay, where was I… Feel free to take 5, I need some -dedicated- help here.

Okay, now comes the actual hard part, now whats going to happen with the loading code? Let me show you this beast of a function

bool MS3DModel::Load(const std::string & name)
{
	const char *ptr1 = 0;
	ptr1= name.data ( );

	ifstream inputFile( ptr1, ios::in | ios::binary);
	if ( inputFile.fail())
	{
		MessageBox(NULL,"Model file not found.","Model Error",MB_OK);
		return false;	// "Couldn't open the model file."
	}

	inputFile.seekg( 0, ios::end );
	long fileSize = inputFile.tellg();
	inputFile.seekg( 0, ios::beg );

	byte *pBuffer = new byte[fileSize];
	inputFile.read((char *)pBuffer, fileSize );
	inputFile.close();

	const byte *pPtr = pBuffer;
	MS3DHeader *pHeader = ( MS3DHeader* )pPtr;
	pPtr += sizeof( MS3DHeader );

	if ( strncmp( pHeader->m_ID, "MS3D000000", 10 ) != 0 )
	{
		MessageBox(NULL,"Not a valid Milkshape3D model file.", "Model Error",MB_OK);
		return false; // "Not a valid Milkshape3D model file."
	}

	if ( pHeader->m_version < 3 &#0124;&#0124; pHeader->m_version > 7 )
	{
		MessageBox(NULL,"Not a valid Milkshape3D file version.","Model Error",MB_OK);
		return false; // "Unhandled file version.  Milkshape3D Version 1.3 through 1.7 is supported." :)
	}

	int nVertices = *( word* )pPtr; 
	NumVertices = nVertices;
	Vertices = new Vertex[nVertices];
	pPtr += sizeof( word );

	int i;
	for ( i = 0; i < nVertices; i++ )
	{
		MS3DVertex *pVertex = ( MS3DVertex* )pPtr;
		Vertices[i].BoneID = pVertex->m_boneID;
		memcpy( Vertices[i].Location, pVertex->m_vertex, sizeof( float )*3 );
		pPtr += sizeof( MS3DVertex );
	}


	int nTriangles = *( word* )pPtr;
	NumTriangles = nTriangles;
	Triangles = new Triangle[nTriangles];
	pPtr += sizeof( word );

	for ( i = 0; i < nTriangles; i++ )
	{
		MS3DTriangle *pTriangle = ( MS3DTriangle* )pPtr;
		int vertexIndices[3] = { pTriangle->m_vertexIndices[0], pTriangle->m_vertexIndices[1], pTriangle->m_vertexIndices[2] };
		float t[3] = { 1.0f-pTriangle->m_t[0], 1.0f-pTriangle->m_t[1], 1.0f-pTriangle->m_t[2] };
		memcpy( Triangles[i].VertexNormals, pTriangle->m_vertexNormals, sizeof( float )*3*3 );
		memcpy( Triangles[i].Textures1, pTriangle->m_s, sizeof( float )*3 );
		memcpy( Triangles[i].Textures2, t, sizeof( float )*3 );
		memcpy( Triangles[i].VertexIndices, vertexIndices, sizeof( int )*3 );
		pPtr += sizeof( MS3DTriangle );
	}

	int nGroups = *( word* )pPtr;
	NumMeshes = nGroups;
	Meshes = new Mesh[nGroups];
	pPtr += sizeof( word );
	for ( i = 0; i < nGroups; i++ )
	{
		pPtr += sizeof( byte );	// flags
		pPtr += 32;				// name

		word nTriangles = *( word* )pPtr;
		pPtr += sizeof( word );
		int *pTriangleIndices = new int[nTriangles];
		for ( int j = 0; j < nTriangles; j++ )
		{
			pTriangleIndices[j] = *( word* )pPtr;
			pPtr += sizeof( word );
		}

		char materialIndex = *( char* )pPtr;
		pPtr += sizeof( char );
	
		Meshes[i].MaterialIndex = materialIndex;
		Meshes[i].NumTriangles = nTriangles;
		Meshes[i].TriangleIndices = pTriangleIndices;
	}

	int nMaterials = *( word* )pPtr;
	NumMaterials = nMaterials;
	Materials = new Material[nMaterials];
	pPtr += sizeof( word );
	for ( i = 0; i < nMaterials; i++ )
	{
		MS3DMaterial *pMaterial = ( MS3DMaterial* )pPtr;
		memcpy( Materials[i].Ambient, pMaterial->m_ambient, sizeof( float )*4 );
		memcpy( Materials[i].Diffuse, pMaterial->m_diffuse, sizeof( float )*4 );
		memcpy( Materials[i].Specular, pMaterial->m_specular, sizeof( float )*4 );
		memcpy( Materials[i].Emissive, pMaterial->m_emissive, sizeof( float )*4 );
		Materials[i].Shininess = pMaterial->m_shininess;
		Materials[i].TextureFilename = new char[strlen( pMaterial->m_texture )+1];
		strcpy(Materials[i].TextureFilename, pMaterial->m_texture);
		pPtr += sizeof( MS3DMaterial );
	}

	ReloadTextures();

	delete[] pBuffer;

	return true;
}

void MS3DModel::ReloadTextures()
{
	for ( int i = 0; i < NumMaterials; i++ )
		if ( strlen(Materials[i].TextureFilename) > 0 )
		{
			std::string TextureFilename = Materials[i].TextureFilename;
			int cutoff = TextureFilename.find_last_of('//');
			TextureFilename = TextureFilename.substr(cutoff + 1);
			Materials[i].Texture = LoadGLTexture( TextureFilename.c_str() );
		}
		else
			Materials[i].Texture = 0;
}

Okay, so one of two things are going to happen. This code will double in size when I am done (meaning I’d have to load it this way and THEN put it into the data types I want…

Or It can be nice and neat, and I can cut to the chase and load it straight into those structures. Oh how I would ever so want this to happen.

Where do I begin? How do I even begin to work on this beast? All I really need are the following, A normal array, a texture coord array, and a vertex array. All of these need to be indexed, indexed to the point where I can input an index and it will draw only the arm or something (for animation purposes)…

So I want my Final structure of data to look something liek this

Struct ModelDat
{
VertexArray[];
NormalArray[];
TexCoordArray[];
}

Wow thats a ton of data packed into a tiny bit of code!

Alright, we have like 5 structures to begin with…

So I see in our loading code we do the vertices here…

	int nVertices = *( word* )pPtr; 
	NumVertices = nVertices;
	Vertices = new Vertex[nVertices];
	pPtr += sizeof( word );

	int i;
	for ( i = 0; i < nVertices; i++ )
	{
		MS3DVertex *pVertex = ( MS3DVertex* )pPtr;
		Vertices[i].BoneID = pVertex->m_boneID;
		memcpy( Vertices[i].Location, pVertex->m_vertex, sizeof( float )*3 );
		pPtr += sizeof( MS3DVertex );
	}

Okay, so far it looks all fine and dandy, and I think I’m right, maybe. It is dynamically allocating vertices[i].location, and creating an array of vertices…

I wonder if I stuck this array of vertices in a VA for opengl it would work? No, of course not, apparently I’m going to have to unshare the vertices. Apparently what I have right now isn’t enough to properly light the model, you can’t share normals I don’t think, you need every one individually.

So finally, you know problem number 1… I hope you’re still with me.

Now onto texture coords, this is where it gets strange…

	int nTriangles = *( word* )pPtr;
	NumTriangles = nTriangles;
	Triangles = new Triangle[nTriangles];
	pPtr += sizeof( word );

	for ( i = 0; i < nTriangles; i++ )
	{
		MS3DTriangle *pTriangle = ( MS3DTriangle* )pPtr;
		int vertexIndices[3] = { pTriangle->m_vertexIndices[0], pTriangle->m_vertexIndices[1], pTriangle->m_vertexIndices[2] };
		float t[3] = { 1.0f-pTriangle->m_t[0], 1.0f-pTriangle->m_t[1], 1.0f-pTriangle->m_t[2] };
		memcpy( Triangles[i].VertexNormals, pTriangle->m_vertexNormals, sizeof( float )*3*3 );
		memcpy( Triangles[i].Textures1, pTriangle->m_s, sizeof( float )*3 );
		memcpy( Triangles[i].Textures2, t, sizeof( float )*3 );
		memcpy( Triangles[i].VertexIndices, vertexIndices, sizeof( int )*3 );
		pPtr += sizeof( MS3DTriangle );
	}

Apparently this is where the tex coords are dynamically allocated, well yes that is true. This is also where we create the index into our vertices, and our normals… Wow, nifty!

So what’s the problem? Well, it does it by TRIANGLE!!! GAH!!! WTF!!! For ever triangle, GOD, why not vertices?! WHY GOD WHY!!! Okay, maybe I’m delirious here, but don’t you need a normal per vertex for proper lighting? And don’t you need a U/V element for every single vertex?

And that is problem number two, which I believe stems from problem number 1…

Now this is all fine and dandy if you’re going to draw your model like this:

		glBegin( GL_TRIANGLES );
		{
			for ( int j = 0; j < Meshes[i].NumTriangles; j++ )
			{
				const int triangleIndex = Meshes[i].TriangleIndices[j];
				const MS3DModel::Triangle * pTri = &(Triangles[triangleIndex]);
 
				for ( int k = 0; k < 3; k++ )
				{
					const int index = pTri->VertexIndices[k];
 
					glNormal3fv( pTri->VertexNormals[k] );
					glTexCoord2f( pTri->Textures1[k], pTri->Textures2[k] );
					glVertex3fv( Vertices[index].Location );
				}
			}
		}
		glEnd();
	}
 
	if ( texEnabled )
		glEnable( GL_TEXTURE_2D );
	else
		glDisable( GL_TEXTURE_2D );
}

Hm, so yeah… Look at that beautifully optimized ms3d code wonder… Yeah, optimized, but purely like, alone in its design, maybe if every model type worked this way, but that isn’t the case!

So MS3D Mode I’m sorry you must change!

I wanna be able to draw you with a simple gldrawelements call for your normals vertices and tex coords…

Soo, can anyone help me?

-Shamino, the absent minded programmer.

i got about 10 lines into that, then gave up reading :stuck_out_tongue:

right then, i guess you are asking, given my nice compact data structures for ms3d, which have an index for each vert/norm/uv etc, how the hell do i render that efficiently in vertex arrays?

Rule1 - Only use a single file format within your game engine.

The rational is simple, why add 20 different file formats into your engine? It’s only going to make your game slower and harder to debug.

Rule2 - The game engine format should be as simple as possible.

Simple == Fast.

Rule3 - a single malloc, and a single fread is all thats needed to load a file into your engine.

honestly it’s true! Excessive numbers of read calls, and excessive number of mallocs slow things down a lot. Don’t do it. It also means your game engine can load data blindingly quickly, and if you are clever, you can optimise the data layout of your files to speed things up a lot.

Rule4 - The loaded data should be constant when loaded.

This really comes into play when you start having animated/skinned meshes. By forcing the loaded data to be constant, you are forceed to create a 2nd structure to hold the data that is required to change. The upshot is, that you automatically support instancing (ie multiple model instances using the same model data)

Rule5 - You can support as many file formats as you want

A slight contradiction to rule 1 here i know, but bear with me. Basically dont support any file formats generated from modelling packages. They are in-efficient for rendering, so don’t use them!
Instead, write a seperate command line application to process the data which generates the file format for your game engine. That app can be added too over time to support more formats (and can also form the basis of editing & viewing tools later on).

Some basic examples…

  1. a very basic static mesh loader for vertex array usage…
// change alignment of data to 1byte alignment
#pragma pack(push,1)  

// each vertex element must have a vert, normal & uv coord.
// note, sizeof(VertData) == 36 bytes
struct VertData 
{
  unsigned bone;   // making this 4 bytes, it's nicer than 1 here... 
  float vertex[3];
  float normal[3];
  float uv[2];
};

// never going to create this struct, it just defines a memory layout
// 
struct MeshData 
{
  unsigned int numFaceIndices;
  unsigned int indicesOffset;
  unsigned int totalSizeInBytes;
  
  // hack to allow you to access the next bit of data
  unsigned char data[1];
  
  
  // some utility funcs to get some data
  const VertData* GetStartVertex() const 
  {
    void* ptr = (void*)data;
    return (VertData*)ptr;
  }
  
  const unsigned short* GetIndices() const 
  {
    void* ptr = (void*)( data + indicesOffset );
    return (unsigned short*) ptr;
  }
  
  const void* GetDataEnd() const 
  {
    return (data + totalSizeInBytes);
  }

private:
  // hide constructor, to prevent an instance 
  // of this class being created.
  MeshData() {};
};


// the mesh file memory layout
class MeshDataFile 
{
public:

  unsigned numMeshes;
  
  // the mesh data exists here
  unsigned char data[1];
  
  const MeshData* GetFirstMesh() const 
  {
    return (MeshData*)( (void*)data );
  }
  
private:
  //
  MeshDataFile() {}
};

#pragma pack(pop)


class MeshFileLoader 
{
public:

  MeshFileLoader() : meshLookup(0) { data = 0; }
  
  ~MeshFileLoader() 
  {
    delete [] meshLookup;
    delete [] data;
  }

  // should be able to render this
  const MeshData* GetMesh(unsigned i) const
  {
    assert(GetNumMeshes()>i);
    assert(meshLookup);
    return meshLookup[i];
  }

  const unsigned GetNumMeshes() const 
  {
    assert(data_file);
    return data_file->numMeshes;
  }
  
  bool Load(const char filename[])
  {
    FILE* fp = fopen(filename,"rb");
    if(fp)
    {
      // load the data
      fseek(fp,0,SEEK_END);
      size_t sz = ftell(fp);
      rewind(fp);
      data = new unsigned char[sz];
      fread(data,1,sz,fp);
      fclose(fp);
      
      // build lookup array
      meshLookup = new MeshData*[ GetNumMeshes() ];
      
      const MeshData* pMesh = data_file->GetFirstMesh();
      for(unsigned i=0;i!=GetNumMeshes();++i)
      {
        meshLookup[i] = (MeshData*) pMesh;
        pMesh = (MeshData*)pMesh->GetDataEnd();
      }
      return true;
    }
    return false;
  }
  
private:

  MeshData** meshLookup;
  
  union 
  {
    MeshDataFile* data_file;
    unsigned char* data;  
  };  
};

when rendering the above, you prolly want to set the array pointers using…

MeshData* mesh = someMesh; // i assume you can figure this out... 

glVertexPointer( 3, GL_FLOAT, sizeof(VertData), mesh->GetFirstVertex()->vertex );
glNormalPointer( GL_FLOAT, sizeof(VertData), mesh->GetFirstVertex()->normal );
glTexCoordPointer( 2, GL_FLOAT, sizeof(VertData), mesh->GetFirstVertex()->vertex );


// render the indices here... 
  1. Vertex array conversion from the vertex data of MS3D. This is not fast, but since it’ll be in a command line app, who cares :wink:
#include <vector>
#include <stdlib.h>
#include <stdio.h>

// your stuff.... I'll assume it's global data, you'll need to properly sort this out.

struct Vertex
{
  char BoneID;
  float Location[3];
};

struct Triangle
{
  float VertexNormals[3][3];
  float Textures1[3], Textures2[3];
  int VertexIndices[3];
};

struct Mesh
{
  int MaterialIndex;
  int NumTriangles;
  int *TriangleIndices;
};

int NumVertices;
Vertex * Vertices;


int NumTriangles;
Triangle *Triangles;


int NumMeshes;
Mesh *Meshes;
  
  
// conversion stuff  


// can't use == for floats!!
#define FCOMP(x,y) ( fabs(x-y) < 0.0001f )

struct FinalVertex
{
  char bone;
  float vertex[3];
  float normal[3];
  float uv[2];
  
  FinalVertex() {}
  FinalVertex(const FinalVertex& vd)
  {
    memcpy(this,&vd,sizeof(VertData));
  }
  
  void write(FILE* fp)
  {
    unsigned ibone = (unsigned)bone;
    fwrite(&ibone,1,sizeof(unsigned),fp);
    fwrite(vertex,3,sizeof(float),fp);
    fwrite(normal,3,sizeof(float),fp);
    fwrite(uv,2,sizeof(float),fp);
  }
  
  bool operator == (const FinalVertex& vd) const
  {
    return  bone == vd.bone &&
            FCOMP(vd.vertex[0],vertex[0]) &&
            FCOMP(vd.vertex[1],vertex[1]) &&
            FCOMP(vd.vertex[2],vertex[2]) &&
            FCOMP(vd.normal[0],normal[0]) &&
            FCOMP(vd.normal[1],normal[1]) &&
            FCOMP(vd.normal[2],normal[2]) &&
            FCOMP(vd.uv[0],uv[0]) &&
            FCOMP(vd.uv[1],uv[1]);
  }
};

// going to fill these data arrays, then write them to the file... 
std::vector<FinalVertex> Verts;
std::vector<unsigned short> Indices;

unsigned short FindIndex(const FinalVertex& vd)
{ 
  unsigned short i = 0;
  
  // hunt for vertex in list
  for( ; i!=Verts.size(); ++i )
  {
    if( Verts[i] == vd )
      return i;
  }
  
  // add to list if not found
  Verts.push_back(vd);
  
  return i;
}


bool WriteFile(const char out_filename[])
{
  FILE* fp = fopen(out_filename,"wb");
  if(!fp)
    return false;
    
    
  // first write num meshes
  {
    unsigned num_meshes = (unsigned)NumMeshes;
    fwrite(&num_meshes,1,sizeof(unsigned),fp);
  }
  
  // now for each mesh, convert to vertex array..
  for(int i=0;i!=NumMeshes;++i)
  {
    // get a ptr to the mesh
    Mesh *TheMesh = Meshes+i;
    
    Indices.resize( TheMesh->NumTriangles*3 );
    
    std::vector<unsigned short>::iterator it_indices = Indices.begin();
    
    for(unsigned j=0;j!=TheMesh->NumTriangles;++j)
    {
      assert(TheMesh->TriangleIndices[j]<NumTriangles);
      
      Triangle* TheTriangle = Triangles + TheMesh->TriangleIndices[j] ;
      
      for(unsigned k=0;k!=3;++k)
      {
        assert( TheTriangle->VertexIndices[k] < NumVertices );
        
        Vertex* TheVertex = Vertices + TheTriangle->VertexIndices[k];
        
        // fill a vertex data structure
        FinalVertex vd;
        vd.bone = TheVertex->BoneID;
        memcpy(vd.vertex,TheVertex->Location,3*sizeof(float));
        memcpy(vd.normal,TheTriangle->VertexNormals[k],3*sizeof(float));
        vd.uv[0] = TheTriangle->Textures1[k];
        vd.uv[1] = TheTriangle->Textures2[k];
        
        // get new index for it... 
        Indices.push_back( FindIndex(vd) );
      }    
    }
    
    // now write this mesh into the file.
        
    unsigned int numFaceIndices = (unsigned)Indices.size();
    unsigned int indicesOffset  = 36 * Verts.size();         // sizeof(VertData)==36
    unsigned int totalSizeInBytes = indicesOffset + numFaceIndices*sizeof(unsigned short);
    
    // start writing MeshData structure
    //
    fwrite(&numFaceIndices,1,sizeof(unsigned),fp);
    fwrite(&indicesOffset,1,sizeof(unsigned),fp);
    fwrite(&totalSizeInBytes,1,sizeof(unsigned),fp);
    
    // write all vertices
    {
      std::vector<FinalVertex>::iterator it =  Verts.begin();
      for( ; it != Verts.end(); ++it )
      {
        it->write(fp);
      }      
    }

    // write all face indices
    {
      std::vector<unsigned short>::iterator it =  Indices.begin();
      for( ; it != Indices.end(); ++it )
      {
        unsigned short index = *it;
        fwrite(&index,1,sizeof(unsigned short),fp);
      }      
    }    
    
    // clear temp arrays for next mesh... 
    Verts.clear();
    Indices.clear();
  }
    
  fclose(fp);
  
  // done!
  
  return true;
}

I’ve knocked out those code samples, the theory is correct, you may need to fix the odd compile error or bug…

I’ve got some bigger examples using MD2 (a crap file format) and blendshapes. The techniques used may give you some ideas though…

http://www.robthebloke.org/opengl_programming.html#5

Rob

Omgooses, I really need to soak this all in…

When you convert the MS3D Model into your own file format (really good idea btw)… Do you now have the ability to throw it into a vertex array and draw it that way, as well as use a proper lighting model?

I heard of a problem where if you just throw an MS3D Model’s normals into a VA there won’t be enough, because they are shared. Are you accounting for this problem?

Originally posted by Shamino:
[b]Do you now have the ability to throw it into a vertex array and draw it that way, as well as use a proper lighting model?

I heard of a problem where if you just throw an MS3D Model’s normals into a VA there won’t be enough, because they are shared. Are you accounting for this problem? [/b]
It assumes that a vertex is only the same if, and only if, the vertex, normal & uv coord are the same. The generated vertex arrays have correct normals and tex coords.

Okay, Lets Isolate this purely into the conversion process of the MS3D model, take away all the file writing parts.

Can you show me just that? :slight_smile: I’m very close to understanding this.

Uhh hi there, I’m not meaning to purposefully bump this thread (I know it’s frowned upon) but I need to ask a very specific question about how you render the incides.

After reading this thread and talking with shamino I implemented my own constructs for converting an ms3d model to vertex arrays that can be rendered as, you guessed it, glDrawArrays.

I ask because when I render in immediate mode, using the converted vertex arrays, it renders perfectly. When I try rendering using glDrawArrays, it simply crashes, and throws an exception inside the ATI driver (meaning I told it to access something that simply does not exist).

First, here’s the code in question that crashes:

#if	USE_NT_INDEX_ARRAY
	#if	0
			int	start_index = mdl_ref.mpIndexArray[mesh_ref.mStartVertIndex];
	#else
			int	start_index = mesh_ref.mStartVertIndex;	
	#endif

			NT_GL(glVertexPointer(3,GL_FLOAT,sizeof(NT_VERTEX),mdl_ref.mpVertices[start_index].vertex))
			NT_GL(glTexCoordPointer(2,GL_FLOAT,sizeof(NT_VERTEX),mdl_ref.mpVertices[start_index].tex_coord))
			NT_GL(glDrawElements(GL_TRIANGLES,mesh_ref.mNumMeshVerts,GL_UNSIGNED_INT,&mdl_ref.mpIndexArray[mesh_ref.mStartMeshIndex]))

As I said, it crashes on the call to glDrawElements. Here are my fundamental data structures, starting with NT_VERTEX, and then NT_MODEL (which contains NT_MESH):

struct	NT_VERTEX
{
	NT_VERTEX()
	{
		memset(tex_coord,0,sizeof(float)*2);
		memset(vertex,0,sizeof(float)*3);
		memset(normal,0,sizeof(float)*3);
	}
	bool	operator==(NT_VERTEX&rhs);
	float	vertex[3];
	float	normal[3];
	float	tex_coord[2];
};

std::ofstream	&	operator << (std::ofstream & out,NT_VERTEX & vert);

/*
	A mesh is a collection of unique vertices
*/

struct	NT_MODEL
{
	NT_MODEL()
	{
		mpMeshes		=	NULL;
		mNumMeshes		=	0;
		
		mpIndexArray	=	NULL;
		mNumIndices		=	0;

		mpVertices		=	NULL;
		mNumVertices	=	0;
		
		mpTextures		=	NULL;
		mNumTextures	=	0;
	}
	~NT_MODEL()
	{
		if(mpMeshes)
		{
			delete[] mpMeshes;	mpMeshes = NULL;	mNumMeshes	=	0;
		}

		if(mpIndexArray)
		{
			delete[]	mpIndexArray;	mpIndexArray = NULL; mNumIndices	=	0;
		}

		if(mpVertices)
		{
			delete[]	mpVertices;	mpVertices	=	NULL;	mNumVertices	=	0;
		}

		if(mpTextures)
		{
			delete[]	mpTextures; mpTextures	=	NULL; mNumTextures	=	0;
		}
	}

	void	BuildFromMS3DModel(CMS3D_MODEL&model);
	int		FindIndex(NT_VERTEX &);
	NT_VERTEX			*mpVertices;
	int					mNumVertices;

	
	struct	NT_MESH
	{
		NT_MESH()
		{
			mStartVertIndex	=	mNumMeshVerts	=	mTextureIndex = mStartMeshIndex = 0;
		}

		void	ConvertMS3DVertsToNT_Verts(CMS3D_MODEL & model,CMS3D_MODEL::mesh & mesh,std::vector<NT_VERTEX> & out);
		
		int					mStartVertIndex;	//real start index
		int					mStartMeshIndex;	//specifically the starting 
		int					mNumMeshVerts;	
		int					mTextureIndex;
	};
	NT_MESH	*mpMeshes;
	int		mNumMeshes;


	int		*mpTextures;	
	int		mNumTextures;	//equal to the number of MS3D materials, don't care about the lighting stuff just now

	int		*mpIndexArray;
	int		mNumIndices;
private:
	void	ConvertMS3DVertsToNT_Verts(CMS3D_MODEL&model,std::vector<NT_VERTEX> & out);
	void	EnsureVertexesAreUnique(std::vector<NT_VERTEX> & heap,std::vector<NT_VERTEX> & unique);
};

Ultimately, the layout is very basic: NT_MODEL holds a heap of unique vertexes. The unique data is found correctly, so I won’t post that code. Here is how I render the same data, in immediate mode, except this way works (all meshes render fine with no artifacts, crashes, etc)

//alternative to the method above, works
			gpNTGLAPI->ntglBegin(GL_TRIANGLES);
			
			//I've used this method of accessing data for immediate mode rendering, works fine
			for(int  j = 0; j < mesh_ref.mNumMeshVerts; j += 3) //reference to NT_MODEL::NT_MESH
			{	
				for(int k = 0; k < 3; k++)	//3 verts in a triangle
				{
					int	index = mdl_ref.mpIndexArray[j + k + mesh_ref.mStartVertIndex];
					
					NT_VERTEX	&	vert_ref = mdl_ref.mpVertices[index];
					gpNTGLAPI->ntglTexCoord2fv(vert_ref.tex_coord);
					gpNTGLAPI->ntglVertex3fv(vert_ref.vertex);
				}	
			}
			gpNTGLAPI->ntglEnd();

Do you have any idea what I might be doing wrong?

Blah, I really need to register. Could a mod delete what I posted? That code is not even the right version.

Anyway, I got it working :slight_smile:

I’m getting errors when I’m trying to assert, can you tell me why? I’m including everything properly…

------ Build started: Project: NeHeGL, Configuration: Debug Win32 ------
Compiling...
Renderer.cpp
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(78) : error C3861: 'assert': identifier not found
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(79) : error C3861: 'assert': identifier not found
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(84) : error C3861: 'assert': identifier not found
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(168) : error C3861: 'fabs': identifier not found
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(169) : error C3861: 'fabs': identifier not found
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(170) : error C3861: 'fabs': identifier not found
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(171) : error C3861: 'fabs': identifier not found
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(172) : error C3861: 'fabs': identifier not found
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(173) : error C3861: 'fabs': identifier not found
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(174) : error C3861: 'fabs': identifier not found
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(175) : error C3861: 'fabs': identifier not found
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(212) : error C3861: 'assert': identifier not found
z:\home\jcoleman\my documents\engine source 2\engine source 2\model_converter.h(216) : error C3861: 'assert': identifier not found
Creating browse information file...
Microsoft Browse Information Maintenance Utility Version 8.00.50727
Copyright (C) Microsoft Corporation. All rights reserved.
Build log was saved at "file://\\intellimark\dfs\Home\jcoleman\My Documents\Engine Source 2\Engine Source 2\Debug\BuildLog.htm"
NeHeGL - 13 error(s), 0 warning(s)
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

I’ll show you the code that is erroring…

	const MeshData* GetMesh(unsigned i) const 
	{   
		assert(GetNumMeshes()>i);  
		assert(meshLookup);  
		return meshLookup[i]; 
	}  
	const unsigned GetNumMeshes() const  
	{  
		assert(data_file);   
		return data_file->numMeshes;  
	}    

Any line that has assert in it is failing, as well as fcomp…

		bool operator == (const FinalVertex& vd) const  
		{   
			return  bone == vd.bone &&    
				FCOMP(vd.vertex[0],vertex[0]) &&    
				FCOMP(vd.vertex[1],vertex[1]) &&       
				FCOMP(vd.vertex[2],vertex[2]) &&  
				FCOMP(vd.normal[0],normal[0]) &&   
				FCOMP(vd.normal[1],normal[1]) &&   
				FCOMP(vd.normal[2],normal[2]) &&    
				FCOMP(vd.uv[0],uv[0]) &&        
				FCOMP(vd.uv[1],uv[1]);  
		}
	};

Any ideas?