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 the pendulum and draws the path
   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