Commits

Michael Ludwig committed d51fb79

Comments (0)

Files changed (4)

test/com/ferox/ApplicationBase.java

+package com.ferox;
+
+import java.awt.Component;
+import java.awt.event.KeyEvent;
+
+import com.ferox.math.Transform;
+import com.ferox.renderer.FrameStatistics;
+import com.ferox.renderer.Renderer;
+import com.ferox.renderer.impl.jogl.BasicJoglRenderer;
+
+/** ApplicationBase provides a common, simple framework for
+ * tests to build off of.  It uses the BasicJoglRenderer.
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public class ApplicationBase {
+	public static final float T_VEL = 20f;
+	
+	private FrameStatistics stats;
+	private Renderer renderer;
+	
+	private InputManager input;
+	private Transform toMove;
+	
+	private boolean exiting;
+	
+	public ApplicationBase(boolean debug) {
+		this.stats = new FrameStatistics();
+		this.input = null;
+		this.renderer = new BasicJoglRenderer(debug);
+	}
+	
+	/** Init, then update and render at full speed, and then 
+	 * clean-up the mess. */
+	public void run() {
+		this.init(this.renderer);
+		
+		while(!this.exiting) {
+			if (this.input != null)
+				this.handleInput(this.input, this.stats.getTotalTime() / 1e9f);
+			
+			this.exiting = this.exiting || this.update() || this.render(this.renderer);
+		}
+		
+		this.destroy(this.renderer);
+		System.exit(0);
+	}
+	
+	/** Configure input handling so that it listens on the given compoent.
+	 * If mover isn't null, it will be the transform automatically moved
+	 * by the default implementation of handleInput(). */
+	protected void configureInputHandling(Component c, Transform mover) {
+		this.input = new InputManager(c);
+		this.input.setKeyBehavior(KeyEvent.VK_R, InputManager.INITIAL_PRESS);
+		this.input.setKeyBehavior(KeyEvent.VK_ESCAPE, InputManager.INITIAL_PRESS);
+		
+		this.toMove = mover;
+	}
+	
+	/** dt is change in time, in seconds. */
+	protected void handleInput(InputManager input, float dt) {
+		if (input.isKeyPressed(KeyEvent.VK_ESCAPE)) 
+			this.exiting = true;
+		if (input.isKeyPressed(KeyEvent.VK_R))
+			this.stats.reportStatistics(System.out);
+		
+		if (this.toMove != null) {
+			float t = dt * T_VEL;
+			
+			if (input.isKeyPressed(KeyEvent.VK_LEFT))
+				this.toMove.getTranslation().x += -t;
+			if (input.isKeyPressed(KeyEvent.VK_RIGHT))
+				this.toMove.getTranslation().x += t;
+			if (input.isKeyPressed(KeyEvent.VK_S))
+				this.toMove.getTranslation().y += -t;
+			if (input.isKeyPressed(KeyEvent.VK_W))
+				this.toMove.getTranslation().y += t;
+			if (input.isKeyPressed(KeyEvent.VK_UP))
+				this.toMove.getTranslation().z += t;
+			if (input.isKeyPressed(KeyEvent.VK_DOWN))
+				this.toMove.getTranslation().z += -t;
+		}
+	}
+	
+	/** Perform initial setup with the given renderer,
+	 * which will be used later on. */
+	protected void init(Renderer renderer) {
+		// do nothing
+	}
+	
+	/** Update any scene elements, etc. before the rendering
+	 * will be started.  Return true if the application should
+	 * exit. */
+	protected boolean update() {
+		// do nothing
+		return false;
+	}
+	
+	/** Render everything.  This implementation just calls
+	 * flushRenderer() and returns false.  Subclasses should
+	 * queue surfaces before calling super().
+	 * 
+	 * Return true if the application should exit. */
+	protected boolean render(Renderer renderer) {
+		renderer.flushRenderer(this.stats);
+		return false;
+	}
+	
+	/** Clean-up things.  This implementation calls
+	 * destroy() on the given renderer, so overridden
+	 * methods must call super() at the end. */
+	protected void destroy(Renderer renderer) {
+		renderer.destroy();
+	}
+}

test/com/ferox/BasicApplication.java

