//**************************************************************************
//   Interactive Barnsley Fern Applet by Rafael Wiemker, April 1996
//
//   (wasting his time on these stupid games
//    instead of doing the real work assigned to him,
//    such as finishing his thesis etc.... )
//
//    ( computed with a Random Iterated Functions System [IFS] 
//      with four affine mappings)
//
//   based on H.-O. Peitgen, H. J"urgens, D. Saupe:
//   Bausteine des Chaos: Fraktale
//   Springer, New York, Heidelberg 1992.
//
//   The method which actually did cost me some sweat is 
//   ColFernPanel.update FacPhi(), ie, determining such parameters that fit 
//   together nicely. The parameter tuning seems to be a pretty 
//   delicate affair. Probably one could do better.
// 
//**************************************************************************

import java.awt.*;
import java.applet.*;


public class ColFern extends Applet 
    implements Runnable {

    Graphics g;
    Thread runner;
    ColFernPanel controls;
    double xcurr, ycurr;
    double[] xarr = new double[100];
    double[] yarr = new double[100];
    Color[] carr = new Color[100];
    double[][][] M = new double[4][2][3];
    
    int wid = 480;
    int hei = 580;

    float[][] img = new float[wid][hei];
    
    double ly = (double)hei;	// plot offset
    double lx = 0; 		// plot offset
    
  
 
    public void start() {
	if (runner == null); {
	    runner = new Thread(this);
	    runner.start();
	}
    }


    public void stop() {
        if (runner != null); {
            runner.stop();
      	    runner = null;
      	}
    }

    
    public void init() {
        //resize(680,473);
   	g = getGraphics();	// current graphics environment
        setBackground(Color.white);
        setLayout(new FlowLayout());

        controls = new ColFernPanel(this);
        add("NORTH",controls);
        
      	M = controls.M;
   	xcurr = controls.x0;
   	ycurr = controls.y0;
    	ly = controls.defaulth;
   
   }
    

    public void run() {
 	while ( true ) {
 		repaint();
		nextpoint();
 		try {Thread.sleep(20);} 
		catch (InterruptedException e) { }
  	}
     }


    public void update(Graphics g) {
        paint(g);		// i.e. don't clear screen
    }
   
    
    public void paint(Graphics g) {
        for (int i=0; i<100; i++) {
             g.setColor(carr[i]);
             g.fillRect( (int)(xarr[i]-lx),(int)(ly-yarr[i]),1,1);
        } 
    }

   
    public void clearAndUpdate() {
   	g.clearRect(0,0,size().width,size().height);
    	M = controls.M;
   	xcurr = controls.x0;
   	ycurr = controls.y0;
    	lx = xcurr - controls.defaultw / 2;
     }





    private void nextpoint( ) {
         double z;
         for (int i=0; i<100; i++) {
             z = Math.random();
         
             if (z < 0.05) {		// altered from originally 0.02
                 mapping(0); 
             } else if (z < 0.17) {
                 mapping(1); 
             } else if (z < 0.30) {
                 mapping(2); 
             } else if (z < 1.00) {
                 mapping(3); 
             }
             xarr[i] = xcurr;
             yarr[i] = ycurr;
             carr[i] = controls.drawColor;

             int xaddr = (int)(xarr[i]-lx);
             int yaddr = (int)(ly-yarr[i]);
             if ((xaddr >= 0) && (xaddr < wid) 
                 && (yaddr >= 0) && (yaddr < hei))  {
                 
                 if (img[xaddr][yaddr] == 0.0f) {
                     img[xaddr][yaddr] = 1.0f;
                 } else
                     img[xaddr][yaddr] *= 0.9f;
                     
                 carr[i] = Color.getHSBColor(
                              controls.drawColorHue,1.0f,img[xaddr][yaddr]);
                 //carr[i] = Color.getHSBColor(img[xaddr][yaddr],1.0f,1.0f);
              }
         
            
         } // for i
         
         
      }


     private void mapping(int id) {
         double xnew, ynew;
     	
         xnew = M[id][0][0]*xcurr + M[id][0][1]*ycurr + M[id][0][2];
         ynew = M[id][1][0]*xcurr + M[id][1][1]*ycurr + M[id][1][2];
         xcurr = xnew;
         ycurr = ynew;
       }

 


}

