/*****************************************************************************
*   Routines to	scan convert the input (polygons), which is sorted into	     *
* global hash table PolyHashTable according to polygons Ymin.		     *
*									     *
* Written by:  Gershon Elber				Ver 2.0, Mar. 1990   *
*****************************************************************************/

#ifdef __MSDOS__
#include <stdlib.h>
#endif /* __MSDOS__ */

#include <math.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include "program.h"
#include "iritprsr.h"

/* #define DEBUG1		       Add some printing to stderr routines. */

#define Z_BUFFER_MIN_Z	-32767

static GifPixelType *MaskSubScanLine = NULL;
static GifPixelType *ImageScanLine = NULL;
static GifPixelType *MaskScanLine = NULL;
static int *ZBuffer;

static void InitScanLine(void);
static void ScanOnePolygon(IPPolygonStruct *PPolygon, int Level);
static IPVertexStruct *GetNeighborVrtx(IPPolygonStruct *PPolygon,
				       IPVertexStruct *V, IPVertexStruct *NotV);
static void UpdateScanLine(int x1, int z1, int x2, int z2,
						int Color1, int Color2);

#ifdef DEBUG1
void PrintHashTable(void);
void PrintPolygon(IPPolygonStruct *PPolygon);
void PrintImageScanLine(void);
#endif /* DEBUG1 */

