//========================================================
// Transforms - a simple applet demonstrating transformations
// Written by Chris DeCoro - cdecoro@cat.nyu.edu
// Sept. 9, 2002
//========================================================

import java.util.*;
import java.net.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import Geometry.*;

//class Face, used to store the vertices, normal, and precomputed color of a face
class Face
{
	Vector3[] vertices = new Vector3[3];
	Vector3 normal = null;
	Color color;

	public String toString()
	{
		return "< " + vertices[0] + " " + vertices[1] + " " + vertices[2] + " >";
	}
}

public class Transforms3D extends ThreeDimensionalApplet
{
	//vertices[] and faces[] contain the currently loaded geometry
	private Vector3[] vertices;
	private Face[] faces;

	//Given a URL, opens an Internet connection and returns a stream
	InputStream openModel(String fileURL)
	{
		try
		{
			URL url = new URL(fileURL);
			return url.openStream();
		}
		catch(Exception e)
		{
			System.out.println(e);
			throw new RuntimeException("Error in reading file");
		}
	}

	//Given a stream, downloads the model, parses it, and fills in vertices[]/faces[]
	void readModel(InputStream stream)
	{
		try {
			StreamTokenizer tok = new StreamTokenizer(stream);

			int vertexCount = -1;
			int faceCount = -1;

			//Parse the ply header and determine the number of fields
			//This is not very robust, and will explode on a non-ply
			for(; tok.sval == null || !tok.sval.equals("header"); tok.nextToken() )
			{

				if(tok.sval != null && tok.sval.equals("element"))
				{
					tok.nextToken();
					if(tok.sval.equals("vertex"))
					{
						tok.nextToken();
						vertexCount = (int)tok.nval;
					}
					else if(tok.sval.equals("face"))
					{
						tok.nextToken();
						faceCount = (int)tok.nval;
					}
				}
			}

			System.out.println("Vertices: " + vertexCount);
			System.out.println("Faces: " + faceCount);

			//Read all of the vertices out of the stream
			vertices = new Vector3[vertexCount];
			for(int i=0; i<vertexCount; i++)
			{
				double valX, valY, valZ;

				tok.nextToken();
				valX = tok.nval;

				tok.nextToken();
				valY = tok.nval;

				tok.nextToken();
				valZ = tok.nval;

				vertices[i] = new Vector3(valX, valY, valZ);
			}


			//Find the maximum and minimum values 
			//max[] and min[] are the corners of the axis-aligned bbox
			double max[] = new double[3];
			double min[] = new double[3];
			max[0] = max[1] = max[2] = Double.MIN_VALUE;
			min[0] = min[1] = min[2] = Double.MAX_VALUE;

			for(int i=0; i<vertices.length; i++)
			{
				for(int j=0; j<3; j++)
				{
					if( vertices[i].array()[j] < min[j] )
						min[j] = vertices[i].array()[j];
					if( vertices[i].array()[j] > max[j] )
						max[j] = vertices[i].array()[j];
				}
			}

			//Find the center of the object
			double center[] = new double[3];
			for(int i=0; i<3; i++)
				center[i] = (max[i] + min[i])/2;

			//Find the scale factor
			double width[] = new double[3];
			for(int i=0; i<3; i++)
				width[i] = max[i] - min[i];
			double scale;
			if(width[0] > width[1])
				scale = width[0];
			else
				scale = width[1];
			if(width[2] > scale)
				scale = width[2];


			//Recenter and rescale
			Vector3 vCenter = new Vector3(center[0], center[1], center[2]);
			for(int i=0; i<vertices.length; i++)
			{
				vertices[i] = vertices[i].sub(vCenter).div(scale)	;
			}

			//Load all of the faces from the stream
			faces = new Face[faceCount];
			for(int i=0; i<faceCount; i++)
			{
				faces[i] = new Face();

				tok.nextToken();
				tok.nextToken();
				faces[i].vertices[0] = vertices[(int)tok.nval];
				tok.nextToken();
				faces[i].vertices[1] = vertices[(int)tok.nval];
				tok.nextToken();
				faces[i].vertices[2] = vertices[(int)tok.nval];
				
				Vector3 v0, v1, v2;
				v0 = faces[i].vertices[0];
				v1 = faces[i].vertices[1];
				v2 = faces[i].vertices[2];

				//Compute the normal
				faces[i].normal = (v1.sub(v0)).cross(v2.sub(v0)).normalize();

				//For speed, precompute the color per face
				normal(faces[i].normal);
				faces[i].color = computeLightedColor();
			}
	

		}
		catch(IOException e)
		{
			System.out.print(e);
			throw new RuntimeException("Error in reading");
		}
	}
	
