/*
 *  Gravity V2.2          June 17, 1986
 *
 *  This program simulates planets in orbit around a star.
 *  See  "Computer Recreations"
 *       SCIENTIFIC AMERICAN, January 1986.
 *
 *  Data file format:
 *
 *  (The (0,0) point is at the center of the screen.)
 *  Input format (all float values) :
 *      Gravitation Constant, Time Step, Display Scale
 *      Mass Star
 *      X Pos P1, Y Pos P1, X Vel P1, Y Vel P1, Mass P1
 *      X Pos P2, Y Pos P2, X Vel P2, Y Vel P2, Mass P2
 *      X Pos P3, Y Pos P3, X Vel P3, Y Vel P3, Mass P3
 *      . . .
 *
 *
 *  Author:
 *    Peter A. Giancola              CompuServ ID: 76337,2542
 *    8005 Mandan Road #101
 *    Greenbelt, Maryland 20770-2156
 *
 *    Copyright (c) 1986 by Peter A. Giancola
 *    Permission is granted for unrestricted non-commercial use.
 *
 *
 */

/*
 * Include all the system defs.
 */

#include "exec/types.h"
#include "exec/libraries.h"
#include "graphics/gfxbase.h"
#include "graphics/text.h"
#include "intuition/intuition.h"
#include "intuition/intuitionbase.h"
#include "lattice/math.h"
#include "lattice/stdio.h"

/*
 * External math functions
 */

extern int SPTst();
extern int SPAbs();
extern int SPSqrt();
extern int SPAdd();
extern int SPSub();
extern int SPMul();
extern int SPDiv();
extern int SPPow();
extern int SPFieee();
extern float SPTieee();

/*
 * Declare global variables.
 */

FILE *DataFile;
UBYTE FileName[32] = "GRAVITY.DAT";
int running = FALSE;
int saverunning = FALSE;
int title = TRUE;
int req = FALSE;

int zero;
int one;
int minus_one;
int two;
int one_hundred;
int minus_one_hundred;
int three_twenty;
int posx[7];                  /* current X positions */
int posy[7];                  /* current Y positions */
int velx[7];                  /* current X velocities */
int vely[7];                  /* current Y velocities */
int mass[7];                  /* mass of planets */
int gmprod[7][7];             /* (G * m1 * m2) product matrix */
int prevx[7];                   /* previous X positions */
int prevy[7];                   /* previous Y positions */

int tstep;                    /* magnitude of time step for each update */
int gcons;                    /* gravitation constant */
int scale;                    /* scale of display screen */
int numplanets;                 /* number of planets in system */

USHORT menu_num_hide_t  = (1 | (0 << 5));
USHORT menu_num_show_t  = (1 | (1 << 5));
USHORT menu_num_go      = (1 | (2 << 5));
USHORT menu_num_stop    = (1 | (3 << 5));

/*
 * Define library and Intuition pointers.
 */

struct GfxBase *GfxBase;
struct IntuitionBase *IntuitionBase;
struct RastPort *RastPort;
struct RastPort *AuthorRastPort;
struct ViewPort *ViewPort;
struct Window *Window;
struct Window *AuthorWindow;
struct Screen *Screen;
struct IntuiMessage *Message;
char *MathBase;
char *MathTransBase;

/*
 * Define screen, window, menu and items.
 */

struct TextAttr Font = 
  {"topax.font", 8, 0, 0};
struct NewScreen NewScreen = 
  {0, 0, 640, 200, 4, 0, 1, HIRES, CUSTOMSCREEN, &Font, 
   "  Gravity V2.2  ", 0, 0};
struct NewWindow NewWindow = 
  {0, 0, 640, 200, -1, -1, REQCLEAR|MENUPICK, BACKDROP|BORDERLESS|ACTIVATE,
   0, 0, 0, 0, 0, 0, 0, 0, 0, CUSTOMSCREEN};
struct NewWindow NewAuthorWindow =
  {100, 50, 500, 85, -1, -1, CLOSEWINDOW, WINDOWDRAG|WINDOWCLOSE|ACTIVATE,
   0, 0, " Author ", 0, 0, 0, 0, 0, 0, CUSTOMSCREEN};
struct StringInfo FileNameString =
  {FileName, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0};
struct Gadget FileNameGadget =
  {0, 10, 15, 260, 10, GADGHCOMP, ENDGADGET, STRGADGET|REQGADGET, 
   0, 0, 0, 0, (APTR)&FileNameString, 0, 0};
struct IntuiText FileNameText =
  {0, 0, JAM1, 65, 1, 0, "Enter file name", 0};
