mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-23 12:32:34 +00:00
971 lines
25 KiB
C++
971 lines
25 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// Copyright 1993-1994 id Software
|
|
// Copyright 1994-1996 Raven Software
|
|
// Copyright 1999-2016 Randy Heit
|
|
// Copyright 2002-2016 Christoph Oelckers
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see http://www.gnu.org/licenses/
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
#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"
|
|
#include "vm.h"
|
|
|
|
// State.
|
|
#include "r_state.h"
|
|
|
|
#include "stats.h"
|
|
#include "g_levellocals.h"
|
|
#include "actorinlines.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 ();
|
|
bool LineBlocksSight(line_t *ld);
|
|
|
|
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 * 0.75;
|
|
|
|
portalgroup = task->portalgroup;
|
|
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);
|
|
if (LineBlocksSight(in->d.line))
|
|
{
|
|
// This may not skip P_SightOpening, but only reduce the open range to 0.
|
|
open.range = 0;
|
|
open.bottom = open.top;
|
|
}
|
|
|
|
FLinePortal *lport = li->getPortal();
|
|
|
|
if (open.range == 0 && open.portalflags == 0 && (lport == nullptr || lport->mType != PORTT_LINKED)) // quick test for totally closed doors (must be delayed if portal checks are needed, though)
|
|
return false; // stop
|
|
|
|
if (in->frac == 0)
|
|
return true;
|
|
|
|
// 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->GetOppositePortalGroup(sector_t::ceiling) });
|
|
}
|
|
if (portaldir != sector_t::ceiling && (open.portalflags & SO_BOTTOMBACK) && !(open.portalflags & SO_BOTTOMFRONT))
|
|
{
|
|
portals.Push({ in->frac, topslope, bottomslope, sector_t::floor, backsec->GetOppositePortalGroup(sector_t::floor) });
|
|
}
|
|
}
|
|
if (lport != nullptr && lport->mDestination != nullptr)
|
|
{
|
|
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
|
|
}
|
|
|
|
|
|
// performs trivial visibility checks.
|
|
bool SightCheck::LineBlocksSight(line_t *ld)
|
|
{
|
|
// try to early out the check
|
|
if (!ld->backsector || !(ld->flags & ML_TWOSIDED) || (ld->flags & ML_BLOCKSIGHT))
|
|
return true; // stop checking
|
|
|
|
// [RH] don't see past block everything lines
|
|
if (ld->flags & ML_BLOCKEVERYTHING)
|
|
{
|
|
if (!(Flags & SF_SEEPASTBLOCKEVERYTHING))
|
|
{
|
|
return true;
|
|
}
|
|
// 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 true;
|
|
}
|
|
if (ld->special != ACS_Execute && ld->special != ACS_ExecuteAlways)
|
|
{
|
|
return true;
|
|
}
|
|
if (ld->args[1] != 0 && ld->args[1] != level.levelnum)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
=
|
|
= 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
|
|
}
|
|
|
|
if (!portalfound) // when portals come into play, the quick-outs here may not be performed
|
|
{
|
|
if (LineBlocksSight(ld)) 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*level.blockmap.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;
|
|
}
|
|
|
|
for (list = level.blockmap.GetLines(x, y); *list != -1; list++)
|
|
{
|
|
if (!P_SightCheckLine (&level.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->GetOppositePortalGroup(sector_t::ceiling) });
|
|
}
|
|
if (portaldir != sector_t::ceiling && checkfloor && !lastsector->PortalBlocksSight(sector_t::floor))
|
|
{
|
|
portals.Push({ 0, topslope, bottomslope, sector_t::floor, lastsector->GetOppositePortalGroup(sector_t::floor) });
|
|
}
|
|
|
|
x1 -= level.blockmap.bmaporgx;
|
|
y1 -= level.blockmap.bmaporgy;
|
|
xt1 = x1 / FBlockmap::MAPBLOCKUNITS;
|
|
yt1 = y1 / FBlockmap::MAPBLOCKUNITS;
|
|
|
|
x2 -= level.blockmap.bmaporgx;
|
|
y2 -= level.blockmap.bmaporgy;
|
|
xt2 = x2 / FBlockmap::MAPBLOCKUNITS;
|
|
yt2 = y2 / FBlockmap::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 = 1. - xt1 + xs_FloorToInt(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 = 1. - yt1 + xs_FloorToInt(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
|
|
|
|
int itres = -1;
|
|
for (count = 0 ; count < 1000 ; 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 (!level.blockmap.isValidBlock(mapx, mapy))
|
|
{
|
|
break;
|
|
}
|
|
itres = P_SightBlockLinesIterator(mapx, mapy);
|
|
if (itres == 0)
|
|
{
|
|
sightcounts[1]++;
|
|
return false; // early out
|
|
}
|
|
|
|
// either reached the end or had an early-out condition with portals left to check,
|
|
if (itres == -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 = 1000;
|
|
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]++;
|
|
|
|
bool traverseres = P_SightTraverseIntercepts ( );
|
|
if (itres == -1) return false; // if the iterator had an early out there was no line of sight. The traverser was only called to collect more portals.
|
|
if (seeingthing->Sector->PortalGroup != portalgroup) return false; // We are in a different group than the seeingthing, so this trace cannot determine visibility alone.
|
|
return traverseres;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
=
|
|
= 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->Index()) * level.sectors.Size() + int(s2->Index());
|
|
|
|
//
|
|
// check for trivial rejection
|
|
//
|
|
if (level.rejectmatrix.Size() > 0 &&
|
|
(level.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->Z() + t1->Height*0.75;
|
|
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;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, CheckSight)
|
|
{
|
|
PARAM_SELF_PROLOGUE(AActor);
|
|
PARAM_OBJECT_NOT_NULL(target, AActor);
|
|
PARAM_INT_DEF(flags);
|
|
ACTION_RETURN_BOOL(P_CheckSight(self, target, flags));
|
|
}
|
|
|
|
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));
|
|
}
|