Skip to main content
GameDev.net gamedev.net
🔒 Locked

MD2 file format

Started by Chaucer Feb 26, 2003 at 11:05 PM 19 replies 950+ views
Original Post
Chaucer
Chaucer
Hello, I have been studying the MD2 file format for the past couple hours and have tried writing a class to load and render one. I was hoping I could get some feedback on my code. It isn''t working properly and at this point, I just want to be able to load and see it. No animation or even textures yet. (I''ve tried commenting out where I read in the texture coordinates and stuff but it still doesn''t work). Anyway I appreciate any feedback or explanations of the format or how to best read in the data and render it. Thanks!
  
void ModelMD2::Load(char *pFilename)
{
	FILE *pFile;		
	char *pTextureName;
	char *buffer;
	int fileLength;

	//open rb to read in as binary

	pFile = fopen(pFilename, "rb");
	//make sure it exists

	if(!pFile) return;

	//get length of file

	fseek(pFile,0,SEEK_END);
	fileLength = ftell(pFile);
	fseek(pFile,0,SEEK_SET);

	//read file into a buffer

	buffer = new char[fileLength];
	fread(buffer,sizeof(char),fileLength,pFile);
	fclose(pFile);

	//get the header from the buffer

	m_pHeader = (HEADER*)buffer;

	//allocate the frames and vertices

	m_pFrames   = new FRAME[m_pHeader->numFrames];
	m_pVertices = new CUSTOMVERTEX3[m_pHeader->numVertices*m_pHeader->numFrames];	
	FRAME *frame;
	for(int j=0;j<m_pHeader->numFrames;j++)
	{
		//get the frame

		frame = (FRAME*)&buffer[m_pHeader->offsetFrames+(m_pHeader->framesize*j)];

		//get the vertices for this frame

		CUSTOMVERTEX3 *pVertices = (CUSTOMVERTEX3*)&m_pVertices[m_pHeader->numVertices*j];

		//copy data from frame

		for(int i=0;i<m_pHeader->numVertices;i++)
		{
			pVertices[i].position.x = frame->scale.x*frame->framePoint[i].vertex.x+
									  frame->translate.x;
			pVertices[i].position.y = frame->scale.y*frame->framePoint[i].vertex.y+
									  frame->translate.y;
			pVertices[i].position.z = frame->scale.y*frame->framePoint[i].vertex.z+
									  frame->translate.z;			
		}
	}

	//read the texture name and load texture

	pTextureName = (char*)&buffer[m_pHeader->offsetSkins+m_pHeader->numSkins];
	if(FAILED(D3DXCreateTextureFromFile(m_pd3dDevice,pTextureName,&m_d3dTexture)))
		return;
	
	//read texture coordinates

	m_pTextureCoordinates = new TEXCOORD[m_pHeader->numTextureCoordinates];
	TEXCOORDINDEX *tcIndex = (TEXCOORDINDEX*)&buffer[m_pHeader->offsetTextureCoords];
	float maxTu, maxTv;
	for(j=0;j<m_pHeader->numTextureCoordinates;j++)
	{
		//get texture coordinates

		m_pTextureCoordinates[j].tu = (float)tcIndex[j].tu;
		m_pTextureCoordinates[j].tv = (float)tcIndex[j].tv;

		//find the max texture coordinate size

		maxTu = max(maxTu, m_pTextureCoordinates[j].tu); 
		maxTv = max(maxTv, m_pTextureCoordinates[j].tv);
	}
	//fix the texture coordinates

	for(j=0;j<m_pHeader->numTextureCoordinates;j++)
	{
		m_pTextureCoordinates[j].tu /= maxTu;
		m_pTextureCoordinates[j].tv /= maxTv;
	}

	//read triangle index data

	m_pTriangleIndices		= new TRIANGLE[m_pHeader->numTriangles];
	TRIANGLE *triangleIndex = (TRIANGLE*)&buffer[m_pHeader->offsetTriangles];	
	
	for(int i=0;i<m_pHeader->numTriangles;i++)
	{
		m_pTriangleIndices[i].indices[0] = triangleIndex[i].indices[0];
		m_pTriangleIndices[i].indices[1] = triangleIndex[i].indices[1];
		m_pTriangleIndices[i].indices[2] = triangleIndex[i].indices[2];

		m_pTriangleIndices[i].textureCoordinates[0] = triangleIndex[i].textureCoordinates[0];
		m_pTriangleIndices[i].textureCoordinates[1] = triangleIndex[i].textureCoordinates[1];
		m_pTriangleIndices[i].textureCoordinates[2] = triangleIndex[i].textureCoordinates[2];
	}
	delete [] buffer;
}
/**
 * Render the Model
 *
 * @param an integer that specifies the frame to render
 */
