ack3d/ack_lib/ACKOBJ.C
Gered 83aaf0d5b9 update acklib to build under watcom
borland conditionals have been removed. asm sources converted fully
to tasm ideal mode. keyboard and timer interrupt handlers moved to c
code copied from fdemo watcom sources
2019-11-02 13:56:52 -04:00

461 lines
19 KiB
C

// This source file contains the internal functions needed to add objects
// to the slice structures as a view is being built.
// (c) 1995 ACK Software (Lary Myers)
#include <stdio.h>
#include <string.h>
#include "ack3d.h" // Main ACK-3D internal and interface data structures
#include "ackeng.h" // Internal data structures and constants
#include "ackext.h" // Defines external (global) variables
extern short gWinStartX; // Global variables to define the left and
extern short gWinEndX; // right edge of the viewport
// A function pointer to refernce the actual routine used to build a wall slice
extern void (*WallMaskRtn)(void);
//±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±
// Internal function called by FindObject(). Your programs may call this
// function if they need to calculate the angle between two points. dx and
// dy represent the deltas between the two points. (i.e. dx = x1 - x and
// dy = y1 - y)
//
// Quadrants
// 2 | 3 If the object is in quadrants 0 or 2, we need
// ---+--- to add the resulting angle to the quad value less
// 1 | 0 than the resulting angle. If the object is in
// quadrants 1 or 3, we need to subtract the
// resulting angle from the next higher quadrant
// value. This is because quads 1 and 3 are negative
// values returned from arctan, while 0 and 2 are
// positive.
//
// The angle between the two points is determined by using the formula:
// tan (angle) = dy/dx. The look-up table LongTanTable[] is used to
// access tangent values of angles.
//±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±
short AckGetObjectAngle(long dx,long dy)
{
short i,quadrant,objAngle;
short Beg;
long avalue;
if (dx == 0 || dy == 0) // Test to see if angle is 0, 90, 180, or 270
{
if (dx == 0) // Distance is directly up or down
{
if (dy < 0) // Distance is straight up
return(INT_ANGLE_270);
return(INT_ANGLE_90);
}
if (dx < 0) // dy = 0; distance is directly left or right
return(INT_ANGLE_180);
return(0);
}
// Need to determine which quadrant is involved
quadrant = 0; // Set to quad 0 as default
if (dx < 0 && dy > 0) // We're in quad 1
quadrant = INT_ANGLE_180;
else
{
if (dx < 0 && dy < 0) // We're in quad 2
quadrant = INT_ANGLE_270;
else
{
if (dx > 0 && dy < 0) // We're in quad 3
quadrant = INT_ANGLE_360;
}
}
// Get the absolute values to use for our ratio
if (dy < 0)
dy = -dy;
if (dx < 0)
dx = -dx;
//=======================================================================
// Next we need to convert dy into the same fixed point representation
// as used in our tangent table. Then, we divide dy by dx (rise/run)
// to get the ratio so we can determine the tangent of the angle between
// the two pints. We use the ratio to search the tangent table
// and the index that is returned tells us what the actual angle is.
// We only need to check angles from 0 to 90 degrees. Later, the angle
// will be adjusted to take into account which quadrant we are in.
//=======================================================================
dy = dy << FP_SHIFT; // Make the dividend the same fixed point as the table
avalue = dy / dx; // Get our ratio to search for
// This ratio tells us the tangent of the angle
if (LongTanTable[INT_ANGLE_90-1] <= avalue) // Angle is 89 degrees
return(INT_ANGLE_90-1);
objAngle = 0; // Initialize angle to 0
//=============================================================================
// Now we use a binary lookup trick to speed up the search. This invloves
// a test to see if the angle is between o and 45 degrees or between 45 and
// 90 degrees. Then, we search the list sequentially to find the first value
// higher than our ratio.
//=============================================================================
Beg = 0; // Assume midpoint between 0 and 45 degrees
if (LongTanTable[INT_ANGLE_45] < avalue)
{
if (LongTanTable[360] < avalue)
Beg = 360; // Use angle of 360
else
Beg = INT_ANGLE_45; // Midpoint between 45 and 90 degrees
}
// Loop to check the tan table and find the correct angle
for (i = Beg; i < INT_ANGLE_90; i++)
{
if (LongTanTable[i] > avalue) // We've passed by the angle
{
objAngle = i - 1; // Get the correct angle
break;
}
}
if (objAngle < 0) // Adjust for angle=0
objAngle = 0;
//============================================================================
// Now we adjust the resulting angle based on the quadrant. If we are in
// quad 0 we do nothing. If we're in quads 1 and 3 we subtract the angle from
// the next higher quad angle. If we're in quad 2 we add the angle to the next
// lower quad angle to get the actual angle (0-1800) between the points.
//============================================================================
if (quadrant)
{
if (quadrant != INT_ANGLE_270)
objAngle = quadrant - objAngle;
else
objAngle += INT_ANGLE_180;
}
// Returns the angle between the two points. This value is mainly used for
// determining the angle between the POV and an object, but it could
// be used for generic purposes as well.
return(objAngle);
}
//±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±
// Internal function that returns the square root of a long value.
// This function is called by Find)bject().
//±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±
short long_sqrt(long v)
{
short i;
unsigned short result,tmp;
unsigned long low,high;
if (v <= 1L) return((unsigned)v); // Value is less than 1; return value
low = v; // Initialize starting variables
high = 0L;
result = 0;
for (i = 0; i < 16; i++)
{
result += result;
high = (high << 2) | ((low >>30) & 0x3);
low <<= 2; // Shift left by 2
tmp = result + result + 1;
if (high >= tmp)
{
result++;
high -= tmp;
}
}
return(result);
}
//±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±
// Internal function called by AckBuildView() which checks the list of
// objects found during the ray cast process and places the object slices
// into the wall slices.
//±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±
void FindObject(void)
{
short i,j,StartX,EndX;
short oCount;
short minAngle,maxAngle,objAngle;
short objColumn;
USHORT distance;
long dx,dy;
short count,SaveCenter;
short ObjNum,oQuad,pQuad,numsides,afact;
short NewX,NewY,LightFlag;
short MaxOpp,Column,ColBeg,ColEnd;
short wt,ObjIndex;
short vidwt,vidht,hoff;
short MaxObjs;
short SliceLen;
USHORT BmpColumn;
long xp,yp;
short wht;
UCHAR *wall,*ScreenBuffer;
UCHAR *pTable;
UCHAR **omaps;
SLICE *sa,*sa2,*saNext;
UCHAR *bmpFlags;
NEWOBJECT **oList;
NEWOBJECT *oPtr;
if (FoundObjectCount) // Make sure objects were found during ray casting
{
oList = &aeGlobal->ObjList[0]; // Get pointer to the array of objects
StartX = gWinStartX; // Starting column of view
EndX = gWinEndX; // Ending column of view
minAngle = PlayerAngle - (INT_ANGLE_32 + 10); // Starting angle of view
if (minAngle < 0) // Check for wrap-around at angle 0
minAngle += INT_ANGLE_360;
maxAngle = PlayerAngle + (INT_ANGLE_32 + 10); // Ending angle of view
if (maxAngle >= INT_ANGLE_360) // Check for wrap-around at angle 360
maxAngle -= INT_ANGLE_360;
TotalObjects = 0; // Stores nmber of objects in view
SliceLen = sizeof(SLICE) - 9; // Amount of slice we'll move later
// Loop and process each object in the view. This invloves setting up
// a few arrays to store the object number, the distance from the player
// to the object, the viewing angle, and the view column where the object
// will be displayed.
for (oCount = 0; oCount < FoundObjectCount; oCount++)
{
i = ObjectsSeen[oCount]; // Get index to possible object
oPtr = oList[i]; // Pointer to object structure
if (oPtr == NULL) // Make sure it's a valid object
continue;
if (!oPtr->Active) // Make sure it's visible
continue;
dx = oPtr->x - xPglobal; // Get the delta x,y between the
dy = oPtr->y - yPglobal; // object and the player
// Calculate the angle the object is relative to the player
if ((objAngle = AckGetObjectAngle(dx,dy)) < 0)
continue; // Negative angle means it can't be seen
// Here we determine if the POV is looking toward the right or
// the left and the actual column of the view (from 0 to view width)
// where the object would be seen.
if (minAngle > maxAngle) // If looking towards the right
{
if (objAngle >= minAngle) // Cal. view column of object
objColumn = objAngle - minAngle;
else
objColumn = (objAngle+INT_ANGLE_360) - minAngle;
}
else
{
objColumn = objAngle - minAngle; // Calc. view column of object
}
// Get the distance to the object
distance = long_sqrt((dx * dx) + (dy * dy));
// No need to check further if it's too far away
if (distance >= MaxDistance)
continue;
// Place the objects in the correct order so further ones are behind
j = TotalObjects; // Current number of objects we've found
if (j)
{
// Sort the objects found by distance so that further ones
// are drawn BEFORE closer ones.
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];
ObjColumn[j] = ObjColumn[j-1];
ObjAngle[j] = ObjAngle[j-1];
}
j = count;
count = TotalObjects;
}
}
}
// Hold onto relavant data for the object found
ObjNumber[j] = i; // Store the object number
ObjRelDist[j] = distance; // Store the distance to the object
ObjColumn[j] = objColumn; // Store view column where object resides
ObjAngle[j] = objAngle; // Store the viewing angle
TotalObjects++; // Bump the count of objects in view
ObjRelDist[TotalObjects] = 0L; // Set to relative dist. in next object to 0
}
// Didn't find any objects on the above pass, so we're done
if (!TotalObjects)
return;
omaps = &aeGlobal->oMaps[0]; // Bitmaps used for objects
pQuad = PlayerAngle / INT_ANGLE_45; // Quadrant POV is facing
// Check each object in the list to be displayed and get the object's
// bitmap. Also, calulate the width and height of the object.
// This loop also checks to see if an object has multiple sides
// and it determines which bitmap should be used to display the object.
for (i = 0; i < TotalObjects; i++)
{
ObjIndex = ObjNumber[i]; // Actual object found
oPtr = oList[ObjIndex]; // Pointer to object structure
if (oPtr == NULL) // Again check for a null object
continue;
// Current bitmap for the object (this number can change if the
// object is animated)
ObjNum = oPtr->CurrentBitmaps[oPtr->CurrentBm];
distance = ObjRelDist[i]; // Get relative distance to object
// Make sure distance is within a reasonable entry in our
// pre-calculated table
if (distance >= (MAX_DISTANCE - 10))
distance = MAX_DISTANCE-11;
// Get the width of the object
wt = DistanceTable[distance]; // Adjust the width using the distance
// Keep the width of the object reasonable
if (wt > 300) // The object is too wide
continue; // Skip over
if (wt < 6) wt = 6; // Adjust if too small
// Get the scale factor which was pre-calculated based on
// distance in AckInitialize() function
yp = AdjustTable[distance];
xp = 0; // First col of the object to display
NewX = ObjColumn[i]; // View column where object resides
// Check if object has multiple sides. If so we need to determine
// the correct bitmap to display based on the angle between the
// POV and the object. We'll perform a trick here by breaking down
// the problem into quadrants and then use the quadrant to determine
// which side we're facing. The object itself is facing a certain
// angle (stored in the Dir field of the object structure) so this
// needs to be taken into account as well.
if (oPtr->Flags & OF_MULTIVIEW)
{
afact = oPtr->aFactor; // Get the angles per side of object
numsides = oPtr->Sides; // Get total sides for this object
pQuad = ObjAngle[i] / afact; // Get the quadrant from POV to object
oQuad = oPtr->Dir / afact; // Get the quadrant it wants to face
// The difference between the POV-Object angle and the angle the
// object is facing determines the actual side of the object that
// can currently be seen
j = (pQuad - oQuad) + (numsides >> 1);
// Check for wrap-around and keep within range
if (j >= numsides)
j -= numsides;
// Check wrap-around in both directions
if (j < 0)
j += numsides;
// Calculate which bitmap set we should use (each side could
// have multiple bitmaps for animation)
j *= oPtr->BitmapsPerView;
j += oPtr->CurrentBm;
// Get the actual bitmap for this side and animation
ObjNum = oPtr->CurrentBitmaps[j];
}
// Done processing multiple sides. Next, find the
// ending column based on the starting column plus the scaled
// width of the object.
ColEnd = NewX + wt;
// Finally get the pointer to the actual bitmap
wall = omaps[ObjNum];
// Pick up the transparent flags at end of bitmap
bmpFlags = &wall[BITMAP_SIZE];
j = distance;
// Loop from starting column to ending column and fold in the
// object into the appropriate slice structure.
for (Column = NewX - wt; Column < ColEnd; Column++)
{
// Make sure column is within view width
if (Column >= StartX && Column <= EndX)
{
// Scale bitmap column down from fixed point
BmpColumn = xp >> FP_SHIFT;
if (bmpFlags[BmpColumn]) // If transparent column
goto keepgoing; // Ouch! But it works
j = distance;
// Account for fisheye effect
dy = ViewCosTable[Column] >> 2;
dx = distance * dy;
// Now we strip off somemore decimal points and check round-up
dy = dx >> 12;
if (dx - (dy << 12) >= 4096)
dy++;
if (dy > 32L)
j = dy;
// Now we pick up the slice for this column and insert sort
// the object slice based on the distance. This allows objects
// to appear between walls at various distances, behind
// transparent walls, and so on.
sa = &Slice[Column]; // Access the corresponding slice
if (sa->Active) // Multiple slices for this column?
{
while (sa != NULL)
{
if (j <= sa->Distance)
{
sa2 = sa;
while (sa2->Next != NULL) // Go to end of slices
sa2 = sa2->Next;
saNext = sa2->Prev;
while (sa2 != sa) // Move slice down to create
{ // a space for new slice
memcpy(sa2,saNext,sizeof(SLICE)-9);
sa2->Active = saNext->Active;
sa2 = sa2->Prev;
saNext = saNext->Prev;
}
// Fill in the slice structure with the
// info about the object
sa->Distance = distance;
sa->bNumber = ObjNum;
sa->bColumn = BmpColumn;
sa->bMap = omaps;
sa->Active = 1;
sa->Type = ST_OBJECT;
sa->Fnc = WallMaskRtn;
break;
}
if (!sa->Active)
break;
sa = sa->Next;
}
}
else // Only one slice is used for this column (typical)
{
if (j <= sa->Distance) // Only put it in if object is
{ // closer than current slice
sa->Active = 1;
saNext = sa->Next;
memcpy(saNext,sa,sizeof(SLICE)-9);
sa->Distance = distance;
sa->bColumn = BmpColumn;
sa->bNumber = ObjNum;
sa->bMap = omaps;
sa->Type = ST_OBJECT;
sa->Fnc = WallMaskRtn;
saNext->Active = 0;
}
}
}
keepgoing:
xp += yp; // Advance the next column to display (scaling)
}
}
}
}
// **** End of Source ****