//Wacky Snowflake Grower Applet
//Modification on Original "Diffusion Limited Aggregation" made by Jon Erickson
//Much thanks to Ross Luengen for his help!
// December 6, 1999

//------------------------------------------------------------------------
//  Diffusion Limited Aggregation (May 1997)
//
//  Copyright(C) 1997 Chi-Hang Lam
//  Permission to distribute, use or modify this software is hereby granted
//  provided that the above copyright notice appear in all copies
//
//  Please send comments, corrections or modifications to
//  Chi-Hang Lam     e-mail: apachlam@polyu.edu.hk
//------------------------------------------------------------------------

import java.awt.*;
import java.util.Vector;
import java.util.Enumeration;

public class dla extends java.applet.Applet {

  Thread grow_thread;
  Cluster cluster;
  Button growbut,demobut,pausebut,resumebut,resetbut;
  Scrollbar zoombar;
  Label Ltitle1,Ltitle2,Ltitle3;
  String Sgrow="Grow";
  String Sdemo="Grow slowly";
  String Spause="Pause";
  String Sresume="Resume";
  String Sreset="Reset";
  int zoomold,zoomcnt=0;
  

  public void init(){

    int data[] = new int[360];
    cluster=new Cluster();
    Panel p = new Panel();  
    p.setLayout(new GridLayout(0,1));
    p.add(new Label(""));
    Ltitle1=new Label("Wacky");
    Ltitle1.setFont(new Font("TimesRoman",Font.BOLD,16));
    p.add(Ltitle1);
    Ltitle2=new Label("Snowflake");
    Ltitle2.setFont(new Font("TimesRoman",Font.BOLD,16));
    p.add(Ltitle2);
    Ltitle3=new Label("Grower");
    Ltitle3.setFont(new Font("TimesRoman",Font.BOLD,16));
    p.add(Ltitle3);
    p.add(new Label("______________________"));
    setFont(new Font("TimesRoman",Font.PLAIN,12));
    p.add(growbut=new Button(Sgrow));
    p.add(pausebut=new Button(Spause));
    p.add(resumebut=new Button(Sresume));
    p.add(resetbut=new Button(Sreset));
    p.add(demobut=new Button(Sdemo));
    p.add(new Label("zoom:"));
    p.add(zoombar=new Scrollbar(Scrollbar.HORIZONTAL,zoomold=50,10,1,100));
    p.add(new Label(""));
    p.add(cluster.LN);
    p.add(cluster.LRg);
    p.add(cluster.LRmax);
    p.add(cluster.LRbirth);
    p.add(cluster.LRkill);
    setLayout(new BorderLayout(2,2));
    add("East",p);
    add("Center",cluster);
    cluster.newcluster();
  }

  public boolean action(Event e, Object arg) {
    if (e.target instanceof Button) {
      if (((String)arg).equals(Sgrow)){
	cluster.demoflg=false;
        cluster.pauseflg=false;
	startgrowth();
      }
      else if (((String)arg).equals(Sdemo)){
	cluster.demoflg=true;
        cluster.pauseflg=false;
	startgrowth();
      }
      else if (((String)arg).equals(Spause)){
	cluster.pauseflg=true;
      }
      else if (((String)arg).equals(Sresume)){
	cluster.pauseflg=false;
	cluster.wake();
      }
      else if (((String)arg).equals(Sreset)){
	reset();
      }
      return true;
    }
    return false;
  }

  void reset(){
    if (grow_thread!=null){
      grow_thread.stop();
      grow_thread=null;
    }
    cluster.newcluster();
    zoombar.setValue(zoomold=50);
    try {Thread.sleep(500);} catch (InterruptedException Ie) {}
  }

  void startgrowth(){
    reset();
    grow_thread = new Thread(cluster);
    grow_thread.setPriority(Thread.MIN_PRIORITY);
    grow_thread.start();	  
  }

  public boolean handleEvent(Event e){
    if (e.target instanceof Scrollbar){
      if (e.target==zoombar){
	zoomcluster();
      }
      return true;
    }
    return super.handleEvent(e);
  }

