AWTGLCanvas Example (Update: 17.02.2006)

Started by Evil-Devil, January 05, 2006, 18:16:55

Previous topic - Next topic

Evil-Devil

Hi everyone.

As the AWTGLCanvas is often questioned and requested for examples, I thought about providing a small example.

The example is a small tool I wrote for my engine to display OBJ models.
The OBJ loader is far from finish, but the more interesting part is the implementation of the AWTGLCanvas.
As you can see on the screenies there are some controls to change the display like rotating the model or just translate it left.

For the implementation I choosed a similar way like in the lwjgl examples which makes its easy to replace the current renderer with another one.
Chaning the renderer is not included in the example, but from the behavior of Interfaces its supported.




To launch the example programm you have to create a new project in your ide and add the included jar file to your build path and of course your lwjgl binaries. I tested the example with version 0.98 and 0.99. But any lwjgl version that comes with the AWTGLCanvas should do it.
The explaining source files are included. any other files like the one in the jar need to recompiled, if you wanna see them ;) or ask for them in this thread.

>> Get The Example <<

//edit: the program does not center the models! So if you load any other than the shipped ones, it might happen that they are out of the screen.

Model files have to be placed within the model folder and textures within the textures folder. Sorry, but at first it was never planned to give it out to public.

Benny

Fool Running

Programmers will, one day, rule the world... and the world won't notice until its too late.Just testing the marquee option ;D

elias4444

Hey Evil-Devil, I love the inclusion of AWTGLCanvas... did you want my OBJ loader code? I've got it setup to import OBJs and their material (MTL) files (including textures) as well (you can check out my app at //www.tommytwisters.com/texture/texture.jnlp).

I'd be happy to send you all the code if you're interested.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

Evil-Devil

Thanks for comments :)
How to use your texture app?

I only got a blank window with a commandline...but there I wasn't able to do something... maybe screen resolution to low?

As for the obj load code, yea. That would be cool. I used your basic model loader code as startup and worked my way through the modelformat based on the obj_file documentation I found via google. And sometimes also with the c++ code from the maya obj exporter plugin. The documentation helped me to implement the older obj formats even if i did not include everything. My main purpose was to have a loader that takes a maya 5 obj file and do me display it :)

Does maya smooth the model by itselfs? If so, i only need to implement manual smoothing and of course centering the model.

I updated the tool with a screenshot function and will upload it next time.

elias4444

Hey Evil-Devil, go ahead and PM me with an e-mail address and I'll send the whole thing to you (I've already implemented centering as well). No need to reinvent the wheel.  :D
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

Evil-Devil

I uploaded a new version of my example. It includes the screenshot function.
Additional i changed some stuff within the model loader classes. Next time there will be also elias4444 provided obj stuff be included to make the loader more complete.

And its still requires J2SE 5!

For those who don't want to download the example, I provide the example itself here in this thread.

Evil-Devil

As announced, here is the code. Be aware of the imported classes, those were available within the package.
I know the GUI code is a bit messy but I didn't had yet the time to reuse the code. ;)

//edit: sry, i had to remove the GUI class, because the overall message text was too long and my code were cut :(


AWTOpenGLCanvas.java
/* =============================================================================
 * AWTOpenGLCanvas.java Bubble_Engine 
 * com.evildevil.bubble.core
 * Copyright (c) 2004 - 2005 Benjamin "Evil-Devil" Behrendt
 * All rights reserved
 * -------------------------------------------------------------------------- */
package com.evildevil.bubble.core;

import java.awt.GraphicsDevice;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.AWTGLCanvas;
import org.lwjgl.opengl.Drawable;
import org.lwjgl.opengl.PixelFormat;

/** 
 * @author Benjamin "Evil-Devil" Behrendt
 * @version 1.0, 28.12.2005
 */
public class AWTOpenGLCanvas extends AWTGLCanvas implements Runnable {
    
    private IRenderable renderer = null;
    private Thread renderThread = null;
    private boolean initialize = false;

    public AWTOpenGLCanvas() throws LWJGLException {
        super();
    }

    public AWTOpenGLCanvas(PixelFormat pixel_format) throws LWJGLException {
        super(pixel_format);
    }

    public AWTOpenGLCanvas(GraphicsDevice device, PixelFormat pixel_format)
            throws LWJGLException {
        super(device, pixel_format);        
    }

    public AWTOpenGLCanvas(GraphicsDevice device, PixelFormat pixel_format,
            Drawable drawable) throws LWJGLException {
        super(device, pixel_format, drawable);        
    }
    
    public AWTOpenGLCanvas(IRenderable renderable) throws LWJGLException {
        this();
        setRenderer(renderable);               
    }
    
    public AWTOpenGLCanvas(PixelFormat pixelFormat, IRenderable renderable)
            throws LWJGLException {
        super(pixelFormat);
        setRenderer(renderable);        
    }
    
    public AWTOpenGLCanvas(GraphicsDevice device, PixelFormat pixelFormat,
            IRenderable renderable) throws LWJGLException {
        super(device,pixelFormat);
        setRenderer(renderable);
    }
    
    public AWTOpenGLCanvas(GraphicsDevice device, PixelFormat pixelFormat,
            Drawable drawable, IRenderable renderable) throws LWJGLException {
        super(device,pixelFormat,drawable);
        setRenderer(renderable);        
    }    
    
    public void setRenderer(IRenderable renderable) {
        this.renderer = renderable;
    }
    
    public void paintGL() {        
        // TODO: redo this -.-
        if (!initialize) {
            System.out.println("initializing");
            initRenderThread();            
            initOpenGL();
            // give the renderer a note that we have initialized our main render stuff
            // and init the renderer itself
            renderer.initRenderer();
            renderer.setInitialized(true);            
            initialize = true;
        }
        synchronized(this) {
            try  {                
                makeCurrent();
                renderer.renderScene();                
                swapBuffers();                
            } catch (LWJGLException lwjgle) {
                // should not happen
                lwjgle.printStackTrace();
            }
        }
    }
    
    private final void initRenderThread() {
        if (renderThread == null) {
            renderThread = new Thread(this);
            renderThread.start();
        }
    }
    
    public void run() {
        while (renderThread != null && renderThread.isAlive()) {
            synchronized(this) {
                repaint();                
                try {                 
                    Thread.sleep(16);
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }                             
            }
        }
    }
    
    /**
     * If the context is created and a IRenderable object is provided, then
     * IRenderable.initRenderer() will be called to setup the states etc that were used.
     * @exception throws LWJGLExeption if there is still no context available
     * @exception throws NullPointerException if there was no IRenderable object provided
     */
    protected void initOpenGL() {
        if (getContext() == null)
            new LWJGLException("There is no context available that could be used to render something to it.");
        if (renderer == null)
            new NullPointerException("No IRenderable instance found, can't be null. Need one to render a scene.");
        renderer.initRenderer();
    }
    
