[net.sources.mac] source for new and VASTLY improved earthplot

mkg@lzaz.UUCP (Marsh Gosnell) (12/21/85)

Here's a new version of earthplot.  For those who missed it, it is
a program that plots an outline of the earth given the latitute,
longitude, and altitude.  I really liked the program but had problems
with it's performance (5-6 minutes to draw the earth).

The new version is vastly improved.  The plot is completed in about
35 seconds.  How did I do it?  I got rid of the unnecessary use of
floating point and substituted integer arithmetic with implied decimal
places (1.0 is represented as 10000).  Accuracy is good enough so that
out of 7000 data points, only 400 are off by no more than one pixel.
I also incorporated the data file into a code segment so the disk i/o
involved in reading the data is also eliminated.

A binhex'ed version of the new program is posted as a separate article
so you won't have to compile this to enjoy it.

Enjoy!!
  Marsh Gosnell   ihnp4!lzma!mkg


/**
 ** EarthPlot -- a program to draw the earth as viewed from space.
 **
 ** Written by:	Michael Peirce
 **		1258 Manet Drive
 **		Sunnyvale, CA 94087
 **
 ** Significant performance improvements by:
 **		 Marsh Gosnell
 **		 35 Godfrey Road
 **		 Montclair, NJ  07043
 **
 **	...in Megamax C (version 2.1)   w/ the SimpleTools package
 **
 ** Based on Microplot program written in Fortran by Richard Heurtley
 ** (This can be found in file B900:MICROPLOT.FTN on the MTS system 
 **  at Rensselear Polytechnic Institute in Troy, New York)
 **
 ** Who	When			What
 ** ===	===========		====================================
 ** mrp	01-dec-1985		Final work done for version 1.0
 ** mkg 20-dec-1985		Performance improvements
 **/

/**
 ** The performance improvments are:
 **
 ** - use of integer arithmetic where possible.  All values are small
 **   enough so that using integer arithmetic with 4 implied decimal
 **   places (e.g., multiplying by 10000) yields sufficient accuracy.
 **   (Out of 7000 displayed points, 400 are off by 1 pixel).
 **
 ** - data is kept in a separate code segment.  avoids disk i/o
 **
 ** - new lmul routine.  The Megamax lmul routine always does things the
 **   hard way even if the two long values are shorts.  The new lmul will
 **   lmul will run really fast if the two values are shorts (which they
 **   usually are in this case).
 **/

/**		
 ** Because Earthplot is in such a state (and because I did it 
 ** primarily as a learning experience) both source and executable
 ** are being placed into the public domain.  I encourage anyone to
 ** pick up on this start and expand on it's theme.  All I ask is
 ** that if anyone does improve on this program them send the
 ** results back to me (either via the net or other means).
 **
 ** The following is a list of possible extensions:
 **
 ** - support cut & paste to the clipboard
 **
 ** - support resizing the drawing window to allow various sizes
 **   of earth plots
 **
 ** - save plots directly into a file (a save option)
 **
 ** - either a scripting facility or a batch runable version so
 **   that a large number of plots can be generated without human
 **   interaction.  This would allow one to either feed these plots
 **   into something like Videoworks to create a "real-time" rotation
 **   of the earth or a special "player" program that would do this.
 **   (Note: this has already been done for my roommate's IBM-PC clone.
 **   Come on MacFolks, we can do it better than THEM).
 **
 ** - compress the data (from ASCII into binary data)
 **
 ** - put the data into (a) the data fork of the executable or (b) a
 **   a resource (chunked to work on 128k machines?)
 **
 ** - improve on hidden line and/or ploting algorithm...
 **
 ** - add a floating point chip to the Mac!!!!!
 **/

#include <qd.h>
#include <font.h>
#include <qdvars.h>
#include <misc.h>
#include <win.h>
#include <string.h>
#include <stdio.h>
#include <fmath.h>
#include <control.h>
#include <event.h>
#include <res.h>

/*
 *	Defines for simpletools	
 */

#define itemdisable 0L
#define itemenable  1L
#define itemcheck   2L
#define itemuncheck 3L

#define watchcursor 4
#define inbutton 10
#define inupbutton 20
#define indownbutton 21
#define inpageup 22
#define inpagedown 23
#define inthumb 129

/*
 *	Define the coords for all my rectangles here
 */

#define info_1 12
#define info_2 25
#define info_3 175
#define info_4 329

#define icon_1 10
#define icon_2 15
#define icon_3 42
#define icon_4 47

