import java.awt.*;
import java.util.*;
import java.lang.Math;

public class Bleep extends BufferedApplet
{
	final int ROTATE_X = 0;
	final int ROTATE_Y = 1;
	final int ROTATE_Z = 2;
	
	final int STACKSIZE = 100;
	
	final int SEGMENT_LENGTH = 35;
	final int ANTENNA_LENGTH = 75;
	final int ARM_SECTION_LENGTH = 60;
	
	final int MAX_SEGMENTS = 6;
	final int IRIS_STATES = 4;
	
	final int LINES_OFF = 0;
	final int LINES_ON = 1;
	final int LINES_MESH = 2;
	
	final double EPSILON = 1.0/20.0;
	final int EPSILON_INV = 20;	
		
	ParamSphere SphereHead;
	ParamSphere SphereTorsoUpper;
	ParamSphere SphereTorsoLower;
	
	ParamSphere SphereEye;
	ParamSphere SphereEyelid;
	ParamSphere SphereEyeIris;
	ParamSphere SphereEyeLeft;
	ParamSphere SphereEyelidLeft;
	ParamSphere SphereEyeIrisLeft;
	ParamSphere SphereEyeRight;
	ParamSphere SphereEyelidRight;
	ParamSphere SphereEyeIrisRight;
	
	ParamSphere SphereAntennaLeft;
	ParamCone ConeAntennaLeft;
	ParamSphere SphereAntennaRight;
	ParamCone ConeAntennaRight;
	
	ParamCylinder ArmLeftUpper;
	ParamCylinder ArmLeftLower;
	ParamSphere SphereHandLeft;
	ParamSphere SphereShoulderLeft;
	ParamSphere SphereElbowLeft;
	
	ParamCylinder ArmRightUpper;
	ParamCylinder ArmRightLower;
	ParamSphere SphereHandRight;
	ParamSphere SphereShoulderRight;
	ParamSphere SphereElbowRight;
	
	ParamConeTapered Tail[] = new ParamConeTapered[MAX_SEGMENTS];
	
	int iWidth, iHeight, mTop, iState, iTick;
	int Stars[][] = new int[200][3];
	double mStack[][][] = new double[STACKSIZE][4][4];
	Matrix3D Matrix3D = new Matrix3D();
	Matrix3D_FK Matrix3D_FK = new Matrix3D_FK();
	
	Color clrSkin = new Color(146,39, 143);
	
	boolean bIsInited = false;
	
	Button BtnLines;
	Slider SldEyelid, SldEyeIris;
	Slider SldArmsX, SldArmsY, SldArmsZ;
	public Random rand = new Random();

	public void render (Graphics g)
	{
		if (bIsInited == false)
		{
			iWidth = bounds().width;
			iHeight = bounds().height;
			mTop = 0;

			for (int i=0; i<200; i++)
				//generates a random starfield
			{
				Stars[i][0] = rand.nextInt(iWidth);
				Stars[i][1] = rand.nextInt(iHeight);
				Stars[i][2] = rand.nextInt(4);
			}
			
			BtnLines = new Button(5,5,60, 20);
			BtnLines.setValue(0);
			String sLinesLabels[] = {"Lines On", "Mesh Only", "Lines Off"};
			BtnLines.setLabel(sLinesLabels);

			//init the slider for the eyelid
			SldEyelid = new Slider(70, 5, 100, 20);
			SldEyelid.setValue(0.7);
			SldEyelid.setLabel("Eyelid Control");
	
			SldEyeIris = new Slider(175, 5, 100, 20);
			SldEyeIris.setValue(0.2);
			SldEyeIris.setLabel("Iris Control");

			SldArmsX = new Slider(5, 525, 100, 20);
			SldArmsX.setValue(0);
			SldArmsX.setLabel("Arms: X Axis");

			SldArmsY = new Slider(5, 550, 100, 20);
			SldArmsY.setValue(0);
			SldArmsY.setLabel("Arms: Y Axis");

			SldArmsZ = new Slider(5, 575, 100, 20);
			SldArmsZ.setValue(0.5);
			SldArmsZ.setLabel("Arms: Z Axis");


			InitShapes();
			System.err.println(Tail[0].getShape()[0][1]);
			
			iTick = 0;
			bIsInited = true;
		}
		
		//do some stuff
		//draw
		g.setColor(Color.black);
		g.fillRect(0,0,iWidth, iHeight);
		StarField(g);
		Update(g);
		
		BtnLines.render(g);
		SldEyelid.render(g);
		SldEyeIris.render(g);
		SldArmsX.render(g);
		SldArmsY.render(g);
		SldArmsZ.render(g);
		
		iTick++;
	}
	
