/****************  ACK-3D ( Animation Construction Kit 3D ) *******************/
/* Engine Code	      */
/* Author: Lary Myers */

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <sys/stat.h>
#include "ack3d.h"
#include "ackext.h"

/****************************************************************************
**									   **
****************************************************************************/
char xRay(int x,int y,int angle)
{
char   Color;
short  i,j,mx,my;
short  TablePosn;
short  MapPosn,CurPosn;
short  xBeg;
long   xPos,xNext;
short  BitmapColumn;
short  xCenter,yCenter,xAdj;
short  ObjPosn;
short  oBegX,oBegY;
long   yPos;
long   yNext;
time_t xd, yd;
long   sy;
long   ObjDist;

	xBeg  = x & 0xFFC0;		/* Get upper left corner of square    */
	yNext = yNextTable[angle];	/* PreCalc'd value of 64 * Tan(angle) */

	if (angle > INT_ANGLE_270 || angle < INT_ANGLE_90) {
    		xPos  = xBeg + GRID_SIZE;	/* Looking to the right */
    		xNext = GRID_SIZE;		/* Positive direction	*/
    	} else {
    		xPos  = xBeg;		/* Looking to the left	*/
    		xNext = -GRID_SIZE;		/* Negative direction	*/
    		yNext = -yNext;
    	}

	/* Calculate the Y coordinate for the current square */
	yPos = (((long)xPos - (long)x) * LongTanTable[angle]) + ((long)y << FP_SHIFT);

	while (1) {
    		if (xPos < 0 || xPos > GRID_XMAX ||
		    yPos < 0 || yPos > GRID_YMAXLONG)
			break;

		/**************	  Fixed point	  Y/64 * 64	X / 64 ***********/
    		MapPosn = ((yPos >> FP_SHIFT) & 0xFFC0) + (xPos >> 6);

		/* First check for an object that has not been seen yet */
	    	if ((Color = xObjGrid[MapPosn]) > 0 && !(ObjFlags[Color] & 3)) {
			ObjFlags[Color] |= 1;		    /* Indicate object has been seen */

			xd = ObjList[Color].x - x;
			yd = ObjList[Color].y - y;
			ObjDist = (xd * xd) + (yd * yd);    /* Calc relative distance */

/* Place the object in the array according to distance, with the ones closer */
/* being drawn last. This will allow closer objects to hide farther objects  */

			j = TotalObjects;
			for (i = 0; i < TotalObjects; i++) {
	    			if (ObjDist > ObjRelDist[i]) {

					for (j = TotalObjects; j > i; j--) {
			    			ObjRelDist[j] = ObjRelDist[j-1];
			    			ObjNumber[j]  = ObjNumber[j-1];
			    			ObjMapPosn[j] = ObjMapPosn[j-1];
			    		}

					j = i;
					i = TotalObjects;
				}
	    		}

/* Hold onto relavant data for the object found */
			ObjNumber[j]  = Color;
			ObjRelDist[j] = ObjDist;
			ObjMapPosn[j] = MapPosn;
			TotalObjects++;
			ObjRelDist[TotalObjects] = 3200000;
		}

/* Now check to see if a wall is being struck by the ray */
    		if ((Color = xGrid[MapPosn]) != 0) {
			xMapPosn = MapPosn;	    /* Hold onto the map location */
			iLastX	 = xPos;
			LastY1	 = yPos;
			if (Color == DOOR_XCODE) {    /* Is this a door? */
		    		yd = (yPos >> FP_SHIFT) & 0xFFC0;	/* Get the left side */
		    		xd = yd + GRID_SIZE;		/* And the right side */
		    		ObjDist = (yPos + (yNext >> 1)) >> FP_SHIFT; /* Calc door distance */
		    		if (ObjDist < yd || ObjDist > xd) {	/* Is door visible? */
					xPos += xNext;			/* Nope, continue casting */
					yPos += yNext;			/* the ray as before	  */
					continue;
				}

		    		LastY1 = yPos + (yNext >> 1);	/* Adjust the X,Y values so   */
		    		iLastX += (xNext >> 1);		/* the door is halfway in sq. */
		    	}

			if (Color == DOOR_SECRETCODE) {
		    		if (xSecretColumn != 0) {
					sy = xSecretColumn * LongTanTable[angle];
					ObjDist = (yPos + sy) >> FP_SHIFT;
					yd = (yPos >> FP_SHIFT) & 0xFFC0;   /* Get the left side */
					xd = yd + GRID_SIZE;		    /* And the right side */
					if (ObjDist < yd || ObjDist > xd) {   /* Is door visible? */
			    			xPos += xNext;		    /* Nope, continue casting */
			    			yPos += yNext;		    /* the ray as before      */
			    			continue;
			    		}
					LastY1 = yPos + sy;
					iLastX += xSecretColumn;
				}
		    	}
	
			return(Color);
		}

    		xPos += xNext;	/* Next X coordinate (fixed at 64 or -64)   */
    		yPos += yNext;	/* Next calculated Y coord for a delta of X */
    	}

	return(0);		/* Return that no wall was found */
}