	private boolean initialized = false;

	//Changes the current model
	//Called from outside of the Java applet (by the scripting in the page)
	public void setModel(String model)
	{
		System.out.println(model);
		readModel(openModel(model));
		setDamage();
	}
	
	//Renders the scene
	public void render(Graphics g) 
	{
		//On the first run, initialize using the following code
		if(!initialized)
		{
			ambientCoeff(0.5);   //0.3 , 0.7 also looked good
			diffuseCoeff(0.75);
			ambientAndDiffuseMaterial(201, 125, 73);
			ambientAndDiffuseColor(255, 255, 255);

			System.out.println(getParameter("model"));
			readModel(openModel(getParameter("model")));
			initialized = true;
		}
		
		//Whenever the display is damaged, redraw using this code
		if (damage) 
		{
			//Provide the graphics context to the pseudoGL system
			setContext(g);

			//Clear off the screen by drawing a big white box (this is glClear())
			transform( (new Matrix4x4()) );
			disable(RENDER_CULL_FACE);
			begin(RENDER_POLYGON);
			color3i(255, 255, 255);
			vertex(0, 0);
			vertex(bounds().width, 0);
			vertex(bounds().width, bounds().height);
			vertex(0, bounds().height);
			end();
			if(cullFace)
				enable(RENDER_CULL_FACE);
	

			//Transform the model based on the current settings
			double hw=bounds().width/2, hh=bounds().height/2;

			transform( (new Matrix4x4())
					.scale(400, -400, -400)
					.leftmul(rotationMatrix)		
					.scale(scaleX, scaleY, 0)
					.translate(translateX+hw,translateY+hh,0)   );

			//Draw the points, if selected
			if( selectedMode == 3 )
			{
				pointSize(4);
				color3i(255, 0, 0);
				begin(RENDER_POINTS);
				for(int i=0; i<vertices.length; i++)
				{
					double[] array = vertices[i].array();
					vertex(array[0], array[1], array[2]);
				}
				end();
			}

			//Draw the triangles the make up the model, if mode is selected
			if( selectedMode == 0 || selectedMode == 1 )
			{
				//enable(RENDER_LIGHTING); //actually, we're using the precomputed lighting here
				//color3i(255, 0, 0);
				begin(RENDER_TRIANGLE);
				for(int i=0; i<faces.length; i++)
				{
					normal(faces[i].normal);
					color(faces[i].color);
					for(int j=0; j<3; j++)
					{
						vertex(faces[i].vertices[j]);
					}
				}
				end();
				disable(RENDER_LIGHTING);
			}

			//Draw the wireframed triangles
			if( selectedMode == 1 || selectedMode == 2 )
			{
				if( selectedMode == 2 )
					enable(RENDER_LIGHTING); //for fun, let's demo the lighting
				else
					color3i(0, 0, 0);

				begin(RENDER_OPEN_TRIANGLE);
				for(int i=0; i<faces.length; i++)
				{
					normal(faces[i].normal);
					for(int j=0; j<3; j++)
					{
						vertex(faces[i].vertices[j]);
					}
				}
				end();
				disable(RENDER_LIGHTING);
			}

			//Draw the axis lines, if selected
			if(drawAxis)
			{
				begin(RENDER_LINES);
				color3i(255, 0, 0);
				vertex(0, 0, 0);
				vertex(1, 0, 0);
				
				color3i(0, 255, 0);
				vertex(0, 0, 0);
				vertex(0, 1, 0);

				color3i(0, 0, 255);
				vertex(0, 0, 0);
				vertex(0, 0, 1);
				end();
			}
		
			//Clear out the context, ending the frame
			setContext(null);
		}
	}

