/* 80 columns:
123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234567890
*/
/*
 * denbeste@world.std.com : Steven C. Den Beste
 *
 * "rm2.c": This is based on the program "retmand" which someone else posted
 * recently. I could not have done this without him, and I hereby announce
 * my gratitude. Nonetheless, I suspect that this is so dramatically modified
 * at this point that the original author wouldn't recognize it.
 *
 * The original program just drew the entire Mandelbrot drawing, using random
 * colors each time. That's all it did - but to do that it had to implement
 * the entire algorithm for CALCULATING it, and all the code
 * for displaying stuff on the Retina. Those are the parts I couldn't have
 * handled. In particular, it used the smart algorithm for calculating
 * the Mandelbrot diagram (the one which optimizes square sections) instead
 * of the stupid algorithm (the one which calculates each point every time).
 *
 * What I DO know how to do is enhance,
 * clean up, and optimize. But to do that I had to
 * perform some modifications. First and foremost: The original code was
 * written for the SAS compiler. What I've got is
 * Aztec C 5.0. Regrettably, they aren't compatible. For a while I tried
 * to maintain compatibility with #ifdef's, but that got so complex that
 * I finally gave up and deleted everything non-aztec, so this code is
 * thoroughly MANXified now. Adapting this back to SAS is probably going to be
 * a pain.
 *
 * The core of any Mandelbrot program is a routine which iterates a
 * rather strange function, waiting until the value of the function
 * exceeds a certain threshold. The value it returns is the integer
 * count of the number of loops it took for this to take place. All the
 * calculations are done in floating point. As a result, Mandelbrot programs
 * have a tendency to take enormous amounts of time unless handled well.
 * One finds implementations using fixed-point integers, but these
 * lead to strange results where the program has a "floor" of detail, for
 * instance. Using floating point prevents this flaw.
 *
 * The innermost loop of this function can iterate literally millions of
 * times depending on the area you are viewing and the dimensions of the
 * screen. Any tiny gains in this function can have significant results in
 * the total compute time.
 *
 * So I dug out my 68882 manual and got to work. It's in ASM now in a separate
 * module (another reason it's probably going to be hard to re-SASify). 
 * My code is optimized to
 * take advantage of the '882's capability to perform simultaneous operations.
 * (Don't worry - it works just fine for an '881 - it just takes longer.)
 * The big win is that there are 8 registers out there, and I can
 * store a lot of values permanently in the FPU - effectively,
 * they become "register double" variables.
 *
 * It is possible that  more juice can be squeezed out of this routine, and
 * even a microsecond or two can make a big difference.
 * I invite anyone who is into this sort of thing to try. I'm a programmer
 * by profession, and we tend to avoid ASM like the plague because
 * it is almost always too expensive to debug. (We use it for interrupt
 * routines, task switchers and not a hell of a lot else.)
 *
 * In the mean time I've added a bunch of features. There are now a bunch
 * of parameters you can specify on the command line, as follows:
 *
 * /r number	- specify the "r" (actually horizontal) value of the CENTER
 *		  of the screen. Negative is left. Values range from about
 *		  minus 2 to plus 2.
 *
 * /i number	- specify the "i" (actually vertical) value of the CENTER
 *		  of the screen. Negative is up. Values range from about
 *		  minus 1 to plus 1.
 *
 * /w number	- specify the width of the screen in "r" coordinate terms.
 *		  The vertical size is computed to maintain an aspect ratio
 *		  of 1.2 to 1.
 *
 * I made /r and /i specify the CENTER deliberately. If you can locate the
 * center of a feature, it is convenient to generate the frames
 * of a zoom movie, since all you have to do is change the "w" value. If
 * /r and /i specified the upper left corner, that would be a royal pain.
 * (The names "R" and "I" for these coordinates were established by Mandelbrot
 * himself, and are maintained here for historical reasons.)
 *
 * If you don't specify any of the above three parameters, you get the entire
 * Mandelbrot diagram.
 *
 * /c number	- Choose a color set. Frankly, right now I'm not very happy
 *		  with the ones I've chosen. If you don't specify anything,
 *		  you get a standard set I think works fairly well. If you
 *		  specify "/c 0" you get the random color set the old program
 *		  would have given you by default. The others are somewhat
 *		  obscure. There is plenty of opportunity here for enhancement.
 *
 * /q		  Use a quarter of the screen. Actually a misnomer - this
 *		  divides the screen linearly by 4, so it divides the area
 *		  by 16. Use this when you're trying to home in on /r and /i
 *		  values for interesting features to cut way down on the
 *		  compute time.
 *
 * /h		  Use a half (that is, 1/4 by area) of the screen.
 *
 * /t number	  Raise the top of the drawing. The default is 255. Values
 *		  above 255 will cause the color set to recycle. Values can
 *		  be specified up to 32767. Please note that depending on the
 *		  image you are computing, raising this value can radically
 *		  slow down compute time.
 *
 * /d number	  Often used in hand with "/t". This takes the output of the
 *		  function and divides it by the parameter for every pixel.
 *		  In essence it is lowering the color thresholds. For instance,
 *		  "/d 3" means that the first color threshold represents the
 *		  first THREE step-values, the second represents the
 *		  next THREE step-values, and so on. This does not alter
 *		  the time to compute the drawing, but it can alter its
 *		  image quite a bit.
 *
 * /s filename	  If this parameter is NOT present, then when the picture is
 *		  completely drawn it will stay on the screen until you hit
 *		  the "ENTER" key. If this parameter IS present, then when
 *		  the image is complete it will be written out as a 24-bit
 *		  PPM file, and then this program will automatically terminate.
 *		  This means if you want to create the frames for a zoom
 *		  movie, you can set up a batch file and let it run overnight,
 *		  or for a week, or for a month, or for ...
 *		  WARNING: These files are ENORMOUS. You can chew up disk space
 *		  really fast. The file takes 3 bytes per pixel. An
 *		  800*600 frame is 1.5 megabytes (sigh). You might want to make
 *		  your batch file alternate between creating files with this
 *		  program and crunching them with "lha", which can typically
 *		  collapse such a file to 100 Kb or less, depending on
 *		  image complexity. Alternatively, you can use "ppmtogif"
 *		  which won't do as well as "lha" but leaves them in a
 *		  displayable form. By definition these files always have
 *		  fewer than 256 colors, so "ppmtogif" won't ever tell you
 *		  to go use "ppmquant" first.
 *
 * /m threshold	  What happens if the Mandelbrot threshold of 4.0 is modified?
 *		  Beats heck out of me, but this parameter allows you to
 *		  experiment. (My early experimenting shows that values above
 *		  about 2.1 all act about the same, and below 1.9 the whole
 *		  image turns black.)
 *
 * To get you started, here are some interesting parameter values to try:
 *
 * /r -.93402 /i -.247638 /w .00004 /d 2 /t 512
 *
 * /r -.5554 /i -.5292 /w .006
 *
 * /r -1.2909 /i -.0861 /w .00155
 *
 * /r -.6763 /i -.36742 /w .0003
 *
 * /r -676204 /i -.367276 /w .000017
 *
 * /r -.6762039 /i -.3672763 /w .0000021 /d 2 /t 512    or
 * /r -.6762039 /i -.3672763 /w .0000021 /d 3 /t 768
 *
 * Even with all the optimization, these are not going to be fast drawings,
 * but the results are spectacular (in my own highly biased opinion). These
 * tended to take more than half an hour on my system at 800*600. Your
 * mileage may vary. (My system is a GVP G-force 68040 at 33 MHZ running
 * burst mode, so a 25 MHZ 68030 is probably going to run 4 or 5 times
 * as long.)
 *
 * If you find any interesting coordinates and feel like doing so, please send
 * them to me via email at the address at the top line. Bugs are likewise of
 * interest.
 *
 * Oh, I also added a hell of a lot of comments.
 * There ARE a few comments here which are not mine, but you'll
 * pick up on my style rapidly. Where there's a chance of confusion, I'll
 * include my initials: SCDB.
 *
 * ...and for my next trick: The one obvious need here is a simple way to pick
 * out the coordinate of some object, so as to center and zoom on that object.
 * The obvious way to do that is to use the mouse. Well, I can't. To do that
 * I have to turn on a Sprite, and the part of the Retina includes for that
 * uses a standard SAS include named "utility/tagitem.h", which Aztec doesn't
 * have. I can't find the equivalent structure ("struct TagItem") or define
 * ("TAG_USER") anywhere in the include structure I've got and I haven't got
 * the faintest idea of what they should be. If someone can tell me, I'll try
 * to put it in. In the mean time, the only way to home in on objects is to use
 * "/q" and to tweak the "/r" and "/i" values.
 */

