//
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;
}
}