	public void InitShapes()
		//the purpose of this method is to keep all of the shapes at the origin
		//or at least around it.  the post-multiplication only works if the shape
		//starts at the origin.
		//this method will re-init the shapes back to the origin and their relative
		//positions on the body
	{
		SphereHead = new ParamSphere(0,0,0, 50, EPSILON, iHeight, iWidth);
		SphereTorsoUpper = new ParamSphere(0,0,0, 40, EPSILON, iHeight, iWidth);
		SphereTorsoLower = new ParamSphere(0,0,0, 35, EPSILON, iHeight, iWidth);
		
		SphereHandLeft = new ParamSphere(0,0,0, 20, EPSILON, iHeight, iWidth);
		SphereHandRight = new ParamSphere(0,0,0, 20, EPSILON, iHeight, iWidth);
		
		SphereEyeLeft = new ParamSphere(0,0,0, 25, EPSILON, iHeight, iWidth);
		SphereEyelidLeft = new ParamSphere(0,0,0, 30, EPSILON, iHeight, iWidth);
		SphereEyeIrisLeft = new ParamSphere(0,0,0, 22, EPSILON, iHeight, iWidth);
		SphereEyeRight = new ParamSphere(0,0,0, 25, EPSILON, iHeight, iWidth);
		SphereEyelidRight = new ParamSphere(0,0,0, 30, EPSILON, iHeight, iWidth);
		SphereEyeIrisRight = new ParamSphere(0,0,0, 22, EPSILON, iHeight, iWidth);
		
		SphereAntennaLeft = new ParamSphere(0,0,0, 10, EPSILON, iHeight, iWidth);
		SphereAntennaRight = new ParamSphere(0,0,0, 10, EPSILON, iHeight, iWidth);
		ConeAntennaLeft = new ParamCone(0,0,0, 10, EPSILON);
		ConeAntennaRight = new ParamCone(0,0,0, 10, EPSILON);
		
		ArmLeftUpper = new ParamCylinder(0,0,0, 10, EPSILON);
		ArmLeftLower = new ParamCylinder(0,0,0, 10, EPSILON);
		ArmRightUpper = new ParamCylinder(0,0,0, 10, EPSILON);
		ArmRightLower = new ParamCylinder(0,0,0, 10, EPSILON);
		
		SphereShoulderLeft = new ParamSphere(0,0,0, 12, EPSILON, iHeight, iWidth);
		SphereShoulderRight = new ParamSphere(0,0,0, 12, EPSILON, iHeight, iWidth);
		SphereElbowLeft = new ParamSphere(0,0,0, 12, EPSILON, iHeight, iWidth);
		SphereElbowRight = new ParamSphere(0,0,0, 12, EPSILON, iHeight, iWidth);
		
		for (int i=0; i<MAX_SEGMENTS; i++)
			Tail[i] = new ParamConeTapered(0,0,0, 30 - i*5, EPSILON, 0.01);
		
		//now scale the things, also rotate
		Matrix3D.IdentityMatrix(mStack[mTop]);
		Matrix3D.RotateX(mStack[mTop], Math.PI/2);
		
		// HEAD
		push();
			Matrix3D.Translate(mStack[mTop], 0, -100, 0);
			Matrix3D.Scale(mStack[mTop], 2, 1, 1);
			SphereHead.Transform(mStack[mTop], SphereHead.getShape());
		pop();
			
		// UPPER TORSO
		push();
			Matrix3D.Scale(mStack[mTop], 1.5, 1, 1);
			SphereTorsoUpper.Transform(mStack[mTop], SphereTorsoUpper.getShape());
		pop();
		
		// LOWER TORSO
		push();
			Matrix3D.Translate(mStack[mTop], 0, 50, 0);
			SphereTorsoLower.Transform(mStack[mTop], SphereTorsoLower.getShape());
		pop();
		
		// IRISES, ORIENTATION
		push();
			Matrix3D.RotateX(mStack[mTop], Math.PI/2);
			Matrix3D.RotateZ(mStack[mTop], Math.PI);
			SphereEyeIrisLeft.Transform(mStack[mTop], SphereEyeIrisLeft.getShape());
			SphereEyeIrisRight.Transform(mStack[mTop], SphereEyeIrisRight.getShape());
		pop();
		
		// EYELIDS, ORIENTATION		
		push();
			Matrix3D.RotateX(mStack[mTop], Math.PI/2);
			Matrix3D.RotateY(mStack[mTop], Math.PI);
			Matrix3D.RotateZ(mStack[mTop], Math.PI/2);
			SphereEyelidLeft.Transform(mStack[mTop], SphereEyelidLeft.getShape());
			SphereEyelidRight.Transform(mStack[mTop], SphereEyelidRight.getShape());
		pop();
		
		// TAIL SEGMENTS
		for (int i=0; i<MAX_SEGMENTS; i++)
		{
			push();
				Matrix3D.RotateX(mStack[mTop], Math.PI);
				Matrix3D.Scale(mStack[mTop], 1, SEGMENT_LENGTH, 1);
				Tail[i].Transform(mStack[mTop], Tail[i].getShape());
			pop();
		}
		
		// ANTENNAE
		push();
			Matrix3D.Scale(mStack[mTop], 1, ANTENNA_LENGTH, 1);
			Matrix3D.RotateX(mStack[mTop], Math.PI);
			Matrix3D.Translate(mStack[mTop], 0, -ANTENNA_LENGTH, 0);
			ConeAntennaLeft.Transform(mStack[mTop], ConeAntennaLeft.getShape());
			ConeAntennaRight.Transform(mStack[mTop], ConeAntennaRight.getShape());
		pop();
		
		push();
			Matrix3D.Translate(mStack[mTop], 0, -ANTENNA_LENGTH, 0);
			SphereAntennaLeft.Transform(mStack[mTop], SphereAntennaLeft.getShape());
			SphereAntennaRight.Transform(mStack[mTop], SphereAntennaRight.getShape());
		pop();
		
		push();
			Matrix3D.Scale(mStack[mTop], 1, ARM_SECTION_LENGTH, 1);
			ArmLeftUpper.Transform(mStack[mTop], ArmLeftUpper.getShape());
			ArmLeftLower.Transform(mStack[mTop], ArmLeftLower.getShape());
			ArmRightUpper.Transform(mStack[mTop], ArmRightUpper.getShape());
			ArmRightLower.Transform(mStack[mTop], ArmRightLower.getShape());
		pop();
	}
	