#define UTILITY_TAGITEM_H  	     /* This disables some nested includes */
				     /* which Aztec doesn/t support. See above. */
#include <intuition/intuitionbase.h> /* Needed to define a major structure. */
				     /* SAS got it from one of the includes */
				     /* files that aztec didn't have, which */
				     /* I deleted. */


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

#include <exec/libraries.h>
#include <devices/timer.h>
#include <time.h>


#include "retina/retina.h"
#include "clib/retina_protos.h"
#include "pragmas/retina_pragmas.h"

#define MAX_ORBITS 256



/* uncomment the following line to avoid repeating calculations */
/* WARNING: For some reason this creates code that will cause
   stack overflow errors on any image larger than 320x400 or so */

/*
 * SCDB: As far as I can determine, this was a problem the original author
 * encountered early and never pursued. Uncommenting this causes compile
 * errors, as parts of the code which are NOT controlled by ifdefs try
 * to use structure members which ARE controlled by ifdefs on this symbol.
 * At this point, I consider the code controlled by this symbol to be dead,
 * but I invite anyone to try to fix it if they wish. The benefit is further
 * speed increases, at the expense of code complexity.
 */
/* #define NOREPEATCALC */

struct BrotPoint {
  unsigned int x, y;
  double r, i;
#ifndef NOREPEATCALC
  int orbits; /* -2 if untested, -1 if never escapes,
		 >= 0 otherwise */
#endif
};