+package com.ferox;
+
+import java.awt.Component;
+import java.awt.Frame;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+import com.ferox.math.Transform;
+import com.ferox.renderer.DisplayOptions;
+import com.ferox.renderer.RenderPass;
+import com.ferox.renderer.RenderQueue;
+import com.ferox.renderer.Renderer;
+import com.ferox.renderer.View;
+import com.ferox.renderer.WindowSurface;
+import com.ferox.renderer.util.BasicRenderPass;
+import com.ferox.renderer.util.StateSortingRenderQueue;
+import com.ferox.resource.BufferData;
+import com.ferox.resource.Geometry;
+import com.ferox.resource.VertexArray;
+import com.ferox.resource.VertexArrayGeometry;
+import com.ferox.resource.VertexBufferGeometry;
+import com.ferox.resource.VertexBufferObject;
+import com.ferox.resource.BufferedGeometry.PolygonType;
+import com.ferox.resource.VertexBufferObject.UsageHint;
+import com.ferox.scene.SceneElement;
+import com.ferox.scene.ViewNode;
+import com.sun.opengl.util.BufferUtil;
+
+/** BasicApplication extends ApplicationBase and imposes
+ * more constraints on the test.
+ * 
+ * It automatically creates a WindowSurface with one render
+ * pass and a view.  The input is configured to move the
+ * constructed view.
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public abstract class BasicApplication extends ApplicationBase {
+	private WindowSurface window;
+	private RenderPass pass;
+	
+	private ViewNode view;
+	private SceneElement scene;
+	
+	public BasicApplication(boolean debug) {
+		super(debug);
+	}
+	
+	public static Geometry buildCube(Renderer renderer, float side, boolean vbo) {
+		float[] v = new float[72];
+		float[] n = new float[72];
+		float[] t = new float[48];
+		
+		// front
+		v[0] = 1f; v[1] = 1f; v[2] = 1f; n[0] = 0f; n[1] = 0f; n[2] = 1f; t[0] = 1f; t[1] = 1f;
+		v[3] = -1f; v[4] = 1f; v[5] = 1f; n[3] = 0f; n[4] = 0f; n[5] = 1f; t[2] = 0f; t[3] = 1f;
+		v[6] = -1f; v[7] = -1f; v[8] = 1f; n[6] = 0f; n[7] = 0f; n[8] = 1f; t[4] = 0f; t[5] = 0f;
+		v[9] = 1f; v[10] = -1f; v[11] = 1f; n[9] = 0f; n[10] = 0f; n[11] = 1f; t[6] = 1f; t[7] = 0f;
+		//back
+		v[12] = -1f; v[13] = -1f; v[14] = -1f; n[12] = 0f; n[13] = 0f; n[14] = -1f; t[8] = 1f; t[9] = 1f;
+		v[21] = 1f; v[22] = -1f; v[23] = -1f; n[21] = 0f; n[22] = 0f; n[23] = -1f; t[10] = 0f; t[11] = 1f;
+		v[18] = 1f; v[19] = 1f; v[20] = -1f; n[18] = 0f; n[19] = 0f; n[20] = -1f; t[12] = 0f; t[13] = 0f;
+		v[15] = -1f; v[16] = 1f; v[17] = -1f; n[15] = 0f; n[16] = 0f; n[17] = -1f; t[14] = 1f; t[15] = 0f;
+		//right
+		v[24] = 1f; v[25] = 1f; v[26] = -1f; n[24] = 1f; n[25] = 0f; n[26] = 0f; t[16] = 1f; t[17] = 1f;
+		v[27] = 1f; v[28] = 1f; v[29] = 1f; n[27] = 1f; n[28] = 0f; n[29] = 0f; t[18] = 0f; t[19] = 1f;
+		v[30] = 1f; v[31] = -1f; v[32] = 1f; n[30] = 1f; n[31] = 0f; n[32] = 0f; t[20] = 0f; t[21] = 0f;
+		v[33] = 1f; v[34] = -1f; v[35] = -1f; n[33] = 1f; n[34] = 0f; n[35] = 0f; t[22] = 1f; t[23] = 0f;
+		//left
+		v[36] = -1f; v[37] = -1f; v[38] = 1f; n[36] = -1f; n[37] = 0f; n[38] = 0f; t[24] = 1f; t[25] = 1f;
+		v[45] = -1f; v[46] = -1f; v[47] = -1f; n[45] = -1f; n[46] = 0f; n[47] = 0f; t[26] = 0f; t[27] = 1f;
+		v[42] = -1f; v[43] = 1f; v[44] = -1f; n[42] = -1f; n[43] = 0f; n[44] = 0f; t[28] = 0f; t[29] = 0f;
+		v[39] = -1f; v[40] = 1f; v[41] = 1f; n[39] = -1f; n[40] = 0f; n[41] = 0f; t[30] = 1f; t[31] = 0f;
+		//top
+		v[48] = -1f; v[49] = 1f; v[50] = -1f; n[48] = 0f; n[49] = 1f; n[50] = 0f; t[32] = 1f; t[33] = 1f;
+		v[57] = 1f; v[58] = 1f; v[59] = -1f; n[57] = 0f; n[58] = 1f; n[59] = 0f; t[34] = 0f; t[35] = 1f;
+		v[54] = 1f; v[55] = 1f; v[56] = 1f; n[54] = 0f; n[55] = 1f; n[56] = 0f; t[36] = 0f; t[37] = 0f;
+		v[51] = -1f; v[52] = 1f; v[53] = 1f; n[51] = 0f; n[52] = 1f; n[53] = 0f; t[38] = 1f; t[39] = 0f;
+		//bottom
+		v[60] = 1f; v[61] = -1f; v[62] = 1f; n[60] = 0f; n[61] = -1f; n[62] = 0f; t[40] = 1f; t[41] = 1f;
+		v[63] = -1f; v[64] = -1f; v[65] = 1f; n[63] = 0f; n[64] = -1f; n[65] = 0f; t[42] = 0f; t[43] = 1f;
+		v[66] = -1f; v[67] = -1f; v[68] = -1f; n[66] = 0f; n[67] = -1f; n[68] = 0f; t[44] = 0f; t[45] = 0f;
+		v[69] = 1f; v[70] = -1f; v[71] = -1f; n[69] = 0f; n[70] = -1f; n[71] = 0f; t[46] = 1f; t[47] = 0f;
+		
+		for (int i = 0; i < v.length; i++)
+			v[i] = v[i] * side / 2f;
+		
+		int[] i = new int[24];
+		for (int u = 0; u < 24; u++)
+			i[u] = u;
+		
+		if (vbo) {
+			VertexBufferObject vbd = new VertexBufferObject(new BufferData(v, false), UsageHint.STATIC);
+			VertexBufferObject nbd = new VertexBufferObject(new BufferData(n, false), UsageHint.STATIC);
+			VertexBufferObject tbd = new VertexBufferObject(new BufferData(t, false), UsageHint.STATIC);
+			
+			VertexBufferObject ibd = new VertexBufferObject(new BufferData(i, true), UsageHint.STATIC);
+			
+			VertexBufferGeometry geom = new VertexBufferGeometry(vbd, new VertexArray(3), ibd, new VertexArray(1), PolygonType.QUADS);
+			geom.setNormals(nbd, new VertexArray(3));
+			geom.setTextureCoordinates(0, tbd, new VertexArray(2));
+			
+			// have to request updates to, best do it here
+			renderer.requestUpdate(vbd, false);
+			renderer.requestUpdate(nbd, false);
+			renderer.requestUpdate(tbd, false);
+			renderer.requestUpdate(ibd, false);
+			
+			renderer.requestUpdate(geom, false);
+			return geom;
+		} else {
+			FloatBuffer vbd = BufferUtil.newFloatBuffer(v.length);
+			vbd.put(v).rewind();
+			FloatBuffer nbd = BufferUtil.newFloatBuffer(n.length);
+			nbd.put(n).rewind();
+			FloatBuffer tbd = BufferUtil.newFloatBuffer(t.length);
+			tbd.put(t).rewind();
+			
+			IntBuffer ibd = BufferUtil.newIntBuffer(i.length);
+			ibd.put(i).rewind();
+			
+			VertexArrayGeometry geom = new VertexArrayGeometry(vbd, new VertexArray(3), ibd, new VertexArray(1), PolygonType.QUADS);
+			geom.setNormals(nbd, new VertexArray(3));
+			geom.setTextureCoordinates(0, tbd, new VertexArray(2));
+			
+			// have to request updates to, best do it here			
+			renderer.requestUpdate(geom, false);
+			return geom;
+		}
+	}
+	
+	protected abstract SceneElement buildScene(Renderer renderer, ViewNode view);
+		
+	/** Return a RenderQueue that will be used with this application's
+	 * single render pass. */
+	protected RenderQueue createQueue() {
+		return new StateSortingRenderQueue();
+	}
+	
+	/** Return a DisplayOptions to use for the created
+	 * WindowSurface in init(). */
+	protected DisplayOptions createOptions() {
+		return new DisplayOptions();
+	}
+
+	@Override
+	protected void init(Renderer renderer) {
+		View v = new View();
+		this.view = new ViewNode(v);
+		Transform viewTrans = this.view.getLocalTransform();
+		viewTrans.getTranslation().set(0f, 0f, 15f);
+		
+		this.scene = this.buildScene(renderer, this.view);
+		this.pass = new BasicRenderPass(this.scene, v, this.createQueue(), false);
+		
+		this.window = renderer.createWindowSurface(this.createOptions(), 10, 10, 640, 480, true, false);
+		this.window.addRenderPass(this.pass);
+		this.window.setTitle(this.getClass().getSimpleName());
+				
+		// somewhat lame to get input working for now
+		Frame f = (Frame) this.window.getWindowImpl();
+		Component child = f.getComponent(0);
+		this.configureInputHandling(child, viewTrans);
+	}
+	
+	@Override
+	protected boolean update() {
+		this.scene.update(true);
+		return false;
+	}
+	
+	@Override
+	protected boolean render(Renderer renderer) {
+		if (this.window.isDestroyed())
+			return true;
+		
+		renderer.queueRender(this.window);
+		return super.render(renderer);
+	}
+	
+	@Override
+	protected void destroy(Renderer renderer) {
+		if (!this.window.isDestroyed())
+			renderer.destroy(this.window);
+		super.destroy(renderer);
+	}
+}

