diff --git a/Build/Configurations/Includes/RingRacers_misc.cfg b/Build/Configurations/Includes/RingRacers_misc.cfg
index 5f479834ddf342ebb686eacd8504c16ccb0d7e66..ac92eec7eb18dc093bc5edd2d349baec0bbf331a 100644
--- a/Build/Configurations/Includes/RingRacers_misc.cfg
+++ b/Build/Configurations/Includes/RingRacers_misc.cfg
@@ -904,6 +904,8 @@ speciallinedefs
 	lowerunpeggedflag = 16;
 	repeatmidtextureflag = 1024;
 	pegmidtextureflag = 256;
+	slopeskewflag = 32;
+	nomidtextureskewflag = 128;
 }
 
 speciallinedefs_udmf
@@ -916,6 +918,8 @@ speciallinedefs_udmf
 	lowerunpeggedflag = "dontpegbottom";
 	repeatmidtextureflag = "wrapmidtex";
 	pegmidtextureflag = "midpeg";
+	slopeskewflag = "skewtd";
+	nomidtextureskewflag = "noskew";
 }
 
 // Texture sources
diff --git a/Build/Configurations/Includes/SRB222_misc.cfg b/Build/Configurations/Includes/SRB222_misc.cfg
index 65ce70c6005560087d39fc8e1c24c1d68ed38314..d2128ad9acc5c0c2014e0a46c38602eec600f741 100644
--- a/Build/Configurations/Includes/SRB222_misc.cfg
+++ b/Build/Configurations/Includes/SRB222_misc.cfg
@@ -595,6 +595,8 @@ speciallinedefs
 	impassableflag = 1;
 	upperunpeggedflag = 8;
 	lowerunpeggedflag = 16;
+	slopeskewflag = 32;
+	nomidtextureskewflag = 128;
 	repeatmidtextureflag = 1024;
 	pegmidtextureflag = 256;
 }
@@ -609,6 +611,8 @@ speciallinedefs_udmf
 	lowerunpeggedflag = "dontpegbottom";
 	repeatmidtextureflag = "wrapmidtex";
 	pegmidtextureflag = "midpeg";
+	slopeskewflag = "skewtd";
+	nomidtextureskewflag = "noskew";
 }
 
 scriptlumpnames
diff --git a/Source/Core/Config/GameConfiguration.cs b/Source/Core/Config/GameConfiguration.cs
index a30ef88e3e4674ef9ed204d55427ccba9c0330ec..7ab4968a895be23aaaffc03454c726b539b54285 100755
--- a/Source/Core/Config/GameConfiguration.cs
+++ b/Source/Core/Config/GameConfiguration.cs
@@ -78,6 +78,8 @@ namespace CodeImp.DoomBuilder.Config
 		private readonly string upperunpeggedflag;
 		private readonly string lowerunpeggedflag;
 		private readonly string pegmidtextureflag;
+		private readonly string slopeskewflag;
+		private readonly string nomidtextureskewflag;
 		private readonly bool mixtexturesflats;
 		private readonly bool generalizedactions;
 		private readonly bool generalizedeffects;
@@ -242,6 +244,8 @@ namespace CodeImp.DoomBuilder.Config
 		public string UpperUnpeggedFlag { get { return upperunpeggedflag; } }
 		public string LowerUnpeggedFlag { get { return lowerunpeggedflag; } }
 		public string PegMidtextureFlag { get { return pegmidtextureflag; } }
+		public string SlopeSkewFlag { get { return slopeskewflag; } }
+		public string NoMidtextureSkewFlag { get { return nomidtextureskewflag; } }
 		public bool MixTexturesFlats { get { return mixtexturesflats; } }
 		public bool GeneralizedActions { get { return generalizedactions; } }
 		public bool GeneralizedEffects { get { return generalizedeffects; } }
@@ -521,6 +525,10 @@ namespace CodeImp.DoomBuilder.Config
 				pegmidtextureflag = ((int)obj == 0) ? lowerunpeggedflag : ((int)obj).ToString(CultureInfo.InvariantCulture);
 			else
 				pegmidtextureflag = obj.ToString();
+			obj = cfg.ReadSettingObject("slopeskewflag", 0);
+			if (obj is int) slopeskewflag = ((int)obj).ToString(CultureInfo.InvariantCulture); else slopeskewflag = obj.ToString();
+			obj = cfg.ReadSettingObject("nomidtextureskewflag", 0);
+			if (obj is int) nomidtextureskewflag = ((int)obj).ToString(CultureInfo.InvariantCulture); else nomidtextureskewflag = obj.ToString();
 
 			// Get texture and flat sources
 			textureranges = cfg.ReadSetting("textures", new Hashtable());
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualLower.cs b/Source/Plugins/BuilderModes/VisualModes/VisualLower.cs
index 325d38549e77ec5c1c39814cb5f60dcb3f06cacc..16fa8dfb8b64639bbc31c84a0be7b37c837c7d7b 100755
--- a/Source/Plugins/BuilderModes/VisualModes/VisualLower.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualLower.cs
@@ -156,40 +156,89 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// the values are stored in a TexturePlane)
 			// NOTE: I use a small bias for the floor height, because if the difference in
 			// height is 0 then the TexturePlane doesn't work!
-			TexturePlane tp = new TexturePlane();
+			Vector3D vlt, vlb, vrt, vrb;
+			Vector2D tlt, tlb, trt, trb;
+
 			double floorbias = (Sidedef.Other.Sector.FloorHeight == Sidedef.Sector.FloorHeight) ? 1.0 : 0.0;