	//Stores the "old" values for each of the transformation factors, and sets the mouse-down pos
	//These old values are used to allow for dynamic updates while we drag the mouse
	public boolean mouseDown(Event e, int x, int y) 
	{		 
		boolean leftButton, centerButton, rightButton;
		centerButton = ((e.modifiers & e.ALT_MASK) > 0);
		rightButton = ((e.modifiers & e.META_MASK) > 0);
		leftButton = !centerButton && !rightButton;
	  
		downX = x;
		downY = y;

		oldTranslateX = translateX;
		oldTranslateY = translateY;

		oldScaleX = scaleX;
		oldScaleY = scaleY;

		oldTheta = theta; //I dont think we use this anymore
	
		oldRotationMatrix = rotationMatrix;

		setDamage();
		return true;
	}

	//Here is where we actually perform the transformations
	public boolean mouseDrag(Event e, int x, int y) 
	{ 
		//See what buttons are down
		//This is a real mess, but I think its JDK1.0 compilant
		boolean leftButton, centerButton, rightButton, shiftDown;
		centerButton = ((e.modifiers & e.ALT_MASK) > 0);
		rightButton = ((e.modifiers & e.META_MASK) > 0);
		leftButton = !centerButton && !rightButton;
		shiftDown = ((e.modifiers & e.SHIFT_MASK) > 0);

		double hw=bounds().width/2, hh=bounds().height/2;

		//Virtual trackball rotation
		if(leftButton && !shiftDown)
		{
			double radius=hw;

			//Determine the center of the object in screen coords
			//This will be the center of our sphere
			Vector3 center = new Vector3(translateX+hw,translateY+hh,0);
			Vector3 i1, i2;

			//Project a line from the mouse click locations through the sphere
			//i1 is the initial mouse down position, i2 is the current position
			//We could optimize by computing i1 once in mouseDown, but oh well
			i1 =isectLineSphere(
					new Vector3(downX,downY,radius), 
					new Vector3(downX,downY,0), 
					center,	
					radius);

			i2 =isectLineSphere(
					new Vector3(x,y,radius), 
					new Vector3(x,y,0), 
					center,
					radius);
			
			//If either are null (no collision) just return
			if( i1 != null && i2 != null )
			{
				//Determine the vectors from the center of the sphere
				// to the point on the surface
				//We can optimize by removing the normalize() with div(radius)
				Vector3 v1, v2;
				v1 = i1.sub(center).normalize();
				v2 = i2.sub(center).normalize();

				//Determine the axis of rotation and the angle
				Vector3 a = v1.cross(v2);
				double theta = Math.asin(a.norm());

				//We have problems when the angle is > 90
				//The following code computes the angle correctly, but will dont rotate right
				//So just stop rotating when we move too far away
				if(v1.dot(v2) < 0)
				{
					theta = Math.PI - theta; //gets right angle, but still problems
					//theta = theta + Math.PI/2; //this is wrong
					setDamage();
					return true;
				}
			
				//Make some practical adjustments to account for the flipped y axis
				rotationAxis = new Vector3(a.x(), -a.y(), a.z()).normalize();
				
				//TODO: Find out if we should comment this next line
				//rotationAxis = (new Matrix4x4()).rotateZ(theta).mul(rotationAxis);
				
				//Convert to degrees
				rotationTheta = theta*180/Math.PI;

				//System.out.println("Dot: " + v1.dot(v2) + " angle: " + rotationTheta);
				
				//Update the rotation matrix	
				rotationMatrix = (oldRotationMatrix).rotate(rotationAxis, rotationTheta);
			}
		}
		else if(leftButton && shiftDown)
		{
			//We have the vector from the center to the mouse-down position
			//We can compute the vector to the 
			Vector3 center = new Vector3(translateX+hw,translateY+hh,0);
			Vector3 handle = (new Vector3(x, y, 0)).sub(center);
			Vector3 start = (new Vector3(downX,downY,0)).sub(center);

			//Get the angle from the dot product, dividing by norms and takign arccos
			//If we wanted to be clever, we could keep the cos as it is, find sin
			// using sin = sqrt(1-cos^2) and plug sin,cos into the rotatematrix directly
			//This is a 2d operation
			double dot = handle.dot(start);
			double angle = Math.acos(dot/handle.norm()/start.norm())*180/Math.PI;	
			
			//If we went left (i think its left), take the negative angle
			//We will know if the crossproduct is pointing back at us
			if( handle.cross(start).z() > 0 )
				angle = -angle;

			//Update the old rotation matrix
			rotationMatrix = (oldRotationMatrix).rotateZ(angle);

		}
		else if(centerButton)
		{
			//Translate the object based on mouse pos.
			translateX = oldTranslateX + (x - downX);
			translateY = oldTranslateY + (y - downY);
		}
		else if(rightButton)
		{
			//Rescale the object based on the mouse position
			scaleX = oldScaleX + (x - downX)/50.0;
			scaleY = oldScaleY + -(y - downY)/50.0;
		}	

		setDamage();
		return true;

	}

