diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f144601 --- /dev/null +++ b/pom.xml @@ -0,0 +1,28 @@ + + 4.0.0 + uk.org.floop + epq-3d + 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + + + + EPQ 3D Renderer + https://www.floop.org.uk/cory/epq/3d + + + com.googlecode.json-simple + json-simple + 1.1 + + + diff --git a/src/main/java/uk/org/floop/epq3d/App.java b/src/main/java/uk/org/floop/epq3d/App.java index 99173c4..1219fc8 100644 --- a/src/main/java/uk/org/floop/epq3d/App.java +++ b/src/main/java/uk/org/floop/epq3d/App.java @@ -1,10 +1,7 @@ package uk.org.floop.epq3d; -import javax.imageio.ImageIO; import javax.swing.*; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; +import java.awt.*; class App { static ObjectCollection mainCollection; @@ -31,6 +28,7 @@ window.setLocationRelativeTo(null); // display the window window.setVisible(true); + long i = 2147483648L; } public static void main(String[] args) { @@ -44,13 +42,21 @@ public static void initObjects(){ mainCollection = new ObjectCollection(); - BufferedImage testTexture; - try { + Texture 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")); + testTexture = new Texture(ImageIO.read(new File("/home/cory/Screenshot from 2022-09-26 13-05-40.png"))); } catch (IOException e) { throw new RuntimeException(e); - } + }*/ + Texture red = new Texture(Color.red); + Texture green = new Texture(Color.GREEN); + Texture blue = new Texture(Color.BLUE); + Texture cyan = new Texture(Color.CYAN); + Texture magenta = new Texture(Color.magenta); + Texture yellow = new Texture(Color.yellow); + + mainCollection.addObject(new Object3d(new PointComp[]{ new PointComp(-30,-10,-10), new PointComp(-30,-10,10), @@ -77,8 +83,8 @@ {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, + false, + new Texture[]{red, green, blue, cyan, magenta, yellow}, true)); // mainCollection.addObject(new Object3d(new PointComp[]{ // new PointComp(-10,10,10), @@ -108,8 +114,8 @@ {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, + false, + new Texture[]{red, blue, cyan, yellow}, true)); } } diff --git a/src/main/java/uk/org/floop/epq3d/Face.java b/src/main/java/uk/org/floop/epq3d/Face.java index 426c6cd..120ece6 100644 --- a/src/main/java/uk/org/floop/epq3d/Face.java +++ b/src/main/java/uk/org/floop/epq3d/Face.java @@ -8,24 +8,18 @@ public Point2D[] UVPoints; public Vector3D normal; public Triangle[] tris; + public int[][] trisFaceList; public boolean hasEdges; - public BufferedImage texture; - public Matrix[] perspectiveMappingMatrices = new Matrix[]{null, null, null, null, null, null}; + public Texture texture; + public Matrix[][] perspectiveMappingMatrices; 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(); + perspectiveMappingMatrices = new Matrix[tris.length][2]; + generateUVMatrices(); // 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(); @@ -53,7 +47,8 @@ // initialise points int numberOfPixels = 0; boolean valid = applyPointTransforms(camMatrix, FPDis, scrX, scrY); - //bakePerspectiveMatrices(); + // this function is completed every frame, without checking whether pixels will be drawn first + bakePerspectiveMatrices(img, zBuf, debugImg, camMatrix, FPDis, scrX, scrY); if (valid) { drawTris(img, zBuf, FPDis, scrX, scrY);} @@ -121,6 +116,7 @@ } return numberOfPixels; } + public void drawTris(BufferedImage img, BufferedImage zBuf, double FPDis, int scrX, int scrY) { for (Triangle tri : tris) { @@ -143,7 +139,13 @@ } public void separateTris(){ Triangle[] newTris = new Triangle[points.length - 2]; + int[][] newTrisFaceList = new int[points.length - 2][3]; + for(int i = 0; i< newTris.length; i+=1){ + newTrisFaceList[i][0] = i; + newTrisFaceList[i][1] = i+1; + newTrisFaceList[i][2] = i+2; + newTris[i] = new Triangle( points[0].getProjectedPoint(), points[i+1].getProjectedPoint(), @@ -152,6 +154,7 @@ texture, perspectiveMappingMatrices[i]); } tris = newTris; + trisFaceList = newTrisFaceList; } public void calculateNormal(){ // too many new variables @@ -174,6 +177,226 @@ if(!valid){throw new RuntimeException("Could not calculate normal of face");} normal = vec1.cross(vec2); } + + private void generateUVMatrices() { + // temporary object for multiplying + Matrix MultMat = new Matrix(4, 4); + // temporary unit up vector for reference + Vector2D vFin = new Vector2D(0, 1); + // temporary vector representing a vector between two points on the uv / triangle + Vector2D vOrig = new Vector2D(0,0); + + // repeat for every triangle (this method must be called BEFORE separateTris is called) + int i = 0; + for (int[] tri: trisFaceList) { + // generate two matrices - + // uvTo00 takes the uv's point 1 to 0,0 with its point 2 at 0,1 (rotated, translated and scaled) + Matrix uvTo00 = new Matrix(4, 4); + uvTo00.setItems(new double[][]{{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}}); + { + vOrig.createFrom2Points(UVPoints[tri[0]], UVPoints[tri[1]]); + double rotAng = -vOrig.angleTo(vFin); + // set to rotation + MultMat.setItems(new double[][]{ + {Math.cos(rotAng), Math.sin(rotAng),0,0}, + {-Math.sin(rotAng),Math.cos(rotAng),0,0}, + {0,0,1,0}, + {0,0,0,1}, + }); + uvTo00.multiply(MultMat); + + double scaleFac = 1/ vOrig.getLength(); + // set to scaling + MultMat.setItems(new double[][]{ + {scaleFac,0,0,0}, + {0,scaleFac,0,0}, + {0,0,scaleFac,0}, + {0,0,0,1}, + }); + uvTo00.multiply(MultMat); + // set to translation + MultMat.setItems(new double[][]{ + {1,0,0,-UVPoints[tri[0]].x}, + {0,1,0,-UVPoints[tri[0]].y}, + {0,0,1,0}, + {0,0,0,1}, + }); + uvTo00.multiply(MultMat); + + } + // faceTo00 takes the face's point 1 to 0,0 with its point 2 at 1,0 (rotated, translated and scale + Matrix faceTo00 = new Matrix(4, 4); + faceTo00.setItems(new double[][]{{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}}); + { + Vector3D rotVector301 = new Vector3D(); + Vector3D rotVector302 = new Vector3D(); + + // rotation (pain) + { + double rotAng; + rotVector301.createFrom2Points(points[tri[0]].point, points[tri[1]].point); + rotVector302.createFrom2Points(points[tri[0]].point, points[tri[2]].point); + + vOrig.x = rotVector301.x; + vOrig.y = rotVector301.y; + rotAng = vFin.angleTo(vOrig); + MultMat.setItems(new double[][]{ + { Math.cos(rotAng), Math.sin(rotAng), 0, 0}, + {-Math.sin(rotAng), Math.cos(rotAng), 0, 0}, + { 0, 0, 1, 0}, + { 0, 0, 0, 1}, + }); + faceTo00 = MultMat.multiplyGetResult(faceTo00); + faceTo00.multiplyVec3to(rotVector301, rotVector301); + + vOrig.x = rotVector301.z; + vOrig.y = rotVector301.y; + rotAng = -vFin.angleTo(vOrig); + MultMat.setItems(new double[][]{ + {1, 0, 0, 0}, + {0, Math.cos(rotAng), Math.sin(rotAng), 0}, + {0, -Math.sin(rotAng), Math.cos(rotAng), 0}, + {0, 0, 0, 1}, + }); + + faceTo00 = MultMat.multiplyGetResult(faceTo00); + faceTo00.multiplyVec3to(rotVector302, rotVector302); + + Vector2D vFin2 = new Vector2D(1, 0); + vOrig.x = rotVector302.x; + vOrig.y = rotVector302.z; + rotAng = -vFin2.angleTo(vOrig); + MultMat.setItems(new double[][]{ + {Math.cos(rotAng), 0, -Math.sin(rotAng), 0}, + {0, 1, 0, 0}, + {Math.sin(rotAng), 0, Math.cos(rotAng), 0}, + {0, 0, 0, 1}, + }); + faceTo00 = MultMat.multiplyGetResult(faceTo00); + } + // scale and translation (less pain) + rotVector301.createFrom2Points(points[tri[0]].point, points[tri[1]].point); + double scaleFac = 1 / rotVector301.getLength(); + MultMat.setItems(new double[][]{ + {scaleFac,0,0,0}, + {0,scaleFac,0,0}, + {0,0,scaleFac,0}, + {0,0,0,1}, + }); + faceTo00.multiply(MultMat); + MultMat.setItems(new double[][]{ + {1,0,0,-points[tri[0]].point.x}, + {0,1,0,-points[tri[0]].point.y}, + {0,0,1,-points[tri[0]].point.z}, + {0,0,0,1}, + }); + faceTo00.multiply(MultMat); + + } + // next, modify the faceto00 matrix to scale and shear such that point 3 matches up with UV point 3. + { + // first, apply the calculated matrices to their respective point 3s + Point3D pointFace = new Point3D(); + pointFace.set(faceTo00.multiplyPoint3raw(points[tri[2]].point.x, points[tri[2]].point.y, points[tri[2]].point.z)); + + Point3D pointUV = new Point3D(); + pointUV.set(uvTo00.multiplyPoint3raw(UVPoints[tri[2]].x, UVPoints[tri[2]].y, 0)); + + double xScale = pointUV.x / pointFace.x; + System.out.println("Scale: " + xScale); + MultMat.setItems(new double[][]{ + {xScale, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1}, + }); + faceTo00 = MultMat.multiplyGetResult(faceTo00); + + double yShearFac = (pointUV.y - pointFace.y) / pointUV.x; + if (!Double.isFinite(yShearFac)){ + yShearFac = 0; + } + MultMat.setItems(new double[][]{ + {1, 0, 0, 0}, + {yShearFac, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1}, + }); + faceTo00 = MultMat.multiplyGetResult(faceTo00); + + // multiply final matrices and set their values in the class + perspectiveMappingMatrices[i][0] = uvTo00.getInverse().multiplyGetResult(faceTo00); + i += 1; + } + /* + System.out.println("UVPoints:###########################"); + //DEBUG - printout the positions of old uv coords and new uv coords + Point3D newPoint = new Point3D(); + for(int i = 0; i < 3; i+=1){ + newPoint.set(uvTo00.multiplyPoint3raw(UVPoints[tri[i]].x, UVPoints[tri[i]].y, 0)); + System.out.println(i + ": " + "xyzOld: " + + UVPoints[tri[i]].x + ", " + UVPoints[tri[i]].y + ", " + 0 + ", new:" + + newPoint.x + ", " + newPoint.y + ", " + newPoint.z); + } + System.out.println("FacePoints:###########################"); + //DEBUG - printout the positions of old face coords and new face coords + Point3D newPoint = new Point3D(); + for (int i = 0; i < 3; i += 1) { + newPoint.set(faceTo00.multiplyPoint3raw(points[tri[i]].point.x, points[tri[i]].point.y, points[tri[i]].point.z)); + System.out.println(i + ": " + "xyzOld: " + + points[tri[i]].point.x + ", " + points[tri[i]].point.y + ", " + points[tri[i]].point.z + ", new:" + + newPoint.x + ", " + newPoint.y + ", " + newPoint.z); + } + */ + } + // OPTIMIZATION - remove the z row from the matrix, since z output should always be zero. + // should decrease processing time of each pixel by a lot + } + + private void bakePerspectiveMatrices(BufferedImage img, BufferedImage zBuf, BufferedImage debugImg, Matrix camMatrix, double FPDis, int scrX, int scrY){ + // calculate matrix which converts 2d points into 3d points + { + // first, we get the rotated plane points and normal points + Point3D planePoint = points[0].getRotatedPoint(); + Vector3D rotatedNormalVector = new Vector3D(0,0,0); + camMatrix.multiplyVec3to(normal, rotatedNormalVector); + /* next we define matrices according to the simultaneous equations: + 1, derived from projection: + point.x = scrX - (scrX*0.5*((fpdis*y)/(z) + 1)); + becomes: + 0 = 0x + y(fpdis)+z(2*point.x/scrX - 1); + where point.x is the 2d x position of the point on the screen. + + 2, derived from projection + point.y = (scrX*0.5*((fpdis*x)/(z) + (scrY/scrX))); + becomes: + 0 = x(scrX*fpDis) + 0y + z(scrY-2*point.y) + where point.y is the 2d y position of the point on the screen. + + 3, derived from the distance to a plane. + a plane's normal vector dotted with a vector that goes from a + point that lies on a plane to the point to be tested, + equals the distance the tested point is from the plane. + This becomes: + x(normal.x)+y(normal.y)+z(normal.z) = + normal.x*planePoint.x + normal.y*planePoint.y + normal.z*planePoint.z + */ + int x = 0; + int y = 0; + Matrix simulMat = new Matrix(3, 3); + simulMat.setItems(new double[][]{ + {0, FPDis, (2*x/(double)scrX)-1}, + {scrX*FPDis, 0, scrY-2*y}, + {normal.x, normal.y, normal.z}, + }); + Matrix simulMat2 = new Matrix(1, 3); + simulMat2.setItems(new double[][]{ + {0}, + {0}, + {normal.x * planePoint.x + normal.y * planePoint.y + normal.z * planePoint.z} + }); + } + } // 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 diff --git a/src/main/java/uk/org/floop/epq3d/Matrix.java b/src/main/java/uk/org/floop/epq3d/Matrix.java index 5c980a9..fa9cfd9 100644 --- a/src/main/java/uk/org/floop/epq3d/Matrix.java +++ b/src/main/java/uk/org/floop/epq3d/Matrix.java @@ -66,6 +66,19 @@ result.z = point.x * getItem(0,2) + point.y* getItem(1,2) + point.z* getItem(2,2) + getItem(3,2); } else {throw new RuntimeException("wrong-dimensions");} } + public void multiplyVec3to(Vector3D vector, Vector3D result){ + if(x==3){ + double x = vector.x * getItem(0,0) + vector.y* getItem(1,0) + vector.z* getItem(2,0); + double y = vector.x * getItem(0,1) + vector.y* getItem(1,1) + vector.z* getItem(2,1); + double z = vector.x * getItem(0,2) + vector.y* getItem(1,2) + vector.z* getItem(2,2); + result.x = x; result.y = y; result.z = z; + } else if(x == 4){ + double x = vector.x * getItem(0,0) + vector.y* getItem(1,0) + vector.z* getItem(2,0) + getItem(3,0); + double y = vector.x * getItem(0,1) + vector.y* getItem(1,1) + vector.z* getItem(2,1) + getItem(3,1); + double z = vector.x * getItem(0,2) + vector.y* getItem(1,2) + vector.z* getItem(2,2) + getItem(3,2); + result.x = x; result.y = y; result.z = z; + } else {throw new RuntimeException("wrong-dimensions");} + } public double[] multiplyPoint3raw(double _x, double _y, double _z) { double[] result = new double[3]; if(x ==3){ @@ -122,18 +135,18 @@ } } // transpose the adjoint matrix (unused because it's easier to do it while copying across - /*{ - double temp; - for(int mx = 0; mx < x; mx+=1){ - for(int my = 0; my <= mx; my += 1){ - if (my != mx){ - temp = adjoint.getItem(mx, my); - adjoint.setItem(mx, my, adjoint.getItem(my, mx)); - adjoint.setItem(my, mx, temp); - } - } - } - }*/ + //{ + // double temp; + // for(int mx = 0; mx < x; mx+=1){ + // for(int my = 0; my <= mx; my += 1){ + // if (my != mx){ + // temp = adjoint.getItem(mx, my); + // adjoint.setItem(mx, my, adjoint.getItem(my, mx)); + // adjoint.setItem(my, mx, temp); + // } + // } + // } + // } // copy the matrix to the result and divide by the determinant for(int mx = 0; mx