	public void Update(Graphics g)
		//updates everything, draws everything
	{
		Matrix3D.IdentityMatrix(mStack[mTop]);
		
		push();
			
			// CIRCULAR MOVEMENT IN SPACE
			Matrix3D_FK.Translate(mStack[mTop], (Math.sin(iTick)*5)-(Math.sin(iTick)*10), 
												(Math.cos(iTick)*5)-(Math.cos(iTick)*10),
												0);
			
			push();
				// HEAD
				push();
					SphereHead.Translate(mStack[mTop], 0, (Math.sin(iTick)*2)-(Math.sin(iTick)*4), 0);
					SphereHead.Transform(mStack[mTop], SphereHead.getShape());
					SphereHead.draw(g, EPSILON, iWidth, iHeight, clrSkin, BtnLines.getValue());
				pop();
				// THE EYES
				//translate the eye into place for left eye, draw, draw iris, draw eyelid
				push();
					SphereEyeLeft.Translate(mStack[mTop], -50, -100, 0);
					SphereEyeLeft.Transform(mStack[mTop], SphereEyeLeft.getShape());
					SphereEyelidLeft.Transform(mStack[mTop], SphereEyelidLeft.getShape());
					SphereEyeIrisLeft.Transform(mStack[mTop], SphereEyeIrisLeft.getShape());
					
					SphereEyeLeft.draw(g, EPSILON, iWidth, iHeight, Color.white, BtnLines.getValue());
					drawIris(g, SphereEyeIrisLeft);
					drawEyelid(g, SphereEyelidLeft);
				pop();
				
				push();
					SphereEyeRight.Translate(mStack[mTop], 50, -100, 0);
					SphereEyeRight.Transform(mStack[mTop], SphereEyeRight.getShape());
					SphereEyelidRight.Transform(mStack[mTop], SphereEyelidRight.getShape());
					SphereEyeIrisRight.Transform(mStack[mTop], SphereEyeIrisRight.getShape());
					
					SphereEyeRight.draw(g, EPSILON, iWidth, iHeight, Color.white, BtnLines.getValue());
					drawIris(g, SphereEyeIrisRight);
					drawEyelid(g, SphereEyelidRight);
				pop();
				
		
				// THE ANTENNAE
				//left
				push();
					SphereAntennaLeft.Translate(mStack[mTop], -50, -130, 0);
					SphereAntennaLeft.Rotate(mStack[mTop], Math.cos(iTick)/6, ROTATE_Z);
					SphereAntennaLeft.Transform(mStack[mTop], SphereAntennaLeft.getShape());
					SphereAntennaLeft.draw(g, EPSILON, iWidth, iHeight, clrSkin, BtnLines.getValue());
					ConeAntennaLeft.Transform(mStack[mTop], ConeAntennaLeft.getShape());
					ConeAntennaLeft.draw(g, EPSILON, iWidth, iHeight, clrSkin, BtnLines.getValue());
				pop();
				//right
				push();
					SphereAntennaRight.Translate(mStack[mTop], 50, -130, 0);
					SphereAntennaLeft.Rotate(mStack[mTop], Math.sin(iTick)/6, ROTATE_Z);
					SphereAntennaRight.Transform(mStack[mTop], SphereAntennaRight.getShape());
					SphereAntennaRight.draw(g, EPSILON, iWidth, iHeight, clrSkin, BtnLines.getValue());
					ConeAntennaRight.Transform(mStack[mTop], ConeAntennaRight.getShape());
					ConeAntennaRight.draw(g, EPSILON, iWidth, iHeight, clrSkin, BtnLines.getValue());
				pop();
			
			pop();
			
			//THE TAIL
			push();
				push();
				//closest tail segment
					Tail[0].Translate(mStack[mTop], 0, 60, 0);
					Tail[0].Rotate(mStack[mTop], Math.cos(iTick)/4, ROTATE_Z);
					Tail[0].Rotate(mStack[mTop], Math.sin(iTick)/4, ROTATE_Y);
					Tail[0].Transform(mStack[mTop], Tail[0].getShape());
					Tail[0].draw(g, EPSILON, iWidth, iHeight, clrSkin, BtnLines.getValue());
										
					for (int i=2; i<MAX_SEGMENTS; i++)
					{
						push();
							Tail[i].Translate(mStack[mTop], 0, SEGMENT_LENGTH, 0);
							if (i < 2)
								Tail[i].Rotate(mStack[mTop], Math.cos(iTick)/4, ROTATE_Z);
							else
								Tail[i].Rotate(mStack[mTop], Math.sin(iTick)/4, ROTATE_Z);
							Tail[i].Rotate(mStack[mTop], Math.sin(iTick)/4, ROTATE_Y);
							Tail[i].Transform(mStack[mTop], Tail[i].getShape());
							Tail[i].draw(g, EPSILON, iWidth, iHeight, clrSkin, BtnLines.getValue());
					}
					for (int i=2; i<MAX_SEGMENTS; i++)
						pop();

				pop();		
			pop();

			// UPPER TORSO
			SphereTorsoUpper.Transform(mStack[mTop], SphereTorsoUpper.getShape());
			SphereTorsoUpper.draw(g, EPSILON, iWidth, iHeight, clrSkin, BtnLines.getValue());
		
			// LOWER TORSO
			SphereTorsoLower.Transform(mStack[mTop], SphereTorsoLower.getShape());
			SphereTorsoLower.draw(g, EPSILON, iWidth, iHeight, clrSkin, BtnLines.getValue());
			
			//THE ARMS
			push();
				push();
				//left upper arm
					ArmLeftUpper.Translate(mStack[mTop], -40, -20, 0);
					
					push();
						SphereShoulderLeft.Transform(mStack[mTop], SphereShoulderLeft.getShape());
						SphereShoulderLeft.draw(g, EPSILON, iWidth, iHeight, clrSkin.darker(), BtnLines.getValue());
					pop();
					
					ArmLeftUpper.Rotate(mStack[mTop], -5*Math.PI/6, ROTATE_Z);
					ArmLeftUpper.Rotate(mStack[mTop], (-2*(Math.PI/3)+(Math.cos(iTick)))*(SldArmsZ.getValue()-0.5), ROTATE_Z);
					ArmLeftUpper.Rotate(mStack[mTop], (-2*(Math.PI/3)-(Math.cos(iTick)))*SldArmsY.getValue(), ROTATE_Y);
					ArmLeftUpper.Rotate(mStack[mTop], (-(Math.PI/3)+(Math.sin(iTick)))*SldArmsX.getValue(), ROTATE_X);
					ArmLeftUpper.Transform(mStack[mTop], ArmLeftUpper.getShape());
					ArmLeftUpper.draw(g, EPSILON, iWidth, iHeight, clrSkin.darker(), BtnLines.getValue());
						
					push();
					//left lower arm
						ArmLeftLower.Translate(mStack[mTop], 0, -ARM_SECTION_LENGTH, 0);

						push();
							SphereElbowLeft.Transform(mStack[mTop], SphereElbowLeft.getShape());
							SphereElbowLeft.draw(g, EPSILON, iWidth, iHeight, clrSkin.darker(), BtnLines.getValue());
						pop();

						ArmLeftLower.Rotate(mStack[mTop], -Math.PI/3, ROTATE_Z);
						ArmLeftLower.Transform(mStack[mTop], ArmLeftLower.getShape());
						ArmLeftLower.draw(g, EPSILON, iWidth, iHeight, clrSkin.darker(), BtnLines.getValue());
						
						push();
						//hand
							SphereHandLeft.Translate(mStack[mTop], 0, -ARM_SECTION_LENGTH, 0);
							SphereHandLeft.Transform(mStack[mTop], SphereHandLeft.getShape());
							SphereHandLeft.draw(g, EPSILON, iWidth, iHeight, clrSkin.darker(), BtnLines.getValue());
						pop();
					pop();
				pop();
				
				push();
				//Right upper arm
					ArmRightUpper.Translate(mStack[mTop], 40, -20, 0);

					push();
						SphereShoulderRight.Transform(mStack[mTop], SphereShoulderRight.getShape());
						SphereShoulderRight.draw(g, EPSILON, iWidth, iHeight, clrSkin.darker(), BtnLines.getValue());
					pop();

					ArmLeftUpper.Rotate(mStack[mTop], 5*Math.PI/6, ROTATE_Z);
					ArmRightUpper.Rotate(mStack[mTop], ((2*(Math.PI/3))+(Math.cos(iTick)))*(SldArmsZ.getValue()-0.5), ROTATE_Z);
					ArmRightUpper.Rotate(mStack[mTop], ((2*(Math.PI/3))-(Math.cos(iTick)))*SldArmsY.getValue(), ROTATE_Y);
					ArmRightUpper.Rotate(mStack[mTop], (((Math.PI/3))+(Math.sin(iTick)))*SldArmsX.getValue(), ROTATE_X);
					ArmRightUpper.Transform(mStack[mTop], ArmRightUpper.getShape());
					ArmRightUpper.draw(g, EPSILON, iWidth, iHeight, clrSkin.darker(), BtnLines.getValue());
						
					push();
					//Right lower arm
						ArmRightLower.Translate(mStack[mTop], 0, -ARM_SECTION_LENGTH, 0);

						push();
							SphereElbowRight.Transform(mStack[mTop], SphereElbowRight.getShape());
							SphereElbowRight.draw(g, EPSILON, iWidth, iHeight, clrSkin.darker(), BtnLines.getValue());
						pop();

						ArmRightLower.Rotate(mStack[mTop], Math.PI/3, ROTATE_Z);
						ArmRightLower.Transform(mStack[mTop], ArmRightLower.getShape());
						ArmRightLower.draw(g, EPSILON, iWidth, iHeight, clrSkin.darker(), BtnLines.getValue());
	
						push();
						//hand
							SphereHandRight.Translate(mStack[mTop], 0, -ARM_SECTION_LENGTH, 0);
							SphereHandRight.Transform(mStack[mTop], SphereHandRight.getShape());
							SphereHandRight.draw(g, EPSILON, iWidth, iHeight, clrSkin.darker(), BtnLines.getValue());
						pop();
					pop();
				pop();
			pop();
						
	
		pop();

		InitShapes();
	}
	
