diff --git a/out/production/EPQ 3D renderer/App.class b/out/production/EPQ 3D renderer/App.class index 6ee0cae..8648eab 100644 --- a/out/production/EPQ 3D renderer/App.class +++ b/out/production/EPQ 3D renderer/App.class Binary files differ diff --git a/out/production/EPQ 3D renderer/Face.class b/out/production/EPQ 3D renderer/Face.class index 4abdb4e..b1aba64 100644 --- a/out/production/EPQ 3D renderer/Face.class +++ b/out/production/EPQ 3D renderer/Face.class Binary files differ diff --git a/out/production/EPQ 3D renderer/Matrix.class b/out/production/EPQ 3D renderer/Matrix.class index 7b2526a..0702f87 100644 --- a/out/production/EPQ 3D renderer/Matrix.class +++ b/out/production/EPQ 3D renderer/Matrix.class Binary files differ diff --git a/out/production/EPQ 3D renderer/Point3D.class b/out/production/EPQ 3D renderer/Point3D.class index 68483dc..a62c208 100644 --- a/out/production/EPQ 3D renderer/Point3D.class +++ b/out/production/EPQ 3D renderer/Point3D.class Binary files differ diff --git a/out/production/EPQ 3D renderer/Screen.class b/out/production/EPQ 3D renderer/Screen.class index 2324799..5a48ebd 100644 --- a/out/production/EPQ 3D renderer/Screen.class +++ b/out/production/EPQ 3D renderer/Screen.class Binary files differ diff --git a/out/production/EPQ 3D renderer/Triangle.class b/out/production/EPQ 3D renderer/Triangle.class index 6a9649d..c4afc42 100644 --- a/out/production/EPQ 3D renderer/Triangle.class +++ b/out/production/EPQ 3D renderer/Triangle.class Binary files differ diff --git a/src/App.java b/src/App.java deleted file mode 100644 index fdc759c..0000000 --- a/src/App.java +++ /dev/null @@ -1,120 +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")); - } 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(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 100)}, - {new Point2D(0, 0), new Point2D(100, 0), new Point2D(0, 100), new Point2D(100, 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/Face.java b/src/Face.java deleted file mode 100644 index 3017d0d..0000000 --- a/src/Face.java +++ /dev/null @@ -1,194 +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 perspectiveMappingMatrix = new Matrix(3, 3); - public boolean isInitialised; - // fixed face - public Face fixedFace; - // working variables - private final Vector2D traVec = new Vector2D(0,0); - private final Vector2D orig01Vec = new Vector2D(0,0); - private final Vector2D fin01Vec = new Vector2D(0,0); - private final Vector2D orig02Vec = new Vector2D(0,0); - private final Vector2D fin02Vec = new Vector2D(0,0); - public void initialise(){ - calculateNormal(); - separateTris(); - fixedFace = new Face(); - fixedFace.hasEdges = hasEdges; - fixedFace.texture = texture; - fixedFace.normal = normal; - fixedFace.perspectiveMappingMatrix = new Matrix(3, 3); - fixedFace.perspectiveMappingMatrix.setItems(new double[][] { - {1,0,0}, - {0,1,0}, - {0,0,1}, - }); - fixedFace.isInitialised = true; - 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 = 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);} - } - // calculatePerspectiveMapping(); - - if(valid){ - for (Triangle tri: - tris) { - numberOfPixels += tri.draw(img, zBuf); - } - } else { - ArrayList newPoints = new ArrayList<>(); - // if there are points behind the camera, loop through all the points and interpolate a point that is - // (including UVS!) // todo 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(); - } - fixedFace.draw(img, zBuf, debugImg, fixedFace.perspectiveMappingMatrix, FPDis, scrX, scrY); - } - } - return numberOfPixels; - } - 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, perspectiveMappingMatrix); - } - 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 calculatePerspectiveMapping() { - traVec.createFrom2Points(UVPoints[0], points[0].getProjectedPoint()); - orig01Vec.createFrom2Points(points[0].getProjectedPoint(), points[1].getProjectedPoint()); - orig02Vec.createFrom2Points(points[0].getProjectedPoint(), points[2].getProjectedPoint()); - fin01Vec.createFrom2Points(UVPoints[0], UVPoints[1]); - fin02Vec.createFrom2Points(UVPoints[0], UVPoints[2]); - - // setting the perspective matrix to the identity matrix - - // it must remain as the same object so pointers elsewhere still work. - perspectiveMappingMatrix.setItems(new double[][]{ - {1,0,0}, - {0,1,0}, - {0,0,1}, - }); - Matrix tMat = new Matrix(3, 3); - tMat.setItems(new double[][]{ - {1, 0, -traVec.x}, - {0, 1, -traVec.y}, - {0, 0, 1} - }); - double scale = (fin01Vec.getLength()) / orig01Vec.getLength(); - Matrix scaMat = new Matrix(3, 3); - scaMat.setItems(new double[][]{ - {scale, 0, 0}, - {0, scale, 0}, - {0, 0, 1} - }); - double ang = orig01Vec.angleTo(fin01Vec); - Matrix rotMat = new Matrix(3, 3); - rotMat.setItems(new double[][]{ - {Math.cos(ang), -Math.sin(ang), 0}, - {Math.sin(ang), Math.cos(ang), 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(rotMat.multiplyGetResult(scaMat.multiplyGetResult(tMat))); - perspectiveMappingMatrix.multiply(scaMat.multiplyGetResult(rotMat.multiplyGetResult(tMat))); - } -} diff --git a/src/Line2d.java b/src/Line2d.java deleted file mode 100644 index 3e03428..0000000 --- a/src/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/Matrix.java b/src/Matrix.java deleted file mode 100644 index 68d2892..0000000 --- a/src/Matrix.java +++ /dev/null @@ -1,87 +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= 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/ObjectCollection.java b/src/ObjectCollection.java deleted file mode 100644 index db9bf6c..0000000 --- a/src/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 points = new ArrayList(); - private Object3d collectionObject; - public double boundingSphereR; - public Point3D boundingSphereC; - - - public ArrayList subCollections = new ArrayList(); - public ArrayList objects = new ArrayList(); - - 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/Plane.java b/src/Plane.java deleted file mode 100644 index fa525cf..0000000 --- a/src/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/Player.java b/src/Player.java deleted file mode 100644 index 9a9d682..0000000 --- a/src/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/Point2D.java b/src/Point2D.java deleted file mode 100644 index 0a3dff0..0000000 --- a/src/Point2D.java +++ /dev/null @@ -1,23 +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}; - } - -// public int intX(){return (int)Math.round(x);} -// public int intY(){return (int)Math.round(y);} - -} diff --git a/src/Point3D.java b/src/Point3D.java deleted file mode 100644 index 81ef356..0000000 --- a/src/Point3D.java +++ /dev/null @@ -1,38 +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( - (int)(((fpdis*y)/(z) + .5)*scrX), - (int)(((fpdis*x)/(z) + .5)*scrY) - // multiply by scrX both times such that the projected screen always projects points between -1 newPoints = new ArrayList(); - 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); - } - - g.drawImage(debugImg, 0, 0, this); - lastTime = System.currentTimeMillis(); - } - - @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/Triangle.java b/src/Triangle.java deleted file mode 100644 index df4f9f1..0000000 --- a/src/Triangle.java +++ /dev/null @@ -1,174 +0,0 @@ -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 - - 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){ - // - 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, 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, 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, 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 - // System.out.println("nZ: " + newZ + " zBuf: " + zBuf.getRGB(x, y) + "Bl: " + Color.HSBtoRGB(0, 0, 0)); - 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); - // double[] result = perspectiveMappingMatrix.multiplyPoint2raw(x, y); -// img.setRGB(x, y, texture.getRGB( -// Math.floorMod((int)(result[0]), texture.getWidth()), -// Math.floorMod((int)(result[1]), texture.getHeight()))); - //img.setRGB(x, y, newZ); - img.setRGB(x, y, Color.HSBtoRGB(0f, 0f, (float) Math.min((10/ZVal), 1))); - } - } -} diff --git a/src/Vector2D.java b/src/Vector2D.java deleted file mode 100644 index 96618ff..0000000 --- a/src/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/Vector3D.java b/src/Vector3D.java deleted file mode 100644 index 6680548..0000000 --- a/src/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/App.java b/src/main/java/App.java new file mode 100644 index 0000000..2f7f969 --- /dev/null +++ b/src/main/java/App.java @@ -0,0 +1,113 @@ +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 new file mode 100644 index 0000000..d14427b --- /dev/null +++ b/src/main/java/Face.java @@ -0,0 +1,273 @@ +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 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 new file mode 100644 index 0000000..faeaf33 --- /dev/null +++ b/src/main/java/JsonWriter.java @@ -0,0 +1,33 @@ +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 listOfStates = new ArrayList(); + 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 new file mode 100644 index 0000000..3e03428 --- /dev/null +++ b/src/main/java/Line2d.java @@ -0,0 +1,138 @@ +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 new file mode 100644 index 0000000..c63ac19 --- /dev/null +++ b/src/main/java/Matrix.java @@ -0,0 +1,100 @@ +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= 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 new file mode 100644 index 0000000..db9bf6c --- /dev/null +++ b/src/main/java/ObjectCollection.java @@ -0,0 +1,129 @@ +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 points = new ArrayList(); + private Object3d collectionObject; + public double boundingSphereR; + public Point3D boundingSphereC; + + + public ArrayList subCollections = new ArrayList(); + public ArrayList objects = new ArrayList(); + + 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 new file mode 100644 index 0000000..fa525cf --- /dev/null +++ b/src/main/java/Plane.java @@ -0,0 +1,27 @@ +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 new file mode 100644 index 0000000..9a9d682 --- /dev/null +++ b/src/main/java/Player.java @@ -0,0 +1,223 @@ +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 new file mode 100644 index 0000000..e203550 --- /dev/null +++ b/src/main/java/Point2D.java @@ -0,0 +1,20 @@ +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 new file mode 100644 index 0000000..e6a51cf --- /dev/null +++ b/src/main/java/Point3D.java @@ -0,0 +1,36 @@ +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 new file mode 100644 index 0000000..730ef6b --- /dev/null +++ b/src/main/java/PointComp.java @@ -0,0 +1,40 @@ +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 new file mode 100644 index 0000000..8034e4c --- /dev/null +++ b/src/main/java/Screen.java @@ -0,0 +1,286 @@ +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 newPoints = new ArrayList(); + 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 new file mode 100644 index 0000000..ff12683 --- /dev/null +++ b/src/main/java/Triangle.java @@ -0,0 +1,178 @@ +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 new file mode 100644 index 0000000..96618ff --- /dev/null +++ b/src/main/java/Vector2D.java @@ -0,0 +1,26 @@ +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 new file mode 100644 index 0000000..6fc0290 --- /dev/null +++ b/src/main/java/Vector3D.java @@ -0,0 +1,39 @@ +// 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; + } +}