import java.awt.Color;
import Vertex3D;
import Matrix3D;
import Surface;
import ZRaster;
public class Triangle
{
	private static Vertex3D vlist[];
	private static Vertex3D tlist[];
	protected int v[];
	protected float r[], g[], b[];
	protected Surface surface;
	private boolean culled;
	private Vertex3D vert[];
	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 Triangle()
	{
	}
	public Triangle(int v0, int v1, int v2)
	{
		v = new int[3];
		v[0] = v0;
		v[1] = v1;
		v[2] = v2;    
		r = new float[3];
		g = new float[3];
		b = new float[3];
		vert = new Vertex3D[3];
		vert[0] = new Vertex3D();
		vert[1] = new Vertex3D();
		vert[2] = new Vertex3D();
		scale = -1;
		culled = false;
	}
	public void ClipAndDraw(ZRaster raster, Matrix3D project)
	{
		// This clipping is done in canonical space, so I just need to find
		// polygons that are less than -1 (Z) and greater than 1 (Z)

		if (vlist[v[0]].z() < -1 && vlist[v[1]].z() < -1 && vlist[v[2]].z() < -1 )
			return; // trivial rejection
		else
			if (vlist[v[0]].z() > 1 && vlist[v[1]].z() > 1 && vlist[v[2]].z() > 1)
				return; // trivial rejection
			else
				if (vlist[v[0]].z() > -1 && vlist[v[1]].z() > -1 && vlist[v[2]].z() > -1 &&
					vlist[v[0]].z() < 1 && vlist[v[1]].z() < 1 && vlist[v[2]].z() < 1 )
				{ 
					// trivial acceptance
					if (tlist[v[0]] == null)
					{
						vert[0] = project.transform(vlist[v[0]]);
						tlist[v[0]] = project.transform(vlist[v[0]]);
					}
					else
						vert[0] = tlist[v[0]];
					
					if (tlist[v[1]] == null)
					{
						vert[1] = project.transform(vlist[v[1]]);
						tlist[v[1]] = project.transform(vlist[v[1]]);
					}
					else
						vert[1] = tlist[v[1]];
						
					if (tlist[v[2]] == null)
					{
						vert[2] = project.transform(vlist[v[2]]);
						tlist[v[2]] = project.transform(vlist[v[2]]);
					}
					else
						vert[2] = tlist[v[2]];
						
					Draw(raster);
					
				} 
				else
				{
					
					Vertex3D clipList1[] = new Vertex3D[4];
					Vertex3D clipList2[] = new Vertex3D[6];
					
					//clip on front plane
					int vertexcount = 0;
					boolean inside = (vlist[v[0]].z() > -1);

					if (inside)
					{
						clipList1[vertexcount++] = vlist[v[0]];
					}
					boolean laststate=inside;

					for ( int i = 1; i <= 2; i++)
					{
						inside = (vlist[v[i]].z() > -1);
					
						if (laststate && !inside || !laststate && inside)
						{
							float t = (-1 - vlist[v[i-1]].z()) / (vlist[v[i]].z() - vlist[v[i-1]].z());
							float x = (vlist[v[i]].x() - vlist[v[i-1]].x())*t + vlist[v[i-1]].x();
							float y = (vlist[v[i]].y() - vlist[v[i-1]].y())*t + vlist[v[i-1]].y();
							clipList1[vertexcount++] = new Vertex3D(x,y,-1);
						}
						if (inside)
						{
							clipList1[vertexcount++] = vlist[v[i]];
						}
						laststate=inside;
					}
					
					inside = (vlist[v[0]].z() > -1);
					
					if (laststate && !inside || !laststate && inside)
					{
						float t = (-1 - vlist[v[2]].z()) / (vlist[v[0]].z() - vlist[v[2]].z());
						float x = (vlist[v[0]].x() - vlist[v[2]].x())*t + vlist[v[2]].x();
						float y = (vlist[v[0]].y() - vlist[v[2]].y())*t + vlist[v[2]].y();
						clipList1[vertexcount++] = new Vertex3D(x,y,-1);
					}
						
					int vertex2count = 0;
					inside = (clipList1[0].z() < 1);

					if (inside)
					{
						clipList2[vertex2count++] = clipList1[0];
					}
					laststate=inside;

					for ( int i = 1; i < vertexcount; i++)
					{
						inside = (clipList1[i].z() < 1);
					
						if (laststate && !inside || !laststate && inside)
						{
							float t = (1 - clipList1[i-1].z()) / (clipList1[i].z() - clipList1[i-1].z());
							float x = (clipList1[i].x() - clipList1[i-1].x())*t + clipList1[i-1].x();
							float y = (clipList1[i].y() - clipList1[i-1].y())*t + clipList1[i-1].y();
							clipList2[vertex2count++]=new Vertex3D(x,y,1);
						}
						if (inside)
						{
							// Add the next vertex
							clipList2[vertex2count++] = clipList1[i];
						}
						laststate=inside;
					}
					inside = (clipList1[0].z() < 1);
					
					if (laststate && !inside || !laststate && inside)
					{
						float t = (1 - clipList1[2].z()) / (clipList1[0].z() - clipList1[2].z());
						float x = (clipList1[0].x() - clipList1[2].x())*t + clipList1[2].x();
						float y = (clipList1[0].y() - clipList1[2].y())*t + clipList1[2].y();
						clipList2[vertex2count++]=new Vertex3D(x,y,1);
					}

					// Now figure out what's happening...
					
					vert[0] = project.transform(clipList2[0]);
					vert[1] = project.transform(clipList2[1]);
					vert[2] = project.transform(clipList2[2]);
					Draw(raster);

					if (vertex2count == 4)
					{
						vert[1] = vert[2];
						vert[2] = project.transform(clipList2[3]);
						Draw(raster);
					}
					if (vertex2count == 5)
					{
						vert[1] = vert[2];
						vert[2] = project.transform(clipList2[4]);
						Draw(raster);
					}
				}
	}
public void Draw(ZRaster raster)
{
	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 (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;
				}
				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;
	}
}
	public Surface getSurface()

	{
		return surface;
	}
	/*
		... you'll need to modify the following two methods ...
	*/

	// I'm going to combine Illumination and culling into one step.
	public void Illuminate(Light l[], int lights, Point3D eye, boolean cull)

	{

		// first cull the triangle, if necessary
 		// create a normal to the eye.  
		
		float x,y,z;
		x = eye.x()-vlist[v[0]].x();
		y = eye.y()-vlist[v[0]].y();
		z = eye.z()-vlist[v[0]].z();
		
		// create normal vector of triangle
		float ox, oy, oz, tx, ty, tz;
		ox = vlist[v[0]].x() - vlist[v[1]].x();
		oy = vlist[v[0]].y() - vlist[v[1]].y();
		oz = vlist[v[0]].z() - vlist[v[1]].z();

		tx = vlist[v[2]].x() - vlist[v[1]].x();
		ty = vlist[v[2]].y() - vlist[v[1]].y();
		tz = vlist[v[2]].z() - vlist[v[1]].z();

		// normal is t(wo) cross o(ne)
		Point3D n = new Point3D(ty*oz-oy*tz, tz*ox-oz*tx, tx*oy-ox*ty);
		// for lighting, normalize the vector;
 		n.multiplyScalar( (float)(1.0 / Math.sqrt(( n.x()*n.x() + n.y()*n.y() + n.z()*n.z()))));

		if ( n.x()*x + n.y()*y + n.z()*z >= 0 || !cull)
		{
			culled = false;
		}
		else
		{
			culled = true;
			return;
		}


// First, I will implement per-surface illumination	
	 for (int i = 0; i < 3; i++) 
   {
	   	vert[i] = vlist[v[i]];
		r[i] = 0;
		g[i] = 0;
		b[i] = 0;
		for (int j = 0; j < lights; ++j)
		{
			float[] color = new float[3];
			color[0] = surface.r;
			color[1] = surface.g;
			color[2] = surface.b;
			
			l[j].lightVertex(n, vert[i], color, surface);
			r[i] += color[0];
			g[i] += color[1];
			b[i] += color[2];
			
		}
		if (r[i] > 1.0) r[i] = 1;
		if (g[i] > 1.0) g[i] = 1;
		if (b[i] > 1.0) b[i] = 1;
	 }
	}
// This seems kinda crappy, since I don't know what the eye normal is here.

	public boolean isVisible()
	{
		return (!culled);
	}
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;
}
	public void setSurface(Surface s)

	{
		surface = s;
	}
	public void setVertexList(Vertex3D list[], Vertex3D transformedList[])

	{
		vlist = list;
		tlist = transformedList;
	}
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;
}
}