    public IRenderable getRenderer() {
        return this.renderer;
    }
}

IRenderable.java
/* =============================================================================
 * IRenderable.java Bubble Engine 
 * Copyright (c) 2004 - 2005 Benjamin "Evil-Devil" Behrendt
 * All rights reserved
 * -----------------------------------------------------------------------------
 * powered by LWJGL - Copyright (c) LWJGL Team (http://www.lwjgl.org)
 * powered by JAVA - Copyright (c) Sun Microsystems (http://java.sun.com)
 * -------------------------------------------------------------------------- */
package com.evildevil.bubble.core;

/**
 * @author Benjamin "Evil-Devil" Behrendt
 * @version 1.0, 09.03.2005
 */
public interface IRenderable {
    
    /**
     * Used to initialize/set states of the render api i.e. openGL
     */
    public void initRenderer();
    
    /**
     * Used to load model, textures and everything that is needed
     * before a scene can be prerendered or plain rendered.
     */
    public void initScene();
    
    /**
     * Used for drawing the scene itself
     */
    public void renderScene();

    /**
     * Used to create things like DisplayLists, VBOs etc
     */
    public void preRenderScene();
    
    public void setInitialized(boolean initialized);
    
    public boolean isInitialized();

}

ModelViewerRenderer.java
/* =============================================================================
 * DemoRenderer.java Bubble_Engine 
 * com.evildevil.bubble.demo
 * Copyright (c) 2004 - 2005 Benjamin "Evil-Devil" Behrendt
 * All rights reserved
 * -------------------------------------------------------------------------- */
package com.evildevil.bubble.tools.model;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.HashMap;

import javax.swing.JOptionPane;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.glu.GLU;

import com.evildevil.bubble.core.IRenderable;
import com.evildevil.bubble.model.Face;
import com.evildevil.bubble.model.Mesh;
import com.evildevil.bubble.model.Model;
import com.evildevil.bubble.model.ModelReader;
import com.evildevil.bubble.model.ModelWriter;
import com.evildevil.bubble.model.Normalf;
import com.evildevil.bubble.model.TexCoordf;
import com.evildevil.bubble.model.Vertex3;
import com.evildevil.bubble.texture.TextureManager;
import com.evildevil.bubble.util.BufferUtils;

/** 
 * @author Benjamin "Evil-Devil" Behrendt
 * @version 1.0, 28.12.2005
 */
public class ModelViewerRenderer implements IRenderable {
    
    private boolean canvasInitialized = false;
    private int modelList = 0;
    private Model model;
    private String modelFileName = null;
    private HashMap<String, Integer> textures = null;
    public short width = 640;
    public short height = 480;    
    private float[] whiteDiffuse = { 1f,1f,1f,1f};
    private float[] posSun = {0.0f, 10.0f ,0.0f, 1.0f };    
    private float rotateX = 0f;
    private float rotateY = 0f;
    private float rotateZ = 0f;    
    private float rotateXSpeed = 0f;
    private float rotateYSpeed = 0f;
    private float rotateZSpeed = 0f;
    private boolean autoRotate = false;
    private boolean autoRotateX = false;
    private boolean autoRotateY = false;
    private boolean autoRotateZ = false;
    private float translateX = 0f;
    private float translateY = 0f;
    private float translateZ = 0f;
    private float translateXSpeed = 0f;
    private float translateYSpeed = 0f;
    private float translateZSpeed = 0f;
    private float fov = 100f;

    public ModelViewerRenderer() {
    }

