//
import java.awt.*;
import java.util.*;
/*
THINGS TO ADD:
Cleaner API - arrays should be created internally
Shapes other than circular disk
Allow some robots to be more "stubborn" than others
General obstacle objects
*/
public class pathApplet extends BufferedApplet
{
int w = 0, h = 0, step = 0, nSteps = 1<<6, relaxing = 0, randomSeed = 0;
PathFinder pf;
// EACH ROBOT HAS A RADIUS OF PERSONAL SPACE
double radius[] = {.20,.18,.16,.14,.12,.10};
// EACH ROBOT HAS A COLOR
double color[][] = {
{1 , 0, 0}, // RED
{1 ,.5, 0}, // ORANGE
{1 ,.8, 0}, // YELLOW
{0 ,.7, 0}, // GREEN
{0 , 0, 1}, // BLUE
{.5, 0,.5}, // VIOLET
};
double X[][] = new double[radius.length][nSteps+1];
double Y[][] = new double[radius.length][nSteps+1];
// DISPLAY EVERYTHING
public void render(Graphics g) {
// MAKE INITIAL PATH
if (w == 0) {
w = bounds().width;
h = bounds().height;
pf = new PathFinder(radius, X, Y);
newRandomPaths(randomSeed);
}
// BLANK OUT THE APPLET WINDOW
g.setColor(Color.white);
g.fillRect(0,0,w,h);
// DRAW THE SOLID DISK OF EACH ROBOT
for (int i = 0 ; i < radius.length ; i++) {
g.setColor(new Color((float)(.7+.3*color[i][0]),
(float)(.7+.3*color[i][1]),
(float)(.7+.3*color[i][2])));
int x = X2x(X[i][step]);
int y = Y2y(Y[i][step]);
int r = R2r(radius[i]);
g.fillOval(x-r,y-r,2*r+1,2*r+1);
}
// DRAW THE PATHS AND THE ROBOT OUTLINES
for (int i = 0 ; i < radius.length ; i++) {
g.setColor(new Color((float)color[i][0],
(float)color[i][1],
(float)color[i][2]));
for (int s = 0 ; s <= nSteps ; s++) {
int x = X2x(X[i][s]);
int y = Y2y(Y[i][s]);
g.fillOval(x-1,y-1,3,3);
}
int x = X2x(X[i][step]);
int y = Y2y(Y[i][step]);
int r = R2r(radius[i]);
g.drawOval(x-r,y-r,2*r,2*r);
int d = step==0 || step==nSteps ? 5 : 3;
g.fillOval(x-d,y-d,2*d,2*d);
}
// DRAW THE "SEED POINTS" RECTANGLE, AND SHOW THE SETUP ID
g.setColor(Color.black);
//g.drawRect(X2x(-.75),Y2y(.75),R2r(1.5),R2r(1.5));
g.drawString("" + randomSeed, 4, 14);
// GRADUALLY RELAX THE PATHS TO SMOOTH THEM OUT
if (relaxing-- > 0) {
pf.relax();
animating = true;
}
}
// RESPOND TO USER KEY COMMANDS
public boolean keyUp(Event e, int key) {
switch (key) {
case '-': // MAKE ROBOTS SMALLER
changeSizes(-.02);
break;
case '+': // MAKE ROBOTS LARGER
changeSizes(.02);
break;
case 1002: // PAGE UP: GO TO END OF PATHS
step = nSteps;
damage = true;
break;
case 1003: // PAGE DOWN: GO TO START OF PATHS
step = 0;
damage = true;
break;
case 1004: // UP ARROW: ADVANCE ALONG PATHS
step = Math.min(step + nSteps/32, nSteps);
damage = true;
break;
case 1005: // DOWN ARROW: RETREAT ALONG PATHS
step = Math.max(step - nSteps/32, 0);
damage = true;
break;
case 1006: // LEFT ARROW: PREVIOUS RANDOM SETUP
newRandomPaths(--randomSeed);
break;
case 1007: // RIGHT ARROW: NEXT RANDOM SETUP
newRandomPaths(++randomSeed);
break;
}
return true;
}
// ALLOW USER TO DRAG ROBOTS AROUND WITH THE MOUSE
int mx, my, id = -1;
public boolean mouseDown(Event e, int x, int y) {
// ON MOUSE DOWN: SEE WHAT DISK, IF ANY, THE MOUSE IS OVER
if (step == 0 || step == nSteps)
for (id = radius.length-1 ; id >= 0 ; id--) {
double dx = x2X(x) - X[id][step];
double dy = y2Y(y) - Y[id][step];
if (dx*dx + dy*dy < radius[id]*radius[id])
break;
}
mx = x;
my = y;
return true;
}
public boolean mouseDrag(Event e, int x, int y) {
if (id >= 0) {
X[id][step] += r2R(x - mx);
Y[id][step] -= r2R(y - my);
mx = x;
my = y;
pf.repel(step);
pf.modifyPaths();
relaxing = 500;
damage = true;
}
return true;
}
public boolean mouseUp(Event e, int x, int y) {
id = -1;
damage = true;
return true;
}
// CHANGE SIZES OF ROBOTS (WITHIN LIMITS)
void changeSizes(double delta) {
// MAKE SURE WE'RE NOT TRYING TO MAKE ROBOTS TOO BIG OR TOO SMALL
double minRadius = 100, maxRadius = -100;
for (int i = 0 ; i < radius.length ; i++) {
minRadius = Math.min(minRadius, radius[i]);
maxRadius = Math.max(maxRadius, radius[i]);
}
// IF WITHIN LIMITS, THEN CHANGE SIZES AND RECOMPUTE THE PATHS
if (minRadius + delta > .01 && maxRadius + delta < .4) {
for (int i = 0 ; i < radius.length ; i++)
radius[i] += delta;
pf.repel(0);
pf.repel(nSteps);
pf.findPaths();
damage = true;
}
}
// MAKE A NEW PATH, GIVEN A RANDOM SEED
void newRandomPaths(int seed) {
// CHOOSE RANDOM POSITIONS AT START AND END OF PATH
Random rand = new Random(seed);
randomize(rand, 0);
randomize(rand, nSteps);
// FIND THE PATHS, AND START GRADUAL RELAXATION
pf.findPaths();
step = 0;
relaxing = 500;
damage = true;
}
// ARRANGE ROBOTS IN SOME RANDOM CONFIGURATION AROUND A SQUARE
void randomize(Random rand, int s) {
for (int i = 0 ; i < radius.length ; i++) {
double x = (Math.abs(rand.nextDouble()) % 1) - .5;
double y = (Math.abs(rand.nextDouble()) % 1) - .5;
if (Math.abs(x) > Math.abs(y)) {
y /= Math.abs(x);
x /= Math.abs(x);
}
else {
x /= Math.abs(y);
y /= Math.abs(y);
}
X[i][s] = .75*x;
Y[i][s] = .75*y;
}
pf.repel(s);
}
// CONVERT SCREEN COORDS TO ROBOT COORDS
double x2X(int x) { return (double)(x - w/2) / (w/2); }
double y2Y(int y) { return (double)(y - h/2) / -(w/2); }
double r2R(int r) { return (double)r / (w/2); }
// CONVERT ROBOT COORDS TO SCREEN COORDS
int X2x(double X) { return (int)(w/2 + X * w/2); }
int Y2y(double Y) { return (int)(h/2 - Y * w/2); }
int R2r(double R) { return (int)(R * w/2); }
}