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

© 2000-2003 A-Square, Inc. Cambridge, MA

SourceCode for Exercise 4e, Planes4d

This HTML document has two Java documents which should each be copied into their own files and given the name of the class followed by .java

As always, you get the sourcecode by selecting and copying any text between the two > that separate the source texts from the rest of this HTML document.

Planes4e.java

Plane.java

Planes4e.java

>
/*
 * @Planes4e    1.0  030115
 *
 * © A-Square, Inc.  
 * 175 Richdale Ave, Cambridge MA 02140
 */
 import rrlet.*;
 import rartbase.*;
 import java.awt.*;
 import java.lang.*;


/**
 *    'Planes rotating
 *
 * This Program is one in the Rart®, or Random Art, series of programs
 * that generate ever changing pictures. Simple geometries and random
 * dynamics create visual effects that may be pleasing to the eye.
 * It uses the Plane class for objects, the planes, that fill the screen.
 *
 * In EX4c, we show how to write a new universe Planes starting witn Eggs
 * In EX4d, we make Planes more sophisticated introducing color shades
 * In EX4e, we Planes is finalized using more color processing
 *
 * @version 1.0e  030115
 *
 * @author Jan Aminoff
 */
public final class Planes4e extends Universe { 
  
   // Section 1     Identifying the Universe
   // ------------------------------------------------------------------------
   public String getName() // Name,
       {return("Planes4e");}   // no more than 20 letters.
   public String getDescription() 
       {return(sd);}       // Description, 
                // no more than 350 letters describing the universe. Will 
               // be displayed in up to five lines of about 70 characters.
                                       
   private String sd=" We shall see how we progressively can add features "+
             "to a new universe to make it more visually interesting. "+
			 "The first version, EX4c, just offers monocolor planes."+
			 "The second version, EX4d, adds shading. And the final version, "+
			 "EX4e, adds a source of light, the angle of which can be varied. "+
			 "Also I added some other stuff. Difficult to stop fiddling!";
    
    public String getRartist()       // Rartist,
       {return("Jan Aminoff 2003");} // no more than 24 characters.


    // Section 2    What the universe does
   // ------------------------------------------------------------------------
    
    // About colors - constants and defaults
    static Color colors[]=      // Standard Java Colors
        {Color.black,Color.blue,Color.cyan,Color.darkGray,Color.gray,
      Color.green,Color.lightGray,Color.magenta,Color.orange,Color.pink,
      Color.red,Color.white,Color.yellow };
  
   private int bc=0;   // default number of color for background (black )
   private int pc=1;   // default number of color for foreground (blue )
   private Color background = colors[bc];  // Only one background in e    
   private boolean resetscreen=false; // Signal to recolor background      
    // Other variables
   private int nnmax = 20;  // maximum number of Planes, set in reset
   private int nn=5;        // actual number of Planes, set as number.getCur()
   private Plane pls[]=new Plane[nnmax];  // Up to 30 Planes.
   private int ss = 30;  // current maximum size
   private int vv = 15;	//  Current max rotation speed in degrees per cycle
   private int ci;		//  Current angle of incident light
   private int np;		// counting planes
   private Debug dB;  // used when debugging, especially as dB.dbg(n, string) 
 
    // The method init() is prescribed by Universe 
   public void init(){
      dB= new Debug();
      dB.setLevel(0);      //  Set level to 1 or 2 for tracing outputs.
      setParams();         //  See Section 3.
      reset();             //  See below.      
   }
      
