//========================================================
// 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 BoneFrame
{
	double pos[] = new double[3];
	double rot[] = new double[3];
}

class Bone
{
	public Bone()
	{
		for(int i=0; i<Model.MAX_ANIMATIONS; i++)
			frames[i] = new BoneFrame[Model.MAX_FRAMES];
	}
	
	int id;

	//Points to the parent
	Bone parent;

	//Points to the first of this node's children
	Bone children;

	//Points to the next sibling
	Bone sibling;

	//Position for this frame - used in traversal
	Vector3 position = null;
	
	//Holds the sequence of frame information
	//Remember, it is in the parent's local coord system
	//(Keep pushing KM as we go down the tree)
	BoneFrame frames[][] = new BoneFrame[Model.MAX_ANIMATIONS][];

}

class Vertex
{
	Vector3 pos=null;
	Vector3 relPos=null;
	Vector3 nowPos=null;
	Vector3 normal=null;
	Color color=null;
	double[] texCoord = new double[2];
	int bone=-1;
}

class Triangle
{
	int texID;
	Vertex vtx[] = new Vertex[3];
}

class Texture
{
	int id;
	String name;
	
}

class Model
{
	Bone[] bones = new Bone[MAX_BONES];
	Triangle[] tris = new Triangle[MAX_TRIS];
	int frameCount[] = new int[MAX_ANIMATIONS];
	Hashtable textureMap = new Hashtable();
	Bitmap[] textures = new Bitmap[MAX_TEXTURES];

	/*
	reference skeleton;
	animation skeletons;
		bones
			boneframes
	triangles
		vertex triples - no indices);
	*/

	static final int MAX_BONES  = 30;
	static final int MAX_TRIS   = 5000;
	static final int MAX_ANIMATIONS = 7;
	static final int MAX_FRAMES = 40;
	static final int MAX_TEXTURES = 10;
}

class Bitmap
{
	int fileSize;
	int width;
	int height;
	int bitsPerPixel;
	int compression;
	int[] colorTable=null;
	int[] indices=null;
	int[] colors=null;
}

class KM extends Object
{
	Matrix4x4 matrix = new Matrix4x4();
	Stack stack = new Stack();

	void initFrame()
	{
		matrix = new Matrix4x4();
	}

	void push()
	{
		stack.push( matrix );	
	}

	void pop()
	{
		matrix = (Matrix4x4)stack.peek();
		stack.pop();
	}

	void translate(double x,double y,double z)
	{
		matrix = matrix.translate(x,y,z);	
	}

	void rotateX(double theta)
	{
		matrix = matrix.rotateX(theta);
	}

	void rotateY(double theta)
	{
		matrix = matrix.rotateY(theta);
	}

	void rotateZ(double theta)
	{
		matrix = matrix.rotateZ(theta);
	}

	void scale(double x,double y,double z)
	{
		matrix = matrix.scale(x, y, z);
	}

	void transform(double src[], double dst[])
	{
		Vector3 ret;
		ret = matrix.mul(new Vector3(src));
		dst[0] = ret.x();
		dst[1] = ret.y();
		dst[2] = ret.z();
	}

	Vector3 transform(Vector3 src)
	{
		return matrix.mul((src));
	}
	
	Vector3 transform(double src[])
	{
		return matrix.mul(new Vector3(src));
	}
	
	Vector3 untransform(Vector3 src)
	{
		return matrix.inverse().mul(src);
	}
	
