 /* areapolydraw.c
  *
  *   This file contains the functions Alloc/FreeAreaInfo() and Alloc/FreeTmpRas()
  *   to initialize and free TmpRas and AreaInfo structures, as well as the
  *   function AreaPolyDraw(), and AddVertex() and KillVertex().
  *
  *   W.G.J. Langeveld, 14 July 1988
  *
  *   AUTHOR/DATE:  W.G.J. Langeveld, July 1988.
  *   ============
  *
  *	CURRENT VERSION:
  *
  *	This version has been converted to SAS C 6.5 format. It has been modified
  *	for modern definition sequences for ANSI compilation. This no longer works
  *	with OS versions prior to 2.04.
  *
  * Latest change is to fix so it compiles with 3.9 NDK.
  *
  *   AUTHOR/DATE:  Joanne Dow, jdow@bix.com, June 1998.
  *   AUTHOR/DATE:  Joanne Dow, jdow@bix.com, 23 Apr 2002.
  *   ============
  *
  **/

#include <functions.h>
#include "ralprotos.h"
#include <exec/types.h>
#include <exec/exec.h>
#include <intuition/intuition.h>
#include <graphics/gfxmacros.h>
#include <devices/timer.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "areapolydraw.h"
#include "areapatterns.h"

static int AllocAreaInfo(struct RastPort *, int);
static int FreeAreaInfo(struct RastPort *);
static int FindAreaSize(struct vtx *, int *, int *, int *, int *);
static int setdither(unsigned int *, int);
static int bm(int *, int);
static struct vtx *AreaPolyClip(struct vtx *, int, int, int, int);
static struct vtx *SHClip(struct vtx *, int, int);
static struct vtx *addvtx(struct vtx *, struct vtx *);
static int inside(struct vtx *, int, int);
static struct vtx *addintersect(struct vtx *, struct vtx *,
struct vtx *, int, int);

extern struct IntuitionBase *IntuitionBase;
extern struct GfxBase *GfxBase;

#define AISIZE ((long) sizeof(struct AreaInfo))
#define TRSIZE ((long) sizeof(struct TmpRas))
	
#define min(a, b) ( ((a) > (b)) ? (b) : (a) )
	
static int bm(), setdither(), FindAreaSize();
static struct vtx *AreaPolyClip();
	
#define BD_LEFT   1
#define BD_RIGHT  2
#define BD_TOP    3
#define BD_BOTTOM 4
	
/**
 *
 *    NAME
 *
 * AllocAreaInfo()
 * ===============
 *
 *    SYNOPSIS
 *
 * AllocAreaInfo(rp, nverts)
 * FreeAreaInfo(rp)
 *
 *    FUNCTION
 *
 * Function to allocate and initialize and free an AreaInfo structure
 * for Area functions.
 *
 *    INPUTS
 *
 * struct RastPort *rp Rastport to be initialized
 * int nverts  Number of vertices allowed
 *
 *    RESULT
 *
 * The fields in Rastport rp are initialized.
 *
 *    ADDITIONAL CONSIDERATIONS
 *
 *    BUGS
 *
 * None known.
 *
 *    AUTHOR
 *
 * W.G.J. Langeveld, 8 November1988
 *
**/
static int AllocAreaInfo( struct RastPort *rp, int nverts )
{
	struct AreaInfo *ai = NULL;
	UBYTE *aidata = NULL;
	
	if (ai = rp->AreaInfo) 
	{
		if (ai->MaxCount >= nverts)
			return(1);
	}
	
	ai = (struct AreaInfo *) AllocMem(AISIZE, MEMF_CLEAR);
	if (ai == NULL)
		goto cleanup;
	
	aidata = (UBYTE *) AllocMem((long) nverts * 5L, MEMF_CLEAR);
	if (aidata == NULL)
		goto cleanup;
	
	InitArea(ai, (WORD *) aidata, (long) nverts);
	
	rp->AreaInfo = ai;
	
	return(2);
	
	cleanup :
	if (aidata)
		FreeMem(aidata, (long) nverts * 5L);
	if (ai)
		FreeMem(ai,     AISIZE);
	
	return(0);
}

