import Vertex3D;
import java.util.Vector;


class Triangle3D {

	public static Vertex3D[] v;
	public int i0, i1, i2;
	public Surface surf;
	public boolean isVisible;
	public int argb0, argb1, argb2;


    public Triangle3D() {
    }



    public Triangle3D(int v0, int v1, int v2)
	{
		argb0 = 0;
		argb1 = 0;
		argb2 = 0;
		isVisible = false;
		i0 = v0;
		i1 = v1;
		i2 = v2;
    };

    

    public void Draw(ZbufferedRaster r)
	{
		Vertex3D v0 = new Vertex3D(v[i0], argb0);
		Vertex3D v1 = new Vertex3D(v[i0], argb1);
		Vertex3D v2 = new Vertex3D(v[i0], argb2);
    	myDrawInterpolated(r, v0, v1, v2);
    }

	

	public void setSurface(Surface s)
	{
		surf = new Surface(s);
	};


	public void setVertexList(Vertex3D[] vlist)
	{
		v = vlist;
	};



	public Vector3 normal()
	{
		Vector3 v1 = new Vector3(v[i0],v[i1]);
		Vector3 v2 = new Vector3(v[i0],v[i2]);
		Vector3 normal = v1.cross(v2);
		normal.normalize();
		return normal;
	};



