package glapp; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.lang.reflect.Method; import java.nio.*; import java.io.*; import java.net.URL; import org.lwjgl.*; import org.lwjgl.opengl.*; import org.lwjgl.input.*; import org.lwjgl.util.glu.*; /** * Collection of functions to init and run an OpenGL app using LWJGL. *

* Includes functions to handle:
* Setup display mode, keyboard, mouse, handle events
* Run main loop of application
* Buffer allocation -- manage IntBuffer, ByteBuffer calls.
* OpenGL functions -- convert screen/world coords, set modes, lights, etc.
* Utility functions -- load images, convert pixels, getTimeInMillis, etc.
*

* Has a main() function to run as an application, though this class has only * minimal placeholder functionality. It is meant to be subclassed, * and the subclass will define setup() draw() mouseMove() functions, etc. *

* GLApp initializes the LWJGL environment for OpenGL rendering, * ie. creates a window, sets the display mode, inits mouse and keyboard, * then runs a loop. *

* Uses GLImage to load and hold image pixels. *

* napier -at- potatoland -dot- org * * @see GLImage */ public class GLApp { // Just for reference public static final String GLAPP_VERSION = ".5"; // Byte size of data types: Used when allocating native buffers public static final int SIZE_DOUBLE = 8; public static final int SIZE_FLOAT = 4; public static final int SIZE_INT = 4; public static final int SIZE_BYTE = 1; // Application settings // These can be tweaked in main() before calling app.run() // to customize app behavior. public static int finishedKey = Keyboard.KEY_ESCAPE; // App will exit when this key is hit (set to 0 for no key exit) public static String window_title = "OpenGL Window"; // window title, set in initDisplay() public static String configFilename = "GLApp.cfg"; // init() calls loadSettings() to load initial settings from this file (OPTIONAL) public static boolean hideNativeCursor = false; // hide the operating system cursor, see hideNativeCursor() public static boolean disableNativeCursor = false; // turn completely off the operating system cursor, see disableNativeCursor() public static boolean VSyncEnabled = true; // if true, synchronize screen updates with video refresh rate public static boolean useCurrentDisplay = false; // when initing display, use the settings of the desktop (whatever the PC was using before app was started) public static boolean fullScreen = false; // full screen or floating window public static boolean showMessages = true; // if true, show debug messages, if false show only error messages (see msg() err()) public static float aspectRatio = 0; // aspect ratio of OpenGL context (if 0, default to displayWidth/displayHeight) // Display settings (settings in glapp.cfg will override these) // initDisplay() will pick a Display that best matches displayWidth, // displayHeight, displayColorBits, displayFrequency. If these values // are -1, initDisplay() will use the desktop screen settings. public static int displayWidth = -1; public static int displayHeight = -1; public static int displayColorBits = -1; public static int displayFrequency = -1; public static int depthBufferBits = 24; // bits per pixel in the depth buffer public static int viewportX, viewportY; // viewport position (will default to 0,0) public static int viewportW, viewportH; // viewport size (will default to screen width, height) //private static int orthoWidth = 0; //private static int orthoHeight = 0; // DisplayMode chosen by initDisplay() // DM and displayMode are the same thing. public static DisplayMode DM, origDM; // hold display mode we set, and the display mode when app first executes public static DisplayMode displayMode; // hold display mode we set (same as DM) // Application variables // These are set internally but can be read by the // subclass application. public static Properties settings=new Properties(); // holds settings from file GLApp.cfg (see loadSettings()) public static boolean finished; // App will exit when finished is true (when finishedKey is hit: see run()) public static int cursorX, cursorY; // mouse position (see handleEvents()) public static double lastFrameTime = 0; // ticks since last frame was drawn (see run() and updateTimer()) public static double secondsSinceLastFrame = 0; // seconds elapsed since last frame was drawn (see updateTimer()) public static long ticksPerSecond = 0; // used to calc time in millis public static int frameCount = 0; // count frames per sec (just to track performance) // For copying screen image to a texture public static int screenTextureSize = 1024; // how large should texture be to hold screen (see makeTextureForScreen()) // NIO Buffers to retrieve OpenGL settings. // For memory efficiency and performance, instantiate these once, and reuse. // see getSetingInt(), getModelviewMatrix(), project(), unProject() public static IntBuffer bufferViewport = allocInts(16); public static FloatBuffer bufferModelviewMatrix = allocFloats(16); public static FloatBuffer bufferProjectionMatrix = allocFloats(16); public static FloatBuffer tmpResult = allocFloats(16); // temp var to hold project/unproject results public static FloatBuffer tmpFloats = allocFloats(4); // temp var used by setLightPos(), setFog() public static ByteBuffer tmpFloat = allocBytes(SIZE_FLOAT); // temp var used by getZDepth() public static IntBuffer tmpInts = allocInts(16); // temp var used by getSettingInt() public static ByteBuffer tmpByte = allocBytes(SIZE_BYTE); // temp var used by getStencilValue() public static ByteBuffer tmpInt = allocBytes(GLApp.SIZE_INT); // temp var used by getPixelColor() // Material colors (see setMaterial()) public static FloatBuffer mtldiffuse = allocFloats(4); // color of the lit surface public static FloatBuffer mtlambient = allocFloats(4); // color of the shadowed surface public static FloatBuffer mtlspecular = allocFloats(4); // reflection color (typically this is a shade of gray) public static FloatBuffer mtlemissive = allocFloats(4); // glow color public static FloatBuffer mtlshininess = allocFloats(4); // size of the reflection highlight // Misc. public static float rotation = 0f; // to rotate cubes (just to put something on screen) public static final float PIOVER180 = 0.0174532925f; // A constant used in navigation: PI/180 public static final float PIUNDER180 = 57.2957795130f; // A constant used in navigation: 180/PI; static Hashtable OpenGLextensions; // will be populated by extensionExists() static double avgSecsPerFrame=.01; // to smooth out motion, keep a moving average of frame render times //======================================================================== // Run main loop of application. Handle mouse and keyboard input. // // The functions main(), run() and init() start and run the application. // The run() function starts a loop that handles mouse and keyboard events // and calls draw() repeatedly. // //======================================================================== public static void main(String args[]) { GLApp demo = new GLApp(); demo.run(); } /** * Runs the application. Calls init() function to setup OpenGL, * input and display. Runs the main loop of the application, which handles * mouse and keyboard input. *

* Calls init(), handleEvents(), update() and draw().
* handleEvents() calls: mouseMove(), mouseDown(), mouseUp(), keyDown(), keyUp() */ public void run() { // hold onto application class in case we need to load images from jar (see getInputStream()) setRootClass(); try { // Init Display, Keyboard, Mouse, OpenGL, load config file init(); // Main loop while (!finished) { if (!Display.isVisible()) { // window is minimized Thread.sleep(200L); } else if (Display.isCloseRequested()) { // window X button clicked finished = true; } else { // yield a little so other threads can work Thread.sleep(1); } updateTimer(); // track when frame was drawn (see secondsSinceLastFrame) handleEvents(); // call key...() and mouse...() functions based on input events update(); // do program logic here (subclass may override this) draw(); // redraw the screen (subclass overrides this) Display.update(); } } catch (Exception e) { err("GLApp.run(): " + e); e.printStackTrace(System.out); } // prepare to exit cleanup(); System.exit(0); } /** * Called only once when app is first started, init() prepares the display, * mouse and OpenGL context for use. Override init() only if you want to * substantially alter the app startup behavior. Otherwise just override * initGL() to tweak the OpenGL context and setup() to load textures, * models, etc.. */ public void init() { // load settings from config file (display size, resolution, etc.) loadSettings(configFilename); initDisplay(); initInput(); initGL(); setup(); // subclass usually overrides this updateTimer(); // Do this once to init time values to something sane, otherwise the first game loop will report a huge secondsElapsedPerFrame } /** * Called by the run() loop. Handles animation and input for each frame. */ public void handleEvents() { int mouseDX = Mouse.getDX(); int mouseDY = Mouse.getDY(); int mouseDW = Mouse.getDWheel(); // handle mouse motion if (mouseDX != 0 || mouseDY != 0 || mouseDW != 0) { cursorX += mouseDX; cursorY += mouseDY; if (cursorX < 0) { cursorX = 0; } else if (cursorX > displayMode.getWidth()) { cursorX = displayMode.getWidth(); } if (cursorY < 0) { cursorY = 0; } else if (cursorY > displayMode.getHeight()) { cursorY = displayMode.getHeight(); } mouseMove(cursorX,cursorY); //msg("DX=" + mouseDX + " DY=" + mouseDY + " cursorX=" + cursorX); } // handle mouse wheel event if (mouseDW != 0) { mouseWheel(mouseDW); } // handle mouse clicks while ( Mouse.next() ) { if(Mouse.getEventButton() == 0 && Mouse.getEventButtonState() == true) { mouseDown(cursorX, cursorY); } if(Mouse.getEventButton() == 0 && Mouse.getEventButtonState() == false) { mouseUp(cursorX, cursorY); } if(Mouse.getEventButton() == 1 && Mouse.getEventButtonState() == true) { mouseDown(cursorX, cursorY); } if(Mouse.getEventButton() == 1 && Mouse.getEventButtonState() == false) { mouseUp(cursorX, cursorY); } } // Handle key hits while ( Keyboard.next() ) { // check for exit key if (Keyboard.getEventKey() == finishedKey) { finished = true; } // pass key event to handler if (Keyboard.getEventKeyState()) { // key was just pressed, trigger keyDown() keyDown(Keyboard.getEventKey()); } else { keyUp(Keyboard.getEventKey()); // key was released } } // Count frames frameCount++; if ((Sys.getTime()-lastFrameTime) > ticksPerSecond) { //msg("==============> FramesPerSec=" + (frameCount/1) + " timeinsecs=" + getTimeInSeconds() + " timeinmillis=" + getTimeInMillis()); frameCount = 0; } } /** * Load configuration settings from optional properties file. * File format is:
*

     * # Comment
     * displayWidth=1024
     * displayHeight=768
     * 
* * @param configFilename */ public void loadSettings(String configFilename) { if (configFilename == null || configFilename.equals("")) { return; } InputStream configFileIn = getInputStream(configFilename); settings = new Properties(); if (configFileIn == null) { msg("GLApp.loadSettings() warning: config file " + configFilename + " not found, will use default settings."); return; } else { try { settings.load(configFileIn); } catch (Exception e) { msg("GLApp.loadSettings() warning: " + e); return; } } // Debug: show the settings settings.list(System.out); // Check for available settings if (settings.getProperty("displayWidth") != null) { displayWidth = Integer.parseInt(settings.getProperty("displayWidth")); } if (settings.getProperty("displayHeight") != null) { displayHeight = Integer.parseInt(settings.getProperty("displayHeight")); } if (settings.getProperty("displayColorBits") != null) { displayColorBits = Integer.parseInt(settings.getProperty("displayColorBits")); } if (settings.getProperty("displayFrequency") != null) { displayFrequency = Integer.parseInt(settings.getProperty("displayFrequency")); } if (settings.getProperty("depthBufferBits") != null) { depthBufferBits = Integer.parseInt(settings.getProperty("depthBufferBits")); } if (settings.getProperty("aspectRatio") != null) { aspectRatio = Float.parseFloat(settings.getProperty("aspectRatio")); } if (settings.getProperty("fullScreen") != null) { fullScreen = settings.getProperty("fullScreen").toUpperCase().equals("YES"); } if (settings.getProperty("useCurrentDisplay") != null) { useCurrentDisplay = settings.getProperty("useCurrentDisplay").toUpperCase().equals("YES"); } if (settings.getProperty("finishedKey") != null) { // key codes are defined in the lwjgl Keyboard class finishedKey = Integer.parseInt(settings.getProperty("finishedKey")); } if (settings.getProperty("window_title") != null) { window_title = settings.getProperty("window_title"); } if (settings.getProperty("VSyncEnabled") != null) { VSyncEnabled = settings.getProperty("VSyncEnabled").toUpperCase().equals("YES"); } } //======================================================================== // Setup display mode // // Initialize Display, Mouse, Keyboard. // //======================================================================== /** * Initialize the Display mode, viewport size, and open a Window. * By default the window is fullscreen, the viewport is the same dimensions * as the window. */ public boolean initDisplay() { origDM = Display.getDisplayMode(); // current display settings msg("GLApp.initDisplay(): Current display mode is " + origDM); // for display properties that have not been specified, default to current display value if (displayHeight == -1) displayHeight = origDM.getHeight(); if (displayWidth == -1) displayWidth = origDM.getWidth(); if (displayColorBits == -1) displayColorBits = origDM.getBitsPerPixel(); if (displayFrequency == -1) displayFrequency = origDM.getFrequency(); // Set display mode try { if (useCurrentDisplay) { // use current display settings (ignore properties file) DM = origDM; } else { // find a display mode that matches the specified settings (or use a sane alternative) if ( (DM = getDisplayMode(displayWidth, displayHeight, displayColorBits, displayFrequency)) == null) { if ( (DM = getDisplayMode(1024, 768, 32, 60)) == null) { if ( (DM = getDisplayMode(1024, 768, 16, 60)) == null) { if ( (DM = getDisplayMode(origDM.getWidth(), origDM.getHeight(), origDM.getBitsPerPixel(), origDM.getFrequency())) == null) { err("GLApp.initDisplay(): Can't find a compatible Display Mode!!!"); } } } } } msg("GLApp.initDisplay(): Setting display mode to " + DM + " with pixel depth = " + depthBufferBits); Display.setDisplayMode(DM); displayMode = DM; displayWidth = DM.getWidth(); displayHeight = DM.getHeight(); displayColorBits = DM.getBitsPerPixel(); displayFrequency = DM.getFrequency(); } catch (Exception exception) { System.err.println("GLApp.initDisplay(): Failed to create display: " + exception); System.exit(1); //!!!!new } // Initialize the Window try { Display.create(new PixelFormat(0, depthBufferBits, 8)); // set bits per buffer: alpha, depth, stencil Display.setTitle(window_title); Display.setFullscreen(fullScreen); Display.setVSyncEnabled(VSyncEnabled); //msg("GLApp.initDisplay(): Created OpenGL window."); } catch (Exception exception1) { System.err.println("GLApp.initDisplay(): Failed to create OpenGL window: " + exception1); System.exit(1); } // Set viewport width/height and Aspect ratio. if (aspectRatio == 0f) { // no aspect ratio was set in GLApp.cfg: default to full screen. aspectRatio = (float)DM.getWidth() / (float)DM.getHeight(); // calc aspect ratio of display } // viewport may not match shape of screen. Adjust to fit. viewportH = DM.getHeight(); // viewport Height viewportW = (int) (DM.getHeight() * aspectRatio); // Width if (viewportW > DM.getWidth()) { viewportW = DM.getWidth(); viewportH = (int) (DM.getWidth() * (1 / aspectRatio)); } // center viewport in screen viewportX = (int) ((DM.getWidth()-viewportW) / 2); viewportY = (int) ((DM.getHeight()-viewportH) / 2); return true; } /** * Retrieve a DisplayMode object with the given params */ public static DisplayMode getDisplayMode(int w, int h, int colorBits, int freq) { try { DisplayMode allDisplayModes[] = Display.getAvailableDisplayModes(); DisplayMode dm = null; for (int j = 0; j < allDisplayModes.length; j++) { dm = allDisplayModes[j]; if (dm.getWidth() == w && dm.getHeight() == h && dm.getBitsPerPixel() == colorBits && dm.getFrequency() == freq) { return dm; } } } catch (LWJGLException lwjgle) { err("GLApp.getDisplayMode() error:" + lwjgle); } return null; } /** * Initialize the Keyboard and Mouse. *

* Disable or hide the native cursor. Set the initial cursor position. Get * the timer resolution (ticks per second). * * @see handleEvents() */ public void initInput() { try { // init keyboard Keyboard.create(); // Turn off native cursor? if (disableNativeCursor) { // Mouse.setGrabbed(true) will turn off the native cursor disableNativeCursor(true); // set initial cursor pos to center screen cursorX = (int) DM.getWidth() / 2; cursorY = (int) DM.getHeight() / 2; } // Hide native cursor when inside application window? if (hideNativeCursor) { hideNativeCursor(true); } // Init hi-res timer (see time functions) ticksPerSecond = Sys.getTimerResolution(); } catch (Exception e) { err("GLApp.initInput(): " + e); } } //======================================================================== // Custom Application functionality: can be overriden by subclass. // // Functions to initialize OpenGL, set the viewing mode, render the scene, // respond to mouse actions, and initialize the app. These functions // are overridden in the subclass to create custom behavior. // //======================================================================== /** * Initialize the OpenGL context. The subclass can override this function * to customize the OpenGL settings. This function is called by init() * once when app starts, but may be called again to restore settings to a * default state, or to initialize a PBuffer object to the exact same * state as the display. */ public void initGL() { try { // Depth test setup GL11.glEnable(GL11.GL_DEPTH_TEST); // Enables Depth Testing GL11.glDepthFunc(GL11.GL_LEQUAL); // The Type Of Depth Testing To Do // Some basic settings GL11.glClearColor(0f, 0f, 0f, 1f); // Black Background GL11.glEnable(GL11.GL_NORMALIZE); // force normal lengths to 1 GL11.glEnable(GL11.GL_CULL_FACE); // don't render hidden faces GL11.glEnable(GL11.GL_TEXTURE_2D); // use textures GL11.glEnable(GL11.GL_BLEND); // enable transparency // How to handle transparency: average colors together GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // Enable alpha test so the transparent backgrounds in texture images don't draw. // This prevents transparent areas from affecting the depth or stencil buffer. // alpha func will accept only fragments with alpha greater than 0 GL11.glEnable(GL11.GL_ALPHA_TEST); GL11.glAlphaFunc(GL11.GL_GREATER, 0f); // Draw specular highlghts on top of textures GL11.glLightModeli(GL12.GL_LIGHT_MODEL_COLOR_CONTROL, GL12.GL_SEPARATE_SPECULAR_COLOR ); // Perspective quality GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST); // Set the size and shape of the screen area GL11.glViewport(viewportX, viewportY, viewportW, viewportH); // setup perspective (see setOrtho() for 2D) setPerspective(); // select model view for subsequent transformations GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glLoadIdentity(); } catch (Exception e) { err("GLApp.initOpenGL(): " + e); } } /** * Setup can be overridden by the subclass to initialize the application * ie load textures, models, and create data structures used by the app. * * This function is called only once, when application starts. It is * called after initDisplay and initOpenGL(), so the OpenGL context is * already created. * * @see init() */ public void setup() { } /** * Update can be overridden by the subclass * * @see run() */ public void update() { } /** * Called by run() to draw one frame. Subclass will override this. * This is an alias function just to be follow Processing and OpenFrameworks * function naming conventions. */ public void draw() { render(); } /** * Same as draw(). Subclass can override render() instead of draw(). * Same thing, just a matter of taste. */ public void render() { // rotate 90 degrees per second rotation += secondsSinceLastFrame * 90f; // clear depth buffer and color GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // select model view for subsequent transforms GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glLoadIdentity(); // set viewpoint 10 units from origin, looking at origin GLU.gluLookAt(0,0,10, 0,0,0, 0,1,0); // rotate, scale and draw cube GL11.glPushMatrix(); { GL11.glRotatef(rotation, 0, 1, 0); GL11.glColor4f(0f, .5f, 1f, 1f); renderCube(); } GL11.glPopMatrix(); // draw another overlapping cube GL11.glPushMatrix(); { GL11.glRotatef(rotation, 1, 1, 1); GL11.glColor4f(.7f, .5f, 0f, 1f); renderCube(); } GL11.glPopMatrix(); } /** * Run() calls this right before exit. Free up allocated resources (display lists) * and gracefully shut down OpenGL context. */ public void cleanup() { destroyFont(); destroyDisplayLists(); Keyboard.destroy(); Display.destroy(); // will call Mouse.destroy() } /** * Shutdown the application. This will call cleanup() before exiting from the * application. * @see cleanup() */ public void exit() { finished = true; } //======================================================================== // Mouse events, called by handleEvents() //======================================================================== /** * Called by handleEvents() when mouse moves */ public void mouseMove(int x, int y) { } public void mouseDown(int x, int y) { } public void mouseUp(int x, int y) { } public void mouseWheel(int wheelMoved) { } /** * Return true if the given mousebutton is down. Typically mouse buttons * are 0=left, 1=right. This function can be called inside mouse events such * as mouseDown() and mouseMove() to see which button is activated. * @param whichButton number of mouse button (0=left button) */ public boolean mouseButtonDown(int whichButton) { return Mouse.isButtonDown(whichButton); } /** * Called when key is pressed. Keycode will be the key ID value as * defined in the LWJGL Keyboard class. * * @see Keyboard class in the LWJGL documentation * @param keycode */ public void keyDown(int keycode) { } /** * Called when key is released. Keycode will be the key ID value as * defined in the LWJGL Keyboard class. * * @see Keyboard class in the LWJGL documentation * @param keycode */ public void keyUp(int keycode) { } /** * Return the character associatated with the last key event. When * called inside keyDown() or keyUp() this function will return the * character equivalent of the keycode that was passed to keyDown() * or keyUp(). */ public char keyChar() { return Keyboard.getEventCharacter(); } //======================================================================== // functions to get values from a Properties object. Properties can be // loaded from a text file containing name=value pairs. //======================================================================== /** * Load configuration settings from a properties file. * File format is:
*

     * # Comment
     * displayWidth=1024
     * displayHeight=768
     * 
* * @param configFilename */ public static Properties loadPropertiesFile(String configFilename) { Properties props = new Properties(); try { settings.load(getInputStream(configFilename)); } catch (Exception e) { msg("GLApp.loadPropertiesFile() warning: " + e); return null; } return props; } public static String getProperty(Properties props, String propName) { String s = null; if (propName != null && propName.length() > 0) { s = props.getProperty(propName); } return s; } public static int getPropertyInt(Properties props, String propName) { String s = getProperty(props,propName); int v = -1; if (s != null) { v = Integer.parseInt(s); } return v; } public static float getPropertyFloat(Properties props, String propName) { String s = getProperty(props,propName); float v = -1f; if (s != null) { v = Float.parseFloat(s); } return v; } public static boolean getPropertyBool(Properties props, String propName) { String s = getProperty(props,propName); boolean v = false; if (s != null) { v = (s.toUpperCase().equals("YES") || s.toUpperCase().equals("TRUE") || s.equals("1")); } return v; } /** * Return a property from the application configuration file (the filename given * in the configFilename variable). This file is optional, so properties may be empty. * @see loadSettings() */ public static String getProperty(String propertyName) { return settings.getProperty(propertyName); } /** * return the width of the Viewport (the screen area that OpenGL will draw into). * By default the viewport is the same size as the Display (see getWidthWindow()), * however the setViewport() function can set the viewport to a sub-region of the screen. *

* This function is only valid after app is running and Display has been initialized. * * @see setViewport(int,int,int,int) */ public static int getWidth() { return viewportW; } /** * return the height of the Viewport (the screen area that OpenGL will draw into). * By default the viewport is the same size as the Display (see getHeightWindow()), * however the setViewport() function can set the viewport to a sub-region of the screen. *

* This function is only valid after app is running and Display has been initialized. * * @see setViewport(int,int,int,int) */ public static int getHeight() { return viewportH; } /** * return the Display width (the width of the full window). Only valid after app * is running and Display has been initialized. */ public static int getWidthWindow() { return displayWidth; } /** * return the Display height (the height of the full window). Only valid after * app is running and Display has been initialized. */ public static int getHeightWindow() { return displayHeight; } //======================================================================== // functions to set basic application behavior //======================================================================== /** * Set the background color of the screen. The red,green,blue * color components are floats in the range 0-1. Black is 0,0,0 * and white is 1,1,1. Color will take effect the next time the * screen is cleared. */ public static void setBackgroundColor(float R, float G, float B) { GL11.glClearColor(R,G,B,1); } /** * If the param is true, turn off the hardware cursor. The application can * decide how to respond to mouse events and whether to draw a position indicator on * screen or not. If running in a window (not fullscreen), there will be no hardware * cursor visible and the user cannot move or click outside the window area. *

* If the param is false, the hardware cursor will behave normally. Use * hideHardwareCursor() to show or hide the hardware cursor. * * @see hideHardwareCursor() */ public static void disableNativeCursor(boolean off) { disableNativeCursor = off; Mouse.setGrabbed(off); } /** * If param is true, make the native cursor transparent. Cursor will be hidden * in the window area, but will be visible outside the window (assuming you're not in * fullscreen mode). I also used this approach with touch screens because the touch * screen drivers needed to read the hardware mouse position, so I * couldn't disable the hardware cursor, but I wanted to hide it. *

* If param is false, reset the cursor to the default. * * @see disableHardwareCursor() */ public static void hideNativeCursor(boolean hide) { hideNativeCursor = hide; if ( (Cursor.getCapabilities() & Cursor.CURSOR_ONE_BIT_TRANSPARENCY) == 0) { err("GLApp.hideHardwareCursor(): No hardwared cursor support!"); return; } try { if (hide) { Cursor cursor = null; int cursorImageCount = 1; int cursorWidth = Cursor.getMaxCursorSize(); int cursorHeight = cursorWidth; IntBuffer cursorImages; IntBuffer cursorDelays = null; // Create a single cursor, completely transparent cursorImages = ByteBuffer.allocateDirect(cursorWidth * cursorHeight * cursorImageCount * SIZE_INT).order(ByteOrder.nativeOrder()).asIntBuffer(); for (int j = 0; j < cursorWidth; j++) { for (int l = 0; l < cursorHeight; l++) { cursorImages.put(0x00000000); } } cursorImages.flip(); cursor = new Cursor(Cursor.getMaxCursorSize(), Cursor.getMaxCursorSize(), Cursor.getMaxCursorSize() / 2, Cursor.getMaxCursorSize() / 2, cursorImageCount, cursorImages, cursorDelays); // turn it on Mouse.setNativeCursor(cursor); } else { Mouse.setNativeCursor(null); } } catch (Exception e) { err("GLApp.hideHardwareCursor(): error " + e); } } /** * Set the cursorX,cursorY position. This will set the screen position of the native * cursor also, unless hideCursor() was called. * * @param screenX * @param screenY */ public static void setCursorPosition(int screenX, int screenY) { Mouse.setCursorPosition(screenX,screenY); cursorX = screenX; cursorY = screenY; } //======================================================================== // Matrix functions: get settings, get matrices, convert // screen to world coords. //======================================================================== /** * retrieve a setting from OpenGL (calls glGetInteger()) * @param whichSetting the id number of the value to be returned (same constants as for glGetInteger()) */ public static int getSettingInt(int whichSetting) { tmpInts.clear(); GL11.glGetInteger(whichSetting, tmpInts); return tmpInt.get(0); } public static FloatBuffer getModelviewMatrix() { bufferModelviewMatrix.clear(); GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, bufferModelviewMatrix); return bufferModelviewMatrix; } public static FloatBuffer getProjectionMatrix() { bufferProjectionMatrix.clear(); GL11.glGetFloat(GL11.GL_PROJECTION_MATRIX, bufferProjectionMatrix); return bufferProjectionMatrix; } public static IntBuffer getViewport() { bufferViewport.clear(); GL11.glGetInteger(GL11.GL_VIEWPORT, bufferViewport); return bufferViewport; } /** * Convert a FloatBuffer matrix to a 4x4 float array. * @param fb FloatBuffer containing 16 values of 4x4 matrix * @return 2 dimensional float array */ public static float[][] getMatrixAsArray(FloatBuffer fb) { float[][] fa = new float[4][4]; fa[0][0] = fb.get(); fa[0][1] = fb.get(); fa[0][2] = fb.get(); fa[0][3] = fb.get(); fa[1][0] = fb.get(); fa[1][1] = fb.get(); fa[1][2] = fb.get(); fa[1][3] = fb.get(); fa[2][0] = fb.get(); fa[2][1] = fb.get(); fa[2][2] = fb.get(); fa[2][3] = fb.get(); fa[3][0] = fb.get(); fa[3][1] = fb.get(); fa[3][2] = fb.get(); fa[3][3] = fb.get(); return fa; } /** * Return the Z depth of the single pixel at the given screen position. */ public static float getZDepth(int x, int y) { tmpFloat.clear(); GL11.glReadPixels(x, y, 1, 1, GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, tmpFloat); return ( (float) (tmpFloat.getFloat(0))); } /** * Find the Z depth of the origin in the projected world view. Used by getWorldCoordsAtScreen() * Projection matrix must be active for this to return correct results (GL.glMatrixMode(GL.GL_PROJECTION)). * For some reason I have to chop this to four decimals or I get bizarre * results when I use the value in project(). */ public static float getZDepthAtOrigin() { float[] resultf = new float[3]; project( 0, 0, 0, resultf); return ((int)(resultf[2] * 10000F)) / 10000f; // truncate to 4 decimals } /** * Return screen coordinates for a given point in world space. The world * point xyz is 'projected' into screen coordinates using the current model * and projection matrices, and the current viewport settings. * * @param x world coordinates * @param y * @param z * @param resultf the screen coordinate as an array of 3 floats */ public static void project(float x, float y, float z, float[] resultf) { // lwjgl 2.0 altered params for GLU funcs GLU.gluProject( x, y, z, getModelviewMatrix(), getProjectionMatrix(), getViewport(), tmpResult); resultf[0] = tmpResult.get(0); resultf[1] = tmpResult.get(1); resultf[2] = tmpResult.get(2); } /** * Return world coordinates for a given point on the screen. The screen * point xyz is 'un-projected' back into world coordinates using the * current model and projection matrices, and the current viewport settings. * * @param x screen x position * @param y screen y position * @param z z-buffer depth position * @param resultf the world coordinate as an array of 3 floats * @see getWorldCoordsAtScreen() */ public static void unProject(float x, float y, float z, float[] resultf) { GLU.gluUnProject( x, y, z, getModelviewMatrix(), getProjectionMatrix(), getViewport(), tmpResult); resultf[0] = tmpResult.get(0); resultf[1] = tmpResult.get(1); resultf[2] = tmpResult.get(2); } /** * For given screen xy, return the world xyz coords in a float array. Assume * Z position is 0. */ public static float[] getWorldCoordsAtScreen(int x, int y) { float z = getZDepthAtOrigin(); float[] resultf = new float[3]; unProject( (float)x, (float)y, (float)z, resultf); return resultf; } /** * For given screen xy and z depth, return the world xyz coords in a float array. */ public static float[] getWorldCoordsAtScreen(int x, int y, float z) { float[] resultf = new float[3]; unProject( (float)x, (float)y, (float)z, resultf); return resultf; } //======================================================================== // Texture functions //======================================================================== /** * 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); } /** * "Select" the given texture for further texture operations. */ public static void activateTexture(int textureHandle) { GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle); } /** * Create a texture and mipmap from the given image file. */ public static int makeTexture(String textureImagePath) { int textureHandle = 0; GLImage textureImg = loadImage(textureImagePath); if (textureImg != null) { textureHandle = makeTexture(textureImg); makeTextureMipMap(textureHandle,textureImg); } return textureHandle; } /** * Create a texture and optional mipmap with the given parameters. * * @param mipmap: if true, create mipmaps for the texture * @param anisotropic: if true, enable anisotropic filtering */ public static int makeTexture(String textureImagePath, boolean mipmap, boolean anisotropic) { int textureHandle = 0; GLImage textureImg = loadImage(textureImagePath); if (textureImg != null) { textureHandle = makeTexture(textureImg.pixelBuffer, textureImg.w, textureImg.h, anisotropic); if (mipmap) { makeTextureMipMap(textureHandle,textureImg); } } return textureHandle; } /** * Create a texture from the given image. */ public static int makeTexture(GLImage textureImg) { if ( textureImg != null ) { if (isPowerOf2(textureImg.w) && isPowerOf2(textureImg.h)) { return makeTexture(textureImg.pixelBuffer, textureImg.w, textureImg.h, false); } else { msg("GLApp.makeTexture(GLImage) Warning: not a power of two: " + textureImg.w + "," + textureImg.h); textureImg.convertToPowerOf2(); return makeTexture(textureImg.pixelBuffer, textureImg.w, textureImg.h, false); } } return 0; } /** * De-allocate the given texture (glDeleteTextures()). */ public static void deleteTexture(int textureHandle) { IntBuffer bufferTxtr = allocInts(1).put(textureHandle);; GL11.glDeleteTextures(bufferTxtr); } /** * Returns true if n is a power of 2. If n is 0 return zero. */ public static boolean isPowerOf2(int n) { if (n == 0) { return false; } return (n & (n - 1)) == 0; } /** * Create a blank square texture with the given size. * @return the texture handle */ public static int makeTexture(int w) { ByteBuffer pixels = allocBytes(w*w*SIZE_INT); // allocate 4 bytes per pixel return makeTexture(pixels, w, w, false); } /** * Create a texture from the given pixels in the default Java ARGB int format.
* Configure the texture to repeat in both directions and use LINEAR for magnification. *

* @return the texture handle */ public static int makeTexture(int[] pixelsARGB, int w, int h, boolean anisotropic) { if (pixelsARGB != null) { ByteBuffer pixelsRGBA = GLImage.convertImagePixelsRGBA(pixelsARGB,w,h,true); return makeTexture(pixelsRGBA, w, h, anisotropic); } return 0; } /** * Create a texture from the given pixels in the default OpenGL RGBA pixel format. * Configure 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, boolean anisotropic) { // get a new empty texture int textureHandle = allocateTexture(); // preserve currently bound texture, so glBindTexture() below won't affect anything) GL11.glPushAttrib(GL11.GL_TEXTURE_BIT); // 'select' the new texture by it's handle GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle); // set texture parameters 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); 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); // make texture "anisotropic" so it will minify more gracefully if (anisotropic && extensionExists("GL_EXT_texture_filter_anisotropic")) { // Due to LWJGL buffer check, you can't use smaller sized buffers (min_size = 16 for glGetFloat()). FloatBuffer max_a = allocFloats(16); // Grab the maximum anisotropic filter. GL11.glGetFloat(EXTTextureFilterAnisotropic.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, max_a); // Set up the anisotropic filter. GL11.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, max_a.get(0)); } // Create the texture from pixels GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, // level of detail GL11.GL_RGBA8, // internal format for texture is RGB with Alpha w, h, // size of texture image 0, // no border GL11.GL_RGBA, // incoming pixel format: 4 bytes in RGBA order GL11.GL_UNSIGNED_BYTE, // incoming pixel data type: unsigned bytes pixels); // incoming pixels // restore previous texture settings GL11.glPopAttrib(); return textureHandle; } /** * Create a texture from the given pixels in ARGB format. The pixels buffer contains * 4 bytes per pixel, in ARGB order. ByteBuffers are created with native hardware byte * orders, so the pixels can be in big-endian (ARGB) order, or little-endian (BGRA) order. * Set the pixel_byte_order accordingly when creating the texture. *

* Configure the texture to repeat in both directions and use LINEAR for magnification. *

* NOTE: I'm having problems creating mipmaps when image pixel data is in GL_BGRA format. * Looks like GLU type param doesn't recognize GL_UNSIGNED_INT_8_8_8_8 and * GL_UNSIGNED_INT_8_8_8_8_REV so I can't specify endian byte order. Mipmaps look * right on PC but colors are reversed on Mac. Have to stick with GL_RGBA * byte order for now. *

* @return the texture handle */ public static int makeTextureARGB(ByteBuffer pixels, int w, int h) { // byte buffer has ARGB ints in little endian or big endian byte order int pixel_byte_order = (pixels.order() == ByteOrder.BIG_ENDIAN)? GL12.GL_UNSIGNED_INT_8_8_8_8 : GL12.GL_UNSIGNED_INT_8_8_8_8_REV; // 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 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); 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, 0, // level of detail GL11.GL_RGBA8, // internal format for texture is RGB with Alpha w, h, // size of texture image 0, // no border GL12.GL_BGRA, // incoming pixel format: 4 bytes in ARGB order pixel_byte_order, // incoming pixel data type: little or big endian ints pixels); // incoming pixels return textureHandle; } /** * Build Mipmaps for currently bound texture (builds a set of textures at various * levels of detail so that texture will scale up and down gracefully) * * @param textureImg the texture image * @return error code of buildMipMap call */ public static int makeTextureMipMap(int textureHandle, GLImage textureImg) { int ret = 0; if (textureImg != null && textureImg.isLoaded()) { GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureHandle); ret = GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D, GL11.GL_RGBA8, textureImg.w, textureImg.h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, textureImg.getPixelBytes()); if (ret != 0) { err("GLApp.makeTextureMipMap(): Error occured while building mip map, ret=" + ret + " error=" + GLU.gluErrorString(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; } /** * Create a texture large enough to hold the screen image. Use RGBA8 format * to insure colors are copied exactly. Use GL_NEAREST for magnification * to prevent slight blurring of image when screen is drawn back. * * @see frameCopy() * @see frameDraw() */ public static int makeTextureForScreen(int screenSize) { // get a texture size big enough to hold screen (512, 1024, 2048 etc.) screenTextureSize = getPowerOfTwoBiggerThan(screenSize); msg("GLApp.makeTextureForScreen(): made texture for screen with size " + screenTextureSize); // get a new empty texture int textureHandle = allocateTexture(); ByteBuffer pixels = allocBytes(screenTextureSize*screenTextureSize*SIZE_INT); // 'select' the new texture by it's handle GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle); // set texture parameters 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); // use GL_NEAREST to prevent blurring during frequent screen restores GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); // Create the texture from pixels: use GL_RGBA8 to insure exact color copy GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, screenTextureSize, screenTextureSize, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels); return textureHandle; } /** * Find a power of two equal to or greater than the given value. * Ie. getPowerOfTwoBiggerThan(800) will return 1024. *

* @see makeTextureForScreen() * @param dimension * @return a power of two equal to or bigger than the given dimension */ public static int getPowerOfTwoBiggerThan(int n) { if (n < 0) return 0; --n; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; return n+1; } /** * Copy pixels from a ByteBuffer to a texture. The buffer pixels are integers in ARGB format * (this is the Java default format you get from a BufferedImage) or BGRA format (this is the * native order of Intel systems. * * The glTexSubImage2D() call treats the incoming pixels as integers * in either big-endian (ARGB) or little-endian (BGRA) formats based on the setting * of the bytebuffer (pixel_byte_order). * * @param bb ByteBuffer of pixels stored as ARGB or BGRA integers * @param w width of source image * @param h height of source image * @param textureHandle texture to copy pixels into */ public static void copyPixelsToTexture(ByteBuffer bb, int w, int h, int textureHandle) { int pixel_byte_order = (bb.order() == ByteOrder.BIG_ENDIAN)? GL12.GL_UNSIGNED_INT_8_8_8_8 : GL12.GL_UNSIGNED_INT_8_8_8_8_REV; // "select" the texture that we'll write into GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureHandle); // Copy pixels to texture GL11.glTexSubImage2D( GL11.GL_TEXTURE_2D, // always GL_TEXTURE_2D 0, // texture detail level: always 0 0, 0, // x,y offset into texture w, h, // dimensions of image pixel data GL12.GL_BGRA, // format of pixels in texture (little_endian - native for PC) pixel_byte_order, // format of pixels in bytebuffer (big or little endian ARGB integers) bb // image pixel data ); } /** * Calls glTexSubImage2D() to copy pixels from an image into a texture. */ public static void copyImageToTexture(GLImage img, int textureHandle) { copyPixelsToTexture(img.pixelBuffer, img.w, img.h, textureHandle); } //======================================================================== // functions to set projection //======================================================================== /** * Set OpenGL to render in 3D perspective. Set the projection matrix using * GLU.gluPerspective(). The projection matrix controls how the scene is * "projected" onto the screen. Think of it as the lens on a camera, which * defines how wide the field of vision is, how deep the scene is, and how * what the aspect ratio will be. */ public static void setPerspective() { // select projection matrix (controls perspective) GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); GLU.gluPerspective(40f, aspectRatio, 1f, 1000f); // return to modelview matrix GL11.glMatrixMode(GL11.GL_MODELVIEW); } /** * Set OpenGL to render in flat 2D (no perspective) with a one-to-one * relation between screen pixels and world coordinates, ie. if * you draw a 10x10 quad at 100,100 it will appear as a 10x10 pixel * square on screen at pixel position 100,100. *

* ABOUT Ortho and Viewport:
* Let's say we're drawing in 2D and want to have a cinema proportioned * viewport (16x9), and want to bound our 2D rendering into that area ie. *

          ___________1024,576
         |           |
         |  Scene    |      Set the bounds on the scene geometry
         |___________|      to the viewport size and shape
      0,0

          ___________1024,576
         |           |
         |  Ortho    |      Set the projection to cover the same
         |___________|      area as the scene
      0,0

          ___________ 1024,768
         |___________|
         |           |1024,672
         |  Viewport |      Set the viewport to the same shape
     0,96|___________|      as scene and ortho, but centered on
         |___________|      screen.
      0,0
     *
*/ public static void setOrtho() { // select projection matrix (controls view on screen) GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); // set ortho to same size as viewport, positioned at 0,0 GL11.glOrtho( 0,viewportW, // left,right 0,viewportH, // bottom,top -500,500); // Zfar, Znear // return to modelview matrix GL11.glMatrixMode(GL11.GL_MODELVIEW); } public static void setOrtho(int width, int height) { // select projection matrix (controls view on screen) GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); // set ortho to same size as viewport, positioned at 0,0 GL11.glOrtho( 0,width, // left,right 0,height, // bottom,top -500,500); // Zfar, Znear // return to modelview matrix GL11.glMatrixMode(GL11.GL_MODELVIEW); } /** * Set OpenGL to render in flat 2D (no perspective) on top of current scene. * Preserve current projection and model views, and disable depth testing. * Ortho world size will be same as viewport size, so any ortho drawing * (drawQuad(), drawImageFullscreen(), etc.) will be scaled to fit viewport. *

* NOTE: if the viewport is the same size as the window (by default it is), * then setOrtho() will make the world coordinates exactly match the screen * pixel positions. This is convenient for mouse interaction, but be warned: * if you setViewport() to something other than fullscreen, then you need * to use getWorldCoordsAtScreen() to convert screen xy to world xy. *

* Once Ortho is on, glTranslate() will take pixel coords as arguments, * with the lower left corner 0,0 and the upper right corner 1024,768 (or * whatever your screen size is). !!! * * @see setOrthoOff() * @see setViewport(int,int,int,int) */ public static void setOrthoOn() { // prepare projection matrix to render in 2D GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glPushMatrix(); // preserve perspective view GL11.glLoadIdentity(); // clear the perspective matrix GL11.glOrtho( // turn on 2D mode ////viewportX,viewportX+viewportW, // left, right ////viewportY,viewportY+viewportH, // bottom, top !!! 0,viewportW, // left, right 0,viewportH, // bottom, top -500,500); // Zfar, Znear // clear the modelview matrix GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glPushMatrix(); // Preserve the Modelview Matrix GL11.glLoadIdentity(); // clear the Modelview Matrix // disable depth test so further drawing will go over the current scene GL11.glDisable(GL11.GL_DEPTH_TEST); } /** * Set the width and height of the Ortho scene. By default GLApp will use * viewportWidth and viewportHeight for the width and height of ortho mode. * You can override this behavior by setting a specific size with setOrthoDimensions(). *

* By default ortho mode (setOrtho(), setOrthoOn()) will set the world width * and height to exactly match the screen pixel width and height (it uses * the viewport size, which is typically the exact same size as the opengl window). * This makes it easy to map screen positions to the world space. Text positions, * images and mouse coordinates map pixel-for-pixel to the screen. *

* The drawback is that the ortho world space will change size on different * resolution screens. In low-res screens the ortho world may be 800x600 while * in a hi-res screen it could be 1920x1200, which means that anything drawn * in ortho mode may shift on different screens. *

* setOrthoDimensions() assigns a fixed size for ortho scenes, independent of * the viewport size. OpenGL will project the ortho world into the * viewport just as it does with a perspective projection. Things drawn in * ortho will stay in the right place on any resolution screen. *

* The drawback with this method is that screen coordinates don't necessarily * map exactly to the ortho world. To translate a screen xy to the ortho * world coordinates, use getWorldCoordsAtScreen(x,y) just as with perspective * worlds. * * @param width width of ortho scene * @param height height of ortho scene * * @see setOrtho() * @see setOrthoOn() * @see setPerspective() *//* public static void setOrthoDimensions(int width, int height) { orthoWidth = width; orthoHeight = height; } */ /** * Turn 2D mode off. Return the projection and model views to their * preserved state that was saved when setOrthoOn() was called, and * re-enable depth testing. * * @see setOrthoOn() */ public static void setOrthoOff() { // restore the original positions and views GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glPopMatrix(); GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glPopMatrix(); // turn Depth Testing back on GL11.glEnable(GL11.GL_DEPTH_TEST); } /** * Define the position and size of the screen area in which the * OpenGL context will draw. Position and size of the area are given * in exact pixel sizes. By default the viewport is the same size as * the window (displayWidth,displayHeight). *

* NOTE: by default the window size, viewport size and setOrtho() size are all * the same, so in ortho mode screen pixel positions exactly match to world * coordinates. THIS IS NO LONGER TRUE if you setViewport() to some other * size. With a custom viewport you need to use getWorldCoordsAtScreen() * to convert screen xy to world xy. *

* @param x position of the lower left of viewport area, in pixels * @param y * @param width size of the viewport area, in pixels * @param height * * @see setPerspective() * @see setOrtho() * @see setOrthoDimensions(int,int) */ public static void setViewport(int x, int y, int width, int height) { viewportX = x; viewportY = y; viewportW = width; viewportH = height; aspectRatio = (float)width / (float)height; GL11.glViewport(x,y,width,height); } /** * Reset the viewport to full screen (displayWidth x displayHeight). * * @see setViewport(int,int,int,int) */ public static void resetViewport() { setViewport(0,0,displayWidth,displayHeight); } /** * A simple way to set eye position. Calls gluLookat() to place * the viewpoint units up the Z axis from the given target position, * looking at the target position. The camera is oriented vertically (Y axis is up). * away. */ public static void lookAt(float lookatX, float lookatY, float lookatZ, float distance) { // set viewpoint GLU.gluLookAt( lookatX,lookatY,lookatZ+distance, // eye is at the same XY as the target, units up the Z axis lookatX,lookatY,lookatZ, // look at the target position 0,1,0); // the Y axis is up } //======================================================================== // Functions to push/pop OpenGL settings //======================================================================== /** * preserve all OpenGL settings that can be preserved. Use this * function to isolate settings changes. Call pushAttrib() before * calling glEnable(), glDisable(), glMatrixMode() etc. After * your code executes, call popAttrib() to return to the * previous settings. * * For better performance, call pushAttrib() with specific settings * flags to preserve only specific settings. * * @see popAttrib() */ public static void pushAttrib() { GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); } /** * preserve the specified OpenGL setting. Call popAttrib() to return to * the preserved state. * * @see popAttrib() */ public static void pushAttrib(int attribute_bits) { GL11.glPushAttrib(attribute_bits); } /** * preserve the OpenGL settings that will be affected when we draw in ortho * mode over the scene. For example if we're drawing an interface layer, * buttons, popup menus, cursor, text, etc. we need to turn off lighting, * turn on blending, set color to white and turn off depth test. *

* call pushAttribOverlay(), enable settings that you need, when done call popAttrib() * * @see popAttrib() */ public static void pushAttribOrtho() { GL11.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_TEXTURE_BIT | GL11.GL_LIGHTING_BIT | GL11.GL_DEPTH_BUFFER_BIT); } /** * preserve the OpenGL viewport settings. *

	 *       pushAttribViewport();
	 *           setViewport(0,0,displaymode.getWidth(),displaymode.getHeight());
	 *           ... do some drawing outside of previous viewport area
	 *       popAttrib();
	 * 
* * @see popAttrib() */ public static void pushAttribViewport() { GL11.glPushAttrib(GL11.GL_VIEWPORT_BIT); } /** * return to the OpenGL settings that were preserved by the previous pushAttrib() call. * * @see pushAttrib() */ public static void popAttrib() { GL11.glPopAttrib(); } //======================================================================== // Lighting functions //======================================================================== /** * Set the color of a 'positional' light (a light that has a specific * position within the scene).
* * Pass in an OpenGL light number (GL11.GL_LIGHT1), * the 'Diffuse' and 'Ambient' colors (direct light and reflected light), * and the position.
* * @param GLLightHandle * @param diffuseLightColor * @param ambientLightColor * @param position */ public static void setLight( int GLLightHandle, float[] diffuseLightColor, float[] ambientLightColor, float[] specularLightColor, float[] position ) { FloatBuffer ltDiffuse = allocFloats(diffuseLightColor); FloatBuffer ltAmbient = allocFloats(ambientLightColor); FloatBuffer ltSpecular = allocFloats(specularLightColor); FloatBuffer ltPosition = allocFloats(position); GL11.glLight(GLLightHandle, GL11.GL_DIFFUSE, ltDiffuse); // color of the direct illumination GL11.glLight(GLLightHandle, GL11.GL_SPECULAR, ltSpecular); // color of the highlight GL11.glLight(GLLightHandle, GL11.GL_AMBIENT, ltAmbient); // color of the reflected light GL11.glLight(GLLightHandle, GL11.GL_POSITION, ltPosition); GL11.glEnable(GLLightHandle); // Enable the light (GL_LIGHT1 - 7) //GL11.glLightf(GLLightHandle, GL11.GL_QUADRATIC_ATTENUATION, .005F); // how light beam drops off } public static void setSpotLight( int GLLightHandle, float[] diffuseLightColor, float[] ambientLightColor, float[] position, float[] direction, float cutoffAngle ) { FloatBuffer ltDirection = allocFloats(direction); setLight(GLLightHandle, diffuseLightColor, ambientLightColor, diffuseLightColor, position); GL11.glLightf(GLLightHandle, GL11.GL_SPOT_CUTOFF, cutoffAngle); // width of the beam GL11.glLight(GLLightHandle, GL11.GL_SPOT_DIRECTION, ltDirection); // which way it points GL11.glLightf(GLLightHandle, GL11.GL_CONSTANT_ATTENUATION, 2F); // how light beam drops off //GL11.glLightf(GLLightHandle, GL11.GL_LINEAR_ATTENUATION, .5F); // how light beam drops off //GL11.glLightf(GLLightHandle, GL11.GL_QUADRATIC_ATTENUATION, .5F); // how light beam drops off } /** * Set the color of the Global Ambient Light. Affects all objects in * scene regardless of their placement. */ public static void setAmbientLight(float[] ambientLightColor) { put(tmpFloats,ambientLightColor); GL11.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, tmpFloats); } /** * Set the position of a light to the given xyz. NOTE: Positional light only, * not directional. */ public static void setLightPosition(int GLLightHandle, float x, float y, float z) { put(tmpFloats, new float[] {x,y,z,1}); GL11.glLight(GLLightHandle, GL11.GL_POSITION, tmpFloats); } /** * Set the position (or direction) of a light to the given xyz. */ public static void setLightPosition(int GLLightHandle, float[] position) { put(tmpFloats,position); GL11.glLight(GLLightHandle, GL11.GL_POSITION, tmpFloats); } /** * enable/disable the given light. The light handle parameter is one of * the predefined OpenGL light handle numbers (GL_LIGHT1, GL_LIGHT2 ... GL_LIGHT7). */ public static void setLight(int GLLightHandle, boolean on) { if (on) { GL11.glEnable(GLLightHandle); } else { GL11.glDisable(GLLightHandle); } } /** * Enable/disable lighting. If parameter value is false, this will turn off all * lights and ambient lighting. * * NOTE: When lighting is disabled, material colors are disabled as well. Use * glColor() to set color properties when ligthing is off. */ public static void setLighting(boolean on) { if (on) { GL11.glEnable(GL11.GL_LIGHTING); } else { GL11.glDisable(GL11.GL_LIGHTING); } } //======================================================================== // Material functions //======================================================================== public static final float[] colorClear = { 0f, 0f, 0f, 0f}; // alpha is 0 public static final float[] colorBlack = { 0f, 0f, 0f, 1f}; public static final float[] colorWhite = { 1f, 1f, 1f, 1f}; public static final float[] colorGray = { .5f, .5f, .5f, 1f}; public static final float[] colorRed = { 1f, 0f, 0f, 1f}; public static final float[] colorGreen = { 0f, 1f, 0f, 1f}; public static final float[] colorBlue = { 0f, 0f, 1f, 1f}; /** * A simple way to set the current material properties to approximate a * "real" surface. Provide the surface color (float[4]]) and shininess * value (range 0-1). *

* Sets diffuse material color to the surfaceColor and ambient material color * to surfaceColor/2. Based on the shiny value (0-1), sets the specular * property to a color between black (0) and white (1), and sets the * shininess property to a value between 0 and 127. *

* Lighting must be enabled for material colors to take effect. *

* @param surfaceColor - must be float[4] {R,G,B,A} * @param reflection - a float from 0-1 (0=very matte, 1=very shiny) */ public static void setMaterial(float[] surfaceColor, float shiny) { float[] reflect = {shiny,shiny,shiny,1}; // make a shade of gray float[] ambient = {surfaceColor[0]*.5f,surfaceColor[1]*.5f,surfaceColor[2]*.5f,1}; // darker surface color mtldiffuse.put(surfaceColor).flip(); // surface directly lit mtlambient.put(ambient).flip(); // surface in shadow mtlspecular.put(reflect).flip(); // reflected light mtlemissive.put(colorBlack).flip(); // no emissive light // size of reflection int openglShininess = ((int)(shiny*127f)); // convert 0-1 to 0-127 if (openglShininess >= 0 && openglShininess <= 127) { mtlshininess.put(new float[] {openglShininess,0,0,0}).flip(); } applyMaterial(); } /** * Set the four material colors and calls glMaterial() to change the current * material color in OpenGL. Lighting must be enabled for material colors to take effect. * * @param shininess: size of reflection (0 is matte, 127 is pinpoint reflection) */ public static void setMaterial(float[] diffuseColor, float[] ambientColor, float[] specularColor, float[] emissiveColor, float shininess) { mtldiffuse.put(diffuseColor).flip(); // surface directly lit mtlambient.put(ambientColor).flip(); // surface in shadow mtlspecular.put(specularColor).flip(); // reflection color mtlemissive.put(emissiveColor).flip(); // glow color if (shininess >= 0 && shininess <= 127) { mtlshininess.put(new float[] {shininess,0,0,0}).flip(); // size of reflection 0=broad 127=pinpoint } applyMaterial(); } /** * Alter the material opacity by setting the diffuse material color * alpha value to the given value * @para alpha 0=transparent 1=opaque */ public static void setMaterialAlpha(float alpha) { if (alpha < 0) alpha = 0; if (alpha > 1) alpha = 1; mtldiffuse.put(3,alpha).flip(); // alpha value of diffuse color applyMaterial(); } /** * Call glMaterial() to activate these material properties in the OpenGL environment. * These properties will stay in effect until you change them or disable lighting. */ public static void applyMaterial() { GL11.glMaterial(GL11.GL_FRONT, GL11.GL_DIFFUSE, mtldiffuse); GL11.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, mtlambient); GL11.glMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, mtlspecular); GL11.glMaterial(GL11.GL_FRONT, GL11.GL_EMISSION, mtlemissive); GL11.glMaterial(GL11.GL_FRONT, GL11.GL_SHININESS, mtlshininess); } //======================================================================== // Fog //======================================================================== /** * Enable atmospheric fog effect, with the given color and density. *

	 *      setFog(new float[] {.5f,.5f,.5f,1f}, .3f);
	 * 
* * @param fogColor float[4] specifies the RGB fog color value * @param fogDensity float in range 0-1 specifies how opaque the fog will be */ public static void setFog(float[] fogColor, float fogdensity) { put(tmpFloats,fogColor); // turn fog on GL11.glEnable(GL11.GL_FOG); // mode: GL_EXP2 is dense fog, GL_EXP is thinner, GL_LINEAR is very thin GL11.glFogi(GL11.GL_FOG_MODE, GL11.GL_EXP2); // start and end (only apply when fog mode=GL_LINEAR //GL11.glFogf(GL11.GL_FOG_START, 100f); //GL11.glFogf(GL11.GL_FOG_END, 1000f); // color GL11.glFog(GL11.GL_FOG_COLOR, tmpFloats); // density GL11.glFogf(GL11.GL_FOG_DENSITY, fogdensity); // quality GL11.glHint(GL11.GL_FOG_HINT, GL11.GL_NICEST); } /** * Enable/disable fog effect. Does not change the fog settings. */ public static void setFog(boolean on) { if (on) { GL11.glEnable(GL11.GL_FOG); } else { GL11.glDisable(GL11.GL_FOG); } } //======================================================================== // Time functions //======================================================================== public static double getTimeInSeconds() { if (ticksPerSecond == 0) { ticksPerSecond = Sys.getTimerResolution(); } return (((double)Sys.getTime())/(double)ticksPerSecond); } public static double getTimeInMillis() { if (ticksPerSecond == 0) { ticksPerSecond = Sys.getTimerResolution(); } return (double) ((((double)Sys.getTime())/(double)ticksPerSecond) * 1000.0); } /** * Calculate time since we last called updateTimer(). Updates secondsSinceLastFrame and * sets lastFrameTime to the current Sys.getTime(). *

* Called by run() at the beginning of each loop. * * @see run() * @see getSecondsPerFrame() */ public static void updateTimer() { // number of frames to average (about one second) double numToAvg = 50; // calc time elapsed since we last rendered secondsSinceLastFrame = (double)(Sys.getTime() - lastFrameTime) / (double)ticksPerSecond; lastFrameTime = Sys.getTime(); // keep a moving average of frame elapsed times if (secondsSinceLastFrame < 1) { avgSecsPerFrame = (double) ((avgSecsPerFrame*numToAvg)+secondsSinceLastFrame) / (numToAvg+1D); } } /** * Return the moving average of the seconds per frame for the last 50 frames. * Useful when animating in real time. Will provide smoother time deltas * than the secondsSinceLastFrame variable, which holds the exact time elapsed * during the last frame (but may jump or lag as processor load varies). * * @see updateTimer() */ public static double getSecondsPerFrame() { return avgSecsPerFrame; } /** * Return the moving average of the frames per second for the last 50 frames. * * @see updateTimer() */ public static double getFramesPerSecond() { return 1d/avgSecsPerFrame; } //======================================================================== // Load images //======================================================================== /** * Make a blank image of the given size. * @return the new GLImage */ public static GLImage makeImage(int w, int h) { ByteBuffer pixels = allocBytes(w*h*SIZE_INT); return new GLImage(pixels,w,h); } /** * Load an image from the given file and return a GLImage object. * @param image filename * @return the loaded GLImage */ public static GLImage loadImage(String imgFilename) { GLImage img = new GLImage(imgFilename); if (img.isLoaded()) { return img; } return null; } /** * Load an image from the given file and return a ByteBuffer containing ARGB pixels.
* Can be used to create textures.
* @param imgFilename * @return */ public static ByteBuffer loadImagePixels(String imgFilename) { GLImage img = new GLImage(imgFilename); return img.pixelBuffer; } /** * Draw a cursor image textured onto a quad at cursorX,cursorY. The cursor * image must be loaded into a 32x32 texture. This function can be called * after scene is drawn to place a cursor on top of scene. *

* NOTE: the cursor is drawn in screen space, at an absolute screen pixel location * without regard for viewport (temporarily zets viewport to entire screen). *

* See handleEvents() for cursorX cursorY and mouse motion handling. *

* Example: *

     *    int cursorTxtr;
     *
     *    public void setup() {
     *        cursorTxtr = makeTexture("images/cursorCrosshair32.gif"); // image must be 32x32
     *    }
     *
     *    public void draw() {
     *        // render scene
     *        ...
     *        drawCursor(cursorTxtr);
     *    }
     * 
* * @param cursorTextureHandle handle to texture containing 32x32 cursor image */ public static void drawCursor(int cursorTextureHandle) { // set projection matrix to 2D fullscreen GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glPushMatrix(); // preserve perspective view GL11.glLoadIdentity(); // clear the perspective matrix GL11.glOrtho( // set ortho to exactly match screen size 0,displayWidth, // left, right 0,displayHeight, // bottom, top -1,1); // Zfar, Znear // clear the modelview matrix GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glPushMatrix(); // preserve current modelview matrix GL11.glLoadIdentity(); // clear the modelview matrix // preserve current settings then draw cursor GL11.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_TEXTURE_BIT | GL11.GL_LIGHTING_BIT | GL11.GL_VIEWPORT_BIT); { // set viewport to full screen GL11.glViewport(0,0,displayWidth,displayHeight); // tweak settings GL11.glEnable(GL11.GL_TEXTURE_2D); // be sure textures are on GL11.glColor4f(1,1,1,1); // no color GL11.glDisable(GL11.GL_LIGHTING); // no lighting GL11.glDisable(GL11.GL_DEPTH_TEST); // no depth test GL11.glEnable(GL11.GL_BLEND); // enable transparency GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // draw 32x32 cursor image drawQuadZ(cursorTextureHandle, cursorX-15, cursorY-15, 0, 32, 32); } GL11.glPopAttrib(); // restore the previous matrix settings GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glPopMatrix(); GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glPopMatrix(); } /** * OLD function: this drew the cursor only into the current viewport, and * could not handle difffent ortho coordinate systems (ortho had to be * exactly mapped to screen size). * * Draw a cursor image textured onto a quad at cursor position. The cursor * image must be loaded into a texture, then this function can be called * after scene is drawn. Uses glPushAttrib() to preserve the current * drawing state. glPushAttrib() may slow performance down, so in your * app you may want to set the states yourself before calling drawCursor() * and take the push/pop out of here. *

* See handleEvents() for cursorX cursorY and mouse motion handling. *

* Example: *

     *    int cursorTxtr;
     *
     *    public void setup() {
     *        cursorTxtr = makeTexture("images/cursorCrosshair32.gif"); // image must be 32x32
     *    }
     *
     *    public void draw() {
     *        // render scene
     *        ...
     *        drawCursor(cursorTxtr);
     *    }
     * 
* * @param cursorTextureHandle handle to texture containing 32x32 cursor image */ public static void drawCursorOLD(int cursorTextureHandle) { setOrthoOn(); GL11.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_TEXTURE_BIT | GL11.GL_LIGHTING_BIT); { // tweak settings GL11.glEnable(GL11.GL_TEXTURE_2D); // be sure textures are on GL11.glColor4f(1,1,1,1); // no color GL11.glDisable(GL11.GL_LIGHTING); // no lighting GL11.glDisable(GL11.GL_DEPTH_TEST); // no depth test GL11.glEnable(GL11.GL_BLEND); // enable transparency GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); drawQuad(cursorTextureHandle, cursorX-15, cursorY-15, 32, 32); // assumes 32x32 pixels } GL11.glPopAttrib(); setOrthoOff(); } /** * Draw an image in ortho mode (2D) over the entire viewport area. * Converts the image to a texture and maps onto a viewport-sized quad. * Depth test is turned off, lighting is off, color is set to white. * Alpha blending is on, so transparent areas will be respected. *

* NOTE: By default the viewport is the same size as the window so this function * will draw the image over the entire window. If you setViewport() to a * custom size the image will be drawn into the custom viewport area. To * insure that the image is drawn truly full screen, call resetViewport() * before drawImageFullScreen(). *

* @see loadImage(String) * @see setViewport(int,int,int,int) * @see resetViewport() */ public static void drawImageFullScreen(GLImage img) { if (img == null || img.isLoaded() == false) { return; } // if image has no texture, convert the image to a texture if (img.textureHandle <= 0) { img.textureHandle = makeTexture(img); } // Calculate the UV dimensions of the image in the texture float maxU = (float)img.w / (float)img.textureW; float maxV = (float)img.h / (float)img.textureH; // preserve settings pushAttribOrtho(); // switch to 2D projection setOrthoOn(); // tweak settings GL11.glEnable(GL11.GL_TEXTURE_2D); // be sure textures are on GL11.glColor4f(1,1,1,1); // no color GL11.glDisable(GL11.GL_LIGHTING); // no lighting GL11.glDisable(GL11.GL_DEPTH_TEST); // no depth test GL11.glEnable(GL11.GL_BLEND); // enable transparency GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // activate the image texture GL11.glBindTexture(GL11.GL_TEXTURE_2D,img.textureHandle); // draw a textured quad GL11.glBegin(GL11.GL_QUADS); { GL11.glTexCoord2f(0, 0); GL11.glVertex3f(0f, 0f, 0f); // Bottom Left GL11.glTexCoord2f(maxU, 0); GL11.glVertex3f(getWidth(), 0f, 0f); // Bottom Right GL11.glTexCoord2f(maxU, maxV); GL11.glVertex3f(getWidth(), getHeight(), 0f); // Top Right GL11.glTexCoord2f(0, maxV); GL11.glVertex3f(0f, getHeight(), 0f); // Top left } GL11.glEnd(); // return to previous projection mode setOrthoOff(); // return to previous settings popAttrib(); } /** * Draw an image in whichever projection mode is current (does not switch to ortho mode). * Convert the image to a texture and draw onto quad. Will draw with current settings * (light, material, depth, blend, etc.) *
* @see loadImage() * @see drawQuad() * @see drawImageFullScreen() */ public static void drawImage(GLImage img, int x, int y, float w, float h) { // if image has no texture, convert the image to a texture if (img.textureHandle <= 0) { img.textureHandle = makeTexture(img); } // preserve settings pushAttribOrtho(); // set color to white //GL11.glColor4f(1,1,1,1); // don't force color to white (may want to tint image) // activate the image texture GL11.glBindTexture(GL11.GL_TEXTURE_2D,img.textureHandle); // draw a textured quad GL11.glNormal3f(0.0f, 0.0f, 1.0f); // normal faces positive Z GL11.glBegin(GL11.GL_QUADS); { GL11.glTexCoord2f(0f, 0f); GL11.glVertex3f( (float)x, (float)y, (float)0); GL11.glTexCoord2f(1f, 0f); GL11.glVertex3f( (float)x+w, (float)y, (float)0); GL11.glTexCoord2f(1f, 1f); GL11.glVertex3f( (float)x+w, (float)y+h, (float)0); GL11.glTexCoord2f(0f, 1f); GL11.glVertex3f( (float)x, (float)y+h, (float)0); } GL11.glEnd(); // return to previous settings popAttrib(); } /** * Draw a textured quad in Ortho mode (2D) at the given xy, scaled to * the given width and height. Depth test is turned off so quad will * be drawn on top of the current scene. Quad will be drawn * with current light and material if any are active. *
* @ee loadImage() */ public static void drawQuad(int textureHandle, int x, int y, float w, float h) { // activate the specified texture GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle); // prepare to render in 2D setOrthoOn(); // draw the textured quad GL11.glNormal3f(0.0f, 0.0f, 1.0f); // normal faces positive Z GL11.glBegin(GL11.GL_QUADS); { GL11.glTexCoord2f(0f, 0f); GL11.glVertex3f( (float)x, (float)y, (float)0); GL11.glTexCoord2f(1f, 0f); GL11.glVertex3f( (float)x+w, (float)y, (float)0); GL11.glTexCoord2f(1f, 1f); GL11.glVertex3f( (float)x+w, (float)y+h, (float)0); GL11.glTexCoord2f(0f, 1f); GL11.glVertex3f( (float)x, (float)y+h, (float)0); } GL11.glEnd(); // restore the previous perspective and model views setOrthoOff(); } /** * Draw a textured quad at the given xyz position in 3D space. Quad will be drawn * with current settings (ie. light, material, depth test, projection, etc.) */ public static void drawQuadZ(int textureHandle, float x, float y, float z, float w, float h) { GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle); // draw textured quad GL11.glNormal3f(0.0f, 0.0f, 1.0f); // normal faces positive Z GL11.glBegin(GL11.GL_QUADS); { GL11.glTexCoord2f(0f, 0f); GL11.glVertex3f( x, y, z); GL11.glTexCoord2f(1f, 0f); GL11.glVertex3f( x+w, y, z); GL11.glTexCoord2f(1f, 1f); GL11.glVertex3f( x+w, y+h, z); GL11.glTexCoord2f(0f, 1f); GL11.glVertex3f( x, y+h, z); } GL11.glEnd(); } //======================================================================== // Functions to get and set framebuffer pixels //======================================================================== /** * Return a ByteBuffer containing ARGB pixels of the entire screen area. */ public static ByteBuffer framePixels() { return framePixels(0,0,displayMode.getWidth(),displayMode.getHeight()); } /** * Return a ByteBuffer containing ARGB pixels from the given screen area. */ public static ByteBuffer framePixels(int x, int y, int w, int h) { // allocate 4 bytes per pixel ByteBuffer pixels = allocBytes(w*h*4); // Get pixels from frame buffer in ARGB format. GL11.glReadPixels(x,y,w,h, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixels); return pixels; } /** * Return an int array containing ARGB pixels from the given screen area. */ public static int[] framePixelsInt(int x, int y, int w, int h) { int[] pixels = new int[w * h]; ByteBuffer pixelsBB = framePixels(x, y, w, h); get(pixelsBB,pixels); return pixels; } /** * Return the color buffer RGB value at the given screen position as byte[3]. * * @param x screen position * @param y * @return rgb byte array */ public static byte[] getPixelColor(int x, int y) { // color value will be stored in an integer tmpInt.clear(); // read the framebuffer color value at the given position, as bytes GL11.glReadPixels(x, y, 1, 1, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, tmpInt); byte[] rgb = new byte[] {tmpInt.get(0), tmpInt.get(1), tmpInt.get(2)}; return rgb; } /** * Return the depth buffer value at the given screen position. * * @param x screen position * @param y * @return float Z depth value */ public static float getPixelDepth(int x, int y) { return getZDepth(x,y); } /** * Return the stencil buffer value at the given screen position. Stencil values are * typically bytes (0-255). The value will be returned as an integer. * * @param x screen position * @param y * @return int stencil value */ public static int getPixelStencil(int x, int y) { return getMaskValue(x,y); } /** * Save entire screen image to a texture. Will copy entire screen even * if a viewport is in use. Texture param must be large enough to hold * screen image (see makeTextureForScreen()). * * @param txtrHandle texture where screen image will be stored * @see frameDraw() * @see makeTextureForScreen() */ public static void frameCopy(int txtrHandle) { frameCopy(txtrHandle, 0,0, DM.getWidth(),DM.getHeight()); // entire screen } /** * Save a region of the screen to a texture. Texture must be large enough to hold * screen image. * * @param txtrHandle texture where screen region will be stored * @see frameDraw() * @see makeTextureForScreen() */ public static void frameCopy(int txtrHandle, int x, int y, int w, int h) { GL11.glColor4f(1,1,1,1); // turn off alpha and color tints GL11.glReadBuffer(GL11.GL_BACK); GL11.glBindTexture(GL11.GL_TEXTURE_2D,txtrHandle); // Copy screen to texture GL11.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0,0, x,y,w,h); } /** * Draw the screen-sized image over entire screen area. The screen image * is stored in the given texture at 0,0 (see frameCopy()) and has the * same dimensions as the current display mode (DM.getWidth(), DM.getHeight()). *

* Reset the viewport and ortho mode to full screen (viewport may be * different proportion than screen if custom aspectRatio is set). Draw the * quad the same size as texture so no stretching or compression of image. * * @param txtrHandle */ public static void frameDraw(int txtrHandle) { // keep it opaque GL11.glDisable(GL11.GL_BLEND); // set viewport to full screen GL11.glViewport(0, 0, DM.getWidth(), DM.getHeight()); // draw square quad that covers entire screen drawQuad(txtrHandle, 0, 0, screenTextureSize, screenTextureSize); // draw the full screen image // restore viewport to custom aspect ratio GL11.glViewport(viewportX, viewportY, viewportW, viewportH); } /** * Save the current frame buffer to a PNG image. Exactly the same as screenShot(). * @see screenShot() */ public static void frameSave() { screenShot(); } //======================================================================== // Functions to render shapes. //======================================================================== /** * Draw a rectangle outline in ortho mode (draws in 2D over the scene). *
* @see setLineWidth() * @see drawRectZ() */ public static void drawRect(int x, int y, float w, float h) { // switch projection to 2D mode setOrthoOn(); // draw rectangle at Z=0 drawRectZ(x,y,0,w,h); // restore the previous perspective and model views setOrthoOff(); } /** * Draw a rectangle outline in world space. Uses opengl line_strip to make * the rectangle. *
* @see setLineWidth() * @see drawRect() */ public static void drawRectZ(int x, int y, int z, float w, float h) { // preserve current settings GL11.glPushAttrib(GL11.GL_TEXTURE_BIT | GL11.GL_LIGHTING_BIT); // de-activate texture and light GL11.glDisable(GL11.GL_TEXTURE_2D); GL11.glDisable(GL11.GL_LIGHTING); // draw the rectangle GL11.glBegin(GL11.GL_LINE_STRIP); { GL11.glVertex3f( (float)x, (float)y, (float)z); GL11.glVertex3f( (float)x+w, (float)y, (float)z); GL11.glVertex3f( (float)x+w, (float)y+h, (float)z); GL11.glVertex3f( (float)x, (float)y+h, (float)z); GL11.glVertex3f( (float)x, (float)y, (float)z); } GL11.glEnd(); // draw points at the corners GL11.glBegin(GL11.GL_POINTS); { GL11.glVertex3f( (float)x, (float)y, (float)z); GL11.glVertex3f( (float)x+w, (float)y, (float)z); GL11.glVertex3f( (float)x+w, (float)y+h, (float)z); GL11.glVertex3f( (float)x, (float)y+h, (float)z); } GL11.glEnd(); // re-enable settings popAttrib(); } /** * Draws a circle with the given radius centered at the given world position. */ public static void drawCircle(int x, int y, int radius, int linewidth) { // switch projection to 2D mode setOrthoOn(); // draw circle at x,y with z=0 GL11.glPushMatrix(); { GL11.glTranslatef(x,y,0); drawCircle(radius-linewidth, radius, 180); } GL11.glPopMatrix(); // restore the previous perspective and model views setOrthoOff(); } /** * Draws a circle with the given radius centered at the given world position. */ public static void drawCircleZ(int x, int y, int z, int radius, int linewidth) { GL11.glPushMatrix(); { GL11.glTranslatef(x,y,z); drawCircle(radius-linewidth, radius, 180); } GL11.glPopMatrix(); } /** * Draws a circle centered at 0,0,0. Use translate() to place circle at desired coords. * Inner and outer radius specify width, stepsize is number of degrees for each segment. */ public static void drawCircle(float innerRadius, float outerRadius, int numSegments) { int s = 0; // start int e = 360; // end int stepSize = 360/numSegments; // degrees per segment GL11.glBegin(GL11.GL_QUAD_STRIP); { // add first 2 vertices float ts = (float) Math.sin(Math.toRadians(s)); float tc = (float) Math.cos(Math.toRadians(s)); GL11.glVertex2f(tc * innerRadius, ts * innerRadius); GL11.glVertex2f(tc * outerRadius, ts * outerRadius); // add intermediate vertices, snap to {step} degrees while ( (s = ( (s + stepSize) / stepSize) * stepSize) < e) { ts = (float) Math.sin(Math.toRadians(s)); tc = (float) Math.cos(Math.toRadians(s)); GL11.glVertex2f(tc * innerRadius, ts * innerRadius); GL11.glVertex2f(tc * outerRadius, ts * outerRadius); } // add last 2 vertices at end angle ts = (float) Math.sin(Math.toRadians(e)); tc = (float) Math.cos(Math.toRadians(e)); GL11.glVertex2f(tc * innerRadius, ts * innerRadius); GL11.glVertex2f(tc * outerRadius, ts * outerRadius); } GL11.glEnd(); } /** * Render a 2 unit cube centered at origin. Includes texture coordinates * and normals. */ public static void renderCube() { GL11.glBegin(GL11.GL_QUADS); // Front Face GL11.glNormal3f( 0.0f, 0.0f, 1.0f); GL11.glTexCoord2f(0.0f, 0.0f); GL11.glVertex3f(-1.0f, -1.0f, 1.0f); // Bottom Left GL11.glTexCoord2f(1.0f, 0.0f); GL11.glVertex3f( 1.0f, -1.0f, 1.0f); // Bottom Right GL11.glTexCoord2f(1.0f, 1.0f); GL11.glVertex3f( 1.0f, 1.0f, 1.0f); // Top Right GL11.glTexCoord2f(0.0f, 1.0f); GL11.glVertex3f(-1.0f, 1.0f, 1.0f); // Top Left // Back Face GL11.glNormal3f( 0.0f, 0.0f, -1.0f); GL11.glTexCoord2f(1.0f, 0.0f); GL11.glVertex3f(-1.0f, -1.0f, -1.0f); // Bottom Right GL11.glTexCoord2f(1.0f, 1.0f); GL11.glVertex3f(-1.0f, 1.0f, -1.0f); // Top Right GL11.glTexCoord2f(0.0f, 1.0f); GL11.glVertex3f( 1.0f, 1.0f, -1.0f); // Top Left GL11.glTexCoord2f(0.0f, 0.0f); GL11.glVertex3f( 1.0f, -1.0f, -1.0f); // Bottom Left // Top Face GL11.glNormal3f( 0.0f, 1.0f, 0.0f); GL11.glTexCoord2f(0.0f, 1.0f); GL11.glVertex3f(-1.0f, 1.0f, -1.0f); // Top Left GL11.glTexCoord2f(0.0f, 0.0f); GL11.glVertex3f(-1.0f, 1.0f, 1.0f); // Bottom Left GL11.glTexCoord2f(1.0f, 0.0f); GL11.glVertex3f( 1.0f, 1.0f, 1.0f); // Bottom Right GL11.glTexCoord2f(1.0f, 1.0f); GL11.glVertex3f( 1.0f, 1.0f, -1.0f); // Top Right // Bottom Face GL11.glNormal3f( 0.0f, -1.0f, 0.0f); GL11.glTexCoord2f(1.0f, 1.0f); GL11.glVertex3f(-1.0f, -1.0f, -1.0f); // Top Right GL11.glTexCoord2f(0.0f, 1.0f); GL11.glVertex3f( 1.0f, -1.0f, -1.0f); // Top Left GL11.glTexCoord2f(0.0f, 0.0f); GL11.glVertex3f( 1.0f, -1.0f, 1.0f); // Bottom Left GL11.glTexCoord2f(1.0f, 0.0f); GL11.glVertex3f(-1.0f, -1.0f, 1.0f); // Bottom Right // Right face GL11.glNormal3f( 1.0f, 0.0f, 0.0f); GL11.glTexCoord2f(1.0f, 0.0f); GL11.glVertex3f( 1.0f, -1.0f, -1.0f); // Bottom Right GL11.glTexCoord2f(1.0f, 1.0f); GL11.glVertex3f( 1.0f, 1.0f, -1.0f); // Top Right GL11.glTexCoord2f(0.0f, 1.0f); GL11.glVertex3f( 1.0f, 1.0f, 1.0f); // Top Left GL11.glTexCoord2f(0.0f, 0.0f); GL11.glVertex3f( 1.0f, -1.0f, 1.0f); // Bottom Left // Left Face GL11.glNormal3f( -1.0f, 0.0f, 0.0f); GL11.glTexCoord2f(0.0f, 0.0f); GL11.glVertex3f(-1.0f, -1.0f, -1.0f); // Bottom Left GL11.glTexCoord2f(1.0f, 0.0f); GL11.glVertex3f(-1.0f, -1.0f, 1.0f); // Bottom Right GL11.glTexCoord2f(1.0f, 1.0f); GL11.glVertex3f(-1.0f, 1.0f, 1.0f); // Top Right GL11.glTexCoord2f(0.0f, 1.0f); GL11.glVertex3f(-1.0f, 1.0f, -1.0f); // Top Left GL11.glEnd(); } /** * draw a cube with the given size, centered at origin. Include texture coordinates. * @param size length of each side * @param segments # segments to divide each side into */ public static void renderCube(float size, int segments) { float halfsize = size/2f; GL11.glPushMatrix(); { GL11.glPushMatrix(); { GL11.glTranslatef(0,0,halfsize); renderPlane(size,segments);// front } GL11.glPopMatrix(); GL11.glPushMatrix(); { GL11.glRotatef(90,0,1,0); GL11.glTranslatef(0,0,halfsize); renderPlane(size,segments);// right } GL11.glPopMatrix(); GL11.glPushMatrix(); { GL11.glRotatef(180,0,1,0); GL11.glTranslatef(0,0,halfsize); renderPlane(size,segments);// back } GL11.glPopMatrix(); GL11.glPushMatrix(); { GL11.glRotatef(270,0,1,0); GL11.glTranslatef(0,0,halfsize); renderPlane(size,segments);// left } GL11.glPopMatrix(); GL11.glPushMatrix(); { GL11.glRotatef(90,1,0,0); GL11.glTranslatef(0,0,halfsize); renderPlane(size,segments);// bottom } GL11.glPopMatrix(); GL11.glPushMatrix(); { GL11.glRotatef(-90,1,0,0); GL11.glTranslatef(0,0,halfsize); renderPlane(size,segments);// top } GL11.glPopMatrix(); } GL11.glPopMatrix(); } /** * draw a square plane in the X,Y axis, centered at origin. Include texture coordinates. * @param size length of each side * @param segments number of segments to divide each side into */ public static void renderPlane(float size, int segments) { renderPlane(size,size,segments,segments); } /** * draw a rectangular plane in the X,Y axis, centered at origin, with the specified size and * number of divisions. Texture will cover entire rectangle without repeating. * @param length length of X axis side * @param height length of Y axis side * @param segments number of segments to divide each side into */ public static void renderPlane(float length, float height, int length_segments, int height_segments) { renderPlane(length, height, length_segments, height_segments, 1, 1); } /** * draw a rectangular plane in the X,Y axis, centered at origin. Include texture coordinates. * Scale the UV coordinates to same proportion as plane dimensions. Texture will repeat as * specified by the tilefactorU and tilefactorV values. If tilefactor values are 1, the texture * will cover the rectangle without tiling. * @param length length on X axis * @param depth length on Y axis * @param segments number of segments to divide each side into */ public static void renderPlane(float length, float height, int length_segments, int height_segments, float tilefactorU, float tilefactorV) { float xpos = - length/2f; float ypos = - height/2f; float segsizeL = length/(float)length_segments; float segsizeH = height/(float)height_segments; float maxDimension = (length > height)? length : height; float uvsegsizeL = (length/maxDimension) / (float)length_segments; float uvsegsizeH = (height/maxDimension) / (float)height_segments; GL11.glBegin(GL11.GL_QUADS); { GL11.glNormal3f(0f, 0f, 1f); // plane is facing up the Z axis for (int x=0; x < length_segments; x++, xpos+=segsizeL) { for (int y=0; y < height_segments; y++, ypos+=segsizeH) { // bottom left GL11.glTexCoord2f((x*uvsegsizeL)*tilefactorU, (y*uvsegsizeH)*tilefactorV); GL11.glVertex3f( xpos, ypos, 0f); // bottom rite GL11.glTexCoord2f(((x*uvsegsizeL)+uvsegsizeL)*tilefactorU, (y*uvsegsizeH)*tilefactorV); GL11.glVertex3f( xpos+segsizeL, ypos, 0f); // top rite GL11.glTexCoord2f(((x*uvsegsizeL)+uvsegsizeL)*tilefactorU, ((y*uvsegsizeH)+uvsegsizeH)*tilefactorV); GL11.glVertex3f( xpos+segsizeL, ypos+segsizeH, 0f); // top left GL11.glTexCoord2f((x*uvsegsizeL)*tilefactorU, ((y*uvsegsizeH)+uvsegsizeH)*tilefactorV); GL11.glVertex3f( xpos, ypos+segsizeH, 0f); } ypos = - height/2f; // reset column position } } GL11.glEnd(); } /** * draw a rectangular plane in the X,Y axis, centered at origin. Include texture coordinates. * Scale the UV coordinates to same proportion as plane dimensions. * @param length length on X axis * @param depth length on Y axis * @param segments number of segments to divide each side into */ public static void renderPlaneORIG(float length, float height, int length_segments, int height_segments) { float xpos = - length/2f; float ypos = - height/2f; float segsizeL = length/(float)length_segments; float segsizeH = height/(float)height_segments; float maxDimension = (length > height)? length : height; float uvsegsizeL = (length/maxDimension) / (float)length_segments; float uvsegsizeH = (height/maxDimension) / (float)height_segments; GL11.glBegin(GL11.GL_QUADS); { GL11.glNormal3f(0f, 0f, 1f); // plane is facing up the Z axis for (int x=0; x < length_segments; x++, xpos+=segsizeL) { for (int y=0; y < height_segments; y++, ypos+=segsizeH) { // bottom left GL11.glTexCoord2f(x*uvsegsizeL, y*uvsegsizeH); GL11.glVertex3f( xpos, ypos, 0f); // bottom rite GL11.glTexCoord2f((x*uvsegsizeL)+uvsegsizeL, y*uvsegsizeH); GL11.glVertex3f( xpos+segsizeL, ypos, 0f); // top rite GL11.glTexCoord2f((x*uvsegsizeL)+uvsegsizeL, (y*uvsegsizeH)+uvsegsizeH); GL11.glVertex3f( xpos+segsizeL, ypos+segsizeH, 0f); // top left GL11.glTexCoord2f(x*uvsegsizeL, (y*uvsegsizeH)+uvsegsizeH); GL11.glVertex3f( xpos, ypos+segsizeH, 0f); } ypos = - height/2f; // reset column position } } GL11.glEnd(); } /** * call the LWJGL Sphere class to draw sphere geometry * with texture coordinates and normals * @param facets number of divisions around longitude and latitude */ public static void renderSphere(int facets) { Sphere s = new Sphere(); // an LWJGL class s.setOrientation(GLU.GLU_OUTSIDE); // normals point outwards s.setTextureFlag(true); // generate texture coords GL11.glPushMatrix(); { GL11.glRotatef(-90f, 1,0,0); // rotate the sphere to align the axis vertically s.draw(1, facets, facets); // run GL commands to draw sphere } GL11.glPopMatrix(); } /** * draw a sphere with 48 facets (pretty smooth) with normals and texture coords */ public static void renderSphere() { renderSphere(48); } /** * Sets glLineWidth() and glPointSize() to the given width. This will * affect geometry drawn using glBegin(GL_LINES), GL_LINE_STRIP, and GL_POINTS. * May only work with widths up to 10 (depends on hardware). */ public static void setLineWidth(int width) { GL11.glLineWidth(width); GL11.glPointSize(width); //GL11.glEnable(GL11.GL_POINT_SMOOTH); //GL11.glEnable(GL11.GL_LINE_SMOOTH); } /** * Set the current color with RGBA floats in range 0-1. The current color * is disabled when lighting is enabled. When lighting is enabled (glEnable(GL_LIGHTING)) * then material colors are in effect and the current color is ignored. */ public static void setColor(float R, float G, float B, float A) { GL11.glColor4f(R,G,B,A); } /** * Set the current color with RGBA bytes in range 0-255. The current color * is disabled when lighting is enabled. When lighting is enabled (glEnable(GL_LIGHTING)) * then material colors are in effect and the current color is ignored. */ public static void setColorB(int R, int G, int B, int A) { GL11.glColor4ub((byte)R,(byte)G,(byte)B,(byte)A); } /** * Set the current color to the given RGB or RGBA float array. Floats are * in range 0-1. The current color is disabled when lighting is enabled. * When lighting is enabled (glEnable(GL_LIGHTING)) then * material colors are in effect and the current color is ignored. */ public static void setColor(float[] rgba) { if (rgba != null) { if (rgba.length == 4) { GL11.glColor4f(rgba[0],rgba[1],rgba[2],rgba[3]); } else if (rgba.length == 3) { GL11.glColor4f(rgba[0],rgba[1],rgba[2],1); } } } /** * Enable/disable the color-material setting. When enabled, the glColor() command * will change the current material color. This provides a convenient and * efficient way to change material colors without having to call glMaterial(). * When disabled, the glColor() command functions normally (has no affect on * material colors). * * @param on when true, glColor() will set the current material color */ public static void setColorMaterial(boolean on) { if (on) { // glColor() will change the diffuse and ambient material colors GL11.glColorMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT_AND_DIFFUSE); GL11.glEnable(GL11.GL_COLOR_MATERIAL); } else { // glColor() behaves normally GL11.glDisable(GL11.GL_COLOR_MATERIAL); } } //======================================================================== // 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 //======================================================================== static int fontListBase = -1; // Base Display List For The character set static int fontTextureHandle = -1; // Texture handle for character set image /** * 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 static boolean buildFont(String charSetImage, int fontWidth) { // make texture from image GLImage textureImg = loadImage(charSetImage); if (textureImg == null) { return false; // image not found } //pushAttrib(); fontTextureHandle = makeTexture(textureImg); // build character set as call list of 256 textured quads buildFont(fontTextureHandle, fontWidth); //popAttrib(); return true; } /** * 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 static void buildFont(int fontTxtrHandle, int fontWidth) { float factor = 1f/16f; 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 - factor); // Texture Coord (Bottom Left) GL11.glVertex2i(0, 0); GL11.glTexCoord2f(cx + factor, 1 - cy - factor); // Texture Coord (Bottom Right) GL11.glVertex2i(16, 0); GL11.glTexCoord2f(cx + factor, 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 static void destroyFont() { if (fontListBase != -1) { GL11.glDeleteLists(fontListBase,256); fontListBase = -1; } } /** * Render a text string in 2D over the scene, using the character set created * by buildFont(). * * @param x screen pixel position of the string * @param y * @param msg text string to draw */ public static void print(int x, int y, String msg) { print(x,y,msg,0); } /** * Render a text string in 2D over the scene, using the character set created * by buildFont(). * * @param x screen pixel position of the string * @param y * @param msg text string to draw * @param set which of the two character sets: 0 or 1 */ public static void print(int x, int y, String msg, int set) { // if font is not initiallized, try loading default font if (fontListBase == -1 || fontTextureHandle == -1) { if (!buildFont("images/font_tahoma.png", 12)) { err("GLApp.print(): character set has not been created -- see buildFont()"); return; } } if (msg != null) { int offset = fontListBase - 32 + (128 * set); // preserve current GL settings pushAttribOrtho(); // turn off lighting GL11.glDisable(GL11.GL_LIGHTING); // enable alpha blending, so character background is transparent GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // 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 pixel coords) for(int i=0; i * @param width * @param height * @return Pbuffer * @see selectPbuffer(), selectDisplay() */ public static Pbuffer makePbuffer(final int width, final int height) { Pbuffer pbuffer = null; try { pbuffer = new Pbuffer(width, height, new PixelFormat(24, //bitsperpixel 8, //alpha 24, //depth 8, //stencil 0), //samples null, null); } catch (LWJGLException e) { err("GLApp.makePbuffer(): exception " + e); } return pbuffer; } /** * Make the pbuffer the current context for opengl commands. All following * gl functions will operate on this buffer instead of the display. *

* NOTE: the Pbuffer may be recreated if it was lost since last used. It's * a good idea to use: *

     *         pbuff = selectPbuffer(pbuff);
     * 
* to hold onto the new Pbuffer reference if Pbuffer was recreated. * * @param pb pbuffer to make current * @return Pbuffer * @see selectDisplay(), makePbuffer() */ public static Pbuffer selectPbuffer(Pbuffer pb) { if (pb != null) { try { // re-create the buffer if necessary if (pb.isBufferLost()) { int w = pb.getWidth(); int h = pb.getHeight(); msg("GLApp.selectPbuffer(): Buffer contents lost - recreating the pbuffer"); pb.destroy(); pb = makePbuffer(w, h); } // select the pbuffer for rendering pb.makeCurrent(); } catch (LWJGLException e) { err("GLApp.selectPbuffer(): exception " + e); } } return pb; } /** * Make the Display the current context for OpenGL commands. Subsequent * gl functions will operate on the Display. * * @see selectPbuffer() */ public static void selectDisplay() { try { Display.makeCurrent(); } catch (LWJGLException e) { err("GLApp.selectDisplay(): exception " + e); } } /** * Copy the pbuffer contents to a texture. (Should this use glCopyTexSubImage2D()? * Is RGB the fastest format?) */ public static void frameCopy(Pbuffer pbuff, int textureHandle) { GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle); GL11.glCopyTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, 0, 0, pbuff.getWidth(), pbuff.getHeight(), 0); } /** * Save the current frame buffer to a PNG image. Same as * screenShot(filename) but the screenshot filename will be automatically * set to -.png */ public static void screenShot() { screenShot(0, 0, displayMode.getWidth(), displayMode.getHeight(), rootClass.getName() + "-" + makeTimestamp() + ".png"); } /** * Save the current frame buffer to a PNG image. Can also * be used with the PBuffer class to copy large images or textures that * have been rendered into the offscreen pbuffer. */ public static void screenShot(String imageFilename) { screenShot(0, 0, displayMode.getWidth(), displayMode.getHeight(), imageFilename); } /** * Save the current Pbuffer to a PNG image. Same as screenShot(filename) * but the Pbuffer will be saved instead of the framebuffer, and the * screenshot filename will be set to -.png * NOTE: Have to call selectPbuffer() before calling this function. */ public static void screenShot(Pbuffer pb) { screenShot(0, 0, pb.getWidth(), pb.getHeight(), rootClass.getName() + "_" + makeTimestamp() + ".png"); } /** * Save a region of the current render buffer to a PNG image. If the current * buffer is the framebuffer then this will work as a screen capture. Can * also be used with the PBuffer class to copy large images or textures that * have been rendered into the offscreen pbuffer. *

* WARNING: this function hogs memory! Call java with more memory * (java -Xms128m -Xmx128m) *

* @see selectPbuffer(Pbuffer) * @see selectDisplay() * @see savePixelsToPNG() */ public static void screenShot(int x, int y, int width, int height, String imageFilename) { // allocate space for ARBG pixels ByteBuffer framebytes = allocBytes(width * height * SIZE_INT); int[] pixels = new int[width * height]; // grab the current frame contents as ARGB ints (BGRA ints reversed) GL11.glReadPixels(x, y, width, height, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, framebytes); // copy ARGB data from ByteBuffer to integer array framebytes.asIntBuffer().get(pixels, 0, pixels.length); // free up this memory framebytes = null; // flip the pixels vertically and save to file GLImage.savePixelsToPNG(pixels, width, height, imageFilename, true); } /** * Save a ByteBuffer of ARGB pixels to a PNG file. * If flipY is true, flip the pixels on the Y axis before saving. */ public static void savePixelsToPNG(ByteBuffer framebytes, int width, int height, String imageFilename, boolean flipY) { if (framebytes != null && imageFilename != null) { // copy ARGB data from ByteBuffer to integer array int[] pixels = new int[width * height]; framebytes.asIntBuffer().get(pixels, 0, pixels.length); // save pixels to file GLImage.savePixelsToPNG(pixels, width, height, imageFilename, flipY); } } /** * Save the contents of the current render buffer to a PNG image. This is * an older version of screenShot() that used the default OpenGL GL_RGBA * pixel format which had to be swizzled into an ARGB format. I'm * keeping the function here for reference. *

* If the current buffer is the framebuffer then this will work as a screen capture. * Can also be used with the PBuffer class to copy large images or textures that * have been rendered into the offscreen pbuffer. *

* WARNING: this function hogs memory! Call java with more memory * (java -Xms128m -Xmx128) *

* @see selectPbuffer(), selectDisplay() */ public static void screenShotRGB(int width, int height, String saveFilename) { // allocate space for RBG pixels ByteBuffer framebytes = GLApp.allocBytes(width * height * 3); int[] pixels = new int[width * height]; int bindex; // grab a copy of the current frame contents as RGB (has to be UNSIGNED_BYTE or colors come out too dark) GL11.glReadPixels(0, 0, width, height, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, framebytes); // copy RGB data from ByteBuffer to integer array for (int i = 0; i < pixels.length; i++) { bindex = i * 3; pixels[i] = 0xFF000000 // A | ((framebytes.get(bindex) & 0x000000FF) << 16) // R | ((framebytes.get(bindex+1) & 0x000000FF) << 8) // G | ((framebytes.get(bindex+2) & 0x000000FF) << 0); // B } // free up some memory framebytes = null; // save to file (flip Y axis before saving) GLImage.savePixelsToPNG(pixels, width, height, saveFilename, true); } //======================================================================== // Stencil functions //======================================================================== /** * clear the stencil buffer */ public static void clearMask() { GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT); } /** * Begin creating a mask. This function turns off the color and depth buffers * so all subsequent drawing will go only into the stencil buffer. * To use: * beginMask(1); * renderModel(); // draw some geometry * endMask(); */ public static void beginMask(int maskvalue) { // turn off writing to the color buffer and depth buffer GL11.glColorMask(false, false, false, false); GL11.glDepthMask(false); // enable stencil buffer GL11.glEnable(GL11.GL_STENCIL_TEST); // set the stencil test to ALWAYS pass GL11.glStencilFunc(GL11.GL_ALWAYS, maskvalue, 0xFFFFFFFF); // REPLACE the stencil buffer value with maskvalue whereever we draw GL11.glStencilOp(GL11.GL_REPLACE, GL11.GL_REPLACE, GL11.GL_REPLACE); } /** * End the mask. Freeze the stencil buffer and activate the color and depth buffers. */ public static void endMask() { // don't let future drawing modify the contents of the stencil buffer GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); // turn the color and depth buffers back on GL11.glColorMask(true, true, true, true); GL11.glDepthMask(true); } /** * Restrict rendering to the masked area. * To use: * GLStencil.beginMask(1); * renderModel(); * GLStencil.endMask(); */ public static void activateMask(int maskvalue) { // enable stencil buffer GL11.glEnable(GL11.GL_STENCIL_TEST); // until stencil test is disabled, only write to areas where the // stencil buffer equals the mask value GL11.glStencilFunc(GL11.GL_EQUAL, maskvalue, 0xFFFFFFFF); } /** * turn off the stencil test so stencil has no further affect on rendering. */ public static void disableMask() { GL11.glDisable(GL11.GL_STENCIL_TEST); } /** * Return the stencil buffer value at the given screen position. */ public static int getMaskValue(int x, int y) { tmpByte.clear(); // read the stencil value at the given position, as an unsigned byte, store it in tmpByte GL11.glReadPixels(x, y, 1, 1, GL11.GL_STENCIL_INDEX, GL11.GL_UNSIGNED_BYTE, tmpByte); return (int) tmpByte.get(0); } //======================================================================== // Display list functions // // Display lists are OpenGL commands that have been optimized and stored // into memory on the graphics card. They greatly improve rendering // performance but also "freeze" the geometry, so are not suitable in cases // where the geometry has to change dynamically. // // Display lists have to be deleted from the graphics card when // the program exits, or they can accumulate and consume memory. The // function destroyDisplayLists() is called by cleanup() to de-allocate // any display lists that were created by these functions. // //======================================================================== public static ArrayList displayLists = new ArrayList(); // will hold display list IDs created by beginDisplayList() /** * Begin a display list. All following OpenGL geometry commands (up to endDisplayList()) * will be stored in a display list, not drawn to screen. *

* To use, create a display list in setup(): *

	 *      int teapotID = beginDisplayList();
	 *      ... // run teapot render code here
	 *      endDisplayList();
	 * 
* * Then call the display list later in render(): *
	 *      callDisplayList(teapotID);
	 * 
* * @return integer display list id * @see endDisplayList(), callDisplayList(), destroyDisplayList() */ public static int beginDisplayList() { int DL_ID = GL11.glGenLists(1); // Allocate 1 new Display List GL11.glNewList(DL_ID, GL11.GL_COMPILE); // Start Building A List displayLists.add( new Integer(DL_ID) ); // save the list ID so we can delete it later (see destroyDisplayLists()) return DL_ID; } /** * Finish display list creation. Use this function only after calling * beginDisplayList() * * @see beginDisplayList() */ public static void endDisplayList() { GL11.glEndList(); } /** * Render the geometry stored in a display list. Use this function after * calling beginDisplayList() and endDisplayList() to create a display list. * * @see beginDisplayList() * @see endDisplayList() */ public static void callDisplayList(int displayListID) { GL11.glCallList(displayListID); } /** * Delete the given display list ID. Frees up resources on the graphics card. */ public static void destroyDisplayList(int DL_ID) { GL11.glDeleteLists(DL_ID,1); } /** * Clean up the allocated display lists. Called by cleanUp() when app exits. * * @see cleanUp(); */ public static void destroyDisplayLists() { while (displayLists.size() > 0) { int displaylistID = ((Integer)displayLists.get(0)).intValue(); GL11.glDeleteLists(displaylistID,1); displayLists.remove(0); } } //======================================================================== // Native IO Buffer allocation functions // // These functions create and populate the native buffers used by LWJGL. //======================================================================== public static ByteBuffer allocBytes(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_BYTE).order(ByteOrder.nativeOrder()); } public static IntBuffer allocInts(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_INT).order(ByteOrder.nativeOrder()).asIntBuffer(); } public static FloatBuffer allocFloats(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer(); } public static DoubleBuffer allocDoubles(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_DOUBLE).order(ByteOrder.nativeOrder()).asDoubleBuffer(); } public static ByteBuffer allocBytes(byte[] bytearray) { ByteBuffer bb = ByteBuffer.allocateDirect(bytearray.length * SIZE_BYTE).order(ByteOrder.nativeOrder()); bb.put(bytearray).flip(); return bb; } public static IntBuffer allocInts(int[] intarray) { IntBuffer ib = ByteBuffer.allocateDirect(intarray.length * SIZE_FLOAT).order(ByteOrder.nativeOrder()).asIntBuffer(); ib.put(intarray).flip(); return ib; } public static FloatBuffer allocFloats(float[] floatarray) { FloatBuffer fb = ByteBuffer.allocateDirect(floatarray.length * SIZE_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer(); fb.put(floatarray).flip(); return fb; } public static DoubleBuffer allocDoubles(double[] darray) { DoubleBuffer fb = ByteBuffer.allocateDirect(darray.length * SIZE_DOUBLE).order(ByteOrder.nativeOrder()).asDoubleBuffer(); fb.put(darray).flip(); return fb; } public static void put(ByteBuffer b, byte[] values) { b.clear(); b.put(values).flip(); } public static void put(IntBuffer b, int[] values) { b.clear(); b.put(values).flip(); } public static void put(FloatBuffer b, float[] values) { b.clear(); b.put(values).flip(); } public static void put(DoubleBuffer b, double[] values) { b.clear(); b.put(values).flip(); } /** * copy ints from the given byteBuffer into the given int array. * @param b source ByteBuffer * @param values target integer array, must be same length as ByteBuffer capacity/4 */ public static void get(ByteBuffer b, int[] values) { b.asIntBuffer().get(values, 0, values.length); } /** * copy ints from the given IntBuffer into the given int array. * @param b source IntBuffer * @param values target integer array, must be same length as IntBuffer */ public static void get(IntBuffer b, int[] values) { b.get(values, 0, values.length); } /** * return the contents of the byteBuffer as an array of ints. * @param b source ByteBuffer */ public static int[] getInts(ByteBuffer b) { int[] values = new int[b.capacity()/SIZE_INT]; b.asIntBuffer().get(values, 0, values.length); return values; } //======================================================================== // Misc functions //======================================================================== public static URL appletBaseURL = null; public static Class rootClass = GLApp.class; /** * Open the given file and return the InputStream. This function assumes * 1) that we're running an application and the file is in the local filesystem. If not found, then assume * 2) we're in a jar file and look for the file in the current jar. If not found, then assume * 3) we're running an applet and look for the file relative to the applet code base. * @param filename to open */ public static InputStream getInputStream(String filename) { InputStream in = null; // 1) look for file in local filesystem try { in = new FileInputStream(filename); } catch (IOException ioe) { msg("GLApp.getInputStream (" + filename + "): " + ioe); if (in != null) { try { in.close(); } catch (Exception e) {} in = null; } } catch (Exception e) { msg("GLApp.getInputStream (" + filename + "): " + e); } // 2) if couldn't open file, look in jar if (in == null && rootClass != null) { // NOTE: class.getResource() looks for files relative to the folder that the class is in. // ideally the class will be an application in the root of the installation, see setRootClass(). URL u = null; if (filename.startsWith(".")) { // remove leading . ie. "./file" filename = filename.substring(1); } try {u = rootClass.getResource(filename);} catch (Exception ue) {msg("GLApp.getInputStream(): Can't find resource: " + ue);} //msg("GLApp.getInputStream (" +filename+ "): try jar resource url=" + u); if (u != null) { try { in = u.openStream(); } catch (Exception e) { msg("GLApp.getInputStream (" +filename+ "): Can't load from jar: " + e); } } // 3) try loading file from applet base url if (in == null && appletBaseURL != null) { try {u = new URL(appletBaseURL,filename);} catch (Exception ue) {msg("GLApp.getInputStream(): Can't make applet base url: " + ue);} //msg("GLApp.getInputStream (" +filename+ "): try applet base url=" + u); try { in = u.openStream(); } catch (Exception e) { msg("GLApp.getInputStream (" +filename+ "): Can't load from applet base URL: " + e); } } } return in; } /** * Return an array of bytes read from an InputStream. Reads all bytes * until the end of stream. Can read an arbitrary number of bytes. * NOTE: Does not close the inputStream! */ public static byte[] getBytesFromStream(InputStream is) { int chunkSize = 1024; int totalRead = 0; int num = 0; byte[] bytes = new byte[chunkSize]; ArrayList byteChunks = new ArrayList(); // Read the bytes in chunks of 1024 try { while ( (num=is.read(bytes)) >= 0) { byteChunks.add(bytes); bytes = new byte[chunkSize]; totalRead += num; } } catch (IOException ioe) { err("GLApp.getBytesFromStream(): IOException " + ioe); } int numCopied = 0; bytes = new byte[totalRead]; // copy byte chunks to byte array (last chunk may be partial) while (byteChunks.size() > 0) { byte[] byteChunk = (byte[]) byteChunks.get(0); int copylen = (totalRead - numCopied > chunkSize)? chunkSize : (totalRead - numCopied); System.arraycopy(byteChunk, 0, bytes, numCopied, copylen); byteChunks.remove(0); numCopied += copylen; } msg("getBytesFromStream() read " + numCopied + " bytes."); return bytes; } /** * Return an array of bytes read from a file. */ public static byte[] getBytesFromFile(String filename) { InputStream is = getInputStream(filename); byte[] bytes = getBytesFromStream(is); try { is.close(); } catch (IOException ioe) { err("GLApp.getBytesFromFile(): IOException " + ioe); } return bytes; } /** * Return a String array containing the path portion of a filename (result[0]), * and the fileame (result[1]). If there is no path, then result[0] will be "" * and result[1] will be the full filename. */ public static String[] getPathAndFile(String filename) { String[] pathAndFile = new String[2]; Matcher matcher = Pattern.compile("^.*/").matcher(filename); if (matcher.find()) { pathAndFile[0] = matcher.group(); pathAndFile[1] = filename.substring(matcher.end()); } else { pathAndFile[0] = ""; pathAndFile[1] = filename; } return pathAndFile; } /** * Hold onto this Class for later class.getResource() calls (to load * resources from JAR files, see getInputStream()) and also to get class * name for use in screenshot filenames (see screenShot()). *

* To load files from a jar we need to access a class in the root folder * of the installation. It's not good to use GLApp.class because that * class is in the glapp package folder, and the getResource() function will not * find model, image and sound files because they're a level higher in * the folder tree. Below we call this.getClass() to record the class of the * application that subclasses GLApp, ie. assume we create an app MyGame that * extends GLApp, and MyGame.class is in the root folder of the installation: *

     *      MyGame.class
     *      models (folder)
     *      images (folder)
     *      sounds (folder)
     *  
* In this case setRootClass() will set the rootClass to MyGame. If MyGame * and subfolders are packaged in a jar file, then getInputStream() should * be able to do a rootClass.getResource("models/some_model.obj") and correctly * retrieve the file from the JAR. *

* @see getInputStream() */ public void setRootClass() { rootClass = this.getClass(); } /** * make a time stamp for filename * @return a string with format "YYYYMMDD-hhmmss" */ public static String makeTimestamp() { Calendar now = Calendar.getInstance(); int year = now.get(Calendar.YEAR); int month = now.get(Calendar.MONTH) + 1; int day = now.get(Calendar.DAY_OF_MONTH); int hours = now.get(Calendar.HOUR_OF_DAY); int minutes = now.get(Calendar.MINUTE); int seconds = now.get(Calendar.SECOND); String datetime = "" + year + (month < 10 ? "0" : "") + month + (day < 10 ? "0" : "") + day + "-" + (hours < 10 ? "0" : "") + hours + (minutes < 10 ? "0" : "") + minutes + (seconds < 10 ? "0" : "") + seconds; return datetime; } /** * Return a random floating point value between 0 and 1 */ public static float random() { return (float)Math.random(); } /** * Return a random floating point value between 0 and upperbound (not including upperbound) */ public static float random(float upperbound) { return (float)(Math.random()*(double)upperbound); } /** * Return a random integer value between 0 and upperbound (not including upperbound) */ public static int random(int upperbound) { return (int)(Math.random()*(double)upperbound); } /** * Round a float value to the nearest int. */ public static int round(float f) { return Math.round(f); } /** * Return true if the OpenGL context supports the given OpenGL extension. */ public static boolean extensionExists(String extensionName) { if (OpenGLextensions == null) { String[] GLExtensions = GL11.glGetString(GL11.GL_EXTENSIONS).split(" "); OpenGLextensions = new Hashtable(); for (int i=0; i < GLExtensions.length; i++) { OpenGLextensions.put(GLExtensions[i].toUpperCase(),""); } } return (OpenGLextensions.get(extensionName.toUpperCase()) != null); } /** * Show a debug message on the system console (calls System.out.println()). If * showMessages flag is false, does nothing. * @param text */ public static void msg(String text) { if (showMessages) { System.out.println(text); } } /** * Show an error message on the system console (calls System.out.println()). * Does not check showMessages flag. * @param text */ public static void err(String text) { System.out.println(text); } /** * Find a method in the given class with the given method name. Assumes the method * takes no parameters. The returned Method can be executed later using invoke() * (similar to a callback function in C/C++). *

* NOTE: method invocation is very fast for methods that take no parameters. If * the method takes parameters then invoking is much slower than calling the function * directly through code. For this reason and for simplicity I assume there are * no parameters on the function. * * @param object object that has the method we want to invoke * @param methodName name of function that we want to invoke * @return the Method object * @see invoke() */ public static Method method(Object object, String methodName) { Method M = null; try { // Look for a method with the given name and no parameters M = object.getClass().getMethod(methodName, null); } catch (Exception e) { err("GLApp.method(): Can't find method (" +methodName+ "). " + e); } return M; } /** * Similar to the static method() function, this looks for the method in the * GLApp class (or it's subclass). * * @param methodName name of function that we want to invoke * @return the Method object * @see invoke() */ public Method method(String methodName) { return method(this,methodName); } /** * Execute a method on the given object. Assumes the method * takes no parameters. Useful as a callback function. * * @param object (the object to call the method on) * @param method (the method that will be executed) * @see method() */ public static void invoke(Object object, Method method) { if (object != null && method != null){ try { // Call the method with this object as the argument! method.invoke(object, null); } catch (Exception e) { // Error handling System.err.println("GLApp.invoke(): couldn't invoke method " + method.getName() + " on object " + object.getClass().getName()); } } } /** * Similar to the static invoke() function, this execute a method on the * GLApp class or subclass. Assumes the method takes no parameters. * Useful as a callback function. * * @param method (the method that will be executed) * @see method() */ public void invoke(Method method) { if (method != null){ try { // Call the method with this object as the argument! method.invoke(this, null); } catch (Exception e) { // Error handling System.err.println("GLApp.invoke(): couldn't invoke method " + method.getName() + " on object " + this.getClass().getName()); } } } }