Browse Source

Merge pull request #1 from Tankernn/animation

Animation
Frans 8 years ago
parent
commit
17cb4c54fb
76 changed files with 2837 additions and 784 deletions
  1. 5 1
      formats.md
  2. 5 5
      src/main/java/eu/tankernn/gameEngine/MainLoop.java
  3. 8 19
      src/main/java/eu/tankernn/gameEngine/TankernnGame3D.java
  4. 0 21
      src/main/java/eu/tankernn/gameEngine/animation/Animation.java
  5. 0 19
      src/main/java/eu/tankernn/gameEngine/animation/AnimationSection.java
  6. 0 94
      src/main/java/eu/tankernn/gameEngine/animation/Bone.java
  7. 0 47
      src/main/java/eu/tankernn/gameEngine/animation/KeyFrame.java
  8. 149 0
      src/main/java/eu/tankernn/gameEngine/animation/animatedModel/AnimatedModel.java
  9. 164 0
      src/main/java/eu/tankernn/gameEngine/animation/animatedModel/Joint.java
  10. 48 0
      src/main/java/eu/tankernn/gameEngine/animation/animation/Animation.java
  11. 237 0
      src/main/java/eu/tankernn/gameEngine/animation/animation/Animator.java
  12. 117 0
      src/main/java/eu/tankernn/gameEngine/animation/animation/JointTransform.java
  13. 56 0
      src/main/java/eu/tankernn/gameEngine/animation/animation/KeyFrame.java
  14. 212 0
      src/main/java/eu/tankernn/gameEngine/animation/animation/Quaternion.java
  15. 88 0
      src/main/java/eu/tankernn/gameEngine/animation/loaders/AnimationLoader.java
  16. 10 15
      src/main/java/eu/tankernn/gameEngine/entities/Camera.java
  17. 2 1
      src/main/java/eu/tankernn/gameEngine/entities/Car.java
  18. 9 6
      src/main/java/eu/tankernn/gameEngine/entities/Entity3D.java
  19. 7 1
      src/main/java/eu/tankernn/gameEngine/entities/Player.java
  20. 156 179
      src/main/java/eu/tankernn/gameEngine/loader/Loader.java
  21. 21 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/AnimatedModelData.java
  22. 13 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/AnimationData.java
  23. 101 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/AnimationLoader.java
  24. 33 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/ColladaLoader.java
  25. 212 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/GeometryLoader.java
  26. 27 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/JointData.java
  27. 14 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/JointTransformData.java
  28. 13 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/JointsData.java
  29. 68 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/JointsLoader.java
  30. 19 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/KeyFrameData.java
  31. 31 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/MeshData.java
  32. 77 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/SkinLoader.java
  33. 16 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/SkinningData.java
  34. 65 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/VertexSkinData.java
  35. 28 0
      src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/WeightedVertex.java
  36. 5 3
      src/main/java/eu/tankernn/gameEngine/loader/font/GUIText.java
  37. 6 10
      src/main/java/eu/tankernn/gameEngine/loader/models/TexturedModel.java
  38. 17 18
      src/main/java/eu/tankernn/gameEngine/loader/obj/Vertex.java
  39. 6 0
      src/main/java/eu/tankernn/gameEngine/loader/textures/ModelTexture.java
  40. 168 0
      src/main/java/eu/tankernn/gameEngine/loader/xmlLoader/XmlNode.java
  41. 102 0
      src/main/java/eu/tankernn/gameEngine/loader/xmlLoader/XmlParser.java
  42. 9 9
      src/main/java/eu/tankernn/gameEngine/particles/ParticleRenderer.java
  43. 22 18
      src/main/java/eu/tankernn/gameEngine/postProcessing/PostProcessor.java
  44. 1 1
      src/main/java/eu/tankernn/gameEngine/postProcessing/bloom/BrightFilter.java
  45. 1 1
      src/main/java/eu/tankernn/gameEngine/postProcessing/gaussianBlur/HorizontalBlur.java
  46. 1 1
      src/main/java/eu/tankernn/gameEngine/postProcessing/gaussianBlur/VerticalBlur.java
  47. 1 4
      src/main/java/eu/tankernn/gameEngine/renderEngine/MasterRenderer.java
  48. 0 150
      src/main/java/eu/tankernn/gameEngine/renderEngine/RawModel.java
  49. 117 0
      src/main/java/eu/tankernn/gameEngine/renderEngine/Vao.java
  50. 38 6
      src/main/java/eu/tankernn/gameEngine/renderEngine/Vbo.java
  51. 39 29
      src/main/java/eu/tankernn/gameEngine/renderEngine/entities/EntityRenderer.java
  52. 8 3
      src/main/java/eu/tankernn/gameEngine/renderEngine/entities/EntityShader.java
  53. 30 3
      src/main/java/eu/tankernn/gameEngine/renderEngine/entities/vertexShader.glsl
  54. 2 8
      src/main/java/eu/tankernn/gameEngine/renderEngine/font/FontRenderer.java
  55. 2 1
      src/main/java/eu/tankernn/gameEngine/renderEngine/font/TextMaster.java
  56. 2 2
      src/main/java/eu/tankernn/gameEngine/renderEngine/gui/GuiRenderer.java
  57. 32 0
      src/main/java/eu/tankernn/gameEngine/renderEngine/shaders/UniformMat4Array.java
  58. 37 34
      src/main/java/eu/tankernn/gameEngine/renderEngine/shadows/ShadowMapEntityRenderer.java
  59. 1 1
      src/main/java/eu/tankernn/gameEngine/renderEngine/shadows/ShadowMapMasterRenderer.java
  60. 7 2
      src/main/java/eu/tankernn/gameEngine/renderEngine/shadows/ShadowShader.java
  61. 1 2
      src/main/java/eu/tankernn/gameEngine/renderEngine/shadows/shadowFragmentShader.glsl
  62. 23 1
      src/main/java/eu/tankernn/gameEngine/renderEngine/shadows/shadowVertexShader.glsl
  63. 0 23
      src/main/java/eu/tankernn/gameEngine/renderEngine/skybox/CubeGenerator.java
  64. 6 5
      src/main/java/eu/tankernn/gameEngine/renderEngine/skybox/Skybox.java
  65. 2 2
      src/main/java/eu/tankernn/gameEngine/renderEngine/skybox/SkyboxRenderer.java
  66. 17 1
      src/main/java/eu/tankernn/gameEngine/renderEngine/water/WaterMaster.java
  67. 2 2
      src/main/java/eu/tankernn/gameEngine/renderEngine/water/WaterRenderer.java
  68. 10 1
      src/main/java/eu/tankernn/gameEngine/renderEngine/water/WaterTile.java
  69. 7 2
      src/main/java/eu/tankernn/gameEngine/settings/Settings.java
  70. 3 3
      src/main/java/eu/tankernn/gameEngine/terrains/Terrain.java
  71. 2 2
      src/main/java/eu/tankernn/gameEngine/terrains/TerrainModelData.java
  72. 2 7
      src/main/java/eu/tankernn/gameEngine/util/DistanceSorter.java
  73. 5 0
      src/main/java/eu/tankernn/gameEngine/util/InternalFile.java
  74. 0 14
      src/main/java/eu/tankernn/gameEngine/util/Maths.java
  75. 4 7
      src/main/java/eu/tankernn/gameEngine/util/MousePicker.java
  76. 88 0
      src/main/java/eu/tankernn/gameEngine/util/OpenGlUtils.java

+ 5 - 1
formats.md

@@ -13,4 +13,8 @@ Available options:
 - shinedamper (float)
 - reflectivity (float)
 - refractivity (float)
-- transparency (boolean)
+- transparency (boolean)
+
+## *.anim
+
+name(string, no spaces) length(int)

+ 5 - 5
src/main/java/eu/tankernn/gameEngine/MainLoop.java