	Vector3 untransform(double src[])
	{
		return matrix.inverse().mul(new Vector3(src));
	}
}

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

	static final int MAX_BONES = 50;
	int numBones = 0;
	//private Bone[] bones = new Bone[50];

	//String modelBase = "http://www.cat.nyu.edu/~cdecoro/HW6/";
	//String modelBase = "http://www.cat.nyu.edu/~cdecoro/HW7/eyeball/";
	String modelBase = "/eyeball/";

	//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");
		}
	}

	double degree(double radians)
	{
		return radians * 180 / Math.PI;
	}

	//function readShortLE
	//will read a short value in little endian format, and will then
	//convert into Java bigendian format
	int readShortLE(DataInput in) throws IOException 
	{
		byte a = in.readByte(); //least significant
		byte b = in.readByte(); //most significant

		return (b << 8) | a;
	}

	int readIntLE(DataInput in) throws IOException 
	{
		byte a = in.readByte(); //least significant
		byte b = in.readByte(); 
		byte c = in.readByte(); 
		byte d = in.readByte(); //most significant

		//return (d << 24) &255 | (c << 16) &255 | (b << 8) &255 | a &255;
		return ((d&255) << 24)  | ((c&255) << 16)  | ((b&255) << 8)  | a &255;
	}

	Bitmap readTexture(InputStream stream)
	{
		try
		{
			DataInputStream in = new DataInputStream(stream);
			Bitmap bmp = new Bitmap();
			int datum;

			//Read the file signature (size 2, off 0);
			datum = readShortLE(in);
			if( datum != 19778 ) //BM
				throw new RuntimeException("Bitmpa file signature not found");


			//Read the file size; (size 4, off 2)
			readIntLE(in);

			//Read both reserved fields (size 4, off 6)
			readShortLE(in);
			readShortLE(in);

			//Read data offset (size 4, off 10)
			readIntLE(in);

			//Read INFOHEADER size (size 4, off 14, start of BITMAPINFO header)
			//Color table will be found at 14 + headerSize;
			int headerSize = readIntLE(in);

			//Read width, height (size 8, off 18)
			bmp.width = readIntLE(in);
			bmp.height = readIntLE(in);

			//Read image planes (size 2, off 26)
			readShortLE(in);

			//Read bits per pixel (size 2, off 28)
			bmp.bitsPerPixel = readShortLE(in);
			if( bmp.bitsPerPixel == 8 )
				bmp.colorTable = new int[256];
			else if( bmp.bitsPerPixel == 24 )
				bmp.colorTable = null;
			else
				throw new RuntimeException("Unknown number of colors in pallete");

			//Read image compression, image size (size 8, off 30)
			readIntLE(in);
			readIntLE(in);

			//Read 4 junk fields (size 16, off 38)
			readIntLE(in);
			readIntLE(in);
			readIntLE(in);
			readIntLE(in);

			//We are now at offset 54 , read over the rest of the header
			for(int i=0; i<(14+headerSize)-54; i++)
				in.readByte();

		

			//Finally, we have the image data itself
			//We do different things for palleted vs. full color
			int dataSize = bmp.width*bmp.height;
			bmp.colors = new int[dataSize];
			if( bmp.bitsPerPixel == 8 )
			{
				//Next is the color table - only read if 8bpp, thus 256 in size
				for(int i=0; i<256; i++)
				{
					byte red, green, blue;
					blue = in.readByte();
					green = in.readByte();
					red = in.readByte();
					in.readByte();
					bmp.colorTable[i] = pack(red, green, blue);
				}
		
				bmp.indices = new int[dataSize];
				bmp.colors = new int[dataSize];
				for(int i=0; i<dataSize; i++)
				{
					bmp.indices[i] = in.readByte() & 255;
					bmp.colors[i] = bmp.colorTable[bmp.indices[i]];
				}

			}
			else if( bmp.bitsPerPixel == 24 )
			{
				for(int i=0; i<dataSize; i++)
				{
					byte red, green, blue;
					blue = in.readByte();
					green = in.readByte();
					red = in.readByte();
					in.readByte();
					bmp.colors[i] = pack(red, green, blue);
				}
			}
			else
			{
				throw new RuntimeException("Oops, how did we get here?");
			}

			return bmp;
		} 
		catch( IOException e )
		{
			throw new RuntimeException("Could not read bitmap");		
		}
	}

	//Given a stream, downloads the model, parses it, and fills in vertices[]/faces[]
	void readModel(InputStream stream, Model model, int animation)
	{
		Bone[] bones = model.bones;

		try {
			StreamTokenizer tok = new StreamTokenizer(stream);

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

			//Look for the string "nodes", then read skeleton parts
			while( tok.sval == null || !tok.sval.equals("nodes") )
				tok.nextToken();

			//System.out.println(tok.sval);
			tok.nextToken();

			//Assume we now have nodes - read until "end"
			int boneNumber=0;
			int boneState = 0;
			int boneParent;
			
			for(; tok.sval == null || !tok.sval.equals("end");/* tok.nextToken() */)
			{
				boneNumber = (int)tok.nval;
				tok.nextToken();

				//System.out.println( tok.sval );
				tok.nextToken();

				boneParent = (int)tok.nval;
				tok.nextToken();

				//System.out.println(boneNumber + " " + boneParent);

				//TODO: REMOVE THIS, WE ARE DOING THE SAME THING OVER AND OVER
				
				if( animation == 0 )
				{
					bones[boneNumber] = new Bone();

					bones[boneNumber].id = boneNumber;
					if( boneParent != -1 )
					{
						bones[boneNumber].parent = bones[boneParent];
						bones[boneNumber].sibling = bones[boneParent].children;
						bones[boneParent].children = bones[boneNumber];
					}
				}
			}
			
			//System.out.println( tok.sval );

			//Read the "skeleton"
			int time=-1;
			tok.nextToken();
			tok.nextToken();
			for(; tok.sval == null || !tok.sval.equals("end"); /*tok.nextToken() */)
			{	
				//Each iteraion of this loop is one time frame
				
				//Get the new time
				//System.out.println("getting new time");
				tok.nextToken();
				time = (int)tok.nval;
				tok.nextToken();
	
				//System.out.println("Time: " + time);
				
				while(tok.sval == null || 
						( !tok.sval.equals("time") && 
						!tok.sval.equals("end") ) )
				{
					//Each iteration is one bone record

					boneNumber = (int)tok.nval;
					tok.nextToken();
					
					//System.out.print("reading bone ");
					//System.out.println("Number: " + boneNumber);

					BoneFrame frame =
						bones[boneNumber].frames[animation][time] = new BoneFrame();

					frame.pos[0] = tok.nval;
					tok.nextToken();
					
					frame.pos[1] = tok.nval;
					tok.nextToken();
					
					frame.pos[2] = tok.nval;
					tok.nextToken();
					
					frame.rot[0] = tok.nval;
					tok.nextToken();
					
					frame.rot[1] = tok.nval;
					tok.nextToken();
					
					frame.rot[2] = tok.nval;
					tok.nextToken();

					//System.out.println(frame.rot[0] + " " + frame.rot[1] + " " + frame.rot[2]);
					//System.out.println("Finished");
						
				}
			} //end reading the skeleton

			//Set the frame count in Model
			model.frameCount[animation] = time+1;
			//System.out.println("Animation: " + animation + " Frame Count: " + (time+1) );
				//skel.numFrames = time+1;

			if( animation != 0 )
				return;

			//Pull out the "triangles"
			tok.nextToken();
			//System.out.println(tok.sval);
			tok.nextToken();
			//System.out.println(tok.sval);

			//System.out.println("Reading the triangles");

			int tri=0;
			int nextTexID = 1;
			double[] array = new double[3];
			while(tok.sval == null || !tok.sval.equals("end") )
			{
				//System.out.println(tok.sval);
				String texFile = tok.sval;
				int texID = -1;

				if( model.textureMap.containsKey(texFile) )
				{
					texID = ((Integer)(model.textureMap.get(texFile))).intValue();
				}
				else
				{
					texID = nextTexID++;
					model.textureMap.put(texFile, new Integer(texID));
					model.textures[texID] = 
						readTexture(openModel(modelBase + texFile));
					//readTexture(openModel("http://www.cat.nyu.edu/~cdecoro/HW6/" + texFile));
				}

				tok.nextToken();

				if(tri >= Model.MAX_TRIS)
					break;

				model.tris[tri] = new Triangle();
				model.tris[tri].texID = texID;

				for(int i=0; i<3; i++)
				{

					Vertex vtx = 
						model.tris[tri].vtx[i] = new Vertex();
					/*
					   Vertex vtx=null;
					   try{						
					   vtx = model.tris[tri].vtx[i];
					   } catch (NullPointerException e)
					   {
					   System.out.println("tri = " + tri);
					   System.out.println("vtx = " + vtx);
					   throw e;
					   }
					   */

					vtx.bone = (int)tok.nval;
					tok.nextToken();

					//read the position
					array[0] = tok.nval;	
					tok.nextToken();
					array[1] = tok.nval;	
					tok.nextToken();
					array[2] = tok.nval;	
					tok.nextToken();
					vtx.pos = new Vector3(array);

					//System.out.println(array[0] + " " + array[1] + " " + array[2]);
					//
					//read the normal
					array[0] = tok.nval;	
					tok.nextToken();
					array[1] = tok.nval;	
					tok.nextToken();
					array[2] = tok.nval;	
					tok.nextToken();
					vtx.normal = new Vector3(array);

					//Using the normal, compute the color at the vertex
					normal(vtx.normal);
					vtx.color = computeLightedColor();
					
					//munch the texcoord
					vtx.texCoord[0] = tok.nval;
					tok.nextToken();

					vtx.texCoord[1] = tok.nval;
					tok.nextToken();
				}

				tri++;
			}

			//System.out.println("Tris = " + tri);

			//Run the pretraverse to compute relative positions
			pretraverse(model.bones[0], model, new KM(), 0);
			
			/*
			System.out.println("Tri 1 pos:");
			System.out.println(model.tris[0].vtx[0].nowPos);
			System.out.println(model.tris[0].vtx[1].nowPos);
			System.out.println(model.tris[0].vtx[2].nowPos);
			*/

			/*
			//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");
		}
	}
	
	//This will traverse through the bone structure, and for each vertex connected to the current bone,
	//  it will determine the relative position of that vertex in the bone's coordinate system
	//We currently do an exhaustive search, while we should really sort all the stuff
	int tris = 0;
	void pretraverse(Bone bone, Model model, KM km, int level)
	{

		//base case
		if( bone == null )
			return;


		//this is for the reference animation, which has only 1 frame
		BoneFrame frame = bone.frames[0][0]; 

		//Save existing settings
		km.push();

		//Transform according to this node's settings
		km.translate( frame.pos[0], frame.pos[1], frame.pos[2] );
		km.rotateZ( degree(frame.rot[2]) );
		km.rotateY( degree(frame.rot[1]) ); 
		km.rotateX( degree(frame.rot[0]) );

		for(int i=0; i<Model.MAX_TRIS; i++)
		{
			if( model.tris[i] == null )
				continue;
			
		
			
			for(int j=0; j<3; j++)
			{
				Vertex vtx = model.tris[i].vtx[j];
				if( vtx.bone == bone.id )
				{
					//if( i == 0 )
					//	System.out.println("Hit tris 0");
					tris++;
					vtx.relPos = km.untransform( vtx.pos );
				//	System.out.println(vtx.relPos);
				}
			}
		}
		
		/*
		//Transform according to this node's settings
		km.translate( frame.pos[0], frame.pos[1], frame.pos[2] );
		km.rotateZ( degree(frame.rot[2]) );
		km.rotateY( degree(frame.rot[1]) ); 
		km.rotateX( degree(frame.rot[0]) );
		*/

		//Traverse child
		pretraverse( bone.children, model, km, level+1 );

		//Undo our settings before hitting the siblings
		km.pop();

		//Traverse siblings
		pretraverse( bone.sibling, model, km, level );

		//System.out.println("Vert: " + tris);

	}

	private boolean initialized = false;
	Model theModel = new Model();

	//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), theModel, 0);
		setDamage();
	}
	
	public void drawCube()
	{
		//Draw the front face
		color3i(0, 0, 255);
		begin(RENDER_POLYGON);
		vertex(-1.0, -1.0, 1);
		vertex(+1.0, -1.0, 1);
		vertex(+1.0, +1.0, 1);
		vertex(-1.0, +1.0, 1);
		end();

		//Draw the back face
		color3i(255, 0, 255);
		begin(RENDER_POLYGON);
		vertex(-1.0, +1.0, -1);
		vertex(+1.0, +1.0, -1);
		vertex(+1.0, -1.0, -1);
		vertex(-1.0, -1.0, -1);
		end();

		//Draw the left face
		color3i(127, 0, 255);
		begin(RENDER_POLYGON);
		vertex(-1, -1.0, -1.0);
		vertex(-1, -1.0, +1.0);
		vertex(-1, +1.0, +1.0);
		vertex(-1, +1.0, -1.0);
		end();
		
		//Draw the right face
		color3i(255, 0, 0);
		begin(RENDER_POLYGON);
		vertex(+1, +1.0, -1.0);
		vertex(+1, +1.0, +1.0);
		vertex(+1, -1.0, +1.0);
		vertex(+1, -1.0, -1.0);
		end();

		//Draw the bottom face
		color3i(0, 0, 0);
		begin(RENDER_POLYGON);
		vertex(-1.0, -1, -1.0);
		vertex(+1.0, -1, -1.0);
		vertex(+1.0, -1, +1.0);
		vertex(-1.0, -1, +1.0);
		end();
		
		//Draw the top face
		color3i(0, 255, 0);
		begin(RENDER_POLYGON);
		vertex(-1.0, +1, +1.0);
		vertex(+1.0, +1, +1.0);
		vertex(+1.0, +1, -1.0);
		vertex(-1.0, +1, -1.0);  
		end();
	}

	void traverse(Bone bone, KM km, int animation, int frameNum, int level)
	{
		//base case
		if( bone == null )
			return;

		BoneFrame frame = bone.frames[animation][frameNum];

		//Save existing settings
		km.push();
		pointSize(4);	

		//For debugging
		/*
		for(int i=0; i<level; i++)
			System.out.print("-->");
		System.out.println( bone.id );
		*/

		//Transform according to this node's settings

		//try {
		bone.position = km.transform( new double[]{frame.pos[0],frame.pos[1],frame.pos[2]} );
		/*
		} catch (NullPointerException e)
		{
			System.out.println("Frame #: " + frame);
			System.out.println("Anim #: " + animation);
			System.out.println("Bone #: " + bone.id);
			System.out.println("Frame: " + frame);
			throw e;
		}
		*/
		
		km.translate( frame.pos[0], frame.pos[1], frame.pos[2] );
		km.rotateZ( degree(frame.rot[2]) );
		km.rotateY( degree(frame.rot[1]) ); //pretty good with pos, neg, pos
		km.rotateX( degree(frame.rot[0]) );
		
		color3i(255, 0, 0);
		
		begin(RENDER_POINTS);
		//vertex( bone.position );
		end();
			
		switch(bone.id)
		{
			case 2:
			case 3:
			case 4:
			case 14:
			case 15:
			case 16:
			case 17:
				color3i(255, 0, 0);
				break;
			case 5:
			case 6:
			case 7:
			case 18:
			case 19:
			case 20:
			case 21:
				color3i(0, 0, 255);
				break;

			default:
				color3i(0, 255, 0);
				break;
		}
		
		//color3i(0, 255, 0);
		begin(RENDER_LINES);
		if( bone.id != 0 )
		{
			//vertex( bone.parent.position  );
			//vertex( bone.position );
		}
		end();
		
		//compute current pos for each vertex using bone.position and vert.relpos
		// to do this, we transform with the km
		//We cannot render yet, because we must transform all points of tri before render
		// the points may be connected to different bones
		for(int i=0; i<Model.MAX_TRIS; i++)
		{
			if( theModel.tris[i] == null )
				continue;

			for(int j=0; j<3; j++)
			{
				Vertex vtx = theModel.tris[i].vtx[j];
				if( vtx.bone != bone.id )
					continue;

				//if( i == 0 )
				//	System.out.println("Hit tris 0");
				vtx.nowPos = km.transform(vtx.relPos);	
			}
		}
		
		//Traverse child
		traverse( bone.children, km, animation, frameNum, level+1 );

		//Undo our settings before hitting the siblings
		km.pop();

		//Traverse siblings
		traverse( bone.sibling, km, animation, frameNum, level );
		
	}

	Bitmap bmp;

	//Renders the scene
	public void render() 
	{
		//Graphics g = im.getGraphics();

		//On the first run, initialize using the following code
		if(!initialized)
		{
			ambientCoeff(0.8);   //0.3 , 0.7 also looked good
			diffuseCoeff(0.75);
			ambientAndDiffuseMaterial(201, 125, 73);
			ambientAndDiffuseColor(255, 255, 255);

			initialized = true;
			//readModel(openModel("http://cat.nyu.edu/~cdecoro/HW6/run2.smd"));
			//readModel(openModel("http://cat.nyu.edu/~cdecoro/HW6/ref.smd"), theModel, 0);
			//readModel(openModel("http://cat.nyu.edu/~cdecoro/HW6/run2.smd"), theModel, 1);
			//readModel(openModel("http://cat.nyu.edu/~cdecoro/HW6/swim.smd"), theModel, 2);
			//readModel(openModel("http://cat.nyu.edu/~cdecoro/HW6/crawl.smd"), theModel, 3);
			//readModel(openModel("http://cat.nyu.edu/~cdecoro/HW6/jump.smd"), theModel, 4);
			//readModel(openModel("http://cat.nyu.edu/~cdecoro/HW6/long_jump.smd"), theModel, 5);
			//readModel(openModel("http://cat.nyu.edu/~cdecoro/HW6/die_backwards.smd"), theModel, 6);
			
			modelBase = getDocumentBase() + modelBase;
			readModel(openModel(modelBase + "ref.smd"), theModel, 0);
			readModel(openModel(modelBase + "run2.smd"), theModel, 1);
			readModel(openModel(modelBase + "swim.smd"), theModel, 2);
			readModel(openModel(modelBase + "crawl.smd"), theModel, 3);
			readModel(openModel(modelBase + "jump.smd"), theModel, 4);
			readModel(openModel(modelBase + "long_jump.smd"), theModel, 5);
			readModel(openModel(modelBase + "die_backwards.smd"), theModel, 6);

			//bmp = readTexture(openModel("http://cat.nyu.edu/~cdecoro/HW6/body.bmp"));

			//System.out.println(getParameter("model"));
			
	

			for(int j=0; j<=6; j++)
			{
				for(int i=0; i<theModel.frameCount[j]; i++)
				{
					theModel.bones[0].frames[j][i].pos[0] = 0.0;
					theModel.bones[0].frames[j][i].pos[1] = 0.0;
					theModel.bones[0].frames[j][i].pos[2] = 0.0;
				}
			}

		}
		
		//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())
			identity();
			/*
			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);
	

			//Stop gap measure until we fix that
			clear();

			//Draw the bitmap directly to the screen
			/*
			for(int i=0; i<bmp.height; i++)
			{
				for(int j=0; j<bmp.width; j++)
				{
					pix[xy2i(i,j)] = bmp.colors[i*bmp.width+j];
				}
			}
			*/

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

			identity();

			/*
			transform( (new Matrix4x4())
					.scale(40, 40, -40)
					.leftmul(rotationMatrix)		
					.scale(scaleX, scaleY, 0)
					.translate(translateX+hw,translateY+hh,0)   );
			*/
				
			transform( (new Matrix4x4())
					.translate(translateX+hw, translateY+hh,0)
					.scale(scaleX, scaleY, 1)
					.mul(rotationMatrix)
					.scale(40, 40, /*-*/ 40)    );
					
			
			//Draw a plain old box model
			/*
			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();
			*/

			KM thiskm = new KM();
			thiskm.initFrame();
		
			int anim = theAnimation;
			long fr = (long)(System.currentTimeMillis() / 50) % theModel.frameCount[anim];
			//long fr = (long)(System.currentTimeMillis() / 50) % 23;
			traverse(theModel.bones[0], thiskm, anim, (int)fr, 0);
			//traverse(theModel.bones[0], thiskm, anim, (int)fr, 0);

			//traverse(theModel.bones[0], thiskm, 0, 0, 0);

			if(cullFace)
				enable(RENDER_CULL_FACE);
			else
				disable(RENDER_CULL_FACE);

			if(doLighting)
				enable(RENDER_LIGHTING);
			else
				disable(RENDER_LIGHTING);

			enable(RENDER_TEXTURE);

			if( !skeletonOnly ) 
			{
				//Lets render the mesh itself
				if(wire)
					begin(RENDER_OPEN_TRIANGLE);
				else
					begin(RENDER_TRIANGLE);

				for(int i=0; i<Model.MAX_TRIS; i++)
				{
					if(theModel.tris[i] == null)
					{
						break;
					}

					Triangle tri = theModel.tris[i];
					texture( theModel.textures[tri.texID] );
		
					for(int j=0; j<3; j++)
					{
						texCoord( tri.vtx[j].texCoord[0], tri.vtx[j].texCoord[1] );
						color( tri.vtx[j].color );
						normal( tri.vtx[j].normal );
						vertex( tri.vtx[j].nowPos );
					}
				
					//vertex( tri.vtx[1].nowPos );
					//vertex( tri.vtx[2].nowPos );
				}
				end();
			}

			//Clear out the context, ending the frame
			setContext(null);
			damage = true;
		}
	}

	//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);
				rotationMatrix = (new Matrix4x4()).rotate(rotationAxis, rotationTheta).mul(oldRotationMatrix);
			}
		}
		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);
			rotationMatrix = (new Matrix4x4()).rotateZ(angle).mul(oldRotationMatrix);

		}
		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;
	boolean wire=false;
	boolean doLighting=true;
	int selectedMode = 0;
	int theAnimation = 1;
	boolean skeletonOnly = false;

	static final int ANIMATION_COUNT = 7;

	//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 'w':
			wire = !wire;
			break;
		case 's':
			skeletonOnly = !skeletonOnly;
			break;
		case 'l':
			doLighting = !doLighting;
			break;
		case ' ':
			theAnimation = (theAnimation+1)	% ANIMATION_COUNT;
			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 scaleX=0.32, scaleY=0.32;
	double scaleX=0.2, scaleY=0.2;
	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 rotationMatrix=new Matrix4x4(0.208, .997, -.009, 0,
											.0026, -.0101, -.999, 0,
											-.978, .208,  -.0046, 0,
											0, 0, 0, 1);
	Matrix4x4 oldRotationMatrix=new Matrix4x4();

	public static void main(String[] args)
	{
		Frame frame = new Frame("Kinematics");
		Kinematics pnl = new Kinematics();	
		pnl.setSize(900, 900);
		frame.setSize(900, 900);
		frame.add(pnl);
		frame.show();
		
		frame.addWindowListener(
			new WindowAdapter()
		{
			public void windowClosing(WindowEvent e)
			{
				e.getWindow().dispose();
				System.exit(0);
			}
		}
			);

		pnl.init();
		pnl.start();
	}
}

