diff --git a/Build/Sprites/Action.png b/Build/Sprites/Action.png index 146bf95b..69de8009 100755 Binary files a/Build/Sprites/Action.png and b/Build/Sprites/Action.png differ diff --git a/Build/Sprites/Actor.png b/Build/Sprites/Actor.png index 41b8da1a..e38d35c1 100755 Binary files a/Build/Sprites/Actor.png and b/Build/Sprites/Actor.png differ diff --git a/Build/Sprites/ActorMover.png b/Build/Sprites/ActorMover.png index 48df6e9b..dc831b99 100755 Binary files a/Build/Sprites/ActorMover.png and b/Build/Sprites/ActorMover.png differ diff --git a/Build/Sprites/Anchor.png b/Build/Sprites/Anchor.png index 3367cd02..41964348 100755 Binary files a/Build/Sprites/Anchor.png and b/Build/Sprites/Anchor.png differ diff --git a/Build/Sprites/Arrow.png b/Build/Sprites/Arrow.png index e7e97e86..5d2bb8ee 100755 Binary files a/Build/Sprites/Arrow.png and b/Build/Sprites/Arrow.png differ diff --git a/Build/Sprites/Bridge.png b/Build/Sprites/Bridge.png index 73afdf57..5f69e223 100755 Binary files a/Build/Sprites/Bridge.png and b/Build/Sprites/Bridge.png differ diff --git a/Build/Sprites/Camera.png b/Build/Sprites/Camera.png index 6a543172..f6d5411e 100755 Binary files a/Build/Sprites/Camera.png and b/Build/Sprites/Camera.png differ diff --git a/Build/Sprites/Decal.png b/Build/Sprites/Decal.png index 139de294..ea41df8d 100755 Binary files a/Build/Sprites/Decal.png and b/Build/Sprites/Decal.png differ diff --git a/Build/Sprites/DeepWater.png b/Build/Sprites/DeepWater.png index 091c4fdb..fb73630f 100755 Binary files a/Build/Sprites/DeepWater.png and b/Build/Sprites/DeepWater.png differ diff --git a/Build/Sprites/InterpolationPoint.png b/Build/Sprites/InterpolationPoint.png index f0816796..1d4c2a16 100755 Binary files a/Build/Sprites/InterpolationPoint.png and b/Build/Sprites/InterpolationPoint.png differ diff --git a/Build/Sprites/InterpolationSpecial.png b/Build/Sprites/InterpolationSpecial.png index a2c2d71b..bdcb108a 100755 Binary files a/Build/Sprites/InterpolationSpecial.png and b/Build/Sprites/InterpolationSpecial.png differ diff --git a/Build/Sprites/Light.png b/Build/Sprites/Light.png index 01d2313f..6359c27c 100755 Binary files a/Build/Sprites/Light.png and b/Build/Sprites/Light.png differ diff --git a/Build/Sprites/MapSpot.png b/Build/Sprites/MapSpot.png index b711f6a7..cb183d36 100755 Binary files a/Build/Sprites/MapSpot.png and b/Build/Sprites/MapSpot.png differ diff --git a/Build/Sprites/MapSpotGravity.png b/Build/Sprites/MapSpotGravity.png index 3055fa8a..ac4aa93a 100755 Binary files a/Build/Sprites/MapSpotGravity.png and b/Build/Sprites/MapSpotGravity.png differ diff --git a/Build/Sprites/PathFollower.png b/Build/Sprites/PathFollower.png index 361cebb8..081e99a1 100755 Binary files a/Build/Sprites/PathFollower.png and b/Build/Sprites/PathFollower.png differ diff --git a/Build/Sprites/PointPuller.png b/Build/Sprites/PointPuller.png index 60a2c03d..9314ec1d 100755 Binary files a/Build/Sprites/PointPuller.png and b/Build/Sprites/PointPuller.png differ diff --git a/Build/Sprites/PointPusher.png b/Build/Sprites/PointPusher.png index 55bec889..4e880838 100755 Binary files a/Build/Sprites/PointPusher.png and b/Build/Sprites/PointPusher.png differ diff --git a/Build/Sprites/Portal_lower.png b/Build/Sprites/Portal_lower.png index e4874a12..56b84f80 100755 Binary files a/Build/Sprites/Portal_lower.png and b/Build/Sprites/Portal_lower.png differ diff --git a/Build/Sprites/Portal_upper.png b/Build/Sprites/Portal_upper.png index 4d5293d6..a59559d0 100755 Binary files a/Build/Sprites/Portal_upper.png and b/Build/Sprites/Portal_upper.png differ diff --git a/Build/Sprites/Secret.png b/Build/Sprites/Secret.png index f2bb04d4..b197eca1 100755 Binary files a/Build/Sprites/Secret.png and b/Build/Sprites/Secret.png differ diff --git a/Build/Sprites/SilentSector.png b/Build/Sprites/SilentSector.png index e7594bbf..ef117900 100755 Binary files a/Build/Sprites/SilentSector.png and b/Build/Sprites/SilentSector.png differ diff --git a/Build/Sprites/SkyboxPicker.png b/Build/Sprites/SkyboxPicker.png index e6890655..5bf80748 100755 Binary files a/Build/Sprites/SkyboxPicker.png and b/Build/Sprites/SkyboxPicker.png differ diff --git a/Build/Sprites/SkyboxViewpoint.png b/Build/Sprites/SkyboxViewpoint.png index f3d53042..cb0eddf8 100755 Binary files a/Build/Sprites/SkyboxViewpoint.png and b/Build/Sprites/SkyboxViewpoint.png differ diff --git a/Build/Sprites/Slope.png b/Build/Sprites/Slope.png index b0ac5467..df6edaa1 100755 Binary files a/Build/Sprites/Slope.png and b/Build/Sprites/Slope.png differ diff --git a/Build/Sprites/Sound.png b/Build/Sprites/Sound.png index 1c30b955..69dec35b 100755 Binary files a/Build/Sprites/Sound.png and b/Build/Sprites/Sound.png differ diff --git a/Build/Sprites/Sparkle.png b/Build/Sprites/Sparkle.png index 3903be5b..0c6a739a 100755 Binary files a/Build/Sprites/Sparkle.png and b/Build/Sprites/Sparkle.png differ diff --git a/Build/Sprites/Target.png b/Build/Sprites/Target.png index 1637d171..9b3459d8 100755 Binary files a/Build/Sprites/Target.png and b/Build/Sprites/Target.png differ diff --git a/Build/Sprites/Teleport.png b/Build/Sprites/Teleport.png index c7c0375c..9c4af793 100755 Binary files a/Build/Sprites/Teleport.png and b/Build/Sprites/Teleport.png differ diff --git a/Build/Sprites/Zone.png b/Build/Sprites/Zone.png index 3d60661b..061ede05 100755 Binary files a/Build/Sprites/Zone.png and b/Build/Sprites/Zone.png differ diff --git a/Source/Core/Data/FileImage.cs b/Source/Core/Data/FileImage.cs index a4e3e40c..bff37bf4 100755 --- a/Source/Core/Data/FileImage.cs +++ b/Source/Core/Data/FileImage.cs @@ -160,7 +160,7 @@ namespace CodeImp.DoomBuilder.Data if (filedata != null) { // Get a reader for the data - bitmap = ImageDataFormat.TryLoadImage(filedata, probableformat, General.Map.Data.Palette); + bitmap = ImageDataFormat.TryLoadImage(filedata, probableformat, General.Map.Data.Palette, out offsetx, out offsety); // Not loaded? if (bitmap == null) diff --git a/Source/Core/Data/HiResImage.cs b/Source/Core/Data/HiResImage.cs index d046e1b5..8c2f1c0d 100755 --- a/Source/Core/Data/HiResImage.cs +++ b/Source/Core/Data/HiResImage.cs @@ -112,6 +112,8 @@ namespace CodeImp.DoomBuilder.Data // Store source properteis sourcesize = new Size(overridden.Width, overridden.Height); sourcescale = overridden.Scale; + offsetx = overridden.OffsetX; + offsety = overridden.OffsetY; } } diff --git a/Source/Core/Data/ImageData.cs b/Source/Core/Data/ImageData.cs index 4c5f24c4..d39802d2 100755 --- a/Source/Core/Data/ImageData.cs +++ b/Source/Core/Data/ImageData.cs @@ -48,6 +48,8 @@ namespace CodeImp.DoomBuilder.Data protected long longname; protected int width; protected int height; + protected int offsetx; + protected int offsety; protected Vector2D scale; protected bool worldpanning; private bool usecolorcorrection; @@ -141,6 +143,8 @@ namespace CodeImp.DoomBuilder.Data public int MipMapLevels { get { return mipmaplevels; } set { mipmaplevels = value; } } public virtual int Width { get { return width; } } public virtual int Height { get { return height; } } + public int OffsetX { get { return offsetx; } } + public int OffsetY { get { return offsety; } } //mxd. Scaled texture size is integer in ZDoom. public virtual float ScaledWidth { get { return (float)Math.Round(width * scale.x); } } public virtual float ScaledHeight { get { return (float)Math.Round(height * scale.y); } } diff --git a/Source/Core/Data/ResourceImage.cs b/Source/Core/Data/ResourceImage.cs index fa9a9ec4..a8dfc832 100755 --- a/Source/Core/Data/ResourceImage.cs +++ b/Source/Core/Data/ResourceImage.cs @@ -47,7 +47,7 @@ namespace CodeImp.DoomBuilder.Data SetName(resourcename); // Temporarily load resource from memory - Stream bitmapdata = assembly.GetManifestResourceStream(resourcename); + Stream bitmapdata = assembly.GetManifestResourceStream(resourcename); Bitmap bmp = (Bitmap)Image.FromStream(bitmapdata); // Get width and height from image diff --git a/Source/Core/Data/SpriteImage.cs b/Source/Core/Data/SpriteImage.cs index 221cf0e7..94c7ab17 100755 --- a/Source/Core/Data/SpriteImage.cs +++ b/Source/Core/Data/SpriteImage.cs @@ -35,20 +35,6 @@ namespace CodeImp.DoomBuilder.Data public sealed class SpriteImage : ImageData, ISpriteImage { - #region ================== Variables - - private int offsetx; - private int offsety; - - #endregion - - #region ================== Properties - - public int OffsetX { get { return offsetx; } } - public int OffsetY { get { return offsety; } } - - #endregion - #region ================== Constructor / Disposer // Constructor diff --git a/Source/Core/Data/TEXTURESImage.cs b/Source/Core/Data/TEXTURESImage.cs index ac2fc2ea..f15845de 100755 --- a/Source/Core/Data/TEXTURESImage.cs +++ b/Source/Core/Data/TEXTURESImage.cs @@ -52,12 +52,14 @@ namespace CodeImp.DoomBuilder.Data #region ================== Constructor / Disposer // Constructor - public TEXTURESImage(string name, string virtualpath, int width, int height, float scalex, float scaley, + public TEXTURESImage(string name, string virtualpath, int width, int height, int offsetx, int offsety, float scalex, float scaley, bool worldpanning, TextureNamespace texturenamespace, bool optional, bool nulltexture) { // Initialize this.width = width; this.height = height; + this.offsetx = offsetx; + this.offsety = offsety; this.scale.x = scalex; this.scale.y = scaley; this.worldpanning = worldpanning; diff --git a/Source/Core/Rendering/IRenderer2D.cs b/Source/Core/Rendering/IRenderer2D.cs old mode 100755 new mode 100644 index b6117c99..924b6c00 --- a/Source/Core/Rendering/IRenderer2D.cs +++ b/Source/Core/Rendering/IRenderer2D.cs @@ -52,7 +52,7 @@ namespace CodeImp.DoomBuilder.Rendering // Rendering management methods bool StartPlotter(bool clear); bool StartThings(bool clear); - bool StartOverlay(bool clear); + bool StartOverlay(bool clear, int layernum = 0); void Finish(); void SetPresentation(Presentation present); void Present(); @@ -69,6 +69,7 @@ namespace CodeImp.DoomBuilder.Rendering void PlotVerticesSet(ICollection vertices, bool checkMode = true); void RenderThing(Thing t, PixelColor c, float alpha); void RenderThingSet(ICollection things, float alpha); + void RenderThingSet(ICollection things, PixelColor c, float alpha); void RenderSRB2Extras(); void RenderRectangle(RectangleF rect, float bordersize, PixelColor c, bool transformrect); void RenderRectangleFilled(RectangleF rect, PixelColor c, bool transformrect); diff --git a/Source/Core/Rendering/Renderer2D.cs b/Source/Core/Rendering/Renderer2D.cs old mode 100755 new mode 100644 index 4324bf9c..4f84f205 --- a/Source/Core/Rendering/Renderer2D.cs +++ b/Source/Core/Rendering/Renderer2D.cs @@ -65,7 +65,7 @@ namespace CodeImp.DoomBuilder.Rendering private Plotter gridplotter; private Plotter plotter; private Texture thingstex; - private Texture overlaytex; + private List overlaytex; private Texture surfacetex; // Rendertarget sizes @@ -181,11 +181,22 @@ namespace CodeImp.DoomBuilder.Rendering public void SetPresentation(Presentation present) { this.present = new Presentation(present); + + // We might have to create additional overlay textures + int numoverlaylayers = present.layers.Count(l => l.layer == RendererLayer.Overlay); + if(numoverlaylayers > overlaytex.Count) + { + Texture t = new Texture(windowsize.Width, windowsize.Height, TextureFormat.Rgba8); + graphics.ClearTexture(General.Colors.Background.WithAlpha(0).ToColorValue(), t); + overlaytex.Add(t); + } } // This draws the image on screen public void Present() { + int currentoverlaylayer = 0; + General.Plugins.OnPresentDisplayBegin(); // Start drawing @@ -281,10 +292,11 @@ namespace CodeImp.DoomBuilder.Rendering // OVERLAY case RendererLayer.Overlay: graphics.SetShader(aapass); - graphics.SetTexture(overlaytex); + graphics.SetTexture(overlaytex[currentoverlaylayer]); graphics.SetSamplerState(TextureAddress.Wrap); - SetDisplay2DSettings(1f / overlaytex.Width, 1f / overlaytex.Height, FSAA_FACTOR, layer.alpha, false, true); + SetDisplay2DSettings(1f / overlaytex[currentoverlaylayer].Width, 1f / overlaytex[currentoverlaylayer].Height, FSAA_FACTOR, layer.alpha, false, true); graphics.Draw(PrimitiveType.TriangleStrip, 0, 2); + currentoverlaylayer++; break; // SURFACE @@ -292,7 +304,7 @@ namespace CodeImp.DoomBuilder.Rendering graphics.SetShader(aapass); graphics.SetTexture(surfacetex); graphics.SetSamplerState(TextureAddress.Wrap); - SetDisplay2DSettings(1f / overlaytex.Width, 1f / overlaytex.Height, FSAA_FACTOR, layer.alpha, false, true); + SetDisplay2DSettings(1f / surfacetex.Width, 1f / surfacetex.Height, FSAA_FACTOR, layer.alpha, false, true); graphics.Draw(PrimitiveType.TriangleStrip, 0, 2); break; } @@ -338,12 +350,12 @@ namespace CodeImp.DoomBuilder.Rendering public void DestroyRendertargets() { // Trash rendertargets - if(plotter != null) plotter.Dispose(); - if(thingstex != null) thingstex.Dispose(); - if(overlaytex != null) overlaytex.Dispose(); - if(surfacetex != null) surfacetex.Dispose(); - if(gridplotter != null) gridplotter.Dispose(); - if(screenverts != null) screenverts.Dispose(); + if (plotter != null) plotter.Dispose(); + if (thingstex != null) thingstex.Dispose(); + if (overlaytex != null) for(int i=0; i < overlaytex.Count; i++) { overlaytex[i].Dispose(); overlaytex[i] = null; } ; + if (surfacetex != null) surfacetex.Dispose(); + if (gridplotter != null) gridplotter.Dispose(); + if (screenverts != null) screenverts.Dispose(); thingstex = null; gridplotter = null; screenverts = null; @@ -371,13 +383,23 @@ namespace CodeImp.DoomBuilder.Rendering plotter = new Plotter(windowsize.Width, windowsize.Height); gridplotter = new Plotter(windowsize.Width, windowsize.Height); thingstex = new Texture(windowsize.Width, windowsize.Height, TextureFormat.Rgba8); - overlaytex = new Texture(windowsize.Width, windowsize.Height, TextureFormat.Rgba8); surfacetex = new Texture(windowsize.Width, windowsize.Height, TextureFormat.Rgba8); - + + if (present == null) + { + overlaytex = new List() { new Texture(windowsize.Width, windowsize.Height, TextureFormat.Rgba8) }; + } + else + { + overlaytex = new List(); + for (int i = 0; i < present.layers.Count(l => l.layer == RendererLayer.Overlay); i++) + overlaytex.Add(new Texture(windowsize.Width, windowsize.Height, TextureFormat.Rgba8)); + } + // Clear rendertargets graphics.ClearTexture(General.Colors.Background.WithAlpha(0).ToColorValue(), thingstex); - graphics.ClearTexture(General.Colors.Background.WithAlpha(0).ToColorValue(), overlaytex); - + foreach(Texture t in overlaytex) graphics.ClearTexture(General.Colors.Background.WithAlpha(0).ToColorValue(), t); + // Create vertex buffers screenverts = new VertexBuffer(); thingsvertices = new VertexBuffer(); @@ -733,7 +755,7 @@ namespace CodeImp.DoomBuilder.Rendering } // This begins a drawing session - public bool StartOverlay(bool clear) + public bool StartOverlay(bool clear, int layernum = 0) { if(renderlayer != RenderLayers.None) { @@ -747,10 +769,10 @@ namespace CodeImp.DoomBuilder.Rendering renderlayer = RenderLayers.Overlay; // Rendertargets available? - if(overlaytex != null) + if(overlaytex != null && layernum >= 0 && layernum < overlaytex.Count) { // Set the rendertarget to the things texture - graphics.StartRendering(clear, General.Colors.Background.WithAlpha(0).ToColorValue(), overlaytex, false); + graphics.StartRendering(clear, General.Colors.Background.WithAlpha(0).ToColorValue(), overlaytex[layernum], false); // Ready for rendering UpdateTransformations(); @@ -1589,6 +1611,13 @@ namespace CodeImp.DoomBuilder.Rendering RenderArrows(LinksCollector.GetSRB2Lines(), true, false); } + // This adds a thing in the things buffer for rendering + public void RenderThingSet(ICollection things, PixelColor c, float alpha) + { + RenderThingsBatch(things, alpha, false, c); + } + + #endregion #region ================== Surface diff --git a/Source/Core/VisualModes/VisualGeometry.cs b/Source/Core/VisualModes/VisualGeometry.cs index c43f2be8..51aa6193 100755 --- a/Source/Core/VisualModes/VisualGeometry.cs +++ b/Source/Core/VisualModes/VisualGeometry.cs @@ -164,7 +164,6 @@ namespace CodeImp.DoomBuilder.VisualModes triangles = vertices.Length / 3; CalculateNormals(); //mxd - PerformAutoSelection(); //mxd } else { @@ -285,7 +284,7 @@ namespace CodeImp.DoomBuilder.VisualModes } //mxd - protected abstract void PerformAutoSelection(); + public abstract void PerformAutoSelection(); #endregion } diff --git a/Source/Core/ZDoom/TextureStructure.cs b/Source/Core/ZDoom/TextureStructure.cs index 5c13de14..d9d33299 100755 --- a/Source/Core/ZDoom/TextureStructure.cs +++ b/Source/Core/ZDoom/TextureStructure.cs @@ -249,7 +249,7 @@ namespace CodeImp.DoomBuilder.ZDoom float scaley = ((yscale == 0.0f) ? General.Map.Config.DefaultTextureScale : 1f / yscale); // Make texture - TEXTURESImage tex = new TEXTURESImage(name, virtualpath, width, height, scalex, scaley, worldpanning, texturenamespace, optional, nulltexture); + TEXTURESImage tex = new TEXTURESImage(name, virtualpath, width, height, xoffset, yoffset, scalex, scaley, worldpanning, texturenamespace, optional, nulltexture); // Add patches foreach(PatchStructure p in patches) tex.AddPatch(new TexturePatch(p));//mxd diff --git a/Source/Plugins/3DFloorMode/ControlSectorArea.cs b/Source/Plugins/3DFloorMode/ControlSectorArea.cs index 5d1f5e26..71d67ce2 100644 --- a/Source/Plugins/3DFloorMode/ControlSectorArea.cs +++ b/Source/Plugins/3DFloorMode/ControlSectorArea.cs @@ -1,589 +1,643 @@ -#region ================== Copyright (c) 2014 Boris Iwanski - -/* - * Copyright (c) 2014 Boris Iwanski - * This program is released under GNU General Public License - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#endregion - -using System; -using System.Windows.Forms; -using System.Collections; -using System.Collections.Specialized; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Linq; -using System.Text; -using System.Drawing; -using System.Diagnostics; -using CodeImp.DoomBuilder.Geometry; -using CodeImp.DoomBuilder.Rendering; -using CodeImp.DoomBuilder.Editing; -using CodeImp.DoomBuilder.Map; - -namespace CodeImp.DoomBuilder.ThreeDFloorMode -{ - [Serializable] - public class NoSpaceInCSAException : Exception - { - public NoSpaceInCSAException() - { } - - public NoSpaceInCSAException(string message) : base(message) - { } - - public NoSpaceInCSAException(string message, Exception innerException) : base(message, innerException) - { } - } - - public class ControlSectorArea - { - - #region ================== Enums - - public enum Highlight - { - None, - OuterLeft, - OuterRight, - OuterTop, - OuterBottom, - OuterTopLeft, - OuterTopRight, - OuterBottomLeft, - OuterBottomRight, - Body - }; - - #endregion - - #region ================== Variables - - private RectangleF outerborder; - private PixelColor bordercolor = new PixelColor(255, 0, 192, 0); - private PixelColor fillcolor = new PixelColor(128, 0, 128, 0); - private PixelColor borderhighlightcolor = new PixelColor(255, 0, 192, 0); - private PixelColor fillhighlightcolor = new PixelColor(128, 0, 192, 0); - private Dictionary lines; - private Dictionary points; - private float gridsize; - private float gridsizeinv; - private float sectorsize; - - private double outerleft; - private double outerright; - private double outertop; - private double outerbottom; - - private bool usecustomtagrange; - private int firsttag; - private int lasttag; - - #endregion - - #region ================== Properties - - public double GridSize { get { return gridsize; } } - public double SectorSize { get { return sectorsize; } } - - public RectangleF OuterBorder { get { return outerborder; } } - - public double OuterLeft - { - get { return outerleft; } - set { outerleft = value; UpdateLinesAndPoints(); } - } - - public double OuterRight - { - get { return outerright; } - set { outerright = value; UpdateLinesAndPoints(); } - } - - public double OuterTop - { - get { return outertop; } - set { outertop = value; UpdateLinesAndPoints(); } - } - - public double OuterBottom - { - get { return outerbottom; } - set { outerbottom = value; UpdateLinesAndPoints(); } - } - - public bool UseCustomTagRnage { get { return usecustomtagrange; } set { usecustomtagrange = value; } } - public int FirstTag { get { return firsttag; } set { firsttag = value; } } - public int LastTag { get { return lasttag; } set { lasttag = value; } } - - #endregion - - #region ================== Constructor / Disposer - - public ControlSectorArea(float outerleft, float outerright, float outertop, float outerbottom, float gridsize, float sectorsize) - { - this.outerleft = outerleft; - this.outerright = outerright; - this.outertop = outertop; - this.outerbottom = outerbottom; - - lines = new Dictionary(); - points = new Dictionary(); - - this.gridsize = gridsize; - gridsizeinv = 1.0f / gridsize; - - this.sectorsize = sectorsize; - - UpdateLinesAndPoints(); - } - - #endregion - - #region ================== Methods - - public void UpdateLinesAndPoints() - { - lines[Highlight.OuterLeft] = new Line2D(outerleft, outertop, outerleft, outerbottom); - lines[Highlight.OuterRight] = new Line2D(outerright, outertop, outerright, outerbottom); - lines[Highlight.OuterTop] = new Line2D(outerleft, outertop, outerright, outertop); - lines[Highlight.OuterBottom] = new Line2D(outerleft, outerbottom, outerright, outerbottom); - - points[Highlight.OuterTopLeft] = new Vector2D(outerleft, outertop); - points[Highlight.OuterTopRight] = new Vector2D(outerright, outertop); - points[Highlight.OuterBottomLeft] = new Vector2D(outerleft, outerbottom); - points[Highlight.OuterBottomRight] = new Vector2D(outerright, outerbottom); - - outerborder = new RectangleF((float)outerleft, (float)outertop, (float)(outerright - outerleft), (float)(outerbottom - outertop)); - } - - public void Draw(IRenderer2D renderer, Highlight highlight) - { - PixelColor fcolor = highlight == Highlight.Body ? fillhighlightcolor : fillcolor; - - renderer.RenderRectangleFilled( - new RectangleF((float)outerleft, (float)outertop, (float)(outerright - outerleft), (float)(outerbottom - outertop)), - fcolor, - true - ); - - // Draw the borders - renderer.RenderRectangle(outerborder, 1.0f, bordercolor, true); - - // Highlight a border if necessary - if (highlight >= Highlight.OuterLeft && highlight <= Highlight.OuterBottom) - renderer.RenderLine(lines[highlight].v1, lines[highlight].v2, 1.0f, borderhighlightcolor, true); - else - { - // Highlight the corners - switch (highlight) - { - // Outer corners - case Highlight.OuterTopLeft: - renderer.RenderLine(lines[Highlight.OuterTop].v1, lines[Highlight.OuterTop].v2, 1.0f, borderhighlightcolor, true); - renderer.RenderLine(lines[Highlight.OuterLeft].v1, lines[Highlight.OuterLeft].v2, 1.0f, borderhighlightcolor, true); - break; - case Highlight.OuterTopRight: - renderer.RenderLine(lines[Highlight.OuterTop].v1, lines[Highlight.OuterTop].v2, 1.0f, borderhighlightcolor, true); - renderer.RenderLine(lines[Highlight.OuterRight].v1, lines[Highlight.OuterRight].v2, 1.0f, borderhighlightcolor, true); - break; - case Highlight.OuterBottomLeft: - renderer.RenderLine(lines[Highlight.OuterBottom].v1, lines[Highlight.OuterBottom].v2, 1.0f, borderhighlightcolor, true); - renderer.RenderLine(lines[Highlight.OuterLeft].v1, lines[Highlight.OuterLeft].v2, 1.0f, borderhighlightcolor, true); - break; - case Highlight.OuterBottomRight: - renderer.RenderLine(lines[Highlight.OuterBottom].v1, lines[Highlight.OuterBottom].v2, 1.0f, borderhighlightcolor, true); - renderer.RenderLine(lines[Highlight.OuterRight].v1, lines[Highlight.OuterRight].v2, 1.0f, borderhighlightcolor, true); - break; - } - } - } - - public Highlight CheckHighlight(Vector2D pos, double scale) - { - double distance = double.MaxValue; - double d; - Highlight highlight = Highlight.None; - - // Find a line to highlight - foreach (Highlight h in (Highlight[])Enum.GetValues(typeof(Highlight))) - { - if (h >= Highlight.OuterLeft && h <= Highlight.OuterBottom) - { - d = Line2D.GetDistanceToLine(lines[h].v1, lines[h].v2, pos, true); - - if (d <= BuilderModes.BuilderPlug.Me.HighlightRange / scale && d < distance) - { - distance = d; - highlight = h; - } - } - } - - distance = double.MaxValue; - - // Find a corner to highlight - foreach (Highlight h in (Highlight[])Enum.GetValues(typeof(Highlight))) - { - if (h >= Highlight.OuterTopLeft && h <= Highlight.OuterBottomRight) - { - d = Vector2D.Distance(pos, points[h]); - - if (d <= BuilderModes.BuilderPlug.Me.HighlightRange / scale && d < distance) - { - distance = d; - highlight = h; - } - } - } - - if (highlight != Highlight.None) - return highlight; - - if (OuterLeft < pos.x && OuterRight > pos.x && OuterTop > pos.y && OuterBottom < pos.y) - return Highlight.Body; - - return Highlight.None; - } - - public void SnapToGrid(Highlight highlight, Vector2D pos, Vector2D lastpos) - { - Vector2D newpos = GridSetup.SnappedToGrid(pos, gridsize, gridsizeinv); - - switch (highlight) - { - case Highlight.Body: - Vector2D diff = GridSetup.SnappedToGrid(pos, gridsize, gridsizeinv) - GridSetup.SnappedToGrid(lastpos, gridsize, gridsizeinv); - Debug.WriteLine("diff: " + (diff).ToString()); - outerleft += diff.x; - outerright += diff.x; - outertop += diff.y; - outerbottom += diff.y; - break; - - // Outer border - case Highlight.OuterLeft: - if (newpos.x < outerright) outerleft = newpos.x; - break; - case Highlight.OuterRight: - if(newpos.x > outerleft) outerright = newpos.x; - break; - case Highlight.OuterTop: - if (newpos.y > outerbottom) outertop = newpos.y; - break; - case Highlight.OuterBottom: - if (newpos.y < outertop) outerbottom = newpos.y; - break; - - // Outer corners - case Highlight.OuterTopLeft: - if (newpos.x < outerright) outerleft = newpos.x; - if (newpos.y > outerbottom) outertop = newpos.y; - break; - case Highlight.OuterTopRight: - if (newpos.x > outerleft) outerright = newpos.x; - if (newpos.y > outerbottom) outertop = newpos.y; - break; - case Highlight.OuterBottomLeft: - if (newpos.x < outerright) outerleft = newpos.x; - if (newpos.y < outertop) outerbottom = newpos.y; - break; - case Highlight.OuterBottomRight: - if (newpos.x > outerleft) outerright = newpos.x; - if (newpos.y < outertop) outerbottom = newpos.y; - break; - } - - UpdateLinesAndPoints(); - } - - public List GetRelocatePositions(int numsectors) - { - List positions = new List(); - BlockMap blockmap = CreateBlockmap(true); - int margin = (int)((gridsize - sectorsize) / 2); - - for (int x = (int)outerleft; x < (int)outerright; x += (int)gridsize) - { - for (int y = (int)outertop; y > (int)outerbottom; y -= (int)gridsize) - { - List blocks = blockmap.GetLineBlocks( - new Vector2D(x + 1, y - 1), - new Vector2D(x + gridsize - 1, y - gridsize + 1) - ); - - // The way our blockmap is built and queried we will always get exactly one block - if (blocks[0].Sectors.Count == 0) - { - positions.Add(new Vector2D(x + margin, y - margin)); - numsectors--; - } - - if (numsectors == 0) - return positions; - } - } - - throw new NoSpaceInCSAException("Not enough space for control sector relocation"); - } - - public List GetNewControlSectorVertices() - { - return GetNewControlSectorVertices(1); - } - - public List GetNewControlSectorVertices(int numsectors) - { - List dv = new List(); - BlockMap blockmap = CreateBlockmap(); - - int margin = (int)((gridsize - sectorsize) / 2); - - // find position for new control sector - for (int x = (int)outerleft; x < (int)outerright; x += (int)gridsize) - { - for (int y = (int)outertop; y > (int)outerbottom; y -= (int)gridsize) - { - List blocks = blockmap.GetLineBlocks( - new Vector2D(x + 1, y - 1), - new Vector2D(x + gridsize - 1, y - gridsize + 1) - ); - - // The way our blockmap is built and queried we will always get exactly one block - if (blocks[0].Sectors.Count == 0) - { - Point p = new Point(x + margin, y - margin); - - dv.Add(SectorVertex(p.X, p.Y)); - dv.Add(SectorVertex(p.X + BuilderPlug.Me.ControlSectorArea.SectorSize, p.Y)); - dv.Add(SectorVertex(p.X + BuilderPlug.Me.ControlSectorArea.SectorSize, p.Y - BuilderPlug.Me.ControlSectorArea.SectorSize)); - dv.Add(SectorVertex(p.X, p.Y - BuilderPlug.Me.ControlSectorArea.SectorSize)); - dv.Add(SectorVertex(p.X, p.Y)); - - numsectors--; - - if (numsectors == 0) - return dv; - } - } - } - - throw new NoSpaceInCSAException("No space left for control sectors"); - } - - public bool Inside(float x, float y) - { - return Inside(new Vector2D(x, y)); - } - - public bool Inside(Vector2D pos) - { - if (pos.x > outerleft && pos.x < outerright && pos.y < outertop && pos.y > outerbottom) - return true; - - return false; - } - - public bool OutsideOuterBounds(float x, float y) - { - return OutsideOuterBounds(new Vector2D(x, y)); - } - - public bool OutsideOuterBounds(Vector2D pos) - { - if(pos.x < outerleft || pos.x > outerright || pos.y > outertop || pos.y < outerbottom) - return true; - - return false; - } - - // Aligns the area to the grid, expanding the area if necessary - private RectangleF AlignAreaToGrid(RectangleF area) - { - List f = new List - { - area.Left, - area.Top, - area.Right, - area.Bottom - }; - - for (int i = 0; i < f.Count; i++) - { - if (f[i] < 0) - f[i] = (float)Math.Floor(f[i] / gridsize) * gridsize; - else - f[i] = (float)Math.Ceiling(f[i] / gridsize) * gridsize; - } - - - float l = f[0]; - float t = f[1]; - float r = f[2]; - float b = f[3]; - - return new RectangleF(l, t, r - l, b - t); - } - - private BlockMap CreateBlockmap() - { - return CreateBlockmap(false); - } - - private BlockMap CreateBlockmap(bool ignorecontrolsectors) - { - // Make blockmap - RectangleF area = MapSet.CreateArea(General.Map.Map.Vertices); - area = MapSet.IncreaseArea(area, new Vector2D(outerleft, outertop)); - area = MapSet.IncreaseArea(area, new Vector2D(outerright, outerbottom)); - area = AlignAreaToGrid(area); - - BlockMap blockmap = new BlockMap(area, (int)gridsize); - - if (ignorecontrolsectors) - { - foreach (Sector s in General.Map.Map.Sectors) - { - // Managed control sectors have the custom UDMF field "user_managed_3d_floor" set to true - // So if the field is NOT set, add the sector to the blockmap - bool managed = s.Fields.GetValue("user_managed_3d_floor", false); - - if (managed == false) - blockmap.AddSector(s); - else // When a tag was manually removed a control sector still might have the user_managed_3d_floor field, but not be - { // recognized as a 3D floor control sector. In that case also add the sector to the blockmap - bool orphaned = true; - - foreach(ThreeDFloor tdf in ((ThreeDFloorHelperMode)General.Editing.Mode).ThreeDFloors) - { - if(tdf.Sector == s) - { - orphaned = false; - break; - } - } - - if (orphaned) - blockmap.AddSector(s); - } - } - } - else - { - blockmap.AddSectorsSet(General.Map.Map.Sectors); - } - - return blockmap; - } - - public void Edit() - { - ControlSectorAreaConfig csacfg = new ControlSectorAreaConfig(this); - - csacfg.ShowDialog((Form)General.Interface); - } - - // When OK is pressed on the preferences dialog - // Prevent inlining, otherwise there are unexpected interactions with Assembly.GetCallingAssembly - // See https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getcallingassembly?view=netframework-4.6.1#remarks - [MethodImplAttribute(MethodImplOptions.NoInlining)] - public void SaveConfig() - { - ListDictionary config = new ListDictionary(); - - config.Add("usecustomtagrange", usecustomtagrange); - - if (usecustomtagrange) - { - config.Add("firsttag", firsttag); - config.Add("lasttag", lasttag); - } - - config.Add("outerleft", outerleft); - config.Add("outerright", outerright); - config.Add("outertop", outertop); - config.Add("outerbottom", outerbottom); - - General.Map.Options.WritePluginSetting("controlsectorarea", config); - } - - // When OK is pressed on the preferences dialog - // Prevent inlining, otherwise there are unexpected interactions with Assembly.GetCallingAssembly - // See https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getcallingassembly?view=netframework-4.6.1#remarks - [MethodImplAttribute(MethodImplOptions.NoInlining)] - public void LoadConfig() - { - ListDictionary config = (ListDictionary)General.Map.Options.ReadPluginSetting("controlsectorarea", new ListDictionary()); - - usecustomtagrange = General.Map.Options.ReadPluginSetting("controlsectorarea.usecustomtagrange", false); - firsttag = General.Map.Options.ReadPluginSetting("controlsectorarea.firsttag", 0); - lasttag = General.Map.Options.ReadPluginSetting("controlsectorarea.lasttag", 0); - - outerleft = General.Map.Options.ReadPluginSetting("controlsectorarea.outerleft", outerleft); - outerright = General.Map.Options.ReadPluginSetting("controlsectorarea.outerright", outerright); - outertop = General.Map.Options.ReadPluginSetting("controlsectorarea.outertop", outertop); - outerbottom = General.Map.Options.ReadPluginSetting("controlsectorarea.outerbottom", outerbottom); - - UpdateLinesAndPoints(); - } - - public int GetNewSectorTag(List tagblacklist) - { - List usedtags = new List(); - - if (usecustomtagrange) - { - for (int i = firsttag; i <= lasttag; i++) - { - if (!tagblacklist.Contains(i) && BuilderPlug.GetSectorsByTag(i).Count == 0) - return i; - } - - throw new Exception("No free tags in the custom range between " + firsttag.ToString() + " and " + lasttag.ToString() + "."); - } - - return General.Map.Map.GetNewTag(tagblacklist); - } - - public int GetNewLineID() - { - return General.Map.Map.GetNewTag(); - } - - // Turns a position into a DrawnVertex and returns it - private DrawnVertex SectorVertex(double x, double y) - { - DrawnVertex v = new DrawnVertex(); - - v.stitch = true; - v.stitchline = true; - v.pos = new Vector2D(Math.Round(x, General.Map.FormatInterface.VertexDecimals), Math.Round(y, General.Map.FormatInterface.VertexDecimals)); - - return v; - } - - private DrawnVertex SectorVertex(Vector2D v) - { - return SectorVertex(v.x, v.y); - } - - static int GCD(int[] numbers) - { - return numbers.Aggregate(GCD); - } - - static int GCD(int a, int b) - { - return b == 0 ? a : GCD(b, a % b); - } - - #endregion - } -} +#region ================== Copyright (c) 2014 Boris Iwanski + +/* + * Copyright (c) 2014 Boris Iwanski + * This program is released under GNU General Public License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#endregion + +using System; +using System.Windows.Forms; +using System.Collections; +using System.Collections.Specialized; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Linq; +using System.Text; +using System.Drawing; +using System.Diagnostics; +using CodeImp.DoomBuilder.Geometry; +using CodeImp.DoomBuilder.Rendering; +using CodeImp.DoomBuilder.Editing; +using CodeImp.DoomBuilder.Map; + +namespace CodeImp.DoomBuilder.ThreeDFloorMode +{ + [Serializable] + public class NoSpaceInCSAException : Exception + { + public NoSpaceInCSAException() + { } + + public NoSpaceInCSAException(string message) : base(message) + { } + + public NoSpaceInCSAException(string message, Exception innerException) : base(message, innerException) + { } + } + + public class ControlSectorArea + { + + #region ================== Enums + + public enum Highlight + { + None, + OuterLeft, + OuterRight, + OuterTop, + OuterBottom, + OuterTopLeft, + OuterTopRight, + OuterBottomLeft, + OuterBottomRight, + Body + }; + + #endregion + + #region ================== Variables + + private RectangleF outerborder; + private PixelColor bordercolor = new PixelColor(255, 0, 192, 0); + private PixelColor fillcolor = new PixelColor(128, 0, 128, 0); + private PixelColor borderhighlightcolor = new PixelColor(255, 0, 192, 0); + private PixelColor fillhighlightcolor = new PixelColor(128, 0, 192, 0); + private Dictionary lines; + private Dictionary points; + private float gridsize; + private float gridsizeinv; + private float sectorsize; + + private double outerleft; + private double outerright; + private double outertop; + private double outerbottom; + + private bool usecustomtagrange; + private int firsttag; + private int lasttag; + + #endregion + + #region ================== Properties + + public double GridSize { get { return gridsize; } } + public double SectorSize { get { return sectorsize; } } + + public RectangleF OuterBorder { get { return outerborder; } } + + public double OuterLeft + { + get { return outerleft; } + set { outerleft = value; UpdateLinesAndPoints(); } + } + + public double OuterRight + { + get { return outerright; } + set { outerright = value; UpdateLinesAndPoints(); } + } + + public double OuterTop + { + get { return outertop; } + set { outertop = value; UpdateLinesAndPoints(); } + } + + public double OuterBottom + { + get { return outerbottom; } + set { outerbottom = value; UpdateLinesAndPoints(); } + } + + public bool UseCustomTagRnage { get { return usecustomtagrange; } set { usecustomtagrange = value; } } + public int FirstTag { get { return firsttag; } set { firsttag = value; } } + public int LastTag { get { return lasttag; } set { lasttag = value; } } + + #endregion + + #region ================== Constructor / Disposer + + public ControlSectorArea(float outerleft, float outerright, float outertop, float outerbottom, float gridsize, float sectorsize) + { + this.outerleft = outerleft; + this.outerright = outerright; + this.outertop = outertop; + this.outerbottom = outerbottom; + + lines = new Dictionary(); + points = new Dictionary(); + + this.gridsize = gridsize; + gridsizeinv = 1.0f / gridsize; + + this.sectorsize = sectorsize; + + UpdateLinesAndPoints(); + } + + #endregion + + #region ================== Methods + + public void UpdateLinesAndPoints() + { + lines[Highlight.OuterLeft] = new Line2D(outerleft, outertop, outerleft, outerbottom); + lines[Highlight.OuterRight] = new Line2D(outerright, outertop, outerright, outerbottom); + lines[Highlight.OuterTop] = new Line2D(outerleft, outertop, outerright, outertop); + lines[Highlight.OuterBottom] = new Line2D(outerleft, outerbottom, outerright, outerbottom); + + points[Highlight.OuterTopLeft] = new Vector2D(outerleft, outertop); + points[Highlight.OuterTopRight] = new Vector2D(outerright, outertop); + points[Highlight.OuterBottomLeft] = new Vector2D(outerleft, outerbottom); + points[Highlight.OuterBottomRight] = new Vector2D(outerright, outerbottom); + + outerborder = new RectangleF((float)outerleft, (float)outertop, (float)(outerright - outerleft), (float)(outerbottom - outertop)); + } + + public void Draw(IRenderer2D renderer, Highlight highlight) + { + PixelColor fcolor = highlight == Highlight.Body ? fillhighlightcolor : fillcolor; + + renderer.RenderRectangleFilled( + new RectangleF((float)outerleft, (float)outertop, (float)(outerright - outerleft), (float)(outerbottom - outertop)), + fcolor, + true + ); + + // Draw the borders + renderer.RenderRectangle(outerborder, 1.0f, bordercolor, true); + + // Highlight a border if necessary + if (highlight >= Highlight.OuterLeft && highlight <= Highlight.OuterBottom) + renderer.RenderLine(lines[highlight].v1, lines[highlight].v2, 1.0f, borderhighlightcolor, true); + else + { + // Highlight the corners + switch (highlight) + { + // Outer corners + case Highlight.OuterTopLeft: + renderer.RenderLine(lines[Highlight.OuterTop].v1, lines[Highlight.OuterTop].v2, 1.0f, borderhighlightcolor, true); + renderer.RenderLine(lines[Highlight.OuterLeft].v1, lines[Highlight.OuterLeft].v2, 1.0f, borderhighlightcolor, true); + break; + case Highlight.OuterTopRight: + renderer.RenderLine(lines[Highlight.OuterTop].v1, lines[Highlight.OuterTop].v2, 1.0f, borderhighlightcolor, true); + renderer.RenderLine(lines[Highlight.OuterRight].v1, lines[Highlight.OuterRight].v2, 1.0f, borderhighlightcolor, true); + break; + case Highlight.OuterBottomLeft: + renderer.RenderLine(lines[Highlight.OuterBottom].v1, lines[Highlight.OuterBottom].v2, 1.0f, borderhighlightcolor, true); + renderer.RenderLine(lines[Highlight.OuterLeft].v1, lines[Highlight.OuterLeft].v2, 1.0f, borderhighlightcolor, true); + break; + case Highlight.OuterBottomRight: + renderer.RenderLine(lines[Highlight.OuterBottom].v1, lines[Highlight.OuterBottom].v2, 1.0f, borderhighlightcolor, true); + renderer.RenderLine(lines[Highlight.OuterRight].v1, lines[Highlight.OuterRight].v2, 1.0f, borderhighlightcolor, true); + break; + } + } + } + + public Highlight CheckHighlight(Vector2D pos, double scale) + { + double distance = double.MaxValue; + double d; + Highlight highlight = Highlight.None; + + // Find a line to highlight + foreach (Highlight h in (Highlight[])Enum.GetValues(typeof(Highlight))) + { + if (h >= Highlight.OuterLeft && h <= Highlight.OuterBottom) + { + d = Line2D.GetDistanceToLine(lines[h].v1, lines[h].v2, pos, true); + + if (d <= BuilderModes.BuilderPlug.Me.HighlightRange / scale && d < distance) + { + distance = d; + highlight = h; + } + } + } + + distance = double.MaxValue; + + // Find a corner to highlight + foreach (Highlight h in (Highlight[])Enum.GetValues(typeof(Highlight))) + { + if (h >= Highlight.OuterTopLeft && h <= Highlight.OuterBottomRight) + { + d = Vector2D.Distance(pos, points[h]); + + if (d <= BuilderModes.BuilderPlug.Me.HighlightRange / scale && d < distance) + { + distance = d; + highlight = h; + } + } + } + + if (highlight != Highlight.None) + return highlight; + + if (OuterLeft < pos.x && OuterRight > pos.x && OuterTop > pos.y && OuterBottom < pos.y) + return Highlight.Body; + + return Highlight.None; + } + + public void SnapToGrid(Highlight highlight, Vector2D pos, Vector2D lastpos) + { + Vector2D newpos = GridSetup.SnappedToGrid(pos, gridsize, gridsizeinv); + + switch (highlight) + { + case Highlight.Body: + Vector2D diff = GridSetup.SnappedToGrid(pos, gridsize, gridsizeinv) - GridSetup.SnappedToGrid(lastpos, gridsize, gridsizeinv); + outerleft += diff.x; + outerright += diff.x; + outertop += diff.y; + outerbottom += diff.y; + break; + + // Outer border + case Highlight.OuterLeft: + if (newpos.x < outerright) outerleft = newpos.x; + break; + case Highlight.OuterRight: + if(newpos.x > outerleft) outerright = newpos.x; + break; + case Highlight.OuterTop: + if (newpos.y > outerbottom) outertop = newpos.y; + break; + case Highlight.OuterBottom: + if (newpos.y < outertop) outerbottom = newpos.y; + break; + + // Outer corners + case Highlight.OuterTopLeft: + if (newpos.x < outerright) outerleft = newpos.x; + if (newpos.y > outerbottom) outertop = newpos.y; + break; + case Highlight.OuterTopRight: + if (newpos.x > outerleft) outerright = newpos.x; + if (newpos.y > outerbottom) outertop = newpos.y; + break; + case Highlight.OuterBottomLeft: + if (newpos.x < outerright) outerleft = newpos.x; + if (newpos.y < outertop) outerbottom = newpos.y; + break; + case Highlight.OuterBottomRight: + if (newpos.x > outerleft) outerright = newpos.x; + if (newpos.y < outertop) outerbottom = newpos.y; + break; + } + + UpdateLinesAndPoints(); + } + + public List GetRelocatePositions(int numsectors) + { + List positions = new List(); + BlockMap blockmap = CreateBlockmap(true); + int margin = (int)((gridsize - sectorsize) / 2); + + for (int x = (int)outerleft; x < (int)outerright; x += (int)gridsize) + { + for (int y = (int)outertop; y > (int)outerbottom; y -= (int)gridsize) + { + List blocks = blockmap.GetLineBlocks( + new Vector2D(x + 1, y - 1), + new Vector2D(x + gridsize - 1, y - gridsize + 1) + ); + + // The way our blockmap is built and queried we will always get exactly one block + // Try the next position of the current one is occupied by another sector + if (blocks[0].Sectors.Any(s => SectorInNewControlSectorSpace(x, y, s))) + continue; + + positions.Add(new Vector2D(x + margin, y - margin)); + numsectors--; + + if (numsectors == 0) + return positions; + } + } + + throw new NoSpaceInCSAException("Not enough space for control sector relocation"); + } + + public List GetNewControlSectorVertices() + { + return GetNewControlSectorVertices(1); + } + + public List GetNewControlSectorVertices(int numsectors) + { + List dv = new List(); + BlockMap blockmap = CreateBlockmap(); + + int margin = (int)((gridsize - sectorsize) / 2); + + // find position for new control sector + for (int x = (int)outerleft; x < (int)outerright; x += (int)gridsize) + { + for (int y = (int)outertop; y > (int)outerbottom; y -= (int)gridsize) + { + List blocks = blockmap.GetLineBlocks( + new Vector2D(x + 1, y - 1), + new Vector2D(x + gridsize - 1, y - gridsize + 1) + ); + + // The way our blockmap is built and queried we will always get exactly one block + // Try the next position of the current one is occupied by another sector + if (blocks[0].Sectors.Any(s => SectorInNewControlSectorSpace(x, y, s))) + continue; + + Point p = new Point(x + margin, y - margin); + + dv.Add(SectorVertex(p.X, p.Y)); + dv.Add(SectorVertex(p.X + BuilderPlug.Me.ControlSectorArea.SectorSize, p.Y)); + dv.Add(SectorVertex(p.X + BuilderPlug.Me.ControlSectorArea.SectorSize, p.Y - BuilderPlug.Me.ControlSectorArea.SectorSize)); + dv.Add(SectorVertex(p.X, p.Y - BuilderPlug.Me.ControlSectorArea.SectorSize)); + dv.Add(SectorVertex(p.X, p.Y)); + + numsectors--; + + if (numsectors == 0) + return dv; + } + } + + throw new NoSpaceInCSAException("No space left for control sectors"); + } + + /// + /// Checks if the given sector and position for the new control sector intersect in any way. + /// + /// Leftmost X position of the new control sector space + /// Topmost Y position of the new control sector space + /// The sector to check against + /// true if there's an intersection, false if there isn't + private bool SectorInNewControlSectorSpace(int x, int y, Sector sector) + { + int margin = (int)((gridsize - sectorsize) / 2); + HashSet sectorvertices = new HashSet(); + + // Any of the sector's sidedef's linedef's vertices inside the new control sector's space? + foreach (Sidedef sd in sector.Sidedefs) + { + sectorvertices.Add(sd.Line.Start); + sectorvertices.Add(sd.Line.End); + } + + foreach (Vertex v in sectorvertices) + if (v.Position.x >= x + margin && v.Position.x <= x + margin + sectorsize && v.Position.y <= y - margin && v.Position.y >= y - margin - sectorsize) + return true; + + // Any of the new vertex positions in the sector? + Vector2D[] points = new Vector2D[] + { + new Vector2D(x + margin, y - margin), + new Vector2D(x + margin + sectorsize, y - margin -sectorsize), + new Vector2D(x + margin + sectorsize, y - margin - sectorsize), + new Vector2D(x + margin, y -margin) + }; + + foreach (Vector2D v in points) + if (sector.Intersect(v)) + return true; + + // Any of the new lines and the sector's lines overlapping? + Line2D[] lines = new Line2D[] + { + new Line2D(points[0], points[1]), + new Line2D(points[1], points[2]), + new Line2D(points[2], points[3]), + new Line2D(points[3], points[0]) + }; + + foreach (Sidedef sd in sector.Sidedefs) + foreach (Line2D line in lines) + if (Line2D.GetIntersection(sd.Line.Line, line)) + return true; + + return false; + } + + public bool Inside(float x, float y) + { + return Inside(new Vector2D(x, y)); + } + + public bool Inside(Vector2D pos) + { + if (pos.x > outerleft && pos.x < outerright && pos.y < outertop && pos.y > outerbottom) + return true; + + return false; + } + + public bool OutsideOuterBounds(float x, float y) + { + return OutsideOuterBounds(new Vector2D(x, y)); + } + + public bool OutsideOuterBounds(Vector2D pos) + { + if(pos.x < outerleft || pos.x > outerright || pos.y > outertop || pos.y < outerbottom) + return true; + + return false; + } + + // Aligns the area to the grid, expanding the area if necessary + private RectangleF AlignAreaToGrid(RectangleF area) + { + List f = new List + { + area.Left, + area.Top, + area.Right, + area.Bottom + }; + + for (int i = 0; i < f.Count; i++) + { + if (f[i] < 0) + f[i] = (float)Math.Floor(f[i] / gridsize) * gridsize; + else + f[i] = (float)Math.Ceiling(f[i] / gridsize) * gridsize; + } + + + float l = f[0]; + float t = f[1]; + float r = f[2]; + float b = f[3]; + + return new RectangleF(l, t, r - l, b - t); + } + + private BlockMap CreateBlockmap() + { + return CreateBlockmap(false); + } + + private BlockMap CreateBlockmap(bool ignorecontrolsectors) + { + // Make blockmap + RectangleF area = MapSet.CreateArea(General.Map.Map.Vertices); + area = MapSet.IncreaseArea(area, new Vector2D(outerleft, outertop)); + area = MapSet.IncreaseArea(area, new Vector2D(outerright, outerbottom)); + area = AlignAreaToGrid(area); + + BlockMap blockmap = new BlockMap(area, (int)gridsize); + + if (ignorecontrolsectors) + { + foreach (Sector s in General.Map.Map.Sectors) + { + // Managed control sectors have the custom UDMF field "user_managed_3d_floor" set to true + // So if the field is NOT set, add the sector to the blockmap + bool managed = s.Fields.GetValue("user_managed_3d_floor", false); + + if (managed == false) + blockmap.AddSector(s); + else // When a tag was manually removed a control sector still might have the user_managed_3d_floor field, but not be + { // recognized as a 3D floor control sector. In that case also add the sector to the blockmap + bool orphaned = true; + + foreach(ThreeDFloor tdf in ((ThreeDFloorHelperMode)General.Editing.Mode).ThreeDFloors) + { + if(tdf.Sector == s) + { + orphaned = false; + break; + } + } + + if (orphaned) + blockmap.AddSector(s); + } + } + } + else + { + blockmap.AddSectorsSet(General.Map.Map.Sectors); + } + + return blockmap; + } + + public void Edit() + { + ControlSectorAreaConfig csacfg = new ControlSectorAreaConfig(this); + + csacfg.ShowDialog((Form)General.Interface); + } + + // When OK is pressed on the preferences dialog + // Prevent inlining, otherwise there are unexpected interactions with Assembly.GetCallingAssembly + // See https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getcallingassembly?view=netframework-4.6.1#remarks + [MethodImplAttribute(MethodImplOptions.NoInlining)] + public void SaveConfig() + { + ListDictionary config = new ListDictionary(); + + config.Add("usecustomtagrange", usecustomtagrange); + + if (usecustomtagrange) + { + config.Add("firsttag", firsttag); + config.Add("lasttag", lasttag); + } + + config.Add("outerleft", outerleft); + config.Add("outerright", outerright); + config.Add("outertop", outertop); + config.Add("outerbottom", outerbottom); + + General.Map.Options.WritePluginSetting("controlsectorarea", config); + } + + // When OK is pressed on the preferences dialog + // Prevent inlining, otherwise there are unexpected interactions with Assembly.GetCallingAssembly + // See https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getcallingassembly?view=netframework-4.6.1#remarks + [MethodImplAttribute(MethodImplOptions.NoInlining)] + public void LoadConfig() + { + ListDictionary config = (ListDictionary)General.Map.Options.ReadPluginSetting("controlsectorarea", new ListDictionary()); + + usecustomtagrange = General.Map.Options.ReadPluginSetting("controlsectorarea.usecustomtagrange", false); + firsttag = General.Map.Options.ReadPluginSetting("controlsectorarea.firsttag", 0); + lasttag = General.Map.Options.ReadPluginSetting("controlsectorarea.lasttag", 0); + + outerleft = General.Map.Options.ReadPluginSetting("controlsectorarea.outerleft", outerleft); + outerright = General.Map.Options.ReadPluginSetting("controlsectorarea.outerright", outerright); + outertop = General.Map.Options.ReadPluginSetting("controlsectorarea.outertop", outertop); + outerbottom = General.Map.Options.ReadPluginSetting("controlsectorarea.outerbottom", outerbottom); + + UpdateLinesAndPoints(); + } + + public int GetNewSectorTag(List tagblacklist) + { + List usedtags = new List(); + + if (usecustomtagrange) + { + for (int i = firsttag; i <= lasttag; i++) + { + if (!tagblacklist.Contains(i) && BuilderPlug.GetSectorsByTag(i).Count == 0) + return i; + } + + throw new Exception("No free tags in the custom range between " + firsttag.ToString() + " and " + lasttag.ToString() + "."); + } + + return General.Map.Map.GetNewTag(tagblacklist); + } + + public int GetNewLineID() + { + return General.Map.Map.GetNewTag(); + } + + // Turns a position into a DrawnVertex and returns it + private DrawnVertex SectorVertex(double x, double y) + { + DrawnVertex v = new DrawnVertex(); + + v.stitch = true; + v.stitchline = true; + v.pos = new Vector2D(Math.Round(x, General.Map.FormatInterface.VertexDecimals), Math.Round(y, General.Map.FormatInterface.VertexDecimals)); + + return v; + } + + private DrawnVertex SectorVertex(Vector2D v) + { + return SectorVertex(v.x, v.y); + } + + static int GCD(int[] numbers) + { + return numbers.Aggregate(GCD); + } + + static int GCD(int a, int b) + { + return b == 0 ? a : GCD(b, a % b); + } + + #endregion + } +} diff --git a/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs b/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs index 8ba7f85a..d8d02f90 100755 --- a/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs +++ b/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs @@ -87,11 +87,15 @@ namespace CodeImp.DoomBuilder.BuilderModes { public readonly SurfaceTextureInfo Floor; public readonly SurfaceTextureInfo Ceiling; + public readonly Vertex FirstVertex; + public readonly Vector2D PreviousFirstVertexPosition; public SectorTextureInfo(Sector s) { - // Get transform properties - Floor.Offset = new Vector2D(UniFields.GetFloat(s.Fields, "xpanningfloor", 0.0), UniFields.GetFloat(s.Fields, "ypanningfloor", 0.0)); + FirstVertex = s.Sidedefs.First().Line.Start; + PreviousFirstVertexPosition = FirstVertex.Position; + // Get transform properties + Floor.Offset = new Vector2D(UniFields.GetFloat(s.Fields, "xpanningfloor", 0.0), UniFields.GetFloat(s.Fields, "ypanningfloor", 0.0)); Ceiling.Offset = new Vector2D(UniFields.GetFloat(s.Fields, "xpanningceiling", 0.0), UniFields.GetFloat(s.Fields, "ypanningceiling", 0.0)); Floor.Scale = new Vector2D(UniFields.GetFloat(s.Fields, "xscalefloor", 1.0), -UniFields.GetFloat(s.Fields, "yscalefloor", 1.0)); Ceiling.Scale = new Vector2D(UniFields.GetFloat(s.Fields, "xscaleceiling", 1.0), -UniFields.GetFloat(s.Fields, "yscaleceiling", 1.0)); @@ -105,7 +109,8 @@ namespace CodeImp.DoomBuilder.BuilderModes // Surface name Floor.Part = "floor"; Ceiling.Part = "ceiling"; - } + + } private static Size GetTextureSize(long hash) { @@ -879,15 +884,20 @@ namespace CodeImp.DoomBuilder.BuilderModes { foreach(KeyValuePair group in selectedsectors) { - group.Key.Fields.BeforeFieldsChange(); + Sector eachSector = group.Key; + SectorTextureInfo eachSectorTexInfo = group.Value; + Vector2D newFirstVertexPosition = new Vector2D( Math.Round(eachSectorTexInfo.FirstVertex.Position.x, General.Map.FormatInterface.VertexDecimals), + Math.Round(eachSectorTexInfo.FirstVertex.Position.y, General.Map.FormatInterface.VertexDecimals) ); - // Apply transforms - UpdateTextureTransform(group.Key.Fields, group.Value.Ceiling /*, transformceiloffsets, rotateceiloffsets, scaleceiloffsets */); - UpdateTextureTransform(group.Key.Fields, group.Value.Floor /*, transformflooroffsets, rotateflooroffsets, scaleflooroffsets */); + eachSector.Fields.BeforeFieldsChange(); - // Update cache - group.Key.UpdateNeeded = true; - group.Key.UpdateCache(); + // Apply transforms + UpdateTextureTransform(eachSector.Fields, eachSectorTexInfo.Ceiling, newFirstVertexPosition, eachSectorTexInfo.PreviousFirstVertexPosition); + UpdateTextureTransform(eachSector.Fields, eachSectorTexInfo.Floor, newFirstVertexPosition, eachSectorTexInfo.PreviousFirstVertexPosition); + + // Update cache + eachSector.UpdateNeeded = true; + eachSector.UpdateCache(); } // Map was changed @@ -895,18 +905,40 @@ namespace CodeImp.DoomBuilder.BuilderModes } //mxd. This updates texture transforms in given UniFields - private void UpdateTextureTransform(UniFields fields, SurfaceTextureInfo si /*, bool transformoffsets, bool rotateoffsets, bool scaleoffsets */) + private void UpdateTextureTransform(UniFields fields, SurfaceTextureInfo si, Vector2D newReferencePosition, Vector2D previousReferencePosition) { if ((si.Part == "floor" && pinfloortextures) || (si.Part == "ceiling" && pinceilingtextures)) { - double texrotation = Angle2D.PI2 - rotation; + if (si.Scale.x != 0 && si.Scale.y != 0) + { + double selectionRotationRad = Angle2D.PI2 - rotation; + double newSurfaceRotationRad = selectionRotationRad + si.Rotation; - double trotation = texrotation + si.Rotation; - Vector2D o = ((referencepoint - selectionbasecenter).GetRotated(-trotation) + selectionbasecenter + this.offset - this.baseoffset).GetRotated(trotation); + Vector2D textureSize = new Vector2D ((double)si.TextureSize.Width / si.Scale.x, + (double)si.TextureSize.Height / -si.Scale.y); - fields["xpanning" + si.Part] = new UniValue(UniversalType.Float, Math.Round(-o.x + si.Offset.x, General.Map.FormatInterface.VertexDecimals)); - fields["ypanning" + si.Part] = new UniValue(UniversalType.Float, Math.Round(o.y + si.Offset.y, General.Map.FormatInterface.VertexDecimals)); - fields["rotation" + si.Part] = new UniValue(UniversalType.Float, General.ClampAngle(Math.Round(Angle2D.RadToDeg(trotation), General.Map.FormatInterface.VertexDecimals))); + double previousSurfaceRotationRad = Angle2D.PI2 - si.Rotation; + + //Set the new surface texture rotation + double newSurfaceRotationDegrees = General.ClampAngle(Math.Round(Angle2D.RadToDeg(newSurfaceRotationRad), General.Map.FormatInterface.VertexDecimals)); + fields["rotation" + si.Part] = new UniValue(UniversalType.Float, newSurfaceRotationDegrees); + + //Find the offset required to place the texture origin point at the reference vector + Vector2D globalOffsetForNewReferencePoint = ConvertToOffsetCoordinates(newReferencePosition); + Vector2D surfaceOffsetForNewReferencePoint = GetClampedOffsetVector(globalOffsetForNewReferencePoint.GetRotated(-newSurfaceRotationRad), textureSize); + + //find an "origin point offset" using the previous texture offset, relative to our reference vertex + Vector2D rotatedPreviousReferencePosition = GetClampedOffsetVector(previousReferencePosition.GetRotated(-previousSurfaceRotationRad), textureSize); + Vector2D previousSurfaceOffset = ConvertToOffsetCoordinates(GetClampedOffsetVector(si.Offset, textureSize)); + Vector2D localSurfaceAdjustment = rotatedPreviousReferencePosition - previousSurfaceOffset; + + //Adjust our offset by applying using the "origin point offset" to the offset for our reference vertex + Vector2D adjustedSurfaceOffset = GetClampedOffsetVector(surfaceOffsetForNewReferencePoint - ConvertToOffsetCoordinates(localSurfaceAdjustment), textureSize); + + //Set the new texture offset + fields["xpanning" + si.Part] = new UniValue(UniversalType.Float, adjustedSurfaceOffset.x); + fields["ypanning" + si.Part] = new UniValue(UniversalType.Float, adjustedSurfaceOffset.y); + } } else { @@ -919,6 +951,25 @@ namespace CodeImp.DoomBuilder.BuilderModes } } + private Vector2D ConvertToOffsetCoordinates(Vector2D v) + { + return new Vector2D(-v.x, v.y); + } + + private Vector2D GetClampedOffsetVector(Vector2D v, Vector2D textureSize) + { + Vector2D roundedV = new Vector2D( + Math.Round(v.x, General.Map.FormatInterface.VertexDecimals), + Math.Round(v.y, General.Map.FormatInterface.VertexDecimals)); + Vector2D roundedTextureSize = new Vector2D( + Math.Round(textureSize.x, General.Map.FormatInterface.VertexDecimals), + Math.Round(textureSize.y, General.Map.FormatInterface.VertexDecimals)); + + return new Vector2D(roundedV.x % roundedTextureSize.x, + roundedV.y % roundedTextureSize.y); + + } + //mxd. This restores texture transforms for all sectors private void RestoreTextureTransform() { @@ -1300,8 +1351,13 @@ namespace CodeImp.DoomBuilder.BuilderModes if (General.Map.UDMF) { foreach (Sector s in General.Map.Map.GetSectorsFromLinedefs(selectedlines)) - if(!s.Fields.ContainsKey(MapSet.VIRTUAL_SECTOR_FIELD)) // Ignore sectors that have the VIRTUAL_SECTOR_FIELD UDMF field created when cloning the MapSet when copying - selectedsectors.Add(s, new SectorTextureInfo(s)); + { + if (!s.Fields.ContainsKey(MapSet.VIRTUAL_SECTOR_FIELD)) // Ignore sectors that have the VIRTUAL_SECTOR_FIELD UDMF field created when cloning the MapSet when copying + { + selectedsectors.Add(s, new SectorTextureInfo(s)); + + } + } } // Array to keep original coordinates diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySector.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySector.cs index 67fcbdce..32e52bf1 100755 --- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySector.cs +++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySector.cs @@ -101,7 +101,7 @@ namespace CodeImp.DoomBuilder.BuilderModes public virtual void SelectNeighbours(bool select, bool withSameTexture, bool withSameHeight) { } //mxd //mxd - override protected void PerformAutoSelection() + override public void PerformAutoSelection() { if(!performautoselection) return; if(Triangles > 0) diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs index 988d4af5..45f4f7ec 100755 --- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs +++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs @@ -94,7 +94,7 @@ namespace CodeImp.DoomBuilder.BuilderModes #region ================== Methods //mxd - override protected void PerformAutoSelection() + public override void PerformAutoSelection() { if(!performautoselection) return; if(Triangles > 0) diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs index 5028cce5..080df7a5 100755 --- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs +++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs @@ -1515,8 +1515,53 @@ namespace CodeImp.DoomBuilder.BuilderModes // (Re)create special effects RebuildElementData(); - //mxd. Update event lines - renderer.SetEventLines(LinksCollector.GetHelperShapes(General.Map.ThingsFilter.VisibleThings, blockmap)); + // Objects are only selected when they are created, so for objects that are selected we have to make sure + // that they are created immediately. Otherwise the selection order will not be correct, or the objects + // will not be selected at all if they are out of the user's camera range when entering visual mode + // See https://github.com/jewalky/UltimateDoomBuilder/issues/938 + if (useSelectionFromClassicMode) + { + foreach (Sector s in General.Map.Map.GetSelectedSectors(true)) + { + BaseVisualSector bvs = CreateBaseVisualSector(s); + bvs.Ceiling.PerformAutoSelection(); + bvs.Floor.PerformAutoSelection(); + } + + // Things are automatically selected on creation + foreach (Thing t in General.Map.Map.GetSelectedThings(true)) + CreateVisualThing(t); + + // For linedefs it's a bit more complicated... + foreach (Linedef ld in General.Map.Map.GetSelectedLinedefs(true)) + { + foreach (Sidedef sd in new Sidedef[] { ld.Front, ld.Back }) + { + if (sd != null) + { + if (!allsectors.ContainsKey(sd.Sector)) + CreateBaseVisualSector(sd.Sector).Rebuild(); // We have to rebuild the sector so that potential 3D floors get created + + VisualSidedefParts vsp = ((BaseVisualSector)allsectors[sd.Sector]).Sides[sd]; + vsp.upper?.PerformAutoSelection(); + vsp.middlesingle?.PerformAutoSelection(); + vsp.middledouble?.PerformAutoSelection(); + vsp.lower?.PerformAutoSelection(); + + if (vsp.middle3d != null) + foreach (VisualMiddle3D vm in vsp.middle3d) + vm.PerformAutoSelection(); + + if (vsp.middleback != null) + foreach (VisualMiddleBack vm in vsp.middleback) + vm.PerformAutoSelection(); + } + } + } + } + + //mxd. Update event lines + renderer.SetEventLines(LinksCollector.GetHelperShapes(General.Map.ThingsFilter.VisibleThings, blockmap)); // [ZZ] this enables calling of this object from the outside world. Only after properly initialized pls. base.OnEngage(); diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualThing.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualThing.cs old mode 100755 new mode 100644 index 2c9ef2d9..7f309cd0 --- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualThing.cs +++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualThing.cs @@ -218,10 +218,16 @@ namespace CodeImp.DoomBuilder.BuilderModes { double thingz = Thing.IsFlipped ? sd.Ceiling.plane.GetZ(Thing.Position) - Thing.Position.z - Thing.Height : Thing.Position.z + sd.Floor.plane.GetZ(Thing.Position); Vector3D thingpos = new Vector3D(Thing.Position.x, Thing.Position.y, thingz); - SectorLevel level = sd.GetLevelAboveOrAt(thingpos); + + // If is thing's height is flush to a 3D floor top it's not rendered at the brightness of the 3D floor, so take the level above that. + // It's actually a bit more intricate, since GZDoom can render multiple vertical brightness levels for each thing, which UDB can't, + // so this is more of a workaround than a real solution + // See https://github.com/jewalky/UltimateDoomBuilder/issues/940 + //SectorLevel level = sd.GetLevelAboveOrAt(thingpos); + SectorLevel level = sd.GetLevelAbove(thingpos); //mxd. Let's use point on floor plane instead of Thing.Sector.FloorHeight; - if(nointeraction && level == null && sd.LightLevels.Count > 0) level = sd.LightLevels[sd.LightLevels.Count - 1]; + if (nointeraction && level == null && sd.LightLevels.Count > 0) level = sd.LightLevels[sd.LightLevels.Count - 1]; //mxd. Use the light level of the highest surface when a thing is above highest sector level. if(level != null) @@ -324,12 +330,8 @@ namespace CodeImp.DoomBuilder.BuilderModes // Determine sprite size and offset float radius = sprite.ScaledWidth * 0.5f; float height = sprite.ScaledHeight; - ISpriteImage spriteimg = sprite as ISpriteImage; - if(spriteimg != null) - { - offsets.x = radius - spriteimg.OffsetX; - offsets.y = spriteimg.OffsetY - height; - } + offsets.x = radius - (sprite.OffsetX == int.MinValue ? 0 : sprite.OffsetX); + offsets.y = (sprite.OffsetY == int.MinValue ? 0 : sprite.OffsetY) - height; // Scale by thing type/actor scale // We do this after the offset x/y determination above, because that is entirely in sprite pixels space diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualLower.cs b/Source/Plugins/BuilderModes/VisualModes/VisualLower.cs index 4b1b0053..4f129bbd 100755 --- a/Source/Plugins/BuilderModes/VisualModes/VisualLower.cs +++ b/Source/Plugins/BuilderModes/VisualModes/VisualLower.cs @@ -386,11 +386,11 @@ namespace CodeImp.DoomBuilder.BuilderModes string skewtype = Sidedef.Fields.GetValue("skew_bottom_type", "none"); - if ((skewtype == "front" || skewtype == "back") && Texture != null) + if ((skewtype == "front_floor" || skewtype == "front_ceiling" || skewtype == "back_floor" || skewtype == "back_ceiling") && Texture != null) { double leftz, rightz; - if (skewtype == "front") + if (skewtype == "front_floor") { if (Sidedef.IsFront) { @@ -405,7 +405,7 @@ namespace CodeImp.DoomBuilder.BuilderModes rightz = plane.GetZ(Sidedef.Line.Start.Position); } } - else // "back" + else if(skewtype == "back_floor") { if (Sidedef.IsFront) { @@ -419,7 +419,36 @@ namespace CodeImp.DoomBuilder.BuilderModes leftz = plane.GetZ(Sidedef.Line.End.Position); rightz = plane.GetZ(Sidedef.Line.Start.Position); } - + } + else if(skewtype == "front_ceiling") + { + if (Sidedef.IsFront) + { + Plane plane = Sector.GetSectorData().Ceiling.plane; + leftz = plane.GetZ(Sidedef.Line.Start.Position); + rightz = plane.GetZ(Sidedef.Line.End.Position); + } + else + { + Plane plane = mode.GetSectorData(Sidedef.Other.Sector).Ceiling.plane; + leftz = plane.GetZ(Sidedef.Line.End.Position); + rightz = plane.GetZ(Sidedef.Line.Start.Position); + } + } + else // Back ceiling + { + if (Sidedef.IsFront) + { + Plane plane = mode.GetSectorData(Sidedef.Other.Sector).Ceiling.plane; + leftz = plane.GetZ(Sidedef.Line.Start.Position); + rightz = plane.GetZ(Sidedef.Line.End.Position); + } + else + { + Plane plane = Sector.GetSectorData().Ceiling.plane; + leftz = plane.GetZ(Sidedef.Line.End.Position); + rightz = plane.GetZ(Sidedef.Line.Start.Position); + } } skew = new Vector2f( diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs b/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs index 2328b12c..5876bb68 100755 --- a/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs +++ b/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs @@ -378,11 +378,11 @@ namespace CodeImp.DoomBuilder.BuilderModes string skewtype = Sidedef.Fields.GetValue("skew_top_type", "none"); - if ((skewtype == "front" || skewtype == "back") && Texture != null) + if ((skewtype == "front_floor" || skewtype == "front_ceiling" || skewtype == "back_floor" || skewtype == "back_ceiling") && Texture != null) { double leftz, rightz; - if (skewtype == "front") + if (skewtype == "front_ceiling") { if (Sidedef.IsFront) { @@ -397,7 +397,7 @@ namespace CodeImp.DoomBuilder.BuilderModes rightz = plane.GetZ(Sidedef.Line.Start.Position); } } - else // "back" + else if (skewtype == "back_ceiling") { if (Sidedef.IsFront) { @@ -411,7 +411,36 @@ namespace CodeImp.DoomBuilder.BuilderModes leftz = plane.GetZ(Sidedef.Line.End.Position); rightz = plane.GetZ(Sidedef.Line.Start.Position); } - + } + else if(skewtype == "front_floor") + { + if(Sidedef.IsFront) + { + Plane plane = Sector.GetSectorData().Floor.plane; + leftz = plane.GetZ(Sidedef.Line.Start.Position); + rightz = plane.GetZ(Sidedef.Line.End.Position); + } + else + { + Plane plane = mode.GetSectorData(Sidedef.Other.Sector).Floor.plane; + leftz = plane.GetZ(Sidedef.Line.End.Position); + rightz = plane.GetZ(Sidedef.Line.Start.Position); + } + } + else // Back floor + { + if (Sidedef.IsFront) + { + Plane plane = mode.GetSectorData(Sidedef.Other.Sector).Floor.plane; + leftz = plane.GetZ(Sidedef.Line.Start.Position); + rightz = plane.GetZ(Sidedef.Line.End.Position); + } + else + { + Plane plane = Sector.GetSectorData().Floor.plane; + leftz = plane.GetZ(Sidedef.Line.End.Position); + rightz = plane.GetZ(Sidedef.Line.Start.Position); + } } skew = new Vector2f( diff --git a/Source/Plugins/SoundPropagationMode/BuilderPlug.cs b/Source/Plugins/SoundPropagationMode/BuilderPlug.cs index 2acfb699..dda75005 100755 --- a/Source/Plugins/SoundPropagationMode/BuilderPlug.cs +++ b/Source/Plugins/SoundPropagationMode/BuilderPlug.cs @@ -41,6 +41,12 @@ namespace CodeImp.DoomBuilder.SoundPropagationMode // Make sure the class is public, because only public classes can be seen // by the core. // + + internal class ToastMessages + { + public static readonly string SOUNDPROPAGATIONMODE = "soundpropagationmode"; + } + public class BuilderPlug : Plug { #region ================== Constants @@ -170,6 +176,9 @@ namespace CodeImp.DoomBuilder.SoundPropagationMode // Keep a static reference me = this; + + // Register toasts + General.ToastManager.RegisterToast(ToastMessages.SOUNDPROPAGATIONMODE, "Sound propagation mode", "Toasts related to sound propagation mode"); } public override void OnMapOpenBegin() diff --git a/Source/Plugins/SoundPropagationMode/LeakFinder.cs b/Source/Plugins/SoundPropagationMode/LeakFinder.cs new file mode 100644 index 00000000..3a937af9 --- /dev/null +++ b/Source/Plugins/SoundPropagationMode/LeakFinder.cs @@ -0,0 +1,221 @@ +#region ================== Copyright (c) 2023 Boris Iwanski + +/* + * This program is free software: you can redistribute it and/or modify + * + * it under the terms of the GNU General Public License as published by + * + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.If not, see. + */ + +#endregion + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CodeImp.DoomBuilder.Geometry; +using CodeImp.DoomBuilder.Map; + +namespace CodeImp.DoomBuilder.SoundPropagationMode +{ + internal class LeakFinder + { + public SoundNode Start { get; } + public SoundNode End { get; } + public List Nodes { get; } + public HashSet Sectors { get; } + public bool Finished { get; internal set; } + + private ConcurrentDictionary linedefs2nodes; + private int numblockingnodes; + + public LeakFinder(Sector source, Vector2D sourceposition, Sector destination, Vector2D destinationposition, HashSet sectors) + { + if (!sectors.Contains(source) || !sectors.Contains(destination)) + throw new ArgumentException("Sound propagation domain does not contain both the start and end sectors"); + + End = new SoundNode(destinationposition); + Start = new SoundNode(sourceposition, End) { G = 0 }; + Sectors = sectors; + + Finished = false; + + Nodes = new List() { Start, End }; + + linedefs2nodes = new ConcurrentDictionary(); + + GenerateNodes(sectors); + + PopulateStartEndNeighbors(source, Start); + PopulateStartEndNeighbors(destination, End); + } + + /// + /// Checks if the linedef is valid for passing sound. + /// + /// The linedef to check + /// true if sound can travel through the linedef, false if not + private bool CheckLinedefValidity(Linedef linedef) + { + if (linedef.Back == null) + return false; + + if (linedef.Front.Sector == linedef.Back.Sector) + return false; + + if (SoundPropagationDomain.IsSoundBlockedByHeight(linedef)) + return false; + + return Sectors.Contains(linedef.Front.Sector) && Sectors.Contains(linedef.Back.Sector); + } + + /// + /// Generates all nodes for the A* search algorithm. + /// + /// sectors to generate the nodes from + private void GenerateNodes(HashSet sectors) + { + // Create sound nodes for all valid linedefs in all given sectors + foreach(Sector s in sectors) + { + IEnumerable sidedefs = s.Sidedefs.Where(sd => CheckLinedefValidity(sd.Line)); + + foreach(Sidedef sd in sidedefs) + { + if(!linedefs2nodes.ContainsKey(sd.Line)) + { + linedefs2nodes[sd.Line] = new SoundNode(sd.Line, End); + Nodes.Add(linedefs2nodes[sd.Line]); + } + } + } + + // We need the number of blocking nodes for safety checking + numblockingnodes = linedefs2nodes.Values.Count(n => n.IsBlocking); + + // Set the neighbors for each node. The amount of interconnections can be very high in complex maps + // (for example there are nearly 3.9 million in Sunder map 20), so do it in parallel for speed + Parallel.ForEach(linedefs2nodes.Keys, ld => + { + foreach (Sidedef sd in ld.Front.Sector.Sidedefs) + { + if (sd.Line != ld && CheckLinedefValidity(sd.Line)) + linedefs2nodes[ld].Neighbors.Add(linedefs2nodes[sd.Line]); + } + + foreach (Sidedef sd in ld.Back.Sector.Sidedefs) + { + if (sd.Line != ld && CheckLinedefValidity(sd.Line)) + linedefs2nodes[ld].Neighbors.Add(linedefs2nodes[sd.Line]); + } + }); + +#if DEBUG + int bla = linedefs2nodes.Values.Sum(n => n.Neighbors.Count); + Console.WriteLine($"There are {linedefs2nodes.Keys.Count} nodes with {bla} interconnections."); +#endif + } + + /// + /// Populates a sound node's neightbors to the linedefs of a sector. This is required for the start and end sound nodes. + /// + /// The sector which linedef's sound nodes are used + /// The sound node to add the neighbors to + private void PopulateStartEndNeighbors(Sector sector, SoundNode node) + { + foreach(Sidedef sd in sector.Sidedefs) + { + if(CheckLinedefValidity(sd.Line) && linedefs2nodes.ContainsKey(sd.Line)) + { + node.Neighbors.Add(linedefs2nodes[sd.Line]); + linedefs2nodes[sd.Line].Neighbors.Add(node); + } + } + } + + /// + /// Finds a sound leak between the start and end sound nodes. + /// + /// true if a leak was found, false if no leak was found + public bool FindLeak() + { + Finished = false; + + // Basic A* search. The twist is that sound blocking lines: we can only pass through one of them, + // and A* doesn't backtrack, so it can fail to find a path even if there is a possible one. If that + // happens we set the sound blocking node we traveled through to be ignored, and start again. We repeat + // that until a path was found, or all blocking nodes are set to be ignored (which shouldn't happen) + while (true) + { + List openset = new List() { Start }; + + while (openset.Count > 0) + { + // Find the node with the lowest F score. Doing it that way seems to be fastest + SoundNode current = openset[0]; + for (int i = 1; i < openset.Count; i++) + { + if (openset[i].F < current.F) + current = openset[i]; + } + + // We're done if the node with the lowest F score is the end node + if (current == End) + { + Finished = true; + return true; + } + + // Remove the current node from the open set + openset.Remove(current); + + // Compute new values for the current node's neighbors + current.ProcessNeighbors(openset, Start); + } + + // If we got here we didn't find a path. So we have to start over + + int currentnumblockingnodes = 0; + + // Reset all nodes + foreach(SoundNode sn in Nodes) + { + // Set the sound nodes that block sound and were visited (the G value was set to something) to be skipped. + if(sn.IsBlocking && sn.G != double.MaxValue) + { + sn.IsSkip = true; + currentnumblockingnodes++; + } + + // We need to reset the sound node's G and F values + sn.Reset(); + } + + // All blocking sound nodes are being skipped, so no path is possible + if (currentnumblockingnodes == numblockingnodes) + { + Finished = true; + + return false; + } + + // Don't forget the reset the start node to its special values + Start.G = 0.0; + Start.F = Start.H; + } + } + } +} diff --git a/Source/Plugins/SoundPropagationMode/Properties/Resources.Designer.cs b/Source/Plugins/SoundPropagationMode/Properties/Resources.Designer.cs index 869ff5c9..f8720250 100755 --- a/Source/Plugins/SoundPropagationMode/Properties/Resources.Designer.cs +++ b/Source/Plugins/SoundPropagationMode/Properties/Resources.Designer.cs @@ -70,6 +70,16 @@ namespace CodeImp.DoomBuilder.SoundPropagationMode.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap SoundPropagationIcon { + get { + object obj = ResourceManager.GetObject("SoundPropagationIcon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/Source/Plugins/SoundPropagationMode/Properties/Resources.resx b/Source/Plugins/SoundPropagationMode/Properties/Resources.resx index 5c5c663f..b1821bd2 100755 --- a/Source/Plugins/SoundPropagationMode/Properties/Resources.resx +++ b/Source/Plugins/SoundPropagationMode/Properties/Resources.resx @@ -112,15 +112,18 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + ..\Resources\ColorManagement.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\SoundPropagationIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\Status0.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/Source/Plugins/SoundPropagationMode/Resources/Actions.cfg b/Source/Plugins/SoundPropagationMode/Resources/Actions.cfg index d671e833..1a80e22d 100755 --- a/Source/Plugins/SoundPropagationMode/Resources/Actions.cfg +++ b/Source/Plugins/SoundPropagationMode/Resources/Actions.cfg @@ -54,3 +54,25 @@ soundpropagationcolorconfiguration allowmouse = true; allowscroll = true; } + +setleakfinderstart +{ + title = "Set leak finder start sector"; + category = "soundpropagationmode"; + description = "Sets the starting sector for the sound leak finder"; + allowkeys = true; + allowmouse = true; + allowscroll = true; + default = 65619; // Shift+S +} + +setleakfinderend +{ + title = "Set leak finder end sector"; + category = "soundpropagationmode"; + description = "Sets the ending sector for the sound leak finder"; + allowkeys = true; + allowmouse = true; + allowscroll = true; + default = 65605; // Shift+E +} \ No newline at end of file diff --git a/Source/Plugins/SoundPropagationMode/Resources/Hints.cfg b/Source/Plugins/SoundPropagationMode/Resources/Hints.cfg new file mode 100644 index 00000000..35751449 --- /dev/null +++ b/Source/Plugins/SoundPropagationMode/Resources/Hints.cfg @@ -0,0 +1,8 @@ +class SoundPropagationMode +group general +"This mode shows between which sectors sound can travel. Highlight a sector to see where the sound can travel freely (default color: green), and which sectors are behind a single sound blocking line (default color: yellow). Sound can not travel to (default) gray colored sectors. If no sector is highlighted each sound propagation zone is shown in a different color" +"Hold builder_pan_view and move the mouse to pan the view" +"Press builder_classicselect to toggle the sound blocking flag on the highlighted line" +"Press soundpropagationmode_setleakfinderstart to set the start sector, and soundpropagationmode_setleakfinderend to set the end sector to find a sound leak between them. Depending on the map size finding the leak can take some time." +"Press builder_clearselection to clear the start and end sectors for finding a sound leak" +"Press soundpropagationmode_soundpropagationcolorconfiguration or use the button in the tool bar to configure the colors" \ No newline at end of file diff --git a/Source/Plugins/SoundPropagationMode/SoundNode.cs b/Source/Plugins/SoundPropagationMode/SoundNode.cs new file mode 100644 index 00000000..aa9e9a6f --- /dev/null +++ b/Source/Plugins/SoundPropagationMode/SoundNode.cs @@ -0,0 +1,148 @@ +#region ================== Copyright (c) 2023 Boris Iwanski + +/* + * This program is free software: you can redistribute it and/or modify + * + * it under the terms of the GNU General Public License as published by + * + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.If not, see. + */ + +#endregion + +using System.Collections.Generic; +using System.Drawing; +using CodeImp.DoomBuilder.Geometry; +using CodeImp.DoomBuilder.Map; +using CodeImp.DoomBuilder.Rendering; + +namespace CodeImp.DoomBuilder.SoundPropagationMode +{ + internal class SoundNode + { + public Vector2D Position { get; set; } + public List Neighbors { get; set; } + public SoundNode From { get; set; } + public double G { get; set; } + public double H { get; } + public double F { get; set; } // It's G + H, but computing it on the fly is too expensive + public bool IsBlocking { get; } + public bool IsSkip { get; set; } + + public SoundNode(Vector2D position) + { + Position = position; + G = double.MaxValue; + H = double.MaxValue; + IsBlocking = false; + IsSkip = false; + Neighbors = new List(); + } + + public SoundNode(Vector2D position, SoundNode destination): this(position) + { + H = Vector2D.Distance(Position, destination.Position); + } + + public SoundNode(Linedef linedef, SoundNode destination) : this(linedef.Line.GetCoordinatesAt(0.5), destination) + { + IsBlocking = linedef.IsFlagSet(SoundPropagationMode.BlockSoundFlag); + } + + /// + /// Recomputes the values for the sound node's neighbors + /// + /// The open set, the add the neighbor to if necessary + /// The start sound node + public void ProcessNeighbors(List openset, SoundNode start) + { + bool blockinginpath = HasBlockingInPath(start); + + foreach (SoundNode neighbor in Neighbors) + { + // Skip neighbors that are blocking if there's already a blocking sound node in the path + // Also skip neighbors that are set to be skipped + if ((neighbor.IsBlocking && blockinginpath) || neighbor.IsSkip) + continue; + + double newg = G + Vector2D.Distance(Position, neighbor.Position); + + // Compute new values if the path is better + if (newg < neighbor.G) + { + neighbor.From = this; + neighbor.G = newg; + neighbor.F = neighbor.G + neighbor.H; + + if (!openset.Contains(neighbor)) + openset.Add(neighbor); + } + } + } + + /// + /// Checks if the path from this sound node to the start sound node has a blocking sound node + /// + /// The start sound node + /// true if there is a blocking sound node in the path, false if there isn't + private bool HasBlockingInPath(SoundNode start) + { + SoundNode current = this; + while(current != start) + { + if (current.IsBlocking) + return true; + current = current.From; + } + + return false; + } + + /// + /// Resets the sound node's G and F values, and the sound node that leads here + /// + public void Reset() + { + From = null; + G = double.MaxValue; + F = double.MaxValue; + } + + /// + /// Renders the path from this node to the beginning. Traces the path from this sound node back to the start sound node + /// + /// The Renderer2D to render with + internal void RenderPath(IRenderer2D renderer) + { + SoundNode current = this; + + // If the current node is null we have reached the beginning + while(current != null) + { + // Do not render the start and end sound nodes + if (current != this && current.From != null) + { + RectangleF rectangle = new RectangleF((float)(current.Position.x - 4 / renderer.Scale), (float)(current.Position.y - 4 / renderer.Scale), 8 / renderer.Scale, 8 / renderer.Scale); + renderer.RenderRectangleFilled(rectangle, PixelColor.FromColor(Color.Red), true); + } + + if(current.From != null) + renderer.RenderLine(current.Position, current.From.Position, 1.0f, PixelColor.FromColor(Color.Red), true); + + // One step back + current = current.From; + } + } + } +} diff --git a/Source/Plugins/SoundPropagationMode/SoundPropagation.csproj b/Source/Plugins/SoundPropagationMode/SoundPropagation.csproj index fe7311ef..ec40b5c9 100755 --- a/Source/Plugins/SoundPropagationMode/SoundPropagation.csproj +++ b/Source/Plugins/SoundPropagationMode/SoundPropagation.csproj @@ -118,6 +118,7 @@ SoundEnvironmentPanel.cs + True True @@ -125,6 +126,7 @@ + Form @@ -193,6 +195,9 @@ true + + + + --> \ No newline at end of file