diff --git a/Build/Configurations/Includes/Eternity_common.cfg b/Build/Configurations/Includes/Eternity_common.cfg
index 58a5e993c5c6b413dec58f51d0e482ea444ae1f8..bd636a084c8b3b690398c0e81db44400fd4df2ea 100755
--- a/Build/Configurations/Includes/Eternity_common.cfg
+++ b/Build/Configurations/Includes/Eternity_common.cfg
@@ -245,6 +245,9 @@ mapformat_udmf
 
 	// Enabled setting brightness for upper, middle, and lower sidedef independently from each other
 	distinctsidedefpartbrightness = true;
+
+	// Enables support for skewing sidedef textures
+	sidedeftextureskewing = true;
 	
 	// Default nodebuilder configurations
 	defaultsavecompiler = "zdbsp_udmf_normal";
diff --git a/Source/Core/Config/GameConfiguration.cs b/Source/Core/Config/GameConfiguration.cs
index b16bc22edd429405dc581613c402a17ebf4df1df..0b6d391388f017c0e2d1cc0643708180fce1fa28 100755
--- a/Source/Core/Config/GameConfiguration.cs
+++ b/Source/Core/Config/GameConfiguration.cs
@@ -114,6 +114,7 @@ namespace CodeImp.DoomBuilder.Config
 		private readonly bool distinctwallbrightness;
 		private readonly bool distinctsidedefpartbrightness;
 		private readonly bool sectormultitag;
+		private readonly bool sidedeftextureskewing;
 
 		// Skills
 		private readonly List<SkillInfo> skills;
@@ -293,6 +294,7 @@ namespace CodeImp.DoomBuilder.Config
 		public bool DistinctWallBrightness { get { return distinctwallbrightness; } }
 		public bool DistinctSidedefPartBrightness { get { return distinctsidedefpartbrightness; } }
 		public bool SectorMultiTag { get { return sectormultitag; } }
+		public bool SidedefTextureSkewing { get { return sidedeftextureskewing; } }
 
 		// Texture/flat/voxel sources
 		public IDictionary TextureRanges { get { return textureranges; } }
@@ -470,6 +472,7 @@ namespace CodeImp.DoomBuilder.Config
 			localsidedeftextureoffsets = (cfg.ReadSetting("localsidedeftextureoffsets", false)); //MaxW
 			effect3dfloorsupport = cfg.ReadSetting("effect3dfloorsupport", false);
 			planeequationsupport = cfg.ReadSetting("planeequationsupport", false);
+			sidedeftextureskewing = cfg.ReadSetting("sidedeftextureskewing", false);
 			distinctfloorandceilingbrightness = cfg.ReadSetting("distinctfloorandceilingbrightness", false);
 			distinctwallbrightness = cfg.ReadSetting("distinctwallbrightness", false);
 			distinctsidedefpartbrightness = cfg.ReadSetting("distinctsidedefpartbrightness", false);
diff --git a/Source/Core/Rendering/RenderDevice.cs b/Source/Core/Rendering/RenderDevice.cs
index a781ce781c0d4c4f529db72e026c53d0af978a80..3042db8cb06385cdef0b8abe66f2b02511abab19 100755
--- a/Source/Core/Rendering/RenderDevice.cs
+++ b/Source/Core/Rendering/RenderDevice.cs
@@ -77,6 +77,8 @@ namespace CodeImp.DoomBuilder.Rendering
             DeclareUniform(UniformName.doomlightlevels, "doomlightlevels", UniformType.Int);
             DeclareUniform(UniformName.sectorLightLevel, "sectorLightLevel", UniformType.Int);
 
+            DeclareUniform(UniformName.skew, "skew", UniformType.Vec2f);
+
             // 2d fsaa
             CompileShader(ShaderName.display2d_fsaa, "display2d.shader", "display2d_fsaa");
             
@@ -804,7 +806,8 @@ namespace CodeImp.DoomBuilder.Rendering
         drawPaletted,
         colormapSize,
         sectorLightLevel,
-        doomlightlevels
+        doomlightlevels,
+        skew
     }
 
     public enum VertexFormat : int { Flat, World }
