/******************************************************************************
 * OCTANT.C														\ 3 | 2 /
 * This group of routines draws a line using the Octantal		 \	|  /
 * Digital Differential Analyser algorithm described on page	4 \ | / 1
 * 25 of "Microcomputer Displays, Graphics and Animation". Its     \|/
 * advantage over the system line drawing routines is that it  -----+-----
 * saves the color register values of the pixels that it draws	   /|\
 * over, so that they can be restored as the line is erased.	5 / | \ 8
 *																 /	|  \
 * These routines are optimized for speed not compactness.		/ 6 | 7	\
 * When a line is drawn, the original pixels and their coordinates are saved.
 * Thus, the line can be erased rather quickly but severl K of ram can be 
 * required. If necessary, the line drawing speed can be increased some what
 * by including the plot() routine as in line code of the octant routines,
 * thus eliminating the function call overhead.
 *
 * These routines do not perform clipping.
 *
 * For normal use, the entry points to this file are draw_line() and
 * restore_line().
 *
 ******************************************************************************/


/* Some of these include files are not necessary.	*/

#include	<functions.h>
#include	<exec/types.h>
#include 	<exec/nodes.h>
#include	<exec/memory.h>
#include	<exec/libraries.h>
#include	<graphics/gfx.h>
#include	<graphics/rastport.h>
#include	<intuition/intuitionbase.h>
#include 	<stdio.h>
/* #include	<exec/tasks.h>			*/
/* #include	<graphics/text.h>		*/


/* This struct keeps track of the image pixels blotted out by the line	*/

static struct saved_line
{
	struct RastPort	*RPort;		/* RastPort the line was drawn in.		*/
	UBYTE			*storage;	/* Color registers of drawn over pixels	*/
	USHORT			*x;			/* X screen coordinates of line points.	*/
	USHORT			*y;			/* Y screen coordinates of line points.	*/
	USHORT			points;		/* Num of elements in the allocated		*/
}line;							/* array. Eq the max index plus 1.		*/

/************************  Error messages  ************************************/

static char alloc_err[] = "Can't alloc pixel arrays.";
static char rp_err[]	= "ERROR READING PIXEL";
static char wp_err[]	= "ERROR WRITINGING PIXEL";
static char line_err[]	= "Plot() called before array allocation.";
static char rest_err[]	= "Tried to restore nonexistant line.";

/*****************************************************************************
 * Allocate arrays pointed to by members of the save_line structure and init 
 * the struct members. Arrays are allocated from Fast RAM if possible.
 *****************************************************************************/

static alloc_line(RPort, length)
struct RastPort	*RPort;
short			length;				/* Number of pixels in the line.	*/
{
ULONG	size;

size = length * sizeof(*line.storage);
line.storage = (UBYTE *)AllocMem(size, MEMF_CLEAR | MEMF_PUBLIC);

size	= length * sizeof(*line.x);
line.x	= (USHORT *)AllocMem(size, MEMF_CLEAR | MEMF_PUBLIC);
line.y	= (USHORT *)AllocMem(size, MEMF_CLEAR | MEMF_PUBLIC);


if (line.storage == NULL || line.x == NULL || line.y == NULL)
{
	free_line();
	return(FALSE);
}
line.RPort  = RPort;
line.points = length;	/* This value can't be altered til arrays are freed. */

return(TRUE);

} /*  End of alloc_line()	*/


/******************************************************************************
 * Release arrays pointed to by members of the line struct.
 ******************************************************************************/

static free_line()
{

if (line.storage)
{
	FreeMem(line.storage, (long)line.points * sizeof(*line.storage) );
	line.storage = NULL;
}
if (line.x)
{
	FreeMem(line.x, (long)line.points * sizeof(*line.x) );
	line.x = NULL;
}
if (line.y)
{
	FreeMem(line.y, (long)line.points * sizeof(*line.y) );
	line.y = NULL;
}
line.RPort	= NULL;
line.points	= 0;

return;

} /*  End of free_line()  */


/******************************************************************************
 * Save image pixel and replace it with a line pixel. This routine does not 
 * waste time checking index to see if it is within the range of the allocated
 * array. The client routine better not fuck up the index calculation.
 ******************************************************************************/

static plot(index, x, y)
short		index, x, y;
{
long		result;

if (!line.points)					/* A major error.	*/
{
	puts(line_err);
	return(FALSE);
}
result = ReadPixel(line.RPort, (long)x, (long)y );

line.storage[index] = result;
line.x[index]		= x;
line.y[index]		= y;

WritePixel(line.RPort, (long)x, (long)y );

} /*  End of plot()  */


