Using the mesh API

I’ve got this class that’s suppose to load MD2 model. I don’t really know how to apply it an actual mesh in the scene? I do know that once I have the mesh I need to do this

 Vector2[] UV = new Vector2[mNumTriangles*3];
		
		int k = 0;
		for( int i = 0; i < mNumTriangles; i++ )
		{
			UV[k] = mTexCoords[mTriangles[i].mTexIndices[0]];
			UV[k+1] = mTexCoords[mTriangles[i].mTexIndices[0]];
			UV[k+2] = mTexCoords[mTriangles[i].mTexIndices[0]];
		}
		
		mesh.vertices = mVertices;
		mesh.uv = UV;

Anyways, any help on hooking this up in a scene would be of great help!

Here’s the class (the drawing function is temporary, it can be changed to work :p)

sing UnityEngine;
using System.Collections;
using System.IO;
using System.Runtime.InteropServices;
using System;

public struct MD2Header
{
	public int mMagic; // 844121161 or "IDP2" - ( ('2' << 24) + ('P' << 16) + ('D' << 8) + 'I' )
	public int mVersion; // 8
	
	public int mSkinWidth; // Texture width
	public int mSkinHeight; // Texture height
	
	public int mFrameSize; // A single frame's size in bytes
	public int mNumSkins; // Number of textures with the model
	public int mNumVertices; // Number of vertices per frame
	public int mNumTexCoords; // Number of texture coordinates
	public int mNumTriangles; // Number of triangles per frame
	public int mNumGLCommands; // Number of OGL commands
	public int mNumFrames; // Number of frames 
	
	public int mSkinsOffset; // Position in bytes from the start to the texture names 
	public int mTexCoordsOffset; // Position in bytes from the start to the texture coords
	public int mTrianglesOffset; // Position in bytes from the start to the triangle list
	public int mFramesOffset; // Position in bytes from the start to the frames list
	public int mGLCommandsOffset;  // Position in bytes from the start to the OGL commands list
	public int mEndOffset;	 // Position in bytes from the start to the end of the file
}

public struct MD2Vertex
{
	public byte[] mVerts; // Compressed vertices 
	public byte mLightNormalIndex; // Index into an array of normals by ID software
}


// float vect[3]; 
//vect[i] = (mVertices.mVerts[i] * mScale[i]) + mTranslate[i];
public struct MD2Frame
{
	public float[] mScale;
	public float[] mTranslate;
	public byte[] name;
	public MD2Vertex[] mVertices;
}

public struct MD2Triangle
{
	public short[] mVertIndices;
	public short[] mTexIndices;
}

// Divide u by SkinWidth and v by SkinHeight to get real tex coords
public struct MD2TexCoordShort
{
	public short u, v;
}

public struct MD2TexCoord
{
	public float u, v;
}

public struct MD2GLCommandVertex
{
	public float s, t;
	public int VertIndex;
}

public struct Animation
{
	public int mFirstFrame; // 1st frame of the animation
	public int mLastFrame; // Number of frame (animate mFirstFrame through mFirstFrame + mLastFrame)
	public int mFPS; // frames per second
}

public struct AnimationState
{
	public int mStartFrame; 		// first frame
	public int mEndFrame; 			// last frame
	public int mFps; 				// fps
	
	/* interpolation */
	public float mTime; 			// current time
	public float mOldTime;			// Old time
	public float mInterpolation; 	// The actual interpolation
	
	public int mType; 				// Animation type (STAND, RUN, ATTACK, ect..)
	public int mFrame; 			// Current frame
	public int mNextFrame; 		// Next frame
	public bool mLoop;				// Does the animation loop?
	public bool mIncrease;			// Should me increase the next frame (for non looping anim)
}


public class CModelMD2 : MonoBehaviour
{	
	private int mNumVerts; // Vertices per frame
	private int mNumFrames; // Total number of frames in the model
	private int mNumTexCoords; // Number of texture coordinates
	private int mNumSkins;
	private int mNumTriangles;
	
	private Vector3[] mVertices; 
	private Vector2[] mTexCoords;
	
	private MD2Frame[] mFrames;
	private MD2Triangle[] mTriangles;
	private MD2TexCoordShort[] mCompressedTexCoords;
	
	private AnimationState mAnimation; // The Current animation state