diff --git a/Source/Core/Rendering/Renderer3D.cs b/Source/Core/Rendering/Renderer3D.cs
index 5c2bc27da291e3c093eaf0b6a7bd4596cb9a6e6a..d6ba50c703e874954d3875a6fcd660db6bafc7b3 100755
--- a/Source/Core/Rendering/Renderer3D.cs
+++ b/Source/Core/Rendering/Renderer3D.cs
@@ -974,6 +974,9 @@ namespace CodeImp.DoomBuilder.Rendering
                         // [ZZ] include desaturation factor
                         graphics.SetUniform(UniformName.desaturation, (float)sector.Sector.Desaturation);
 
+						// Skew
+						graphics.SetUniform(UniformName.skew, g.Skew);
+
 						// Render!
 						graphics.Draw(PrimitiveType.TriangleList, g.VertexOffset, g.Triangles);
 					}
diff --git a/Source/Core/Resources/world3d.shader b/Source/Core/Resources/world3d.shader
index 1ef9944ad474c618eb719dd7726a55d82ed739f9..25d5e23d51347c61fc117dfeca34dbc2a60ab1e9 100755
--- a/Source/Core/Resources/world3d.shader
+++ b/Source/Core/Resources/world3d.shader
@@ -36,6 +36,9 @@ uniforms
 	// Slope handle length
 	float slopeHandleLength;
 
+	// Skewing
+	vec2 skew; // the x component is used to negate the texture offset for the skew computation. There must be a better way
+
 }
 
 functions
