import java.awt.Color;
import Vertex3D;
import Matrix3D;
import Surface;
import ZRaster;

class EdgeEqn {
    public float A, B, C;
    public int flag;    

    public EdgeEqn(Vertex3D v0, Vertex3D v1)
    {
        A = v0.y - v1.y;
        B = v1.x - v0.x;
        C = -0.5f * (A*(v0.x + v1.x) + B*(v0.y + v1.y));

        flag = 0;
        if (A >= 0) flag += 8;
        if (B >= 0) flag += 1;
    }

    public void flip()
    {
        A = -A;
        B = -B;
        C = -C;
    }

    public float evaluate(int x, int y)
    {
        return (A*x + B*y + C);
    }
}

public class Triangle {
    private static Vertex3D vlist[];
    protected int v[];
    protected float r[], g[], b[], n[];
    protected Surface surface;
    private boolean culled;
    private Vertex3D vert[];
    private static final int clipfar = 20;
    private static final int clipnear = -20;
    private boolean PixClip;
    public  static Vertex3D ClipPoints[];
	

    public Triangle()
    {
    }

    public Triangle(int v0, int v1, int v2)
    {
        v = new int[3];
        v[0] = v0;
        v[1] = v1;
        v[2] = v2; 
	n = new float[3];
        r = new float[3];
        g = new float[3];
        b = new float[3];
        vert = new Vertex3D[3];
	ClipPoints = new Vertex3D[2];
	ClipPoints[0] = new Vertex3D(0,0,clipnear );
	ClipPoints[1] = new Vertex3D(0,0,clipfar);
        vert[0] = new Vertex3D();
        vert[1] = new Vertex3D();
        vert[2] = new Vertex3D();
        scale = -1;
        culled = false;
    }
    
    public void setSurface(Surface s)
    {
        surface = s;
    }

    public Surface getSurface()
    {
        return surface;
    }

    public boolean isVisible()
    {
        return (!culled);
    }
    
    public void CalculateNormal(){
	vlist[v[0]].normalize();
	vlist[v[1]].normalize();
	vlist[v[2]].normalize();
	vert[0] = vlist[v[0]];
	vert[1] = vlist[v[1]];
	vert[2] = vlist[v[2]];
	/*System.out.println(vert[0].toString());
	System.out.println(vert[1].toString());
	System.out.println(vert[2].toString());*/
	float A=vert[0].y*(vert[1].z-vert[2].z)+
	    vert[1].y*(vert[2].z-vert[0].z)+vert[2].y*(vert[0].z-vert[1].z);
	float B=vert[0].z*(vert[1].x-vert[2].x)+
	    vert[1].z*(vert[2].x-vert[0].x)+vert[2].z*(vert[0].x-vert[1].x);
	float C=vert[0].x*(vert[1].y-vert[2].y)+
	    vert[1].x*(vert[2].y-vert[0].y)+vert[2].x*(vert[0].y-vert[1].y);
	float denom = (float) Math.sqrt(A*A+B*B+C*C);
	n[0] = (float) A/denom;
	n[1] = (float) B/denom;
	n[2] = (float) C/denom;
	vert[0].addNormal(A,B,C);
	vert[1].addNormal(A,B,C);
	vert[2].addNormal(A,B,C);
    }
    /*
      ... you'll need to modify the following two methods ...
    */
    public void Illuminate(int shadeType, Light l[], int lights, Point3D eye, boolean cull)
    {
	float Ir, Ig, Ib;
	Ir = Ig = Ib = 0;
	float dot = 0;
	dot = (n[0]*eye.x+n[1]*eye.y+n[2]*eye.z);
	culled = (dot < 0)? true:false;
	if (!culled){
	    if (shadeType == 1){ 
	    for (int i = 0; i < lights; i++){
		if (l[i].lightType == l[i].AMBIENT){
		    Ir+= surface.ka*l[i].ir;
		    Ig+= surface.ka*l[i].ig;
		    Ib+= surface.ka*l[i].ib;
		}else if (l[i].lightType==l[i].DIRECTIONAL){
		    dot = (n[0]*l[i].x+n[1]*l[i].y+n[2]*l[i].z);
		    dot = (dot > 0)? dot:-dot; 
		    Ir+=surface.kd*l[i].ir*dot;
		    Ig+=surface.kd*l[i].ig*dot;
		    Ib+=surface.kd*l[i].ib*dot;    
		}
	    }
	    for (int i = 0; i < 3; i++) {
		r[i] = surface.r*Ir;
		g[i] = surface.g*Ig;
		b[i] = surface.b*Ib;
	    }
	    } else {
		for (int j=0; j<3;j++){ 
		  Ir = Ig = Ib = 0;
		  for (int i = 0; i < lights; i++){
		       dot =(float) (vert[j].nx*l[i].x+vert[j].ny*l[i].y+vert[j].nz*l[i].z);
		      if (l[i].lightType == l[i].AMBIENT){
			  Ir+= surface.ka*l[i].ir;
			  Ig+= surface.ka*l[i].ig;
			  Ib+= surface.ka*l[i].ib;
		      }else if (l[i].lightType==l[i].DIRECTIONAL){
			  dot = (dot > 0)? dot:-dot;  
			  Ir+=surface.kd*l[i].ir*dot;
			  Ig+=surface.kd*l[i].ig*dot;
			  Ib+=surface.kd*l[i].ib*dot;
		      }
		  }
		  r[j] = surface.r*Ir;
		  g[j] = surface.g*Ig;
		  b[j] = surface.b*Ib;		  
		}
	    }
	}
    }
    
