doom3-bfg/doomclassic/doom/p_maputl.cpp
2012-11-26 12:58:24 -06:00

877 lines
17 KiB
C++

/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "Precompiled.h"
#include "globaldata.h"
#include <stdlib.h>
#include "m_bbox.h"
#include "doomdef.h"
#include "p_local.h"
// State.
#include "r_state.h"
//
// 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);
}
//
// P_PointOnLineSide
// Returns 0 or 1
//
int
P_PointOnLineSide
( fixed_t x,
fixed_t y,
line_t* line )
{
fixed_t dx;
fixed_t dy;
fixed_t left;
fixed_t right;
if (!line->dx)
{
if (x <= line->v1->x)
return line->dy > 0;
return line->dy < 0;
}
if (!line->dy)
{
if (y <= line->v1->y)
return line->dx < 0;
return line->dx > 0;
}
dx = (x - line->v1->x);
dy = (y - line->v1->y);
left = FixedMul ( line->dy>>FRACBITS , dx );
right = FixedMul ( dy , line->dx>>FRACBITS );
if (right < left)
return 0; // front side
return 1; // back side
}
//
// P_BoxOnLineSide
// Considers the line to be infinite
// Returns side 0 or 1, -1 if box crosses the line.
//
int
P_BoxOnLineSide
( fixed_t* tmbox,
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;
}
if (p1 == p2)
return p1;
return -1;
}
//
// P_PointOnDivlineSide
// Returns 0 or 1.
//
int
P_PointOnDivlineSide
( fixed_t x,
fixed_t y,
divline_t* line )
{
fixed_t dx;
fixed_t dy;
fixed_t left;
fixed_t right;
if (!line->dx)
{
if (x <= line->x)
return line->dy > 0;
return line->dy < 0;
}
if (!line->dy)
{
if (y <= line->y)
return line->dx < 0;
return line->dx > 0;
}
dx = (x - line->x);
dy = (y - line->y);
// try to quickly decide by looking at sign bits
if ( (line->dy ^ line->dx ^ dx ^ dy)&0x80000000 )
{
if ( (line->dy ^ dx) & 0x80000000 )
return 1; // (left is negative)
return 0;
}
left = FixedMul ( line->dy>>8, dx>>8 );
right = FixedMul ( dy>>8 , line->dx>>8 );
if (right < left)
return 0; // front side
return 1; // back side
}
//
// P_MakeDivline
//
void
P_MakeDivline
( 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
( divline_t* v2,
divline_t* v1 )
{
#if 1
fixed_t frac;
fixed_t num;
fixed_t den;
den = FixedMul (v1->dy>>8,v2->dx) - FixedMul(v1->dx>>8,v2->dy);
if (den == 0)
return 0;
// I_Error ("P_InterceptVector: parallel");
num =
FixedMul ( (v1->x - v2->x)>>8 ,v1->dy )
+FixedMul ( (v2->y - v1->y)>>8, v1->dx );
frac = FixedDiv (num , den);
return frac;
#else // UNUSED, float debug.
float frac;
float num;
float den;
float v1x;
float v1y;
float v1dx;
float v1dy;
float v2x;
float v2y;
float v2dx;
float v2dy;
v1x = (float)v1->x/FRACUNIT;
v1y = (float)v1->y/FRACUNIT;
v1dx = (float)v1->dx/FRACUNIT;
v1dy = (float)v1->dy/FRACUNIT;
v2x = (float)v2->x/FRACUNIT;
v2y = (float)v2->y/FRACUNIT;
v2dx = (float)v2->dx/FRACUNIT;
v2dy = (float)v2->dy/FRACUNIT;
den = v1dy*v2dx - v1dx*v2dy;
if (den == 0)
return 0; // parallel
num = (v1x - v2x)*v1dy + (v2y - v1y)*v1dx;
frac = num / den;
return frac*FRACUNIT;
#endif
}
//
// P_LineOpening
// Sets ::g->opentop and ::g->openbottom to the window
// through a two sided line.
// OPTIMIZE: keep this precalculated
//
void P_LineOpening (line_t* maputil_linedef)
{
sector_t* front;
sector_t* back;
if (maputil_linedef->sidenum[1] == -1)
{
// single sided line
::g->openrange = 0;
return;
}
front = maputil_linedef->frontsector;
back = maputil_linedef->backsector;
if (front->ceilingheight < back->ceilingheight)
::g->opentop = front->ceilingheight;
else
::g->opentop = back->ceilingheight;
if (front->floorheight > back->floorheight)
{
::g->openbottom = front->floorheight;
::g->lowfloor = back->floorheight;
}
else
{
::g->openbottom = back->floorheight;
::g->lowfloor = front->floorheight;
}
::g->openrange = ::g->opentop - ::g->openbottom;
}
//
// THING POSITION SETTING
//
//
// P_UnsetThingPosition
// Unlinks a thing from block map and ::g->sectors.
// On each position change, BLOCKMAP and other
// lookups maintaining lists ot things inside
// these structures need to be updated.
//
void P_UnsetThingPosition (mobj_t* thing)
{
int blockx;
int blocky;
if ( ! (thing->flags & MF_NOSECTOR) )
{
// inert things don't need to be in blockmap?
// unlink from subsector
if (thing->snext)
thing->snext->sprev = thing->sprev;
if (thing->sprev)
thing->sprev->snext = thing->snext;
else
thing->subsector->sector->thinglist = thing->snext;
}
if ( ! (thing->flags & MF_NOBLOCKMAP) )
{
// inert things don't need to be in ::g->blockmap
// unlink from block map
if (thing->bnext)
thing->bnext->bprev = thing->bprev;
if (thing->bprev)
thing->bprev->bnext = thing->bnext;
else
{
blockx = (thing->x - ::g->bmaporgx)>>MAPBLOCKSHIFT;
blocky = (thing->y - ::g->bmaporgy)>>MAPBLOCKSHIFT;
if (blockx>=0 && blockx < ::g->bmapwidth
&& blocky>=0 && blocky < ::g->bmapheight)
{
::g->blocklinks[blocky*::g->bmapwidth+blockx] = thing->bnext;
}
}
}
}
//
// P_SetThingPosition
// Links a thing into both a block and a subsector
// based on it's x y.
// Sets thing->subsector properly
//
void
P_SetThingPosition (mobj_t* thing)
{
subsector_t* ss;
sector_t* sec;
int blockx;
int blocky;
mobj_t** link;
// link into subsector
ss = R_PointInSubsector (thing->x,thing->y);
thing->subsector = ss;
if ( ! (thing->flags & MF_NOSECTOR) )
{
// invisible things don't go into the sector links
sec = ss->sector;
thing->sprev = NULL;
thing->snext = sec->thinglist;
if (sec->thinglist)
sec->thinglist->sprev = thing;
sec->thinglist = thing;
}
// link into ::g->blockmap
if ( ! (thing->flags & MF_NOBLOCKMAP) )
{
// inert things don't need to be in ::g->blockmap
blockx = (thing->x - ::g->bmaporgx)>>MAPBLOCKSHIFT;
blocky = (thing->y - ::g->bmaporgy)>>MAPBLOCKSHIFT;
if (blockx>=0
&& blockx < ::g->bmapwidth
&& blocky>=0
&& blocky < ::g->bmapheight)
{
link = &::g->blocklinks[blocky*::g->bmapwidth+blockx];
thing->bprev = NULL;
thing->bnext = *link;
if (*link)
(*link)->bprev = thing;
*link = thing;
}
else
{
// thing is off the map
thing->bnext = thing->bprev = NULL;
}
}
}
//
// 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.
//
//
// P_BlockLinesIterator
// The ::g->validcount flags are used to avoid checking ::g->lines
// that are marked in multiple mapblocks,
// so increment ::g->validcount before the first call
// to P_BlockLinesIterator, then make one or more calls
// to it.
//
qboolean
P_BlockLinesIterator
( int x,
int y,
qboolean(*func)(line_t*) )
{
int offset;
short* list;
line_t* ld;
if (x<0
|| y<0
|| x>=::g->bmapwidth
|| y>=::g->bmapheight)
{
return true;
}
offset = y*::g->bmapwidth+x;
offset = *(::g->blockmap+offset);
for ( list = ::g->blockmaplump+offset ; *list != -1 ; list++)
{
ld = &::g->lines[*list];
if (ld->validcount == ::g->validcount)
continue; // line has already been checked
ld->validcount = ::g->validcount;
if ( !func(ld) )
return false;
}
return true; // everything was checked
}
//
// P_BlockThingsIterator
//
qboolean
P_BlockThingsIterator
( int x,
int y,
qboolean(*func)(mobj_t*) )
{
mobj_t* mobj;
if ( x<0
|| y<0
|| x>=::g->bmapwidth
|| y>=::g->bmapheight)
{
return true;
}
for (mobj = ::g->blocklinks[y*::g->bmapwidth+x] ;
mobj ;
mobj = mobj->bnext)
{
if (!func( mobj ) )
return false;
}
return true;
}
//
// INTERCEPT ROUTINES
//
//
// PIT_AddLineIntercepts.
// Looks for ::g->lines in the given block
// that intercept the given ::g->trace
// to add to the ::g->intercepts list.
//
// A line is crossed if its endpoints
// are on opposite ::g->sides of the ::g->trace.
// Returns true if ::g->earlyout and a solid line hit.
//
qboolean
PIT_AddLineIntercepts (line_t* ld)
{
int s1;
int s2;
fixed_t frac;
divline_t dl;
// avoid precision problems with two routines
if ( ::g->trace.dx > FRACUNIT*16
|| ::g->trace.dy > FRACUNIT*16
|| ::g->trace.dx < -FRACUNIT*16
|| ::g->trace.dy < -FRACUNIT*16)
{
s1 = P_PointOnDivlineSide (ld->v1->x, ld->v1->y, &::g->trace);
s2 = P_PointOnDivlineSide (ld->v2->x, ld->v2->y, &::g->trace);
}
else
{
s1 = P_PointOnLineSide (::g->trace.x, ::g->trace.y, ld);
s2 = P_PointOnLineSide (::g->trace.x+::g->trace.dx, ::g->trace.y+::g->trace.dy, ld);
}
if (s1 == s2)
return true; // line isn't crossed
// hit the line
P_MakeDivline (ld, &dl);
frac = P_InterceptVector (&::g->trace, &dl);
if (frac < 0)
return true; // behind source
// try to early out the check
if (::g->earlyout
&& frac < FRACUNIT
&& !ld->backsector)
{
return false; // stop checking
}
::g->intercept_p->frac = frac;
::g->intercept_p->isaline = true;
::g->intercept_p->d.line = ld;
::g->intercept_p++;
return true; // continue
}
//
// PIT_AddThingIntercepts
//
qboolean PIT_AddThingIntercepts (mobj_t* thing)
{
fixed_t x1;
fixed_t y1;
fixed_t x2;
fixed_t y2;
int s1;
int s2;
qboolean tracepositive;
divline_t dl;
fixed_t frac;
tracepositive = (::g->trace.dx ^ ::g->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, &::g->trace);
s2 = P_PointOnDivlineSide (x2, y2, &::g->trace);
if (s1 == s2)
return true; // line isn't crossed
dl.x = x1;
dl.y = y1;
dl.dx = x2-x1;
dl.dy = y2-y1;
frac = P_InterceptVector (&::g->trace, &dl);
if (frac < 0)
return true; // behind source
::g->intercept_p->frac = frac;
::g->intercept_p->isaline = false;
::g->intercept_p->d.thing = thing;
::g->intercept_p++;
return true; // keep going
}
//
// P_TraverseIntercepts
// Returns true if the traverser function returns true
// for all ::g->lines.
//
qboolean
P_TraverseIntercepts
( traverser_t func,
fixed_t maxfrac )
{
int count;
fixed_t dist;
intercept_t* scan;
intercept_t* in;
count = ::g->intercept_p - ::g->intercepts;
in = 0; // shut up compiler warning
while (count--)
{
dist = MAXINT;
for (scan = ::g->intercepts ; scan < ::g->intercept_p ; scan++)
{
if (scan->frac < dist)
{
dist = scan->frac;
in = scan;
}
}
if (dist > maxfrac)
return true; // checked everything in range
#if 0 // UNUSED
{
// don't check these yet, there may be others inserted
in = scan = ::g->intercepts;
for ( scan = ::g->intercepts ; scan<::g->intercept_p ; scan++)
if (scan->frac > maxfrac)
*in++ = *scan;
::g->intercept_p = in;
return false;
}
#endif
if ( !func (in) )
return false; // don't bother going farther
in->frac = MAXINT;
}
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 ::g->lines.
//
qboolean
P_PathTraverse
( fixed_t x1,
fixed_t y1,
fixed_t x2,
fixed_t y2,
int flags,
qboolean (*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;
::g->earlyout = flags & PT_EARLYOUT;
::g->validcount++;
::g->intercept_p = ::g->intercepts;
if ( ((x1-::g->bmaporgx)&(MAPBLOCKSIZE-1)) == 0)
x1 += FRACUNIT; // don't side exactly on a line
if ( ((y1-::g->bmaporgy)&(MAPBLOCKSIZE-1)) == 0)
y1 += FRACUNIT; // don't side exactly on a line
::g->trace.x = x1;
::g->trace.y = y1;
::g->trace.dx = x2 - x1;
::g->trace.dy = y2 - y1;
x1 -= ::g->bmaporgx;
y1 -= ::g->bmaporgy;
xt1 = x1>>MAPBLOCKSHIFT;
yt1 = y1>>MAPBLOCKSHIFT;
x2 -= ::g->bmaporgx;
y2 -= ::g->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))
return false; // early out
}
if (flags & PT_ADDTHINGS)
{
if (!P_BlockThingsIterator (mapx, mapy,PIT_AddThingIntercepts))
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 );
}