diff --git a/src/hardware/Sourcefile b/src/hardware/Sourcefile
index 1c05de76cca6d71251023e3e9e7bdde7d8cffaab..0a6279f4580d3b5c613b081dc6c6592c916c61bf 100644
--- a/src/hardware/Sourcefile
+++ b/src/hardware/Sourcefile
@@ -10,4 +10,5 @@ hw_md3load.c
 hw_model.c
 u_list.c
 hw_batching.c
+hw_splitpoly.c
 r_opengl/r_opengl.c
diff --git a/src/hardware/hw_bsp.c b/src/hardware/hw_bsp.c
index 4db69ff8b130b00eb46c403f8a47a8c86e535947..32086c51d70a0ecad6789697e2e4a9ee2248f9f9 100644
--- a/src/hardware/hw_bsp.c
+++ b/src/hardware/hw_bsp.c
@@ -13,6 +13,7 @@
 #include "../doomstat.h"
 #ifdef HWRENDER
 #include "hw_glob.h"
+#include "hw_splitpoly.h"
 #include "../r_local.h"
 #include "../z_zone.h"
 #include "../console.h"
@@ -35,12 +36,6 @@ extrasubsector_t *extrasubsectors = NULL;
 static size_t totsubsectors;
 size_t addsubsector;
 
-typedef struct
-{
-	float x, y;
-	float dx, dy;
-} fdivline_t;
-
 // ==========================================================================
 //                                    FLOOR & CEILING CONVEX POLYS GENERATION
 // ==========================================================================
@@ -164,7 +159,7 @@ static void HWR_FreePoly(poly_t *poly)
 // with the polygon segment
 //
 static float bspfrac;
