From ad05a43ade3cca8570ee56d64effaab3240ce7fe Mon Sep 17 00:00:00 2001
From: codeimp <codeimp@e0d998f2-2e9b-42fe-843d-47128df60a06>
Date: Fri, 1 May 2009 20:31:17 +0000
Subject: [PATCH] @ work in progress (do not use this revision for
 building/testing)

---
 MakeRelease.bat                               |   6 +-
 Source/Core/Editing/UndoManager.cs            |  36 +++++
 Source/Core/Editing/UndoSnapshot.cs           |   2 +-
 Source/Core/Rendering/PixelColor.cs           |   9 ++
 Source/Core/Rendering/Renderer3D.cs           |  60 ++++-----
 Source/Core/VisualModes/IVisualPickable.cs    |   1 +
 Source/Core/VisualModes/VisualGeometry.cs     |   8 ++
 Source/Core/VisualModes/VisualThing.cs        |  10 +-
 .../Plugins/BuilderModes/BuilderModes.csproj  |   1 +
 .../ClassicModes/BaseClassicMode.cs           |   2 +-
 .../VisualModes/BaseVisualGeometrySector.cs   |  27 ++--
 .../VisualModes/BaseVisualGeometrySidedef.cs  |  17 +--
 .../VisualModes/BaseVisualMode.cs             | 123 +++++++++++++++++-
 .../VisualModes/BaseVisualSector.cs           |  25 +++-
 .../VisualModes/BaseVisualThing.cs            |  17 ++-
 .../VisualModes/VisualActionResult.cs         |  43 ++++++
 16 files changed, 321 insertions(+), 66 deletions(-)
 create mode 100644 Source/Plugins/BuilderModes/VisualModes/VisualActionResult.cs

diff --git a/MakeRelease.bat b/MakeRelease.bat
index 74440522d..283fe46b9 100644
--- a/MakeRelease.bat
+++ b/MakeRelease.bat
@@ -8,6 +8,8 @@ ECHO.       - Microsoft HTML Help compiler
 ECHO.       - Inno Setup 5
 ECHO.
 ECHO.     You have to commit your work before using this script.
+ECHO.     Results will be in the 'Release' directory. Anything currently in
+ECHO.     the 'Release' directory may be overwritten.
 ECHO.
 ECHO.
 PAUSE
@@ -87,14 +89,14 @@ GOTO LEAVE
 ECHO.
 ECHO.     BUILD FAILED (Tool returned error)
 ECHO.
-PAUSE
+PAUSE > NUL
 GOTO LEAVE
 
 :FILEFAIL
 ECHO.
 ECHO.     BUILD FAILED (Output file was not built)
 ECHO.
-PAUSE
+PAUSE > NUL
 GOTO LEAVE
 
 :LEAVE
diff --git a/Source/Core/Editing/UndoManager.cs b/Source/Core/Editing/UndoManager.cs
index 88e1702f0..2fa6cd740 100644
--- a/Source/Core/Editing/UndoManager.cs
+++ b/Source/Core/Editing/UndoManager.cs
@@ -323,6 +323,42 @@ namespace CodeImp.DoomBuilder.Editing
 			}
 		}
 
+		// This changes the description of previously made undo
+		public void SetUndoDescription(int ticket, string description)
+		{
+			// Anything to undo?
+			if(undos.Count > 0)
+			{
+				// Check if the ticket id matches
+				if(ticket == undos[0].TicketID)
+				{
+					lock(undos)
+					{
+						undos[0].Description = description;
+					}
+					
+					// Update
+					General.MainWindow.UpdateInterface();
+				}
+			}
+		}
+
+		// This changes the grouping settings
+		public void SetUndoGrouping(int ticket, UndoGroup group, int grouptag)
+		{
+			// Anything to undo?
+			if(undos.Count > 0)
+			{
+				// Check if the ticket id matches
+				if(ticket == undos[0].TicketID)
+				{
+					// Keep grouping info
+					lastgroup = group;
+					lastgrouptag = grouptag;
+				}
+			}
+		}
+
 		// This performs an undo
 		[BeginAction("undo")]
 		public void PerformUndo()