/****************************************************************************
**									   **
****************************************************************************/
char yRay(int x,int y,int angle)
{
char    Color;
short   i,j,mx,my;
short   MapPosn;
short   yBeg;
long    yPos,yNext;
short   BitmapColumn;
short   xCenter,yCenter,yAdj;
short   ObjPosn;
short   oBegX;
long    xPos;
long    xNext;
long    xd,yd,ObjDist,sx;

yBeg  = y & 0xFFC0;		/* Same as div 64 then mul 64	       */
xNext = xNextTable[angle];	/* Pre-calc'd value of 64 / tan(angle) */

if (angle < INT_ANGLE_180)
    {
    yPos  = yBeg + GRID_SIZE;	/* Looking down	      */
    yNext = GRID_SIZE;		/* Positive direction */
    }
else
    {
    yPos  = yBeg;		/* Looking up	      */
    yNext = -GRID_SIZE;		/* Negative direction */
    xNext = -xNext;
    }

/* Calculate the X coordinate for the current square */
xPos = (((long)yPos - (long)y) * LongInvTanTable[angle]) + ((long)x << FP_SHIFT);

oBegX = 0;

while (1)
    {
    if (xPos < 0 || xPos > GRID_XMAXLONG ||
	yPos < 0 || yPos > GRID_YMAX)
	break;

/***********   Y/64 * 64	 Fixed point and /64 ******/
    MapPosn = (yPos & 0xFFC0) + (xPos >> (FP_SHIFT+6));

/* Check for unseen objects first */
    if ((Color = yObjGrid[MapPosn]) > 0 && !(ObjFlags[Color] & 3))
	{
	ObjFlags[Color] |= 2;		/* Signal object seen on Y cast */

	xd = ObjList[Color].x - x;
	yd = ObjList[Color].y - y;
	yd = (xd * xd) + (yd * yd);	/* Calc relative distance to object */

/* Place the object in the array according to distance, with the ones closer */
/* being drawn last. This will allow closer objects to hide farther objects  */
	j = TotalObjects;
	for (i = 0; i < TotalObjects; i++)
	    {
	    if (yd > ObjRelDist[i])
		{
		for (j = TotalObjects; j > i; j--)
		    {
		    ObjRelDist[j] = ObjRelDist[j-1];
		    ObjNumber[j]  = ObjNumber[j-1];
		    ObjMapPosn[j] = ObjMapPosn[j-1];
		    }

		j = i;
		i = TotalObjects;
		}
	    }

	ObjNumber[j]  = Color;		/* Hold onto object data for later */
	ObjRelDist[j] = yd;
	ObjMapPosn[j] = MapPosn;
	TotalObjects++;
	ObjRelDist[TotalObjects] = 3200000;
	}

/** Check for a wall being struck **/
    if ((Color = yGrid[MapPosn]) != 0)
	{
	yMapPosn = MapPosn;		/* Hold onto map position */
	LastX1	 = xPos;
	iLastY	 = yPos;

	if (Color == DOOR_YCODE)	/* Is this a door? */
	    {
	    yd = (xPos >> FP_SHIFT) & 0xFFC0;	/* Calc top side of square   */
	    xd = yd + GRID_SIZE;		/* And bottom side of square */
	    ObjDist = (xPos + (xNext >> 1)) >> FP_SHIFT;
	    if (ObjDist < yd || ObjDist > xd)	/* Is door visible? */
		{
		xPos += xNext;		/* No, continue on with ray cast */
		yPos += yNext;
		continue;
		}

	    LastX1 = xPos + (xNext >> 1);   /* Adjust coordinates so door is */
	    iLastY += (yNext >> 1);	    /* Halfway into wall	     */
	    }

	if (Color == DOOR_SECRETCODE)
	    {
	    if (ySecretColumn != 0)
		{
		sx = ySecretColumn * LongInvTanTable[angle];
		ObjDist = (xPos + sx) >> FP_SHIFT;
		yd = (xPos >> FP_SHIFT) & 0xFFC0;   /* Get the top side	   */
		xd = yd + GRID_SIZE;		    /* And the bottom side */
		if (ObjDist < yd || ObjDist > xd)   /* Is door visible? */
		    {
		    xPos += xNext;		    /* Nope, continue casting */
		    yPos += yNext;		    /* the ray as before      */
		    continue;
		    }
		LastX1 = xPos + sx;
		iLastY += ySecretColumn;
		}

	    }

	return(Color);
	}

    xPos += xNext;		/* Next calculated X value for delta Y */
    yPos += yNext;		/* Next fixed value of 64 or -64       */

    if (++oBegX > 64)		/* Check a maximum number of squares   */
	break;
    }

return(0);		/* Return here if no Y wall is found */
}