void ModelMD2::Render(int frameNumber) 
{ 
	if(frameNumber > m_pHeader->numFrames) return; 

	m_pd3dDevice->SetVertexShader(D3DFVF_CUSTOMVERTEX3);
	
	CUSTOMVERTEX3 *pVertices = &m_pVertices[m_pHeader->numVertices*frameNumber];
	for(int j=0;j<m_pHeader->numTriangles;j++)
		m_pd3dDevice->DrawPrimitiveUP(D3DPT_TRIANGLELIST,1,(BYTE**)&pVertices[j],
									  sizeof(D3DFVF_CUSTOMVERTEX3));
}
  
Thanks!
Igilima
Igilima
Chaucer, I have a book that covers md3. There is source code on the cd for loading md3 I think. I looked for md2 but no joy. I haven''t looked too closely at that stuff yet so other than being able to send the md3 code to you, I can''t help much right now.
Muhammad Haggag
Muhammad Haggag

  pVertices[i].position.x = frame->scale.x*frame->framePoint[i].vertex.x+frame->translate.x;
pVertices[i].position.y = frame->scale.y*frame->framePoint[i].vertex.y+frame->translate.y;
pVertices[i].position.z = frame->scale.y*frame->framePoint[i].vertex.z+frame->translate.z;

The axes in md2's are somewhat swapped. As far as I recall, you should do:
myX = md2X;
myY = md2Z;
myZ = -md2Y;


quote:
for(j=0;jnumTextureCoordinates;j++)
{
...
maxTu = max(maxTu, m_pTextureCoordinates[j].tu);
maxTv = max(maxTv, m_pTextureCoordinates[j].tv);
}

MaxTU and maxTV are SkinWidth and SkinHeight (available in the md2 header).

quote:
m_pd3dDevice->DrawPrimitiveUP(D3DPT_TRIANGLELIST,1,(BYTE**)&pVertices[j], sizeof(D3DFVF_CUSTOMVERTEX3));

The 2nd parameter is the number of primitives (triangles). You're passing 1?!

Anyway, I suggest that you read about the "other" way of rendering md2's... using the glCommands. The glCommands method is somewhat optimized for OpenGL, but with a couple of tricks you can optimize it for d3d. The method relies on the following:
The glCommands is an array of "long int" commands. Starting with the 1st command:

1. Check its sign:
a) positive: We're going to draw a triangle strip. Number of vertices is the value of the command.
b) negative: We're going to draw a triangle fan. Number of vertices is negative the value of the command.

2. Loop in commands vertexCount times. For each vertex, its data can be found in the next 3 commands
a) tu & tv are the 1st 2 commands.
b) Its index is the 3rd command.

3. Repeat from 1, stopping whenever the command == 0

Here's some old code I used:

      // Loop until we're out of commands

while( 0 != (*pCommand) )
{
// If it's a triangle strip

if(*pCommand > 0)
{
PrimitiveType = D3DPT_TRIANGLESTRIP;
iVertexCount = *pCommand;
pCommand++;
}
// If it's a triangle fan

else
{
PrimitiveType = D3DPT_TRIANGLEFAN;
iVertexCount = -(*pCommand);
pCommand++;
}
assert(iVertexCount >= 3);
iPrimitiveCount = iVertexCount - 2;

// Lock the buffer

struct Vert_t
{
float x,y,z;
DWORD col;
float tu,tv;
};
Vec3_t &vScale = Frame.vScale,
&vTranslate = Frame.vTranslate;
UINT wIndex;
Vert_t* pVertices = (Vert_t*) m_vbRender.Lock();

for(UINT i=0; i < iVertexCount; i++)
{
// Read the tex coords

pVertices[i].tu = *((float*)(pCommand));
pCommand++;
pVertices[i].tv = *((float*)(pCommand));
pCommand++;

wIndex = *pCommand;
pCommand++;
// Read the vertex positions

pVertices[i].x = ( (float(Frame.pVertices[wIndex].ucVertex[1]) * vScale[1]) + vTranslate[1]) * m_fScale;
pVertices[i].y = ( (float(Frame.pVertices[wIndex].ucVertex[2]) * vScale[2]) + vTranslate[2]) * m_fScale;
pVertices[i].z = -( (float(Frame.pVertices[wIndex].ucVertex[0]) * vScale[0]) + vTranslate[0]) * m_fScale;
pVertices[i].col = 0xffffffff;
// TODO : if we've passed the upper limit : unlock, render then relock

// This would cause some stupid problems due to using trianglestrips & fans.

// I'll have to find some workaround, later.

}
m_vbRender.Unlock();
// Init the rDevice for rendering

pDxgDevice->SetStreamSource( 0,m_vbRender.GetVB(),m_vbRender.GetStride() );
pDxgDevice->SetVertexShader( m_vbRender.GetFVF() ) ;
pDxgDevice->DrawPrimitive( PrimitiveType,0,iPrimitiveCount );

I've never passed the upper limit, so I never tried to find a workaround for such situations (md2's render few vertices every command).

