MD2 Model Frame Stuff

Started by
4 comments, last by xg0blin 20 years, 6 months ago
The MD2 Model seems fairly straightforward and simple, up until one point. What is stored after you get to the frame offset? Reading the code supplied from gametutorials.com, it seems like the data there is in the format

struct AliasVertex
{
unsigned char vertex[3];
unsigned char NormalIndex;
};

struct AliasFrame
{
float scale[3];
float translate[3];
char name[16]
AliasVertex *Vertices; 
// It appears to me that this number will be assigned *Vertices = (unsigned char *)malloc(sizeof(unsigned char) * NumVertices))

};
This would make sense to me, as the code given by gametutorials has a loop that looks like this:

	for (int j=0; j < m_Header.numVertices; j++)
		{
			pVertices[j].vertex[0] = pFrame->aliasVertices[j].vertex[0] * pFrame->scale[0] + pFrame->translate[0];
			pVertices[j].vertex[2] = -1 * (pFrame->aliasVertices[j].vertex[1] * pFrame->scale[1] + pFrame->translate[1]);
			pVertices[j].vertex[1] = pFrame->aliasVertices[j].vertex[2] * pFrame->scale[2] + pFrame->translate[2];
		}
Where pFrame is a pointer to an unsigned char buffer, that is casted to an AliasTriangle. Anyways, I mean that scale, transform are never incremented, so it looks like there is just one, with a whole list of vertices. Also, it looks like the name is only specified once because they have

strcpy(m_pFrames[i].strName, pFrame->name);		
If it were specified as I stated above, this might make sense to me, but it''s not. Here is what they do. They have these struct:

struct AliasTriangle
{
unsigned char vertex[3];
unsigned char NormalIndex;
};

struct AliasFrame
{
float scale[3];
float translate[3];
char name[16]
AliasTriangle Vertices[1]; 
// It appears to me that this number will be assigned *Vertices = (unsigned char *)malloc(sizeof(unsigned char) * NumVertices)) so why in the world is there only one space allocated, and why in an unsigned char array, parsed as an AliasFrame *, is there only one of everything but this?

};

What in the hell? How is their loop possible then? Only one vertex is ever allocated. Why aren''t there a series of AliasFrames then, and only one name, one scale, and one translate value, (i.e. no pframe.scale[0]. They just put pframe.scale[0]) (also no pframe.scale[0] or pframe.translate[0]. It is just pframe.scale[0] and pframe.translate[0]). I''m so lost as to how this little piece of code is working. Anyways, if you know how the data is formatted at this section of the file, please enlighten me, as I''d love to know. If you want to look at the code itself, it is the md2 animation code at www.gametutorials.com. </i>
Advertisement
I don''t really advise going to gametutorials, you should rather use the MD2 tutorial from nehe.

Anyways...

The reason why there is a scale and an offset for each frame is because the positions of each vertex are stored in 8 bit integers (to save memory on the hard drive and in the ram). Now, they have put a frame and an offset for each animation frame so that it could be tweaked as finely as possible... If you want this said otherwise: Vertex based animation is primitive, and Quake 2 is full of such "hacks".

The reason this is put this way:

AliasTriangle Vertices[1];

Is because in the file, there is a variable size array of vertices. The number vertices is the same for each frame, but its different for each model.

To work with MD2 in a "clean" way, you need to create your own structures and store it in your own way. You might want to precompute the position of the vertices in floating point vectors and store that in a large vertex buffers directly, getting rid of the scale/offset math in the render loop.


Looking for a serious game project?
www.xgameproject.com

Looking for a serious game project?
www.xgameproject.com
quote:Original post by Max_Payne
The reason why there is a scale and an offset for each frame is because the positions of each vertex are stored in 8 bit integers (to save memory on the hard drive and in the ram). Now, they have put a frame and an offset for each animation frame so that it could be tweaked as finely as possible... If you want this said otherwise: Vertex based animation is primitive, and Quake 2 is full of such "hacks".


I get this part (so far anyways)


quote:Original post by Max_Payne
AliasTriangle Vertices[1];

Is because in the file, there is a variable size array of vertices. The number vertices is the same for each frame, but its different for each model.

To work with MD2 in a "clean" way, you need to create your own structures and store it in your own way. You might want to precompute the position of the vertices in floating point vectors and store that in a large vertex buffers directly, getting rid of the scale/offset math in the render loop.


This is the part that''s bothering me. It only has memory for one. So does the data have this kind of format