  void zoomcluster(){
    int zoom=zoombar.getValue();
    if (Math.abs(zoom-zoomold)>0){
      cluster.zoomfactor=Math.pow(10., (50.-zoom)/50.);
      cluster.repaint(300);
      zoomold=zoom;
    }
  }
}

class Cluster extends Canvas implements Runnable {

    TextArea text;

  Graphics gr;
  Vector Balls;
  Ball walker,oldwalker;
  DrawCluster drawcluster;
  double stepsize=0.1;
  double hitdist=1.;
  double Birthrad,killRad,killRad2,Rmax,Rg,Rg2,Rmaxold,scrsize;
  double cx,cy,centx,centy,redraw_thre;
  int dpixel,N=1;
  boolean walkerdrawn=false,demoflg;
  boolean jumpflg=true,showwflg=true,autozoomflg,pauseflg=false,birthflg=true;
  double marginfactor=1.2,zoomfactor;
  Color contcolor[];
  int Nlastcolor,ballcnt;
  Label LN,LRg,LRmax,LRbirth,LRkill;

  Cluster(){

      Frame newFrame = new Frame( "Jon's Funky Out" );

      text = new TextArea();
      text.setBounds( 5, 5, 30, 40 );
      newFrame.add( text );
      text.setEditable( false );
      newFrame.pack();
      newFrame.setVisible( true );

    setcontcolor();
    LN=new Label("");
    LRmax=new Label("");
    LRg=new Label("");
    LRbirth=new Label("");
    LRkill=new Label("");
  }

  int             n1=0;
  double           r1=1,g1=0,b1=0;
  int ncolor=256;
  void setcont(int dn, double r2,double g2, double b2){
    double r, g, b;
      for (int n = n1; (n < n1 + dn) && (n < ncolor); ++n) {
	r = r1 + (r2 - r1) * (n - n1) / dn;
	g = g1 + (g2 - g1) * (n - n1) / dn;
	b = b1 + (b2 - b1) * (n - n1) / dn;
	contcolor[n]=new Color((float)r,(float)g,(float)b);
      }
   n1 += dn;
   r1 = r2;
   g1 = g2;
   b1 = b2;
  }

  void setcontcolor(){
   int             dn;
   contcolor=new Color[ncolor];
   dn = (int) (ncolor / 5.)+1;
   setcont(dn, 1., 1., 0.);
   setcont(dn, 0.3, 1., 0.3);
   setcont(dn, 0., 1., 1.);
   setcont(dn, 0.3, 0.3, 1.);
   setcont(dn, 1., 0.3, 1.);
  }

  void newcluster(){
    gr=getGraphics();
    setBackground(Color.black);
    if (Balls==null) Balls=new Vector(2000,500);
    else Balls.removeAllElements();
    N=0;
    Birthrad=0;
    Rg2=0;
    Rmax=-1;
    zoomfactor=1;
    autozoomflg=true;
    scrsize=20.;  
    ballcnt=0;
    Nlastcolor=80;
    jumpflg=!demoflg;
    repaint();
    launch();
    attach();
  }

  public void run(){
      int currentN = 0;
      int data[] = new int[360];
      for (int j = 0 ; j<360 ; j++) {
	  data[j] = 0;
      }

      Draw_Hexagon();

    do{ 
	currentN = currentN + 1;
	data = walk(data, currentN);
     
    } while (true);
	
  }

    int counter = 0;

    public void Draw_Hexagon() {
	//the middle "square" portion

	for(int i = -2 ; i<=2 ; i++){
	    for (double j = -4.5 ; j<=4.5 ; j ++){
		walker = new Ball(j,i);
		attach();
		counter++;
	    }
	}
	
	//the triangle and inverted triangle

	double m = 1/Math.sqrt(3);
	
	double start_x = 4;
	double start_y = 3;
	
	//the bottom triangle       
	do{
	    for (double x = -1*start_x ; x <=start_x ; x++){
		walker = new Ball(x, start_y);
		    attach();
		    counter++;
	    }
	    start_x = start_x -0.5;
	    start_y = start_y + 0.5*m;
	    
	}while(start_y <=5.5);
	
	double x2 = 4;
	double y2 = -3;
	//the top triangle
	do{
	    for (double x = -1*x2 ; x <= x2 ; x++){
		walker = new Ball(x, y2);
		attach();
		counter++;
	    }
	        x2 = x2 -0.5;
		y2 = y2 -0.5*m;
		        
	}while(y2 >=-5.5);
	System.out.println("counter = " + counter);
    }

