Jump to content
  • Advertisement
Sign in to follow this  
CoinOperatedBoy

MS3D Models - Vertex Arrays...

This topic is 4434 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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
Code:
#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
Code:
#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.TriangleIndices;
	for ( i = 0; i < NumMaterials; i++ )
		delete[] Materials.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 || 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.BoneID = pVertex->m_boneID;
		memcpy( Vertices.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.VertexNormals, pTriangle->m_vertexNormals, sizeof( float )*3*3 );
		memcpy( Triangles.Textures1, pTriangle->m_s, sizeof( float )*3 );
		memcpy( Triangles.Textures2, t, sizeof( float )*3 );
		memcpy( Triangles.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.MaterialIndex = materialIndex;
		Meshes.NumTriangles = nTriangles;
		Meshes.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.Ambient, pMaterial->m_ambient, sizeof( float )*4 );
		memcpy( Materials.Diffuse, pMaterial->m_diffuse, sizeof( float )*4 );
		memcpy( Materials.Specular, pMaterial->m_specular, sizeof( float )*4 );
		memcpy( Materials.Emissive, pMaterial->m_emissive, sizeof( float )*4 );
		Materials.Shininess = pMaterial->m_shininess;
		Materials.TextureFilename = new char[strlen( pMaterial->m_texture )+1];
		strcpy(Materials.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.TextureFilename) > 0 )
		{
			std::string TextureFilename = Materials.TextureFilename;
			int cutoff = TextureFilename.find_last_of('//');
			TextureFilename = TextureFilename.substr(cutoff + 1);
			Materials.Texture = LoadGLTexture( TextureFilename.c_str() );
		}
		else
			Materials.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.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.NumTriangles; j++ )
			{
				const int triangleIndex = Meshes.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

Code:
	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

Code:
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 || 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.BoneID = pVertex->m_boneID;
		memcpy( Vertices.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.VertexNormals, pTriangle->m_vertexNormals, sizeof( float )*3*3 );
		memcpy( Triangles.Textures1, pTriangle->m_s, sizeof( float )*3 );
		memcpy( Triangles.Textures2, t, sizeof( float )*3 );
		memcpy( Triangles.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.MaterialIndex = materialIndex;
		Meshes.NumTriangles = nTriangles;
		Meshes.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.Ambient, pMaterial->m_ambient, sizeof( float )*4 );
		memcpy( Materials.Diffuse, pMaterial->m_diffuse, sizeof( float )*4 );
		memcpy( Materials.Specular, pMaterial->m_specular, sizeof( float )*4 );
		memcpy( Materials.Emissive, pMaterial->m_emissive, sizeof( float )*4 );
		Materials.Shininess = pMaterial->m_shininess;
		Materials.TextureFilename = new char[strlen( pMaterial->m_texture )+1];
		strcpy(Materials.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.TextureFilename) > 0 )
		{
			std::string TextureFilename = Materials.TextureFilename;
			int cutoff = TextureFilename.find_last_of('//');
			TextureFilename = TextureFilename.substr(cutoff + 1);
			Materials.Texture = LoadGLTexture( TextureFilename.c_str() );
		}
		else
			Materials.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
Code:
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....
Code:

	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.BoneID = pVertex->m_boneID;
		memcpy( Vertices.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.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...
Code:
	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.VertexNormals, pTriangle->m_vertexNormals, sizeof( float )*3*3 );
		memcpy( Triangles.Textures1, pTriangle->m_s, sizeof( float )*3 );
		memcpy( Triangles.Textures2, t, sizeof( float )*3 );
		memcpy( Triangles.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:
Code:
		glBegin( GL_TRIANGLES );
		{
			for ( int j = 0; j < Meshes.NumTriangles; j++ )
			{
				const int triangleIndex = Meshes.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?

Share this post


Link to post
Share on other sites
Advertisement
i answered your previous post on opengl.org :

http://www.opengl.org/discussion_boards/ubb/ultimatebb.php?ubb=get_topic;f=2;t=020458

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!