static int FreeAreaInfo( struct RastPort *rp )
{
	struct AreaInfo *ai;
	UBYTE *aidata;
	
	if (ai = rp->AreaInfo) 
	{
		if (aidata = (UBYTE *) ai->VctrTbl) 
		{
			FreeMem(aidata, (long) ai->MaxCount * 5L);
		}
		FreeMem(ai, AISIZE);
		rp->AreaInfo = NULL;
	}
	
	return(0);
}


/**
 *
 *    NAME
 *
 * AllocTmpRas()
 * =============
 *
 *    SYNOPSIS
 *
 * AllocTmpRas(rp, width, height)
 *
 *    FUNCTION
 *
 * Function to allocate and initialize and free a TmpRas structure
 * for Area functions.
 *
 *    INPUTS
 *
 * struct RastPort *rp Rastport to be initialized
 * int width, height Size of TmpRas needed
 *
 *    RESULT
 *
 * The fields in Rastport rp are initialized.
 *
 *    ADDITIONAL CONSIDERATIONS
 *
 *    BUGS
 *
 * None known.
 *
 *    AUTHOR
 *
 * W.G.J. Langeveld, 24 July 1988
 *
**/
long AllocTmpRas( struct RastPort *rp, int width, int height)
{
	struct TmpRas *tr = NULL;
	UBYTE *trdata = NULL;
	
	if (tr = rp->TmpRas) 
	{
		if (tr->Size >= (long) RASSIZE((long) width, (long) height))
			return(1);
	}
	
	tr = (struct TmpRas *) AllocMem (TRSIZE, MEMF_CLEAR);
	if (tr == NULL)
		goto cleanup;
	
	trdata = (UBYTE *) AllocRaster((long) width, (long) height);
	if (trdata == NULL)
		goto cleanup;
	
	InitTmpRas(tr, trdata, (long) RASSIZE((long) width, (long) height) );
	
	rp->TmpRas = tr;
	
	return(2);
	
	cleanup :
	if (trdata)
		FreeRaster(trdata, (long) width, (long) height);
	if (tr)
		FreeMem(tr,     TRSIZE);
	
	return(0);
}

long FreeTmpRas( struct RastPort *rp )
{
	struct TmpRas *tr;
	
	if (tr = rp->TmpRas) 
	{
		FreeMem(tr->RasPtr, tr->Size);
		FreeMem(tr, TRSIZE);
		rp->TmpRas = NULL;
	}
	
	return(0);
}