	//Just reset the mouse positions to their old values
	public boolean mouseUp(Event e, int x, int y) 
	{		 
		boolean leftButton, centerButton, rightButton;
		centerButton = ((e.modifiers & e.ALT_MASK) > 0);
		rightButton = ((e.modifiers & e.META_MASK) > 0);
		leftButton = !centerButton && !rightButton;
	   
		if( downX == -1 )
			return true;

		downX = -1;
		downY = -1;
		setDamage();
		return true;
	}


	boolean cullFace=true;
	boolean drawAxis=true;
	int selectedMode = 0;

	//Toggle a couple of settings, based on input
	public boolean keyDown(Event evt, int key)
	{
		switch(key)
		{
		case 'b':
			cullFace = !cullFace;
			break;
		case 'x':
			drawAxis = !drawAxis;
			break;
		case ' ':
			selectedMode = (selectedMode+1)	% 4;
			break;
		case Event.ESCAPE:
			translateX = translateY = oldTranslateX = oldTranslateY = theta = oldTheta = rotationTheta = 0;	
			scaleX = scaleY = oldScaleX = oldScaleY = 1;
			rotationMatrix = oldRotationMatrix = new Matrix4x4();
			break;
		}

		setDamage();
		return true;
	}
	
	//Settings for transformations
	int downX=-1, downY=-1;
	double translateX=0, translateY=0;
	double oldTranslateX=0, oldTranslateY=0;

	double scaleX=1, scaleY=1;
	double oldScaleX=1, oldScaleY=1;

	double theta=0, oldTheta=0;

	Vector3 rotationAxis=new Vector3(1, 0, 0);
	double rotationTheta=0;

	Matrix4x4 rotationMatrix=new Matrix4x4();
	Matrix4x4 oldRotationMatrix=new Matrix4x4();

}

class ThreeDimensionalApplet extends GenericApplet
{
	int x = 100, y = 100;

	//function setContext
	//Sets the current graphics object to draw on
	void setContext(Graphics g)
	{
		graphics = g;
	}
	
	//function begin
	//Starts a primitive-drawing operation
	void begin(int shapeType)
	{
		if( renderState != RENDER_NONE )
		{
			throw new RuntimeException("Cannot call begin() twice without intervening end()");
		}

		if( shapeType < RENDER_NONE || shapeType > RENDER_LAST )
		{
			throw new RuntimeException("Attempted to draw an invalid primitive");
		}

		renderState = shapeType;
		sentVertices = 0;
		polygon = new Polygon();
	}

	//function end
	//Concludes a primitive-drawing operation
	void end()
	{
		if( renderState == RENDER_NONE )
		{
			throw new RuntimeException("Cannot call end() without begin()");
		}
		else if( renderState == RENDER_POLYGON )
		{
			if(!cullPolygon || counterClockwise(polygon))
				graphics.fillPolygon(polygon);
		}
		else if( renderState == RENDER_OPEN_POLYGON )
		{
				graphics.drawPolygon(polygon);
		}

		renderState = RENDER_NONE;
	}

