/******************************************************************************\
*                                                                              *
*     IFS Lab, Copyright (C) 1992 by N. Zeldes. All rights reserved.           *
*                                                                              *
*                      Version 1.0, April 9, 1992                              *
*                                                                              *
*  An interactive program for exploring the IFS Code method of Fractal Image   *
*  representation and reconstruction.                                          *
*                                                                              *
*  Requires the Fox/Dawson freeware req.library in libs: directory.            *
*                                                                              *
*  Compile under Aztec C 5.0, with -ff option (Fast floating point)            *
*   and -safmnpsu options (code optimization).                                 *
*  Link with +cd option (forces sprite and image data to load in CHIP RAM).    *
*  Link with IFF files (from CBM IFF Disk, Fish disk #185), and with the glue  *
*  module for the Fox/Dawson requester library, thus:                          *
*                                                                              *
*  cc  -ff -safmnpsu -i IFF -i req IFSLab.c                                    *
*  ln  +cd IFSLab.o IFF/putpict.o IFF/ilbmw.o IFF/packer.o                     *
*    IFF/iffw.o IFF/readpict.o IFF/ilbmr.o IFF/unpacker.o IFF/iffr.o           *
*    IFF/remalloc.o req/myreqglue.o  mf.lib c.lib                              *
*                                                                              *
\******************************************************************************/

#include <intuition/intuition.h>
#include <exec/memory.h> 
        /* exec/memory.h required to use AllocMem() */
#include <graphics/text.h>
        /* graphics/text.h required for font TextAttr structure */
#include <stdio.h>
#include <math.h>    /* Floating point math: sqrt, fabs, trig functions */
#include <errno.h>   /* Required by myatan2() */
#include <graphics/display.h> /* needed for definition of INTERLACE */
#include <stdlib.h>   /* stdlib.h needed for rand() */
#include <time.h>     /* time.h needed for time() */
#include <string.h>   /* needed for memset */

#include <ilbm.h>              /* Header file from IFF Disk (Note that   */
                               /* ilbm.h #includes compiler.h and iff.h) */
#include <readpict.h>          /* From IFF Disk */
#include <remalloc.h>          /* From IFF Disk; For ChipAlloc(), RemFree() */
#include <libraries/dos.h>     /* For IFF file I/O stuff */
#include <libraries/dosextens.h> /* For the Process structure definition. */
#include <reqbase.h>             /* Fox/Dawson file requester header file */
#include "IFSLab.h"   /* PowerWindows-generated structures and defines */

#define INTUITION_REV 31L
#define GRAPHICS_REV  31L

#define OUTLINE  0    /* parameters for SetMode() */
#define COLLAGE  1

#define FREEHAND 0    /* drawmode values */
#define LINES    1
#define ERASE    2
#define FILL     3
#define VECTOR   4

#define CODES      0  /* parameters for AboutText() */
#define IFS_THEORY 1
#define HELP1      2
#define HELP2      3
#define AUTHOR     4
#define DEMOTEXT   5

#define WIDTH   Window->Width
#define HEIGHT  Window->Height
#define GZZWIDTH   Window->GZZWidth
#define GZZHEIGHT  Window->GZZHeight

#define MAX_N 19      /* max permitted piece index */

struct Screen *Screen = NULL, *ImageScreen;
struct RastPort *scrp;     /* Pointer to Screen RastPort */
struct Window *Window = NULL, *NumWindow;
struct RastPort *r;  /* Pointer to RastPort of main editing window */
struct Window *ImageWindow;  /* Window for rendering Image */
struct RastPort *Textrp;   /* Rastport of Text Window */
struct IntuitionBase *IntuitionBase = NULL;
struct GfxBase *GfxBase = NULL;
void *MathBase = NULL;
void *MathTransBase = NULL;
struct ReqLib *ReqBase = NULL;     /* Fox/Dawson Requester Library */
int drawmode = FREEHAND;   
  /* Current mode - FREEHAND,LINES,ERASE,FILL (outline) or VECTOR (Collage) */
int exists_outline = 0, exists_image = 0, exists_numwindow = 0;
int renderwidth = 640, renderheight = 400; /* initialize to default */
int renderdepth = 1;  /* initialize to default */
long mx, my;  /* Current Mouse coordinates in editing */

struct pixel {
  long x;
  long y;
};
struct piece {
  double a, b, c, d, e, f;
  double s1, s2, r1, r2;
  double dens;
  double p;
  double det;
  UBYTE *piecemap;
  struct pixel boxo, boxx, boxy, boxz;
};
struct piece *pieceptr[MAX_N + 1]; /* Array of pointers to piece structures */
int N = -1;   /* Index of highest existing Piece Initialized to zero pieces */
int selpiece; /* Index of currently selected Piece */
char curcor; /* Currently dragged Box corner ('o','x','y','z') or none ('0') */
struct pixel ghboxo, ghboxx, ghboxy, ghboxz; /* Current corners of ghost Box */
struct pixel tmpboxo, tmpboxx, tmpboxy, tmpboxz;
struct piece *AllocPiece();
double myatan2();  /* My version of atan2() which is missing in Manx mf.lib */
char *fname, *GetFileName();
struct Library *OpenLibrary();
void *AllocMem(), *AllocRaster();
UBYTE *outlinebufptr=NULL; /* ptr to buffer holding outline in collage mode */
long i;      /* handy as a loop index */

/*** IFF error messages (external) */

char MsgOkay[] = { "(IFF_OKAY) A good IFF file" };
char MsgEndMark[] = { "(END_MARK) IFF Loader Error" };
char MsgDone[] = { "(IFF_DONE) Well Done!" };
char MsgDos[] = { "DOS Error - Aborting Load!" };
char MsgNot[] = { "Not an IFF file - Aborting Load!" };
char MsgNoFile[] = { "No such file found - Aborting Load!" };
char MsgClientError[] = { "(CLIENT_ERROR) IFF Checker bug"};
char MsgForm[] = { "(BAD_FORM) IFF Loader Error" };
char MsgShort[] = { "(SHORT_CHUNK) IFF Loader Error" };
char MsgBad[] = { "A mangled IFF file - Aborting Load!" };

/* MUST GET THESE IN RIGHT ORDER!!*/
char *IFFPMessages[-LAST_ERROR+1] = {
    /*IFF_OKAY*/  MsgOkay,
    /*END_MARK*/  MsgEndMark,
    /*IFF_DONE*/  MsgDone,
    /*DOS_ERROR*/ MsgDos,
    /*NOT_IFF*/   MsgNot,
    /*NO_FILE*/   MsgNoFile,
    /*CLIENT_ERROR*/ MsgClientError,
    /*BAD_FORM*/  MsgForm,
    /*SHORT_CHUNK*/  MsgShort,
    /*BAD_IFF*/   MsgBad
    };

struct FileRequester MyFileReqStruct;    /* File Requester stuff */
char filename[FCHARS+1];
char directoryname[DSIZE+1];
char pathname[FCHARS+DSIZE+2+4];  /* +4 to permit space for appending '.ifs' */

/*==========================================================================*/

