Introduction to Java. A tutorial from A-SQUARE, Inc. January 2003

© 2000-2003 A-Square, Inc.

Source Code for Flakes.java

This code is used in Exercise 5d..A separate listng is available for the supporting classs rPoint60.java

Note the three main sections of a Rart universe:

Section 1 Identifying the universe

Section 2 What the universe does

Section 3 uParameters as used in the universe

>

/*
 * @Flakes    2.0  001210
 *
 * © A-Square, Inc. 1996-2000
 * 1648 Waters Edge Lane, Reston Virginia, 20190, USA
 */

import java.awt.*;
import java.util.Stack;
import rartbase.*;

/**
 * Flakes universe
 *
 * In the Flakes universe branches grow from the center in an approximation
 * of the growth of a hexagonal crystals, as formed by for example a snow
 * flake. 
 * 
 * The actual drawing of the branches is performed by the class rPoint60,
 * which emulates the geometry including the shaded drawing of branches up
 * to 5 pixels wide.
 *
 * author Jan Aminoff/	© A-Square 1998
 *
 * Rart universe using Java as defined through JDK 1.0.2.
 * Version 1.0 released as part of RDK 1.0 beta	 July 1999.
 * This is a cleaned up version issued as part of the Introduction to Java
 * Tutorial in December 2000. See Exercise 5d.
 * 
 */

public final class Flakes extends Universe {
    
    // Section 1     Identifying the Universe
	// -----------------------------------------------------------------------

	public String getName(){return("Flakes");}	// Name of the universe.
	public String getDescription(){return(sd);}	// Describing the universe. 
	private String sd=
	         "  See the FLAKES Universe where, in approximation of "+
			 "the rules of growth of a snowflake, branches grow and "+
			 "branch always at 30 or 60 degree angle and in circular "+
			 "symmetry.";
	 public String getRartist(){        //	  Name of Rartist.
		return(" Jan Aminoff 1989");}			
	
	// end of Section 1
    
	// Section 2    What the universe does
	// -----------------------------------------------------------------------
	
	// This universe is more complicated than others and so the documentation
	// for Section 2 has been subdivided in subsections as follows
	// Subsection 2a Initiation
	// With methods init, reset, and newFlake (which is quite long)
	// Subsection 2b Operation
	// With methods cycle, drawFlake, and eraseFlake (Erases a flake)
	// Subsection 2c Changing
	// with method manageChange
	
	// Subsection 2a Initiation
	// With methods getBackground, init, reset, and newFlake (which is 
	// quite long) and with declarations of static variables as well as 
	// of a number of arrays associated with the flakes.
	
	// Static Variables

	private static int nn_max =10;  // maximum number of flakes
	
    // The arms of a flake can branch with 5, 2 or one branch. The selection 
	// is made from the following table indexed with a random index.
	private static int branches[]={5,5,2,2,2,2,1,1,1,1};
	
	// The thickness of a branch is selected from the following table where
	// the first index is proportional to distance from center and second 
	// index is random. The effect is that brances ner the center are more
	// likely to be thicker.
	private static int thickness [][]= {
		{5,5,5,5,5,5,5,3,3,1},
		{5,5,5,3,3,3,3,3,1,1},		
		{5,3,3,3,3,3,1,1,1,1},
		{3,3,3,3,1,1,1,1,1,1},
		{3,3,1,1,1,1,1,1,1,1}
	};
	
	// Arrays whose values with index i are associatd with the i-th flake.
	// There are a maximum of nn_max flakes.					
	private int R[] = new int [nn_max];     // radius of enclosing circle
	private int X[] = new int [nn_max];     // x-coordinate of center     
	private int Y[] = new int [nn_max];     // y-coordinate of center
	private int lvl[] = new int [nn_max];   // level of brancing
	private int lmax[] = new int [nn_max];  // maximum level of branching
	private int rMin1[] = new int [nn_max];
	private boolean done[]=new boolean [nn_max];
	private Stack stackA[] =new Stack [nn_max];
	private Stack stackB= new Stack();
	
	private int rMin, dr, w;
	private rPoint60 p0,p1,p2; 

