From fb937b9e61fc076b067bef137382c7cb974d40cd Mon Sep 17 00:00:00 2001
From: MaxED <j.maxed@gmail.com>
Date: Wed, 16 Nov 2016 20:31:04 +0000
Subject: [PATCH] Added "Apply Directional Shading" mode. It can be used to
 apply directional colored flat shading to sloped terrain sectors (line and
 vertex slopes are supported) and sidedefs. Updated ZDoom ACC. Updated
 ZDoom_ACS.cfg.

---
 Build/Compilers/ZDoom/zspecial.acs            |   6 +-
 Build/Scripting/ZDoom_ACS.cfg                 |   4 +-
 Source/Core/Controls/ColorFieldsControl.cs    |  10 +
 Source/Core/Rendering/PixelColor.cs           |   3 +
 Source/Core/Windows/SectorEditFormUDMF.cs     |   3 +-
 .../BuilderEffects/BuilderEffects.csproj      |  12 +
 Source/Plugins/BuilderEffects/BuilderPlug.cs  | 222 ++++++++++++---
 .../BuilderEffects/Controls/IntControl.cs     |   1 +
 .../Interface/DirectionalShadingForm.cs       | 262 ++++++++++++++++++
 .../DirectionalShadingForm.designer.cs        | 225 +++++++++++++++
 .../Interface/DirectionalShadingForm.resx     | 120 ++++++++
 .../Interface/MenusForm.Designer.cs           | 102 ++++---
 .../BuilderEffects/Interface/MenusForm.cs     |  20 +-
 .../Properties/Resources.Designer.cs          |  11 +-
 .../BuilderEffects/Properties/Resources.resx  |  11 +-
 .../BuilderEffects/Resources/Actions.cfg      |  11 +
 .../BuilderEffects/Resources/FlatShading.png  | Bin 0 -> 1036 bytes
 Source/Plugins/NodesViewer/NodesViewerMode.cs |   2 +-
 18 files changed, 926 insertions(+), 99 deletions(-)
 create mode 100644 Source/Plugins/BuilderEffects/Interface/DirectionalShadingForm.cs
 create mode 100644 Source/Plugins/BuilderEffects/Interface/DirectionalShadingForm.designer.cs
 create mode 100644 Source/Plugins/BuilderEffects/Interface/DirectionalShadingForm.resx
 create mode 100644 Source/Plugins/BuilderEffects/Resources/FlatShading.png

diff --git a/Build/Compilers/ZDoom/zspecial.acs b/Build/Compilers/ZDoom/zspecial.acs
index fb36f84fc..7df803014 100644
--- a/Build/Compilers/ZDoom/zspecial.acs
+++ b/Build/Compilers/ZDoom/zspecial.acs
@@ -327,7 +327,7 @@ special
 	-58:GetUserCVarString(2),
 	-59:SetUserCVarString(3),
 	-60:LineAttack(4,9),
-	-61:PlaySound(2,6),
+	-61:PlaySound(2,7),
 	-62:StopSound(1,2),
 	-63:strcmp(2,3),
 	-64:stricmp(2,3),
@@ -402,6 +402,10 @@ special
 	-202:SetActorFlag(3),
 	-203:SetTranslation(2),
 	
+	// Eternity's
+	-300:GetLineX(3),
+	-301:GetLineY(3),
+	
 	// ZDaemon's
 	-19620:GetTeamScore(1),
 	-19621:SetTeamScore(2),
diff --git a/Build/Scripting/ZDoom_ACS.cfg b/Build/Scripting/ZDoom_ACS.cfg
index 4a54267ee..18ccc4481 100644
--- a/Build/Scripting/ZDoom_ACS.cfg
+++ b/Build/Scripting/ZDoom_ACS.cfg
@@ -212,6 +212,8 @@ keywords
 	GetLineRowOffset = "int GetLineRowOffset()";
 	GetLineUDMFFixed = "fixed GetLineUDMFFixed(int lineid, str key)";
 	GetLineUDMFInt = "int GetLineUDMFInt(int lineid, str key)";
+	GetLineX = "int GetLineX(int line_id, fixed line_ratio, fixed perpendicular_distance)";
+	GetLineY = "int GetLineY(int line_id, fixed line_ratio, fixed perpendicular_distance)";
 	GetMaxInventory = "int GetMaxInventory(int tid, str inventory)";
 	GetPlayerInfo = "int GetPlayerInfo(int playernumber, int playerinfo)";
 	GetPlayerInput = "int GetPlayerInput(int player, int input)";
@@ -295,7 +297,7 @@ keywords
 	PlayerInGame = "bool PlayerInGame(int playernumber)\nReturns true if the player [0..7] is in the game";
 	PlayerIsBot = "bool PlayerIsBot(int playernumber)\nReturns TRUE if the player [0..7] is a bot and FALSE if not";
 	PlayerNumber = "int PlayerNumber()\nReturns the player number for the player who activated the script, starting at 0.\nFor scripts that were not activated by a player, PlayerNumber will return -1.";
-	PlaySound = "void PlaySound(int tid, str sound[, int channel = CHAN_BODY[, fixed volume = 1.0[, bool looping = false[, fixed attenuation = ATTN_NORM]]]])";
+	PlaySound = "void PlaySound(int tid, str sound[, int channel = CHAN_BODY[, fixed volume = 1.0[, bool looping = false[, fixed attenuation = ATTN_NORM[, bool local]]]]])";
 	Polyobj_DoorSlide = "Polyobj_DoorSlide(po, speed, angle, dist, delay)";
 	Polyobj_DoorSwing = "Polyobj_DoorSwing(po, speed, angle, delay)";
 	Polyobj_Move = "Polyobj_Move(po, speed, angle, dist)";
diff --git a/Source/Core/Controls/ColorFieldsControl.cs b/Source/Core/Controls/ColorFieldsControl.cs
index dc0d524eb..370c1d2ab 100644
--- a/Source/Core/Controls/ColorFieldsControl.cs
+++ b/Source/Core/Controls/ColorFieldsControl.cs
@@ -33,6 +33,16 @@ namespace CodeImp.DoomBuilder.Controls
 		public int DefaultValue { get { return defaultvalue; } set { defaultvalue = value; } }
 		public string Label { get { return cpColor.Label; } set { cpColor.Label = value; } }
 		public string Field { get { return field; } set { field = value; } }
+		public PixelColor Color
+		{
+			get { return cpColor.Color; }
+			set
+			{
+				blockevents = true;
+				tbColor.Text = String.Format("{0:X6}", value.ToInt() & 0x00FFFFFF);
+				blockevents = false;
+			}
+		}
 
 		#endregion
 
diff --git a/Source/Core/Rendering/PixelColor.cs b/Source/Core/Rendering/PixelColor.cs
index 7dfccd79e..4c2a7d0a4 100644
--- a/Source/Core/Rendering/PixelColor.cs
+++ b/Source/Core/Rendering/PixelColor.cs
@@ -33,6 +33,9 @@ namespace CodeImp.DoomBuilder.Rendering
 		//mxd. Some color constants, full alpha
 		public const int INT_BLACK = -16777216;
 		public const int INT_WHITE = -1;
+
+		//mxd. Some color constants, no alpha
+		public const int INT_WHITE_NO_ALPHA = 16777215;
 		
 		#endregion
 
