diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 41e2c56939dbbf155416d67a20d3cc01bdff114a..48e66e1148ba0a5ff18ddd06af0730f1a2394641 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -2714,6 +2714,8 @@ static void HWR_AddLine(seg_t * line)
 	static sector_t tempsec;
 
 	fixed_t v1x, v1y, v2x, v2y; // the seg's vertexes as fixed_t
+	if (line->glseg)
+		return;
 #ifdef POLYOBJECTS
 	if (line->polyseg && !(line->polyseg->flags & POF_RENDERSIDES))
 		return;
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index c37926adff675a0d396dce7ba868ae61f4bbe6e4..4709e5e6fce4c8245b6da856426a0a9098ecb0c6 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -400,6 +400,8 @@ static void Polyobj_findSegs(polyobj_t *po, seg_t *seg)
 		// Find backfacings
 		for (s = 0;  s < numsegs; s++)
 		{
+			if (segs[s].glseg)
+				continue;
 			if (segs[s].linedef == seg->linedef
 				&& segs[s].side == 1)
 			{
@@ -436,6 +438,8 @@ newseg:
 	// seg's ending vertex.
 	for (i = 0; i < numsegs; ++i)
 	{
+		if (segs[i].glseg)
+			continue;
 		if (segs[i].side != 0) // needs to be frontfacing
 			continue;
 		if (segs[i].v1->x == seg->v2->x && segs[i].v1->y == seg->v2->y)
@@ -460,6 +464,9 @@ newseg:
 				// Find backfacings
 				for (q = 0;  q < numsegs; q++)
 				{
+					if (segs[q].glseg)
+						continue;
+
 					if (segs[q].linedef == segs[i].linedef
 						&& segs[q].side == 1)
 					{
@@ -606,6 +613,9 @@ static void Polyobj_spawnPolyObj(INT32 num, mobj_t *spawnSpot, INT32 id)
 		INT32 poflags = POF_SOLID|POF_TESTHEIGHT|POF_RENDERSIDES;
 		INT32 parentID = 0, potrans = 0;
 
+		if (seg->glseg)
+			continue;
+
 		if (seg->side != 0) // needs to be frontfacing
 			continue;
 
diff --git a/src/p_setup.c b/src/p_setup.c
index ea61a2ec78a3580e7d0529ae8ba17529d8657b55..b74bf83a2398ea3725a7e758a724fe6909ca79f4 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -1456,31 +1456,312 @@ static void P_LoadSegs(UINT8 *data)
 	}
 }
 
+// Auxiliary function: Shrink node ID from 32-bit to 16-bit.
+static UINT16 ShrinkNodeID(UINT32 x) {
+	UINT16 mask = (x >> 16) & 0xC000;
+	UINT16 result = x;
+	return result | mask;
+}
+
+typedef enum {
+	NT_DOOM,
+	NT_XNOD,
+	NT_ZNOD,
+	NT_XGLN,
+	NT_ZGLN,
+	NT_XGL2,
+	NT_ZGL2,
+	NT_XGL3,
+	NT_ZGL3,
+	NT_UNSUPPORTED
+} nodetype_t;
+
 static void P_LoadMapBSP(const virtres_t *virt)
 {
 	virtlump_t* virtssectors = vres_Find(virt, "SSECTORS");
 	virtlump_t* virtsegs     = vres_Find(virt, "SEGS");
 	virtlump_t* virtnodes    = vres_Find(virt, "NODES");
 
-	numsubsectors = virtssectors->size / sizeof(mapsubsector_t);
-	numnodes      = virtnodes->size    / sizeof(mapnode_t);
-	numsegs       = virtsegs->size     / sizeof(mapseg_t);
-
-	if (numsubsectors <= 0)
-		I_Error("Level has no subsectors (did you forget to run it through a nodesbuilder?)");
-	if (numnodes <= 0)
-		I_Error("Level has no nodes");
-	if (numsegs <= 0)
-		I_Error("Level has no segs");
-
-	subsectors = Z_Calloc(numsubsectors * sizeof(*subsectors), PU_LEVEL, NULL);
-	nodes      = Z_Calloc(numnodes * sizeof(*nodes), PU_LEVEL, NULL);
-	segs       = Z_Calloc(numsegs * sizeof(*segs), PU_LEVEL, NULL);
-
-	// Nodes
-	P_LoadSubsectors(virtssectors->data);
-	P_LoadNodes(virtnodes->data);
-	P_LoadSegs(virtsegs->data);
+	nodetype_t nodetype = NT_UNSUPPORTED;
+
+	// Find out the BSP format.
+	if (vres_Find(virt, "TEXTMAP"))
+	{
+		virtnodes = vres_Find(virt, "ZNODES");
+		if (!memcmp(virtnodes->data, "XGLN", 4))
+			nodetype = NT_XGLN;
+		else if (!memcmp(virtnodes->data, "XGL3", 4))
+			nodetype = NT_XGL3;
+	}
+	else
+	{
+		if (!virtsegs || !virtsegs->size)
+		{
+			// Possibly ZDoom extended nodes: SSECTORS is empty, NODES has a signature.
+			if (!virtssectors || !virtssectors->size)
+			{
+				if (!memcmp(virtnodes->data, "XNOD", 4))
+					nodetype = NT_XNOD;
+				else if (!memcmp(virtnodes->data, "ZNOD", 4)) // Compressed variant.
+					nodetype = NT_ZNOD;
+			}
+			// Possibly GL nodes: NODES ignored, SSECTORS takes precedence as nodes lump, (It is confusing yeah) and has a signature.
+			else
+			{
+				if (!memcmp(virtssectors->data, "XGLN", 4))
+				{
+					virtnodes = virtssectors;
+					nodetype = NT_XGLN;
+				}
+				else if (!memcmp(virtssectors->data, "ZGLN", 4)) // Compressed variant.
+				{
+					virtnodes = virtssectors;
+					nodetype = NT_ZGLN;
+				}
+				else if (!memcmp(virtssectors->data, "XGL3", 4)) // Compressed variant.
+				{
+					virtnodes = virtssectors;
+					nodetype = NT_XGL3;
+				}
+			}
+		}
+		else // Traditional map format BSP tree.
+			nodetype = NT_DOOM;
+	}
+
+	switch (nodetype)
+	{
+	case NT_DOOM:
+		numsubsectors = virtssectors->size / sizeof(mapsubsector_t);
+		numnodes      = virtnodes->size    / sizeof(mapnode_t);
+		numsegs       = virtsegs->size     / sizeof(mapseg_t);
+
+		if (numsubsectors <= 0)
+			I_Error("Level has no subsectors (did you forget to run it through a nodesbuilder?)");
+		if (numnodes <= 0)
+			I_Error("Level has no nodes");
+		if (numsegs <= 0)
+			I_Error("Level has no segs");
+
+		subsectors = Z_Calloc(numsubsectors * sizeof(*subsectors), PU_LEVEL, NULL);
+		nodes      = Z_Calloc(numnodes * sizeof(*nodes), PU_LEVEL, NULL);
+		segs       = Z_Calloc(numsegs * sizeof(*segs), PU_LEVEL, NULL);
+
+		P_LoadSubsectors(virtssectors->data);
+		P_LoadNodes(virtnodes->data);
+		P_LoadSegs(virtsegs->data);
+		break;
+
+	case NT_XNOD:
+	case NT_XGLN:
+	case NT_XGL3:
+	{
+		size_t i, j, k;
+		INT16 m;
+		UINT8* data = virtnodes->data + 4;
+
+		/// Extended node formats feature additional vertexes; useful for OpenGL, but totally useless in gamelogic.
+		UINT32 orivtx, xtrvtx;
+		orivtx = READUINT32(data);
+		xtrvtx = READUINT32(data);
+
+		if (numvertexes != orivtx) /// If native vertex count doesn't match node original vertex count, bail out (broken data?).
+		{
+			CONS_Alert(CONS_WARNING, "Vertex count in map data and nodes differ!\n");
+			return;
+		}
+
+		if (xtrvtx) /// If extra vertexes were generated, reallocate the vertex array and fix the pointers.
+		{
+			line_t*	ld		= lines;
+			size_t	oldpos	= (size_t) vertexes;
+			ssize_t	offset;
+			numvertexes+= xtrvtx;
+			vertexes	= Z_Realloc(vertexes, numvertexes * sizeof (*vertexes), PU_LEVEL, NULL);
+			offset		= ((size_t) vertexes) - oldpos;
+
+			for (i = 0, ld = lines; i < numlines; i++, ld++)
+			{
+				ld->v1 = (vertex_t*) ((size_t) ld->v1 + offset);
+				ld->v2 = (vertex_t*) ((size_t) ld->v2 + offset);
+			}
+		}
+
+		// Read vertex data.
+		for (i = orivtx; i < numvertexes; i++)
+		{
+			vertexes[i].x = READFIXED(data);
+			vertexes[i].y = READFIXED(data);
+		}
+
+		// Subsectors
+		numsubsectors	= READUINT32(data);
+		subsectors		= Z_Calloc(numsubsectors * sizeof (*subsectors), PU_LEVEL, NULL);
+
+		for (i = 0; i < numsubsectors; i++)
+			subsectors[i].numlines = READUINT32(data);
+
+		// Segs
+		numsegs		= READUINT32(data);
+		segs		= Z_Calloc(numsegs * sizeof (*segs), PU_LEVEL, NULL);
+
+		for (i = 0, k = 0; i < numsubsectors; i++)
+		{
+			subsectors[i].firstline = k;
+
+			switch (nodetype)
+			{
+			case NT_XGLN:
+				for (m = 0; m < subsectors[i].numlines; m++, k++)
+				{
+					UINT16 linenum;
+					UINT32 vert;
+					vert = READUINT32(data);
+					segs[k].v1 = &vertexes[vert];
+					if (m == 0)
+						segs[k + subsectors[i].numlines - 1].v2 = &vertexes[vert];
+					else
+						segs[k - 1].v2 = segs[k].v1;
+					data += 4;// partner; can be ignored by software renderer;
+					linenum = READUINT16(data);
+					if (linenum == 0xFFFF)
+					{
+						segs[k].glseg = true;
+						segs[k].linedef = NULL;
+					}
+					else
+					{
+						segs[k].glseg = false;
+						segs[k].linedef = &lines[linenum];
+					}
+					segs[k].side = READUINT8(data);
+				}
+				break;
+
+			case NT_XGL3:
+				for (m = 0; m < subsectors[i].numlines; m++, k++)
+				{
+					UINT32 linenum;
+					UINT32 vert;
+					vert = READUINT32(data);
+					segs[k].v1 = &vertexes[vert];
+					if (m == 0)
+						segs[k + subsectors[i].numlines - 1].v2 = &vertexes[vert];
+					else
+						segs[k - 1].v2 = segs[k].v1;
+					data += 4;// partner; can be ignored by software renderer;
+					linenum = READUINT32(data);
+					if (linenum == 0xFFFFFFFF)
+					{
+						segs[k].glseg = true;
+						segs[k].linedef = NULL;
+					}
+					else
+					{
+						segs[k].glseg = false;
+						segs[k].linedef = NULL;
+					}
+					segs[k].side = READUINT8(data);
+				}
+				break;
+
+			case NT_XNOD:
+				for (m = 0; m < subsectors[i].numlines; m++, k++)
+				{
+					segs[k].v1		= &vertexes[READUINT32(data)];
+					segs[k].v2		= &vertexes[READUINT32(data)];
+					segs[k].linedef	= &lines[READUINT16(data)];
+					segs[k].side	= READUINT8(data);
+				}
+				break;
+
+			default:
+				return;
+			}
+		}
+
+		{
+			INT32 side;
+			seg_t *li;
+
+			for (i = 0, li = segs; i < numsegs; i++, li++)
+			{
+				vertex_t *v1 = li->v1;
+				vertex_t *v2 = li->v2;
+				li->angle = R_PointToAngle2(v1->x, v1->y, v2->x, v2->y);
+				li->offset = FixedHypot(v1->x - li->linedef->v1->x, v1->y - li->linedef->v1->y);
+				side = li->side;
+				li->sidedef = &sides[li->linedef->sidenum[side]];
+
+				li->frontsector = sides[li->linedef->sidenum[side]].sector;
+				if (li->linedef->flags & ML_TWOSIDED)
+					li->backsector = sides[li->linedef->sidenum[side^1]].sector;
+				else
+					li->backsector = 0;
+
+				segs[i].numlights = 0;
+				segs[i].rlights = NULL;
+			}
+		}
+
+		// Nodes
+		numnodes = READINT32(data);
+		nodes    = Z_Calloc(numnodes * sizeof (*nodes), PU_LEVEL, NULL);
+		if (nodetype == NT_XGL3)
+		{
+			UINT32 x, y, dx, dy;
+			UINT32 c0, c1;
+			node_t *mn;
+			for (i = 0, mn = nodes; i < numnodes; i++, mn++)
+			{
+				// Splitter.
+				x = READINT32(data);
+				y = READINT32(data);
+				dx = READINT32(data);
+				dy = READINT32(data);
+				mn->x = x;
+				mn->y = y;
+				mn->dx = dx;
+				mn->dy = dy;
+
+				// Bounding boxes and children.
+				for (j = 0; j < 2; j++)
+					for (k = 0; k < 4; k++)
+						mn->bbox[j][k] = READINT16(data)<<FRACBITS;
+				c0 = READUINT32(data);
+				c1 = READUINT32(data);
+				mn->children[0] = ShrinkNodeID(c0); /// \todo Use UINT32 for node children in a future, instead?
+				mn->children[1] = ShrinkNodeID(c1);
+			}
+		}
+		else
+		{
+			UINT32 c0, c1;
+			node_t *mn;
+			for (i = 0, mn = nodes; i < numnodes; i++, mn++)
+			{
+				// Splitter.
+				mn->x = READINT16(data)<<FRACBITS;
+				mn->y = READINT16(data)<<FRACBITS;
+				mn->dx = READINT16(data)<<FRACBITS;
+				mn->dy = READINT16(data)<<FRACBITS;
+				// Bounding boxes and children.
+				for (j = 0; j < 2; j++)
+					for (k = 0; k < 4; k++)
+						mn->bbox[j][k] = READINT16(data)<<FRACBITS;
+				c0 = READUINT32(data);
+				c1 = READUINT32(data);
+				mn->children[0] = ShrinkNodeID(c0); /// \todo Use UINT32 for node children in a future, instead?
+				mn->children[1] = ShrinkNodeID(c1);
+			}
+		}
+	}
+		break;
+	default:
+		CONS_Alert(CONS_WARNING, "Unsupported BSP format detected.\n");
+		return;
+	}
+	return;
 }
 
 // Split from P_LoadBlockMap for convenience
diff --git a/src/r_bsp.c b/src/r_bsp.c
index 51d9bd3fddad73389f6756305a9fe469c76beb69..6b4667aeef2afab999348a74f7a3ba4c8e505a3a 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -1169,9 +1169,11 @@ static void R_Subsector(size_t num)
 	while (count--)
 	{
 //		CONS_Debug(DBG_GAMELOGIC, "Adding normal line %d...(%d)\n", line->linedef-lines, leveltime);
+		if (!line->glseg
 #ifdef POLYOBJECTS
-		if (!line->polyseg) // ignore segs that belong to polyobjects
+		&& !line->polyseg // ignore segs that belong to polyobjects
 #endif
+		)
 		R_AddLine(line);
 		line++;
 		curline = NULL; /* cph 2001/11/18 - must clear curline now we're done with it, so stuff doesn't try using it for other things */
diff --git a/src/r_defs.h b/src/r_defs.h
index 4cb02f4f5c5b05d6ba3ce0f97d3e0eda3535cdc4..ceb694c57a7b0abf0a1bda63759cd95e34e884c4 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -586,6 +586,7 @@ typedef struct seg_s
 	polyobj_t *polyseg;
 	boolean dontrenderme;
 #endif
+	boolean glseg;
 } seg_t;
 
 //
diff --git a/src/r_segs.c b/src/r_segs.c
index 29120ebb89b4b7bac4ca3bdb7de6d16852dafe0a..0fc91a8236e6c16effd4d334aeee96941e6f63ce 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -308,6 +308,10 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	//   for horizontal / vertical / diagonal. Diagonal?
 	// OPTIMIZE: get rid of LIGHTSEGSHIFT globally
 	curline = ds->curline;
+
+	if (curline->glseg)
+		return;
+
 	frontsector = curline->frontsector;
 	backsector = curline->backsector;
 	texnum = R_GetTextureNum(curline->sidedef->midtexture);