@@ -223,7 +226,7 @@ shader world3d_main
 	
 	fragment
 	{
-		vec4 tcolor = texture(texture1, v2f.UV);
+		vec4 tcolor = texture(texture1, v2f.UV + vec2(0.0, (v2f.UV.x - skew.x) * skew.y));
 		tcolor = mix(tcolor, vec4(stencilColor.rgb, tcolor.a), stencilColor.a);
 		tcolor = getDynLightContribution(tcolor, v2f.Color, v2f.PosW, v2f.Normal);
 		out.FragColor = desaturate(tcolor);
@@ -240,7 +243,7 @@ shader world3d_fullbright extends world3d_main
 {
 	fragment
 	{
-		vec4 tcolor = texture(texture1, v2f.UV);
+		vec4 tcolor = texture(texture1, v2f.UV + vec2(0.0, (v2f.UV.x - skew.x) * skew.y));
 		tcolor = mix(tcolor, vec4(stencilColor.rgb, tcolor.a), stencilColor.a);
 		tcolor.a *= v2f.Color.a;
 		out.FragColor = tcolor;
@@ -257,7 +260,7 @@ shader world3d_main_highlight extends world3d_main
 {
 	fragment
 	{
-		vec4 tcolor = texture(texture1, v2f.UV);
+		vec4 tcolor = texture(texture1, v2f.UV + vec2(0.0, (v2f.UV.x - skew.x) * skew.y));
 		tcolor = mix(tcolor, vec4(stencilColor.rgb, tcolor.a), stencilColor.a);
 		tcolor = getDynLightContribution(tcolor, v2f.Color, v2f.PosW, v2f.Normal);
 		if (tcolor.a == 0.0)
@@ -284,7 +287,7 @@ shader world3d_fullbright_highlight extends world3d_fullbright
 {
 	fragment
 	{
-		vec4 tcolor = texture(texture1, v2f.UV);
+		vec4 tcolor = texture(texture1, v2f.UV + vec2(0.0, (v2f.UV.x - skew.x) * skew.y));
 		tcolor = mix(tcolor, vec4(stencilColor.rgb, tcolor.a), stencilColor.a);
 		if(tcolor.a == 0.0)
 		{
@@ -362,7 +365,7 @@ shader world3d_main_fog extends world3d_main
 {
 	fragment
 	{
-		vec4 tcolor = texture(texture1, v2f.UV);
+		vec4 tcolor = texture(texture1, v2f.UV + vec2(0.0, (v2f.UV.x - skew.x) * skew.y));
 		tcolor = mix(tcolor, vec4(stencilColor.rgb, tcolor.a), stencilColor.a);
 		tcolor = getDynLightContribution(tcolor, v2f.Color, v2f.PosW, v2f.Normal);
 		if (tcolor.a == 0.0)
@@ -386,7 +389,7 @@ shader world3d_main_highlight_fog extends world3d_main_fog
 {
 	fragment
 	{
-		vec4 tcolor = texture(texture1, v2f.UV);
+		vec4 tcolor = texture(texture1, v2f.UV + vec2(0.0, (v2f.UV.x - skew.x) * skew.y));
 		tcolor = mix(tcolor, vec4(stencilColor.rgb, tcolor.a), stencilColor.a);
 		tcolor = vec4(getDynLightContribution(tcolor, v2f.Color, v2f.PosW, v2f.Normal).rgb, tcolor.a);
 		if (tcolor.a == 0.0)
@@ -458,7 +461,7 @@ shader world3d_classic extends world3d_main
 		 
 		if (bool(drawPaletted))
 		{
-		    vec4 color = texture(texture1, v2f.UV);
+			vec4 color = texture(texture1, v2f.UV + vec2(0.0, (v2f.UV.x - skew.x) * skew.y));
 		    int entry = int(color.r * 255);
 		    float alpha = color.a;
 		    int lightLevel = lightLevelFromVertexColor(v2f.flatColor.rgb);
@@ -487,7 +490,7 @@ shader world3d_classic_highlight extends world3d_main
 		 
 		if (bool(drawPaletted))
         {
-            vec4 color = texture(texture1, v2f.UV);
+			vec4 color = texture(texture1, v2f.UV + vec2(0.0, (v2f.UV.x - skew.x) * skew.y));
             int entry = int(color.r * 255);
             float alpha = color.a;
             int lightLevel = lightLevelFromVertexColor(v2f.flatColor.rgb);
diff --git a/Source/Core/VisualModes/VisualGeometry.cs b/Source/Core/VisualModes/VisualGeometry.cs
index 3ba6977f9d45633848d49951f8c3435bd948e31e..c43f2be8bbc712a92db4567a6a7cbdd04b0456e3 100755
--- a/Source/Core/VisualModes/VisualGeometry.cs
+++ b/Source/Core/VisualModes/VisualGeometry.cs
@@ -76,6 +76,8 @@ namespace CodeImp.DoomBuilder.VisualModes
 		protected VisualGeometryType geometrytype;
 		protected string partname; //UDMF part name
 		protected bool renderassky;
+
+		protected Vector2f skew;
 		
 		#endregion
 
@@ -117,6 +119,11 @@ namespace CodeImp.DoomBuilder.VisualModes
 		/// </summary>
 		public bool Selected { get { return selected; } set { selected = value; } }
 
+		/// <summary>
+		/// How much a texture is skewed.
+		/// </summary>
+		public Vector2f Skew { get { return skew; } }
+
 		#endregion
 
 		#region ================== Constructor / Destructor
@@ -128,6 +135,7 @@ namespace CodeImp.DoomBuilder.VisualModes
 		{
 			this.sector = vs;
 			this.geometrytype = VisualGeometryType.UNKNOWN; //mxd
+			skew = new Vector2f(0.0f);
 		}
 
 		/// <summary>
@@ -138,6 +146,7 @@ namespace CodeImp.DoomBuilder.VisualModes
 			this.sector = vs;
 			this.sidedef = sd;
 			this.geometrytype = VisualGeometryType.UNKNOWN; //mxd
+			skew = new Vector2f(0.0f);
 		}
 
 		#endregion
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualLower.cs b/Source/Plugins/BuilderModes/VisualModes/VisualLower.cs
index 325d38549e77ec5c1c39814cb5f60dcb3f06cacc..2d4d93c320b11ee099eaea91cff0758a44772968 100755
--- a/Source/Plugins/BuilderModes/VisualModes/VisualLower.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualLower.cs
@@ -19,6 +19,7 @@
 using System;
 using System.Collections.Generic;
 using System.Drawing;
+using System.Linq;
 using CodeImp.DoomBuilder.Map;
 using CodeImp.DoomBuilder.Geometry;
 using CodeImp.DoomBuilder.Rendering;
@@ -127,7 +128,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				base.Texture = General.Map.Data.MissingTexture3D;
 				setuponloadedtexture = 0;
 			}
-			
+
 			// Get texture scaled size. Round up, because that's apparently what GZDoom does
 			Vector2D tsz = new Vector2D(Math.Ceiling(base.Texture.ScaledWidth / tscale.x), Math.Ceiling(base.Texture.ScaledHeight / tscale.y));
 			
@@ -229,6 +230,10 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				if(verts.Count > 2)
 				{
 					base.SetVertices(verts);
+
+					// Set skewing
+					UpdateSkew();
+
 					return true;
 				}
 			}
@@ -318,7 +323,63 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			FitTexture(options);
 			Setup();
 		}
-		
+
+		/// <summary>
+		/// Updates the value for texture skewing. Has to be done after the texture is set.
+		/// </summary>
+		public void UpdateSkew()
+		{
+			// Reset
+			skew = new Vector2f(0.0f);
+
+			if (!General.Map.Config.SidedefTextureSkewing)
+				return;
+			
+			string skewtype = Sidedef.Fields.GetValue("skew_bottom_type", "none");
+
+			if ((skewtype == "front" || skewtype == "back") && Texture != null)
+			{
+				double leftz, rightz;
+
+				if (skewtype == "front")
+				{
+					if (Sidedef.IsFront)
+					{
+						Plane plane = Sector.GetSectorData().Floor.plane;
+						leftz = plane.GetZ(Sidedef.Line.Start.Position);
+						rightz = plane.GetZ(Sidedef.Line.End.Position);
+					}
+					else
+					{
+						Plane plane = mode.GetSectorData(Sidedef.Other.Sector).Floor.plane;
+						leftz = plane.GetZ(Sidedef.Line.End.Position);
+						rightz = plane.GetZ(Sidedef.Line.Start.Position);
+					}
+				}
+				else // "back"
+				{
+					if (Sidedef.IsFront)
+					{
+						Plane plane = mode.GetSectorData(Sidedef.Other.Sector).Floor.plane;
+						leftz = plane.GetZ(Sidedef.Line.Start.Position);
+						rightz = plane.GetZ(Sidedef.Line.End.Position);
+					}
+					else
+					{
+						Plane plane = Sector.GetSectorData().Floor.plane;
+						leftz = plane.GetZ(Sidedef.Line.End.Position);
+						rightz = plane.GetZ(Sidedef.Line.Start.Position);
+					}
+
+				}
+
+				skew = new Vector2f(
+					Vertices.Min(v => v.u), // Get the lowest horizontal texture offset
+					(float)((rightz - leftz) / Sidedef.Line.Length * ((double)Texture.Width / Texture.Height))
+					);
+			}
+		}
+
 		#endregion
 	}
 }
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualMiddleDouble.cs b/Source/Plugins/BuilderModes/VisualModes/VisualMiddleDouble.cs
index 7f3069462f93f7dac94e0971c97e115ec89737f0..9fbd32641468a7160c478d8c9f0334f70e9efab5 100755
--- a/Source/Plugins/BuilderModes/VisualModes/VisualMiddleDouble.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualMiddleDouble.cs
@@ -19,6 +19,7 @@
 using System;
 using System.Collections.Generic;
 using System.Drawing;
+using System.Linq;
 using CodeImp.DoomBuilder.Map;
 using CodeImp.DoomBuilder.Geometry;
 using CodeImp.DoomBuilder.Rendering;
@@ -231,8 +232,15 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				double texbottom = textop - Math.Abs(tsz.y);
 
 				// Create crop planes (we also need these for intersection testing)
-				topclipplane = new Plane(new Vector3D(0, 0, -1), textop);
-				bottomclipplane = new Plane(new Vector3D(0, 0, 1), -texbottom);
+				if (General.Map.Config.SidedefTextureSkewing)
+				{
+					(topclipplane, bottomclipplane) = CreateSkewClipPlanes(textop, texbottom, sd, osd);
+				}
+				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);
@@ -267,6 +275,10 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					}
 
 					base.SetVertices(verts);
+
+					// Set skewing
+					UpdateSkew();
+
 					return true;
 				}
 			//}
@@ -407,7 +419,216 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			FitTexture(options);
 			Setup();
 		}
-		
+
+		/// <summary>
+		/// Updates the value for texture skewing. Has to be done after the texture is set.
+		/// </summary>
+		public void UpdateSkew()
+		{
+			// Reset
+			skew = new Vector2f(0.0f);
+
+			if (!General.Map.Config.SidedefTextureSkewing)
+				return;
+
+			string skewtype = Sidedef.Fields.GetValue("skew_middle_type", "none");
+
+			if ((skewtype == "front_floor" || skewtype == "front_ceiling" || skewtype == "back_floor" || skewtype == "back_ceiling") && Texture != null)
+			{
+				double leftz, rightz;
+
+				if(skewtype == "front_floor")
+				{
+					if (Sidedef.IsFront)
+					{
+						Plane plane = Sector.GetSectorData().Floor.plane;
+						leftz = plane.GetZ(Sidedef.Line.Start.Position);
+						rightz = plane.GetZ(Sidedef.Line.End.Position);
+					}
+					else
+					{
+						Plane plane = mode.GetSectorData(Sidedef.Other.Sector).Floor.plane;
+						leftz = plane.GetZ(Sidedef.Line.End.Position);
+						rightz = plane.GetZ(Sidedef.Line.Start.Position);
+					}
+				}
+				else if(skewtype == "front_ceiling")
+				{
+					if (Sidedef.IsFront)
+					{
+						Plane plane = Sector.GetSectorData().Ceiling.plane;
+						leftz = plane.GetZ(Sidedef.Line.Start.Position);
+						rightz = plane.GetZ(Sidedef.Line.End.Position);
+					}
+					else
+					{
+						Plane plane = mode.GetSectorData(Sidedef.Other.Sector).Ceiling.plane;
+						leftz = plane.GetZ(Sidedef.Line.End.Position);
+						rightz = plane.GetZ(Sidedef.Line.Start.Position);
+					}
+				}
+				else if (skewtype == "back_floor")
+				{
+					if (Sidedef.IsFront)
+					{
+						Plane plane = mode.GetSectorData(Sidedef.Other.Sector).Floor.plane;
+						leftz = plane.GetZ(Sidedef.Line.Start.Position);
+						rightz = plane.GetZ(Sidedef.Line.End.Position);
+					}
+					else
+					{
+						Plane plane = Sector.GetSectorData().Floor.plane;
+						leftz = plane.GetZ(Sidedef.Line.End.Position);
+						rightz = plane.GetZ(Sidedef.Line.Start.Position);
+					}
+				}
+				else // Back ceiling
+				{
+					if (Sidedef.IsFront)
+					{
+						Plane plane = mode.GetSectorData(Sidedef.Other.Sector).Ceiling.plane;
+						leftz = plane.GetZ(Sidedef.Line.Start.Position);
+						rightz = plane.GetZ(Sidedef.Line.End.Position);
+					}
+					else
+					{
+						Plane plane = Sector.GetSectorData().Ceiling.plane;
+						leftz = plane.GetZ(Sidedef.Line.End.Position);
+						rightz = plane.GetZ(Sidedef.Line.Start.Position);
+					}
+				}
+
+				skew = new Vector2f(
+					Vertices.Min(v => v.u), // Get the lowest horizontal texture offset
+					(float)((rightz - leftz) / Sidedef.Line.Length * ((double)Texture.Width / Texture.Height))
+					);
+			}
+		}
+
+		/// <summary>
+		/// Creates clipping planes for skewed sidedefs
+		/// </summary>
+		/// <param name="textop">The texture's top position</param>
+		/// <param name="texbottom">The texture's bottom position</param>
+		/// <param name="sd">This sidedef's sector data</param>
+		/// <param name="osd">The other sidedef's sector data</param>
+		/// <returns>The top and bottom clipping planes</returns>
+		private (Plane, Plane) CreateSkewClipPlanes(double textop, double texbottom, SectorData sd, SectorData osd)
+		{
+			string skewtype = Sidedef.Fields.GetValue("skew_middle_type", "none");
+			if ((skewtype == "front_floor" || skewtype == "front_ceiling" || skewtype == "back_floor" || skewtype == "back_ceiling") && Texture != null)
+			{
+				double diff;
+				Line2D line;
+
+				if (skewtype == "front_ceiling")
+					(diff, line) = GetZDiff(false, true);
+				else if(skewtype == "back_ceiling")
+					(diff, line) = GetZDiff(false, false);
+				else if(skewtype == "front_floor")
+					(diff, line) = GetZDiff(true, true);
+				else // back_floor
+					(diff, line) = GetZDiff(true, false);
+
+
+				Plane p1 = new Plane(
+					new Vector3D(line.v1, textop),
+					new Vector3D(line.v2, textop + diff),
+					new Vector3D(line.GetPerpendicular() * 10, textop),
+					false);
+
+				Plane p2 = new Plane(
+					new Vector3D(line.v1, texbottom),
+					new Vector3D(line.v2, texbottom + diff),
+					new Vector3D(line.GetPerpendicular() * 10, texbottom),
+					true);
+
+				return (p1, p2);
+
+			}
+			else // Invalid skew type
+			{
+				return (
+					new Plane(new Vector3D(0, 0, -1), textop),
+					new Plane(new Vector3D(0, 0, 1), -texbottom)
+				);
+			}
+
+			// Returns the z position at the start and end vertices of the line, and a line that always goes from left to right
+			(double, Line2D) GetZDiff(bool floor, bool front)
+			{
+				double leftz, rightz;
+				Vector2D ls, le;
+
+				if (Sidedef.IsFront)
+				{
+					ls = Sidedef.Line.Start.Position;
+					le = Sidedef.Line.End.Position;
+
+					if (floor)
+					{
+						if (front)
+						{
+							leftz = sd.Floor.plane.GetZ(Sidedef.Line.Start.Position);
+							rightz = sd.Floor.plane.GetZ(Sidedef.Line.End.Position);
+						}
+						else
+						{
+							leftz = osd.Floor.plane.GetZ(Sidedef.Line.Start.Position);
+							rightz = osd.Floor.plane.GetZ(Sidedef.Line.End.Position);
+						}
+					}
+					else
+					{
+						if (front)
+						{
+							leftz = sd.Ceiling.plane.GetZ(Sidedef.Line.Start.Position);
+							rightz = sd.Ceiling.plane.GetZ(Sidedef.Line.End.Position);
+						}
+						else
+						{
+							leftz = osd.Ceiling.plane.GetZ(Sidedef.Line.Start.Position);
+							rightz = osd.Ceiling.plane.GetZ(Sidedef.Line.End.Position);
+						}
+					}
+				}
+				else
+				{
+					ls = Sidedef.Line.End.Position;
+					le = Sidedef.Line.Start.Position;
+
+					if (floor)
+					{
+						if (front)
+						{
+							leftz = osd.Floor.plane.GetZ(Sidedef.Line.End.Position);
+							rightz = osd.Floor.plane.GetZ(Sidedef.Line.Start.Position);
+						}
+						else
+						{
+							leftz = sd.Floor.plane.GetZ(Sidedef.Line.End.Position);
+							rightz = sd.Floor.plane.GetZ(Sidedef.Line.Start.Position);
+						}
+					}
+					else
+					{
+						if (front)
+						{
+							leftz = osd.Ceiling.plane.GetZ(Sidedef.Line.End.Position);
+							rightz = osd.Ceiling.plane.GetZ(Sidedef.Line.Start.Position);
+						}
+						else
+						{
+							leftz = sd.Ceiling.plane.GetZ(Sidedef.Line.End.Position);
+							rightz = sd.Ceiling.plane.GetZ(Sidedef.Line.Start.Position);
+						}
+					}
+				}
+
+				return (rightz - leftz, new Line2D(ls, le));
+			}
+		}
+
 		#endregion
 	}
 }
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualMiddleSingle.cs b/Source/Plugins/BuilderModes/VisualModes/VisualMiddleSingle.cs
index 9af2c2dbbfefc6c21cc11bdb255ac25ca87791ad..5e59d41b84f8e648fb4c573669f42b0f52916ee1 100755
--- a/Source/Plugins/BuilderModes/VisualModes/VisualMiddleSingle.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualMiddleSingle.cs
@@ -19,6 +19,7 @@
 using System;
 using System.Collections.Generic;
 using System.Drawing;
+using System.Linq;
 using CodeImp.DoomBuilder.Map;
 using CodeImp.DoomBuilder.Geometry;
 using CodeImp.DoomBuilder.Rendering;
@@ -56,7 +57,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// We have no destructor
 			GC.SuppressFinalize(this);
 		}
-		
+
 		// This builds the geometry. Returns false when no geometry created.
 		public override bool Setup()
 		{
@@ -69,12 +70,12 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 			Vector2D tscale = new Vector2D(Sidedef.Fields.GetValue("scalex_mid", 1.0),
 										   Sidedef.Fields.GetValue("scaley_mid", 1.0));
-            Vector2D tscaleAbs = new Vector2D(Math.Abs(tscale.x), Math.Abs(tscale.y));
+			Vector2D tscaleAbs = new Vector2D(Math.Abs(tscale.x), Math.Abs(tscale.y));
 			Vector2D toffset = new Vector2D(Sidedef.Fields.GetValue("offsetx_mid", 0.0),
 											Sidedef.Fields.GetValue("offsety_mid", 0.0));
-			
+
 			// Left and right vertices for this sidedef
-			if(Sidedef.IsFront)
+			if (Sidedef.IsFront)
 			{
 				vl = new Vector2D(Sidedef.Line.Start.Position.x, Sidedef.Line.Start.Position.y);
 				vr = new Vector2D(Sidedef.Line.End.Position.x, Sidedef.Line.End.Position.y);
@@ -87,20 +88,20 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 			// Load sector data
 			SectorData sd = mode.GetSectorData(Sidedef.Sector);
-			
+
 			// Texture given?
-			if(Sidedef.LongMiddleTexture != MapSet.EmptyLongName)
+			if (Sidedef.LongMiddleTexture != MapSet.EmptyLongName)
 			{
 				// Load texture
 				base.Texture = General.Map.Data.GetTextureImage(Sidedef.LongMiddleTexture);
-				if(base.Texture == null || base.Texture is UnknownImage)
+				if (base.Texture == null || base.Texture is UnknownImage)
 				{
 					base.Texture = General.Map.Data.UnknownTexture3D;
 					setuponloadedtexture = Sidedef.LongMiddleTexture;
 				}
 				else
 				{
-					if(!base.Texture.IsImageLoaded)
+					if (!base.Texture.IsImageLoaded)
 						setuponloadedtexture = Sidedef.LongMiddleTexture;
 				}
 			}
@@ -141,26 +142,26 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// 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.LowerUnpeggedFlag))
+			if (Sidedef.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag))
 			{
 				// When lower unpegged is set, the middle texture is bound to the bottom
 				tp.tlt.y = 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));
-			
+
 			// Apply texture offset
 			tp.tlt += tof;
 			tp.trb += 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);
