//--------------------------------------------------------------------------
//  I M P A C T          Version 2.3
//
//  (C)  COPYRIGHT International Business Machines Corp. 1996
//
//  by John Henckel, john@formulus.com              June 1996
//
//  This is a little java program that simulates the interactions of balls.
//  You can grab the balls with your mouse and drag them or throw them.  You
//  can control the forces of viscosity and gravity.  You can make the walls
//  be bouncy, or wrap-around.  You can turn on trace and make interesting
//  designs.  Have fun with it!
//
//  The public class (Impact) can be executed as either an applet from html
//  or as a standalone application using the java interpreter.
//
//  If you have any comments about Impact, or if you make any modifications
//  to the program, please send me a note.
//
//  changes for 2.2
//        fixed gravity to be m/r^2 instead of m/r.  THANKS Ron Legere!
//      added a moon to the initial screen.
//        add fill option
//  changes for 2.3
//      fix the add ball button.   THANKS Jim Worley
//
//---------------------------------------------------------------------------
//  Permission to use, copy, modify, distribute and sell this software
//  and its documentation for any purpose is hereby granted without fee,
//  provided that the above copyright notice appear in all copies and
//  that both that copyright notice and this permission notice appear
//  in supporting documentation.
//
//  This program has not been thoroughly tested under all conditions.  IBM,
//  therefore, cannot guarantee or imply reliability, serviceability, or
//  function of this program.  The Program contained herein is provided
//  'AS IS'.  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
//  PARTICULAR PURPOSE ARE EXPRESSLY DISCLAIMED.
//  IBM shall not be liable for any lost revenue, lost profits or other
//  consequential damages caused by the Program even if advised of the
//  possibility of such damages.  Furthermore, IBM shall not be liable for
//  any delays, losses or any other damages which may result from
//  furnishing the Program, even if advised of the possibility of such
//  damages.

import java.awt.*;

//-------------------------------------------------------------------
//  This is the main applet class

public class Impact extends java.applet.Applet {
  MainWindow b;

  public void init() {
    add(new Button("Start"));
  }

  public boolean action(Event e,Object arg) {
    if (e.target instanceof Button &&
        ((String)arg).equals("Start")) {
      b = new MainWindow("Impact Simulator", true);
      b.setSize(640,480);
      b.show();
      b.start();
    }
    return true;
  }

  public void stop() {
    if (b!=null) b.stop();
  }

  //  These methods allow the applet to also run as an application.

  public static void main(String args[]) {
    new Impact().begin();
  }
  private void begin() {
    b = new MainWindow("Impact Simulator", false);
    b.setSize(640,480);
    b.show();
    b.start();
  }
}

//-------------------------------------------------------------------
//  This class controls the main window

class MainWindow extends Frame {
  Animator anim;
  Ticker tick;
  Thread anim_thread;
  Thread tick_thread;
  Dialog db;                // dialog box for new ball
  boolean is_applet;

  //  This method creates layout and main objects.

  MainWindow(String title, boolean isapp) {
    super(title);
    is_applet = isapp;
    Panel p = new Panel();        // control panel
    Label n = new Label("Impact 2.3");
    p.setLayout(new GridLayout(0,1));        // vertical layout
    p.setFont(new Font("Helvetica",Font.PLAIN,10));
    n.setFont(new Font("TimesRoman",Font.BOLD,16));
    p.add(n);
    p.add(new Button("add ball"));
    p.add(new Button("show data"));
    p.add(new Button("hide data"));
    p.add(new Button("delete ball"));
    p.add(new Button("quit"));
    tick = new Ticker(p);      // pace maker for the animator
    anim = new Animator(tick,p);
    setLayout(new BorderLayout(2,2));
    add("Center",anim);
    add("West",p);

    TextField t;
    db = new Dialog(this, "New Ball", false);
    db.setLayout(new GridLayout(8,2,2,0));
    db.add(new Label("Mass"));        db.add(t=new TextField(13)); t.setText("64");
    db.add(new Label("Radius"));db.add(t=new TextField(13)); t.setText("0");
    db.add(new Label("Color")); db.add(t=new TextField(13)); t.setText("any");
    db.add(new Label("X Loc")); db.add(t=new TextField(13)); t.setText("100");
    db.add(new Label("Y Loc")); db.add(t=new TextField(13)); t.setText("100");
    db.add(new Label("X Vel")); db.add(t=new TextField(13)); t.setText("2");
    db.add(new Label("Y Vel")); db.add(t=new TextField(13)); t.setText("1");
    db.setSize(200,300);
    db.setResizable(true);
  }