	public void Illuminate(Light[] lightList, int lights, Point3D eye, boolean cull)
	{
		Vector3 v1 = new Vector3(v[i0],v[i1]);
		Vector3 v2 = new Vector3(v[i0],v[i2]);
		Vector3 normal = v1.cross(v2);
		normal.normalize();
		if (cull) {
			float A = normal.x;
			float B = normal.y;
			float C = normal.z;
			float D = -(A*v[i0].x + B*v[i0].y + C*v[i0].z);
			isVisible = (A*eye.x + B*eye.y + C*eye.z + D) > 0;
		}
		else
			isVisible = true;

		if (!isVisible) return;
		float Ir0=0; float Ir1=0; float Ir2=0;
		float Ig0=0; float Ig1=0; float Ig2=0;
		float Ib0=0; float Ib1=0; float Ib2=0;
		float NdotL, NdotL1, NdotL2;
		float V0dotRtoN, V1dotRtoN, V2dotRtoN;
		float temp0, temp1, temp2;
		float tempr, tempg, tempb;
		Vector3 R, R1, R2, V0, V1, V2;
		
		for (int i=0; i<lights; i++) {
			Light l = lightList[i];
			if (l.type == Light.AMBIENT) {
				tempr = surf.ka*surf.r;
				tempg = surf.ka*surf.g;
				tempb = surf.ka*surf.b;
				Ir0 += tempr * l.r;
				Ir1 += tempr * l.r;
				Ir2 += tempr * l.r;
				Ig0 += tempg * l.g;
				Ig1 += tempg * l.g;
				Ig2 += tempg * l.g;
				Ib0 += tempb * l.b;
				Ib1 += tempb * l.b;
				Ib2 += tempb * l.b;
			};
			else if (l.type == Light.DIRECTIONAL) {
				NdotL = normal.dot(l.vector);
				if (NdotL>0) {
					if (surf.ks==0) { //no specular, only diffuse
						tempr = surf.kd*NdotL;
						float dIr = surf.r * l.r * tempr;
						float dIg = surf.g * l.g * tempr;
						float dIb = surf.b * l.b * tempr;
						Ir0 += dIr;
						Ir1 += dIr;
						Ir2 += dIr;
						Ig0 += dIg;
						Ig1 += dIg;
						Ig2 += dIg;
						Ib0 += dIb;
						Ib1 += dIb;
						Ib2 += dIb;
					};
					else { //diffuse and specular
						R = new Vector3(normal);
						R.scale(2*NdotL);
						R.subtract(l.vector);
						V0 = new Vector3(eye.x-v[i0].x, eye.y-v[i0].y, eye.z-v[i0].z, true);
						V1 = new Vector3(eye.x-v[i1].x, eye.y-v[i1].y, eye.z-v[i1].z, true);
						V2 = new Vector3(eye.x-v[i2].x, eye.y-v[i2].y, eye.z-v[i2].z, true);
						tempr = l.r*surf.r;
						tempg = l.g*surf.g;
						tempb = l.b*surf.b;
						float V0dotR = V0.dot(R);
						float V1dotR = V1.dot(R);
						float V2dotR = V2.dot(R);
						if (V0dotR>0) {
							V0dotRtoN = (float) Math.pow(V0.dot(R), surf.nshiney);
							temp0 = surf.kd*NdotL + surf.ks*V0dotRtoN;
							Ir0 += tempr * temp0;
							Ig0 += tempg * temp0;
							Ib0 += tempb * temp0;
						};
						if (V1dotR>0) {
							V1dotRtoN = (float) Math.pow(V1.dot(R), surf.nshiney);
							temp1 = surf.kd*NdotL + surf.ks*V1dotRtoN;
							Ir1 += tempr * temp1;
							Ig1 += tempg * temp1;
							Ib1 += tempb * temp1;
						};
						if (V2dotR>0) {
							V2dotRtoN = (float) Math.pow(V2.dot(R), surf.nshiney);
							temp2 = surf.kd*NdotL + surf.ks*V2dotRtoN;
							Ir2 += tempr * temp2;
							Ig2 += tempg * temp2;
							Ib2 += tempb * temp2;
						};
						//I += diffuse + specular
					};
				};
			};
			else if (l.type==Light.POINT) {
				Vector3 L0 = new Vector3(v[i0], l.vector, true);
				Vector3 L1 = new Vector3(v[i1], l.vector, true);
				Vector3 L2 = new Vector3(v[i2], l.vector, true);

				//Vector3 L0 = new Vector3(l.x-v[i0].x, l.y-v[i0].y, l.z-v[i0].z, true);
				//Vector3 L1 = new Vector3(l.x-v[i1].x, l.y-v[i1].y, l.z-v[i1].z, true);
				//Vector3 L2 = new Vector3(l.x-v[i2].x, l.y-v[i2].y, l.z-v[i2].z, true);

				NdotL = normal.dot(L0);
				NdotL1 = normal.dot(L1);
				NdotL2 = normal.dot(L2);
				tempr = l.r*surf.r;
				tempg = l.g*surf.g;
				tempb = l.b*surf.b;

				if (NdotL>0) {
					R = new Vector3(normal);
					R.scale(2*NdotL);
					R.subtract(L0);
					V0 = new Vector3(eye.x-v[i0].x, eye.y-v[i0].y, eye.z-v[i0].z);
					V0dotRtoN = (float) Math.pow(V0.dot(R), surf.nshiney);
					temp0 = surf.kd*NdotL + surf.ks*V0dotRtoN;
					Ir0 += tempr * temp0;
					Ig0 += tempg * temp0;
					Ib0 += tempb * temp0;
				};
				if (NdotL1>0) {
					R1 = new Vector3(normal);
					R1.scale(2*NdotL1);
					R1.subtract(L1);
					V1 = new Vector3(eye.x-v[i1].x, eye.y-v[i1].y, eye.z-v[i1].z);
					V1dotRtoN = (float) Math.pow(V1.dot(R1), surf.nshiney);
					temp1 = surf.kd*NdotL1 + surf.ks*V1dotRtoN;
					Ir1 += tempr * temp1;
					Ig1 += tempg * temp1;
					Ib1 += tempb * temp1;
				};
				if (NdotL2>0) {
					R2 = new Vector3(normal);
					R2.scale(2*NdotL);
					R2.subtract(L2);
					V2 = new Vector3(eye.x-v[i2].x, eye.y-v[i2].y, eye.z-v[i2].z);
					V2dotRtoN = (float) Math.pow(V2.dot(R2), surf.nshiney);
					temp2 = surf.kd*NdotL2 + surf.ks*V2dotRtoN;
					Ir2 += tempr * temp2;
					Ig2 += tempg * temp2;
					Ib2 += tempb * temp2;
				};
			};
		};
		//vertex 1
		int Iri = (int)(Ir0*255);
		int Igi = (int)(Ig0*255);
		int Ibi = (int)(Ib0*255);
		Iri = ((Iri>255) ? 255 : Iri)<<16;
		Igi = ((Igi>255) ? 255 : Igi)<<8;
		Ibi = (Ibi>255) ? 255 : Ibi;
		argb0 = 0xff000000 | Iri | Igi | Ibi;

		//vertex 2
		Iri = (int)(Ir1*255);
		Igi = (int)(Ig1*255);
		Ibi = (int)(Ib1*255);
		Iri = ((Iri>255) ? 255 : Iri)<<16;
		Igi = ((Igi>255) ? 255 : Igi)<<8;
		Ibi = (Ibi>255) ? 255 : Ibi;
		argb1 = 0xff000000 | Iri | Igi | Ibi;

		//vertex 3
		Iri = (int)(Ir2*255);
		Igi = (int)(Ig2*255);
		Ibi = (int)(Ib2*255);
		Iri = ((Iri>255) ? 255 : Iri)<<16;
		Igi = ((Igi>255) ? 255 : Igi)<<8;
		Ibi = (Ibi>255) ? 255 : Ibi;
		argb2 = 0xff000000 | Iri | Igi | Ibi;

	};