/*****************************************************************************
* Routine to scan convert all polygons in PolyHashTable.		     *
* The real images goes to GifFile, while GifMask (if not NULL) will have a   *
* booleam mask, where image was created.				     *
*****************************************************************************/
void ScanConvertData(GifFileType *GifFile, GifFileType *GifMask)
{
    char *ImageSubBGScanLine;
    int i, j, k, OrigScrnXSize, OrigScrnYSize, *ImageSubScanLine,
	SubSamplePixelSquare, *MinIntensityIndex, SubSamplePixel,
	MinYScan = 0, SubSampleLine = 0, LineCount = 0;
    long SaveTime = time(NULL);
    IPPolygonStruct *PPolygon, *PPolygonLast;
    GifPixelType *OneSubScanLine;

    OrigScrnXSize = GlblShadeInfo.ScrnXSize;
    OrigScrnYSize = GlblShadeInfo.ScrnYSize;

    GlblShadeInfo.ScrnXSize *= GlblShadeInfo.SubSamplePixel;
    GlblShadeInfo.ScrnYSize *= GlblShadeInfo.SubSamplePixel;

    if (GlblShadeInfo.SubSamplePixel > 1) {
	OneSubScanLine = (GifPixelType *) IritMalloc(sizeof(GifPixelType) *
						     OrigScrnXSize);
	ImageSubScanLine = (int *) IritMalloc(sizeof(int) *
					      OrigScrnXSize);
	ImageSubBGScanLine = (char *) IritMalloc(sizeof(char) *
						 OrigScrnXSize);
    }
    ImageScanLine = (GifPixelType *) IritMalloc(sizeof(GifPixelType) *
						GlblShadeInfo.ScrnXSize);
    if (GifMask != NULL) {
	if (GlblShadeInfo.SubSamplePixel > 1)
	    MaskSubScanLine = (GifPixelType *)
		IritMalloc(sizeof(GifPixelType) * OrigScrnXSize);
	MaskScanLine = (GifPixelType *) IritMalloc(sizeof(GifPixelType) *
						   GlblShadeInfo.ScrnXSize);
    }
    ZBuffer = (int *) IritMalloc(sizeof(int) * GlblShadeInfo.ScrnXSize);

    MinIntensityIndex = GlblShadeInfo.MinIntensityIndex;
    SubSamplePixel = GlblShadeInfo.SubSamplePixel;
    SubSamplePixelSquare = SQR(SubSamplePixel);

    fprintf(stderr, "\nPass 4, Level [%4d] =      ",
	    OrigScrnYSize);

    for (i = 0; i < GlblShadeInfo.ScrnYSize; i++) {	     /* Scan line i: */
	/* First lets scan and delete all polygons below this scan line: */
	for (j = MinYScan; j <= i; j++) {
	    PPolygon = PPolygonLast = PolyHashTable[j];
	    while (PPolygon) {
		if (PPolygon -> BBox[1][1] < i) {    /* Delete this polygon. */
		    /* If it is first one, update the hash table also. Note  */
		    /* we dont free the polygon as, we are not going to      */
		    /* allocate anything any more, so why bather.	     */
		    if (PPolygon == PolyHashTable[j])
			PolyHashTable[j] = PPolygonLast = PPolygon =
			    PPolygon -> Pnext;
		    else
			PPolygonLast -> Pnext = PPolygon = PPolygon -> Pnext;
		}
		else {
		    PPolygonLast = PPolygon;
		    PPolygon = PPolygon -> Pnext;
		}
	    }
	    /* If this level is empty - advance the minimum level to scan: */
	    if (j == MinYScan && PolyHashTable[j] == NULL)
		MinYScan++;
	}

	/* New do the scan conversion of the active polygons: */
	InitScanLine();
	for (j = MinYScan; j <= i; j++) {
	    PPolygon = PPolygonLast = PolyHashTable[j];
	    while (PPolygon) {
		ScanOnePolygon(PPolygon, i);
		PPolygonLast = PPolygon;
		PPolygon = PPolygon -> Pnext;
	    }
	}

	if (SubSamplePixel > 1) {
	    if (SubSampleLine == 0) {	  /* Reset the sub sumpling buffers. */
		memset(ImageSubScanLine, 0,
			sizeof(int) * OrigScrnXSize);
		memset(OneSubScanLine, 0,
			sizeof(GifPixelType) * OrigScrnXSize);
		memset(ImageSubBGScanLine, 0,
				   sizeof(char) * OrigScrnXSize);
		if (GifMask)
		    memset(MaskSubScanLine, 0,
			   sizeof(GifPixelType) * OrigScrnXSize);
	    }
	    for (j = 0; j < GlblShadeInfo.ScrnXSize; j++)
		if (ImageScanLine[j] == 0)
		    ImageSubBGScanLine[j / SubSamplePixel]++;
		else {
		    k = j / SubSamplePixel;
		    ImageSubScanLine[k] += ImageScanLine[j];
		    OneSubScanLine[k] = ImageScanLine[j];
		    if (GifMask)
			MaskSubScanLine[k] = MaskScanLine[j] != 0 ||
							   MaskSubScanLine[k];
		}
	    SubSampleLine++;
	    if (SubSampleLine == SubSamplePixel) {
		for (j = 0; j < OrigScrnXSize; j++) {
		    /* Instead of Back Ground - add the lowest intensity of */
		    /* This color. Note we still may have problems if two   */
		    /* colors are averaged to a color index in between...   */
		    if (ImageSubScanLine[j] > 0)
			ImageSubScanLine[j] += ImageSubBGScanLine[j] *
					MinIntensityIndex[OneSubScanLine[j]];
		    OneSubScanLine[j] = ImageSubScanLine[j] /
							SubSamplePixelSquare;
		}
		fprintf(stderr, "\b\b\b\b\b%5d", ++LineCount);

#	        ifndef DEBUG_NO_GIF
		    EGifPutLine(GifFile, OneSubScanLine,
				OrigScrnXSize);
		    if (GifMask)
			EGifPutLine(GifMask, MaskSubScanLine,
				    OrigScrnXSize);
#	        endif /* DEBUG_NO_GIF */

		SubSampleLine = 0;
	    }
	}
	else {
	    fprintf(stderr, "\b\b\b\b\b%5d", ++LineCount);

#	    ifndef DEBUG_NO_GIF
		EGifPutLine(GifFile, ImageScanLine, GlblShadeInfo.ScrnXSize);
		if (GifMask)
		    EGifPutLine(GifMask, MaskScanLine,
				GlblShadeInfo.ScrnXSize);
#	    endif /* DEBUG_NO_GIF */
	}
    }

    fprintf(stderr, ",  %ld seconds.", time(NULL) - SaveTime);
#ifdef AMIGA
    fflush(stderr);
#endif /* AMIGA */

    GlblShadeInfo.ScrnXSize = OrigScrnXSize;
    GlblShadeInfo.ScrnYSize = OrigScrnYSize;
}

