Implement dynasegs, from Eternity

This commit is contained in:
Jaime Ita Passos 2021-06-14 21:36:32 -03:00
parent 786516fa5f
commit 1fb70c7d12
21 changed files with 1883 additions and 141 deletions

View file

@ -131,6 +131,8 @@ set(SRB2_CORE_RENDER_SOURCES
r_bsp.c
r_data.c
r_draw.c
r_dynabsp.c
r_dynseg.c
r_main.c
r_plane.c
r_segs.c
@ -148,6 +150,8 @@ set(SRB2_CORE_RENDER_SOURCES
r_data.h
r_defs.h
r_draw.h
r_dynabsp.h
r_dynseg.h
r_local.h
r_main.h
r_plane.h

View file

@ -531,6 +531,8 @@ OBJS:=$(i_main_o) \
$(OBJDIR)/r_bsp.o \
$(OBJDIR)/r_data.o \
$(OBJDIR)/r_draw.o \
$(OBJDIR)/r_dynabsp.o\
$(OBJDIR)/r_dynseg.o \
$(OBJDIR)/r_main.o \
$(OBJDIR)/r_plane.o \
$(OBJDIR)/r_segs.o \

View file

@ -444,7 +444,7 @@ static poly_t *CutOutSubsecPoly(seg_t *lseg, INT32 count, poly_t *poly)
{
line_t *line = lseg->linedef;
if (lseg->glseg)
if (lseg->glseg || lseg->polyseg)
continue;
//x,y,dx,dy (like a divline)

View file

@ -28,6 +28,7 @@
#include "../r_patch.h"
#include "../r_picformats.h"
#include "../r_bsp.h"
#include "../r_dynseg.h"
#include "../d_clisrv.h"
#include "../w_wad.h"
#include "../z_zone.h"
@ -2677,10 +2678,10 @@ static inline void HWR_AddPolyObjectSegs(void)
M_Memcpy(gl_fakeline, po_ptrs[i]->segs[j], sizeof(seg_t));
// Now convert the line to float and add it to be rendered
pv1->x = FIXED_TO_FLOAT(gl_fakeline->v1->x);
pv1->y = FIXED_TO_FLOAT(gl_fakeline->v1->y);
pv2->x = FIXED_TO_FLOAT(gl_fakeline->v2->x);
pv2->y = FIXED_TO_FLOAT(gl_fakeline->v2->y);
pv1->x = FIXED_TO_FLOAT(gl_fakeline->dyv1->x);
pv1->y = FIXED_TO_FLOAT(gl_fakeline->dyv1->y);
pv2->x = FIXED_TO_FLOAT(gl_fakeline->dyv2->x);
pv2->y = FIXED_TO_FLOAT(gl_fakeline->dyv2->y);
gl_fakeline->pv1 = pv1;
gl_fakeline->pv2 = pv2;
@ -3227,7 +3228,7 @@ static void HWR_Subsector(size_t num)
numpolys = 0;
// Count all the polyobjects, reset the list, and recount them
// Count all the polyobjects
while (po)
{
++numpolys;

View file

@ -50,8 +50,13 @@ FUNCINLINE static ATTRINLINE void M_DLListRemove(mdllistitem_t *item)
mdllistitem_t **prev = item->prev;
mdllistitem_t *next = item->next;
if ((*prev = next))
// haleyjd 05/07/13: safety #1: only if prev is non-null
if (prev && (*prev = next))
next->prev = prev;
// haleyjd 05/07/13: safety #2: clear links.
item->prev = NULL;
item->next = NULL;
}
#endif

View file

@ -241,6 +241,37 @@ static INT32 P_PointOnDivlineSide(fixed_t x, fixed_t y, divline_t *line)
return 1; // back side
}
INT32 P_PointOnDivlineSidePrecise(fixed_t x, fixed_t y, const divline_t *line)
{
fixed_t dx, dy;
INT64 left, 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);
left = line->dy * dx;
right = dy * line->dx;
if (right < left)
return 0; // front side
return 1; // back side
}
//
// P_MakeDivline
//

View file

@ -45,6 +45,7 @@ FUNCMATH fixed_t P_AproxDistance(fixed_t dx, fixed_t dy);
void P_ClosestPointOnLine(fixed_t x, fixed_t y, line_t *line, vertex_t *result);
void P_ClosestPointOnLine3D(const vector3_t *p, const vector3_t *line, vector3_t *result);
INT32 P_PointOnLineSide(fixed_t x, fixed_t y, line_t *line);
INT32 P_PointOnDivlineSidePrecise(fixed_t x, fixed_t y, const divline_t *line);
void P_MakeDivline(line_t *li, divline_t *dl);
void P_CameraLineOpening(line_t *plinedef);
fixed_t P_InterceptVector(divline_t *v2, divline_t *v1);

View file

@ -3288,7 +3288,7 @@ void P_MobjCheckWater(mobj_t *mobj)
{ // Water removes electric and non-water fire shields...
if (electric)
P_FlashPal(p, PAL_WHITE, 1);
p->powers[pw_shield] = p->powers[pw_shield] & SH_STACK;
}
}

View file