diff --git a/Source/Core/Editing/UndoSnapshot.cs b/Source/Core/Editing/UndoSnapshot.cs
index a39c7a3ba..641afe8a1 100644
--- a/Source/Core/Editing/UndoSnapshot.cs
+++ b/Source/Core/Editing/UndoSnapshot.cs
@@ -52,7 +52,7 @@ namespace CodeImp.DoomBuilder.Editing
 
 		#region ================== Properties
 
-		public string Description { get { return description; } }
+		public string Description { get { return description; } set { description = value; } }
 		public int TicketID { get { return ticketid; } }
 		internal bool StoreOnDisk { get { return storeondisk; } set { storeondisk = value; } }
 		public bool IsOnDisk { get { return isondisk; } }
diff --git a/Source/Core/Rendering/PixelColor.cs b/Source/Core/Rendering/PixelColor.cs
index 28e72cc0d..5d94892bb 100644
--- a/Source/Core/Rendering/PixelColor.cs
+++ b/Source/Core/Rendering/PixelColor.cs
@@ -130,6 +130,15 @@ namespace CodeImp.DoomBuilder.Rendering
 							  (float)g * BYTE_TO_FLOAT,
 							  (float)b * BYTE_TO_FLOAT);
 		}
+
+		// To ColorValue
+		public Color4 ToColorValue(float withalpha)
+		{
+			return new Color4(withalpha,
+							  (float)r * BYTE_TO_FLOAT,
+							  (float)g * BYTE_TO_FLOAT,
+							  (float)b * BYTE_TO_FLOAT);
+		}
 		
 		// This returns a new PixelColor with adjusted alpha
 		public PixelColor WithAlpha(byte a)
diff --git a/Source/Core/Rendering/Renderer3D.cs b/Source/Core/Rendering/Renderer3D.cs
index 26a3f068b..f5b9673d8 100644
--- a/Source/Core/Rendering/Renderer3D.cs
+++ b/Source/Core/Rendering/Renderer3D.cs
@@ -514,6 +514,9 @@ namespace CodeImp.DoomBuilder.Rendering
 		// This performs a single render pass
 		private void RenderSinglePass(int pass)
 		{
+			int currentshaderpass = shaderpass;
+			int highshaderpass = shaderpass + 2;
+			
 			// Get geometry for this pass
 			Dictionary<ImageData, BinaryHeap<VisualGeometry>> geopass = geometry[pass];
 
@@ -569,27 +572,26 @@ namespace CodeImp.DoomBuilder.Rendering
 					
 					if(sector != null)
 					{
-						// Highlight this object?
-						if(g == highlighted)
+						// Determine the shader pass we want to use for this object
+						int wantedshaderpass = ((g == highlighted) || g.Selected) ? highshaderpass : shaderpass;
+						
+						// Switch shader pass?
+						if(currentshaderpass != wantedshaderpass)
 						{
-							// Temporarely switch shader and use a highlight color
 							graphics.Shaders.World3D.EndPass();
-							Color4 highlight = General.Colors.Highlight.ToColorValue();
-							highlight.Alpha = highlightglow;
-							graphics.Shaders.World3D.SetHighlightColor(highlight.ToArgb());
-							graphics.Shaders.World3D.BeginPass(shaderpass + 2);
+							graphics.Shaders.World3D.BeginPass(wantedshaderpass);
+							currentshaderpass = wantedshaderpass;
 						}
 						
+						// Set the color to use
+						Color4 highlightcolor = new Color4(0);
+						if(g.Selected) highlightcolor = General.Colors.Selection.ToColorValue(highlightglow);
+						if(g == highlighted) highlightcolor = Color4.Lerp(highlightcolor, General.Colors.Highlight.ToColorValue(), highlightglow);
+						graphics.Shaders.World3D.SetHighlightColor(highlightcolor.ToArgb());
+						graphics.Shaders.World3D.ApplySettings();
+						
 						// Render!
 						graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, g.VertexOffset, g.Triangles);
-						
-						// Reset highlight settings
-						if(g == highlighted)
-						{
-							graphics.Shaders.World3D.EndPass();
-							graphics.Shaders.World3D.SetHighlightColor(0);
-							graphics.Shaders.World3D.BeginPass(shaderpass);
-						}
 					}
 				}
 			}