struct Requester FileNameRequester = 
  {0, 50, 50, 280, 30, 0, 0, &FileNameGadget, 0, &FileNameText, 0, 
   8, 0, 0, 0, 0};
struct IntuiText AuthorText[6] =
  {
    {3, 0, JAM2, 5, 15, 0, 
     "Peter A. Giancola", &AuthorText[1]},
    {3, 0, JAM2, 5, 25, 0, 
     "8005 Mandan Road #101", &AuthorText[2]},
    {3, 0, JAM2, 5, 35, 0, 
     "Greenbelt, Maryland 20770-2156", &AuthorText[3]},
    {5, 0, JAM2, 270, 15, 0, 
     "CompuServ ID: 76337,2542", &AuthorText[4]},
    {4, 0, JAM2, 5, 60, 0, 
     "Copyright (c) 1986 by Peter A. Giancola", &AuthorText[5]},
    {4, 0, JAM2, 5, 70, 0, 
     "Permission is granted for unrestricted non-commercial use.", 0}
  };
struct IntuiText MenuItemText[3][5] =
  {
    {
      {0, 1, JAM2, 0, 0, 0, "Load    ", 0},
      {0, 1, JAM2, 0, 0, 0, "Save    ", 0},
      {0, 1, JAM2, 0, 0, 0, "Edit    ", 0},
      {0, 1, JAM2, 0, 0, 0, "Quit    ", 0},
      {0, 0, 0, 0, 0, 0, 0, 0}
    },
    {
      {0, 1, JAM2, 0, 0, 0, "Hide Title", 0},
      {0, 1, JAM2, 0, 0, 0, "Show Title", 0},
      {0, 1, JAM2, 0, 0, 0, "Go        ", 0},
      {0, 1, JAM2, 0, 0, 0, "Stop      ", 0},
      {0, 1, JAM2, 0, 0, 0, "Restart   ", 0}
    },
    {
      {0, 1, JAM2, 0, 0, 0, "Author", 0},
      {0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0}
    }
  };
struct MenuItem MenuItem[3][5] =
  {
    {
      {&MenuItem[0][1], 0, 0, 80, 10, ITEMTEXT|ITEMENABLED|HIGHBOX,
       0, (APTR)&MenuItemText[0][0], 0, 0, 0, 0},
      {&MenuItem[0][2], 0, 10, 80, 10, ITEMTEXT|HIGHBOX,
       0, (APTR)&MenuItemText[0][1], 0, 0, 0, 0},
      {&MenuItem[0][3], 0, 20, 80, 10, ITEMTEXT|HIGHBOX,
       0, (APTR)&MenuItemText[0][2], 0, 0, 0, 0},
      {0, 0, 30, 80, 10, ITEMTEXT|ITEMENABLED|HIGHBOX,
       0, (APTR)&MenuItemText[0][3], 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
    },
    {
      {&MenuItem[1][1], 0, 0, 90, 10, ITEMTEXT|ITEMENABLED|HIGHBOX,
       0, (APTR)&MenuItemText[1][0], 0, 0, 0, 0},
      {&MenuItem[1][2], 0, 10, 90, 10, ITEMTEXT|HIGHBOX,
       0, (APTR)&MenuItemText[1][1], 0, 0, 0, 0},
      {&MenuItem[1][3], 0, 20, 90, 10, ITEMTEXT|ITEMENABLED|HIGHBOX,
       0, (APTR)&MenuItemText[1][2], 0, 0, 0, 0},
      {&MenuItem[1][4], 0, 30, 90, 10, ITEMTEXT|HIGHBOX,
       0, (APTR)&MenuItemText[1][3], 0, 0, 0, 0},
      {0, 0, 40, 90, 10, ITEMTEXT|ITEMENABLED|HIGHBOX,
       0, (APTR)&MenuItemText[1][4], 0, 0, 0, 0}
    },
    {
      {0, 0, 0, 60, 10, ITEMTEXT|ITEMENABLED|HIGHBOX,
       0, (APTR)&MenuItemText[2][0], 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
    },
  };
struct Menu Menu[3] =
  {
    {&Menu[1], 0, 0, 80, 10, MENUENABLED, "Projects", &MenuItem[0][0]},
    {&Menu[2], 85, 0, 70, 10, MENUENABLED, "Control", &MenuItem[1][0]},
    {0, 160, 0, 60, 10, MENUENABLED, "Author", &MenuItem[2][0]}
  };

/*
 *            G r a v i t y
 */

main ()
{

  /* Initialize library and Intuition pointers */

  AuthorWindow = 0;
  Window = 0;
  Screen = 0;
  IntuitionBase = 0;
  GfxBase = 0;

  /* Initialize the system */

  dolibs();  /* Open the libraries */
  zero = to_ffp(0.0);
  one = to_ffp(1.0);
  minus_one = to_ffp(-1.0);
  two = to_ffp(2.0);
  one_hundred = to_ffp(100.0);
  minus_one_hundred = to_ffp(-100.0);
  three_twenty = to_ffp(320.0);
  doscreen();  /* Set up the screen and window */
  doplanets();  /* Initialize the planets */

  /* Begin running the simulation 'till the universe collapses... */

  FOREVER
    {
    domsg();  /* Process any system messages */
    if (running) docycle();  /* Update the planets */
    };
}


/*
 * Open the libraries
 */

dolibs ()
{
  GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0);
  if (GfxBase == 0) quit();
  IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",
                                                      0);
  if (IntuitionBase == 0) quit();
  MathBase = (char *)OpenLibrary("mathffp.library", 0);
  if (MathBase == 0) quit();
  MathTransBase = (char *)OpenLibrary("mathtrans.library", 0);
  if (MathTransBase == 0) quit();
}