@ -27,6 +27,7 @@
#include "r_main.h"
#include "r_state.h"
#include "r_defs.h"
#include "r_dynseg.h"
/*
Theory behind Polyobjects:
@ -601,7 +602,7 @@ static void Polyobj_moveToSpawnSpot(mapthing_t *anchor)
// Attaches a polyobject to its appropriate subsector.
static void Polyobj_attachToSubsec(polyobj_t *po)
{
subsector_t *ss;
subsector_t *ss;
fixed_t center_x = 0, center_y = 0;
fixed_t numVertices;
size_t i;
@ -610,6 +611,10 @@ static void Polyobj_attachToSubsec(polyobj_t *po)
if (po->isBad)
return;
// already attached?
if (po->attached)
return;
numVertices = (fixed_t)(po->numVertices*FRACUNIT);
for (i = 0; i < po->numVertices; ++i)
@ -622,25 +627,24 @@ static void Polyobj_attachToSubsec(polyobj_t *po)
po->centerPt.y = center_y;
ss = R_PointInSubsector(po->centerPt.x, po->centerPt.y);
M_DLListInsert(&po->link, (mdllistitem_t **)(void *)(&ss->polyList));
#ifdef R_LINKEDPORTALS
// set spawnSpot's groupid for correct portal sound behavior
po->spawnSpot.groupid = ss->sector->groupid;
#endif
po->attached = true;
R_AttachPolyObject(po);
}
// Removes a polyobject from the subsector to which it is attached.
static void Polyobj_removeFromSubsec(polyobj_t *po)
{
if (po->attached)
{
M_DLListRemove(&po->link);
po->attached = false;
}
// a bad polyobject should never have been attached in the first place
if (po->isBad)
return;
// not attached?
if (!po->attached)
return;
M_DLListRemove(&po->link);
R_DetachPolyObject(po);
}
// Blockmap Functions

View file

@ -76,8 +76,12 @@ typedef struct polyobj_s
INT32 parent; // numeric id of parent polyobject
size_t segCount; // number of segs in polyobject
size_t numSegsAlloc; // number of segs allocated
struct subsector_s **dynaSubsecs; // list of subsectors holding fragments
INT32 numDSS; // number of subsector pointers
INT32 numDSSAlloc; // number of subsector pointers allocated
size_t segCount; // number of segs in polyobject
size_t numSegsAlloc; // number of segs allocated
struct seg_s **segs; // the segs, a reallocating array.
size_t numVertices; // number of vertices (generally == segCount)
@ -88,16 +92,16 @@ typedef struct polyobj_s
size_t numLines; // number of linedefs (generally <= segCount)
size_t numLinesAlloc; // number of linedefs allocated
struct line_s **lines; // linedefs this polyobject must move
struct line_s **lines; // linedefs this polyobject must move
degenmobj_t spawnSpot; // location of spawn spot
vertex_t centerPt; // center point
fixed_t zdist; // viewz distance for sorting
angle_t angle; // for rotation
UINT8 attached; // if true, is attached to a subsector
UINT8 attached; // if true, is attached to a subsector
fixed_t blockbox[4]; // bounding box for clipping
UINT8 linked; // is linked to blockmap
UINT8 linked; // is linked to blockmap
size_t validcount; // for clipping: prevents multiple checks
INT32 damage; // damage to inflict on stuck things
fixed_t thrust; // amount of thrust to put on blocking objects
@ -105,7 +109,7 @@ typedef struct polyobj_s
thinker_t *thinker; // pointer to a thinker affecting this polyobj
UINT8 isBad; // a bad polyobject: should not be rendered/manipulated
UINT8 isBad; // a bad polyobject: should not be rendered/manipulated
INT32 translucency; // index to translucency tables
INT16 triggertag; // Tag of linedef executor to trigger on touch

View file

@ -33,6 +33,7 @@
#include "r_picformats.h"
#include "r_sky.h"
#include "r_draw.h"
#include "r_dynseg.h"
#include "s_sound.h"
#include "st_stuff.h"
@ -103,6 +104,7 @@ seg_t *segs;
sector_t *sectors;
subsector_t *subsectors;
node_t *nodes;
fnode_t *fnodes;
line_t *lines;
side_t *sides;
mapthing_t *mapthings;
@ -948,6 +950,12 @@ void P_WriteThings(void)
// MAP LOADING FUNCTIONS
//
static void P_InitializeVertex(vertex_t *vt)
{
vt->floorzset = vt->ceilingzset = false;
vt->floorz = vt->ceilingz = 0;
}
static void P_LoadVertices(UINT8 *data)
{
mapvertex_t *mv = (mapvertex_t *)data;
@ -959,8 +967,7 @@ static void P_LoadVertices(UINT8 *data)
{
v->x = SHORT(mv->x)<<FRACBITS;
v->y = SHORT(mv->y)<<FRACBITS;
v->floorzset = v->ceilingzset = false;
v->floorz = v->ceilingz = 0;
P_InitializeVertex(v);
}
}
@ -1136,6 +1143,23 @@ static void P_SetLinedefV2(size_t i, UINT16 vertex_num)
lines[i].v2 = &vertexes[vertex_num];
}
//
// P_MakeLineNormal
//
// Calculates a 2D normal for the given line and stores it in the line
//
static void P_MakeLineNormal(line_t *line)
{
float linedx, linedy, length;
linedx = FixedToFloat(line->v2->x) - FixedToFloat(line->v1->x);
linedy = FixedToFloat(line->v2->y) - FixedToFloat(line->v1->y);
length = (float)sqrt(linedx * linedx + linedy * linedy);
line->nx = linedy / length;
line->ny = -linedx / length;
}
static void P_LoadLinedefs(UINT8 *data)
{
maplinedef_t *mld = (maplinedef_t *)data;
@ -1158,6 +1182,7 @@ static void P_LoadLinedefs(UINT8 *data)
ld->sidenum[1] = SHORT(mld->sidenum[1]);
P_InitializeLinedef(ld);
P_MakeLineNormal(ld);
}
}
@ -1828,8 +1853,7 @@ static void P_LoadTextmap(void)
{
// Defaults.
vt->x = vt->y = INT32_MAX;
vt->floorzset = vt->ceilingzset = false;
vt->floorz = vt->ceilingz = 0;
P_InitializeVertex(vt);
TextmapParse(vertexesPos[i], i, ParseTextmapVertexParameter);
@ -1905,6 +1929,7 @@ static void P_LoadTextmap(void)
I_Error("P_LoadTextmap: linedef %s has no sidefront value set!\n", sizeu1(i));
P_InitializeLinedef(ld);
P_MakeLineNormal(ld);
}
for (i = 0, sd = sides; i < numsides; i++, sd++)
@ -2121,6 +2146,29 @@ static inline void P_LoadSubsectors(UINT8 *data)
}
}
//
// P_CalcNodeCoefficients
//
// haleyjd 06/14/10: Separated from P_LoadNodes, this routine precalculates
// general line equation coefficients for a node, which are used during the
// process of dynaseg generation.
//
static void P_CalcNodeCoefficients(node_t *node, fnode_t *fnode)
{
// haleyjd 05/16/08: keep floating point versions as well for dynamic
// seg splitting operations
double fx = (double)FixedToFloat(node->x);
double fy = (double)FixedToFloat(node->y);
double fdx = (double)FixedToFloat(node->dx);
double fdy = (double)FixedToFloat(node->dy);
// haleyjd 05/20/08: precalculate general line equation coefficients
fnode->a = -fdy;
fnode->b = fdx;
fnode->c = fdy * fx - fdx * fy;
fnode->len = sqrt(fdx * fdx + fdy * fdy);
}
static void P_LoadNodes(UINT8 *data)
{
UINT8 j, k;
@ -2134,6 +2182,10 @@ static void P_LoadNodes(UINT8 *data)
no->y = SHORT(mn->y)<<FRACBITS;
no->dx = SHORT(mn->dx)<<FRACBITS;
no->dy = SHORT(mn->dy)<<FRACBITS;
// haleyjd: calculate floating-point data
P_CalcNodeCoefficients(no, &fnodes[i]);
for (j = 0; j < 2; j++)
{
no->children[j] = SHORT(mn->children[j]);
@ -2148,7 +2200,7 @@ static void P_LoadNodes(UINT8 *data)
* \param seg Seg to compute length for.
* \return Length in fracunits.
*/
static fixed_t P_SegLength(seg_t *seg)
fixed_t P_SegLength(seg_t *seg)
{
INT64 dx = (seg->v2->x - seg->v1->x)>>1;
INT64 dy = (seg->v2->y - seg->v1->y)>>1;
@ -2446,6 +2498,7 @@ static void P_LoadExtendedNodes(UINT8 **data, nodetype_t nodetype)
numnodes = READINT32((*data));
nodes = Z_Calloc(numnodes*sizeof(*nodes), PU_LEVEL, NULL);
fnodes = Z_Calloc(numnodes*sizeof(*fnodes), PU_LEVEL, NULL);
for (i = 0, mn = nodes; i < numnodes; i++, mn++)
{
@ -2455,6 +2508,9 @@ static void P_LoadExtendedNodes(UINT8 **data, nodetype_t nodetype)
mn->dx = xgl3 ? READINT32((*data)) : (READINT16((*data)) << FRACBITS);
mn->dy = xgl3 ? READINT32((*data)) : (READINT16((*data)) << FRACBITS);
// haleyjd: calculate floating-point data
P_CalcNodeCoefficients(mn, &fnodes[i]);
// Bounding boxes
for (j = 0; j < 2; j++)
for (k = 0; k < 4; k++)
@ -2492,6 +2548,7 @@ static void P_LoadMapBSP(const virtres_t *virt)
subsectors = Z_Calloc(numsubsectors * sizeof(*subsectors), PU_LEVEL, NULL);
nodes = Z_Calloc(numnodes * sizeof(*nodes), PU_LEVEL, NULL);
fnodes = Z_Calloc(numnodes * sizeof(*fnodes), PU_LEVEL, NULL);
segs = Z_Calloc(numsegs * sizeof(*segs), PU_LEVEL, NULL);
P_LoadSubsectors(virtssectors->data);
@ -3235,7 +3292,7 @@ static void P_ConvertBinaryMap(void)
lines[i].args[4] |= TMSC_BACKTOFRONTCEILING;
lines[i].special = 720;
break;
case 900: //Translucent wall (10%)
case 901: //Translucent wall (20%)
case 902: //Translucent wall (30%)
@ -4228,6 +4285,9 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
Z_Free(ss->attachedsolid);
}
// haleyjd 05/16/08: clear dynamic segs
R_ClearDynaSegs();
// Clear pointers that would be left dangling by the purge
R_FlushTranslationColormapCache();

View file

@ -93,6 +93,8 @@ INT32 P_CheckLevelFlat(const char *flatname);
extern size_t nummapthings;
extern mapthing_t *mapthings;
fixed_t P_SegLength(seg_t *seg);
void P_SetupLevelSky(INT32 skynum, boolean global);
#ifdef SCANTHINGS
void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum);

View file

@ -5188,13 +5188,13 @@ static void P_PlayerOnSpecial3DFloor(player_t *player, sector_t *sector)
continue;
}
// We're inside it! Yess...
if (!polysec->special)
{
po = (polyobj_t *)(po->link.next);
continue;
}
// We're inside it! Yess...
if (!(po->flags & POF_TESTHEIGHT)) // Don't do height checking
;
else if (po->flags & POF_SOLID)

View file

