diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5fadf0b2d..6906bec5b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -679,6 +679,7 @@ add_executable( zdoom WIN32
 	p_effect.cpp
 	p_enemy.cpp
 	p_floor.cpp
+	p_glnodes.cpp
 	p_interaction.cpp
 	p_lights.cpp
 	p_linkedsectors.cpp
diff --git a/src/actor.h b/src/actor.h
index 15439e767..2a8921b56 100644
--- a/src/actor.h
+++ b/src/actor.h
@@ -40,6 +40,7 @@
 #include "r_blend.h"
 #include "s_sound.h"
 
+struct subsector_t;
 //
 // NOTES: AActor
 //
@@ -773,6 +774,7 @@ public:
 	fixed_t			pitch, roll;
 	FBlockNode		*BlockNode;			// links in blocks (if needed)
 	struct sector_t	*Sector;
+	subsector_t *		subsector;
 	fixed_t			floorz, ceilingz;	// closest together of contacted secs
 	fixed_t			dropoffz;		// killough 11/98: the lowest floor over all contacted Sectors.
 
diff --git a/src/am_map.cpp b/src/am_map.cpp
index 9dccdf2c0..63d328c64 100644
--- a/src/am_map.cpp
+++ b/src/am_map.cpp
@@ -36,6 +36,9 @@
 #include "r_translate.h"
 #include "d_event.h"
 #include "gi.h"
+#include "r_bsp.h"
+#include "p_setup.h"
+#include "c_bind.h"
 
 #include "m_cheat.h"
 #include "i_system.h"
@@ -183,6 +186,14 @@ CVAR (Color, am_ovthingcolor_item,		0xe88800,	CVAR_ARCHIVE);
 CVAR (Color, am_ovthingcolor_citem,		0xe88800,	CVAR_ARCHIVE);
 
 
+static int bigstate = 0;
+static bool textured = 1;	// internal toggle for texture mode
+
+CUSTOM_CVAR(Bool, am_textured, false, CVAR_ARCHIVE)
+{
+	textured |= self;
+}
+
 CVAR(Int, am_showsubsector, -1, 0);
 
 
@@ -225,21 +236,6 @@ CUSTOM_CVAR (Int, am_showalllines, -1, 0)	// This is a cheat so don't save it.
 }
 
 
-// drawing stuff
-#define AM_PANDOWNKEY	KEY_DOWNARROW
-#define AM_PANUPKEY		KEY_UPARROW
-#define AM_PANRIGHTKEY	KEY_RIGHTARROW
-#define AM_PANLEFTKEY	KEY_LEFTARROW
-#define AM_ZOOMINKEY	KEY_EQUALS
-#define AM_ZOOMINKEY2	0x4e	// DIK_ADD
-#define AM_ZOOMOUTKEY	KEY_MINUS
-#define AM_ZOOMOUTKEY2	0x4a	// DIK_SUBTRACT
-#define AM_GOBIGKEY		0x0b	// DIK_0
-#define AM_FOLLOWKEY	'f'
-#define AM_GRIDKEY		'g'
-#define AM_MARKKEY		'm'
-#define AM_CLEARMARKKEY	'c'
-
 #define AM_NUMMARKPOINTS 10
 
 // player radius for automap checking
@@ -417,7 +413,6 @@ static int	amclock;
 
 static mpoint_t	m_paninc;		// how far the window pans each tic (map coords)
 static fixed_t	mtof_zoommul;	// how far the window zooms in each tic (map coords)
-static fixed_t	ftom_zoommul;	// how far the window zooms in each tic (fb coords)
 
 static fixed_t	m_x, m_y;		// LL x,y where the window is on the map (map coords)
 static fixed_t	m_x2, m_y2;		// UR x,y where the window is on the map (map coords)
@@ -466,7 +461,69 @@ static void AM_calcMinMaxMtoF();
 void AM_rotatePoint (fixed_t *x, fixed_t *y);
 void AM_rotate (fixed_t *x, fixed_t *y, angle_t an);
 void AM_doFollowPlayer ();
-static void AM_ToggleFollowPlayer();
+
+
+//=============================================================================
+//
+// map functions
+//
+//=============================================================================
+bool AM_addMark ();
+bool AM_clearMarks ();
+void AM_saveScaleAndLoc ();
+void AM_restoreScaleAndLoc ();
+void AM_minOutWindowScale ();
+
+
+CCMD(am_togglefollow)
+{
+	followplayer = !followplayer;
+	f_oldloc.x = FIXED_MAX;
+	Printf ("%s\n", GStrings(followplayer ? "AMSTR_FOLLOWON" : "AMSTR_FOLLOWOFF"));
+}
+
+CCMD(am_togglegrid)
+{
+	grid = !grid;
+	Printf ("%s\n", GStrings(grid ? "AMSTR_GRIDON" : "AMSTR_GRIDOFF"));
+}
+
+CCMD(am_toggletexture)
+{
+	if (am_textured && hasglnodes)
+	{
+		textured = !textured;
+		Printf ("%s\n", GStrings(textured ? "AMSTR_TEXON" : "AMSTR_TEXOFF"));
+	}
+}
+
+CCMD(am_setmark)
+{
+	if (AM_addMark())
+	{
+		Printf ("%s %d\n", GStrings("AMSTR_MARKEDSPOT"), markpointnum);
+	}
+}
+
+CCMD(am_clearmarks)
+{
+	if (AM_clearMarks())
+	{
+		Printf ("%s\n", GStrings("AMSTR_MARKSCLEARED"));
+	}
+}
+
+CCMD(am_gobig)
+{
+	bigstate = !bigstate;
+	if (bigstate)
+	{
+		AM_saveScaleAndLoc();
+		AM_minOutWindowScale();
+	}
+	else
+		AM_restoreScaleAndLoc();
+}
 
 // Calculates the slope and slope according to the x-axis of a line
 // segment in map coordinates (with the upright y-axis n' all) so
@@ -775,11 +832,19 @@ void AM_initVariables ()
 
 	automapactive = true;
 
+	// Reset AM buttons
+	Button_AM_PanLeft.Reset();
+	Button_AM_PanRight.Reset();
+	Button_AM_PanUp.Reset();
+	Button_AM_PanDown.Reset();
+	Button_AM_ZoomIn.Reset();
+	Button_AM_ZoomOut.Reset();
+
+
 	f_oldloc.x = FIXED_MAX;
 	amclock = 0;
 
 	m_paninc.x = m_paninc.y = 0;
-	ftom_zoommul = MAPUNIT;
 	mtof_zoommul = MAPUNIT;
 
 	m_w = FTOM(SCREENWIDTH);
@@ -1158,127 +1223,28 @@ void AM_ToggleMap ()
 //
 //=============================================================================
 
-bool AM_Responder (event_t *ev)
+bool AM_Responder (event_t *ev, bool last)
 {
-	bool rc;
-	static int cheatstate = 0;
-	static int bigstate = 0;
-
-	rc = false;
-
-	if (automapactive && ev->type == EV_KeyDown)
+	if (automapactive && (ev->type == EV_KeyDown || ev->type == EV_KeyUp))
 	{
-		rc = true;
-		switch (ev->data1)
+		if (followplayer)
 		{
-		case AM_PANRIGHTKEY: // pan right
-			if (!followplayer)
-				m_paninc.x = FTOM(F_PANINC);
-			else
-				rc = false;
-			break;
-		case AM_PANLEFTKEY: // pan left
-			if (!followplayer)
-				m_paninc.x = -FTOM(F_PANINC);
-			else
-				rc = false;
-			break;
-		case AM_PANUPKEY: // pan up
-			if (!followplayer)
-				m_paninc.y = FTOM(F_PANINC);
-			else
-				rc = false;
-			break;
-		case AM_PANDOWNKEY: // pan down
-			if (!followplayer)
-				m_paninc.y = -FTOM(F_PANINC);
-			else
-				rc = false;
-			break;
-		case AM_ZOOMOUTKEY: // zoom out
-		case AM_ZOOMOUTKEY2:
-			mtof_zoommul = M_ZOOMOUT;
-			ftom_zoommul = M_ZOOMIN;
-			break;
-		case AM_ZOOMINKEY: // zoom in
-		case AM_ZOOMINKEY2:
-			mtof_zoommul = M_ZOOMIN;
-			ftom_zoommul = M_ZOOMOUT;
-			break;
-		case AM_GOBIGKEY:
-			bigstate = !bigstate;
-			if (bigstate)
-			{
-				AM_saveScaleAndLoc();
-				AM_minOutWindowScale();
-			}
-			else
-				AM_restoreScaleAndLoc();
-			break;
-		default:
-			switch (ev->data2)
-			{
-			case AM_FOLLOWKEY:
-				AM_ToggleFollowPlayer();
-				break;
-			case AM_GRIDKEY:
-				grid = !grid;
-				Printf ("%s\n", GStrings(grid ? "AMSTR_GRIDON" : "AMSTR_GRIDOFF"));
-				break;
-			case AM_MARKKEY:
-				if (AM_addMark())
-				{
-					Printf ("%s %d\n", GStrings("AMSTR_MARKEDSPOT"), markpointnum);
-				}
-				else
-				{
-					rc = false;
-				}
-				break;
-			case AM_CLEARMARKKEY:
-				if (AM_clearMarks())
-				{
-					Printf ("%s\n", GStrings("AMSTR_MARKSCLEARED"));
-				}
-				else
-				{
-					rc = false;
-				}
-				break;
-			default:
-				cheatstate = 0;
-				rc = false;
-			}
+			// check for am_pan* and ignore in follow mode
+			const char *defbind = AutomapBindings.GetBind(ev->data1);
+			if (!strnicmp(defbind, "+am_pan", 7)) return false;
 		}
-	}
-	else if (ev->type == EV_KeyUp)
-	{
-		rc = false;
-		switch (ev->data1)
-		{
-		case AM_PANRIGHTKEY:
-			if (!followplayer) m_paninc.x = 0;
-			break;
-		case AM_PANLEFTKEY:
-			if (!followplayer) m_paninc.x = 0;
-			break;
-		case AM_PANUPKEY:
-			if (!followplayer) m_paninc.y = 0;
-			break;
-		case AM_PANDOWNKEY:
-			if (!followplayer) m_paninc.y = 0;
-			break;
-		case AM_ZOOMOUTKEY:
-		case AM_ZOOMOUTKEY2:
-		case AM_ZOOMINKEY:
-		case AM_ZOOMINKEY2:
-			mtof_zoommul = MAPUNIT;
-			ftom_zoommul = MAPUNIT;
-			break;
-		}
-	}
 
-	return rc;
+		bool res = C_DoKey(ev, &AutomapBindings, NULL);
+		if (res && ev->type == EV_KeyUp && !last)
+		{
+			// If this is a release event we also need to check if it released a button in the main Bindings
+			// so that that button does not get stuck.
+			const char *defbind = Bindings.GetBind(ev->data1);
+			return (defbind[0] != '+'); // Let G_Responder handle button releases
+		}
+		return res;
+	}
+	return false;
 }
 
 
@@ -1290,6 +1256,11 @@ bool AM_Responder (event_t *ev)
 
 void AM_changeWindowScale ()
 {
+	int mtof_zoommul;
+
+	if (Button_AM_ZoomIn.bDown) mtof_zoommul = M_ZOOMIN;
+	else if (Button_AM_ZoomOut.bDown) mtof_zoommul = M_ZOOMOUT;
+
 	// Change the scaling multipliers
 	scale_mtof = MapMul(scale_mtof, mtof_zoommul);
 	scale_ftom = MapDiv(MAPUNIT, scale_mtof);
@@ -1334,19 +1305,6 @@ void AM_doFollowPlayer ()
 	}
 }
 
-//=============================================================================
-//
-//
-//
-//=============================================================================
-
-static void AM_ToggleFollowPlayer()
-{
-	followplayer = !followplayer;
-	f_oldloc.x = FIXED_MAX;
-	Printf ("%s\n", GStrings(followplayer ? "AMSTR_FOLLOWON" : "AMSTR_FOLLOWOFF"));
-}
-
 //=============================================================================
 //
 // Updates on Game Tick
@@ -1361,10 +1319,20 @@ void AM_Ticker ()
 	amclock++;
 
 	if (followplayer)
+	{
 		AM_doFollowPlayer();
+	}
+	else
+	{
+		m_paninc.x = m_paninc.y = 0;
+		if (Button_AM_PanLeft.bDown) m_paninc.x -= FTOM(F_PANINC);
+		if (Button_AM_PanRight.bDown) m_paninc.x += FTOM(F_PANINC);
+		if (Button_AM_PanUp.bDown) m_paninc.y += FTOM(F_PANINC);
+		if (Button_AM_PanDown.bDown) m_paninc.y -= FTOM(F_PANINC);
+	}
 
 	// Change the zoom if necessary
-	if (ftom_zoommul != MAPUNIT)
+	if (Button_AM_ZoomIn.bDown || Button_AM_ZoomOut.bDown)
 		AM_changeWindowScale();
 
 	// Change x,y location
@@ -1622,6 +1590,92 @@ void AM_drawGrid (const AMColor &color)
 	}
 }
 
+//=============================================================================
+//
+// AM_drawSubsectors
+//
+//=============================================================================
+
+void AM_drawSubsectors()
+{
+	static TArray<FVector2> points;
+	float scale = float(scale_mtof);
+	angle_t rotation;
+	sector_t tempsec;
+	int floorlight, ceilinglight;
+	double originx, originy;
+	FDynamicColormap *colormap;
+
+
+	for (int i = 0; i < numsubsectors; ++i)
+	{
+		if ((!(subsectors[i].flags & SSECF_DRAWN) || (subsectors[i].render_sector->MoreFlags & SECF_HIDDEN)) && am_cheat == 0)
+		{
+			continue;
+		}
+		// Fill the points array from the subsector.
+		points.Resize(subsectors[i].numlines);
+		for (DWORD j = 0; j < subsectors[i].numlines; ++j)
+		{
+			mpoint_t pt = { subsectors[i].firstline[j].v1->x >> FRACTOMAPBITS,
+							subsectors[i].firstline[j].v1->y >> FRACTOMAPBITS };
+			if (am_rotate == 1 || (am_rotate == 2 && viewactive))
+			{
+				AM_rotatePoint(&pt.x, &pt.y);
+			}
+			points[j].X = f_x + ((pt.x - m_x) * scale / float(1 << 24));
+			points[j].Y = f_y + (f_h - (pt.y - m_y) * scale / float(1 << 24));
+		}
+		// For lighting and texture determination
+		sector_t *sec = R_FakeFlat (subsectors[i].render_sector, &tempsec, &floorlight,
+			&ceilinglight, false);
+		// Find texture origin.
+		mpoint_t originpt = { -sec->GetXOffset(sector_t::floor) >> FRACTOMAPBITS,
+							  sec->GetYOffset(sector_t::floor) >> FRACTOMAPBITS };
+		rotation = 0 - sec->GetAngle(sector_t::floor);
+		// Apply the floor's rotation to the texture origin.
+		if (rotation != 0)
+		{
+			AM_rotate(&originpt.x, &originpt.y, rotation);
+		}
+		// Apply the automap's rotation to the texture origin.
+		if (am_rotate == 1 || (am_rotate == 2 && viewactive))
+		{
+			rotation += ANG90 - players[consoleplayer].camera->angle;
+			AM_rotatePoint(&originpt.x, &originpt.y);
+		}
+		originx = f_x + ((originpt.x - m_x) * scale / float(1 << 24));
+		originy = f_y + (f_h - (originpt.y - m_y) * scale / float(1 << 24));
+		// Coloring for the polygon
+		colormap = sec->ColorMap;
+		// If this subsector has not actually been seen yet (because you are cheating
+		// to see it on the map), tint and desaturate it.
+		if (!(subsectors[i].flags & SSECF_DRAWN))
+		{
+			colormap = GetSpecialLights(
+				MAKERGB(
+					(colormap->Color.r + 255) / 2,
+					(colormap->Color.g + 200) / 2,
+					(colormap->Color.b + 160) / 2),
+				colormap->Fade,
+				255 - (255 - colormap->Desaturate) / 4);
+			floorlight = (floorlight + 200*15) / 16;
+		}
+
+		// Draw the polygon.
+		screen->FillSimplePoly(
+			TexMan(sec->GetTexture(sector_t::floor)),
+			&points[0], points.Size(),
+			originx, originy,
+			scale / (FIXED2FLOAT(sec->GetXScale(sector_t::floor)) * float(1 << MAPBITS)),
+			scale / (FIXED2FLOAT(sec->GetYScale(sector_t::floor)) * float(1 << MAPBITS)),
+			rotation,
+			colormap,
+			floorlight
+			);
+	}
+}
+
 //=============================================================================
 //
 //
@@ -2232,11 +2286,19 @@ void AM_drawAuthorMarkers ()
 
 		while (marked != NULL)
 		{
-			if (mark->args[1] == 0 || (mark->args[1] == 1 && marked->Sector->MoreFlags & SECF_DRAWN))
+			if (mark->args[1] == 0 || (mark->args[1] == 1))
 			{
-				DrawMarker (tex, marked->x >> FRACTOMAPBITS, marked->y >> FRACTOMAPBITS, 0,
-					flip, mark->scaleX, mark->scaleY, mark->Translation,
-					mark->alpha, mark->fillcolor, mark->RenderStyle);
+				// Use more correct info if we have GL nodes available
+				INTBOOL drawn = hasglnodes?
+					marked->subsector->flags & SSECF_DRAWN :
+					marked->Sector->MoreFlags & SECF_DRAWN;
+
+				if (drawn)
+				{
+					DrawMarker (tex, marked->x >> FRACTOMAPBITS, marked->y >> FRACTOMAPBITS, 0,
+						flip, mark->scaleX, mark->scaleY, mark->Translation,
+						mark->alpha, mark->fillcolor, mark->RenderStyle);
+				}
 			}
 			marked = mark->args[0] != 0 ? it.Next() : NULL;
 		}
@@ -2291,6 +2353,9 @@ void AM_Drawer ()
 	}
 	AM_activateNewScale();
 
+	if (am_textured && hasglnodes && textured)
+		AM_drawSubsectors();
+
 	if (grid)	
 		AM_drawGrid(GridColor);
 
diff --git a/src/am_map.h b/src/am_map.h
index 71740b9a7..20da6b96b 100644
--- a/src/am_map.h
+++ b/src/am_map.h
@@ -26,7 +26,7 @@ struct event_t;
 class FArchive;
 
 // Called by main loop.
-bool AM_Responder (event_t* ev);
+bool AM_Responder (event_t* ev, bool last);
 
 // Called by main loop.
 void AM_Ticker (void);
diff --git a/src/asm_ia32/tmap.asm b/src/asm_ia32/tmap.asm
index e9713131f..cbcd9f4f1 100644
--- a/src/asm_ia32/tmap.asm
+++ b/src/asm_ia32/tmap.asm
@@ -309,6 +309,8 @@ GLOBAL R_DrawSpanP_ASM
 ; edi: dest
 ; ebp: scratch
 ; esi: count
+; [esp]: xstep
+; [esp+4]: ystep
 
 	align	16
 
@@ -324,6 +326,7 @@ R_DrawSpanP_ASM:
 	push	edi
 	push	ebp
 	push	esi
+	sub		esp, 8
 
 	mov	edi,ecx
 	add	edi,[dc_destorg]
@@ -335,13 +338,13 @@ dsy1:	shl	edx,6
 dsy3:	shr	ebp,26
 	 xor	ebx,ebx
 	lea	esi,[eax+1]
-	 mov	[ds_xstep],edx
+	 mov	[esp],edx
 	mov	edx,[ds_ystep]
 	 mov	ecx,[ds_xfrac]
 dsy4:	shr	ecx,26
 dsm8:	 and	edx,0xffffffc0
 	or	ebp,edx
-	 mov	[ds_ystep],ebp
+	 mov	[esp+4],ebp
 	mov	ebp,[ds_yfrac]
 	 mov	edx,[ds_xfrac]
 dsy2:	shl	edx,6
@@ -355,8 +358,8 @@ dsm9:	 and	ebp,0xffffffc0
 		mov	ebp,ecx
 dsx1:		rol	ebp,6
 dsm1:		and	ebp,0xfff
-		 add	edx,[ds_xstep]
-		adc	ecx,[ds_ystep]
+		 add	edx,[esp]
+		adc	ecx,[esp+4]
 spreada		 mov	bl,[ebp+SPACEFILLER4]
 spmapa		mov	bl,[ebx+SPACEFILLER4]
 		mov	[edi],bl
@@ -367,13 +370,13 @@ dseven1		shr	esi,1
 
 ; do two more pixels
 		mov	ebp,ecx
-		 add	edx,[ds_xstep]
-		adc	ecx,[ds_ystep]
+		 add	edx,[esp]
+		adc	ecx,[esp+4]
 dsm2:		 and	ebp,0xfc00003f
 dsx2:		rol	ebp,6
 		mov	eax,ecx
-		 add	edx,[ds_xstep]
-		adc	ecx,[ds_ystep]
+		 add	edx,[esp]
+		adc	ecx,[esp+4]
 spreadb		 mov	bl,[ebp+SPACEFILLER4]	;read texel1
 dsx3:		rol	eax,6
 dsm6:		and	eax,0xfff
@@ -392,13 +395,13 @@ dsrest		test	esi,esi
 		align 16
 
 dsloop		mov	ebp,ecx
-spstep1d	 add	edx,[ds_xstep]
-spstep2d	adc	ecx,[ds_ystep]
+spstep1d	 add	edx,[esp]
+spstep2d	adc	ecx,[esp+4]
 dsm3:		 and	ebp,0xfc00003f
 dsx4:		rol	ebp,6
 		mov	eax,ecx
-spstep1e	 add	edx,[ds_xstep]
-spstep2e	adc	ecx,[ds_ystep]
+spstep1e	 add	edx,[esp]
+spstep2e	adc	ecx,[esp+4]
 spreadd		 mov	bl,[ebp+SPACEFILLER4]	;read texel1
 dsx5:		rol	eax,6
 dsm5:		and	eax,0xfff
@@ -406,8 +409,8 @@ spmapd		 mov	bl,[ebx+SPACEFILLER4]	;map texel1
 		mov	[edi],bl		;store texel1
 		 mov	ebp,ecx
 spreade		mov	bl,[eax+SPACEFILLER4]	;read texel2
-spstep1f	 add	edx,[ds_xstep]
-spstep2f	adc	ecx,[ds_ystep]
+spstep1f	 add	edx,[esp]
+spstep2f	adc	ecx,[esp+4]
 dsm4:		 and	ebp,0xfc00003f
 dsx6:		rol	ebp,6
 spmape		mov	bl,[ebx+SPACEFILLER4]	;map texel2
@@ -420,14 +423,15 @@ dsx7:		rol	eax,6
 dsm7:		and	eax,0xfff
 		 mov	[edi-2],bl		;store texel3
 spreadg		mov	bl,[eax+SPACEFILLER4]	;read texel4
-spstep1g	 add	edx,[ds_xstep]
-spstep2g	adc	ecx,[ds_ystep]
+spstep1g	 add	edx,[esp]
+spstep2g	adc	ecx,[esp+4]
 spmapg		 mov	bl,[ebx+SPACEFILLER4]	;map texel4
 		dec	esi
 		 mov	[edi-1],bl		;store texel4
 		jnz near dsloop
 
-dsdone	pop	esi
+dsdone	add esp,8
+	pop	esi
 	pop	ebp
 	pop	edi
 	pop	ebx
@@ -448,6 +452,8 @@ GLOBAL R_DrawSpanMaskedP_ASM
 ; edi: dest
 ; ebp: scratch
 ; esi: count
+; [esp]: xstep
+; [esp+4]: ystep
 
 	align	16
 
@@ -463,6 +469,7 @@ R_DrawSpanMaskedP_ASM:
 	push	edi
 	push	ebp
 	push	esi
+	sub		esp,8
 
 	mov	edi,ecx
 	add	edi,[dc_destorg]
@@ -474,13 +481,13 @@ dmsy1:	shl	edx,6
 dmsy3:	shr	ebp,26
 	 xor	ebx,ebx
 	lea	esi,[eax+1]
-	 mov	[ds_xstep],edx
+	 mov	[esp],edx
 	mov	edx,[ds_ystep]
 	 mov	ecx,[ds_xfrac]
 dmsy4:	shr	ecx,26
 dmsm8:	 and	edx,0xffffffc0
 	or	ebp,edx
-	 mov	[ds_ystep],ebp
+	 mov	[esp+4],ebp
 	mov	ebp,[ds_yfrac]
 	 mov	edx,[ds_xfrac]
 dmsy2:	shl	edx,6