#define north_1 5
#define north_2 104
#define north_3 74
#define north_4 124

#define south_1 75
#define south_2 104
#define south_3 145
#define south_4 124

#define east_1 5
#define east_2 184
#define east_3 74
#define east_4 204

#define west_1 75
#define west_2 184
#define west_3 145
#define west_4 204

#define mi_1 5
#define mi_2 264
#define mi_3 65
#define mi_4 284

#define km_1 66
#define km_2 264
#define km_3 175
#define km_4 284

#define lat_sb_1 -1
#define lat_sb_2 128
#define lat_sb_3 164
#define lat_sb_4 144

#define lon_sb_1 -1
#define lon_sb_2 208
#define lon_sb_3 164
#define lon_sb_4 224

#define alt_sb_1 -1
#define alt_sb_2 289
#define alt_sb_3 164
#define alt_sb_4 305

#define earth_1 195
#define earth_2 25
#define earth_3 499
#define earth_4 329

#define alt_scale 1000

/*
 *	The resources I use
 */

#define icon_id 512

/*
 *	Some usefull constants
 */

#define diam  7926.0
#define pi    3.141592654
#define pi100 0.03141592654	/* pi / 100 */
#define conv  0.017453293	/* pi / 180 */

#define xsize 300	/* size of window */
#define ysize 300
#define half_xsize 150
#define half_ysize 150
#define fudge 10000
/* real fudge so we can shift to divide rather than use expensive ldiv */
#define fudge2 20078

#define bitmap_size xsize * ysize / 8

extern char	applestring[]; /* defined in simpletools */
extern int	wprocid;
extern windowptr windowpointer();

double	alatd, alond, height;

double	cos1, cos2, sin1, sin2, xpos, xmax, scaler;
long	icos1, icos2, isin1, isin2, ixmax, ixpos, iscaler;
double	alat, alon;

int	lat, lon, alt;
int	latnlong;

curshandle	watch_cursor;

rect		erect;
rect		lat_data, lon_data, alt_data;
rect		icon_rect;

handle		icon_handle;

controlhandle	alt_sb;
controlhandle	lat_sb;
controlhandle	lon_sb;
controlhandle	north_check;
controlhandle	south_check;
controlhandle	east_check;
controlhandle	west_check;
controlhandle	mi_check;
controlhandle	km_check;

extern unsigned char	getch();   /* get next data point from data segment */

windowptr	wind;
int	storage[12000];
int	draw;	/* != 0 if we want to draw the line.  ==0 if move to point */
int	over;	/* != 0 when point is outside visible area */
bitmap	saved_bitmap;
char	controls[] = "Controls";
char	earthwin[] = "Earth";

/*
 *	beep - make a noise!
 */

beep ()
{
	sysbeep (5); /* useful in debugging */
}



leave() 			/* Attached to the Quit menu */
{
	exit(0);
}



nop() 
{
}


xxx(xabs, yabs, zabs)	/* plot a line or move current position */
long	xabs, yabs, zabs;
{
	int	x, y;
	long	ifactor, ixtmp, ixrel, iyrel, izrel;

	/*
	 * draw line if we want to draw (as opposed to move)
	 * and the previous point was drawn too.
	 */
	draw = draw && !over;

	ixtmp = ((xabs * icos2) + (yabs * isin2)) / fudge;
	ixrel = ((ixtmp * icos1) + (zabs * isin1)) / fudge;

	over = (ixrel < ixmax);

	if (over) 
		return;

	iyrel = ((yabs * icos2) - (xabs * isin2)) / fudge;
	izrel = ((zabs * icos1) - (ixtmp * isin1)) / fudge;

	ifactor = ixpos * iscaler / (ixpos - ixrel);

 /* you'll want this if the earth window size becomes variable
  *	x = ((((iyrel * ifactor) / fudge) + fudge) * half_xsize) / fudge;
  *	y = ((((-izrel * ifactor) / fudge) + fudge) * half_ysize) / fudge;
  *
  * another hack to get rid of two ldiv's.
  * approximation is close enough
  */
	x = ((((iyrel * ifactor) / fudge) + fudge) * 123) >> 13;
	y = ((((-izrel * ifactor) / fudge) + fudge) * 123) >> 13;

	if (draw) 
		lineto (x, y);
	else 
		moveto (x, y);
}



/*
 *	brag - put up our ABOUT BOX
 */