@ -16,6 +16,8 @@
#include "r_local.h"
#include "r_state.h"
#include "r_portal.h" // Add seg portals
#include "r_dynabsp.h"
#include "r_dynseg.h"
#include "r_splats.h"
#include "p_local.h" // camera
@ -716,106 +718,53 @@ void R_SortPolyObjects(subsector_t *sub)
}
//
// R_PolysegCompare
// Recurse through a polynode mini-BSP
//
// Callback for qsort to sort the segs of a polyobject. Returns such that the
// closer one is sorted first. I sure hope this doesn't break anything. -Red
//
static int R_PolysegCompare(const void *p1, const void *p2)
static void R_renderPolyNode(const rpolynode_t *node)
{
const seg_t *seg1 = *(const seg_t * const *)p1;
const seg_t *seg2 = *(const seg_t * const *)p2;
fixed_t dist1v1, dist1v2, dist2v1, dist2v2;
while(node)
{
seg_t *seg = &node->partition->seg;
// TODO might be a better way to get distance?
#define pdist(x, y) (FixedMul(R_PointToDist(x, y), FINECOSINE((R_PointToAngle(x, y)-viewangle)>>ANGLETOFINESHIFT))+0xFFFFFFF)
#define vxdist(v) pdist(v->x, v->y)
// render frontspace
int side = R_PointOnDynaSegSide(node->partition, FixedToFloat(viewx), FixedToFloat(viewy));
R_renderPolyNode(node->children[side]);
dist1v1 = vxdist(seg1->v1);
dist1v2 = vxdist(seg1->v2);
dist2v1 = vxdist(seg2->v1);
dist2v2 = vxdist(seg2->v2);
// render partition seg
seg->angle = R_PointToAngleEx(seg->v1->x, seg->v1->y, seg->v2->x, seg->v2->y);
seg->polyseg = node->partition->polyobj;
R_AddLine(seg);
if (min(dist1v1, dist1v2) != min(dist2v1, dist2v2))
return min(dist1v1, dist1v2) - min(dist2v1, dist2v2);
{ // That didn't work, so now let's try this.......
fixed_t delta1, delta2, x1, y1, x2, y2;
vertex_t *near1, *near2, *far1, *far2; // wherever you are~
delta1 = R_PointToDist2(seg1->v1->x, seg1->v1->y, seg1->v2->x, seg1->v2->y);
delta2 = R_PointToDist2(seg2->v1->x, seg2->v1->y, seg2->v2->x, seg2->v2->y);
delta1 = FixedDiv(128<<FRACBITS, delta1);
delta2 = FixedDiv(128<<FRACBITS, delta2);
if (dist1v1 < dist1v2)
{
near1 = seg1->v1;
far1 = seg1->v2;
}
else
{
near1 = seg1->v2;
far1 = seg1->v1;
}
if (dist2v1 < dist2v2)
{
near2 = seg2->v1;
far2 = seg2->v2;
}
else
{
near2 = seg2->v2;
far2 = seg2->v1;
}
x1 = near1->x + FixedMul(far1->x-near1->x, delta1);
y1 = near1->y + FixedMul(far1->y-near1->y, delta1);
x2 = near2->x + FixedMul(far2->x-near2->x, delta2);
y2 = near2->y + FixedMul(far2->y-near2->y, delta2);
return pdist(x1, y1)-pdist(x2, y2);
// continue to render backspace
node = node->children[side^1];
}
#undef vxdist
#undef pdist
}
//
// R_AddPolyObjects
// haleyjd: Adds dynamic segs contained in all of the rpolyobj_t fragments
// contained inside the given subsector into a mini-BSP tree and then
// renders the BSP. BSPs are only recomputed when polyobject fragments
// move into or out of the subsector. This is the ultimate heart of the
// polyobject code.
//
// haleyjd 02/19/06
// Adds all segs in all polyobjects in the given subsector.
// See r_dynseg.c to see how dynasegs get attached to a subsector in the
// first place :)
//
static void R_AddPolyObjects(subsector_t *sub)
// See r_dynabsp.c for rpolybsp generation.
//
static void R_addDynaSegs(subsector_t *sub)
{
polyobj_t *po = sub->polyList;
size_t i, j;
boolean needbsp = (!sub->bsp || sub->bsp->dirty);
numpolys = 0;
// count polyobjects
while (po)
if(needbsp)
{
++numpolys;
po = (polyobj_t *)(po->link.next);
if(sub->bsp)
R_FreeDynaBSP(sub->bsp);
sub->bsp = R_BuildDynaBSP(sub);
}
// for render stats
ps_numpolyobjects += numpolys;
// sort polyobjects
R_SortPolyObjects(sub);
// render polyobjects
for (i = 0; i < numpolys; ++i)
{
qsort(po_ptrs[i]->segs, po_ptrs[i]->segCount, sizeof(seg_t *), R_PolysegCompare);
for (j = 0; j < po_ptrs[i]->segCount; ++j)
R_AddLine(po_ptrs[i]->segs[j]);
}
if(sub->bsp)
R_renderPolyNode(sub->bsp->root);
}
//
@ -988,19 +937,21 @@ static void R_Subsector(size_t num)
}
// Polyobjects have planes, too!
if (sub->polyList)
if (sub->renderPolyList)
{
polyobj_t *po = sub->polyList;
rpolyobj_t *rpo = sub->renderPolyList;
sector_t *polysec;
while (po)
while (rpo)
{
polyobj_t *po = rpo->polyobj;
if (numffloors >= MAXFFLOORS)
break;
if (!(po->flags & POF_RENDERPLANES)) // Don't draw planes
{
po = (polyobj_t *)(po->link.next);
rpo = (rpolyobj_t *)(rpo->link.next);
continue;
}
@ -1049,7 +1000,7 @@ static void R_Subsector(size_t num)
numffloors++;
}
po = (polyobj_t *)(po->link.next);
rpo = (rpolyobj_t *)(rpo->link.next);
}
}
@ -1070,8 +1021,8 @@ static void R_Subsector(size_t num)
firstseg = NULL;
// haleyjd 02/19/06: draw polyobjects before static lines
if (sub->polyList)
R_AddPolyObjects(sub);
if (sub->renderPolyList)
R_addDynaSegs(sub);
while (count--)
{

View file

@ -24,10 +24,6 @@
#include "screen.h" // MAXVIDWIDTH, MAXVIDHEIGHT
#ifdef HWRENDER
#include "m_aatree.h"
#endif
#include "taglist.h"
//
@ -386,6 +382,7 @@ typedef struct line_s
vertex_t *v2;
fixed_t dx, dy; // Precalculated v2 - v1 for side checking.
float nx, ny; // SoM 05/11/09: Pre-calculated 2D normal for the line
// Animation related.
INT16 flags;
@ -453,7 +450,9 @@ typedef struct subsector_s
sector_t *sector;
INT16 numlines;
UINT16 firstline;
struct polyobj_s *polyList; // haleyjd 02/19/06: list of polyobjects
struct polyobj_s *polyList; // List of polyobjects that physically exist in this subsector.
struct rpolyobj_s *renderPolyList; // haleyjd 02/19/06: list of polyobjects
struct rpolybsp_s *bsp; // haleyjd 05/05/13: sub-BSP tree
size_t validcount;
} subsector_t;
@ -523,8 +522,11 @@ typedef struct lightmap_s
//
typedef struct seg_s
{
vertex_t *v1;
vertex_t *v2;
union
{
struct { vertex_t *v1, *v2; };
struct { struct dynavertex_s *dyv1, *dyv2; };
};
INT32 side;
@ -541,6 +543,7 @@ typedef struct seg_s
sector_t *backsector;
fixed_t length; // precalculated seg length
#ifdef HWRENDER
// new pointers so that AdjustSegs doesn't mess with v1/v2
void *pv1; // polyvertex_t
@ -550,10 +553,13 @@ typedef struct seg_s
lightmap_t *lightmaps; // for static lightmap
#endif
polyobj_t *polyseg;
sector_t *polysector;
// Why slow things down by calculating lightlists for every thick side?
size_t numlights;
r_lightlist_t *rlights;
polyobj_t *polyseg;
boolean dontrenderme;
boolean glseg;
} seg_t;
@ -574,6 +580,20 @@ typedef struct
UINT16 children[2];
} node_t;
//
// fnode
//
// haleyjd 12/07/12: The fnode structure holds floating-point general line
// equation coefficients and float versions of partition line coordinates and
// lengths. It is kept separate from node_t for purposes of not causing that
// structure to become cache inefficient.
//
typedef struct fnode_s
{
double a, b, c; // haleyjd 05/20/08: coefficients for general line equation
double len; // length of partition line, for normalization
} fnode_t;
#if defined(_MSC_VER)
#pragma pack(1)
#endif

719
src/r_dynabsp.c Normal file
View file

