Commits

Roi Atalla committed ee08925

Created classes: Matrix3, Vector4, Utils, and MousePoles. Implemented Example 9.1 but it's still slightly buggy.

Comments (0)

Files changed (19)

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter6/example4/Example6_4.java

 
 import com.ra4king.opengl.GLProgram;
 import com.ra4king.opengl.util.ShaderProgram;
+import com.ra4king.opengl.util.Utils;
 import com.ra4king.opengl.util.math.Matrix4;
 import com.ra4king.opengl.util.math.MatrixStack;
 import com.ra4king.opengl.util.math.Vector3;
 		
 		public void adjUpperArm(boolean increment) {
 			angleUpperArm += increment ? STANDARD_ANGLE_INCREMENT : -STANDARD_ANGLE_INCREMENT;
-			angleUpperArm = clamp(angleUpperArm, - 90, 0);
+			angleUpperArm = Utils.clamp(angleUpperArm, - 90, 0);
 		}
 		
 		public void adjLowerArm(boolean increment) {
 			angleLowerArm += increment ? STANDARD_ANGLE_INCREMENT : -STANDARD_ANGLE_INCREMENT;
-			angleLowerArm = clamp(angleLowerArm, 0, 146.25f);
+			angleLowerArm = Utils.clamp(angleLowerArm, 0, 146.25f);
 		}
 		
 		public void adjWristPitch(boolean increment) {
 			angleWristPitch += increment ? STANDARD_ANGLE_INCREMENT : -STANDARD_ANGLE_INCREMENT;
-			angleWristPitch = clamp(angleWristPitch, 0, 90);
+			angleWristPitch = Utils.clamp(angleWristPitch, 0, 90);
 		}
 		
 		public void adjWristRoll(boolean increment) {
 		
 		public void adjFingerOpen(boolean increment) {
 			angleFingerOpen += increment ? STANDARD_ANGLE_INCREMENT : -STANDARD_ANGLE_INCREMENT;
-			angleFingerOpen = clamp(angleFingerOpen, 9, 180);
+			angleFingerOpen = Utils.clamp(angleFingerOpen, 9, 180);
 		}
 		
 		public void writePose() {
 			
 			modelToCameraStack.popMatrix();
 		}
-		
-		private float clamp(float value, float low, float high) {
-			return Math.max(Math.min(value, high), low);
-		}
 	}
 }

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter7/example1/Example7_1.java

 import com.ra4king.opengl.GLProgram;
 import com.ra4king.opengl.util.Mesh;
 import com.ra4king.opengl.util.ShaderProgram;
+import com.ra4king.opengl.util.Utils;
 import com.ra4king.opengl.util.math.Matrix4;
 import com.ra4king.opengl.util.math.MatrixStack;
 import com.ra4king.opengl.util.math.Vector3;
 		if(Keyboard.isKeyDown(Keyboard.KEY_U))
 			sphereCamRelPos.add(0,0,speed3);
 		
-		sphereCamRelPos.y(clamp(sphereCamRelPos.y(), -78.75f, -1));
+		sphereCamRelPos.y(Utils.clamp(sphereCamRelPos.y(), -78.75f, -1));
 		camTarget.y(camTarget.y() > 0 ? camTarget.y() : 0);
 		sphereCamRelPos.z(sphereCamRelPos.z() > 5 ? sphereCamRelPos.z() : 5);
 	}
 	