/******************************************************************************
 * First (and fourth) octant.     Dy < 0       |Dy| < |Dx|
 ******************************************************************************/

static octant1(RPort, x, y, deltax, deltay)
struct RastPort	*RPort;
short			x, y, deltax, deltay;
{
register short countdown;	/* Number of pixels still to be drawn.	*/
register short err;			/* Running error term.					*/
register short index;		/* Index of current point.				*/

countdown	= deltax + 1;
err			= -deltax / 2;	/* err starts out being less than zero.	*/
index		= 0;

if ( !alloc_line(RPort, countdown) )
{
	puts(alloc_err);
	return(FALSE);
}

/*----------------------------------------------------------------------*
 * While there are more points to plot... There must be something wrong
 * with an algorythm that requires both assending and decending counters.
 *----------------------------------------------------------------------*/

while (countdown--)
{
	plot(index++, x++, y);
	err -= deltay;			/* err gradually becomes positive.	*/
	if (err >= 0)
	{
		--y;				/* Moving up screen.				*/
		err -= deltax;		/* err goes negative again.			*/
	}
}
return(TRUE);

} /*  End of octant1()  */


/*****************************************************************************
 * Second (and third) octant.		|Dy| > |Dx|
 *****************************************************************************/

static octant2(RPort, x, y, deltax, deltay)
struct RastPort	*RPort;
short			x, y, deltax, deltay;
{
register short countdown;	/* Number of pixels still to be drawn.	*/
register short err;			/* Running error term.					*/
register short index;		/* Index of current point.				*/

countdown	= -deltay + 1;
err			= deltay / 2;
index		= 0;

if ( !alloc_line(RPort, countdown) )
{
	puts(alloc_err);
	return(FALSE);
}
while (countdown--)
{
	plot(index++, x, y--);
	err += deltax;			/* err growing positive.	*/
	if (err >= 0)
	{
		++x;
		err += deltay;		/* err goes negative.		*/
	}
}
return(TRUE);

} /*  End of octant2()  */


/******************************************************************************
 * Seventh (and sixth) octant.   Dy > Dx > 0
 ******************************************************************************/

static octant7(RPort, x, y, deltax, deltay)
struct RastPort	*RPort;
short			x, y, deltax, deltay;
{
register short countdown;	/* Number of pixels still to be drawn.	*/
register short err;			/* Running error term.					*/
register short index;		/* Index of current point.				*/

countdown	= deltay + 1;
err			= -deltay / 2;
index		= 0;

if ( !alloc_line(RPort, countdown) )
{
	puts(alloc_err);
	return(FALSE);
}
while (countdown--)
{
	plot(index++, x, y++);
	err += deltax;			/* err growing positive	*/
	if (err >= 0)
	{
		++x;
		err -= deltay;		/* err goes negative.	*/
	}
}
return(TRUE);

} /*  End of octant7()  */


/*****************************************************************************
 * Eighth (and fifth) octant.      Dx > Dy > 0.
 *****************************************************************************/

static octant8(RPort, x, y, deltax, deltay)
struct RastPort	*RPort;
short			x, y, deltax, deltay;
{
register short countdown;	/* Number of pixels still to be drawn.	*/
register short err;			/* Running error term.					*/
register short index;		/* Index of current point.				*/

countdown	= deltax + 1;
err			= -deltax / 2;
index		= 0;

if ( !alloc_line(RPort, countdown) )
{
	puts(alloc_err);
	return(FALSE);
}
while (countdown--)
{
	plot(index++, x++, y);	/* Moving left to right.	*/
	err += deltay;			/* err growing positive.	*/
	if (err >= 0)
	{
		++y;				/* Move down screen next time.	*/
		err -= deltax;		/* err goes negative.		*/
	}
}
return(TRUE);

} /*  End of octant8()  */


/*****************************************************************************
 *	A quicker vertical line stroker. The end point supplied by client must be
 *  the one with the smallest y value.
 *****************************************************************************/

static vert_line(RPort, x, y, len)
struct RastPort	*RPort;
short			x, y, len;
{

register short index;		/* Index of current point.				*/

index = 0;

if ( !alloc_line(RPort, len) )
{
	puts(alloc_err);
	return(FALSE);
}
while (index < len)
	plot(index++, x, y++);

return(TRUE);

} /*  End of vert_line()  */


/*****************************************************************************
 * Quick horizontal line draw. The end point supplied must be the one with 
 * the smallest x value.
 *****************************************************************************/