diff --git a/Source/Core/Windows/SectorEditFormUDMF.cs b/Source/Core/Windows/SectorEditFormUDMF.cs
index 29b0725bc..f7961e450 100644
--- a/Source/Core/Windows/SectorEditFormUDMF.cs
+++ b/Source/Core/Windows/SectorEditFormUDMF.cs
@@ -7,6 +7,7 @@ using System.Windows.Forms;
 using CodeImp.DoomBuilder.Controls;
 using CodeImp.DoomBuilder.Geometry;
 using CodeImp.DoomBuilder.Map;
+using CodeImp.DoomBuilder.Rendering;
 using CodeImp.DoomBuilder.Types;
 
 #endregion
@@ -113,7 +114,7 @@ namespace CodeImp.DoomBuilder.Windows
 				CeilTexture = s.CeilTexture;
 
 				//UDMF stuff
-				LightColor = UniFields.GetInteger(s.Fields, "lightcolor", 16777215);
+				LightColor = UniFields.GetInteger(s.Fields, "lightcolor", PixelColor.INT_WHITE_NO_ALPHA);
 				FadeColor = UniFields.GetInteger(s.Fields, "fadecolor", 0);
 
 				//UDMF Ceiling
diff --git a/Source/Plugins/BuilderEffects/BuilderEffects.csproj b/Source/Plugins/BuilderEffects/BuilderEffects.csproj
index b85696f57..e07034ca3 100644
--- a/Source/Plugins/BuilderEffects/BuilderEffects.csproj
+++ b/Source/Plugins/BuilderEffects/BuilderEffects.csproj
@@ -99,6 +99,12 @@
     <Compile Include="Interface\ObjImportSettingsForm.Designer.cs">
       <DependentUpon>ObjImportSettingsForm.cs</DependentUpon>
     </Compile>
+    <Compile Include="Interface\DirectionalShadingForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Interface\DirectionalShadingForm.designer.cs">
+      <DependentUpon>DirectionalShadingForm.cs</DependentUpon>
+    </Compile>
     <Compile Include="Modes\ImportObjAsTerrainMode.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\Resources.Designer.cs">
@@ -128,6 +134,9 @@
     <EmbeddedResource Include="Interface\ObjImportSettingsForm.resx">
       <DependentUpon>ObjImportSettingsForm.cs</DependentUpon>
     </EmbeddedResource>
+    <EmbeddedResource Include="Interface\DirectionalShadingForm.resx">
+      <DependentUpon>DirectionalShadingForm.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="Properties\Resources.resx">
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
@@ -153,6 +162,9 @@
   <ItemGroup>
     <None Include="Resources\Folder.png" />
   </ItemGroup>
+  <ItemGroup>
+    <None Include="Resources\FlatShading.png" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
diff --git a/Source/Plugins/BuilderEffects/BuilderPlug.cs b/Source/Plugins/BuilderEffects/BuilderPlug.cs
index 762d0672b..3bb6e3fe3 100644
--- a/Source/Plugins/BuilderEffects/BuilderPlug.cs
+++ b/Source/Plugins/BuilderEffects/BuilderPlug.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using System.Drawing;
 using System.Windows.Forms;
 using CodeImp.DoomBuilder.Actions;
+using CodeImp.DoomBuilder.Map;
 using CodeImp.DoomBuilder.Plugins;
 using CodeImp.DoomBuilder.VisualModes;
 using CodeImp.DoomBuilder.Windows;
