flopoloco's picture

Model Loaders

Hello, do you know if someone programmer made an OpenTK model loader available to public? Currently I could use one "already-made" because I am developing some game examples. ;-)

P.S. Is there any general planning for OpenTK supporting model loaders? I would like to get my hands dirty with "Collada".


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
flopoloco's picture

I got one cool model from here

Woman N290708 - 3D model (*.3ds) for interior 3d visualization.

TheKrokodil's picture

If anyone needs some .md2 Loader:

Struct Loader:

		/// <summary>
		/// Read Struct from stream
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="s"></param>
		/// <returns></returns>
		public static T ReadStruct<T>(Stream s) where T : struct {
			return ReadStructs<T>(s, 1)[0];
		/// <summary>
		/// Read structs from stream
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="s"></param>
		/// <param name="position"></param>
		/// <param name="count"></param>
		/// <returns></returns>
		public static T[] ReadStructs<T>(Stream s, int position, int count) where T : struct {
			s.Position = position;
			return ReadStructs<T>(s, count);
		/// <summary>
		/// Read structs from stream
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="s"></param>
		/// <returns></returns>
		public static unsafe T[] ReadStructs<T>(Stream s, int count) where T : struct {
			Type type = typeof (T);
			int size = Marshal.SizeOf(type);
			byte[] data = new byte[size];
			T[] res = new T[count];
			for (int i = 0; i < count; i++) {
				s.Read(data, 0, size);
				fixed (byte* ptr = data)
					res[i] = (T) Marshal.PtrToStructure(new IntPtr(ptr), type);
			return res;
		/// <summary>
		/// Print Fields
		/// </summary>
		/// <param name="o"></param>
		/// <returns></returns>
		public static string PrintFields(object o) {
			Type t = o.GetType();
			StringBuilder b = new StringBuilder();
			b.AppendLine(string.Format("{0} ({1})", t.Name, o.GetHashCode()));
			foreach (FieldInfo f in t.GetFields(BindingFlags.Instance | BindingFlags.Public))
				b.AppendLine(f.Name.PadRight(29) + " " + f.GetValue(o));
			return b.ToString();


	/// <summary>
	/// .MD2 Loader
	/// </summary>
	public class MD2Loader : IModelLoader {
		#region Structs & Constants
		private const int MAGIC_KEY = (('I' << 0) + ('D' << 8) + ('P' << 16) + ('2' << 24));
		private static readonly float[,] NORMALS = new float[,]
		                                           		{-0.525731f, 0.000000f, 0.850651f},
		                                           		{-0.442863f, 0.238856f, 0.864188f},
		                                           		{-0.295242f, 0.000000f, 0.955423f},
		                                           		{-0.309017f, 0.500000f, 0.809017f},
		                                           		{-0.162460f, 0.262866f, 0.951056f},
		                                           		{0.000000f, 0.000000f, 1.000000f},
		                                           		{0.000000f, 0.850651f, 0.525731f},
		                                           		{-0.147621f, 0.716567f, 0.681718f},
		                                           		{0.147621f, 0.716567f, 0.681718f},
		                                           		{0.000000f, 0.525731f, 0.850651f},
		                                           		{0.309017f, 0.500000f, 0.809017f},
		                                           		{0.525731f, 0.000000f, 0.850651f},
		                                           		{0.295242f, 0.000000f, 0.955423f},
		                                           		{0.442863f, 0.238856f, 0.864188f},
		                                           		{0.162460f, 0.262866f, 0.951056f},
		                                           		{-0.681718f, 0.147621f, 0.716567f},
		                                           		{-0.809017f, 0.309017f, 0.500000f},
		                                           		{-0.587785f, 0.425325f, 0.688191f},
		                                           		{-0.850651f, 0.525731f, 0.000000f},
		                                           		{-0.864188f, 0.442863f, 0.238856f},
		                                           		{-0.716567f, 0.681718f, 0.147621f},
		                                           		{-0.688191f, 0.587785f, 0.425325f},
		                                           		{-0.500000f, 0.809017f, 0.309017f},
		                                           		{-0.238856f, 0.864188f, 0.442863f},
		                                           		{-0.425325f, 0.688191f, 0.587785f},
		                                           		{-0.716567f, 0.681718f, -0.147621f},
		                                           		{-0.500000f, 0.809017f, -0.309017f},
		                                           		{-0.525731f, 0.850651f, 0.000000f},
		                                           		{0.000000f, 0.850651f, -0.525731f},
		                                           		{-0.238856f, 0.864188f, -0.442863f},
		                                           		{0.000000f, 0.955423f, -0.295242f},
		                                           		{-0.262866f, 0.951056f, -0.162460f},
		                                           		{0.000000f, 1.000000f, 0.000000f},
		                                           		{0.000000f, 0.955423f, 0.295242f},
		                                           		{-0.262866f, 0.951056f, 0.162460f},
		                                           		{0.238856f, 0.864188f, 0.442863f},
		                                           		{0.262866f, 0.951056f, 0.162460f},
		                                           		{0.500000f, 0.809017f, 0.309017f},
		                                           		{0.238856f, 0.864188f, -0.442863f},
		                                           		{0.262866f, 0.951056f, -0.162460f},
		                                           		{0.500000f, 0.809017f, -0.309017f},
		                                           		{0.850651f, 0.525731f, 0.000000f},
		                                           		{0.716567f, 0.681718f, 0.147621f},
		                                           		{0.716567f, 0.681718f, -0.147621f},
		                                           		{0.525731f, 0.850651f, 0.000000f},
		                                           		{0.425325f, 0.688191f, 0.587785f},
		                                           		{0.864188f, 0.442863f, 0.238856f},
		                                           		{0.688191f, 0.587785f, 0.425325f},
		                                           		{0.809017f, 0.309017f, 0.500000f},
		                                           		{0.681718f, 0.147621f, 0.716567f},
		                                           		{0.587785f, 0.425325f, 0.688191f},
		                                           		{0.955423f, 0.295242f, 0.000000f},
		                                           		{1.000000f, 0.000000f, 0.000000f},
		                                           		{0.951056f, 0.162460f, 0.262866f},
		                                           		{0.850651f, -0.525731f, 0.000000f},
		                                           		{0.955423f, -0.295242f, 0.000000f},
		                                           		{0.864188f, -0.442863f, 0.238856f},
		                                           		{0.951056f, -0.162460f, 0.262866f},
		                                           		{0.809017f, -0.309017f, 0.500000f},
		                                           		{0.681718f, -0.147621f, 0.716567f},
		                                           		{0.850651f, 0.000000f, 0.525731f},
		                                           		{0.864188f, 0.442863f, -0.238856f},
		                                           		{0.809017f, 0.309017f, -0.500000f},
		                                           		{0.951056f, 0.162460f, -0.262866f},
		                                           		{0.525731f, 0.000000f, -0.850651f},
		                                           		{0.681718f, 0.147621f, -0.716567f},
		                                           		{0.681718f, -0.147621f, -0.716567f},
		                                           		{0.850651f, 0.000000f, -0.525731f},
		                                           		{0.809017f, -0.309017f, -0.500000f},
		                                           		{0.864188f, -0.442863f, -0.238856f},
		                                           		{0.951056f, -0.162460f, -0.262866f},
		                                           		{0.147621f, 0.716567f, -0.681718f},
		                                           		{0.309017f, 0.500000f, -0.809017f},
		                                           		{0.425325f, 0.688191f, -0.587785f},
		                                           		{0.442863f, 0.238856f, -0.864188f},
		                                           		{0.587785f, 0.425325f, -0.688191f},
		                                           		{0.688191f, 0.587785f, -0.425325f},
		                                           		{-0.147621f, 0.716567f, -0.681718f},
		                                           		{-0.309017f, 0.500000f, -0.809017f},
		                                           		{0.000000f, 0.525731f, -0.850651f},
		                                           		{-0.525731f, 0.000000f, -0.850651f},
		                                           		{-0.442863f, 0.238856f, -0.864188f},
		                                           		{-0.295242f, 0.000000f, -0.955423f},
		                                           		{-0.162460f, 0.262866f, -0.951056f},
		                                           		{0.000000f, 0.000000f, -1.000000f},
		                                           		{0.295242f, 0.000000f, -0.955423f},
		                                           		{0.162460f, 0.262866f, -0.951056f},
		                                           		{-0.442863f, -0.238856f, -0.864188f},
		                                           		{-0.309017f, -0.500000f, -0.809017f},
		                                           		{-0.162460f, -0.262866f, -0.951056f},
		                                           		{0.000000f, -0.850651f, -0.525731f},
		                                           		{-0.147621f, -0.716567f, -0.681718f},
		                                           		{0.147621f, -0.716567f, -0.681718f},
		                                           		{0.000000f, -0.525731f, -0.850651f},
		                                           		{0.309017f, -0.500000f, -0.809017f},
		                                           		{0.442863f, -0.238856f, -0.864188f},
		                                           		{0.162460f, -0.262866f, -0.951056f},
		                                           		{0.238856f, -0.864188f, -0.442863f},
		                                           		{0.500000f, -0.809017f, -0.309017f},
		                                           		{0.425325f, -0.688191f, -0.587785f},
		                                           		{0.716567f, -0.681718f, -0.147621f},
		                                           		{0.688191f, -0.587785f, -0.425325f},
		                                           		{0.587785f, -0.425325f, -0.688191f},
		                                           		{0.000000f, -0.955423f, -0.295242f},
		                                           		{0.000000f, -1.000000f, 0.000000f},
		                                           		{0.262866f, -0.951056f, -0.162460f},
		                                           		{0.000000f, -0.850651f, 0.525731f},
		                                           		{0.000000f, -0.955423f, 0.295242f},
		                                           		{0.238856f, -0.864188f, 0.442863f},
		                                           		{0.262866f, -0.951056f, 0.162460f},
		                                           		{0.500000f, -0.809017f, 0.309017f},
		                                           		{0.716567f, -0.681718f, 0.147621f},
		                                           		{0.525731f, -0.850651f, 0.000000f},
		                                           		{-0.238856f, -0.864188f, -0.442863f},
		                                           		{-0.500000f, -0.809017f, -0.309017f},
		                                           		{-0.262866f, -0.951056f, -0.162460f},
		                                           		{-0.850651f, -0.525731f, 0.000000f},
		                                           		{-0.716567f, -0.681718f, -0.147621f},
		                                           		{-0.716567f, -0.681718f, 0.147621f},
		                                           		{-0.525731f, -0.850651f, 0.000000f},
		                                           		{-0.500000f, -0.809017f, 0.309017f},
		                                           		{-0.238856f, -0.864188f, 0.442863f},
		                                           		{-0.262866f, -0.951056f, 0.162460f},
		                                           		{-0.864188f, -0.442863f, 0.238856f},
		                                           		{-0.809017f, -0.309017f, 0.500000f},
		                                           		{-0.688191f, -0.587785f, 0.425325f},
		                                           		{-0.681718f, -0.147621f, 0.716567f},
		                                           		{-0.442863f, -0.238856f, 0.864188f},
		                                           		{-0.587785f, -0.425325f, 0.688191f},
		                                           		{-0.309017f, -0.500000f, 0.809017f},
		                                           		{-0.147621f, -0.716567f, 0.681718f},
		                                           		{-0.425325f, -0.688191f, 0.587785f},
		                                           		{-0.162460f, -0.262866f, 0.951056f},
		                                           		{0.442863f, -0.238856f, 0.864188f},
		                                           		{0.162460f, -0.262866f, 0.951056f},
		                                           		{0.309017f, -0.500000f, 0.809017f},
		                                           		{0.147621f, -0.716567f, 0.681718f},
		                                           		{0.000000f, -0.525731f, 0.850651f},
		                                           		{0.425325f, -0.688191f, 0.587785f},
		                                           		{0.587785f, -0.425325f, 0.688191f},
		                                           		{0.688191f, -0.587785f, 0.425325f},
		                                           		{-0.955423f, 0.295242f, 0.000000f},
		                                           		{-0.951056f, 0.162460f, 0.262866f},
		                                           		{-1.000000f, 0.000000f, 0.000000f},
		                                           		{-0.850651f, 0.000000f, 0.525731f},
		                                           		{-0.955423f, -0.295242f, 0.000000f},
		                                           		{-0.951056f, -0.162460f, 0.262866f},
		                                           		{-0.864188f, 0.442863f, -0.238856f},
		                                           		{-0.951056f, 0.162460f, -0.262866f},
		                                           		{-0.809017f, 0.309017f, -0.500000f},
		                                           		{-0.864188f, -0.442863f, -0.238856f},
		                                           		{-0.951056f, -0.162460f, -0.262866f},
		                                           		{-0.809017f, -0.309017f, -0.500000f},
		                                           		{-0.681718f, 0.147621f, -0.716567f},
		                                           		{-0.681718f, -0.147621f, -0.716567f},
		                                           		{-0.850651f, 0.000000f, -0.525731f},
		                                           		{-0.688191f, 0.587785f, -0.425325f},
		                                           		{-0.587785f, 0.425325f, -0.688191f},
		                                           		{-0.425325f, 0.688191f, -0.587785f},
		                                           		{-0.425325f, -0.688191f, -0.587785f},
		                                           		{-0.587785f, -0.425325f, -0.688191f},
		                                           		{-0.688191f, -0.587785f, -0.425325f}
		#region Nested type: md2_frame
		private struct md2_frame {
			public Vector3 scale; /* scale factor */
			public Vector3 translate; /* translation vector */
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] public string name; /* frame name */
		#region Nested type: md2_glcmd
		private struct md2_glcmd {
			public float s; /* s texture coord. */
			public float t; /* t texture coord. */
			public int index; /* vertex index */
		#region Nested type: md2_header
		private struct md2_header {
			public int ident; /* magic number: "IDP2" */
			public int version; /* version: must be 8 */
			public int skinwidth; /* texture width */
			public int skinheight; /* texture height */
			public int framesize; /* size in bytes of a frame */
			public int num_skins; /* number of skins */
			public int num_vertices; /* number of vertices per frame */
			public int num_st; /* number of texture coordinates */
			public int num_tris; /* number of triangles */
			public int num_glcmds; /* number of opengl commands */
			public int num_frames; /* number of frames */
			public int offset_skins; /* offset skin data */
			public int offset_st; /* offset texture coordinate data */
			public int offset_tris; /* offset triangle data */
			public int offset_frames; /* offset frame data */
			public int offset_glcmds; /* offset OpenGL command data */
			public int offset_end; /* offset end of file */
		#region Nested type: md2_skin
		private struct md2_skin {
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public string name; /* texture file name */
		#region Nested type: md2_texCoord
		private struct md2_texCoord {
			public short s;
			public short t;
		#region Nested type: md2_triangle
		private struct md2_triangle {
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public ushort[] vertex; /* vertex indices of the triangle */
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public ushort[] st; /* tex. coord. indices */
		#region Nested type: md2_vector
		private struct md2_vector {
			public float x;
			public float y;
			public float z;
		} ;
		#region Nested type: md2_vertex
		private struct md2_vertex {
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] v; /* position */
			public byte normalIndex; /* normal vector index */
		#region IModelLoader Members
		public string[] SupportedFormats {
			get { return new string[] {".md2"}; }
		public Model Load(string filename) {
			Stream file = ContentMgr.GetFile("models", filename);
			if (file == null)
				throw new ArgumentNullException("filename", filename);
			md2_header header = Objects.ReadStruct<md2_header>(file);
			if (header.ident != MAGIC_KEY) throw new ArgumentException("Magic Key mismatch!");
			if (header.version != 8) throw new ArgumentException("Unknown Version: " + header.version);
			//Load Data
			md2_skin[] skins = Objects.ReadStructs<md2_skin>(file, header.offset_skins, header.num_skins);
			md2_texCoord[] st = Objects.ReadStructs<md2_texCoord>(file, header.offset_st, header.num_st);
			md2_triangle[] tris = Objects.ReadStructs<md2_triangle>(file, header.offset_tris, header.num_tris);
			//md2_glcmd[] glcmds = Objects.ReadStructs<md2_glcmd>(file, header.offset_glcmds+sizeof(int), header.num_glcmds);
			//todo: glcmds // primitives?
			VBO vbo;
			using (MeshBuilder b = new MeshBuilder(BeginMode.Triangles)) {
				b.Dynamic = true;
				foreach (md2_triangle tri in tris) {
					b.AddV(0, 0, 0); //uploaded later
					b.AddV(0, 0, 0); //uploaded later
					b.AddV(0, 0, 0); //uploaded later
					b.AddN(0, 0, 0); //uploaded later
					b.AddN(0, 0, 0); //uploaded later
					b.AddN(0, 0, 0); //uploaded later
					md2_texCoord tx = st[tri.st[0]];
					md2_texCoord ty = st[tri.st[1]];
					md2_texCoord tz = st[tri.st[2]];
					b.SetT(tri.vertex[0], (float) tx.s/header.skinwidth, (float) tx.t/header.skinheight);
					b.SetT(tri.vertex[1], (float) ty.s/header.skinwidth, (float) ty.t/header.skinheight);
					b.SetT(tri.vertex[2], (float) tz.s/header.skinwidth, (float) tz.t/header.skinheight);
				vbo = b.ToVBO();
			Texture tex = null;
			Stream texStream = null;
			if (skins.Length > 0)
				texStream = ContentMgr.GetFile("models", skins[0].name) ?? ContentMgr.GetFile("models", Path.GetFileNameWithoutExtension(skins[0].name) + ".png");
			if (texStream == null) texStream = ContentMgr.GetFile("models", Path.GetFileNameWithoutExtension(filename) + ".png");
			if (texStream != null) tex = new Texture((Bitmap) Image.FromStream(texStream), TextureWrapMode.Repeat);
			Vector3 minBB = Vector3.One*float.MaxValue;
			Vector3 maxBB = Vector3.One*float.MinValue;
			List<Animation> anims = new List<Animation>();
			string curName = "-1";
			List<Frame> frames = new List<Frame>();
			file.Position = header.offset_frames;
			for (int i = 0; i < header.num_frames; i++) {
				md2_frame frame = Objects.ReadStruct<md2_frame>(file);
				string name = frame.name.Substring(0, frame.name.Length - 2);
				if (curName != name) {
					if (frames.Count > 0) {
						anims.Add(new Animation(curName, frames.ToArray()));
					curName = name;
				//Generate Frame
				md2_vertex[] vertices = Objects.ReadStructs<md2_vertex>(file, header.num_vertices);
				Vector3[] vecs = new Vector3[vertices.Length];
				Vector3[] norms = new Vector3[vertices.Length];
				for (int j = 0; j < vertices.Length; j++) {
					md2_vertex old = vertices[j];
					Vector3 v = new Vector3(frame.scale.X*old.v[0] + frame.translate.X,
					                        frame.scale.Y*old.v[1] + frame.translate.Y,
					                        frame.scale.Z*old.v[2] + frame.translate.Z);
					if (v.X < minBB.X) minBB.X = v.X;
					if (v.Y < minBB.Y) minBB.Y = v.Y;
					if (v.Z < minBB.Z) minBB.Z = v.Z;
					if (v.X > maxBB.X) maxBB.X = v.X;
					if (v.Y > maxBB.Y) maxBB.Y = v.Y;
					if (v.Z > maxBB.Z) maxBB.Z = v.Z;
					vecs[j] = v;
					norms[j] = new Vector3(NORMALS[old.normalIndex, 0],
					                       NORMALS[old.normalIndex, 1],
					                       NORMALS[old.normalIndex, 2]);
				frames.Add(new Frame(vecs, norms));
			if (frames.Count > 0) anims.Add(new Animation(curName, frames.ToArray()));
			if (anims.Count < 1)
				throw new ArgumentException("No Animation found!");
			Animation anim = anims[0];
			if (anim.FrameCount < 1)
				throw new ArgumentException("No frame within animation found");
			Frame f = anim.Frames[0];
			return new Model(vbo, anims.ToArray(), tex, new AABox(minBB, maxBB));
the Fiddler's picture

Awesome, thank you!

Is it possible to post the definitions (or at least describe the interface) for the "Animation" and "Model" classes?

Also how is the "NORMALS" array used? (Is it part of the MD2 file format?)

TheKrokodil's picture


* NORMALs is part of the .md2 specifications - Quake 2 only offers 162 different normals, predefined in one of its header files.

* Model is a simple container class containing VBO, Texture, Animation[] array and BoundingBox.

public Model(VBO vbo, Animation[] anims, Texture tex, AABox aabb) {

* Animation is another container class which descripes one animation sequence (e.g. Walk); It consists out of Name, FrameCount and a Frame[] array.

public Animation(string name, Frame[] frames)

* Frame is yet another container class, and holds both Normals and Vertices of the current frame.

public Frame(Vector3[] verts, Vector3[] norms) {

* VBO is just a wrapper around OpenGL's VBO's for easier rendering

* MeshBuilder has some List<..>s, in which you can add vertices one after another; In the end you can convert it to either a DisplayList or VBO

* The AnimationController uses the Animation to interpolate the current animation state:

        /// <summary>
	/// Animation Controller
	/// </summary>
	public class AnimationController {
		/// <summary>
		/// Animation Controller
		/// </summary>
		/// <param name="m"></param>
		public AnimationController(Model m) {
			Model = m;
			Current = m.Animations[0];
		/// <summary>
		/// Model
		/// </summary>
		public Model Model { get; private set; }
		/// <summary>
		/// Current Animation
		/// </summary>
		public Animation Current { get; set; }
		/// <summary>
		/// Frame
		/// </summary>
		public float Frame { get; set; }
		/// <summary>
		/// Calculate
		/// </summary>
		/// <param name="vecs"></param>
		/// <param name="norms"></param>
		public void Calculate(ref Vector3[] vecs, ref Vector3[] norms) {
			Frame a = Current.Frames[(int) Frame];
			Frame b = Current.Frames[(int) (Frame + 1)%Current.FrameCount];
			float perc = Frame%1;
			LERP(a.Vertices, b.Vertices, vecs, perc);
			LERP(a.Normals, b.Normals, norms, perc);
		/// <summary>
		/// Updates the controller and the associated mesh
		/// </summary>
		/// <param name="time"></param>
		public void Advance(float time) {
			Frame = (Frame + time)%Current.FrameCount;
		/// <summary>
		/// Update Mesh
		/// </summary>
		public void UpdateMesh() {
			Vector3[] v, n;
			Model.GetBuffers(out v, out n);
			Calculate(ref v, ref n);
			Model.SetBuffers(ref v, ref n);
		private static void LERP(Vector3[] a, Vector3[] b, Vector3[] res, float perc) {
			for (int i = 0; i < a.Length; i++) Vector3.Lerp(ref a[i], ref b[i], perc, out res[i]);
puklaus's picture

There is good loaders out there, but you can check csat too, it loads .obj and animated md5 (doom3 fileformat) files too, link found at "Contributed" site too.

Now it's working on 0.9.9, hope it works on osx too, not 100% sure about that.