You can always batch strips together using degenerate triangles. You can also load your vertices and indices, put them in an ID3DXMesh, and call its stripification functions.

Cheers,
Muhammad Haggag

[edited by - Coder on February 27, 2003 1:02:49 AM]

[edited by - Coder on February 27, 2003 1:04:06 AM]

Chaucer
Chaucer
Thanks for the help. I was drawing 1 triangle at a time because I was looping through the number of triangles in the model. I guess this is the same?


  
m_pd3dDevice->DrawPrimitiveUP(D3DPT_TRIANGLELIST,m_pHeader->numTriangles,(BYTE**)&m_pVertices[0],sizeof(D3DFVF_CUSTOMVERTEX3));


I still get nothing, though. Thanks for the tip on swapping the Z and Y values. I looked it up and that was a problem as well.

I think I''ll wait to add the glcommands until I can get this working first.

Still looking through the C# code. Hopefully I''ll find some answers there. Thanks
Thanks!
Muhammad Haggag
Muhammad Haggag
- Check the debug spew. It''ll tell you if you''re doing something wrong.

- I hope your test app has a camera of some sort. Try moving around a little, the model might be behind your camera.

- Try rendering a triangle centered at - for example - (0,0,20). If it doesn''t show up, you obviously have something wrong with your renderstate/sampler_state setup.

Cheers,
Muhammad Haggag

Chaucer
Chaucer
Yeah, I''ve got a camera.

Should I be creating a vertex buffer and using DrawPrimitive instead?
Thanks!
63616C68h
63616C68h
You're lucky you know, that you get to peek into my secret cupboard:What'sit File Formats

[edited by - 63616C68h on February 27, 2003 4:56:49 PM]
Keep coming back, because it's worth it, if you work it, so work it, you're worth it!
Smurfwow
Smurfwow
Chaucer: That''s your second priority.

first you get it working, then you optimise it

Chaucer
Chaucer
Thanks for the tip

I''ve verified that I''m reading the data in and most of it is correct. I''m not sure about the vertex data. Some of my y values are "1.#QNAN" so I''m not so sure.

I guess its more likely that there''s still a problem rendering.

I''ve changed my render method to this:


  
void ModelMD2::Render(int frameNumber)
{
if(frameNumber > m_pHeader->numFrames) return;

D3DXMATRIX worldMatrix;
D3DXMatrixTranslation(&worldMatrix, 20.0f, 9.0f, 20.0f);
m_pd3dDevice->SetTransform(D3DTS_WORLD, &worldMatrix);

CUSTOMVERTEX3 *pVertices = &m_pVertices[m_pHeader->numVertices*frameNumber];
m_pd3dDevice->SetVertexShader(D3DFVF_CUSTOMVERTEX3);
m_pd3dDevice->DrawPrimitiveUP(D3DPT_TRIANGLELIST,
(m_pHeader->numVertices*frameNumber)/3,
(BYTE**)&pVertices[0],
sizeof(D3DFVF_CUSTOMVERTEX3));

D3DXMatrixIdentity(&worldMatrix);
m_pd3dDevice->SetTransform(D3DTS_WORLD, &worldMatrix);
}
Thanks!
Chaucer
Chaucer
Do I need to even get the triangle indices? Doesn''t look like I need them.
Thanks!
Smurfwow
Smurfwow
that would depend on whether your using glcommands or not.

(if you are, you dont need em)

Chaucer
Chaucer
I''m not using glcommands. Why do I need the triangle indices though? I''m just drawing the vertices as I loaded them from the file. Unless I create an index buffer for this...
Thanks!
JustinHust
JustinHust
I was under the impression that you must use the triangle''s vertex indices to figure out what constitutes the triangle

OR

You can forget about using indices, and just use the glCommands which let you loop from 0 to numVerticesInFrame - 1 and for each vertex, draw a strip or fan.