@@ -494,8 +501,8 @@ dmsm9:	 and	ebp,0xffffffc0
 		mov	ebp,ecx
 dmsx1:		rol	ebp,6
 dmsm1:		and	ebp,0xfff
-		 add	edx,[ds_xstep]
-		adc	ecx,[ds_ystep]
+		 add	edx,[esp]
+		adc	ecx,[esp+4]
 mspreada	 mov	bl,[ebp+SPACEFILLER4]
 		cmp	bl,0
 		 je	mspskipa
@@ -508,13 +515,13 @@ dmseven1	shr	esi,1
 
 ; do two more pixels
 		mov	ebp,ecx
-		 add	edx,[ds_xstep]
-		adc	ecx,[ds_ystep]
+		 add	edx,[esp]
+		adc	ecx,[esp+4]
 dmsm2:		 and	ebp,0xfc00003f
 dmsx2:		rol	ebp,6
 		mov	eax,ecx
-		 add	edx,[ds_xstep]
-		adc	ecx,[ds_ystep]
+		 add	edx,[esp]
+		adc	ecx,[esp+4]
 mspreadb	 mov	bl,[ebp+SPACEFILLER4]	;read texel1
 dmsx3:		rol	eax,6
 dmsm6:		and	eax,0xfff
@@ -537,13 +544,13 @@ dmsrest		test	esi,esi
 		align 16
 
 dmsloop		mov	ebp,ecx
-mspstep1d	 add	edx,[ds_xstep]
-mspstep2d	adc	ecx,[ds_ystep]
+mspstep1d	 add	edx,[esp]
+mspstep2d	adc	ecx,[esp+4]
 dmsm3:		 and	ebp,0xfc00003f
 dmsx4:		rol	ebp,6
 		mov	eax,ecx
-mspstep1e	 add	edx,[ds_xstep]
-mspstep2e	adc	ecx,[ds_ystep]
+mspstep1e	 add	edx,[esp]
+mspstep2e	adc	ecx,[esp+4]
 mspreadd	 mov	bl,[ebp+SPACEFILLER4]	;read texel1
 dmsx5:		rol	eax,6
 dmsm5:		and	eax,0xfff
@@ -553,8 +560,8 @@ dmsm5:		and	eax,0xfff
 mspmapd		mov	bl,[ebx+SPACEFILLER4]	;map texel1
 		mov	[edi],bl		;store texel1
 mspreade	mov	bl,[eax+SPACEFILLER4]	;read texel2
-mspstep1f	 add	edx,[ds_xstep]
-mspstep2f	adc	ecx,[ds_ystep]
+mspstep1f	 add	edx,[esp]
+mspstep2f	adc	ecx,[esp+4]
 dmsm4:		 and	ebp,0xfc00003f
 dmsx6:		rol	ebp,6
 		 cmp	bl,0
@@ -571,8 +578,8 @@ dmsm7:		and	eax,0xfff
 mspmapf		mov	bl,[ebx+SPACEFILLER4]	;map texel3
 		 mov	[edi-2],bl		;store texel3
 mspreadg	mov	bl,[eax+SPACEFILLER4]	;read texel4
-mspstep1g	 add	edx,[ds_xstep]
-mspstep2g	adc	ecx,[ds_ystep]
+mspstep1g	 add	edx,[esp]
+mspstep2g	adc	ecx,[esp+4]
 		cmp	bl,0
 		 je	mspskipg
 mspmapg		 mov	bl,[ebx+SPACEFILLER4]	;map texel4
@@ -580,7 +587,8 @@ mspmapg		 mov	bl,[ebx+SPACEFILLER4]	;map texel4
 mspskipg	dec	esi
 		 jnz near dmsloop
 
-dmsdone	pop	esi
+dmsdone	add esp,8
+	pop	esi
 	pop	ebp
 	pop	edi
 	pop	ebx
diff --git a/src/c_bind.cpp b/src/c_bind.cpp
index d96b5a742..6afb0c677 100644
--- a/src/c_bind.cpp
+++ b/src/c_bind.cpp
@@ -47,12 +47,6 @@
 #include <math.h>
 #include <stdlib.h>
 
-struct FBinding
-{
-	const char *Key;
-	const char *Bind;
-};
-
 /* Default keybindings for Doom (and all other games)
  */
 static const FBinding DefBindings[] =
@@ -178,6 +172,27 @@ static const FBinding DefStrifeBindings[] =
 	// h - use health
 };
 
+static const FBinding DefAutomapBindings[] =
+{
+	{ "f", "am_togglefollow" },
+	{ "g", "am_togglegrid" },
+	{ "t", "am_toggletexture" },
+	{ "m", "am_setmark" },
+	{ "c", "am_clearmarks" },
+	{ "0", "am_gobig" },
+	{ "rightarrow", "+am_panright" },
+	{ "leftarrow", "+am_panleft" },
+	{ "uparrow", "+am_panup" },
+	{ "downarrow", "+am_pandown" },
+	{ "-", "+am_zoomout" },
+	{ "=", "+am_zoomin" },
+	{ "kp-", "+am_zoomout" },
+	{ "kp+", "+am_zoomin" },
+	{ NULL }
+};
+
+
+
 const char *KeyNames[NUM_KEYS] =
 {
 	// This array is dependant on the particular keyboard input
@@ -278,11 +293,19 @@ const char *KeyNames[NUM_KEYS] =
 	"pad_a", "pad_b", "pad_x", "pad_y"
 };
 
-static FString Bindings[NUM_KEYS];
-static FString DoubleBindings[NUM_KEYS];
+FKeyBindings Bindings;
+FKeyBindings DoubleBindings;
+FKeyBindings AutomapBindings;
+
 static unsigned int DClickTime[NUM_KEYS];
 static BYTE DClicked[(NUM_KEYS+7)/8];
 
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
 static int GetKeyFromName (const char *name)
 {
 	int i;
@@ -302,380 +325,15 @@ static int GetKeyFromName (const char *name)
 	return 0;
 }
 
-static const char *KeyName (int key)
-{
-	static char name[5];
-
-	if (KeyNames[key])
-		return KeyNames[key];
-
-	mysnprintf (name, countof(name), "#%d", key);
-	return name;
-}
-
-void C_UnbindAll ()
-{
-	for (int i = 0; i < NUM_KEYS; ++i)
-	{
-		Bindings[i] = "";
-		DoubleBindings[i] = "";
-	}
-}
-
-CCMD (unbindall)
-{
-	C_UnbindAll ();
-}
-
-CCMD (unbind)
-{
-	int i;
-
-	if (argv.argc() > 1)
-	{
-		if ( (i = GetKeyFromName (argv[1])) )
-		{
-			Bindings[i] = "";
-		}
-		else
-		{
-			Printf ("Unknown key \"%s\"\n", argv[1]);
-			return;
-		}
-
-	}
-}
-
-CCMD (bind)
-{
-	int i;
-
-	if (argv.argc() > 1)
-	{
-		i = GetKeyFromName (argv[1]);
-		if (!i)
-		{
-			Printf ("Unknown key \"%s\"\n", argv[1]);
-			return;
-		}
-		if (argv.argc() == 2)
-		{
-			Printf ("\"%s\" = \"%s\"\n", argv[1], Bindings[i].GetChars());
-		}
-		else
-		{
-			Bindings[i] = argv[2];
-		}
-	}
-	else
-	{
-		Printf ("Current key bindings:\n");
-		
-		for (i = 0; i < NUM_KEYS; i++)
-		{
-			if (!Bindings[i].IsEmpty())
-				Printf ("%s \"%s\"\n", KeyName (i), Bindings[i].GetChars());
-		}
-	}
-}
-
-//==========================================================================
+//=============================================================================
 //
-// CCMD defaultbind
 //
-// Binds a command to a key if that key is not already bound and if
-// that command is not already bound to another key.
 //
-//==========================================================================
+//=============================================================================
 
-CCMD (defaultbind)
+static int GetConfigKeyFromName (const char *key)
 {
-	if (argv.argc() < 3)
-	{
-		Printf ("Usage: defaultbind <key> <command>\n");
-	}
-	else
-	{
-		int key = GetKeyFromName (argv[1]);
-		if (key == 0)
-		{
-			Printf ("Unknown key \"%s\"\n", argv[1]);
-			return;
-		}
-		if (!Bindings[key].IsEmpty())
-		{ // This key is already bound.
-			return;
-		}
-		for (int i = 0; i < NUM_KEYS; ++i)
-		{
-			if (!Bindings[i].IsEmpty() && stricmp (Bindings[i], argv[2]) == 0)
-			{ // This command is already bound to a key.
-				return;
-			}
-		}
-		// It is safe to do the bind, so do it.
-		Bindings[key] = argv[2];
-	}
-}
-
-CCMD (undoublebind)
-{
-	int i;
-
-	if (argv.argc() > 1)
-	{
-		if ( (i = GetKeyFromName (argv[1])) )
-		{
-			DoubleBindings[i] = "";
-		}
-		else
-		{
-			Printf ("Unknown key \"%s\"\n", argv[1]);
-			return;
-		}
-
-	}
-}
-
-CCMD (doublebind)
-{
-	int i;
-
-	if (argv.argc() > 1)
-	{
-		i = GetKeyFromName (argv[1]);
-		if (!i)
-		{
-			Printf ("Unknown key \"%s\"\n", argv[1]);
-			return;
-		}
-		if (argv.argc() == 2)
-		{
-			Printf ("\"%s\" = \"%s\"\n", argv[1], DoubleBindings[i].GetChars());
-		}
-		else
-		{
-			DoubleBindings[i] = argv[2];
-		}
-	}
-	else
-	{
-		Printf ("Current key doublebindings:\n");
-		
-		for (i = 0; i < NUM_KEYS; i++)
-		{
-			if (!DoubleBindings[i].IsEmpty())
-				Printf ("%s \"%s\"\n", KeyName (i), DoubleBindings[i].GetChars());
-		}
-	}
-}
-
-CCMD (rebind)
-{
-	FString *bindings;
-
-	if (key == 0)
-	{
-		Printf ("Rebind cannot be used from the console\n");
-		return;
-	}
-
-	if (key & KEY_DBLCLICKED)
-	{
-		bindings = DoubleBindings;
-		key &= KEY_DBLCLICKED-1;
-	}
-	else
-	{
-		bindings = Bindings;
-	}
-
-	if (argv.argc() > 1)
-	{
-		bindings[key] = argv[1];
-	}
-}
-
-static void SetBinds (const FBinding *array)
-{
-	while (array->Key)
-	{
-		C_DoBind (array->Key, array->Bind, false);
-		array++;
-	}
-}
-
-void C_BindDefaults ()
-{
-	SetBinds (DefBindings);
-
-	if (gameinfo.gametype & (GAME_Raven|GAME_Strife))
-	{
-		SetBinds (DefRavenBindings);
-	}
-
-	if (gameinfo.gametype == GAME_Heretic)
-	{
-		SetBinds (DefHereticBindings);
-	}
-
-	if (gameinfo.gametype == GAME_Hexen)
-	{
-		SetBinds (DefHexenBindings);
-	}
-
-	if (gameinfo.gametype == GAME_Strife)
-	{
-		SetBinds (DefStrifeBindings);
-	}
-}
-
-CCMD(binddefaults)
-{
-	C_BindDefaults ();
-}
-
-void C_SetDefaultBindings ()
-{
-	C_UnbindAll ();
-	C_BindDefaults ();
-}
-
-bool C_DoKey (event_t *ev)
-{
-	FString binding;
-	bool dclick;
-	int dclickspot;
-	BYTE dclickmask;
-
-	if (ev->type != EV_KeyDown && ev->type != EV_KeyUp)
-		return false;
-
-	if ((unsigned int)ev->data1 >= NUM_KEYS)
-		return false;
-
-	dclickspot = ev->data1 >> 3;
-	dclickmask = 1 << (ev->data1 & 7);
-	dclick = false;
-
-	// This used level.time which didn't work outside a level.
-	if (DClickTime[ev->data1] > I_MSTime() && ev->type == EV_KeyDown)
-	{
-		// Key pressed for a double click
-		binding = DoubleBindings[ev->data1];
-		DClicked[dclickspot] |= dclickmask;
-		dclick = true;
-	}
-	else
-	{
-		if (ev->type == EV_KeyDown)
-		{ // Key pressed for a normal press
-			binding = Bindings[ev->data1];
-			DClickTime[ev->data1] = I_MSTime() + 571;
-		}
-		else if (DClicked[dclickspot] & dclickmask)
-		{ // Key released from a double click
-			binding = DoubleBindings[ev->data1];
-			DClicked[dclickspot] &= ~dclickmask;
-			DClickTime[ev->data1] = 0;
-			dclick = true;
-		}
-		else
-		{ // Key released from a normal press
-			binding = Bindings[ev->data1];
-		}
-	}
-
-
-	if (binding.IsEmpty())
-	{
-		binding = Bindings[ev->data1];
-		dclick = false;
-	}
-
-	if (!binding.IsEmpty() && (chatmodeon == 0 || ev->data1 < 256))
-	{
-		if (ev->type == EV_KeyUp && binding[0] != '+')
-		{
-			return false;
-		}
-
-		char *copy = binding.LockBuffer();
-
-		if (ev->type == EV_KeyUp)
-		{
-			copy[0] = '-';
-		}
-
-		AddCommandString (copy, dclick ? ev->data1 | KEY_DBLCLICKED : ev->data1);
-		return true;
-	}
-	return false;
-}
-
-const char *C_ConfigKeyName(int keynum)
-{
-	const char *name = KeyName(keynum);
-	if (name[1] == 0)	// Make sure given name is config-safe
-	{
-		if (name[0] == '[')
-			return "LeftBracket";
-		else if (name[0] == ']')
-			return "RightBracket";
-		else if (name[0] == '=')
-			return "Equals";
-		else if (strcmp (name, "kp=") == 0)
-			return "KP-Equals";
-	}
-	return name;
-}
-
-// This function is first called for functions in custom key sections.
-// In this case, matchcmd is non-NULL, and only keys bound to that command
-// are stored. If a match is found, its binding is set to "\1".
-// After all custom key sections are saved, it is called one more for the
-// normal Bindings and DoubleBindings sections for this game. In this case
-// matchcmd is NULL and all keys will be stored. The config section was not
-// previously cleared, so all old bindings are still in place. If the binding
-// for a key is empty, the corresponding key in the config is removed as well.
-// If a binding is "\1", then the binding itself is cleared, but nothing
-// happens to the entry in the config.
-void C_ArchiveBindings (FConfigFile *f, bool dodouble, const char *matchcmd)
-{
-	FString *bindings;
-	int i;
-
-	bindings = dodouble ? DoubleBindings : Bindings;
-
-	for (i = 0; i < NUM_KEYS; i++)
-	{
-		if (bindings[i].IsEmpty())
-		{
-			if (matchcmd == NULL)
-			{
-				f->ClearKey(C_ConfigKeyName(i));
-			}
-		}
-		else if (matchcmd == NULL || stricmp(bindings[i], matchcmd) == 0)
-		{
-			if (bindings[i][0] == '\1')
-			{
-				bindings[i] = "";
-				continue;
-			}
-			f->SetValueForKey(C_ConfigKeyName(i), bindings[i]);
-			if (matchcmd != NULL)
-			{ // If saving a specific command, set a marker so that
-			  // it does not get saved in the general binding list.
-				bindings[i] = "\1";
-			}
-		}
-	}
-}
-
-void C_DoBind (const char *key, const char *bind, bool dodouble)
-{
-	int keynum = GetKeyFromName (key);
+	int keynum = GetKeyFromName(key);
 	if (keynum == 0)
 	{
 		if (stricmp (key, "LeftBracket") == 0)
@@ -695,32 +353,55 @@ void C_DoBind (const char *key, const char *bind, bool dodouble)
 			keynum = GetKeyFromName ("kp=");
 		}
 	}
-	if (keynum != 0)
-	{
-		(dodouble ? DoubleBindings : Bindings)[keynum] = bind;
-	}
+	return keynum;
 }
 
-int C_GetKeysForCommand (char *cmd, int *first, int *second)
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+static const char *KeyName (int key)
 {
-	int c, i;
+	static char name[5];
 
-	*first = *second = c = i = 0;
+	if (KeyNames[key])
+		return KeyNames[key];
 
-	while (i < NUM_KEYS && c < 2)
-	{
-		if (stricmp (cmd, Bindings[i]) == 0)
-		{
-			if (c++ == 0)
-				*first = i;
-			else
-				*second = i;
-		}
-		i++;
-	}
-	return c;
+	mysnprintf (name, countof(name), "#%d", key);
+	return name;
 }
 
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+static const char *ConfigKeyName(int keynum)
+{
+	const char *name = KeyName(keynum);
+	if (name[1] == 0)	// Make sure given name is config-safe
+	{
+		if (name[0] == '[')
+			return "LeftBracket";
+		else if (name[0] == ']')
+			return "RightBracket";
+		else if (name[0] == '=')
+			return "Equals";
+		else if (strcmp (name, "kp=") == 0)
+			return "KP-Equals";
+	}
+	return name;
+}
+
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
 void C_NameKeys (char *str, int first, int second)
 {
 	int c = 0;
@@ -744,28 +425,471 @@ void C_NameKeys (char *str, int first, int second)
 		*str = '\0';
 }
 
-void C_UnbindACommand (char *str)
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+void FKeyBindings::DoBind (const char *key, const char *bind)
+{
+	int keynum = GetConfigKeyFromName (key);
+	if (keynum != 0)
+	{
+		Binds[keynum] = bind;
+	}
+}
+
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+void FKeyBindings::SetBinds(const FBinding *binds)
+{
+	while (binds->Key)
+	{
+		DoBind (binds->Key, binds->Bind);
+		binds++;
+	}
+}
+
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+void FKeyBindings::UnbindAll ()
+{
+	for (int i = 0; i < NUM_KEYS; ++i)
+	{
+		Binds[i] = "";
+	}
+}
+
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+void FKeyBindings::UnbindKey(const char *key)
+{
+	int i;
+
+	if ( (i = GetKeyFromName (key)) )
+	{
+		Binds[i] = "";
+	}
+	else
+	{
+		Printf ("Unknown key \"%s\"\n", key);
+		return;
+	}
+}
+
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+void FKeyBindings::PerformBind(FCommandLine &argv, const char *msg)
+{
+	int i;
+
+	if (argv.argc() > 1)
+	{
+		i = GetKeyFromName (argv[1]);
+		if (!i)
+		{
+			Printf ("Unknown key \"%s\"\n", argv[1]);
+			return;
+		}
+		if (argv.argc() == 2)
+		{
+			Printf ("\"%s\" = \"%s\"\n", argv[1], Binds[i].GetChars());
+		}
+		else
+		{
+			Binds[i] = argv[2];
+		}
+	}
+	else
+	{
+		Printf ("%s:\n", msg);
+		
+		for (i = 0; i < NUM_KEYS; i++)
+		{
+			if (!Binds[i].IsEmpty())
+				Printf ("%s \"%s\"\n", KeyName (i), Binds[i].GetChars());
+		}
+	}
+}
+
+
+//=============================================================================
+//
+// This function is first called for functions in custom key sections.
+// In this case, matchcmd is non-NULL, and only keys bound to that command
+// are stored. If a match is found, its binding is set to "\1".
+// After all custom key sections are saved, it is called one more for the
+// normal Bindings and DoubleBindings sections for this game. In this case
+// matchcmd is NULL and all keys will be stored. The config section was not
+// previously cleared, so all old bindings are still in place. If the binding
+// for a key is empty, the corresponding key in the config is removed as well.
+// If a binding is "\1", then the binding itself is cleared, but nothing
+// happens to the entry in the config.
+//
+//=============================================================================
+
+void FKeyBindings::ArchiveBindings(FConfigFile *f, const char *matchcmd)
 {
 	int i;
 
 	for (i = 0; i < NUM_KEYS; i++)
 	{
-		if (!stricmp (str, Bindings[i]))
+		if (Binds[i].IsEmpty())
 		{
-			Bindings[i] = "";
+			if (matchcmd == NULL)
+			{
+				f->ClearKey(ConfigKeyName(i));
+			}
+		}
+		else if (matchcmd == NULL || stricmp(Binds[i], matchcmd) == 0)
+		{
+			if (Binds[i][0] == '\1')
+			{
+				Binds[i] = "";
+				continue;
+			}
+			f->SetValueForKey(ConfigKeyName(i), Binds[i]);
+			if (matchcmd != NULL)
+			{ // If saving a specific command, set a marker so that
+			  // it does not get saved in the general binding list.
+				Binds[i] = "\1";
+			}
 		}
 	}
 }
 
-void C_ChangeBinding (const char *str, int newone)
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+int FKeyBindings::GetKeysForCommand (char *cmd, int *first, int *second)
 {
-	if ((unsigned int)newone < NUM_KEYS)
+	int c, i;
+
+	*first = *second = c = i = 0;
+
+	while (i < NUM_KEYS && c < 2)
 	{
-		Bindings[newone] = str;
+		if (stricmp (cmd, Binds[i]) == 0)
+		{
+			if (c++ == 0)
+				*first = i;
+			else
+				*second = i;
+		}
+		i++;
+	}
+	return c;
+}
+
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+void FKeyBindings::UnbindACommand (char *str)
+{
+	int i;
+
+	for (i = 0; i < NUM_KEYS; i++)
+	{
+		if (!stricmp (str, Binds[i]))
+		{
+			Binds[i] = "";
+		}
 	}
 }
 
-const char *C_GetBinding (int key)
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+void FKeyBindings::DefaultBind(const char *keyname, const char *cmd)
 {
-	return (unsigned int)key < NUM_KEYS ? Bindings[key].GetChars() : NULL;
+	int key = GetKeyFromName (keyname);
+	if (key == 0)
+	{
+		Printf ("Unknown key \"%s\"\n", keyname);
+		return;
+	}
+	if (!Binds[key].IsEmpty())
+	{ // This key is already bound.
+		return;
+	}
+	for (int i = 0; i < NUM_KEYS; ++i)
+	{
+		if (!Binds[i].IsEmpty() && stricmp (Binds[i], cmd) == 0)
+		{ // This command is already bound to a key.
+			return;
+		}
+	}
+	// It is safe to do the bind, so do it.
+	Binds[key] = cmd;
 }