    public void ClipAndDraw(ZRaster raster, Matrix3D project)
    {
	vert[0] = vlist[v[0]];
	vert[1] = vlist[v[1]];
	vert[2] = vlist[v[2]];
	
	//trivial rejection
	if (!(((vert[0].z > clipfar)&&(vert[1].z > clipfar)&&(vert[2].z > clipfar))||
	      ((vert[0].z < clipnear)&&(vert[1].z < clipnear)&&(vert[2].z < clipnear))))
	    {
		vert[0] = project.transform(vlist[v[0]]);
		vert[1] = project.transform(vlist[v[1]]);
		vert[2] = project.transform(vlist[v[2]]);
		ClipPoints[0] = project.transform(ClipPoints[0]);
		ClipPoints[1] = project.transform(ClipPoints[1]);
		Draw(raster, false);
	    } else if (((vert[0].z > clipfar)||(vert[1].z > clipfar)||
			(vert[2].z > clipfar))||
		       ((vert[0].z < clipnear)||
			(vert[1].z < clipnear)||
			(vert[2].z < clipnear))){
		vert[0] = project.transform(vlist[v[0]]);
		vert[1] = project.transform(vlist[v[1]]);
		vert[2] = project.transform(vlist[v[2]]);
		ClipPoints[0] = project.transform(ClipPoints[0]);
		ClipPoints[1] = project.transform(ClipPoints[1]);
		Draw(raster, true);
		 }
    }

    public void setVertexList(Vertex3D list[])
    {
        vlist = list;
    }

    protected EdgeEqn edge[];
    protected float area;
    protected int xMin, xMax, yMin, yMax;
    protected float scale;
    private static byte sort[][] = {
        {0, 1}, {1, 2}, {0, 2}, {2, 0}, {2, 1}, {1, 0}
    };

    public void PlaneEqn(float eqn[], float p0, float p1, float p2)
    {
        float Ap, Bp, Cp;

        float sp0 = scale * p0;
        float sp1 = scale * p1;
        float sp2 = scale * p2;
        Ap = edge[0].A*sp2 + edge[1].A*sp0 + edge[2].A*sp1;
        Bp = edge[0].B*sp2 + edge[1].B*sp0 + edge[2].B*sp1;
        Cp = edge[0].C*sp2 + edge[1].C*sp0 + edge[2].C*sp1;
        eqn[0] = Ap;
        eqn[1] = Bp;
        eqn[2] = Ap*xMin + Bp*yMin + Cp;
    }

    protected boolean triangleSetup(Raster r)
    {
        if (edge == null) edge = new EdgeEqn[3];

        /*
	  Compute the three edge equations
        */
        edge[0] = new EdgeEqn(vert[0], vert[1]);
        edge[1] = new EdgeEqn(vert[1], vert[2]);
        edge[2] = new EdgeEqn(vert[2], vert[0]);

        /*
	  Trick #1: Orient edges so that the
	  triangle's interior lies within all
	  of their positive half-spaces.

	  Assuring that the area is positive
	  accomplishes this
        */
        area = edge[0].C + edge[1].C + edge[2].C;

        if (area == 0)
            return false;
            
        if (area < 0) {
            edge[0].flip();
            edge[1].flip();
            edge[2].flip();
            area = -area;
        }

        /*
	  Trick #2: compute bounding box
        */
        int xflag = edge[0].flag + 2*edge[1].flag + 4*edge[2].flag;
        int yflag = (xflag >> 3) - 1;
        xflag = (xflag & 7) - 1;

        xMin = (int) (vert[sort[xflag][0]].x);
        xMax = (int) (vert[sort[xflag][1]].x + 1);
        yMin = (int) (vert[sort[yflag][1]].y);
        yMax = (int) (vert[sort[yflag][0]].y + 1);

        /*
	  clip triangle's bounding box to raster
        */
        xMin = (xMin < 0) ? 0 : xMin;
        xMax = (xMax >= r.width) ? r.width - 1 : xMax;
        yMin = (yMin < 0) ? 0 : yMin;
        yMax = (yMax >= r.height) ? r.height - 1 : yMax;
        return true;
    }
    