    public void initRenderer() {
        GL11.glShadeModel(GL11.GL_SMOOTH);
        GL11.glClearColor(0.0f,0.0f,0.0f,0.0f);         // black background
        GL11.glClearDepth(1.0f);                        // depth of 0 to 1
        GL11.glEnable(GL11.GL_DEPTH_TEST);              // enable depth testing        
        GL11.glDepthFunc(GL11.GL_LEQUAL);        
        GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT,GL11.GL_NICEST);
        GL11.glEnable(GL11.GL_TEXTURE_2D);
        GL11.glLight(GL11.GL_LIGHT0,GL11.GL_DIFFUSE,BufferUtils.createFloatBuffer(whiteDiffuse));
        GL11.glLight(GL11.GL_LIGHT0,GL11.GL_POSITION,BufferUtils.createFloatBuffer(posSun));
        GL11.glEnable(GL11.GL_LIGHT0);
        GL11.glEnable(GL11.GL_LIGHTING);
        GL11.glViewport(0,0,width,height);        
        GL11.glMatrixMode(GL11.GL_PROJECTION);
        GL11.glLoadIdentity();
        // a nice 45Ã,° perspective
        GLU.gluPerspective(45.0f,(float)width/(float)height,0.1f,fov);
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        GL11.glLoadIdentity();
    }

    public void initScene() {
        modelList =  GL11.glGenLists(1);
        ModelReader mr = null;
        // we have no model? so don't try to load one
        if (modelFileName == null)
            return;
        StringBuilder msg = new StringBuilder();
        try {
            // read            
            mr = new ModelReader(new FileReader(new File("models/"+modelFileName)));            
            mr.readModel();
            model = mr.getModel();            
            // write if exists
            File outModel = new File(modelFileName.substring(0,modelFileName.length()-4)+".bmf");
            if (!outModel.exists()) {
                ModelWriter mw = new ModelWriter(new FileOutputStream(outModel),model);
                mw.writeModel();
            }
        } catch (FileNotFoundException fnfe) {            
            fnfe.printStackTrace();
        } catch (NumberFormatException nfe) {
            //nfe.printStackTrace()
            msg.append("Line: ("+mr.getLineNumber()+ ") "+mr.getLastReadLine()+"\n\n");
            msg.append(nfe.getMessage()+"\n\n");
            for (StackTraceElement ste : nfe.getStackTrace()) {
                msg.append(ste.toString()+"\n");
            }
            JOptionPane.showMessageDialog(null,msg.toString(),"Error on model loading",JOptionPane.ERROR_MESSAGE);
            return;     // on number parse errors, return!        
        } catch (IOException ioe) {
            ioe.printStackTrace();
            //return;     // on read errors, just return to read the next model
        
        }
        textures = new HashMap<String,Integer>();
        // preload textures
        for (String material : model.getMaterials().keySet()) {            
            textures.put(material,TextureManager.getManager().getTexture("textures/"+model.getMaterials().get(material).trim()));        
        }
    }

    public void renderScene() {
            
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
        GL11.glLoadIdentity();
        GL11.glTranslatef(0.0f+translateX,-5.0f+translateY,-40.0f+translateZ);         
        GL11.glRotatef(rotateX,1.0f,0.0f,0.0f);
        GL11.glRotatef(rotateY,0.0f,1.0f,0.0f);
        GL11.glRotatef(rotateZ,0.0f,0.0f,1.0f);
        GL11.glListBase(modelList);
        GL11.glCallList(modelList);        
        
        if (autoRotate) {
            if (autoRotateX)
                rotateX();
            if (autoRotateY)
                rotateY();
            if (autoRotateZ)
                rotateZ();
        }        
    }

    public void preRenderScene() {
        if (model == null)
            return; // no model, no rendering
        
        GL11.glNewList(modelList,GL11.GL_COMPILE);          
        for (Mesh mesh : model.getMeshes()) {
            // bind the texture this mesh is using              
            if (model.getMaterials().get(mesh.getMaterial()) != null) {             
                GL11.glBindTexture(GL11.GL_TEXTURE_2D,textures.get(mesh.getMaterial()));                
            }               
    
            for (Face face : mesh.getFaces()) {                 
                final int[] vertices = face.getVertices();
                final int[] uvs = face.getUVs();
                final int[] normals = face.getNormals();
                GL11.glBegin(face.getGLPolygonMode());                  
                    for (int i=0; i<vertices.length; i++) {                         
                        Vertex3<Float> v = model.getVertices().get(vertices[i]);
                        Normalf n = model.getNormals().get(normals[i]);                        
                        n.glNormal();
                        
                        if (uvs.length > 0) {                       
                            TexCoordf uv = model.getUVs().get(uvs[i]);
                            uv.glTexCoord();
                        }
                        GL11.glVertex3f(v.getX(),v.getY(),v.getZ());
                    }                       
                GL11.glEnd();
            }       
        }
        GL11.glEndList();
    }
    
    public void rotateX() {
        rotateX += rotateXSpeed;        
    }
    
    public void rotateY() {
        rotateY += rotateYSpeed;        
    }
    
    public void rotateZ() {
        rotateZ += rotateZSpeed;        
    }
    
    public void translateX() {
        translateX += translateXSpeed;        
    }
    
    public void translateY() {
        translateY += translateYSpeed;        
    }
    
    public void translateZ() {
        translateZ += translateZSpeed;        
    }
    
    public void setAutoRotateX(boolean auto) {
        this.autoRotateX = auto;
    }
    
    public void setAutoRotateY(boolean auto) {
        this.autoRotateY = auto;
    }
    
    public void setAutoRotateZ(boolean auto) {
        this.autoRotateZ = auto;
    }
    
    public void setAutoRotate(boolean auto) {
        this.autoRotate = auto;
    }
    
    public void setRotateXSpeed(float speed) {
        this.rotateXSpeed = speed;
    }
    
    public void setRotateYSpeed(float speed) {
        this.rotateYSpeed = speed;
    }
    
    public void setRotateZSpeed(float speed) {
        this.rotateZSpeed = speed;
    }
    
    public void setTranslateXSpeed(float speed) {
        this.translateXSpeed = speed;
    }
    
    public void setTranslateYSpeed(float speed) {
        this.translateYSpeed = speed;
    }
    
    public void setTranslateZSpeed(float speed) {
        this.translateZSpeed = speed;
    }
    
    public void setTexturing(boolean enable) {
        if (enable)
            GL11.glEnable(GL11.GL_TEXTURE_2D);
        else
            GL11.glDisable(GL11.GL_TEXTURE_2D);
    }
    
    public void setLighting(boolean enable) {
        if (enable)
            GL11.glEnable(GL11.GL_LIGHTING);
        else
            GL11.glDisable(GL11.GL_LIGHTING);
    }
    
    public void setWireframe(boolean enable) {
        if (enable)
            GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK,GL11.GL_LINE);
        else
            GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK,GL11.GL_FILL);
    }
    
    public void reset() {
        // reset rotations
        rotateX = 0f;
        rotateY = 0f;
        rotateZ = 0f;
        
        // reset translations
        translateX = 0f;
        translateY = 0f;
        translateZ = 0f;
    }

    public void setInitialized(boolean initialized) {
        this.canvasInitialized = initialized;
    }

    public boolean isInitialized() {     
        return this.canvasInitialized;
    }
    
    public void setModelFileName(String name) {
        if (name == null)
            throw new NullPointerException("Modelname can't be null");
        this.modelFileName = name;
    }
    
    public void resetRenderer() {
        // we are the first time here? ok, return
        if (textures == null || modelList == 0)
            return;
            
        int[] tex = new int[textures.size()];
        
        for (int i=0; i<tex.length; i++) {
            if (textures.get(i) == null)
                tex[i] = 0;
            else
                tex[i] = textures.get(i);
        }        
        // start with deleting the textures
        GL11.glDeleteTextures(BufferUtils.createIntBuffer(tex));
                
        // delete also the displaylist
        GL11.glDeleteLists(modelList,1);
        
        // reset also the placeholder
        textures = null;
        modelList = 0;
    }
    
    public final void setFOV(float fov) {
        this.fov = fov;
    }
    
    public final float getFOV() {
        return this.fov;
    }
    
    public final void updateFOV() {
        GL11.glMatrixMode(GL11.GL_PROJECTION);
        GL11.glLoadIdentity();
        // a nice 45Ã,° perspective
        GLU.gluPerspective(45.0f,(float)width/(float)height,0.1f,fov);
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        GL11.glLoadIdentity();
    }
    
    public final void takeScreenShot() {
        ByteBuffer screenData = BufferUtils.createByteBuffer(width*height*3);
        
        GL11.glReadPixels(0,0,(int)width,(int)height,GL11.GL_RGB,GL11.GL_UNSIGNED_BYTE,screenData);
        // swap the r and g values
        ByteBuffer swapedScreenData = BufferUtils.createByteBuffer(width*height*3);
        for (int i=0; i<swapedScreenData.capacity(); i+=3) {
            final byte red = screenData.get();
            final byte blue = screenData.get();
            final byte green = screenData.get();
            swapedScreenData.put(green);     // G
            swapedScreenData.put(blue);     // B
            swapedScreenData.put(red);     // R
        }
        swapedScreenData.flip();
        writeScreenShot(swapedScreenData);
    }
    
    private void writeScreenShot(ByteBuffer data) {
        byte[] tgaHeader = new byte[] { 0,0,2,0,0,0,0,0,0,0,0,0 };        
        final Date date = new Date();
        File screenFile = new File("Screenshot_"+date.getDate()+date.getMonth()+date.getYear()+"_"+date.getHours()+date.getMinutes()+".tga");
        try {
            FileOutputStream fos = new FileOutputStream(screenFile);
            DataOutputStream dos = new DataOutputStream(fos);
            // write the header
            fos.write(tgaHeader);
            dos.write(128);
            dos.write(2);
            dos.write(224);
            dos.write(1);
            dos.write(24);
            dos.write(0);
            // write the image data
            fos.getChannel().write(data);           
            fos.close();
        } catch (FileNotFoundException fnfe) {
            fnfe.printStackTrace();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        
    }
}