	private Debug dB;       // In case needed. See init
	
	private Color background = Color.darkGray;
	public Color getBackground(){return background;}	
	private boolean resetScreen = false;
	
	public void init(){
	    /* 
	    // Following establishes a pattern for setting and use of Debug.
	    // Debug is not used in this release of the Lines universe.
		 dB=new Debug();
		 int dblevel=2
		 dB.setLevel(dblevel);
		 dB.dbg(1,"Debug Level now set to "+dblevel");
		 */
		setParams();    // See Section 3 about uParameters
		reset();
	}   // end init
	
	// reset used in init and in manageChange for change of screensize
	// uParameter with index VIEWSIZE, and number of flakes uParameter
	// number with index NUMBER
	private void reset(){
		Vabs =xm * ym;				 //xm, ym defined in Universe class
		n_flakes = number.getCur();
		for (int i=0; i< n_flakes; i++){
			R[i]=0;
			X[i]=0;
			Y[i]=0;
		}		 
		for (int i=0; i< n_flakes; i++)
			newFlake(i);
		resetScreen = true;
	}   // end reset
	
	// Number of searches for empty space survives newFlake
	private int nsearch=0;
	//Vabs is a measure of total screenspace initiated in reset.				
	private int Vabs;           
	
	
	/**
	 * Finds a circular area nonoverlapping with other existing areas.
	 *
	 * @param i     index to arrays of the ith flake
	 */
	private void newFlake(int i){
	/*
	 * The strategy is to select a radius which reasonably should fit
	 * in the space left over from other areas. The method randomly places
	 * the center 30 times and checks if the selected spot is nonoverlapping.
	 * If unsuccessful after 30 tries, reduce radius by 4 and try again 60 
	 * times and so on. As a last resort reduce the number of areas. However,
	 * if we are sucessful more than 500 times we add in a new area or 
	 * increase tentatively the number from which we derive the radius of any 
	 * new areas.
	 */
	    
		boolean overlap=true;
		boolean allocated=false;
		int b=10;           // width of border
		int tries=30;		//tries 30 times to find a nonoverlapping spot
		int r,d,x,y, left, top;
		x=0;	//need to initalize x and y
		y=0;	//need to initalize x and y
		
		int sr=0;   //sr a measure of occupied screenspace
		for (int j=0; j< n_flakes; j++) if(i!=j) sr=sr+SQUARE(2*R[j]);
		int temp=Math.max(0,(int)(Math.sqrt(Vabs-sr)));
		//Vabs-sr is a measure of how much area is left over after existing flakes
		
		r=8+RND(temp)/2;	//radius
		d=2*r;					//diameter
		
		//We now need to select coordinates for the center of the new flake such that				
		//it does not overlap with other flakes and so that it is more than b (border)
		//pixels from the border. The area is the rectangle 0,0 and xm,ym
		left=r+b;
		top=r+b; 
				
		while (!allocated){
			x=left+RND(xm - 2 * left);
			y=top+RND(ym - 2 * top);
			allocated=true;
			for (int j=0; j < n_flakes; j++){  // go through other areas
				if ((i!=j)&&(R[j] > 0)){       // check if other radius>0
                    // Following inequality compares the squared sum of the 
                    // radii of the two areas to the square of the distance 
                    // between (x,y) and (x[j],y[j]).  
					overlap=((SQUARE(r+R[j]) >=
					        (SQUARE(x-X[j])+SQUARE(y-Y[j]))));
					if (overlap) {
						tries--;        // count down the number of tries
						allocated=false;
						if (tries<0){  // not successful
							r=r-4;	    //reduce radius
							d=2*r;
							left=r+b;
							top=r+b;
							//dB.dbg( 1,"Missed on 30 tries, r= "+r);
							tries=60;//try again 60 times
						}
					}
				}
			}	//next j
				
		}	//wend
		// If unsucessful in finding even one suitable spot (r reduced to <0)
		// take the last flake and move it in the array from n_flakes-1 to i
		if (r<0){			
			if (i < n_flakes-1){  
				r=R[n_flakes-1];
				x=X[n_flakes-1];
				y=Y[n_flakes-1];
			}
			n_flakes--;	  // reduce number of flakes as a last resort
			//dB.dbg( 1,"Number of flakes: "+n_flakes);
			//dB.dbg( 1,"Sum of flakes' areas "+sr+" Vabs: "+Vabs);
			nsearch=0;	  //restart success counter
		}
		// We have now found a viable radius, r, and location (x,y)
		// i th new flake:
		R[i]=r;	 
		X[i]=x;    
		Y[i]=y;
		lvl[i]=0; // the number of stacklevels
		stackA[i]=new Stack();  // one stack per flake
		com=complexity.getCur();
		// lmax gives number of branches or levels, function of the
		// uParameter complexity.
		lmax[i]=1+((r < 20)?1+RND(2):1+RND(com));	 //At least one branch
		//rMin1 is distance to circles edge at previous level	
		rMin1[i]=r;			
		Point P=new Point(X[i],Y[i]);	 
		p0=new rPoint60(P);			
		// establishes center of flake in the rPoint system
		p0.dir=RND(2);	//0 or 30 degree angle initially
		//Work from branching points from previous level in StackA		
		stackA[i].push(p0);			//Start at center with one point in StackA
		done[i]=false;
		//Now ready to go into branc drawing mode		
		//dB.dbg( 1,"Success "+(31-tries)+ " tries, r="+r);
		nsearch++;  // we have searched and found space for a flake once more.
		if (nsearch > 500){  // done it 500 times!
			if (n_flakes < nn){  // we had to reduce the number of flakes
			    //so we now increase number of flakes again 
				n_flakes++; 

			}else{
			    // otherwise increase the virtual area Vabs
				Vabs=(int)(Vabs*1.2);
				//dB.dbg( 1,"Area increased to: "+Vabs);
			}
			nsearch=0;			 //start all over counting sucesses
			//dB.dbg( 1,"Number of flakes: "+n_flakes);
		}	
	}   // end newFlake
	
	
	// Subsection 2b Operation
	// With methods cycle, drawNextLevel, drawBranch, eraseFlake
	// and static methods for SQUARE and rMin
	
