/*
 * Celestial Mechanics Simulation Tool
 *
 *	W. John Guineau
 *      3 Royal Crest Drive #9
 *      Marlboro, Mass. 01752
 *      (508) 485-6233
 *
 * Files:
 *	    cm.c
 *	    cm.h
 *          cm.doc
 *
 * To Compile with Lattice 5.02:
 *
 *	    lc -b0 -Lm cm
 *
 *
 *			      NOTICE
 *			      ------ 
 *
 *  I have placed this software in the Public Domain with the
 * condition that all the files remain together and that I remain
 * listed as the original author. This software may not be used for
 * commercial purposes or to make money in any way without expressed
 * written permission from the author (me). I'm including the source
 * code so if you make any significant modifications please concider
 * sending me a copy at the above address. I'd also be interested in
 * any interesting saved setup files you create.
 *
 *
 * This is my first Amiga program so I welcome any comments at all.
 * I wrote this program as both a way to learn the Amiga environment
 * and in response to a conversation I had with a friend on Celestial
 * Mechanics.
 *
 *
 */

#include <stdio.h>
#include <ctype.h>
#include <math.h>

#include <exec/types.h>
#include <exec/ports.h>
#include <exec/devices.h>
#include <libraries/dosextens.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <graphics/rastport.h>
#include <graphics/gfxbase.h>
#include <graphics/gfx.h>
#include <graphics/display.h>
#include <graphics/text.h>




#include "cm.h"



#define NOWAIT 0
#define WAIT   1


extern struct IntuitionBase *IntuitionBase;
extern struct GfxBase *GfxBase;
struct IntuiMessage *message;

struct Screen *s;
struct Window *w;
struct Window *pw;
struct RastPort *rp;
struct RastPort *prp;
struct ViewPort *vp;


#define ReqNone  0
#define ReqBody  1
#define ReqSetup 2
#define ReqFile  3
#define ReqAbout 4


UBYTE  CancelHit=0;	   /* If user selects CANCEL from Reqester */
UBYTE  ReqActive=ReqNone;  /* Set when a Reqester is active */
			   /*  to keep DoIntuiEvents() from returning */
			   /*  until it gets a REQCLEAR msg */


struct setup {
   double   G;
   double   ds;
   double   dt;
   UBYTE    t;
   WORD     TrailLength;
   UBYTE    ShowTime;
} SI;

#define MAXTRAILEN 300
   
struct Body {		    /* Info for a Celestial Body */
   UBYTE    real;	    /* If it's real */
   UBYTE    fixed;	    /* Body fixed in place */
   UBYTE    pen;            /* Color Register */
   char     name[10];	    /* user given name */
   ULONG    Radius;	    /* radius */
   double   Mass;	    /* mass */
   double   x,y;	    /* current position */
   double   Vx,Vy;	    /* velocity components */
   double   Fx,Fy;	    /* resultant force components */
   double   Dir;	    /* direction angle (radians) */
   double   TrailX[MAXTRAILEN]; /* X of trail of pixels to clear */
   double   TrailY[MAXTRAILEN]; /* X of trail of pixels to clear */
   WORD     TrailIDX;
};


#define MAXBODYS 10
struct Body Bodys[MAXBODYS];		 /* The array of bodys */
BYTE   CurBody,TotalBodys=0;

ULONG	Et;				/* elapsed time */


#define MODE_CREATE 1
#define MODE_MODIFY 2
#define MODE_START  3
#define MODE_STOP   4
UBYTE	mode = MODE_STOP;		/* Current simulation mode */




/* pointer text for ShowMouse() */
char	pbuf[80];
struct IntuiText pos_txt = {
   WHTPEN,BLKPEN,			/* FrontPen, BackPen */
   JAM2,				/* DrawMode */
   7,2,  				/* LeftEdge, TopEdge */
   &TxtAt_Plain,			/* TextAttr */
   pbuf,				/* IText */
   NULL 				/* NextText */
};



struct	MsgPort *TimerPort;	    /* for Timer stuff */
struct	timerequest *TR;
UBYTE	TimerOpen=0;





/* ============================================================
 *   Main
 *
 * ============================================================*/

main(argc,argv)
int   argc;
char  *argv[];
{

OpenStuff();
Init();

DoIntuiEvents(WAIT);

return(0);

}