Anonymous

How do you handle canvas resizing?

I've been having problems getting gluPerspective to work once the GLContext is running.

Fool Running

Just set up the perspective every paint call.
Programmers will, one day, rule the world... and the world won't notice until its too late.Just testing the marquee option ;D

Evil-Devil

update:
As i removed the GUI code in the first time. Now there is a reworked version of it. I reused most of the actions code so it look cleaner and might be a little bit harder to read. But as you have to read anyway through it, that should no matter. Here we go, the GUI code.
BubbleModelViewer.java
/* =============================================================================
 * BubbleModelViewer.java Bubble_Engine 
 * com.evildevil.bubble.tools
 * Copyright (c) 2004 - 2005 Benjamin "Evil-Devil" Behrendt
 * All rights reserved
 * -------------------------------------------------------------------------- */
package com.evildevil.bubble.tools.model;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Hashtable;

import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSlider;
import javax.swing.JTextField;

import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.html.HTMLEditorKit;

import org.lwjgl.LWJGLException;

import com.evildevil.bubble.core.AWTOpenGLCanvas;

import com.evildevil.tools.designtools.Screen;

/**
 * @author Benjamin "Evil-Devil" Behrendt
 * @version 1.0, 11.11.2005
 */
public class BubbleModelViewer {
    
    private final String APP_TITLE = "Bubble Model Viewer";
    private final String APP_ABOUT_IMAGE_NAME = "resources/Bubble_Engine_Logo_Small.png";
    private final float APP_VERSION = 1.1f;
    private final String APP_ABOUT_TEXT = "<html><h2>"+APP_TITLE+"</h2><hr>"+
                                "<strong>Version:</strong> "+APP_VERSION+"<br><br>"+
                                "<strong><em>(c) Copyright 2005 Benjamin &quot;Evil-Devil&quot; Behrendt<br>"+
                                "All rights reserved</em></strong><hr>"+
                                "Visit us at: <a href=\"http://www.evil-devil.com\" target=\"_blank\" style=\"hover:font-weight: bold;\">"+
                                "http://www.evil-devil.com</a></html>";
    
    private JFrame mvFrame = null;
    private AWTOpenGLCanvas awtOGLCanvas = null;
    private JMenuBar mvMenuBar = null;
    
    private JCheckBox autoRot;
    private JCheckBox autoRotX;
    private JCheckBox autoRotY;
    private JCheckBox autoRotZ;
    private final JSlider rotXSpeed = createControlSlider(-100,100,0,10,5);
    private final JSlider rotYSpeed = createControlSlider(-100,100,0,10,5);
    private final JSlider rotZSpeed = createControlSlider(-100,100,0,10,5);
    private final JSlider moveXSpeed = createControlSlider(-100,100,0,10,5);
    private final JSlider moveYSpeed = createControlSlider(-100,100,0,10,5);
    private final JSlider moveZSpeed = createControlSlider(-100,100,0,10,5);
    private final JTextField rotX = new JTextField("0");
    private final JTextField rotY = new JTextField("0");
    private final JTextField rotZ = new JTextField("0");
    private final JTextField moveX = new JTextField("0");
    private final JTextField moveY = new JTextField("0");
    private final JTextField moveZ = new JTextField("0");
    private JCheckBox texturing;
    private JCheckBox wireframe;
    private JCheckBox lighting;
    
    public BubbleModelViewer() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
    
    private void createAndShowGUI() {
        mvFrame = new JFrame(this.APP_TITLE);
        mvFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // add menu
        mvFrame.setJMenuBar(createModelViewerMenu());
        
        JPanel mvPanel = new JPanel();          // becomes our ContentPane
        JPanel displayPanel = new JPanel();     // will hold the GLCanvas
        JPanel toolPanel = new JPanel();        // takes our tools        
        JPanel consolePanel = new JPanel();     // takes our output console
        JPanel renderAutoPanel = new JPanel();
        renderAutoPanel.setLayout(new BoxLayout(renderAutoPanel,BoxLayout.LINE_AXIS));
        renderAutoPanel.add(createRenderingPanel());
        renderAutoPanel.add(Box.createRigidArea(new Dimension(8,0)));
        renderAutoPanel.add(createAutoRotationPanel());
        
        // mvPanel setup
        mvPanel.setLayout(new BoxLayout(mvPanel,BoxLayout.LINE_AXIS));
        
        // displayPanel setup
        displayPanel.setLayout(new BorderLayout());
        displayPanel.setMaximumSize(new Dimension(640,480));
        try {
            awtOGLCanvas = new AWTOpenGLCanvas(new ModelViewerRenderer());
            awtOGLCanvas.setSize(640,480);
            // init the scene data
        } catch (LWJGLException lwjgle) {
            lwjgle.printStackTrace();
        }
        displayPanel.add(awtOGLCanvas,BorderLayout.CENTER);
        
        // toolPanel setup
        toolPanel.setLayout(new BoxLayout(toolPanel,BoxLayout.PAGE_AXIS));
        toolPanel.setBorder(new EmptyBorder(2,4,2,2));
        toolPanel.add(renderAutoPanel);
        toolPanel.add(Box.createRigidArea(new Dimension(0,9)));
        toolPanel.add(createTranslationPanel());
        toolPanel.add(Box.createRigidArea(new Dimension(0,9)));
        toolPanel.add(createRotationPanel());
        
        // add everything
        mvPanel.add(displayPanel);
        mvPanel.add(toolPanel);
        mvFrame.getContentPane().add(mvPanel);
        
        // pack it and set the position
        mvFrame.pack();
        mvFrame.setLocation(Screen.getCenteredTopLeftPosition(mvFrame.getSize()));
        
        // Display the stuff
        mvFrame.setResizable(false);
        awtOGLCanvas.setVisible(true);
        mvFrame.setVisible(true);
    }
    