	//functions color3d, color3i, color
	//Sets the current color.  
	//Drawing functions will use the current color when a primitive is drawn
	//color3d accepts values in the range [0.0, 1.0]
	//color3i accepts values in the range [0 255]
	//color accepts a java.awt.Color object
	//They are given seperate names to avoid accidental type promotion
	void color3d(double r, double g, double b)
	{
		graphics.setColor(new Color((int)(r*255.0), (int)(g*255.0), (int)(b*255.0)));
	}
	
	void color3i(int r, int g, int b)
	{
		graphics.setColor(new Color(r,g,b));
	}

	void color(Color color)
	{
		graphics.setColor(color);
	}
	
	//function vertex
	//Submits vertices for drawing primitives, 2d integer form
	void vertex(int x, int y)
	{
		vertex((double)x, (double)y, 0);
	}
	
	//function vertex
	//Submits vertices for drawing primitives, double form
	void vertex(double x, double y, double z)
	{
		vertex(new Vector3(x,y,z));
	}
	
	//function vertex(Vector3)
	//Submits vertices for drawing primitives, vector/double form
	void vertex(Vector3 worldCoord)
	{
		//Three different coordinates
		//worldCoord is the input coordinate
		//transCoord is the post-modelview-transformation coordinate
		//screenCoord is the post-projection coordinate

		boolean fastMul = true;
		int screenX, screenY;

		//This following section is probably the key part of the assignment
		//  Here is where we take the vertex input and transform it, 
		//  according to the current transformation matrix
		//  We assume we have orthogonal projection, so applying the projection matrix
		//  would be (more or less) equivalent to dropping the z coordinate
		//To save time, we use a very optimized partial matrix multiplier, fastMul()
		//  see the notes in Geometry/Matrix4x4.java on our hacks
		if( fastMul )
		{
			double[] transCoord;
			transCoord = transformMatrix.fastMul(worldCoord);
			screenX = (int)transCoord[0];
			screenY = (int)transCoord[1];
		}
		else
		{
			//This way is a more proper, but slower, version above the above
			//It uses a general (complete) matrix multipler, as opposed to the other
			//  which makes assumptions of the Matrix input

			Vector3 transCoord, screenCoord;
			transCoord = transformMatrix.mul(worldCoord);
			screenCoord = transCoord; 
			screenX = (int)screenCoord.x();
			screenY = (int)screenCoord.y();
		}

		//Take different actions based on the render state
		//For most primitives, we test if we need to light before drawing
		if( renderState == RENDER_NONE )
		{
			throw new RuntimeException("Must call begin() before sending a vertex");
		}
		else if( renderState == RENDER_POINTS )
		{	
			//For points, jsut draw an appropriatly sized box on every vertex()
			if(lighting) graphics.setColor(computeLightedColor());
			graphics.fillRect(screenX-pointSize/2, screenY-pointSize/2, pointSize, pointSize);
		}		
		else if( renderState == RENDER_LINES )
		{
			//For lines, call drawLine() for every other vertex()
			if( sentVertices == 0 )
			{
				lineX = screenX;
				lineY = screenY;
				sentVertices++;
			}	
			else
			{
				if(lighting) graphics.setColor(computeLightedColor());
				graphics.drawLine(lineX, lineY, screenX, screenY);
				sentVertices = 0;
			}
		}
		else if( renderState == RENDER_LINE_STRIP )
		{
			//For line strips, perform drawLine() on every vertex() after the first
			//A line strip is the same thing as a polyline in many draw programs
			if( sentVertices > 0 )
			{
				if(lighting) graphics.setColor(computeLightedColor());
				graphics.drawLine(lineX, lineY, screenX, screenY);
			}

			lineX = screenX;
			lineY = screenY;
			sentVertices++;			
		}
		else if( renderState == RENDER_POLYGON  ||
		    renderState == RENDER_OPEN_POLYGON ||
		    renderState == RENDER_TRIANGLE ||
		    renderState == RENDER_OPEN_TRIANGLE )
		{
			//For all of the polygon types, we will need to store the additional vertex
			//With triangles, we will draw on the third vtx, below
			//With polygons, we draw on the end() call
			polygon.addPoint(screenX, screenY);
			sentVertices++;
		}

		//Draw a triangle on every third vertex
		if( renderState == RENDER_TRIANGLE && sentVertices == 3 )
		{
			//Perform back face culling
			if(!cullPolygon || counterClockwise(polygon))
			{
				if(lighting) graphics.setColor(computeLightedColor());
				graphics.fillPolygon(polygon);
			}
			
			polygon = new Polygon();
			sentVertices = 0;
		}
		else if( renderState == RENDER_OPEN_TRIANGLE && sentVertices == 3 )
		{
			//perform back face culling
			if(!cullPolygon || counterClockwise(polygon))
			{
				if(lighting) graphics.setColor(computeLightedColor());
				graphics.drawPolygon(polygon);
			}

			polygon = new Polygon();
			sentVertices = 0;
		}
	}