@@ -82,9 +82,9 @@ public class MainLoop {
 		TerrainPack terrainPack = new TerrainPack(loader, texturePack, blendMap, SEED);
 
 		// Player
-		Entity3D entity = new Entity3D(0, new Vector3f(0, 0, 20), new Vector3f(0, 0, 0), 1, loader.getBoundingBox(0));
+		Entity3D entity = new Entity3D(loader.getModel(0), new Vector3f(0, 0, 20), new Vector3f(0, 0, 0), 1, loader.getBoundingBox(0));
 		entities.add(entity);
-		Player player = new Player(0, new Vector3f(10, 0, 50), new Vector3f(0, 0, 0), 1, loader.getBoundingBox(0),
+		Player player = new Player(loader.getModel(0), new Vector3f(10, 0, 50), new Vector3f(0, 0, 0), 1, loader.getBoundingBox(0),
 				terrainPack);
 		entities.add(player);
 		Camera camera = new PlayerCamera(player, terrainPack);
@@ -92,7 +92,7 @@ public class MainLoop {
 		InternalFile[] dayTextures = InternalFile.fromFilenames("skybox", TEXTURE_FILES, "png"),
 				nightTextures = InternalFile.fromFilenames("skybox", NIGHT_TEXTURE_FILES, "png");
 
-		Skybox skybox = new Skybox(Texture.newCubeMap(dayTextures, 200), Texture.newCubeMap(nightTextures, 200), 200);
+		Skybox skybox = new Skybox(loader, Texture.newCubeMap(dayTextures, 200), Texture.newCubeMap(nightTextures, 200), 200);
 
 		MasterRenderer renderer = new MasterRenderer(loader, camera, skybox);
 		ParticleMaster particleMaster = new ParticleMaster(loader, camera.getProjectionMatrix());
@@ -105,7 +105,7 @@ public class MainLoop {
 		textMaster.loadText(text);
 
 		// Barrel
-		Entity3D barrel = new Entity3D(1, new Vector3f(75, 10, 75), new Vector3f(0, 0, 0), 1f, loader.getBoundingBox(1));
+		Entity3D barrel = new Entity3D(loader.getModel(1), new Vector3f(75, 10, 75), new Vector3f(0, 0, 0), 1f, loader.getBoundingBox(1));
 		entities.add(barrel);
 
 		Light sun = new Light(new Vector3f(100000, 150000, -70000), new Vector3f(1f, 1f, 1f));
@@ -122,7 +122,7 @@ public class MainLoop {
 			float x = rand.nextFloat() * 1000;
 			float z = rand.nextFloat() * 1000;
 
-			entities.add(new Entity3D(2, new Vector3f(x, terrainPack.getTerrainHeightByWorldPos(x, z), z), new Vector3f(),
+			entities.add(new Entity3D(loader.getModel(2), new Vector3f(x, terrainPack.getTerrainHeightByWorldPos(x, z), z), new Vector3f(),
 					1, loader.getBoundingBox(2)));
 		}
 

+ 8 - 19
src/main/java/eu/tankernn/gameEngine/TankernnGame3D.java

@@ -1,6 +1,5 @@
 package eu.tankernn.gameEngine;
 
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -14,7 +13,7 @@ import eu.tankernn.gameEngine.entities.Entity3D;
 import eu.tankernn.gameEngine.entities.Light;
 import eu.tankernn.gameEngine.entities.Player;
 import eu.tankernn.gameEngine.environmentMap.EnvironmentMapRenderer;
-import eu.tankernn.gameEngine.loader.Loader;
+import eu.tankernn.gameEngine.loader.textures.Texture;
 import eu.tankernn.gameEngine.particles.ParticleMaster;
 import eu.tankernn.gameEngine.postProcessing.PostProcessor;
 import eu.tankernn.gameEngine.renderEngine.Fbo;
@@ -37,8 +36,8 @@ public class TankernnGame3D extends TankernnGame {
 	protected Skybox sky;
 	protected MousePicker picker;
 
-	protected List<Entity3D> entities;
-	protected List<Light> lights;
+	protected List<Entity3D> entities = new ArrayList<>();
+	protected List<Light> lights= new ArrayList<>();
 	private Light sun;
 	protected TerrainPack terrainPack;
 	protected Player player;
@@ -48,33 +47,23 @@ public class TankernnGame3D extends TankernnGame {
 	private Fbo outputFbo = new Fbo(Display.getWidth(), Display.getHeight(), Fbo.DEPTH_TEXTURE),
 			outputFbo2 = new Fbo(Display.getWidth(), Display.getHeight(), Fbo.DEPTH_TEXTURE);
 	
-	public TankernnGame3D(String name, Skybox skybox, String dudvMap, String normalMap, Light sun) {
+	public TankernnGame3D(String name, String[] dayTextures, String[] nightTextures, Light sun) {
 		super(name);
-		entities = new ArrayList<Entity3D>();
-		lights = new ArrayList<Light>();
-		this.sky = skybox;
 		this.sun = sun;
 		lights.add(sun);
 		try {
-			loader = new Loader();
 			loader.readModelSpecification(new InternalFile("models.json"));
 		} catch (IOException e) {
 			e.printStackTrace();
 		}
-		camera = new Camera();
-		renderer = new MasterRenderer(loader, camera, skybox);
-		try {
-			waterMaster = new WaterMaster(loader, loader.loadTexture(dudvMap), loader.loadTexture(normalMap), camera);
-		} catch (FileNotFoundException e) {
-			e.printStackTrace();
-		}
-		particleMaster = new ParticleMaster(loader, camera.getProjectionMatrix());
-		postProcessor = new PostProcessor(loader);
-		picker = new MousePicker(camera, camera.getProjectionMatrix(), entities, guiMaster.getGuis());
+		this.sky = new Skybox(loader, Texture.newCubeMap(InternalFile.fromFilenames("skybox", dayTextures, "png"), 400),
+				Texture.newCubeMap(InternalFile.fromFilenames("skybox", nightTextures, "png"), 400), 400);
 	}
 
 	public void update() {
 		super.update();
+		for (Entity3D e : entities)
+			e.update();
 		player.move();
 		picker.update(terrainPack);
 		camera.update();

+ 0 - 21
src/main/java/eu/tankernn/gameEngine/animation/Animation.java

@@ -1,21 +0,0 @@
-package eu.tankernn.gameEngine.animation;
-
-import java.util.List;
-
-public class Animation {
-	private int animationID;
-	private int length;
-	private String name;
-	private List<AnimationSection> sections;
-	
-	public int getAnimationID() {
-		return animationID;
-	}
-	public int getLength() {
-		return length;
-	}
-	public String getName() {
-		return name;
-	}
-	
-}

+ 0 - 19
src/main/java/eu/tankernn/gameEngine/animation/AnimationSection.java

@@ -1,19 +0,0 @@
-package eu.tankernn.gameEngine.animation;
-
-import java.util.List;
-
-import org.lwjgl.util.vector.Matrix4f;
-
-public class AnimationSection {
-	private int bodyPart;
-	private List<KeyFrame> keyFrames;
-	
-	public Matrix4f getRotationMatrix(float time) {
-		for (KeyFrame keyFrame : keyFrames) {
-			if (keyFrame.getTime() == time) {
-				return keyFrame.getRotationMatrix();
-			}
-		}
-		return new Matrix4f();
-	}
-}

+ 0 - 94
src/main/java/eu/tankernn/gameEngine/animation/Bone.java

@@ -1,94 +0,0 @@
-package eu.tankernn.gameEngine.animation;
-
-import java.io.BufferedReader;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.lwjgl.util.vector.Vector3f;
-import org.lwjgl.util.vector.Vector4f;
-
-public class Bone {
-	private String name;
-	private List<Bone> children;
-	private Bone parent;
-	private float length;
-	private Vector3f position;
-	private Vector4f rotation;
-	
-	public Bone(String name, float length, Vector3f position, Vector4f rotation) {
-		this.name = name;
-		this.children = new ArrayList<Bone>();
-		this.length = length;
-		this.position = position;
-		this.rotation = rotation;
-	}
-	
-	public Bone(String[] args) {
-		this.children = new ArrayList<Bone>();
-		this.position = new Vector3f(Float.parseFloat(args[1]), Float.parseFloat(args[2]), Float.parseFloat(args[3]));
-		this.rotation = new Vector4f(Float.parseFloat(args[4]), Float.parseFloat(args[5]), Float.parseFloat(args[6]), Float.parseFloat(args[7]));
-		this.length = Float.parseFloat(args[8]);
-		this.name = args[9];
-	}
-	
-	public static Bone fromFile(String filename) throws IOException {
-		BufferedReader reader;
-		try {
-			reader = new BufferedReader(new FileReader(filename));
-		} catch (FileNotFoundException e) {
-			e.printStackTrace();
-			return null;
-		}
-		
-		Bone root = new Bone(reader.readLine().split(" "));
-		
-		while (reader.ready()) {
-			String[] line = reader.readLine().split(" ");
-			String depthBuffer = line[0];
-			int depth = depthBuffer.length() - 1;
-			if (depth < 0) {
-				System.err.println("Wrong bone depth.");
-			}
-			
-			
-		}
-		reader.close();
-		return root;
-	}
-	
-	public void addChild(Bone child) {
-		child.setParent(this);
-		this.children.add(child);
-	}
-	
-	public Bone getParent() {
-		return parent;
-	}
-	
-	protected void setParent(Bone parent) {
-		this.parent = parent;
-	}
-	
-	public String getName() {
-		return name;
-	}
-	
-	public List<Bone> getChildren() {
-		return children;
-	}
-	
-	public float getLength() {
-		return length;
-	}
-	
-	public Vector3f getPosition() {
-		return position;
-	}
-	
-	public Vector4f getRotation() {
-		return rotation;
-	}
-}

+ 0 - 47
src/main/java/eu/tankernn/gameEngine/animation/KeyFrame.java

@@ -1,47 +0,0 @@
-package eu.tankernn.gameEngine.animation;
-
-import org.lwjgl.util.vector.Matrix4f;
-import org.lwjgl.util.vector.Vector4f;
-
-public class KeyFrame {
-	private float time;
-	private Vector4f rotation;
-	
-	public KeyFrame(float time, Vector4f rotation) {
-		this.time = time;
-		this.rotation = rotation;
-	}
-	
-	public float getTime() {
-		return time;
-	}
-	
-	public Vector4f getRotation() {
-		return rotation;
-	}
-	
-	public Matrix4f getRotationMatrix() {
-		Matrix4f rotationMatrix = new Matrix4f();
-		rotationMatrix.m00 = (float) (1 - 2 * Math.pow(rotation.y, 2) - 2 * Math.pow(rotation.z, 2));
-		rotationMatrix.m01 = 2 * rotation.x * rotation.y + 2 * rotation.w * rotation.z;
-		rotationMatrix.m02 = 2 * rotation.x * rotation.z - 2 * rotation.w * rotation.y;
-		rotationMatrix.m03 = 0;
-		
-		rotationMatrix.m10 = 2 * rotation.x * rotation.y - 2 * rotation.w * rotation.z;
-		rotationMatrix.m11 = (float) (1 - 2 * Math.pow(rotation.x, 2) - 2 * Math.pow(rotation.z, 2));
-		rotationMatrix.m12 = 2 * rotation.y * rotation.z - 2 * rotation.w * rotation.x;
-		rotationMatrix.m13 = 0;
-		
-		rotationMatrix.m20 = 2 * rotation.x * rotation.z + 2 * rotation.w * rotation.y;
-		rotationMatrix.m21 = 2 * rotation.y * rotation.z + 2 * rotation.w * rotation.x;
-		rotationMatrix.m22 = (float) (1 - 2 * Math.pow(rotation.x, 2) - 2 * Math.pow(rotation.y, 2));
-		rotationMatrix.m23 = 0;
-		
-		rotationMatrix.m30 = 0;
-		rotationMatrix.m31 = 0;
-		rotationMatrix.m32 = 0;
-		rotationMatrix.m33 = 1;
-		return rotationMatrix;
-	}
-	
-}

+ 149 - 0
src/main/java/eu/tankernn/gameEngine/animation/animatedModel/AnimatedModel.java

@@ -0,0 +1,149 @@
+package eu.tankernn.gameEngine.animation.animatedModel;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.lwjgl.util.vector.Matrix4f;
+
+import eu.tankernn.gameEngine.animation.animation.Animation;
+import eu.tankernn.gameEngine.animation.animation.Animator;
+import eu.tankernn.gameEngine.loader.models.TexturedModel;
+import eu.tankernn.gameEngine.loader.textures.ModelTexture;
+import eu.tankernn.gameEngine.renderEngine.Vao;
+
+/**
+ * 
+ * This class represents an entity in the world that can be animated. It
+ * contains the model's VAO which contains the mesh data, the texture, and the
+ * root joint of the joint hierarchy, or "skeleton". It also holds an int which
+ * represents the number of joints that the model's skeleton contains, and has
+ * its own {@link Animator} instance which can be used to apply animations to
+ * this entity.
+ * 
+ * @author Karl
+ *
+ */
+public class AnimatedModel extends TexturedModel {
+
+	// skeleton
+	private final Joint rootJoint;
+	private final int jointCount;
+
+	private final Animator animator;
+	private Map<String, Animation> animations = new HashMap<>();
+
+	/**
+	 * Creates a new entity capable of animation. The inverse bind transform for
+	 * all joints is calculated in this constructor. The bind transform is
+	 * simply the original (no pose applied) transform of a joint in relation to
+	 * the model's origin (model-space). The inverse bind transform is simply
+	 * that but inverted.
+	 * 
+	 * @param model
+	 *            - the VAO containing the mesh data for this entity. This
+	 *            includes vertex positions, normals, texture coords, IDs of
+	 *            joints that affect each vertex, and their corresponding
+	 *            weights.
+	 * @param texture
+	 *            - the diffuse texture for the entity.
+	 * @param rootJoint
+	 *            - the root joint of the joint hierarchy which makes up the
+	 *            "skeleton" of the entity.
+	 * @param jointCount
+	 *            - the number of joints in the joint hierarchy (skeleton) for
+	 *            this entity.
+	 * 
+	 */
+	public AnimatedModel(Vao model, ModelTexture texture, Joint rootJoint, int jointCount) {
+		super(model, texture);
+		this.rootJoint = rootJoint;
+		this.jointCount = jointCount;
+		this.animator = new Animator(this);
+		rootJoint.calcInverseBindTransform(new Matrix4f());
+	}
+
+	public AnimatedModel(AnimatedModel model) {
+		this(model.getModel(), model.getTexture(), new Joint(model.rootJoint), model.jointCount);
+		this.animations = new HashMap<>(model.animations);
+	}
+
+	/**
+	 * @return The root joint of the joint hierarchy. This joint has no parent,
+	 *         and every other joint in the skeleton is a descendant of this
+	 *         joint.
+	 */
+	public Joint getRootJoint() {
+		return rootJoint;
+	}
+
+	/**
+	 * Deletes the OpenGL objects associated with this entity, namely the model
+	 * (VAO) and texture.
+	 */
+	public void delete() {
+		getModel().delete();
+		getTexture().delete();
+	}
+
+	/**
+	 * Instructs this entity to carry out a given animation. To do this it
+	 * basically sets the chosen animation as the current animation in the
+	 * {@link Animator} object.
+	 * 
+	 * @param animation
+	 *            - the animation to be carried out.
+	 */
+	public void doAnimation(Animation animation) {
+		animator.doAnimation(animation);
+	}
+	
+	public void doAnimation(String animationId) {
+		doAnimation(animations.get(animationId));
+	}
+	
+	public void registerAnimations(Map<String, Animation> animations) {
+		this.animations = new HashMap<>(animations);
+	}
+
+	/**
+	 * Updates the animator for this entity, basically updating the animated
+	 * pose of the entity. Must be called every frame.
+	 */
+	public void update() {
+		animator.update();
+	}
+
+	/**
+	 * Gets an array of the all important model-space transforms of all the
+	 * joints (with the current animation pose applied) in the entity. The
+	 * joints are ordered in the array based on their joint index. The position
+	 * of each joint's transform in the array is equal to the joint's index.
+	 * 
+	 * @return The array of model-space transforms of the joints in the current
+	 *         animation pose.
+	 */
+	public Matrix4f[] getJointTransforms() {
+		Matrix4f[] jointMatrices = new Matrix4f[jointCount];
+		addJointsToArray(rootJoint, jointMatrices);
+		return jointMatrices;
+	}
+
+	/**
+	 * This adds the current model-space transform of a joint (and all of its
+	 * descendants) into an array of transforms. The joint's transform is added
+	 * into the array at the position equal to the joint's index.
+	 * 
+	 * @param headJoint
+	 *            - the current joint being added to the array. This method also
+	 *            adds the transforms of all the descendents of this joint too.
+	 * @param jointMatrices
+	 *            - the array of joint transforms that is being filled.
+	 */
+	private void addJointsToArray(Joint headJoint, Matrix4f[] jointMatrices) {
+		jointMatrices[headJoint.index] = headJoint.getAnimatedTransform();
+		for (Joint childJoint : headJoint.children) {
+			addJointsToArray(childJoint, jointMatrices);
+		}
+	}
+
+}

+ 164 - 0
src/main/java/eu/tankernn/gameEngine/animation/animatedModel/Joint.java

@@ -0,0 +1,164 @@
+package eu.tankernn.gameEngine.animation.animatedModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.lwjgl.util.vector.Matrix4f;
+
+import eu.tankernn.gameEngine.animation.animation.Animator;
+import eu.tankernn.gameEngine.loader.colladaLoader.JointData;
+
+/**
+ * 
+ * Represents a joint in a "skeleton". It contains the index of the joint which
+ * determines where in the vertex shader uniform array the joint matrix for this
+ * joint is loaded up to. It also contains the name of the bone, and a list of
+ * all the child joints.
+ * 
+ * The "animatedTransform" matrix is the joint transform that I keep referring
+ * to in the tutorial. This is the transform that gets loaded up to the vertex
+ * shader and is used to transform vertices. It is a model-space transform that
+ * transforms the joint from it's bind (original position, no animation applied)
+ * position to it's current position in the current pose. Changing this
+ * transform changes the position/rotation of the joint in the animated entity.
+ * 
+ * The two other matrices are transforms that are required to calculate the
+ * "animatedTransform" in the {@link Animator} class. It also has the local bind
+ * transform which is the original (no pose/animation applied) transform of the
+ * joint relative to the parent joint (in bone-space).
+ * 
+ * The "localBindTransform" is the original (bind) transform of the joint
+ * relative to its parent (in bone-space). The inverseBindTransform is that bind
+ * transform in model-space, but inversed.
+ * 
+ * @author Karl
+ *
+ */
+public class Joint {
+
+	public final int index;// ID
+	public final String name;
+	public final List<Joint> children = new ArrayList<Joint>();
+
+	private Matrix4f animatedTransform = new Matrix4f();
+	
+	private final Matrix4f localBindTransform;
+	private Matrix4f inverseBindTransform = new Matrix4f();
+
+	/**
+	 * @param index
+	 *            - the joint's index (ID).
+	 * @param name
+	 *            - the name of the joint. This is how the joint is named in the
+	 *            collada file, and so is used to identify which joint a joint
+	 *            transform in an animation keyframe refers to.
+	 * @param bindLocalTransform
+	 *            - the bone-space transform of the joint in the bind position.
+	 */
+	public Joint(int index, String name, Matrix4f bindLocalTransform) {
+		this.index = index;
+		this.name = name;
+		this.localBindTransform = bindLocalTransform;
+	}
+	
+	public Joint(Joint joint) {
+		this(joint.index, joint.name, joint.localBindTransform);
+		for (Joint j : joint.children)
+			this.children.add(new Joint(j));
+	}
+	
+	/**
+	 * Constructs the joint-hierarchy skeleton from the data extracted from the
+	 * collada file.
+	 * 
+	 * @param data
+	 *            - the joints data from the collada file for the head joint.
+	 * @return The created joint, with all its descendants added.
+	 */
+	public Joint(JointData data) {
+		this(data.index, data.nameId, data.bindLocalTransform);
+		for (JointData child : data.children)
+			this.addChild(new Joint(child));
+	}
+
+	/**
+	 * Adds a child joint to this joint. Used during the creation of the joint
+	 * hierarchy. Joints can have multiple children, which is why they are
+	 * stored in a list (e.g. a "hand" joint may have multiple "finger" children
+	 * joints).
+	 * 
+	 * @param child
+	 *            - the new child joint of this joint.
+	 */
+	public void addChild(Joint child) {
+		this.children.add(child);
+	}
+
+	/**
+	 * The animated transform is the transform that gets loaded up to the shader
+	 * and is used to deform the vertices of the "skin". It represents the
+	 * transformation from the joint's bind position (original position in
+	 * model-space) to the joint's desired animation pose (also in model-space).
+	 * This matrix is calculated by taking the desired model-space transform of
+	 * the joint and multiplying it by the inverse of the starting model-space
+	 * transform of the joint.
+	 * 
+	 * @return The transformation matrix of the joint which is used to deform
+	 *         associated vertices of the skin in the shaders.
+	 */
+	public Matrix4f getAnimatedTransform() {
+		return animatedTransform;
+	}
+
+	/**
+	 * This method allows those all important "joint transforms" (as I referred
+	 * to them in the tutorial) to be set by the animator. This is used to put
+	 * the joints of the animated model in a certain pose.
+	 * 
+	 * @param animationTransform - the new joint transform.
+	 */
+	public void setAnimationTransform(Matrix4f animationTransform) {
+		this.animatedTransform = animationTransform;
+	}
+
+	/**
+	 * This returns the inverted model-space bind transform. The bind transform
+	 * is the original model-space transform of the joint (when no animation is
+	 * applied). This returns the inverse of that, which is used to calculate
+	 * the animated transform matrix which gets used to transform vertices in
+	 * the shader.
+	 * 
+	 * @return The inverse of the joint's bind transform (in model-space).
+	 */
+	public Matrix4f getInverseBindTransform() {
+		return inverseBindTransform;
+	}
+
+	/**
+	 * This is called during set-up, after the joints hierarchy has been
+	 * created. This calculates the model-space bind transform of this joint
+	 * like so: </br>
+	 * </br>
+	 * {@code bindTransform = parentBindTransform * localBindTransform}</br>
+	 * </br>
+	 * where "bindTransform" is the model-space bind transform of this joint,
+	 * "parentBindTransform" is the model-space bind transform of the parent
+	 * joint, and "localBindTransform" is the bone-space bind transform of this
+	 * joint. It then calculates and stores the inverse of this model-space bind
+	 * transform, for use when calculating the final animation transform each
+	 * frame. It then recursively calls the method for all of the children
+	 * joints, so that they too calculate and store their inverse bind-pose
+	 * transform.
+	 * 
+	 * @param parentBindTransform
+	 *            - the model-space bind transform of the parent joint.
+	 */
+	protected void calcInverseBindTransform(Matrix4f parentBindTransform) {
+		Matrix4f bindTransform = Matrix4f.mul(parentBindTransform, localBindTransform, null);
+		Matrix4f.invert(bindTransform, inverseBindTransform);
+		for (Joint child : children) {
+			child.calcInverseBindTransform(bindTransform);
+		}
+	}
+
+}

+ 48 - 0
src/main/java/eu/tankernn/gameEngine/animation/animation/Animation.java

@@ -0,0 +1,48 @@
+package eu.tankernn.gameEngine.animation.animation;
+
+import eu.tankernn.gameEngine.animation.animatedModel.AnimatedModel;
+
+/**
+ * 
+ * * Represents an animation that can applied to an {@link AnimatedModel} . It
+ * contains the length of the animation in seconds, and a list of
+ * {@link KeyFrame}s.
+ * 
+ * @author Karl
+ * 
+ *
+ */
+public class Animation {
+
+	private final float length;
+	private final KeyFrame[] keyFrames;
+
+	/**
+	 * @param lengthInSeconds
+	 *            - the total length of the animation in seconds.
+	 * @param frames
+	 *            - all the keyframes for the animation, ordered by time of
+	 *            appearance in the animation.
+	 */
+	public Animation(float lengthInSeconds, KeyFrame[] frames) {
+		this.keyFrames = frames;
+		this.length = lengthInSeconds;
+	}
+
+	/**
+	 * @return The length of the animation in seconds.
+	 */
+	public float getLength() {
+		return length;
+	}
+
+	/**
+	 * @return An array of the animation's keyframes. The array is ordered based
+	 *         on the order of the keyframes in the animation (first keyframe of
+	 *         the animation in array position 0).
+	 */
+	public KeyFrame[] getKeyFrames() {
+		return keyFrames;
+	}
+
+}

+ 237 - 0
src/main/java/eu/tankernn/gameEngine/animation/animation/Animator.java

@@ -0,0 +1,237 @@
+package eu.tankernn.gameEngine.animation.animation;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.lwjgl.util.vector.Matrix4f;
+
+import eu.tankernn.gameEngine.animation.animatedModel.AnimatedModel;
+import eu.tankernn.gameEngine.animation.animatedModel.Joint;
+import eu.tankernn.gameEngine.renderEngine.DisplayManager;
+
+
+/**
+ * 
+ * This class contains all the functionality to apply an animation to an
+ * animated entity. An Animator instance is associated with just one
+ * {@link AnimatedModel}. It also keeps track of the running time (in seconds)
+ * of the current animation, along with a reference to the currently playing
+ * animation for the corresponding entity.
+ * 
+ * An Animator instance needs to be updated every frame, in order for it to keep
+ * updating the animation pose of the associated entity. The currently playing
+ * animation can be changed at any time using the doAnimation() method. The
+ * Animator will keep looping the current animation until a new animation is
+ * chosen.
+ * 
+ * The Animator calculates the desired current animation pose by interpolating
+ * between the previous and next keyframes of the animation (based on the
+ * current animation time). The Animator then updates the transforms all of the
+ * joints each frame to match the current desired animation pose.
+ * 
+ * @author Karl
+ *
+ */
+public class Animator {
+
+	private final AnimatedModel entity;
+
+	private float animationTime = 0;
+	private Animation currentAnimation;
+
+	private float speed;
+
+	/**
+	 * @param entity
+	 *            - the entity which will by animated by this animator.
+	 */
+	public Animator(AnimatedModel entity) {
+		this.entity = entity;
+	}
+
+	/**
+	 * Indicates that the entity should carry out the given animation. Resets
+	 * the animation time so that the new animation starts from the beginning.
+	 * 
+	 * @param animation
+	 *            - the new animation to carry out.
+	 */
+	public void doAnimation(Animation animation, float speed) {
+		if (this.currentAnimation != null && this.currentAnimation.equals(animation))
+			return;
+		this.speed = speed;
+		this.animationTime = 0;
+		this.currentAnimation = animation;
+	}
+	
+	public void doAnimation(Animation animation) {
+		this.doAnimation(animation, 1);
+	}
+
+	/**
+	 * This method should be called each frame to update the animation currently
+	 * being played. This increases the animation time (and loops it back to
+	 * zero if necessary), finds the pose that the entity should be in at that
+	 * time of the animation, and then applies that pose to all the model's
+	 * joints by setting the joint transforms.
+	 */
+	public void update() {
+		if (currentAnimation == null) {
+			return;
+		}
+		increaseAnimationTime();
+		Map<String, Matrix4f> currentPose = calculateCurrentAnimationPose();
+		applyPoseToJoints(currentPose, entity.getRootJoint(), new Matrix4f());
+
+	}
+
+	/**
+	 * Increases the current animation time which allows the animation to
+	 * progress. If the current animation has reached the end then the timer is
+	 * reset, causing the animation to loop.
+	 */
+	private void increaseAnimationTime() {
+		animationTime += DisplayManager.getFrameTimeSeconds() * speed;
+		if (animationTime > currentAnimation.getLength()) {
+			this.animationTime %= currentAnimation.getLength();
+		}
+	}
+
+	/**
+	 * This method returns the current animation pose of the entity. It returns
+	 * the desired local-space transforms for all the joints in a map, indexed
+	 * by the name of the joint that they correspond to.
+	 * 
+	 * The pose is calculated based on the previous and next keyframes in the
+	 * current animation. Each keyframe provides the desired pose at a certain
+	 * time in the animation, so the animated pose for the current time can be
+	 * calculated by interpolating between the previous and next keyframe.
+	 * 
+	 * This method first finds the preious and next keyframe, calculates how far
+	 * between the two the current animation is, and then calculated the pose
+	 * for the current animation time by interpolating between the transforms at
+	 * those keyframes.
+	 * 
+	 * @return The current pose as a map of the desired local-space transforms
+	 *         for all the joints. The transforms are indexed by the name ID of
+	 *         the joint that they should be applied to.
+	 */
+	private Map<String, Matrix4f> calculateCurrentAnimationPose() {
+		KeyFrame[] frames = getPreviousAndNextFrames();
+		float progression = calculateProgression(frames[0], frames[1]);
+		return interpolatePoses(frames[0], frames[1], progression);
+	}
+
+	/**
+	 * This is the method where the animator calculates and sets those all-
+	 * important "joint transforms" that I talked about so much in the tutorial.
+	 * 
+	 * This method applies the current pose to a given joint, and all of its
+	 * descendants. It does this by getting the desired local-transform for the
+	 * current joint, before applying it to the joint. Before applying the
+	 * transformations it needs to be converted from local-space to model-space
+	 * (so that they are relative to the model's origin, rather than relative to
+	 * the parent joint). This can be done by multiplying the local-transform of
+	 * the joint with the model-space transform of the parent joint.
+	 * 
+	 * The same thing is then done to all the child joints.
+	 * 
+	 * Finally the inverse of the joint's bind transform is multiplied with the
+	 * model-space transform of the joint. This basically "subtracts" the
+	 * joint's original bind (no animation applied) transform from the desired
+	 * pose transform. The result of this is then the transform required to move
+	 * the joint from its original model-space transform to it's desired
+	 * model-space posed transform. This is the transform that needs to be
+	 * loaded up to the vertex shader and used to transform the vertices into
+	 * the current pose.
+	 * 
+	 * @param currentPose
+	 *            - a map of the local-space transforms for all the joints for
+	 *            the desired pose. The map is indexed by the name of the joint
+	 *            which the transform corresponds to.
+	 * @param joint
+	 *            - the current joint which the pose should be applied to.
+	 * @param parentTransform
+	 *            - the desired model-space transform of the parent joint for
+	 *            the pose.
+	 */
+	private void applyPoseToJoints(Map<String, Matrix4f> currentPose, Joint joint, Matrix4f parentTransform) {
+		Matrix4f currentLocalTransform = currentPose.get(joint.name);
+		Matrix4f currentTransform = Matrix4f.mul(parentTransform, currentLocalTransform, null);
+		for (Joint childJoint : joint.children) {
+			applyPoseToJoints(currentPose, childJoint, currentTransform);
+		}
+		Matrix4f.mul(currentTransform, joint.getInverseBindTransform(), currentTransform);
+		joint.setAnimationTransform(currentTransform);
+	}
+
+	/**
+	 * Finds the previous keyframe in the animation and the next keyframe in the
+	 * animation, and returns them in an array of length 2. If there is no
+	 * previous frame (perhaps current animation time is 0.5 and the first
+	 * keyframe is at time 1.5) then the next keyframe is used as both the
+	 * previous and next keyframe. The reverse happens if there is no next
+	 * keyframe.
+	 * 
+	 * @return The previous and next keyframes, in an array which therefore will
+	 *         always have a length of 2.
+	 */
+	private KeyFrame[] getPreviousAndNextFrames() {
+		KeyFrame previousFrame = null;
+		KeyFrame nextFrame = null;
+		for (KeyFrame frame : currentAnimation.getKeyFrames()) {
+			if (frame.getTimeStamp() > animationTime) {
+				nextFrame = frame;
+				break;
+			}
+			previousFrame = frame;
+		}
+		previousFrame = previousFrame == null ? nextFrame : previousFrame;
+		nextFrame = nextFrame == null ? previousFrame : nextFrame;
+		return new KeyFrame[] { previousFrame, nextFrame };
+	}
+
+	/**
+	 * Calculates how far between the previous and next keyframe the current
+	 * animation time is, and returns it as a value between 0 and 1.
+	 * 
+	 * @param previousFrame
+	 *            - the previous keyframe in the animation.
+	 * @param nextFrame
+	 *            - the next keyframe in the animation.
+	 * @return A number between 0 and 1 indicating how far between the two
+	 *         keyframes the current animation time is.
+	 */
+	private float calculateProgression(KeyFrame previousFrame, KeyFrame nextFrame) {
+		float timeDifference = nextFrame.getTimeStamp() - previousFrame.getTimeStamp();
+		return (animationTime - previousFrame.getTimeStamp()) / timeDifference;
+	}
+
+	/**
+	 * Calculates all the local-space joint transforms for the desired current
+	 * pose by interpolating between the transforms at the previous and next
+	 * keyframes.
+	 * 
+	 * @param previousFrame
+	 *            - the previous keyframe in the animation.
+	 * @param nextFrame
+	 *            - the next keyframe in the animation.
+	 * @param progression
+	 *            - a number between 0 and 1 indicating how far between the
+	 *            previous and next keyframes the current animation time is.
+	 * @return The local-space transforms for all the joints for the desired
+	 *         current pose. They are returned in a map, indexed by the name of
+	 *         the joint to which they should be applied.
+	 */
+	private Map<String, Matrix4f> interpolatePoses(KeyFrame previousFrame, KeyFrame nextFrame, float progression) {
+		Map<String, Matrix4f> currentPose = new HashMap<String, Matrix4f>();
+		for (String jointName : previousFrame.getJointKeyFrames().keySet()) {
+			JointTransform previousTransform = previousFrame.getJointKeyFrames().get(jointName);
+			JointTransform nextTransform = nextFrame.getJointKeyFrames().get(jointName);
+			JointTransform currentTransform = JointTransform.interpolate(previousTransform, nextTransform, progression);
+			currentPose.put(jointName, currentTransform.getLocalTransform());
+		}
+		return currentPose;
+	}
+
+}

+ 117 - 0
src/main/java/eu/tankernn/gameEngine/animation/animation/JointTransform.java

@@ -0,0 +1,117 @@
+package eu.tankernn.gameEngine.animation.animation;
+
+import org.lwjgl.util.vector.Matrix4f;
+import org.lwjgl.util.vector.Vector3f;
+
+/**
+ * 
+ * Represents the local bone-space transform of a joint at a certain keyframe
+ * during an animation. This includes the position and rotation of the joint,
+ * relative to the parent joint (for the root joint it's relative to the model's
+ * origin, seeing as the root joint has no parent). The transform is stored as a
+ * position vector and a quaternion (rotation) so that these values can be
+ * easily interpolated, a functionality that this class also provides.
+ * 
+ * @author Karl
+ *
+ */
+
+public class JointTransform {
+
+	// remember, this position and rotation are relative to the parent bone!
+	private final Vector3f position;
+	private final Quaternion rotation;
+
+	/**
+	 * 
+	 * @param position
+	 *            - the position of the joint relative to the parent joint
+	 *            (bone-space) at a certain keyframe. For example, if this joint
+	 *            is at (5, 12, 0) in the model's coordinate system, and the
+	 *            parent of this joint is at (2, 8, 0), then the position of
+	 *            this joint relative to the parent is (3, 4, 0).
+	 * @param rotation
+	 *            - the rotation of the joint relative to the parent joint
+	 *            (bone-space) at a certain keyframe.
+	 */
+	public JointTransform(Vector3f position, Quaternion rotation) {
+		this.position = position;
+		this.rotation = rotation;
+	}
+
+	/**
+	 * @param localTransform
+	 *            - the joint's local-transform at a certain keyframe of an
+	 *            animation.
+	 */
+	public JointTransform(Matrix4f localTransform) {
+		this.position = new Vector3f(localTransform.m30, localTransform.m31, localTransform.m32);
+		this.rotation = new Quaternion(localTransform);
+	}
+
+	/**
+	 * In this method the bone-space transform matrix is constructed by
+	 * translating an identity matrix using the position variable and then
+	 * applying the rotation. The rotation is applied by first converting the
+	 * quaternion into a rotation matrix, which is then multiplied with the
+	 * transform matrix.
+	 * 
+	 * @return This bone-space joint transform as a matrix. The exact same
+	 *         transform as represented by the position and rotation in this
+	 *         instance, just in matrix form.
+	 */
+	protected Matrix4f getLocalTransform() {
+		Matrix4f matrix = new Matrix4f();
+		matrix.translate(position);
+		Matrix4f.mul(matrix, rotation.toRotationMatrix(), matrix);
+		return matrix;
+	}
+
+	/**
+	 * Interpolates between two transforms based on the progression value. The
+	 * result is a new transform which is part way between the two original
+	 * transforms. The translation can simply be linearly interpolated, but the
+	 * rotation interpolation is slightly more complex, using a method called
+	 * "SLERP" to spherically-linearly interpolate between 2 quaternions
+	 * (rotations). This gives a much much better result than trying to linearly
+	 * interpolate between Euler rotations.
+	 * 
+	 * @param frameA
+	 *            - the previous transform
+	 * @param frameB
+	 *            - the next transform
+	 * @param progression
+	 *            - a number between 0 and 1 indicating how far between the two
+	 *            transforms to interpolate. A progression value of 0 would
+	 *            return a transform equal to "frameA", a value of 1 would
+	 *            return a transform equal to "frameB". Everything else gives a
+	 *            transform somewhere in-between the two.
+	 * @return
+	 */
+	protected static JointTransform interpolate(JointTransform frameA, JointTransform frameB, float progression) {
+		Vector3f pos = interpolate(frameA.position, frameB.position, progression);
+		Quaternion rot = Quaternion.nlerp(frameA.rotation, frameB.rotation, progression);
+		return new JointTransform(pos, rot);
+	}
+
+	/**
+	 * Linearly interpolates between two translations based on a "progression"
+	 * value.
+	 * 
+	 * @param start
+	 *            - the start translation.
+	 * @param end
+	 *            - the end translation.
+	 * @param progression
+	 *            - a value between 0 and 1 indicating how far to interpolate
+	 *            between the two translations.
+	 * @return
+	 */
+	private static Vector3f interpolate(Vector3f start, Vector3f end, float progression) {
+		float x = start.x + (end.x - start.x) * progression;
+		float y = start.y + (end.y - start.y) * progression;
+		float z = start.z + (end.z - start.z) * progression;
+		return new Vector3f(x, y, z);
+	}
+
+}

+ 56 - 0
src/main/java/eu/tankernn/gameEngine/animation/animation/KeyFrame.java

@@ -0,0 +1,56 @@
+package eu.tankernn.gameEngine.animation.animation;
+
+import java.util.Map;
+
+/**
+ * 
+ * Represents one keyframe of an animation. This contains the timestamp of the
+ * keyframe, which is the time (in seconds) from the start of the animation when
+ * this keyframe occurs.
+ * 
+ * It also contains the desired bone-space transforms of all of the joints in
+ * the animated entity at this keyframe in the animation (i.e. it contains all
+ * the joint transforms for the "pose" at this time of the animation.). The
+ * joint transforms are stored in a map, indexed by the name of the joint that
+ * they should be applied to.
+ * 
+ * @author Karl
+ *
+ */
+public class KeyFrame {
+
+	private final float timeStamp;
+	private final Map<String, JointTransform> pose;
+
+	/**
+	 * @param timeStamp
+	 *            - the time (in seconds) that this keyframe occurs during the
+	 *            animation.
+	 * @param jointKeyFrames
+	 *            - the local-space transforms for all the joints at this
+	 *            keyframe, indexed by the name of the joint that they should be
+	 *            applied to.
+	 */
+	public KeyFrame(float timeStamp, Map<String, JointTransform> jointKeyFrames) {
+		this.timeStamp = timeStamp;
+		this.pose = jointKeyFrames;
+	}
+
+	/**
+	 * @return The time in seconds of the keyframe in the animation.
+	 */
+	protected float getTimeStamp() {
+		return timeStamp;
+	}
+
+	/**
+	 * @return The desired bone-space transforms of all the joints at this
+	 *         keyframe, of the animation, indexed by the name of the joint that
+	 *         they correspond to. This basically represents the "pose" at this
+	 *         keyframe.
+	 */
+	protected Map<String, JointTransform> getJointKeyFrames() {
+		return pose;
+	}
+
+}

+ 212 - 0
src/main/java/eu/tankernn/gameEngine/animation/animation/Quaternion.java

@@ -0,0 +1,212 @@
+package eu.tankernn.gameEngine.animation.animation;
+
+import org.lwjgl.util.vector.Matrix4f;
+
+/**
+ * A quaternion simply represents a 3D rotation. The maths behind it is quite
+ * complex (literally; it involves complex numbers) so I wont go into it in too
+ * much detail. The important things to note are that it represents a 3d
+ * rotation, it's very easy to interpolate between two quaternion rotations
+ * (which would not be easy to do correctly with Euler rotations or rotation
+ * matrices), and you can convert to and from matrices fairly easily. So when we
+ * need to interpolate between rotations we'll represent them as quaternions,
+ * but when we need to apply the rotations to anything we'll convert back to a
+ * matrix.
+ * 
+ * @author Karl
+ *
+ */
+public class Quaternion {
+
+	private float x, y, z, w;
+
+	/**
+	 * Creates a quaternion and normalizes it.
+	 * 
+	 * @param x
+	 * @param y
+	 * @param z
+	 * @param w
+	 */
+	public Quaternion(float x, float y, float z, float w) {
+		this.x = x;
+		this.y = y;
+		this.z = z;
+		this.w = w;
+		normalize();
+	}
+
+	/**
+	 * Extracts the rotation part of a transformation matrix and converts it to
+	 * a quaternion using the magic of maths.
+	 * 
+	 * @param matrix
+	 *            - the transformation matrix containing the rotation which this
+	 *            quaternion shall represent.
+	 */
+	public Quaternion(Matrix4f matrix) {
+		float diagonal = matrix.m00 + matrix.m11 + matrix.m22;
+		if (diagonal > 0) {
+			float w4 = (float) (Math.sqrt(diagonal + 1f) * 2f);
+			this.w = w4 / 4f;
+			this.x = (matrix.m21 - matrix.m12) / w4;
+			this.y = (matrix.m02 - matrix.m20) / w4;
+			this.z = (matrix.m10 - matrix.m01) / w4;
+		} else if ((matrix.m00 > matrix.m11) && (matrix.m00 > matrix.m22)) {
+			float x4 = (float) (Math.sqrt(1f + matrix.m00 - matrix.m11 - matrix.m22) * 2f);
+			this.w = (matrix.m21 - matrix.m12) / x4;
+			this.x = x4 / 4f;
+			this.y = (matrix.m01 + matrix.m10) / x4;
+			this.z = (matrix.m02 + matrix.m20) / x4;
+		} else if (matrix.m11 > matrix.m22) {
+			float y4 = (float) (Math.sqrt(1f + matrix.m11 - matrix.m00 - matrix.m22) * 2f);
+			this.w = (matrix.m02 - matrix.m20) / y4;
+			this.x = (matrix.m01 + matrix.m10) / y4;
+			this.y = y4 / 4f;
+			this.z = (matrix.m12 + matrix.m21) / y4;
+		} else {
+			float z4 = (float) (Math.sqrt(1f + matrix.m22 - matrix.m00 - matrix.m11) * 2f);
+			this.w = (matrix.m10 - matrix.m01) / z4;
+			this.x = (matrix.m02 + matrix.m20) / z4;
+			this.y = (matrix.m12 + matrix.m21) / z4;
+			this.z = z4 / 4f;
+		}
+		this.normalize();
+	}
+	
+//	public static Quaternion nlerp(Quaternion a, Quaternion b, float blend){
+//		a.normalize();
+//		b.normalize();
+//		float w = a.w + (blend * (b.w - a.w));
+//		float x = a.x + (blend * (b.x - a.x));
+//		float y = a.y + (blend * (b.y - a.y));
+//		float z = a.z + (blend * (b.z - a.z));
+//		Quaternion q = new Quaternion(x, y, z, w);
+//		q.normalize();
+//		return q;
+//	}
+	
+	public static Quaternion nlerp(Quaternion a, Quaternion b, float blend)
+	{
+		Quaternion result = new Quaternion(0, 0, 0, 0);
+	     float dot = a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z;
+	     float blendI = 1.0f - blend;
+	     if(dot < 0.0f)
+	     {
+	          float tmpFw = -b.w;
+	          float tmpFx = -b.x;
+	          float tmpFy = -b.y;
+	          float tmpFz = -b.z;
+	          result.w = blendI*a.w + blend*tmpFw;
+	          result.x = blendI*a.x + blend*tmpFx;
+	          result.y = blendI*a.y + blend*tmpFy;
+	          result.z = blendI*a.z + blend*tmpFz;
+	     }
+	     else
+	     {
+	          result.w = blendI*a.w + blend*b.w;
+	          result.x = blendI*a.x + blend*b.x;
+	          result.y = blendI*a.y + blend*b.y;
+	          result.z = blendI*a.z + blend*b.z;
+	     }
+	     result.normalize();
+	     return result;
+	}
+
+	/**
+	 * Converts the quaternion to a 4x4 matrix representing the exact same
+	 * rotation as this quaternion. (The rotation is only contained in the
+	 * top-left 3x3 part, but a 4x4 matrix is returned here for convenience
+	 * seeing as it will be multiplied with other 4x4 matrices).
+	 * 
+	 * @return The rotation matrix which represents the exact same rotation as
+	 *         this quaternion.
+	 */
+	public Matrix4f toRotationMatrix() {
+		Matrix4f matrix = new Matrix4f();
+		final float xy = x * y;
+		final float xz = x * z;
+		final float xw = x * w;
+		final float yz = y * z;
+		final float yw = y * w;
+		final float zw = z * w;
+		final float xSquared = x * x;
+		final float ySquared = y * y;
+		final float zSquared = z * z;
+		matrix.m00 = 1 - 2 * (ySquared + zSquared);
+		matrix.m01 = 2 * (xy - zw);
+		matrix.m02 = 2 * (xz + yw);
+		matrix.m03 = 0;
+		matrix.m10 = 2 * (xy + zw);
+		matrix.m11 = 1 - 2 * (xSquared + zSquared);
+		matrix.m12 = 2 * (yz - xw);
+		matrix.m13 = 0;
+		matrix.m20 = 2 * (xz - yw);
+		matrix.m21 = 2 * (yz + xw);
+		matrix.m22 = 1 - 2 * (xSquared + ySquared);
+		matrix.m23 = 0;
+		matrix.m30 = 0;
+		matrix.m31 = 0;
+		matrix.m32 = 0;
+		matrix.m33 = 1;
+		return matrix;
+	}
+
+	/**
+	 * Normalizes the quaternion.
+	 */
+	public void normalize() {
+		float mag = (float) Math.sqrt(w * w + x * x + y * y + z * z);
+		w /= mag;
+		x /= mag;
+		y /= mag;
+		z /= mag;
+	}
+
+	/**
+	 * Interpolates between two quaternion rotations and returns a new
+	 * quaternion which represents a rotation somewhere in between the two input
+	 * rotations.
+	 * 
+	 * @param start
+	 *            - the starting rotation.
+	 * @param end
+	 *            - the end rotation.
+	 * @param progression
+	 *            - a value between 0 and 1 indicating how much to interpolate
+	 *            between the two rotations. 0 would retrun the start rotation,
+	 *            and 1 would return the end rotation.
+	 * @return The interpolated rotation as a quaternion.
+	 */
+	public static Quaternion slerp(Quaternion start, Quaternion end, float progression) {
+		start.normalize();
+		end.normalize();
+		final float d = start.x * end.x + start.y * end.y + start.z * end.z + start.w * end.w;
+		float absDot = d < 0f ? -d : d;
+		float scale0 = 1f - progression;
+		float scale1 = progression;
+
+		if ((1 - absDot) > 0.1f) {
+
+			final float angle = (float) Math.acos(absDot);
+			final float invSinTheta = 1f / (float) Math.sin(angle);
+			scale0 = ((float) Math.sin((1f - progression) * angle) * invSinTheta);
+			scale1 = ((float) Math.sin((progression * angle)) * invSinTheta);
+		}
+
+		if (d < 0f) {
+			scale1 = -scale1;
+		}
+		float newX = (scale0 * start.x) + (scale1 * end.x);
+		float newY = (scale0 * start.y) + (scale1 * end.y);
+		float newZ = (scale0 * start.z) + (scale1 * end.z);
+		float newW = (scale0 * start.w) + (scale1 * end.w);
+		return new Quaternion(newX, newY, newZ, newW);
+	}
+
+	@Override
+	public String toString() {
+		return x + ", " + y + ", " + z + ", " + w;
+	}
+
+}

+ 88 - 0
src/main/java/eu/tankernn/gameEngine/animation/loaders/AnimationLoader.java

@@ -0,0 +1,88 @@
+package eu.tankernn.gameEngine.animation.loaders;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.lwjgl.util.vector.Matrix4f;
+import org.lwjgl.util.vector.Vector3f;
+
+import eu.tankernn.gameEngine.animation.animation.Animation;
+import eu.tankernn.gameEngine.animation.animation.JointTransform;
+import eu.tankernn.gameEngine.animation.animation.KeyFrame;
+import eu.tankernn.gameEngine.animation.animation.Quaternion;
+import eu.tankernn.gameEngine.loader.colladaLoader.AnimationData;
+import eu.tankernn.gameEngine.loader.colladaLoader.ColladaLoader;
+import eu.tankernn.gameEngine.loader.colladaLoader.JointTransformData;
+import eu.tankernn.gameEngine.loader.colladaLoader.KeyFrameData;
+import eu.tankernn.gameEngine.util.InternalFile;
+
+/**
+ * This class loads up an animation collada file, gets the information from it,
+ * and then creates and returns an {@link Animation} from the extracted data.
+ * 
+ * @author Karl
+ */
+public class AnimationLoader {
+	
+	/**
+	 * Loads up a collada animation file, and returns and animation created from
+	 * the extracted animation data from the file.
+	 * 
+	 * @param colladaFile - the collada file containing data about the desired
+	 *        animation.
+	 * @return The animation made from the data in the file.
+	 * @throws IOException 
+	 */
+	public static Map<String, Animation> loadAnimations(InternalFile colladaFile, InternalFile animationFile) throws IOException {
+		Map<String, Animation> animations = new HashMap<>();
+		AnimationData animationData = ColladaLoader.loadColladaAnimation(colladaFile);
+		int dataIndex = 0;
+		BufferedReader r = animationFile.getReader();
+		String line;
+		while (r.ready()) {
+			line = r.readLine();
+			String[] split = line.split(" ");
+			KeyFrame[] frames = new KeyFrame[Integer.parseInt(split[1])];
+			float animationStart = animationData.keyFrames[dataIndex].time;
+			for (int i = 0; i < frames.length; i++) {
+				frames[i] = createKeyFrame(animationData.keyFrames[dataIndex++], animationStart);
+			}
+			float animationEnd = animationData.keyFrames[dataIndex -1].time;
+			animations.put(split[0], new Animation(animationEnd - animationStart, frames));
+		}
+		
+		return animations;
+	}
+	
+	/**
+	 * Creates a keyframe from the data extracted from the collada file.
+	 * 
+	 * @param data - the data about the keyframe that was extracted from the
+	 *        collada file.
+	 * @return The keyframe.
+	 */
+	private static KeyFrame createKeyFrame(KeyFrameData data, float animationStart) {
+		Map<String, JointTransform> map = new HashMap<String, JointTransform>();
+		for (JointTransformData jointData: data.jointTransforms) {
+			JointTransform jointTransform = createTransform(jointData);
+			map.put(jointData.jointNameId, jointTransform);
+		}
+		return new KeyFrame(data.time - animationStart, map);
+	}
+	
+	/**
+	 * Creates a joint transform from the data extracted from the collada file.
+	 * 
+	 * @param data - the data from the collada file.
+	 * @return The joint transform.
+	 */
+	private static JointTransform createTransform(JointTransformData data) {
+		Matrix4f mat = data.jointLocalTransform;
+		Vector3f translation = new Vector3f(mat.m30, mat.m31, mat.m32);
+		Quaternion rotation = new Quaternion(mat);
+		return new JointTransform(translation, rotation);
+	}
+	
+}

+ 10 - 15
src/main/java/eu/tankernn/gameEngine/entities/Camera.java

@@ -10,7 +10,6 @@ import org.lwjgl.util.vector.Vector3f;
 
 import eu.tankernn.gameEngine.util.ICamera;
 import eu.tankernn.gameEngine.util.IPositionable;
-import eu.tankernn.gameEngine.util.Maths;
 
 /**
  * Camera for determining the view frustum when rendering.
@@ -20,7 +19,6 @@ import eu.tankernn.gameEngine.util.Maths;
 public class Camera implements IPositionable, ICamera {
 	
 	private Matrix4f projectionMatrix;
-	private Matrix4f viewMatrix = new Matrix4f();
 	
 	protected Vector3f position = new Vector3f(0, 10, 0);
 	protected float pitch = 20;
@@ -54,7 +52,6 @@ public class Camera implements IPositionable, ICamera {
 	}
 	
 	public void update() {
-		updateViewMatrix();
 	}
 	
 	public Vector3f getPosition() {
@@ -87,15 +84,6 @@ public class Camera implements IPositionable, ICamera {
 		this.roll = -roll;
 	}
 	
-	private void updateViewMatrix() {
-		viewMatrix.setIdentity();
-		Matrix4f.rotate((float) Math.toRadians(pitch), new Vector3f(1, 0, 0), viewMatrix,
-				viewMatrix);
-		Matrix4f.rotate((float) Math.toRadians(yaw), new Vector3f(0, 1, 0), viewMatrix, viewMatrix);
-		Vector3f negativeCameraPos = new Vector3f(-position.x,-position.y,-position.z);
-		Matrix4f.translate(negativeCameraPos, viewMatrix, viewMatrix);
-	}
-	
 	private static Matrix4f createProjectionMatrix(){
 		Matrix4f projectionMatrix = new Matrix4f();
 		float aspectRatio = (float) Display.getWidth() / (float) Display.getHeight();
@@ -114,14 +102,21 @@ public class Camera implements IPositionable, ICamera {
 
 	@Override
 	public Matrix4f getViewMatrix() {
-		return Maths.createViewMatrix(this);
+		Matrix4f viewMatrix = new Matrix4f();
+		viewMatrix.setIdentity();
+		Matrix4f.rotate((float) Math.toRadians(getPitch()), new Vector3f(1, 0, 0), viewMatrix, viewMatrix);
+		Matrix4f.rotate((float) Math.toRadians(getYaw()), new Vector3f(0, 1, 0), viewMatrix, viewMatrix);
+		Matrix4f.rotate((float) Math.toRadians(getRoll()), new Vector3f(0, 0, 1), viewMatrix, viewMatrix);
+		Vector3f cameraPos = getPosition();
+		Vector3f negativeCameraPos = new Vector3f(-cameraPos.x, -cameraPos.y, -cameraPos.z);
+		Matrix4f.translate(negativeCameraPos, viewMatrix, viewMatrix);
+		return viewMatrix;
 	}
 	
 	@Override
 	public void reflect(float height){
 		this.pitch = -pitch;
 		this.position.y = position.y - 2 * (position.y - height);
-		updateViewMatrix();
 	}
 
 	@Override
@@ -131,7 +126,7 @@ public class Camera implements IPositionable, ICamera {
 
 	@Override
 	public Matrix4f getProjectionViewMatrix() {
-		return Matrix4f.mul(projectionMatrix, viewMatrix, null);
+		return Matrix4f.mul(projectionMatrix, getViewMatrix(), null);
 	}
 	
 }

+ 2 - 1
src/main/java/eu/tankernn/gameEngine/entities/Car.java

@@ -4,6 +4,7 @@ import org.lwjgl.input.Keyboard;
 import org.lwjgl.util.vector.Vector3f;
 
 import eu.tankernn.gameEngine.loader.models.AABB;
+import eu.tankernn.gameEngine.loader.models.TexturedModel;
 import eu.tankernn.gameEngine.renderEngine.DisplayManager;
 import eu.tankernn.gameEngine.settings.Physics;
 import eu.tankernn.gameEngine.terrains.Terrain;
@@ -14,7 +15,7 @@ public class Car extends Player {
 	private static final float MAX_SPEED = 100.0f, ACCELERATION = 20.0f, DECELERATION = 10.0f, BRAKE = 40.0f,
 			TURN_FORCE = 160.0f;
 
-	public Car(int model, Vector3f position, Vector3f rotation, float scale, AABB boundingBox, TerrainPack terrainPack) {
+	public Car(TexturedModel model, Vector3f position, Vector3f rotation, float scale, AABB boundingBox, TerrainPack terrainPack) {
 		super(model, position, rotation, scale, boundingBox, terrainPack);
 	}
 

+ 9 - 6
src/main/java/eu/tankernn/gameEngine/entities/Entity3D.java

@@ -2,17 +2,19 @@ package eu.tankernn.gameEngine.entities;
 
 import org.lwjgl.util.vector.Vector3f;
 
+import eu.tankernn.gameEngine.animation.animatedModel.AnimatedModel;
 import eu.tankernn.gameEngine.loader.models.AABB;
+import eu.tankernn.gameEngine.loader.models.TexturedModel;
 import eu.tankernn.gameEngine.util.IPositionable;
 
 public class Entity3D implements IPositionable {
-	private int model;
+	private TexturedModel model;
 	private Vector3f position;
 	private Vector3f rotation;
 	private float scale;
 	private AABB boundingBox;
 	
-	public Entity3D(int model, Vector3f position, Vector3f rotation, float scale, AABB boundingBox) {
+	public Entity3D(TexturedModel model, Vector3f position, Vector3f rotation, float scale, AABB boundingBox) {
 		this.model = model;
 		this.position = position;
 		this.rotation = rotation;
@@ -25,22 +27,23 @@ public class Entity3D implements IPositionable {
 		this.position.x += dx;
 		this.position.y += dy;
 		this.position.z += dz;
-		updateBoundingBox();
 	}
 	
 	public void increaseRotation(Vector3f deltaRotation) {
 		Vector3f.add(this.rotation, deltaRotation, this.rotation);
 	}
 	
-	private void updateBoundingBox() {
+	public void update() {
 		this.boundingBox.updatePosition(this.position);
+		if (model instanceof AnimatedModel)
+			((AnimatedModel) model).update();
 	}
 	
-	public int getModel() {
+	public TexturedModel getModel() {
 		return model;
 	}
 	
-	public void setModel(int model) {
+	public void setModel(TexturedModel model) {
 		this.model = model;
 	}
 	

+ 7 - 1
src/main/java/eu/tankernn/gameEngine/entities/Player.java

@@ -4,7 +4,9 @@ import org.lwjgl.input.Keyboard;
 import org.lwjgl.input.Mouse;
 import org.lwjgl.util.vector.Vector3f;
 
+import eu.tankernn.gameEngine.animation.animatedModel.AnimatedModel;
 import eu.tankernn.gameEngine.loader.models.AABB;
+import eu.tankernn.gameEngine.loader.models.TexturedModel;
 import eu.tankernn.gameEngine.renderEngine.DisplayManager;
 import eu.tankernn.gameEngine.settings.Physics;
 import eu.tankernn.gameEngine.terrains.Terrain;
@@ -25,7 +27,7 @@ public class Player extends Entity3D {
 	
 	private float height = 2.0f;
 	
-	public Player(int model, Vector3f position, Vector3f rotation, float scale, AABB boundingBox, TerrainPack terrainPack) {
+	public Player(TexturedModel model, Vector3f position, Vector3f rotation, float scale, AABB boundingBox, TerrainPack terrainPack) {
 		super(model, position, rotation, scale, boundingBox);
 		this.terrainPack = terrainPack;
 	}
@@ -65,11 +67,15 @@ public class Player extends Entity3D {
 	
 	protected void checkInputs() {
 		if (Keyboard.isKeyDown(Keyboard.KEY_W) || (Mouse.isButtonDown(0) && Mouse.isButtonDown(1))) {
+			if (this.getModel() instanceof AnimatedModel)
+				((AnimatedModel) getModel()).doAnimation("run");
 			this.currentSpeed = RUN_SPEED;
 		} else if (Keyboard.isKeyDown(Keyboard.KEY_S)) {
 			this.currentSpeed = -RUN_SPEED;
 		} else {
 			this.currentSpeed = 0;
+			if (this.getModel() instanceof AnimatedModel)
+				((AnimatedModel) getModel()).doAnimation("idle");
 		}
 		
 		if (Keyboard.isKeyDown(Keyboard.KEY_A)) {

+ 156 - 179
src/main/java/eu/tankernn/gameEngine/loader/Loader.java

@@ -3,116 +3,98 @@ package eu.tankernn.gameEngine.loader;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.nio.FloatBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
-import org.lwjgl.BufferUtils;
-import org.lwjgl.opengl.GL11;
-import org.lwjgl.opengl.GL15;
-import org.lwjgl.opengl.GL20;
-import org.lwjgl.opengl.GL30;
-import org.lwjgl.opengl.GL33;
 
+import eu.tankernn.gameEngine.animation.animatedModel.AnimatedModel;
+import eu.tankernn.gameEngine.animation.animatedModel.Joint;
+import eu.tankernn.gameEngine.animation.loaders.AnimationLoader;
+import eu.tankernn.gameEngine.loader.colladaLoader.AnimatedModelData;
+import eu.tankernn.gameEngine.loader.colladaLoader.ColladaLoader;
+import eu.tankernn.gameEngine.loader.colladaLoader.JointsData;
+import eu.tankernn.gameEngine.loader.colladaLoader.MeshData;
 import eu.tankernn.gameEngine.loader.models.AABB;
 import eu.tankernn.gameEngine.loader.models.TexturedModel;
 import eu.tankernn.gameEngine.loader.obj.ModelData;
 import eu.tankernn.gameEngine.loader.obj.ObjLoader;
 import eu.tankernn.gameEngine.loader.textures.ModelTexture;
 import eu.tankernn.gameEngine.loader.textures.Texture;
-import eu.tankernn.gameEngine.renderEngine.RawModel;
+import eu.tankernn.gameEngine.renderEngine.Vao;
 import eu.tankernn.gameEngine.util.InternalFile;
 
 /**
  * General purpose loader
  * 
  * @author Frans
- *
  */
 public class Loader {
-	private List<Integer> vaos = new ArrayList<Integer>();
-	private List<Integer> vbos = new ArrayList<Integer>();
-	private List<RawModel> rawModels = new ArrayList<RawModel>();
-	private List<Texture> textures = new ArrayList<Texture>();
-	private Map<Integer, TexturedModel> models = new HashMap<Integer, TexturedModel>();
-
-	public RawModel loadToVAO(float[] vertices, float[] textureCoords, float[] normals, int[] indices) {
-		RawModel model = RawModel.create();
-		model.storeData(indices, vertices.length / 3, vertices, textureCoords, normals);
-		rawModels.add(model);
-		return model;
+	public static final int MAX_WEIGHTS = 3;
+	
+	private List<Vao> vaos = new ArrayList<>();
+	private List<Texture> textures = new ArrayList<>();
+	private Map<Integer, TexturedModel> models = new HashMap<>();
+	private Map<Integer, AABB> boundingBoxes = new HashMap<>();
+	
+	public Vao loadToVAO(float[] vertices, float[] textureCoords, float[] normals, int[] indices) {
+		return loadToVAO(vertices, textureCoords, normals, null, null, null, indices);
 	}
-
-	public RawModel loadToVAO(float[] vertices, float[] textureCoords, float[] normals, float[] tangents,
-			int[] indices) {
-		RawModel model = RawModel.create();
-		model.storeData(indices, vertices.length / 3, vertices, textureCoords, normals, tangents);
-		rawModels.add(model);
+	
+	public Vao loadToVAO(float[] vertices, float[] textureCoords, float[] normals, float[] tangents, int[] jointIds, float[] weights, int[] indices) {
+		Vao model = Vao.create();
+		model.bind();
+		model.createIndexBuffer(indices);
+		model.createAttribute(0, vertices, 3);
+		model.createAttribute(1, textureCoords, 2);
+		model.createAttribute(2, normals, 3);
+		if (tangents != null)
+			model.createAttribute(3, tangents, 3);
+		if (jointIds != null)
+			model.createIntAttribute(4, jointIds, 3);
+		if (weights != null)
+			model.createAttribute(5, weights, 3);
+		model.unbind();
+		vaos.add(model);
 		return model;
 	}
-
-	public int createEmptyVBO(int floatCount) {
-		int vboID = GL15.glGenBuffers();
-		vbos.add(vboID);
-		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
-		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, floatCount * 4, GL15.GL_STREAM_DRAW);
-		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
-		return vboID;
-	}
-
-	public void addInstacedAttribute(int vao, int vbo, int attribute, int dataSize, int instancedDataLength,
-			int offset) {
-		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
-		GL30.glBindVertexArray(vao);
-		GL20.glVertexAttribPointer(attribute, dataSize, GL11.GL_FLOAT, false, instancedDataLength * 4, offset * 4);
-		GL33.glVertexAttribDivisor(attribute, 1);
-		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
-		GL30.glBindVertexArray(0);
-	}
-
-	public void updateVBO(int vboID, float[] data, FloatBuffer buffer) {
-		buffer.clear();
-		buffer.put(data);
-		buffer.flip();
-		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
-		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer.capacity() * 4, GL15.GL_STREAM_DRAW);
-		GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, buffer);
-		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
+	
+	public Vao loadToVAO(float[] positions, float[] textureCoords) {
+		Vao vao = Vao.create();
+		vao.bind();
+		vao.storeDataInAttributeList(0, 2, positions);
+		vao.storeDataInAttributeList(1, 2, textureCoords);
+		vao.unbind();
+		vaos.add(vao);
+		return vao;
 	}
-
-	public int loadToVAO(float[] positions, float[] textureCoords) {
-		int vaoID = createVAO();
-		storeDataInAttributeList(0, 2, positions);
-		storeDataInAttributeList(1, 2, textureCoords);
-		unbindVAO();
-		return vaoID;
+	
+	public Vao loadToVAO(float[] positions, int dimensions) {
+		Vao vao = Vao.create(positions.length / 2);
+		vao.bind();
+		vao.storeDataInAttributeList(0, dimensions, positions);
+		vao.unbind();
+		vaos.add(vao);
+		return vao;
 	}
-
-	public RawModel loadToVAO(float[] positions, int dimensions) {
-		int vaoID = createVAO();
-		this.storeDataInAttributeList(0, dimensions, positions);
-		unbindVAO();
-		return new RawModel(vaoID, positions.length / 2);
+	
+	public Vao loadToVAO(ModelData data) {
+		return loadToVAO(data.getVertices(), data.getTextureCoords(), data.getNormals(), data.getTangents(), null, null, data.getIndices());
 	}
-
-	public RawModel loadToVAO(ModelData data) {
-		return (RawModel) loadToVAO(data.getVertices(), data.getTextureCoords(), data.getNormals(), data.getTangents(),
-				data.getIndices());
+	
+	public Vao loadToVAO(MeshData data) {
+		return loadToVAO(data.getVertices(), data.getTextureCoords(), data.getNormals(), data.getTangents(), data.getJointIds(), data.getVertexWeights(), data.getIndices());
 	}
-
+	
 	/**
 	 * Loads a texture to the GPU.
 	 * 
-	 * @param filename
-	 *            The path, relative to the root of the jar file, of the file to
-	 *            load.
+	 * @param filename The path, relative to the root of the jar file, of the
+	 *        file to load.
 	 * @return The texture ID
 	 * @throws FileNotFoundException
 	 */
@@ -125,101 +107,96 @@ public class Loader {
 	public Texture loadTexture(String filename) throws FileNotFoundException {
 		return loadTexture(new InternalFile(filename));
 	}
-
+	
 	/**
 	 * Creates a new cube map from the images specified. File 0: Right face File
 	 * 1: Left face File 2: Top face File 3: Bottom face File 4: Back face File
 	 * 5: Front face
 	 * 
-	 * @param textureFiles
-	 *            Filenames of images that make up the cube map
+	 * @param textureFiles Filenames of images that make up the cube map
 	 * @return The ID of the new cube map
 	 */
-
+	
 	public Texture loadCubeMap(InternalFile[] textureFiles) {
 		Texture cubeMap = Texture.newCubeMap(textureFiles, 500);
 		textures.add(cubeMap);
 		return cubeMap;
 	}
-
+	
+	private static final int[] CUBE_INDICES = {0, 1, 3, 1, 2, 3, 1, 5, 2, 2, 5, 6, 4, 7, 5, 5, 7, 6, 0, 3, 4, 4, 3, 7, 7, 3, 6, 6, 3, 2, 4, 5, 0, 0, 5, 1};
+	
+	public Vao generateCube(float size) {
+		Vao vao = Vao.create();
+		vao.bind();
+		vao.createIndexBuffer(CUBE_INDICES);
+		vao.createAttribute(0, getCubeVertexPositions(size), 3);
+		vao.unbind();
+		vaos.add(vao);
+		return vao;
+	}
+	
+	private static float[] getCubeVertexPositions(float size) {
+		return new float[] {-size, size, size, size, size, size, size, -size, size, -size, -size, size, -size, size, -size, size, size, -size, size, -size, -size, -size, -size, -size};
+	}
+	
 	public void cleanUp() {
-		for (int vao : vaos)
-			GL30.glDeleteVertexArrays(vao);
-		for (int vbo : vbos)
-			GL15.glDeleteBuffers(vbo);
-		for (Texture tex : textures)
+		for (Texture tex: textures)
 			tex.delete();
-		for (RawModel model : rawModels)
+		for (Vao model: vaos)
 			model.delete();
 	}
-
-	private int createVAO() {
-		int vaoID = GL30.glGenVertexArrays();
-		vaos.add(vaoID);
-		GL30.glBindVertexArray(vaoID);
-		return vaoID;
-	}
-
-	private void storeDataInAttributeList(int attributeNumber, int coordinateSize, float[] data) {
-		int vboID = GL15.glGenBuffers();
-		vbos.add(vboID);
-		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
-		FloatBuffer buffer = storeDataInFloatBuffer(data);
-		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
-		GL20.glVertexAttribPointer(attributeNumber, coordinateSize, GL11.GL_FLOAT, false, 0, 0);
-		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
-	}
-
-	private void unbindVAO() {
-		GL30.glBindVertexArray(0);
-	}
-
-	private FloatBuffer storeDataInFloatBuffer(float[] data) {
-		FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length);
-		buffer.put(data);
-		buffer.flip();
-		return buffer;
-	}
-
-	public RawModel loadOBJ(InternalFile objFile) {
+	
+	public Vao loadOBJ(InternalFile objFile) {
 		ModelData data = ObjLoader.loadOBJ(objFile);
-		return this.loadToVAO(data).withBoundingBox(data);
+		Vao vao = this.loadToVAO(data);
+		boundingBoxes.put(vao.id, new AABB(data));
+		return vao;
 	}
-
+	
+	/**
+	 * Creates an AnimatedEntity from the data in an entity file. It loads up
+	 * the collada model data, stores the extracted data in a VAO, sets up the
+	 * joint heirarchy, and loads up the entity's texture.
+	 * 
+	 * @param entityFile - the file containing the data for the entity.
+	 * @return The animated entity (no animation applied though)
+	 */
+	public AnimatedModel loadDAE(InternalFile modelFile, ModelTexture texture) {
+		AnimatedModelData entityData = ColladaLoader.loadColladaModel(modelFile, MAX_WEIGHTS);
+		Vao model = loadToVAO(entityData.getMeshData());
+		boundingBoxes.put(model.id, new AABB(entityData.getMeshData()));
+		JointsData skeletonData = entityData.getJointsData();
+		Joint headJoint = new Joint(skeletonData.headJoint);
+		return new AnimatedModel(model, texture, headJoint, skeletonData.jointCount);
+	}
+	
 	public void readModelSpecification(InternalFile file) throws IOException {
-		Map<InternalFile, RawModel> cachedRawModels = new HashMap<InternalFile, RawModel>();
+		Map<InternalFile, Vao> cachedRawModels = new HashMap<InternalFile, Vao>();
 		Map<InternalFile, Texture> cachedTextures = new HashMap<InternalFile, Texture>();
 		JSONObject spec;
-
+		
 		JSONArray jsonArr = new JSONArray(file.readFile());
-
+		
 		for (int j = 0; j < jsonArr.length(); j++) {
 			spec = jsonArr.getJSONObject(j);
-
+			
 			int id;
-			RawModel model;
+			Vao model;
 			ModelTexture modelTexture;
-
+			
 			id = spec.getInt("id");
-
-			InternalFile objFile = new InternalFile(optFilename(spec, "model", ".obj"));
-
-			String[] textureFiles = {
-					optFilename(spec, "texture", ".png"), 
-					optFilename(spec, "specular", "S.png"),
-					optFilename(spec, "normal", "N.png")
-			};
-
-			if (cachedRawModels.containsKey(objFile))
-				model = cachedRawModels.get(objFile);
-			else {
-				model = loadOBJ(objFile);
-				cachedRawModels.put(objFile, model);
+			InternalFile modelFile;
+			try {
+				modelFile = new InternalFile("models/" + optFilename(spec, "model", ".obj"));
+			} catch (FileNotFoundException e) {
+				modelFile = new InternalFile("models/" + optFilename(spec, "model", ".dae"));
 			}
-
+			
+			String[] textureFiles = {optFilename(spec, "texture", ".png"), optFilename(spec, "specular", "S.png"), optFilename(spec, "normal", "N.png")};
+			
 			Texture[] textures = Arrays.stream(textureFiles).map(fileName -> {
 				try {
-					InternalFile f = new InternalFile(fileName);
+					InternalFile f = new InternalFile("textures/" + fileName);
 					if (cachedTextures.containsKey(f)) {
 						return cachedTextures.get(f);
 					} else {
@@ -233,61 +210,61 @@ public class Loader {
 					return null;
 				}
 			}).toArray(size -> new Texture[size]);
-
+			
 			modelTexture = new ModelTexture(textures[0]);
 			if (textures[1] != null)
 				modelTexture.setNormalMap(textures[2]);
 			if (textures[2] != null)
 				modelTexture.setSpecularMap(textures[1]);
-
+			
 			modelTexture.setShineDamper(optFloat(spec, "shinedamper", 10.0f));
 			modelTexture.setReflectivity(optFloat(spec, "reflectivity", 0f));
 			modelTexture.setRefractivity(optFloat(spec, "refractivity", 0f));
-
+			
 			modelTexture.setHasTransparency(spec.optBoolean("transparency"));
-
-			models.put(id, new TexturedModel(model, modelTexture));
+			
+			if (cachedRawModels.containsKey(modelFile)) {
+				model = cachedRawModels.get(modelFile);
+				models.put(id, new TexturedModel(model, modelTexture));
+			} else {
+				switch (modelFile.getExtension()) {
+				case "obj":
+					model = loadOBJ(modelFile);
+					cachedRawModels.put(modelFile, model);
+					models.put(id, new TexturedModel(model, modelTexture));
+					break;
+				case "dae":
+					AnimatedModel animatedModel = loadDAE(modelFile, modelTexture);
+					String animations = spec.getString("animations");
+					animatedModel.registerAnimations(AnimationLoader.loadAnimations(modelFile, new InternalFile("models/" + animations)));
+					models.put(id, animatedModel);
+					break;
+				default:
+					throw new UnsupportedOperationException("Unsupported file format: " + modelFile.getExtension());
+				}
+			}
 		}
 	}
-
+	
 	private float optFloat(JSONObject spec, String key, float defaultValue) {
 		return BigDecimal.valueOf(spec.optDouble(key, (double) defaultValue)).floatValue();
 	}
-
+	
 	private String optFilename(JSONObject spec, String key, String extension) {
 		return spec.has(key) ? spec.getString(key) : spec.get("name") + extension;
 	}
-
-	public int registerModel(int id, TexturedModel model) throws Exception {
-		if (models.containsKey(id)) {
-			throw new Exception("There is already a model registered for the key " + id + ".");
-		} else {
-			models.put(id, model);
-			return id;
-		}
-	}
-
-	public int registerModel(TexturedModel model) throws Exception {
-		if (models.containsValue(model)) {
-			Iterator<Entry<Integer, TexturedModel>> i = models.entrySet().iterator();
-
-			while (i.hasNext()) {
-				Entry<Integer, TexturedModel> e = i.next();
-				if (e.getValue().equals(model)) {
-					return e.getKey();
-				}
-			}
-			// Should be impossible
-			throw new IllegalStateException();
-		} else
-			return registerModel(models.size(), model);
-	}
-
+	
 	public TexturedModel getModel(int id) {
-		return models.get(id);
+		TexturedModel model = models.get(id);
+		if (model instanceof AnimatedModel)
+			return new AnimatedModel((AnimatedModel) model);
+		else
+			return model;
 	}
-
-	public AABB getBoundingBox(int id) {
-		return getModel(id).getBoundingBox();
+	
+	public AABB getBoundingBox(int vaoId) {
+		if (!boundingBoxes.containsKey(vaoId))
+			throw new NullPointerException("Unable to find bounding box for vaoId " + vaoId);
+		return boundingBoxes.get(vaoId);
 	}
 }

+ 21 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/AnimatedModelData.java

@@ -0,0 +1,21 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+public class AnimatedModelData {
+
+	private final JointsData joints;
+	private final MeshData mesh;
+	
+	protected AnimatedModelData(MeshData mesh, JointsData joints){
+		this.joints = joints;
+		this.mesh = mesh;
+	}
+	
+	public JointsData getJointsData(){
+		return joints;
+	}
+	
+	public MeshData getMeshData(){
+		return mesh;
+	}
+	
+}

+ 13 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/AnimationData.java

@@ -0,0 +1,13 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+public class AnimationData {
+	
+	public final float lengthSeconds;
+	public final KeyFrameData[] keyFrames;
+	
+	public AnimationData(float lengthSeconds, KeyFrameData[] keyFrames){
+		this.lengthSeconds = lengthSeconds;
+		this.keyFrames = keyFrames;
+	}
+
+}

+ 101 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/AnimationLoader.java

@@ -0,0 +1,101 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+import java.nio.FloatBuffer;
+import java.util.List;
+
+import org.lwjgl.BufferUtils;
+import org.lwjgl.util.vector.Matrix4f;
+import org.lwjgl.util.vector.Vector3f;
+
+import eu.tankernn.gameEngine.loader.xmlLoader.XmlNode;
+
+
+public class AnimationLoader {
+	
+	private static final Matrix4f CORRECTION = new Matrix4f().rotate((float) Math.toRadians(-90), new Vector3f(1, 0, 0));
+	
+	private XmlNode animationData;
+	private XmlNode jointHierarchy;
+	
+	public AnimationLoader(XmlNode animationData, XmlNode jointHierarchy){
+		this.animationData = animationData;
+		this.jointHierarchy = jointHierarchy;
+	}
+	
+	public AnimationData extractAnimation(){
+		String rootNode = findRootJointName();
+		float[] times = getKeyTimes();
+		float duration = times[times.length-1];
+		KeyFrameData[] keyFrames = initKeyFrames(times);
+		List<XmlNode> animationNodes = animationData.getChildren("animation");
+		for(XmlNode jointNode : animationNodes){
+			loadJointTransforms(keyFrames, jointNode, rootNode);
+		}
+		return new AnimationData(duration, keyFrames);
+	}
+	
+	private float[] getKeyTimes(){
+		XmlNode timeData = animationData.getChild("animation").getChild("source").getChild("float_array");
+		String[] rawTimes = timeData.getData().split(" ");
+		float[] times = new float[rawTimes.length];
+		for(int i=0;i<times.length;i++){
+			times[i] = Float.parseFloat(rawTimes[i]);
+		}
+		return times;
+	}
+	
+	private KeyFrameData[] initKeyFrames(float[] times){
+		KeyFrameData[] frames = new KeyFrameData[times.length];
+		for(int i=0;i<frames.length;i++){
+			frames[i] = new KeyFrameData(times[i]);
+		}
+		return frames;
+	}
+	
+	private void loadJointTransforms(KeyFrameData[] frames, XmlNode jointData, String rootNodeId){
+		String jointNameId = getJointName(jointData);
+		String dataId = getDataId(jointData);
+		XmlNode transformData = jointData.getChildWithAttribute("source", "id", dataId);
+		String[] rawData = transformData.getChild("float_array").getData().split(" ");
+		processTransforms(jointNameId, rawData, frames, jointNameId.equals(rootNodeId));
+	}
+	
+	private String getDataId(XmlNode jointData){
+		XmlNode node = jointData.getChild("sampler").getChildWithAttribute("input", "semantic", "OUTPUT");
+		return node.getAttribute("source").substring(1);
+	}
+	
+	private String getJointName(XmlNode jointData){
+		XmlNode channelNode = jointData.getChild("channel");
+		String data = channelNode.getAttribute("target");
+		return data.split("/")[0];
+	}
+	
+	private void processTransforms(String jointName, String[] rawData, KeyFrameData[] keyFrames, boolean root){
+		FloatBuffer buffer = BufferUtils.createFloatBuffer(16);
+		float[] matrixData = new float[16];
+		for(int i=0;i<keyFrames.length;i++){
+			for(int j=0;j<16;j++){
+				matrixData[j] = Float.parseFloat(rawData[i*16 + j]);
+			}
+			buffer.clear();
+			buffer.put(matrixData);
+			buffer.flip();
+			Matrix4f transform = new Matrix4f();
+			transform.load(buffer);
+			transform.transpose();
+			if(root){
+				//because up axis in Blender is different to up axis in game
+				Matrix4f.mul(CORRECTION, transform, transform);
+			}
+			keyFrames[i].addJointTransform(new JointTransformData(jointName, transform));
+		}
+	}
+	
+	private String findRootJointName(){
+		XmlNode skeleton = jointHierarchy.getChild("visual_scene").getChildWithAttribute("node", "id", "Armature");
+		return skeleton.getChild("node").getAttribute("id");
+	}
+
+
+}

+ 33 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/ColladaLoader.java

@@ -0,0 +1,33 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+import eu.tankernn.gameEngine.loader.xmlLoader.XmlNode;
+import eu.tankernn.gameEngine.loader.xmlLoader.XmlParser;
+import eu.tankernn.gameEngine.util.InternalFile;
+
+public class ColladaLoader {
+
+	public static AnimatedModelData loadColladaModel(InternalFile colladaFile, int maxWeights) {
+		XmlNode node = XmlParser.loadXmlFile(colladaFile);
+
+		SkinLoader skinLoader = new SkinLoader(node.getChild("library_controllers"), maxWeights);
+		SkinningData skinningData = skinLoader.extractSkinData();
+
+		JointsLoader jointsLoader = new JointsLoader(node.getChild("library_visual_scenes"), skinningData.jointOrder);
+		JointsData jointsData = jointsLoader.extractBoneData();
+
+		GeometryLoader g = new GeometryLoader(node.getChild("library_geometries"), skinningData.verticesSkinData);
+		MeshData meshData = g.extractModelData();
+
+		return new AnimatedModelData(meshData, jointsData);
+	}
+
+	public static AnimationData loadColladaAnimation(InternalFile colladaFile) {
+		XmlNode node = XmlParser.loadXmlFile(colladaFile);
+		XmlNode animNode = node.getChild("library_animations");
+		XmlNode jointsNode = node.getChild("library_visual_scenes");
+		AnimationLoader loader = new AnimationLoader(animNode, jointsNode);
+		AnimationData animData = loader.extractAnimation();
+		return animData;
+	}
+
+}

+ 212 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/GeometryLoader.java

@@ -0,0 +1,212 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.lwjgl.util.vector.Matrix4f;
+import org.lwjgl.util.vector.Vector2f;
+import org.lwjgl.util.vector.Vector3f;
+import org.lwjgl.util.vector.Vector4f;
+
+import eu.tankernn.gameEngine.loader.xmlLoader.XmlNode;
+
+
+/**
+ * Loads the mesh data for a model from a collada XML file.
+ * @author Karl
+ *
+ */
+public class GeometryLoader {
+
+	private final XmlNode meshData;
+	
+	private final List<VertexSkinData> vertexWeights;
+	
+	private float[] verticesArray;
+	private float[] normalsArray;
+	private float[] texturesArray;
+	private float[] tangentsArray;
+	private int[] indicesArray;
+	private int[] jointIdsArray;
+	private float[] weightsArray;
+
+	List<WeightedVertex> vertices = new ArrayList<WeightedVertex>();
+	List<Vector2f> textures = new ArrayList<Vector2f>();
+	List<Vector3f> normals = new ArrayList<Vector3f>();
+	List<Integer> indices = new ArrayList<Integer>();
+	
+	private Matrix4f correction = new Matrix4f().rotate((float) Math.toRadians(-90), new Vector3f(1, 0,0));
+	
+	public GeometryLoader(XmlNode geometryNode, List<VertexSkinData> vertexWeights) {
+		this.vertexWeights = vertexWeights;
+		this.meshData = geometryNode.getChild("geometry").getChild("mesh");
+	}
+	
+	public MeshData extractModelData(){
+		readRawData();
+		assembleVertices();
+		removeUnusedVertices();
+		initArrays();
+		convertDataToArrays();
+		convertIndicesListToArray();
+		return new MeshData(verticesArray, texturesArray, normalsArray, tangentsArray, indicesArray, jointIdsArray, weightsArray, 1);
+	}
+
+	private void readRawData() {
+		readPositions();
+		readNormals();
+		readTextureCoords();
+	}
+
+	private void readPositions() {
+		String positionsId = meshData.getChild("vertices").getChild("input").getAttribute("source").substring(1);
+		XmlNode positionsData = meshData.getChildWithAttribute("source", "id", positionsId).getChild("float_array");
+		int count = Integer.parseInt(positionsData.getAttribute("count"));
+		String[] posData = positionsData.getData().split(" ");
+		for (int i = 0; i < count/3; i++) {
+			float x = Float.parseFloat(posData[i * 3]);
+			float y = Float.parseFloat(posData[i * 3 + 1]);
+			float z = Float.parseFloat(posData[i * 3 + 2]);
+			Vector4f position = new Vector4f(x, y, z, 1);
+			Matrix4f.transform(correction, position, position);
+			vertices.add(new WeightedVertex(vertices.size(), new Vector3f(position.x, position.y, position.z), vertexWeights.get(vertices.size())));
+		}
+	}
+
+	private void readNormals() {
+		String normalsId = meshData.getChild("polylist").getChildWithAttribute("input", "semantic", "NORMAL")
+				.getAttribute("source").substring(1);
+		XmlNode normalsData = meshData.getChildWithAttribute("source", "id", normalsId).getChild("float_array");
+		int count = Integer.parseInt(normalsData.getAttribute("count"));
+		String[] normData = normalsData.getData().split(" ");
+		for (int i = 0; i < count/3; i++) {
+			float x = Float.parseFloat(normData[i * 3]);
+			float y = Float.parseFloat(normData[i * 3 + 1]);
+			float z = Float.parseFloat(normData[i * 3 + 2]);
+			Vector4f norm = new Vector4f(x, y, z, 0f);
+			Matrix4f.transform(correction, norm, norm);
+			normals.add(new Vector3f(norm.x, norm.y, norm.z));
+		}
+	}
+
+	private void readTextureCoords() {
+		String texCoordsId = meshData.getChild("polylist").getChildWithAttribute("input", "semantic", "TEXCOORD")
+				.getAttribute("source").substring(1);
+		XmlNode texCoordsData = meshData.getChildWithAttribute("source", "id", texCoordsId).getChild("float_array");
+		int count = Integer.parseInt(texCoordsData.getAttribute("count"));
+		String[] texData = texCoordsData.getData().split(" ");
+		for (int i = 0; i < count/2; i++) {
+			float s = Float.parseFloat(texData[i * 2]);
+			float t = Float.parseFloat(texData[i * 2 + 1]);
+			textures.add(new Vector2f(s, t));
+		}
+	}
+	
+	private void assembleVertices(){
+		XmlNode poly = meshData.getChild("polylist");
+		int typeCount = poly.getChildren("input").size();
+		String[] indexData = poly.getChild("p").getData().split(" ");
+		for(int i=0;i<indexData.length/typeCount;i++){
+			int positionIndex = Integer.parseInt(indexData[i * typeCount]);
+			int normalIndex = Integer.parseInt(indexData[i * typeCount + 1]);
+			int texCoordIndex = Integer.parseInt(indexData[i * typeCount + 2]);
+			processVertex(positionIndex, normalIndex, texCoordIndex);
+		}
+	}
+	
+
+	private WeightedVertex processVertex(int posIndex, int normIndex, int texIndex) {
+		WeightedVertex currentVertex = vertices.get(posIndex);
+		if (!currentVertex.isSet()) {
+			currentVertex.setTextureIndex(texIndex);
+			currentVertex.setNormalIndex(normIndex);
+			indices.add(posIndex);
+			return currentVertex;
+		} else {
+			return dealWithAlreadyProcessedVertex(currentVertex, texIndex, normIndex);
+		}
+	}
+
+	private int[] convertIndicesListToArray() {
+		this.indicesArray = new int[indices.size()];
+		for (int i = 0; i < indicesArray.length; i++) {
+			indicesArray[i] = indices.get(i);
+		}
+		return indicesArray;
+	}
+
+	private float convertDataToArrays() {
+		float furthestPoint = 0;
+		for (int i = 0; i < vertices.size(); i++) {
+			WeightedVertex currentVertex = vertices.get(i);
+			if (currentVertex.getLength() > furthestPoint) {
+				furthestPoint = currentVertex.getLength();
+			}
+			Vector3f position = currentVertex.getPosition();
+			Vector2f textureCoord = textures.get(currentVertex.getTextureIndex());
+			Vector3f normalVector = normals.get(currentVertex.getNormalIndex());
+			Vector3f tangent = currentVertex.getAverageTangent();
+			verticesArray[i * 3] = position.x;
+			verticesArray[i * 3 + 1] = position.y;
+			verticesArray[i * 3 + 2] = position.z;
+			texturesArray[i * 2] = textureCoord.x;
+			texturesArray[i * 2 + 1] = 1 - textureCoord.y;
+			normalsArray[i * 3] = normalVector.x;
+			normalsArray[i * 3 + 1] = normalVector.y;
+			normalsArray[i * 3 + 2] = normalVector.z;
+			tangentsArray[i * 3] = tangent.x;
+			tangentsArray[i * 3 + 1] = tangent.y;
+			tangentsArray[i * 3 + 2] = tangent.z;
+			VertexSkinData weights = currentVertex.getWeightsData();
+			jointIdsArray[i * 3] = weights.jointIds.get(0);
+			jointIdsArray[i * 3 + 1] = weights.jointIds.get(1);
+			jointIdsArray[i * 3 + 2] = weights.jointIds.get(2);
+			weightsArray[i * 3] = weights.weights.get(0);
+			weightsArray[i * 3 + 1] = weights.weights.get(1);
+			weightsArray[i * 3 + 2] = weights.weights.get(2);
+
+		}
+		return furthestPoint;
+	}
+
+	private WeightedVertex dealWithAlreadyProcessedVertex(WeightedVertex previousVertex, int newTextureIndex, int newNormalIndex) {
+		if (previousVertex.hasSameTextureAndNormal(newTextureIndex, newNormalIndex)) {
+			indices.add(previousVertex.getIndex());
+			return previousVertex;
+		} else {
+			WeightedVertex anotherVertex = previousVertex.getDuplicateVertex();
+			if (anotherVertex != null) {
+				return dealWithAlreadyProcessedVertex(anotherVertex, newTextureIndex, newNormalIndex);
+			} else {
+				WeightedVertex duplicateVertex = new WeightedVertex(vertices.size(), previousVertex.getPosition(), previousVertex.getWeightsData());
+				duplicateVertex.setTextureIndex(newTextureIndex);
+				duplicateVertex.setNormalIndex(newNormalIndex);
+				previousVertex.setDuplicateVertex(duplicateVertex);
+				vertices.add(duplicateVertex);
+				indices.add(duplicateVertex.getIndex());
+				return duplicateVertex;
+			}
+
+		}
+	}
+	
+	private void initArrays(){
+		this.verticesArray = new float[vertices.size() * 3];
+		this.texturesArray = new float[vertices.size() * 2];
+		this.normalsArray = new float[vertices.size() * 3];
+		this.tangentsArray = new float[vertices.size() * 3];
+		this.jointIdsArray = new int[vertices.size() * 3];
+		this.weightsArray = new float[vertices.size() * 3];
+	}
+
+	private void removeUnusedVertices() {
+		for (WeightedVertex vertex : vertices) {
+			vertex.averageTangents();
+			if (!vertex.isSet()) {
+				vertex.setTextureIndex(0);
+				vertex.setNormalIndex(0);
+			}
+		}
+	}
+	
+}

+ 27 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/JointData.java

@@ -0,0 +1,27 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.lwjgl.util.vector.Matrix4f;
+
+public class JointData {
+
+	public final int index;
+	public final String nameId;
+	public final Matrix4f bindLocalTransform;
+	
+	public final List<JointData> children = new ArrayList<JointData>();
+	
+	protected JointData(int index, String nameId, Matrix4f bindLocalTransform){
+		this.index = index;
+		this.nameId = nameId;
+		this.bindLocalTransform = bindLocalTransform;
+	}
+	
+	public void addChild(JointData child){
+		children.add(child);
+	}
+	
+	
+}

+ 14 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/JointTransformData.java

@@ -0,0 +1,14 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+import org.lwjgl.util.vector.Matrix4f;
+
+public class JointTransformData {
+
+	public final String jointNameId;
+	public final Matrix4f jointLocalTransform;
+	
+	protected JointTransformData(String jointNameId, Matrix4f jointLocalTransform){
+		this.jointNameId = jointNameId;
+		this.jointLocalTransform = jointLocalTransform;
+	}
+}

+ 13 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/JointsData.java

@@ -0,0 +1,13 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+public class JointsData {
+	
+	public final int jointCount;
+	public final JointData headJoint;
+	
+	protected JointsData(int jointCount, JointData headJoint){
+		this.jointCount = jointCount;
+		this.headJoint = headJoint;
+	}
+
+}

+ 68 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/JointsLoader.java

@@ -0,0 +1,68 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+import java.nio.FloatBuffer;
+import java.util.List;
+
+import org.lwjgl.BufferUtils;
+import org.lwjgl.util.vector.Matrix4f;
+import org.lwjgl.util.vector.Vector3f;
+
+import eu.tankernn.gameEngine.loader.xmlLoader.XmlNode;
+
+
+public class JointsLoader {
+
+	private XmlNode armatureData;
+	
+	private List<String> boneOrder;
+	
+	private int jointCount = 0;
+	
+	private static final Matrix4f CORRECTION = new Matrix4f().rotate((float) Math.toRadians(-90), new Vector3f(1, 0, 0));
+
+	public JointsLoader(XmlNode visualSceneNode, List<String> boneOrder) {
+		this.armatureData = visualSceneNode.getChild("visual_scene").getChildWithAttribute("node", "id", "Armature");
+		this.boneOrder = boneOrder;
+	}
+	
+	public JointsData extractBoneData(){
+		XmlNode headNode = armatureData.getChild("node");
+		JointData headJoint = loadJointData(headNode, true);
+		return new JointsData(jointCount, headJoint);
+	}
+	
+	private JointData loadJointData(XmlNode jointNode, boolean isRoot){
+		JointData joint = extractMainJointData(jointNode, isRoot);
+		for(XmlNode childNode : jointNode.getChildren("node")){
+			joint.addChild(loadJointData(childNode, false));
+		}
+		return joint;
+	}
+	
+	private JointData extractMainJointData(XmlNode jointNode, boolean isRoot){
+		String nameId = jointNode.getAttribute("id");
+		int index = boneOrder.indexOf(nameId);
+		String[] matrixData = jointNode.getChild("matrix").getData().split(" ");
+		Matrix4f matrix = new Matrix4f();
+		matrix.load(convertData(matrixData));
+		matrix.transpose();
+		if(isRoot){
+			//because in Blender z is up, but in our game y is up.
+			Matrix4f.mul(CORRECTION, matrix, matrix);
+		}
+		jointCount++;
+		return new JointData(index, nameId, matrix);
+	}
+	
+	private FloatBuffer convertData(String[] rawData){
+		float[] matrixData = new float[16];
+		for(int i=0;i<matrixData.length;i++){
+			matrixData[i] = Float.parseFloat(rawData[i]);
+		}
+		FloatBuffer buffer = BufferUtils.createFloatBuffer(16);
+		buffer.put(matrixData);
+		buffer.flip();
+		return buffer;
+	}
+
+}

+ 19 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/KeyFrameData.java

@@ -0,0 +1,19 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class KeyFrameData {
+
+	public final float time;
+	public final List<JointTransformData> jointTransforms = new ArrayList<JointTransformData>();
+	
+	protected KeyFrameData(float time){
+		this.time = time;
+	}
+	
+	protected void addJointTransform(JointTransformData transform){
+		jointTransforms.add(transform);
+	}
+	
+}

+ 31 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/MeshData.java

@@ -0,0 +1,31 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+import eu.tankernn.gameEngine.loader.obj.ModelData;
+
+public class MeshData extends ModelData {
+
+	private static final int DIMENSIONS = 3;
+
+	private int[] jointIds;
+	private float[] vertexWeights;
+
+	public MeshData(float[] vertices, float[] textureCoords, float[] normals, float[] tangents, int[] indices,
+			int[] jointIds, float[] vertexWeights, float furthestPoint) {
+		super(vertices, textureCoords, normals, tangents, indices, furthestPoint);
+		this.jointIds = jointIds;
+		this.vertexWeights = vertexWeights;
+	}
+
+	public int[] getJointIds() {
+		return jointIds;
+	}
+	
+	public float[] getVertexWeights(){
+		return vertexWeights;
+	}
+
+	public int getVertexCount() {
+		return getVertices().length / DIMENSIONS;
+	}
+
+}

+ 77 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/SkinLoader.java

@@ -0,0 +1,77 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.tankernn.gameEngine.loader.xmlLoader.XmlNode;
+
+public class SkinLoader {
+
+	private final XmlNode skinningData;
+	private final int maxWeights;
+
+	public SkinLoader(XmlNode controllersNode, int maxWeights) {
+		this.skinningData = controllersNode.getChild("controller").getChild("skin");
+		this.maxWeights = maxWeights;
+	}
+
+	public SkinningData extractSkinData() {
+		List<String> jointsList = loadJointsList();
+		float[] weights = loadWeights();
+		XmlNode weightsDataNode = skinningData.getChild("vertex_weights");
+		int[] effectorJointCounts = getEffectiveJointsCounts(weightsDataNode);
+		List<VertexSkinData> vertexWeights = getSkinData(weightsDataNode, effectorJointCounts, weights);
+		return new SkinningData(jointsList, vertexWeights);
+	}
+
+	private List<String> loadJointsList() {
+		XmlNode inputNode = skinningData.getChild("vertex_weights");
+		String jointDataId = inputNode.getChildWithAttribute("input", "semantic", "JOINT").getAttribute("source").substring(1);
+		XmlNode jointsNode = skinningData.getChildWithAttribute("source", "id", jointDataId).getChild("Name_array");
+		String[] names = jointsNode.getData().split(" ");
+		List<String> jointsList = new ArrayList<String>();
+		for (String name : names) {
+			jointsList.add(name);
+		}
+		return jointsList;
+	}
+
+	private float[] loadWeights() {
+		XmlNode inputNode = skinningData.getChild("vertex_weights");
+		String weightsDataId = inputNode.getChildWithAttribute("input", "semantic", "WEIGHT").getAttribute("source").substring(1);
+		XmlNode weightsNode = skinningData.getChildWithAttribute("source", "id", weightsDataId).getChild("float_array");
+		String[] rawData = weightsNode.getData().split(" ");
+		float[] weights = new float[rawData.length];
+		for (int i = 0; i < weights.length; i++) {
+			weights[i] = Float.parseFloat(rawData[i]);
+		}
+		return weights;
+	}
+
+	private int[] getEffectiveJointsCounts(XmlNode weightsDataNode) {
+		String[] rawData = weightsDataNode.getChild("vcount").getData().split(" ");
+		int[] counts = new int[rawData.length];
+		for (int i = 0; i < rawData.length; i++) {
+			counts[i] = Integer.parseInt(rawData[i]);
+		}
+		return counts;
+	}
+
+	private List<VertexSkinData> getSkinData(XmlNode weightsDataNode, int[] counts, float[] weights) {
+		String[] rawData = weightsDataNode.getChild("v").getData().split(" ");
+		List<VertexSkinData> skinningData = new ArrayList<VertexSkinData>();
+		int pointer = 0;
+		for (int count : counts) {
+			VertexSkinData skinData = new VertexSkinData();
+			for (int i = 0; i < count; i++) {
+				int jointId = Integer.parseInt(rawData[pointer++]);
+				int weightId = Integer.parseInt(rawData[pointer++]);
+				skinData.addJointEffect(jointId, weights[weightId]);
+			}
+			skinData.limitJointNumber(maxWeights);
+			skinningData.add(skinData);
+		}
+		return skinningData;
+	}
+
+}

+ 16 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/SkinningData.java

@@ -0,0 +1,16 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+import java.util.List;
+
+public class SkinningData {
+	
+	public final List<String> jointOrder;
+	public final List<VertexSkinData> verticesSkinData;
+	
+	protected SkinningData(List<String> jointOrder, List<VertexSkinData> verticesSkinData){
+		this.jointOrder = jointOrder;
+		this.verticesSkinData = verticesSkinData;
+	}
+
+
+}

+ 65 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/VertexSkinData.java

@@ -0,0 +1,65 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class VertexSkinData {
+	
+	public final List<Integer> jointIds = new ArrayList<Integer>();
+	public final List<Float> weights = new ArrayList<Float>();
+	
+	protected void addJointEffect(int jointId, float weight){
+		for(int i=0;i<weights.size();i++){
+			if(weight > weights.get(i)){
+				jointIds.add(i, jointId);
+				weights.add(i, weight);
+				return;
+			}
+		}
+		jointIds.add(jointId);
+		weights.add(weight);
+	}
+	
+	protected void limitJointNumber(int max){
+		if(jointIds.size() > max){
+			float[] topWeights = new float[max];
+			float total = saveTopWeights(topWeights);
+			refillWeightList(topWeights, total);
+			removeExcessJointIds(max);
+		}else if(jointIds.size() < max){
+			fillEmptyWeights(max);
+		}
+	}
+
+	private void fillEmptyWeights(int max){
+		while(jointIds.size() < max){
+			jointIds.add(0);
+			weights.add(0f);
+		}
+	}
+	
+	private float saveTopWeights(float[] topWeightsArray){
+		float total = 0;
+		for(int i=0;i<topWeightsArray.length;i++){
+			topWeightsArray[i] = weights.get(i);
+			total += topWeightsArray[i];
+		}
+		return total;
+	}
+	
+	private void refillWeightList(float[] topWeights, float total){
+		weights.clear();
+		for(int i=0;i<topWeights.length;i++){
+			weights.add(Math.min(topWeights[i]/total, 1));
+		}
+	}
+	
+	private void removeExcessJointIds(int max){
+		while(jointIds.size() > max){
+			jointIds.remove(jointIds.size()-1);
+		}
+	}
+	
+	
+
+}

+ 28 - 0
src/main/java/eu/tankernn/gameEngine/loader/colladaLoader/WeightedVertex.java

@@ -0,0 +1,28 @@
+package eu.tankernn.gameEngine.loader.colladaLoader;
+
+import org.lwjgl.util.vector.Vector3f;
+
+import eu.tankernn.gameEngine.loader.obj.Vertex;
+
+public class WeightedVertex extends Vertex {
+	private VertexSkinData weightsData;
+	protected WeightedVertex duplicateVertex = null;
+	
+	public WeightedVertex(int index,Vector3f position, VertexSkinData weightsData){
+		super(index, position);
+		this.weightsData = weightsData;
+	}
+	
+	public VertexSkinData getWeightsData(){
+		return weightsData;
+	}
+
+	public WeightedVertex getDuplicateVertex() {
+		return duplicateVertex;
+	}
+
+	public void setDuplicateVertex(WeightedVertex duplicateVertex) {
+		this.duplicateVertex = duplicateVertex;
+	}
+
+}

+ 5 - 3
src/main/java/eu/tankernn/gameEngine/loader/font/GUIText.java

@@ -3,6 +3,8 @@ package eu.tankernn.gameEngine.loader.font;
 import org.lwjgl.util.vector.Vector2f;
 import org.lwjgl.util.vector.Vector3f;
 
+import eu.tankernn.gameEngine.renderEngine.Vao;
+
 /**
  * Represents a piece of text in the game.
  * 
@@ -16,7 +18,7 @@ public class GUIText {
 	
 	private float fontSize;
 
-	private int textMeshVao;
+	private Vao textMeshVao;
 	private int vertexCount;
 	private Vector3f colour = new Vector3f(0f, 0f, 0f);
 
@@ -114,7 +116,7 @@ public class GUIText {
 	 * @return the ID of the text's VAO, which contains all the vertex data for
 	 *         the quads on which the text will be rendered.
 	 */
-	public int getMesh() {
+	public Vao getMesh() {
 		return textMeshVao;
 	}
 
@@ -127,7 +129,7 @@ public class GUIText {
 	 * @param verticesCount
 	 *            - the total number of vertices in all of the quads.
 	 */
-	public void setMeshInfo(int vao, int verticesCount) {
+	public void setMeshInfo(Vao vao, int verticesCount) {
 		this.textMeshVao = vao;
 		this.vertexCount = verticesCount;
 	}

+ 6 - 10
src/main/java/eu/tankernn/gameEngine/loader/models/TexturedModel.java

@@ -1,20 +1,20 @@
 package eu.tankernn.gameEngine.loader.models;
 
 import eu.tankernn.gameEngine.loader.textures.ModelTexture;
-import eu.tankernn.gameEngine.renderEngine.RawModel;
+import eu.tankernn.gameEngine.renderEngine.Vao;
 
 public class TexturedModel {
-	private RawModel rawModel;
+	private Vao rawModel;
 	private ModelTexture texture;
 	
 	private int textureIndex = 0;
 	
-	public TexturedModel(RawModel rawModel, ModelTexture texture) {
+	public TexturedModel(Vao rawModel, ModelTexture texture) {
 		this.rawModel = rawModel;
 		this.texture = texture;
 	}
 	
-	public TexturedModel(RawModel rawModel, ModelTexture texture, int textureIndex) {
+	public TexturedModel(Vao rawModel, ModelTexture texture, int textureIndex) {
 		this(rawModel, texture);
 		this.textureIndex = textureIndex;
 	}
@@ -29,15 +29,11 @@ public class TexturedModel {
 		return (float) row / (float) texture.getNumberOfRows();
 	}
 
-	public RawModel getRawModel() {
+	public Vao getModel() {
 		return rawModel;
 	}
 
-	public ModelTexture getModelTexture() {
+	public ModelTexture getTexture() {
 		return texture;
 	}
-	
-	public AABB getBoundingBox() {
-		return rawModel.getBoundingBox();
-	}
 }

+ 17 - 18
src/main/java/eu/tankernn/gameEngine/loader/obj/Vertex.java

@@ -12,30 +12,29 @@ public class Vertex {
 	private Vector3f position;
 	private int textureIndex = NO_INDEX;
 	private int normalIndex = NO_INDEX;
-	private Vertex duplicateVertex = null;
+	protected Vertex duplicateVertex = null;
 	private int index;
 	private float length;
 	private List<Vector3f> tangents = new ArrayList<Vector3f>();
 	private Vector3f averagedTangent = new Vector3f(0, 0, 0);
 	
-	protected Vertex(int index, Vector3f position){
+	public Vertex(int index, Vector3f position){
 		this.index = index;
 		this.position = position;
 		this.length = position.length();
 	}
 	
-	protected void addTangent(Vector3f tangent){
+	public void addTangent(Vector3f tangent){
 		tangents.add(tangent);
 	}
 	
-	//NEW
-	protected Vertex duplicate(int newIndex){
+	public Vertex duplicate(int newIndex){
 		Vertex vertex = new Vertex(newIndex, position);
 		vertex.tangents = this.tangents;
 		return vertex;
 	}
 	
-	protected void averageTangents(){
+	public void averageTangents(){
 		if(tangents.isEmpty()){
 			return;
 		}
@@ -45,51 +44,51 @@ public class Vertex {
 		averagedTangent.normalise();
 	}
 	
-	protected Vector3f getAverageTangent(){
+	public Vector3f getAverageTangent(){
 		return averagedTangent;
 	}
 	
-	protected int getIndex(){
+	public int getIndex(){
 		return index;
 	}
 	
-	protected float getLength(){
+	public float getLength(){
 		return length;
 	}
 	
-	protected boolean isSet(){
+	public boolean isSet(){
 		return textureIndex!=NO_INDEX && normalIndex!=NO_INDEX;
 	}
 	
-	protected boolean hasSameTextureAndNormal(int textureIndexOther,int normalIndexOther){
+	public boolean hasSameTextureAndNormal(int textureIndexOther,int normalIndexOther){
 		return textureIndexOther==textureIndex && normalIndexOther==normalIndex;
 	}
 	
-	protected void setTextureIndex(int textureIndex){
+	public void setTextureIndex(int textureIndex){
 		this.textureIndex = textureIndex;
 	}
 	
-	protected void setNormalIndex(int normalIndex){
+	public void setNormalIndex(int normalIndex){
 		this.normalIndex = normalIndex;
 	}
 
-	protected Vector3f getPosition() {
+	public Vector3f getPosition() {
 		return position;
 	}
 
-	protected int getTextureIndex() {
+	public int getTextureIndex() {
 		return textureIndex;
 	}
 
-	protected int getNormalIndex() {
+	public int getNormalIndex() {
 		return normalIndex;
 	}
 
-	protected Vertex getDuplicateVertex() {
+	public Vertex getDuplicateVertex() {
 		return duplicateVertex;
 	}
 
-	protected void setDuplicateVertex(Vertex duplicateVertex) {
+	public void setDuplicateVertex(Vertex duplicateVertex) {
 		this.duplicateVertex = duplicateVertex;
 	}
 

+ 6 - 0
src/main/java/eu/tankernn/gameEngine/loader/textures/ModelTexture.java

@@ -98,4 +98,10 @@ public class ModelTexture {
 		this.refractivity = refractivity;
 		return this;
 	}
+	
+	public void delete() {
+		colorTexture.delete();
+		normalMap.delete();
+		specularMap.delete();
+	}
 }

+ 168 - 0
src/main/java/eu/tankernn/gameEngine/loader/xmlLoader/XmlNode.java

@@ -0,0 +1,168 @@
+package eu.tankernn.gameEngine.loader.xmlLoader;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a node in an XML file. This contains the name of the node, a map
+ * of the attributes and their values, any text data between the start and end
+ * tag, and a list of all its children nodes.
+ * 
+ * @author Karl
+ *
+ */
+public class XmlNode {
+
+	private String name;
+	private Map<String, String> attributes;
+	private String data;
+	private Map<String, List<XmlNode>> childNodes;
+
+	protected XmlNode(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * @return The name of the XML node.
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return Any text data contained between the start and end tag of the
+	 *         node.
+	 */
+	public String getData() {
+		return data;
+	}
+
+	/**
+	 * Gets the value of a certain attribute of the node. Returns {@code null}
+	 * if the attribute doesn't exist.
+	 * 
+	 * @param attr
+	 *            - the name of the attribute.
+	 * @return The value of the attribute.
+	 */
+	public String getAttribute(String attr) {
+		if (attributes != null) {
+			return attributes.get(attr);
+		} else {
+			return null;
+		}
+	}
+
+	/**
+	 * Gets a certain child node of this node.
+	 * 
+	 * @param childName
+	 *            - the name of the child node.
+	 * @return The child XML node with the given name.
+	 */
+	public XmlNode getChild(String childName) {
+		if (childNodes != null) {
+			List<XmlNode> nodes = childNodes.get(childName);
+			if (nodes != null && !nodes.isEmpty()) {
+				return nodes.get(0);
+			}
+		}
+		return null;
+
+	}
+
+	/**
+	 * Gets a child node with a certain name, and with a given value of a given
+	 * attribute. Used to get a specific child when there are multiple child
+	 * nodes with the same node name.
+	 * 
+	 * @param childName
+	 *            - the name of the child node.
+	 * @param attr
+	 *            - the attribute whose value is to be checked.
+	 * @param value
+	 *            - the value that the attribute must have.
+	 * @return The child node which has the correct name and the correct value
+	 *         for the chosen attribute.
+	 */
+	public XmlNode getChildWithAttribute(String childName, String attr, String value) {
+		List<XmlNode> children = getChildren(childName);
+		if (children == null || children.isEmpty()) {
+			return null;
+		}
+		for (XmlNode child : children) {
+			String val = child.getAttribute(attr);
+			if (value.equals(val)) {
+				return child;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Get the child nodes of this node that have a given name.
+	 * 
+	 * @param name
+	 *            - the name of the child nodes.
+	 * @return A list of the child nodes with the given name. If none exist then
+	 *         an empty list is returned.
+	 */
+	public List<XmlNode> getChildren(String name) {
+		if (childNodes != null) {
+			List<XmlNode> children = childNodes.get(name);
+			if (children != null) {
+				return children;
+			}
+		}
+		return new ArrayList<XmlNode>();
+	}
+
+	/**
+	 * Adds a new attribute to this node. An attribute has a name and a value.
+	 * Attributes are stored in a HashMap which is initialized in here if it was
+	 * previously null.
+	 * 
+	 * @param attr
+	 *            - the name of the attribute.
+	 * @param value
+	 *            - the value of the attribute.
+	 */
+	protected void addAttribute(String attr, String value) {
+		if (attributes == null) {
+			attributes = new HashMap<String, String>();
+		}
+		attributes.put(attr, value);
+	}
+
+	/**
+	 * Adds a child node to this node.
+	 * 
+	 * @param child
+	 *            - the child node to add.
+	 */
+	protected void addChild(XmlNode child) {
+		if (childNodes == null) {
+			childNodes = new HashMap<String, List<XmlNode>>();
+		}
+		List<XmlNode> list = childNodes.get(child.name);
+		if (list == null) {
+			list = new ArrayList<XmlNode>();
+			childNodes.put(child.name, list);
+		}
+		list.add(child);
+	}
+
+	/**
+	 * Sets some data for this node.
+	 * 
+	 * @param data
+	 *            - the data for this node (text that is found between the start
+	 *            and end tags of this node).
+	 */
+	protected void setData(String data) {
+		this.data = data;
+	}
+
+}

+ 102 - 0
src/main/java/eu/tankernn/gameEngine/loader/xmlLoader/XmlParser.java

@@ -0,0 +1,102 @@
+package eu.tankernn.gameEngine.loader.xmlLoader;
+
+import java.io.BufferedReader;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import eu.tankernn.gameEngine.util.InternalFile;
+
+/**
+ * Reads an XML file and stores all the data in {@link XmlNode} objects,
+ * allowing for easy access to the data contained in the XML file.
+ * 
+ * @author Karl
+ *
+ */
+public class XmlParser {
+
+	private static final Pattern DATA = Pattern.compile(">(.+?)<");
+	private static final Pattern START_TAG = Pattern.compile("<(.+?)>");
+	private static final Pattern ATTR_NAME = Pattern.compile("(.+?)=");
+	private static final Pattern ATTR_VAL = Pattern.compile("\"(.+?)\"");
+	private static final Pattern CLOSED = Pattern.compile("(</|/>)");
+
+	/**
+	 * Reads an XML file and stores all the data in {@link XmlNode} objects,
+	 * allowing for easy access to the data contained in the XML file.
+	 * 
+	 * @param file - the XML file
+	 * @return The root node of the XML structure.
+	 */
+	public static XmlNode loadXmlFile(InternalFile file) {
+		BufferedReader reader = null;
+		try {
+			reader = file.getReader();
+		} catch (Exception e) {
+			e.printStackTrace();
+			System.err.println("Can't find the XML file: " + file.getPath());
+			System.exit(0);
+			return null;
+		}
+		try {
+			reader.readLine();
+			XmlNode node = loadNode(reader);
+			reader.close();
+			return node;
+		} catch (Exception e) {
+			e.printStackTrace();
+			System.err.println("Error with XML file format for: " + file.getPath());
+			System.exit(0);
+			return null;
+		}
+	}
+
+	private static XmlNode loadNode(BufferedReader reader) throws Exception {
+		String line = reader.readLine().trim();
+		if (line.startsWith("</")) {
+			return null;
+		}
+		String[] startTagParts = getStartTag(line).split(" ");
+		XmlNode node = new XmlNode(startTagParts[0].replace("/", ""));
+		addAttributes(startTagParts, node);
+		addData(line, node);
+		if (CLOSED.matcher(line).find()) {
+			return node;
+		}
+		XmlNode child = null;
+		while ((child = loadNode(reader)) != null) {
+			node.addChild(child);
+		}
+		return node;
+	}
+
+	private static void addData(String line, XmlNode node) {
+		Matcher matcher = DATA.matcher(line);
+		if (matcher.find()) {
+			node.setData(matcher.group(1));
+		}
+	}
+
+	private static void addAttributes(String[] titleParts, XmlNode node) {
+		for (int i = 1; i < titleParts.length; i++) {
+			if (titleParts[i].contains("=")) {
+				addAttribute(titleParts[i], node);
+			}
+		}
+	}
+
+	private static void addAttribute(String attributeLine, XmlNode node) {
+		Matcher nameMatch = ATTR_NAME.matcher(attributeLine);
+		nameMatch.find();
+		Matcher valMatch = ATTR_VAL.matcher(attributeLine);
+		valMatch.find();
+		node.addAttribute(nameMatch.group(1), valMatch.group(1));
+	}
+
+	private static String getStartTag(String line) {
+		Matcher match = START_TAG.matcher(line);
+		match.find();
+		return match.group(1);
+	}
+
+}

+ 9 - 9
src/main/java/eu/tankernn/gameEngine/particles/ParticleRenderer.java

@@ -6,6 +6,7 @@ import java.util.Map;
 
 import org.lwjgl.BufferUtils;
 import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GL15;
 import org.lwjgl.opengl.GL20;
 import org.lwjgl.opengl.GL30;
 import org.lwjgl.opengl.GL31;
@@ -14,7 +15,8 @@ import org.lwjgl.util.vector.Vector3f;
 
 import eu.tankernn.gameEngine.entities.Camera;
 import eu.tankernn.gameEngine.loader.Loader;
-import eu.tankernn.gameEngine.renderEngine.RawModel;
+import eu.tankernn.gameEngine.renderEngine.Vao;
+import eu.tankernn.gameEngine.renderEngine.Vbo;
 
 public class ParticleRenderer {
 	
@@ -24,20 +26,18 @@ public class ParticleRenderer {
 	
 	private static final FloatBuffer buffer = BufferUtils.createFloatBuffer(MAX_INSTANCES * INSTANCE_DATA_LENGTH);
 	
-	private RawModel quad;
+	private Vao quad;
 	private ParticleShader shader;
 	
-	private Loader loader;
-	private int vbo;
+	private Vbo vbo;
 	private int pointer = 0;
 	
 	protected ParticleRenderer(Loader loader, Matrix4f projectionMatrix) {
-		this.loader = loader;
-		this.vbo = loader.createEmptyVBO(INSTANCE_DATA_LENGTH * MAX_INSTANCES);
+		this.vbo = Vbo.create(GL15.GL_ARRAY_BUFFER, GL15.GL_STREAM_DRAW, INSTANCE_DATA_LENGTH * MAX_INSTANCES);
 		quad = loader.loadToVAO(VERTICES, 2);
 		for (int i = 0; i < 5; i++)
-			loader.addInstacedAttribute(quad.id, vbo, i + 1, 4, INSTANCE_DATA_LENGTH, i * 4);
-		loader.addInstacedAttribute(quad.id, vbo, 6, 1, INSTANCE_DATA_LENGTH, 20);
+			quad.addInstacedAttribute(vbo, i + 1, 4, INSTANCE_DATA_LENGTH, i * 4);
+		quad.addInstacedAttribute(vbo, 6, 1, INSTANCE_DATA_LENGTH, 20);
 		shader = new ParticleShader();
 		shader.start();
 		shader.projectionMatrix.loadMatrix(projectionMatrix);
@@ -56,7 +56,7 @@ public class ParticleRenderer {
 				updateModelViewMatrix(p.getPosition(), p.getRotation(), p.getScale(), viewMatrix, vboData);
 				updateTexCoordInfo(p, vboData);
 			}
-			loader.updateVBO(vbo, vboData, buffer);
+			vbo.updateData(vboData, buffer);
 			GL31.glDrawArraysInstanced(GL11.GL_TRIANGLE_STRIP, 0, quad.getIndexCount(), particleList.size());
 		}
 		finishRendering();

+ 22 - 18
src/main/java/eu/tankernn/gameEngine/postProcessing/PostProcessor.java

@@ -12,32 +12,36 @@ import eu.tankernn.gameEngine.postProcessing.bloom.BrightFilter;
 import eu.tankernn.gameEngine.postProcessing.bloom.CombineFilter;
 import eu.tankernn.gameEngine.postProcessing.gaussianBlur.HorizontalBlur;
 import eu.tankernn.gameEngine.postProcessing.gaussianBlur.VerticalBlur;
-import eu.tankernn.gameEngine.renderEngine.RawModel;
+import eu.tankernn.gameEngine.renderEngine.Vao;
 
 public class PostProcessor {
-
-	private final int blurFactor = 0;
-
-	private final float[] POSITIONS = { -1, 1, -1, -1, 1, 1, 1, -1 };
-	private RawModel quad;
+	
+	private final float[] POSITIONS = {-1, 1, -1, -1, 1, 1, 1, -1};
+	private Vao quad;
 	private List<PostProcessingEffect<?>> effects = new ArrayList<PostProcessingEffect<?>>();
 	private CombineFilter combineFilter;
+	public final int blurFactor;
 	
-	public PostProcessor(Loader loader) {
+	public PostProcessor(Loader loader, boolean underWater) {
+		this.blurFactor = underWater ? 2 : 0;
 		quad = loader.loadToVAO(POSITIONS, 2);
-		effects.add(new ContrastChanger());
-		for (int i = 0; i < blurFactor; i++) {
-			int temp = i + 1;
-			effects.add(new HorizontalBlur(Display.getWidth() / temp, Display.getHeight() / temp));
-			effects.add(new VerticalBlur(Display.getWidth() / temp, Display.getHeight() / temp));
+		if (!underWater)
+			effects.add(new ContrastChanger());
+		for (int i = 1; i < blurFactor + 1; i++) {
+			effects.add(new HorizontalBlur(Display.getWidth() / i, Display.getHeight() / i));
+			effects.add(new VerticalBlur(Display.getWidth() / i, Display.getHeight() / i));
 		}
 		effects.add(new BrightFilter(Display.getWidth() / 2, Display.getHeight() / 2));
 		combineFilter = new CombineFilter();
 	}
-
+	
+	public PostProcessor(Loader loader) {
+		this(loader, false);
+	}
+	
 	public void doPostProcessing(Texture colorTexture, Texture brightTexture) {
 		start();
-		for (PostProcessingEffect<?> effect : effects) {
+		for (PostProcessingEffect<?> effect: effects) {
 			effect.render(colorTexture, brightTexture);
 			colorTexture = effect.getOutputColorTexture();
 			brightTexture = effect.getOutputBrightTexture();
@@ -45,20 +49,20 @@ public class PostProcessor {
 		combineFilter.render(colorTexture, brightTexture);
 		end();
 	}
-
+	
 	public void cleanUp() {
 		effects.forEach(p -> p.cleanUp());
 		combineFilter.cleanUp();
 	}
-
+	
 	private void start() {
 		quad.bind(0);
 		GL11.glDisable(GL11.GL_DEPTH_TEST);
 	}
-
+	
 	private void end() {
 		GL11.glEnable(GL11.GL_DEPTH_TEST);
 		quad.unbind(0);
 	}
-
+	
 }

+ 1 - 1
src/main/java/eu/tankernn/gameEngine/postProcessing/bloom/BrightFilter.java

@@ -1,8 +1,8 @@
 package eu.tankernn.gameEngine.postProcessing.bloom;
 
 import eu.tankernn.gameEngine.loader.textures.Texture;
-import eu.tankernn.gameEngine.postProcessing.PostProcessingEffect;
 import eu.tankernn.gameEngine.postProcessing.ImageRenderer;
+import eu.tankernn.gameEngine.postProcessing.PostProcessingEffect;
 
 public class BrightFilter extends PostProcessingEffect<BrightFilterShader> {
 

+ 1 - 1
src/main/java/eu/tankernn/gameEngine/postProcessing/gaussianBlur/HorizontalBlur.java

@@ -1,8 +1,8 @@
 package eu.tankernn.gameEngine.postProcessing.gaussianBlur;
 
 import eu.tankernn.gameEngine.loader.textures.Texture;
-import eu.tankernn.gameEngine.postProcessing.PostProcessingEffect;
 import eu.tankernn.gameEngine.postProcessing.ImageRenderer;
+import eu.tankernn.gameEngine.postProcessing.PostProcessingEffect;
 
 public class HorizontalBlur extends PostProcessingEffect<HorizontalBlurShader> {
 	

+ 1 - 1
src/main/java/eu/tankernn/gameEngine/postProcessing/gaussianBlur/VerticalBlur.java

@@ -1,8 +1,8 @@
 package eu.tankernn.gameEngine.postProcessing.gaussianBlur;
 
 import eu.tankernn.gameEngine.loader.textures.Texture;
-import eu.tankernn.gameEngine.postProcessing.PostProcessingEffect;
 import eu.tankernn.gameEngine.postProcessing.ImageRenderer;
+import eu.tankernn.gameEngine.postProcessing.PostProcessingEffect;
 
 public class VerticalBlur extends PostProcessingEffect<VerticalBlurShader> {
 	

+ 1 - 4
src/main/java/eu/tankernn/gameEngine/renderEngine/MasterRenderer.java

@@ -37,8 +37,6 @@ import eu.tankernn.gameEngine.util.ICamera;
 public class MasterRenderer {
 	private static final Vector4f NO_CLIP = new Vector4f(0, 0, 0, 1);
 	
-	private Loader loader;
-	
 	private EntityRenderer<EntityShader> entityRenderer;
 	private TerrainRenderer terrainRenderer;
 	private SkyboxRenderer skyboxRenderer;
@@ -57,7 +55,6 @@ public class MasterRenderer {
 	 */
 	public MasterRenderer(Loader loader, Camera camera, Skybox skybox) {
 		enableCulling();
-		this.loader = loader;
 		terrainRenderer = new TerrainRenderer(camera.getProjectionMatrix());
 		shadowMapRenderer = new ShadowMapMasterRenderer(camera);
 		skyboxRenderer = new SkyboxRenderer(loader, camera.getProjectionMatrix(), skybox);
@@ -135,7 +132,7 @@ public class MasterRenderer {
 	 *            Entity to add to the list
 	 */
 	public void processEntity(Entity3D entity) {
-		TexturedModel entityModel = loader.getModel(entity.getModel());
+		TexturedModel entityModel = entity.getModel();
 		List<Entity3D> batch = entities.get(entityModel);
 		if (batch != null) {
 			batch.add(entity);

+ 0 - 150
src/main/java/eu/tankernn/gameEngine/renderEngine/RawModel.java

@@ -1,150 +0,0 @@
-package eu.tankernn.gameEngine.renderEngine;
-
-import org.lwjgl.opengl.GL11;
-import org.lwjgl.opengl.GL15;
-import org.lwjgl.opengl.GL20;
-import org.lwjgl.opengl.GL30;
-
-import eu.tankernn.gameEngine.loader.models.AABB;
-import eu.tankernn.gameEngine.loader.obj.ModelData;
-
-public class RawModel {
-	
-	private static final int BYTES_PER_FLOAT = 4;
-	
-	public final int id;
-	private Vbo dataVbo;
-	private Vbo indexVbo;
-	private int indexCount;
-	private AABB boundingBox;
-
-	public static RawModel create() {
-		int id = GL30.glGenVertexArrays();
-		return new RawModel(id);
-	}
-
-	private RawModel(int id) {
-		this.id = id;
-	}
-	
-	public RawModel(int id, int indexCount) {
-		this(id);
-		this.indexCount = indexCount;
-	}
-	
-	public int getIndexCount(){
-		return indexCount;
-	}
-
-	public void bind() {
-		GL30.glBindVertexArray(id);
-	}
-	
-	public void bind(int... attributes){
-		bind();
-		for (int i : attributes) {
-			GL20.glEnableVertexAttribArray(i);
-		}
-	}
-
-	public void unbind() {
-		GL30.glBindVertexArray(0);
-	}
-	
-	public void unbind(int... attributes){
-		for (int i : attributes) {
-			GL20.glDisableVertexAttribArray(i);
-		}
-		unbind();
-	}
-	
-	public RawModel storeData(int[] indices, int vertexCount, float[]... data){
-		bind();
-		storeData(vertexCount, data);
-		createIndexBuffer(indices);
-		unbind();
-		return this;
-	}
-	
-	public RawModel withBoundingBox(ModelData data) {
-		this.boundingBox = new AABB(data);
-		return this;
-	}
-	
-	public void delete() {
-		GL30.glDeleteVertexArrays(id);
-		dataVbo.delete();
-		indexVbo.delete();
-	}
-
-	private void createIndexBuffer(int[] indices){
-		this.indexVbo = Vbo.create(GL15.GL_ELEMENT_ARRAY_BUFFER);
-		indexVbo.bind();
-		indexVbo.storeData(indices);
-		this.indexCount = indices.length;
-	}
-	
-	private void storeData(int vertexCount, float[]... data) {
-		float[] interleavedData = interleaveFloatData(vertexCount, data);
-		int[] lengths = getAttributeLengths(data, vertexCount);
-		storeInterleavedData(interleavedData, lengths);
-	}
-	
-	private int[] getAttributeLengths(float[][] data, int vertexCount){
-		int[] lengths = new int[data.length];
-		for (int i = 0; i < data.length; i++) {
-			lengths[i] = data[i].length / vertexCount;
-		}
-		return lengths;
-	}
-
-	private void storeInterleavedData(float[] data, int... lengths) {
-		dataVbo = Vbo.create(GL15.GL_ARRAY_BUFFER);
-		dataVbo.bind();
-		dataVbo.storeData(data);
-		int bytesPerVertex = calculateBytesPerVertex(lengths);
-		linkVboDataToAttributes(lengths, bytesPerVertex);
-		dataVbo.unbind();
-	}
-	
-	private void linkVboDataToAttributes(int[] lengths, int bytesPerVertex){
-		int total = 0;
-		for (int i = 0; i < lengths.length; i++) {
-			GL20.glVertexAttribPointer(i, lengths[i], GL11.GL_FLOAT, false, bytesPerVertex, BYTES_PER_FLOAT * total);
-			total += lengths[i];
-		}
-	}
-	
-	private int calculateBytesPerVertex(int[] lengths){
-		int total = 0;
-		for (int i = 0; i < lengths.length; i++) {
-			total += lengths[i];
-		}
-		return BYTES_PER_FLOAT * total;
-	}
-
-	private float[] interleaveFloatData(int count, float[]... data) {
-		int totalSize = 0;
-		int[] lengths = new int[data.length];
-		for (int i = 0; i < data.length; i++) {
-			int elementLength = data[i].length / count;
-			lengths[i] = elementLength;
-			totalSize += data[i].length;
-		}
-		float[] interleavedBuffer = new float[totalSize];
-		int pointer = 0;
-		for (int i = 0; i < count; i++) {
-			for (int j = 0; j < data.length; j++) {
-				int elementLength = lengths[j];
-				for (int k = 0; k < elementLength; k++) {
-					interleavedBuffer[pointer++] = data[j][i * elementLength + k];
-				}
-			}
-		}
-		return interleavedBuffer;
-	}
-	
-	public AABB getBoundingBox() {
-		return this.boundingBox.copy();
-	}
-}

+ 117 - 0
src/main/java/eu/tankernn/gameEngine/renderEngine/Vao.java

@@ -0,0 +1,117 @@
+package eu.tankernn.gameEngine.renderEngine;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GL15;
+import org.lwjgl.opengl.GL20;
+import org.lwjgl.opengl.GL30;
+import org.lwjgl.opengl.GL33;
+
+public class Vao {
+	
+	protected static final int BYTES_PER_FLOAT = 4;
+	private static final int BYTES_PER_INT = 4;
+	public final int id;
+	private List<Vbo> dataVbos = new ArrayList<Vbo>();
+	private Vbo indexVbo;
+	private int indexCount;
+	
+	public static Vao create() {
+		int id = GL30.glGenVertexArrays();
+		return new Vao(id);
+	}
+	
+	public static Vao create(int indexCount) {
+		int id = GL30.glGenVertexArrays();
+		return new Vao(id, indexCount);
+	}
+	
+	private Vao(int id) {
+		this.id = id;
+	}
+	
+	private Vao(int id, int indexCount) {
+		this(id);
+		this.indexCount = indexCount;
+	}
+	
+	public int getIndexCount() {
+		return indexCount;
+	}
+	
+	public void bind(int... attributes) {
+		bind();
+		for (int i: attributes) {
+			GL20.glEnableVertexAttribArray(i);
+		}
+	}
+	
+	public void unbind(int... attributes) {
+		for (int i: attributes) {
+			GL20.glDisableVertexAttribArray(i);
+		}
+		unbind();
+	}
+	
+	public void createIndexBuffer(int[] indices) {
+		this.indexVbo = Vbo.create(GL15.GL_ELEMENT_ARRAY_BUFFER);
+		indexVbo.bind();
+		indexVbo.storeData(indices);
+		this.indexCount = indices.length;
+	}
+	
+	public void createAttribute(int attribute, float[] data, int attrSize) {
+		Vbo dataVbo = Vbo.create(GL15.GL_ARRAY_BUFFER);
+		dataVbo.bind();
+		dataVbo.storeData(data);
+		GL20.glVertexAttribPointer(attribute, attrSize, GL11.GL_FLOAT, false, attrSize * BYTES_PER_FLOAT, 0);
+		dataVbo.unbind();
+		dataVbos.add(dataVbo);
+	}
+	
+	public void createIntAttribute(int attribute, int[] data, int attrSize) {
+		Vbo dataVbo = Vbo.create(GL15.GL_ARRAY_BUFFER);
+		dataVbo.bind();
+		dataVbo.storeData(data);
+		GL30.glVertexAttribIPointer(attribute, attrSize, GL11.GL_INT, attrSize * BYTES_PER_INT, 0);
+		dataVbo.unbind();
+		dataVbos.add(dataVbo);
+	}
+	
+	public void addInstacedAttribute(Vbo vbo, int attribute, int dataSize, int instancedDataLength, int offset) {
+		vbo.bind();
+		this.bind();
+		GL20.glVertexAttribPointer(attribute, dataSize, GL11.GL_FLOAT, false, instancedDataLength * 4, offset * 4);
+		GL33.glVertexAttribDivisor(attribute, 1);
+		vbo.unbind();
+		this.unbind();
+	}
+	
+	public void storeDataInAttributeList(int attributeNumber, int coordinateSize, float[] data) {
+		Vbo dataVbo = Vbo.create(GL15.GL_ARRAY_BUFFER, GL15.GL_STATIC_DRAW);
+		dataVbo.bind();
+		dataVbo.storeData(data);
+		GL20.glVertexAttribPointer(attributeNumber, coordinateSize, GL11.GL_FLOAT, false, 0, 0);
+		dataVbo.unbind();
+		dataVbos.add(dataVbo);
+	}
+	
+	public void delete() {
+		GL30.glDeleteVertexArrays(id);
+		for (Vbo vbo: dataVbos) {
+			vbo.delete();
+		}
+		if (indexVbo != null)
+			indexVbo.delete();
+	}
+	
+	private void bind() {
+		GL30.glBindVertexArray(id);
+	}
+	
+	private void unbind() {
+		GL30.glBindVertexArray(0);
+	}
+}

+ 38 - 6
src/main/java/eu/tankernn/gameEngine/renderEngine/Vbo.java

@@ -10,15 +10,33 @@ public class Vbo {
 	
 	private final int vboId;
 	private final int type;
+	private final int usage;
 	
-	private Vbo(int vboId, int type){
+	private Vbo(int vboId, int type, int usage){
 		this.vboId = vboId;
 		this.type = type;
+		this.usage = usage;
 	}
 	
-	public static Vbo create(int type){
+	private Vbo(int vboId, int type, int usage, int size) {
+		this(vboId, type, usage);
+		bind();
+		GL15.glBufferData(type, size * 4, usage);
+		unbind();
+	}
+
+	public static Vbo create(int type, int usage, int size) {
 		int id = GL15.glGenBuffers();
-		return new Vbo(id, type);
+		return new Vbo(id, type, usage, size);
+	}
+	
+	public static Vbo create(int type, int usage){
+		int id = GL15.glGenBuffers();
+		return new Vbo(id, type, usage);
+	}
+	
+	public static Vbo create(int type) {
+		return create(type, GL15.GL_STATIC_DRAW);
 	}
 	
 	public void bind(){
@@ -37,7 +55,7 @@ public class Vbo {
 	}
 	
 	public void storeData(FloatBuffer data){
-		GL15.glBufferData(type, data, GL15.GL_STATIC_DRAW);
+		GL15.glBufferData(type, data, usage);
 	}
 	
 	public void storeData(int[] data){
@@ -48,11 +66,25 @@ public class Vbo {
 	}
 	
 	public void storeData(IntBuffer data){
-		GL15.glBufferData(type, data, GL15.GL_STATIC_DRAW);
+		GL15.glBufferData(type, data, usage);
+	}
+
+	public void updateData(float[] data) {
+		updateData(data, BufferUtils.createFloatBuffer(data.length));
+	}
+	public void updateData(float[] data, FloatBuffer buffer) {
+		bind();
+		buffer.clear();
+		buffer.put(data);
+		buffer.flip();
+		GL15.glBindBuffer(type, vboId);
+		GL15.glBufferData(type, buffer.capacity() * 4, usage);
+		GL15.glBufferSubData(type, 0, buffer);
+		GL15.glBindBuffer(type, 0);
+		unbind();
 	}
 	
 	public void delete(){
 		GL15.glDeleteBuffers(vboId);
 	}
-
 }

+ 39 - 29
src/main/java/eu/tankernn/gameEngine/renderEngine/entities/EntityRenderer.java

@@ -12,6 +12,7 @@ import org.lwjgl.util.vector.Matrix4f;
 import org.lwjgl.util.vector.Vector3f;
 import org.lwjgl.util.vector.Vector4f;
 
+import eu.tankernn.gameEngine.animation.animatedModel.AnimatedModel;
 import eu.tankernn.gameEngine.entities.Entity3D;
 import eu.tankernn.gameEngine.entities.Light;
 import eu.tankernn.gameEngine.loader.models.TexturedModel;
@@ -20,23 +21,22 @@ import eu.tankernn.gameEngine.loader.textures.Texture;
 import eu.tankernn.gameEngine.renderEngine.MasterRenderer;
 import eu.tankernn.gameEngine.util.ICamera;
 import eu.tankernn.gameEngine.util.Maths;
+import eu.tankernn.gameEngine.util.OpenGlUtils;
 
 /**
  * Renderer for entities.
  * 
  * @author Frans
- *
  */
 public class EntityRenderer<S extends EntityShader> {
 	protected S shader;
-
+	
 	/**
 	 * Starts shader and loads initial values.
 	 * 
-	 * @param shader
-	 *            The shader to use when rendering entities
-	 * @param projectionMatrix
-	 *            The projection matrix to use when rendering entities
+	 * @param shader The shader to use when rendering entities
+	 * @param projectionMatrix The projection matrix to use when rendering
+	 *        entities
 	 */
 	@SuppressWarnings("unchecked")
 	public EntityRenderer(Matrix4f projectionMatrix) {
@@ -50,45 +50,53 @@ public class EntityRenderer<S extends EntityShader> {
 		shader.connectTextureUnits();
 		shader.stop();
 	}
-
+	
 	/**
 	 * Renders entities to the current frame buffer.
 	 * 
-	 * @param entities
-	 *            The entities to render.
-	 * @param toShadowSpace
-	 *            Transformation matrix to shadow space. Used for applying
-	 *            shadows.
+	 * @param entities The entities to render.
+	 * @param toShadowSpace Transformation matrix to shadow space. Used for
+	 *        applying shadows.
 	 */
-	public void render(Map<TexturedModel, List<Entity3D>> entities, Matrix4f toShadowSpace, ICamera cam,
-			Vector4f clipPlane, List<Light> lights, Texture environmentMap) {
+	public void render(Map<TexturedModel, List<Entity3D>> entities, Matrix4f toShadowSpace, ICamera cam, Vector4f clipPlane, List<Light> lights, Texture environmentMap) {
+		OpenGlUtils.antialias(true);
+		OpenGlUtils.disableBlending();
+		OpenGlUtils.enableDepthTesting(true);
 		shader.start();
 		shader.plane.loadVec4(clipPlane);
 		shader.skyColor.loadVec3(RED, GREEN, BLUE);
 		shader.loadLights(lights, cam.getViewMatrix());
 		shader.viewMatrix.loadCamera(cam);
-
+		
 		shader.toShadowMapSpace.loadMatrix(toShadowSpace);
 		shader.cameraPosition.loadVec3(cam.getPosition());
-		for (TexturedModel model : entities.keySet()) {
+		for (TexturedModel model: entities.keySet()) {
 			prepareTexturedModel(model, environmentMap);
 			List<Entity3D> batch = entities.get(model);
-			for (Entity3D entity : batch) {
+			for (Entity3D entity: batch) {
 				prepareInstance(entity, model);
-				GL11.glDrawElements(GL11.GL_TRIANGLES, model.getRawModel().getIndexCount(), GL11.GL_UNSIGNED_INT, 0);
+				GL11.glDrawElements(GL11.GL_TRIANGLES, model.getModel().getIndexCount(), GL11.GL_UNSIGNED_INT, 0);
 			}
 			unbindTexturedModel(model);
+			
 		}
 		shader.stop();
 	}
-
+	
 	public void cleanUp() {
 		shader.cleanUp();
 	}
-
+	
 	private void prepareTexturedModel(TexturedModel model, Texture environmentMap) {
-		model.getRawModel().bind(0, 1, 2, 3);
-		ModelTexture texture = model.getModelTexture();
+		if (model instanceof AnimatedModel) {
+			model.getModel().bind(0, 1, 2, 4, 5);
+			shader.animated.loadBoolean(true);
+			shader.jointTransforms.loadMatrixArray(((AnimatedModel) model).getJointTransforms());
+		} else {
+			model.getModel().bind(0, 1, 2, 3);
+			shader.animated.loadBoolean(false);
+		}
+		ModelTexture texture = model.getTexture();
 		shader.numberOfRows.loadFloat(texture.getNumberOfRows());
 		if (texture.hasTransparency())
 			MasterRenderer.disableCulling();
@@ -96,7 +104,7 @@ public class EntityRenderer<S extends EntityShader> {
 		shader.shineDamper.loadFloat(texture.getShineDamper());
 		shader.reflectivity.loadFloat(texture.getReflectivity());
 		shader.refractivity.loadFloat(texture.getRefractivity());
-		model.getModelTexture().getTexture().bindToUnit(0);
+		model.getTexture().getTexture().bindToUnit(0);
 		shader.usesSpecularMap.loadBoolean(texture.hasSpecularMap());
 		if (texture.hasSpecularMap()) {
 			texture.getSpecularMap().bindToUnit(2);
@@ -106,20 +114,22 @@ public class EntityRenderer<S extends EntityShader> {
 			texture.getNormalMap().bindToUnit(1);
 		bindEnvironmentMap(environmentMap);
 	}
-
+	
 	private void bindEnvironmentMap(Texture environmentMap) {
 		environmentMap.bindToUnit(10);
 	}
-
+	
 	private void unbindTexturedModel(TexturedModel model) {
 		MasterRenderer.enableCulling();
-		model.getRawModel().unbind(0, 1, 2, 3);
+		if (model instanceof AnimatedModel)
+			model.getModel().unbind(0, 1, 2, 4, 5);
+		else
+			model.getModel().unbind(0, 1, 2, 3);
 	}
-
+	
 	protected void prepareInstance(Entity3D entity, TexturedModel model) {
 		Vector3f rot = entity.getRotation();
-		Matrix4f transformationMatrix = Maths.createTransformationMatrix(entity.getPosition(), rot.x, rot.y, rot.z,
-				entity.getScale());
+		Matrix4f transformationMatrix = Maths.createTransformationMatrix(entity.getPosition(), rot.x, rot.y, rot.z, entity.getScale());
 		shader.transformationMatrix.loadMatrix(transformationMatrix);
 		shader.offset.loadVec2(model.getTextureXOffset(), model.getTextureYOffset());
 	}

+ 8 - 3
src/main/java/eu/tankernn/gameEngine/renderEngine/entities/EntityShader.java

@@ -10,15 +10,17 @@ import eu.tankernn.gameEngine.entities.Light;
 import eu.tankernn.gameEngine.renderEngine.shaders.ShaderProgram;
 import eu.tankernn.gameEngine.renderEngine.shaders.UniformBoolean;
 import eu.tankernn.gameEngine.renderEngine.shaders.UniformFloat;
+import eu.tankernn.gameEngine.renderEngine.shaders.UniformMat4Array;
 import eu.tankernn.gameEngine.renderEngine.shaders.UniformMatrix;
 import eu.tankernn.gameEngine.renderEngine.shaders.UniformSampler;
 import eu.tankernn.gameEngine.renderEngine.shaders.UniformVec2;
 import eu.tankernn.gameEngine.renderEngine.shaders.UniformVec3;
 import eu.tankernn.gameEngine.renderEngine.shaders.UniformVec4;
 import eu.tankernn.gameEngine.renderEngine.shaders.UniformViewMatrix;
+import eu.tankernn.gameEngine.settings.Settings;
 
 public class EntityShader extends ShaderProgram {
-
+	
 	private static final String VERTEX_FILE = "/eu/tankernn/gameEngine/renderEngine/entities/vertexShader.glsl";
 	private static final String FRAGMENT_FILE = "/eu/tankernn/gameEngine/renderEngine/entities/fragmentShader.glsl";
 
@@ -42,13 +44,16 @@ public class EntityShader extends ShaderProgram {
 	protected UniformSampler enviroMap = new UniformSampler("enviroMap");
 	protected UniformSampler normalMap = new UniformSampler("normalMap");
 	public UniformBoolean usesNormalMap = new UniformBoolean("usesNormalMap");
+	protected UniformMat4Array jointTransforms = new UniformMat4Array("jointTransforms", Settings.MAX_JOINTS);
+	protected UniformBoolean animated = new UniformBoolean("isAnimated");
 
 	public EntityShader() {
-		super(VERTEX_FILE, FRAGMENT_FILE, "position", "textureCoords", "normal", "tangent");
+		super(VERTEX_FILE, FRAGMENT_FILE, "position", "textureCoords", "normal", "tangent", "in_jointIndices", "in_weights");
 		super.getLightUniformLocations();
 		super.storeAllUniformLocations(transformationMatrix, projectionMatrix, viewMatrix, shineDamper, reflectivity,
 				refractivity, useFakeLighting, skyColor, numberOfRows, offset, plane, toShadowMapSpace, shadowMap,
-				specularMap, usesSpecularMap, modelTexture, cameraPosition, enviroMap, normalMap, usesNormalMap);
+				specularMap, usesSpecularMap, modelTexture, cameraPosition, enviroMap, normalMap, usesNormalMap,
+				jointTransforms, animated);
 	}
 
 	public EntityShader(String vertexFile, String fragmentFile, String... string) {

+ 30 - 3
src/main/java/eu/tankernn/gameEngine/renderEngine/entities/vertexShader.glsl

@@ -1,9 +1,14 @@
 #version 400 core
 
+const int MAX_JOINTS = 50;//max joints allowed in a skeleton
+const int MAX_WEIGHTS = 3;//max number of joints that can affect a vertex
+
 in vec3 position;
 in vec2 textureCoords;
 in vec3 normal;
 in vec3 tangent;
+in ivec3 in_jointIndices;
+in vec3 in_weights;
 
 out vec2 pass_textureCoords;
 out vec3 surfaceNormal;
@@ -35,16 +40,38 @@ const float gradient = 2.0;
 
 uniform vec4 plane;
 
+uniform mat4 jointTransforms[MAX_JOINTS];
+uniform float isAnimated;
+
 void main(void) {
-	vec4 worldPosition = transformationMatrix * vec4(position, 1.0);
+	
+	vec4 totalLocalPos = vec4(position, 1.0);
+	vec4 totalNormal = vec4(normal, 0.0);
+	
+	
+	if (isAnimated > 0.5) {
+		totalLocalPos = vec4(0.0);
+		totalNormal = vec4(0.0);
+		
+		for(int i=0;i<MAX_WEIGHTS;i++){
+			mat4 jointTransform = jointTransforms[in_jointIndices[i]];
+			vec4 posePosition = jointTransform * vec4(position, 1.0);
+			totalLocalPos += posePosition * in_weights[i];
+			
+			vec4 worldNormal = jointTransform * vec4(normal, 0.0);
+			totalNormal += worldNormal * in_weights[i];
+		}
+	}
+	
+	vec4 worldPosition = transformationMatrix * totalLocalPos;
 	gl_ClipDistance[0] = dot(worldPosition, plane);
 	shadowCoords = toShadowMapSpace * worldPosition;
 	mat4 modelViewMatrix = viewMatrix * transformationMatrix;
-	vec4 positionRelativeToCam = modelViewMatrix * vec4(position, 1.0);
+	vec4 positionRelativeToCam = modelViewMatrix * totalLocalPos;
 	gl_Position = projectionMatrix * positionRelativeToCam;
 	pass_textureCoords = (textureCoords / numberOfRows) + offset;
 
-	vec3 actualNormal = normal;
+	vec3 actualNormal = totalNormal.xyz;
 	if (useFakeLighting > 0.5) {
 		actualNormal = vec3(0.0, 1.0, 0.0);
 	}

+ 2 - 8
src/main/java/eu/tankernn/gameEngine/renderEngine/font/FontRenderer.java

@@ -4,8 +4,6 @@ import java.util.List;
 import java.util.Map;
 
 import org.lwjgl.opengl.GL11;
-import org.lwjgl.opengl.GL20;
-import org.lwjgl.opengl.GL30;
 
 import eu.tankernn.gameEngine.loader.font.FontType;
 import eu.tankernn.gameEngine.loader.font.GUIText;
@@ -41,15 +39,11 @@ public class FontRenderer {
 	}
 	
 	private void renderText(GUIText text) {
-		GL30.glBindVertexArray(text.getMesh());
-		GL20.glEnableVertexAttribArray(0);
-		GL20.glEnableVertexAttribArray(1);
+		text.getMesh().bind(0, 1);
 		shader.color.loadVec3(text.getColor());
 		shader.translation.loadVec2(text.getPosition());
 		GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, text.getVertexCount());
-		GL20.glDisableVertexAttribArray(0);
-		GL20.glDisableVertexAttribArray(1);
-		GL30.glBindVertexArray(0);
+		text.getMesh().unbind(0, 1);
 	}
 	
 	private void endRendering() {

+ 2 - 1
src/main/java/eu/tankernn/gameEngine/renderEngine/font/TextMaster.java

@@ -9,6 +9,7 @@ import eu.tankernn.gameEngine.loader.Loader;
 import eu.tankernn.gameEngine.loader.font.FontType;
 import eu.tankernn.gameEngine.loader.font.GUIText;
 import eu.tankernn.gameEngine.loader.font.TextMeshData;
+import eu.tankernn.gameEngine.renderEngine.Vao;
 
 public class TextMaster {
 
@@ -34,7 +35,7 @@ public class TextMaster {
 	public void updateText(GUIText text) {
 		FontType font = text.getFont();
 		TextMeshData data = font.loadText(text);
-		int vao = loader.loadToVAO(data.getVertexPositions(), data.getTextureCoords());
+		Vao vao = loader.loadToVAO(data.getVertexPositions(), data.getTextureCoords());
 		text.setMeshInfo(vao, data.getVertexCount());
 	}
 

+ 2 - 2
src/main/java/eu/tankernn/gameEngine/renderEngine/gui/GuiRenderer.java

@@ -6,12 +6,12 @@ import org.lwjgl.opengl.GL11;
 import org.lwjgl.util.vector.Matrix4f;
 
 import eu.tankernn.gameEngine.loader.Loader;
-import eu.tankernn.gameEngine.renderEngine.RawModel;
+import eu.tankernn.gameEngine.renderEngine.Vao;
 import eu.tankernn.gameEngine.util.Maths;
 
 public class GuiRenderer {
 	
-	private final RawModel quad;
+	private final Vao quad;
 	private GuiShader shader;
 	
 	public GuiRenderer(Loader loader) {

+ 32 - 0
src/main/java/eu/tankernn/gameEngine/renderEngine/shaders/UniformMat4Array.java

@@ -0,0 +1,32 @@
+package eu.tankernn.gameEngine.renderEngine.shaders;
+
+import org.lwjgl.util.vector.Matrix4f;
+
+public class UniformMat4Array extends Uniform{
+	
+	private UniformMatrix[] matrixUniforms;
+	
+	public UniformMat4Array(String name, int size) {
+		super(name);
+		matrixUniforms = new UniformMatrix[size];
+		for(int i=0;i<size;i++){
+			matrixUniforms[i] = new UniformMatrix(name + "["+i+"]");
+		}
+	}
+	
+	@Override
+	protected void storeUniformLocation(int programID) {
+		for(UniformMatrix matrixUniform : matrixUniforms){
+			matrixUniform.storeUniformLocation(programID);
+		}
+	}
+
+	public void loadMatrixArray(Matrix4f[] matrices){
+		for(int i=0;i<matrices.length;i++){
+			matrixUniforms[i].loadMatrix(matrices[i]);
+		}
+	}
+	
+	
+
+}

+ 37 - 34
src/main/java/eu/tankernn/gameEngine/renderEngine/shadows/ShadowMapEntityRenderer.java

@@ -9,85 +9,88 @@ import org.lwjgl.opengl.GL30;
 import org.lwjgl.util.vector.Matrix4f;
 import org.lwjgl.util.vector.Vector3f;
 
+import eu.tankernn.gameEngine.animation.animatedModel.AnimatedModel;
 import eu.tankernn.gameEngine.entities.Entity3D;
 import eu.tankernn.gameEngine.loader.models.TexturedModel;
 import eu.tankernn.gameEngine.renderEngine.MasterRenderer;
-import eu.tankernn.gameEngine.renderEngine.RawModel;
 import eu.tankernn.gameEngine.util.Maths;
 
 public class ShadowMapEntityRenderer {
-
+	
 	private Matrix4f projectionViewMatrix;
 	private ShadowShader shader;
-
+	
 	/**
-	 * @param shader
-	 *            - the simple shader program being used for the shadow render
-	 *            pass.
-	 * @param projectionViewMatrix
-	 *            - the orthographic projection matrix multiplied by the light's
-	 *            "view" matrix.
+	 * @param shader - the simple shader program being used for the shadow
+	 *        render pass.
+	 * @param projectionViewMatrix - the orthographic projection matrix
+	 *        multiplied by the light's "view" matrix.
 	 */
 	protected ShadowMapEntityRenderer(ShadowShader shader, Matrix4f projectionViewMatrix) {
 		this.shader = shader;
 		this.projectionViewMatrix = projectionViewMatrix;
 	}
-
+	
 	/**
 	 * Renders entieis to the shadow map. Each model is first bound and then all
 	 * of the entities using that model are rendered to the shadow map.
 	 * 
-	 * @param entities
-	 *            - the entities to be rendered to the shadow map.
+	 * @param entities - the entities to be rendered to the shadow map.
 	 */
 	protected void render(Map<TexturedModel, List<Entity3D>> entities) {
-		for (TexturedModel model : entities.keySet()) {
-			RawModel rawModel = model.getRawModel();
-			bindModel(rawModel);
-			model.getModelTexture().getTexture().bindToUnit(0);
-			if (model.getModelTexture().hasTransparency()) {
+		for (TexturedModel model: entities.keySet()) {
+			bindModel(model);
+			model.getTexture().getTexture().bindToUnit(0);
+			if (model.getTexture().hasTransparency()) {
 				MasterRenderer.disableCulling();
 			}
-			for (Entity3D entity : entities.get(model)) {
+			for (Entity3D entity: entities.get(model)) {
 				prepareInstance(entity);
-				GL11.glDrawElements(GL11.GL_TRIANGLES, rawModel.getIndexCount(), GL11.GL_UNSIGNED_INT, 0);
+				GL11.glDrawElements(GL11.GL_TRIANGLES, model.getModel().getIndexCount(), GL11.GL_UNSIGNED_INT, 0);
 			}
-			if (model.getModelTexture().hasTransparency()) {
+			if (model.getTexture().hasTransparency()) {
 				MasterRenderer.enableCulling();
 			}
 		}
 		GL20.glDisableVertexAttribArray(0);
 		GL20.glDisableVertexAttribArray(1);
+		GL20.glDisableVertexAttribArray(4);
+		GL20.glDisableVertexAttribArray(5);
 		GL30.glBindVertexArray(0);
 	}
-
+	
 	/**
-	 * Binds a raw model before rendering. Only the attribute 0 is enabled here
-	 * because that is where the positions are stored in the VAO, and only the
-	 * positions are required in the vertex shader.
+	 * Binds a raw model before rendering. Only the attribute 0 and 1 are
+	 * enabled here because that is where the positions and texture coordinates
+	 * are stored in the VAO. The texture coordinates are used to determine
+	 * whether the entity is transparent or not.
 	 * 
-	 * @param rawModel
-	 *            - the model to be bound.
+	 * @param rawModel - the model to be bound.
 	 */
-	private void bindModel(RawModel rawModel) {
-		rawModel.bind(0, 1);
+	private void bindModel(TexturedModel model) {
+		if (model instanceof AnimatedModel) {
+			model.getModel().bind(0, 1, 4, 5);
+			shader.animated.loadBoolean(true);
+			shader.jointTransforms.loadMatrixArray(((AnimatedModel) model).getJointTransforms());
+		} else {
+			model.getModel().bind(0, 1);
+			shader.animated.loadBoolean(false);
+		}
 	}
-
+	
 	/**
 	 * Prepares an entity to be rendered. The model matrix is created in the
 	 * usual way and then multiplied with the projection and view matrix (often
 	 * in the past we've done this in the vertex shader) to create the
 	 * mvp-matrix. This is then loaded to the vertex shader as a uniform.
 	 * 
-	 * @param entity
-	 *            - the entity to be prepared for rendering.
+	 * @param entity - the entity to be prepared for rendering.
 	 */
 	private void prepareInstance(Entity3D entity) {
 		Vector3f rot = entity.getRotation();
-		Matrix4f modelMatrix = Maths.createTransformationMatrix(entity.getPosition(), rot.x, rot.y, rot.z,
-				entity.getScale());
+		Matrix4f modelMatrix = Maths.createTransformationMatrix(entity.getPosition(), rot.x, rot.y, rot.z, entity.getScale());
 		Matrix4f mvpMatrix = Matrix4f.mul(projectionViewMatrix, modelMatrix, null);
 		shader.mvpMatrix.loadMatrix(mvpMatrix);
 	}
-
+	
 }

+ 1 - 1
src/main/java/eu/tankernn/gameEngine/renderEngine/shadows/ShadowMapMasterRenderer.java

@@ -25,7 +25,7 @@ import eu.tankernn.gameEngine.loader.textures.Texture;
  */
 public class ShadowMapMasterRenderer {
 
-	public static final int SHADOW_MAP_SIZE = 1024 * 2;
+	public static final int SHADOW_MAP_SIZE = 1024 * 4;
 
 	private ShadowFrameBuffer shadowFbo;
 	private ShadowShader shader;

+ 7 - 2
src/main/java/eu/tankernn/gameEngine/renderEngine/shadows/ShadowShader.java

@@ -1,7 +1,10 @@
 package eu.tankernn.gameEngine.renderEngine.shadows;
 
 import eu.tankernn.gameEngine.renderEngine.shaders.ShaderProgram;
+import eu.tankernn.gameEngine.renderEngine.shaders.UniformBoolean;
+import eu.tankernn.gameEngine.renderEngine.shaders.UniformMat4Array;
 import eu.tankernn.gameEngine.renderEngine.shaders.UniformMatrix;
+import eu.tankernn.gameEngine.settings.Settings;
 
 public class ShadowShader extends ShaderProgram {
 	
@@ -9,10 +12,12 @@ public class ShadowShader extends ShaderProgram {
 	private static final String FRAGMENT_FILE = "/eu/tankernn/gameEngine/renderEngine/shadows/shadowFragmentShader.glsl";
 	
 	protected UniformMatrix mvpMatrix = new UniformMatrix("mvpMatrix");
+	protected UniformMat4Array jointTransforms = new UniformMat4Array("jointTransforms", Settings.MAX_JOINTS);
+	protected UniformBoolean animated = new UniformBoolean("isAnimated");
 	
 	protected ShadowShader() {
-		super(VERTEX_FILE, FRAGMENT_FILE, "in_position", "in_textureCoords");
-		super.storeAllUniformLocations(mvpMatrix);
+		super(VERTEX_FILE, FRAGMENT_FILE, "in_position", "in_textureCoords", "normal", "tangent", "in_jointIndices", "in_weights");
+		super.storeAllUniformLocations(mvpMatrix, jointTransforms, animated);
 	}
 	
 }

+ 1 - 2
src/main/java/eu/tankernn/gameEngine/renderEngine/shadows/shadowFragmentShader.glsl

@@ -4,7 +4,7 @@ in vec2 textureCoords;
 
 out vec4 out_colour;
 
-uniform sampler2D modelTexture;//will use this next week
+uniform sampler2D modelTexture;
 
 void main(void){
 	
@@ -14,5 +14,4 @@ void main(void){
 	}
 	
 	out_colour = vec4(1.0);
-	
 }

+ 23 - 1
src/main/java/eu/tankernn/gameEngine/renderEngine/shadows/shadowVertexShader.glsl

@@ -1,15 +1,37 @@
 #version 150
 
+const int MAX_JOINTS = 50;//max joints allowed in a skeleton
+const int MAX_WEIGHTS = 3;//max number of joints that can affect a vertex
+
 in vec3 in_position;
 in vec2 in_textureCoords;
+in vec3 normal;
+in vec3 tangent;
+in ivec3 in_jointIndices;
+in vec3 in_weights;
 
 out vec2 textureCoords;
 
 uniform mat4 mvpMatrix;
 
+uniform mat4 jointTransforms[MAX_JOINTS];
+uniform float isAnimated;
+
 void main(void){
+	
+	vec4 totalLocalPos = vec4(in_position, 1.0);
+	
+	if (isAnimated > 0.5) {
+		totalLocalPos = vec4(0.0);
+		
+		for(int i=0;i<MAX_WEIGHTS;i++){
+			mat4 jointTransform = jointTransforms[in_jointIndices[i]];
+			vec4 posePosition = jointTransform * vec4(in_position, 1.0);
+			totalLocalPos += posePosition * in_weights[i];
+		}
+	}
 
-	gl_Position = mvpMatrix * vec4(in_position, 1.0);
+	gl_Position = mvpMatrix * totalLocalPos;
 	
 	textureCoords = in_textureCoords;
 }

+ 0 - 23
src/main/java/eu/tankernn/gameEngine/renderEngine/skybox/CubeGenerator.java

@@ -1,23 +0,0 @@
-package eu.tankernn.gameEngine.renderEngine.skybox;
-
-import eu.tankernn.gameEngine.renderEngine.RawModel;
-
-public class CubeGenerator {
-
-	private static final int VERTEX_COUNT = 8;
-	private static final int[] INDICES = { 0, 1, 3, 1, 2, 3, 1, 5, 2, 2, 5, 6, 4, 7, 5, 5, 7, 6, 0,
-			3, 4, 4, 3, 7, 7, 3, 6, 6, 3, 2, 4, 5, 0, 0, 5, 1 };
-
-	public static RawModel generateCube(float size) {
-		RawModel vao = RawModel.create();
-		vao.storeData(INDICES, VERTEX_COUNT, getVertexPositions(size));
-		return vao;
-	}
-
-	private static float[] getVertexPositions(float size) {
-		return new float[] { -size, size, size, size, size, size, size, -size, size, -size, -size,
-				size, -size, size, -size, size, size, -size, size, -size, -size, -size, -size,
-				-size };
-	}
-
-}

+ 6 - 5
src/main/java/eu/tankernn/gameEngine/renderEngine/skybox/Skybox.java

@@ -1,20 +1,21 @@
 package eu.tankernn.gameEngine.renderEngine.skybox;
 
+import eu.tankernn.gameEngine.loader.Loader;
 import eu.tankernn.gameEngine.loader.textures.Texture;
-import eu.tankernn.gameEngine.renderEngine.RawModel;
+import eu.tankernn.gameEngine.renderEngine.Vao;
 
 public class Skybox {
 
-	private RawModel cube;
+	private Vao cube;
 	private Texture dayTexture, nightTexture;
 
-	public Skybox(Texture dayTexture, Texture nightTexture, float size) {
-		cube = CubeGenerator.generateCube(size);
+	public Skybox(Loader loader, Texture dayTexture, Texture nightTexture, float size) {
+		cube = loader.generateCube(size);
 		this.dayTexture = dayTexture;
 		this.nightTexture = nightTexture;
 	}
 
-	public RawModel getCubeVao() {
+	public Vao getCubeVao() {
 		return cube;
 	}
 

+ 2 - 2
src/main/java/eu/tankernn/gameEngine/renderEngine/skybox/SkyboxRenderer.java

@@ -8,7 +8,7 @@ import org.lwjgl.util.vector.Matrix4f;
 import eu.tankernn.gameEngine.loader.Loader;
 import eu.tankernn.gameEngine.loader.textures.Texture;
 import eu.tankernn.gameEngine.renderEngine.DisplayManager;
-import eu.tankernn.gameEngine.renderEngine.RawModel;
+import eu.tankernn.gameEngine.renderEngine.Vao;
 import eu.tankernn.gameEngine.util.ICamera;
 
 public class SkyboxRenderer {
@@ -34,7 +34,7 @@ public class SkyboxRenderer {
 		skybox.getCubeVao().bind();
 		GL20.glEnableVertexAttribArray(0);
 		bindTextures();
-		RawModel model = skybox.getCubeVao();
+		Vao model = skybox.getCubeVao();
 		model.bind(0);
 		GL11.glDrawElements(GL11.GL_TRIANGLES, model.getIndexCount(), GL11.GL_UNSIGNED_INT, 0);
 		model.unbind(0);

+ 17 - 1
src/main/java/eu/tankernn/gameEngine/renderEngine/water/WaterMaster.java

@@ -5,6 +5,7 @@ import java.util.List;
 
 import org.lwjgl.opengl.GL11;
 import org.lwjgl.opengl.GL30;
+import org.lwjgl.util.vector.Vector3f;
 import org.lwjgl.util.vector.Vector4f;
 
 import eu.tankernn.gameEngine.entities.Camera;
@@ -13,6 +14,7 @@ import eu.tankernn.gameEngine.loader.Loader;
 import eu.tankernn.gameEngine.loader.textures.Texture;
 import eu.tankernn.gameEngine.renderEngine.MasterRenderer;
 import eu.tankernn.gameEngine.renderEngine.Scene;
+import eu.tankernn.gameEngine.util.DistanceSorter;
 import eu.tankernn.gameEngine.util.ICamera;
 
 public class WaterMaster {
@@ -30,7 +32,8 @@ public class WaterMaster {
 	}
 	
 	public void renderBuffers(MasterRenderer renderer, Scene scene) {
-		float waterHeight = waterTiles.get(0).getHeight(); //TODO Using only the first watertile is BAD
+		DistanceSorter.sort(waterTiles, scene.getCamera());
+		float waterHeight = waterTiles.get(0).getHeight();
 		GL11.glEnable(GL30.GL_CLIP_DISTANCE0);
 		
 		// Reflection
@@ -53,6 +56,19 @@ public class WaterMaster {
 		buffers.getReflectionFbo().unbindFrameBuffer();
 	}
 	
+	public boolean isPointUnderWater(Vector3f point) {
+		for (WaterTile tile : waterTiles) {
+			if (point.y < tile.getHeight()) {
+				if (tile.getX() - tile.getSize() <= point.x && point.x <= tile.getX() + tile.getSize()) {
+					if (tile.getZ() - tile.getSize() <= point.z && point.z <= tile.getZ() + tile.getSize()) {
+						return true;
+					}
+				}
+			}
+		}
+		return false;
+	}
+	
 	public void renderWater(Camera camera, List<Light> lights) {
 		waterRenderer.render(waterTiles, camera, lights);
 	}

+ 2 - 2
src/main/java/eu/tankernn/gameEngine/renderEngine/water/WaterRenderer.java

@@ -13,7 +13,7 @@ import eu.tankernn.gameEngine.entities.Light;
 import eu.tankernn.gameEngine.loader.Loader;
 import eu.tankernn.gameEngine.loader.textures.Texture;
 import eu.tankernn.gameEngine.renderEngine.DisplayManager;
-import eu.tankernn.gameEngine.renderEngine.RawModel;
+import eu.tankernn.gameEngine.renderEngine.Vao;
 import eu.tankernn.gameEngine.settings.Settings;
 import eu.tankernn.gameEngine.util.Maths;
 
@@ -24,7 +24,7 @@ public class WaterRenderer {
 	private static final float SHINE_DAMPER = 20.0f;
 	private static final float REFLECTIVITY = 0.5f;
 	
-	private RawModel quad;
+	private Vao quad;
 	private WaterShader shader;
 	private WaterFrameBuffers buffers;
 	

+ 10 - 1
src/main/java/eu/tankernn/gameEngine/renderEngine/water/WaterTile.java

@@ -1,6 +1,10 @@
 package eu.tankernn.gameEngine.renderEngine.water;
 
-public class WaterTile {
+import org.lwjgl.util.vector.Vector3f;
+
+import eu.tankernn.gameEngine.util.IPositionable;
+
+public class WaterTile implements IPositionable {
 	
 	private float height;
 	private float x, z;
@@ -28,5 +32,10 @@ public class WaterTile {
 	public float getSize() {
 		return size;
 	}
+
+	@Override
+	public Vector3f getPosition() {
+		return new Vector3f(x, height, z);
+	}
 	
 }

+ 7 - 2
src/main/java/eu/tankernn/gameEngine/settings/Settings.java

@@ -4,7 +4,7 @@ public class Settings {
 	//Display settings
 	public static final float FOV = 70;
 	public static final float NEAR_PLANE = 0.1f;
-	public static final float FAR_PLANE = 500;
+	public static final float FAR_PLANE = 1000;
 	
 	//Sky color
 	public static final float RED = 0.7f;
@@ -14,5 +14,10 @@ public class Settings {
 	public static final float TERRAIN_SIZE = 800;
 	
 	public static final float ANISOTROPIC_FILTERING_AMOUNT = 4f;
-	public static final int MULTISAMPLING = 0;
+	public static final int MULTISAMPLING = 2;
+	
+	/**
+	 * Maximum number of joints in a skeleton.
+	 */
+	public static final int MAX_JOINTS = 50;
 }

+ 3 - 3
src/main/java/eu/tankernn/gameEngine/terrains/Terrain.java

@@ -9,7 +9,7 @@ import org.lwjgl.util.vector.Vector3f;
 import eu.tankernn.gameEngine.loader.Loader;
 import eu.tankernn.gameEngine.loader.textures.TerrainTexturePack;
 import eu.tankernn.gameEngine.loader.textures.Texture;
-import eu.tankernn.gameEngine.renderEngine.RawModel;
+import eu.tankernn.gameEngine.renderEngine.Vao;
 import eu.tankernn.gameEngine.util.InternalFile;
 import eu.tankernn.gameEngine.util.Maths;
 
@@ -17,7 +17,7 @@ public class Terrain {
 
 	private float x, z;
 	private int gridX, gridZ;
-	private RawModel model;
+	private Vao model;
 	private TerrainTexturePack texturePack;
 	private Texture blendMap;
 
@@ -72,7 +72,7 @@ public class Terrain {
 		return TERRAIN_SIZE;
 	}
 
-	public RawModel getModel() {
+	public Vao getModel() {
 		return model;
 	}
 

+ 2 - 2
src/main/java/eu/tankernn/gameEngine/terrains/TerrainModelData.java

@@ -10,7 +10,7 @@ import javax.imageio.ImageIO;
 import org.lwjgl.util.vector.Vector3f;
 
 import eu.tankernn.gameEngine.loader.Loader;
-import eu.tankernn.gameEngine.renderEngine.RawModel;
+import eu.tankernn.gameEngine.renderEngine.Vao;
 import eu.tankernn.gameEngine.util.InternalFile;
 
 public class TerrainModelData {
@@ -156,7 +156,7 @@ public class TerrainModelData {
 		return generator.generateHeight(x, z);
 	}
 
-	public RawModel getModel(Loader loader) {
+	public Vao getModel(Loader loader) {
 		return loader.loadToVAO(vertices, textureCoords, normals, indices);
 	}
 

+ 2 - 7
src/main/java/eu/tankernn/gameEngine/util/DistanceSorter.java

@@ -1,17 +1,12 @@
 package eu.tankernn.gameEngine.util;
 
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 
 public class DistanceSorter {
 	public static <T extends IPositionable> void sort(List<T> list, IPositionable centerPoint) {
-		Collections.sort(list, new Comparator<T>() {
-			@Override
-			public int compare(T t1, T t2) {
-				return Float.compare(Maths.distanceBetweenPoints(t1.getPosition(), centerPoint.getPosition()),
-						Maths.distanceBetweenPoints(t2.getPosition(), centerPoint.getPosition()));
-			}
+		Collections.sort(list, (t1, t2) -> {
+			return Float.compare(Maths.distanceBetweenPoints(t1.getPosition(), centerPoint.getPosition()), Maths.distanceBetweenPoints(t2.getPosition(), centerPoint.getPosition()));
 		});
 	}
 }

+ 5 - 0
src/main/java/eu/tankernn/gameEngine/util/InternalFile.java

@@ -114,4 +114,9 @@ public class InternalFile {
 		}
 	}
 
+	public String getExtension() {
+		String[] split = getName().split("\\.");
+		return split[split.length - 1];
+	}
+
 }

+ 0 - 14
src/main/java/eu/tankernn/gameEngine/util/Maths.java

@@ -5,8 +5,6 @@ import org.lwjgl.util.vector.Vector2f;
 import org.lwjgl.util.vector.Vector3f;
 import org.lwjgl.util.vector.Vector4f;
 
-import eu.tankernn.gameEngine.entities.Camera;
-
 public class Maths {
 	public static float distanceBetweenPoints(Vector3f pos1, Vector3f pos2) {
 		float baseWidth = pos1.x - pos2.x;
@@ -77,16 +75,4 @@ public class Maths {
 		Matrix4f.scale(new Vector3f(scale, scale, scale), matrix, matrix);
 		return matrix;
 	}
-	
-	public static Matrix4f createViewMatrix(Camera camera) {
-		Matrix4f viewMatrix = new Matrix4f();
-		viewMatrix.setIdentity();
-		Matrix4f.rotate((float) Math.toRadians(camera.getPitch()), new Vector3f(1, 0, 0), viewMatrix, viewMatrix);
-		Matrix4f.rotate((float) Math.toRadians(camera.getYaw()), new Vector3f(0, 1, 0), viewMatrix, viewMatrix);
-		Matrix4f.rotate((float) Math.toRadians(camera.getRoll()), new Vector3f(0, 0, 1), viewMatrix, viewMatrix);
-		Vector3f cameraPos = camera.getPosition();
-		Vector3f negativeCameraPos = new Vector3f(-cameraPos.x, -cameraPos.y, -cameraPos.z);
-		Matrix4f.translate(negativeCameraPos, viewMatrix, viewMatrix);
-		return viewMatrix;
-	}
 }

+ 4 - 7
src/main/java/eu/tankernn/gameEngine/util/MousePicker.java

@@ -37,7 +37,6 @@ public class MousePicker {
 	public MousePicker(Camera cam, Matrix4f projection, List<Entity3D> entities, List<GuiTexture> guis) {
 		camera = cam;
 		projectionMatrix = projection;
-		viewMatrix = camera.getViewMatrix();
 		this.entities = entities;
 		this.guis = guis;
 	}
@@ -69,15 +68,13 @@ public class MousePicker {
 			currentTerrainPoint = null;
 		}
 
-		boolean foundTarget = false;
 		for (Entity3D e : entities) {
-			if (entityInstersect(e) && !foundTarget) {
-				e.setScale(2);
-				foundTarget = true;
-			} else {
-				e.setScale(1);
+			if (entityInstersect(e)) {
+				currentEntity = e;
+				return;
 			}
 		}
+		currentEntity = null;
 	}
 
 	private Vector3f calculateMouseRay() {

+ 88 - 0
src/main/java/eu/tankernn/gameEngine/util/OpenGlUtils.java

@@ -0,0 +1,88 @@
+package eu.tankernn.gameEngine.util;
+
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GL13;
+
+/**
+ * Useful class for updating OpenGL state, such as alpha blending, depth testing, etc.
+ * 
+ * @author Karl
+ *
+ */
+public class OpenGlUtils {
+	
+	private static boolean cullingBackFace = false;
+	private static boolean inWireframe = false;
+	private static boolean isAlphaBlending = false;
+	private static boolean additiveBlending = false;
+	private static boolean antialiasing = false;
+	private static boolean depthTesting = false;
+
+	public static void antialias(boolean enable) {
+		if (enable && !antialiasing) {
+			GL11.glEnable(GL13.GL_MULTISAMPLE);
+			antialiasing = true;
+		} else if (!enable && antialiasing) {
+			GL11.glDisable(GL13.GL_MULTISAMPLE);
+			antialiasing = false;
+		}
+	}
+
+	public static void enableAlphaBlending() {
+		if (!isAlphaBlending) {
+			GL11.glEnable(GL11.GL_BLEND);
+			GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
+			isAlphaBlending = true;
+			additiveBlending = false;
+		}
+	}
+
+	public static void enableAdditiveBlending() {
+		if (!additiveBlending) {
+			GL11.glEnable(GL11.GL_BLEND);
+			GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
+			additiveBlending = true;
+			isAlphaBlending = false;
+		}
+	}
+
+	public static void disableBlending() {
+		if (isAlphaBlending || additiveBlending) {
+			GL11.glDisable(GL11.GL_BLEND);
+			isAlphaBlending = false;
+			additiveBlending = false;
+		}
+	}
+	
+	public static void enableDepthTesting(boolean enable){
+		if(enable && !depthTesting){
+			GL11.glEnable(GL11.GL_DEPTH_TEST);
+			depthTesting = true;
+		}else if(!enable && depthTesting){
+			GL11.glDisable(GL11.GL_DEPTH_TEST);
+			depthTesting = false;
+		}
+	}
+
+	public static void cullBackFaces(boolean cull) {
+		if (cull && !cullingBackFace) {
+			GL11.glEnable(GL11.GL_CULL_FACE);
+			GL11.glCullFace(GL11.GL_BACK);
+			cullingBackFace = true;
+		} else if (!cull && cullingBackFace) {
+			GL11.glDisable(GL11.GL_CULL_FACE);
+			cullingBackFace = false;
+		}
+	}
+
+	public static void goWireframe(boolean goWireframe) {
+		if (goWireframe && !inWireframe) {
+			GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE);
+			inWireframe = true;
+		} else if (!goWireframe && inWireframe) {
+			GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
+			inWireframe = false;
+		}
+	}
+
+}