	public void drawIris(Graphics g, ParamSphere Iris)
		//this method will basically do the same thing as draw in ParamSurface
		//but only for certain values so ill get an Iris looking think
	{
		double vector1[], vector2[], vector3[], vector4[];
		int len = Iris.getShape().length;
		double dEps = Iris.dEpsilon;
		double dEpsInv = 1/Iris.dEpsilon;
		
		int iTest;
		
		for (int i=0; i<(len-(dEpsInv)); i++)
		{
			iTest = (int)((i%dEpsInv)-(dEpsInv/4));
			
			if (((iTest<=0)&&(iTest>-((SldEyeIris.getValue()*IRIS_STATES)+2))) 
					|| ((iTest>0)&&(iTest<(SldEyeIris.getValue()*IRIS_STATES)+1)))
				//iris states x some slider value
			{
				vector1	= Iris.getShape()[i];
				vector2	= Iris.getShape()[(int)((i+1)%len)];
				vector3	= Iris.getShape()[(int)(((i+1)+(dEpsInv))%len)];
				vector4	= Iris.getShape()[(int)((i+(dEpsInv))%len)];
		 		
		 		Iris.drawQuad(vector1, vector2, vector3, vector4, g, 
		 				Color.black, dEps, BtnLines.getValue());
			}
		}
	}
	
