Files
SRB2Kart-Saturn/src/p_sight.c
T
Alug d2844e7087 add P_DivlineCrossedFast
to take more advantage of P_DivlineSideFast
2025-04-23 16:07:38 +02:00

465 lines
13 KiB
C

// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2021 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file p_sight.c
/// \brief Line of sight/visibility checks, uses REJECT lookup table
#include "doomdef.h"
#include "doomstat.h"
#include "p_local.h"
#include "p_slopes.h"
#include "r_main.h"
#include "r_state.h"
//
// P_CheckSight
//
// killough 4/19/98:
// Convert LOS info to struct for reentrancy and efficiency of data locality
typedef struct {
fixed_t sightzstart, t2x, t2y; // eye z of looker
divline_t strace; // from t1 to t2
fixed_t topslope, bottomslope; // slopes to top and bottom of target
fixed_t bbox[4];
} los_t;
static INT32 sightcounts[2];
typedef INT32 (*divlinefunc)(fixed_t x, fixed_t y, const divline_t *node);
typedef INT32 (*divlinecrossfunc)(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, const divline_t *node);
//
// P_DivlineSide
//
// Returns side 0 (front), 1 (back), or 2 (on).
//
// killough 4/19/98: made static, cleaned up
static INT32 P_DivlineSide(fixed_t x, fixed_t y, const divline_t *node)
{
fixed_t left, right;
return(
!node->dx ? x == node->x ? 2 : x <= node->x ? node->dy > 0 : node->dy < 0 :
!node->dy ? y == node->y ? 2 : y <= node->y ? node->dx < 0 : node->dx > 0 :
(right = ((y - node->y) >> FRACBITS) * (node->dx >> FRACBITS)) <
(left = ((x - node->x) >> FRACBITS) * (node->dy >> FRACBITS)) ? 0 :
right == left ? 2 : 1);
}
static inline INT32 P_DivlineSideFast(fixed_t x, fixed_t y, const divline_t *node)
{
INT64 v = ((INT64)y - node->y) * node->dx - ((INT64)x - node->x) * node->dy;
if (v == 0)
return 2;
return v > 0;
}
static inline INT32 P_DivlineCrossed(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, const divline_t *node)
{
return (P_DivlineSide(x1, y1, node) == P_DivlineSide(x2, y2, node));
}
static inline INT32 P_DivlineCrossedFast(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, const divline_t *node)
{
return (P_DivlineSideFast(x1, y1, node) == P_DivlineSideFast(x2, y2, node));
}
static boolean P_CrossSubsecPolyObj(polyobj_t *po, register los_t *los)
{
size_t i;
sector_t *polysec;
if (!(po->flags & POF_RENDERALL))
return true; // the polyobject isn't visible, so we can ignore it
polysec = po->lines[0]->backsector;
for (i = 0; i < po->numLines; ++i)
{
line_t *line = po->lines[i];
divline_t divl;
const vertex_t *v1,*v2;
fixed_t frac;
fixed_t topslope, bottomslope;
// already checked other side?
if (line->validcount == validcount)
continue;
line->validcount = validcount;
// OPTIMIZE: killough 4/20/98: Added quick bounding-box rejection test
if (line->bbox[BOXLEFT ] > los->bbox[BOXRIGHT ] ||
line->bbox[BOXRIGHT ] < los->bbox[BOXLEFT ] ||
line->bbox[BOXBOTTOM] > los->bbox[BOXTOP ] ||
line->bbox[BOXTOP] < los->bbox[BOXBOTTOM])
continue;
v1 = line->v1;
v2 = line->v2;
// line isn't crossed?
if (P_DivlineCrossed(v1->x, v1->y, v2->x, v2->y, &los->strace))
continue;
divl.dx = v2->x - (divl.x = v1->x);
divl.dy = v2->y - (divl.y = v1->y);
// line isn't crossed?
if (P_DivlineCrossed(los->strace.x, los->strace.y, los->t2x, los->t2y, &divl))
continue;
frac = P_InterceptVector(&los->strace, &divl);
// get slopes of top and bottom of this polyobject line
topslope = FixedDiv(polysec->ceilingheight - los->sightzstart , frac);
bottomslope = FixedDiv(polysec->floorheight - los->sightzstart , frac);
if (topslope >= los->topslope && bottomslope <= los->bottomslope)
return false; // view completely blocked
}
return true;
}
//
// P_CrossSubsector
//
// Returns true if strace crosses the given subsector successfully.
//
static boolean P_CrossSubsector(size_t num, register los_t *los, boolean fast)
{
seg_t *seg;
INT32 count;
polyobj_t *po; // haleyjd 02/23/06
#ifdef RANGECHECK
if (num >= numsubsectors)
I_Error("P_CrossSubsector: ss %s with numss = %s\n", sizeu1(num), sizeu2(numsubsectors));
#endif
// haleyjd 02/23/06: this assignment should be after the above check
seg = segs + subsectors[num].firstline;
// haleyjd 02/23/06: check polyobject lines
if ((po = subsectors[num].polyList))
{
while (po)
{
if (po->validcount != validcount)
{
po->validcount = validcount;
if (!P_CrossSubsecPolyObj(po, los))
return false;
}
po = (polyobj_t *)(po->link.next);
}
}
const divlinecrossfunc divlinecrossFunc = fast ? P_DivlineCrossedFast : P_DivlineCrossed;
for (count = subsectors[num].numlines; --count >= 0; seg++) // check lines
{
line_t *line = seg->linedef;
divline_t divl;
fixed_t popentop, popenbottom;
const sector_t *front, *back;
const vertex_t *v1,*v2;
fixed_t frac;
fixed_t frontf, backf, frontc, backc;
fixed_t fracx, fracy;
// already checked other side?
if (line->validcount == validcount)
continue;
line->validcount = validcount;
// OPTIMIZE: killough 4/20/98: Added quick bounding-box rejection test
if (line->bbox[BOXLEFT ] > los->bbox[BOXRIGHT ] ||
line->bbox[BOXRIGHT ] < los->bbox[BOXLEFT ] ||
line->bbox[BOXBOTTOM] > los->bbox[BOXTOP ] ||
line->bbox[BOXTOP] < los->bbox[BOXBOTTOM])
continue;
v1 = line->v1;
v2 = line->v2;
// line isn't crossed?
if (divlinecrossFunc(v1->x, v1->y, v2->x, v2->y, &los->strace))
continue;
divl.dx = v2->x - (divl.x = v1->x);
divl.dy = v2->y - (divl.y = v1->y);
// line isn't crossed?
if (divlinecrossFunc(los->strace.x, los->strace.y, los->t2x, los->t2y, &divl))
continue;
// stop because it is not two sided anyway
if (!(line->flags & ML_TWOSIDED))
return false;
// calculate fractional intercept (how far along we are divided by how far we are from t2)
frac = P_InterceptVector(&los->strace, &divl);
front = seg->frontsector;
back = seg->backsector;
// calculate position at intercept
fracx = los->strace.x + FixedMul(los->strace.dx, frac);
fracy = los->strace.y + FixedMul(los->strace.dy, frac);
// calculate sector heights
frontf = P_GetSectorFloorZAt (front, fracx, fracy);
frontc = P_GetSectorCeilingZAt(front, fracx, fracy);
backf = P_GetSectorFloorZAt (back , fracx, fracy);
backc = P_GetSectorCeilingZAt(back , fracx, fracy);
// crosses a two sided line
// no wall to block sight with?
if (frontf == backf && frontc == backc
&& !front->ffloors & !back->ffloors) // (and no FOFs)
continue;
// possible occluder
// because of ceiling height differences
popentop = min(frontc, backc);
// because of floor height differences
popenbottom = max(frontf, backf);
// quick test for totally closed doors
if (popenbottom >= popentop)
return false;
if (frontf != backf)
{
fixed_t slope = FixedDiv(popenbottom - los->sightzstart , frac);
if (slope > los->bottomslope)
los->bottomslope = slope;
}
if (frontc != backc)
{
fixed_t slope = FixedDiv(popentop - los->sightzstart , frac);
if (slope < los->topslope)
los->topslope = slope;
}
if (los->topslope <= los->bottomslope)
return false;
// Monster Iestyn: check FOFs!
if (front->ffloors || back->ffloors)
{
ffloor_t *rover;
fixed_t topslope, bottomslope;
fixed_t topz, bottomz;
// check front sector's FOFs first
for (rover = front->ffloors; rover; rover = rover->next)
{
if (!(rover->flags & FF_EXISTS)
|| !(rover->flags & FF_RENDERSIDES) || (rover->flags & (FF_TRANSLUCENT|FF_FOG)))
{
continue;
}
topz = P_GetFFloorTopZAt (rover, fracx, fracy);
bottomz = P_GetFFloorBottomZAt(rover, fracx, fracy);
topslope = FixedDiv( topz - los->sightzstart, frac);
bottomslope = FixedDiv(bottomz - los->sightzstart, frac);
if (topslope >= los->topslope && bottomslope <= los->bottomslope)
return false; // view completely blocked
}
// check back sector's FOFs as well
for (rover = back->ffloors; rover; rover = rover->next)
{
if (!(rover->flags & FF_EXISTS)
|| !(rover->flags & FF_RENDERSIDES) || (rover->flags & (FF_TRANSLUCENT|FF_FOG)))
{
continue;
}
topz = P_GetFFloorTopZAt (rover, fracx, fracy);
bottomz = P_GetFFloorBottomZAt(rover, fracx, fracy);
topslope = FixedDiv( topz - los->sightzstart, frac);
bottomslope = FixedDiv(bottomz - los->sightzstart, frac);
if (topslope >= los->topslope && bottomslope <= los->bottomslope)
return false; // view completely blocked
}
// TODO: figure out if it's worth considering partially blocked cases or not?
// maybe to adjust los's top/bottom slopes if needed
}
}
// passed the subsector ok
return true;
}
//
// P_CrossBSPNode
// Returns true
// if strace crosses the given node successfully.
//
// killough 4/20/98: rewritten to remove tail recursion, clean up, and optimize
static boolean P_CrossBSPNode(INT32 bspnum, register los_t *los, boolean fast)
{
const divlinefunc divlineFunc = fast ? P_DivlineSideFast : P_DivlineSide;
while (!(bspnum & NF_SUBSECTOR))
{
register node_t *bsp = nodes + bspnum;
INT32 side = divlineFunc(los->strace.x, los->strace.y, (divline_t *)bsp) & 1;
INT32 side2 = divlineFunc(los->t2x, los->t2y, (divline_t *) bsp);
if (side == side2)
{
// doesn't touch the other side
bspnum = bsp->children[side];
}
else // the partition plane is crossed here
{
if (!P_CrossBSPNode(bsp->children[side], los, fast))
return false; // cross the starting side
bspnum = bsp->children[side^1]; // cross the ending side
}
}
return P_CrossSubsector((bspnum == -1 ? 0 : bspnum & ~NF_SUBSECTOR), los, fast);
}
//
// P_CheckSight
//
// Returns true if a straight line between t1 and t2 is unobstructed.
// Uses REJECT.
//
boolean P_CheckSight2(mobj_t *t1, mobj_t *t2, boolean fast)
{
const sector_t *s1, *s2;
size_t pnum;
los_t los;
// First check for trivial rejection.
if (!t1 || !t2)
return false;
I_Assert(!P_MobjWasRemoved(t1));
I_Assert(!P_MobjWasRemoved(t2));
if (!t1->subsector || !t2->subsector
|| !t1->subsector->sector || !t2->subsector->sector)
return false;
s1 = t1->subsector->sector;
s2 = t2->subsector->sector;
pnum = (s1-sectors)*numsectors + (s2-sectors);
if (rejectmatrix != NULL)
{
// Check in REJECT table.
if (rejectmatrix[pnum>>3] & (1 << (pnum&7))) // can't possibly be connected
return false;
}
// killough 11/98: shortcut for melee situations
// same subsector? obviously visible
// haleyjd 02/23/06: can't do this if there are polyobjects in the subsec
if (!t1->subsector->polyList &&
t1->subsector == t2->subsector)
return true;
// An unobstructed LOS is possible.
// Now look from eyes of t1 to any part of t2.
sightcounts[1]++;
validcount++;
los.topslope =
(los.bottomslope = t2->z - (los.sightzstart =
t1->z + t1->height -
(t1->height>>2))) + t2->height;
los.strace.dx = (los.t2x = t2->x) - (los.strace.x = t1->x);
los.strace.dy = (los.t2y = t2->y) - (los.strace.y = t1->y);
if (t1->x > t2->x)
los.bbox[BOXRIGHT] = t1->x, los.bbox[BOXLEFT] = t2->x;
else
los.bbox[BOXRIGHT] = t2->x, los.bbox[BOXLEFT] = t1->x;
if (t1->y > t2->y)
los.bbox[BOXTOP] = t1->y, los.bbox[BOXBOTTOM] = t2->y;
else
los.bbox[BOXTOP] = t2->y, los.bbox[BOXBOTTOM] = t1->y;
// Prevent SOME cases of looking through 3dfloors
//
// This WILL NOT work for things like 3d stairs with monsters behind
// them - they will still see you! TODO: Fix.
//
if (s1 == s2) // Both sectors are the same.
{
ffloor_t *rover;
fixed_t topz1, bottomz1; // top, bottom heights at t1's position
fixed_t topz2, bottomz2; // likewise but for t2
for (rover = s1->ffloors; rover; rover = rover->next)
{
// Allow sight through water, fog, etc.
/// \todo Improve by checking fog density/translucency
/// and setting a sight limit.
if (!(rover->flags & FF_EXISTS)
|| !(rover->flags & FF_RENDERPLANES) || (rover->flags & (FF_TRANSLUCENT|FF_FOG)))
{
continue;
}
topz1 = P_GetFFloorTopZAt (rover, t1->x, t1->y);
topz2 = P_GetFFloorTopZAt (rover, t2->x, t2->y);
bottomz1 = P_GetFFloorBottomZAt(rover, t1->x, t1->y);
bottomz2 = P_GetFFloorBottomZAt(rover, t2->x, t2->y);
// Check for blocking floors here.
if ((los.sightzstart < bottomz1 && t2->z >= topz2)
|| (los.sightzstart >= topz1 && t2->z + t2->height < bottomz2))
{
// no way to see through that
return false;
}
if (rover->flags & FF_SOLID)
continue; // shortcut since neither mobj can be inside the 3dfloor
if (rover->flags & FF_BOTHPLANES || !(rover->flags & FF_INVERTPLANES))
{
if (los.sightzstart >= topz1 && t2->z + t2->height < topz2)
return false; // blocked by upper outside plane
if (los.sightzstart < bottomz1 && t2->z >= bottomz2)
return false; // blocked by lower outside plane
}
if (rover->flags & FF_BOTHPLANES || rover->flags & FF_INVERTPLANES)
{
if (los.sightzstart < topz1 && t2->z >= topz2)
return false; // blocked by upper inside plane
if (los.sightzstart >= bottomz1 && t2->z + t2->height < bottomz2)
return false; // blocked by lower inside plane
}
}
}
// the head node is the last node output
return (P_CrossBSPNode((INT32)numnodes - 1, &los, fast));
}