Newer
Older
EPQ-3D-renderer / src / main / java / uk / org / floop / epq3d / Face.java
@cory cory on 1 Feb 2023 33 KB Fix #14
package uk.org.floop.epq3d;

import java.awt.image.BufferedImage;

public class Face {
    public PointComp[] points;
    public Point2D[] UVPoints;
    public Vector3D normal;
    public Triangle[] tris;
    public int[][] trisFaceList;
    public boolean hasEdges;
    public Texture texture;
    public Matrix[][] perspectiveMappingMatrices;
    public boolean isInitialised;
    public boolean isValid;
    // fixed face
    public Face fixedFace;
    public void initialise(){
        calculateNormal();
        perspectiveMappingMatrices = new Matrix[points.length - 2][2];
        separateTris();
        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();
        fixedFace.hasEdges = hasEdges;
        fixedFace.texture = texture;
        fixedFace.normal = normal;
        fixedFace.isInitialised = true;
        // the fixed face inherits the perspective mapping matrices from the true face. This is because the transforms
        // stay the same, just the edge bounds of the face have changed.
        fixedFace.perspectiveMappingMatrices = perspectiveMappingMatrices;
        isInitialised = true;
    }

    public void invalidate(){
        for (Triangle tri:
             tris) {
            tri.invalidate();
        }
    }
    public int draw(int[][] img, int[][] zBuf, BufferedImage debugImg, Matrix camMatrix, double FPDis, int scrX, int scrY) {
        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(camMatrix, FPDis, scrX, scrY);
            // this function is completed every frame, without checking whether pixels will be drawn first
            bakePerspectiveMatrices(img, zBuf, debugImg, camMatrix, FPDis, scrX, scrY);

            // if all the points are valid (in front of the camera) draw all tris normally.
            if (valid) {
                for (Triangle tri :
                        tris) {
                    tri.draw(img, zBuf, FPDis, scrX, scrY, ang);
                }
            } 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;
                    for (int i = 0; i < 3; i += 1) {
                        if (points[trisFaceList[tri_i][i]].getRotatedPoint().z < 0.1) {
                            numOfInvalidPoints += 1;
                        }
                    }
                    if (numOfInvalidPoints == 0) { // if there are no invalid points, treat the triangle normally
                        // project all points in triangle
                        tris[tri_i].draw(img, zBuf, FPDis, scrX, scrY, 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.1) {
                                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.1 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);

                            newPoint1 = new Point3D(
                                    (0.1 + gradX * oldPoint1.x - oldPoint1.z) / gradX,
                                    (0.1 + gradY * oldPoint1.y - oldPoint1.z) / gradY,
                                    0.1);
                            if (!Double.isFinite(gradX)) {
                                newPoint1.x = oldPoint1.x;
                            }
                            if (!Double.isFinite(gradY)) {
                                newPoint1.y = oldPoint1.y;
                            }
                        }

                        // interpolate between oldPoint2 and invalid point
                        {
                            // interpolate between oldPoint2 and invalidPoint
                            // solving for z = 0.1 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);

                            newPoint2 = new Point3D(
                                    (0.1 + gradX * oldPoint2.x - oldPoint2.z) / gradX,
                                    (0.1 + gradY * oldPoint2.y - oldPoint2.z) / gradY,
                                    0.1);
                            if (!Double.isFinite(gradX)) {
                                newPoint2.x = oldPoint2.x;
                            }
                            if (!Double.isFinite(gradY)) {
                                newPoint2.y = oldPoint2.y;
                            }
                        }

                        Triangle newTri;
                        newTri = new Triangle(oldPoint1.project(FPDis, scrX, scrY), newPoint2.project(FPDis, scrX, scrY), newPoint1.project(FPDis, scrX, scrY),
                                new boolean[]{false, false, false},
                                tris[tri_i].texture,
                                tris[tri_i].perspectiveMappingMatrix);
                        newTri.draw(img, zBuf, FPDis, scrX, scrY, ang);
                        newTri = new Triangle(oldPoint1.project(FPDis, scrX, scrY), oldPoint2.project(FPDis, scrX, scrY), newPoint2.project(FPDis, scrX, scrY),
                                new boolean[]{false, false, false},
                                tris[tri_i].texture,
                                tris[tri_i].perspectiveMappingMatrix);
                        newTri.draw(img, zBuf, FPDis, scrX, scrY, 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.1) {
                                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.1 between invalid1 and oldPoint
                        {
                            double gradX = (oldPoint.z - invalidPoint1.z) /
                                    (oldPoint.x - invalidPoint1.x);
                            double gradY = (oldPoint.z - invalidPoint1.z) /
                                    (oldPoint.y - invalidPoint1.y);

                            newPoint1 = new Point3D(
                                    (0.1 + gradX * oldPoint.x - oldPoint.z) / gradX,
                                    (0.1 + gradY * oldPoint.y - oldPoint.z) / gradY,
                                    0.1);
                            if (!Double.isFinite(gradX)) {
                                newPoint1.x = oldPoint.x;
                            }
                            if (!Double.isFinite(gradY)) {
                                newPoint1.y = oldPoint.y;
                            }
                        }
                        // interpolate for z = 0.1 between invalid2 and oldPoint
                        {
                            double gradX = (oldPoint.z - invalidPoint2.z) /
                                    (oldPoint.x - invalidPoint2.x);
                            double gradY = (oldPoint.z - invalidPoint2.z) /
                                    (oldPoint.y - invalidPoint2.y);

                            newPoint2 = new Point3D(
                                    (0.1 + gradX * oldPoint.x - oldPoint.z) / gradX,
                                    (0.1 + gradY * oldPoint.y - oldPoint.z) / gradY,
                                    0.1);
                            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(FPDis, scrX, scrY), newPoint2.project(FPDis, scrX, scrY), oldPoint.project(FPDis, scrX, scrY),
                                new boolean[]{false, false, false},
                                tris[tri_i].texture,
                                tris[tri_i].perspectiveMappingMatrix);
                        newTri.draw(img, zBuf, FPDis, scrX, scrY, ang);
                    } // if all points are invalid, do nothing
                }
            }
            return numberOfPixels;

        } else {
            return 0;
        }
    }

