import java.awt.image.BufferedImage;
import java.util.ArrayList;
public class Face {
public PointComp[] points;
public Point2D[] UVPoints;
public Vector3D normal;
public Triangle[] tris;
public boolean hasEdges;
public BufferedImage texture;
public Matrix perspectiveMappingMatrix = new Matrix(3, 3);
public boolean isInitialised;
// fixed face
public Face fixedFace;
// working variables
private final Vector2D traVec = new Vector2D(0,0);
private final Vector2D orig01Vec = new Vector2D(0,0);
private final Vector2D fin01Vec = new Vector2D(0,0);
private final Vector2D orig02Vec = new Vector2D(0,0);
private final Vector2D fin02Vec = new Vector2D(0,0);
public void initialise(){
calculateNormal();
separateTris();
fixedFace = new Face();
fixedFace.hasEdges = hasEdges;
fixedFace.texture = texture;
fixedFace.normal = normal;
fixedFace.perspectiveMappingMatrix = new Matrix(3, 3);
fixedFace.perspectiveMappingMatrix.setItems(new double[][] {
{1,0,0},
{0,1,0},
{0,0,1},
});
fixedFace.isInitialised = true;
isInitialised = true;
}
public void invalidate(){
for (Triangle tri:
tris) {
tri.invalidate();
}
}
public int draw(BufferedImage img, BufferedImage zBuf, BufferedImage debugImg, Matrix camMatrix, double FPDis, int scrX, int scrY){
if(!isInitialised){
throw new RuntimeException("Face not initialised");
}
// check for backface culling has been done previously.
// initialise points
int numberOfPixels = 0;
boolean valid = true;
for (PointComp point:
points) {
point.setRotatedPoint(camMatrix);
// if any points are behind the camera, we will need to handle it differently.
if(point.getRotatedPoint().z < 0.1){valid = false;}
// only worth calculating the projected point if they are all valid
if(valid){point.setProjectedPoint(FPDis, scrX, scrY);}
}
// calculatePerspectiveMapping();
if(valid){
for (Triangle tri:
tris) {
numberOfPixels += tri.draw(img, zBuf);
}
} else {
ArrayList<PointComp> 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];
for(int i = 0; i< newTris.length; i+=1){
newTris[i] = new Triangle(
points[0].getProjectedPoint(),
points[i+1].getProjectedPoint(),
points[i+2].getProjectedPoint(),
new boolean[]{hasEdges, hasEdges, hasEdges},
texture, perspectiveMappingMatrix);
}
tris = newTris;
}
public void calculateNormal(){
// too many new variables
Point3D point0 = points[0].point;
Point3D point1 = points[1].point;
Point3D point2;
Vector3D vec1 = new Vector3D(point1.x - point0.x, point1.y - point0.y, point1.z - point0.z);
Vector3D vec2 = new Vector3D(0,0,0); // initialisation otherwise intellij gets mad
// find a vector which is not inline with other vectors
boolean valid = false; int i = 2;
while(!valid && i < points.length) {
point2 = points[i].point;
vec2 = new Vector3D(point2.x - point0.x, point2.y - point0.y, point2.z - point0.z);
double angle = Math.abs(vec1.angleTo(vec2));
if(angle > 0.1 && angle < 2*Math.PI - 0.1){
// if the angle between the vectors is between a threshold, the two vectors are valid.
// else, calculate the second vector using a different set of points.
valid = true;
}}
if(!valid){throw new RuntimeException("Could not calculate normal of face");}
normal = vec1.cross(vec2);
}
private void calculatePerspectiveMapping() {
traVec.createFrom2Points(UVPoints[0], points[0].getProjectedPoint());
orig01Vec.createFrom2Points(points[0].getProjectedPoint(), points[1].getProjectedPoint());
orig02Vec.createFrom2Points(points[0].getProjectedPoint(), points[2].getProjectedPoint());
fin01Vec.createFrom2Points(UVPoints[0], UVPoints[1]);
fin02Vec.createFrom2Points(UVPoints[0], UVPoints[2]);
// setting the perspective matrix to the identity matrix -
// it must remain as the same object so pointers elsewhere still work.
perspectiveMappingMatrix.setItems(new double[][]{
{1,0,0},
{0,1,0},
{0,0,1},
});
Matrix tMat = new Matrix(3, 3);
tMat.setItems(new double[][]{
{1, 0, -traVec.x},
{0, 1, -traVec.y},
{0, 0, 1}
});
double scale = (fin01Vec.getLength()) / orig01Vec.getLength();
Matrix scaMat = new Matrix(3, 3);
scaMat.setItems(new double[][]{
{scale, 0, 0},
{0, scale, 0},
{0, 0, 1}
});
double ang = orig01Vec.angleTo(fin01Vec);
Matrix rotMat = new Matrix(3, 3);
rotMat.setItems(new double[][]{
{Math.cos(ang), -Math.sin(ang), 0},
{Math.sin(ang), Math.cos(ang), 0},
{0, 0, 1}
});
// Matrix that returns the position on a texture from 3d camera space on the face of an object
// we already know the Z depth because of calculations for Z buffers.
//perspectiveMappingMatrix.multiply(rotMat.multiplyGetResult(scaMat.multiplyGetResult(tMat)));
perspectiveMappingMatrix.multiply(scaMat.multiplyGetResult(rotMat.multiplyGetResult(tMat)));
}
}