@ -0,0 +1,719 @@
// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// Copyright(C) 2013 James Haley et al.
//
// Portions derived from BSP 5.2, "Node builder for DOOM levels"
// (c) 1996 Raphael Quinet
// (c) 1998 Colin Reed, Lee Killough
// (c) 2001 Simon Howard
// (c) 2006 Colin Phipps
//
// 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:
// Dynamic BSP sub-trees for dynaseg sorting.
//
//-----------------------------------------------------------------------------
#include "z_zone.h"
#include "p_setup.h"
#include "r_dynabsp.h"
//=============================================================================
//
// rpolynode Maintenance
//
static rpolynode_t *polyNodeFreeList;
//
// R_GetFreePolyNode
//
// Gets a node from the free list or allocates a new one.
//
static rpolynode_t *R_GetFreePolyNode(void)
{
rpolynode_t *ret = NULL;
if(polyNodeFreeList)
{
ret = polyNodeFreeList;
polyNodeFreeList = polyNodeFreeList->children[0];
memset(ret, 0, sizeof(*ret));
}
else
ret = calloc(sizeof(rpolynode_t), 1);
return ret;
}
//
// R_FreePolyNode
//
// Puts a dynamic node onto the free list.
//
static void R_FreePolyNode(rpolynode_t *rpn)
{
rpn->children[0] = polyNodeFreeList;
polyNodeFreeList = rpn;
}
//=============================================================================
//
// Dynaseg Setup
//
static void InsertIntoNodeList(dsegnode_t **tail, dynaseg_t *seg)
{
dsegnode_t *node = Z_Calloc(sizeof(dsegnode_t), PU_STATIC, NULL);
node->dynaseg = seg;
if (*tail)
{
node->prev = (*tail);
(*tail)->next = node;
}
(*tail) = node;
}
static void RemoveFromNodeList(dsegnode_t *node, dsegnode_t **tail, dsegnode_t **head)
{
if (node->next)
node->next->prev = node->prev;
else
(*tail) = node->prev;
if (node->prev)
node->prev->next = node->next;
else
(*head) = node->next;
Z_Free(node);
}
static void FindNodeListHead(dsegnode_t **list)
{
if (!*list)
return;
while ((*list)->prev)
(*list) = (*list)->prev;
}
static void FindNodeListTail(dsegnode_t **list)
{
if (!*list)
return;
while ((*list)->next)
(*list) = (*list)->next;
}
//
// R_setupDSForBSP
//
// Dynasegs need a small amount of preparation in order to achieve maximum
// efficiency during the node building process.
//
static void R_setupDSForBSP(dynaseg_t *ds)
{
// Fast access to double-precision coordinates
ds->psx = FixedToFloat(ds->seg.v1->x);
ds->psy = FixedToFloat(ds->seg.v1->y);
ds->pex = FixedToFloat(ds->seg.v2->x);
ds->pey = FixedToFloat(ds->seg.v2->y);
// Fast access to delta x, delta y
ds->pdx = ds->pex - ds->psx;
ds->pdy = ds->pey - ds->psy;
// General line equation coefficient 'c'
ds->ptmp = ds->pdx * ds->psy - ds->psx * ds->pdy;
// Length - we DEFINITELY don't want to do this any more than necessary
ds->len = sqrt(ds->pdx * ds->pdx + ds->pdy * ds->pdy);
}
//=============================================================================
//
// Partition Selection
//
// I am not TOO worried about accuracy during partition selection, but...
#define CHECK_EPSILON 0.0001
static inline boolean nearzero(double d)
{
return (d < CHECK_EPSILON && d > -CHECK_EPSILON);
}
// split weighting factor
// I have no idea why "17" is good, but let's stick with it.
#define FACTOR 17
//
// R_selectPartition
//
// This routine decides the best dynaseg to use as a nodeline by attempting to
// minimize the number of splits it would cause in remaining dynasegs.
//
// Credit to Raphael Quinet and DEU.
//
// Rewritten by Lee Killough for significant performance increases.
// (haleyjd - using gotos, naturally ;)
//
static dsegnode_t *R_selectPartition(dsegnode_t *segs)
{
dsegnode_t *rover;
dsegnode_t *best = NULL;
int bestcost = INT_MAX;
int cnt = 0;
// count the number of segs on the input list
for(rover = segs; rover; rover = rover->next)
++cnt;
// Try each seg as a partition line
for(rover = segs; rover; rover = rover->next)
{
dynaseg_t *part = rover->dynaseg;
dsegnode_t *crover;
int cost = 0, tot = 0, diff = cnt;
// haleyjd: add one seg worth of cost to non-orthogonal lines
if(part->seg.linedef->slopetype > ST_VERTICAL)
cost += FACTOR;
// Check partition against all segs
for(crover = segs; crover; crover = crover->next)
{
dynaseg_t *check = crover->dynaseg;
// classify both end points
double a = part->pdy * check->psx - part->pdx * check->psy + part->ptmp;
double b = part->pdy * check->pex - part->pdx * check->pey + part->ptmp;
if(!(a*b >= 0)) // oppositely signed?
{
if(!nearzero(a) && !nearzero(b)) // not within epsilon of 0?
{
// line is split
double l = check->len;
double d = (l * a) / (a - b); // distance from intersection
if(d >= 2.0)
{
cost += FACTOR;
// killough: This is the heart of my pruning idea - it
// catches bad segs early on.
if(cost > bestcost)
goto prune;
++tot;
}
else if(l - d < 2.0 ? check->pdx * part->pdx + check->pdy * part->pdy < 0 : b < 0)
goto leftside;
}
else
goto leftside;
}
else if(a <= 0 && (!nearzero(a) ||
(nearzero(b) && check->pdx * part->pdx + check->pdy * part->pdy < 0)))
{
leftside:
diff -= 2;
}
} // end for
// Take absolute value; diff is being used to obtain the min/max values
// by way of min(a, b) = (a + b - abs(a - b)) / 2
if((diff -= tot) < 0)
diff = -diff;
// Make sure at least one seg is on each side of the partition
if(tot + cnt > diff && (cost += diff) < bestcost)
{
// we have a new better choice
bestcost = cost;
best = rover; // remember the best seg
}
prune: ; // early exit and skip past the tests above
} // end for
// haleyjd: failsafe. Maybe there's just one left in the list. I'm not
// taking any chances that the above algorithm might freak out when that
// becomes the case. I KNOW the list is not empty.
if(!best)
best = segs;
return best; // All finished, return best seg
}
//=============================================================================
//
// Tree Building
//
//
// Calculate the point of intersection of two lines
//
void R_ComputeIntersection(const dynaseg_t *part, const dynaseg_t *seg, double *outx, double *outy)
{
double a2, b2, l2, w, d;
double dx, dy, dx2, dy2;
dx = part->pex - part->psx;
dy = part->pey - part->psy;
dx2 = seg->pex - seg->psx;
dy2 = seg->pey - seg->psy;
l2 = sqrt(dx2*dx2 + dy2*dy2);
if(l2 == 0.0)
{
// feh.
*outx = seg->psx;
*outy = seg->psy;
return;
}
a2 = dx2 / l2;
b2 = dy2 / l2;
d = dy * a2 - dx * b2;
if(d != 0.0)
{
w = (dx * (seg->psy - part->psy) + dy * (part->psx - seg->psx)) / d;
(*outx) = seg->psx + (a2 * w);
(*outy) = seg->psy + (b2 * w);
}
else
{
(*outx) = seg->psx;
(*outy) = seg->psy;
}
}
// return-type enumeration for R_classifyDynaSeg
enum
{
END_ON = 0x01,
END_LEFT = 0x02,
END_RIGHT = 0x04,
START_ON = 0x10,
START_LEFT = 0x20,
START_RIGHT = 0x40,
// cases where seg must be split
SPLIT_SL_ER = (START_LEFT | END_RIGHT),
SPLIT_SR_EL = (START_RIGHT | END_LEFT ),
// cases where seg is not split
CLASSIFY_LEFT = (START_LEFT | END_LEFT ),
CLASSIFY_RIGHT = (START_RIGHT | END_RIGHT),
CLASSIFY_ON = (START_ON | END_ON )
};
//
// R_classifyDynaSeg
//
// The seg is either left, right, or on the partition line.
//
static int R_classifyDynaSeg(const dynaseg_t *part, const dynaseg_t *seg, double pdx, double pdy)
{
double dx2, dy2, dx3, dy3, a, b;
// check line against partition
dx2 = part->psx - seg->psx;
dy2 = part->psy - seg->psy;
dx3 = part->psx - seg->pex;
dy3 = part->psy - seg->pey;
a = pdy * dx2 - pdx * dy2;
b = pdy * dx3 - pdx * dy3;
if(!(a*b >= 0) && !nearzero(a) && !nearzero(b))
{
double x = 0.0, y = 0.0;
// line is split
R_ComputeIntersection(part, seg, &x, &y);
// find distance from line start to split point
dx2 = seg->psx - x;
dy2 = seg->psy - y;
if(nearzero(dx2) && nearzero(dy2))
a = 0.0;
else
{
double l = dx2*dx2 + dy2*dy2;
if(l < 4.0)
a = 0.0;
}
dx3 = seg->pex - x;
dy3 = seg->pey - y;
if(nearzero(dx3) && nearzero(dy3))
b = 0.0;
else
{
double l = dx3*dx3 + dy3*dy3;
if(l < 4.0)
b = 0.0;
}
}
int val = 0;
if(nearzero(a)) // start is on the partition
val |= START_ON;
else if(a < 0.0) // start is on left
val |= START_LEFT;
else // start is on right
val |= START_RIGHT;
if(nearzero(b)) // end is on partition
val |= END_ON;
else if(b < 0.0) // end is on left
val |= END_LEFT;
else // end is on right
val |= END_RIGHT;
return val;
}
//
// R_divideSegs
//
// Split the input list of segs into left and right lists using one of the segs
// selected as a partition line for the current node.
//
static void R_divideSegs(rpolynode_t *rpn, dsegnode_t **ts, dsegnode_t **rs, dsegnode_t **ls)
{
dynaseg_t *best, *add_to_rs = NULL, *add_to_ls = NULL;
dsegnode_t **tail = ts, **head = ts, *partition;
double pdx, pdy;
dsegnode_t *cur;
// select best seg to use as partition line
partition = R_selectPartition(*ts);
best = rpn->partition = partition->dynaseg;
head = tail = ts;
FindNodeListTail(tail);
RemoveFromNodeList(partition, head, tail);
#ifdef RANGECHECK
// Should not happen.
if(!rpn->partition)
I_Error("R_divideSegs: could not select partition!\n");
#endif
// use the partition line to split any other lines in the list that intersect
// it into left and right halves
pdx = best->psx - best->pex;
pdy = best->psy - best->pey;
// iterate from beginning until the original list is empty
while ((cur = *head))
{
dynaseg_t *seg = cur->dynaseg;
add_to_ls = add_to_rs = NULL;
int val = R_classifyDynaSeg(best, seg, pdx, pdy);
if(val == SPLIT_SR_EL || val == SPLIT_SL_ER)
{
double x, y;
// seg is split by the partition
R_ComputeIntersection(best, seg, &x, &y);
// create a new vertex at the intersection point
dynavertex_t *nv = R_GetFreeDynaVertex();
nv->x = FloatToFixed(x);
nv->y = FloatToFixed(y);
// create a new dynaseg from nv to v2
dynaseg_t *nds = R_CreateDynaSeg(seg, nv, seg->seg.dyv2);
R_setupDSForBSP(nds);
nds->seg.polysector = seg->seg.polysector;
nds->seg.frontsector = seg->seg.frontsector;
nds->seg.backsector = seg->seg.backsector;
nds->seg.length = FloatToFixed(nds->len);
// modify original seg to run from v1 to nv
boolean notmarked = !seg->originalv2;
if(notmarked) // only if not already marked!
R_SetDynaVertexRef(&seg->originalv2, seg->seg.dyv2);
R_SetDynaVertexRef(&seg->seg.dyv2, nv);
R_setupDSForBSP(seg);
seg->seg.length = FloatToFixed(seg->len);
// add the new seg to the current node's ownership list,
// so it can get freed later
M_DLListInsert(&nds->ownerlink.link, (mdllistitem_t **)(&rpn->owned));
nds->ownerlink.link.next = NULL;
nds->ownerlink.dynaseg = nds;
if(notmarked)
{
M_DLListInsert(&seg->alterlink.link, (mdllistitem_t **)(&rpn->altered));
seg->alterlink.link.next = NULL;
seg->alterlink.dynaseg = seg;
}
// classify left or right
if(val == SPLIT_SR_EL)
{
add_to_ls = nds;
add_to_rs = seg;
}
else
{
add_to_ls = seg;
add_to_rs = nds;
}
}
else
{
// Not split; which side?
if(val & CLASSIFY_LEFT)
add_to_ls = seg; // at least one vertex is left, other is left or on
if(val & CLASSIFY_RIGHT)
add_to_rs = seg; // at least one vertex is right, other is right or on
if(val == CLASSIFY_ON)
{
// We know the segs are parallel or nearly so; take their dot
// product to determine their relative orientation
if((seg->psx - seg->pex) * pdx + (seg->psy - seg->pey) * pdy < 0.0)
add_to_ls = seg;
else
add_to_rs = seg;
}
}
// add to right side?
if(add_to_rs)
{
//CONS_Printf("Added %p into right side\n", cur);
if (add_to_rs == seg)
RemoveFromNodeList(cur, head, tail);
InsertIntoNodeList(rs, add_to_rs);
}
// add to left side?
if(add_to_ls)
{
//CONS_Printf("Added %p into left side\n", cur);
if (add_to_ls == seg)
RemoveFromNodeList(cur, head, tail);
InsertIntoNodeList(ls, add_to_ls);
}
}
FindNodeListHead(rs);
FindNodeListHead(ls);
}
//
// R_createNode
//
// Primary recursive node building routine. Partition the segs using one of the
// segs as the partition line, then recurse into the back and front spaces until
// there are no segs left to classify.
//
// A tree of rpolynode instances is returned. NULL is returned in the terminal
// case where there are no segs left to classify.
//
static rpolynode_t *R_createNode(dsegnode_t **ts)
{
dsegnode_t *rights = NULL;
dsegnode_t *lefts = NULL;
if(!*ts)
return NULL; // terminal case: empty list
rpolynode_t *rpn = R_GetFreePolyNode();
// divide the segs into two lists
R_divideSegs(rpn, ts, &rights, &lefts);
// recurse into right space
rpn->children[0] = R_createNode(&rights);
// recurse into left space
rpn->children[1] = R_createNode(&lefts);
return rpn;
}
//=============================================================================
//
// Transform subsector fragments
//
//
// R_collapseFragmentsToDSList
//
// Take a subsector and turn its list of rpolyobj fragments into a flat list of
// dynasegs linked by their bsplinks. The dynasegs' BSP-related fields will also
// be initialized. The result is suitable for input to R_BuildDynaBSP.
//
static boolean R_collapseFragmentsToDSList(const subsector_t *subsec, dsegnode_t **list)
{
rpolyobj_t *fragment = subsec->renderPolyList;
// Nothing to do? (We shouldn't really be called in that case, but, hey...)
if(!fragment)
return false;
while(fragment)
{
dynaseg_t *ds = fragment->dynaSegs;
while(ds)
{
R_setupDSForBSP(ds);
InsertIntoNodeList(list, ds);
// NB: fragment links are not disturbed by this process.
ds = ds->subnext;
}
fragment = (rpolyobj_t *)(fragment->link.next);
}
FindNodeListHead(list);
return (*list != NULL);
}
//=============================================================================
//
// Freeing
//
// Almost as much work as building it was :P
//
//
// R_returnOwnedList
//
// Return all dynasegs on a node's "owned" list. These are dynasegs that were
// created during the splitting process and are not referenced by rpolyobj_t
// instances.
//
static void R_returnOwnedList(rpolynode_t *node)
{
dseglink_t *dsl = node->owned;
while(dsl)
{
dseglink_t *next = (dseglink_t *)(dsl->link.next);
dynaseg_t *ds = dsl->dynaseg;
R_FreeDynaSeg(ds);
dsl = next;
}
// Now also fix altered polyobject dynasegs
dsl = node->altered;
while(dsl)
{
dseglink_t *next = (dseglink_t *)(dsl->link.next);
dynaseg_t *ds = dsl->dynaseg;
R_SetDynaVertexRef(&ds->seg.dyv2, ds->originalv2);
R_FreeDynaVertex(&ds->originalv2);
P_CalcDynaSegLength(ds);
M_DLListRemove(&dsl->link);
dsl = next;
}
}
//
// R_freeTreeRecursive
//
// Recursively free the BSP tree nodes.
//
static void R_freeTreeRecursive(rpolynode_t *root)
{
if(!root)
return;
// free right and left sides
R_freeTreeRecursive(root->children[0]);
R_freeTreeRecursive(root->children[1]);
// free resources stored in this node
R_returnOwnedList(root);
// return the bsp node
R_FreePolyNode(root);
}
//=============================================================================
//
// External Interface
//
//
// R_BuildDynaBSP
//
// Call to build a dynamic BSP sub-tree for sorting of dynasegs.
//
rpolybsp_t *R_BuildDynaBSP(const subsector_t *subsec)
{
rpolybsp_t *bsp = NULL;
dsegnode_t *segs = NULL;
if(R_collapseFragmentsToDSList(subsec, &segs))
{
bsp = Z_Calloc(sizeof(rpolybsp_t), PU_LEVEL, NULL);
bsp->dirty = false;
bsp->root = R_createNode(&segs);
}
while (segs)
{
dsegnode_t *next = segs->next;
Z_Free(segs);
segs = next;
}
return bsp;
}
//
// R_FreeDynaBSP
//
// Return all resources owned by a dynamic BSP tree.
//
void R_FreeDynaBSP(rpolybsp_t *bsp)
{
R_freeTreeRecursive(bsp->root);
Z_Free(bsp);
}
// EOF