/*
        if (valid) {
            //drawTris(img, zBuf, FPDis, scrX, scrY);}
        else {
            ArrayList<PointComp> 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){
        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;
            }
            else{
                // only worth calculating the projected point if that point is valid
                point.setProjectedPoint(FPDis, scrX, scrY);
            }
        }
        return valid;
    }
    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] = 0;
            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(),
                    new boolean[]{hasEdges, hasEdges, hasEdges},
                    texture, perspectiveMappingMatrices[i]);
        }
        tris = newTris;
        trisFaceList = newTrisFaceList;
    }
    public void calculateNormal(){
        // too many new variables
        Point3D point0 = points[0].point;
        Point3D point1 = points[1].point;
        Vector3D vec1 = new Vector3D(point1.x - point0.x, point1.y - point0.y, point1.z - point0.z);
        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) {
            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;
            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;
            }
            i += 1;
        }
        isValid = valid;
        if (!isValid){
            int a = 12;
        }
        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(int[][] img, int[][] 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
//        // reverse projecting and interpolating in the triangle - this gives camera coordinates.
//        // next, we need to use the inverse camera matrix to convert from camera coordinates to world coordinates.
//        // then, we can use the perspective mapping matrix unique and baked to each triangle on each face to convert from those world coordinates, into uv coordinates.
//
//
//
//        real01Vec.createFrom2Points(points[0].getRotatedPoint(), points[1].getRotatedPoint());
//        real02Vec.createFrom2Points(points[0].getRotatedPoint(), points[2].getRotatedPoint());
//        // scaVec.createFrom2Points(points[0].getProjectedPoint(), points[1].getProjectedPoint());
//        UV01Vec.createFrom2Points(UVPoints[0], UVPoints[1]);
//        UV02Vec.createFrom2Points(UVPoints[0], UVPoints[2]);
//        // it must remain as the same object so pointers elsewhere still work.
//        // invert x and y coordinates because in rotated coordinates, they are the wrong way round.
//        perspectiveMappingMatrix.setItems(new double[][]{
//                {0,1,0,0},
//                {1,0,0,0},
//                {0,0,1,0},
//                {0,0,0,1},
//        });
//
//        traVec.createFrom2Points(new Point3D(UVPoints[0].y, UVPoints[0].x, 0), points[0].getRotatedPoint());
//        Matrix tMat = new Matrix(4, 4);
////        tMat.setItems(new double[][]{
////                {1, 0, 0, traVec.x},
////                {0, 1, 0, traVec.y},
////                {0, 0, 1, traVec.z},
////                {0, 0, 0, 1},
////        });
//        tMat.setItems(new double[][]{
//                {1, 0, 0, 0},
//                {0, 1, 0, 0},
//                {0, 0, 1, points[0].getRotatedPoint().z},
//                {0, 0, 0, 1},
//        });
//        double scale = 0.1;//(real01Vec.getLength() / UV01Vec.getLength());
//        Matrix scaMat = new Matrix(4, 4);
//        scaMat.setItems(new double[][]{
//                {scale, 0,     0,     0},
//                {0,     scale, 0,     0},
//                {0,     0,     scale, 0},
//                {0,     0,     0,     1}
//        });
//        // find z rotation and define matrix
//        double zAng = new Vector2D(real01Vec.x, real01Vec.y).angleTo(UV01Vec);
//        Matrix zMat = new Matrix(4, 4);
//        zMat.setItems(new double[][]{
//                {Math.cos(zAng),  Math.sin(zAng), 0, 0},
//                {-Math.sin(zAng), Math.cos(zAng), 0, 0},
//                {0, 0, 1, 0},
//                {0, 0, 0, 1}}
//        );
//        // rotate "real" vectors using the Z matrix
//        result = zMat.multiplyPoint3raw(real01Vec.x, real01Vec.y, real01Vec.z);
//        real01Vec.x = result[0];real01Vec.y = result[1];real01Vec.z = result[2];
//        result = zMat.multiplyPoint3raw(real02Vec.x, real02Vec.y, real02Vec.z);
//        real02Vec.x = result[0];real02Vec.y = result[1];real02Vec.z = result[2];
//        zMat.setItems(new double[][]{
//                {Math.cos(-zAng),  Math.sin(-zAng), 0, 0},
//                {-Math.sin(-zAng), Math.cos(-zAng), 0, 0},
//                {0, 0, 1, 0},
//                {0, 0, 0, 1}}
//        );
//        // find Y rotation and define matrix
//        double yAng = new Vector2D(real01Vec.x, real01Vec.z).angleTo(new Vector2D(UV01Vec.x, 0));
//        Matrix yMat = new Matrix(4, 4);
//        yMat.setItems(new double[][]{
//                {Math.cos(yAng), 0, -Math.sin(yAng), 0},
//                {0,              1, 0, 0},
//                {Math.sin(yAng), 0, Math.cos(yAng), 0},
//                {0, 0, 0, 1}}
//        );
//        result = yMat.multiplyPoint3raw(real02Vec.x, real02Vec.y, real02Vec.z);
//        real02Vec.x = result[0];real02Vec.y = result[1];real02Vec.z = result[2];
//        yMat.setItems(new double[][]{
//                {Math.cos(-yAng), 0, -Math.sin(-yAng), 0},
//                {0,              1, 0, 0},
//                {Math.sin(-yAng), 0, Math.cos(-yAng), 0},
//                {0, 0, 0, 1}}
//        );
//        double xAng = new Vector2D(real02Vec.y, real02Vec.z).angleTo(new Vector2D(UV01Vec.x, 0)); // this is fine
//        Matrix xMat = new Matrix(4, 4);
//        xMat.setItems(new double[][]{
//                {1, 0, 0, 0},
//                {0, Math.cos(-xAng), Math.sin(-xAng), 0},
//                {0, -Math.sin(-xAng), Math.cos(-xAng), 0},
//                {0, 0, 0, 1}}
//        );
//        // Matrix that returns the position on a texture from 3d camera space on the face of an object
//        // we already know the Z depth because of calculations for Z buffers.
//        //perspectiveMappingMatrix.multiply(scaMat);
//        perspectiveMappingMatrix.multiply(scaMat.multiplyGetResult(tMat));
//        //perspectiveMappingMatrix.multiply((scaMat.multiplyGetResult(xMat.multiplyGetResult(yMat.multiplyGetResult(zMat.multiplyGetResult(tMat))))));
//        // perspectiveMappingMatrix.multiply(scaMat.multiplyGetResult(zMat.multiplyGetResult(yMat.multiplyGetResult(xMat.multiplyGetResult(tMat)))));
//    }
}