/*

	RecursiveFractal.java--

	By Orion Lawlor, December 1997



	This is an interesting visual effect, and my first foray into Java.

	This code is very poorly documented-- it's mostly a throwaway test run.

*/



import java.util.*;

import java.awt.*;

import AnimApplet;

class Xform1d {

	double start,slope;

	public Xform1d(double inMin,double inMax,

					double outMin,double outMax)

	{

		slope=(outMax-outMin)/(inMax-inMin);

		start=outMin-slope*inMin;

	}

	public double in2out(double x) {return start+slope*x;}

	public double out2in(double x) {return (x-start)/slope;}

};

class Pt2d {

	public double x,y;

	//Create the point Nx,Ny

	public Pt2d(double Nx,double Ny) {x=Nx;y=Ny;}

	//Create the point from a to b.

	public Pt2d(Pt2d a,Pt2d b) {x=b.x-a.x;y=b.y-a.y;}

};

class Xform2d {

	Xform1d dx,dy;

	public Xform2d(Xform1d Ndx,Xform1d Ndy) {dx=Ndx;dy=Ndy;}

	public Pt2d in2out(Pt2d p) {return new Pt2d(dx.in2out(p.x),dy.in2out(p.y));}

	public Pt2d out2in(Pt2d p) {return new Pt2d(dx.out2in(p.x),dy.out2in(p.y));}

};

class Dot {

	public double x,y;

	public double screenX,screenY;

	public int ix,iy;

	int type=0;

	public Dot (double Nx,double Ny,Xform2d xf,int Ntype) {setPos(Nx,Ny,xf);type=Ntype;}

	public void setPos(double Nx,double Ny,Xform2d xf)

	{

		x=Nx;y=Ny;

		Pt2d p=xf.in2out(new Pt2d(x,y));

		screenX=p.x;screenY=p.y;

		ix=(int)screenX;iy=(int)screenY;

	}

	public boolean isNear(int mx,int my)

	{

		int dist=Math.abs(mx-ix)+Math.abs(my-iy);

		if (dist<7)

			return true;

		else return false;

	}

	public void draw(Graphics g)

	{

		int size=6;

		switch(type)

		{

			case 1: //Triangle, empty

				size=3;

				int lilY=1,lilX=2;

				g.drawLine(ix-lilX,iy+lilY,ix+lilX,iy+lilY);

				g.drawLine(ix+lilX,iy+lilY,ix,iy-size);

				g.drawLine(ix,iy-size,ix-lilX,iy+lilY);

				break;

			case 0: //Box, empty

				g.drawRect(ix-size/2,iy-size/2,size,size);

				break;

			case 2: //Box, filled

				g.fillRect(ix-size/2,iy-size/2,size,size);

				break;

		}

	}

};

class Box {

	public Dot []pt;

	public Box(Xform2d xf,Pt2d a,Pt2d b)

	{

		pt=new Dot[3];

		pt[0]=new Dot(a.x,a.y,xf,0);

		pt[1]=new Dot(b.x,a.y,xf,1);

		pt[2]=new Dot(a.x,b.y,xf,2);

		recache();

	}

	public void movePt(int ptNo,Pt2d where,Xform2d xf)

	{

		if (ptNo<=2)

			pt[ptNo].setPos(where.x,where.y,xf);

		else {

			pt[1].setPos(where.x,pt[1].y,xf);

			pt[2].setPos(pt[2].x,where.y,xf);

		}

		recache();

	}

	public int isNear(int x,int y)

	{

		int i;

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

			if (pt[i].isNear(x,y))

				return i;

		return -1;

	}

	public void draw(Graphics g)

	{

		int x4,y4;

		x4=pt[1].ix+(pt[2].ix-pt[0].ix);

		y4=pt[1].iy+(pt[2].iy-pt[0].iy);

		int i;

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

			pt[i].draw(g);

		g.drawLine(pt[2].ix,pt[2].iy,pt[0].ix,pt[0].iy);

		g.drawLine(pt[0].ix,pt[0].iy,pt[1].ix,pt[1].iy);

		g.drawLine(pt[1].ix,pt[1].iy,x4,y4);

		g.drawLine(x4,y4,pt[2].ix,pt[2].iy);

	}

	Pt2d off=new Pt2d(0,0),x=new Pt2d(0,0),y=new Pt2d(0,0);

	void recache()

	{

		off.x=pt[0].x;off.y=pt[0].y;

		x.x=pt[1].x-pt[0].x;x.y=pt[1].y-pt[0].y;

		y.x=pt[2].x-pt[0].x;y.y=pt[2].y-pt[0].y;

	}	

	public void applyToPoint(Pt2d p)

	{

		double px=off.x+p.x*x.x+p.y*y.x;

		double py=off.y+p.x*x.y+p.y*y.y;

		p.x=px;p.y=py;

	}

};