@@ -14,40 +15,33 @@ namespace CodeImp.DoomBuilder.BuilderEffects
 {
 	public class BuilderPlug : Plug
 	{
+		#region ================== Variables
+
 		// Static instance
 		private static BuilderPlug me;
 
 		// Main objects
-		private MenusForm menusForm;
-		private Form form;
+		private MenusForm menusform;
+
+		#endregion
 
-		private Point formLocation; //used to keep form's location constant
+		#region ================== Properties
 
 		public override string Name { get { return "Builder Effects"; } }
 		public static BuilderPlug Me { get { return me; } }
 
-		// When plugin is initialized
-		public override void OnInitialize() 
-		{
-			// Setup
-			base.OnInitialize();
-			me = this;
-
-			// Load menus form
-			menusForm = new MenusForm();
+		#endregion
 
-			General.Actions.BindMethods(this);
-		}
+		#region ================== Disposer
 
-		// Disposer
-		public override void Dispose() 
+		public override void Dispose()
 		{
 			// Not already disposed?
-			if(!IsDisposed) 
+			if(!IsDisposed)
 			{
-				menusForm.Unregister();
-				menusForm.Dispose();
-				menusForm = null;
+				menusform.Unregister();
+				menusform.Dispose();
+				menusform = null;
 
 				// Done
 				me = null;
@@ -55,38 +49,59 @@ namespace CodeImp.DoomBuilder.BuilderEffects
 			}
 		}
 
+		#endregion
+
+		#region ================== Events
+
+		// When plugin is initialized
+		public override void OnInitialize() 
+		{
+			// Setup
+			base.OnInitialize();
+			me = this;
+
+			// Load menus form
+			menusform = new MenusForm();
+
+			General.Actions.BindMethods(this);
+		}
+
 		public override void OnMapNewEnd() 
 		{
 			base.OnMapNewEnd();
-			menusForm.Register();
+			menusform.Register();
 		}
 
 		public override void OnMapOpenEnd() 
 		{
 			base.OnMapOpenEnd();
-			menusForm.Register();
+			menusform.Register();
 		}
 
 		public override void OnMapCloseEnd() 
 		{
 			base.OnMapCloseEnd();
-			menusForm.Unregister();
+			menusform.Unregister();
 		}
 
 		public override void OnReloadResources() 
 		{
 			base.OnReloadResources();
-			menusForm.Register();
+			menusform.Register();
 		}
 
-		//actions
+		#endregion
+
+		#region ================== Actions
+
 		[BeginAction("applyjitter")]
 		private void ApplyJitterTransform() 
 		{
 			if(General.Editing.Mode == null) return;
 			string currentModeName = General.Editing.Mode.GetType().Name;
+			DelayedForm form = null;
 
-			//display one of colorPickers or tell the user why we can't do that
+			// Display one of colorPickers or tell the user why we can't do that
 			if(currentModeName == "ThingsMode") 
 			{
 				if(General.Map.Map.SelectedThingsCount == 0) 
@@ -125,10 +140,10 @@ namespace CodeImp.DoomBuilder.BuilderEffects
 			} 
 			else if(currentModeName == "BaseVisualMode") 
 			{
-				//no visual things selected in visual mode?
+				// No visual things selected in visual mode?
 				if(((VisualMode)General.Editing.Mode).GetSelectedVisualThings(true).Count == 0) 
 				{
-					//check selected geometry
+					// Check selected geometry
 					List<VisualGeometry> list = ((VisualMode)General.Editing.Mode).GetSelectedSurfaces();
 					if(list.Count > 0) 
 					{
@@ -155,31 +170,152 @@ namespace CodeImp.DoomBuilder.BuilderEffects
 					form = new JitterThingsForm(currentModeName);
 				}
 			} 
-			else //wrong mode
+			else // Wrong mode
 			{ 
 				General.Interface.DisplayStatus(StatusType.Warning, "Switch to Sectors, Things, Vertices, Linedefs or Visual mode first!");
 				return;
 			}
 
-			//position and show form
-			if(formLocation.X == 0 && formLocation.Y == 0)
+			form.ShowDialog(General.Interface);
+		}
+
+		[BeginAction("applydirectionalshading")]
+		private void ApplyDirectionalShading()
+		{
+			// Boilerplate
+			if(General.Editing.Mode == null) return;
+			if(!General.Map.UDMF)
 			{
-				Size displaySize = General.Interface.Display.Size;
-				Point displayLocation = General.Interface.Display.LocationAbs;
-				formLocation = new Point(displayLocation.X + displaySize.Width - form.Width - 16, displayLocation.Y + 16);
+				General.Interface.DisplayStatus(StatusType.Warning, "This action is available only in UDMF map format!");
+				return;
 			}
 
-			form.Location = formLocation;
-			form.FormClosed += form_FormClosed;
+			DirectionalShadingForm form;
+			string currentmodename = General.Editing.Mode.GetType().Name;
+
+			// Create the form or tell the user why we can't do that
+			if(currentmodename == "SectorsMode")
+			{
+				if(General.Map.Map.SelectedSectorsCount == 0)
+				{
+					General.Interface.DisplayStatus(StatusType.Warning, "Select some sectors first!");
+					return;
+				}
+
+				// Collect sectors
+				ICollection<Sector> sectors = General.Map.Map.GetSelectedSectors(true);
+
+				// Collect sidedefs
+				HashSet<Sidedef> sides = new HashSet<Sidedef>();
+				foreach(Sector s in sectors)
+				{
+					foreach(Sidedef sd in s.Sidedefs)
+					{
+						sides.Add(sd);
+						if(sd.Other != null) sides.Add(sd.Other);
+					}
+				}
+
+				// Create the form
+				form = new DirectionalShadingForm(sectors, sides, null);
+			}
+			else if(currentmodename == "LinedefsMode")
+			{
+				if(General.Map.Map.SelectedLinedefsCount == 0)
+				{
+					General.Interface.DisplayStatus(StatusType.Warning, "Select some linedefs first!");
+					return;
+				}
+
+				// Collect linedefs
+				ICollection<Linedef> linedefs = General.Map.Map.GetSelectedLinedefs(true);
+
+				// Collect sectors
+				ICollection<Sector> sectors = General.Map.Map.GetSectorsFromLinedefs(linedefs);
+
+				// Collect sidedefs from linedefs
+				HashSet<Sidedef> sides = new HashSet<Sidedef>();
+				foreach(Linedef l in linedefs)
+				{
+					if(l.Front != null) sides.Add(l.Front);
+					if(l.Back != null) sides.Add(l.Back);
+				}
+
+				// Collect sidedefs from sectors
+				foreach(Sector s in sectors)
+				{
+					foreach(Sidedef sd in s.Sidedefs)
+					{
+						sides.Add(sd);
+						if(sd.Other != null) sides.Add(sd.Other);
+					}
+				}
+
+				// Create the form
+				form = new DirectionalShadingForm(sectors, sides, null);
+			}
+			else if(currentmodename == "BaseVisualMode")
+			{
+				// Check selected geometry
+				VisualMode mode = (VisualMode)General.Editing.Mode;
+				List<VisualGeometry> list = mode.GetSelectedSurfaces();
+				HashSet<VisualSector> selectedgeo = new HashSet<VisualSector>();
+				List<Sector> sectors = new List<Sector>();
+				HashSet<Sidedef> sides = new HashSet<Sidedef>();
+				
+				// Collect sectors and sides
+				if(list.Count > 0)
+				{
+					foreach(VisualGeometry vg in list)
+					{
+						switch(vg.GeometryType)
+						{
+							case VisualGeometryType.FLOOR:
+								selectedgeo.Add(vg.Sector);
+								sectors.Add(vg.Sector.Sector);
+								break;
+
+							case VisualGeometryType.WALL_UPPER:
+							case VisualGeometryType.WALL_MIDDLE:
+							case VisualGeometryType.WALL_LOWER:
+								sides.Add(vg.Sidedef);
+								selectedgeo.Add(mode.GetVisualSector(vg.Sidedef.Sector));
+								break;
+						}
+					}
+				}
+
+				// Add sides from selected sectors
+				foreach(Sector s in sectors)
+				{
+					foreach(Sidedef sd in s.Sidedefs)
+					{
+						sides.Add(sd);
+						if(sd.Other != null) sides.Add(sd.Other);
+					}
+				}
+
+				// Create the form?
+				if(sectors.Count > 0 || sides.Count > 0)
+				{
+					form = new DirectionalShadingForm(sectors, sides, selectedgeo);
+				}
+				else
+				{
+					General.Interface.DisplayStatus(StatusType.Warning, "Select some floor or wall surfaces first!");
+					return;
+				}
+			}
+			else // Wrong mode
+			{
+				General.Interface.DisplayStatus(StatusType.Warning, "Switch to Sectors, Linedefs or Visual mode first!");
+				return;
+			}
+
+			// Show the form
 			form.ShowDialog(General.Interface);
 		}
 
-//events
-		private void form_FormClosed(object sender, FormClosedEventArgs e) 
-		{
-			formLocation = form.Location;
-			form.Dispose();
-			form = null;
-		}
+		#endregion
 	}
 }
diff --git a/Source/Plugins/BuilderEffects/Controls/IntControl.cs b/Source/Plugins/BuilderEffects/Controls/IntControl.cs
index 4882765fa..215ec9aa4 100644
--- a/Source/Plugins/BuilderEffects/Controls/IntControl.cs
+++ b/Source/Plugins/BuilderEffects/Controls/IntControl.cs
@@ -23,6 +23,7 @@ namespace CodeImp.DoomBuilder.BuilderEffects
 				blockEvents = true;
 				previousValue = General.Clamp(value, (int)numericUpDown1.Minimum, (int)numericUpDown1.Maximum);
 				numericUpDown1.Value = previousValue;
+				trackBar1.Value = previousValue;
 				valueChanged = false;
 				blockEvents = false;
 			}