OpenStuff()
{

TimerPort = (struct MsgPort *)CreatePort(0,0);
if(TimerPort == 0) {
   printf("Cant get a Timer Port\n");
   CleanUp();
   exit(10);
};

TR = (struct timerequest *)CreateExtIO(TimerPort,
				       sizeof(struct timerequest));
if(TR == 0) {
   printf("Cant get a Timer Request\n");
   CleanUp();
   exit(11);
};


if(OpenDevice(TIMERNAME,UNIT_VBLANK,TR,0) != 0) {
   printf("Cant open Timer Device\n");
   CleanUp();
   exit(12);
};
TimerOpen = 1;


if(!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0))) {
   printf("no graphics library!!!\n");
   CleanUp();
   exit(13);
};

if(!(IntuitionBase = (struct IntuitionBase *)
			      OpenLibrary("intuition.library",0))) {
   printf("no intuition here!!\n");
   CleanUp();
   exit(14);
};



if (!(s = (struct Screen *)OpenScreen(&ns) )) {
   printf("could not open the screen\n");
   CleanUp();
   exit(15);
};


/*
 * set up color map for new screen
 */
vp = (struct ViewPort *)&s->ViewPort;
LoadRGB4(vp,colortable,COLORS);

nw.Screen = s;	     /* point to our screen */
npw.Screen = s;	     /* pointer window struct also */

if (!(w = (struct Window *)OpenWindow(&nw) )) {
   printf("could not open the window\n");
   CleanUp();
   exit(16);
};

rp = w->RPort;
SetAPen(rp,WHTPEN);

SetMenuStrip(w,&Control);

return(0);

}



Init()
{

SI.G = 6.67;
SI.ds = 1.0;
SI.dt = 1.0;
SI.t = 0;
SI.TrailLength = 0;

cancel_text.IText = cantxt;

OffMenu(w,MN_CStop);
OffMenu(w,MN_EMod);
OffMenu(w,MN_FSScr);


ClearScreen();
ClearBodys();
SetupGADefaults();
BodyGADefaults();

return(0);

}




ClearScreen()
{

SetRast(rp,0);

SetAPen(rp,WHTPEN);

return(0);

}


DoIntuiEvents(wait)
UBYTE wait;
{
ULONG  MessageClass;
USHORT code,qual;
struct Gadget *GAD;
struct MsgPort *mp;

mp = w->UserPort;

for(;;) {

   if(message = (struct IntuiMessage *)GetMsg(w->UserPort)) {

      MessageClass = message->Class;
      code = message->Code;
      qual = message->Qualifier;
      GAD = (struct Gadget *)message->IAddress;

      ReplyMsg(message);

      switch (MessageClass) {

	 case GADGETUP	  : DoGadget(GAD);
			    break;

	 case MENUPICK    : if(code != MENUNULL) {
                               DoMenu(code);
                            };
                            break;

	 case CLOSEWINDOW : CleanUp();
			    exit(0);
			    break;

	 case MOUSEBUTTONS: if(code == SELECTDOWN) {
			       MakeBody(w->GZZMouseX,w->GZZMouseY);
			    };
			    break;

	 case MOUSEMOVE   : if(mode == MODE_CREATE) {
			       ShowMouse(w->GZZMouseX,w->GZZMouseY);
			    };
			    break;

	 case REQCLEAR	  : 
			    ReqActive = ReqNone;
			    break;

	 default	  : 
			    break;

      };

   } else {

      if(!wait&&!ReqActive) return(0);

      WaitPort(mp);

   };

};
return(0);
}