  //  This starts the threads.

  public void start() {
    if (anim_thread==null) {
      anim_thread = new Thread(anim);
      anim_thread.start();          // start new thread
    }
    if (tick_thread==null) {
      tick_thread = new Thread(tick);
      tick_thread.start();   // start new thread
    }
  }

  //  This stops the threads.

  public void stop() {
    if (anim_thread!=null) {
      anim_thread.stop();          // kill the thread
      anim_thread = null;          // release object
    }
    if (tick_thread!=null) {
      tick_thread.stop();          // kill the thread
      tick_thread = null;          // release object
    }
  }

  //  This handles user input events.

  public boolean action(Event e, Object arg) {
    if (e.target instanceof Button) {
      if (((String)arg).equals("clean"))
        anim.clearAll = true;
      else if (((String)arg).equals("delete ball"))
        anim.delBall();
      else if (((String)arg).equals("hide data"))
        db.hide();
      else if (((String)arg).equals("show data"))
        db.show();
      else if (((String)arg).equals("quit")) {
        stop();
        db.hide();
        hide();                // I don't know if all this is necessary.
        removeAll();
        dispose();
        if (!is_applet) System.exit(0);
      }
      else if (((String)arg).equals("add ball"))
        anim.addBall(new Ball(get_field(db,1), get_field(db,3),
                            get_color(db,5), get_field(db,7),
                            anim.ysize-get_field(db,9),
                            get_field(db,11), -get_field(db,13), anim));
      else if (((String)arg).equals("drift"))
        anim.zeroMomentum();
      else if (((String)arg).equals("center"))
        anim.centerMass();
      else if (((String)arg).equals("orbit"))
        anim.orbit();
      else return false;
      return true;
    }
    return false;
  }

  //  These are little utility methods to get a dialog data

  private double get_field(Dialog d, int i) {
    TextComponent t = (TextComponent)d.getComponent(i);
    if (t instanceof TextComponent)
      return Double.valueOf(t.getText()).doubleValue();
    return 1;
  }
  private Color get_color(Dialog d, int i) {
    TextComponent t = (TextComponent)d.getComponent(i);
    if (!(t instanceof TextComponent))        return Color.cyan;
    if (t.getText().equals("red"))        return Color.red;
    if (t.getText().equals("orange"))        return Color.orange;
    if (t.getText().equals("white"))        return Color.white;
    if (t.getText().equals("gray"))        return Color.gray;
    if (t.getText().equals("pink"))        return Color.pink;
    if (t.getText().equals("black"))        return Color.black;
    if (t.getText().equals("yellow"))        return Color.yellow;
    if (t.getText().equals("green"))        return Color.green;
    if (t.getText().equals("blue"))        return Color.blue;
    if (t.getText().equals("magenta"))        return Color.magenta;
    if (t.getText().equals("cyan"))        return Color.cyan;
    return new Color(Color.HSBtoRGB((float)Math.random(),1.0F,1.0F));
  }
}

//-------------------------------------------------------------------
//  This class performs the animation in the main canvas.

class Animator extends Canvas implements Runnable {
  final int max = 100;
  int num;                        // number of balls
  int cur,mx,my;                // current ball and mouse x y
  Ball[] ball = new Ball[max];        // array of balls
  Ticker tick;
  boolean clearAll;