62
src/r_dynabsp.h Normal file
View file

@ -0,0 +1,62 @@
// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// Copyright (C) 2013 James Haley et al.
//
// 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:
// Dynamic BSP sub-trees for dynaseg sorting.
//
//-----------------------------------------------------------------------------
#ifndef R_DYNABSP_H__
#define R_DYNABSP_H__
#include "r_dynseg.h"
typedef struct rpolynode_s
{
dynaseg_t *partition; // partition dynaseg
struct rpolynode_s *children[2]; // child node lists (0=right, 1=left)
dseglink_t *owned; // owned segs created by partition splits
dseglink_t *altered; // polyobject-owned segs altered by partitions.
} rpolynode_t;
typedef struct rpolybsp_s
{
boolean dirty; // needs to be rebuilt if true
rpolynode_t *root; // root of tree
} rpolybsp_t;
rpolybsp_t *R_BuildDynaBSP(const subsector_t *subsec);
void R_FreeDynaBSP(rpolybsp_t *bsp);
//
// R_PointOnDynaSegSide
//
// Returns 0 for front/right, 1 for back/left.
//
static inline int R_PointOnDynaSegSide(const dynaseg_t *ds, float x, float y)
{
return ((ds->pdx * (y - ds->psy)) >= (ds->pdy * (x - ds->psx)));
}
void R_ComputeIntersection(const dynaseg_t *part, const dynaseg_t *seg, double *outx, double *outy);
#endif
// EOF

761
src/r_dynseg.c Normal file
View file

