/* main.c
   This is a quickie program to generate a mandelbrot set on a Retina
   screen in 256 colors. */

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

#ifdef _M68881
#include <m68881.h>
#endif

#include <exec/libraries.h>

#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>
#include <devices/timer.h>
#include <clib/alib_protos.h>
#include <time.h>

#include <pragmas/exec_pragmas.h>
#include <pragmas/intuition_pragmas.h>

#include "retina.h"
#include "retina_protos.h"
#include "retina_pragmas.h"

#define MAX_ORBITS 256

/* uncomment the following line to use my_RectFill */

#define MYRECTFILL 1

/* 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 */
/*
#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
};

static int max_orbits = MAX_ORBITS;

void Compute_Block(struct BrotPoint, struct BrotPoint);
short int __inline __regargs Test_Point(double, double);
#ifdef MYRECTFILL
void __inline __regargs my_RectFill(unsigned int, unsigned int,
				    unsigned int, unsigned int);
#endif

/* stack space must be BIG */
long __stack = 100000;

/* version string */
UBYTE vers[] = "$VER: RetinaMand V0.1 (18.7.93)";

extern struct IntuitionBase *IntuitionBase;
struct _xy_RetinaBase *RetinaBase;
struct DefaultScreenInfo screen_info;

int __oslibversion = 37;

double dr, di;
struct RetinaScreen *rScreen;

int main(int argc, char *argv[])
{
  register int i, rstep, gstep, bstep;
  register UBYTE foo, bar, baz;
  struct BrotPoint start, end;

  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);

	/* set up screen colors; color 0 must always be black */
	Retina_SetPalette(rScreen, 0, 0, 0, 0);
	srand48((long) time(NULL));
	foo = (lrand48() >> 3) % 256;
	bar = (lrand48() >> 4) % 256;
	baz = (lrand48() >> 3) % 256;
	rstep = bstep = (lrand48() >> 5) % 16;
	gstep = (lrand48() >> 5) % 10;
	for (i = 1; i < 256; i++) {
	  Retina_SetPalette(rScreen, i, foo, bar, baz);
	  foo += rstep;  foo %= 256;
	  bar += gstep;  bar %= 256;
	  baz += bstep;  baz %= 256;
	}
	Retina_SetDrMd(rScreen, RDM_JAM1);

	dr = (2.0 - (-2.0))/((double) rScreen->_rs_Width);
	di = (-1.5 - 1.5)/((double) rScreen->_rs_Height);

	/* OK, folks, enough initializing.  Let's call the
	   Mbrot routine. */
	start.x = 0; start.r = -2.0; start.y = 0; start.i = 1.5;
	end.x = rScreen->_rs_Width >> 1; end.r = 0.0;
	end.y = rScreen->_rs_Height >> 1; end.i = 0.0;
#ifndef NOREPEATCALC
	start.orbits = -2;
	end.orbits = -2;
#endif
	Compute_Block(start, end);
	start.y = end.y + 1; start.i = end.i + di;
	end.y = rScreen->_rs_Height - 1; end.i = -1.5;
	Compute_Block(start, end);
	start.x = end.x + 1; start.r = end.r + dr;
	end.x = rScreen->_rs_Width - 1; end.r = 2.0;
	Compute_Block(start, end);
	start.y = 0; start.i = 1.5;
	end.y = rScreen->_rs_Height >> 1; end.i = 0.0;
	Compute_Block(start, end);

	printf("Hit any key to quit.\n");
	getch();

	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");
  }
}

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);
    if (q > 0) Retina_SetPixel(rScreen, uleft.x, uleft.y, q);
#else
    if (uleft.orbits == -2)
      uleft.orbits = Test_Point(uleft.r, uleft.i);
    if (uleft.orbits > 0) Retina_SetPixel(rScreen, uleft.x, uleft.y,
					  uleft.orbits);
#endif
    q = Test_Point(uleft.r, lright.i);
    if (q > 0) Retina_SetPixel(rScreen, uleft.x, lright.y, q);
#ifdef NOREPEATCALC
    q = Test_Point(lright.r, lright.i);
    if (q > 0) Retina_SetPixel(rScreen, lright.x, lright.y, q);
#else
    if (lright.orbits == -2)
      lright.orbits = Test_Point(lright.r, lright.i);
    if (lright.orbits > 0) Retina_SetPixel(rScreen,
					   lright.x, lright.y,
					   lright.orbits);
#endif
    q = Test_Point(lright.r, uleft.i);
    if (q > 0) Retina_SetPixel(rScreen, lright.x, uleft.y, q);
    return;
  }

  /* draw a box -- if the borders are all the same, then fill it that
     color */
  q = Test_Point(uleft.r, uleft.i);  uleft.orbits = q;
  if (q > 0)
    Retina_SetPixel(rScreen, uleft.x, uleft.y, q);
  z = Test_Point(lright.r, lright.i);  lright.orbits = z;
  if (z > 0)
    Retina_SetPixel(rScreen, lright.x, lright.y, 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);
    if (z != q) {
      goto recurse;
    }
    else {
      if (z > 0)
	Retina_SetPixel(rScreen, i, uleft.y, z);
    }
    z = Test_Point(j, lright.i);
    if (z != q) {
      goto recurse;
    }
    else {
      if (z > 0)
	Retina_SetPixel(rScreen, i, lright.y, z);
    }
  }
  /* left and right */
  for (i = uleft.y, j = uleft.i; i <= lright.y; i++, j += di) {
    z = Test_Point(uleft.r, j);
    if (z != q) {
      goto recurse;
    }
    else {
      if (z > 0)
	Retina_SetPixel(rScreen, uleft.x, i, z);
    }
    z = Test_Point(lright.r, j);
    if (z != q) {
      goto recurse;
    }
    else {
      if (z > 0)
	Retina_SetPixel(rScreen, lright.x, i, z);
    }
  }

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

#ifdef MYRECTFILL
    my_RectFill(uleft.x, uleft.y, lright.x, lright.y);
#else
    /* fakin' it with line drawing */
    for (i = uleft.y; i <= lright.y; i++)
      Retina_Line(rScreen, uleft.x, i, lright.x, i);
#endif
  }
  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);
  }
}

#ifdef MYRECTFILL
void __inline __regargs 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, y1, tx2, y2);
    if (x2 - tx2 > 1) {
      for (v = tx2; v <= x2; v++)
	Retina_Line(rScreen, v, y1, v, y2);
    }
    if (tx1 - x1 > 1) {
      for (v = x1; v <= tx1; v++)
	Retina_Line(rScreen, v, y1, v, y2);
    }
  }
  else {
    /* fake it with line drawing */
    for (v = y1; v <= y2; v++) {
      Retina_Line(rScreen, x1, v, x2, v);
    }
  }
}
#endif

/* Test_Point() tests points in the C plane to determine
   whether they orbit stably or escape to infinity.
   Returns the escape velocity or -1 if the point is
   in the M-set. */

short int __inline __regargs Test_Point(double cx, double cy)
{
  double x = 0.0, y = 0.0, tx;
  register short int i;

  /* z = z^2 + c */
  for (i = 0; i < max_orbits; i++) {
    tx = x*x - y*y + cx;
    y = 2.0 * x * y + cy;
    x = tx;
    if (x*x + y*y > 4.0) {
      return(i);
    }
  }
  return(-1);
}