	public void drawEyelid(Graphics g, ParamSphere Eyelid)
	{
		double vector1[], vector2[], vector3[], vector4[];
		int len = Eyelid.getShape().length;

		int i = 0;
		for (double v = 0; v < 1; v += Eyelid.dEpsilon)
		{
			for (double u = 0; u < 1; u += Eyelid.dEpsilon)
			{
				if ((Eyelid.getShape()[i][2] > Eyelid.getCenter()[2])&&(u>((SldEyelid.getValue()*0.5)+0.5)))
				{
					vector1	= Eyelid.getShape()[i];
					vector2	= Eyelid.getShape()[(int)((i+1)%len)];
					vector3	= Eyelid.getShape()[(int)(((i+1)+(1/Eyelid.dEpsilon))%len)];
					vector4	= Eyelid.getShape()[(int)((i+(1/Eyelid.dEpsilon))%len)];
			 		
			 		Eyelid.drawQuad(vector1, vector2, vector3, vector4, g, 
			 				clrSkin.darker(), EPSILON, BtnLines.getValue());
				}
				i++;
			}
		}
	}

	public void StarField(Graphics g)
		//draws a field of stars
	{
		g.setColor(Color.white);
		for (int i=0; i<200; i++)
			g.fillOval(Stars[i][0], Stars[i][1], Stars[i][2], Stars[i][2]);
	}