@@ -170,21 +171,21 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			double fr = sd.Floor.plane.GetZ(vr);
 			double cl = sd.Ceiling.plane.GetZ(vl);
 			double cr = sd.Ceiling.plane.GetZ(vr);
-			
+
 			// Anything to see?
-			if(((cl - fl) > 0.01f) || ((cr - fr) > 0.01f))
+			if (((cl - fl) > 0.01f) || ((cr - fr) > 0.01f))
 			{
 				// Keep top and bottom planes for intersection testing
 				top = sd.Ceiling.plane;
 				bottom = sd.Floor.plane;
-				
+
 				// Create initial polygon, which is just a quad between floor and ceiling
 				WallPolygon poly = new WallPolygon();
 				poly.Add(new Vector3D(vl.x, vl.y, fl));
 				poly.Add(new Vector3D(vl.x, vl.y, cl));
 				poly.Add(new Vector3D(vr.x, vr.y, cr));
 				poly.Add(new Vector3D(vr.x, vr.y, fr));
-				
+
 				// Determine initial color
 				int lightlevel = lightabsolute ? lightvalue : sd.Ceiling.brightnessbelow + lightvalue;
 
@@ -198,18 +199,22 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				List<WallPolygon> polygons = new List<WallPolygon> { poly };
 				ClipExtraFloors(polygons, sd.ExtraFloors, false); //mxd
 
-				if(polygons.Count > 0)
+				if (polygons.Count > 0)
 				{
 					// Process the polygon and create vertices
 					List<WorldVertex> verts = CreatePolygonVertices(polygons, tp, sd, lightvalue, lightabsolute);
-					if(verts.Count > 2)
+					if (verts.Count > 2)
 					{
 						base.SetVertices(verts);
+
+						// Set skewing
+						UpdateSkew();
+
 						return true;
 					}
 				}
 			}