-static polyvertex_t *fracdivline(fdivline_t *bsp, polyvertex_t *v1,
+static polyvertex_t *fracdivline(FDivLine *bsp, polyvertex_t *v1,
 	polyvertex_t *v2)
 {
 	static polyvertex_t pt;
@@ -216,32 +211,11 @@ static polyvertex_t *fracdivline(fdivline_t *bsp, polyvertex_t *v1,
 // point. Note: hardcoded value, 1.0f could be anything else.
 static boolean SameVertice (polyvertex_t *p1, polyvertex_t *p2)
 {
-#if 0
-	float diff;
-	diff = p2->x - p1->x;
-	if (diff < -1.5f || diff > 1.5f)
-		return false;
-	diff = p2->y - p1->y;
-	if (diff < -1.5f || diff > 1.5f)
-		return false;
-#elif 0
-	if (p1->x != p2->x)
-		return false;
-	if (p1->y != p2->y)
-		return false;
-#elif 0
-	if (fabsf( p2->x - p1->x ) > 1.0E-36f )
-		return false;
-	if (fabsf( p2->y - p1->y ) > 1.0E-36f )
-		return false;
-#else
-#define  DIVLINE_VERTEX_DIFF   0.45f
 	float ep = DIVLINE_VERTEX_DIFF;
 	if (fabsf( p2->x - p1->x ) > ep )
 		return false;
 	if (fabsf( p2->y - p1->y ) > ep )
 		return false;
-#endif
 	// p1 and p2 are considered the same vertex
 	return true;
 }
@@ -252,7 +226,7 @@ static boolean SameVertice (polyvertex_t *p1, polyvertex_t *p2)
 //   frontpoly : polygon on right side of bsp line
 //   backpoly  : polygon on left side
 //
-static void SplitPoly (fdivline_t *bsp,         //splitting parametric line
+static void SplitPoly (FDivLine *bsp,           //splitting parametric line
                        poly_t *poly,            //the convex poly we split
                        poly_t **frontpoly,      //return one poly here
                        poly_t **backpoly)       //return the other here
@@ -435,7 +409,7 @@ static poly_t *CutOutSubsecPoly(seg_t *lseg, INT32 count, poly_t *poly)
 		p1 = {0, 0, 0}, p2 = {0, 0, 0};
 	float fracs = 0.0f;
 
-	fdivline_t cutseg; // x, y, dx, dy as start of node_t struct
+	FDivLine cutseg; // x, y, dx, dy as start of node_t struct
 
 	poly_t *temppoly;
 
@@ -566,7 +540,7 @@ static inline void HWR_SubsecPoly(INT32 num, poly_t *poly)
 
 // the bsp divline have not enouth presition
 // search for the segs source of this divline
-static inline void SearchDivline(node_t *bsp, fdivline_t *divline)
+static inline void SearchDivline(node_t *bsp, FDivLine *divline)
 {
 	divline->x = FIXED_TO_FLOAT(bsp->x);
 	divline->y = FIXED_TO_FLOAT(bsp->y);
@@ -609,7 +583,7 @@ static void WalkBSPNode(INT32 bspnum, poly_t *poly, UINT16 *leafnode, fixed_t *b
 {
 	node_t *bsp;
 	poly_t *backpoly, *frontpoly;
-	fdivline_t fdivline;
+	FDivLine fdivline;
 	polyvertex_t *pt;
 	INT32 i;
 
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 8df9b8916b2e563d7c1eebb16511b222a636a312..e4357f3bb6da10bdb31d66b9ad254bc41817ef05 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -72,7 +72,7 @@ typedef struct FColorARGB FColorARGB;
 typedef struct
 {
 	FLOAT x,y;
-} F2DCoord, v2d_t;
+} F2DCoord;
 
 // Simple 3D vector
 typedef struct FVector
@@ -80,11 +80,11 @@ typedef struct FVector
 	FLOAT x,y,z;
 } FVector;
 
-// ======================
-//      wallVert3D
-// ----------------------
-// :crab: IS GONE! :crab:
-// ======================
+// Simple 2D line
+typedef struct
+{
+	F2DCoord v1, v2;
+} F2DLine;
 
 // -----------
 // structures
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index 8223705bd1afa4a30e6d1c1239693fcab98d8374..ecfaff7a4e533d540a7b3993e165b24b7dc1e4dc 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -20,6 +20,7 @@
 #include "hw_main.h"
 #include "hw_glob.h"
 #include "hw_drv.h"
+#include "hw_splitpoly.h"
 
 #include "../m_misc.h" //FIL_WriteFile()
 #include "../r_draw.h" //viewborderlump
@@ -382,6 +383,344 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p
 		HWD.pfnDrawPolygon(NULL, v, 4, flags);
 }
 
+#if 0
+static FOutVector *Intersect2DRectangle(FOutVector *v, F2DLine *line, INT32 *numpts, boolean useTop)
+{
+	FOutVector *frontpoly, *backpoly;
+	INT32 frontpts = 0, backpts = 0, i;
+
+	FOutVector *toppoly = NULL;
+	INT32 toppts = 0;
+
+	if (!HWR_SplitPolygon(line, v, *numpts, &frontpoly, &frontpts, &backpoly, &backpts))
+		return v;
+
+	for (i = 0; i < frontpts; i++)
+	{
+		if ((useTop && frontpoly[i].y < (INT32)line->v1.y)
+		|| (!useTop && frontpoly[i].y >= (INT32)line->v1.y))
+		{
+			toppoly = frontpoly;
+			toppts = frontpts;
+		}
+		else
+		{
+			toppoly = backpoly;
+			toppts = backpts;
+			break;
+		}
+	}
+
+	*numpts = toppts;
+	return toppoly;
+}
+#endif
+
+void HWR_DrawRotatedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, angle_t angle, INT32 option, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h)
+{
+	FOutVector v[4];
+	FOutVector *vPtr = v;
+	INT32 numpts = 4;
+	FBITFIELD flags;
+	float cx = FIXED_TO_FLOAT(x);
+	float cy = FIXED_TO_FLOAT(y);
+	float ang = ANG2RAD(angle);
+	float offsetx, offsety;
+	float stex[2];
+	UINT8 alphalevel = ((option & V_ALPHAMASK) >> V_ALPHASHIFT);
+	GLPatch_t *hwrPatch;
+	INT32 i;
+
+//  3--2
+//  | /|
+//  |/ |
+//  0--1
+	float dupx, dupy, fscalew, fscaleh, fwidth, fheight;
+
+	UINT8 perplayershuffle = 0;
+
+	if (alphalevel >= 10 && alphalevel < 13)
+		return;
+
+	// make patch ready in hardware cache
+	if (!colormap)
+		HWR_GetPatch(gpatch);
+	else
+		HWR_GetMappedPatch(gpatch, colormap);
+
+	hwrPatch = ((GLPatch_t *)gpatch->hardware);
+
+	dupx = (float)vid.dupx;
+	dupy = (float)vid.dupy;
+
+	switch (option & V_SCALEPATCHMASK)
+	{
+	case V_NOSCALEPATCH:
+		dupx = dupy = 1.0f;
+		break;
+	case V_SMALLSCALEPATCH:
+		dupx = (float)vid.smalldupx;
+		dupy = (float)vid.smalldupy;
+		break;
+	case V_MEDSCALEPATCH:
+		dupx = (float)vid.meddupx;
+		dupy = (float)vid.meddupy;
+		break;
+	}
+
+	dupx = dupy = (dupx < dupy ? dupx : dupy);
+	fscalew = fscaleh = FIXED_TO_FLOAT(pscale);
+	if (vscale != pscale)
+		fscaleh = FIXED_TO_FLOAT(vscale);
+
+	// left offset
+	if (option & V_FLIP)
+		offsetx = (float)(gpatch->width - gpatch->leftoffset) * fscalew;
+	else
+		offsetx = (float)(gpatch->leftoffset) * fscalew;
+
+	// top offset
+	offsety = (float)(gpatch->topoffset) * fscaleh;
+
+	cx -= offsetx;
+	cy -= offsety;
+
+	offsetx *= dupx;
+	offsety *= dupy;
+
+	if (splitscreen && (option & V_PERPLAYER))
+	{
+		float adjusty = ((option & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+		//fscaleh /= 2;
+		cy /= 2;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			float adjustx = ((option & V_NOSCALESTART) ? vid.width : BASEVIDWIDTH)/2.0f;
+			//fscalew /= 2;
+			cx /= 2;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				option &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				cx += adjustx;
+				option &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				cy += adjusty;
+				option &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				cx += adjustx;
+				cy += adjusty;
+				option &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle = 1;
+				option &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle = 2;
+				cy += adjusty;
+				option &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
+	if (!(option & V_NOSCALESTART))
+	{
+		cx = cx * dupx;
+		cy = cy * dupy;
+
+		if (!(option & V_SCALEPATCHMASK))
+		{
+			// if it's meant to cover the whole screen, black out the rest
+			// no the patch is cropped do not do this ever
+
+			// centre screen
+			if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
+			{
+				if (option & V_SNAPTORIGHT)
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx));
+				else if (!(option & V_SNAPTOLEFT))
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/2;
+				if (perplayershuffle & 4)
+					cx -= ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/4;
+				else if (perplayershuffle & 8)
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/4;
+			}
+			if (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f)
+			{
+				if (option & V_SNAPTOBOTTOM)
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy));
+				else if (!(option & V_SNAPTOTOP))
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/2;
+				if (perplayershuffle & 1)
+					cy -= ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/4;
+				else if (perplayershuffle & 2)
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/4;
+			}
+		}
+	}
+
+	fwidth = FIXED_TO_FLOAT(w);
+	fheight = FIXED_TO_FLOAT(h);
+
+	if (sx + w > gpatch->width<<FRACBITS)
+		fwidth = FIXED_TO_FLOAT((gpatch->width<<FRACBITS) - sx);
+
+	if (sy + h > gpatch->height<<FRACBITS)
+		fheight = FIXED_TO_FLOAT((gpatch->height<<FRACBITS) - sy);
+
+	if (pscale != FRACUNIT || vscale != FRACUNIT || (splitscreen && option & V_PERPLAYER))
+	{
+		fwidth *= fscalew * dupx;
+		fheight *= fscaleh * dupy;
+	}
+	else
+	{
+		fwidth *= dupx;
+		fheight *= dupy;
+	}
+
+	// set the polygon vertices to the right positions
+	v[0].x = v[3].x = 0.0f;
+	v[2].x = v[1].x = fwidth;
+
+	v[0].y = v[1].y = 0.0f;
+	v[2].y = v[3].y = fheight;
+
+	for (i = 0; i < 4; i++)
+	{
+		float temp_x = v[i].x - offsetx;
+		float temp_y = v[i].y - offsety;
+
+		float rotated_x = (temp_x * cos(ang)) - (temp_y * sin(ang));
+		float rotated_y = (temp_x * sin(ang)) + (temp_y * cos(ang));
+
+		v[i].x = rotated_x + offsetx + cx;
+		v[i].y = rotated_y + offsety;
+
+		if (splitscreen && (option & V_PERPLAYER))
+			v[i].y /= 2;
+
+		v[i].y += cy;
+	}
+
+	stex[0] = (FIXED_TO_FLOAT(sx)/(float)(gpatch->width))*hwrPatch->max_s;
+	if (sx + w > gpatch->width<<FRACBITS)
+		stex[1] = hwrPatch->max_s;
+	else
+		stex[1] = (FIXED_TO_FLOAT(sx+w)/(float)(gpatch->width))*hwrPatch->max_s;
+
+	if (option & V_FLIP)
+	{
+		v[2].s = v[1].s = stex[0];
+		v[0].s = v[3].s = stex[1];
+	}
+	else
+	{
+		v[0].s = v[3].s = stex[0];
+		v[2].s = v[1].s = stex[1];
+	}
+
+	v[0].t = v[1].t = (FIXED_TO_FLOAT(sy)/(float)(gpatch->height))*hwrPatch->max_t;
+	if (sy + h > gpatch->height<<FRACBITS)
+		v[2].t = v[3].t = hwrPatch->max_t;
+	else
+		v[2].t = v[3].t = (FIXED_TO_FLOAT(sy+h)/(float)(gpatch->height))*hwrPatch->max_t;
+
+#if 0
+	// Auto-crop at splitscreen borders!
+	if (splitscreen && (option & V_PERPLAYER))
+	{
+		boolean p1 = (stplyr == &players[displayplayer]);
+		boolean visible = false;
+
+		F2DLine line;
+
+		line.v1.x = 0.0;
+		line.v2.x = vid.width;
+
+		line.v1.y = line.v2.y = vid.height / 2;
+
+		vPtr = Intersect2DRectangle(v, &line, &numpts, p1);
+		if (!numpts)
+			return;
+
+		for (i = 0; i < numpts; i++)
+		{
+			if ((p1 && vPtr[i].y < vid.height / 2) || (!p1 && vPtr[i].y >= vid.height / 2))
+			{
+				visible = true;
+				break;
+			}
+		}
+
+		if (!visible)
+			return;
+	}
+#endif
+
+	for (i = 0; i < numpts; i++)
+	{
+		vPtr[i].x = -1 + (vPtr[i].x / (vid.width/2));
+		vPtr[i].y = 1 - (vPtr[i].y / (vid.height/2));
+		vPtr[i].z = 1.0f;
+	}
+
+	flags = PF_Translucent|PF_NoDepthTest;
+
+	if (option & V_WRAPX)
+		flags |= PF_ForceWrapX;
+	if (option & V_WRAPY)
+		flags |= PF_ForceWrapY;
+
+	// clip it since it is used for bunny scroll in doom I
+	if (alphalevel)
+	{
+		FSurfaceInfo Surf;
+		Surf.PolyColor.s.red = Surf.PolyColor.s.green = Surf.PolyColor.s.blue = 0xff;
+		if (alphalevel == 13) Surf.PolyColor.s.alpha = softwaretranstogl_lo[st_translucency];
+		else if (alphalevel == 14) Surf.PolyColor.s.alpha = softwaretranstogl[st_translucency];
+		else if (alphalevel == 15) Surf.PolyColor.s.alpha = softwaretranstogl_hi[st_translucency];
+		else Surf.PolyColor.s.alpha = softwaretranstogl[10-alphalevel];
+		flags |= PF_Modulated;
+		HWD.pfnDrawPolygon(&Surf, vPtr, numpts, flags);
+	}
+	else
+		HWD.pfnDrawPolygon(NULL, vPtr, numpts, flags);
+}
+
 void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h)
 {
 	FOutVector v[4];
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index a55555ef4619f7058575f5233711738b1dfc5666..d189c6c83cab585f3fee29d00e25ba22ec4cbe02 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -1042,7 +1042,7 @@ static void HWR_DrawSkyWall(FOutVector *wallVerts, FSurfaceInfo *Surf)
 static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 {
 	FOutVector wallVerts[4];
-	v2d_t vs, ve; // start, end vertices of 2d line (view from above)
+	F2DCoord vs, ve; // start, end vertices of 2d line (view from above)
 
 	fixed_t worldtop, worldbottom;
 	fixed_t worldhigh = 0, worldlow = 0;
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index b751b2a6e1c7698663e353c56d49e778e81fb7f0..60e28994e8026814373331976540d2eeeccf3878 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -39,6 +39,7 @@ void HWR_InitTextureMapping(void);
 void HWR_SetViewSize(void);
 void HWR_DrawPatch(patch_t *gpatch, INT32 x, INT32 y, INT32 option);
 void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap);
+void HWR_DrawRotatedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, angle_t angle, INT32 option, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
 void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
 void HWR_MakePatch(const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipmap, boolean makebitmap);
 void HWR_CreatePlanePolygons(INT32 bspnum);
diff --git a/src/hardware/hw_splitpoly.c b/src/hardware/hw_splitpoly.c
new file mode 100644
index 0000000000000000000000000000000000000000..4dcc4cfa41c8adade7201d55ac8ec8fa0a31133e
--- /dev/null
+++ b/src/hardware/hw_splitpoly.c
@@ -0,0 +1,253 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file hw_splitpoly.c
+/// \brief Convex polygon splitting
+
+#include "../doomdef.h"
+#include "../doomstat.h"
+
+#ifdef HWRENDER
+#include "hw_glob.h"
+#include "hw_splitpoly.h"
+
+static FOutVector *IntersectLines (FOutVector *v1, FOutVector *v2, F2DLine *line, float *outfrac)
+{
+	static FOutVector pt;
+	double frac;
+	double num;
+	double den;
+	double v1x,v1y,v1dx,v1dy;
+	double v2x,v2y,v2dx,v2dy;
+
+	// a segment of a polygon
+	v1x  = v1->x;
+	v1y  = v1->y;
+	v1dx = v2->x - v1->x;
+	v1dy = v2->y - v1->y;
+
+	// the intersection line
+	v2x  = line->v1.x;
+	v2y  = line->v1.y;
+	v2dx = line->v2.x - line->v1.x;
+	v2dy = line->v2.y - line->v1.y;
+
+	den = v2dy*v1dx - v2dx*v1dy;
+	if (fabsf((float)den) < 1.0E-36f) // avoid checking exactly for 0.0
+		return NULL;       // parallel
+
+	// first check the frac along the polygon segment,
+	// (do not accept hit with the extensions)
+	num = (v2x - v1x)*v2dy + (v1y - v2y)*v2dx;
+	frac = num / den;
+	if (frac < 0.0l || frac > 1.0l)
+		return NULL;
+
+	// now get the frac along the line
+	// which is useful to determine what is left, what is right
+	num = (v2x - v1x)*v1dy + (v1y - v2y)*v1dx;
+	frac = num / den;
+	*outfrac = (float)frac;
+
+	// find the interception point along the partition line
+	pt.x = (float)(v2x + v2dx*frac);
+	pt.y = (float)(v2y + v2dy*frac);
+
+	return &pt;
+}
+
+// if two vertice coords have a x and/or y difference
+// of less or equal than 1 FRACUNIT, they are considered the same
+// point. Note: hardcoded value, 1.0f could be anything else.
+static boolean SameVertice (FOutVector *p1, FOutVector *p2)
+{
+	float ep = DIVLINE_VERTEX_DIFF;
+	if (fabsf( p2->x - p1->x ) > ep )
+		return false;
+	if (fabsf( p2->y - p1->y ) > ep )
+		return false;
+	// p1 and p2 are considered the same vertex
+	return true;
+}
+
+// Splits a convex polygon into two convex polygons
+boolean HWR_SplitPolygon (F2DLine *line,
+                          FOutVector *poly, INT32 numpts,
+                          FOutVector **frontpoly, INT32 *frontpts,
+                          FOutVector **backpoly, INT32 *backpts)
+{
+	INT32 i, j;
+	FOutVector *pv;
+
+	INT32 ps = -1, pe = -1;
+	INT32 nptfront, nptback;
+	FOutVector vs = {0,0,0,0,0};
+	FOutVector ve = {0,0,0,0,0};
+	FOutVector lastpv = {0,0,0,0,0};
+	float fracs = 0.0f, frace = 0.0f;
+	INT32 psonline = 0, peonline = 0;
+
+	static FOutVector polyfront[16];
+	static FOutVector polyback[16];
+
+	for (i = 0; i < numpts; i++)
+	{
+		float frac;
+
+		j = i + 1;
+		if (j == numpts) j = 0;
+
+		// start & end points
+		pv = IntersectLines(&poly[i], &poly[j], line, &frac);
+
+		if (pv == NULL)
+			continue;
+
+		if (ps < 0)
+		{
+			// first point
+			ps = i;
+			vs = *pv;
+			fracs = frac;
+		}
+		else
+		{
+			// the partition line traverse a junction between two segments
+			// or the two points are so close, they can be considered as one
+			// thus, don't accept, since split 2 must be another vertex
+			if (SameVertice(pv, &lastpv))
+			{
+				if (pe < 0)
+				{
+					ps = i;
+					psonline = 1;
+				}
+				else
+				{
+					pe = i;
+					peonline = 1;
+				}
+			}
+			else
+			{
+				if (pe < 0)
+				{
+					pe = i;
+					ve = *pv;
+					frace = frac;
+				}
+				else
+				{
+					// a frac, not same vertice as last one
+					// we already got pt2 so pt 2 is not on the line,
+					// so we probably got back to the start point
+					// which is on the line
+					if (SameVertice(pv, &vs))
+						psonline = 1;
+					break;
+				}
+			}
+		}
+
+		// remember last point intercept to detect identical points
+		lastpv = *pv;
+	}
+
+	// no split: the partition line is either parallel and
+	// aligned with one of the poly segments, or the line is totally
+	// out of the polygon and doesn't traverse it
+	if (ps < 0 || pe < 0)
+	{
+		*frontpoly = poly;
+		*frontpts = numpts;
+		*backpoly = NULL;
+		*backpts = 0;
+		return false;
+	}
+	else if (pe <= ps)
+	{
+		*frontpoly = NULL;
+		*frontpts = 0;
+		*backpoly = NULL;
+		*backpts = 0;
+		return false;
+	}
+
+	// number of points on each side, _not_ counting those
+	// that may lie just one the line
+	nptback  = pe - ps - peonline;
+	nptfront = numpts - peonline - psonline - nptback;
+
+	if (nptback > 0)
+	{
+		*backpoly = polyback;
+		*backpts = 2 + nptback;
+	}
+	else
+	{
+		*backpoly = NULL;
+		*backpts = 0;
+	}
+
+	if (nptfront > 0)
+	{
+		*frontpoly = polyfront;
+		*frontpts = 2 + nptfront;
+	}
+	else
+	{
+		*frontpoly = NULL;
+		*frontpts = 0;
+	}
+
+	// generate FRONT poly
+	if (*frontpoly)
+	{
+		pv = *frontpoly;
+		*pv++ = vs;
+		*pv++ = ve;
+		i = pe;
+		do
+		{
+			if (++i == numpts)
+				i = 0;
+			*pv++ = poly[i];
+		} while (i != ps && --nptfront);
+	}
+
+	// generate BACK poly
+	if (*backpoly)
+	{
+		pv = *backpoly;
+		*pv++ = ve;
+		*pv++ = vs;
+		i = ps;
+		do
+		{
+			if (++i == numpts)
+				i = 0;
+			*pv++ = poly[i];
+		} while (i != pe && --nptback);
+	}
+
+	// make sure frontpoly is the one on the 'right' side
+	// of the partition line
+	if (fracs > frace)
+	{
+		FOutVector *swappoly = *backpoly;
+		INT32 swappts = *backpts;
+		*backpoly = *frontpoly;
+		*backpts = *frontpts;
+		*frontpoly = swappoly;
+		*frontpts = swappts;
+	}
+
+	return true;
+}
+
+#endif
diff --git a/src/hardware/hw_splitpoly.h b/src/hardware/hw_splitpoly.h
new file mode 100644
index 0000000000000000000000000000000000000000..e218ffce108181136f1988232ed783ab4540f669
--- /dev/null
+++ b/src/hardware/hw_splitpoly.h
@@ -0,0 +1,28 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file hw_splitpoly.h
+/// \brief Convex polygon splitting
+
+#ifndef __HWR_SPLITPOLY_H__
+#define __HWR_SPLITPOLY_H__
+
+#define DIVLINE_VERTEX_DIFF 0.45f
+
+typedef struct
+{
+	float x, y;
+	float dx, dy;
+} FDivLine;
+
+boolean HWR_SplitPolygon (F2DLine *line,
+                         FOutVector *poly, INT32 numpts,
+                         FOutVector **frontpoly, INT32 *frontpts,
+                         FOutVector **backpoly, INT32 *backpts);
+
+#endif // __HWR_SPLITPOLY_H__
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 0a8081fbc159338ec04e8e2ca4291d198a7664d6..6b37d37f066cc0fe9f34de3dd7115226e0c506bc 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -725,6 +725,35 @@ static int libd_drawStretched(lua_State *L)
 	return 0;
 }
 
+static int libd_drawRotated(lua_State *L)
+{
+	fixed_t x, y, hscale, vscale;
+	angle_t angle;
+	INT32 flags;
+	patch_t *patch;
+	const UINT8 *colormap = NULL;
+
+	HUDONLY
+	x = luaL_checkinteger(L, 1);
+	y = luaL_checkinteger(L, 2);
+	hscale = luaL_checkinteger(L, 3);
+	if (hscale < 0)
+		return luaL_error(L, "negative horizontal scale");
+	vscale = luaL_checkinteger(L, 4);
+	if (vscale < 0)
+		return luaL_error(L, "negative vertical scale");
+	angle = luaL_checkangle(L, 5);
+	patch = *((patch_t **)luaL_checkudata(L, 6, META_PATCH));
+	flags = luaL_optinteger(L, 7, 0);
+	if (!lua_isnoneornil(L, 8))
+		colormap = *((UINT8 **)luaL_checkudata(L, 8, META_COLORMAP));
+
+	flags &= ~V_PARAMMASK; // Don't let crashes happen.
+
+	V_DrawRotatedPatch(x, y, hscale, vscale, angle, flags, patch, colormap, 0, 0, patch->width<<FRACBITS, patch->height<<FRACBITS);
+	return 0;
+}
+
 static int libd_drawCropped(lua_State *L)
 {
 	fixed_t x, y, hscale, vscale, sx, sy, w, h;
@@ -1226,6 +1255,7 @@ static luaL_Reg lib_draw[] = {
 	{"draw", libd_draw},
 	{"drawScaled", libd_drawScaled},
 	{"drawStretched", libd_drawStretched},
+	{"drawRotated", libd_drawRotated},
 	{"drawCropped", libd_drawCropped},
 	{"drawNum", libd_drawNum},
 	{"drawPaddedNum", libd_drawPaddedNum},
diff --git a/src/v_video.c b/src/v_video.c
index c3993854403fe87db28c06e18705a69f01932a84..4562c4b002e42becc2d594527d136051c7ff63ba 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -490,23 +490,55 @@ static UINT8 hudminusalpha[11] = { 10,  9,  9,  8,  8,  7,  7,  6,  6,  5,  5};
 static const UINT8 *v_colormap = NULL;
 static const UINT8 *v_translevel = NULL;
 
+#define STANDARDPDRAW(px) px
+#define MAPPEDPDRAW(px) *(v_colormap + px)
+#define TRANSLUCENTPDRAW(px) *(v_translevel + ((px<<8)&0xff00) + (*dest&0xff))
+#define TRANSMAPPEDPDRAW(px) *(v_translevel + (((*(v_colormap + px))<<8)&0xff00) + (*dest&0xff))
+
 static inline UINT8 standardpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs)
 {
-	(void)dest; return source[ofs>>FRACBITS];
+	(void)dest; return STANDARDPDRAW(source[ofs>>FRACBITS]);
 }
 static inline UINT8 mappedpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs)
 {
-	(void)dest; return *(v_colormap + source[ofs>>FRACBITS]);
+	(void)dest; return MAPPEDPDRAW(source[ofs>>FRACBITS]);
 }
 static inline UINT8 translucentpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs)
 {
-	return *(v_translevel + ((source[ofs>>FRACBITS]<<8)&0xff00) + (*dest&0xff));
+	return TRANSLUCENTPDRAW(source[ofs>>FRACBITS]);
 }
 static inline UINT8 transmappedpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs)
 {
-	return *(v_translevel + (((*(v_colormap + source[ofs>>FRACBITS]))<<8)&0xff00) + (*dest&0xff));
+	return TRANSMAPPEDPDRAW(source[ofs>>FRACBITS]);
+}
+
+// Used in V_DrawRotatedPatch.
+static inline UINT8 standardpdrawspan(const UINT8 *dest, const UINT16 *source, fixed_t ofs)
+{
+	UINT8 px = (source[ofs>>FRACBITS] & 0xFF);
+	(void)dest; return STANDARDPDRAW(px);
+}
+static inline UINT8 mappedpdrawspan(const UINT8 *dest, const UINT16 *source, fixed_t ofs)
+{
+	UINT8 px = (source[ofs>>FRACBITS] & 0xFF);
+	(void)dest; return MAPPEDPDRAW(px);
+}
+static inline UINT8 translucentpdrawspan(const UINT8 *dest, const UINT16 *source, fixed_t ofs)
+{
+	UINT8 px = (source[ofs>>FRACBITS] & 0xFF);
+	return TRANSLUCENTPDRAW(px);
+}
+static inline UINT8 transmappedpdrawspan(const UINT8 *dest, const UINT16 *source, fixed_t ofs)
+{
+	UINT8 px = (source[ofs>>FRACBITS] & 0xFF);
+	return TRANSMAPPEDPDRAW(px);
 }
 