-			if(Sidedef.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag))
+			double planefloorbias = Math.Abs(osd.Floor.plane.GetZ(vr) - sd.Floor.plane.GetZ(vr)) < 0.5 ? 1.0 : 0.0;
+			double texturevpeg = 0;
+
+			bool lowerunpegged = Sidedef.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag);
+			bool slopeskew = Sidedef.Line.IsFlagSet(General.Map.Config.SlopeSkewFlag);
+
+			if (lowerunpegged)
 			{
-				if(Sidedef.Sector.HasSkyCeiling && Sidedef.Other.Sector.HasSkyCeiling) 
+				if (slopeskew)
 				{
-					// mxd. Replicate Doom texture offset glitch when front and back sector's ceilings are sky
-					tp.tlt.y = (double)Sidedef.Other.Sector.CeilHeight - Sidedef.Other.Sector.FloorHeight;
-				} 
+					texturevpeg = sd.Ceiling.plane.GetZ(vl) - osd.Floor.plane.GetZ(vl);
+				}
+				//if(Sidedef.Sector.HasSkyCeiling && Sidedef.Other.Sector.HasSkyCeiling) 
+				//{
+				//	// mxd. Replicate Doom texture offset glitch when front and back sector's ceilings are sky
+				//	texturevpeg = (double)Sidedef.Other.Sector.CeilHeight - Sidedef.Other.Sector.FloorHeight;
+				//} 
 				else 
 				{
 					// When lower unpegged is set, the lower texture is bound to the bottom
-					tp.tlt.y = (double) Sidedef.Sector.CeilHeight - Sidedef.Other.Sector.FloorHeight;
+					texturevpeg = (double) Sidedef.Sector.CeilHeight - Sidedef.Other.Sector.FloorHeight;
 				}
 			}
-			tp.trb.x = tp.tlt.x + Math.Round(Sidedef.Line.Length); //mxd. (G)ZDoom snaps texture coordinates to integral linedef length
-			tp.trb.y = tp.tlt.y + (Sidedef.Other.Sector.FloorHeight - (Sidedef.Sector.FloorHeight + floorbias));
-			
+			tlt.x = tlb.x = 0;
+			trt.x = trb.x = Math.Round(Sidedef.Line.Length); //mxd. (G)ZDoom snaps texture coordinates to integral linedef length
+			tlt.y = trt.y = texturevpeg;
+			tlb.y = trb.y = texturevpeg + (Sidedef.Other.Sector.FloorHeight - (Sidedef.Sector.FloorHeight + floorbias));
+
+			if (!slopeskew)
+			{
+				// Unskewed
+				tlb.y -= sd.Floor.plane.GetZ(vl) - Sidedef.Sector.FloorHeight;
+				trb.y -= sd.Floor.plane.GetZ(vr) - Sidedef.Sector.FloorHeight;
+				tlt.y -= osd.Floor.plane.GetZ(vl) - Sidedef.Other.Sector.FloorHeight;
+				trt.y -= osd.Floor.plane.GetZ(vr) - Sidedef.Other.Sector.FloorHeight;
+			}
+			else if (lowerunpegged)
+			{
+				// Skewed by bottom
+				tlb.y = texturevpeg + osd.Floor.plane.GetZ(vl) - sd.Floor.plane.GetZ(vl);
+				trb.y = tlb.y;
+				tlt.y = tlb.y - (osd.Floor.plane.GetZ(vl) - sd.Floor.plane.GetZ(vl));
+				trt.y = trb.y - (osd.Floor.plane.GetZ(vr) - sd.Floor.plane.GetZ(vr));
+			}
+			else
+			{
+				// Skewed by top
+				tlb.y = texturevpeg + osd.Floor.plane.GetZ(vl) - sd.Floor.plane.GetZ(vl);
+				trb.y = texturevpeg + osd.Floor.plane.GetZ(vr) - sd.Floor.plane.GetZ(vr);
+			}
+
+			if (Math.Abs(trb.y - trt.y) < 0.5f) trb.y = trt.y - 1.0f;
+
 			// Apply texture offset
-			tp.tlt += tof;
-			tp.trb += tof;
-			
+			tlt += tof;
+			tlb += tof;
+			trb += tof;
+			trt += tof;
+
 			// Transform pixel coordinates to texture coordinates