class ThreeDimensionalApplet extends PixApplet
{
	int x = 100, y = 100;
	int coords[][] = new int[3][];
	double normals[][] = new double[3][];
	int colors[][] = new int[3][];
	int currentColor[] = new int[3];
	int texCoords[][] = new int[3][];


	ThreeDimensionalApplet()
	{
		for(int i=0; i<coords.length; i++)
		{
			coords[i] = new int[3];
			normals[i] = new double[3];
			colors[i] = new int[3];
			texCoords[i] = new int[2];
		}
	}

	//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));
		currentColor[0] = r;
		currentColor[1] = g;
		currentColor[2] = b;

		colors[sentVertices][0] = r;
		colors[sentVertices][1] = g;
		colors[sentVertices][2] = 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 = false;
		int screenX, screenY;
		int screenZ;

		//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];
			screenZ = (int)transCoord[2];
		}
		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();
			
			double doubleZ = screenCoord.z();

			screenZ = (int)( Integer.MAX_VALUE*((doubleZ - near)/(far - near)) );
			//screenZ = (int)screenCoord.z();
		}

		//lighting = true;
		if( !lighting )
		{
			colors[sentVertices][0] = currentColor[0];
			colors[sentVertices][1] = currentColor[1];
			colors[sentVertices][2] = currentColor[2];
		}
		else
		{
			Color col = computeLightedColor();
			colors[sentVertices][0] = col.getRed();
			colors[sentVertices][1] = col.getGreen();
			colors[sentVertices][2] = col.getBlue();
		}

		//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);

			//coords[sentVertices] = new Vector3(screenX, screenY, screenZ);
			coords[sentVertices][0] = screenX;
			coords[sentVertices][1] = screenY;
			coords[sentVertices][2] = screenZ;

			sentVertices++;
		}

		//Draw a triangle on every third vertex
		if( renderState == RENDER_TRIANGLE && sentVertices == 3 )
		{
			//Perform back face culling
			if(!cullPolygon || counterClockwise(coords))
			{
				//if(lighting) graphics.setColor(computeLightedColor());
				//graphics.fillPolygon(polygon);
				
				/*
				drawTriangle(true, 
					polygon.xpoints[0], polygon.ypoints[0], 0, 0, 0, 0, 0, 0,
					polygon.xpoints[1], polygon.ypoints[1], 0, 0, 0,0, 0, 0, 
					polygon.xpoints[2], polygon.ypoints[2], 0, 0, 0, 0, 0, 0);
				*/
				
				drawTriangle(true, true,
					coords[0][0], coords[0][1], coords[0][2], colors[0][0], colors[0][1], colors[0][2], texCoords[0][0], texCoords[0][1],
					coords[1][0], coords[1][1], coords[1][2], colors[1][0], colors[1][1], colors[1][2], texCoords[1][0], texCoords[1][1],
					coords[2][0], coords[2][1], coords[2][2], colors[2][0], colors[2][1], colors[2][2], texCoords[2][0], texCoords[2][1] );
			
			}
			
			polygon = new Polygon();
			sentVertices = 0;
		}
		else if( renderState == RENDER_OPEN_TRIANGLE && sentVertices == 3 )
		{
			//perform back face culling
			if(!cullPolygon || counterClockwise(coords))
			{
				//if(lighting) graphics.setColor(computeLightedColor());
				//graphics.drawPolygon(polygon);
				
				drawTriangle(false, false,
					coords[0][0], coords[0][1], coords[0][2], colors[0][0], colors[0][1], colors[0][2], 0, 0,
					coords[1][0], coords[1][1], coords[1][2], colors[1][0], colors[1][1], colors[1][2], 0, 0,
					coords[2][0], coords[2][1], coords[2][2], colors[2][0], colors[2][1], colors[2][2], 0, 0 );
				/*
				drawTriangle(false, 
					polygon.xpoints[0], polygon.ypoints[0], 0, 0, 0, 0, 0, 0,
					polygon.xpoints[1], polygon.ypoints[1], 0, 0, 0,0, 0, 0, 
					polygon.xpoints[2], polygon.ypoints[2], 0, 0, 0, 0, 0, 0);
					*/

			}

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

	//function identity
	void identity()
	{
		transformMatrix = new Matrix4x4();
	}

	//function transform(Matrix)
	//Sets the current modelview transform matrix
	//Essentially equivalent to glLoadMatrix()
	void transform(Matrix4x4 mat)
	{
		//transformMatrix = mat.mul(transformMatrix);
		transformMatrix = transformMatrix.mul(mat);
		//System.out.println(mat);
		
		//We want to append this matrix onto the end of the chain
		//Therefore out = in * 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 );
	}

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

	//function ortho
	//Sets up an orthoganl projection
	//Right now, only the near and far args have effect
	void ortho(double _near, double _far)
	{
		near = _near;
		far = _far;
		zInterval = (far - near) / (Integer.MAX_VALUE / 2);
	}

	double near=-1000;
	double far=1000;
	double zInterval = (far - near) / (Integer.MAX_VALUE / 2);

	//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;
	static final int RENDER_TEXTURE=258;

	void enable(int param)
	{
		switch(param)
		{
		case RENDER_CULL_FACE:
			cullPolygon = true;
			break;
		case RENDER_LIGHTING:
			lighting = true;
			break;
		case RENDER_TEXTURE:
			texturing = 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;
		case RENDER_TEXTURE:
			texturing = 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 texture
	//Sets the currently used texture - texturing must be enabled seperatly
	void texture(Bitmap bmp)
	{
		currentTexture = bmp;
	}

	void texCoord(double s, double t)
	{
		texCoords[sentVertices][0] = (int)(s*currentTexture.width);
		texCoords[sentVertices][1] = (int)(t*currentTexture.height);
	}

	//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(int 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];
		*/

		int x0 = (int)poly[0][0];
		int y0 = (int)poly[0][1];

		int x1 = (int)poly[1][0];
		int y1 = (int)poly[1][1];

		int x2 = (int)poly[2][0];
		int y2 = (int)poly[2][1];

		//This way seems to work, though it is the opposite of what we had before
		return (x1-x0)*(y2-y0) < (x2-x0)*(y1-y0); 

	}

	int rgb[] = new int[3];

	void drawTriangle(
		boolean filled, boolean textured,
		int x1, int y1, int z1, int r1, int g1, int b1, int s1, int t1,
		int x2, int y2, int z2, int r2, int g2, int b2, int s2, int t2,
		int x3, int y3, int z3, int r3, int g3, int b3, int s3, int t3)
	{
		int pix[] = this.pix;
		int zbuffer[] = this.zbuffer;

		int xlow, xmid, xhigh;
		int ylow, ymid, yhigh;
		int zlow, zmid, zhigh;
		int rlow, rmid, rhigh;
		int glow, gmid, ghigh;
		int blow, bmid, bhigh;
		int slow, smid, shigh;
		int tlow, tmid, thigh;

		if( x1 < 0 && x2 < 0 && x3 < 0 )
			return;
		if( y1 < 0 && y2 < 0 && y3 < 0 )
			return;

		if( x1 < x2 )
		{
			if( x1 < x3 ) 
			{ 
				if( x2 < x3 ) //1,2, 3
				{
					xlow = x1;
					ylow = y1;
					zlow = z1;
					rlow = r1;
					glow = g1;
					blow = b1;
					slow = s1;
					tlow = t1;

					xmid = x2;
					ymid = y2;
					zmid = z2;
					rmid = r2;
					gmid = g2;
					bmid = b2;
					smid = s2;
					tmid = t2;

					xhigh = x3;
					yhigh = y3;
					zhigh = z3;
					rhigh = r3;
					ghigh = g3;
					bhigh = b3;
					shigh = s3;
					thigh = t3;
				}
				else //1, 3, 2
				{
					xlow = x1;
					ylow = y1;
					zlow = z1;
					rlow = r1;
					glow = g1;
					blow = b1;
					slow = s1;
					tlow = t1;

					xmid = x3;
					ymid = y3;
					zmid = z3;
					rmid = r3;
					gmid = g3;
					bmid = b3;
					smid = s3;
					tmid = t3;

					xhigh = x2;
					yhigh = y2;
					zhigh = z2;
					rhigh = r2;
					ghigh = g2;
					bhigh = b2;
					shigh = s2;
					thigh = t2;
				}
			}
			else //order is 3, 1, 2
			{
				xlow = x3;
				ylow = y3;
				zlow = z3;
				rlow = r3;
				glow = g3;
				blow = b3;
				slow = s3;
				tlow = t3;

				xmid = x1;
				ymid = y1;
				zmid = z1;
				rmid = r1;
				gmid = g1;
				bmid = b1;
				smid = s1;
				tmid = t1;

				xhigh = x2;
				yhigh = y2;
				zhigh = z2;
				rhigh = r2;
				ghigh = g2;
				bhigh = b2;
				shigh = s2;
				thigh = t2;
			}
		}
		else // y2 < y1
		{
			if( x1 < x3 ) //order is 2, 1, 3
			{
				xlow = x2;
				ylow = y2;
				zlow = z2;
				rlow = r2;
				glow = g2;
				blow = b2;
				slow = s2;
				tlow = t2;

				xmid = x1;
				ymid = y1;
				zmid = z1;
				rmid = r1;
				gmid = g1;
				bmid = b1;
				smid = s1;
				tmid = t1;

				xhigh = x3;
				yhigh = y3;
				zhigh = z3;
				rhigh = r3;
				ghigh = g3;
				bhigh = b3;
				shigh = s3;
				thigh = t3;
			}
			else
			{
				if( x2 < x3 ) //2, 3, 1
				{
					xlow = x2;
					ylow = y2;
					zlow = z2;
					rlow = r2;
					glow = g2;
					blow = b2;
					slow = s2;
					tlow = t2;

					xmid = x3;
					ymid = y3;
					zmid = z3;
					rmid = r3;
					gmid = g3;
					bmid = b3;
					smid = s3;
					tmid = t3;

					xhigh = x1;
					yhigh = y1;
					zhigh = z1;
					rhigh = r1;
					ghigh = g1;
					bhigh = b1;
					shigh = s1;
					thigh = t1;	
				}		
				else //3, 2, 1
				{
					xlow = x3;
					ylow = y3;
					zlow = z3;
					rlow = r3;
					glow = g3;
					blow = b3;
					slow = s3;
					tlow = t3;

					xmid = x2;
					ymid = y2;
					zmid = z2;
					rmid = r2;
					gmid = g2;
					bmid = b2;
					smid = s2;
					tmid = t2;

					xhigh = x1;
					yhigh = y1;
					zhigh = z1;
					rhigh = r1;
					ghigh = g1;
					bhigh = b1;
					shigh = s1;
					thigh = t1;
				}
			}
		}

		//======================================================================
		// We have sorted, now draw the first triangle
		//======================================================================

		double runAB = xmid - xlow;
		double runBC = xhigh - xmid;
		double runAC = xhigh - xlow;

		//Compute the slope of the upper line segment
		//Also compute the slopes of r,g,b on this segment
		//The key is that we want all dX1 to be on the upper line, 
		//	and dX2 on the lower line
		double dy1, dz1, dr1, dg1, db1, ds1, dt1;
		double dy2, dz2, dr2, dg2, db2, ds2, dt2;

		dy1 = (ymid - ylow) / runAB;
		dy2 = (yhigh - ylow) / runAC;

		//If the above holds, then it will be the dX1 that will hit the other vtx,
		//	and that will have to change
		int changes = 1;

		if( dy1 > dy2 )
		{
			//if dy1 > dy2, all is good, assign the others
			//This means that the line from the low to mid is above line from low to high
			//dy1 = (ymid - ylow) / runAB;
			dz1 = (zmid - zlow) / runAB;
			dr1 = (rmid - rlow) / runAB;
			dg1 = (gmid - glow) / runAB;
			db1 = (bmid - blow) / runAB;
			ds1 = (smid - slow) / runAB;
			dt1 = (tmid - tlow) / runAB;

			//then compute the slopes for the lower segment
			//dy2 = (yhigh - ylow) / runAC;
			dz2 = (zhigh - zlow) / runAC;
			dr2 = (rhigh - rlow) / runAC;
			dg2 = (ghigh - glow) / runAC;
			db2 = (bhigh - blow) / runAC;
			ds2 = (shigh - slow) / runAC;
			dt2 = (thigh - tlow) / runAC;
		}
		else
		{
			//If dy1 is not > dy2, this is bad.  Swap and then assign stuff
			dy2 = (ymid - ylow) / runAB;
			dz2 = (zmid - zlow) / runAB;
			dr2 = (rmid - rlow) / runAB;
			dg2 = (gmid - glow) / runAB;
			db2 = (bmid - blow) / runAB;
			ds2 = (smid - slow) / runAB;
			dt2 = (tmid - tlow) / runAB;

			dy1 = (yhigh - ylow) / runAC;
			dz1 = (zhigh - zlow) / runAC;
			dr1 = (rhigh - rlow) / runAC;
			dg1 = (ghigh - glow) / runAC;
			db1 = (bhigh - blow) / runAC;
			ds1 = (shigh - slow) / runAC;
			dt1 = (thigh - tlow) / runAC;

			//Now, the other path will hit the new vtx
			changes = 2;
		}

		//Draw the left triangle
		double yUp=ylow, yDown=ylow;
		double zUp=zlow, zDown=zlow;
		double rUp=rlow, rDown=rlow;
		double gUp=glow, gDown=glow;
		double bUp=blow, bDown=blow;
		double sUp=slow, sDown=slow;
		double tUp=tlow, tDown=tlow;

		/*
		drawHalfTriangle(filled, xlow, xmid, 
			yUp, yDown, dy1, dy2, 
			zUp, zDown, dz1, dz2,
			rUp, rDown, dr1, dr2,
			gUp, gDown, dg1, dg2,
			bUp, bDown, db1, db2 );
		*/		
		
		int texHeight = currentTexture.height;
		int texWidth = currentTexture.width;
		int colors[] = currentTexture.colors;

		//try
		//{

			for(int i=(int)xlow; i<(int)xmid; i++)
			{
				double ddz, ddr, ddg, ddb, dds, ddt;
		
				if( yUp - yDown != 0 )
				{
					ddz = (zUp - zDown) / (yUp - yDown);
					ddr = (rUp - rDown) / (yUp - yDown);
					ddg = (gUp - gDown) / (yUp - yDown);
					ddb = (bUp - bDown) / (yUp - yDown);
					dds = (sUp - sDown) / (yUp - yDown);
					ddt = (tUp - tDown) / (yUp - yDown);

					double zNow=zDown, rNow=rDown, gNow=gDown, bNow=bDown;
					double sNow=sDown, tNow=tDown;

					if( filled )
					{
						for(int j=(int)yDown; j<(int)yUp+1; j++)
						{
							//Draw the stuff here
							//try 
							//{
							int idx = xy2i(i,j);
							int texIdx = (int)tNow*texWidth + (int)sNow;
							if( texIdx < 0 ) texIdx = 0;
							if( texIdx >= texWidth*texHeight ) texIdx = 0;

							//int zInt = (int)Math.round(zNow);
							int zInt = (int)(zNow);
							if( zbuffer[idx] > zInt )
							{
								//pix[idx] = pack((int)rNow, (int)gNow, (int)bNow);
								//pix[idx] = (colors[texIdx]*rNow)>>1;
								//unpack(rgb, colors[texIdx]);
								unpack(rgb, colors[texIdx]);
								pix[idx] = pack((int)(rNow * rgb[0]/255.0),
									(int)(gNow * rgb[1]/255.0),
									(int)(bNow * rgb[2]/255.0));
								zbuffer[idx] = zInt;
							}
							//} 
							//catch( ArrayIndexOutOfBoundsException e ) { return; }
							//End draw

							zNow += ddz;
							rNow += ddr;
							gNow += ddg;
							bNow += ddb;
							sNow += dds;
							tNow += ddt;
						}
					}
					else //wire framed
					{
						//try
						//{
						pix[xy2i(i,(int)yUp)] = pack((int)rUp, (int)gUp, (int)bUp);
						pix[xy2i(i,(int)yDown)] = pack((int)rDown, (int)gDown, (int)bDown);
						//} 
						//catch( ArrayIndexOutOfBoundsException e ) { return; }
					}
				}

				yUp += dy1;  
				yDown += dy2;

				zUp += dz1;
				zDown += dz2;

				rUp += dr1;
				rDown += dr2;
			
				gUp += dg1;
				gDown += dg2;
			
				bUp += db1;
				bDown += db2;

				sUp += ds1;
				sDown += ds2;

				tUp += dt1;
				tDown += dt2;
			}

			if( xlow == xmid )
			{
				if( ylow > ymid )
				{
					yUp = ylow;
					rUp = rlow;
					gUp = glow;
					bUp = blow;
					sUp = slow;
					tUp = tlow;

					yDown = ymid;
					rDown = rmid;
					gDown = gmid;
					bDown = bmid;
					sDown = smid;
					tDown = tmid;
				}
				else
				{
					yDown = ylow;
					rDown = rlow;
					gDown = glow;
					bDown = blow;
					sDown = slow;
					tDown = tlow;

					yUp = ymid;
					rUp = rmid;
					gUp = gmid;
					bUp = bmid;
					sUp = smid;
					tUp = tmid;
				}
			}

			//=======================================================
			// We have now drawn the first triangle, draw second
			//=======================================================

			//If the runBC is zero, we dont have a second triangle to draw
	
			//We know that the line from (yUp->end) is above line (yDown->end)
			//This eliminates some of the extra tests
			//For one of the two lines, we just continue as before
			//For the other, we have the new vertex to deal with

			if( changes == 1 )
			{
				dy1 = (yhigh - ymid) / runBC;
				dz1 = (zhigh - zmid) / runBC;
				dr1 = (rhigh - rmid) / runBC;
				dg1 = (ghigh - gmid) / runBC;
				db1 = (bhigh - bmid) / runBC;
				ds1 = (shigh - smid) / runBC;
				dt1 = (thigh - tmid) / runBC;

			}
			else
			{
				//then compute the slopes for the lower segment
				dy2 = (yhigh - ymid) / runBC;
				dz2 = (zhigh - zmid) / runBC;
				dr2 = (rhigh - rmid) / runBC;
				dg2 = (ghigh - gmid) / runBC;
				db2 = (bhigh - bmid) / runBC;
				ds2 = (shigh - smid) / runBC;
				dt2 = (thigh - tmid) / runBC;
			}

			//Draw the right triangle
			//yUp, yDown, etc. stay as they were - we have changed the slope

			/*
			 drawHalfTriangle(filled, xmid, xhigh, 
			 yUp, yDown, dy1, dy2, 
			 zUp, zDown, dz1, dz2,
			 rUp, rDown, dr1, dr2,
			 gUp, gDown, dg1, dg2,
			 bUp, bDown, db1, db2 );
			 */

		
			for(int i=(int)xmid; i<(int)xhigh+1; i++)
			{
				double ddz, ddr, ddg, ddb, dds, ddt;
		
				if( yUp - yDown != 0 )
				{
					ddz = (zUp - zDown) / (yUp - yDown);
					ddr = (rUp - rDown) / (yUp - yDown);
					ddg = (gUp - gDown) / (yUp - yDown);
					ddb = (bUp - bDown) / (yUp - yDown);
					dds = (sUp - sDown) / (yUp - yDown);
					ddt = (tUp - tDown) / (yUp - yDown);

					double zNow=zDown, rNow=rDown, gNow=gDown, bNow=bDown, sNow=sDown, tNow=tDown;
					if( filled )
					{
						for(int j=(int)yDown; j<(int)yUp+1; j++)
						{
							//Draw the stuff here
							//try 
							//{
							int idx = xy2i(i,j);
							int zInt = (int)Math.round(zNow);
							int texIdx = (int)tNow*texWidth + (int)sNow;
							if( texIdx < 0 ) texIdx = 0;
							if( texIdx >= texWidth*texHeight ) texIdx = 0;
							if( zbuffer[idx] > zInt )
							{
								//pix[idx] = pack((int)rNow, (int)gNow, (int)bNow);
								//pix[idx] = colors[texIdx];
								unpack(rgb, colors[texIdx]);
								pix[idx] = pack((int)(rNow * rgb[0]/255.0),
									(int)(gNow * rgb[1]/255.0),
									(int)(bNow * rgb[2]/255.0));
								zbuffer[idx] = zInt;
							}
							//} 
							//catch( ArrayIndexOutOfBoundsException e ) { return; }
							//End draw

							zNow += ddz;
							rNow += ddr;
							gNow += ddg;
							bNow += ddb;
							sNow += dds;
							tNow += ddt;
						}
					}
					else //wire framed
					{
						//try
						//{
						pix[xy2i(i,(int)yUp)] = pack((int)rUp, (int)gUp, (int)bUp);
						pix[xy2i(i,(int)yDown)] = pack((int)rDown, (int)gDown, (int)bDown);
						//} 
						//catch( ArrayIndexOutOfBoundsException e ) { return; }
					}
				}

				yUp += dy1;  
				yDown += dy2;

				zUp += dz1;
				zDown += dz2;

				rUp += dr1;
				rDown += dr2;
			
				gUp += dg1;
				gDown += dg2;
			
				bUp += db1;
				bDown += db2;

				sUp += ds1;
				sDown += ds2;

				tUp += dt1;
				tDown += dt2;
			}
		//}
		//catch( ArrayIndexOutOfBoundsException e ) 
		//{ 
		//	return; 
		//};


	}

	void clear()
	{
		int[] pix = this.pix;
		int value = pack(200,200,200);
		int zvalue = Integer.MAX_VALUE;
		for(int i=0; i<H*W; i++)
		{
			pix[i] = value;
			zbuffer[i] = zvalue;
		}
	}

	void drawHalfTriangle(boolean filled, int begin, int end,
		double yUp, double yDown, double dy1, double dy2,
		double zUp, double zDown, double dz1, double dz2,
		double rUp, double rDown, double dr1, double dr2,
		double gUp, double gDown, double dg1, double dg2,
		double bUp, double bDown, double db1, double db2 )
	{
		int[] pix = this.pix;

		for(int i=(int)begin; i<(int)end; i++)
		{
			double ddz, ddr, ddg, ddb;
		
			if( yUp - yDown != 0 )
			{
				ddz = (zUp - zDown) / (yUp - yDown);
				ddr = (rUp - rDown) / (yUp - yDown);
				ddg = (gUp - gDown) / (yUp - yDown);
				ddb = (bUp - bDown) / (yUp - yDown);

				double zNow=zDown, rNow=rDown, gNow=gDown, bNow=bDown;
				if( filled )
				{
					for(int j=(int)yDown; j<(int)yUp; j++)
					{
						//Draw the stuff here
						try 
						{
							int idx = xy2i(i,j);
							//if( zbuffer[idx] > zNow )
							//{
							pix[idx] = pack((int)rNow, (int)gNow, (int)bNow);
							//zbuffer[idx] = zNow;
							//}
						} 
						catch( ArrayIndexOutOfBoundsException e ) { return; }
						//End draw

						zNow += ddz;
						rNow += ddr;
						gNow += ddg;
						bNow += ddb;
					}
				}
				else //wire framed
				{
					try
					{
						pix[xy2i(i,(int)yUp)] = pack((int)rUp, (int)gUp, (int)bUp);
						pix[xy2i(i,(int)yDown)] = pack((int)rDown, (int)gDown, (int)bDown);
					} 
					catch( ArrayIndexOutOfBoundsException e ) { return; }
				}
			}

			yUp += dy1;  
			yDown += dy2;

			zUp += dz1;
			zDown += dz2;

			rUp += dr1;
			rDown += dr2;
			
			gUp += dg1;
			gDown += dg2;
			
			bUp += db1;
			bDown += db2;
		}

	}

	void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3)
	{
		drawTriangle(true, false,
			x1, y1, 0, 255, 0, 0, 0, 0,
			x2, y2, 0, 0, 255, 0, 0, 0, 
			x3, y3, 0, 0, 0, 255, 0, 0
			);
	}

	public void render()
	{
		clear();

		drawTriangle(10, 10, 60, 10, 30, 30);

		//No side aligned
		drawTriangle(30, 60, 10, 100, 50, 50);
 
		drawTriangle(300, 300, 500, 400, 560, 50);

		//Left side y aligned
		drawTriangle(10, 100, 10, 150, 50, 120);
	}

	//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;
	
	//Do we have texturing?
	boolean texturing=false;
	
	//The currently assigned texture
	Bitmap currentTexture=null;

	//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);
   }
}



