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 void draw(drawData drawData, double ang, double random){ // //long lastNanos = System.nanoTime(); if (!is_initialised){ if(initialise(drawData)){ int[] point1; int[] point2; char currentLine = 'A'; point1 = LineLong.nextPix(); point2 = LineA.nextPix(); //drawData.trisTimeList[0] += System.nanoTime() - lastNanos; for(int x = min.x+1; x <= max.x; x += 1) { //long lastNanos2 = System.nanoTime(); 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.setElem(point1[0] + point1[1]*drawData.scrX, 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.setElem(point2[0] + point2[1]*drawData.scrX, 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.setElem(point2[0] + point2[1]*drawData.scrX, Color.HSBtoRGB(0, 1, 1)); } point2 = LineB.nextPix(); } } // drawData.trisTimeList[1] += System.nanoTime() - lastNanos2; // lastNanos2 = System.nanoTime(); // 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, random); } } 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, random); } } } //drawData.trisTimeList[2] += System.nanoTime() - lastNanos2; } // lastNanos = (System.nanoTime() - lastNanos); // drawData.timeSpentDrawingTris += lastNanos /1000000d; } } } 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 && !drawData.texturesDisabled) { // 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, double random){ //long nanotime = System.nanoTime(); // 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/startPoint.z + (x - startPoint.x)* xzGradient + (y - startPoint.y)* yzGradient)); //int newZ = (int)(2147483647d/(zVal + 1)); int newZ = (int)(zVal * 16384); // 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] || drawData.zBufferDisabled){ drawData.zBuf[x][y] = newZ; // project result int pixColor; if(isTextured && !drawData.texturesDisabled){ double[] pos = textureMappingMatrix.multiplyPoint2raw(x, y); //pixColor = texture.getColor(ang, 0, 0); pixColor = texture.getColor(ang, (int)(pos[0]*texture.image.getWidth()), texture.image.getHeight()-(int)(pos[1]*texture.image.getHeight())); // Math.floorMod((int)(pos[0]*texture.image.getWidth()), texture.image.getWidth()), // Math.floorMod(-1-(int)(pos[1]*texture.image.getHeight()), texture.image.getHeight()) //); //drawData.timeSpentGettingPixels += System.nanoTime()-nanoTime; } else { pixColor = texture.getColor(ang, random); } if(drawData.drawZBuffer){ drawData.drawImg.setElem(x + y*drawData.scrX, Color.getHSBColor(0, 0, (float)newZ/16384f + 0.5f).getRGB()); //drawData.drawImg[x][y] = Color.getHSBColor(0, 0, (float)newZ/2147483648f + 0.5f).getRGB(); } else{ drawData.drawImg.setElem(x + y*drawData.scrX, pixColor); //drawData.drawImg[x][y] = pixColor; } } //terdrawData.trisTimeList[3] += System.nanoTime() - nanotime; } }