-			tp.tlt /= tsz;
-			tp.trb /= tsz;
-			
-			// Left top and right bottom of the geometry that
-			tp.vlt = new Vector3D(vl.x, vl.y, Sidedef.Other.Sector.FloorHeight);
-			tp.vrb = new Vector3D(vr.x, vr.y, Sidedef.Sector.FloorHeight + floorbias);
-			
-			// Make the right-top coordinates
-			tp.trt = new Vector2D(tp.trb.x, tp.tlt.y);
-			tp.vrt = new Vector3D(tp.vrb.x, tp.vrb.y, tp.vlt.z);
-			
+			tlt /= tsz;
+			tlb /= tsz;
+			trb /= tsz;
+			trt /= tsz;
+
+			// Geometry coordinates
+			vlt = new Vector3D(vl.x, vl.y, osd.Floor.plane.GetZ(vl));
+			vlb = new Vector3D(vl.x, vl.y, sd.Floor.plane.GetZ(vl));
+			vrb = new Vector3D(vr.x, vr.y, sd.Floor.plane.GetZ(vr) + planefloorbias);
+			vrt = new Vector3D(vr.x, vr.y, osd.Floor.plane.GetZ(vr));
+
+			TexturePlane tp = new TexturePlane();
+			tp.tlt = lowerunpegged ? tlb : tlt;
+			tp.trb = trb;
+			tp.trt = trt;
+			tp.vlt = lowerunpegged ? vlb : vlt;
+			tp.vrb = vrb;
+			tp.vrt = vrt;
+
 			// Create initial polygon, which is just a quad between floor and ceiling
 			WallPolygon poly = new WallPolygon();
 			poly.Add(new Vector3D(vl.x, vl.y, vlzf));
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualMiddle3D.cs b/Source/Plugins/BuilderModes/VisualModes/VisualMiddle3D.cs
index aa8645348dece9b61d5d5334ca37e5bc0a137fe2..bcd389efe004fa378d2e4849d4ed2f4e6d8661b0 100755
--- a/Source/Plugins/BuilderModes/VisualModes/VisualMiddle3D.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualMiddle3D.cs
@@ -162,7 +162,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			Vector2D tsz = new Vector2D(Math.Ceiling(base.Texture.ScaledWidth / tscale.x), Math.Ceiling(base.Texture.ScaledHeight / tscale.y));
 			
 			// Get texture offsets
-			Vector2D tof = new Vector2D(Sidedef.OffsetX, Sidedef.OffsetY) + new Vector2D(sourceside.OffsetX, sourceside.OffsetY);
+			//Vector2D tof = new Vector2D(Sidedef.OffsetX, Sidedef.OffsetY) + new Vector2D(sourceside.OffsetX, sourceside.OffsetY);
+			Vector2D tof = new Vector2D(Sidedef.OffsetX, 0.0f) + new Vector2D(0.0f, sourceside.OffsetY);
 
 			tof = tof + toffset1 + toffset2;
 
@@ -184,35 +185,59 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// We choose here.
 			double sourcetopheight = extrafloor.VavoomType ? sourceside.Sector.FloorHeight : sourceside.Sector.CeilHeight;
 			double sourcebottomheight = extrafloor.VavoomType ? sourceside.Sector.CeilHeight : sourceside.Sector.FloorHeight;
-			
+
 			// Determine texture coordinates plane as they would be in normal circumstances.
 			// We can then use this plane to find any texture coordinate we need.
 			// The logic here is the same as in the original VisualMiddleSingle (except that
 			// the values are stored in a TexturePlane)
 			// NOTE: I use a small bias for the floor height, because if the difference in
 			// height is 0 then the TexturePlane doesn't work!
-			TexturePlane tp = new TexturePlane();
-			double floorbias = (sourcetopheight == sourcebottomheight) ? 1.0f : 0.0f;
+			Vector3D vlt, vlb, vrt, vrb;
+			Vector2D tlt, tlb, trt, trb;
+			bool lowerunpegged = sourceside.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag);
+			bool slopeskew = sourceside.Line.IsFlagSet(General.Map.Config.SlopeSkewFlag);
+
+			double topheight = slopeskew ? extrafloor.Ceiling.plane.GetZ(vl) : sourcetopheight;
+			double bottomheight = slopeskew ? extrafloor.Floor.plane.GetZ(vl) : sourcebottomheight;
+			double topheight2 = slopeskew ? extrafloor.Ceiling.plane.GetZ(vr) : sourcetopheight;
+			double bottomheight2 = slopeskew ? extrafloor.Floor.plane.GetZ(vr) : sourcebottomheight;
+			//float floorbias = (topheight == bottomheight) ? 1.0f : 0.0f;
+			//float floorbias2 = (topheight2 == bottomheight2) ? 1.0f : 0.0f;
+
+			tlt.x = tlb.x = 0;
+			trt.x = trb.x = Sidedef.Line.Length;
+			// For SRB2, invert Lower Unpegged behavior for non-skewed 3D floors
+			tlt.y = !(lowerunpegged ^ slopeskew) ? 0 : -(topheight - bottomheight);
+			trt.y = !(lowerunpegged ^ slopeskew) ? 0 : -(topheight2 - bottomheight2);
+			tlb.y = !(!lowerunpegged ^ slopeskew) ? 0 : (topheight - bottomheight);
+			trb.y = !(!lowerunpegged ^ slopeskew) ? 0 : (topheight2 - bottomheight2);
 
-			tp.trb.x = tp.tlt.x + Math.Round(Sidedef.Line.Length); //mxd. (G)ZDoom snaps texture coordinates to integral linedef length
-			tp.trb.y = tp.tlt.y + (sourcetopheight - sourcebottomheight) + floorbias;
-			
 			// Apply texture offset
-			tp.tlt += tof;
-			tp.trb += tof;
-			
+			tlt += tof;
+			tlb += tof;
+			trb += tof;
+			trt += tof;
+
 			// Transform pixel coordinates to texture coordinates