   private void reset(){
       /** Reset is used in init and also in manageChange when restarting
        *  the universe after change of some parameters.
        */
       // Initialize Planearray
       nn=number.getCur();  // Have to use possibly modified parameters. 
       vv= speed.getCur();  // Max rotation speed in degrees per second

	   ci = lightangle.getCur();	// Angle of light 
	   Plane.setSi(ci);
	   //int vc = vv*ct.getCur(); // Max rot in degrees per 1000 cycles
       pc=pcolor.getCur();  // Index to colors array.
	   ss = psize.getCur();		     // Maxsize
	   Plane.setSz(ss);
       dB.dbg( 1 ,"In Planes.reset, xm: "+xm+", ym: "+ym);
	   dB.dbg( 1 ,"In Planes.reset, planecolor is "+colornames[pc]);
	   int idx = 0;
	   np = 0;
	   for (int i=0; i < nn+1; i++) pls[i] = null;
       for (int i=0; i < nn; i++) {                 
         //pls[i] = new Plane(xm, ym, vv, pc);
		 addPlane( new Plane(xm, ym, vv, pc	, false));
      }
	  
 
    }   // end reset
    
    /*
	 * addPlane makes sure the pls is sorted so the lagest planes
	 * are rendered last. Requires a new public getSize()method in Plane
	 */
    private void addPlane( Plane p){
		//Go through array until we find a bigger plane than p
		int news = p.getSize();
		dB.dbg(2, "addPlane-line 110: size "+news);
 	    //if (np>nn)displayPLS();
		int i = 0;
		Plane pi;
		// First find a slot i which is either the first slot where entry is null
		//or the first where the entry is greater than news
		for( i = 0; i<nn ; i++){
			pi = pls[i];
			if (pi == null) break;
			if (news< pi.getSize()){
				// move up existing planes freeing the spot for p
				int nn = pls.length;
				for (int j = nn-1; j>i; j--)
					pls[j]=pls[j-1];
				 break;
			 }
		}  // end i
 	    // move up existing planes freeing the spot for p
		pls[i] = p;
		// if (np > nn) displayPLS();
		np++;
	} 	// end addPlane
			
	private void displayPLS(){		// Just checking the array
		for (int k = 0; k<nn; k++)
			dB.dbg(1, "index "+ k + "  size "+
		 	pls[k].getSize());
		nc =0;
	}
		
   
   // This method is prescribed by Universe  
   public Color getBackground(){return background;}       

   // The following three variables are important in cycle.
   private int m;      // Index to current waxing or waning egg.
   private int status; // See Plane for the significance of status.
   
   
   // This method is prescribed by Universe. Everything interesting happens
   // in the cycle method which is called periodically by the RartRunner
   public void cycle ( Graphics g){ 
   /** In init, nn Planes have been initiated in pls. The planes rotate
    * clockvisw and counterclockvise for a total of 360 degrees, when it will
    * be replaced with a new plane.
    */
      // Reset screen always
      g.setColor(background);          
      g.fillRect(0,0,xm,ym);
	  // We go through all planes
	  nn = number.getCur();
      for (int i=0; i<nn; i++){
	  	Plane pi=pls[i];   // Current Plane.
      	status = pi.drawChange(g);
     	if (status == Plane.DONE){   // If plane rotated 360 degrees,
			// compact the pls
			int j = i;
			while (j<nn){
		 		pls[j]=pls[j+1];
				j++;
			}
			// put a new Plane in Planearray,
        	//pls[i]= new Plane(xm, ym, vv*ct.getCur(), pc, true);
			pi = new Plane(xm, ym, vv, pc	, true);
			addPlane(pi );
 		    dB.dbg( 1 ,"In Planes,cycle, new Plane at i: "+
					i+ ", size " + pi.getSize() );			
      	}
	  }         
   }  // end of cycle(g)
   
   
   // Section 3   uParameters as used in the universe
   // ------------------------------------------------------------------------   