@@ -634,17 +636,23 @@ namespace CodeImp.DoomBuilder.Rendering
 							// Only do this sector when a vertexbuffer is created
 							if(t.GeometryBuffer != null)
 							{
-								// Highlight this object?
-								if(t == highlighted)
+								// Determine the shader pass we want to use for this object
+								int wantedshaderpass = ((t == highlighted) || t.Selected) ? highshaderpass : shaderpass;
+
+								// Switch shader pass?
+								if(currentshaderpass != wantedshaderpass)
 								{
-									// Temporarely switch shader and use a highlight color
 									graphics.Shaders.World3D.EndPass();
-									Color4 highlight = General.Colors.Highlight.ToColorValue();
-									highlight.Alpha = highlightglow;
-									graphics.Shaders.World3D.SetHighlightColor(highlight.ToArgb());
-									graphics.Shaders.World3D.BeginPass(shaderpass + 2);
+									graphics.Shaders.World3D.BeginPass(wantedshaderpass);
+									currentshaderpass = wantedshaderpass;
 								}
 
+								// Set the color to use
+								Color4 highlightcolor = new Color4(0);
+								if(t.Selected) highlightcolor = General.Colors.Selection.ToColorValue(highlightglow);
+								if(t == highlighted) highlightcolor = Color4.Lerp(highlightcolor, General.Colors.Highlight.ToColorValue(), highlightglow);
+								graphics.Shaders.World3D.SetHighlightColor(highlightcolor.ToArgb());
+
 								// Create the matrix for positioning / rotation
 								world = t.Orientation;
 								if(t.Billboard) world = Matrix.Multiply(world, billboard);
@@ -657,14 +665,6 @@ namespace CodeImp.DoomBuilder.Rendering
 
 								// Render!
 								graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, 0, t.Triangles);
-
-								// Reset highlight settings
-								if(t == highlighted)
-								{
-									graphics.Shaders.World3D.EndPass();
-									graphics.Shaders.World3D.SetHighlightColor(0);
-									graphics.Shaders.World3D.BeginPass(shaderpass);
-								}
 							}
 						}
 					}
