diff --git a/out/production/EPQ 3D renderer/App.class b/out/production/EPQ 3D renderer/App.class index a13221e..6ee0cae 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 54b5092..d1c181a 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/Line.class b/out/production/EPQ 3D renderer/Line.class deleted file mode 100644 index 20f60ad..0000000 --- a/out/production/EPQ 3D renderer/Line.class +++ /dev/null Binary files differ diff --git a/out/production/EPQ 3D renderer/Matrix.class b/out/production/EPQ 3D renderer/Matrix.class index 698f676..5897f80 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/Object3d.class b/out/production/EPQ 3D renderer/Object3d.class index 694bbc1..7e0b039 100644 --- a/out/production/EPQ 3D renderer/Object3d.class +++ b/out/production/EPQ 3D renderer/Object3d.class Binary files differ diff --git a/out/production/EPQ 3D renderer/Player.class b/out/production/EPQ 3D renderer/Player.class index b8bf0db..2fa269f 100644 --- a/out/production/EPQ 3D renderer/Player.class +++ b/out/production/EPQ 3D renderer/Player.class Binary files differ diff --git a/out/production/EPQ 3D renderer/Point2D.class b/out/production/EPQ 3D renderer/Point2D.class index d351e0d..2ef102e 100644 --- a/out/production/EPQ 3D renderer/Point2D.class +++ b/out/production/EPQ 3D renderer/Point2D.class Binary files differ diff --git a/out/production/EPQ 3D renderer/Point3D.class b/out/production/EPQ 3D renderer/Point3D.class index bb70794..68483dc 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/PointComp.class b/out/production/EPQ 3D renderer/PointComp.class index e1aaa8f..4cfb75a 100644 --- a/out/production/EPQ 3D renderer/PointComp.class +++ b/out/production/EPQ 3D renderer/PointComp.class Binary files differ diff --git a/out/production/EPQ 3D renderer/Screen.class b/out/production/EPQ 3D renderer/Screen.class index 6d7f20a..4df8c31 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 5a043b9..b9daefa 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 index a2131d2..fdc759c 100644 --- a/src/App.java +++ b/src/App.java @@ -1,4 +1,8 @@ +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; @@ -37,24 +41,50 @@ public static void initObjects(){ mainCollection = new ObjectCollection(); -// 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(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}, -// }, true)); + + 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), @@ -76,7 +106,15 @@ }, new int[][]{ {0,1,2}, {0,2,3}, - {0,3,1} - }, true)); + {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 index 2ad549d..8879a50 100644 --- a/src/Face.java +++ b/src/Face.java @@ -1,15 +1,38 @@ import java.awt.image.BufferedImage; -import java.security.cert.TrustAnchor; import java.util.ArrayList; public class Face { - PointComp[] points; - Vector3D normal; - Triangle[] tris; - + 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(){ @@ -18,26 +41,79 @@ tri.invalidate(); } } - public int draw(BufferedImage img, BufferedImage debugImg, Matrix camMatrix, double FPDis, int scrX, int scrY){ + 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; + int numberOfPixels = 0; boolean valid = true; for (PointComp point: points) { point.setRotatedPoint(camMatrix); - point.setProjectedPoint(FPDis, scrX, scrY); - // skip rendering if any points are behind the camera - if(point.getRotatedPoint().z < 0.1){ - valid = false; - break; - } + // 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);} } - if(valid){for (Triangle tri: + // calculatePerspectiveMapping(); + + if(valid){ + for (Triangle tri: tris) { - numberofPixels += tri.draw(img); - }} - return numberofPixels; + 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)); + } + lastValid = thisValid; + } + // 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]; @@ -45,7 +121,9 @@ newTris[i] = new Triangle( points[0].getProjectedPoint(), points[i+1].getProjectedPoint(), - points[i+2].getProjectedPoint()); + points[i+2].getProjectedPoint(), + new boolean[]{hasEdges, hasEdges, hasEdges}, + texture, perspectiveMappingMatrix); } tris = newTris; } @@ -70,4 +148,43 @@ 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/Line.java b/src/Line.java deleted file mode 100644 index 3184881..0000000 --- a/src/Line.java +++ /dev/null @@ -1,127 +0,0 @@ -import java.awt.*; -import java.awt.image.BufferedImage; - -// handles line and line drawing -public class Line { - public Point2D point1; - public Point2D point2; - - // 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}; - // drawing progress variables - private int iteratorVal; - private int D; - public Line(Point2D _point1, Point2D _point2){ - point1 = _point1; - point2 = _point2; - } - - /** - * 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(){ - 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; - is_initialised = true; - } - - /** - * @return the x and y coordinate of the next pixel in the line. - */ - public int[] nextPix(){ - if(!is_initialised){initialise();} - //OPTIMIZATION - avoid casting. have a different point class that uses integers instead of float - 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; - } - } - diff --git a/src/Line2d.java b/src/Line2d.java new file mode 100644 index 0000000..ab1cff6 --- /dev/null +++ b/src/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 = true; + + // 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 index 7202d29..4480cc9 100644 --- a/src/Matrix.java +++ b/src/Matrix.java @@ -19,7 +19,7 @@ public void setItems(double[][] newItems){ items = newItems; } - public Matrix multiply(Matrix mult) { + public Matrix multiplyGetResult(Matrix mult) { Matrix result = new Matrix(mult.x, this.y); double newItem; if(x==mult.y){ @@ -36,6 +36,23 @@ } return result; } + public void multiply(Matrix mult) { + Matrix result = new Matrix(mult.x, this.y); + double newItem; + if(x==mult.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 points = new ArrayList(); private Object3d collectionObject; + public double boundingSphereR; + public Point3D boundingSphereC; + + public ArrayList subCollections = new ArrayList(); public ArrayList objects = new ArrayList(); @@ -27,7 +31,7 @@ } } - public void draw(BufferedImage img, BufferedImage debugImg, Matrix camMatrix, Point3D playerPos, Plane[] frustumPlanes, double FPDis, int scrX, int scrY){ + 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; @@ -41,12 +45,23 @@ break;} i += 1; } - if(draw){object.draw(img, debugImg, camMatrix, FPDis, scrX, scrY, playerPos);} + if(draw){object.draw(img, zBuf, debugImg, camMatrix, FPDis, scrX, scrY, playerPos);} } // todo check for frustum culling for(ObjectCollection collection: subCollections){ - collection.draw(img, debugImg, camMatrix, playerPos, frustumPlanes, FPDis, scrX, scrY); + 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){ @@ -73,5 +88,42 @@ } } } - // + 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/Player.java b/src/Player.java index cb16b47..9a9d682 100644 --- a/src/Player.java +++ b/src/Player.java @@ -153,9 +153,9 @@ {0, 0, 1, -position.z}} ); // multiply out matrices - rotMatrix = xMat.multiply(yMat).multiply(zMat); - camMatrix = rotMatrix.multiply(traMat); - invRotMatrix = izMat.multiply(iyMat).multiply(ixMat); + 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); diff --git a/src/Point2D.java b/src/Point2D.java index ab17c36..0a3dff0 100644 --- a/src/Point2D.java +++ b/src/Point2D.java @@ -3,6 +3,8 @@ 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; diff --git a/src/PointComp.java b/src/PointComp.java index 1c0ecdc..730ef6b 100644 --- a/src/PointComp.java +++ b/src/PointComp.java @@ -33,6 +33,7 @@ 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/Screen.java b/src/Screen.java index 05594fc..f14a627 100644 --- a/src/Screen.java +++ b/src/Screen.java @@ -1,6 +1,8 @@ import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; import javax.swing.*; public class Screen extends JPanel implements ActionListener, KeyListener, MouseListener, MouseMotionListener { @@ -14,7 +16,7 @@ public ObjectCollection mainCollection; // testing\ - //private Line line = new Line(new Point2D(200, 200),new Point2D(1, 1)); + //private Line2d line = new Line2d(new Point2D(200, 200),new Point2D(1, 1)); //private Triangle Triangle = new Triangle(new Point2D(200, 200), new Point2D(1, 1), new Point2D(400,200)); private Point3D[] points = new Point3D[]{ new Point3D(10,10,-1), @@ -85,9 +87,14 @@ } 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; @@ -122,7 +129,7 @@ try{t2.draw(img);}catch (NullPointerException ignored){} ang += 0.02;*/ mainCollection.invalidate(true); - mainCollection.draw(img, debugImg, player.camMatrix, player.getPos(), player.frustumPlanes, player.Fpdis, getWidth(), getHeight()); + mainCollection.draw(img, zBuf, debugImg, player.camMatrix, player.getPos(), player.frustumPlanes, player.Fpdis, getWidth(), getHeight()); g.drawImage(img, 0, 0, this); // DEBUG DRAWING @@ -162,7 +169,6 @@ g.drawImage(debugImg, 0, 0, this); lastTime = System.currentTimeMillis(); - } @Override diff --git a/src/Triangle.java b/src/Triangle.java index 08c5c43..99f134a 100644 --- a/src/Triangle.java +++ b/src/Triangle.java @@ -6,9 +6,17 @@ public Point2D point2; public Point2D point3; - private Line LineLong; - private Line LineA; - private Line LineB; + 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; @@ -18,15 +26,20 @@ public void invalidate() { is_initialised = false; } - public Triangle(Point2D _pA, Point2D _pB, Point2D _pC){ + 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){ + public int draw(BufferedImage img, BufferedImage zBuf){ + // long lastMillis; + if (!is_initialised){initialise();} int[] point1; int[] point2; @@ -38,17 +51,36 @@ point2 = LineA.nextPix(); for(int x = min.x+1; x <= max.x; x += 1) { while(x-1 == point1[0]) { - point1 = LineLong.nextPix(); + 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{point2 = LineA.nextPix();} + 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(); + // 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(); } } @@ -59,11 +91,12 @@ // 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) { - img.setRGB(x, y, Color.HSBtoRGB(.5f, 1, 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) { - img.setRGB(x, y, Color.HSBtoRGB(.5f, 1, 1)); + drawPix(img, zBuf, currentLine, x,y, point1[1], point2[1]); } } } @@ -78,38 +111,64 @@ 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. - Point2D realPointA; - Point2D realPointB; - Point2D realPointC; - if (point1.x == min.x) { realPointA = point1; - if (point2.x == max.x){ realPointB = point2; - realPointC = point3; - } else { - realPointB = point3; - realPointC = point2; - } + // 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) { realPointA = point2; - if (point1.x == max.x) { realPointB = point1; - realPointC = point3; - } else { realPointB = point3; - realPointC = point1; - } + } + 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 { - realPointA = point3; - if (point1.x == max.x) { realPointB = point1; - realPointC = point2; - } else { realPointB = point2; - realPointC = point1; - } + } + 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 - LineLong = new Line(realPointA, realPointB); - LineA = new Line(realPointA, realPointC); - LineB = new Line(realPointC, realPointB); - 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 > (int)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 new file mode 100644 index 0000000..96618ff --- /dev/null +++ b/src/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); + } +}