Newer
Older
EPQ-3D-renderer / src / main / java / uk / org / floop / epq3d / Triangle.java
@cory cory on 25 Feb 2023 16 KB Messing with tornado
package uk.org.floop.epq3d;

import uk.ac.manchester.tornado.api.annotations.Parallel;

import java.awt.*;

public class Triangle{
    public PointComp point1;
    public PointComp point2;
    public PointComp point3;
    public double[] UVPoint1;
    public double[] UVPoint2;
    public double[] UVPoint3;

    public Matrix textureMappingMatrix;
    public Matrix uvTo00;
    public Matrix uvTo00_inv;


    public boolean[] edgeList; //edge 1-2 , 2-3, 3-1

    private Line2d LineLong;
    private Line2d LineA;
    private Line2d LineB;

    public Texture texture;
    public boolean isTextured;

    // initialisation variables
    private boolean is_initialised = false;
    private Point2D min;
    private Point2D max;
    private Point2D startPoint;
    private double xzGradient;
    private double yzGradient;


    private final Point2D result2 = new Point2D(0,0);
    public void invalidate() {
        is_initialised = false;
    }
    public Triangle(PointComp _pA, PointComp _pB, PointComp _pC,  boolean[] _edgeList, Texture _texture){
        isTextured = false;
        point1 = _pA;
        point2 = _pB;
        point3 = _pC;
        edgeList = _edgeList;
        texture = _texture;
    }
    public Triangle(PointComp _pA, PointComp _pB, PointComp _pC, double[] _uvA, double[] _uvB, double[] _uvC, boolean[] _edgeList, Texture _texture){
        isTextured = !_texture.isSolid();
        point1 = _pA;
        point2 = _pB;
        point3 = _pC;
        UVPoint1 = _uvA;
        UVPoint2 = _uvB;
        UVPoint3 = _uvC;
        edgeList = _edgeList;
        texture = _texture;
        // temporary object for multiplying
        Matrix MultMat = new Matrix(3, 3);
        // 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);
        // calculate uvTo00 matrix
        // uvTo00 takes the uv's point 1 to 0,0 with its point 2 at 0,1 (rotated, translated and scaled)
        uvTo00 = new Matrix(3, 3);
        uvTo00.setItems(new double[][]{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}});
        {
            vOrig.x = (UVPoint2[0] - UVPoint1[0]);
            vOrig.y = (UVPoint2[1] - UVPoint1[1]);
            double rotAng = -vOrig.angleTo(vFin);
            // set to rotation
            MultMat.setItems(new double[][]{
                    {Math.cos(rotAng), Math.sin(rotAng), 0},
                    {-Math.sin(rotAng), Math.cos(rotAng), 0},
                    {0, 0, 1},
            });
            uvTo00.multiply(MultMat);

            double scaleFac = 1 / vOrig.getLength();
            // set to scaling
            MultMat.setItems(new double[][]{
                    {scaleFac, 0, 0},
                    {0, scaleFac, 0},
                    {0, 0, 1},
            });
            uvTo00.multiply(MultMat);
            // set to translation
            MultMat.setItems(new double[][]{
                    {1, 0, -UVPoint1[0]},
                    {0, 1, -UVPoint1[1]},
                    {0, 0, 1},
            });
            uvTo00.multiply(MultMat);
            uvTo00_inv = uvTo00.getInverse();
        }
    }
    //  returns int for debug
    public int draw(drawData drawData, double ang){
        //
        long lastMillis;
        if (!is_initialised){
            if(initialise(drawData)){
                int[] point1;
                int[] point2;
                char currentLine = 'A';

                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 || drawData.drawLines) && // 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 || drawData.drawLines) &&                // 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';
                            }
                        }
                        else {
                            if((LineB.isDrawn || drawData.drawLines) &&                // 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) {
                        if (point1[1] < point2[1]) {
                            double z1 = LineLong.getZVal(x); double z2;
                            if(currentLine=='A'){
                                z2=LineA.getZVal(x);
                            } else{
                                z2=LineB.getZVal(x);
                            }
                            double gradient = -(z2 - z1) / (point2[1]-point1[1]);
                            for (@Parallel 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, x, y, ang);
                            }
                        } else {
                            double z2 = LineLong.getZVal(x); double z1;
                            if(currentLine=='A'){
                                z1=LineA.getZVal(x);
                            } else{
                                z1=LineB.getZVal(x);
                            }
                            double gradient = -(z2 - z1) / (point1[1]-point2[1]);
                            for (@Parallel int y = Math.max(point2[1], 0); y <= Math.min(point1[1], drawData.scrY - 1); y += 1) {
                                drawPix(drawData, x, y, ang);
                            }
                        }
                    }
                }
                lastMillis = (System.currentTimeMillis() - lastMillis);
                return (int)lastMillis;
            }
        }
        return 0;
    }
    public boolean initialise(drawData drawData){
        if (point1 == null || point2 == null || point3 == null){
            throw new NullPointerException();
        }
        min = new Point2D(
                Math.min(point1.getProjectedPoint().x,
                        Math.min(point2.getProjectedPoint().x, point3.getProjectedPoint().x)),
                Math.min(point1.getProjectedPoint().y,
                        Math.min(point2.getProjectedPoint().y, point3.getProjectedPoint().y)));
        max = new Point2D(
                Math.max(point1.getProjectedPoint().x,
                        Math.max(point2.getProjectedPoint().x, point3.getProjectedPoint().x)),
                Math.max(point1.getProjectedPoint().y,
                        Math.max(point2.getProjectedPoint().y, point3.getProjectedPoint().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
        if (point1.getProjectedPoint().x == min.x) {
            if (point2.getProjectedPoint().x == max.x){
                LineLong = new Line2d(point1, point2, UVPoint1, UVPoint2, edgeList[0]);
                LineA = new Line2d(point1, point3, UVPoint1, UVPoint3, edgeList[2]);
                LineB = new Line2d(point3, point2, UVPoint3, UVPoint2, edgeList[1]);
            } else {
                LineLong = new Line2d(point1, point3, UVPoint1, UVPoint3, edgeList[2]);
                LineA = new Line2d(point1, point2, UVPoint1, UVPoint2, edgeList[0]);
                LineB = new Line2d(point2, point3, UVPoint2, UVPoint3, edgeList[1]);
            }
        }
        else if (point2.getProjectedPoint().x == min.x) {
            if (point1.getProjectedPoint().x == max.x) {
                LineLong = new Line2d(point2, point1, UVPoint2, UVPoint1, edgeList[0]);
                LineA = new Line2d(point2, point3, UVPoint2, UVPoint3, edgeList[1]);
                LineB = new Line2d(point3, point1, UVPoint3, UVPoint1, edgeList[2]);
            } else {
                LineLong = new Line2d(point2, point3, UVPoint2, UVPoint3, edgeList[1]);
                LineA = new Line2d(point2, point1, UVPoint2, UVPoint1, edgeList[0]);
                LineB = new Line2d(point1, point3, UVPoint1, UVPoint3, edgeList[2]);
            }
        }
        else if (point3.getProjectedPoint().x == min.x){
            if (point1.getProjectedPoint().x == max.x) {
                LineLong = new Line2d(point3, point1, UVPoint3, UVPoint1, edgeList[2]);
                LineA = new Line2d(point3, point2, UVPoint3, UVPoint2, edgeList[1]);
                LineB = new Line2d(point2, point1, UVPoint2, UVPoint1, edgeList[0]);
            } else {
                LineLong = new Line2d(point3, point2, UVPoint3, UVPoint2, edgeList[2]);
                LineA = new Line2d(point3, point1, UVPoint3, UVPoint1,edgeList[1]);
                LineB = new Line2d(point1, point2, UVPoint1, UVPoint2, edgeList[0]);
            }
        }
        // find z calculation constants
        // they are always relative to lineLong point1. This is an arbitrary decision but it must be consistent
        startPoint = LineLong.point1.getProjectedPoint();

        // find two vectors in screen coordinates from the point
        Vector3D vec1 = new Vector3D(
                LineLong.point2.getProjectedPoint().x - startPoint.x,
                LineLong.point2.getProjectedPoint().y - startPoint.y,
                1/LineLong.point2.getRotatedPoint().z - 1/startPoint.z
                );
        // in theory, projected point and rotated point Z values are the same. However, at some point i'd like to remove z values from 2d points
        Vector3D vec2 = new Vector3D(
                LineA.point2.getProjectedPoint().x - startPoint.x,
                LineA.point2.getProjectedPoint().y - startPoint.y,
                1/LineA.point2.getRotatedPoint().z - 1/startPoint.z
        );
        // calculate the cross product of these two vectors in order to obtain a normal vector
        Vector3D cross = vec1.cross(vec2);
        // find xzGradient and yzGradient for the triangle, in terms of z. We take the negative reciprocal of these gradients, because of the normal vector.
        xzGradient = -cross.x / cross.z;
        yzGradient = -cross.y / cross.z;
        if(!Double.isFinite(xzGradient)){
            xzGradient = 0;
        }
        if(!Double.isFinite(yzGradient)) {
            yzGradient = 0;
        }
        // if the triangle is textured, calculate a matrix to apply the texture
        if(isTextured) {
            // temporary object for multiplying
            Matrix MultMat = new Matrix(3, 3);
            // 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);
            // calculate triTo00 matrix
            // triTo00 takes the triangle's point 1 to 0,0 with its point 2 at 0,1 (rotated, translated and scaled)
            Matrix faceTo00 = new Matrix(3, 3);
            faceTo00.setItems(new double[][]{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}});
            {
                vOrig.createFrom2Points(point1.getProjectedPoint(), point2.getProjectedPoint());
                double rotAng = -vOrig.angleTo(vFin);
                // set to rotation
                MultMat.setItems(new double[][]{
                        {Math.cos(rotAng), Math.sin(rotAng), 0},
                        {-Math.sin(rotAng), Math.cos(rotAng), 0},
                        {0, 0, 1},
                });
                faceTo00.multiply(MultMat);

                double scaleFac = 1 / vOrig.getLength();
                // set to scaling
                MultMat.setItems(new double[][]{
                        {scaleFac, 0, 0},
                        {0, scaleFac, 0},
                        {0, 0, 1},
                });
                faceTo00.multiply(MultMat);
                // set to translation
                MultMat.setItems(new double[][]{
                        {1, 0, -point1.getProjectedPoint().x},
                        {0, 1, -point1.getProjectedPoint().y},
                        {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
                    double[] pointFace = faceTo00.multiplyPoint2raw(
                            point3.getProjectedPoint().x,
                            point3.getProjectedPoint().y
                    );
                    double[] pointUV = (uvTo00.multiplyPoint2raw(UVPoint3[0], UVPoint3[1]));

                    double xScale = pointUV[0] / pointFace[0];
                    MultMat.setItems(new double[][]{
                            {xScale, 0, 0},
                            {0, 1, 0},
                            {0, 0, 1},
                    });
                    faceTo00 = MultMat.multiplyGetResult(faceTo00);

                    double yShearFac = (pointUV[1] - pointFace[1]) / pointUV[0];
                    if (!Double.isFinite(yShearFac)) {
                        yShearFac = 0;
                    }
                    MultMat.setItems(new double[][]{
                            {1, 0, 0},
                            {yShearFac, 1, 0},
                            {0, 0, 1},
                    });
                    faceTo00 = MultMat.multiplyGetResult(faceTo00);
                    // multiply final matrices and set their values in the class
                    textureMappingMatrix = uvTo00_inv.multiplyGetResult(faceTo00);
                }
            }
        }
        is_initialised = true;
        return true;
    }
    private void drawPix(drawData drawData, int x, int y, double ang){
        // a value of 0 represents a value which is on the camera, at z=0.
        // not the best mapping but it's probably good enough
        double zVal = 1/((1/startPoint.z +
                (x - startPoint.x)* xzGradient +
                (y - startPoint.y)* yzGradient));
        int newZ = (int)(2147483647d/(zVal + 1));
        // if the new Z value is greater than the existing Z value on the buffer, the new pixel is calculated and drawn
        if(drawData.zBuf[x][y] == 0 ||
        newZ > drawData.zBuf[x][y]){ //newZ > zBuf.getRGB(x, y) ||
            drawData.zBuf[x][y] = newZ;
            // project result
            Color pixColor;
            if(isTextured){
                double[] pos = textureMappingMatrix.multiplyPoint2raw(x, y);
                pixColor = texture.getColor(ang,
                        Math.floorMod((int)(pos[0]*texture.image.getWidth()), texture.image.getWidth()),
                        Math.floorMod(-1-(int)(pos[1]*texture.image.getHeight()), texture.image.getHeight())
                );
            } else {
                pixColor = texture.getColor(ang);
            }
            if(drawData.drawZBuffer){
                drawData.drawImg[x][y] = Color.getHSBColor((float)newZ/2147483648f + 0.5f, 1, 1).getRGB();
            } else{
                drawData.drawImg[x][y] = pixColor.getRGB();
            }
        }
    }
}