diff --git a/config.json b/config.json index ca5dcdd..b4f5cd2 100644 --- a/config.json +++ b/config.json @@ -4,11 +4,14 @@ "screenHeight":600, "maxFPS":60, "FOV":100, +"playerSpeed":5.0, +"playerSprintModifier":2.0, "debug":{ "__comment":"values are: enabled/disabled", "masterDebugToggle":"enabled", "drawDebugHud":"disabled", "drawLines":"enabled", - "overrideBackFaceCulling":"disabled" + "overrideBackFaceCulling":"disabled", + "frustumCullingOverridePercent":0 } } diff --git a/src/main/java/uk/org/floop/epq3d/Face.java b/src/main/java/uk/org/floop/epq3d/Face.java index 3ee09fe..5d66ebb 100644 --- a/src/main/java/uk/org/floop/epq3d/Face.java +++ b/src/main/java/uk/org/floop/epq3d/Face.java @@ -4,6 +4,11 @@ public PointComp[] points; public Point2D[] UVPoints; public Vector3D normal; + + public double boundingSphereR; + + public Point3D boundingSphereC; + public Triangle[] tris; public int[][] trisFaceList; public boolean hasEdges; @@ -13,7 +18,42 @@ public boolean isValid; // fixed face public Face fixedFace; - public void initialise(){ + + public void initialise() { + double distance = 0; + double newDis; + Point3D pointA = points[0].point; + Point3D pointB = points[0].point; + Vector3D distanceVec = new Vector3D(); + for (int i = 1; i < points.length; i += 1) { + distanceVec.createFrom2Points(points[0].point, points[i].point); + newDis = distanceVec.getLength(); + if (newDis >= distance) { + pointA = points[i].point; + distance = newDis; + } + } + for (PointComp point : points) { + distanceVec.createFrom2Points(points[0].point, points[0].point); + newDis = distanceVec.getLength(); + 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) { + distanceVec.createFrom2Points(boundingSphereC, point.point); + distance = distanceVec.getLength(); + boundingSphereR = Math.max(boundingSphereR, distance); + } + + calculateNormal(); perspectiveMappingMatrices = new Matrix[points.length - 2][2]; separateTris(); @@ -31,295 +71,234 @@ isInitialised = true; } - public void invalidate(){ - for (Triangle tri: - tris) { + public void invalidate() { + for (Triangle tri : + tris) { tri.invalidate(); } } - public int draw(drawData drawData) { + + public void draw(drawData drawData) { double ang = normal.angleTo(new Vector3D(0.2, 0.5, 1)); if (!isInitialised) { throw new RuntimeException("Face not initialised"); } - if (isValid) { - // the check for backface culling has been done previously. - // initialise points - int numberOfPixels = 0; - // apply point transforms for all points within the face - boolean valid = applyPointTransforms(drawData.camMatrix, drawData.FPDis, drawData.scrX, drawData.scrY); - // this function is completed every frame, without checking whether pixels will be drawn first - bakePerspectiveMatrices(drawData); + // the check for backface culling has been done previously. + // initialise points + // apply point transforms for all points within the face + boolean valid = applyPointTransforms(drawData); + // this function is completed every frame, without checking whether pixels will be drawn first + bakePerspectiveMatrices(drawData); - // if all the points are valid (in front of the camera) draw all tris normally. - if (valid) { - for (Triangle tri : - tris) { - tri.draw(drawData, ang); + // if all the points are valid (in front of the camera) draw all tris normally. + if (valid) { + for (Triangle tri : + tris) { + tri.draw(drawData, ang); + } + } else { + for (int tri_i = 0; tri_i < trisFaceList.length; tri_i += 1) { + // first, count up the number of invalid points in the triangle (points which are behind the camera) + int numOfInvalidPoints = 0; + for (int i = 0; i < 3; i += 1) { + if (points[trisFaceList[tri_i][i]].getRotatedPoint().z < 0.001) { + numOfInvalidPoints += 1; + } } - } else { - /*todo check wether there is any slowdown here - it might be that the compiler just sorts it but i'm not 100% sure. - fix would be to change it so that I only need to loop through points once, and also reduce the number - of temporary point objects i use */ - for (int tri_i = 0; tri_i < trisFaceList.length; tri_i += 1) { - // first, count up the number of invalid points in the triangle (points which are behind the camera) - int numOfInvalidPoints = 0; + if (numOfInvalidPoints == 0) { // if there are no invalid points, treat the triangle normally + // project all points in triangle + tris[tri_i].draw(drawData, ang); + } else if (numOfInvalidPoints == 1) {// if one point is invalid, interpolate 2 new points and draw 2 new triangles: + // find which point is the invalid one TODO could optimise and find this beforehand + int invalidIndex = -1; for (int i = 0; i < 3; i += 1) { if (points[trisFaceList[tri_i][i]].getRotatedPoint().z < 0.001) { - numOfInvalidPoints += 1; + invalidIndex = i; + break; } } - if (numOfInvalidPoints == 0) { // if there are no invalid points, treat the triangle normally - // project all points in triangle - tris[tri_i].draw(drawData, ang); - } else if (numOfInvalidPoints == 1) {// if one point is invalid, interpolate 2 new points and draw 2 new triangles: - // find which point is the invalid one TODO could optimise and find this beforehand - int invalidIndex = -1; - for (int i = 0; i < 3; i += 1) { - if (points[trisFaceList[tri_i][i]].getRotatedPoint().z < 0.001) { - invalidIndex = i; - break; - } - } - if (invalidIndex == -1) { - throw new RuntimeException("How did this happen?!"); - } - Point3D oldPoint1 = points[trisFaceList[tri_i][Math.floorMod(invalidIndex + 1, 3)]].getRotatedPoint(); - Point3D oldPoint2 = points[trisFaceList[tri_i][Math.floorMod(invalidIndex + 2, 3)]].getRotatedPoint(); - Point3D invalidPoint = points[trisFaceList[tri_i][invalidIndex]].getRotatedPoint(); - Point3D newPoint1; - Point3D newPoint2; - // assign old points 1 and 2 based on the index of the invalid point - { - // interpolate between oldPoint1 and invalidPoint - // solving for z = 0.001 for the line between oldPoint1 and invalidPoint, - // separately in the xz and yz planes. - double gradX = (oldPoint1.z - invalidPoint.z) / - (oldPoint1.x - invalidPoint.x); - double gradY = (oldPoint1.z - invalidPoint.z) / - (oldPoint1.y - invalidPoint.y); + if (invalidIndex == -1) { + throw new RuntimeException("How did this happen?!"); + } + Point3D oldPoint1 = points[trisFaceList[tri_i][Math.floorMod(invalidIndex + 1, 3)]].getRotatedPoint(); + Point3D oldPoint2 = points[trisFaceList[tri_i][Math.floorMod(invalidIndex + 2, 3)]].getRotatedPoint(); + Point3D invalidPoint = points[trisFaceList[tri_i][invalidIndex]].getRotatedPoint(); + Point3D newPoint1; + Point3D newPoint2; + // assign old points 1 and 2 based on the index of the invalid point + // interpolate between oldPoint1 and invalidPoint + newPoint1 = interpolate(oldPoint1, invalidPoint); + // interpolate between oldPoint2 and invalid point + newPoint2 = interpolate(oldPoint2, invalidPoint); - newPoint1 = new Point3D( - (0.001 + gradX * oldPoint1.x - oldPoint1.z) / gradX, - (0.001 + gradY * oldPoint1.y - oldPoint1.z) / gradY, - 0.001); - if (!Double.isFinite(gradX)) { - newPoint1.x = oldPoint1.x; - } - if (!Double.isFinite(gradY)) { - newPoint1.y = oldPoint1.y; - } + Triangle newTri; + newTri = new Triangle(oldPoint1.project(drawData), newPoint2.project(drawData), newPoint1.project(drawData), + new boolean[]{false, false, false}, + tris[tri_i].texture, + tris[tri_i].perspectiveMappingMatrix); + newTri.draw(drawData, ang); + newTri = new Triangle(oldPoint1.project(drawData), oldPoint2.project(drawData), newPoint2.project(drawData), + new boolean[]{false, false, false}, + tris[tri_i].texture, + tris[tri_i].perspectiveMappingMatrix); + newTri.draw(drawData, ang); + } else if (numOfInvalidPoints == 2) { // if two points are invalid, interpolate and draw triangle w/ new points: + int validIndex = -1; + for (int i = 0; i < 3; i += 1) { + if (points[trisFaceList[tri_i][i]].getRotatedPoint().z > 0.001) { + validIndex = i; + break; } + } + if (validIndex == -1) { + throw new RuntimeException("How did this happen?!"); + } + Point3D invalidPoint1 = points[trisFaceList[tri_i][Math.floorMod(validIndex + 1, 3)]].getRotatedPoint(); + Point3D invalidPoint2 = points[trisFaceList[tri_i][Math.floorMod(validIndex + 2, 3)]].getRotatedPoint(); + Point3D oldPoint = points[trisFaceList[tri_i][validIndex]].getRotatedPoint(); + Point3D newPoint1; + Point3D newPoint2; + // interpolate for z = 0.001 between invalid1 and oldPoint + newPoint1 = interpolate(oldPoint, invalidPoint1); - // interpolate between oldPoint2 and invalid point - { - // interpolate between oldPoint2 and invalidPoint - // solving for z = 0.001 for the line between oldPoint2 and invalidPoint, - // separately in the xz and yz planes. - double gradX = (oldPoint2.z - invalidPoint.z) / - (oldPoint2.x - invalidPoint.x); - double gradY = (oldPoint2.z - invalidPoint.z) / - (oldPoint2.y - invalidPoint.y); + // interpolate for z = 0.001 between invalid2 and oldPoint + newPoint2 = interpolate(oldPoint, invalidPoint2); - newPoint2 = new Point3D( - (0.001 + gradX * oldPoint2.x - oldPoint2.z) / gradX, - (0.001 + gradY * oldPoint2.y - oldPoint2.z) / gradY, - 0.001); - if (!Double.isFinite(gradX)) { - newPoint2.x = oldPoint2.x; - } - if (!Double.isFinite(gradY)) { - newPoint2.y = oldPoint2.y; - } - } + // create and draw new triangle + Triangle newTri; + newTri = new Triangle(newPoint1.project(drawData), newPoint2.project(drawData), oldPoint.project(drawData), + new boolean[]{false, false, false}, + tris[tri_i].texture, + tris[tri_i].perspectiveMappingMatrix); + newTri.draw(drawData, ang); + } // if all points are invalid, do nothing + } + } + } + private Point3D interpolate(Point3D oldPoint, Point3D invalidPoint){ + double gradX = (oldPoint.z - invalidPoint.z) / + (oldPoint.x - invalidPoint.x); + double gradY = (oldPoint.z - invalidPoint.z) / + (oldPoint.y - invalidPoint.y); - Triangle newTri; - newTri = new Triangle(oldPoint1.project(drawData), newPoint2.project(drawData), newPoint1.project(drawData), - new boolean[]{false, false, false}, - tris[tri_i].texture, - tris[tri_i].perspectiveMappingMatrix); - newTri.draw(drawData, ang); - newTri = new Triangle(oldPoint1.project(drawData), oldPoint2.project(drawData), newPoint2.project(drawData), - new boolean[]{false, false, false}, - tris[tri_i].texture, - tris[tri_i].perspectiveMappingMatrix); - newTri.draw(drawData, ang); - } else if (numOfInvalidPoints == 2) { // if two points are invalid, interpolate and draw triangle w/ new points: - int validIndex = -1; - for (int i = 0; i < 3; i += 1) { - if (points[trisFaceList[tri_i][i]].getRotatedPoint().z > 0.001) { - validIndex = i; - break; - } - } - if (validIndex == -1) { - throw new RuntimeException("How did this happen?!"); - } - Point3D invalidPoint1 = points[trisFaceList[tri_i][Math.floorMod(validIndex + 1, 3)]].getRotatedPoint(); - Point3D invalidPoint2 = points[trisFaceList[tri_i][Math.floorMod(validIndex + 2, 3)]].getRotatedPoint(); - Point3D oldPoint = points[trisFaceList[tri_i][validIndex]].getRotatedPoint(); - Point3D newPoint1; - Point3D newPoint2; - // interpolate for z = 0.001 between invalid1 and oldPoint - { - double gradX = (oldPoint.z - invalidPoint1.z) / - (oldPoint.x - invalidPoint1.x); - double gradY = (oldPoint.z - invalidPoint1.z) / - (oldPoint.y - invalidPoint1.y); + Point3D newPoint = new Point3D( + (0.001 + gradX * oldPoint.x - oldPoint.z) / gradX, + (0.001 + gradY * oldPoint.y - oldPoint.z) / gradY, + 0.001); + if (!Double.isFinite(gradX)) { + newPoint.x = oldPoint.x; + } + if (!Double.isFinite(gradY)) { + newPoint.y = oldPoint.y; + } + return newPoint; + } + /* + if (valid) { + //drawTris(img, zBuf, FPDis, scrX, scrY);} + else { + ArrayList newPoints = new ArrayList<>(); - newPoint1 = new Point3D( - (0.001 + gradX * oldPoint.x - oldPoint.z) / gradX, - (0.001 + gradY * oldPoint.y - oldPoint.z) / gradY, - 0.001); - if (!Double.isFinite(gradX)) { - newPoint1.x = oldPoint.x; - } - if (!Double.isFinite(gradY)) { - newPoint1.y = oldPoint.y; - } - } - // interpolate for z = 0.001 between invalid2 and oldPoint - { - double gradX = (oldPoint.z - invalidPoint2.z) / - (oldPoint.x - invalidPoint2.x); - double gradY = (oldPoint.z - invalidPoint2.z) / - (oldPoint.y - invalidPoint2.y); + // 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); - newPoint2 = new Point3D( - (0.001 + gradX * oldPoint.x - oldPoint.z) / gradX, - (0.001 + gradY * oldPoint.y - oldPoint.z) / gradY, - 0.001); - if (!Double.isFinite(gradX)) { - newPoint2.x = oldPoint.x; - } - if (!Double.isFinite(gradY)) { - newPoint2.y = oldPoint.y; - } - } - - // create and draw new triangle - Triangle newTri; - newTri = new Triangle(newPoint1.project(drawData), newPoint2.project(drawData), oldPoint.project(drawData), - new boolean[]{false, false, false}, - tris[tri_i].texture, - tris[tri_i].perspectiveMappingMatrix); - newTri.draw(drawData, ang); - } // if all points are invalid, do nothing + 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; - - } else { - return 0; } - } - -/* - 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 boolean applyPointTransforms(Matrix camMatrix, double FPDis, int scrX, int scrY){ + */ + public boolean applyPointTransforms(drawData drawData) { boolean valid = true; - for (PointComp point: + for (PointComp point : points) { - point.setRotatedPoint(camMatrix); + point.setRotatedPoint(drawData.camMatrix); // if any points are behind the camera, we will need to handle it differently. - if(point.getRotatedPoint().z < 0.001){ + if (point.getRotatedPoint().z < 0.001) { valid = false; - } - else{ + } else { // only worth calculating the projected point if that point is valid - point.setProjectedPoint(FPDis, scrX, scrY); + point.setProjectedPoint(drawData); } } return valid; } - public void separateTris(){ + + 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){ + for (int i = 0; i < newTris.length; i += 1) { newTrisFaceList[i][0] = 0; - newTrisFaceList[i][1] = i+1; - newTrisFaceList[i][2] = i+2; + newTrisFaceList[i][1] = i + 1; + newTrisFaceList[i][2] = i + 2; newTris[i] = new Triangle( points[0].getProjectedPoint(), - points[i+1].getProjectedPoint(), - points[i+2].getProjectedPoint(), + points[i + 1].getProjectedPoint(), + points[i + 2].getProjectedPoint(), new boolean[]{hasEdges, hasEdges, hasEdges}, texture, perspectiveMappingMatrices[i]); } tris = newTris; trisFaceList = newTrisFaceList; } - public void calculateNormal(){ + + public void calculateNormal() { // too many new variables Point3D point0 = points[0].point; Point3D point1 = points[1].point; @@ -327,14 +306,17 @@ Vector3D vec2 = new Vector3D(); // 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) { + boolean valid = false; + int i = 2; + while (!valid && i <= points.length) { point0 = point1; point1 = points[Math.floorMod(i, points.length)].point; - vec2.x = point1.x - point0.x; vec2.y = point1.y - point0.y; vec2.z = point1.z - point0.z; + vec2.x = point1.x - point0.x; + vec2.y = point1.y - point0.y; + vec2.z = point1.z - point0.z; double angle = Math.abs(vec1.angleTo(vec2)); - if(angle > 0.1 && angle < 2*Math.PI - 0.1){ + 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; @@ -342,9 +324,6 @@ i += 1; } isValid = valid; - if (!isValid){ - int a = 12; - } normal = vec1.cross(vec2); } @@ -354,49 +333,49 @@ // 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); + 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) { + 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}}); + 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}, + {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(); + 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}, + {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}, + {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}}); + 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(); @@ -411,10 +390,10 @@ vOrig.y = rotVector301.y; rotAng = vFin.angleTo(vOrig); MultMat.setItems(new double[][]{ - { Math.cos(rotAng), Math.sin(rotAng), 0, 0}, + {Math.cos(rotAng), Math.sin(rotAng), 0, 0}, {-Math.sin(rotAng), Math.cos(rotAng), 0, 0}, - { 0, 0, 1, 0}, - { 0, 0, 0, 1}, + {0, 0, 1, 0}, + {0, 0, 0, 1}, }); faceTo00 = MultMat.multiplyGetResult(faceTo00); faceTo00.multiplyVec3to(rotVector301, rotVector301); @@ -423,8 +402,8 @@ vOrig.y = rotVector301.y; rotAng = -vFin.angleTo(vOrig); MultMat.setItems(new double[][]{ - {1, 0, 0, 0}, - {0, Math.cos(rotAng), Math.sin(rotAng), 0}, + {1, 0, 0, 0}, + {0, Math.cos(rotAng), Math.sin(rotAng), 0}, {0, -Math.sin(rotAng), Math.cos(rotAng), 0}, {0, 0, 0, 1}, }); @@ -438,8 +417,8 @@ 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, 1, 0, 0}, + {Math.sin(rotAng), 0, Math.cos(rotAng), 0}, {0, 0, 0, 1}, }); faceTo00 = MultMat.multiplyGetResult(faceTo00); @@ -448,17 +427,17 @@ 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}, + {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}, + {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); @@ -483,7 +462,7 @@ faceTo00 = MultMat.multiplyGetResult(faceTo00); double yShearFac = (pointUV.y - pointFace.y) / pointUV.x; - if (!Double.isFinite(yShearFac)){ + if (!Double.isFinite(yShearFac)) { yShearFac = 0; } MultMat.setItems(new double[][]{ @@ -523,12 +502,12 @@ // should decrease processing time of each pixel by a lot } - private void bakePerspectiveMatrices(drawData drawData){ + private void bakePerspectiveMatrices(drawData drawData) { // 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); + Vector3D rotatedNormalVector = new Vector3D(0, 0, 0); drawData.camMatrix.multiplyVec3to(normal, rotatedNormalVector); /* next we define matrices according to the simultaneous equations: 1, derived from projection: @@ -555,8 +534,8 @@ int y = 0; Matrix simulMat = new Matrix(3, 3); simulMat.setItems(new double[][]{ - {0, drawData.FPDis, (2*x/(double)drawData.scrX)-1}, - {drawData.scrX*drawData.FPDis, 0, drawData.scrY-2*y}, + {0, drawData.FPDis, (2 * x / (double) drawData.scrX) - 1}, + {drawData.scrX * drawData.FPDis, 0, drawData.scrY - 2 * y}, {normal.x, normal.y, normal.z}, }); Matrix simulMat2 = new Matrix(1, 3); diff --git a/src/main/java/uk/org/floop/epq3d/Object3d.java b/src/main/java/uk/org/floop/epq3d/Object3d.java index ff85518..c6327b3 100644 --- a/src/main/java/uk/org/floop/epq3d/Object3d.java +++ b/src/main/java/uk/org/floop/epq3d/Object3d.java @@ -1,5 +1,7 @@ package uk.org.floop.epq3d; +import java.util.ArrayList; + public class Object3d { public PointComp[] points; public int[][] faceList; @@ -11,46 +13,57 @@ public boolean hasEdges; public Texture[] textures; - public Object3d(PointComp[] _points, int[][] _faceList, Point2D[][] _uvPoints, boolean _hasEdges, Texture[] _textures, boolean initialise){ + public Object3d(PointComp[] _points, int[][] _faceList, Point2D[][] _uvPoints, boolean _hasEdges, Texture[] _textures, boolean initialise) { points = _points; faceList = _faceList; uvPoints = _uvPoints; hasEdges = _hasEdges; textures = _textures; - if(initialise){initialise();} + if (initialise) { + initialise(); + } } - public void invalidate(){ - for (Face face: - faces) { + public void invalidate() { + for (Face face : + faces) { face.invalidate(); } } - public void draw(drawData drawData){ - int iterator = 0; - for (Face face: - faces) { + + public void draw(drawData drawData, ArrayList frustumInfo) { + for (Face face : + faces) { // check whether faces are pointing towards or away from the camera - Vector3D camVec = new Vector3D(0,0,0); + Vector3D camVec = new Vector3D(0, 0, 0); camVec.createFrom2Points(drawData.playerPos, face.points[0].point); - int numberOfPixels = 0; - if(drawData.backfaceCullingDisabled || face.normal.angleTo(camVec) >= Math.PI/2-0.01){ - numberOfPixels = face.draw(drawData); + if (drawData.backfaceCullingDisabled || face.normal.angleTo(camVec) >= Math.PI / 2 - 0.01) { + // check for frustum culling of faces + // works the same as for collections. + // see explanation within ObjectCollection + boolean draw = true; + for (int planeId: + frustumInfo) { + double dist = drawData.frustumPlanes[planeId].getDistance(face.boundingSphereC); + if (dist < -face.boundingSphereR + face.boundingSphereR * drawData.frustumCullingOverridePercent){ + draw = false; + break; + } + } + if(draw){face.draw(drawData);} } - if(drawData.debugHud){ - drawData.debugImg.getGraphics().drawString(iterator + ": " +numberOfPixels , 10, 20 + 10*iterator); - iterator += 1;} } } - public void initialise(){ + + public void initialise() { // init faces faces = new Face[faceList.length]; - for (int face = 0; face < faceList.length; face+= 1) { + for (int face = 0; face < faceList.length; face += 1) { faces[face] = new Face(); faces[face].points = new PointComp[faceList[face].length]; - for (int point = 0; point < faceList[face].length; point += 1){ + for (int point = 0; point < faceList[face].length; point += 1) { faces[face].points[point] = points[faceList[face][point]]; } faces[face].hasEdges = hasEdges; @@ -63,36 +76,33 @@ double newDis; Point3D pointA = points[0].point; Point3D pointB = points[0].point; - // todo - maybe use some vector classes?7 - // i can't be bothered - for (int i = 1; i < points.length; i+=1) { - newDis = Math.pow(points[0].point.x - points[i].point.x, 2) + - Math.pow(points[0].point.y - points[i].point.y, 2) + - Math.pow(points[0].point.z - points[i].point.z, 2); - if (newDis >= distance){ + Vector3D distanceVec = new Vector3D(); + for (int i = 1; i < points.length; i += 1) { + distanceVec.createFrom2Points(points[0].point, points[i].point); + newDis = distanceVec.getLength(); + if (newDis >= distance) { pointA = points[i].point; - distance = newDis;}} + 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){ + distanceVec.createFrom2Points(points[0].point, points[0].point); + newDis = distanceVec.getLength(); + if (newDis >= distance) { pointB = point.point; - distance = newDis;}} + 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: + (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; - } + distanceVec.createFrom2Points(boundingSphereC, point.point); + distance = distanceVec.getLength(); + boundingSphereR = Math.max(boundingSphereR, distance); } } } diff --git a/src/main/java/uk/org/floop/epq3d/ObjectCollection.java b/src/main/java/uk/org/floop/epq3d/ObjectCollection.java index 0433f08..999ddf1 100644 --- a/src/main/java/uk/org/floop/epq3d/ObjectCollection.java +++ b/src/main/java/uk/org/floop/epq3d/ObjectCollection.java @@ -5,14 +5,14 @@ // stores both objects and other object collections public class ObjectCollection { public String name; - public ArrayList points = new ArrayList(); + 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 ArrayList subCollections = new ArrayList<>(); + public ArrayList objects = new ArrayList<>(); public ArrayList initialiseAll(){ for (ObjectCollection subCollection: @@ -24,6 +24,43 @@ return points; } + public void initialise(){ + // init bounding sphere + double distance = 0; + double newDis; + Point3D pointA = points.get(0).point; + Point3D pointB = points.get(0).point; + Vector3D distanceVec = new Vector3D(); + // find the point which is the furthest away from arbitrary point 0 + for (int i = 1; i < points.size(); i+=1) { + distanceVec.createFrom2Points(points.get(0).point, points.get(i).point); + newDis = distanceVec.getLength(); + if (newDis >= distance){ + pointA = points.get(i).point; + distance = newDis; + } + } + // find the point which is the furthest away the point which was found previously + for (PointComp point : points) { + distanceVec.createFrom2Points(pointA, point.point); + newDis = distanceVec.getLength(); + if (newDis >= distance){ + pointB = point.point; + distance = newDis;}} + // define the bounding sphere in terms of these two furthest points + boundingSphereC = new Point3D( + (pointA.x + pointB.x)/2, + (pointA.y + pointB.y)/2, + (pointA.z + pointB.z)/2); + boundingSphereR = Math.sqrt(distance)/2; + // make the radius bigger depending on the ?? + for (PointComp point: + points) { + distanceVec.createFrom2Points(boundingSphereC, point.point); + distance = distanceVec.getLength(); + boundingSphereR = Math.max(boundingSphereR, distance); + } + } 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 @@ -41,37 +78,60 @@ } } - public void draw(drawData drawData){ + public void draw(drawData drawData, ArrayList frustumInfo){ for (Object3d object: objects) { boolean draw = true; - int i = 0; - for (Plane plane: - drawData.frustumPlanes) { + ArrayList newFrustumInfo = new ArrayList<>(); + // works the same as for collections. + // see explanation below + for (int planeId: + frustumInfo) { + double dist = drawData.frustumPlanes[planeId].getDistance(object.boundingSphereC); + if(drawData.debugHud || true) { drawData.debugImg.getGraphics().drawString( - "Dis: " + String.format("%.1f", plane.getDistance(object.boundingSphereC)), 500, 10 + 20*i); - if(plane.getDistance(object.boundingSphereC) < -object.boundingSphereR){ + "Dis: " + String.format("%.1f", dist + object.boundingSphereR), 500, 10 + 20 * planeId); + } + if (dist < -object.boundingSphereR){ draw = false; - break;} - i += 1; + break; + } else if (dist < object.boundingSphereR){ + newFrustumInfo.add(planeId); + } } - if(draw){object.draw(drawData);} + if(draw){object.draw(drawData, newFrustumInfo);} } - // todo check for frustum culling for(ObjectCollection collection: subCollections){ boolean draw = true; - int i = 0; - for (Plane plane: - drawData.frustumPlanes) { - drawData.debugImg.getGraphics().drawString( - "Dis: " + String.format("%.1f", plane.getDistance(collection.boundingSphereC)), 500, 10 + 20*i); - if(plane.getDistance(collection.boundingSphereC) < -collection.boundingSphereR){ + ArrayList newFrustumInfo = new ArrayList<>(); + for (int planeId: + frustumInfo){ + double dist = drawData.frustumPlanes[planeId].getDistance(collection.boundingSphereC); + if(drawData.debugHud) { + drawData.debugImg.getGraphics().drawString( + "Dis: " + String.format("%.1f", dist), 500, 10 + 20 * planeId); + } + /* if the distance is less than (more negative than) the negative bounding sphere radius, we know that + the bounding sphere lies completely outside the plane, so we don't need to draw it. + */ + if(dist < -collection.boundingSphereR){ draw = false; - break;} - i += 1; + break; + } + /* if the distance is less than the _positive_ radius of the sphere, then we know that the sphere + intersects that frustum plane. Therefore, it's worth checking that frustum plane further collections + down the line. + However, if the distance is greater than the positive radius, then we know that the sphere does not + intersect that frustum plane at all, which means it is pointless to check it. + */ + else if(dist < collection.boundingSphereR) { + newFrustumInfo.add(planeId); + } + // else if not technically required because of break but it makes me happy + } - collection.draw(drawData); + if(draw){collection.draw(drawData, newFrustumInfo);} } } public void addCollection(){ @@ -82,60 +142,22 @@ } // making sure that all the point indices make sense might be a nightmare but ehh public void addObject(Object3d object){ - PointComp[] newpoints = object.points; + PointComp[] newPoints = object.points; objects.add(object); // add the new object // merge lists - for (PointComp newpoint : newpoints) { + 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) { + 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; + points.add(newPoint); } } } diff --git a/src/main/java/uk/org/floop/epq3d/Player.java b/src/main/java/uk/org/floop/epq3d/Player.java index eeac9ce..16d0be2 100644 --- a/src/main/java/uk/org/floop/epq3d/Player.java +++ b/src/main/java/uk/org/floop/epq3d/Player.java @@ -7,20 +7,32 @@ //public Plane[] frustumPlanes = new Plane[6]; // image that represents the player's position on the board // current position of the player on the board grid + // working variables private final Matrix rotMatrix = new Matrix(3, 3); public final Matrix camMatrix = new Matrix(4, 3); private final Matrix invRotMatrix = new Matrix(3, 3); private final Matrix invCamMatrix = new Matrix(4, 3); protected Point3D FPWorldPos; protected PointComp[] ScreenCornerPosS = new PointComp[4]; + public Vector3D viewVector = new Vector3D(0,0,0); + + public boolean isSprinting = false; + + // movement variables private final Point3D position = new Point3D(0,0,0); private final Point3D rotation = new Point3D(0,Math.PI / 2, 0); private final Vector3D direction = new Vector3D(0,0,0); - public Vector3D viewVector = new Vector3D(0,0,0); + + // button variables + public int[] keysPressed = new int[6]; + // order: fwd, bkwd, left, right, up, down + // constants public double mouseSensitivity = 0.005; // radians per pixel // called when the player is initialised public Player(drawData drawData) { - drawData.FPDis = 1/Math.tan(Math.toRadians(drawData.FOV)/2d); + //drawData.FPDis = 1/Math.tan(Math.toRadians(drawData.FOV)/2d); + drawData.FPDis = 1/Math.tan(Math.toRadians(drawData.FOV/2d)); + for(int i = 0; i < 6; i+=1){ drawData.frustumPlanes[i] = new Plane(); } @@ -29,41 +41,82 @@ double yVal = (double)drawData.scrY / (double)drawData.scrX; // flip x and y because reasons // thank you past me for this incredible description - 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); + ScreenCornerPosS[0] = new PointComp(-yVal,-1, drawData.FPDis); + ScreenCornerPosS[1] = new PointComp(yVal,-1, drawData.FPDis); + ScreenCornerPosS[2] = new PointComp(-yVal,1, drawData.FPDis); + ScreenCornerPosS[3] = new PointComp(yVal,1, drawData.FPDis); } 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; + // handling direction + switch(key){ + case KeyEvent.VK_W: + keysPressed[0] = 1; + break; + case KeyEvent.VK_S: + keysPressed[1] = 1; + break; + case KeyEvent.VK_A: + keysPressed[2] = 1; + break; + case KeyEvent.VK_D: + keysPressed[3] = 1; + break; + case KeyEvent.VK_SPACE: + keysPressed[4] = 1; + break; + case KeyEvent.VK_SHIFT: + keysPressed[5] = 1; + break; } +// 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; + switch(key){ + case KeyEvent.VK_W: + keysPressed[0] = 0; + break; + case KeyEvent.VK_S: + keysPressed[1] = 0; + break; + case KeyEvent.VK_A: + keysPressed[2] = 0; + break; + case KeyEvent.VK_D: + keysPressed[3] = 0; + break; + case KeyEvent.VK_SPACE: + keysPressed[4] = 0; + break; + case KeyEvent.VK_SHIFT: + keysPressed[5] = 0; + break; } +// 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(drawData drawData) { // gets called once every tick, before the repainting process happens. @@ -82,6 +135,14 @@ } else if(rotation.y > Math.PI){ rotation.y = Math.PI; } + // calculate the speed multiplier based on playerSpeed and the last frame time. + double speedMultiplier = drawData.playerSpeed * drawData.lastFrameTime / 1000; + // calculate the direction based on the current keys pressed + direction.x = keysPressed[0] - keysPressed[1]; + direction.y = keysPressed[2] - keysPressed[3]; + direction.z = keysPressed[4] - keysPressed[5]; + + // define rotation and translation matrices Matrix zMat = new Matrix(3, 3); zMat.setItems(new double[][]{ @@ -126,9 +187,9 @@ izMat.multiplyVec3to(direction, dirVec); double dirLen = dirVec.getLength(); if (dirLen != 0) { - dirVec.x = dirVec.x / (dirLen * 40); - dirVec.y = dirVec.y / (dirLen * 40); - dirVec.z = dirVec.z / (dirLen * 40); + dirVec.x = dirVec.x * speedMultiplier / (dirLen); + dirVec.y = dirVec.y * speedMultiplier / (dirLen); + dirVec.z = dirVec.z * speedMultiplier / (dirLen); } position.translate(dirVec); @@ -162,9 +223,9 @@ // calculate camera focal point and edges in world coordinates FPWorldPos = new Point3D( - position.x - viewVector.x*drawData.FPDis, - position.y - viewVector.y*drawData.FPDis, - position.z - viewVector.z* drawData.FPDis); + position.x, + position.y, + position.z); for (PointComp point: ScreenCornerPosS) { point.invalidate(); @@ -172,8 +233,9 @@ } // find frustum planes // near plane - drawData.frustumPlanes[0].initFrom3Points(ScreenCornerPosS[0].getRotatedPoint(), - ScreenCornerPosS[1].getRotatedPoint(), ScreenCornerPosS[2].getRotatedPoint()); +// drawData.frustumPlanes[0].initFrom3Points(ScreenCornerPosS[0].getRotatedPoint(), +// ScreenCornerPosS[1].getRotatedPoint(), ScreenCornerPosS[2].getRotatedPoint()); + drawData.frustumPlanes[0].initFromPointAndVector(position, viewVector); // far plane int farPlaneDis = 1000; drawData.frustumPlanes[1].initFromPointAndVector( @@ -196,11 +258,7 @@ // bottom plane drawData.frustumPlanes[5].initFrom3Points(FPWorldPos, ScreenCornerPosS[2].getRotatedPoint(), ScreenCornerPosS[0].getRotatedPoint()); - - // update drawData } - - // returnValue Functions public Point3D getPos() { return position; } diff --git a/src/main/java/uk/org/floop/epq3d/Point3D.java b/src/main/java/uk/org/floop/epq3d/Point3D.java index 3d1b2cc..d8fe766 100644 --- a/src/main/java/uk/org/floop/epq3d/Point3D.java +++ b/src/main/java/uk/org/floop/epq3d/Point3D.java @@ -46,16 +46,34 @@ ); } public Point2D project(drawData drawData) { - return new Point2D( - drawData.scrX - (int)(drawData.scrX*0.5*((drawData.FPDis*y)/(z) + 1)), - (int)(drawData.scrX*0.5*((drawData.FPDis*x)/(z) + ((double)drawData.scrY/(double)drawData.scrX))) - ); + Point2D point = new Point2D(0,0); +// draw Data.scrX - (int)(drawData.scrX*0.5*((drawData.FPDis*y)/(z) + 1)), +// (int)(drawData.scrX*0.5*((drawData.FPDis*x)/(z) + ((double)drawData.scrY/(double)drawData.scrX))) + point.x = drawData.scrX - (int)(drawData.scrX*0.5*((drawData.FPDis*y)/(z) + 1)); + point.y = (int)(drawData.scrX*0.5*((drawData.FPDis*x)/(z) + ((double)drawData.scrY/(double)drawData.scrX))); + /* link: + https://en.wikipedia.org/wiki/3D_projection + Mathematical formula + */ +// point.x = (int)((point.y * drawData.scrX) / (point.z * 1)*drawData.FPDis); +// point.y = (int)((point.x * drawData.scrX) / (point.z * 1)*drawData.FPDis); + + if(Math.abs(point.x) > 5000 || Math.abs(point.y) > 5000){ + int debug = 12; + } + return point; } - public void projectOn(double fpdis, int scrX, int scrY, Point2D point){ + public void projectOn(drawData drawData, 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))); + point.x = drawData.scrX - (int)(drawData.scrX*0.5*((drawData.FPDis*y)/(z) + 1)); + point.y = (int)(drawData.scrX*0.5*((drawData.FPDis*x)/(z) + ((double)drawData.scrY/(double)drawData.scrX))); + /* link: + https://en.wikipedia.org/wiki/3D_projection + Mathematical formula + */ +// point.x = (int)((-y * drawData.scrX) / (z * 2)*drawData.FPDis) + drawData.scrX / 2; +// point.y = (int)((x * drawData.scrX) / (z * 2)*drawData.FPDis) + drawData.scrX / 2; } public double[] get(){ return new double[]{x, y, z}; diff --git a/src/main/java/uk/org/floop/epq3d/PointComp.java b/src/main/java/uk/org/floop/epq3d/PointComp.java index 934be18..c82a1e3 100644 --- a/src/main/java/uk/org/floop/epq3d/PointComp.java +++ b/src/main/java/uk/org/floop/epq3d/PointComp.java @@ -32,9 +32,9 @@ isRotatedPointValid = true; } } - public void setProjectedPoint(double FPDis, int scrX, int scrY){ + public void setProjectedPoint(drawData drawData){ if(!isProjectedPointValid) { - rotatedPoint.projectOn(FPDis, scrX, scrY, projectedPoint); + rotatedPoint.projectOn(drawData, projectedPoint); projectedPoint.z = rotatedPoint.z; isProjectedPointValid = true; } diff --git a/src/main/java/uk/org/floop/epq3d/Screen.java b/src/main/java/uk/org/floop/epq3d/Screen.java index 4baf557..f0b7841 100644 --- a/src/main/java/uk/org/floop/epq3d/Screen.java +++ b/src/main/java/uk/org/floop/epq3d/Screen.java @@ -4,11 +4,12 @@ import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; +import java.util.ArrayList; public class Screen extends JPanel implements ActionListener, KeyListener, MouseListener, MouseMotionListener { // __working variables__ - public boolean initialised = false; + public boolean initialised; public long lastTime = 0; // mouse private boolean captured = false; @@ -40,7 +41,6 @@ // double ang = 0; // __config variables__ // controls the delay between each tick in ms - private final int DELAY = 1; // suppress serialization warning private static final long serialVersionUID = 490905409104883233L; @@ -69,14 +69,9 @@ // initialize the game state player = new Player(drawData); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - System.out.println("aaaaaaaaaaa"); - //throw new RuntimeException(e); - } + lastTime = System.currentTimeMillis(); // this frameTimer will call the actionPerformed() method every DELAY ms - frameTimer = new Timer(DELAY, this); + frameTimer = new Timer(drawData.delay, this); frameTimer.start(); initialised = true; } @@ -106,8 +101,6 @@ Toolkit.getDefaultToolkit().sync(); } private void drawScreen(Graphics g) { - - // System.out.println(zBuf.getRGB(0,0)); // zBuf.setRGB(0,0, 100); // System.out.println(zBuf.getRGB(0,0)); @@ -145,12 +138,14 @@ try{t1.draw(img);}catch (NullPointerException ignored){} try{t2.draw(img);}catch (NullPointerException ignored){} ang += 0.02;*/ - + drawData.lastFrameTime = System.currentTimeMillis()- lastTime; + lastTime = System.currentTimeMillis(); mainCollection.invalidate(true); - mainCollection.draw(drawData); + ArrayList FrustumInfo = new ArrayList<>(); + for(int i = 0; i<6; i += 1){FrustumInfo.add(i);} + mainCollection.draw(drawData, FrustumInfo); // DEBUG DRAWING { - drawData.debugImg.getGraphics().setColor(new Color(255,255,255,0)); //debugImg.getGraphics().drawString(Math.round(1000 / (float) (System.currentTimeMillis() - lastTime)) + " fps", 10, 10); drawData.debugImg.getGraphics().drawString("FrameTime: " + (System.currentTimeMillis() - lastTime) + " millis", 10, 10); if(drawData.debugHud) { @@ -188,19 +183,21 @@ } } // write debug overlay - g.drawImage(drawData.debugImg, 0, 0, this); BufferedImage bufImg = new BufferedImage(drawData.scrX, drawData.scrY, BufferedImage.TYPE_INT_ARGB); for(int x = 0; x < drawData.scrX; x +=1){ for(int y = 0; y < drawData.scrY; y += 1){ bufImg.setRGB(x, y, drawData.drawImg[x][y]); drawData.drawImg[x][y] = 0; drawData.zBuf[x][y] = 0; - drawData.debugImg.setRGB(x, y, 0); } } g.drawImage(bufImg, 0, 0, this); - - lastTime = System.currentTimeMillis(); + g.drawImage(drawData.debugImg, 0, 0, this); + for(int x = 0; x < drawData.scrX; x +=1){ + for(int y = 0; y < drawData.scrY; y += 1){ + drawData.debugImg.setRGB(x, y, 0); + } + } } // HTTPPost post = new HTTPPost(); // // json WRITING diff --git a/src/main/java/uk/org/floop/epq3d/Triangle.java b/src/main/java/uk/org/floop/epq3d/Triangle.java index b6cd6c2..3bc0e14 100644 --- a/src/main/java/uk/org/floop/epq3d/Triangle.java +++ b/src/main/java/uk/org/floop/epq3d/Triangle.java @@ -39,77 +39,84 @@ public int draw(drawData drawData, double ang){ // long lastMillis; + if (!is_initialised){ + if(initialise(drawData)){ + int[] point1; + int[] point2; + char currentLine = 'A'; - if (!is_initialised){initialise();} - int[] point1; - int[] point2; - char currentLine = 'A'; + lastMillis = System.currentTimeMillis(); - 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] < drawData.scrX && point1[1] < drawData.scrY){ - drawData.drawImg[point1[0]][point1[1]] = Color.HSBtoRGB(0, 1, 1); - } - try { - 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] < drawData.scrX && point2[1] < drawData.scrY){ - drawData.drawImg[point2[0]][point2[1]] = Color.HSBtoRGB(0, 1, 1); + 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] < drawData.scrX && point1[1] < drawData.scrY){ + drawData.drawImg[point1[0]][point1[1]] = Color.HSBtoRGB(0, 1, 1); } - point2 = LineA.nextPix(); + try { + point1 = LineLong.nextPix(); + } + catch (Exception e){ + throw new RuntimeException("accessed too many line pixels"); + } } - catch (RuntimeException e){ - currentLine = 'B'; - // point2 = LineB.nextPix(); + 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] < drawData.scrX && point2[1] < drawData.scrY){ + drawData.drawImg[point2[0]][point2[1]] = Color.HSBtoRGB(0, 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] < drawData.scrX && point2[1] < drawData.scrY){ + drawData.drawImg[point2[0]][point2[1]] = Color.HSBtoRGB(0, 1, 1); + } + point2 = LineB.nextPix(); + } + } + // cancel drawing if the x value of the triangle is out of bounds + if (x >= drawData.scrX) {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], drawData.scrY - 1); y += 1) { + // function only exists so I don't have to copy paste code everywhere. + drawPix(drawData, currentLine, x, y, point1[1], point2[1], ang); + } + } else { + for (int y = Math.max(point2[1], 0); y <= Math.min(point1[1], drawData.scrY - 1); y += 1) { + drawPix(drawData, currentLine, x,y, point1[1], point2[1], ang); + } + } } } - else { - if(LineB.isDrawn && // draw line pixels if needed, and on screen - point2[0] > 0 && point2[1] > 0 && point2[0] < drawData.scrX && point2[1] < drawData.scrY){ - drawData.drawImg[point2[0]][point2[1]] = Color.HSBtoRGB(0, 1, 1); - } - point2 = LineB.nextPix(); - } - } - // cancel drawing if the x value of the triangle is out of bounds - if (x >= drawData.scrX) {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], drawData.scrY - 1); y += 1) { - // function only exists so I don't have to copy paste code everywhere. - drawPix(drawData, currentLine, x, y, point1[1], point2[1], ang); - } - } else { - for (int y = Math.max(point2[1], 0); y <= Math.min(point1[1], drawData.scrY - 1); y += 1) { - drawPix(drawData, currentLine, x,y, point1[1], point2[1], ang); + lastMillis = (System.currentTimeMillis() - lastMillis); + return (int)lastMillis; + } } - } - } - lastMillis = (System.currentTimeMillis() - lastMillis); - return (int)lastMillis; + return 0; } - public void initialise(){ + public boolean initialise(drawData drawData){ 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))); + if(max.x < 0 || min.x > drawData.scrX || max.y < 0 || min.y > drawData.scrY){ + return false; + } // 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 @@ -148,6 +155,7 @@ } // assign points to lines is_initialised = true; + return true; } private void drawPix(drawData drawData, char currentLine, int x, int y, int y1, int y2, double ang){ // find Z coordinate of pixel diff --git a/src/main/java/uk/org/floop/epq3d/drawData.java b/src/main/java/uk/org/floop/epq3d/drawData.java index 71121d5..ea51778 100644 --- a/src/main/java/uk/org/floop/epq3d/drawData.java +++ b/src/main/java/uk/org/floop/epq3d/drawData.java @@ -5,6 +5,7 @@ import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; +import java.awt.*; import java.awt.image.BufferedImage; import java.io.FileReader; import java.io.IOException; @@ -15,13 +16,20 @@ */ public class drawData { // config - int scrX; - int scrY; - int delay; - int FOV; - boolean debugHud; - boolean backfaceCullingDisabled; + public int scrX; + public int scrY; + public int delay; + public int FOV; + + // debug + public boolean debugHud; + public boolean backfaceCullingDisabled; + public int frustumCullingOverridePercent; + + // player + public double playerSpeed; + public double playerSprintModifier; // draw onto public int[][] drawImg; public int[][] zBuf; @@ -34,6 +42,7 @@ public double FPDis; public Plane[] frustumPlanes = new Plane[6]; public Point2D mouseRel; + public drawData(String filePath){ Object obj; try { @@ -46,8 +55,10 @@ JSONObject file = (JSONObject)obj; scrX = (int) (long) file.get("screenWidth"); scrY = (int) (long) file.get("screenHeight"); - delay = (int) (1/((double) (long) file.get("maxFPS"))); + delay = (int) ( 1000*(1/((double) (long) file.get("maxFPS")))); FOV = (int) (long) file.get("FOV"); + playerSpeed = (double) file.get("playerSpeed"); + playerSprintModifier = (double) file.get("playerSprintModifier"); // debug @@ -55,12 +66,14 @@ if(toBoolean(debugFile.get("masterDebugToggle"))){ debugHud = toBoolean(debugFile.get("drawDebugHud")); backfaceCullingDisabled = toBoolean(debugFile.get("overrideBackFaceCulling")); + frustumCullingOverridePercent = (int)(long)debugFile.get("frustumCullingOverridePercent"); } drawImg = new int[scrX][scrY]; zBuf = new int[scrX][scrY]; debugImg = new BufferedImage(scrX, scrY, BufferedImage.TYPE_INT_ARGB); debugImg.createGraphics(); + debugImg.getGraphics().setColor(new Color(0,0,0,255)); } private Boolean toBoolean(Object obj){ if(Objects.equals(obj.toString(), "enabled")){