/****************************************************************************
** This is the heart of the engine. This routine will cast two rays for	   **
** each column of the screen. The first ray will look for any walls that   **
** fall on the X boundaries (vertical walls). The second ray will look for **
** walls on the Y boundaries (horizontal walls). The wall with the shorter **
** distance will be used to draw a sliver of the bitmap at the current	   **
** screen column (0-319). A 60 degree field of view was selected to give   **
** a realistic perspective to the walls.				   **
**									   **
****************************************************************************/
void DrawView(int xPlayer,int yPlayer,int PlayerAngle)
{
short   i,j,index;
unsigned    char    xBitmap,yBitmap,BitmapNumber;
short   ViewAngle;
long    xDistance,yDistance;
long    WallDistance;
long    distance;
unsigned    int	    BitmapColumn,yBitmapColumn;
short   WallCode,OldMapPosn,OldMapPosn1;
long    xd,yd;
unsigned    char    *Video;
unsigned    int	    offset;

/* Clear out the object seen flags so objects can be found */
memset(ObjFlags,0,MaxObjects);


/* Begin looking 30 degrees to the left of our current angle */
ViewAngle = PlayerAngle - INT_ANGLE_30;

if (ViewAngle < 0)
    ViewAngle += INT_ANGLE_360;

/* Reset total objects found on last DrawView pass */
TotalObjects = 0;

MaxDistance = 0;


/* Cast two rays for each column of the video display */

for (i = 0; i < VIEW_WIDTH; i++) {
    WallDistance = 3000000;	/* Set to a ridiculous distance */
    BitmapColumn = -1;		/* Set to no walls found	*/
    WallCode	 = 0;		/* Set to no shadowing		*/

/* Don't even cast an X ray if impossible to intersect the X walls */
    if (ViewAngle != INT_ANGLE_90 && ViewAngle != INT_ANGLE_270) {
	yd = CLOCK_PTR;
	BitmapNumber = xRay(xPlayer,yPlayer,ViewAngle);

	if (BitmapNumber) {	/* A wall was found */
	    /* Use the Y intercept to determine the wall column */
	    BitmapColumn = (LastY1 >> FP_SHIFT) & 0x3F;

	    /* Keep the orientation the same no matter which side we're on */
	    if ((int)iLastX < xPlayer)
		BitmapColumn = 63 - BitmapColumn;

	    /* Did we strike a door? */
	    if (BitmapNumber == DOOR_XCODE) {
		index = FindDoor(xMapPosn);
		if (index >= 0) {			    /* This is a valid door */
		    j = Door[index].ColOffset;	    /* Get its current pos  */
		    if ((int)iLastX > xPlayer)	    /* Handle orientation   */
			j = -j;
		    BitmapColumn += j;		    /* Adjust column to show */
		    if (BitmapColumn > 63) {	    /* Door is opening	     */
			/* Get the grid coordinates for this door */
			OldMapPosn = Door[index].mPos;
			OldMapPosn1 = Door[index].mPos1;

			/* Fake the engine into thinking no door is there */
			xGrid[OldMapPosn] = 0;
			xGrid[OldMapPosn1] = 0;

			/* Cast the ray to get walls beyond the door */
			BitmapNumber = xRay(xPlayer,yPlayer,ViewAngle);

			/* Put back the door codes if not fully open */
			if (Door[index].ColOffset < 63) {
			    xGrid[OldMapPosn] = Door[index].mCode;
			    xGrid[OldMapPosn1] = Door[index].mCode1;
			    }

			/* Calc the new bitmap column of wall behind door */
			BitmapColumn = (LastY1 >> FP_SHIFT) & 0x3F;
			if ((int)iLastX < xPlayer)
			    BitmapColumn = 63 - BitmapColumn;
			}
		    }
		}

	    /* Calculate the distance to the wall. Since the X position was  */
	    /* fixed to move 64 or -64 we can use it to determine the actual */
	    /* wall distance. The InvCosTable values were stored with a fixed */
	    /* point of 20 decimal places. At this time we'll knock off 14 of */
	    /* them so we can later multiple with a fixed point value of 16   */
	    xd = iLastX - xPlayer;
	    WallDistance = (xd * InvCosTable[ViewAngle]) >> 14;


	    /* Still looking for the reason this may occur. But this check  */
	    /* will force the distance to a ridiculous value so no wall is  */
	    /* seen later on when the X and Y walls are compared.	    */
	    if (WallDistance < 0)
		WallDistance = 120000L;

	    /* Walls that are not doors will have shadowing */
	    if (BitmapNumber < DOOR_XCODE)
		WallCode = 1;
	    }

	txRay += CLOCK_PTR - yd;
	}

/* Don't cast a Y ray if its impossible to intercept any Y walls */
    if (ViewAngle != 0 && ViewAngle != INT_ANGLE_180) {
	xd = CLOCK_PTR;
	yBitmap = yRay(xPlayer,yPlayer,ViewAngle);

	if (yBitmap) {	    /* A wall was found */

	    /* Use the X intercept to determine the column of the bitmap */
	    yBitmapColumn = (LastX1 >> FP_SHIFT) & 0x3F;

	    /* Handle orientation from either side of the wall */
	    if ((int)iLastY > yPlayer)
		yBitmapColumn = 63 - yBitmapColumn;

	    /* Did we strike a door? */
	    if (yBitmap == DOOR_YCODE) {
		index = FindDoor(yMapPosn);
		if (index >= 0) {			/* This is a valid door */
		    /* Get the current door column offset */
		    j = Door[index].ColOffset;

		    /* Deal with orientation */
		    if ((int)iLastY < yPlayer)
			j = -j;

		    yBitmapColumn += j;

		    /* If beyond width of bitmap than cast again */
		    if (yBitmapColumn > 63) {

			/* Get the yGrid coordinates for this door */
			OldMapPosn = Door[index].mPos;
			OldMapPosn1 = Door[index].mPos1;

			/* Fool the engine into thinking no door is there */
			yGrid[OldMapPosn] = 0;
			yGrid[OldMapPosn1] = 0;

			/* Cast again for walls beyond the door */
			yBitmap = yRay(xPlayer,yPlayer,ViewAngle);

			/* Put door code back if not fully open */
			if (Door[index].ColOffset < 63)
			    {
			    yGrid[OldMapPosn] = Door[index].mCode;
			    yGrid[OldMapPosn1] = Door[index].mCode1;
			    }

			/* Get the bitmap column of wall beyond door */
			yBitmapColumn = (LastX1 >> FP_SHIFT) & 0x3F;
			if ((int)iLastY > yPlayer)
			    yBitmapColumn = 63 - yBitmapColumn;

			}
		    }
		}


	    /* Calculate the distance to the wall. Since the Y position was  */
	    /* fixed to move 64 or -64 we can use it to determine the actual */
	    /* wall distance. The InvSinTable values were stored with a fixed */
	    /* point of 20 decimal places. At this time we'll knock off 14 of */
	    /* them so we can later multiple with a fixed point value of 16   */
	    yd = iLastY - yPlayer;
	    yDistance = (yd * InvSinTable[ViewAngle]) >> 14;

	    /* Don't know the reason but change negative value into ridiculous */
	    if (yDistance < 0)
		yDistance = 120000L;


	    /* At this point check the distance to the Y wall against the X */
	    /* wall to see which one is closer. The closer one is the one   */
	    /* we'll draw at this column of the screen.                     */
	    if (yDistance < WallDistance)
		{
		WallCode = 0;		    /* Turn off shadowing if on */
		WallDistance = yDistance;
		BitmapNumber = yBitmap;
		BitmapColumn = yBitmapColumn;
		}

	    }

	tyRay += CLOCK_PTR - xd;
	}

    if (BitmapColumn < 64)	/* A wall was found (either X or Y) */
	{

	/* To avoid a fishbowl affect we need to adjust the distance so */
	/* it appears perpendicular to the center point of the display	*/
	/* which is relative angle 0 from the players current angle. We */
	/* started at -30 degrees for the first screen column and will	*/
	/* cycle from -30 down to 0 then back up to +30 degrees. This	*/
	/* cosine value was pre-calculated and placed in ViewCosTable.	*/
	WallDistance *= ViewCosTable[i];

	/* Now we strip off somemore decimal points and check round-up	*/
	xd = WallDistance >> 14;
	if (WallDistance - (xd << 14) >= 8096)
	    xd++;

	/* The last decimal points from the multiplication after the X and */
	/* Y rays is stripped off and checked for round-up.		   */
	WallDistance = xd >> 6;
	if (xd - (WallDistance << 6) >= 32)
	    WallDistance++;

	/* Don't really need to, but put it into an integer for fast compare */
	distance = WallDistance;

	/* This is an arbitrary minimum distance to look for */
	if (distance < 10)
	    distance = 10;

	/* Don't want it to go outside our table boundaries */
	if (distance >= MAX_DISTANCE)
	    distance = MAX_DISTANCE - 1;

	/* Save the wall data to display when done with entire screen */
	Walls[i].Distance = distance;
	Walls[i].Number = (BitmapNumber & 0x3F) + WallCode;
	Walls[i].Column = BitmapColumn;

	if (distance > MaxDistance)
	    MaxDistance = distance;
	}

    ViewAngle++;
    if (ViewAngle >= INT_ANGLE_360)
	ViewAngle -= INT_ANGLE_360;

    }

xd = CLOCK_PTR;
/* This routine gets the Walls structure and draws all of the walls */
DrawWalls();
tDOW += CLOCK_PTR - xd;

/* Now we look at any objects that may be closer than the walls */
FindObject(xPlayer,yPlayer,PlayerAngle);

xd = CLOCK_PTR;
/* Reset mask to allow all planes then flip to the page we just drew to */
#if 0
port_out(255, 0x3c5);
#endif
flippage();
#if 0
PageNum ^= 1;
usepage(PageNum);
#endif
tFlip += CLOCK_PTR - xd;

}