DoGadget(GAD)
struct	Gadget *GAD;
{
ULONG	val;
struct	StringInfo *info;


info = (struct StringInfo *)GAD->SpecialInfo;
val  = info->LongInt;

switch(GAD->GadgetID) {

   case NAMEGAD		: 
                          ActivateGadget(&mass,w,&BodyInfo);
                          break;
   case RADIUSGAD       : 
                          break;

   case MASSGAD         : 
                          ActivateGadget(&velocity,w,&BodyInfo);
                          break;

   case VELOCITYGAD     : 
                          ActivateGadget(&direction,w,&BodyInfo);
                          break;

   case DIRECTIONGAD    : 
                          break;

   case OKGAD		: 
			  CancelHit=0;
                          if(pw)
                             WindowToFront(pw);
			  break;

   case CANCELGAD	: 
			  CancelHit=1;
                          if(pw)
                             WindowToFront(pw);
			  break;

   case RESETGAD        : 
                          switch(ReqActive) {
                             case ReqBody  : BodyGADefaults();
                                             RefreshGadgets(&mass,
                                                            w,&BodyInfo);
                                             break;
                             case ReqSetup : SetupGADefaults();
                                             RefreshGadgets(&dt,
                                                            w,&SetupInfo);
                                             break;
                            
                          };
                          break;

   case GGAD            : 
                          ActivateGadget(&dt,w,&SetupInfo);
                          break;

   case DTGAD           : 
                          ActivateGadget(&t,w,&SetupInfo);
                          break;

   case TGAD            : 
                          ActivateGadget(&ds,w,&SetupInfo);
                          break;

   case DSGAD           : 
                          ActivateGadget(&tl,w,&SetupInfo);
                          break;

   case TLGAD           : 
                          break;

   case COLOR1GAD	: 
                          Bodys[CurBody].pen = (UBYTE)GAD->UserData;
                          break;
   case COLOR2GAD	: 
                          Bodys[CurBody].pen = (UBYTE)GAD->UserData;
                          break;
   case COLOR3GAD	: 
                          Bodys[CurBody].pen = (UBYTE)GAD->UserData;
                          break;
   case COLOR4GAD	: 
                          Bodys[CurBody].pen = (UBYTE)GAD->UserData;
                          break;
   case COLOR5GAD	: 
                          Bodys[CurBody].pen = (UBYTE)GAD->UserData;
                          break;
   case COLOR6GAD	: 
                          Bodys[CurBody].pen = (UBYTE)GAD->UserData;
                          break;
   case COLOR7GAD	: 
                          Bodys[CurBody].pen = (UBYTE)GAD->UserData;
                          break;
   case FNOKGAD		: 
                          CancelHit=0;
                          break;
   case FNCANGAD	: 
                          CancelHit=1;
                          break;
   case STGAD		: if(st.Flags&SELECTED) {
                             st.GadgetText = &yes_text;
                             RefreshGadgets(&st,w,&SetupInfo);
                             SI.ShowTime=1;
                          } else {
                             st.GadgetText = &no_text;
                             RefreshGadgets(&st,w,&SetupInfo);
                             SI.ShowTime=0;
                          };
                          break;
   case FXGAD		: if(fixed.Flags&SELECTED) {
                             fixed.GadgetText = &yes_text;
                             RefreshGadgets(&fixed,w,&BodyInfo);
                             Bodys[CurBody].fixed=1;
                          } else {
                             fixed.GadgetText = &no_text;
                             RefreshGadgets(&fixed,w,&BodyInfo);
                             Bodys[CurBody].fixed=0;
                          };
                          break;

   default		: 
			  break;

};

return(0);
}



DoMenu(MN)
USHORT MN;
{

switch(MENUNUM(MN)) {

   case M_Control : 
                    switch(ITEMNUM(MN)) { 

                       case I_Stop    : 
                                        mode = MODE_STOP;
                                        ClosePw();
			                OffMenu(w,MN_CStop);
			                OnMenu(w,MN_ECreate);
			                OnMenu(w,MN_ESetup);
			                OnMenu(w,MN_EMod);
			                OnMenu(w,MN_EClearS);
			                OnMenu(w,MN_EClearB);
                                        OnMenu(w,MN_FSDat);
                                        OnMenu(w,MN_FLDat);
                                        OnMenu(w,MN_CStart);
                                        break;
                       case I_Start   : 
                                        if(TotalBodys == 0) {
			                   MSG("No Bodys Created!!","OK","OK");
			                   break;
			                };
			                mode = MODE_START;
			                ModifyIDCMP(w,IDCMPFL);
                                        if(SI.ShowTime) OpenPw();
			                OffMenu(w,MN_ECreate);
			                OffMenu(w,MN_ESetup);
			                OffMenu(w,MN_EMod);
			                OffMenu(w,MN_CStart);
			                OffMenu(w,MN_EClearS);
			                OffMenu(w,MN_EClearB);
                                        OffMenu(w,MN_FSDat);
                                        OffMenu(w,MN_FLDat);
                                        OnMenu(w,MN_CStop);
   			                DoSimulation();
                                        break;
                    };
                    break;

   case M_Edit    :
                    switch(ITEMNUM(MN)) {

                       case I_ClearB  : if(MSG("Really Clear Bodys?",
                                               "YES!","NO!!")) {
                                           ClearBodys();
                                           OnMenu(w,MN_FSDat);
                                           OnMenu(w,MN_FLDat);
                                        };

                                        break;
                       case I_ClearS  : 
                                        ClearScreen();
                                        break;
                       case I_Modify  : 
                                        mode = MODE_MODIFY;
                                        ModifyBodys();
                                        break;
                       case I_Create  : 
                                        mode = MODE_CREATE;
			                ModifyIDCMP(w,IDCMPFL_MM);
                                        OpenPw();
			                OnMenu(w,MN_CStart);
			                OnMenu(w,MN_EMod);
			                OffMenu(w,MN_ECreate);
                                        OffMenu(w,MN_FLDat);
                                        cancel_text.IText = cantxt;
                                        break;
                       case I_Setup   : 
                                        Request(&SetupInfo,w);
                                        Sleep(0L,100000L);
                                        ActivateGadget(&G,w,&SetupInfo);
                                        ReqActive = ReqSetup;
                                        DoIntuiEvents(NOWAIT);
                                        cancel_text.IText = cantxt;
                                        if(!CancelHit) GetSetupInfo();
                                        break;
                    };
                    break;

   case M_File    :
                    switch(ITEMNUM(MN)) {

                       case I_Exit    : 
                                        CleanUp();
                                        exit(0);
                                        break;
                       case I_SavScr  : 
                                        break;
                       case I_SavDat  : 
                                        DoSaveData();
                                        break;
                       case I_LoadDat : 
                                        DoLoadData();
                                        break;
                       case I_About   : 
                                        Request(&RAbout,w);
                                        ReqActive = ReqAbout;
                                        DoIntuiEvents(NOWAIT);
                                        break;
                    };
                    break;

};

return(0);

}