    private JPanel createRotationPanel() {
        final JPanel rotPanel = new JPanel();    // the final panel
        final TitledBorder title = new TitledBorder("Rotation");
        JPanel buttonPanel;
        JPanel sliderPanel;
        JPanel fieldPanel;
        
        rotPanel.setAlignmentY(Component.LEFT_ALIGNMENT);
        rotPanel.setBorder(title);
        rotPanel.setLayout(new BoxLayout(rotPanel,BoxLayout.LINE_AXIS));
        
        // create and add the buttons
        buttonPanel = new JPanel();
        buttonPanel.setLayout(new BoxLayout(buttonPanel,BoxLayout.PAGE_AXIS));
        buttonPanel.add(createControlButton("Rotate X",new RotateAction("X")));
        buttonPanel.add(Box.createRigidArea(new Dimension(0,35)));
        buttonPanel.add(createControlButton("Rotate Y",new RotateAction("Y")));
        buttonPanel.add(Box.createRigidArea(new Dimension(0,38)));
        buttonPanel.add(createControlButton("Rotate Z",new RotateAction("Z")));
        
        rotPanel.add(buttonPanel);
        rotPanel.add(Box.createRigidArea(new Dimension(16,0)));
        
        // create and add the sliders
        sliderPanel = new JPanel();
        sliderPanel.setLayout(new BoxLayout(sliderPanel,BoxLayout.PAGE_AXIS));
        JLabel sliderLabel = createControlLabel("Speed");
        sliderLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        rotXSpeed.addChangeListener(new SliderAction("RX",rotX));
        sliderPanel.add(sliderLabel);
        sliderPanel.add(rotXSpeed);
        
        sliderLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        rotYSpeed.addChangeListener(new SliderAction("RY",rotY));
        sliderPanel.add(Box.createRigidArea(new Dimension(0,10)));
        sliderPanel.add(rotYSpeed);
        
        sliderLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        rotZSpeed.addChangeListener(new SliderAction("RZ",rotZ));
        sliderPanel.add(Box.createRigidArea(new Dimension(0,10)));
        sliderPanel.add(rotZSpeed);
        rotPanel.add(sliderPanel);
        rotPanel.add(Box.createRigidArea(new Dimension(16,0)));
        
        // create and add the textfields
        fieldPanel = new JPanel();
        fieldPanel.setLayout(new BoxLayout(fieldPanel,BoxLayout.PAGE_AXIS));
        fieldPanel.add(Box.createRigidArea(new Dimension(0,12)));
        rotX.setColumns(4);
        rotX.addActionListener(new SliderFieldAction(rotXSpeed,"RX"));
        fieldPanel.add(rotX);
        fieldPanel.add(Box.createRigidArea(new Dimension(0,30)));
        rotY.setColumns(4);
        rotY.addActionListener(new SliderFieldAction(rotYSpeed,"RY"));
        fieldPanel.add(rotY);
        fieldPanel.add(Box.createRigidArea(new Dimension(0,38)));
        rotZ.setColumns(4);
        rotZ.addActionListener(new SliderFieldAction(rotZSpeed,"RZ"));
        fieldPanel.add(rotZ);
        fieldPanel.add(Box.createRigidArea(new Dimension(0,12)));
        rotPanel.add(fieldPanel);
        
        return rotPanel;
    }
    
    private JPanel createAutoRotationPanel() {
        final JPanel rotPanel = new JPanel();
        final TitledBorder title = new TitledBorder("Auto-Rotation");
        
        rotPanel.setAlignmentY(Component.LEFT_ALIGNMENT);
        rotPanel.setLayout(new BoxLayout(rotPanel,BoxLayout.PAGE_AXIS));
        rotPanel.setBorder(title);
        
        final JPanel autoPanel = new JPanel();
        autoPanel.setLayout(new BoxLayout(autoPanel,BoxLayout.LINE_AXIS));
        autoPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        autoPanel.add(autoRot = createControlCheckBox("Enable",new RenderingAndAutoRotateAction("AUTO"),true,false));
        
        final JPanel xyzPanel = new JPanel();
        xyzPanel.setLayout(new BoxLayout(xyzPanel,BoxLayout.LINE_AXIS));
        xyzPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        xyzPanel.add(autoRotX = createControlCheckBox("X",new RenderingAndAutoRotateAction("X"),false,false));
        xyzPanel.add(Box.createRigidArea(new Dimension(16,0)));
        xyzPanel.add(autoRotY = createControlCheckBox("Y",new RenderingAndAutoRotateAction("Y"),false,false));
        
        xyzPanel.add(Box.createRigidArea(new Dimension(16,0)));
        xyzPanel.add(autoRotZ = createControlCheckBox("Z",new RenderingAndAutoRotateAction("Z"),false,false));
        
        rotPanel.add(autoPanel);
        rotPanel.add(Box.createVerticalStrut(8));
        rotPanel.add(xyzPanel);
        
        return rotPanel;
    }
    
    private JPanel createTranslationPanel() {
        final JPanel rotPanel = new JPanel();    // the final panel
        final TitledBorder title = new TitledBorder("Translation");
        JPanel buttonPanel;
        JPanel sliderPanel;
        JPanel fieldPanel;
        
        rotPanel.setAlignmentY(Component.LEFT_ALIGNMENT);
        rotPanel.setBorder(title);
        rotPanel.setLayout(new BoxLayout(rotPanel,BoxLayout.LINE_AXIS));
        
        // create and add the buttons with the apropriate listeners
        buttonPanel = new JPanel();
        buttonPanel.setLayout(new BoxLayout(buttonPanel,BoxLayout.PAGE_AXIS));
        buttonPanel.add(createControlButton("Transl. X",new TranslateAction("X")));
        buttonPanel.add(Box.createRigidArea(new Dimension(0,35)));
        buttonPanel.add(createControlButton("Transl. Y",new TranslateAction("Y")));
        buttonPanel.add(Box.createRigidArea(new Dimension(0,38)));
        buttonPanel.add(createControlButton("Transl. Z",new TranslateAction("Z")));
        
        rotPanel.add(buttonPanel);
        rotPanel.add(Box.createRigidArea(new Dimension(16,0)));
        
        // create and add the sliders
        sliderPanel = new JPanel();
        sliderPanel.setLayout(new BoxLayout(sliderPanel,BoxLayout.PAGE_AXIS));
        JLabel sliderLabel = createControlLabel("Speed");
        sliderLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        moveXSpeed.addChangeListener(new SliderAction("TX",moveX));
        sliderPanel.add(sliderLabel);
        sliderPanel.add(moveXSpeed);
        
        sliderLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        moveYSpeed.addChangeListener(new SliderAction("TY",moveY));
        sliderPanel.add(Box.createRigidArea(new Dimension(0,10)));
        sliderPanel.add(moveYSpeed);
        
        sliderLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        moveZSpeed.addChangeListener(new SliderAction("TZ",moveZ));
        sliderPanel.add(Box.createRigidArea(new Dimension(0,10)));
        sliderPanel.add(moveZSpeed);
        rotPanel.add(sliderPanel);
        rotPanel.add(Box.createRigidArea(new Dimension(16,0)));
        
        // create and add the textfields
        fieldPanel = new JPanel();
        fieldPanel.setLayout(new BoxLayout(fieldPanel,BoxLayout.PAGE_AXIS));
        fieldPanel.add(Box.createRigidArea(new Dimension(0,12)));
        moveX.setColumns(4);
        moveX.addActionListener(new SliderFieldAction(moveXSpeed,"TX"));
        fieldPanel.add(moveX);
        fieldPanel.add(Box.createRigidArea(new Dimension(0,30)));
        moveY.setColumns(4);
        moveY.addActionListener(new SliderFieldAction(moveYSpeed,"TY"));
        fieldPanel.add(moveY);
        fieldPanel.add(Box.createRigidArea(new Dimension(0,38)));
        moveZ.setColumns(4);
        moveZ.addActionListener(new SliderFieldAction(moveZSpeed,"TZ"));
        fieldPanel.add(moveZ);
        fieldPanel.add(Box.createRigidArea(new Dimension(0,12)));
        rotPanel.add(fieldPanel);
        
        return rotPanel;
    }
    
