/* 4D Navigator   ©1991, Jerry D. Hedden, All Rights Reserved

The 4D Navigator program, source code, documenation, and icon imagery are
freely redistributable, but may not be modified in any way, or used in part
or in whole without the express written consent of the author.	Images
generated by the 4D Navigator program may be used for any purpose provided
credit is given to the program's author.

	Jerry D. Hedden
	28 Windemere Dr.
	Sicklerville, NJ  08081
	Phone:	(609)629-3975
	Email:	Hedden@ESDSDF.DNet.GE.com

===========================================================================

This source code REQUIRES Aztec 'C' v3.6a.  It probably cannot be handled
by any other compiler because of the assumed register usage within the
embedded assembly code.  Make with the following:

	as startup
	cc +x3 -e500 -z10000 4d
	ln -m -o "4D Navigator" startup.o 4d.o -lc

===========================================================================

The following copyright notice accompanied the source code for Palette
Requester which was used as a model for this program's Color and View
Parameter requesters.

Palette Requester -- (c) Copyright 1989 Jonathan Potter
This program is freely redistributable, although all rights to it remain
with the author. It may be used freely in any program as long as this
notice remains intact, however, if you do this, please mention the author
in the program.  If you wish to use this in a commercial program of any
kind, you must register with a $15 donation.
Please send donations, bug reports, comments and suggestions to :
	Jonathan Potter
	3 William Street
	Clarence Park 5034
	South Australia
	Australia

===========================================================================

History:

v1.00 - 10 Aug 1991
  Initial release

**************************************************************************/

#include <intuition/intuition.h>

#define TIMING 0
#define DEBUG  0

/* corrects <exec/memory.h> long integer problems */
#define CLEARED_CHIP	0x00010002L

/* determines "grid spacing" */
#define INTERVAL	18 /* degrees */
#define SEGMENTS	(360/INTERVAL)
#define ONE80		(SEGMENTS/2)
#define LATRNG		((SEGMENTS/4)-1)
#define NUM_GRID_PTS	(long)(SEGMENTS*LATRNG*SEGMENTS)

/* circle constants */
#define PI		(float)3.1415926535897932384626433832795
#define HALF_DEG	(float)(PI/360.0)

/* rotation constants */
#define RESTART        -1
#define STOPPED 	0
#define TEN_DEGREES	20
#define NINETY_DEGREES	180
#define THREE_SIXTY	720

/* the 4 coordinates */
#define X 0L
#define Y 1L
#define Z 2L
#define W 3L

/* required libraries */
struct Library	*IntuitionBase, *GfxBase, *MathBase, *MathTransBase,
		*_OpenLibrary();

/* Motorola FFP math routines */
long  SPFix(), SPCmp(), SPTst();
float SPFlt(), SPNeg(), SPAdd(), SPSub(), SPMul(), SPDiv();
float SPSincos(), SPAcos(), SPSqrt();

/* information for display and graphics */
#define WIDTH		656L
#define HEIGHT		430L
#define PLANES		3
#define PLANESIZE	(long)((WIDTH>>3)*HEIGHT)

struct Screen *screen, *OpenScreen();
struct ViewPort *vp;
struct Window *window, *OpenWindow();
struct RastPort *rp;
struct IntuiMessage *_GetMsg();
PLANEPTR buffer1[PLANES], buffer2[PLANES];

struct NewScreen MyScreen = {
  0,0, WIDTH,HEIGHT,PLANES, 0,1, HIRES|LACE,CUSTOMSCREEN,
  NULL,(UBYTE *)"4D Navigator",NULL,NULL
};

struct NewWindow MyWindow = {
  0,0, WIDTH,HEIGHT, 0,1, RAWKEY,
  NOCAREREFRESH|BORDERLESS|ACTIVATE|SIMPLE_REFRESH|RMBTRAP,
  NULL,NULL,NULL,NULL,NULL, WIDTH,HEIGHT, WIDTH,HEIGHT, CUSTOMSCREEN
};

/* colors */
#define BLACK	0L
#define WHITE	7L

UWORD ColorTables[3][1<<PLANES] = {
  /* B/W    RED   GREEN   R+G	BLUE	R+B    G+B    W/B */
  {0x0000,0x0A00,0x0070,0x0A90,0x000A,0x0B0A,0x0079,0x0CCC},
  {0x0FFF,0x0F00,0x00C0,0x0F90,0x000F,0x0C0E,0x00BA,0x0000},
  /* used by color requester */
  {0x0000,0x0A00,0x0070,0x0A90,0x000A,0x0B0A,0x0079,0x0CCC}
};

/* arrays for holding pre-computed, converted "floating point" values */
short *cos, *sin, *factor, *_AllocMem();

/* arrays for holding the "GRID" */
short *p4d, *e4d, *x4d, *p3d, *e3d, *x3d, *p2d, *e2d, *x2d;
/* indexing macro for grid points */
#define INDX(i,j,k) (((((i)*LATRNG)+(j))*SEGMENTS)+(k))

/* view parameters */
short FrontLimit = 0, BackLimit = 15000, ScaleFactor = 30;

/* point is out of range */
#define DONT_PLOT	32000
#asm
NO_PLOT 	equ	32000
#endasm


/* sets up the pre-computed, converted "floating point" arrays */
Initialize()
{
  register long i;
  register float tsin;
  float tcos;
  register float w;
  register short *x, *y;

  Countdown();
  x = cos;  y = sin;
  for (i=0; i<720; i++) {
    tsin = SPSincos(&tcos,SPMul(HALF_DEG,SPFlt(i)));
    *(x++) = (SPTst(tcos) >= 0) ? SPFix(SPAdd(0.5,SPMul(tcos,8192.0))) :
				  SPFix(SPSub(0.5,SPMul(tcos,8192.0)));
    *(y++) = (SPTst(tsin) >= 0) ? SPFix(SPAdd(0.5,SPMul(tsin,8192.0))) :
				  SPFix(SPSub(0.5,SPMul(tsin,8192.0)));
  }

  Countdown();
  x = factor+1;
  for (i=-8191; i<=8191; i++) {
    if (!(i&0x000001FF))  Countdown();
    w = SPDiv(8192.0,SPFlt(i));
    *(x++) = SPFix(SPAdd(SPMul(SPDiv(SPSqrt(SPSub(SPMul(w,w),1.0)),
							SPAcos(w)),128.0),0.5));
  }
  Countdown();
}


/* sets the "GRID" to the "starting position" */
initial_pos()
{
  short plong,lat,elong;
  register short regd4, regd5;
  long ind;
  register long ind1,ind2;
  register short *pts = x4d;

  ind = 0;
  for (plong=0; plong<180; plong+=INTERVAL) {
    e4d[ind+X] = p4d[ind+Z] = sin[plong*2];
    e4d[ind+W] = p4d[ind+Y] = cos[plong*2];
    e4d[ind+Y] = e4d[ind+Z] = p4d[ind+X] = p4d[ind+W] = 0;
    e4d[ind+(ONE80*4+X)] = p4d[ind+(ONE80*4+Z)] = sin[(plong+180)*2];
    e4d[ind+(ONE80*4+W)] = p4d[ind+(ONE80*4+Y)] = cos[(plong+180)*2];
    e4d[ind+(ONE80*4+Y)] = e4d[ind+(ONE80*4+Z)] =
    p4d[ind+(ONE80*4+X)] = p4d[ind+(ONE80*4+W)] = 0;
    ind += 4;
    for (lat=INTERVAL; lat<=(90-INTERVAL); lat+=INTERVAL) {
      ind1 = INDX(plong/INTERVAL,(lat/INTERVAL)-1,0)*4;
      ind2 = INDX((plong/INTERVAL)+ONE80,(lat/INTERVAL)-1,ONE80)*4;
      for (elong=0; elong<180; elong+=INTERVAL) {

	regd4 = cos[lat*2]; regd5 = sin[plong*2];
#asm
	muls	d4,d5
	moveq	#13,d4
	asr.l	d4,d5
#endasm
	pts[ind1+X] = regd5; regd5 = (-regd5); pts[ind2+X] = regd5;

	regd4 = sin[lat*2]; regd5 = cos[elong*2];
#asm
	muls	d4,d5
	moveq	#13,d4
	asr.l	d4,d5
#endasm
	pts[ind1+Y] = regd5; regd5 = (-regd5); pts[ind2+Y] = regd5;

	regd4 = sin[lat*2]; regd5 = sin[elong*2];
#asm
	muls	d4,d5
	moveq	#13,d4
	asr.l	d4,d5
#endasm
	pts[ind1+Z] = regd5; regd5 = (-regd5); pts[ind2+Z] = regd5;

	regd4 = cos[lat*2]; regd5 = cos[plong*2];
#asm
	muls	d4,d5
	moveq	#13,d4
	asr.l	d4,d5
#endasm
	pts[ind1+W] = regd5; regd5 = (-regd5); pts[ind2+W] = regd5;

	ind1+=4; ind2+=4;
      }
      ind1 = INDX(plong/INTERVAL,(lat/INTERVAL)-1,ONE80)*4;
      ind2 = INDX((plong/INTERVAL)+ONE80,(lat/INTERVAL)-1,0)*4;
      for (elong=180; elong<360; elong+=INTERVAL) {

	regd4 = cos[lat*2]; regd5 = sin[plong*2];
#asm
	muls	d4,d5
	moveq	#13,d4
	asr.l	d4,d5
#endasm
	pts[ind1+X] = regd5; regd5 = (-regd5); pts[ind2+X] = regd5;

	regd4 = sin[lat*2]; regd5 = cos[elong*2];
#asm
	muls	d4,d5
	moveq	#13,d4
	asr.l	d4,d5
#endasm
	pts[ind1+Y] = regd5; regd5 = (-regd5); pts[ind2+Y] = regd5;

	regd4 = sin[lat*2]; regd5 = sin[elong*2];
#asm
	muls	d4,d5
	moveq	#13,d4
	asr.l	d4,d5
#endasm
	pts[ind1+Z] = regd5; regd5 = (-regd5); pts[ind2+Z] = regd5;

	regd4 = cos[lat*2]; regd5 = cos[plong*2];
#asm
	muls	d4,d5
	moveq	#13,d4
	asr.l	d4,d5
#endasm
	pts[ind1+W] = regd5; regd5 = (-regd5); pts[ind2+W] = regd5;

	ind1+=4; ind2+=4;
      }
    }
  }
}