Frame 1:AliasFrame[1]{ scale[0] scale[1] scale[2] tran[0] tran[1] tran[2] name[0 - 16]  AliasTriangle    vertex[0] vertex[1] vertex[2]    lightindex}AliasFrame[2]{ scale[0] scale[1] scale[2] tran[0] tran[1] tran[2] name[0 - 16]  AliasTriangle    vertex[0] vertex[1] vertex[2]    lightindex}... up to AliasFrame[n]


That would make things possible like Frame[12].AliasFrame[11].scale[0]

That doesn''t seem like what is there. The reason I''m confused is because it seems the data would be like this in the file

frame 1: scale[0] scale[1] scale[2] tran[0] tran[1] tran[2] name[0 - 16] AliasTriangle[0]  vertex[0] vertex[1] vertex[2]  lightindex AliasTriangle[1]    vertex[0] vertex[1] vertex[2]  lightindex AliasTriangle[2]  vertex[0] vertex[1] vertex[2]  lightindex  ...  up to AliasTriangle[n]


That''s what it seems like it is doing (judging by the code) to me, but then how does that work if you only have AliasTraingle[1] allocated?

quote:Original post by Max_Payne
The reason this is put this way:

AliasTriangle Vertices[1];

Is because in the file, there is a variable size array of vertices.


Ok, I just reread your post and pinpointed what I''m trying to say is my problem. There is only one set of scale, translate and name values, but a variable number of vertices. How then does it work to have Vertices[1], when there are plainly more?

And then when you''re going about the indicies, you have

unsigned char BUFFER[A BIG VALUE];

AliasFrame *pFrame = (AliasFrame *)buffer

AliasFrame is a 44 byte structure, so everytime you moved the index, the compiler would move the pointer 44 bytes.

So, could this exist: pFrame[24].scale[3]? I see nowhere in the code where they have pFrame followed by an index. They only have the vertices part with the variable number of indicies. I don''t understand why this works.

MD2 Model Header file

#ifndef MD2_H#define MD2_H#define MD2_IDENTITY	((''2''<<24) + (''P''<<16) + (''D''<<8) + ''I'')	// MD2 "magic" number - 844121161#define MD2_VERSION		8											// MD2 version number - 8/* MD2 Header */struct MD2Header {	int identity;		// magic number	int version;		// md2 version number	int texWidth;		// texture width	int texHeight;		// texture height	int frameSize;		// frame size in bytes	int numTex;			// number of textures	int numVert;		// number of vertices	int numTexCoord;	// number of texture coordinates	int numTri;			// number of triangles	int numCmd;			// number of openGL commands	int numFrame;		// number of frames	int ofsName;		// offset to skin names	int ofsTexCoord;	// offset to texture coordinates	int ofsTri;			// offset to triangles	int ofsFrame;		// offset to frame data	int ofsCmd;			// offset to OpenGL commands	int ofsEnd;			// offset to EOF};/* MD2 Frame Header */struct MD2FrameHeader {	Vector3	scale;	Vector3	translate;	char	name[16];};/* MD2 Vertex */struct MD2Vertex {	byte x;	byte y;	byte z;	byte normalIndex;};/* MD2 Triangle */struct MD2Triangle {	unsigned short vertIndex[3];	unsigned short texIndex[3];};/* MD2 Texture Coordinate */struct MD2TexCoord {	unsigned short u;	unsigned short v;};#endif



MD2 Model Loader