/**
 *
 *    NAME
 *
 * AreaPolyDraw()
 * ==============
 *
 *    SYNOPSIS
 *
 * AreaPolyDraw(rp, vertex, ppen, xoff, yoff)
 *
 *    FUNCTION
 *
 * Draws a filled n-vertex polygon according to the outline in linked
 * list of vertices. Performs pre-clipping, allows various textures and
 * 4-pixel dithering patterns.
 *
 *    INPUTS
 *
 * w  Pointer to a window to receive the imagery.
 *       vertex  Ptr to linked list of vtx structures
 * ppen  Pattern pen. 0->15: use APen 0 through 15.
 *                If texture flag set, use one of 16 textures.
 *                If dither flag is set use one of 125 dithers.
 *                If outline flag is set, use outline.
 * xoff  X offset for polygon
 * yoff  Y offset for polygon
 *
 *    RESULT
 *
 * Zero on failure, non-zero otherwise.
 *
 *    ADDITIONAL CONSIDERATIONS
 *
 * This routine allocates its own AreaInfo and TmpRas structures, and
 * frees them as well. If the RastPort already had these, they are saved
 * and restored at the end.
 * The routine preclips all AreaMoves and AreaDraws so they won't
 *       require a TmpRas larger than a single full bitplane the size of
 *       the window. Window borders are taken into account.
 *
 *    BUGS
 *
 * None known.
 *
 *    AUTHOR
 *
 * W.G.J. Langeveld, 24 July 1988
 *          revised: November 1989.
 *
**/
AreaPolyDraw( struct Window *w, struct vtx *v, int ppen, int xoff, int yoff )
{
	register int n;
	register struct vtx *tmp;
	register int pow, clipped = 0;
	int resai = 0, restr = 0, res = 0, notmoved, type;
	struct RastPort *rp;
	struct RastPort tmprp;
	int minx, maxx, miny, maxy;
	int clipxmin, clipymin, clipxmax, clipymax;
	unsigned int colpat[64];
	
	if (w == NULL)
		return(0);
	rp = w->RPort;
	
	if (v == NULL)
		return(0);
	if (ppen & APD_NOFILL)
		return(0);
	/*
	 *   See if we need clipping
	 */
	clipxmin =             w->BorderLeft;
	clipymin =             w->BorderTop;
	clipxmax = w->Width  - w->BorderRight - 1;
	clipymax = w->Height - w->BorderBottom - 1;
	
	n = FindAreaSize(v, &minx, &miny, &maxx, &maxy);
	/*
	 *   If so, clip the vertexlist, compute new size and set flag.
	 */
	if ((minx < clipxmin) || (miny < clipymin) ||
	(maxx > clipxmax) || (maxy > clipymax)  ) 
	{
		v = AreaPolyClip(v, clipxmin, clipymin, clipxmax, clipymax);
		n = FindAreaSize(v, &minx, &miny, &maxx, &maxy);
		clipped = 1;
	}
	/*
	 *   Check for empty set
	 */
	if (v == NULL)
		return(0);
	if (n == 0)
		goto cleanup;
	/*
	 *   Make a copy of the rastport
	 */
	movmem(rp, &tmprp, sizeof(struct RastPort));
	/*
	 *   Add an outline if requested
	 */
	if (ppen & APD_OUTLINE) 
	{
		SetOPen(&tmprp, (long) tmprp.FgPen);
	}
	else
	{
		BNDRYOFF(&tmprp);
	}
	/*
	 *   Set APen
	 */
	if ((ppen & 0xFF) < 16)
		SetAPen(&tmprp, (long) (ppen & 0xFF));
	/*
	 *   If object still too large, just draw it, don't bother filling
	 */
	if ((minx < clipxmin) || (miny < clipymin) ||
	(maxx > clipxmax) || (maxy > clipymax)  ) 
	{
		tmp = v;
		notmoved = 1;
		while (tmp) 
		{
			type = tmp->type;
			if (notmoved && (type == AREADRAW))
				type = AREAMOVE;
			switch (type) 
			{
			case AREAMOVE :
				Move(&tmprp, (long) (tmp->x + xoff), (long) (tmp->y + yoff));
				notmoved = 0;
				break;
			case AREADRAW :
				Draw(&tmprp, (long) (tmp->x + xoff), (long) (tmp->y + yoff) );
				break;
			case AREACIRCLE :
			case AREAELLIPSE :
				DrawEllipse(&tmprp, (long) (tmp->x + xoff), (long) (tmp->y + yoff),
				(long) tmp->a         , (long) tmp->b );
				break;
			}
			tmp = tmp->next;
		}
		res = 1;
	}
	else
	{
		/*
		 *   Allocate new AreaInfo/TmpRas (resxx = 2) or use the ones we have (resxx = 1).
		 */
		resai = AllocAreaInfo(&tmprp, n + 10);
		restr = AllocTmpRas(&tmprp, maxx - minx + 32, maxy - miny + 2);
		/*
		 *   AreaMove, AreaDraw, AreaCircle, AreaEllipse.
		 */
		if (resai && restr) 
		{
			tmp = v;
			notmoved = 1;
			while (tmp) 
			{
				type = tmp->type;
				if (notmoved && (type == AREADRAW))
					type = AREAMOVE;
				switch (type) 
				{
				case AREAMOVE :
					AreaMove(&tmprp, (long) (tmp->x + xoff),
					(long) (tmp->y + yoff));
					notmoved = 0;
					break;
				case AREADRAW :
					AreaDraw(&tmprp, (long) (tmp->x + xoff),
					(long) (tmp->y + yoff) );
					break;
				case AREACIRCLE :
				case AREAELLIPSE :
					AreaEllipse(&tmprp, (long) (tmp->x + xoff),
					(long) (tmp->y + yoff),
					(long) tmp->a         ,
					(long) tmp->b );
					break;
				}
				tmp = tmp->next;
			}
			/*
			 *   Set up an areafill pattern
			 */
			setmem(colpat, 128, 0);
			/*
			 *   Copy either a texture...
			 */
			if (ppen & APD_TEXTURE) 
			{
				pow = arpt[ppen & 0xF].power;
				movmem(arpt[ppen & 0xF].data, colpat,
				(pow < 0) ? (1 << (1 - pow)) * 3 : 1 << (pow + 1));
				
				SetAfPt(&tmprp, (unsigned short *) colpat, pow); // Stop a complaint
				if (pow < 0)
					SetAPen(&tmprp, 255L);
				else
					SetAPen(&tmprp, 1L);
				SetDrMd(&tmprp, JAM2);
			}
			/*
			 *   ...or a dithered color
			 */
			else
			if (ppen & APD_DITHER) 
			{
				setdither(colpat, ppen & 0xFF);
				
				SetAfPt(&tmprp, (unsigned short *) colpat, -1);
				SetAPen(&tmprp, 255L);
				SetDrMd(&tmprp, JAM2);
			}
			/*
			 *   Do the blit. Make sure the blit finishes before deallocating.
			 */
			AreaEnd(&tmprp);
			WaitBlit();
			
			res = 1;
		}
		/*
		 *   Free allocated resources.
		 */
		if (resai == 2)
			FreeAreaInfo(&tmprp);
		if (restr == 2)
			FreeTmpRas(&tmprp);
	}
	/*
	 *   Deallocate our local copy of the list
	 */
	
	cleanup:
	
	if (clipped)
		KillVertex(v);
	
	return(res);
}