/* rotates the four-dimensional space */
rotate_4d(dir1,dir2,angle)
long dir1, dir2;
short angle;
{
  short plong,lat,elong;
  register short regd4, regd5, cangle, sangle;
  short *p4d11, *p4d12, *p4d21, *p4d22;
  short *e4d11, *e4d12, *e4d21, *e4d22;
  register short *x4d11, *x4d12;
  short *x4d21, *x4d22;

  if (angle == RESTART)  {
    initial_pos();
    return;
  }

  cangle = cos[angle];	sangle = sin[angle];

  p4d11 = p4d + dir1;
  p4d12 = p4d + dir2;
  p4d21 = p4d + ONE80*4 + dir1;
  p4d22 = p4d + ONE80*4 + dir2;

  e4d11 = e4d + dir1;
  e4d12 = e4d + dir2;
  e4d21 = e4d + ONE80*4 + dir1;
  e4d22 = e4d + ONE80*4 + dir2;

  x4d11 = x4d + dir1;
  x4d12 = x4d + dir2;
  x4d21 = x4d + INDX(ONE80,0,-ONE80)*4 + dir1;
  x4d22 = x4d + INDX(ONE80,0,-ONE80)*4 + dir2;

  for (plong=0; plong++ < ONE80; ) {
    regd4 = *p4d11; regd5 = *p4d12;
#asm
	move.w	d4,d0	; d1' = d1*cos - d2*sin
	muls	d6,d0
	move.w	d5,d2
	muls	d7,d2
	sub.l	d2,d0
	move.w	d4,d1	; d2' = d1*sin + d2*cos
	muls	d7,d1
	move.w	d5,d2
	muls	d6,d2
	add.l	d2,d1
	moveq	#13,d3	; normalize
	asr.l	d3,d0
	asr.l	d3,d1
	move.w	d0,d4
	move.w	d1,d5
#endasm
    *p4d11 = regd4;  regd4 = (-regd4);  *p4d21 = regd4;
    *p4d12 = regd5;  regd5 = (-regd5);  *p4d22 = regd5;
    p4d11+=4; p4d12+=4; p4d21+=4; p4d22+=4;

    regd4 = *e4d11; regd5 = *e4d12;
#asm
	move.w	d4,d0
	muls	d6,d0
	move.w	d5,d2
	muls	d7,d2
	sub.l	d2,d0
	move.w	d4,d1
	muls	d7,d1
	move.w	d5,d2
	muls	d6,d2
	add.l	d2,d1
	moveq	#13,d3
	asr.l	d3,d0
	asr.l	d3,d1
	move.w	d0,d4
	move.w	d1,d5
#endasm
    *e4d11 = regd4;  regd4 = (-regd4);  *e4d21 = regd4;
    *e4d12 = regd5;  regd5 = (-regd5);  *e4d22 = regd5;
    e4d11+=4; e4d12+=4; e4d21+=4; e4d22+=4;

    for (lat=0; lat++ < LATRNG; ) {
      x4d21 += (SEGMENTS*4);  x4d22 += (SEGMENTS*4);
      for (elong=0; elong++ < ONE80; ) {
	regd4 = *x4d11; regd5 = *x4d12;
#asm
	move.w	d4,d0
	muls	d6,d0
	move.w	d5,d2
	muls	d7,d2
	sub.l	d2,d0
	move.w	d4,d1
	muls	d7,d1
	move.w	d5,d2
	muls	d6,d2
	add.l	d2,d1
	moveq	#13,d3
	asr.l	d3,d0
	asr.l	d3,d1
	move.w	d0,d4
	move.w	d1,d5
#endasm
	*x4d11 = regd4;  regd4 = (-regd4);  *x4d21 = regd4;
	*x4d12 = regd5;  regd5 = (-regd5);  *x4d22 = regd5;
	x4d11+=4; x4d12+=4; x4d21+=4; x4d22+=4;
      }

      x4d21 -= (SEGMENTS*4);  x4d22 -= (SEGMENTS*4);
      for (elong=ONE80; elong++ < SEGMENTS; ) {
	regd4 = *x4d11; regd5 = *x4d12;
#asm
	move.w	d4,d0
	muls	d6,d0
	move.w	d5,d2
	muls	d7,d2
	sub.l	d2,d0
	move.w	d4,d1
	muls	d7,d1
	move.w	d5,d2
	muls	d6,d2
	add.l	d2,d1
	moveq	#13,d3
	asr.l	d3,d0
	asr.l	d3,d1
	move.w	d0,d4
	move.w	d1,d5
#endasm
	*x4d11 = regd4;  regd4 = (-regd4);  *x4d21 = regd4;
	*x4d12 = regd5;  regd5 = (-regd5);  *x4d22 = regd5;
	x4d11+=4; x4d12+=4; x4d21+=4; x4d22+=4;
      }
    }
  }
}


/* maps the four-dimensional space into three dimensions */
map_to_3d()
{
  register short regd4, regd5, regd6, regd7;
  short wval;
  short plong,lat,elong;
  short *p4d1, *p3d1, *p3d2;
  short *e4d1, *e3d1, *e3d2;
  register short *x4d1;
  short *x3d1;
  register short *x3d2;

  p4d1 = p4d;  p3d1 = p3d;  p3d2 = p3d + ONE80*3;
  e4d1 = e4d;  e3d1 = e3d;  e3d2 = e3d + ONE80*3;
  x4d1 = x4d;  x3d1 = x3d;  x3d2 = x3d + INDX(ONE80,0,-ONE80)*3;

  for (plong=0; plong++ < ONE80; ) {
    if ((wval = *(p4d1+W)) >= 8192) {
      *(p4d1+W) = 8192;
      *(p3d1+X) = *(p4d1+X); *(p3d1+Y) = *(p4d1+Y); *(p3d1+Z) = *(p4d1+Z);
      *(p4d1+ONE80*4+W) = -8192;
      *(p3d2+X) = DONT_PLOT; *(p3d2+Y) = *(p3d2+Z) = 0;
    } else
    if (wval <= -8192) {
      *(p4d1+W) = -8192;
      *(p3d1+X) = DONT_PLOT; *(p3d1+Y) = *(p3d1+Z) = 0;
      *(p4d1+ONE80*4+W) = 8192;
      *(p3d2+X) = (-(*(p4d1+X))); *(p3d2+Y) = (-(*(p4d1+Y)));
      *(p3d2+Z) = (-(*(p4d1+Z)));
    } else {
      regd6 = factor[8192+wval]; regd7 = factor[8192-wval];

      regd4 = *(p4d1+X);
#asm
dummy1: move.w	d4,d5
	neg.w	d5
	cmpi.w	#1000,d6
	blt.s	1$
	move.w	#NO_PLOT,d4
	bra.s	2$
1$	muls	d6,d4
	asr.l	#7,d4
	cmpi.w	#1000,d7
	blt.s	2$
	move.w	#NO_PLOT,d5
	bra.s	3$
2$	muls	d7,d5
	asr.l	#7,d5
3$
#endasm
      *(p3d1+X) = regd4; *(p3d2+X) = regd5;

      regd4 = *(p4d1+Y);
#asm
	move.w	d4,d5
	neg.w	d5
	muls	d6,d4
	muls	d7,d5
	asr.l	#7,d4
	asr.l	#7,d5
#endasm
      *(p3d1+Y) = regd4; *(p3d2+Y) = regd5;

      regd4 = *(p4d1+Z);
#asm
	move.w	d4,d5
	neg.w	d5
	muls	d6,d4
	muls	d7,d5
	asr.l	#7,d4
	asr.l	#7,d5
#endasm
      *(p3d1+Z) = regd4; *(p3d2+Z) = regd5;
    }
    p4d1+=4; p3d1+=3; p3d2+=3;

    if ((wval = *(e4d1+W)) >= 8192) {
      *(e4d1+W) = 8192;
      *(e3d1+X) = *(e4d1+X); *(e3d1+Y) = *(e4d1+Y); *(e3d1+Z) = *(e4d1+Z);
      *(e4d1+ONE80*4+W) = -8192;
      *(e3d2+X) = DONT_PLOT; *(e3d2+Y) = *(e3d2+Z) = 0;
    } else
    if (wval <= -8192) {
      *(e4d1+W) = -8192;
      *(e3d1+X) = DONT_PLOT; *(e3d1+Y) = *(e3d1+Z) = 0;
      *(e4d1+ONE80*4+W) = 8192;
      *(e3d2+X) = (-(*(e4d1+X))); *(e3d2+Y) = (-(*(e4d1+Y)));
      *(e3d2+Z) = (-(*(e4d1+Z)));
    } else {
      regd6 = factor[8192+wval]; regd7 = factor[8192-wval];

      regd4 = *(e4d1+X);
#asm
dummy2: move.w	d4,d5
	neg.w	d5
	cmpi.w	#1000,d6
	blt.s	1$
	move.w	#NO_PLOT,d4
	bra.s	2$
1$	muls	d6,d4
	asr.l	#7,d4
	cmpi.w	#1000,d7
	blt.s	2$
	move.w	#NO_PLOT,d5
	bra.s	3$
2$	muls	d7,d5
	asr.l	#7,d5
3$
#endasm
      *(e3d1+X) = regd4; *(e3d2+X) = regd5;

      regd4 = *(e4d1+Y);
#asm
	move.w	d4,d5
	neg.w	d5
	muls	d6,d4
	muls	d7,d5
	asr.l	#7,d4
	asr.l	#7,d5
#endasm
      *(e3d1+Y) = regd4; *(e3d2+Y) = regd5;

      regd4 = *(e4d1+Z);
#asm
	move.w	d4,d5
	neg.w	d5
	muls	d6,d4
	muls	d7,d5
	asr.l	#7,d4
	asr.l	#7,d5
#endasm
      *(e3d1+Z) = regd4; *(e3d2+Z) = regd5;
    }
    e4d1+=4; e3d1+=3; e3d2+=3;

    for (lat=0; lat++ < LATRNG; ) {
      x3d2 += (SEGMENTS*3);
      for (elong=0; elong++ < ONE80; ) {
	if ((wval = *(x4d1+W)) >= 8192) {
	  *(x4d1+W) = 8192;
	  *(x3d1+X) = *(x4d1+X); *(x3d1+Y) = *(x4d1+Y); *(x3d1+Z) = *(x4d1+Z);
	  *(x4d1+INDX(ONE80,0,ONE80)*4+W) = -8192;
	  *(x3d2+X) = DONT_PLOT; *(x3d2+Y) = *(x3d2+Z) = 0;
	} else
	if (wval <= -8192) {
	  *(x4d1+W) = -8192;
	  *(x3d1+X) = DONT_PLOT; *(x3d1+Y) = *(x3d1+Z) = 0;
	  *(x4d1+INDX(ONE80,0,ONE80)*4+W) = 8192;
	  *(x3d2+X) = (-(*(x4d1+X))); *(x3d2+Y) = (-(*(x4d1+Y)));
	  *(x3d2+Z) = (-(*(x4d1+Z)));
	} else {
	  regd6 = factor[8192+wval]; regd7 = factor[8192-wval];

	  regd4 = *(x4d1+X);
#asm
dummy3: move.w	d4,d5
	neg.w	d5
	cmpi.w	#1000,d6
	blt.s	1$
	move.w	#NO_PLOT,d4
	bra.s	2$
1$	muls	d6,d4
	asr.l	#7,d4
	cmpi.w	#1000,d7
	blt.s	2$
	move.w	#NO_PLOT,d5
	bra.s	3$
2$	muls	d7,d5
	asr.l	#7,d5
3$
#endasm
	  *(x3d1+X) = regd4; *(x3d2+X) = regd5;

	  regd4 = *(x4d1+Y);
#asm
	move.w	d4,d5
	neg.w	d5
	muls	d6,d4
	muls	d7,d5
	asr.l	#7,d4
	asr.l	#7,d5
#endasm
	  *(x3d1+Y) = regd4; *(x3d2+Y) = regd5;

	  regd4 = *(x4d1+Z);
#asm
	move.w	d4,d5
	neg.w	d5
	muls	d6,d4
	muls	d7,d5
	asr.l	#7,d4
	asr.l	#7,d5
#endasm
	  *(x3d1+Z) = regd4; *(x3d2+Z) = regd5;
	}
	x4d1+=4; x3d1+=3; x3d2+=3;
      }
      x3d2 -= (SEGMENTS*3);
      for (elong=ONE80; elong++ < SEGMENTS; ) {
	if ((wval = *(x4d1+W)) >= 8192) {
	  *(x4d1+W) = 8192;
	  *(x3d1+X) = *(x4d1+X); *(x3d1+Y) = *(x4d1+Y); *(x3d1+Z) = *(x4d1+Z);
	  *(x4d1+INDX(ONE80,0,-ONE80)*4+W) = -8192;
	  *(x3d2+X) = DONT_PLOT; *(x3d2+Y) = *(x3d2+Z) = 0;
	} else
	if (wval <= -8192) {
	  *(x4d1+W) = -8192;
	  *(x3d1+X) = DONT_PLOT; *(x3d1+Y) = *(x3d1+Z) = 0;
	  *(x4d1+INDX(ONE80,0,-ONE80)*4+W) = 8192;
	  *(x3d2+X) = (-(*(x4d1+X))); *(x3d2+Y) = (-(*(x4d1+Y)));
	  *(x3d2+Z) = (-(*(x4d1+Z)));
	} else {
	  regd6 = factor[8192+wval]; regd7 = factor[8192-wval];

	  regd4 = *(x4d1+X);
#asm
dummy4: move.w	d4,d5
	neg.w	d5
	cmpi.w	#1000,d6
	blt.s	1$
	move.w	#NO_PLOT,d4
	bra.s	2$
1$	muls	d6,d4
	asr.l	#7,d4
	cmpi.w	#1000,d7
	blt.s	2$
	move.w	#NO_PLOT,d5
	bra.s	3$
2$	muls	d7,d5
	asr.l	#7,d5
3$
#endasm
	  *(x3d1+X) = regd4; *(x3d2+X) = regd5;

	  regd4 = *(x4d1+Y);
#asm
	move.w	d4,d5
	neg.w	d5
	muls	d6,d4
	muls	d7,d5
	asr.l	#7,d4
	asr.l	#7,d5
#endasm
	  *(x3d1+Y) = regd4; *(x3d2+Y) = regd5;

	  regd4 = *(x4d1+Z);
#asm
	move.w	d4,d5
	neg.w	d5
	muls	d6,d4
	muls	d7,d5
	asr.l	#7,d4
	asr.l	#7,d5
#endasm
	  *(x3d1+Z) = regd4; *(x3d2+Z) = regd5;
	}
	x4d1+=4; x3d1+=3; x3d2+=3;
      }
    }
  }
}


