public class Render3D {
	private static final String VERTEX_SHADER = "main.vert", FRAGMENT_SHADER = "main.frag";
	/** ModelView and Projection matrices */
	public static Matrix4f projection, modelview;
	
	private static Matrix4f oldModelview = new Matrix4f();
	
	/** Draw distance and field-of-view to use for rendering */
	public static float drawDistance = 3000000.0f, fov =  45.0f;
	
	/** old aspect ratio is saved to know when to change the projection matrix */
	private static float oldAspect = (float) DisplayHelper.windowWidth
			/ (float) DisplayHelper.windowHeight;
	
	/** the shader program to use */
	public static GLSLProgram program;
	
	/** default diffuse color */
	private static final Vector3f DEFAULT_KD = new Vector3f(0.5f, 0.5f, 0.5f);
	/** default ambient color */
	private static final Vector3f DEFAULT_KA = new Vector3f(0.5f, 0.5f, 0.5f);
	/** default specular color */
	private static final Vector3f DEFAULT_KS = new Vector3f(0.8f, 0.8f, 0.8f);
	/** default shiny factor */
	private static final float DEFAULT_SHINY = 50.0f;
	
	/**
	 * Renders the 3D scene
	 */
	public static void render3DScene(){
		setUp3DRender();
		
		transformToCamera();
		
		if(Entities.skybox != null)
			drawSkybox();
		
		setUpLights();
		
		drawLights();
		
		drawDynamicEntities();
		
		drawPassiveEntities();
		
		drawPlayer();
		
		/*
		 * Modelview matrix
		 * Rotate, translate, transpose
		 * Why so confusing
		 */
	}
	
	/**
	 * Sets up for doing a 3D render
	 */
	private static void setUp3DRender(){
		// make sure we're using our shader
		program.use();
		// set the dafault material, just in case
		useDefaultMaterial();
		
		// calculate the current aspect ratio and change the projection matrix if it's changed
		float aspect = (float) DisplayHelper.windowWidth
				/ (float) DisplayHelper.windowHeight;
		
		if(aspect != oldAspect){
			projection = MatrixHelper.perspective(fov, aspect, 1.0f, drawDistance);
			program.setUniform("ProjectionMatrix", projection);
			oldAspect = aspect;
		}
		
		GL11.glDisable(GL11.GL_BLEND);
		GL11.glEnable(GL11.GL_DEPTH_TEST);
		
		modelview.setIdentity();
	}
	
	/**
	 * Sets the current material being used for rendering
	 * @param mat Material to use
	 */
	public static void setCurrentMaterial(Material mat){
		program.setUniform("Material.Kd" , mat.getKd());
		program.setUniform("Material.Ka", mat.getKa());
		program.setUniform("Material.Ks", mat.getKs());
		program.setUniform("Material.Shininess", mat.getShininess());
	}
	
	/**
	 * Set the current material to the default material
	 */
	public static void useDefaultMaterial(){
		program.setUniform("Material.Kd" , DEFAULT_KD);
		program.setUniform("Material.Ka", DEFAULT_KA);
		program.setUniform("Material.Ks", DEFAULT_KS);
		program.setUniform("Material.Shininess", DEFAULT_SHINY);
	}
	
	/**
	 * Transforms the ModelView matrix to represent the camera's location and rotation
	 */
	private static void transformToCamera(){
		// translate to the camera's location
		modelview.translate(new Vector3f(Entities.camera.xOffset, Entities.camera.yOffset, -Entities.camera.zoom));
		
		// reverse the camera's quaternion (we want to look OUT from the camera)
		Quaternion reverse = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		Quaternion.negate(Entities.camera.rotation, reverse);
		Matrix4f.mul(modelview, QuaternionHelper.toMatrix(reverse), modelview);
	}
	
	/**
	 * Sets up lights for rendering
	 */
	private static void setUpLights(){
		// FIXME only one light supported right now!
		if(Entities.lights.size() > 1)
			System.out.println("More than one light! Multiple lighting not yet implemented.");
		Light l = Entities.lights.iterator().next();
		float transX = Entities.camera.location.x - l.location.x;
		float transY = Entities.camera.location.y - l.location.y;
		float transZ = Entities.camera.location.z - l.location.z;
		
		// crazy quaternion and vector math to get the light into world coordinates
		Quaternion reverse = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		Quaternion.negate(Entities.camera.rotation, reverse);
		Vector3f rotated = QuaternionHelper.rotateVectorByQuaternion(new Vector3f(transX, transY, transZ), reverse);
		
		// set uniforms
		program.setUniform("Light.LightPosition", new Vector4f(rotated.x, rotated.y, rotated.z, 0.0f));
		program.setUniform("Light.LightIntensity", l.intensity);
		program.setUniform("Light.LightEnabled", true);
	}
	
	/**
	 * Draws the skybox
	 */
	private static void drawSkybox(){
		program.setUniform("Light.LightEnabled", false);
			
		float transX = Entities.camera.location.x - Entities.skybox.location.x;
		float transY = Entities.camera.location.y - Entities.skybox.location.y;
		float transZ = Entities.camera.location.z - Entities.skybox.location.z;
		
		oldModelview.load(modelview);{
			modelview.translate(new Vector3f(transX, transY, transZ));
			
			Matrix4f.mul(modelview, QuaternionHelper.toMatrix(Entities.skybox.rotation), modelview);
			
			program.setUniform("ModelViewMatrix", modelview);
			Entities.skybox.draw();
		}modelview.load(oldModelview);
		program.setUniform("Light.LightEnabled", true);
	}
	
	/**
	 * Draws any lights
	 */
	private static void drawLights(){
		program.setUniform("Light.LightEnabled", false);
		Iterator<Light> lightIterator = Entities.lights.iterator();
		while(lightIterator.hasNext()){
			Light light = lightIterator.next();
			
			float transX = Entities.camera.location.x - light.location.x;
			float transY = Entities.camera.location.y - light.location.y;
			float transZ = Entities.camera.location.z - light.location.z;
			
			oldModelview.load(modelview);{
				modelview.translate(new Vector3f(transX, transY, transZ));
				
				program.setUniform("ModelViewMatrix", modelview);
				light.draw();
			}modelview.load(oldModelview);
		}
		program.setUniform("Light.LightEnabled", true);
	}
	
	/**
	 * Draws any passive entities
	 */
	private static void drawPassiveEntities(){
		Iterator<Entity> entityIterator = Entities.passiveEntities.iterator();
		while(entityIterator.hasNext()){
			// FIXME might be a better spot to put this
			useDefaultMaterial();
			
			Entity ent = entityIterator.next();
			
			float transX = Entities.camera.location.x - ent.location.x;
			float transY = Entities.camera.location.y - ent.location.y;
			float transZ = Entities.camera.location.z - ent.location.z;
			
			oldModelview.load(modelview);{
				modelview.translate(new Vector3f(transX, transY, transZ));
				
				Matrix4f.mul(modelview, QuaternionHelper.toMatrix(ent.rotation), modelview);
				
				program.setUniform("ModelViewMatrix", modelview);
				ent.draw();
			}modelview.load(oldModelview);
		}
	}
	
	/**
	 * Draws any dynamic entities
	 */
	private static void drawDynamicEntities(){
		Iterator<DynamicEntity> entityIterator = Entities.dynamicEntities.iterator();
		while(entityIterator.hasNext()){
			DynamicEntity ent = entityIterator.next();
			
			float transX = Entities.camera.location.x - ent.location.x;
			float transY = Entities.camera.location.y - ent.location.y;
			float transZ = Entities.camera.location.z - ent.location.z;
			
			oldModelview.load(modelview);{
				modelview.translate(new Vector3f(transX, transY, transZ));
				
				if(Physics.drawDebug){
					ent.drawPhysicsDebug();
				}
				
				Matrix4f.mul(modelview, QuaternionHelper.toMatrix(ent.rotation), modelview);
				
				program.setUniform("ModelViewMatrix", modelview);
				ent.draw();
			}modelview.load(oldModelview);
		}
	}
	
	/**
	 * Draws the player
	 */
	private static void drawPlayer(){
		float transX = Entities.camera.location.x - Entities.player.location.x;
		float transY = Entities.camera.location.y - Entities.player.location.y;
		float transZ = Entities.camera.location.z - Entities.player.location.z;
		
		oldModelview.load(modelview);{
			modelview.translate(new Vector3f(transX, transY, transZ));
			
			Matrix4f.mul(modelview, QuaternionHelper.toMatrix(Entities.player.rotation), modelview);
			
			program.setUniform("ModelViewMatrix", modelview);
			Entities.player.draw();
		}modelview.load(oldModelview);
	}
	