brag ()
{

	char	mess[255];		/* string to form a message */

	strcpy (mess, "\265EarthPlot -- Version 2.0 -- December 1985\r");
	strcat (mess, "Public domain program by Michael Peirce\r");
	strcat (mess, "Version 2 improvements by Marsh Gosnell");
	if (message (mess))
		nop ();
}


sorry ()
{

	char	mess[255];		/* string to form a message */

	strcpy (mess, "You'll have to use shift-option-3.");
	if (message (mess))
		nop ();
}


/*
 *	eraseearth - erase earth window
 */
eraseearth ()
{
	withwindow(earthwin);
	fillrect(&erect, qdvars.qdwhite);
	frameoval(&erect);
}



/*
 *	drawearth - actually draw the picture
 */
drawearth ()
{

	int	temp, i, j, first;
	eventrecord theevent;
	double	coslat, coslon, sinlon, x, y, z;
	long	ix, iy, iz;

	setcursor(*watch_cursor);

	eraseearth();

	alatd = getctlvalue(lat_sb);
	alond = getctlvalue(lon_sb);
	height = getctlvalue(alt_sb) * (long)alt_scale;
	if (getctlvalue(mi_check) == 0)
		height = height * 1.61;

	alat = alatd * conv;
	alon = alond * conv;

	cos1 = cos(alat);
	cos2 = cos(alon);
	sin1 = sin(alat);
	sin2 = sin(alon);

	if (getctlvalue(south_check) == 1) 
		sin1 = -sin1;
	if (getctlvalue(west_check)  == 1) 
		sin2 = -sin2;

	xpos = (height + diam) / diam;
	xmax = 1.0 / xpos;
	scaler = sqrt (1.0 - (xmax * xmax));

	icos1 = (long)(cos1 * fudge);
	icos2 = (long)(cos2 * fudge);
	isin1 = (long)(sin1 * fudge);
	isin2 = (long)(sin2 * fudge);
	ixmax = (long)(xmax * fudge);
	ixpos = (long)(xpos * fudge);
	iscaler = (long)(scaler * fudge);

	moveto((int)xsize, (int)half_ysize);

	if (latnlong) {
		for (j = 1; j < 11; j++) {

			alat   = (double)((j - 6) * 15) * conv;
			coslat = cos(alat) * fudge;
			iz = (long)(sin(alat) * fudge);
			draw = FALSE;
			over = FALSE;
			for (i = 0; i < 204; i += 2) {

				alon = (double)(i) * pi100;
				ix = (long)(coslat * cos(alon));
				iy = (long)(coslat * sin(alon));

				xxx(ix, iy, iz);

				draw = TRUE;

			}
			if (getnextevent(everyevent, &theevent) != 0)
				if (theevent.what == mousedown) 
					goto abort_drawing;
		}

		for (j = 0; j < 24; j++) {

			alon   = (double)((j * 15)) * conv;
			coslon = cos(alon) * fudge;
			sinlon = sin(alon) * fudge;
			draw = FALSE;
			over = FALSE;
			for (i = 0; i < 104; i += 2) {

				alat = ((double)(i) / 100.0 - 0.5) * pi;
				coslat = cos(alat);
				ix = (long)(coslat * coslon);
				iy = (long)(coslat * sinlon);
				iz = (long)(sin(alat) * fudge);

				xxx(ix, iy, iz);

				draw = TRUE;

			}
			if (getnextevent(everyevent, &theevent) != 0)
				if (theevent.what == mousedown) 
					goto abort_drawing;
		}
	}

	getch(1);	/* reset data pointer */
	over = FALSE;
	while ((ix = getch(0)) != 0xff) {
		iy = getch(0);
		iz = getch(0);
		draw = ix || iy || iz;
		if (!draw) {
			ix = getch(0);
			iy = getch(0);
			iz = getch(0);
		}
		/* x>>8 == x/265 */
		ix = ((ix * fudge2) >> 8) - fudge;
		iy = ((iy * fudge2) >> 8) - fudge;
		iz = ((iz * fudge2) >> 8) - fudge;

		xxx(ix, iy, iz);
	}

abort_drawing:

	initcursor();

	wind = windowpoint(earthwin);
	copybits(&(wind->portbits), &(saved_bitmap),
	    &erect, &erect, srccopy, NULL);

}


/*
 *	dolines - toggles lat & long lines
 */

dolines()
{
	latnlong = !latnlong;

	if (latnlong)
		menu("\265Plot", "Grid on/G", itemcheck);
	else
		menu("\265Plot", "Grid on/G", itemuncheck);
}


/*
 *	restore_earth - keeps earth drawn
 */