-			tp.tlt /= tsz;
-			tp.trb /= tsz;
-			
-			// Left top and right bottom of the geometry that
-			tp.vlt = new Vector3D(vl.x, vl.y, sourcetopheight);
-			tp.vrb = new Vector3D(vr.x, vr.y, sourcebottomheight + floorbias);
-			
-			// Make the right-top coordinates
-			tp.trt = new Vector2D(tp.trb.x, tp.tlt.y);
-			tp.vrt = new Vector3D(tp.vrb.x, tp.vrb.y, tp.vlt.z);
-			
+			tlt /= tsz;
+			tlb /= tsz;
+			trb /= tsz;
+			trt /= tsz;
+
+			// Geometry coordinates
+			vlt = new Vector3D(vl.x, vl.y, topheight);
+			vlb = new Vector3D(vl.x, vl.y, bottomheight);
+			vrb = new Vector3D(vr.x, vr.y, bottomheight2);
+			vrt = new Vector3D(vr.x, vr.y, topheight2);
+
+			TexturePlane tp = new TexturePlane();
+			tp.tlt = lowerunpegged ? tlb : tlt;
+			tp.trb = trb;
+			tp.trt = trt;
+			tp.vlt = lowerunpegged ? vlb : vlt;
+			tp.vrb = vrb;
+			tp.vrt = vrt;
+
 			//mxd. Get ceiling and floor heights. Use our and neighbour sector's data
 			SectorData sdo = mode.GetSectorData(Sidedef.Other.Sector);
 
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualMiddleDouble.cs b/Source/Plugins/BuilderModes/VisualModes/VisualMiddleDouble.cs
index e958a2d8c9d6e45d7b4011b7857ea4eb6d1f0c60..589c59c1a0bb3f2ee2da9aeba420609cb4483a26 100755
--- a/Source/Plugins/BuilderModes/VisualModes/VisualMiddleDouble.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualMiddleDouble.cs
@@ -38,6 +38,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		#region ================== Variables
 
+		private int repetitions;
 		private bool repeatmidtex;
 		private Plane topclipplane;
 		private Plane bottomclipplane;
@@ -46,6 +47,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		#region ================== Properties
 
+		private bool RepeatIndefinitely { get { return repeatmidtex && repetitions == 1; } }
+
 		#endregion
 
 		#region ================== Constructor / Setup
@@ -153,35 +156,141 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// the values are stored in a TexturePlane)
 			// NOTE: I use a small bias for the floor height, because if the difference in
 			// height is 0 then the TexturePlane doesn't work!
-			TexturePlane tp = new TexturePlane();
-			double floorbias = (Sidedef.Sector.CeilHeight == Sidedef.Sector.FloorHeight) ? 1.0 : 0.0;
+			Vector3D vlt, vlb, vrt, vrb;
+			Vector2D tlt, tlb, trt, trb;
 			double geotop = Math.Min(Sidedef.Sector.CeilHeight, Sidedef.Other.Sector.CeilHeight);
 			double geobottom = Math.Max(Sidedef.Sector.FloorHeight, Sidedef.Other.Sector.FloorHeight);
-			double zoffset = Sidedef.Sector.CeilHeight - Sidedef.Other.Sector.CeilHeight; //mxd
+			double geoplanetop = Math.Min(sd.Ceiling.plane.GetZ(vl), osd.Ceiling.plane.GetZ(vl));
+			double geoplanebottom = Math.Max(sd.Floor.plane.GetZ(vl), osd.Floor.plane.GetZ(vl));
 
-			// When peg midtexture is set, the middle texture is bound to the bottom
-			if(Sidedef.Line.IsFlagSet(General.Map.Config.PegMidtextureFlag))
-				tp.tlt.y = tsz.y - (geotop - geobottom);
-			
-			if(zoffset > 0) tp.tlt.y -= zoffset; //mxd
-			tp.trb.x = tp.tlt.x + Math.Round(Sidedef.Line.Length); //mxd. (G)ZDoom snaps texture coordinates to integral linedef length
-			tp.trb.y = tp.tlt.y + (Sidedef.Sector.CeilHeight - (Sidedef.Sector.FloorHeight + floorbias));
+			bool lowerunpegged = Sidedef.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag);
+			bool pegmidtexture = Sidedef.Line.IsFlagSet(General.Map.Config.PegMidtextureFlag);
+			bool nomidtextureskew = Sidedef.Line.IsFlagSet(General.Map.Config.NoMidtextureSkewFlag);
+
+			double textop = nomidtextureskew ? geotop : geoplanetop;
+			double texbottom = nomidtextureskew ? geobottom : geoplanebottom;
+
+			if (General.Map.UDMF)
+			{
+				repeatmidtex = Sidedef.IsFlagSet("wrapmidtex") || Sidedef.Line.IsFlagSet("wrapmidtex"); //mxd
+				repetitions = repeatmidtex ? Sidedef.Fields.GetValue("repeatcnt", 0) + 1 : 1;
+			}
+			else if (General.Map.HEXEN)
+			{
+				repeatmidtex = Sidedef.Line.Action == 121 && (Sidedef.Line.Args[1] & 16) == 16;
+				repetitions = 1;
+			}
+			else
+			{
+				repeatmidtex = false;
+				repetitions = 1;
+			}
 
-			// Apply texture offset
-			tp.tlt += tof;
-			tp.trb += tof;
+			// First determine the visible portion of the texture
+			if (!RepeatIndefinitely)
+			{
+				// Determine top portion height
+				if (Sidedef.Line.IsFlagSet(General.Map.Config.PegMidtextureFlag))
+					textop = texbottom + tof.y + repetitions * Math.Abs(tsz.y);
+				else
+					textop += tof.y;
+
+				// Calculate bottom portion height
+				texbottom = textop - repetitions * Math.Abs(tsz.y);
+			}
+			else
+			{
+				if (Sidedef.Line.IsFlagSet(General.Map.Config.PegMidtextureFlag))
+				{
+					//textop = topheight;
+					texbottom += tof.y;
+				}
+				else
+				{
+					textop += tof.y;
+					//texbottom = bottomheight;
+				}
+			}
+
+			double h = Math.Min(textop, geoplanetop);
+			double l = Math.Max(texbottom, geoplanebottom);
+			double texturevpeg;
+
+			// When lower unpegged is set, the middle texture is bound to the bottom
+			if (pegmidtexture)
+				texturevpeg = repetitions * Math.Abs(tsz.y) - h + texbottom;
+			else
+				texturevpeg = textop - h;
 
