Uploaded to www.rart.com/j2me-presentation/html_Source/ March 19, 2002

The rP60ME Class

This class provides almost all support needed to draw a "Flake" in the J2ME environment. This means it supports trigonometric functions Sin and Cos for all angles from 0 to 360 in multiples of 30 degrees. It also provides a am integer square root function used to calculate the distance between pixels. The class was tested in an AWT environment. The corresponding changes were commented out but otherwise retained in the following source.


/*
 * @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 rartme.*;
 //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 functionality 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. For reasons of
 * debugging the class was tested in the AWT environment. The necessary 
 * modifications have been commented out but retained as an instructive example 
 * of the differences between javax.microedition.lcdui and awt
 *
 * Released as a component for the FlakesME sample universe, September 2001.
 *
 */

final class rP60ME {

	// Static variables having to do with geometry
	// We adapt to ME by multiplying the sin and cos values by 10000
	// AWT: Note that this works also in the AWT environment and may be
	// faster and more compact than the use of 64 bit floating numbers.
	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};
	
	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!
	 * 
	 * The method SQRT was borrowed from mathME 
	 */
	public static int SQRT(int x){
		// The method DebugME can not be used in a static method and so we
		// have retained some trace System.out.println for debugging
		/*
		 * 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 square 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 blue (gray) shading
	private static int blues[]= new int[8];			// lcdui
	private static int satValue[]={0,10,20,35,55,75,100};
	//private static Color blues[]= new Color[8];	// AWT
	
	/**
 	 * This assumes the awt rbg colormodel and transforms an integer
	 * colorvalue as used in ColorME to the corresponding color in AWT
	 * The method makeColor was borrowed from ColorMEtest (Which runs
	 * in the AWT environment.
	 */
	/*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;
		//System.out.println("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 blue (gray) for shadowing
			int dig, shade;
			// following is only reference to package rartme
			int col = UniverseME.numColor;  // for lcdui only
			//int col = 256;	// for AWT forcing color display
			for(int i=0; i < 7; i++){ 
				if (ABS(col) < 255){		// Not even grayscale
					// all white
					shade = 0xffffff;
				}
				else {
					dig = 255-satValue[i]*255/100;
					//System.out.println( "rP60ME.constructor "+i+", "+dig);
					if (col > 0)		// color device	shade = (((dig<<8)+dig)<<8)+255;
						shade = (((dig<<8)+dig)<<8)+255;
					else						// grayscale device
						shade = (((dig<<8)+dig)<<8)+dig;	
				}	
				blues[i]=shade;					// lcdui	
				// blues[i]=makeColor(shade);	// AWT
			}
			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 using drawShadowedLine60
	 *
	 * @param rP60ME 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	rP60ME			a new rP60ME 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;
		drawShadowedLine60(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 using 
	 * drawLine60, each time rotated 60 degrees relative to the origin P0.
	 *
	 * @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;
			}
		}
		drawShadowedLine60(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 and width w shadowed in shades of blue (or gray)
	 * if colors are supported (see constructor 1).
	 * 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 drawShadowedLine60
			  (int x1,int y1,int x2,int y2, int dir, Graphics g,int w){
		int dx,dy;
		g.setColor(blues[1]);
		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.setColor(blues[1]);
					g.drawLine(x1,y1-dy,x2,y2-dy);
					g.setColor(blues[3]);
					g.drawLine(x1,y1+dy,x2,y2+dy);
					if (w<5) break;
					g.setColor(blues[1]);
					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.setColor(blues[0]);
					g.drawLine(x1-1,y1-dy,x2-1,y2-dy);
					g.setColor(blues[4]);
					g.drawLine(x1+1,y1+dy,x2+1,y2+dy);
					if (w<5) break;
					g.setColor(blues[1]);
					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.setColor(blues[6]);
					g.drawLine(x1-dx,y1-1,x2-dx,y2+1);
					g.setColor(blues[0]);
					g.drawLine(x1+dx,y1+1,x2+dx,y2+1);
					if (w<5) break;
					g.setColor(blues[1]);
					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.setColor(blues[4]);
					g.drawLine(x1-dx,y1,x2-dx,y2);
					g.setColor(blues[0]);
					g.drawLine(x1+dx,y1,x2+dx,y2);
					if (w<5) break;
					g.setColor(blues[1]);
					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.setColor(blues[3]);
					g.drawLine(x1-dx,y1-1,x2-dx,y2-1);
					g.setColor(blues[0]);
					g.drawLine(x1+dx,y1+1,x2+dx,y2+1);
					if (w<5) break;
					g.setColor(blues[1]);
					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.setColor(blues[1]);
					g.drawLine(x1-1,y1-dy,x2-1,y2-dy);
					g.setColor(blues[1]);
					g.drawLine(x1+1,y1+dy,x2+1,y2+dy);
					if (w<5) break;
					g.setColor(blues[1]);
					g.drawLine(x1,y1+1,x2,y2+1);
					g.drawLine(x1,y1-1,x2,y2-1);
					break;
			} // end switch
		}	// end wider than 1 
	}	 // end drawShadowedLine60

	/**
	 * 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){
		//  This otherwise useful method is not used in Flakes
		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