-	private float clamp(float value, float low, float high) {
-		return Math.min(Math.max(value, low), high);
-	}
-	
 	@Override
 	public void keyPressed(int key, char c, long nanos) {
 		switch(key) {

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter7/example2/Example7_2.java

 import com.ra4king.opengl.GLProgram;
 import com.ra4king.opengl.util.Mesh;
 import com.ra4king.opengl.util.ShaderProgram;
+import com.ra4king.opengl.util.Utils;
 import com.ra4king.opengl.util.math.Matrix4;
 import com.ra4king.opengl.util.math.MatrixStack;
 import com.ra4king.opengl.util.math.Vector3;
 		if(Keyboard.isKeyDown(Keyboard.KEY_U))
 			sphereCamRelPos.add(0,0,speed3);
 		
-		sphereCamRelPos.y(clamp(sphereCamRelPos.y(), -78.75f, -1));
+		sphereCamRelPos.y(Utils.clamp(sphereCamRelPos.y(), -78.75f, -1));
 		camTarget.y(camTarget.y() > 0 ? camTarget.y() : 0);
 		sphereCamRelPos.z(sphereCamRelPos.z() > 5 ? sphereCamRelPos.z() : 5);
 	}
 	
-	private float clamp(float value, float low, float high) {
-		return Math.min(Math.max(value, low), high);
-	}
-	
 	@Override
 	public void keyPressed(int key, char c, long nanos) {
 		switch(key) {

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter8/example3/Example8_3.java

 import com.ra4king.opengl.GLProgram;
 import com.ra4king.opengl.util.Mesh;
 import com.ra4king.opengl.util.ShaderProgram;
+import com.ra4king.opengl.util.Utils;
 import com.ra4king.opengl.util.math.Matrix4;
 import com.ra4king.opengl.util.math.MatrixStack;
 import com.ra4king.opengl.util.math.Quaternion;
 		orientation.normalize();
 	}
 	
-	private float clamp(float value, float low, float high) {
-		return Math.min(Math.max(value, low), high);
-	}
-	
 	@Override
 	public void update(long deltaTime) {
 		float speed = 90 * deltaTime / (float)1e9;
 		if(Keyboard.isKeyDown(Keyboard.KEY_L))
 			sphereCamRelPos.add(speed, 0, 0);
 		
-		sphereCamRelPos.y(clamp(sphereCamRelPos.y(), -78.75f, 10));
+		sphereCamRelPos.y(Utils.clamp(sphereCamRelPos.y(), -78.75f, 10));
 	}
 	
 	@Override

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter8/example4/Example8_4.java

 import com.ra4king.opengl.util.Mesh;
 import com.ra4king.opengl.util.ShaderProgram;
 import com.ra4king.opengl.util.Timer;
+import com.ra4king.opengl.util.Utils;
 import com.ra4king.opengl.util.Timer.Type;
 import com.ra4king.opengl.util.math.Matrix4;
 import com.ra4king.opengl.util.math.MatrixStack;
 					return lerp(initial, orients[finalOrient], timer.getAlpha());
 			}
 			
-			private float clamp(float value, float low, float high) {
-				return Math.min(Math.max(value, low), high);
-			}
-			
 			private Quaternion slerp(Quaternion q0, Quaternion q1, float alpha) {
 				q0 = new Quaternion(q0);
 				q1 = new Quaternion(q1);
 				if(dot > DOT_THRESHOLD)
 					return lerp(q0, q1, alpha);
 				
-				dot = clamp(dot, -1, 1);
+				dot = Utils.clamp(dot, -1, 1);
 				float theta = (float)Math.acos(dot) * alpha;
 				
 				Quaternion q2 = q1.sub(new Quaternion(q0).mult(dot)).normalize();

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter9/example1/Example9_1.java

 package com.ra4king.opengl.arcsynthesis.gl33.chapter9.example1;
 
-public class Example9_1 {
+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.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;
+
+public class Example9_1 extends GLProgram {
+	public static void main(String[] args) {
+		new Example9_1().run(true);
+	}
 	
+	private ProgramData whiteDiffuseColor;
+	private ProgramData vertexDiffuseColor;
+	
+	private int projectionUniformBuffer;
+	private final int projectionBlockIndex = 2;
+	
+	private Mesh cylinderMesh;
+	private Mesh planeMesh;
+	
+	private ViewPole viewPole;
+	private ObjectPole objectPole;
+	
+	private Vector4 lightDirection = new Vector4(0.866f, 0.5f, 0, 0);
+	
+	private boolean drawColoredCyl = true;
+	
+	public Example9_1() {
+		super("Example 9.1", 500, 500, true);
+	}
+	
+	@Override
+	public void init() {
+		glClearColor(0, 0, 0, 0);
+		glClearDepth(1);
+		
+		ViewData initialViewData = new ViewData(new Vector3(0, 0.5f, 0), new Quaternion(0.3826834f, 0, 0, 0.92387953f), 5, 0);
+		ViewScale viewScale = new ViewScale(3, 20, 1.5f, 0.5f, 0, 0, 90f/250f);
+		ObjectData initialObjectData = new ObjectData(new Vector3(0, 0.5f, 0), new Quaternion());
+		
+		viewPole = new ViewPole(initialViewData, viewScale, MouseButton.LEFT_BUTTON, false);
+		objectPole = new ObjectPole(initialObjectData, 90f/250f, MouseButton.RIGHT_BUTTON, viewPole);
+		
+		whiteDiffuseColor = loadShader("example9.1.VertexLighting_PN.vert", "example9.1.frag");
+		vertexDiffuseColor = loadShader("example9.1.VertexLighting_PCN.vert", "example9.1.frag");
+		
+		try {
+			cylinderMesh = new Mesh(getClass().getResource("example9.1.UnitCylinder.xml"));
+			planeMesh = new Mesh(getClass().getResource("example9.1.LargePlane.xml"));
+		}
+		catch(Exception exc) {
+			exc.printStackTrace();
+			destroy();
+		}
+		
+		glEnable(GL_CULL_FACE);
+		glCullFace(GL_BACK);
+		glFrontFace(GL_CW);
+		
+		glEnable(GL_DEPTH_TEST);
+		glDepthMask(true);
+		glDepthFunc(GL_LEQUAL);
+		glDepthRange(0, 1);
+		glEnable(GL_DEPTH_CLAMP);
+		
+		projectionUniformBuffer = glGenBuffers();
+		glBindBuffer(GL_UNIFORM_BUFFER, projectionUniformBuffer);
+		glBufferData(GL_UNIFORM_BUFFER, 16*4, GL_DYNAMIC_DRAW);
+		glBindBufferRange(GL_UNIFORM_BUFFER, projectionBlockIndex, projectionUniformBuffer, 0, 16*4);
+		glBindBuffer(GL_UNIFORM_BUFFER, 0);
+	}
+	
+	private ProgramData loadShader(String vertFile, String fragFile) {
+		ProgramData data = new ProgramData(new ShaderProgram(readFromFile(vertFile), readFromFile(fragFile)));
+		data.modelToCameraMatrixUniform = glGetUniformLocation(data.program.getProgram(), "modelToCameraMatrix");
+		data.normalModelToCameraMatrixUniform = glGetUniformLocation(data.program.getProgram(), "normalModelToCameraMatrix");
+		data.dirTolightUniform = glGetUniformLocation(data.program.getProgram(), "dirToLight");
+		data.lightIntensityUniform = glGetUniformLocation(data.program.getProgram(), "lightIntensity");
+		
+		int projectionBlock = glGetUniformBlockIndex(data.program.getProgram(), "Projection");
+		glUniformBlockBinding(data.program.getProgram(), projectionBlock, projectionBlockIndex);
+		
+		return data;
+	}
+	
+	@Override
+	public void resized() {
+		super.resized();
+		
+		glBindBuffer(GL_UNIFORM_BUFFER, projectionUniformBuffer);
+		glBufferSubData(GL_UNIFORM_BUFFER, 0, new Matrix4().clearToPerspectiveDeg(45, getWidth(), getHeight(), 1, 1000).toBuffer());
+		glBindBuffer(GL_UNIFORM_BUFFER, 0);
+	}
+	
+	@Override
+	public void update(long deltaTime) {
+		Utils.updateMousePoles(viewPole, objectPole);
+	}
+	
+	@Override
+	public void keyPressed(int key, char c, long nanos) {
+		if(key == Keyboard.KEY_SPACE)
+			drawColoredCyl = !drawColoredCyl;
+	}
+	
+	@Override
+	public void render() {
+		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		
+		MatrixStack modelMatrix = new MatrixStack();
+		modelMatrix.setTop(viewPole.calcMatrix());
+		
+		Vector4 lightDirCameraSpace = modelMatrix.getTop().mult(lightDirection);
+		
+		whiteDiffuseColor.program.begin();
+		glUniform3(whiteDiffuseColor.dirTolightUniform, lightDirCameraSpace.toBuffer());
+		vertexDiffuseColor.program.begin();
+		glUniform3(vertexDiffuseColor.dirTolightUniform, lightDirCameraSpace.toBuffer());
+		vertexDiffuseColor.program.end();
+		
+		{
+			modelMatrix.pushMatrix();
+			
+			{
+				modelMatrix.pushMatrix();
+				
+				whiteDiffuseColor.program.begin();
+				glUniformMatrix4(whiteDiffuseColor.modelToCameraMatrixUniform, false, modelMatrix.getTop().toBuffer());
+				glUniformMatrix3(whiteDiffuseColor.normalModelToCameraMatrixUniform, false, new Matrix3(modelMatrix.getTop()).toBuffer());
+				glUniform4f(whiteDiffuseColor.lightIntensityUniform, 1, 1, 1, 1);
+				planeMesh.render();
+				whiteDiffuseColor.program.end();
+				
+				modelMatrix.popMatrix();
+			}
+			
+			{
+				modelMatrix.pushMatrix();
+				
+				modelMatrix.getTop().mult(objectPole.calcMatrix());
+				
+				if(drawColoredCyl) {
+					vertexDiffuseColor.program.begin();
+					glUniformMatrix4(vertexDiffuseColor.modelToCameraMatrixUniform, false, modelMatrix.getTop().toBuffer());
+					glUniformMatrix3(vertexDiffuseColor.normalModelToCameraMatrixUniform, false, new Matrix3(modelMatrix.getTop()).toBuffer());
+					glUniform4f(vertexDiffuseColor.lightIntensityUniform, 1, 1, 1, 1);
+					cylinderMesh.render("lit-color");
+					vertexDiffuseColor.program.end();
+				}
+				else {
+					whiteDiffuseColor.program.begin();
+					glUniformMatrix4(whiteDiffuseColor.modelToCameraMatrixUniform, false, modelMatrix.getTop().toBuffer());
+					glUniformMatrix3(whiteDiffuseColor.normalModelToCameraMatrixUniform, false, new Matrix3(modelMatrix.getTop()).toBuffer());
+					glUniform4f(whiteDiffuseColor.lightIntensityUniform, 1, 1, 1, 1);
+					cylinderMesh.render("lit");
+					whiteDiffuseColor.program.end();
+				}
+				
+				modelMatrix.popMatrix();
+			}
+			
+			modelMatrix.popMatrix();
+		}
+	}
+	
+	private static class ProgramData {
+		private ShaderProgram program;
+		
+		private int dirTolightUniform;
+		private int lightIntensityUniform;
+		
+		private int modelToCameraMatrixUniform;
+		private int normalModelToCameraMatrixUniform;
+		
+		public ProgramData(ShaderProgram program) {
+			this.program = program;
+		}
+	}
 }

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter9/example1/example9.1.LargePlane.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<?oxygen RNGSchema="../../Documents/meshFormat.rnc" type="compact"?>
+
+<mesh xmlns="http://www.arcsynthesis.com/gltut/mesh" >
+	<attribute index="0" type="float" size="3" > 
+		3 0 -3
+		3 0 3
+		-3 0 3
+		-3 0 -3
+		3 0 -3
+		3 0 3
+		-3 0 3
+		-3 0 -3
+	</attribute>
+	<attribute index="2" type="float" size="3" > 
+		0 1 0
+		0 1 0
+		0 1 0
+		0 1 0
+		0 -1 0
+		0 -1 0
+		0 -1 0
+		0 -1 0
+	</attribute>
+	<indices cmd="triangles" type="ushort" > 
+		0 1 2
+		2 3 0
+		4 6 5
+		6 4 7
+	</indices>
+</mesh>

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter9/example1/example9.1.UnitCylinder.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<?oxygen RNGSchema="../../Documents/meshFormat.rnc" type="compact"?>
+
+<mesh xmlns="http://www.arcsynthesis.com/gltut/mesh" >
+	<attribute index="0" type="float" size="3" > 
+		0 0.5 0
+		0.5 0.5 0
+		0.5 -0.5 0
+		0.48907381875731 0.5 0.1039557588888
+		0.48907381875731 -0.5 0.1039557588888
+		0.45677280077542 0.5 0.20336815992623
+		0.45677280077542 -0.5 0.20336815992623
+		0.40450865316151 0.5 0.29389241146627
+		0.40450865316151 -0.5 0.29389241146627
+		0.33456556611288 0.5 0.37157217599218
+		0.33456556611288 -0.5 0.37157217599218
+		0.2500003830126 0.5 0.43301248075957
+		0.2500003830126 -0.5 0.43301248075957
+		0.15450900193016 0.5 0.47552809414644
+		0.15450900193016 -0.5 0.47552809414644
+		0.052264847412855 0.5 0.49726088296277
+		0.052264847412855 -0.5 0.49726088296277
+		-0.052263527886268 0.5 0.49726102165048
+		-0.052263527886268 -0.5 0.49726102165048
+		-0.15450774007312 0.5 0.47552850414828
+		-0.15450774007312 -0.5 0.47552850414828
+		-0.24999923397422 0.5 0.43301314415651
+		-0.24999923397422 -0.5 0.43301314415651
+		-0.33456458011157 0.5 0.37157306379065
+		-0.33456458011157 -0.5 0.37157306379065
+		-0.40450787329018 0.5 0.29389348486527
+		-0.40450787329018 -0.5 0.29389348486527
+		-0.45677226111814 0.5 0.20336937201315
+		-0.45677226111814 -0.5 0.20336937201315
+		-0.48907354289964 0.5 0.10395705668972
+		-0.48907354289964 -0.5 0.10395705668972
+		-0.49999999999824 0.5 1.3267948966764e-006
+		-0.49999999999824 -0.5 1.3267948966764e-006
+		-0.48907409461153 0.5 -0.10395446108714
+		-0.48907409461153 -0.5 -0.10395446108714
+		-0.45677334042948 0.5 -0.20336694783787
+		-0.45677334042948 -0.5 -0.20336694783787
+		-0.40450943302999 0.5 -0.2938913380652
+		-0.40450943302999 -0.5 -0.2938913380652
+		-0.33456655211184 0.5 -0.3715712881911
+		-0.33456655211184 -0.5 -0.3715712881911
+		-0.25000153204922 0.5 -0.43301181735958
+		-0.25000153204922 -0.5 -0.43301181735958
+		-0.15451026378611 0.5 -0.47552768414126
+		-0.15451026378611 -0.5 -0.47552768414126
+		-0.052266166939075 0.5 -0.49726074427155
+		-0.052266166939075 -0.5 -0.49726074427155
+		0.052262208359312 0.5 -0.4972611603347
+		0.052262208359312 -0.5 -0.4972611603347
+		0.15450647821499 0.5 -0.47552891414676
+		0.15450647821499 -0.5 -0.47552891414676
+		0.24999808493408 0.5 -0.4330138075504
+		0.24999808493408 -0.5 -0.4330138075504
+		0.3345635941079 0.5 -0.37157395158649
+		0.3345635941079 -0.5 -0.37157395158649
+		0.40450709341601 0.5 -0.2938945582622
+		0.40450709341601 -0.5 -0.2938945582622
+		0.45677172145764 0.5 -0.20337058409865
+		0.45677172145764 -0.5 -0.20337058409865
+		0.48907326703854 0.5 -0.10395835448992
+		0.48907326703854 -0.5 -0.10395835448992
+		0 -0.5 0
+		0.5 0.5 0
+		0.5 -0.5 0
+		0.48907381875731 0.5 0.1039557588888
+		0.48907381875731 -0.5 0.1039557588888
+		0.45677280077542 0.5 0.20336815992623
+		0.45677280077542 -0.5 0.20336815992623
+		0.40450865316151 0.5 0.29389241146627
+		0.40450865316151 -0.5 0.29389241146627
+		0.33456556611288 0.5 0.37157217599218
+		0.33456556611288 -0.5 0.37157217599218
+		0.2500003830126 0.5 0.43301248075957
+		0.2500003830126 -0.5 0.43301248075957
+		0.15450900193016 0.5 0.47552809414644
+		0.15450900193016 -0.5 0.47552809414644
+		0.052264847412855 0.5 0.49726088296277
+		0.052264847412855 -0.5 0.49726088296277
+		-0.052263527886268 0.5 0.49726102165048
+		-0.052263527886268 -0.5 0.49726102165048
+		-0.15450774007312 0.5 0.47552850414828
+		-0.15450774007312 -0.5 0.47552850414828
+		-0.24999923397422 0.5 0.43301314415651
+		-0.24999923397422 -0.5 0.43301314415651
+		-0.33456458011157 0.5 0.37157306379065
+		-0.33456458011157 -0.5 0.37157306379065
+		-0.40450787329018 0.5 0.29389348486527
+		-0.40450787329018 -0.5 0.29389348486527
+		-0.45677226111814 0.5 0.20336937201315
+		-0.45677226111814 -0.5 0.20336937201315
+		-0.48907354289964 0.5 0.10395705668972
+		-0.48907354289964 -0.5 0.10395705668972
+		-0.49999999999824 0.5 1.3267948966764e-006
+		-0.49999999999824 -0.5 1.3267948966764e-006
+		-0.48907409461153 0.5 -0.10395446108714
+		-0.48907409461153 -0.5 -0.10395446108714
+		-0.45677334042948 0.5 -0.20336694783787
+		-0.45677334042948 -0.5 -0.20336694783787
+		-0.40450943302999 0.5 -0.2938913380652
+		-0.40450943302999 -0.5 -0.2938913380652
+		-0.33456655211184 0.5 -0.3715712881911
+		-0.33456655211184 -0.5 -0.3715712881911
+		-0.25000153204922 0.5 -0.43301181735958
+		-0.25000153204922 -0.5 -0.43301181735958
+		-0.15451026378611 0.5 -0.47552768414126
+		-0.15451026378611 -0.5 -0.47552768414126
+		-0.052266166939075 0.5 -0.49726074427155
+		-0.052266166939075 -0.5 -0.49726074427155
+		0.052262208359312 0.5 -0.4972611603347
+		0.052262208359312 -0.5 -0.4972611603347
+		0.15450647821499 0.5 -0.47552891414676
+		0.15450647821499 -0.5 -0.47552891414676
+		0.24999808493408 0.5 -0.4330138075504
+		0.24999808493408 -0.5 -0.4330138075504
+		0.3345635941079 0.5 -0.37157395158649
+		0.3345635941079 -0.5 -0.37157395158649
+		0.40450709341601 0.5 -0.2938945582622
+		0.40450709341601 -0.5 -0.2938945582622
+		0.45677172145764 0.5 -0.20337058409865
+		0.45677172145764 -0.5 -0.20337058409865
+		0.48907326703854 0.5 -0.10395835448992
+		0.48907326703854 -0.5 -0.10395835448992
+	</attribute>
+	<attribute index="1" type="float" size="4" > 
+		1 1 1 1
+		0.9 0.5 0.5 1
+		0.9 0.5 0.5 1
+		0.82 0.42 0.42 1
+		0.82 0.42 0.42 1
+		0.74 0.34 0.34 1
+		0.74 0.34 0.34 1
+		0.66 0.26 0.26 1
+		0.66 0.26 0.26 1
+		0.58 0.18 0.18 1
+		0.58 0.18 0.18 1
+		0.5 0.1 0.1 1
+		0.5 0.1 0.1 1
+		0.58 0.18 0.18 1
+		0.58 0.18 0.18 1
+		0.66 0.26 0.26 1
+		0.66 0.26 0.26 1
+		0.74 0.34 0.34 1
+		0.74 0.34 0.34 1
+		0.82 0.42 0.42 1
+		0.82 0.42 0.42 1
+		0.9 0.5 0.5 1
+		0.9 0.5 0.5 1
+		0.82 0.42 0.42 1
+		0.82 0.42 0.42 1
+		0.74 0.34 0.34 1
+		0.74 0.34 0.34 1
+		0.66 0.26 0.26 1
+		0.66 0.26 0.26 1
+		0.58 0.18 0.18 1
+		0.58 0.18 0.18 1
+		0.5 0.1 0.1 1
+		0.5 0.1 0.1 1
+		0.58 0.18 0.18 1
+		0.58 0.18 0.18 1
+		0.66 0.26 0.26 1
+		0.66 0.26 0.26 1
+		0.74 0.34 0.34 1
+		0.74 0.34 0.34 1
+		0.82 0.42 0.42 1
+		0.82 0.42 0.42 1
+		0.9 0.5 0.5 1
+		0.9 0.5 0.5 1
+		0.82 0.42 0.42 1
+		0.82 0.42 0.42 1
+		0.74 0.34 0.34 1
+		0.74 0.34 0.34 1
+		0.66 0.26 0.26 1
+		0.66 0.26 0.26 1
+		0.58 0.18 0.18 1
+		0.58 0.18 0.18 1
+		0.5 0.1 0.1 1
+		0.5 0.1 0.1 1
+		0.58 0.18 0.18 1
+		0.58 0.18 0.18 1
+		0.66 0.26 0.26 1
+		0.66 0.26 0.26 1
+		0.74 0.34 0.34 1
+		0.74 0.34 0.34 1
+		0.82 0.42 0.42 1
+		0.82 0.42 0.42 1
+		1 1 1 1
+		0.9 0.5 0.5 1
+		0.9 0.5 0.5 1
+		0.82 0.42 0.42 1
+		0.82 0.42 0.42 1
+		0.74 0.34 0.34 1
+		0.74 0.34 0.34 1
+		0.66 0.26 0.26 1
+		0.66 0.26 0.26 1
+		0.58 0.18 0.18 1
+		0.58 0.18 0.18 1
+		0.5 0.1 0.1 1
+		0.5 0.1 0.1 1
+		0.58 0.18 0.18 1
+		0.58 0.18 0.18 1
+		0.66 0.26 0.26 1
+		0.66 0.26 0.26 1
+		0.74 0.34 0.34 1
+		0.74 0.34 0.34 1
+		0.82 0.42 0.42 1
+		0.82 0.42 0.42 1
+		0.9 0.5 0.5 1
+		0.9 0.5 0.5 1
+		0.82 0.42 0.42 1
+		0.82 0.42 0.42 1
+		0.74 0.34 0.34 1
+		0.74 0.34 0.34 1
+		0.66 0.26 0.26 1
+		0.66 0.26 0.26 1
+		0.58 0.18 0.18 1
+		0.58 0.18 0.18 1
+		0.5 0.1 0.1 1
+		0.5 0.1 0.1 1
+		0.58 0.18 0.18 1
+		0.58 0.18 0.18 1
+		0.66 0.26 0.26 1
+		0.66 0.26 0.26 1
+		0.74 0.34 0.34 1
+		0.74 0.34 0.34 1
+		0.82 0.42 0.42 1
+		0.82 0.42 0.42 1
+		0.9 0.5 0.5 1
+		0.9 0.5 0.5 1
+		0.82 0.42 0.42 1
+		0.82 0.42 0.42 1
+		0.74 0.34 0.34 1
+		0.74 0.34 0.34 1
+		0.66 0.26 0.26 1
+		0.66 0.26 0.26 1
+		0.58 0.18 0.18 1
+		0.58 0.18 0.18 1
+		0.5 0.1 0.1 1
+		0.5 0.1 0.1 1
+		0.58 0.18 0.18 1
+		0.58 0.18 0.18 1
+		0.66 0.26 0.26 1
+		0.66 0.26 0.26 1
+		0.74 0.34 0.34 1
+		0.74 0.34 0.34 1
+		0.82 0.42 0.42 1
+		0.82 0.42 0.42 1
+	</attribute>
+	<attribute index="2" type="float" size="3" > 
+		0 1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 1 0
+		0 -1 0
+		0 -1 0
+		1 0 0
+		1 0 0
+		0.97814763751461 0 0.20791151777759
+		0.97814763751461 0 0.20791151777759
+		0.91354560155084 0 0.40673631985245
+		0.91354560155084 0 0.40673631985245
+		0.80901730632302 0 0.58778482293254
+		0.80901730632302 0 0.58778482293254
+		0.66913113222576 0 0.74314435198437
+		0.66913113222576 0 0.74314435198437
+		0.5000007660252 0 0.86602496151913
+		0.5000007660252 0 0.86602496151913
+		0.30901800386032 0 0.95105618829288
+		0.30901800386032 0 0.95105618829288
+		0.10452969482571 0 0.99452176592553
+		0.10452969482571 0 0.99452176592553
+		-0.10452705577254 0 0.99452204330096
+		-0.10452705577254 0 0.99452204330096
+		-0.30901548014624 0 0.95105700829655
+		-0.30901548014624 0 0.95105700829655
+		-0.49999846794844 0 0.86602628831301
+		-0.49999846794844 0 0.86602628831301
+		-0.66912916022314 0 0.7431461275813
+		-0.66912916022314 0 0.7431461275813
+		-0.80901574658037 0 0.58778696973054
+		-0.80901574658037 0 0.58778696973054
+		-0.91354452223627 0 0.40673874402631
+		-0.91354452223627 0 0.40673874402631
+		-0.97814708579929 0 0.20791411337945
+		-0.97814708579929 0 0.20791411337945
+		-0.99999999999648 0 2.6535897933527e-006
+		-0.99999999999648 0 2.6535897933527e-006
+		-0.97814818922305 0 -0.20790892217427
+		-0.97814818922305 0 -0.20790892217427
+		-0.91354668085897 0 -0.40673389567574
+		-0.91354668085897 0 -0.40673389567574
+		-0.80901886605998 0 -0.58778267613041
+		-0.80901886605998 0 -0.58778267613041
+		-0.66913310422368 0 -0.74314257638221
+		-0.66913310422368 0 -0.74314257638221
+		-0.50000306409843 0 -0.86602363471916
+		-0.50000306409843 0 -0.86602363471916
+		-0.30902052757222 0 -0.95105536828251
+		-0.30902052757222 0 -0.95105536828251
+		-0.10453233387815 0 -0.9945214885431
+		-0.10453233387815 0 -0.9945214885431
+		0.10452441671862 0 -0.99452232066939
+		0.10452441671862 0 -0.99452232066939
+		0.30901295642998 0 -0.95105782829353
+		0.30901295642998 0 -0.95105782829353
+		0.49999616986816 0 -0.8660276151008
+		0.49999616986816 0 -0.8660276151008
+		0.66912718821581 0 -0.74314790317299
+		0.66912718821581 0 -0.74314790317299
+		0.80901418683202 0 -0.5877891165244
+		0.80901418683202 0 -0.5877891165244
+		0.91354344291528 0 -0.40674116819729
+		0.91354344291528 0 -0.40674116819729
+		0.97814653407707 0 -0.20791670897984
+		0.97814653407707 0 -0.20791670897984
+	</attribute>
+	<vao name="lit-color" >
+		<source attrib="0" />
+		<source attrib="1" />
+		<source attrib="2" />
+	</vao>
+	<vao name="lit" >
+		<source attrib="0" />
+		<source attrib="2" />
+	</vao>
+	<vao name="color" >
+		<source attrib="0" />
+		<source attrib="1" />
+	</vao>
+	<indices cmd="tri-fan" type="ushort" >0 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 1</indices>
+	<indices cmd="tri-fan" type="ushort" >61 60 58 56 54 52 50 48 46 44 42 40 38 36 34 32 30 28 26 24 22 20 18 16 14 12 10 8 6 4 2 60</indices>
+	<indices cmd="tri-strip" type="ushort" >62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 62 63</indices>
+</mesh>

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter9/example1/example9.1.VertexLighting_PCN.vert

+#version 330
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec4 diffuseColor;
+layout(location = 2) in vec3 normal;
+
+smooth out vec4 interpColor;
+
+uniform vec3 dirToLight;
+uniform vec4 lightIntensity;
+
+uniform mat4 modelToCameraMatrix;
+uniform mat3 normalModelToCameraMatrix;
+
+layout(std140) uniform Projection
+{
+	mat4 cameraToClipMatrix;
+};
+
+void main()
+{
+	gl_Position = cameraToClipMatrix * (modelToCameraMatrix * vec4(position, 1));
+	
+	vec3 normCamSpace = normalize(normalModelToCameraMatrix * normal);
+	
+	float cosAngIncidence = dot(normCamSpace, dirToLight);
+	cosAngIncidence = clamp(cosAngIncidence, 0, 1);
+	
+	interpColor = lightIntensity * diffuseColor * cosAngIncidence;
+}

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter9/example1/example9.1.VertexLighting_PN.vert

+#version 330
+
+layout(location = 0) in vec3 position;
+layout(location = 2) in vec3 normal;
+
+smooth out vec4 interpColor;
+
+uniform vec3 dirToLight;
+uniform vec4 lightIntensity;
+
+uniform mat4 modelToCameraMatrix;
+uniform mat3 normalModelToCameraMatrix;
+
+layout(std140) uniform Projection
+{
+	mat4 cameraToClipMatrix;
+};
+
+void main()
+{
+	gl_Position = cameraToClipMatrix * (modelToCameraMatrix * vec4(position, 1));
+	
+	vec3 normCamSpace = normalize(normalModelToCameraMatrix * normal);
+	
+	float cosAngIncidence = dot(normCamSpace, dirToLight);
+	cosAngIncidence = clamp(cosAngIncidence, 0, 1);
+	
+	interpColor = lightIntensity * cosAngIncidence;
+}

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter9/example1/example9.1.frag

+#version 330
+
+smooth in vec4 interpColor;
+
+out vec4 outputColor;
+
+void main()
+{
+	outputColor = interpColor;
+}

src/main/java/com/ra4king/opengl/util/MousePoles.java

+package com.ra4king.opengl.util;
+
+import com.ra4king.opengl.util.math.Matrix4;
+import com.ra4king.opengl.util.math.Quaternion;
+import com.ra4king.opengl.util.math.Vector3;
+
+public class MousePoles {
+	private static abstract class ViewProvider {
+		public abstract Matrix4 calcMatrix();
+	}
+	
+	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 enum MouseModifier {
+		KEY_SHIFT,
+		KEY_CTRL,
+		KEY_ALT
+	}
+	
+	public static class ObjectData {
+		private Vector3 position;
+		private Quaternion orientation;
+		
+		public ObjectData() {
+			position = new Vector3();
+			orientation = new Quaternion();
+		}
+		
+		public ObjectData(Vector3 v, Quaternion q) {
+			position = v;
+			orientation = q;
+		}
+	}
+	
+	public static class ObjectPole {
+		private enum Axis {
+			AXIS_X,
+			AXIS_Y,
+			AXIS_Z;
+		}
+		
+		private enum RotateMode {
+			RDUAL_AXIS,
+			RBIAXIAL,
+			RSPIN;
+		}
+		
+		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 int prevMousePosX, prevMousePosY;
+		private int startDragMousePosX, startDragMousePosY;
+		private Quaternion startDragOrient;
+		
+		public ObjectPole(ObjectData initialData, float rotateScale, MouseButton actionButton, ViewProvider lookAtProvider) {
+			this.view = lookAtProvider;
+			this.po = initialData;
+			this.initialPo = initialData;
+			this.rotateScale = rotateScale;
+			this.actionButton = actionButton;
+		}
+		
+		public Matrix4 calcMatrix() {
+			Matrix4 translateMat = new Matrix4().clearToIdentity();
+			translateMat.translate(po.position);
+			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 = initialPo;
+		}
+		
+		private Quaternion calcRotationQuat(Axis axis, float angle) {
+			return calcRotationQuat(axis.ordinal(), angle);
+		}
+		
+		private Quaternion calcRotationQuat(int axis, float angle) {
+			return new Quaternion(axisVectors[axis].x(), axisVectors[axis].y(), axisVectors[axis].z(), angle);
+		}
+		
+		public void rotateWorldDegrees(Quaternion rot, boolean fromInitial) {
+			if(!isDragging)
+				fromInitial = false;
+			
+			po.orientation = new Quaternion(rot).mult(fromInitial ? startDragOrient : po.orientation).normalize();
+		}
+		
+		public void rotateLocalDegrees(Quaternion rot, boolean fromInitial) {
+			if(!isDragging)
+				fromInitial = false;
+			
+			po.orientation = new Quaternion(fromInitial ? startDragOrient : po.orientation).mult(rot).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 = new Quaternion(viewQuat).conjugate();
+				po.orientation = inViewQuat.mult(rot).mult(viewQuat).mult(fromInitial ? startDragOrient : po.orientation).normalize();
+			}
+		}
+		
+		public void mouseMove(int positionX, int positionY) {
+			if(isDragging) {
+				int diffX = positionX - prevMousePosX;
+				int diffY = positionY - prevMousePosY;
+				
+				switch(rotateMode) {
+					case RDUAL_AXIS:
+						{
+							Quaternion rot = calcRotationQuat(Axis.AXIS_Y, diffX * rotateScale);
+							rot = calcRotationQuat(Axis.AXIS_X, diffY * rotateScale).mult(rot).normalize();
+							rotateViewDegrees(rot, false);
+						}
+						break;
+					case RBIAXIAL:
+						{
+							int initDiffX = positionX - startDragMousePosX;
+							int initDiffY = positionY - startDragMousePosY;
+							
+							Axis axis;
+							float degAngle;
+							if(Math.abs(initDiffX) > Math.abs(initDiffY)) {
+								axis = Axis.AXIS_Y;
+								degAngle = initDiffX * rotateScale;
+							}
+							else {
+								axis = Axis.AXIS_X;
+								degAngle = initDiffY * rotateScale;
+							}
+							
+							rotateViewDegrees(calcRotationQuat(axis, degAngle), true);
+						}
+						break;
+					case RSPIN:
+						rotateViewDegrees(calcRotationQuat(Axis.AXIS_Z, -diffX * rotateScale), false);
+						break;
+				}
+				
+				prevMousePosX = positionX;
+				prevMousePosY = positionY;
+			}
+		}
+		
+		public void mouseClick(MouseButton button, boolean isPressed, MouseModifier modifiers, int positionX, int positionY) {
+			if(isPressed) {
+				if(!isDragging) {
+					if(button == actionButton) {
+						if(modifiers == MouseModifier.KEY_ALT)
+							rotateMode = RotateMode.RSPIN;
+						else if(modifiers == MouseModifier.KEY_CTRL)
+							rotateMode = RotateMode.RBIAXIAL;
+						else
+							rotateMode = RotateMode.RDUAL_AXIS;
+						
+						prevMousePosX = positionX;
+						prevMousePosY = positionY;
+						
+						startDragMousePosX = positionX;
+						startDragMousePosY = positionY;
+						
+						startDragOrient = po.orientation;
+						
+						isDragging = true;
+					}
+				}
+			}
+			else {
+				if(isDragging) {
+					if(button == actionButton) {
+						mouseMove(positionX, positionY);
+						
+						isDragging = false;
+					}
+				}
+			}
+		}
+	}
+	
+	public static class ViewData {
+		private Vector3 targetPos;
+		private Quaternion orient;
+		private float radius;
+		private float degSpinRotation;
+		
+		public ViewData() {
+			targetPos = new Vector3();
+			orient = new Quaternion();
+		}
+		
+		public ViewData(Vector3 v, Quaternion q, float r, float d) {
+			targetPos = v;
+			orient = q;
+			radius = r;
+			degSpinRotation = d;
+		}
+	}
+	
+	public static class ViewScale {
+		private float minRadius;
+		private float maxRadius;
+		private float largeRadiusDelta;
+		private float smallRadiusDelta;
+		private float largePosOffset;
+		private float smallPosOffset;
+		private float rotationScale;
+		
+		public ViewScale() {}
+		
+		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 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 int startDragMouseLocX, startDragMouseLocY;
+		private Quaternion startDragOrient;
+		
+		public ViewPole(ViewData initialView, ViewScale viewScale, MouseButton actionButton, boolean rightKeyboardCtrls) {
+			this.currView = initialView;
+			this.viewScale = viewScale;
+			this.initialView = initialView;
+			this.actionButton = actionButton;
+			this.rightKeyboardCtrls = rightKeyboardCtrls;
+		}
+		
+		public Matrix4 calcMatrix() {
+			Matrix4 mat = new Matrix4().clearToIdentity();
+			
+			mat.translate(0, 0, -currView.radius);
+			
+			Quaternion fullRotation = new Quaternion(0, 0, 1, currView.degSpinRotation).mult(currView.orient);
+			mat.mult(fullRotation.toMatrix());
+			
+			mat.translate(new Vector3(currView.targetPos).mult(-1));
+			
+			return mat;
+		}
+		
+		public void reset() {
+			if(!isDragging)
+				currView = 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 = new Quaternion(startDragOrient).mult(new Quaternion(0, 1, 0, degAngleDiff));
+		}
+		
+		public void processYChange(int diffY) {
+			float degAngleDiff = diffY * viewScale.rotationScale;
+			
+			currView.orient = new Quaternion(1, 0, 0, degAngleDiff).mult(startDragOrient);
+		}
+		
+		public void processXYChange(int diffX, int diffY ) {
+			float degXAngleDiff = diffX * viewScale.rotationScale;
+			float degYAngleDiff = diffY * viewScale.rotationScale;
+			
+			currView.orient = new Quaternion(startDragOrient).mult(new Quaternion(0, 1, 0, degXAngleDiff));
+			currView.orient = new Quaternion(1, 0, 0, degYAngleDiff).mult(currView.orient);
+		}
+		
+		public void processSpinAxis(int diffX, int diffY) {
+			float degSpinDiff = diffX * viewScale.rotationScale;
+			currView.degSpinRotation = degSpinDiff + degStarDragSpin;
+		}
+		
+		public void beginDragRotate(int startX, int startY, RotateMode rotMode) {
+			rotateMode = rotMode;
+			
+			startDragMouseLocX = startX;
+			startDragMouseLocY = startY;
+			
+			degStarDragSpin = currView.degSpinRotation;
+			
+			startDragOrient = currView.orient;
+			
+			isDragging = true;
+		}
+		
+		public void onDragRotate(int currX, int currY) {
+			int diffX = currX - startDragMouseLocX;
+			int diffY = currY - startDragMouseLocY;
+			
+			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, diffY);
+					break;
+			}
+		}
+		
+		public void endDragRotate(int endX, int endY, boolean keepResults) {
+			if(keepResults)
+				onDragRotate(endX, endY);
+			else
+				currView.orient  = startDragOrient;
+			
+			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;
+		}
+		
+		public void mouseMove(int positionX, int positionY) {
+			if(isDragging)
+				onDragRotate(positionX, positionY);
+		}
+		
+		public void mouseClick(MouseButton button, boolean isPressed, MouseModifier modifiers, int positionX, int positionY) {
+			if(isPressed) {
+				if(!isDragging) {
+					if(button == actionButton) {
+						if(modifiers == MouseModifier.KEY_CTRL)
+							beginDragRotate(positionX, positionY, RotateMode.BIAXIAL_ROTATE);
+						else if(modifiers == MouseModifier.KEY_ALT)
+							beginDragRotate(positionX, positionY, RotateMode.SPIN_VIEW_AXIS);
+						else
+							beginDragRotate(positionX, positionY, 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(positionX, positionY, false);
+					}
+				}
+			}
+		}
+		
+		public void mouseWheel(int direction, MouseModifier modifiers, int positionX, int positionY) {
+			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()]);
+		}
+		
+		private void offsetTargetPos(Vector3 cameraOffset) {
+			currView.targetPos.add(calcMatrix().toQuaternion().conjugate().mult(cameraOffset));
+		}
+		
+		public void charPress(char key) {
+			if(rightKeyboardCtrls) {
+				switch(key) {
+					case 'i': offsetTargetPos(TargetOffsetDir.DIR_FORWARD, viewScale.largePosOffset); break;
+					case 'k': offsetTargetPos(TargetOffsetDir.DIR_BACKWARD, viewScale.largePosOffset); break;
+					case 'l': offsetTargetPos(TargetOffsetDir.DIR_RIGHT, viewScale.largePosOffset); break;
+					case 'j': offsetTargetPos(TargetOffsetDir.DIR_LEFT, viewScale.largePosOffset); break;
+					case 'o': offsetTargetPos(TargetOffsetDir.DIR_UP, viewScale.largePosOffset); break;
+					case 'u': offsetTargetPos(TargetOffsetDir.DIR_DOWN, viewScale.largePosOffset); break;
+
+					case 'I': offsetTargetPos(TargetOffsetDir.DIR_FORWARD, viewScale.smallPosOffset); break;
+					case 'K': offsetTargetPos(TargetOffsetDir.DIR_BACKWARD, viewScale.smallPosOffset); break;
+					case 'L': offsetTargetPos(TargetOffsetDir.DIR_RIGHT, viewScale.smallPosOffset); break;
+					case 'J': offsetTargetPos(TargetOffsetDir.DIR_LEFT, viewScale.smallPosOffset); break;
+					case 'O': offsetTargetPos(TargetOffsetDir.DIR_UP, viewScale.smallPosOffset); break;
+					case 'U': offsetTargetPos(TargetOffsetDir.DIR_DOWN, viewScale.smallPosOffset); break;
+				}
+			}
+			else {
+				switch(key) {
+					case 'w': offsetTargetPos(TargetOffsetDir.DIR_FORWARD, viewScale.largePosOffset); break;
+					case 's': offsetTargetPos(TargetOffsetDir.DIR_BACKWARD, viewScale.largePosOffset); break;
+					case 'd': offsetTargetPos(TargetOffsetDir.DIR_RIGHT, viewScale.largePosOffset); break;
+					case 'a': offsetTargetPos(TargetOffsetDir.DIR_LEFT, viewScale.largePosOffset); break;
+					case 'e': offsetTargetPos(TargetOffsetDir.DIR_UP, viewScale.largePosOffset); break;
+					case 'q': offsetTargetPos(TargetOffsetDir.DIR_DOWN, viewScale.largePosOffset); break;
+
+					case 'W': offsetTargetPos(TargetOffsetDir.DIR_FORWARD, viewScale.smallPosOffset); break;
+					case 'S': offsetTargetPos(TargetOffsetDir.DIR_BACKWARD, viewScale.smallPosOffset); break;
+					case 'D': offsetTargetPos(TargetOffsetDir.DIR_RIGHT, viewScale.smallPosOffset); break;
+					case 'A': offsetTargetPos(TargetOffsetDir.DIR_LEFT, viewScale.smallPosOffset); break;
+					case 'E': offsetTargetPos(TargetOffsetDir.DIR_UP, viewScale.smallPosOffset); break;
+					case 'Q': offsetTargetPos(TargetOffsetDir.DIR_DOWN, viewScale.smallPosOffset); break;
+				}
+			}
+		}
+	}
+}

