// PhilVaz OpenGL Demo // Load and Render MD2 Quake 2 Model // Oct 30, 2005 // INCLUDES /////////////////////////////////////////////// #define WIN32_LEAN_AND_MEAN #include // include all the windows headers #include // include useful macros #include // for rand functions #include #include #include // OpenGL32 library #include // GLU32 library #include // GLAUX library #include "MD2.h" // include MD2 file definitions, structs, classes // DEFINES //////////////////////////////////////////////// // defines for windows #define WINDOW_CLASS_NAME "WINCLASS1" #define WINDOW_WIDTH 800 // size of game window #define WINDOW_HEIGHT 600 #define WINDOW_BPP 16 #define GAME_SPEED 15 // speed of game (increase to go slower) #define ANIM_SPEED 35 // space between anim changes (CTRL key) #define SPIN_SPEED .05f // Y rotate speed #define ZOOM_SPEED .50f // Z (depth) speed #define ZOOM_START -80.0f // Z (depth) start position #define FILE_NAME "model.md2" // 3D model MD2 file #define TEXTURE_NAME "skin.bmp" // 3D model BMP skin // MACROS ///////////////////////////////////////////////// #define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0) #define KEYUP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1) // GLOBALS //////////////////////////////////////////////// HWND game_window = NULL; // global game window handle HINSTANCE game_instance = NULL; // global game instance handle HDC game_dc = NULL; // global device context (GDI) handle DEVMODE game_screen; // global for full screen mode HGLRC game_rc = NULL; // global rendering context (OpenGL) handle bool draw_ok; // for whether OpenGL init ok char text[255]; GLfloat yAngle = 0.0f; // Y Axis Angle for model GLfloat ySpin = 0.0f; // Y Axis Spin for model (no spin to start) GLfloat zPos = ZOOM_START; // Zoom on Z Axis int anim_count = 0; // space between animations (to slow down CTRL key) UINT model_texture[MAX_TEXTURES] = {0}; // texture info referenced by an ID CLoadMD2 game_model; // global MD2 class t3DModel game_object; // global MD2 Model object int render_mode = GL_TRIANGLES; // FUNCTIONS ////////////////////////////////////////////// bool GameInit(); void GameMain(); void GameQuit(); // MD2 animation functions bool CreateTexture(UINT texArray[], LPSTR filename, int texID); float ReturnCurrentTime(t3DModel *pModel, int nextFrame); void RenderModel(t3DModel *pModel); // WINPROC //////////////////////////////////////////////// LRESULT CALLBACK WinProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // this is the main message handler of the system HDC hdc; // handle to a device context PAINTSTRUCT ps; // used in WM_PAINT switch(msg) // what is the message { case WM_CREATE: { // do initialization stuff here return(0); // return success } break; case WM_PAINT: { hdc = BeginPaint(hwnd, &ps); // validate the window EndPaint(hwnd, &ps); return(0); // return success } break; case WM_DESTROY: { PostQuitMessage(0); // kill the application, sends a WM_QUIT message return(0); // return success } break; default:break; } // end switch // process any messages that we didn't take care of return (DefWindowProc(hwnd, msg, wparam, lparam)); } // end WinProc // WINMAIN //////////////////////////////////////////////// int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow) { WNDCLASSEX winclass; // this will hold the class we create HWND hwnd; // generic window handle MSG msg; // generic message // first fill in the window class structure winclass.cbSize = sizeof(WNDCLASSEX); winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW; winclass.lpfnWndProc = WinProc; winclass.cbClsExtra = 0; winclass.cbWndExtra = 0; winclass.hInstance = hinstance; winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); winclass.hCursor = LoadCursor(NULL, IDC_ARROW); winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); winclass.lpszMenuName = NULL; winclass.lpszClassName = WINDOW_CLASS_NAME; // save the game instance handle game_instance = hinstance; // register the window class if (!RegisterClassEx(&winclass)) return(0); // create the window if (!(hwnd = CreateWindowEx(NULL, // extended style WINDOW_CLASS_NAME, // class "Model Loading Demo", // title WS_POPUP | WS_VISIBLE, // use POPUP for full screen 0,0, // initial game window x,y WINDOW_WIDTH, // initial game width WINDOW_HEIGHT, // initial game height NULL, // handle to parent NULL, // handle to menu hinstance, // instance of this application NULL))) // extra creation parms return(0); // save the game window handle game_window = hwnd; draw_ok = GameInit(); // game initialization function called here // enter main event loop using PeekMessage() to retrieve messages while(TRUE) { // get initial tick count to keep game speed constant DWORD start_tick = GetTickCount(); // is there a message in queue, if so get it if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { // test if this is a quit if (msg.message == WM_QUIT) break; // translate any accelerator keys TranslateMessage(&msg); // send the message to WinProc DispatchMessage(&msg); } // end if GameMain(); // game main processing function called here // check for key and send quit game if (KEYDOWN(VK_ESCAPE) || !draw_ok) SendMessage (hwnd, WM_CLOSE, 0, 0); // wait until we hit correct game speed frame rate while ((GetTickCount() - start_tick) < GAME_SPEED); } // end while GameQuit(); // game quit function and clean up before exit called here return(msg.wParam); // return to Windows } // end WinMain // BEGIN GAME CODE //////////////////////////////////////// /////////////////////////////////////////////////////////// // // GAME INITIALIZATION // /////////////////////////////////////////////////////////// bool GameInit() { int i; // for loop int pf; // pixel format // temporary change to full screen mode ZeroMemory(&game_screen, sizeof(game_screen)); // clear out size of DEVMODE struct game_screen.dmSize = sizeof(game_screen); game_screen.dmPelsWidth = WINDOW_WIDTH; game_screen.dmPelsHeight = WINDOW_HEIGHT; game_screen.dmBitsPerPel = WINDOW_BPP; game_screen.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; ChangeDisplaySettings(&game_screen, CDS_FULLSCREEN); game_dc = GetDC(game_window); // get the GDI device context // set up the pixel format desc struct PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // size of this PFD 1, // version number PFD_DRAW_TO_WINDOW | // supports window PFD_SUPPORT_OPENGL | // supports OpenGL PFD_DOUBLEBUFFER, // support double buff PFD_TYPE_RGBA, // request RGBA format WINDOW_BPP, // select color depth 0, 0, 0, 0, 0, 0, // color bits ignored 0, // no alpha buff 0, // shift bit ignored 0, // no accum buff 0, 0, 0, 0, // accum bits ignored 16, // 16-bit Z-buff (depth buff) 0, // no stencil buff 0, // no aux buff PFD_MAIN_PLANE, // main drawing layer 0, // reserved 0, 0, 0 // layer masks ignored }; if (!(pf = ChoosePixelFormat(game_dc, &pfd))) // match the pixel format { MessageBox(game_window, "OpenGL could not be initialized -- ChoosePixelFormat Error","OpenGL Error",MB_OK); return FALSE; // error returned } if (!SetPixelFormat(game_dc, pf, &pfd)) // set the pixel format { MessageBox(game_window, "OpenGL could not be initialized -- SetPixelFormat Error","OpenGL Error",MB_OK); return FALSE; // error returned } if (!(game_rc = wglCreateContext(game_dc))) // create the rendering context { MessageBox(game_window, "OpenGL could not be initialized -- CreateContext Error","OpenGL Error",MB_OK); return FALSE; // error returned } if (!wglMakeCurrent(game_dc, game_rc)) // make it current { MessageBox(game_window, "OpenGL could not be initialized -- MakeCurrent Error","OpenGL Error",MB_OK); return FALSE; // error returned } MessageBox(game_window, "OpenGL Initialized Okay -- Entering GameMain Loop -- Rotate/Move the Model with arrow keys, SPACE to reset, CTRL for next animation, and P = Pause and Show Model Stats","OpenGL OK",MB_OK); // set up viewport and perspective glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0f, (GLfloat)WINDOW_WIDTH / (GLfloat)WINDOW_HEIGHT, 0.5f, 1000.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glEnable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); // face culling on glCullFace(GL_FRONT); // front culling // LOAD THE MD2 MODEL if (!game_model.ImportMD2(&game_object, FILE_NAME, TEXTURE_NAME)) { MessageBox(game_window, "Sorry, MD2 model did not load properly or not a valid MD2 model","LoadMD2Model Error",MB_OK); return FALSE; // error returned } // end if for (i = 0; i < game_object.numMats; i++) // loop through materials { if(strlen(game_object.pMats[i].filename) > 0) // is there a file name for material? { if (!CreateTexture(model_texture, game_object.pMats[i].filename, i)) { MessageBox(game_window, "Sorry, BMP skin did not load properly or not a valid BMP","LoadMD2Model Error",MB_OK); return FALSE; } // end if } // end if game_object.pMats[i].texID = i; // set texture ID for material } // end for i // game_object.pAnims[1].startFrame = 40; // FIX DRAGON RUN HACK return TRUE; // success init OpenGL and 3D model loaded } // END OF GameInit /////////////////////////////////////////////////////////// // // GAME MAIN LOOP AND PROCESSING // /////////////////////////////////////////////////////////// void GameMain() { if (!draw_ok) return; // set up camera, clear screen, and render scene glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glTranslatef(0.0f, 0.0f, zPos); glRotatef(yAngle, 0.0f, 1.0f, 0.0f); // rotate object around Y Axis RenderModel(&game_object); // call animation function to draw and animate model object SwapBuffers(game_dc); yAngle += ySpin; // rotate on Y Axis if (KEYDOWN(VK_RIGHT)) ySpin += SPIN_SPEED; // right/left controls spin on Y if (KEYDOWN(VK_LEFT)) ySpin -= SPIN_SPEED; if (KEYDOWN(VK_DOWN)) zPos += ZOOM_SPEED; // up/down controls size (depth) on Z if (KEYDOWN(VK_UP)) zPos -= ZOOM_SPEED; if (KEYDOWN(VK_SPACE)) // reset model position { ySpin = 0.0f; yAngle = 0.0f; zPos = ZOOM_START; } if (KEYDOWN(80)) // pause and show model stats { sprintf(text, "Vertices = %d Faces (Tris) = %d current Anim = %d current Frame = %d", game_object.pObject[0].numVerts, game_object.pObject[0].numFaces, game_object.currentAnim, game_object.currentFrame); MessageBox(game_window, text,"Pause LoadMD2Model -- Showing Model Stats",MB_OK); } if (KEYDOWN(VK_CONTROL)) // CTRL changes to next MD2 animation { anim_count++; if (anim_count > ANIM_SPEED) // spaces between CTRL key presses { game_object.currentAnim = (game_object.currentAnim + 1) % (game_object.numAnims); game_object.currentFrame = game_object.pAnims[game_object.currentAnim].startFrame; anim_count = 0; } // end if } // end if } // END OF GameMain /////////////////////////////////////////////////////////// // // GAME QUIT AND CLEAN UP // /////////////////////////////////////////////////////////// void GameQuit() { int i; // for loop for (i = 0; i < game_object.numFrames; i++) // free faces, normals, vertices, and texture coords { if (game_object.pObject[i].pFaces) delete [] game_object.pObject[i].pFaces; if (game_object.pObject[i].pNormals) delete [] game_object.pObject[i].pNormals; if (game_object.pObject[i].pVerts) delete [] game_object.pObject[i].pVerts; if (game_object.pObject[i].pTexVerts) delete [] game_object.pObject[i].pTexVerts; } if (game_rc) // if rendering context (OpenGL) exists, delete it { wglMakeCurrent(NULL,NULL); wglDeleteContext(game_rc); } // release the device context (GDI) from the game window ReleaseDC(game_window, game_dc); // return to original display settings ChangeDisplaySettings(NULL,NULL); } // END OF GameQuit ///////////////////////////////////////////////////////////////////////////////////////// // // MD2 MODEL ANIMATION FUNCTIONS // ///////////////////////////////////////////////////////////////////////////////////////// // // Returns time (t) for interpolation between current and next keyframe // ///////////////////////////////////////////////////////////////////////////////////////// float ReturnCurrentTime(t3DModel *pModel, int nextFrame) { static float elapsed_time = 0.0f; static float last_time = 0.0f; float time = GetTickCount(); // get current time in milliseconds elapsed_time = time - last_time; // get time elapsed since last float t = elapsed_time / (1000.0f / ANIM_KEY); // t = elapsed divided by 1 second = t if (elapsed_time >= (1000.0f / ANIM_KEY)) // check if advance to next keyframe { pModel->currentFrame = nextFrame; // set current to next keyframe last_time = time; // save last time } return t; // t is interpolation time } // end ReturnCurrentTime ///////////////////////////////////////////////////////////////////////////////////////// // // Render and Animate MD2 Model with interpolation // ///////////////////////////////////////////////////////////////////////////////////////// void RenderModel(t3DModel *pModel) { int i, j; // for loops if (pModel->pObject.size() <= 0) return; // do we have valid object? tAnimationInfo *pAnim = &(pModel->pAnims[pModel->currentAnim]); // get current animation int nextFrame = (pModel->currentFrame + 1) % pAnim->endFrame; // get current frame if (nextFrame == 0) nextFrame = pAnim->startFrame; // if zero start animation over t3DObject *pFrame = &pModel->pObject[pModel->currentFrame]; // get current keyframe t3DObject *pNextFrame = &pModel->pObject[nextFrame]; // get next for interpolation t3DObject *pFirstFrame = &pModel->pObject[0]; // get first keyframe for addr to texture and face info float t = ReturnCurrentTime(pModel, nextFrame); // get interpolation time (from 0 to 1 = 100%) glBegin(render_mode); // start rendering model (based on render mode) for (j = 0; j < pFrame->numFaces; j++) // loop through all faces of current frame { for (i = 0; i < 3; i++) // loop each vertex of triangle { int vertIndex = pFirstFrame->pFaces[j].vertIndex[i]; // get index for each face point int texIndex = pFirstFrame->pFaces[j].texIndex[i]; // get index for each texture coord if(pFirstFrame->pTexVerts) // check UVW map applied to object { // get texture coordinate for this vertex glTexCoord2f(pFirstFrame->pTexVerts[texIndex].x, pFirstFrame->pTexVerts[texIndex].y); } // store current and next frame vertex CVector3 vPoint1 = pFrame->pVerts[vertIndex]; // for interpolation equation below CVector3 vPoint2 = pNextFrame->pVerts[vertIndex]; // save current and next frame vertex // using equation p(t) = p0 + t(p1 - p0) with time t, find interpolated x, y, z glVertex3f(vPoint1.x + t * (vPoint2.x - vPoint1.x), vPoint1.y + t * (vPoint2.y - vPoint1.y), vPoint1.z + t * (vPoint2.z - vPoint1.z)); } // end for i } // end for j glEnd(); // stop rendering } // end RenderModel ////////////////////////////////////////////////////////////////////////////// // // Load BMP texture for MD2 model // ////////////////////////////////////////////////////////////////////////////// bool CreateTexture(UINT texArray[], LPSTR filename, int texID) { AUX_RGBImageRec *pBitmap = NULL; if(!filename) return FALSE; // check if BMP file exists pBitmap = auxDIBImageLoad(filename); // load BMP file if (!pBitmap) return FALSE; // check if BMP loaded properly glGenTextures(1, &texArray[texID]); // generate texture with ID in array glPixelStorei (GL_UNPACK_ALIGNMENT, 1); // set align for each pixel row glBindTexture(GL_TEXTURE_2D, texArray[texID]); // bind and init texture and build mipmaps gluBuild2DMipmaps(GL_TEXTURE_2D, 3, pBitmap->sizeX, pBitmap->sizeY, GL_RGB, GL_UNSIGNED_BYTE, pBitmap->data); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST); // texture map quality glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR); if (pBitmap) // free BMP { if (pBitmap->data) free(pBitmap->data); // free data free(pBitmap); // free pointer } return TRUE; // BMP loaded properly } // end CreateTexture // END GAME CODE //////////////////////////////////////////