og-odamex/common/p_maputl.cpp
2026-07-30 01:41:16 -05:00

1295 lines
28 KiB
C++

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id$
//
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2006-2026 by The Odamex Team.
//
// 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 2
// 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.
//
// DESCRIPTION:
// Movement/collision utility functions,
// as used by function in p_map.c.
// BLOCKMAP Iterator functions,
// and some PIT_* functions to use for iteration.
//
//-----------------------------------------------------------------------------
#include "odamex.h"
#include "m_bbox.h"
#include "p_local.h"
#include "p_mobj.h"
#include "r_data.h"
#include "m_random.h"
// State.
#include "r_state.h"
EXTERN_CVAR (co_blockmapfix)
EXTERN_CVAR (co_zdoomphys)
//
//
// P_PointOnSide
//
// Traverse BSP (sub) tree, check point against partition plane.
// Returns side 0 (front) or 1 (back).
//
// killough 5/2/98: reformatted
// [SL] This is a version for the physics code so that R_PointOnSide
// may be changed without affecting compatibility.
//
static int P_PointOnSide(fixed_t x, fixed_t y, node_t *node)
{
if (!node->dx)
return x <= node->x ? node->dy > 0 : node->dy < 0;
if (!node->dy)
return y <= node->y ? node->dx < 0 : node->dx > 0;
x -= node->x;
y -= node->y;
// Try to quickly decide by looking at sign bits.
if ((node->dy ^ node->dx ^ x ^ y) < 0)
return (node->dy ^ x) < 0; // (left is negative)
return FixedMul (y, node->dx >> FRACBITS) >= FixedMul (node->dy >> FRACBITS, x);
}
//
//
// P_PointInSubsector
//
//
subsector_t* P_PointInSubsector(fixed_t x, fixed_t y)
{
node_t *node;
int side;
int nodenum;
// single subsector is a special case
if (!numnodes)
return subsectors;
nodenum = numnodes-1;
while (! (nodenum & NF_SUBSECTOR) )
{
node = &nodes[nodenum];
side = P_PointOnSide (x, y, node);
nodenum = node->children[side];
}
return &subsectors[nodenum & ~NF_SUBSECTOR];
}
AActor::ActorBlockMapListNode::ActorBlockMapListNode(AActor *mo) :
m_actor (mo),
m_next (1, nullptr), // Always at least 1 element so that an index calculation of 0 results in valid access.
m_prev (1, nullptr) // Always at least 1 element so that an index calculation of 0 results in valid access.
{
clear();
}
void AActor::ActorBlockMapListNode::Link()
{
int left = (m_actor->x - m_actor->radius - bmaporgx) >> MAPBLOCKSHIFT;
int right = (m_actor->x + m_actor->radius - bmaporgx) >> MAPBLOCKSHIFT;
int top = (m_actor->y - m_actor->radius - bmaporgy) >> MAPBLOCKSHIFT;
int bottom = (m_actor->y + m_actor->radius - bmaporgy) >> MAPBLOCKSHIFT;
if (!co_blockmapfix)
{
// originally Doom only used the block containing the center point
// of the actor even if the actor overlapped into other blocks
top = bottom = (m_actor->y - bmaporgy) >> MAPBLOCKSHIFT;
left = right = (m_actor->x - bmaporgx) >> MAPBLOCKSHIFT;
}
// do not ignore actors only *partially* outside blockmap
// e.g. do not ignore an actor just because its left edge is off the left
// side of the blockmap - its *right* edge must be off the left side as well
if (right >= 0 && left < bmapwidth && bottom >= 0 && top < bmapheight)
{
// however, need to clamp a partially off-limits actor to the grid
if (left < 0) left = 0;
if (right >= bmapwidth) right = bmapwidth - 1;
if (top < 0) top = 0;
if (bottom >= bmapheight) bottom = bmapheight - 1;
m_originx = left;
m_originy = top;
m_blockcntx = right - left + 1;
m_blockcnty = bottom - top + 1;
m_next.resize(m_blockcntx * m_blockcnty);
m_prev.resize(m_blockcntx * m_blockcnty);
// [SL] 2012-05-15 - Add the actor to the blocklinks list for all of the
// blockmaps it overlaps, not just the blockmap for the actor's center point.
for (int bmy = top; bmy <= bottom; bmy++)
{
for (int bmx = left; bmx <= right; bmx++)
{
// killough 8/11/98: simpler scheme using pointer-to-pointer prev
// pointers, allows head nodes to be treated like everything else
AActor** headptr = &blocklinks[bmy * bmapwidth + bmx];
AActor* headactor = *headptr;
size_t thisidx = getIndex(bmx, bmy);
if ((m_next[thisidx] = headactor))
{
size_t nextidx = headactor->bmapnode.getIndex(bmx, bmy);
headactor->bmapnode.m_prev[nextidx] = & m_next[thisidx];
}
m_prev[thisidx] = headptr;
*headptr = m_actor;
}
}
}
else
{
clear();
}
}
void AActor::ActorBlockMapListNode::Unlink()
{
for (int bmy = m_originy; bmy < m_originy + m_blockcnty; bmy++)
{
for (int bmx = m_originx; bmx < m_originx + m_blockcntx; bmx++)
{
// killough 8/11/98: simpler scheme using pointers-to-pointers for prev
// pointers, allows head node pointers to be treated like everything else
//
// Also more robust, since it doesn't depend on current position for
// unlinking. Old method required computing head node based on position
// at time of unlinking, assuming it was the same position as during
// linking.
size_t thisidx = getIndex(bmx, bmy);
AActor* nextactor = m_next[thisidx];
AActor** prevactor = m_prev[thisidx];
if (prevactor && (*prevactor = nextactor))
{
size_t nextidx = nextactor->bmapnode.getIndex(bmx, bmy);
nextactor->bmapnode.m_prev[nextidx] = prevactor;
}
}
}
}
AActor* AActor::ActorBlockMapListNode::Next(int bmx, int bmy)
{
if (bmx < 0 || bmx >= bmapwidth || bmy < 0 || bmy >= bmapheight)
return nullptr;
return m_next[getIndex(bmx, bmy)];
}
void AActor::ActorBlockMapListNode::clear()
{
m_originx = 0;
m_originy = 0;
m_blockcntx = 0;
m_blockcnty = 0;
std::fill(m_next.begin(), m_next.end(), nullptr);
std::fill(m_prev.begin(), m_prev.end(), nullptr);
}
size_t AActor::ActorBlockMapListNode::getIndex(int bmx, int bmy)
{
if (!co_blockmapfix)
return 0;
// range check
if (bmx < m_originx || bmx > m_originx + m_blockcntx - 1 ||
bmy < m_originy || bmy > m_originy + m_blockcnty - 1)
return 0;
return (bmy - m_originy) * m_blockcntx + bmx - m_originx;
}
//
// P_AproxDistance
// Gives an estimation of distance (not exact)
//
fixed_t P_AproxDistance (fixed_t dx, fixed_t dy)
{
dx = abs(dx);
dy = abs(dy);
if (dx < dy)
return dx+dy-(dx>>1);
return dx+dy-(dy>>1);
}
fixed_t P_AproxDistance2 (const fixed_t *pos_array, fixed_t x, fixed_t y)
{
if (pos_array)
{
fixed_t adx = abs(pos_array[0] - x);
fixed_t ady = abs(pos_array[1] - y);
// From _GG1_ p.428. Appox. eucledian distance fast.
return adx + ady - ((adx < ady ? adx : ady)>>1);
}
else
return 0;
}
fixed_t P_AproxDistance2 (const AActor *mo, fixed_t x, fixed_t y)
{
if (mo)
return P_AproxDistance2(&mo->x, x, y);
else
return 0;
}
fixed_t P_AproxDistance2 (const AActor *a, const AActor *b)
{
if (a && b)
return P_AproxDistance2(&a->x, b->x, b->y);
else
return 0;
}
//
// P_PointOnLineSide
// Returns 0 (front) or 1 (back)
//
int P_PointOnLineSide (fixed_t x, fixed_t y, const line_t *line)
{
if (co_zdoomphys)
{
// Make use of vector cross product
return int64_t(y - line->v1->y) * int64_t(line->dx) +
int64_t(line->v1->x - x) * int64_t(line->dy) >= 0;
}
else
{
if (!line->dx)
{
return (x <= line->v1->x) ? (line->dy > 0) : (line->dy < 0);
}
else if (!line->dy)
{
return (y <= line->v1->y) ? (line->dx < 0) : (line->dx > 0);
}
else
{
return FixedMul (line->dy >> FRACBITS, x - line->v1->x)
<= FixedMul (y - line->v1->y , line->dx >> FRACBITS);
}
}
}
//
// P_BoxOnLineSide
// Considers the line to be infinite
// Returns side 0 or 1, -1 if box crosses the line.
//
int P_BoxOnLineSide (const fixed_t *tmbox, const line_t *ld)
{
int p1 = 0;
int p2 = 0;
switch (ld->slopetype)
{
case ST_HORIZONTAL:
p1 = tmbox[BOXTOP] > ld->v1->y;
p2 = tmbox[BOXBOTTOM] > ld->v1->y;
if (ld->dx < 0)
{
p1 ^= 1;
p2 ^= 1;
}
break;
case ST_VERTICAL:
p1 = tmbox[BOXRIGHT] < ld->v1->x;
p2 = tmbox[BOXLEFT] < ld->v1->x;
if (ld->dy < 0)
{
p1 ^= 1;
p2 ^= 1;
}
break;
case ST_POSITIVE:
p1 = P_PointOnLineSide (tmbox[BOXLEFT], tmbox[BOXTOP], ld);
p2 = P_PointOnLineSide (tmbox[BOXRIGHT], tmbox[BOXBOTTOM], ld);
break;
case ST_NEGATIVE:
p1 = P_PointOnLineSide (tmbox[BOXRIGHT], tmbox[BOXTOP], ld);
p2 = P_PointOnLineSide (tmbox[BOXLEFT], tmbox[BOXBOTTOM], ld);
break;
}
return (p1 == p2) ? p1 : -1;
}
//
// P_PointOnDivlineSide
// Returns 0 (front) or 1 (back).
//
int P_PointOnDivlineSide (fixed_t x, fixed_t y, const divline_t *line)
{
if (co_zdoomphys)
{
// Make use of vector cross product
return int64_t(y - line->y) * int64_t(line->dx) +
int64_t(line->x - x) * int64_t(line->dy) >= 0;
}
else
{
if (!line->dx)
{
return (x <= line->x) ? (line->dy > 0) : (line->dy < 0);
}
else if (!line->dy)
{
return (y <= line->y) ? (line->dx < 0) : (line->dx > 0);
}
else
{
fixed_t dx = (x - line->x);
fixed_t dy = (y - line->y);
// try to quickly decide by looking at sign bits
if ((line->dy ^ line->dx ^ dx ^ dy) & 0x80000000)
{ // (left is negative)
return ((line->dy ^ dx) & 0x80000000) ? 1 : 0;
}
else
{ // if (left >= right), return 1, 0 otherwise
return FixedMul (dy >> 8, line->dx >> 8) >= FixedMul (line->dy >> 8, dx >> 8);
}
}
}
}
//
// P_MakeDivline
//
void P_MakeDivline (const line_t *li, divline_t *dl)
{
dl->x = li->v1->x;
dl->y = li->v1->y;
dl->dx = li->dx;
dl->dy = li->dy;
}
//
// P_InterceptVector
// Returns the fractional intercept point along the first divline.
// This is only called by the addthings and addlines traversers.
//
fixed_t P_InterceptVector (const divline_t *v2, const divline_t *v1)
{
if (co_zdoomphys)
{
// [RH] Use 64 bit ints, so long divlines don't overflow
int64_t den =
(int64_t(v1->dy) * int64_t(v2->dx) -
int64_t(v1->dx) * int64_t(v2->dy)) >> FRACBITS;
if (den == 0)
return 0; // parallel
int64_t num =
int64_t(v1->x - v2->x) * int64_t(v1->dy) +
int64_t(v2->y - v1->y) * int64_t(v1->dx);
return (fixed_t)(num / den);
}
else
{
fixed_t den = FixedMul (v1->dy>>8,v2->dx) - FixedMul(v1->dx>>8,v2->dy);
if (den == 0)
return 0;
fixed_t num =
FixedMul ( (v1->x - v2->x)>>8 ,v1->dy )
+FixedMul ( (v2->y - v1->y)>>8, v1->dx );
fixed_t frac = FixedDiv (num , den);
return frac;
}
}
//
// P_LineOpening
// Sets opentop and openbottom to the window
// through a two sided line.
// OPTIMIZE: keep this precalculated
//
fixed_t opentop;
fixed_t openbottom;
fixed_t openrange;
fixed_t lowfloor;
sector_t *openbottomsec;
void P_LineOpening (const line_t *linedef, fixed_t x, fixed_t y, fixed_t refx, fixed_t refy)
{
if (linedef->sidenum[1] == R_NOSIDE)
{
// single sided line
openrange = 0;
return;
}
sector_t *front = linedef->frontsector;
sector_t *back = linedef->backsector;
fixed_t fc = P_CeilingHeight(x, y, front);
fixed_t ff = P_FloorHeight(x, y, front);
fixed_t bc = P_CeilingHeight(x, y, back);
fixed_t bf = P_FloorHeight(x, y, back);
opentop = MIN<fixed_t>(fc, bc);
bool fflevel = P_IsPlaneLevel(&front->floorplane);
bool bflevel = P_IsPlaneLevel(&back->floorplane);
bool usefront = (ff > bf);
// [RH] fudge a bit for actors that are moving across lines
// bordering a slope/non-slope that meet on the floor. Note
// that imprecisions in the plane equation mean there is a
// good chance that even if a slope and non-slope look like
// they line up, they won't be perfectly aligned.
if ((!fflevel || !bflevel) && abs(ff - bf) < 256)
{
if (fflevel)
usefront = true;
else if (bflevel)
usefront = false;
else if (refx != limits::MINFIXED)
usefront = !P_PointOnLineSide(refx, refy, linedef);
}
if (usefront)
{
openbottom = ff;
lowfloor = bf;
openbottomsec = front;
}
else
{
openbottom = bf;
lowfloor = ff;
openbottomsec = back;
}
openrange = opentop - openbottom;
}
//
// THING POSITION SETTING
//
//
// P_UnsetThingPosition
// Unlinks a thing from block map and sectors.
// On each position change, BLOCKMAP and other
// lookups maintaining lists of things inside
// these structures need to be updated.
//
void AActor::UnlinkFromWorld ()
{
sector_list = NULL;
if(!subsector)
return;
if (!(flags & MF_NOSECTOR))
{
// invisible things don't need to be in sector list
// unlink from subsector
// killough 8/11/98: simpler scheme using pointers-to-pointers for prev
// pointers, allows head node pointers to be treated like everything else
AActor **prev = sprev;
AActor *next = snext;
if ((*prev = next)) // unlink from sector list
next->sprev = prev;
// phares 3/14/98
//
// Save the sector list pointed to by touching_sectorlist.
// In P_SetThingPosition, we'll keep any nodes that represent
// sectors the Thing still touches. We'll add new ones then, and
// delete any nodes for sectors the Thing has vacated. Then we'll
// put it back into touching_sectorlist. It's done this way to
// avoid a lot of deleting/creating for nodes, when most of the
// time you just get back what you deleted anyway.
//
// If this Thing is being removed entirely, then the calling
// routine will clear out the nodes in sector_list.
sector_list = touching_sectorlist;
touching_sectorlist = NULL; //to be restored by P_SetThingPosition
}
if ( !(flags & MF_NOBLOCKMAP) )
{
bmapnode.Unlink();
}
subsector = NULL;
}
//
// P_SetThingPosition
// Links a thing into both a block and a subsector based on it's x y.
// Sets thing->subsector properly
//
void AActor::LinkToWorld ()
{
// link into subsector
subsector = P_PointInSubsector (x, y);
if (!subsector)
return;
if ( !(flags & MF_NOSECTOR) )
{
// invisible things don't go into the sector links
// killough 8/11/98: simpler scheme using pointer-to-pointer prev
// pointers, allows head nodes to be treated like everything else
AActor **link = &subsector->sector->thinglist;
AActor *next = *link;
if ((snext = next))
next->sprev = &snext;
sprev = link;
*link = this;
// phares 3/16/98
//
// If sector_list isn't NULL, it has a collection of sector
// nodes that were just removed from this Thing.
// Collect the sectors the object will live in by looking at
// the existing sector_list and adding new nodes and deleting
// obsolete ones.
// When a node is deleted, its sector links (the links starting
// at sector_t->touching_thinglist) are broken. When a node is
// added, new sector links are created.
P_CreateSecNodeList (this, x, y);
touching_sectorlist = sector_list; // Attach to thing
sector_list = NULL; // clear for next time
}
// link into blockmap
if ( !(flags & MF_NOBLOCKMAP) )
{
bmapnode.Link();
}
}
void AActor::SetOrigin (fixed_t ix, fixed_t iy, fixed_t iz)
{
UnlinkFromWorld ();
x = ix;
y = iy;
z = iz;
LinkToWorld ();
}
//
// INTERCEPT ROUTINES
//
// denis - make intercepts array resizeable
std::vector<intercept_t> intercepts;
divline_t trace;
//
// PIT_AddLineIntercepts.
// Looks for lines in the given block
// that intercept the given trace
// to add to the intercepts list.
//
// A line is crossed if its endpoints
// are on opposite sides of the trace.
// Returns true if earlyout and a solid line hit.
//
bool PIT_AddLineIntercepts (line_t& ld, bool earlyout)
{
int s1;
int s2;
divline_t dl;
// avoid precision problems with two routines
if ( trace.dx > FRACUNIT*16
|| trace.dy > FRACUNIT*16
|| trace.dx < -FRACUNIT*16
|| trace.dy < -FRACUNIT*16)
{
s1 = P_PointOnDivlineSide (ld.v1->x, ld.v1->y, &trace);
s2 = P_PointOnDivlineSide (ld.v2->x, ld.v2->y, &trace);
}
else
{
s1 = P_PointOnLineSide (trace.x, trace.y, &ld);
s2 = P_PointOnLineSide (trace.x+trace.dx, trace.y+trace.dy, &ld);
}
if (s1 == s2)
return true; // line isn't crossed
// hit the line
P_MakeDivline (&ld, &dl);
const fixed_t frac = P_InterceptVector (&trace, &dl);
if (frac < 0)
return true; // behind source
// try to early out the check
if (earlyout
&& frac < FRACUNIT
&& !ld.backsector)
{
return false; // stop checking
}
intercept_t intercept;
intercept.frac = frac;
intercept.isaline = true;
intercept.d.line = &ld;
intercepts.push_back(intercept);
return true; // continue
}
//
// PIT_AddThingIntercepts
//
bool PIT_AddThingIntercepts (AActor& thing)
{
fixed_t x1;
fixed_t y1;
fixed_t x2;
fixed_t y2;
divline_t dl;
const bool tracepositive = (trace.dx ^ trace.dy)>0;
// check a corner to corner crossection for hit
if (tracepositive)
{
x1 = thing.x - thing.radius;
y1 = thing.y + thing.radius;
x2 = thing.x + thing.radius;
y2 = thing.y - thing.radius;
}
else
{
x1 = thing.x - thing.radius;
y1 = thing.y - thing.radius;
x2 = thing.x + thing.radius;
y2 = thing.y + thing.radius;
}
const int s1 = P_PointOnDivlineSide (x1, y1, &trace);
const int s2 = P_PointOnDivlineSide (x2, y2, &trace);
if (s1 == s2)
return true; // line isn't crossed
dl.x = x1;
dl.y = y1;
dl.dx = x2-x1;
dl.dy = y2-y1;
const fixed_t frac = P_InterceptVector (&trace, &dl);
if (frac < 0)
return true; // behind source
intercept_t intercept;
intercept.frac = frac;
intercept.isaline = false;
intercept.d.thing = &thing;
intercepts.push_back(intercept);
return true; // keep going
}
//
// P_TraverseIntercepts
// Returns true if the traverser function returns true
// for all lines.
//
bool P_TraverseIntercepts (traverser_t func, fixed_t maxfrac)
{
size_t count = intercepts.size();
fixed_t dist;
intercept_t* in = 0;
while (count--)
{
dist = limits::MAXFIXED;
for (intercept_t& intercept : intercepts)
{
if (intercept.frac < dist)
{
dist = intercept.frac;
in = &intercept;
}
}
if (dist > maxfrac)
return true; // checked everything in range
if ( !func (in) )
return false; // don't bother going farther
in->frac = limits::MAXFIXED;
}
return true; // everything was traversed
}
//
// P_PathTraverse
// Traces a line from x1,y1 to x2,y2,
// calling the traverser function for each.
// Returns true if the traverser function returns true
// for all lines.
//
bool P_PathTraverse (fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, int flags, bool (*trav) (intercept_t *))
{
fixed_t xt1;
fixed_t yt1;
fixed_t xt2;
fixed_t yt2;
fixed_t xstep;
fixed_t ystep;
fixed_t partial;
fixed_t xintercept;
fixed_t yintercept;
int mapx;
int mapy;
int mapxstep;
int mapystep;
int count;
const bool earlyout = flags & PT_EARLYOUT;
validcount++;
intercepts.clear();
if ( ((x1-bmaporgx)&(MAPBLOCKSIZE-1)) == 0)
x1 += FRACUNIT; // don't side exactly on a line
if ( ((y1-bmaporgy)&(MAPBLOCKSIZE-1)) == 0)
y1 += FRACUNIT; // don't side exactly on a line
trace.x = x1;
trace.y = y1;
trace.dx = x2 - x1;
trace.dy = y2 - y1;
x1 -= bmaporgx;
y1 -= bmaporgy;
xt1 = x1>>MAPBLOCKSHIFT;
yt1 = y1>>MAPBLOCKSHIFT;
x2 -= bmaporgx;
y2 -= bmaporgy;
xt2 = x2>>MAPBLOCKSHIFT;
yt2 = y2>>MAPBLOCKSHIFT;
if (xt2 > xt1)
{
mapxstep = 1;
partial = FRACUNIT - ((x1>>MAPBTOFRAC)&(FRACUNIT-1));
ystep = FixedDiv (y2-y1,abs(x2-x1));
}
else if (xt2 < xt1)
{
mapxstep = -1;
partial = (x1>>MAPBTOFRAC)&(FRACUNIT-1);
ystep = FixedDiv (y2-y1,abs(x2-x1));
}
else
{
mapxstep = 0;
partial = FRACUNIT;
ystep = 256*FRACUNIT;
}
yintercept = (y1>>MAPBTOFRAC) + FixedMul (partial, ystep);
if (yt2 > yt1)
{
mapystep = 1;
partial = FRACUNIT - ((y1>>MAPBTOFRAC)&(FRACUNIT-1));
xstep = FixedDiv (x2-x1,abs(y2-y1));
}
else if (yt2 < yt1)
{
mapystep = -1;
partial = (y1>>MAPBTOFRAC)&(FRACUNIT-1);
xstep = FixedDiv (x2-x1,abs(y2-y1));
}
else
{
mapystep = 0;
partial = FRACUNIT;
xstep = 256*FRACUNIT;
}
xintercept = (x1>>MAPBTOFRAC) + FixedMul (partial, xstep);
// Step through map blocks.
// Count is present to prevent a round off error
// from skipping the break.
mapx = xt1;
mapy = yt1;
for (count = 0 ; count < 64 ; count++)
{
if (flags & PT_ADDLINES)
{
if (!P_BlockLinesIterator (mapx, mapy,PIT_AddLineIntercepts, earlyout))
return false; // early out
}
if (flags & PT_ADDTHINGS)
{
if (!P_BlockThingsIterator (mapx, mapy,PIT_AddThingIntercepts, nullptr))
return false; // early out
}
if (mapx == xt2 && mapy == yt2)
{
break;
}
if ( (yintercept >> FRACBITS) == mapy)
{
yintercept += ystep;
mapx += mapxstep;
}
else if ( (xintercept >> FRACBITS) == mapx)
{
xintercept += xstep;
mapy += mapystep;
}
}
// go through the sorted list
return P_TraverseIntercepts ( trav, FRACUNIT );
}
//
// P_PointToAngle
//
// To get a global angle from cartesian coordinates,
// the coordinates are flipped until they are in
// the first octant of the coordinate system, then
// the y (<=x) is scaled and divided by x to get a
// tangent (slope) value which is looked up in the
// tantoangle[] table. The +1 size of tantoangle[]
// is to handle the case when x==y without additional
// checking.
//
// killough 5/2/98: reformatted, cleaned up
// haleyjd 01/28/10: restored to Vanilla and made some modifications;
// added P_ version for use by gamecode.
//
angle_t P_PointToAngle(fixed_t xo, fixed_t yo, fixed_t x, fixed_t y)
{
x -= xo;
y -= yo;
if((x | y) == 0)
return 0;
if(x >= 0)
{
if (y >= 0)
{
if(x > y)
{
// octant 0
return p_tantoangle[SlopeDiv(y, x)];
}
else
{
// octant 1
return ANG90 - 1 - p_tantoangle[SlopeDiv(x, y)];
}
}
else
{
y = -y;
if(x > y)
{
// octant 8
return 0 - p_tantoangle[SlopeDiv(y, x)];
}
else
{
// octant 7
return ANG270 + p_tantoangle[SlopeDiv(x, y)];
}
}
}
else
{
x = -x;
if(y >= 0)
{
if(x > y)
{
// octant 3
return ANG180 - 1 - p_tantoangle[SlopeDiv(y, x)];
}
else
{
// octant 2
return ANG90 + p_tantoangle[SlopeDiv(x, y)];
}
}
else
{
y = -y;
if(x > y)
{
// octant 4
return ANG180 + p_tantoangle[SlopeDiv(y, x)];
}
else
{
// octant 5
return ANG270 - 1 - p_tantoangle[SlopeDiv(x, y)];
}
}
}
return 0;
}
//
// P_ActorInFOV
//
// Returns true if the actor mo is in the field-of-view of the actor origin,
// with FOV specified by f (0.0 - 180.0) and within a maximum distance specified
// by dist.
//
bool P_ActorInFOV(const AActor* origin, const AActor* mo , float f, fixed_t dist)
{
if (f <= 0.0f)
return false;
if (f > 180.0f)
f = 180.0f;
if (!mo)
return false;
// check that the actors are within a radius of dist of each other
// (A very cheap calculation)
if (P_AproxDistance2(origin, mo) > dist)
return false;
// check that the actor mo is in front of origin's field of view
// (Not so expensive...)
// transform and rotate so that tx and ty represent mo's location with respect
// to the direction origin is looking
fixed_t tx, ty;
R_RotatePoint(mo->x - origin->x, mo->y - origin->y, ANG90 - origin->angle, tx, ty);
// mo is behind origin?
if (ty < 4*FRACUNIT)
return false;
// calculate the angle from the direction origin is facing to mo
float ang;
tx = abs(tx); // just to make calculations simplier
if (tx > ty)
ang = 360.0f * (ANG90 - 1 - tantoangle_acc[SlopeDiv(ty, tx)]) / ANG360;
else
ang = 360.0f * tantoangle_acc[SlopeDiv(tx, ty)] / ANG360;
// is the actor mo within the FOV specified by f?
if (ang > f / 2.0f)
return false;
// check to see if the actor mo is hidden behind walls, etc
// (A very expensive calculation)
if (!P_CheckSightEdges(origin, mo, 0.0))
return false;
return true;
}
//
// RoughMonsterCheck
// Searches though the surrounding mapblocks for monsters/players
// based on Hexen's P_RoughMonsterSearch
//
// This allows friendlies (and hostiles) to target each other
//
// distance is in MAPBLOCKUNITS
AActor* RoughMonsterCheck(AActor* mo, int index, angle_t fov)
{
const int bx = index % bmapwidth;
const int by = index / bmapwidth;
for (AActor* link = blocklinks[index]; link != nullptr; link = link->bmapnode.Next(bx, by))
{
// skip non-shootable actors
if (!(link->flags & MF_SHOOTABLE))
continue;
// skip yourself
if (link == mo)
continue;
// skip barrels and other shootable but not alive things
if (!sentient(link))
continue;
// Don't target things friendly to you.
if (P_IsFriendlyThing(mo, link))
continue;
// Don't target players or spectators (done elsewhere)
if (link->player || (link->player && link->player->spectator))
continue;
// skip actors outside of specified FOV
if (fov > 0 && !P_CheckFov(mo, link, fov))
continue;
// skip actors not in line of sight
if (!P_CheckSight(mo, link))
continue;
// all good! return it.
return link;
}
// couldn't find a valid target
return NULL;
}
//
// RoughTracerCheck
// Searches though the surrounding mapblocks for monsters/players
// based on Hexen's P_RoughMonsterSearch
//
// Special logic to handle tracers (actor->target is owner of tracer)
//
// distance is in MAPBLOCKUNITS
AActor* RoughTracerCheck(AActor* mo, int index, angle_t fov)
{
const int bx = index % bmapwidth;
const int by = index / bmapwidth;
for (AActor* link = blocklinks[index]; link != nullptr; link = link->bmapnode.Next(bx, by))
{
// skip non-shootable actors
if (!(link->flags & MF_SHOOTABLE))
continue;
// skip the projectile's owner
if (link == mo->target)
continue;
// [Blair] Don't target friendlies
if (P_IsFriendlyThing(mo->target, link))
continue;
// [Blair] Don't target spectators
if (link->player && link->player->spectator)
continue;
// [Blair] Don't target teammates
if (mo->target->player && link->player &&
P_AreTeammates(*mo->target->player, *link->player))
continue;
// skip actors outside of specified FOV
if (fov > 0 && !P_CheckFov(mo, link, fov))
continue;
// skip actors not in line of sight
if (!P_CheckSight(mo, link))
continue;
// all good! return it.
return link;
}
// couldn't find a valid target
return NULL;
}
AActor* P_RoughTargetSearch(AActor* mo, angle_t fov, int distance, AActor* (*searchFunc)(AActor*, int, angle_t))
{
int blockX;
int blockY;
int startX, startY;
int blockIndex;
int firstStop;
int secondStop;
int thirdStop;
int finalStop;
int count;
AActor* target;
startX = (mo->x - bmaporgx) >> MAPBLOCKSHIFT;
startY = (mo->y - bmaporgy) >> MAPBLOCKSHIFT;
if (startX >= 0 && startX < bmapwidth && startY >= 0 && startY < bmapheight)
{
if ((target = searchFunc(mo, startY * bmapwidth + startX, fov)))
{ // found a target right away
return target;
}
}
for (count = 1; count <= distance; count++)
{
blockX = startX - count;
blockY = startY - count;
if (blockY < 0)
{
blockY = 0;
}
else if (blockY >= bmapheight)
{
blockY = bmapheight - 1;
}
if (blockX < 0)
{
blockX = 0;
}
else if (blockX >= bmapwidth)
{
blockX = bmapwidth - 1;
}
blockIndex = blockY * bmapwidth + blockX;
firstStop = startX + count;
if (firstStop < 0)
{
continue;
}
if (firstStop >= bmapwidth)
{
firstStop = bmapwidth - 1;
}
secondStop = startY + count;
if (secondStop < 0)
{
continue;
}
if (secondStop >= bmapheight)
{
secondStop = bmapheight - 1;
}
thirdStop = secondStop * bmapwidth + blockX;
secondStop = secondStop * bmapwidth + firstStop;
firstStop += blockY * bmapwidth;
finalStop = blockIndex;
// Trace the first block section (along the top)
for (; blockIndex <= firstStop; blockIndex++)
{
if ((target = searchFunc(mo, blockIndex, fov)))
{
return target;
}
}
// Trace the second block section (right edge)
for (blockIndex--; blockIndex <= secondStop; blockIndex += bmapwidth)
{
if ((target = searchFunc(mo, blockIndex, fov)))
{
return target;
}
}
// Trace the third block section (bottom edge)
for (blockIndex -= bmapwidth; blockIndex >= thirdStop; blockIndex--)
{
if ((target = searchFunc(mo, blockIndex, fov)))
{
return target;
}
}
// Trace the final block section (left edge)
for (blockIndex++; blockIndex > finalStop; blockIndex -= bmapwidth)
{
if ((target = searchFunc(mo, blockIndex, fov)))
{
return target;
}
}
}
return NULL;
}
VERSION_CONTROL (p_maputl_cpp, "$Id$")