ShowMouse(x,y)
SHORT x,y;
{

if(x<0||y<0) return(0);

sprintf(pbuf,"  %.3e , %.3e",
   (double)(x)*SI.ds,(double)(w->GZZHeight-y+1)*SI.ds);
PrintIText(prp,&pos_txt,0,0);

return(0);
}

OpenPw()
{

if(pw) return(0);

if (!(pw = (struct Window *)OpenWindow(&npw) )) {
   printf("could not open the pointer window\n");
   CleanUp();
   exit(17);
};

prp = pw->RPort;
SetAPen(prp,BLKPEN);
SetBPen(prp,WHTPEN);

return(0);

}

ClosePw()
{
if(pw) CloseWindow(pw);
pw = (struct Window *)NULL;
return(0);
}



GetBodyInfo()
{
double V;


strcpy(Bodys[CurBody].name,name_sbuf);

if(sscanf(mass_sbuf,"%le",&Bodys[CurBody].Mass) != 1) {
   MSG("Bad Value for Mass!","OK","OK");
   return(0);
};

Bodys[CurBody].Dir  =
   (double)direction_txstr.LongInt * PI/180.0;

if(sscanf(velocity_sbuf,"%le",&V) != 1) {
   MSG("Bad Value for Velocity!","OK","OK");
   return(0);
};

Bodys[CurBody].Vx  =
   V * cos(Bodys[CurBody].Dir);
Bodys[CurBody].Vy  =
   V * sin(Bodys[CurBody].Dir);

if(Bodys[CurBody].pen == 0)
   Bodys[CurBody].pen = WHTPEN;

Bodys[CurBody].Fx  =  0.0;
Bodys[CurBody].Fy  =  0.0;


return(0);
}





GetSetupInfo()
{
WORD	n;

if(sscanf(G_sbuf,"%le",&SI.G) != 1) {
   MSG("Bad Value for G!","OK","OK");
   return(0);
};

if(sscanf(ds_sbuf,"%le",&SI.ds) != 1) {
   MSG("Bad Value for ds!","OK","OK");
   return(0);
};

if(sscanf(dt_sbuf,"%le",&SI.dt) != 1) {
   MSG("Bad Value for dt!","OK","OK");
   return(0);
};

SI.t  = (UBYTE)t_txstr.LongInt;

n = (WORD)tl_txstr.LongInt;
if(n>MAXTRAILEN) {
   MSG("TrailLength too large!","OK","OK");
   return(0);
};

SI.TrailLength=n;

return(0);
}




MakeBody(x,y)
SHORT x,y;
{


if(mode != MODE_CREATE)
   return(0);
if(x<0 || y<0)
   return(0);

if(TotalBodys++ > MAXBODYS) {
   TotalBodys--;
   MSG("No More Body's Available!!","OK","OK");
   return(0);
};

SetAPen(rp,REDPEN);
WritePixel(rp,x,y);
CurBody = FindFreeBody();
if(CurBody == -1) {
   TotalBodys--;
   MSG("No More Body's Available!!","OK","OK");
   return(0);
};
cancel_text.IText = cantxt;

BodyGADefaults();
sprintf(xy_buf," %.3e , %.3e ",
   (double)(x)*SI.ds,(double)(w->GZZHeight-y+1)*SI.ds);
Request(&BodyInfo,w);
ReqActive = ReqBody;
Sleep(0L,100000L);
ActivateGadget(&name,w,&BodyInfo);
DoIntuiEvents(NOWAIT);

if(CancelHit) {
   TotalBodys--;
   SetAPen(rp,BLKPEN);
   WritePixel(rp,x,y);
   return(0);
};

GetBodyInfo();

if(Bodys[CurBody].Mass == 0.0) {
   TotalBodys--;
   SetAPen(rp,BLKPEN);
   WritePixel(rp,x,y);
   MSG("Mass can't be ZERO!!","OK","OK");
   return(0);
};

Bodys[CurBody].x = (double)(x)*SI.ds;
Bodys[CurBody].y = (double)(y)*SI.ds;
Bodys[CurBody].real = 1;
SetAPen(rp,Bodys[CurBody].pen);
WritePixel(rp,x,y);

return(0);

}