  public int[] walk(int[] data, int currentN){
    
    double theta,dx,dy,R2,jumpsize,dr;
    boolean hitflg;
    Thread t=Thread.currentThread();
    launch();
    jumpsize=dist();
    do {
     t.yield();
     if (pauseflg) pause();
     theta=Math.random()*6.283185;
     if (jumpflg)dr=jumpsize-hitdist+stepsize;
     else dr=stepsize;
     walker.x+=dr*Math.cos(theta);
     walker.y+=dr*Math.sin(theta);
     movewalker();
     R2=walker.x*walker.x+walker.y*walker.y;
     if (R2>killRad2){
       if (demoflg){
	 gr.setColor(Color.blue);
	 flashcircle(killRad);
       }
       launch();
     }
     jumpsize=dist();
     hitflg=(jumpsize<hitdist);
    }while (!hitflg);
    clearwalker();
    data = Data_Set(data, walker.x, walker.y, currentN);    
    attach();
    
    try {Thread.sleep(10);} catch (InterruptedException Ie) {}
    return data;
  }

  void launch(){
    double theta,x,y;
    theta=Math.random()*6.283185;
    x=Birthrad*Math.cos(theta);
    y=Birthrad*Math.sin(theta);
    walker=new Ball(x,y);
    if (demoflg&&(N!=0)){
      movewalker();
      gr.setColor(Color.red);
      flashcircle(Birthrad);
    }
  }

  synchronized void wake(){
    notify();
  }

  synchronized void pause(){
    try{wait();} catch (InterruptedException e){};
  }

     int max_number_particles = 5000 + counter;

  void attach(){

    ++N;
    LN.setText("no. of particles = "+N);
    drawattached(walker);
    Balls.addElement(walker);
    double R=Math.sqrt(walker.x*walker.x+walker.y*walker.y);
    //System.out.println("(" + walker.x + ", " + walker.y + ")");
    
    
     if (N > max_number_particles){
	 pause();
     }
    
    Rg2+=R*R;
    Rg=Math.sqrt(Rg2/N);
    LRg.setText("rad. of gyration = "+d2str(Rg,1));
    if (R>Rmax){
      Rmax=R;
      if (!demoflg){
	Birthrad=Rmax+1;
	killRad=(Rmax+1)*5;
      }
      else {
	Birthrad=Math.round((Rmax+2));
	killRad=Math.round((Rmax+8));
      }
      killRad2=Math.pow(killRad,2.);
      LRmax.setText("maximum radius = "+d2str(Rmax,1));
      LRbirth.setText("rad. of launching = "+d2str(Birthrad,1));
      LRkill.setText("rad. of killing = "+d2str(killRad,1)+"      ");
      if (autozoomflg&&(2*Rmax>scrsize)){
	scrsize=2*Rmax*marginfactor;
	repaint();
      }
    }
    
    walker=null;
    walkerdrawn=false;
  }

  String d2str(double x,int d){
    double f=Math.pow(10.,d);
    return( new Double(Math.round(x*f)/f).toString()  );
  
  }

    public int[] Data_Set(int[] data, double x, double y, int currentN){
	
	    int ang = 0;
	    if(x !=0){
		ang = (int) (Math.floor(Math.atan(y/x)*(180/Math.PI)));
		if((x>0) && (y<0)) ang = ang + 360;
		if (x<0) ang = ang + 180;	     
	    }
	    
	    else{
		if(y >= 0) ang = 90;
		else ang = 270;
		
	    }
     
	    //System.out.println("psi = " + ang);
	    data[ang] = data[ang] + 1;
	    //System.out.println("data [" +ang+ "] = " + data[ang]);    

	    if(currentN == max_number_particles - counter) {
		for (int j= 0 ; j<360 ; j++) {
		    text.append( data[j] + "\n");
		}
	    }
	    return data;
    }