	public boolean isVisible()
	{
		return isVisible;
	};



	public void ClipAndDraw(ZbufferedRaster r, Matrix3D m)
	{
		Vertex3D v0 = new Vertex3D(v[i0], argb0);
		Vertex3D v1 = new Vertex3D(v[i1], argb1);
		Vertex3D v2 = new Vertex3D(v[i2], argb2);
		int flag0 = calcFlag(v0);
		int flag1 = calcFlag(v1);
		int flag2 = calcFlag(v2);
		if ((flag0|flag1|flag2)==0)	// then all inside
			Draw(r, m, v0, v1, v2);
		else if ((flag0&flag1&flag2)==0) { // partially inside, needs clipping
			Polygon3D poly = new Polygon3D(v0,v1,v2);
			poly.clip();
			poly.Draw(r, m);
		};
		return;

	};



	public static void Draw(ZbufferedRaster r, Matrix3D m, Vertex3D v0, Vertex3D v1, Vertex3D v2)
	{
		v0 = m.transform(v0);
		v1 = m.transform(v1);
		v2 = m.transform(v2);
    	myDrawInterpolated(r, v0, v1, v2);
    }



	//Private Methods/////////////////////////////////////////////////////////////////////

	
	private static void setup(Vertex3D v0, Vertex3D v1, Vertex3D v2, float[] out, int shift)
	{
		int r0 = (v0.argb >> shift) & 255;
		int r1 = (v1.argb >> shift) & 255;
		int r2 = (v2.argb >> shift) & 255;
		out[0] = (v1.y-v2.y)*r0 + (v2.y-v0.y)*r1 + (v0.y-v1.y)*r2;
		out[1] = (v2.x-v1.x)*r0 + (v0.x-v2.x)*r1 + (v1.x-v0.x)*r2;
		out[2] = v1.x*v2.y*r0 - v2.x*v1.y*r0 + v2.x*v0.y*r1 - v0.x*v2.y*r1 + v0.x*v1.y*r2 - v1.x*v0.y*r2;
		//compute 1/(2*area)
		float scale = 1/((v1.x-v0.x)*(v2.y-v0.y)-(v1.y-v0.y)*(v2.x-v0.x));
		out[0] *= scale;
		out[1] *= scale;
		out[2] *= scale;
	};



	private static void setupZ(Vertex3D v0, Vertex3D v1, Vertex3D v2, float[] out)
	{
//figure out what to do about infinity.
		//compute 1/(2*area)
		float scale = 1/((v1.x-v0.x)*(v2.y-v0.y)-(v1.y-v0.y)*(v2.x-v0.x));
		if (Float.isInfinite(scale)) {
			out[0] = 0;
			out[1] = 0;
			out[2] = 0;
		}
		else {
			float z0 = v0.z;
			float z1 = v1.z;
			float z2 = v2.z;
			out[0] = (v1.y-v2.y)*z0 + (v2.y-v0.y)*z1 + (v0.y-v1.y)*z2;
			out[1] = (v2.x-v1.x)*z0 + (v0.x-v2.x)*z1 + (v1.x-v0.x)*z2;
			out[2] = v1.x*v2.y*z0 - v2.x*v1.y*z0 + v2.x*v0.y*z1 - v0.x*v2.y*z1 + v0.x*v1.y*z2 - v1.x*v0.y*z2;
			out[0] *= scale;
			out[1] *= scale;
			out[2] *= scale;
		};
	};