diff --git a/Source/Core/VisualModes/IVisualPickable.cs b/Source/Core/VisualModes/IVisualPickable.cs
index 56eeb5628..a374eedf2 100644
--- a/Source/Core/VisualModes/IVisualPickable.cs
+++ b/Source/Core/VisualModes/IVisualPickable.cs
@@ -42,6 +42,7 @@ namespace CodeImp.DoomBuilder.VisualModes
 {
 	public interface IVisualPickable
 	{
+		bool Selected { get; set; }
 		bool PickFastReject(Vector3D from, Vector3D to, Vector3D dir);
 		bool PickAccurate(Vector3D from, Vector3D to, Vector3D dir, ref float u_ray);
 	}
diff --git a/Source/Core/VisualModes/VisualGeometry.cs b/Source/Core/VisualModes/VisualGeometry.cs
index 237bb48d9..7d232a11a 100644
--- a/Source/Core/VisualModes/VisualGeometry.cs
+++ b/Source/Core/VisualModes/VisualGeometry.cs
@@ -55,6 +55,9 @@ namespace CodeImp.DoomBuilder.VisualModes
 		private PixelColor modulatecolor;
 		private Color4 modcolor4;
 		
+		// Selected?
+		protected bool selected;
+		
 		// Elements that this geometry is bound to
 		// Only the sector is required, sidedef is only for walls
 		private VisualSector sector;
@@ -112,6 +115,11 @@ namespace CodeImp.DoomBuilder.VisualModes
 		/// </summary>
 		public Sidedef Sidedef { get { return sidedef; } }
 
+		/// <summary>
+		/// Selected or not? This is only used by the core to determine what color to draw it with.
+		/// </summary>
+		public bool Selected { get { return selected; } set { selected = value; } }
+
 		#endregion
 
 		#region ================== Constructor / Destructor
diff --git a/Source/Core/VisualModes/VisualThing.cs b/Source/Core/VisualModes/VisualThing.cs
index a78203b24..8f491e614 100644
--- a/Source/Core/VisualModes/VisualThing.cs
+++ b/Source/Core/VisualModes/VisualThing.cs
@@ -69,7 +69,10 @@ namespace CodeImp.DoomBuilder.VisualModes
 		private Vector2D pos2d;
 		private float cameradistance;
 		private int cagecolor;
-		
+
+		// Selected?
+		protected bool selected;
+
 		// Disposing
 		private bool isdisposed = false;
 		
@@ -111,6 +114,11 @@ namespace CodeImp.DoomBuilder.VisualModes
 		/// Disposed or not?
 		/// </summary>
 		public bool IsDisposed { get { return isdisposed; } }
+
+		/// <summary>
+		/// Selected or not? This is only used by the core to determine what color to draw it with.
+		/// </summary>
+		public bool Selected { get { return selected; } set { selected = value; } }
 		
 		#endregion
 		
diff --git a/Source/Plugins/BuilderModes/BuilderModes.csproj b/Source/Plugins/BuilderModes/BuilderModes.csproj
index 256ee8708..a3cf1ef4b 100644
--- a/Source/Plugins/BuilderModes/BuilderModes.csproj
+++ b/Source/Plugins/BuilderModes/BuilderModes.csproj
@@ -247,6 +247,7 @@
     <Compile Include="FindReplace\FindThingThingRef.cs" />
     <Compile Include="FindReplace\FindThingType.cs" />
     <Compile Include="FindReplace\FindVertexNumber.cs" />
+    <Compile Include="VisualModes\VisualActionResult.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="Resources\ViewSelectionIndex.png" />
diff --git a/Source/Plugins/BuilderModes/ClassicModes/BaseClassicMode.cs b/Source/Plugins/BuilderModes/ClassicModes/BaseClassicMode.cs
index ab53fa98a..2d5a38322 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/BaseClassicMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/BaseClassicMode.cs
@@ -139,7 +139,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// Mouse must be inside window
 			if(!mouseinside) return;
 			
-			General.Interface.DisplayStatus(StatusType.Action, "Places Visual Mode camera start thing.");
+			General.Interface.DisplayStatus(StatusType.Action, "Placed Visual Mode camera start thing.");
 			
 			// Go for all things
 			List<Thing> things = new List<Thing>(General.Map.Map.Things);
diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySector.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySector.cs
index 12a1915ef..7eca98b7d 100644
--- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySector.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySector.cs
@@ -47,13 +47,19 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		protected BaseVisualMode mode;
 		protected long setuponloadedtexture;
+
+		// This is only used to see if this object has already received a change
+		// in a multiselection. The Changed property on the BaseVisualSector is
+		// used to indicate a rebuild is needed.
+		protected bool changed;
 		
 		#endregion
 
 		#region ================== Properties
 		
 		new public BaseVisualSector Sector { get { return (BaseVisualSector)base.Sector; } }
-		
+		public bool Changed { get { return changed; } set { changed |= value; } }
+
 		#endregion
 
 		#region ================== Constructor / Destructor
@@ -75,7 +81,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		protected void UpdateSectorGeometry(bool includeneighbours)
 		{
 			// Rebuild sector
-			Sector.Rebuild();
+			Sector.Changed = true;
 
 			// Go for all things in this sector
 			foreach(Thing t in General.Map.Map.Things)
@@ -86,7 +92,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					{
 						// Update thing
 						BaseVisualThing vt = (mode.GetVisualThing(t) as BaseVisualThing);
-						vt.Setup();
+						vt.Changed = true;
 					}
 				}
 			}
