//***********************************************************************// // // // - "Talk to me like I'm a 3 year old!" Programming Lessons - // // // // $Author: Ben Humphrey digiben@gametutorilas.com // // // // $Program: Triangle // // // // $Description: Init OpenGL and Draw a triangle to the screen // // // // $Date: 3/3/01 // // // //***********************************************************************// using Tao.Sdl.Sdl; using Tao.OpenGl.Gl; using Tao.OpenGl.Glu; using Nemerle.IO; module Game { ///////////////////////////////// INIT GAME WINDOW \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function initializes the game window. ///// ///////////////////////////////// INIT GAME WINDOW \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal Init() : void { Init.InitializeOpenGL(Init.SCREEN_WIDTH, Init.SCREEN_HEIGHT); // Initialize openGL // *Hint* We will put all our game init stuff here // Some things include loading models, textures and network initialization } ///////////////////////////////// DRAW TRIANGLE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function draws a colored triangle centered around (x, y, z). ///// The size of this triangle depends on the width and height passed in ///// ///////////////////////////////// DRAW TRIANGLE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* DrawTriangle(x : float, y : float, z : float, width : float, height : float) : void { // Now we can draw a triangle very easily with a certain position. // This will come in handy in our RenderScene() function so as not to clutter it. // We would rather focus on the matrix code that we are going to learn about. // The three points of our triangle are created from the variables passed in. // We will make a triangle that is centered around (x, y, z) and with a width and height // of the passed in width and height, but on each side, and top and bottom of the triangle. // If we wanted to make it a TRUE width and height, we would divide the width and height by 2. // IE. glVertex3f(x + width/2, y - height/2, z); - But it's not necessary to convey the point. // Below we say that we want to draw triangles glBegin (GL_TRIANGLES); // This is our BEGIN to draw glColor3ub(255b, 0b, 0b); // Make the top vertex RED glVertex3f(x, y + height, z); // Here is the top point of the triangle glColor3ub(0b, 255b, 0b); // Make the left vertex GREEN glVertex3f(x + width, y - height, z); // Here is the right point of the triangle glColor3ub(0b, 0b, 255b); // Make the right vertex BLUE glVertex3f(x - width, y - height, z); // Here is the left point of the triangle glEnd(); // This is the END of drawing } ///////////////////////////////// RENDER SCENE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function renders the entire scene. ///// ///////////////////////////////// RENDER SCENE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal RenderScene() : void { glClear(GL_COLOR_BUFFER_BIT %| GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer glLoadIdentity(); // Reset The View // Position View Up Vector gluLookAt(0.0, 0.0, 6.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // This determines where the camera's position and view is // Below we will be introduce to using matrices. Basically what they are, is they // hold the rotations, translations, and scaling for the world. // When you call glPushMatrix(), you are now using a different matrix than the previous // matrix. That means that if you call a glTranslatef() or glRotatef() after you // pushed on a new matrix, it only effect the polygons that are draw in the scope of // that matrix. Then, when you are finished with a certain group of polygons, then you // call glPopMatrix() and that pops that current matrix off the stack and goes back to // the previous matrix being used. Initially, we use the main world matrix. If we push // on another matrix, and then start doing some rotations and translations or scaling, it // doesn't do those operations to the polygons drawn OUTSIDE of that matrix scope. At the // bottom of this file, Look in *Quick Notes* for more explanation on matrices and what they // look like. The nice thing about OpenGL is that we don't have to get tangled up in matrix // operations, it does it for us. On the other hand, I recommend doing it at least once yourself. // Otherwise, you will be ignorant to what is really going on, and you aren't always going to have // OpenGL to do this stuff. If you work on a Console game, they have their own SDK. You have // to do a lot more manipulations with matrices in that case. // Here we draw a triangle with our own draw function. This draws a triangle // centered around the origin at (0, 0, 0) with a width and height of (1, 1). DrawTriangle( .0f, .0f, .0f, 1.0f, 1.0f); // Now we want to draw another triangle, but we want to move it. Let's draw the triangle // at the same position the last triangle was drawn, but lets move it to a new position. // First, let's push on a matrix. We only want to move THIS triangle, so let's use a // separate matrix so it doesn't effect any of the other triangles. glPushMatrix(); // Push on another matrix to use // Now that we have a separate matrix, let's draw another triangle, // but let's move it off to the right. Before we draw the triangle though, // we need to translate it to the new position. This changes the current matrix, // and then when the triangle is drawn, it is effected by the translation. // Basically what that means is that, you can't draw the triangle, and then move it. // You need to set the position it will go, then draw it at that position. // glTranslate() moves the current position of all the 3D points to a new (X, Y, Z) // This translation will move the next triangle back into the screen and off to the right glTranslatef(1.0f, 0.0f, -1.0f); // This moves everything draw afterwards to (1, 0, -1) // Now we will draw the triangle at the origin, but the current matrix will actually // translate the triangle to a different location (back and to the right). DrawTriangle(.0f, .0f, .0f, 1.0f, 1.0f); glPopMatrix(); // Pop off this matrix and go back to the previous one // Let's draw another triangle, but off to the left now. // Since we are doing more translations, let's push on another matrix. // Realistically, we don't have to since this is the last thing being drawn, // so if we change the current default world matrix, it won't affect any of the // other triangles since they are already drawn, but let's do it just for the sake of practice. :) // Here we push on a new matrix to manipulate glPushMatrix(); // Let's translate the last triangle back and to the left glTranslatef(-1.0f, 0.0f, -1.0f); // Draw the triangle at the origin (But will appear back and to the left) DrawTriangle(.0f, .0f, .0f, 1.0f, 1.0f); glPopMatrix(); // Pop this matrix off the stack. // That is the simplest way to use matrices in OpenGL. They come in handy in almost // everything you do. The reason why we use a matrix is because it is faster than // rotating or translating every point manually. There is less operations. it is also // easier to work with. If we want to scale, rotate, AND translate a point, we can do // it in one matrix operation, where we would have to do it in 3 if we did it manually. // This is explained below in *Quick Notes* a bit more. // So, let's see if you understand what is happening here. What would happen // if, in this last triangle drawn, we drew the triangle at (1, 0, 0) instead of (0, 0, 0)? // Try and it and see. Where would it draw it? The answer is: (0, 0, -1). // Why you ask? Because we translated the triangle over (-1, 0, -1). It doesn't // move the triangle to the X Y and Z position, it offsets it by that amount. // So, if we draw the triangle off to the right, but we translated it to the left // by one, and back 1... it just moves it back to the center and moves it back by one. // Try it if you don't believe me :) Hopefully this will give you a quick start to // understanding how to use matrices. SDL_GL_SwapBuffers(); // Swap the backbuffers to the foreground } ///////////////////////////////////////////////////////////////////////////////// // // * QUICK NOTES * // // Matrices are cool huh? They let you do a lot for a little amount of code. // And that's really what we want isn't it? :) // // Here is an example of a 4x4 homogeneous matrix: // // [ 1 0 0 0 ] // [ 0 1 0 0 ] // [ 0 0 1 0 ] // [ 0 0 0 1 ] // // Does this look familiar from math class? Yup, it's the identity matrix. // // The translation for x y and z are stored in these slots: // // [ 1 0 0 x ] // [ 0 1 0 y ] // [ 0 0 1 z ] // [ 0 0 0 1 ] // // The scaling slots are these (scaling of X Y and Z): // // [ x 0 0 0 ] // [ 0 y 0 0 ] // [ 0 0 z 0 ] // [ 0 0 0 1 ] // // If we put them all together we have 1 matrix that does scaling and rotation: // // [ x 0 0 x2 ] // X Y Z are the scale value, where X2 Y2 and Z2 are the translation values // [ 0 y 0 y2 ] // [ 0 0 z z2 ] // [ 0 0 0 1 ] // // This is what it looks like under the scenes. A matrix is just 4 rows and 4 columns. // If we were to create a matrix array we would do this: // // float matrix[16] or float maxtrix[4][4] // // One thing I want to explain is that, just like scopes in C/C++ { } you can do the same // thing with matrices. Here is an example: // // glPushMatrix(); // // glTranslate(0, 1, 0); // DrawTriangle(0, 0, 0, 1, 1); // // glPushMatrix(); // // glTranslate(0, 1, 0); // DrawTriangle(0, 0, 0, 1, 1); // // glPopMatrix(); // // glPopMatrix(); // // The first Triangle would be draw at (0, 1, 0), but where would the second one be drawn? // Here is the tricky part. Once again, remember that glTranslate() does not necessarily mean // it will draw at those coordinates. Since we already moved the current matrix to draw at (0, 1, 0), // the matrix that the second triangle is working with is working FROM that last matrix. // This will then draw the triangle at (0, 2, 0). // // Before we do any translating or rotation, the initial matrix is pure. that means, // if we translate anything to a certain (X, Y, Z), it will actually move it to that (X, Y, Z) // position. But if we then call that same translation function with the same values, it acts // more like a delta value because the matrix is changed from then on out, until we call glLoadIdentiy(). // It will actually move the new position to the new X Y and Z STARTING from that initial value. // Does that make sense? It's like, if you have: // // int x = 0; // // Then, you say: // // x += 2; // // X was pure before we added 2 to it. Now x is changed. so if we say: // // x += 2; // // again, it will not be 2 still, it will now be 4. That is like how matrices work, but not really. // The concept is the same though, once you change them, you are then working with a different matrix. // // Try using glScalef() and glRotatef() to further understand this subject. // // Let us know if this tutorial helped you. // // // Ben Humphrey (DigiBen) // Game Programmer // DigiBen@GameTutorials.com // Co-Web Host of www.GameTutorials.com // // ////////////////////////////// MAIN GAME LOOP \\\\\\\\\\\\\\\\\\\\\\\\\\\\* ////// ////// This function handles the main game loop ////// ////////////////////////////// MAIN GAME LOOP \\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal MainLoop() : void { mutable done = false; // is our job done ? not yet ! mutable even = SDL_Event (); while(!done) // as long as our job's not done { while( SDL_PollEvent (out even) != 0 ) // look for events (like keystrokes, resizing etc.) { def ty = (even.@type :> int); // what kind of event have we got ? when (ty == SDL_QUIT) // if user wishes to quit done = true; // this implies our job is done when (ty == SDL_KEYDOWN) // if the user has pressed a key Init.HandleKeyPressEvent( even.key.keysym ); // callback for handling keystrokes, arg is key pressed when (ty == SDL_VIDEORESIZE) { // if there is a resize event // request SDL to resize the window to the size and depth etc. that we specify Init.MainWindow = SDL_SetVideoMode(even.resize.w, even.resize.h, Init.SCREEN_DEPTH, Init.VideoFlags ); Init.SizeOpenGLScreen(even.resize.w, even.resize.h); // now resize the OpenGL viewport when (Init.MainWindow == System.IntPtr.Zero) // if window resize has failed { printf ("Failed resizing SDL window : %s\n", SDL_GetError()); // report error Init.Quit(0); } } } // while( SDL_ ... RenderScene(); // draw our OpenGL scene } // while( ! done) } } module Init { public SCREEN_WIDTH : int = 800; // We want our screen width 800 pixels public SCREEN_HEIGHT : int = 600; // We want our screen height 600 pixels public SCREEN_DEPTH : int = 24; // We want 16 bits per pixel mutable internal VideoFlags : int; // Video Flags for the Create Window function mutable internal MainWindow : System.IntPtr; // drawing surface on the SDL window /////////////////////////////////// TOGGLE FULL SCREEN \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* /////// /////// This function TOGGLES between FULLSCREEN and WINDOWED mode /////// /////////////////////////////////// TOGGLE FULL SCREEN \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ToggleFullScreen() : void { when (SDL_WM_ToggleFullScreen (MainWindow) == 0) // try to toggle fullscreen mode for window 'MainWindow' { printf ("Failed to Toggle Fullscreen mode : %s\n", SDL_GetError()); // report error in case toggle fails Quit(0); } } /////////////////////////////// CREATE MY WINDOW \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* //////// //////// This function CREATES our WINDOW for drawing the GL stuff //////// /////////////////////////////// CREATE MY WINDOW \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* CreateMyWindow(strWindowName : string, width : int, height : int, VideoFlags : int) : void { // SCREEN_DEPTH is const for bits per pixel MainWindow = SDL_SetVideoMode(width, height, SCREEN_DEPTH, VideoFlags); when ( MainWindow == System.IntPtr.Zero ) // if window creation failed { printf ("Failed to Create Window : %s\n", SDL_GetError()); // report error Quit(0); } SDL_WM_SetCaption(strWindowName, strWindowName); // set the window caption (first argument) and icon caption (2nd arg) } ///////////////////////////// SETUP PIXEL FORMAT \\\\\\\\\\\\\\\\\\\\\\\\\\\\* /////// /////// Sets the pixel format for openGL and video flags for SDL /////// ///////////////////////////// SETUP PIXEL FORMAT \\\\\\\\\\\\\\\\\\\\\\\\\\\\* SetupPixelFormat() : void { //////// SURFACE IS THE DRAWABLE PORTION OF AN SDL WINDOW \\\\\\\\* ///////////// we set the common flags here VideoFlags = SDL_OPENGL; // it's an openGL window VideoFlags |= SDL_HWPALETTE; // exclusive access to hardware colour palette VideoFlags |= SDL_RESIZABLE; // the window must be resizeable def VideoInfo = SDL_GetVideoInfo(); // query SDL for information about our video hardware ///////////// we set the system dependant flags here if(VideoInfo.hw_available != 0) // is it a hardware surface VideoFlags |= SDL_HWSURFACE; else VideoFlags |= SDL_SWSURFACE; // Blitting is fast copying / moving /swapping of contiguous sections of memory // for more about blitting check out : // http://www.csc.liv.ac.uk/~fish/HTML/blitzman/bm_blitter.html when (VideoInfo.blit_hw != 0) // is hardware blitting available VideoFlags |= SDL_HWACCEL; // tell SDL that the GL drawing is going to be double buffered def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1 ); // size of depth buffer def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_DEPTH_SIZE, SCREEN_DEPTH); // we aren't going to use the stencil buffer def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_STENCIL_SIZE, 0); // this and the next three lines set the bits allocated per pixel - def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_ACCUM_RED_SIZE, 0); // - for the accumulation buffer to 0 def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_ACCUM_GREEN_SIZE, 0); def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_ACCUM_BLUE_SIZE, 0); def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_ACCUM_ALPHA_SIZE, 0); () } //////////////////////////// RESIZE OPENGL SCREEN \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function resizes the viewport for OpenGL. ///// //////////////////////////// RESIZE OPENGL SCREEN \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal SizeOpenGLScreen(width : int, height : int) : void // Initialize The GL Window { def height = // Prevent A Divide By Zero error if (height == 0) 1 else height; // Make the Height Equal One glViewport(0, 0, width, height); // Make our viewport the whole window // We could make the view smaller inside // Our window if we wanted too. // The glViewport takes (x, y, width, height) // This basically means, what our drawing boundries glMatrixMode(GL_PROJECTION); // Select The Projection Matrix glLoadIdentity(); // Reset The Projection Matrix // Calculate The Aspect Ratio Of The Window // The parameters are: // (view angle, aspect ration of the width to the height, // The closest distance to the camera before it clips, // FOV // Ratio // The farthest distance before it stops drawing) gluPerspective(45.0, (width :> double) / (height :> double), 1.0, 150.0); // * Note * - The farthest distance should be at least 1 if you don't want some // funny artifacts when dealing with lighting and distance polygons. This is a special // thing that not many people know about. If it's less than 1 it creates little flashes // on far away polygons when lighting is enabled. glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix glLoadIdentity(); // Reset The Modelview Matrix } //////////////////////////////// INITIALIZE GL \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function handles all the initialization for openGL ///// //////////////////////////////// INITIALIZE GL \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal InitializeOpenGL(width : int, height : int) : void { glEnable (GL_DEPTH_TEST); SizeOpenGLScreen (width, height); // resize the OpenGL Viewport to the given height and width } /////////////////// HANDLE KEY PRESS EVENT \\\\\\\\\\\\\\\\\\\\\\\ ////// ////// This function handles the keypress events generated when the user presses a key ////// /////////////////// HANDLE KEY PRESS EVENT \\\\\\\\\\\\\\\\\\\\\\\\ internal HandleKeyPressEvent(keysym : SDL_keysym) : void { def sym = (keysym.sym :> SDLKey); // which key have we got when (sym == SDLKey.SDLK_F1) // if it is F1 ToggleFullScreen(); // toggle between fullscreen and windowed mode when (sym == SDLKey.SDLK_ESCAPE) // if it is ESCAPE Quit(0); // quit after cleaning up } ////////////////////////////// MAIN \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ////// ////// create the window and calling the initialization functions ////// ////////////////////////////// MAIN \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* Main () : void { // print user instructions printf (" Hit the F1 key to Toggle between Fullscreen and windowed mode\n"); printf (" Hit ESC to quit\n"); if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) // try to initialize SDL video module // report error if it fails printf ("Failed initializing SDL Video : %s\n", SDL_GetError()); else { // Set up the format for the pixels of the OpenGL drawing surface SetupPixelFormat(); // Create our window, we pass caption for the window, // the width, height and video flags required CreateMyWindow("www.GameTutorials.com - First OpenGL Program", SCREEN_WIDTH, SCREEN_HEIGHT, VideoFlags); // Initializes our OpenGL drawing surface Game.Init(); // Run our message loop Game.MainLoop(); } } ////////////////////////////// QUIT \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ////// ////// This will shutdown SDL and quit the program ////// ////////////////////////////// QUIT \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal Quit(ret_val : int) : void { SDL_Quit(); // shuts down SDL stuff System.Environment.Exit(ret_val); // quit the program } }