/**
 *
 *   Find number of vertices and horizontal and vertical extent.
 *
**/
static int FindAreaSize(struct vtx *v, int *minx, int *miny, int *maxx, int *maxy )
{
	int n = 0, t;
	
	*minx = *miny = 20000;
	*maxx = *maxy = 0;
	
	while (v) 
	{
		switch (v->type) 
		{
		case AREAMOVE :
			n++;
		case AREADRAW :
			n++;
			if ((t = v->x) < *minx)
				*minx = t;
			if (t > *maxx)
				*maxx = t;
			if ((t = v->y) < *miny)
				*miny = t;
			if (t > *maxy)
				*maxy = t;
			break;
		case AREACIRCLE :
			v->b = v->a;
		case AREAELLIPSE :
			n += 2;
			if ((t = v->x - v->a) < *minx)
				*minx = t;
			if ((t = v->x + v->a) > *maxx)
				*maxx = t;
			if ((t = v->y - v->b) < *miny)
				*miny = t;
			if ((t = v->y + v->b) > *maxy)
				*maxy = t;
			break;
		}
		v = v->next;
	}
	return(n);
}

/**
 *
 *   Allocate a new vtx structure, and add to list.
 *
**/
struct vtx *AddVertex( struct vtx *v )
{
	struct vtx *t, *m;
	
	t = v;	// Stop compiler squawk placing it here.
	if (v) 
	{
//		t = v;
		while (t->next)
			t = t->next;
	}
	
	m = (struct vtx *) AllocMem((long) sizeof(struct vtx), MEMF_CLEAR);
	
	if (v)
		t->next = m;
	
	return(m);
}

/**
 *
 *   Delete vtx structure list.
 *
**/
struct vtx *KillVertex( struct vtx *v )
{
	struct vtx *t;
	
	while (v) 
	{
		t = v->next;
		FreeMem(v, (long) sizeof(struct vtx));
		v = t;
	}
	
	return(NULL);
}



/**
 *
 *   This function calculates a dithering pattern for color n,
 *   where n is a number between 0 and 124 (inclusive) according to
 *
 *      n = r * 25 + g * 5 + b,
 *
 *   with r, g, and b between 0 and 4 (inclusive).
 *
**/
static unsigned int bits[16][2] = 
{
	{0x0000, 0x0000
}
,
{
	0xaaaa, 0x0000
}
,
{
	0x5555, 0x0000
}
,
{
	0xffff, 0x0000
}
,
{
	0x0000, 0xaaaa
}
,
{
	0xaaaa, 0xaaaa
}
,
{
	0x5555, 0xaaaa
}
,
{
	0xffff, 0xaaaa
}
,
{
	0x0000, 0x5555
}
,
{
	0xaaaa, 0x5555
}
,
{
	0x5555, 0x5555
}
,
{
	0xffff, 0x5555
}
,
{
	0x0000, 0xffff
}
,
{
	0xaaaa, 0xffff
}
,
{
	0x5555, 0xffff
}
,
{
	0xffff, 0xffff
}
};