    public void Draw(ZRaster raster, boolean clipped)
    {
	
	    float zPlane[] = new float[3];
	    float rPlane[] = new float[3];
	    float gPlane[] = new float[3];
	    float bPlane[] = new float[3];
	
	    if (!triangleSetup(raster)) return;
	    scale = 1 / area;
	    PlaneEqn(zPlane, vert[0].z, vert[1].z, vert[2].z);
	    PlaneEqn(rPlane, r[0], r[1], r[2]);
	    PlaneEqn(gPlane, g[0], g[1], g[2]);
	    PlaneEqn(bPlane, b[0], b[1], b[2]);

	    int x, y;
	    float A0 = edge[0].A;        float B0 = edge[0].B;        float t0 = A0*xMin + B0*yMin + edge[0].C;
	    float A1 = edge[1].A;        float B1 = edge[1].B;        float t1 = A1*xMin + B1*yMin + edge[1].C;
	    float A2 = edge[2].A;        float B2 = edge[2].B;        float t2 = A2*xMin + B2*yMin + edge[2].C;
	    float Az = zPlane[0];        float Bz = zPlane[1];        float tz = zPlane[2];
	    float Ar = rPlane[0];        float Br = rPlane[1];        float tr = rPlane[2];
	    float Ag = gPlane[0];        float Bg = gPlane[1];        float tg = gPlane[2];
	    float Ab = bPlane[0];        float Bb = bPlane[1];        float tb = bPlane[2];

	    yMin *= raster.width;
	    yMax *= raster.width;

	    /*
	      .... scan convert triangle ....
	    */
	    for (y = yMin; y <= yMax; y += raster.width) {
	        float e0 = t0;
	        float e1 = t1;
	        float e2 = t2;
	        float r = tr;
	        float g = tg;
	        float b = tb;
	        float z = tz;
	        boolean beenInside = false;
	        for (x = xMin; x <= xMax; x++) {
	            if ((e0 >= 0) && (e1 >= 0) && (e2 >= 0)) {       // all 3 edges must be >= 0
	                int iz = (int) z;
			if (!clipped){
			    if (iz <= raster.zbuff[y+x]) {
				int pixr = (int) (255.0f*r);
				int pixg = (int) (255.0f*g);
				int pixb = (int) (255.0f*b);
				pixr = ((pixr & ~255) == 0) ? pixr << 16 : ((r < 0)	? 0 : 255<<16);
				pixg = ((pixg & ~255) == 0) ? pixg << 8  : ((g < 0)	? 0 : 255<<8);
				pixb = ((pixb & ~255) == 0) ? pixb       : ((b < 0)	? 0 : 255);
				raster.pixel[y+x] = (0xff000000 | pixr | pixg | pixb);
				raster.zbuff[y+x] = iz;
			    }
			}else {
			    if ((iz <= raster.zbuff[y+x])&&((iz < ClipPoints[1].z)&&(iz> ClipPoints[0].z))){
				int pixr = (int) (255.0f*r);
				int pixg = (int) (255.0f*g);
				int pixb = (int) (255.0f*b);
				pixr = ((pixr & ~255) == 0) ? pixr << 16 : ((r < 0)	? 0 : 255<<16);
				pixg = ((pixg & ~255) == 0) ? pixg << 8  : ((g < 0)	? 0 : 255<<8);
				pixb = ((pixb & ~255) == 0) ? pixb       : ((b < 0)	? 0 : 255);
				raster.pixel[y+x] = (0xff000000 | pixr | pixg | pixb);
				raster.zbuff[y+x] = iz;
				
			    }
			}
			beenInside = true;
	            } else if (beenInside) break;
	            e0 += A0;   e1 += A1;   e2 += A2;
	            z += Az;    r += Ar;    g += Ag;    b += Ab;
	        }
	        t0 += B0;   t1 += B1;   t2 += B2;
	        tz += Bz;   tr += Br;   tg += Bg;   tb += Bb;
	    }
    }
}