FindFreeBody()
{
int	x;

for(x=0 ; x<MAXBODYS ; x++)
   if(!Bodys[x].real) return(x);

return(-1);

}


ModifyBodys()
{
int 	n;
SHORT	x,y;


mode = MODE_MODIFY;
ModifyIDCMP(w,IDCMPFL);
ClosePw();
OffMenu(w,MN_CStart);
OffMenu(w,MN_EMod);
OffMenu(w,MN_ECreate);
cancel_text.IText = deltxt;


for(n=0 ; n<MAXBODYS ; n++) {

   CurBody = n;
   if(Bodys[n].real) {

      Bodys[n].real=0;

      LoadBodyReq(n);
      x = (SHORT)(Bodys[n].x/SI.ds);
      y = (SHORT)(Bodys[n].y/SI.ds);

      color_text.FrontPen = Bodys[n].pen;
      sprintf(xy_buf," %.3e , %.3e ",
              (double)(x)*SI.ds,(double)(w->GZZHeight-y+1)*SI.ds);
      Request(&BodyInfo,w);
      Sleep(0L,100000L);
      ReqActive = ReqBody;
      ActivateGadget(&name,w,&BodyInfo);
      DoIntuiEvents(NOWAIT);
   
      if(CancelHit) {		/* GAD really says "DELETE" */
         TotalBodys--;
         SetAPen(rp,BLKPEN);
         WritePixel(rp,x,y);
         continue;
      };
   
      GetBodyInfo();
   
      if(Bodys[n].Mass == 0.0) {
         TotalBodys--;
         SetAPen(rp,BLKPEN);
         WritePixel(rp,(SHORT)x,(SHORT)y);
         MSG("Mass can't be ZERO!!","OK","OK");
         continue;
      };
   
      Bodys[n].real=1;
      SetAPen(rp,Bodys[CurBody].pen);
      WritePixel(rp,x,y);

   };
};


color_text.FrontPen = WHTPEN;

OnMenu(w,MN_CStart);
OnMenu(w,MN_ECreate);
if(TotalBodys)
   OnMenu(w,MN_EMod);
else
   OffMenu(w,MN_EMod);

return(0);

}



LoadBodyReq(n)
int	n;
{
double	v;

name_txstr.NumChars = sprintf(name_sbuf,"%s",Bodys[n].name);
mass_txstr.NumChars = sprintf(mass_sbuf,"%.3e",Bodys[n].Mass);

direction_txstr.LongInt = (ULONG)(Bodys[n].Dir * 180.0/PI);
direction_txstr.NumChars = sprintf(direction_sbuf,"%ld",
                                   direction_txstr.LongInt);

v = Bodys[n].Vx*Bodys[n].Vx + Bodys[n].Vy*Bodys[n].Vy;
v = sqrt(v);
velocity_txstr.NumChars = sprintf(velocity_sbuf,"%.3e",v);

if(Bodys[n].fixed) {
   fixed.GadgetText = &yes_text;
   fixed.Flags |= SELECTED;
} else {
   fixed.GadgetText = &no_text;
   fixed.Flags &= ~SELECTED;
}

return(0);
}




