/******************* ( Animation Construction Kit 3D ) ***********************/
/*			 Build Current Viewport				     */
/* CopyRight (c) 1993	   Author: Lary Myers				     */
/*****************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
#include <mem.h>
#include <alloc.h>
#include <io.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <sys\stat.h>
#include "ack3d.h"
#include "ackeng.h"
#include "ackext.h"

void draw_col2(int Col,int slice,int dist,int width,int ht,UCHAR far *Wall,UCHAR far *Screen,UCHAR far *PalTable,int LightFlag,int hoff);
void AckCopyBackground(UCHAR far *scrn,UCHAR far *bkgd,int len,int offset);
void DrawSlices(ACKENG *ae);

void FindObject(int xPlayer,int yPlayer,int PlayerAngle,ACKENG *ae);
UINT xRay(int x,int y,int angle,ACKENG *ae);
UINT yRay(int x,int y,int angle,ACKENG *ae);
int FindDoor(int MapPosn,ACKENG *ae);
long long_sqrt(long v);

void CheckDoors(ACKENG *ae);

extern UCHAR	xLight;
extern UCHAR	yLight;

/****************************************************************************
** This is the main routine the application should call to create the 3D   **
** view of the current POV. This routine will build all the walls and	   **
** objects in the current view into the ScreenBuffer pointer within the	   **
** ACKENG structure. No display to the video is done by this routine. The  **
** application can determine when and how actual display is to be done.	   **
**									   **
****************************************************************************/
int AckBuildView(ACKENG *ae)
{
	    int	    i,j,index,xPlayer,yPlayer,PlayerAngle;
	    UINT    xBitmap,yBitmap,BitmapNumber;
	    int	    ViewAngle;
	    int	    DoorOpenColumn;
	    long    xDistance,yDistance;
	    long    WallDistance;
	    int	    distance,LightAdj;
	    UINT    BitmapColumn,yBitmapColumn;
	    int	    OldMapPosn,OldMapPosn1;
	    long    xd,yd;
	    UINT    offset;

CheckDoors(ae);
xPlayer = ae->xPlayer;
yPlayer = ae->yPlayer;
PlayerAngle = ae->PlayerAngle;

/* Begin looking 30 degrees to the left of our current angle */
ViewAngle = PlayerAngle - INT_ANGLE_30;

if (ViewAngle < 0)
    ViewAngle += INT_ANGLE_360;

MaxDistance = 0;
LightAdj = 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	*/

/* Don't even cast an X ray if impossible to intersect the X walls */
    if (ViewAngle != INT_ANGLE_90 && ViewAngle != INT_ANGLE_270)
	{
	BitmapNumber = xRay(xPlayer,yPlayer,ViewAngle,ae);

	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 & 0xFF) == DOOR_XCODE)
		{
		index = FindDoor(xMapPosn,ae);
		if (index >= 0)			    /* This is a valid door */
		    {
		    j = ae->Door[index].ColOffset;  /* Get its current pos  */
		    offset = 0;

		if (BitmapNumber & DOOR_TYPE_SLIDE)
		    {
		    DoorOpenColumn = 63;
		    if ((int)iLastX > xPlayer)	    /* Handle orientation   */
			j = -j;
		    BitmapColumn += j;		    /* Adjust column to show */
		    }

		if (BitmapNumber & DOOR_TYPE_SPLIT)
		    {
		    DoorOpenColumn = 31;
		    if (BitmapColumn < 32)
			{
			BitmapColumn += j;
			if (BitmapColumn > 31)
			    offset = 1;
			}
		    else
			{
			BitmapColumn -= j;
			if (BitmapColumn < 32)
			    offset = 1;
			}

		    }


		    if (offset == 1 || BitmapColumn > 63)
			{
			/* Get the grid coordinates for this door */
			OldMapPosn = ae->Door[index].mPos;
			OldMapPosn1 = ae->Door[index].mPos1;

			/* Fake the engine into thinking no door is there */
			ae->xGrid[OldMapPosn] = 0;
			ae->xGrid[OldMapPosn1] = 0;

			/* Cast the ray to get walls beyond the door */
			BitmapNumber = xRay(xPlayer,yPlayer,ViewAngle,ae);

			/* Put back the door codes if not fully open */
			if (ae->Door[index].ColOffset < DoorOpenColumn)
			    {
			    ae->xGrid[OldMapPosn] = ae->Door[index].mCode;
			    ae->xGrid[OldMapPosn1] = ae->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 multiply 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;

	    LightAdj = xLight;
	    }

	}

/* Don't cast a Y ray if its impossible to intercept any Y walls */
    if (ViewAngle != 0 && ViewAngle != INT_ANGLE_180)
	{
	yBitmap = yRay(xPlayer,yPlayer,ViewAngle,ae);

	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 & 0xFF) == DOOR_YCODE)
		{
		index = FindDoor(yMapPosn,ae);
		if (index >= 0)			/* This is a valid door */
		    {
		    /* Get the current door column offset */
		    j = ae->Door[index].ColOffset;
		    offset = 0;

		    /* Deal with orientation */

		if (yBitmap & DOOR_TYPE_SLIDE)
		    {
		    DoorOpenColumn = 63;
		    if ((int)iLastY < yPlayer)
			j = -j;
		    yBitmapColumn += j;
		    }

		if (yBitmap & DOOR_TYPE_SPLIT)
		    {
		    DoorOpenColumn = 31;
		    if (yBitmapColumn < 32)
			{
			yBitmapColumn += j;
			if (yBitmapColumn > 31)
			    offset = 1;
			}
		    else
			{
			yBitmapColumn -= j;
			if (yBitmapColumn < 32)
			    offset = 1;
			}
		    }

		    /* If beyond width of bitmap than cast again */
		    if (offset == 1 || yBitmapColumn > 63)
			{

			/* Get the yGrid coordinates for this door */
			OldMapPosn = ae->Door[index].mPos;
			OldMapPosn1 = ae->Door[index].mPos1;

			/* Fool the engine into thinking no door is there */
			ae->yGrid[OldMapPosn] = 0;
			ae->yGrid[OldMapPosn1] = 0;

			/* Cast again for walls beyond the door */
			yBitmap = yRay(xPlayer,yPlayer,ViewAngle,ae);

			/* Put door code back if not fully open */
			if (ae->Door[index].ColOffset < DoorOpenColumn)
			    {
			    ae->yGrid[OldMapPosn] = ae->Door[index].mCode;
			    ae->yGrid[OldMapPosn1] = ae->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 multiply 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)
		{
		WallDistance = yDistance;
		BitmapNumber = yBitmap;
		BitmapColumn = yBitmapColumn;
		LightAdj = yLight;
		}

	    }

	}

    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 & 0xFF;
	Walls[i].Column = BitmapColumn;
	Walls[i].LightAdj = LightAdj;

	if (distance > MaxDistance)
	    MaxDistance = distance;
	}

    ViewAngle++;
    if (ViewAngle >= INT_ANGLE_360)
	ViewAngle -= INT_ANGLE_360;

    }