diff --git a/Source/Plugins/BuilderEffects/Interface/DirectionalShadingForm.cs b/Source/Plugins/BuilderEffects/Interface/DirectionalShadingForm.cs
new file mode 100644
index 000000000..b71683b0e
--- /dev/null
+++ b/Source/Plugins/BuilderEffects/Interface/DirectionalShadingForm.cs
@@ -0,0 +1,262 @@
+#region ================== Namespaces
+
+using System;
+using System.Collections.Generic;
+using System.Windows.Forms;
+using CodeImp.DoomBuilder.Geometry;
+using CodeImp.DoomBuilder.Map;
+using CodeImp.DoomBuilder.Rendering;
+using CodeImp.DoomBuilder.VisualModes;
+using CodeImp.DoomBuilder.Windows;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderEffects
+{
+	public partial class DirectionalShadingForm : DelayedForm
+	{
+		#region ================== Variables
+
+		private Dictionary<Sector, Vector3D> sectors; // <sector, floor normal>
+		private Dictionary<Sidedef, Vector3D> sides; // <side, side normal>
+		private Dictionary<Sector, int> sectorbrightness;
+		private Dictionary<Sector, int> sectorcolors;
+		private Dictionary<Sidedef, int> sidebrightness;
+		private IEnumerable<VisualSector> visualsectors;
+		private Vector3D sunvector;
+		private bool blockupdate;
+
+		#endregion
+
+		#region ================== Constructor
+
+		private DirectionalShadingForm() { }
+		public DirectionalShadingForm(IEnumerable<Sector> selectedsectors, IEnumerable<Sidedef> selectedsides, IEnumerable<VisualSector> selectedvisualsectors)
+		{
+			InitializeComponent();
+
+			// Store sectors, collect floor normals
+			visualsectors = selectedvisualsectors;
+			sectors = new Dictionary<Sector, Vector3D>();
+			sectorbrightness = new Dictionary<Sector, int>();
+			sectorcolors = new Dictionary<Sector, int>();
+			foreach(Sector s in selectedsectors)
+			{
+				sectors.Add(s, Sector.GetFloorPlane(s).Normal);
+				sectorcolors[s] = UniFields.GetInteger(s.Fields, "lightcolor", PixelColor.INT_WHITE_NO_ALPHA);
+
+				// Store initial brightness
+				if(s.IsFlagSet("lightfloorabsolute"))
+					sectorbrightness[s] = UniFields.GetInteger(s.Fields, "lightfloor");
+				else
+					sectorbrightness[s] = s.Brightness;
+			}
+
+			// Store sidedefs, collect side normals
+			sides = new Dictionary<Sidedef, Vector3D>();
+			sidebrightness = new Dictionary<Sidedef, int>();
+			foreach(Sidedef sd in selectedsides)
+			{
+				Vector3D normal = Vector3D.FromAngleXY(sd.Line.Angle + Angle2D.PIHALF);
+				if(sd == sd.Line.Front) normal *= -1;
+				sides.Add(sd, normal);
+
+				// Store initial brightness
+				if(sd.IsFlagSet("lightabsolute"))
+					sidebrightness[sd] = UniFields.GetInteger(sd.Fields, "light");
+				else
+					sidebrightness[sd] = sd.Sector.Brightness;
+			}
+
+			// Create undo
+			string sectorscount = (sectors.Count > 0 ? (sectors.Count > 1 ? " sectors" : " sector") : "");
+			string sidescount = (!string.IsNullOrEmpty(sectorscount) ? " and " : "");
+			sidescount += (sides.Count > 0 ? (sides.Count > 1 ? " sidedefs" : " sidedef") : "");
+			General.Map.UndoRedo.ClearAllRedos();
+			General.Map.UndoRedo.CreateUndo("Apply directional shading to " + sectorscount + sidescount);
+
+			// Prepare your fields!
+			foreach(Sector s in sectors.Keys) s.Fields.BeforeFieldsChange();
+			foreach(Sidedef sd in sides.Keys) sd.Fields.BeforeFieldsChange();
+
+			// Load settings
+			blockupdate = true;
+
+			int angle = General.Settings.ReadPluginSetting("directionalshading.sunangle", 45);
+			sunangle.Angle = angle;
+			sunangletb.Text = angle.ToString();
+			lightamount.Value = General.Settings.ReadPluginSetting("directionalshading.lightamount", 64);
+			lightcolor.Color = PixelColor.FromInt(General.Settings.ReadPluginSetting("directionalshading.lightcolor", 0xFDEBD7));
+			shadeamount.Value = General.Settings.ReadPluginSetting("directionalshading.shadeamount", 16);
+			shadecolor.Color = PixelColor.FromInt(General.Settings.ReadPluginSetting("directionalshading.shadecolor", 0xABC8EB));
+
+			blockupdate = false;
+
+			OnSunAngleChanged();
+		}
+
+		#endregion
+
+		#region ================== Methods
+
+		private void UpdateShading()
+		{
+			// Update sector shading
+			foreach(KeyValuePair<Sector, Vector3D> group in sectors)
+			{
+				// Calculate light amount
+				float anglediff = Vector3D.DotProduct(group.Value, sunvector);
+				int targetlight;
+				PixelColor targetcolor;
+
+				// Calculate light and light color when surface normal is rotated towards the sun vector
+				if(anglediff >= 0.5f)
+				{
+					float lightmul = (anglediff - 0.5f) * 2.0f;
+					targetlight = (int)Math.Round(lightamount.Value * lightmul);
+					targetcolor = InterpolationTools.InterpolateColor(shadecolor.Color, lightcolor.Color, anglediff);
+				}
+				// Otherwise calculate shade and shade color
+				else
+				{
+					float lightmul = (0.5f - anglediff) * -2.0f;
+					targetlight = (int)Math.Round(shadeamount.Value * lightmul);
+					targetcolor = InterpolationTools.InterpolateColor(shadecolor.Color, lightcolor.Color, anglediff);
+				}
+
+				// Apply settings
+				if(group.Key.IsFlagSet("lightfloorabsolute"))
+					UniFields.SetInteger(group.Key.Fields, "lightfloor", General.Clamp(targetlight + sectorbrightness[group.Key], 0, 255), 0);
+				else
+					UniFields.SetInteger(group.Key.Fields, "lightfloor", General.Clamp(targetlight, -255, 255), 0);
+
+				// Apply sector color
+				int c = (targetcolor.ToInt() & 0x00FFFFFF);
+
+				// Restore initial color?
+				if(c == PixelColor.INT_WHITE_NO_ALPHA) c = sectorcolors[group.Key];
+
+				// Apply color
+				UniFields.SetInteger(group.Key.Fields, "lightcolor", c, PixelColor.INT_WHITE_NO_ALPHA);
+
+				// Mark for update
+				group.Key.UpdateNeeded = true;
+			}
+
+			// Update sidedef shading
+			foreach(KeyValuePair<Sidedef, Vector3D> group in sides)
+			{
+				// Calculate light amount
+				float anglediff = Vector3D.DotProduct(group.Value, sunvector);
+				int targetlight;
+
+				// Calculate light and light color when surface normal is rotated towards the sun vector
+				if(anglediff >= 0.5f)
+				{
+					float lightmul = (anglediff - 0.5f) * 2.0f;
+					targetlight = (int)Math.Round(lightamount.Value * lightmul);
+				}
+				// Otherwise calculate shade and shade color
+				else
+				{
+					float lightmul = (0.5f - anglediff) * -2.0f;
+					targetlight = (int)Math.Round(shadeamount.Value * lightmul);
+				}
+
+				// Apply settings
+				if(group.Key.IsFlagSet("lightabsolute"))
+					UniFields.SetInteger(group.Key.Fields, "light", General.Clamp(targetlight + sidebrightness[group.Key], 0, 255), 0);
+				else
+					UniFields.SetInteger(group.Key.Fields, "light", General.Clamp(targetlight, -255, 255), 0);
+
+				// Mark for update
+				group.Key.Sector.UpdateNeeded = true;
+			}
+
+			// Update map
+			General.Map.Map.Update();
+			General.Map.IsChanged = true;
+
+			// Update view
+			if(visualsectors != null)
+			{
+				foreach(var vs in visualsectors) vs.UpdateSectorData();
+			}
+			else if(sectors.Count > 0)
+			{
+				General.Interface.RedrawDisplay();
+			}
+		}
+
+		private void OnSunAngleChanged()
+		{
+			// TODO: Altitude?
+			sunvector = Vector3D.FromAngleXYZ(Angle2D.DegToRad(sunangle.Angle + 90), Angle2D.DegToRad(45));
+			UpdateShading();
+		}
+
+		#endregion
+
+		#region ================== Events
+
+		private void sunangle_AngleChanged(object sender, EventArgs e)
+		{
+			if(blockupdate) return;
+
+			blockupdate = true;
+			sunangletb.Text = sunangle.Angle.ToString();
+			blockupdate = false;
+
+			OnSunAngleChanged();
+		}
+
+		private void sunangletb_WhenTextChanged(object sender, EventArgs e)
+		{
+			if(blockupdate) return;
+
+			blockupdate = true;
+			sunangle.Angle = sunangletb.GetResult(sunangle.Angle);
+			blockupdate = false;
+
+			OnSunAngleChanged();
+		}
+
+		private void OnShadingChanged(object sender, EventArgs e)
+		{
+			if(!blockupdate) UpdateShading();
+		}
+
+		private void apply_Click(object sender, EventArgs e)
+		{
+			this.DialogResult = DialogResult.OK;
+			Close();
+		}
+
+		private void cancel_Click(object sender, EventArgs e)
+		{
+			this.DialogResult = DialogResult.Cancel;
+			Close();
+		}
+
+		private void DirectionalShadingForm_FormClosing(object sender, FormClosingEventArgs e)
+		{
+			if(this.DialogResult == DialogResult.OK)
+			{
+				// Save settings
+				General.Settings.WritePluginSetting("directionalshading.sunangle", sunangle.Angle);
+				General.Settings.WritePluginSetting("directionalshading.lightamount", lightamount.Value);
+				General.Settings.WritePluginSetting("directionalshading.lightcolor", lightcolor.Color.ToInt());
+				General.Settings.WritePluginSetting("directionalshading.shadeamount", shadeamount.Value);
+				General.Settings.WritePluginSetting("directionalshading.shadecolor", shadecolor.Color.ToInt());
+			}
+			else
+			{
+				// Undo changes
+				General.Map.UndoRedo.WithdrawUndo(); 
+			}
+		}
+
+		#endregion
+
+	}
+}
diff --git a/Source/Plugins/BuilderEffects/Interface/DirectionalShadingForm.designer.cs b/Source/Plugins/BuilderEffects/Interface/DirectionalShadingForm.designer.cs
new file mode 100644
index 000000000..43aa26ba1
--- /dev/null
+++ b/Source/Plugins/BuilderEffects/Interface/DirectionalShadingForm.designer.cs
@@ -0,0 +1,225 @@
+namespace CodeImp.DoomBuilder.BuilderEffects
+{
+	partial class DirectionalShadingForm
+	{
+		/// <summary>
+		/// Required designer variable.
+		/// </summary>
+		private System.ComponentModel.IContainer components = null;
+
+		/// <summary>
+		/// Clean up any resources being used.
+		/// </summary>
+		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+		protected override void Dispose(bool disposing)
+		{
+			if(disposing && (components != null))
+			{
+				components.Dispose();
+			}
+			base.Dispose(disposing);
+		}
+
+		#region Windows Form Designer generated code
+
+		/// <summary>
+		/// Required method for Designer support - do not modify
+		/// the contents of this method with the code editor.
+		/// </summary>
+		private void InitializeComponent()
+		{
+			this.groupBox1 = new System.Windows.Forms.GroupBox();
+			this.sunangletb = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.sunangle = new CodeImp.DoomBuilder.Controls.AngleControlEx();
+			this.groupBox2 = new System.Windows.Forms.GroupBox();
+			this.lightamount = new IntControl();
+			this.lightcolor = new CodeImp.DoomBuilder.Controls.ColorFieldsControl();
+			this.cancel = new System.Windows.Forms.Button();
+			this.apply = new System.Windows.Forms.Button();
+			this.groupBox3 = new System.Windows.Forms.GroupBox();
+			this.shadeamount = new IntControl();
+			this.shadecolor = new CodeImp.DoomBuilder.Controls.ColorFieldsControl();
+			this.groupBox1.SuspendLayout();
+			this.groupBox2.SuspendLayout();
+			this.groupBox3.SuspendLayout();
+			this.SuspendLayout();
+			// 
+			// groupBox1
+			// 
+			this.groupBox1.Controls.Add(this.sunangletb);
+			this.groupBox1.Controls.Add(this.sunangle);
+			this.groupBox1.Location = new System.Drawing.Point(12, 12);
+			this.groupBox1.Name = "groupBox1";
+			this.groupBox1.Size = new System.Drawing.Size(91, 154);
+			this.groupBox1.TabIndex = 0;
+			this.groupBox1.TabStop = false;
+			this.groupBox1.Text = " Sun angle ";
+			// 
+			// sunangletb
+			// 
+			this.sunangletb.AllowDecimal = false;
+			this.sunangletb.AllowExpressions = true;
+			this.sunangletb.AllowNegative = true;
+			this.sunangletb.AllowRelative = true;
+			this.sunangletb.ButtonStep = 5;
+			this.sunangletb.ButtonStepBig = 15F;
+			this.sunangletb.ButtonStepFloat = 1F;
+			this.sunangletb.ButtonStepSmall = 1F;
+			this.sunangletb.ButtonStepsUseModifierKeys = true;
+			this.sunangletb.ButtonStepsWrapAround = false;
+			this.sunangletb.Location = new System.Drawing.Point(6, 100);
+			this.sunangletb.Name = "sunangletb";
+			this.sunangletb.Size = new System.Drawing.Size(76, 24);
+			this.sunangletb.StepValues = null;
+			this.sunangletb.TabIndex = 22;
+			this.sunangletb.WhenTextChanged += new System.EventHandler(this.sunangletb_WhenTextChanged);
+			// 
+			// sunangle
+			// 
+			this.sunangle.Angle = 0;
+			this.sunangle.AngleOffset = 0;
+			this.sunangle.DoomAngleClamping = false;
+			this.sunangle.Location = new System.Drawing.Point(6, 18);
+			this.sunangle.Name = "sunangle";
+			this.sunangle.Size = new System.Drawing.Size(76, 76);
+			this.sunangle.TabIndex = 21;
+			this.sunangle.AngleChanged += new System.EventHandler(this.sunangle_AngleChanged);
+			// 
+			// groupBox2
+			// 
+			this.groupBox2.Controls.Add(this.lightamount);
+			this.groupBox2.Controls.Add(this.lightcolor);
+			this.groupBox2.Location = new System.Drawing.Point(109, 12);
+			this.groupBox2.Name = "groupBox2";
+			this.groupBox2.Size = new System.Drawing.Size(293, 74);
+			this.groupBox2.TabIndex = 1;
+			this.groupBox2.TabStop = false;
+			this.groupBox2.Text = " Light";
+			// 
+			// lightamount
+			// 
+			this.lightamount.AllowNegative = false;
+			this.lightamount.ExtendedLimits = false;
+			this.lightamount.Label = "Amount:";
+			this.lightamount.Location = new System.Drawing.Point(6, 15);
+			this.lightamount.Maximum = 255;
+			this.lightamount.Minimum = 0;
+			this.lightamount.Name = "lightamount";
+			this.lightamount.Size = new System.Drawing.Size(281, 24);
+			this.lightamount.TabIndex = 28;
+			this.lightamount.Value = 0;
+			this.lightamount.OnValueChanged += new System.EventHandler(this.OnShadingChanged);
+			this.lightamount.OnValueChanging += new System.EventHandler(this.OnShadingChanged);
+			// 
+			// lightcolor
+			// 
+			this.lightcolor.DefaultValue = 16777215;
+			this.lightcolor.Field = "";
+			this.lightcolor.Label = "Color:";
+			this.lightcolor.Location = new System.Drawing.Point(33, 41);
+			this.lightcolor.Name = "lightcolor";
+			this.lightcolor.Size = new System.Drawing.Size(207, 31);
+			this.lightcolor.TabIndex = 27;
+			this.lightcolor.OnValueChanged += new System.EventHandler(this.OnShadingChanged);
+			// 
+			// cancel
+			// 
+			this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+			this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+			this.cancel.Location = new System.Drawing.Point(312, 176);
+			this.cancel.Name = "cancel";
+			this.cancel.Size = new System.Drawing.Size(90, 29);
+			this.cancel.TabIndex = 2;
+			this.cancel.Text = "Cancel";
+			this.cancel.UseVisualStyleBackColor = true;
+			this.cancel.Click += new System.EventHandler(this.cancel_Click);
+			// 
+			// apply
+			// 
+			this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+			this.apply.Location = new System.Drawing.Point(216, 176);
+			this.apply.Name = "apply";
+			this.apply.Size = new System.Drawing.Size(90, 29);
+			this.apply.TabIndex = 3;
+			this.apply.Text = "Apply";
+			this.apply.UseVisualStyleBackColor = true;
+			this.apply.Click += new System.EventHandler(this.apply_Click);
+			// 
+			// groupBox3
+			// 
+			this.groupBox3.Controls.Add(this.shadeamount);
+			this.groupBox3.Controls.Add(this.shadecolor);
+			this.groupBox3.Location = new System.Drawing.Point(109, 92);
+			this.groupBox3.Name = "groupBox3";
+			this.groupBox3.Size = new System.Drawing.Size(293, 74);
+			this.groupBox3.TabIndex = 29;
+			this.groupBox3.TabStop = false;
+			this.groupBox3.Text = " Shade";
+			// 
+			// shadeamount
+			// 
+			this.shadeamount.AllowNegative = false;
+			this.shadeamount.ExtendedLimits = false;
+			this.shadeamount.Label = "Amount:";
+			this.shadeamount.Location = new System.Drawing.Point(6, 15);
+			this.shadeamount.Maximum = 255;
+			this.shadeamount.Minimum = 0;
+			this.shadeamount.Name = "shadeamount";
+			this.shadeamount.Size = new System.Drawing.Size(281, 24);
+			this.shadeamount.TabIndex = 28;
+			this.shadeamount.Value = 0;
+			this.shadeamount.OnValueChanged += new System.EventHandler(this.OnShadingChanged);
+			this.shadeamount.OnValueChanging += new System.EventHandler(this.OnShadingChanged);
+			// 
+			// shadecolor
+			// 
+			this.shadecolor.DefaultValue = 16777215;
+			this.shadecolor.Field = "";
+			this.shadecolor.Label = "Color:";
+			this.shadecolor.Location = new System.Drawing.Point(33, 41);
+			this.shadecolor.Name = "shadecolor";
+			this.shadecolor.Size = new System.Drawing.Size(207, 31);
+			this.shadecolor.TabIndex = 27;
+			this.shadecolor.OnValueChanged += new System.EventHandler(this.OnShadingChanged);
+			// 
+			// DirectionalShadingForm
+			// 
+			this.AcceptButton = this.apply;
+			this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
+			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+			this.CancelButton = this.cancel;
+			this.ClientSize = new System.Drawing.Size(414, 211);
+			this.Controls.Add(this.groupBox3);
+			this.Controls.Add(this.apply);
+			this.Controls.Add(this.cancel);
+			this.Controls.Add(this.groupBox2);
+			this.Controls.Add(this.groupBox1);
+			this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+			this.MaximizeBox = false;
+			this.MinimizeBox = false;
+			this.Name = "DirectionalShadingForm";
+			this.ShowInTaskbar = false;
+			this.Text = "Terrain Shading";
+			this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.TerrainShadingForm_FormClosing);
+			this.groupBox1.ResumeLayout(false);
+			this.groupBox2.ResumeLayout(false);
+			this.groupBox3.ResumeLayout(false);
+			this.ResumeLayout(false);
+
+		}
+
+		#endregion
+
+		private System.Windows.Forms.GroupBox groupBox1;
+		private CodeImp.DoomBuilder.Controls.AngleControlEx sunangle;
+		private CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox sunangletb;
+		private System.Windows.Forms.GroupBox groupBox2;
+		private System.Windows.Forms.Button cancel;
+		private System.Windows.Forms.Button apply;
+		private CodeImp.DoomBuilder.Controls.ColorFieldsControl lightcolor;
+		private System.Windows.Forms.GroupBox groupBox3;
+		private CodeImp.DoomBuilder.Controls.ColorFieldsControl shadecolor;
+		private IntControl lightamount;
+		private IntControl shadeamount;
+	}
+}
\ No newline at end of file
diff --git a/Source/Plugins/BuilderEffects/Interface/DirectionalShadingForm.resx b/Source/Plugins/BuilderEffects/Interface/DirectionalShadingForm.resx
new file mode 100644
index 000000000..ff31a6db5
--- /dev/null
+++ b/Source/Plugins/BuilderEffects/Interface/DirectionalShadingForm.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/Source/Plugins/BuilderEffects/Interface/MenusForm.Designer.cs b/Source/Plugins/BuilderEffects/Interface/MenusForm.Designer.cs
index 2087d9eb4..56a336511 100644
--- a/Source/Plugins/BuilderEffects/Interface/MenusForm.Designer.cs
+++ b/Source/Plugins/BuilderEffects/Interface/MenusForm.Designer.cs
@@ -26,12 +26,14 @@
 		/// </summary>
 		private void InitializeComponent() {
 			this.menuStrip = new System.Windows.Forms.MenuStrip();
-			this.importStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+			this.stripimport = new System.Windows.Forms.ToolStripMenuItem();
 			this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem();
-			this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripMenuItem();
-			this.jitterItem = new System.Windows.Forms.ToolStripMenuItem();
+			this.stripmodes = new System.Windows.Forms.ToolStripMenuItem();
+			this.menujitter = new System.Windows.Forms.ToolStripMenuItem();
+			this.menusectorflatshading = new System.Windows.Forms.ToolStripMenuItem();
 			this.toolStrip = new System.Windows.Forms.ToolStrip();
-			this.jitterButton = new System.Windows.Forms.ToolStripButton();
+			this.buttonjitter = new System.Windows.Forms.ToolStripButton();
+			this.buttonsectorflatshading = new System.Windows.Forms.ToolStripButton();
 			this.menuStrip.SuspendLayout();
 			this.toolStrip.SuspendLayout();
 			this.SuspendLayout();
@@ -39,21 +41,21 @@
 			// menuStrip
 			// 
 			this.menuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
-            this.importStripMenuItem,
-            this.toolStripMenuItem3});
+            this.stripimport,
+            this.stripmodes});
 			this.menuStrip.Location = new System.Drawing.Point(0, 0);
 			this.menuStrip.Name = "menuStrip";
 			this.menuStrip.Size = new System.Drawing.Size(452, 24);
 			this.menuStrip.TabIndex = 0;
 			this.menuStrip.Text = "menuStrip1";
 			// 
