import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import org.lwjgl.Sys; import org.lwjgl.opengl.*; import org.lwjgl.util.glu.*; import org.lwjgl.input.Keyboard; /** * GLART_9_timing.java * * Simulates a clock with second hand moving in real-time. Demonstrates * use of a timing loop to control program execution speed. * * The frequency at which screen images are drawn is called the * framerate and is usually measure in frames per second. The goal * in this demo is to animate a clock at the same speed (real time) * no matter how fast or slow the system is rendering. * * Since cpu performance varies, program execution speed can vary from * one computer to another. If you move an animation a fixed amount * in each render(), then the animation will move at different rates on * different computers, as render() is called more often on the faster * cpu. * * VSync also affects the framerate. Display.setVSyncEnabled(true) * will synchronize OpenGL frame updates with the monitor refresh rate. * Most monitors refresh the screen 60 or 75 per second, and VSync will * force Display.update() to wait till the monitor is ready to draw. * This controls the refresh rate somewhat, but monitors can have different refresh * rates, and slower graphics cards will slow this down the refresh rate. * * To insure that animations run at the same * rate across various computers, you need to move animations according * to actual time elapsed, not frame renders. * * Check how much time has elapsed since the last frame rendered, then * "step" the simulation forward by that amount of time. The simulation * advances in proportion to the time elapsed, and keeps track of * how much time has elapsed within the simulatulation. * * Java's time function, System.currentTimeMillis(), does not measure * time accurately below 10 milliseconds, and will not give accurate * results when measuring time elapsed between frames. * * LWJGL includes a hardware timer that measures cpu ticks (Sys.getTime()). * May vary in resolution from one computer to another. Use double data * type for highest precision. */ public class GLART_9_timing { private boolean done = false; private final String windowTitle = "Timing Demo"; private DisplayMode displayMode; private float rotation = 0f; // texture handle (a number that refers to an allocated texture) int myTextureHandle = 0; // For Text: character set display list "base" and Font texture int fontListBase = -1; // Base Display List For The character set int fontTextureHandle = -1; // Texture handle for character set image // For time tracking double startTime; // starting time of simulation (in seconds) double simulationTime; // current time of simulation (in seconds) /** * Main function just creates and runs the application. */ public static void main(String args[]) { GLART_9_timing app = new GLART_9_timing(); app.run(); } /** * Initialize the app, then sit in a render loop until done==true. */ public void run() { try { init(); while (!done) { mainloop(); render(); Display.update(); } cleanup(); } catch (Exception e) { e.printStackTrace(); System.exit(0); } } /** * Initialize the environment * @throws Exception */ private void init() throws Exception { initDisplay(); initGL(); // Load the image as RGBA pixels GLImage textureImg = new GLImage("images/grid_texture_marked.png"); // Allocate and configure a texture based on the image myTextureHandle = makeTexture(textureImg); // Make "mipmap" so texture can scale up/down gracefully makeTextureMipMap(textureImg); //---------------------------------------------------- // Initialize values for tracking time: //---------------------------------------------------- resetTimeCounters(); } /** * Create an OpenGL display, in this case a fullscreen window. * @throws Exception */ private void initDisplay() throws Exception { // get all possible display resolutions DisplayMode d[] = Display.getAvailableDisplayModes(); // find a resolution we like for (int i = 0; i < d.length; i++) { if (d[i].getWidth() == 800 && d[i].getHeight() == 600 && d[i].getBitsPerPixel() == 32) { displayMode = d[i]; break; } } // set the display to the resolution we picked Display.setDisplayMode(displayMode); Display.setTitle(windowTitle); // syncronize refresh to monitor refresh rate (usually 60 or 75 frames per second) Display.setVSyncEnabled(true); // if true, set to full screen, no chrome Display.setFullscreen(false); // create the window Display.create(); } /** * Initialize OpenGL * */ private void initGL() { // Select the Projection Matrix (controls perspective) GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); // Reset The Projection Matrix // Define perspective GLU.gluPerspective( 45.0f, // Field Of View (float)displayMode.getWidth() / (float)displayMode.getHeight(), // aspect ratio 0.1f, // near Z clipping plane 100.0f); // far Z clipping plane // set the background color GL11.glClearColor(.2f, .2f, .23f, 1); //=========================================================== // For text rendering //=========================================================== // Enable blending so that text background is transparent GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // Turn texturing on (text is a texture so we need this on) GL11.glEnable(GL11.GL_TEXTURE_2D); // prepare character set for text rendering buildFont("images/font_tahoma.png", 12); } /** * Handle keyboard input. Just check for escape key or user * clicking to close the window. */ private void mainloop() { if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) { // Escape is pressed done = true; } if(Display.isCloseRequested()) { // Window is closed done = true; } if(Keyboard.isKeyDown(Keyboard.KEY_SPACE)) { // Escape is pressed resetTimeCounters(); } } /** * Reset the timer values. */ public void resetTimeCounters() { // reset simulation time from LWJGL timer startTime = simulationTime = getTimeInSeconds(); // rotation of clock hand in degrees rotation = 0; } /** * Render the scene. */ private void render() { // how far should second hand rotate per second float degreesPerSecond = 6f; // time elapsed since we last rendered double secondsSinceLastFrame = getTimeInSeconds() - simulationTime; // update the simulation current time simulationTime += secondsSinceLastFrame; /////////////////////////////////////////////////////// // Animate second hand of clock PICK ONE: // OLD WAY: rotate an arbitrary amount //rotation += .06f; // BETTER: rotate in proportion to time elapsed rotation += degreesPerSecond * secondsSinceLastFrame; // done /////////////////////////////////////////////////////// // Clear screen and depth buffer GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // Select The Modelview Matrix (controls model orientation) GL11.glMatrixMode(GL11.GL_MODELVIEW); // Reset the Modelview matrix // this resets the coordinate system to center of screen GL11.glLoadIdentity(); // Where is the 'eye' GLU.gluLookAt( -1f, 0f, 7f, // eye position -1f, 0f, 0f, // target to look at 0f, 1f, 0f); // which way is up // draw the clock face GL11.glDisable(GL11.GL_TEXTURE_2D); GL11.glColor4f(.5f, .5f, .5f, 1f); GL11.glPushMatrix(); { // draw markers every 5 minutes (30 degrees) for (int i=0; i < 12; i++) { GL11.glRotatef(30f, 0,0,1); GL11.glBegin(GL11.GL_LINES); { GL11.glVertex3f(0f, 1.5f, -.1f); GL11.glVertex3f(0f, 2f, -.1f); } GL11.glEnd(); } } GL11.glPopMatrix(); // draw the second hand GL11.glColor4f(.9f, .8f, .8f, 1f); GL11.glPushMatrix(); { // rotate coordinate system GL11.glRotatef(-rotation, 0,0,1); // draw a triangular second hand GL11.glBegin(GL11.GL_TRIANGLES); { GL11.glTexCoord2f(0, 0); GL11.glVertex3f(-.1f, 0f, 0f); // Bottom Left GL11.glTexCoord2f(1, 0); GL11.glVertex3f( .1f, 0f, 0f); // Bottom Right GL11.glTexCoord2f(.5f, .5f); GL11.glVertex3f( 0, 2f, 0f); // Top } GL11.glEnd(); } GL11.glPopMatrix(); // text needs textures ON GL11.glEnable(GL11.GL_TEXTURE_2D); // Show the simulated time elapsed. // This is the time that we have processed within our simulation. glText(-4.5f, -.25f, 0f, 0, .03f, formatTime(simulationTime-startTime) ); // Show the actual system time elapsed. // This is the computer clock time that has elapsed since we started the simulation. glPrint(50,40,0, "Actual time elapsed: " + formatTime(getTimeInSeconds()-startTime) ); } /** * Cleanup all the resources. * */ private void cleanup() { Display.destroy(); } //===================================================== // Time functions //===================================================== public static long ticksPerSecond = 0; // used to calculate time /** * Returns the current time in seconds from the LWJGL high-precision * timer. This function can be used to measure relative time ie. * the time elapsed between two events, not an absolute time-of-day. * * Calls the LWJGL function Sys.getTime() to get the number of * cpu "ticks". Divides this by the ticks-per-second to get the * current time in seconds. */ public static double getTimeInSeconds() { if (ticksPerSecond == 0) { // initialize ticksPerSecond ticksPerSecond = Sys.getTimerResolution(); } return (((double)Sys.getTime())/(double)ticksPerSecond); } /** * Convenience function returns time in milliseconds. Calls * getTimeInSeconds(). */ public static double getTimeInMillis() { return (double) (getTimeInSeconds() * 1000.0); } /** * Given time in seconds, returns minutes:seconds formatted string. */ public String formatTime(double seconds) { int secs = (int)(seconds % 60); String strSecs = (secs < 10)? "0" + secs : "" + secs; return "" + ((int)(seconds/60f)) + ":" + strSecs; } //===================================================== // Texture functions //===================================================== /** * Create a texture from the given image. */ public static int makeTexture(GLImage textureImg) { if ( textureImg == null ) { return 0; } else { return makeTexture(textureImg.pixelBuffer, textureImg.w, textureImg.h); } } /** * Create a texture from the given pixels in RGBA format. Set the texture * to repeat in both directions and use LINEAR for magnification. * @return the texture handle */ public static int makeTexture(ByteBuffer pixels, int w, int h) { // get a new empty texture int textureHandle = allocateTexture(); // 'select' the new texture by it's handle GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle); // set texture parameters: // how to wrap texture GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); // how to scale up texture GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); //GL11.GL_NEAREST); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); //GL11.GL_NEAREST); // Create the texture from pixels GL11.glTexImage2D(GL11.GL_TEXTURE_2D, // type of texture we're creating 0, // level-of-detail: use 0 GL11.GL_RGBA, // texture pixel format w, h, // width and height of texture image (powers of 2) 0, // widtt of the border (either 0 or 1, use 0) GL11.GL_RGBA, // image pixel format GL11.GL_UNSIGNED_BYTE, // image pixel data type pixels // image pixel data ); return textureHandle; } /** * Build "Mipmap" for currently selected texture. Builds different * versions of the texture image at lower levels of detail, so texture * can scale down gracefully at longer distances. * * @param textureImg the texture image * @return error code of buildMipMap call */ public static int makeTextureMipMap(GLImage textureImg) { int ret = GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D, 4, textureImg.w, textureImg.h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, textureImg.getPixelsRGBA()); if (ret != 0) { System.out.println("GLApp.makeTextureMipMap(): Error occured while building mip map, ret=" + ret); } //Assign the mip map levels and texture info GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_NEAREST); GL11.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); return ret; } /** * Allocate a texture (glGenTextures) and return the handle to it. */ public static int allocateTexture() { IntBuffer textureHandle = allocInts(1); GL11.glGenTextures(textureHandle); return textureHandle.get(0); } public static final int SIZE_INT = 4; public static IntBuffer allocInts(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_INT).order(ByteOrder.nativeOrder()).asIntBuffer(); } //======================================================================== // Functions to build a character set and draw text strings. // // Example: // buildFont("Font_tahoma.png"); // ... // glPrint(100, 100, 0, "Here's some text"); // ... // destroyFont(); // cleanup //======================================================================== /** * Build a character set from the given texture image. * * @param charSetImage texture image containing 256 characters in a 16x16 grid * @param fontWidth how many pixels to allow per character on screen * * @see destroyFont() */ public void buildFont(String charSetImage, int fontWidth) { // make texture from image GLImage textureImg = new GLImage(charSetImage); fontTextureHandle = makeTexture(textureImg); // build character set as call list of 256 textured quads buildFont(fontTextureHandle, fontWidth); } /** * Build the character set display list from the given texture. Creates * one quad for each character, with one letter textured onto each quad. * Assumes the texture is a 256x256 image containing every * character of the charset arranged in a 16x16 grid. Each character * is 16x16 pixels. Call destroyFont() to release the display list memory. * * Should be in ORTHO (2D) mode to render text (see setOrtho()). * * Special thanks to NeHe and Giuseppe D'Agata for the "2D Texture Font" * tutorial (http://nehe.gamedev.net). * * @param charSetImage texture image containing 256 characters in a 16x16 grid * @param fontWidth how many pixels to allow per character on screen * * @see destroyFont() */ public void buildFont(int fontTxtrHandle, int fontWidth) { float cx, cy; fontListBase = GL11.glGenLists(256); // Creating 256 Display Lists for (int i = 0; i < 256; i++) { cx = (float) (i % 16) / 16f; // X Texture Coord Of Character (0 - 1.0) cy = (float) (i / 16) / 16f; // Y Texture Coord Of Character (0 - 1.0) GL11.glNewList(fontListBase + i, GL11.GL_COMPILE); // Start Building A List GL11.glBegin(GL11.GL_QUADS); // Use A 16x16 pixel Quad For Each Character GL11.glTexCoord2f(cx, 1 - cy - 0.0625f); // Texture Coord (Bottom Left) GL11.glVertex2i(0, 0); GL11.glTexCoord2f(cx + 0.0625f, 1 - cy - 0.0625f); // Texture Coord (Bottom Right) GL11.glVertex2i(16, 0); GL11.glTexCoord2f(cx + 0.0625f, 1 - cy); // Texture Coord (Top Right) GL11.glVertex2i(16, 16); GL11.glTexCoord2f(cx, 1 - cy); // Texture Coord (Top Left) GL11.glVertex2i(0, 16); GL11.glEnd(); // Done Building Our Quad (Character) GL11.glTranslatef(fontWidth, 0, 0); // Move To The Right Of The Character GL11.glEndList(); // Done Building The Display List } // Loop Until All 256 Are Built } /** * Clean up the allocated display lists for the character set. */ public void destroyFont() { if (fontListBase != -1) { GL11.glDeleteLists(fontListBase,256); fontListBase = -1; } } /** * Render a text string onto the screen, using the character set created * by buildCharSet(). */ public void glPrint(int x, int y, int set, String msg) { int offset = fontListBase - 32 + (128 * set); if (fontListBase == -1 || fontTextureHandle == -1) { System.out.println("glPrint(): character set has not been created -- run buildFont() first"); return; } if (msg != null) { // enable the charset texture GL11.glBindTexture(GL11.GL_TEXTURE_2D, fontTextureHandle); // prepare to render in 2D setOrthoOn(); // draw the text GL11.glTranslatef(x, y, 0); // Position The Text (in pixels coords) for(int i=0; i