DrawSlices(ae);

/* Now we look at any objects that may be closer than the walls */
FindObject(xPlayer,yPlayer,PlayerAngle,ae);

return(0);
}

/****************************************************************************
** Internal routine used to draw the walls, called by AckDrawView().	   **
**									   **
****************************************************************************/
void DrawSlices(ACKENG *ae)
{
    int	    i,x1,wt,ht,LightFlag,hoff;
    UCHAR   far *wall,*ScreenBuffer;
    UCHAR   far *pTable;
    UCHAR   far **bmaps;

AckCopyBackground(ae->ScreenBuffer,ae->BkgdBuffer,ae->WinLength,ae->WinStartOffset);

x1 = ae->WinEndX;

ht = ae->CenterRow;
wt = BYTES_PER_ROW;
hoff = ht * wt;
ScreenBuffer = ae->ScreenBuffer;
bmaps = ae->bMaps;
pTable = ae->PalTable;
LightFlag = ae->LightFlag;

for (i = ae->WinStartX; i < x1; i++)
    {
    wall = AckGetBitmapPtr(Walls[i].Number,bmaps);
    draw_col2(i,Walls[i].Column,Walls[i].Distance,wt,ht,wall,
	      ScreenBuffer,pTable,LightFlag,hoff);
    }
}