  //  The following are some "physical" properties.  Each property
  //  has a value and a control.  The values are updated once per
  //  animation loop (this is for efficiency).

  public double g,mg,f,r;
  public boolean trc,col,mu,wr,sm,fi;
  public int xsize,ysize;
  Scrollbar grav,mgrav,fric,rest;
  Checkbox trace,collide,mush,wrap,smooth,filled;

  //  The ctor method creates initial objects.

  Animator(Ticker t, Panel p) {
    tick = t;
    setBackground(Color.black);
    grav = new Scrollbar(Scrollbar.HORIZONTAL,0,1,0,20);
    p.add(new Label("v-gravity"));
    p.add(grav);
    mgrav = new Scrollbar(Scrollbar.HORIZONTAL,10,1,0,20);
    p.add(new Label("m-gravity"));
    p.add(mgrav);
    fric = new Scrollbar(Scrollbar.HORIZONTAL,0,1,0,20);
    p.add(new Label("viscosity"));
    p.add(fric);
    rest = new Scrollbar(Scrollbar.HORIZONTAL,17,1,0,20);
    p.add(new Label("restitution"));
    p.add(rest);
    trace = new Checkbox("trace");    // initially false
    p.add(trace);
    collide = new Checkbox("collide");
    collide.setState(true);
    p.add(collide);
    mush = new Checkbox("mush");
    p.add(mush);
    wrap = new Checkbox("wrap");
    p.add(wrap);
    smooth = new Checkbox("flicker");
    p.add(smooth);
    filled = new Checkbox("filled");
    filled.setState(true);
    p.add(filled);
    p.add(new Button("orbit"));
    p.add(new Button("drift"));
    p.add(new Button("center"));
    p.add(new Button("clean"));

    //        Add three balls

    addBall(new Ball(100,0,Color.yellow,300,200,0.45,0,this));        // sun
    addBall(new Ball(16,0,Color.blue,300,100,-2.8,0,this));        // earth
    addBall(new Ball(1,0,Color.gray,300,85,-5.9,0,this));        // moon
    my = -17;            // mouse up
  }

  //  The run method updates the locations of the balls.

  public void run() {
    while (true) {
      readControls();
      for (int i=0; i0) {
      if (cur=num) a=num-1;
    if (a<0) return;
    b = nearestBall((int)ball[a].x, (int)ball[a].y, a);
    if (b==a) return;

    double d,m,dx,dy,t;
    d = Ball.hypot(ball[a].x-ball[b].x,ball[a].y-ball[b].y);
    if (d<1e-20) return;        // too close
    m = ball[a].m + ball[b].m;
    if (m<1e-50 && m>-1e-50) return;        // too small
    t = Math.sqrt(mg/m/d)/d;
    dy = t*(ball[a].x - ball[b].x);   // perpendicular direction vector
    dx = t*(ball[b].y - ball[a].y);

    ball[a].vx = ball[b].vx - dx*ball[b].m;
    ball[a].vy = ball[b].vy - dy*ball[b].m;
    ball[b].vx += dx*ball[a].m;
    ball[b].vy += dy*ball[a].m;
  }

  // This adjusts the frame of reference so that the total momentum becomes zero.

  void zeroMomentum() {
    double mx=0,my=0,M=0;
    for (int i=0; i xsize*0.75) x -= xsize;
        if (y > ysize*0.75) y -= ysize;
      }
      cx += ball[i].x * ball[i].m;
      cy += ball[i].y * ball[i].m;
      M += ball[i].m;
    }
    if (M != 0)
      for (int i=0; i xsize) ball[i].x -= xsize;
        while (ball[i].y < 0) ball[i].y += ysize;
        while (ball[i].y > ysize) ball[i].y -= ysize;
      }
  }
}


//-------------------------------------------------------------------
//  The Ball class