DoSimulation()
{
UBYTE b;
double	 F,Fx=0.0,Fy=0.0,V;


Et = 0L;

while(mode == MODE_START) {

 if(SI.ShowTime) {
   sprintf(pbuf," Time  %ld",Et);
   PrintIText(prp,&pos_txt,0,0);
 };

 /*
  * Calculate new directions & velocities
  * resulting from all external forces (all other bodies)
  */

 for(b=0 ; b<MAXBODYS ; b++ ) {

   if(!Bodys[b].real) continue;
   if(Bodys[b].fixed) continue;

   GetForces(b,&Fx,&Fy);

   if(Fx != 0.0 && Fy != 0.0) {

      F = sqrt(Fx*Fx+Fy*Fy);           /* Total force on this body */
				       /*  indirection of A */
      V = F*SI.dt/Bodys[b].Mass;       /* FT = MV (impulse) */

      Bodys[b].Vx += V*Fx/F; /* Fx/F = cos(A). adj vel as a result of impulse */
      Bodys[b].Vy += V*Fy/F; /* Fy/F = sin(A).  in direction of resultant Force */

      Bodys[b].Dir = atan2(Bodys[b].Vy,Bodys[b].Vx);  /* new direction */
      if(Bodys[b].Dir < 0.0)
	 Bodys[b].Dir += 2.0*PI;
   };


 };

 /*
  * Display new positions
  */

 for(b=0 ; b<MAXBODYS ; b++) {

   if(!Bodys[b].real) continue;

   if(!SI.TrailLength) {
      SetAPen(rp,BLKPEN);
      WritePixel(rp,(SHORT)(Bodys[b].x/SI.ds),(SHORT)(Bodys[b].y/SI.ds));
   } else {
      if(SI.TrailLength>0) {
         if(Bodys[b].TrailIDX >= SI.TrailLength)
            Bodys[b].TrailIDX=0;
         if(Bodys[b].TrailX[Bodys[b].TrailIDX]>=0) {
            SetAPen(rp,BLKPEN);
            WritePixel(rp,(SHORT)(Bodys[b].TrailX[Bodys[b].TrailIDX]/SI.ds),
                          (SHORT)(Bodys[b].TrailY[Bodys[b].TrailIDX]/SI.ds));
            
         };
         Bodys[b].TrailX[Bodys[b].TrailIDX] = Bodys[b].x;
         Bodys[b].TrailY[Bodys[b].TrailIDX] = Bodys[b].y;
         Bodys[b].TrailIDX++;
      };
   };

   Bodys[b].x += (Bodys[b].Vx * SI.dt);
   Bodys[b].y -= (Bodys[b].Vy * SI.dt);

   SetAPen(rp,Bodys[b].pen);
   WritePixel(rp,(SHORT)(Bodys[b].x/SI.ds),(SHORT)(Bodys[b].y/SI.ds));

 };

DoIntuiEvents(NOWAIT);

if(SI.t)
   Sleep(0L,(ULONG)SI.t*1000L);

Et++;

};

return(0);
}


GetForces(b,fx,fy)
UBYTE b;
double *fx,*fy;
{
int x;
double dY,dX,Fx,Fy,F,R;

Fx = Fy = 0.0;
for(x=0 ; x<MAXBODYS ; x++) {

   if(x==b) continue;
   if(!Bodys[x].real) continue;

   dX =  (Bodys[x].x - Bodys[b].x) * SI.ds; /* X2 - X1 */
   dY =  (Bodys[b].y - Bodys[x].y) * SI.ds; /* Y2 - Y1 BUT */
					     /* normalized to bottom */
					     /* left of window */

   R = dX*dX+dY*dY;	/* part of sqrt(x^2+y^2) */
			/* for F calc below. F needs R^2 so we'll */
			/* do sqrt() part after (sqrt(x)^2 = x) */

   F =	SI.G * (Bodys[b].Mass * Bodys[x].Mass) / R;

   R = sqrt(R);         /* distance between points */

   Fx += F*(dX/R);    /* dX/R = cos(A) but faster */
   Fy += F*(dY/R);    /* dY/R = sin(A) but faster */

};

*fx = Fx;
*fy = Fy;

return(0);
}


SetupGADefaults()
{

strcpy(G_sbuf,"6.67e0");
G_txstr.NumChars=6;

strcpy(t_sbuf,"0");
t_txstr.NumChars=1;
t_txstr.LongInt = 0L;

strcpy(ds_sbuf,"1.0e0");
ds_txstr.NumChars=5;

strcpy(dt_sbuf,"1.0e0");
dt_txstr.NumChars=5;

strcpy(tl_sbuf,"0");
dt_txstr.NumChars=1;

st.GadgetText = &no_text;
st.Flags &= ~SELECTED;

return(0);

}


BodyGADefaults()
{

strcpy(name_sbuf,"SUN");
name_txstr.NumChars=3;

strcpy(mass_sbuf,"1.0e0");
mass_txstr.NumChars=5;

strcpy(velocity_sbuf,"0.0e0");
velocity_txstr.NumChars=5;

strcpy(direction_sbuf,"0");
direction_txstr.NumChars=1;
direction_txstr.LongInt = 0L;

fixed.GadgetText = &no_text;
fixed.Flags &= ~SELECTED;

return(0);

}