-			
+
 			base.SetVertices(null); //mxd
 			return false;
 		}
@@ -287,7 +292,36 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			SectorData sd = mode.GetSectorDataEx(Sector.Sector);
 			if(sd != null) sd.Reset(true);
 		}
-		
+
+		/// <summary>
+		/// Updates the value for texture skewing. Has to be done after the texture and vertices are set.
+		/// </summary>
+		public void UpdateSkew()
+		{
+			// Reset
+			skew = new Vector2f(0.0f);
+
+			if (!General.Map.Config.SidedefTextureSkewing)
+				return;
+
+			string skewtype = Sidedef.Fields.GetValue("skew_middle_type", "none");
+
+			// We don't have to check for back because this it's single-sided
+			if ((skewtype == "front_floor" || skewtype == "front_ceiling") && Texture != null)
+			{
+				double leftz, rightz;
+				Plane plane = skewtype == "front_floor" ? Sector.GetSectorData().Floor.plane : Sector.GetSectorData().Ceiling.plane;
+
+				leftz = plane.GetZ(Sidedef.Line.Start.Position);
+				rightz = plane.GetZ(Sidedef.Line.End.Position);
+
+				skew = new Vector2f(
+					Vertices.Min(v => v.u), // Get the lowest horizontal texture offset
+					(float)((rightz - leftz) / Sidedef.Line.Length * ((double)Texture.Width / Texture.Height))
+					);
+			}
+		}
+
 		#endregion
 	}
 }
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs b/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs
index 3e09a375ccbcd13b7046018d5fb03cc4e1b9ae79..dccfe4fc0e382cd49c31d64a1b91227f9ac57654 100755
--- a/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs
@@ -19,6 +19,7 @@
 using System;
 using System.Collections.Generic;
 using System.Drawing;
