//
import java.awt.*;
import java.util.*;

public class topView extends BufferedApplet
{
   double time0 = 0, theta = 0, rate = 3;
   double X[], Y[], TX[], TY[];
   Color color[] = {
      new Color(255,0,0),   // RED
      new Color(255,160,0), // ORANGE
      new Color(255,255,0), // YELLOW
      new Color(0,200,0),   // GREEN
      new Color(0,255,255), // CYAN
      new Color(0,0,255),   // BLUE
      new Color(200,0,255), // INDIGO
      new Color(255,0,190), // VIOLET
      new Color(0,0,0),     // BLACK
   };

   // INITIALIZE EVERYTHING

   public void init() {
      super.init();
      time0 = System.currentTimeMillis() / 1000.; // INITIAL CLOCK TIME
      X = new double[color.length];               // ALLOCATE ARRAYS
      Y = new double[color.length];
      TX = new double[color.length];
      TY = new double[color.length];
      Random R = new Random();
      for (int i = 0 ; i < color.length ; i++) {  // ASSIGN
         X[i] = 2 * (R.nextDouble() - .5);        // RANDOM POSITIONS
         Y[i] = 2 * (R.nextDouble() - .5);
         TX[i] = i % 3 - 1;                       // ASSIGN TARGET
         TY[i] = i / 3 - 1;                       // POSITIONS
      }
   }

   double C0 = -10, S0; // PREVIOUS SINE AND COSINE
   double R = .2;       // DISK SIZE

   public void render(Graphics g) {

      // CLEAR THE WINDOW

      g.setColor(Color.gray.brighter());
      g.fillRect(0,0,width(),height());

      int r = R2r(R);

      // ADVANCE TO NEXT STIRRING ANGLE

      theta = rate * Math.PI * (System.currentTimeMillis()/1000.-time0);
      double C = .06 * Math.cos(theta);
      double S = .06 * Math.sin(theta);

      if (C0 == -10) {
         C0 = C;
         S0 = S;
      }

      // DRAW THE STIRRER TABLE

      int x0 = width()/10, y0 = height()/10;
      int x = x0 + R2r(C), y = y0 - R2r(S), w = 8*width()/10;

      int d = R2r(.09);
      g.setColor(Color.gray);
      g.fillOval(x0  -d, y0  -d, 2*d,2*d);
      g.fillOval(x0+w-d, y0  -d, 2*d,2*d);
      g.fillOval(x0+w-d, y0+w-d, 2*d,2*d);
      g.fillOval(x0  -d, y0+w-d, 2*d,2*d);

      g.setColor(Color.white);
      g.fillRect(x,y,w,w);
      g.setColor(Color.black);
      g.drawRect(x,y,w,w);

      // SHAKE EACH DISK TOWARD ITS TARGET

      for (int i = 0 ; i < X.length ; i++)
         if (i != ci) {
	    double DX = TX[i] - X[i], DY = TY[i] - Y[i];
	    if (DX*S + DY*C < 0) {
	       double ampl = Math.min(1, Math.sqrt(DX*DX + DY*DY) / R);
	       if (ampl > .01) {
	          X[i] += ampl * (C - C0);
	          Y[i] -= ampl * (S - S0);
	       }
	    }
         }

      // DON'T LET DISKS GO OFF TABLE

      for (int i = 0 ; i < X.length ; i++) {
         X[i] = Math.min(X[i], 1.5 - R/2);
         X[i] = Math.max(X[i], R/2 - 1.5);
         Y[i] = Math.min(Y[i], 1.5 - R/2);
         Y[i] = Math.max(Y[i], R/2 - 1.5);
      }

      // DON'T LET DISKS INTERPENETRATE EACH OTHER

      for (int i = 0 ; i < X.length-1 ; i++)
      for (int j = i+1 ; j < X.length ; j++) {
         double DX = X[j] - X[i], DY = Y[j] - Y[i];
	 if (DX*DX + DY*DY < 4*R*R) {
	    double t = Math.sqrt(DX*DX + DY*DY);
	    double u = DX / t * (2*R - t);
	    double v = DY / t * (2*R - t);
	    double f = ci == i ? 0 : ci == j ? 1 : .5;
	    X[i] -= u * f;
	    Y[i] -= v * f;
	    X[j] += u * (1-f);
	    Y[j] += v * (1-f);
	 }
      }

      // DRAW THE DISKS

      for (int i = 0 ; i < X.length ; i++) {
         g.setColor(color[i].darker());
         g.fillOval(X2x(X[i])-r+2, Y2y(Y[i])-r+2, 2*r, 2*r);
         g.setColor(color[i]);
         g.fillOval(X2x(X[i])-r, Y2y(Y[i])-r, 2*r, 2*r);
      }

      // REMEMBER PREVIOUS STIR POSITION.  FORCE ANIMATION.

      C0 = C;
      S0 = S;
      animating = true;
   }

   // CONVERSION BETWEEN CONTINUOUS TABLE COORDS AND PIXEL COORDS

   int X2x(double X) { return (int)(width ()/2 + width()/4 * X); }
   int Y2y(double Y) { return (int)(height()/2 + width()/4 * Y); }
   int R2r(double R) { return (int)(             width()/4 * R); }

   double x2X(int x) { return (double)(x - width ()/2) / (width()/4); }
   double y2Y(int y) { return (double)(y - height()/2) / (width()/4); }
   double r2R(int r) { return (double) r               / (width()/4); }

   // LET USER DRAG A DISK AROUND WITH THE MOUSE

   int mx, my, ci = -1;

   public boolean mouseDown(Event e, int x, int y) {
      for (ci = X.length-1 ; ci >= 0 ; ci--) {
         double DX = x2X(x) - X[ci], DY = y2Y(y) - Y[ci];
	 if (DX*DX + DY*DY < R*R)
	    break;
      }
      mx = x;
      my = y;
      return true;
   }
   public boolean mouseDrag(Event e, int x, int y) {
      if (ci >= 0) {
         X[ci] += r2R(x - mx);
         Y[ci] += r2R(y - my);
         mx = x;
         my = y;
      }
      return true;
   }
   public boolean mouseUp(Event e, int x, int y) {
      ci = -1;
      return true;
   }

   // LET USER TOGGLE RATE WITH SPACE KEY

   public boolean keyUp(Event e, int key) {
      switch (key) {
      case ' ':
         rate = rate == 3 ? 9 : 3;
	 break;
      }
      return true;
   }
}