+			// If the difference in height is zero, TexturePlane works funkily
+			double leftfloorbias = Math.Abs(h - l) <= double.Epsilon ? 1.0 : 0.0;
+
+			tlt.x = tlb.x = tof.x;
+			trt.x = trb.x = tof.x + Sidedef.Line.Length;
+			tlt.y = trt.y = texturevpeg;
+			tlb.y = trb.y = texturevpeg + h - (l + leftfloorbias);
+
+			// Correct to account for slopes
+			double midtextureslant;
+
+			if (nomidtextureskew)
+				midtextureslant = 0;
+			else if (pegmidtexture)
+				midtextureslant = osd.Floor.plane.GetZ(vl) < sd.Floor.plane.GetZ(vl)
+						  ? sd.Floor.plane.GetZ(vr) - sd.Floor.plane.GetZ(vl)
+						  : osd.Floor.plane.GetZ(vr) - osd.Floor.plane.GetZ(vl);
+			else
+				midtextureslant = sd.Ceiling.plane.GetZ(vl) < osd.Ceiling.plane.GetZ(vl)
+						  ? sd.Ceiling.plane.GetZ(vr) - sd.Ceiling.plane.GetZ(vl)
+						  : osd.Ceiling.plane.GetZ(vr) - osd.Ceiling.plane.GetZ(vl);
+
+			double newtextop = textop + midtextureslant;
+			double newtexbottom = texbottom + midtextureslant;
+
+			double highcut = geotop + (sd.Ceiling.plane.GetZ(vl) < osd.Ceiling.plane.GetZ(vl)
+						  ? sd.Ceiling.plane.GetZ(vr) - sd.Ceiling.plane.GetZ(vl)
+						  : osd.Ceiling.plane.GetZ(vr) - osd.Ceiling.plane.GetZ(vl));
+			double lowcut = geobottom + (osd.Floor.plane.GetZ(vl) < sd.Floor.plane.GetZ(vl)
+						  ? sd.Floor.plane.GetZ(vr) - sd.Floor.plane.GetZ(vl)
+						  : osd.Floor.plane.GetZ(vr) - osd.Floor.plane.GetZ(vl));
+
+			// Texture stuff
+			double rh = Math.Min(highcut, newtextop);
+			double rl = Math.Max(newtexbottom, lowcut);
+
+			double newtexturevpeg;
+
+			// When lower unpegged is set, the middle texture is bound to the bottom
+			if (pegmidtexture)
+				newtexturevpeg = repetitions * Math.Abs(tsz.y) - rh + newtexbottom;
+			else
+				newtexturevpeg = newtextop - rh;
+
+			// If the difference in height is zero, TexturePlane works funkily
+			double rightfloorbias = Math.Abs(rh - rl) <= double.Epsilon ? 1.0 : 0.0;
+
+			trt.y = newtexturevpeg;
+			trb.y = newtexturevpeg + rh - (rl + rightfloorbias);
+			
 			// Transform pixel coordinates to texture coordinates
-			tp.tlt /= tsz;
-			tp.trb /= tsz;
+			tlt /= tsz;
+			tlb /= tsz;
+			trb /= tsz;
+			trt /= tsz;
 
-			// Left top and right bottom of the geometry that
-			tp.vlt = new Vector3D(vl.x, vl.y, Sidedef.Sector.CeilHeight);
-			tp.vrb = new Vector3D(vr.x, vr.y, Sidedef.Sector.FloorHeight + floorbias);
+			// Geometry coordinates
+			vlt = new Vector3D(vl.x, vl.y, h);
+			vlb = new Vector3D(vl.x, vl.y, l + leftfloorbias);
+			vrb = new Vector3D(vr.x, vr.y, rl + rightfloorbias);
+			vrt = new Vector3D(vr.x, vr.y, rh);
 
-			// Make the right-top coordinates
-			tp.trt = new Vector2D(tp.trb.x, tp.tlt.y);
-			tp.vrt = new Vector3D(tp.vrb.x, tp.vrb.y, tp.vlt.z);
+			TexturePlane tp = new TexturePlane();
+			tp.tlt = pegmidtexture ? tlb : tlt;
+			tp.trb = trb;
+			tp.trt = trt;
+			tp.vlt = pegmidtexture ? vlb : vlt;
+			tp.vrb = vrb;
+			tp.vrt = vrt;
 
 			// Keep top and bottom planes for intersection testing
 			top = sd.Ceiling.plane;
@@ -207,38 +316,43 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			CropPoly(ref poly, osd.Ceiling.plane, true);
 			CropPoly(ref poly, osd.Floor.plane, true);
 
-			// Determine if we should repeat the middle texture. In UDMF this is done with a flag, in Hexen with
-			// a argument to the 121:Line_SetIdentification. See https://www.zdoom.org/w/index.php?title=Line_SetIdentification
-			if (General.Map.UDMF)
-				repeatmidtex = Sidedef.IsFlagSet("wrapmidtex") || Sidedef.Line.IsFlagSet("wrapmidtex"); //mxd
-			else if (General.Map.HEXEN)
-				repeatmidtex = Sidedef.Line.Action == 121 && (Sidedef.Line.Args[1] & 16) == 16;
-			else
-				repeatmidtex = false;
+			// First determine the visible portion of the texture
+			// NOTE: this is done earlier for SRB2 since it's needed earlier
 