test/com/ferox/InputManager.java

+package com.ferox;
+
+import java.awt.event.*;
+import java.awt.*;
+import javax.swing.SwingUtilities;
+//FIXME: make this better, cleaner and more interfacy, not permanent
+public class InputManager implements KeyListener, MouseListener, MouseMotionListener, MouseWheelListener {
+	public static final int NORMAL=0;
+	public static final int RELATIVE=1;
+	public static final int INITIAL_PRESS=1;
+	public static final int MOVED=0;
+	public static final int DRAGGED=1;
+	public static final int RELEASED=0;
+	public static final int PRESSED=1;
+	public static final int CLICKED=2;
+	public static final int WAITING_FOR_RELEASE=2;
+	
+	private static final int NUM_KEYS=600;
+	private static final int NUM_MOUSE_BUTTONS=3;
+	
+	public static final Cursor INVISIBLE_CURSOR=Toolkit.getDefaultToolkit().createCustomCursor(Toolkit.getDefaultToolkit().getImage(""),new Point(0,0),"invisible");
+	
+	private int keyPressed[][];
+	private int mouseButtonPressed[];
+	private int mouseMotionType=0;
+	private int mouseWheelChange=0;
+	private int mouseXChange=0;
+	private int mouseYChange=0;
+	private int mouseBehavior;
+	private int buttonOnDrag;
+	
+	public static final int centerWheel=2;
+	public static final int leftClick=1;
+	public static final int rightClick=3;
+	
+	private Point mouseLocation;
+	private Point centerLocation;
+	private Point mouseAtButton1[];
+	private Point mouseAtButton2[];
+	private Point mouseAtButton3[];
+	private Point mouseAtWheel;
+	
+	private Component comp;
+	public Robot robot;
+	
+	private boolean isRecentering;
+	
+	public InputManager(Component comp) {
+		this(comp,NORMAL);
+	}
+	
+	public InputManager(Component comp,int behavior) {
+		mouseLocation=new Point();
+		centerLocation=new Point();
+		mouseAtButton1=new Point[3];
+		mouseAtButton2=new Point[3];
+		mouseAtButton3=new Point[3];
+		mouseAtWheel=new Point();
+		
+		for (int i=0;i<3;i++) {
+			mouseAtButton1[i]=new Point();
+			mouseAtButton2[i]=new Point();
+			mouseAtButton3[i]=new Point();
+		}
+		
+		keyPressed=new int[NUM_KEYS][2];
+		mouseButtonPressed=new int[NUM_MOUSE_BUTTONS];
+		setInputComponent(comp);
+		setBehavior(behavior);
+	}
+	
+	public void setInputComponent(Component comp) {
+		if (this.comp!=null) {
+			this.comp.removeKeyListener(this);
+			this.comp.removeMouseListener(this);
+			this.comp.removeMouseMotionListener(this);
+			this.comp.removeMouseWheelListener(this);
+		}
+		this.comp=comp;
+		if (this.comp!=null) {
+			this.comp.addKeyListener(this);
+			this.comp.addMouseListener(this);
+			this.comp.addMouseMotionListener(this);
+			this.comp.addMouseWheelListener(this);
+		}
+	}
+	
+	public void setCursor(Cursor cursor) {
+		comp.setCursor(cursor);
+	}
+	
+	public void setBehavior(int behavior) {
+		if (behavior==0||behavior==1) 
+			mouseBehavior=behavior;
+		else mouseBehavior=0;
+		if (behavior==1) 
+			setRelativeMouseMode(mouseBehavior);
+	}
+	
+	private void setRelativeMouseMode(int mode) {
+		if (mode==isRelativeMouseMode()) 
+			return;
+		if (mode==RELATIVE) {
+				try {
+					robot=new Robot();
+					recenterMouse();
+					setCursor(INVISIBLE_CURSOR);
+				}
+				catch (AWTException ex) {
+					robot=null;
+				}
+		} else {
+			robot=null;
+		}
+	}
+	
+	public int isRelativeMouseMode() {
+		if (robot==null) 
+			return NORMAL;
+		else return RELATIVE;
+	}
+	
+	private synchronized void recenterMouse() {
+		if (robot!=null&&comp.isShowing()) {
+			centerLocation.x=comp.getWidth()/2;
+			centerLocation.y=comp.getHeight()/2;
+			SwingUtilities.convertPointToScreen(centerLocation,comp);
+			isRecentering=true;
+			robot.mouseMove(centerLocation.x,centerLocation.y);
+		}
+		
+	}
+	
+	public void mouseMoved(MouseEvent e) {
+		if (isRecentering&&mouseBehavior==1&&centerLocation.x==e.getX()&&centerLocation.y==e.getY()) {
+			isRecentering=false;
+		} else {
+			mouseXChange=(e.getX())-mouseLocation.x;
+			mouseYChange=(e.getY())-mouseLocation.y;
+			if (mouseBehavior==1)
+				recenterMouse();
+			mouseMotionType=MOVED;
+		}
+		mouseLocation.x=(e.getX());
+		mouseLocation.y=(e.getY());
+		e.consume();
+	}
+	
+	public void mouseDragged(MouseEvent e) {
+		mouseMoved(e);
+		mouseMotionType=DRAGGED;
+	}
+	
+	public void mousePressed(MouseEvent e) {
+		int button=0;
+		
+		switch(e.getButton()) {
+			case MouseEvent.BUTTON1:button=0;mouseAtButton1[0].x=(e.getX());mouseAtButton1[0].y=(e.getY());break;
+			case MouseEvent.BUTTON2:button=1;mouseAtButton2[0].x=(e.getX());mouseAtButton2[0].y=(e.getY());break;
+			case MouseEvent.BUTTON3:button=2;mouseAtButton3[0].x=(e.getX());mouseAtButton3[0].y=(e.getY());break;
+			default:button=0;break;
+		}
+		mouseButtonPressed[button]=PRESSED;
+		buttonOnDrag=e.getButton();
+		e.consume();
+	}
+	
+	public void mouseReleased(MouseEvent e) {
+		int button=0;
+		
+		switch(e.getButton()) {
+			case MouseEvent.BUTTON1:button=0;mouseAtButton1[1].x=(e.getX());mouseAtButton1[1].y=(e.getY());break;
+			case MouseEvent.BUTTON2:button=1;mouseAtButton2[1].x=(e.getX());mouseAtButton2[1].y=(e.getY());break;
+			case MouseEvent.BUTTON3:button=2;mouseAtButton3[1].x=(e.getX());mouseAtButton3[1].y=(e.getY());break;
+			default:button=0;break;
+		}
+		mouseButtonPressed[button]=RELEASED;
+		buttonOnDrag=-1;
+		e.consume();
+	}
+	
+	public void mouseClicked(MouseEvent e) {
+		int button=0;
+		
+		switch(e.getButton()) {
+			case MouseEvent.BUTTON1:button=0;mouseAtButton1[2].x=(e.getX());mouseAtButton1[2].y=(e.getY());break;
+			case MouseEvent.BUTTON2:button=1;mouseAtButton2[2].x=(e.getX());mouseAtButton2[2].y=(e.getY());break;
+			case MouseEvent.BUTTON3:button=2;mouseAtButton3[2].x=(e.getX());mouseAtButton3[2].y=(e.getY());break;
+			
+			default:button=0;break;
+		}
+		mouseButtonPressed[button]=CLICKED;
+		buttonOnDrag=-1;
+		e.consume();
+	}
+	
+	public void mouseEntered(MouseEvent e) {
+		mouseMoved(e);
+	}
+	
+	public void mouseExited(MouseEvent e) {
+		mouseMoved(e);
+	}
+	
+	public void keyPressed(KeyEvent e) {
+		if (e.getKeyCode()>=0&&e.getKeyCode()<600) {
+			if (keyPressed[e.getKeyCode()][0]==RELEASED||keyPressed[e.getKeyCode()][0]!=WAITING_FOR_RELEASE) 
+				keyPressed[e.getKeyCode()][0]=PRESSED;
+		}
+		e.consume();
+	}
+	
+	public void keyReleased(KeyEvent e) {
+		if (e.getKeyCode()>=0&&e.getKeyCode()<600) {
+			keyPressed[e.getKeyCode()][0]=RELEASED;
+		}
+		e.consume();
+	}
+	
+	public void keyTyped(KeyEvent e) {
+		e.consume();
+	}
+	
+	public void mouseWheelMoved(MouseWheelEvent e) {
+		mouseWheelChange=e.getWheelRotation();
+		mouseAtWheel.x=(e.getX());//+(int)currentComp.getBounds().getX());//
+		mouseAtWheel.y=(e.getY());//)+(int)currentComp.getBounds().getY());
+		e.consume();
+	}
+	
+	public boolean isKeyPressed(int keyCode) {
+		if (keyCode>=0&&keyCode<600) {
+			//return (keyPressed[keyCode][0]==PRESSED);
+			if (keyPressed[keyCode][0]==PRESSED&&keyPressed[keyCode][1]==NORMAL)
+				return true;
+			if (keyPressed[keyCode][0]==PRESSED&&keyPressed[keyCode][1]==INITIAL_PRESS) {
+				keyPressed[keyCode][0]=WAITING_FOR_RELEASE;
+				return true;
+			}
+			return false;
+		} else return false;
+	}
+	
+	public boolean isMousePressed(int button) {
+		button--;
+		if (button>=0&&button<3) {
+			if (mouseButtonPressed[button]==PRESSED) {
+				//mouseButtonPressed[button]=RELEASED;
+				return true;
+			} else return false;
+		}
+		return false;
+	}
+	
+	public int getLastMouseX() {
+		return mouseLocation.x;
+	}
+	
+	public int getLastMouseY() {
+		return mouseLocation.y;
+	}
+	
+	public int getMouseAtLastButtonPressX(int button) {
+		switch(button) {
+			case 1:return mouseAtButton1[0].x;
+			case 2:return mouseAtButton2[0].x;
+			case 3:return mouseAtButton3[0].x;
+			default: return -0;
+		}
+	}
+	
+	public int getMouseAtLastButtonPressY(int button) {
+		switch(button) {
+			case 1:return mouseAtButton1[0].y;
+			case 2:return mouseAtButton2[0].y;
+			case 3:return mouseAtButton3[0].y;
+			default: return -0;
+		}
+	}
+	
+	public int getMouseAtLastButtonReleaseX(int button) {
+		switch(button) {
+			case 1:return mouseAtButton1[1].x;
+			case 2:return mouseAtButton2[1].x;
+			case 3:return mouseAtButton3[1].x;
+			default: return -0;
+		}
+	}
+	
+	public int getMouseAtLastButtonReleaseY(int button) {
+		switch(button) {
+			case 1:return mouseAtButton1[1].y;
+			case 2:return mouseAtButton2[1].y;
+			case 3:return mouseAtButton3[1].y;
+			default: return -0;
+		}
+	}
+	
+	public int getMouseAtLastButtonClickX(int button) {
+		switch(button) {
+			case 1:return mouseAtButton1[2].x;
+			case 2:return mouseAtButton2[2].x;
+			case 3:return mouseAtButton3[2].x;
+			default: return -0;
+		}
+	}
+	
+	public int getMouseAtLastButtonClickY(int button) {
+		switch(button) {
+			case 1:return mouseAtButton1[2].y;
+			case 2:return mouseAtButton2[2].y;
+			case 3:return mouseAtButton3[2].y;
+			default: return -0;
+		}
+	}
+	
+	public int getMouseAtLastWheelMoveX() {
+		return mouseAtWheel.x;
+	}
+	
+	public int getMouseAtLastWheelMoveY() {
+		return mouseAtWheel.y;
+	}
+
+	public int getMouseXChange() {
+		int mouseX=mouseXChange;
+		mouseXChange=0;
+		if (mouseBehavior==RELATIVE)
+			return -mouseX;
+		return mouseX;
+	}
+	
+	public int getMouseYChange() {
+		int mouseY=mouseYChange;
+		mouseYChange=0;
+		if (mouseBehavior==RELATIVE)
+			return -mouseY;
+		return mouseY;
+	}
+	
+	public int getMouseMotionType() {
+		return mouseMotionType;
+	}
+	
+	public int getWheelChange() {
+		int wheel=mouseWheelChange;
+		mouseWheelChange=0;
+		return wheel;
+	}
+	
+	public int getMouseBehavior() {
+		return mouseBehavior;
+	}
+	
+	public int getKeyBehavior(int keyCode) {
+		if (keyCode>=0&&keyCode<600) {
+			return keyPressed[keyCode][1];
+		} else return -1;
+	}
+	
+	public Component getListeningComponent() {
+		return comp;
+	}
+	
+	public int getButtonOnDrag() {
+		return buttonOnDrag;
+	}
+	
+	public void setKeyBehavior(int keyCode,int behavior) {
+		if (keyCode>=0&&keyCode<600) {
+			if(behavior==0||behavior==1) {
+				keyPressed[keyCode][1]=behavior;
+			}
+		}
+	}
+}