@ -0,0 +1,761 @@
// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// Copyright (C) 2013 James Haley et al.
//
// 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/
//
// Additional terms and conditions compatible with the GPLv3 apply. See the
// file COPYING-EE for details.
//
//-----------------------------------------------------------------------------
//
// DESCRIPTION:
// Dynamic segs for PolyObject re-implementation.
//
//-----------------------------------------------------------------------------
#include "z_zone.h"
#include "i_system.h"
#include "m_bbox.h"
#include "p_maputl.h"
#include "r_main.h"
#include "r_dynseg.h"
#include "r_dynabsp.h"
#include "r_state.h"
//
// dynaseg free list
//
// Let's do as little allocation as possible.
//
static dynaseg_t *dynaSegFreeList;
//
// dynaseg vertex free list
//
static dynavertex_t *dynaVertexFreeList;
//
// rpolyobj_t freelist
//
static rpolyobj_t *freePolyFragments;
//
// Used for dynasegs, not base segs
//
void P_CalcDynaSegLength(dynaseg_t *dynaseg)
{
seg_t *lseg = &dynaseg->seg;
lseg->length = P_SegLength(lseg);
#ifdef HWRENDER
lseg->flength = FixedToFloat(lseg->length);
#endif
}
//
// R_AddDynaSubsec
//
// Keeps track of pointers to subsectors which hold dynasegs in a
// reallocating array, for purposes of later detaching the dynasegs.
// Each polyobject contains its own subsector array.
//
static void R_AddDynaSubsec(subsector_t *ss, polyobj_t *po)
{
int i;
// If the subsector has a BSP tree, it will need to be rebuilt.
if(ss->bsp)
ss->bsp->dirty = true;
// make sure subsector is not already tracked
for(i = 0; i < po->numDSS; ++i)
{
if(po->dynaSubsecs[i] == ss)
return;
}
if(po->numDSS >= po->numDSSAlloc)
{
po->numDSSAlloc = po->numDSSAlloc ? po->numDSSAlloc * 2 : 8;
po->dynaSubsecs = Z_Realloc(po->dynaSubsecs, po->numDSSAlloc * sizeof(subsector_t *), PU_LEVEL, NULL);
}
po->dynaSubsecs[po->numDSS++] = ss;
}
//
// R_GetFreeDynaVertex
//
// Gets a vertex from the free list or allocates a new one.
//
dynavertex_t *R_GetFreeDynaVertex(void)
{
dynavertex_t *ret = NULL;
if(dynaVertexFreeList)
{
ret = dynaVertexFreeList;
dynaVertexFreeList = dynaVertexFreeList->dynanext;
memset(ret, 0, sizeof(dynavertex_t));
}
else
ret = calloc(sizeof(dynavertex_t), 1);
return ret;
}
//
// R_FreeDynaVertex
//
// Puts a dynamic vertex onto the free list, if its refcount becomes zero.
//
void R_FreeDynaVertex(dynavertex_t **vtx)
{
dynavertex_t *v;
if(!*vtx)
return;
v = *vtx;
if(v->refcount > 0)
{
v->refcount--;
if(v->refcount == 0)
{
v->refcount = -1;
v->dynanext = dynaVertexFreeList;
dynaVertexFreeList = v;
}
}
*vtx = NULL;
}
//
// R_SetDynaVertexRef
//
// Safely set a reference to a dynamic vertex, maintaining the reference count.
// Do not assign dynavertex pointers without using this routine! Note that if
// *target already points to a vertex, that vertex WILL be freed if its ref
// count reaches zero.
//
void R_SetDynaVertexRef(dynavertex_t **target, dynavertex_t *vtx)
{
if(*target)
R_FreeDynaVertex(target);
if((*target = vtx))
(*target)->refcount++;
}
//
// R_GetFreeDynaSeg
//
// Gets a dynaseg from the free list or allocates a new one.
//
static dynaseg_t *R_GetFreeDynaSeg(void)
{
dynaseg_t *ret = NULL;
if(dynaSegFreeList)
{
ret = dynaSegFreeList;
dynaSegFreeList = dynaSegFreeList->freenext;
memset(ret, 0, sizeof(dynaseg_t));
}
else
ret = calloc(sizeof(dynaseg_t), 1);
return ret;
}
//
// R_FreeDynaSeg
//
// Puts a dynaseg onto the free list.
//
void R_FreeDynaSeg(dynaseg_t *dseg)
{
R_FreeDynaVertex(&dseg->seg.dyv1);
R_FreeDynaVertex(&dseg->seg.dyv2);
R_FreeDynaVertex(&dseg->originalv2);
M_DLListRemove(&dseg->alterlink.link); // remove it from alterable list
dseg->freenext = dynaSegFreeList;
dynaSegFreeList = dseg;
}
//
// R_GetFreeRPolyObj
//
// Gets an rpolyobj_t from the free list or creates a new one.
//
static rpolyobj_t *R_GetFreeRPolyObj(void)
{
rpolyobj_t *ret = NULL;
if(freePolyFragments)
{
ret = freePolyFragments;
freePolyFragments = freePolyFragments->freenext;
memset(ret, 0, sizeof(rpolyobj_t));
}
else
ret = calloc(sizeof(rpolyobj_t), 1);
return ret;
}
//
// R_FreeRPolyObj
//
// Puts an rpolyobj_t on the freelist.
//
static void R_FreeRPolyObj(rpolyobj_t *rpo)
{
rpo->freenext = freePolyFragments;
freePolyFragments = rpo;
}
//
// R_FindFragment
//
// Looks in the given subsector for a polyobject fragment corresponding
// to the given polyobject. If one is not found, then a new one is created
// and returned.
//
static rpolyobj_t *R_FindFragment(subsector_t *ss, polyobj_t *po)
{
rpolyobj_t *link = ss->renderPolyList;
rpolyobj_t *rpo;
while(link)
{
if(link->polyobj == po)
return link;
link = (rpolyobj_t *)(link->link.next);
}
// there is not one, so create a new one and link it in
rpo = R_GetFreeRPolyObj();
rpo->polyobj = po;
M_DLListInsert(&rpo->link, (mdllistitem_t **)(void *)(&ss->renderPolyList));
return rpo;
}
//
// Calculates dynaseg offset using the originating seg's dynavertices.
//
static void R_calcDynaSegOffset(dynaseg_t *dynaseg, int side)
{
fixed_t dx = (side ? dynaseg->linev2->x : dynaseg->linev1->x) - dynaseg->seg.v1->x;
fixed_t dy = (side ? dynaseg->linev2->y : dynaseg->linev1->y) - dynaseg->seg.v1->y;
dynaseg->seg.offset = FixedHypot(dx>>1, dy>>1)<<1;
}
//
// R_CreateDynaSeg
//
// Gets a new dynaseg and initializes it with all needed information.
//
dynaseg_t *R_CreateDynaSeg(const dynaseg_t *proto, dynavertex_t *v1, dynavertex_t *v2)
{
dynaseg_t *ret = R_GetFreeDynaSeg();
// properties inherited from prototype seg
ret->polyobj = proto->polyobj;
ret->seg.linedef = proto->seg.linedef;
ret->seg.sidedef = proto->seg.sidedef;
ret->seg.side = proto->seg.side;
ret->linev1 = proto->linev1;
ret->linev2 = proto->linev2;
// vertices
R_SetDynaVertexRef(&ret->seg.dyv1, v1);
R_SetDynaVertexRef(&ret->seg.dyv2, v2);
// calculate texture offset
R_calcDynaSegOffset(ret, ret->seg.side);
return ret;
}
//
// R_IntersectPoint
//
// Finds the point where a node line crosses a seg.
//
static boolean R_IntersectPoint(const seg_t *lseg, const node_t *node, dynavertex_t *nv)
{
// get the fnode for the node
fnode_t *bsp = &fnodes[node - nodes];
double a1 = FixedToFloat(lseg->v2->y) - FixedToFloat(lseg->v1->y);
double b1 = FixedToFloat(lseg->v1->x) - FixedToFloat(lseg->v2->x);
double c1 = FixedToFloat(lseg->v2->x) * FixedToFloat(lseg->v1->y) - FixedToFloat(lseg->v1->x) * FixedToFloat(lseg->v2->y);
// haleyjd 05/13/09: massive optimization
double a2 = -bsp->a;
double b2 = -bsp->b;
double c2 = -bsp->c;
double d = a1 * b2 - a2 * b1;
float fx, fy;
// lines are parallel?? shouldn't be.
// FIXME: could this occur due to roundoff error in R_PointOnSide?
// Guess we'll find out the hard way ;)
// If so I'll need my own R_PointOnSide routine with some
// epsilon values.
if(d == 0.0)
return false;
fx = (float)((b1 * c2 - b2 * c1) / d);
fy = (float)((a2 * c1 - a1 * c2) / d);
// set fixed-point coordinates
nv->x = FloatToFixed(fx);
nv->y = FloatToFixed(fy);
return true;
}
//
// R_PartitionDistance
//
// This routine uses the general line equation, whose coefficients are now
// precalculated in the BSP nodes, to determine the distance of the point
// from the partition line. If the distance is too small, we may decide to
// change our idea of sidedness.
//
static inline double R_PartitionDistance(double x, double y, const fnode_t *node)
{
return fabs((node->a * x + node->b * y + node->c) / node->len);
}
#define DS_EPSILON 0.3125
//
// Checks if seg is on top of a partition line
//
static boolean R_segIsOnPartition(const seg_t *seg, const subsector_t *frontss)
{
const line_t *line;
float midpx, midpy;
int sign;
if(seg->backsector)
return true;
line = seg->linedef;
sign = line->frontsector == seg->frontsector ? 1 : -1;
midpx = (float)((FixedToFloat(seg->v1->x) + FixedToFloat(seg->v2->x)) / 2 - line->nx * DS_EPSILON * sign);
midpy = (float)((FixedToFloat(seg->v1->y) + FixedToFloat(seg->v2->y)) / 2 - line->ny * DS_EPSILON * sign);
return (R_PointInSubsector(FloatToFixed(midpx), FloatToFixed(midpy)) != frontss);
}
//
// Checks the subsector for any wall segs which should cut or totally remove dseg.
// Necessary to avoid polyobject bleeding. Returns true if entire dynaseg is gone.
//
static boolean R_cutByWallSegs(dynaseg_t *dseg, dynaseg_t *backdseg, const subsector_t *ss)
{
INT32 i;
// The dynaseg must be in front of all wall segs. Otherwise, it's considered
// hidden behind walls.
seg_t *lseg = &dseg->seg;
dseg->psx = FixedToFloat(lseg->v1->x);
dseg->psy = FixedToFloat(lseg->v1->y);
dseg->pex = FixedToFloat(lseg->v2->x);
dseg->pey = FixedToFloat(lseg->v2->y);
// Fast access to delta x, delta y
dseg->pdx = dseg->pex - dseg->psx;
dseg->pdy = dseg->pey - dseg->psy;
for(i = 0; i < ss->numlines; ++i)
{
const seg_t *wall = &segs[ss->firstline + i];
const vertex_t *v1 = wall->v1;
const vertex_t *v2 = wall->v2;
const divline_t walldl = { v1->x, v1->y, v2->x - v1->x, v2->y - v1->y };
int side_v1, side_v2;
dynaseg_t part; // this shall be the wall
double vx, vy;
dynavertex_t *nv;
if(R_segIsOnPartition(wall, ss))
continue; // only check 1-sided lines
side_v1 = P_PointOnDivlineSidePrecise(lseg->v1->x, lseg->v1->y, &walldl);
side_v2 = P_PointOnDivlineSidePrecise(lseg->v2->x, lseg->v2->y, &walldl);
if(side_v1 == 0 && side_v2 == 0)
continue; // this one is fine.
if(side_v1 == 1 && side_v2 == 1)
return true; // totally occluded by one
// We have a real intersection: cut it now.
part.psx = FixedToFloat(wall->v1->x);
part.psy = FixedToFloat(wall->v1->y);
part.pex = FixedToFloat(wall->v2->x);
part.pey = FixedToFloat(wall->v2->y);
R_ComputeIntersection(&part, dseg, &vx, &vy);
nv = R_GetFreeDynaVertex();
nv->x = FloatToFixed(vx);
nv->y = FloatToFixed(vy);
if(side_v1 == 0)
{
R_SetDynaVertexRef(&lseg->dyv2, nv);
if(backdseg)
{
R_SetDynaVertexRef(&backdseg->seg.dyv1, nv);
R_calcDynaSegOffset(backdseg, 1);
}
}
else
{
R_SetDynaVertexRef(&lseg->dyv1, nv);
R_calcDynaSegOffset(dseg, 0); // also need to update this
if(backdseg)
R_SetDynaVertexRef(&backdseg->seg.dyv2, nv);
}
// Keep looking for other intersectors
}
return false; // all are in front. So return.
}
//
// R_SplitLine
//
// Given a single dynaseg representing the full length of a linedef, generates a
// set of dynasegs by recursively splitting the line through the BSP tree.
// Also does the same for a back dynaseg for 2-sided lines.
//
static void R_SplitLine(dynaseg_t *dseg, dynaseg_t *backdseg, int bspnum)
{
int num;
rpolyobj_t *fragment;
while(!(bspnum & NF_SUBSECTOR))
{
const node_t *bsp = &nodes[bspnum];
const fnode_t *fnode = &fnodes[bspnum];
const seg_t *lseg = &dseg->seg;
// test vertices against node line
int side_v1 = R_PointOnSide(lseg->v1->x, lseg->v1->y, bsp);
int side_v2 = R_PointOnSide(lseg->v2->x, lseg->v2->y, bsp);
// ioanch 20160226: fix the polyobject visual clipping bug
M_AddToBox(bsp->bbox[side_v1], lseg->v1->x, lseg->v1->y);
M_AddToBox(bsp->bbox[side_v2], lseg->v2->x, lseg->v2->y);
// get distance of vertices from partition line
double dist_v1 = R_PartitionDistance(FixedToFloat(lseg->v1->x), FixedToFloat(lseg->v1->y), fnode);
double dist_v2 = R_PartitionDistance(FixedToFloat(lseg->v2->x), FixedToFloat(lseg->v2->y), fnode);
// If the distances are less than epsilon, consider the points as being
// on the same side as the polyobj origin. Why? People like to build
// polyobject doors flush with their door tracks. This breaks using the
// usual assumptions.
if(dist_v1 <= DS_EPSILON)
{
if(dist_v2 <= DS_EPSILON)
{
// both vertices are within epsilon distance; classify the seg
// with respect to the polyobject center point
side_v1 = side_v2 = R_PointOnSide(dseg->polyobj->centerPt.x, dseg->polyobj->centerPt.y, bsp);
}
else
side_v1 = side_v2; // v1 is very close; classify as v2 side
}
else if(dist_v2 <= DS_EPSILON)
{
side_v2 = side_v1; // v2 is very close; classify as v1 side
}
if(side_v1 != side_v2)
{
// the partition line crosses this seg, so we must split it.
dynavertex_t *nv = R_GetFreeDynaVertex();
dynaseg_t *nds;
if(R_IntersectPoint(lseg, bsp, nv))
{
dynaseg_t *backnds;
// ioanch 20160722: fix the polyobject visual clipping bug (more needed)
M_AddToBox(bsp->bbox[0], nv->x, nv->y);
M_AddToBox(bsp->bbox[1], nv->x, nv->y);
// create new dynaseg from nv to seg->v2
nds = R_CreateDynaSeg(dseg, nv, lseg->dyv2);
// alter current seg to run from seg->v1 to nv
R_SetDynaVertexRef(&lseg->dyv2, nv);
if(backdseg)
{
backnds = R_CreateDynaSeg(backdseg, backdseg->seg.dyv1, nv);
R_SetDynaVertexRef(&backdseg->seg.dyv1, nv);
R_calcDynaSegOffset(backdseg, 1);
}
else
backnds = NULL;
// recurse to split v2 side
R_SplitLine(nds, backnds, bsp->children[side_v2]);
}
else
{
// Classification failed (this really should not happen, but, math
// on computers is not ideal...). Return the dynavertex and do
// nothing here; the seg will be classified on v1 side for lack of
// anything better to do with it.
R_FreeDynaVertex(&nv);
}
}
// continue on v1 side
bspnum = bsp->children[side_v1];
}
// reached a subsector: attach dynaseg
num = bspnum == -1 ? 0 : bspnum & ~NF_SUBSECTOR;
#ifdef RANGECHECK
if(num >= numsubsectors)
I_Error("R_SplitLine: ss %d with numss = %d\n", num, numsubsectors);
#endif
// First, cut it off by any wall segs
if(R_cutByWallSegs(dseg, backdseg, &subsectors[num]))
{
// If it's occluded by everything, cancel it.
R_FreeDynaSeg(dseg);
if(backdseg)
R_FreeDynaSeg(backdseg);
return;
}
// see if this subsector already has an rpolyobj_t for this polyobject
// if it does not, then one will be created.
fragment = R_FindFragment(&subsectors[num], dseg->polyobj);
// link this seg in at the end of the list in the rpolyobj_t
if(fragment->dynaSegs)
{
dynaseg_t *fdseg = fragment->dynaSegs;
while(fdseg->subnext)
fdseg = fdseg->subnext;
fdseg->subnext = dseg;
}
else
fragment->dynaSegs = dseg;
dseg->subnext = backdseg;
// 05/13/09: calculate seg length for SoM
P_CalcDynaSegLength(dseg);
if(backdseg)
backdseg->seg.length = dseg->seg.length;
// 07/15/09: rendering consistency - set frontsector/backsector here
dseg->seg.polysector = subsectors[num].sector;
dseg->seg.frontsector = dseg->seg.linedef->frontsector;
// 10/30/09: only set backsector if line is 2S
if(dseg->seg.linedef->backsector)
dseg->seg.backsector = dseg->seg.linedef->backsector;
else
dseg->seg.backsector = NULL;
if(backdseg)
{
backdseg->seg.polysector = subsectors[num].sector;
backdseg->seg.frontsector = dseg->seg.linedef->frontsector;
backdseg->seg.backsector = dseg->seg.linedef->backsector;
}
// add the subsector if it hasn't been added already
R_AddDynaSubsec(&subsectors[num], dseg->polyobj);
}
//
// R_AttachPolyObject
//
// Generates dynamic segs for a single polyobject.
//
void R_AttachPolyObject(polyobj_t *poly)
{
size_t i;
// iterate on the polyobject lines array
for(i = 0; i < poly->numLines; ++i)
{
line_t *line = poly->lines[i];
side_t *side = &sides[line->sidenum[0]];
dynaseg_t *backdseg;
// create initial dseg representing the entire linedef
dynaseg_t *idseg = R_GetFreeDynaSeg();
dynavertex_t *v1 = R_GetFreeDynaVertex();
dynavertex_t *v2 = R_GetFreeDynaVertex();
memcpy(v1, line->v1, sizeof(vertex_t));
memcpy(v2, line->v2, sizeof(vertex_t));
idseg->polyobj = poly;
idseg->seg.linedef = line;
idseg->seg.sidedef = side;
R_SetDynaVertexRef(&idseg->seg.dyv1, v1);
R_SetDynaVertexRef(&idseg->seg.dyv2, v2);
idseg->linev1 = line->v1;
idseg->linev2 = line->v2;
// create backside dynaseg now
if (!(poly->flags & POF_ONESIDE))
{
backdseg = R_GetFreeDynaSeg();
backdseg->polyobj = poly;
backdseg->seg.side = 1;
backdseg->seg.linedef = line;
backdseg->seg.sidedef = side;
R_SetDynaVertexRef(&backdseg->seg.dyv1, v2);
R_SetDynaVertexRef(&backdseg->seg.dyv2, v1);
backdseg->linev1 = line->v1;
backdseg->linev2 = line->v2;
}
else
backdseg = NULL;
// Split seg into BSP tree to generate more dynasegs;
// The dynasegs are stored in the subsectors in which they finally end up.
R_SplitLine(idseg, backdseg, numnodes - 1);
}
poly->attached = true;
}
//
// R_DetachPolyObject
//
// Removes a polyobject from all subsectors to which it is attached, reclaiming
// all dynasegs, vertices, and rpolyobj_t fragment objects associated with the
// given polyobject.
//
void R_DetachPolyObject(polyobj_t *poly)
{
int i;
// no dynaseg-containing subsecs?
if(!poly->dynaSubsecs || !poly->numDSS)
{
poly->attached = false;
return;
}
// iterate over stored subsector pointers
for(i = 0; i < poly->numDSS; ++i)
{
subsector_t *ss = poly->dynaSubsecs[i];
rpolyobj_t *link = ss->renderPolyList;
rpolyobj_t *next;
// mark BSPs dirty
if(ss->bsp)
ss->bsp->dirty = true;
// iterate on subsector rpolyobj_t lists
while(link)
{
rpolyobj_t *rpo = link;
next = (rpolyobj_t *)(rpo->link.next);
if(rpo->polyobj == poly)
{
// iterate on segs in rpolyobj_t
while(rpo->dynaSegs)
{
dynaseg_t *ds = rpo->dynaSegs;
dynaseg_t *nextds = ds->subnext;
// free dynamic vertices
// put this dynaseg on the free list
R_FreeDynaSeg(ds);
rpo->dynaSegs = nextds;
}
// unlink this rpolyobj_t
M_DLListRemove(&link->link);
// put it on the freelist
R_FreeRPolyObj(rpo);
}
link = next;
}
// no longer tracking this subsector
poly->dynaSubsecs[i] = NULL;
}
// no longer tracking any subsectors
poly->numDSS = 0;
poly->attached = false;
}
//
// R_ClearDynaSegs
//
// Call at the end of a level to clear all dynasegs.
//
// If this were not done, all dynasegs, their vertices, and polyobj fragments
// would be lost.
//
void R_ClearDynaSegs(void)
{
size_t i;
for(i = 0; i < (unsigned)numPolyObjects; i++)
R_DetachPolyObject(&PolyObjects[i]);
for(i = 0; i < numsubsectors; i++)
{
if(subsectors[i].bsp)
R_FreeDynaBSP(subsectors[i].bsp);
}
}
// EOF