/*****************************************************************************
* Reset all buffers to clear state:					     *
*****************************************************************************/
static void InitScanLine(void)
{
    memset(ImageScanLine, 0, sizeof(GifPixelType) * GlblShadeInfo.ScrnXSize);

    if (MaskScanLine)
        memset(MaskScanLine, 0, sizeof(GifPixelType) * GlblShadeInfo.ScrnXSize);

    /* Set all Zbuffer to Z_BUFFER_MIN_Z. Can be done in a regular loop as:  */
    /* for (i = 0; i < ScrnXSize; i++) ZBuffer[i] = Z_BUFFER_MIN_Z;	     */
    /* As memset allows setting of only one byte, the minimum we can set     */
    /* here is -32640 which is 0x8080, so we do that instead:		     */
    memset(ZBuffer, 0x80, sizeof(int) * GlblShadeInfo.ScrnXSize);
}

/*****************************************************************************
* Scan convert one polygon:						     *
* 1. If one of the left/right boundaries is found to be below current level  *
*    that boundary edge is updated.					     *
* 2. Interpolate the Color and Z value of the intersection of scan line with *
*    the boundaries and call UpdateScanLine to update the buffers.	     *
*****************************************************************************/
static void ScanOnePolygon(IPPolygonStruct *PPolygon, int Level)
{
    int x1, z1, x2, z2, Color1, Color2;
    RealType t1, t2, *Coord1, *Coord2;
    IPVertexStruct *V;
    PolygonScanConvertStruct
	*PScan = (PolygonScanConvertStruct *) PPolygon -> PAux;

    /* Stage 1 - verify that both boundaries are in range: */
    if (PScan -> Bndry1.MaxEdgeY < Level) {
	V = PScan -> Bndry1.VMinY;
	PScan -> Bndry1.VMinY = PScan -> Bndry1.VMaxY;
	PScan -> Bndry1.VMaxY =
	    GetNeighborVrtx(PPolygon, PScan -> Bndry1.VMaxY, V);
	PScan -> Bndry1.MaxEdgeY =
	    (int) PScan -> Bndry1.VMaxY -> Coord[1];
    }

    if (PScan -> Bndry2.MaxEdgeY < Level) {
	V = PScan -> Bndry2.VMinY;
	PScan -> Bndry2.VMinY = PScan -> Bndry2.VMaxY;
	PScan -> Bndry2.VMaxY =
	    GetNeighborVrtx(PPolygon, PScan -> Bndry2.VMaxY, V);
	PScan -> Bndry2.MaxEdgeY =
	    (int) PScan -> Bndry2.VMaxY -> Coord[1];
    }

    /* Stage 2 - evaluate the interpolated X & Z for this line and call      */
    /* UpdateScanLine with them. Note we could do it using some sort of DDA  */
    /* (integer arithmetic) but as UpdateScanLine is much more expensive     */
    /* we will gain almost nothing in speed, doing that.		     */
    /* Also as this polygon assumed to intersect with the scan line using    */
    /* integer arithmetic, it might not be so, when we actuallt evaluate it. */
    /* We simply quit if that is the case.				     */
    Coord1 = PScan -> Bndry1.VMinY -> Coord;
    Coord2 = PScan -> Bndry1.VMaxY -> Coord;
    if (ABS(Coord2[1] - Coord1[1]) == 0.0)
	t1 = 0.5;
    else
	t1 = (Coord2[1] - Level) / (Coord2[1] - Coord1[1]);
    if (t1 < 0.0 || t1 > 1.0)
	return;

    x1 = Coord1[0] * t1 + Coord2[0] * (1.0 - t1);
    z1 = Coord1[2] * t1 + Coord2[2] * (1.0 - t1);

    Coord1 = PScan -> Bndry2.VMinY -> Coord;
    Coord2 = PScan -> Bndry2.VMaxY -> Coord;
    if (ABS(Coord2[1] - Coord1[1]) == 0.0)
	t2 = 0.5;
    else
	t2 = (Coord2[1] - Level) / (Coord2[1] - Coord1[1]);
    if (t2 < 0.0 || t2 > 1.0)
	return;

    x2 = Coord1[0] * t2 + Coord2[0] * (1.0 - t2);
    z2 = Coord1[2] * t2 + Coord2[2] * (1.0 - t2);

    if (GlblShadeInfo.Gouraud) {
	/* Need to interpolate the colors as well: */
	Color1 = (int) (PScan -> Bndry1.VMinY -> Color * t1 +
			PScan -> Bndry1.VMaxY -> Color * (1.0 - t1));
	Color2 = (int) (PScan -> Bndry2.VMinY -> Color * t2 +
			PScan -> Bndry2.VMaxY -> Color * (1.0 - t2));
	UpdateScanLine(x1, z1, x2, z2, Color1, Color2);
    }
    else {
	/* Flat shading - all vertices has same intensity - pick one: */
	UpdateScanLine(x1, z1, x2, z2, PScan -> Bndry1.VMinY -> Color,
				       PScan -> Bndry1.VMinY -> Color);
    }
}