ClearBodys()
{
int	x,t;

for(x=0 ; x<MAXBODYS ; x++) {
   Bodys[x].real = 0;
   Bodys[x].pen = 0;

   for(t=0 ; t<MAXTRAILEN ; t++) {
      Bodys[x].TrailX[t]=-1.0;
      Bodys[x].TrailY[t]=-1.0;
   };
   Bodys[x].TrailIDX = 0;

};

ClearScreen();
TotalBodys = 0;

return(0);

}



DoSaveData()
{
int	x;
FILE	*fp;


Request(&FileName,w);
Sleep(0L,100000L);
ActivateGadget(&fn,w,&FileName);
ReqActive = ReqFile;
DoIntuiEvents(NOWAIT);

if(CancelHit)
   return(0);


fp = fopen(fn_sbuf,"w");
if(!fp) {
   MSG("Can't open output file!","OK!","OH WELL!");
   return(0);
};

fprintf(fp,"--- CM Setup Data ---\n");
fprintf(fp,"G=%.3e\n",SI.G);
fprintf(fp,"ds=%.3e\n",SI.ds);
fprintf(fp,"dt=%.3e\n",SI.dt);
fprintf(fp,"t=%d\n",(int)SI.t);
fprintf(fp,"TrailLength=%d\n",(int)SI.TrailLength);
fprintf(fp,"ShowTime=%d\n",(int)SI.ShowTime);


fprintf(fp,"--- CM Body Data ---\n");
fprintf(fp,"#=name ;pen;fixed;Radius;Mass;x,y;Vx,Vy;Dir\n");
for(x=0 ; x<MAXBODYS ; x++)
   if(Bodys[x].real) {
      fprintf(fp,"%d=%s ;%d;%d;%ld;%.3e;%.3e,%.3e;%.3e,%.3e;%.3e\n",
		x,
		Bodys[x].name,
		(int)Bodys[x].pen,
                (int)Bodys[x].fixed,
		Bodys[x].Radius,
		Bodys[x].Mass,
		Bodys[x].x,w->GZZHeight-Bodys[x].y+1,
		Bodys[x].Vx,Bodys[x].Vy,
		Bodys[x].Dir*180.0/PI);
		
   };

fclose(fp);

return(0);

}