test/com/ferox/scene/CubeTest.java

+package com.ferox.scene;
+
+import org.openmali.vecmath.Vector3f;
+
+import com.ferox.BasicApplication;
+import com.ferox.math.BoundSphere;
+import com.ferox.math.Color;
+import com.ferox.renderer.Renderer;
+import com.ferox.resource.Geometry;
+import com.ferox.state.Appearance;
+import com.ferox.state.LightReceiver;
+import com.ferox.state.Material;
+
+public class CubeTest extends BasicApplication {
+	public static final boolean DEBUG = false;
+	public static final boolean USE_VBO = true;
+	
+	public static final int NUM_CUBES = 10000;
+	public static final int BOUNDS = 100;
+	
+	protected Geometry geom;
+	protected Shape firstCube;
+	
+	public static void main(String[] args) {
+		new CubeTest(DEBUG).run();
+	}
+	
+	public CubeTest(boolean debug) {
+		super(debug);
+	}
+
+	@Override
+	protected SceneElement buildScene(Renderer renderer, ViewNode view) {
+		view.getView().setPerspective(60f, 1f, 1f, 1000f);
+		view.getLocalTransform().getTranslation().z = 2f * BOUNDS;
+		
+		Group root = new Group();
+		root.add(view);
+		
+		Light light = new SpotLight();
+		
+		light.setAmbient(new Color(.2f, 2f, 2f, 1f));
+		light.setSpecular(new Color(0f, 0f, 0f, 1f));
+		light.setLocalBounds(new BoundSphere(BOUNDS));
+		light.getLocalTransform().getTranslation().set(0f, 0f, 0f);
+		
+		view.add(light);
+		
+		Appearance a = this.createAppearance();
+		this.geom = buildCube(renderer, 2f, USE_VBO);
+		
+		for (int i = 0; i < NUM_CUBES; i++) {
+			Shape shape = new Shape(geom, a);
+			shape.setLocalBounds(new BoundSphere());
+			Vector3f pos = shape.getLocalTransform().getTranslation();
+			
+			if (i != 0) {
+				// randomly place all but the 1st cube
+				pos.x = (float) (Math.random() * BOUNDS - BOUNDS / 2f);
+				pos.y = (float) (Math.random() * BOUNDS - BOUNDS / 2f);
+				pos.z = (float) (Math.random() * BOUNDS - BOUNDS / 2f);
+			} else
+				this.firstCube = shape;
+			
+			root.add(shape);
+		}
+		
+		return root;
+	}
+	
+	private Appearance createAppearance() {
+		Material m = new Material(new Color(.8f, .5f, .5f));
+		LightReceiver lr = new LightReceiver();
+		m.setSmoothShaded(false);
+		return new Appearance(m, lr);
+	}
+}