	private static void myDrawFlat(ZbufferedRaster r, Vertex3D v0, Vertex3D v1, Vertex3D v2, int argb)
	{
		Vertex3D vtop, vleft, vright, temp;
		if (v0.y<v1.y) {
			if (v0.y<v2.y) { // 0 is top
				vtop = v0;
				vleft = v1;
				vright = v2;
			}
			else { // 2 is top
				vtop = v2;
				vleft = v0;
				vright = v1;
			}
		}
		else if (v2.y<v1.y) { // 2 is top
			vtop = v2;
			vleft = v0;
			vright = v1;
		}
		else { // 1 is top
			vtop = v1;
			vleft = v2;
			vright = v0;
		};
		boolean leftbp = (vleft.y < vright.y);
		if (leftbp) {
			temp = vtop.interpolate(vright, (vleft.y-vtop.y)/(vright.y-vtop.y));
			if (((int)vtop.y)!=((int)vleft.y))
				drawFlatHelper(r, vtop, vleft, temp, true, argb);
			if (((int)vleft.y)!=((int)vright.y))
				drawFlatHelper(r, vleft, vright, temp, false, argb);
		}
		else { //rightbp
			temp = vtop.interpolate(vleft,  (vright.y-vtop.y)/(vleft.y-vtop.y));
			if (((int)vtop.y)!=((int)vright.y))
				drawFlatHelper(r, vtop, temp, vright, true, argb);
			if (((int)vleft.y)!=((int)vright.y))
				drawFlatHelper(r, temp, vleft, vright, false, argb);
		};
	};



