// body3.java  three body gravitational attraction                        
//          F = G m1 m2 / d^2  F, A, V, S have x,y,z components        
//          F = m2 v^2 / d     d^2 = (x2-x1)^2 + (y2-y1)^2 + (z2-z1)^2 
//          A = F / m1         acceleration along force components     
//          V = V + A dt       velocity update at delta time  dt       
//          S = S + V dt       position update at delta time  dt       

import java.*;
import java.awt.*;
import java.awt.event.*;

public class body3 extends Frame
{
  // define number of masses NM, add initial values 
  int NM=3;
                         // subscript is mass index 
  xyz S[] = new xyz[NM]; // initialized positions
  xyz V[] = new xyz[NM]; // initialized velocities to orbit 
  xyz A[] = new xyz[NM]; // computed 
  xyz F[] = new xyz[NM]; // computed  
  double R[] = {0.5, 0.0625, 0.03125};   // radius of mass 
  double M[] = {1.0, 0.125,  0.00625};   // normalized mass 
  double G = 2.0;    // normalized gravitational constant 
  double dt = 0.1; // normalized time step
  boolean debug = false;

  // mouse and display variables
  int xm, ym, bm; // mouse position
  int xc;   // center location
  int yc;
  int xp;         // last mouse location during drag
  int yp;
  boolean tracking = false; // tracking mouse motion
  boolean loose = false;    // loose to move
  double x = 0.0;           // integrated position
  double vx = 0.0;          // integrated velocity
  boolean hspeed = false;   // yellow buttons
  boolean dspeed = false;
  boolean reset = false;
  int height = 600;
  int width = 600;
  double xmin = -5.0;
  double xmax =  5.0;
  double ymin = -5.0;
  double ymax =  5.0;
  double xs, ys;

  public body3()
  {
    System.out.println("body3.java running, NM="+NM);
    init();
    xc = width/2;
    yc = height/2;
    xs = (double)width/(xmax-xmin);
    ys = (double)height/(ymax-ymin);
    System.out.println("xmin="+xmin+",xmax="+xmax+
                       ", ymin="+ymin+", ymax="+ymax);
    System.out.println("xs="+xs+",ys="+ys);

    setTitle("body3");
    setSize(width,height);
    setBackground(Color.black);
    setForeground(Color.white);
    addWindowListener(new WindowAdapter()
    {
      public void windowClosing(WindowEvent e)
      {
        System.exit(0);
      }
    });
    setVisible(true);
    this.addMouseListener (new mousePressHandler());
    this.addMouseListener (new mouseReleaseHandler());
    this.addMouseMotionListener (new mouseMotionHandler());
    new MyThread("physics").start();
  }

  public class xyz
  {
    public double x = 0.0;
    public double y = 0.0;
    public double z = 0.0;
    xyz()
    {
    }

    xyz(double a, double b, double c)
    {
      x=a;
      y=b;
      z=c;
    }
  }

  void physics()
  {
    int i, j;
    double fg, fd;
    double Dijx, Dijy, Dijz;
    double d2ij;
  
    //Idle callback, compute force, acceleration, update velocity and position 
    for(i=0; i<NM; i++)
    {
      F[i].x = 0.0;
      F[i].y = 0.0;
      F[i].z = 0.0;
      if(debug) System.out.println("old S["+i+"]={"+S[i].x+","+S[i].y+
                ","+S[i].z+"}, V["+i+"]={"+V[i].x+","+V[i].y+","+V[i].z+"}");
    }
    for(i=0; i<NM-1; i++)
    {
      for(j=i+1; j<NM; j++)
      {
        Dijx = S[i].x - S[j].x; 
        Dijy = S[i].y - S[j].y; 
        Dijz = S[i].z - S[j].z;
        d2ij = Dijx*Dijx + Dijy*Dijy + Dijz*Dijz;
        fg = G * M[i] * M[j] / d2ij;
        fd = Math.sqrt(d2ij);
        if(debug) System.out.println("i="+i+", j="+j+", fg="+fg+
                                      ", fd="+fd+", d2ij="+d2ij);
        F[i].x = F[i].x - fg*Dijx/fd;
        F[i].y = F[i].y - fg*Dijy/fd;
        F[i].z = F[i].z - fg*Dijz/fd;
        F[j].x = F[j].x + fg*Dijx/fd;
        F[j].y = F[j].y + fg*Dijy/fd;
        F[j].z = F[j].z + fg*Dijz/fd;
      }
    }
    for(i=0; i<NM; i++)
    {
      if(debug)System.out.println("F["+i+"] = {"+F[i].x+", "+F[i].y+", "+F[i].z+"}");
      A[i].x = F[i].x/M[i];
      A[i].y = F[i].y/M[i];
      A[i].z = F[i].z/M[i];
      if(debug)System.out.println("A["+i+"] = {"+A[i].x+", "+A[i].y+", "+A[i].z+"}");
      V[i].x = V[i].x + A[i].x*dt;
      V[i].y = V[i].y + A[i].y*dt;
      V[i].z = V[i].z + A[i].z*dt;
      S[i].x = S[i].x + V[i].x*dt;
      S[i].y = S[i].y + V[i].y*dt;
      S[i].z = S[i].z + V[i].z*dt;
    }
    repaint();
  } // end physics 

