diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..7b72545
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <excludeFromCompile>
+      <directory url="file://$PROJECT_DIR$/src/main/resources/archetype-resources" includeSubdirectories="true" />
+    </excludeFromCompile>
+    <annotationProcessing>
+      <profile name="Maven default annotation processors profile" enabled="true">
+        <sourceOutputDir name="target/generated-sources/annotations" />
+        <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+        <outputRelativeToContentRoot value="true" />
+        <module name="EPQ 3D renderer" />
+      </profile>
+    </annotationProcessing>
+    <bytecodeTargetLevel>
+      <module name="EPQ 3D renderer" target="11" />
+    </bytecodeTargetLevel>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..aa00ffa
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..712ab9d
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RemoteRepositoriesConfiguration">
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Central Repository" />
+      <option name="url" value="https://repo.maven.apache.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Maven Central repository" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="jboss.community" />
+      <option name="name" value="JBoss Community repository" />
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+    </remote-repository>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/libraries/Maven__com_googlecode_json_simple_json_simple_1_1.xml b/.idea/libraries/Maven__com_googlecode_json_simple_json_simple_1_1.xml
new file mode 100644
index 0000000..ea70fe1
--- /dev/null
+++ b/.idea/libraries/Maven__com_googlecode_json_simple_json_simple_1_1.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: com.googlecode.json-simple:json-simple:1.1">
+    <CLASSES>
+      <root url="jar://$MAVEN_REPOSITORY$/com/googlecode/json-simple/json-simple/1.1/json-simple-1.1.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$MAVEN_REPOSITORY$/com/googlecode/json-simple/json-simple/1.1/json-simple-1.1-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$MAVEN_REPOSITORY$/com/googlecode/json-simple/json-simple/1.1/json-simple-1.1-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..e8d124d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..7130ed8
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/EPQ 3D renderer.iml" filepath="$PROJECT_DIR$/EPQ 3D renderer.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Palette2">
+    <group name="Swing">
+      <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+      </item>
+      <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
+        <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+        <initial-values>
+          <property name="text" value="Button" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="RadioButton" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="CheckBox" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="Label" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+          <preferred-size width="-1" height="20" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+      </item>
+    </group>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/EPQ 3D renderer.iml b/EPQ 3D renderer.iml
new file mode 100644
index 0000000..e9c3f97
--- /dev/null
+++ b/EPQ 3D renderer.iml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_11">
+    <output url="file://$MODULE_DIR$/target/classes" />
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+      <excludeFolder url="file://$MODULE_DIR$/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="opencsv-5.7.1" level="project" />
+    <orderEntry type="library" name="Maven: com.googlecode.json-simple:json-simple:1.1" level="project" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/out/production/EPQ 3D renderer/App.class b/out/production/EPQ 3D renderer/App.class
deleted file mode 100644
index 8648eab..0000000
--- a/out/production/EPQ 3D renderer/App.class
+++ /dev/null
Binary files differ
diff --git a/out/production/EPQ 3D renderer/Face.class b/out/production/EPQ 3D renderer/Face.class
deleted file mode 100644
index b1aba64..0000000
--- a/out/production/EPQ 3D renderer/Face.class
+++ /dev/null
Binary files differ
diff --git a/out/production/EPQ 3D renderer/Matrix.class b/out/production/EPQ 3D renderer/Matrix.class
deleted file mode 100644
index 0702f87..0000000
--- a/out/production/EPQ 3D renderer/Matrix.class
+++ /dev/null
Binary files differ
diff --git a/out/production/EPQ 3D renderer/Object3d.class b/out/production/EPQ 3D renderer/Object3d.class
deleted file mode 100644
index 95c5e2b..0000000
--- a/out/production/EPQ 3D renderer/Object3d.class
+++ /dev/null
Binary files differ
diff --git a/out/production/EPQ 3D renderer/Player.class b/out/production/EPQ 3D renderer/Player.class
deleted file mode 100644
index 2fa269f..0000000
--- a/out/production/EPQ 3D renderer/Player.class
+++ /dev/null
Binary files differ
diff --git a/out/production/EPQ 3D renderer/Point2D.class b/out/production/EPQ 3D renderer/Point2D.class
deleted file mode 100644
index 2ef102e..0000000
--- a/out/production/EPQ 3D renderer/Point2D.class
+++ /dev/null
Binary files differ
diff --git a/out/production/EPQ 3D renderer/Point3D.class b/out/production/EPQ 3D renderer/Point3D.class
deleted file mode 100644
index a62c208..0000000
--- a/out/production/EPQ 3D renderer/Point3D.class
+++ /dev/null
Binary files differ
diff --git a/out/production/EPQ 3D renderer/PointComp.class b/out/production/EPQ 3D renderer/PointComp.class
deleted file mode 100644
index 4cfb75a..0000000
--- a/out/production/EPQ 3D renderer/PointComp.class
+++ /dev/null
Binary files differ
diff --git a/out/production/EPQ 3D renderer/Screen.class b/out/production/EPQ 3D renderer/Screen.class
deleted file mode 100644
index 5a48ebd..0000000
--- a/out/production/EPQ 3D renderer/Screen.class
+++ /dev/null
Binary files differ
diff --git a/out/production/EPQ 3D renderer/Triangle.class b/out/production/EPQ 3D renderer/Triangle.class
deleted file mode 100644
index c4afc42..0000000
--- a/out/production/EPQ 3D renderer/Triangle.class
+++ /dev/null
Binary files differ
diff --git a/out/production/EPQ 3D renderer/Vector3D.class b/out/production/EPQ 3D renderer/Vector3D.class
deleted file mode 100644
index 79370e5..0000000
--- a/out/production/EPQ 3D renderer/Vector3D.class
+++ /dev/null
Binary files differ
diff --git a/src/main/java/App.java b/src/main/java/App.java
deleted file mode 100644
index 2f7f969..0000000
--- a/src/main/java/App.java
+++ /dev/null
@@ -1,113 +0,0 @@
-import javax.imageio.ImageIO;
-import javax.swing.*;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.IOException;
-
-class App {
-    static ObjectCollection mainCollection;
-    private static void initWindow() {
-        // create a window frame and set the title in the toolbar
-        JFrame window = new JFrame("Testing");
-        // when we close the window, stop the app
-        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-        // create the jpanel to draw on.
-        // this also initializes the game loop
-        initObjects();
-        Screen screen = new Screen(mainCollection);
-        // add the jpanel to the window
-        window.add(screen);
-        window.addKeyListener(screen);
-        window.addMouseListener(screen);
-        window.addMouseMotionListener(screen);
-        // don't allow the user to resize the window
-        window.setResizable(false);
-        // fit the window size around the components (just our jpanel).
-        // pack() should be called after setResizable() to avoid issues on some platforms
-        window.pack();
-        // open window in the center of the screen
-        window.setLocationRelativeTo(null);
-        // display the window
-        window.setVisible(true);
-    }
-
-    public static void main(String[] args) {
-        // invokeLater() is used here to prevent our graphics processing from
-        // blocking the GUI. https://stackoverflow.com/a/22534931/4655368
-        // this is a lot of boilerplate code that you shouldn't be too concerned about.
-        // just know that when main runs it will call initWindow() once.
-        SwingUtilities.invokeLater(App::initWindow);
-    }
-
-    public static void initObjects(){
-        mainCollection = new ObjectCollection();
-
-        BufferedImage testTexture;
-        try {
-            //testTexture = ImageIO.read(new File("/home/cory/Screenshot from 2022-06-06 18-52-12.png"));
-            testTexture = ImageIO.read(new File("/home/cory/Screenshot from 2022-09-26 13-05-40.png"));
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        mainCollection.addObject(new Object3d(new PointComp[]{
-                new PointComp(-30,-10,-10),
-                new PointComp(-30,-10,10),
-                new PointComp(-30,10,-10),
-                new PointComp(-30,10,10),
-                new PointComp(-10,-10,-10),
-                new PointComp(-10,-10,10),
-                new PointComp(-10,10,-10),
-                new PointComp(-10,10,10)
-
-        }, new int[][]{
-                {0,2,3,1},
-                {4,5,7,6},
-                {0,1,5,4},
-                {1,3,7,5},
-                {3,2,6,7},
-                {2,0,4,6},
-        }, new Point2D[][]{
-                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
-                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
-                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
-                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
-                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
-                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
-                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
-        },
-                true,
-                testTexture,
-                true));
-//        mainCollection.addObject(new Object3d(new PointComp[]{
-//                new PointComp(-10,10,10),
-//                new PointComp(-10,-10,10),
-//                new PointComp(10,10,10),
-//                new PointComp(10,-10,10),
-//                new PointComp(0,0,24)
-//        }, new int[][]{
-//                {0,2,3,1},
-//                {1,0,4},
-//                {0,2,4},
-//                {2,3,4},
-//                {3,1,4}
-//        }, true));
-        mainCollection.addObject(new Object3d(new PointComp[]{
-                new PointComp(0,0,0),
-                new PointComp(20,0,0),
-                new PointComp(0,10,0),
-                new PointComp(0,0,10),
-        }, new int[][]{
-                {0,1,2},
-                {0,2,3},
-                {0,3,1},
-                {2,1,3}
-        }, new Point2D[][]{
-                {new Point2D(0,0), new Point2D(100,0), new Point2D(0,100)},
-                {new Point2D(0,0), new Point2D(100,0), new Point2D(0,100)},
-                {new Point2D(0,0), new Point2D(100,0), new Point2D(0,100)},
-        },
-                true,
-                testTexture,
-                true));
-    }
-}
diff --git a/src/main/java/Face.java b/src/main/java/Face.java
deleted file mode 100644
index d14427b..0000000
--- a/src/main/java/Face.java
+++ /dev/null
@@ -1,273 +0,0 @@
-import java.awt.image.BufferedImage;
-import java.util.ArrayList;
-
-public class Face {
-    public PointComp[] points;
-    public Point2D[] UVPoints;
-    public Vector3D normal;
-    public Triangle[] tris;
-    public boolean hasEdges;
-    public BufferedImage texture;
-    public Matrix[] perspectiveMappingMatrices = new Matrix[]{null, null, null, null, null, null};
-    public boolean isInitialised;
-    // fixed face
-    public Face fixedFace;
-    // working variables
-    private final Vector3D traVec = new Vector3D(0,0, 0);
-    // private final Vector2D scaVec = new Vector2D(0,0);
-
-    private final Vector3D real01Vec = new Vector3D(0,0,0);
-    private final Vector2D UV01Vec = new Vector2D(0,0);
-    private final Vector3D real02Vec = new Vector3D(0,0,0);
-    private final Vector2D UV02Vec = new Vector2D(0,0);
-    double[] result;
-    public void initialise(){
-        calculateNormal();
-        separateTris();
-        // fixedFace is a constant object which is used temporarily when the face intersects with the camera,
-        // and thus needs to be sliced.
-        fixedFace = new Face();
-        fixedFace.hasEdges = hasEdges;
-        fixedFace.texture = texture;
-        fixedFace.normal = normal;
-        fixedFace.isInitialised = true;
-        // the fixed face inherits the perspective mapping matrices from the true face. This is because the transforms
-        // stay the same, just the edge bounds of the face have changed.
-        fixedFace.perspectiveMappingMatrices = perspectiveMappingMatrices;
-        isInitialised = true;
-    }
-
-    public void invalidate(){
-        for (Triangle tri:
-             tris) {
-            tri.invalidate();
-        }
-    }
-    public int draw(BufferedImage img, BufferedImage zBuf, BufferedImage debugImg, Matrix camMatrix, double FPDis, int scrX, int scrY){
-        if(!isInitialised){
-            throw new RuntimeException("Face not initialised");
-        }
-        // check for backface culling has been done previously.
-        // initialise points
-        int numberOfPixels = 0;
-        boolean valid = applyPointTransforms(camMatrix, FPDis, scrX, scrY);
-        //bakePerspectiveMatrices();
-
-        if (valid) {
-            drawTris(img, zBuf, FPDis, scrX, scrY);}
-        else {
-            ArrayList<PointComp> newPoints = new ArrayList<>();
-            // if there are points behind the camera, loop through all the points and interpolate a point that is.
-            // The perspective mapping matrix is calculated beforehand, so we don't need to move UVS
-            PointComp lastPoint = points[points.length - 1];
-            boolean lastValid = lastPoint.getRotatedPoint().z > 0.1;
-            boolean thisValid;
-            for (PointComp point:
-                    points) {
-                thisValid = point.getRotatedPoint().z > 0.1;
-                // We need to do different things depending on whether the previous point was also a valid point, or not.
-                // first - if only 1 of the last point or this point were valid,
-                // interpolate between them to get the point at the screen. (XOR)
-                if(lastValid ^ thisValid){
-                    // solving for z = 0.1 for the line between thisPoint and lastPoint,
-                    // separately in the xz and yz planes.
-                    double gradX = (point.getRotatedPoint().z - lastPoint.getRotatedPoint().z) /
-                            (point.getRotatedPoint().x - lastPoint.getRotatedPoint().x);
-                    double gradY = (point.getRotatedPoint().z - lastPoint.getRotatedPoint().z) /
-                            (point.getRotatedPoint().y - lastPoint.getRotatedPoint().y);
-
-                    newPoints.add(new PointComp(
-                            (0.1+gradX*point.getRotatedPoint().x-point.getRotatedPoint().z)/gradX,
-                            (0.1+gradY*point.getRotatedPoint().y-point.getRotatedPoint().z)/gradY,
-                            0.1));
-                    if(!Double.isFinite(gradX)){
-                        newPoints.get(newPoints.size() - 1).point.x = point.getRotatedPoint().x;}
-                    if(!Double.isFinite(gradY)){
-                        newPoints.get(newPoints.size() - 1).point.y = point.getRotatedPoint().y;}
-                }
-                // finally - if the current point is valid, then add it to the list
-                if(thisValid){
-                    newPoints.add(new PointComp(
-                            point.getRotatedPoint().x,
-                            point.getRotatedPoint().y,
-                            point.getRotatedPoint().z));
-                }
-                lastPoint = point;
-                lastValid = thisValid;
-            }
-            // there must be at least 3 points in the face for it to be drawn successfully
-            if(newPoints.size() >= 3) {
-                // finished fixing points, now we need to create a new face consisting of those points.
-                fixedFace.points = newPoints.toArray(new PointComp[0]);
-                fixedFace.separateTris();
-                // invalidate all the points so they are actually calculated
-                for (PointComp point :
-                        newPoints) {
-                    point.invalidate();
-                }
-                Matrix identMat = new Matrix(3,3);
-                // we use an identity matrix because the points of the fixed face are in camera coordinates already.
-                // we just need the projected 2d point.
-                identMat.setItems(new double[][]{
-                        {1,0,0},
-                        {0,1,0},
-                        {0,0,1}
-                });
-                fixedFace.applyPointTransforms(identMat, FPDis, scrX, scrY);
-                fixedFace.drawTris(img, zBuf, FPDis, scrX, scrY);
-            }
-        }
-        return numberOfPixels;
-    }
-    public void drawTris(BufferedImage img, BufferedImage zBuf, double FPDis, int scrX, int scrY) {
-        for (Triangle tri :
-                tris) {
-            tri.draw(img, zBuf, FPDis, scrX, scrY);
-        }
-    }
-    public boolean applyPointTransforms(Matrix camMatrix, double FPDis, int scrX, int scrY){
-        boolean valid = true;
-        for (PointComp point:
-                points) {
-            point.setRotatedPoint(camMatrix);
-            // if any points are behind the camera, we will need to handle it differently.
-            if(point.getRotatedPoint().z < 0.1){
-                valid = false;}
-            // only worth calculating the projected point if they are all valid
-            if(valid){
-                point.setProjectedPoint(FPDis, scrX, scrY);}
-        }
-        return valid;
-    }
-    public void separateTris(){
-        Triangle[] newTris = new Triangle[points.length - 2];
-        for(int i = 0; i< newTris.length; i+=1){
-            newTris[i] = new Triangle(
-                    points[0].getProjectedPoint(),
-                    points[i+1].getProjectedPoint(),
-                    points[i+2].getProjectedPoint(),
-                    new boolean[]{hasEdges, hasEdges, hasEdges},
-                    texture, perspectiveMappingMatrices[i]);
-        }
-        tris = newTris;
-    }
-    public void calculateNormal(){
-        // too many new variables
-        Point3D point0 = points[0].point;
-        Point3D point1 = points[1].point;
-        Point3D point2;
-        Vector3D vec1 = new Vector3D(point1.x - point0.x, point1.y - point0.y, point1.z - point0.z);
-        Vector3D vec2 = new Vector3D(0,0,0); // initialisation otherwise intellij gets mad
-        // find a vector which is not inline with other vectors
-        boolean valid = false; int i = 2;
-        while(!valid && i < points.length) {
-            point2 = points[i].point;
-            vec2 = new Vector3D(point2.x - point0.x, point2.y - point0.y, point2.z - point0.z);
-            double angle = Math.abs(vec1.angleTo(vec2));
-            if(angle > 0.1 && angle < 2*Math.PI - 0.1){
-                //  if the angle between the vectors is between a threshold, the two vectors are valid.
-                // else, calculate the second vector using a different set of points.
-                valid = true;
-            }}
-        if(!valid){throw new RuntimeException("Could not calculate normal of face");}
-        normal = vec1.cross(vec2);
-    }
-//    private void bakePerspectiveMatrices() {
-//        // one mapping matrix for each triangle
-//        // to achieve perspective mapping, we need to convert from the 2d screen position, to the 3d world position by
-//        // reverse projecting and interpolating in the triangle - this gives camera coordinates.
-//        // next, we need to use the inverse camera matrix to convert from camera coordinates to world coordinates.
-//        // then, we can use the perspective mapping matrix unique and baked to each triangle on each face to convert from those world coordinates, into uv coordinates.
-//
-//
-//
-//        real01Vec.createFrom2Points(points[0].getRotatedPoint(), points[1].getRotatedPoint());
-//        real02Vec.createFrom2Points(points[0].getRotatedPoint(), points[2].getRotatedPoint());
-//        // scaVec.createFrom2Points(points[0].getProjectedPoint(), points[1].getProjectedPoint());
-//        UV01Vec.createFrom2Points(UVPoints[0], UVPoints[1]);
-//        UV02Vec.createFrom2Points(UVPoints[0], UVPoints[2]);
-//        // it must remain as the same object so pointers elsewhere still work.
-//        // invert x and y coordinates because in rotated coordinates, they are the wrong way round.
-//        perspectiveMappingMatrix.setItems(new double[][]{
-//                {0,1,0,0},
-//                {1,0,0,0},
-//                {0,0,1,0},
-//                {0,0,0,1},
-//        });
-//
-//        traVec.createFrom2Points(new Point3D(UVPoints[0].y, UVPoints[0].x, 0), points[0].getRotatedPoint());
-//        Matrix tMat = new Matrix(4, 4);
-////        tMat.setItems(new double[][]{
-////                {1, 0, 0, traVec.x},
-////                {0, 1, 0, traVec.y},
-////                {0, 0, 1, traVec.z},
-////                {0, 0, 0, 1},
-////        });
-//        tMat.setItems(new double[][]{
-//                {1, 0, 0, 0},
-//                {0, 1, 0, 0},
-//                {0, 0, 1, points[0].getRotatedPoint().z},
-//                {0, 0, 0, 1},
-//        });
-//        double scale = 0.1;//(real01Vec.getLength() / UV01Vec.getLength());
-//        Matrix scaMat = new Matrix(4, 4);
-//        scaMat.setItems(new double[][]{
-//                {scale, 0,     0,     0},
-//                {0,     scale, 0,     0},
-//                {0,     0,     scale, 0},
-//                {0,     0,     0,     1}
-//        });
-//        // find z rotation and define matrix
-//        double zAng = new Vector2D(real01Vec.x, real01Vec.y).angleTo(UV01Vec);
-//        Matrix zMat = new Matrix(4, 4);
-//        zMat.setItems(new double[][]{
-//                {Math.cos(zAng),  Math.sin(zAng), 0, 0},
-//                {-Math.sin(zAng), Math.cos(zAng), 0, 0},
-//                {0, 0, 1, 0},
-//                {0, 0, 0, 1}}
-//        );
-//        // rotate "real" vectors using the Z matrix
-//        result = zMat.multiplyPoint3raw(real01Vec.x, real01Vec.y, real01Vec.z);
-//        real01Vec.x = result[0];real01Vec.y = result[1];real01Vec.z = result[2];
-//        result = zMat.multiplyPoint3raw(real02Vec.x, real02Vec.y, real02Vec.z);
-//        real02Vec.x = result[0];real02Vec.y = result[1];real02Vec.z = result[2];
-//        // invert the Z matrix (todo cleanup)
-//        zMat.setItems(new double[][]{
-//                {Math.cos(-zAng),  Math.sin(-zAng), 0, 0},
-//                {-Math.sin(-zAng), Math.cos(-zAng), 0, 0},
-//                {0, 0, 1, 0},
-//                {0, 0, 0, 1}}
-//        );
-//        // find Y rotation and define matrix
-//        double yAng = new Vector2D(real01Vec.x, real01Vec.z).angleTo(new Vector2D(UV01Vec.x, 0));
-//        Matrix yMat = new Matrix(4, 4);
-//        yMat.setItems(new double[][]{
-//                {Math.cos(yAng), 0, -Math.sin(yAng), 0},
-//                {0,              1, 0, 0},
-//                {Math.sin(yAng), 0, Math.cos(yAng), 0},
-//                {0, 0, 0, 1}}
-//        );
-//        result = yMat.multiplyPoint3raw(real02Vec.x, real02Vec.y, real02Vec.z);
-//        real02Vec.x = result[0];real02Vec.y = result[1];real02Vec.z = result[2];
-//        yMat.setItems(new double[][]{
-//                {Math.cos(-yAng), 0, -Math.sin(-yAng), 0},
-//                {0,              1, 0, 0},
-//                {Math.sin(-yAng), 0, Math.cos(-yAng), 0},
-//                {0, 0, 0, 1}}
-//        );
-//        double xAng = new Vector2D(real02Vec.y, real02Vec.z).angleTo(new Vector2D(UV01Vec.x, 0)); // this is fine
-//        Matrix xMat = new Matrix(4, 4);
-//        xMat.setItems(new double[][]{
-//                {1, 0, 0, 0},
-//                {0, Math.cos(-xAng), Math.sin(-xAng), 0},
-//                {0, -Math.sin(-xAng), Math.cos(-xAng), 0},
-//                {0, 0, 0, 1}}
-//        );
-//        // Matrix that returns the position on a texture from 3d camera space on the face of an object
-//        // we already know the Z depth because of calculations for Z buffers.
-//        //perspectiveMappingMatrix.multiply(scaMat);
-//        perspectiveMappingMatrix.multiply(scaMat.multiplyGetResult(tMat));
-//        //perspectiveMappingMatrix.multiply((scaMat.multiplyGetResult(xMat.multiplyGetResult(yMat.multiplyGetResult(zMat.multiplyGetResult(tMat))))));
-//        // perspectiveMappingMatrix.multiply(scaMat.multiplyGetResult(zMat.multiplyGetResult(yMat.multiplyGetResult(xMat.multiplyGetResult(tMat)))));
-//    }
-}
diff --git a/src/main/java/JsonWriter.java b/src/main/java/JsonWriter.java
deleted file mode 100644
index faeaf33..0000000
--- a/src/main/java/JsonWriter.java
+++ /dev/null
@@ -1,33 +0,0 @@
-import org.codehaus.jackson.map.ObjectMapper;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-public class JsonWriter {
-
-    public static void main(String[] args) {
-
-        Country countryObj = new Country();
-        countryObj.name = "India";
-        countryObj.population = 1000000;
-
-        List<String> listOfStates = new ArrayList<String>();
-        listOfStates.add("Madhya Pradesh");
-        listOfStates.add("Maharastra");
-        listOfStates.add("Rajasthan");
-
-        countryObj.states = listOfStates ;
-        ObjectMapper mapper = new ObjectMapper();
-
-        try {
-
-            // Writing to a file
-            mapper.writeValue(new File("c:\\country.json"), countryObj );
-
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/Line2d.java b/src/main/java/Line2d.java
deleted file mode 100644
index 3e03428..0000000
--- a/src/main/java/Line2d.java
+++ /dev/null
@@ -1,138 +0,0 @@
-import java.awt.*;
-import java.awt.image.BufferedImage;
-
-// handles line and line drawing
-public class Line2d {
-    public Point2D point1;
-    public Point2D point2;
-
-    public boolean isDrawn;
-
-    // initializer variables
-    boolean is_initialised =  false;
-    private Point2D realPoint1; private Point2D realPoint2;
-    private int dx;
-    private int dy; private int sDy;
-    private char iterator;
-    private Point2D xy;
-    int[] returnVal = new int[]{0,0};
-    private double gradient; // z gradient of the line in terms of X
-
-    // drawing progress variables
-    private int iteratorVal;
-    private int D;
-    public Line2d(Point2D _point1, Point2D _point2, boolean _isDrawn){
-        point1 = _point1;
-        point2 = _point2;
-        isDrawn = _isDrawn;
-    }
-
-    /**
-     * Draws onto a given image
-     * @param img the image to draw onto
-     */
-    public void draw(BufferedImage img){
-        if (!is_initialised){initialise();}
-        if (iterator == 'x'){
-            for (int i = 0; i <= dx; i++){
-                nextPix();
-                if(returnVal[0] >= 0 && returnVal[0] < img.getWidth() && returnVal[1] >= 0 && returnVal[1] < img.getHeight()) { // check if drawing in bounds
-                    img.setRGB(returnVal[0], returnVal[1], Color.HSBtoRGB(1, 1, 1));
-                }
-
-            }
-        } else {
-            for (int i = 0; i <= dy; i++){
-                nextPix();
-                if(returnVal[0] >= 0 && returnVal[0] < img.getWidth() && returnVal[1] >= 0 && returnVal[1] < img.getHeight()) { // check if drawing in bounds
-                    img.setRGB(returnVal[0], returnVal[1], Color.HSBtoRGB(.5f, 1, 1));
-                }
-            }
-        }
-        is_initialised = false;
-        /*
-         debug - draws the start and end of each line in white and green
-         img.setRGB(realPoint1.intX(), realPoint1.intY(), Color.blue.getRGB());
-         img.setRGB(realPoint2.intX(), realPoint2.intY(), Color.green.getRGB());
-        */
-    }
-
-    /**
-     * Performs the initial calculations required to draw the line
-     * Is automatically called whenever nextPix() or draw() are called when initialise() has not run.
-     */
-    public void initialise(){
-        // initialise brensenham algorithm
-        if (point2.x > point1.x) {
-            realPoint1 = point1;
-            realPoint2 = point2;
-        } else {  // if dx is less than zero, swap the points around
-            realPoint1 = point2;
-            realPoint2 = point1;
-        }
-        dx = realPoint2.x - realPoint1.x;
-        dy = realPoint2.y - realPoint1.y;
-        sDy = (int) Math.signum(dy); dy = Math.abs(dy);
-
-        xy = new Point2D(realPoint1.x,realPoint1.y);  // starting point
-
-        // check if dy is greater than or less than dx
-        if (dy < dx){
-            iterator = 'x';
-            D = (2*dy) - dx;
-        }else{
-            iterator='y';
-            D = (2*dx) - dy;
-        }
-        iteratorVal = 0;
-
-        // init other variables
-        // dz / dx
-        gradient = (realPoint2.z-realPoint1.z) /
-                (realPoint2.x - realPoint1.x);
-        is_initialised = true;
-    }
-
-    /**
-     * @return the x and y coordinate of the next pixel in the line.
-     */
-    public int[] nextPix(){
-        if(!is_initialised){initialise();}
-        returnVal[0] = xy.x;
-        returnVal[1] = xy.y;
-        if (iterator=='x' && iteratorVal < dx && iteratorVal != -1){
-            if (D > 0) {
-                D += 2 * (dy - dx);
-                xy.y += sDy;
-            } else {
-                D += 2*dy;
-            }
-            xy.x += 1; // the line is always drawn left to right
-        } else if(iterator =='y' && iteratorVal < dy && iteratorVal != -1) {
-            if (D > 0) {
-                D += 2 * (dx-dy);
-                xy.x += 1; // the line is always drawn left to right
-            } else {
-                D += 2*dx;
-            }
-            xy.y += sDy;
-
-        }
-        else if(iteratorVal != -1) {
-            iteratorVal = -1;
-            returnVal[0] = realPoint2.x;
-            returnVal[1] = realPoint2.y;
-            return returnVal;
-        }
-        else {
-            is_initialised = false;
-            throw new RuntimeException("Accessed too many line pixels");
-        }
-        iteratorVal += 1;
-        return returnVal;
-    }
-    public double getZVal(int x){
-        return realPoint1.z + (gradient * (x - realPoint1.x));
-    }
-    }
-
diff --git a/src/main/java/Matrix.java b/src/main/java/Matrix.java
deleted file mode 100644
index c63ac19..0000000
--- a/src/main/java/Matrix.java
+++ /dev/null
@@ -1,100 +0,0 @@
-public class Matrix {
-    protected int x;
-    protected int y;
-    private double[][] items;
-
-    public Matrix(int _x, int _y){
-        x = _x; y= _y;
-        items = new double[y][x];
-    }
-    public double getItem(int _x, int _y){
-        return items[_y][_x];
-    }
-    public void setItem(int _x, int _y, double val){
-        items[_y][_x] = val;
-    }
-    public double[][] getItems() {
-        return items;
-    }
-    public void setItems(double[][] newItems){
-        items = newItems;
-    }
-    public Matrix multiplyGetResult(Matrix multiplier) {
-        Matrix result = new Matrix(multiplier.x, this.y);
-        double newItem;
-        if(x== multiplier.y){
-            for(int rx = 0; rx< result.x; rx+=1){
-                for(int ry = 0; ry< result.y; ry+=1){
-                    newItem = 0;
-                    for(int i = 0; i<x; i+=1){
-                        newItem += this.getItem(i, ry)* multiplier.getItem(rx, i);
-                    }
-                    result.setItem(rx, ry, newItem);
-            }}
-        } else {
-            throw new RuntimeException("wrong dimensions");
-        }
-        return result;
-    }
-    public void multiply(Matrix multiplier) {
-        Matrix result = new Matrix(multiplier.x, this.y);
-        double newItem;
-        if(x== multiplier.y){
-            for(int rx = 0; rx< result.x; rx+=1){
-                for(int ry = 0; ry< result.y; ry+=1){
-                    newItem = 0;
-                    for(int i = 0; i<x; i+=1){
-                        newItem += this.getItem(i, ry)* multiplier.getItem(rx, i);
-                    }
-                    result.setItem(rx, ry, newItem);
-                }}
-        } else {
-            throw new RuntimeException("wrong dimensions");
-        }
-        setItems(result.getItems());
-    }
-    public void multiplyPoint3to(Point3D point, Point3D result) {
-        if(x==3){
-            result.x = point.x * getItem(0,0) + point.y* getItem(1,0) + point.z* getItem(2,0);
-            result.y = point.x * getItem(0,1) + point.y* getItem(1,1) + point.z* getItem(2,1);
-            result.z = point.x * getItem(0,2) + point.y* getItem(1,2) + point.z* getItem(2,2);
-        } else if(x == 4){
-            result.x = point.x * getItem(0,0) + point.y* getItem(1,0) + point.z* getItem(2,0) + getItem(3,0);
-            result.y = point.x * getItem(0,1) + point.y* getItem(1,1) + point.z* getItem(2,1) + getItem(3,1);
-            result.z = point.x * getItem(0,2) + point.y* getItem(1,2) + point.z* getItem(2,2) + getItem(3,2);
-        } else {throw new RuntimeException("wrong-dimensions");}
-    }
-    public double[] multiplyPoint3raw(double _x, double _y, double _z) {
-        double[] result = new double[3];
-        if(x ==3){
-            result[0] = _x * getItem(0,0) + _y * getItem(1,0) + _z * getItem(2,0);
-            result[1] = _x * getItem(0,1) + _y * getItem(1,1) + _z * getItem(2,1);
-            result[2] = _x * getItem(0,2) + _y * getItem(1,2) + _z * getItem(2,2);
-        } else if(x == 4){
-            result[0] = _x * getItem(0,0) + _y * getItem(1,0) + _z * getItem(2,0) + getItem(3,0);
-            result[1] = _x * getItem(0,1) + _y * getItem(1,1) + _z * getItem(2,1) + getItem(3,1);
-            result[2] = _x * getItem(0,2) + _y * getItem(1,2) + _z * getItem(2,2) + getItem(3,2);
-        } else {throw new RuntimeException("wrong-dimensions");}
-        return result;
-    }
-    public void multiplyPoint2to(Point2D point, Point2D result) {
-        if(x==2){
-            result.x = (int)(point.x * getItem(0,0) + point.y* getItem(1,0));
-            result.y = (int)(point.x * getItem(0,1) + point.y* getItem(1,1));
-        } else if(x == 3){
-            result.x = (int)(point.x * getItem(0,0) + point.y* getItem(1,0) + getItem(2,0));
-            result.y = (int)(point.x * getItem(0,1) + point.y* getItem(1,1) + getItem(2,1));
-        } else {throw new RuntimeException("wrong-dimensions");}
-    }
-    public double[] multiplyPoint2raw(double px, double py) {
-        double[] result = new double[2];
-        if(x==2){
-            result[0] = (int)(px * getItem(0,0) + py* getItem(1,0));
-            result[1] = (int)(px * getItem(0,1) + py* getItem(1,1));
-        } else if(x==3){
-            result[0] = (int)(px * getItem(0,0) + py* getItem(1,0) + getItem(2,0));
-            result[1] = (int)(px * getItem(0,1) + py* getItem(1,1) + getItem(2,1));
-        } else {throw new RuntimeException("wrong-dimensions");}
-        return result;
-    }
-}
diff --git a/src/main/java/Object3d.java b/src/main/java/Object3d.java
deleted file mode 100644
index e6891dc..0000000
--- a/src/main/java/Object3d.java
+++ /dev/null
@@ -1,96 +0,0 @@
-import java.awt.image.BufferedImage;
-
-public class Object3d {
-    public PointComp[] points;
-    public int[][] faceList;
-    public Point2D[][] uvPoints;
-
-    public Face[] faces;
-    public Point3D boundingSphereC;
-    public double boundingSphereR;
-
-    public boolean hasEdges;
-    public BufferedImage texture;
-
-    public Object3d(PointComp[] _points, int[][] _faceList, Point2D[][] _uvPoints, boolean _hasEdges, BufferedImage _texture, boolean initialise){
-        points = _points;
-        faceList = _faceList;
-        uvPoints = _uvPoints;
-        hasEdges = _hasEdges;
-        texture = _texture;
-        if(initialise){initialise();}
-    }
-    public void invalidate(){
-        for (Face face:
-             faces) {
-            face.invalidate();
-        }
-    }
-    public void draw(BufferedImage img, BufferedImage zBuf, BufferedImage debugImg, Matrix camMatrix, double FPDis, int scrX, int scrY, Point3D playerPos){
-        int iterator = 0;
-        for (Face face:
-             faces) {
-            // check whether faces are pointing towards or away from the camera
-
-            Vector3D camVec = new Vector3D(0,0,0);
-            camVec.createFrom2Points(playerPos, face.points[0].point);
-            int numberOfPixels = 0;
-            if(face.normal.angleTo(camVec) <= Math.PI/2-0.01){
-                numberOfPixels = face.draw(img, zBuf, debugImg, camMatrix, FPDis, scrX, scrY);
-            }
-            debugImg.getGraphics().drawString(iterator + ": " +numberOfPixels , 10, 20 + 10*iterator);
-            iterator += 1;
-        }
-    }
-    public void initialise(){
-        // init faces
-        faces = new Face[faceList.length];
-        for (int face = 0; face < faceList.length; face+= 1) {
-            faces[face] = new Face();
-            faces[face].points = new PointComp[faceList[face].length];
-            for (int point = 0; point < faceList[face].length; point += 1){
-                faces[face].points[point] = points[faceList[face][point]];
-            }
-            faces[face].hasEdges = hasEdges;
-            faces[face].texture = texture;
-            faces[face].UVPoints = uvPoints[0];
-            faces[face].initialise();
-        }
-        // init bounding sphere
-        double distance = 0;
-        double newDis;
-        Point3D pointA = points[0].point;
-        Point3D pointB = points[0].point;
-        // todo - maybe use some vector classes?7
-        // i can't be bothered
-        for (int i = 1; i < points.length; i+=1) {
-            newDis = Math.pow(points[0].point.x - points[i].point.x, 2) +
-                    Math.pow(points[0].point.y - points[i].point.y, 2) +
-                    Math.pow(points[0].point.z - points[i].point.z, 2);
-            if (newDis >= distance){
-                pointA = points[i].point;
-                distance = newDis;}}
-        for (PointComp point : points) {
-            newDis = Math.pow(pointA.x - point.point.x, 2) +
-                    Math.pow(pointA.y - point.point.y, 2) +
-                    Math.pow(pointA.z - point.point.z, 2);
-            if (newDis >= distance){
-                pointB = point.point;
-                distance = newDis;}}
-        boundingSphereC = new Point3D(
-                (pointA.x + pointB.x) / 2,
-                (pointA.y + pointB.y)/2,
-                (pointA.z + pointB.z)/2);
-        boundingSphereR = Math.sqrt(distance)/2;
-        for (PointComp point:
-                points) {
-            distance = Math.sqrt(
-                    Math.pow(boundingSphereC.x - point.point.x, 2)+
-                    Math.pow(boundingSphereC.y - point.point.y, 2)+
-                    Math.pow(boundingSphereC.z - point.point.z, 2));
-            if(distance > boundingSphereR){
-                boundingSphereR = distance;
-            }
-        }
-    }
-}
diff --git a/src/main/java/ObjectCollection.java b/src/main/java/ObjectCollection.java
deleted file mode 100644
index db9bf6c..0000000
--- a/src/main/java/ObjectCollection.java
+++ /dev/null
@@ -1,129 +0,0 @@
-import java.awt.*;
-import java.awt.image.BufferedImage;
-import java.util.ArrayList;
-import java.util.Objects;
-
-// stores both objects and other object collections
-public class ObjectCollection {
-    public ArrayList<PointComp> points = new ArrayList<PointComp>();
-    private Object3d collectionObject;
-    public double boundingSphereR;
-    public Point3D boundingSphereC;
-
-
-    public ArrayList<ObjectCollection> subCollections = new ArrayList<ObjectCollection>();
-    public ArrayList<Object3d> objects = new ArrayList<Object3d>();
-
-    public void invalidate(boolean invalidatePoints){
-        // the first level of object collections should contain all the points for the sublevels.
-        // this means that we only need to invalidate them at the top level
-        if(invalidatePoints){for (PointComp point:
-             points) {
-            point.invalidate();
-        }}
-        for(ObjectCollection subCollection:
-        subCollections){
-            subCollection.invalidate(false);
-        }
-        for(Object3d object:
-        objects){
-            object.invalidate();
-        }
-    }
-
-    public void draw(BufferedImage img, BufferedImage zBuf, BufferedImage debugImg, Matrix camMatrix, Point3D playerPos, Plane[] frustumPlanes, double FPDis, int scrX, int scrY){
-        for (Object3d object:
-             objects) {
-            boolean draw = true;
-            int i = 0;
-            for (Plane plane:
-                 frustumPlanes) {
-                    debugImg.getGraphics().drawString(
-                            "Dis: " + String.format("%.1f", plane.getDistance(object.boundingSphereC)), 500, 10 + 20*i);
-                if(plane.getDistance(object.boundingSphereC) < -object.boundingSphereR){
-                    draw = false;
-                    break;}
-                i += 1;
-            }
-            if(draw){object.draw(img, zBuf, debugImg, camMatrix, FPDis, scrX, scrY, playerPos);}
-        }
-        // todo check for frustum culling
-        for(ObjectCollection collection:
-        subCollections){
-            boolean draw = true;
-            int i = 0;
-            for (Plane plane:
-                    frustumPlanes) {
-                debugImg.getGraphics().drawString(
-                        "Dis: " + String.format("%.1f", plane.getDistance(collection.boundingSphereC)), 500, 10 + 20*i);
-                if(plane.getDistance(collection.boundingSphereC) < -collection.boundingSphereR){
-                    draw = false;
-                    break;}
-                i += 1;
-            }
-            collection.draw(img, zBuf, debugImg, camMatrix, playerPos, frustumPlanes, FPDis, scrX, scrY);
-        }
-    }
-    public void addCollection(int[] pointList){
-        subCollections.add(new ObjectCollection());
-    }
-    // making sure that all the point indices make sense might be a nightmare but ehh
-    public void addObject(Object3d object){
-        PointComp[] newpoints = object.points;
-        objects.add(object);
-        // add the new object
-
-        // merge lists
-        for (PointComp newpoint : newpoints) {
-            boolean found = false;
-            // find out if any of the new points already exist in the list
-            for (PointComp point : points) {
-                if (newpoint == point) {
-                    found = true;
-                    break;
-                }
-            }
-            if (!found) {
-                points.add(newpoint);
-            }
-        }
-    }
-    public void initialise(){
-        // init bounding sphere
-        double distance = 0;
-        double newDis;
-        Point3D pointA = points.get(0).point;
-        Point3D pointB = points.get(0).point;
-        // todo - maybe use some vector classes?
-        for (int i = 1; i < points.size(); i+=1) {
-            newDis = Math.pow(points.get(0).point.x - points.get(i).point.x, 2) +
-                    Math.pow(points.get(0).point.y - points.get(i).point.y, 2) +
-                    Math.pow(points.get(0).point.z - points.get(i).point.z, 2);
-            if (newDis >= distance){
-                pointA = points.get(i).point;
-                distance = newDis;}}
-        for (PointComp point : points) {
-            newDis = Math.pow(pointA.x - point.point.x, 2) +
-                    Math.pow(pointA.y - point.point.y, 2) +
-                    Math.pow(pointA.z - point.point.z, 2);
-            if (newDis >= distance){
-                pointB = point.point;
-                distance = newDis;}}
-        boundingSphereC = new Point3D(
-                (pointA.x + pointB.x) / 2,
-                (pointA.y + pointB.y)/2,
-                (pointA.z + pointB.z)/2);
-        boundingSphereR = Math.sqrt(distance)/2;
-        boolean valid = false;
-        for (PointComp point:
-                points) {
-            distance = Math.sqrt(
-                    Math.pow(boundingSphereC.x - point.point.x, 2)+
-                            Math.pow(boundingSphereC.y - point.point.y, 2)+
-                            Math.pow(boundingSphereC.z - point.point.z, 2));
-            if(distance > boundingSphereR){
-                boundingSphereR = distance;
-            }
-        }
-    }
-}
diff --git a/src/main/java/Plane.java b/src/main/java/Plane.java
deleted file mode 100644
index fa525cf..0000000
--- a/src/main/java/Plane.java
+++ /dev/null
@@ -1,27 +0,0 @@
-public class Plane {
-    public Point3D mainPoint;
-    public Vector3D normalVector;
-
-    public double getDistance(Point3D point){
-        Vector3D vec = new Vector3D(0,0,0);
-        vec.createFrom2Points(mainPoint, point);
-        return normalVector.dot(vec);
-    }
-    public void initFrom3Points(Point3D _mainPoint, Point3D point1, Point3D point2){
-        // find normal vector to 3 points
-        Vector3D vec1 = new Vector3D(0,0,0);
-        Vector3D vec2 = new Vector3D(0,0,0);
-        vec1.createFrom2Points(_mainPoint, point1);
-        vec2.createFrom2Points(point1, point2);
-        initFromPointAndVector(_mainPoint, vec1.cross(vec2));
-    }
-    public void initFromPointAndVector(Point3D _mainPoint, Vector3D _normalVector){
-        // convert to unit vector
-        mainPoint = _mainPoint;
-        double len = _normalVector.getLength();
-        normalVector = new Vector3D(
-                _normalVector.x / len,
-                _normalVector.y / len,
-                _normalVector.z / len);
-    }
-}
diff --git a/src/main/java/Player.java b/src/main/java/Player.java
deleted file mode 100644
index 9a9d682..0000000
--- a/src/main/java/Player.java
+++ /dev/null
@@ -1,223 +0,0 @@
-import java.awt.*;
-import java.awt.event.KeyEvent;
-import java.awt.image.BufferedImage;
-import java.awt.image.ImageObserver;
-
-public class Player {
-
-    public Plane[] frustumPlanes = new Plane[6];
-    // image that represents the player's position on the board
-    private BufferedImage image;
-    // current position of the player on the board grid
-    private Matrix rotMatrix;
-    public Matrix camMatrix;
-    private Matrix invRotMatrix;
-    private Matrix invCamMatrix;
-    public double FOV = 100;
-    protected double Fpdis;
-    protected Point3D FPWorldPos;
-    protected PointComp[] ScreenCornerPosS = new PointComp[4];
-    private Point3D position = new Point3D(0,0,0);
-    private Point3D rotation = new Point3D(0,Math.PI / 2, 0);
-    private Point3D direction = new Point3D(0,0,0);
-    public Vector3D viewVector = new Vector3D(0,0,0);
-    public double mouseSensitivity = 0.005; // radians per pixel
-    // called when the player is initialised
-    public Player(int scrX, int scrY) {
-        Fpdis = 1/Math.tan(Math.toRadians(FOV)/2d);
-        for(int i = 0; i < 6; i+=1){
-            frustumPlanes[i] = new Plane();
-        }
-        double yVal = (double)scrY / (double)scrX;
-        // flip x and y because reasons
-        ScreenCornerPosS[0] = new PointComp(-yVal,-1,  0);
-        ScreenCornerPosS[1] = new PointComp(yVal,-1,  0);
-        ScreenCornerPosS[2] = new PointComp(-yVal,1,  0);
-        ScreenCornerPosS[3] = new PointComp(yVal,1,  0);
-    }
-
-    // unused because the player should not be drawn
-    // I'm leaving it in as a hint to buffered Images
-    public void draw(Graphics g, ImageObserver observer) {
-        int width = 100;
-        int height = 100;
-
-        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB );
-
-        for ( int py = 0; py < height; py++ ) {
-            for ( int px = 0; px < width; px++ ) {
-                // Set the pixel colour of the image n.b. x = cc, y = rc
-                img.setRGB(px, py, Color.BLACK.getRGB() );
-            }
-        }
-        g.drawImage(img, 0, 0, observer);
-    }
-
-    public void keyPressed(int key) {
-        if (key == KeyEvent.VK_W && direction.x < 1) {
-            direction.x += 1;
-        } else if (key == KeyEvent.VK_S && -direction.x < 1) {
-            direction.x += -1;
-        } else if (key == KeyEvent.VK_A && direction.y < 1) {
-            direction.y += 1;
-        } else if (key == KeyEvent.VK_D && -direction.y < 1) {
-            direction.y += -1;
-        } else if (key == KeyEvent.VK_SPACE && direction.z < 1) {
-            direction.z += 1;
-        } else if (key == KeyEvent.VK_SHIFT && -direction.z < 1) {
-            direction.z += -1;
-        }
-    }
-    public void keyReleased(int key){
-        if (key == KeyEvent.VK_W && direction.x > -1) {
-            direction.x -= 1;
-        } else if (key == KeyEvent.VK_S && -direction.x > -1) {
-            direction.x -= -1;
-        } else if (key == KeyEvent.VK_A && direction.y > -1) {
-            direction.y -= 1;
-        } else if (key == KeyEvent.VK_D && -direction.y > -1) {
-            direction.y -= -1;
-        } else if (key == KeyEvent.VK_SPACE && direction.z > -1) {
-            direction.z -= 1;
-        } else if (key == KeyEvent.VK_SHIFT && -direction.z > -1) {
-            direction.z -= -1;
-        }
-    }
-    public void tick(Point2D mouseRel) {
-        // gets called once every tick, before the repainting process happens.
-        // change rotation depending on mouse movement
-        rotation.z -= mouseRel.x * mouseSensitivity;
-        // force z rotation to wrap around
-        if(rotation.z<-Math.PI){
-            rotation.z = Math.PI;
-        } else if(rotation.z>Math.PI){
-            rotation.z = -Math.PI;
-        }
-        rotation.y += mouseRel.y * mouseSensitivity;
-        // max out y rotation at 0 and -90 degrees
-        if(rotation.y < 0){
-            rotation.y = 0;
-        } else if(rotation.y > Math.PI){
-            rotation.y = Math.PI;
-        }
-        // define rotation and translation matrices
-        Matrix zMat = new Matrix(3, 3);
-        zMat.setItems(new double[][]{
-                {Math.cos(rotation.z),  Math.sin(rotation.z), 0},
-                {-Math.sin(rotation.z), Math.cos(rotation.z), 0},
-                {0,                      0,                     1}}
-        );
-        Matrix yMat = new Matrix(3, 3);
-        yMat.setItems(new double[][]{
-                {Math.cos(rotation.y), 0, -Math.sin(rotation.y)},
-                {0,                    1, 0                    },
-                {Math.sin(rotation.y), 0, Math.cos(rotation.y) }}
-        );
-        Matrix xMat = new Matrix(3, 3);
-        xMat.setItems(new double[][]{
-                {1, 0,                    0                   },
-                {0, Math.cos(rotation.x), Math.sin(rotation.x)},
-                {0, -Math.sin(rotation.x), Math.cos(rotation.x)}}
-        );
-        // calculate inverse matrices
-        Matrix izMat = new Matrix(3, 3);
-        izMat.setItems(new double[][]{
-                {Math.cos(-rotation.z),  Math.sin(-rotation.z), 0},
-                {-Math.sin(-rotation.z), Math.cos(-rotation.z), 0},
-                {0,                      0,                     1}}
-        );
-        Matrix iyMat = new Matrix(3, 3);
-        iyMat.setItems(new double[][]{
-                {Math.cos(-rotation.y), 0, -Math.sin(-rotation.y)},
-                {0,                    1, 0                    },
-                {Math.sin(-rotation.y), 0, Math.cos(-rotation.y) }}
-        );
-        Matrix ixMat = new Matrix(3, 3);
-        ixMat.setItems(new double[][]{
-                {1, 0,                    0                   },
-                {0, Math.cos(-rotation.x), Math.sin(-rotation.x)},
-                {0, -Math.sin(-rotation.x), Math.cos(-rotation.x)}}
-        );
-        // apply izMat to direction vector
-        try {
-            Point3D dirvec = new Point3D(0,0,0);
-            izMat.multiplyPoint3to(direction, dirvec);
-            position.translate(dirvec);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-        Matrix traMat = new Matrix(4, 3);
-        traMat.setItems(new double[][]
-                {{1, 0, 0, -position.x},
-                 {0, 1, 0, -position.y},
-                 {0, 0, 1, -position.z}}
-        );
-        // multiply out matrices
-        rotMatrix = xMat.multiplyGetResult(yMat).multiplyGetResult(zMat);
-        camMatrix = rotMatrix.multiplyGetResult(traMat);
-        invRotMatrix = izMat.multiplyGetResult(iyMat).multiplyGetResult(ixMat);
-        // I really don't want to do it like this, but I don't think there's any other way without messing up
-        // all my other matrices :(
-        invCamMatrix = new Matrix(4, 3);
-        for(int x = 0; x<3; x+=1){
-            for(int y = 0; y<3; y+=1){
-                invCamMatrix.setItem(x, y, invRotMatrix.getItem(x, y));
-            }}
-        for(int y = 0; y<3; y+=1){
-            invCamMatrix.setItem(3, y, -traMat.getItem(3, y));
-        }
-
-        // calculate view vector
-        Point3D viewPoint = new Point3D(0,0,0);
-        invRotMatrix.multiplyPoint3to(new Point3D(0,0,1), viewPoint);
-        // todo - work out why i need to reverse this
-        viewVector.x = viewPoint.x;viewVector.y=viewPoint.y;viewVector.z=viewPoint.z;
-
-        // calculate camera focal point and edges in world coordinates
-        FPWorldPos = new Point3D(
-                position.x - viewVector.x*Fpdis,
-                position.y - viewVector.y*Fpdis,
-                position.z - viewVector.z*Fpdis);
-        for (PointComp point:
-             ScreenCornerPosS) {
-            point.invalidate();
-            point.setRotatedPoint(invCamMatrix);
-        }
-        // find frustum planes
-        // near plane
-        //frustumPlanes[0].initFromPointAndVector(position, viewVector);
-        frustumPlanes[0].initFrom3Points(ScreenCornerPosS[0].getRotatedPoint(),
-                ScreenCornerPosS[1].getRotatedPoint(), ScreenCornerPosS[2].getRotatedPoint());
-        // far plane
-        int farPlaneDis = 1000;
-        frustumPlanes[1].initFromPointAndVector(
-                new Point3D(
-                        position.x + viewVector.x*farPlaneDis,
-                        position.y + viewVector.y*farPlaneDis,
-                        position.z + viewVector.z*farPlaneDis),
-                // invert the view vector because the normal needs to point the other way
-                new Vector3D(-viewVector.x, -viewVector.y, -viewVector.z));
-        // mid planes
-        // left plane
-        frustumPlanes[2].initFrom3Points(FPWorldPos,
-                ScreenCornerPosS[0].getRotatedPoint(), ScreenCornerPosS[1].getRotatedPoint());
-        // right plane
-        frustumPlanes[3].initFrom3Points(FPWorldPos,
-                ScreenCornerPosS[3].getRotatedPoint(), ScreenCornerPosS[2].getRotatedPoint());
-        // top plane
-        frustumPlanes[4].initFrom3Points(FPWorldPos,
-                ScreenCornerPosS[1].getRotatedPoint(), ScreenCornerPosS[3].getRotatedPoint());
-        // bottom plane
-        frustumPlanes[5].initFrom3Points(FPWorldPos,
-                ScreenCornerPosS[2].getRotatedPoint(), ScreenCornerPosS[0].getRotatedPoint());
-    }
-
-    // returnValue Functions
-    public Point3D getPos() {
-        return position;
-    }
-    public Point3D getRot() {
-        return rotation;
-    }
-
-}
\ No newline at end of file
diff --git a/src/main/java/Point2D.java b/src/main/java/Point2D.java
deleted file mode 100644
index e203550..0000000
--- a/src/main/java/Point2D.java
+++ /dev/null
@@ -1,20 +0,0 @@
-import java.util.ArrayList;
-
-public class Point2D {
-    public int x;
-    public int y;
-    public double z;
-    // contains a Z value so that projected points can have their Z values calculated efficiently
-    public Point2D(int _x, int _y){
-        x = _x;
-        y = _y;
-    }
-    public void set(int _x, int _y){
-        x = _x;
-        y = _y;
-    }
-    public int[] get(){
-        return new int[]{x, y};
-    }
-
-}
diff --git a/src/main/java/Point3D.java b/src/main/java/Point3D.java
deleted file mode 100644
index e6a51cf..0000000
--- a/src/main/java/Point3D.java
+++ /dev/null
@@ -1,36 +0,0 @@
-public class Point3D {
-    public double x;
-    public double y;
-    public double z;
-
-    public Point3D(double _x, double _y, double _z){
-        x = _x;
-        y = _y;
-        z = _z;
-    }
-    public void set(double[] _new){
-        x = _new[0];
-        y = _new[1];
-        z = _new[2];
-    }
-    public void translate(Point3D trVec){
-        x += trVec.x;
-        y += trVec.y;
-        z += trVec.z;
-    }
-    public Point2D project(double fpdis, int scrX, int scrY){
-        return new Point2D(
-                scrX - (int)(scrX*0.5*((fpdis*y)/(z) + 1)),
-                (int)(scrX*0.5*((fpdis*x)/(z) + ((double)scrY/(double)scrX)))
-        );
-    }
-    public void projectOn(double fpdis, int scrX, int scrY, Point2D point){
-        // this is a mess of random fixes because none of my coordinates are correct. :(
-        // sorry
-        point.x = scrX - (int)(scrX*0.5*((fpdis*y)/(z) + 1));
-        point.y = (int)(scrX*0.5*((fpdis*x)/(z) + ((double)scrY/(double)scrX)));
-    }
-    public double[] get(){
-        return new double[]{x, y, z};
-    }
-}
diff --git a/src/main/java/PointComp.java b/src/main/java/PointComp.java
deleted file mode 100644
index 730ef6b..0000000
--- a/src/main/java/PointComp.java
+++ /dev/null
@@ -1,40 +0,0 @@
-public class PointComp {
-    public Point3D point = new Point3D(0,0,0);
-    private final Point3D rotatedPoint = new Point3D(0,0,0);
-    private boolean isRotatedPointValid = true;
-    private final Point2D projectedPoint = new Point2D(0,0);
-    private boolean isProjectedPointValid = true;
-    public PointComp(double _x, double _y, double _z){
-        point.x = _x;
-        point.y = _y;
-        point.z = _z;
-    }
-    public void invalidate(){
-        isProjectedPointValid = false; isRotatedPointValid = false;
-    }
-    public Point3D getRotatedPoint(){
-        if(!isRotatedPointValid){
-            throw new RuntimeException("Rotated point not initialised");
-        }
-        return rotatedPoint;
-    }
-    public Point2D getProjectedPoint(){
-        if(!isRotatedPointValid){
-            throw new RuntimeException("Projected point not initialised");
-        }
-        return projectedPoint;
-    }
-    public void setRotatedPoint(Matrix camMatrix){
-        if(!isRotatedPointValid) {
-            camMatrix.multiplyPoint3to(point, rotatedPoint);
-            isRotatedPointValid = true;
-        }
-    }
-    public void setProjectedPoint(double FPDis, int scrX, int scrY){
-        if(!isProjectedPointValid) {
-            rotatedPoint.projectOn(FPDis, scrX, scrY, projectedPoint);
-            projectedPoint.z = rotatedPoint.z;
-            isProjectedPointValid = true;
-        }
-    }
-}
diff --git a/src/main/java/Screen.java b/src/main/java/Screen.java
deleted file mode 100644
index 8034e4c..0000000
--- a/src/main/java/Screen.java
+++ /dev/null
@@ -1,286 +0,0 @@
-import com.zetcode.CSV;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.*;
-import java.awt.image.BufferedImage;
-
-public class Screen extends JPanel implements ActionListener, KeyListener, MouseListener, MouseMotionListener {
-    // __working variables__
-    public long lastTime = 0;
-        // mouse
-    private boolean captured = false;
-    private final Point2D mouseRel = new Point2D(0,0);
-    private final Cursor invisibleCursor;
-
-    public ObjectCollection mainCollection;
-    public CSV csv;
-
-        // testing\
-//    private Line2d line = new Line2d(
-//            new Point2D(200, 200),
-//                new Point2D(1, 1),
-//                true);
-//    private Triangle Triangle = new Triangle(
-//            new Point2D(200, 200), new Point2D(1, 1), new Point2D(400,200)
-//    ,new boolean[]{false, false, false},
-//            null,
-//            new Matrix(3, 3));
-//    private final Point3D[] points = new Point3D[]{
-//            new Point3D(10,10,-1),
-//        new Point3D(-10, 10, -1),
-//        new Point3D(-10, -10, -1),
-//        new Point3D(10, -10, -1)};
-    // double ang = 0;
-    // __config variables__
-    // controls the delay between each tick in ms
-    private final int DELAY = 25;
-    // controls the size of the board
-    public static final int TILE_SIZE = 50;
-    public static final int ROWS = 12;
-    public static final int COLUMNS = 18;
-    // suppress serialization warning
-    private static final long serialVersionUID = 490905409104883233L;
-
-    // keep a reference to the timer object that triggers actionPerformed() in
-    // case we need access to it in another method
-    private final Timer timer;
-    private final Player player;
-
-    public Screen(ObjectCollection _mainCollection) {
-        mainCollection = _mainCollection;
-
-        // set the game board size
-        setPreferredSize(new Dimension(TILE_SIZE * COLUMNS, TILE_SIZE * ROWS));
-        // set the game board background color
-        setBackground(new Color(232, 232, 232));
-        // hide the mouse cursor (https://stackoverflow.com/questions/191592/how-do-i-get-rid-of-the-mouse-cursor-in-full-screen-exclusive-mode)
-        Toolkit toolkit = Toolkit.getDefaultToolkit();
-        Point hotSpot = new Point(0,0);
-        BufferedImage cursorImage = new BufferedImage(1, 1, BufferedImage.TRANSLUCENT);
-        invisibleCursor = toolkit.createCustomCursor(cursorImage, hotSpot, "InvisibleCursor");
-
-        // initialize the game state
-        player = new Player(TILE_SIZE*COLUMNS, TILE_SIZE*ROWS);
-        // this timer will call the actionPerformed() method every DELAY ms
-        timer = new Timer(DELAY, this);
-        timer.start();
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        // this method is called by the timer every DELAY ms.
-        // runs the tick of the player before the board is redrawn
-        player.tick(get_mouse_rel());
-
-        // calling repaint() will trigger paintComponent() to run again,
-        // which will refresh/redraw the graphics.
-        repaint();
-    }
-
-    @Override
-    public void paintComponent(Graphics g) {
-        super.paintComponent(g);
-        // when calling g.drawImage() we can use "this" for the ImageObserver 
-        // because Component implements the ImageObserver interface, and JPanel 
-        // extends from Component. So "this" Board instance, as a Component, can 
-        // react to imageUpdate() events triggered by g.drawImage()
-
-        // draw graphics.
-        drawScreen(g);
-        // player.draw(g, this);
-
-        // this smooths out animations on some systems
-        Toolkit.getDefaultToolkit().sync();
-    }
-    private void drawScreen(Graphics g)  {
-        BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB );
-        BufferedImage zBuf = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
-        BufferedImage debugImg = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
-        debugImg.createGraphics();
-        g.setColor(Color.white);
-
-//        System.out.println(zBuf.getRGB(0,0));
-//        zBuf.setRGB(0,0, 100);
-//        System.out.println(zBuf.getRGB(0,0));
-
-//        line.point2.x = 100*Math.sin(ang) + 200;
-//        line.point2.y = 100*Math.cos(ang) + 200;
-//        Triangle.point2.x = 100*Math.sin(ang+3) + 200;
-//        Triangle.point2.y = 100*Math.cos(ang+3) + 200;
-//        line.draw(img);
-      /*  ArrayList<Point3D> newPoints = new ArrayList<Point3D>();
-        for (Point3D point: points) {
-            Point3D _new = new Point3D(0,0,0);
-            player.camMatrix.multiplyPoint3to(point, _new);
-            if(_new.z > .1) {
-                newPoints.add(_new);
-            } else{
-                newPoints.add(null);
-            }
-        }
-        Triangle t1 = null;
-        Triangle t2 = null;
-        try {
-            t1 = new Triangle(
-                    newPoints.get(0).project(player.FPDis, getWidth(), getHeight()),
-                    newPoints.get(1).project(player.FPDis, getWidth(), getHeight()),
-                    newPoints.get(2).project(player.FPDis, getWidth(), getHeight()));
-        } catch(NullPointerException ignored){}
-        try{
-            t2 = new Triangle(
-                newPoints.get(0).project(player.FPDis, getWidth(), getHeight()),
-                newPoints.get(2).project(player.FPDis, getWidth(), getHeight()),
-                newPoints.get(3).project(player.FPDis, getWidth(), getHeight()));
-        } catch(NullPointerException ignored){}
-
-        try{t1.draw(img);}catch (NullPointerException ignored){}
-        try{t2.draw(img);}catch (NullPointerException ignored){}
-        ang += 0.02;*/
-
-        mainCollection.invalidate(true);
-        mainCollection.draw(img, zBuf, debugImg, player.camMatrix, player.getPos(), player.frustumPlanes, player.Fpdis, getWidth(), getHeight());
-        g.drawImage(img, 0, 0, this);
-
-        // DEBUG DRAWING
-        {
-            debugImg.getGraphics().drawString(Math.round(1000 / (float) (System.currentTimeMillis() - lastTime)) + " fps", 10, 10);
-            debugImg.getGraphics().drawString("fpPos: " +
-                    String.format("%.2f", player.FPWorldPos.x) + " " +
-                    String.format("%.2f", player.FPWorldPos.y) + " " +
-                    String.format("%.2f", player.FPWorldPos.z) + " ", 100, 30);
-            g.drawImage(debugImg, 0, 0, this);
-            debugImg.getGraphics().drawString("playerPos: " +
-                    String.format("%.2f", player.getPos().x) + " " +
-                    String.format("%.2f", player.getPos().y) + " " +
-                    String.format("%.2f", player.getPos().z) + " ", 100, 50);
-            debugImg.getGraphics().drawString("rotation: " +
-                    Math.round(Math.toDegrees(player.getRot().x)) + " " +
-                    Math.round(Math.toDegrees(player.getRot().y)) + " " +
-                    Math.round(Math.toDegrees(player.getRot().z)) + " ", 300, 50);
-            debugImg.getGraphics().drawString("viewVec: " +
-                    String.format("%.2f", player.viewVector.x) + " " +
-                    String.format("%.2f", player.viewVector.y) + " " +
-                    String.format("%.2f", player.viewVector.z) + " ", 100, 70);
-            for (int i = 0; i < 4; i += 1) {
-                debugImg.getGraphics().drawString("camPoint " + i + ": " +
-                        String.format("%.2f", player.ScreenCornerPosS[i].getRotatedPoint().x) + " " +
-                        String.format("%.2f", player.ScreenCornerPosS[i].getRotatedPoint().y) + " " +
-                        String.format("%.2f", player.ScreenCornerPosS[i].getRotatedPoint().z), 10, 90 + 20 * i);
-            }
-            for (int i = 0; i < 6; i += 1) {
-                debugImg.getGraphics().drawString("plane " + i + ": " +
-                        String.format("%.2f", player.frustumPlanes[i].mainPoint.x) + " " +
-                        String.format("%.2f", player.frustumPlanes[i].mainPoint.y) + " " +
-                        String.format("%.2f", player.frustumPlanes[i].mainPoint.z) + ", normal: " +
-                        String.format("%.2f", player.frustumPlanes[i].normalVector.x) + " " +
-                        String.format("%.2f", player.frustumPlanes[i].normalVector.y) + " " +
-                        String.format("%.2f", player.frustumPlanes[i].normalVector.z), 10, 170 + 20 * i);
-            }
-            // write debug overlay
-            g.drawImage(debugImg, 0, 0, this);
-            lastTime = System.currentTimeMillis();
-        }
-        // CSV WRITING
-        // each two lines line in the file represents an object in blender.
-        // They contain a list of vertices on even numbered lines, and a list of faces on odd numbered lines
-        csv = new CSV("debugOUT.csv");
-        String[] writingString = new String[]{};
-        // camera object
-        for (int i = 0; i < 4; i += 1) {
-                    String.format("%.2f", player.ScreenCornerPosS[i].getRotatedPoint().x),
-                    String.format("%.2f", player.ScreenCornerPosS[i].getRotatedPoint().y),
-                    String.format("%.2f", player.ScreenCornerPosS[i].getRotatedPoint().z);
-        csv.writeLine();
-    }
-
-    @Override
-    public void keyTyped(KeyEvent e) {}
-
-    @Override
-    public void keyPressed(KeyEvent e) {
-        int key = e.getKeyCode();
-
-        if (key == KeyEvent.VK_ESCAPE){
-            captured = false;
-            setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
-        }
-        // pass key-press to the player, so they can handle it as well
-        player.keyPressed(key);
-    }
-
-    @Override
-    public void keyReleased(KeyEvent e) {
-        int key = e.getKeyCode();
-
-        // pass key-release to the player, so they can handle it as well
-        player.keyReleased(key);
-    }
-
-    // gets the relative position of the mouse since the last time this function was called
-    public Point2D get_mouse_rel(){
-        Point2D rel;
-        // this doesn't work if the mouse isn't captured. If I need it to, I can
-        // probably make it, but I don't know what the functionality looks like.
-        if (captured) {
-            rel = new Point2D(MouseInfo.getPointerInfo().getLocation().x - getLocationOnScreen().x - getSize().width / 2 + mouseRel.x,
-                    MouseInfo.getPointerInfo().getLocation().y - getLocationOnScreen().y - getSize().height / 2 + mouseRel.y);
-            mouseRel.set(0, 0);
-            // set the position of the mouse back to (0,0)
-            try {
-                Robot robot = new Robot();
-                robot.mouseMove(getLocationOnScreen().x + getSize().width / 2,
-                        getLocationOnScreen().y + getSize().height / 2);
-            } catch (AWTException ex) {
-                ex.printStackTrace();
-            }
-        }
-        else{
-            rel = new Point2D(0,0);
-        }
-        return rel;
-    }
-    @Override
-    public void mouseClicked(MouseEvent mouseEvent) {
-        if (mouseEvent.getButton() == MouseEvent.BUTTON1){
-            captured = true;
-            get_mouse_rel();
-            setCursor(invisibleCursor);
-        }
-    }
-
-    @Override
-    public void mousePressed(MouseEvent mouseEvent) {}
-
-    @Override
-    public void mouseReleased(MouseEvent mouseEvent) {}
-
-    @Override
-    public void mouseEntered(MouseEvent mouseEvent) {}
-
-    @Override
-    public void mouseExited(MouseEvent mouseEvent) {
-        if (captured) {
-            // add current mouse relative location to mouseRel so mouse movements are not lost
-            mouseRel.x += mouseEvent.getX() - getSize().width / 2d;
-            mouseRel.y += mouseEvent.getY() - getSize().height / 2d;
-
-            // System.out.println(mouseEvent.getX() + " " + mouseEvent.getY());
-            // set the position of the mouse back to (0,0)
-            try {
-                Robot robot = new Robot();
-                robot.mouseMove(getLocationOnScreen().x + getSize().width / 2,
-                                getLocationOnScreen().y + getSize().height / 2);
-            } catch (AWTException ex) {
-                ex.printStackTrace();
-            }
-        }
-    }
-
-    @Override
-    public void mouseDragged(MouseEvent mouseEvent) {}
-
-    @Override
-    public void mouseMoved(MouseEvent mouseEvent) {
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/Triangle.java b/src/main/java/Triangle.java
deleted file mode 100644
index ff12683..0000000
--- a/src/main/java/Triangle.java
+++ /dev/null
@@ -1,178 +0,0 @@
-import org.omg.CORBA.PolicyTypeHelper;
-
-import java.awt.*;
-import java.awt.image.BufferedImage;
-
-public class Triangle{
-    public Point2D point1;
-    public Point2D point2;
-    public Point2D point3;
-
-    public Matrix perspectiveMappingMatrix;
-
-
-    public boolean[] edgeList; //edge 1-2 , 2-3, 3-1
-
-    private Line2d LineLong;
-    private Line2d LineA;
-    private Line2d LineB;
-
-    public BufferedImage texture;
-
-    // initialisation variables
-    private boolean is_initialised = false;
-    private Point2D min;
-    private Point2D max;
-    // progress variables
-    private Point3D result = new Point3D(0,0,0);
-    private Point2D result2 = new Point2D(0,0);
-    public void invalidate() {
-        is_initialised = false;
-    }
-    public Triangle(Point2D _pA, Point2D _pB, Point2D _pC, boolean[] _edgeList, BufferedImage _texture, Matrix mapMatrix){
-        point1 = _pA;
-        point2 = _pB;
-        point3 = _pC;
-        edgeList = _edgeList;
-        texture = _texture;
-        perspectiveMappingMatrix = mapMatrix;
-    }
-    //  returns int for debug
-    public int draw(BufferedImage img, BufferedImage zBuf, double FPDis, int scrX, int scrY){
-        //
-        long lastMillis;
-
-
-        if (!is_initialised){initialise();}
-        int[] point1;
-        int[] point2;
-        char currentLine = 'A';
-
-        lastMillis = System.currentTimeMillis();
-
-        point1 = LineLong.nextPix();
-        point2 = LineA.nextPix();
-        for(int x = min.x+1; x <= max.x; x += 1) {
-            while(x-1 == point1[0]) {
-                if(LineLong.isDrawn && // draw line pixels if needed, and on screen
-                        point1[0] > 0 && point1[1] > 0 && point1[0] < img.getWidth() && point1[1] < img.getHeight()){
-                    img.setRGB(point1[0], point1[1], Color.HSBtoRGB(0, 1, 1));
-                }
-                try { // this error seems to be thrown randomly, for various reasons // todo fix
-                    point1 = LineLong.nextPix();
-                }
-                catch (Exception e){
-                    throw new RuntimeException("accessed too many line pixels");
-                }
-            }
-            while(x-1 == point2[0]) {
-                if (currentLine == 'A') {
-                    try{
-                        if(LineA.isDrawn &&                // draw line pixels if needed, and on screen
-                                point2[0] > 0 && point2[1] > 0 && point2[0] < img.getWidth() && point2[1] < img.getHeight()){
-                            img.setRGB(point2[0], point2[1], Color.HSBtoRGB(0f, 1, 1));
-                        }
-                        point2 = LineA.nextPix();
-                    }
-                    catch (RuntimeException e){
-                        currentLine = 'B';
-                        // point2 = LineB.nextPix();
-                    }
-                }
-                else {
-                    if(LineB.isDrawn &&                // draw line pixels if needed, and on screen
-                            point2[0] > 0 && point2[1] > 0 && point2[0] < img.getWidth() && point2[1] < img.getHeight()){
-                        img.setRGB(point2[0], point2[1], Color.HSBtoRGB(0f, 1, 1));
-                    }
-                    point2 = LineB.nextPix();
-                }
-            }
-            // cancel drawing if the x value of the triangle is out of bounds
-            if (x >= img.getWidth()) {break;}
-            if (x > 0) {
-                // check which way to loop
-                // TODO - work out a way of not needing to test for this every time
-                if (point1[1] < point2[1]) {
-                    for (int y = Math.max(point1[1], 0); y <= Math.min(point2[1], img.getHeight() - 1); y += 1) {
-                        // function only exists so I don't have to copy paste code everywhere.
-                        drawPix(img, zBuf, FPDis, scrX, scrY, currentLine, x, y, point1[1], point2[1]);
-                    }
-                } else {
-                    for (int y = Math.max(point2[1], 0); y <= Math.min(point1[1], img.getHeight() - 1); y += 1) {
-                        drawPix(img, zBuf, FPDis, scrX, scrY, currentLine, x,y, point1[1], point2[1]);
-                    }
-                }
-            }
-        }
-        lastMillis = (System.currentTimeMillis() - lastMillis);
-        return (int)lastMillis;
-    }
-    public void initialise(){
-        if (point1 == null || point2 == null || point3 == null){
-            throw new NullPointerException();
-        }
-        min = new Point2D(Math.min(point1.x, Math.min(point2.x, point3.x)), Math.min(point1.y, Math.min(point2.y, point3.y)));
-        max = new Point2D(Math.max(point1.x, Math.max(point2.x, point3.x)), Math.max(point1.y, Math.max(point2.y, point3.y)));
-        // woo horrible IFs mess.
-        // we need to figure out which points touch the edges in order to find which line is the 'full length' edge,
-        // and then assign line A and line B in order
-        if (point1.x == min.x) {
-            if (point2.x == max.x){
-                LineLong = new Line2d(point1, point2, edgeList[0]);
-                LineA = new Line2d(point1, point3, edgeList[2]);
-                LineB = new Line2d(point3, point2, edgeList[1]);
-            } else {
-                LineLong = new Line2d(point1, point3, edgeList[2]);
-                LineA = new Line2d(point1, point2, edgeList[0]);
-                LineB = new Line2d(point2, point3, edgeList[1]);
-            }
-        }
-        else if (point2.x == min.x) {
-            if (point1.x == max.x) {
-                LineLong = new Line2d(point2, point1, edgeList[0]);
-                LineA = new Line2d(point2, point3, edgeList[1]);
-                LineB = new Line2d(point3, point1, edgeList[2]);
-            } else {
-                LineLong = new Line2d(point2, point3, edgeList[1]);
-                LineA = new Line2d(point2, point1, edgeList[0]);
-                LineB = new Line2d(point1, point3, edgeList[2]);
-            }
-        }
-        else if (point3.x == min.x){
-            if (point1.x == max.x) {
-                LineLong = new Line2d(point3, point1, edgeList[2]);
-                LineA = new Line2d(point3, point2, edgeList[1]);
-                LineB = new Line2d(point2, point1, edgeList[0]);
-            } else {
-                LineLong = new Line2d(point3, point2, edgeList[2]);
-                LineA = new Line2d(point3, point1, edgeList[1]);
-                LineB = new Line2d(point1, point2, edgeList[0]);
-            }
-        }
-        // assign points to lines
-        is_initialised = true;
-    }
-    private void drawPix(BufferedImage img, BufferedImage zBuf, double FPDis, int scrX, int scrY, char currentLine, int x, int y, int y1, int y2){
-        // find Z coordinate of pixel
-        double z1 = LineLong.getZVal(x); double z2;
-        if(currentLine=='A'){z2=LineA.getZVal(x);} else{z2=LineB.getZVal(x);}
-        double ZVal = z1 + ((z1-z2)/ (y1-y2) * (y - y1));
-
-        // a value of 0 represents a value which is on the camera, at z=0.
-        // not the best mapping but it's probably good enough
-        int newZ = (int)((2147483648L/ZVal));
-        // if the new Z value is greater than the existing Z value on the buffer, the new pixel is calculated and drawn
-        if(zBuf.getRGB(x, y) == Color.HSBtoRGB(0, 0, 0) ||
-        newZ > zBuf.getRGB(x, y)){ //newZ > zBuf.getRGB(x, y) ||
-            zBuf.setRGB(x, y, newZ);
-            result = new Point3D(0,0,0);
-            //perspectiveMappingMatrix.multiplyPoint3to(new Point3D(x, y, 0), result);
-            // project result
-            result.projectOn(FPDis, scrX, scrY, result2);
-            int colour = texture.getRGB(
-                    Math.floorMod((int)(result2.x), texture.getWidth()),
-                    Math.floorMod((int)(result2.y), texture.getHeight()));
-            img.setRGB(x, y, (int) (colour - 10*(result2.x/ texture.getWidth()) - 10*(result2.y/ texture.getHeight())));
-        }
-    }
-}
diff --git a/src/main/java/Vector2D.java b/src/main/java/Vector2D.java
deleted file mode 100644
index 96618ff..0000000
--- a/src/main/java/Vector2D.java
+++ /dev/null
@@ -1,26 +0,0 @@
-public class Vector2D {
-    // technically not required as vectors have the same information as points, but it's useful to have separate vector and point things..
-    public double x;
-    public double y;
-
-    public Vector2D(double _x, double _y){
-        x = _x;
-        y = _y;
-    }
-    public void createFrom2Points(Point2D start, Point2D end){
-        x = end.x - start.x;
-        y = end.y - start.y;
-    }
-    public double getLength(){
-        return Math.sqrt(x*x + y*y);
-    }
-    public double angleTo(Vector2D vec2){
-        return Math.acos(
-                dot(vec2)
-                / (getLength()*vec2.getLength()))
-                * Math.signum(x*vec2.y - y *vec2.x);
-    }
-    public double dot(Vector2D vec2){
-        return (x*vec2.x) + (y*vec2.y);
-    }
-}
diff --git a/src/main/java/Vector3D.java b/src/main/java/Vector3D.java
deleted file mode 100644
index 6fc0290..0000000
--- a/src/main/java/Vector3D.java
+++ /dev/null
@@ -1,39 +0,0 @@
-// technically not required as vectors have the same information as points, but it's useful to have separate vector and point things..
-
-import java.util.Vector;
-
-public class Vector3D {
-    public double x;
-    public double y;
-    public double z;
-
-    public Vector3D(double _x, double _y, double _z){
-        x = _x;
-        y = _y;
-        z = _z;
-    }
-    public void createFrom2Points(Point3D start, Point3D end){
-            x = end.x - start.x;
-        y = end.y - start.y;
-        z = end.z - start.z;
-    }
-    public double getLength(){
-        return Math.sqrt(x*x + y*y + z*z);
-    }
-    public double angleTo(Vector3D vec2){
-        return Math.acos(
-                (x*vec2.x + y*vec2.y + z*vec2.z)
-                        /( //-----------------------------------------------------------
-                        Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2)) *
-                        Math.sqrt(Math.pow(vec2.x,  2) + Math.pow(vec2.y, 2) + Math.pow(vec2.z, 2))));
-    }
-    public Vector3D cross(Vector3D vec2){
-        return new Vector3D(
-                y*vec2.z - z*vec2.y,
-                z*vec2.x - x*vec2.z,
-                x*vec2.y - y*vec2.x);
-    }
-    public double dot(Vector3D vec2){
-        return x*vec2.x + y* vec2.y+z* vec2.z;
-    }
-}
diff --git a/src/main/java/uk/org/floop/epq3d/App.java b/src/main/java/uk/org/floop/epq3d/App.java
new file mode 100644
index 0000000..99173c4
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/App.java
@@ -0,0 +1,115 @@
+package uk.org.floop.epq3d;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+class App {
+    static ObjectCollection mainCollection;
+    private static void initWindow() {
+        // create a window frame and set the title in the toolbar
+        JFrame window = new JFrame("Testing");
+        // when we close the window, stop the app
+        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        // create the jpanel to draw on.
+        // this also initializes the game loop
+        initObjects();
+        Screen screen = new Screen(mainCollection);
+        // add the jpanel to the window
+        window.add(screen);
+        window.addKeyListener(screen);
+        window.addMouseListener(screen);
+        window.addMouseMotionListener(screen);
+        // don't allow the user to resize the window
+        window.setResizable(false);
+        // fit the window size around the components (just our jpanel).
+        // pack() should be called after setResizable() to avoid issues on some platforms
+        window.pack();
+        // open window in the center of the screen
+        window.setLocationRelativeTo(null);
+        // display the window
+        window.setVisible(true);
+    }
+
+    public static void main(String[] args) {
+        // invokeLater() is used here to prevent our graphics processing from
+        // blocking the GUI. https://stackoverflow.com/a/22534931/4655368
+        // this is a lot of boilerplate code that you shouldn't be too concerned about.
+        // just know that when main runs it will call initWindow() once.
+        SwingUtilities.invokeLater(App::initWindow);
+    }
+
+    public static void initObjects(){
+        mainCollection = new ObjectCollection();
+
+        BufferedImage testTexture;
+        try {
+            //testTexture = ImageIO.read(new File("/home/cory/Screenshot from 2022-06-06 18-52-12.png"));
+            testTexture = ImageIO.read(new File("/home/cory/Screenshot from 2022-09-26 13-05-40.png"));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        mainCollection.addObject(new Object3d(new PointComp[]{
+                new PointComp(-30,-10,-10),
+                new PointComp(-30,-10,10),
+                new PointComp(-30,10,-10),
+                new PointComp(-30,10,10),
+                new PointComp(-10,-10,-10),
+                new PointComp(-10,-10,10),
+                new PointComp(-10,10,-10),
+                new PointComp(-10,10,10)
+
+        }, new int[][]{
+                {0,2,3,1},
+                {4,5,7,6},
+                {0,1,5,4},
+                {1,3,7,5},
+                {3,2,6,7},
+                {2,0,4,6},
+        }, new Point2D[][]{
+                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
+                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
+                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
+                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
+                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
+                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
+                {new Point2D(0, 0), new Point2D(100, 0), new Point2D(100, 100), new Point2D(0, 100)},
+        },
+                true,
+                testTexture,
+                true));
+//        mainCollection.addObject(new Object3d(new PointComp[]{
+//                new PointComp(-10,10,10),
+//                new PointComp(-10,-10,10),
+//                new PointComp(10,10,10),
+//                new PointComp(10,-10,10),
+//                new PointComp(0,0,24)
+//        }, new int[][]{
+//                {0,2,3,1},
+//                {1,0,4},
+//                {0,2,4},
+//                {2,3,4},
+//                {3,1,4}
+//        }, true));
+        mainCollection.addObject(new Object3d(new PointComp[]{
+                new PointComp(0,0,0),
+                new PointComp(20,0,0),
+                new PointComp(0,10,0),
+                new PointComp(0,0,10),
+        }, new int[][]{
+                {0,1,2},
+                {0,2,3},
+                {0,3,1},
+                {2,1,3}
+        }, new Point2D[][]{
+                {new Point2D(0,0), new Point2D(100,0), new Point2D(0,100)},
+                {new Point2D(0,0), new Point2D(100,0), new Point2D(0,100)},
+                {new Point2D(0,0), new Point2D(100,0), new Point2D(0,100)},
+        },
+                true,
+                testTexture,
+                true));
+    }
+}
diff --git a/src/main/java/uk/org/floop/epq3d/Face.java b/src/main/java/uk/org/floop/epq3d/Face.java
new file mode 100644
index 0000000..426c6cd
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/Face.java
@@ -0,0 +1,275 @@
+package uk.org.floop.epq3d;
+
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+
+public class Face {
+    public PointComp[] points;
+    public Point2D[] UVPoints;
+    public Vector3D normal;
+    public Triangle[] tris;
+    public boolean hasEdges;
+    public BufferedImage texture;
+    public Matrix[] perspectiveMappingMatrices = new Matrix[]{null, null, null, null, null, null};
+    public boolean isInitialised;
+    // fixed face
+    public Face fixedFace;
+    // working variables
+    private final Vector3D traVec = new Vector3D(0,0, 0);
+    // private final Vector2D scaVec = new Vector2D(0,0);
+
+    private final Vector3D real01Vec = new Vector3D(0,0,0);
+    private final Vector2D UV01Vec = new Vector2D(0,0);
+    private final Vector3D real02Vec = new Vector3D(0,0,0);
+    private final Vector2D UV02Vec = new Vector2D(0,0);
+    double[] result;
+    public void initialise(){
+        calculateNormal();
+        separateTris();
+        // fixedFace is a constant object which is used temporarily when the face intersects with the camera,
+        // and thus needs to be sliced.
+        fixedFace = new Face();
+        fixedFace.hasEdges = hasEdges;
+        fixedFace.texture = texture;
+        fixedFace.normal = normal;
+        fixedFace.isInitialised = true;
+        // the fixed face inherits the perspective mapping matrices from the true face. This is because the transforms
+        // stay the same, just the edge bounds of the face have changed.
+        fixedFace.perspectiveMappingMatrices = perspectiveMappingMatrices;
+        isInitialised = true;
+    }
+
+    public void invalidate(){
+        for (Triangle tri:
+             tris) {
+            tri.invalidate();
+        }
+    }
+    public int draw(BufferedImage img, BufferedImage zBuf, BufferedImage debugImg, Matrix camMatrix, double FPDis, int scrX, int scrY){
+        if(!isInitialised){
+            throw new RuntimeException("Face not initialised");
+        }
+        // check for backface culling has been done previously.
+        // initialise points
+        int numberOfPixels = 0;
+        boolean valid = applyPointTransforms(camMatrix, FPDis, scrX, scrY);
+        //bakePerspectiveMatrices();
+
+        if (valid) {
+            drawTris(img, zBuf, FPDis, scrX, scrY);}
+        else {
+            ArrayList<PointComp> newPoints = new ArrayList<>();
+            // if there are points behind the camera, loop through all the points and interpolate a point that is.
+            // The perspective mapping matrix is calculated beforehand, so we don't need to move UVS
+            PointComp lastPoint = points[points.length - 1];
+            boolean lastValid = lastPoint.getRotatedPoint().z > 0.1;
+            boolean thisValid;
+            for (PointComp point:
+                    points) {
+                thisValid = point.getRotatedPoint().z > 0.1;
+                // We need to do different things depending on whether the previous point was also a valid point, or not.
+                // first - if only 1 of the last point or this point were valid,
+                // interpolate between them to get the point at the screen. (XOR)
+                if(lastValid ^ thisValid){
+                    // solving for z = 0.1 for the line between thisPoint and lastPoint,
+                    // separately in the xz and yz planes.
+                    double gradX = (point.getRotatedPoint().z - lastPoint.getRotatedPoint().z) /
+                            (point.getRotatedPoint().x - lastPoint.getRotatedPoint().x);
+                    double gradY = (point.getRotatedPoint().z - lastPoint.getRotatedPoint().z) /
+                            (point.getRotatedPoint().y - lastPoint.getRotatedPoint().y);
+
+                    newPoints.add(new PointComp(
+                            (0.1+gradX*point.getRotatedPoint().x-point.getRotatedPoint().z)/gradX,
+                            (0.1+gradY*point.getRotatedPoint().y-point.getRotatedPoint().z)/gradY,
+                            0.1));
+                    if(!Double.isFinite(gradX)){
+                        newPoints.get(newPoints.size() - 1).point.x = point.getRotatedPoint().x;}
+                    if(!Double.isFinite(gradY)){
+                        newPoints.get(newPoints.size() - 1).point.y = point.getRotatedPoint().y;}
+                }
+                // finally - if the current point is valid, then add it to the list
+                if(thisValid){
+                    newPoints.add(new PointComp(
+                            point.getRotatedPoint().x,
+                            point.getRotatedPoint().y,
+                            point.getRotatedPoint().z));
+                }
+                lastPoint = point;
+                lastValid = thisValid;
+            }
+            // there must be at least 3 points in the face for it to be drawn successfully
+            if(newPoints.size() >= 3) {
+                // finished fixing points, now we need to create a new face consisting of those points.
+                fixedFace.points = newPoints.toArray(new PointComp[0]);
+                fixedFace.separateTris();
+                // invalidate all the points so they are actually calculated
+                for (PointComp point :
+                        newPoints) {
+                    point.invalidate();
+                }
+                Matrix identMat = new Matrix(3,3);
+                // we use an identity matrix because the points of the fixed face are in camera coordinates already.
+                // we just need the projected 2d point.
+                identMat.setItems(new double[][]{
+                        {1,0,0},
+                        {0,1,0},
+                        {0,0,1}
+                });
+                fixedFace.applyPointTransforms(identMat, FPDis, scrX, scrY);
+                fixedFace.drawTris(img, zBuf, FPDis, scrX, scrY);
+            }
+        }
+        return numberOfPixels;
+    }
+    public void drawTris(BufferedImage img, BufferedImage zBuf, double FPDis, int scrX, int scrY) {
+        for (Triangle tri :
+                tris) {
+            tri.draw(img, zBuf, FPDis, scrX, scrY);
+        }
+    }
+    public boolean applyPointTransforms(Matrix camMatrix, double FPDis, int scrX, int scrY){
+        boolean valid = true;
+        for (PointComp point:
+                points) {
+            point.setRotatedPoint(camMatrix);
+            // if any points are behind the camera, we will need to handle it differently.
+            if(point.getRotatedPoint().z < 0.1){
+                valid = false;}
+            // only worth calculating the projected point if they are all valid
+            if(valid){
+                point.setProjectedPoint(FPDis, scrX, scrY);}
+        }
+        return valid;
+    }
+    public void separateTris(){
+        Triangle[] newTris = new Triangle[points.length - 2];
+        for(int i = 0; i< newTris.length; i+=1){
+            newTris[i] = new Triangle(
+                    points[0].getProjectedPoint(),
+                    points[i+1].getProjectedPoint(),
+                    points[i+2].getProjectedPoint(),
+                    new boolean[]{hasEdges, hasEdges, hasEdges},
+                    texture, perspectiveMappingMatrices[i]);
+        }
+        tris = newTris;
+    }
+    public void calculateNormal(){
+        // too many new variables
+        Point3D point0 = points[0].point;
+        Point3D point1 = points[1].point;
+        Point3D point2;
+        Vector3D vec1 = new Vector3D(point1.x - point0.x, point1.y - point0.y, point1.z - point0.z);
+        Vector3D vec2 = new Vector3D(0,0,0); // initialisation otherwise intellij gets mad
+        // find a vector which is not inline with other vectors
+        boolean valid = false; int i = 2;
+        while(!valid && i < points.length) {
+            point2 = points[i].point;
+            vec2 = new Vector3D(point2.x - point0.x, point2.y - point0.y, point2.z - point0.z);
+            double angle = Math.abs(vec1.angleTo(vec2));
+            if(angle > 0.1 && angle < 2*Math.PI - 0.1){
+                //  if the angle between the vectors is between a threshold, the two vectors are valid.
+                // else, calculate the second vector using a different set of points.
+                valid = true;
+            }}
+        if(!valid){throw new RuntimeException("Could not calculate normal of face");}
+        normal = vec1.cross(vec2);
+    }
+//    private void bakePerspectiveMatrices() {
+//        // one mapping matrix for each triangle
+//        // to achieve perspective mapping, we need to convert from the 2d screen position, to the 3d world position by
+//        // reverse projecting and interpolating in the triangle - this gives camera coordinates.
+//        // next, we need to use the inverse camera matrix to convert from camera coordinates to world coordinates.
+//        // then, we can use the perspective mapping matrix unique and baked to each triangle on each face to convert from those world coordinates, into uv coordinates.
+//
+//
+//
+//        real01Vec.createFrom2Points(points[0].getRotatedPoint(), points[1].getRotatedPoint());
+//        real02Vec.createFrom2Points(points[0].getRotatedPoint(), points[2].getRotatedPoint());
+//        // scaVec.createFrom2Points(points[0].getProjectedPoint(), points[1].getProjectedPoint());
+//        UV01Vec.createFrom2Points(UVPoints[0], UVPoints[1]);
+//        UV02Vec.createFrom2Points(UVPoints[0], UVPoints[2]);
+//        // it must remain as the same object so pointers elsewhere still work.
+//        // invert x and y coordinates because in rotated coordinates, they are the wrong way round.
+//        perspectiveMappingMatrix.setItems(new double[][]{
+//                {0,1,0,0},
+//                {1,0,0,0},
+//                {0,0,1,0},
+//                {0,0,0,1},
+//        });
+//
+//        traVec.createFrom2Points(new Point3D(UVPoints[0].y, UVPoints[0].x, 0), points[0].getRotatedPoint());
+//        Matrix tMat = new Matrix(4, 4);
+////        tMat.setItems(new double[][]{
+////                {1, 0, 0, traVec.x},
+////                {0, 1, 0, traVec.y},
+////                {0, 0, 1, traVec.z},
+////                {0, 0, 0, 1},
+////        });
+//        tMat.setItems(new double[][]{
+//                {1, 0, 0, 0},
+//                {0, 1, 0, 0},
+//                {0, 0, 1, points[0].getRotatedPoint().z},
+//                {0, 0, 0, 1},
+//        });
+//        double scale = 0.1;//(real01Vec.getLength() / UV01Vec.getLength());
+//        Matrix scaMat = new Matrix(4, 4);
+//        scaMat.setItems(new double[][]{
+//                {scale, 0,     0,     0},
+//                {0,     scale, 0,     0},
+//                {0,     0,     scale, 0},
+//                {0,     0,     0,     1}
+//        });
+//        // find z rotation and define matrix
+//        double zAng = new Vector2D(real01Vec.x, real01Vec.y).angleTo(UV01Vec);
+//        Matrix zMat = new Matrix(4, 4);
+//        zMat.setItems(new double[][]{
+//                {Math.cos(zAng),  Math.sin(zAng), 0, 0},
+//                {-Math.sin(zAng), Math.cos(zAng), 0, 0},
+//                {0, 0, 1, 0},
+//                {0, 0, 0, 1}}
+//        );
+//        // rotate "real" vectors using the Z matrix
+//        result = zMat.multiplyPoint3raw(real01Vec.x, real01Vec.y, real01Vec.z);
+//        real01Vec.x = result[0];real01Vec.y = result[1];real01Vec.z = result[2];
+//        result = zMat.multiplyPoint3raw(real02Vec.x, real02Vec.y, real02Vec.z);
+//        real02Vec.x = result[0];real02Vec.y = result[1];real02Vec.z = result[2];
+//        // invert the Z matrix (todo cleanup)
+//        zMat.setItems(new double[][]{
+//                {Math.cos(-zAng),  Math.sin(-zAng), 0, 0},
+//                {-Math.sin(-zAng), Math.cos(-zAng), 0, 0},
+//                {0, 0, 1, 0},
+//                {0, 0, 0, 1}}
+//        );
+//        // find Y rotation and define matrix
+//        double yAng = new Vector2D(real01Vec.x, real01Vec.z).angleTo(new Vector2D(UV01Vec.x, 0));
+//        Matrix yMat = new Matrix(4, 4);
+//        yMat.setItems(new double[][]{
+//                {Math.cos(yAng), 0, -Math.sin(yAng), 0},
+//                {0,              1, 0, 0},
+//                {Math.sin(yAng), 0, Math.cos(yAng), 0},
+//                {0, 0, 0, 1}}
+//        );
+//        result = yMat.multiplyPoint3raw(real02Vec.x, real02Vec.y, real02Vec.z);
+//        real02Vec.x = result[0];real02Vec.y = result[1];real02Vec.z = result[2];
+//        yMat.setItems(new double[][]{
+//                {Math.cos(-yAng), 0, -Math.sin(-yAng), 0},
+//                {0,              1, 0, 0},
+//                {Math.sin(-yAng), 0, Math.cos(-yAng), 0},
+//                {0, 0, 0, 1}}
+//        );
+//        double xAng = new Vector2D(real02Vec.y, real02Vec.z).angleTo(new Vector2D(UV01Vec.x, 0)); // this is fine
+//        Matrix xMat = new Matrix(4, 4);
+//        xMat.setItems(new double[][]{
+//                {1, 0, 0, 0},
+//                {0, Math.cos(-xAng), Math.sin(-xAng), 0},
+//                {0, -Math.sin(-xAng), Math.cos(-xAng), 0},
+//                {0, 0, 0, 1}}
+//        );
+//        // Matrix that returns the position on a texture from 3d camera space on the face of an object
+//        // we already know the Z depth because of calculations for Z buffers.
+//        //perspectiveMappingMatrix.multiply(scaMat);
+//        perspectiveMappingMatrix.multiply(scaMat.multiplyGetResult(tMat));
+//        //perspectiveMappingMatrix.multiply((scaMat.multiplyGetResult(xMat.multiplyGetResult(yMat.multiplyGetResult(zMat.multiplyGetResult(tMat))))));
+//        // perspectiveMappingMatrix.multiply(scaMat.multiplyGetResult(zMat.multiplyGetResult(yMat.multiplyGetResult(xMat.multiplyGetResult(tMat)))));
+//    }
+}
diff --git a/src/main/java/uk/org/floop/epq3d/HTTPPost.java b/src/main/java/uk/org/floop/epq3d/HTTPPost.java
new file mode 100644
index 0000000..c46cb8b
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/HTTPPost.java
@@ -0,0 +1,46 @@
+package uk.org.floop.epq3d;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+public class HTTPPost {
+    public void invokePost(JsonWriter json) {
+
+        try {
+            String requestBody = json.file.toJSONString();
+            HttpClient client = HttpClient.newHttpClient();
+            HttpRequest request = HttpRequest
+                    .newBuilder()
+                    .uri(URI.create("http://localhost:8080"))
+                    .POST(HttpRequest.BodyPublishers.ofString(requestBody))
+                    .header("Accept", "application/json")
+                    .build();
+
+            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
+
+            //System.out.println(response.body());
+        } catch (IOException | InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+//    private String prepareRequest() throws JsonProcessingException {
+//        var values = new HashMap<String, String>() {
+//            {
+//                put("Id", "12345");
+//                put("Customer", "Roger Moose");
+//                put("Quantity", "3");
+//                put("Price","167.35");
+//            }
+//        };
+//
+//        var objectMapper = new ObjectMapper();
+//        String requestBody = objectMapper.writeValueAsString(values);
+//        return requestBody;
+//    }
+
+}
+
diff --git a/src/main/java/uk/org/floop/epq3d/Line2d.java b/src/main/java/uk/org/floop/epq3d/Line2d.java
new file mode 100644
index 0000000..5054308
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/Line2d.java
@@ -0,0 +1,140 @@
+package uk.org.floop.epq3d;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+// handles line and line drawing
+public class Line2d {
+    public Point2D point1;
+    public Point2D point2;
+
+    public boolean isDrawn;
+
+    // initializer variables
+    boolean is_initialised =  false;
+    private Point2D realPoint1; private Point2D realPoint2;
+    private int dx;
+    private int dy; private int sDy;
+    private char iterator;
+    private Point2D xy;
+    int[] returnVal = new int[]{0,0};
+    private double gradient; // z gradient of the line in terms of X
+
+    // drawing progress variables
+    private int iteratorVal;
+    private int D;
+    public Line2d(Point2D _point1, Point2D _point2, boolean _isDrawn){
+        point1 = _point1;
+        point2 = _point2;
+        isDrawn = _isDrawn;
+    }
+
+    /**
+     * Draws onto a given image
+     * @param img the image to draw onto
+     */
+    public void draw(BufferedImage img){
+        if (!is_initialised){initialise();}
+        if (iterator == 'x'){
+            for (int i = 0; i <= dx; i++){
+                nextPix();
+                if(returnVal[0] >= 0 && returnVal[0] < img.getWidth() && returnVal[1] >= 0 && returnVal[1] < img.getHeight()) { // check if drawing in bounds
+                    img.setRGB(returnVal[0], returnVal[1], Color.HSBtoRGB(1, 1, 1));
+                }
+
+            }
+        } else {
+            for (int i = 0; i <= dy; i++){
+                nextPix();
+                if(returnVal[0] >= 0 && returnVal[0] < img.getWidth() && returnVal[1] >= 0 && returnVal[1] < img.getHeight()) { // check if drawing in bounds
+                    img.setRGB(returnVal[0], returnVal[1], Color.HSBtoRGB(.5f, 1, 1));
+                }
+            }
+        }
+        is_initialised = false;
+        /*
+         debug - draws the start and end of each line in white and green
+         img.setRGB(realPoint1.intX(), realPoint1.intY(), Color.blue.getRGB());
+         img.setRGB(realPoint2.intX(), realPoint2.intY(), Color.green.getRGB());
+        */
+    }
+
+    /**
+     * Performs the initial calculations required to draw the line
+     * Is automatically called whenever nextPix() or draw() are called when initialise() has not run.
+     */
+    public void initialise(){
+        // initialise brensenham algorithm
+        if (point2.x > point1.x) {
+            realPoint1 = point1;
+            realPoint2 = point2;
+        } else {  // if dx is less than zero, swap the points around
+            realPoint1 = point2;
+            realPoint2 = point1;
+        }
+        dx = realPoint2.x - realPoint1.x;
+        dy = realPoint2.y - realPoint1.y;
+        sDy = (int) Math.signum(dy); dy = Math.abs(dy);
+
+        xy = new Point2D(realPoint1.x,realPoint1.y);  // starting point
+
+        // check if dy is greater than or less than dx
+        if (dy < dx){
+            iterator = 'x';
+            D = (2*dy) - dx;
+        }else{
+            iterator='y';
+            D = (2*dx) - dy;
+        }
+        iteratorVal = 0;
+
+        // init other variables
+        // dz / dx
+        gradient = (realPoint2.z-realPoint1.z) /
+                (realPoint2.x - realPoint1.x);
+        is_initialised = true;
+    }
+
+    /**
+     * @return the x and y coordinate of the next pixel in the line.
+     */
+    public int[] nextPix(){
+        if(!is_initialised){initialise();}
+        returnVal[0] = xy.x;
+        returnVal[1] = xy.y;
+        if (iterator=='x' && iteratorVal < dx && iteratorVal != -1){
+            if (D > 0) {
+                D += 2 * (dy - dx);
+                xy.y += sDy;
+            } else {
+                D += 2*dy;
+            }
+            xy.x += 1; // the line is always drawn left to right
+        } else if(iterator =='y' && iteratorVal < dy && iteratorVal != -1) {
+            if (D > 0) {
+                D += 2 * (dx-dy);
+                xy.x += 1; // the line is always drawn left to right
+            } else {
+                D += 2*dx;
+            }
+            xy.y += sDy;
+
+        }
+        else if(iteratorVal != -1) {
+            iteratorVal = -1;
+            returnVal[0] = realPoint2.x;
+            returnVal[1] = realPoint2.y;
+            return returnVal;
+        }
+        else {
+            is_initialised = false;
+            throw new RuntimeException("Accessed too many line pixels");
+        }
+        iteratorVal += 1;
+        return returnVal;
+    }
+    public double getZVal(int x){
+        return realPoint1.z + (gradient * (x - realPoint1.x));
+    }
+    }
+
diff --git a/src/main/java/uk/org/floop/epq3d/Matrix.java b/src/main/java/uk/org/floop/epq3d/Matrix.java
new file mode 100644
index 0000000..5c980a9
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/Matrix.java
@@ -0,0 +1,211 @@
+package uk.org.floop.epq3d;
+
+public class Matrix {
+    protected int x;
+    protected int y;
+    private double[][] items;
+
+    public Matrix(int _x, int _y){
+        x = _x; y= _y;
+        items = new double[y][x];
+    }
+    public double getItem(int _x, int _y){
+        return items[_y][_x];
+    }
+    public void setItem(int _x, int _y, double val){
+        items[_y][_x] = val;
+    }
+    public double[][] getItems() {
+        return items;
+    }
+    public void setItems(double[][] newItems){
+        items = newItems;
+    }
+    public Matrix multiplyGetResult(Matrix multiplier) {
+        Matrix result = new Matrix(multiplier.x, this.y);
+        double newItem;
+        if(x== multiplier.y){
+            for(int rx = 0; rx< result.x; rx+=1){
+                for(int ry = 0; ry< result.y; ry+=1){
+                    newItem = 0;
+                    for(int i = 0; i<x; i+=1){
+                        newItem += this.getItem(i, ry)* multiplier.getItem(rx, i);
+                    }
+                    result.setItem(rx, ry, newItem);
+            }}
+        } else {
+            throw new RuntimeException("wrong dimensions");
+        }
+        return result;
+    }
+    public void multiply(Matrix multiplier) {
+        Matrix result = new Matrix(multiplier.x, this.y);
+        double newItem;
+        if(x== multiplier.y){
+            for(int rx = 0; rx< result.x; rx+=1){
+                for(int ry = 0; ry< result.y; ry+=1){
+                    newItem = 0;
+                    for(int i = 0; i<x; i+=1){
+                        newItem += this.getItem(i, ry)* multiplier.getItem(rx, i);
+                    }
+                    result.setItem(rx, ry, newItem);
+                }}
+        } else {
+            throw new RuntimeException("wrong dimensions");
+        }
+        setItems(result.getItems());
+    }
+    public void multiplyPoint3to(Point3D point, Point3D result) {
+        if(x==3){
+            result.x = point.x * getItem(0,0) + point.y* getItem(1,0) + point.z* getItem(2,0);
+            result.y = point.x * getItem(0,1) + point.y* getItem(1,1) + point.z* getItem(2,1);
+            result.z = point.x * getItem(0,2) + point.y* getItem(1,2) + point.z* getItem(2,2);
+        } else if(x == 4){
+            result.x = point.x * getItem(0,0) + point.y* getItem(1,0) + point.z* getItem(2,0) + getItem(3,0);
+            result.y = point.x * getItem(0,1) + point.y* getItem(1,1) + point.z* getItem(2,1) + getItem(3,1);
+            result.z = point.x * getItem(0,2) + point.y* getItem(1,2) + point.z* getItem(2,2) + getItem(3,2);
+        } else {throw new RuntimeException("wrong-dimensions");}
+    }
+    public double[] multiplyPoint3raw(double _x, double _y, double _z) {
+        double[] result = new double[3];
+        if(x ==3){
+            result[0] = _x * getItem(0,0) + _y * getItem(1,0) + _z * getItem(2,0);
+            result[1] = _x * getItem(0,1) + _y * getItem(1,1) + _z * getItem(2,1);
+            result[2] = _x * getItem(0,2) + _y * getItem(1,2) + _z * getItem(2,2);
+        } else if(x == 4){
+            result[0] = _x * getItem(0,0) + _y * getItem(1,0) + _z * getItem(2,0) + getItem(3,0);
+            result[1] = _x * getItem(0,1) + _y * getItem(1,1) + _z * getItem(2,1) + getItem(3,1);
+            result[2] = _x * getItem(0,2) + _y * getItem(1,2) + _z * getItem(2,2) + getItem(3,2);
+        } else {throw new RuntimeException("wrong-dimensions");}
+        return result;
+    }
+    public void multiplyPoint2to(Point2D point, Point2D result) {
+        if(x==2){
+            result.x = (int)(point.x * getItem(0,0) + point.y* getItem(1,0));
+            result.y = (int)(point.x * getItem(0,1) + point.y* getItem(1,1));
+        } else if(x == 3){
+            result.x = (int)(point.x * getItem(0,0) + point.y* getItem(1,0) + getItem(2,0));
+            result.y = (int)(point.x * getItem(0,1) + point.y* getItem(1,1) + getItem(2,1));
+        } else {throw new RuntimeException("wrong-dimensions");}
+    }
+    public double[] multiplyPoint2raw(double px, double py) {
+        double[] result = new double[2];
+        if(x==2){
+            result[0] = (int)(px * getItem(0,0) + py* getItem(1,0));
+            result[1] = (int)(px * getItem(0,1) + py* getItem(1,1));
+        } else if(x==3){
+            result[0] = (int)(px * getItem(0,0) + py* getItem(1,0) + getItem(2,0));
+            result[1] = (int)(px * getItem(0,1) + py* getItem(1,1) + getItem(2,1));
+        } else {throw new RuntimeException("wrong-dimensions");}
+        return result;
+    }
+    public Matrix getInverse() {
+        // handles the first level of determinant /
+        // adjoint finding, and then uses getDeterminant for finding the determinants of sub-matrices
+        Matrix result = new Matrix(x, y);
+        /* find determinant and adjoint
+         since both these calculations use some of the same values, I'm hardcoding the first level here for an extra
+         2% performance gain*/
+        Matrix adjoint = new Matrix(x, y);
+        double determinant = 0;
+
+        double[] COFResult;
+        for(int origX = 0; origX < x; origX +=1) {
+            for (int origY = 0; origY < x; origY += 1) {
+                if (origX == 0){
+                    COFResult = getCofactor(origX, origY, true);
+                    determinant += COFResult[1];
+                }else{
+                    COFResult = getCofactor(origX, origY, false);
+                }
+                adjoint.setItem(origX, origY, COFResult[0]);
+            }
+        }
+        // transpose the adjoint matrix (unused because it's easier to do it while copying across
+        /*{
+            double temp;
+            for(int mx = 0; mx < x; mx+=1){
+                for(int my = 0; my <= mx; my += 1){
+                    if (my != mx){
+                        temp = adjoint.getItem(mx, my);
+                        adjoint.setItem(mx, my, adjoint.getItem(my, mx));
+                        adjoint.setItem(my, mx, temp);
+                    }
+                }
+            }
+        }*/
+        // copy the matrix to the result and divide by the determinant
+        for(int mx = 0; mx<x;mx+=1){
+            for(int my = 0; my<y;my+=1){
+                result.setItem(my, mx, adjoint.getItem(mx, my) / determinant);
+            }
+        }
+        return result;
+    }
+    // gets the cofactor element of a matrix specified by x and y
+    // if given true, it also returns the determinant
+    public double[] getCofactor(int origX, int origY, Boolean getDeterminant){
+        Matrix minor = new Matrix(x-1, y-1);
+        int setX = 0;
+        for (int getX = 0; getX < x; getX += 1) {
+            // if newX is the same as the x value of the selected element, skip that column
+            if (getX == origX) {
+                getX += 1;
+                // bug where if the selected element is also the final element, we 'skip' it to a point outside the matrix
+                // there's probably a better fix but this works
+                if (!(getX < x)) {
+                    break;
+                }
+            }
+            int setY = 0;
+            for (int getY = 0; getY < y; getY += 1) {
+                if (getY == origY) {
+                    getY += 1;
+                    // bug where if the selected element is also the final element, we 'skip' it to a point outside the matrix
+                    // there's probably a better fix but this works
+                    if (!(getY < y)) {
+                        break;
+                    }
+                }
+                minor.setItem(setX, setY, getItem(getX, getY));
+                setY += 1;
+            }
+            setX += 1;
+        }
+        /*
+         multiplying the determinant of a minor, by -1^(i+j), gives the co factor.
+         the sum of all the cofactors in a row or column equals the determinant
+         */
+        double coFactor = Math.pow(-1, origX + origY) * minor.getDeterminant();
+        if (getDeterminant){
+            double determinant = getItem(origX, origY) * coFactor;
+            return new double[]{coFactor, determinant};
+        }
+        else{
+            return new double[]{coFactor};
+
+        }
+    }
+    /*
+    gets the determinant of a matrix
+    recursively calls getCoFactor, which in turn calls getDeterminant, for handling of any sized matrix
+    probably really inefficient, it's n^n or something ridiculous,
+    but hopefully I'll only need it for 3x3 mats, maybe some 4x4
+    */
+    public double getDeterminant() {
+        double determinant = 0;
+        if (x != y) {
+            throw new RuntimeException("Determinants are only defined for square matrices, given: x=" + x + " y=" + y);
+        }
+        else if (x == 2){
+            return getItem(0,0)*getItem(1,1) -
+                    getItem(1,0)*getItem(0,1);
+        }
+        else{
+            for(int i = 0; i<x; i+=1) {
+                determinant += getCofactor(i, 0, true)[1];
+            }
+        }
+        return determinant;
+    }
+}
diff --git a/src/main/java/uk/org/floop/epq3d/Object3d.java b/src/main/java/uk/org/floop/epq3d/Object3d.java
new file mode 100644
index 0000000..4dee73c
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/Object3d.java
@@ -0,0 +1,98 @@
+package uk.org.floop.epq3d;
+
+import java.awt.image.BufferedImage;
+
+public class Object3d {
+    public PointComp[] points;
+    public int[][] faceList;
+    public Point2D[][] uvPoints;
+
+    public Face[] faces;
+    public Point3D boundingSphereC;
+    public double boundingSphereR;
+
+    public boolean hasEdges;
+    public BufferedImage texture;
+
+    public Object3d(PointComp[] _points, int[][] _faceList, Point2D[][] _uvPoints, boolean _hasEdges, BufferedImage _texture, boolean initialise){
+        points = _points;
+        faceList = _faceList;
+        uvPoints = _uvPoints;
+        hasEdges = _hasEdges;
+        texture = _texture;
+        if(initialise){initialise();}
+    }
+    public void invalidate(){
+        for (Face face:
+             faces) {
+            face.invalidate();
+        }
+    }
+    public void draw(BufferedImage img, BufferedImage zBuf, BufferedImage debugImg, Matrix camMatrix, double FPDis, int scrX, int scrY, Point3D playerPos){
+        int iterator = 0;
+        for (Face face:
+             faces) {
+            // check whether faces are pointing towards or away from the camera
+
+            Vector3D camVec = new Vector3D(0,0,0);
+            camVec.createFrom2Points(playerPos, face.points[0].point);
+            int numberOfPixels = 0;
+            if(face.normal.angleTo(camVec) <= Math.PI/2-0.01){
+                numberOfPixels = face.draw(img, zBuf, debugImg, camMatrix, FPDis, scrX, scrY);
+            }
+            debugImg.getGraphics().drawString(iterator + ": " +numberOfPixels , 10, 20 + 10*iterator);
+            iterator += 1;
+        }
+    }
+    public void initialise(){
+        // init faces
+        faces = new Face[faceList.length];
+        for (int face = 0; face < faceList.length; face+= 1) {
+            faces[face] = new Face();
+            faces[face].points = new PointComp[faceList[face].length];
+            for (int point = 0; point < faceList[face].length; point += 1){
+                faces[face].points[point] = points[faceList[face][point]];
+            }
+            faces[face].hasEdges = hasEdges;
+            faces[face].texture = texture;
+            faces[face].UVPoints = uvPoints[0];
+            faces[face].initialise();
+        }
+        // init bounding sphere
+        double distance = 0;
+        double newDis;
+        Point3D pointA = points[0].point;
+        Point3D pointB = points[0].point;
+        // todo - maybe use some vector classes?7
+        // i can't be bothered
+        for (int i = 1; i < points.length; i+=1) {
+            newDis = Math.pow(points[0].point.x - points[i].point.x, 2) +
+                    Math.pow(points[0].point.y - points[i].point.y, 2) +
+                    Math.pow(points[0].point.z - points[i].point.z, 2);
+            if (newDis >= distance){
+                pointA = points[i].point;
+                distance = newDis;}}
+        for (PointComp point : points) {
+            newDis = Math.pow(pointA.x - point.point.x, 2) +
+                    Math.pow(pointA.y - point.point.y, 2) +
+                    Math.pow(pointA.z - point.point.z, 2);
+            if (newDis >= distance){
+                pointB = point.point;
+                distance = newDis;}}
+        boundingSphereC = new Point3D(
+                (pointA.x + pointB.x) / 2,
+                (pointA.y + pointB.y)/2,
+                (pointA.z + pointB.z)/2);
+        boundingSphereR = Math.sqrt(distance)/2;
+        for (PointComp point:
+                points) {
+            distance = Math.sqrt(
+                    Math.pow(boundingSphereC.x - point.point.x, 2)+
+                    Math.pow(boundingSphereC.y - point.point.y, 2)+
+                    Math.pow(boundingSphereC.z - point.point.z, 2));
+            if(distance > boundingSphereR){
+                boundingSphereR = distance;
+            }
+        }
+    }
+}
diff --git a/src/main/java/uk/org/floop/epq3d/ObjectCollection.java b/src/main/java/uk/org/floop/epq3d/ObjectCollection.java
new file mode 100644
index 0000000..5e2cd6b
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/ObjectCollection.java
@@ -0,0 +1,129 @@
+package uk.org.floop.epq3d;
+
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+
+// stores both objects and other object collections
+public class ObjectCollection {
+    public ArrayList<PointComp> points = new ArrayList<PointComp>();
+    private Object3d collectionObject;
+    public double boundingSphereR;
+    public Point3D boundingSphereC;
+
+
+    public ArrayList<ObjectCollection> subCollections = new ArrayList<ObjectCollection>();
+    public ArrayList<Object3d> objects = new ArrayList<Object3d>();
+
+    public void invalidate(boolean invalidatePoints){
+        // the first level of object collections should contain all the points for the sublevels.
+        // this means that we only need to invalidate them at the top level
+        if(invalidatePoints){for (PointComp point:
+             points) {
+            point.invalidate();
+        }}
+        for(ObjectCollection subCollection:
+        subCollections){
+            subCollection.invalidate(false);
+        }
+        for(Object3d object:
+        objects){
+            object.invalidate();
+        }
+    }
+
+    public void draw(BufferedImage img, BufferedImage zBuf, BufferedImage debugImg, Matrix camMatrix, Point3D playerPos, Plane[] frustumPlanes, double FPDis, int scrX, int scrY){
+        for (Object3d object:
+             objects) {
+            boolean draw = true;
+            int i = 0;
+            for (Plane plane:
+                 frustumPlanes) {
+                    debugImg.getGraphics().drawString(
+                            "Dis: " + String.format("%.1f", plane.getDistance(object.boundingSphereC)), 500, 10 + 20*i);
+                if(plane.getDistance(object.boundingSphereC) < -object.boundingSphereR){
+                    draw = false;
+                    break;}
+                i += 1;
+            }
+            if(draw){object.draw(img, zBuf, debugImg, camMatrix, FPDis, scrX, scrY, playerPos);}
+        }
+        // todo check for frustum culling
+        for(ObjectCollection collection:
+        subCollections){
+            boolean draw = true;
+            int i = 0;
+            for (Plane plane:
+                    frustumPlanes) {
+                debugImg.getGraphics().drawString(
+                        "Dis: " + String.format("%.1f", plane.getDistance(collection.boundingSphereC)), 500, 10 + 20*i);
+                if(plane.getDistance(collection.boundingSphereC) < -collection.boundingSphereR){
+                    draw = false;
+                    break;}
+                i += 1;
+            }
+            collection.draw(img, zBuf, debugImg, camMatrix, playerPos, frustumPlanes, FPDis, scrX, scrY);
+        }
+    }
+    public void addCollection(int[] pointList){
+        subCollections.add(new ObjectCollection());
+    }
+    // making sure that all the point indices make sense might be a nightmare but ehh
+    public void addObject(Object3d object){
+        PointComp[] newpoints = object.points;
+        objects.add(object);
+        // add the new object
+
+        // merge lists
+        for (PointComp newpoint : newpoints) {
+            boolean found = false;
+            // find out if any of the new points already exist in the list
+            for (PointComp point : points) {
+                if (newpoint == point) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                points.add(newpoint);
+            }
+        }
+    }
+    public void initialise(){
+        // init bounding sphere
+        double distance = 0;
+        double newDis;
+        Point3D pointA = points.get(0).point;
+        Point3D pointB = points.get(0).point;
+        // todo - maybe use some vector classes?
+        for (int i = 1; i < points.size(); i+=1) {
+            newDis = Math.pow(points.get(0).point.x - points.get(i).point.x, 2) +
+                    Math.pow(points.get(0).point.y - points.get(i).point.y, 2) +
+                    Math.pow(points.get(0).point.z - points.get(i).point.z, 2);
+            if (newDis >= distance){
+                pointA = points.get(i).point;
+                distance = newDis;}}
+        for (PointComp point : points) {
+            newDis = Math.pow(pointA.x - point.point.x, 2) +
+                    Math.pow(pointA.y - point.point.y, 2) +
+                    Math.pow(pointA.z - point.point.z, 2);
+            if (newDis >= distance){
+                pointB = point.point;
+                distance = newDis;}}
+        boundingSphereC = new Point3D(
+                (pointA.x + pointB.x) / 2,
+                (pointA.y + pointB.y)/2,
+                (pointA.z + pointB.z)/2);
+        boundingSphereR = Math.sqrt(distance)/2;
+        boolean valid = false;
+        for (PointComp point:
+                points) {
+            distance = Math.sqrt(
+                    Math.pow(boundingSphereC.x - point.point.x, 2)+
+                            Math.pow(boundingSphereC.y - point.point.y, 2)+
+                            Math.pow(boundingSphereC.z - point.point.z, 2));
+            if(distance > boundingSphereR){
+                boundingSphereR = distance;
+            }
+        }
+    }
+}
diff --git a/src/main/java/uk/org/floop/epq3d/Plane.java b/src/main/java/uk/org/floop/epq3d/Plane.java
new file mode 100644
index 0000000..586a272
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/Plane.java
@@ -0,0 +1,29 @@
+package uk.org.floop.epq3d;
+
+public class Plane {
+    public Point3D mainPoint;
+    public Vector3D normalVector;
+
+    public double getDistance(Point3D point){
+        Vector3D vec = new Vector3D(0,0,0);
+        vec.createFrom2Points(mainPoint, point);
+        return normalVector.dot(vec);
+    }
+    public void initFrom3Points(Point3D _mainPoint, Point3D point1, Point3D point2){
+        // find normal vector to 3 points
+        Vector3D vec1 = new Vector3D(0,0,0);
+        Vector3D vec2 = new Vector3D(0,0,0);
+        vec1.createFrom2Points(_mainPoint, point1);
+        vec2.createFrom2Points(point1, point2);
+        initFromPointAndVector(_mainPoint, vec1.cross(vec2));
+    }
+    public void initFromPointAndVector(Point3D _mainPoint, Vector3D _normalVector){
+        // convert to unit vector
+        mainPoint = _mainPoint;
+        double len = _normalVector.getLength();
+        normalVector = new Vector3D(
+                _normalVector.x / len,
+                _normalVector.y / len,
+                _normalVector.z / len);
+    }
+}
diff --git a/src/main/java/uk/org/floop/epq3d/Player.java b/src/main/java/uk/org/floop/epq3d/Player.java
new file mode 100644
index 0000000..a02f22a
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/Player.java
@@ -0,0 +1,225 @@
+package uk.org.floop.epq3d;
+
+import java.awt.*;
+import java.awt.event.KeyEvent;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImageObserver;
+
+public class Player {
+
+    public Plane[] frustumPlanes = new Plane[6];
+    // image that represents the player's position on the board
+    private BufferedImage image;
+    // current position of the player on the board grid
+    private Matrix rotMatrix;
+    public Matrix camMatrix;
+    private Matrix invRotMatrix;
+    private Matrix invCamMatrix;
+    public double FOV = 100;
+    protected double Fpdis;
+    protected Point3D FPWorldPos;
+    protected PointComp[] ScreenCornerPosS = new PointComp[4];
+    private Point3D position = new Point3D(0,0,0);
+    private Point3D rotation = new Point3D(0,Math.PI / 2, 0);
+    private Point3D direction = new Point3D(0,0,0);
+    public Vector3D viewVector = new Vector3D(0,0,0);
+    public double mouseSensitivity = 0.005; // radians per pixel
+    // called when the player is initialised
+    public Player(int scrX, int scrY) {
+        Fpdis = 1/Math.tan(Math.toRadians(FOV)/2d);
+        for(int i = 0; i < 6; i+=1){
+            frustumPlanes[i] = new Plane();
+        }
+        double yVal = (double)scrY / (double)scrX;
+        // flip x and y because reasons
+        ScreenCornerPosS[0] = new PointComp(-yVal,-1,  0);
+        ScreenCornerPosS[1] = new PointComp(yVal,-1,  0);
+        ScreenCornerPosS[2] = new PointComp(-yVal,1,  0);
+        ScreenCornerPosS[3] = new PointComp(yVal,1,  0);
+    }
+
+    // unused because the player should not be drawn
+    // I'm leaving it in as a hint to buffered Images
+    public void draw(Graphics g, ImageObserver observer) {
+        int width = 100;
+        int height = 100;
+
+        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB );
+
+        for ( int py = 0; py < height; py++ ) {
+            for ( int px = 0; px < width; px++ ) {
+                // Set the pixel colour of the image n.b. x = cc, y = rc
+                img.setRGB(px, py, Color.BLACK.getRGB() );
+            }
+        }
+        g.drawImage(img, 0, 0, observer);
+    }
+
+    public void keyPressed(int key) {
+        if (key == KeyEvent.VK_W && direction.x < 1) {
+            direction.x += 1;
+        } else if (key == KeyEvent.VK_S && -direction.x < 1) {
+            direction.x += -1;
+        } else if (key == KeyEvent.VK_A && direction.y < 1) {
+            direction.y += 1;
+        } else if (key == KeyEvent.VK_D && -direction.y < 1) {
+            direction.y += -1;
+        } else if (key == KeyEvent.VK_SPACE && direction.z < 1) {
+            direction.z += 1;
+        } else if (key == KeyEvent.VK_SHIFT && -direction.z < 1) {
+            direction.z += -1;
+        }
+    }
+    public void keyReleased(int key){
+        if (key == KeyEvent.VK_W && direction.x > -1) {
+            direction.x -= 1;
+        } else if (key == KeyEvent.VK_S && -direction.x > -1) {
+            direction.x -= -1;
+        } else if (key == KeyEvent.VK_A && direction.y > -1) {
+            direction.y -= 1;
+        } else if (key == KeyEvent.VK_D && -direction.y > -1) {
+            direction.y -= -1;
+        } else if (key == KeyEvent.VK_SPACE && direction.z > -1) {
+            direction.z -= 1;
+        } else if (key == KeyEvent.VK_SHIFT && -direction.z > -1) {
+            direction.z -= -1;
+        }
+    }
+    public void tick(Point2D mouseRel) {
+        // gets called once every tick, before the repainting process happens.
+        // change rotation depending on mouse movement
+        rotation.z -= mouseRel.x * mouseSensitivity;
+        // force z rotation to wrap around
+        if(rotation.z<-Math.PI){
+            rotation.z = Math.PI;
+        } else if(rotation.z>Math.PI){
+            rotation.z = -Math.PI;
+        }
+        rotation.y += mouseRel.y * mouseSensitivity;
+        // max out y rotation at 0 and -90 degrees
+        if(rotation.y < 0){
+            rotation.y = 0;
+        } else if(rotation.y > Math.PI){
+            rotation.y = Math.PI;
+        }
+        // define rotation and translation matrices
+        Matrix zMat = new Matrix(3, 3);
+        zMat.setItems(new double[][]{
+                {Math.cos(rotation.z),  Math.sin(rotation.z), 0},
+                {-Math.sin(rotation.z), Math.cos(rotation.z), 0},
+                {0,                      0,                     1}}
+        );
+        Matrix yMat = new Matrix(3, 3);
+        yMat.setItems(new double[][]{
+                {Math.cos(rotation.y), 0, -Math.sin(rotation.y)},
+                {0,                    1, 0                    },
+                {Math.sin(rotation.y), 0, Math.cos(rotation.y) }}
+        );
+        Matrix xMat = new Matrix(3, 3);
+        xMat.setItems(new double[][]{
+                {1, 0,                    0                   },
+                {0, Math.cos(rotation.x), Math.sin(rotation.x)},
+                {0, -Math.sin(rotation.x), Math.cos(rotation.x)}}
+        );
+        // calculate inverse matrices
+        Matrix izMat = new Matrix(3, 3);
+        izMat.setItems(new double[][]{
+                {Math.cos(-rotation.z),  Math.sin(-rotation.z), 0},
+                {-Math.sin(-rotation.z), Math.cos(-rotation.z), 0},
+                {0,                      0,                     1}}
+        );
+        Matrix iyMat = new Matrix(3, 3);
+        iyMat.setItems(new double[][]{
+                {Math.cos(-rotation.y), 0, -Math.sin(-rotation.y)},
+                {0,                    1, 0                    },
+                {Math.sin(-rotation.y), 0, Math.cos(-rotation.y) }}
+        );
+        Matrix ixMat = new Matrix(3, 3);
+        ixMat.setItems(new double[][]{
+                {1, 0,                    0                   },
+                {0, Math.cos(-rotation.x), Math.sin(-rotation.x)},
+                {0, -Math.sin(-rotation.x), Math.cos(-rotation.x)}}
+        );
+        // apply izMat to direction vector
+        try {
+            Point3D dirvec = new Point3D(0,0,0);
+            izMat.multiplyPoint3to(direction, dirvec);
+            position.translate(dirvec);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        Matrix traMat = new Matrix(4, 3);
+        traMat.setItems(new double[][]
+                {{1, 0, 0, -position.x},
+                 {0, 1, 0, -position.y},
+                 {0, 0, 1, -position.z}}
+        );
+        // multiply out matrices
+        rotMatrix = xMat.multiplyGetResult(yMat).multiplyGetResult(zMat);
+        camMatrix = rotMatrix.multiplyGetResult(traMat);
+        invRotMatrix = izMat.multiplyGetResult(iyMat).multiplyGetResult(ixMat);
+        // I really don't want to do it like this, but I don't think there's any other way without messing up
+        // all my other matrices :(
+        invCamMatrix = new Matrix(4, 3);
+        for(int x = 0; x<3; x+=1){
+            for(int y = 0; y<3; y+=1){
+                invCamMatrix.setItem(x, y, invRotMatrix.getItem(x, y));
+            }}
+        for(int y = 0; y<3; y+=1){
+            invCamMatrix.setItem(3, y, -traMat.getItem(3, y));
+        }
+
+        // calculate view vector
+        Point3D viewPoint = new Point3D(0,0,0);
+        invRotMatrix.multiplyPoint3to(new Point3D(0,0,1), viewPoint);
+        // todo - work out why i need to reverse this
+        viewVector.x = viewPoint.x;viewVector.y=viewPoint.y;viewVector.z=viewPoint.z;
+
+        // calculate camera focal point and edges in world coordinates
+        FPWorldPos = new Point3D(
+                position.x - viewVector.x*Fpdis,
+                position.y - viewVector.y*Fpdis,
+                position.z - viewVector.z*Fpdis);
+        for (PointComp point:
+             ScreenCornerPosS) {
+            point.invalidate();
+            point.setRotatedPoint(invCamMatrix);
+        }
+        // find frustum planes
+        // near plane
+        //frustumPlanes[0].initFromPointAndVector(position, viewVector);
+        frustumPlanes[0].initFrom3Points(ScreenCornerPosS[0].getRotatedPoint(),
+                ScreenCornerPosS[1].getRotatedPoint(), ScreenCornerPosS[2].getRotatedPoint());
+        // far plane
+        int farPlaneDis = 1000;
+        frustumPlanes[1].initFromPointAndVector(
+                new Point3D(
+                        position.x + viewVector.x*farPlaneDis,
+                        position.y + viewVector.y*farPlaneDis,
+                        position.z + viewVector.z*farPlaneDis),
+                // invert the view vector because the normal needs to point the other way
+                new Vector3D(-viewVector.x, -viewVector.y, -viewVector.z));
+        // mid planes
+        // left plane
+        frustumPlanes[2].initFrom3Points(FPWorldPos,
+                ScreenCornerPosS[0].getRotatedPoint(), ScreenCornerPosS[1].getRotatedPoint());
+        // right plane
+        frustumPlanes[3].initFrom3Points(FPWorldPos,
+                ScreenCornerPosS[3].getRotatedPoint(), ScreenCornerPosS[2].getRotatedPoint());
+        // top plane
+        frustumPlanes[4].initFrom3Points(FPWorldPos,
+                ScreenCornerPosS[1].getRotatedPoint(), ScreenCornerPosS[3].getRotatedPoint());
+        // bottom plane
+        frustumPlanes[5].initFrom3Points(FPWorldPos,
+                ScreenCornerPosS[2].getRotatedPoint(), ScreenCornerPosS[0].getRotatedPoint());
+    }
+
+    // returnValue Functions
+    public Point3D getPos() {
+        return position;
+    }
+    public Point3D getRot() {
+        return rotation;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/uk/org/floop/epq3d/Point2D.java b/src/main/java/uk/org/floop/epq3d/Point2D.java
new file mode 100644
index 0000000..a18ca1d
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/Point2D.java
@@ -0,0 +1,22 @@
+package uk.org.floop.epq3d;
+
+import java.util.ArrayList;
+
+public class Point2D {
+    public int x;
+    public int y;
+    public double z;
+    // contains a Z value so that projected points can have their Z values calculated efficiently
+    public Point2D(int _x, int _y){
+        x = _x;
+        y = _y;
+    }
+    public void set(int _x, int _y){
+        x = _x;
+        y = _y;
+    }
+    public int[] get(){
+        return new int[]{x, y};
+    }
+
+}
diff --git a/src/main/java/uk/org/floop/epq3d/Point3D.java b/src/main/java/uk/org/floop/epq3d/Point3D.java
new file mode 100644
index 0000000..3eda4ed
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/Point3D.java
@@ -0,0 +1,38 @@
+package uk.org.floop.epq3d;
+
+public class Point3D {
+    public double x;
+    public double y;
+    public double z;
+
+    public Point3D(double _x, double _y, double _z){
+        x = _x;
+        y = _y;
+        z = _z;
+    }
+    public void set(double[] _new){
+        x = _new[0];
+        y = _new[1];
+        z = _new[2];
+    }
+    public void translate(Point3D trVec){
+        x += trVec.x;
+        y += trVec.y;
+        z += trVec.z;
+    }
+    public Point2D project(double fpdis, int scrX, int scrY){
+        return new Point2D(
+                scrX - (int)(scrX*0.5*((fpdis*y)/(z) + 1)),
+                (int)(scrX*0.5*((fpdis*x)/(z) + ((double)scrY/(double)scrX)))
+        );
+    }
+    public void projectOn(double fpdis, int scrX, int scrY, Point2D point){
+        // this is a mess of random fixes because none of my coordinates are correct. :(
+        // sorry
+        point.x = scrX - (int)(scrX*0.5*((fpdis*y)/(z) + 1));
+        point.y = (int)(scrX*0.5*((fpdis*x)/(z) + ((double)scrY/(double)scrX)));
+    }
+    public double[] get(){
+        return new double[]{x, y, z};
+    }
+}
diff --git a/src/main/java/uk/org/floop/epq3d/PointComp.java b/src/main/java/uk/org/floop/epq3d/PointComp.java
new file mode 100644
index 0000000..934be18
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/PointComp.java
@@ -0,0 +1,42 @@
+package uk.org.floop.epq3d;
+
+public class PointComp {
+    public Point3D point = new Point3D(0,0,0);
+    private final Point3D rotatedPoint = new Point3D(0,0,0);
+    private boolean isRotatedPointValid = true;
+    private final Point2D projectedPoint = new Point2D(0,0);
+    private boolean isProjectedPointValid = true;
+    public PointComp(double _x, double _y, double _z){
+        point.x = _x;
+        point.y = _y;
+        point.z = _z;
+    }
+    public void invalidate(){
+        isProjectedPointValid = false; isRotatedPointValid = false;
+    }
+    public Point3D getRotatedPoint(){
+        if(!isRotatedPointValid){
+            throw new RuntimeException("Rotated point not initialised");
+        }
+        return rotatedPoint;
+    }
+    public Point2D getProjectedPoint(){
+        if(!isRotatedPointValid){
+            throw new RuntimeException("Projected point not initialised");
+        }
+        return projectedPoint;
+    }
+    public void setRotatedPoint(Matrix camMatrix){
+        if(!isRotatedPointValid) {
+            camMatrix.multiplyPoint3to(point, rotatedPoint);
+            isRotatedPointValid = true;
+        }
+    }
+    public void setProjectedPoint(double FPDis, int scrX, int scrY){
+        if(!isProjectedPointValid) {
+            rotatedPoint.projectOn(FPDis, scrX, scrY, projectedPoint);
+            projectedPoint.z = rotatedPoint.z;
+            isProjectedPointValid = true;
+        }
+    }
+}
diff --git a/src/main/java/uk/org/floop/epq3d/Screen.java b/src/main/java/uk/org/floop/epq3d/Screen.java
new file mode 100644
index 0000000..b9ed661
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/Screen.java
@@ -0,0 +1,290 @@
+package uk.org.floop.epq3d;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+
+public class Screen extends JPanel implements ActionListener, KeyListener, MouseListener, MouseMotionListener {
+    // __working variables__
+    public long lastTime = 0;
+        // mouse
+    private boolean captured = false;
+    private final Point2D mouseRel = new Point2D(0,0);
+    private final Cursor invisibleCursor;
+
+    public ObjectCollection mainCollection;
+    public JsonWriter json;
+
+        // testing\
+//    private Line2d line = new Line2d(
+//            new Point2D(200, 200),
+//                new Point2D(1, 1),
+//                true);
+//    private Triangle Triangle = new Triangle(
+//            new Point2D(200, 200), new Point2D(1, 1), new Point2D(400,200)
+//    ,new boolean[]{false, false, false},
+//            null,
+//            new Matrix(3, 3));
+//    private final Point3D[] points = new Point3D[]{
+//            new Point3D(10,10,-1),
+//        new Point3D(-10, 10, -1),
+//        new Point3D(-10, -10, -1),
+//        new Point3D(10, -10, -1)};
+    // double ang = 0;
+    // __config variables__
+    // controls the delay between each tick in ms
+    private final int DELAY = 25;
+    // controls the size of the board
+    public static final int TILE_SIZE = 50;
+    public static final int ROWS = 12;
+    public static final int COLUMNS = 18;
+    // suppress serialization warning
+    private static final long serialVersionUID = 490905409104883233L;
+
+    // keep a reference to the frameTimer object that triggers actionPerformed() in
+    // case we need access to it in another method
+    private final Timer frameTimer;
+    private final Player player;
+
+    public Screen(ObjectCollection _mainCollection) {
+        mainCollection = _mainCollection;
+
+        // set the game board size
+        setPreferredSize(new Dimension(TILE_SIZE * COLUMNS, TILE_SIZE * ROWS));
+        // set the game board background color
+        setBackground(new Color(232, 232, 232));
+        // hide the mouse cursor (https://stackoverflow.com/questions/191592/how-do-i-get-rid-of-the-mouse-cursor-in-full-screen-exclusive-mode)
+        Toolkit toolkit = Toolkit.getDefaultToolkit();
+        Point hotSpot = new Point(0,0);
+        BufferedImage cursorImage = new BufferedImage(1, 1, BufferedImage.TRANSLUCENT);
+        invisibleCursor = toolkit.createCustomCursor(cursorImage, hotSpot, "InvisibleCursor");
+
+        // initialize the game state
+        player = new Player(TILE_SIZE*COLUMNS, TILE_SIZE*ROWS);
+        // this frameTimer will call the actionPerformed() method every DELAY ms
+        frameTimer = new Timer(DELAY, this);
+        frameTimer.start();
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        // called by the frameTimer every DELAY ms.
+        // runs the tick of the player before the board is redrawn
+        player.tick(get_mouse_rel());
+        // calling repaint() will trigger paintComponent() to run again,
+        // which will refresh/redraw the graphics.
+        repaint();
+    }
+
+    @Override
+    public void paintComponent(Graphics g) {
+        super.paintComponent(g);
+        // when calling g.drawImage() we can use "this" for the ImageObserver 
+        // because Component implements the ImageObserver interface, and JPanel 
+        // extends from Component. So "this" Board instance, as a Component, can 
+        // react to imageUpdate() events triggered by g.drawImage()
+
+        // draw graphics.
+        drawScreen(g);
+        // player.draw(g, this);
+
+        // this smooths out animations on some systems
+        Toolkit.getDefaultToolkit().sync();
+    }
+    private void drawScreen(Graphics g)  {
+        BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB );
+        BufferedImage zBuf = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
+        BufferedImage debugImg = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
+        debugImg.createGraphics();
+        g.setColor(Color.white);
+
+//        System.out.println(zBuf.getRGB(0,0));
+//        zBuf.setRGB(0,0, 100);
+//        System.out.println(zBuf.getRGB(0,0));
+
+//        line.point2.x = 100*Math.sin(ang) + 200;
+//        line.point2.y = 100*Math.cos(ang) + 200;
+//        Triangle.point2.x = 100*Math.sin(ang+3) + 200;
+//        Triangle.point2.y = 100*Math.cos(ang+3) + 200;
+//        line.draw(img);
+      /*  ArrayList<Point3D> newPoints = new ArrayList<Point3D>();
+        for (Point3D point: points) {
+            Point3D _new = new Point3D(0,0,0);
+            player.camMatrix.multiplyPoint3to(point, _new);
+            if(_new.z > .1) {
+                newPoints.add(_new);
+            } else{
+                newPoints.add(null);
+            }
+        }
+        Triangle t1 = null;
+        Triangle t2 = null;
+        try {
+            t1 = new Triangle(
+                    newPoints.get(0).project(player.FPDis, getWidth(), getHeight()),
+                    newPoints.get(1).project(player.FPDis, getWidth(), getHeight()),
+                    newPoints.get(2).project(player.FPDis, getWidth(), getHeight()));
+        } catch(NullPointerException ignored){}
+        try{
+            t2 = new Triangle(
+                newPoints.get(0).project(player.FPDis, getWidth(), getHeight()),
+                newPoints.get(2).project(player.FPDis, getWidth(), getHeight()),
+                newPoints.get(3).project(player.FPDis, getWidth(), getHeight()));
+        } catch(NullPointerException ignored){}
+
+        try{t1.draw(img);}catch (NullPointerException ignored){}
+        try{t2.draw(img);}catch (NullPointerException ignored){}
+        ang += 0.02;*/
+
+        mainCollection.invalidate(true);
+        mainCollection.draw(img, zBuf, debugImg, player.camMatrix, player.getPos(), player.frustumPlanes, player.Fpdis, getWidth(), getHeight());
+        g.drawImage(img, 0, 0, this);
+
+        // DEBUG DRAWING
+        {
+            debugImg.getGraphics().drawString(Math.round(1000 / (float) (System.currentTimeMillis() - lastTime)) + " fps", 10, 10);
+            debugImg.getGraphics().drawString("fpPos: " +
+                    String.format("%.2f", player.FPWorldPos.x) + " " +
+                    String.format("%.2f", player.FPWorldPos.y) + " " +
+                    String.format("%.2f", player.FPWorldPos.z) + " ", 100, 30);
+            g.drawImage(debugImg, 0, 0, this);
+            debugImg.getGraphics().drawString("playerPos: " +
+                    String.format("%.2f", player.getPos().x) + " " +
+                    String.format("%.2f", player.getPos().y) + " " +
+                    String.format("%.2f", player.getPos().z) + " ", 100, 50);
+            debugImg.getGraphics().drawString("rotation: " +
+                    Math.round(Math.toDegrees(player.getRot().x)) + " " +
+                    Math.round(Math.toDegrees(player.getRot().y)) + " " +
+                    Math.round(Math.toDegrees(player.getRot().z)) + " ", 300, 50);
+            debugImg.getGraphics().drawString("viewVec: " +
+                    String.format("%.2f", player.viewVector.x) + " " +
+                    String.format("%.2f", player.viewVector.y) + " " +
+                    String.format("%.2f", player.viewVector.z) + " ", 100, 70);
+            for (int i = 0; i < 4; i += 1) {
+                debugImg.getGraphics().drawString("camPoint " + i + ": " +
+                        String.format("%.2f", player.ScreenCornerPosS[i].getRotatedPoint().x) + " " +
+                        String.format("%.2f", player.ScreenCornerPosS[i].getRotatedPoint().y) + " " +
+                        String.format("%.2f", player.ScreenCornerPosS[i].getRotatedPoint().z), 10, 90 + 20 * i);
+            }
+            for (int i = 0; i < 6; i += 1) {
+                debugImg.getGraphics().drawString("plane " + i + ": " +
+                        String.format("%.2f", player.frustumPlanes[i].mainPoint.x) + " " +
+                        String.format("%.2f", player.frustumPlanes[i].mainPoint.y) + " " +
+                        String.format("%.2f", player.frustumPlanes[i].mainPoint.z) + ", normal: " +
+                        String.format("%.2f", player.frustumPlanes[i].normalVector.x) + " " +
+                        String.format("%.2f", player.frustumPlanes[i].normalVector.y) + " " +
+                        String.format("%.2f", player.frustumPlanes[i].normalVector.z), 10, 170 + 20 * i);
+            }
+            // write debug overlay
+            g.drawImage(debugImg, 0, 0, this);
+            lastTime = System.currentTimeMillis();
+        }
+        HTTPPost post = new HTTPPost();
+        // json WRITING
+        json = new JsonWriter();
+        Point3D[] vertexList;
+        int[][] faceList;
+
+        // camera object
+        vertexList = new Point3D[5];
+        faceList = new int[][]{{0, 1, 2, 3}};
+        for (int i = 0; i < 4; i += 1) {
+            vertexList[i] = player.ScreenCornerPosS[i].getRotatedPoint();
+        }
+        vertexList[4] = player.FPWorldPos;
+        json.addBlenderObject(vertexList, faceList);
+
+        post.invokePost(json);
+    }
+
+    @Override
+    public void keyTyped(KeyEvent e) {}
+
+    @Override
+    public void keyPressed(KeyEvent e) {
+        int key = e.getKeyCode();
+
+        if (key == KeyEvent.VK_ESCAPE){
+            captured = false;
+            setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+        }
+        // pass key-press to the player, so they can handle it as well
+        player.keyPressed(key);
+    }
+
+    @Override
+    public void keyReleased(KeyEvent e) {
+        int key = e.getKeyCode();
+
+        // pass key-release to the player, so they can handle it as well
+        player.keyReleased(key);
+    }
+
+    // gets the relative position of the mouse since the last time this function was called
+    public Point2D get_mouse_rel(){
+        Point2D rel;
+        // this doesn't work if the mouse isn't captured. If I need it to, I can
+        // probably make it, but I don't know what the functionality looks like.
+        if (captured) {
+            rel = new Point2D(MouseInfo.getPointerInfo().getLocation().x - getLocationOnScreen().x - getSize().width / 2 + mouseRel.x,
+                    MouseInfo.getPointerInfo().getLocation().y - getLocationOnScreen().y - getSize().height / 2 + mouseRel.y);
+            mouseRel.set(0, 0);
+            // set the position of the mouse back to (0,0)
+            try {
+                Robot robot = new Robot();
+                robot.mouseMove(getLocationOnScreen().x + getSize().width / 2,
+                        getLocationOnScreen().y + getSize().height / 2);
+            } catch (AWTException ex) {
+                ex.printStackTrace();
+            }
+        }
+        else{
+            rel = new Point2D(0,0);
+        }
+        return rel;
+    }
+    @Override
+    public void mouseClicked(MouseEvent mouseEvent) {
+        if (mouseEvent.getButton() == MouseEvent.BUTTON1){
+            captured = true;
+            get_mouse_rel();
+            setCursor(invisibleCursor);
+        }
+    }
+
+    @Override
+    public void mousePressed(MouseEvent mouseEvent) {}
+
+    @Override
+    public void mouseReleased(MouseEvent mouseEvent) {}
+
+    @Override
+    public void mouseEntered(MouseEvent mouseEvent) {}
+
+    @Override
+    public void mouseExited(MouseEvent mouseEvent) {
+        if (captured) {
+            // add current mouse relative location to mouseRel so mouse movements are not lost
+            mouseRel.x += mouseEvent.getX() - getSize().width / 2d;
+            mouseRel.y += mouseEvent.getY() - getSize().height / 2d;
+
+            // System.out.println(mouseEvent.getX() + " " + mouseEvent.getY());
+            // set the position of the mouse back to (0,0)
+            try {
+                Robot robot = new Robot();
+                robot.mouseMove(getLocationOnScreen().x + getSize().width / 2,
+                                getLocationOnScreen().y + getSize().height / 2);
+            } catch (AWTException ex) {
+                ex.printStackTrace();
+            }
+        }
+    }
+
+    @Override
+    public void mouseDragged(MouseEvent mouseEvent) {}
+
+    @Override
+    public void mouseMoved(MouseEvent mouseEvent) {
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/uk/org/floop/epq3d/Triangle.java b/src/main/java/uk/org/floop/epq3d/Triangle.java
new file mode 100644
index 0000000..89b58b0
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/Triangle.java
@@ -0,0 +1,178 @@
+package uk.org.floop.epq3d;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+public class Triangle{
+    public Point2D point1;
+    public Point2D point2;
+    public Point2D point3;
+
+    public Matrix perspectiveMappingMatrix;
+
+
+    public boolean[] edgeList; //edge 1-2 , 2-3, 3-1
+
+    private Line2d LineLong;
+    private Line2d LineA;
+    private Line2d LineB;
+
+    public BufferedImage texture;
+
+    // initialisation variables
+    private boolean is_initialised = false;
+    private Point2D min;
+    private Point2D max;
+    // progress variables
+    private Point3D result = new Point3D(0,0,0);
+    private Point2D result2 = new Point2D(0,0);
+    public void invalidate() {
+        is_initialised = false;
+    }
+    public Triangle(Point2D _pA, Point2D _pB, Point2D _pC, boolean[] _edgeList, BufferedImage _texture, Matrix mapMatrix){
+        point1 = _pA;
+        point2 = _pB;
+        point3 = _pC;
+        edgeList = _edgeList;
+        texture = _texture;
+        perspectiveMappingMatrix = mapMatrix;
+    }
+    //  returns int for debug
+    public int draw(BufferedImage img, BufferedImage zBuf, double FPDis, int scrX, int scrY){
+        //
+        long lastMillis;
+
+
+        if (!is_initialised){initialise();}
+        int[] point1;
+        int[] point2;
+        char currentLine = 'A';
+
+        lastMillis = System.currentTimeMillis();
+
+        point1 = LineLong.nextPix();
+        point2 = LineA.nextPix();
+        for(int x = min.x+1; x <= max.x; x += 1) {
+            while(x-1 == point1[0]) {
+                if(LineLong.isDrawn && // draw line pixels if needed, and on screen
+                        point1[0] > 0 && point1[1] > 0 && point1[0] < img.getWidth() && point1[1] < img.getHeight()){
+                    img.setRGB(point1[0], point1[1], Color.HSBtoRGB(0, 1, 1));
+                }
+                try { // this error seems to be thrown randomly, for various reasons // todo fix
+                    point1 = LineLong.nextPix();
+                }
+                catch (Exception e){
+                    throw new RuntimeException("accessed too many line pixels");
+                }
+            }
+            while(x-1 == point2[0]) {
+                if (currentLine == 'A') {
+                    try{
+                        if(LineA.isDrawn &&                // draw line pixels if needed, and on screen
+                                point2[0] > 0 && point2[1] > 0 && point2[0] < img.getWidth() && point2[1] < img.getHeight()){
+                            img.setRGB(point2[0], point2[1], Color.HSBtoRGB(0f, 1, 1));
+                        }
+                        point2 = LineA.nextPix();
+                    }
+                    catch (RuntimeException e){
+                        currentLine = 'B';
+                        // point2 = LineB.nextPix();
+                    }
+                }
+                else {
+                    if(LineB.isDrawn &&                // draw line pixels if needed, and on screen
+                            point2[0] > 0 && point2[1] > 0 && point2[0] < img.getWidth() && point2[1] < img.getHeight()){
+                        img.setRGB(point2[0], point2[1], Color.HSBtoRGB(0f, 1, 1));
+                    }
+                    point2 = LineB.nextPix();
+                }
+            }
+            // cancel drawing if the x value of the triangle is out of bounds
+            if (x >= img.getWidth()) {break;}
+            if (x > 0) {
+                // check which way to loop
+                // TODO - work out a way of not needing to test for this every time
+                if (point1[1] < point2[1]) {
+                    for (int y = Math.max(point1[1], 0); y <= Math.min(point2[1], img.getHeight() - 1); y += 1) {
+                        // function only exists so I don't have to copy paste code everywhere.
+                        drawPix(img, zBuf, FPDis, scrX, scrY, currentLine, x, y, point1[1], point2[1]);
+                    }
+                } else {
+                    for (int y = Math.max(point2[1], 0); y <= Math.min(point1[1], img.getHeight() - 1); y += 1) {
+                        drawPix(img, zBuf, FPDis, scrX, scrY, currentLine, x,y, point1[1], point2[1]);
+                    }
+                }
+            }
+        }
+        lastMillis = (System.currentTimeMillis() - lastMillis);
+        return (int)lastMillis;
+    }
+    public void initialise(){
+        if (point1 == null || point2 == null || point3 == null){
+            throw new NullPointerException();
+        }
+        min = new Point2D(Math.min(point1.x, Math.min(point2.x, point3.x)), Math.min(point1.y, Math.min(point2.y, point3.y)));
+        max = new Point2D(Math.max(point1.x, Math.max(point2.x, point3.x)), Math.max(point1.y, Math.max(point2.y, point3.y)));
+        // woo horrible IFs mess.
+        // we need to figure out which points touch the edges in order to find which line is the 'full length' edge,
+        // and then assign line A and line B in order
+        if (point1.x == min.x) {
+            if (point2.x == max.x){
+                LineLong = new Line2d(point1, point2, edgeList[0]);
+                LineA = new Line2d(point1, point3, edgeList[2]);
+                LineB = new Line2d(point3, point2, edgeList[1]);
+            } else {
+                LineLong = new Line2d(point1, point3, edgeList[2]);
+                LineA = new Line2d(point1, point2, edgeList[0]);
+                LineB = new Line2d(point2, point3, edgeList[1]);
+            }
+        }
+        else if (point2.x == min.x) {
+            if (point1.x == max.x) {
+                LineLong = new Line2d(point2, point1, edgeList[0]);
+                LineA = new Line2d(point2, point3, edgeList[1]);
+                LineB = new Line2d(point3, point1, edgeList[2]);
+            } else {
+                LineLong = new Line2d(point2, point3, edgeList[1]);
+                LineA = new Line2d(point2, point1, edgeList[0]);
+                LineB = new Line2d(point1, point3, edgeList[2]);
+            }
+        }
+        else if (point3.x == min.x){
+            if (point1.x == max.x) {
+                LineLong = new Line2d(point3, point1, edgeList[2]);
+                LineA = new Line2d(point3, point2, edgeList[1]);
+                LineB = new Line2d(point2, point1, edgeList[0]);
+            } else {
+                LineLong = new Line2d(point3, point2, edgeList[2]);
+                LineA = new Line2d(point3, point1, edgeList[1]);
+                LineB = new Line2d(point1, point2, edgeList[0]);
+            }
+        }
+        // assign points to lines
+        is_initialised = true;
+    }
+    private void drawPix(BufferedImage img, BufferedImage zBuf, double FPDis, int scrX, int scrY, char currentLine, int x, int y, int y1, int y2){
+        // find Z coordinate of pixel
+        double z1 = LineLong.getZVal(x); double z2;
+        if(currentLine=='A'){z2=LineA.getZVal(x);} else{z2=LineB.getZVal(x);}
+        double ZVal = z1 + ((z1-z2)/ (y1-y2) * (y - y1));
+
+        // a value of 0 represents a value which is on the camera, at z=0.
+        // not the best mapping but it's probably good enough
+        int newZ = (int)((2147483648L/ZVal));
+        // if the new Z value is greater than the existing Z value on the buffer, the new pixel is calculated and drawn
+        if(zBuf.getRGB(x, y) == Color.HSBtoRGB(0, 0, 0) ||
+        newZ > zBuf.getRGB(x, y)){ //newZ > zBuf.getRGB(x, y) ||
+            zBuf.setRGB(x, y, newZ);
+            result = new Point3D(0,0,0);
+            //perspectiveMappingMatrix.multiplyPoint3to(new Point3D(x, y, 0), result);
+            // project result
+            result.projectOn(FPDis, scrX, scrY, result2);
+            int colour = texture.getRGB(
+                    Math.floorMod((int)(result2.x), texture.getWidth()),
+                    Math.floorMod((int)(result2.y), texture.getHeight()));
+            img.setRGB(x, y, (int) (colour - 10*(result2.x/ texture.getWidth()) - 10*(result2.y/ texture.getHeight())));
+        }
+    }
+}
diff --git a/src/main/java/uk/org/floop/epq3d/Vector2D.java b/src/main/java/uk/org/floop/epq3d/Vector2D.java
new file mode 100644
index 0000000..319d43b
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/Vector2D.java
@@ -0,0 +1,28 @@
+package uk.org.floop.epq3d;
+
+public class Vector2D {
+    // technically not required as vectors have the same information as points, but it's useful to have separate vector and point things..
+    public double x;
+    public double y;
+
+    public Vector2D(double _x, double _y){
+        x = _x;
+        y = _y;
+    }
+    public void createFrom2Points(Point2D start, Point2D end){
+        x = end.x - start.x;
+        y = end.y - start.y;
+    }
+    public double getLength(){
+        return Math.sqrt(x*x + y*y);
+    }
+    public double angleTo(Vector2D vec2){
+        return Math.acos(
+                dot(vec2)
+                / (getLength()*vec2.getLength()))
+                * Math.signum(x*vec2.y - y *vec2.x);
+    }
+    public double dot(Vector2D vec2){
+        return (x*vec2.x) + (y*vec2.y);
+    }
+}
diff --git a/src/main/java/uk/org/floop/epq3d/Vector3D.java b/src/main/java/uk/org/floop/epq3d/Vector3D.java
new file mode 100644
index 0000000..c8e076c
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/Vector3D.java
@@ -0,0 +1,37 @@
+package uk.org.floop.epq3d;// technically not required as vectors have the same information as points, but it's useful to have separate vector and point things..
+
+public class Vector3D {
+    public double x;
+    public double y;
+    public double z;
+
+    public Vector3D(double _x, double _y, double _z){
+        x = _x;
+        y = _y;
+        z = _z;
+    }
+    public void createFrom2Points(Point3D start, Point3D end){
+            x = end.x - start.x;
+        y = end.y - start.y;
+        z = end.z - start.z;
+    }
+    public double getLength(){
+        return Math.sqrt(x*x + y*y + z*z);
+    }
+    public double angleTo(Vector3D vec2){
+        return Math.acos(
+                (x*vec2.x + y*vec2.y + z*vec2.z)
+                        /( //-----------------------------------------------------------
+                        Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2)) *
+                        Math.sqrt(Math.pow(vec2.x,  2) + Math.pow(vec2.y, 2) + Math.pow(vec2.z, 2))));
+    }
+    public Vector3D cross(Vector3D vec2){
+        return new Vector3D(
+                y*vec2.z - z*vec2.y,
+                z*vec2.x - x*vec2.z,
+                x*vec2.y - y*vec2.x);
+    }
+    public double dot(Vector3D vec2){
+        return x*vec2.x + y* vec2.y+z* vec2.z;
+    }
+}
diff --git a/src/main/java/uk/org/floop/epq3d/testing.java b/src/main/java/uk/org/floop/epq3d/testing.java
new file mode 100644
index 0000000..05aa248
--- /dev/null
+++ b/src/main/java/uk/org/floop/epq3d/testing.java
@@ -0,0 +1,25 @@
+package uk.org.floop.epq3d;
+
+class testing {
+    public static void main(String[] args) {
+//        Matrix matt = new Matrix(2, 2);
+//        matt.setItems(new double[][]{
+//                {3, 7},
+//                {4, 8},
+//        });
+//        Matrix matt = new Matrix(3, 3);
+//        matt.setItems(new double[][]{
+//                {2, -1, 3},
+//                {0, 5, 2},
+//                {1, -1, -2}
+//        });
+
+        Matrix matt = new Matrix(4, 4);
+        matt.setItems(new double[][]{
+                {3, 3, 3, 0},
+                {4, 8, 7, 2.3},
+                {5, 1, 3, 9.4},
+                {2, 1, 4, 7}
+        });
+    }
+}