This sounds very cool if I am thinking correctly.
Chaucer
Chaucer
I created a DATA object that holds an array of vertices for each frame. Then I'm loading the vertices using the triangle indices. I've found though, that I'm getting very large numbers for my indices. Does it look like I could be reading in the data from the wrong place in the file?


    
FILE *pFile;
//char *pTextureName;

char *buffer;
int fileLength;

//open rb to read in as binary

pFile = fopen(pFilename, "rb");
//make sure it exists

if(!pFile) return;

//get length of file

fseek(pFile,0,SEEK_END);
fileLength = ftell(pFile);
fseek(pFile,0,SEEK_SET);

//read file into a buffer

buffer = new char[fileLength];
fread(buffer,sizeof(char),fileLength,pFile);
fclose(pFile);

//get the header from the buffer

HEADER *pHeader = (HEADER*)buffer;
m_pHeader = new HEADER;
m_pHeader->framesize = pHeader->framesize;
m_pHeader->id = pHeader->id;
m_pHeader->numFrames = pHeader->numFrames;
m_pHeader->numGLCommands = pHeader->numGLCommands;
m_pHeader->numSkins = pHeader->numSkins;
m_pHeader->numTextureCoordinates = pHeader->numTextureCoordinates;
m_pHeader->numTriangles = pHeader->numTriangles;
m_pHeader->numVertices = pHeader->numVertices;
m_pHeader->offsetEnd = pHeader->offsetEnd;
m_pHeader->offsetFrames = pHeader->offsetFrames;
m_pHeader->offsetGLCommands = pHeader->offsetGLCommands;
m_pHeader->offsetSkins = pHeader->offsetSkins;
m_pHeader->offsetTextureCoords = pHeader->offsetTextureCoords;
m_pHeader->offsetTriangles = pHeader->offsetTriangles;
m_pHeader->skinheight = pHeader->skinheight;
m_pHeader->skinwidth = pHeader->skinwidth;
m_pHeader->version = pHeader->version;

//allocate the vertices

m_pVertices = new CUSTOMVERTEX3[m_pHeader->numVertices*m_pHeader->numFrames];
for(int j=0;j<m_pHeader->numFrames;j++)
{
//get the frame

FRAME *pFrame = (FRAME*)&buffer[m_pHeader->offsetFrames+(m_pHeader->framesize*j)];
CUSTOMVERTEX3 *pVertices = &m_pVertices[m_pHeader->numVertices*j];
//copy data from frame

for(int i=0;i<m_pHeader->numVertices;i++)
{
pVertices[i].position.x = (pFrame->scale.x*pFrame->framePoint[i].vertex.x+pFrame->translate.x);
pVertices[i].position.y = (pFrame->scale.z*pFrame->framePoint[i].vertex.z+pFrame->translate.z);
pVertices[i].position.z = (pFrame->scale.y*pFrame->framePoint[i].vertex.z+pFrame->translate.y);
}
}

/*read the texture name and load texture
for(j=0;j<m_pHeader->numSkins;j++)
{
pTextureName = (char*)&buffer[m_pHeader->offsetSkins+j];
if(FAILED(D3DXCreateTextureFromFile(m_pd3dDevice,pTextureName,&m_d3dTexture)))
return;
}*/


//read texture coordinates

m_pTextureCoordinates = new TEXCOORD[m_pHeader->numTextureCoordinates];
TEXCOORDINDEX *tcIndex = (TEXCOORDINDEX*)&buffer[m_pHeader->offsetTextureCoords];
for(j=0;j<m_pHeader->numTextureCoordinates;j++)
{
//get texture coordinates

m_pTextureCoordinates[j].tu = (float)(tcIndex[j].tu/m_pHeader->skinwidth);
m_pTextureCoordinates[j].tv = (float)(tcIndex[j].tv/m_pHeader->skinheight);
}
//read triangle index data

m_pTriangleIndices = new TRIANGLE[m_pHeader->numTriangles];
TRIANGLE *pTriangleIndices = (TRIANGLE*)&buffer[m_pHeader->offsetTriangles];
for(int i=0;i<m_pHeader->numTriangles;i++)
{
m_pTriangleIndices[i].vertexIndices[0] = pTriangleIndices[i].vertexIndices[0];
m_pTriangleIndices[i].vertexIndices[1] = pTriangleIndices[i].vertexIndices[1];
m_pTriangleIndices[i].vertexIndices[2] = pTriangleIndices[i].vertexIndices[2];

m_pTriangleIndices[i].texCoordIndices[0] = pTriangleIndices[i].texCoordIndices[0];
m_pTriangleIndices[i].texCoordIndices[1] = pTriangleIndices[i].texCoordIndices[1];
m_pTriangleIndices[i].texCoordIndices[2] = pTriangleIndices[i].texCoordIndices[2];
}


