© 2000-2003 A-Square, Inc.
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
>