-			// importStripMenuItem
+			// stripimport
 			// 
-			this.importStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+			this.stripimport.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
             this.toolStripMenuItem1});
-			this.importStripMenuItem.Name = "importStripMenuItem";
-			this.importStripMenuItem.Size = new System.Drawing.Size(55, 20);
-			this.importStripMenuItem.Text = "Import";
+			this.stripimport.Name = "stripimport";
+			this.stripimport.Size = new System.Drawing.Size(55, 20);
+			this.stripimport.Text = "Import";
 			// 
 			// toolStripMenuItem1
 			// 
@@ -64,43 +66,65 @@
 			this.toolStripMenuItem1.Text = "Wavefront .obj as Terrain...";
 			this.toolStripMenuItem1.Click += new System.EventHandler(this.InvokeTaggedAction);
 			// 
-			// toolStripMenuItem3
+			// stripmodes
 			// 
-			this.toolStripMenuItem3.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
-            this.jitterItem});
-			this.toolStripMenuItem3.Name = "toolStripMenuItem3";
-			this.toolStripMenuItem3.Size = new System.Drawing.Size(55, 20);
-			this.toolStripMenuItem3.Text = "Modes";
+			this.stripmodes.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.menujitter,
+            this.menusectorflatshading});
+			this.stripmodes.Name = "stripmodes";
+			this.stripmodes.Size = new System.Drawing.Size(55, 20);
+			this.stripmodes.Text = "Modes";
 			// 