/*
 * Set up the screen and window
 */

doscreen ()
{
  Screen = (struct Screen *)OpenScreen(&NewScreen);
  if (Screen == 0) quit();

  NewWindow.Screen = Screen;
  NewAuthorWindow.Screen = Screen;
  Window = (struct Window *)OpenWindow(&NewWindow);
  if (Window == 0) quit();

  ViewPort = &Screen->ViewPort;
  RastPort = Window->RPort;
  SetMenuStrip(Window, &Menu[0]);

  if (title != TRUE)
    {
    OffMenu(Window,menu_num_hide_t);
    OnMenu(Window,menu_num_show_t);
    ShowTitle(Screen,FALSE);
    };

  SetRGB4(ViewPort,  0, 00, 00, 00);
  SetRGB4(ViewPort,  1, 13, 13, 13);
  SetRGB4(ViewPort,  2, 00, 00, 13);

  SetRGB4(ViewPort,  3, 15, 15, 00);
  SetRGB4(ViewPort,  4, 15, 00, 00);
  SetRGB4(ViewPort,  5, 00, 15, 00);
  SetRGB4(ViewPort,  6, 00, 00, 15);
  SetRGB4(ViewPort,  7, 00, 15, 15);

  SetRGB4(ViewPort,  8, 09, 09, 09);
  SetRGB4(ViewPort,  9, 06, 00, 00);

  SetRGB4(ViewPort, 10, 08, 08, 00);
  SetRGB4(ViewPort, 11, 08, 00, 00);
  SetRGB4(ViewPort, 12, 00, 08, 00);
  SetRGB4(ViewPort, 13, 00, 00, 08);
  SetRGB4(ViewPort, 14, 00, 08, 08);

  SetRGB4(ViewPort, 15, 10, 10, 00);
}



/*
 * Clean up and exit
 */

quit ()
{
  if (AuthorWindow)
    {
    running = saverunning;
    SetMenuStrip(Window, &Menu[0]);
    CloseWindow(AuthorWindow);
    AuthorWindow = 0;
    AuthorRastPort = 0;
    };
/*
  if (FileNameWindow)
    {
    running = saverunning;
    SetMenuStrip(Window, &Menu[0]);
    CloseWindow(FileNameWindow);
    FileNameWindow = 0;
    };
 */
  if (Window)
    {
    ClearMenuStrip(Window);
    CloseWindow(Window);
    };
  if (Screen) CloseScreen(Screen);
  if (MathTransBase) CloseLibrary(MathTransBase);
  if (MathBase) CloseLibrary(MathBase);
  if (IntuitionBase) CloseLibrary(IntuitionBase);
  if (GfxBase) CloseLibrary(GfxBase);
  exit();
}



/*
 * Kill screen for restart
 */

killscreen ()
{
  if (AuthorWindow)
    {
    running = saverunning;
    SetMenuStrip(Window, &Menu[0]);
    CloseWindow(AuthorWindow);
    AuthorWindow = 0;
    AuthorRastPort = 0;
    };
  if (Window)
    {
    SetRast(RastPort,0);
    };
}



/*
 * Process system messages
 */