   // number of Planes  
   private uParameter number; //Number of Planes - PARAMETER
   private int nnmin=1;       // nnmax and nn are given values before init    
   private String ndescr=  
      "This parameter indicates the maximum number of 'Planes' "+
      "visible at any one time. ";
   // maximum speed of an Plane
   private uParameter speed; // speed of rotation - PARAMETER  
   private int v=2,vmax=5,vmin=1;  
   private String sdescr=
      "The parameter actually determines the maximum rate of rotation "+
      "of all Planes. It is here given in degrees per cycle.";
   // cycletime
   private uParameter ct;  // Cycletime - PARAMETER
   // Detailes of the ct parameter can be seen in Universe
   // Plane color
   private uParameter pcolor;  // Plane Color - PARAMETER
   private uParameter bcolor;  // Background Color - PARAMETER
   private String cdescr=
      "Plane color may be chosen from the 13 primary colors "+
      "offered by Java. Except that black or the shades of grey look "+
	  "the same and look similar to what you get if you select white." ;
   static String colornames[]=
       {" black "," blue "," cyan"," dark grey "," grey ",
       " green "," light grey "," magenta "," orange ",
      " pink "," red "," white "," yellow "};
   private uParameter lightangle;	// angle of incident light
   private String cidescr =
   		"The angle is the angle of light to the normal of the  "+
		"plane of the observer window (the screen). Right behind "+
		"the observer the angle is 0 degrees.";
   private uParameter psize;		// Max size of planes
   private String szdescr = 
   		"The sizes of the planes vary randomly, within a range with "+
		"the maximum about 75 percent of the witdh of the screen.";
   private void setParams(){
   	   // nn = 1; // Testline  - easier to test with one plane only
       number= new uParameter 
           (NUMBER,"Number of Planes",nnmin,nn,nnmax,ndescr);
       //The adduParameter() method is defined in Universe.
       adduParameter(number);
       
       speed=new uParameter(PARA4,"Rotation Speed",vmin,v, vmax, sdescr);
       adduParameter(speed);	   
       
      ct= new uParameter
          (CYCLETIME,"Cycle Time", 20,100,200, CycleTimeDescr);  
          // Since "Cycle Time" is a compulsory parameter, Universe
          // provides the description CycleTimeDescr.
      adduParameter(ct);
      
      // The color parameters pcolor and bcolor have the same description
      // and array, colornames, for selection.  
      pcolor= new uParameter(PARA5,"Plane Color",pc,colornames, cdescr);
      adduParameter(pcolor);

      psize = new uParameter(PARA6,"Max Plane Size",20,30,75, szdescr); 
      adduParameter(psize); 
	  
	  lightangle = new uParameter
	  		( PARA7, "Angle of Incident Light", -90, 45, 90, cidescr );
	  adduParameter(lightangle);
    } //end setParams()
   
   // This method is prescribed by universe. Its purpose is to deal with any 
   // changes in parameters by the user. 
   public void manageChange(uParameter uP){
   /** This method is called by the RartRunner when a user has modified any
    * of the parameters. The new information is conveyed in a uParameter.
    * In the case of Planes, we have elected to restart the universe for any
    * changes even if for some parameters it would not be strictly neccesary.
    */
      int idx=uP.getIndex();
      dB.dbg( 2,"In Planes, Change in : "+uP.getName());
      if (uPs[idx]!=null) uPs[idx]=uP; // ensures that uPs, the array of 
                                       // current parameters is up to date         
      switch (idx){
         case VIEWSIZE:
            reset();  // Cange in windowsize, just restart.
            break;
         case NUMBER:   
            number=uP;   // A more sophisticated program could handle
            reset();     // changes in the maximum number of Planes,
            break;       // but this is simpler!
         case PARA4:     // used for speed of rotation
            speed=uP;
            reset();     // simple solution.
            break;
         case PARA5:     // used for for Plane color
             pcolor=uP;
             reset();    // No alternative to restart here.
             dB.dbg(1,"Planecolor is now "+colornames[pc]);
             break;
         case PARA6:     // used for max size
             psize=uP;
             reset();    // restart is easiest
             break;
		 case PARA7:	// used for angle of light
		 	lightangle =uP;
			reset();	// no alternative to restart.
         }  
      }  // end of manageChange()
      
   
   // Sections 4, and 5 in Universe have no corresponding entries in the
   // Planes universe.
      