restore_earth()
{
	wind = windowpoint(earthwin);

	copybits(&(saved_bitmap), &(wind->portbits),
	    &erect, &erect, srccopy, NULL);
}


copy_earth()
{
	/*
long		temp;
pichandle	pic;
restype		pict_res;
rect		xrect;

	pic = openpicture(&erect);

	hlock(pic);
	
	restore_earth();

	closepicture();

	pict_res[0] = 'P';	pict_res[1] = 'I';
	pict_res[2] = 'C';	pict_res[3] = 'T';

	zeroscrap();

	temp = gethandlesize(pic);
	temp = putscrap(temp,&pict_res,*pic);
	killpicture(pic);beep();
*/
}


TextUpdate()
{
	windowptr wind;

	wind = windowpoint(controls);
	drawcontrols(wind);
	textface(9);
	moveto(60, 36);
	textface(0);
	printf("\265EarthPlot V2\n");
	moveto(9, 92);
	printf("Latitude:\n");
	moveto(9, 172);
	printf("Longitude:\n");
	moveto(9, 252);
	printf("Altitude:\n");
}


DataUpdate()
{
	windowptr wind;

	wind = windowpoint(controls);

	fillrect(&lat_data, white);
	moveto(75, 92);
	printf("%d\241\n", getctlvalue(lat_sb));
	fillrect(&lon_data, white);
	moveto(82, 172);
	printf("%d\241\n", getctlvalue(lon_sb));
	fillrect(&alt_data, white);
	moveto(73, 252);
	printf("%ld\n", getctlvalue(alt_sb) * (long)alt_scale);
}


doupdate()
{
	windowptr wind;

	wind = windowpoint(controls);

	ploticon(&icon_rect, icon_handle);
	TextUpdate();
	DataUpdate();
	drawcontrols(wind);
	moveto(0, 61);
	lineto(175, 61);
	moveto(0, 64);
	lineto(175, 64);
}


pascal track(control, partcode)
controlhandle	control;
int	partcode;
{
	int	i, step;

	if (partcode == 0) 
		return;

	switch (partcode) {
	case inupbutton:
		step = -1;
		break;
	case indownbutton:
		step =  1;
		break;
	case inpageup:
		step = -10;
		break;
	case inpagedown:
		step =  10;
		break;
	default:
		return;
	}

	i = getctlvalue(control) + step;
	if (*control == *lat_sb) {
		if (i > 90) 
			i = 90;
		if (i <  0) 
			i =  0;
	}
	if (*control == *lon_sb) {
		if (i > 180) 
			i = 180;
		if (i <   0) 
			i =   0;
	}
	if (*control == *alt_sb) {
		if (i > 160) 
			i = 1600;
		if (i <    0) 
			i =    0;
	}
	setctlvalue(control, i);
	DataUpdate();
}


doincontent(x, y, thewindow, theevent)
int	x, y;
windowptr thewindow;
eventrecord *theevent;
{
	controlhandle	thecontrol;
	int	partcode;

	globaltolocal(&theevent->where);

	partcode = findcontrol(&theevent->where, thewindow, &thecontrol);

	if (partcode) {
		switch (partcode) {
		case incheckbox:
			partcode = trackcontrol(thecontrol, &theevent->where, NULL);

			if (*thecontrol == *north_check) {
				setctlvalue(north_check, 1);
				setctlvalue(south_check, 0);
			}
			if (*thecontrol == *south_check) {
				setctlvalue(north_check, 0);
				setctlvalue(south_check, 1);
			}
			if (*thecontrol == *east_check) {
				setctlvalue(east_check, 1);
				setctlvalue(west_check, 0);
			}
			if (*thecontrol == *west_check) {
				setctlvalue(east_check, 0);
				setctlvalue(west_check, 1);
			}
			if (*thecontrol == *mi_check) {
				setctlvalue(mi_check, 1);
				setctlvalue(km_check, 0);
			}
			if (*thecontrol == *km_check) {
				setctlvalue(mi_check, 0);
				setctlvalue(km_check, 1);
			}

			break;
		case inupbutton:
		case indownbutton:
		case inpageup:
		case inpagedown:
			partcode = trackcontrol(thecontrol, &theevent->where, track);
			break;
		case inthumb:
			partcode = trackcontrol(thecontrol, &theevent->where, NULL);
			break;
		}
	}
	DataUpdate();
}


/*
 *	setup - do our initial housekeeping
 */