+
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+void C_UnbindAll ()
+{
+	Bindings.UnbindAll();
+	DoubleBindings.UnbindAll();
+	AutomapBindings.UnbindAll();
+}
+
+CCMD (unbindall)
+{
+	C_UnbindAll ();
+}
+
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+CCMD (unbind)
+{
+	if (argv.argc() > 1)
+	{
+		Bindings.UnbindKey(argv[1]);
+	}
+}
+
+CCMD (undoublebind)
+{
+	if (argv.argc() > 1)
+	{
+		DoubleBindings.UnbindKey(argv[1]);
+	}
+}
+
+CCMD (unmapbind)
+{
+	if (argv.argc() > 1)
+	{
+		AutomapBindings.UnbindKey(argv[1]);
+	}
+}
+
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+CCMD (bind)
+{
+	Bindings.PerformBind(argv, "Current key bindings");
+}
+
+CCMD (doublebind)
+{
+	DoubleBindings.PerformBind(argv, "Current key doublebindings");
+}
+
+CCMD (mapbind)
+{
+	AutomapBindings.PerformBind(argv, "Current automap key bindings");
+}
+
+//==========================================================================
+//
+// CCMD defaultbind
+//
+// Binds a command to a key if that key is not already bound and if
+// that command is not already bound to another key.
+//
+//==========================================================================
+
+CCMD (defaultbind)
+{
+	if (argv.argc() < 3)
+	{
+		Printf ("Usage: defaultbind <key> <command>\n");
+	}
+	else
+	{
+		Bindings.DefaultBind(argv[1], argv[2]);
+	}
+}
+
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+CCMD (rebind)
+{
+	FKeyBindings *bindings;
+
+	if (key == 0)
+	{
+		Printf ("Rebind cannot be used from the console\n");
+		return;
+	}
+
+	if (key & KEY_DBLCLICKED)
+	{
+		bindings = &DoubleBindings;
+		key &= KEY_DBLCLICKED-1;
+	}
+	else
+	{
+		bindings = &Bindings;
+	}
+
+	if (argv.argc() > 1)
+	{
+		bindings->SetBind(key, argv[1]);
+	}
+}
+
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+void C_BindDefaults ()
+{
+	Bindings.SetBinds (DefBindings);
+
+	if (gameinfo.gametype & (GAME_Raven|GAME_Strife))
+	{
+		Bindings.SetBinds (DefRavenBindings);
+	}
+
+	if (gameinfo.gametype == GAME_Heretic)
+	{
+		Bindings.SetBinds (DefHereticBindings);
+	}
+
+	if (gameinfo.gametype == GAME_Hexen)
+	{
+		Bindings.SetBinds (DefHexenBindings);
+	}
+
+	if (gameinfo.gametype == GAME_Strife)
+	{
+		Bindings.SetBinds (DefStrifeBindings);
+	}
+
+	AutomapBindings.SetBinds(DefAutomapBindings);
+}
+
+CCMD(binddefaults)
+{
+	C_BindDefaults ();
+}
+
+void C_SetDefaultBindings ()
+{
+	C_UnbindAll ();
+	C_BindDefaults ();
+}
+
+//=============================================================================
+//
+//
+//
+//=============================================================================
+
+bool C_DoKey (event_t *ev, FKeyBindings *binds, FKeyBindings *doublebinds)
+{
+	FString binding;
+	bool dclick;
+	int dclickspot;
+	BYTE dclickmask;
+
+	if (ev->type != EV_KeyDown && ev->type != EV_KeyUp)
+		return false;
+
+	if ((unsigned int)ev->data1 >= NUM_KEYS)
+		return false;
+
+	dclickspot = ev->data1 >> 3;
+	dclickmask = 1 << (ev->data1 & 7);
+	dclick = false;
+
+	// This used level.time which didn't work outside a level.
+	if (DClickTime[ev->data1] > I_MSTime() && ev->type == EV_KeyDown)
+	{
+		// Key pressed for a double click
+		if (doublebinds != NULL) binding = doublebinds->GetBinding(ev->data1);
+		DClicked[dclickspot] |= dclickmask;
+		dclick = true;
+	}
+	else
+	{
+		if (ev->type == EV_KeyDown)
+		{ // Key pressed for a normal press
+			binding = binds->GetBinding(ev->data1);
+			DClickTime[ev->data1] = I_MSTime() + 571;
+		}
+		else if (DClicked[dclickspot] & dclickmask)
+		{ // Key released from a double click
+			if (doublebinds != NULL) binding = doublebinds->GetBinding(ev->data1);
+			DClicked[dclickspot] &= ~dclickmask;
+			DClickTime[ev->data1] = 0;
+			dclick = true;
+		}
+		else
+		{ // Key released from a normal press
+			binding = binds->GetBinding(ev->data1);
+		}
+	}
+
+
+	if (binding.IsEmpty())
+	{
+		binding = binds->GetBinding(ev->data1);
+		dclick = false;
+	}
+
+	if (!binding.IsEmpty() && (chatmodeon == 0 || ev->data1 < 256))
+	{
+		if (ev->type == EV_KeyUp && binding[0] != '+')
+		{
+			return false;
+		}
+
+		char *copy = binding.LockBuffer();
+
+		if (ev->type == EV_KeyUp)
+		{
+			copy[0] = '-';
+		}
+
+		AddCommandString (copy, dclick ? ev->data1 | KEY_DBLCLICKED : ev->data1);
+		return true;
+	}
+	return false;
+}
+
diff --git a/src/c_bind.h b/src/c_bind.h
index 2b46118cf..7d71b462a 100644
--- a/src/c_bind.h
+++ b/src/c_bind.h
@@ -34,25 +34,65 @@
 #ifndef __C_BINDINGS_H__
 #define __C_BINDINGS_H__
 
+#include "doomdef.h"
 
 struct event_t;
 class FConfigFile;
+class FCommandLine;
 
-bool C_DoKey (event_t *ev);
-void C_ArchiveBindings (FConfigFile *f, bool dodouble, const char *matchcmd=NULL);
+void C_NameKeys (char *str, int first, int second);
+
+struct FBinding
+{
+	const char *Key;
+	const char *Bind;
+};
+
+class FKeyBindings
+{
+	FString Binds[NUM_KEYS];
+
+public:
+	void PerformBind(FCommandLine &argv, const char *msg);
+	void SetBinds(const FBinding *binds);
+	bool DoKey(event_t *ev);
+	void ArchiveBindings(FConfigFile *F, const char *matchcmd = NULL);
+	int  GetKeysForCommand (char *cmd, int *first, int *second);
+	void UnbindACommand (char *str);
+	void UnbindAll ();
+	void UnbindKey(const char *key);
+	void DoBind (const char *key, const char *bind);
+	void DefaultBind(const char *keyname, const char *cmd);
+
+	void SetBind(unsigned int key, const char *bind)
+	{
+		if (key < NUM_KEYS) Binds[key] = bind;
+	}
+
+	const FString &GetBinding(unsigned int index) const
+	{
+		return Binds[index];
+	}
+
+	const char *GetBind(unsigned int index) const
+	{
+		if (index < NUM_KEYS) return Binds[index];
+		else return NULL;
+	}
+
+};
+
+extern FKeyBindings Bindings;
+extern FKeyBindings DoubleBindings;
+extern FKeyBindings AutomapBindings;
+
+
+bool C_DoKey (event_t *ev, FKeyBindings *binds, FKeyBindings *doublebinds);
 
 // Stuff used by the customize controls menu
-int  C_GetKeysForCommand (char *cmd, int *first, int *second);
-void C_NameKeys (char *str, int first, int second);
-void C_UnbindACommand (char *str);
-void C_ChangeBinding (const char *str, int newone);
-void C_DoBind (const char *key, const char *bind, bool doublebind);
 void C_SetDefaultBindings ();
 void C_UnbindAll ();
 
-// Returns string bound to given key (NULL if none)
-const char *C_GetBinding (int key);
-
 extern const char *KeyNames[];
 
 #endif //__C_BINDINGS_H__
diff --git a/src/c_dispatch.cpp b/src/c_dispatch.cpp
index 2fbafd2a9..413b8d64c 100644
--- a/src/c_dispatch.cpp
+++ b/src/c_dispatch.cpp
@@ -119,7 +119,9 @@ FButtonStatus Button_Mlook, Button_Klook, Button_Use, Button_AltAttack,
 	Button_Forward, Button_Right, Button_Left, Button_MoveDown,
 	Button_MoveUp, Button_Jump, Button_ShowScores, Button_Crouch,
 	Button_Zoom, Button_Reload,
-	Button_User1, Button_User2, Button_User3, Button_User4;
+	Button_User1, Button_User2, Button_User3, Button_User4,
+	Button_AM_PanLeft, Button_AM_PanRight, Button_AM_PanDown, Button_AM_PanUp,
+	Button_AM_ZoomIn, Button_AM_ZoomOut;
 
 bool ParsingKeyConf;
 
@@ -131,13 +133,16 @@ bool ParsingKeyConf;
 
 FActionMap ActionMaps[] =
 {
+	{ 0x0d52d67b, &Button_AM_PanLeft,	"am_panleft"},
 	{ 0x125f5226, &Button_User2,		"user2" },
 	{ 0x1eefa611, &Button_Jump,			"jump" },
 	{ 0x201f1c55, &Button_Right,		"right" },
 	{ 0x20ccc4d5, &Button_Zoom,			"zoom" },
 	{ 0x23a99cd7, &Button_Back,			"back" },
+	{ 0x41df90c2, &Button_AM_ZoomIn,	"am_zoomin"},
 	{ 0x426b69e7, &Button_Reload,		"reload" },
 	{ 0x4463f43a, &Button_LookDown,		"lookdown" },
+	{ 0x51f7a334, &Button_AM_ZoomOut,	"am_zoomout"},
 	{ 0x534c30ee, &Button_User4,		"user4" },
 	{ 0x5622bf42, &Button_Attack,		"attack" },
 	{ 0x577712d0, &Button_User1,		"user1" },
@@ -147,12 +152,15 @@ FActionMap ActionMaps[] =
 	{ 0x676885b8, &Button_AltAttack,	"altattack" },
 	{ 0x6fa41b84, &Button_MoveLeft,		"moveleft" },
 	{ 0x818f08e6, &Button_MoveRight,	"moveright" },
+	{ 0x8197097b, &Button_AM_PanRight,	"am_panright"},
+	{ 0x8d89955e, &Button_AM_PanUp,		"am_panup"} ,
 	{ 0xa2b62d8b, &Button_Mlook,		"mlook" },
 	{ 0xab2c3e71, &Button_Crouch,		"crouch" },
 	{ 0xb000b483, &Button_Left,			"left" },
 	{ 0xb62b1e49, &Button_LookUp,		"lookup" },
 	{ 0xb6f8fe92, &Button_User3,		"user3" },
 	{ 0xb7e6a54b, &Button_Strafe,		"strafe" },
+	{ 0xce301c81, &Button_AM_PanDown,	"am_pandown"},
 	{ 0xd5897c73, &Button_ShowScores,	"showscores" },
 	{ 0xe0ccb317, &Button_Speed,		"speed" },
 	{ 0xe0cfc260, &Button_Use,			"use" },
@@ -160,6 +168,7 @@ FActionMap ActionMaps[] =
 };
 #define NUM_ACTIONS countof(ActionMaps)
 
+
 // PRIVATE DATA DEFINITIONS ------------------------------------------------
 
 static const char *KeyConfCommands[] =
diff --git a/src/c_dispatch.h b/src/c_dispatch.h
index a19352d32..d93709c90 100644
--- a/src/c_dispatch.h
+++ b/src/c_dispatch.h
@@ -146,6 +146,7 @@ struct FButtonStatus
 	bool PressKey (int keynum);		// Returns true if this key caused the button to be pressed.
 	bool ReleaseKey (int keynum);	// Returns true if this key is no longer pressed.
 	void ResetTriggers () { bWentDown = bWentUp = false; }
+	void Reset () { bDown = bWentDown = bWentUp = false; }
 };
 
 extern FButtonStatus Button_Mlook, Button_Klook, Button_Use, Button_AltAttack,
@@ -154,7 +155,9 @@ extern FButtonStatus Button_Mlook, Button_Klook, Button_Use, Button_AltAttack,
 	Button_Forward, Button_Right, Button_Left, Button_MoveDown,
 	Button_MoveUp, Button_Jump, Button_ShowScores, Button_Crouch,
 	Button_Zoom, Button_Reload,
-	Button_User1, Button_User2, Button_User3, Button_User4;
+	Button_User1, Button_User2, Button_User3, Button_User4,
+	Button_AM_PanLeft, Button_AM_PanRight, Button_AM_PanDown, Button_AM_PanUp,
+	Button_AM_ZoomIn, Button_AM_ZoomOut;
 extern bool ParsingKeyConf;
 
 void ResetButtonTriggers ();	// Call ResetTriggers for all buttons
diff --git a/src/cmdlib.cpp b/src/cmdlib.cpp
index 9c06b6714..36a900709 100644
--- a/src/cmdlib.cpp
+++ b/src/cmdlib.cpp
@@ -2,10 +2,14 @@
 
 #ifdef _WIN32
 #include <direct.h>
+#include <io.h>
 #else
 #include <unistd.h>
 #include <sys/types.h>
 #include <pwd.h>
+#if !defined(__sun)
+#include <fts.h>
+#endif
 #endif
 #include "doomtype.h"
 #include "cmdlib.h"
@@ -885,3 +889,153 @@ FString NicePath(const char *path)
 	return where;
 #endif
 }
+
+
+#ifdef _WIN32
+
+//==========================================================================
+//
+// ScanDirectory
+//
+//==========================================================================
+
+void ScanDirectory(TArray<FFileList> &list, const char *dirpath)
+{
+	struct _finddata_t fileinfo;
+	intptr_t handle;
+	FString dirmatch;
+
+	dirmatch << dirpath << "*";
+
+	if ((handle = _findfirst(dirmatch, &fileinfo)) == -1)
+	{
+		I_Error("Could not scan '%s': %s\n", dirpath, strerror(errno));
+	}
+	else
+	{
+		do
+		{
+			if (fileinfo.attrib & _A_HIDDEN)
+			{
+				// Skip hidden files and directories. (Prevents SVN bookkeeping
+				// info from being included.)
+				continue;
+			}
+
+			if (fileinfo.attrib & _A_SUBDIR)
+			{
+				if (fileinfo.name[0] == '.' &&
+					(fileinfo.name[1] == '\0' ||
+					 (fileinfo.name[1] == '.' && fileinfo.name[2] == '\0')))
+				{
+					// Do not record . and .. directories.
+					continue;
+				}
+
+				FFileList *fl = &list[list.Reserve(1)];
+				fl->Filename << dirpath << fileinfo.name;
+				fl->isDirectory = true;
+				FString newdir = fl->Filename;
+				newdir << "/";
+				ScanDirectory(list, newdir);
+			}
+			else
+			{
+				FFileList *fl = &list[list.Reserve(1)];
+				fl->Filename << dirpath << fileinfo.name;
+				fl->isDirectory = false;
+			}
+		} 
+		while (_findnext(handle, &fileinfo) == 0);
+		_findclose(handle);
+	}
+}
+
+#elif defined(__sun) || defined(linux)
+
+//==========================================================================
+//
+// ScanDirectory
+// Solaris version
+//
+// Given NULL-terminated array of directory paths, create trees for them.
+//
+//==========================================================================
+
+void ScanDirectory(TArray<FFileList> &list, const char *dirpath)
+{
+	DIR *directory = opendir(dirpath);
+	if(directory == NULL)
+		return;
+
+	struct dirent *file;
+	while((file = readdir(directory)) != NULL)
+	{
+		if(file->d_name[0] == '.') //File is hidden or ./.. directory so ignore it.
+			continue;
+
+		FFileList *fl = &list[list.Reserve(1)];
+		fl->Filename << dirpath << file->d_name;
+
+		struct stat fileStat;
+		stat(fl->Filename, &fileStat);
+		fl->isDirectory = S_ISDIR(fileStat.st_mode);
+
+		if(fl->isDirectory)
+		{
+			FString newdir = fl->Filename;
+			newdir += "/";
+			ScanDirectory(list, newdir);
+			continue;
+		}
+	}
+
+	closedir(directory);
+}
+
+#else
+
+//==========================================================================
+//
+// ScanDirectory
+// 4.4BSD version
+//
+//==========================================================================
+
+void ScanDirectory(TArray<FFileList> &list, const char *dirpath)
+{
+	const char **argv[] = {dirpath, NULL };
+	FTS *fts;
+	FTSENT *ent;
+
+	fts = fts_open(argv, FTS_LOGICAL, NULL);
+	if (fts == NULL)
+	{
+		I_Error("Failed to start directory traversal: %s\n", strerror(errno));
+		return;
+	}
+	while ((ent = fts_read(fts)) != NULL)
+	{
+		if (ent->fts_info == FTS_D && ent->fts_name[0] == '.')
+		{
+			// Skip hidden directories. (Prevents SVN bookkeeping
+			// info from being included.)
+			fts_set(fts, ent, FTS_SKIP);
+		}
+		if (ent->fts_info == FTS_D && ent->fts_level == 0)
+		{
+			FFileList *fl = &list[list.Reserve(1)];
+			fl->Filename = ent->fts_path;
+			fl->isDirectory = true;
+		}
+		if (ent->fts_info == FTS_F)
+		{
+			// We're only interested in remembering files.
+			FFileList *fl = &list[list.Reserve(1)];
+			fl->Filename = ent->fts_path;
+			fl->isDirectory = false;
+		}
+	}
+	fts_close(fts);
+}
+#endif
diff --git a/src/cmdlib.h b/src/cmdlib.h
index f9d7fae70..7c29644d5 100644
--- a/src/cmdlib.h
+++ b/src/cmdlib.h
@@ -54,4 +54,12 @@ void CreatePath(const char * fn);
 FString ExpandEnvVars(const char *searchpathstring);
 FString NicePath(const char *path);
 
+struct FFileList
+{
+	FString Filename;
+	bool isDirectory;
+};
+
+void ScanDirectory(TArray<FFileList> &list, const char *dirpath);
+
 #endif
diff --git a/src/d_main.cpp b/src/d_main.cpp
index deef1d6c7..20ba2c467 100644
--- a/src/d_main.cpp
+++ b/src/d_main.cpp
@@ -2294,6 +2294,14 @@ void FStartupScreen::AppendStatusLine(const char *status)
 //
 //==========================================================================
 
+//==========================================================================
+//
+// STAT fps
+//
+// Displays statistics about rendering times
+//
+//==========================================================================
+
 ADD_STAT (fps)
 {
 	FString out;
@@ -2302,6 +2310,24 @@ ADD_STAT (fps)
 	return out;
 }
 
+
+static double f_acc, w_acc,p_acc,m_acc;
+static int acc_c;
+
+ADD_STAT (fps_accumulated)
+{
+	f_acc += FrameCycles.TimeMS();
+	w_acc += WallCycles.TimeMS();
+	p_acc += PlaneCycles.TimeMS();
+	m_acc += MaskedCycles.TimeMS();
+	acc_c++;
+	FString out;
+	out.Format("frame=%04.1f ms  walls=%04.1f ms  planes=%04.1f ms  masked=%04.1f ms  %d counts",
+		f_acc/acc_c, w_acc/acc_c, p_acc/acc_c, m_acc/acc_c, acc_c);
+	Printf(PRINT_LOG, "%s\n", out.GetChars());
+	return out;
+}
+
 //==========================================================================
 //
 // STAT wallcycles
diff --git a/src/f_finale.cpp b/src/f_finale.cpp
index c37d1f459..a851fdc31 100644
--- a/src/f_finale.cpp
+++ b/src/f_finale.cpp
@@ -723,7 +723,7 @@ bool F_CastResponder (event_t* ev)
 	if (ev->type != EV_KeyDown)
 		return false;
 			
-	const char *cmd = C_GetBinding (ev->data1);
+	const char *cmd = Bindings.GetBind (ev->data1);
 
 	if (cmd != NULL && !stricmp (cmd, "toggleconsole"))
 		return false;		
diff --git a/src/g_game.cpp b/src/g_game.cpp
index fadfb6c55..f85601c6e 100644
--- a/src/g_game.cpp
+++ b/src/g_game.cpp
@@ -879,7 +879,7 @@ bool G_Responder (event_t *ev)
 	if (gameaction == ga_nothing && 
 		(demoplayback || gamestate == GS_DEMOSCREEN || gamestate == GS_TITLELEVEL))
 	{
-		const char *cmd = C_GetBinding (ev->data1);
+		const char *cmd = Bindings.GetBind (ev->data1);
 
 		if (ev->type == EV_KeyDown)
 		{
@@ -902,11 +902,11 @@ bool G_Responder (event_t *ev)
 			}
 			else
 			{
-				return C_DoKey (ev);
+				return C_DoKey (ev, &Bindings, &DoubleBindings);
 			}
 		}
 		if (cmd && cmd[0] == '+')
-			return C_DoKey (ev);
+			return C_DoKey (ev, &Bindings, &DoubleBindings);
 
 		return false;
 	}
@@ -918,7 +918,7 @@ bool G_Responder (event_t *ev)
 	{
 		if (ST_Responder (ev))
 			return true;		// status window ate it
-		if (!viewactive && AM_Responder (ev))
+		if (!viewactive && AM_Responder (ev, false))
 			return true;		// automap ate it
 	}
 	else if (gamestate == GS_FINALE)
@@ -930,12 +930,12 @@ bool G_Responder (event_t *ev)
 	switch (ev->type)
 	{
 	case EV_KeyDown:
-		if (C_DoKey (ev))
+		if (C_DoKey (ev, &Bindings, &DoubleBindings))
 			return true;
 		break;
 
 	case EV_KeyUp:
-		C_DoKey (ev);
+		C_DoKey (ev, &Bindings, &DoubleBindings);
 		break;
 
 	// [RH] mouse buttons are sent as key up/down events
@@ -949,7 +949,7 @@ bool G_Responder (event_t *ev)
 	// the events *last* so that any bound keys get precedence.
 
 	if (gamestate == GS_LEVEL && viewactive)
-		return AM_Responder (ev);
+		return AM_Responder (ev, true);
 
 	return (ev->type == EV_KeyDown ||
 			ev->type == EV_Mouse);
diff --git a/src/g_level.cpp b/src/g_level.cpp
index b14959efe..8f475d3f3 100644
--- a/src/g_level.cpp
+++ b/src/g_level.cpp
@@ -1464,8 +1464,8 @@ void G_SerializeLevel (FArchive &arc, bool hubLoad)
 	P_SerializeThinkers (arc, hubLoad);
 	P_SerializeWorld (arc);
 	P_SerializePolyobjs (arc);
+	P_SerializeSubsectors(arc);
 	StatusBar->Serialize (arc);
-	//SerializeInterpolations (arc);
 
 	arc << level.total_monsters << level.total_items << level.total_secrets;
 
diff --git a/src/gameconfigfile.cpp b/src/gameconfigfile.cpp
index 936d4e7f7..acf2bbad1 100644
--- a/src/gameconfigfile.cpp
+++ b/src/gameconfigfile.cpp
@@ -399,29 +399,38 @@ void FGameConfigFile::DoGameSetup (const char *gamename)
 		ReadCVars (0);
 	}
 
-	strncpy (subsection, "Bindings", sublen);
-	if (!SetSection (section))
-	{ // Config has no bindings for the given game
-		if (!bMigrating)
-		{
-			C_SetDefaultBindings ();
-		}
-	}
-	else
+	if (!bMigrating)
 	{
-		C_UnbindAll ();
+		C_SetDefaultBindings ();
+	}
+
+	strncpy (subsection, "Bindings", sublen);
+	if (SetSection (section))
+	{
+		Bindings.UnbindAll();
 		while (NextInSection (key, value))
 		{
-			C_DoBind (key, value, false);
+			Bindings.DoBind (key, value);
 		}
 	}
 
 	strncpy (subsection, "DoubleBindings", sublen);
 	if (SetSection (section))
 	{
+		DoubleBindings.UnbindAll();
 		while (NextInSection (key, value))
 		{
-			C_DoBind (key, value, true);
+			DoubleBindings.DoBind (key, value);
+		}
+	}
+
+	strncpy (subsection, "AutomapBindings", sublen);
+	if (SetSection (section))
+	{
+		AutomapBindings.UnbindAll();
+		while (NextInSection (key, value))
+		{
+			AutomapBindings.DoBind (key, value);
 		}
 	}
 
@@ -512,11 +521,15 @@ void FGameConfigFile::ArchiveGameData (const char *gamename)
 
 	strcpy (subsection, "Bindings");
 	SetSection (section, true);
-	C_ArchiveBindings (this, false);
+	Bindings.ArchiveBindings (this);
 
 	strncpy (subsection, "DoubleBindings", sublen);
 	SetSection (section, true);
-	C_ArchiveBindings (this, true);
+	DoubleBindings.ArchiveBindings (this);
+
+	strncpy (subsection, "AutomapBindings", sublen);
+	SetSection (section, true);
+	AutomapBindings.ArchiveBindings (this);
 }
 
 void FGameConfigFile::ArchiveGlobalData ()
diff --git a/src/m_menu.h b/src/m_menu.h
index 4f45cbed2..5653b694a 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -116,6 +116,7 @@ typedef enum {
 	joy_slider,
 	joy_map,
 	joy_inverter,
+	mapcontrol,
 } itemtype;
 
 struct IJoystickConfig;
diff --git a/src/m_options.cpp b/src/m_options.cpp
index 06c3017bf..dae45c994 100644
--- a/src/m_options.cpp
+++ b/src/m_options.cpp
@@ -204,6 +204,7 @@ static menu_t ConfirmMenu = {
  *
  *=======================================*/
 
+static void StartAutomapMenu (void);
 static void CustomizeControls (void);
 static void GameplayOptions (void);
 static void CompatibilityOptions (void);
@@ -228,6 +229,7 @@ static menuitem_t OptionItems[] =
 	{ more,		"Player Setup",			{NULL},					{0.0}, {0.0},	{0.0}, {(value_t *)M_PlayerSetup} },
 	{ more,		"Gameplay Options",		{NULL},					{0.0}, {0.0},	{0.0}, {(value_t *)GameplayOptions} },
 	{ more,		"Compatibility Options",{NULL},					{0.0}, {0.0},	{0.0}, {(value_t *)CompatibilityOptions} },
+	{ more,		"Automap Options",		{NULL},					{0.0}, {0.0},	{0.0}, {(value_t *)StartAutomapMenu} },
 	{ more,		"Sound Options",		{NULL},					{0.0}, {0.0},	{0.0}, {(value_t *)SoundOptions} },
 	{ more,		"Display Options",		{NULL},					{0.0}, {0.0},	{0.0}, {(value_t *)VideoOptions} },
 	{ more,		"Set video mode",		{NULL},					{0.0}, {0.0},	{0.0}, {(value_t *)SetVidMode} },
@@ -419,13 +421,13 @@ menu_t ControlsMenu =
 	2,
 };
 