   // Section 6   Methods related to security. 
   // -----------------------------------------------------------------------
   
   // The following constructor invokes the Universe constructor which is 
   // programmed to throw an exception in order to avoid multiple 
   // instantiations by mistake or bad intent.
   
   public Planes4e( ) throws InstantiationException
   { }

}  // end of Planes class
>

Top


Plane.java


>
/*
 * @Plane    1.0e  030115
 *
 * © A-Square, Inc.  
 * 175 Richdale Ave, Cambridge MA 02140
 */
import java.awt.*;
import java.lang.*;
import rartbase.*;
//import java.lang.math;		// for sin, cos and pi

public class Plane{
/** --------------------------------------------------------------------------
 * The Plane is a rectangle which rotates around a vertical axis at some speed.
 * The effect of rotation is achieved through the modification of the color
 * which changes as the angle of the light, which is thought to come from an
 * infinitely distant source behind the right the observer, at 45 degrees to 
 * the plane of the screen.
 *
 * @version 1.0e  030115
 * Used in Exercise 4e in the Introduction to Java tutorial.
 *
 * @author Jan Aminoff  
 */
   private int x;        // x pos of center of oval
   private int y;        // y pos of center of oval 
   private static int xys = 40;   // minimum space between center and edge of window
   private static int si = 90;	// angle of incident light
   private static int ss = 30;	// max size in percent of screen
   private int dir;   // dir = 1 for positive rotation, -1 for negative	
   private int dv;  // Speed degrees/cycle set as dir*(5+RND(speed-5))
   private int ndegrees; // number of degrees until done 90+(2+RND(5))*360
   //private int ffi; // Floating point repr of fi
   private int fi;		// Angle from plane of screen in degrees.
   private int rr;    // half max size of plane
   private int hh;	  // half height of plane
   private int nc = 0;
   
   public int status;  
   public static final int INITIALIZED=1;         
   public static final int ROTATING=2; 
   public static final int DONE=3; 
   
   private static int RND(int x){
       return(Universe.RND(x));
   }

   private static int ABS(int x){
   		return ((x<0)? -x:x);
	}

   // About colors - constants and defaults
    static Color colors[]=      // Standard Java Colors
        {Color.black,Color.blue,Color.cyan,Color.darkGray,Color.gray,
      Color.green,Color.lightGray,Color.magenta,Color.orange,Color.pink,
      Color.red,Color.white,Color.yellow };
   
	private int colorindex;

	//Color for shadows, black with hint of blue
	private Color shadowColor = new Color (50,50,50);   // Dark Gray
    //Color for flash, white
	private Color flashColor = new Color (255, 255, 255);

	private boolean shadow = false; 
   