-			if(!repeatmidtex) 
+			// Create crop planes (we also need these for intersection testing)
+			if (!nomidtextureskew)
 			{
-				// First determine the visible portion of the texture
-				double textop;
-
-				// Determine top portion height
-				if(Sidedef.Line.IsFlagSet(General.Map.Config.PegMidtextureFlag))
-					textop = geobottom + tof.y + Math.Abs(tsz.y);
-				else
-					textop = geotop + tof.y;
+				if (pegmidtexture)
+				{
+					bottomclipplane = sd.Floor.plane.GetZ(vl) > osd.Floor.plane.GetZ(vl) ? sd.Floor.plane : osd.Floor.plane;
+					Vector3D up = new Vector3D(0f, 0f, texbottom - bottomclipplane.GetZ(vl));
+					bottomclipplane.Offset -= Vector3D.DotProduct(up, bottomclipplane.Normal);
 
-				// Calculate bottom portion height
-				double texbottom = textop - Math.Abs(tsz.y);
+					topclipplane = bottomclipplane.GetInverted();
+					Vector3D up2 = new Vector3D(0f, 0f, textop - texbottom);
+					topclipplane.Offset -= Vector3D.DotProduct(up2, topclipplane.Normal);
+				}
+				else //Skew according to ceiling
+				{
+					topclipplane = sd.Ceiling.plane.GetZ(vl) < osd.Ceiling.plane.GetZ(vl) ? sd.Ceiling.plane : osd.Ceiling.plane;
+					Vector3D up = new Vector3D(0f, 0f, textop - topclipplane.GetZ(vl));
+					topclipplane.Offset -= Vector3D.DotProduct(up, topclipplane.Normal);
 
-				// Create crop planes (we also need these for intersection testing)
+					bottomclipplane = topclipplane.GetInverted();
+					Vector3D down = new Vector3D(0f, 0f, texbottom - textop);
+					bottomclipplane.Offset -= Vector3D.DotProduct(down, bottomclipplane.Normal);
+				}
+			}
+			else
+			{
 				topclipplane = new Plane(new Vector3D(0, 0, -1), textop);
 				bottomclipplane = new Plane(new Vector3D(0, 0, 1), -texbottom);
-
-				// Crop polygon by these heights
-				CropPoly(ref poly, topclipplane, true);
-				CropPoly(ref poly, bottomclipplane, true);
 			}
 
+			// Crop polygon by these heights
+			CropPoly(ref poly, topclipplane, true);
+			CropPoly(ref poly, bottomclipplane, true);
+
 			//mxd. In(G)ZDoom, middle sidedef parts are not clipped by extrafloors of any type...
 			List<WallPolygon> polygons = new List<WallPolygon> { poly };
 			//ClipExtraFloors(polygons, sd.ExtraFloors, true); //mxd
@@ -316,11 +430,11 @@ namespace CodeImp.DoomBuilder.BuilderModes
                 % imageWidth);
 
             int oy;