@@ -94,16 +100,14 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			if(includeneighbours)
 			{
 				// Also rebuild surrounding sectors, because outside sidedefs may need to be adjusted
-				Dictionary<Sector, int> rebuilt = new Dictionary<Sector, int>();
 				foreach(Sidedef sd in Sector.Sector.Sidedefs)
 				{
-					if((sd.Other != null) && !rebuilt.ContainsKey(sd.Other.Sector))
+					if(sd.Other != null)
 					{
 						if(mode.VisualSectorExists(sd.Other.Sector))
 						{
 							BaseVisualSector bvs = (BaseVisualSector)mode.GetVisualSector(sd.Other.Sector);
-							rebuilt.Add(sd.Other.Sector, 1);
-							bvs.Rebuild();
+							bvs.Changed = true;
 						}
 					}
 				}
@@ -116,8 +120,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		// Unused
 		public abstract bool Setup();
-		public virtual void OnSelectBegin() { }
-		public virtual void OnSelectEnd() { }
+		public virtual void OnSelectBegin(){ }
 		public virtual void OnEditBegin() { }
 		public virtual void OnMouseMove(MouseEventArgs e) { }
 		public virtual void OnChangeTextureOffset(int horizontal, int vertical) { }
@@ -131,6 +134,12 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		public virtual void OnDelete() { }
 		protected virtual void SetTexture(string texturename) { }
 
+		// Select or deselect
+		public virtual void OnSelectEnd()
+		{
+			this.selected = !this.selected;
+		}
+		
 		// Processing
 		public virtual void OnProcess(double deltatime)
 		{
diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs
index 4e0639b4a..8708149cc 100644
--- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs
@@ -129,7 +129,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				Sidedef.SetTextureMid(General.Settings.DefaultTexture);
 
 				// Update
-				Sector.Rebuild();
+				Sector.Changed = true;
 				
 				// Other side as well
 				if(string.IsNullOrEmpty(Sidedef.Other.MiddleTexture) || (Sidedef.Other.MiddleTexture[0] == '-'))
@@ -138,7 +138,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 					// Update
 					VisualSector othersector = mode.GetVisualSector(Sidedef.Other.Sector);
-					if(othersector is BaseVisualSector) (othersector as BaseVisualSector).Rebuild();
+					if(othersector is BaseVisualSector) (othersector as BaseVisualSector).Changed = true;
 				}
 			}
 		}
@@ -152,7 +152,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			SetTexture("-");
 
 			// Update
-			Sector.Rebuild();
+			Sector.Changed = true;
 		}
 		
 		// Processing
@@ -435,11 +435,11 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				
 				// Update sectors on both sides
 				BaseVisualSector front = (BaseVisualSector)mode.GetVisualSector(Sidedef.Sector);
-				if(front != null) front.Rebuild();
+				if(front != null) front.Changed = true;
 				if(Sidedef.Other != null)
 				{
 					BaseVisualSector back = (BaseVisualSector)mode.GetVisualSector(Sidedef.Other.Sector);
-					if(back != null) back.Rebuild();
+					if(back != null) back.Changed = true;
 				}
 				mode.ShowTargetInfo();
 			}
@@ -471,6 +471,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			else
 			{
 				// Add/remove selection
+				this.selected = !this.selected;
 			}
 		}
 		
@@ -485,7 +486,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					List<Linedef> lines = new List<Linedef>();
 					lines.Add(this.Sidedef.Line);
 					DialogResult result = General.Interface.ShowEditLinedefs(lines);
-					if(result == DialogResult.OK) (this.Sector as BaseVisualSector).Rebuild();
+					if(result == DialogResult.OK) (this.Sector as BaseVisualSector).Changed = true;
 				}
 			}
 		}
@@ -567,7 +568,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			Sector.Sector.UpdateCache();
 			
 			// Rebuild sector
-			Sector.Rebuild();
+			Sector.Changed = true;
 
 			// Go for all things in this sector
 			foreach(Thing t in General.Map.Map.Things)
@@ -578,7 +579,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					{
 						// Update thing
 						BaseVisualThing vt = (mode.GetVisualThing(t) as BaseVisualThing);
-						vt.Setup();
+						vt.Changed = true;
 					}
 				}
 			}
diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
index d4bb60165..9782cee67 100644
--- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
@@ -60,21 +60,32 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		#endregion
 		
 		#region ================== Variables
+
+		// Gravity vector
+		private Vector3D gravity;
 		
 		// Object picking
 		private VisualPickResult target;
 		private double lastpicktime;
 		private bool locktarget;
 		
-		// Gravity vector
-		private Vector3D gravity;
+		// This is true when a selection was made because the action is performed
+		// on an object that was not selected. In this case the previous selection
+		// is cleared and the targeted object is temporarely selected to perform
+		// the action on. After the action is completed, the object is deselected.
+		private bool temporaryselection;
 		