/*
 * SCDB: "threshold" is traditionally "4.0" as defined by Mandelbrot.
 * However, I thought it might be fun to allow it to be modified, so I've
 * made it a variable here whose value can be changed on the command line.
 * It gets loaded by the ASM routine into fp1.
 */
double threshold = 4.0;

int max_orbits = MAX_ORBITS;
int step_size = 1;

int screen_width, screen_height;
int quarter_flag = FALSE;
int half_flag = FALSE;
int h_offset = 0, v_offset = 0;

void Compute_Block(struct BrotPoint, struct BrotPoint);
extern short int Test_Point(double, double);
void my_RectFill(unsigned int, unsigned int,
				    unsigned int, unsigned int);
/* version string */
UBYTE vers[] = "$VER: RetinaMand as modified by SCDB, Dec. 28, 1993";

struct IntuitionBase *IntuitionBase;

struct _xy_RetinaBase *RetinaBase;
struct DefaultScreenInfo screen_info;

/*
 * SCDB: I haven't the faintest idea what this does. It probably doesn't do
 * do anything under Aztec. Anyway, it seems to cause no harm, so I left it in.
 */
int __oslibversion = 37;

/*
 * SCDB: The code depends on the fact that the following number is divisible by
 * 3. If it ceases to be so, this program will crash horribly.
 * "out_buffer" is used to accumulate large blocks of data for writing out to
 * the PPM file if "/s" is used. It is a fact that programs work far more
 * efficiently when they write large blocks rarely than if they write small
 * blocks frequently.
 */
#define BUFF_SIZE 30000
unsigned char	out_buffer[BUFF_SIZE];

/*
 * SCDB: Later on, if we decide to save a file, we're going to have to know
 * what the palette was. The following array will store those values.
 */
unsigned long palette[256];


double dr, di;
struct RetinaScreen *rScreen;