domsg ()
{
  ULONG class;
  USHORT code;
  USHORT menunum;
  USHORT itemnum;
  int mousex;
  int mousey;

  /* If the author window is open, look for close window messages
     from it */

  if (AuthorWindow != 0)
    {
    Message = (struct IntuiMessage *)GetMsg(AuthorWindow->UserPort);
    if (Message != 0)
      {
      class = Message->Class;
      ReplyMsg(Message);
      if (class == CLOSEWINDOW)
        {
        running = saverunning;
        CloseWindow(AuthorWindow);
        AuthorWindow = 0;
        AuthorRastPort = 0;
        SetMenuStrip(Window, &Menu[0]);
        };
      };
    };

  /* Now look for menu selection messages */

  Message = (struct IntuiMessage *)GetMsg(Window->UserPort);
  if (Message != 0)
    {
    class = Message->Class;
    code = Message->Code;
    mousex = Window->MouseX;
    mousey = Window->MouseY;
    ReplyMsg(Message);
    if (class == REQCLEAR)
      {
      killscreen();
      doplanets();
      SetMenuStrip(Window, &Menu[0]);
      OffMenu(Window,menu_num_go);
      OnMenu(Window,menu_num_stop);
      running = saverunning;
      };

    if (class == MENUPICK && code != MENUNULL)
      {
      menunum = MENUNUM(code);
      itemnum = ITEMNUM(code);
      switch (menunum)
        {
        case 0:
          /* Projects */
          switch (itemnum)
            {
            case 0:
              /* Load */
              ClearMenuStrip(Window);
              saverunning = TRUE;
              running = FALSE;
              Request(FileNameRequester, Window);
              break;
            case 1:
              /* Save */
              break;
            case 2:
              /* Edit */
              break;
            case 3:
              /* Quit */
              quit();
              break;
            };
          break;
        case 1:
          /* Control */
          switch (itemnum)
            {
            case 0:
              /* Hide Title */
              menu_num_hide_t = code;
              OffMenu(Window,code);
              OnMenu(Window,menu_num_show_t);
              ShowTitle(Screen,FALSE);
              title = FALSE;
              break;
            case 1:
              /* Show Title */
              ShowTitle(Screen,TRUE);
              menu_num_show_t = code;
              OffMenu(Window,code);
              OnMenu(Window,menu_num_hide_t);
              title = TRUE;
              break;
            case 2:
              /* Go */
              menu_num_go = code;
              OffMenu(Window,code);
              OnMenu(Window,menu_num_stop);
              running = TRUE;
              break;
            case 3:
              /* Stop */
              menu_num_stop = code;
              OffMenu(Window,code);
              OnMenu(Window,menu_num_go);
              running = FALSE;
              break;
            case 4:
              /* Restart */
              killscreen();
              doplanets();
              break;
            };
          break;
        case 2:
          /* Author */
          ClearMenuStrip(Window);
          saverunning = running;
          running = FALSE;
          AuthorWindow = (struct Window *)OpenWindow(&NewAuthorWindow);
          if (AuthorWindow == 0) quit();
          AuthorRastPort = AuthorWindow->RPort;
          PrintIText(AuthorRastPort,&AuthorText[0],0,0);
          break;
        };
      };
    };
}



/*
 * Update the planets
 */

