//////////////////////////////////////////////////////////// // graphics.cpp // // Basic graphics objects and functions for EDrive, a multiplatform driving // simulator. // // The two main classes here are // graphics - a target on which things are drawn // thing - a 3D shape which is drawn onto a graphics instance // but there is also a smattering of useful graphics functions and in // the DirectX version there is also a class // graphicslist - lists available graphics devices/video modes // // Copyright 2004 by Evan Alexander Weaver // // Despite the copyright, this is free software. See edrive.cpp for details. // // Date last modified: 31 May 2004 #include #include "platform.h" #include "error.h" #include "edmath.h" #include "graphics.h" #if ED_DIRECTX_9_GRAPHICS //************************************************ // Global variables shared by graphics and graphicslist. Yuck. (I // dislike resorting to global variables). // static LPDIRECT3D9 D3d = NULL; // the required Direct3D9 object static int D3d_count = 0; // number of instances using D3d // "safe" release of COM object #define RELEASE(x) if (x) { x->Release(); x = NULL; } // some Windows header files have a DELETE we don't want, since // we'll have our own DELETE #ifdef DELETE #undef DELETE #endif #endif //******************************************************************* // "safe" deletion of dynamically allocated memory #define DELETE(x) if (x) { delete [] x; x = 0; } //////////////////////////////////////////////////////////////////////////// // General graphics functions // // Rotate a vector, "v", "rad" radians around the axis "axis" // vector &rotateaxis(vector &v, float rad, const vector &axis) { #if ED_DIRECTX_9_GRAPHICS //******************************************** matrix mat; vector newvec; D3DXVec3TransformCoord(&newvec, &v, D3DXMatrixRotationAxis(&mat, &axis, rad)); return v = newvec; #else //**************************************************************** return v = matrotaxis(rad, axis) * v; #endif //*************************************************************** } // Determine if the point that projects to (x, z) on the XZ plane is // inside the triangle defined by the projection of p1, p2, p3 onto the // XZ plane. // bool inxztriangle(float x, float z, vector &p1, vector &p2, vector &p3) { bool rc = false; // first do a quick range check to see if it is worth checking further. // if ((x >= p1.x || x >= p2.x || x >= p3.x) && (x <= p1.x || x <= p2.x || x <= p3.x) && (z >= p1.z || z >= p2.z || z >= p3.z) && (z <= p1.z || z <= p2.z || z <= p3.z)) { // Compute the determinants of the triangle 123 and the two // triangles p23 and 1p3 (where p is (x,z)). The determinant is // twice the area of the triangle in absolute value, and its sign // determines the orientation (clockwise or not) of the triangle. // If p is in 123, then these three value will all be the same sign, // and the sum of the latter two will not exceed the first in // absolute value. // float a123 = (p1.x-p3.x) * (p2.z-p3.z) - (p1.z-p3.z) * (p2.x-p3.x), ap23 = (x-p3.x) * (p2.z-p3.z) - (z-p3.z) * (p2.x-p3.x), a1p3 = (p1.x-p3.x) * (z-p3.z) - (p1.z-p3.z) * (x-p3.x); rc = (a123 > 0 && ap23 >= 0 && a1p3 >= 0 && ap23+a1p3 <= a123) || (a123 < 0 && ap23 <= 0 && a1p3 <= 0 && ap23+a1p3 >= a123); } return rc; } #if ED_OPENGL //************************************************************ // Allocates, fills and returns a block of memory with pixel data // from a 24-bit BMP file. Returns 0 if it can't. The pixel width // and height are returned through "width" and "height". // // Important: Use delete [] on the returned address when done! // unsigned char *createtexdata(const char *file, int &width, int &height) { unsigned char *rc = 0, c[2] = { 0, 0 }; FILE *fp; unsigned int w = 0, h = 0, offs = 0, comp = 0, size = 0; unsigned short bpp = 0; if (file) if (fp = fopen(file, "rb")) { // Examine the BMP file header so we can be sure it // is the kind of data we are expecting. fread(c, 2, 1, fp); // should be 'B' 'M' fseek(fp, 8, SEEK_CUR); fread(&offs, 4, 1, fp); // offset of bitmap data fseek(fp, 4, SEEK_CUR); fread(&w, 4, 1, fp); // pixel width of image fread(&h, 4, 1, fp); // pixel height of image fseek(fp, 2, SEEK_CUR); fread(&bpp, 2, 1, fp); // bits per pixel (should be 24) fread(&comp, 4, 1, fp); // compression scheme (should be 0) // Fortunately, the order that pixel data is stored in a BMP // is compatible with OpenGL, so we can just read it in one // big block. // if (c[0] == 'B' && c[1] == 'M' && offs && w && h && bpp == 24 && comp == 0 && (rc = new unsigned char[size = w * h * 3])) { fseek(fp, offs, SEEK_SET); fread(rc, size, 1, fp); width = w; height = h; } } if (!rc) errorf("Error reading texture file %s", file); return rc; } #endif //******************************************************************* #if ED_DIRECTX_9_GRAPHICS //************************************************ //////////////////////////////////////////////////////////////////////// // graphicslist class // // Tells us the graphics modes available (only used in DirectX 9 version) // // List of possible formats that are acceptable to us. Actually, these // are all formats listed in the DirectX 9 documentation; in fact it // might be better to only use a subset of this, but I really don't // know what some strange video drivers might have, so I just put them // all in. // D3DFORMAT graphicslist::formatid[6] = { D3DFMT_R5G6B5, D3DFMT_X1R5G5B5, D3DFMT_A1R5G5B5, D3DFMT_X8R8G8B8, D3DFMT_A8R8G8B8, D3DFMT_A2R10G10B10 }; // Descriptions of the above formats (for display to user). // char *graphicslist::formatdesc[7] = { "16 bit", "15 bit (XRGB)", "16 bit (ARGB)", "32 bit", "32 bit (ARGB)", "32 bit (2 channel alpha)", "unknown format" }; graphicslist::graphicslist() { displaycnt = modecnt = 0; display = -1; modes = widths = heights = hzs = NULL; formats = NULL; // instantiate the Direct3D9 object if it hasn't already been // if (D3d || (D3d = Direct3DCreate9(D3D_SDK_VERSION))) displaycnt = D3d->GetAdapterCount(); else error("Can't create Direct3D9 object"); // count use of D3d (so it can be Released when the last object // that uses it disappears). // D3d_count++; } graphicslist::~graphicslist() { destroy(); // Release D3d if this was the last object using it // if (0 == --D3d_count) RELEASE(D3d) } // Empty the list of available modes // void graphicslist::destroy() { DELETE(modes) DELETE(widths) DELETE(heights) DELETE(formats) DELETE(hzs) display = -1; modecnt = 0; } // Make the list contain the available modes for display device "dispid". // bool graphicslist::select(int dispid) { bool rc = false; int i, j, n; D3DDISPLAYMODE dm; if (0 <= dispid && dispid < displaycnt) { destroy(); // start over // find out how many different video modes are available n = 0; for (i = 0; i < 6; i++) n += D3d->GetAdapterModeCount(dispid, formatid[i]); // create and fill arrays with video mode information if (n > 0 && (modes = new int[n]) && (widths = new int[n]) && (heights = new int[n]) && (formats = new D3DFORMAT[n]) && (hzs = new int[n])) { for (i = 0; i < 6; i++) { n = D3d->GetAdapterModeCount(dispid, formatid[i]); for (j = 0; j < n; j++) { // only use mode if Z buffer is available // note that the arrays we made may be a bit // too big, since not all modes will support a // a Z buffer. // if (SUCCEEDED(D3d->EnumAdapterModes(dispid, formatid[i], j, &dm)) && dm.Width >= 640 && dm.Height >= 480 && (D3D_OK == D3d->CheckDeviceFormat(dispid, D3DDEVTYPE_HAL, dm.Format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D16) || D3D_OK == D3d->CheckDeviceFormat(dispid, D3DDEVTYPE_HAL, dm.Format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D32) || D3D_OK == D3d->CheckDeviceFormat(dispid, D3DDEVTYPE_REF, dm.Format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D16) || D3D_OK == D3d->CheckDeviceFormat(dispid, D3DDEVTYPE_REF, dm.Format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D32))) { modes[modecnt] = j; widths[modecnt] = dm.Width; heights[modecnt] = dm.Height; formats[modecnt] = dm.Format; hzs[modecnt] = dm.RefreshRate; modecnt++; } } } display = dispid; rc = true; } else destroy(); } return rc; } // Return a description of the specified video mode (e.g. for displaying // to the user) // const char *graphicslist::modedesc(int i) { char hz[20] = ""; static char rc[60]; rc[0] = '\0'; if (0 <= i && i < modecnt) { if (hzs[i]) wsprintf(hz, "%d Hz ", hzs[i]); int j = 0; while (j < 6 && formats[i] != formatid[j]) j++; wsprintf(rc, "%dx%d %s%s", widths[i], heights[i], hz, formatdesc[j]); } return rc; } // Return the number of available display devices // int graphicslist::displaycount() { return displaycnt; } // Return the number of available video modes for the current device // int graphicslist::modecount() { return modecnt; } // Return the name of the specified display device (e.g. to display // to the user). Note that this returns a pointer to a static array, // which gets reused with each call, so you should use the returned // string immediately (e.g. copy it somewhere more permanent) // const char *graphicslist::displayname(int i) { static char rc[81]; strcpy(rc, "error getting display name"); if (0 <= i && i < displaycnt) { D3DADAPTER_IDENTIFIER9 d3di; if (SUCCEEDED(D3d->GetAdapterIdentifier(i, 0, &d3di))) { rc[80] = '\0'; strncpy(rc, d3di.Description, 80); } } return rc; } // Return the video mode details for the specified video mode on the // current device. // bool graphicslist::getdetails(int i, int &mode, int &width, int &height, D3DFORMAT &format, int &hz) { bool rc = false; if (0 <= i && i < modecnt) { mode = modes[i]; width = widths[i]; height = heights[i]; format = formats[i]; hz = hzs[i]; rc = true; } return rc; } #endif //******************************************************************* /////////////////////////////////////////////////////////////////////////// // graphics class // // represents something on which we draw // #if ED_DIRECTX_9_GRAPHICS //************************************************ // For DirectX 9, we need a structure to hold all the information for // a vertex // ULTVERTEX structure: an unlit vertex with a texture coordinates. // struct ULTVERTEX { FLOAT x, y, z; // coordinates for the point FLOAT nx, ny, nz; // normal (perpendicular) vector for lighting FLOAT tu, tv; // texture coordinates }; #define D3DFVF_ULTVERTEX (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 |\ D3DFVF_TEXCOORDSIZE2(0)) #endif //******************************************************************* // Default constructor makes an graphics object unattached to any device // graphics::graphics() { #if ED_DIRECTX_9_GRAPHICS //******************************************** d3dd = NULL; ZeroMemory(&d3dpp, sizeof(d3dpp)); D3DXMatrixIdentity(&view); font = NULL; hwnd = NULL; hinst = NULL; D3d_count++; #else //**************************************************************** id = -1; #endif //*************************************************************** full = false; width = 640; height = 480; drawframe = 0; } // Destructor // graphics::~graphics() { destroy(); #if ED_DIRECTX_9_GRAPHICS //******************************************** // Get rid of Direct3D object if this was the last object using it if (0 == --D3d_count) RELEASE(D3d); #endif //*************************************************************** } // Disassociate the graphics object from any device // void graphics::destroy() { #if ED_DIRECTX_9_GRAPHICS //******************************************** RELEASE(font) RELEASE(d3dd) #else //**************************************************************** if (full) { glutLeaveGameMode(); full = false; } if (id != -1) { glutDestroyWindow(id); id = -1; } #endif //*************************************************************** } #if ED_OPENGL_GLUT //******************************************************* // Declare GLUT callbacks that are defined in edrive.cpp (because they // use the Globals global variable defined there). Yuck. // extern void Screensizechange(int width, int height); extern void Visibilitychange(int); #endif //******************************************************************* // create function associates the graphics object with a display device of // the specified resolution // #if ED_DIRECTX_9_GRAPHICS //************************************************ bool graphics::create(HINSTANCE hinstance, WNDPROC mainwindowproc, int display, int w, int h, D3DFORMAT fmt, int hz, void (*df)()) #else //******************************************************************** bool graphics::create(int *pargc, char **argv, int w, int h, void (*df)()) #endif //******************************************************************* { bool rc = false; width = w; height = h; drawframe = df; #if ED_DIRECTX_9_GRAPHICS //******************************************** WNDCLASS wc; char classname[] = "edrive"; hinst = hinstance; full = (display != -1); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = mainwindowproc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hinst; wc.hIcon = LoadIcon(hinst, IDI_APPLICATION); wc.hCursor = NULL; wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = classname; RegisterClass(&wc); // If display is -1, then the user has asked to draw in a window // rather than changing the video mode and taking over the screen. if (NULL == D3d && NULL == (D3d = Direct3DCreate9(D3D_SDK_VERSION))) error("Unable to make Direct3D9 object"); else if (!(hwnd = CreateWindowEx(0, classname, "EDrive", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hinst, NULL))) error("Unable to make main window"); else { ShowWindow(hwnd, SW_SHOWNORMAL /* cmdshow */); UpdateWindow(hwnd); if (change(display, w, h, fmt, hz)) { rc = true; viewpoint(vector(0, 0, 0), vector(1, 0, 1), vector(0, 1, 0)); } } #else //**************************************************************** full = false; glutInit(pargc, argv); glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); glutInitWindowSize(w, h); id = glutCreateWindow("EDrive"); init(); rc = true; #endif //*************************************************************** return rc; } #if ED_DIRECTX_9_GRAPHICS //************************************************ // Change the display device to match the specified criteria. If "display" // is -1, then windowed mode is used, and "fmt" and "hz" are ignored // // Note that when switching back and forth between windowed mode and // various resolutions, this ocassionally crashes, but I haven't been // able to figure out why yet. // bool graphics::change(int display, int w, int h, D3DFORMAT fmt, int hz) { bool rc = false; static bool firsttime = true; if (hwnd) { full = (display != -1); width = w; height = h; ZeroMemory(&d3dpp, sizeof d3dpp); d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.BackBufferCount = 1; if (full) { d3dpp.BackBufferWidth = width; d3dpp.BackBufferHeight = height; d3dpp.BackBufferFormat = fmt; d3dpp.FullScreen_RefreshRateInHz = hz; } else d3dpp.Windowed = TRUE; // If all else fails, use the REF (software emulation) device // rather than the HAL (hardware accelerated) device, and use a // 16-bit depth buffer, hoping that it will work. (If it doesn't, // we are out of luck anyway.) // D3DDEVTYPE devtype = D3DDEVTYPE_REF; // REF or HAL d3dpp.AutoDepthStencilFormat = D3DFMT_D16; // Z-buffer depth UINT disp = (full ? display : D3DADAPTER_DEFAULT); if (firsttime) { firsttime = false; D3DDISPLAYMODE dm; if (FAILED(D3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &dm))) { error("couldn't find display format"); windowformat = D3DFMT_R5G6B5; } else windowformat = dm.Format; } if (display == -1) fmt = windowformat; // Now, let's see if we can find better choices: // if (D3D_OK == D3d->CheckDeviceFormat(disp, D3DDEVTYPE_HAL, fmt, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D32)) { d3dpp.AutoDepthStencilFormat = D3DFMT_D32; devtype = D3DDEVTYPE_HAL; } else if (D3D_OK == D3d->CheckDeviceFormat(disp, D3DDEVTYPE_HAL, fmt, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D16)) { d3dpp.AutoDepthStencilFormat = D3DFMT_D16; devtype = D3DDEVTYPE_HAL; } else if (D3D_OK == D3d->CheckDeviceFormat(disp, D3DDEVTYPE_REF, fmt, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D32)) { d3dpp.AutoDepthStencilFormat = D3DFMT_D32; devtype = D3DDEVTYPE_REF; } destroy(); // Resize the window if going to windowed more // if (display == -1) SetWindowPos(hwnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE); // Ready to make the D3D device. If we can't make use of hardware // vertex transformations, then fall back to slower software mode. // if (FAILED(D3d->CreateDevice(disp, devtype, hwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &d3dd)) && FAILED(D3d->CreateDevice(disp, devtype, hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3dd))) error("Unable to make D3D device"); else { // Set up rendering parameters, etc. // init(); rc = true; } } return rc; } #else //******************************************************************** // Handle change in window size, to be called by the GLUT handler // for size changes. Note that this assume square pixels. If pixels // are other than square, you'll need to change the second parameter // to gluPerspective. // void graphics::newsize(int w, int h) { glViewport(0, 0, width = w, height = h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(50, width/(double)height, .3, 2000); glMatrixMode(GL_MODELVIEW); } #endif //******************************************************************* // Initialize various rendering parameters which may need to be set up over // and over again, // void graphics::init() { #if ED_DIRECTX_9_GRAPHICS //******************************************** // Make a directional light source ("the sun") // D3DLIGHT9 light; ZeroMemory(&light, sizeof light); light.Type = D3DLIGHT_DIRECTIONAL; light.Diffuse = D3DXCOLOR(0.7, 0.7, 0.7, 1); light.Ambient = D3DXCOLOR(0.3, 0.3, 0.3, 1); light.Specular = D3DXCOLOR(1, 1, 1, 1); light.Direction = D3DXVECTOR3(-2, -5, -1); if (FAILED(d3dd->SetLight(0, &light)) || FAILED(d3dd->LightEnable(0, TRUE))) error("Unable to create main light"); // allow colour dithering (much smoother looking when using lights) d3dd->SetRenderState(D3DRS_DITHERENABLE, TRUE); // how alpha-blending should be done, when we draw transparent things d3dd->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); d3dd->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); // put a little bit of light on everything, for when there is no light d3dd->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50)); // describe the vertex format we'll be using d3dd->SetFVF(D3DFVF_ULTVERTEX); // set up the projection and view matrices // D3DXMATRIX projection; D3DXMatrixPerspectiveFovLH(&projection,.9, width/(float)height, 0.3, 2000); d3dd->SetTransform(D3DTS_PROJECTION, &projection); d3dd->SetTransform(D3DTS_VIEW, &view); // make the font for text display. Note that all text gets displayed // in the same font. // if (font) { font->Release(); font = NULL; } D3DXCreateFont(d3dd, int(0.05 * height), 0, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE | DEFAULT_PITCH, "ARIAL", &font); #else //**************************************************************** // Install glut callbacks glutReshapeFunc(Screensizechange); // handles size change glutDisplayFunc(drawframe); // handles screen redraw glutVisibilityFunc(Visibilitychange); // handles visibility change // Clear the depth buffer. This shouldn't be necessary here but // when switching to/from fullscreen the first frame redraw doesn't // alway come out right if this isn't done here (i.e. this is a // kludge to get around a GLUT bug) // glClear(GL_DEPTH_BUFFER_BIT); // Set up lighting float white[] = {.7, .7, .7, 1}, dark[] = {.3, .3, .3, 1}, black[] = {0, 0, 0, 1}; glEnable(GL_LIGHTING); glMaterialfv(GL_FRONT, GL_EMISSION, black); glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); lpos[0] = 2; lpos[1] = 5; lpos[2] = -1; lpos[3] = 0; //directional light glLightfv(GL_LIGHT0, GL_POSITION, lpos); // probably premature glLightfv(GL_LIGHT0, GL_AMBIENT, dark); glLightfv(GL_LIGHT0, GL_DIFFUSE, white); glEnable(GL_LIGHT0); // Set up the projection and view matrices // IMPORTANT NOTE: throughout the edrive code, the MatrixMode must // be left set to MODELVIEW (the most frequently changing matrix), // so that we don't have to continually change it to MODELVIEW // "just in case". // glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(50, width/(double)height, .3, 2000); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // make clockwise the front face to match DirectX glEnable(GL_CULL_FACE); glFrontFace(GL_CW); #endif //*************************************************************** } // Make the graphics object usable again after we've lost control // of the device to which it is attached. // void graphics::restore() { #if ED_DIRECTX_9_GRAPHICS //******************************************** RELEASE(font) if (d3dd) { if (FAILED(d3dd->Reset(&d3dpp))) error("Unable to reset D3D device"); else init(); } #else //**************************************************************** init(); #endif //*************************************************************** } // Change the viewpoint to be looking from "vpt" in the direction "dir", // where "up" points up. // void graphics::viewpoint(const vector &vpt, const vector &dir, const vector &up) { vector at = vpt + dir; #if ED_DIRECTX_9_GRAPHICS //******************************************** D3DXMatrixLookAtLH(&view, &vpt, &at, &up); d3dd->SetTransform(D3DTS_VIEW, &view); #else //**************************************************************** // assume MatrixMode is already MODELVIEW glLoadIdentity(); gluLookAt(vpt.x, vpt.y, vpt.z, at.x, at.y, at.z, up.x, up.y, up.z); #endif //*************************************************************** } // Draw the string "str" starting at location (x, y), where x and y are // expressed as percentages of how far to the right (x) or down (y) // you want the text to start. Note that all text is displayed in the // same font. // void graphics::drawtext(int x, int y, const char *str, color c) { #if ED_DIRECTX_9_GRAPHICS //******************************************** int ytop = (y * height)/100; RECT rect = { (x * width)/100, ytop, width - 1, ytop + height/20 }; if (font) font->DrawText(NULL, str, -1, &rect, DT_LEFT, D3DCOLOR_XRGB(int(c.r * 255), int(c.g * 255), int(c.b * 255))); #else //**************************************************************** // Temporarily switch from perspective projection to orthographic // x range is 5000, y range is 2000, centred around (0, 0) // glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(-2500, 2500, -1000, 1000, -1, 1); // Disable Z buffer check, lighting and texturing // glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); // vary stroke width based on window size // glLineWidth(1 + height/300); glColor3fv((GLfloat*)&c); // Position for first letter, then output letters. Note that // glutStrokeCharacter changes the view matrix glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glTranslatef(x * 50 - 2500, 900 - y * 19, 0); for (int i = 0; str[i]; i++) glutStrokeCharacter(GLUT_STROKE_ROMAN, str[i]); // If you want to use Bitmap fonts instead of stroke fonts, // do this instead of the section above: // glRasterPos2f(.018*x - 0.9, 0.9 - .018*y); // for (int i = 0; str[i]; i++) // glutBitmapCharacter(GLUT_BITMAP_9_BY_15, str[i]); // Renable settings that were disabled (except texturing, which // will be reenabled later if it needs to be) // glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); // put projection and view matrices back the way they were // glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); #endif //*************************************************************** } // Start a new frame, clearing the background to a specified colour // void graphics::startframe(color c) { #if ED_DIRECTX_9_GRAPHICS //******************************************** d3dd->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(int(c.r*255), int(c.g*255), int(c.b*255)), 1.0, 0); d3dd->BeginScene(); #else //**************************************************************** glEnable(GL_DEPTH_TEST); glClearColor(c.r, c.g, c.b, c.a); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLightfv(GL_LIGHT0, GL_POSITION, lpos); #endif //*************************************************************** } // Make frame just drawn appear on the screen // void graphics::endframe() { #if ED_DIRECTX_9_GRAPHICS //******************************************** d3dd->EndScene(); if (FAILED(d3dd->Present(NULL, NULL, NULL, NULL))) error("Failed to flip backbuffer"); #else //**************************************************************** glutSwapBuffers(); #endif //*************************************************************** } // Initiate the drawing of a frame ("immediate" means draw it right now // rather than waiting for the program's main loop to get around to it). // void graphics::nextframe(bool immediate) { if (immediate) drawframe(); #if ED_OPENGL_GLUT //*************************************************** // note that only in GLUT does the nature of the main loop require // the next frame to be else glutPostRedisplay(); #endif //*************************************************************** } // Change the frame drawing function to "df" // void graphics::chdrawframe(void (*df)()) { drawframe = df; #if ED_OPENGL_GLUT //*************************************************** glutDisplayFunc(df); #endif //*************************************************************** } // Check to see if "df" is the current frame drawing function // bool graphics::isdrawframe(void (*df)()) { return drawframe == df; } #if ED_DIRECTX_9_GRAPHICS //************************************************ // Return the program's HINSTANCE HINSTANCE graphics::gethinstance() { return hinst; } // Return the HWND for the graphics' window HWND graphics::gethwnd() { return hwnd; } #else //******************************************************************** // Change to full screen mode. Note that the window is hidden, rather // than destroyed, since I couldn't find a way to destroy it without // the program crashing. // bool graphics::gofullscreen() { if (!full) { // save width and height for when we return to windowed mode owidth = width; oheight = height; if (id != -1) { glutHideWindow(); } glutEnterGameMode(); if (glutGameModeGet(GLUT_GAME_MODE_ACTIVE)) { full = true; // We have a new OpenGL context, so reinitialize it init(); } else // something went wrong, go back to windowed mode gowindow(); } return full; } // Change to windowed mode (presumably back from fullscreen mode) // // Note that swapping back and forth between fullscreen and windowed // mode crashes with some X servers, for reasons I don't fully understand // yet. // bool graphics::gowindow() { // if we were fullscreen, we want to restore the old window size if (full) { glutLeaveGameMode(); full = false; width = owidth; height = oheight; } // if there is no main window, make one, otherwise make it visible // if (id == -1) { glutInitWindowSize(width, height); id = glutCreateWindow("EDrive"); } else { glutSetWindow(id); glutShowWindow(); } // We have a new OpenGL context, so reinitialize it // init(); return id != -1; } #endif //******************************************************************* //////////////////////////////////////////////////////////////////////////// // thing class // // A 3D thing which can be drawn. // // A thing has one set of vertices and one texture/color, but up to 10 // pieces built using those vertices by way of an optional index buffer. // It is possible to avoid the use of the index buffer, but then the thing // is limited to being just one piece big. // // default constructor makes it as empty and nonfunctional as possible // thing::thing() { vb = 0; ib = 0; tex = 0; maxcount = count = maxicount = icount = 0; pieces = 1; col = mkcolor(1, 1, 1); #if ED_DIRECTX_9_GRAPHICS //******************************************** D3DXMatrixIdentity(&world); #else //**************************************************************** world = matrix(1); tc = 0; nv = 0; #endif //*************************************************************** } // destructor gets rid of all external resources // thing::~thing() { destroy(); } // Free up memory/COM objects/OpenGL resources used by the thing // void thing::destroy() { #if ED_DIRECTX_9_GRAPHICS //******************************************** // free up COM objects RELEASE(ib) RELEASE(vb) RELEASE(tex) #else //**************************************************************** // delete allocated memory DELETE(vb) DELETE(nv) DELETE(tc) DELETE(ib) // delete texture which is stored in the OpenGL context if (tex) { glDeleteTextures(1, &tex); tex = 0; } #endif //*************************************************************** maxcount = count = maxicount = icount = 0; pieces = 1; } // Initialize the thing by associating it with a graphics instance (g), // giving it a colour (c, which is only used if the texture can't be // used), an initial shape type (t, describing the first piece), a // texture file name (texfile), the number of elements for the vertex // buffer (maxvertices) and the number of elements for the index buffer // (maxindices, which may be 0, meaning that the thing will have one // piece and won't use an index buffer). // bool thing::create(graphics &g, color c, shapetype t, const char *texfile, int maxvertices, int maxindices) { destroy(); // just in case we call create twice! col = c; type[0] = t; startindex[0] = 0; primitivecnt[0] = 0; maxcount = maxvertices; maxicount = maxindices; #if ED_DIRECTX_9_GRAPHICS //******************************************** // make a shiny thing of the specified color ZeroMemory(&mat, sizeof(mat)); mat.Ambient = D3DXCOLOR(0.3, 0.3, 0.3, c.a); mat.Diffuse = D3DXCOLOR(0.7, 0.7, 0.7, c.a); // reflected from lights mat.Specular = D3DXCOLOR(1, 1, 1, c.a); // shine from lights mat.Power = 100; // 0 if it shouldn't be shiny if (FAILED(g.d3dd->CreateVertexBuffer(maxcount * sizeof(ULTVERTEX), 0, D3DFVF_ULTVERTEX, D3DPOOL_MANAGED, &vb, NULL))) error("Couldn't make vertex buffer"); else if (maxicount > 0 && FAILED(g.d3dd->CreateIndexBuffer( maxicount * 2, 0, D3DFMT_INDEX16, D3DPOOL_MANAGED, &ib, NULL))) { error("Couldn't make index buffer"); vb->Release(); vb = NULL; } if (texfile && FAILED(D3DXCreateTextureFromFile(g.d3dd, texfile, &tex))) { error("Couldn't load texture"); tex = NULL; } // note that failure to load the texture is not fatal, we'll // use the specified colour and draw a more boring looking // thing instead. // if (tex == NULL) { mat.Ambient = D3DXCOLOR(c.r*0.7, c.g*0.7, c.b*0.7, c.a); mat.Diffuse = c; // reflected from lights } #else //**************************************************************** unsigned char *tdata; // temporarily store texture here int tw, th; if (!(vb = new float [maxcount][3]) || !(nv = new float [maxcount][3]) || !(ib = new short [maxicount]) || !(tc = new float [maxcount][2])) destroy(); else if (tdata = createtexdata(texfile, tw, th)) { glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, tw, th, GL_BGR, GL_UNSIGNED_BYTE, tdata); delete [] tdata; // createtexdata had allocated the space } #endif //*************************************************************** return vb != 0; } // Add a vertex ((x, y, z) plus a normal vector and texture coordinates) to // our vertex buffer. "newprimitive" tells us if this vertex completes a // primitive (e.g. triangle, line segment) or not, and is not relevant if // we will be using an index buffer. // void thing::add(float x, float y, float z, float nx, float ny, float nz, float tu, float tv, bool newprimitive) { bool ok = false; if (count < maxcount && vb) { #if ED_DIRECTX_9_GRAPHICS //**************************************** ULTVERTEX *pv; if (SUCCEEDED(vb->Lock(count*sizeof(ULTVERTEX), sizeof(ULTVERTEX), (void**)&pv, 0))) { pv->x = x; pv->y = y; pv->z = z; pv->nx = nx; pv->ny = ny; pv->nz = nz; pv->tu = tu; pv->tv = tv; vb->Unlock(); ok = true; } #else //************************************************************ vb[count][0] = x; vb[count][1] = y; vb[count][2] = z; nv[count][0] = nx; nv[count][1] = ny; nv[count][2] = nz; tc[count][0] = tu; tc[count][1] = tv; ok = true; #endif //*********************************************************** } if (ok) { count++; // only count the number of primitives if there is no index buffer if (newprimitive && maxicount == 0) primitivecnt[0]++; } else error("Can't add vertex to vertex buffer"); } // Add an index to the index buffer. "newprimitive" tells us if this index // completes a primitive (e.g. triangle, line segment) or not. Note that // this index gets added to the current "piece" of the thing. // void thing::addind(short i, bool newprimitive) { short *p; bool ok = false; if (icount < maxicount && ib) { #if ED_DIRECTX_9_GRAPHICS //**************************************** if (SUCCEEDED(ib->Lock(icount * 2, 2, (void**)&p, 0))) { *p = i; ib->Unlock(); icount++; ok = true; } #else //************************************************************ ib[icount++] = i; ok = true; #endif //*********************************************************** } if (ok) { if (newprimitive) primitivecnt[pieces - 1]++; } else error("Can't add index to index buffer"); } // Start a new piece of the thing, of shape "s". // void thing::newpiece(shapetype s) { if (pieces < 10) { primitivecnt[pieces] = 0; startindex[pieces] = icount; type[pieces] = s; pieces++; } } // Draw the thing, assuming BeginScene() has already been called. // void thing::draw(graphics &g) { #if ED_DIRECTX_9_GRAPHICS //******************************************** g.d3dd->SetStreamSource(0, vb, 0, sizeof(ULTVERTEX)); g.d3dd->SetTransform(D3DTS_WORLD, &world); g.d3dd->SetMaterial(&mat); // use the texture (if available) if (tex) g.d3dd->SetTexture(0, tex); if (icount == 0) // no index buffer, just draw it g.d3dd->DrawPrimitive(type[0], 0, primitivecnt[0]); else { // using an index buffer, draw each piece g.d3dd->SetIndices(ib); for (int i = 0; i < pieces; i++) g.d3dd->DrawIndexedPrimitive(type[i], 0, 0, maxcount, startindex[i], primitivecnt[i]); } // stop using the texture if (tex) g.d3dd->SetTexture(0, NULL); #else //**************************************************************** // Note: assumes MatrixMode is MODELVIEW and the current matrix is // the point of view. // glPushMatrix(); // save point of view glMultMatrixf((GLfloat*)&world); // move thing into position if (tex) { // Use the texture if available glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, tex); glColor3f(.7, .7, .7); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, 0, tc); } else { // otherwise draw in the thing's colour glDisable(GL_TEXTURE_2D); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glColor3fv((GLfloat*)&col); } glEnableClientState(GL_NORMAL_ARRAY); glNormalPointer(GL_FLOAT, 0, nv); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, 0, vb); if (icount == 0) // no index buffer, just draw it glDrawArrays(type[0], 0, count); else { // using an index buffer, draw each piece int i; for (i = 0; i < pieces - 1; i++) { glDrawElements(type[i], startindex[i + 1] - startindex[i], GL_UNSIGNED_SHORT, ib + startindex[i]); } glDrawElements(type[i], icount - startindex[i], GL_UNSIGNED_SHORT, ib + startindex[i]); } glPopMatrix(); // restore point of view #endif //*************************************************************** } // Rotate the thing around the major axes // void thing::rotatex(float rad) { #if ED_DIRECTX_9_GRAPHICS //******************************************** world *= matrotationx(rad); #else //**************************************************************** world = matrotationx(rad) * world; #endif //*************************************************************** } void thing::rotatey(float rad) { #if ED_DIRECTX_9_GRAPHICS //******************************************** world *= matrotationy(rad); #else //**************************************************************** world = matrotationy(rad) * world; #endif //*************************************************************** } void thing::rotatez(float rad) { #if ED_DIRECTX_9_GRAPHICS //******************************************** world *= matrotationz(rad); #else //**************************************************************** world = matrotationz(rad) * world; #endif //*************************************************************** } // Move the thing by the vector (x, y, z) // void thing::move(float x, float y, float z) { #if ED_DIRECTX_9_GRAPHICS //******************************************** world *= mattranslate(x, y, z); #else //**************************************************************** world = mattranslate(x, y, z) * world; #endif //*************************************************************** } // Reposition (or otherwise change) the thing by replacing its world matrix. // void thing::reposition(const matrix &m) { world = m; }