   static int primes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31,   //11
                41, 43, 43,47, 53, 59, 61, 67, 71, 73,      //10
                  47, 83, 83};                        //3
   private Debug dB;  // used when debugging, especially as dB.dbg(n, string) 
   // constructor
   /**
    * Constructor for a Plane, rotating around its middle vertical axis
	* @param xmax		Width of present screen in pixels
	* @param ymax		Height of screen in pixels
	* @param speed		Speed of rotation in degrees per cycle
	* @param c			Basic color of plane, index to color array
	* @param boolean perpendicular	If true initial fi is 90, else fi random
	*/
   public Plane(int xmax, int ymax, int speed, int c, boolean perpendicular) {
      // We may need Debug to develop this universe
      dB= new Debug();
	  int debuglevel=0;	    //  Set level to 1 or 2 for tracing outputs.
      dB.setLevel(debuglevel);      
     // Center of plane is more than xys away from edge
      x = xys+RND(xmax-xys);
      y = xys+RND(ymax-xys);
      status = INITIALIZED;
	  colorindex = c;
      // Note that dx and dy are selected independently from the primes array. 
      // Causes great variation in the configurations.
      hh = (primes[4+RND(20)]*ymax)*ss/15000;   // max size 25% of window height
      rr = (int) (1.8*hh );  // same proportions for all planes
      fi = (perpendicular)? 90: -180+RND(360);  //Angle to plane of screen  
	  dir = 2*RND(2)-1;		// 1 or -1, that is clockwise or counterclocwise
	  dv = 5+RND(speed-5); // Increment angle per cycle
	  ndegrees = 90+(2+RND(5))*360; // number of degrees until done
	  //dB.dbg(1, "Plane.Plane dv "+dv);
	  // dB.dbg(1,"Debug activated at level "+debuglevel);
   }  // End of Plane constructor

	public static void setSi(int shi){
		si = (shi<-90)?-90: (shi> 90)? 90: shi;
	}

	public static void setSz(int s){
		ss  = (s<0)? 1: (s>70)? 70:s;
	}

	public int getSize(){
		return rr;
	}	
    
   public int drawChange(Graphics g){
   /** The drawChange method provides the functionality of the planes class.
    * It redraws the plane after a rotation of  nc*dv degrees .
    * It returns its status which relates to the number of complete rotations
    * passing the plane perpendicular to the screen.
    */
       int newstatus=status;
       switch(status){
           case INITIALIZED:
               newstatus = ROTATING;
           case ROTATING:
               drawPlane4e(g);
			   fi += dv;
			   // dB.dbg(1,"Plane.drawChange x "+x+", ffi "+ffi);
			   if (ABS(fi)>ndegrees)	newstatus = DONE;
               break;
       }
       status = newstatus;
       return (status);
   }   // end DrawChange
           
	private void drawPlane4e( Graphics g){
	/*
 	 * Draws the plane
	 * Width is rr*COS(fi+90), Height is constant hh
	 *
	 * First determine the color
	 * Our strategy is to consider the thirteen colors separately, and for
	 * each color consider how the tree components move. See shadedColor.
	 *
	 * In Plane4e, we consider light as coming from a source at an angle si, 
	 * -90 to 90 degrees measured from normal of the screen. When si goes from   
	 * -90 to 90, the light moves from left to right behind the observer. si 
	 * is static. Its value set by setSi() for all planes at the same time.
	 * 
	 * The color of the plane is determined by the angle of the plane to the 
	 * angle of the observer. To simplify the consideration we deal with
	 * the angle f which is derived from fi such that f always is in the
	 * range 0 to 180. f= (fi+90) mod 180.
 	 *
	 * The color of the plane depends on the angle psi, between the normal
	 * to the plane f, and the angle of view of the oserver, 0. This angle
	 * is psi = abs(f/2) and the intensity of the color
	 * is proportional to cos(psi).
	 *
	 * The plane has a shady side and a bright side. The shade is visible to 
	 * the observer when either 90<f+90<si or when f-90<si <90
	 * 
	 * Finally a flash is seen when the plane is not in shade and the normal
	 * of the plane bisects the angle between the incident light and the normal
	 * perpendicular to the screen, ie when the plane is not in shadow and
	 * abs(si/2)=f
	 *
	 */
		int idx = colorindex;
		Color pColor= colors[idx];
		shadow = false;
		boolean flash = false;
		int psi=0;
		int fn = (fi+90)%180;	  // fn normal to plane in range 0 to 180
		if (((fn>90) && (fn-90>90-si))||((fn<90)&&(fn+90<90-si))) shadow =true;
		if (shadow)pColor=shadowColor; 
		else{ 
				flash =(ABS(si/2+90-fn) <= 2*dv);
				if (flash) pColor = flashColor;
				else {
					psi = ABS(si-fn+90)%90;   //Ensures  psi <90
					double alpha = ABS(psi)*Math.PI / 180;
					// special case for white, idx = 11
					int sat = (int)(((idx == 11)? 100.0:50.0)*Math.cos(alpha));
					// Problem when sat<0. cos is < 0 when abs (psi) >90  
					pColor =  shadedColor(idx, sat);
			   }
		}
		dB.dbg(2, "drawPlane, fi="+
						fi+ ", si= "+si+", fn="+fn+", psi="+ psi);
		dB.dbg(2, "Shadow: "+ ((shadow)? "Yes":"No")+ "  Flash: "+
				((flash)?"Yes":"No"));
		double beta =  fi * Math.PI / 180.0 ;
		int r = (int) (rr*Math.cos(beta));
		boolean leftpointing = (r>0);
		r = ABS(r);
		// dB.dbg(1, "Plane.drawPlane Width="+2*r+" Height="+hh+ " fi="+fi);
		g.setColor(pColor);
		g.fillRect(x-r, y-hh, 2*r, 2*hh);
		if (!shadow){
			int xx = (int)(0.5 * r);
			int yy = (int) (0.5 * hh);
			dB.dbg(2, "Leftpointing Triangle: "+ ((leftpointing)? "Yes":"No"));
			int xps[] = new int[3];
			int yps[] = new int[3];
			if (leftpointing){
				xps[0] =x-xx; yps[0]=y;
				xps[1] =x+xx; yps[1]=y-yy;
				xps[2] =x+xx; yps[2]=y+yy;
			}else{
				xps[0] =x-xx; yps[0]=y -yy;
				xps[1] =x+xx; yps[1]=y ;
				xps[2] =x-xx; yps[2]=y +yy;
			}
			//g.setColor();
			
			
			g.setXORMode(Color.black); 
			g.setColor(pColor);
			g.fillPolygon(xps,yps,3);
			//g.fillPolygon(xps,yps,3);	
        }
		g.setPaintMode();   
   }	// end drawPlane4e

  /**
   * Produces a color that is linearly changed from original basic Java
   * color to white.
   * @ idx 		index to one of 13 Java colors
   * @ sat      measure of saturation (?) percent from 0 to 100
   */
   private Color shadedColor( int idx, int sat){	   // New for 4d
   			int dig, dig1;		//Digits of Color
			Color c1;
		try{	// Resulting color	
   		switch(idx){
 	    	case 0:		// Black Shades of Grey treated the dame
			case 3:		// Dark Grey
			case 4:	    // Light gray
			case 6:		// Grey
				dig=(int)(sat*255.0/100);
				c1= new Color(dig,dig,dig);
				break;
			case 1:		// Blue
				dig = (int)(sat*255.0/100);
				c1= new Color(dig,dig,255);
				break;
			case 2:		// Cyan
				dig = (int)(127.0+sat*128.0/100);
				c1 = new Color(dig,255,255);
				break;
			case 5:		// Green
				dig = (int)(127.0+sat*128.0/100);
				c1 = new Color(dig,255,dig);
				break;
			case 7:		// Magenta
				dig = (int)(127.0+sat*128.0/100);
				c1 = new Color(255,dig,255);
				break;
			case 8:		// Orange
				dig = (int)(127.0+sat*128.0/100);
				dig1 = (int)(sat*255.0/100);
				c1 = new Color(255,dig,dig1);
				break;
			case 9:		// Pink
				dig = (int)(163.0+sat*93.0/100);
				c1 = new Color(255,dig,dig);
				break;
			case 10:	// Red
				dig = (int)(sat*255.0/100);
				c1 = new Color(255,dig,dig);
				break;
			case 11:	// White - Shades from light grey to whitw
				dig = (int)(163.0+sat*93.0/100);
				c1 = new Color(dig,dig,dig);
				break;
			case 12:	// Yellow
				dig = (int)(sat*255.0/100);
				c1 = new Color(255,255,dig);
				break;
			default:
				dB.dbg(0,"Color " +idx + " not yet supported.");
				c1 = null;
				break;
		}
		}catch(Exception e){
			dB.dbg(0, "in shadedColor Exception "+e);
			dB.dbg(0, "sat "+sat+" case "+idx);
			c1 = colors[idx];
		}
		return c1;
	}	// end shadedColor
				
}  // end class Plane
>

Top