/****************************************************************************
** This routine is used when the player moves location as well as by the   **
** moveable objects. Its purpose is to determine if a valid obstacle is	   **
** in front of the player or object.					   **
**									   **
****************************************************************************/
int CheckHit(int xPlayer,int yPlayer,int ViewAngle)
{
    int	    BitmapColumn,BitmapNumber,yBitmap,distance;
    int	    i,WallCode;
    long    WallDistance,xd,yd,yDistance;
    long    CheckDist;

WallDistance = 3000000;	    /* Set to a ridiculous value */
WallCode     = 3;	    /* Initialize to strike an object */
CheckDist    = 48L;	    /* Initial minimum distance to look for */
BitmapNumber = 0;	    /* Initialize to no bitmap found */

/* Clear out any objects seen from previous passes */
memset(ObjFlags,0,MaxObjects);

/* Set number of objects seen on this pass */
TotalObjects = 0;

/* Don't allow one of these angles, causes either the X or Y ray to not be */
/* cast which gives a false reading about an obstacle.			   */
if (ViewAngle == INT_ANGLE_45 ||
    ViewAngle == INT_ANGLE_135 ||
    ViewAngle == INT_ANGLE_225 ||
    ViewAngle == INT_ANGLE_315)
    ViewAngle++;

/* Don't cast an X ray if no chance of striking a X wall */
if (ViewAngle != INT_ANGLE_90 && ViewAngle != INT_ANGLE_270)
    {
    BitmapNumber = xRay(xPlayer,yPlayer,ViewAngle);

    if (BitmapNumber)
	{
	xd = iLastX - xPlayer;

	/* Use the delta X to determine the distance to the wall */
	WallDistance = (xd * InvCosTable[ViewAngle]) >> 14;

	if (WallDistance < 0)
	    WallDistance = 120000L;

	/* Set the wall struck code to an X wall */
	WallCode = 1;
	}
    }

/* Don't cast a Y ray if impossible to strike a Y wall */
if (ViewAngle != 0 && ViewAngle != INT_ANGLE_180)
    {
    yBitmap = yRay(xPlayer,yPlayer,ViewAngle);

    if (yBitmap)
	{
	yd = iLastY - yPlayer;

	/* Use the delta Y to determine distance to the wall */
	yDistance = (yd * InvSinTable[ViewAngle]) >> 14;
	if (yDistance < 0)
	    yDistance = 120000L;

	/* If Y wall closer than X wall then use Y wall data */
	if (yDistance < WallDistance)
	    {
	    WallDistance = yDistance;

	    /* Indicate the wall struck was a Y wall */
	    WallCode = 2;
	    BitmapNumber = yBitmap;
	    }

	}

    }

/* Since doors appear in the middle of the wall, adjust the minimum distance */
/* to it. This handles walking up close to a door.			     */
if (BitmapNumber >= DOOR_XCODE)
    CheckDist += 34L;

if (WallCode)
    {
    /* Adjust the distance based on the center of the screen */
    WallDistance *= ViewCosTable[160];

    /* Remove fixed point and round-up */
    xd = WallDistance >> 14;
    if (WallDistance - (xd << 14) >= 8096)
	xd++;

    /* Remove initial fixed point and round-up */
    WallDistance = xd >> 6;
    if (xd - (WallDistance << 6) >= 32)
	WallDistance++;

    /* Get minimum distance to any objects between player and wall */
    yDistance = CheckObjects(xPlayer,yPlayer,ViewAngle);

    /* An object was found, if closer than use object as obstacle */
    if (yDistance > 0L && yDistance < WallDistance)
	{
	/* Return code for an object struck */
	WallCode = 3;
	/* Adjust minimum distance to keep object from getting too close */
	CheckDist += 32L;
	WallDistance = yDistance;
	}

    /* If the wall or object is further than the minimum distance, we can */
    /* continue moving in this direction.				  */
    if (WallDistance > CheckDist)
	WallCode = 0;
    }

return(WallCode);
}