-            if (repeatmidtex)
+            if (RepeatIndefinitely)
             {
                 bool pegbottom = Sidedef.Line.IsFlagSet(General.Map.Config.PegMidtextureFlag);
-				double zoffset = (pegbottom ? Sidedef.Sector.FloorHeight : Sidedef.Sector.CeilHeight);
-                oy = (int)Math.Floor(((pickintersect.z - zoffset) * UniFields.GetFloat(Sidedef.Fields, "scaley_mid", 1.0f) / texscale.y
+				double zoffset = (pegbottom ? bottomclipplane.GetZ(pickintersect) : topclipplane.GetZ(pickintersect));
+                oy = (int)Math.Ceiling(((pickintersect.z - zoffset) * UniFields.GetFloat(Sidedef.Fields, "scaley_mid", 1.0f) / texscale.y
                     - ((Sidedef.OffsetY - UniFields.GetFloat(Sidedef.Fields, "offsety_mid")) / imgscale.y))
                     % imageHeight);
             }
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualMiddleSingle.cs b/Source/Plugins/BuilderModes/VisualModes/VisualMiddleSingle.cs
index c9d75e5b08818607f4de9a18aedea6cddd842090..c045e30a7460645562b254c41ad95fc106050e33 100755
--- a/Source/Plugins/BuilderModes/VisualModes/VisualMiddleSingle.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualMiddleSingle.cs
@@ -139,31 +139,76 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// the values are stored in a TexturePlane)
 			// NOTE: I use a small bias for the floor height, because if the difference in
 			// height is 0 then the TexturePlane doesn't work!
-			TexturePlane tp = new TexturePlane();
-			double floorbias = (Sidedef.Sector.CeilHeight == Sidedef.Sector.FloorHeight) ? 1.0 : 0.0;
-			if(Sidedef.Line.IsFlagSet(General.Map.Config.PegMidtextureFlag))
+			Vector3D vlt, vlb, vrt, vrb;
+			Vector2D tlt, tlb, trt, trb;
+			double texturevpeg = 0;
+			double floorbias = (Sidedef.Sector.CeilHeight == Sidedef.Sector.FloorHeight) ? 1.0f : 0.0f;
+
+			bool lowerunpegged = Sidedef.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag);
+			bool nomidtextureskew = Sidedef.Line.IsFlagSet(General.Map.Config.NoMidtextureSkewFlag);
+
+			if (lowerunpegged)
 			{
-				// When peg midtexture is set, the middle texture is bound to the bottom
-				tp.tlt.y = tsz.y - (Sidedef.Sector.CeilHeight - Sidedef.Sector.FloorHeight);
+				// When lower unpegged is set, the middle texture is bound to the bottom
+				if (!nomidtextureskew)
+				{
+					texturevpeg = tsz.y - (sd.Ceiling.plane.GetZ(vl) - sd.Floor.plane.GetZ(vl));
+				}
+				else
+				{
+					texturevpeg = tsz.y - (Sidedef.Sector.CeilHeight - Sidedef.Sector.FloorHeight);
+				}
 			}
-			tp.trb.x = tp.tlt.x + Math.Round(Sidedef.Line.Length); //mxd. (G)ZDoom snaps texture coordinates to integral linedef length
-			tp.trb.y = tp.tlt.y + (Sidedef.Sector.CeilHeight - (Sidedef.Sector.FloorHeight + floorbias));
-			
+
+			tlt.x = tlb.x = 0;
+			trt.x = trb.x = Sidedef.Line.Length;
+			tlt.y = trt.y = texturevpeg;
+			tlb.y = trb.y = texturevpeg + (Sidedef.Sector.CeilHeight - (Sidedef.Sector.FloorHeight + floorbias));
+
+			// Texture correction for slopes
+			if (nomidtextureskew)
+			{
+				tlt.y += Sidedef.Sector.CeilHeight - sd.Ceiling.plane.GetZ(vl);
+				trt.y += Sidedef.Sector.CeilHeight - sd.Ceiling.plane.GetZ(vr);
+				tlb.y += Sidedef.Sector.FloorHeight - sd.Floor.plane.GetZ(vl);
+				trb.y += Sidedef.Sector.FloorHeight - sd.Floor.plane.GetZ(vr);
+			}
+			else if (lowerunpegged)
+			{
+				tlt.y = tlb.y + sd.Floor.plane.GetZ(vl) - sd.Ceiling.plane.GetZ(vl);
+				trt.y = trb.y + sd.Floor.plane.GetZ(vr) - sd.Ceiling.plane.GetZ(vr);
+			}
+			else
+			{
+				tlb.y = tlt.y - (sd.Floor.plane.GetZ(vl) - sd.Ceiling.plane.GetZ(vl));
+				trb.y = trt.y - (sd.Floor.plane.GetZ(vr) - sd.Ceiling.plane.GetZ(vr));
+			}
+
 			// Apply texture offset
-			tp.tlt += tof;
-			tp.trb += tof;
-			
+			tlt += tof;
+			tlb += tof;
+			trb += tof;
+			trt += tof;
+
 			// Transform pixel coordinates to texture coordinates
-			tp.tlt /= tsz;
-			tp.trb /= tsz;
-			
-			// Left top and right bottom of the geometry that
-			tp.vlt = new Vector3D(vl.x, vl.y, Sidedef.Sector.CeilHeight);
-			tp.vrb = new Vector3D(vr.x, vr.y, Sidedef.Sector.FloorHeight + floorbias);
-			
-			// Make the right-top coordinates
-			tp.trt = new Vector2D(tp.trb.x, tp.tlt.y);
-			tp.vrt = new Vector3D(tp.vrb.x, tp.vrb.y, tp.vlt.z);
+			tlt /= tsz;
+			tlb /= tsz;
+			trb /= tsz;
+			trt /= tsz;
+
+			// Geometry coordinates
+			vlt = new Vector3D(vl.x, vl.y, sd.Ceiling.plane.GetZ(vl));
+			vlb = new Vector3D(vl.x, vl.y, sd.Floor.plane.GetZ(vl) + floorbias);
+			vrb = new Vector3D(vr.x, vr.y, sd.Floor.plane.GetZ(vr));
+			vrt = new Vector3D(vr.x, vr.y, sd.Ceiling.plane.GetZ(vr));
+
+			TexturePlane tp = new TexturePlane();
+			tp.tlt = lowerunpegged ? tlb : tlt;
+			tp.trb = trb;
+			tp.trt = trt;
+			tp.vlt = lowerunpegged ? vlb : vlt;
+			tp.vrb = vrb;
+			tp.vrt = vrt;
 
 			// Get ceiling and floor heights
 			double fl = sd.Floor.plane.GetZ(vl);
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs b/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs
index 3e09a375ccbcd13b7046018d5fb03cc4e1b9ae79..4b300b68c7b8cedbb4c7960a74fc2e6b66b054b5 100755
--- a/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs
@@ -154,32 +154,85 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// the values are stored in a TexturePlane)
 			// NOTE: I use a small bias for the floor height, because if the difference in
 			// height is 0 then the TexturePlane doesn't work!
-			TexturePlane tp = new TexturePlane();
+			Vector3D vlt, vlb, vrt, vrb;
+			Vector2D tlt, tlb, trt, trb;
 			double ceilbias = (Sidedef.Other.Sector.CeilHeight == Sidedef.Sector.CeilHeight) ? 1.0 : 0.0;
-			if(!Sidedef.Line.IsFlagSet(General.Map.Config.UpperUnpeggedFlag))
+			double planeceilbias = (Math.Abs(osd.Ceiling.plane.GetZ(vr) - sd.Ceiling.plane.GetZ(vr)) < 0.5) ? 1.0 : 0.0;
+			double texturevpeg = 0;
+
+			bool upperunpegged = Sidedef.Line.IsFlagSet(General.Map.Config.UpperUnpeggedFlag);
+			bool slopeskew = Sidedef.Line.IsFlagSet(General.Map.Config.SlopeSkewFlag);
+
+			if (!upperunpegged)
 			{
 				// When lower unpegged is set, the lower texture is bound to the bottom
-				tp.tlt.y = tsz.y - (Sidedef.Sector.CeilHeight - Sidedef.Other.Sector.CeilHeight);
+				if (slopeskew)
+				{
+					texturevpeg = osd.Ceiling.plane.GetZ(vl) + tsz.y - sd.Ceiling.plane.GetZ(vl);
+				}
+				else
+				{
+					texturevpeg = tsz.y - ((float)Sidedef.Sector.CeilHeight - Sidedef.Other.Sector.CeilHeight);
+				}
 			}
-			tp.trb.x = tp.tlt.x + Math.Round(Sidedef.Line.Length); //mxd. (G)ZDoom snaps texture coordinates to integral linedef length
-			tp.trb.y = tp.tlt.y + (Sidedef.Sector.CeilHeight - (Sidedef.Other.Sector.CeilHeight + ceilbias));
-			
+			tlt.x = tlb.x = 0;
+			trt.x = trb.x = Sidedef.Line.Length;
+			tlt.y = trt.y = texturevpeg;
+			tlb.y = trb.y = texturevpeg + (Sidedef.Sector.CeilHeight - (Sidedef.Other.Sector.CeilHeight + ceilbias));
+
+			// Adjust texture y value for sloped walls
+			if (!slopeskew)
+			{
+				// Unskewed
+				tlt.y -= sd.Ceiling.plane.GetZ(vl) - Sidedef.Sector.CeilHeight;
+				trt.y -= sd.Ceiling.plane.GetZ(vr) - Sidedef.Sector.CeilHeight;
+				tlb.y -= osd.Ceiling.plane.GetZ(vl) - Sidedef.Other.Sector.CeilHeight;
+				trb.y -= osd.Ceiling.plane.GetZ(vr) - Sidedef.Other.Sector.CeilHeight;
+			}
+			else if (upperunpegged)
+			{
+				// Skewed by top
+				tlb.y = texturevpeg + sd.Ceiling.plane.GetZ(vl) - osd.Ceiling.plane.GetZ(vl);
+				trb.y = texturevpeg + sd.Ceiling.plane.GetZ(vr) - osd.Ceiling.plane.GetZ(vr);
+			}
+			else
+			{
+				// Skewed by bottom
+				tlb.y = texturevpeg + sd.Ceiling.plane.GetZ(vl) - osd.Ceiling.plane.GetZ(vl);
+				trb.y = tlb.y;
+				tlt.y = tlb.y - (sd.Ceiling.plane.GetZ(vl) - osd.Ceiling.plane.GetZ(vl));
+				trt.y = trb.y - (sd.Ceiling.plane.GetZ(vr) - osd.Ceiling.plane.GetZ(vr));
+			}
+
+
+			if (Math.Abs(trb.y - trt.y) < 0.5f) trb.y = trt.y - 1.0f;
+
 			// Apply texture offset
-			tp.tlt += tof;
-			tp.trb += tof;
-			
+			tlt += tof;
+			tlb += tof;
+			trb += tof;
+			trt += tof;
+
 			// Transform pixel coordinates to texture coordinates
-			tp.tlt /= tsz;
-			tp.trb /= tsz;
-			
-			// Left top and right bottom of the geometry that
-			tp.vlt = new Vector3D(vl.x, vl.y, Sidedef.Sector.CeilHeight);
-			tp.vrb = new Vector3D(vr.x, vr.y, Sidedef.Other.Sector.CeilHeight + ceilbias);
-			
-			// Make the right-top coordinates
-			tp.trt = new Vector2D(tp.trb.x, tp.tlt.y);
-			tp.vrt = new Vector3D(tp.vrb.x, tp.vrb.y, tp.vlt.z);
-			
+			tlt /= tsz;
+			tlb /= tsz;
+			trb /= tsz;
+			trt /= tsz;
+
+			// Geometry coordinates
+			vlt = new Vector3D(vl.x, vl.y, sd.Ceiling.plane.GetZ(vl));
+			vlb = new Vector3D(vl.x, vl.y, osd.Ceiling.plane.GetZ(vl));
+			vrb = new Vector3D(vr.x, vr.y, osd.Ceiling.plane.GetZ(vr) + planeceilbias);
+			vrt = new Vector3D(vr.x, vr.y, sd.Ceiling.plane.GetZ(vr));
+
+			TexturePlane tp = new TexturePlane();
+			tp.tlt = Sidedef.Line.IsFlagSet(General.Map.Config.UpperUnpeggedFlag) ? tlt : tlb;
+			tp.trb = trb;
+			tp.trt = trt;
+			tp.vlt = Sidedef.Line.IsFlagSet(General.Map.Config.UpperUnpeggedFlag) ? vlt : vlb;
+			tp.vrb = vrb;
+			tp.vrt = vrt;
+
 			// Create initial polygon, which is just a quad between floor and ceiling
 			WallPolygon poly = new WallPolygon();
 			poly.Add(new Vector3D(vl.x, vl.y, sd.Floor.plane.GetZ(vl)));