diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj index d50d8206..a81cc69e 100644 --- a/Source/Core/Builder.csproj +++ b/Source/Core/Builder.csproj @@ -252,6 +252,8 @@ + + Form diff --git a/Source/Core/General/MapManager.cs b/Source/Core/General/MapManager.cs index 96cf2fd9..f5d1df49 100755 --- a/Source/Core/General/MapManager.cs +++ b/Source/Core/General/MapManager.cs @@ -2181,6 +2181,7 @@ namespace CodeImp.DoomBuilder // Update settings renderer3d.CreateProjection(); renderer3d.UpdateVertexHandle(); //mxd + renderer3d.UpdateVisualSlopeHandle(); // Things filters General.MainWindow.UpdateThingsFilters(); diff --git a/Source/Core/Rendering/IRenderer3D.cs b/Source/Core/Rendering/IRenderer3D.cs index 28ea5866..17a7579e 100755 --- a/Source/Core/Rendering/IRenderer3D.cs +++ b/Source/Core/Rendering/IRenderer3D.cs @@ -50,6 +50,7 @@ namespace CodeImp.DoomBuilder.Rendering void AddSectorGeometry(VisualGeometry g); void AddThingGeometry(VisualThing t); void SetVisualVertices(List verts); + void SetVisualSlopeHandles(List handles); void SetEventLines(List lines); void RenderCrosshair(); void SetFogMode(bool usefog); diff --git a/Source/Core/Rendering/RenderDevice.cs b/Source/Core/Rendering/RenderDevice.cs index 54375cc1..134a7ec7 100755 --- a/Source/Core/Rendering/RenderDevice.cs +++ b/Source/Core/Rendering/RenderDevice.cs @@ -68,6 +68,7 @@ namespace CodeImp.DoomBuilder.Rendering DeclareUniform(UniformName.fogcolor, "fogcolor", UniformType.Vec4f); DeclareUniform(UniformName.sectorfogcolor, "sectorfogcolor", UniformType.Vec4f); DeclareUniform(UniformName.lightsEnabled, "lightsEnabled", UniformType.Float); + DeclareUniform(UniformName.slopeHandleLength, "slopeHandleLength", UniformType.Float); // 2d fsaa CompileShader(ShaderName.display2d_fsaa, "display2d.shader", "display2d_fsaa"); @@ -100,6 +101,9 @@ namespace CodeImp.DoomBuilder.Rendering CompileShader(ShaderName.world3d_main_fog_vertexcolor, "world3d.shader", "world3d_main_fog_vertexcolor"); CompileShader(ShaderName.world3d_main_highlight_fog_vertexcolor, "world3d.shader", "world3d_main_highlight_fog_vertexcolor"); + // Slope handle + CompileShader(ShaderName.world3d_slope_handle, "world3d.shader", "world3d_slope_handle"); + SetupSettings(); } @@ -730,7 +734,8 @@ namespace CodeImp.DoomBuilder.Rendering world3d_p13, world3d_main_highlight_fog_vertexcolor, world3d_vertex_color, - world3d_constant_color + world3d_constant_color, + world3d_slope_handle } public enum UniformType : int @@ -772,7 +777,8 @@ namespace CodeImp.DoomBuilder.Rendering fogsettings, fogcolor, sectorfogcolor, - lightsEnabled + lightsEnabled, + slopeHandleLength } public enum VertexFormat : int { Flat, World } diff --git a/Source/Core/Rendering/Renderer3D.cs b/Source/Core/Rendering/Renderer3D.cs index 4a753ff0..f8e86840 100755 --- a/Source/Core/Rendering/Renderer3D.cs +++ b/Source/Core/Rendering/Renderer3D.cs @@ -64,7 +64,10 @@ namespace CodeImp.DoomBuilder.Rendering //mxd private VisualVertexHandle vertexhandle; private int[] lightOffsets; - + + // Slope handle + private VisualSlopeHandle visualslopehandle; + // Crosshair private FlatVertex[] crosshairverts; private bool crosshairbusy; @@ -112,6 +115,9 @@ namespace CodeImp.DoomBuilder.Rendering //mxd. Visual vertices private List visualvertices; + // Visual slope handles + private List visualslopehandles; + //mxd. Event lines private List eventlines; @@ -166,7 +172,8 @@ namespace CodeImp.DoomBuilder.Rendering { // Clean up if(vertexhandle != null) vertexhandle.Dispose(); //mxd - + if (visualslopehandle != null) visualslopehandle.Dispose(); + // Done base.Dispose(); } @@ -232,8 +239,17 @@ namespace CodeImp.DoomBuilder.Rendering } } + internal void UpdateVisualSlopeHandle() + { + if (visualslopehandle != null) + { + visualslopehandle.UnloadResource(); + visualslopehandle.ReloadResource(); + } + } + #endregion - + #region ================== Presentation // This creates the projection @@ -344,7 +360,10 @@ namespace CodeImp.DoomBuilder.Rendering //mxd. Crate vertex handle if(vertexhandle == null) vertexhandle = new VisualVertexHandle(); - + + // Create slope handle + if (visualslopehandle == null) visualslopehandle = new VisualSlopeHandle(); + // Ready return true; } @@ -447,8 +466,12 @@ namespace CodeImp.DoomBuilder.Rendering //mxd. Visual vertices RenderVertices(); + // Slope handles + if (General.Map.UDMF /* && General.Settings.ShowVisualSlopeHandles */) + RenderSlopeHandles(); + //mxd. Event lines - if(General.Settings.GZShowEventLines) RenderArrows(eventlines); + if (General.Settings.GZShowEventLines) RenderArrows(eventlines); // Remove references graphics.SetTexture(null); @@ -615,6 +638,45 @@ namespace CodeImp.DoomBuilder.Rendering graphics.SetUniform(UniformName.texturefactor, new Color4(1f, 1f, 1f, 1f)); } + private void RenderSlopeHandles() + { + if (visualslopehandles == null || !showselection) return; + + graphics.SetAlphaBlendEnable(true); + graphics.SetAlphaTestEnable(false); + graphics.SetZWriteEnable(false); + graphics.SetSourceBlend(Blend.SourceAlpha); + graphics.SetDestinationBlend(Blend.InverseSourceAlpha); + + graphics.SetShader(ShaderName.world3d_slope_handle); + + foreach (VisualSlope handle in visualslopehandles) + { + PixelColor color = General.Colors.Vertices; + + if (handle.Pivot) + color = General.Colors.Guideline; + else if (handle.Selected) + color = General.Colors.Selection3D; + else if (handle == highlighted) + color = General.Colors.Highlight3D; + else if (handle.SmartPivot) + color = General.Colors.Vertices; + + world = handle.Position; + graphics.SetUniform(UniformName.world, ref world); + graphics.SetUniform(UniformName.slopeHandleLength, handle.Length); + graphics.SetUniform(UniformName.vertexColor, color.ToColorValue()); + + graphics.SetVertexBuffer(visualslopehandle.Geometry); + graphics.Draw(PrimitiveType.TriangleList, 0, 2); + + } + + // Done + graphics.SetUniform(UniformName.texturefactor, new Color4(1f, 1f, 1f, 1f)); + } + //mxd private void RenderArrows(ICollection lines) { @@ -1755,6 +1817,8 @@ namespace CodeImp.DoomBuilder.Rendering //mxd public void SetVisualVertices(List verts) { visualvertices = verts; } + public void SetVisualSlopeHandles(List handles) { visualslopehandles = handles; } + //mxd public void SetEventLines(List lines) { eventlines = lines; } diff --git a/Source/Core/Rendering/VisualSlopeHandle.cs b/Source/Core/Rendering/VisualSlopeHandle.cs new file mode 100644 index 00000000..7b01a2d3 --- /dev/null +++ b/Source/Core/Rendering/VisualSlopeHandle.cs @@ -0,0 +1,89 @@ +#region ================== Namespaces + +using System; + +#endregion + +namespace CodeImp.DoomBuilder.Rendering +{ + internal sealed class VisualSlopeHandle : IDisposable, IRenderResource + { + #region ================== Variables + + private VertexBuffer geometry; + private bool isdisposed; + + #endregion + + #region ================== Properties + + public VertexBuffer Geometry { get { return geometry; } } + + #endregion + + #region ================== Constructor / Disposer + + public VisualSlopeHandle() + { + // Create geometry + ReloadResource(); + + // Register as source + General.Map.Graphics.RegisterResource(this); + } + + public void Dispose() + { + // Not already disposed? + if (!isdisposed) + { + if (geometry != null) + geometry.Dispose(); + + // Unregister resource + General.Map.Graphics.UnregisterResource(this); + + // Done + isdisposed = true; + } + } + + #endregion + + #region ================== Methods + + // This is called resets when the device is reset + // (when resized or display adapter was changed) + public void ReloadResource() + { + WorldVertex v0 = new WorldVertex(0.0f, -8.0f, 0.1f); + WorldVertex v1 = new WorldVertex(0.0f, 0.0f, 0.1f); + WorldVertex v2 = new WorldVertex(1.0f, 0.0f, 0.1f); + WorldVertex v3 = new WorldVertex(1.0f, -8.0f, 0.1f); + + v1.c = v2.c = PixelColor.INT_WHITE; + v0.c = v3.c = PixelColor.INT_WHITE_NO_ALPHA; + + WorldVertex[] vertices = new[] + { + v0, v1, v2, + v0, v2, v3 + }; + + geometry = new VertexBuffer(); + General.Map.Graphics.SetBufferData(geometry, vertices); + } + + // This is called before a device is reset + // (when resized or display adapter was changed) + public void UnloadResource() + { + if (geometry != null) + geometry.Dispose(); + + geometry = null; + } + + #endregion + } +} \ No newline at end of file diff --git a/Source/Core/Resources/world3d.shader b/Source/Core/Resources/world3d.shader index d400c70c..84503bd7 100755 --- a/Source/Core/Resources/world3d.shader +++ b/Source/Core/Resources/world3d.shader @@ -25,6 +25,9 @@ uniforms float ignoreNormals; float lightsEnabled; + // Slope handle length + float slopeHandleLength; + } functions @@ -355,4 +358,16 @@ shader world3d_main_highlight_fog_vertexcolor extends world3d_main_highlight_fog v2f.UV = in.TextureCoordinate; v2f.Normal = normalize((modelnormal * vec4(in.Normal, 1.0)).xyz); } +} + +// Slope handle shader +shader world3d_slope_handle extends world3d_vertex_color +{ + vertex + { + v2f.viewpos = view * world * vec4(in.Position.x * slopeHandleLength, in.Position.y, in.Position.z, 1.0); + gl_Position = projection * v2f.viewpos; + v2f.Color = in.Color * vertexColor; + v2f.UV = in.TextureCoordinate; + } } \ No newline at end of file diff --git a/Source/Core/VisualModes/VisualMode.cs b/Source/Core/VisualModes/VisualMode.cs index 2c750ad9..9bb71f8b 100755 --- a/Source/Core/VisualModes/VisualMode.cs +++ b/Source/Core/VisualModes/VisualMode.cs @@ -30,6 +30,12 @@ using CodeImp.DoomBuilder.Editing; namespace CodeImp.DoomBuilder.VisualModes { + public enum PickingMode + { + Default, + SlopeHandles + } + /// /// Provides specialized functionality for a visual (3D) Doom Builder editing mode. /// @@ -68,10 +74,14 @@ namespace CodeImp.DoomBuilder.VisualModes private Vector3D playerStartPosition; private float playerStartAngle; + // For picking + protected PickingMode pickingmode; + // Map protected VisualBlockMap blockmap; protected Dictionary allthings; protected Dictionary allsectors; + protected Dictionary> allslopehandles; protected List visibleblocks; protected List visiblethings; protected List visiblesectors; @@ -85,6 +95,7 @@ namespace CodeImp.DoomBuilder.VisualModes public bool ProcessThings { get { return processthings; } set { processthings = value; } } public VisualBlockMap BlockMap { get { return blockmap; } } public Dictionary VisualVertices { get { return vertices; } } //mxd + public Dictionary> AllSlopeHandles { get { return allslopehandles; } } // Rendering public IRenderer3D Renderer { get { return renderer; } } @@ -103,6 +114,7 @@ namespace CodeImp.DoomBuilder.VisualModes this.blockmap = new VisualBlockMap(); this.allsectors = new Dictionary(General.Map.Map.Sectors.Count); this.allthings = new Dictionary(General.Map.Map.Things.Count); + this.allslopehandles = new Dictionary>(General.Map.Map.Sectors.Count); this.visibleblocks = new List(); this.visiblesectors = new List(50); this.visiblegeometry = new List(200); @@ -110,6 +122,7 @@ namespace CodeImp.DoomBuilder.VisualModes this.processgeometry = true; this.processthings = true; this.vertices = new Dictionary(); //mxd + this.pickingmode = PickingMode.Default; //mxd. Synch camera position to cursor position or center of the screen in 2d-mode if(General.Settings.GZSynchCameras && General.Editing.Mode is ClassicMode) @@ -223,8 +236,8 @@ namespace CodeImp.DoomBuilder.VisualModes // Dispose foreach(KeyValuePair vt in allthings) - if(vt.Value != null) vt.Value.Dispose(); - + if(vt.Value != null) vt.Value.Dispose(); + // Apply camera position to thing General.Map.VisualCamera.ApplyToThing(); @@ -720,6 +733,10 @@ namespace CodeImp.DoomBuilder.VisualModes VisualSector vs = allsectors[General.Map.VisualCamera.Sector]; sectors.Add(General.Map.VisualCamera.Sector, vs); foreach(VisualGeometry g in vs.FixedGeometry) pickables.Add(g); + + // Add slope handles + if (General.Map.UDMF && pickingmode == PickingMode.SlopeHandles && allslopehandles.ContainsKey(General.Map.VisualCamera.Sector)) + pickables.AddRange(allslopehandles[General.Map.VisualCamera.Sector]); } // Go for all lines to see which ones we intersect @@ -758,12 +775,16 @@ namespace CodeImp.DoomBuilder.VisualModes if(!sectors.ContainsKey(ld.Front.Sector)) { sectors.Add(ld.Front.Sector, vs); - foreach(VisualGeometry g in vs.FixedGeometry) + foreach (VisualGeometry g in vs.FixedGeometry) { // Must have content - if(g.Triangles > 0) + if (g.Triangles > 0) pickables.Add(g); } + + // Add slope handles + if (General.Map.UDMF && pickingmode == PickingMode.SlopeHandles && allslopehandles.ContainsKey(ld.Front.Sector)) + pickables.AddRange(allslopehandles[ld.Front.Sector]); } // Add sidedef if on the front side @@ -795,12 +816,16 @@ namespace CodeImp.DoomBuilder.VisualModes if(!sectors.ContainsKey(ld.Back.Sector)) { sectors.Add(ld.Back.Sector, vs); - foreach(VisualGeometry g in vs.FixedGeometry) + foreach (VisualGeometry g in vs.FixedGeometry) { // Must have content - if(g.Triangles > 0) + if (g.Triangles > 0) pickables.Add(g); } + + // Add slope handles + if (General.Map.UDMF && pickingmode == PickingMode.SlopeHandles && allslopehandles.ContainsKey(ld.Back.Sector)) + pickables.AddRange(allslopehandles[ld.Back.Sector]); } // Add sidedef if on the front side @@ -828,9 +853,9 @@ namespace CodeImp.DoomBuilder.VisualModes foreach(VisualThing vt in visiblethings) pickables.Add(vt); //mxd. And all visual vertices - if(General.Map.UDMF && General.Settings.GZShowVisualVertices) + if (General.Map.UDMF && General.Settings.GZShowVisualVertices) { - foreach(KeyValuePair pair in vertices) + foreach (KeyValuePair pair in vertices) pickables.AddRange(pair.Value.Vertices); } @@ -864,6 +889,11 @@ namespace CodeImp.DoomBuilder.VisualModes // Setup final result result.hitpos = from + to * result.u_ray; + // If picking mode is for slope handles only return slope handles. We have to do it this + // way because otherwise it's possible to pick slope handles through other geometry + if (pickingmode == PickingMode.SlopeHandles && !(result.picked is VisualSlope)) + result.picked = null; + // Done return result; } diff --git a/Source/Core/VisualModes/VisualSlope.cs b/Source/Core/VisualModes/VisualSlope.cs new file mode 100644 index 00000000..dceebdae --- /dev/null +++ b/Source/Core/VisualModes/VisualSlope.cs @@ -0,0 +1,138 @@ +using System; +using CodeImp.DoomBuilder.Geometry; +using CodeImp.DoomBuilder.Map; +using CodeImp.DoomBuilder.Rendering; + +namespace CodeImp.DoomBuilder.VisualModes +{ + public abstract class VisualSlope : IVisualPickable + { + #region ================== Variables + + // Disposing + private bool isdisposed; + + // Selected? + protected bool selected; + + // Pivot? + protected bool pivot; + + // Smart Pivot? + protected bool smartpivot; + + // Was changed? + private bool changed; + + protected float length; + + private Matrix position; + + #endregion + + #region ================== Properties + + /// + /// Selected or not? This is only used by the core to determine what color to draw it with. + /// + public bool Selected { get { return selected; } set { selected = value; } } + + /// + /// Pivot or not? This is only used by the core to determine what color to draw it with. + /// + public bool Pivot { get { return pivot; } set { pivot = value; } } + + /// + /// Disposed or not? + /// + public bool IsDisposed { get { return isdisposed; } } + + public bool SmartPivot { get { return smartpivot; } set { smartpivot = value; } } + + public bool Changed { get { return changed; } set { changed = value; } } + + public float Length { get { return length; } } + + public Matrix Position { get { return position; } } + + #endregion + + #region ================== Constructor / Destructor + + public VisualSlope() + { + pivot = false; + smartpivot = false; + } + + #endregion + + #region ================== Methods + + // This is called before a device is reset (when resized or display adapter was changed) + public void UnloadResource() + { + } + + // This is called resets when the device is reset + // (when resized or display adapter was changed) + public void ReloadResource() + { + } + + /// + /// This is called when the thing must be tested for line intersection. This should reject + /// as fast as possible to rule out all geometry that certainly does not touch the line. + /// + public virtual bool PickFastReject(Vector3D from, Vector3D to, Vector3D dir) + { + return true; + } + + /// + /// This is called when the thing must be tested for line intersection. This should perform + /// accurate hit detection and set u_ray to the position on the ray where this hits the geometry. + /// + public virtual bool PickAccurate(Vector3D from, Vector3D to, Vector3D dir, ref float u_ray) + { + return true; + } + + public virtual void Update() {} + + public void SetPosition(Line2D line, Plane plane) + { + Line3D line3d = new Line3D(new Vector3D(line.v1, plane.GetZ(line.v1)), new Vector3D(line.v2, plane.GetZ(line.v2))); + + // This vector is perpendicular to the line, with a 90° angle between it and the plane normal + Vector3D perpendicularvector = Vector3D.CrossProduct(line3d.GetDelta().GetNormal(), plane.Normal) * (-1); + + // This vector is on the plane, with a 90° angle to the perpendicular vector (so effectively + // it's on the line, but in 3D + Vector3D linevector = Vector3D.CrossProduct(plane.Normal, perpendicularvector) * (-1); + + Matrix m = Matrix.Null; + + m.M11 = linevector.x; + m.M12 = linevector.y; + m.M13 = linevector.z; + + m.M21 = perpendicularvector.x; + m.M22 = perpendicularvector.y; + m.M23 = perpendicularvector.z; + + m.M31 = plane.Normal.x; + m.M32 = plane.Normal.y; + m.M33 = plane.Normal.z; + + m.M44 = 1.0f; + + // The matrix is at the 0,0 origin, so move it to the start vertex of the line + Vector3D tp = new Vector3D(line.v1, plane.GetZ(line.v1)); + + position = Matrix.Multiply(m, Matrix.Translation(RenderDevice.V3(tp))); + } + + #endregion + } +} \ No newline at end of file diff --git a/Source/Plugins/BuilderModes/BuilderModes.csproj b/Source/Plugins/BuilderModes/BuilderModes.csproj index 5c02495d..06813607 100755 --- a/Source/Plugins/BuilderModes/BuilderModes.csproj +++ b/Source/Plugins/BuilderModes/BuilderModes.csproj @@ -495,6 +495,7 @@ + diff --git a/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs b/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs index c1e10e87..2a143346 100755 --- a/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs +++ b/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs @@ -170,6 +170,7 @@ namespace CodeImp.DoomBuilder.BuilderModes private ICollection unselectedvertices; private ICollection unselectedlines; private ICollection unstablelines; //mxd + private Dictionary slopeheights; // Modification private float rotation; @@ -1325,6 +1326,32 @@ namespace CodeImp.DoomBuilder.BuilderModes thingpos.Add(t.Position); thingangle.Add(t.Angle); } + + // Get z heights of floor and ceiling slopes (from the center of the sector). This is used + // to easily compute the new slope after moving and rotating. We can't simply use the sector + // heights, since they might be wrong (as sector heights are technically irrelevant for slopes) + // Floor/ceiling heights are stored if there is no slope, but they won't get used anyway + // Important: this has to be done before the first call to UpdateGeometry, since that will change + // the sector and subsequently the bounding box, but not the slope + slopeheights = new Dictionary(); + + foreach(Sector s in sectors) + { + // Make sure the sector has a valid bounding box + s.UpdateCache(); + + Vector2D center = new Vector2D(s.BBox.X + s.BBox.Width / 2, s.BBox.Y + s.BBox.Height / 2); + float floorz = s.FloorHeight; + float ceilingz = s.CeilHeight; + + if (!float.IsNaN(s.FloorSlopeOffset)) + floorz = new Plane(s.FloorSlope, s.FloorSlopeOffset).GetZ(center); + + if (!float.IsNaN(s.CeilSlopeOffset)) + ceilingz = new Plane(s.CeilSlope, s.CeilSlopeOffset).GetZ(center); + + slopeheights.Add(s, new float[] { floorz, ceilingz }); + } // Calculate size size = right - offset; @@ -1550,7 +1577,7 @@ namespace CodeImp.DoomBuilder.BuilderModes // Update floor slope? if(s.FloorSlope.GetLengthSq() > 0 && !float.IsNaN(s.FloorSlopeOffset / s.FloorSlope.z)) { - Vector3D center = new Vector3D(s.BBox.X + s.BBox.Width / 2, s.BBox.Y + s.BBox.Height / 2, s.FloorHeight); + Vector3D center = new Vector3D(s.BBox.X + s.BBox.Width / 2, s.BBox.Y + s.BBox.Height / 2, slopeheights[s][0]); Plane p = new Plane(center, s.FloorSlope.GetAngleXY() + rotation + Angle2D.PIHALF, -s.FloorSlope.GetAngleZ(), true); s.FloorSlope = p.Normal; s.FloorSlopeOffset = p.Offset; @@ -1559,7 +1586,7 @@ namespace CodeImp.DoomBuilder.BuilderModes // Update ceiling slope? if(s.CeilSlope.GetLengthSq() > 0 && !float.IsNaN(s.CeilSlopeOffset / s.CeilSlope.z)) { - Vector3D center = new Vector3D(s.BBox.X + s.BBox.Width / 2, s.BBox.Y + s.BBox.Height / 2, s.CeilHeight); + Vector3D center = new Vector3D(s.BBox.X + s.BBox.Width / 2, s.BBox.Y + s.BBox.Height / 2, slopeheights[s][1]); Plane p = new Plane(center, s.CeilSlope.GetAngleXY() + rotation + Angle2D.PIHALF, -s.CeilSlope.GetAngleZ(), false); s.CeilSlope = p.Normal; s.CeilSlopeOffset = p.Offset; diff --git a/Source/Plugins/BuilderModes/General/BuilderPlug.cs b/Source/Plugins/BuilderModes/General/BuilderPlug.cs index 75d8f0b7..2b3c5df2 100755 --- a/Source/Plugins/BuilderModes/General/BuilderPlug.cs +++ b/Source/Plugins/BuilderModes/General/BuilderPlug.cs @@ -136,6 +136,7 @@ namespace CodeImp.DoomBuilder.BuilderModes private bool alphabasedtexturehighlighting; //mxd private bool showlightradii; //mxd private bool showsoundradii; //mxd + private int scaletexturesonslopes; // 0 = base scale of 1, 1 = use current scale as base, 2 = don't scale #endregion @@ -189,6 +190,7 @@ namespace CodeImp.DoomBuilder.BuilderModes public bool AlphaBasedTextureHighlighting { get { return alphabasedtexturehighlighting; } internal set { alphabasedtexturehighlighting = value; } } //mxd public bool ShowLightRadii { get { return showlightradii; } internal set { showlightradii = value; } } //mxd public bool ShowSoundRadii { get { return showsoundradii; } internal set { showsoundradii = value; } } //mxd + public int ScaleTexturesOnSlopes { get { return scaletexturesonslopes; } internal set { scaletexturesonslopes = value; } } //mxd. "Make Door" action persistent settings internal MakeDoorSettings MakeDoor; @@ -291,6 +293,7 @@ namespace CodeImp.DoomBuilder.BuilderModes autoAlignTextureOffsetsOnCreate = General.Settings.ReadPluginSetting("autoaligntextureoffsetsoncreate", false); //mxd dontMoveGeometryOutsideMapBoundary = General.Settings.ReadPluginSetting("dontmovegeometryoutsidemapboundary", false); //mxd syncSelection = General.Settings.ReadPluginSetting("syncselection", false); //mxd + scaletexturesonslopes = General.Settings.ReadPluginSetting("scaletexturesonslopes", 0); } //mxd. Load settings, which can be changed via UI diff --git a/Source/Plugins/BuilderModes/Interface/PreferencesForm.Designer.cs b/Source/Plugins/BuilderModes/Interface/PreferencesForm.Designer.cs index d284d309..1db5dac6 100755 --- a/Source/Plugins/BuilderModes/Interface/PreferencesForm.Designer.cs +++ b/Source/Plugins/BuilderModes/Interface/PreferencesForm.Designer.cs @@ -40,6 +40,7 @@ namespace CodeImp.DoomBuilder.BuilderModes this.defaultbrightness = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox(); this.label11 = new System.Windows.Forms.Label(); this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.additivepaintselect = new System.Windows.Forms.CheckBox(); this.switchviewmodes = new System.Windows.Forms.CheckBox(); this.autodrawonedit = new System.Windows.Forms.CheckBox(); this.syncSelection = new System.Windows.Forms.CheckBox(); @@ -72,7 +73,8 @@ namespace CodeImp.DoomBuilder.BuilderModes this.label10 = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label(); this.heightbysidedef = new System.Windows.Forms.ComboBox(); - this.additivepaintselect = new System.Windows.Forms.CheckBox(); + this.label18 = new System.Windows.Forms.Label(); + this.scaletexturesonslopes = new System.Windows.Forms.ComboBox(); this.tabs.SuspendLayout(); this.taboptions.SuspendLayout(); this.groupBox4.SuspendLayout(); @@ -120,7 +122,7 @@ namespace CodeImp.DoomBuilder.BuilderModes this.groupBox4.Controls.Add(this.label12); this.groupBox4.Controls.Add(this.defaultbrightness); this.groupBox4.Controls.Add(this.label11); - this.groupBox4.Location = new System.Drawing.Point(6, 300); + this.groupBox4.Location = new System.Drawing.Point(6, 335); this.groupBox4.Name = "groupBox4"; this.groupBox4.Size = new System.Drawing.Size(272, 136); this.groupBox4.TabIndex = 2; @@ -240,13 +242,23 @@ namespace CodeImp.DoomBuilder.BuilderModes this.groupBox3.Controls.Add(this.editnewthing); this.groupBox3.Controls.Add(this.editnewsector); this.groupBox3.Controls.Add(this.additiveselect); - this.groupBox3.Location = new System.Drawing.Point(284, 104); + this.groupBox3.Location = new System.Drawing.Point(284, 139); this.groupBox3.Name = "groupBox3"; this.groupBox3.Size = new System.Drawing.Size(379, 332); this.groupBox3.TabIndex = 3; this.groupBox3.TabStop = false; this.groupBox3.Text = " Options "; // + // additivepaintselect + // + this.additivepaintselect.AutoSize = true; + this.additivepaintselect.Location = new System.Drawing.Point(13, 135); + this.additivepaintselect.Name = "additivepaintselect"; + this.additivepaintselect.Size = new System.Drawing.Size(233, 17); + this.additivepaintselect.TabIndex = 11; + this.additivepaintselect.Text = "Additive paint selecting without holding Shift"; + this.additivepaintselect.UseVisualStyleBackColor = true; + // // switchviewmodes // this.switchviewmodes.AutoSize = true; @@ -375,7 +387,7 @@ namespace CodeImp.DoomBuilder.BuilderModes this.groupBox2.Controls.Add(this.label6); this.groupBox2.Controls.Add(this.label4); this.groupBox2.Controls.Add(this.label7); - this.groupBox2.Location = new System.Drawing.Point(6, 104); + this.groupBox2.Location = new System.Drawing.Point(6, 139); this.groupBox2.Name = "groupBox2"; this.groupBox2.Size = new System.Drawing.Size(272, 190); this.groupBox2.TabIndex = 1; @@ -569,13 +581,15 @@ namespace CodeImp.DoomBuilder.BuilderModes // // groupBox1 // + this.groupBox1.Controls.Add(this.scaletexturesonslopes); + this.groupBox1.Controls.Add(this.label18); this.groupBox1.Controls.Add(this.splitbehavior); this.groupBox1.Controls.Add(this.label10); this.groupBox1.Controls.Add(this.label1); this.groupBox1.Controls.Add(this.heightbysidedef); this.groupBox1.Location = new System.Drawing.Point(6, 6); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(657, 92); + this.groupBox1.Size = new System.Drawing.Size(657, 127); this.groupBox1.TabIndex = 0; this.groupBox1.TabStop = false; this.groupBox1.Text = " Behavior "; @@ -607,7 +621,7 @@ namespace CodeImp.DoomBuilder.BuilderModes // label1 // this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(9, 22); + this.label1.Location = new System.Drawing.Point(15, 22); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(308, 13); this.label1.TabIndex = 0; @@ -628,15 +642,28 @@ namespace CodeImp.DoomBuilder.BuilderModes this.heightbysidedef.Size = new System.Drawing.Size(309, 21); this.heightbysidedef.TabIndex = 0; // - // additivepaintselect + // label18 // - this.additivepaintselect.AutoSize = true; - this.additivepaintselect.Location = new System.Drawing.Point(13, 135); - this.additivepaintselect.Name = "additivepaintselect"; - this.additivepaintselect.Size = new System.Drawing.Size(233, 17); - this.additivepaintselect.TabIndex = 11; - this.additivepaintselect.Text = "Additive paint selecting without holding Shift"; - this.additivepaintselect.UseVisualStyleBackColor = true; + this.label18.AutoSize = true; + this.label18.Location = new System.Drawing.Point(133, 94); + this.label18.Name = "label18"; + this.label18.Size = new System.Drawing.Size(190, 13); + this.label18.TabIndex = 2; + this.label18.Text = "When auto-aligning textures on slopes:"; + this.label18.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // scaletexturesonslopes + // + this.scaletexturesonslopes.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.scaletexturesonslopes.FormattingEnabled = true; + this.scaletexturesonslopes.Items.AddRange(new object[] { + "Use a scale of 1 as base", + "Use current scale as base", + "Don\'t scale"}); + this.scaletexturesonslopes.Location = new System.Drawing.Point(342, 91); + this.scaletexturesonslopes.Name = "scaletexturesonslopes"; + this.scaletexturesonslopes.Size = new System.Drawing.Size(309, 21); + this.scaletexturesonslopes.TabIndex = 3; // // PreferencesForm // @@ -710,5 +737,7 @@ namespace CodeImp.DoomBuilder.BuilderModes private System.Windows.Forms.Label label16; private System.Windows.Forms.Label label17; private System.Windows.Forms.CheckBox additivepaintselect; + private System.Windows.Forms.ComboBox scaletexturesonslopes; + private System.Windows.Forms.Label label18; } } \ No newline at end of file diff --git a/Source/Plugins/BuilderModes/Interface/PreferencesForm.cs b/Source/Plugins/BuilderModes/Interface/PreferencesForm.cs index 762a6a76..38ff83de 100755 --- a/Source/Plugins/BuilderModes/Interface/PreferencesForm.cs +++ b/Source/Plugins/BuilderModes/Interface/PreferencesForm.cs @@ -64,6 +64,7 @@ namespace CodeImp.DoomBuilder.BuilderModes defaultbrightness.Text = General.Settings.DefaultBrightness.ToString(); //mxd defaultceilheight.Text = General.Settings.DefaultCeilingHeight.ToString();//mxd defaultfloorheight.Text = General.Settings.DefaultFloorHeight.ToString(); //mxd + scaletexturesonslopes.SelectedIndex = General.Settings.ReadPluginSetting("scaletexturesonslopes", 0); } #endregion @@ -91,8 +92,10 @@ namespace CodeImp.DoomBuilder.BuilderModes General.Settings.WritePluginSetting("autoaligntextureoffsetsoncreate", autoaligntexturesoncreate.Checked);//mxd General.Settings.WritePluginSetting("dontmovegeometryoutsidemapboundary", dontMoveGeometryOutsideBounds.Checked);//mxd General.Settings.WritePluginSetting("syncselection", syncSelection.Checked);//mxd + General.Settings.WritePluginSetting("scaletexturesonslopes", scaletexturesonslopes.SelectedIndex); General.Settings.SwitchViewModes = switchviewmodes.Checked; //mxd General.Settings.SplitLineBehavior = (SplitLineBehavior)splitbehavior.SelectedIndex;//mxd + //default sector values General.Settings.DefaultBrightness = General.Clamp(defaultbrightness.GetResult(192), 0, 255); diff --git a/Source/Plugins/BuilderModes/Resources/Actions.cfg b/Source/Plugins/BuilderModes/Resources/Actions.cfg index 1b2eaffb..f5328e9f 100755 --- a/Source/Plugins/BuilderModes/Resources/Actions.cfg +++ b/Source/Plugins/BuilderModes/Resources/Actions.cfg @@ -1405,3 +1405,25 @@ visualpaintselect disregardcontrol = true; disregardalt = true; } + +togglevisualslopepicking +{ + title = "Toggle Visual Slope Picking"; + category = "visual"; + description = "Toggles picking visual slope handles."; + allowkeys = true; + allowmouse = true; + allowscroll = false; + default = 65623; // Shift-W +} + +slopebetweenhandles +{ + title = "Slope Between Handles"; + category = "visual"; + description = "Slopes the selected floors and ceilings between the selected slope handles."; + allowkeys = true; + allowmouse = true; + allowscroll = false; + default = 131142; // Ctrl-F +} \ No newline at end of file diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySector.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySector.cs index 0de67de4..c4647c12 100755 --- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySector.cs +++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySector.cs @@ -298,6 +298,32 @@ namespace CodeImp.DoomBuilder.BuilderModes //update angle UniFields.SetFloat(Sector.Sector.Fields, (isFloor ? "rotationfloor" : "rotationceiling"), sourceAngle, 0f); + // Scale texture if it's a slope and the appropriate option is set + if (level.plane.Normal.z != 1.0f && BuilderPlug.Me.ScaleTexturesOnSlopes != 2) + { + Vector2D basescale = new Vector2D(1.0f, 1.0f); + + // User wants to use the current scale as a base? + if(BuilderPlug.Me.ScaleTexturesOnSlopes == 1) + { + basescale.x = scaleX; + basescale.y = scaleY; + } + + // Create a unit vector of the direction of the target line in 3D space + Vector3D targetlinevector = new Line3D(new Vector3D(targetLine.Start.Position, level.plane.GetZ(targetLine.Start.Position)), new Vector3D(targetLine.End.Position, level.plane.GetZ(targetLine.End.Position))).GetDelta().GetNormal(); + + // Get a perpendicular vector of the target line in 3D space. This is used to get the slope angle relative to the target line + Vector3D targetlineperpendicular = Vector3D.CrossProduct(targetlinevector, level.plane.Normal); + + if (alignx) + scaleX = Math.Abs(basescale.x * (1.0f / (float)Math.Cos(targetlinevector.GetAngleZ()))); + + if (aligny) + scaleY = Math.Abs(basescale.y * (1.0f / (float)Math.Cos(targetlineperpendicular.GetAngleZ()))); + + } + //set scale UniFields.SetFloat(Sector.Sector.Fields, (isFloor ? "xscalefloor" : "xscaleceiling"), scaleX, 1.0f); UniFields.SetFloat(Sector.Sector.Fields, (isFloor ? "yscalefloor" : "yscaleceiling"), scaleY, 1.0f); @@ -323,96 +349,6 @@ namespace CodeImp.DoomBuilder.BuilderModes Sector.UpdateSectorGeometry(false); } - //mxd - protected void AlignTextureToSlopeLine(Linedef slopeSource, float slopeAngle, bool isFront, bool alignx, bool aligny) - { - bool isFloor = (geometrytype == VisualGeometryType.FLOOR); - Sector.Sector.Fields.BeforeFieldsChange(); - float sourceAngle = (float)Math.Round(General.ClampAngle(isFront ? -Angle2D.RadToDeg(slopeSource.Angle) + 90 : -Angle2D.RadToDeg(slopeSource.Angle) - 90), 1); - - if(isFloor) - { - if((isFront && slopeSource.Front.Sector.FloorHeight > slopeSource.Back.Sector.FloorHeight) || - (!isFront && slopeSource.Front.Sector.FloorHeight < slopeSource.Back.Sector.FloorHeight)) - { - sourceAngle = General.ClampAngle(sourceAngle + 180); - } - } - else - { - if((isFront && slopeSource.Front.Sector.CeilHeight < slopeSource.Back.Sector.CeilHeight) || - (!isFront && slopeSource.Front.Sector.CeilHeight > slopeSource.Back.Sector.CeilHeight)) - { - sourceAngle = General.ClampAngle(sourceAngle + 180); - } - } - - //update angle - UniFields.SetFloat(Sector.Sector.Fields, (isFloor ? "rotationfloor" : "rotationceiling"), sourceAngle, 0f); - - //update scaleY - string xScaleKey = (isFloor ? "xscalefloor" : "xscaleceiling"); - string yScaleKey = (isFloor ? "yscalefloor" : "yscaleceiling"); - - float scaleX = Sector.Sector.Fields.GetValue(xScaleKey, 1.0f); - float scaleY; - - //set scale - if(aligny) - { - scaleY = (float)Math.Round(scaleX * (1 / (float)Math.Cos(slopeAngle)), 2); - UniFields.SetFloat(Sector.Sector.Fields, yScaleKey, scaleY, 1.0f); - } - else - { - scaleY = Sector.Sector.Fields.GetValue(yScaleKey, 1.0f); - } - - //update texture offsets - Vector2D offset; - if(isFloor) - { - if((isFront && slopeSource.Front.Sector.FloorHeight < slopeSource.Back.Sector.FloorHeight) || - (!isFront && slopeSource.Front.Sector.FloorHeight > slopeSource.Back.Sector.FloorHeight)) - { - offset = slopeSource.End.Position; - } - else - { - offset = slopeSource.Start.Position; - } - } - else - { - if((isFront && slopeSource.Front.Sector.CeilHeight > slopeSource.Back.Sector.CeilHeight) || - (!isFront && slopeSource.Front.Sector.CeilHeight < slopeSource.Back.Sector.CeilHeight)) - { - offset = slopeSource.End.Position; - } - else - { - offset = slopeSource.Start.Position; - } - } - - offset = offset.GetRotated(Angle2D.DegToRad(sourceAngle)); - - if(alignx) - { - if(Texture != null && Texture.IsImageLoaded) offset.x %= Texture.Width / scaleX; - UniFields.SetFloat(Sector.Sector.Fields, (isFloor ? "xpanningfloor" : "xpanningceiling"), (float)Math.Round(-offset.x), 0f); - } - - if(aligny) - { - if(Texture != null && Texture.IsImageLoaded) offset.y %= Texture.Height / scaleY; - UniFields.SetFloat(Sector.Sector.Fields, (isFloor ? "ypanningfloor" : "ypanningceiling"), (float)Math.Round(offset.y), 0f); - } - - //update geometry - Sector.UpdateSectorGeometry(false); - } - //mxd protected void ClearFields(IEnumerable keys, string undodescription, string resultdescription) { @@ -878,8 +814,16 @@ namespace CodeImp.DoomBuilder.BuilderModes } if(vs != null) vs.UpdateSectorGeometry(true); + + // Visual slope handles need to be updated, too + if (General.Map.UDMF) + { + if (mode.AllSlopeHandles.ContainsKey(level.sector)) + foreach (VisualSidedefSlope handle in mode.AllSlopeHandles[level.sector]) + handle.Changed = true; + } } - + // Sector brightness change public virtual void OnChangeTargetBrightness(bool up) { diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs index 612d3303..52e2cf61 100755 --- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs +++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs @@ -18,6 +18,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Windows.Forms; using CodeImp.DoomBuilder.BuilderModes.Interface; using CodeImp.DoomBuilder.Windows; @@ -392,6 +394,15 @@ namespace CodeImp.DoomBuilder.BuilderModes } } + if (General.Map.UDMF) + { + foreach (KeyValuePair> kvp in allslopehandles) + { + foreach (VisualSlope handle in kvp.Value) + if (handle.Selected) selectedobjects.Add((VisualSidedefSlope)handle); + } + } + //mxd UpdateSelectionInfo(); } @@ -411,7 +422,19 @@ namespace CodeImp.DoomBuilder.BuilderModes allsectors.Add(s, vs); //mxd return vs; } - + + internal VisualSlope CreateVisualSlopeHandle(SectorLevel level, Sidedef sd, bool up) + { + VisualSidedefSlope handle = new VisualSidedefSlope(this, level, sd, up); + + if (!allslopehandles.ContainsKey(sd.Sector)) + allslopehandles.Add(sd.Sector, new List()); + + allslopehandles[sd.Sector].Add(handle); + + return handle; + } + // This creates a visual thing protected override VisualThing CreateVisualThing(Thing t) { @@ -448,12 +471,32 @@ namespace CodeImp.DoomBuilder.BuilderModes // Should we update the info on panels? bool updateinfo = (newtarget.picked != target.picked); - + + if (updateinfo) + { + if (newtarget.picked is VisualSidedefSlope) + { + // Get the smart pivot handle for the targeted slope handle, so that it can be drawn + VisualSidedefSlope handle = VisualSidedefSlope.GetSmartPivotHandle((VisualSidedefSlope)newtarget.picked, this); + if (handle != null) + handle.SmartPivot = true; + } + else if(target.picked is VisualSidedefSlope) + { + + // Clear smart pivot handles, otherwise it will keep being displayed + foreach (KeyValuePair> kvp in allslopehandles) + foreach (VisualSidedefSlope checkhandle in kvp.Value) + checkhandle.SmartPivot = false; + } + } + // Apply new target target = newtarget; // Show target info - if(updateinfo) ShowTargetInfo(); + if (updateinfo) + ShowTargetInfo(); } // This shows the picked target information @@ -512,7 +555,15 @@ namespace CodeImp.DoomBuilder.BuilderModes if(vs.Value != null) { BaseVisualSector bvs = (BaseVisualSector)vs.Value; - if(bvs.Changed) bvs.Rebuild(); + if(bvs.Changed) + { + bvs.Rebuild(); + + // Also update slope handles + if (allslopehandles.ContainsKey(vs.Key)) + foreach (VisualSidedefSlope handle in allslopehandles[vs.Key]) + handle.Update(); + } } } @@ -1121,6 +1172,42 @@ namespace CodeImp.DoomBuilder.BuilderModes break; } } + + // Visual slope handles + foreach (KeyValuePair> kvp in allslopehandles) + { + foreach (VisualSlope handle in kvp.Value) + if (handle != null) + if (handle.Selected) RemoveSelectedObject((VisualSidedefSlope)handle); + + kvp.Value.Clear(); + } + allslopehandles.Clear(); + + if (General.Map.UDMF /* && General.Settings.ShowVisualSlopeHandles */) + { + foreach (Sector s in General.Map.Map.Sectors) + { + SectorData sectordata = GetSectorData(s); + + sectordata.Update(); + + foreach (Sidedef sidedef in s.Sidedefs) + { + VisualSlope handle = CreateVisualSlopeHandle(sectordata.Floor, sidedef, true); + handle = CreateVisualSlopeHandle(sectordata.Ceiling, sidedef, false); + + if (sectordata.ExtraFloors.Count > 0) + { + foreach (Effect3DFloor floor in sectordata.ExtraFloors) + { + handle = CreateVisualSlopeHandle(floor.Floor, sidedef, false); + handle = CreateVisualSlopeHandle(floor.Ceiling, sidedef, true); + } + } + } + } + } } #endregion @@ -1361,7 +1448,16 @@ namespace CodeImp.DoomBuilder.BuilderModes renderer.SetVisualVertices(verts); } - + + // Visual slope handles + List handles = new List(); + foreach (KeyValuePair> kvp in allslopehandles) + foreach (VisualSlope handle in kvp.Value) + if (handle.Selected || handle.Pivot || handle.SmartPivot || target.picked == handle) + handles.Add(handle); + + renderer.SetVisualSlopeHandles(handles); + // Done rendering geometry renderer.FinishGeometry(); @@ -1660,7 +1756,7 @@ namespace CodeImp.DoomBuilder.BuilderModes // Apply texture offsets public void ApplyTextureOffsetChange(int dx, int dy) { - List objs = GetSelectedObjects(false, true, false, false); + List objs = GetSelectedObjects(false, true, false, false, false); //mxd. Because Upper/Middle/Lower textures offsets should be threated separately in UDMF //MaxW. But they're not for Eternity, so this needs its own config setting @@ -1702,7 +1798,7 @@ namespace CodeImp.DoomBuilder.BuilderModes public void ApplyFlatOffsetChange(int dx, int dy) { HashSet donesectors = new HashSet(); - List objs = GetSelectedObjects(true, false, false, false); + List objs = GetSelectedObjects(true, false, false, false, false); foreach(IVisualEventReceiver i in objs) { BaseVisualGeometrySector bvs = (BaseVisualGeometrySector)i; @@ -1759,7 +1855,7 @@ namespace CodeImp.DoomBuilder.BuilderModes // Apply upper unpegged flag public void ApplyUpperUnpegged(bool set) { - List objs = GetSelectedObjects(false, true, false, false); + List objs = GetSelectedObjects(false, true, false, false, false); foreach(IVisualEventReceiver i in objs) { i.ApplyUpperUnpegged(set); @@ -1769,7 +1865,7 @@ namespace CodeImp.DoomBuilder.BuilderModes // Apply lower unpegged flag public void ApplyLowerUnpegged(bool set) { - List objs = GetSelectedObjects(false, true, false, false); + List objs = GetSelectedObjects(false, true, false, false, false); foreach(IVisualEventReceiver i in objs) { i.ApplyLowerUnpegged(set); @@ -1784,12 +1880,12 @@ namespace CodeImp.DoomBuilder.BuilderModes if(General.Map.Config.MixTexturesFlats) { // Apply on all compatible types - objs = GetSelectedObjects(true, true, false, false); + objs = GetSelectedObjects(true, true, false, false, false); } else { // We don't want to mix textures and flats, so apply only on the appropriate type - objs = GetSelectedObjects(flat, !flat, false, false); + objs = GetSelectedObjects(flat, !flat, false, false, false); } foreach(IVisualEventReceiver i in objs) @@ -1799,7 +1895,7 @@ namespace CodeImp.DoomBuilder.BuilderModes } // This returns all selected objects - internal List GetSelectedObjects(bool includesectors, bool includesidedefs, bool includethings, bool includevertices) + internal List GetSelectedObjects(bool includesectors, bool includesidedefs, bool includethings, bool includevertices, bool includeslopehandles) { List objs = new List(); foreach(IVisualEventReceiver i in selectedobjects) @@ -1808,6 +1904,7 @@ namespace CodeImp.DoomBuilder.BuilderModes else if(includesidedefs && (i is BaseVisualGeometrySidedef)) objs.Add(i); else if(includethings && (i is BaseVisualThing)) objs.Add(i); else if(includevertices && (i is BaseVisualVertex)) objs.Add(i); //mxd + else if (includeslopehandles && (i is VisualSlope)) objs.Add(i); // biwa } // Add highlight? @@ -1818,6 +1915,7 @@ namespace CodeImp.DoomBuilder.BuilderModes else if(includesidedefs && (i is BaseVisualGeometrySidedef)) objs.Add(i); else if(includethings && (i is BaseVisualThing)) objs.Add(i); else if(includevertices && (i is BaseVisualVertex)) objs.Add(i); //mxd + else if (includeslopehandles && (i is VisualSlope)) objs.Add(i); // biwa } return objs; @@ -2002,13 +2100,39 @@ namespace CodeImp.DoomBuilder.BuilderModes return verts; } + + // This returns all selected slope handles, no doubles + private List GetSelectedSlopeHandles() + { + HashSet added = new HashSet(); + List handles = new List(); + + foreach(IVisualEventReceiver i in selectedobjects) + { + VisualSidedefSlope handle = i as VisualSidedefSlope; + if(handle != null && !added.Contains(handle)) + { + handles.Add(handle); + added.Add(handle); + } + } + + // Add highlight? + if((selectedobjects.Count == 0) && (target.picked is VisualSidedefSlope)) + { + VisualSidedefSlope handle = (VisualSidedefSlope)target.picked; + if (!added.Contains(handle)) handles.Add(handle); + } + + return handles; + } // This returns the IVisualEventReceiver on which the action must be performed private IVisualEventReceiver GetTargetEventReceiver(bool targetonly) { if(target.picked != null) { - if(singleselection || target.picked.Selected || targetonly) + if(singleselection || target.picked.Selected || targetonly || target.picked is VisualSlope) { return (IVisualEventReceiver)target.picked; } @@ -2068,14 +2192,15 @@ namespace CodeImp.DoomBuilder.BuilderModes #region ================== Actions // [ZZ] I moved this out of ClearSelection because "cut selection" action needs this to only affect things. - private void ClearSelection(bool clearsectors, bool clearsidedefs, bool clearthings, bool clearvertices, bool displaystatus) + private void ClearSelection(bool clearsectors, bool clearsidedefs, bool clearthings, bool clearvertices, bool clearslopehandles, bool displaystatus) { selectedobjects.RemoveAll(obj => { return ((obj is BaseVisualGeometrySector && clearsectors) || (obj is BaseVisualGeometrySidedef && clearsidedefs) || (obj is BaseVisualThing && clearthings) || - (obj is BaseVisualVertex && clearvertices)); + (obj is BaseVisualVertex && clearvertices) || + (obj is VisualSlope && clearslopehandles)); }); // @@ -2126,8 +2251,24 @@ namespace CodeImp.DoomBuilder.BuilderModes } } - //mxd - if (displaystatus) + // biwa + if (clearslopehandles) + { + if (General.Map.UDMF) + { + foreach (KeyValuePair> kvp in allslopehandles) + { + foreach (VisualSidedefSlope handle in kvp.Value) + { + handle.Selected = false; + handle.Pivot = false; + } + } + } + } + + //mxd + if (displaystatus) { General.Interface.DisplayStatus(StatusType.Selection, string.Empty); } @@ -2136,7 +2277,7 @@ namespace CodeImp.DoomBuilder.BuilderModes [BeginAction("clearselection", BaseAction = true)] public void ClearSelection() { - ClearSelection(true, true, true, true, true); + ClearSelection(true, true, true, true, true, true); } [BeginAction("visualselect", BaseAction = true)] @@ -2196,8 +2337,11 @@ namespace CodeImp.DoomBuilder.BuilderModes public void RaiseSector8() { PreAction(UndoGroup.SectorHeightChange); - List objs = GetSelectedObjects(true, true, true, true); - foreach(IVisualEventReceiver i in objs) i.OnChangeTargetHeight(8); + List objs = GetSelectedObjects(true, true, true, true, true); + bool hasvisualslopehandles = objs.Any(o => o is VisualSlope); + foreach (IVisualEventReceiver i in objs) // If slope handles are selected only apply the action to them + if (!hasvisualslopehandles || (hasvisualslopehandles && i is VisualSlope)) + i.OnChangeTargetHeight(8); PostAction(); } @@ -2205,45 +2349,56 @@ namespace CodeImp.DoomBuilder.BuilderModes public void LowerSector8() { PreAction(UndoGroup.SectorHeightChange); - List objs = GetSelectedObjects(true, true, true, true); - foreach(IVisualEventReceiver i in objs) i.OnChangeTargetHeight(-8); + List objs = GetSelectedObjects(true, true, true, true, true); + bool hasvisualslopehandles = objs.Any(o => o is VisualSlope); + foreach (IVisualEventReceiver i in objs) // If slope handles are selected only apply the action to them + if (!hasvisualslopehandles || (hasvisualslopehandles && i is VisualSlope)) + i.OnChangeTargetHeight(-8); PostAction(); } [BeginAction("raisesector1")] public void RaiseSector1() { PreAction(UndoGroup.SectorHeightChange); - List objs = GetSelectedObjects(true, true, true, true); - foreach (IVisualEventReceiver i in objs) - i.OnChangeTargetHeight(1); - PostAction(); + List objs = GetSelectedObjects(true, true, true, true, true); + bool hasvisualslopehandles = objs.Any(o => o is VisualSlope); + foreach (IVisualEventReceiver i in objs) // If slope handles are selected only apply the action to them + if (!hasvisualslopehandles || (hasvisualslopehandles && i is VisualSlope)) + i.OnChangeTargetHeight(1); + PostAction(); } [BeginAction("lowersector1")] public void LowerSector1() { PreAction(UndoGroup.SectorHeightChange); - List objs = GetSelectedObjects(true, true, true, true); - foreach (IVisualEventReceiver i in objs) - i.OnChangeTargetHeight(-1); - PostAction(); + List objs = GetSelectedObjects(true, true, true, true, true); + bool hasvisualslopehandles = objs.Any(o => o is VisualSlope); + foreach (IVisualEventReceiver i in objs) // If slope handles are selected only apply the action to them + if (!hasvisualslopehandles || (hasvisualslopehandles && i is VisualSlope)) + i.OnChangeTargetHeight(-1); + PostAction(); } [BeginAction("raisesector128")] public void RaiseSector128() { PreAction(UndoGroup.SectorHeightChange); - List objs = GetSelectedObjects(true, true, true, true); - foreach (IVisualEventReceiver i in objs) - i.OnChangeTargetHeight(128); - PostAction(); + List objs = GetSelectedObjects(true, true, true, true, true); + bool hasvisualslopehandles = objs.Any(o => o is VisualSlope); + foreach (IVisualEventReceiver i in objs) // If slope handles are selected only apply the action to them + if (!hasvisualslopehandles || (hasvisualslopehandles && i is VisualSlope)) + i.OnChangeTargetHeight(128); + PostAction(); } [BeginAction("lowersector128")] public void LowerSector128() { PreAction(UndoGroup.SectorHeightChange); - List objs = GetSelectedObjects(true, true, true, true); - foreach (IVisualEventReceiver i in objs) - i.OnChangeTargetHeight(-128); - PostAction(); + List objs = GetSelectedObjects(true, true, true, true, true); + bool hasvisualslopehandles = objs.Any(o => o is VisualSlope); + foreach (IVisualEventReceiver i in objs) // If slope handles are selected only apply the action to them + if (!hasvisualslopehandles || (hasvisualslopehandles && i is VisualSlope)) + i.OnChangeTargetHeight(-128); + PostAction(); } @@ -2251,397 +2406,479 @@ namespace CodeImp.DoomBuilder.BuilderModes [BeginAction("raisesectortonearest")] public void RaiseSectorToNearest() { - Dictionary floors = new Dictionary(); - Dictionary ceilings = new Dictionary(); - List things = new List(); - bool withinSelection = General.Interface.CtrlState; + List selectedhandles = GetSelectedSlopeHandles(); - // Get selection - if(selectedobjects.Count == 0) + if (selectedhandles.Count > 0) { - if(target.picked is VisualFloor) + if (selectedhandles.Count > 1) { - VisualFloor vf = (VisualFloor)target.picked; - floors.Add(vf.Level.sector, vf); - } - else if(target.picked is VisualCeiling) - { - VisualCeiling vc = (VisualCeiling)target.picked; - ceilings.Add(vc.Level.sector, vc); - } - else if(target.picked is BaseVisualThing) - { - things.Add((BaseVisualThing)target.picked); + General.Interface.DisplayStatus(StatusType.Warning, "Can only raise to nearest when one visual slope handle is selected"); + return; } - } - else - { - foreach(IVisualEventReceiver i in selectedobjects) + + int startheight = (int)Math.Round(selectedhandles[0].GetCenterPoint().z); + int targetheight = int.MaxValue; + + foreach (KeyValuePair> kvp in allslopehandles) { - if(i is VisualFloor) + foreach (VisualSidedefSlope handle in kvp.Value) { - VisualFloor vf = (VisualFloor)i; - floors.Add(vf.Level.sector, vf); - } - else if(i is VisualCeiling) - { - VisualCeiling vc = (VisualCeiling)i; - ceilings.Add(vc.Level.sector, vc); - } - else if(i is BaseVisualThing) - { - things.Add((BaseVisualThing)i); + if (handle != selectedhandles[0] && handle.Sidedef.Line == selectedhandles[0].Sidedef.Line) + { + int z = (int)Math.Round(handle.GetCenterPoint().z); + + if (z > startheight && z < targetheight) + targetheight = z; + } } } - } - // Check what we have - if(floors.Count + ceilings.Count == 0 && (things.Count == 0 || !General.Map.FormatInterface.HasThingHeight)) - { - General.Interface.DisplayStatus(StatusType.Warning, "No suitable objects found!"); - return; - } - - if(withinSelection) - { - string s = string.Empty; - - if(floors.Count == 1) s = "floors"; - - if(ceilings.Count == 1) + if (targetheight != int.MaxValue) { - if(!string.IsNullOrEmpty(s)) s += " and "; - s += "ceilings"; + PreAction(UndoGroup.SectorHeightChange); + selectedhandles[0].OnChangeTargetHeight(targetheight - startheight); + PostAction(); + } + else + { + General.Interface.DisplayStatus(StatusType.Warning, "Can't raise: already at the highest level"); + } + } + else + { + Dictionary floors = new Dictionary(); + Dictionary ceilings = new Dictionary(); + List things = new List(); + bool withinSelection = General.Interface.CtrlState; + + // Get selection + if (selectedobjects.Count == 0) + { + if (target.picked is VisualFloor) + { + VisualFloor vf = (VisualFloor)target.picked; + floors[vf.Level.sector] = vf; + } + else if (target.picked is VisualCeiling) + { + VisualCeiling vc = (VisualCeiling)target.picked; + ceilings[vc.Level.sector] = vc; + } + else if (target.picked is BaseVisualThing) + { + things.Add((BaseVisualThing)target.picked); + } + } + else + { + foreach (IVisualEventReceiver i in selectedobjects) + { + if (i is VisualFloor) + { + VisualFloor vf = (VisualFloor)i; + floors[vf.Level.sector] = vf; + } + else if (i is VisualCeiling) + { + VisualCeiling vc = (VisualCeiling)i; + ceilings[vc.Level.sector] = vc; + } + else if (i is BaseVisualThing) + { + things.Add((BaseVisualThing)i); + } + } } - if(!string.IsNullOrEmpty(s)) + // Check what we have + if (floors.Count + ceilings.Count == 0 && (things.Count == 0 || !General.Map.FormatInterface.HasThingHeight)) { - General.Interface.DisplayStatus(StatusType.Warning, "Can't do: at least 2 selected " + s + " are required!"); + General.Interface.DisplayStatus(StatusType.Warning, "No suitable objects found!"); return; } - } - // Process floors... - int maxSelectedHeight = int.MinValue; - int minSelectedCeilingHeight = int.MaxValue; - int targetCeilingHeight = int.MaxValue; - - // Get highest ceiling height from selection - foreach(KeyValuePair group in ceilings) - { - if(group.Key.CeilHeight > maxSelectedHeight) maxSelectedHeight = group.Key.CeilHeight; - } - - if(withinSelection) - { - // We are raising, so we don't need to check anything - targetCeilingHeight = maxSelectedHeight; - } - else - { - // Get next higher floor or ceiling from surrounding unselected sectors - foreach(Sector s in BuilderModesTools.GetSectorsAround(this, ceilings.Keys)) + if (withinSelection) { - if(s.FloorHeight < targetCeilingHeight && s.FloorHeight > maxSelectedHeight) - targetCeilingHeight = s.FloorHeight; - else if(s.CeilHeight < targetCeilingHeight && s.CeilHeight > maxSelectedHeight) - targetCeilingHeight = s.CeilHeight; + string s = string.Empty; + + if (floors.Count == 1) s = "floors"; + + if (ceilings.Count == 1) + { + if (!string.IsNullOrEmpty(s)) s += " and "; + s += "ceilings"; + } + + if (!string.IsNullOrEmpty(s)) + { + General.Interface.DisplayStatus(StatusType.Warning, "Can't do: at least 2 selected " + s + " are required!"); + return; + } } - } - // Ceilings... - maxSelectedHeight = int.MinValue; - int targetFloorHeight = int.MaxValue; + // Process floors... + int maxSelectedHeight = int.MinValue; + int minSelectedCeilingHeight = int.MaxValue; + int targetCeilingHeight = int.MaxValue; - // Get maximum floor and minimum ceiling heights from selection - foreach(KeyValuePair group in floors) - { - if(group.Key.FloorHeight > maxSelectedHeight) maxSelectedHeight = group.Key.FloorHeight; - if(group.Key.CeilHeight < minSelectedCeilingHeight) minSelectedCeilingHeight = group.Key.CeilHeight; - } - - if(withinSelection) - { - // Check heights - if(minSelectedCeilingHeight < maxSelectedHeight) + // Get highest ceiling height from selection + foreach (KeyValuePair group in ceilings) { - General.Interface.DisplayStatus(StatusType.Warning, "Can't do: lowest ceiling is lower than highest floor!"); + if (group.Key.CeilHeight > maxSelectedHeight) maxSelectedHeight = group.Key.CeilHeight; + } + + if (withinSelection) + { + // We are raising, so we don't need to check anything + targetCeilingHeight = maxSelectedHeight; + } + else + { + // Get next higher floor or ceiling from surrounding unselected sectors + foreach (Sector s in BuilderModesTools.GetSectorsAround(this, ceilings.Keys)) + { + if (s.FloorHeight < targetCeilingHeight && s.FloorHeight > maxSelectedHeight) + targetCeilingHeight = s.FloorHeight; + else if (s.CeilHeight < targetCeilingHeight && s.CeilHeight > maxSelectedHeight) + targetCeilingHeight = s.CeilHeight; + } + } + + // Ceilings... + maxSelectedHeight = int.MinValue; + int targetFloorHeight = int.MaxValue; + + // Get maximum floor and minimum ceiling heights from selection + foreach (KeyValuePair group in floors) + { + if (group.Key.FloorHeight > maxSelectedHeight) maxSelectedHeight = group.Key.FloorHeight; + if (group.Key.CeilHeight < minSelectedCeilingHeight) minSelectedCeilingHeight = group.Key.CeilHeight; + } + + if (withinSelection) + { + // Check heights + if (minSelectedCeilingHeight < maxSelectedHeight) + { + General.Interface.DisplayStatus(StatusType.Warning, "Can't do: lowest ceiling is lower than highest floor!"); + return; + } + targetFloorHeight = maxSelectedHeight; + } + else + { + // Get next higher floor or ceiling from surrounding unselected sectors + foreach (Sector s in BuilderModesTools.GetSectorsAround(this, floors.Keys)) + { + if (s.FloorHeight > maxSelectedHeight && s.FloorHeight < targetFloorHeight && s.FloorHeight <= minSelectedCeilingHeight) + targetFloorHeight = s.FloorHeight; + else if (s.CeilHeight > maxSelectedHeight && s.CeilHeight < targetFloorHeight && s.CeilHeight <= minSelectedCeilingHeight) + targetFloorHeight = s.CeilHeight; + } + } + + //CHECK VALUES + string alignFailDescription = string.Empty; + + if (floors.Count > 0 && targetFloorHeight == int.MaxValue) + { + // Raise to lowest ceiling? + if (!withinSelection && minSelectedCeilingHeight > maxSelectedHeight) + { + targetFloorHeight = minSelectedCeilingHeight; + } + else + { + alignFailDescription = floors.Count > 1 ? "floors" : "floor"; + } + } + + if (ceilings.Count > 0 && targetCeilingHeight == int.MaxValue) + { + if (!string.IsNullOrEmpty(alignFailDescription)) alignFailDescription += " and "; + alignFailDescription += ceilings.Count > 1 ? "ceilings" : "ceiling"; + } + + if (!string.IsNullOrEmpty(alignFailDescription)) + { + General.Interface.DisplayStatus(StatusType.Warning, "Unable to align selected " + alignFailDescription + "!"); return; - } - targetFloorHeight = maxSelectedHeight; - } - else - { - // Get next higher floor or ceiling from surrounding unselected sectors - foreach(Sector s in BuilderModesTools.GetSectorsAround(this, floors.Keys)) - { - if(s.FloorHeight > maxSelectedHeight && s.FloorHeight < targetFloorHeight && s.FloorHeight <= minSelectedCeilingHeight) - targetFloorHeight = s.FloorHeight; - else if(s.CeilHeight > maxSelectedHeight && s.CeilHeight < targetFloorHeight && s.CeilHeight <= minSelectedCeilingHeight) - targetFloorHeight = s.CeilHeight; } - } - //CHECK VALUES - string alignFailDescription = string.Empty; + //APPLY VALUES + PreAction(UndoGroup.SectorHeightChange); - if(floors.Count > 0 && targetFloorHeight == int.MaxValue) - { - // Raise to lowest ceiling? - if(!withinSelection && minSelectedCeilingHeight > maxSelectedHeight) + // Change floors heights + if (floors.Count > 0) { - targetFloorHeight = minSelectedCeilingHeight; - } - else - { - alignFailDescription = floors.Count > 1 ? "floors" : "floor"; + foreach (KeyValuePair group in floors) + { + if (targetFloorHeight != group.Key.FloorHeight) + group.Value.OnChangeTargetHeight(targetFloorHeight - group.Key.FloorHeight); + } } - } - if(ceilings.Count > 0 && targetCeilingHeight == int.MaxValue) - { - if(!string.IsNullOrEmpty(alignFailDescription)) alignFailDescription += " and "; - alignFailDescription += ceilings.Count > 1 ? "ceilings" : "ceiling"; - } - - if(!string.IsNullOrEmpty(alignFailDescription)) - { - General.Interface.DisplayStatus(StatusType.Warning, "Unable to align selected " + alignFailDescription + "!"); - return; - } - - //APPLY VALUES - PreAction(UndoGroup.SectorHeightChange); - - // Change floors heights - if(floors.Count > 0) - { - foreach(KeyValuePair group in floors) + // Change ceilings heights + if (ceilings.Count > 0) { - if(targetFloorHeight != group.Key.FloorHeight) - group.Value.OnChangeTargetHeight(targetFloorHeight - group.Key.FloorHeight); + foreach (KeyValuePair group in ceilings) + { + if (targetCeilingHeight != group.Key.CeilHeight) + group.Value.OnChangeTargetHeight(targetCeilingHeight - group.Key.CeilHeight); + } } - } - // Change ceilings heights - if(ceilings.Count > 0) - { - foreach(KeyValuePair group in ceilings) + // Change things heights. Align to higher 3d floor or actual ceiling. + if (General.Map.FormatInterface.HasThingHeight) { - if(targetCeilingHeight != group.Key.CeilHeight) - group.Value.OnChangeTargetHeight(targetCeilingHeight - group.Key.CeilHeight); + foreach (BaseVisualThing vt in things) + { + if (vt.Thing.Sector == null) continue; + SectorData sd = GetSectorData(vt.Thing.Sector); + vt.OnMove(new Vector3D(vt.Thing.Position, BuilderModesTools.GetHigherThingZ(this, sd, vt))); + } } - } - // Change things heights. Align to higher 3d floor or actual ceiling. - if(General.Map.FormatInterface.HasThingHeight) - { - foreach(BaseVisualThing vt in things) - { - if(vt.Thing.Sector == null) continue; - SectorData sd = GetSectorData(vt.Thing.Sector); - vt.OnMove(new Vector3D(vt.Thing.Position, BuilderModesTools.GetHigherThingZ(this, sd, vt))); - } + PostAction(); } - - PostAction(); } //mxd [BeginAction("lowersectortonearest")] public void LowerSectorToNearest() { - Dictionary floors = new Dictionary(); - Dictionary ceilings = new Dictionary(); - List things = new List(); - bool withinSelection = General.Interface.CtrlState; + List selectedhandles = GetSelectedSlopeHandles(); - // Get selection - if(selectedobjects.Count == 0) + if (selectedhandles.Count > 0) { - if(target.picked is VisualFloor) + if (selectedhandles.Count > 1) { - VisualFloor vf = (VisualFloor)target.picked; - floors.Add(vf.Level.sector, vf); - } - else if(target.picked is VisualCeiling) + General.Interface.DisplayStatus(StatusType.Warning, "Can only lower to nearest when one visual slope handle is selected"); + return; + } + + int startheight = (int)Math.Round(selectedhandles[0].GetCenterPoint().z); + int targetheight = int.MinValue; + + foreach (KeyValuePair> kvp in allslopehandles) { - VisualCeiling vc = (VisualCeiling)target.picked; - ceilings.Add(vc.Level.sector, vc); - } - else if(target.picked is BaseVisualThing) + foreach (VisualSidedefSlope handle in kvp.Value) + { + if (handle != selectedhandles[0] && handle.Sidedef.Line == selectedhandles[0].Sidedef.Line) + { + int z = (int)Math.Round(handle.GetCenterPoint().z); + + if (z < startheight && z > targetheight) + targetheight = z; + } + } + } + + if (targetheight != int.MinValue) { - things.Add((BaseVisualThing)target.picked); + PreAction(UndoGroup.SectorHeightChange); + selectedhandles[0].OnChangeTargetHeight(-(startheight - targetheight)); + PostAction(); + } + else + { + General.Interface.DisplayStatus(StatusType.Warning, "Can't lower: already at the lowest level"); } } else { - foreach(IVisualEventReceiver i in selectedobjects) + Dictionary floors = new Dictionary(); + Dictionary ceilings = new Dictionary(); + List things = new List(); + bool withinSelection = General.Interface.CtrlState; + + // Get selection + if (selectedobjects.Count == 0) { - if(i is VisualFloor) + if (target.picked is VisualFloor) { - VisualFloor vf = (VisualFloor)i; - floors.Add(vf.Level.sector, vf); - } - else if(i is VisualCeiling) + VisualFloor vf = (VisualFloor)target.picked; + floors[vf.Level.sector] = vf; + } + else if (target.picked is VisualCeiling) { - VisualCeiling vc = (VisualCeiling)i; - ceilings.Add(vc.Level.sector, vc); - } - else if(i is BaseVisualThing) + VisualCeiling vc = (VisualCeiling)target.picked; + ceilings[vc.Level.sector] = vc; + } + else if (target.picked is BaseVisualThing) { - things.Add((BaseVisualThing)i); + things.Add((BaseVisualThing)target.picked); } } - } - - // Check what we have - if(floors.Count + ceilings.Count == 0 && (things.Count == 0 || !General.Map.FormatInterface.HasThingHeight)) - { - General.Interface.DisplayStatus(StatusType.Warning, "No suitable objects found!"); - return; - } - - if(withinSelection) - { - string s = string.Empty; - - if(floors.Count == 1) s = "floors"; - - if(ceilings.Count == 1) + else { - if(!string.IsNullOrEmpty(s)) s += " and "; - s += "ceilings"; + foreach (IVisualEventReceiver i in selectedobjects) + { + if (i is VisualFloor) + { + VisualFloor vf = (VisualFloor)i; + floors[vf.Level.sector] = vf; + } + else if (i is VisualCeiling) + { + VisualCeiling vc = (VisualCeiling)i; + ceilings[vc.Level.sector] = vc; + } + else if (i is BaseVisualThing) + { + things.Add((BaseVisualThing)i); + } + } } - if(!string.IsNullOrEmpty(s)) + // Check what we have + if (floors.Count + ceilings.Count == 0 && (things.Count == 0 || !General.Map.FormatInterface.HasThingHeight)) { - General.Interface.DisplayStatus(StatusType.Warning, "Can't do: at least 2 selected " + s + " are required!"); + General.Interface.DisplayStatus(StatusType.Warning, "No suitable objects found!"); return; } - } - // Process floors... - int minSelectedHeight = int.MaxValue; - int targetFloorHeight = int.MinValue; - - // Get minimum floor height from selection - foreach(KeyValuePair group in floors) - { - if(group.Key.FloorHeight < minSelectedHeight) minSelectedHeight = group.Key.FloorHeight; - } - - if(withinSelection) - { - // We are lowering, so we don't need to check anything - targetFloorHeight = minSelectedHeight; - } - else - { - // Get next lower ceiling or floor from surrounding unselected sectors - foreach(Sector s in BuilderModesTools.GetSectorsAround(this, floors.Keys)) + if (withinSelection) { - if(s.CeilHeight > targetFloorHeight && s.CeilHeight < minSelectedHeight) - targetFloorHeight = s.CeilHeight; - else if(s.FloorHeight > targetFloorHeight && s.FloorHeight < minSelectedHeight) - targetFloorHeight = s.FloorHeight; + string s = string.Empty; + + if (floors.Count == 1) s = "floors"; + + if (ceilings.Count == 1) + { + if (!string.IsNullOrEmpty(s)) s += " and "; + s += "ceilings"; + } + + if (!string.IsNullOrEmpty(s)) + { + General.Interface.DisplayStatus(StatusType.Warning, "Can't do: at least 2 selected " + s + " are required!"); + return; + } } - } - // Ceilings... - minSelectedHeight = int.MaxValue; - int maxSelectedFloorHeight = int.MinValue; - int targetCeilingHeight = int.MinValue; + // Process floors... + int minSelectedHeight = int.MaxValue; + int targetFloorHeight = int.MinValue; - // Get minimum ceiling and maximum floor heights from selection - foreach(KeyValuePair group in ceilings) - { - if(group.Key.CeilHeight < minSelectedHeight) minSelectedHeight = group.Key.CeilHeight; - if(group.Key.FloorHeight > maxSelectedFloorHeight) maxSelectedFloorHeight = group.Key.FloorHeight; - } - - if(withinSelection) - { - if(minSelectedHeight < maxSelectedFloorHeight) + // Get minimum floor height from selection + foreach (KeyValuePair group in floors) { - General.Interface.DisplayStatus(StatusType.Warning, "Can't do: lowest ceiling is lower than highest floor!"); + if (group.Key.FloorHeight < minSelectedHeight) minSelectedHeight = group.Key.FloorHeight; + } + + if (withinSelection) + { + // We are lowering, so we don't need to check anything + targetFloorHeight = minSelectedHeight; + } + else + { + // Get next lower ceiling or floor from surrounding unselected sectors + foreach (Sector s in BuilderModesTools.GetSectorsAround(this, floors.Keys)) + { + if (s.CeilHeight > targetFloorHeight && s.CeilHeight < minSelectedHeight) + targetFloorHeight = s.CeilHeight; + else if (s.FloorHeight > targetFloorHeight && s.FloorHeight < minSelectedHeight) + targetFloorHeight = s.FloorHeight; + } + } + + // Ceilings... + minSelectedHeight = int.MaxValue; + int maxSelectedFloorHeight = int.MinValue; + int targetCeilingHeight = int.MinValue; + + // Get minimum ceiling and maximum floor heights from selection + foreach (KeyValuePair group in ceilings) + { + if (group.Key.CeilHeight < minSelectedHeight) minSelectedHeight = group.Key.CeilHeight; + if (group.Key.FloorHeight > maxSelectedFloorHeight) maxSelectedFloorHeight = group.Key.FloorHeight; + } + + if (withinSelection) + { + if (minSelectedHeight < maxSelectedFloorHeight) + { + General.Interface.DisplayStatus(StatusType.Warning, "Can't do: lowest ceiling is lower than highest floor!"); + return; + } + targetCeilingHeight = minSelectedHeight; + } + else + { + // Get next lower ceiling or floor from surrounding unselected sectors + foreach (Sector s in BuilderModesTools.GetSectorsAround(this, ceilings.Keys)) + { + if (s.CeilHeight > targetCeilingHeight && s.CeilHeight < minSelectedHeight && s.CeilHeight >= maxSelectedFloorHeight) + targetCeilingHeight = s.CeilHeight; + else if (s.FloorHeight > targetCeilingHeight && s.FloorHeight < minSelectedHeight && s.FloorHeight >= maxSelectedFloorHeight) + targetCeilingHeight = s.FloorHeight; + } + } + + //CHECK VALUES: + string alignFailDescription = string.Empty; + + if (floors.Count > 0 && targetFloorHeight == int.MinValue) + alignFailDescription = floors.Count > 1 ? "floors" : "floor"; + + if (ceilings.Count > 0 && targetCeilingHeight == int.MinValue) + { + // Drop to highest floor? + if (!withinSelection && maxSelectedFloorHeight < minSelectedHeight) + { + targetCeilingHeight = maxSelectedFloorHeight; + } + else + { + if (!string.IsNullOrEmpty(alignFailDescription)) alignFailDescription += " and "; + alignFailDescription += ceilings.Count > 1 ? "ceilings" : "ceiling"; + } + } + + if (!string.IsNullOrEmpty(alignFailDescription)) + { + General.Interface.DisplayStatus(StatusType.Warning, "Unable to align selected " + alignFailDescription + "!"); return; - } - targetCeilingHeight = minSelectedHeight; - } - else - { - // Get next lower ceiling or floor from surrounding unselected sectors - foreach(Sector s in BuilderModesTools.GetSectorsAround(this, ceilings.Keys)) - { - if(s.CeilHeight > targetCeilingHeight && s.CeilHeight < minSelectedHeight && s.CeilHeight >= maxSelectedFloorHeight) - targetCeilingHeight = s.CeilHeight; - else if(s.FloorHeight > targetCeilingHeight && s.FloorHeight < minSelectedHeight && s.FloorHeight >= maxSelectedFloorHeight) - targetCeilingHeight = s.FloorHeight; } - } - //CHECK VALUES: - string alignFailDescription = string.Empty; + //APPLY VALUES: + PreAction(UndoGroup.SectorHeightChange); - if(floors.Count > 0 && targetFloorHeight == int.MinValue) - alignFailDescription = floors.Count > 1 ? "floors" : "floor"; - - if(ceilings.Count > 0 && targetCeilingHeight == int.MinValue) - { - // Drop to highest floor? - if(!withinSelection && maxSelectedFloorHeight < minSelectedHeight) + // Change floor height + if (floors.Count > 0) { - targetCeilingHeight = maxSelectedFloorHeight; - } - else - { - if(!string.IsNullOrEmpty(alignFailDescription)) alignFailDescription += " and "; - alignFailDescription += ceilings.Count > 1 ? "ceilings" : "ceiling"; + foreach (KeyValuePair group in floors) + { + if (targetFloorHeight != group.Key.FloorHeight) + group.Value.OnChangeTargetHeight(targetFloorHeight - group.Key.FloorHeight); + } } - } - if(!string.IsNullOrEmpty(alignFailDescription)) - { - General.Interface.DisplayStatus(StatusType.Warning, "Unable to align selected " + alignFailDescription + "!"); - return; - } - - //APPLY VALUES: - PreAction(UndoGroup.SectorHeightChange); - - // Change floor height - if(floors.Count > 0) - { - foreach(KeyValuePair group in floors) + // Change ceiling height + if (ceilings.Count > 0) { - if(targetFloorHeight != group.Key.FloorHeight) - group.Value.OnChangeTargetHeight(targetFloorHeight - group.Key.FloorHeight); + foreach (KeyValuePair group in ceilings) + { + if (targetCeilingHeight != group.Key.CeilHeight) + group.Value.OnChangeTargetHeight(targetCeilingHeight - group.Key.CeilHeight); + } } - } - // Change ceiling height - if(ceilings.Count > 0) - { - foreach(KeyValuePair group in ceilings) + // Change things height. Drop to lower 3d floor or to actual sector's floor. + if (General.Map.FormatInterface.HasThingHeight) { - if(targetCeilingHeight != group.Key.CeilHeight) - group.Value.OnChangeTargetHeight(targetCeilingHeight - group.Key.CeilHeight); + foreach (BaseVisualThing vt in things) + { + if (vt.Thing.Sector == null) continue; + SectorData sd = GetSectorData(vt.Thing.Sector); + vt.OnMove(new Vector3D(vt.Thing.Position, BuilderModesTools.GetLowerThingZ(this, sd, vt))); + } } - } - // Change things height. Drop to lower 3d floor or to actual sector's floor. - if(General.Map.FormatInterface.HasThingHeight) - { - foreach(BaseVisualThing vt in things) - { - if(vt.Thing.Sector == null) continue; - SectorData sd = GetSectorData(vt.Thing.Sector); - vt.OnMove(new Vector3D(vt.Thing.Position, BuilderModesTools.GetLowerThingZ(this, sd, vt))); - } + PostAction(); } - - PostAction(); } //mxd @@ -2800,7 +3037,7 @@ namespace CodeImp.DoomBuilder.BuilderModes public void RaiseBrightness8() { PreAction(UndoGroup.SectorBrightnessChange); - List objs = GetSelectedObjects(true, true, false, false); + List objs = GetSelectedObjects(true, true, false, false, false); foreach(IVisualEventReceiver i in objs) i.OnChangeTargetBrightness(true); PostAction(); } @@ -2809,7 +3046,7 @@ namespace CodeImp.DoomBuilder.BuilderModes public void LowerBrightness8() { PreAction(UndoGroup.SectorBrightnessChange); - List objs = GetSelectedObjects(true, true, false, false); + List objs = GetSelectedObjects(true, true, false, false, false); foreach(IVisualEventReceiver i in objs) i.OnChangeTargetBrightness(false); PostAction(); } @@ -2831,7 +3068,7 @@ namespace CodeImp.DoomBuilder.BuilderModes private void MoveTextureByOffset(int ox, int oy) { PreAction(UndoGroup.TextureOffsetChange); - IEnumerable objs = RemoveDuplicateSidedefs(GetSelectedObjects(true, true, false, false)); + IEnumerable objs = RemoveDuplicateSidedefs(GetSelectedObjects(true, true, false, false, false)); foreach(IVisualEventReceiver i in objs) i.OnChangeTextureOffset(ox, oy, true); PostAction(); } @@ -2848,7 +3085,7 @@ namespace CodeImp.DoomBuilder.BuilderModes private void ScaleTexture(int incrementx, int incrementy) { PreAction(UndoGroup.TextureScaleChange); - List objs = GetSelectedObjects(true, true, true, false); + List objs = GetSelectedObjects(true, true, true, false, false); foreach(IVisualEventReceiver i in objs) i.OnChangeScale(incrementx, incrementy); PostAction(); } @@ -2878,7 +3115,7 @@ namespace CodeImp.DoomBuilder.BuilderModes public void TexturePaste() { PreAction(UndoGroup.None); - List objs = GetSelectedObjects(true, true, false, false); + List objs = GetSelectedObjects(true, true, false, false, false); foreach(IVisualEventReceiver i in objs) i.OnPasteTexture(); PostAction(); } @@ -2980,7 +3217,7 @@ namespace CodeImp.DoomBuilder.BuilderModes General.Map.Map.ClearMarkedSidedefs(false); //get selection - List objs = GetSelectedObjects(false, true, false, false); + List objs = GetSelectedObjects(false, true, false, false, false); //align foreach(IVisualEventReceiver i in objs) @@ -3016,7 +3253,7 @@ namespace CodeImp.DoomBuilder.BuilderModes PreAction(UndoGroup.None); // Get selection - List objs = GetSelectedObjects(false, true, false, false); + List objs = GetSelectedObjects(false, true, false, false, false); List sides = new List(); foreach(IVisualEventReceiver i in objs) { @@ -3068,7 +3305,7 @@ namespace CodeImp.DoomBuilder.BuilderModes public void ResetTexture() { PreAction(UndoGroup.None); - List objs = GetSelectedObjects(true, true, true, false); + List objs = GetSelectedObjects(true, true, true, false, false); foreach(IVisualEventReceiver i in objs) i.OnResetTextureOffset(); PostAction(); } @@ -3077,7 +3314,7 @@ namespace CodeImp.DoomBuilder.BuilderModes public void ResetLocalOffsets() { PreAction(UndoGroup.None); - List objs = GetSelectedObjects(true, true, true, false); + List objs = GetSelectedObjects(true, true, true, false, false); foreach(IVisualEventReceiver i in objs) i.OnResetLocalTextureOffset(); PostAction(); } @@ -3102,7 +3339,7 @@ namespace CodeImp.DoomBuilder.BuilderModes public void TexturePasteOffsets() { PreAction(UndoGroup.None); - List objs = GetSelectedObjects(true, true, false, false); + List objs = GetSelectedObjects(true, true, false, false, false); foreach(IVisualEventReceiver i in objs) i.OnPasteTextureOffsets(); PostAction(); } @@ -3119,7 +3356,7 @@ namespace CodeImp.DoomBuilder.BuilderModes public void PasteProperties() { PreAction(UndoGroup.None); - List objs = GetSelectedObjects(true, true, true, true); + List objs = GetSelectedObjects(true, true, true, true, false); foreach(IVisualEventReceiver i in objs) i.OnPasteProperties(false); PostAction(); } @@ -3134,7 +3371,7 @@ namespace CodeImp.DoomBuilder.BuilderModes var selection = new List(); // Sectors selected? - var obj = GetSelectedObjects(true, false, false, false); + var obj = GetSelectedObjects(true, false, false, false, false); if(obj.Count > 0) { targettypes.Add(MapElementType.SECTOR); @@ -3153,7 +3390,7 @@ namespace CodeImp.DoomBuilder.BuilderModes } // Sidedefs selected? - obj = GetSelectedObjects(false, true, false, false); + obj = GetSelectedObjects(false, true, false, false, false); if(obj.Count > 0) { targettypes.Add(MapElementType.SIDEDEF); @@ -3172,7 +3409,7 @@ namespace CodeImp.DoomBuilder.BuilderModes } // Things selected? - obj = GetSelectedObjects(false, false, true, false); + obj = GetSelectedObjects(false, false, true, false, false); if(obj.Count > 0) { targettypes.Add(MapElementType.THING); @@ -3191,7 +3428,7 @@ namespace CodeImp.DoomBuilder.BuilderModes } // Vertices selected? - obj = GetSelectedObjects(false, false, false, true); + obj = GetSelectedObjects(false, false, false, true, false); if(obj.Count > 0) { targettypes.Add(MapElementType.VERTEX); @@ -3267,7 +3504,7 @@ namespace CodeImp.DoomBuilder.BuilderModes public void Delete() { PreAction(UndoGroup.None); - List objs = GetSelectedObjects(true, true, true, true); + List objs = GetSelectedObjects(true, true, true, true, false); foreach (IVisualEventReceiver i in objs) { if (i is BaseVisualThing) @@ -3283,7 +3520,7 @@ namespace CodeImp.DoomBuilder.BuilderModes [BeginAction("copyselection", BaseAction = true)] public void CopySelection() { - List objs = GetSelectedObjects(false, false, true, false); + List objs = GetSelectedObjects(false, false, true, false, false); if(objs.Count == 0) return; copybuffer.Clear(); @@ -3308,7 +3545,7 @@ namespace CodeImp.DoomBuilder.BuilderModes CreateUndo("Cut " + rest); General.Interface.DisplayStatus(StatusType.Info, "Cut " + rest); - List objs = GetSelectedObjects(false, false, true, false); + List objs = GetSelectedObjects(false, false, true, false, false); foreach(IVisualEventReceiver i in objs) { BaseVisualThing thing = (BaseVisualThing)i; @@ -3322,7 +3559,7 @@ namespace CodeImp.DoomBuilder.BuilderModes General.Map.ThingsFilter.Update(); // [ZZ] Clear selected things. - ClearSelection(false, false, true, false, false); + ClearSelection(false, false, true, false, false, false); // Update event lines renderer.SetEventLines(LinksCollector.GetHelperShapes(General.Map.ThingsFilter.VisibleThings, blockmap)); @@ -3398,7 +3635,7 @@ namespace CodeImp.DoomBuilder.BuilderModes { PreAction(UndoGroup.ThingAngleChange); - List selection = GetSelectedObjects(true, false, true, false); + List selection = GetSelectedObjects(true, false, true, false, false); if(selection.Count == 0) return; foreach(IVisualEventReceiver obj in selection) @@ -3460,7 +3697,7 @@ namespace CodeImp.DoomBuilder.BuilderModes { PreAction(UndoGroup.ThingPitchChange); - List selection = GetSelectedObjects(false, false, true, false); + List selection = GetSelectedObjects(false, false, true, false, false); if(selection.Count == 0) return; foreach(IVisualEventReceiver obj in selection) @@ -3491,7 +3728,7 @@ namespace CodeImp.DoomBuilder.BuilderModes { PreAction(UndoGroup.ThingRollChange); - List selection = GetSelectedObjects(false, false, true, false); + List selection = GetSelectedObjects(false, false, true, false, false); if(selection.Count == 0) return; foreach(IVisualEventReceiver obj in selection) @@ -3862,6 +4099,105 @@ namespace CodeImp.DoomBuilder.BuilderModes GetTargetEventReceiver(true).OnPaintSelectEnd(); } + [BeginAction("togglevisualslopepicking")] + public void ToggleVisualSlopePicking() + { + if (pickingmode != PickingMode.SlopeHandles) + pickingmode = PickingMode.SlopeHandles; + else + { + pickingmode = PickingMode.Default; + + // Clear smart pivot handles, otherwise it will keep being displayed + foreach (KeyValuePair> kvp in allslopehandles) + foreach (VisualSidedefSlope checkhandle in kvp.Value) + checkhandle.SmartPivot = false; + } + } + + [BeginAction("slopebetweenhandles")] + public void SlopeBetweenHandles() + { + List selectedsectors = GetSelectedObjects(true, false, false, false, false); + if (selectedsectors.Count == 0) + { + General.Interface.DisplayStatus(StatusType.Warning, "You need to select floors or ceilings to slope between slope handles."); + return; + } + + List handles = GetSelectedSlopeHandles(); + + // No handles selected, try to slope between highlighted handle and it smart pivot + if (handles.Count == 0 && HighlightedTarget is VisualSidedefSlope) + { + VisualSidedefSlope handle = VisualSidedefSlope.GetSmartPivotHandle((VisualSidedefSlope)HighlightedTarget, this); + if (handle == null) + { + General.Interface.DisplayStatus(StatusType.Warning, "Couldn't find a smart pivot handle."); + return; + } + + handles.Add((VisualSidedefSlope)HighlightedTarget); + handles.Add(handle); + } + // One handle selected, try to slope between it and the highlighted handle or the selected one's smart pivot + else if (handles.Count == 1) + { + if (HighlightedTarget == handles[0] || !(HighlightedTarget is VisualSidedefSlope)) + { + VisualSidedefSlope handle; + + if(HighlightedTarget is VisualSidedefSlope) + handle = VisualSidedefSlope.GetSmartPivotHandle((VisualSidedefSlope)HighlightedTarget, this); + else + handle = VisualSidedefSlope.GetSmartPivotHandle(handles[0], this); + + if (handle == null) + { + General.Interface.DisplayStatus(StatusType.Warning, "Couldn't find a smart pivot handle."); + return; + } + + handles.Add(handle); + } + else + { + handles.Add((VisualSidedefSlope)HighlightedTarget); + } + } + // Return if more than two handles are selected + else if(handles.Count > 2) + { + General.Interface.DisplayStatus(StatusType.Warning, "Too many slope handles selected."); + return; + } + // Everything else + else if(handles.Count != 2) + { + General.Interface.DisplayStatus(StatusType.Warning, "No slope handles selected or highlighted."); + return; + } + + General.Map.UndoRedo.CreateUndo("Slope between slope handles"); + + // Create the new plane + Vector3D p1 = new Vector3D(handles[0].Sidedef.Line.Start.Position, handles[0].Level.plane.GetZ(handles[0].Sidedef.Line.Start.Position)); + Vector3D p2 = new Vector3D(handles[0].Sidedef.Line.End.Position, handles[0].Level.plane.GetZ(handles[0].Sidedef.Line.End.Position)); + Vector3D p3 = new Vector3D(handles[1].Sidedef.Line.Line.GetCoordinatesAt(0.5f), handles[1].Level.plane.GetZ(handles[1].Sidedef.Line.Line.GetCoordinatesAt(0.5f))); + Plane plane = new Plane(p1, p2, p3, true); + + // Apply slope + foreach (BaseVisualGeometrySector bvgs in selectedsectors) + { + VisualSidedefSlope.ApplySlope(bvgs.Level, plane, this); + bvgs.Sector.UpdateSectorGeometry(true); + } + + UpdateChangedObjects(); + + General.Interface.DisplayStatus(StatusType.Action, "Sloped between slope handles."); + } + #endregion #region ================== Texture Alignment diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualCeiling.cs b/Source/Plugins/BuilderModes/VisualModes/VisualCeiling.cs index 2ca6960c..c9ca4fa0 100755 --- a/Source/Plugins/BuilderModes/VisualModes/VisualCeiling.cs +++ b/Source/Plugins/BuilderModes/VisualModes/VisualCeiling.cs @@ -391,6 +391,7 @@ namespace CodeImp.DoomBuilder.BuilderModes //mxd. Modify slope offset? if(level.sector.CeilSlope.GetLengthSq() > 0) { + /* Vector3D center = new Vector3D(level.sector.BBox.X + level.sector.BBox.Width / 2, level.sector.BBox.Y + level.sector.BBox.Height / 2, level.sector.CeilHeight); @@ -401,6 +402,9 @@ namespace CodeImp.DoomBuilder.BuilderModes false); level.sector.CeilSlopeOffset = p.Offset; + */ + + level.sector.CeilSlopeOffset -= level.sector.CeilSlope.z * amount; } } @@ -650,42 +654,7 @@ namespace CodeImp.DoomBuilder.BuilderModes { if(!General.Map.UDMF) return; - //is is a surface with line slope? - float slopeAngle = level.plane.Normal.GetAngleZ() - Angle2D.PIHALF; - - if(slopeAngle == 0) //it's a horizontal plane - { - AlignTextureToClosestLine(alignx, aligny); - } - else //it can be a surface with line slope - { - Linedef slopeSource = null; - bool isFront = false; - - foreach(Sidedef side in Sector.Sector.Sidedefs) - { - if(side.Line.Action == 181) - { - if(side.Line.Args[1] == 1 && side.Line.Front != null && side.Line.Front == side) - { - slopeSource = side.Line; - isFront = true; - break; - } - - if(side.Line.Args[1] == 2 && side.Line.Back != null && side.Line.Back == side) - { - slopeSource = side.Line; - break; - } - } - } - - if(slopeSource != null && slopeSource.Front != null && slopeSource.Front.Sector != null && slopeSource.Back != null && slopeSource.Back.Sector != null) - AlignTextureToSlopeLine(slopeSource, slopeAngle, isFront, alignx, aligny); - else - AlignTextureToClosestLine(alignx, aligny); - } + AlignTextureToClosestLine(alignx, aligny); } #endregion diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualFloor.cs b/Source/Plugins/BuilderModes/VisualModes/VisualFloor.cs index 8dc4bf31..8b28ce1e 100755 --- a/Source/Plugins/BuilderModes/VisualModes/VisualFloor.cs +++ b/Source/Plugins/BuilderModes/VisualModes/VisualFloor.cs @@ -351,6 +351,7 @@ namespace CodeImp.DoomBuilder.BuilderModes //mxd. Modify slope offset? if(level.sector.FloorSlope.GetLengthSq() > 0) { + /* Vector3D center = new Vector3D(level.sector.BBox.X + level.sector.BBox.Width / 2, level.sector.BBox.Y + level.sector.BBox.Height / 2, level.sector.FloorHeight); @@ -361,6 +362,9 @@ namespace CodeImp.DoomBuilder.BuilderModes true); level.sector.FloorSlopeOffset = p.Offset; + */ + + level.sector.FloorSlopeOffset -= level.sector.FloorSlope.z * amount; } } @@ -582,42 +586,7 @@ namespace CodeImp.DoomBuilder.BuilderModes { if(!General.Map.UDMF) return; - //is is a surface with line slope? - float slopeAngle = level.plane.Normal.GetAngleZ() - Angle2D.PIHALF; - - if(slopeAngle == 0) //it's a horizontal plane - { - AlignTextureToClosestLine(alignx, aligny); - } - else //it can be a surface with line slope - { - Linedef slopeSource = null; - bool isFront = false; - - foreach(Sidedef side in Sector.Sector.Sidedefs) - { - if(side.Line.Action == 181) - { - if(side.Line.Args[0] == 1 && side.Line.Front != null && side.Line.Front == side) - { - slopeSource = side.Line; - isFront = true; - break; - } - - if(side.Line.Args[0] == 2 && side.Line.Back != null && side.Line.Back == side) - { - slopeSource = side.Line; - break; - } - } - } - - if(slopeSource != null && slopeSource.Front != null && slopeSource.Front.Sector != null && slopeSource.Back != null && slopeSource.Back.Sector != null) - AlignTextureToSlopeLine(slopeSource, slopeAngle, isFront, alignx, aligny); - else - AlignTextureToClosestLine(alignx, aligny); - } + AlignTextureToClosestLine(alignx, aligny); } #endregion diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualSidedefSlope.cs b/Source/Plugins/BuilderModes/VisualModes/VisualSidedefSlope.cs new file mode 100644 index 00000000..e35b3503 --- /dev/null +++ b/Source/Plugins/BuilderModes/VisualModes/VisualSidedefSlope.cs @@ -0,0 +1,412 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; +using CodeImp.DoomBuilder.BuilderModes; +using CodeImp.DoomBuilder.Geometry; +using CodeImp.DoomBuilder.Map; +using CodeImp.DoomBuilder.Rendering; + +namespace CodeImp.DoomBuilder.VisualModes +{ + internal class VisualSidedefSlope : VisualSlope, IVisualEventReceiver + { + #region ================== Variables + + private readonly BaseVisualMode mode; + private readonly Sidedef sidedef; + private readonly SectorLevel level; + private readonly bool up; + private Vector3D pickintersect; + private float pickrayu; + private Plane plane; + + #endregion + + #region ================== Constants + + private const int SIZE = 8; + + #endregion + + #region ================== Properties + + public Sidedef Sidedef { get { return sidedef; } } + public SectorLevel Level { get { return level; } } + public int NormalizedAngleDeg { get { return (sidedef.Line.AngleDeg >= 180) ? (sidedef.Line.AngleDeg - 180) : sidedef.Line.AngleDeg; } } + + #endregion + + #region ================== Constructor / Destructor + + public VisualSidedefSlope(BaseVisualMode mode, SectorLevel level, Sidedef sidedef, bool up) : base() + { + this.mode = mode; + this.sidedef = sidedef; + this.level = level; + this.up = up; + + // length = sidedef.Line.Length; + + Update(); + + // We have no destructor + GC.SuppressFinalize(this); + } + + #endregion + + #region ================== Methods + + public Vector3D GetCenterPoint() + { + Vector2D p = sidedef.Line.GetCenterPoint(); + return new Vector3D(p, level.plane.GetZ(p)); + } + + public override void Update() + { + plane = new Plane(level.plane.Normal, level.plane.Offset - 0.1f); + + if (!up) + plane = plane.GetInverted(); + + UpdatePosition(); + + length = new Line3D(new Vector3D(sidedef.Line.Line.v1, plane.GetZ(sidedef.Line.Line.v1)), new Vector3D(sidedef.Line.Line.v2, plane.GetZ(sidedef.Line.Line.v2))).GetDelta().GetLength(); + } + + /// + /// This is called when the thing must be tested for line intersection. This should reject + /// as fast as possible to rule out all geometry that certainly does not touch the line. + /// + public override bool PickFastReject(Vector3D from, Vector3D to, Vector3D dir) + { + RectangleF bbox = sidedef.Sector.BBox; + + if ((up && plane.Distance(from) > 0.0f) || (!up && plane.Distance(from) < 0.0f)) + { + if (plane.GetIntersection(from, to, ref pickrayu)) + { + if (pickrayu > 0.0f) + { + pickintersect = from + (to - from) * pickrayu; + + return ((pickintersect.x >= bbox.Left) && (pickintersect.x <= bbox.Right) && + (pickintersect.y >= bbox.Top) && (pickintersect.y <= bbox.Bottom)); + } + } + } + + return false; + } + + /// + /// This is called when the thing must be tested for line intersection. This should perform + /// accurate hit detection and set u_ray to the position on the ray where this hits the geometry. + /// + public override bool PickAccurate(Vector3D from, Vector3D to, Vector3D dir, ref float u_ray) + { + u_ray = pickrayu; + + Sidedef sd = MapSet.NearestSidedef(sidedef.Sector.Sidedefs, pickintersect); + if (sd == sidedef) { + float side = sd.Line.SideOfLine(pickintersect); + + if ((side <= 0.0f && sd.IsFront) || (side > 0.0f && !sd.IsFront)) + return true; + } + + return false; + } + + /// + /// Updates the position. Depending on 3D floors and which side of the linedef the slope handle is on the + /// direction of the line used as a base has to be inverted + /// + public void UpdatePosition() + { + bool invertline = false; + + if (up) + { + if (level.extrafloor && level.type == SectorLevelType.Ceiling) + { + if (sidedef.IsFront) + invertline = true; + } + else + { + if (!sidedef.IsFront) + invertline = true; + } + } + else + { + if (level.extrafloor && level.type == SectorLevelType.Floor) + { + if (!sidedef.IsFront) + invertline = true; + } + else + { + if (sidedef.IsFront) + invertline = true; + } + } + + if (invertline) + SetPosition(new Line2D(sidedef.Line.End.Position, sidedef.Line.Start.Position), level.plane); + else + SetPosition(sidedef.Line.Line, level.plane); + } + + /// + /// Tries to find a slope handle to pivot around. If possible if finds the handle belonging to a line that has the + /// same angle as the start handle, and is the furthest away. If such a handle does not exist it finds one that's + /// closest to those specs + /// + /// The slope handle to start from (the one we need to find a pivot handle for) + /// + public static VisualSidedefSlope GetSmartPivotHandle(VisualSidedefSlope starthandle, BaseVisualMode mode) + { + VisualSidedefSlope handle = starthandle; + List potentialhandles = new List(); + List selectedsectors = mode.GetSelectedObjects(true, false, false, false, false); + + if (selectedsectors.Count == 0) + { + // No sectors selected, so find all handles that belong to the same level + foreach (VisualSidedefSlope checkhandle in mode.AllSlopeHandles[starthandle.Sidedef.Sector]) + if (checkhandle != starthandle && checkhandle.Level == starthandle.Level) + potentialhandles.Add(checkhandle); + } + else + { + // Sectors are selected, get all handles from those sectors that have the same level + HashSet sectors = new HashSet(); + + foreach (BaseVisualGeometrySector bvgs in selectedsectors) + sectors.Add(bvgs.Sector.Sector); + + foreach (Sector s in sectors) + foreach (VisualSidedefSlope checkhandle in mode.AllSlopeHandles[s]) + if(checkhandle != starthandle) + foreach (BaseVisualGeometrySector bvgs in selectedsectors) + if (bvgs.Level == checkhandle.Level) + potentialhandles.Add(checkhandle); + } + + foreach (KeyValuePair> kvp in mode.AllSlopeHandles) + foreach (VisualSidedefSlope checkhandle in kvp.Value) + checkhandle.SmartPivot = false; + + // Sort potential handles by their angle difference to the start handle. That means that handles with less angle difference will be at the beginning of the list + List anglediffsortedhandles = potentialhandles.OrderBy(h => Math.Abs(starthandle.NormalizedAngleDeg - h.NormalizedAngleDeg)).ToList(); + + // Get all potential handles that have to same angle as the one that's closest to the start handle, then sort them by distance, and take the one that's furthest away + if (anglediffsortedhandles.Count > 0) + handle = anglediffsortedhandles.Where(h => h.NormalizedAngleDeg == anglediffsortedhandles[0].NormalizedAngleDeg).OrderByDescending(h => Math.Abs(starthandle.Sidedef.Line.Line.GetDistanceToLine(h.sidedef.Line.GetCenterPoint(), false))).First(); + + if (handle == starthandle) + return null; + + return handle; + } + + public static void ApplySlope(SectorLevel level, Plane plane, BaseVisualMode mode) + { + bool applytoceiling = false; + + Vector2D center = new Vector2D(level.sector.BBox.X + level.sector.BBox.Width / 2, + level.sector.BBox.Y + level.sector.BBox.Height / 2); + + if (level.extrafloor) + { + // The top side of 3D floors is the ceiling of the sector, but it's a "floor" in UDB, so the + // ceiling of the control sector has to be modified + if (level.type == SectorLevelType.Floor) + applytoceiling = true; + } + else + { + if (level.type == SectorLevelType.Ceiling) + applytoceiling = true; + } + + if (applytoceiling) + { + Plane downplane = plane.GetInverted(); + level.sector.CeilSlope = downplane.Normal; + level.sector.CeilSlopeOffset = downplane.Offset; + level.sector.CeilHeight = (int)new Plane(level.sector.CeilSlope, level.sector.CeilSlopeOffset).GetZ(center); + } + else + { + level.sector.FloorSlope = plane.Normal; + level.sector.FloorSlopeOffset = plane.Offset; + level.sector.FloorHeight = (int)new Plane(level.sector.FloorSlope, level.sector.FloorSlopeOffset).GetZ(center); + } + + // Rebuild sector + BaseVisualSector vs; + if (mode.VisualSectorExists(level.sector)) + { + vs = (BaseVisualSector)mode.GetVisualSector(level.sector); + } + else + { + vs = mode.CreateBaseVisualSector(level.sector); + } + + if (vs != null) vs.UpdateSectorGeometry(true); + } + + #endregion + + #region ================== Events + + public void OnChangeTargetHeight(int amount) + { + VisualSlope pivothandle = null; + List selectedsectors = mode.GetSelectedObjects(true, false, false, false, false); + List levels = new List(); + + if (selectedsectors.Count == 0) + levels.Add(level); + else + { + foreach (BaseVisualGeometrySector bvgs in selectedsectors) + levels.Add(bvgs.Level); + + if (!levels.Contains(level)) + levels.Add(level); + } + + // Try to find a slope handle the user set to be the pivot handle + // TODO: doing this every time is kind of stupid. Maybe store the pivot handle in the mode? + foreach (KeyValuePair> kvp in mode.AllSlopeHandles) + { + foreach (VisualSidedefSlope handle in kvp.Value) + { + if (handle.Pivot) + { + pivothandle = handle; + break; + } + } + } + + // User didn't set a pivot handle, try to find the smart pivot handle + if(pivothandle == null) + pivothandle = GetSmartPivotHandle(this, mode); + + // Still no pivot handle, cancle + if (pivothandle == null) + return; + + pivothandle.SmartPivot = true; + + mode.CreateUndo("Change slope"); + + Plane originalplane = level.plane; + Plane pivotplane = ((VisualSidedefSlope)pivothandle).Level.plane; + + // Build a new plane. p1 and p2 are the points of the slope handle that is modified, p3 is on the line of the pivot handle + Vector3D p1 = new Vector3D(sidedef.Line.Start.Position, (float)Math.Round(originalplane.GetZ(sidedef.Line.Start.Position))); + Vector3D p2 = new Vector3D(sidedef.Line.End.Position, (float)Math.Round(originalplane.GetZ(sidedef.Line.End.Position))); + Vector3D p3 = new Vector3D(((VisualSidedefSlope)pivothandle).Sidedef.Line.Line.GetCoordinatesAt(0.5f), (float)Math.Round(pivotplane.GetZ(((VisualSidedefSlope)pivothandle).Sidedef.Line.Line.GetCoordinatesAt(0.5f)))); + + // Move the points of the handle up/down + p1 += new Vector3D(0f, 0f, amount); + p2 += new Vector3D(0f, 0f, amount); + + Plane plane = new Plane(p1, p2, p3, true); + + // Apply slope to surfaces + foreach (SectorLevel l in levels) + ApplySlope(l, plane, mode); + + mode.SetActionResult("Changed slope."); + } + + // Select or deselect + public void OnSelectEnd() + { + if (this.selected) + { + this.selected = false; + mode.RemoveSelectedObject(this); + } + else + { + if(this.pivot) + { + General.Interface.DisplayStatus(Windows.StatusType.Warning, "It is not allowed to mark pivot slope handles as selected."); + return; + } + + this.selected = true; + mode.AddSelectedObject(this); + } + } + + public void OnEditEnd() + { + // We can only have one pivot handle, so remove it from all first + foreach (KeyValuePair> kvp in mode.AllSlopeHandles) + { + foreach (VisualSlope handle in kvp.Value) + { + if (handle == mode.HighlightedTarget) + { + if (handle.Selected) + General.Interface.DisplayStatus(Windows.StatusType.Warning, "It is not allowed to mark selected slope handles as pivot slope handles."); + else + handle.Pivot = !handle.Pivot; + } + else + handle.Pivot = false; + } + } + } + + // Return texture name + public string GetTextureName() { return ""; } + + // Unused + public void OnSelectBegin() { } + public void OnEditBegin() { } + public void OnChangeTargetBrightness(bool up) { } + public void OnChangeTextureOffset(int horizontal, int vertical, bool doSurfaceAngleCorrection) { } + public void OnSelectTexture() { } + public void OnCopyTexture() { } + public void OnPasteTexture() { } + public void OnCopyTextureOffsets() { } + public void OnPasteTextureOffsets() { } + public void OnTextureAlign(bool alignx, bool aligny) { } + public void OnToggleUpperUnpegged() { } + public void OnToggleLowerUnpegged() { } + public void OnProcess(long deltatime) { } + public void OnTextureFloodfill() { } + public void OnInsert() { } + public void OnTextureFit(FitTextureOptions options) { } //mxd + public void ApplyTexture(string texture) { } + public void ApplyUpperUnpegged(bool set) { } + public void ApplyLowerUnpegged(bool set) { } + public void SelectNeighbours(bool select, bool withSameTexture, bool withSameHeight) { } //mxd + public virtual void OnPaintSelectEnd() { } // biwa + public void OnChangeScale(int x, int y) { } + public void OnResetTextureOffset() { } + public void OnResetLocalTextureOffset() { } + public void OnCopyProperties() { } + public void OnPasteProperties(bool usecopysetting) { } + public void OnDelete() { } + public void OnPaintSelectBegin() { } + public void OnMouseMove(MouseEventArgs e) { } + + #endregion + } +} \ No newline at end of file