DoLoadData()
{
int	n;
FILE	*fp;
char	buf[101],name[10];
ULONG	pen,fixed,Radius;
double	x,y,Mass,Vx,Vy,Dir;


CancelHit=0;
Request(&FileName,w);
Sleep(0L,100000L);
ActivateGadget(&fn,w,&FileName);
ReqActive = ReqFile;
DoIntuiEvents(NOWAIT);

if(CancelHit)
   return(0);


fp = fopen(fn_sbuf,"r");
if(!fp) {
   MSG("Can't open Data File!","OK!","OH WELL!");
   return(0);
};

fgets(buf,100,fp);	/* forget first line, it's just a header */
fgets(buf,100,fp);
if(sscanf(buf,"G=%le\n",&SI.G) != 1) {
   MSG("Bad data file (G)!","OK!","OOPS!");
   fclose(fp);
   return(0);
};

fgets(buf,100,fp);
if(sscanf(buf,"ds=%le\n",&SI.ds) != 1) {
   MSG("Bad data file (ds)!","OK!","OOPS!");
   fclose(fp);
   return(0);
};

fgets(buf,100,fp);
if(sscanf(buf,"dt=%le\n",&SI.dt) != 1) {
   MSG("Bad data file (dt)!","OK!","OOPS!");
   fclose(fp);
   return(0);
};

fgets(buf,100,fp);
if(sscanf(buf,"t=%d\n",&n) != 1) {
   MSG("Bad data file (t)!","OK!","OOPS!");
   fclose(fp);
   return(0);
};
SI.t = (UBYTE)n;

fgets(buf,100,fp);
if(sscanf(buf,"TrailLength=%d\n",&n) != 1) {
   MSG("Bad data file (TrailLength)!","OK!","OOPS!");
   fclose(fp);
   return(0);
};

if(n>MAXTRAILEN) {
   MSG("TrailLength too big","OK!","OOPS!");
   fclose(fp);
   return(0);
};
SI.TrailLength = (WORD)n;

fgets(buf,100,fp);
if(sscanf(buf,"ShowTime=%d\n",&n) != 1) {
   MSG("Bad data file (ShowTime)!","OK!","OOPS!");
   fclose(fp);
   return(0);
};
SI.ShowTime = (WORD)n;
if(n) {
   st.GadgetText = &yes_text;
   st.Flags |= SELECTED;
} else {
   st.GadgetText = &no_text;
   st.Flags &= ~SELECTED;
};   


G_txstr.NumChars  = sprintf(G_sbuf,"%.3e",SI.G);
dt_txstr.NumChars = sprintf(dt_sbuf,"%.3e",SI.dt);
t_txstr.NumChars  = sprintf(t_sbuf,"%d",SI.t);
t_txstr.LongInt   = (ULONG)SI.t;
ds_txstr.NumChars = sprintf(ds_sbuf,"%.3e",SI.ds);
tl_txstr.LongInt  = (LONG)SI.TrailLength; 
tl_txstr.NumChars = sprintf(tl_sbuf,"%d",SI.TrailLength);

ClearBodys();

fgets(buf,100,fp);
fgets(buf,100,fp);

while(fgets(buf,100,fp) != (char *)NULL) {
   if(sscanf(buf,"%d=%s;%d;%d;%ld;%le;%le,%le;%le,%le;%le",
             &n,name,&pen,&fixed,&Radius,&Mass,&x,&y,&Vx,&Vy,&Dir) != 11) {
      MSG("Bad Body in data file","OK!","OOPS!");
      fclose(fp);
      return(0);
   };
   if(n>MAXBODYS) {
      MSG("Bad Body # in data file","OK!","OOPS!");
      fclose(fp);
      return(0);
   };

   if(!Bodys[n].real) TotalBodys++; /* don't count same definition twice */
   Bodys[n].real   = 1;
   Bodys[n].pen    = pen;
   Bodys[n].fixed  = fixed;
   Bodys[n].Radius = Radius; 
   Bodys[n].Mass   = Mass;
   Bodys[n].x      = x;
   Bodys[n].y      = w->GZZHeight-y+1;
   Bodys[n].Vx     = Vx;
   Bodys[n].Vy     = Vy;
   Bodys[n].Dir    = Dir*PI/180.0;
   strcpy(Bodys[n].name,name);

   SetAPen(rp,pen);
   WritePixel(rp,(SHORT)(Bodys[n].x/SI.ds),(SHORT)(Bodys[n].y/SI.ds));
		
};

fclose(fp);

OnMenu(w,MN_ECreate);
OnMenu(w,MN_EMod);


return(0);

}


MSG(msg,p,n)
char	*msg,*p,*n;
{

struct IntuiText msg_txt = {
   BLKPEN,REDPEN,			/* FrontPen, BackPen */
   JAM1,				/* DrawMode */
   10,20,				  /* LeftEdge, TopEdge */
   &TxtAt_Plain,			/* TextAttr */
   0,					/* IText */
   NULL 				/* NextText */
};

struct IntuiText pos_txt = {
   BLKPEN,REDPEN,			/* FrontPen, BackPen */
   JAM1,				/* DrawMode */
   3,3, 				/* LeftEdge, TopEdge */
   &TxtAt_Plain,			/* TextAttr */
   0,					/* IText */
   NULL 				/* NextText */
};

struct IntuiText neg_txt = {
   BLKPEN,REDPEN,			/* FrontPen, BackPen */
   JAM1,				/* DrawMode */
   3,3, 				/* LeftEdge, TopEdge */
   &TxtAt_Plain,			/* TextAttr */
   0,					/* IText */
   NULL 				/* NextText */
};

msg_txt.IText = msg;
pos_txt.IText = p;
neg_txt.IText = n;


return(AutoRequest(w,&msg_txt,&pos_txt,&neg_txt,
		   NULL,NULL,40+IntuiTextLength(&msg_txt),80));

}






Sleep(secs,usecs)
ULONG	secs,usecs;
{

TR->tr_node.io_Command = TR_ADDREQUEST;
TR->tr_time.tv_secs = secs;
TR->tr_time.tv_micro = usecs;
DoIO(TR);

return(0);

}





CleanUp()
{

if(TimerPort) DeletePort(TimerPort);
if(TimerOpen) CloseDevice(TR);
if(TR) DeleteExtIO(TR,sizeof(struct timerequest));
if(w) {
   ClearMenuStrip(w);
   CloseWindow(w);
};
if(pw) CloseWindow(pw);
if(s) {
   CloseScreen(s);
/*   FreeColorMap(cm); */
};
if(GfxBase) CloseLibrary(GfxBase);
(void)OpenWorkBench();
if(IntuitionBase) CloseLibrary(IntuitionBase);
return(0);
}