  double dist(){
    double dx,dy,d2,d2min;
    d2min=9999;
    for(Enumeration e = Balls.elements(); e.hasMoreElements();) {
        Ball b=(Ball)e.nextElement();
	dx=walker.x-b.x;
	dy=walker.y-b.y;
	d2=dx*dx+dy*dy;
	if (d2<d2min) d2min=d2;
    }
    return(Math.sqrt(d2min));
  }

   public void paint(Graphics g){
    update(g);
  }

   public void update(Graphics g){
     Dimension d=size();
     centx=d.width/2;
     centy=d.height/2;
     int scrsizepixel=2*(int)((centx<centy)?centx:centy);
     dpixel=(int)(scrsizepixel/scrsize*zoomfactor);
     if (dpixel<2) dpixel=2;
     redraw_thre=5./dpixel;
     cx=d.width/2-0.5*dpixel;
     cy=d.height/2-0.5*dpixel;
     setBackground(Color.black);
     g.clearRect(0,0,d.width,d.height);
     ballcnt=0;
     Nlastcolor=(int)(N*Math.pow(marginfactor,1.73))+80;
     if (walkerdrawn)drawwalker();
     if ((drawcluster!=null)&&(drawcluster.t.isAlive()))
       drawcluster.t.stop();
     drawcluster=new DrawCluster(this,Balls,g);
   }

  synchronized void drawball(Ball b,Color c){ 
    gr.setColor(c);
    gr.fillOval((int)(b.x*dpixel+cx),(int)(b.y*dpixel+cy),dpixel,dpixel);
  }

   void drawattached(Ball b){
    int cindex=(int)((double)ncolor*(ballcnt++)/Nlastcolor);
    if (cindex>=ncolor) cindex=ncolor-1;
    drawball(b,contcolor[cindex]);
  }

   void drawwalker(){
    if (showwflg){ 
      walkerdrawn=true;
      oldwalker=new Ball(walker.x,walker.y);
      drawball(walker,Color.red);
    }
  }

   void clearwalker(){
    if (walkerdrawn){
      walkerdrawn=false;
      drawball(oldwalker,Color.black);
    }
   }

   void movewalker(){
     if (showwflg){
       if (!walkerdrawn) drawwalker();
       else{
	 Ball b=walker;
	 Ball bo=oldwalker;
	 if ((Math.abs(bo.x-b.x)>redraw_thre) |
	     (Math.abs(bo.y-b.y)>redraw_thre)){
	   clearwalker();
	   drawwalker();
	 }
       }
     }
   }

  void flashcircle(double rad){
    try { Thread.sleep(200);} catch (InterruptedException Ie) {}
    rad*=dpixel;
    gr.drawOval((int)(centx-rad),(int)(centy-rad),(int)(2*rad),(int)(2*rad));
    try { Thread.sleep(2000);} catch (InterruptedException Ie) {}
    gr.setColor(Color.black);
    gr.drawOval((int)(centx-rad),(int)(centy-rad),(int)(2*rad),(int)(2*rad));
  }
}

class Ball{
  double x,y;		
  Ball(double x_in, double y_in){
    x=x_in;
    y=y_in;
  }
}

class DrawCluster implements Runnable {
  Cluster cluster;
  Vector Balls;
  Graphics g;
  Thread t;

  DrawCluster(Cluster cluster,Vector Balls,Graphics g){
    this.cluster=cluster;
    this.Balls=Balls;
    this.g=g;
    t=new Thread(this);
    t.start();
  }

  public void run(){
    Thread t=Thread.currentThread();
    for(Enumeration e = Balls.elements();
	e.hasMoreElements();) {
      Ball b=(Ball)e.nextElement();
      cluster.drawattached(b);
      t.yield();
     }
  }
}