	//function transform(Matrix)
	//Sets the current modelview transform matrix
	//Essentially equivalent to glLoadMatrix()
	void transform(Matrix4x4 mat)
	{
		transformMatrix = mat;
		//System.out.println(mat);
	
	}

	//function getTransform()
	//Retrieves the current transform matrix
	Matrix4x4 getTransform()
	{
		return transformMatrix;
	}
	
	//function pushMatrix
	//Pushes the current transform matrix onto the transform stack
	//Essentially equivalent to glPushMatrix()
	void pushMatrix()
	{
		transformStack.push( transformMatrix.clone() );
	}

	//function popMatrix
	//Pushes the current transform matrix onto the transform stack
	//Essentially equivalent to glPopMatrix()
	void popMatrix()
	{
		transformMatrix = (Matrix4x4)transformStack.peek();
		transformStack.pop();
	}

	//function project
	//Given the current modelview and projection matrices,
	//Projects the input in world coordinates to screen coordinates
	//Similar to gluProject()
	Vector3 project(Vector3 world)
	{
		return transformMatrix.mul(world);
	}

	//function unProject
	//Given the current modelview and projection matrices,
	//Performs the inverse of the projection operation, which switches from
	//screen coordinates to world coordinates; Similar to gluUnProject()
	Vector3 unProject(Vector3 screen)
	{
		return transformMatrix.inverse().mul(screen);
	}

	//function setDamage
	//Informs the system that the window has been damaged/invalidated
	//and needs to be redrawn
	void setDamage()
	{
		damage = true;
	}


	//Render modes - passed to begin()
	static final int RENDER_NONE=0;
	static final int RENDER_LINES=1;
	static final int RENDER_LINE_STRIP=2;
	static final int RENDER_POLYGON=3;
	static final int RENDER_OPEN_POLYGON=4;
	static final int RENDER_TRIANGLE=5;
	static final int RENDER_OPEN_TRIANGLE=6;
	static final int RENDER_POINTS=7;
	static final int RENDER_LAST=RENDER_POINTS;
	
	//Option settings - passed to enable() / disable()
	static final int RENDER_CULL_FACE=256;
	static final int RENDER_LIGHTING=257;

	void enable(int param)
	{
		switch(param)
		{
		case RENDER_CULL_FACE:
			cullPolygon = true;
			break;
		case RENDER_LIGHTING:
			lighting = true;
			break;
		default:
			throw new RuntimeException("Invalid parameter passed to enable");
		}
	}

	void disable(int param)
	{
		switch(param)
		{
		case RENDER_CULL_FACE:
			cullPolygon = false;
			break;
		case RENDER_LIGHTING:
			lighting = false;
			break;
		default:
			throw new RuntimeException("Invalid parameter passed to enable");
		}
	}

	//function pointSize
	//Sets the size of the boxes used to draw the point primitives
	void pointSize(int size)
	{
		pointSize = size;
	}
	
	//function normal
	//Sets the current normal, used in dynamic lighting calculations
	void normal(Vector3 vec)
	{
		currentNormal = vec;
	}

	//function lightDirection
	//Sets the direction of the incoming light (we have only one)
	//Note that we assume light is at infinite distance
	void lightDirection(Vector3 pos)
	{
		lightPos = pos;	
	}

	//Various functions to set lighting properties
	//These include ambient/diffuse color/material/weight-coefficient
	//As well as functions to set ambient & diffuse at same time
	void ambientColor(int r, int g, int b)
	{
		ambientColor = new Color(r, b, g);
	}