class Ball {
  double x,y;                 // location
  double z;                 // radius
  double vx,vy;                 // velocity
  Color c;                 // color
  double m;                 // mass
  boolean hit;                 // scratch field
  double ox,oy;                 // old location (for smooth redraw)
  final double vmin = 1e-20;          // a weak force to prevent overlapping
  Animator a;
  boolean iok;                // image is ok.
  Image img;                // a bitmap to use in "filled" mode

  Ball(double mass, double radius, Color color,
       double px, double py, double sx, double sy, Animator an) {
    m=mass; z=radius-0.5; c=color;
    if (z<0.5) z = Math.min(Math.sqrt(Math.abs(m)),Math.min(px,py));
    if (z<0.5) z=0.5;
    x=px; y=py; vx=sx; vy=sy;
    iok = false;
    a = an;
  }

  //  This updates a ball according to the physical universe.
  //  The reason I exempt a ball from gravity during a hit is
  //  to simulate "at rest" equilibrium when the ball is resting
  //  on the floor or on another ball.

  public void update() {
    x += vx;
    if (x+z > a.xsize)
      if (a.wr) x -= a.xsize;
      else {
        if (vx > 0) vx *= a.r;                // restitution
        vx = -Math.abs(vx)-vmin;        // reverse velocity
        hit = true;
        //  Check if location is completely off screen
        if (x-z > a.xsize) x = a.xsize + z;
      }
    if (x-z < 0)
      if (a.wr) x += a.xsize;
      else {
        if (vx < 0) vx *= a.r;
        vx = Math.abs(vx)+vmin;
        hit = true;
        if (x+z < 0) x = -z;
      }
    y += vy;
    if (y+z > a.ysize)
      if (a.wr) y -= a.ysize;
      else {
        if (vy > 0) vy *= a.r;
        vy = -Math.abs(vy)-vmin;
        hit = true;
        if (y-z > a.ysize) y = a.ysize + z;
      }
    if (y-z < 0)
      if (a.wr) y += a.ysize;
      else {
        if (vy < 0) vy *= a.r;
        vy = Math.abs(vy)+vmin;
        hit = true;
        if (y+z < 0) y = -z;
      }
    if (a.f > 0 && m != 0) {                // viscosity
      double t = 100/(100 + a.f*hypot(vx,vy)*z*z/m);
      vx *= t; vy *= t;
    }
    if (!hit) vy += a.g;        // if not hit, exert gravity
    hit = false;                // reset flag
  }

  //  This computes the interaction of two balls, either collision
  //  or gravitational force.
  //  Returns TRUE if ball b should be deleted.

