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

© 2000-2003 A-Square, Inc.



Source Code for Exercise 5e, Seasons Greetings 2002

The code for the electronic greeting is in three parts. The universe is called Xmas02. Its most prominent feature are the falling flakes, rendered through the Flake class which uses the rP60ME class, an adaptation for interger math of the rPoint60 class used in the original Flakes universe (Exercise 5d).

Xmas02.java

Flake.java

rP60ME.java

Xmas02.java

This code is used in Exercise 5e.

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

>

/*
 * @Xmas02    special  021203
 *
 * © A-Square, Inc. 1996-2002
 * 175 Richdale Ave. Cambridge MA 02104, USA
 */

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


/**
 * Xmas02 universe
 * Introduced as the annual greetingcard for 2002. It uses
 * approximations of snowflakes as created in the 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 rP60ME,
 * which emulates the geometry including the shaded drawing of branches up
 * to 5 pixels wide. 
 *
 * author Jan Aminoff/	© A-Square 2001-2002
 *
 * Rart universe using Java as defined through JDK 1.1.8.
 * However, this card uses image processing which necessitates some
 * tweeking including access to the RartRunner and the off screen buffer.
 * This uses the ME version of rPoint60 named rP60ME. It uses exclusively 
 * integer arithmetic and is used also in the J2ME version of Flakes.
 * 
 */

public final class Xmas02 extends Universe {
    
    // Section 1     Identifying the Universe
	// -----------------------------------------------------------------------
	// Name of the universe.
	public String getName(){return("Greeting for X-mas 2002");}
	// Describing the universe.	
	public String getDescription(){
	    reset();  // otherwise nc1 is not reset
	    return(sd);
	    }	 
	private String sd=
		"It is that time again. In 2003, I will "+
		"be able to release RDK 2.0 which was used to  "+
		"develop this universe. "+
		"I also hope for corporate support to create an international "+
		"community of Rartists. "+
		"See ReadMe about how to customize this greeting.";
	 public String getRartist(){			//	  Name of Rartist.
		return("Jan "+
			 "Aminoff and A-Square!");}		
    
	// 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. However, much initial
	// processing takes place in the first execution of cycle since it
	// has to await the reading of textfiles.
	
	// Static Variables

	private static int in_max =60;
	private Debug dB;       // In case needed. See init
	private Image flakeImages[] = new Image[in_max];
	private Point points[] = new Point[in_max];
	private int sdd;// Used to distinguish larger from smaller flakes
	private Color background = new Color(0,0,100);
	public Color getBackground(){return background;}	
	private boolean resetScreen = false;
	private boolean done = false;		// Signals completed input
	private String s1[] = new String[4];			// Used for output
	 // txt[][] holds processed text. Initialized with defaults
	private String[][] txt= new String[4][20];
	private String defaulttxt[][]= 	    
			{{"For Jenise and Alexander"},
			{"A Merry Christmas", "A Happy New Year","En GOD Jul", 
			"Ett Gott Nytt År"}, {""}, {"And Much Love from Jan"}};
		
	private String[] textFileNames = {"xmasLine1.txt","xmasLine2.txt", 
		"xmasLine3.txt", "xmasLine4.txt"};
	
