public abstract class GLProgram {
	private int fps;
	
	/**
	 * Initializes the application in fullscreen mode.
	 * 
	 * @param vsync Enables/disables the vertical-sync feature, where the rendering is in sync with the monitor's refresh rate.
	 *        With v-sync off, there is no framerate cap and the gameloop will run as fast as the hardware can handle.
	 *        A framerate can be set with the <code>setFPS(int fps)</code> method.
	 */
	public GLProgram(boolean vsync) {
		try {
			Display.setFullscreen(true);
			Display.setVSyncEnabled(vsync);
		} catch(Exception exc) {
			exc.printStackTrace();
		}
	}
	
	/**
	 * Initializes a windowed application. The framerate is set to 60 and can be modified using <code>setFPS(int fps)</code>.
	 * 
	 * @param name The title of the window.
	 * @param width The width of the window.
	 * @param height The height of the window.
	 * @param resizable Enables/disables the ability to resize the window.
	 */
	public GLProgram(String name, int width, int height, boolean resizable) {
		Display.setTitle(name);
		
		try {
			Display.setDisplayMode(new DisplayMode(width, height));
		} catch(Exception exc) {
			exc.printStackTrace();
		}
		
		Display.setResizable(resizable);
		
		fps = 60;
	}
	
	/**
	 * Sets the framerate of the game loop.
	 * 
	 * @param fps The desired framerate, in frames per second. It must be a positive integer or 0.
	 *        Specifying 0 means no rendering cap. Specifying a negative value will result in undefined behavior.
	 */
	public void setFPS(int fps) {
		this.fps = fps;
	}
	
	/**
	 * Returns the framerate of the game loop.
	 * 
	 * @return The framerate, in frames per second.
	 */
	public int getFPS() {
		return fps;
	}
	
	/**
	 * Initializes the context with default settings, which is generally the latest OpenGL version supported and compatibility
	 * profile if supported. For Mac OS X, the core profile must be specifically requested therefore this method is not
	 * sufficient.
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * Equivalent to <code>run(false)</code>.
	 */
	public final void run() {
		run(false);
	}
	
	/**
	 * Initializes the context depending on the value of <code>core</code>. Supplying <code>false</code> is equivalent to
	 * simply calling <code>run()</code>, while <code>true</code> requests the Core profile if available.
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param core <code>True</code> requests the Core profile, while <code>false</code> keeps default settings.
	 */
	public final void run(boolean core) {
		run(core, new PixelFormat());
	}
	
	/**
	 * Initializes the context depending on the value of <code>core</code> and the supplied PixelFormat.
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param core <code>True</code> requests the Core profile, while <code>false</code> keeps default settings.
	 * @param format The PixelFormat specifying the buffers.
	 */
	public final void run(boolean core, PixelFormat format) {
		run(format, core ? new ContextAttribs(3, 2).withProfileCore(true) : null);
	}
	
	/**
	 * Initializes the context requesting the specified OpenGL version in the format: <code>major.minor</code>
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param major
	 * @param minor
	 */
	public final void run(int major, int minor) {
		run(major, minor, false);
	}
	
	/**
	 * Initializes the context depending on the value of <code>core</code> and the specified OpenGL version in the format: <code>major.minor</code>
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param major
	 * @param minor
	 * @param core <code>True</code> requests the Core profile, while <code>false</code> requests the Compatibility profile.
	 */
	public final void run(int major, int minor, boolean core) {
		run(major, minor, core, new PixelFormat());
	}
	
	/**
	 * Initializes the context depending on the value of <code>core</code>, the supplied PixelFormat, and the specified
	 * OpenGL version in the format: <code>major.minor</code>
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param major
	 * @param minor
	 * @param core <<code>True</code> requests the Core profile, while <code>false</code> requests the Compatibility profile.
	 * @param format The PixelFormat specifying the buffers.
	 */
	public final void run(int major, int minor, boolean core, PixelFormat format) {
		run(format, core ? new ContextAttribs(major, minor).withProfileCore(core) : new ContextAttribs(major, minor));
	}
	
	/**
	 * Initializes the context with default settings and the supplied PixelFormat.
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param format The PixelFormat specifying the buffers.
	 */
	public final void run(PixelFormat format) {
		run(format, null);
	}
	
	/**
	 * Initializes the context with its attributes supplied.
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param attribs The context attributes.
	 */
	public final void run(ContextAttribs attribs) {
		run(new PixelFormat(), attribs);
	}
	
	/**
	 * Initializes the context with its attributes and PixelFormat supplied.
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param format The PixelFormat specifying the buffers.
	 * @param attribs The context attributes.
	 */
	public final void run(PixelFormat format, ContextAttribs attribs) {
		try {
			Display.create(format, attribs);
		} catch(Exception exc) {
			exc.printStackTrace();
			System.exit(1);
		}
		
		gameLoop();
	}
	
	private void gameLoop() {
		try {
			init();
			
			Utils.checkGLError("init");
			
			resized();
			
			Utils.checkGLError("resized");
			
			long lastTime, lastFPS;
			lastTime = lastFPS = System.nanoTime();
			int frames = 0;
			
			while(!shouldStop()) {
				long deltaTime = System.nanoTime() - lastTime;
				lastTime += deltaTime;
				
				if(Display.wasResized())
					resized();
				
				while(Keyboard.next()) {
					if(Keyboard.getEventKeyState())
						keyPressed(Keyboard.getEventKey(), Keyboard.getEventCharacter());
					else
						keyReleased(Keyboard.getEventKey(), Keyboard.getEventCharacter());
				}
				
				update(deltaTime);
				
				Utils.checkGLError("update");
				
				render();
				
				Utils.checkGLError("render");
				
				Display.update();
				
				frames++;
				if(System.nanoTime() - lastFPS >= 1e9) {
					System.out.println("FPS: ".concat(String.valueOf(frames)));
					lastFPS += 1e9;
					frames = 0;
				}
				
				Display.sync(fps);
			}
		} catch(Throwable exc) {
			exc.printStackTrace();
		} finally {
			destroy();
		}
	}
	
	/**
	 * Returns the width of the window.
	 * 
	 * @return The width of the window.
	 */
	public int getWidth() {
		return Display.getWidth();
	}
	
	/**
	 * Returns the height of the window.
	 * 
	 * @return The height of the window.
	 */
	public int getHeight() {
		return Display.getHeight();
	}
	
	/**
	 * Called at most once after one of the <code>run</code> methods are called. This method
	 * must be implemented by user code.
	 */
	public abstract void init();
	
	/**
	 * Called when the window is resized. This method updates the <code>glViewport</code> but may be overridden
	 * with custom code. Make sure to call <code>super.resized()</code> if overriding, or remember to manually update
	 * the <code>glViewport</code>!
	 */
	public void resized() {
		glViewport(0, 0, getWidth(), getHeight());
	}
	
	/**
	 * Consistently polled once per frame to test whether the game loop should stop. This
	 * method checks if either the window's close button has been clicked, ALT-F4 has been pressed, or if the ESCAPE key is pressed.
	 * 
	 * @return Returns <code>true</code> if the game loop should stop, otherwise <code>false</code>.
	 */
	public boolean shouldStop() {
		return Display.isCloseRequested() || Keyboard.isKeyDown(Keyboard.KEY_ESCAPE);
	}
	
	/**
	 * Called when a key has been pressed.
	 * 
	 * @param key The @see org.lwjgl.input.Keyboard keycode of the pressed key.
	 * @param c The literal character of the pressed key.
	 */
	public void keyPressed(int key, char c) {}
	
	/**
	 * Called when a key has been released.
	 *
	 * @param key The @see org.lwjgl.input.Keyboard keycode of the released key.
	 * @param c The literal character of the released key.
	 */
	public void keyReleased(int key, char c) {}
	
	/**
	 * Called once per frame and given the elapsed time since the last call
	 * to this method.
	 * 
	 * @param deltaTime The elapsed time since the last call to this method, in nanoseconds.
	 */
	public void update(long deltaTime) {}
	
	/**
	 * Called once per frame and must be implemented by user code. Render operations should
	 * occur here.
	 */
	public abstract void render();
	
	/**
	 * Destroys the context.
	 */
	public void destroy() {
		Display.destroy();
	}
	