	public void push()
	{
		Matrix3D.Copy(mStack[mTop], mStack[mTop+1]);
		mTop++;
	}
	
	public void pop()
	{
		mTop--;
	}
	
	public boolean mouseDrag(Event e, int x, int y)
	{
		SldEyelid.drag(x, y);
		SldEyeIris.drag(x, y);
		SldArmsX.drag(x,y);
		SldArmsY.drag(x,y);
		SldArmsZ.drag(x,y);
		return true;	
	}

	public boolean mouseUp(Event e, int x, int y)
	{
		BtnLines.up(x,y);
		return true;
	}
	
	public boolean mouseDown(Event e, int x, int y)
	{
		BtnLines.down(x,y);	
		SldEyelid.down(x,y);
		SldEyeIris.down(x,y);
		SldArmsX.down(x,y);
		SldArmsY.down(x,y);
		SldArmsZ.down(x,y);
		return true;			
	}		
}

class BufferedApplet extends java.applet.Applet implements Runnable
{
/*
	THIS CLASS HANDLES DOUBLE BUFFERING FOR YOU, SO THAT THE IMAGE
	DOESN'T FLICKER WHILE YOU'RE RENDERING IT.  YOU DON'T REALLY
	NEED TO WORRY ABOUT THIS TOO MUCH, AND YOU'LL PROBABLY NEVER NEED
	TO CHANGE IT.  IT'S REALLY JUST USEFUL LOW LEVEL PLUMBING.
*/
	final double TICK = 10.0; //frames per second
	
