import java.awt.Color;

public class PlaneEqnTriDrawer extends TriangleDrawer {

  public static int A_MASK = 0xFF000000;
  public static int R_MASK = 0x00FF0000;
  public static int G_MASK = 0x0000FF00;
  public static int B_MASK = 0x000000FF;

  // The alphaVec variable can be enabled to support Alpha blending,
  // but due to overflow problems the quality of the drawing degrades.
  // Therefore it is commented out.  

  // public double[] alphaVec;

  public double[] redVec;
  public double[] greenVec;
  public double[] blueVec;
  

  public PlaneEqnTriDrawer(Vertex2D v0, Vertex2D v1, Vertex2D v2) {
    super(v0, v1, v2);

    // alphaVec = new double[3];
    redVec = new double[3];
    greenVec = new double[3];
    blueVec = new double[3];

    double[][] matrix = new double[3][3];
    matrix[0][0] = v1.y - v2.y; 
    matrix[0][1] = v2.y - v0.y; 
    matrix[0][2] = v0.y - v1.y;
    matrix[1][0] = v2.x - v1.x; 
    matrix[1][1] = v0.x - v2.x; 
    matrix[1][2] = v1.x - v0.x;
    matrix[2][0] =  (v1.x*v2.y - v2.x*v1.y); 
    matrix[2][1] =  (v2.x*v0.y - v0.x*v2.y); 
    matrix[2][2] =  (v0.x*v1.y - v1.x*v0.y);
    
    double scale = 
      1. / (v0.x*v1.y + v1.x*v2.y + v2.x*v0.y -
	   v0.x*v2.y - v1.x*v0.y - v2.x*v1.y);

    /*
      alphaVec[0] = scale * (matrix[0][0]*(v0.argb & A_MASK) +
      matrix[0][1]*(v1.argb & A_MASK) +
      matrix[0][2]*(v2.argb & A_MASK));
      alphaVec[1] = scale * (matrix[1][0]*(v0.argb & A_MASK) +
      matrix[1][1]*(v1.argb & A_MASK) +
      matrix[1][2]*(v2.argb & A_MASK));
      alphaVec[2] = scale * (matrix[2][0]*(v0.argb & A_MASK) +
      matrix[2][1]*(v1.argb & A_MASK) +
      matrix[2][2]*(v2.argb & A_MASK));
    */
    redVec[0] = scale * (matrix[0][0]*(v0.argb & R_MASK) +
			 matrix[0][1]*(v1.argb & R_MASK) +
			 matrix[0][2]*(v2.argb & R_MASK));
    redVec[1] = scale * (matrix[1][0]*(v0.argb & R_MASK) +
			 matrix[1][1]*(v1.argb & R_MASK) +
			 matrix[1][2]*(v2.argb & R_MASK));
    redVec[2] = scale * (matrix[2][0]*(v0.argb & R_MASK) +
			 matrix[2][1]*(v1.argb & R_MASK) +
			 matrix[2][2]*(v2.argb & R_MASK));
    greenVec[0] = scale * (matrix[0][0]*(v0.argb & G_MASK) +
			   matrix[0][1]*(v1.argb & G_MASK) +
			   matrix[0][2]*(v2.argb & G_MASK));
    greenVec[1] = scale * (matrix[1][0]*(v0.argb & G_MASK) +
			   matrix[1][1]*(v1.argb & G_MASK) +
			   matrix[1][2]*(v2.argb & G_MASK));
    greenVec[2] = scale * (matrix[2][0]*(v0.argb & G_MASK) +
			   matrix[2][1]*(v1.argb & G_MASK) +
			   matrix[2][2]*(v2.argb & G_MASK));
    blueVec[0] = scale * (matrix[0][0]*(v0.argb & B_MASK) +
			  matrix[0][1]*(v1.argb & B_MASK) +
			  matrix[0][2]*(v2.argb & B_MASK));
    blueVec[1] = scale * (matrix[1][0]*(v0.argb & B_MASK) +
			  matrix[1][1]*(v1.argb & B_MASK) +
			  matrix[1][2]*(v2.argb & B_MASK));
    blueVec[2] = scale * (matrix[2][0]*(v0.argb & B_MASK) +
			  matrix[2][1]*(v1.argb & B_MASK) +
			  matrix[2][2]*(v2.argb & B_MASK));
  }
  
