* typo in calculating end position from a trace vector * must use floor to convert from floating point block coordinate to block index to account for running off the negative side of the blockmap. (Int cast always rounds toward zero which is wrong here.) * bad calculation of sight checking slopes - they has the actor's z coordinate duplicated. - fixed scaling of automap markers.
926 lines
23 KiB
C++
926 lines
23 KiB
C++
//**************************************************************************
|
|
//**
|
|
//** p_sight.cpp : Heretic 2 : Raven Software, Corp.
|
|
//**
|
|
//** $RCSfile: p_sight.c,v $
|
|
//** $Revision: 1.1 $
|
|
//** $Date: 95/05/11 00:22:50 $
|
|
//** $Author: bgokey $
|
|
//**
|
|
//**************************************************************************
|
|
|
|
#include <assert.h>
|
|
|
|
#include "doomdef.h"
|
|
#include "i_system.h"
|
|
#include "p_local.h"
|
|
#include "p_maputl.h"
|
|
#include "p_blockmap.h"
|
|
#include "m_random.h"
|
|
#include "m_bbox.h"
|
|
#include "p_lnspec.h"
|
|
#include "g_level.h"
|
|
#include "po_man.h"
|
|
#include "r_utility.h"
|
|
#include "b_bot.h"
|
|
#include "p_spec.h"
|
|
|
|
// State.
|
|
#include "r_state.h"
|
|
|
|
#include "stats.h"
|
|
|
|
static FRandom pr_botchecksight ("BotCheckSight");
|
|
static FRandom pr_checksight ("CheckSight");
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
P_CheckSight
|
|
|
|
This uses specialized forms of the maputils routines for optimized performance
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
// Performance meters
|
|
static int sightcounts[6];
|
|
static cycle_t SightCycles;
|
|
static cycle_t MaxSightCycles;
|
|
|
|
enum
|
|
{
|
|
SO_TOPFRONT = 1,
|
|
SO_TOPBACK = 2,
|
|
SO_BOTTOMFRONT = 4,
|
|
SO_BOTTOMBACK = 8,
|
|
|
|
};
|
|
|
|
struct SightOpening
|
|
{
|
|
double top;
|
|
double bottom;
|
|
int range;
|
|
int portalflags;
|
|
|
|
void SwapSides()
|
|
{
|
|
portalflags = ((portalflags & (SO_TOPFRONT | SO_BOTTOMFRONT)) << 1) | ((portalflags & (SO_TOPBACK | SO_BOTTOMBACK)) >> 1);
|
|
}
|
|
};
|
|
|
|
struct SightTask
|
|
{
|
|
double Frac;
|
|
double topslope;
|
|
double bottomslope;
|
|
int direction;
|
|
int portalgroup;
|
|
};
|
|
|
|
|
|
static TArray<intercept_t> intercepts (128);
|
|
static TArray<SightTask> portals(32);
|
|
|
|
class SightCheck
|
|
{
|
|
DVector3 sightstart;
|
|
DVector2 sightend;
|
|
double Startfrac;
|
|
|
|
AActor * seeingthing;
|
|
double Lastztop; // z at last line
|
|
double Lastzbottom; // z at last line
|
|
sector_t * lastsector; // last sector being entered by trace
|
|
double topslope, bottomslope; // slopes to top and bottom of target
|
|
int Flags;
|
|
divline_t Trace;
|
|
int portaldir;
|
|
int portalgroup;
|
|
bool portalfound;
|
|
unsigned int myseethrough;
|
|
|
|
void P_SightOpening(SightOpening &open, const line_t *linedef, double x, double y);
|
|
bool PTR_SightTraverse (intercept_t *in);
|
|
bool P_SightCheckLine (line_t *ld);
|
|
int P_SightBlockLinesIterator (int x, int y);
|
|
bool P_SightTraverseIntercepts ();
|
|
|
|
public:
|
|
bool P_SightPathTraverse ();
|
|
|
|
void init(AActor * t1, AActor * t2, sector_t *startsector, SightTask *task, int flags)
|
|
{
|
|
sightstart = t1->PosRelative(task->portalgroup);
|
|
sightend = t2->PosRelative(task->portalgroup);
|
|
sightstart.Z += t1->Height / 2;
|
|
|
|
Startfrac = task->Frac;
|
|
Trace = { sightstart.X, sightstart.Y, sightend.X - sightstart.X, sightend.Y - sightstart.Y };
|
|
Lastztop = Lastzbottom = sightstart.Z;
|
|
lastsector = startsector;
|
|
seeingthing=t2;
|
|
topslope = task->topslope;
|
|
bottomslope = task->bottomslope;
|
|
Flags = flags;
|
|
portaldir = task->direction;
|
|
portalfound = false;
|
|
|
|
myseethrough = FF_SEETHROUGH;
|
|
}
|
|
};
|
|
|
|
//==========================================================================
|
|
//
|
|
// P_SightOpening
|
|
//
|
|
// Simplified version that removes everything not needed for a sight check
|
|
//
|
|
//==========================================================================
|
|
|
|
void SightCheck::P_SightOpening(SightOpening &open, const line_t *linedef, double x, double y)
|
|
{
|
|
open.portalflags = 0;
|
|
sector_t *front = linedef->frontsector;
|
|
sector_t *back = linedef->backsector;
|
|
|
|
if (back == NULL)
|
|
{
|
|
// single sided line
|
|
if (linedef->flags & ML_PORTALCONNECT)
|
|
{
|
|
if (!front->PortalBlocksSight(sector_t::ceiling)) open.portalflags |= SO_TOPFRONT;
|
|
if (!front->PortalBlocksSight(sector_t::floor)) open.portalflags |= SO_BOTTOMFRONT;
|
|
}
|
|
|
|
open.range = 0;
|
|
return;
|
|
}
|
|
|
|
|
|
double fc = 0, ff = 0, bc = 0, bf = 0;
|
|
|
|
if (linedef->flags & ML_PORTALCONNECT)
|
|
{
|
|
if (!front->PortalBlocksSight(sector_t::ceiling)) fc = LINEOPEN_MAX, open.portalflags |= SO_TOPFRONT;
|
|
if (!back->PortalBlocksSight(sector_t::ceiling)) bc = LINEOPEN_MAX, open.portalflags |= SO_TOPBACK;
|
|
if (!front->PortalBlocksSight(sector_t::floor)) ff = LINEOPEN_MIN, open.portalflags |= SO_BOTTOMFRONT;
|
|
if (!back->PortalBlocksSight(sector_t::floor)) bf = LINEOPEN_MIN, open.portalflags |= SO_BOTTOMBACK;
|
|
}
|
|
|
|
if (fc == 0) fc = front->ceilingplane.ZatPoint(x, y);
|
|
if (bc == 0) bc = back->ceilingplane.ZatPoint(x, y);
|
|
if (ff == 0) ff = front->floorplane.ZatPoint(x, y);
|
|
if (bf == 0) bf = back->floorplane.ZatPoint(x, y);
|
|
|
|
open.bottom = MAX(ff, bf);
|
|
open.top = MIN(fc, bc);
|
|
|
|
// we only want to know if there is an opening, not how large it is.
|
|
open.range = open.bottom < open.top;
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
=
|
|
= PTR_SightTraverse
|
|
=
|
|
==============
|
|
*/
|
|
|
|
bool SightCheck::PTR_SightTraverse (intercept_t *in)
|
|
{
|
|
line_t *li;
|
|
double slope;
|
|
SightOpening open;
|
|
int frontflag = -1;
|
|
|
|
li = in->d.line;
|
|
|
|
//
|
|
// crosses a two sided line
|
|
//
|
|
|
|
// ignore self referencing sectors if COMPAT_TRACE is on
|
|
if ((i_compatflags & COMPATF_TRACE) && li->frontsector == li->backsector)
|
|
return true;
|
|
|
|
double trX = Trace.x + Trace.dx * in->frac;
|
|
double trY = Trace.y + Trace.dy * in->frac;
|
|
P_SightOpening (open, li, trX, trY);
|
|
|
|
FLinePortal *lport = li->getPortal();
|
|
|
|
if (open.range == 0 && open.portalflags == 0 && (lport == NULL || lport->mType != PORTT_LINKED)) // quick test for totally closed doors (must be delayed if portal checks are needed, though)
|
|
return false; // stop
|
|
|
|
// check bottom
|
|
if (open.bottom > LINEOPEN_MIN)
|
|
{
|
|
slope = (open.bottom - sightstart.Z) / in->frac;
|
|
if (slope > bottomslope)
|
|
bottomslope = slope;
|
|
}
|
|
|
|
// check top
|
|
if (open.top < LINEOPEN_MAX)
|
|
{
|
|
slope = (open.top - sightstart.Z) / in->frac;
|
|
if (slope < topslope)
|
|
topslope = slope;
|
|
}
|
|
|
|
if (open.portalflags)
|
|
{
|
|
sector_t *frontsec, *backsec;
|
|
frontflag = P_PointOnLineSidePrecise(sightstart, li);
|
|
if (!frontflag)
|
|
{
|
|
frontsec = li->frontsector;
|
|
backsec = li->backsector;
|
|
}
|
|
else
|
|
{
|
|
frontsec = li->backsector;
|
|
if (!frontsec) return false; // We are looking through the backside of a one-sided line. Just abort if that happens.
|
|
backsec = li->frontsector;
|
|
open.SwapSides(); // swap flags to make the next checks simpler.
|
|
}
|
|
|
|
if (portaldir != sector_t::floor && (open.portalflags & SO_TOPBACK) && !(open.portalflags & SO_TOPFRONT))
|
|
{
|
|
portals.Push({ in->frac, topslope, bottomslope, sector_t::ceiling, backsec->SkyBoxes[sector_t::ceiling]->Sector->PortalGroup });
|
|
}
|
|
if (portaldir != sector_t::ceiling && (open.portalflags & SO_BOTTOMBACK) && !(open.portalflags & SO_BOTTOMFRONT))
|
|
{
|
|
portals.Push({ in->frac, topslope, bottomslope, sector_t::floor, backsec->SkyBoxes[sector_t::floor]->Sector->PortalGroup });
|
|
}
|
|
}
|
|
if (lport)
|
|
{
|
|
portals.Push({ in->frac, topslope, bottomslope, portaldir, lport->mDestination->frontsector->PortalGroup });
|
|
return false;
|
|
}
|
|
|
|
if (topslope <= bottomslope || open.range == 0)
|
|
return false; // stop
|
|
|
|
// now handle 3D-floors
|
|
if(li->frontsector->e->XFloor.ffloors.Size() || li->backsector->e->XFloor.ffloors.Size())
|
|
{
|
|
if (frontflag == -1) frontflag = P_PointOnLineSidePrecise(sightstart, li);
|
|
|
|
//Check 3D FLOORS!
|
|
for(int i=1;i<=2;i++)
|
|
{
|
|
sector_t * s=i==1? li->frontsector:li->backsector;
|
|
double highslope, lowslope;
|
|
|
|
double topz= topslope * in->frac + sightstart.Z;
|
|
double bottomz= bottomslope * in->frac + sightstart.Z;
|
|
|
|
for (auto rover : s->e->XFloor.ffloors)
|
|
{
|
|
if ((rover->flags & FF_SEETHROUGH) == myseethrough || !(rover->flags & FF_EXISTS)) continue;
|
|
if ((Flags & SF_IGNOREWATERBOUNDARY) && (rover->flags & FF_SOLID) == 0) continue;
|
|
|
|
double ff_bottom = rover->bottom.plane->ZatPoint(trX, trY);
|
|
double ff_top = rover->top.plane->ZatPoint(trX, trY);
|
|
|
|
highslope = (ff_top - sightstart.Z) / in->frac;
|
|
lowslope = (ff_bottom - sightstart.Z) / in->frac;
|
|
|
|
if (highslope >= topslope)
|
|
{
|
|
// blocks completely
|
|
if (lowslope <= bottomslope) return false;
|
|
// blocks upper edge of view
|
|
if (lowslope < topslope) topslope = lowslope;
|
|
}
|
|
else if (lowslope <= bottomslope)
|
|
{
|
|
// blocks lower edge of view
|
|
if (highslope > bottomslope) bottomslope = highslope;
|
|
}
|
|
else
|
|
{
|
|
// the 3D-floor is inside the viewing cone but neither clips the top nor the bottom so by
|
|
// itself it can't be view blocking.
|
|
// However, if there's a 3D-floor on the other side that obstructs the same vertical range
|
|
// the 2 together will block sight.
|
|
sector_t * sb = i == 2 ? li->frontsector : li->backsector;
|
|
|
|
for (auto rover2 : sb->e->XFloor.ffloors)
|
|
{
|
|
if ((rover2->flags & FF_SEETHROUGH) == myseethrough || !(rover2->flags & FF_EXISTS)) continue;
|
|
if ((Flags & SF_IGNOREWATERBOUNDARY) && (rover->flags & FF_SOLID) == 0) continue;
|
|
|
|
double ffb_bottom = rover2->bottom.plane->ZatPoint(trX, trY);
|
|
double ffb_top = rover2->top.plane->ZatPoint(trX, trY);
|
|
|
|
if ((ffb_bottom >= ff_bottom && ffb_bottom <= ff_top) ||
|
|
(ffb_top <= ff_top && ffb_top >= ff_bottom) ||
|
|
(ffb_top >= ff_top && ffb_bottom <= ff_bottom) ||
|
|
(ffb_top <= ff_top && ffb_bottom >= ff_bottom))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// trace is leaving a sector with a 3d-floor
|
|
if (s == lastsector && frontflag == i - 1)
|
|
{
|
|
// upper slope intersects with this 3d-floor
|
|
if (Lastztop <= ff_bottom && topz > ff_top)
|
|
{
|
|
topslope = lowslope;
|
|
}
|
|
// lower slope intersects with this 3d-floor
|
|
if (Lastzbottom >= ff_top && bottomz < ff_top)
|
|
{
|
|
bottomslope = highslope;
|
|
}
|
|
}
|
|
if (topslope <= bottomslope) return false; // stop
|
|
}
|
|
}
|
|
lastsector = frontflag==0 ? li->backsector : li->frontsector;
|
|
}
|
|
else lastsector=NULL; // don't need it if there are no 3D-floors
|
|
|
|
Lastztop = (topslope * in->frac) + sightstart.Z;
|
|
Lastzbottom = (bottomslope * in->frac) + sightstart.Z;
|
|
|
|
return true; // keep going
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
==================
|
|
=
|
|
= P_SightCheckLine
|
|
=
|
|
===================
|
|
*/
|
|
|
|
bool SightCheck::P_SightCheckLine (line_t *ld)
|
|
{
|
|
divline_t dl;
|
|
|
|
if (ld->validcount == validcount)
|
|
{
|
|
return true;
|
|
}
|
|
ld->validcount = validcount;
|
|
if (P_PointOnDivlineSide (ld->v1->fPos(), &Trace) ==
|
|
P_PointOnDivlineSide (ld->v2->fPos(), &Trace))
|
|
{
|
|
return true; // line isn't crossed
|
|
}
|
|
P_MakeDivline (ld, &dl);
|
|
if (P_PointOnDivlineSide (Trace.x, Trace.y, &dl) ==
|
|
P_PointOnDivlineSide (Trace.x+Trace.dx, Trace.y+Trace.dy, &dl))
|
|
{
|
|
return true; // line isn't crossed
|
|
}
|
|
|
|
// try to early out the check
|
|
if (!ld->backsector || !(ld->flags & ML_TWOSIDED) || (ld->flags & ML_BLOCKSIGHT))
|
|
return false; // stop checking
|
|
|
|
// [RH] don't see past block everything lines
|
|
if (ld->flags & ML_BLOCKEVERYTHING)
|
|
{
|
|
if (!(Flags & SF_SEEPASTBLOCKEVERYTHING))
|
|
{
|
|
return false;
|
|
}
|
|
// Pretend the other side is invisible if this is not an impact line
|
|
// that runs a script on the current map. Used to prevent monsters
|
|
// from trying to attack through a block everything line unless
|
|
// there's a chance their attack will make it nonblocking.
|
|
if (!(Flags & SF_SEEPASTSHOOTABLELINES))
|
|
{
|
|
if (!(ld->activation & SPAC_Impact))
|
|
{
|
|
return false;
|
|
}
|
|
if (ld->special != ACS_Execute && ld->special != ACS_ExecuteAlways)
|
|
{
|
|
return false;
|
|
}
|
|
if (ld->args[1] != 0 && ld->args[1] != level.levelnum)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
sightcounts[3]++;
|
|
// store the line for later intersection testing
|
|
intercept_t newintercept;
|
|
newintercept.isaline = true;
|
|
newintercept.d.line = ld;
|
|
intercepts.Push (newintercept);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
=
|
|
= P_SightBlockLinesIterator
|
|
=
|
|
===================
|
|
*/
|
|
|
|
int SightCheck::P_SightBlockLinesIterator (int x, int y)
|
|
{
|
|
int offset;
|
|
int *list;
|
|
int res = 1;
|
|
|
|
polyblock_t *polyLink;
|
|
unsigned int i;
|
|
extern polyblock_t **PolyBlockMap;
|
|
|
|
offset = y*bmapwidth+x;
|
|
|
|
// if any of the previous blocks may contain a portal we may abort the collection of lines here, but we may not abort the sight check.
|
|
// (We still try to delay activating this for as long as possible.)
|
|
portalfound = portalfound || PortalBlockmap(x, y).containsLinkedPortals;
|
|
|
|
polyLink = PolyBlockMap[offset];
|
|
portalfound |= (polyLink && PortalBlockmap.hasLinkedPolyPortals);
|
|
while (polyLink)
|
|
{
|
|
if (polyLink->polyobj)
|
|
{ // only check non-empty links
|
|
if (polyLink->polyobj->validcount != validcount)
|
|
{
|
|
polyLink->polyobj->validcount = validcount;
|
|
for (i = 0; i < polyLink->polyobj->Linedefs.Size(); i++)
|
|
{
|
|
if (!P_SightCheckLine(polyLink->polyobj->Linedefs[i]))
|
|
{
|
|
if (!portalfound) return 0;
|
|
else res = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
polyLink = polyLink->next;
|
|
}
|
|
|
|
offset = *(blockmap + offset);
|
|
|
|
for (list = blockmaplump + offset + 1; *list != -1; list++)
|
|
{
|
|
if (!P_SightCheckLine (&lines[*list]))
|
|
{
|
|
if (!portalfound) return 0;
|
|
else res = -1;
|
|
}
|
|
}
|
|
|
|
return res; // everything was checked
|
|
}
|
|
|
|
/*
|
|
====================
|
|
=
|
|
= P_SightTraverseIntercepts
|
|
=
|
|
= Returns true if the traverser function returns true for all lines
|
|
====================
|
|
*/
|
|
|
|
bool SightCheck::P_SightTraverseIntercepts ()
|
|
{
|
|
unsigned count;
|
|
double dist;
|
|
intercept_t *scan, *in;
|
|
unsigned scanpos;
|
|
divline_t dl;
|
|
|
|
count = intercepts.Size ();
|
|
//
|
|
// calculate intercept distance
|
|
//
|
|
for (scanpos = 0; scanpos < intercepts.Size (); scanpos++)
|
|
{
|
|
scan = &intercepts[scanpos];
|
|
P_MakeDivline (scan->d.line, &dl);
|
|
scan->frac = P_InterceptVector (&Trace, &dl);
|
|
if (scan->frac < Startfrac)
|
|
{
|
|
scan->frac = INT_MAX;
|
|
count--;
|
|
}
|
|
}
|
|
|
|
//
|
|
// go through in order
|
|
// proper order is needed to handle 3D floors and portals.
|
|
//
|
|
in = NULL;
|
|
|
|
while (count--)
|
|
{
|
|
dist = INT_MAX;
|
|
for (scanpos = 0; scanpos < intercepts.Size (); scanpos++)
|
|
{
|
|
scan = &intercepts[scanpos];
|
|
if (scan->frac < dist)
|
|
{
|
|
dist = scan->frac;
|
|
in = scan;
|
|
}
|
|
}
|
|
|
|
if (in != NULL)
|
|
{
|
|
if (!PTR_SightTraverse (in))
|
|
return false; // don't bother going farther
|
|
in->frac = INT_MAX;
|
|
}
|
|
}
|
|
|
|
if (lastsector == seeingthing->Sector && lastsector->e->XFloor.ffloors.Size())
|
|
{
|
|
// we must do one last check whether the trace has crossed a 3D floor in the last sector
|
|
|
|
double topz = topslope + sightstart.Z;
|
|
double bottomz = bottomslope + sightstart.Z;
|
|
|
|
for (auto rover : lastsector->e->XFloor.ffloors)
|
|
{
|
|
if ((rover->flags & FF_SOLID) == myseethrough || !(rover->flags & FF_EXISTS)) continue;
|
|
if ((Flags & SF_IGNOREWATERBOUNDARY) && (rover->flags & FF_SOLID) == 0) continue;
|
|
|
|
double ff_bottom = rover->bottom.plane->ZatPoint(seeingthing);
|
|
double ff_top = rover->top.plane->ZatPoint(seeingthing);
|
|
|
|
if (Lastztop <= ff_bottom && topz > ff_bottom && Lastzbottom <= ff_bottom && bottomz > ff_bottom) return false;
|
|
if (Lastzbottom >= ff_top && bottomz < ff_top && Lastztop >= ff_top && topz < ff_top) return false;
|
|
}
|
|
}
|
|
return true; // everything was traversed
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
==================
|
|
=
|
|
= P_SightPathTraverse
|
|
=
|
|
= Traces a line from x1,y1 to x2,y2, calling the traverser function for each block
|
|
= Returns true if the traverser function returns true for all lines
|
|
==================
|
|
*/
|
|
|
|
bool SightCheck::P_SightPathTraverse ()
|
|
{
|
|
double x1, x2, y1, y2;
|
|
double xt1,yt1,xt2,yt2;
|
|
double xstep,ystep;
|
|
double partialx, partialy;
|
|
double xintercept, yintercept;
|
|
int mapx, mapy, mapxstep, mapystep;
|
|
int count;
|
|
|
|
validcount++;
|
|
intercepts.Clear ();
|
|
x1 = sightstart.X + Startfrac * Trace.dx;
|
|
y1 = sightstart.Y + Startfrac * Trace.dy;
|
|
x2 = sightend.X;
|
|
y2 = sightend.Y;
|
|
if (lastsector == NULL) lastsector = P_PointInSector(x1, y1);
|
|
|
|
// for FF_SEETHROUGH the following rule applies:
|
|
// If the viewer is in an area without FF_SEETHROUGH he can only see into areas without this flag
|
|
// If the viewer is in an area with FF_SEETHROUGH he can only see into areas with this flag
|
|
bool checkfloor = true, checkceiling = true;
|
|
for(auto rover : lastsector->e->XFloor.ffloors)
|
|
{
|
|
if(!(rover->flags & FF_EXISTS)) continue;
|
|
|
|
double ff_bottom=rover->bottom.plane->ZatPoint(sightstart);
|
|
double ff_top=rover->top.plane->ZatPoint(sightstart);
|
|
|
|
if (sightstart.Z < ff_top) checkceiling = false;
|
|
if (sightstart.Z >= ff_bottom) checkfloor = false;
|
|
|
|
if (sightstart.Z < ff_top && sightstart.Z >= ff_bottom)
|
|
{
|
|
myseethrough = rover->flags & FF_SEETHROUGH;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We also must check if the starting sector contains portals, and start sight checks in those as well.
|
|
if (portaldir != sector_t::floor && checkceiling && !lastsector->PortalBlocksSight(sector_t::ceiling))
|
|
{
|
|
portals.Push({ 0, topslope, bottomslope, sector_t::ceiling, lastsector->SkyBoxes[sector_t::ceiling]->Sector->PortalGroup });
|
|
}
|
|
if (portaldir != sector_t::ceiling && checkfloor && !lastsector->PortalBlocksSight(sector_t::floor))
|
|
{
|
|
portals.Push({ 0, topslope, bottomslope, sector_t::floor, lastsector->SkyBoxes[sector_t::floor]->Sector->PortalGroup });
|
|
}
|
|
|
|
x1 -= bmaporgx;
|
|
y1 -= bmaporgy;
|
|
xt1 = x1 / MAPBLOCKUNITS;
|
|
yt1 = y1 / MAPBLOCKUNITS;
|
|
|
|
x2 -= bmaporgx;
|
|
y2 -= bmaporgy;
|
|
xt2 = x2 / MAPBLOCKUNITS;
|
|
yt2 = y2 / MAPBLOCKUNITS;
|
|
|
|
mapx = xs_FloorToInt(xt1);
|
|
mapy = xs_FloorToInt(yt1);
|
|
int mapex = xs_FloorToInt(xt2);
|
|
int mapey = xs_FloorToInt(yt2);
|
|
|
|
|
|
if (mapex > mapx)
|
|
{
|
|
mapxstep = 1;
|
|
partialx = xs_CeilToInt(xt1) - xt1;
|
|
ystep = (y2 - y1) / fabs(x2 - x1);
|
|
}
|
|
else if (mapex < mapx)
|
|
{
|
|
mapxstep = -1;
|
|
partialx = xt1 - xs_FloorToInt(xt1);
|
|
ystep = (y2 - y1) / fabs(x2 - x1);
|
|
}
|
|
else
|
|
{
|
|
mapxstep = 0;
|
|
partialx = 1.;
|
|
ystep = 256;
|
|
}
|
|
yintercept = yt1 + partialx * ystep;
|
|
|
|
if (mapey > mapy)
|
|
{
|
|
mapystep = 1;
|
|
partialy = xs_CeilToInt(yt1) - yt1;
|
|
xstep = (x2 - x1) / fabs(y2 - y1);
|
|
}
|
|
else if (mapey < mapy)
|
|
{
|
|
mapystep = -1;
|
|
partialy = yt1 - xs_FloorToInt(yt1);
|
|
xstep = (x2 - x1) / fabs(y2 - y1);
|
|
}
|
|
else
|
|
{
|
|
mapystep = 0;
|
|
partialy = 1;
|
|
xstep = 256;
|
|
}
|
|
xintercept = xt1 + partialy * xstep;
|
|
|
|
// [RH] Fix for traces that pass only through blockmap corners. In that case,
|
|
// xintercept and yintercept can both be set ahead of mapx and mapy, so the
|
|
// for loop would never advance anywhere.
|
|
|
|
if (fabs(xstep) == 1. && fabs(ystep) == 1.)
|
|
{
|
|
if (ystep < 0)
|
|
{
|
|
partialx = 1. - partialx;
|
|
}
|
|
if (xstep < 0)
|
|
{
|
|
partialy = 1. - partialy;
|
|
}
|
|
if (partialx == partialy)
|
|
{
|
|
xintercept = xt1;
|
|
yintercept = yt1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// step through map blocks
|
|
// Count is present to prevent a round off error from skipping the break
|
|
|
|
for (count = 0 ; count < 100 ; count++)
|
|
{
|
|
// end traversing when reaching the end of the blockmap
|
|
// an early out is not possible because with portals a trace can easily land outside the map's bounds.
|
|
if (mapx < 0 || mapx >= bmapwidth || mapy < 0 || mapy >= bmapheight)
|
|
{
|
|
break;
|
|
}
|
|
int res = P_SightBlockLinesIterator(mapx, mapy);
|
|
if (res == 0)
|
|
{
|
|
sightcounts[1]++;
|
|
return false; // early out
|
|
}
|
|
|
|
// either reached the end or had an early-out condition with portals left to check,
|
|
if (res == -1 || (mapxstep | mapystep) == 0)
|
|
break;
|
|
|
|
switch (((xs_FloorToInt(yintercept) == mapy) << 1) | (xs_FloorToInt(xintercept) == mapx))
|
|
{
|
|
case 0: // neither xintercept nor yintercept match!
|
|
sightcounts[5]++;
|
|
// Continuing won't make things any better, so we might as well stop right here
|
|
count = 100;
|
|
break;
|
|
|
|
case 1: // xintercept matches
|
|
xintercept += xstep;
|
|
mapy += mapystep;
|
|
if (mapy == mapey)
|
|
mapystep = 0;
|
|
break;
|
|
|
|
case 2: // yintercept matches
|
|
yintercept += ystep;
|
|
mapx += mapxstep;
|
|
if (mapx == mapex)
|
|
mapxstep = 0;
|
|
break;
|
|
|
|
case 3: // xintercept and yintercept both match
|
|
sightcounts[4]++;
|
|
// The trace is exiting a block through its corner. Not only does the block
|
|
// being entered need to be checked (which will happen when this loop
|
|
// continues), but the other two blocks adjacent to the corner also need to
|
|
// be checked.
|
|
if (!P_SightBlockLinesIterator (mapx + mapxstep, mapy) ||
|
|
!P_SightBlockLinesIterator (mapx, mapy + mapystep))
|
|
{
|
|
sightcounts[1]++;
|
|
return false;
|
|
}
|
|
xintercept += xstep;
|
|
yintercept += ystep;
|
|
mapx += mapxstep;
|
|
mapy += mapystep;
|
|
if (mapx == mapex)
|
|
mapxstep = 0;
|
|
if (mapy == mapey)
|
|
mapystep = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// couldn't early out, so go through the sorted list
|
|
//
|
|
sightcounts[2]++;
|
|
|
|
return P_SightTraverseIntercepts ( );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
=
|
|
= P_CheckSight
|
|
=
|
|
= Returns true if a straight line between t1 and t2 is unobstructed
|
|
= look from eyes of t1 to any part of t2
|
|
=
|
|
= killough 4/20/98: cleaned up, made to use new LOS struct
|
|
=
|
|
=====================
|
|
*/
|
|
|
|
bool P_CheckSight (AActor *t1, AActor *t2, int flags)
|
|
{
|
|
SightCycles.Clock();
|
|
|
|
bool res;
|
|
|
|
assert (t1 != NULL);
|
|
assert (t2 != NULL);
|
|
if (t1 == NULL || t2 == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const sector_t *s1 = t1->Sector;
|
|
const sector_t *s2 = t2->Sector;
|
|
int pnum = int(s1 - sectors) * numsectors + int(s2 - sectors);
|
|
|
|
//
|
|
// check for trivial rejection
|
|
//
|
|
if (rejectmatrix != NULL &&
|
|
(rejectmatrix[pnum>>3] & (1 << (pnum & 7))))
|
|
{
|
|
sightcounts[0]++;
|
|
res = false; // can't possibly be connected
|
|
goto done;
|
|
}
|
|
|
|
//
|
|
// check precisely
|
|
//
|
|
// [RH] Andy Baker's stealth monsters:
|
|
// Cannot see an invisible object
|
|
if ((flags & SF_IGNOREVISIBILITY) == 0 && ((t2->renderflags & RF_INVISIBLE) || !t2->RenderStyle.IsVisible(t2->Alpha)))
|
|
{ // small chance of an attack being made anyway
|
|
if ((bglobal.m_Thinking ? pr_botchecksight() : pr_checksight()) > 50)
|
|
{
|
|
res = false;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
// killough 4/19/98: make fake floors and ceilings block monster view
|
|
|
|
if (!(flags & SF_IGNOREWATERBOUNDARY))
|
|
{
|
|
if ((s1->GetHeightSec() &&
|
|
((t1->Top() <= s1->heightsec->floorplane.ZatPoint(t1) &&
|
|
t2->Z() >= s1->heightsec->floorplane.ZatPoint(t2)) ||
|
|
(t1->Z() >= s1->heightsec->ceilingplane.ZatPoint(t1) &&
|
|
t2->Top() <= s1->heightsec->ceilingplane.ZatPoint(t2))))
|
|
||
|
|
(s2->GetHeightSec() &&
|
|
((t2->Top() <= s2->heightsec->floorplane.ZatPoint(t2) &&
|
|
t1->Z() >= s2->heightsec->floorplane.ZatPoint(t1)) ||
|
|
(t2->Z() >= s2->heightsec->ceilingplane.ZatPoint(t2) &&
|
|
t1->Top() <= s2->heightsec->ceilingplane.ZatPoint(t1)))))
|
|
{
|
|
res = false;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
// An unobstructed LOS is possible.
|
|
// Now look from eyes of t1 to any part of t2.
|
|
|
|
validcount++;
|
|
portals.Clear();
|
|
{
|
|
sector_t *sec;
|
|
double lookheight = t1->Center();
|
|
t1->GetPortalTransition(lookheight, &sec);
|
|
|
|
double bottomslope = t2->Z() - lookheight;
|
|
double topslope = bottomslope + t2->Height;
|
|
SightTask task = { 0, topslope, bottomslope, -1, sec->PortalGroup };
|
|
|
|
|
|
SightCheck s;
|
|
s.init(t1, t2, sec, &task, flags);
|
|
res = s.P_SightPathTraverse ();
|
|
if (!res)
|
|
{
|
|
double dist = t1->Distance2D(t2);
|
|
for (unsigned i = 0; i < portals.Size(); i++)
|
|
{
|
|
portals[i].Frac += 1 / dist;
|
|
s.init(t1, t2, NULL, &portals[i], flags);
|
|
if (s.P_SightPathTraverse())
|
|
{
|
|
res = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
done:
|
|
SightCycles.Unclock();
|
|
return res;
|
|
}
|
|
|
|
ADD_STAT (sight)
|
|
{
|
|
FString out;
|
|
out.Format ("%04.1f ms (%04.1f max), %5d %2d%4d%4d%4d%4d\n",
|
|
SightCycles.TimeMS(), MaxSightCycles.TimeMS(),
|
|
sightcounts[3], sightcounts[0], sightcounts[1], sightcounts[2], sightcounts[4], sightcounts[5]);
|
|
return out;
|
|
}
|
|
|
|
void P_ResetSightCounters (bool full)
|
|
{
|
|
if (full)
|
|
{
|
|
MaxSightCycles.Reset();
|
|
}
|
|
if (SightCycles.Time() > MaxSightCycles.Time())
|
|
{
|
|
MaxSightCycles = SightCycles;
|
|
}
|
|
SightCycles.Reset();
|
|
memset (sightcounts, 0, sizeof(sightcounts));
|
|
}
|