	public void cycle(java.awt.Graphics g){
		if (resetScreen){
			g.setColor(background);
			g.fillRect(0, 0, xm, ym);  //clear the screen
            resetScreen=false;
		}	
		// In this universe we deal with a flake at a time
		int i = RND(n_flakes);	
		//dB.dbg( 1,"Circle i: "+i+" State: "+ st[i]);
		if (!done[i]) drawNextLevel(i,g)	; 
		lvl[i]++;
		if (lvl[i] > lmax[i]){			
			eraseFlake(i,g);			//eliminates the flake and
			newFlake(i);				//initiates a new one
		}
	}
	//Draws next level of the i-th flake
	void drawNextLevel(int i,Graphics gc){
		int r=R[i];
		rMin=r;			//rMin measures distance to circles edge during calculation
		int level=lvl[i];
		if(!done[i]){	//Cycle until level reaches lmax or distance to edge 2 or less
			level++;
			int nb=(level==1)?1:branches[RND(10)];	 //number of branches, 1, 2, or 5, see branches
			w=(level < 5)?thickness[level][RND(10)]:1;	//thickness of branch, see thickness
			int ddr = 2*r/lmax[i];
			dr=1+RND(ddr);								//length of branch, see ddr
			dr=Math.min(dr,rMin1[i]);				 //not further than circles edge
					//Store next level of branching points in StackB
			//work from A until empty
			while (!stackA[i].empty()){
				p1=(rPoint60)stackA[i].pop();	 //next branch point
				switch (nb) {					//nb number of branches
					//Each branch point comes with an inherent direction dir. 
					case 1:		//one branch
						drawBranch(r,0,gc);	//straight ahead
						break;
					case 2:		// two branches
						drawBranch(r,-1,gc);	 //+ and - 30 degreas
						drawBranch(r,1,gc);
						break;
					case 3:		//only in error at this time
						break;
					case 4:		//only in error at this time
						break;
					case 5:		//five branches	  //60 degrees all around
						drawBranch(r,-4,gc);
						drawBranch(r,-2,gc);
						drawBranch(r,0,gc);
						drawBranch(r,2,gc);
						drawBranch(r,4,gc);				
						break;
				}	//switch nb
			}	//stack A empty
			//move points from B to A until B is empty
			while (!stackB.empty()) stackA[i].push(stackB.pop());	 
			rMin1[i]=rMin;			//saves the value of rMin
			
		}	
		if((level > lmax[i])||(rMin<3)){
			done[i]=true;
			lmax[i]=4*level;
		}
	}	// drawNextLevel
	