docycle ()
{
  int tmp;
  int tmp2;
  int i;
  int j;
  int dx;
  int dy;
  int dist;
  int force;
  int forx;
  int fory;
  int scrx;
  int scry;

  /* Make one pass for each planet in the system */

  for (i = 1; i < numplanets; ++i)
    {

    /* Accumulate the forces on the current planet due to 
       all the other planets */

    forx = zero;
    fory = zero;
    for (j = 0; j < numplanets; ++j)
      {
      if (j != i)
        {

        /* Calculate the distance between the two selected planets */

        dx = SPSub (posx[i], posx[j]);
        dy = SPSub (posy[i], posy[j]);
        dist = SPSqrt(SPAdd(SPPow(two,dx),SPPow(two,dy)));
        tmp = SPSub (dist, one);
        if (! SPTst (SPSub (tmp, SPAbs(tmp)))) dist = one;

        /* Now calculate the force due to gravity using the G cons *
           M1 * M2 product matrix to speed the calculation. */

        force = SPDiv(SPMul(dist, dist), gmprod[i][j]);

        /* The force is a vector, so split it into components. */

        forx = SPAdd(forx,SPDiv(dist,SPMul(force,dx)));
        fory = SPAdd(fory,SPDiv(dist,SPMul(force,dy)));
        };
      };

    /* Use the force vector to calculate the planet's new velocity. */

    velx[i] = SPAdd(velx[i], SPMul(SPDiv(mass[i], forx), tstep));
    vely[i] = SPAdd(vely[i], SPMul(SPDiv(mass[i], fory), tstep));
    };

  /* Now update the positions of all planets using the accumulated
     velocity. The new positions are scaled and limited to the screen
     boundries. */

  for (i = 0; i < numplanets; ++i)
    {

    /* Update the X position */

    posx[i] = SPAdd(posx[i],SPMul(velx[i],tstep));
    tmp = SPSub (one_hundred, posx[i]);
    tmp2 = SPSub (posx[i], minus_one_hundred);
    if ((! SPTst (SPSub (tmp, SPAbs(tmp)))) ||
        (! SPTst (SPSub (tmp2, SPAbs(tmp2)))))
      {
      velx[i] = zero;
      vely[i] = zero;
      };
    scrx = (int) (SPTieee(SPAdd(SPMul(SPMul(two,scale),posx[i]),
        three_twenty)));
    if (scrx < 0) scrx = 0;
    if (scrx > 639) scrx = 639;

    /* Update the Y position */

    posy[i] = SPAdd(posy[i],SPMul(vely[i],tstep));
    tmp = SPSub (one_hundred, posy[i]);
    tmp2 = SPSub (posy[i], minus_one_hundred);
    if ((! SPTst (SPSub (tmp, SPAbs(tmp)))) ||
        (! SPTst (SPSub (tmp2, SPAbs(tmp2)))))
      {
      velx[i] = zero;
      vely[i] = zero;
      };
    scry = (int) (SPTieee(SPAdd(SPMul(SPMul(scale,minus_one),posy[i]),
        one_hundred)));
    if (scry < 0) scry = 0;
    if (scry > 199) scry = 199;

    /* Now use the saved previous screen position to leave a trail. */

    SetAPen(RastPort,(i + 10));
    j = WritePixel(RastPort, prevx[i], prevy[i]);

    /* Draw the new position and save it as the previous position. */

    SetAPen(RastPort,(i + 3));
    j = WritePixel(RastPort, scrx, scry);
    prevx[i] = scrx;
    prevy[i] = scry;
    };
}



/*
 * Read the initial conditions.
 */

doplanets ()
{
  int i;
  int j;
  int scrx;
  int scry;
  float gc;
  float x[4];
  float m[7];

  OffMenu(Window,menu_num_stop);
  OnMenu(Window,menu_num_go);
  running = FALSE;
  DataFile = fopen(FileName, "r");
  if (DataFile != NULL)
    {
    j = fscanf(DataFile, "%f %f %f", &gc, &x[0], &x[1]);
    gcons = to_ffp(gc);
    tstep = to_ffp(x[0]);
    scale = to_ffp(x[1]);
    i = 0;

    /* Read the star's mass first. */

    j = fscanf(DataFile, "%f", &m[i]);
    mass[i] = to_ffp(m[i]);
    posx[i] = zero;
    posy[i] = zero;
    velx[i] = zero;
    vely[i] = zero;

    /* Now read the planet's parameters. */

    while (j != EOF && i < 5)
      {
      i += 1;
      j = fscanf(DataFile, "%f %f %f %f %f", 
                 &x[0], &x[1], &x[2], &x[3], &m[i]);
      posx[i] = to_ffp(x[0]);
      posy[i] = to_ffp(x[1]);
      velx[i] = to_ffp(x[2]);
      vely[i] = to_ffp(x[3]);
      mass[i] = to_ffp(m[i]);
      };
    numplanets = i;
    i = fclose(DataFile);

    /* Initialize the G cons * M1 * M2 product matrix. */

    for (i = 0; i < numplanets; ++i)
      {
      scrx = (int) (SPTieee(SPAdd(SPMul(SPMul(two,scale),posx[i]),
          three_twenty)));
      if (scrx < 0) scrx = 0;
      if (scrx > 639) scrx = 639;
      scry = (int) (SPTieee(SPAdd(SPMul(SPMul(scale,minus_one),posy[i]),
          one_hundred)));
      if (scry < 0) scry = 0;
      if (scry > 199) scry = 199;
      prevx[i] = scrx;
      prevy[i] = scry;
      for (j = 0; j < numplanets; ++j)
        {
        if (i != j) gmprod[i][j] = to_ffp(gc * m[i] * m[j]);
        else gmprod[i][j] = zero;
        };
      };
    OffMenu(Window, menu_num_go);
    OnMenu(Window, menu_num_stop);
    running = TRUE;
    };
}



/*
 * convert from Lattice float to FFP.
 */

int to_ffp (arg)
  double arg;
  {
  union
    {
    int i;
    float r;
    } temp;
  temp.r = arg;
  return (SPFieee(temp.i));
  }