+		// Actions
+		private int lastchangeoffsetticket;
+
 		#endregion
 		
 		#region ================== Properties
 
 		public IRenderer3D Renderer { get { return renderer; } }
 
+		public bool IsTemporarySelection { get { return temporaryselection; } }
+
 		#endregion
 		
 		#region ================== Constructor / Disposer
@@ -106,6 +117,36 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		
 		#region ================== Methods
 		
+		// This is called before an action is performed
+		private void PreAction(string multiundodescription)
+		{
+			int undogrouptag = 0;
+			
+			PickTargetUnlocked();
+			
+			// If the action is not performed on a selected object, clear the
+			// current selection and make a temporary selection for the target.
+			if(!target.picked.Selected)
+			{
+				temporaryselection = true;
+				ClearSelection();
+				target.picked.Selected = true;
+				
+				if(target.picked is BaseVisualGeometrySector)
+					undogrouptag = (target.picked as BaseVisualGeometrySector).Sector.Sector.FixedIndex;
+			}
+			
+			// Make an undo level
+			//lastundoticket = General.Map.UndoRedo.CreateUndo(multiundodescription, undogroup, undogrouptag);
+		}
+
+		// This is called after an action is performed
+		private void PostAction(VisualActionResult result)
+		{
+			UpdateChangedObjects();
+			ShowTargetInfo();
+		}
+
 		// This creates a visual sector
 		protected override VisualSector CreateVisualSector(Sector s)
 		{
@@ -117,9 +158,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		protected override VisualThing CreateVisualThing(Thing t)
 		{
 			BaseVisualThing vt = new BaseVisualThing(this, t);
-			if(vt.Setup()) return vt; else return null;
+			return vt.Setup() ? vt : null;
 		}
-		
+
 		// This locks the target so that it isn't changed until unlocked
 		public void LockTarget()
 		{
@@ -188,6 +229,22 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			}
 		}
 		
+		// This updates the VisualSectors and VisualThings that have their Changed property set
+		private void UpdateChangedObjects()
+		{
+			foreach(KeyValuePair<Sector, VisualSector> vs in allsectors)
+			{
+				BaseVisualSector bvs = (BaseVisualSector)vs.Value;
+				if(bvs.Changed) bvs.Rebuild();
+			}
+
+			foreach(KeyValuePair<Thing, VisualThing> vt in allthings)
+			{
+				BaseVisualThing bvt = (BaseVisualThing)vt.Value;
+				if(bvt.Changed) bvt.Setup();
+			}
+		}
+		
 		#endregion
 		
 		#region ================== Events
@@ -321,17 +378,44 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		#region ================== Actions
 
+		[BeginAction("clearselection", BaseAction = true)]
+		public void ClearSelection()
+		{
+			foreach(KeyValuePair<Sector, VisualSector> vs in allsectors)
+			{
+				BaseVisualSector bvs = (BaseVisualSector)vs.Value;
+				if(bvs.Floor != null) bvs.Floor.Selected = false;
+				if(bvs.Ceiling != null) bvs.Ceiling.Selected = false;
+				foreach(Sidedef sd in vs.Key.Sidedefs)
+				{
+					List<VisualGeometry> sidedefgeos = bvs.GetSidedefGeometry(sd);
+					foreach(VisualGeometry sdg in sidedefgeos)
+					{
+						sdg.Selected = false;
+					}
+				}
+			}
+
+			foreach(KeyValuePair<Thing, VisualThing> vt in allthings)
+			{
+				BaseVisualThing bvt = (BaseVisualThing)vt.Value;
+				bvt.Selected = false;
+			}
+		}
+
 		[BeginAction("visualselect", BaseAction = true)]
 		public void BeginSelect()
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnSelectBegin();
+			UpdateChangedObjects();
 		}
 
 		[EndAction("visualselect", BaseAction = true)]
 		public void EndSelect()
 		{
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnSelectEnd();
+			UpdateChangedObjects();
 		}
 
 		[BeginAction("visualedit", BaseAction = true)]