  public boolean interact(Ball b) {
    double p = b.x - x;
    double q = b.y - y;
    if (a.wr) {                                // wrap around, use shortest distance
      if (p > a.xsize/2) p-=a.xsize;
      else if (p < -a.xsize/2) p+=a.xsize;
      if (q > a.ysize/2) q-=a.ysize;
      else if (q < -a.ysize/2) q+=a.ysize;
    }
    double h2 = p*p + q*q;
    double h = Math.sqrt(h2);
    if (a.col) {                        // collisions enabled
      if (h < z+b.z) {                        // HIT
        hit = b.hit = true;
        if (a.mu) {                        // mush together
          if (m < b.m) c=b.c;                // color
          if (b.m+m != 0) {
            double t = b.m/(b.m+m);
            x += p*t;
            y += q*t;
            vx += (b.vx - vx)*t;
            vy += (b.vy - vy)*t;
            if (x > a.xsize) x -= a.xsize;
            if (x < 0) x += a.xsize;
            if (y > a.ysize) y -= a.ysize;
            if (y < 0) y += a.ysize;
          }
          m += b.m;
          z = hypot(b.z,z);
          iok = false;
          return true;               // delete b
        }
        else if (h > 1e-10) {
          //  Compute the elastic collision of two balls.
          //  The math involved here is not for the faint of heart!

          double v1,v2,r1,r2,s,t,v;
          p /= h;  q /= h;                // normalized impact direction
          v1 = vx*p + vy*q;
          v2 = b.vx*p + b.vy*q;                // impact velocity
          r1 = vx*q - vy*p;
          r2 = b.vx*q - b.vy*p;                // remainder velocity
          if (v1 1e-10 &&
        !hit && !b.hit) {         // gravity is enabled
      double dv;
      dv = a.mg*b.m/h2/h;        // for ver 2.2 added '/h'
      vx += dv*p;
      vy += dv*q;
      dv = a.mg*m/h2/h;
      b.vx -= dv*p;
      b.vy -= dv*q;
    }
    return false;
  }

  public void fiximg() {
    img = a.createImage((int)(2*z+1),(int)(2*z+1));
    //  int t = a.getColorModel().getTransparentPixel();
    Graphics g = img.getGraphics();
    g.setColor(Color.black);                // transparent I hope!
    g.fillRect(0,0,(int)(2*z+1),(int)(2*z+1));
    float hsb[] = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null);
    g.setColor(c);
    for (double i=z; i>0; --i) {
      if (z > 2) g.setColor(Color.getHSBColor(hsb[0],hsb[1],(float)Math.sqrt(1-i*i/z/z)));
      g.fillOval((int)((z-i)*0.5),(int)((z-i)*0.7),(int)(2*i+1),(int)(2*i+1));
    }
    iok = true;
  }

  public void draw(Graphics g) {
    if (!a.sm || a.trc) {        // if not smooth, always draw
      if (a.fi) {
        if (!iok) fiximg();
        g.drawImage(img,(int)(x-z),(int)(y-z),(int)(2*z+1),(int)(2*z+1),a);
      }
      else {
        g.setColor(c);
        g.drawOval((int)(x-z),(int)(y-z),(int)(2*z),(int)(2*z));
      }
      ox = x; oy = y;                // save new location
    }
    //        For smooth mode, only redraw if ball moved a pixel or more.

    else if (Math.abs(x - ox) > 1 || Math.abs(y - oy) > 1) {
      if (a.fi) {
        if (!iok) fiximg();
        g.setColor(Color.black);
//        g.fillOval((int)(ox-z),(int)(oy-z),(int)(2*z+1),(int)(2*z+1));
        g.clearRect((int)(ox-z),(int)(oy-z),(int)(2*z+1),(int)(2*z+1));
        g.drawImage(img,(int)(x-z),(int)(y-z),(int)(2*z+1),(int)(2*z+1),a);
      }
      else {
        g.setColor(Color.black);
        g.drawOval((int)(ox-z),(int)(oy-z),(int)(2*z),(int)(2*z));
        g.setColor(c);
        g.drawOval((int)(x-z),(int)(y-z),(int)(2*z),(int)(2*z));
      }
      ox = x; oy = y;                // save new location
    }
  }

  static double hypot(double x,double y) {
    return Math.sqrt(x*x + y*y);
  }
}

//-------------------------------------------------------------------
//  To use the Ticker class, create an object and create a thread
//  to run it.        Thereafter, any other thread can use it as a
//  pacemaker.

class Ticker implements Runnable {
  int t;                // ticks elapsed
  Checkbox pause;        // pause ticker
  Scrollbar speed;        // animation rate

  Ticker(Panel p) {
    speed = new Scrollbar(Scrollbar.HORIZONTAL,4,1,0,7);
    p.add(new Label("speed"));
    p.add(speed);
    pause = new Checkbox("pause");    // initially false
    p.add(pause);
  }
  public void run() {
    while (true) {
      try { Thread.sleep(dura()); }
      catch (InterruptedException e) {}
      t += pause.getState() ? 0 : 1;
    }
  }
  void poll(int eat) {        // poll for non-zero tick
    while (t==0) {
      try { Thread.sleep(dura()/10 + 1); }
      catch (InterruptedException e) {}
    }
    if (eat > t) t = 0;
    else t-=eat;
  }
  void poll() { poll(30000); }
  int dura() { return 1<<(10-speed.getValue()); }
}

    Source: geocities.com/paris/6502

               ( geocities.com/paris)