
/*******************************************************************************


Class Moire implements an animated Moire pattern.  A Moire patterns occurs when
two similar patters are almost superimposed.  There is a kind of visual interference.
In this case, the pattern consists of lines radiating out from a common center.
One such pattern is drawn with the lines radiating out from the center of the
applet.  A second pattern has a center that drifts about, producing a changing
interference pattern.

You can also click and drag on the applet to move the second pattern about by
hand.

Several parameters can be set by <param> tags:

    Name       Default    Legal values     Meaning 
    ---------  ---------  ---------------  -----------------------------------
    lineCount   36         1 to 100         Number of lines drawn in each set
                                               of lines.  (Note:  each "line"
                                               gives two "spokes" radiating from
                                               the center.)
    lineColor   red        any color        Color of the lines.
    bgColor     cyan       any color        Color seen behind the lines.
    sleepTime   25         1 to 5000        Time, in milliseconds, between
                                               movements of the pattern; smaller
                                               values give faster movements.
    border      0          0 to 50          Width of border drawn around the
                                               Moire pattern; note that the
                                               default is to have no border.
    borderColor blue       any color        Color of the border.
    
Note that a color can be specified either as a set of three integers between
0 and 255, giving the red, blue, and green components of the color, or it
can be specified as one of the built-in color names: white, black, red, green,
blue, yellow, cyan, magneta, pink, orange, gray, lightGray, or darkGray.
Color names are not case sensitive, although param names are.

Note:  You might find the method getColorParam() useful in other applications.

Modified in July 1998 to be fully complient with Java 1.1.

BY:  David Eck
     Department of Mathematics and Computer Science
     Hobart and William Smith Colleges
     Geneva, NY   14456
     
     E-mail:  eck@hws.edu


NOTE:  YOU CAN DO ANYTHING YOU WANT WITH THIS CODE AND APPLET, EXCEPT
       TRY TO COPYRIGHT OR PATENT THEM YOURSELF.

*******************************************************************************/


import java.awt.*;
import java.awt.event.*;
import java.util.Random;