setup ()
{
	int	temp, i;
	rect	alt_rect, lat_rect, lon_rect, mi_rect, km_rect;
	rect	north_rect, south_rect, east_rect, west_rect;

	watch_cursor = getcursor(watchcursor);
	hlock(watch_cursor);

	latnlong = FALSE;

	setrect(&erect, -1, -1, (int)xsize + 1, (int)ysize + 1);

	menu(applestring, "About \265Plot", brag);
	menu(applestring, "About \265Plot", itemenable);
	menu("Edit", "Undo", nop);
	menu("Edit", "Undo", itemdisable);
	menu("Edit", "Cut/X", sorry);
	menu("Edit", "Cut/X", itemenable);
	menu("Edit", "Copy/C", nop);
	menu("Edit", "Copy/C", itemdisable);
	menu("Edit", "Paste/V", nop);
	menu("Edit", "Paste/V", itemdisable);
	menu("Edit", "Clear", eraseearth);
	menu("File", "Quit/Q", leave);
	menu("File", "Quit/Q", itemenable);
	menu("\265Plot", "Grid on/G", dolines); 
	menu("\265Plot", "Erase  Earth/B", eraseearth);
	menu("\265Plot", "Draw Earth/D", drawearth);

	temp = wprocid;
	wprocid = 3;
	window (earthwin, earth_1, earth_2, earth_3, earth_4,
	    nop, nop, restore_earth, nop);
	setorigin(-2, -2);
	wprocid = temp;

	temp = wprocid;
	wprocid = 3;
	window (controls, info_1, info_2, info_3, info_4,
	    nop, nop, doupdate, doincontent);
	wprocid = temp;

	wind = windowpoint(controls);

	saved_bitmap.baseaddr = (qdptr) & storage;
	saved_bitmap.rowbytes = wind->portbits.rowbytes;
	saved_bitmap.bounds   = wind->portbits.bounds;

	setrect(&lat_rect, lat_sb_1, lat_sb_2, lat_sb_3, lat_sb_4);
	setrect(&lon_rect, lon_sb_1, lon_sb_2, lon_sb_3, lon_sb_4);
	setrect(&alt_rect, alt_sb_1, alt_sb_2, alt_sb_3, alt_sb_4);

	setrect(&lat_data, 75, 72, 175, 92);
	setrect(&lon_data, 82, 152, 175, 172);
	setrect(&alt_data, 73, 232, 175, 252);

	setrect(&north_rect, north_1, north_2, north_3, north_4);
	setrect(&south_rect, south_1, south_2, south_3, south_4);
	setrect(&east_rect, east_1, east_2, east_3, east_4);
	setrect(&west_rect, west_1, west_2, west_3, west_4);
	setrect(&mi_rect, mi_1, mi_2, mi_3, mi_4);
	setrect(&km_rect, km_1, km_2, km_3, km_4);

	setrect(&icon_rect, icon_1, icon_2, icon_3, icon_4);

	lat = 0;
	lon = 0;
	alt = 160; /* 160 * 1000 = 160,000 */

	lat_sb = newcontrol(wind, &lat_rect, "", TRUE, lat, 0, 90, 16, (long)0);
	lon_sb = newcontrol(wind, &lon_rect, "", TRUE, lon, 0, 180, 16, (long)0);
	alt_sb = newcontrol(wind, &alt_rect, "", TRUE, alt, 1, 160, 16, (long)0);

	north_check = newcontrol(wind, &north_rect, "North", TRUE, 1, 0, 1, 2, (long)0);
	south_check = newcontrol(wind, &south_rect, "South", TRUE, 0, 0, 1, 2, (long)0);
	east_check  = newcontrol(wind, &east_rect, "East", TRUE, 1, 0, 1, 2, (long)0);
	west_check  = newcontrol(wind, &west_rect, "West", TRUE, 0, 0, 1, 2, (long)0);
	mi_check    = newcontrol(wind, &mi_rect,   "Miles", TRUE, 1, 0, 1, 2, (long)0);
	km_check    = newcontrol(wind, &km_rect,   "Kilometers", TRUE, 0, 0, 1, 2, (long)0);

	icon_handle = geticon(icon_id);

	textfont(0);
	eraseearth();
	wind = windowpoint(earthwin);
	copybits(&(wind->portbits), &(saved_bitmap),
	    &erect, &erect, srccopy, NULL);
	getch(1);	/* call here to get disk activity done up front */
}

main ()
{

	simpletools("About \265Plot");

	setup();

	for (; ; ) 
		simpleevents ();
}