    private JPanel createRenderingPanel() {
        final JPanel renderPanel = new JPanel();
        final TitledBorder title = new TitledBorder("Rendering");
        
        renderPanel.setAlignmentY(Component.LEFT_ALIGNMENT);
        renderPanel.setBorder(title);
        renderPanel.setLayout(new BoxLayout(renderPanel,BoxLayout.PAGE_AXIS));
        
        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel,BoxLayout.LINE_AXIS));
        panel.setAlignmentX(Component.LEFT_ALIGNMENT);
        panel.add(texturing = createControlCheckBox("Texturing",new RenderingAndAutoRotateAction("TEXTURE"),true));
        panel.add(Box.createRigidArea(new Dimension(13,0)));
        panel.add(lighting = createControlCheckBox("Lighting",new RenderingAndAutoRotateAction("LIGHT"),true));
        renderPanel.add(panel);
        renderPanel.add(Box.createRigidArea(new Dimension(0,8)));
        
        panel = new JPanel();
        panel.setLayout(new BoxLayout(panel,BoxLayout.LINE_AXIS));
        panel.setAlignmentX(Component.LEFT_ALIGNMENT);
        panel.add(wireframe = createControlCheckBox("Wireframe",new RenderingAndAutoRotateAction("WIREFRAME"),false));
        panel.add(Box.createRigidArea(new Dimension(4,0)));
        panel.add(createControlCheckBox("Reset all",new RenderingAndAutoRotateAction("RESET"),false));
        renderPanel.add(panel);
        
        return renderPanel;
    }
    
    private void setAutoRotate(boolean auto) {
        this.autoRotX.setEnabled(auto);
        this.autoRotY.setEnabled(auto);
        this.autoRotZ.setEnabled(auto);
    }
    
    
    
    private JLabel createControlLabel(String title) {
        final JLabel label = new JLabel(title);
        return label;
    }

    private JButton createControlButton(String title) {
        final JButton button = new JButton(title);
        // we want small buttons ^_^
        button.setMargin(new Insets(1,8,0,8));
        return button;
    }
    
    private JButton createControlButton(String title, ActionListener listener) {
        final JButton button = createControlButton(title);
        button.addActionListener(listener);
        return button;
    }
    
    private JCheckBox createControlCheckBox(String title, ItemListener listener, boolean enabled, boolean selected) {
        final JCheckBox box = createControlCheckBox(title,listener,selected);
        box.setEnabled(enabled);
        return box;
    }
    
    private JCheckBox createControlCheckBox(String title, ItemListener listener, boolean selected) {
        final JCheckBox box = new JCheckBox(title);
        box.setSelected(selected);
        box.addItemListener(listener);
        return box;
    }
    
    private JSlider createControlSlider(int min, int max, int start,int major, int minor) {
        final JSlider slider = new JSlider(min,max,start);
        final Hashtable<Integer,JLabel> sliderTexts = new Hashtable<Integer,JLabel>();
        sliderTexts.put(min,new JLabel(Float.toString(min/major)));
        sliderTexts.put(start,new JLabel(Float.toString(start)));
        sliderTexts.put(max,new JLabel(Float.toString(max/major)));
        
        slider.setMajorTickSpacing(major);
        slider.setMinorTickSpacing(minor);
        slider.setLabelTable(sliderTexts);
        slider.setPaintTicks(true);
        slider.setPaintTrack(true);
        slider.setPaintLabels(true);
        return slider;
    }
    
    private JMenuBar createModelViewerMenu() {
        final JMenuBar bar = new JMenuBar();
        JPopupMenu.setDefaultLightWeightPopupEnabled(false);    // disable leightweight
        JMenu menu = new JMenu("File");
        menu.add(createMenuItem("Open",new OpenAction()));
        //menu.addSeparator();
        //menu.add(createMenuItem("Show Temple Model",new ShowAction("temple_tex.obj")));
        //menu.add(createMenuItem("Show F16 Model",new ShowAction("AF_F16.obj")));
        //menu.addSeparator();
        menu.add(createMenuItem("Preferences",new PreferencesAction()));
        menu.add(createMenuItem("Take Screenshot",new ScreenShotAction()));
        menu.add(createMenuItem("Exit",new ExitAction()));
        bar.add(menu);
        menu = new JMenu("?");
        menu.add(createMenuItem("About",new AboutAction()));
        bar.add(menu);
        JPopupMenu.setDefaultLightWeightPopupEnabled(true);     // enable leightweight
        return bar;
    }
    
    private JMenuItem createMenuItem(String name, ActionListener listener) {
        final JMenuItem item = new JMenuItem(name);
        item.addActionListener(listener);
        return item;
    }
    
    private void reset() {
        // reset all checkboxes and sliders we have
        texturing.setSelected(true);
        lighting.setSelected(true);
        wireframe.setSelected(false);
        
        // reset the autorot stuff
        autoRot.setSelected(false);
        autoRotX.setSelected(false);
        autoRotY.setSelected(false);
        autoRotZ.setSelected(false);
        
        // reset the rotspeed sliders
        rotXSpeed.setValue(0);
        rotYSpeed.setValue(0);
        rotZSpeed.setValue(0);
        
        // reset the transspeed sliders
        moveXSpeed.setValue(0);
        moveYSpeed.setValue(0);
        moveZSpeed.setValue(0);
        
        // reset also the rotation and translation of the model
        getRenderer().reset();
    }
    
    public ModelViewerRenderer getRenderer() {
        return (ModelViewerRenderer)awtOGLCanvas.getRenderer();
    }
    
    /* =========================================================================
     * MAIN Entrance
     * ---------------------------------------------------------------------- */
    public static void main(String[] args) {
        BubbleModelViewer bmv = new BubbleModelViewer();
    }
    
    /* =========================================================================
     * ACTIONS
     * ---------------------------------------------------------------------- */
    class ExitAction extends AbstractAction {
        
        public void actionPerformed(ActionEvent e) {
            mvFrame.dispose();
            System.exit(0);     // just to make sure the JVM will exit
        }
    }
    
    class AboutAction extends AbstractAction {
        
        public void actionPerformed(ActionEvent e) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    createAboutDialog();
                }
            });
        }
        
        private void createAboutDialog() {
            final JDialog dialog = new JDialog(mvFrame,"About",true);
            dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
            JPanel panel = new JPanel();
            panel.setLayout(new BoxLayout(panel,BoxLayout.Y_AXIS));
            panel.setBorder(new EmptyBorder(5,5,5,5));
            
            JEditorPane textPane = new JEditorPane();
            textPane.setEditable(false);
            textPane.setBackground(dialog.getBackground());
            textPane.setEditorKit(new HTMLEditorKit());
            textPane.setText(APP_ABOUT_TEXT);
            panel.add(textPane);
            
            JButton okButton = new JButton("OK");
            okButton.setAlignmentX(Component.RIGHT_ALIGNMENT);
            okButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    dialog.dispose();
                }
            });
            panel.add(Box.createRigidArea(new Dimension(0,8)));
            panel.add(okButton);
            dialog.setContentPane(panel);
            
            // packing :)
            dialog.pack();
            // center it
            dialog.setLocation(Screen.getCenteredTopLeftPosition(dialog.getSize()));
            dialog.setVisible(true);
        }
    }
    
    class OpenAction extends AbstractAction {
        
        public void actionPerformed(ActionEvent e) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    showOpenDialog();
                }
            });
        }
        
        private void showOpenDialog() {
            JFileChooser fc = new JFileChooser("models/");
            int result = fc.showOpenDialog(mvFrame);
            
            if (result == JFileChooser.APPROVE_OPTION) {
                // we assign the rendercanvas to our own one :)
                final ModelViewerRenderer renderer = getRenderer();
                // only load the model if the canvas that is used by the renderer
                // acknowlegded the renderer that its ready
                if (renderer.isInitialized()) {
                    // reset any modified controls
                    reset();
                    // reset the renderer just to be sure there is no old data rest
                    renderer.resetRenderer();
                    // set the modelFileName
                    renderer.setModelFileName(fc.getSelectedFile().getName());
                    // init the data
                    renderer.initScene();
                    // we got the data, now pre-render it
                    renderer.preRenderScene();
                    // everything is done, the model should now be displayed
                }
            }
        }
    }
    
    class PreferencesAction extends AbstractAction {
        private float fov = 0f;
        private boolean needUpdate = false;
        
        public void actionPerformed(ActionEvent e) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    showPreferences();
                }
            });
        }
        
        private void showPreferences() {
            final JDialog dialog = new JDialog(mvFrame,"Preferences",true);
            final JButton okButton = new JButton("OK");
            final JButton cancelButton = new JButton("Cancel");
            final JButton applyButton = new JButton("Apply");
            dialog.setSize(223,98);
            final float x = mvFrame.getX() + mvFrame.getWidth()/2 - dialog.getWidth()/2;
            final float y = mvFrame.getY() + mvFrame.getHeight()/2 - dialog.getHeight()/2;
            dialog.setLocation((int)x,(int)y);
            JPanel dialogPanel = new JPanel();
            dialogPanel.setLayout(new BoxLayout(dialogPanel,BoxLayout.PAGE_AXIS));
            dialogPanel.setBorder(new EmptyBorder(5,5,5,5));
            
            // build the dialog GUI
            JPanel panel = new JPanel();
            final JTextField fovField = new JTextField(Float.toString(getRenderer().getFOV()));
            fovField.setColumns(5);
            fovField.addActionListener(new ActionListener() {
               public void actionPerformed(ActionEvent e) {
                   final JTextField source = (JTextField)e.getSource();
                   if (source.getText() == null || source.getText().length() == 0)
                       return;
                   try {
                       fov = Float.parseFloat(source.getText());
                   } catch (NumberFormatException nfe) {
                       source.setText(Float.toString(getRenderer().getFOV()));
                   }
                   // as the value was changed or even not, disable the ok button and enable apply button
                   okButton.setEnabled(false);
                   applyButton.setEnabled(true);
               }
            });
            panel.setLayout(new BoxLayout(panel,BoxLayout.LINE_AXIS));
            panel.add(createControlLabel("Field Of View (FOV):"));
            panel.add(Box.createRigidArea(new Dimension(8,0)));
            panel.add(fovField);
            dialogPanel.add(panel);
            dialogPanel.add(Box.createRigidArea(new Dimension(0,8)));
            
            panel = new JPanel();
            okButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    // call the renderer updateFOV()
                    // only update if the new fov differs from the current
                    if (needUpdate) {
                        // make sure that we have a valid renderer,
                        // for the case that someone change something within
                        // the preferences before everything else
                        getRenderer().updateFOV();
                        needUpdate = false;
                    }
                    // exit the dialog
                    dialog.dispose();
                }
            });
            
            cancelButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    // just exist without doing something;
                    dialog.dispose();
                }
            });
            
            applyButton.setEnabled(false);
            applyButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    // update the fov value
                    if (fov > 0f && fov != getRenderer().getFOV())
                        getRenderer().setFOV(fov);
                    // enable the ok button
                    okButton.setEnabled(true);
                    // disable you
                    applyButton.setEnabled(false);
                    needUpdate = true;
                }
            });
            panel.setLayout(new BoxLayout(panel,BoxLayout.LINE_AXIS));
            panel.add(okButton);
            panel.add(Box.createRigidArea(new Dimension(8,0)));
            panel.add(cancelButton);
            panel.add(Box.createRigidArea(new Dimension(8,0)));
            panel.add(applyButton);
            dialogPanel.add(panel);
            dialog.setContentPane(dialogPanel);
            dialog.pack();
            dialog.setVisible(true);
        }
    }
    
    class ScreenShotAction extends AbstractAction {
                
        public void actionPerformed(ActionEvent e) {
            if (getRenderer().isInitialized())
                getRenderer().takeScreenShot();
        }
    }
    
    class RotateAction extends AbstractAction {
        private String rotationTarget = null;
        
        public RotateAction(String rotation) {
            this.rotationTarget = rotation;
        }
        
        public void actionPerformed(ActionEvent e) {
            if (rotationTarget.equalsIgnoreCase("X"))
                ((ModelViewerRenderer)awtOGLCanvas.getRenderer()).rotateX();
            if (rotationTarget.equalsIgnoreCase("Y"))
                ((ModelViewerRenderer)awtOGLCanvas.getRenderer()).rotateY();
            if (rotationTarget.equalsIgnoreCase("Z"))
                ((ModelViewerRenderer)awtOGLCanvas.getRenderer()).rotateZ();
        }        
    }
    
    class TranslateAction extends AbstractAction {
        private String translateTarget = null;
        
        public TranslateAction(String translation) {
            this.translateTarget = translation;
        }
        
        public void actionPerformed(ActionEvent e) {
            if (translateTarget.equalsIgnoreCase("X"))
                getRenderer().translateX();
            if (translateTarget.equalsIgnoreCase("Y"))
                getRenderer().translateY();
            if (translateTarget.equalsIgnoreCase("Z"))
                getRenderer().translateZ();
        }    
    }
    
    class RenderingAndAutoRotateAction implements ItemListener {
        private String renderingTarget = null;
        
        public RenderingAndAutoRotateAction(String rendering) {
            this.renderingTarget = rendering;
        }
        
        public void itemStateChanged(ItemEvent e) {
            final boolean active = e.getStateChange() == ItemEvent.SELECTED ? true : false;
            
            if (renderingTarget.equalsIgnoreCase("TEXTURE"))
                getRenderer().setTexturing(active);
            if (renderingTarget.equalsIgnoreCase("LIGHT"))
                getRenderer().setLighting(active);
            if (renderingTarget.equalsIgnoreCase("WIREFRAME"))
                getRenderer().setWireframe(active);
            if (renderingTarget.equalsIgnoreCase("RESET") && active) {
                reset();    // reset our GUI elements and the renderer                                
                ((JCheckBox)e.getSource()).setSelected(false);
            }
            if (renderingTarget.equalsIgnoreCase("AUTO")) {
                setAutoRotate(active);
                getRenderer().setAutoRotate(active);
            }
            if (autoRot.isEnabled()) {
                if (renderingTarget.equalsIgnoreCase("X"))
                    getRenderer().setAutoRotateX(active);
                if (renderingTarget.equalsIgnoreCase("Y"))
                    getRenderer().setAutoRotateY(active);
                if (renderingTarget.equalsIgnoreCase("Z"))
                    getRenderer().setAutoRotateZ(active);
            }
        }
    }
    
    class SliderAction implements ChangeListener {
        private String sliderTarget = "";
        private JTextField inputTarget = null;
        
        public SliderAction(String slider, JTextField input) {
            this.sliderTarget = slider;
            this.inputTarget = input;
        }
        
        public void stateChanged(ChangeEvent e) {
            final JSlider slider = (JSlider)e.getSource();
            
            if(!slider.getValueIsAdjusting()) {
                final float speed = (float)slider.getValue()/10;                
                // rotation speed
                if (sliderTarget.equalsIgnoreCase("RX"))
                    getRenderer().setRotateXSpeed(speed);
                if (sliderTarget.equalsIgnoreCase("RY"))
                    getRenderer().setRotateYSpeed(speed);
                if (sliderTarget.equalsIgnoreCase("RZ"))
                    getRenderer().setRotateZSpeed(speed);
                
                // translation speed
                if (sliderTarget.equalsIgnoreCase("TX"))
                    getRenderer().setTranslateXSpeed(speed);
                if (sliderTarget.equalsIgnoreCase("TY"))
                    getRenderer().setTranslateYSpeed(speed);
                if (sliderTarget.equalsIgnoreCase("TZ"))
                    getRenderer().setTranslateZSpeed(speed);
                if (inputTarget != null)
                    inputTarget.setText(Float.toString((float)slider.getValue()/10));
            }            
        }
    }
    
    class SliderFieldAction extends AbstractAction {
        private JSlider sliderTarget = null;        
        private String renderTarget = "";

        public SliderFieldAction(JSlider slider, String render) {
            this.sliderTarget = slider;            
            this.renderTarget = render;
        }
        
        public void actionPerformed(ActionEvent e) {
            final JTextField field = (JTextField)e.getSource();
            
            if (field.getText() != null) {
                try {
                    final int value = Integer.parseInt(field.getText().substring(0,field.getText().length() > 3 ? 3 : field.getText().length()));
                    applyToSlider(this.sliderTarget,value,10);
                    field.setText(Float.toString(value));
                    setRenderAction(this.renderTarget,value);
                } catch (NumberFormatException nfe) {
                    field.setText("0");
                    setRenderAction(this.renderTarget,0);
                }
            }
        }
        
        private void setRenderAction(String render, float value) {
            if (render.equalsIgnoreCase("TX"))
                getRenderer().setTranslateXSpeed(value);
            if (render.equalsIgnoreCase("TY"))
                getRenderer().setTranslateYSpeed(value);
            if (render.equalsIgnoreCase("TZ"))
                getRenderer().setTranslateZSpeed(value);
            if (render.equalsIgnoreCase("RX"))
                getRenderer().setRotateXSpeed(value);
            if (render.equalsIgnoreCase("RY"))
                getRenderer().setRotateYSpeed(value);
            if (render.equalsIgnoreCase("RZ"))
                getRenderer().setRotateZSpeed(value);
        }
        
        private void applyToSlider(JSlider slider, int value, int multiple) {
            if (value >= slider.getMaximum())
                slider.setValue(slider.getMaximum());
            else if (value <= slider.getMinimum())
                slider.setValue(slider.getMinimum());
            else slider.setValue(value*multiple);
        }        
    }
    
    class ShowAction extends AbstractAction {
        private String modelName = null;
        
        public ShowAction(String model) {
            this.modelName = model;
        }        

        public void actionPerformed(ActionEvent e) {           
            final ModelViewerRenderer renderer = getRenderer();
            // only load the model if the canvas that is used by the renderer
            // acknowlegded the renderer that its ready
            if (this.modelName != null && renderer.isInitialized()) {
                // reset any modified controls
                reset();
                // reset the renderer just to be sure there is no old data rest
                renderer.resetRenderer();
                // set the modelFileName
                renderer.setModelFileName(this.modelName);
                // init the data
                renderer.initScene();
                // we got the data, now pre-render it
                renderer.preRenderScene();
                // everything is done, the model should now be displayed
            }
        }
        
    }
}