117
src/r_dynseg.h Normal file
View file

@ -0,0 +1,117 @@
// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// Copyright (C) 2013 James Haley et al.
//
// 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/
//
// Additional terms and conditions compatible with the GPLv3 apply. See the
// file COPYING-EE for details.
//
//-----------------------------------------------------------------------------
//
// DESCRIPTION:
// Dynamic segs for PolyObject re-implementation.
//
//-----------------------------------------------------------------------------
#ifndef R_DYNSEG_H__
#define R_DYNSEG_H__
#include "r_defs.h"
#include "m_dllist.h"
#include "p_polyobj.h"
typedef struct dseglink_s
{
mdllistitem_t link;
struct dynaseg_s *dynaseg;
} dseglink_t;
typedef struct dynavertex_s // : vertex_t
{
fixed_t x, y;
boolean floorzset, ceilingzset;
fixed_t floorz, ceilingz;
struct dynavertex_s *dynanext;
int refcount;
} dynavertex_t;
//
// dynaseg
//
typedef struct dynaseg_s
{
seg_t seg; // a dynaseg is a seg, after all ;)
dynavertex_t *originalv2; // reference to original v2 before a split
vertex_t *linev1, *linev2; // dynavertices belonging to the endpoint segs
struct dynaseg_s *subnext; // next dynaseg in fragment
struct dynaseg_s *freenext; // next dynaseg on freelist
polyobj_t *polyobj; // polyobject
dseglink_t ownerlink; // link for owning node chain
dseglink_t alterlink; // link for non-dynaBSP segs changed by dynaBSP
float prevlen, prevofs; // for interpolation (keep them out of seg_t)
// properties needed for efficiency in the BSP builder
double psx, psy, pex, pey; // end points
double pdx, pdy; // delta x, delta y
double ptmp; // general line coefficient 'c'
double len; // length
} dynaseg_t;
// Replaced dseglist_t with a different linked list implementation.
typedef struct dsegnode_s
{
dynaseg_t *dynaseg;
struct dsegnode_s *prev, *next;
} dsegnode_t;
//
// rpolyobj_t
//
// Subsectors now hold pointers to rpolyobj_t's instead of to polyobj_t's.
// An rpolyobj_t is a set of dynasegs belonging to a single polyobject.
// It is necessary to keep dynasegs belonging to different polyobjects
// separate from each other so that the renderer can continue to efficiently
// support multiple polyobjects per subsector (we do not want to do a z-sort
// on every single dynaseg, as that is significant unnecessary overhead).
//
typedef struct rpolyobj_s
{
mdllistitem_t link; // for subsector links; must be first
dynaseg_t *dynaSegs; // list of dynasegs
polyobj_t *polyobj; // polyobject of which this rpolyobj_t is a fragment
struct rpolyobj_s *freenext; // next on freelist
} rpolyobj_t;
void P_CalcDynaSegLength(dynaseg_t *lseg);
dynavertex_t *R_GetFreeDynaVertex(void);
void R_FreeDynaVertex(dynavertex_t **vtx);
void R_SetDynaVertexRef(dynavertex_t **target, dynavertex_t *vtx);
dynaseg_t *R_CreateDynaSeg(const dynaseg_t *proto, dynavertex_t *v1, dynavertex_t *v2);
void R_FreeDynaSeg(dynaseg_t *dseg);
void R_AttachPolyObject(polyobj_t *poly);
void R_DetachPolyObject(polyobj_t *poly);
void R_ClearDynaSegs(void);
#endif
// EOF