	public void LoadModel( string filename )
	{
		FileStream fs = new FileStream( filename, FileMode.Open, FileAccess.Read );
		BinaryReader pFile = new BinaryReader(fs);
		MD2Header header;
		
		// Read in the header
		byte[] buff = pFile.ReadBytes(Marshal.SizeOf(typeof(MD2Header)));//read the header as binary data
		GCHandle handle = GCHandle.Alloc(buff, GCHandleType.Pinned); //prevent the GC from affecting the buffer
		header = (MD2Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(MD2Header)); //get the header from the buffer
		handle.Free();
		
		if( header.mMagic != 844121161 || header.mVersion != 8 )
		{
			return;
		}
		
		mNumVerts = header.mNumVertices;
		mNumFrames = header.mNumFrames;
		mNumTexCoords = header.mNumTexCoords;
		mNumTriangles = header.mNumTriangles;
		mNumSkins = header.mNumSkins;
		
		mVertices = new Vector3[mNumVerts * mNumFrames];
		mFrames = new MD2Frame[mNumFrames * header.mFrameSize];
		mTriangles = new MD2Triangle[mNumTriangles];
		mTexCoords = new Vector2[mNumTexCoords];
		mCompressedTexCoords = new MD2TexCoordShort[mNumTexCoords];
		
		// Read in the texture coords
		pFile.BaseStream.Seek( header.mTexCoordsOffset, SeekOrigin.Begin );
		for(int i = 0; i < mNumTexCoords; i++ )
		{
			buff = pFile.ReadBytes(Marshal.SizeOf(typeof(MD2TexCoordShort)));//read the header as binary data
			handle = GCHandle.Alloc(buff, GCHandleType.Pinned); //prevent the GC from affecting the buffer
			mCompressedTexCoords[i] = (MD2TexCoordShort)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(MD2TexCoordShort)); //get the header from the buffer
			handle.Free();
			
			// Get the real tex coords
			mTexCoords[i].x = mCompressedTexCoords[i].u / (float)header.mSkinWidth;
			mTexCoords[i].y = mCompressedTexCoords[i].v / (float)header.mSkinHeight;
			mTexCoords[i].y = 1.0f - mTexCoords[i].y;
		}
		
		// Read in the triangles
		pFile.BaseStream.Seek( header.mTrianglesOffset, SeekOrigin.Begin );
		for(int i = 0; i < mNumTriangles; i++ )
		{
			buff = pFile.ReadBytes(Marshal.SizeOf(typeof(MD2Triangle)));//read the header as binary data
			handle = GCHandle.Alloc(buff, GCHandleType.Pinned); //prevent the GC from affecting the buffer
			mTriangles[i] = (MD2Triangle)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(MD2Triangle)); //get the header from the buffer
			handle.Free();
		}
		
		// Read in the frames
		pFile.BaseStream.Seek( header.mFramesOffset, SeekOrigin.Begin );
		for(int i = 0; i < mNumTriangles; i++ )
		{
			buff = pFile.ReadBytes(Marshal.SizeOf(typeof(MD2Frame)));//read the header as binary data
			handle = GCHandle.Alloc(buff, GCHandleType.Pinned); //prevent the GC from affecting the buffer
			mFrames[i] = (MD2Frame)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(MD2Frame)); //get the header from the buffer
			handle.Free();
		}
		
		// read in the vertices
		int k = 0;
		for( int i = 0; i < mNumFrames; i++ )
		{
			for( int j = 0; j < mNumVerts; j++ )
			{
				mVertices[k].x = (mFrames[i].mVertices[j].mVerts[0] * mFrames[i].mScale[0]) + mFrames[i].mTranslate[0];
				mVertices[k].y = (mFrames[i].mVertices[j].mVerts[1] * mFrames[i].mScale[1]) + mFrames[i].mTranslate[1];
				mVertices[k].z = (mFrames[i].mVertices[j].mVerts[2] * mFrames[i].mScale[2]) + mFrames[i].mTranslate[2];
				k++;
			}
		}
	}

	public void DrawFrame( int frame, Mesh mesh )
	{
		Vector2[] UV = new Vector2[mNumTriangles*3];
		
		int k = 0;
		for( int i = 0; i < mNumTriangles; i++ )
		{
			UV[k] = mTexCoords[mTriangles[i].mTexIndices[0]];
			UV[k+1] = mTexCoords[mTriangles[i].mTexIndices[0]];
			UV[k+2] = mTexCoords[mTriangles[i].mTexIndices[0]];
		}
		
		mesh.vertices = mVertices;
		mesh.uv = UV;
		
	}
	
	// Use this for initialization
	void Start ()
	{
		
	}
	
	// Update is called once per frame
	void Update () {
	
	}
}

Start with an empty object and give it a MeshFilter and a MeshRenderer component (from Components > Mesh menu). The GameObject class has an instance method called GetComponent that enables you to access the MeshFilter as an object from the script. The MeshFilter component has a property called “mesh” which is an instance of the Mesh class. If you look at the docs for this class, you will find it has properties called vertices, triangles, uv, etc. You just need to assign the arrays you get from parsing your original model data to these properties. When the display updates (after the Update or FixedUpdate method is called) then your mesh will be visible - assuming you did everything correctly!

I strongly recommend doing things in stages when testing your mesh code. Join the verts in array order with Debug lines to get a good rough impression that they are OK. Then, add triangles and check for holes. If the object disappears when you add normals then check they are the right way round by looking at the object from inside. Check each “feature” you add before proceeding to the next and the whole process shouldn’t turn into a nightmare.

If you need any further help, I could pass you the text of an article on mesh generation that I originally wrote for the vanished Unity magazine. Just let me know…

Thanks, I’ll give this a try! I know the actual loading code, and getting the acual vertices work because this was all C++ and OpenGL before :stuck_out_tongue:

Thanks!