import java.applet.*; import java.awt.*; public class Tripolar extends Applet implements Runnable { Thread kicker_ = null; boolean timeToDie_; private int speed_; Image offScrImage_; Graphics offScrGC_; int screenWidth_, screenHeight_; double centerX_, centerY_; double scale_; boolean down_ = false; Point mousePt_; double probeX_ = 1e10, probeY_ = 1e10; double damping_ = 0.97; double gravity_ = 0.005; double magnetism_ = 0.1; double height_ = 0.1; double mass_ = 1.0; double dtSim_ = 0.01; double [] magnetX_, magnetY_; 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; } } } public void start() { requestFocus(); if (kicker_ == null) { kicker_ = new Thread(this); kicker_.start(); } } 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_); offScrImage_ = createImage(screenWidth_, screenHeight_); offScrGC_ = offScrImage_.getGraphics(); setBackground(Color.white); setForeground(Color.black); mousePt_ = new Point(); magnetX_ = new double [3]; magnetY_ = new double [3]; setMagnets(0.5); timeToDie_ = false; } 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); } 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 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; 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; } fX -= vX*damping_; fY -= vY*damping_; vX += dtSim_ * fX / mass_; vY += dtSim_ * fY / mass_; 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_)); } public void update(Graphics g) { paint(g); } void updateAnimation() { if (!down_) return; 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) { probeX_ += dx/2; probeY_ += dy/2; } else { probeX_ += dx * 0.01; probeY_ += dy * 0.01; } } public void paint(Graphics g) { offScrGC_ = offScrImage_.getGraphics(); offScrGC_.setColor(Color.white); offScrGC_.fillRect(0, 0, screenWidth_, screenHeight_); if (probeX_ != 1e10) { updatePaths((probeX_ - centerX_) / scale_, (probeY_ - centerY_) / scale_, offScrGC_); } 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) { requestFocus(); return true; } }

Code with comments