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