	public void render(Graphics g) { }   // *you* define how to render
	public boolean damage = true;        // you can force a render
	
	private Image image = null;
	private Graphics buffer = null;
	private Thread t;
	private Rectangle r = new Rectangle(0, 0, 0, 0);
	
	private double dBaseTime = System.currentTimeMillis()/1000.0;
	private double dCurrentTime;

	// A BACKGROUND THREAD CHECKS FOR CHANGES ABOUT 30 TIMES PER SECOND
	
	public void start() { if (t == null) { t = new Thread(this); t.start(); } }
	public void stop()  { if (t != null) { t.stop(); t = null; } }
	public void run()   { try { while (true) { repaint(); t.sleep(30); } }
	                         catch(InterruptedException e){}; }
/*
	UPDATE GETS CALLED BY THE SYSTEM.
	IT CALLS YOUR RENDER METHOD, WHICH DRAWS INTO AN OFF-SCREEN IMAGE.
	THEN UPDATE COPIES THE IMAGE YOU'VE RENDERED ONTO THE APPLET WINDOW.
*/

	public void update(Graphics g) 
	{
		if (r.width != bounds().width || r.height != bounds().height) 
		{
			image = createImage(bounds().width, bounds().height);
			buffer = image.getGraphics();
			r = bounds();
			damage = true;
		}
		
		dCurrentTime = System.currentTimeMillis()/1000.0;
		if ((dCurrentTime - dBaseTime)>(1.0/TICK))
		{
			dBaseTime = dCurrentTime;
			render(buffer);
		}
		if (image != null)
		g.drawImage(image,0,0,this);
	}
}