From 1fb70c7d121fe05a8b4e30d98ecdd6e2951a4b9d Mon Sep 17 00:00:00 2001
From: Jaime Ita Passos <jp6781615@gmail.com>
Date: Mon, 14 Jun 2021 21:36:32 -0300
Subject: [PATCH] Implement dynasegs, from Eternity

---
 src/CMakeLists.txt     |   4 +
 src/Makefile           |   2 +
 src/hardware/hw_bsp.c  |   2 +-
 src/hardware/hw_main.c |  11 +-
 src/m_dllist.h         |   7 +-
 src/p_maputl.c         |  31 ++
 src/p_maputl.h         |   1 +
 src/p_mobj.c           |   2 +-
 src/p_polyobj.c        |  30 +-
 src/p_polyobj.h        |  16 +-
 src/p_setup.c          |  72 +++-
 src/p_setup.h          |   2 +
 src/p_spec.c           |   2 +-
 src/r_bsp.c            | 133 +++----
 src/r_defs.h           |  36 +-
 src/r_dynabsp.c        | 719 ++++++++++++++++++++++++++++++++++++++
 src/r_dynabsp.h        |  62 ++++
 src/r_dynseg.c         | 761 +++++++++++++++++++++++++++++++++++++++++
 src/r_dynseg.h         | 117 +++++++
 src/r_segs.c           |  13 +-
 src/r_state.h          |   1 +
 21 files changed, 1883 insertions(+), 141 deletions(-)
 create mode 100644 src/r_dynabsp.c
 create mode 100644 src/r_dynabsp.h
 create mode 100644 src/r_dynseg.c
 create mode 100644 src/r_dynseg.h

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 33898c9a17..eadc05a174 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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
diff --git a/src/Makefile b/src/Makefile
index 1d3551ce24..94bfa5326c 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -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   \
diff --git a/src/hardware/hw_bsp.c b/src/hardware/hw_bsp.c
index 4db69ff8b1..9737f28ce0 100644
--- a/src/hardware/hw_bsp.c
+++ b/src/hardware/hw_bsp.c
@@ -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)
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index d413e3bbeb..062eb47223 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -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;
diff --git a/src/m_dllist.h b/src/m_dllist.h
index 65303b4a33..840d2a8035 100644
--- a/src/m_dllist.h
+++ b/src/m_dllist.h
@@ -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
diff --git a/src/p_maputl.c b/src/p_maputl.c
index efcebe7363..29d5d08621 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -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
 //
diff --git a/src/p_maputl.h b/src/p_maputl.h
index cec344d03d..ad573d547b 100644
--- a/src/p_maputl.h
+++ b/src/p_maputl.h
@@ -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);
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 91e0910905..c1f8c2abfd 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -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;
 			}
 		}
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index 6431e46248..54ca7beb3d 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -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
diff --git a/src/p_polyobj.h b/src/p_polyobj.h
index 7c814e0bf1..56eb0c5a10 100644
--- a/src/p_polyobj.h
+++ b/src/p_polyobj.h
@@ -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
 
diff --git a/src/p_setup.c b/src/p_setup.c
index 51d2f474de..50310fb925 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -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();
 
diff --git a/src/p_setup.h b/src/p_setup.h
index 9fa70d516f..0d60c99eb9 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -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);
diff --git a/src/p_spec.c b/src/p_spec.c
index 742fd544bb..d6f75046fc 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -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)
diff --git a/src/r_bsp.c b/src/r_bsp.c
index 5acd4e70c5..f030b46e2b 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -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;
-
-	// 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)
-
-	dist1v1 = vxdist(seg1->v1);
-	dist1v2 = vxdist(seg1->v2);
-	dist2v1 = vxdist(seg2->v1);
-	dist2v2 = vxdist(seg2->v2);
-
-	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;
-		}
+	while(node)
+	{
+		seg_t *seg = &node->partition->seg;
 
-		x1 = near1->x + FixedMul(far1->x-near1->x, delta1);
-		y1 = near1->y + FixedMul(far1->y-near1->y, delta1);
+		// render frontspace
+		int side = R_PointOnDynaSegSide(node->partition, FixedToFloat(viewx), FixedToFloat(viewy));
+		R_renderPolyNode(node->children[side]);
 
-		x2 = near2->x + FixedMul(far2->x-near2->x, delta2);
-		y2 = near2->y + FixedMul(far2->y-near2->y, delta2);
+		// 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);
 
-		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.
+//
+// See r_dynseg.c to see how dynasegs get attached to a subsector in the
+// first place :)
 //
-// haleyjd 02/19/06
-// Adds all segs in all polyobjects in the given subsector.
+// See r_dynabsp.c for rpolybsp generation.
 //
-static void R_AddPolyObjects(subsector_t *sub)
+static void R_addDynaSegs(subsector_t *sub)
 {
-	polyobj_t *po = sub->polyList;
-	size_t i, j;
-
-	numpolys = 0;
+	boolean needbsp = (!sub->bsp || sub->bsp->dirty);
 
-	// 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--)
 	{
diff --git a/src/r_defs.h b/src/r_defs.h
index 1be3a1b8cd..4b4587c659 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -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
diff --git a/src/r_dynabsp.c b/src/r_dynabsp.c
new file mode 100644
index 0000000000..23ccf7674e
--- /dev/null
+++ b/src/r_dynabsp.c
@@ -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
diff --git a/src/r_dynabsp.h b/src/r_dynabsp.h
new file mode 100644
index 0000000000..c4b6156750
--- /dev/null
+++ b/src/r_dynabsp.h
@@ -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
diff --git a/src/r_dynseg.c b/src/r_dynseg.c
new file mode 100644
index 0000000000..a4f5cc4607
--- /dev/null
+++ b/src/r_dynseg.c
@@ -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
diff --git a/src/r_dynseg.h b/src/r_dynseg.h
new file mode 100644
index 0000000000..f0586fb2c4
--- /dev/null
+++ b/src/r_dynseg.h
@@ -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
diff --git a/src/r_segs.c b/src/r_segs.c
index a8c85ec33b..9472291b8d 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -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);
diff --git a/src/r_state.h b/src/r_state.h
index 5a606ed8c9..7f9b46fbbb 100644
--- a/src/r_state.h
+++ b/src/r_state.h
@@ -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;
-- 
GitLab