		public void init(){   
	    // Following establishes a pattern for setting and use of Debug.
		 dB=new Debug();
		 // debugLevel may be set on the following line overriding the setting
		 // provided in HTML or command line for the rartrunner or commented 
		 // out to accept the setting for the RartRunner
		 //debugLevel =2;				// Overrides setting in rartrunner 
		 dB.setLevel(debugLevel);
		 dB.dbg(2,"Xmas01: Debug Level now set to "+debugLevel);
		 // Requesting specific resources from the rartrunner
		 resourceObjects = rartrunner.getResourceObjects(textFileNames);
		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(){
		nn = number.getCur();
		ss = size.getCur();
		sdd = 12+ss/2;
		first = true;
		resetScreen = true;
	}   // end reset
	
	
	// Subsection 2b Operation
	// With methods cycle, personaliseGreeting, processText
	// and static methods for SQUARE and rMin
	boolean first = true;
	public void cycle(java.awt.Graphics g){
		g.setColor(background);
		g.fillRect(0, 0, xm, ym);  //clear the screen

		// We generate flakes on the screen fist time (first == true)
		//dB.dbg(2,"Xmas02.cycle rartrunner "+rartrunner);
		// This works only for doublebuffered rart runners
		if (rartrunner.db && first){
			ss = size.getCur();
			nn = number.getCur();
			int x1 = xm-xm/3-30;
			int y1 = ym -180;
			int x, y,r;
			for (int i=0; i<nn; i++){	
				do{
					x = 20+RND(xm-40);
					y = 20+RND(ym-40);
					r = ((i> nn/2)?5 : 0)+ RND(size.getCur());
					flakeImages[i] = makeFlake(g, r, x , y);
				}while  ((y+r > y1)&&(x+r>x1));
				//dB.dbg(2,"Xmas01.cycle line 149: i, flakeImages[i] "+i+", "+
				//		flakeImages[i] );
				points[i]= new Point(x,y);	
			}
			//if (nc<5)dB.dbg(2,"Xmas01.cycle line 153: nc "+nc); 
			// Now, do the processing of the requested textfile resources
			for (int k=0; k<4; k++){
				String outk[];
				Object obj = resourceObjects[k];
				if (obj != null)  {
				    outk = processText( (String)obj);
 			    }else {
					// if user provides no input, default is applied
				 	outk = defaulttxt[k];
					//dB.dbg(2,"Xmas02.cycle: k, outk[0] "+k+", "+outk[0]);	
				}
				// count elements of outk
				dB.dbg(2,"Xmas02.cycle k, outk[] "+k+", "+outk);
				txt[k] = outk;
				
				dB.dbg(2,"Xmas02.cycle k, txt[k][0] "+k+", "+txt[k][0]); 
				s1[k] =txt[k][0];  // Initialize s1
			}
			dB.dbg(2,"Xmas02.cycle line 171: im generated, txt processed");
			first = false;
		}   //	End of initiation in cycle
		
 	    personaliseGreeting(g);	// Draw text message before flakes
		// Draw flakes
		for (int i=0; i<nn; i++){
			if (points[i].y > ym){	     // flake has reached bottom
				flakeImages[i] = null;
			}
			Image imf = null;
			imf = flakeImages[i];
			if (imf == null){   // reached bottom or usuccessful generation
				int x = 20+RND(xm-40);	
				int r = 5 + ((i> nn/2)?10 : 0)+ RND(size.getCur());
				flakeImages[i] = makeFlake(g, r, x , r);
				// dB.dbg(2,"Xmas02.cycle: Line 187 i, flakeImages[i] "+i+", "+
				//		flakeImages[i] );
				points[i]= new Point(x,r);
				//dB.dbg(2,"Xmas02.cycle: i, r, points[i].x, points[i].y "+i+
				//		", "+r+", "+points[i].x+", "+points[i].y);
			}
			// now draw the flake and move it if it exsists
			if (imf != null){
				int r= imf.getWidth(this)/2; 
				// drifting down, faster if larger
				points[i].y += ((r>sdd)? 0:2)+RND(4);
				g.drawImage(imf, points[i].x-r, points[i].y-r, this); 
				if (r<sdd){
				    // drifting sideways period 3 for smaller flakes
					points[i].x += (((i+nc)/10)%3)-1;;	  
				}else{
					// drifting sideways period 7 for larger flakes
				    points[i].x += (((i+nc)/20)%7)-3;	    
				}
			}  // finish drawing
		}	// end i		
	}	// end cycle
	
	
	private void personaliseGreeting(Graphics g){
		int wi = xm/3;
		int hi = 150;
		int xi = xm/4;
		int yi = ym /4;
		int len = 0;
		for (int k=0; k<4; k++){
			len =txt[k].length;				
			// dB.dbg(2,"personalize: k, txt[k].length "+k+", "+len);
			if (len >= 0){
				// if (nc==20) dB.dbg(2,"personalize: txt[k][0] "+txt[k][0]);	
	    		if (nc%((RND(5))+5)==0) {s1[k] =txt[k][RND(len)];}
	    		printString(g,s1[k],xi+20,yi+k*50,40);
			}
		}
	}   // end personalizeGreeting
	
	// This method parses a comma ',' separated string, in, and puts the 
	// parts purged from initial or trailing spaces in the string array out[]
	// maximum 21 entries.
    private String[] processText(String in){
    	// Gets the strings into temp
    	String s = in;
    	String[] out=null;
    	Vector vout =new Vector();
    	int len = s.length();
    	int idx;
		int p =0;
    	if (len > 0){
			idx = s.indexOf(",",0);
    		while (idx > -1){
    			vout.addElement(purge(s.substring(0,idx)));
				s = s.substring(idx+1);
				idx = s.indexOf(",",0);
				p++;
			}
			vout.addElement(purge(s));
			vout.trimToSize();
			int nn = vout.size();
			dB.dbg(2, "nn, elements of Vector "+nn);
			out = new String[nn];
			for(int i=0; i<nn; i++){
			    String ss =(String)vout.elementAt(i);
			    dB.dbg(2, "(String)vout.elementAt(i) "+ss);
			    out[i]=ss;
			}   
			dB.dbg(2,"processText line 257 p,out[p] "+p+", "+out[p]);	
    	}
        return out;    		
    }	// end processText
    
    // End Personalizing Greetings
    
  public static void printString
			(Graphics g, String st, int x, int y, int fontSize)
	//------------------------------------------------- 
	{
		// Used for standardized output of text in dialogboxes and may
		// Adapted to print Serif text in Red	
		String s=st;
		int i=1, ln=0;
		Font	HelFont = new Font("Serif", Font.BOLD, fontSize);
		g.setFont(HelFont);
		g.setColor(Color.red);
		while (i>-1){
			i=s.indexOf("\n",0);
			if (i > -1){
				g.drawString(s.substring(0,i), x, y+ln*(fontSize+2));
				//System.out.println("Xmas01.printString: i, s "+i+", X"+s+"X");
				s=s.substring(i+1,s.length());
				ln++;				
			}else g.drawString( s, x, y+ln*(fontSize+2) );
		}
	}   // end printString
	
	public static String purge( String in){
		String sp = " ";   //space
		String out = in;
		while (out.startsWith(sp)) out = out.substring(1);
		while (out.endsWith(sp)) out = out.substring(0,out.lastIndexOf(sp));
		return out;
	}

	private Image makeFlake(Graphics g, int radius, int x_cor, int y_cor){
		int r = radius;
		int d = 2*r;
		int x = x_cor;
		int y = y_cor;
		Flake f = new Flake(r, x, y, complexity.getCur());
		int rr = background.getRed();
		int rg = background.getGreen();
		int rb = background.getBlue();
		// dB.dbg(1,"Xmas02.makeFlake: background r,g,b "+rr + ", "+
		//		rg + ", "+rb );
		g.setColor(background);
		g.fillRect(x-r,y-r,d,d); 
		f.drawFlake(g);
		ImageFilter cropper = new CropImageFilter(x-r,y-r,d,d);
		ImageProducer prod = new 
				FilteredImageSource(rartrunner.imageBuffer.getSource(), cropper);
		Image newFlake = createImage(prod);
		try{
		    newFlake = getRidOfBackground(newFlake, r);
		}catch(Exception e){
			dB.dbg(2,"Xmas02.makeFlake EXCEPTION "+e);
			newFlake=null;
		}
		return newFlake;
	
	}	// end makeFlake
		
	private Image getRidOfBackground(Image im1, int rad){
		int w = 2*rad;
		int h = 2*rad;
		Image im = im1;
	  	
		//grab the pixels
		int[] pgPixels = new int [w * h];
		PixelGrabber pg = new PixelGrabber
				(im, 0, 0, w, h, pgPixels, 0, w);
		try {
		    pg.grabPixels();
		} catch (InterruptedException e) {
	    	System.err.println("interrupted waiting for pixels!");
			return im1;
		}
		
		// I skipped the following test since it invariably seemed
		// to be false skipping the meat of the processing.
		//if (((pg.getStatus() & ImageObserver.ALLBITS) !=0)){
			// Change the background bits
			int rr = background.getRed();
			int rg = background.getGreen();
			int rb = background.getBlue();
			// dB.dbg(1,"Xmas02.getRidofBackground: background r,g,b "+
			//		rr + ", "+rg + ", "+rb );
			int d  = 12;
			for (int y=0; y<h; y++){
				for (int x=0; x<w; x++){
					
					int i = y*w+x;
					int a = (pgPixels[i] & 0xff000000)>>24;
					int r = (pgPixels[i] & 0x00ff0000)>>16;
					int g = (pgPixels[i] & 0x0000ff00)>>8;
					int b = (pgPixels[i] & 0x000000ff);
					//if ((y==2)&&(x==2))dB.dbg(1,"Xmas02: flake r,g,b "+
					//		r + ", "+g + ", "+b );
					if ((b>(rb-d))&&(b<(rb+d))){
						pgPixels[i] = r<<16 + g <<8 +b;
					}  // pixel processed
					// NOTE: In the above, the test is made only on the blue 
					// component. This is to accommodate a mystreious bug in 
					// jdk1.1.8. It works here but is not satisfactory generally. 
					// The following tests were removed(r>(rr-d))&&(r<(rr+d)))
					// &&((g>(rg-d))&&(g<(rg+d)))
				} // for x
			}// for y
		//}
		return( createImage(new MemoryImageSource (w, h, pgPixels, 0, w)));
  } //end getRidOfBackground
  
  
	// Subsection 2c Changing
	// with method manageChange

    public void manageChange(uParameter uP){
		int idx=uP.getIndex();
		//dB.dbg( 2,"In Xmas01, 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;
			case PARA5:
				size = uP;
				reset();
				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 in=10, in_min=1;	// in_max static defined in Section 2	
	private int nn=in;						//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 size;	// Maximum radius -PARAMETER
	private int ss = 15, ss_max =45, ss_min =5;
	private String ssdescr =
			"The maximum size (actually the radius of the enclosing circle) "+
			"can be varied. Actual size varies randomly. "+
			"Larger flakes fall slower than smaller flakes.";		
	private uParameter ct;
	
    private void setParams(){
	    number= new uParameter 
	        (NUMBER,"Number of Flakes",in_min,in,in_max,ndescr);
	 // The adduParameter method is defined in Universe
		adduParameter(number);
		complexity=new uParameter
		    (PARA4,"Complexity",1,com, com_max, cdescr);
		adduParameter(complexity);
		size = new uParameter
			(PARA5,"Size of Flakes",ss_min, ss, ss_max, ssdescr);
		adduParameter(size);
    // Universe provides the description for uParameter with index CYCLETIME
		ct= new uParameter
			(CYCLETIME,"Cycle Time", 40,100,1000, CycleTimeDescr);	
		adduParameter(ct);
	}   // End setParam
        public Xmas02() throws InstantiationException {}
}   // end of Xmas02 universe		

>

Flake.java

>

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


public final class Flake {
     // 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}
	};
	
	/**
 	 * This assumes the awt rbg colormodel and transforms an integer
	 * colorvalue as used in ColorME to the corresponding color in AWT
	 */
	public Color makeColor( int colorvalue){
		int v = colorvalue;
		int g = colorvalue % 256;
		v /= 256;
		int b = v % 256;
		v /= 256;
		int r = v % 256;
		//dB.dbg("colorvalue r :"+r+", b "+b+", g "+g+".");
		return new Color(r, b, g);
	}  // end makeColor
	
	private boolean done=false;
	private Stack stackA;
	private Stack stackB;
	int r;			// radius;
	int x;			// x_coord;
	int y;			// y_coord;
	private int rMin, dr, w;
	private rP60ME p0,p1,p2; 
	private int lvl=0;   // level of brancing
	private int lmax;  // maximum level of branching
	private int rMin1;
	
	public Flake(int radius, 
	        int x_coord, int y_coord, int complexity){
	    dB=new Debug();
		int dblevel=2;
		dB.setLevel(dblevel);
		//dB.dbg(2,"Flake: Debug Level now set to "+dblevel);
		r = radius;
		x = x_coord;
		y = y_coord;
		stackA = new Stack();
		int com = complexity;
		//dB.dbg(2,"Flake r, x, y, com "+r+", "+x+", "+y+", "+com);
		// lmax gives number of branches or levels, function of the
		// complexity.
		lmax=1+((r < 20)?1+RND(2):1+RND(com));	 //At least one branch
		//rMin1 is distance to circles edge at previous level	
		rMin1=r;			
		Point P=new Point(x,y);	 
		p0= new rP60ME(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.push(p0);			//Start at center with one point in StackA
		//dB.dbg(2,"Flake.constructor: lmax "+lmax);
	}	// end Flake constructor
	
	private Debug dB;       // In case needed. See init
	
	public boolean drawNextLevel(Graphics gc){
		rMin=r;			//rMin measures distance to circles edge during calculation
		int level=lvl;
		dB.dbg(3,"Flake.drawNextLevel lvl "+lvl);
		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;
		dr=1+RND(ddr);								//length of branch, see ddr
		dr=Math.min(dr,rMin1);				 //not further than circles edge
				//Store next level of branching points in StackB
		//work from A until empty
		stackB= new Stack();
		while (!stackA.empty()){
			p1=(rP60ME)stackA.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.push(stackB.pop());	 
		rMin1=rMin;			//saves the value of rMin
		dB.dbg(3,"Flake.drawNextLevel r,rMin "+r+", "+rMin);
		stackB = null;	
		//Cycle until level reaches lmax or distance to edge 2 or less
		lvl=level;
		if((level > lmax)||(rMin<3)){
			done=true;
		}
		return done;
	}	// drawNextLevel
	
	//erases the flake
	public void erase(Graphics gc, Color background){
		gc.setColor(background);
		gc.fillOval(x-r,y-r,2*r,2*r);
	}   // end eraseFlake

	private static int rMin(int rMIN,int r, rP60ME 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);
	}
	
	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
	
	static int SQUARE(int x){
		return x*x;
	}
	
	static int RND(int x){
		return Universe.RND(x);
	}
	
	public void drawFlake(Graphics g){
		// Draw the flake in a square 2*r by 2*r on a transparent background
		g.setColor(makeColor(0));
		boolean done = false;
		while (!done){
			done = drawNextLevel(g);
		}
	}	// end drawFlake
	
}   // End of class Flake

>rP60ME.java
>

/*
 * @rP60ME	 1.0	010905
 *
 * © A-Square, Inc. 1996-2000
 * 1648 Waters Edge Lane, Reston Virginia, 20190, USA
 */

 //package rartme.univ;
 //import javax.microedition.lcdui.*;
 import java.awt.*;


/**
 * rPoint60 stands for relative origin. An rPoint60 serves as the center of a
 * flake in the Flakes universe but serves also as the origin of any branch.
 *
 * The rPoint60 class encapsulates the geometry of flakes, that is, branches
 * of 60 or 30 degrees. The branches are 1, 3 or 5 pixels wide. Each
 * rPoint60 is associated with a direction 0, 1, 2, . . . 11 corresponding to
 * 0, 30, 60, . . . 330 degrees.
 *
 * @Author Jan Aminoff 1996-2000.
 *
 * The ME Version 1.0 has exactly the same functionally as rPoint60 Version 2.0
 * released as part of the Introduction to Java Tutorial, Exercise 5d. The
 * challenge is to adapt to the integer math available in J2ME.
 *
 */

final class rP60ME {

	// Static variables having to do with geometry
	// We adapt to ME by multiplying the sin and cos values by 10000
	private static int sin30 = 5000; 			//  .5
	private static int cos30 = 8660;			// sqrt(3)/2.0;
	private static int sin[]={0,sin30,cos30,10000,cos30,sin30,
							0,-sin30,-cos30,-10000,-cos30,-sin30};
	private static int cos[]={10000,cos30,sin30,0,-sin30,-cos30,
							-10000,-cos30,-sin30,0,sin30,cos30};
	// We also borrow the method SQRT from mathME

	private static int SQUARE(int x){return x*x;}
	/**
	 * Square root implemented with integer math, ie the argument and the
	 * result are both integers.
	 * The result of SQRT(x), where x is an integer >= 0, is identical
	 * to the result of the expression (int)java.lang.Math.sqrt(x)for J2SE
	 *
	 * @param	integer x	Note:  for x<0 SQRT(x) returns 0!
	 */
	public static int SQRT(int x){
		// First, find the power of 100, nd, such that x/nd yields the one,
		// if x has odd number of digits, or two most significant digits of x.
		int nd = 0;
		int n = x;
		int t = 1;

		while (n > 0){
			nd =t;
			t *= 100;
			n =x / t;
			//System.out.println("SQRT n, t "+n+", "+t);
		}
		//System.out.println("SQRT x, nd "+x+", "+nd);
		/*
		 * Second, consider the most significant digits of x starting with
		 * x/nd and stepping nd, dividing by 100, then adding pairs of
		 * digits until the least significant digits are done.
		 */
		int d ; // will contain the most significant digits of the answer
		t = 0;
		int iter = 0;		// just to keep track of innermost loop
		while (nd > 0){
			n = x / nd;
			d = 10 * t;
			//System.out.println("SQRT, in begin while n, d "+n+", "+d);
			/* In the for loop we step d until its squre is larger than
			 * x. We save the prevous value in t, which thus is our answer,
			 * the largest number whose square is less than x.
			 */
			for (int i = d; i < d+10; i++){
				iter++;
				if (SQUARE(i) > n) break;
				t = i;
			}	// end for
			nd /= 100;
		}
		return t;
	}	// end SQRT

	
	private static int ABS(int x){return ((x < 0)? -x: x);}
	// Static variables used  for grayshading
	// not used in this version which only uses plain white
	
	private static Color flakecolor = Color.white;
	
	/**
 	 * This assumes the awt rbg colormodel and transforms an integer
	 * colorvalue as used in ColorME to the corresponding color in AWT
	 * Taken from ColorMEtest
	 */
	public static Color makeColor( int colorvalue){
		int v = colorvalue;
		int g = colorvalue % 256;
		v /= 256;
		int b = v % 256;
		v /= 256;
		int r = v % 256;
		//dB.dbg("colorvalue r :"+r+", b "+b+", g "+g+".");
		return new Color(r, b, g);
	}  // end makeColor


	// Instance variables

	// Coordinates relative to Origin at x0,y0
	public int x,y;
	 // Direction of a line is indicated as 0, 1, 2 , . . 11 corresponding to
	// 0, 30, 60, . . 330 or in 30 degree increments counting counterclockvise.
	public int dir;
	// Origin
	private Point P0;
	// Origin of reference coordinate system in absolute coordinates
	private int x0,y0;
	// Signal Origin has been set
	private boolean originSet=false;

	/**
	 * Constructor 1, Sets the origin, must be first constructor called
	 *
	 * @Param Point P The point of origin in absolute coordinates
	 */
	public rP60ME(Point P){
		if (!originSet){
			// Construct shades of gray for shadowing
			flakecolor = Color.white;
			P0=P;
			x0=P.x;
			y0=P.y;
			//System.out.println("rP60ME Constructor x0, y0 "+x0+", "+y0);
			dir=1;
			originSet=true;
		}else{
			System.out.println
					  ("Attempt to set Origin in rPoint60 more than once");
		}
	}	 // end rPoint60 constructor 1
	/**
	 * Constructor 2, Creates a new rPoint60 given its direction
	 * and coordinates relative to the origin Po.
	 *
	 * @param Dir		  direction from origin to
	 *						  this point
	 * @param X,Y		  coordinates relative to Po
	 * @param Point Po  the origin
	 */
	public rP60ME(int Dir, int X, int Y, Point Po){
		this(Po);
		if (originSet){
			x=X;
			y=Y;
			dir=Dir;
		 }else{
			System.out.println
					  ("Attempt to invoke rPoint60 without having set origin");
		}
	}	 // end rPoint60 constructor 2

	/**
	 * Constructor 3 Creates a new rPoint60 at a point PA
	 * given its direction relative to the origin Po.
	 *
	 * @param Dir		  direction from origin to
	 *						  this point
	 * @param Point PA  point relative to Po
	 * @param Point Po  the origin
	 */
	public rP60ME(int Dir, Point PA, Point Po){
		this(Po);
		if (originSet){
			x=PA.x-x0;
			y=PA.y-y0;
			dir=Dir;
		}else{
			System.out.println
					  ("Attempt to invoke rPoint60 without having set origin");
		}
	}	 // end rPoint60 constructor 3

	/**
	 * Draws a shadowed line of length r and width w from a point p
	 * in the direction dir.
	 *
	 * @param rPoint60 p		origin of the line
	 * @param dir				direction (as an integer 0, 1, 2, . . 11.)
	 * @param r					length of the line
	 * @param Graphics g		graphics context
	 * @param w					width of the line
	 *
	 * @return	rPoint60		a new rPoint60 at the end of the line
	 *
	 */
	public static rP60ME extendBranch60
			  (rP60ME p,int dir, int r, Graphics g, int w){
		int x1=p.x+p.x0;
		int y1=p.y+p.y0;
		int di=(dir<0)?dir+12:dir;
		int xr=p.x+(r*cos[di]/10000);
		int yr=p.y+(r*sin[di]/10000);
		int x2=xr+p.x0;
		int y2=yr+p.y0;
		drawWhiteLine60(x1,y1,x2,y2,di,g,w);
		rP60ME P= new rP60ME(di,xr,yr,p.P0);
		return P;
	}	 // end extendBranch60

	/**
	 * draws a line between two rP60ME points P1 and P2 five times, each time
	 * rotated 60 degrees relative to the origin.
	 *
	 * @param rP60ME P1	one endpoint of the line
	 * @param rP60ME P2	the other endpoint of the line
	 * @param Graphics g		the graphics context
	 * @param w					the width of the line
	 */
	public static void rotateLine60(rP60ME P1, rP60ME P2,  Graphics g, int w){
		rP60ME pa,pb;
		//rotates the line from P1 to P2 around origo 5 times in 60 degree increments
		for (int i=1;i<6;i++){
			pa= rotate60(P1,2*i);
			pb= rotate60(P2,2*i);
			drawLine60(pa,pb,g,w);
		}
	}	 // end rotateLine60

	 /**
	  * Draws a line between two rPoints
	  *
	  * @param rP60ME P1	 origin of line
	  * @param rP60ME P2	 termination of line
	  * @param Graphics g	 the graphics context
	  * @param w				 width of the line (1, 3 or 5 pixels)
	  */
	private static void drawLine60
			  (rP60ME P1, rP60ME P2,	Graphics g, int w){
		//Prepares for invocation of drawShadowedLine
		int x1=P1.x+P1.x0;
		int y1=P1.y+P1.y0;
		int x2=P2.x+P2.x0;
		int y2=P2.y+P2.y0;
		int dir=0;
		// Find the direction of the line
		int delta=50;
		int r=SQRT((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
		if (r<1) return;
		int co=10000*(x2-x1)/r;
		int si=10000*(y2-y1)/r;
		for (int i=0; i<12; i++){
			if ((ABS(co-cos[i])<delta)&&(ABS(si-sin[i])<delta)){
				dir=i;
				//System.out.println("cos: "+co+" sin: "+ si+" dir: "+i);
				break;
			}
		}
		drawWhiteLine60(x1,y1,x2,y2,dir,g, w);
	}	 // end 

	/**
	 * Draws a line between two points with coordinates x1, y1 and x2, y2,
	 * in the direction dir in this version all white.
	 * Used by drawLine60 and extendBranch60
	 * @param x1, y1			coordinates of starting point
	 * @param x2, y2			coordinates of end point
	 * @param dir				direction of line
	 * @param Graphics g		graphics context
	 * @param w					width (1, 3 or 5) of line
	 */
	private static void drawWhiteLine60
			  (int x1,int y1,int x2,int y2, int dir, Graphics g,int w){
		int dx,dy;
		g.setColor(flakecolor);
		g.drawLine(x1,y1,x2,y2);
		if (w>1){
			switch ((dir<6)?dir:dir-6){
				case 0:		//		0 degrees
					dy=(w<5)?1:2;
					g.drawLine(x1,y1-dy,x2,y2-dy);
					g.drawLine(x1,y1+dy,x2,y2+dy);
					if (w<5) break;
					g.drawLine(x1,y1-1,x2,y2-1);
					g.drawLine(x1,y1+1,x2,y2+1);
					break;
				case 1:		//30 degrees
					dy=(w<5)?1:2;
					g.drawLine(x1-1,y1-dy,x2-1,y2-dy);
					g.drawLine(x1+1,y1+dy,x2+1,y2+dy);
					if (w<5) break;
					g.drawLine(x1,y1-1,x2,y2-1);
					g.drawLine(x1,y1+1,x2,y2+1);
					break;
				case 2:	//60 degrees
					dx=(w<5)?1:2;
					g.drawLine(x1-dx,y1-1,x2-dx,y2+1);
					g.drawLine(x1+dx,y1+1,x2+dx,y2+1);
					if (w<5) break;
					g.drawLine(x1-1,y1,x2-1,y2);
					g.drawLine(x1+1,y1,x2+1,y2);
					break;
				case 3:		//90 degrees
					dx=(w<5)?1:2;
					g.drawLine(x1-dx,y1,x2-dx,y2);
					g.drawLine(x1+dx,y1,x2+dx,y2);
					if (w<5) break;
					g.drawLine(x1-1,y1,x2-1,y2);
					g.drawLine(x1+1,y1,x2+1,y2);
					break;
				case 4:		//	 120 degrees
					dx=(w<5)?1:2;
					g.drawLine(x1-dx,y1-1,x2-dx,y2-1);
					g.drawLine(x1+dx,y1+1,x2+dx,y2+1);
					if (w<5) break;
					g.drawLine(x1-1,y1,x2-1,y2);
					g.drawLine(x1+1,y1,x2+1,y2);
					break;
				case 5:	//	 150 degrees
					dy=(w<5)?1:2;
					g.drawLine(x1-1,y1-dy,x2-1,y2-dy);
					g.drawLine(x1+1,y1+dy,x2+1,y2+dy);
					if (w<5) break;
					g.drawLine(x1,y1+1,x2,y2+1);
					g.drawLine(x1,y1-1,x2,y2-1);
					break;
			} // end switch
		}	// end wider than 1 
	}	 // end drawWhiteLine60

	/**
	 * Reflects the point P1 in a line through the origin at P0 in direction
	 * dir and returns the virtual point of reflexion.
	 *
	 * @param rP60ME P1	the point to be reflected
	 * @param dir				the direction ( 0, 1, 2 ... 11) of the line in
	 *								which P1 is reflected
	 * @return rP60ME		the virtual reflection of P1
	 */
	private static rP60ME reflect60(rP60ME P1, int dir){
		//
		int di=(2*P1.dir-dir+12)%12;
		int twodir=(2*dir)%12;
		int xr=(cos[twodir]*P1.x/10000+sin[twodir]*P1.y/10000);
		int yr=(sin[twodir]*P1.x/10000-cos[twodir]*P1.y/10000);
		rP60ME P2= new rP60ME(di,xr,yr,P1.P0);
		return P2;
	}

	/**
	 * rotates the point P1 an angle dir*pi/12 radians, that is dir*30
	 * degrees, and returns a new rP60ME as rotated
	 *
	 * @param rP60ME P1	the rP60ME to be rotated
	 * @param dir				the multiple of 30 degree angles P1 will be rotated
	 *
	 * @return rP60ME		a new rP60ME rotated dir*30 degrees relative to P1
	 */
	private static rP60ME rotate60(rP60ME P1, int dir){
		//

		int di=(P1.dir+dir)%12;
		int xr=(cos[dir]*P1.x/10000-sin[dir]*P1.y/10000);
		int yr=(sin[dir]*P1.x/10000+cos[dir]*P1.y/10000);
		rP60ME P2= new rP60ME(di, xr,yr,P1.P0);
		return P2;
	}	// end rotate60

}	 // end of class rP60ME
>