/****************************************************************************
** Internal routine used to determine which objects are visible in the	   **
** current POV. There is still one major anomaly with this routine in that **
** "ghost" objects tend to appear at certain angles and distances. Any	   **
** thoughts/comments on why this occurs would be greatly appreciated!	   **
**									   **
****************************************************************************/
void FindObject(int xPlayer,int yPlayer,int PlayerAngle,ACKENG *ae)
{
    int	    i,j,count,SaveCenter;
    int	    ObjX,ObjY,ObjNum;
    int	    NewX,NewY,LightFlag;
    int	    MaxOpp,Column,ColBeg,ColEnd;
    int	    wt,ObjIndex,CenterColumn;
    int	    vidwt,vidht,hoff;
    long    deltax,deltay;
    long    xp,yp,distance;
    long    SinValue,CosValue;
    UCHAR   far *wall,*ScreenBuffer;
    UCHAR   far *pTable;
    UCHAR   far **omaps;

    char    mBuf[40];

vidht = ae->CenterRow;
vidwt = BYTES_PER_ROW;
hoff = vidht * vidwt;
ScreenBuffer = ae->ScreenBuffer;
omaps = ae->oMaps;


TotalObjects = 0;
ObjRelDist[0] = 0L;
SinValue = SinTable[PlayerAngle];
CosValue = CosTable[PlayerAngle];

/* First thing we'll do is check all the objects to see which ones may be */
/* completely out of view, and to get some initial values for later...	  */
for (i = 0; i < MAX_OBJECTS; i++)
    {
    if (!ae->ObjList[i].Active)
	continue;

    ObjX = ae->ObjList[i].x;
    ObjY = ae->ObjList[i].y;

/* Translate the object coordinates to make relative to the POV */
    NewX = ObjX - xPlayer;
    NewY = ObjY - yPlayer;

    if (abs(NewX) < 10 && abs(NewY) < 10)
	continue;


#if 0
    if (PlayerAngle > INT_ANGLE_180 && (NewY-63) > 0)
	continue;

    if (PlayerAngle < INT_ANGLE_180 && (NewY+63) < 0)
	continue;

    if (PlayerAngle > INT_ANGLE_270 || PlayerAngle < INT_ANGLE_90)
	{
	if ((NewX+63) < 0)
	    continue;
	}

    if (PlayerAngle < INT_ANGLE_270 && PlayerAngle > INT_ANGLE_90)
	{
	if ((NewX-63) > 0)
	    continue;
	}
#endif

    if ((PlayerAngle == 0 || PlayerAngle == INT_ANGLE_180) && abs(NewX) < 2)
	continue;

    if ((PlayerAngle == INT_ANGLE_90 || PlayerAngle == INT_ANGLE_270) &&
	abs(NewY) < 2)
	continue;



/* Rotate coordinates to current player angle */
    xp = ((NewX * CosValue) + (NewY * SinValue)) >> FP_SHIFT;
    yp = ((NewY * CosValue) - (NewX * SinValue)) >> FP_SHIFT;

    if (xp <= 0)
	continue;

    distance = (long)long_sqrt((xp * xp) + (yp * yp));

    if (distance >= MAX_DISTANCE)
	continue;

/* Place the objects in the correct order so further ones are behind */
    j = TotalObjects;
    if (j)
	{
	for (count = 0; count < TotalObjects; count++)
	    {
	    if (distance > ObjRelDist[count])
		{
		for (j = TotalObjects; j > count; j--)
		    {
		    ObjRelDist[j] = ObjRelDist[j-1];
		    ObjNumber[j]  = ObjNumber[j-1];
		    ObjDeltaX[j]  = ObjDeltaX[j-1];
		    ObjDeltaY[j]  = ObjDeltaY[j-1];
		    }

		j = count;
		count = TotalObjects;
		}
	    }
	}
/* Hold onto relavant data for the object found */
    ObjNumber[j]  = i;
    ObjRelDist[j] = distance;
    ObjDeltaX[j] = xp;
    ObjDeltaY[j] = yp;
    TotalObjects++;
    ObjRelDist[TotalObjects] = 0L;
    }

/* Didn't find any objects on the above pass, so we're done */
if (!TotalObjects)
    return;

CenterColumn = ae->WinStartX + (ae->WinWidth / 2);

pTable = ae->PalTable;
LightFlag = ae->LightFlag;

for (i = 0; i < TotalObjects; i++)
    {
    ObjIndex = ObjNumber[i];
    ObjNum = ae->ObjList[ObjIndex].bmNum[ae->ObjList[ObjIndex].CurNum];

    ObjY = ae->ObjList[ObjIndex].y;
    NewY = ObjY - yPlayer;

    distance = ObjRelDist[i];
    deltax   = ObjDeltaX[i];
    yp = deltay = ObjDeltaY[i];

    MaxOpp = ((LongTanTable[INT_ANGLE_30] * (long)deltax) >> FP_SHIFT);

    if (NewY < ObjY)
	{
	MaxOpp = -MaxOpp;
	yp = -yp;
	}

    if ((yp+32) < MaxOpp)
	continue;

    Column = CenterColumn;

    if (MaxOpp)
	Column = CenterColumn - ((deltay * CenterColumn) / MaxOpp);

    yp = ViewCosTable[Column];
    if (yp)
	{
	distance = distance * yp;

	xp = distance >> 14;
	if (distance - (xp << 14) >= 8096)
	    xp++;

	distance = xp;
	}

    if (distance < 0)
	continue;

    if (distance >= (MAX_DISTANCE - 10))
	distance = MAX_DISTANCE-11;

    wt = DistanceTable[distance];

/* Keep the width of the object reasonable */

    if (wt > 300)
	continue;

    if (wt < 16) wt = 16;

    yp = AdjustTable[distance];
    xp = 0;			/* First col of the object to display */

    NewX = Column;

    if (ae->ObjList[ObjIndex].Sides)
	ObjNum =
	 ae->ObjList[ObjIndex].bmNum[((PlayerAngle / ae->ObjList[ObjIndex].Sides) & 7)];

    SaveCenter = ae->CenterRow;
    ae->CenterRow = ae->ObjList[ObjIndex].VidRow;
    ColEnd = NewX + wt;

    for (Column = NewX - wt; Column < ColEnd; Column++)
	{
	if (Column >= ae->WinStartX && Column <= ae->WinEndX)
	    {
	    if (distance < (Walls[Column].Distance + 10))
		{
		wall = AckGetBitmapPtr(ObjNum,omaps);

		draw_col2(Column,xp >> FP_SHIFT,distance,vidwt,vidht,wall,
			  ScreenBuffer,pTable,LightFlag,hoff);
		}

	    }
	xp += yp;   /* Advance the next column to display (scaling) */
	}

    ae->CenterRow = SaveCenter;
    }

}