	private void drawBranch(int r, int di,Graphics gc){
		int di1=(p1.dir+di)%12;	  //dir must be >=0 and <12 around the circle
		// Calculate new endpoint at p2 and draw the line from p1 to p2
		p2=p0.extendBranch60(p1,di1, dr,gc,w);
		// Draw the new line rotated in 60 degree increments around the circle	 
		p0.rotateLine60(p1,p2,gc,w);
		//Save the new endpoint in stackB
		stackB.push(p2);
		rMin=rMin(rMin,r,p2);
	}   // end drawBranch
	
	//erases a flake at X[i],Y[i] with radius R[i]
	private void eraseFlake(int i, Graphics gc){
		int x=X[i];
		int y=Y[i];
		int r=R[i]+1;
		int d=2*r;
		gc.setColor(background);
		gc.fillOval(x-r,y-r,d,d);		
	}   // end eraseFlake

	private static int rMin(int rMIN,int r, rPoint60 p){
		//checks distance to the edge of the sector bounded by x=0, a 60 degree radius 
		//and radius r from the center with relative coordinates	 x,y
		//returns minimum distance from any point to edge of circle
		int rd=(int)(r-Math.sqrt(SQUARE(p.x)+SQUARE(p.y)));
		//rd=Math.min(rd,p.y);
		return Math.min( rMIN,rd);
	}
	
	static int SQUARE(int x){
		return x*x;
	}
	
	// Subsection 2c Changing
	// with method manageChange

    public void manageChange(uParameter uP){
		int idx=uP.getIndex();
		//dB.dbg( 2,"In Flakes, Change in : "+uP.getName());
		if (uPs[idx]!=null) uPs[idx]=uP; //ensures that uPs, the list of current parameters is up to date			
		switch (idx){
			case VIEWSIZE:
				reset();
				break;
			case CYCLETIME:
				ct=uP;
				break;
			case NUMBER:
				number=uP;
				reset();
				break;
			case PARA4:
				complexity=uP;
				break;
				
			}
	}   // end manageChange
		
	// end of Section 2		   
      
	// Section 3   uParameters as used in the universe
    // -----------------------------------------------------------------------
	private uParameter number;  //Number of Flakes requested - PARAMETER
	private int nn=5, nn_min=1;	// nn_max static defined in Section 2	
	private int n_flakes=nn;						//actual number of Flakes
	private String ndescr=	
	        "This parameter indicates the maximum number of 'Flakes' "+
			"on the screen. ";
	private uParameter complexity;  //A measure of complexity - PARAMETER
	private int com=4,com_max=10,com_min=2;	
	private String cdescr= 
	        "Complexity has to do with the number of branchings until "+
		    "a growing branch reaches the predetermined circle that "+
		    "defines the edge of the flake. The parameter determines the "+
		    "maximum number of brancings. The actual number is less and "+
		    "random.";
	
	private uParameter ct;
	
    private void setParams(){
	    number= new uParameter 
	        (NUMBER,"Number of Flakes",nn_min,nn,nn_max,ndescr);
	 // The adduParameter method is defined in Universe
		adduParameter(number);
		complexity=new uParameter
		    (PARA4,"Complexity",1,com, com_max, cdescr);
		adduParameter(complexity);
    // Universe provides the description for uParameter with index CYCLETIME
		ct= new uParameter
			(CYCLETIME,"Cycle Time", 40,100,1000, CycleTimeDescr);	
		adduParameter(ct);
	}   // End setParam
        public Flakes() throws InstantiationException {}
}   // end of Flakes universe

>