int main(int argc, char *argv[])
{
  int i;
  double rstep, gstep, bstep;
  int c;
  UWORD x_scan, y_scan;
  long	color_value;

  /*
   * SCDB:
   *
   * "red_value" used to be named "foo".
   * "green_value" used to be named "bar".
   * "blue_value" used to be named "baz".
   *
   * Where I work this would get you hung without a trial.
   *
   * These also used to be UBYTEs, but you can get better palette results if
   * they are floating point numbers, since this allows you fractional steps
   * (which round when ultimately converted to RGB values).
   *
   * (I have to admit a bad habit, however: I tend to use "i", "j" and "k" for
   * arbitrary loop counters when they are used very locally. Depending on how
   * old you are, you may have heard of a language named "FORTRAN". Variables
   * whose names began with letters between "I" and "N" were integers, with
   * all other variables being floating point. It was routine to use i/j/k
   * for loop-control variables. However, I never use such names when
   * their scope is large. In this program I use "i", and I'm only using it
   * twice, both times to step through arrays: I use it to scan 'argv' for
   * parameters, and I use it to construct output buffers when writing PPMs.)
   */
  double red_value, green_value, blue_value;
  struct BrotPoint start, end;

  /*
   * The image created by this program used to be forced to the entire
   * diagram. Part of my redesign is to allow a subsection to be passed in
   * as invocation parameters, and to calculate and display just that part.
   * That required replacing a bunch of magic numbers with variables and
   * calculations in the initialization code.
   */
  double left_coord, top_coord, width, height;
  double i_center, r_center;

  FILE *save_file = NULL;

	/*
	 * If there are no parameters later for a subsection, use values which
	 * create the entire diagram. "height" is calculated to keep the aspect
	 * ratio of the screen we opened.
	 * These are not the values used in the original program, which
	 * discarded the first contour, which is a large circle.
	 */
	r_center = 0;
	i_center = 0;
	width = 6;

	/*
	 * These values are used if no color parameter is specified.
	 */
	red_value = 0;
	rstep = 4;
	green_value = 0;
	gstep = 2;
	blue_value = 0;
	bstep = 12;

	/*
	 * Now for something completely new: Invocation parameters!
	 * The parameter checking here is not quite as rigorous as it probably
	 * should be. Any switch using parameters only checks to see that there
	 * are enough entries in "argv" remaining to satisfy its needs. They
	 * do not check to see if they make sense. Let's assume some intelligence
	 * on the part of the users. There's prudence, and then there's
	 * paranoia.
	 */
	for (i=1; i<argc; i++) {
		if (strcmp(argv[i],"/c") == 0) {
			/*
			 * "/c n" predefined color sets, as follows:
			 */
			if (argc < i+1) {
				fprintf(stderr, "Not enough parameters for /c\n");
				exit(1);
			}
			c = atoi(argv[++i]);
			switch (c) {
			case 0:
				/*
`				 * 0: random values, sort of. This is a direct
				 * steal from the original program.
				 */
				srand((long) time(NULL));
				red_value = (rand() >> 3) & 0xFF;
				green_value = (rand() >> 4) & 0xFF;
				blue_value = (rand() >> 3) & 0xFF;
				rstep = ((rand() >> 5) & 0x0F) + 1;
				bstep = ((rand() >> 5) & 0x0F) + 1;
				gstep = ((rand() >> 5) & 0x0F) + 1;
				break;
			case 1:
				/*
				 * 1: Gray scale, 1 slope total over the course
				 * of 256 contours.
				 */
				red_value = 1;
				green_value = 1;
				blue_value = 1;
				rstep = 1;
				gstep = 1;
				bstep = 1;
				break;
			case 2:
				/*
				 * 2: Gray scale, 4 slopes over 256 contours.
				 */
				red_value = 4;
				green_value = 4;
				blue_value = 4;
				rstep = 4;
				gstep = 4;
				bstep = 4;
				break;
			case 3: /*
				 * 3: Gold. Properly used, this can
				 * yield a picture which looks as if it glows.
				 * This is why we use floating point values
				 * for color construction...
				 */
				red_value = 16;
				green_value = 16;
				blue_value = 16;
				rstep = 0.935;
				gstep = 0.935;
				bstep = 0.5;
				break;
			default:
				fprintf(stderr, "Unknown color set %d\n", c);
				exit(1);

			}
		} else if (strcmp(argv[i],"/s") == 0) {
			/*
			 * /s filename
			 * store the results into that filename as a
			 * ppm-format file. This can get BIG.
			 */
			if (argc < i+1) {
				fprintf(stderr, "Not enough parameters for /s\n");
				exit(1);
			}
			save_file = fopen(argv[++i], "w");
			if (save_file == NULL) {
				fprintf(stderr,
					"Couldn't open file <%s> for output\n",
					argv[i]);
				exit(1);
			}
		} else if (strcmp(argv[i],"/r") == 0) {
			/*
			 * /r CENTER of the R axis
			 */
			if (argc < i+1) {
				fprintf(stderr, "Not enough parameters for /r\n");
				exit(1);
			}
			r_center = atof(argv[++i]);
		} else if (strcmp(argv[i],"/w") == 0) {
			/*
			 * /w width
			 */
			if (argc < i+1) {
				fprintf(stderr, "Not enough parameters for /w\n");
				exit(1);
			}
			width = atof(argv[++i]);
		} else if (strcmp(argv[i],"/i") == 0) {
			/*
			 * /i CENTER of the I axis
			 */
			if (argc < i+1) {
				fprintf(stderr, "Not enough parameters for /i\n");
				exit(1);
			}
			/*
			 * In order to have the same coordinate system as
			 * "MandelVroom" this value has to be negated.
			 * This allows MandelVroom to be used as a sort of
			 * spotter-scope for this program.
			 *
			 * ...Which is a great theory, but for reasons I don't
			 * understand, there is some sort of shift between
			 * "MandelVroom" coordinates and those used here. It is
			 * entirely possible that this is due to a systemic error
			 * I've induced in my optimized computation function.
			 * Anyway, "MandelVroom" gets you near, and you can
			 * refine it from there using /q.
			 */
			i_center = -atof(argv[++i]);
		} else if (strcmp(argv[i],"/q") == 0) {
			/*
			 * /q - use only a quarter of the screen (per direction)
			 * which means a 16th of the area.
			 */
			quarter_flag = TRUE;
		} else if (strcmp(argv[i],"/h") == 0) {
			/*
			 * /h - use only a half of the screen (per direction)
			 * which means a quarter of the area.
			 */
			half_flag = TRUE;
		} else if (strcmp(argv[i],"/d") == 0) {
			/*
			 * /d - change the step-size
			 */
			if (argc < i+1) {
				fprintf(stderr, "Not enough parameters for /d\n");
				exit(1);
			}
			step_size = atol(argv[++i]);
			if (step_size > 126) {
				fprintf(stderr, "/d divider cannot exceed 126\n");
				exit(1);
			}
		} else if (strcmp(argv[i],"/t") == 0) {
			/*
			 * /t - Raise the ceiling.
			 */
			if (argc < i+1) {
				fprintf(stderr, "Not enough parameters for /t\n");
				exit(1);
			}
			max_orbits = atol(argv[++i]);
			if (max_orbits > 32767) {
				fprintf(stderr, "/t cannot exceed 32767\n");
				exit(1);
			}
		} else if (strcmp(argv[i],"/m") == 0) {
			/*
			 * /m threshold
			 * Traditionally the threshold is 4.0. But what if
			 * it isn't?
			 */
			if (argc < i+1) {
				fprintf(stderr, "Not enough parameters for /m\n");
				exit(1);
			}
			threshold = atof(argv[++i]);
		} else {
			fprintf(stderr, "Unknown parameter <%s>\n", argv[i]);
			exit(1);
		}
	}

	/*
	 * SCDB: Compute the height to maintain an aspect ratio of 1.2 to 1.
	 */
	height = width * 0.83;

	/*
	 * SCDB: Calculate the left and top coordinates based on center, width
	 * and height values.
	 */
	left_coord = r_center-width/2;
	top_coord = i_center+height/2;

  if ((RetinaBase = (struct _xy_RetinaBase *)
       OpenLibrary("retina.library", 2L)) != NULL) {
    if ((IntuitionBase = (struct IntuitionBase *)
	 OpenLibrary("intuition.library", 37L)) != NULL) {
      if ((rScreen = Retina_OpenScreen(RSCR_STDWIDTH, RSCR_STDHEIGHT,
				       MID_DEFAULT_08,
				       RSFF_AUTOADJUST, 0L))
	  != NULL) {
	printf("Screen Opened successfully: %d by %d\n",
	       rScreen->_rs_Width, rScreen->_rs_Height);

	/*
	 * SCDB: Well, ONE of the following flags has to have precedence. If the
	 * user is silly enough to specify both, we'll use the smaller one.
	 */
	if (quarter_flag) {
		screen_width  = rScreen->_rs_Width/4;
		h_offset      = (rScreen->_rs_Width*3)/8; /* to center it */
		screen_height = rScreen->_rs_Height/4;
		v_offset      = (rScreen->_rs_Height*3)/8; /* to center it */
	} else if (half_flag) {
		screen_width  =  rScreen->_rs_Width/2;
		h_offset      = rScreen->_rs_Width/4; /* to center it */
		screen_height =  rScreen->_rs_Height/2;
		v_offset      = rScreen->_rs_Height/4; /* etc. */
	} else {
		screen_width = rScreen->_rs_Width;
		screen_height = rScreen->_rs_Height;
	}


	/* set up screen colors; color 0 must always be black */
	Retina_SetPalette(rScreen, 0, 0, 0, 0);
	palette[0] = 0;
	for (i = 1; i < 256; i++) {
	  palette[i] = ((long)red_value<<16)+((long)green_value<<8)+(long)blue_value;
	  Retina_SetPalette(rScreen, i, red_value, green_value, blue_value);
	  red_value += rstep;
	  green_value += gstep;
	  blue_value += bstep;
	}

	Retina_SetDrMd(rScreen, RDM_JAM1);

	/*
	 * SCDB: I deduce that 'dr' is the the numeric increment in
	 * calculation-space represented by one pixel to the right, and
	 * 'di' is the numeric increment in calculation-sapce of one pixel down.
	 */
	dr = (width)/((double) screen_width);
	di = -(height)/((double) screen_height);

	/* OK, folks, enough initializing.  Let's call the
	   Mbrot routine. */

	/*
	 * SCDB:
	 * The screen is created in four quadrants. Create the upper left
	 * quadrant first.
	 * The parameters are:
	 *    "x" is the pixel position horizontally.
	 *    "y" is the pixel position vertically.
	 *    "r" is the value in calculation-space horizontally.
	 *		(0 to the left)
	 *    "i" is the value in calculation-space vertically.
	 *		(0 below, but we inverted the parameter so 0 is
	 *		 effectively above)
	 *
	 * The original code used incremental changes on the structures
	 * to set up these values, but in the interest of clarity
	 * I'm going to do them completely for each quadrant. The
	 * increase in execution time is miniscule.
	 *
	 * A long description of a change I made: I don't think that the
	 * original code handled this correctly. Both axes were handled the same
	 * way, so I'll describe only one of them for simplicity's sake. The
	 * original code described the first quadrant as 0-(width>>1) and the
	 * second as (width>>1)-width; which bothered me because
	 * different screen pixels were used for the last of the first
	 * quadrant and the first of the other quadrant, but they were computed
	 * using the same value. I modified this so that the first quadrant goes
	 * 0-((width>>1)-1) but I think I missed something in the logic.
	 * As I've observed it writing the screen, it 
	 * sometimes redundantly writes over pixels it has previously calculated
	 * BUT WITH DIFFERENT COLORS. There's no way that can be correct.
	 * The result is still esthetically pleasing and shows no important
	 * gaps or asymmetries, so I haven't pursued it. I don't understand the
	 * code which recursively divides squares below this point, and the
	 * problem lies in there somewhere.
	 * This is another invitation for change to anyone interested.
	 *
	 * First describe the pixel set of the quadrant
	 */
	start.x = 0;
	end.x = (screen_width >> 1)-1;
	start.y = 0;
	end.y = (screen_height >> 1)-1;

	/*
	 * Then derive the numeric set for the quadrant.
	 * This is the same for every quadrant. I've described it this way
	 * because it has negligible effect on execution time, but makes the
	 * code clearer.
	 */
	start.r = left_coord + dr*(double)start.x;
	end.r = left_coord + dr*(double)end.x;
	start.i = top_coord + di*(double)start.y;
	end.i = top_coord + di*(double)end.y;


#ifndef NOREPEATCALC
	start.orbits = -2;
	end.orbits = -2;
#endif
	Compute_Block(start, end);


	/*
	 * Create the lower left quadrant.
	 */
	start.x = 0;
	end.x = (screen_width >> 1)-1;
	start.y = (screen_height>>1);
	end.y = screen_height-1;

	start.r = left_coord + dr*(double)start.x;
	end.r = left_coord + dr*(double)end.x;
	start.i = top_coord + di*(double)start.y;
	end.i = top_coord + di*(double)end.y;

	Compute_Block(start, end);

	/*
	 * Create the lower right quadrant.
	 */
	start.x = (screen_width>>1);
	end.x = screen_width-1;
	start.y = (screen_height>>1);
	end.y = screen_height-1;

	start.r = left_coord + dr*(double)start.x;
	end.r = left_coord + dr*(double)end.x;
	start.i = top_coord + di*(double)start.y;
	end.i = top_coord + di*(double)end.y;

	Compute_Block(start, end);

	/*
	 * Create the upper right quadrant;
	 */
	start.x = (screen_width>>1);
	end.x = screen_width-1;
	start.y = 0;
	end.y = (screen_height >> 1)-1;

	start.r = left_coord + dr*(double)start.x;
	end.r = left_coord + dr*(double)end.x;
	start.i = top_coord + di*(double)start.y;
	end.i = top_coord + di*(double)end.y;

	Compute_Block(start, end);


	if (save_file != NULL) {
		/*
		 * Then the user wants the results saved to a PPM file.
		 * The file has already been opened, so the only thing we
		 * have to do is write it out.
		 *
		 * The first part of a PPM is "P6" to indicate that
		 * it IS a PPM, the x and y sizes and "255" to
		 * indicate full brightness level. This is then followed
		 * by byte triples (R,G,B) scanning x within y.
		 * It took me a while to realize that the placement of spaces
		 * and newlines here is critical for some programs which are
		 * picky.
		 */
		fprintf(save_file, "P6\n%d %d\n255\n",
			screen_width, screen_height);
		i = 0;
		for (y_scan=0; y_scan<screen_height; y_scan++) {
		    for (x_scan=0; x_scan<screen_width; x_scan++) {

			/*
			 * The documentation says that Retina_ReadPixel returns
			 * the RGB value. Actually for 8 bit screens it returns
			 * the palette index for that pixel.
			 * We therefore have to translate
			 * the palette index into RGB based on the values we
			 * saved when we set the palette up in the first place.
			 */
			color_value = Retina_ReadPixel(rScreen,
				x_scan+h_offset, y_scan+v_offset);
			color_value = palette[color_value];

			out_buffer[i++] = (unsigned char)
				((color_value >> 16) & 0xFFL);
			out_buffer[i++] = (unsigned char)
				((color_value >> 8) & 0xFFL);
			out_buffer[i++] = (unsigned char)(color_value & 0xFFL);

			/*
			 * The output has been accumulated in a big buffer
			 * because programs run faster if they write out a few
			 * big blocks than if they write out a lot of small
			 * ones, which is what would have happened if we'd
			 * been using "fwrite" on the color values directly.
			 */
			if (i>=BUFF_SIZE) {
				fwrite(out_buffer, i, 1, save_file);
				i = 0;
			}
		    }
		}

		/*
		 * We've put all values into the output buffer, and most of
		 * it has probably been written out. If there is any residue,
		 * write it out, too.
		 */
		if (i != 0)
			fwrite(out_buffer, i, 1, save_file);
		fclose(save_file);

	} else {

		/*
		 * If no output file was specified, leave the picture on the
		 * screen until the user indicates it should go away.
		 * For people (like me) who use retina workbench emulation,
		 * the following printf is useless - but
		 * there might be someone out there who is keeping
		 * their workbench on something else. The rest of us are just
		 * going to have to remember to hit CR afterwards.
		 *
		 * In theory, any keypress should work. In practice, "getchar"
		 * (at least for Aztec) stacks up all input until CR is pressed
		 * and only presents it to the program at that point.
		 */
		printf("Press -- ENTER -- to quit.\n"); 
		getchar();
	}

	Retina_CloseScreen(rScreen);
      }
      else {
	fprintf(stderr, "Unable to open Retina screen.\n");
      }
    }
    else {
      fprintf(stderr, "Can't open intuition.library V37+\n");
    }
  }
  else {
    fprintf(stderr, "Can not open retina.library!\n");
  }
  exit(0);
}