	/**
	 * This must be called before any 3D rendering is done!
	 * Loads in the shaders and initializes matrices
	 */
	public static void init(){
		modelview = new Matrix4f();
		
		// create vertex shader
		GLSLShader vertShader = new GLSLShader(ShaderTypes.VERTEX);
		String vertFile = Paths.SHADER_PATH.path() + VERTEX_SHADER;
		if (!vertShader.compileShaderFromFile(vertFile))
			System.out.println(vertShader.log());

		// create fragment shader
		GLSLShader fragShader = new GLSLShader(ShaderTypes.FRAGMENT);
		String fragFile = Paths.SHADER_PATH.path() + FRAGMENT_SHADER;
		if (!fragShader.compileShaderFromFile(fragFile)) {
			System.out.println(fragShader.log());
		}

		// create and use program
		program = new GLSLProgram();
		program.addShader(fragShader);
		program.addShader(vertShader);
		program.link();
		program.use();
		
		// calculate the current aspect ratio
		float oldAspect = (float) DisplayHelper.windowWidth
				/ (float) DisplayHelper.windowHeight;
		
		projection = MatrixHelper.perspective(fov, oldAspect, 1.0f, drawDistance);
		program.setUniform("ProjectionMatrix", projection);
	}
} public enum KeyBindings {
	CONTROL_FORWARD(new Button[]{Keys.W}),
	CONTROL_BACKWARD(new Button[]{Keys.S}),
	CONTROL_LEFT(new Button[]{Keys.A}),
	CONTROL_RIGHT(new Button[]{Keys.D}),
	CONTROL_DESCEND(new Button[]{Keys.LSHIFT}),
	CONTROL_ASCEND(new Button[]{Keys.SPACE}),
	CONTROL_ROLL_LEFT(new Button[]{Keys.Q}),
	CONTROL_ROLL_RIGHT(new Button[]{Keys.E}),
	CONTROL_STABILIZE(new Button[]{Keys.LCONTROL}),
	CONTROL_STOP(new Button[]{Keys.F}),
	CONTROL_BOOST(new Button[]{Keys.Z}),
	
	SYS_CAMERA_MODE(new Button[]{Keys.C}),
	SYS_BUILD_MODE(new Button[]{Keys.B}),
	SYS_CONSOLE(new Button[]{Keys.GRAVE}),
	SYS_COMMAND(new Button[]{Keys.SLASH}),
	SYS_CHAT(new Button[]{Keys.T}),
	SYS_PAUSE(new Button[]{Keys.ESCAPE}),
	SYS_FULLSCREEN(new Button[]{Keys.F11}),
	SYS_DEBUG(new Button[]{Keys.F3}),
	SYS_SCREENSHOT(new Button[]{Keys.F2}),
	SYS_DEBUG_PHYSICS(new Button[]{Keys.F4}),
	
	SYS_CONSOLE_PREVIOUS_COMMAND(new Button[]{Keys.UP}),
	SYS_CONSOLE_NEXT_COMMAND(new Button[]{Keys.DOWN}),
	SYS_CONSOLE_SUBMIT(new Button[]{Keys.RETURN}),
	SYS_CONSOLE_BACKSPACE(new Button[]{Keys.BACK}),
	SYS_CONSOLE_SCROLL_UP(new Button[]{Keys.NEXT}),
	SYS_CONSOLE_SCROLL_DOWN(new Button[]{Keys.PRIOR}),
	
	BUILDER_OPEN_ADD_MENU(new Button[]{Keys.TAB});
	
	/** the buttons that activate this binding */
	private Button[] buttons;
	
	/**
	 * KeyBindings constructor
	 * @param buttons Buttons to use for the binding
	 */
	private KeyBindings(Button[] buttons){
		this.buttons = buttons;
	}
	
	/**
	 * @return Whether or not the binding is being pressed
	 */
	public boolean isPressed(){
		for(Button button : buttons){
			if(button.isPressed())
				return true;
		}
		return false;
	}
	
	/**
	 * @return True if this is the first call to pressedOnce since the key was pressed, else false
	 */
	public boolean pressedOnce(){
		for(Button button : buttons){
			if(button.pressedOnce())
				return true;
		}
		return false;
	}
	
	/**
	 * Set the binding to have a new set of buttons
	 * @param newKey Key to set binding to
	 */
	public void setButtons(Button[] newButtons){
		this.buttons = newButtons;
	}
	
	/**
	 * Adds a button that will activate the binding
	 * @param newButton Button to add to binding
	 */
	public void addButton(Button newButton){
		Button[] newKeys = new Keys[buttons.length + 1];
		for(int i = 0; i < buttons.length; i++)
			newKeys[i] = buttons[i];
		newKeys[buttons.length] = newButton;
		this.setButtons(newKeys);
	}
	
	/**
	 * Removes a button that activated the binding (does nothing if button doesn't already activate binding)
	 * @param oldButton Button to remove
	 */
	public void removeButton(Button oldButton){
		// index of oldButton in buttons
		int bIndex = -1;
		
		// find bIndex
		for(int i = 0; i < buttons.length; i++){
			if(buttons[i] == oldButton)
				bIndex = i;
		}
		
		// found oldButton
		if(bIndex != -1){
			Button[] newButtons = new Button[buttons.length - 1];
			for(int i = 0; i < buttons.length; i++){
				// skip the button being removed
				if(i == bIndex)
					continue;
				
				// add old buttons, minus the old button
				if(i < bIndex)
					newButtons[i] = buttons[i];
				else
					newButtons[i - 1] = buttons[i];
			}
			
			this.setButtons(newButtons);
		}
	}
	
	/**
	 * Checks to see if the binding is activated by the given button
	 * @param other Button to check for
	 * @return Whether or not the button activates the binding
	 */
	public boolean isButton(Button other){
		for(Button key : buttons){
			if(key == other)
				return true;
		}
		return false;
	}
	
	/**
	 * To string, or not to string -- that is the question
	 * Whether 'tis nobler in the mind to suffer
	 * The slings and arrows of outrageous fortune
	 * Or to take arms against a sea of troubles
	 * And by opposing them. To die, to sleep--
	 * No more
	 * 
	 * Shakespeare while you code!
	 */
	public String toString(){
		String ret = "";
		for(int i = 0; i < buttons.length; i++){
			ret += buttons[i].toString() + " ";
		}
		
		return ret;
	}
} public class MouseManager {
	/** whether or not button a is being pressed */
	public static boolean button0, button1, button2;

	/** how much the mouse has moved on the X axis */
	public static float dx;
	/** how much the mouse has move on the Y axis */
	public static float dy;
	/** how much the mouse wheel has moved */
	public static float wheel;
	/** the mouse's current location */
	public static int x, y;

	public static boolean inverted = false;

	/**
	 * sensitivity values for mouse movement
	 * NOTE:The mouse movement is divided by these 
	 * So setting them to 10 would result in 1/10th of the mouse movement 
	 * (higher numbers == less sensitivity)
	 */
	public static float verticalSensitivity = 10.0f;
	public static float horizontalSensitivity = 10.0f;

	public void update() {
		while (Mouse.next()) {
			String eventButton = Mouse.getButtonName(Mouse.getEventButton());
			if (eventButton != null) {
				if (Mouse.getEventButtonState()) {
					if (eventButton.equals("BUTTON0")) {
						button0 = true;
					}
					if (eventButton.equals("BUTTON1")) {
						button1 = true;
					}
					if (eventButton.equals("BUTTON2")) {
						button2 = true;
					}
				} else {
					if (eventButton.equals("BUTTON0")) {
						button0 = false;
					}
					if (eventButton.equals("BUTTON1")) {
						button1 = false;
					}
					if (eventButton.equals("BUTTON2")) {
						button2 = false;
					}
				}
			}
		}

		// Only update dx and dy if the mouse is grabbed
		if (Mouse.isGrabbed()) {
			dx = (-(float) Mouse.getDX()) / verticalSensitivity;
			if(!inverted)
				dy = ((float) Mouse.getDY()) / horizontalSensitivity;
			else
				dy = -((float) Mouse.getDY()) / horizontalSensitivity;
		} else {
			dx = 0.0f;
			dy = 0.0f;
		}

		// grab wheel change amount
		wheel = (float) Mouse.getDWheel();

		x = Mouse.getX();
		// we want the top-left corner to be 0,0
		y = DisplayHelper.windowHeight - Mouse.getY();
	}

} public class Builder {
	/** Camera object for this builder */
	private Camera camera;

	/** What the builder is looking at */
	public DynamicEntity lookingAt;
	/** whether or not what the builder is looking at has been grabbed */
	public boolean leftGrabbed = false, rightGrabbed = false;

	/** Menu for selecting a model to add */
	Picker<Models> picker;
	/** Buttons for adding models */
	MenuButton addButton, cancelButton;

	boolean modelSelected = false;

	/**
	 * Builder constructor
	 * 
	 * @param camera
	 *            Camera that this builder will see from
	 */
	public Builder(Camera camera) {
		this.camera = camera;

		// create the menu for selecting a model
		picker = new Picker<Models>(-50, -20, 20, 200, Models.values(),
				Textures.MENU_PICKER_ACTIVE, Textures.MENU_PICKER_MOUSEOVER,
				Textures.MENU_PICKER_PRESSED, Textures.MENU_PICKER_SELECTED);

		// create button to add model to world
		addButton = new MenuButton("Add", 119, 28, 115, -17);
		addButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (picker.itemHasBeenSelected()) {
					addDynamicEntity(picker.getSelectedItem());

					closePickerMenu();
				}
			}
		});

		// create button to cancel adding a model
		cancelButton = new MenuButton("Cancel", 119, 28, 115, 17);
		cancelButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				closePickerMenu();
			}
		});
	}

	/**
	 * Updates the builder (called when the camera is in builder mode)
	 */
	public void update(float timeStep) {
		whatsTheCameraLookingAt();

		if (lookingAt != null && (leftGrabbed || rightGrabbed))
			grabLogic(timeStep);

		if (Keys.P.isPressed())
			addRandomPlanet();
		
		if(Keys.I.isPressed())
			addRandomAsteroid();
		
		if(Keys.O.isPressed()){
			addRandomDiamond();
			addRandomDiamond();
		}

		if (KeyBindings.BUILDER_OPEN_ADD_MENU.pressedOnce()){
			if(GUI.menuUp)
				closePickerMenu();
			else
				openPickerMenu();
		}
	}

	/**
	 * Opens the menu to select a model
	 */
	private void openPickerMenu() {
		GUI.addGUIObject(picker);
		GUI.addGUIObject(addButton);
		GUI.addGUIObject(cancelButton);

		GUI.menuUp = true;
	}
	
	/**
	 * Closes the menu to select a model
	 */
	private void closePickerMenu(){
		GUI.removeGUIObject(picker);
		GUI.removeGUIObject(addButton);
		GUI.removeGUIObject(cancelButton);

		GUI.menuUp = false;
	}

	/**
	 * Adds a dynamic entity to the world right in front of the camera
	 * @param model Model to use when adding entity
	 */
	private void addDynamicEntity(Models model) {
		float mass = 50.0f;
		float restitution = 1.0f;

		Vector3f location = new Vector3f(0.0f, 0.0f, 0.0f);

		Vector3f downInFront = QuaternionHelper.rotateVectorByQuaternion(
				new Vector3f(0.0f, 0.0f, 15.0f), camera.rotation);

		Vector3f.add(camera.location, downInFront, location);

		Quaternion rotation = camera.rotation;

		DynamicEntity ent = new DynamicEntity(location, rotation, model, mass,
				restitution);

		Entities.addDynamicEntity(ent);
		
		ent.rigidBody.activate();
	}

	/**
	 * Figures out what's at the center of the camera. Moves what's being looked
	 * at if the left mouse button is down, rotates what's being looked at if
	 * the right mouse button is down
	 */
	private void whatsTheCameraLookingAt() {
		if (!leftGrabbed && !rightGrabbed) {
			// check if anything has been grabbed
			ClosestRayResultCallback cameraRay = camera.rayTestAtCenter();

			if (cameraRay.hasHit()) {
				lookingAt = (DynamicEntity) cameraRay.collisionObject
						.getUserPointer();
				
				if(lookingAt == camera)
					lookingAt = null;

				// grab the entity if the mouse button is down
				if (!leftGrabbed && MouseManager.button0) {
					// stop any linear velocity
					lookingAt.rigidBody
							.setLinearVelocity(new javax.vecmath.Vector3f(0.0f,
									0.0f, 0.0f));
					leftGrabbed = true;
				}

				if (!rightGrabbed && MouseManager.button1) {
					// stop any angular velocity
					lookingAt.rigidBody
							.setAngularVelocity(new javax.vecmath.Vector3f(
									0.0f, 0.0f, 0.0f));
					rightGrabbed = true;
				}
			} else {
				// looking at nothing
				lookingAt = null;
			}
		}
	}

	/**
	 * Moves or rotates what is grabbed
	 * 
	 * @param timeStep
	 *            Amount of time passed (gotten from camera)
	 */
	private void grabLogic(float timeStep) {
		// activate the rigid body if it's not active (since we're directly
		// modifying its world transform and not just it's velocities)
		if (!lookingAt.rigidBody.isActive())
			lookingAt.rigidBody.activate();

		// move logic
		if (leftGrabbed) {
			if (!MouseManager.button0) {
				leftGrabbed = false;
			} else {
				// how far to move what we're looking at
				float dx = MouseManager.dx * 10;
				float dy = -MouseManager.dy * 10;
				float dz = MouseManager.wheel / 10;

				// rotate the distance by the camera's rotation
				Vector3f impulse = QuaternionHelper.rotateVectorByQuaternion(
						new Vector3f(dx, dy, dz), Entities.camera.rotation);

				// get the entity's transform
				Transform trans = new Transform();
				lookingAt.rigidBody.getWorldTransform(trans);

				trans.origin.add(new javax.vecmath.Vector3f(impulse.x,
						impulse.y, impulse.z));

				// set the transform to the new location
				lookingAt.rigidBody.setWorldTransform(trans);

				// set the location so that it gets rendered properly
				// (otherwise, the rendering location wouldn't be update until
				// the end of the next physics world tick, which would be one
				// frame too late)
				lookingAt.location.set(trans.origin.x, trans.origin.y,
						trans.origin.z);
			}

			// check for right grab
			if (MouseManager.button1) {
				lookingAt.rigidBody
						.setAngularVelocity(new javax.vecmath.Vector3f(0.0f,
								0.0f, 0.0f));
				rightGrabbed = true;
			}
		}

		// rotate logic
		if (rightGrabbed) {
			if (!MouseManager.button1) {
				rightGrabbed = false;
			} else if (MouseManager.dx != 0 || MouseManager.dy != 0
					|| MouseManager.wheel != 0) {
				Transform trans = new Transform();
				lookingAt.rigidBody.getWorldTransform(trans);

				Quat4f rot = new Quat4f();
				trans.getRotation(rot);

				// how much rotation to apply
				Vector3f amount = new Vector3f(-MouseManager.dy * 10,
						MouseManager.dx * 10, MouseManager.wheel / 10);

				// rotate amount by the camera's rotation
				Vector3f impulse = QuaternionHelper.rotateVectorByQuaternion(
						amount, Entities.camera.rotation);

				// rotate entity's rotation by our rotated amount
				Quaternion newRot = QuaternionHelper.rotate(new Quaternion(
						rot.x, rot.y, rot.z, rot.w), impulse);

				// set the rotation to the new rotation
				trans.setRotation(new Quat4f(newRot.x, newRot.y, newRot.z,
						newRot.w));

				// set the entity's world transform
				lookingAt.rigidBody.setWorldTransform(trans);

				// set the rotation so that it gets rendered properly
				// (otherwise, the rendering rotation wouldn't be update until
				// the end of the next physics world tick, which would be one
				// frame too late)
				lookingAt.rotation.set(newRot.x, newRot.y, newRot.z, newRot.w);
			}

			// check for left grab
			if (MouseManager.button0) {
				lookingAt.rigidBody
						.setLinearVelocity(new javax.vecmath.Vector3f(0.0f,
								0.0f, 0.0f));
				leftGrabbed = true;
			}
		}

		// move the entity with the camera
		moveEntityWithCamera(timeStep);
	}

	/**
	 * Moves the grabbed entity with the camera. Note that this is nearly
	 * identical to the <code>freeMode(float timeStep)</code> method in the
	 * {@link Camera} class.
	 * 
	 * @param timeStep
	 *            Amount of time passed (gotten from camera)
	 */
	private void moveEntityWithCamera(float timeStep) {
		javax.vecmath.Vector3f linvec = new javax.vecmath.Vector3f();
		camera.rigidBody.getLinearVelocity(linvec);
		lookingAt.rigidBody.setLinearVelocity(linvec);
	}
	
    /**
     * Adds a random sphere to the world, right in front of the camera
     */
    private void addRandomPlanet() {
            Random randy = new Random();
            float sphereSize = randy.nextInt(200) / 10.0f;

            Textures sphereTexture;
            String tex;

            switch (randy.nextInt(4)) {
            case 0:
                    sphereTexture = Textures.EARTH;
                    tex = "Earth";
                    break;
            case 1:
                    sphereTexture = Textures.MERCURY;
                    tex = "Mercury";
                    break;
            case 2:
                    sphereTexture = Textures.VENUS;
                    tex = "Venus";
                    break;
            case 3:
                    sphereTexture = Textures.MARS;
                    tex = "Mars";
                    break;
            default:
                    sphereTexture = Textures.EARTH;
                    tex = "Earth";
            }

            float sphereX = randy.nextFloat() * 75.0f;
            float sphereY = randy.nextFloat() * 75.0f;
            float sphereZ = -100.0f;
            Vector3f sphereLocation = new Vector3f(sphereX, sphereY, sphereZ);

            // put the sphere right in front of the camera
            Vector3f downInFront = QuaternionHelper.rotateVectorByQuaternion(
                            new Vector3f(0.0f, 0.0f, 300.0f), camera.rotation);

            Vector3f.add(camera.location, downInFront, downInFront);

            Vector3f.add(sphereLocation, downInFront, sphereLocation);

            Quaternion sphereRotation = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);

            // float sphereMass = randy.nextFloat() * 10.0f;
            float sphereMass = sphereSize * 2.0f;

            Planet p = new Planet(sphereLocation, sphereRotation, sphereSize,
                            sphereMass, 0.0f, sphereTexture);
            p.type = tex;

            Entities.addDynamicEntity(p);
    }

	/**
	 * Adds a random sphere to the world, right in front of the camera
	 */
	private void addRandomAsteroid() {
		Random randy = new Random();
		float asteroidSize = randy.nextInt(500) / 10.0f;

		float asteroidX = randy.nextFloat() * 1000.0f;
		float asteroidY = randy.nextFloat() * 1000.0f;
		float asteroidZ = -100.0f;
		Vector3f asteroidLocation = new Vector3f(asteroidX, asteroidY, asteroidZ);

		// put the sphere right in front of the camera
		Vector3f downInFront = QuaternionHelper.rotateVectorByQuaternion(
				new Vector3f(0.0f, 0.0f, 300.0f), camera.rotation);

		Vector3f.add(camera.location, downInFront, downInFront);

		Vector3f.add(asteroidLocation, downInFront, asteroidLocation);

		Quaternion asteroidRotation = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);

		Asteroid p = new Asteroid(asteroidLocation, asteroidRotation, asteroidSize, null);

		Entities.addDynamicEntity(p);
	}
	
	/**
	 * Adds a random diamond to the world, right in front of the camera
	 */
	private void addRandomDiamond() {
		Random randy = new Random();

		float diamondX = randy.nextFloat() * 10.0f;
		float diamondY = randy.nextFloat() * 10.0f;
		float diamondZ = randy.nextFloat() * 100.0f;
		
		if(randy.nextBoolean()) diamondX = -diamondX;
		if(randy.nextBoolean()) diamondY = -diamondY;
		
		Vector3f diamondLocation = new Vector3f();

		// put the sphere right in front of the camera
		Vector3f downInFront = QuaternionHelper.rotateVectorByQuaternion(
				new Vector3f(diamondX, diamondY, diamondZ), camera.rotation);

		Vector3f.add(camera.location, downInFront, diamondLocation);

		Quaternion diamondRotation = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		
		float xRot = randy.nextFloat() * 100.0f;
		float yRot = randy.nextFloat() * 100.0f;
		float zRot = randy.nextFloat() * 100.0f;
		
		diamondRotation = QuaternionHelper.rotate(diamondRotation, new Vector3f(xRot,yRot, zRot));
		
		Diamond d = new Diamond(diamondLocation, diamondRotation, 0.3f);

		Entities.addDynamicEntity(d);
	}
} public class CollisionTypes {
	public static final short NOTHING = 0;
	public static final short SHIP = bit(0);
	public static final short WALL = bit(1);
	public static final short PLANET = bit(2);
	public static final short PROJECTILE = bit(3);
	public static final short PICKUP = bit(4);
	
	public static final short EVERYTHING = (short)(SHIP | WALL | PLANET | PROJECTILE | PICKUP); 
	
	private static short bit(int x) {
		return (short) (1 << x);
	}
} public class Physics {
	/** maximum number of substeps to do on each tick */
	private final static int SUBSTEPS = 10;

	/** the discrete dynamics world */
	public static DiscreteDynamicsWorld dynamicsWorld;

	/** broadphase interface */
	public static BroadphaseInterface broadphase;

	/** collision dispatcher */
	public static CollisionDispatcher dispatcher;

	/** impulse constraint solver */
	public static SequentialImpulseConstraintSolver solver;

	/** clock to use for framerate independence */
	private static Clock clock = new Clock();

	/** to keep the debug key from being held */
	private static boolean debugDown = false;

	/** whether or not to draw physics debug info */
	public static boolean drawDebug = false;

	/**
	 * Initializes the physics engine
	 */
	public static void initPhysics() {
		// broadphase interface
		broadphase = new DbvtBroadphase();

		// collision configuration and dispatcher
		DefaultCollisionConfiguration collisionConfiguration = new DefaultCollisionConfiguration();
		dispatcher = new CollisionDispatcher(collisionConfiguration);

		// wat
		solver = new SequentialImpulseConstraintSolver();

		// the world everything is in
		dynamicsWorld = new DiscreteDynamicsWorld(dispatcher, broadphase,
				solver, collisionConfiguration);

		// no gravity, we're in space!
		dynamicsWorld.setGravity(new Vector3f(0, 0, 0));

		// see the PhysicsDebugDrawer class
		dynamicsWorld.setDebugDrawer(new PhysicsDebugDrawer());
		
		Physics.dynamicsWorld.setInternalTickCallback(new DynamicEntityCallback(), null);
	}

	/**
	 * Updates the dynamics world
	 */
	public static void update() {
		dynamicsWorld.stepSimulation(getDeltaTimeMicroseconds() / 1000000.0f,
				SUBSTEPS);

		// handle the physics debug key
		if (KeyBindings.SYS_DEBUG_PHYSICS.isPressed() && !debugDown) {
			drawDebug = !drawDebug;
			debugDown = true;
		}
		if (!KeyBindings.SYS_DEBUG_PHYSICS.isPressed()) {
			debugDown = false;
		}
	}

	/**
	 * Cleans up all the physics stuff
	 */
	public static void cleanup() {
		dynamicsWorld.destroy();
		dynamicsWorld = null;
		broadphase = null;
		dispatcher = null;
		solver = null;

	}

	/**
	 * @return Change in time in microseconds
	 */
	private static float getDeltaTimeMicroseconds() {
		float delta = clock.getTimeMicroseconds();
		clock.reset();
		return delta;
	}
	
	public static void convexSweepTest(DynamicEntity origin, org.lwjgl.util.vector.Vector3f distance, ConvexShape shape, CollisionWorld.ConvexResultCallback callback){
		// grab current world transform
		Transform from = new Transform(), to = new Transform();
		origin.rigidBody.getWorldTransform(to);
		origin.rigidBody.getWorldTransform(from);
		
		// set 'to' transform to be in front of the player
		org.lwjgl.util.vector.Vector3f forward = QuaternionHelper.rotateVectorByQuaternion(distance, origin.rotation);
		from.origin.add(new javax.vecmath.Vector3f(forward.x, forward.y, forward.z));
		
		dynamicsWorld.convexSweepTest(shape, to, from, callback);	
	}
} public class DisplayHelper {
	private static final String ICON_PATH = "res/images/";
	
	/** How many samples to use for multisample anti-aliasing */
	public static final int MSAA_SAMPLES = 4;
	
	/**
	 * these change whenever the screen size is changed. The values that they
	 * are now will be the initial size of the window.
	 */
	public static int windowWidth = 1024;
	public static int windowHeight = 768;
	
	/** the minimum size that the window can be */
	private static final int MIN_WINDOW_WIDTH = 200;
	private static final int MIN_WINDOW_HEIGHT = 200;
	
	/** the window's title */
	private static String windowTitle = "Spaceout Pre-alpha " + Runner.VERSION;

	/** target fps (might not be reached on slower machines) */
	public static int targetFPS = 60;
	
	public static boolean vsync = false;

	/** this is just to handle the resizeable window */
	private final static AtomicReference<Dimension> newCanvasSize = new AtomicReference<Dimension>();

	/** the frame everything is in so that the window is resizeable */
	public static Frame frame;
	
	/**
	 * used to preserve the size of the window when switching between fullscreen
	 * and windowed
	 */
	private static int oldWindowHeight;
	private static int oldWindowWidth;
	
	/** whether or not the game is running in fullscreen mode */
	public static boolean fullscreen = false;
	
	/** boolean to check for fullscreen key being held down */
	private static boolean fullscreenDown = false;
	
	/**
	 * Creates a window to render to
	 */
	public static void createWindow(){
		// create the frame that holds everything
		frame = new Frame(windowTitle);
		frame.setLayout(new BorderLayout());
		frame.setBackground(java.awt.Color.black);
		frame.setIconImage(Toolkit.getDefaultToolkit().getImage(
				ICON_PATH + "icon.png"));
		final Canvas canvas = new Canvas();

		// add listeners to the canvas
		// this one handles resizes
		canvas.addComponentListener(new ComponentAdapter() {
			@Override
			public void componentResized(ComponentEvent e) {
				newCanvasSize.set(canvas.getSize());
			}
		});

		// the canvas is the only thing we want focus on
		frame.addWindowFocusListener(new WindowAdapter() {
			@Override
			public void windowGainedFocus(WindowEvent e) {
				canvas.requestFocusInWindow();
			}
		});

		// handles closing the window
		frame.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				Runner.done = true;
			}
		});

		// add the canvas to the frame
		frame.add(canvas, BorderLayout.CENTER);

		try {
			// set the display to use the canvas
			Display.setParent(canvas);
			Display.setVSyncEnabled(vsync);

			// pretty self explanatory, boilerplate awt frame creation
			frame.setPreferredSize(new Dimension(windowWidth, windowHeight));
			frame.setMinimumSize(new Dimension(MIN_WINDOW_WIDTH,
					MIN_WINDOW_HEIGHT));
			frame.pack();
			frame.setVisible(true);
			
			// for creating a display with multisampling
			PixelFormat pf = new PixelFormat().withSamples(MSAA_SAMPLES);
			
			try{
				Display.create(pf);
			} catch(LWJGLException e){
				System.out.println("Couldn't initialize display with " + MSAA_SAMPLES + "x MSAA, initializing with no anti-aliasing instead");
				Display.create();
			}
		} catch (LWJGLException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Resizes the window if it's dimensions have been changed
	 */
	public static void resizeWindow(){
		Dimension d = new Dimension();

		/*
		 * newCanvasSize is updated by the listener on the canvas that
		 * everything is being displayed on. This gets the current
		 * newCanvasSize, and then sets it to null. This way, whenever the size
		 * hasn't changed newCanvasSize is null and nothing happens. If the size
		 * has changed, newCanvasSize will contain the new size and won't be
		 * null.
		 */
		d = newCanvasSize.getAndSet(null);

		if (d != null) {
			// change the height and width
			windowWidth = (int) d.getWidth();
			windowHeight = (int) d.getHeight();
			// reset the viewport
			GL11.glViewport(0, 0, windowWidth, windowHeight);
		}
		
		doFullscreenLogic();
	}
	
	/**
	 * Checks to see if the fullscreen key has been pressed and, if it has, acts accordingly
	 */
	private static void doFullscreenLogic(){
		//check for fullscreen key press
		if (KeyBindings.SYS_FULLSCREEN.isPressed() && !fullscreenDown) {
			fullscreen = !fullscreen;
			fullscreenDown = true;
		}

		if (!KeyBindings.SYS_FULLSCREEN.isPressed()) {
			fullscreenDown = false;
		}
		
		
		//take care of necessary OpenGL calls
		try {
			// switch from windowed to fullscreen
			if (fullscreen && !Display.isFullscreen()) {
				// save the window width and height
				oldWindowWidth = windowWidth;
				oldWindowHeight = windowHeight;
				// fullscreen the display
				Display.setFullscreen(fullscreen);
				// set the width and height
				DisplayMode dm = Display.getDisplayMode();
				windowWidth = dm.getWidth();
				windowHeight = dm.getHeight();
				// change the viewport
				GL11.glViewport(0, 0, windowWidth, windowHeight);
				
				// switch from fullscreen to windowed
			} else if (!fullscreen && Display.isFullscreen()) {
				// get the window width and height from before fullscreen
				windowWidth = oldWindowWidth;
				windowHeight = oldWindowHeight;
				// un-fullscreen the display
				Display.setFullscreen(fullscreen);
				// change the viewport
				GL11.glViewport(0, 0, windowWidth, windowHeight);
			}
		} catch (LWJGLException e) {
			e.printStackTrace();
		}

		// make sure the frame's title is right (this allows for a changing title!)
		if (!(frame.getTitle().equals(windowTitle)))
			frame.setTitle(windowTitle);
		
		// make sure the display's title is right
		if(!(Display.getTitle().equals(windowTitle)))
			Display.setTitle(windowTitle);
	}
} public class QuaternionHelper {
	final static float PIOVER180 = ((float) Math.PI) / 180.0f;

	/**
	 * Converts angles to a quaternion
	 * @param pitch X axis rotation
	 * @param yaw Y axis rotation
	 * @param roll Z axis rotation
	 * @return Quaternion representing angles
	 */
	public static Quaternion getQuaternionFromAngles(float pitch, float yaw,
			float roll) {
		Quaternion quat;

		float p = pitch * PIOVER180 / 2.0f;
		float y = yaw * PIOVER180 / 2.0f;
		float r = roll * PIOVER180 / 2.0f;

		float sinp = (float) Math.sin(p);
		float siny = (float) Math.sin(y);
		float sinr = (float) Math.sin(r);
		float cosp = (float) Math.cos(p);
		float cosy = (float) Math.cos(y);
		float cosr = (float) Math.cos(r);

		float quatX = sinr * cosp * cosy - cosr * sinp * siny;
		float quatY = cosr * sinp * cosy + sinr * cosp * siny;
		float quatZ = cosr * cosp * siny - sinr * sinp * cosy;
		float quatW = cosr * cosp * cosy + sinr * sinp * siny;

		quat = new Quaternion(quatX, quatY, quatZ, quatW);
		Quaternion retQuat = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		Quaternion.normalise(quat, retQuat);

		return retQuat;
	}

	/**
	 * Not sure if this works
	 * @param quat Quaternion to convert
	 * @return float array with 4 elements- x, y, z, and angle
	 */
	public static float[] convertToAxisAngle(Quaternion quat) {
		float scale = (float) Math
				.sqrt(((quat.x * quat.x) + (quat.y * quat.y) + (quat.z * quat.z)));

		float ax = quat.x / scale;
		float ay = quat.y / scale;
		float az = quat.z / scale;

		float angle = 2.0f * (float) Math.acos((double) quat.w);

		return new float[] { ax, ay, az, angle };
	}

	/**
	 * Converts a quaternion to a rotation matrix stored in a FloatBuffer
	 * 
	 * @param quat
	 *            The quaternion to convert
	 * @param dest
	 *            The float buffer to put the rotation matrix into. MUST have a
	 *            capacity of 16 and be direct
	 */
	public static void toFloatBuffer(Quaternion quat, FloatBuffer dest) {
		if (!dest.isDirect()) {
			System.out
					.println("QuaternionHelper toFloatBuffer was passed an indirect FloatBuffer!");
		} else if (dest.capacity() != 16) {
			System.out
					.println("QuaternionHelper toFloatBuffer was passed a buffer of the incorrect size!");
		} else {
			dest.clear();

			float x = quat.x;
			float y = quat.y;
			float z = quat.z;
			float w = quat.w;

			float x2 = x * x;
			float y2 = y * y;
			float z2 = z * z;
			float xy = x * y;
			float xz = x * z;
			float yz = y * z;
			float wx = w * x;
			float wy = w * y;
			float wz = w * z;

			dest.put(1.0f - 2.0f * (y2 + z2));
			dest.put(2.0f * (xy - wz));
			dest.put(2.0f * (xz + wy));
			dest.put(0.0f);
			dest.put(2.0f * (xy + wz));
			dest.put(1.0f - 2.0f * (x2 + z2));
			dest.put(2.0f * (yz - wx));
			dest.put(0.0f);
			dest.put(2.0f * (xz - wy));
			dest.put(2.0f * (yz + wx));
			dest.put(1.0f - 2.0f * (x2 + y2));
			dest.put(0.0f);
			dest.put(0.0f);
			dest.put(0.0f);
			dest.put(0.0f);
			dest.put(1.0f);

			dest.rewind();
		}
	}

	/**
	 * Rotates a vector by a quaternion
	 * 
	 * @param vector
	 *            The vector to rotate
	 * @param quat
	 *            The quaternion to rotate the vector by
	 * @return Rotate vector
	 */
	public static Vector3f rotateVectorByQuaternion(Vector3f vector,
			Quaternion quat) {
		Quaternion vecQuat = new Quaternion(vector.x, vector.y, vector.z, 0.0f);

		Quaternion quatNegate = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		quat.negate(quatNegate);

		Quaternion resQuat = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		Quaternion.mul(vecQuat, quatNegate, resQuat);
		Quaternion.mul(quat, resQuat, resQuat);

		return new Vector3f(resQuat.x, resQuat.y, resQuat.z);
	}
	
	/**
	 * Rotates a vector by a quaternion
	 * 
	 * @param vector
	 *            The vector to rotate
	 * @param quat
	 *            The quaternion to rotate the vector by
	 * @return Rotate vector
	 */
	public static Vector3f rotateVectorByQuaternion(javax.vecmath.Vector3f vector,
			Quaternion quat) {
		Quaternion vecQuat = new Quaternion(vector.x, vector.y, vector.z, 0.0f);

		Quaternion quatNegate = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		quat.negate(quatNegate);

		Quaternion resQuat = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		Quaternion.mul(vecQuat, quatNegate, resQuat);
		Quaternion.mul(quat, resQuat, resQuat);

		return new Vector3f(resQuat.x, resQuat.y, resQuat.z);
	}
	
	/**
	 * Rotates a vector by a quaternion
	 * 
	 * @param vector
	 *            The vector to rotate
	 * @param quat
	 *            The quaternion to rotate the vector by
	 * @return Rotate vector
	 */
	public static Vector3f rotateVectorByQuaternion(javax.vecmath.Vector3f vector,
			Quat4f quat) {
		Quaternion vecQuat = new Quaternion(vector.x, vector.y, vector.z, 0.0f);

		Quat4f quatNegate = new Quat4f(0.0f, 0.0f, 0.0f, 1.0f);
		quat.negate(quatNegate);

		Quaternion resQuat = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		Quaternion.mul(vecQuat, new Quaternion(quatNegate.x, quatNegate.y, quatNegate.z, quatNegate.w), resQuat);
		Quaternion.mul(new Quaternion(quat.x, quat.y, quat.z, quat.w), resQuat, resQuat);

		return new Vector3f(resQuat.x, resQuat.y, resQuat.z);
	}

	/**
	 * Converts a quaternion to good ol' euler angles
	 * 
	 * @param quat
	 *            The quaternion to convert
	 * @return A vector containing the three euler angles
	 */
	public static Vector3f getEulerAnglesFromQuaternion(Quaternion quat) {
		float xn = (2 * ((quat.x * quat.y) + (quat.z * quat.w)))
				/ (1 - (2 * ((quat.y * quat.y) + (quat.z * quat.z))));
		float x = (float) (Math.atan((double) xn));

		float yn = 2 * ((quat.x * quat.z) - (quat.w * quat.y));
		float y = (float) (Math.asin((double) yn));

		float zn = (2 * ((quat.x * quat.w) + (quat.y * quat.z)))
				/ (1 - (2 * ((quat.z * quat.z) + (quat.w * quat.w))));
		float z = (float) (Math.atan((double) zn));

		x = x * (180.0f / (float) Math.PI);
		y = y * (180.0f / (float) Math.PI);
		z = z * (180.0f / (float) Math.PI);

		return new Vector3f(x, y, z);
	}
	
	/**
	 * Rotate a quaternion by a vector
	 * @param quat Quaternion to rotate
	 * @param amount Amount to rotate quaternion by
	 * @return Rotated quaternion
	 */
	public static Quaternion rotate(Quaternion quat, Vector3f amount){
		Quaternion ret = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		
		ret = rotateX(quat, amount.x);
		ret = rotateY(ret, amount.y);
		ret = rotateZ(ret, amount.z);
		
		return ret;
	}

	/**
	 * Rotate a quaternion along it's x axis a certain amount
	 * 
	 * @param amount
	 *            Amount to rotate the quaternion
	 * @return Rotated quaternion
	 */
	public static Quaternion rotateX(Quaternion quat, float amount) {
		Quaternion ret = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		double radHalfAngle = Math.toRadians((double) amount) / 2.0;
		float sinVal = (float) Math.sin(radHalfAngle);
		float cosVal = (float) Math.cos(radHalfAngle);
		Quaternion rot = new Quaternion(sinVal, 0.0f, 0.0f, cosVal);
		Quaternion.mul(quat, rot, ret);
		return ret;
	}

	/**
	 * Rotate a quaternion along it's y axis a certain amount
	 * 
	 * @param amount
	 *            Amount to rotate the quaternion
	 * @return Rotated quaternion
	 */
	public static Quaternion rotateY(Quaternion quat, float amount) {
		Quaternion ret = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		double radHalfAngle = Math.toRadians((double) amount) / 2.0;
		float sinVal = (float) Math.sin(radHalfAngle);
		float cosVal = (float) Math.cos(radHalfAngle);
		Quaternion rot = new Quaternion(0.0f, sinVal, 0.0f, cosVal);
		Quaternion.mul(quat, rot, ret);
		return ret;
	}

	/**
	 * Rotate a quaternion along it's z axis a certain amount
	 * 
	 * @param amount
	 *            Amount to rotate the quaternion
	 * @return Rotated quaternion
	 */
	public static Quaternion rotateZ(Quaternion quat, float amount) {
		Quaternion ret = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		double radHalfAngle = Math.toRadians((double) amount) / 2.0;
		float sinVal = (float) Math.sin(radHalfAngle);
		float cosVal = (float) Math.cos(radHalfAngle);
		Quaternion rot = new Quaternion(0.0f, 0.0f, sinVal, cosVal);
		Quaternion.mul(quat, rot, ret);
		return ret;
	}

	/**
	 * Moves the given vector a certain amount along the rotation of the given
	 * quaternion
	 * 
	 * @param quat
	 *            Rotation to use for movement
	 * @param vec
	 *            Vector to add to
	 * @param amount
	 *            Amount to add to the vector
	 */
	public static Vector3f moveX(Quaternion quat, Vector3f vec, float amount) {
		Vector3f ret = new Vector3f(0.0f, 0.0f, 0.0f);
		Vector3f multi = rotateVectorByQuaternion(new Vector3f(amount, 0.0f,
				0.0f), quat);
		Vector3f.add(vec, multi, ret);
		return ret;
	}

	/**
	 * Moves the given vector a certain amount along the rotation of the given
	 * quaternion
	 * 
	 * @param quat
	 *            Rotation to use for movement
	 * @param vec
	 *            Vector to add to
	 * @param amount
	 *            Amount to add to the vector
	 */
	public static Vector3f moveY(Quaternion quat, Vector3f vec, float amount) {
		Vector3f ret = new Vector3f(0.0f, 0.0f, 0.0f);
		Vector3f multi = rotateVectorByQuaternion(new Vector3f(0.0f, amount,
				0.0f), quat);
		Vector3f.add(vec, multi, ret);
		return ret;
	}

	/**
	 * Moves the given vector a certain amount along the rotation of the given
	 * quaternion
	 * 
	 * @param quat
	 *            Rotation to use for movement
	 * @param vec
	 *            Vector to add to
	 * @param amount
	 *            Amount to add to the vector
	 */
	public static Vector3f moveZ(Quaternion quat, Vector3f vec, float amount) {
		Vector3f ret = new Vector3f(0.0f, 0.0f, 0.0f);
		Vector3f multi = rotateVectorByQuaternion(new Vector3f(0.0f, 0.0f,
				amount), quat);
		Vector3f.add(vec, multi, ret);
		return ret;
	}
	
	/**
	 * Converts a quaternion to a rotation matrix
	 * @param quat Quaternion to convert
	 * @return Rotation matrix representing given quaternion
	 */
	public static Matrix4f toMatrix(Quaternion quat){
		float x2 = quat.x * quat.x;
		float y2 = quat.y * quat.y;
		float z2 = quat.z * quat.z;
		float xy = quat.x * quat.y;
		float xz = quat.x * quat.z;
		float yz = quat.y * quat.z;
		float wx = quat.w * quat.x;
		float wy = quat.w * quat.y;
		float wz = quat.w * quat.z;
		
		Matrix4f ret = new Matrix4f();


		ret.m00 = (1.0f - 2.0f * (y2 + z2));
		ret.m10 = (2.0f * (xy - wz));
		ret.m20 = (2.0f * (xz + wy));
		ret.m30 = (0.0f);
		
		ret.m01 = (2.0f * (xy + wz));
		ret.m11 = (1.0f - 2.0f * (x2 + z2));
		ret.m21 = (2.0f * (yz - wx));
		ret.m31 = (0.0f);
		
		ret.m02 = (2.0f * (xz - wy));
		ret.m12 = (2.0f * (yz + wx));
		ret.m22 = (1.0f - 2.0f * (x2 + y2));
		ret.m32 = (0.0f);
		
		ret.m03 = (0.0f);
		ret.m13 = (0.0f);
		ret.m23 = (0.0f);
		ret.m33 = (1.0f);
		
		return ret;
	}
	
	/**
	 * Finds the quaternion between two vectors. Assumes that the vectors are NOT unit length.
	 * @param vec1 First vector
	 * @param vec2 Second vector
	 * @return Quaternion between vectors
	 */
	public static Quaternion quaternionBetweenVectors(Vector3f vec1, Vector3f vec2){
		Vector3f c = new Vector3f();
		Vector3f.cross(vec1, vec2, c);
		
		double v1squr = (double)vec1.lengthSquared();
		double v2squr = (double)vec2.lengthSquared();
		double angle = Math.sqrt(v1squr * v2squr) + (double)Vector3f.dot(vec1, vec2);
		
		Quaternion q = new Quaternion(c.x, c.y, c.z, (float) angle);
		q.normalise(q);
		return q;
	}
	
	/**
	 * Converts an axis and an angle to a quaternion
	 * @param axis Axis
	 * @param angle Angle
	 * @return Quaternion representing rotation
	 */
	public static Quaternion quaternionFromAxisAngle(Vector3f axis, double angle){
		if(Math.abs(angle) < 1e-6)
			return new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		
		double halfAngle = angle * 0.5;
		
		float s = (float)Math.sin(halfAngle);
		
		float x = axis.x * s;
		float y = axis.y * s;
		float z = axis.z * s;
		float w = (float)Math.cos(halfAngle);
		
		return new Quaternion(x, y, z, w);
	}
} public class Console {
	/*
	 * TODO: Decide what needs to be static and what doesn't 
	 * - The input bar will remain static, while the command scrollback is per object.
	 * 		(this should already work fine) 
	 * - The console itself (Console.console) probably shouldn't be static, especially if we plan on having
	 * 	 multiple consoles
	 * TODO: Implement tabs 
	 * TODO: Fix scrolling so it only happens in the tab you have open 
	 * TODO: Consider the implications of moving issueCommand to another class
	 * TODO: Implement chat log files
	 * TODO: Fix backspace erratic behvaior
	 * TODO: A bunch of crap that belongs in util.console is still in util.Debug
	 * FIXME: The code to draw the console is super complex and muddled, it might help to split it into multiple methods
	 */

	/** the console */
	public static Console console = new Console();

	/** whether or not the console is up */
	public static boolean consoleOn = false;
	
	/**
	 * whether or not the console is up because the command key was pressed
	 * (closes the console on submission)
	 */
	public static boolean commandOn = false;

	/**
	 * This is currently pointless, but it may come in handy in the future.
	 * Disabling this will completely disable the console window visibilty.
	 * (warning: does not disable keyboard bindings)
	 */
	private static boolean consoleEnabled = true;

	/**
	 * The following is static because only 
	 * the current tab will be visible anyway...
	 */

	/** the maximum alpha for console text */
	public static float consoleTextMaxAlpha = 1.0f;
	/** the minimum alpha for console text */
	public static float consoleTextMinAlpha = 0.0f;

	/** the alpha difference for each update */
	public static float consoleTextFadeValue = 0.011f;
	/** the time in seconds before the text begins to fade */
	public static int consoleTextFadeDelay = 5;

	/**
	 * the current alpha for console text (should always be consoleTextMaxAlpha)
	 */
	public static float consoleTextAlpha = consoleTextMaxAlpha;
	/** the current fade time for console (should always be 0) */
	public static float consoleTextFadeDelayCurrent = 0;

	/**
	 * if blink is true, there's an underscore at the end of the input string,
	 * else there's not
	 */
	private static boolean blink = true;
	/** counter for blink */
	private static int blinkCount = 0;
	/** how often to blink (this is changed to match the current FPS) */
	private static int blinkInterval = 30;

	/** font for printing stuff to the screen */
	public static UnicodeFont font = null;

	/** whether or not to close the console when line is submitted */
	public static boolean autoClose = false;

	/** START non-static code */

	/** location to draw the console at */
	private int x;
	private int y;

	/** Height of the console in characters */
	private int consoleHeight;

	/** Width of the console in characters */
	private int consoleWidth;

	/**
	 * if scroll is 0, we're at the most recent line, 1 is one line up, 2 is two
	 * lines up, etc
	 */
	private int scroll;

	/** the text being typed into the console */
	public static String input;

	/** all the text that the console contains and will print out */
	private ArrayList<String> text;

	/** all the commands that are typed in */
	private ArrayList<String> commandHistoryList;
	private int chIndex;
	
	/**
	 * Default console constructor
	 */
	public Console() {
		this(0, 10, 65, 14);
	}

	/**
	 * Creates a new console with the specified parameters
	 * 
	 * @param x
	 *            distance from the left side of the screen
	 * @param y
	 *            distance from the bottom of the screen
	 * @param w
	 *            window width expanding to the right
	 * @param h
	 *            window height expanding up
	 */
	public Console(int x, int y, int w, int h) {
		this.x = x;
		this.y = y;
		consoleWidth = w;
		consoleHeight = h;

		input = "";
		autoClose = false;
		scroll = 0;

		text = new ArrayList<String>();
		commandHistoryList = new ArrayList<String>();
		commandHistoryList.add("");
		chIndex = 0;
		updateCommandHistory();
		
		// set System.out to go to the console
		ConsoleOutputStream out = new ConsoleOutputStream(this);
		PrintStream stream = new PrintStream(out);
		System.setOut(stream);
		Debug.printSysInfo();
	}

	/**
	 * Puts a character into the input for the console
	 * 
	 * @param c
	 *            The character to add to the input
	 */
	public void putCharacter(Character c) {
		input += c;
		updateCommandHistory();
	}

	/**
	 * Goes back one space in the input string
	 */
	public void backspace() {
		if (input.length() > 0)
			input = input.substring(0, input.length() - 1);
		updateCommandHistory();
	}

	/**
	 * Changes the value of the current spot in the command history
	 */
	private void updateCommandHistory() {
		commandHistoryList.set(chIndex, input);
	}

	/**
	 * Adds a string to the console, wraps if necessary. Also, the console will
	 * brighten if dim.
	 * 
	 * @param s
	 *            The string to print to the console
	 */
	protected void print(String s) {
		// Make the console text bright
		this.wake();

		// Make the string an array of words
		String words[] = s.split(" ");

		// The current number of characters, and the iterator
		int currentWidth = 0;

		// If the next word is too big, time to split the line.
		if (words[0].length() >= consoleWidth) {
			currentWidth = consoleWidth;
		} else {
			// Otherwise, for each word:
			for (int i = 0; i < words.length
					&& (currentWidth + words[i].length()) < consoleWidth; i++)
				currentWidth += words[i].length() + 1;
			// Add the width of that word and a space
			// unless the combined number of characters excedes the width of the
			// console.
		}

		try{
			// Add the words to the screen.
			text.add(s.substring(0, currentWidth - 1));
		} catch (StringIndexOutOfBoundsException e){
			currentWidth++;
			text.add(s.substring(0, currentWidth - 1));
		}

		// Recurse and print the remaining stuff.
		if (s.length() > consoleWidth)
			print(s.substring(currentWidth));
	}

	/**
	 * Scroll up
	 * 
	 * @param amount
	 *            Number of lines to scroll up
	 */
	public void scrollUp(int amount) {
		console.wake();
		if (scroll - amount >= 0)
			scroll -= amount;
	}

	/**
	 * Scroll down
	 * 
	 * @param amount
	 *            Number of lines to scroll down
	 */
	public void scrollDown(int amount) {
		console.wake();
		if (scroll + amount < text.size())
			scroll += amount;
	}

	/**
	 * Scroll through console submission history -1 is older, 1 is more recent
	 */
	public void commandHistory(int roll) {
		if (Console.consoleOn) {
			if (roll == -1 && chIndex + 1 < commandHistoryList.size())
				chIndex++;
			else if (roll == 1 && chIndex - 1 >= 0)
				chIndex--;
			else if (roll == 0)
				chIndex = 0;
				
			if (!commandHistoryList.get(chIndex).equals(""))
				input = commandHistoryList.get(chIndex);
			else
				input = "";
		}
	}

	/**
	 * What happens when the return key is pressed. If the current input starts
	 * with a /, this issues a command
	 */
	public void submit() {
		if (input.length() > 0) {
			// trim off any whitespace
			input.trim();
			// do a command if the input starts with a /
			if (input.charAt(0) == '/')
				issueCommand(input);
			// otherwise just add it to the text
			else
				// TODO give the player a name
				System.out.println("<" + System.getProperty("user.name") + "> " + input);

			// adds the input to the command history
			if (!commandHistoryList.get(0).equals(""))
				commandHistoryList.add(0, "");
			else
				commandHistoryList.add(1, input);

			// rolls to the new empty spot in the commandHistory List
			commandHistory((byte)0);

			scroll = 0;
		}
		// clear the input string (this is important!)
		input = "";

		if (autoClose) {
			autoClose = false;
			Console.consoleOn = false;
		}
	}

	/**
	 * Clears the text
	 */
	public void clear() {
		text.clear();
	}

	/**
	 * Makes the text bright until the fade delay is reached again.
	 */
	public void wake() {
		// Set the alpha to the max alpha value
		consoleTextAlpha = consoleTextMaxAlpha;
		// Reset the current fade delay timer
		consoleTextFadeDelayCurrent = 0;
	}

	/**
	 * This gets called every time that the console is drawn to make the
	 * underscore at the end of the input blink
	 */
	private void updateBlink() {
		// blink twice every second
		blinkInterval = Debug.currentFPS / 2;

		if (blink) {
			blinkCount++;
			if (blinkCount >= blinkInterval)
				blink = false;
		} else {
			blinkCount--;
			if (blinkCount <= 0)
				blink = true;
		}

	}

	/**
	 * Issues the given command. See {@link ConsoleCommands}.
	 * 
	 * @param comm
	 *            Command to issue
	 */
	public void issueCommand(String comm) {
		// make sure the command isn't empty
		if (comm.length() > 1) {
			// split the command at the spaces
			StringTokenizer toker = new StringTokenizer(comm, " ");

			/*
			 * Grab the actual command and lop off the / at the beginning
			 * Note that calling toker.nextToken() advances the tokenizer to the next token.
			 * This way, when the tokenizer is passed on to the command being executed,
			 * calling toker.nextToken() again will give you the first argument for the command.
			 */
			String commandString = toker.nextToken();
			commandString = commandString.substring(1, commandString.length());

			if(commandString.charAt(0) == '?')
				commandString = "help";

			try{
				ConsoleCommands.valueOf(commandString).issue(toker);
			} catch (NumberFormatException e) {
				System.out.println("Incorrect number format " + e.getLocalizedMessage().toLowerCase());
			} catch (IllegalArgumentException e) {
				System.out.println("Command not found! (" + commandString + ")");
			} catch (NoSuchElementException e) {
				System.out.println("Not enough vairbales for command '"
						+ commandString + "'!");
			}
		}
	}

	/**
	 * Update the console
	 */
	public void update() {
		// check for command
		if (Console.commandOn) {
			input = "/";
			Console.commandOn = false;
		}

		// Logic for scrolling, cursor blinking, and console fading.
		if (Console.consoleOn) {
			// Brighten the console
			this.wake();

			// scroll with the mouse wheel
			int scamt = scroll + (int)MouseManager.wheel / 100;
			if (scamt >= 0 && scamt < text.size())
				scroll = scamt;

			// do blinking effect
			updateBlink();

		} else if (consoleTextAlpha > consoleTextMinAlpha
				&& consoleTextFadeDelayCurrent >= consoleTextFadeDelay * 60) {
			// Subtract the proper amount from the current console text
			// alpha value
			consoleTextAlpha -= consoleTextFadeValue;
		} else {
			// if statement to avoid overflow exception
			if (consoleTextFadeDelayCurrent < consoleTextFadeDelay * 60) {
				consoleTextFadeDelayCurrent++;
			}
		}
	}

	/**
	 * Draws the console
	 */
	public void draw() {
		// how tall each line is
		int advanceY = Debug.font.getAscent();
		// where to draw the console (x stays at 10)
		y = DisplayHelper.windowHeight - advanceY - 10;

		// If the console is enabled.
		if (consoleEnabled) {
			if (Console.consoleOn) {
				// Draw the box
				Textures.WHITE.texture().bind();
				/* BEGIN CONSOLE BACKGROUND */
				GL11.glColor4f(0.15f, 0.15f, 0.15f, 0.35f);
				GL11.glBegin(GL11.GL_QUADS);
				{
					GL11.glVertex2f(
							0.0f,
							DisplayHelper.windowHeight - Debug.font.getAscent() - 7
					);
					
					GL11.glVertex2f(
							0.0f,
							DisplayHelper.windowHeight - ((Console.console.consoleHeight + 2) * Debug.font.getAscent())
					);
					
					GL11.glVertex2f(
							(consoleWidth * 9) + 10,
							DisplayHelper.windowHeight - ((Console.console.consoleHeight + 2) * Debug.font.getAscent())
					);
					
					GL11.glVertex2f(
							(consoleWidth * 9) + 10,
							DisplayHelper.windowHeight - Debug.font.getAscent() - 7
					);
				}
				GL11.glEnd();
				/* END CONSOLE BACKGROUND */

				/* BEGIN INPUT BOX */
				GL11.glColor4f(0.20f, 0.20f, 0.20f, 0.35f);
				GL11.glBegin(GL11.GL_QUADS);
				{
					GL11.glVertex2f(0.0f, DisplayHelper.windowHeight);
					GL11.glVertex2f(0.0f, DisplayHelper.windowHeight
							- Debug.font.getAscent() - 7);
					GL11.glVertex2f(DisplayHelper.windowWidth,
							DisplayHelper.windowHeight - Debug.font.getAscent()
									- 7);
					GL11.glVertex2f(DisplayHelper.windowWidth,
							DisplayHelper.windowHeight);
				}
				GL11.glEnd();
				/* END INPUT BOX */

				// Draw the blinking cursor
				String toPrint = "> " + input;
				if (blink)
					toPrint += "_";
				Debug.font.drawString(x, y, toPrint, new Color(38, 255, 0));

			}
			GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

			// figure out how many lines to print out
			int stringsToPrint = text.size() - (consoleHeight + 1);
			// avoid any possibility of out of bounds (the for loop is kind of
			// weird. if stringsToPrint == -1, then it doesn't print out any
			// lines)
			if (stringsToPrint < 0)
				stringsToPrint = -1;

			// print out however many strings, going backwards
			// (we want the most recent strings to be printed first)
			for (int i = text.size() - 1 - scroll; i > stringsToPrint - scroll
					&& i >= 0; i--) {

				// which line we're at in the console itself
				int line = text.size() - (i + scroll);

				// draw the string, going up on the y axis by how tall each line
				// is
				Debug.font.drawString(x, y - (advanceY * line), text.get(i),
						new Color(0.15f, 1.0f, 0.0f, consoleTextAlpha));

			}
		}

		GL11.glColor3f(1.0f, 1.0f, 1.0f);
	}
} public enum Textures {
	// Environment
	SKYBOX("PNG", Paths.TEXTURE_PATH.path() + "skybox.png", true, GL11.GL_NEAREST),
	WHITE("BMP", Paths.TEXTURE_PATH.path() + "white.bmp", true, GL11.GL_NEAREST),
	CHECKERS("BMP", Paths.TEXTURE_PATH.path() + "checkers.bmp", true, GL11.GL_NEAREST),
	
	// Model textures
	WING_X("PNG", Paths.SHIPS_PATH.path() + "wing_x/wing_x.png", false, GL11.GL_NEAREST),
	LASERBULLET("PNG", Paths.MODEL_PATH.path() + "laserbullet/laserbullet.png", false, GL11.GL_NEAREST),
	SAUCER("PNG", Paths.SHIPS_PATH.path() + "saucer/saucer.png", false, GL11.GL_NEAREST),
	WESCOTT("PNG", Paths.SHIPS_PATH.path() + "wescott-8-beta/wescott-8-beta.png", false, GL11.GL_NEAREST),
	DIAMOND("PNG", Paths.MODEL_PATH.path() + "diamond/diamond.png", false, GL11.GL_NEAREST),
	MISSILE("PNG", Paths.MODEL_PATH.path() + "missile/missile.png", false, GL11.GL_NEAREST),
	ASTEROID("PNG", Paths.MODEL_PATH.path() + "asteroid/asteroid.png", false, GL11.GL_LINEAR),
	TRANSPARENT_CHECKERS("PNG", Paths.TEXTURE_PATH.path() + "transparent_checkers.png", false, GL11.GL_NEAREST),
	
	// Planets
	EARTH ("JPG", Paths.TEXTURE_PATH.path() + "planets/earth.jpg", true, GL11.GL_LINEAR),
	MERCURY("JPG", Paths.TEXTURE_PATH.path() + "planets/mercury.jpg", true, GL11.GL_LINEAR),
	VENUS("JPG", Paths.TEXTURE_PATH.path() + "planets/venus.jpg", true, GL11.GL_LINEAR),
	MARS("JPG", Paths.TEXTURE_PATH.path() + "planets/mars.jpg", true, GL11.GL_LINEAR),
	SUN("JPG", Paths.TEXTURE_PATH.path() + "planets/sun.jpg", true, GL11.GL_LINEAR),
	
	// Particles
	FIRE("PNG", Paths.PARTICLE_IMAGE_PATH.path() + "fire.png", false, GL11.GL_NEAREST),
	TRAIL("PNG", Paths.PARTICLE_IMAGE_PATH.path() + "trail.png", false, GL11.GL_NEAREST),
	
	// GUI
	CROSSHAIR("PNG", Paths.CROSSHAIR_PATH.path(), true, GL11.GL_NEAREST),
	TARGET("PNG", Paths.TARGET_PATH.path(), false, GL11.GL_NEAREST),
	MENU_BACKGROUND1("JPG", Paths.BACKGROUND_IMAGE_PATH.path() + "apod1.jpg", false, GL11.GL_NEAREST),
	MENU_BACKGROUND2("JPG", Paths.BACKGROUND_IMAGE_PATH.path() + "apod2.jpg", false, GL11.GL_NEAREST),
	MENU_SPACEOUT_TEXT("PNG", Paths.MENU_IMAGE_PATH.path() + "spaceout.png", false, GL11.GL_NEAREST),
	
	MENU_PICKER_ACTIVE("PNG", Paths.FILEPICKER_IMAGE_PATH.path() + "active.png", false, GL11.GL_NEAREST),
	MENU_PICKER_MOUSEOVER("PNG", Paths.FILEPICKER_IMAGE_PATH.path() + "mouseover.png", false, GL11.GL_NEAREST),
	MENU_PICKER_SELECTED("PNG", Paths.FILEPICKER_IMAGE_PATH.path() + "selected.png", false, GL11.GL_NEAREST),
	MENU_PICKER_PRESSED("PNG", Paths.FILEPICKER_IMAGE_PATH.path() + "active.png", false, GL11.GL_NEAREST),
	
	MENU_BUTTON_ACTIVE("PNG",Paths.MENUBUTTON_IMAGE_PATH.path()  + "active.png", false, GL11.GL_NEAREST),
	MENU_BUTTON_MOUSEOVER("PNG",Paths.MENUBUTTON_IMAGE_PATH.path()  + "mouseover.png", false, GL11.GL_NEAREST),
	MENU_BUTTON_PRESSED("PNG",Paths.MENUBUTTON_IMAGE_PATH.path()  + "pressed.png", false, GL11.GL_NEAREST),
	MENU_BUTTON_INACTIVE("PNG",Paths.MENUBUTTON_IMAGE_PATH.path()  + "inactive.png", false, GL11.GL_NEAREST),
	
	BUILDER_OPEN("PNG", Paths.BUILDER_IMAGE_PATH.path + "open.png", true, GL11.GL_NEAREST),
	BUILDER_GRABBED("PNG", Paths.BUILDER_IMAGE_PATH.path + "grabbed.png", true, GL11.GL_NEAREST);
	
	/** the texture for the texture object */
	private Texture texture;
	
	/** the format of the file and the actual file path */
	private String format, file;
	
	/** whether or not the image is flipped */
	private boolean flipped;
	
	/** the GL filter to use for the image */
	private int filter;
	private Textures(String format, String file,
			boolean flipped, int filter){
		this.format = format;
		this.file = file;
		this.flipped = flipped;
		this.filter = filter;
	}
	
	/**
	 * @return Texture object for this texture reference. If the texture isn't loaded yet, it will be (which can cause the game to stop for a second- try to load textures with initTexture before using them!)
	 */
	public Texture texture(){
		if(!textureLoaded())
			initTexture();
		return texture; 
	}
	
	/**
	 * Initializes the texture object for this texture reference
	 */
	protected void initTexture(){
		if(!textureLoaded()){
			try{
				FileInputStream fis = new FileInputStream(file);
				texture = TextureLoader.getTexture(format, fis, flipped, filter);
				fis.close();
			} catch(IOException e){
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * @return Whether or not the texture object for this texture object has been loaded
	 */
	public boolean textureLoaded(){
		return texture != null;
	}
}
import javax.vecmath.Quat4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Quaternion;
import org.lwjgl.util.vector.Vector3f;
import com.bitwaffle.spaceguts.graphics.render.Render3D;
import com.bitwaffle.spaceguts.input.KeyBindings;
import com.bitwaffle.spaceguts.input.MouseManager;
import com.bitwaffle.spaceguts.physics.Builder;
import com.bitwaffle.spaceguts.physics.CollisionTypes;
import com.bitwaffle.spaceguts.physics.Physics;
import com.bitwaffle.spaceguts.util.DisplayHelper;
import com.bitwaffle.spaceguts.util.QuaternionHelper;
import com.bitwaffle.spaceguts.util.console.Console;
import com.bitwaffle.spaceout.resources.Textures;
import com.bulletphysics.collision.dispatch.CollisionObject;
import com.bulletphysics.collision.dispatch.CollisionWorld.ClosestRayResultCallback;
import com.bulletphysics.collision.shapes.SphereShape;
import com.bulletphysics.linearmath.Transform;
package com.bitwaffle.spaceguts.entities;





/**
 * A camera that describes how the scene is being looked at and can move around inside of the physics world
 * There should only be one camera at a time, Entities.camera
 * 
 * @author TranquilMarmot
 */
public class Camera extends DynamicEntity {
	/** initial values for when the camera is created */
	private static final float INIT_ZOOM = 12.0f;
	private static final float INIT_XOFFSET = 0.0f;
	private static final float INIT_YOFFSET = -2.7F;

	/** the entity that the camera is following */
	public DynamicEntity following;
	
	/** handles special interactions with the game world */
	public Builder builder;

	/** how fast the camera moves in free mode */
	public float speed = 350.0f;
	private float maxSpeed = 10000.0f, minSpeed = 0.01f;

	/** Offset along Y axis */
	public float yOffset;
	/** Offset along X axis */
	public float xOffset;
	
	/** used to preserve offset when switching modes */
	private float oldYOffset, oldXOffset, oldMinZoom;

	/** zoom level */
	public float zoom;
	/** maximum and minimum zoom level */
	private float maxZoom = 3000.0f, minZoom = 10.0f;
	/** changes how fast the zoom is based on the current zoom distance */
	float zoomSensitivity = 10;
	private float oldZoom;
	
	/** for switching into/out of build mode */
	private Vector3f oldLocation = new Vector3f(0.0f, 0.0f, 0.0f);

	/**
	 * The camera has three main modes:
	 * 	Normal Mode - vanityMode = false, freeMode = false
	 * 		Camera simply follows behind whatever it's attached to
	 * 	Vanity Mode - vanityMode = true, freeMode = false
	 * 		Camera spins around whatever it's following
	 * 	Free Mode - vanityMode = false, freeMode = true
	 * 		Camera can move about freely on its own and zoom out real far
	 */
	// FIXME there's a more clever way to do this- it could easily be a short with each bit representing a mode- 00 would be normal, 01 would be vanity, 10 would be free, and 11 would be build
	public boolean vanityMode = false;
	public boolean freeMode = false;
	public boolean buildMode = false;
	
	/** how fast the camera rolls */
	float rollSpeed = 100.0f;
	
	// FIXME should the crosshair be its own class?
	public int crosshairWidth = 8, crosshairHeight = 8;
	public int handWidth = 15, handHeight = 17;
	private int currentCrosshairWidth = crosshairWidth, currentCrosshairHeight = crosshairHeight;
	private Textures currentCrosshair = Textures.CROSSHAIR;
	public Vector3f defaultCrosshairColor = new Vector3f(1.0f, 1.0f, 1.0f);

	/**
	 * Camera constructor
	 */
	public Camera() {
		super(new Vector3f(0.0f, 0.0f, 0.0f), new Quaternion(0.0f, 0.0f, 0.0f, 1.0f), new SphereShape(10.0f), 0.0001f, 0.01f, CollisionTypes.NOTHING, CollisionTypes.EVERYTHING);
		this.zoom = INIT_ZOOM;
		this.yOffset = INIT_YOFFSET;
		this.xOffset = INIT_XOFFSET;
		rotation = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
		this.type = "camera";
		builder = new Builder(this);
		
		this.rigidBody.setActivationState(CollisionObject.DISABLE_DEACTIVATION);
	}

	/**
	 * Update the camera. This handles following other things, mode switches etc.
	 */
	public void update(float timeStep) {
		this.rigidBody.applyDamping(100.0f);
		
		// get the rigid body's tranform
		Transform trans = new Transform();
		this.rigidBody.getWorldTransform(trans);
		
		// set location to be with rigid body
		this.location.set(trans.origin.x, trans.origin.y, trans.origin.z);
		
		// set rotation to be with rigid body
		Quat4f rot = new Quat4f();
		trans.getRotation(rot);
		this.rotation.set(rot.x, rot.y, rot.z, rot.w);
		
		if(buildMode)
			builder.update(timeStep);
		
		// if we're not following anything, we're in free mode
		if (following == null) {
			vanityMode = false;
			freeMode = true;
		}

		// perform zoom logic
		mousewheelLogic();

		// check for any key presses
		checkForModeSwitch();
		
		// only look around if the builder isn't rotating something
		if(!builder.rightGrabbed)
			lookLogic(timeStep, trans);

		// if we're not in free mode, move the camera to be behind whatever it's
		// following
		if (!freeMode) {
			this.location.set(following.location);
			trans.origin.set(location.x, location.y, location.z);
		} else if (freeMode && !vanityMode) {
			// else do logic for moving around in free mode
			freeMode(timeStep, trans);
		}
		
		this.rigidBody.setWorldTransform(trans);
	}
	
	/**
	 * Changes where the camera is looking based on mouse movement/keyboard input
	 * @param timeStep Time since last update
	 * @param trans Transform to modify
	 */
	private void lookLogic(float timeStep, Transform trans){
		// roll left/right
		float dz = 0.0f;
		boolean rollRight = KeyBindings.CONTROL_ROLL_RIGHT.isPressed();
		boolean rollLeft = KeyBindings.CONTROL_ROLL_LEFT.isPressed();
		if (rollRight)
			dz = timeStep * -rollSpeed;
		if (rollLeft)
			dz = timeStep * rollSpeed;
		
		// apply any rotation changes
		this.rotation = QuaternionHelper.rotate(this.rotation, new Vector3f(MouseManager.dy, MouseManager.dx, dz));
		// update rigid body transform
		trans.setRotation(new Quat4f(rotation.x, rotation.y, rotation.z, rotation.w));
	}

	/**
	 * Checks if the mode change button has been pressed and changes states
	 * accordingly
	 */
	private void checkForModeSwitch() {
		// check for mode key
		if(!Console.consoleOn){