public class Moire extends java.applet.Applet 
                     implements Runnable, MouseListener, MouseMotionListener {

   static final Random rand = new Random();

   Thread runner;   // thread to produce the animation

   Image buffer = null;  // an off-screen canvas
   int w,h;       // width and height of the buffer
   double center_x, center_y;   // current position of the center of
                                // the second set of lines.
   int cx,cy;  // for use in mouseDrag, mouseExit;
               // position of center during dragging

   
   int mouseStart_x, mouseStart_y;   // used during dragging to hold the
                                     // location of the original mouse click
   volatile boolean dragging = false;    // set to "true" while dragging is in
                                         // progress, as signal to "run" method
                                         // to pause the regular animation.
   volatile boolean stopped = false;   // toggled when user shift-clicks on the
                                       // applet.
                            
   final static int GO = 0, SUSPEND = 1, TERMINATE = 2;           
   volatile int status = GO;
   
   int sleepTime = 25;             // applet <param>'s, described above
   Color backColor = Color.cyan;
   Color lineColor = Color.red;
   int lines = 36;
   int border = 0;
   Color borderColor = Color.blue;
      
   double[] cos, sin;  // hold sines and cosines of angle; one for each line
                       // line to be drawn.  This just avoids recomputing this
                       // all the time.
   
   public void init() {
      Color c;
      Integer temp;
      if ( (c = getColorParam("bgColor")) != null )
         backColor = c;
      if ( (c = getColorParam("lineColor")) != null )
         lineColor = c;
      if ( (temp = getIntParam("lineCount")) != null )
         lines = temp.intValue();
      if (lines < 1 || lines > 100)
         lines = 36;
      if ( (temp = getIntParam("sleepTime")) != null )
         sleepTime = temp.intValue();
      if (sleepTime < 1 || sleepTime > 5000)
         sleepTime = 25;
      if ( (temp = getIntParam("border")) != null )
         border = temp.intValue();
      if (border < 0 || border > 50)
         border = 0;
      if ( (c = getColorParam("borderColor")) != null )
         borderColor = c;
      setBackground(borderColor);
      double delta = 180.0 / lines;
      cos = new double[lines];
      sin = new double[lines];
      for (int i = 0; i < lines; i++) {
         double angle = ((i * delta * Math.PI)/180.0);
         cos[i] = Math.cos(angle);
         sin[i] = Math.sin(angle);  
      }
      this.addMouseListener(this);
      this.addMouseMotionListener(this);
   }
   
    Integer getIntParam(String paramName) {
       // retrieve an integer <param>; return null if the specified
       // param is not present or if the value is illegal
       String param = getParameter(paramName);
       if (param == null)
          return null;
       int i;
       try {
          i = Integer.parseInt(param);
       }
       catch (NumberFormatException e) {
          return null;
       }
       return new Integer(i);
    }
    
    Color getColorParam(String paramName) {
       // retrieve a color <param>; return null if the specified
       // param is not present or if the value is illegal.  Legal
       // values include the 13 named Java colors ("red", "black",
       // etc.) and RGB values given as 3 integers between 0 and 255.
       // Color names are not case sensitive.  Integers in RGB
       // colors can be separated by any non-digit characters.
       String param = getParameter(paramName);
       if (param == null || param.length() == 0)
          return null;
       if (Character.isDigit(param.charAt(0))) {  // try to parse RGB color
          int r=0,g=0,b=0;
          int pos=0;
          int d=0;
          int len=param.length();
          while (pos < len && Character.isDigit(param.charAt(pos)) && r < 255) {
              d = Character.digit(param.charAt(pos),10);
              r = 10*r + d;
              pos++;
          }
          if (r > 255)
             return null;
          while (pos < len && !Character.isDigit(param.charAt(pos)))
             pos++;
          if (pos >= len)
             return null;
          while (pos < len && Character.isDigit(param.charAt(pos)) && g < 255) {
              d = Character.digit(param.charAt(pos),10);
              g = 10*g + d;
              pos++;
          }
          if (g > 255)
             return null;
          while (pos < len && !Character.isDigit(param.charAt(pos)))
             pos++;
          if (pos >= len)
             return null;
          while (pos < len && Character.isDigit(param.charAt(pos)) && b < 255) {
              d = Character.digit(param.charAt(pos),10);
              b = 10*b + d;
              pos++;
          }
          if (b > 255)
             return null;
          return new Color(r,g,b);          
       }
       param.toLowerCase();
       if (param.equals("black"))
          return Color.black;
       if (param.equals("white"))
          return Color.white;
       if (param.equals("red"))
          return Color.red;
       if (param.equals("green"))
          return Color.green;
       if (param.equals("blue"))
          return Color.blue;
       if (param.equals("yellow"))
          return Color.yellow;
       if (param.equals("cyan"))
          return Color.cyan;
       if (param.equals("magenta"))
          return Color.magenta;
       if (param.equals("pink"))
          return Color.pink;
       if (param.equals("orange"))
          return Color.orange;
       if (param.equals("gray"))
          return Color.gray;
       if (param.equals("darkgray"))
          return Color.darkGray;
       if (param.equals("lightgray"))
          return Color.lightGray;
       return null;  // param is not a legal color
    }

   synchronized public void start() {
      if (runner == null || !runner.isAlive()) {
         runner = new Thread(this);
         status = GO;
         runner.start();
      }
      else {
         status = GO;
         notify();
      }
   }
   
   synchronized public void stop() {
      if (runner != null && runner.isAlive()) {
         status = SUSPEND;
         notify();
      }
   }
   
   synchronized public void destroy() {
      if (runner != null && runner.isAlive()) {
         status = TERMINATE;
         notify();
         try {
            runner.join(1000);
         }
         catch (InterruptedException e) {
         }
         if (runner.isAlive())
            runner.stop();
      }
   }
   
   synchronized public void mousePressed(MouseEvent evt) {
      if (evt.isShiftDown()) {  // toggle the drifting of the pattern
         stopped = !stopped;
      }
      else {  // begin dragging lines
         mouseStart_x = evt.getX();
         mouseStart_y = evt.getY();
         dragging = true;
      }
      notify();
   }
   
   synchronized public void mouseDragged(MouseEvent evt) {
      if (dragging) {
         int offset_x = evt.getX() - mouseStart_x;
         int offset_y = evt.getY() - mouseStart_y;
         cx = (int)center_x + offset_x;
         cy = (int)center_y + offset_y;
         Graphics g = buffer.getGraphics();
         makeMoire(g,w,h,cx,cy);
         g.dispose();
         repaint();
      }
   }
      
   synchronized public void mouseReleased(MouseEvent evt) {
      if (dragging) {
         dragging = false;
         center_x = center_x + evt.getX() - mouseStart_x;
         center_y = center_y + evt.getY() - mouseStart_y;
      }
      notify();
   }
   
   public void mouseClicked(MouseEvent evt) { }  // to satify the MouseListener/MouseMotionListener interfaces
   public void mouseEntered(MouseEvent evt) { }
   public void mouseMoved(MouseEvent evt) { }
   public void mouseExited(MouseEvent evt) { }
   
   synchronized public void paint(Graphics g) {  // paint method just copies canvas to applet
      g.drawImage(buffer,border,border,this);
   }
   
   public void update(Graphics g) {  // replace standard update method so it doesn't
                                     // erase the screen
      paint(g);
   }
   
   synchronized void makeMoire(Graphics g, int w, int h, int cx, int cy) {
     // Create the Moire pattern in the buffer.  w and h give the
     // size of the buffer (and yes, that could have been gotten from g).
     // cx and cy give the position of the center of the second set of lines.
      g.setColor(backColor);
      g.fillRect(0,0,w,h);
      g.setColor(lineColor);
      drawMoire(g,2*w,2*h,w/2,h/2);
      drawMoire(g,2*w,2*h,cx,cy);
   }   

   void drawMoire(Graphics g, int width, int height, int center_x, int center_y) {
     // one set of lines, with center at center_x, center_y
     int s = ( (width > height)? width : height );
     s = (int)((double)s * 0.72);
     for (int i = 0; i < lines; i++) {
        int x = (int)(s*cos[i]);
        int y = (int)(s*sin[i]);
        g.drawLine(center_x + x, center_y + y, center_x - x, center_y - y);
     }
   }
   
   public void run() {        // run method for animation thread
   
      if (buffer == null) {
         w = getSize().width - 2*border;   // get the width and height of the canvas
         h = getSize().height - 2*border;  //   (Applet is not effectively resizable.)
         buffer = createImage(w,h);  // create an off screen canvas of the same size
      }
      center_x = w / 2;   // initial position of center of second set of lines
      center_y = h / 2;
      double x_min = center_x - 25;   // limits for wandering of second set of lines
      double x_max = center_x + 25;
      double y_min = center_y - 25;
      double y_max = center_y + 25;
      double dx, dy;   // amount by which center should move at each step
      do {
          dx = 5 * (rand.nextDouble() - 0.5);
          dy = 5 * (rand.nextDouble() - 0.5);
      } while (dx*dx + dy*dy < 1);
      
      while (true) {
        synchronized(this) { 
           while (status == SUSPEND || ( (stopped || dragging) && status != TERMINATE) ) {
              try {
                 wait();
              }
              catch (InterruptedException e) {
              }
           }
        }
        if (status == TERMINATE)
           return;
        if (!dragging && !stopped) {
          center_x += dx;   // drift by amount (dx,dy)
          center_y += dy;
          if (center_x < x_min) {  // make sure motion is towards the right
             do {
                dx = rand.nextDouble() * 2.5;
             } while (dx*dx + dy*dy < 1);
          }
          else if (center_x > x_max) {  // make sure motion is towards the left
             do {
                dx = - rand.nextDouble() * 2.5;
             } while (dx*dx + dy*dy < 1);
          }
          if (center_y < y_min) {  // make sure motion is downwards
             do {
                dy = rand.nextDouble() * 2.5;
             } while (dx*dx + dy*dy < 1);
          }
          else if (center_y > y_max) {  // make sure motion is upwards
             do {
                dy = - rand.nextDouble() * 2.5;
             } while (dx*dx + dy*dy < 1);
          }
          if (rand.nextDouble() < 0.01) {  // Occasionally, change motion vector at random
              do {
                dx = 5 * (rand.nextDouble() - 0.5);
                dy = 5 * (rand.nextDouble() - 0.5);
              } while (dx*dx + dy*dy < 1);
          }
          Graphics g = buffer.getGraphics();   // create and display the pattern.
          makeMoire(g,w,h,(int)center_x,(int)center_y);
          g.dispose();
          repaint();
        }
        synchronized(this) {
          try {
             wait(sleepTime);
          }
          catch (InterruptedException e) {
          }
        }
      } // end while
      
   } // end run()

}  // end of class Moire