public class RecursiveFractal extends AnimApplet //implements Runnable

{

	protected Vector boxes=null;

	protected int nboxes=1;

	protected int selBox,selPt;

	protected Xform2d xf;

	Thread bgRedraw;

	boolean boxesChanged=true;

	int drawDefault=100,drawMul=2;

	int drawDelay=drawDefault;

	public void init()

	{

		super.init();

	}

	public void start()

	{

		selBox=selPt=-1;

		boxes=new Vector(50);

		boxes.addElement(new Box(xf,new Pt2d(0,0),new Pt2d(1,1)));//Background box.

		super.start();

	}

	public boolean handleEvent(Event e) {

		switch (e.id) {

		  case Event.MOUSE_DOWN:

		  	mouseDown(e.x,e.y);

		    return true;

		  case Event.MOUSE_DRAG:

		  	mouseDrag(e.x,e.y);

		  	return true;

		  case Event.MOUSE_UP:

		  	mouseUp(e.x,e.y);

		  	return true;

		  case Event.WINDOW_DESTROY:

		    System.exit(0);

		    return true;

		  default:

		    return false;

		}

    }

    Pt2d startPt;

    boolean newBox=false;

	void mouseDown(int mx,int my)

	{

		int i;

		selBox=selPt=-1;

		for (i=1;i<nboxes;i++)

		{

			Box b=(Box)boxes.elementAt(i);

			if (-1!=(selPt=b.isNear(mx,my)))

			{

				selBox=i;

				break;

			}

		}

		startPt=new Pt2d(mx,my);

		if (selBox==-1)

		{//create new box at user mouse position.

			Pt2d p=xf.out2in(startPt);

			Box b=new Box(xf,p,p);

			boxes.addElement(b);

			selBox=nboxes;

			selPt=3;

			nboxes++;

			newBox=true;

		}

		boxesChanged=true;

	}

	void mouseDrag(int mx,int my)

	{

		if (selBox!=-1)

		{

			Box b=(Box)boxes.elementAt(selBox);

			b.movePt(selPt,xf.out2in(new Pt2d(mx,my)),xf);

		}

		drawDelay=drawDefault;

		boxesChanged=true;

		repaint();

	}

	void mouseUp(int mx,int my)

	{

		mouseDrag(mx,my);

		if (newBox&&(Math.abs(mx-startPt.x)+Math.abs(my-startPt.y)<7))

			boxes.removeElementAt(--nboxes);//kill too-small boxes

		selBox=selPt=-1;

		newBox=false;

		boxesChanged=true;

		repaint();

	}

	protected void 

	fillBackBuffer(Graphics g,Graphics win_g,int w,int h)

	{

		int i;

		if (nboxes>1)

		{

			boxesChanged=false;

			g.setColor(Color.red);

			long drawEndTime=System.currentTimeMillis()+drawDelay;

			Random rand=new Random(drawEndTime);

			Pt2d p=new Pt2d(rand.nextFloat(),rand.nextFloat());

			//Get the fractal started...

			for (i=0;i<50;i++)

			{

				Box b=(Box)boxes.elementAt((((rand.nextInt()&0xff)*(nboxes-1))>>8)+1);

				b.applyToPoint(p);

			}

			while (!boxesChanged&&System.currentTimeMillis()<drawEndTime)

			{

				Box b=(Box)boxes.elementAt((((rand.nextInt()&0xff)*(nboxes-1))>>8)+1);

				b.applyToPoint(p);//Apply random box to point

				Pt2d outPt=xf.in2out(p);//Map point to screen

				g.drawLine((int)outPt.x,(int)outPt.y,(int)outPt.x,(int)outPt.y);

			}

			if (!boxesChanged) //we've not been tossed out because of user action.

				drawDelay*=drawMul;

			else drawDelay=drawDefault;

		}

		g.setColor(Color.darkGray);

		((Box)boxes.elementAt(0)).draw(g);

		g.setColor(Color.black);

		for (i=1;i<nboxes;i++)

			((Box)boxes.elementAt(i)).draw(g);

	}

	protected void res(int width,int height)

	{

		xf=new Xform2d(new Xform1d(-0.1,1.1,0,width),

						new Xform1d(-0.1,1.1,0,height));

		//Update boxes

		boxesChanged=true;

		if (boxes!=null)

			for (int i=0;i<nboxes;i++)

			{

				Box b=(Box)boxes.elementAt(i);

				for (int j=0;j<3;j++)

					b.movePt(j,new Pt2d(b.pt[j].x,b.pt[j].y),xf);

			}

	}

	public void resize(int width,int height)

	{

		res(width,height);

		super.resize(width,height);

	}

	public void resize(Dimension d)

	{

		res(d.width,d.height);

		super.resize(d);

	}

}