static int setdither( unsigned int ptdata[], int n )
{
	int r, g, b;
	int l, m, nblack, nwhite, nmagenta, ncyan, nyellow;
	static int indices[4];
	static int map[4] = 
	{
		0, 3, 1, 2 
	};
	
	/*
	 *   Argument tests
	 */
	if (ptdata == 0L)
		return;
	if (n < 0)
		return;
	
	n = (n & 0x7f);
	if (n > 124)
		n = 124;
	/*
	 *   Extract r, g, and b
	 */
	r = n / 25;
	b = n - r * 25;
	g = b / 5;
	b = b - g * 5;
	/*
	 *   Initialize number of black pixels in a square
	 */
	nblack = 4;
	/*
	 *   Number of white pixels...
	 */
	if (nwhite = min(min(r, g), b)) 
	{
		r -= nwhite;
		g -= nwhite;
		b -= nwhite;
		nblack -= nwhite;
	}
	/*
	 *   ...and cyan, magenta or yellow pixels
	 */
	ncyan    = min(g, b);
	nmagenta = min(r, b);
	nyellow  = min(r, g);
	
	if (ncyan) 
	{
		g -= ncyan;
		b -= ncyan;
		nblack -= ncyan;
	}
	else
	if (nmagenta) 
	{
		r -= nmagenta;
		b -= nmagenta;
		nblack -= nmagenta;
	}
	else
	if (nyellow) 
	{
		r -= nyellow;
		g -= nyellow;
		nblack -= nyellow;
	}
	/*
	 *   ...and red green and blue pixels
	 */
	if      (r)
		nblack -= r;
	else
	if (g)
		nblack -= g;
	else
	if (b)
		nblack -= b;
	/*
	 *   Set up the color indices for the four pixels
	 */
	m = 0;
	for (l = 0; l < nblack;   l++, m++)
		indices[map[m]] = 0;
	for (l = 0; l < nwhite;   l++, m++)
		indices[map[m]] = 1;
	for (l = 0; l < ncyan;    l++, m++)
		indices[map[m]] = 5;
	for (l = 0; l < nmagenta; l++, m++)
		indices[map[m]] = 6;
	for (l = 0; l < nyellow;  l++, m++)
		indices[map[m]] = 7;
	for (l = 0; l < r;        l++, m++)
		indices[map[m]] = 2;
	for (l = 0; l < g;        l++, m++)
		indices[map[m]] = 3;
	for (l = 0; l < b;        l++, m++)
		indices[map[m]] = 4;
	/*
	 *   Convert to bitmaps and return in ptdata.
	 */
	movmem(bits[bm(indices, 0)], &ptdata[0], 4);
	movmem(bits[bm(indices, 1)], &ptdata[2], 4);
	movmem(bits[bm(indices, 2)], &ptdata[4], 4);
	
	return;
}

/**
 *
 *   Extract bitmap n from quadruple s
 *
**/
static int bm( int s[], int n )
{
	int index = 0;
	int mask = 1 << n;
	
	
	index = (( ((s[3] & mask) << 3) +
	((s[2] & mask) << 2) +
	((s[1] & mask) << 1) +
	((s[0] & mask)     )   ) >> n) & 0xf ;
	
	return(index);
}

/**
 *
 *   Clip a vertex list against a rectangle
 *
**/
static struct vtx *AreaPolyClip( struct vtx *vi, int x1, int y1, int x2, int y2 )
{
	struct vtx *vo = NULL;
	
	vo = SHClip(vi, BD_LEFT, x1);
	/*
	 *   Be careful not to deallocate the input list!
	 */
	vi = vo;
	
	vo = SHClip(vi, BD_RIGHT, x2);
	KillVertex(vi);
	vi = vo;
	
	vo = SHClip(vi, BD_TOP, y1);
	KillVertex(vi);
	vi = vo;
	
	vo = SHClip(vi, BD_BOTTOM, y2);
	KillVertex(vi);
	
	return(vo);
}

/**
 *
 *   Polygon clipping against a single boundary
 *   Sutherland-Hodgman, Foley and Van dam page 454
 *   Modified to do just rectangular clips.
 *
**/
static struct vtx *SHClip( struct vtx *vi, int bordertype, int bordervalue )
{
	struct vtx *p, *s, *vo, *t, *u;
	int first = 1;
	/*
	 *   Find last vertex s
	 */
	s = NULL;
	