/* maps the 3D space into coordinates for the screen */
map_to_screen()
{
  register short scale = (-ScaleFactor), back = BackLimit, front = FrontLimit, temp;
  short plong, lat, elong;
  short *p3d1, *p2d1, *e3d1, *e2d1;
  register short *x3d1, *x2d1;

  p3d1 = p3d; p2d1 = p2d;  e3d1 = e3d; e2d1 = e2d;  x3d1 = x3d; x2d1 = x2d;

  for (plong=0; plong++ < SEGMENTS; ) {
    if ((temp=*(p3d1+X)) < front  ||  temp > back)
      *(p2d1+X) = DONT_PLOT;
    else {
      *(p2d1+X) = (WIDTH/2) + (*(p3d1+Y)/scale);
      temp = *(p3d1+Z);
#asm
      muls   #7,d7	; for aspect ratio
      asr.l  #3,d7
      divs   d4,d7	; *(p2d1+Y) = (HEIGHT/2) + (*(p3d1+Z)/scale);
      add.w  #215,d7
#endasm
      *(p2d1+Y) = temp;
    }
    p3d1+=3;  p2d1+=2;

    if ((temp=*(e3d1+X)) < front  ||  temp > back)
      *(e2d1+X) = DONT_PLOT;
    else {
      *(e2d1+X) = (WIDTH/2)  + (*(e3d1+Y)/scale);
      temp = *(e3d1+Z);
#asm
      muls   #7,d7	; for aspect ratio
      asr.l  #3,d7
      divs   d4,d7	; *(e2d1+Y) = (HEIGHT/2) + (*(e3d1+Z)/scale);
      add.w  #215,d7
#endasm
      *(e2d1+Y) = temp;
    }
    e3d1+=3;  e2d1+=2;

    for (lat=0; lat++ < LATRNG; ) {
      for (elong=0; elong++ < SEGMENTS; ) {
	if ((temp=*(x3d1+X)) < front  ||  temp > back)
	  *(x2d1+X) = DONT_PLOT;
	else {
	  *(x2d1+X) = (WIDTH/2)  + (*(x3d1+Y)/scale);
	  temp = *(x3d1+Z);
#asm
	  muls	 #7,d7	    ; for aspect ratio
	  asr.l  #3,d7
	  divs	 d4,d7	    ; *(x2d1+Y) = (HEIGHT/2) + (*(x3d1+Z)/scale);
	  add.w  #215,d7
#endasm
	  *(x2d1+Y) = temp;
	}
	x3d1+=3;  x2d1+=2;
      }
    }
  }
}


#define LEFT	1
#define RIGHT	2
#define TOP	4
#define BOTTOM	8