	void ambientMaterial(int r, int g, int b)
	{
		ambientMaterial = new Color(r, b, g);
	}

	void ambientAndDiffuseMaterial(int r, int g, int b)
	{
		ambientMaterial = new Color(r, b, g);
		diffuseMaterial = new Color(r, g, b);
	}

	void ambientAndDiffuseColor(int r, int g, int b)
	{
		ambientColor = new Color(r, b, g);
		diffuseColor = new Color(r, g, b);
	}

	void diffuseColor(int r, int g, int b)
	{
		diffuseColor = new Color(r, g, b);
	}
	
	void diffuseMaterial(int r, int g, int b)
	{
		diffuseMaterial = new Color(r, g, b);
	}
	
	void ambientCoeff(double coeff)
	{
		ambientCoeff = coeff;
	}
	
	void diffuseCoeff(double coeff)
	{
		diffuseCoeff = coeff;
	}
	
	//function computeLightedColor
	//Given the current light settings and normal,
	//computes the Phong difuse lighting equation and returns the color
	public Color computeLightedColor()
	{
		double rgb[] = new double[3];

		for(int i=0; i<3; i++)
		{
			rgb[i] = 
			 ambientCoeff*getRGB(ambientColor,i)*getRGB(ambientMaterial,i)
			 + diffuseCoeff*getRGB(diffuseColor,i)*getRGB(diffuseMaterial,i)
			     * max(0,currentNormal.dot(lightPos));
		}

		return new Color(d2i(rgb[0]), d2i(rgb[1]), d2i(rgb[2]));
	}

	//function isectLineSphere
	//Determines the intersection of a line (not segment) from p1->p2 
	// with a sphere centered at p3 w/ radius r.
	//Returns the closest intersection to the start of the ray
	Vector3 isectLineSphere(Vector3 p1, Vector3 p2, Vector3 p3, double r)
	{
		//Extract fields to make them easier to deal with
		double x1, x2, x3;
		double y1, y2, y3;
		double z1, z2, z3;
		x1 = p1.x();
		x2 = p2.x();
		x3 = p3.x();

		y1 = p1.y();
		y2 = p2.y();
		y3 = p3.y();

		z1 = p1.z();
		z2 = p2.z();
		z3 = p3.z();

		//Substitue the equations of the line "p1 + u*(p2-p1)" 
		//Into the equation of the circle "(x-p3.x)^2 + (y-p3.y)^2 + (z-p3.z)^2 = r"
		//And we get a quadratic form a*u^2 + b*u + c, where a,b,c are the following
		double a, b, c;
		a = sq(x2-x1) + sq(y2-y1) + sq(z2-z1);
		b = 2*( (x2-x1)*(x1-x3) + (y2-y1)*(y1-y3) + (z2-z1)*(z1-z3) );
		c = sq(x3) + sq(y3) + sq(z3) + sq(x1) + sq(y1) + sq(z1) - 2*(x3*x1 + y3*y1 + z3*z1) - sq(r);
		
		//We can solve with the quadratic equation (there may be 2 real solutions)
		//The discriminant (part under the radical in the quad.eq.) will tell us how many.
		double discrim;
		discrim = sq(b) - 4*a*c;
		double sqrt_discrim = Math.sqrt(discrim);

		//If the discriminant is less than zero, there was no collision
		if( discrim < 0 )
			return null;

		//If the discriminant is zero, there is one soln. (w/ multiplicity 2)
		else if( discrim == 0 )
		{
			double u = -b/(2*a);
			return p1.add( (p2.sub(p1)).mul(u) );
		}

		//Otherwise, there are two solutions, pick the one w/ parameter closest to p1
		else
		{
			double u1 = ( -b - sqrt_discrim)  / (2*a);
			double u2 = ( -b + sqrt_discrim)  / (2*a);
			
			//since sqrt_disc is > 0, I think this will always be u1?
			double u = min(u1, u2); 

			return p1.add( (p2.sub(p1)).mul(u) );
		}
	}