src/main/java/com/ra4king/opengl/util/Timer.java

 			case LOOP:
 				return (secAccumTime%secDuration) / secDuration;
 			case SINGLE:
-				return clamp(secAccumTime/secDuration, 0, 1);
+				return Utils.clamp(secAccumTime/secDuration, 0, 1);
 			case INFINITE:
 			default:
 				return -1;
 			case LOOP:
 				return secAccumTime%secDuration;
 			case SINGLE:
-				return clamp(secAccumTime, 0, secDuration);
+				return Utils.clamp(secAccumTime, 0, secDuration);
 			case INFINITE:
 			default:
 				return -1;
 	public float getTimeSinceStart() {
 		return secAccumTime;
 	}
-	
-	private float clamp(float value, float low, float high) {
-		return Math.min(Math.max(value, low), high);
-	}
 }

src/main/java/com/ra4king/opengl/util/Utils.java

+package com.ra4king.opengl.util;
+
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+
+import com.ra4king.opengl.util.MousePoles.MouseButton;
+import com.ra4king.opengl.util.MousePoles.MouseModifier;
+import com.ra4king.opengl.util.MousePoles.ObjectPole;
+import com.ra4king.opengl.util.MousePoles.ViewPole;
+
+public class Utils {
+	public static float clamp(float value, float low, float high) {
+		return Math.min(Math.max(value, low), high);
+	}
+	
+	public static void updateMousePoles(ViewPole viewPole, ObjectPole objectPole) {
+		while(Mouse.next()) {
+			viewPole.mouseMove(Mouse.getX(), Mouse.getY());
+			objectPole.mouseMove(Mouse.getX(), Mouse.getY());
+			
+			MouseButton button = MouseButton.getButton(Mouse.getEventButton());
+			if(button != null) {
+				viewPole.mouseClick(button, Mouse.getEventButtonState(), Utils.getModifier(), Mouse.getX(), Mouse.getY());
+				objectPole.mouseClick(button, Mouse.getEventButtonState(), Utils.getModifier(), Mouse.getX(), Mouse.getY());
+			}
+			
+			viewPole.mouseWheel(Mouse.getEventDWheel(), Utils.getModifier(), Mouse.getX(), Mouse.getY());
+		}
+	}
+	
+	public 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) || Keyboard.isKeyDown(Keyboard.KEY_RMENU))
+			return MouseModifier.KEY_ALT;
+		
+		return null;
+	}
+}