-			// jitterItem
+			// menujitter
 			// 
-			this.jitterItem.Image = global::CodeImp.DoomBuilder.BuilderEffects.Properties.Resources.Jitter;
-			this.jitterItem.Name = "jitterItem";
-			this.jitterItem.Size = new System.Drawing.Size(133, 22);
-			this.jitterItem.Tag = "applyjitter";
-			this.jitterItem.Text = "Randomize";
-			this.jitterItem.Click += new System.EventHandler(this.InvokeTaggedAction);
+			this.menujitter.Image = global::CodeImp.DoomBuilder.BuilderEffects.Properties.Resources.Jitter;
+			this.menujitter.Name = "menujitter";
+			this.menujitter.Size = new System.Drawing.Size(220, 22);
+			this.menujitter.Tag = "applyjitter";
+			this.menujitter.Text = "Randomize...";
+			this.menujitter.Click += new System.EventHandler(this.InvokeTaggedAction);
+			// 
+			// menusectorflatshading
+			// 
+			this.menusectorflatshading.Image = global::CodeImp.DoomBuilder.BuilderEffects.Properties.Resources.FlatShading;
+			this.menusectorflatshading.Name = "menusectorflatshading";
+			this.menusectorflatshading.Size = new System.Drawing.Size(220, 22);
+			this.menusectorflatshading.Tag = "applydirectionalshading";
+			this.menusectorflatshading.Text = "Apply Directional Shading...";
+			this.menusectorflatshading.Click += new System.EventHandler(this.InvokeTaggedAction);
 			// 
 			// toolStrip
 			// 
 			this.toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