/*****************************************************************************
* Returns the other neighbor of vertex V in polygon PPolygon which is not    *
* the neighbor NotV.							     *
*****************************************************************************/
static IPVertexStruct *GetNeighborVrtx(IPPolygonStruct *PPolygon,
				       IPVertexStruct *V, IPVertexStruct *NotV)
{
    int i;
    IPVertexStruct *List2,
	*List = PPolygon -> PVertex;

    /* Find index of vertex V. */
    for (i = 0; List != V; i++, List = List -> Pnext);

    if (i == 0) {
	/* The vertex is first - its neighbors are second and last in list.  */
	if (PPolygon -> PVertex -> Pnext == NotV) {
	    /* We need the last one: */
	    for (List2 = PPolygon -> PVertex;
		 List2 -> Pnext != NULL;
		 List2 = List2 -> Pnext);
	    return List2;
	}
	else
	    return PPolygon -> PVertex -> Pnext;
    }
    else if (List -> Pnext == NULL) {
	/* The vertex is last - its neighbors are first and second from last.*/
	if (PPolygon -> PVertex == NotV) {
	    /* We need the second from last: */
	    for (List2 = PPolygon -> PVertex;
		 List2 -> Pnext != List;
		 List2 = List2 -> Pnext);
	    return List2;
	}
	else
	    return PPolygon -> PVertex;
    }
    else {
	if (List -> Pnext == NotV) {
	    for (List2 = PPolygon -> PVertex;
		 List2 -> Pnext != List;
		 List2 = List2 -> Pnext);
	    return List2;
	}
	else
	    return List -> Pnext;
    }
}