  public void draw(Raster r) {
    Vertex2D topV, midV, botV;
    
    // sort vertices by height
    if (v0.y > v1.y && v0.y > v2.y) {
      botV = v0;
      if (v1.y > v2.y) {
	midV = v1;  topV = v2;
      } else {
	midV = v2;  topV = v1;
      }
    } else if (v1.y > v2.y) {
      botV = v1;
      if (v0.y > v2.y) {
	midV = v0;  topV = v2;
      } else {
	midV = v2;  topV = v0;
      }
    } else {
      botV = v2;
      if (v0.y > v1.y) {
	midV = v0;  topV = v1;
      } else {
	midV = v1;  topV = v0;
      }
    }
      
    double xMin, xMax;
    int rgb = 0;

    double invSlopeLeft, invSlopeRight;
    boolean midPointOnLeft;
    
    // find inverse slope for incremental edge walking
    if (getPoint(topV, botV, midV.y) > midV.x) {
      invSlopeLeft = getInverseSlope(topV, midV);
      invSlopeRight = getInverseSlope(topV, botV);
      midPointOnLeft = true;
    } else {
      invSlopeLeft = getInverseSlope(topV, botV);
      invSlopeRight = getInverseSlope(topV, midV);
      midPointOnLeft = false;
    }

    // draw top half
    xMin = xMax = topV.x;        
    
    for (double y = Math.ceil(topV.y); y < Math.ceil(midV.y); y++ ) {
      for (double x = Math.ceil(xMin); x <= Math.ceil(xMax); x++ ) {
	rgb = 
	  A_MASK + // ((int)(alphaVec[0]*x + alphaVec[1]*y + alphaVec[2]) & A_MASK ) +
	  ((int)(redVec[0]*x + redVec[1]*y + redVec[2] +0x00008000) & R_MASK ) +
	  ((int)(greenVec[0]*x + greenVec[1]*y + greenVec[2] + 0x00000080) & G_MASK) +
	  ((int)(blueVec[0]*x + blueVec[1]*y + blueVec[2] +0.5) & B_MASK);
	r.setPixel( rgb, (int) x, (int) y);	
      }      
      xMin += invSlopeLeft;
      xMax += invSlopeRight;
    }

    // draw bottom half
    if (midPointOnLeft) {
      xMin = midV.x;
      // xMax = getPoint(topV, botV, midV.y);
      invSlopeLeft = getInverseSlope(midV, botV);
    } else {
      // xMin = getPoint(topV, botV, midV.y);
      xMax = midV.x;
      invSlopeRight = getInverseSlope(midV, botV);
    }    
    for (double y = Math.ceil(midV.y); y < Math.ceil(botV.y); y++ ) {
      for (double x =  Math.ceil(xMin); x <= Math.ceil(xMax); x++ ) {
	rgb = 
	  A_MASK + //((int)(alphaVec[0]*x + alphaVec[1]*y + alphaVec[2]) & A_MASK ) +
	  ((int)(redVec[0]*x + redVec[1]*y + redVec[2] +0x00008000) & R_MASK ) +
	  ((int)(greenVec[0]*x + greenVec[1]*y + greenVec[2] +0x00000080) & G_MASK) +
	  ((int)(blueVec[0]*x + blueVec[1]*y + blueVec[2] + 0.5) & B_MASK);
	r.setPixel( rgb, (int) x,  (int) y);
      }
      xMin += invSlopeLeft;
      xMax += invSlopeRight;
    }

  }
  
  private double getInverseSlope(Vertex2D p0, Vertex2D p1) {
    return (p1.x - p0.x)/(p1.y - p0.y);
  }
  
  private double getPoint(Vertex2D p0, Vertex2D p1, double y) {
    double x;
    if (p1.x == p0.x) {
      x = p0.x;
    } else if (p1.y == p0.y) {
      x = p1.x;
    } else {
      double m = (p1.y - p0.y) / (p1.x - p0.x);
      double b = p1.y - m*p1.x;
      x =  ((y - b) / m);
    }
    return x;
  }

}