  void init()
  {
    S[0] = new xyz(0.0,0.0,0.0);
    S[1] = new xyz(2.8,0.0,0.0);
    S[2] = new xyz(3.3,0.0,0.0);
    V[0] = new xyz(0.0,-0.1,0.0);
    V[1] = new xyz(0.0,0.8,0.0);
    V[2] = new xyz(0.0,1.4,0.0);
    A[0] = new xyz(0.0,0.0,0.0);
    A[1] = new xyz(0.0,0.0,0.0);
    A[2] = new xyz(0.0,0.0,0.0);
    F[0] = new xyz(0.0,0.0,0.0);
    F[1] = new xyz(0.0,0.0,0.0);
    F[2] = new xyz(0.0,0.0,0.0);
    dt = 0.1;
  } // end init 

  boolean in_rect(int x, int y, int w, int h)
  {
      return xm>x && xm<x+w && ym>y && ym<y+h;
  }

  class mousePressHandler extends MouseAdapter
  {
    public void mousePressed (MouseEvent e)
    {
      xm = e.getX();
      ym = e.getY();
      bm = e.getButton();

      
      if(in_rect(xc-150, yc-150, 300, 300))
      {
        tracking = true;
        loose = false;
        xp = xm;
        yp = ym;
      }

      if(in_rect(220,40,160,35)) init();        // reset
      if(in_rect(40 ,40,150,35)) dt = dt/2.0;   // hspeed
      if(in_rect(410,40,150,35)) dt = dt*2.0;   // dspeed

      requestFocus();
      // System.out.println("down xm="+xm+", ym="+ym+", bm="+bm); // debug 
      repaint();
    }
  }

  class mouseReleaseHandler extends MouseAdapter
  {
    public void mouseReleased (MouseEvent e)
    {
      xm = e.getX();
      ym = e.getY();
      bm = e.getButton();
      tracking = false;
      loose = true;
      // System.out.println("up   xm="+xm+"   ym="+ym+"   bm="+bm); // debug print
    }
  }

  class mouseMotionHandler extends MouseMotionAdapter
  {
    public void mouseDragged (MouseEvent e)
    {
      int xct, yct;
      
      if(tracking)
      {
        xm = e.getX();
        ym = e.getY();
        bm = e.getButton();
        // xc = xc + (xm-xp); // mouse moved (new-old)
        xp = xm;
        yp = ym;
        // if(xc<50)xc=50;
        // if(xc>50)xc=50;

        requestFocus();
        // System.out.println("drag xm="+xm+"   ym="+ym+"   bm="+bm); // debug print
        repaint();
      }
    } // end mouseDragged
  } // end class mouseMotionHandler

  public void paint(Graphics g)
  {
    g.drawString("Sun, Earth and Moon against the night sky, not to scale.", 20, 35);

    g.setColor(Color.yellow);
    g.fillRect(220,40,160,35);
    g.setColor(Color.black);
    g.drawString("reset to initial state", 225, 55);
    g.drawString("start again", 225, 70);

    g.setColor(Color.yellow);
    g.fillRect(40,40,150,35);
    g.setColor(Color.black);
    g.drawString("decrease speed", 45, 55);
    g.drawString("note orbit change", 45, 70);

    g.setColor(Color.yellow);
    g.fillRect(410,40,150,35);
    g.setColor(Color.black);
    g.drawString("increase speed", 415, 55);
    g.drawString("note orbit change", 415, 70);

    g.setColor(Color.white);
    g.drawString("Basic physics:", 20, height-70);
    g.drawString("f = G m1 m2 / d^2  the force of gravity is inversely proportional to distance squared", 20, height-50);
    g.drawString("f = m a  the acceleration, a, of a body of is force divided by mass, f / m ", 20, height-30);
    g.drawString("velocity  v = v_prev + a * dt,  position  s = s_prev + v * dt  for a time step dt", 20, height-10);

    g.setColor(Color.white); // sun [0]  2D no Z component
    g.fillOval((int)(xc+S[0].x*xs)-15, (int)(yc+S[0].y*ys)-15, 30, 30);

    g.setColor(Color.blue); // earth [1]
    g.fillOval((int)(xc+S[1].x*xs)-5, (int)(yc+S[1].y*ys)-5, 10, 10);

    g.setColor(Color.yellow); // moon [2]
    g.fillOval((int)(xc+S[2].x*xs)-3, (int)(yc+S[2].y*ys)-3, 6, 6);
    
  } // end paint

  class MyThread extends Thread
  {
    String id;
    
    MyThread(String who)
    {
      id = who;
    }
    
    public void run()
    {
      while(true)
      {
        try
        {
          physics();
          // System.out.println(id+" thread is running"); // debug
          sleep(100); // milliseconds delay 
        }
        catch(InterruptedException e)
        {
          System.out.println("interruptedException in "+id);
        }
      }
    } // end run
  } // end class MyThread

  public static void main(String[] args)
  {
    new body3();
  }
} // end class of body3.java 

