mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-29 23:33:00 +00:00
1981 lines
52 KiB
C++
1981 lines
52 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// Copyright 1993-1996 id Software
|
|
// Copyright 1994-1996 Raven Software
|
|
// Copyright 1998-1998 Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
|
|
// Copyright 1999-2016 Randy Heit
|
|
// Copyright 2002-2017 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/
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// DESCRIPTION:
|
|
// Movement/collision utility functions,
|
|
// as used by function in p_map.c.
|
|
// BLOCKMAP Iterator functions,
|
|
// and some PIT_* functions to use for iteration.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/* For code that originates from ZDoom the following applies:
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
#include "m_bbox.h"
|
|
|
|
#include "doomdef.h"
|
|
#include "doomdata.h"
|
|
#include "doomstat.h"
|
|
#include "p_local.h"
|
|
#include "p_maputl.h"
|
|
#include "p_3dmidtex.h"
|
|
#include "p_blockmap.h"
|
|
#include "r_utility.h"
|
|
#include "actor.h"
|
|
#include "actorinlines.h"
|
|
|
|
// State.
|
|
#include "po_man.h"
|
|
#include "vm.h"
|
|
|
|
sector_t *P_PointInSectorBuggy(double x, double y);
|
|
int P_VanillaPointOnDivlineSide(double x, double y, const divline_t* line);
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// P_AproxDistance
|
|
//
|
|
// Gives an estimation of distance (not exact)
|
|
//
|
|
//==========================================================================
|
|
|
|
int P_AproxDistance (int dx, int dy)
|
|
{
|
|
dx = abs(dx);
|
|
dy = abs(dy);
|
|
return (dx < dy) ? dx+dy-(dx>>1) : dx+dy-(dy>>1);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// P_InterceptVector
|
|
//
|
|
// Returns the fractional intercept point along the first divline.
|
|
//
|
|
//==========================================================================
|
|
|
|
double P_InterceptVector(const divline_t *v2, const divline_t *v1)
|
|
{
|
|
double num;
|
|
double den;
|
|
|
|
double v1x = v1->x;
|
|
double v1y = v1->y;
|
|
double v1dx = v1->dx;
|
|
double v1dy = v1->dy;
|
|
double v2x = v2->x;
|
|
double v2y = v2->y;
|
|
double v2dx = v2->dx;
|
|
double v2dy = v2->dy;
|
|
|
|
den = v1dy*v2dx - v1dx*v2dy;
|
|
|
|
if (den == 0)
|
|
return 0; // parallel
|
|
|
|
num = (v1x - v2x)*v1dy + (v2y - v1y)*v1dx;
|
|
return num / den;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// P_LineOpening
|
|
//
|
|
// Sets opentop and openbottom to the window
|
|
// through a two sided line.
|
|
//
|
|
//==========================================================================
|
|
|
|
void P_LineOpening (FLineOpening &open, AActor *actor, const line_t *linedef, const DVector2 &pos, const DVector2 *ref, int flags)
|
|
{
|
|
if (!(flags & FFCF_ONLY3DFLOORS))
|
|
{
|
|
sector_t *front, *back;
|
|
double fc = 0, ff = 0, bc = 0, bf = 0;
|
|
|
|
if (linedef->backsector == NULL)
|
|
{
|
|
// single sided line
|
|
open.range = 0;
|
|
return;
|
|
}
|
|
|
|
front = linedef->frontsector;
|
|
back = linedef->backsector;
|
|
|
|
if (!(flags & FFCF_NOPORTALS) && (linedef->flags & ML_PORTALCONNECT))
|
|
{
|
|
if (!linedef->frontsector->PortalBlocksMovement(sector_t::ceiling)) fc = LINEOPEN_MAX;
|
|
if (!linedef->backsector->PortalBlocksMovement(sector_t::ceiling)) bc = LINEOPEN_MAX;
|
|
if (!linedef->frontsector->PortalBlocksMovement(sector_t::floor)) ff = LINEOPEN_MIN;
|
|
if (!linedef->backsector->PortalBlocksMovement(sector_t::floor)) bf = LINEOPEN_MIN;
|
|
}
|
|
|
|
if (fc == 0) fc = front->ceilingplane.ZatPoint(pos);
|
|
if (bc == 0) bc = back->ceilingplane.ZatPoint(pos);
|
|
if (ff == 0) ff = front->floorplane.ZatPoint(pos);
|
|
if (bf == 0) bf = back->floorplane.ZatPoint(pos);
|
|
|
|
|
|
/*Printf ("]]]]]] %d %d\n", ff, bf);*/
|
|
|
|
open.topsec = fc < bc? front : back;
|
|
open.ceilingpic = open.topsec->GetTexture(sector_t::ceiling);
|
|
open.top = fc < bc ? fc : bc;
|
|
|
|
bool usefront;
|
|
|
|
// [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 (ff == -FLT_MIN || bf == -FLT_MIN || ref == NULL || fabs (ff-bf) > 1./256)
|
|
{
|
|
usefront = (ff > bf);
|
|
}
|
|
else
|
|
{
|
|
if (!front->floorplane.isSlope())
|
|
usefront = true;
|
|
else if (!back->floorplane.isSlope())
|
|
usefront = false;
|
|
else
|
|
usefront = !P_PointOnLineSide (*ref, linedef);
|
|
}
|
|
|
|
if (usefront)
|
|
{
|
|
open.bottom = ff;
|
|
open.bottomsec = front;
|
|
open.floorpic = front->GetTexture(sector_t::floor);
|
|
open.floorterrain = front->GetTerrain(sector_t::floor);
|
|
if (bf != -FLT_MIN) open.lowfloor = bf;
|
|
else if (!(flags & FFCF_NODROPOFF))
|
|
{
|
|
// We must check through the portal for the actual dropoff.
|
|
// If there's no lines in the lower sections we'd never get a usable value otherwise.
|
|
open.lowfloor = NextLowestFloorAt(back, pos.X, pos.Y, back->GetPortalPlaneZ(sector_t::floor) - EQUAL_EPSILON);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
open.bottom = bf;
|
|
open.bottomsec = back;
|
|
open.floorpic = back->GetTexture(sector_t::floor);
|
|
open.floorterrain = back->GetTerrain(sector_t::floor);
|
|
if (ff != -FLT_MIN) open.lowfloor = ff;
|
|
else if (!(flags & FFCF_NODROPOFF))
|
|
{
|
|
// We must check through the portal for the actual dropoff.
|
|
// If there's no lines in the lower sections we'd never get a usable value otherwise.
|
|
open.lowfloor = NextLowestFloorAt(front, pos.X, pos.Y, front->GetPortalPlaneZ(sector_t::floor) - EQUAL_EPSILON);
|
|
}
|
|
}
|
|
open.frontfloorplane = front->floorplane;
|
|
open.backfloorplane = back->floorplane;
|
|
}
|
|
else
|
|
{ // Dummy stuff to have some sort of opening for the 3D checks to modify
|
|
open.topsec = NULL;
|
|
open.ceilingpic.SetInvalid();
|
|
open.top = LINEOPEN_MAX;
|
|
open.bottomsec = NULL;
|
|
open.floorpic.SetInvalid();
|
|
open.floorterrain = -1;
|
|
open.bottom = LINEOPEN_MIN;
|
|
open.lowfloor = LINEOPEN_MAX;
|
|
open.frontfloorplane.SetAtHeight(LINEOPEN_MIN, sector_t::floor);
|
|
open.backfloorplane.SetAtHeight(LINEOPEN_MIN, sector_t::floor);
|
|
}
|
|
|
|
open.topffloor = open.bottomffloor = nullptr;
|
|
// Check 3D floors
|
|
if (actor != NULL)
|
|
{
|
|
P_LineOpening_XFloors(open, actor, linedef, pos.X, pos.Y, !!(flags & FFCF_3DRESTRICT));
|
|
}
|
|
|
|
if (actor != NULL && linedef->frontsector != NULL && linedef->backsector != NULL &&
|
|
linedef->flags & ML_3DMIDTEX)
|
|
{
|
|
open.touchmidtex = P_LineOpening_3dMidtex(actor, linedef, open, !!(flags & FFCF_3DRESTRICT));
|
|
}
|
|
else
|
|
{
|
|
open.abovemidtex = open.touchmidtex = false;
|
|
}
|
|
|
|
// avoid overflows in the opening.
|
|
open.range = clamp(open.top - open.bottom, LINEOPEN_MIN, LINEOPEN_MAX);
|
|
}
|
|
|
|
|
|
//
|
|
// 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 (FLinkContext *ctx)
|
|
{
|
|
if (ctx != nullptr) ctx->sector_list = nullptr;
|
|
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 != NULL) // prev will be NULL if this actor gets deleted due to cleaning up from a broken savegame
|
|
{
|
|
if ((*prev = next)) // unlink from sector list
|
|
next->sprev = prev;
|
|
snext = NULL;
|
|
sprev = (AActor **)(size_t)0xBeefCafe; // Woo! Bug-catching value!
|
|
|
|
// 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 (ctx != nullptr)
|
|
{
|
|
ctx->sector_list = touching_sectorlist;
|
|
ctx->render_list = touching_rendersectors;
|
|
}
|
|
else
|
|
{
|
|
P_DelSeclist(touching_sectorlist, §or_t::touching_thinglist);
|
|
P_DelSeclist(touching_rendersectors, §or_t::touching_renderthings);
|
|
}
|
|
touching_sectorlist = nullptr; //to be restored by P_SetThingPosition
|
|
touching_rendersectors = nullptr;
|
|
}
|
|
}
|
|
|
|
if (!(flags & MF_NOBLOCKMAP))
|
|
{
|
|
// [RH] Unlink from all blocks this actor uses
|
|
FBlockNode *block = this->BlockNode;
|
|
|
|
while (block != NULL)
|
|
{
|
|
if (block->NextActor != NULL)
|
|
{
|
|
block->NextActor->PrevActor = block->PrevActor;
|
|
}
|
|
*(block->PrevActor) = block->NextActor;
|
|
FBlockNode *next = block->NextBlock;
|
|
block->Release ();
|
|
block = next;
|
|
}
|
|
BlockNode = NULL;
|
|
}
|
|
ClearRenderSectorList();
|
|
ClearRenderLineList();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// If the thing is exactly on a line, move it into the sector
|
|
// slightly in order to resolve clipping issues in the renderer.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool AActor::FixMapthingPos()
|
|
{
|
|
sector_t *secstart = P_PointInSectorBuggy(X(), Y());
|
|
|
|
int blockx = level.blockmap.GetBlockX(X());
|
|
int blocky = level.blockmap.GetBlockY(Y());
|
|
bool success = false;
|
|
|
|
if (level.blockmap.isValidBlock(blockx, blocky))
|
|
{
|
|
int *list;
|
|
|
|
for (list = level.blockmap.GetLines(blockx, blocky); *list != -1; ++list)
|
|
{
|
|
line_t *ldef = &level.lines[*list];
|
|
|
|
if (ldef->frontsector == ldef->backsector)
|
|
{ // Skip two-sided lines inside a single sector
|
|
continue;
|
|
}
|
|
if (ldef->backsector != NULL)
|
|
{
|
|
if (ldef->frontsector->floorplane == ldef->backsector->floorplane &&
|
|
ldef->frontsector->ceilingplane == ldef->backsector->ceilingplane)
|
|
{ // Skip two-sided lines without any height difference on either side
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Not inside the line's bounding box
|
|
if (X() + radius <= ldef->bbox[BOXLEFT]
|
|
|| X() - radius >= ldef->bbox[BOXRIGHT]
|
|
|| Y() + radius <= ldef->bbox[BOXBOTTOM]
|
|
|| Y() - radius >= ldef->bbox[BOXTOP])
|
|
continue;
|
|
|
|
// Get the exact distance to the line
|
|
divline_t dll, dlv;
|
|
double linelen = ldef->Delta().Length();
|
|
|
|
P_MakeDivline(ldef, &dll);
|
|
|
|
dlv.x = X();
|
|
dlv.y = Y();
|
|
dlv.dx = dll.dy / linelen;
|
|
dlv.dy = -dll.dx / linelen;
|
|
|
|
double distance = fabs(P_InterceptVector(&dlv, &dll));
|
|
|
|
if (distance < radius)
|
|
{
|
|
DPrintf(DMSG_NOTIFY, "%s at (%f,%f) lies on %s line %d, distance = %f\n",
|
|
this->GetClass()->TypeName.GetChars(), X(), Y(),
|
|
ldef->Delta().X == 0 ? "vertical" : ldef->Delta().Y == 0 ? "horizontal" : "diagonal",
|
|
ldef->Index(), distance);
|
|
DAngle ang = ldef->Delta().Angle();
|
|
if (ldef->backsector != NULL && ldef->backsector == secstart)
|
|
{
|
|
ang += 90.;
|
|
}
|
|
else
|
|
{
|
|
ang -= 90.;
|
|
}
|
|
|
|
// Get the distance we have to move the object away from the wall
|
|
distance = radius - distance;
|
|
SetXY(Pos().XY() + ang.ToVector(distance));
|
|
ClearInterpolation();
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// P_SetThingPosition
|
|
// Links a thing into both a block and a subsector based on its x y.
|
|
// Sets thing->sector properly
|
|
//
|
|
//==========================================================================
|
|
|
|
void AActor::LinkToWorld(FLinkContext *ctx, bool spawningmapthing, sector_t *sector)
|
|
{
|
|
bool spawning = spawningmapthing;
|
|
|
|
if (spawning)
|
|
{
|
|
if ((flags4 & MF4_FIXMAPTHINGPOS) && sector == NULL)
|
|
{
|
|
if (FixMapthingPos()) spawning = false;
|
|
}
|
|
}
|
|
|
|
if (sector == NULL)
|
|
{
|
|
if (!spawning)
|
|
{
|
|
sector = P_PointInSector(Pos());
|
|
}
|
|
else
|
|
{
|
|
sector = P_PointInSectorBuggy(X(), Y());
|
|
}
|
|
}
|
|
|
|
Sector = sector;
|
|
subsector = R_PointInSubsector(Pos()); // this is from the rendering nodes, not the gameplay nodes!
|
|
section = subsector->section;
|
|
|
|
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 = §or->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.
|
|
touching_sectorlist = P_CreateSecNodeList(this, radius, ctx != nullptr? ctx->sector_list : nullptr, §or_t::touching_thinglist); // Attach to thing
|
|
if (renderradius >= 0) touching_rendersectors = P_CreateSecNodeList(this, MAX(radius, renderradius), ctx != nullptr ? ctx->render_list : nullptr, §or_t::touching_renderthings);
|
|
else
|
|
{
|
|
touching_rendersectors = nullptr;
|
|
if (ctx != nullptr) P_DelSeclist(ctx->render_list, §or_t::touching_renderthings);
|
|
}
|
|
}
|
|
|
|
|
|
// link into blockmap (inert things don't need to be in the blockmap)
|
|
if (!(flags & MF_NOBLOCKMAP))
|
|
{
|
|
FPortalGroupArray check;
|
|
|
|
P_CollectConnectedGroups(Sector->PortalGroup, Pos(), Top(), radius, check);
|
|
|
|
BlockNode = NULL;
|
|
FBlockNode **alink = &this->BlockNode;
|
|
for (int i = -1; i < (int)check.Size(); i++)
|
|
{
|
|
DVector3 pos = i==-1? Pos() : PosRelative(check[i] & ~FPortalGroupArray::FLAT);
|
|
|
|
int x1 = level.blockmap.GetBlockX(pos.X - radius);
|
|
int x2 = level.blockmap.GetBlockX(pos.X + radius);
|
|
int y1 = level.blockmap.GetBlockY(pos.Y - radius);
|
|
int y2 = level.blockmap.GetBlockY(pos.Y + radius);
|
|
|
|
if (x1 >= level.blockmap.bmapwidth || x2 < 0 || y1 >= level.blockmap.bmapheight || y2 < 0)
|
|
{ // thing is off the map
|
|
}
|
|
else
|
|
{ // [RH] Link into every block this actor touches, not just the center one
|
|
x1 = MAX(0, x1);
|
|
y1 = MAX(0, y1);
|
|
x2 = MIN(level.blockmap.bmapwidth - 1, x2);
|
|
y2 = MIN(level.blockmap.bmapheight - 1, y2);
|
|
for (int y = y1; y <= y2; ++y)
|
|
{
|
|
for (int x = x1; x <= x2; ++x)
|
|
{
|
|
FBlockNode **link = &level.blockmap.blocklinks[y*level.blockmap.bmapwidth + x];
|
|
FBlockNode *node = FBlockNode::Create(this, x, y, this->Sector->PortalGroup);
|
|
|
|
// Link in to block
|
|
if ((node->NextActor = *link) != NULL)
|
|
{
|
|
(*link)->PrevActor = &node->NextActor;
|
|
}
|
|
node->PrevActor = link;
|
|
*link = node;
|
|
|
|
// Link in to actor
|
|
node->PrevBlock = alink;
|
|
node->NextBlock = NULL;
|
|
(*alink) = node;
|
|
alink = &node->NextBlock;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Portal links cannot be done unless the level is fully initialized.
|
|
if (!spawningmapthing) UpdateRenderSectorList();
|
|
}
|
|
|
|
void AActor::SetOrigin(double x, double y, double z, bool moving)
|
|
{
|
|
FLinkContext ctx;
|
|
UnlinkFromWorld (&ctx);
|
|
SetXYZ(x, y, z);
|
|
LinkToWorld (&ctx);
|
|
P_FindFloorCeiling(this, FFCF_ONLYSPAWNPOS);
|
|
if (!moving) ClearInterpolation();
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// BLOCK MAP ITERATORS
|
|
// For each line/thing in the given mapblock,
|
|
// call the passed PIT_* function.
|
|
// If the function returns false,
|
|
// exit with false without checking anything else.
|
|
//
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// FBlockLinesIterator
|
|
//
|
|
//===========================================================================
|
|
|
|
FBlockLinesIterator::FBlockLinesIterator(int _minx, int _miny, int _maxx, int _maxy, bool keepvalidcount)
|
|
{
|
|
if (!keepvalidcount) validcount++;
|
|
minx = _minx;
|
|
maxx = _maxx;
|
|
miny = _miny;
|
|
maxy = _maxy;
|
|
Reset();
|
|
}
|
|
|
|
void FBlockLinesIterator::init(const FBoundingBox &box)
|
|
{
|
|
validcount++;
|
|
maxy = level.blockmap.GetBlockY(box.Top());
|
|
miny = level.blockmap.GetBlockY(box.Bottom());
|
|
maxx = level.blockmap.GetBlockX(box.Right());
|
|
minx = level.blockmap.GetBlockX(box.Left());
|
|
Reset();
|
|
}
|
|
|
|
FBlockLinesIterator::FBlockLinesIterator(const FBoundingBox &box)
|
|
{
|
|
init(box);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// FBlockLinesIterator :: StartBlock
|
|
//
|
|
//===========================================================================
|
|
|
|
void FBlockLinesIterator::StartBlock(int x, int y)
|
|
{
|
|
curx = x;
|
|
cury = y;
|
|
if (level.blockmap.isValidBlock(x, y))
|
|
{
|
|
unsigned offset = y*level.blockmap.bmapwidth + x;
|
|
polyLink = level.PolyBlockMap.Size() > offset? level.PolyBlockMap[offset] : nullptr;
|
|
polyIndex = 0;
|
|
|
|
list = level.blockmap.GetLines(x, y);
|
|
}
|
|
else
|
|
{
|
|
// invalid block
|
|
list = NULL;
|
|
polyLink = NULL;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// FBlockLinesIterator :: Next
|
|
//
|
|
//===========================================================================
|
|
|
|
line_t *FBlockLinesIterator::Next()
|
|
{
|
|
while (true)
|
|
{
|
|
while (polyLink != NULL)
|
|
{
|
|
if (polyLink->polyobj)
|
|
{
|
|
if (polyIndex == 0)
|
|
{
|
|
if (polyLink->polyobj->validcount == validcount)
|
|
{
|
|
polyLink = polyLink->next;
|
|
continue;
|
|
}
|
|
polyLink->polyobj->validcount = validcount;
|
|
}
|
|
|
|
line_t *ld = polyLink->polyobj->Linedefs[polyIndex];
|
|
|
|
if (++polyIndex >= (int)polyLink->polyobj->Linedefs.Size())
|
|
{
|
|
polyLink = polyLink->next;
|
|
polyIndex = 0;
|
|
}
|
|
|
|
if (ld->validcount == validcount)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
ld->validcount = validcount;
|
|
return ld;
|
|
}
|
|
}
|
|
else polyLink = polyLink->next;
|
|
}
|
|
|
|
if (list != NULL)
|
|
{
|
|
while (*list != -1)
|
|
{
|
|
line_t *ld = &level.lines[*list];
|
|
|
|
list++;
|
|
if (ld->validcount != validcount)
|
|
{
|
|
ld->validcount = validcount;
|
|
return ld;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (++curx > maxx)
|
|
{
|
|
curx = minx;
|
|
if (++cury > maxy) return NULL;
|
|
}
|
|
StartBlock(curx, cury);
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// FMultiBlockLinesIterator :: FMultiBlockLinesIterator
|
|
//
|
|
// An iterator that can check multiple portal groups.
|
|
//
|
|
//===========================================================================
|
|
|
|
FMultiBlockLinesIterator::FMultiBlockLinesIterator(FPortalGroupArray &check, AActor *origin, double checkradius)
|
|
: checklist(check)
|
|
{
|
|
checkpoint = origin->Pos();
|
|
if (!check.inited) P_CollectConnectedGroups(origin->Sector->PortalGroup, checkpoint, origin->Top(), checkradius, checklist);
|
|
checkpoint.Z = checkradius == -1? origin->radius : checkradius;
|
|
basegroup = origin->Sector->PortalGroup;
|
|
startsector = origin->Sector;
|
|
Reset();
|
|
}
|
|
|
|
FMultiBlockLinesIterator::FMultiBlockLinesIterator(FPortalGroupArray &check, double checkx, double checky, double checkz, double checkh, double checkradius, sector_t *newsec)
|
|
: checklist(check)
|
|
{
|
|
checkpoint = { checkx, checky, checkz };
|
|
if (newsec == NULL) newsec = P_PointInSector(checkx, checky);
|
|
startsector = newsec;
|
|
basegroup = newsec->PortalGroup;
|
|
if (!check.inited) P_CollectConnectedGroups(basegroup, checkpoint, checkz + checkh, checkradius, checklist);
|
|
checkpoint.Z = checkradius;
|
|
Reset();
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Go up a ceiling portal
|
|
//
|
|
//===========================================================================
|
|
|
|
bool FMultiBlockLinesIterator::GoUp(double x, double y)
|
|
{
|
|
if (continueup)
|
|
{
|
|
if (!cursector->PortalBlocksMovement(sector_t::ceiling))
|
|
{
|
|
if (startIteratorForGroup(cursector->GetOppositePortalGroup(sector_t::ceiling)))
|
|
{
|
|
portalflags = FFCF_NOFLOOR;
|
|
return true;
|
|
}
|
|
}
|
|
continueup = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Go down a floor portal
|
|
//
|
|
//===========================================================================
|
|
|
|
bool FMultiBlockLinesIterator::GoDown(double x, double y)
|
|
{
|
|
if (continuedown)
|
|
{
|
|
if (!cursector->PortalBlocksMovement(sector_t::floor))
|
|
{
|
|
if (startIteratorForGroup(cursector->GetOppositePortalGroup(sector_t::floor)))
|
|
{
|
|
portalflags = FFCF_NOCEILING;
|
|
return true;
|
|
}
|
|
}
|
|
continuedown = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Gets the next line - also manages switching between portal groups
|
|
//
|
|
//===========================================================================
|
|
|
|
bool FMultiBlockLinesIterator::Next(FMultiBlockLinesIterator::CheckResult *item)
|
|
{
|
|
line_t *line = blockIterator.Next();
|
|
if (line != NULL)
|
|
{
|
|
item->line = line;
|
|
item->Position.X = offset.X;
|
|
item->Position.Y = offset.Y;
|
|
item->portalflags = portalflags;
|
|
return true;
|
|
}
|
|
bool onlast = unsigned(index + 1) >= checklist.Size();
|
|
int nextflags = onlast ? 0 : checklist[index + 1] & FPortalGroupArray::FLAT;
|
|
|
|
if (portalflags == FFCF_NOFLOOR && nextflags != FPortalGroupArray::UPPER)
|
|
{
|
|
// if this is the last upper portal in the list, check if we need to go further up to find the real ceiling.
|
|
if (GoUp(offset.X, offset.Y)) return Next(item);
|
|
}
|
|
else if (portalflags == FFCF_NOCEILING && nextflags != FPortalGroupArray::LOWER)
|
|
{
|
|
// if this is the last lower portal in the list, check if we need to go further down to find the real floor.
|
|
if (GoDown(offset.X, offset.Y)) return Next(item);
|
|
}
|
|
if (onlast)
|
|
{
|
|
cursector = startsector;
|
|
// We reached the end of the list. Check if we still need to check up- and downwards.
|
|
if (GoUp(checkpoint.X, checkpoint.Y) ||
|
|
GoDown(checkpoint.X, checkpoint.Y))
|
|
{
|
|
return Next(item);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
index++;
|
|
startIteratorForGroup(checklist[index] & ~FPortalGroupArray::FLAT);
|
|
switch (nextflags)
|
|
{
|
|
case FPortalGroupArray::UPPER:
|
|
portalflags = FFCF_NOFLOOR;
|
|
break;
|
|
|
|
case FPortalGroupArray::LOWER:
|
|
portalflags = FFCF_NOCEILING;
|
|
break;
|
|
|
|
default:
|
|
portalflags = 0;
|
|
}
|
|
|
|
return Next(item);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// start iterating a new group
|
|
//
|
|
//===========================================================================
|
|
|
|
bool FMultiBlockLinesIterator::startIteratorForGroup(int group)
|
|
{
|
|
offset = level.Displacements.getOffset(basegroup, group);
|
|
offset.X += checkpoint.X;
|
|
offset.Y += checkpoint.Y;
|
|
cursector = group == startsector->PortalGroup ? startsector : P_PointInSector(offset);
|
|
// If we ended up in a different group,
|
|
// presumably because the spot to be checked is too far outside the actual portal group,
|
|
// the search needs to abort.
|
|
if (cursector->PortalGroup != group) return false;
|
|
bbox.setBox(offset.X, offset.Y, checkpoint.Z);
|
|
blockIterator.init(bbox);
|
|
return true;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Resets the iterator
|
|
//
|
|
//===========================================================================
|
|
|
|
void FMultiBlockLinesIterator::Reset()
|
|
{
|
|
continueup = continuedown = true;
|
|
index = -1;
|
|
portalflags = 0;
|
|
startIteratorForGroup(basegroup);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// FBlockThingsIterator :: FBlockThingsIterator
|
|
//
|
|
//===========================================================================
|
|
|
|
FBlockThingsIterator::FBlockThingsIterator()
|
|
: DynHash(0)
|
|
{
|
|
minx = maxx = 0;
|
|
miny = maxy = 0;
|
|
ClearHash();
|
|
block = NULL;
|
|
}
|
|
|
|
FBlockThingsIterator::FBlockThingsIterator(int _minx, int _miny, int _maxx, int _maxy)
|
|
: DynHash(0)
|
|
{
|
|
minx = _minx;
|
|
maxx = _maxx;
|
|
miny = _miny;
|
|
maxy = _maxy;
|
|
ClearHash();
|
|
Reset();
|
|
}
|
|
|
|
void FBlockThingsIterator::init(const FBoundingBox &box)
|
|
{
|
|
maxy = level.blockmap.GetBlockY(box.Top());
|
|
miny = level.blockmap.GetBlockY(box.Bottom());
|
|
maxx = level.blockmap.GetBlockX(box.Right());
|
|
minx = level.blockmap.GetBlockX(box.Left());
|
|
ClearHash();
|
|
Reset();
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// FBlockThingsIterator :: ClearHash
|
|
//
|
|
//===========================================================================
|
|
|
|
void FBlockThingsIterator::ClearHash()
|
|
{
|
|
memset(Buckets, -1, sizeof(Buckets));
|
|
NumFixedHash = 0;
|
|
DynHash.Clear();
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// FBlockThingsIterator :: StartBlock
|
|
//
|
|
//===========================================================================
|
|
|
|
void FBlockThingsIterator::StartBlock(int x, int y)
|
|
{
|
|
curx = x;
|
|
cury = y;
|
|
if (level.blockmap.isValidBlock(x, y))
|
|
{
|
|
block = level.blockmap.blocklinks[y*level.blockmap.bmapwidth + x];
|
|
}
|
|
else
|
|
{
|
|
// invalid block
|
|
block = NULL;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// FBlockThingsIterator :: SwitchBlock
|
|
//
|
|
//===========================================================================
|
|
|
|
void FBlockThingsIterator::SwitchBlock(int x, int y)
|
|
{
|
|
minx = maxx = x;
|
|
miny = maxy = y;
|
|
StartBlock(x, y);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// FBlockThingsIterator :: Next
|
|
//
|
|
//===========================================================================
|
|
|
|
AActor *FBlockThingsIterator::Next(bool centeronly)
|
|
{
|
|
for (;;)
|
|
{
|
|
while (block != NULL)
|
|
{
|
|
AActor *me = block->Me;
|
|
FBlockNode *mynode = block;
|
|
HashEntry *entry;
|
|
int i;
|
|
|
|
block = block->NextActor;
|
|
// Don't recheck things that were already checked
|
|
if (mynode->NextBlock == NULL && mynode->PrevBlock == &me->BlockNode)
|
|
{ // This actor doesn't span blocks, so we know it can only ever be checked once.
|
|
return me;
|
|
}
|
|
if (centeronly)
|
|
{
|
|
// Block boundaries for compatibility mode
|
|
double blockleft = (curx * FBlockmap::MAPBLOCKUNITS) + level.blockmap.bmaporgx;
|
|
double blockright = blockleft + FBlockmap::MAPBLOCKUNITS;
|
|
double blockbottom = (cury * FBlockmap::MAPBLOCKUNITS) + level.blockmap.bmaporgy;
|
|
double blocktop = blockbottom + FBlockmap::MAPBLOCKUNITS;
|
|
|
|
// only return actors with the center in this block
|
|
if (me->X() >= blockleft && me->X() < blockright &&
|
|
me->Y() >= blockbottom && me->Y() < blocktop)
|
|
{
|
|
return me;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
size_t hash = ((size_t)me >> 3) % countof(Buckets);
|
|
for (i = Buckets[hash]; i >= 0; )
|
|
{
|
|
entry = GetHashEntry(i);
|
|
if (entry->Actor == me)
|
|
{ // I've already been checked. Skip to the next actor.
|
|
break;
|
|
}
|
|
i = entry->Next;
|
|
}
|
|
if (i < 0)
|
|
{ // Add me to the hash table and return me.
|
|
if (NumFixedHash < (int)countof(FixedHash))
|
|
{
|
|
entry = &FixedHash[NumFixedHash];
|
|
entry->Next = Buckets[hash];
|
|
Buckets[hash] = NumFixedHash++;
|
|
}
|
|
else
|
|
{
|
|
if (DynHash.Size() == 0)
|
|
{
|
|
DynHash.Grow(50);
|
|
}
|
|
i = DynHash.Reserve(1);
|
|
entry = &DynHash[i];
|
|
entry->Next = Buckets[hash];
|
|
Buckets[hash] = i + countof(FixedHash);
|
|
}
|
|
entry->Actor = me;
|
|
return me;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (++curx > maxx)
|
|
{
|
|
curx = minx;
|
|
if (++cury > maxy) return NULL;
|
|
}
|
|
StartBlock(curx, cury);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// FMultiBlockThingsIterator :: FMultiBlockThingsIterator
|
|
//
|
|
// An iterator that can check multiple portal groups.
|
|
//
|
|
//===========================================================================
|
|
|
|
FMultiBlockThingsIterator::FMultiBlockThingsIterator(FPortalGroupArray &check, AActor *origin, double checkradius, bool ignorerestricted)
|
|
: checklist(check)
|
|
{
|
|
checkpoint = origin->Pos();
|
|
if (!check.inited) P_CollectConnectedGroups(origin->Sector->PortalGroup, checkpoint, origin->Top(), checkradius, checklist);
|
|
checkpoint.Z = checkradius == -1? origin->radius : checkradius;
|
|
basegroup = origin->Sector->PortalGroup;
|
|
Reset();
|
|
}
|
|
|
|
FMultiBlockThingsIterator::FMultiBlockThingsIterator(FPortalGroupArray &check, double checkx, double checky, double checkz, double checkh, double checkradius, bool ignorerestricted, sector_t *newsec)
|
|
: checklist(check)
|
|
{
|
|
checkpoint.X = checkx;
|
|
checkpoint.Y = checky;
|
|
checkpoint.Z = checkz;
|
|
if (newsec == NULL) newsec = P_PointInSector(checkx, checky);
|
|
basegroup = newsec->PortalGroup;
|
|
if (!check.inited) P_CollectConnectedGroups(basegroup, checkpoint, checkz + checkh, checkradius, checklist);
|
|
checkpoint.Z = checkradius;
|
|
Reset();
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Gets the next line - also manages switching between portal groups
|
|
//
|
|
//===========================================================================
|
|
|
|
bool FMultiBlockThingsIterator::Next(FMultiBlockThingsIterator::CheckResult *item)
|
|
{
|
|
AActor *thing = blockIterator.Next();
|
|
if (thing != NULL)
|
|
{
|
|
item->thing = thing;
|
|
item->Position = checkpoint + level.Displacements.getOffset(basegroup, thing->Sector->PortalGroup);
|
|
item->portalflags = portalflags;
|
|
return true;
|
|
}
|
|
bool onlast = unsigned(index + 1) >= checklist.Size();
|
|
int nextflags = onlast ? 0 : checklist[index + 1] & FPortalGroupArray::FLAT;
|
|
|
|
if (onlast)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
index++;
|
|
startIteratorForGroup(checklist[index] & ~FPortalGroupArray::FLAT);
|
|
switch (nextflags)
|
|
{
|
|
case FPortalGroupArray::UPPER:
|
|
portalflags = FFCF_NOFLOOR;
|
|
break;
|
|
|
|
case FPortalGroupArray::LOWER:
|
|
portalflags = FFCF_NOCEILING;
|
|
break;
|
|
|
|
default:
|
|
portalflags = 0;
|
|
}
|
|
|
|
return Next(item);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// start iterating a new group
|
|
//
|
|
//===========================================================================
|
|
|
|
void FMultiBlockThingsIterator::startIteratorForGroup(int group)
|
|
{
|
|
DVector2 offset = level.Displacements.getOffset(basegroup, group);
|
|
offset.X += checkpoint.X;
|
|
offset.Y += checkpoint.Y;
|
|
bbox.setBox(offset.X, offset.Y, checkpoint.Z);
|
|
blockIterator.init(bbox);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Resets the iterator
|
|
//
|
|
//===========================================================================
|
|
|
|
void FMultiBlockThingsIterator::Reset()
|
|
{
|
|
index = -1;
|
|
portalflags = 0;
|
|
startIteratorForGroup(basegroup);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// FPathTraverse :: Intercepts
|
|
//
|
|
//===========================================================================
|
|
|
|
TArray<intercept_t> FPathTraverse::intercepts(128);
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// FPathTraverse :: 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.
|
|
//
|
|
//===========================================================================
|
|
|
|
void FPathTraverse::AddLineIntercepts(int bx, int by)
|
|
{
|
|
FBlockLinesIterator it(bx, by, bx, by, true);
|
|
line_t *ld;
|
|
|
|
while ((ld = it.Next()))
|
|
{
|
|
int s1;
|
|
int s2;
|
|
double frac;
|
|
divline_t dl;
|
|
|
|
s1 = P_PointOnDivlineSide (ld->v1->fX(), ld->v1->fY(), &trace);
|
|
s2 = P_PointOnDivlineSide (ld->v2->fX(), ld->v2->fY(), &trace);
|
|
|
|
if (s1 == s2) continue; // line isn't crossed
|
|
|
|
// hit the line
|
|
P_MakeDivline (ld, &dl);
|
|
frac = P_InterceptVector (&trace, &dl);
|
|
|
|
if (frac < Startfrac || frac > 1.) continue; // behind source or beyond end point
|
|
|
|
intercept_t newintercept;
|
|
|
|
newintercept.frac = frac;
|
|
newintercept.isaline = true;
|
|
newintercept.done = false;
|
|
newintercept.d.line = ld;
|
|
intercepts.Push (newintercept);
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// FPathTraverse :: AddThingIntercepts
|
|
//
|
|
//===========================================================================
|
|
|
|
void FPathTraverse::AddThingIntercepts (int bx, int by, FBlockThingsIterator &it, bool compatible)
|
|
{
|
|
AActor *thing;
|
|
|
|
it.SwitchBlock(bx, by);
|
|
while ((thing = it.Next(compatible)))
|
|
{
|
|
int numfronts = 0;
|
|
divline_t line;
|
|
int i;
|
|
|
|
|
|
if (!compatible)
|
|
{
|
|
// [RH] Don't check a corner to corner crossection for hit.
|
|
// Instead, check against the actual bounding box (but not if compatibility optioned.)
|
|
|
|
// There's probably a smarter way to determine which two sides
|
|
// of the thing face the trace than by trying all four sides...
|
|
for (i = 0; i < 4; ++i)
|
|
{
|
|
switch (i)
|
|
{
|
|
case 0: // Top edge
|
|
line.y = thing->Y() + thing->radius;
|
|
if (trace.y < line.y) continue;
|
|
line.x = thing->X() + thing->radius;
|
|
line.dx = -thing->radius * 2;
|
|
line.dy = 0;
|
|
break;
|
|
|
|
case 1: // Right edge
|
|
line.x = thing->X() + thing->radius;
|
|
if (trace.x < line.x) continue;
|
|
line.y = thing->Y() - thing->radius;
|
|
line.dx = 0;
|
|
line.dy = thing->radius * 2;
|
|
break;
|
|
|
|
case 2: // Bottom edge
|
|
line.y = thing->Y() - thing->radius;
|
|
if (trace.y > line.y) continue;
|
|
line.x = thing->X() - thing->radius;
|
|
line.dx = thing->radius * 2;
|
|
line.dy = 0;
|
|
break;
|
|
|
|
case 3: // Left edge
|
|
line.x = thing->X() - thing->radius;
|
|
if (trace.x > line.x) continue;
|
|
line.y = thing->Y() + thing->radius;
|
|
line.dx = 0;
|
|
line.dy = thing->radius * -2;
|
|
break;
|
|
}
|
|
// Check if this side is facing the trace origin
|
|
numfronts++;
|
|
|
|
// If it is, see if the trace crosses it
|
|
if (P_PointOnDivlineSide (line.x, line.y, &trace) !=
|
|
P_PointOnDivlineSide (line.x + line.dx, line.y + line.dy, &trace))
|
|
{
|
|
// It's a hit
|
|
double frac = P_InterceptVector (&trace, &line);
|
|
if (frac < Startfrac)
|
|
{ // behind source
|
|
if (Startfrac > 0)
|
|
{
|
|
// check if the trace starts within this actor
|
|
switch (i)
|
|
{
|
|
case 0:
|
|
line.y -= 2 * thing->radius;
|
|
break;
|
|
|
|
case 1:
|
|
line.x -= 2 * thing->radius;
|
|
break;
|
|
|
|
case 2:
|
|
line.y += 2 * thing->radius;
|
|
break;
|
|
|
|
case 3:
|
|
line.x += 2 * thing->radius;
|
|
break;
|
|
}
|
|
double frac2 = P_InterceptVector(&trace, &line);
|
|
if (frac2 >= Startfrac) goto addit;
|
|
}
|
|
continue;
|
|
}
|
|
addit:
|
|
intercept_t newintercept;
|
|
newintercept.frac = frac;
|
|
newintercept.isaline = false;
|
|
newintercept.done = false;
|
|
newintercept.d.thing = thing;
|
|
intercepts.Push (newintercept);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If none of the sides was facing the trace, then the trace
|
|
// must have started inside the box, so add it as an intercept.
|
|
if (numfronts == 0)
|
|
{
|
|
intercept_t newintercept;
|
|
newintercept.frac = 0;
|
|
newintercept.isaline = false;
|
|
newintercept.done = false;
|
|
newintercept.d.thing = thing;
|
|
intercepts.Push (newintercept);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Old code for compatibility purposes
|
|
double x1, y1, x2, y2;
|
|
int s1, s2;
|
|
divline_t dl;
|
|
double frac;
|
|
|
|
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;
|
|
}
|
|
|
|
s1 = P_PointOnDivlineSide (x1, y1, &trace);
|
|
s2 = P_PointOnDivlineSide (x2, y2, &trace);
|
|
|
|
if (s1 != s2)
|
|
{
|
|
dl.x = x1;
|
|
dl.y = y1;
|
|
dl.dx = x2-x1;
|
|
dl.dy = y2-y1;
|
|
|
|
frac = P_InterceptVector (&trace, &dl);
|
|
|
|
if (frac >= Startfrac)
|
|
{
|
|
intercept_t newintercept;
|
|
newintercept.frac = frac;
|
|
newintercept.isaline = false;
|
|
newintercept.done = false;
|
|
newintercept.d.thing = thing;
|
|
intercepts.Push (newintercept);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// FPathTraverse :: Next
|
|
//
|
|
//===========================================================================
|
|
|
|
intercept_t *FPathTraverse::Next()
|
|
{
|
|
intercept_t *in = NULL;
|
|
|
|
double dist = FLT_MAX;
|
|
for (unsigned scanpos = intercept_index; scanpos < intercepts.Size (); scanpos++)
|
|
{
|
|
intercept_t *scan = &intercepts[scanpos];
|
|
if (scan->frac < dist && !scan->done)
|
|
{
|
|
dist = scan->frac;
|
|
in = scan;
|
|
}
|
|
}
|
|
|
|
if (dist > 1. || in == NULL) return NULL; // checked everything in range
|
|
in->done = true;
|
|
return in;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// FPathTraverse
|
|
// Traces a line from x1,y1 to x2,y2,
|
|
//
|
|
//===========================================================================
|
|
|
|
void FPathTraverse::init(double x1, double y1, double x2, double y2, int flags, double startfrac)
|
|
{
|
|
double xt1, yt1, xt2, yt2;
|
|
double xstep, ystep;
|
|
double partialx, partialy;
|
|
double xintercept, yintercept;
|
|
|
|
int mapx;
|
|
int mapy;
|
|
|
|
int mapxstep;
|
|
int mapystep;
|
|
|
|
int count;
|
|
|
|
trace.x = x1;
|
|
trace.y = y1;
|
|
if (flags & PT_DELTA)
|
|
{
|
|
trace.dx = x2;
|
|
trace.dy = y2;
|
|
}
|
|
else
|
|
{
|
|
trace.dx = x2 - x1;
|
|
trace.dy = y2 - y1;
|
|
}
|
|
if (startfrac > 0)
|
|
{
|
|
double startdx = trace.dx * startfrac;
|
|
double startdy = trace.dy * startfrac;
|
|
|
|
x1 += startdx;
|
|
y1 += startdy;
|
|
x2 = trace.dx - startdx;
|
|
y2 = trace.dy - startdy;
|
|
flags |= PT_DELTA;
|
|
}
|
|
|
|
validcount++;
|
|
intercept_index = intercepts.Size();
|
|
Startfrac = startfrac;
|
|
|
|
if (flags & PT_DELTA)
|
|
{
|
|
x2 += x1;
|
|
y2 += y1;
|
|
}
|
|
|
|
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 statement.
|
|
|
|
bool compatible = (flags & PT_COMPATIBLE) && (i_compatflags & COMPATF_HITSCAN);
|
|
|
|
// we want to use one list of checked actors for the entire operation
|
|
FBlockThingsIterator btit;
|
|
for (count = 0 ; count < 1000 ; count++)
|
|
{
|
|
if (flags & PT_ADDLINES)
|
|
{
|
|
AddLineIntercepts(mapx, mapy);
|
|
}
|
|
|
|
if (flags & PT_ADDTHINGS)
|
|
{
|
|
AddThingIntercepts(mapx, mapy, btit, compatible);
|
|
}
|
|
|
|
// both coordinates reached the end, so end the traversing.
|
|
if ((mapxstep | mapystep) == 0)
|
|
break;
|
|
|
|
|
|
// [RH] Handle corner cases properly instead of pretending they don't exist.
|
|
switch (((xs_FloorToInt(yintercept) == mapy) << 1) | (xs_FloorToInt(xintercept) == mapx))
|
|
{
|
|
case 0: // neither xintercept nor yintercept match!
|
|
count = 1000; // Stop traversing, because somebody screwed up.
|
|
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
|
|
// 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.
|
|
// Since Doom.exe did not do this, this code won't either if run in compatibility mode.
|
|
if (!compatible)
|
|
{
|
|
if (flags & PT_ADDLINES)
|
|
{
|
|
AddLineIntercepts(mapx + mapxstep, mapy);
|
|
AddLineIntercepts(mapx, mapy + mapystep);
|
|
}
|
|
|
|
if (flags & PT_ADDTHINGS)
|
|
{
|
|
AddThingIntercepts(mapx + mapxstep, mapy, btit, false);
|
|
AddThingIntercepts(mapx, mapy + mapystep, btit, false);
|
|
}
|
|
xintercept += xstep;
|
|
yintercept += ystep;
|
|
mapx += mapxstep;
|
|
mapy += mapystep;
|
|
if (mapx == mapex)
|
|
mapxstep = 0;
|
|
if (mapy == mapey)
|
|
mapystep = 0;
|
|
}
|
|
else
|
|
{
|
|
count = 1000; // Doom originally did not handle this case so do the same in compatibility mode.
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Relocates the trace when going through a line portal
|
|
//
|
|
//===========================================================================
|
|
|
|
int FPathTraverse::PortalRelocate(intercept_t *in, int flags, DVector3 *optpos)
|
|
{
|
|
if (!in->isaline || !in->d.line->isLinePortal()) return false;
|
|
if (P_PointOnLineSidePrecise(trace.x, trace.y, in->d.line) == 1) return false;
|
|
|
|
double hitx = trace.x;
|
|
double hity = trace.y;
|
|
double endx = trace.x + trace.dx;
|
|
double endy = trace.y + trace.dy;
|
|
|
|
P_TranslatePortalXY(in->d.line, hitx, hity);
|
|
P_TranslatePortalXY(in->d.line, endx, endy);
|
|
if (optpos != NULL)
|
|
{
|
|
P_TranslatePortalXY(in->d.line, optpos->X, optpos->Y);
|
|
P_TranslatePortalZ(in->d.line, optpos->Z);
|
|
}
|
|
line_t *saved = in->d.line; // this gets overwritten by the init call.
|
|
intercepts.Resize(intercept_index);
|
|
init(hitx, hity, endx, endy, flags, in->frac + EQUAL_EPSILON);
|
|
return saved->getPortal()->mType == PORTT_LINKED? 1:-1;
|
|
}
|
|
|
|
void FPathTraverse::PortalRelocate(const DVector2 &displacement, int flags, double hitfrac)
|
|
{
|
|
double hitx = trace.x + displacement.X;
|
|
double hity = trace.y + displacement.Y;
|
|
double endx = hitx + trace.dx;
|
|
double endy = hity + trace.dy;
|
|
intercepts.Resize(intercept_index);
|
|
init(hitx, hity, endx, endy, flags, hitfrac);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
FPathTraverse::~FPathTraverse()
|
|
{
|
|
intercepts.Resize(intercept_index);
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_RoughMonsterSearch
|
|
//
|
|
// Searches though the surrounding mapblocks for monsters/players
|
|
// distance is in FBlockmap::MAPBLOCKUNITS
|
|
//===========================================================================
|
|
|
|
AActor *P_BlockmapSearch (AActor *mo, int distance, AActor *(*check)(AActor*, int, void *), void *params)
|
|
{
|
|
int blockX;
|
|
int blockY;
|
|
int startX, startY;
|
|
int blockIndex;
|
|
int firstStop;
|
|
int secondStop;
|
|
int thirdStop;
|
|
int finalStop;
|
|
int count;
|
|
AActor *target;
|
|
int bmapwidth = level.blockmap.bmapwidth;
|
|
int bmapheight = level.blockmap.bmapheight;
|
|
|
|
startX = level.blockmap.GetBlockX(mo->X());
|
|
startY = level.blockmap.GetBlockY(mo->Y());
|
|
validcount++;
|
|
|
|
if (level.blockmap.isValidBlock(startX, startY))
|
|
{
|
|
if ( (target = check (mo, startY*bmapwidth+startX, params)) )
|
|
{ // found a target right away
|
|
return target;
|
|
}
|
|
}
|
|
for (count = 1; count <= distance; count++)
|
|
{
|
|
blockX = clamp (startX-count, 0, bmapwidth-1);
|
|
blockY = clamp (startY-count, 0, bmapheight-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 = check (mo, blockIndex, params)) )
|
|
{
|
|
return target;
|
|
}
|
|
}
|
|
// Trace the second block section (right edge)
|
|
for (blockIndex--; blockIndex <= secondStop; blockIndex += bmapwidth)
|
|
{
|
|
if ( (target = check (mo, blockIndex, params)) )
|
|
{
|
|
return target;
|
|
}
|
|
}
|
|
// Trace the third block section (bottom edge)
|
|
for (blockIndex -= bmapwidth; blockIndex >= thirdStop; blockIndex--)
|
|
{
|
|
if ( (target = check (mo, blockIndex, params)) )
|
|
{
|
|
return target;
|
|
}
|
|
}
|
|
// Trace the final block section (left edge)
|
|
for (blockIndex++; blockIndex > finalStop; blockIndex -= bmapwidth)
|
|
{
|
|
if ( (target = check (mo, blockIndex, params)) )
|
|
{
|
|
return target;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct BlockCheckInfo
|
|
{
|
|
bool onlyseekable;
|
|
bool frontonly;
|
|
divline_t frontline;
|
|
};
|
|
|
|
//===========================================================================
|
|
//
|
|
// RoughBlockCheck
|
|
//
|
|
//===========================================================================
|
|
|
|
static AActor *RoughBlockCheck (AActor *mo, int index, void *param)
|
|
{
|
|
BlockCheckInfo *info = (BlockCheckInfo *)param;
|
|
|
|
FBlockNode *link;
|
|
|
|
for (link = level.blockmap.blocklinks[index]; link != NULL; link = link->NextActor)
|
|
{
|
|
if (link->Me != mo)
|
|
{
|
|
if (info->onlyseekable && !mo->CanSeek(link->Me))
|
|
{
|
|
continue;
|
|
}
|
|
if (info->frontonly && P_PointOnDivlineSide(link->Me->X(), link->Me->Y(), &info->frontline) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
if (mo->IsOkayToAttack (link->Me))
|
|
{
|
|
return link->Me;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
AActor *P_RoughMonsterSearch(AActor *mo, int distance, bool onlyseekable, bool frontonly)
|
|
{
|
|
BlockCheckInfo info;
|
|
info.onlyseekable = onlyseekable;
|
|
if ((info.frontonly = frontonly))
|
|
{
|
|
info.frontline.x = mo->X();
|
|
info.frontline.y = mo->Y();
|
|
info.frontline.dx = -mo->Angles.Yaw.Sin();
|
|
info.frontline.dy = -mo->Angles.Yaw.Cos();
|
|
}
|
|
|
|
return P_BlockmapSearch(mo, distance, RoughBlockCheck, (void *)&info);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// [RH] LinkToWorldForMapThing
|
|
//
|
|
// Emulate buggy PointOnLineSide and fix actors that lie on
|
|
// lines to compensate for some IWAD maps.
|
|
//
|
|
//==========================================================================
|
|
|
|
static int R_PointOnSideSlow(double xx, double yy, node_t *node)
|
|
{
|
|
// [RH] This might have been faster than two multiplies and an
|
|
// add on a 386/486, but it certainly isn't on anything newer than that.
|
|
auto x = FloatToFixed(xx);
|
|
auto y = FloatToFixed(yy);
|
|
double left;
|
|
double right;
|
|
|
|
if (!node->dx)
|
|
{
|
|
if (x <= node->x)
|
|
return node->dy > 0;
|
|
|
|
return node->dy < 0;
|
|
}
|
|
if (!node->dy)
|
|
{
|
|
if (y <= node->y)
|
|
return node->dx < 0;
|
|
|
|
return node->dx > 0;
|
|
}
|
|
|
|
auto dx = (x - node->x);
|
|
auto dy = (y - node->y);
|
|
|
|
// Try to quickly decide by looking at sign bits.
|
|
if ((node->dy ^ node->dx ^ dx ^ dy) & 0x80000000)
|
|
{
|
|
if ((node->dy ^ dx) & 0x80000000)
|
|
{
|
|
// (left is negative)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// we must use doubles here because the fixed point code will produce errors due to loss of precision for extremely short linedefs.
|
|
// Note that this function is used for all map spawned actors and not just a compatibility fallback!
|
|
left = (double)node->dy * (double)dx;
|
|
right = (double)dy * (double)node->dx;
|
|
|
|
if (right < left)
|
|
{
|
|
// front side
|
|
return 0;
|
|
}
|
|
// back side
|
|
return 1;
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_VanillaPointOnLineSide
|
|
// P_PointOnLineSide() from the initial Doom source code release
|
|
//
|
|
//===========================================================================
|
|
|
|
int P_VanillaPointOnLineSide(double x, double y, const line_t* line)
|
|
{
|
|
DVector2 delta = line->Delta();
|
|
|
|
if (delta.X == 0)
|
|
{
|
|
if (x <= line->v1->fX())
|
|
return delta.Y > 0;
|
|
|
|
return delta.Y < 0;
|
|
}
|
|
if (delta.Y == 0)
|
|
{
|
|
if (y <= line->v1->fY())
|
|
return delta.X < 0;
|
|
|
|
return delta.X > 0;
|
|
}
|
|
|
|
// Note: This cannot really be converted to floating point
|
|
// without breaking the intended use of this function
|
|
// (i.e. to emulate the horrible imprecision of the entire method)
|
|
|
|
auto dx = FloatToFixed(x - line->v1->fX());
|
|
auto dy = FloatToFixed(y - line->v1->fY());
|
|
|
|
auto left = FixedMul( int(delta.Y * 256) , dx );
|
|
auto right = FixedMul( dy , int(delta.X * 256) );
|
|
|
|
if (right < left)
|
|
return 0; // front side
|
|
return 1; // back side
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// P_PointInSubsector
|
|
//
|
|
//==========================================================================
|
|
|
|
subsector_t *P_PointInSubsector(double x, double y)
|
|
{
|
|
int side;
|
|
|
|
auto node = level.HeadGamenode();
|
|
if (node == nullptr) return &level.subsectors[0];
|
|
|
|
fixed_t xx = FLOAT2FIXED(x);
|
|
fixed_t yy = FLOAT2FIXED(y);
|
|
do
|
|
{
|
|
side = R_PointOnSide(xx, yy, node);
|
|
node = (node_t *)node->children[side];
|
|
} while (!((size_t)node & 1));
|
|
|
|
return (subsector_t *)((uint8_t *)node - 1);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Use buggy PointOnSide and fix actors that lie on
|
|
// lines to compensate for some IWAD maps.
|
|
//
|
|
//==========================================================================
|
|
|
|
sector_t *P_PointInSectorBuggy(double x, double y)
|
|
{
|
|
// single subsector is a special case
|
|
auto node = level.HeadGamenode();
|
|
if (node == nullptr) return level.subsectors[0].sector;
|
|
do
|
|
{
|
|
// Use original buggy point-on-side test when spawning
|
|
// things at level load so that the map spots in the
|
|
// emerald key room of Hexen MAP01 are spawned on the
|
|
// window ledge instead of the blocking floor in front
|
|
// of it. Why do I consider it buggy? Because a point
|
|
// that lies directly on a line should always be
|
|
// considered as "in front" of the line. The orientation
|
|
// of the line should be irrelevant.
|
|
node = (node_t *)node->children[R_PointOnSideSlow(x, y, node)];
|
|
} while (!((size_t)node & 1));
|
|
|
|
subsector_t *ssec = (subsector_t *)((uint8_t *)node - 1);
|
|
return ssec->sector;
|
|
}
|