@@ -339,12 +423,14 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnEditBegin();
+			UpdateChangedObjects();
 		}
 
 		[EndAction("visualedit", BaseAction = true)]
 		public void EndEdit()
 		{
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnEditEnd();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -353,6 +439,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTargetHeight(8);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -361,6 +448,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTargetHeight(-8);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -369,6 +457,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTargetHeight(1);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 		
@@ -377,6 +466,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTargetHeight(-1);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -392,6 +482,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTargetBrightness(true);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -400,6 +491,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTargetBrightness(false);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -408,6 +500,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTextureOffset(-1, 0);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -416,6 +509,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTextureOffset(1, 0);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -424,6 +518,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTextureOffset(0, -1);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -432,6 +527,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTextureOffset(0, 1);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -440,6 +536,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTextureOffset(-8, 0);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -448,6 +545,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTextureOffset(8, 0);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -456,6 +554,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTextureOffset(0, -8);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -464,6 +563,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnChangeTextureOffset(0, 8);
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -474,6 +574,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			renderer.SetCrosshairBusy(true);
 			General.Interface.RedrawDisplay();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnSelectTexture();
+			UpdateChangedObjects();
 			renderer.SetCrosshairBusy(false);
 			ShowTargetInfo();
 		}
@@ -483,6 +584,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnCopyTexture();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -491,6 +593,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnPasteTexture();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -501,6 +604,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			renderer.SetCrosshairBusy(true);
 			General.Interface.RedrawDisplay();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnTextureAlign(true, false);
+			UpdateChangedObjects();
 			renderer.SetCrosshairBusy(false);
 			ShowTargetInfo();
 		}
@@ -512,6 +616,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			renderer.SetCrosshairBusy(true);
 			General.Interface.RedrawDisplay();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnTextureAlign(false, true);
+			UpdateChangedObjects();
 			renderer.SetCrosshairBusy(false);
 			ShowTargetInfo();
 		}
@@ -521,6 +626,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnToggleUpperUnpegged();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -529,6 +635,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnToggleLowerUnpegged();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -553,6 +660,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnResetTextureOffset();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -561,6 +669,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnTextureFloodfill();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -569,6 +678,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnCopyTextureOffsets();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -577,6 +687,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnPasteTextureOffsets();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -585,6 +696,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnCopyProperties();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -593,6 +705,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnPasteProperties();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 		
@@ -601,6 +714,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnInsert();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 
@@ -609,6 +723,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			PickTargetUnlocked();
 			if(target.picked != null) (target.picked as IVisualEventReceiver).OnDelete();
+			UpdateChangedObjects();
 			ShowTargetInfo();
 		}
 		
diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualSector.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualSector.cs
index 125480196..7ef1e1691 100644
--- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualSector.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualSector.cs
@@ -50,13 +50,17 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		protected VisualCeiling ceiling;
 		protected Dictionary<Sidedef, VisualSidedefParts> sides;
 		
+		// If this is set to true, the sector will be rebuilt after the action is performed.
+		protected bool changed;
+
 		#endregion
 
 		#region ================== Properties
 
 		public VisualFloor Floor { get { return floor; } }
 		public VisualCeiling Ceiling { get { return ceiling; } }
-
+		public bool Changed { get { return changed; } set { changed |= value; } }
+		
 		#endregion
 
 		#region ================== Constructor / Disposer
@@ -100,30 +104,34 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			base.ClearGeometry();
 			
 			// Create floor
-			floor = new VisualFloor(mode, this);
+			if(floor == null) floor = new VisualFloor(mode, this);
 			if(floor.Setup()) base.AddGeometry(floor);
 
 			// Create ceiling
-			ceiling = new VisualCeiling(mode, this);
+			if(ceiling == null) ceiling = new VisualCeiling(mode, this);
 			if(ceiling.Setup()) base.AddGeometry(ceiling);
 
 			// Go for all sidedefs