/* plots the "GRID" into the screen buffer */
plot_in_buffer()
{
  short i, plong, lat, elong;
  long index;
  register long num;
  short firstx, firsty, farfirst;
  register short x, y;
  short far1;
  register short far2=0;
  register short *toplot, *toget;
  short plot_buf[(SEGMENTS+1)*2];

  toplot = plot_buf;
  SetBuffer();

  /*** EQUATOR & POLAR RING ***/
  for (i=0; i<2; i++) {
    toget = (i==0) ? e2d: p2d;
    num = 0; far1 = 0;
    if ((firstx = toget[0]) != DONT_PLOT) {
      toplot[0] = firstx;  toplot[1] = firsty = toget[1];  num = 2;
      if (firstx < 0)
	far1 |= LEFT;
      else if (firstx >= WIDTH)
	far1 |= RIGHT;
      if (firsty < 0)
	far1 |= TOP;
      else if (firsty >= HEIGHT)
	far1 |= BOTTOM;
      farfirst = far1;
    }
    for (elong=2; elong<SEGMENTS*2; elong+=2) {
      if ((x = toget[elong]) != DONT_PLOT) {
	y = toget[elong+1];
	if (x < 0)
	  far2 |= LEFT;
	else if (x >= WIDTH)
	  far2 |= RIGHT;
	if (y < 0)
	  far2 |= TOP;
	else if (y >= HEIGHT)
	  far2 |= BOTTOM;
	if (far1 & far2) {
	  if (num >= 4) {
	    Move(rp,(long)toplot[0],(long)toplot[1]);
	    PolyDraw(rp,num>>1,toplot);
	    num = 2;
	  }
	  toplot[0] = x;
	  toplot[1] = y;
	} else {
	  toplot[num++] = x;
	  toplot[num++] = y;
	}
	far1 = far2;  far2 = 0;
      } else {
	if (num >= 4) {
	  Move(rp,(long)toplot[0],(long)toplot[1]);
	  PolyDraw(rp,num>>1,toplot);
	}
	num = 0;  far1 = 0;
      }
    }
    if (num >= 2) {
      if (firstx != DONT_PLOT) {
	if (!(far1 & farfirst)) {
	  toplot[num++] = firstx;  toplot[num++] = firsty;
	}
      }
      if (num >= 4) {
	Move(rp,(long)toplot[0],(long)toplot[1]);
	PolyDraw(rp,num>>1,toplot);
      }
    }
  }

  toget = x2d;

  /*** CIRCUM-EQUATORIAL ***/
  rp->Mask = 0xFC;
  for (lat=0; lat<LATRNG; lat++) {
    for (elong=0; elong<SEGMENTS; elong++) {
      num = 0; far1 = 0;
      index=INDX(0,lat,elong)*2;
      if ((firstx = toget[index]) != DONT_PLOT) {
	toplot[0] = firstx;  toplot[1] = firsty = toget[index+1];  num = 2;
	if (firstx < 0)
	  far1 |= LEFT;
	else if (firstx >= WIDTH)
	  far1 |= RIGHT;
	if (firsty < 0)
	  far1 |= TOP;
	else if (firsty >= HEIGHT)
	  far1 |= BOTTOM;
	farfirst = far1;
      }
      for (plong=2; plong<SEGMENTS*2; plong+=2) {
	index += LATRNG*SEGMENTS*2;
	if ((x = toget[index]) != DONT_PLOT) {
	  y = toget[index+1];
	  if (x < 0)
	    far2 |= LEFT;
	  else if (x >= WIDTH)
	    far2 |= RIGHT;
	  if (y < 0)
	    far2 |= TOP;
	  else if (y >= HEIGHT)
	    far2 |= BOTTOM;
	  if (far1 & far2) {
	    if (num >= 4) {
	      Move(rp,(long)toplot[0],(long)toplot[1]);
	      PolyDraw(rp,num>>1,toplot);
	      num = 2;
	    }
	    toplot[0] = x;
	    toplot[1] = y;
	  } else {
	    toplot[num++] = x;
	    toplot[num++] = y;
	  }
	  far1 = far2;	far2 = 0;
	} else {
	  if (num >= 4) {
	    Move(rp,(long)toplot[0],(long)toplot[1]);
	    PolyDraw(rp,num>>1,toplot);
	  }
	  num = 0;  far1 = 0;
	}
      }
      if (num >= 2) {
	if (firstx != DONT_PLOT) {
	  if (!(far1 & farfirst)) {
	    toplot[num++] = firstx;  toplot[num++] = firsty;
	  }
	}
	if (num >= 4) {
	  Move(rp,(long)toplot[0],(long)toplot[1]);
	  PolyDraw(rp,num>>1,toplot);
	}
      }
    }
  }

  /*** CIRCUM-POLAR ***/
  rp->Mask = 0xF9;
  for (lat=0; lat<LATRNG; lat++) {
    for (plong=0; plong<SEGMENTS; plong++) {
      num = 0; far1 = 0;
      index=INDX(plong,lat,0)*2;
      if ((firstx = toget[index]) != DONT_PLOT) {
	toplot[0] = firstx;  toplot[1] = firsty = toget[index+1];  num = 2;
	if (firstx < 0)
	  far1 |= LEFT;
	else if (firstx >= WIDTH)
	  far1 |= RIGHT;
	if (firsty < 0)
	  far1 |= TOP;
	else if (firsty >= HEIGHT)
	  far1 |= BOTTOM;
	farfirst = far1;
      }
      for (elong=2; elong<SEGMENTS*2; elong+=2) {
	index += 2;
	if ((x = toget[index]) != DONT_PLOT) {
	  y = toget[index+1];
	  if (x < 0)
	    far2 |= LEFT;
	  else if (x >= WIDTH)
	    far2 |= RIGHT;
	  if (y < 0)
	    far2 |= TOP;
	  else if (y >= HEIGHT)
	    far2 |= BOTTOM;
	  if (far1 & far2) {
	    if (num >= 4) {
	      Move(rp,(long)toplot[0],(long)toplot[1]);
	      PolyDraw(rp,num>>1,toplot);
	      num = 2;
	    }
	    toplot[0] = x;
	    toplot[1] = y;
	  } else {
	    toplot[num++] = x;
	    toplot[num++] = y;
	  }
	  far1 = far2;	far2 = 0;
	} else {
	  if (num >= 4) {
	    Move(rp,(long)toplot[0],(long)toplot[1]);
	    PolyDraw(rp,num>>1,toplot);
	  }
	  num = 0;  far1 = 0;
	}
      }
      if (num >= 2) {
	if (firstx != DONT_PLOT) {
	  if (!(far1 & farfirst)) {
	    toplot[num++] = firstx;  toplot[num++] = firsty;
	  }
	}
	if (num >= 4) {
	  Move(rp,(long)toplot[0],(long)toplot[1]);
	  PolyDraw(rp,num>>1,toplot);
	}
      }
    }
  }

  /*** LONG LINES ***/
  rp->Mask = 0xFA;
  for (elong=0; elong<SEGMENTS; elong++) {
    for (plong=0; plong<SEGMENTS; plong++) {
      num = 0; far1 = 0;
      if (!(elong&1))
	if ((x = e2d[plong*2]) != DONT_PLOT) {
	  toplot[0] = x; y = toplot[1] = e2d[plong*2+1];  num=2;
	  if (x < 0)
	    far1 |= LEFT;
	  else if (x >= WIDTH)
	    far1 |= RIGHT;
	  if (y < 0)
	    far1 |= TOP;
	  else if (y >= HEIGHT)
	    far1 |= BOTTOM;
	}
      index=INDX(plong,0,elong)*2;
      for (lat=0; lat<LATRNG; lat++) {
	if ((x = toget[index]) != DONT_PLOT) {
	  y = toget[index+1];
	  if (x < 0)
	    far2 |= LEFT;
	  else if (x >= WIDTH)
	    far2 |= RIGHT;
	  if (y < 0)
	    far2 |= TOP;
	  else if (y >= HEIGHT)
	    far2 |= BOTTOM;
	  if (far1 & far2) {
	    if (num >= 4) {
	      Move(rp,(long)toplot[0],(long)toplot[1]);
	      PolyDraw(rp,num>>1,toplot);
	      num = 2;
	    }
	    toplot[0] = x;
	    toplot[1] = y;
	  } else {
	    toplot[num++] = x;
	    toplot[num++] = y;
	  }
	  far1 = far2;	far2 = 0;
	} else {
	  if (num >= 4) {
	    Move(rp,(long)toplot[0],(long)toplot[1]);
	    PolyDraw(rp,num>>1,toplot);
	  }
	  num = 0;  far1 = 0;
	}
	index += SEGMENTS*2;
      }
      if (num >= 2) {
	if (!(plong&1))
	  if ((x = p2d[elong*2]) != DONT_PLOT) {
	    y = p2d[elong*2+1];
	    if (x < 0)
	      far2 |= LEFT;
	    else if (x >= WIDTH)
	      far2 |= RIGHT;
	    if (y < 0)
	      far2 |= TOP;
	    else if (y >= HEIGHT)
	      far2 |= BOTTOM;
	    if (!(far1 & far2)) {
	      toplot[num++] = x; toplot[num++] = y;
	    }
	    far2 = 0;
	  }
	if (num >= 4) {
	  Move(rp,(long)toplot[0],(long)toplot[1]);
	  PolyDraw(rp,num>>1,toplot);
	}
      }
    }
  }
  rp->Mask = 0xFF;
  DisplayBuffer();
}


/* for double buffering */
SetBuffer()
{
  register long i;

  if (rp->BitMap->Planes[0] == buffer1[0])
    for (i=0; i<PLANES; i++)
      rp->BitMap->Planes[i] = buffer2[i];
  else
    for (i=0; i<PLANES; i++)
      rp->BitMap->Planes[i] = buffer1[i];
  SetRast(rp,BLACK);
}


DisplayBuffer()
{
  WaitBOVP(vp);
  WaitTOF();
  ScrollVPort(vp);
  RethinkDisplay();
}


#define ONE_KEY 	1
#define ZERO_KEY	10
#define ESCAPE		69
#define SPACE		64
#define RKEY		19
#define TKEY		20
#define XKEY		50
#define CKEY		51
#define VKEY		52
#define KP8		62
#define KP2		30
#define KP4		45
#define KP6		47
#define LEFTARROW	79
#define RIGHTARROW	78
#define UPARROW 	76
#define DOWNARROW	77
#define HELP		95
#if DEBUG
#define DKEY		34
#endif

#define TRANSLATE	1
#define ROTATE		0