//---------------------------------------------------------------------------

class ColFernPanel extends Panel {

    ColFern outerParent;
    Label l1, l2, l3, l4, l5;
    Scrollbar sb1, sb2, sb3, sb4, sb5;
    int v1, v2, v3, v4, v5;
    Color drawColor = Color.getHSBColor(0.44f,1.0f,1.0f);
    float drawColorHue = 0.44f;
    double x0, y0; 	// start values
    double xstart = 0.5;		// seed point, fix point of mapping [0]
    double ystart = 0.0;
    double[][][] M = new double[4][2][3];	// affine mappings[0..3]
    double[] xfac = new double[4];		// branch factors
    double[] yfac = new double[4];
    double[] xphi = new double[4];		// branch angles
    double[] yphi = new double[4];
    double[] xfix = new double[4];		// branch off points
    double[] yfix = new double[4];
    double branchAngle , shootAngle  , branchSize ;
    double  defaulth = 580;	// default overall height
    double  defaultw = 480;	// default overall size
    double w;			// actual overall size


    ColFernPanel(ColFern fern) {
        outerParent = fern;
 	
        setBackground(Color.gray);
        setLayout(new GridLayout(3,4)); // 4 hor. lines + 3 vert. lines

	l1 = new Label("0", Label.CENTER);
        sb1 = new Scrollbar(Scrollbar.HORIZONTAL,0,1,-45,45);

	l2 = new Label("0", Label.CENTER);
        sb2 = new Scrollbar(Scrollbar.HORIZONTAL,0,1,0,170);

	l3 = new Label("0", Label.CENTER);
        sb3 = new Scrollbar(Scrollbar.HORIZONTAL,0,1,0,100);

	l4 = new Label("100", Label.CENTER);
        sb4 = new Scrollbar(Scrollbar.HORIZONTAL,0,1,0,35);

	l5 = new Label("0", Label.CENTER);
        sb5 = new Scrollbar(Scrollbar.HORIZONTAL,0,1,0,200);

        Panel p1 = new Panel();
        Panel p2 = new Panel();
        Panel p3 = new Panel();

        p1.add(new Button("original Barnsley"));

        p1.add(new Label("color:", Label.RIGHT));
        p1.add(l4);
        p1.add(sb4);
 

        p3.add(new Label("curvature:", Label.RIGHT));
        p3.add(l1);
        p3.add(sb1);

        p3.add(new Label("branch angle:", Label.RIGHT));
        p3.add(l2);
        p3.add(sb2);

        p2.add(new Label("overall size:", Label.RIGHT));
        p2.add(l5);
        p2.add(sb5);
 
        p2.add(new Label("branch size:", Label.RIGHT));
        p2.add(l3);
        p2.add(sb3);

        add(p1);
        add(p2);
        add(p3);
        
	// initialize:
        sb4.setValue(16);	// color
        sb5.setValue(100);	// overall size
        w = defaultw;
        
        resetMappings();
        updateScrollbars();
         
    }
     
  

    public boolean handleEvent(Event evt) {
    	if (evt.target instanceof Scrollbar) {
     	    updateScrollbars();
      	    updateFacPhi();
   	    updateM();
    	    outerParent.clearAndUpdate();
       	    return true;
    	} 
    	else if (evt.target instanceof Button) {
      	    resetMappings();
    	    updateScrollbars();
    	    outerParent.clearAndUpdate();
      	    return true;
    	}
    	else return false;
    }


    
   