+using System.Linq;
 using CodeImp.DoomBuilder.Map;
 using CodeImp.DoomBuilder.Geometry;
 using CodeImp.DoomBuilder.Rendering;
@@ -84,7 +85,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			double vrzc = sd.Ceiling.plane.GetZ(vr);
 
 			//mxd. Side is visible when our sector's ceiling is higher than the other's at any vertex
-			if(!(vlzc > osd.Ceiling.plane.GetZ(vl) || vrzc > osd.Ceiling.plane.GetZ(vr)))
+			if (!(vlzc > osd.Ceiling.plane.GetZ(vl) || vrzc > osd.Ceiling.plane.GetZ(vr)))
 			{
 				base.SetVertices(null);
 				return false;
@@ -215,6 +216,10 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				if(verts.Count > 2)
 				{
 					base.SetVertices(verts);
+
+					// Set skewing
+					UpdateSkew();
+
 					return true;
 				}
 			}
@@ -306,6 +311,62 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			FitTexture(options);
 			Setup();
 		}
+
+		/// <summary>
+		/// Updates the value for texture skewing. Has to be done after the texture is set.
+		/// </summary>
+		public void UpdateSkew()
+		{
+			// Reset
+			skew = new Vector2f(0.0f);
+
+			if (!General.Map.Config.SidedefTextureSkewing)
+				return;
+
+			string skewtype = Sidedef.Fields.GetValue("skew_top_type", "none");
+
+			if ((skewtype == "front" || skewtype == "back") && Texture != null)
+			{
+				double leftz, rightz;
+
+				if (skewtype == "front")
+				{
+					if (Sidedef.IsFront)
+					{
+						Plane plane = Sector.GetSectorData().Ceiling.plane;
+						leftz = plane.GetZ(Sidedef.Line.Start.Position);
+						rightz = plane.GetZ(Sidedef.Line.End.Position);
+					}
+					else
+					{
+						Plane plane = mode.GetSectorData(Sidedef.Other.Sector).Ceiling.plane;
+						leftz = plane.GetZ(Sidedef.Line.End.Position);
+						rightz = plane.GetZ(Sidedef.Line.Start.Position);
+					}
+				}
+				else // "back"
+				{
+					if (Sidedef.IsFront)
+					{
+						Plane plane = mode.GetSectorData(Sidedef.Other.Sector).Ceiling.plane;
+						leftz = plane.GetZ(Sidedef.Line.Start.Position);
+						rightz = plane.GetZ(Sidedef.Line.End.Position);
+					}
+					else
+					{
+						Plane plane = Sector.GetSectorData().Ceiling.plane;
+						leftz = plane.GetZ(Sidedef.Line.End.Position);
+						rightz = plane.GetZ(Sidedef.Line.Start.Position);
+					}
+
+				}
+
+				skew = new Vector2f(
+					Vertices.Min(v => v.u), // Get the lowest horizontal texture offset
+					(float)((rightz - leftz) / Sidedef.Line.Length * ((double)Texture.Width / Texture.Height))
+					);
+			}
+		}
 		
 		#endregion
 	}