static horiz_line(RPort, x, y, len)
struct RastPort	*RPort;
USHORT			x, y, len;
{

register USHORT index;		/* Index of current point.				*/

index = 0;

if ( !alloc_line(RPort, len) )
{
	puts(alloc_err);
	return(FALSE);
}

while (index < len)
	plot(index++, x++, y);

return(TRUE);

} /*  End of horiz_line()  */


/*****************************************************************************
 * Quicker diagonal line drawer. For line with a negative slope
 *****************************************************************************/

static diagdn_line(RPort, x, y, len)
struct RastPort	*RPort;
short			x, y, len;
{

register short index;		/* Index of current point.				*/

index		= 0;

if ( !alloc_line(RPort, len) )
{
	puts(alloc_err);
	return(0);
}

while (index < len)
	plot(index++, x++, y++);

return(1);

} /*  End of diagdn_line()  */


/*****************************************************************************
 * Quicker diagonal line drawer. For line with a positive slope
 *****************************************************************************/

static diagup_line(RPort, x, y, len)
struct RastPort	*RPort;
short			x, y, len;
{

register short index;		/* Index of current point.				*/

index = 0;

if ( !alloc_line(RPort, len) )
{
	puts(alloc_err);
	return(FALSE);
}

while (index < len)
	plot(index++, x++, y--);

return(TRUE);

} /*  End of diagup_line()  */


/******************************************************************************
 * This function is the normal entry point for line drawing. It determins 
 * which octant the line is in and calls the appropriate line drawing routine.
 * the screen's coordinate system is used. Thus, Y values increase as you move
 * down the screen. Lines are always drawn from left to right, swapping end
 * points if necessary.
 *
 *	RPort		Pointer to the RastPort of the window being drawn in.
 *  (x1, y1)	First end point.
 *  (x2, y2)	Other end point.
 *
 ******************************************************************************/

draw_line(RPort, color, x1, y1, x2, y2)
struct RastPort	*RPort;
USHORT			color;
USHORT			x1, y1, x2, y2;
{

short			deltax, deltay;
short			rc;
short			temp;
BYTE			save_color;

if (x1 > x2)
{
	temp = x1;	x1 = x2;  x2 = temp;
	temp = y1;	y1 = y2;  y2 = temp;
}
deltax = x2 - x1;
deltay = y2 - y1;

save_color = RPort->FgPen;			/* Save pen color for later restoration. */
SetAPen(RPort, (long)color);

/* Trap hoizontal and vertical lines for special treatment.	*/

if (!deltay)
{
	rc = horiz_line(RPort, x1, y1, deltax + 1);
}
else if (!deltax)
{
	if (deltay > 0)
		rc = vert_line(RPort, x1, y1, deltay + 1);
	else
		rc = vert_line(RPort, x2, y2, 1 - deltay);
}
else if (deltay > 0)								/* Fourth Quadrant	*/
{
	if (deltax == deltay)
		rc = diagdn_line(RPort, x1, y1, deltax + 1);
	else if (deltay > deltax)
		rc = octant7(RPort, x1, y1, deltax, deltay);
	else
		rc = octant8(RPort, x1, y1, deltax, deltay);
}
else												/* First Quadrant	*/
{
	if ( !(deltax + deltay) )
		rc = diagup_line(RPort, x1, y1, deltax + 1);
	else if (-deltay > deltax)
		rc = octant2(RPort, x1, y1, deltax, deltay);
	else
		rc = octant1(RPort, x1, y1, deltax, deltay);
}

SetAPen(RPort, (long)save_color);	/* Restore original pen color. */

return(rc);

} /*  End of draw_line()  */


/******************************************************************************
 * Restore image pixels that have been overdrawn by the line. This is the 
 * entry point for line erasure. This routine can not mess with the value of 
 * line.points because it will be needed to release the arrays. Note that if
 * line.points is initially zero, this routine is a nop.
 ******************************************************************************/

restore_line()
{
register short	index;
BYTE			save_color;

if (!line.points)
{
	puts(rest_err);
	return(FALSE);
}

save_color = line.RPort->FgPen;

index = line.points;		/* Index of last point + 1.	*/

while (index--)
{
	SetAPen(line.RPort, (ULONG)line.storage[index] );

	/* if (!line.x[index]) puts("Restored point with zero X value."); */

	WritePixel(line.RPort, (ULONG)line.x[index], (ULONG)line.y[index] );
}
SetAPen(line.RPort, (ULONG)save_color);
free_line();

} /*  End of restore_line()  */
