Using the DLL and Tao, the test program is only one source file in size:
using System;using Collections = System.Collections;using Drawing = System.Drawing;using IO = System.IO;using Tao.Glfw;using Tao.OpenGl;using Quake = id.Quake;namespace Testbed{ public class Application { private const int AnimationSpeed = 4; private static int FPS = 0; private static int Frame = 0; private static Quake.MDL.Model Model = null; private static Quake.Palette Palette = null; private static Quake.MDL.Vector Position = new Quake.MDL.Vector(); private static Quake.MDL.Vector Rotation = new Quake.MDL.Vector(); private static int Skin = 0; private static int Tick = 0; private static Quake.MDL.Vector[] VectorArray = null; private static void Load() { Palette = new Quake.Palette( IO.File.OpenRead( @"gfx\palette.lmp" ), 0 ); Model = new Quake.MDL.Model( IO.File.OpenRead( @"C:\id\Quake\Id1\progs\demon.mdl" ), 0 ); Gl.glBindTexture( Gl.GL_TEXTURE_2D, Skin ); Gl.glGenTextures( 1, out Skin ); Glu.gluBuild2DMipmaps( Gl.GL_TEXTURE_2D, 4, Model.SkinWidth, Model.SkinHeight, Gl.GL_BGRA, Gl.GL_UNSIGNED_BYTE, Model.SkinArray[0].ToTrueColor( Palette ) ); return; } private static void PollInput() { Glfw.glfwPollEvents(); if( Glfw.glfwGetKey( Glfw.GLFW_KEY_ESC ) == Glfw.GLFW_PRESS ) { Glfw.glfwCloseWindow(); } if( Glfw.glfwGetKey( Glfw.GLFW_KEY_F1 ) == Glfw.GLFW_PRESS ) { Gl.glPolygonMode( Gl.GL_FRONT_AND_BACK, Gl.GL_LINE ); } if( Glfw.glfwGetKey( Glfw.GLFW_KEY_F2 ) == Glfw.GLFW_PRESS ) { Gl.glPolygonMode( Gl.GL_FRONT_AND_BACK, Gl.GL_FILL ); } if( Glfw.glfwGetKey( Glfw.GLFW_KEY_UP ) == Glfw.GLFW_PRESS ) { Position.Z++; } if( Glfw.glfwGetKey( Glfw.GLFW_KEY_DOWN ) == Glfw.GLFW_PRESS ) { Position.Z--; } if( Glfw.glfwGetKey( Glfw.GLFW_KEY_LEFT ) == Glfw.GLFW_PRESS ) { Position.X++; } if( Glfw.glfwGetKey( Glfw.GLFW_KEY_RIGHT ) == Glfw.GLFW_PRESS ) { Position.X--; } if( Glfw.glfwGetKey( Glfw.GLFW_KEY_PAGEUP ) == Glfw.GLFW_PRESS ) { Position.Y++; } if( Glfw.glfwGetKey( Glfw.GLFW_KEY_PAGEDOWN ) == Glfw.GLFW_PRESS ) { Position.Y--; } if( Glfw.glfwGetKey( 'W' ) == Glfw.GLFW_PRESS ) { Rotation.X++; } if( Glfw.glfwGetKey( 'S' ) == Glfw.GLFW_PRESS ) { Rotation.X--; } if( Glfw.glfwGetKey( 'A' ) == Glfw.GLFW_PRESS ) { Rotation.Y++; } if( Glfw.glfwGetKey( 'D' ) == Glfw.GLFW_PRESS ) { Rotation.Y--; } return; } private static void Render() { Gl.glClear( Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT ); Gl.glTranslatef( Position.X, Position.Y, Position.Z ); Gl.glRotatef( Rotation.X * 2.0f, 1.0f, 0.0f, 0.0f ); Gl.glRotatef( Rotation.Y * 2.0f, 0.0f, 1.0f, 0.0f ); Gl.glBegin( Gl.GL_TRIANGLES ); Gl.glBindTexture( Gl.GL_TEXTURE_2D, Skin ); Gl.glColor3f( 1.0f, 1.0f, 1.0f ); for( int i = 0; i < Model.TriangleArray.Length; i++ ) { for( int j = 0; j < Model.TriangleArray.IndexArray.Length; j++ ) { if( ( Model.TriangleArray.FrontFace == false ) && ( Model.TextureCoordinateArray[Model.TriangleArray.IndexArray[j]].OnSeam ) ) { Gl.glTexCoord2f( Model.TextureCoordinateArray[Model.TriangleArray.IndexArray[j]].BackFaceS, Model.TextureCoordinateArray[Model.TriangleArray.IndexArray[j]].T ); } else { Gl.glTexCoord2f( Model.TextureCoordinateArray[Model.TriangleArray.IndexArray[j]].S, Model.TextureCoordinateArray[Model.TriangleArray.IndexArray[j]].T ); } Gl.glVertex3f( VectorArray[Model.TriangleArray.IndexArray[j]].Y, VectorArray[Model.TriangleArray.IndexArray[j]].Z, -VectorArray[Model.TriangleArray.IndexArray[j]].X ); } } Gl.glEnd(); Glfw.glfwSwapBuffers(); FPS++; return; } private static void Setup() { Glfw.glfwInit(); if( Glfw.glfwOpenWindow( 640, 480, 8, 8, 8, 8, 24, 0, Glfw.GLFW_WINDOW ) != Gl.GL_TRUE ) { Glfw.glfwTerminate(); return; } Glfw.glfwSetWindowTitle( "OpenGL Testbed" ); Glfw.glfwEnable( Glfw.GLFW_STICKY_KEYS ); Glfw.glfwEnable( Glfw.GLFW_STICKY_MOUSE_BUTTONS ); Gl.glClearColor( 0.0f, 0.0f, 1.0f, 0.0f ); Gl.glClearDepth( 1.0f ); Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR ); Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR ); Gl.glEnable( Gl.GL_TEXTURE_2D ); Gl.glEnable( Gl.GL_DEPTH_TEST ); Gl.glDepthFunc( Gl.GL_LEQUAL ); Gl.glEnable( Gl.GL_ALPHA_TEST ); Gl.glAlphaFunc( Gl.GL_GEQUAL, 0.1f ); Gl.glHint( Gl.GL_PERSPECTIVE_CORRECTION_HINT, Gl.GL_NICEST ); Gl.glShadeModel( Gl.GL_SMOOTH ); Gl.glViewport( 0, 0, 640, 480 ); Gl.glMatrixMode( Gl.GL_PROJECTION ); Gl.glLoadIdentity(); Glu.gluPerspective( 80.0, 640.0 / 480.0, 0.1, 1000.0 ); Gl.glMatrixMode( Gl.GL_MODELVIEW ); Gl.glLoadIdentity(); Glu.gluLookAt( 0.0f, 0.0f, -100.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f ); return; } private static void Update() { Frame = ( Tick / AnimationSpeed ) % Model.FrameArray.Length; VectorArray = Model.FrameArray[Frame].Interpolate( Model.FrameArray[(Frame+1)%Model.FrameArray.Length], (float)( Tick % AnimationSpeed ) / AnimationSpeed ); return; } [STAThread] public static void Main( string[] ArgumentArray ) { int LastTick = 0, Second = 0, LastSecond = 0; Setup(); Load(); while( Glfw.glfwGetWindowParam( Glfw.GLFW_OPENED ) == Gl.GL_TRUE ) { Second = (int)Math.Floor( Glfw.glfwGetTime() ); if( Second != LastSecond ) { Glfw.glfwSetWindowTitle( "OpenGL Testbed - " + ( FPS + 1 ).ToString() + " FPS" ); FPS = 0; LastSecond = Second; } Tick = (int)Math.Floor( Glfw.glfwGetTime() / 0.017 ); if( ( Tick == 0 ) || ( Tick != LastTick ) ) { PollInput(); Update(); Render(); LastTick = Tick; } } Glfw.glfwTerminate(); return; } } }
Should have MD2s in by the Monday and then I'll add in support for Quake BSPs.