2017-04-17 11:33:19 +00:00
//-----------------------------------------------------------------------------
//
// 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/
//
//-----------------------------------------------------------------------------
//
2016-03-01 15:47:10 +00:00
# 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"
2017-04-12 23:12:04 +00:00
# include "vm.h"
2016-03-01 15:47:10 +00:00
// State.
# include "r_state.h"
# include "stats.h"
2017-01-08 17:45:30 +00:00
# include "g_levellocals.h"
2017-03-10 01:22:42 +00:00
# include "actorinlines.h"
2016-03-01 15:47:10 +00:00
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 ;
2016-03-07 20:58:34 +00:00
enum
{
SO_TOPFRONT = 1 ,
SO_TOPBACK = 2 ,
SO_BOTTOMFRONT = 4 ,
SO_BOTTOMBACK = 8 ,
} ;
struct SightOpening
{
2016-03-28 14:22:21 +00:00
double top ;
double bottom ;
2016-03-07 20:58:34 +00:00
int range ;
int portalflags ;
void SwapSides ( )
{
portalflags = ( ( portalflags & ( SO_TOPFRONT | SO_BOTTOMFRONT ) ) < < 1 ) | ( ( portalflags & ( SO_TOPBACK | SO_BOTTOMBACK ) ) > > 1 ) ;
}
} ;
struct SightTask
{
2016-03-28 14:22:21 +00:00
double Frac ;
double topslope ;
double bottomslope ;
2016-03-07 20:58:34 +00:00
int direction ;
int portalgroup ;
} ;
2016-03-01 15:47:10 +00:00
static TArray < intercept_t > intercepts ( 128 ) ;
2016-03-07 20:58:34 +00:00
static TArray < SightTask > portals ( 32 ) ;
2016-03-01 15:47:10 +00:00
class SightCheck
{
2016-03-28 14:22:21 +00:00
DVector3 sightstart ;
DVector2 sightend ;
double Startfrac ;
2016-03-07 20:58:34 +00:00
AActor * seeingthing ;
2016-03-28 14:22:21 +00:00
double Lastztop ; // z at last line
double Lastzbottom ; // z at last line
2016-03-01 15:47:10 +00:00
sector_t * lastsector ; // last sector being entered by trace
2016-03-28 14:22:21 +00:00
double topslope , bottomslope ; // slopes to top and bottom of target
2016-03-01 15:47:10 +00:00
int Flags ;
2016-03-28 14:22:21 +00:00
divline_t Trace ;
2016-03-07 22:41:16 +00:00
int portaldir ;
2016-03-08 09:44:03 +00:00
int portalgroup ;
2016-03-08 00:26:13 +00:00
bool portalfound ;
2016-03-01 15:47:10 +00:00
unsigned int myseethrough ;
2016-03-28 14:22:21 +00:00
void P_SightOpening ( SightOpening & open , const line_t * linedef , double x , double y ) ;
2016-03-01 15:47:10 +00:00
bool PTR_SightTraverse ( intercept_t * in ) ;
bool P_SightCheckLine ( line_t * ld ) ;
2016-03-07 20:58:34 +00:00
int P_SightBlockLinesIterator ( int x , int y ) ;
2016-03-01 15:47:10 +00:00
bool P_SightTraverseIntercepts ( ) ;
2017-02-24 18:00:10 +00:00
bool LineBlocksSight ( line_t * ld ) ;
2016-03-01 15:47:10 +00:00
public :
2016-03-07 20:58:34 +00:00
bool P_SightPathTraverse ( ) ;
2016-03-01 15:47:10 +00:00
2016-03-07 20:58:34 +00:00
void init ( AActor * t1 , AActor * t2 , sector_t * startsector , SightTask * task , int flags )
2016-03-01 15:47:10 +00:00
{
2016-03-28 14:22:21 +00:00
sightstart = t1 - > PosRelative ( task - > portalgroup ) ;
sightend = t2 - > PosRelative ( task - > portalgroup ) ;
2016-04-18 14:46:56 +00:00
sightstart . Z + = t1 - > Height * 0.75 ;
2016-03-07 20:58:34 +00:00
2017-01-09 00:28:07 +00:00
portalgroup = task - > portalgroup ;
2016-03-28 14:22:21 +00:00
Startfrac = task - > Frac ;
Trace = { sightstart . X , sightstart . Y , sightend . X - sightstart . X , sightend . Y - sightstart . Y } ;
Lastztop = Lastzbottom = sightstart . Z ;
2016-03-07 20:58:34 +00:00
lastsector = startsector ;
2016-03-01 15:47:10 +00:00
seeingthing = t2 ;
2016-03-07 20:58:34 +00:00
topslope = task - > topslope ;
bottomslope = task - > bottomslope ;
2016-03-01 15:47:10 +00:00
Flags = flags ;
2016-03-07 20:58:34 +00:00
portaldir = task - > direction ;
2016-03-08 00:26:13 +00:00
portalfound = false ;
2016-03-01 15:47:10 +00:00
myseethrough = FF_SEETHROUGH ;
}
} ;
2016-03-07 20:58:34 +00:00
//==========================================================================
//
// P_SightOpening
//
// Simplified version that removes everything not needed for a sight check
//
//==========================================================================
2016-03-28 14:22:21 +00:00
void SightCheck : : P_SightOpening ( SightOpening & open , const line_t * linedef , double x , double y )
2016-03-07 20:58:34 +00:00
{
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 ;
}
2016-03-28 14:22:21 +00:00
double fc = 0 , ff = 0 , bc = 0 , bf = 0 ;
2016-03-07 20:58:34 +00:00
if ( linedef - > flags & ML_PORTALCONNECT )
{
2016-03-28 14:22:21 +00:00
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 ;
2016-03-07 20:58:34 +00:00
}
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.
2016-03-28 14:22:21 +00:00
open . range = open . bottom < open . top ;
2016-03-07 20:58:34 +00:00
}
2016-03-01 15:47:10 +00:00
/*
= = = = = = = = = = = = = =
=
= PTR_SightTraverse
=
= = = = = = = = = = = = = =
*/
bool SightCheck : : PTR_SightTraverse ( intercept_t * in )
{
line_t * li ;
2016-03-28 14:22:21 +00:00
double slope ;
2016-03-07 20:58:34 +00:00
SightOpening open ;
int frontflag = - 1 ;
2016-03-01 15:47:10 +00:00
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 ;
2016-03-31 14:52:25 +00:00
double trX = Trace . x + Trace . dx * in - > frac ;
double trY = Trace . y + Trace . dy * in - > frac ;
2017-02-24 18:00:10 +00:00
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 ;
}
2016-03-01 15:47:10 +00:00
2016-03-07 20:58:34 +00:00
FLinePortal * lport = li - > getPortal ( ) ;
2017-08-12 10:51:45 +00:00
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)
2016-03-01 15:47:10 +00:00
return false ; // stop
2016-04-06 23:16:07 +00:00
if ( in - > frac = = 0 )
return true ;
2016-03-01 15:47:10 +00:00
// check bottom
2016-03-28 14:22:21 +00:00
if ( open . bottom > LINEOPEN_MIN )
2016-03-07 20:58:34 +00:00
{
2016-03-31 14:52:25 +00:00
slope = ( open . bottom - sightstart . Z ) / in - > frac ;
2016-03-07 20:58:34 +00:00
if ( slope > bottomslope )
bottomslope = slope ;
}
2016-03-01 15:47:10 +00:00
// check top
2016-03-28 14:22:21 +00:00
if ( open . top < LINEOPEN_MAX )
2016-03-07 20:58:34 +00:00
{
2016-03-31 14:52:25 +00:00
slope = ( open . top - sightstart . Z ) / in - > frac ;
2016-03-07 20:58:34 +00:00
if ( slope < topslope )
topslope = slope ;
}
if ( open . portalflags )
{
sector_t * frontsec , * backsec ;
2016-03-28 14:22:21 +00:00
frontflag = P_PointOnLineSidePrecise ( sightstart , li ) ;
2016-03-07 20:58:34 +00:00
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.
2016-03-08 00:26:13 +00:00
backsec = li - > frontsector ;
2016-03-07 20:58:34 +00:00
open . SwapSides ( ) ; // swap flags to make the next checks simpler.
}
if ( portaldir ! = sector_t : : floor & & ( open . portalflags & SO_TOPBACK ) & & ! ( open . portalflags & SO_TOPFRONT ) )
{
2016-04-19 09:35:28 +00:00
portals . Push ( { in - > frac , topslope , bottomslope , sector_t : : ceiling , backsec - > GetOppositePortalGroup ( sector_t : : ceiling ) } ) ;
2016-03-07 20:58:34 +00:00
}
if ( portaldir ! = sector_t : : ceiling & & ( open . portalflags & SO_BOTTOMBACK ) & & ! ( open . portalflags & SO_BOTTOMFRONT ) )
{
2016-04-19 09:35:28 +00:00
portals . Push ( { in - > frac , topslope , bottomslope , sector_t : : floor , backsec - > GetOppositePortalGroup ( sector_t : : floor ) } ) ;
2016-03-07 20:58:34 +00:00
}
}
2017-08-12 10:51:45 +00:00
if ( lport ! = nullptr & & lport - > mDestination ! = nullptr )
2016-03-07 20:58:34 +00:00
{
2016-03-31 14:52:25 +00:00
portals . Push ( { in - > frac , topslope , bottomslope , portaldir , lport - > mDestination - > frontsector - > PortalGroup } ) ;
2016-03-07 20:58:34 +00:00
return false ;
}
2016-03-01 15:47:10 +00:00
2016-03-07 20:58:34 +00:00
if ( topslope < = bottomslope | | open . range = = 0 )
2016-03-01 15:47:10 +00:00
return false ; // stop
// now handle 3D-floors
if ( li - > frontsector - > e - > XFloor . ffloors . Size ( ) | | li - > backsector - > e - > XFloor . ffloors . Size ( ) )
{
2016-03-28 14:22:21 +00:00
if ( frontflag = = - 1 ) frontflag = P_PointOnLineSidePrecise ( sightstart , li ) ;
2016-03-01 15:47:10 +00:00
//Check 3D FLOORS!
for ( int i = 1 ; i < = 2 ; i + + )
{
sector_t * s = i = = 1 ? li - > frontsector : li - > backsector ;
2016-03-28 14:22:21 +00:00
double highslope , lowslope ;
2016-03-01 15:47:10 +00:00
2016-03-31 14:52:25 +00:00
double topz = topslope * in - > frac + sightstart . Z ;
double bottomz = bottomslope * in - > frac + sightstart . Z ;
2016-03-01 15:47:10 +00:00
2016-03-28 14:22:21 +00:00
for ( auto rover : s - > e - > XFloor . ffloors )
2016-03-01 15:47:10 +00:00
{
2016-03-28 14:22:21 +00:00
if ( ( rover - > flags & FF_SEETHROUGH ) = = myseethrough | | ! ( rover - > flags & FF_EXISTS ) ) continue ;
2016-03-01 15:47:10 +00:00
if ( ( Flags & SF_IGNOREWATERBOUNDARY ) & & ( rover - > flags & FF_SOLID ) = = 0 ) continue ;
2016-03-28 14:22:21 +00:00
double ff_bottom = rover - > bottom . plane - > ZatPoint ( trX , trY ) ;
double ff_top = rover - > top . plane - > ZatPoint ( trX , trY ) ;
2016-03-31 14:52:25 +00:00
highslope = ( ff_top - sightstart . Z ) / in - > frac ;
lowslope = ( ff_bottom - sightstart . Z ) / in - > frac ;
2016-03-01 15:47:10 +00:00
2016-03-28 14:22:21 +00:00
if ( highslope > = topslope )
2016-03-01 15:47:10 +00:00
{
// blocks completely
2016-03-28 14:22:21 +00:00
if ( lowslope < = bottomslope ) return false ;
2016-03-01 15:47:10 +00:00
// blocks upper edge of view
2016-03-28 14:22:21 +00:00
if ( lowslope < topslope ) topslope = lowslope ;
2016-03-01 15:47:10 +00:00
}
2016-03-28 14:22:21 +00:00
else if ( lowslope < = bottomslope )
2016-03-01 15:47:10 +00:00
{
// blocks lower edge of view
2016-03-28 14:22:21 +00:00
if ( highslope > bottomslope ) bottomslope = highslope ;
2016-03-01 15:47:10 +00:00
}
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.
2016-03-28 14:22:21 +00:00
sector_t * sb = i = = 2 ? li - > frontsector : li - > backsector ;
2016-03-01 15:47:10 +00:00
2016-03-28 14:22:21 +00:00
for ( auto rover2 : sb - > e - > XFloor . ffloors )
2016-03-01 15:47:10 +00:00
{
2016-03-28 14:22:21 +00:00
if ( ( rover2 - > flags & FF_SEETHROUGH ) = = myseethrough | | ! ( rover2 - > flags & FF_EXISTS ) ) continue ;
2016-03-01 15:47:10 +00:00
if ( ( Flags & SF_IGNOREWATERBOUNDARY ) & & ( rover - > flags & FF_SOLID ) = = 0 ) continue ;
2016-03-28 14:22:21 +00:00
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 ) | |
2016-03-01 15:47:10 +00:00
( ffb_top < = ff_top & & ffb_top > = ff_bottom ) | |
( ffb_top > = ff_top & & ffb_bottom < = ff_bottom ) | |
2016-03-28 14:22:21 +00:00
( ffb_top < = ff_top & & ffb_bottom > = ff_bottom ) )
2016-03-01 15:47:10 +00:00
{
return false ;
}
}
}
// trace is leaving a sector with a 3d-floor
2016-03-28 14:22:21 +00:00
if ( s = = lastsector & & frontflag = = i - 1 )
2016-03-01 15:47:10 +00:00
{
// upper slope intersects with this 3d-floor
2016-03-28 14:22:21 +00:00
if ( Lastztop < = ff_bottom & & topz > ff_top )
2016-03-01 15:47:10 +00:00
{
2016-03-28 14:22:21 +00:00
topslope = lowslope ;
2016-03-01 15:47:10 +00:00
}
// lower slope intersects with this 3d-floor
2016-03-28 14:22:21 +00:00
if ( Lastzbottom > = ff_top & & bottomz < ff_top )
2016-03-01 15:47:10 +00:00
{
2016-03-28 14:22:21 +00:00
bottomslope = highslope ;
2016-03-01 15:47:10 +00:00
}
}
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
2016-03-31 14:52:25 +00:00
Lastztop = ( topslope * in - > frac ) + sightstart . Z ;
Lastzbottom = ( bottomslope * in - > frac ) + sightstart . Z ;
2016-03-01 15:47:10 +00:00
return true ; // keep going
}
2017-02-24 18:00:10 +00:00
// 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 ;
}
2016-03-01 15:47:10 +00:00
/*
= = = = = = = = = = = = = = = = = =
=
= P_SightCheckLine
=
= = = = = = = = = = = = = = = = = = =
*/
bool SightCheck : : P_SightCheckLine ( line_t * ld )
{
2016-03-28 14:22:21 +00:00
divline_t dl ;
2016-03-01 15:47:10 +00:00
if ( ld - > validcount = = validcount )
{
return true ;
}
ld - > validcount = validcount ;
2016-03-31 19:13:32 +00:00
if ( P_PointOnDivlineSide ( ld - > v1 - > fPos ( ) , & Trace ) = =
P_PointOnDivlineSide ( ld - > v2 - > fPos ( ) , & Trace ) )
2016-03-01 15:47:10 +00:00
{
return true ; // line isn't crossed
}
P_MakeDivline ( ld , & dl ) ;
2016-03-31 14:52:25 +00:00
if ( P_PointOnDivlineSide ( Trace . x , Trace . y , & dl ) = =
P_PointOnDivlineSide ( Trace . x + Trace . dx , Trace . y + Trace . dy , & dl ) )
2016-03-01 15:47:10 +00:00
{
return true ; // line isn't crossed
}
2017-02-24 18:00:10 +00:00
if ( ! portalfound ) // when portals come into play, the quick-outs here may not be performed
2016-03-01 15:47:10 +00:00
{
2017-02-24 18:00:10 +00:00
if ( LineBlocksSight ( ld ) ) return false ;
2016-03-01 15:47:10 +00:00
}
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
=
= = = = = = = = = = = = = = = = = = =
*/
2016-03-07 20:58:34 +00:00
int SightCheck : : P_SightBlockLinesIterator ( int x , int y )
2016-03-01 15:47:10 +00:00
{
int offset ;
int * list ;
2016-03-07 20:58:34 +00:00
int res = 1 ;
2016-03-01 15:47:10 +00:00
polyblock_t * polyLink ;
unsigned int i ;
extern polyblock_t * * PolyBlockMap ;
2017-03-17 13:24:21 +00:00
offset = y * level . blockmap . bmapwidth + x ;
2016-03-08 00:26:13 +00:00
// 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.)
2018-04-01 18:17:39 +00:00
portalfound = portalfound | | level . PortalBlockmap ( x , y ) . containsLinkedPortals ;
2016-03-01 15:47:10 +00:00
polyLink = PolyBlockMap [ offset ] ;
2018-04-01 18:17:39 +00:00
portalfound | = ( polyLink & & level . PortalBlockmap . hasLinkedPolyPortals ) ;
2016-03-01 15:47:10 +00:00
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 + + )
{
2016-03-07 20:58:34 +00:00
if ( ! P_SightCheckLine ( polyLink - > polyobj - > Linedefs [ i ] ) )
{
2016-03-08 00:26:13 +00:00
if ( ! portalfound ) return 0 ;
2016-03-07 20:58:34 +00:00
else res = - 1 ;
}
2016-03-01 15:47:10 +00:00
}
}
}
polyLink = polyLink - > next ;
}
2017-03-17 13:24:21 +00:00
for ( list = level . blockmap . GetLines ( x , y ) ; * list ! = - 1 ; list + + )
2016-03-01 15:47:10 +00:00
{
2017-01-08 13:39:16 +00:00
if ( ! P_SightCheckLine ( & level . lines [ * list ] ) )
2016-03-07 20:58:34 +00:00
{
2016-03-08 00:26:13 +00:00
if ( ! portalfound ) return 0 ;
2016-03-07 20:58:34 +00:00
else res = - 1 ;
}
2016-03-01 15:47:10 +00:00
}
2016-03-07 20:58:34 +00:00
return res ; // everything was checked
2016-03-01 15:47:10 +00:00
}
/*
= = = = = = = = = = = = = = = = = = = =
=
= P_SightTraverseIntercepts
=
= Returns true if the traverser function returns true for all lines
= = = = = = = = = = = = = = = = = = = =
*/
bool SightCheck : : P_SightTraverseIntercepts ( )
{
unsigned count ;
2016-03-28 14:22:21 +00:00
double dist ;
2016-03-01 15:47:10 +00:00
intercept_t * scan , * in ;
unsigned scanpos ;
2016-03-28 14:22:21 +00:00
divline_t dl ;
2016-03-01 15:47:10 +00:00
count = intercepts . Size ( ) ;
//
// calculate intercept distance
//
for ( scanpos = 0 ; scanpos < intercepts . Size ( ) ; scanpos + + )
{
scan = & intercepts [ scanpos ] ;
P_MakeDivline ( scan - > d . line , & dl ) ;
2016-03-31 14:52:25 +00:00
scan - > frac = P_InterceptVector ( & Trace , & dl ) ;
if ( scan - > frac < Startfrac )
2016-03-08 00:26:13 +00:00
{
2016-03-31 14:52:25 +00:00
scan - > frac = INT_MAX ;
2016-03-08 00:26:13 +00:00
count - - ;
}
2016-03-01 15:47:10 +00:00
}
//
// go through in order
2016-03-07 20:58:34 +00:00
// proper order is needed to handle 3D floors and portals.
2016-03-01 15:47:10 +00:00
//
in = NULL ;
while ( count - - )
{
2016-03-28 14:22:21 +00:00
dist = INT_MAX ;
2016-03-01 15:47:10 +00:00
for ( scanpos = 0 ; scanpos < intercepts . Size ( ) ; scanpos + + )
{
scan = & intercepts [ scanpos ] ;
2016-03-31 14:52:25 +00:00
if ( scan - > frac < dist )
2016-03-01 15:47:10 +00:00
{
2016-03-31 14:52:25 +00:00
dist = scan - > frac ;
2016-03-01 15:47:10 +00:00
in = scan ;
}
}
if ( in ! = NULL )
{
if ( ! PTR_SightTraverse ( in ) )
return false ; // don't bother going farther
2016-03-31 14:52:25 +00:00
in - > frac = INT_MAX ;
2016-03-01 15:47:10 +00:00
}
}
2016-03-28 14:22:21 +00:00
if ( lastsector = = seeingthing - > Sector & & lastsector - > e - > XFloor . ffloors . Size ( ) )
2016-03-01 15:47:10 +00:00
{
// we must do one last check whether the trace has crossed a 3D floor in the last sector
2016-03-28 14:22:21 +00:00
double topz = topslope + sightstart . Z ;
double bottomz = bottomslope + sightstart . Z ;
2016-03-01 15:47:10 +00:00
2016-03-28 14:22:21 +00:00
for ( auto rover : lastsector - > e - > XFloor . ffloors )
{
if ( ( rover - > flags & FF_SOLID ) = = myseethrough | | ! ( rover - > flags & FF_EXISTS ) ) continue ;
2016-03-01 15:47:10 +00:00
if ( ( Flags & SF_IGNOREWATERBOUNDARY ) & & ( rover - > flags & FF_SOLID ) = = 0 ) continue ;
2016-03-30 07:41:46 +00:00
double ff_bottom = rover - > bottom . plane - > ZatPoint ( seeingthing ) ;
double ff_top = rover - > top . plane - > ZatPoint ( seeingthing ) ;
2016-03-28 14:22:21 +00:00
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 ;
2016-03-01 15:47:10 +00:00
}
}
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
= = = = = = = = = = = = = = = = = =
*/
2016-03-07 20:58:34 +00:00
bool SightCheck : : P_SightPathTraverse ( )
2016-03-01 15:47:10 +00:00
{
2016-03-28 14:22:21 +00:00
double x1 , x2 , y1 , y2 ;
double xt1 , yt1 , xt2 , yt2 ;
double xstep , ystep ;
double partialx , partialy ;
double xintercept , yintercept ;
2016-03-01 15:47:10 +00:00
int mapx , mapy , mapxstep , mapystep ;
int count ;
validcount + + ;
intercepts . Clear ( ) ;
2016-03-28 14:22:21 +00:00
x1 = sightstart . X + Startfrac * Trace . dx ;
y1 = sightstart . Y + Startfrac * Trace . dy ;
x2 = sightend . X ;
y2 = sightend . Y ;
2016-03-07 20:58:34 +00:00
if ( lastsector = = NULL ) lastsector = P_PointInSector ( x1 , y1 ) ;
2016-03-01 15:47:10 +00:00
// 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
2016-03-08 09:44:03 +00:00
bool checkfloor = true , checkceiling = true ;
2016-03-28 14:22:21 +00:00
for ( auto rover : lastsector - > e - > XFloor . ffloors )
2016-03-01 15:47:10 +00:00
{
if ( ! ( rover - > flags & FF_EXISTS ) ) continue ;
2016-03-28 14:22:21 +00:00
double ff_bottom = rover - > bottom . plane - > ZatPoint ( sightstart ) ;
double ff_top = rover - > top . plane - > ZatPoint ( sightstart ) ;
2016-03-01 15:47:10 +00:00
2016-03-28 14:22:21 +00:00
if ( sightstart . Z < ff_top ) checkceiling = false ;
if ( sightstart . Z > = ff_bottom ) checkfloor = false ;
2016-03-08 09:44:03 +00:00
2016-03-28 14:22:21 +00:00
if ( sightstart . Z < ff_top & & sightstart . Z > = ff_bottom )
2016-03-01 15:47:10 +00:00
{
myseethrough = rover - > flags & FF_SEETHROUGH ;
break ;
}
}
2016-03-08 09:44:03 +00:00
// 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 ) )
{
2016-04-19 09:35:28 +00:00
portals . Push ( { 0 , topslope , bottomslope , sector_t : : ceiling , lastsector - > GetOppositePortalGroup ( sector_t : : ceiling ) } ) ;
2016-03-08 09:44:03 +00:00
}
if ( portaldir ! = sector_t : : ceiling & & checkfloor & & ! lastsector - > PortalBlocksSight ( sector_t : : floor ) )
{
2016-04-19 09:35:28 +00:00
portals . Push ( { 0 , topslope , bottomslope , sector_t : : floor , lastsector - > GetOppositePortalGroup ( sector_t : : floor ) } ) ;
2016-03-08 09:44:03 +00:00
}
2017-03-17 13:24:21 +00:00
x1 - = level . blockmap . bmaporgx ;
y1 - = level . blockmap . bmaporgy ;
xt1 = x1 / FBlockmap : : MAPBLOCKUNITS ;
yt1 = y1 / FBlockmap : : MAPBLOCKUNITS ;
2016-03-01 15:47:10 +00:00
2017-03-17 13:24:21 +00:00
x2 - = level . blockmap . bmaporgx ;
y2 - = level . blockmap . bmaporgy ;
xt2 = x2 / FBlockmap : : MAPBLOCKUNITS ;
yt2 = y2 / FBlockmap : : MAPBLOCKUNITS ;
2016-03-28 14:22:21 +00:00
2016-04-02 10:14:56 +00:00
mapx = xs_FloorToInt ( xt1 ) ;
mapy = xs_FloorToInt ( yt1 ) ;
int mapex = xs_FloorToInt ( xt2 ) ;
int mapey = xs_FloorToInt ( yt2 ) ;
2016-03-01 15:47:10 +00:00
2016-03-28 14:22:21 +00:00
if ( mapex > mapx )
2016-03-01 15:47:10 +00:00
{
mapxstep = 1 ;
2016-04-06 11:19:09 +00:00
partialx = 1. - xt1 + xs_FloorToInt ( xt1 ) ;
2016-03-28 14:22:21 +00:00
ystep = ( y2 - y1 ) / fabs ( x2 - x1 ) ;
2016-03-01 15:47:10 +00:00
}
2016-03-28 14:22:21 +00:00
else if ( mapex < mapx )
2016-03-01 15:47:10 +00:00
{
mapxstep = - 1 ;
2016-03-28 14:22:21 +00:00
partialx = xt1 - xs_FloorToInt ( xt1 ) ;
ystep = ( y2 - y1 ) / fabs ( x2 - x1 ) ;
2016-03-01 15:47:10 +00:00
}
else
{
mapxstep = 0 ;
2016-03-28 14:22:21 +00:00
partialx = 1. ;
ystep = 256 ;
2016-03-01 15:47:10 +00:00
}
2016-03-28 14:22:21 +00:00
yintercept = yt1 + partialx * ystep ;
2016-03-01 15:47:10 +00:00
2016-03-28 14:22:21 +00:00
if ( mapey > mapy )
2016-03-01 15:47:10 +00:00
{
mapystep = 1 ;
2016-04-06 11:19:09 +00:00
partialy = 1. - yt1 + xs_FloorToInt ( yt1 ) ;
2016-03-28 14:22:21 +00:00
xstep = ( x2 - x1 ) / fabs ( y2 - y1 ) ;
2016-03-01 15:47:10 +00:00
}
2016-03-28 14:22:21 +00:00
else if ( mapey < mapy )
2016-03-01 15:47:10 +00:00
{
mapystep = - 1 ;
2016-03-28 14:22:21 +00:00
partialy = yt1 - xs_FloorToInt ( yt1 ) ;
xstep = ( x2 - x1 ) / fabs ( y2 - y1 ) ;
2016-03-01 15:47:10 +00:00
}
else
{
mapystep = 0 ;
2016-03-28 14:22:21 +00:00
partialy = 1 ;
xstep = 256 ;
2016-03-01 15:47:10 +00:00
}
2016-03-28 14:22:21 +00:00
xintercept = xt1 + partialy * xstep ;
2016-03-01 15:47:10 +00:00
// [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.
2016-03-28 14:22:21 +00:00
if ( fabs ( xstep ) = = 1. & & fabs ( ystep ) = = 1. )
2016-03-01 15:47:10 +00:00
{
if ( ystep < 0 )
{
2016-03-28 14:22:21 +00:00
partialx = 1. - partialx ;
2016-03-01 15:47:10 +00:00
}
if ( xstep < 0 )
{
2016-03-28 14:22:21 +00:00
partialy = 1. - partialy ;
2016-03-01 15:47:10 +00:00
}
if ( partialx = = partialy )
{
2016-03-28 14:22:21 +00:00
xintercept = xt1 ;
yintercept = yt1 ;
2016-03-01 15:47:10 +00:00
}
}
//
// step through map blocks
// Count is present to prevent a round off error from skipping the break
2017-03-10 09:37:43 +00:00
int itres = - 1 ;
2016-04-03 19:41:58 +00:00
for ( count = 0 ; count < 1000 ; count + + )
2016-03-01 15:47:10 +00:00
{
2016-03-08 00:26:13 +00:00
// 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.
2017-03-18 10:44:26 +00:00
if ( ! level . blockmap . isValidBlock ( mapx , mapy ) )
2016-03-08 00:26:13 +00:00
{
break ;
}
2016-12-08 10:23:08 +00:00
itres = P_SightBlockLinesIterator ( mapx , mapy ) ;
if ( itres = = 0 )
2016-03-01 15:47:10 +00:00
{
2016-03-08 00:26:13 +00:00
sightcounts [ 1 ] + + ;
2016-03-01 15:47:10 +00:00
return false ; // early out
}
2016-03-07 20:58:34 +00:00
// either reached the end or had an early-out condition with portals left to check,
2016-12-08 10:23:08 +00:00
if ( itres = = - 1 | | ( mapxstep | mapystep ) = = 0 )
2016-03-01 15:47:10 +00:00
break ;
2016-04-02 10:14:56 +00:00
switch ( ( ( xs_FloorToInt ( yintercept ) = = mapy ) < < 1 ) | ( xs_FloorToInt ( xintercept ) = = mapx ) )
2016-03-01 15:47:10 +00:00
{
case 0 : // neither xintercept nor yintercept match!
sightcounts [ 5 ] + + ;
// Continuing won't make things any better, so we might as well stop right here
2016-04-03 19:41:58 +00:00
count = 1000 ;
2016-03-01 15:47:10 +00:00
break ;
case 1 : // xintercept matches
xintercept + = xstep ;
mapy + = mapystep ;
2016-03-28 14:22:21 +00:00
if ( mapy = = mapey )
2016-03-01 15:47:10 +00:00
mapystep = 0 ;
break ;
case 2 : // yintercept matches
yintercept + = ystep ;
mapx + = mapxstep ;
2016-03-28 14:22:21 +00:00
if ( mapx = = mapex )
2016-03-01 15:47:10 +00:00
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 ;
2016-03-28 14:22:21 +00:00
if ( mapx = = mapex )
2016-03-01 15:47:10 +00:00
mapxstep = 0 ;
2016-03-28 14:22:21 +00:00
if ( mapy = = mapey )
2016-03-01 15:47:10 +00:00
mapystep = 0 ;
break ;
}
}
//
// couldn't early out, so go through the sorted list
//
sightcounts [ 2 ] + + ;
2016-12-08 10:23:08 +00:00
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.
2017-01-08 18:34:04 +00:00
if ( seeingthing - > Sector - > PortalGroup ! = portalgroup ) return false ; // We are in a different group than the seeingthing, so this trace cannot determine visibility alone.
2016-12-08 10:23:08 +00:00
return traverseres ;
2016-03-01 15:47:10 +00:00
}
/*
= = = = = = = = = = = = = = = = = = = = =
=
= 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
=
= = = = = = = = = = = = = = = = = = = = =
*/
2016-03-07 20:58:34 +00:00
bool P_CheckSight ( AActor * t1 , AActor * t2 , int flags )
2016-03-01 15:47:10 +00:00
{
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 ;
2017-01-07 19:02:25 +00:00
int pnum = int ( s1 - > Index ( ) ) * level . sectors . Size ( ) + int ( s2 - > Index ( ) ) ;
2016-03-01 15:47:10 +00:00
//
// check for trivial rejection
//
2017-03-17 11:49:43 +00:00
if ( level . rejectmatrix . Size ( ) > 0 & &
( level . rejectmatrix [ pnum > > 3 ] & ( 1 < < ( pnum & 7 ) ) ) )
2016-03-01 15:47:10 +00:00
{
sightcounts [ 0 ] + + ;
res = false ; // can't possibly be connected
goto done ;
}
//
// check precisely
//
// [RH] Andy Baker's stealth monsters:
// Cannot see an invisible object
2016-03-21 11:18:46 +00:00
if ( ( flags & SF_IGNOREVISIBILITY ) = = 0 & & ( ( t2 - > renderflags & RF_INVISIBLE ) | | ! t2 - > RenderStyle . IsVisible ( t2 - > Alpha ) ) )
2016-03-01 15:47:10 +00:00
{ // 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 ( ) & &
2016-03-30 07:41:46 +00:00
( ( 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 ) ) ) )
2016-03-01 15:47:10 +00:00
| |
( s2 - > GetHeightSec ( ) & &
2016-03-30 07:41:46 +00:00
( ( 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 ) ) ) ) )
2016-03-01 15:47:10 +00:00
{
res = false ;
goto done ;
}
}
// An unobstructed LOS is possible.
// Now look from eyes of t1 to any part of t2.
validcount + + ;
2016-03-08 00:26:13 +00:00
portals . Clear ( ) ;
2016-03-01 15:47:10 +00:00
{
2016-03-07 20:58:34 +00:00
sector_t * sec ;
2016-04-07 10:14:34 +00:00
double lookheight = t1 - > Z ( ) + t1 - > Height * 0.75 ;
2016-03-07 20:58:34 +00:00
t1 - > GetPortalTransition ( lookheight , & sec ) ;
2016-04-02 10:14:56 +00:00
double bottomslope = t2 - > Z ( ) - lookheight ;
2016-03-28 14:22:21 +00:00
double topslope = bottomslope + t2 - > Height ;
2016-03-07 20:58:34 +00:00
SightTask task = { 0 , topslope , bottomslope , - 1 , sec - > PortalGroup } ;
SightCheck s ;
s . init ( t1 , t2 , sec , & task , flags ) ;
res = s . P_SightPathTraverse ( ) ;
if ( ! res )
{
2016-03-28 08:01:24 +00:00
double dist = t1 - > Distance2D ( t2 ) ;
2016-03-07 20:58:34 +00:00
for ( unsigned i = 0 ; i < portals . Size ( ) ; i + + )
{
2016-03-28 14:22:21 +00:00
portals [ i ] . Frac + = 1 / dist ;
2016-03-07 20:58:34 +00:00
s . init ( t1 , t2 , NULL , & portals [ i ] , flags ) ;
2016-03-08 00:26:13 +00:00
if ( s . P_SightPathTraverse ( ) )
{
res = true ;
break ;
}
2016-03-07 20:58:34 +00:00
}
}
2016-03-01 15:47:10 +00:00
}
done :
SightCycles . Unclock ( ) ;
return res ;
}
2016-10-26 09:30:30 +00:00
DEFINE_ACTION_FUNCTION ( AActor , CheckSight )
{
PARAM_SELF_PROLOGUE ( AActor ) ;
2016-12-02 11:06:49 +00:00
PARAM_OBJECT_NOT_NULL ( target , AActor ) ;
2016-10-27 22:32:52 +00:00
PARAM_INT_DEF ( flags ) ;
2016-10-26 09:30:30 +00:00
ACTION_RETURN_BOOL ( P_CheckSight ( self , target , flags ) ) ;
}
2016-03-01 15:47:10 +00:00
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 ) ) ;
}