	private static void myDrawInterpolated(ZbufferedRaster r, Vertex3D v0, Vertex3D v1, Vertex3D v2)
	{
		Vertex3D vtop, vleft, vright, temp;
		if (v0.y<v1.y) {
			if (v0.y<v2.y) { // 0 is top
				vtop = v0;
				vleft = v1;
				vright = v2;
			}
			else { // 2 is top
				vtop = v2;
				vleft = v0;
				vright = v1;
			}
		}
		else if (v2.y<v1.y) { // 2 is top
			vtop = v2;
			vleft = v0;
			vright = v1;
		}
		else { // 1 is top
			vtop = v1;
			vleft = v2;
			vright = v0;
		};
		boolean leftbp = (vleft.y < vright.y);
		if (leftbp) {
			temp = vtop.interpolate(vright, (vleft.y-vtop.y)/(vright.y-vtop.y));
			if (((int)vtop.y)!=((int)vleft.y))
				drawInterpolatedHelper(r, vtop, vleft, temp, true);
			if (((int)vleft.y)!=((int)vright.y))
				drawInterpolatedHelper(r, vleft, vright, temp, false);
		}
		else { //rightbp
			temp = vtop.interpolate(vleft,  (vright.y-vtop.y)/(vleft.y-vtop.y));
			if (((int)vtop.y)!=((int)vright.y))
				drawInterpolatedHelper(r, vtop, temp, vright, true);
			if (((int)vleft.y)!=((int)vright.y))
				drawInterpolatedHelper(r, temp, vleft, vright, false);
		};
	};


	
/*	private static void myDrawInterpolated(ZbufferedRaster r, Vertex3D v0, Vertex3D v1, Vertex3D v2, int argb)
	{
		v0 = new Vertex3D(v0);
		v1 = new Vertex3D(v1);
		v2 = new Vertex3D(v2);
		float[] alpha = new float[3];
		float[] red = new float[3];
		float[] green = new float[3];
		float[] blue = new float[3];
		setup(v0, v1, v2, alpha,24); setup(v0, v1, v2, red,16); setup(v0, v1, v2, green,8); setup(v0, v1, v2, blue,0);
		float Aa = alpha[0]; float Ba = alpha[1]; float Ca = alpha[2];
		float Ar = red[0]; float Br = red[1]; float Cr = red[2];
		float Ag = green[0]; float Bg = green[1]; float Cg = green[2];
		float Ab = blue[0]; float Bb = blue[1]; float Cb = blue[2];

		//Calculate A,B,C for z
		float[] zCoeff = new float[3];
		setupZ(v0, v1, v2, zCoeff);
		float Az = zCoeff[0]; float Bz = zCoeff[1]; float Cz = zCoeff[2];

		Vertex3D vtop = v0;
		Vertex3D vbp, vmax;
		if (v1.y>v2.y)
			{ vbp = v2; vmax = v1; }
		else
			{ vbp = v1; vmax = v2; }

		float m1 = (float) (vbp.x - v0.x)/(vbp.y - v0.y);
		float m2 = (float) (vmax.x - v0.x)/(vmax.y - v0.y);
		boolean leftbp;
		Vertex3D vleft, vright;
		if (m1<m2)
		{
			leftbp = true;
			vleft = vbp;
			vright = vmax;
		}
		else
		{
			leftbp = false;
			Vertex3D temp = v1;
			v1 = v2;
			v2 = temp;
			float tempf = m1;
			m1 = m2;
			m2 = tempf;
			vleft = vmax;
			vright = vbp;
		};

		int y = (int)Math.ceil(v0.y);
		float x1 = vleft.x - (vleft.y - y)*m1;
		float x2 = vright.x - (vright.y - y)*m2;
		
		int ybp = (int)Math.floor(vbp.y);
		int ymax = (int)Math.floor(vmax.y);

		while (true)
		{
			if (y>ybp) //break when we pass the breakpoint
				break;
			for (int i=(int)Math.ceil(x1); i<=Math.floor(x2); i++)
			{
				int za = (int) (Aa*i + Ba*((int)y) + Ca + .5f);
				int zr = (int) (Ar*i + Br*((int)y) + Cr + .5f);
				int zg = (int) (Ag*i + Bg*((int)y) + Cg + .5f);
				int zb = (int) (Ab*i + Bb*((int)y) + Cb + .5f);
				za = (za>255) ? 255 : za;
				zr = (zr>255) ? 255 : zr;
				zg = (zg>255) ? 255 : zg;
				zb = (zb>255) ? 255 : zb;

				int c = (za<<24)|(zr<<16)|(zg<<8)|zb;
				int z = (int) (Az*i + Bz*((int)y) + Cz + .5f);
				//r.setPixel(c, i, (int)y, z);
				r.setPixel(argb, i, (int)y, z);
			};
			y++;
			x1 += m1;
			x2 += m2;
		}
		
		if (leftbp)
		{
			m1 = (float) (vmax.x - vbp.x)/(vmax.y- vbp.y);
			x1 = vmax.x - (vmax.y - y)*m1;
		}
		else
		{
			m2 = (float) (vmax.x - vbp.x)/(vmax.y - vbp.y);
			x2 = vmax.x - (vmax.y - y)*m2;
		};
	
		//draw the part of the triangle under the breakpoint
		while (true)
		{
			if (y>ymax) //break when we pass the bottom
				break;
			for (int i=(int)Math.ceil(x1); i<=Math.floor(x2); i++)
			{
				int za = (int) (Aa*i + Ba*((int)y) + Ca + .5f);
				int zr = (int) (Ar*i + Br*((int)y) + Cr + .5f);
				int zg = (int) (Ag*i + Bg*((int)y) + Cg + .5f);
				int zb = (int) (Ab*i + Bb*((int)y) + Cb + .5f);
				za = (za>255) ? 255 : za;
				zr = (zr>255) ? 255 : zr;
				zg = (zg>255) ? 255 : zg;
				zb = (zb>255) ? 255 : zb;

				int c = (za<<24)|(zr<<16)|(zg<<8)|zb;
				int z = (int) (Az*i + Bz*((int)y) + Cz + .5f);

				//r.setPixel(c, i, (int)y, z);
				r.setPixel(argb, i, (int)y, z);
			};
			y++;
			x1 += m1;
			x2 += m2;
		};
	};*/


	private int calcFlag(Vertex3D v)
	{
		//bit | represents
		//----+------------
		// 5  | top
		// 4  | bottom
		// 3  | right
		// 2  | left
		// 1  | far
		// 0  | near

		//       near                far         
		return ((v.z<-1) ? 1:0) | (((v.z>1) ? 1:0)<<1) | (((v.x<-1) ? 1:0)<<2) | (((v.x>1) ? 1:0)<<3) | (((v.y<-1) ? 1:0)<<4) | (((v.y>1) ? 1:0)<<5);
	};