+			Dictionary<Sidedef, VisualSidedefParts> oldsides = sides ?? new Dictionary<Sidedef, VisualSidedefParts>(1);
 			sides = new Dictionary<Sidedef, VisualSidedefParts>(base.Sector.Sidedefs.Count);
 			foreach(Sidedef sd in base.Sector.Sidedefs)
 			{
+				// VisualSidedef already exists?
+				VisualSidedefParts parts = oldsides.ContainsKey(sd) ? oldsides[sd] : new VisualSidedefParts();
+				
 				// Doublesided or singlesided?
 				if(sd.Other != null)
 				{
 					// Create upper part
-					VisualUpper vu = new VisualUpper(mode, this, sd);
+					VisualUpper vu = parts.upper ?? new VisualUpper(mode, this, sd);
 					if(vu.Setup()) base.AddGeometry(vu);
 					
 					// Create lower part
-					VisualLower vl = new VisualLower(mode, this, sd);
+					VisualLower vl = parts.lower ?? new VisualLower(mode, this, sd);
 					if(vl.Setup()) base.AddGeometry(vl);
 					
 					// Create middle part
-					VisualMiddleDouble vm = new VisualMiddleDouble(mode, this, sd);
+					VisualMiddleDouble vm = parts.middledouble ?? new VisualMiddleDouble(mode, this, sd);
 					if(vm.Setup()) base.AddGeometry(vm);
 
 					// Store
@@ -132,13 +140,16 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				else
 				{
 					// Create middle part
-					VisualMiddleSingle vm = new VisualMiddleSingle(mode, this, sd);
+					VisualMiddleSingle vm = parts.middlesingle ?? new VisualMiddleSingle(mode, this, sd);
 					if(vm.Setup()) base.AddGeometry(vm);
 					
 					// Store
 					sides.Add(sd, new VisualSidedefParts(vm));
 				}
 			}
+			
+			// Done
+			changed = false;
 		}
 		
 		// This returns the visual sidedef parts for a given sidedef
diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualThing.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualThing.cs
index 21d90a8f1..5e3c75d20 100644
--- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualThing.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualThing.cs
@@ -58,10 +58,15 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		
 		// Undo/redo
 		private int undoticket;
-		
+
+		// If this is set to true, the thing will be rebuilt after the action is performed.
+		protected bool changed;
+
 		#endregion
 		
 		#region ================== Properties
+
+		public bool Changed { get { return changed; } set { changed |= value; } }
 		
 		#endregion
 		
@@ -193,6 +198,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			boxp2 = new Vector3D(pos.x + info.Radius, pos.y + info.Radius, pos.z + info.Height);
 			
 			// Done
+			changed = false;
 			return true;
 		}
 		
@@ -348,7 +354,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		// Unused
 		public virtual void OnSelectBegin() { }
-		public virtual void OnSelectEnd() { }
 		public virtual void OnEditBegin() { }
 		public virtual void OnMouseMove(MouseEventArgs e) { }
 		public virtual void OnChangeTargetBrightness(bool up) { }
@@ -369,6 +374,12 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		
 		// Return texture name
 		public virtual string GetTextureName() { return ""; }
+
+		// Select or deselect
+		public virtual void OnSelectEnd()
+		{
+			this.selected = !this.selected;
+		}
 		
 		// Copy properties
 		public virtual void OnCopyProperties()
@@ -419,7 +430,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 				General.Interface.DisplayStatus(StatusType.Action, "Changed thing height to " + Thing.Position.z + ".");
 
-				this.Setup();
+				this.Changed = true;
 			}
 		}
 		
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualActionResult.cs b/Source/Plugins/BuilderModes/VisualModes/VisualActionResult.cs
new file mode 100644
index 000000000..39724b15c
--- /dev/null
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualActionResult.cs
@@ -0,0 +1,43 @@
+
+#region ================== Copyright (c) 2007 Pascal vd Heiden
+
+/*
+ * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
+ * This program is released under GNU General Public License
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using System.Windows.Forms;
+using System.IO;
+using System.Reflection;
+using CodeImp.DoomBuilder.Windows;
+using CodeImp.DoomBuilder.IO;
+using CodeImp.DoomBuilder.Map;
+using CodeImp.DoomBuilder.Rendering;
+using CodeImp.DoomBuilder.Geometry;
+using CodeImp.DoomBuilder.Editing;
+using CodeImp.DoomBuilder.VisualModes;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderModes
+{
+	internal struct VisualActionResult
+	{
+		public string displaystatus;
+	}
+}
-- 
GitLab