-            this.jitterButton});
+            this.buttonjitter,
+            this.buttonsectorflatshading});
 			this.toolStrip.Location = new System.Drawing.Point(0, 24);
 			this.toolStrip.Name = "toolStrip";
 			this.toolStrip.Size = new System.Drawing.Size(452, 25);
 			this.toolStrip.TabIndex = 1;
 			this.toolStrip.Text = "toolStrip1";
 			// 
-			// jitterButton
+			// buttonjitter
+			// 
+			this.buttonjitter.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+			this.buttonjitter.Image = global::CodeImp.DoomBuilder.BuilderEffects.Properties.Resources.Jitter;
+			this.buttonjitter.ImageTransparentColor = System.Drawing.Color.Magenta;
+			this.buttonjitter.Name = "buttonjitter";
+			this.buttonjitter.Size = new System.Drawing.Size(23, 22);
+			this.buttonjitter.Tag = "applyjitter";
+			this.buttonjitter.Text = "Randomize";
+			this.buttonjitter.Click += new System.EventHandler(this.InvokeTaggedAction);
+			// 
+			// buttonsectorflatshading
 			// 
-			this.jitterButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
-			this.jitterButton.Image = global::CodeImp.DoomBuilder.BuilderEffects.Properties.Resources.Jitter;
-			this.jitterButton.ImageTransparentColor = System.Drawing.Color.Magenta;
-			this.jitterButton.Name = "jitterButton";
-			this.jitterButton.Size = new System.Drawing.Size(23, 22);
-			this.jitterButton.Tag = "applyjitter";
-			this.jitterButton.Text = "Randomize";
-			this.jitterButton.Click += new System.EventHandler(this.InvokeTaggedAction);
+			this.buttonsectorflatshading.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+			this.buttonsectorflatshading.Image = global::CodeImp.DoomBuilder.BuilderEffects.Properties.Resources.FlatShading;
+			this.buttonsectorflatshading.ImageTransparentColor = System.Drawing.Color.Magenta;
+			this.buttonsectorflatshading.Name = "buttonsectorflatshading";
+			this.buttonsectorflatshading.Size = new System.Drawing.Size(23, 22);
+			this.buttonsectorflatshading.Tag = "applydirectionalshading";
+			this.buttonsectorflatshading.Text = "Apply Directional Shading";
+			this.buttonsectorflatshading.Click += new System.EventHandler(this.InvokeTaggedAction);
 			// 
 			// MenusForm
 			// 
@@ -124,11 +148,13 @@
 		#endregion
 
 		private System.Windows.Forms.MenuStrip menuStrip;
-		private System.Windows.Forms.ToolStripMenuItem importStripMenuItem;
+		private System.Windows.Forms.ToolStripMenuItem stripimport;
 		private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem1;
 		private System.Windows.Forms.ToolStrip toolStrip;
-		private System.Windows.Forms.ToolStripButton jitterButton;
-		private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem3;
-		private System.Windows.Forms.ToolStripMenuItem jitterItem;
+		private System.Windows.Forms.ToolStripButton buttonjitter;
+		private System.Windows.Forms.ToolStripMenuItem stripmodes;
+		private System.Windows.Forms.ToolStripMenuItem menujitter;
+		private System.Windows.Forms.ToolStripMenuItem menusectorflatshading;
+		private System.Windows.Forms.ToolStripButton buttonsectorflatshading;
 	}
 }
\ No newline at end of file
diff --git a/Source/Plugins/BuilderEffects/Interface/MenusForm.cs b/Source/Plugins/BuilderEffects/Interface/MenusForm.cs
index 7c38fb133..521cc3a8b 100644
--- a/Source/Plugins/BuilderEffects/Interface/MenusForm.cs
+++ b/Source/Plugins/BuilderEffects/Interface/MenusForm.cs
@@ -21,22 +21,26 @@ namespace CodeImp.DoomBuilder.BuilderEffects
 		public void Register() 
 		{
 			// Add the menus to the core
-			General.Interface.AddModesMenu(jitterItem, "002_modify");
-			General.Interface.AddModesButton(jitterButton, "002_modify");
+			General.Interface.AddModesMenu(menujitter, "002_modify");
+			General.Interface.AddModesButton(buttonjitter, "002_modify");
+			General.Interface.AddModesMenu(menusectorflatshading, "002_modify");
+			General.Interface.AddModesButton(buttonsectorflatshading, "002_modify");
 
-			for(int i = 0; i < importStripMenuItem.DropDownItems.Count; i++)
-				General.Interface.AddMenu(importStripMenuItem.DropDownItems[i] as ToolStripMenuItem, MenuSection.FileImport);
+			for(int i = 0; i < stripimport.DropDownItems.Count; i++)
+				General.Interface.AddMenu(stripimport.DropDownItems[i] as ToolStripMenuItem, MenuSection.FileImport);
 		}
 
 		// This unregisters from the core
 		public void Unregister() 
 		{
 			// Remove the menus from the core
-			General.Interface.RemoveMenu(jitterItem);
-			General.Interface.RemoveButton(jitterButton);
+			General.Interface.RemoveMenu(menujitter);
+			General.Interface.RemoveButton(buttonjitter);
+			General.Interface.RemoveMenu(menusectorflatshading);
+			General.Interface.RemoveButton(buttonsectorflatshading);
 
-			for(int i = 0; i < importStripMenuItem.DropDownItems.Count; i++)
-				General.Interface.RemoveMenu(importStripMenuItem.DropDownItems[i] as ToolStripMenuItem);
+			for(int i = 0; i < stripimport.DropDownItems.Count; i++)
+				General.Interface.RemoveMenu(stripimport.DropDownItems[i] as ToolStripMenuItem);
 		}
 	}
 }