	//flatbottom=true if /\ or false for \/.
	private static void drawFlatHelper(ZbufferedRaster r, Vertex3D v0, Vertex3D v1, Vertex3D v2, boolean flatbottom, int argb)
	{
		float[] temp = new float[3];
		setupZ(v0,v1,v2,temp);
		float A = temp[0];
		float B = temp[1];
		float C = temp[2] + 0.5f;
		float x1, x2, dx1, dx2;
		int z;
		int y;
		int ymax = (int) Math.floor(v1.y);
		if (flatbottom) {
			y = (int) Math.floor(v0.y);
			dx2 = (v2.x-v0.x)/(v2.y-v0.y);
			x2 = v2.x - (v2.y-y)*dx2;
		}
		else { //flattop 
			y = (int) Math.ceil(v0.y);
			dx2 = (v1.x-v2.x)/(v1.y-v2.y);
			x2 = v1.x - (v1.y-y)*dx2;
		};
		dx1 = (v1.x-v0.x)/(v1.y-v0.y);
		x1 = v1.x - (v1.y-y)*dx1;
		while (y<=ymax) {
			for (int i=(int)Math.ceil(x1); i<=(int)Math.floor(x2); i++) {
				z = (int)(A*i + B*y + C);
try {
				r.setPixel(argb, i, y, (int)z);
}
catch (ArrayIndexOutOfBoundsException e)
{
	System.out.println("Well, there's a little bug here.");
	System.out.println("xrange = ["+x1+"  ,  "+x2+"]");
	System.out.println("setPixel at ("+i+", "+y+")");
};
			};
			x1 += dx1;
			x2 += dx2;
			y++;
		};
	};





	//flatbottom=true if /\ or false for \/.
	//this function assumes alpha is always 255.
	private static void drawInterpolatedHelper(ZbufferedRaster ras, Vertex3D v0, Vertex3D v1, Vertex3D v2, boolean flatbottom)
	{

		//speed this up by finding dA*/dx and doing * += dA*/dx when you do x++

		//Find A, B, C for interpolation
		float[] red = new float[3];
		float[] green = new float[3];
		float[] blue = new float[3];
		setup(v0, v1, v2, red,16); setup(v0, v1, v2, green,8); setup(v0, v1, v2, blue,0);
		float Ar = red[0]; float Br = red[1]; float Cr = red[2];
		float Ag = green[0]; float Bg = green[1]; float Cg = green[2];
		float Ab = blue[0]; float Bb = blue[1]; float Cb = blue[2];

		float[] temp = new float[3];
		setupZ(v0,v1,v2,temp);
		float Az = temp[0];
		float Bz = temp[1];
		float Cz = temp[2] + 0.5f;
		float x1, x2, dx1, dx2;
		float z, r, g, b;
		int iz, ir, ig, ib;
		int argb;
		int y;
		int ymax = (int) Math.floor(v1.y);
		if (flatbottom) {
			y = (int) Math.floor(v0.y);
			dx2 = (v2.x-v0.x)/(v2.y-v0.y);
			x2 = v2.x - (v2.y-y)*dx2;
		}
		else { //flattop 
			y = (int) Math.ceil(v0.y);
			dx2 = (v1.x-v2.x)/(v1.y-v2.y);
			x2 = v1.x - (v1.y-y)*dx2;
		};
		dx1 = (v1.x-v0.x)/(v1.y-v0.y);
		x1 = v1.x - (v1.y-y)*dx1;

		while (y<=ymax) {
			int xmin = (int)Math.ceil(x1);
			z = Az*xmin + Bz*y + Cz;
			r = Ar*xmin + Br*y + Cr;
			g = Ag*xmin + Bg*y + Cg;
			b = Ab*xmin + Bb*y + Cb;
			for (int i=xmin; i<=(int)Math.floor(x2); i++) {
				argb = 0xFF000000|((int)(r)<<16)|((int)(g)<<8)|(int)(b);
try {
				ras.setPixel(argb, i, y, (int)z);
}
catch (ArrayIndexOutOfBoundsException e)
{
	System.out.println("Well, there's a little bug here.");
	System.out.println("xrange = ["+x1+"  ,  "+x2+"]");
	System.out.println("setPixel at ("+i+", "+y+")");
};
				//remember d<something>/dx = A<something>
				z += Az;
				r += Ar;
				g += Ag;
				b += Ab;
			};
			x1 += dx1;
			x2 += dx2;
			y++;
		};
	};




}