	/**
	 * Returns the entire contents of the file in a String. The file should be specified as relative to the user class
	 * that extends GLProgram.
	 * 
	 * @param file The file path, relative to the user class extending GLProgram.
	 * @return The entire contents of the file.
	 */
	protected String readFromFile(String file) {
		try {
			return Utils.readFully(getClass().getResourceAsStream(file));
		} catch(Exception exc) {
			throw new RuntimeException("Failure reading file " + file, exc);
		}
	}
}
 public class Mesh {
	private int vao;
	private HashMap<String,Integer> vaoMap;
	
	private ArrayList<RenderCmd> renderCommands;
	
	public Mesh(URL url) throws IOException, XmlPullParserException {
		renderCommands = new ArrayList<>();
		
		ArrayList<Attribute> attributes = new ArrayList<>();
		
		ByteBuffer attributeData = BufferUtils.createByteBuffer(0), indexData = BufferUtils.createByteBuffer(0);
		
		class VAO {
			String name;
			ArrayList<Integer> sources;
			
			VAO(String name) {
				this.name = name;
				
				sources = new ArrayList<>();
			}
		}
		
		ArrayList<VAO> vaos = null;
		
		try(InputStream is = url.openStream()) {
			XmlPullParser xml = XmlPullParserFactory.newInstance().newPullParser();
			xml.setInput(is, "UTF-8");
			
			xml.next();
			
			xml.require(XmlPullParser.START_TAG, null, "mesh");
			
			do {
				switch(xml.nextTag()) {
					case XmlPullParser.START_TAG:
						switch(xml.getName()) {
							case "attribute": {
								String index = xml.getAttributeValue(null, "index");
								String type = xml.getAttributeValue(null, "type");
								String size = xml.getAttributeValue(null, "size");
								
								if(index == null)
									throw new IllegalArgumentException("<attribute> missing 'index'");
								if(type == null)
									throw new IllegalArgumentException("<attribute> missing 'type'");
								if(size == null)
									throw new IllegalArgumentException("<attribute> missing 'size'");
								
								Attribute attrib = new Attribute(Integer.parseInt(index), type, Integer.parseInt(size));
								attributes.add(attrib);
								
								xml.next();
								xml.require(XmlPullParser.TEXT, null, null);
								
								attributeData = attrib.storeData(attributeData, clean(xml.getText().trim().replace("\r\n", " ").replace('\n', ' ').split(" ")));
								
								xml.next();
								xml.require(XmlPullParser.END_TAG, null, "attribute");
								
								break;
							}
							case "indices": {
								String primitive = xml.getAttributeValue(null, "cmd");
								String type = xml.getAttributeValue(null, "type");
								
								if(primitive == null)
									throw new IllegalArgumentException("<indices> missing 'cmd'");
								if(type == null)
									throw new IllegalArgumentException("<indices> missing 'type'");
								
								RenderCmd cmd = new RenderCmd(primitive, type);
								renderCommands.add(cmd);
								
								xml.next();
								xml.require(XmlPullParser.TEXT, null, null);
								
								indexData = cmd.storeData(indexData, clean(xml.getText().trim().replace("\r\n", " ").replace('\n', ' ').split(" ")));
								
								xml.next();
								xml.require(XmlPullParser.END_TAG, null, "indices");
								
								break;
							}
							case "vao": {
								if(vaos == null)
									vaos = new ArrayList<>();
								
								String name = xml.getAttributeValue(null, "name");
								
								if(name == null)
									throw new IllegalArgumentException("<vao> missing 'name'");
								
								VAO vao = new VAO(name);
								vaos.add(vao);
								
								while(xml.nextTag() == XmlPullParser.START_TAG) {
									xml.require(XmlPullParser.START_TAG, null, "source");
									
									String attrib = xml.getAttributeValue(null, "attrib");
									
									if(attrib == null)
										throw new IllegalArgumentException("<source> missing 'attrib'");
									
									vao.sources.add(Integer.parseInt(attrib));
									xml.nextTag();
									xml.require(XmlPullParser.END_TAG, null, "source");
								}
								
								xml.require(XmlPullParser.END_TAG, null, "vao");
								
								break;
							}
							case "arrays": {
								String primitive = xml.getAttributeValue(null, "cmd");
								String start = xml.getAttributeValue(null, "start");
								String count = xml.getAttributeValue(null, "count");
								
								if(primitive == null)
									throw new IllegalArgumentException("<arrays> missing 'cmd'");
								if(start == null)
									throw new IllegalArgumentException("<arrays> missing 'start'");
								if(count == null)
									throw new IllegalArgumentException("<arrays> missing 'count'");
								
								RenderCmd cmd = new RenderCmd(primitive, Integer.parseInt(start), Integer.parseInt(count));
								renderCommands.add(cmd);
								
								xml.next();
								xml.require(XmlPullParser.END_TAG, null, "arrays");
								
								break;
							}
							default:
								throw new IllegalArgumentException("Invalid TAG name: " + xml.getName());
						}
						
						break;
				}
			} while(xml.next() != XmlPullParser.END_DOCUMENT);
		}
		
		if(attributes.size() == 0)
			throw new IllegalArgumentException("There must be at least 1 set of attributes.");
		if(renderCommands.size() == 0)
			throw new IllegalArgumentException("There must be at least 1 render command.");
		
		attributeData.flip();
		indexData.flip();
		
		int vbo1 = glGenBuffers();
		glBindBuffer(GL_ARRAY_BUFFER, vbo1);
		glBufferData(GL_ARRAY_BUFFER, (ByteBuffer)BufferUtils.createByteBuffer(attributeData.capacity()).put(attributeData).flip(), GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		
		int vbo2 = -1;
		if(indexData.hasRemaining()) {
			vbo2 = glGenBuffers();
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo2);
			glBufferData(GL_ELEMENT_ARRAY_BUFFER, (ByteBuffer)BufferUtils.createByteBuffer(indexData.capacity()).put(indexData).flip(), GL_STATIC_DRAW);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		}
		
		vao = glGenVertexArrays();
		glBindVertexArray(vao);
		
		glBindBuffer(GL_ARRAY_BUFFER, vbo1);
		for(Attribute attrib : attributes) {
			glEnableVertexAttribArray(attrib.index);
			glVertexAttribPointer(attrib.index, attrib.size, attrib.type.dataType, attrib.type.normalized, 0, attrib.offset);
		}
		
		if(vbo2 >= 0)
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo2);
		
		glBindVertexArray(0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		
		if(vaos != null) {
			vaoMap = new HashMap<>();
			
			for(VAO vao : vaos) {
				int v = glGenVertexArrays();
				glBindVertexArray(v);
				
				vaoMap.put(vao.name, v);
				
				glBindBuffer(GL_ARRAY_BUFFER, vbo1);
				for(Attribute attrib : attributes) {
					if(vao.sources.contains(attrib.index)) {
						glEnableVertexAttribArray(attrib.index);
						glVertexAttribPointer(attrib.index, attrib.size, attrib.type.dataType, attrib.type.normalized, 0, attrib.offset);
					}
				}
				
				if(vbo2 >= 0)
					glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo2);
				
				glBindVertexArray(0);
				glBindBuffer(GL_ARRAY_BUFFER, 0);
				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
			}
		}
	}
	
	private String[] clean(String[] data) {
		ArrayList<String> clean = new ArrayList<>();
		for(String s : data)
			if(!(s = s.trim()).isEmpty())
				clean.add(s);
		return clean.toArray(new String[clean.size()]);
	}
	
	public void render() {
		glBindVertexArray(vao);
		for(RenderCmd cmd : renderCommands)
			cmd.render();
		glBindVertexArray(0);
	}
	
	public void render(String name) {
		if(vaoMap == null)
			throw new IllegalArgumentException("vaoMap is null, must call render() method.");
		
		if(!vaoMap.containsKey(name))
			throw new IllegalArgumentException(name + " could not be found.");
		
		glBindVertexArray(vaoMap.get(name));
		for(RenderCmd cmd : renderCommands)
			cmd.render();
		glBindVertexArray(0);
	}
	
	private static class RenderCmd {
		private boolean isIndexedCmd;
		private int primitive;
		private int start;
		private int count;
		private RenderCommandType type;
		
		public RenderCmd(String primitive, String type) {
			isIndexedCmd = true;
			this.primitive = getPrimitive(primitive);
			this.type = RenderCommandType.getRenderType(type);
		}
		
		public RenderCmd(String primitive, int start, int count) {
			isIndexedCmd = false;
			this.primitive = getPrimitive(primitive);
			this.start = start;
			this.count = count;
		}
		
		public ByteBuffer storeData(ByteBuffer b, String[] data) {
			start = b.position();
			count = data.length;
			
			ByteBuffer b2 = BufferUtils.createByteBuffer(start + count * type.size);
			b2.put((ByteBuffer)b.flip());
			type.store(b2, data);
			
			return b2;
		}
		
		private static int getPrimitive(String name) {
			switch(name) {
				case "triangles":
					return GL_TRIANGLES;
				case "tri-fan":
					return GL_TRIANGLE_FAN;
				case "tri-strip":
					return GL_TRIANGLE_STRIP;
				case "lines":
					return GL_LINES;
				case "line-strip":
					return GL_LINE_STRIP;
				case "line-loop":
					return GL_LINE_LOOP;
				case "points":
					return GL_POINTS;
				default:
					throw new IllegalArgumentException("Invalid primitive name: " + name);
			}
		}
		
		public void render() {
			if(isIndexedCmd)
				glDrawElements(primitive, count, type.dataType, start);
			else
				glDrawArrays(primitive, start, count);
		}
	}
	
	private enum RenderCommandType {
		UBYTE("ubyte", GL_UNSIGNED_BYTE, 1) {
			public void store(ByteBuffer b, String[] data) {
				for(String s : data)
					b.put((byte)Long.parseLong(s));
			}
		},
		USHORT("ushort", GL_UNSIGNED_SHORT, 2) {
			public void store(ByteBuffer b, String[] data) {
				for(String s : data)
					b.putShort((short)Long.parseLong(s));
			}
		},
		UINT("uint", GL_UNSIGNED_INT, 4) {
			public void store(ByteBuffer b, String[] data) {
				for(String s : data)
					b.putInt((int)Long.parseLong(s));
			}
		};
		
		private String name;
		private int dataType;
		private int size;
		
		private RenderCommandType(String name, int type, int size) {
			this.name = name;
			this.dataType = type;
			this.size = size;
		}
		
		public abstract void store(ByteBuffer b, String[] data);
		
		public static RenderCommandType getRenderType(String name) {
			for(RenderCommandType rt : values()) {
				if(rt.name.equals(name))
					return rt;
			}
			
			throw new IllegalArgumentException("Unsupported render command type!");
		}
	}
	
	private class Attribute {
		private int index;
		private AttributeType type;
		private int size;
		private int offset;
		
		public Attribute(int index, String type, int size) {
			this.index = index;
			this.size = size;
			
			this.type = AttributeType.getAttributeType(type);
		}
		
		public ByteBuffer storeData(ByteBuffer b, String[] data) {
			offset = b.position();
			
			ByteBuffer b2 = BufferUtils.createByteBuffer(offset + data.length * type.size);
			b2.put((ByteBuffer)b.flip());
			type.store(b2, data);
			
			return b2;
		}
	}
	
	private enum AttributeType {
		FLOAT("float", false, GL_FLOAT, 4),
		INT("int", false, GL_INT, 4), UINT("uint", false, GL_UNSIGNED_INT, 4), NORM_INT("norm-int", true, GL_INT, 4), NORM_UINT("norm-uint", true, GL_UNSIGNED_INT, 4),
		SHORT("short", false, GL_SHORT, 2), USHORT("ushort", false, GL_UNSIGNED_SHORT, 2), NORM_SHORT("norm-short", true, GL_SHORT, 2), NORM_USHORT("norm-ushort", true, GL_UNSIGNED_SHORT, 2),
		BYTE("byte", false, GL_BYTE, 1), UBYTE("ubyte", false, GL_UNSIGNED_BYTE, 1), NORM_BYTE("norm-byte", true, GL_BYTE, 1), NORM_UBYTE("norm-ubyte", true, GL_UNSIGNED_BYTE, 1);
		
		private String name;
		private boolean normalized;
		private int dataType;
		private int size;
		
		private AttributeType(String name, boolean normalized, int type, int size) {
			this.name = name;
			this.normalized = normalized;
			this.dataType = type;
			this.size = size;
		}
		
		public void store(ByteBuffer b, String[] data) {
			switch(dataType) {
				case GL_FLOAT:
					for(String s : data)
						b.putFloat(Float.parseFloat(s));
					break;
				case GL_INT:
				case GL_UNSIGNED_INT:
					for(String s : data)
						b.putInt((int)Long.parseLong(s));
					break;
				case GL_SHORT:
				case GL_UNSIGNED_SHORT:
					for(String s : data)
						b.putShort((short)Long.parseLong(s));
					break;
				case GL_BYTE:
				case GL_UNSIGNED_BYTE:
					for(String s : data)
						b.put((byte)Long.parseLong(s));
					break;
			}
		}
		
		public static AttributeType getAttributeType(String name) {
			for(AttributeType at : values()) {
				if(at.name.equals(name))
					return at;
			}
			
			throw new IllegalArgumentException("Unsupported attribute type!");
		}
	}
}
 public enum MouseButton {
	LEFT_BUTTON,
	MIDDLE_BUTTON,
	RIGHT_BUTTON;
		
	public static MouseButton getButton(int button) {
		switch(button) {
			case 0:
				return MouseButton.LEFT_BUTTON;
			case 1:
				return MouseButton.RIGHT_BUTTON;
			case 2:
				return MouseButton.MIDDLE_BUTTON;
			case -1:
				return null;
			default:
				throw new IllegalArgumentException("Invalid button: " + button);
		}
	}
}
 public static class ObjectData {
	public Vector3 position;
	public Quaternion orientation;
		
	public ObjectData(ObjectData data) {
		position = data.position.copy();
		orientation = data.orientation.copy();
	}
		
	public ObjectData(Vector3 v, Quaternion q) {
		position = v.copy();
		orientation = q.copy();
	}
}
 public static class ObjectPole extends Pole {
	private enum Axis {
		AXIS_X,
		AXIS_Y,
		AXIS_Z
	}
		
	private enum RotateMode {
		DUAL_AXIS,
		BIAXIAL,
		SPIN
	}
		
	private Vector3[] axisVectors = {
			new Vector3(1, 0, 0),
			new Vector3(0, 1, 0),
			new Vector3(0, 0, 1)
	};
		
	private ViewProvider view;
	private ObjectData po;
	private ObjectData initialPo;
		
	private float rotateScale;
	private MouseButton actionButton;
		
	private RotateMode rotateMode;
	private boolean isDragging;
		
	private Vector2 prevMousePos;
	private Vector2 startDragMousePos;
	private Quaternion startDragOrient;
		
	public ObjectPole(ObjectData initialData, float rotateScale, MouseButton actionButton, ViewProvider lookAtProvider) {
		this.view = lookAtProvider;
		this.po = new ObjectData(initialData);
		this.initialPo = initialData;
		this.rotateScale = rotateScale;
		this.actionButton = actionButton;
	}
		
	public Matrix4 calcMatrix() {
		Matrix4 translateMat = new Matrix4().clearToIdentity();
		translateMat.putColumn(3, po.position, 1);
		return translateMat.mult(po.orientation.toMatrix());
	}
		
	public void setRotationScale(float rotateScale) {
		this.rotateScale = rotateScale;
	}
		
	public float getRotationScale() {
		return rotateScale;
	}
		
	public ObjectData getPosOrient() {
		return po;
	}
		
	public boolean isDragging() {
		return isDragging;
	}
		
	public void reset() {
		if(!isDragging)
			po = new ObjectData(initialPo);
	}
		
	private Quaternion calcRotationQuat(Axis axis, float angle) {
		return calcRotationQuat(axis.ordinal(), angle);
	}
		
	private Quaternion calcRotationQuat(int axis, float angle) {
		return Utils.angleAxisDeg(angle, axisVectors[axis]);
	}
		
	private void rotateWorldDegrees(Quaternion rot, boolean fromInitial) {
		if(!isDragging)
			fromInitial = false;
			
		po.orientation = rot.copy().mult(fromInitial ? startDragOrient : po.orientation).normalize();
	}
		
	public void rotateViewDegrees(Quaternion rot, boolean fromInitial) {
		if(!isDragging)
			fromInitial = false;
			
		if(view == null)
			rotateWorldDegrees(rot, fromInitial);
		else {
			Quaternion viewQuat = view.calcMatrix().toQuaternion();
			Quaternion inViewQuat = viewQuat.copy().conjugate();
			po.orientation = inViewQuat.mult(rot).mult(viewQuat).mult(fromInitial ? startDragOrient : po.orientation).normalize();
		}
	}
		
	@Override
	public void mouseMove(Vector2 position) {
		if(isDragging) {
			Vector2 diff = position.copy().sub(prevMousePos);
				
			switch(rotateMode) {
				case DUAL_AXIS:
					Quaternion rot = calcRotationQuat(Axis.AXIS_Y, diff.x() * rotateScale);
					rot = calcRotationQuat(Axis.AXIS_X, diff.y() * rotateScale).mult(rot).normalize();
					rotateViewDegrees(rot, false);
					break;
				case BIAXIAL: {
					Vector2 initDiff = position.copy().sub(startDragMousePos);
						
					Axis axis;
					float degAngle;
					if(Math.abs(initDiff.x()) > Math.abs(initDiff.y())) {
						axis = Axis.AXIS_Y;
						degAngle = initDiff.x() * rotateScale;
					} else {
						axis = Axis.AXIS_X;
						degAngle = initDiff.y() * rotateScale;
					}
						
					rotateViewDegrees(calcRotationQuat(axis, degAngle), true);
				}
					break;
				case SPIN:
					rotateViewDegrees(calcRotationQuat(Axis.AXIS_Z, -diff.x() * rotateScale), false);
					break;
			}
				
			prevMousePos = position.copy();
		}
	}
		
	@Override
	public void mouseClick(MouseButton button, boolean isPressed, MouseModifier modifiers, Vector2 position) {
		if(isPressed) {
			if(!isDragging) {
				if(button == actionButton) {
					if(modifiers == MouseModifier.KEY_ALT)
						rotateMode = RotateMode.SPIN;
					else if(modifiers == MouseModifier.KEY_CTRL)
						rotateMode = RotateMode.BIAXIAL;
					else
						rotateMode = RotateMode.DUAL_AXIS;
						
					prevMousePos = position.copy();
						
					startDragMousePos = position.copy();
						
					startDragOrient = po.orientation.copy();
						
					isDragging = true;
				}
			}
		} else {
			if(isDragging) {
				if(button == actionButton) {
					mouseMove(position);
						
					isDragging = false;
				}
			}
		}
	}
		
	@Override
	public void mouseWheel(int direction, MouseModifier modifiers) {}
		
	@Override
	public void charPress(long deltaTime) {}
}
 public static class ViewData {
	public Vector3 targetPos;
	public Quaternion orient;
	public float radius;
	public float degSpinRotation;
		
	public ViewData(ViewData data) {
		targetPos = data.targetPos.copy();
		orient = data.orient.copy();
		radius = data.radius;
		degSpinRotation = data.degSpinRotation;
	}
		
	public ViewData(Vector3 v, Quaternion q, float r, float d) {
		targetPos = v;
		orient = q.copy();
		radius = r;
		degSpinRotation = d;
	}
}
 public static class ViewPole extends ViewProvider {
	private enum TargetOffsetDir {
		DIR_UP,
		DIR_DOWN,
		DIR_FORWARD,
		DIR_BACKWARD,
		DIR_RIGHT,
		DIR_LEFT
	}
		
	private enum RotateMode {
		DUAL_AXIS_ROTATE,
		BIAXIAL_ROTATE,
		XZ_AXIS_ROTATE,
		Y_AXIS_ROTATE,
		SPIN_VIEW_AXIS
	}
		
	private Vector3[] offsets = {
			new Vector3(0, 1, 0),
			new Vector3(0, -1, 0),
			new Vector3(0, 0, -1),
			new Vector3(0, 0, 1),
			new Vector3(1, 0, 0),
			new Vector3(-1, 0, 0)
	};
		
	private ViewData currView;
	private ViewScale viewScale;
		
	private ViewData initialView;
	private MouseButton actionButton;
	private boolean rightKeyboardCtrls;
		
	private boolean isDragging;
	private RotateMode rotateMode;
		
	private float degStarDragSpin;
	private Vector2 startDragMouseLoc;
	private Quaternion startDragOrient;
		
	public ViewPole(ViewData initialView, ViewScale viewScale, MouseButton actionButton) {
		this(initialView, viewScale, actionButton, false);
	}
		
	public ViewPole(ViewData initialView, ViewScale viewScale, MouseButton actionButton, boolean rightKeyboardCtrls) {
		this.currView = new ViewData(initialView);
		this.viewScale = new ViewScale(viewScale);
		this.initialView = initialView;
		this.actionButton = actionButton;
		this.rightKeyboardCtrls = rightKeyboardCtrls;
	}
		
	@Override
	public Matrix4 calcMatrix() {
		Matrix4 mat = new Matrix4().clearToIdentity();
			
		mat.translate(0, 0, -currView.radius);
			
		Quaternion fullRotation = Utils.angleAxisDeg(currView.degSpinRotation, new Vector3(0, 0, 1)).mult(currView.orient);
		mat.mult(fullRotation.toMatrix());
			
		mat.translate(currView.targetPos.copy().mult(-1));
			
		return mat;
	}
		
	public void reset() {
		if(!isDragging)
			currView = new ViewData(initialView);
	}
		
	public void setRotationScale(float rotateScale) {
		viewScale.rotationScale = rotateScale;
	}
		
	public float getRotationScale() {
		return viewScale.rotationScale;
	}
		
	public ViewData getView() {
		return currView;
	}
		
	public boolean isDragging() {
		return isDragging;
	}
		
	public void processXChange(int diffX) {
		float degAngleDiff = diffX * viewScale.rotationScale;
			
		currView.orient = startDragOrient.copy().mult(Utils.angleAxisDeg(degAngleDiff, new Vector3(0, 1, 0)));
	}
		
	public void processYChange(int diffY) {
		float degAngleDiff = diffY * viewScale.rotationScale;
			
		currView.orient = Utils.angleAxisDeg(degAngleDiff, new Vector3(1, 0, 0)).mult(startDragOrient);
	}
		
	public void processXYChange(int diffX, int diffY) {
		float degXAngleDiff = diffX * viewScale.rotationScale;
		float degYAngleDiff = diffY * viewScale.rotationScale;
			
		currView.orient = startDragOrient.copy().mult(Utils.angleAxisDeg(degXAngleDiff, new Vector3(0, 1, 0)));
		currView.orient = Utils.angleAxisDeg(degYAngleDiff, new Vector3(1, 0, 0)).mult(currView.orient);
	}
		
	public void processSpinAxis(int diffX) {
		float degSpinDiff = diffX * viewScale.rotationScale;
		currView.degSpinRotation = degSpinDiff + degStarDragSpin;
	}
		
	public void beginDragRotate(Vector2 start, RotateMode rotMode) {
		rotateMode = rotMode;
			
		startDragMouseLoc = start.copy();
			
		degStarDragSpin = currView.degSpinRotation;
			
		startDragOrient = currView.orient.copy();
			
		isDragging = true;
	}
		
	public void onDragRotate(Vector2 position) {
		int diffX = (int)(position.x() - startDragMouseLoc.x());
		int diffY = -(int)(position.y() - startDragMouseLoc.y());
			
		switch(rotateMode) {
			case DUAL_AXIS_ROTATE:
				processXYChange(diffX, diffY);
				break;
			case BIAXIAL_ROTATE:
				if(Math.abs(diffX) > Math.abs(diffY))
					processXChange(diffX);
				else
					processYChange(diffY);
				break;
			case XZ_AXIS_ROTATE:
				processXChange(diffX);
				break;
			case Y_AXIS_ROTATE:
				processYChange(diffY);
				break;
			case SPIN_VIEW_AXIS:
				processSpinAxis(diffX);
				break;
		}
	}
		
	public void endDragRotate(Vector2 end, boolean keepResults) {
		if(keepResults)
			onDragRotate(end);
		else
			currView.orient = startDragOrient.copy();
			
		isDragging = false;
	}
		
	public void moveCloser(boolean largeStep) {
		if(largeStep)
			currView.radius -= viewScale.largeRadiusDelta;
		else
			currView.radius -= viewScale.smallRadiusDelta;
			
		if(currView.radius < viewScale.minRadius)
			currView.radius = viewScale.minRadius;
	}
		
	public void moveAway(boolean largeStep) {
		if(largeStep)
			currView.radius += viewScale.largeRadiusDelta;
		else
			currView.radius += viewScale.smallRadiusDelta;
			
		if(currView.radius > viewScale.maxRadius)
			currView.radius = viewScale.maxRadius;
	}
		
	@Override
	public void mouseMove(Vector2 position) {
		if(isDragging)
			onDragRotate(position);
	}
		
	@Override
	public void mouseClick(MouseButton button, boolean isPressed, MouseModifier modifiers, Vector2 position) {
		if(isPressed) {
			if(!isDragging) {
				if(button == actionButton) {
					if(modifiers == MouseModifier.KEY_CTRL)
						beginDragRotate(position, RotateMode.BIAXIAL_ROTATE);
					else if(modifiers == MouseModifier.KEY_ALT)
						beginDragRotate(position, RotateMode.SPIN_VIEW_AXIS);
					else
						beginDragRotate(position, RotateMode.DUAL_AXIS_ROTATE);
				}
			}
		} else {
			if(isDragging) {
				if(button == actionButton) {
					if(rotateMode == RotateMode.DUAL_AXIS_ROTATE ||
							rotateMode == RotateMode.SPIN_VIEW_AXIS ||
							rotateMode == RotateMode.BIAXIAL_ROTATE)
						endDragRotate(position, true);
				}
			}
		}
	}
		
	@Override
	public void mouseWheel(int direction, MouseModifier modifiers) {
		if(direction > 0)
			moveCloser(modifiers != MouseModifier.KEY_SHIFT);
		else
			moveAway(modifiers != MouseModifier.KEY_SHIFT);
	}
		
	private void offsetTargetPos(TargetOffsetDir dir, float worldDistance) {
		offsetTargetPos(offsets[dir.ordinal()].copy().mult(worldDistance));
	}
		
	private void offsetTargetPos(Vector3 cameraOffset) {
		currView.targetPos.add(calcMatrix().toQuaternion().conjugate().mult(cameraOffset));
	}
		
	@Override
	public void charPress(long deltaTime) {
		boolean isShiftPressed = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT);
			
		float offset = (isShiftPressed ? viewScale.smallPosOffset : viewScale.largePosOffset) * 10 * deltaTime / (float)1e9;
			
		if(rightKeyboardCtrls) {
			if(Keyboard.isKeyDown(Keyboard.KEY_I))
				offsetTargetPos(TargetOffsetDir.DIR_FORWARD, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_K))
				offsetTargetPos(TargetOffsetDir.DIR_BACKWARD, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_L))
				offsetTargetPos(TargetOffsetDir.DIR_RIGHT, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_J))
				offsetTargetPos(TargetOffsetDir.DIR_LEFT, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_O))
				offsetTargetPos(TargetOffsetDir.DIR_UP, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_U))
				offsetTargetPos(TargetOffsetDir.DIR_DOWN, offset);
		}
		else {
			if(Keyboard.isKeyDown(Keyboard.KEY_W))
				offsetTargetPos(TargetOffsetDir.DIR_FORWARD, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_S))
				offsetTargetPos(TargetOffsetDir.DIR_BACKWARD, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_D))
				offsetTargetPos(TargetOffsetDir.DIR_RIGHT, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_A))
				offsetTargetPos(TargetOffsetDir.DIR_LEFT, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_E))
				offsetTargetPos(TargetOffsetDir.DIR_UP, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_Q))
				offsetTargetPos(TargetOffsetDir.DIR_DOWN, offset);
		}
	}
}
 public static class ViewScale {
	public float minRadius;
	public float maxRadius;
	public float largeRadiusDelta;
	public float smallRadiusDelta;
	public float largePosOffset;
	public float smallPosOffset;
	public float rotationScale;
		
	public ViewScale(float min, float max, float large, float small, float largePos, float smallPos, float rot) {
		minRadius = min;
		maxRadius = max;
		largeRadiusDelta = large;
		smallRadiusDelta = small;
		largePosOffset = largePos;
		smallPosOffset = smallPos;
		rotationScale = rot;
	}
		
	public ViewScale(ViewScale v) {
		minRadius = v.minRadius;
		maxRadius = v.maxRadius;
		largeRadiusDelta = v.largeRadiusDelta;
		smallRadiusDelta = v.smallRadiusDelta;
		largePosOffset = v.largePosOffset;
		smallPosOffset = v.smallPosOffset;
		rotationScale = v.rotationScale;
	}
}
 public class ShaderProgram {
	private int program;
	
	public ShaderProgram(String vertexShader, String fragmentShader) {
		this(vertexShader, null, fragmentShader, null);
	}
	
	public ShaderProgram(String vertexShader, String fragmentShader, Map<Integer,String> attributes) {
		this(vertexShader, null, fragmentShader, attributes);
	}
	
	public ShaderProgram(String vertexShader, String geometryShader, String fragmentShader) {
		this(vertexShader, geometryShader, fragmentShader, null);
	}
	
	public ShaderProgram(String vertexShader, String geometryShader, String fragmentShader, Map<Integer,String> attributes) {
		int vs = compileShader(vertexShader, GL_VERTEX_SHADER);
		int gs = compileShader(geometryShader, GL_GEOMETRY_SHADER);
		int fs = compileShader(fragmentShader, GL_FRAGMENT_SHADER);
		
		program = glCreateProgram();
		glAttachShader(program, vs);
		if(gs != -1)
			glAttachShader(program, gs);
		glAttachShader(program, fs);
		
		if(attributes != null)
			for(int i : attributes.keySet())
				glBindAttribLocation(program, i, attributes.get(i));
		
		glLinkProgram(program);
		
		String infoLog = glGetProgramInfoLog(program, glGetProgrami(program, GL_INFO_LOG_LENGTH));
		
		if(glGetProgrami(program, GL_LINK_STATUS) == GL_FALSE)
			throw new RuntimeException("Failure in linking program. Error log:\n" + infoLog);
		else {
			System.out.print("Linking program successful.");
			if(infoLog != null && !(infoLog = infoLog.trim()).isEmpty())
				System.out.println(" Log:\n" + infoLog);
			else
				System.out.println();
		}
		
		glDetachShader(program, vs);
		if(gs != -1)
			glDetachShader(program, gs);
		glDetachShader(program, fs);
		
		glDeleteShader(vs);
		if(gs != -1)
			glDeleteShader(gs);
		glDeleteShader(fs);
	}
	
	private int compileShader(String source, int type) {
		if(source == null)
			return -1;
		
		int shader = glCreateShader(type);
		glShaderSource(shader, source);
		
		glCompileShader(shader);
		
		String infoLog = glGetShaderInfoLog(shader, glGetShaderi(shader, GL_INFO_LOG_LENGTH));
		
		if(glGetShaderi(shader, GL_COMPILE_STATUS) == GL_FALSE)
			throw new RuntimeException("Failure in compiling " + getName(type) + " shader. Error log:\n" + infoLog);
		else {
			System.out.print("Compiling " + getName(type) + " shader successful.");
			if(infoLog != null && !(infoLog = infoLog.trim()).isEmpty())
				System.out.println(" Log:\n" + infoLog);
			else
				System.out.println();
		}
		
		return shader;
	}
	
	private String getName(int shaderType) {
		if(shaderType == GL_VERTEX_SHADER)
			return "vertex";
		if(shaderType == GL_GEOMETRY_SHADER)
			return "geometry";
		if(shaderType == GL_FRAGMENT_SHADER)
			return "fragment";
		
		throw new IllegalArgumentException("Invalid shaderType, must be either GL_VERTEX_SHADER, GL_GEOMETRY_SHADER, or GL_FRAGMENT_SHADER");
	}
	
	public int getProgram() {
		return program;
	}
	
	public int getUniformLocation(String name) {
		return glGetUniformLocation(program, name);
	}
	
	public int getUniformBlockIndex(String name) {
		return glGetUniformBlockIndex(program, name);
	}
	
	public void begin() {
		glUseProgram(program);
	}
	
	public void end() {
		glUseProgram(0);
	}
	
	public void destroy() {
		glDeleteProgram(program);
	}
}
 public class Timer {
	public enum Type {
		LOOP,
		SINGLE,
		INFINITE
	}
	
	private Type type;
	private float secDuration;
	
	private boolean hasUpdated;
	private boolean isPaused;
	
	private float absPrevTime;
	private float secAccumTime;
	
	private long elapsedTime;
	
	public Timer() {
		this(Type.INFINITE, 1);
	}
	
	public Timer(Type type, float duration) {
		this.type = type;
		this.secDuration = duration;
		
		if(type != Type.INFINITE && duration <= 0)
			throw new IllegalArgumentException("duration cannot be less than or equal to 0");
	}
	
	public void reset() {
		hasUpdated = false;
		secAccumTime = 0;
	}
	
	public boolean togglePause() {
		isPaused = !isPaused;
		return isPaused;
	}
	
	public boolean isPaused() {
		return isPaused;
	}
	
	public void setPause(boolean pause) {
		isPaused = pause;
	}
	
	public boolean update(long deltaTime) {
		elapsedTime += deltaTime;
		
		float currTime = elapsedTime / (float)1e9;
		
		if(!hasUpdated) {
			absPrevTime = currTime;
			hasUpdated = true;
		}
		
		if(isPaused) {
			absPrevTime = currTime;
			return false;
		}
		
		float delta = currTime - absPrevTime;
		secAccumTime += delta;
		
		absPrevTime = currTime;
		
		if(type == Type.SINGLE)
			return secAccumTime > secDuration;
		
		return false;
	}
	
	public void rewind(float secRewind) {
		secAccumTime -= secRewind;
		if(secAccumTime < 0)
			secAccumTime = 0;
	}
	
	public void fastForward(float secFF) {
		secAccumTime += secFF;
	}
	
	public float getAlpha() {
		switch(type) {
			case LOOP:
				return (secAccumTime % secDuration) / secDuration;
			case SINGLE:
				return Utils.clamp(secAccumTime / secDuration, 0, 1);
			case INFINITE:
			default:
				return -1;
		}
	}
	
	public float getProgression() {
		switch(type) {
			case LOOP:
				return secAccumTime % secDuration;
			case SINGLE:
				return Utils.clamp(secAccumTime, 0, secDuration);
			case INFINITE:
			default:
				return -1;
		}
	}
	
	public float getTimeSinceStart() {
		return secAccumTime;
	}
}
 public class Utils {
	public static boolean ENABLE_ERROR_CHECKING = true;
	
	public static void checkGLError(String event) {
		int error;
		if(ENABLE_ERROR_CHECKING && (error = glGetError()) != GL_NO_ERROR)
			throw new RuntimeException("OpenGL Error during " + event + ": " + gluErrorString(error));
	}
	
	public static Quaternion angleAxisDeg(float angle, Vector3 vec) {
		return new Quaternion((float)Math.toRadians(angle), vec);
	}
	
	public static float clamp(float value, float low, float high) {
		return Math.min(Math.max(value, low), high);
	}
	
	public static float mix(float f1, float f2, float a) {
		return f1 + (f2 - f1) * a;
	}
	
	public static Matrix4 lookAt(Vector3 eye, Vector3 center, Vector3 up) {
		Vector3 f = center.copy().sub(eye).normalize();
		up = up.copy().normalize();
		
		Vector3 s = f.cross(up);
		Vector3 u = s.cross(f);
		
		return new Matrix4(new float[] {
				s.x(), u.x(), -f.x(), 0,
				s.y(), u.y(), -f.y(), 0,
				s.z(), u.z(), -f.z(), 0,
				0, 0, 0, 1
		}).translate(eye.copy().mult(-1));
	}
	
	public static void updateMousePoles(ViewPole... viewPoles) {
		updateMousePoles(viewPoles, null);
	}
	
	public static void updateMousePoles(ObjectPole... objectPoles) {
		updateMousePoles(null, objectPoles);
	}
	
	public static void updateMousePoles(ViewPole viewPole, ObjectPole objectPole) {
		updateMousePoles(viewPole == null ? null : new ViewPole[] { viewPole }, objectPole == null ? null : new ObjectPole[] { objectPole });
	}
	
	public static void updateMousePoles(ViewPole[] viewPoles, ObjectPole[] objectPoles) {
		while(Mouse.next()) {
			MouseButton button = MouseButton.getButton(Mouse.getEventButton());
			if(button != null) {
				boolean pressed = Mouse.getEventButtonState();
				if(viewPoles != null)
					for(ViewPole v : viewPoles)
						v.mouseClick(button, pressed, getModifier(), new Vector2(Mouse.getX(), Mouse.getY()));
				if(objectPoles != null)
					for(ObjectPole o : objectPoles)
						o.mouseClick(button, pressed, getModifier(), new Vector2(Mouse.getX(), Mouse.getY()));
			} else {
				int dwheel = Mouse.getDWheel();
				
				if(dwheel != 0) {
					if(viewPoles != null)
						for(ViewPole v : viewPoles)
							v.mouseWheel(dwheel, getModifier());
					if(objectPoles != null)
						for(ObjectPole o : objectPoles)
							o.mouseWheel(dwheel, getModifier());
				} else {
					if(viewPoles != null)
						for(ViewPole v : viewPoles)
							v.mouseMove(new Vector2(Mouse.getX(), Mouse.getY()));
					if(objectPoles != null)
						for(ObjectPole o : objectPoles)
							o.mouseMove(new Vector2(Mouse.getX(), Mouse.getY()));
				}
			}
		}
	}
	
	private static MouseModifier getModifier() {
		if(Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT))
			return MouseModifier.KEY_SHIFT;
		
		if(Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL))
			return MouseModifier.KEY_CTRL;
		
		if(Keyboard.isKeyDown(Keyboard.KEY_LMENU))
			return MouseModifier.KEY_ALT;
		
		return null;
	}
	
	public static String readFully(InputStream is) {
		try(BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
			StringBuilder s = new StringBuilder();
			String l;
			
			while((l = reader.readLine()) != null)
				s.append(l).append('\n');
			
			return s.toString();
		} catch(Exception exc) {
			throw new RuntimeException("Failure reading input stream", exc);
		}
	}
	
	public static Quaternion parseQuaternion(String s) {
		String[] comp = StringUtil.split(s, ' ');
		if(comp.length != 4)
			throw new IllegalArgumentException("invalid Quaternion");
		
		Quaternion quat = new Quaternion();
		quat.x(Float.parseFloat(comp[0]));
		quat.y(Float.parseFloat(comp[1]));
		quat.z(Float.parseFloat(comp[2]));
		quat.w(Float.parseFloat(comp[3]));
		
		return quat;
	}
	
	public static Vector4 parseVector4(String s) {
		String[] comp = StringUtil.split(s, ' ');
		if(comp.length != 4)
			throw new IllegalArgumentException("invalid Vector4");
		
		Vector4 vec = new Vector4();
		vec.x(Float.parseFloat(comp[0]));
		vec.y(Float.parseFloat(comp[1]));
		vec.z(Float.parseFloat(comp[2]));
		vec.w(Float.parseFloat(comp[3]));
		
		return vec;
	}
	
	public static Vector3 parseVector3(String s) throws NumberFormatException {
		String[] comp = StringUtil.split(s, ' ');
		if(comp.length != 3)
			throw new IllegalArgumentException("invalid Vector3");
		
		Vector3 vec = new Vector3();
		vec.x(Float.parseFloat(comp[0]));
		vec.y(Float.parseFloat(comp[1]));
		vec.z(Float.parseFloat(comp[2]));
		
		return vec;
	}
	
	public static Vector2 parseVector2(String s) throws NumberFormatException {
		String[] comp = StringUtil.split(s, ' ');
		if(comp.length != 2)
			throw new IllegalArgumentException("invalid Vector2");
		
		Vector2 vec = new Vector2();
		vec.x(Float.parseFloat(comp[0]));
		vec.y(Float.parseFloat(comp[1]));
		
		return vec;
	}
}
 public class Matrix3 {
	private float[] matrix;
	
	public Matrix3() {
		matrix = new float[9];
	}
	
	public Matrix3(float[] m) {
		this();
		put(m);
	}
	
	public Matrix3(Matrix3 m) {
		this();
		put(m);
	}
	
	public Matrix3(Matrix4 m) {
		this();
		put(m);
	}
	
	public Matrix3 clear() {
		Arrays.fill(matrix, 0);
		return this;
	}
	
	public Matrix3 clearToIdentity() {
		return clear().put(0, 1).put(4, 1).put(8, 1);
	}
	
	public float get(int index) {
		return matrix[index];
	}
	
	public Matrix3 put(int index, float f) {
		matrix[index] = f;
		return this;
	}
	
	public Matrix3 putColumn(int index, Vector3 v) {
		put(index * 3 + 0, v.x());
		put(index * 3 + 1, v.y());
		put(index * 3 + 2, v.z());
		return this;
	}
	
	public Matrix3 put(float[] m) {
		if(m.length < matrix.length)
			throw new IllegalArgumentException("float array must have at least " + matrix.length + " values.");
		
		System.arraycopy(m, 0, matrix, 0, matrix.length);
		
		return this;
	}
	
	public Matrix3 put(Matrix3 m) {
		return put(m.matrix);
	}
	
	public Matrix3 put(Matrix4 m) {
		for(int a = 0; a < 3; a++) {
			put(a * 3 + 0, m.get(a * 4 + 0));
			put(a * 3 + 1, m.get(a * 4 + 1));
			put(a * 3 + 2, m.get(a * 4 + 2));
		}
		
		return this;
	}
	
	public Matrix3 mult(float f) {
		for(int a = 0; a < matrix.length; a++)
			put(a, get(a) * f);
		
		return this;
	}
	
	public Matrix3 mult(float[] m) {
		float[] newm = new float[matrix.length];
		
		for(int a = 0; a < matrix.length; a += 3) {
			newm[a + 0] = get(0) * m[a] + get(3) * m[a + 1] + get(6) * m[a + 2];
			newm[a + 1] = get(1) * m[a] + get(4) * m[a + 1] + get(7) * m[a + 2];
			newm[a + 2] = get(2) * m[a] + get(5) * m[a + 1] + get(8) * m[a + 2];
		}
		
		put(newm);
		
		return this;
	}
	
	public Matrix3 mult(Matrix3 m) {
		return mult(m.matrix);
	}
	
	public Vector3 mult(Vector3 vec) {
		Vector3 v = new Vector3();
		
		v.x(get(0) * vec.x() + get(3) * vec.y() + get(6) * vec.z());
		v.x(get(1) * vec.x() + get(4) * vec.y() + get(7) * vec.z());
		v.x(get(2) * vec.x() + get(5) * vec.y() + get(8) * vec.z());
		
		return v;
	}
	
	public Matrix3 transpose() {
		float old = get(1);
		put(1, get(3));
		put(3, old);
		
		old = get(2);
		put(2, get(6));
		put(6, old);
		
		old = get(5);
		put(5, get(7));
		put(7, old);
		
		return this;
	}
	
	public float determinant() {
		return +get(0) * get(4) * get(8) + get(3) * get(7) * get(2) + get(6) * get(1) * get(5)
				- get(2) * get(4) * get(6) - get(5) * get(7) * get(0) - get(8) * get(1) * get(3);
	}
	
	public Matrix3 inverse() {
		Matrix3 inv = new Matrix3();
		
		inv.put(0, +(get(4) * get(8) - get(5) * get(7)));
		inv.put(1, -(get(3) * get(8) - get(5) * get(6)));
		inv.put(2, +(get(3) * get(7) - get(4) * get(6)));
		
		inv.put(3, -(get(1) * get(8) - get(2) * get(7)));
		inv.put(4, +(get(0) * get(8) - get(2) * get(6)));
		inv.put(5, -(get(0) * get(7) - get(1) * get(6)));
		
		inv.put(6, +(get(1) * get(5) - get(2) * get(4)));
		inv.put(7, -(get(0) * get(5) - get(2) * get(3)));
		inv.put(8, +(get(0) * get(4) - get(1) * get(3)));
		
		return put(inv.transpose().mult(1 / determinant()));
	}
	
	private final static FloatBuffer direct = BufferUtils.createFloatBuffer(9);
	
	public FloatBuffer toBuffer() {
		direct.clear();
		direct.put(matrix);
		direct.flip();
		return direct;
	}
}
 public class Matrix4 {
	private float[] matrix;
	
	public Matrix4() {
		matrix = new float[16];
	}
	
	public Matrix4(float[] m) {
		this();
		set(m);
	}
	
	public Matrix4(Matrix3 m) {
		this();
		set(m);
	}
	
	public Matrix4(Matrix4 m) {
		this();
		set(m);
	}
	
	public Matrix4 copy() {
		return new Matrix4(this);
	}
	
	public Matrix4 clear() {
		Arrays.fill(matrix, 0);
		return this;
	}
	
	public Matrix4 clearToIdentity() {
		return clear().put(0, 1)
				.put(5, 1)
				.put(10, 1)
				.put(15, 1);
	}
	
	public Matrix4 clearToOrtho(float left, float right, float bottom, float top, float near, float far) {
		return clear().put(0, 2 / (right - left))
				.put(5, 2 / (top - bottom))
				.put(10, -2 / (far - near))
				.put(12, -(right + left) / (right - left))
				.put(13, -(top + bottom) / (top - bottom))
				.put(14, -(far + near) / (far - near))
				.put(15, 1);
	}
	
	public Matrix4 clearToPerspective(float fovRad, float width, float height, float near, float far) {
		float fov = 1 / (float)Math.tan(fovRad / 2);
		return clear().put(0, fov / (width / height))
				.put(5, fov).put(10, (far + near) / (near - far))
				.put(14, (2 * far * near) / (near - far))
				.put(11, -1);
	}
	
	public Matrix4 clearToPerspectiveDeg(float fov, float width, float height, float near, float far) {
		return clearToPerspective((float)Math.toRadians(fov), width, height, near, far);
	}
	
	public float get(int index) {
		return matrix[index];
	}
	
	public Vector4 getColumn(int index) {
		return new Vector4(get(index * 4 + 0), get(index * 4 + 1), get(index * 4 + 2), get(index * 4 + 3));
	}
	
	public Matrix4 put(int index, float f) {
		matrix[index] = f;
		return this;
	}
	
	public Matrix4 putColumn(int index, Vector4 v) {
		put(index * 4 + 0, v.x());
		put(index * 4 + 1, v.y());
		put(index * 4 + 2, v.z());
		put(index * 4 + 3, v.z());
		return this;
	}
	
	public Matrix4 putColumn(int index, Vector3 v) {
		put(index * 4 + 0, v.x());
		put(index * 4 + 1, v.y());
		put(index * 4 + 2, v.z());
		return this;
	}
	
	public Matrix4 putColumn(int index, Vector3 v, float w) {
		put(index * 4 + 0, v.x());
		put(index * 4 + 1, v.y());
		put(index * 4 + 2, v.z());
		put(index * 4 + 3, w);
		return this;
	}
	
	public Matrix4 set(float[] m) {
		if(m.length < matrix.length)
			throw new IllegalArgumentException("float array must have at least " + matrix.length + " values.");
		
		System.arraycopy(m, 0, matrix, 0, matrix.length);
		
		return this;
	}
	
	public Matrix4 set(Matrix4 m) {
		return set(m.matrix);
	}
	
	public Matrix4 set(Matrix3 m) {
		for(int a = 0; a < 3; a++) {
			matrix[a * 4 + 0] = m.get(a * 3 + 0);
			matrix[a * 4 + 1] = m.get(a * 3 + 1);
			matrix[a * 4 + 2] = m.get(a * 3 + 2);
		}
		
		return this;
	}
	
	public Matrix4 mult(float f) {
		for(int a = 0; a < matrix.length; a++)
			put(a, get(a) * f);
		
		return this;
	}
	
	public Matrix4 mult(float[] m) {
		float[] newm = new float[matrix.length];
		
		for(int a = 0; a < matrix.length; a += 4) {
			newm[a + 0] = get(0) * m[a] + get(4) * m[a + 1] + get(8) * m[a + 2] + get(12) * m[a + 3];
			newm[a + 1] = get(1) * m[a] + get(5) * m[a + 1] + get(9) * m[a + 2] + get(13) * m[a + 3];
			newm[a + 2] = get(2) * m[a] + get(6) * m[a + 1] + get(10) * m[a + 2] + get(14) * m[a + 3];
			newm[a + 3] = get(3) * m[a] + get(7) * m[a + 1] + get(11) * m[a + 2] + get(15) * m[a + 3];
		}
		
		set(newm);
		
		return this;
	}
	
	public Matrix4 mult(Matrix4 m) {
		return mult(m.matrix);
	}
	
	public Vector4 mult(Vector4 vec) {
		Vector4 v = new Vector4();
		
		v.x(get(0) * vec.x() + get(4) * vec.y() + get(8) * vec.z() + get(12) * vec.w());
		v.y(get(1) * vec.x() + get(5) * vec.y() + get(9) * vec.z() + get(13) * vec.w());
		v.z(get(2) * vec.x() + get(6) * vec.y() + get(10) * vec.z() + get(14) * vec.w());
		v.w(get(3) * vec.x() + get(7) * vec.y() + get(11) * vec.z() + get(15) * vec.w());
		
		return v;
	}
	
	public Matrix4 transpose() {
		float old = get(1);
		put(1, get(4));
		put(4, old);
		
		old = get(2);
		put(2, get(8));
		put(8, old);
		
		old = get(3);
		put(3, get(12));
		put(12, old);
		
		old = get(7);
		put(7, get(13));
		put(13, old);
		
		old = get(11);
		put(11, get(14));
		put(14, old);
		
		old = get(6);
		put(6, get(9));
		put(9, old);
		
		return this;
	}
	
	public Matrix4 translate(float x, float y, float z) {
		float[] m = new float[matrix.length];
		
		m[0] = 1;
		m[5] = 1;
		m[10] = 1;
		m[15] = 1;
		
		m[12] = x;
		m[13] = y;
		m[14] = z;
		
		return mult(m);
	}
	
	public Matrix4 translate(Vector3 vec) {
		return translate(vec.x(), vec.y(), vec.z());
	}
	
	public Matrix4 scale(float f) {
		return scale(f, f, f);
	}
	
	public Matrix4 scale(float x, float y, float z) {
		float[] m = new float[matrix.length];
		
		m[0] = x;
		m[5] = y;
		m[10] = z;
		m[15] = 1;
		
		return mult(m);
	}
	
	public Matrix4 scale(Vector3 vec) {
		return scale(vec.x(), vec.y(), vec.z());
	}
	
	public Matrix4 rotate(float angle, float x, float y, float z) {
		float cos = (float)Math.cos(angle);
		float sin = (float)Math.sin(angle);
		float oneMinusCos = 1 - cos;
		
		Vector3 v = new Vector3(x, y, z).normalize();
		
		float[] m = new float[matrix.length];
		m[0] = v.x() * v.x() + (1 - v.x() * v.x()) * cos;
		m[4] = v.x() * v.y() * oneMinusCos - v.z() * sin;
		m[8] = v.x() * v.z() * oneMinusCos + v.y() * sin;
		
		m[1] = v.y() * v.x() * oneMinusCos + v.z() * sin;
		m[5] = v.y() * v.y() + (1 - v.y() * v.y()) * cos;
		m[9] = v.y() * v.z() * oneMinusCos - v.x() * sin;
		
		m[2] = v.z() * v.x() * oneMinusCos - v.y() * sin;
		m[6] = v.z() * v.y() * oneMinusCos + v.x() * sin;
		m[10] = v.z() * v.z() + (1 - v.z() * v.z()) * cos;
		
		m[15] = 1;
		
		return mult(m);
	}
	
	public Matrix4 rotate(float angle, Vector3 vec) {
		return rotate(angle, vec.x(), vec.y(), vec.z());
	}
	
	public Matrix4 rotateDeg(float angle, float x, float y, float z) {
		return rotate((float)Math.toRadians(angle), x, y, z);
	}
	
	public Matrix4 rotateDeg(float angle, Vector3 vec) {
		return rotate((float)Math.toRadians(angle), vec);
	}
	
	public float determinant() {
		float a = get(5) * get(10) * get(15) + get(9) * get(14) * get(7) + get(13) * get(6) * get(11) - get(7) * get(10) * get(13) - get(11) * get(14) * get(5) - get(15) * get(6) * get(9);
		float b = get(1) * get(10) * get(15) + get(9) * get(14) * get(3) + get(13) * get(2) * get(11) - get(3) * get(10) * get(13) - get(11) * get(14) * get(1) - get(15) * get(2) * get(9);
		float c = get(1) * get(6) * get(15) + get(5) * get(14) * get(3) + get(13) * get(2) * get(7) - get(3) * get(6) * get(13) - get(7) * get(14) * get(1) - get(15) * get(2) * get(5);
		float d = get(1) * get(6) * get(11) + get(5) * get(10) * get(3) + get(9) * get(2) * get(7) - get(3) * get(6) * get(9) - get(7) * get(10) * get(1) - get(11) * get(2) * get(5);
		
		return get(0) * a - get(4) * b + get(8) * c - get(12) * d;
	}
	
	public Matrix4 inverse() {
		Matrix4 inv = new Matrix4();
		
		inv.put(0, +(get(5) * get(10) * get(15) + get(9) * get(14) * get(7) + get(13) * get(6) * get(11) - get(7) * get(10) * get(13) - get(11) * get(14) * get(5) - get(15) * get(6) * get(9)));
		inv.put(1, -(get(4) * get(10) * get(15) + get(8) * get(14) * get(7) + get(12) * get(6) * get(11) - get(7) * get(10) * get(12) - get(11) * get(14) * get(4) - get(15) * get(6) * get(8)));
		inv.put(2, +(get(4) * get(9) * get(15) + get(8) * get(13) * get(7) + get(12) * get(5) * get(11) - get(7) * get(9) * get(12) - get(11) * get(13) * get(4) - get(15) * get(5) * get(8)));
		inv.put(3, -(get(4) * get(9) * get(14) + get(8) * get(13) * get(6) + get(12) * get(5) * get(10) - get(6) * get(9) * get(12) - get(10) * get(13) * get(4) - get(14) * get(5) * get(8)));
		
		inv.put(4, -(get(1) * get(10) * get(15) + get(9) * get(14) * get(3) + get(13) * get(2) * get(11) - get(3) * get(10) * get(13) - get(11) * get(14) * get(1) - get(15) * get(2) * get(9)));
		inv.put(5, +(get(0) * get(10) * get(15) + get(8) * get(14) * get(3) + get(12) * get(2) * get(11) - get(3) * get(10) * get(12) - get(11) * get(14) * get(0) - get(15) * get(2) * get(8)));
		inv.put(6, -(get(0) * get(9) * get(15) + get(8) * get(13) * get(3) + get(12) * get(1) * get(11) - get(3) * get(9) * get(12) - get(11) * get(13) * get(0) - get(15) * get(1) * get(8)));
		inv.put(7, +(get(0) * get(9) * get(14) + get(8) * get(13) * get(2) + get(12) * get(1) * get(10) - get(2) * get(9) * get(12) - get(10) * get(13) * get(0) - get(14) * get(1) * get(8)));
		
		inv.put(8, +(get(1) * get(6) * get(15) + get(5) * get(14) * get(3) + get(13) * get(2) * get(7) - get(3) * get(6) * get(13) - get(7) * get(14) * get(1) - get(15) * get(2) * get(5)));
		inv.put(9, -(get(0) * get(6) * get(15) + get(4) * get(14) * get(3) + get(12) * get(2) * get(7) - get(3) * get(6) * get(12) - get(7) * get(14) * get(0) - get(15) * get(2) * get(4)));
		inv.put(10, +(get(0) * get(5) * get(15) + get(4) * get(13) * get(3) + get(12) * get(1) * get(7) - get(3) * get(5) * get(12) - get(7) * get(13) * get(0) - get(15) * get(1) * get(4)));
		inv.put(11, -(get(0) * get(5) * get(14) + get(4) * get(13) * get(2) + get(12) * get(1) * get(6) - get(2) * get(5) * get(12) - get(6) * get(13) * get(0) - get(14) * get(1) * get(4)));
		
		inv.put(12, -(get(1) * get(6) * get(11) + get(5) * get(10) * get(3) + get(9) * get(2) * get(7) - get(3) * get(6) * get(9) - get(7) * get(10) * get(1) - get(11) * get(2) * get(5)));
		inv.put(13, +(get(0) * get(6) * get(11) + get(4) * get(10) * get(3) + get(8) * get(2) * get(7) - get(3) * get(6) * get(8) - get(7) * get(10) * get(0) - get(11) * get(2) * get(4)));
		inv.put(14, -(get(0) * get(5) * get(11) + get(4) * get(9) * get(3) + get(8) * get(1) * get(7) - get(3) * get(5) * get(8) - get(7) * get(9) * get(0) - get(11) * get(1) * get(4)));
		inv.put(15, +(get(0) * get(5) * get(10) + get(4) * get(9) * get(2) + get(8) * get(1) * get(6) - get(2) * get(5) * get(8) - get(6) * get(9) * get(0) - get(10) * get(1) * get(4)));
		
		return set(inv.transpose().mult(1 / determinant()));
	}
	
	public Quaternion toQuaternion() {
		float x = get(0) - get(5) - get(10);
		float y = get(5) - get(0) - get(10);
		float z = get(10) - get(0) - get(5);
		float w = get(0) + get(5) + get(10);
		
		int biggestIndex = 0;
		float biggest = w;
		
		if(x > biggest) {
			biggest = x;
			biggestIndex = 1;
		}
		
		if(y > biggest) {
			biggest = y;
			biggestIndex = 2;
		}
		
		if(z > biggest) {
			biggest = z;
			biggestIndex = 3;
		}
		
		float biggestVal = (float)(Math.sqrt(biggest + 1) * 0.5);
		float mult = 0.25f / biggestVal;
		
		Quaternion res = new Quaternion();
		
		switch(biggestIndex) {
			case 0:
				res.w(biggestVal);
				res.x((get(6) - get(9)) * mult);
				res.y((get(8) - get(2)) * mult);
				res.z((get(1) - get(4)) * mult);
				break;
			case 1:
				res.w((get(6) - get(9)) * mult);
				res.x(biggestVal);
				res.y((get(1) + get(4)) * mult);
				res.z((get(8) + get(2)) * mult);
				break;
			case 2:
				res.w((get(8) - get(2)) * mult);
				res.x((get(1) + get(4)) * mult);
				res.y(biggestVal);
				res.z((get(6) + get(9)) * mult);
				break;
			case 3:
				res.w((get(1) - get(4)) * mult);
				res.x((get(8) + get(2)) * mult);
				res.y((get(6) + get(9)) * mult);
				res.z(biggestVal);
				break;
		}
		
		return res;
	}
	
	private final static FloatBuffer direct = BufferUtils.createFloatBuffer(16);
	
	public FloatBuffer toBuffer() {
		direct.clear();
		direct.put(matrix);
		direct.flip();
		return direct;
	}
}
 public class MatrixStack {
	private Stack<Matrix4> stack;
	private Matrix4 current;
	
	public MatrixStack() {
		current = new Matrix4().clearToIdentity();
		stack = new Stack<>();
	}
	
	public Matrix4 getTop() {
		return current;
	}
	
	public void setTop(Matrix4 m) {
		current = m;
	}
	
	public void pushMatrix() {
		stack.push(current);
		current = new Matrix4(current);
	}
	
	public void popMatrix() {
		current = stack.pop();
	}
}
 public class Quaternion {
	private float x, y, z, w;
	
	public Quaternion() {
		reset();
	}
	
	public Quaternion(float x, float y, float z, float w) {
		set(x, y, z, w);
	}
	
	public Quaternion(float angle, Vector3 vec) {
		float s = (float)Math.sin(angle / 2);
		
		x = vec.x() * s;
		y = vec.y() * s;
		z = vec.z() * s;
		w = (float)Math.cos(angle / 2);
	}
	
	public Quaternion(Quaternion q) {
		set(q);
	}
	
	public Quaternion copy() {
		return new Quaternion(this);
	}
	
	public float x() {
		return x;
	}
	
	public Quaternion x(float x) {
		this.x = x;
		return this;
	}
	
	public float y() {
		return y;
	}
	
	public Quaternion y(float y) {
		this.y = y;
		return this;
	}
	
	public float z() {
		return z;
	}
	
	public Quaternion z(float z) {
		this.z = z;
		return this;
	}
	
	public float w() {
		return w;
	}
	
	public Quaternion w(float w) {
		this.w = w;
		return this;
	}
	
	public Quaternion set(float x, float y, float z, float w) {
		this.x = x;
		this.y = y;
		this.z = z;
		this.w = w;
		return this;
	}
	
	public Quaternion set(Quaternion q) {
		return set(q.x, q.y, q.z, q.w);
	}
	
	public Quaternion reset() {
		x = y = z = 0;
		w = 1;
		return this;
	}
	
	public float length() {
		return (float)Math.sqrt(x * x + y * y + z * z + w * w);
	}
	
	public Quaternion normalize() {
		float length = length();
		x /= length;
		y /= length;
		z /= length;
		w /= length;
		return this;
	}
	
	public float dot(Quaternion q) {
		return x * q.x + y * q.y + z * q.z + w * q.w;
	}
	
	public Quaternion add(float x, float y, float z, float w) {
		this.x += x;
		this.y += y;
		this.z += z;
		this.w += w;
		
		return this;
	}
	
	public Quaternion add(Quaternion q) {
		return add(q.x, q.y, q.z, q.w);
	}
	
	public Quaternion sub(float x, float y, float z, float w) {
		this.x -= x;
		this.y -= y;
		this.z -= z;
		this.w -= w;
		
		return this;
	}
	
	public Quaternion sub(Quaternion q) {
		return sub(q.x, q.y, q.z, q.w);
	}
	
	public Quaternion mult(float f) {
		this.x *= f;
		this.y *= f;
		this.z *= f;
		this.w *= f;
		
		return this;
	}
	
	public Vector3 mult(Vector3 v) {
		Vector3 uv, uuv;
		Vector3 quatVector = new Vector3(x, y, z);
		
		uv = quatVector.cross(v);
		uuv = quatVector.cross(uv);
		
		uv.mult(w * 2);
		uuv.mult(2);
		
		return new Vector3(v).add(uv).add(uuv);
	}
	
	public Quaternion mult(Quaternion q) {
		float xx = w * q.x + x * q.w + y * q.z - z * q.y;
		float yy = w * q.y + y * q.w + z * q.x - x * q.z;
		float zz = w * q.z + z * q.w + x * q.y - y * q.x;
		float ww = w * q.w - x * q.x - y * q.y - z * q.z;
		
		x = xx;
		y = yy;
		z = zz;
		w = ww;
		
		return this;
	}
	
	public Quaternion conjugate() {
		x *= -1;
		y *= -1;
		z *= -1;
		
		return this;
	}
	
	public Matrix4 toMatrix() {
		float[] m = {
				1 - 2 * y * y - 2 * z * z, 2 * x * y + 2 * w * z, 2 * x * z - 2 * w * y, 0,
				2 * x * y - 2 * w * z, 1 - 2 * x * x - 2 * z * z, 2 * y * z + 2 * w * x, 0,
				2 * x * z + 2 * w * y, 2 * y * z - 2 * w * x, 1 - 2 * x * x - 2 * y * y, 0,
				0, 0, 0, 1,
		};
		
		return new Matrix4(m);
	}
}
 public class Vector3 implements Vector<Vector3> {
	private float x, y, z;
	
	public Vector3() {
		set(0, 0, 0);
	}
	
	public Vector3(float v) {
		this(v, v, v);
	}
	
	public Vector3(float x, float y, float z) {
		set(x, y, z);
	}
	
	public Vector3(Vector2 vec) {
		set(vec);
	}
	
	public Vector3(Vector2 vec, float z) {
		set(vec, z);
	}
	
	public Vector3(Vector3 vec) {
		set(vec);
	}
	
	public Vector3(Vector4 vec) {
		set(vec);
	}
	
	@Override
	public Vector3 copy() {
		return new Vector3(this);
	}
	
	public float x() {
		return x;
	}
	
	public Vector3 x(float x) {
		this.x = x;
		return this;
	}
	
	public float y() {
		return y;
	}
	
	public Vector3 y(float y) {
		this.y = y;
		return this;
	}
	
	public float z() {
		return z;
	}
	
	public Vector3 z(float z) {
		this.z = z;
		return this;
	}
	
	@Override
	public boolean equals(Object o) {
		if(o instanceof Vector3) {
			Vector3 v = (Vector3)o;
			return x == v.x && y == v.y && z == v.z;
		}
		
		return false;
	}
	
	public Vector3 set(float x, float y, float z) {
		this.x = x;
		this.y = y;
		this.z = z;
		return this;
	}
	
	public Vector3 set(Vector2 vec) {
		return set(vec.x(), vec.y(), 0);
	}
	
	public Vector3 set(Vector2 vec, float z) {
		return set(vec.x(), vec.y(), z);
	}
	
	public Vector3 set(Vector3 vec) {
		return set(vec.x, vec.y, vec.z);
	}
	
	public Vector3 set(Vector4 vec) {
		return set(vec.x(), vec.y(), vec.z());
	}
	
	public Vector3 reset() {
		x = y = z = 0;
		return this;
	}
	
	@Override
	public float length() {
		return (float)Math.sqrt(x * x + y * y + z * z);
	}
	
	public Vector3 normalize() {
		float length = length();
		x /= length;
		y /= length;
		z /= length;
		return this;
	}
	
	public float dot(Vector3 vec) {
		return x * vec.x + y * vec.y + z * vec.z;
	}
	
	public Vector3 cross(Vector3 vec) {
		return new Vector3(y * vec.z - vec.y * z, z * vec.x - vec.z * x, x * vec.y - vec.x * y);
	}
	
	public Vector3 add(float x, float y, float z) {
		this.x += x;
		this.y += y;
		this.z += z;
		return this;
	}
	
	@Override
	public Vector3 add(Vector3 vec) {
		return add(vec.x, vec.y, vec.z);
	}
	
	public Vector3 sub(float x, float y, float z) {
		this.x -= x;
		this.y -= y;
		this.z -= z;
		return this;
	}
	
	@Override
	public Vector3 sub(Vector3 vec) {
		return sub(vec.x, vec.y, vec.z);
	}
	
	@Override
	public Vector3 mult(float f) {
		return mult(f, f, f);
	}
	
	public Vector3 mult(float x, float y, float z) {
		this.x *= x;
		this.y *= y;
		this.z *= z;
		return this;
	}
	
	@Override
	public Vector3 mult(Vector3 vec) {
		return mult(vec.x, vec.y, vec.z);
	}
	
	@Override
	public Vector3 divide(float f) {
		return divide(f, f, f);
	}
	
	public Vector3 divide(float x, float y, float z) {
		this.x /= x;
		this.y /= y;
		this.z /= z;
		return this;
	}
	
	@Override
	public Vector3 divide(Vector3 vec) {
		return divide(vec.x, vec.y, vec.z);
	}
	
	private final static FloatBuffer direct = BufferUtils.createFloatBuffer(3);
	
	@Override
	public FloatBuffer toBuffer() {
		direct.clear();
		direct.put(x).put(y).put(z);
		direct.flip();
		return direct;
	}
}
 public class Vector4 implements Vector<Vector4> {
	private float x, y, z, w;
	
	public Vector4() {
		set(0, 0, 0, 0);
	}
	
	public Vector4(float v) {
		this(v, v, v, v);
	}
	
	public Vector4(float x, float y, float z, float w) {
		set(x, y, z, w);
	}
	
	public Vector4(Vector2 vec) {
		set(vec);
	}
	
	public Vector4(Vector2 vec, float z, float w) {
		set(vec, z, w);
	}
	
	public Vector4(Vector3 vec) {
		set(vec);
	}
	
	public Vector4(Vector3 vec, float w) {
		set(vec, w);
	}
	
	public Vector4(Vector4 vec) {
		set(vec);
	}
	
	@Override
	public Vector4 copy() {
		return new Vector4(this);
	}
	
	public float x() {
		return x;
	}
	
	public Vector4 x(float x) {
		this.x = x;
		return this;
	}
	
	public float y() {
		return y;
	}
	
	public Vector4 y(float y) {
		this.y = y;
		return this;
	}
	
	public float z() {
		return z;
	}
	
	public Vector4 z(float z) {
		this.z = z;
		return this;
	}
	
	public float w() {
		return w;
	}
	
	public Vector4 w(float w) {
		this.w = w;
		return this;
	}
	
	@Override
	public boolean equals(Object o) {
		if(o instanceof Vector4) {
			Vector4 v = (Vector4)o;
			return x == v.x && y == v.y && z == v.z && w == v.w;
		}
		
		return false;
	}
	
	public Vector4 set(float x, float y, float z, float w) {
		this.x = x;
		this.y = y;
		this.z = z;
		this.w = w;
		return this;
	}
	
	public Vector4 set(Vector2 vec) {
		return set(vec, 0, 0);
	}
	
	public Vector4 set(Vector2 vec, float z, float w) {
		return set(vec.x(), vec.y(), z, w);
	}
	
	public Vector4 set(Vector3 vec) {
		return set(vec, 0);
	}
	
	public Vector4 set(Vector3 vec, float w) {
		return set(vec.x(), vec.y(), vec.z(), w);
	}
	
	public Vector4 set(Vector4 vec) {
		return set(vec.x, vec.y, vec.z, vec.w);
	}
	
	public Vector4 reset() {
		x = y = z = w = 0;
		return this;
	}
	
	@Override
	public float length() {
		return (float)Math.sqrt(x * x + y * y + z * z + w * w);
	}
	
	public Vector4 normalize() {
		float length = length();
		x /= length;
		y /= length;
		z /= length;
		w /= length;
		return this;
	}
	
	public float dot(Vector4 vec) {
		return x * vec.x + y * vec.y + z * vec.z + w * vec.w;
	}
	
	public Vector4 add(float x, float y, float z, float w) {
		this.x += x;
		this.y += y;
		this.z += z;
		this.w += w;
		return this;
	}
	
	@Override
	public Vector4 add(Vector4 vec) {
		return add(vec.x, vec.y, vec.z, vec.w);
	}
	
	public Vector4 sub(float x, float y, float z, float w) {
		this.x -= x;
		this.y -= y;
		this.z -= z;
		this.w -= w;
		return this;
	}
	
	@Override
	public Vector4 sub(Vector4 vec) {
		return sub(vec.x, vec.y, vec.z, vec.w);
	}
	
	@Override
	public Vector4 mult(float f) {
		return mult(f, f, f, f);
	}
	
	public Vector4 mult(float x, float y, float z, float w) {
		this.x *= x;
		this.y *= y;
		this.z *= z;
		this.w *= w;
		return this;
	}
	
	@Override
	public Vector4 mult(Vector4 vec) {
		return mult(vec.x, vec.y, vec.z, vec.w);
	}
	
	@Override
	public Vector4 divide(float f) {
		return divide(f, f, f, f);
	}
	
	public Vector4 divide(float x, float y, float z, float w) {
		this.x /= x;
		this.y /= y;
		this.z /= z;
		this.w /= w;
		return this;
	}
	
	@Override
	public Vector4 divide(Vector4 vec) {
		return divide(vec.x, vec.y, vec.z, vec.w);
	}
	
	private final static FloatBuffer direct = BufferUtils.createFloatBuffer(4);
	
	@Override
	public FloatBuffer toBuffer() {
		direct.clear();
		direct.put(x).put(y).put(z).put(w);
		direct.flip();
		return direct;
	}
}

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL31.*;
import static org.lwjgl.opengl.GL32.*;
import org.lwjgl.input.Keyboard;
import com.ra4king.opengl.GLProgram;
import com.ra4king.opengl.util.Mesh;
import com.ra4king.opengl.util.MousePoles.MouseButton;
import com.ra4king.opengl.util.MousePoles.ObjectData;
import com.ra4king.opengl.util.MousePoles.ObjectPole;
import com.ra4king.opengl.util.MousePoles.ViewData;
import com.ra4king.opengl.util.MousePoles.ViewPole;
import com.ra4king.opengl.util.MousePoles.ViewScale;
import com.ra4king.opengl.util.ShaderProgram;
import com.ra4king.opengl.util.Timer;
import com.ra4king.opengl.util.Utils;
import com.ra4king.opengl.util.math.Matrix3;
import com.ra4king.opengl.util.math.Matrix4;
import com.ra4king.opengl.util.math.MatrixStack;
import com.ra4king.opengl.util.math.Quaternion;
import com.ra4king.opengl.util.math.Vector3;
import com.ra4king.opengl.util.math.Vector4;

package com.ra4king.opengl.arcsynthesis.gl33.chapter10.example1;




public class Example10_1 extends GLProgram {
	public static void main(String[] args) {
		new Example10_1().run(true);
	}
	
	private ProgramData whiteDiffuseColor;
	private ProgramData vertexDiffuseColor;
	
	private UnlitProgramData unlit;
	
	private int projectionUniformBuffer;
	private final int projectionBlockIndex = 2;
	
	private Mesh cylinderMesh;
	private Mesh planeMesh;
	private Mesh cubeMesh;
	
	private ViewPole viewPole;
	private ObjectPole objectPole;
	

	private Timer lightTimer = new Timer(Timer.Type.LOOP, 5);