/*
 * SCDB: I do not understand the following code and have made no attempt
 * to debug or optimize it. Heare bee dragones.
 */

void Compute_Block(struct BrotPoint uleft, struct BrotPoint lright)
{
  unsigned int i;
  double j;
  short int q, z;

  if (lright.x - uleft.x < 2 && lright.y - uleft.y < 2) {
#ifdef NOREPEATCALC
    q = Test_Point(uleft.r, uleft.i)/step_size;
    if (q > 0) Retina_SetPixel(rScreen, uleft.x+h_offset, uleft.y+v_offset, q);
#else
    if (uleft.orbits == -2) {
      uleft.orbits = Test_Point(uleft.r, uleft.i)/step_size;

    }
    if (uleft.orbits > 0) Retina_SetPixel(rScreen, uleft.x+h_offset,
				uleft.y+v_offset, uleft.orbits);
#endif
    q = Test_Point(uleft.r, lright.i)/step_size;

    if (q > 0) Retina_SetPixel(rScreen, uleft.x+h_offset, lright.y+v_offset, q);
#ifdef NOREPEATCALC
    q = Test_Point(lright.r, lright.i)/step_size;
    if (q > 0) Retina_SetPixel(rScreen, lright.x+h_offset, lright.y+v_offset, q);
#else
    if (lright.orbits == -2) {
      lright.orbits = Test_Point(lright.r, lright.i)/step_size;
    }

    if (lright.orbits > 0) Retina_SetPixel(rScreen, lright.x+h_offset,
				lright.y+v_offset, lright.orbits);
#endif
    q = Test_Point(lright.r, uleft.i)/step_size;

    if (q > 0) Retina_SetPixel(rScreen, lright.x+h_offset, uleft.y+v_offset, q);
    return;
  }

  /* draw a box -- if the borders are all the same, then fill it that
     color */
  q = Test_Point(uleft.r, uleft.i)/step_size;

  uleft.orbits = q;
  if (q > 0)
    Retina_SetPixel(rScreen, uleft.x+h_offset, uleft.y+v_offset, q);
  z = Test_Point(lright.r, lright.i)/step_size;

  lright.orbits = z;
  if (z > 0)
    Retina_SetPixel(rScreen, lright.x+h_offset, lright.y+v_offset, z);
  if (z != q) {
    goto recurse;
  }

  /* top & bottom */
  for (i = uleft.x, j = uleft.r; i <= lright.x; i++, j += dr) {
    z = Test_Point(j, uleft.i)/step_size;

    if (z != q) {
      goto recurse;
    }
    else {
      if (z > 0)
	Retina_SetPixel(rScreen, i+h_offset, uleft.y+v_offset, z);
    }
    z = Test_Point(j, lright.i)/step_size;

    if (z != q) {
      goto recurse;
    }
    else {
      if (z > 0)
	Retina_SetPixel(rScreen, i+h_offset, lright.y+v_offset, z);
    }
  }
  /* left and right */
  for (i = uleft.y, j = uleft.i; i <= lright.y; i++, j += di) {
    z = Test_Point(uleft.r, j)/step_size;

    if (z != q) {
      goto recurse;
    }
    else {
      if (z > 0)
	Retina_SetPixel(rScreen, uleft.x+h_offset, i+v_offset, z);
    }
    z = Test_Point(lright.r, j)/step_size;

    if (z != q) {
      goto recurse;
    }
    else {
      if (z > 0)
	Retina_SetPixel(rScreen, lright.x+h_offset, i+v_offset, z);
    }
  }

  /* if we passed through all that, we draw a filled rectangle */
  if (q > 0) {
    Retina_SetAPen(rScreen, q);

    my_RectFill(uleft.x, uleft.y, lright.x, lright.y);
  }
  return;

 recurse:
  {
    struct BrotPoint top, bot, left, right, middle;

    top.x = bot.x = middle.x = (uleft.x + lright.x) >> 1;
    left.y = right.y = middle.y = (uleft.y + lright.y) >> 1;
    top.r = bot.r = middle.r = (uleft.r + lright.r)/2.0;
    left.i = right.i = middle.i = (uleft.i + lright.i)/2.0;
    top.y = uleft.y;
    top.i = uleft.i;
    bot.y = lright.y;
    bot.i = lright.i;
    left.x = uleft.x;
    left.r = uleft.r;
    right.x = lright.x;
    right.r = lright.r;
#ifndef NOREPEATCALC
    middle.orbits = top.orbits = bot.orbits = left.orbits =
      right.orbits = -2;
#endif

    Compute_Block(uleft, middle);
#ifdef NOREPEATCALC
    left.y += 1;
    left.i += di;
#endif
    Compute_Block(left, bot);
#ifdef NOREPEATCALC
    middle.x += 1;
    middle.y += 1;
    middle.r += dr;
    middle.i += di;
    top.x += 1;
    top.r += dr;
#endif
    Compute_Block(middle, lright);
    Compute_Block(top, right);
  }
}