+#undef STANDARDPDRAW
+#undef MAPPEDPDRAW
+#undef TRANSLUCENTPDRAW
+#undef TRANSMAPPEDPDRAW
+
 // Draws a patch scaled to arbitrary size.
 void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap)
 {
@@ -808,6 +840,473 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 	}
 }
 
+struct rotationraster {
+	fixed_t startcol, stopcol;
+	vector2_t *pstart, *pstop;
+	fixed_t srcregion[4];
+	fixed_t invrow, invcol;
+	fixed_t invcos, invsin;
+	fixed_t colfrac, rowfrac;
+	UINT8 *desttop;
+	patch_t *patch;
+	UINT8 (*patchdrawfunc)(const UINT8*, const UINT16*, fixed_t);
+};
+
+static struct rotationraster v_rotationraster;
+
+static void RasterizeRotatedSpan(fixed_t row)
+{
+	struct rotationraster *r = &v_rotationraster;
+	UINT16 *source = r->patch->flats[0];
+
+	// Precalculate sine and cosine for this row
+	fixed_t rowcos = FixedMul(r->invrow, r->invcos);
+	fixed_t rowsin = FixedMul(r->invrow, r->invsin);
+
+	UINT8 *deststart = r->desttop + (row * vid.width); // Pointer to row
+	UINT8 *dest = deststart + r->startcol; // Pointer to starting column
+	UINT8 *destend = deststart + r->stopcol; // Pointer to ending column
+
+	for (; dest <= destend; dest++, r->startcol++, r->invcol += FRACUNIT)
+	{
+		vector2_t srcpos;
+		UINT16 *srcline;
+		UINT16 srcpx;
+
+		if (r->startcol < 0)
+			continue;
+
+		// Wherever it is
+		srcpos.x = FixedMul(r->invcol, r->invcos) - rowsin;
+		srcpos.y = FixedMul(r->invcol, r->invsin) + rowcos;
+
+		if (srcpos.x < r->pstart->x || srcpos.x >= r->pstop->x
+		|| srcpos.y < r->pstart->y || srcpos.y >=r-> pstop->y)
+			continue;
+
+		// Scale it and offset by cropping position
+		srcpos.x = r->srcregion[0] + FixedMul(srcpos.x - r->pstart->x, r->colfrac);
+		srcpos.y = r->srcregion[1] + FixedMul(srcpos.y - r->pstart->y, r->rowfrac);
+
+		srcline = &(source[(srcpos.y >> FRACBITS) * r->patch->width]);
+		srcpx = srcline[srcpos.x >> FRACBITS];
+
+		if (srcpx & 0xFF00)
+			*dest = r->patchdrawfunc(dest, srcline, srcpos.x);
+	}
+}
+
+static void RasterizeRotatedSpanFlipX(fixed_t row)
+{
+	struct rotationraster *r = &v_rotationraster;
+	UINT16 *source = r->patch->flats[0];
+
+	// Precalculate sine and cosine for this row
+	fixed_t rowcos = FixedMul(r->invrow, r->invcos);
+	fixed_t rowsin = FixedMul(r->invrow, r->invsin);
+
+	UINT8 *deststart = r->desttop + (row * vid.width); // Pointer to row
+	UINT8 *dest = deststart + r->startcol; // Pointer to starting column
+	UINT8 *destend = deststart + r->stopcol; // Pointer to ending column
+
+	for (; dest <= destend; dest++, r->startcol++, r->invcol += FRACUNIT)
+	{
+		vector2_t srcpos;
+		UINT16 *srcline;
+		UINT16 srcpx;
+
+		if (r->startcol < 0)
+			continue;
+
+		// Wherever it is
+		srcpos.x = FixedMul(r->invcol, r->invcos) - rowsin;
+		srcpos.y = FixedMul(r->invcol, r->invsin) + rowcos;
+
+		if (srcpos.x < r->pstart->x || srcpos.x >= r->pstop->x
+		|| srcpos.y < r->pstart->y || srcpos.y >=r-> pstop->y)
+			continue;
+
+		// Scale it and offset by cropping position
+		srcpos.x = r->srcregion[2] - FixedMul(srcpos.x - r->pstart->x, r->colfrac);
+		srcpos.y = r->srcregion[1] + FixedMul(srcpos.y - r->pstart->y, r->rowfrac);
+
+		srcline = &(source[(srcpos.y >> FRACBITS) * r->patch->width]);
+		srcpx = srcline[srcpos.x >> FRACBITS];
+
+		if (srcpx & 0xFF00)
+			*dest = r->patchdrawfunc(dest, srcline, srcpos.x);
+	}
+}
+
+// Draws a scaled, cropped, and rotated patch.
+void V_DrawRotatedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, angle_t angle, INT32 scrn, patch_t *patch, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h)
+{
+	void (*spanraster)(fixed_t) = RasterizeRotatedSpan;
+	UINT32 alphalevel = 0;
+	fixed_t offsetx = 0, offsety = 0;
+
+	INT32 dupx, dupy;
+	INT32 startrow = 0;
+	INT32 stoprow = vid.height;
+	fixed_t pwidth, pheight; // patch width and height
+	fixed_t destrow, destcol;
+	fixed_t invcol, invrow;
+
+	boolean perplayer = (splitscreen && (scrn & V_PERPLAYER));
+	UINT8 perplayershuffle = 0;
+	fixed_t invfrac = FRACUNIT;
+
+	vector2_t rasterstart, rasterstop;
+	vector2_t patchstart, patchstop;
+
+	struct rotationraster *r = &v_rotationraster;
+
+	if (!angle)
+	{
+		V_DrawCroppedPatch(x, y, pscale, vscale, scrn, patch, colormap, sx, sy, w, h);
+		return;
+	}
+
+	if (rendermode == render_none)
+		return;
+
+#ifdef HWRENDER
+	if (rendermode == render_opengl)
+	{
+		HWR_DrawRotatedPatch(patch, x, y, pscale, vscale, angle, scrn, colormap, sx, sy, w, h);
+		return;
+	}
+#endif
+
+	r->patchdrawfunc = standardpdrawspan;
+
+	v_translevel = NULL;
+	if ((alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT)))
+	{
+		if (alphalevel == 13)
+			alphalevel = hudminusalpha[st_translucency];
+		else if (alphalevel == 14)
+			alphalevel = 10 - st_translucency;
+		else if (alphalevel == 15)
+			alphalevel = hudplusalpha[st_translucency];
+
+		if (alphalevel >= 10)
+			return; // invis
+
+		if (alphalevel)
+		{
+			v_translevel = R_GetTranslucencyTable(alphalevel);
+			r->patchdrawfunc = translucentpdrawspan;
+		}
+	}
+
+	v_colormap = NULL;
+	if (colormap)
+	{
+		v_colormap = colormap;
+		r->patchdrawfunc = (v_translevel) ? transmappedpdrawspan : mappedpdrawspan;
+	}
+
+	dupx = vid.dupx;
+	dupy = vid.dupy;
+	if (scrn & V_SCALEPATCHMASK) switch ((scrn & V_SCALEPATCHMASK) >> V_SCALEPATCHSHIFT)
+	{
+		case 1: // V_NOSCALEPATCH
+			dupx = dupy = 1;
+			break;
+		case 2: // V_SMALLSCALEPATCH
+			dupx = vid.smalldupx;
+			dupy = vid.smalldupy;
+			break;
+		case 3: // V_MEDSCALEPATCH
+			dupx = vid.meddupx;
+			dupy = vid.meddupy;
+			break;
+		default:
+			break;
+	}
+
+	// only use one dup, to avoid stretching (har har)
+	dupx = dupy = (dupx < dupy ? dupx : dupy);
+
+	// left offset
+	if (scrn & V_FLIP)
+		offsetx = FixedMul((patch->width - patch->leftoffset)<<FRACBITS, pscale) + 1;
+	else
+		offsetx = FixedMul(patch->leftoffset<<FRACBITS, pscale);
+
+	// top offset
+	offsety = FixedMul(patch->topoffset<<FRACBITS, vscale);
+
+	if (perplayer)
+	{
+		fixed_t adjusty = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1);
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1));
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				scrn &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				scrn &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				scrn &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				scrn &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle = 1;
+				scrn &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle = 2;
+				y += adjusty;
+				scrn &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
+	r->desttop = screens[scrn&V_PARAMMASK];
+	if (!r->desttop)
+		return;
+
+	if (!(scrn & V_NOSCALESTART))
+	{
+		x = FixedMul(x,dupx<<FRACBITS);
+		y = FixedMul(y,dupy<<FRACBITS);
+
+		// Center it if necessary
+		if (!(scrn & V_SCALEPATCHMASK))
+		{
+			if (vid.width != BASEVIDWIDTH * dupx)
+			{
+				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
+				// so center this imaginary screen
+				if (scrn & V_SNAPTORIGHT)
+					x += (vid.width - (BASEVIDWIDTH * dupx)) * FRACUNIT;
+				else if (!(scrn & V_SNAPTOLEFT))
+					x += ((vid.width - (BASEVIDWIDTH * dupx)) / 2) * FRACUNIT;
+				if (perplayershuffle & 4)
+					x -= ((vid.width - (BASEVIDWIDTH * dupx)) / 4) * FRACUNIT;
+				else if (perplayershuffle & 8)
+					x += ((vid.width - (BASEVIDWIDTH * dupx)) / 4) * FRACUNIT;
+			}
+			if (vid.height != BASEVIDHEIGHT * dupy)
+			{
+				// same thing here
+				if (scrn & V_SNAPTOBOTTOM)
+					y += (vid.height - (BASEVIDHEIGHT * dupy)) * FRACUNIT;
+				else if (!(scrn & V_SNAPTOTOP))
+					y += ((vid.height - (BASEVIDHEIGHT * dupy)) / 2) * FRACUNIT;
+				if (perplayershuffle & 1)
+					y -= ((vid.height - (BASEVIDHEIGHT * dupy)) / 4) * FRACUNIT;
+				else if (perplayershuffle & 2)
+					y += ((vid.height - (BASEVIDHEIGHT * dupy)) / 4) * FRACUNIT;
+			}
+		}
+	}
+
+	// Auto-crop at splitscreen borders!
+	if (perplayer)
+	{
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			#error Auto-cropping doesnt take quadscreen into account! Fix it!
+			// Hint: For player 1/2, copy player 1's code below. For player 3/4, copy player 2's code below
+			// For player 1/3 and 2/4, hijack the X wrap prevention lines? That's probably easiest
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer]) // Player 1's screen, crop at the bottom
+			{
+				// Just put a big old stop sign halfway through the screen
+				stoprow = vid.height>>1;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer]) // Player 2's screen, crop at the top
+			{
+				// Start drawing at the border
+				startrow = vid.height>>1;
+			}
+		}
+	}
+
+	if (pscale != FRACUNIT) // scale width properly
+	{
+		pwidth = patch->width<<FRACBITS;
+		pwidth = FixedMul(pwidth, pscale);
+		pwidth = FixedMul(pwidth, dupx<<FRACBITS);
+	}
+	else
+		pwidth = patch->width * dupx<<FRACBITS;
+
+	if (vscale != FRACUNIT) // scale height properly
+	{
+		pheight = patch->height<<FRACBITS;
+		pheight = FixedMul(pheight, vscale);
+		pheight = FixedMul(pheight, dupy<<FRACBITS);
+	}
+	else
+		pheight = patch->height * dupy<<FRACBITS;
+
+	if (perplayer)
+	{
+		y += pheight / 2;
+		invfrac <<= 1;
+	}
+
+	// X/Y scaling values
+	r->colfrac = FixedDiv(w, pwidth);
+	r->rowfrac = FixedDiv(h, pheight);
+
+	// Determine patch region
+	offsetx = -FixedMul(offsetx, dupx<<FRACBITS);
+	offsety = -FixedMul(offsety, dupy<<FRACBITS);
+
+	patchstart.x = offsetx;
+	patchstop.x = patchstart.x + pwidth;
+	patchstart.y = offsety;
+	patchstop.y = patchstart.y + pheight;
+
+	// Determine region to rasterize into
+	rasterstart.x = patchstart.x;
+	rasterstart.y = patchstart.y;
+	rasterstop.x = patchstop.x;
+	rasterstop.y = patchstop.y;
+
+#define CALC_SIDE(sx, sy) { \
+	fixed_t angcos = FINECOSINE(angle>>ANGLETOFINESHIFT); \
+	fixed_t angsin = FINESINE(angle>>ANGLETOFINESHIFT); \
+	fixed_t xcalc = FixedMul(sx, angcos) - FixedMul(sy, angsin); \
+	fixed_t ycalc = FixedMul(sx, angsin) + FixedMul(sy, angcos); \
+	if (rasterstart.x > xcalc) \
+		rasterstart.x = xcalc; \
+	if (rasterstop.x < xcalc) \
+		rasterstop.x = xcalc; \
+	if (rasterstart.y > ycalc) \
+		rasterstart.y = ycalc; \
+	if (rasterstop.y < ycalc) \
+		rasterstop.y = ycalc; \
+}
+
+	CALC_SIDE(patchstart.x, patchstart.y);
+	CALC_SIDE(patchstop.x, patchstart.y);
+	CALC_SIDE(patchstart.x, patchstop.y);
+	CALC_SIDE(patchstop.x, patchstop.y);
+
+#undef CALC_SIDE
+
+	// Offset bounding box by the patch position
+	rasterstart.x += x;
+	rasterstart.y += y;
+	rasterstop.x += x;
+	rasterstop.y += y;
+
+	if (rasterstart.x < 0 && rasterstop.x < 0)
+		return;
+	else if (rasterstart.y < startrow && rasterstop.y < startrow)
+		return;
+	else if (rasterstart.x>>FRACBITS >= vid.width && rasterstop.x>>FRACBITS >= vid.width)
+		return;
+	else if (rasterstart.y>>FRACBITS >= stoprow && rasterstop.y>>FRACBITS >= stoprow)
+		return;
+	else if (rasterstop.x < rasterstart.x || rasterstop.y < rasterstart.y)
+		return;
+
+	rasterstop.x /= FRACUNIT;
+	rasterstop.y /= FRACUNIT;
+
+	if (rasterstop.y >= stoprow)
+		rasterstop.y = stoprow - 1;
+
+	destrow = rasterstart.y >> FRACBITS;
+	invrow = rasterstart.y - y;
+
+	// Precalculated sine and cosine
+	angle = InvAngle(angle);
+	r->invcos = FINECOSINE(angle>>ANGLETOFINESHIFT);
+	r->invsin = FINESINE(angle>>ANGLETOFINESHIFT);
+
+	Patch_GenerateFlat((r->patch = patch), 0);
+
+	r->srcregion[0] = sx;
+	r->srcregion[1] = sy;
+	r->srcregion[2] = sx + w;
+	r->srcregion[3] = sy + h;
+
+	r->pstart = &patchstart;
+	r->pstop = &patchstop;
+
+	if (scrn & V_FLIP)
+		spanraster = RasterizeRotatedSpanFlipX;
+
+	for (; destrow <= rasterstop.y; destrow++, invrow += invfrac)
+	{
+		fixed_t stopcol;
+
+		if (destrow < startrow)
+			continue;
+
+		destcol = rasterstart.x / FRACUNIT;
+		invcol = rasterstart.x - x;
+		stopcol = destcol + rasterstop.x;
+
+		// Expand the ending column if the starting one is out of bounds
+		if (destcol < 0)
+			stopcol += -destcol;
+
+		if (stopcol >= vid.width - 1)
+			stopcol = vid.width - 1;
+
+		r->startcol = destcol;
+		r->stopcol = stopcol;
+		r->invrow = invrow;
+		r->invcol = invcol;
+
+		spanraster(destrow);
+	}
+}
+
 // Draws a patch cropped and scaled to arbitrary size.
 void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h)
 {
diff --git a/src/v_video.h b/src/v_video.h
index c10ab22cea8f56497f998aa449fb672b59f62f84..e4af33e1b560bd1e4d6ede2b281407fb215d2cfd 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -164,7 +164,9 @@ void V_CubeApply(UINT8 *red, UINT8 *green, UINT8 *blue);
 #define V_DrawTinyTranslucentPatch(x,y,s,p) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/4, s, p, NULL)
 #define V_DrawSciencePatch(x,y,s,p,sc) V_DrawFixedPatch(x,y,sc,s,p,NULL)
 #define V_DrawFixedPatch(x,y,sc,s,p,c) V_DrawStretchyFixedPatch(x,y,sc,sc,s,p,c)
+
 void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap);
+void V_DrawRotatedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, angle_t angle, INT32 scrn, patch_t *patch, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
 void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
 
 void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT16 skincolor);