src/main/java/com/ra4king/opengl/util/math/Matrix3.java

+package com.ra4king.opengl.util.math;
+
+import java.nio.FloatBuffer;
+import java.util.Arrays;
+
+import org.lwjgl.BufferUtils;
+
+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 put(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[] 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;
+	}
+	
+	private final static FloatBuffer direct = BufferUtils.createFloatBuffer(9);
+	
+	public FloatBuffer toBuffer() {
+		direct.clear();
+		direct.put(matrix);
+		direct.flip();
+		return direct;
+	}
+}

src/main/java/com/ra4king/opengl/util/math/Matrix4.java

 package com.ra4king.opengl.util.math;
 
 import java.nio.FloatBuffer;
+import java.util.Arrays;
 
 import org.lwjgl.BufferUtils;
 
 	}
 	
 	public Matrix4 clear() {
-		for(int a = 0; a < matrix.length; a++)
-			put(a,0);
+		Arrays.fill(matrix, 0);
 		return this;
 	}
 	
 		return this;
 	}
 	
+	public Matrix4 put(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 put(int index, Vector3 v) {
 		put(index*4+0,v.x());
 		put(index*4+1,v.y());
 	}
 	
 	public Matrix4 put(float[] m) {
-		if(m.length < 16)
-			throw new IllegalArgumentException("float array must have at least 16 values.");
+		if(m.length < matrix.length)
+			throw new IllegalArgumentException("float array must have at least " + matrix.length + " values.");
 		
-		for(int a = 0; a < matrix.length; a++)
-			put(a,m[a]);
+		System.arraycopy(m, 0, matrix, 0, matrix.length);
 		
 		return this;
 	}
 	}
 	
 	public Matrix4 mult(float[] m) {
-		float[] newm = new float[16];
+		float[] newm = new float[matrix.length];
 		
-		for(int a = 0; a < 16; a += 4) {
+		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];
 		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.x(get(1)*vec.x() + get(5)*vec.y() + get(9)*vec.z() + get(13)*vec.w());
+		v.x(get(2)*vec.x() + get(6)*vec.y() + get(10)*vec.z() + get(14)*vec.w());
+		v.x(get(3)*vec.x() + get(7)*vec.y() + get(12)*vec.z() + get(15)*vec.w());
+		
+		return v;
+	}
+	
 	public Matrix4 transpose() {
 		float old = get(1);
 		put(1,get(4));
 	}
 	
 	public Matrix4 translate(float x, float y, float z) {
-		float[] m = new float[16];
+		float[] m = new float[matrix.length];
 		
 		m[0] = 1;
 		m[5] = 1;
 	}
 	
 	public Matrix4 scale(float x, float y, float z) {
-		float[] m = new float[16];
+		float[] m = new float[matrix.length];
 		
 		m[0] = x;
 		m[5] = y;
 		
 		Vector3 v = new Vector3(x,y,z).normalize();
 		
-		float[] m = new float[16];
+		float[] m = new float[matrix.length];
 		m[0] = v.x()*v.x() + (1 - v.x()*v.x())*cos;
 		m[4] = v.x()*v.y()*invCos - v.z()*sin;
 		m[8] = v.x()*v.z()*invCos + v.y()*sin;

src/main/java/com/ra4king/opengl/util/math/Quaternion.java

 		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;

src/main/java/com/ra4king/opengl/util/math/Vector3.java

 package com.ra4king.opengl.util.math;
 
+import java.nio.FloatBuffer;
+
+import org.lwjgl.BufferUtils;
+
 public class Vector3 {
 	private float x, y, z;
 	
 	public Vector3() {
-		this(0,0,0);
+		set(0,0,0);
 	}