MainLoop()
{
  register struct IntuiMessage *msg;
  register ULONG class;
  register USHORT code, qualifier;
  short mode, amount, step_rate;
  long fromdir, toward;
  short ColorSet = BLACK;
#if TIMING
  long starttime[3],endtime[3];
#endif

  Initialize();
  mode = TRANSLATE; fromdir = X; toward  = W; amount = RESTART, step_rate = 10;

  FOREVER {
    if (amount == STOPPED) {
      _WaitPort(window->UserPort);
    } else {

#if TIMING
      DateStamp(starttime);
#endif
      rotate_4d(fromdir,toward,amount);
      map_to_3d();
      map_to_screen();
      plot_in_buffer();
#if TIMING
      DateStamp(endtime);
      PrintTime("Frame time",starttime,endtime);
#endif
      if (amount != step_rate  &&  amount != THREE_SIXTY-step_rate)
	amount = STOPPED;
    }
    while (msg = _GetMsg(window->UserPort)) {
      class = msg->Class;  code = msg->Code;  qualifier = msg->Qualifier;
      _ReplyMsg(msg);
      if (class == RAWKEY) {
	switch (code) {
#if DEBUG
	  case DKEY:
	    if (qualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
	      dump_coordinates(0);      /* only plotted points */
	    else if (qualifier & (IEQUALIFIER_LALT|IEQUALIFIER_RALT))
	      dump_coordinates(1);      /* all points */
	    else
	      dump_screen_plot();
	    break;
#endif
	  case RKEY:
	  case TKEY:
	     mode = (code == TKEY);
	     break;
	  case XKEY:
	    mode = TRANSLATE;  fromdir = X;  toward  = W;  amount = RESTART;
	    break;
	  case CKEY:
	    if (qualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT)) {
	      ColorRequester(ColorSet);
	      if (amount == STOPPED)  plot_in_buffer();
	    } else {
	      ColorSet = 1 - ColorSet;
	      LoadRGB4(vp, &ColorTables[ColorSet][0], 1L<<PLANES);
	    }
	    break;
	  case VKEY:
	    ViewRequester();
	    if (amount == STOPPED) {
	      map_to_screen();
	      plot_in_buffer();
	    }
	    break;
	  case SPACE:	amount = STOPPED;  break;
	  case KP4:
	  case KP6:
	    fromdir = Y;  toward = (mode) ? W : X;
	    goto calc_amount;
	  case KP8:
	  case KP2:
	    fromdir = Z;  toward = (mode) ? W : X;
	    goto calc_amount;
	  case LEFTARROW:
	  case RIGHTARROW:
	    mode = ROTATE;  fromdir = Z;  toward =  Y;
	    goto calc_amount;
	  case UPARROW:
	  case DOWNARROW:
	    mode = TRANSLATE; fromdir = X; toward =  W;
calc_amount:
	    if (qualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
	      amount = TEN_DEGREES;
	    else if (qualifier & IEQUALIFIER_CONTROL)
	      amount = NINETY_DEGREES;
	    else
	      amount = step_rate;
	    if (code == KP2  ||  code == LEFTARROW  ||
		code == KP6  ||  code == DOWNARROW)
	      amount = THREE_SIXTY - amount;
	    break;
	  case ESCAPE:
	    cleanup(0L);
	  case HELP:
	    LoadRGB4(vp, &ColorTables[0][0], 1L<<PLANES);
	    info();
	    LoadRGB4(vp, &ColorTables[ColorSet][0], 1L<<PLANES);
	    if (amount == STOPPED)  plot_in_buffer();
	    break;
	  default:
	    if (code >= ONE_KEY  &&  code <= ZERO_KEY) {
	      if (amount != STOPPED)
		if (amount == step_rate)
		  amount = code;
		else
		  amount = THREE_SIXTY - code;
	      step_rate = code;
	    }
	}
      }
    }
  }
}


main()
{
  long i;

  if (IntuitionBase = _OpenLibrary("intuition.library", 0L))
  if (GfxBase       = _OpenLibrary("graphics.library", 0L))
  if (MathBase      = _OpenLibrary("mathffp.library", 0L))
  if (MathTransBase = _OpenLibrary("mathtrans.library", 0L))
  if (p4d = _AllocMem((long)(SEGMENTS*8),0L))
  if (e4d = _AllocMem((long)(SEGMENTS*8),0L))
  if (x4d = _AllocMem((long)(NUM_GRID_PTS*8L),0L))
  if (p3d = _AllocMem((long)(SEGMENTS*6),0L))
  if (e3d = _AllocMem((long)(SEGMENTS*6),0L))
  if (x3d = _AllocMem((long)(NUM_GRID_PTS*6L),0L))
  if (p2d = _AllocMem((long)(SEGMENTS*4),0L))
  if (e2d = _AllocMem((long)(SEGMENTS*4),0L))
  if (x2d = _AllocMem((long)(NUM_GRID_PTS*4L),0L))
  if (cos = _AllocMem(1440L,0L))
  if (sin = _AllocMem(1440L,0L))
  if (factor = _AllocMem(32768L,0L))
  if (screen = OpenScreen(&MyScreen)) {
    vp = &screen->ViewPort;
    LoadRGB4(vp,&ColorTables[0][0],1L<<PLANES);
    MyWindow.Screen = screen;
  if (window = OpenWindow(&MyWindow)) {
    rp = window->RPort;
    SetDrMd(rp,(long)JAM1);
    SetAPen(rp,WHITE);
    for (i=0; i<PLANES; i++) {
      buffer1[i] = rp->BitMap->Planes[i];
      if (!(buffer2[i] = (PLANEPTR)_AllocMem(PLANESIZE,CLEARED_CHIP)))
	cleanup(101L);
    }
    MainLoop();   /* terminates via cleanup() */
  }
  }
  cleanup(100L);  /* no direct indication of what went wrong above */
}


cleanup(code)
long code;
{
  long i;

  if (p4d)      _FreeMem(p4d,(long)(SEGMENTS*8));
  if (e4d)      _FreeMem(e4d,(long)(SEGMENTS*8));
  if (x4d)      _FreeMem(x4d,(long)(NUM_GRID_PTS*8L));
  if (p3d)      _FreeMem(p3d,(long)((long)SEGMENTS*6L));
  if (e3d)      _FreeMem(e3d,(long)((long)SEGMENTS*6L));
  if (x3d)      _FreeMem(x3d,(long)(NUM_GRID_PTS*6L));
  if (p2d)      _FreeMem(p2d,(long)((long)SEGMENTS*4L));
  if (e2d)      _FreeMem(e2d,(long)((long)SEGMENTS*4L));
  if (x2d)      _FreeMem(x2d,(long)(NUM_GRID_PTS*4L));
  if (cos)      _FreeMem(cos,1440L);
  if (sin)      _FreeMem(sin,1440L);
  if (factor)   _FreeMem(factor,32768L);
  if (window) {
    if (rp->BitMap->Planes[0] == buffer2[0]) {
      SetBuffer();
      DisplayBuffer();
    }
    CloseWindow(window);
  }
  for (i=0; i<PLANES; i++)
    if (buffer2[i])     _FreeMem(buffer2[i],PLANESIZE);
  if (screen)           CloseScreen(screen);
  if (MathTransBase)    _CloseLibrary(MathTransBase);
  if (MathBase)         _CloseLibrary(MathBase);
  if (GfxBase)          _CloseLibrary(GfxBase);
  if (IntuitionBase)    _CloseLibrary(IntuitionBase);
  exit(code);
}


#define X0	0L
#define X1	20L
#define X2	65L
#define X3	85L
#define Y0	0L
#define Y1	20L
#define Y2	60L
#define Y3	80L
#define Y4	120L
#define Y5	140L

Digit(n,x,y)
short n;
long x,y;
{
  /* first a white rectangle */
  RectFill(rp,x+X0,y+Y0,x+X3,y+Y5);
  /* then "cut out" the appropriate parts */
  SetAPen(rp,BLACK);
  switch (n) {
    case 0: RectFill(rp,x+X1,y+Y1,x+X2,y+Y4); return;
    case 1: RectFill(rp,x+X0,y+Y0,x+X2,y+Y5); return;
    case 2:
    case 3: RectFill(rp,x+X0,y+Y1,x+X2,y+Y2); break;  /* upper section */
    case 4: RectFill(rp,x+X1,y+Y0,x+X2,y+Y2);
	    RectFill(rp,x+X0,y+Y3,x+X2,y+Y5); return;
    case 5:
    case 6: RectFill(rp,x+X1,y+Y1,x+X3,y+Y2); break;  /* upper section */
    case 7: RectFill(rp,x+X0,y+Y1,x+X2,y+Y5); return;
    case 8:
    case 9: RectFill(rp,x+X1,y+Y1,x+X2,y+Y2);         /* upper section */
  }
  /* lower sections */
  if (n==2)
    RectFill(rp,x+X1,y+Y3,x+X3,y+Y4);
  else if (n==3 || n==5 || n==9)
    RectFill(rp,x+X0,y+Y3,x+X2,y+Y4);
  else	/* n==6 || n==8 */
    RectFill(rp,x+X1,y+Y3,x+X2,y+Y4);
}


#define TENS_X	225L
#define ONES_X	340L
#define DIGITY	115L

Countdown()
{
  static short _counter = 33;
  SetBuffer();
  Digit(_counter/10,TENS_X,DIGITY);
  SetAPen(rp,WHITE);
  Digit(_counter%10,ONES_X,DIGITY);
  SetAPen(rp,WHITE);
  DisplayBuffer();
  _counter--;
}


/*** Info function ***/

struct TextAttr InfoFont =
  {(UBYTE *)"topaz.font",8,FS_NORMAL,FPF_ROMFONT|FPF_DESIGNED};
struct IntuiText Text = {6,0,JAM2,0,0,&InfoFont,NULL,NULL};

#define TEXT_MARGIN	20L
#define TEXT_START	25L

char *InfoTitle  =
	" 4D Navigator v1.00    \2511991, Jerry D. Hedden, All Rights Reserved";
char *InfoText[] = {
"You are embedded within the three-dimensional 'surface' of a four-dimensional",
"sphere.  Your initial position is on the equator looking east.  The 'north",
"pole' of the polar ring is to your left.  Pressing 'x' will reset you back to",
"this initial position.  To quit, press 'esc'.",
"",
"You can move (translate) or turn (rotate) using the arrow- and keypad keys.",
"The up/down arrows send you moving forward/backward; the left/right arrows",
"rotate you clockwise/counterclockwise.  The keypad 8, 2, 4 and 6 represent",
"the up, down, left and right directions.  Pressing 't' sets translate mode",
"such that the keypad moves you in the appropriated directions; 'r' set rotate",
"mode so you can turn.  Pressing an arrow-/keypad key alone causes its action",
"to repeat continuously.  Press 'space' to stop.  The change between frames",
"can be set from 0.5\260 to 5\260 using the keyboard 1 thru 0.  'Shift-' arrow-/",
"keypad keys cause a single 10\260 change; 'ctrl-' causes a 90\260 change.",
"",
"The view of your three-dimensional space is controlled by three parameters --",
"the scaling factor, the front cutoff, and the rear cutoff.  The first con-",
"trols the width/height of your view; the other two control its depth.  Press",
"'v' to bring up a requester to change these values.  N.B.  Your view of the",
"space is mapped to the screen without perspective.",
"",
"The coordinate system is:  Latitude (LAT) -- the angular distance between the",
"equator (0\260) and the polar ring (90\260); Polar Longitude (PLONG) -- the angular",
"direction around the polar ring (0\260 - 360\260); and Equatorial Longitude (ELONG)",
"-- the angular direction around the equator (0\260 - 360\260).  This is an extrap-",
"olation into three dimensions of latitude and longitude as used on the earth.",
"",
"The two white 'circles' are the equator and the polar ring.  Green 'longi-",
"tude' lines run between them -- connecting points with the same PLONG and",
"ELONG.  Red connects points with the same LAT and PLONG, and blue connects",
"points with the same LAT and ELONG.",
"",
"Press 'c' to toggle between two color sets (the 'white' set is intended for",
"printing).  'Shift-c' brings up a color requester.",
"",
"The 4D Navigator source code is a combination of 'C' and assembly using Manx",
"Aztec 'C' v3.6a.  The source contains examples of double-buffering, fixed-",
"and floating-point math, and in-line assembly code.  I also used some of the",
"code from Jonathan Potter's color requester.  Send $5.00 to obtain a disk",
"containing source code and updated program.",
"",
"        Comments, questions, bug reports, source requests:",
"",
"                       Jerry D. Hedden",
"                       28 Windemere Dr.",
"                       Sicklerville, NJ  08081",
"                       Phone:  (609)629-3975",
"                       Email:  Hedden@ESDSDF.DNet.GE.com",
NULL
};


info()
{
  register long line;
  register struct Window *info_window;
  register struct IntuiMessage *msg;
  register ULONG class;
  register short all_done;

  MyWindow.IDCMPFlags = CLOSEWINDOW;
  MyWindow.Flags = SIMPLE_REFRESH|NOCAREREFRESH|ACTIVATE|WINDOWCLOSE;
  MyWindow.Title = (UBYTE *)InfoTitle;

  if (!(info_window = OpenWindow(&MyWindow)))   return;

  for (line = 0; Text.IText = (UBYTE *)InfoText[line]; line++)
    PrintIText(info_window->RPort,&Text,TEXT_MARGIN,TEXT_START+(line<<3));

  for (all_done=0; !all_done; ) {
    _WaitPort(info_window->UserPort);
    while (msg = _GetMsg(info_window->UserPort)) {
      class = msg->Class;
      _ReplyMsg(msg);
      if (class == CLOSEWINDOW)  all_done = 1;
    }
  }
  CloseWindow(info_window);
}


/*** common requester stuff ***/

#define RESET	41
#define CANCEL	42
#define OKAY	43

short border_xy[] = {0,0, 115,0, 115,10, 0,10, 0,0};
struct Border MyBorder = {-1,-1,4,0,JAM1,5,border_xy,NULL};

struct IntuiText
  OkayText   = {2,0,JAM1, 41,1,NULL,(UBYTE *)"OKAY",  NULL},
  ResetText  = {3,0,JAM1, 37,1,NULL,(UBYTE *)"RESET", NULL},
  CancelText = {1,0,JAM1, 33,1,NULL,(UBYTE *)"CANCEL",NULL};

struct Image PropImage1, PropImage2, PropImage3;

struct Window *ReqWindow;


/*** Color Requester ***/


#define RED	31
#define GREEN	32
#define BLUE	33

struct IntuiText
  RedText    = {1,0,JAM1,-14,2,NULL,(UBYTE *)"R",NULL},
  GreenText  = {2,0,JAM1,-14,2,NULL,(UBYTE *)"G",NULL},
  BlueText   = {4,0,JAM1,-14,2,NULL,(UBYTE *)"B",NULL},
  ValuesText = {0,1,JAM2,  0,0,NULL,(UBYTE *)"RGB",NULL};

struct PropInfo
  RedProp   = {AUTOKNOB|FREEHORIZ,0,0,0x1000},
  GreenProp = {AUTOKNOB|FREEHORIZ,0,0,0x1000},
  BlueProp  = {AUTOKNOB|FREEHORIZ,0,0,0x1000};

struct Image CurColorImage = {0,0,235,8,1,NULL,0,1,NULL};

struct Gadget
  RedCGadg = {NULL,24,54,100,11,
    GADGHNONE|GADGIMAGE,RELVERIFY|GADGIMMEDIATE,PROPGADGET,
    (APTR)&PropImage1,NULL,&RedText,NULL,(APTR)&RedProp,RED,NULL},
  GreenCGadg = {&RedCGadg,24,66,100,11,
    GADGHNONE|GADGIMAGE,RELVERIFY|GADGIMMEDIATE,PROPGADGET,
    (APTR)&PropImage2,NULL,&GreenText,NULL,(APTR)&GreenProp,GREEN,NULL},
  BlueCGadg = {&GreenCGadg,24,78,100,11,
    GADGHNONE|GADGIMAGE,RELVERIFY|GADGIMMEDIATE,PROPGADGET,
    (APTR)&PropImage3,NULL,&BlueText,NULL,(APTR)&BlueProp,BLUE,NULL},
  OkayCGadg = {&BlueCGadg,130,79,114,9,GADGHCOMP,RELVERIFY,BOOLGADGET,
    (APTR)&MyBorder,NULL,&OkayText,NULL,NULL,OKAY,NULL},
  ResetCGadg = {&OkayCGadg,130,67,114,9,GADGHCOMP,RELVERIFY,BOOLGADGET,
    (APTR)&MyBorder,NULL,&ResetText,NULL,NULL,RESET,NULL},
  CancelCGadg = {&ResetCGadg,130,55,114,9,GADGHCOMP,RELVERIFY,BOOLGADGET,
    (APTR)&MyBorder,NULL,&CancelText,NULL,NULL,CANCEL,NULL};

struct NewWindow ColorNewWindow={
  40,23,255,93,1,4,GADGETUP|GADGETDOWN|MOUSEBUTTONS|RAWKEY,
  ACTIVATE|SIMPLE_REFRESH|NOCAREREFRESH|RMBTRAP,
  &CancelCGadg,NULL,(UBYTE *)" 4D Navigator Color Requester",
  NULL,NULL,0,0,0,0,CUSTOMSCREEN};

long CurColor;


ColorRequester(ColorSet)
short ColorSet;
{
  register long i;
  register ULONG Class;
  register USHORT Code, GadgetID;
  register struct IntuiMessage *msg;

  ColorNewWindow.Screen = screen;
  if (!(ReqWindow = OpenWindow(&ColorNewWindow)))  return;

  for (i=0; i < (1<<PLANES); i++)
    ColorTables[2][i] = ColorTables[ColorSet][i];

  DrawPalette(PLANES);
  CurColorImage.PlaneOnOff = CurColor = 1;
  ValuesText.FrontPen = CurColor^((1<<PLANES)-1);
  ValuesText.BackPen = 1;
  DrawImage(ReqWindow->RPort,&CurColorImage,10L,45L);
  SetRGBProps();

  FOREVER {
    _WaitPort(ReqWindow->UserPort);
    while (msg = _GetMsg(ReqWindow->UserPort)) {
      Class = msg->Class;  Code = msg->Code;
      if (Class == GADGETUP  ||  Class == GADGETDOWN)
	GadgetID = ((struct Gadget *)msg->IAddress)->GadgetID;
      _ReplyMsg(msg);
      switch (Class) {
	case MOUSEBUTTONS: {
	  long x,y;
	  long NewColor, ReadPixel();
	  if (Code != SELECTDOWN)  break;
	  x=ReqWindow->MouseX;	y=ReqWindow->MouseY;
	  if (x<12 || x>243 || y<15 || y>40)  break;
	  if (CurColor == (NewColor = ReadPixel(ReqWindow->RPort,x,y)))  break;
	  CurColorImage.PlaneOnOff = CurColor = NewColor;
	  DrawImage(ReqWindow->RPort,&CurColorImage,10L,45L);
	  SetRGBProps();
	  break;
	  }
	case GADGETDOWN:
	  if (GadgetID >= RED  &&  GadgetID <= BLUE) {
	    do {
	      ReadRGBProps();
	    } while (!(msg = _GetMsg(ReqWindow->UserPort)));
	    _ReplyMsg(msg);
	    ReadRGBProps();
	  }
	  break;
	case RAWKEY:
	  if (Code != ESCAPE)  break;
	  GadgetID = RESET;
	case GADGETUP:
	  switch (GadgetID) {
	    case RESET:
	      for (i=0; i < (1<<PLANES); i++)
		ColorTables[2][i] = ColorTables[ColorSet][i];
	      LoadRGB4(vp, &ColorTables[2][0], 1L<<PLANES);
	      SetRGBProps();
	      break;
	    case OKAY:
	      for (i=0; i < (1<<PLANES); i++)
		ColorTables[ColorSet][i] = ColorTables[2][i];
	    case CANCEL:
	      LoadRGB4(vp, &ColorTables[ColorSet][0], 1L<<PLANES);
	      while (msg = _GetMsg(ReqWindow->UserPort))  _ReplyMsg(msg);
	      CloseWindow(ReqWindow);
	      return;
	  }
      }
    }
  }
}


SetRGBProps()
{
  register UWORD cval = ColorTables[2][CurColor];
  NewModifyProp(&RedCGadg,  ReqWindow,0L,(long)(FREEHORIZ|AUTOKNOB),
			(long)(((cval>>8)&0xf)*0x1111),0L,0x1000L,0L,1L);
  NewModifyProp(&GreenCGadg,ReqWindow,0L,(long)(FREEHORIZ|AUTOKNOB),
			(long)(((cval>>4)&0xf)*0x1111),0L,0x1000L,0L,1L);
  NewModifyProp(&BlueCGadg, ReqWindow,0L,(long)(FREEHORIZ|AUTOKNOB),
			(long)((cval&0xf)*0x1111),0L,0x1000L,0L,1L);
  ShowRGBValues();
}


ReadRGBProps()
{
  register long re,gr,bl;

  re = RedProp.  HorizPot >> 12;
  gr = GreenProp.HorizPot >> 12;
  bl = BlueProp. HorizPot >> 12;
  ColorTables[2][CurColor] = (re<<8)|(gr<<4)|bl;
  SetRGB4(vp,CurColor,re,gr,bl);
  ShowRGBValues();
}


char *hex = "0123456789ABCDEF";

ShowRGBValues()
{
  register UWORD cval = ColorTables[2][CurColor];
  ValuesText.IText[0] = hex[(cval>>8)&0xf];
  ValuesText.IText[1] = hex[(cval>>4)&0xf];
  ValuesText.IText[2] = hex[ cval    &0xf];
  ValuesText.FrontPen = CurColor^((1<<PLANES)-1);
  ValuesText.BackPen  = CurColor;
  PrintIText(ReqWindow->RPort,&ValuesText,115L,45L);
}


DrawPalette(depth)
int depth;
{
  long w,h,ox,x,y,i;

#if PLANES>3
  if (depth==4)
    { w=29; h=12; ox=13; }
  else if (depth==5)
    { w=14; h=12; ox=17; }
  else
#endif
    { w=235/(1<<depth); h=25; ox=12; }
  i=0; x=ox; y=15;
  for (i=0; i<(1<<depth); i++) {
    SetAPen(ReqWindow->RPort,i);
    RectFill(ReqWindow->RPort,x,y,x+w,y+h);
#if PLANES>3
    if (depth>3 && i==(1<<(depth-1))-1) {
      x=ox; y+=h;
    } else
#endif
      x+=w;
  }
}


/*** View Requester ***/

#define FRONT	31
#define BACK	32
#define SCALE	33

#define TEXTX	5L
#define TEXTY	-10L
#define GADGX	8L
#define FGADY	25L
#define BGADY	50L
#define SGADY	75L
#define FBODY	126L
#define BBODY	126L
#define SBODY	661L

struct IntuiText
  FrontText = {6,0,JAM2,TEXTX,TEXTY,NULL,(UBYTE *)"Front Cutoff:       ",NULL},
  BackText  = {6,0,JAM2,TEXTX,TEXTY,NULL,(UBYTE *)"Rear Cutoff :       ",NULL},
  ScaleText = {6,0,JAM2,TEXTX,TEXTY,NULL,(UBYTE *)"Scaling Factor:     ",NULL};

struct PropInfo
  FrontProp = {AUTOKNOB|FREEHORIZ,0,0,FBODY},
  BackProp  = {AUTOKNOB|FREEHORIZ,0,0,BBODY},
  ScaleProp = {AUTOKNOB|FREEHORIZ,0,0,SBODY};

struct Gadget
  FrontVGadg = {NULL, GADGX,FGADY, 640,11,
    GADGHNONE|GADGIMAGE, RELVERIFY|GADGIMMEDIATE, PROPGADGET,
    (APTR)&PropImage1, NULL, &FrontText, NULL, (APTR)&FrontProp, FRONT, NULL},
  BackVGadg  = {&FrontVGadg, GADGX,BGADY, 640,11,
    GADGHNONE|GADGIMAGE, RELVERIFY|GADGIMMEDIATE, PROPGADGET,
    (APTR)&PropImage2, NULL, &BackText, NULL, (APTR)&BackProp, BACK, NULL},
  ScaleVGadg = {&BackVGadg, GADGX,SGADY, 265,11,
    GADGHNONE|GADGIMAGE, RELVERIFY|GADGIMMEDIATE, PROPGADGET,
    (APTR)&PropImage3, NULL, &ScaleText, NULL, (APTR)&ScaleProp, SCALE, NULL},
  OkayVGadg   = {&ScaleVGadg, 285,SGADY+1, 114,9,
    GADGHCOMP, RELVERIFY, BOOLGADGET,
    (APTR)&MyBorder, NULL, &OkayText, NULL, NULL, OKAY, NULL},
  ResetVGadg  = {&OkayVGadg,	409,SGADY+1, 114,9,
    GADGHCOMP, RELVERIFY, BOOLGADGET,
    (APTR)&MyBorder, NULL, &ResetText, NULL, NULL, RESET, NULL},
  CancelVGadg = {&ResetVGadg, 533,SGADY+1, 114,9,
    GADGHCOMP, RELVERIFY, BOOLGADGET,
    (APTR)&MyBorder, NULL, &CancelText, NULL, NULL, CANCEL, NULL};

struct NewWindow ViewNewWindow={
  0,50, 656,93, 1,4, GADGETUP|GADGETDOWN|MOUSEBUTTONS,
  ACTIVATE|SIMPLE_REFRESH|NOCAREREFRESH|RMBTRAP,
  &CancelVGadg,NULL,(UBYTE *)"  4D Navigator View Modifiers",
  NULL,NULL,0,0,0,0,CUSTOMSCREEN};

short NewFront, NewBack, NewScale;


ViewRequester()
{
  register ULONG Class;
  register USHORT GadgetID;
  register struct IntuiMessage *msg;

  ViewNewWindow.Screen = screen;
  if (!(ReqWindow = OpenWindow(&ViewNewWindow)))  return;

  SetViewProps();

  FOREVER {
    _WaitPort(ReqWindow->UserPort);
    while (msg = _GetMsg(ReqWindow->UserPort)) {
      Class = msg->Class;
      if (Class == GADGETUP  ||  Class == GADGETDOWN)
	GadgetID = ((struct Gadget *)msg->IAddress)->GadgetID;
      _ReplyMsg(msg);
      switch (Class) {
	case GADGETDOWN:
	  if (GadgetID >= FRONT  &&  GadgetID <= SCALE) {
	    do {
	      ReadViewProps();
	    } while (!(msg = _GetMsg(ReqWindow->UserPort)));
	    _ReplyMsg(msg);
	    ReadViewProps();
	  }
	  break;
	case GADGETUP:
	  switch (GadgetID) {
	    case RESET:
	      SetViewProps();
	      break;
	    case OKAY:
	      if (NewFront == NewBack)
		if (NewFront > 0)
		  NewFront -= 100;
		else
		  NewBack += 100;
	      if (NewFront < NewBack) {
		FrontLimit = NewFront;
		BackLimit = NewBack;
	      } else {
		FrontLimit = NewBack;
		BackLimit = NewFront;
	      }
	      ScaleFactor = NewScale;
	    case CANCEL:
	      while (msg=_GetMsg(ReqWindow->UserPort))  _ReplyMsg(msg);
	      CloseWindow(ReqWindow);
	      return;
	  }
      }
    }
  }
}


SetViewProps()
{
  NewFront = FrontLimit;
  NewBack  = BackLimit;
  NewScale = ScaleFactor;
  NewModifyProp(&FrontVGadg,ReqWindow,0L,(long)(FREEHORIZ|AUTOKNOB),
			(ULONG)(((NewFront/100)+258)*127),0L,FBODY,0L,1L);
  NewModifyProp(&BackVGadg, ReqWindow,0L,(long)(FREEHORIZ|AUTOKNOB),
			(ULONG)(((NewBack /100)+258)*127),0L,BBODY,0L,1L);
  NewModifyProp(&ScaleVGadg,ReqWindow,0L,(long)(FREEHORIZ|AUTOKNOB),
				(ULONG)((NewScale-1)*661),0L,SBODY,0L,1L);
  ShowViewValues();
}


ReadViewProps()
{
  NewFront = (((USHORT)FrontProp.HorizPot/127)-258)*100;
  NewBack  = (((USHORT)BackProp .HorizPot/127)-258)*100;
  NewScale = ( (USHORT)ScaleProp.HorizPot/656)+1;
  ShowViewValues();
}


ShowViewValues()
{
  SPrintF(&FrontText.IText[14],"%6d",NewFront);
  SPrintF(&BackText. IText[14],"%6d",NewBack);
  SPrintF(&ScaleText.IText[17],"%3d",NewScale);
  PrintIText(ReqWindow->RPort,&FrontText,GADGX,FGADY);
  PrintIText(ReqWindow->RPort,&BackText, GADGX,BGADY);
  PrintIText(ReqWindow->RPort,&ScaleText,GADGX,SGADY);
}


#asm

; A small sprintf() function using the Exec library RawDoFmt() function

	public	_SysBase,_LVORawDoFmt

	public	_SPrintF
_SPrintF
	movem.l a2/a3,-(sp)
	move.l	16(sp),a0
	lea.l	20(sp),a1
	lea.l	put_ch(pc),a2
	move.l	12(sp),a3
	move.l	_SysBase,a6
	jsr	_LVORawDoFmt(a6)
	movem.l (sp)+,a2/a3
	rts

put_ch	move.b	d0,(a3)+
	rts


; Stubs for accessing the math libraries

	public	_MathBase
	public	_MathTransBase

_LVOSPCeil	equ	-96
_LVOSPFloor	equ	-90
_LVOSPDiv	equ	-84
_LVOSPMul	equ	-78
_LVOSPSub	equ	-72
_LVOSPAdd	equ	-66
_LVOSPNeg	equ	-60
_LVOSPAbs	equ	-54
_LVOSPTst	equ	-48
_LVOSPCmp	equ	-42
_LVOSPFlt	equ	-36
_LVOSPFix	equ	-30

_LVOSPLog10	equ	-126
_LVOSPAcos	equ	-120
_LVOSPAsin	equ	-114
_LVOSPFieee	equ	-108
_LVOSPTieee	equ	-102
_LVOSPSqrt	equ	-96
_LVOSPPow	equ	-90
_LVOSPLog	equ	-84
_LVOSPExp	equ	-78
_LVOSPTanh	equ	-72
_LVOSPCosh	equ	-66
_LVOSPSinh	equ	-60
_LVOSPSincos	equ	-54
_LVOSPTan	equ	-48
_LVOSPCos	equ	-42
_LVOSPSin	equ	-36
_LVOSPAtan	equ	-30


	public	_SPFix
_SPFix
	move.l	4(sp),d0
	move.l	_MathBase,a6
	jmp	_LVOSPFix(a6)

	public	_SPFlt
_SPFlt
	move.l	4(sp),d0
	move.l	_MathBase,a6
	jmp	_LVOSPFlt(a6)

	public	_SPCmp
_SPCmp
	move.l	4(sp),d1
	move.l	12(sp),d0
	move.l	_MathBase,a6
	jmp	_LVOSPCmp(a6)

	public	_SPNeg
_SPNeg
	move.l	4(sp),d0
	move.l	_MathBase,a6
	jmp	_LVOSPNeg(a6)

	public	_SPAdd
_SPAdd
	move.l	4(sp),d1
	move.l	12(sp),d0
	move.l	_MathBase,a6
	jmp	_LVOSPAdd(a6)

	public	_SPSub
_SPSub
	move.l	4(sp),d1
	move.l	12(sp),d0
	move.l	_MathBase,a6
	jmp	_LVOSPSub(a6)

	public	_SPMul
_SPMul
	move.l	4(sp),d1
	move.l	12(sp),d0
	move.l	_MathBase,a6
	jmp	_LVOSPMul(a6)

	public	_SPDiv
_SPDiv
	move.l	4(sp),d1
	move.l	12(sp),d0
	move.l	_MathBase,a6
	jmp	_LVOSPDiv(a6)

	public	_SPTst
_SPTst
	move.l	4(sp),d1
	move.l	_MathBase,a6
	jmp	_LVOSPTst(a6)

;	 public  _SPAbs
;_SPAbs
;	 move.l  4(sp),d0
;	 move.l  _MathBase,a6
;	 jmp	 _LVOSPAbs(a6)
;
;	 public  _SPCeil
;_SPCeil
;	 move.l  4(sp),d0
;	 move.l  _MathBase,a6
;	 jmp	 _LVOSPCeil(a6)
;
;	 public  _SPFloor
;_SPFloor
;	 move.l  4(sp),d0
;	 move.l  _MathBase,a6
;	 jmp	 _LVOSPFloor(a6)


	public	_SPSqrt
_SPSqrt
	move.l	4(sp),d0
	move.l	_MathTransBase,a6
	jmp	_LVOSPSqrt(a6)

	public	_SPSincos
_SPSincos
	move.l	4(sp),d1
	move.l	8(sp),d0
	move.l	_MathTransBase,a6
	jmp	_LVOSPSincos(a6)

;	 public  _SPSin
;_SPSin
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPSin(a6)
;
;	 public  _SPCos
;_SPCos
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPCos(a6)
;
;	 public  _SPTan
;_SPTan
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPTan(a6)
;
;	 public  _SPAsin
;_SPAsin
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPAsin(a6)

	public	_SPAcos
_SPAcos
	move.l	4(sp),d0
	move.l	_MathTransBase,a6
	jmp	_LVOSPAcos(a6)

;	 public  _SPAtan
;_SPAtan
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPAtan(a6)
;
;	 public  _SPSinh
;_SPSinh
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPSinh(a6)
;
;	 public  _SPCosh
;_SPCosh
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPCosh(a6)
;
;	 public  _SPTanh
;_SPTanh
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPTanh(a6)
;
;	 public  _SPExp
;_SPExp
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPExp(a6)
;
;	 public  _SPLog
;_SPLog
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPLog(a6)
;
;	 public  _SPPow
;_SPPow
;	 move.l  4(sp),d1
;	 move.l  12(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPPow(a6)
;
;	 public  _SPLog10
;_SPLog10
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPLog10(a6)
;
;	 public  _SPTieee
;_SPTieee
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPTieee(a6)
;
;	 public  _SPFieee
;_SPFieee
;	 move.l  4(sp),d0
;	 move.l  _MathTransBase,a6
;	 jmp	 _LVOSPFieee(a6)

#endasm


#if TIMING

PrintTime(what,start,finish)
char *what;
long *start,*finish;
{
  long dd;
  register long hh, mm, ss, xx;  /*** d4, d5, d6, d7 ***/

  if (start[2] > finish[2])   { finish[1]--;  finish[2] += 3000; }
  if (start[1] > finish[1])   { finish[0]--;  finish[1] += 1440; }
  dd=finish[0]-start[0]; mm=finish[1]-start[1]; xx=finish[2]-start[2];
#asm
	move.l	d5,d4	; hh = mm/60
	divu	#60,d4
	ext.l	d4
	move.w	d4,d0	; mm -= hh*60
	mulu	#60,d0
	sub.l	d0,d5
	move.l	d7,d6	; ss = xx/50
	divu	#50,d6
	ext.l	d6
	move.w	d6,d0	; xx -= ss*50
	mulu	#50,d0
	sub.l	d0,d7
	asl.l	#1,d7	; xx *= 2
#endasm
  PrintF("%s: %ld-%02ld:%02ld:%02ld.%02ld\n",what,dd,hh,mm,ss,xx);
}

#endif


#if DEBUG

dump_screen_plot()
{
  register short plong,lat,elong;
  register long index;
  register short *toplot, *toget;
  long num;
  short firstx, firsty;
  short plot_buf[(SEGMENTS+1)*2];
  long file,_Open();

  if (!(file = _Open("ram:lines.dat",1006L)))  return;

  toplot = plot_buf;

  toget = e2d;
  firstx = DONT_PLOT;
  if ((toplot[num=0] = toget[0]) != DONT_PLOT) {
    firstx = toplot[0];  firsty = toplot[1] = toget[1];  num=2;
  }
  for (elong=2; elong<SEGMENTS*2; elong+=2)
    if ((toplot[num] = toget[elong]) != DONT_PLOT) {
      toplot[++num] = toget[elong+1];  num++;
    } else {
      if (num >= 4) {
	plot_dump(file,"EQUATOR",0,0,elong/2,num,toplot);
      }
      num = 0;
    }
  if (num >= 2  &&  firstx != DONT_PLOT) {
    toplot[num++] = firstx;  toplot[num++] = firsty;
  }
  if (num >= 4) {
    plot_dump(file,"EQUATOR",0,0,elong/2,num,toplot);
  }

  toget = p2d;
  firstx = DONT_PLOT;
  if ((toplot[num=0] = toget[0]) != DONT_PLOT) {
    firstx = toplot[0];  firsty = toplot[1] = toget[1];  num=2;
  }
  for (plong=2; plong<SEGMENTS*2; plong+=2)
    if ((toplot[num] = toget[plong]) != DONT_PLOT) {
      toplot[++num] = toget[plong+1];  num++;
    } else {
      if (num >= 4) {
	plot_dump(file,"POLAR RING",plong/2,0,0,num,toplot);
      }
      num = 0;
    }
  if (num >= 2  &&  firstx != DONT_PLOT) {
    toplot[num++] = firstx;  toplot[num++] = firsty;
  }
  if (num >= 4) {
    plot_dump(file,"POLAR RING",plong/2,0,0,num,toplot);
  }

  toget = x2d;
  for (lat=0; lat<LATRNG; lat++)
    for (elong=0; elong<SEGMENTS; elong++) {
      firstx = DONT_PLOT;
      if ((toplot[num=0] = toget[index=INDX(0,lat,elong)*2]) != DONT_PLOT) {
	firstx = toplot[0];  firsty = toplot[1] = toget[index+1];  num=2;
      }
      for (plong=1; plong<SEGMENTS; plong++)
	if ((toplot[num]=toget[index=INDX(plong,lat,elong)*2])!=DONT_PLOT) {
	  toplot[++num] = toget[index+1];  num++;
	} else {
	  if (num >= 4) {
	    plot_dump(file,"CIRCUMPOLAR",plong,lat+1,elong,num,toplot);
	  }
	  num = 0;
	}
      if (num >= 2  &&  firstx != DONT_PLOT) {
	toplot[num++] = firstx;  toplot[num++] = firsty;
      }
      if (num >= 4) {
	plot_dump(file,"CIRCUMPOLAR",plong,lat+1,elong,num,toplot);
      }
    }

  for (lat=0; lat<LATRNG; lat++)
    for (plong=0; plong<SEGMENTS; plong++) {
      firstx = DONT_PLOT;
      if ((toplot[num=0] = toget[index=INDX(plong,lat,0)*2]) != DONT_PLOT) {
	firstx = toplot[0];  firsty = toplot[1] = toget[index+1];  num=2;
      }
      for (elong=1; elong<SEGMENTS; elong++)
	if ((toplot[num]=toget[index=INDX(plong,lat,elong)*2])!=DONT_PLOT) {
	  toplot[++num] = toget[index+1];  num++;
	} else {
	  if (num >= 4) {
	    plot_dump(file,"CIRCUMEQUATORIAL",plong,lat+1,elong,num,toplot);
	  }
	  num = 0;
	}
      if (num >= 2  &&  firstx != DONT_PLOT) {
	toplot[num++] = firstx;  toplot[num++] = firsty;
      }
      if (num >= 4) {
	plot_dump(file,"CIRCUMEQUATORIAL",plong,lat+1,elong,num,toplot);
      }
    }

  for (elong=0; elong<SEGMENTS; elong++)
    for (plong=0; plong<SEGMENTS; plong++) {
      num = 0;
      if (!(elong&1))
	if ((toplot[0] = e2d[plong*2]) != DONT_PLOT) {
	  toplot[1] = e2d[plong*2+1];  num=2;
	}
      for (lat=0; lat<LATRNG; lat++)
	if ((toplot[num]=toget[index=INDX(plong,lat,elong)*2])!=DONT_PLOT) {
	  toplot[++num] = toget[index+1];  num++;
	} else {
	  if (num >= 4) {
	    plot_dump(file,"LONGITUDE LINES",plong,lat+1,elong,num,toplot);
	  }
	  num = 0;
	}
      if (!(plong&1))
	if ((toplot[num] = p2d[elong*2]) != DONT_PLOT) {
	  toplot[++num] = p2d[elong*2+1];  num++;
	}
      if (num >= 4) {
	plot_dump(file,"LONGITUDE LINES",plong,lat+1,elong,num,toplot);
      }
    }

  _Close(file);
}


plot_dump(file,label,plong,lat,elong,num,toplot)
long file;
char *label;
short plong,lat,elong;
long num;
short *toplot;
{
  long i;

  FPrintF(file,"%s\nplong = %d  lat = %d  elong = %d  num = %ld\n",
	label,plong*INTERVAL,lat*INTERVAL,elong*INTERVAL,num);
  for (i=0; i < num; i+=2)
    FPrintF(file,"%5d\t%5d\n",toplot[i],toplot[i+1]);
  FPrintF(file,"\n");
}


dump_coordinates(all)
int all;
{
  short plong,lat,elong;
  long file, _Open();

  if (!(file = _Open("ram:coord.dat",1006L)))  return;

  for (plong=0; plong<SEGMENTS; plong++)
    for (lat=0; lat<LATRNG; lat++)
      for (elong=0; elong<SEGMENTS; elong++)
	if (all  ||  x2d[INDX(plong,lat,elong)*2+X] != DONT_PLOT)
	  FPrintF(file,
	   "x[%3d][%2d][%3d] = (%6d,%6d,%6d,%6d)   (%6d,%6d,%6d)   (%6d,%6d)\n",
		plong*INTERVAL, (lat+1)*INTERVAL, elong*INTERVAL,
		x4d[INDX(plong,lat,elong)*4+X], x4d[INDX(plong,lat,elong)*4+Y],
		x4d[INDX(plong,lat,elong)*4+Z], x4d[INDX(plong,lat,elong)*4+W],
		x3d[INDX(plong,lat,elong)*3+X], x3d[INDX(plong,lat,elong)*3+Y],
		x3d[INDX(plong,lat,elong)*3+Z],
		x2d[INDX(plong,lat,elong)*2+X], x2d[INDX(plong,lat,elong)*2+Y]);

  for (plong=0; plong<SEGMENTS; plong++)
    if (all  ||  p2d[plong*2+X] != DONT_PLOT)
      FPrintF(file, "p[%3d] = (%6d,%6d,%6d,%6d)   (%6d,%6d,%6d)   (%6d,%6d)\n",
	plong*INTERVAL,
	p4d[plong*4+X], p4d[plong*4+Y], p4d[plong*4+Z], p4d[plong*4+W],
	p3d[plong*3+X], p3d[plong*3+Y], p3d[plong*3+Z],
	p2d[plong*2+X], p2d[plong*2+Y]);

  for (elong=0; elong<SEGMENTS; elong++)
    if (all  ||  e2d[elong*2+X] != DONT_PLOT)
      FPrintF(file, "e[%3d] = (%6d,%6d,%6d,%6d)   (%6d,%6d,%6d)   (%6d,%6d)\n",
	elong*INTERVAL,
	e4d[elong*4+X], e4d[elong*4+Y], e4d[elong*4+Z], e4d[elong*4+W],
	e3d[elong*3+X], e3d[elong*3+Y], e3d[elong*3+Z],
	e2d[elong*2+X], e2d[elong*2+Y]);

  _Close(file);
}

#endif


#if TIMING | DEBUG

#asm
;   FUNCTION				  IMPLEMENTATION
; void PrintF(fmt_str,args...)          FPrintF(Output(),fmt_str,args...)
; void FPrintF(fh,fmt_str,args...)
;
; These functions have no return value, unlike the standard 'C' versions
;  that return the number of items matched in the format string.
;
; Note that PrintF and FPrintF use stack space for buffering the resulting
;  string.  The length of the resulting string is set up below to be limited
;  to < 256 characters.  If longer strings are needed, increase the buffer
;  size (ResBuf), but beware of stack overflow when doing so.

;Globals
	public	_SysBase,_DOSBase
	public	_LVORawDoFmt,_LVOWrite,_LVOOutput

ResBuf	equ	256

	public	_PrintF
_PrintF
	move.l	_DOSBase,a6	; get file handle of current output
	jsr	_LVOOutput(a6)  ;   using Output()
	move.l	(sp),-(sp)      ; duplicate return address
	move.l	d0,4(sp)        ; put file handle "under" it
				; fall into FPrintF

	public	_FPrintF
_FPrintF
	movem.l a2/a3,-(sp)     ; don't need to save registers d0-3,a0-1,a6

	move.l	16(sp),a0       ; format string
	lea.l	20(sp),a1       ; data stream
	lea.l	put_char(pc),a2 ; output procedure called by RawDoFmt

	lea.l	-ResBuf(sp),sp  ; allocate some stack space where
	move.l	sp,a3		;   put_ch will put characters from RawDoFmt

	move.l	_SysBase,a6	; do the magic
	jsr	_LVORawDoFmt(a6) ;  using RawDoFmt()

	move.l	ResBuf+12(sp),d1 ; output file handle
	move.l	sp,d2		; the result string is on the stack
	moveq	#0,d3
	move.w	str_len,d3	; string length
	beq.s	1$		; ignore a zero length string

	move.l	_DOSBase,a6	; output the results
	jsr	_LVOWrite(a6)   ;   using Write()

1$	move.w	#-1,str_len	; reset for next time
	lea.l	ResBuf(sp),sp   ; release stack space
	movem.l (sp)+,a2/a3     ; restore registers
	rts			; all done


	; length of the output string minus the terminating null
	dseg
	public str_len
str_len dc.w -1
	cseg

	far data
put_char
	addq.w	#1,str_len	; add 1 to string length
	near data
	move.b	d0,(a3)+        ; this "procedure" stuffs the characters from
	rts			;  RawDoFmt into the destination buffer

#endasm

#endif