	t = vi;
	while (t) 
	{
		if (t->type == AREADRAW)
			s = t;
		t = t->next;
	}
	/*
	 *   If there is no last vertex, there is nothing to do
	 */
	if (s == NULL)
		return(NULL);
	/*
	 *   Set up output vertex array
	 */
	vo = AddVertex(NULL);
	if (vo == NULL)
		return(NULL);
	/*
	 *   Loop over the list
	 */
	while (vi) 
	{
		/*
		 *   Skip non-vertices.
		 */
		if ((vi->type == AREAMOVE) || (vi->type == AREADRAW)) 
		{
			p = vi;
			/*
			 *   Now s and p correspond to the vertices in Fig. 11.21 in
			 *   Foley and Van Dam
			 */
			if (inside(p, bordertype, bordervalue)) 
			{
				if (inside(s, bordertype, bordervalue)) 
				{
					addvtx(vo, p);
				}
				else
				{
					t = addintersect(vo, s, p, bordertype, bordervalue);
					u = addvtx(vo, p);
					/*
					 *   Tricky: (only the first time!) if the first point is inside, but the
					 *   previous point (which is the last point in the list) is outside,
					 *   we want to reverse the functions of the two added points. The new
					 *   intersect (t) becomes a "move", whereas the point at hand becomes a
					 *   "draw" (if they were "draw" and "move" before). Reason: the intersect
					 *   gets added *first*, even though it is the leftover of the *last* point.
					 */
					if (first && t && u) 
					{
						if ((s->type == AREADRAW) && (p->type == AREAMOVE)) 
						{
							t->type = AREAMOVE;
							u->type = AREADRAW;
						}
					}
				}
			}
			else
			{
				if (inside(s, bordertype, bordervalue)) 
				{
					addintersect(vo, s, p, bordertype, bordervalue);
				}
			}
			first = 0;
			/*
			 *   Advance to next pair of vertices
			 */
			s = p;
		}
		else
		if (vi->type) 
		{
			/*
			 *   If this is something other than AREADRAW or MOVE, but not NULL,
			 *   copy it straight into the output.
			 */
			addvtx(vo, vi);
		}
		vi = vi->next;
	}
	
	return(vo);
}

/**
 *
 *   Add a vertex p to list vo.
 *
**/
static struct vtx *addvtx( struct vtx *vo, struct vtx *p )
{
	struct vtx *t;
	
	if (t = AddVertex(vo)) 
	{
		t->type = p->type;
		t->x    = p->x;
		t->y    = p->y;
		t->a    = p->a;
		t->b    = p->b;
	}
	return(t);
}

/**
 *
 *   Determine whether a vertex is inside or outside the border
 *
**/
static int inside( struct vtx *s, int bordertype, int bordervalue )
{
	if (s) 
	{
		switch (bordertype) 
		{
		case BD_LEFT :
			return(s->x >= bordervalue);
			break;
		case BD_RIGHT :
			return(s->x < bordervalue);
			break;
		case BD_TOP :
			return(s->y >= bordervalue);
			break;
		case BD_BOTTOM :
			return(s->y < bordervalue);
			break;
		}
	}
	return(0);
}

/**
 *
 *   Add the intersect of vectors s and p with the border to list vo
 *   Routine assumes that one vector is inside, the other outside (no
 *   particular order).
 *
**/
static struct vtx *addintersect( struct vtx *vo, struct vtx *s, struct vtx *p, int bordertype, int bordervalue )
{
	struct vtx *v;
	
	v = AddVertex(vo);
	
	if (s && p && v) 
	{
		v->type = p->type;
		
		switch (bordertype) 
		{
		case BD_LEFT :
		case BD_RIGHT :
			v->x = bordervalue;
			v->y = s->y + (int) (((long) (bordervalue - s->x) * (p->y - s->y)) /
			(p->x - s->x));
			break;
		case BD_TOP :
		case BD_BOTTOM :
			v->y = bordervalue;
			v->x = s->x + (int) (((long) (bordervalue - s->y) * (p->x - s->x)) /
			(p->y - s->y));
			break;
		}
	}
	return(v);
}