/*****************************************************************************
* Update the scan line itself by linearly interplate the two end points for  *
* all internal points of scan line if closer than old data.		     *
*****************************************************************************/
static void UpdateScanLine(int x1, int z1, int x2, int z2,
						int Color1, int Color2)
{
    int i, Dx, Dz, Dc, Az, Ac, x, z, Color;
    RealType t;

    if (x2 < x1) {
	i = x2; x2 = x1; x1 = i;
	i = z2; z2 = z1; z1 = i;
	i = Color2; Color2 = Color1; Color1 = i;
    }
    if (x1 < 0) {
	/* Update lower limit to zero: */
	if (x2 == x1)
	    return;
	t = -x1 / (x2 - x1);
	x1 = 0;
	z1 = (int) (z1 * (1.0 - t) + z2 * t);
	Color1 = (int) (Color1 * (1.0 - t) + Color2 * t);
    }
    if (x2 >= GlblShadeInfo.ScrnXSize) {
	/* Update upper limit to GlblShadeInfo.ScrnXSize - 1: */
	if (x2 == x1)
	    return;
	t = (x2 - (GlblShadeInfo.ScrnXSize - 1)) / (x2 - x1);
	x2 = GlblShadeInfo.ScrnXSize - 1;
	z2 = (int) (z1 * t + z2 * (1.0 - t));
	Color2 = (int) (Color1 * t + Color2 * (1.0 - t));
    }

    Dx = x2 - x1;
    x = x1;
    Dz = z2 - z1;
    Az = -Dx;			      /* DDA accumulator of Z interpolation. */
    z = z1;
    Color = Color1;
    if (GlblShadeInfo.Gouraud) {
	Dc = Color2 - Color1;	     /* Needed if Gouraud shading is in use. */
	Ac = -Dx;		  /* DDA accumulator of Color interpolation. */
    }

    /* We are going to execute the loop once but might be stack forever in   */
    /* one of the internal interplation loops, so make it non zero:	     */
    if (Dx == 0)
	Dx = 999;

    if (Dz > 0) {
	while (x <= x2) {
	    /* Update buffers iff is closer to view point: */
	    if (ZBuffer[x] < z) {
		ZBuffer[x] = z;
		ImageScanLine[x] = Color;
		if (MaskScanLine != NULL)
		    MaskScanLine[x] = 1;
	    }
	    else if (ZBuffer[x] == z) {
		/* This case is hard to solve, and it may create high	     */
		/* intensity lines on polygon boundaries. To prevent from    */
		/* that, update iff new color intensity is less than old on. */
		if (ImageScanLine[x] < Color)
		    ImageScanLine[x] = Color;
		if (MaskScanLine != NULL)
		    MaskScanLine[x] = 1;
	    }

	    Az += Dz;
	    x++;
	    while (Az > 0) {
		z++;
		Az -= Dx;
	    }
	    if (GlblShadeInfo.Gouraud) {
		if (Dc > 0) {
		    Ac += Dc;
		    while (Ac > 0) {
			Color++;
			Ac -= Dx;
		    }
		}
		else { /* Dc < 0 */
		    Ac -= Dc;
		    while (Ac > 0) {
			Color--;
			Ac -= Dx;
		    }
		}
	    }
	}
    }
    else { /* Dz < 0 */
	while (x <= x2) {
	    /* Update buffers iff is closer to view point: */
	    if (ZBuffer[x] < z) {
		ZBuffer[x] = z;
		ImageScanLine[x] = Color;
		if (MaskScanLine != NULL)
		    MaskScanLine[x] = 1;
	    }
	    else if (ZBuffer[x] == z) {
		/* This case is hard to solve, and it may create high	     */
		/* intensity lines on polygon boundaries. To prevent from    */
		/* that, update iff new color intensity is less than old on. */
		if (ImageScanLine[x] < Color)
		    ImageScanLine[x] = Color;
		if (MaskScanLine != NULL)
		    MaskScanLine[x] = 1;
	    }

	    Az -= Dz;
	    x++;
	    while (Az > 0) {
		z--;
		Az -= Dx;
	    }
	    if (GlblShadeInfo.Gouraud) {
		if (Dc > 0) {
		    Ac += Dc;
		    while (Ac > 0) {
			Color++;
			Ac -= Dx;
		    }
		}
		else { /* Dc < 0 */
		    Ac -= Dc;
		    while (Ac > 0) {
			Color--;
			Ac -= Dx;
		    }
		}
	    }
	}
    }
}

#ifdef DEBUG1

/*****************************************************************************
* Routine to print the content of a given edge:				     *
*****************************************************************************/
void PrintHashTable(void)
{
    int i;
    IPPolygonStruct *PPolygon;

    for (i = 0; i < GlblShadeInfo.ScrnYSize; i++)
	if (PolyHashTable[i] != NULL) {
	    fprintf(stderr,
	        "\n***************** HashTable entry %d *****************\n", i);
	    PPolygon = PolyHashTable[i];
	    while (PPolygon) {
	        fprintf(stderr, "\t+++++++ Polygon:\n");
	        PrintPolygon(PPolygon);
	        PPolygon = PPolygon -> Pnext;
	    }
        }
}

/*****************************************************************************
* Routine to print the content of a given edge:				     *
*****************************************************************************/
void PrintPolygon(IPPolygonStruct *PPolygon)
{
    int i;
    IPVertexStruct *VList = PPolygon -> PVertex;

    for ( ; VList != NULL; VList = VList -> Pnext) {
	fprintf(stderr, "\t%12f %12f %12f (Color = %d).\n",
	    PList -> Coord[0],
	    PList -> Coord[1],
	    PList -> Coord[2],
	    PList -> Color);
    }
}

/*****************************************************************************
* Routine to print content of Image Scan Line buffer:			     *
*****************************************************************************/
void PrintImageScanLine(void)
{
    int i;

    for (i = 0; i < GlblShadeInfo.ScrnXSize; i++) {
	if (i % 26 == 0)
	    fprintf(stderr, "\n");
	fprintf(stderr, "%02x ", ImageScanLine[i]);
    }
}

#endif /* DEBUG1 */