/*
 * SCDB: The routine which follows used to be controlled by an ifdef. I've
 * made it unconditional, since it works very well.
 * "my" is the original author, not me. He did good.
 */
void my_RectFill(unsigned int x1, unsigned int y1,
				    unsigned int x2, unsigned int y2)
{
  short int tx1, tx2, v;

  /* a little magic because Retina_RectFill() ignores the low-order
     3 bits of the x-coords for 256-color screens */
  tx1 = (x1 >> 3) + 1; tx2 = (x2 >> 3) - 1;
  tx1 <<= 3;
  tx2 <<= 3;

  if (tx2 > tx1) {
    Retina_RectFill(rScreen, tx1+h_offset, y1+v_offset, tx2+h_offset, y2+v_offset);
    if (x2 - tx2 > 1) {
      for (v = tx2; v <= x2; v++)
	Retina_Line(rScreen, v+h_offset, y1+v_offset, v+h_offset, y2+v_offset);
    }
    if (tx1 - x1 > 1) {
      for (v = x1; v <= tx1; v++)
	Retina_Line(rScreen, v+h_offset, y1+v_offset, v+h_offset, y2+v_offset);
    }
  }
  else {
    /* fake it with line drawing */
    for (v = y1; v <= y2; v++) {
      Retina_Line(rScreen, x1+h_offset, v+v_offset, x2+h_offset, v+v_offset);
    }
  }
}

#ifdef UNUSED
/*
 * SCDB: Sorry folks, a bit of a debugging hook. I never got the hang of the
 * Aztec debugger, powerful though it is, since I've never really had the need.
 * My traditional way of debugging is to sprinkle printf's all through the code
 * to figure what the hell is happening. These are mechanisms which can
 * be called from the FPU routine to see what's happening there.
 * Unless further debugging is required there by someone using as primitive
 * of techniques as I used, they are unnecessary.
 */
int	trace_buff[32] = {-1,-1,-1,-1,-1,-1,-1,-1
			  -1,-1,-1,-1,-1,-1,-1,-1,
			  -1,-1,-1,-1,-1,-1,-1,-1,
			  -1,-1,-1,-1,-1,-1,-1,-1};

int	trace_index=0;

void trace(val)
int val;
{
	trace_buff[trace_index] = val;
	if (++trace_index >= 32)
		trace_index = 0;
}

void trace_dump()
{
	int	i;

	for (i=trace_index; i<32; i++)
		printf("%d: %d\n", i, trace_buff[i]);

	for (i=0; i<trace_index; i++)
		printf("%d: %d\n", i, trace_buff[i]);
}
#endif