main()
{
  struct Message * GetMsg();
  void ReplyMsg();
  struct IntuiMessage *message;
  ULONG class;
  USHORT code, MenuNumber, menuid, itemid, subitemid;
  struct MenuItem *ItemAddress(), *ItemAddr;
  long labs();
  int w;  /* temporary int variable */
  int hitgadget;  /* GadgetID # of last gadget hit by mouse */
  struct Gadget *hitgadstruct; /* Gadget Structure of last hit gadget */
  int numgadgpending;  /* GadgetID of unserviced coeff gadget, or -1 if none */
  struct piece tmppiece;  /* will be used to hold temporary trans' coeffs */
  long mx0,my0, mx1,my1;  /* coords of endpoints of ghost line in Lines mode */
  int pendown = 0;
             /* 0 if mouse SELECT button is currently pressed in window */
  SHORT WindowBdrArray[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
             /* will be filled later to corners of inner GZZ Window */
  struct Gadget *gad;  /* used as a temporary variable */

  /**** Initialize stuff, set up edit window in custom screen etc. ****/

  OpenLibraries();
  OpenDisplay(); /* Open Outline Sketch  window in custom screen */

  SetAPen(r, 2L);
  WindowBdrArray[2] = WindowBdrArray[4] = GZZWIDTH - 1;
  WindowBdrArray[5] = WindowBdrArray[7] = GZZHEIGHT - 1;

  /*** Allocate memory for Outline buffer bitmap */
  if ((outlinebufptr = 
         (UBYTE *)AllocMem((long)RASSIZE(WIDTH,HEIGHT), MEMF_CHIP)) == NULL) {
    ShowError("Can't allocate Outline Buffer Memory!");
    CloseAll();
  }
       /**** Main Loop ****/
 
  while(1) {

      mx = (long)Window->GZZMouseX;
      my = (long)Window->GZZMouseY;

    if (pendown == 1) {

     /*** Do Mouse editing to board per drawmode and mouse coords */
      switch (drawmode) {

      case FREEHAND:
        Draw(r, mx, my); 
        break;

      case LINES:
        SetDrMd(r, JAM1|COMPLEMENT);  /* COMPLEMENT used for ghost line */
        Move(r, mx0, my0);
        Draw(r, mx1,my1);   /* Undraw previous ghost line */
        Move(r, mx0, my0);
        Draw(r, mx1 = mx, my1 = my);  /* Draw a new ghost line */
        SetDrMd(r, JAM1);
        break;

      case ERASE:
        RectFill(r, mx - 2, my - 2, mx + 2, my + 2);
        break;

      case FILL:
        break;

      case VECTOR:    /* Change the Ghost Vector Box */
        if (exists_numwindow || curcor == '0')
          break;

        /* Check for attempt to size box to a point */
        if ( (mx==pieceptr[selpiece]->boxo.x && my==pieceptr[selpiece]->boxo.y)
         &&  ( (curcor == 'z') || 
               (curcor == 'x' && 
                pieceptr[selpiece]->boxy.x == pieceptr[selpiece]->boxo.x &&
                pieceptr[selpiece]->boxy.y == pieceptr[selpiece]->boxo.y) ||
               (curcor == 'y' && 
                pieceptr[selpiece]->boxx.x == pieceptr[selpiece]->boxo.x &&
                pieceptr[selpiece]->boxx.y == pieceptr[selpiece]->boxo.y)
              )
        )
          mx = (mx == 0) ? 1 : mx - 1; /* If attempted, tweak mx to avoid */

        ComputeNewBox();       /* Compute New Ghost Box from mouse coords */
        ToggleGhostBox();      /* Undraw old ghost box */
        ghboxo = tmpboxo;      /* Update ghost box variables */
        ghboxx = tmpboxx;
        ghboxy = tmpboxy;
        ghboxz = tmpboxz;
        ToggleGhostBox();      /* Draw new ghost box */
        break;
      }    /* End Switch */
    }    /* end if pendown == 1 */

    /* Get an IDCMP event, if any, from Window */
    if (message = (struct IntuiMessage *)GetMsg(Window->UserPort)) { 
      class = message->Class;
      code = message->Code;
      ReplyMsg(message);
    }
    else  /* no message */
      class = code = NULL; /* so event routines below will be skipped */

    /*** Go case by case over possible events from port */

    if (class == MOUSEBUTTONS) {    /*** Respond to user mousebutton clicks */
      if (code == SELECTDOWN) {
        pendown = 1;
        if (drawmode != ERASE)
          exists_outline = 1;
        switch (drawmode) {

        case FREEHAND:
        case ERASE:
          Move(r, mx, my);
          break;

        case LINES:
          mx0 = mx1 = mx; /* Attach both ends of line to pixel */
          my0 = my1 = my;
          break;

        case FILL:
          ModifyMousePtr(Window, 1);   /* Bring up 'ZZ' pointer */
          SetAPen(r, 2L);
          Move(r, 0L, 0L);
          PolyDraw(r,5L,WindowBdrArray); /* temporary border to prevent */
          Flood(r, 0L, mx, my);          /* messes on flood fill to edge */
          SetAPen(r, 0L);
          Move(r, 0L, 0L);
          PolyDraw(r,5L,WindowBdrArray);
          ModifyMousePtr(Window, -1);   /* Remove 'ZZ' pointer */
          break;

        case VECTOR:
          if (exists_numwindow)
            break;
          /*** Decide which corner, if any, the user clicked on */
          curcor = '0';
          w = selpiece;    /* Just to keep source lines shorter... */
          if (labs(mx-pieceptr[w]->boxo.x)<5&&labs(my-pieceptr[w]->boxo.y)<5)
            curcor = 'o';
          if (labs(mx-pieceptr[w]->boxx.x)<5&&labs(my-pieceptr[w]->boxx.y)<5)
            curcor = 'x';
          if (labs(mx-pieceptr[w]->boxy.x)<5&&labs(my-pieceptr[w]->boxy.y)<5)
            curcor = 'y';
          if (labs(mx-pieceptr[w]->boxz.x)<5&&labs(my-pieceptr[w]->boxz.y)<5)
            curcor = 'z';
          if (curcor != '0') {     /* Set up ghost box for starters */

            /*** Update ghost box variables by calling ComputeNewBox() */
            ComputeNewBox();
            ghboxo = tmpboxo;               /* Structure Assignments */
            ghboxx = tmpboxx;
            ghboxy = tmpboxy;
            ghboxz = tmpboxz;

            ToggleGhostBox();  /* Draw initial ghost box */
          }
          break;
        }            /* End switch */
      }       /* End if SELECTDOWN */

      else if (code == SELECTUP) {
        pendown = 0;
        switch (drawmode) {

        case FREEHAND:
        case ERASE:
        case FILL:
          break;

        case LINES:
          SetAPen(r, 2L);   /* Draw final line (Jam onto last Ghost line) */
          Move(r,mx0, my0);
          Draw(r, mx1, my1);
          break;

        case VECTOR:
          if (exists_numwindow || curcor == '0')
            break;
          ToggleGhostBox();  /* Undraw Ghost Box */
          /*** Compute all coeffs from ghbox corners, put into tmppiece */
          tmppiece = *pieceptr[selpiece];  /* Structure assignment */
          tmppiece.s1 = 
           sqrt((double)((ghboxx.x-ghboxo.x)*(ghboxx.x-ghboxo.x) +
            (ghboxx.y-ghboxo.y)*(ghboxx.y-ghboxo.y)))/GZZWIDTH;
          tmppiece.s2 = 
           sqrt((double)((ghboxy.x-ghboxo.x)*(ghboxy.x-ghboxo.x) +
            (ghboxy.y-ghboxo.y)*(ghboxy.y-ghboxo.y)))/GZZHEIGHT;
          tmppiece.r1 = myatan2((double)(ghboxx.y-ghboxo.y),
           (double)(ghboxx.x-ghboxo.x));
          tmppiece.r2 = myatan2((double)(ghboxo.x-ghboxy.x),
           (double)(ghboxy.y-ghboxo.y));
          tmppiece.a = tmppiece.s1*cos(tmppiece.r1);
          tmppiece.b = -tmppiece.s2*sin(tmppiece.r2);
          tmppiece.c = tmppiece.s1*sin(tmppiece.r1);
          tmppiece.d = tmppiece.s2*cos(tmppiece.r2);
          tmppiece.e = (double)(ghboxo.x)/GZZWIDTH;
          tmppiece.f = (double)(ghboxo.y)/GZZWIDTH;

          /*** Erase Plane 3 before transformation delay */
          SetWrMsk(r,0xFFF8);  /* Write-protect planes 0,1,2 */
          SetRast(r, 0L);
          SetWrMsk(r,0xFFFF);
          /* Transform the selected piece per user drag of vector box */
          TransformPiece(outlinebufptr,
                                   pieceptr[selpiece]->piecemap, &tmppiece);
           /*** Erase Plane 2, then Blit Selected Piece from piecemap to it */
          SetWrMsk(r,0xFFF4);  /* Write-protect planes 0,1,3 */
          SetRast(r, 0L);
          SetAPen(r, 4L);
          BltTemplate((char*)pieceptr[selpiece]->piecemap,
           (long)Window->BorderLeft,(long)WIDTH/8, r, 0L, 0L,
             (long)GZZWIDTH, (long)GZZHEIGHT);
          /* Rastport WrMask will be restored in DrawBox() called below */

          /*** Recompute probabilities and update piece structures */
          tmppiece.boxo = ghboxo;  /* structure assignment */
          tmppiece.boxx = ghboxx;  /* structure assignment */
          tmppiece.boxy = ghboxy;  /* structure assignment */
          tmppiece.boxz = ghboxz;  /* structure assignment */
          *pieceptr[selpiece] = tmppiece;  /* structure assignment */
          pieceptr[selpiece]->det = 
           fabs(tmppiece.a * tmppiece.d - tmppiece.b * tmppiece.c);
          if (pieceptr[selpiece]->det == 0.0)
            pieceptr[selpiece]->det = 0.01;
          ComputeProbs();      /* Adjust all piece probabilities */
          DrawBox(); /* Redraw Vector Box for modified selected piece */
          curcor = '0';
          break;
        }            /* End switch */
      }              /* End else if SELECTUP */
    }                /* End if MOUSEBUTTONS */

    else if (class == GADGETUP) {   /*** Respond to Gadget clicked in Window */

      hitgadget = ((struct Gadget *)(message->IAddress))->GadgetID;
                                   /* ID# of just-selected gadget structure */
      if (hitgadget <= 3)    /* a draw mode gadget in Outline mode */
        HiliteGadget(hitgadget);
      switch(hitgadget) {

      case 0:
        drawmode = FREEHAND;
        SetAPen(r,2L);
        break;

      case 1:
        drawmode = LINES;
        SetAPen(r,2L);
        break;

      case 2:
        drawmode = ERASE;
        SetAPen(r, 0L);
        SetOPen(r, 0L);
        break;

      case 3:
        drawmode = FILL;
        SetOPen(r, 2L);   /* (A Pen defined before doing the flood fill) */
        break;

      case 4:             /* CLROUT (Clear Outline) gadget */
        SetRast(r, 0L);
        exists_outline = 0;
        if(drawmode == ERASE || drawmode == FILL) {
          drawmode = FREEHAND;
          SetAPen(r,2L);
          HiliteGadget(0);
        }
        break;

      case 5:             /* DONE gadget */
        SetMode(COLLAGE);
        AddDefPiece();
        DrawCollage();
        break;

      case 6:             /* ADD gadget */
        AddDefPiece();
        DrawCollage();
        if (exists_numwindow)
          UpdateNumStrings();
        break;

      case 7:             /* DUP gadget */
        DupSelPiece();
        DrawCollage();
        if (exists_numwindow)
          UpdateNumStrings();
        break;

      case 8:             /* SELECT gadget */
        if (--selpiece < 0)
          selpiece = N;
        DrawCollage();
        if (exists_numwindow)
          UpdateNumStrings();
        break;

      case 9:             /* DELETE gadget */
        DeleteSelPiece();
        DrawCollage();
        ComputeProbs();
        if (exists_numwindow)
          UpdateNumStrings();
        break;

      case 10:            /* CLRIFS gadget */
        ClearIFS();
        AddDefPiece();
        DrawCollage();
        if (exists_numwindow)
          UpdateNumStrings();
        break;

      case 11:            /* NUMBERS Gadget */
        if (!exists_numwindow) {
          UpdateNumStrings();
          OpenNumWindow();
          numgadgpending = -1;
        }

        break;
      }   /* end switch hitgadget */
    }     /* End else if GADGETUP */

    else if (class == MENUPICK) {     /*** Respond to menu selections */

      MenuNumber = code;         /* (First) item selection returned by IDCMP */
      while (MenuNumber != MENUNULL) { /*** service multiply chosen items */
                                       /* (if any) */
        ItemAddr = ItemAddress(&MenuList1, (long)MenuNumber);
       
        menuid = MENUNUM(MenuNumber); /* Extract menu, item from IDCMP code */
        itemid = ITEMNUM(MenuNumber);
        subitemid = SUBNUM(MenuNumber);

        if (menuid == 0) {   /* PROJECT Menu */
          switch (itemid) {

          case 0:            /* NEW */
            if (SetMode(OUTLINE) == 1) {
              SetRast(r, 0L);
              exists_outline = 0;
              if (exists_image)
                CloseImageScreen();
            }
            break;

          case 1:            /* EDIT OUTLINE */
            SetMode(OUTLINE);
            break;

          case 2:            /* LOAD FILE (with subitems) */
            if(ReqBase == NULL) {    /* req.library missing - no file I/O */
              ShowError("req.library not found!");
              break;
            }
            fname = GetFileName("File to Load:");
            if (*fname != '\0') {

              switch (subitemid) {

              case 0:    /* Load Outline */
                if (SetMode(OUTLINE) == 1)
                  LoadILBM(fname, 0);
                break;

              case 1:    /* Load IFS */
                SetMode(COLLAGE);
                if (LoadIFS(fname) == NULL) {  /* If load failed */
                  ClearIFS();
                  AddDefPiece();
                  DrawCollage();
                }
                break;

              case 2:    /* Load Image */
                LoadILBM(fname, 1);
                break;
              }    /* end switch subitemid */
            }      /* end if *fname etc */
            break;

          case 3:            /* SAVE FILE (with subitems) */
            if(ReqBase == NULL) {    /* req.library missing - no file I/O */
              ShowError("req.library not found!");
              break;
            }
            switch (subitemid) {

            case 0:    /* Save IFS */
              SaveIFS();
              break;

            case 1:    /* Save Image */
              SaveILBM();
              break;
            }    /* end switch subitemid */
            break;

          case 4:            /* OPTIMIZE */
            if (drawmode != VECTOR)
              ShowError("No IFS to Optimize!");
            else
              Optimize(1);
            break;

          case 5:            /* QUIT */
            CloseAll();
            break;
          }   /* end switch itemid */
        }     /* end if menuid == 0) */

        else if (menuid == 1) {   /* DISPLAY Menu */
          switch (itemid) {

          case 0:            /* SHOW CODES */
            if (drawmode != VECTOR)
              ShowError("No IFS!");
            else
              AboutText(CODES); 
            break;

          case 1:            /* SHOW IMAGE */
            if (exists_image) {
              MoveScreen(ImageScreen, 0L, (long)(-1*ImageScreen->TopEdge));
              ScreenToFront(ImageScreen);
            }
            else
              ShowError("No Image!");
            break;
          }   /* end switch itemid */
        }     /* end else if menuid == 1) */

        else if (menuid == 2) {   /* RENDER Menu */
          switch (itemid) {

          case 0:            /* RESOLUTION (with subitems) */
            renderwidth = (subitemid > 0) ? 640 : 320;
            renderheight = (subitemid > 1) ? 400 : 200;
            break;

          case 1:            /* GRAY LEVELS (with subitems) */
            renderdepth = subitemid + 1;
            break;

          case 2:            /* START */
            while(message = (struct IntuiMessage *)GetMsg(Window->UserPort)) 
              ReplyMsg(message);  /* Clean out IDCMP port before closing it */
            ModifyIDCMP(Window, NULL);   /* Disable IDCMP during rendering */
            ClearMenuStrip(Window); /* remove main window menus during render*/
            RenderImage(0);     /* render the attractor in Image Screen */
            SetMenuStrip(Window, &MenuList1);
            ModifyIDCMP(Window, MOUSEBUTTONS|GADGETUP|MENUPICK);
            break;
          }   /* end switch itemid */
        }     /* end else if menuid == 2) */

        else if (menuid == 3) {   /* ABOUT Menu */
          switch (itemid) {

          case 0:            /* IFS THEORY */
            AboutText(IFS_THEORY);
            break;

          case 1:            /* HELP */
            AboutText(HELP1);
            AboutText(HELP2);
            break;

          case 2:            /* AUTHOR */
            AboutText(AUTHOR);
            break;

          case 3:            /* DEMO */
            DoDemo();
            break;
          }   /* end switch itemid */
        }     /* end else if menuid == 3) */

        MenuNumber = ItemAddr->NextSelect; 
                      /* Loop back for next multiply selected item, if any */       
      }   /* end while MenuNumber != MENUNULL */
    }     /* end else if MENUPICK */

    /*** Get & serve an IDCMP event, if any, from Coefficients Window */

    if (exists_numwindow) {
      if (message = (struct IntuiMessage *)GetMsg(NumWindow->UserPort)) { 
        ReplyMsg(message);

        /* Note that all events in this window are GADGETUP, GADGETDOWN */
        /* or CLOSEWINDOW */

        if (message->Class == CLOSEWINDOW) {
          CloseWindow(NumWindow);
          exists_numwindow = 0;
          numgadgpending = -1;
          SetMenuStrip(Window, &MenuList1);
        }
        else if (message->Class == GADGETDOWN) {  /* ENTER or string gadget */
          if (numgadgpending != -1)
            RecomputeCoeffs(numgadgpending);
          hitgadstruct = (struct Gadget *)(message->IAddress);
                                               /* pointer to just-hit gadget */
          numgadgpending = hitgadstruct->GadgetID;
                                   /* ID# of just-selected gadget structure */

          /**** Refresh all string gadgets except just-hit one  */
          for(gad = &NumAcoeffGadg; gad != NULL; gad = gad->NextGadget)
            if (gad->GadgetID != numgadgpending)
              RefreshGList(gad, NumWindow, NULL, 1L);
          if (numgadgpending == 12)
            numgadgpending = -1;     /* It was only the ENTER Gadget */
        }
        else if (message->Class == GADGETUP) {
          hitgadget = ((struct Gadget *)(message->IAddress))->GadgetID;
                                   /* ID# of just-selected gadget structure */
          switch (hitgadget) {

          case 12:       /* ENTER */

            tmppiece = *pieceptr[selpiece];  /* Structure assignment */

            /* Copy all coeffs from string gadgets to tmppiece */

            sscanf((char*)NumNumR2coeffGadgSIBuff, "%lf", &tmppiece.r2);
            sscanf((char*)NumNumR1coeffGadgSIBuff, "%lf", &tmppiece.r1);
            sscanf((char*)NumNumS2coeffGadgSIBuff, "%lf", &tmppiece.s2);
            sscanf((char*)NumNumS1coeffGadgSIBuff, "%lf", &tmppiece.s1);
            sscanf((char*)NumNumPcoeffGadgSIBuff, "%lf", &tmppiece.p);
            sscanf((char*)NumNumDenscoeffGadgSIBuff, "%lf",
                                                   &tmppiece.dens);
            sscanf((char*)NumNumFcoeffGadgSIBuff, "%lf", &tmppiece.f);
            sscanf((char*)NumNumEcoeffGadgSIBuff, "%lf", &tmppiece.e);
            sscanf((char*)NumNumDcoeffGadgSIBuff, "%lf", &tmppiece.d);
            sscanf((char*)NumNumCcoeffGadgSIBuff, "%lf", &tmppiece.c);
            sscanf((char*)NumNumBcoeffGadgSIBuff, "%lf", &tmppiece.b);
            sscanf((char*)NumNumAcoeffGadgSIBuff, "%lf", &tmppiece.a);

            tmppiece.r1 = tmppiece.r1*0.017453293; /* convert degs to radians*/
            tmppiece.r2 = tmppiece.r2*0.017453293;

            /*** Erase Plane 3 before transformation delay */
            SetWrMsk(r,0xFFF8);  /* Write-protect planes 0,1,2 */
            SetRast(r, 0L);
            SetWrMsk(r, 0xFFFF);

            /* Transform the selected piece per user-entered coefficients */
            TransformPiece(outlinebufptr,
                               pieceptr[selpiece]->piecemap, &tmppiece);
            /*** Erase Plane 2, then Blit Selected Piece from piecemap to it */
            SetWrMsk(r,0xFFF4);  /* Write-protect planes 0,1,3 */
            SetRast(r, 0L);
            SetAPen(r, 4L);
            BltTemplate((char*)pieceptr[selpiece]->piecemap,
             (long)Window->BorderLeft,(long)WIDTH/8, r, 0L, 0L,
              (long)GZZWIDTH, (long)GZZHEIGHT);
            /* Rastport WrMask will be restored in DrawBox() called below */

            /*** Compute new Box corners from new coeffs */
            tmppiece.boxo.x = tmppiece.e * GZZWIDTH;
            tmppiece.boxo.y = tmppiece.f * GZZWIDTH; /* sic! */
            tmppiece.boxx.x = (tmppiece.a + tmppiece.e) * GZZWIDTH;
            tmppiece.boxx.y = (tmppiece.c + tmppiece.f) * GZZWIDTH;
            tmppiece.boxy.x = (tmppiece.b*GZZHEIGHT/GZZWIDTH + tmppiece.e) *
                               GZZWIDTH;
            tmppiece.boxy.y = (tmppiece.d*GZZHEIGHT/GZZWIDTH + tmppiece.f) *
                               GZZWIDTH;
            tmppiece.boxz.x = 
                       tmppiece.boxy.x + tmppiece.boxx.x - tmppiece.boxo.x;
            tmppiece.boxz.y = 
                       tmppiece.boxy.y + tmppiece.boxx.y - tmppiece.boxo.y;

            *pieceptr[selpiece] = tmppiece;  /* structure assignment */
            /*** Recompute probabilities and update piece structures */
            pieceptr[selpiece]->det = 
             fabs(tmppiece.a * tmppiece.d - tmppiece.b * tmppiece.c);
            if (pieceptr[selpiece]->det == 0.0)
              pieceptr[selpiece]->det = 0.01;
            ComputeProbs();      /* Adjust all piece probabilities */
            DrawBox(); /* Redraw Vector Box for modified selected piece */
            break;

          default:       /* GADGETUP on any coefficient string gadget */              
            numgadgpending = -1;
            RecomputeCoeffs(hitgadget);
            RefreshGadgets(&NumGadgetList3, NumWindow, NULL);
            break;
          }  /* end switch hitgadget */
        }    /* end else if ... GADGETUP */
      }      /* end if message etc of coeffs window */
    }        /* end if exists_numwindow */

    /*** Get & serve an IDCMP event, if any, from Image Window */
    /*   Note that all events in this window are MOUSEBUTTONS  */

    if (exists_image) {
      if (message = (struct IntuiMessage *)GetMsg(ImageWindow->UserPort)) { 
        ReplyMsg(message);
        if (message->Code == SELECTUP) { /* User clicked - push Image to back*/
          ScreenToBack(ImageScreen);
          MoveScreen(ImageScreen, 0L, (long)(-1*ImageScreen->TopEdge));
          ScreenToFront(Screen);
          ActivateWindow(Window);
        }
      }
    }        /* End if exists_image */
  }          /* End while of main loop */
}            /* End main() */
/*=========================================================================*/

char * GetFileName(prompt)    /* Returns pointer to a filename selected from */
char *prompt;        /* a file requester. Prompt appears at top of requester */
                     /* window. If user selected CANCEL,returns a nullstring */
                     /* Places the requester in a custom 640x200 screen.     */

                     /* Uses the Fox/Dawson File Requester; you must link    */
                     /* with glue module myreqglue.o. You need to include    */
                     /* reqbase.h and also libraries/dosextens.h (for the    */
                     /* Process structure).                                  */
{
  struct Screen *ReqScreen;
  struct Window *ReqWindow;
  struct NewScreen ns;
  struct Screen *OpenScreen();
  struct NewWindow nw;
  struct Window *OpenWindow();

  struct Task *FindTask();
  struct Process *myprocess;
  APTR oldwindowptr;

  /* Link the buffers to the FileRequester struct (all declared as globals) */

  MyFileReqStruct.File = filename;
  MyFileReqStruct.Dir = directoryname;
  MyFileReqStruct.PathName = pathname;

  /*** Open a window in a custom Hires screen for requester display */

  ns.LeftEdge = 0;      /*** Initialize a NewScreen structure */
  ns.TopEdge = 0;
  ns.Width = 640;
  ns.Height = 200;
  ns.Depth = 2;
  ns.DetailPen = 0;  /* Colour of text in screen title bar */
  ns.BlockPen = 1;   /* Colour of screen Title Bar */
  ns.ViewModes = HIRES;
  ns.Type = CUSTOMSCREEN;
  ns.Font = &TOPAZ80;
  ns.DefaultTitle = NULL;
  ns.Gadgets = NULL;
  ns.CustomBitMap = NULL;

  nw.LeftEdge = 0;         /*** Initialize a NewWindow structure */
  nw.TopEdge = 0;
  nw.Width = 640;
  nw.Height = 200;
  nw.DetailPen = 0; /* Menu title text color */
  nw.BlockPen  = 1; /* Menu item box and title bar background */
  nw.IDCMPFlags = NULL;
  nw.Flags = ACTIVATE|NOCAREREFRESH;
  nw.FirstGadget = NULL;
  nw.CheckMark = NULL;
  nw.Title = (UBYTE*)" ";
  nw.Screen = NULL;  /* Will set it to ReqScreen after the screen is opened */
  nw.BitMap = NULL;
  nw.MinWidth = 0;
  nw.MinHeight = 0;
  nw.MaxWidth = 0;
  nw.MaxHeight = 0;
  nw.Type = CUSTOMSCREEN;


  /*** Open a Hires screen to place the requester in */

  if ((ReqScreen = (struct Screen *) OpenScreen(&ns)) == NULL) {
    ShowError("Couldn't open Requester screen!!!");
    pathname[0] = '\0'; /* make returned buffer a nullstring */
    return(pathname);
  }

  /*** Open the Window to place the requester in */
  nw.Screen = ReqScreen;
  if ((ReqWindow = (struct Window *) OpenWindow(&nw)) == NULL) {
    CloseScreen(ReqScreen);
    ShowError("Couldn't open Requester window!!!");
    pathname[0] = '\0'; /* make returned buffer a nullstring */
    return(pathname);
  }  

  /* Set pointer to tell req.library requesters where to appear */
  myprocess = (struct Process *)FindTask((char *)0);
  oldwindowptr = myprocess->pr_WindowPtr;
  myprocess->pr_WindowPtr = (APTR)ReqWindow;

  /*** Set up file requester structure fields to customize requester */

  MyFileReqStruct.Title = prompt;    /* Text at top of requester */
  MyFileReqStruct.dirnamescolor = 3; /* colours of requester elements */
  MyFileReqStruct.devicenamescolor = 3;
  MyFileReqStruct.WindowLeftEdge = 0;
  MyFileReqStruct.WindowTopEdge = 0;
  MyFileReqStruct.Flags = FRQCACHINGM |    /* Directory Caching */
                       FRQABSOLUTEXYM | /* Fix requester position in screen  */
                       FRQNOHALFCACHEM| /* Don't cache half-read directories */
                       FRQNODRAGM;      /* No drag bar on requester */

  if (FileRequester(&MyFileReqStruct) == NULL)   /* Call the File Requester */
    pathname[0] = '\0'; /* If user selected no file, return a nullstring*/

  /* restore pr_WindowPtr before closing the window! */
  myprocess->pr_WindowPtr = oldwindowptr;

  CloseWindow(ReqWindow);   /*** Close requester screen stuff and return */
  CloseScreen(ReqScreen);

  return(pathname); /* Will be the pointer to a string which now contains the */
                    /* full file name selected, or a NULL string if the user */
                    /* selected CANCEL or no filename */
}
/*===========================================================================*/

OpenLibraries()     /* Open needed libraries */
{
  IntuitionBase = (struct IntuitionBase *)
          OpenLibrary("intuition.library",INTUITION_REV);
  if (IntuitionBase == NULL) {
    printf("Can't open Intuition library!!!!!!\n");
    CloseAll();
  }

  GfxBase = (struct GfxBase *)
          OpenLibrary("graphics.library",GRAPHICS_REV);
  if (GfxBase == NULL) {
    printf("Can't open graphics library!!!!!!\n");
    CloseAll();
  }

  MathBase = (void *)OpenLibrary("mathffp.library",0L);
  if (MathBase == NULL) {
    printf("Can't open mathffp library!!!!!!\n");
    CloseAll();
  }

  MathTransBase = (void *)OpenLibrary("mathtrans.library",0L);
  if (MathTransBase == NULL) {
    printf("Can't open mathtrans library!!!!!!\n");
    CloseAll();
  }
  ReqBase = (struct ReqLib *)OpenLibrary("req.library", 0L);
  if (ReqBase == NULL) {
    printf("Couldn't open req.library!!!!!!\n");
    printf("IFSlab requires req.library in libs: to be able to do file I/O\n");
  }
}
/*=========================================================================*/

OpenDisplay()   /* Open Outline Sketch window in custom screen */

{
  /* NEWSCREENSTRUCTURE, PALETTE and NewWindowStructure1 are in IFSLab.h */
  /* and, along with pointers Window, Screen, have been declared externally */

  struct Screen *OpenScreen();
  struct Window *OpenWindow();
  struct ViewPort *vp;

  /*** Open the screen */
  if ((Screen = (struct Screen *) OpenScreen(&NEWSCREENSTRUCTURE)) == NULL) {
    printf("\nCouldn't open screen!!!!\n");
    CloseAll();
  }
  /*** Link Custom colours defined in PALETTE to screen's ViewPort */  
  vp = &(Screen->ViewPort);/* ptr to viewport structure assoc'd with screen */
  LoadRGB4(vp,PALETTE,16L);   /* Change to custom colors defined above */

  /*** Open the Window */

  NewWindowStructure1.Screen = Screen;
  if ((Window =  (struct Window *) OpenWindow(&NewWindowStructure1)) == NULL) {
    printf("\nCouldn't open window!!!!\n");
    CloseAll();
  }

  scrp = &(Screen->RastPort); /* Screen RastPort,used to draw in outer window*/
  SetDrMd(scrp, JAM1);
  SetAPen(scrp, 1L);
  r = (Window->RPort);  /* Window's rastport, used for drawing in GZZ bitmap */
  SetDrMd(r, JAM1);
  HiliteGadget(0);
  SetMenuStrip(Window,&MenuList1);
  OffMenu(Window, NOITEM<<5 | 2);     /* disable render menu and its items */
}
/*===========================================================================*/

OpenNumWindow()   /* Open Coefficients Window and disable menus */
{ 
  /* Assumes window is not open already */

  NumNewWindowStructure3.Screen = Screen;
  if ((NumWindow = (struct Window *)OpenWindow(&NumNewWindowStructure3))
                                                                 == NULL) {
    ShowError("Error - Couldn't open coeffs window");
    return;
  }
  ClearMenuStrip(Window); /* remove main window menus while Coeffs window on */
  SetWindowTitles(NumWindow, -1L, (char *)"           Collage Editor");
  exists_numwindow = 1;
}
/*===========================================================================*/

CloseAll()    /* Close everything neatly and exit program */
{
  if (exists_image)
    CloseImageScreen();
  for (i = 0; i <= N; i++)  /* Dealocate Pieces' struct and piecemap memory */
    FreePiece(pieceptr[i]);
  if (outlinebufptr)
    FreeMem(outlinebufptr, (long)RASSIZE(WIDTH, HEIGHT));
  if (Window) {
    ClearMenuStrip(Window);
    CloseWindow(Window);
  }
  if (exists_numwindow)
    CloseWindow(NumWindow);
  if (Screen)
    CloseScreen(Screen);
  if (IntuitionBase)
    CloseLibrary(IntuitionBase);
  if (GfxBase)
    CloseLibrary(GfxBase);
  if (MathBase)
    CloseLibrary(MathBase);
  if (MathTransBase)
    CloseLibrary(MathTransBase);
  if (ReqBase) {
    PurgeFiles(&MyFileReqStruct);    /* function in req.library */
    CloseLibrary(ReqBase);
  }

  exit(0);
}
/*===========================================================================*/

int SetMode(newmode)   /* Sets up and enters requested mode */
int newmode;           /* newmode can be OUTLINE or COLLAGE */
{                   /* returns 1 normally; returns 0 if user chose CANCEL in */
                    /* "IFS will be LOST" requester */
  BOOL AutoRequest();
  static  struct IntuiText Cancel = {
    3,0,                 /* FrontPen, BackPen */
    JAM1,                /* DrawMode */
    6,3,                 /* LeftEdge, TopEdge */
    &TOPAZ80,            /* ITextFont */
    (UBYTE*)"Cancel",    /* IText */
    NULL                 /* NextText */
  };
  static struct IntuiText Doit, ReqBodyText;
  Doit = ReqBodyText = Cancel;  /* structure assignment */
  ReqBodyText.IText = (UBYTE *)" IFS will be LOST!";
  ReqBodyText.TopEdge = 8;
  Doit.IText = (UBYTE *)"Do it!";

  switch (newmode) {

  case OUTLINE:
    if (drawmode != VECTOR)     /* already in desired mode! */
      return(1);
    if (AutoRequest(Window, &ReqBodyText, &Doit, &Cancel, 
                          NULL, NULL, 180L, 56L) == FALSE)  {
      /* Set IDCMP flags right in case AutoRequest() messed 'em up */
      ModifyIDCMP(Window, MOUSEBUTTONS|GADGETUP|MENUPICK);
      return(0);
    }
    ModifyIDCMP(Window, MOUSEBUTTONS|GADGETUP|MENUPICK); /* After AutoReq. */
    ClearIFS();
    RemoveGList(Window, &CollageGadgetList2, 6L);
    AddGList(Window, &GadgetList1, 0L, 6L, NULL); /* change gadgets */
    SetDrMd(scrp, JAM1);
    SetAPen(scrp, 0L);
    RectFill(scrp, 280L, 12L, 316L, 117L); /* Blot out old gadget images */
    RefreshGadgets(&GadgetList1, Window, NULL);
    OffMenu(Window, NOITEM<<5 | 2);     /* disable render menu and its items */
    SetWindowTitles(Window, -1L, "           Outline Editor");
    drawmode = FREEHAND;
    HiliteGadget(0);
    SetAPen(r, 2L);
    break;

  case COLLAGE:
    if (drawmode == VECTOR)     /* already in desired mode! */
      return(1);
    if (exists_outline == 0)
      PutDefOutline();

    CopyMem((char*)Window->RPort->BitMap->Planes[1], 
      (char*)outlinebufptr, (long)RASSIZE(WIDTH,HEIGHT));

    RemoveGList(Window, &GadgetList1, 6L);
    AddGList(Window, &CollageGadgetList2, 0L, 6L, NULL);
    SetDrMd(scrp, JAM1);
    SetAPen(scrp, 0L);
    RectFill(scrp, 280L, 12L, 316L, 117L); /* Blot out old gadget images */
    RefreshGadgets(&CollageGadgetList2, Window, NULL);
    OnMenu(Window, NOITEM<<5 | 2);     /* Enable render menu and its items */
    SetWindowTitles(Window, -1L, "           Collage Editor");
    drawmode = VECTOR;
    curcor = '0';
    break;
  }    /* end switch newmode */
  return(1);
}
/*===========================================================================*/

DupSelPiece()       /* Generate a copy of Selected Piece, make it highest */
{                   /* numbered and Selected. Increment N. */
  if (N >= MAX_N) {
    ShowError("Too many transformations");
    return;
  }
  /*** Allocate and fill piece structure, make it Selected */

  if ((pieceptr[++N] = AllocPiece()) == NULL) {  /* Allocate Piece */
    N--;         /* if couldn't allocate piece */
    if (N == -1) {    /* No memory for even one Piece - Quit program */
      ShowError("Re-run with more free memory!");
      CloseAll();
    }
    return;
  }
  pieceptr[N]->a = pieceptr[selpiece]->a;
  pieceptr[N]->b = pieceptr[selpiece]->b;
  pieceptr[N]->c = pieceptr[selpiece]->c;
  pieceptr[N]->d = pieceptr[selpiece]->d;
  pieceptr[N]->e = pieceptr[selpiece]->e;
  pieceptr[N]->f = pieceptr[selpiece]->f;
  pieceptr[N]->s1 = pieceptr[selpiece]->s1;
  pieceptr[N]->s2 = pieceptr[selpiece]->s2;
  pieceptr[N]->r1 = pieceptr[selpiece]->r1;
  pieceptr[N]->r2 = pieceptr[selpiece]->r2;
  pieceptr[N]->dens = 1.0;
  pieceptr[N]->det = pieceptr[selpiece]->det;
  pieceptr[N]->boxo = pieceptr[selpiece]->boxo;  /* Structure assignment! */
  pieceptr[N]->boxx = pieceptr[selpiece]->boxx;  /* Structure assignment! */
  pieceptr[N]->boxy = pieceptr[selpiece]->boxy;  /* Structure assignment! */
  pieceptr[N]->boxz = pieceptr[selpiece]->boxz;  /* Structure assignment! */

  ComputeProbs();  /* Adjust all Piece probabilities */
  /* Copy selected piece's piecemap to the new piece's piecemap */
  CopyMem((char*)pieceptr[selpiece]->piecemap, (char*)pieceptr[N]->piecemap,
                                              (long)RASSIZE(WIDTH,GZZHEIGHT));
  selpiece = N;   /* make this Piece Selected */
}
/*===========================================================================*/

AddDefPiece()       /* Generate a default Piece of current Outline, make */
{                   /* it highest numbered and Selected. Increment N. */
  if (N >= MAX_N) {
    ShowError("Too many transformations");
    return;
  }
  /*** Allocate and fill piece structure, make it Selected */

  if ((pieceptr[++N] = AllocPiece()) == NULL) {  /* Allocate Piece */
    N--;         /* if couldn't allocate piece */
    if (N == -1) {    /* No memory for even one Piece - Quit program */
      ShowError("Re-run with more free memory!");
      CloseAll();
    }
    return;
  }
  pieceptr[N]->a = 0.5;
  pieceptr[N]->b = 0.0;
  pieceptr[N]->c = 0.0;
  pieceptr[N]->d = 0.5;
  pieceptr[N]->e = 0.25;              
  pieceptr[N]->f = 0.16938406;    /*   = 0.25*GZZHEIGHT/GZZWIDTH */
  pieceptr[N]->s1 = 0.5;
  pieceptr[N]->s2 = 0.5;
  pieceptr[N]->r1 = 0.0;
  pieceptr[N]->r2 = 0.0;
  pieceptr[N]->dens = 1.0;
  pieceptr[N]->det = 0.25;
  pieceptr[N]->boxo.x = 0.25 * GZZWIDTH;
  pieceptr[N]->boxo.y = 0.25 * GZZHEIGHT;
  pieceptr[N]->boxx.x = 0.75 * GZZWIDTH;
  pieceptr[N]->boxx.y = 0.25 * GZZHEIGHT;
  pieceptr[N]->boxy.x = 0.25 * GZZWIDTH;
  pieceptr[N]->boxy.y = 0.75 * GZZHEIGHT;
  pieceptr[N]->boxz.x = 0.75 * GZZWIDTH;
  pieceptr[N]->boxz.y = 0.75 * GZZHEIGHT;

  ComputeProbs();  /* Adjust all Piece probabilities */
  /* Transform Outline by this piece's transformation into its piecemap */
  TransformPiece(outlinebufptr, pieceptr[N]->piecemap, pieceptr[N]);
  selpiece = N;   /* make this Piece Selected */
}
/*===========================================================================*/

ComputeProbs()       /* Recompute all piece probabilities */
{
  double sum = 0;

  for (i = 0; i <= N; i++)
    sum = sum + pieceptr[i]->dens * pieceptr[i]->det;

  for (i = 0; i <= N; i++)
    pieceptr[i]->p = (pieceptr[i]->dens * pieceptr[i]->det)/sum;
}
/*===========================================================================*/

DrawBox()   /* Erase bitplane 3, and draw Vector Box of Selected Piece in it */
{
  SetWrMsk(r,0xFFF8);  /* Write-protect planes 0,1,2 */
  SetRast(r, 0L);      /* Erase plane 3 */

  SetAPen(r, 8L);    /*** Draw Vector Box */
  Move(r, pieceptr[selpiece]->boxo.x, pieceptr[selpiece]->boxo.y);
  Draw(r, pieceptr[selpiece]->boxx.x, pieceptr[selpiece]->boxx.y);
  Draw(r, pieceptr[selpiece]->boxz.x, pieceptr[selpiece]->boxz.y);
  Draw(r, pieceptr[selpiece]->boxy.x, pieceptr[selpiece]->boxy.y);
  Draw(r, pieceptr[selpiece]->boxo.x, pieceptr[selpiece]->boxo.y);

  SetDrMd(r, JAM2|INVERSVID); /*** Draw Corner "gadgets" */
  SetBPen(r, 0L);
  Move(r, pieceptr[selpiece]->boxo.x - 3L, pieceptr[selpiece]->boxo.y + 3L);
  Text(r, "O", 1L);
  Move(r, pieceptr[selpiece]->boxx.x - 3L, pieceptr[selpiece]->boxx.y + 3L);
  Text(r, "x", 1L);
  Move(r, pieceptr[selpiece]->boxz.x - 3L, pieceptr[selpiece]->boxz.y + 3L);
  Text(r, " ", 1L);
  Move(r, pieceptr[selpiece]->boxy.x - 3L, pieceptr[selpiece]->boxy.y + 3L);
  Text(r, "y", 1L);

  SetDrMd(r, JAM1);
  SetWrMsk(r,0xFFFF);
}
/*===========================================================================*/

ToggleGhostBox()        /* Draw or undraw current ghost box, as given in */
{                                /* ghbox? variables, in COMPLEMENT mode */
  SetDrMd(r, JAM1|COMPLEMENT);
  Move(r, ghboxo.x, ghboxo.y);
  Draw(r, ghboxx.x, ghboxx.y);
  Draw(r, ghboxz.x, ghboxz.y);
  Draw(r, ghboxy.x, ghboxy.y);
  Draw(r, ghboxo.x, ghboxo.y);
  SetDrMd(r, JAM1);

}
/*===========================================================================*/

ComputeNewBox()    /* Compute New Ghost Box from mouse coords, put in tmpbox */
{                  /* variables. */
  long dx, dy;   /* increments of mouse position from corner's previous pos */
  double s, a;   /* Scaling and Rotation components of change in vector Z */
  double cosa, sina;   /* cosine and sine of angle a */
  double Qx, Qy;
  double tmp;

  switch (curcor) { 

  case 'o':
    dx = mx - pieceptr[selpiece]->boxo.x;
    dy = my - pieceptr[selpiece]->boxo.y;
    tmpboxo.x = mx;
    tmpboxo.y = my;
    tmpboxx.x = pieceptr[selpiece]->boxx.x + dx;
    tmpboxx.y = pieceptr[selpiece]->boxx.y + dy;
    tmpboxy.x = pieceptr[selpiece]->boxy.x + dx;
    tmpboxy.y = pieceptr[selpiece]->boxy.y + dy;
    tmpboxz.x = pieceptr[selpiece]->boxz.x + dx;
    tmpboxz.y = pieceptr[selpiece]->boxz.y + dy;
    break;

  case 'x':
    dx = mx - pieceptr[selpiece]->boxx.x;
    dy = my - pieceptr[selpiece]->boxx.y;
    tmpboxo = pieceptr[selpiece]->boxo;   /* Structure Assignment */
    tmpboxx.x = mx;
    tmpboxx.y = my;
    tmpboxy = pieceptr[selpiece]->boxy;   /* Structure Assignment */
    tmpboxz.x = pieceptr[selpiece]->boxz.x + dx;
    tmpboxz.y = pieceptr[selpiece]->boxz.y + dy;
    break;

  case 'y':
    dx = mx - pieceptr[selpiece]->boxy.x;
    dy = my - pieceptr[selpiece]->boxy.y;
    tmpboxo = pieceptr[selpiece]->boxo;   /* Structure Assignment */
    tmpboxx = pieceptr[selpiece]->boxx;   /* Structure Assignment */
    tmpboxy.x = mx;
    tmpboxy.y = my;
    tmpboxz.x = pieceptr[selpiece]->boxz.x + dx;
    tmpboxz.y = pieceptr[selpiece]->boxz.y + dy;
    break;

  case 'z':
    s = sqrt((double)((mx-pieceptr[selpiece]->boxo.x)*
     (mx-pieceptr[selpiece]->boxo.x)+(my-pieceptr[selpiece]->boxo.y)*
     (my-pieceptr[selpiece]->boxo.y))/
     (double)((pieceptr[selpiece]->boxz.x-pieceptr[selpiece]->boxo.x)*
     (pieceptr[selpiece]->boxz.x-pieceptr[selpiece]->boxo.x)+
     (pieceptr[selpiece]->boxz.y-pieceptr[selpiece]->boxo.y)*
     (pieceptr[selpiece]->boxz.y-pieceptr[selpiece]->boxo.y)));

    a = myatan2((double)(my - pieceptr[selpiece]->boxo.y), (double)(mx - 
     pieceptr[selpiece]->boxo.x)) - myatan2((double)
     (pieceptr[selpiece]->boxz.y - pieceptr[selpiece]->boxo.y), 
     (double)(pieceptr[selpiece]->boxz.x - pieceptr[selpiece]->boxo.x));

    cosa = cos(a);
    sina = sin(a);

    Qx = pieceptr[selpiece]->boxo.x + s * (pieceptr[selpiece]->boxo.y * sina -
     pieceptr[selpiece]->boxo.x * cosa);
    Qy = pieceptr[selpiece]->boxo.y - s * (pieceptr[selpiece]->boxo.y * cosa +
     pieceptr[selpiece]->boxo.x * sina);

    tmpboxo = pieceptr[selpiece]->boxo;   /* Structure Assignment */

    /* Below, the conditional assignments assure rounding on the */
    /* double-to-int conversion rather than truncation */
    tmp = s * (pieceptr[selpiece]->boxx.x * cosa - 
     pieceptr[selpiece]->boxx.y * sina) + Qx;
    tmpboxx.x = (tmp - floor(tmp) > 0.5) ? tmp + 1. : tmp;
    tmp = s * (pieceptr[selpiece]->boxx.x * sina + 
     pieceptr[selpiece]->boxx.y * cosa) + Qy;
    tmpboxx.y = (tmp - floor(tmp) > 0.5) ? tmp + 1. : tmp;
    tmp = s * (pieceptr[selpiece]->boxy.x * cosa - 
     pieceptr[selpiece]->boxy.y * sina) + Qx;
    tmpboxy.x = (tmp - floor(tmp) > 0.5) ? tmp + 1. : tmp;
    tmp = s * (pieceptr[selpiece]->boxy.x * sina + 
     pieceptr[selpiece]->boxy.y * cosa) + Qy;
    tmpboxy.y = (tmp - floor(tmp) > 0.5) ? tmp + 1. : tmp;
    tmpboxz.x = tmpboxy.x + tmpboxx.x - tmpboxo.x;
    tmpboxz.y = tmpboxx.y + tmpboxy.y - tmpboxo.y;
    break;
  }         /* End switch curcor */
}
/*===========================================================================*/

DeleteSelPiece()  /* Delete the Selected Piece. If it was the only piece,  */
{                 /* replace it with a Default Piece. Rearrange Piece list */
  FreePiece(pieceptr[selpiece]);   /* free memory associated with the piece */
  for (i = selpiece; i < N; i++)   /* close gap in pointer array */
    pieceptr[i] = pieceptr[i+1];
  if (selpiece == N)
    selpiece--;
  N--;

  if (N == -1)     /* if deleted last piece, place a default piece */
    AddDefPiece();
}
/*==========================================================================*/

ClearIFS()  /* Clear IFS variables, memory, and Collage image */ 
{
  /* Clear all GZZ bitplanes except Outline plane */
  SetWrMsk(r,0xFFFD);  /* Write-protect plane 1, Outline */
  SetRast(r, 0L);
  SetWrMsk(r,0xFFFF);

  for(i = 0; i <= N; i++)
    FreePiece(pieceptr[i]);
  N = -1;
}
/*==========================================================================*/

HiliteGadget(gadgetid)        /* frame gadget of given ID# in Red */
        /* Implements in software Gadget Mutual Exclude */
        /* Because window is GZZ and gadgets are in its border, this routine */
        /* Draws the frame directly on the Screen,not in Window. It is quite */
        /* non-generic because gadget coordinates are coded in numerically */
int gadgetid;
{
  long i;
     /*** Define frames as border structures ***/

  static SHORT Frame_data[10] = { 0,0, 36,0, 36,15, 0,15, 0,0 };
                                              /* Gadget select frame data */

  static struct Border OnFrame_Bdr = {  /* Red (Selected) Gadget border */
     0,0,                 /* LeftEdge, TopEdge */
     4,0,JAM1,            /* FrontPen, BackPen, DrawMode */
     5,                   /* Count */
     Frame_data,          /* XY */
     NULL                 /* Next Border */
  };

  static struct Border OffFrame_Bdr = { /* Lt Green (deselected) Gadg border */
     0,0,                 /* LeftEdge, TopEdge */
     3,0,JAM1,            /* FrontPen, BackPen, DrawMode */
     5,                   /* Count */
     Frame_data,          /* XY */
     NULL                 /* Next Border */
  };

  /*** Unframe all gadgets, then frame selected one */ 
  for (i = 0; i <= 3; i++)
    DrawBorder(scrp, &OffFrame_Bdr, 280L, i*18L+12L);
  DrawBorder(scrp, &OnFrame_Bdr, 280L, gadgetid*18L+12L);
}
/*==========================================================================*/

DrawCollage()    /* Draw the current IFS as a collage image with */
{                /* vector Box of Selected Piece (wipe out old box)*/
  /*** Erase all bitplanes except Outline plane */
  SetWrMsk(r,0xFFFD);  /* Write-protect plane 1, Outline */
  SetRast(r, 0L);

  /* Blit Deselected pieces from piecemaps to plane 0 */
  SetWrMsk(r,0x0001);  /* Write protect all planes except plane 0 */
  SetAPen(r, 1L);
  for (i = 0; i <= N; i++)
    if (i != selpiece)    /* skip Selected piece */
      BltTemplate((char*)pieceptr[i]->piecemap,(long)Window->BorderLeft,
        (long)WIDTH/8, r, 0L, 0L, (long)GZZWIDTH, (long)GZZHEIGHT);
  /* Blit Selected Piece from piecemap to plane 2 */
  SetWrMsk(r,0x0004);  /* Write protect all planes except plane 2 */
  SetAPen(r, 4L);
  BltTemplate((char*)pieceptr[selpiece]->piecemap,(long)Window->BorderLeft,
   (long)WIDTH/8, r, 0L, 0L, (long)GZZWIDTH, (long)GZZHEIGHT);
  
  DrawBox();  /* Draw Vector Box of Selected Piece */
  /* Note that Rastport's WrMask is restored within DrawBox() */
}
/*==========================================================================*/

double myatan2(y,x) /* atan2() as defined in Aztec C but missing from mf.lib */
double x, y;        /* returns arctangent of y/x in range -pi to pi.         */
{                   /* If x=y=0, returns 0, sets errno=EDOM. */
                    /* Requires #include of errno.h and math.h */

  #define PI 3.141592654
  if (x == 0.0) {
    if (y == 0.0) {
      errno = EDOM;
      return(0.0);
    }
    else
      x = 1.0e-10;
  }

  if (y >= 0.0) {
    if (x >= 0.0)
      return(atan(y/x));        /* Quadrant I */
    else
      return(PI - atan(-y/x));  /* Quadrant II */
  }
  else {
    if (x >= 0.0)
      return(-atan(-y/x));      /* Quadrant IV */
    else
      return(atan(y/x) - PI);      /* Quadrant III */
  }
}
/*==========================================================================*/

ModifyMousePtr(Wind, n)
  /* Change to custom mouse pointer in Window if n=1, or restore default */
  /* arrow and free CHIP RAM if n=-1. Use in only one window at a time! */
struct Window *Wind;
int n;           
{
  static short *ChipPtr;
  static struct ViewPort *vprt;
  static long color17, color18, color19;
  long GetRGB4();

  /*** Define  Custom mouse pointer sprite data */

  #define ROWS 14    /* Number of Pixel rows in pointer */

  static USHORT Mouseptr[] = {
    0x0000, 0x0000,
                                  /*  ++++++++++++++++++  */
    0x00f0, 0x00f0,               /*  +        3333    +  */
    0x03f8, 0x03f8,               /*  +      3333333   +  */
    0x07fc, 0x079c,               /*  +     333311333  +  */
    0x0ffe, 0x0e0e,               /*  +    33311111333 +  */
    0x0ffe, 0x0c06,               /*  +    33111111133 +  */
    0xafff, 0x0c73,               /*  +1 1 331113331133+  */
    0xafff, 0x0cfb,               /*  +1 1 331133333133+  */
    0xafff, 0x0ccb,               /*  +1 1 331133113133+  */
    0xcfff, 0x0cdb,               /*  +11  331133133133+  */
    0xdfff, 0x18c3,               /*  +11 3311133111133+  */
    0xffff, 0x1ce7,               /*  +1113331133311333+  */
    0xfffe, 0x07fe,               /*  +111113333333333 +  */
    0x7ffe, 0x003c,               /*  + 11111111133331 +  */
    0x1fff, 0x0000,               /*  +   1111111111111+  */
                                  /*  ++++++++++++++++++  */
    0x0000, 0x0000
  };

  if (n == 1 && ChipPtr == NULL) { 

    /*** Get colors of default pointer in Window's screen */
    vprt = &(Wind->WScreen->ViewPort); /* ptr to viewport of screen */
    color17 = GetRGB4(vprt->ColorMap, 17L);
    color18 = GetRGB4(vprt->ColorMap, 18L);
    color19 = GetRGB4(vprt->ColorMap, 19L);

    /*** Copy sprite data into chip RAM to be accessible to graphic chips */
    ChipPtr =(short*)AllocMem((long)((ROWS+2)*4),MEMF_CHIP); /* Alloc block */
    CopyMem((char*)Mouseptr,(char*)ChipPtr,(long)((ROWS+2)*4)); /* Copy data*/
    /*** Attach custom pointer to window */
    SetPointer(Wind, ChipPtr, (long)ROWS, 16L, -1L, -1L);

    /*** Change custom pointer colors */
    SetRGB4(vprt, 17L, 15L, 12L, 10L);
    SetRGB4(vprt, 18L, 13L, 2L, 2L); 
    SetRGB4(vprt, 19L, 15L, 0L, 0L); 
  }
  else if (n == -1 && ChipPtr != NULL) {
    ClearPointer(Wind);     /* Reset default pointer and its original colors */
    SetRGB4(vprt, 17L,  color17>>8, (color17>>4L)&15, color17&15);
    SetRGB4(vprt, 18L,  color18>>8, (color18>>4L)&15, color18&15);
    SetRGB4(vprt, 19L,  color19>>8, (color19>>4L)&15, color19&15);
    FreeMem(ChipPtr, (long)((ROWS+2)*4)); /* release memory of pointer data */
    ChipPtr = NULL;
  }
  else 
   DisplayBeep(NULL);  /* Illegal call (called twice in a row with same n */
}
/*===========================================================================*/

UpdateNumStrings()   /* Update values of Coeff window string gadgets from */
{       /* selected piece structure. Redraw gadgets if the window is open */
  double degr1, degr2;

  degr1 = pieceptr[selpiece]->r1 / 0.017453293; /* convert radians to degrees*/
  degr2 = pieceptr[selpiece]->r2 / 0.017453293;

  sprintf((char*)NumNumT2coeffGadgSIBuff, "%5.2lf", pieceptr[selpiece]->f);
  sprintf((char*)NumNumT1coeffGadgSIBuff, "%5.2lf", pieceptr[selpiece]->e);
  sprintf((char*)NumNumR2coeffGadgSIBuff, "%5.lf", degr2);
  sprintf((char*)NumNumR1coeffGadgSIBuff, "%5.lf", degr1);
  sprintf((char*)NumNumS2coeffGadgSIBuff, "%5.2lf", pieceptr[selpiece]->s2);
  sprintf((char*)NumNumS1coeffGadgSIBuff, "%5.2lf", pieceptr[selpiece]->s1);
  sprintf((char*)NumNumPcoeffGadgSIBuff, "%5.3lf", pieceptr[selpiece]->p);
  sprintf((char*)NumNumDenscoeffGadgSIBuff,"%4.1lf", pieceptr[selpiece]->dens);
  sprintf((char*)NumNumFcoeffGadgSIBuff, "%5.2lf", pieceptr[selpiece]->f);
  sprintf((char*)NumNumEcoeffGadgSIBuff, "%5.2lf", pieceptr[selpiece]->e);
  sprintf((char*)NumNumDcoeffGadgSIBuff, "%5.2lf", pieceptr[selpiece]->d);
  sprintf((char*)NumNumCcoeffGadgSIBuff, "%5.2lf", pieceptr[selpiece]->c);
  sprintf((char*)NumNumBcoeffGadgSIBuff, "%5.2lf", pieceptr[selpiece]->b);
  sprintf((char*)NumNumAcoeffGadgSIBuff, "%5.2lf", pieceptr[selpiece]->a);
  if (exists_numwindow)
    RefreshGadgets(&NumGadgetList3, NumWindow, NULL);
}
/*===========================================================================*/

RecomputeCoeffs(gadgetid)         /* Re-Calculate 13 coefficients based on */
int gadgetid;      /* change in value of the one in the string gadget with */
{                  /* the given gadgetid; put new values in string gadgets */
                   /* Does range checking on entered coefficients; If user */
                   /* entered an illegal value, will restore previous      */
                   /* value and inform user before returning.              */
  double a, b, c, d, e, f, s1, s2, r1, r2, t1, t2, dens, p;
  double sum, p0, det, olddet, olddens;

  /* Put current coefficient values into temporary variables */
  sscanf((char*)NumNumT2coeffGadgSIBuff, "%lf", &t2);
  sscanf((char*)NumNumT1coeffGadgSIBuff, "%lf", &t1);
  sscanf((char*)NumNumR2coeffGadgSIBuff, "%lf", &r2);
  sscanf((char*)NumNumR1coeffGadgSIBuff, "%lf", &r1);
  sscanf((char*)NumNumS2coeffGadgSIBuff, "%lf", &s2);
  sscanf((char*)NumNumS1coeffGadgSIBuff, "%lf", &s1);
  sscanf((char*)NumNumPcoeffGadgSIBuff, "%lf", &p);
  sscanf((char*)NumNumDenscoeffGadgSIBuff, "%lf", &dens);
  sscanf((char*)NumNumFcoeffGadgSIBuff, "%lf", &f);
  sscanf((char*)NumNumEcoeffGadgSIBuff, "%lf", &e);
  sscanf((char*)NumNumDcoeffGadgSIBuff, "%lf", &d);
  sscanf((char*)NumNumCcoeffGadgSIBuff, "%lf", &c);
  sscanf((char*)NumNumBcoeffGadgSIBuff, "%lf", &b);
  sscanf((char*)NumNumAcoeffGadgSIBuff, "%lf", &a);

  while (r1 > 180. || r1 < -180.) /* Get angles to range -180 => +180 */
    r1 = (r1 > 0.) ? r1 - 360. : r1 + 360.;
  while (r2 > 180. || r2 < -180.)
    r2 = (r2 > 0.) ? r2 - 360. : r2 + 360.;

  r1 = r1*0.017453293;            /* convert degrees to radians*/
  r2 = r2*0.017453293;

  /*** Check that all coefficients are in legal ranges */
  if (a < -1. || a > 1. || b < -1. || b > 1. || c < -1. || c > 1. ||
      d < -1. || d > 1. || e < -0.25 || e > 1. || f < -0.25 || f > 1. ||
      s1 < -1. || s1 > 1. || s2 < -1. || s2 > 1. || dens <= 0. ||
      t1 < -0.25 || t1 > 1. || t2 < -0.25 || t2 > 1. || p <= 0. || p > 1.) {
    ShowError("Coefficient out of Range!");
    a = pieceptr[selpiece]->a;    /*** Undo the error */
    b = pieceptr[selpiece]->b;
    c = pieceptr[selpiece]->c;
    d = pieceptr[selpiece]->d;
    e = pieceptr[selpiece]->e;
    f = pieceptr[selpiece]->f;
    p = pieceptr[selpiece]->p;
    dens = pieceptr[selpiece]->dens;
    s1 = pieceptr[selpiece]->s1;
    s2 = pieceptr[selpiece]->s2;
    r1 = pieceptr[selpiece]->r1;
    r2 = pieceptr[selpiece]->r2;
    t1 = pieceptr[selpiece]->e;
    t2 = pieceptr[selpiece]->f;
  }

  det = fabs(a * d - b * c);
  if (det == 0.0)
    det = 0.01;
  sum = -(pieceptr[selpiece]->dens * pieceptr[selpiece]->det) + dens * det;
  for (i = 0; i <= N; i++)
    sum = sum + pieceptr[i]->dens * pieceptr[i]->det;

  if (gadgetid <= 26) {   /* id's 21 thru 26, matrix coeffs a - f */
    s1 = sqrt(a*a + c*c);
    s2 = sqrt(b*b + d*d);
    r1 = myatan2(c, a);
    r2 = myatan2(-b, d);
    t1 = e;
    t2 = f;
    p = (dens * det)/sum;
  }

  if (gadgetid == 27) {   /* density coefficient */
    p = (dens * det)/sum;
  }

  if (gadgetid == 28)  {   /* probability coefficient */
    olddens = dens;
    p0 = det / (sum - olddens*det + det); /* What p SHOULD be with dens == 1 */
    dens = p / p0;
    p = (dens * det)/(sum - olddens*det + dens*det);
  }

  if (gadgetid >= 29) {   /* id's 29 thru 34, geometrical coeffs s1 thru t2 */
    olddet = fabs(a * d - b * c);
    if (olddet == 0.0)
      olddet = 0.01;
    a = s1 * cos(r1);
    b = -s2 * sin(r2);
    c = s1 * sin(r1);
    d = s2 * cos(r2);
    e = t1;
    f = t2;
    det = fabs(a * d - b * c);  /*** recompute det and probability */
    if (det == 0.0)
      det = 0.01;
    sum = sum - olddet*dens + det*dens;
    p = (dens * det)/sum;
  }

  r1 = r1 / 0.017453293; /* convert radians to degrees */
  r2 = r2 / 0.017453293;

  /* Put resultant coefficient values into their string gadgets */
  sprintf((char*)NumNumT2coeffGadgSIBuff, "%5.2lf", f);
  sprintf((char*)NumNumT1coeffGadgSIBuff, "%5.2lf", e);
  sprintf((char*)NumNumR2coeffGadgSIBuff, "%5.lf", r2);
  sprintf((char*)NumNumR1coeffGadgSIBuff, "%5.lf", r1);
  sprintf((char*)NumNumS2coeffGadgSIBuff, "%5.2lf", s2);
  sprintf((char*)NumNumS1coeffGadgSIBuff, "%5.2lf", s1);
  sprintf((char*)NumNumPcoeffGadgSIBuff, "%5.3lf", p);
  sprintf((char*)NumNumDenscoeffGadgSIBuff, "%4.1lf", dens);
  sprintf((char*)NumNumFcoeffGadgSIBuff, "%5.2lf", f);
  sprintf((char*)NumNumEcoeffGadgSIBuff, "%5.2lf", e);
  sprintf((char*)NumNumDcoeffGadgSIBuff, "%5.2lf", d);
  sprintf((char*)NumNumCcoeffGadgSIBuff, "%5.2lf", c);
  sprintf((char*)NumNumBcoeffGadgSIBuff, "%5.2lf", b);
  sprintf((char*)NumNumAcoeffGadgSIBuff, "%5.2lf", a);

  return;
}
/*==========================================================================*/

ShowError(text)    /* Show error message in a Window, return on user */
                   /* clicking in the OK gadget. Text string must not */
                   /* exceed Window's width minus two characters. */
                   /* Window is used and not a requester so it is in front */
                   /* of coeffs window & GZZ Border of main Window */
char *text;  /* The message to be displayed */
{
  struct Message * GetMsg();
  void ReplyMsg();
  struct IntuiMessage *message;
  int gadgetid;
  struct Window *ErrWindow, *OpenWindow();

  #define TEXTCOLOR 3      /* Text color for message and "OK" */
  #define BGCOLOR   14     /* Color for Window and Gadget background */
  #define GADGBDRCOLOR 3   /* Color for border of "OK" Gadget */
  #define BDRCOLOR 3       /* Color for border of Error-window */

  /*** Initialize Error-window structures */

  static  struct IntuiText OKGadgetMsg = {
    TEXTCOLOR, BGCOLOR,  /* FrontPen, BackPen */
    JAM1,                /* DrawMode */
    4,3,                 /* LeftEdge, TopEdge */
    NULL,                /* ITextFont */
    (UBYTE*)"OK",        /* IText */
    NULL                 /* NextText */
  };

  static SHORT OKGadgetCoord[18] = { 0,0, 23,0, 23,12, 0,12, 0,0,
                                     24,0, 24,12, -1,12, -1,0 };
  /* NOTE: Second line above gives 2-pixel-wide border for hires screen */

  static struct Border OKGadgetBdr = {
    0,0,                 /* LeftEdge, TopEdge */
    GADGBDRCOLOR, BGCOLOR, JAM1,   /* FrontPen, BackPen, DrawMode */
    9,                   /* Count */
    &OKGadgetCoord[0],   /* XY */
    NULL                 /* NextBorder */
  };

  static struct Gadget OKGadget = {
    NULL,                /*  NextGadget */
    -40,35,24,13,       /* LeftEdge, TopEdge, Width, Height */
    GADGHCOMP | GRELRIGHT,  /* Flags */
    ENDGADGET |
    RELVERIFY,           /* Activation */
    BOOLGADGET,          /* GadgetType */
    (APTR)&OKGadgetBdr,  /* GadgetRender */
    NULL,                /* SelectRender */
    &OKGadgetMsg,        /* GadgetText */
    0,                   /* MutualExclude */
    NULL,                /* SpecialInfo */
    99,                  /* GadgetID */
    NULL                 /* UserData */
  };
  
  static SHORT BdrCoords[10] = { 2,2, 0,2, 0,57, 2,57, 2,2 };
                                              /* 0's to be replaced later */
  static struct Border Bdr = {
    0, 0,           /* LeftEdge, TopEdge */
    BDRCOLOR, BGCOLOR, JAM1,   /* FrontPen, BackPen, DrawMode */
    5,              /* Count */
    &BdrCoords[0],  /* XY */
    NULL            /* NextBorder */
  };

  static struct IntuiText ErrText = {
    TEXTCOLOR, BGCOLOR, JAM1,   /* FrontPen, BackPen, DrawMode */
    8, 18,             /* LeftEdge, TopEdge */
    NULL,              /* ITextFont */
    NULL,              /* IText -- Will be filled later */
    NULL,              /* NextText */
  };

  static struct Image ErrWinBgnd = {    /* Just a filled rectangle */
    0, 0,              /* LeftEdge, TopEdge */
    0, 60, 0,          /* Width,Height, Depth */
    NULL,              /* ImageData */
    0x0, 0xE,          /* PlanePick, PlaneOnOff */
    NULL               /* NextImage */
  };

  static struct NewWindow ErrNewWindow = {
    0,60,   /* window XY origin relative to TopLeft of screen */
    0,60,    /* window width and height */
    TEXTCOLOR,BDRCOLOR,  /* detail and block pens */
    GADGETUP,  /* IDCMP flags */
    ACTIVATE|SIMPLE_REFRESH|BORDERLESS,  /* other window flags */
    &OKGadget,   /* first gadget in gadget list */
    NULL,   /* custom CHECKMARK imagery */
    NULL,   /* window title */
    NULL,   /* custom screen pointer */
    NULL,   /* custom bitmap */
    5,5,    /* minimum width and height */
    -1,-1,  /* maximum width and height */
    CUSTOMSCREEN  /* destination screen type */
  };

  /*** Compute and fill in text and window-dependent stuff */

  ErrNewWindow.Screen = Screen;
  ErrNewWindow.Width = strlen(text) * 8 + 16;
  if (ErrNewWindow.Width < 44)
    ErrNewWindow.Width = 44;
  ErrWinBgnd.Width = ErrNewWindow.Width;

  ErrNewWindow.LeftEdge = (320 - ErrNewWindow.Width)/2;
  if (ErrNewWindow.LeftEdge < 0)
    ErrNewWindow.LeftEdge = 0;

  BdrCoords[2] = BdrCoords[4] = ErrNewWindow.Width - 3;

  ErrText.IText = (UBYTE*)text;

  if (Window->WScreen->Width = 320) /* If Lo-res -- remove vert. double line */
    OKGadgetBdr.Count = 5;
  else
    OKGadgetBdr.Count = 9;

  /*** Display the Error-Window */


  if ((ErrWindow = OpenWindow(&ErrNewWindow)) == NULL) {
    printf("\nCouldn't open Error Message window!!!\n");
    CloseAll();
  }
  DrawImage(ErrWindow->RPort, &ErrWinBgnd, 0L, 0L);
  PrintIText(ErrWindow->RPort, &ErrText, 0L, 0L);
  DrawBorder(ErrWindow->RPort, &Bdr, 0L, 0L);
  RefreshGadgets(&OKGadget, ErrWindow, NULL);
                     /* Because DrawImage() obscured the OK Gadget image */

  /*** Wait until user clicks the Error-window's "OK" Gadget */
  gadgetid = 9999;
  while (gadgetid != 99) {
    if (message = (struct IntuiMessage *)GetMsg(ErrWindow->UserPort)) { 
      gadgetid = ((struct Gadget *)(message->IAddress))->GadgetID;
      ReplyMsg(message);
    }
  }
  CloseWindow(ErrWindow);
}
/*===========================================================================*/

OpenImageScreen(width, height, depth)  /* Open a Custom screen & window for */
                                       /* the Image; Set exists_image flag */
int width, height, depth;
{ 
  struct NewScreen ns;
  struct Screen *OpenScreen();
  struct NewWindow nw;
  struct Window *OpenWindow();
  long numcols;  /* # of colors in screen */
  UWORD *colortableptr[5];   /* Array of Pointers to screen colortables */
                             /* Note: element 0 is not used! */

  static UWORD colortable1[] =     /*** Colors for 1-bitplane screen */
    {0x000, 0x068, 0x0AF, 0x5CF};
  static UWORD colortable2[] =     /*** Colors for 2-bitplane screen */
    {0x000, 0x057, 0x07B, 0xAF};
  static UWORD colortable3[] =     /*** Colors for 3-bitplane screen */
    {0x000, 0x034, 0x046, 0x068, 0x06A, 0x08C, 0x09E, 0x0AF};
  static UWORD colortable4[] =     /*** Colors for 4-bitplane screen */
    {0x000, 0x011, 0x012, 0x023, 0x034, 0x045, 0x046, 0x057,
     0x068, 0x069, 0x06A, 0x07B, 0x08C, 0x08D, 0x09E, 0x0AF};

  colortableptr[1] = colortable1; 
  colortableptr[2] = colortable2; 
  colortableptr[3] = colortable3; 
  colortableptr[4] = colortable4;

  ns.LeftEdge = 0;      /*** Initialize NewScreen structure */
  ns.TopEdge = 0;
  ns.Width = width;
  ns.Height = height;
  ns.Depth = (depth == 1) ? 2 : depth;
  ns.DetailPen = 3;  /* Colour of text in screen title bar */
  ns.BlockPen = 1;   /* Colour of screen Title Bar */
  ns.ViewModes = ((width == 640) ? HIRES:0) | ((height == 400) ? INTERLACE:0);
  ns.Type = CUSTOMSCREEN;
  ns.Font = &TOPAZ80;
  ns.DefaultTitle = (UBYTE*)" Click in screen to stop rendering";
  ns.Gadgets = NULL;
  ns.CustomBitMap = NULL;

  switch (depth) {    /* Customize Image Title and Border colors to depth */
  case (1):
    ns.DetailPen = 3;
    ns.BlockPen = 1;
    break;
  case (2):
    ns.DetailPen = 3;
    ns.BlockPen = 1;
    break;
  case (3):
    ns.DetailPen = 7;
    ns.BlockPen = 3;
    break;
  case (4):
    ns.DetailPen = 15;
    ns.BlockPen = 7;
    break;
  }

  nw.LeftEdge = 0;         /*** Initialize NewWindow structure */
  nw.TopEdge = 0;
  nw.Width = width;
  nw.Height = height;
  nw.DetailPen = ns.DetailPen; /* Title text color */
  nw.BlockPen  = ns.BlockPen;  /* Title bar background */
  nw.IDCMPFlags = MOUSEBUTTONS;
  nw.Flags = ACTIVATE|GIMMEZEROZERO|SIMPLE_REFRESH|BACKDROP;
  nw.FirstGadget = NULL;
  nw.CheckMark = NULL;
  nw.Title = (UBYTE*)" ";   /* Never seen - stays behind screen title bar */
  nw.Screen = NULL; /* Will set it to ImageScreen after the screen is opened */
  nw.BitMap = NULL;
  nw.MinWidth = 0;
  nw.MinHeight = 0;
  nw.MaxWidth = 0;
  nw.MaxHeight = 0;
  nw.Type = CUSTOMSCREEN;

  /*** Open the screen */
  if ((ImageScreen = (struct Screen *) OpenScreen(&ns)) == NULL) {
    ShowError("Couldn't open Image screen!");
    return;
  }

  /* Link Custom colours defined above to screen's ViewPort */  
  numcols = 1 << ns.Depth;
  LoadRGB4(&(ImageScreen->ViewPort), colortableptr[depth], numcols);

  /*** Open the Window */
  nw.Screen = ImageScreen;
  if ((ImageWindow = (struct Window *) OpenWindow(&nw)) == NULL) {
    CloseScreen(ImageScreen);
    ShowError("Couldn't open Image window!");
    return;
  }
  SetWindowTitles(ImageWindow,-1L,
                              (char *)" Click in screen to stop rendering");
  exists_image = 1;
}
/*===========================================================================*/

CloseImageScreen()   /* Close image screen & Window, reset exists_image flag */
{
  struct Message * GetMsg();
  void ReplyMsg();
  struct IntuiMessage *message;

  /* Empty ImageWindow IDCMP queue if any */
  while (message = (struct IntuiMessage *)GetMsg(ImageWindow->UserPort))
      ReplyMsg(message);

  CloseWindow(ImageWindow);
  CloseScreen(ImageScreen);
  exists_image = 0;
}
/*===========================================================================*/

DoDemo()        /* Demonstrate IFS process, then NEW and return */
{
  if (SetMode(OUTLINE) == 1) {
    SetRast(r, 0L);
    exists_outline = 0;
  }
  else   /* User clicked CANCEL in IFS WILL BE LOST requester - Abort demo */
    return;
  ModifyIDCMP(Window, NULL);   /* Disable Editor Window IDCMP during Demo */
  ClearMenuStrip(Window); /* remove main window menus during demo */
  AboutText(DEMOTEXT);    /* Show explanation of demo in text screen */
  ModifyMousePtr(Window, 1);   /* Bring up 'ZZ' pointer */

  /* Draw outline of Sierpinski Triangle */
  SetDrMd(r, JAM1);
  for (i = 0; i < 100000; i++) ; /* Delay */
  Move(r, 138L, 24L);
  Draw(r, 24L, 172L);
  for (i = 0; i < 100000; i++) ; /* Delay */
  Draw(r, 238L, 131L);
  for (i = 0; i < 100000; i++) ; /* Delay */
  Draw(r, 138L, 24L);
  for (i = 0; i < 100000; i++) ; /* Delay */
  Move(r, 82L, 98L);
  Draw(r, 188L, 77L);
  for (i = 0; i < 100000; i++) ; /* Delay */
  Draw(r, 130L, 152L);
  for (i = 0; i < 100000; i++) ; /* Delay */
  Draw(r, 82L, 98L);
  exists_outline = 1;
  for (i = 0; i < 150000; i++)  /* Delay before entering Collage Editor */
    ;

  /*** Place 3 Pieces to form Collage */

  SetMode(COLLAGE);
  for (i = 0; i < 100000; i++) ; /* Delay */
  ModifyMousePtr(Window, -1);   /* Remove 'ZZ' pointer */
  AddDefPiece();
  DrawCollage();
  pieceptr[0]->e = 0.25;
  pieceptr[0]->f = 0.045;
  pieceptr[0]->dens = 1.0;
  ComputeBoxCorners(pieceptr[0]);
  ComputeProbs();
  for (i = 0; i < 100000; i++) ; /* Delay */
  TransformPiece(outlinebufptr, pieceptr[0]->piecemap, pieceptr[0]);
  DrawCollage();

  for (i = 0; i < 100000; i++) ; /* Delay */
  AddDefPiece();
  DrawCollage();
  pieceptr[1]->e = 0.045;
  pieceptr[1]->f = 0.315;
  pieceptr[1]->dens = 0.4;
  ComputeBoxCorners(pieceptr[1]);
  ComputeProbs();
  for (i = 0; i < 100000; i++) ; /* Delay */
  TransformPiece(outlinebufptr, pieceptr[1]->piecemap, pieceptr[1]);
  DrawCollage();

  for (i = 0; i < 100000; i++) ; /* Delay */
  AddDefPiece();
  DrawCollage();
  pieceptr[2]->e = 0.43;
  pieceptr[2]->f = 0.24;   
  pieceptr[2]->dens = 0.7;
  ComputeBoxCorners(pieceptr[2]);
  ComputeProbs();
  for (i = 0; i < 100000; i++) ; /* Delay */
  TransformPiece(outlinebufptr, pieceptr[2]->piecemap, pieceptr[2]);
  DrawCollage();

  for (i = 0; i < 300000; i++)  /* Delay before rendering */
    ;
  RenderImage(0);               /* Render, until user clicks in image screen */

  /*** After rendering ended, do a NEW (without IFS WILL BE LOST requester) */
  ClearIFS();
  RemoveGList(Window, &CollageGadgetList2, 6L);
  AddGList(Window, &GadgetList1, 0L, 6L, NULL); /* change gadgets */
  SetDrMd(scrp, JAM1);
  SetAPen(scrp, 0L);
  RectFill(scrp, 280L, 12L, 316L, 117L); /* Blot out old gadget images */
  RefreshGadgets(&GadgetList1, Window, NULL);
  OffMenu(Window, NOITEM<<5 | 2);     /* disable render menu and its items */
  SetWindowTitles(Window, -1L, "           Outline Editor");
  drawmode = FREEHAND;
  HiliteGadget(0);
  SetAPen(r, 2L);
  SetRast(r, 0L);
  exists_outline = 0;
  CloseImageScreen();
  ModifyIDCMP(Window, MOUSEBUTTONS|GADGETUP|MENUPICK);  /* Restore IDCMP */
  SetMenuStrip(Window, &MenuList1);  /* Restore menu strip */
}
/*===========================================================================*/

ComputeBoxCorners(pcptr)     /* Compute Vector Box corners from Piece coeffs */
struct piece *pcptr;
{
  pcptr->boxo.x = pcptr->e * GZZWIDTH;
  pcptr->boxo.y = pcptr->f * GZZWIDTH; /* sic! */
  pcptr->boxx.x = (pcptr->a + pcptr->e) * GZZWIDTH;
  pcptr->boxx.y = (pcptr->c + pcptr->f) * GZZWIDTH;
  pcptr->boxy.x = (pcptr->b*GZZHEIGHT/GZZWIDTH + pcptr->e) * GZZWIDTH;
  pcptr->boxy.y = (pcptr->d*GZZHEIGHT/GZZWIDTH + pcptr->f) * GZZWIDTH;
  pcptr->boxz.x =  pcptr->boxy.x + pcptr->boxx.x - pcptr->boxo.x;
  pcptr->boxz.y = pcptr->boxy.y + pcptr->boxx.y - pcptr->boxo.y;
}
/*===========================================================================*/

LoadILBM(fnam, dest)        /* Load an Image from IFF file; If dest = 1,    */
char *fnam;                 /* put image and colormap in Image Screen;      */
int dest;                   /* if dest = 0, put plane 0 of image in Outline */
{
  struct BitMap picbitmap = {0};    /* Empty BitMap structure to "Fill" */
  ILBMFrame iFrame;         /* ILBM Frame to be used by Reader Routines */
  LONG file;  /* File handle [we use AmigaDOS Open(), not Manx open()] */
  LONG iffp;
  char *blitbuffer; /* Needed for the Blit operation only */
  struct BitMap tmpbitmap;  /* used for 2-step blit to Outline */

  #define MIN(a,b) ((a)<(b)?(a):(b))

  /* Open the File to read */
  if ((file = Open(fnam, MODE_OLDFILE)) == NULL) {
    ShowError("Can't open file!");
    return;
  }
  ModifyMousePtr(Window, 1);   /* Bring up 'ZZ' pointer */

  /* Use EA IFF routine ReadPicture() to Read image from ILBM file into */
  /*  memory, modify picbitmap accordingly */

  iffp = ReadPicture(file,&picbitmap,&iFrame,ChipAlloc);

  Close(file);

  if (iffp != IFF_DONE) {  /* React to error message from EA routine, if any */
    ShowError(IFFPMessages[-iffp]);
    if (picbitmap.Planes[0]) /* free planes allocated by ReadPicture, if any */
      RemFree(picbitmap.Planes[0]);
    ModifyMousePtr(Window, -1);   /* Remove 'ZZ' pointer */
    return;
  }

  /*** Now image is in memory; copy it to the proper destination bitmap */

  switch (dest) {

  case 0:  /***  Copy plane 0 of loaded image to Outline (plane 1 in Window) */

    SetRast(r, 0L);   /* Erase old outline, set drawmode to FREEHAND */
    if(drawmode == ERASE || drawmode == FILL) {
      drawmode = FREEHAND;
      SetAPen(r,2L);
      HiliteGadget(0);
    }

    /* NOTE: Must use 2 steps because must blit (possibly part of) loaded */
    /*  image of arbitrary size, adjusting for GZZ borders, and from plane 0 */
    /*  to plane 1. Neither BltBitMap nor BltTemplate can do this in 1 step */

    /* Step 1: Blit plane 0 of loaded image to temp bitmap  */
    /*         (uses outline buffer as its single bitplane) */
    InitBitMap(&tmpbitmap, 1L, WIDTH, HEIGHT);
    tmpbitmap.Planes[0] = (PLANEPTR)outlinebufptr;

    blitbuffer = (char *)AllocMem(80L,MEMF_CHIP); /* Allocate temp buffer */
    BltBitMap(&picbitmap, 0L, 0L, &tmpbitmap, 0L, 0L,
     MIN(WIDTH, picbitmap.BytesPerRow*8L),
     MIN(HEIGHT, picbitmap.Rows), 192L, 0x1, blitbuffer);
     /* 192L is minterm for Direct Copy; 0x1 is mask, will copy plane 0 */
    FreeMem(blitbuffer, 80L);

    /* Step 2: Blit the temporary bitplane from Outline Buffer to plane 1 */
    /*         of GZZ Window */

    SetAPen(r, 2L);
    SetDrMd(r, JAM1);
    SetWrMsk(r,0x0002);  /* Write protect all planes except plane 1 */
    BltTemplate((char*)outlinebufptr, 0L, WIDTH/8L, Window->RPort, 0L, 0L, 
     MIN(GZZWIDTH, picbitmap.BytesPerRow*8L), MIN(GZZHEIGHT, picbitmap.Rows));
    SetWrMsk(r,0xFFFF);

    exists_outline = 1;
    break;

  case 1:   /***  Set up a (new) Image Screen and blit loaded image into it */
    if (exists_image)
      CloseImageScreen();
    OpenImageScreen(iFrame.bmHdr.pageWidth, iFrame.bmHdr.pageHeight,
                                                            picbitmap.Depth);

    /* Link colours read in from file to screen's ViewPort */  
    LoadRGB4(&(ImageScreen->ViewPort), iFrame.colorMap, 1<<picbitmap.Depth);

    SetWindowTitles(ImageWindow,
               (char *)" ", (char *)" Click in screen to push it back");
    blitbuffer = (char *)AllocMem(80L,MEMF_CHIP); /* Allocate temp buffer */
    BltBitMap(&picbitmap, 2L, 11L, ImageWindow->RPort->BitMap, 2L, 11L,
     MIN(ImageWindow->Width-4L,picbitmap.BytesPerRow*8L-2L),
     MIN(ImageWindow->Height-13L,picbitmap.Rows-11L), 192L, 0xFF, blitbuffer);
     /* 192L is minterm for Direct Copy; 0xFF is mask, will copy all planes */
     /* 2L, 11L, 4L, 13L cause The blit to skip the window borders */
    FreeMem(blitbuffer, 80L);
    exists_image = 1;
    break;
  }       /* End switch dest */

  if (picbitmap.Planes[0])  /* free planes allocated by ReadPicture, if any */
    RemFree(picbitmap.Planes[0]);
  ModifyMousePtr(Window, -1);   /* Remove 'ZZ' pointer */
}
/*==========================================================================*/

AboutText(whichtext)   /* Opens the text screen and displays in it */
int whichtext;         /* the selected About text or IFS code table */
                       /* according to whichtext. Closes on click in screen */
       /* whichtext can be: CODES, IFS_THEORY, HELP1, HELP2, AUTHOR, DEMOTEXT*/
{
  struct Screen *TextScreen;
  struct Window *TextWindow;
  struct NewScreen ns;
  struct Screen *OpenScreen();
  struct NewWindow nw;
  struct Window *OpenWindow();
  struct Message * GetMsg();
  struct IntuiMessage *message;
  static struct IntuiText RedText = {
    3, 0, JAM1,        /* FrontPen, BackPen, DrawMode */
    0, 0,              /* LeftEdge, TopEdge */
    &TOPAZ80,          /* ITextFont */
    (UBYTE*)"Click Mouse to Continue", NULL  /* IText, NextText */
  };
  char codelinebuf[78];   /* Buffer for line of codes in CODES display */
  static struct IntuiText CodesText = {     /* Codes line for display */
    1, 0, JAM1,        /* FrontPen, BackPen, DrawMode */
    0, 0,              /* LeftEdge, TopEdge */
    &TOPAZ80,          /* ITextFont */
    NULL, NULL  /* IText, NextText */
  };

  /*** Open a window in a custom Hires screen for text display */

  ns.LeftEdge = 0;      /*** Initialize NewScreen structure */
  ns.TopEdge = 0;
  ns.Width = 640;
  ns.Height = 200;
  ns.Depth = 2;
  ns.DetailPen = 0;  /* Colour of text in screen title bar */
  ns.BlockPen = 1;   /* Colour of screen Title Bar */
  ns.ViewModes = HIRES;
  ns.Type = CUSTOMSCREEN;
  ns.Font = &TOPAZ80;
  ns.DefaultTitle = NULL;
  ns.Gadgets = NULL;
  ns.CustomBitMap = NULL;

  nw.LeftEdge = 0;         /*** Initialize NewWindow structure */
  nw.TopEdge = 0;
  nw.Width = 640;
  nw.Height = 200;
  nw.DetailPen = 0; /* Menu title text color */
  nw.BlockPen  = 1; /* Menu item box and title bar background */
  nw.IDCMPFlags = MOUSEBUTTONS;
  nw.Flags = ACTIVATE|SIMPLE_REFRESH;
  nw.FirstGadget = NULL;
  nw.CheckMark = NULL;
  nw.Title = (UBYTE*)" ";
  nw.Screen = NULL;  /* Will set it to TextScreen after the screen is opened */
  nw.BitMap = NULL;
  nw.MinWidth = 0;
  nw.MinHeight = 0;
  nw.MaxWidth = 0;
  nw.MaxHeight = 0;
  nw.Type = CUSTOMSCREEN;

  /*** Open the screen */
  if ((TextScreen = (struct Screen *) OpenScreen(&ns)) == NULL) {
    ShowError("Couldn't open Text screen!!!");
    return;
  }

  /*** Open the Window */
  nw.Screen = TextScreen;
  if ((TextWindow = (struct Window *) OpenWindow(&nw)) == NULL) {
    CloseScreen(TextScreen);
    ShowError("Couldn't open Text window!!!");
    return;
  }  
  Textrp = (TextWindow->RPort);  /* Window's rastport, used by p() */

  p(NULL);  /* reset screen print routine to top of screen */
  p(" ");   /* skip 2 lines to clear text window title bar */
  p(" ");

  switch (whichtext) {   /**** Print appropreiate text into window */

  case IFS_THEORY:
    SetWindowTitles(TextWindow,  " IFS Theory Background", -1L);

p("   IFS is a method developed by Michael F. Barnsley that allows one to en\
code a");
p(" Fractal as a small set of numbers, the coefficients of an Iterated Funct\
ion");
p(" System (IFS) Code, and later to reconstruct it from those numbers. An IF\
S Code");
p(" is a set of Contractive Affine Transformations, which are transformation\
s of");
p(" the plane that combine a Linear Transformation (consisting of combinatio\
ns of");
p(" stretching and rotation) and a Translation of the origin. Each such affi\
ne");
p(" transformation can be represented by six real numbers. In addition, with\
 each");
p(" transformation is associated a probability value.");
p(" ");
p("   To derive the IFS Code for a given Fractal image, one must find a set \
of");
p(" transformations of the image - reduced, deformed copies of it - that whe\
n");
p(" taken together cover the original image. To reconstruct  the image, one \
starts");
p(" with any point in the plane,  picks at random one of the transformations");
p(" defined above, applies it to the point, and draws the resulting point. T\
o this");
p(" new point one applies the same procedure, and so on. The probabilities d\
efine");
p(" how often each transformation will be picked. The 'Collage Theorem' assu\
res");
p(" that this process will in fact reconstruct the original Fractal.");
p(" ");
p("   For more detailed information on the theory and use of IFS, refer to:");
p("    - Barnsley and Sloan: A Better Way to Compress Images, BYTE, Jan. 88\
.");
p("    - Peitgen and Saupe, eds.: The Science of Fractal Images, Chapter 5.");

    break;

  case HELP1:
    SetWindowTitles(TextWindow,  " A Summary of Program Operation", -1L);

p(" This program requires two steps to generate an IFS Code for a Fractal:");
p(" ");
p(" a. OUTLINE EDITOR: This step allows you to draw the approximate Outline \
of");
p("  the planned Fractal. The user interface is a simple Paint program, with");
p("  Freestyle, Lines, Erase (XXX), Fill and Clear gadgets. Note that the le\
ss");
p("  pixels you draw, the faster things will move later, in the Collage Edit\
or");
p("  phase. When your Outline is finished, click DONE to enter the Collage E\
ditor.");
p(" ");
p(" b. COLLAGE EDITOR: Here you create the Collage, by defining the affine");
p("  transformations ('Pieces') to cover the Outline with. When you enter th\
is");
p("  mode, you see your Outline, and one Default transformation - a half-siz\
ed");
p("  copy of this Outline, drawn in red. The box around it is the image of t\
he");
p("  screen boundary under the transformation. You can modify the Piece by");
p("  dragging any corner of this box with the mouse. The corner marked 'O' w\
ill");
p("  translate the Origin; 'X' and 'Y' will stretch and rotate these axes; T\
he");
p("  last corner will enlarge and rotate the Piece without deforming it. Cli\
ck");
p("  ADD to add a new Default Piece, or DUP to add a duplicate of the curren\
t");
p("  Piece. The Piece drawn in red is the 'Selected' one; all editing action\
s");
p("  apply to it. Clicking SEL repeatedly will make one Piece after another \
the");
p("  Selected one, allowing you to modify (or delete, by clicking DEL) previ\
ously");
p("  defined Pieces.");

    break;

  case HELP2:
    SetWindowTitles(TextWindow, " A Summary of Program Operation (Continued)",
                                                                          -1L);

p("    You can click the NUM gadget to get a numeric display of the Selected");
p("  transformation's coefficients (a-f, p), as well as the associated Proba\
bility");
p("  Density (probability per unit area) and Scalings, Rotations and Transla\
tions.");
p("  You can modify any one of these values in their string gadgets, and cli\
ck");
p("  ENTER to make the changes take effect. To quit the numeric window, clic\
k its");
p("  Close gadget.");
p(" ");
p("    When you've covered the Outline fully with Pieces, use the Render men\
u");
p("  to generate the Attractor of the IFS in the resolution and gray level c\
ount");
p("  of your choice.");
p(" ");
p("    The Optimize menu item will modify the current IFS so that its Attrac\
tor");
p("  will fill the screen; it will also generate an exact Collage for it, by\
 using");
p("  the Attractor itself as an Outline.");
p(" ");
p("    File I/O menuitems allow you to save the IFS codes or the Image to d\
isk,");
p("  to load IFS Codes from files saved by IFSLab and by most other IFS prog\
rams,");
p("  and to load any Non-HAM IFF image file into the Image screen or as the");
p("  Outline.");
    break;

  case AUTHOR:
    SetWindowTitles(TextWindow,  "  Author Information", -1L);
p(" ");
p(" ");
p(" ");
p(" ");
p("                       IFS Lab written by Nathan Zeldes");
p(" ");
p(" ");
p("              Copyright (C) 1992 by N. Zeldes. All Rights Reserved. ");
p(" ");
p("           Thanks to C.W. Fox and Bruce Dawson for the File Requester");
p(" ");
p(" ");
p("                                                                       ");
p(" ");
p(" ");
p(" ");

    break;

  case DEMOTEXT:  
    SetWindowTitles(TextWindow,  " A Demonstration of IFS in Action", -1L);

p(" ");
p("   This demo will define and render the Sierpinski Triangle, a well known");
p(" fractal.");
p(" ");
p("   The program will first enter the Outline Editor and draw an Outline of\
 the");
p(" Sierpinski triangle. After a brief delay it will switch to the Collage E\
ditor");
p(" and show how the Outline can be covered by three half-sized copies of it\
self.");
p(" When it has built this Collage, the program will initiate rendering of t\
he");
p(" fractal. Note that this demo intentionally assigns unequal probabilities\
 to");
p(" the three transformations, which causes the uneven 'shading' effect in t\
he");
p(" rendered image.");
p(" ");
p("   To start the demo, click the left mouse button.  Do no more until the"); 
p(" rendering begins. When you've had your fill of the beauty of the emergin\
g");
p(" image, click twice in the Image screen to exit the demo.");

    break;

  case CODES:    /* Display Codes Table */
    SetWindowTitles(TextWindow,  " IFS Code Coefficients", -1L);

    p("    A      B      C      D     E     F     P     Dens   S1    S2\
    R1    R2 ");
    if (N < MAX_N)
      p(" ------ ------ ------ ------ ----- ----- ----- ------- ----- -----\
 ----- ----- ");   /* The underline is omitted to allow display of 20 pieces */

    for(i = 0; i <= N; i++) {    /* Put lines of coefficients in text window */

      sprintf(codelinebuf, " % 6.3lf % 6.3lf % 6.3lf % 6.3lf %5.3lf %5.3lf\
 %5.3lf %7.3lf% 5.3lf %5.3lf % 4.0lf  % 4.0lf", pieceptr[i]->a, pieceptr[i]->b,
       pieceptr[i]->c, pieceptr[i]->d, pieceptr[i]->e, pieceptr[i]->f,
       pieceptr[i]->p, pieceptr[i]->dens, pieceptr[i]->s1, pieceptr[i]->s2,
       pieceptr[i]->r1/0.017453293, pieceptr[i]->r2/0.017453293); 

      CodesText.FrontPen = (i == selpiece) ? 3 : 1; /* Selected piece in red */
      CodesText.IText = (UBYTE*)codelinebuf;
      CodesText.TopEdge = (N < MAX_N) ? 8L * i + 32L : 8L * i + 24L;
      PrintIText(Textrp, &CodesText, 0L, 0L);
    }
    break;

  }  /* End switch */

  PrintIText(Textrp, &RedText, 450L, 190L); /* 'Click Mouse...' text */

  /*** Wait with text displayed, poll IDCMP until you get a SELECTDOWN */
  while (!(message = (struct IntuiMessage *)GetMsg(TextWindow->UserPort))) 
    ;   /* here any event must be a SELECTDOWN due to IDCMP flag definition */
  ReplyMsg(message);
  while (!(message = (struct IntuiMessage *)GetMsg(TextWindow->UserPort))) 
    ;    /* get rid of SELECTUP message following the SELECTDOWN */
  ReplyMsg(message);

  CloseWindow(TextWindow);   /* Close text display and return */
  CloseScreen(TextScreen);
}
/*===========================================================================*/

PutDefOutline()  /* Place default Outline in Outline Bitmap */
{
  ModifyMousePtr(Window, 1);   /* Bring up 'ZZ' pointer */
  SetDrMd(r, JAM1);
  SetAPen(r, 2L);
  SetOPen(r, 2L);
  SetWrMsk(r,0x0002);  /* Write protect all planes except plane 1 */
  SetRast(r, 0L);

  DrawCircle(r, 138L, 93L, 22L);   /* Face outline */
  DrawCircle(r, 138L, 93L, 13L);   /* To become mouth outline */
  SetAPen(r, 0L);
  SetOPen(r, 0L);
  RectFill(r, 124L, 80L, 152L, 100L); /* Blot out top part of mouth circle */
  SetAPen(r, 2L);
  SetOPen(r, 2L);
  DrawCircle(r, 129L, 85L, 3L);   /* Left Eye */
  DrawCircle(r, 147L, 85L, 3L);   /* Right Eye */
 
  exists_outline = 1;
  SetWrMsk(r,0xFFFF);
  ModifyMousePtr(Window, -1);   /* Remove 'ZZ' pointer */
}
/*===========================================================================*/

TransformPiece(source, dest, pc)      /* Apply transformation of Piece */
     /* To image in source bitplane, put result in dest piecemap. */
     /* Return 1 if done OK, return 0 if a pixel overflowed plane boudary. */
     /* Any overflow pixels are not put into the destination piecemap. */
     /* Skips title bar and borders of source bitplane properly. */
     /* Replaces doubles with scaled-up integers for speed. */

UBYTE *source;      /* Pointer to 1st byte in a single Window-sized Bitplane */
UBYTE *dest;        /* Pointer to first byte in target piecemap */
struct piece *pc;   /* pointer to piece structure containing trans' coeffs */
{
  #define LEFTBDRBITS     4  /* Width in BITS of left border of Window */
  #define TOPBDRBYTES   440  /* bytes within Top Border of Window */
  #define RIGHTBDRBYTES   5  /* bytes within right Border (must be integral) */
  #define MAPROWBYTES    35  /* Number of bytes in a net row of piecemap */
  #define MAPROWBYTESPLUS 40 /* Number of bytes in a gross row of piecemap */
  #define PIECEMAPWIDTH  GZZWIDTH+Window->BorderLeft

  UBYTE *srcbyte;      /* address of current byte in source bitplane */
  int bitpos;     /* bit position in byte (leftmost bit = 0) */
  int rowbyte;   /* number of current source byte within row */
  int srcx, srcy, destx, desty;  /* Source and destination pixel X-Y coords */
  long ai, bi, ci, di, ei, fi; /* Scaled integer transformation coefficients */
  long lbdri;   /* Scaled LEFTBDRBITS */
  int overflowflag = 0;

  ModifyMousePtr(Window, 1);   /* Bring up 'ZZ' pointer */
  /* Erase piecemap */
  BltClear((char*)dest, (long)RASSIZE(WIDTH,GZZHEIGHT), 1L);

  /*** Convert coefficients to scaled integers */

  ai = (long)(pc->a * 1000000);
  bi = (long)(pc->b * 1000000);
  ci = (long)(pc->c * 1000000);
  di = (long)(pc->d * 1000000);
  ei = (long)(pc->e * 1000000 * GZZWIDTH);
  fi = (long)(pc->f * 1000000 * GZZWIDTH);
  lbdri = (long)(LEFTBDRBITS * 1000000);

  srcbyte = source + TOPBDRBYTES;  /* Initialize srcbyte to after Top Border */

  for (srcy = 0; srcy < GZZHEIGHT; srcy++) {   /* Loop on source plane rows */
    /* Loop on bytes in source row - up to right border only */
    for (rowbyte = 0; rowbyte < MAPROWBYTES; rowbyte++) {

      if (*srcbyte != 0) {    /* If Source byte not empty */

        for(bitpos = 0; bitpos < 8; bitpos++) { /* loop on bits in byte */
          if ((*srcbyte & (128>>bitpos)) != 0) { /* current src pixel is '1' */

            srcx = rowbyte * 8 + bitpos - LEFTBDRBITS;

            /* do transformation */
            destx = (ai * srcx + bi * srcy + ei + lbdri) / 1000000;
            desty = (ci * srcx + di * srcy + fi) / 1000000;

            if(destx < LEFTBDRBITS || destx >= PIECEMAPWIDTH ||
             desty < 0 || desty >= GZZHEIGHT)     /* Don't write transformed */
              overflowflag = 1;  /* pixel to dest - it is outside  bitplane! */
            else 
              /* write '1' pixel to destination. Expression to left of |=  */
              /* is destination byte address;  128>>bitpos is the bitmask. */
              *( dest + desty*MAPROWBYTESPLUS + destx/8 ) |= 128>>(destx % 8);
          }  /* End if current source pixel is '1' */
        }    /* End loop on bits in byte */
      }      /* End If source byte not empty */
      srcbyte++;
    }        /* End loop on bytes in source row */
    srcbyte += RIGHTBDRBYTES;   /* skip right border */
  }          /* End loop on source rows */
  ModifyMousePtr(Window, -1);   /* Remove 'ZZ' pointer */
  return(!overflowflag);
}
/*===========================================================================*/

SaveIFS()    /* Save IFS coefficients, if any, to a disk file */
{
  FILE *fopen(), *fp;
  char *fnam, *p;
  char *GetFileName();
  int strcmp();
  char *strcat();
  long k;
  
  if (drawmode != VECTOR) {
    ShowError("No IFS to Save!");
    return;
  }
  /*** Open disk file,deleting prev rev if any; if error, exit function */

  fnam = GetFileName("Save to File:");
  if (*fnam == '\0') {
    return;  /* do nothing - user selected CANCEL or no filename */
  }

  k = 0;      /* Set k to 1 if filename ends with .ifs */
  for (p = fnam; *p != '\0'; p++)
    if (strcmp(p, ".ifs") == 0)
      k = 1;
  if (k == 0)      /* Add .ifs to filename if needed */
    strcat(fnam, ".ifs");

  if ((fp = fopen(fnam, "w")) == NULL) {
    ShowError(" Cannot open file!!!");
    return;
  }

  /*** Save the current IFS codes to a disk file */

  fprintf(fp, "    A      B      C      D     E     F     P      Dens   S1\
    S2    R1    R2\n");
  for(i = 0; i <= N; i++)
      fprintf(fp, " % 6.3lf % 6.3lf % 6.3lf % 6.3lf %5.3lf %5.3lf\
 %5.3lf %7.3lf% 5.3lf %5.3lf % 4.0lf  % 4.0lf\n", pieceptr[i]->a,
       pieceptr[i]->b, pieceptr[i]->c, pieceptr[i]->d, pieceptr[i]->e,
       pieceptr[i]->f, pieceptr[i]->p, pieceptr[i]->dens, pieceptr[i]->s1,
       pieceptr[i]->s2, pieceptr[i]->r1/0.017453293,
       pieceptr[i]->r2/0.017453293); 

  fclose(fp);         /* close disk file */
}
/*===========================================================================*/

int LoadIFS(fnam)   /* Load IFS Code variables from disk file */
char *fnam;         /* Returns 1 if done OK, 0 if failed to load */
                    /* Skips lines of wrong form (e.g. text headers) */
                    /* and ignores excess info on a line (e.g. Geom. Coeffs) */
                    /* Calls Optimize() to create a Collage and assure */
{                   /* Attractor will not overflow window */
  FILE *fopen(), *fp;
  int c, eofread, scan;
  char linebuf[161];  /* Input lines will be truncated after 160 chars */
  double mindens;
  long xmin, xmax, ymin, ymax;
           /* GZZ PIXEL coords of boundary rect of old (as-loaded) attractor*/
  int j;

  /*** Open disk file for reading */
  if ((fp = fopen(fnam,"r")) == NULL) {
    ShowError(" Cannot open file!!!");
    return(0);
  }

  ClearIFS();   /* Erase old IFS (if any), set N to -1 */

  /*** Load the IFS codes line by line */

  eofread = 0;
  while (N < MAX_N && !eofread) {  /* Loop on lines in file */

    if ((pieceptr[++N] = AllocPiece()) == NULL) {  /* Allocate Piece */
      N--;         /* if couldn't allocate piece */
      ShowError("Couldn't allocate memory!");
      if (N == -1) {    /* No memory for even one Piece - Quit program */
        ShowError("Re-run with more free memory!");
        CloseAll();
      }
      ShowError("        Aborting Load!         ");
      fclose(fp);
      return(0);
    }

    /*** Read in a single line from file into memory, then sscanf it */
    /* (Don't use fscanf from file because it ignores end of line */
    for (i = 0; i < 160 && (c = getc(fp)) != EOF && c != '\n'; i++)
      linebuf[i] = c;
    linebuf[i] = '\0';
    if (c != EOF && c != '\n')  /* Line has been truncated after 160 chars */
      while ((c = getc(fp)) != '\n' && c != EOF)
        ;                   /* Remove any other stuff on the file line */ 
    if (c == EOF)
      eofread = 1;
   
    /* Now a null-terminated line is in linebuf & the file is positioned at */
    /* start of next file line or past EOF;eofread is set if EOF was reached */

    scan = sscanf(linebuf, "%lf %lf %lf %lf %lf %lf %lf", &pieceptr[N]->a,
            &pieceptr[N]->b, &pieceptr[N]->c, &pieceptr[N]->d, &pieceptr[N]->e,
            &pieceptr[N]->f, &pieceptr[N]->p);

    if (scan == 7) {   /* converted successfully 7 coeffs of one IFS */

      /*** Check that coefficients a - d and p are in legal ranges */
      if (pieceptr[N]->a < -1. || pieceptr[N]->a > 1. || pieceptr[N]->b < -1.||
       pieceptr[N]->b > 1. || pieceptr[N]->c < -1. || pieceptr[N]->c > 1. ||
       pieceptr[N]->d < -1. || pieceptr[N]->d > 1. || 
       pieceptr[N]->p <= 0. || pieceptr[N]->p > 1.) {
        ShowError("Coefficient out of Range!");
        ShowError("      Aborting Load!     ");
        fclose(fp);
        return(0);
      }

      /*** compute the geometrical coeffs from matrix coeffs read from file */
      pieceptr[N]->s1 = sqrt(pieceptr[N]->a*pieceptr[N]->a +
                                            pieceptr[N]->c*pieceptr[N]->c);
      pieceptr[N]->s2 = sqrt(pieceptr[N]->b*pieceptr[N]->b +
                                            pieceptr[N]->d*pieceptr[N]->d);
      pieceptr[N]->r1 = myatan2(pieceptr[N]->c, pieceptr[N]->a);
      pieceptr[N]->r2 = myatan2(-pieceptr[N]->b, pieceptr[N]->d);
      pieceptr[N]->det = fabs(pieceptr[N]->a * pieceptr[N]->d -
                                         pieceptr[N]->b * pieceptr[N]->c);
      if (pieceptr[N]->det == 0.0)
        pieceptr[N]->det = 0.01;
      pieceptr[N]->dens = pieceptr[N]->p / pieceptr[N]->det;

      /*** Compute new Box corners from new coeffs */
      pieceptr[N]->boxo.x = pieceptr[N]->e * GZZWIDTH;
      pieceptr[N]->boxo.y = pieceptr[N]->f * GZZWIDTH; /* sic! */
      pieceptr[N]->boxx.x = (pieceptr[N]->a + pieceptr[N]->e) * GZZWIDTH;
      pieceptr[N]->boxx.y = (pieceptr[N]->c + pieceptr[N]->f) * GZZWIDTH;
      pieceptr[N]->boxy.x = (pieceptr[N]->b * GZZHEIGHT/GZZWIDTH +
         pieceptr[N]->e) * GZZWIDTH;
      pieceptr[N]->boxy.y = (pieceptr[N]->d * GZZHEIGHT/GZZWIDTH +
         pieceptr[N]->f) * GZZWIDTH;
      pieceptr[N]->boxz.x = pieceptr[N]->boxy.x + pieceptr[N]->boxx.x -
         pieceptr[N]->boxo.x;
      pieceptr[N]->boxz.y = pieceptr[N]->boxy.y + pieceptr[N]->boxx.y -
         pieceptr[N]->boxo.y;
    }

    else    /* scanf failed to match 7 doubles on line - skip it! */
      FreePiece(pieceptr[N--]);

  }    /* End while loop on lines */
 
  selpiece = N;

  if (N == MAX_N && !eofread && getc(fp) != EOF)  /* Exited loop at N==MAX_N */
    ShowError("File too long; IFS may be truncated");

  if (N == -1) {   /* Not a single transformation was found! */
    ShowError("No IFS found in File - Aborting Load!");
    fclose(fp);
    return(0);
  }

  /*** If probabilities read from file were cumulative, convert to non-cum */
  if (pieceptr[N]->p == 1.000) {
    for (i = N; i > 0; i--) {
      pieceptr[i]->p = pieceptr[i]->p - pieceptr[i-1]->p;
      pieceptr[i]->dens = pieceptr[i]->p / pieceptr[i]->det;
    }
  }

  /*** Normalize Piece densities so smallest one is 1.000 */
  mindens = 1000000.;
  for (i = 0; i <= N; i++)
    if (pieceptr[i]->dens < mindens)
      mindens = pieceptr[i]->dens;
  for (i = 0; i <= N; i++)
    pieceptr[i]->dens /= mindens;
 
  ComputeProbs(); /* Adjust all probabilities in case rounding errors in */
                  /* file made their sum != 1.000 */

  fclose(fp);         /* close disk file */

  /* Transform loaded IFS to fit in window if it exceeds it, */
  /* and generate Collage */
  Optimize(0);

  return(1);
}
/*===========================================================================*/
int Optimize(mode) /* Creates an Outline from current Attractor, creates    */
int mode;          /* a Collage from it and displays it. If mode==1, also   */
                   /* modifies the IFS so its attractor fills the window.   */
                   /* If mode==0, does this only if current Attractor would */
                   /* overflow the window.                                  */
                   /* Returns 1 if successful and 0 if it failed (because   */
{                  /* not in Collage Editor) and no modifications were done.*/
  long xmin, xmax, ymin, ymax;  /* GZZ PIXEL coords of old boundary rect */
  int px1, py1; /* GZZ PIXEL coords  of Topleft corner of new boundary rect */
  double x0,y0; /* REAL plane coords of Topleft corner of old boundary rect */
  double x1,y1; /* REAL plane coords of Topleft corner of new boundary rect */
  double m;     /* Magnification to apply to attractor so it fills window */
  double attrw, attrh;  /* PIXEL Width & Height of old boundary rect */
  struct piece tmppiece;  /* will be used to hold temporary trans' coeffs */
  int j;

  if (drawmode != VECTOR) {     /* Abort if not in Collage Editor! */
    ShowError("No IFS!");
    return(0);
  }

  ModifyMousePtr(Window, 1);   /* Bring up 'ZZ' pointer */

  SetRast(r, 0L);  /* Erase  Old Outline and Collage */

  FindBoundary(&xmin, &xmax, &ymin, &ymax); /* Identify old boundary rect */

  /*** If mode is 1, or mode is 0 but attractor exceeds window, modify IFS ***/

  if (mode==1 || xmin<0 || xmax >= GZZWIDTH || ymin<0 || ymax >= GZZHEIGHT) {
    /*** Compute required magnification and relocation of attractor */

    attrw = (double)((xmax - xmin) + 1);
    attrh = (double)((ymax - ymin) + 1);

    if ((double)attrw/(double)attrh >= (double)GZZWIDTH/(double)GZZHEIGHT) {
      m = 0.95 * (double)GZZWIDTH / attrw;
      px1 = 0.025 * (double)GZZWIDTH;
      py1 = 0.025 * (double)GZZHEIGHT +
                           (0.95 * (double)GZZHEIGHT - m * attrh) / 2.0;
    }
    else {
      m = 0.95 * (double)GZZHEIGHT / attrh;
      px1 = 0.025 * (double)GZZWIDTH +
                           (0.95 * (double)GZZWIDTH - m * attrw) / 2.0;
      py1 = 0.025 * (double)GZZHEIGHT;
    }
    x0 = (double)xmin / (double)GZZWIDTH;
    y0 = (double)ymin / (double)GZZWIDTH;
    x1 = (double)px1 / (double)GZZWIDTH;
    y1 = (double)py1 / (double)GZZWIDTH;

    /*** Modify all piece coefficients to make new attractor fill window */

    for (i = 0; i <= N; i++) {   /* Loop on pieces */
      pieceptr[i]->e = m * pieceptr[i]->e + 
       (pieceptr[i]->a - 1) * (m * x0 - x1) + pieceptr[i]->b * (m * y0 - y1);
      pieceptr[i]->f = m * pieceptr[i]->f + 
       pieceptr[i]->c * (m * x0 - x1) + (pieceptr[i]->d - 1) * (m * y0 - y1);

      /*** Recompute new Box corners from new coeffs */
      pieceptr[i]->boxo.x = pieceptr[i]->e * GZZWIDTH;
      pieceptr[i]->boxo.y = pieceptr[i]->f * GZZWIDTH; /* sic! */
      pieceptr[i]->boxx.x = (pieceptr[i]->a + pieceptr[i]->e) * GZZWIDTH;
      pieceptr[i]->boxx.y = (pieceptr[i]->c + pieceptr[i]->f) * GZZWIDTH;
      pieceptr[i]->boxy.x = (pieceptr[i]->b * GZZHEIGHT/GZZWIDTH +
         pieceptr[i]->e) * GZZWIDTH;
      pieceptr[i]->boxy.y = (pieceptr[i]->d * GZZHEIGHT/GZZWIDTH +
         pieceptr[i]->f) * GZZWIDTH;
      pieceptr[i]->boxz.x = pieceptr[i]->boxy.x + pieceptr[i]->boxx.x -
         pieceptr[i]->boxo.x;
      pieceptr[i]->boxz.y = pieceptr[i]->boxy.y + pieceptr[i]->boxx.y -
       pieceptr[i]->boxo.y;
    }    /* End loop on pieces */
  }    /* End if mode==1... */

  RenderImage(1);   /* Render new attractor in OUTLINE Bitplane */
  CopyMem((char*)Window->RPort->BitMap->Planes[1], 
              (char*)outlinebufptr, (long)RASSIZE(WIDTH,HEIGHT));
  ModifyMousePtr(Window, -1);   /* Remove 'ZZ' pointer */

  /*** Transform the new Outline (=attractor) into all pieces' piecemaps */
  /*** Draw each piece into the collage as you go */

  selpiece = N;
  for (i = 0; i <= N-1; i++) {  /* Loop on deselected pieces */

    TransformPiece(outlinebufptr, pieceptr[i]->piecemap, pieceptr[i]);

    /* Blit this piece from its piecemap to plane 0 */
    SetWrMsk(r,0x0001);  /* Write protect all planes except plane 0 */
    SetAPen(r, 1L);
    BltTemplate((char*)pieceptr[i]->piecemap,(long)Window->BorderLeft,
        (long)WIDTH/8, r, 0L, 0L, (long)GZZWIDTH, (long)GZZHEIGHT);
  }

  /* Now transform Selected Piece & Blit it from piecemap to plane 2 */
  TransformPiece(outlinebufptr, pieceptr[i]->piecemap, pieceptr[i]);
  SetWrMsk(r,0x0004);  /* Write protect all planes except plane 2 */
  SetAPen(r, 4L);
  BltTemplate((char*)pieceptr[selpiece]->piecemap,(long)Window->BorderLeft,
   (long)WIDTH/8, r, 0L, 0L, (long)GZZWIDTH, (long)GZZHEIGHT);
  
  DrawBox();  /* Draw Vector Box of Selected Piece */
  /* Note that Rastport's WrMask is restored within DrawBox() */

  return(1);
}
/*===========================================================================*/

RenderImage(z) /* If z == 0, Render the Attractor in Image Screen at current */
int z;   /*  resolution & depth settings; stop and return on click in Image */
         /* screen. If z == 1, render it in Outline bitplane. */
         /* Probabilities and coefficients are scaled to integers so can use */
         /* faster integer math */
{
  struct Message * GetMsg();
  struct IntuiMessage *message;
  int randprob;   /* Random probability value */
  double sum;        /* cum probability */
  int k;
  long unitwidth, unitheight;
    /* width, height in image screen pixels of unit square in the plane */
  long iter; /* Iteration counter */
  struct RastPort *imagerp;  /* Pointer to rastport of Image window */
  long pix;  /* Color (Pen #) of a pixel */
  long ReadPixel();
  long ai[MAX_N+1], bi[MAX_N+1], ci[MAX_N+1],
       di[MAX_N+1], ei[MAX_N+1], fi[MAX_N+1]; /* Scaled integer coefficients */
  long xi, yi, tmpxi; /* Scaled integer pixel coordinates */
  int cumprobi[MAX_N+2];   /* Scaled integer Cum Probabilities */
     /* cumprobi array has MAX_N+2 elements because using MAX_N+1 caused a */
     /* Crash for unclear reasons ?!?! */

    /*** Prepare stuff for iteration loop ***/

  if (z == 0) {     /* Rendering is in Image Screen */
    /*** Open Image Window in its screen (Closing any previous image screen) */
    if (exists_image)
      CloseImageScreen();
    OpenImageScreen(renderwidth, renderheight, renderdepth);
                              /* OpenImageScreen() also opens ImageWindow */
    imagerp = ImageWindow->RPort; 
    SetAPen(imagerp,2L); /* Will be modified in loop for Greyscale rendering */
    SetDrMd(imagerp, JAM1);

    SetWindowTitles(ImageWindow, -1L,
                               (char *)" Click in screen to stop rendering");

    unitwidth = GZZWIDTH*(renderwidth/320); /* GZZWIDTH is of Collage window!*/
    unitheight = GZZWIDTH * (renderheight/200);

  }
  else {   /* Rendering in Outline plane */
    SetWrMsk(r, 0x0002);  /* Write protect all planes except plane 1 */
    SetAPen(r, 2L);
    SetDrMd(r, JAM1);
    
    unitwidth = unitheight = GZZWIDTH;
  }

  srand((unsigned int)(time(NULL)%10000L));  /* seed random gen from sys time*/
  
  sum = 0;
  for (k = 0; k <= N; k++) {          /* Scale Coefficients as longs */
    ai[k] = (long)(pieceptr[k]->a * 1000000);
    bi[k] = (long)(pieceptr[k]->b * 1000000);
    ci[k] = (long)(pieceptr[k]->c * 1000000);
    di[k] = (long)(pieceptr[k]->d * 1000000);
    ei[k] = (long)(pieceptr[k]->e * 1000000 * unitwidth);
    fi[k] = (long)(pieceptr[k]->f * 1000000 * unitwidth);

    sum = sum + pieceptr[k]->p;  /* Compute scaled Cum Probabilities */
    cumprobi[k] = (int)(sum * (double)RAND_MAX);
  }

  xi = unitwidth / 3;   /* Initial coordinates */
  yi = unitwidth / 3;

    /*** Iterate until user clicks in Image window or until 1000 points ***/
    /*** (depending on z) ***/

  for (iter = 0; 1; iter++) {         /* endless loop */

    /*** Select a Piece at random */
    randprob = rand();
                 /* rand() returns a random # (between 0 - RAND_MAX) */
    for (k = 0; cumprobi[k] < randprob; k++) {
      /* the following IF is to trap problem case when randprob = RAND_MAX */
      /* exactly and cum probability is RAND_MAX minus slight delta due to */
      /* rounding errors */
      if (k > N) {
        k--;
        break;
      }
    }

    /* Apply transformation #k to previous (x,y) */
    tmpxi = (ai[k] * xi + bi[k] * yi + ei[k]) / 1000000;
    yi = (ci[k] * xi + di[k] * yi + fi[k]) / 1000000;
    xi = tmpxi;

    if (iter > 19) {    /* Render the pixel (except 1st 20 points) */

      if (z == 0) {   /*** draw in Image Screen, quit on mouseclick in it */

        if (renderdepth > 1) {  /* Grayscale rendering */
          pix = ReadPixel(imagerp, xi, yi * unitheight / unitwidth);
          if (++pix < (1<<renderdepth)) {
            SetAPen(imagerp, pix);
            WritePixel(imagerp, xi, yi * unitheight / unitwidth);
          }
        }
        else    /* B&W rendering - A Pen was set outside main loop */
          WritePixel(imagerp, xi, yi * unitheight / unitwidth);

        /*** Quit rendering loop if user clicked in ImageWindow */
        if (message = (struct IntuiMessage *)GetMsg(ImageWindow->UserPort)) {
          ReplyMsg(message);
          if (message->Code == SELECTUP) {
            SetWindowTitles(ImageWindow,
                     (char *)" Click in screen to push it back", -1L);
            ShowTitle(ImageScreen, FALSE);  /* Hide screen depth gadgets! */
            break;  /* From for iter */
          }
        }
      }

      else {     /* z == 1, draw in outline plane, quit after 1000 iters */

        WritePixel(r, xi, yi);
        if (iter > 999)
          break;  /* From for iter */
      }
    }  /* End if iter > 19 */
  }  /* End for iter */

  if (z == 0) {  /*** Wait for user click in Image screen to push it to back */
    while (!(message = (struct IntuiMessage*)GetMsg(ImageWindow->UserPort)))
      ;       /* Wait for a message */
    ReplyMsg(message);
    SetWindowTitles(ImageWindow,
               (char *)" ", (char *)" Click in screen to push it back");
    ShowTitle(ImageScreen, TRUE);  /* OK to have depth gadgets now */
    ScreenToBack(ImageScreen);
    MoveScreen(ImageScreen, 0L, (long)(-1*ImageScreen->TopEdge));
    ScreenToFront(Screen);
    ActivateWindow(Window);
  }
  else  /* z == 1, unprotect Outline window planes */
    SetWrMsk(r, 0xFFFF);
}
/*===========================================================================*/

SaveILBM()    /* Saves the current Image to an IFF disk file */
{
  LONG file;  /* File handle [we use AmigaDOS Open(), not Manx open()] */
  UBYTE *savebuffer;
  char *fnam;

  if (exists_image == 0) {
    ShowError("No Image to save!");
    return;
  }

  fnam = GetFileName("Save to File:");  /* Get the filename */
  if (*fnam == '\0') /* User selected CANCEL or gave no filename */
    return;

  if ((file = Open(fnam, MODE_NEWFILE)) == NULL) {
    ShowError("Can't open file!");
    return;
  }
  if ((savebuffer = (UBYTE *)AllocMem(8000L, MEMF_CHIP|MEMF_PUBLIC)) == NULL) {
    ShowError("Can't allocate Save Buffer Memory!");
    return;
  }

  /* Use EA IFF routine PutPict() to save the image to file */
  ModifyMousePtr(Window, 1);   /* Bring up 'ZZ' pointer */
  SetWindowTitles(ImageWindow,-1L,
                              (char *)"");
  ShowTitle(ImageScreen, FALSE);  /* Hide screen depth gadgets! */
  ActivateWindow(ImageWindow); /* To make title change take effect */
  ActivateWindow(Window);
  PutPict(file, ImageWindow->RPort->BitMap, ImageScreen->Width, ImageScreen->Height,
   (WORD *)(&(ImageScreen->ViewPort))->ColorMap->ColorTable, savebuffer,8000L);
  SetWindowTitles(ImageWindow,-1L,
                              (char *)" Click in screen to push it back");
  ShowTitle(ImageScreen, TRUE);  /* OK to have depth gadgets now */
  ActivateWindow(ImageWindow); /* To make title change take effect */
  ActivateWindow(Window);
  FreeMem(savebuffer, 8000L);
  Close(file);
  ModifyMousePtr(Window, -1);   /* Remove 'ZZ' pointer */
}
/*===========================================================================*/

struct piece *AllocPiece()     /* Allocate memory for a piece structure and */
{                    /* its piecemap. Return pointer to the piece structure */
                     /* Or NULL if can't allocate memory */
  struct piece *tmppieceptr;
  UBYTE *mapptr;

  /* Allocate memory for piece structure */
  if ((tmppieceptr = 
      (struct piece *)AllocMem((long)sizeof(struct piece), NULL)) == NULL) {
    ShowError("Can't allocate Memory!");
    return(NULL);
  }
  /* Allocate piecemap CHIP RAM */
  if ((mapptr = (UBYTE*)AllocRaster((long)WIDTH, (long)GZZHEIGHT)) == NULL) {
    FreeMem((void*)tmppieceptr, (long)sizeof(struct piece));
    ShowError("Can't allocate Memory!");
    return(NULL);
  }
  /* Clear piecemap */
  BltClear((char*)mapptr, (long)RASSIZE(WIDTH,GZZHEIGHT), 1L);
  tmppieceptr->piecemap = mapptr;   /* Link bitplane to piece structure */
  return(tmppieceptr);
}
/*===========================================================================*/

FreePiece(piecepntr)          /* Frees the memory used for a piece structure */
struct piece *piecepntr;      /* and its piecemap. */
{
  /* Deallocate CHIP RAM of piecemap bitplane */
  FreeRaster((void*)(piecepntr->piecemap), (long)WIDTH, (long)GZZHEIGHT);

  /* Deallocate memory of piece structure */
  FreeMem((void*)piecepntr, (long)sizeof(struct piece));
}
/*===========================================================================*/

p(text)      /* screen text output routine, similar to BASIC's PRINT */
             /* puts text in consecutive lines in rastport pointed at by the */
             /* external pointer Textrp. Line position is maintained between */
             /* calls, but is reset to top if text==NULL. No overflow check */
char *text;  /* is done -- text should fit in rastport area! */
{
  static struct IntuiText Textline = {
    1, 0, JAM1,        /* FrontPen, BackPen, DrawMode */
    0, NULL,            /* LeftEdge, TopEdge */
    NULL,              /* ITextFont */
    NULL, NULL            /* IText, NextText */
  };
  static int line = 0; /* vert. position of line to be printed next (0 - 24) */

  if (text == NULL)   /* reset to top line of rastport */
    line = 0;
  else {
    Textline.TopEdge = 8 * line;   /* Put text into line on screen */
    Textline.IText = (UBYTE*)text;
    PrintIText(Textrp, &Textline, 0L, 0L);
    line++;  /* increment line for next call */
  }
}
/*===========================================================================*/

FindBoundary(xminp, xmaxp, yminp, ymaxp)     /* Iterates IFS without drawing */
              /* and returns PIXEL coords of boundary rectangle of attractor */
              /* in variables pointed at by its arguments */ 
              /* Uses float math, not scaled int, to avoid overflow on very */
              /* large coeffs of loaded IFS files from other programs */
long *xminp, *xmaxp, *yminp, *ymaxp;   /* Pointers! */
{
  int randprob;   /* Random probability value */
  double sum;        /* cum probability */
  int k;
  long iter; /* Iteration counter */
  double xd, yd, tmpxd; /* Real pixel coordinates */
  double xdmin, ydmin, xdmax, ydmax; /* Real boundary rect coords */
  int cumprobi[MAX_N+2];   /* Scaled integer Cum Probabilities */
    /* cumprobi array has MAX_N+2 elements because using MAX_N+1 caused a */
    /* Crash for unclear reasons in RenderImage() ?!?! */

  /*** Prepare stuff for iteration loop ***/

  srand((unsigned int)(time(NULL)%10000L));  /* seed random gen from sys time*/

  sum = 0;
  for (k = 0; k <= N; k++) {          /* Scale Coefficients as longs */
    sum = sum + pieceptr[k]->p;  /* Compute scaled Cum Probabilities */
    cumprobi[k] = (int)(sum * (double)RAND_MAX);
  }

  xdmin = 1000000000.;     /* Initialize boundary rectangle coords */
  xdmax = -1000000000.;
  ydmin = 1000000000.;
  ydmax = -1000000000.;

  xd = 0.3;   /* Initial coordinates */
  yd = 0.3;

  /*** Iterate 1000 times without drawing ***/

  for (iter = 0; iter < 1000; iter++) {

    /*** Select a Piece at random */
    randprob = rand();
                 /* rand() returns a random # (between 0 - RAND_MAX) */
    for (k = 0; cumprobi[k] < randprob; k++) {
      /* the following IF is to trap problem case when randprob = RAND_MAX */
      /* exactly and cum probability is RAND_MAX minus slight delta due to */
      /* rounding errors */
      if (k > N) {
        k--;
        break;
      }
    }

    /* Apply transformation #k to previous (x,y) */
    tmpxd = pieceptr[k]->a * xd + pieceptr[k]->b * yd + pieceptr[k]->e;
    yd = pieceptr[k]->c * xd + pieceptr[k]->d * yd + pieceptr[k]->f;
    xd = tmpxd;

    if (iter > 19) {    /* Adjust rectangle  (except 1st 20 points) */
      if (xd < xdmin)
        xdmin = xd;
      if (xd > xdmax)
        xdmax = xd;
      if (yd < ydmin)
        ydmin = yd;
      if (yd > ydmax)
        ydmax = yd;
    }

  }  /* End for iter */

  /***  Convert real coords to pixel coords for returned values */

  *xminp = (long)(xdmin * GZZWIDTH);
  *xmaxp = (long)(xdmax * GZZWIDTH);
  *yminp = (long)(ydmin * GZZWIDTH);
  *ymaxp = (long)(ydmax * GZZWIDTH);
}
/*===========================================================================*/