+
 /*=======================================
  *
  * Display Options Menu
  *
  *=======================================*/
 static void StartMessagesMenu (void);
-static void StartAutomapMenu (void);
 static void StartScoreboardMenu (void);
 static void InitCrosshairsList();
 
@@ -495,7 +497,6 @@ static value_t DisplayTagsTypes[] = {
 
 static menuitem_t VideoItems[] = {
 	{ more,		"Message Options",		{NULL},					{0.0}, {0.0},	{0.0}, {(value_t *)StartMessagesMenu} },
-	{ more,		"Automap Options",		{NULL},					{0.0}, {0.0},	{0.0}, {(value_t *)StartAutomapMenu} },
 	{ more,		"Scoreboard Options",	{NULL},					{0.0}, {0.0},	{0.0}, {(value_t *)StartScoreboardMenu} },
 	{ redtext,	" ",					{NULL},					{0.0}, {0.0},	{0.0}, {NULL} },
 	{ slider,	"Screen size",			{&screenblocks},	   	{3.0}, {12.0},	{1.0}, {NULL} },
@@ -538,6 +539,7 @@ menu_t VideoMenu =
  *
  *=======================================*/
 static void StartMapColorsMenu (void);
+static void StartMapControlsMenu (void);
 
 EXTERN_CVAR (Int, am_rotate)
 EXTERN_CVAR (Int, am_overlay)
@@ -548,6 +550,7 @@ EXTERN_CVAR (Bool, am_showtime)
 EXTERN_CVAR (Int, am_map_secrets)
 EXTERN_CVAR (Bool, am_showtotaltime)
 EXTERN_CVAR (Bool, am_drawmapback)
+EXTERN_CVAR (Bool, am_textured)
 
 static value_t MapColorTypes[] = {
 	{ 0, "Custom" },
@@ -577,9 +580,11 @@ static value_t OverlayTypes[] = {
 static menuitem_t AutomapItems[] = {
 	{ discrete, "Map color set",		{&am_colorset},			{4.0}, {0.0},	{0.0}, {MapColorTypes} },
 	{ more,		"Set custom colors",	{NULL},					{0.0}, {0.0},	{0.0}, {(value_t*)StartMapColorsMenu} },
+	{ more,		"Customize map controls",	{NULL},					{0.0}, {0.0},	{0.0}, {(value_t*)StartMapControlsMenu} },
 	{ redtext,	" ",					{NULL},					{0.0}, {0.0},	{0.0}, {NULL} },
 	{ discrete, "Rotate automap",		{&am_rotate},		   	{3.0}, {0.0},	{0.0}, {RotateTypes} },
 	{ discrete, "Overlay automap",		{&am_overlay},			{3.0}, {0.0},	{0.0}, {OverlayTypes} },
+	{ discrete, "Enable textured display",		{&am_textured},			{3.0}, {0.0},	{0.0}, {OnOff} },
 	{ redtext,	" ",					{NULL},					{0.0}, {0.0},	{0.0}, {NULL} },
 	{ discrete, "Show item counts",		{&am_showitems},		{2.0}, {0.0},	{0.0}, {OnOff} },
 	{ discrete, "Show monster counts",	{&am_showmonsters},		{2.0}, {0.0},	{0.0}, {OnOff} },
@@ -600,6 +605,37 @@ menu_t AutomapMenu =
 	AutomapItems,
 };
 
+menuitem_t MapControlsItems[] =
+{
+	{ redtext,"ENTER to change, BACKSPACE to clear", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} },
+	{ redtext,	" ",					{NULL},	{0.0}, {0.0}, {0.0}, {NULL} },
+	{ whitetext,"Map Controls",				{NULL},	{0.0}, {0.0}, {0.0}, {NULL} },
+	{ mapcontrol,	"Pan left",					{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_panleft"} },
+	{ mapcontrol,	"Pan right",				{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_panright"} },
+	{ mapcontrol,	"Pan up",					{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_panup"} },
+	{ mapcontrol,	"Pan down",					{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_pandown"} },
+	{ mapcontrol,	"Zoom in",					{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_zoomin"} },
+	{ mapcontrol,	"Zoom out",					{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_zoomout"} },
+	{ mapcontrol,	"Toggle zoom",				{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_gobig"} },
+	{ mapcontrol,	"Toggle follow",			{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_togglefollow"} },
+	{ mapcontrol,	"Toggle grid",				{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_togglegrid"} },
+	{ mapcontrol,	"Toggle texture",			{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_toggletexture"} },
+	{ mapcontrol,	"Set mark",					{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_setmark"} },
+	{ mapcontrol,	"Clear mark",				{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_clearmarks"} },
+};
+
+menu_t MapControlsMenu =
+{
+	"CUSTOMIZE MAP CONTROLS",
+	3,
+	countof(MapControlsItems),
+	0,
+	MapControlsItems,
+	2,
+};
+
+
+
 /*=======================================
  *
  * Map Colors Menu
@@ -1536,7 +1572,9 @@ void M_BuildKeyList (menuitem_t *item, int numitems)
 	for (i = 0; i < numitems; i++, item++)
 	{
 		if (item->type == control)
-			C_GetKeysForCommand (item->e.command, &item->b.key1, &item->c.key2);
+			Bindings.GetKeysForCommand (item->e.command, &item->b.key1, &item->c.key2);
+		else if (item->type == mapcontrol)
+			AutomapBindings.GetKeysForCommand (item->e.command, &item->b.key1, &item->c.key2);
 	}
 }
 
@@ -1825,7 +1863,7 @@ void M_OptDrawer ()
 
 			default:
 				x = indent - width;
-				color = (item->type == control && menuactive == MENU_WaitKey && i == CurrentItem)
+				color = ((item->type == control || item->type == mapcontrol) && menuactive == MENU_WaitKey && i == CurrentItem)
 					? CR_YELLOW : LabelColor;
 				break;
 			}
@@ -1983,6 +2021,7 @@ void M_OptDrawer ()
 				break;
 
 			case control:
+			case mapcontrol:
 			{
 				char description[64];
 
@@ -2165,7 +2204,14 @@ void M_OptResponder(event_t *ev)
 	{
 		if (ev->data1 != KEY_ESCAPE)
 		{
-			C_ChangeBinding(item->e.command, ev->data1);
+			if (item->type == control)
+			{
+				Bindings.SetBind(ev->data1, item->e.command);
+			}
+			else if (item->type == mapcontrol)
+			{
+				AutomapBindings.SetBind(ev->data1, item->e.command);
+			}
 			M_BuildKeyList(CurrentMenu->items, CurrentMenu->numitems);
 		}
 		menuactive = MENU_On;
@@ -2812,7 +2858,12 @@ void M_OptButtonHandler(EMenuKey key, bool repeat)
 	case MKEY_Clear:
 		if (item->type == control)
 		{
-			C_UnbindACommand (item->e.command);
+			Bindings.UnbindACommand (item->e.command);
+			item->b.key1 = item->c.key2 = 0;
+		}
+		else if (item->type == mapcontrol)
+		{
+			AutomapBindings.UnbindACommand (item->e.command);
 			item->b.key1 = item->c.key2 = 0;
 		}
 		break;
@@ -2880,7 +2931,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat)
 
 			S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE);
 		}
-		else if (item->type == control)
+		else if (item->type == control || item->type == mapcontrol)
 		{
 			menuactive = MENU_WaitKey;
 			OldMessage = CurrentMenu->items[0].label;
@@ -3000,6 +3051,12 @@ static void StartMapColorsMenu (void)
 	M_SwitchMenu (&MapColorsMenu);
 }
 
+static void StartMapControlsMenu (void)
+{
+	M_BuildKeyList (MapControlsMenu.items, MapControlsMenu.numitems);
+	M_SwitchMenu (&MapControlsMenu);
+}
+
 CCMD (menu_mapcolors)
 {
 	M_StartControlPanel (true);
@@ -3644,12 +3701,14 @@ void M_LoadKeys (const char *modname, bool dbl)
 
 	mysnprintf (section, countof(section), "%s.%s%sBindings", GameNames[gameinfo.gametype], modname,
 		dbl ? ".Double" : ".");
+
+	FKeyBindings *bindings = dbl? &DoubleBindings : &Bindings;
 	if (GameConfig->SetSection (section))
 	{
 		const char *key, *value;
 		while (GameConfig->NextInSection (key, value))
 		{
-			C_DoBind (key, value, dbl);
+			bindings->DoBind (key, value);
 		}
 	}
 }
@@ -3660,12 +3719,13 @@ int M_DoSaveKeys (FConfigFile *config, char *section, int i, bool dbl)
 
 	config->SetSection (section, true);
 	config->ClearCurrentSection ();
+	FKeyBindings *bindings = dbl? &DoubleBindings : &Bindings;
 	for (++i; i < most; ++i)
 	{
 		menuitem_t *item = &CustomControlsItems[i];
 		if (item->type == control)
 		{
-			C_ArchiveBindings (config, dbl, item->e.command);
+			bindings->ArchiveBindings (config, item->e.command);
 			continue;
 		}
 		break;
@@ -3711,7 +3771,7 @@ void FreeKeySections()
 	for (i = numStdControls; i < CustomControlsItems.Size(); ++i)
 	{
 		menuitem_t *item = &CustomControlsItems[i];
-		if (item->type == whitetext || item->type == control)
+		if (item->type == whitetext || item->type == control || item->type == mapcontrol)
 		{
 			if (item->label != NULL)
 			{
diff --git a/src/namedef.h b/src/namedef.h
index cb3e78ea2..99737eda5 100644
--- a/src/namedef.h
+++ b/src/namedef.h
@@ -442,6 +442,7 @@ xx(nofakecontrast)
 xx(smoothlighting)
 xx(blockprojectiles)
 xx(blockuse)
+xx(hidden)
 
 xx(Renderstyle)
 
diff --git a/src/nodebuild.h b/src/nodebuild.h
index d1ed2cb15..da82e26a2 100644
--- a/src/nodebuild.h
+++ b/src/nodebuild.h
@@ -111,6 +111,12 @@ class FNodeBuilder
 		bool Forward;
 	};
 
+	struct glseg_t : public seg_t
+	{
+		DWORD Partner;
+	};
+
+
 	// Like a blockmap, but for vertices instead of lines
 	class IVertexMap
 	{
@@ -200,7 +206,7 @@ public:
 	~FNodeBuilder ();
 
 	void Extract (node_t *&nodes, int &nodeCount,
-		seg_t *&segs, int &segCount,
+		seg_t *&segs, glsegextra_t *&glsegextras, int &segCount,
 		subsector_t *&ssecs, int &subCount,
 		vertex_t *&verts, int &vertCount);
 
@@ -282,10 +288,10 @@ private:
 	DWORD AddMiniseg (int v1, int v2, DWORD partner, DWORD seg1, DWORD splitseg);
 	void SetNodeFromSeg (node_t &node, const FPrivSeg *pseg) const;
 
-	int CloseSubsector (TArray<seg_t> &segs, int subsector, vertex_t *outVerts);
-	DWORD PushGLSeg (TArray<seg_t> &segs, const FPrivSeg *seg, vertex_t *outVerts);
-	void PushConnectingGLSeg (int subsector, TArray<seg_t> &segs, vertex_t *v1, vertex_t *v2);
-	int OutputDegenerateSubsector (TArray<seg_t> &segs, int subsector, bool bForward, double lastdot, FPrivSeg *&prev, vertex_t *outVerts);
+	int CloseSubsector (TArray<glseg_t> &segs, int subsector, vertex_t *outVerts);
+	DWORD PushGLSeg (TArray<glseg_t> &segs, const FPrivSeg *seg, vertex_t *outVerts);
+	void PushConnectingGLSeg (int subsector, TArray<glseg_t> &segs, vertex_t *v1, vertex_t *v2);
+	int OutputDegenerateSubsector (TArray<glseg_t> &segs, int subsector, bool bForward, double lastdot, FPrivSeg *&prev, vertex_t *outVerts);
 
 	static int STACK_ARGS SortSegs (const void *a, const void *b);
 
diff --git a/src/nodebuild_extract.cpp b/src/nodebuild_extract.cpp
index d8675aa6e..5355179ea 100644
--- a/src/nodebuild_extract.cpp
+++ b/src/nodebuild_extract.cpp
@@ -54,7 +54,7 @@
 #endif
 
 void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount,
-	seg_t *&outSegs, int &segCount,
+	seg_t *&outSegs, glsegextra_t *&outSegExtras, int &segCount,
 	subsector_t *&outSubs, int &subCount,
 	vertex_t *&outVerts, int &vertCount)
 {
@@ -99,7 +99,7 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount,
 
 	if (GLNodes)
 	{
-		TArray<seg_t> segs (Segs.Size()*5/4);
+		TArray<glseg_t> segs (Segs.Size()*5/4);
 
 		for (i = 0; i < subCount; ++i)
 		{
@@ -110,14 +110,12 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount,
 
 		segCount = segs.Size ();
 		outSegs = new seg_t[segCount];
-		memcpy (outSegs, &segs[0], segCount*sizeof(seg_t));
+		outSegExtras = new glsegextra_t[segCount];
 
 		for (i = 0; i < segCount; ++i)
 		{
-			if (outSegs[i].PartnerSeg != NULL)
-			{
-				outSegs[i].PartnerSeg = &outSegs[Segs[(unsigned int)(size_t)outSegs[i].PartnerSeg-1].storedseg];
-			}
+			outSegs[i] = *(seg_t *)&segs[i];
+			outSegExtras[i].PartnerSeg = segs[i].Partner;
 		}
 	}
 	else
@@ -125,6 +123,7 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount,
 		memcpy (outSubs, &Subsectors[0], subCount*sizeof(subsector_t));
 		segCount = Segs.Size ();
 		outSegs = new seg_t[segCount];
+		outSegExtras = NULL;
 		for (i = 0; i < segCount; ++i)
 		{
 			const FPrivSeg *org = &Segs[SegList[i].SegNum];
@@ -138,8 +137,6 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount,
 			out->frontsector = org->frontsector;
 			out->linedef = Level.Lines + org->linedef;
 			out->sidedef = Level.Sides + org->sidedef;
-			out->PartnerSeg = NULL;
-			out->bPolySeg = false;
 		}
 	}
 	for (i = 0; i < subCount; ++i)
@@ -194,19 +191,17 @@ void FNodeBuilder::ExtractMini (FMiniBSP *bsp)
 
 	if (GLNodes)
 	{
+		TArray<glseg_t> glsegs;
 		for (i = 0; i < Subsectors.Size(); ++i)
 		{
-			DWORD numsegs = CloseSubsector (bsp->Segs, i, &bsp->Verts[0]);
+			DWORD numsegs = CloseSubsector (glsegs, i, &bsp->Verts[0]);
 			bsp->Subsectors[i].numlines = numsegs;
 			bsp->Subsectors[i].firstline = &bsp->Segs[bsp->Segs.Size() - numsegs];
 		}
-
-		for (i = 0; i < Segs.Size(); ++i)
+		bsp->Segs.Resize(glsegs.Size());
+		for (i = 0; i < glsegs.Size(); ++i)
 		{
-			if (bsp->Segs[i].PartnerSeg != NULL)
-			{
-				bsp->Segs[i].PartnerSeg = &bsp->Segs[Segs[(unsigned int)(size_t)bsp->Segs[i].PartnerSeg-1].storedseg];
-			}
+			bsp->Segs[i] = *(seg_t *)&glsegs[i];
 		}
 	}
 	else
@@ -234,8 +229,6 @@ void FNodeBuilder::ExtractMini (FMiniBSP *bsp)
 				out->linedef = NULL;
 				out->sidedef = NULL;
 			}
-			out->PartnerSeg = NULL;
-			out->bPolySeg = false;
 		}
 		for (i = 0; i < bsp->Subsectors.Size(); ++i)
 		{
@@ -244,7 +237,7 @@ void FNodeBuilder::ExtractMini (FMiniBSP *bsp)
 	}
 }
 
-int FNodeBuilder::CloseSubsector (TArray<seg_t> &segs, int subsector, vertex_t *outVerts)
+int FNodeBuilder::CloseSubsector (TArray<glseg_t> &segs, int subsector, vertex_t *outVerts)
 {
 	FPrivSeg *seg, *prev;
 	angle_t prevAngle;
@@ -406,7 +399,7 @@ int FNodeBuilder::CloseSubsector (TArray<seg_t> &segs, int subsector, vertex_t *
 	return count;
 }
 
-int FNodeBuilder::OutputDegenerateSubsector (TArray<seg_t> &segs, int subsector, bool bForward, double lastdot, FPrivSeg *&prev, vertex_t *outVerts)
+int FNodeBuilder::OutputDegenerateSubsector (TArray<glseg_t> &segs, int subsector, bool bForward, double lastdot, FPrivSeg *&prev, vertex_t *outVerts)
 {
 	static const double bestinit[2] = { -DBL_MAX, DBL_MAX };
 	FPrivSeg *seg;
@@ -473,9 +466,9 @@ int FNodeBuilder::OutputDegenerateSubsector (TArray<seg_t> &segs, int subsector,
 	return count;
 }
 
-DWORD FNodeBuilder::PushGLSeg (TArray<seg_t> &segs, const FPrivSeg *seg, vertex_t *outVerts)
+DWORD FNodeBuilder::PushGLSeg (TArray<glseg_t> &segs, const FPrivSeg *seg, vertex_t *outVerts)
 {
-	seg_t newseg;
+	glseg_t newseg;
 
 	newseg.v1 = outVerts + seg->v1;
 	newseg.v2 = outVerts + seg->v2;
@@ -491,14 +484,13 @@ DWORD FNodeBuilder::PushGLSeg (TArray<seg_t> &segs, const FPrivSeg *seg, vertex_
 		newseg.linedef = NULL;
 		newseg.sidedef = NULL;
 	}
-	newseg.PartnerSeg = (seg_t *)(seg->partner == DWORD_MAX ? 0 : (size_t)seg->partner + 1);
-	newseg.bPolySeg = false;
+	newseg.Partner = seg->partner;
 	return (DWORD)segs.Push (newseg);
 }
 
-void FNodeBuilder::PushConnectingGLSeg (int subsector, TArray<seg_t> &segs, vertex_t *v1, vertex_t *v2)
+void FNodeBuilder::PushConnectingGLSeg (int subsector, TArray<glseg_t> &segs, vertex_t *v1, vertex_t *v2)
 {
-	seg_t newseg;
+	glseg_t newseg;
 
 	newseg.v1 = v1;
 	newseg.v2 = v2;
@@ -506,7 +498,6 @@ void FNodeBuilder::PushConnectingGLSeg (int subsector, TArray<seg_t> &segs, vert
 	newseg.frontsector = NULL;
 	newseg.linedef = NULL;
 	newseg.sidedef = NULL;
-	newseg.PartnerSeg = NULL;
-	newseg.bPolySeg = false;
+	newseg.Partner = DWORD_MAX;
 	segs.Push (newseg);
 }
diff --git a/src/p_acs.cpp b/src/p_acs.cpp
index f04cfbdff..bf480fe73 100644
--- a/src/p_acs.cpp
+++ b/src/p_acs.cpp
@@ -5097,7 +5097,7 @@ int DLevelScript::RunScript ()
 			{
 				int key1 = 0, key2 = 0;
 
-				C_GetKeysForCommand ((char *)lookup, &key1, &key2);
+				Bindings.GetKeysForCommand ((char *)lookup, &key1, &key2);
 
 				if (key2)
 					work << KeyNames[key1] << " or " << KeyNames[key2];
diff --git a/src/p_glnodes.cpp b/src/p_glnodes.cpp
new file mode 100644
index 000000000..426552c37
--- /dev/null
+++ b/src/p_glnodes.cpp
@@ -0,0 +1,1537 @@
+/*
+** gl_nodes.cpp
+**
+**---------------------------------------------------------------------------
+** Copyright 2005-2010 Christoph Oelckers
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+#include <math.h>
+#ifdef _MSC_VER
+#include <malloc.h>		// for alloca()
+#include <direct.h>
+#endif
+
+#ifndef _WIN32
+#include <unistd.h>
+
+#else
+
+#define rmdir _rmdir
+
+// TODO, maybe: stop using DWORD so I don't need to worry about conflicting
+// with Windows' typedef. Then I could just include the header file instead
+// of declaring everything here.
+#define MAX_PATH				260
+#define CSIDL_LOCAL_APPDATA		0x001c
+extern "C" __declspec(dllimport) long __stdcall SHGetFolderPathA(void *hwnd, int csidl, void *hToken, unsigned long dwFlags, char *pszPath);
+
+#endif
+
+#ifdef __APPLE__
+#include <CoreServices/CoreServices.h>
+#endif
+
+#include "templates.h"
+#include "m_alloc.h"
+#include "m_argv.h"
+#include "c_dispatch.h"
+#include "m_swap.h"
+#include "g_game.h"
+#include "i_system.h"
+#include "w_wad.h"
+#include "doomdef.h"
+#include "p_local.h"
+#include "nodebuild.h"
+#include "doomstat.h"
+#include "vectors.h"
+#include "stats.h"
+#include "doomerrors.h"
+#include "p_setup.h"
+#include "x86.h"
+#include "version.h"
+#include "md5.h"
+
+void P_GetPolySpots (MapData * lump, TArray<FNodeBuilder::FPolyStart> &spots, TArray<FNodeBuilder::FPolyStart> &anchors);
+
+CVAR(Bool, gl_cachenodes, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
+CVAR(Float, gl_cachetime, 0.6f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
+
+void P_LoadZNodes (FileReader &dalump, DWORD id);
+static bool CheckCachedNodes(MapData *map);
+static void CreateCachedNodes(MapData *map);
+
+
+// fixed 32 bit gl_vert format v2.0+ (glBsp 1.91)
+struct mapglvertex_t
+{
+  fixed_t x,y;
+};
+
+struct gl3_mapsubsector_t
+{
+	SDWORD numsegs;
+	SDWORD firstseg;    // Index of first one; segs are stored sequentially.
+};
+
+struct glseg_t
+{
+	WORD	v1;		 // start vertex		(16 bit)
+	WORD	v2;		 // end vertex			(16 bit)
+	WORD	linedef; // linedef, or -1 for minisegs
+	WORD	side;	 // side on linedef: 0 for right, 1 for left
+	WORD	partner; // corresponding partner seg, or 0xffff on one-sided walls
+};
+
+struct glseg3_t
+{
+	SDWORD			v1;
+	SDWORD			v2;
+	WORD			linedef;
+	WORD			side;
+	SDWORD			partner;
+};
+
+struct gl5_mapnode_t
+{
+	SWORD 	x,y,dx,dy;	// partition line
+	SWORD 	bbox[2][4];	// bounding box for each child
+	// If NF_SUBSECTOR is or'ed in, it's a subsector,
+	// else it's a node of another subtree.
+	DWORD children[2];
+};
+
+
+
+//==========================================================================
+//
+// Collect all sidedefs which are not entirely covered by segs
+// Old ZDBSPs could create such maps. If such a BSP is discovered
+// a node rebuild must be done to ensure proper rendering
+//
+//==========================================================================
+
+static int CheckForMissingSegs()
+{
+	float *added_seglen = new float[numsides];
+	int missing = 0;
+
+	memset(added_seglen, 0, sizeof(float)*numsides);
+	for(int i=0;i<numsegs;i++)
+	{
+		seg_t * seg = &segs[i];
+
+		if (seg->sidedef!=NULL)
+		{
+			// check all the segs and calculate the length they occupy on their sidedef
+			TVector2<double> vec1(seg->v2->x - seg->v1->x, seg->v2->y - seg->v1->y);
+			added_seglen[seg->sidedef - sides] += float(vec1.Length());
+		}
+	}
+
+	for(int i=0;i<numsides;i++)
+	{
+		side_t * side =&sides[i];
+		line_t * line = side->linedef;
+
+		TVector2<double> lvec(line->dx, line->dy);
+		float linelen = float(lvec.Length());
+
+		missing += (added_seglen[i] < linelen - FRACUNIT);
+	}
+
+	delete [] added_seglen;
+	return missing;
+}
+
+//==========================================================================
+//
+// Checks whether the nodes are suitable for GL rendering
+//
+//==========================================================================
+
+bool P_CheckForGLNodes()
+{
+	int i;
+
+	for(i=0;i<numsubsectors;i++)
+	{
+		subsector_t * sub = &subsectors[i];
+		seg_t * firstseg = sub->firstline;
+		seg_t * lastseg = sub->firstline + sub->numlines - 1;
+
+		if (firstseg->v1 != lastseg->v2)
+		{
+			// This subsector is incomplete which means that these
+			// are normal nodes
+			return false;
+		}
+		else
+		{
+			for(DWORD j=0;j<sub->numlines;j++)
+			{
+				if (segs[j].linedef==NULL)	// miniseg
+				{
+					// We already have GL nodes. Great!
+					return true;
+				}
+			}
+		}
+	}
+	// all subsectors were closed but there are no minisegs
+	// Although unlikely this can happen. Such nodes are not a problem.
+	// all that is left is to check whether the BSP covers all sidedefs completely.
+	int missing = CheckForMissingSegs();
+	if (missing > 0)
+	{
+		Printf("%d missing segs counted\nThe BSP needs to be rebuilt", missing);
+	}
+	return missing == 0;
+}
+
+
+//==========================================================================
+//
+// LoadGLVertexes
+//
+// loads GL vertices
+//
+//==========================================================================
+
+#define gNd2		MAKE_ID('g','N','d','2')
+#define gNd4		MAKE_ID('g','N','d','4')
+#define gNd5		MAKE_ID('g','N','d','5')
+
+#define GL_VERT_OFFSET  4
+static int firstglvertex;
+static bool format5;
+
+static bool LoadGLVertexes(FileReader * f, wadlump_t * lump)
+{
+	BYTE *gldata;
+	int                 i;
+
+	firstglvertex = numvertexes;
+	
+	int gllen=lump->Size;
+
+	gldata = new BYTE[gllen];
+	f->Seek(lump->FilePos, SEEK_SET);
+	f->Read(gldata, gllen);
+
+	if (*(int *)gldata == gNd5) 
+	{
+		format5=true;
+	}
+	else if (*(int *)gldata != gNd2) 
+	{
+		// GLNodes V1 and V4 are unsupported.
+		// V1 because the precision is insufficient and
+		// V4 due to the missing partner segs
+		Printf("GL nodes v%d found. This format is not supported by "GAMENAME"\n",
+			(*(int *)gldata == gNd4)? 4:1);
+
+		delete [] gldata;
+		return false;
+	}
+	else format5=false;
+
+	mapglvertex_t*	mgl;
+
+	vertex_t * oldvertexes = vertexes;
+	numvertexes += (gllen - GL_VERT_OFFSET)/sizeof(mapglvertex_t);
+	vertexes	 = new vertex_t[numvertexes];
+	mgl			 = (mapglvertex_t *) (gldata + GL_VERT_OFFSET);	
+
+	memcpy(vertexes, oldvertexes, firstglvertex * sizeof(vertex_t));
+	for(i=0;i<numlines;i++)
+	{
+		lines[i].v1 = vertexes + (lines[i].v1 - oldvertexes);
+		lines[i].v2 = vertexes + (lines[i].v2 - oldvertexes);
+	}
+
+	for (i = firstglvertex; i < numvertexes; i++)
+	{
+		vertexes[i].x = LittleLong(mgl->x);
+		vertexes[i].y = LittleLong(mgl->y);
+		mgl++;
+	}
+	delete[] gldata;
+	return true;
+}
+
+//==========================================================================
+//
+// GL Nodes utilities
+//
+//==========================================================================
+
+static inline int checkGLVertex(int num)
+{
+	if (num & 0x8000)
+		num = (num&0x7FFF)+firstglvertex;
+	return num;
+}
+
+static inline int checkGLVertex3(int num)
+{
+	if (num & 0xc0000000)
+		num = (num&0x3FFFFFFF)+firstglvertex;
+	return num;
+}
+
+//==========================================================================
+//
+// LoadGLSegs
+//
+//==========================================================================
+
+static bool LoadGLSegs(FileReader * f, wadlump_t * lump)
+{
+	char		*data;
+	int			i;
+	line_t		*ldef=NULL;
+	
+	numsegs = lump->Size;
+	data= new char[numsegs];
+	f->Seek(lump->FilePos, SEEK_SET);
+	f->Read(data, lump->Size);
+	segs=NULL;
+
+#ifdef _MSC_VER
+	__try
+#endif
+	{
+		if (!format5 && memcmp(data, "gNd3", 4))
+		{
+			numsegs/=sizeof(glseg_t);
+			segs = new seg_t[numsegs];
+			memset(segs,0,sizeof(seg_t)*numsegs);
+			glsegextras = new glsegextra_t[numsegs];
+			
+			glseg_t * ml = (glseg_t*)data;
+			for(i = 0; i < numsegs; i++)
+			{							// check for gl-vertices
+				segs[i].v1 = &vertexes[checkGLVertex(LittleShort(ml->v1))];
+				segs[i].v2 = &vertexes[checkGLVertex(LittleShort(ml->v2))];
+				
+				glsegextras[i].PartnerSeg = ml->partner == 0xFFFF ? DWORD_MAX : LittleShort(ml->partner);
+				if(ml->linedef != 0xffff)
+				{
+					ldef = &lines[LittleShort(ml->linedef)];
+					segs[i].linedef = ldef;
+	
+					
+					ml->side=LittleShort(ml->side);
+					segs[i].sidedef = ldef->sidedef[ml->side];
+					segs[i].frontsector = ldef->sidedef[ml->side]->sector;
+					if (ldef->flags & ML_TWOSIDED && ldef->sidedef[ml->side^1] != NULL)
+					{
+						segs[i].backsector = ldef->sidedef[ml->side^1]->sector;
+					}
+					else
+					{
+						ldef->flags &= ~ML_TWOSIDED;
+						segs[i].backsector = 0;
+					}
+	
+				}
+				else
+				{
+					segs[i].linedef = NULL;
+					segs[i].sidedef = NULL;
+	
+					segs[i].frontsector = NULL;
+					segs[i].backsector  = NULL;
+				}
+				ml++;		
+			}
+		}
+		else
+		{
+			if (!format5) numsegs-=4;
+			numsegs/=sizeof(glseg3_t);
+			segs = new seg_t[numsegs];
+			memset(segs,0,sizeof(seg_t)*numsegs);
+			glsegextras = new glsegextra_t[numsegs];
+			
+			glseg3_t * ml = (glseg3_t*)(data+ (format5? 0:4));
+			for(i = 0; i < numsegs; i++)
+			{							// check for gl-vertices
+				segs[i].v1 = &vertexes[checkGLVertex3(LittleLong(ml->v1))];
+				segs[i].v2 = &vertexes[checkGLVertex3(LittleLong(ml->v2))];
+				
+				glsegextras[i].PartnerSeg = LittleLong(ml->partner);
+	
+				if(ml->linedef != 0xffff) // skip minisegs 
+				{
+					ldef = &lines[LittleLong(ml->linedef)];
+					segs[i].linedef = ldef;
+	
+					
+					ml->side=LittleShort(ml->side);
+					segs[i].sidedef = ldef->sidedef[ml->side];
+					segs[i].frontsector = ldef->sidedef[ml->side]->sector;
+					if (ldef->flags & ML_TWOSIDED && ldef->sidedef[ml->side^1] != NULL)
+					{
+						segs[i].backsector = ldef->sidedef[ml->side^1]->sector;
+					}
+					else
+					{
+						ldef->flags &= ~ML_TWOSIDED;
+						segs[i].backsector = 0;
+					}
+	
+				}
+				else
+				{
+					segs[i].linedef = NULL;
+					segs[i].sidedef = NULL;
+					segs[i].frontsector = NULL;
+					segs[i].backsector  = NULL;
+				}
+				ml++;		
+			}
+		}
+		delete [] data;
+		return true;
+	}
+#ifdef _MSC_VER
+	__except(1)
+	{
+		// Invalid data has the bad habit of requiring extensive checks here
+		// so let's just catch anything invalid and output a message.
+		// (at least under MSVC. GCC can't do SEH even for Windows... :( )
+		Printf("Invalid GL segs. The BSP will have to be rebuilt.\n");
+		delete [] data;
+		delete [] segs;
+		segs = NULL;
+		return false;
+	}
+#endif
+}
+
+
+//==========================================================================
+//
+// LoadGLSubsectors
+//
+//==========================================================================
+
+static bool LoadGLSubsectors(FileReader * f, wadlump_t * lump)
+{
+	char * datab;
+	int  i;
+	
+	numsubsectors = lump->Size;
+	datab = new char[numsubsectors];
+	f->Seek(lump->FilePos, SEEK_SET);
+	f->Read(datab, lump->Size);
+	
+	if (numsubsectors == 0)
+	{
+		delete [] datab;
+		return false;
+	}
+	
+	if (!format5 && memcmp(datab, "gNd3", 4))
+	{
+		mapsubsector_t * data = (mapsubsector_t*) datab;
+		numsubsectors /= sizeof(mapsubsector_t);
+		subsectors = new subsector_t[numsubsectors];
+		memset(subsectors,0,numsubsectors * sizeof(subsector_t));
+	
+		for (i=0; i<numsubsectors; i++)
+		{
+			subsectors[i].numlines  = LittleShort(data[i].numsegs );
+			subsectors[i].firstline = segs + LittleShort(data[i].firstseg);
+
+			if (subsectors[i].numlines == 0)
+			{
+				delete [] datab;
+				return false;
+			}
+		}
+	}
+	else
+	{
+		gl3_mapsubsector_t * data = (gl3_mapsubsector_t*) (datab+(format5? 0:4));
+		numsubsectors /= sizeof(gl3_mapsubsector_t);
+		subsectors = new subsector_t[numsubsectors];
+		memset(subsectors,0,numsubsectors * sizeof(subsector_t));
+	
+		for (i=0; i<numsubsectors; i++)
+		{
+			subsectors[i].numlines  = LittleLong(data[i].numsegs );
+			subsectors[i].firstline = segs + LittleLong(data[i].firstseg);
+
+			if (subsectors[i].numlines == 0)
+			{
+				delete [] datab;
+				return false;
+			}
+		}
+	}
+
+	for (i=0; i<numsubsectors; i++)
+	{
+		for(unsigned j=0;j<subsectors[i].numlines;j++)
+		{
+			seg_t * seg = subsectors[i].firstline + j;
+			if (seg->linedef==NULL) seg->frontsector = seg->backsector = subsectors[i].firstline->frontsector;
+		}
+		seg_t *firstseg = subsectors[i].firstline;
+		seg_t *lastseg = subsectors[i].firstline + subsectors[i].numlines - 1;
+		// The subsector must be closed. If it isn't we can't use these nodes and have to do a rebuild.
+		if (lastseg->v2 != firstseg->v1)
+		{
+			delete [] datab;
+			return false;
+		}
+
+	}
+	delete [] datab;
+	return true;
+}
+
+//==========================================================================
+//
+// P_LoadNodes
+//
+//==========================================================================
+
+static bool LoadNodes (FileReader * f, wadlump_t * lump)
+{
+	const int NF_SUBSECTOR = 0x8000;
+	const int GL5_NF_SUBSECTOR = (1 << 31);
+
+	int 		i;
+	int 		j;
+	int 		k;
+	node_t* 	no;
+	WORD*		used;
+
+	if (!format5)
+	{
+		mapnode_t*	mn, * basemn;
+		numnodes = lump->Size / sizeof(mapnode_t);
+
+		if (numnodes == 0) return false;
+
+		nodes = new node_t[numnodes];		
+		f->Seek(lump->FilePos, SEEK_SET);
+
+		basemn = mn = new mapnode_t[numnodes];
+		f->Read(mn, lump->Size);
+
+		used = (WORD *)alloca (sizeof(WORD)*numnodes);
+		memset (used, 0, sizeof(WORD)*numnodes);
+
+		no = nodes;
+
+		for (i = 0; i < numnodes; i++, no++, mn++)
+		{
+			no->x = LittleShort(mn->x)<<FRACBITS;
+			no->y = LittleShort(mn->y)<<FRACBITS;
+			no->dx = LittleShort(mn->dx)<<FRACBITS;
+			no->dy = LittleShort(mn->dy)<<FRACBITS;
+			for (j = 0; j < 2; j++)
+			{
+				WORD child = LittleShort(mn->children[j]);
+				if (child & NF_SUBSECTOR)
+				{
+					child &= ~NF_SUBSECTOR;
+					if (child >= numsubsectors)
+					{
+						delete [] basemn;
+						return false;
+					}
+					no->children[j] = (BYTE *)&subsectors[child] + 1;
+				}
+				else if (child >= numnodes)
+				{
+					delete [] basemn;
+					return false;
+				}
+				else if (used[child])
+				{
+					delete [] basemn;
+					return false;
+				}
+				else
+				{
+					no->children[j] = &nodes[child];
+					used[child] = j + 1;
+				}
+				for (k = 0; k < 4; k++)
+				{
+					no->bbox[j][k] = LittleShort(mn->bbox[j][k])<<FRACBITS;
+				}
+			}
+		}
+		delete [] basemn;
+	}
+	else
+	{
+		gl5_mapnode_t*	mn, * basemn;
+		numnodes = lump->Size / sizeof(gl5_mapnode_t);
+
+		if (numnodes == 0) return false;
+
+		nodes = new node_t[numnodes];		
+		f->Seek(lump->FilePos, SEEK_SET);
+
+		basemn = mn = new gl5_mapnode_t[numnodes];
+		f->Read(mn, lump->Size);
+
+		used = (WORD *)alloca (sizeof(WORD)*numnodes);
+		memset (used, 0, sizeof(WORD)*numnodes);
+
+		no = nodes;
+
+		for (i = 0; i < numnodes; i++, no++, mn++)
+		{
+			no->x = LittleShort(mn->x)<<FRACBITS;
+			no->y = LittleShort(mn->y)<<FRACBITS;
+			no->dx = LittleShort(mn->dx)<<FRACBITS;
+			no->dy = LittleShort(mn->dy)<<FRACBITS;
+			for (j = 0; j < 2; j++)
+			{
+				SDWORD child = LittleLong(mn->children[j]);
+				if (child & GL5_NF_SUBSECTOR)
+				{
+					child &= ~GL5_NF_SUBSECTOR;
+					if (child >= numsubsectors)
+					{
+						delete [] basemn;
+						return false;
+					}
+					no->children[j] = (BYTE *)&subsectors[child] + 1;
+				}
+				else if (child >= numnodes)
+				{
+					delete [] basemn;
+					return false;
+				}
+				else if (used[child])
+				{
+					delete [] basemn;
+					return false;
+				}
+				else
+				{
+					no->children[j] = &nodes[child];
+					used[child] = j + 1;
+				}
+				for (k = 0; k < 4; k++)
+				{
+					no->bbox[j][k] = LittleShort(mn->bbox[j][k])<<FRACBITS;
+				}
+			}
+		}
+		delete [] basemn;
+	}
+	return true;
+}
+
+//==========================================================================
+//
+// loads the GL node data
+//
+//==========================================================================
+
+static bool DoLoadGLNodes(FileReader * f, wadlump_t * lumps)
+{
+	if (!LoadGLVertexes(f, &lumps[0]))
+	{
+		return false;
+	}
+	if (!LoadGLSegs(f, &lumps[1]))
+	{
+		delete [] segs;
+		segs = NULL;
+		return false;
+	}
+	if (!LoadGLSubsectors(f, &lumps[2]))
+	{
+		delete [] subsectors;
+		subsectors = NULL;
+		delete [] segs;
+		segs = NULL;
+		return false;
+	}
+	if (!LoadNodes(f, &lumps[3]))
+	{
+		delete [] nodes;
+		nodes = NULL;
+		delete [] subsectors;
+		subsectors = NULL;
+		delete [] segs;
+		segs = NULL;
+		return false;
+	}
+
+	// Quick check for the validity of the nodes
+	// For invalid nodes there is a high chance that this test will fail
+
+	for (int i = 0; i < numsubsectors; i++)
+	{
+		seg_t * seg = subsectors[i].firstline;
+		if (!seg->sidedef) 
+		{
+			Printf("GL nodes contain invalid data. The BSP has to be rebuilt.\n");
+			delete [] nodes;
+			nodes = NULL;
+			delete [] subsectors;
+			subsectors = NULL;
+			delete [] segs;
+			segs = NULL;
+			return false;
+		}
+	}
+
+	// check whether the BSP covers all sidedefs completely.
+	int missing = CheckForMissingSegs();
+	if (missing > 0)
+	{
+		Printf("%d missing segs counted in GL nodes.\nThe BSP has to be rebuilt", missing);
+	}
+	return missing == 0;
+}
+
+
+//===========================================================================
+//
+// MatchHeader
+//
+// Checks whether a GL_LEVEL header belongs to this level
+//
+//===========================================================================
+
+static bool MatchHeader(const char * label, const char * hdata)
+{
+	if (!memcmp(hdata, "LEVEL=", 6) == 0)
+	{
+		size_t labellen = strlen(label);
+
+		if (strnicmp(hdata+6, label, labellen)==0 && 
+			(hdata[6+labellen]==0xa || hdata[6+labellen]==0xd))
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+//===========================================================================
+//
+// FindGLNodesInWAD
+//
+// Looks for GL nodes in the same WAD as the level itself
+//
+//===========================================================================
+
+static int FindGLNodesInWAD(int labellump)
+{
+	int wadfile = Wads.GetLumpFile(labellump);
+	FString glheader;
+
+	glheader.Format("GL_%s", Wads.GetLumpFullName(labellump));
+	if (glheader.Len()<=8)
+	{
+		int gllabel = Wads.CheckNumForName(glheader, ns_global, wadfile);
+		if (gllabel >= 0) return gllabel;
+	}
+	else
+	{
+		// Before scanning the entire WAD directory let's check first whether
+		// it is necessary.
+		int gllabel = Wads.CheckNumForName("GL_LEVEL", ns_global, wadfile);
+
+		if (gllabel >= 0)
+		{
+			int lastlump=0;
+			int lump;
+			while ((lump=Wads.FindLump("GL_LEVEL", &lastlump))>=0)
+			{
+				if (Wads.GetLumpFile(lump)==wadfile)
+				{
+					FMemLump mem = Wads.ReadLump(lump);
+					if (MatchHeader(Wads.GetLumpFullName(labellump), (const char *)mem.GetMem())) return true;
+				}
+			}
+		}
+	}
+	return -1;
+}
+
+//===========================================================================
+//
+// FindGLNodesInWAD
+//
+// Looks for GL nodes in the same WAD as the level itself
+// When this function returns the file pointer points to
+// the directory entry of the GL_VERTS lump
+//
+//===========================================================================
+
+static int FindGLNodesInFile(FileReader * f, const char * label)
+{
+	FString glheader;
+	bool mustcheck=false;
+	DWORD id, dirofs, numentries;
+	DWORD offset, size;
+	char lumpname[9];
+
+	glheader.Format("GL_%.8s", label);
+	if (glheader.Len()>8)
+	{
+		glheader="GL_LEVEL";
+		mustcheck=true;
+	}
+
+	f->Seek(0, SEEK_SET);
+	(*f) >> id >> numentries >> dirofs;
+
+	if ((id == IWAD_ID || id == PWAD_ID) && numentries > 4)
+	{
+		f->Seek(dirofs, SEEK_SET);
+		for(DWORD i=0;i<numentries-4;i++)
+		{
+			(*f) >> offset >> size;
+			f->Read(lumpname, 8);
+			if (!strnicmp(lumpname, glheader, 8))
+			{
+				if (mustcheck)
+				{
+					char check[16]={0};
+					int filepos = f->Tell();
+					f->Seek(offset, SEEK_SET);
+					f->Read(check, 16);
+					f->Seek(filepos, SEEK_SET);
+					if (MatchHeader(label, check)) return i;
+				}
+				else return i;
+			}
+		}
+	}
+	return -1;
+}
+
+//==========================================================================
+//
+// Checks for the presence of GL nodes in the loaded WADs or a .GWA file
+// returns true if successful
+//
+//==========================================================================
+
+bool P_LoadGLNodes(MapData * map)
+{
+	if (!CheckCachedNodes(map))
+	{
+		wadlump_t gwalumps[4];
+		char path[256];
+		int li;
+		int lumpfile = Wads.GetLumpFile(map->lumpnum);
+		bool mapinwad = map->file == Wads.GetFileReader(lumpfile);
+		FileReader * fr = map->file;
+		FILE * f_gwa = NULL;
+
+		const char * name = Wads.GetWadFullName(lumpfile);
+
+		if (mapinwad)
+		{
+			li = FindGLNodesInWAD(map->lumpnum);
+
+			if (li>=0)
+			{
+				// GL nodes are loaded with a WAD
+				for(int i=0;i<4;i++)
+				{
+					gwalumps[i].FilePos=Wads.GetLumpOffset(li+i+1);
+					gwalumps[i].Size=Wads.LumpLength(li+i+1);
+				}
+				return DoLoadGLNodes(fr, gwalumps);
+			}
+			else
+			{
+				strcpy(path, name);
+
+				char * ext = strrchr(path, '.');
+				if (ext)
+				{
+					strcpy(ext, ".gwa");
+					// Todo: Compare file dates
+
+					f_gwa = fopen(path, "rb");
+					if (f_gwa==NULL) return false;
+
+					fr = new FileReader(f_gwa);
+
+					strncpy(map->MapLumps[0].Name, Wads.GetLumpFullName(map->lumpnum), 8);
+				}
+			}
+		}
+
+		bool result = false;
+		li = FindGLNodesInFile(fr, map->MapLumps[0].Name);
+		if (li!=-1)
+		{
+			static const char check[][9]={"GL_VERT","GL_SEGS","GL_SSECT","GL_NODES"};
+			result=true;
+			for(unsigned i=0; i<4;i++)
+			{
+				(*fr) >> gwalumps[i].FilePos;
+				(*fr) >> gwalumps[i].Size;
+				fr->Read(gwalumps[i].Name, 8);
+				if (strnicmp(gwalumps[i].Name, check[i], 8))
+				{
+					result=false;
+					break;
+				}
+			}
+			if (result) result = DoLoadGLNodes(fr, gwalumps);
+		}
+
+		if (f_gwa)
+		{
+			delete fr;
+			fclose(f_gwa);
+		}
+		return result;
+	}
+	else return true;
+}
+
+//==========================================================================
+//
+// Checks whether nodes are GL friendly or not
+//
+//==========================================================================
+
+bool P_CheckNodes(MapData * map, bool rebuilt, int buildtime)
+{
+	bool ret = false;
+
+	// If the map loading code has performed a node rebuild we don't need to check for it again.
+	if (!rebuilt && !P_CheckForGLNodes())
+	{
+		ret = true;	// we are not using the level's original nodes if we get here.
+		for (int i = 0; i < numsubsectors; i++)
+		{
+			gamesubsectors[i].sector = gamesubsectors[i].firstline->sidedef->sector;
+		}
+
+		nodes = NULL;
+		numnodes = 0;
+		subsectors = NULL;
+		numsubsectors = 0;
+		if (segs) delete [] segs;
+		segs = NULL;
+		numsegs = 0;
+
+		// Try to load GL nodes (cached or GWA)
+		if (!P_LoadGLNodes(map))
+		{
+			// none found - we have to build new ones!
+			unsigned int startTime, endTime;
+
+			startTime = I_FPSTime ();
+			TArray<FNodeBuilder::FPolyStart> polyspots, anchors;
+			P_GetPolySpots (map, polyspots, anchors);
+			FNodeBuilder::FLevel leveldata =
+			{
+				vertexes, numvertexes,
+				sides, numsides,
+				lines, numlines
+			};
+			leveldata.FindMapBounds ();
+			FNodeBuilder builder (leveldata, polyspots, anchors, true);
+			delete[] vertexes;
+			builder.Extract (nodes, numnodes,
+				segs, glsegextras, numsegs,
+				subsectors, numsubsectors,
+				vertexes, numvertexes);
+			endTime = I_FPSTime ();
+			DPrintf ("BSP generation took %.3f sec (%d segs)\n", (endTime - startTime) * 0.001, numsegs);
+			buildtime = endTime - startTime;
+		}
+	}
+
+#ifdef DEBUG
+	// Building nodes in debug is much slower so let's cache them only if cachetime is 0
+	buildtime = 0;
+#endif
+	if (gl_cachenodes && buildtime/1000.f >= gl_cachetime)
+	{
+		DPrintf("Caching nodes\n");
+		CreateCachedNodes(map);
+	}
+	else
+	{
+		DPrintf("Not caching nodes (time = %f)\n", buildtime/1000.f);
+	}
+
+
+	if (!gamenodes)
+	{
+		gamenodes = nodes;
+		numgamenodes = numnodes;
+		gamesubsectors = subsectors;
+		numgamesubsectors = numsubsectors;
+	}
+	return ret;
+}
+
+//==========================================================================
+//
+// Node caching
+//
+//==========================================================================
+
+typedef TArray<BYTE> MemFile;
+
+static FString GetCachePath()
+{
+	FString path;
+
+#ifdef _WIN32
+	char pathstr[MAX_PATH];
+	if (0 != SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, pathstr))
+	{ // Failed (e.g. On Win9x): use program directory
+		path = progdir;
+	}
+	else
+	{
+		path = pathstr;
+	}
+	path += "/zdoom/cache";
+#elif defined(__APPLE__)
+	char pathstr[PATH_MAX];
+	FSRef folder;
+
+	if (noErr == FSFindFolder(kLocalDomain, kApplicationSupportFolderType, kCreateFolder, &folder) &&
+		noErr == FSRefMakePath(&folder, (UInt8*)cpath, PATH_MAX))
+	{
+		path = pathstr;
+	}
+	else
+	{
+		path = progdir;
+	}
+	path += "/zdoom/cache";
+#else
+	// Don't use GAME_DIR and such so that ZDoom and its child ports can share the node cache.
+	path = NicePath("~/.zdoom/cache");
+#endif
+	return path;
+}
+
+static FString CreateCacheName(MapData *map, bool create)
+{
+	FString path = GetCachePath();
+	FString lumpname = Wads.GetLumpFullPath(map->lumpnum);
+	int separator = lumpname.IndexOf(':');
+	path << '/' << lumpname.Left(separator);
+	if (create) CreatePath(path);
+
+	lumpname.ReplaceChars('/', '%');
+	path << '/' << lumpname.Right(lumpname.Len() - separator - 1) << ".gzc";
+	return path;
+}
+
+static void WriteByte(MemFile &f, BYTE b)
+{
+	f.Push(b);
+}
+
+static void WriteWord(MemFile &f, WORD b)
+{
+	int v = f.Reserve(2);
+	f[v] = (BYTE)b;
+	f[v+1] = (BYTE)(b>>8);
+}
+
+static void WriteLong(MemFile &f, DWORD b)
+{
+	int v = f.Reserve(4);
+	f[v] = (BYTE)b;
+	f[v+1] = (BYTE)(b>>8);
+	f[v+2] = (BYTE)(b>>16);
+	f[v+3] = (BYTE)(b>>24);
+}
+
+static void CreateCachedNodes(MapData *map)
+{
+	MemFile ZNodes;
+
+	WriteLong(ZNodes, 0);
+	WriteLong(ZNodes, numvertexes);
+	for(int i=0;i<numvertexes;i++)
+	{
+		WriteLong(ZNodes, vertexes[i].x);
+		WriteLong(ZNodes, vertexes[i].y);
+	}
+
+	WriteLong(ZNodes, numsubsectors);
+	for(int i=0;i<numsubsectors;i++)
+	{
+		WriteLong(ZNodes, subsectors[i].numlines);
+	}
+
+	WriteLong(ZNodes, numsegs);
+	for(int i=0;i<numsegs;i++)
+	{
+		WriteLong(ZNodes, DWORD(segs[i].v1 - vertexes));
+		WriteLong(ZNodes, DWORD(glsegextras[i].PartnerSeg));
+		if (segs[i].linedef)
+		{
+			WriteLong(ZNodes, DWORD(segs[i].linedef - lines));
+			WriteByte(ZNodes, segs[i].sidedef == segs[i].linedef->sidedef[0]? 0:1);
+		}
+		else
+		{
+			WriteLong(ZNodes, 0xffffffffu);
+			WriteByte(ZNodes, 0);
+		}
+	}
+
+	WriteLong(ZNodes, numnodes);
+	for(int i=0;i<numnodes;i++)
+	{
+		WriteWord(ZNodes, nodes[i].x >> FRACBITS);
+		WriteWord(ZNodes, nodes[i].y >> FRACBITS);
+		WriteWord(ZNodes, nodes[i].dx >> FRACBITS);
+		WriteWord(ZNodes, nodes[i].dy >> FRACBITS);
+		for (int j = 0; j < 2; ++j)
+		{
+			for (int k = 0; k < 4; ++k)
+			{
+				WriteWord(ZNodes, nodes[i].bbox[j][k] >> FRACBITS);
+			}
+		}
+
+		for (int j = 0; j < 2; ++j)
+		{
+			DWORD child;
+			if ((size_t)nodes[i].children[j] & 1)
+			{
+				child = 0x80000000 | DWORD((subsector_t *)((BYTE *)nodes[i].children[j] - 1) - subsectors);
+			}
+			else
+			{
+				child = DWORD((node_t *)nodes[i].children[j] - nodes);
+			}
+			WriteLong(ZNodes, child);
+		}
+	}
+
+	uLongf outlen = ZNodes.Size();
+	BYTE *compressed;
+	int offset = numlines * 8 + 12 + 16;
+	int r;
+	do
+	{
+		compressed = new Bytef[outlen + offset];
+		r = compress (compressed + offset, &outlen, &ZNodes[0], ZNodes.Size());
+		if (r == Z_BUF_ERROR)
+		{
+			delete[] compressed;
+			outlen += 1024;
+		}
+	} 
+	while (r == Z_BUF_ERROR);
+
+	memcpy(compressed, "CACH", 4);
+	DWORD len = LittleLong(numlines);
+	memcpy(compressed+4, &len, 4);
+	map->GetChecksum(compressed+8);
+	for(int i=0;i<numlines;i++)
+	{
+		DWORD ndx[2] = {LittleLong(DWORD(lines[i].v1 - vertexes)), LittleLong(DWORD(lines[i].v2 - vertexes)) };
+		memcpy(compressed+8+16+8*i, ndx, 8);
+	}
+	memcpy(compressed + offset - 4, "ZGL2", 4);
+
+	FString path = CreateCacheName(map, true);
+	FILE *f = fopen(path, "wb");
+	fwrite(compressed, 1, outlen+offset, f);
+	fclose(f);
+}
+
+
+static bool CheckCachedNodes(MapData *map)
+{
+	char magic[4] = {0,0,0,0};
+	BYTE md5[16];
+	BYTE md5map[16];
+	DWORD numlin;
+	DWORD *verts;
+
+	FString path = CreateCacheName(map, false);
+	FILE *f = fopen(path, "rb");
+	if (f == NULL) return false;
+
+	if (fread(magic, 1, 4, f) != 4) goto errorout;
+	if (memcmp(magic, "CACH", 4))  goto errorout;
+
+	if (fread(&numlin, 4, 1, f) != 1) goto errorout; 
+	numlin = LittleLong(numlin);
+	if (numlin != numlines) goto errorout;
+
+	if (fread(md5, 1, 16, f) != 16) goto errorout;
+	map->GetChecksum(md5map);
+	if (memcmp(md5, md5map, 16)) goto errorout;
+
+	verts = new DWORD[numlin * 8];
+	if (fread(verts, 8, numlin, f) != numlin) goto errorout;
+
+	if (fread(magic, 1, 4, f) != 4) goto errorout;
+	if (memcmp(magic, "ZGL2", 4))  goto errorout;
+
+
+	try
+	{
+		long pos = ftell(f);
+		FileReader fr(f);
+		fr.Seek(pos, SEEK_SET);
+		P_LoadZNodes (fr, MAKE_ID('Z','G','L','2'));
+	}
+	catch (CRecoverableError &error)
+	{
+		Printf ("Error loading nodes: %s\n", error.GetMessage());
+
+		if (subsectors != NULL)
+		{
+			delete[] subsectors;
+			subsectors = NULL;
+		}
+		if (segs != NULL)
+		{
+			delete[] segs;
+			segs = NULL;
+		}
+		if (nodes != NULL)
+		{
+			delete[] nodes;
+			nodes = NULL;
+		}
+		goto errorout;
+	}
+
+	for(int i=0;i<numlines;i++)
+	{
+		lines[i].v1 = &vertexes[LittleLong(verts[i*2])];
+		lines[i].v2 = &vertexes[LittleLong(verts[i*2+1])];
+	}
+	delete [] verts;
+
+	fclose(f);
+	return true;
+
+errorout:
+	fclose(f);
+	return false;
+}
+
+CCMD(clearnodecache)
+{
+	TArray<FFileList> list;
+	FString path = GetCachePath();
+	path += "/";
+
+	try
+	{
+		ScanDirectory(list, path);
+	}
+	catch (CRecoverableError &err)
+	{
+		Printf("%s", err.GetMessage());
+		return;
+	}
+
+	// Scan list backwards so that when we reach a directory
+	// all files within are already deleted.
+	for(int i = list.Size()-1; i >= 0; i--)
+	{
+		if (list[i].isDirectory)
+		{
+			rmdir(list[i].Filename);
+		}
+		else
+		{
+			remove(list[i].Filename);
+		}
+	}
+
+		
+}
+
+//==========================================================================
+//
+// Keep both the original nodes from the WAD and the GL nodes created here.
+// The original set is only being used to get the sector for in-game 
+// positioning of actors but not for rendering.
+//
+// This is necessary because ZDBSP is much more sensitive
+// to sloppy mapping practices that produce overlapping sectors.
+// The crane in P:AR E1M3 is a good example that would be broken if
+// this wasn't done.
+//
+//==========================================================================
+
+
+//==========================================================================
+//
+// P_PointInSubsector
+//
+//==========================================================================
+
+subsector_t *P_PointInSubsector (fixed_t x, fixed_t y)
+{
+	node_t *node;
+	int side;
+
+	// single subsector is a special case
+	if (numgamenodes == 0)
+		return gamesubsectors;
+				
+	node = gamenodes + numgamenodes - 1;
+
+	do
+	{
+		side = R_PointOnSide (x, y, node);
+		node = (node_t *)node->children[side];
+	}
+	while (!((size_t)node & 1));
+		
+	return (subsector_t *)((BYTE *)node - 1);
+}
+
+
+//==========================================================================
+//
+// PointOnLine
+//
+// Same as the one im the node builder, but not part of a specific class
+//
+//==========================================================================
+
+static bool PointOnLine (int x, int y, int x1, int y1, int dx, int dy)
+{
+	const double SIDE_EPSILON = 6.5536;
+
+	// For most cases, a simple dot product is enough.
+	double d_dx = double(dx);
+	double d_dy = double(dy);
+	double d_x = double(x);
+	double d_y = double(y);
+	double d_x1 = double(x1);
+	double d_y1 = double(y1);
+
+	double s_num = (d_y1-d_y)*d_dx - (d_x1-d_x)*d_dy;
+
+	if (fabs(s_num) < 17179869184.0)	// 4<<32
+	{
+		// Either the point is very near the line, or the segment defining
+		// the line is very short: Do a more expensive test to determine
+		// just how far from the line the point is.
+		double l = sqrt(d_dx*d_dx+d_dy*d_dy);
+		double dist = fabs(s_num)/l;
+		if (dist < SIDE_EPSILON)
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+
+//==========================================================================
+//
+// SetRenderSector
+//
+// Sets the render sector for each GL subsector so that the proper flat 
+// information can be retrieved
+//
+//==========================================================================
+
+void P_SetRenderSector()
+{
+	int 				i;
+	DWORD 				j;
+	TArray<subsector_t *> undetermined;
+	subsector_t *		ss;
+
+	// hide all sectors on textured automap that only have hidden lines.
+	bool *hidesec = new bool[numsectors];
+	for(i = 0; i < numsectors; i++)
+	{
+		hidesec[i] = true;
+	}
+	for(i = 0; i < numlines; i++)
+	{
+		if (!(lines[i].flags & ML_DONTDRAW))
+		{
+			hidesec[lines[i].frontsector - sectors] = false;
+			if (lines[i].backsector != NULL)
+			{
+				hidesec[lines[i].backsector - sectors] = false;
+			}
+		}
+	}
+	for(i = 0; i < numsectors; i++)
+	{
+		if (hidesec[i]) sectors[i].MoreFlags |= SECF_HIDDEN;
+	}
+	delete [] hidesec;
+
+	// Check for incorrect partner seg info so that the following code does not crash.
+	for(i=0;i<numsegs;i++)
+	{
+		int partner = (int)glsegextras[i].PartnerSeg;
+
+		if (partner<0 || partner>=numsegs/*eh? || &segs[partner]!=glsegextras[i].PartnerSeg*/)
+		{
+			glsegextras[i].PartnerSeg=DWORD_MAX;
+		}
+
+		// glbsp creates such incorrect references for Strife.
+		if (segs[i].linedef && glsegextras[i].PartnerSeg != DWORD_MAX && !segs[glsegextras[i].PartnerSeg].linedef)
+		{
+			glsegextras[i].PartnerSeg = glsegextras[glsegextras[i].PartnerSeg].PartnerSeg = DWORD_MAX;
+		}
+	}
+
+	for(i=0;i<numsegs;i++)
+	{
+		if (glsegextras[i].PartnerSeg != DWORD_MAX && glsegextras[glsegextras[i].PartnerSeg].PartnerSeg!=i)
+		{
+			glsegextras[i].PartnerSeg=DWORD_MAX;
+		}
+	}
+
+	// look up sector number for each subsector
+	for (i = 0; i < numsubsectors; i++)
+	{
+		// For rendering pick the sector from the first seg that is a sector boundary
+		// this takes care of self-referencing sectors
+		ss = &subsectors[i];
+		seg_t *seg = ss->firstline;
+
+		// Check for one-dimensional subsectors. These should be ignored when
+		// being processed for automap drawinng etc.
+		ss->flags |= SSECF_DEGENERATE;
+		for(j=2; j<ss->numlines; j++)
+		{
+			if (!PointOnLine(seg[j].v1->x, seg[j].v1->y, seg->v1->x, seg->v1->y, seg->v2->x-seg->v1->x, seg->v2->y-seg->v1->y))
+			{
+				// Not on the same line
+				ss->flags &= ~SSECF_DEGENERATE;
+				break;
+			}
+		}
+
+		seg = ss->firstline;
+		for(j=0; j<ss->numlines; j++)
+		{
+			if(seg->sidedef && (glsegextras[seg - segs].PartnerSeg == DWORD_MAX || seg->sidedef->sector!=segs[glsegextras[seg - segs].PartnerSeg].sidedef->sector))
+			{
+				ss->render_sector = seg->sidedef->sector;
+				break;
+			}
+			seg++;
+		}
+		if(ss->render_sector == NULL) 
+		{
+			undetermined.Push(ss);
+		}
+	}
+
+	// assign a vaild render sector to all subsectors which haven't been processed yet.
+	while (undetermined.Size())
+	{
+		bool deleted=false;
+		for(i=undetermined.Size()-1;i>=0;i--)
+		{
+			ss=undetermined[i];
+			seg_t * seg = ss->firstline;
+			
+			for(j=0; j<ss->numlines; j++)
+			{
+				DWORD partner = glsegextras[seg - segs].PartnerSeg;
+				if (partner != DWORD_MAX && glsegextras[partner].Subsector)
+				{
+					sector_t * backsec = glsegextras[partner].Subsector->render_sector;
+					if (backsec)
+					{
+						ss->render_sector=backsec;
+						undetermined.Delete(i);
+						deleted=1;
+						break;
+					}
+				}
+				seg++;
+			}
+		}
+		// We still got some left but the loop above was unable to assign them.
+		// This only happens when a subsector is off the map.
+		// Don't bother and just assign the real sector for rendering
+		if (!deleted && undetermined.Size()) 
+		{
+			for(i=undetermined.Size()-1;i>=0;i--)
+			{
+				ss=undetermined[i];
+				ss->render_sector=ss->sector;
+			}
+			break;
+		}
+	}
+
+#if 0	// may be useful later so let's keep it here for now
+	// now group the subsectors by sector
+	subsector_t ** subsectorbuffer = new subsector_t * [numsubsectors];
+
+	for(i=0, ss=subsectors; i<numsubsectors; i++, ss++)
+	{
+		ss->render_sector->subsectorcount++;
+	}
+
+	for (i=0; i<numsectors; i++) 
+	{
+		sectors[i].subsectors = subsectorbuffer;
+		subsectorbuffer += sectors[i].subsectorcount;
+		sectors[i].subsectorcount = 0;
+	}
+	
+	for(i=0, ss = subsectors; i<numsubsectors; i++, ss++)
+	{
+		ss->render_sector->subsectors[ss->render_sector->subsectorcount++]=ss;
+	}
+#endif
+
+}
diff --git a/src/p_local.h b/src/p_local.h
index 91b134605..2997f3121 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -456,9 +456,10 @@ const secplane_t * P_CheckSlopeWalk (AActor *actor, fixed_t &xmove, fixed_t &ymo
 // (For ZDoom itself this doesn't make any difference here but for GZDoom it does.)
 //
 //----------------------------------------------------------------------------------
+subsector_t *P_PointInSubsector (fixed_t x, fixed_t y);
 inline sector_t *P_PointInSector(fixed_t x, fixed_t y)
 {
-	return R_PointInSubsector(x,y)->sector;
+	return P_PointInSubsector(x,y)->sector;
 }
 
 //
diff --git a/src/p_maputl.cpp b/src/p_maputl.cpp
index 91370c070..9f4ddb39a 100644
--- a/src/p_maputl.cpp
+++ b/src/p_maputl.cpp
@@ -302,7 +302,7 @@ void AActor::LinkToWorld (bool buggy)
 	// link into subsector
 	sector_t *sec;
 
-	if (!buggy || numnodes == 0)
+	if (!buggy || numgamenodes == 0)
 	{
 		sec = P_PointInSector (x, y);
 	}
@@ -322,6 +322,7 @@ void AActor::LinkToWorld (sector_t *sec)
 		return;
 	}
 	Sector = sec;
+	subsector = R_PointInSubsector(x, y);	// this is from the rendering nodes, not the gameplay nodes!
 
 	if ( !(flags & MF_NOSECTOR) )
 	{
@@ -460,7 +461,7 @@ static int R_PointOnSideSlow (fixed_t x, fixed_t y, node_t *node)
 
 sector_t *AActor::LinkToWorldForMapThing ()
 {
-	node_t *node = nodes + numnodes - 1;
+	node_t *node = gamenodes + numgamenodes - 1;
 
 	do
 	{
diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp
index 9dfa8d5ca..98f0801bc 100644
--- a/src/p_mobj.cpp
+++ b/src/p_mobj.cpp
@@ -4705,6 +4705,20 @@ bool P_HitWater (AActor * thing, sector_t * sec, fixed_t x, fixed_t y, fixed_t z
 	// don't splash above the object
 	if (checkabove && z > thing->z + (thing->height >> 1)) return false;
 
+#if 0 // needs some rethinking before activation
+
+	// This avoids spawning splashes on invisible self referencing sectors.
+	// For network consistency do this only in single player though because
+	// it is not guaranteed that all players have GL nodes loaded.
+	if (!multiplayer && thing->subsector->sector != thing->subsector->render_sector)
+	{
+		fixed_t zs = thing->subsector->sector->floorplane.ZatPoint(x, y);
+		fixed_t zr = thing->subsector->render_sector->floorplane.ZatPoint(x, y);
+
+		if (zs > zr && thing->z >= zs) return false;
+	}
+#endif
+
 #ifdef _3DFLOORS
 	for(unsigned int i=0;i<sec->e->XFloor.ffloors.Size();i++)
 	{		
@@ -4823,7 +4837,7 @@ bool P_HitFloor (AActor *thing)
 	{
 		thing->flags6 &= ~MF6_ARMED; // Disarm
 		P_DamageMobj (thing, NULL, NULL, thing->health, NAME_Crush, DMG_FORCED);  // kill object
-		return true;
+		return false;
 	}
 
 	if (thing->flags2 & MF2_FLOATBOB || thing->flags3 & MF3_DONTSPLASH)
diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp
index 1ee18eeb2..1b4442649 100644
--- a/src/p_saveg.cpp
+++ b/src/p_saveg.cpp
@@ -47,6 +47,7 @@
 #include "r_interpolate.h"
 #include "g_level.h"
 #include "po_man.h"
+#include "p_setup.h"
 
 static void CopyPlayer (player_t *dst, player_t *src, const char *name);
 static void ReadOnePlayer (FArchive &arc, bool skipload);
@@ -542,3 +543,108 @@ void P_SerializePolyobjs (FArchive &arc)
 		}
 	}
 }
+
+//==========================================================================
+//
+// RecalculateDrawnSubsectors
+//
+// In case the subsector data is unusable this function tries to reconstruct
+// if from the linedefs' ML_MAPPED info.
+//
+//==========================================================================
+
+void RecalculateDrawnSubsectors()
+{
+	for(int i=0;i<numsubsectors;i++)
+	{
+		subsector_t *sub = &subsectors[i];
+		for(unsigned int j=0;j<sub->numlines;j++)
+		{
+			if (sub->firstline[j].linedef != NULL && 
+				(sub->firstline[j].linedef->flags & ML_MAPPED))
+			{
+				sub->flags |= SSECF_DRAWN;
+			}
+		}
+	}
+}
+
+//==========================================================================
+//
+// ArchiveSubsectors
+//
+//==========================================================================
+
+void P_SerializeSubsectors(FArchive &arc)
+{
+	int num_verts, num_subs, num_nodes;	
+	BYTE by;
+
+	if (arc.IsStoring())
+	{
+		if (hasglnodes)
+		{
+			arc << numvertexes << numsubsectors << numnodes;	// These are only for verification
+			for(int i=0;i<numsubsectors;i+=8)
+			{
+				by = 0;
+				for(int j=0;j<8;j++)
+				{
+					if ((subsectors[i+j].flags & SSECF_DRAWN) && i+j<numsubsectors)
+					{
+						by |= (1<<j);
+					}
+				}
+				arc << by;
+			}
+		}
+		else
+		{
+			int v = 0;
+			arc << v << v << v;
+		}
+	}
+	else
+	{
+		if (SaveVersion < 2500)
+		{
+			if (hasglnodes)
+			{
+				RecalculateDrawnSubsectors();
+			}
+			return;
+		}
+
+		arc << num_verts << num_subs << num_nodes;
+		if (num_verts != numvertexes ||
+			num_subs != numsubsectors ||
+			num_nodes != numnodes)
+		{
+			// Nodes don't match - we can't use this info
+			for(int i=0;i<num_subs;i+=8)
+			{
+				// Skip the subsector info.
+				arc << by;
+			}
+			if (hasglnodes)
+			{
+				RecalculateDrawnSubsectors();
+			}
+			return;
+		}
+		else
+		{
+			for(int i=0;i<numsubsectors;i+=8)
+			{
+				arc << by;
+				for(int j=0;j<8;j++)
+				{
+					if ((by & (1<<j)) && i+j<numsubsectors)
+					{
+						subsectors[i+j].flags |= SSECF_DRAWN;
+					}
+				}
+			}
+		}
+	}
+}
diff --git a/src/p_saveg.h b/src/p_saveg.h
index 3002800bb..e09b863d5 100644
--- a/src/p_saveg.h
+++ b/src/p_saveg.h
@@ -44,6 +44,7 @@ void P_SerializePlayers (FArchive &arc, bool fakeload);
 void P_SerializeWorld (FArchive &arc);
 void P_SerializeThinkers (FArchive &arc, bool);
 void P_SerializePolyobjs (FArchive &arc);
+void P_SerializeSubsectors(FArchive &arc);
 void P_SerializeSounds (FArchive &arc);
 
 void P_ReadACSDefereds (PNGHandle *png);
diff --git a/src/p_setup.cpp b/src/p_setup.cpp
index 35ec40a43..b0a92b2c4 100644
--- a/src/p_setup.cpp
+++ b/src/p_setup.cpp
@@ -83,6 +83,8 @@ void P_ParseTextMap(MapData *map);
 extern int numinterpolations;
 extern unsigned int R_OldBlend;
 
+EXTERN_CVAR(Bool, am_textured)
+
 CVAR (Bool, genblockmap, false, CVAR_SERVERINFO|CVAR_GLOBALCONFIG);
 CVAR (Bool, gennodes, false, CVAR_SERVERINFO|CVAR_GLOBALCONFIG);
 CVAR (Bool, genglnodes, false, CVAR_SERVERINFO);
@@ -103,6 +105,7 @@ vertex_t*		vertexes;
 
 int 			numsegs;
 seg_t*			segs;
+glsegextra_t*	glsegextras;
 
 int 			numsectors;
 sector_t*		sectors;
@@ -122,6 +125,14 @@ side_t* 		sides;
 int				numzones;
 zone_t*			zones;
 
+node_t * 		gamenodes;
+int 			numgamenodes;
+
+subsector_t * 	gamesubsectors;
+int 			numgamesubsectors;
+
+bool			hasglnodes;
+
 FExtraLight*	ExtraLights;
 FLightStack*	LightStacks;
 
@@ -132,8 +143,6 @@ sidei_t *sidetemp;
 
 TArray<int>		linemap;
 
-bool			UsingGLNodes;
-
 // BLOCKMAP
 // Created from axis aligned bounding box
 // of the map, a rectangular array of
@@ -152,7 +161,6 @@ fixed_t 		bmaporgx;		// origin of block map
 fixed_t 		bmaporgy;
 
 FBlockNode**	blocklinks;		// for thing chains
-			
 
 
 // REJECT
@@ -790,7 +798,6 @@ void P_LoadZSegs (FileReaderBase &data)
 		segs[i].v2 = &vertexes[v2];
 		segs[i].linedef = ldef = &lines[line];
 		segs[i].sidedef = ldef->sidedef[side];
-		segs[i].PartnerSeg = NULL;
 		segs[i].frontsector = ldef->sidedef[side]->sector;
 		if (ldef->flags & ML_TWOSIDED && ldef->sidedef[side^1] != NULL)
 		{
@@ -846,14 +853,7 @@ void P_LoadGLZSegs (FileReaderBase &data, int type)
 			{
 				seg[-1].v2 = seg->v1;
 			}
-			if (partner == 0xFFFFFFFF)
-			{
-				seg->PartnerSeg = NULL;
-			}
-			else
-			{
-				seg->PartnerSeg = &segs[partner];
-			}
+			glsegextras[seg - segs].PartnerSeg = partner;
 			if (line != 0xFFFFFFFF)
 			{
 				line_t *ldef;
@@ -887,7 +887,7 @@ void P_LoadGLZSegs (FileReaderBase &data, int type)
 //
 //===========================================================================
 
-static void LoadZNodes(FileReaderBase &data, int glnodes)
+void LoadZNodes(FileReaderBase &data, int glnodes)
 {
 	// Read extra vertices added during node building
 	DWORD orgVerts, newVerts;
@@ -956,6 +956,7 @@ static void LoadZNodes(FileReaderBase &data, int glnodes)
 	numsegs = numSegs;
 	segs = new seg_t[numsegs];
 	memset (segs, 0, numsegs*sizeof(seg_t));
+	glsegextras = NULL;
 
 	for (i = 0; i < numSubs; ++i)
 	{
@@ -968,6 +969,7 @@ static void LoadZNodes(FileReaderBase &data, int glnodes)
 	}
 	else
 	{
+		glsegextras = new glsegextra_t[numsegs];
 		P_LoadGLZSegs (data, glnodes);
 	}
 
@@ -1014,7 +1016,7 @@ static void LoadZNodes(FileReaderBase &data, int glnodes)
 }
 
 
-static void P_LoadZNodes (FileReader &dalump, DWORD id)
+void P_LoadZNodes (FileReader &dalump, DWORD id)
 {
 	int type;
 	bool compressed;
@@ -1162,7 +1164,6 @@ void P_LoadSegs (MapData * map)
 
 			li->v1 = &vertexes[vnum1];
 			li->v2 = &vertexes[vnum2];
-			li->PartnerSeg = NULL;
 
 			segangle = (WORD)LittleShort(ml->angle);
 
@@ -2904,6 +2905,16 @@ static void P_GroupLines (bool buildmap)
 	{
 		subsectors[i].sector = subsectors[i].firstline->sidedef->sector;
 	}
+	if (glsegextras != NULL)
+	{
+		for (i = 0; i < numsubsectors; i++)
+		{
+			for (jj = 0; jj < subsectors[i].numlines; ++jj)
+			{
+				glsegextras[subsectors[i].firstline - segs + jj].Subsector = &subsectors[i];
+			}
+		}
+	}
 	times[0].Unclock();
 
 	// count number of lines in each sector
@@ -3339,6 +3350,11 @@ void P_FreeLevelData ()
 		delete[] segs;
 		segs = NULL;
 	}
+	if (glsegextras != NULL)
+	{
+		delete[] glsegextras;
+		glsegextras = NULL;
+	}
 	if (sectors != NULL)
 	{
 		delete[] sectors[0].e;
@@ -3346,6 +3362,18 @@ void P_FreeLevelData ()
 		sectors = NULL;
 		numsectors = 0;	// needed for the pointer cleanup code
 	}
+	if (gamenodes && gamenodes!=nodes)
+	{
+		delete [] gamenodes;
+		gamenodes = NULL;
+		numgamenodes = 0;
+	}
+	if (gamesubsectors && gamesubsectors!=subsectors)
+	{
+		delete [] gamesubsectors;
+		gamesubsectors = NULL;
+		numgamesubsectors = 0;
+	}
 	if (subsectors != NULL)
 	{
 		for (int i = 0; i < numsubsectors; ++i)
@@ -3480,6 +3508,9 @@ void P_SetupLevel (char *lumpname, int position)
 	int i;
 	bool buildmap;
 
+	// This is motivated as follows:
+	bool RequireGLNodes = am_textured;
+
 	for (i = 0; i < (int)countof(times); ++i)
 	{
 		times[i].Reset();
@@ -3537,6 +3568,7 @@ void P_SetupLevel (char *lumpname, int position)
 
 	// find map num
 	level.lumpnum = map->lumpnum;
+	hasglnodes = false;
 
 	// [RH] Support loading Build maps (because I felt like it. :-)
 	buildmap = false;
@@ -3665,23 +3697,23 @@ void P_SetupLevel (char *lumpname, int position)
 	{
 		ForceNodeBuild = true;
 	}
+	bool reloop = false;
 
-	UsingGLNodes = false;
 	if (!ForceNodeBuild)
 	{
 		// Check for compressed nodes first, then uncompressed nodes
 		FWadLump test;
 		DWORD id = MAKE_ID('X','x','X','x'), idcheck = 0, idcheck2 = 0, idcheck3 = 0, idcheck4 = 0;
 
-		if (map->MapLumps[ML_ZNODES].Size != 0 && !UsingGLNodes)
+		if (map->MapLumps[ML_ZNODES].Size != 0)
 		{
+			// Test normal nodes first
 			map->Seek(ML_ZNODES);
 			idcheck = MAKE_ID('Z','N','O','D');
 			idcheck2 = MAKE_ID('X','N','O','D');
 		}
 		else if (map->MapLumps[ML_GLZNODES].Size != 0)
 		{
-			// If normal nodes are not present but GL nodes are, use them.
 			map->Seek(ML_GLZNODES);
 			idcheck = MAKE_ID('Z','G','L','N');
 			idcheck2 = MAKE_ID('Z','G','L','2');
@@ -3756,10 +3788,23 @@ void P_SetupLevel (char *lumpname, int position)
 			else ForceNodeBuild = true;
 		}
 		else ForceNodeBuild = true;
+
+		// If loading the regular nodes failed try GL nodes before considering a rebuild
+		if (ForceNodeBuild)
+		{
+			if (P_LoadGLNodes(map)) 
+			{
+				ForceNodeBuild=false;
+				reloop = true;
+			}
+		}
 	}
+	else reloop = true;
+
+	unsigned int startTime=0, endTime=0;
+
 	if (ForceNodeBuild)
 	{
-		unsigned int startTime, endTime;
 
 		startTime = I_FPSTime ();
 		TArray<FNodeBuilder::FPolyStart> polyspots, anchors;
@@ -3771,15 +3816,46 @@ void P_SetupLevel (char *lumpname, int position)
 			lines, numlines
 		};
 		leveldata.FindMapBounds ();
-		UsingGLNodes |= genglnodes;
-		FNodeBuilder builder (leveldata, polyspots, anchors, UsingGLNodes);
+		// We need GL nodes if am_textured is on.
+		// In case a sync critical game mode is started, also build GL nodes to avoid problems
+		// if the different machines' am_textured setting differs.
+		bool BuildGLNodes = am_textured || multiplayer || demoplayback || demorecording || genglnodes;
+		FNodeBuilder builder (leveldata, polyspots, anchors, BuildGLNodes);
 		delete[] vertexes;
 		builder.Extract (nodes, numnodes,
-			segs, numsegs,
+			segs, glsegextras, numsegs,
 			subsectors, numsubsectors,
 			vertexes, numvertexes);
 		endTime = I_FPSTime ();
 		DPrintf ("BSP generation took %.3f sec (%d segs)\n", (endTime - startTime) * 0.001, numsegs);
+		reloop = true;
+	}
+
+	// Copy pointers to the old nodes so that R_PointInSubsector can use them
+	if (nodes && subsectors)
+	{
+		gamenodes = nodes;
+		numgamenodes = numnodes;
+		gamesubsectors = subsectors;
+		numgamesubsectors = numsubsectors;
+	}
+	else
+	{
+		gamenodes=NULL;
+	}
+
+	if (RequireGLNodes)
+	{
+		// Build GL nodes if we want a textured automap or GL nodes are forced to be built.
+		// If the original nodes being loaded are not GL nodes they will be kept around for
+		// use in P_PointInSubsector to avoid problems with maps that depend on the specific
+		// nodes they were built with (P:AR E1M3 is a good example for a map where this is the case.)
+		reloop |= P_CheckNodes(map, ForceNodeBuild, endTime - startTime);
+		hasglnodes = true;
+	}
+	else
+	{
+		hasglnodes = P_CheckForGLNodes();
 	}
 
 	times[10].Clock();
@@ -3798,6 +3874,11 @@ void P_SetupLevel (char *lumpname, int position)
 	P_FloodZones ();
 	times[13].Unclock();
 
+	if (hasglnodes)
+	{
+		P_SetRenderSector();
+	}
+
 	bodyqueslot = 0;
 // phares 8/10/98: Clear body queue so the corpses from previous games are
 // not assumed to be from this one.
@@ -3845,7 +3926,7 @@ void P_SetupLevel (char *lumpname, int position)
 	P_SpawnSpecials ();
 
 	times[16].Clock();
-	if (ForceNodeBuild) P_LoopSidedefs (false);
+	if (reloop) P_LoopSidedefs (false);
 	PO_Init ();	// Initialize the polyobjs
 	times[16].Unclock();
 
@@ -3922,6 +4003,12 @@ void P_SetupLevel (char *lumpname, int position)
 		}
 	}
 	MapThingsConverted.Clear();
+
+	if (glsegextras != NULL)
+	{
+		delete[] glsegextras;
+		glsegextras = NULL;
+	}
 }
 
 
diff --git a/src/p_setup.h b/src/p_setup.h
index 80196e317..aa860af0e 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -111,6 +111,12 @@ int P_TranslateSectorSpecial (int);
 int GetUDMFInt(int type, int index, const char *key);
 fixed_t GetUDMFFixed(int type, int index, const char *key);
 
+bool P_LoadGLNodes(MapData * map);
+bool P_CheckNodes(MapData * map, bool rebuilt, int buildtime);
+bool P_CheckForGLNodes();
+void P_SetRenderSector();
+
+
 struct sidei_t	// [RH] Only keep BOOM sidedef init stuff around for init
 {
 	union
@@ -133,5 +139,7 @@ struct sidei_t	// [RH] Only keep BOOM sidedef init stuff around for init
 	};
 };
 extern sidei_t *sidetemp;
+extern bool hasglnodes;
+extern struct glsegextra_t *glsegextras;
 
 #endif
diff --git a/src/p_udmf.cpp b/src/p_udmf.cpp
index e75d324df..5f2fca941 100644
--- a/src/p_udmf.cpp
+++ b/src/p_udmf.cpp
@@ -1227,6 +1227,10 @@ public:
 					sec->seqType = -1;
 					continue;
 
+				case NAME_hidden:
+					sec->MoreFlags |= SECF_HIDDEN;
+					break;
+
 				default:
 					break;
 			}
diff --git a/src/po_man.cpp b/src/po_man.cpp
index 1aa065c19..7619bd06a 100644
--- a/src/po_man.cpp
+++ b/src/po_man.cpp
@@ -1827,12 +1827,6 @@ void PO_Init (void)
 	// [RH] Don't need the seg lists anymore
 	KillSideLists ();
 
-	// We still need to flag the segs of the polyobj's sidedefs so that they are excluded from rendering.
-	for(int i=0;i<numsegs;i++)
-	{
-		segs[i].bPolySeg = (segs[i].sidedef != NULL && segs[i].sidedef->Flags & WALLF_POLYOBJ);
-	}
-
 	for(int i=0;i<numnodes;i++)
 	{
 		node_t *no = &nodes[i];
diff --git a/src/r_bsp.cpp b/src/r_bsp.cpp
index 71d4f210a..e7ba7c116 100644
--- a/src/r_bsp.cpp
+++ b/src/r_bsp.cpp
@@ -36,6 +36,7 @@
 
 #include "i_system.h"
 #include "p_lnspec.h"
+#include "p_setup.h"
 
 #include "r_main.h"
 #include "r_plane.h"
@@ -112,6 +113,8 @@ TArray<size_t> WallMirrors;
 static FNodeBuilder::FLevel PolyNodeLevel;
 static FNodeBuilder PolyNodeBuilder(PolyNodeLevel);
 
+static subsector_t *InSubsector;
+
 CVAR (Bool, r_drawflat, false, 0)		// [RH] Don't texture segs?
 
 
@@ -167,10 +170,11 @@ static cliprange_t		solidsegs[MAXWIDTH/2+2];
 //
 //==========================================================================
 
-void R_ClipWallSegment (int first, int last, bool solid)
+bool R_ClipWallSegment (int first, int last, bool solid)
 {
 	cliprange_t *next, *start;
 	int i, j;
+	bool res = false;
 
 	// Find the first range that touches the range
 	// (adjacent pixels are touching).
@@ -180,6 +184,7 @@ void R_ClipWallSegment (int first, int last, bool solid)
 
 	if (first < start->first)
 	{
+		res = true;
 		if (last <= start->first)
 		{
 			// Post is entirely visible (above start).
@@ -205,7 +210,7 @@ void R_ClipWallSegment (int first, int last, bool solid)
 					next->last = last;
 				}
 			}
-			return;
+			return true;
 		}
 
 		// There is a fragment above *start.
@@ -220,7 +225,7 @@ void R_ClipWallSegment (int first, int last, bool solid)
 
 	// Bottom contained in start?
 	if (last <= start->last)
-		return;
+		return res;
 
 	next = start;
 	while (last >= (next+1)->first)
@@ -257,6 +262,31 @@ crunch:
 			newend = start+i;
 		}
 	}
+	return true;
+}
+
+bool R_CheckClipWallSegment (int first, int last)
+{
+	cliprange_t *start;
+
+	// Find the first range that touches the range
+	// (adjacent pixels are touching).
+	start = solidsegs;
+	while (start->last < first)
+		start++;
+
+	if (first < start->first)
+	{
+		return true;
+	}
+
+	// Bottom contained in start?
+	if (last > start->last)
+	{
+		return true;
+	}
+
+	return false;
 }
 
 
@@ -626,6 +656,10 @@ void R_AddLine (seg_t *line)
 
 	if (line->linedef == NULL)
 	{
+		if (R_CheckClipWallSegment (WallSX1, WallSX2))
+		{
+			InSubsector->flags |= SSECF_DRAWN;
+		}
 		return;
 	}
 
@@ -792,6 +826,16 @@ void R_AddLine (seg_t *line)
 			// Reject empty lines used for triggers and special events.
 			// Identical floor and ceiling on both sides, identical light levels
 			// on both sides, and no middle texture.
+
+			// When using GL nodes, do a clipping test for these lines so we can
+			// mark their subsectors as visible for automap texturing.
+			if (hasglnodes && !(InSubsector->flags & SSECF_DRAWN))
+			{
+				if (R_CheckClipWallSegment(WallSX1, WallSX2))
+				{
+					InSubsector->flags |= SSECF_DRAWN;
+				}
+			}
 			return;
 		}
 	}
@@ -827,7 +871,10 @@ void R_AddLine (seg_t *line)
 #endif
 	}
 
-	R_ClipWallSegment (WallSX1, WallSX2, solid);
+	if (R_ClipWallSegment (WallSX1, WallSX2, solid))
+	{
+		InSubsector->flags |= SSECF_DRAWN;
+	}
 }
 
 
@@ -1096,12 +1143,21 @@ void R_Subsector (subsector_t *sub)
 	sector_t     tempsec;				// killough 3/7/98: deep water hack
 	int          floorlightlevel;		// killough 3/16/98: set floor lightlevel
 	int          ceilinglightlevel;		// killough 4/11/98
+	bool		 outersubsector;
+
+	if (InSubsector != NULL)
+	{ // InSubsector is not NULL. This means we are rendering from a mini-BSP.
+		outersubsector = false;
+	}
+	else
+	{
+		outersubsector = true;
+		InSubsector = sub;
+	}
 
-#if 0
 #ifdef RANGECHECK
-	if (sub - subsectors >= (ptrdiff_t)numsubsectors)
+	if (outersubsector && sub - subsectors >= (ptrdiff_t)numsubsectors)
 		I_Error ("R_Subsector: ss %ti with numss = %i", sub - subsectors, numsubsectors);
-#endif
 #endif
 
 	assert(sub->sector != NULL);
@@ -1109,6 +1165,10 @@ void R_Subsector (subsector_t *sub)
 	if (sub->polys)
 	{ // Render the polyobjs in the subsector first
 		R_AddPolyobjs(sub);
+		if (outersubsector)
+		{
+			InSubsector = NULL;
+		}
 		return;
 	}
 
@@ -1190,12 +1250,16 @@ void R_Subsector (subsector_t *sub)
 
 	while (count--)
 	{
-		if (!line->bPolySeg)
+		if (!outersubsector || line->sidedef == NULL || !(line->sidedef->Flags & WALLF_POLYOBJ))
 		{
 			R_AddLine (line);
 		}
 		line++;
 	}
+	if (outersubsector)
+	{
+		InSubsector = NULL;
+	}
 }
 
 //
diff --git a/src/r_defs.h b/src/r_defs.h
index 3c8ab46d7..55b896a80 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -319,6 +319,7 @@ enum
 	SECF_FORCEDUNDERWATER= 64,	// sector is forced to be underwater
 	SECF_UNDERWATERMASK	= 32+64,
 	SECF_DRAWN			= 128,	// sector has been drawn at least once
+	SECF_HIDDEN			= 256,	// Do not draw on textured automap
 };
 
 enum
@@ -942,10 +943,12 @@ struct seg_t
 	// Sector references. Could be retrieved from linedef, too.
 	sector_t*		frontsector;
 	sector_t*		backsector;		// NULL for one-sided lines
+};
 
-	seg_t*			PartnerSeg;
-
-	BITFIELD		bPolySeg:1;
+struct glsegextra_t
+{
+	DWORD		 PartnerSeg;
+	subsector_t *Subsector;
 };
 
 //
@@ -954,13 +957,22 @@ struct seg_t
 // Basically, this is a list of LineSegs indicating the visible walls that
 // define (all or some) sides of a convex BSP leaf.
 //
+
+enum
+{
+	SSECF_DEGENERATE = 1,
+	SSECF_DRAWN = 2,
+};
+
 struct subsector_t
 {
 	sector_t	*sector;
 	FPolyNode	*polys;
 	FMiniBSP	*BSP;
 	seg_t		*firstline;
+	sector_t	*render_sector;
 	DWORD		numlines;
+	int			flags;
 };
 
 
diff --git a/src/r_draw.cpp b/src/r_draw.cpp
index ce836f839..4334f6f79 100644
--- a/src/r_draw.cpp
+++ b/src/r_draw.cpp
@@ -1000,6 +1000,77 @@ const BYTE*				ds_source;
 
 // just for profiling
 int 					dscount;
+
+#ifdef X86_ASM
+extern "C" void R_SetSpanSource_ASM (const BYTE *flat);
+extern "C" void STACK_ARGS R_SetSpanSize_ASM (int xbits, int ybits);
+extern "C" void R_SetSpanColormap_ASM (BYTE *colormap);
+extern "C" BYTE *ds_curcolormap, *ds_cursource, *ds_curtiltedsource;
+#endif
+}
+
+//==========================================================================
+//
+// R_SetSpanSource
+//
+// Sets the source bitmap for the span drawing routines.
+//
+//==========================================================================
+
+void R_SetSpanSource(const BYTE *pixels)
+{
+	ds_source = pixels;
+#ifdef X86_ASM
+	if (ds_cursource != ds_source)
+	{
+		R_SetSpanSource_ASM(pixels);
+	}
+#endif
+}
+
+//==========================================================================
+//
+// R_SetSpanColormap
+//
+// Sets the colormap for the span drawing routines.
+//
+//==========================================================================
+
+void R_SetSpanColormap(BYTE *colormap)
+{
+	ds_colormap = colormap;
+#ifdef X86_ASM
+	if (ds_colormap != ds_curcolormap)
+	{
+		R_SetSpanColormap_ASM (ds_colormap);
+	}
+#endif
+}
+
+//==========================================================================
+//
+// R_SetupSpanBits
+//
+// Sets the texture size for the span drawing routines.
+//
+//==========================================================================
+
+void R_SetupSpanBits(FTexture *tex)
+{
+	tex->GetWidth ();
+	ds_xbits = tex->WidthBits;
+	ds_ybits = tex->HeightBits;
+	if ((1 << ds_xbits) > tex->GetWidth())
+	{
+		ds_xbits--;
+	}
+	if ((1 << ds_ybits) > tex->GetHeight())
+	{
+		ds_ybits--;
+	}
+#ifdef X86_ASM
+	R_SetSpanSize_ASM (ds_xbits, ds_ybits);
+#endif
 }
 
 //
diff --git a/src/r_draw.h b/src/r_draw.h
index 4d452b49d..96e58bf8d 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -91,8 +91,11 @@ extern void (*R_DrawShadedColumn)(void);
 //	Green/Red/Blue/Indigo shirts.
 extern void (*R_DrawTranslatedColumn)(void);
 
-// Span drawing for rows, floor/ceiling. No Sepctre effect needed.
+// Span drawing for rows, floor/ceiling. No Spectre effect needed.
 extern void (*R_DrawSpan)(void);
+void R_SetupSpanBits(FTexture *tex);
+void R_SetSpanColormap(BYTE *colormap);
+void R_SetSpanSource(const BYTE *pixels);
 
 // Span drawing for masked textures.
 extern void (*R_DrawSpanMasked)(void);
diff --git a/src/r_plane.cpp b/src/r_plane.cpp
index b89a2979e..cd0b9f4a5 100644
--- a/src/r_plane.cpp
+++ b/src/r_plane.cpp
@@ -991,22 +991,9 @@ void R_DrawSinglePlane (visplane_t *pl, fixed_t alpha, bool masked)
 		{ // Don't waste time on a masked texture if it isn't really masked.
 			masked = false;
 		}
-		tex->GetWidth ();
-		ds_xbits = tex->WidthBits;
-		ds_ybits = tex->HeightBits;
-		if ((1 << ds_xbits) > tex->GetWidth())
-		{
-			ds_xbits--;
-		}
-		if ((1 << ds_ybits) > tex->GetHeight())
-		{
-			ds_ybits--;
-		}
+		R_SetupSpanBits(tex);
 		pl->xscale = MulScale16 (pl->xscale, tex->xScale);
 		pl->yscale = MulScale16 (pl->yscale, tex->yScale);
-#ifdef X86_ASM
-		R_SetSpanSize_ASM (ds_xbits, ds_ybits);
-#endif
 		ds_source = tex->GetPixels ();
 
 		basecolormap = pl->colormap;
diff --git a/src/r_plane.h b/src/r_plane.h
index d52fc6db2..369083bad 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -110,5 +110,4 @@ bool R_PlaneInitData (void);
 extern visplane_t*		floorplane;
 extern visplane_t*		ceilingplane;
 
-
 #endif // __R_PLANE_H__
diff --git a/src/r_polymost.cpp b/src/r_polymost.cpp
index 2cb3e2225..12050440f 100644
--- a/src/r_polymost.cpp
+++ b/src/r_polymost.cpp
@@ -1485,7 +1485,7 @@ void RP_Subsector (subsector_t *sub)
 
 	while (count--)
 	{
-		if (!line->bPolySeg)
+		if (line->sidedef == NULL || !(line->sidedef->Flags & WALLF_POLYOBJ))
 		{
 			RP_AddLine (line);
 		}
diff --git a/src/r_state.h b/src/r_state.h
index 38f8e4c3e..9044da72b 100644
--- a/src/r_state.h
+++ b/src/r_state.h
@@ -77,6 +77,13 @@ extern side_t*			sides;
 extern int				numzones;
 extern zone_t*			zones;
 
+extern node_t * 		gamenodes;
+extern int 				numgamenodes;
+
+extern subsector_t * 	gamesubsectors;
+extern int 				numgamesubsectors;
+
+
 extern FExtraLight*		ExtraLights;
 extern FLightStack*		LightStacks;
 
diff --git a/src/v_draw.cpp b/src/v_draw.cpp
index 3c2ed74d0..af6b804d1 100644
--- a/src/v_draw.cpp
+++ b/src/v_draw.cpp
@@ -62,6 +62,9 @@ int CleanWidth, CleanHeight;
 // Above minus 1 (or 1, if they are already 1)
 int CleanXfac_1, CleanYfac_1, CleanWidth_1, CleanHeight_1;
 
+// FillSimplePoly uses this
+extern "C" short spanend[MAXHEIGHT];
+
 CVAR (Bool, hud_scale, false, CVAR_ARCHIVE);
 
 // For routines that take RGB colors, cache the previous lookup in case there
@@ -69,6 +72,7 @@ CVAR (Bool, hud_scale, false, CVAR_ARCHIVE);
 static int LastPal = -1;
 static uint32 LastRGB;
 
+
 static int PalFromRGB(uint32 rgb)
 {
 	if (LastPal >= 0 && LastRGB == rgb)
@@ -186,7 +190,7 @@ void STACK_ARGS DCanvas::DrawTextureV(FTexture *img, double x, double y, uint32
 		double iyscale = 1 / yscale;
 
 		spryscale = FLOAT2FIXED(yscale);
-
+		assert(spryscale > 2);
 #if 0
 		// Fix precision errors that are noticeable at some resolutions
 		if ((y0 + parms.destheight) > (y0 + yscale * img->GetHeight()))
@@ -1091,6 +1095,172 @@ void DCanvas::Clear (int left, int top, int right, int bottom, int palcolor, uin
 	}
 }
 
+//==========================================================================
+//
+// DCanvas :: FillSimplePoly
+//
+// Fills a simple polygon with a texture. Here, "simple" means that a
+// horizontal scanline at any vertical position within the polygon will
+// not cross it more than twice.
+//
+// The originx, originy, scale, and rotation parameters specify
+// transformation of the filling texture, not of the points.
+//
+// The points must be specified in clockwise order.
+//
+//==========================================================================
+
+void DCanvas::FillSimplePoly(FTexture *tex, FVector2 *points, int npoints,
+	double originx, double originy, double scalex, double scaley, angle_t rotation,
+	FDynamicColormap *colormap, int lightlevel)
+{
+	// Use an equation similar to player sprites to determine shade
+	fixed_t shade = LIGHT2SHADE(lightlevel) - 12*FRACUNIT;
+	float topy, boty, leftx, rightx;
+	int toppt, botpt, pt1, pt2;
+	int i;
+	int y1, y2, y;
+	fixed_t x;
+	double rot = rotation * M_PI / double(1u << 31);
+	bool dorotate = rot != 0;
+	double cosrot, sinrot;
+
+	if (--npoints < 2 || Buffer == NULL)
+	{ // not a polygon or we're not locked
+		return;
+	}
+
+	// Find the extents of the polygon, in particular the highest and lowest points.
+	for (botpt = toppt = 0, boty = topy = points[0].Y, leftx = rightx = points[0].X, i = 1; i <= npoints; ++i)
+	{
+		if (points[i].Y < topy)
+		{
+			topy = points[i].Y;
+			toppt = i;
+		}
+		if (points[i].Y > boty)
+		{
+			boty = points[i].Y;
+			botpt = i;
+		}
+		if (points[i].X < leftx)
+		{
+			leftx = points[i].X;
+		}
+		if (points[i].X > rightx)
+		{
+			rightx = points[i].X;
+		}
+	}
+	if (topy >= Height ||		// off the bottom of the screen
+		boty <= 0 ||			// off the top of the screen
+		leftx >= Width ||		// off the right of the screen
+		rightx <= 0)			// off the left of the screen
+	{
+		return;
+	}
+
+	cosrot = cos(rot);
+	sinrot = sin(rot);
+
+	// Setup constant texture mapping parameters.
+	R_SetupSpanBits(tex);
+	R_SetSpanColormap(colormap != NULL ? &colormap->Maps[clamp(shade >> FRACBITS, 0, NUMCOLORMAPS-1) * 256] : identitymap);
+	R_SetSpanSource(tex->GetPixels());
+	scalex = double(1u << (32 - ds_xbits)) / scalex;
+	scaley = double(1u << (32 - ds_ybits)) / scaley;
+	ds_xstep = xs_RoundToInt(cosrot * scalex);
+	ds_ystep = xs_RoundToInt(sinrot * scaley);
+
+	// Travel down the right edge and create an outline of that edge.
+	pt1 = toppt;
+	pt2 = toppt + 1;	if (pt2 > npoints) pt2 = 0;
+	y1 = xs_RoundToInt(points[pt1].Y + 0.5f);
+	do
+	{
+		x = FLOAT2FIXED(points[pt1].X + 0.5f);
+		y2 = xs_RoundToInt(points[pt2].Y + 0.5f);
+		if (y1 >= y2 || (y1 < 0 && y2 < 0) || (y1 >= Height && y2 >= Height))
+		{
+		}
+		else
+		{
+			fixed_t xinc = FLOAT2FIXED((points[pt2].X - points[pt1].X) / (points[pt2].Y - points[pt1].Y));
+			int y3 = MIN(y2, Height);
+			if (y1 < 0)
+			{
+				x += xinc * -y1;
+				y1 = 0;
+			}
+			for (y = y1; y < y3; ++y)
+			{
+				spanend[y] = clamp<short>(x >> FRACBITS, -1, Width);
+				x += xinc;
+			}
+		}
+		y1 = y2;
+		pt1 = pt2;
+		pt2++;			if (pt2 > npoints) pt2 = 0;
+	} while (pt1 != botpt);
+
+	// Travel down the left edge and fill it in.
+	pt1 = toppt;
+	pt2 = toppt - 1;	if (pt2 < 0) pt2 = npoints;
+	y1 = xs_RoundToInt(points[pt1].Y + 0.5f);
+	do
+	{
+		x = FLOAT2FIXED(points[pt1].X + 0.5f);
+		y2 = xs_RoundToInt(points[pt2].Y + 0.5f);
+		if (y1 >= y2 || (y1 < 0 && y2 < 0) || (y1 >= Height && y2 >= Height))
+		{
+		}
+		else
+		{
+			fixed_t xinc = FLOAT2FIXED((points[pt2].X - points[pt1].X) / (points[pt2].Y - points[pt1].Y));
+			int y3 = MIN(y2, Height);
+			if (y1 < 0)
+			{
+				x += xinc * -y1;
+				y1 = 0;
+			}
+			for (y = y1; y < y3; ++y)
+			{
+				int x1 = x >> FRACBITS;
+				int x2 = spanend[y];
+				if (x2 > x1 && x2 > 0 && x1 < Width)
+				{
+					x1 = MAX(x1, 0);
+					x2 = MIN(x2, Width);
+#if 0
+					memset(this->Buffer + y * this->Pitch + x1, (int)tex, x2 - x1);
+#else
+					ds_y = y;
+					ds_x1 = x1;
+					ds_x2 = x2 - 1;
+
+					TVector2<double> tex(x1 - originx, y - originy);
+					if (dorotate)
+					{
+						double t = tex.X;
+						tex.X = t * cosrot - tex.Y * sinrot;
+						tex.Y = tex.Y * cosrot + t * sinrot;
+					}
+					ds_xfrac = xs_RoundToInt(tex.X * scalex);
+					ds_yfrac = xs_RoundToInt(tex.Y * scaley);
+
+					R_DrawSpan();
+#endif
+				}
+				x += xinc;
+			}
+		}
+		y1 = y2;
+		pt1 = pt2;
+		pt2--;			if (pt2 < 0) pt2 = npoints;
+	} while (pt1 != botpt);
+}
+
+
 /********************************/
 /*								*/
 /* Other miscellaneous routines */
diff --git a/src/v_video.h b/src/v_video.h
index 77a3f8a65..b1ba7a863 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -133,6 +133,7 @@ enum
 class FFont;
 struct FRemapTable;
 class player_t;
+typedef uint32 angle_t;
 
 //
 // VIDEO
@@ -175,6 +176,11 @@ public:
 	// Fill an area with a texture
 	virtual void FlatFill (int left, int top, int right, int bottom, FTexture *src, bool local_origin=false);
 
+	// Fill a simple polygon with a texture
+	virtual void FillSimplePoly(FTexture *tex, FVector2 *points, int npoints,
+		double originx, double originy, double scalex, double scaley, angle_t rotation,
+		struct FDynamicColormap *colormap, int lightlevel);
+
 	// Set an area to a specified color
 	virtual void Clear (int left, int top, int right, int bottom, int palcolor, uint32 color);
 
diff --git a/src/win32/fb_d3d9.cpp b/src/win32/fb_d3d9.cpp
index b2c8e1bfa..020be2de1 100644
--- a/src/win32/fb_d3d9.cpp
+++ b/src/win32/fb_d3d9.cpp
@@ -278,7 +278,7 @@ D3DFB::D3DFB (UINT adapter, int width, int height, bool fullscreen)
 	GatheringWipeScreen = false;
 	ScreenWipe = NULL;
 	InScene = false;
-	QuadExtra = new BufferedQuad[MAX_QUAD_BATCH];
+	QuadExtra = new BufferedTris[MAX_QUAD_BATCH];
 	Packs = NULL;
 	PixelDoubling = 0;
 	SkipAt = -1;
@@ -1865,7 +1865,7 @@ void D3DFB::DrawPackedTextures(int packnum)
 
 		CheckQuadBatch();
 
-		BufferedQuad *quad = &QuadExtra[QuadBatchPos];
+		BufferedTris *quad = &QuadExtra[QuadBatchPos];
 		FBVERTEX *vert = &VertexData[VertexPos];
 
 		quad->Group1 = 0;
@@ -1881,6 +1881,8 @@ void D3DFB::DrawPackedTextures(int packnum)
 		}
 		quad->Palette = NULL;
 		quad->Texture = pack->Tex;
+		quad->NumVerts = 4;
+		quad->NumTris = 2;
 
 		float x0 = float(x) - 0.5f;
 		float y0 = float(y) - 0.5f;
@@ -3021,16 +3023,20 @@ void STACK_ARGS D3DFB::DrawTextureV (FTexture *img, double x, double y, uint32 t
 	parms.bilinear = false;
 
 	D3DCOLOR color0, color1;
-	if (!SetStyle(tex, parms, color0, color1, QuadExtra[QuadBatchPos]))
+	BufferedTris *quad = &QuadExtra[QuadBatchPos];
+
+	if (!SetStyle(tex, parms, color0, color1, *quad))
 	{
 		return;
 	}
 
-	QuadExtra[QuadBatchPos].Texture = tex->Box->Owner->Tex;
+	quad->Texture = tex->Box->Owner->Tex;
 	if (parms.bilinear)
 	{
-		QuadExtra[QuadBatchPos].Flags |= BQF_Bilinear;
+		quad->Flags |= BQF_Bilinear;
 	}
+	quad->NumTris = 2;
+	quad->NumVerts = 4;
 
 	float yoffs = GatheringWipeScreen ? 0.5f : 0.5f - LBOffset;
 
@@ -3152,7 +3158,7 @@ void D3DFB::FlatFill(int left, int top, int right, int bottom, FTexture *src, bo
 
 	CheckQuadBatch();
 
-	BufferedQuad *quad = &QuadExtra[QuadBatchPos];
+	BufferedTris *quad = &QuadExtra[QuadBatchPos];
 	FBVERTEX *vert = &VertexData[VertexPos];
 
 	quad->Group1 = 0;
@@ -3168,6 +3174,8 @@ void D3DFB::FlatFill(int left, int top, int right, int bottom, FTexture *src, bo
 	}
 	quad->Palette = NULL;
 	quad->Texture = tex->Box->Owner->Tex;
+	quad->NumVerts = 4;
+	quad->NumTris = 2;
 
 	vert[0].x = x0;
 	vert[0].y = y0;
@@ -3217,6 +3225,128 @@ void D3DFB::FlatFill(int left, int top, int right, int bottom, FTexture *src, bo
 	IndexPos += 6;
 }
 
+//==========================================================================
+//
+// D3DFB :: FillSimplePoly
+//
+// Here, "simple" means that a simple triangle fan can draw it.
+//
+//==========================================================================
+
+void D3DFB::FillSimplePoly(FTexture *texture, FVector2 *points, int npoints,
+	double originx, double originy, double scalex, double scaley,
+	angle_t rotation, FDynamicColormap *colormap, int lightlevel)
+{
+	// Use an equation similar to player sprites to determine shade
+	fixed_t shade = LIGHT2SHADE(lightlevel) - 12*FRACUNIT;
+	BufferedTris *quad;
+	FBVERTEX *verts;
+	D3DTex *tex;
+	float yoffs, uscale, vscale;
+	int i, ipos;
+	D3DCOLOR color0, color1;
+	float ox, oy;
+	float cosrot, sinrot;
+	float rot = float(rotation * M_PI / float(1u << 31));
+	bool dorotate = rot != 0;
+
+	if (npoints < 3)
+	{ // This is no polygon.
+		return;
+	}
+	if (In2D < 2)
+	{
+		Super::FillSimplePoly(texture, points, npoints, originx, originy, scalex, scaley, rotation, colormap, lightlevel);
+		return;
+	}
+	if (!InScene)
+	{
+		return;
+	}
+	tex = static_cast<D3DTex *>(texture->GetNative(true));
+	if (tex == NULL)
+	{
+		return;
+	}
+
+	cosrot = cos(rot);
+	sinrot = sin(rot);
+
+	CheckQuadBatch(npoints - 2, npoints);
+	quad = &QuadExtra[QuadBatchPos];
+	verts = &VertexData[VertexPos];
+
+	color0 = 0;
+	color1 = 0xFFFFFFFF;
+
+	quad->Group1 = 0;
+	if (tex->GetTexFormat() == D3DFMT_L8 && !tex->IsGray)
+	{
+		quad->Flags = BQF_WrapUV | BQF_GamePalette | BQF_DisableAlphaTest;
+		quad->ShaderNum = BQS_PalTex;
+		if (colormap != NULL)
+		{
+			if (colormap->Desaturate != 0)
+			{
+				quad->Flags |= BQF_Desaturated;
+			}
+			quad->ShaderNum = BQS_InGameColormap;
+			color0 = D3DCOLOR_ARGB(colormap->Desaturate,
+				colormap->Color.r, colormap->Color.g, colormap->Color.b);
+			double fadelevel = clamp(shade / (NUMCOLORMAPS * 65536.0), 0.0, 1.0);
+			color1 = D3DCOLOR_ARGB(DWORD((1 - fadelevel) * 255),
+				DWORD(colormap->Fade.r * fadelevel),
+				DWORD(colormap->Fade.g * fadelevel),
+				DWORD(colormap->Fade.b * fadelevel));
+		}
+	}
+	else
+	{
+		quad->Flags = BQF_WrapUV | BQF_DisableAlphaTest;
+		quad->ShaderNum = BQS_Plain;
+	}
+	quad->Palette = NULL;
+	quad->Texture = tex->Box->Owner->Tex;
+	quad->NumVerts = npoints;
+	quad->NumTris = npoints - 2;
+
+	yoffs = GatheringWipeScreen ? 0 : LBOffset;
+	uscale = float(1.f / (texture->GetWidth() * scalex));
+	vscale = float(1.f / (texture->GetHeight() * scaley));
+	ox = float(originx);
+	oy = float(originy);
+
+	for (i = 0; i < npoints; ++i)
+	{
+		verts[i].x = points[i].X;
+		verts[i].y = points[i].Y + yoffs;
+		verts[i].z = 0;
+		verts[i].rhw = 1;
+		verts[i].color0 = color0;
+		verts[i].color1 = color1;
+		float u = points[i].X - 0.5f - ox;
+		float v = points[i].Y - 0.5f - oy;
+		if (dorotate)
+		{
+			float t = u;
+			u = t * cosrot - v * sinrot;
+			v = v * cosrot + t * sinrot;
+		}
+		verts[i].tu = u * uscale;
+		verts[i].tv = v * vscale;
+	}
+	for (ipos = IndexPos, i = 2; i < npoints; ++i, ipos += 3)
+	{
+		IndexData[ipos    ] = VertexPos;
+		IndexData[ipos + 1] = VertexPos + i - 1;
+		IndexData[ipos + 2] = VertexPos + i;
+	}
+
+	QuadBatchPos++;
+	VertexPos += npoints;
+	IndexPos = ipos;
+}
+
 //==========================================================================
 //
 // D3DFB :: AddColorOnlyQuad
@@ -3227,7 +3357,7 @@ void D3DFB::FlatFill(int left, int top, int right, int bottom, FTexture *src, bo
 
 void D3DFB::AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR color)
 {
-	BufferedQuad *quad;
+	BufferedTris *quad;
 	FBVERTEX *verts;
 
 	CheckQuadBatch();
@@ -3247,6 +3377,8 @@ void D3DFB::AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR
 	}
 	quad->Palette = NULL;
 	quad->Texture = NULL;
+	quad->NumVerts = 4;
+	quad->NumTris = 2;
 
 	verts[0].x = x;
 	verts[0].y = y;
@@ -3300,17 +3432,19 @@ void D3DFB::AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR
 //
 // D3DFB :: CheckQuadBatch
 //
-// Make sure there's enough room in the batch for one more quad.
+// Make sure there's enough room in the batch for one more set of triangles.
 //
 //==========================================================================
 
-void D3DFB::CheckQuadBatch()
+void D3DFB::CheckQuadBatch(int numtris, int numverts)
 {
 	if (BatchType == BATCH_Lines)
 	{
 		EndLineBatch();
 	}
-	else if (QuadBatchPos == MAX_QUAD_BATCH)
+	else if (QuadBatchPos == MAX_QUAD_BATCH ||
+		VertexPos + numverts > NUM_VERTS ||
+		IndexPos + numtris * 3 > NUM_INDEXES)
 	{
 		EndQuadBatch();
 	}
@@ -3371,23 +3505,33 @@ void D3DFB::EndQuadBatch()
 	D3DDevice->SetIndices(IndexBuffer);
 	bool uv_wrapped = false;
 	bool uv_should_wrap;
+	int indexpos, vertpos;
 
+	indexpos = vertpos = 0;
 	for (int i = 0; i < QuadBatchPos; )
 	{
-		const BufferedQuad *quad = &QuadExtra[i];
+		const BufferedTris *quad = &QuadExtra[i];
 		int j;
 
+		int startindex = indexpos;
+		int startvertex = vertpos;
+
+		indexpos += quad->NumTris * 3;
+		vertpos += quad->NumVerts;
+
 		// Quads with matching parameters should be done with a single
 		// DrawPrimitive call.
 		for (j = i + 1; j < QuadBatchPos; ++j)
 		{
-			const BufferedQuad *q2 = &QuadExtra[j];
+			const BufferedTris *q2 = &QuadExtra[j];
 			if (quad->Texture != q2->Texture ||
 				quad->Group1 != q2->Group1 ||
 				quad->Palette != q2->Palette)
 			{
 				break;
 			}
+			indexpos += q2->NumTris * 3;
+			vertpos += q2->NumVerts;
 		}
 
 		// Set the palette (if one)
@@ -3467,7 +3611,12 @@ void D3DFB::EndQuadBatch()
 		}
 
 		// Draw the quad
-		D3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 4 * i, 4 * (j - i), 6 * i, 2 * (j - i));
+		D3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0,
+			startvertex,					// MinIndex
+			vertpos - startvertex,			// NumVertices
+			startindex,						// StartIndex
+			(indexpos - startindex) / 3		// PrimitiveCount
+			/*4 * i, 4 * (j - i), 6 * i, 2 * (j - i)*/);
 		i = j;
 	}
 	if (uv_wrapped)
@@ -3508,7 +3657,7 @@ void D3DFB::EndBatch()
 //
 //==========================================================================
 
-bool D3DFB::SetStyle(D3DTex *tex, DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedQuad &quad)
+bool D3DFB::SetStyle(D3DTex *tex, DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedTris &quad)
 {
 	D3DFORMAT fmt = tex->GetTexFormat();
 	FRenderStyle style = parms.style;
diff --git a/src/win32/fb_d3d9_wipe.cpp b/src/win32/fb_d3d9_wipe.cpp
index 9024fa804..3407136aa 100644
--- a/src/win32/fb_d3d9_wipe.cpp
+++ b/src/win32/fb_d3d9_wipe.cpp
@@ -466,7 +466,7 @@ bool D3DFB::Wiper_Melt::Run(int ticks, D3DFB *fb)
 				{
 					fb->CheckQuadBatch();
 
-					BufferedQuad *quad = &fb->QuadExtra[fb->QuadBatchPos];
+					BufferedTris *quad = &fb->QuadExtra[fb->QuadBatchPos];
 					FBVERTEX *vert = &fb->VertexData[fb->VertexPos];
 					WORD *index = &fb->IndexData[fb->IndexPos];
 
@@ -475,6 +475,8 @@ bool D3DFB::Wiper_Melt::Run(int ticks, D3DFB *fb)
 					quad->ShaderNum = BQS_Plain;
 					quad->Palette = NULL;
 					quad->Texture = fb->InitialWipeScreen;
+					quad->NumVerts = 4;
+					quad->NumTris = 2;
 
 					// Fill the vertex buffer.
 					float u0 = rect.left / float(fb->FBWidth);
diff --git a/src/win32/win32iface.h b/src/win32/win32iface.h
index e46c392c3..62f06fa99 100644
--- a/src/win32/win32iface.h
+++ b/src/win32/win32iface.h
@@ -257,6 +257,9 @@ public:
 	void FlatFill (int left, int top, int right, int bottom, FTexture *src, bool local_origin);
 	void DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32 realcolor);
 	void DrawPixel(int x, int y, int palcolor, uint32 rgbcolor);
+	void FillSimplePoly(FTexture *tex, FVector2 *points, int npoints,
+		double originx, double originy, double scalex, double scaley,
+		angle_t rotation, FDynamicColormap *colormap, int lightlevel);
 	bool WipeStartScreen(int type);
 	void WipeEndScreen();
 	bool WipeDo(int ticks);
@@ -278,7 +281,7 @@ private:
 	};
 #define D3DFVF_FBVERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_SPECULAR | D3DFVF_TEX1)
 
-	struct BufferedQuad
+	struct BufferedTris
 	{
 		union
 		{
@@ -293,6 +296,8 @@ private:
 		};
 		D3DPal *Palette;
 		IDirect3DTexture9 *Texture;
+		WORD NumVerts;		// Number of _unique_ vertices used by this set.
+		WORD NumTris;		// Number of triangles used by this set.
 	};
 
 	enum
@@ -355,12 +360,12 @@ private:
 	void DrawPackedTextures(int packnum);
 	void DrawLetterbox();
 	void Draw3DPart(bool copy3d);
-	bool SetStyle(D3DTex *tex, DCanvas::DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedQuad &quad);
+	bool SetStyle(D3DTex *tex, DCanvas::DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedTris &quad);
 	static D3DBLEND GetStyleAlpha(int type);
 	static void SetColorOverlay(DWORD color, float alpha, D3DCOLOR &color0, D3DCOLOR &color1);
 	void DoWindowedGamma();
 	void AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR color);
-	void CheckQuadBatch();
+	void CheckQuadBatch(int numtris=2, int numverts=4);
 	void BeginQuadBatch();
 	void EndQuadBatch();
 	void BeginLineBatch();
@@ -433,7 +438,7 @@ private:
 	FBVERTEX *VertexData;
 	IDirect3DIndexBuffer9 *IndexBuffer;
 	WORD *IndexData;
-	BufferedQuad *QuadExtra;
+	BufferedTris *QuadExtra;
 	int VertexPos;
 	int IndexPos;
 	int QuadBatchPos;
diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu
index 883b0e325..6a7580fbf 100644
--- a/wadsrc/static/language.enu
+++ b/wadsrc/static/language.enu
@@ -268,6 +268,8 @@ AMSTR_FOLLOWON = "Follow Mode ON";
 AMSTR_FOLLOWOFF = "Follow Mode OFF";
 AMSTR_GRIDON = "Grid ON";
 AMSTR_GRIDOFF = "Grid OFF";
+AMSTR_TEXON = "Texture Mode ON";
+AMSTR_TEXOFF = "Texture Mode OFF";
 AMSTR_MARKEDSPOT = "Marked Spot";
 AMSTR_MARKSCLEARED = "All Marks Cleared";
 STSTR_MUS = "Music Change";
diff --git a/zdoom.vcproj b/zdoom.vcproj
index 21247821f..b2a3f4fe3 100644
--- a/zdoom.vcproj
+++ b/zdoom.vcproj
@@ -804,6 +804,10 @@
 				RelativePath=".\src\p_floor.cpp"
 				>
 			</File>
+			<File
+				RelativePath=".\src\p_glnodes.cpp"
+				>
+			</File>
 			<File
 				RelativePath=".\src\p_interaction.cpp"
 				>