diff --git a/Source/Plugins/BuilderEffects/Properties/Resources.Designer.cs b/Source/Plugins/BuilderEffects/Properties/Resources.Designer.cs
index a83b4356a..abda0bd3c 100644
--- a/Source/Plugins/BuilderEffects/Properties/Resources.Designer.cs
+++ b/Source/Plugins/BuilderEffects/Properties/Resources.Designer.cs
@@ -1,7 +1,7 @@
 //------------------------------------------------------------------------------
 // <auto-generated>
 //     This code was generated by a tool.
-//     Runtime Version:2.0.50727.5466
+//     Runtime Version:2.0.50727.5485
 //
 //     Changes to this file may cause incorrect behavior and will be lost if
 //     the code is regenerated.
@@ -38,7 +38,7 @@ namespace CodeImp.DoomBuilder.BuilderEffects.Properties {
         [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
         internal static global::System.Resources.ResourceManager ResourceManager {
             get {
-                if(object.ReferenceEquals(resourceMan, null)) {
+                if (object.ReferenceEquals(resourceMan, null)) {
                     global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CodeImp.DoomBuilder.BuilderEffects.Properties.Resources", typeof(Resources).Assembly);
                     resourceMan = temp;
                 }
@@ -60,6 +60,13 @@ namespace CodeImp.DoomBuilder.BuilderEffects.Properties {
             }
         }
         
+        internal static System.Drawing.Bitmap FlatShading {
+            get {
+                object obj = ResourceManager.GetObject("FlatShading", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
         internal static System.Drawing.Bitmap Folder {
             get {
                 object obj = ResourceManager.GetObject("Folder", resourceCulture);
diff --git a/Source/Plugins/BuilderEffects/Properties/Resources.resx b/Source/Plugins/BuilderEffects/Properties/Resources.resx
index 7d1cece63..81e09218b 100644
--- a/Source/Plugins/BuilderEffects/Properties/Resources.resx
+++ b/Source/Plugins/BuilderEffects/Properties/Resources.resx
@@ -118,16 +118,19 @@
     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+  <data name="Jitter" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Jitter.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
   <data name="Terrain" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Terrain.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
-  <data name="Jitter" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\Jitter.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  <data name="Folder" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Folder.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
   <data name="Update" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Update.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
-  <data name="Folder" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\Folder.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  <data name="FlatShading" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\FlatShading.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
 </root>
\ No newline at end of file
diff --git a/Source/Plugins/BuilderEffects/Resources/Actions.cfg b/Source/Plugins/BuilderEffects/Resources/Actions.cfg
index 2f57391cd..09394f314 100644
--- a/Source/Plugins/BuilderEffects/Resources/Actions.cfg
+++ b/Source/Plugins/BuilderEffects/Resources/Actions.cfg
@@ -15,6 +15,17 @@ applyjitter
 	default = 131146; //Ctrl-J
 }
 
+applydirectionalshading
+{
+	title = "Apply Directional Shading";
+	category = "transform";
+	description = "Allows to apply directional colored flat shading to the selection.";
+	allowkeys = true;
+	allowmouse = false;
+	allowscroll = false;
+	default = 131148; //Ctrl-L
+}
+
 importobjasterrain
 {
 	title = "Import Wavefront .obj as terrain";
diff --git a/Source/Plugins/BuilderEffects/Resources/FlatShading.png b/Source/Plugins/BuilderEffects/Resources/FlatShading.png
new file mode 100644
index 0000000000000000000000000000000000000000..f5a2eaf3dc1fa554c46e5625a021ed292e3baa8b
GIT binary patch
literal 1036
zcmaJ=Pe>F|7@u576NDf-SP+k~gNE~W|Fk=}i956AQX{f%xOA!6nWsDG%$sJO?YI<K
zhe9GyhzKLdTZ#lihsZ-hcJSmW2<uQp(j_`nU=Y!pb$9L1Hq6ZTy~p?aeZTj8-<ug4
zJl)oMq?Mwmw#<N%CH7$OwKS3c>|8NMOgm2J@h}?2CC!CYpMeSxWGrnAW}#+GUD|*>
z6cy?;b9tOs&q_M7SS^TQeaj(eit6e09ZesH7!=@`X(#Egt1oC^8cBL2s`9EM!=gD*
zcHwY&FsGNt^@Kt9o&r6-L<lU1HQ-wlwkP>Xy22|FKiKAIPzk}~NqSdQUL685av_Ma
zF-8}{A~=~~g=j*IN4kN?3o(w5a6*C+!V-BSJg7c2$>tiPQda4&<|3;kUBuXtIIdJG
zv86DJ+%bYpBsgB=M3Es8j5lRt&1Y<{qsE{>Pj^iRo5%(MqgFtZI7t(wyGO8`I<4(h
z%R~ys`I^HCEFT=H7O1NK54Eg%w1=~BPwzj8z1)-oxh(Y1q^pz0jdld797%SehLM{?
zXrfldp(4V_D<TKTa!|D>8wH&srfr~-*HuBOs+6%ktl2uuC`p>|u%>B9F+~v4N?7cZ
z#We9^#LB6(BE%ypxnGFKYFGv7lNPjb4QuRS!ws>)Kv)jRtU%Yi42^ylS)g)o$!x5J
zZ^&208jZCG4Y3>%!v*dAtKGFNGDCq|A6~Mkk3Y1@%)4Z`cXqDbCKsceQTlTJR^iIG
z?Y2<Y^vrc-didqyxz(a6U-%+|pC8r_Y`vj`V@*7>u(rSBK_T*S<$P-G=Kc1~mA8-V
zyZeqa50~aX{d#VPHkO#qd+(nv7_ZL6k3Mcrht~a;{MGL#X5TqKxcT49^M~TrTJFyB
et-0;R!(fdvKX<?0d{$}>?q?=Fs60tsyzvL<_EC5M

literal 0
HcmV?d00001

diff --git a/Source/Plugins/NodesViewer/NodesViewerMode.cs b/Source/Plugins/NodesViewer/NodesViewerMode.cs
index 1275296aa..7e712122e 100644
--- a/Source/Plugins/NodesViewer/NodesViewerMode.cs
+++ b/Source/Plugins/NodesViewer/NodesViewerMode.cs
@@ -15,7 +15,7 @@ using CodeImp.DoomBuilder.Map;
 
 namespace CodeImp.DoomBuilder.Plugins.NodesViewer
 {
-	[EditMode(DisplayName = "Nodes Viewer",
+	[EditMode(DisplayName = "Nodes Viewer Mode",
 			  SwitchAction = "nodesviewermode",
 			  ButtonImage = "NodesView.png",
 			  ButtonOrder = 350,
-- 
GitLab