package jocode;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.lang.reflect.Method;
import java.net.*;
import java.nio.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.sun.opengl.util.*;
import javax.media.opengl.*;
import javax.media.opengl.glu.*;

/**
 * JOApp is a base class that wraps essential OpenGL functionality, using the JOGL opengl binding.
 * <P>
 * This class is meant to be extended by a subclass.  The subclass only needs to override the 
 * draw() function to render something on the screen.  The setup() function can be overridden to 
 * perform one-time initialization.  See DemoHelloWorld.java for a very basic opengl example.
 * <P>
 * Although this class will typically be extended by a subclass, for test and demo purposes
 * JOApp is a fully functioning app.  If you execute this class it will open a window with an
 * animation of two rotating cubes.
 * <P>
 * To get deeper into this code you can start with the run() function, and the callbacks functions
 * init() and display().
 * <P>
 * napier <at> potatoland <dot> org
 *
 */public class JOApp implements GLEventListener, KeyListener, MouseListener, MouseMotionListener {
    
    //========================================================================
    // Constants
    //========================================================================
    
    // 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;
    
    public static boolean showMessages = true;  // if true, show debug messages, if false show only error messages (see msg() err())
    
    public static Hashtable<String, String> OpenGLextensions;  // will be populated by extensionExists()
    
    public static final float PI_OVER_180  = 0.0174532925f;   // A constant used in navigation: PI/180
    public static final float PI_UNDER_180 = 57.2957795130f;   // A constant used in navigation: 180/PI;
    
    
    //========================================================================
    // Static Attributes
    //========================================================================
    
    public static GL gl;
    public static GLU glu;
    
    public static Properties settings = new Properties();  // holds settings from file JOApp.cfg (see loadSettings())
    
    // Application settings
    // These can be tweaked in main() before calling app.run() to customize app behavior.
    // TODO Implement vsync
    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
    
    // TODO Update this comment
    // Display settings (settings in joapp.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 float aspectRatio = 0;                // aspect ratio of current viewport (defaults to aspect ratio of display).  See initDisplay() and setViewport();
    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)
    
    // DisplayMode chosen by initDisplay()
    // DM and displayMode are the same thing.
    public static GraphicsDevice graphicsDevice;        // hold graphics device (screen) information
    public static DisplayMode DM, origDM;               // hold display mode we set, and the display mode when app first executes
    
    public static Frame frame;
    public static GLCanvas canvas;
    public static String windowTitle = "";
    public static int finishedKey = KeyEvent.VK_ESCAPE;  // App will exit when this key is hit (set to 0 for no key exit)
    public static String configFilename = "JOApp.cfg";   // init() calls loadSettings() to load initial settings from this file (OPTIONAL)
    //public static boolean finished = false;
    public static float rotation = 0f;  // to rotate cubes (just to put something on screen)
    
    public static double lastFrameTime = 0;
    public static double secondsSinceLastFrame = 0;
    public static double ticksPerSecond = 1000000000; 
    public static float avgSecsPerFrame = .01f;
    
    public static GLPbuffer currBuffer;
    public static GLContext bufferContext;
    
    public static int cursorX, cursorY, cursorOffsetY;
    public static boolean[] buttonPressed = new boolean[4];
    public static Robot mouseRobot;
    
    // 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 DoubleBuffer  bufferModelviewMatrix = allocDoubles(16);
    public static DoubleBuffer  bufferProjectionMatrix = allocDoubles(16);
    public static DoubleBuffer  tmpResult = allocDoubles(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(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
    
    // The root location to look for data files (images, models, fonts, etc.)
    public static String dataPrefix = "";

    //========================================================================
    // Application setup functions
    //========================================================================
    
    public static void main(String args[]) {
        JOApp demo = new JOApp();
        demo.run();
    }
    
    /**
     * Build and start the app.
     */
    public JOApp() {
        this("JOCode Application", 640, 480);
    }
    
    /**
     * Build and start the app.
     */
    public JOApp(String _windowTitle) {
        this(_windowTitle, 640, 480);
    }

    /**
     * Build and start the app.
     */
    public JOApp(String _windowTitle, int _displayWidth, int _displayHeight) {
        displayWidth = _displayWidth;
        displayHeight = _displayHeight;
        aspectRatio = (float)displayWidth / (float)displayHeight;
        windowTitle = _windowTitle; 
        
        // hold onto application class in case we need to load images from jar (see getInputStream())
        setRootClass();
    }
    
    /**
     * Runs the application.
     */
    public void run() {
        // load settings from config file (display size, resolution, etc.)
        loadSettings(configFilename);
        initDisplay();
        updateTimer();  // Do this once to init time values to something sane, otherwise the first game loop will report a huge secondsElapsedPerFrame
    }
    
    /**
     * Load configuration settings from optional properties file.
     * File format is:<BR>
     * <PRE>
     * # Comment
     * displayWidth=1024
     * displayHeight=768
     * </PRE>
     *
     * @param configFilename
     */
    public void loadSettings(String configFilename) {
        if (configFilename == null || configFilename.equals("")) {
            return;
        }
        InputStream configFileIn = getInputStream(configFilename);
        settings = new Properties();
        if (configFileIn == null) {
            msg("JOApp.loadSettings() warning: config file " + configFilename + " not found, will use default settings.");
            return;
        }
        else {
            try { settings.load(configFileIn); }
            catch (Exception e) {
                msg("JOApp.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 Java KeyEvent class
            finishedKey = Integer.parseInt(settings.getProperty("finishedKey"));
        }
        if (settings.getProperty("window_title") != null) {
            windowTitle = settings.getProperty("window_title");
        }
        if (settings.getProperty("VSyncEnabled") != null) {
            VSyncEnabled = settings.getProperty("VSyncEnabled").toUpperCase().equals("YES");
        }
    }
    
    /**
     * 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 void initDisplay() {
        graphicsDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        origDM = graphicsDevice.getDisplayMode();  // current display settings
        msg("JOApp.initDisplay(): Current display mode is " + printDisplayMode(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.getBitDepth();
        if (displayFrequency == -1) displayFrequency = origDM.getRefreshRate();
        
        // initialize the frame
        frame = new Frame(windowTitle);
        
        // Create an OpenGL display
        canvas = new GLCanvas(new GLCapabilities());
        canvas.addGLEventListener(this);
        
        final Animator animator = new Animator(canvas);
        animator.start();

        frame.add(canvas);
        frame.setSize(displayWidth, displayHeight);
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent _e) {
                animator.stop();
                destroyFont();
                destroyDisplayLists();
                restoreDisplay();
                
                System.exit(0);
            }
        });
        
        if (fullScreen && graphicsDevice.isFullScreenSupported()) {
            try {
                frame.setUndecorated(true);
                graphicsDevice.setFullScreenWindow(frame);
            } catch (Exception e) {
                err("JOApp.initDisplay(): ERROR Setting fullscreen window");
                frame.setUndecorated(false);
                graphicsDevice.setFullScreenWindow(null);
                fullScreen = false; 
            }
        }
        
        // set display mode
        if (useCurrentDisplay || !fullScreen || !graphicsDevice.isDisplayChangeSupported()) {
            // 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.getBitDepth(), origDM.getRefreshRate())) == null) {
                            err("JOApp.initDisplay(): Can't find a compatible Display Mode!!!");
                        }
                    }
                }
            }
            
            if (DM != origDM) {
                try {
                    msg("JOApp.initDisplay(): Setting display mode to " + printDisplayMode(DM) + " with pixel depth = " + depthBufferBits);
                    graphicsDevice.setDisplayMode(DM);
                } catch( Exception e ) {
                    err("JOApp.initDisplay(): ERROR setting display mode to " + printDisplayMode(DM) + " with pixel depth = " + depthBufferBits);
                    graphicsDevice.setDisplayMode(origDM);
                }
            }
        }
        
        // hold onto viewport and display dimensions
        viewportX = viewportY = 0;
        displayWidth = viewportW = DM.getWidth();
        displayHeight = viewportH = DM.getHeight();
        displayColorBits = DM.getBitDepth();
        displayFrequency = DM.getRefreshRate();
        
        frame.setVisible(true);
        canvas.requestFocus();
        
        // calculate the cursor offset
        cursorOffsetY = DM.getHeight() - canvas.getHeight();
    }

    /**
     * Retrieve a DisplayMode object with the given params
     */
    public static DisplayMode getDisplayMode(int w, int h, int colorBits, int freq) {
        DisplayMode allDisplayModes[] = graphicsDevice.getDisplayModes();
        DisplayMode dm = null;
        for (int j = 0; j < allDisplayModes.length; j++) {
            dm = allDisplayModes[j];
            if (dm.getWidth() == w && dm.getHeight() == h && dm.getBitDepth() == colorBits && dm.getRefreshRate() == freq) {
                return dm;
            }
        }

        return null;
    }
    
    /**
     * Restore display settings back to original
     */
    public static void restoreDisplay() {
        if (fullScreen) {
            msg("JOApp.restoreDisplay(): Restoring fullscreen window");
            graphicsDevice.setFullScreenWindow(null);
        }
        if (DM != origDM) {
            msg("JOApp.restoreDisplay(): Restoring display mode to " + printDisplayMode(DM));
            graphicsDevice.setDisplayMode(origDM);
        }
    }
    
    /**
     * Print DisplayMode information
     */
    public static String printDisplayMode(DisplayMode dm) {
        return dm.getWidth() + " x " + dm.getHeight() + " x " + dm.getBitDepth() + " @" + dm.getRefreshRate() + "Hz";
    }

    
    //========================================================================
    // GL callback functions
    //========================================================================

    /**
     * Initialise the environment.
     * Called automatically on startup.
     */
    public void init(GLAutoDrawable _glDrawable) {
        gl = _glDrawable.getGL();
        glu = new GLU();
        
        // init input callbacks
        _glDrawable.addKeyListener(this);
        _glDrawable.addMouseListener(this);
        _glDrawable.addMouseMotionListener(this);
        
        initGL();
        setup();
    }

    /**
     * Render the scene.
     * Called automatically on startup, then by the Animator on every frame.
     */    
    public void display(GLAutoDrawable _glDrawable) {
        gl = _glDrawable.getGL();
        
        updateTimer();
        update();
        draw();
    }

    /** Called when the display mode has been changed.  <B>!! CURRENTLY UNIMPLEMENTED IN JOGL !!</B>
     * @param _glDrawable The GLDrawable object.
     * @param _modeChanged Indicates if the video mode has changed.
     * @param _deviceChanged Indicates if the video device has changed.
     */
    public void displayChanged(GLAutoDrawable _glDrawable, boolean _modeChanged, boolean _deviceChanged) {}

    /** Called by the drawable during the first repaint after the component has 
     * been resized. This function calls reshape(newWidth,newHeight) which the app
     * can override if desired to control resize event behavior.
     *  
     * @param _glDrawable The GLDrawable object.
     * @param _x The X Coordinate of the viewport rectangle.
     * @param _y The Y coordinate of the viewport rectanble.
     * @param _width The new width of the window.
     * @param _height The new height of the window.
     * 
     * @see reshape(int,int)
     */
    public void reshape(GLAutoDrawable _glDrawable, int _x, int _y, int _width, int _height) {
        gl = _glDrawable.getGL();
        reshape(_glDrawable.getWidth(), _glDrawable.getHeight());
    }
    
    /**
     * Respond to a resize event.  Reshape() is called on the first repaint after the component has 
     * been resized.  The client can override reshape() to reset perspective, ortho, viewport or anything
     * else that could be affected by a change to the aspect ratio and size of the opengl display.
     * By default reset the viewport to the full display area and reset perspective.
     * 
     * @param newDisplayWidth    width of the new display
     * @param newDisplayHeight   height of the new display
     * 
     * @see reshape(GLAutoDrawable,int,int,int,int)
     */
    public void reshape(int newDisplayWidth, int newDisplayHeight) {
    	setViewport(0,0,newDisplayWidth,newDisplayHeight);
    	setPerspective();
    }
    
    // key events
    public void keyPressed(KeyEvent _event) {
        if (_event.getKeyCode() == finishedKey)
            System.exit(0);
    }
    public void keyReleased(KeyEvent _event) {}
    public void keyTyped(KeyEvent _event) {}
    
    // mouse events
    public void mouseClicked(MouseEvent _event) {}
    public void mousePressed(MouseEvent _event) { 
        buttonPressed[_event.getButton()] = true;
    }
    public void mouseReleased(MouseEvent _event) {
        buttonPressed[_event.getButton()] = false;
    }
    public void mouseEntered(MouseEvent _event) {}
    public void mouseExited(MouseEvent _event) {}
    
    /**
     * 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 buttonPressed[whichButton]; 
    }
    
    // mouse motion events
    public void mouseMoved(MouseEvent _event) {
        cursorX = _event.getX();
        cursorY = displayHeight - _event.getY() - cursorOffsetY;
    }
    public void mouseDragged(MouseEvent _event) {
        cursorX = _event.getX();
        cursorY = displayHeight - _event.getY() - cursorOffsetY;
    }
    
    
    //========================================================================
    // Init functions
    //========================================================================
    
    public static void begin() {
        // update the timer
        updateTimer();
        
        pushAttrib();
        gl.glFrontFace(GL.GL_CCW);
        
        // save the current positions and views
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glPushMatrix();
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glPushMatrix();
        
        initGL();
    }
    
    public static void end() {
        // restore the original positions and views
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glPopMatrix();
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glPopMatrix();
        
        gl.glFrontFace(GL.GL_CW);
        popAttrib();
    }
    
    /**
     * Initialise the OpenGL view.
     */
    public static void initGL() {
        // Depth test setup
        gl.glEnable(GL.GL_DEPTH_TEST); // Enables Depth Testing
        gl.glDepthFunc(GL.GL_LEQUAL);  // The Type Of Depth Testing To Do

        // Some basic settings
        gl.glClearColor(0f, 0f, 0f, 1f); // Black Background
        gl.glEnable(GL.GL_NORMALIZE);  // force normal lengths to 1
        gl.glEnable(GL.GL_CULL_FACE);  // don't render hidden faces
        gl.glEnable(GL.GL_TEXTURE_2D); // use textures
        gl.glEnable(GL.GL_BLEND);      // enable transparency
        
        // How to handle transparency: average colors together
        gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.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
        gl.glEnable(GL.GL_ALPHA_TEST);
        gl.glAlphaFunc(GL.GL_GREATER, 0f);

        // Draw specular highlghts on top of textures
        gl.glLightModeli(GL.GL_LIGHT_MODEL_COLOR_CONTROL, GL.GL_SEPARATE_SPECULAR_COLOR );

        // Perspective quality
        gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);

        // Set the size and shape of the screen area
        gl.glViewport(viewportX, viewportY, viewportW, viewportH);

        // setup perspective (see setOrtho() for 2D)
        setPerspective(40f, 1f, 1000f);

        // select model view for subsequent transformations
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
    }
    
    /**
     *  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() {}
    
    
    //========================================================================
    // Looping functions
    //========================================================================
    
    /**
     *  Update can be overridden by the subclass.
     */
    public void update() {}

    /**
     * Draws 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 += 90 / getFramesPerSecond();
        
        // clear depth buffer and color
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

        // select model view for subsequent transforms
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.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
        gl.glPushMatrix();
        {
            gl.glRotatef(rotation, 0, 1, 0);
            gl.glColor4f(0f, .5f, 1f, 1f);
            renderCube();
        }
        gl.glPopMatrix();

        // draw another overlapping cube
        gl.glPushMatrix();
        {
            gl.glRotatef(rotation, 1, 1, 1);
            gl.glColor4f(.7f, .5f, 0f, 1f);
            renderCube();
        }
        gl.glPopMatrix();
    }
    
    
    //========================================================================
    // Properties functions
    //========================================================================

    /**
     * Load configuration settings from a properties file.
     * File format is:<BR>
     * <PRE>
     * # Comment
     * displayWidth=1024
     * displayHeight=768
     * </PRE>
     *
     * @param configFilename
     */
    public static Properties loadPropertiesFile(String configFilename) {
        Properties props = new Properties();
        try { settings.load(getInputStream(configFilename)); }
        catch (Exception e) {
            msg("JOApp.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.
     * <P>
     * 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.
     * <P>
     * 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;
    }
    
    
    //========================================================================
    // Basic application behaviour functions
    //========================================================================
    
    /**
     * 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) {
        gl.glClearColor(R, G, B, 1);
    }
    
    /**
     *  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.
     *  <P>
     *  If param is false, reset the cursor to the default.
     *
     *  @see disableHardwareCursor()
     */
    public void hideNativeCursor(boolean hide) {
        if (hide) {
            // hide the cursor
            int w = 1;
            int h = 1;
            int pix[] = new int[w * h];

            Image image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(w, h, pix, 0, w));
            Cursor cursor = Toolkit.getDefaultToolkit().createCustomCursor(image, new Point(0, 0), "custom");

            canvas.setCursor(cursor);
            
        } else {
            // show the standard cursor
            canvas.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
        }
    }

    /**
     * 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 void setCursorPosition(int screenX, int screenY) {
        if (mouseRobot == null) {
            try {
                mouseRobot = new Robot();
            } catch (Exception e) {
                err("setCursorPosition(): ERROR creating Robot");
            }
        }
        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();
        gl.glGetIntegerv(whichSetting, tmpInts);
        return tmpInt.get(0);
    }

    public static DoubleBuffer getModelviewMatrix() {
        bufferModelviewMatrix.clear();
        gl.glGetDoublev(GL.GL_MODELVIEW_MATRIX, bufferModelviewMatrix);
        return bufferModelviewMatrix;
    }

    public static DoubleBuffer getProjectionMatrix() {
        bufferProjectionMatrix.clear();
        gl.glGetDoublev(GL.GL_PROJECTION_MATRIX, bufferProjectionMatrix);
        return bufferProjectionMatrix;
    }

    public static IntBuffer getViewport() {
        bufferViewport.clear();
        gl.glGetIntegerv(GL.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();
        gl.glReadPixels(x, y, 1, 1, GL.GL_DEPTH_COMPONENT, GL.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) {
        glu.gluProject(x, y, z, getModelviewMatrix(), getProjectionMatrix(), getViewport(), tmpResult);
        resultf[0] = (float)tmpResult.get(0);
        resultf[1] = (float)tmpResult.get(1);
        resultf[2] = (float)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] = (float)tmpResult.get(0);
        resultf[1] = (float)tmpResult.get(1);
        resultf[2] = (float)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);
        gl.glGenTextures(1, textureHandle);
        return textureHandle.get(0);
    }
    
    /**
     * "Select" the given texture for further texture operations.
     */
    public static void activateTexture(int textureHandle) {
        gl.glBindTexture(GL.GL_TEXTURE_2D, textureHandle);
    }
    
    /**
     * Create a texture and mipmap from the given image file.
     */
    public static int makeTexture(String textureImagePath) {
        int textureHandle = 0;
        JOImage 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;
        JOImage 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(JOImage textureImg) {
        if ( textureImg != null ) {
        	return makeTexture(textureImg.pixelBuffer, textureImg.w, textureImg.h, false);
        }
        return 0;
    }

    /**
     * De-allocate the given texture (glDeleteTextures()).
     */
    public static void deleteTexture(int textureHandle) {
        gl.glDeleteTextures(1, new int[] { textureHandle }, 0);
    }

    /**
     *  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.<BR>
     * Configure the texture to repeat in both directions and use LINEAR for magnification.
     * <P>
     * @return  the texture handle
     */
    public static int makeTexture(int[] pixelsARGB, int w, int h, boolean anisotropic) {
        if (pixelsARGB != null) {
            ByteBuffer pixelsRGBA = JOImage.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.
     * <P>
     * @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)
        gl.glPushAttrib(GL.GL_TEXTURE_BIT);
        // 'select' the new texture by it's handle
        gl.glBindTexture(GL.GL_TEXTURE_2D,textureHandle);
        // set texture parameters
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); //gl.GL_NEAREST);
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); //gl.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.
            gl.glGetFloatv(GL.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, max_a);
            // Set up the anisotropic filter.
            gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAX_ANISOTROPY_EXT, max_a.get(0));
        }

        // Create the texture from pixels
        gl.glTexImage2D(GL.GL_TEXTURE_2D,
                0,                      // level of detail
                GL.GL_RGBA8,          // internal format for texture is RGB with Alpha
                w, h,                   // size of texture image
                0,                      // no border
                GL.GL_RGBA,           // incoming pixel format: 4 bytes in RGBA order
                GL.GL_UNSIGNED_BYTE,  // incoming pixel data type: unsigned bytes
                pixels);                // incoming pixels

        // restore previous texture settings
        gl.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.
     * <P>
     * Configure the texture to repeat in both directions and use LINEAR for magnification.
     * <P>
     * 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.
     * <P>
     * @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)?
                GL.GL_UNSIGNED_INT_8_8_8_8 :
                GL.GL_UNSIGNED_INT_8_8_8_8_REV;
        // get a new empty texture
        int textureHandle = allocateTexture();
        // 'select' the new texture by it's handle
        gl.glBindTexture(GL.GL_TEXTURE_2D,textureHandle);
        // set texture parameters
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); //gl.GL_NEAREST);
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); //gl.GL_NEAREST);
        // Create the texture from pixels
        gl.glTexImage2D(GL.GL_TEXTURE_2D,
                0,                      // level of detail
                GL.GL_RGBA8,            // internal format for texture is RGB with Alpha
                w, h,                   // size of texture image
                0,                      // no border
                GL.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, JOImage textureImg) {
        int ret = 0;
        if (textureImg != null && textureImg.isLoaded()) {
            gl.glBindTexture(GL.GL_TEXTURE_2D, textureHandle);
            ret = glu.gluBuild2DMipmaps(GL.GL_TEXTURE_2D, GL.GL_RGBA8,
                    textureImg.w, textureImg.h,
                    GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, textureImg.getPixelBytes());
            if (ret != 0) {
                err("JOApp.makeTextureMipMap(): Error occured while building mip map, ret=" + ret + " error=" + glu.gluErrorString(ret));
            }
            // Assign the mip map levels and texture info
            gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_NEAREST);
            gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);
        }
        return ret;
    }

    /**
     * Create a texture the same size as 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 screenW, int screenH) {
        // get a texture size big enough to hold screen (512, 1024, 2048 etc.)
        //screenTextureSize = getPowerOfTwoBiggerThan(screenSize);
        //msg("JOApp.makeTextureForScreen(): made texture for screen with size " + screenTextureSize);
        // get a new empty texture
        int textureHandle = allocateTexture();
        ByteBuffer pixels = allocBytes(screenW*screenH*SIZE_INT);
        // 'select' the new texture by it's handle
        gl.glBindTexture(GL.GL_TEXTURE_2D,textureHandle);
        // set texture parameters
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);
        // use GL_NEAREST to prevent blurring during frequent screen restores
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
        // Create the texture from pixels: use GL_RGBA8 to insure exact color copy
        gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA8, screenW, screenH, 0, GL.GL_RGBA, GL.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.
     * <P>
     * @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)?
                                        GL.GL_UNSIGNED_INT_8_8_8_8 :
                                        GL.GL_UNSIGNED_INT_8_8_8_8_REV;

        // "select" the texture that we'll write into
        gl.glBindTexture(GL.GL_TEXTURE_2D, textureHandle);

        // Copy pixels to texture
        gl.glTexSubImage2D(
                GL.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
                GL.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(JOImage img, int textureHandle) {
        copyPixelsToTexture(img.pixelBuffer, img.w, img.h, textureHandle);
    }
 
    
    //========================================================================
    // Projection functions
    //========================================================================
    
    /**
     * Set OpenGL to render in 3D perspective.  The field of view parameter 
     * sets what angle of the scene will be visible, for example 20 will 
     * show a small area of the scene, 150 will be like a wide angle lens.
     * Will set the Z clipping planes to 1 and 1000.  For more control call
     * setPerspective(float field_of_view, float z_plane_close, float z_plane_far).
     * 
     * @param field_of_view  angle defines how wide the view of the scene will be
     * 
     * @see setPerspective(float,float,float)
     * @see setViewport(int,int,int,int)
     */
    public static void setPerspective(float field_of_view) {
    	setPerspective(field_of_view, 1, 1000);
    }

    /**
     * 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.  Like the lens on a camera, it
     * defines how wide the field of vision is, how deep the scene is, and what
     * the aspect ratio of the rendered scene will be.  The aspect ratio comes
     * from the current viewport dimensions. 
     * 
     * @param field_of_view  angle defines how wide the view of the scene will be
     * @param z_plane_close  how close to the camera should geometry be rendered
     * @param z_plane_far    how far from the camera should geometry be rendered
     * 
     * @see setViewport(int,int,int,int)
     */
    public static void setPerspective(float field_of_view, float z_plane_close, float z_plane_far) {
        // select projection matrix (controls perspective)
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluPerspective(field_of_view, (float)viewportW / (float)viewportH, z_plane_close, z_plane_far);
        // return to modelview matrix
        gl.glMatrixMode(GL.GL_MODELVIEW);
    }

    /**
     * Sets opengl to render in perspective with generally useful settings.
     * Field of View is 40, close Z clipping plane is 1, far Z clipping plane is 1000.
     * The subclass may override this to customize scene perspective. 
     * 
     * @see setPerspective(float field_of_view, float z_plane_close, float z_plane_far)
     * @see setOrtho()
     */
    public void setPerspective() {
        setPerspective(40f, 1f, 1000f);
    }

    /**
     * Sets opengl to render without perspective ("orthogonal" rendering, for example 
     * if you want to draw a border around the screen).  By default this will create 
     * a one-to-one relation between screen pixels and world coordinates.  
     * The subclass may override this to customize ortho settings.
     * 
     * @see setOrtho(int x, int y, int width, int height)
     * @see setPerspective()
     */
    public void setOrtho() {
    	setOrtho(0,0,viewportW,viewportH);
    }

    /**
     * Set OpenGL to render in flat 2D (no perspective) within the current viewport.  
     * By default this will create 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.  HOWEVER if the 
     * viewport has been changed so it is not the same size as the full window then
     * ortho will map into the viewport, not the display.
     */
    public static void setOrtho(int x, int y, int width, int height) {
        // select projection matrix (controls view on screen)
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        // set ortho to same size as viewport, positioned at 0,0
        gl.glOrtho(
                x, width,    // left,right
                y, height,   // bottom,top
                -500, 500);  // Zfar, Znear
        // return to modelview matrix
        gl.glMatrixMode(GL.GL_MODELVIEW);
    }

    /**
     * Set OpenGL to render in flat 2D (no perspective) on top of current scene.
     * Preserves 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.
     * Ortho can be turned off and previous projection restored with setOrthoOff().
     * <P>
     * 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 mapping mouse location to world
     * coordinates, but be warned: if you setViewport() to something smaller than 
     * the full display dimensions then you need to use getWorldCoordsAtScreen() 
     * to convert screen xy to world xy.
     * <P>
     * 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
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glPushMatrix();                   // preserve perspective view
        gl.glLoadIdentity();                 // clear the perspective matrix
        gl.glOrtho(                          // turn on 2D mode
                0, viewportW,  // left, right
                0, viewportH,  // bottom, top
                -500, 500);    // Zfar, Znear
        // clear the modelview matrix
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glPushMatrix();                   // Preserve the Modelview Matrix
        gl.glLoadIdentity();                 // clear the Modelview Matrix
        // disable depth test so further drawing will go over the current scene
        gl.glDisable(GL.GL_DEPTH_TEST);
    }

    /**
     * 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
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glPopMatrix();
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glPopMatrix();
        // turn Depth Testing back on
        gl.glEnable(GL.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).
     * <P>
     * 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.
     * <P>
     * @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;
        gl.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 <distance> 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, <distance> 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() {
        gl.glPushAttrib(GL.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) {
        gl.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.
     * <P>
     * call pushAttribOverlay(), enable settings that you need, when done call popAttrib()
     *
     * @see popAttrib()
     */
    public static void pushAttribOrtho() {
        gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_TEXTURE_BIT | GL.GL_LIGHTING_BIT | GL.GL_DEPTH_BUFFER_BIT);
    }

    /**
     * preserve the OpenGL viewport settings.
     * <pre>
     *       pushAttribViewport();
     *           setViewport(0,0,displaymode.getWidth(),displaymode.getHeight());
     *           ... do some drawing outside of previous viewport area
     *       popAttrib();
     * </pre>
     *
     * @see popAttrib()
     */
    public static void pushAttribViewport() {
        gl.glPushAttrib(GL.GL_VIEWPORT_BIT);
    }

    /**
     * return to the OpenGL settings that were preserved by the previous pushAttrib() call.
     *
     * @see pushAttrib()
     */
    public static void popAttrib() {
        gl.glPopAttrib();
    }
    
    
    //========================================================================
    // Lighting functions
    //========================================================================

    /**
     * Set the color of a 'positional' light (a light that has a specific
     * position within the scene).  <BR>
     *
     * Pass in an OpenGL light number (GL.GL_LIGHT1),
     * the 'Diffuse' and 'Ambient' colors (direct light and reflected light),
     * and the position.<BR>
     *
     * @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);
        gl.glLightfv(GLLightHandle, GL.GL_DIFFUSE, ltDiffuse);   // color of the direct illumination
        gl.glLightfv(GLLightHandle, GL.GL_SPECULAR, ltSpecular); // color of the highlight
        gl.glLightfv(GLLightHandle, GL.GL_AMBIENT, ltAmbient);   // color of the reflected light
        gl.glLightfv(GLLightHandle, GL.GL_POSITION, ltPosition);
        gl.glEnable(GLLightHandle);   // Enable the light (GL_LIGHT1 - 7)
        //GL.glLightf(GLLightHandle, GL.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);
        gl.glLightf(GLLightHandle, GL.GL_SPOT_CUTOFF, cutoffAngle);   // width of the beam
        gl.glLightfv(GLLightHandle, GL.GL_SPOT_DIRECTION, ltDirection);    // which way it points
        gl.glLightf(GLLightHandle, GL.GL_CONSTANT_ATTENUATION, 2F);    // how light beam drops off
        //gl.glLightf(GLLightHandle, gl.GL_LINEAR_ATTENUATION, .5F);    // how light beam drops off
        //gl.glLightf(GLLightHandle, gl.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);
        gl.glLightModelfv(GL.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});
        gl.glLightfv(GLLightHandle, GL.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);
        gl.glLightfv(GLLightHandle, GL.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) {
            gl.glEnable(GLLightHandle);
        }
        else {
            gl.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) {
            gl.glEnable(GL.GL_LIGHTING);
        }
        else {
            gl.glDisable(GL.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).
    *  <P>
    *  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.
    *  <P>
    *  Lighting must be enabled for material colors to take effect.
    *  <P>
    *  @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() {
        gl.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, mtldiffuse);
        gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, mtlambient);
        gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, mtlspecular);
        gl.glMaterialfv(GL.GL_FRONT, GL.GL_EMISSION, mtlemissive);
        gl.glMaterialfv(GL.GL_FRONT, GL.GL_SHININESS, mtlshininess);
    }


    //========================================================================
    // Fog
    //========================================================================

    /**
     * Enable atmospheric fog effect, with the given color and density.
     * <PRE>
     *      setFog(new float[] {.5f,.5f,.5f,1f}, .3f);
     * </PRE>
     *
     * @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
        gl.glEnable(GL.GL_FOG);
        // mode: GL_EXP2 is dense fog, GL_EXP is thinner, GL_LINEAR is very thin
        gl.glFogi(GL.GL_FOG_MODE, GL.GL_EXP2);
        // start and end (only apply when fog mode=GL_LINEAR
        //gl.glFogf(gl.GL_FOG_START, 100f);
        //gl.glFogf(gl.GL_FOG_END, 1000f);
        // color
        gl.glFogfv(GL.GL_FOG_COLOR, tmpFloats);
        // density
        gl.glFogf(GL.GL_FOG_DENSITY, fogdensity);
        // quality
        gl.glHint(GL.GL_FOG_HINT, GL.GL_NICEST);
    }


    /**
     * Enable/disable fog effect.  Does not change the fog settings.
     */
    public static void setFog(boolean on) {
        if (on) {
            gl.glEnable(GL.GL_FOG);
        }
        else {
            gl.glDisable(GL.GL_FOG);
        }
    }
    
    
    //========================================================================
    // Time functions
    //========================================================================
    
    public long getTimeInSeconds() {
        return (long)(System.nanoTime() / ticksPerSecond);
    }

    public long getTimeInMillis() {
        return (long)((System.nanoTime() / ticksPerSecond) * 1000.0);
    }
    
    /**
     *  Calculate time since we last called updateTimer().  Updates secondsSinceLastFrame and
     *  sets lastFrameTime to the current Sys.getTime().
     *  <P>
     *  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)
        float numToAvg = 50;
        // calc time elapsed since we last rendered
        secondsSinceLastFrame = (System.nanoTime() - lastFrameTime) / ticksPerSecond;
        lastFrameTime = System.nanoTime();
        // keep a moving average of frame elapsed times
        if (secondsSinceLastFrame < 1) {
            avgSecsPerFrame = (float)((avgSecsPerFrame*numToAvg)+secondsSinceLastFrame) / (numToAvg+1f);
        }   
    }

    /**
     * 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 float getSecondsPerFrame() {
        return avgSecsPerFrame;
    }

    /**
     * Return the moving average of the frames per second for the last 50 frames.
     *
     * @see updateTimer()
     */
    public float getFramesPerSecond() {
        return 1f/avgSecsPerFrame;
    }
    
    
    //========================================================================
    // Image Loading functions
    //========================================================================
    
    /**
     * Make a blank image of the given size.
     * @return  the new JOImage
     */
    public static JOImage makeImage(int w, int h) {
        ByteBuffer pixels = allocBytes(w*h*SIZE_INT);
        return new JOImage(pixels, w, h);
    }
    
    /**
     * Load an image from the given file and return a JOImage object.
     * @param image filename
     * @return the loaded JOImage
     */
    public static JOImage loadImage(String imgFilename) {
        JOImage img = new JOImage(imgFilename);
        if (img.isLoaded()) {
            return img;
        }
        return null;
    }

    /**
     * Load an image from the given file and return a ByteBuffer containing ARGB pixels.<BR>
     * Can be used to create textures. <BR>
     * @param imgFilename
     * @return
     */
    public static ByteBuffer loadImagePixels(String imgFilename) {
        JOImage img = new JOImage(dataPrefix + 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.
     * <P>
     * NOTE: the cursor is drawn in screen space, at an absolute screen pixel location
     * without regard for viewport (temporarily zets viewport to entire screen).
     * <P>
     * See handleEvents() for cursorX cursorY and mouse motion handling.
     * <P>
     * Example:
     * <PRE>
     *    int cursorTxtr;
     *
     *    public void setup() {
     *        cursorTxtr = makeTexture("images/cursorCrosshair32.gif"); // image must be 32x32
     *    }
     *
     *    public void draw() {
     *        // render scene
     *        ...
     *        drawCursor(cursorTxtr);
     *    }
     * </PRE>
     *
     * @param cursorTextureHandle  handle to texture containing 32x32 cursor image
     */
    public static void drawCursor(int cursorTextureHandle) {
        // set projection matrix to 2D fullscreen
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glPushMatrix();                   // preserve perspective view
        gl.glLoadIdentity();                 // clear the perspective matrix
        gl.glOrtho(                          // set ortho to exactly match screen size
                0,displayWidth,     // left, right
                0,displayHeight,    // bottom, top
                -1,1);              // Zfar, Znear
        // clear the modelview matrix
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glPushMatrix();                   // preserve current modelview matrix
        gl.glLoadIdentity();                 // clear the modelview matrix

        // preserve current settings then draw cursor
        gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_TEXTURE_BIT | GL.GL_LIGHTING_BIT | GL.GL_VIEWPORT_BIT);
        {
            // set viewport to full screen
            gl.glViewport(0,0,displayWidth,displayHeight);
            // tweak settings
            gl.glEnable(GL.GL_TEXTURE_2D);   // be sure textures are on
            gl.glColor4f(1,1,1,1);           // no color
            gl.glDisable(GL.GL_LIGHTING);    // no lighting
            gl.glDisable(GL.GL_DEPTH_TEST);  // no depth test
            gl.glEnable(GL.GL_BLEND);        // enable transparency
            gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
            // draw 32x32 cursor image
            drawQuadZ(cursorTextureHandle, cursorX-15, cursorY-15, 0, 32, 32);
        }
        gl.glPopAttrib();

        // restore the previous matrix settings
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glPopMatrix();
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glPopMatrix();
    }
    
    /**
     * 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.
     * <P>
     * 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().
     * <P>
     * @see loadImage(String)
     * @see setViewport(int,int,int,int)
     * @see resetViewport()
     */
    public static void drawImageFullScreen(JOImage 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);
        }
        // preserve settings
        pushAttribOrtho();
        // switch to 2D projection
        setOrthoOn();
        // tweak settings
        gl.glEnable(GL.GL_TEXTURE_2D);   // be sure textures are on
        gl.glColor4f(1,1,1,1);           // no color
        gl.glDisable(GL.GL_LIGHTING);    // no lighting
        gl.glDisable(GL.GL_DEPTH_TEST);  // no depth test
        gl.glEnable(GL.GL_BLEND);        // enable transparency
        gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
        // activate the image texture
        gl.glBindTexture(GL.GL_TEXTURE_2D,img.textureHandle);
        // draw a textured quad
        gl.glBegin(GL.GL_QUADS);
        {
            gl.glTexCoord2f(0, 0);
            gl.glVertex3f(0f, 0f, 0f);         // Bottom Left

            gl.glTexCoord2f(1, 0);
            gl.glVertex3f(getWidth(), 0f, 0f);         // Bottom Right

            gl.glTexCoord2f(1, 1);
            gl.glVertex3f(getWidth(), getHeight(), 0f);   // Top Right

            gl.glTexCoord2f(0, 1);
            gl.glVertex3f(0f, getHeight(), 0f);       // Top left
        }
        gl.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.)
     * <BR>
     * @see loadImage()
     * @see drawQuad()
     * @see drawImageFullScreen()
     */
    public static void drawImage(JOImage 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
        //gl.glColor4f(1,1,1,1);   // don't force color to white (may want to tint image)
        // activate the image texture
        gl.glBindTexture(GL.GL_TEXTURE_2D,img.textureHandle);
        // draw a textured quad
        gl.glNormal3f(0.0f, 0.0f, 1.0f); // normal faces positive Z
        gl.glBegin(GL.GL_QUADS);
        {
            gl.glTexCoord2f(0f, 0f);
            gl.glVertex3f(x, y, 0);
            gl.glTexCoord2f(1f, 0f);
            gl.glVertex3f(x+w, y, 0);
            gl.glTexCoord2f(1f, 1f);
            gl.glVertex3f(x+w, y+h, 0);
            gl.glTexCoord2f(0f, 1f);
            gl.glVertex3f(x, y+h, 0);
        }
        gl.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.
     * <BR>
     * @ee loadImage()
     */
    public static void drawQuad(int textureHandle, int x, int y, float w, float h) {
        // activate the specified texture
        gl.glBindTexture(GL.GL_TEXTURE_2D,textureHandle);
        // prepare to render in 2D
        setOrthoOn();
        // draw the textured quad
        gl.glNormal3f(0.0f, 0.0f, 1.0f); // normal faces positive Z
        gl.glBegin(GL.GL_QUADS);
        {
            gl.glTexCoord2f(0f, 0f);
            gl.glVertex3f(x, y, 0);
            gl.glTexCoord2f(1f, 0f);
            gl.glVertex3f(x+w, y, 0);
            gl.glTexCoord2f(1f, 1f);
            gl.glVertex3f(x+w, y+h, 0);
            gl.glTexCoord2f(0f, 1f);
            gl.glVertex3f(x, y+h, 0);
        }
        gl.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) {
        gl.glBindTexture(GL.GL_TEXTURE_2D,textureHandle);
        // draw  textured quad
        gl.glNormal3f(0.0f, 0.0f, 1.0f); // normal faces positive Z
        gl.glBegin(GL.GL_QUADS);
        {
            gl.glTexCoord2f(0f, 0f);
            gl.glVertex3f(x, y, z);
            gl.glTexCoord2f(1f, 0f);
            gl.glVertex3f(x+w, y, z);
            gl.glTexCoord2f(1f, 1f);
            gl.glVertex3f(x+w, y+h, z);
            gl.glTexCoord2f(0f, 1f);
            gl.glVertex3f(x, y+h, z);
        }
        gl.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, getWidthWindow(), getHeightWindow());
    }

    /**
     * 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.
        gl.glReadPixels(x,y,w,h, GL.GL_BGRA, GL.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
        gl.glReadPixels(x, y, 1, 1, GL.GL_RGB, GL.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, getWidthWindow(), getHeightWindow());   // 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) {
        gl.glColor4f(1,1,1,1);                // turn off alpha and color tints
        gl.glReadBuffer(GL.GL_BACK);
        gl.glBindTexture(GL.GL_TEXTURE_2D,txtrHandle);
        // Copy screen to texture
        gl.glCopyTexSubImage2D(GL.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()).
     * <P>
     * 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
        gl.glDisable(GL.GL_BLEND);
        // set viewport to full screen
        gl.glViewport(0, 0, getWidthWindow(), getHeightWindow());
        // draw square quad that covers entire screen
        drawQuad(txtrHandle, 0, 0, DM.getWidth(), DM.getHeight()); // draw the full screen image
        // restore viewport to custom aspect ratio
        gl.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).
     * <BR>
     * @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.
     * <BR>
     * @see setLineWidth()
     * @see drawRect()
     */
    public static void drawRectZ(int x, int y, int z, float w, float h) {
        // preserve current settings
        gl.glPushAttrib(GL.GL_TEXTURE_BIT | GL.GL_LIGHTING_BIT);
        // de-activate texture and light
        gl.glDisable(GL.GL_TEXTURE_2D);
        gl.glDisable(GL.GL_LIGHTING);
        // draw the rectangle
        gl.glBegin(GL.GL_LINE_STRIP);
        {
            gl.glVertex3f( (float)x, (float)y, (float)z);
            gl.glVertex3f( (float)x+w, (float)y, (float)z);
            gl.glVertex3f( (float)x+w, (float)y+h, (float)z);
            gl.glVertex3f( (float)x, (float)y+h, (float)z);
            gl.glVertex3f( (float)x, (float)y, (float)z);
        }
        gl.glEnd();
        // draw points at the corners
        gl.glBegin(GL.GL_POINTS);
        {
            gl.glVertex3f( (float)x, (float)y, (float)z);
            gl.glVertex3f( (float)x+w, (float)y, (float)z);
            gl.glVertex3f( (float)x+w, (float)y+h, (float)z);
            gl.glVertex3f( (float)x, (float)y+h, (float)z);
        }
        gl.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
        gl.glPushMatrix();
        {
            gl.glTranslatef(x,y,0);
            drawCircle(radius-linewidth, radius, 180);
        }
        gl.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) {
        gl.glPushMatrix();
        {
            gl.glTranslatef(x,y,z);
            drawCircle(radius-linewidth, radius, 180);
        }
        gl.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
        gl.glBegin(GL.GL_QUAD_STRIP);
        {
            // add first 2 vertices
            float ts = (float) Math.sin(Math.toRadians(s));
            float tc = (float) Math.cos(Math.toRadians(s));
            gl.glVertex2f(tc * innerRadius, ts * innerRadius);
            gl.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));
                gl.glVertex2f(tc * innerRadius, ts * innerRadius);
                gl.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));
            gl.glVertex2f(tc * innerRadius, ts * innerRadius);
            gl.glVertex2f(tc * outerRadius, ts * outerRadius);
        }
        gl.glEnd();
    }

    /**
     * Render a 2 unit cube centered at origin.  Includes texture coordinates
     * and normals.
     */
    public static void renderCube() {
        gl.glBegin(GL.GL_QUADS);
        // Front Face
        gl.glNormal3f( 0.0f, 0.0f, 1.0f);
        gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f(-1.0f, -1.0f,  1.0f);    // Bottom Left
        gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f( 1.0f, -1.0f,  1.0f);    // Bottom Right
        gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f( 1.0f,  1.0f,  1.0f);    // Top Right
        gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f(-1.0f,  1.0f,  1.0f);    // Top Left
        // Back Face
        gl.glNormal3f( 0.0f, 0.0f, -1.0f);
        gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f(-1.0f, -1.0f, -1.0f);    // Bottom Right
        gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f(-1.0f,  1.0f, -1.0f);    // Top Right
        gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f( 1.0f,  1.0f, -1.0f);    // Top Left
        gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f( 1.0f, -1.0f, -1.0f);    // Bottom Left
        // Top Face
        gl.glNormal3f( 0.0f, 1.0f, 0.0f);
        gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f(-1.0f,  1.0f, -1.0f);    // Top Left
        gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f(-1.0f,  1.0f,  1.0f);    // Bottom Left
        gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f( 1.0f,  1.0f,  1.0f);    // Bottom Right
        gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f( 1.0f,  1.0f, -1.0f);    // Top Right
        // Bottom Face
        gl.glNormal3f( 0.0f, -1.0f, 0.0f);
        gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f(-1.0f, -1.0f, -1.0f);    // Top Right
        gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f( 1.0f, -1.0f, -1.0f);    // Top Left
        gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f( 1.0f, -1.0f,  1.0f);    // Bottom Left
        gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f(-1.0f, -1.0f,  1.0f);    // Bottom Right
        // Right face
        gl.glNormal3f( 1.0f, 0.0f, 0.0f);
        gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f( 1.0f, -1.0f, -1.0f);    // Bottom Right
        gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f( 1.0f,  1.0f, -1.0f);    // Top Right
        gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f( 1.0f,  1.0f,  1.0f);    // Top Left
        gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f( 1.0f, -1.0f,  1.0f);    // Bottom Left
        // Left Face
        gl.glNormal3f( -1.0f, 0.0f, 0.0f);
        gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f(-1.0f, -1.0f, -1.0f);    // Bottom Left
        gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f(-1.0f, -1.0f,  1.0f);    // Bottom Right
        gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f(-1.0f,  1.0f,  1.0f);    // Top Right
        gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f(-1.0f,  1.0f, -1.0f);    // Top Left
        gl.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;
        gl.glPushMatrix();
        {
            gl.glPushMatrix();
            {
                gl.glTranslatef(0,0,halfsize);
                renderPlane(size,segments);  // front
            }
            gl.glPopMatrix();
            gl.glPushMatrix();
            {
                gl.glRotatef(90,0,1,0);
                gl.glTranslatef(0,0,halfsize);
                renderPlane(size,segments);  // right
            }
            gl.glPopMatrix();
            gl.glPushMatrix();
            {
                gl.glRotatef(180,0,1,0);
                gl.glTranslatef(0,0,halfsize);
                renderPlane(size,segments);  // back
            }
            gl.glPopMatrix();
            gl.glPushMatrix();
            {
                gl.glRotatef(270,0,1,0);
                gl.glTranslatef(0,0,halfsize);
                renderPlane(size,segments);  // left
            }
            gl.glPopMatrix();
            gl.glPushMatrix();
            {
                gl.glRotatef(90,1,0,0);
                gl.glTranslatef(0,0,halfsize);
                renderPlane(size,segments);  // bottom
            }
            gl.glPopMatrix();
            gl.glPushMatrix();
            {
                gl.glRotatef(-90,1,0,0);
                gl.glTranslatef(0,0,halfsize);
                renderPlane(size,segments);  // top
            }
            gl.glPopMatrix();
        }
        gl.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;
        gl.glBegin(GL.GL_QUADS); {
            gl.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
                    gl.glTexCoord2f((x*uvsegsizeL)*tilefactorU, (y*uvsegsizeH)*tilefactorV);
                    gl.glVertex3f( xpos, ypos, 0f);
                    // bottom rite
                    gl.glTexCoord2f(((x*uvsegsizeL)+uvsegsizeL)*tilefactorU, (y*uvsegsizeH)*tilefactorV);
                    gl.glVertex3f( xpos+segsizeL, ypos,  0f);
                    // top rite
                    gl.glTexCoord2f(((x*uvsegsizeL)+uvsegsizeL)*tilefactorU, ((y*uvsegsizeH)+uvsegsizeH)*tilefactorV);
                    gl.glVertex3f( xpos+segsizeL,  ypos+segsizeH, 0f);
                    // top left
                    gl.glTexCoord2f((x*uvsegsizeL)*tilefactorU, ((y*uvsegsizeH)+uvsegsizeH)*tilefactorV);
                    gl.glVertex3f( xpos,  ypos+segsizeH, 0f);
                }
                ypos = - height/2f; // reset column position
            }
        }
        gl.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;
        gl.glBegin(GL.GL_QUADS); {
            gl.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
                    gl.glTexCoord2f(x*uvsegsizeL, y*uvsegsizeH);
                    gl.glVertex3f( xpos, ypos, 0f);
                    // bottom rite
                    gl.glTexCoord2f((x*uvsegsizeL)+uvsegsizeL, y*uvsegsizeH);
                    gl.glVertex3f( xpos+segsizeL, ypos,  0f);
                    // top rite
                    gl.glTexCoord2f((x*uvsegsizeL)+uvsegsizeL, (y*uvsegsizeH)+uvsegsizeH);
                    gl.glVertex3f( xpos+segsizeL,  ypos+segsizeH, 0f);
                    // top left
                    gl.glTexCoord2f(x*uvsegsizeL, (y*uvsegsizeH)+uvsegsizeH);
                    gl.glVertex3f( xpos,  ypos+segsizeH, 0f);
                }
                ypos = - height/2f; // reset column position
            }
        }
        gl.glEnd();
    }
    
    /**
     * call the GLU sphere function to draw sphere geometry
     * with texture coordinates and normals
     * @param facets  number of divisions around longitude and latitude
     */
    public static void renderSphere(int facets) {
        // create a textured quadric
        GLUquadric sphere = glu.gluNewQuadric();
        glu.gluQuadricTexture(sphere, true);
        
        gl.glPushMatrix();
        {
            gl.glRotatef(-90f, 1,0,0);    // rotate the sphere to align the axis vertically
            glu.gluSphere(sphere, 1, facets, facets);
            //s.draw(1, facets, facets);              // run GL commands to draw sphere
        }
        gl.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) {
        gl.glLineWidth(width);
        gl.glPointSize(width);
        //gl.glEnable(GL.GL_POINT_SMOOTH);
        //gl.glEnable(GL.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) {
        gl.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) {
        gl.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) {
                gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            }
            else if (rgba.length == 3) {
                gl.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
            gl.glColorMaterial(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE);
            gl.glEnable(GL.GL_COLOR_MATERIAL);
        } else {
            // glColor() behaves normally
            gl.glDisable(GL.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
        JOImage 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 = gl.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)
            gl.glNewList(fontListBase + i, GL.GL_COMPILE); // Start Building A List
            gl.glBegin(GL.GL_QUADS);              // Use A 16x16 pixel Quad For Each Character
            gl.glTexCoord2f(cx, 1 - cy - factor);  // Texture Coord (Bottom Left)
            gl.glVertex2i(0, 0);
            gl.glTexCoord2f(cx + factor, 1 - cy - factor); // Texture Coord (Bottom Right)
            gl.glVertex2i(16, 0);
            gl.glTexCoord2f(cx + factor, 1 - cy);   // Texture Coord (Top Right)
            gl.glVertex2i(16, 16);
            gl.glTexCoord2f(cx, 1 - cy);             // Texture Coord (Top Left)
            gl.glVertex2i(0, 16);
            gl.glEnd();                              // Done Building Our Quad (Character)
            gl.glTranslatef(fontWidth, 0, 0);        // Move To The Right Of The Character
            gl.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) {
            gl.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("JOApp.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
            gl.glDisable(GL.GL_LIGHTING);
            // enable alpha blending, so character background is transparent
            gl.glEnable(GL.GL_BLEND);
            gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
            // enable the charset texture
            gl.glBindTexture(GL.GL_TEXTURE_2D, fontTextureHandle);
            // prepare to render in 2D
            setOrthoOn();
            // draw the text
            gl.glTranslatef(x, y, 0);        // Position The Text (in pixel coords)
            for(int i=0; i<msg.length(); i++) {
                gl.glCallList(offset + msg.charAt(i));
            }
            // restore the original positions and views
            setOrthoOff();
            // restore previous settings
            popAttrib();
        }
    }

    /**
     * Render a text string in model space, using the character set created
     * by buildFont().
     */
    public static void printZ(float x, float y, float z, int set, float scale, String msg) {
        int offset;
        if (fontListBase == -1 || fontTextureHandle == -1) {
            // font is not initiallized, try this default
            if (!buildFont("images/font_tahoma.png", 12)) {
                err("JOApp.printZ(): character set has not been created -- see buildFont()");
                return;
            }
        }
        offset = fontListBase - 32 + (128 * set);
        if (msg != null) {
            // enable the charset texture
            gl.glBindTexture(GL.GL_TEXTURE_2D, fontTextureHandle);
            // draw the text
            gl.glPushMatrix();
            {
                gl.glTranslatef(x, y, z); // Position The Text (in pixels coords)
                gl.glScalef(scale,scale,scale);  // make it smaller (arbitrary kludge!!!!)
                for (int i = 0; i < msg.length(); i++) {
                    gl.glCallList(offset + msg.charAt(i));
                }
            }
            gl.glPopMatrix();
        }
    }
    
    
    //========================================================================
    // PBuffer functions
    //
    // Pbuffers are offscreen buffers that can be rendered into just like
    // the regular framebuffer.  A pbuffer can be larger than the screen,
    // which allows for the creation of higher resolution images.
    //
    //========================================================================

    /**
     * Create a Pbuffer for use as an offscreen buffer, with the given
     * width and height.  Use selectPbuffer() to make the pbuffer the
     * context for all subsequent opengl commands.  Use selectDisplay() to
     * make the Display the context for opengl commands.
     * <P>
     * @param width
     * @param height
     * @return Pbuffer
     * @see selectPbuffer(), selectDisplay()
     */
    public static GLPbuffer makePbuffer(final int width, final int height) {
        GLDrawableFactory factory = GLDrawableFactory.getFactory();
        GLCapabilities caps = new GLCapabilities();
        caps.setDoubleBuffered(false);  // if this line is missing, Windows throws an error
        // create a new buffer
        GLPbuffer buffer = factory.createGLPbuffer(caps, null, width, height, null);
        buffer.createContext(null);
        // TODO Deal with this, GLPBuffers won't work if there's no GL listener!
        //buffer.addGLEventListener(this);
        return buffer;
    }

    /**
     * Make the pbuffer the current context for opengl commands.  All following
     * gl functions will operate on this buffer instead of the display.
     * <P>
     * NOTE: the Pbuffer may be recreated if it was lost since last used.  It's
     * a good idea to use:
     * <PRE>
     *         pbuff = selectPbuffer(pbuff);
     * </PRE>
     * to hold onto the new Pbuffer reference if Pbuffer was recreated.
     *
     * @param pb  pbuffer to make current
     * @return    Pbuffer
     * @see       selectDisplay(), makePbuffer()
     */
    public static GLPbuffer selectPbuffer(GLPbuffer pb) {
        if (pb != null) {
            currBuffer = pb;
            bufferContext = pb.getContext();
            bufferContext.makeCurrent();
        }
        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() {
        if (currBuffer != null) {
            bufferContext.release();
            bufferContext.destroy();
            currBuffer.destroy();
            currBuffer = null;
        }
    }

    /**
     * Copy the pbuffer contents to a texture.  (Should this use glCopyTexSubImage2D()?
     * Is RGB the fastest format?)
     */
    public static void frameCopy(GLPbuffer pbuff, int textureHandle) {
        gl.glBindTexture(GL.GL_TEXTURE_2D, textureHandle);
        gl.glCopyTexImage2D(GL.GL_TEXTURE_2D, 0, GL.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 <applicationClassName>-<timestamp>.png
     */
    public static void screenShot() {
        screenShot(0, 0, getWidthWindow(), getHeightWindow(), 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, getWidthWindow(), getHeightWindow(), 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 <applicationClassName>-<timestamp>.png
     * NOTE: Have to call selectPbuffer() before calling this function.
     */
    public static void screenShot(GLPbuffer 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.
     * <P>
     * WARNING: this function hogs memory!  Call java with more memory
     * (java -Xms128m -Xmx128m)
     * <P>
     * @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)
        gl.glReadPixels(x, y, width, height, GL.GL_BGRA, GL.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
        JOImage.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
            JOImage.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.
     * <P>
     * 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.
     * <P>
     * WARNING: this function hogs memory!  Call java with more memory
     * (java -Xms128m -Xmx128)
     * <P>
     * @see   selectPbuffer(), selectDisplay()
     */
    public static void screenShotRGB(int width, int height, String saveFilename) {
        // allocate space for RBG pixels
        ByteBuffer framebytes = 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)
        gl.glReadPixels(0, 0, width, height, GL.GL_RGB, GL.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)
        JOImage.savePixelsToPNG(pixels, width, height, saveFilename, true);
    }

    
    //========================================================================
    // Stencil functions
    //========================================================================

    /**
     * clear the stencil buffer
     */
    public static void clearMask() {
        gl.glClear(GL.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
        gl.glColorMask(false, false, false, false);
        gl.glDepthMask(false);

        // enable stencil buffer
        gl.glEnable(GL.GL_STENCIL_TEST);

        // set the stencil test to ALWAYS pass
        gl.glStencilFunc(GL.GL_ALWAYS, maskvalue, 0xFFFFFFFF);
        // REPLACE the stencil buffer value with maskvalue whereever we draw
        gl.glStencilOp(GL.GL_REPLACE, GL.GL_REPLACE, GL.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
        gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_KEEP);

        // turn the color and depth buffers back on
        gl.glColorMask(true, true, true, true);
        gl.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
        gl.glEnable(GL.GL_STENCIL_TEST);

        // until stencil test is disabled, only write to areas where the
        // stencil buffer equals the mask value
        gl.glStencilFunc(GL.GL_EQUAL, maskvalue, 0xFFFFFFFF);
    }

    /**
     *  turn off the stencil test so stencil has no further affect on rendering.
     */
    public static void disableMask() {
        gl.glDisable(GL.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
        gl.glReadPixels(x, y, 1, 1, GL.GL_STENCIL_INDEX, GL.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<Integer> displayLists = new ArrayList<Integer>();  // 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.
     * <P>
     * To use, create a display list in setup():
     * <PRE>
     *      int teapotID = beginDisplayList();
     *      ... // run teapot render code here
     *      endDisplayList();
     * </PRE>
     *
     * Then call the display list later in render():
     * <PRE>
     *      callDisplayList(teapotID);
     * </PRE>
     *
     * @return integer display list id
     * @see endDisplayList(), callDisplayList(), destroyDisplayList()
     */
    public static int beginDisplayList() {
        int DL_ID = gl.glGenLists(1);         // Allocate 1 new Display List
        gl.glNewList(DL_ID, GL.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() {
        gl.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) {
        gl.glCallList(displayListID);
    }

    /**
     * Delete the given display list ID.  Frees up resources on the graphics card.
     */
    public static void destroyDisplayList(int DL_ID) {
        gl.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();
            gl.glDeleteLists(displaylistID, 1);
            displayLists.remove(0);
        }
    }
    
    
  //========================================================================
    // Native IO Buffer allocation functions
    // These functions create and populate the native buffers used by JOGL.
    //========================================================================
    
    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 = JOApp.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(dataPrefix + filename);
        }
        catch (IOException ioe) {
            msg("JOApp.getInputStream (" + filename + "): " + ioe);
            if (in != null) {
                try {
                    in.close();
                }
                catch (Exception e) {}
                in = null;
            }
        }
        catch (Exception e) {
            msg("JOApp.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("JOApp.getInputStream(): Can't find resource: " + ue);}
            //msg("JOApp.getInputStream (" +filename+ "): try jar resource url=" + u);
            if (u != null) {
                try {
                    in = u.openStream();
                }
                catch (Exception e) {
                    msg("JOApp.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("JOApp.getInputStream(): Can't make applet base url: " + ue);}
                //msg("JOApp.getInputStream (" +filename+ "): try applet base url=" + u);
                try {
                    in = u.openStream();
                }
                catch (Exception e) {
                    msg("JOApp.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<byte[]> byteChunks = new ArrayList<byte[]>();

        // 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("JOApp.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("JOApp.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()).
     * <P>
     * 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 JOApp.class because that
     * class is in the JOApp 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 JOApp, ie. assume we create an app MyGame that
     * extends JOApp, and MyGame.class is in the root folder of the installation:
     * <PRE>
     *      MyGame.class
     *      models (folder)
     *      images (folder)
     *      sounds (folder)
     *  </PRE>
     *  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.
     *  <P>
     *  @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 = gl.glGetString(GL.GL_EXTENSIONS).split(" ");
            OpenGLextensions = new Hashtable<String, String>();
            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++).
     * <P>
     * 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("JOApp.method(): Can't find method (" +methodName+ ").  " + e);
        }
        return M;
    }

    /**
     * Similar to the static method() function, this looks for the method in the
     * JOApp 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, (Object[])null);
            } catch (Exception e) {
                // Error handling
                System.err.println("JOApp.invoke(): couldn't invoke method " + method.getName() + " on object " + object.getClass().getName());
            }
        }
    }

    /**
     * Similar to the static invoke() function, this execute a method on the
     * JOApp 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, (Object[])null);
            } catch (Exception e) {
                // Error handling
                System.err.println("JOApp.invoke(): couldn't invoke method " + method.getName() + " on object " + this.getClass().getName());
            }
        }
    }
}
