import java.applet.*;
import java.awt.*;
// Tripolar by Scott Snibbe. July 2002.
// Tripolar simulates a pendulum swinging above three magnets. The program draws
// the complete path that a pendulum would follow if it were released
// above the table exactly at the mouse point. This is a well-known
// chaotic system - very small changes to the starting position produce
// large changes to the path and which magnet on which it unltimately ends.
// The program drags the starting position slowly towards the actual
// mouse position, so that one can explore points between pixels, simulating
// a screen resolution hundreds of times the actual pixel resolution.
// The source code demonstrates the "meta-chaos" of the program itself.
// A set of key variables defines all the parameters of the simulation.
// Changing any of these parameters radically alters the artwork, in most
// cases making it non-functional - in some cases the program will hang,
// in others the paths will explode, implode or oscillate.
// By its title, the program is also meant to suggest the connection between
// mental states and these chaotic phenomena - even small, simple physical
// systems are as unpredictable and sensitive to initial conditions as our
// own minds. Chaos and complexity reign at all scales.
public class Tripolar extends Applet implements Runnable
{
// Controls whether we're updating animation
Thread kicker_ = null;
// Indication that animation thread has halted
boolean timeToDie_;
// Milliseconds between frame updates
private int speed_;
// Offscreen buffer for double-buffering
Image offScrImage_;
Graphics offScrGC_;
int screenWidth_, screenHeight_;
double centerX_, centerY_;
double scale_;
boolean down_ = false;
Point mousePt_;
double probeX_ = 1e10, probeY_ = 1e10;
// These constants define the artwork - very slight changes to parameters
// will make the program, implode, explode, or simply less interesting.
// As a work exploring chaotic phenomena, these parameters show the "meta-chaos"
// of the program itself - a nonlinear system extremely sensitive to
// initial conditions.
// These choices also represent the hand of the individual artist in an
// algorithmic/technical work.
double damping_ = 0.97; // damping constant
double gravity_ = 0.005; // gravitational constant
double magnetism_ = 0.1;
double height_ = 0.1; // vertical distance from magnets
double mass_ = 1.0; // mass of pendulum
double dtSim_ = 0.01; // time step
double [] magnetX_, magnetY_;
// Thread proc which updates animation
public void run() {
while (kicker_ != null) {
updateAnimation();
repaint();
try {
if (!timeToDie_) {
Thread.sleep(speed_);
} else {
if (kicker_ != null) {
kicker_.stop();
kicker_ = null;
break;
}
}
} catch (InterruptedException e) {
break;
}
}
}
// Start the applet
public void start() {
requestFocus();
if (kicker_ == null) {
kicker_ = new Thread(this);
kicker_.start();
}
}
// Stop the applet.
public void stop() {
timeToDie_ = true;
}
public void init()
{
speed_ = 33;
screenWidth_ = size().width;
screenHeight_ = size().height;
centerX_ = (double) screenWidth_ / 2.0;
centerY_ = (double) screenHeight_ / 2.0;
scale_ = Math.min(centerX_, centerY_); // scale to normalize
// Create offscreen buffer
offScrImage_ = createImage(screenWidth_, screenHeight_);
offScrGC_ = offScrImage_.getGraphics();
// Background color for our applet
setBackground(Color.white);
setForeground(Color.black);
mousePt_ = new Point();
magnetX_ = new double [3];
magnetY_ = new double [3];
setMagnets(0.5);
timeToDie_ = false;
}
// create three magnets at given radius from center
void setMagnets(double r) {
magnetX_[0] = r * Math.cos(Math.PI/2);
magnetY_[0] = r * Math.sin(Math.PI/2);
magnetX_[1] = r * Math.cos(Math.PI/2 + (2*Math.PI) / 3);
magnetY_[1] = r * Math.sin(Math.PI/2 + (2*Math.PI) / 3);
magnetX_[2] = r * Math.cos(Math.PI/2 - (2*Math.PI) / 3);
magnetY_[2] = r * Math.sin(Math.PI/2 - (2*Math.PI) / 3);
}
// performs the simulation of th/v2/img/enterproject.gif
void updatePaths(double x, double y, Graphics g) {
double vX=0, vY=0; // velocity
double fX,fY;
double r, over_rsq, over_rcube;
double dx, dy;
double filtVel = 1;
double lastX, lastY;
int iter = 0;
g.setColor(Color.black);
while (filtVel > 0.1 && iter < 10000) {
iter++;
fX=0;
fY=0; // zero forces
// simulate gravitational pull towards center
r = x*x + y*y;
if (r < 0.00001)
r= 0.00001;
over_rsq = 1.0 / r;
fX -= (x * gravity_) * over_rsq;
fY -= (y * gravity_) * over_rsq;
// simulate magnetic forces towards three magnets using
// Coulomb's 2nd law - attraction falls off with distance square
for (int m = 0; m < 3; m++) {
dx = magnetX_[m] - x;
dy = magnetY_[m] - y;
r = Math.sqrt(dx*dx + dy*dy + height_*height_);
if (r < 0.00001)
r = 0.00001;
over_rcube = 1.0 / (r*r*r);
fX += magnetism_ * dx * over_rcube;
fY += magnetism_ * dy * over_rcube;
}
// friction proportional to velocity
fX -= vX*damping_;
fY -= vY*damping_;
// Apply forces using Newton's Law: F=mA
vX += dtSim_ * fX / mass_;
vY += dtSim_ * fY / mass_;
// compute filtered velocity, to determine when the pendulum stops
filtVel = 0.99 * filtVel + 0.1 * Math.max(Math.abs(vX), Math.abs(vY));
lastX = x;
lastY = y;
x += vX;
y += vY;
drawNormalizedLine(lastX, lastY, x, y, g);
}
}
void drawNormalizedLine(double x1, double y1, double x2, double y2, Graphics g) {
g.drawLine((int) Math.round(x1 * scale_ + centerX_),
(int) Math.round(y1 * scale_ + centerY_),
(int) Math.round(x2 * scale_ + centerX_),
(int) Math.round(y2 * scale_ + centerY_));
}
// Override update so that it only calls paint.
public void update(Graphics g) {
paint(g);
}
void updateAnimation() {
if (!down_) return;
// interpolate from probe position to mouse position
if (probeX_ == 1e10) {
probeX_ = mousePt_.x;
probeY_ = mousePt_.y;
}
double dx = mousePt_.x - probeX_;
double dy = mousePt_.y - probeY_;
double distSq = dx*dx + dy*dy;
if (distSq > 1) { // exponentially proceed to pixel clicked on
probeX_ += dx/2;
probeY_ += dy/2;
} else { // logarithmically interpolate over last pixel
// This allows one to explore values between pixels
// by "pulling" the actual probe very slowly towards
// the mouse position.
probeX_ += dx * 0.01;
probeY_ += dy * 0.01;
}
}
public void paint(Graphics g)
{
offScrGC_ = offScrImage_.getGraphics();
// Draw all the graphics onto offscreen buffer
offScrGC_.setColor(Color.white);
offScrGC_.fillRect(0, 0, screenWidth_, screenHeight_);
if (probeX_ != 1e10) {
updatePaths((probeX_ - centerX_) / scale_,
(probeY_ - centerY_) / scale_, offScrGC_);
}
// Copy the offscreen buffer onscreen
g.drawImage(offScrImage_, 0, 0, this);
}
public boolean mouseDown(Event evt, int x, int y) {
down_ = true;
mousePt_.setLocation(x, y);
return true;
}
public boolean mouseUp(Event evt, int x, int y) {
down_ = false;
return true;
}
public boolean mouseDrag(Event evt, int x, int y) {
if (down_) {
mousePt_.setLocation(x, y);
}
return true;
}
public boolean mouseEnter(Event evt, int x, int y) {
// Request that events are delivered to our applet
requestFocus();
return true;
}
}
Code without comments