m_pData = new DATA[m_pHeader->numFrames];
for(j=0;j<m_pHeader->numFrames;j++)
{
m_pData[j].pVertices = new CUSTOMVERTEX3[m_pHeader->numTriangles*3];
int count=0;
for(int i=0;i<m_pHeader->numTriangles;i++)
{
m_pData[j].pVertices[count] = m_pVertices[m_pTriangleIndices[i].vertexIndices[0]];
m_pData[j].pVertices[count+1] = m_pVertices[m_pTriangleIndices[i].vertexIndices[1]];
m_pData[j].pVertices[count+2] = m_pVertices[m_pTriangleIndices[i].vertexIndices[2]];
count+=3;
}
}

delete [] buffer;


edit:
btw I've checked the data in the header and I'm getting 670 trianlges with the triangleoffset at 2776 so that looks normal to me.

[edited by - Chaucer on February 28, 2003 9:27:19 PM]
Thanks!
JustinHust
JustinHust
Chaucer,

I just wrote an MD2 loader in VB that has some good debugging features. If you''ll post what model you are using I can verify your debug values with mine.

That might help. BTW, my code was designed from Nehe''s MD2/MS3D tutorial and also the MD2 file format posted in this thread.
Chaucer
Chaucer
Hey, thanks. I'm using the model that comes with the md2loader tutorial on gametutorials.com

I actually think I'm getting the correct indicies now. When I print out my vertex data, though, occasionaly, I get some very large numbers. I'm now seeing stuff on the screen, only it pretty much takes up the whole screen no matter which way I go. The large vertex numbers explain that. I just don't see how I'm reading them in wrong since I'm getting each frame by doing


  
//allocate the vertices

m_pVertices = new CUSTOMVERTEX3[m_pHeader->numVertices*m_pHeader->numFrames];
for(int j=0;j<m_pHeader->numFrames;j++)
{
//get the frame

FRAME *pFrame = (FRAME*)&buffer[m_pHeader->offsetFrames+(m_pHeader->framesize*j)];
CUSTOMVERTEX3 *pVertices = &m_pVertices[m_pHeader->numVertices*j];
//copy data from frame

for(int i=0;i<m_pHeader->numVertices;i++)
{
pVertices[i].position.x = (pFrame->scale.x*pFrame->framePoint[i].vertex.x+pFrame->translate.x);
pVertices[i].position.y = (pFrame->scale.z*pFrame->framePoint[i].vertex.z+pFrame->translate.z);
pVertices[i].position.z = -1*(pFrame->scale.y*pFrame->framePoint[i].vertex.y+pFrame->translate.y);
}
}



[edited by - Chaucer on March 1, 2003 12:19:29 PM]
Thanks!
JustinHust
JustinHust
Are you using GL Commands?
If not, you need to read in the triangles array from file using header.numtriangles.

After doing so, you need to say

// allocate enough space for all vertices
verts() = allocate numtriangles * 3 to hold vertices

// for each tri
for each triangle {
// get vertex indices
vertex0Index = triangle.verts(0)
vertex1Index = triangle.verts(1)
vertex2Index = triangle.verts(2)

// fill vertices from frame data
verts(tri * 3 + 0) = frame.scale.x * frame.vertices(vertex0index).x + frame.scale.x
verts(tri * 3 + 0) = frame.scale.z * frame.vertices(vertex0index).z + frame.scale.z
verts(tri * 3 + 0) = frame.scale.y * frame.vertices(vertex0index).y + frame.scale.y

verts(tri * 3 + 1) = frame.scale.x * frame.vertices(vertex1index).x + frame.scale.x
verts(tri * 3 + 1) = frame.scale.z * frame.vertices(vertex1index).z + frame.scale.z
verts(tri * 3 + 1) = frame.scale.y * frame.vertices(vertex1index).y + frame.scale.y

verts(tri * 3 + 2) = frame.scale.x * frame.vertices(vertex2index).x + frame.scale.x
verts(tri * 3 + 2) = frame.scale.z * frame.vertices(vertex2index).z + frame.scale.z
verts(tri * 3 + 2) = frame.scale.y * frame.vertices(vertex2index).y + frame.scale.y

}

Hope that helps.
Chaucer
Chaucer
Thanks for all your help, everyone. Turns out I was just reading the framepoint in as the wrong type of data. Just had to change my vertex data in a frame point to unsigned char.

[edited by - Chaucer on March 1, 2003 3:23:03 PM]
Thanks!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.