	//function counterClockwise
	//Determines if the given polygon (in screen space) has counterclockwise winding
	boolean counterClockwise(Polygon poly)
	{
		//We grab three points, and use the relation of the third to the
		//  line formed by the first two to determine winding
		
		//Specifically, is the determinant of the matrix consisting of the 
		//  second and third vector translated to originate from the first
		//  positive or negative

		//More specifically, is (p1_x-p0_x) * (p2_y-p0_y) > (p2_x-p0_x) * (p1_y-p0_y)
		//	if so, it is ccw
		int x0 = poly.xpoints[0];
		int y0 = poly.ypoints[0];
		
		int x1 = poly.xpoints[1];
		int y1 = poly.ypoints[1];
		
		int x2 = poly.xpoints[2];
		int y2 = poly.ypoints[2];
		
		return (x1-x0)*(y2-y0) > (x2-x0)*(y1-y0); //i really think this is correct

	}

	//Square
	final double sq(double x)
	{
		return x*x;
	}

	//Maximum
	final double max(double a, double b)
	{
		return (a > b ? a : b);
	}

	//Minimum
	final double min(double a, double b)
	{
		return (a < b ? a : b);
	}

	//double color value [0.0,1.0] --> integer color value [0,255]
	final int d2i(double c) 
	{
		return (int)max(0.0, min(255, 255*c));
	}
	
	//extracts an rgb color component, in double [0.0, 1.0]
	final double getRGB(Color color, int i)
	{
		switch(i)
		{
		case 0:
			return color.getRed()/255.0;
		case 1:
			return color.getGreen()/255.0;
		case 2:
			return color.getBlue()/255.0;
		default:
			throw new RuntimeException("Passed a valid color component");
		}
	}
	
	//Current transform matrix, which takes world coord to screen coord
	private Matrix4x4 transformMatrix = new Matrix4x4();

	//Current transform-matrix stack, the previous var points to the head of this
	private Stack transformStack = new Stack();

	//Current Render state; which primitive, if any, is being rendered
	private int renderState = RENDER_NONE;

	//Storage for any polygon-based primitive being submitted
	Polygon polygon = new Polygon();

	//Storage for the last coordinate for a line-based primitive
	private int lineX= 0;
	private int lineY= 0;

	//Counts the number of vertices sent for the current primitive
	private int sentVertices = 0;

	//Current rendering context
	private Graphics graphics = null;

	//Do we cull faces?
	boolean cullPolygon=true;

	//Do we have lighting?
	boolean lighting=false;

	//Size for rendering points
	int pointSize=1;

	//Currently set normal (we only have face normals for now)
	Vector3 currentNormal=new Vector3(0,1,0);

	//Information used for lighting (we have a diffuse lighting model)
	Vector3 lightPos=new Vector3(0,1,0);

	Color ambientColor=new Color(0,64,127);
	Color diffuseColor=new Color(0,64,127);

	Color ambientMaterial=new Color(255,255,255);
	Color diffuseMaterial=new Color(255,255,255);

	double ambientCoeff=0.2;
	double diffuseCoeff=0.6;

}

class GenericApplet extends java.applet.Applet implements Runnable
{
   public boolean damage = true;        // you can force a render
   public void render(Graphics g) { }   // you can define how to render

   private Image image = null;
   private Graphics buffer = null;
   private Thread t;
   private Rectangle r = new Rectangle(0, 0, 0, 0);

   public void start() { if (t == null) { 
	   
	//System.out.println("Got here start");
	   t = new Thread(this); t.start(); } }
   public void stop()  { 
	   
	//System.out.println("Got here stop");
	   if (t != null) { t.stop(); t = null; } }
   public void run()   { 
	   
	//System.out.println("Got here run");
	   try { while (true) { repaint(); t.sleep(30); } }
                             catch(InterruptedException e){}; }

   public void update(Graphics g) {
	//System.out.println("Got here update");
      if (r.width != bounds().width || r.height != bounds().height) {
         image = createImage(bounds().width, bounds().height);
         buffer = image.getGraphics();
         r = bounds();
         damage = true;
      }
      render(buffer);
      damage = false;
      if (image != null)
         g.drawImage(image,0,0,this);
   }
}