void ModelLoader::LoadMD2(FILE* file, Model* model) {	// allocate memory for header	MD2Header* md2Header = new MD2Header();	// load MD2 File	LoadMD2Header(file, md2Header, model);	LoadMD2Data(file, md2Header, model);	// clean up	delete md2Header;}	/////////////// PRIVATE ///////////////	/* LoadMD2Header() */void ModelLoader::LoadMD2Header(FILE* file, MD2Header* md2Header, Model* model) {	// read header data	if(fread(md2Header, sizeof(MD2Header), 1, file) < 1)		return;	// validate header	if((md2Header->identity != MD2_IDENTITY) && (md2Header->version  != MD2_VERSION))		return;		// copy header data into model	model->numFrame = md2Header->numFrame;	model->numVert	= md2Header->numVert;	model->numTri	= md2Header->numTri;}/* Load MD2Data() */void ModelLoader::LoadMD2Data(FILE* file, MD2Header* md2Header, Model* model) {	// load vertex data into model	LoadMD2VertexData(file, md2Header, model);	// load triangle and texture indices into model	LoadMD2TriangleAndTextureData(file, md2Header, model);	// load texture coordinates	LoadMD2TextureCoordinates(file, md2Header, model);}/* Load MD2VertexData() */void ModelLoader::LoadMD2VertexData(FILE* file, MD2Header* md2Header, Model* model) {	// frame and vertex info	int numVert = md2Header->numVert;	int numFrame = md2Header->numFrame;	int frameSize = md2Header->frameSize;	int frameOffset = md2Header->ofsFrame;	// allocate memory in model for frames	model->frame = new Frame[numFrame];	// store vertex data in model, one frame at at time	for(int frameIndex = 0; frameIndex < numFrame; frameIndex++) {		// allocate memory for header		MD2FrameHeader* frameHeader = new MD2FrameHeader();		// read in frame header		int seekOffset = frameOffset + (frameIndex*frameSize) + sizeof(MD2FrameHeader);		fseek(file, frameOffset + (frameIndex * frameSize), SEEK_SET);		fread(frameHeader, sizeof(MD2FrameHeader), 1, file);		cout << frameHeader->name << endl;		// allocate memory for md2 frame vertex data		MD2Vertex* frameVertexData = new MD2Vertex[numVert];		// read in frame vertex data		fseek(file, seekOffset, SEEK_SET);		fread(frameVertexData, sizeof(MD2Vertex), numVert, file);		// allocate memory for model vertices of this frame		model->frame[frameIndex].vert = new Vertex[numVert];		// decompress MD2 vertex data and store it in model		for(int i = 0; i < numVert; i++) {			// setup pointers to vertex data in md2 and model			Vertex* modelVertexPtr = &model->frame[frameIndex].vert[i];			MD2Vertex* md2VertexPtr = &frameVertexData[i];			// decompress and store in model			modelVertexPtr->x = (md2VertexPtr->x * frameHeader->scale.x) + frameHeader->translate.x;			modelVertexPtr->z = (md2VertexPtr->y * frameHeader->scale.y) + frameHeader->translate.y;			modelVertexPtr->y = (md2VertexPtr->z * frameHeader->scale.z) + frameHeader->translate.z;			modelVertexPtr->norm = md2VertexPtr->normalIndex;		}		// clean up		delete frameHeader;		delete [] frameVertexData;	}}/* LoadMD2Triangle() */void ModelLoader::LoadMD2TriangleAndTextureData(FILE* file, MD2Header* md2Header, Model* model) {	// allocate memory for md2 triangles	MD2Triangle* md2Tri = new MD2Triangle[md2Header->numTri];		// read in Triangle data	fseek(file, md2Header->ofsTri, SEEK_SET);	fread(md2Tri, sizeof(MD2Triangle), md2Header->numTri, file);	// allocate memory in model for triangle and texture indices	model->tri = new Triangle[md2Header->numTri];	model->tex = new Triangle[md2Header->numTri];	// copy triangle data into model	for(int index = 0; index < md2Header->numTri; index++) {		model->tri[index].i = md2Tri[index].vertIndex[0];		model->tri[index].j = md2Tri[index].vertIndex[1];		model->tri[index].k = md2Tri[index].vertIndex[2];		model->tex[index].i = md2Tri[index].texIndex[0];		model->tex[index].j = md2Tri[index].texIndex[1];		model->tex[index].k = md2Tri[index].texIndex[2];	}	// clean up	delete [] md2Tri;}/* LoadMD2TextureCoordinates() */void ModelLoader::LoadMD2TextureCoordinates(FILE* file, MD2Header* md2Header, Model* model) {	// allocate memory for md2 texture coordinates	MD2TexCoord* md2TexCoord = new MD2TexCoord[md2Header->numTexCoord];	// read in texture coordinates	fseek(file, md2Header->ofsTexCoord, SEEK_SET);	fread(md2TexCoord, sizeof(MD2TexCoord), md2Header->numTexCoord, file);	// allocate memory in model for texture coordinates	model->texCoord = new TextureCoordinate[md2Header->numTexCoord];	// convert and copy texture coordinates to model	for(int index = 0; index < md2Header->numTexCoord; index++) {		model->texCoord[index].u = (float)md2TexCoord[index].u / (float)md2Header->texWidth;		model->texCoord[index].v = (float)md2TexCoord[index].v / (float)md2Header->texHeight;	}	// clean up	delete [] md2TexCoord;}



enjoy
Thanks a million man. That answers my question perfectly.

This topic is closed to new replies.

Advertisement