    void resetMappings() {
        sb1.setValue(3);
        sb2.setValue(50);
        sb3.setValue(30);
       

        // almost original Barnsley Parameters
        // slightly changed by Peitgen/J"urgens/Saupe
                         
        int id = 0;	// stem
        M[id][0][0] = 0.0;	// x : x multiplier
        M[id][0][1] = 0.0;	// x : y multiplier
        M[id][0][2] = 0.5*w;	// x : offset
        M[id][1][0] = 0.0;	// y : x multiplier
        M[id][1][1] = 0.27;	// y : y multiplier	
        M[id][1][2] = 0.0*w;	// y : offset
    
        id = 1;		// right leaf
        M[id][0][0] = -0.139;	// x : x multiplier
        M[id][0][1] = 0.263;	// x : y multiplier
        M[id][0][2] = 0.57*w;	// x : offset
        M[id][1][0] = 0.246;	// y : x multiplier
        M[id][1][1] = 0.224;	// y : y multiplier	
        M[id][1][2] = -0.036*w;	// y : offset

        id = 2;		// left leaf
        M[id][0][0] = 0.17;	// x : x multiplier
        M[id][0][1] = -0.215;	// x : y multiplier
        M[id][0][2] = 0.408*w;	// x : offset
        M[id][1][0] = 0.222;	// y : x multiplier
        M[id][1][1] = 0.176;	// y : y multiplier	
        M[id][1][2] = 0.0893*w;	// y : offset

        id = 3;		// shoot
        M[id][0][0] = 0.781;	// x : x multiplier
        M[id][0][1] = 0.034;	// x : y multiplier
        M[id][0][2] = 0.1075*w;	// x : offset
        M[id][1][0] = -0.032;	// y : x multiplier
        M[id][1][1] = 0.739;	// y : y multiplier	
        M[id][1][2] = 0.27*w;	// y : offset



    }
    

   
    void updateScrollbars() {
        v1 = sb1.getValue();
        v2 = sb2.getValue();
        v3 = sb3.getValue();
        v4 = sb4.getValue();
        v5 = sb5.getValue();
    
        shootAngle   = - v1 / 180.0 * 3.1415;
        branchAngle  = v2 / 180.0 * 3.1415;
        branchSize  = v3 / 100.0;
        w = v5/100.0f * defaultw;
        x0 = xstart*w;
        y0 = ystart*w;
   
        l1.setText(String.valueOf(v1)+" deg");
        l2.setText(String.valueOf(v2)+" deg");
        l3.setText(String.valueOf(branchSize));
        l4.setText(String.valueOf(v4*10)+" deg hue");
        l5.setText(String.valueOf(v5)+"%");
       
        drawColor = Color.getHSBColor((float)v4/36,1.0f,1.0f);
        drawColorHue = (float)v4/36;
    }
    
     
   
    void updateFacPhi() {
    	double xstemloc = 0.5;
    	double shootfac = 0.78;
    	double stemheight = 0.21;
    	double stemthick = 0.01;
    	double overallheight = 0.0;
    	
    	// determine will-be-overallheight:
    	if (shootAngle != 0) {
    	for (int i=0; i < (Math.abs(3.1415/2/shootAngle)+2); i++)  {
            overallheight +=
                 stemheight * Math.pow(shootfac,i) * Math.cos(i*shootAngle);
        }
        } else overallheight = 0.80;
    
        xfix[0] = xfix[1] = xfix[2] = xfix[3] = xstemloc*w;
        yfix[0] = 0.00*stemheight*w;
        yfix[1] = 0.25*stemheight*w;
        yfix[2] = 0.75*stemheight*w;
        yfix[3] = 1.00*stemheight*w;
  
        xfac[0] = stemthick ; 
        yfac[0] = stemheight / overallheight; 
        xfac[1] = -branchSize ;
        xfac[2] = yfac[1] = yfac[2] = branchSize ;
        xfac[3] = yfac[3] = shootfac;
        
        xphi[0] = yphi[0] = 0.0;
        xphi[1] = yphi[1] = -branchAngle ;
        xphi[2] = yphi[2] = branchAngle ;
        xphi[3] = yphi[3] = shootAngle  ;
        
     }

    
    void updateM() {
        for (int i=0; i < 4; i++) {
        
            // factors:         
            M[i][0][0] =  xfac[i]*Math.cos(xphi[i]);	// x : x multiplier
            M[i][0][1] = -yfac[i]*Math.sin(yphi[i]) ;	// x : y multiplier
 
            M[i][1][0] =  xfac[i]*Math.sin(xphi[i]);	// y : x multiplier
            M[i][1][1] =  yfac[i]*Math.cos(xphi[i]);	// y : y multiplier	

 	    // offsets:
 	    M[i][0][2] = xfix[i] - ( M[i][0][0]*x0 + M[i][0][1]*y0 );
	    M[i][1][2] = yfix[i] - ( M[i][1][0]*x0 + M[i][1][1]*y0 );

         }
         
 

    }  
    

}

//---------------------------------------------------------------------------

