//
//  Created by Steve Butler on 22 July 2008
//
//  Need to have window of size 840x733 for program
//

import java.text.DecimalFormat;
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class deep_new extends Applet implements MouseListener, MouseMotionListener {
	static int BUFFER=20, T_WIDTH=800, T_HEIGHT=693;  // constants used for size of triangle

	String[] centerTypes = { "bisectors", "medians", "Lemoine point", "Gergonne point",  "Spieker center", 
		"Isogonal of Spieker", "bisectors (x3)", "medians (x3)", "Lemoine point (x3)", "Gergonne point (x3)", 
		 "Spieker center (x3)", "Isogonal of Spieker (x3)"};
	int method=3;  //used to keep track of type of center used in the calculations
	int depth=4;
	int radius=2, diameter=5;

	static Point location = new Point(T_WIDTH/2-BUFFER,T_HEIGHT/2+BUFFER);

	boolean inTriangle=true;
	boolean isDragging=false;
	boolean notDraggingInside=false;
	
	Image backbuffer;
	Graphics backg;

	public void init() {
		backbuffer = createImage( 2*BUFFER+T_WIDTH, 2*BUFFER+T_HEIGHT );
		backg = backbuffer.getGraphics();
		newCoat();
		
		addMouseListener( this );
		addMouseMotionListener( this );
	}

	public void mouseEntered( MouseEvent e ) { }
	public void mouseExited( MouseEvent e ) { }
	public void mouseMoved( MouseEvent e ) { }
	public void mouseClicked( MouseEvent e ) { // GUI controls
		Point test = new Point (e.getX(),e.getY());
//  Insert GUI controls
		if(testInTriangle(test)==false){
			if(test.x>BUFFER && test.x<BUFFER+15 && test.y>BUFFER && test.y<BUFFER+15){
				method++; if(method==12) { method=0; if(depth>6){ depth=6; }}
			}
			if(test.x>BUFFER && test.x<BUFFER+15 && test.y>BUFFER+15 && test.y<BUFFER+30){
				method--; if(method==-1) { method=11; } if(method==5 && depth>6) { depth=6; }
			}
			if(test.x>BUFFER && test.x<BUFFER+15 && test.y>BUFFER+50 && test.y<BUFFER+50+15){
				if(method<=5 && depth<6) { depth++; }
				else if(method>5 && depth<10) {depth++; }
			}
			if(test.x>BUFFER && test.x<BUFFER+15 && test.y>BUFFER+50+15 && test.y<BUFFER+50+30){
				if(depth>1){ depth--; }
			}
			if((method<=5 && depth<5) || (method>5 && depth<7)){ diameter=5; radius=2; }
			else{ diameter=3; radius=1; }
			newCoat();
		}
		e.consume();
	}
	public void mousePressed( MouseEvent e ) {
		Point test = new Point (e.getX(),e.getY());

		if(testInTriangle(test)==true){
			inTriangle=true; isDragging=true;
			location=test;
		}
		else{ inTriangle=false; }
		newCoat();
		e.consume();
	}
	public void mouseReleased( MouseEvent e ) {
		isDragging = false;
		e.consume();
	}
	public void mouseDragged( MouseEvent e ) {
		Point test = new Point(e.getX(),e.getY());
		if(isDragging==true){
			location=nearestInTriangle(test);
			newCoat();
			e.consume();
		}
	}
	
//
//  The following two functions are to eliminate flicker
//
	public void update( Graphics g ) {
		g.drawImage( backbuffer, 0, 0, this );
	}

	public void paint( Graphics g ) {
		update( g );
	}
	
	public void newCoat(){
		DecimalFormat df = new DecimalFormat("##0.0");
		DecimalFormat dint = new DecimalFormat("#0");
		backg.setColor(Color.black);
		backg.fillRect(0,0,2*BUFFER+T_WIDTH,2*BUFFER+T_HEIGHT);
// draw the large white triangle
		int[] XArray={BUFFER,(T_WIDTH/2)+BUFFER,T_WIDTH+BUFFER};
		int[] YArray={BUFFER+T_HEIGHT,BUFFER,BUFFER+T_HEIGHT};
		backg.setColor( Color.white );
		backg.fillPolygon(XArray,YArray,3);

//draw GUI
		backg.setColor( Color.white );
		backg.drawRect( BUFFER,BUFFER,15,15);
		int[] UpTriangleX1 = {BUFFER+3,BUFFER+11,BUFFER+7};
		int[] UpTriangleY1 = {BUFFER+12,BUFFER+12,BUFFER+3};
		backg.fillPolygon( UpTriangleX1, UpTriangleY1, 3 );
		backg.drawRect( BUFFER,BUFFER+15,15,15);
		int[] DownTriangleX1 = {BUFFER+3,BUFFER+11,BUFFER+7};
		int[] DownTriangleY1 = {BUFFER+15+3,BUFFER+15+3,BUFFER+15+12};
		backg.fillPolygon( DownTriangleX1, DownTriangleY1, 3 );
		backg.drawRect(BUFFER+15,BUFFER,225,30);
		backg.drawString(centerTypes[method], BUFFER+30, BUFFER+20);


		backg.drawRect( BUFFER,BUFFER+50,15,15);
		int[] UpTriangleX2 = {BUFFER+3,BUFFER+11,BUFFER+7};
		int[] UpTriangleY2 = {BUFFER+50+12,BUFFER+50+12,BUFFER+50+3};
		backg.fillPolygon( UpTriangleX2, UpTriangleY2, 3 );
		backg.drawRect( BUFFER,BUFFER+50+15,15,15);
		int[] DownTriangleX2 = {BUFFER+3,BUFFER+11,BUFFER+7};
		int[] DownTriangleY2 = {BUFFER+50+15+3,BUFFER+50+15+3,BUFFER+50+15+12};
		backg.fillPolygon( DownTriangleX2, DownTriangleY2, 3 );
		backg.drawRect(BUFFER+15,BUFFER+50,50,30);
		backg.drawString(dint.format(depth), BUFFER+30, BUFFER+50+20);

//draw blue cross indicating current triangle at red cross
		backg.setColor( Color.blue );
		backg.drawLine( 620-5, 30+5,  620+5, 30-5);
		backg.drawLine( 620+5, 30+5,  620-5, 30-5);
		backg.drawLine( 620-5, 30+4,  620+4, 30-5);
		backg.drawLine( 620+5, 30+4,  620-4, 30-5);
		backg.drawLine( 620-4, 30+5,  620+5, 30-4);
		backg.drawLine( 620+4, 30+5,  620-5, 30-4);
		double angles[]=findTriangle(location);
		backg.setColor( Color.yellow );
		backg.drawString(df.format(angles[0]*180.0/Math.PI)+" -- "
						+df.format(angles[1]*180.0/Math.PI)+" -- "
						+df.format(angles[2]*180.0/Math.PI), 635,35);

//mark the daughters
		backg.setColor( Color.red );
		recurse(findTriangle(location),0);

//draw blue cross indicating initial location
				backg.setColor( Color.blue );
				backg.drawLine( location.x-5, location.y+5,  location.x+5, location.y-5);
				backg.drawLine( location.x+5, location.y+5,  location.x-5, location.y-5);
				backg.drawLine( location.x-5, location.y+4,  location.x+4, location.y-5);
				backg.drawLine( location.x+5, location.y+4,  location.x-4, location.y-5);
				backg.drawLine( location.x-4, location.y+5,  location.x+5, location.y-4);
				backg.drawLine( location.x+4, location.y+5,  location.x-5, location.y-4);

//output the picture
		repaint();
	}
	
	public void recurse (double[] triangle, int level) {
		if(level==depth){ // mark daughter
			Point P=findPoint(triangle[0],triangle[1],triangle[2]);
			backg.fillOval(P.x-radius,P.y-radius,diameter,diameter);
			}
		else{ //recurse to next level
			double Aa,Ab,Ac;  // angles to be used to compute daughters
			if(method==0 || method==6) {  // bisector
				Aa=bisector(triangle[0],triangle[1],triangle[2]);
				Ab=bisector(triangle[1],triangle[2],triangle[0]);
				Ac=bisector(triangle[2],triangle[0],triangle[1]);
			}
			else if(method==1 || method==7){  // medians
				Aa=median(triangle[0],triangle[1],triangle[2]);
				Ab=median(triangle[1],triangle[2],triangle[0]);
				Ac=median(triangle[2],triangle[0],triangle[1]);
			}
			else if(method==2 || method==8){  // Lemoine
				Aa=Lemoine(triangle[0],triangle[1],triangle[2]);
				Ab=Lemoine(triangle[1],triangle[2],triangle[0]);
				Ac=Lemoine(triangle[2],triangle[0],triangle[1]);
			}
			else if(method==3 || method==9){  // Gergonne
				Aa=Gergonne(triangle[0],triangle[1],triangle[2]);
				Ab=Gergonne(triangle[1],triangle[2],triangle[0]);
				Ac=Gergonne(triangle[2],triangle[0],triangle[1]);
			}
			else if(method==4 || method==10){  // Spieker
				Aa=Spieker(triangle[0],triangle[1],triangle[2]);
				Ab=Spieker(triangle[1],triangle[2],triangle[0]);
				Ac=Spieker(triangle[2],triangle[0],triangle[1]);
			}
			else{ // (method==5 || method==11)  // isogonal of Spieker 
				Aa=isoSpieker(triangle[0],triangle[1],triangle[2]);
				Ab=isoSpieker(triangle[1],triangle[2],triangle[0]);
				Ac=isoSpieker(triangle[2],triangle[0],triangle[1]);
			}
			if(method<=5){  //6 daughters
				double[] daughter1 = { Aa, Ac+triangle[0]-Aa, Math.PI-Ac-triangle[0] };
				double[] daughter2 = { triangle[1]-Ab, Ac+triangle[0], Math.PI+Ab-Ac-triangle[0]-triangle[1] };
				double[] daughter3 = { Aa+triangle[1]-Ab, Ab, Math.PI-Aa-triangle[1] };
				double[] daughter4 = { Math.PI+Ac-Aa-triangle[1]-triangle[2], Aa+triangle[1], triangle[2]-Ac };
				double[] daughter5 = { Math.PI-Ab-triangle[2], Ac, Ab+triangle[2]-Ac };
				double[] daughter6 = { Ab+triangle[2], Math.PI+Aa-Ab-triangle[2]-triangle[0], triangle[0]-Aa };
				recurse(daughter1, level+1);
				recurse(daughter2, level+1);
				recurse(daughter3, level+1);
				recurse(daughter4, level+1);
				recurse(daughter5, level+1);
				recurse(daughter6, level+1);
			}
			else{  //3 daughters
				double[] daughter1 = { Aa, triangle[1]-Ab, Math.PI+Ab-Aa-triangle[1] };
				double[] daughter2 = { triangle[2]-Ac, Math.PI+Ac-Ab-triangle[2], Ab };
				double[] daughter3 = { Math.PI+Aa-Ac-triangle[0], Ac, triangle[0]-Aa };
				recurse(daughter1, level+1);
				recurse(daughter2, level+1);
				recurse(daughter3, level+1);
			}
		}
	}

//
//  The following function is to test if a point is in the triangle
//
	public boolean testInTriangle (Point P) {
		if(P.y>BUFFER+T_HEIGHT){ return false; }  // below triangle
		else if(T_WIDTH*P.y<-2*T_HEIGHT*P.x+2*T_HEIGHT*BUFFER+BUFFER*T_WIDTH+T_HEIGHT*T_WIDTH){ return false; }  // off to left side
		else if(T_WIDTH*P.y<2*T_HEIGHT*P.x-2*T_HEIGHT*BUFFER-T_HEIGHT*T_WIDTH+BUFFER*T_WIDTH){ return false; } // off to right side
		else{ return true; }
	}

//
//  The following function projects to a point in the triangle
//
	public Point nearestInTriangle(Point P) {
		int newX,newY;
		
		if(P.y>BUFFER+T_HEIGHT){ //  below the triangle
			newY=BUFFER+T_HEIGHT;
			if(P.x<BUFFER) { newX=BUFFER; }
			else if(P.x>BUFFER+T_WIDTH) { newX=BUFFER+T_WIDTH; }
			else{ newX=P.x; }
		}
		else if(T_WIDTH*P.y<-2*T_HEIGHT*P.x+2*T_HEIGHT*BUFFER+BUFFER*T_WIDTH+T_HEIGHT*T_WIDTH){  //project onto left side
			double m=-2.0*(double)(T_HEIGHT)/(double)(T_WIDTH);
			double b=(double)(2*T_HEIGHT*BUFFER+T_WIDTH*BUFFER+T_HEIGHT*T_WIDTH)/(double)(T_WIDTH);
			newX=(int)(((double)(P.x)+m*((double)(P.y)-b))/(m*m+1.0));
			newY=(int)((b+m*(double)(P.x)+(double)(P.y)*m*m)/(m*m+1.0));
			if(newX<BUFFER){
				newX=BUFFER;
				newY=BUFFER+T_HEIGHT;
			}
			if(newY<BUFFER){
				newX=BUFFER+(T_WIDTH/2);
				newY=BUFFER;
			}
		}
		else if(T_WIDTH*P.y<2*T_HEIGHT*P.x-2*T_HEIGHT*BUFFER-T_HEIGHT*T_WIDTH+BUFFER*T_WIDTH){  //project onto right side
			double m=2.0*(double)(T_HEIGHT)/(double)(T_WIDTH);
			double b=(double)(-2*T_HEIGHT*BUFFER+T_WIDTH*BUFFER-T_HEIGHT*T_WIDTH)/(double)(T_WIDTH);
			newX=(int)(((double)(P.x)+m*((double)(P.y)-b))/(m*m+1.0));
			newY=(int)((b+m*(double)(P.x)+(double)(P.y)*m*m)/(m*m+1.0));
			if(newX>BUFFER+T_WIDTH){
				newX=BUFFER+T_WIDTH;
				newY=BUFFER+T_HEIGHT;
			}
			if(newY<BUFFER){
				newX=BUFFER+(T_WIDTH/2);
				newY=BUFFER;
			}
		}
		else{ // we are in the triangle
			newX=P.x;
			newY=P.y;
		}
		return new Point(newX,newY);
	}

//
//  The following two functions translate back and forth between points on the screen and triangles
//
	public Point findPoint(double X, double Y, double Z) { 
		return new Point(BUFFER+(int)((0.5*Y+Z)*(double)(T_WIDTH)/Math.PI),
							BUFFER+(int)((X+Z)*(double)(T_HEIGHT)/Math.PI));
	}

	public double[] findTriangle(Point P) { 
		double angles[]={0.0,0.0,0.0};
		double alpha=(double)(T_HEIGHT+BUFFER-P.y)/(double)(T_HEIGHT);
		double beta=(double)(P.x-BUFFER)/(T_WIDTH);
		angles[0]=(1-0.5*alpha-beta)*Math.PI;
		angles[1]=alpha*Math.PI;
		angles[2]=(beta-0.5*alpha)*Math.PI;
//  if "close" to an edge push back a bit
		if( Math.min(angles[0],Math.min(angles[1],angles[2])) < 0.01 ) {
			angles[0]=Math.PI*(angles[0]+0.01)/(Math.PI+0.03);
			angles[1]=Math.PI*(angles[1]+0.01)/(Math.PI+0.03);
			angles[2]=Math.PI*(angles[2]+0.01)/(Math.PI+0.03);
			}
		return angles;
	}

//
//  The following functions take a triangle with angles at points (X,Y,Z)
//  and generates the angle <PXY where P is the point of interest.
//
	public static double bisector(double X, double Y, double Z){
		return (X/2.0);
	}
	
	public static double median(double X, double Y, double Z){
		double y_part=Math.sin(X)*Math.sin(Y);
		double x_part=Math.sin(Z)+Math.cos(X)*Math.sin(Y);
		if(x_part<0){
			return (Math.PI+Math.atan(y_part/x_part));
		}
		else if(x_part>0){
			return Math.atan(y_part/x_part);
		}
		else{
			return (Math.PI/2.0);
		}
	}
	
	public static double Gergonne(double X, double Y, double Z){
		double p,q,r,s,t;
		p=Math.sin(Z)/Math.sin(X);
		q=Math.cos(Y)*p;
		r=Math.sin(Y)*p;
		s=Math.cos(0.5*Y)*Math.sin(0.5*Z)/Math.sin(0.5*(Y+Z));
		t=(q-s)*(q-s)+r*r;

		return Math.acos((p*p+t-s*s)/(2.0*p*Math.sqrt(t)));
	}

	public static double Lemoine(double X, double Y, double Z){
		double y_part=Math.sin(X)*Math.sin(Y);
		double x_part=Math.sin(Z)+Math.cos(X)*Math.sin(Y);
		double median;
		if(x_part<0){
			median=Math.PI+Math.atan(y_part/x_part);
		}
		else if(x_part>0){
			median=Math.atan(y_part/x_part);
		}
		else{
			median=Math.PI/2.0;
		}
		return X-median;
	}

	public static double Spieker(double X, double Y, double Z) {
		double p,q,r,s,t,u,v,x;
		double SpiekerX,SpiekerY;
		p=Math.sin(Y)/Math.sin(X+Y);
		q=Math.cos(X)*p;
		r=Math.sin(X)*p;
		s=Math.sin(0.5*Y)/Math.sin(0.5*X+0.5*Y);
		t=Math.cos(0.5*X)*s;
		u=Math.sin(0.5*X)*s;
		SpiekerX=0.5*(1.0+q-t);
		SpiekerY=0.5*(r-u);
		v=SpiekerX*SpiekerX+SpiekerY*SpiekerY;
		x=(1.0-SpiekerX)*(1.0-SpiekerX)+SpiekerY*SpiekerY;

		return Math.acos((v+1.0-x)/(2.0*Math.sqrt(v)));
	}

	public static double isoSpieker(double X, double Y, double Z) {
		double p,q,r,s,t,u,v,x;
		double SpiekerX,SpiekerY;
		p=Math.sin(Y)/Math.sin(X+Y);
		q=Math.cos(X)*p;
		r=Math.sin(X)*p;
		s=Math.sin(0.5*Y)/Math.sin(0.5*X+0.5*Y);
		t=Math.cos(0.5*X)*s;
		u=Math.sin(0.5*X)*s;
		SpiekerX=0.5*(1.0+q-t);
		SpiekerY=0.5*(r-u);
		v=SpiekerX*SpiekerX+SpiekerY*SpiekerY;
		x=(1.0-SpiekerX)*(1.0-SpiekerX)+SpiekerY*SpiekerY;

		return X-Math.acos((v+1.0-x)/(2.0*Math.sqrt(v)));
	}
}