View file

@ -140,13 +140,9 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
INT64 overflow_test;
INT32 range;
// Calculate light table.
// Use different light tables
// for horizontal / vertical / diagonal. Diagonal?
// OPTIMIZE: get rid of LIGHTSEGSHIFT globally
curline = ds->curline;
frontsector = curline->frontsector;
frontsector = curline->polyseg ? curline->polysector : curline->frontsector;
backsector = curline->backsector;
texnum = R_GetTextureNum(curline->sidedef->midtexture);
windowbottom = windowtop = sprbotscreen = INT32_MAX;
@ -159,7 +155,6 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
{
dc_transmap = R_GetTranslucencyTable(R_GetLinedefTransTable(ldef->alpha));
colfunc = colfuncs[COLDRAWFUNC_FUZZY];
}
else if (ldef->special == 909)
{
@ -229,8 +224,6 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
rlight->height = (centeryfrac) - FixedMul(leftheight , ds->scale1);
rlight->heightstep = (centeryfrac) - FixedMul(rightheight, ds->scale2);
rlight->heightstep = (rlight->heightstep-rlight->height)/(range);
//if (x1 > ds->x1)
//rlight->height -= (x1 - ds->x1)*rlight->heightstep;
rlight->startheight = rlight->height; // keep starting value here to reset for each repeat
rlight->lightlevel = *light->lightlevel;
rlight->extra_colormap = *light->extra_colormap;
@ -255,6 +248,10 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
}
else
{
// Calculate light table.
// Use different light tables
// for horizontal / vertical / diagonal. Diagonal?
// OPTIMIZE: get rid of LIGHTSEGSHIFT globally
if ((colfunc != colfuncs[COLDRAWFUNC_FUZZY])
|| (frontsector->extra_colormap && (frontsector->extra_colormap->flags & CMF_FOG)))
lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT);

View file

@ -72,6 +72,7 @@ extern subsector_t *subsectors;
extern size_t numnodes;
extern node_t *nodes;
extern fnode_t *fnodes;
extern size_t numlines;
extern line_t *lines;