#region ================== Namespaces using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; using CodeImp.DoomBuilder.Actions; using CodeImp.DoomBuilder.Controls; using CodeImp.DoomBuilder.Editing; using CodeImp.DoomBuilder.Geometry; using CodeImp.DoomBuilder.Map; using CodeImp.DoomBuilder.Rendering; using CodeImp.DoomBuilder.Windows; #endregion namespace CodeImp.DoomBuilder.BuilderModes { [EditMode(DisplayName = "Draw Grid Mode", SwitchAction = "drawgridmode", ButtonImage = "DrawGridMode.png", //mxd ButtonOrder = int.MinValue + 5, //mxd ButtonGroup = "000_drawing", //mxd AllowCopyPaste = false, Volatile = true, Optional = false)] public class DrawGridMode : DrawGeometryMode { #region ================== Enums public enum GridLockMode { NONE, HORIZONTAL, VERTICAL, BOTH, } #endregion #region ================== Variables // Settings private int horizontalslices; private int verticalslices; private bool triangulate; private bool relativeinterpolation; private GridLockMode gridlockmode; private InterpolationTools.Mode horizontalinterpolation; private InterpolationTools.Mode verticalinterpolation; // Drawing private readonly List gridpoints; private HintLabel hintlabel; private int width; private int height; private int slicesH; private int slicesV; private Vector2D start; private Vector2D end; // Interface private DrawGridOptionsPanel panel; private Docker docker; #endregion #region ================== Constructor public DrawGridMode() { snaptogrid = true; usefourcardinaldirections = true; autoclosedrawing = false; gridpoints = new List(); } #endregion #region ================== Events override public void OnAccept() { Cursor.Current = Cursors.AppStarting; General.Settings.FindDefaultDrawSettings(); // When we have a shape... if(gridpoints.Count > 0) { // Make undo for the draw General.Map.UndoRedo.CreateUndo("Grid draw"); // Make an analysis and show info string[] adjectives = new[] { "beautiful", "lovely", "romantic", "stylish", "cheerful", "comical", "awesome", "accurate", "adorable", "adventurous", "attractive", "cute", "elegant", "glamorous", "gorgeous", "handsome", "magnificent", "unusual", "outstanding", "mysterious", "amusing", "charming", "fantastic", "jolly" }; string word = adjectives[new Random().Next(adjectives.Length - 1)]; string a = ((word[0] == 'a') || (word[0] == 'e') || (word[0] == 'o') || (word[0] == 'u')) ? "an " : "a "; General.Interface.DisplayStatus(StatusType.Action, "Created " + a + word + " grid."); List newsectors = new List(); foreach(DrawnVertex[] shape in gridpoints) { if(Tools.DrawLines(shape, true, BuilderPlug.Me.AutoAlignTextureOffsetsOnCreate)) { // Update cached values after each step... General.Map.Map.Update(); newsectors.AddRange(General.Map.Map.GetMarkedSectors(true)); // Snap to map format accuracy General.Map.Map.SnapAllToAccuracy(); // Clear selection General.Map.Map.ClearAllSelected(); //mxd. Outer sectors may require some splittin... if(General.Settings.SplitJoinedSectors) Tools.SplitOuterSectors(General.Map.Map.GetMarkedLinedefs(true)); // Edit new sectors? if(BuilderPlug.Me.EditNewSector && (newsectors.Count > 0)) General.Interface.ShowEditSectors(newsectors); // Update the used textures General.Map.Data.UpdateUsedTextures(); //mxd General.Map.Renderer2D.UpdateExtraFloorFlag(); // Map is changed General.Map.IsChanged = true; } else { // Drawing failed // NOTE: I have to call this twice, because the first time only cancels this volatile mode General.Map.UndoRedo.WithdrawUndo(); General.Map.UndoRedo.WithdrawUndo(); } } } // Done Cursor.Current = Cursors.Default; if(continuousdrawing) { // Reset settings points.Clear(); labels.Clear(); drawingautoclosed = false; // Redraw display General.Interface.RedrawDisplay(); } else { // Return to original mode General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name); } } public override void OnDisengage() { if(hintlabel != null) hintlabel.Dispose(); base.OnDisengage(); } private void OptionsPanelOnValueChanged(object sender, EventArgs eventArgs) { triangulate = panel.Triangulate; horizontalslices = panel.HorizontalSlices + 1; verticalslices = panel.VerticalSlices + 1; horizontalinterpolation = panel.HorizontalInterpolationMode; verticalinterpolation = panel.VerticalInterpolationMode; Update(); } private void OptionsPanelOnGridLockChanged(object sender, EventArgs eventArgs) { gridlockmode = panel.GridLockMode; General.Hints.ShowHints(this.GetType(), ((gridlockmode != GridLockMode.NONE) ? "gridlockhelp" : "general")); Update(); } public override void OnHelp() { General.ShowHelp("/gzdb/features/classic_modes/mode_drawgrid.html"); } #endregion #region ================== Methods override protected void Update() { PixelColor stitchcolor = General.Colors.Highlight; PixelColor losecolor = General.Colors.Selection; // We WANT snaptogrid and DON'T WANT snaptonearest when lock to grid is enabled snaptocardinaldirection = General.Interface.ShiftState && General.Interface.AltState; //mxd snaptogrid = (snaptocardinaldirection || gridlockmode != GridLockMode.NONE || (General.Interface.ShiftState ^ General.Interface.SnapToGrid)); snaptonearest = (gridlockmode == GridLockMode.NONE && (General.Interface.CtrlState ^ General.Interface.AutoMerge)); DrawnVertex curp = GetCurrentPosition(); Vector2D curvertexpos = curp.pos; float vsize = (renderer.VertexSize + 1.0f) / renderer.Scale; curp.pos = curp.pos.GetRotated(-General.Map.Grid.GridRotate); // Render drawing lines if(renderer.StartOverlay(true)) { PixelColor color = snaptonearest ? stitchcolor : losecolor; if (points.Count == 1) { UpdateReferencePoints(points[0], curp); List shapes = GetShapes(start, end); Vector2D startrotated = start.GetRotated(General.Map.Grid.GridRotate); Vector2D endrotated = end.GetRotated(General.Map.Grid.GridRotate); // Rotate the shape to fit the grid rotation foreach (Vector2D[] shape in shapes) { for(int i=0; i < shape.Length; i++) shape[i] = shape[i].GetRotated(General.Map.Grid.GridRotate); } // Render guidelines if(showguidelines) RenderGuidelines(startrotated, endrotated, General.Colors.Guideline.WithAlpha(80), -General.Map.Grid.GridRotate); //render shape foreach(Vector2D[] shape in shapes) { for(int i = 1; i < shape.Length; i++) renderer.RenderLine(shape[i - 1], shape[i], LINE_THICKNESS, color, true); } //vertices foreach(Vector2D[] shape in shapes) { for(int i = 0; i < shape.Length; i++) renderer.RenderRectangleFilled(new RectangleF((float)(shape[i].x - vsize), (float)(shape[i].y - vsize), vsize * 2.0f, vsize * 2.0f), color, true); } //and labels if(width == 0 || height == 0) { // Render label for line labels[0].Move(startrotated, endrotated); renderer.RenderText(labels[0].TextLabel); } else { // Render labels for grid Vector2D[] labelCoords = { startrotated, new Vector2D(end.x, start.y).GetRotated(General.Map.Grid.GridRotate), endrotated, new Vector2D(start.x, end.y).GetRotated(General.Map.Grid.GridRotate), startrotated }; for (int i = 1; i < 5; i++) { labels[i - 1].Move(labelCoords[i], labelCoords[i - 1]); renderer.RenderText(labels[i - 1].TextLabel); } } //render hint if(horizontalslices > 1 || verticalslices > 1) { string text = "H: " + (slicesH - 1) + "; V: " + (slicesV - 1); if(Math.Abs(width) > text.Length * vsize && Math.Abs(height) > 16 * vsize) { hintlabel.Text = text; hintlabel.Move(startrotated, endrotated); renderer.RenderText(hintlabel.TextLabel); } } } else { // Render vertex at cursor renderer.RenderRectangleFilled(new RectangleF((float)(curvertexpos.x - vsize), (float)(curvertexpos.y - vsize), vsize * 2.0f, vsize * 2.0f), color, true); } // Done renderer.Finish(); } // Done renderer.Present(); } // This draws a point at a specific location override public bool DrawPointAt(Vector2D pos, bool stitch, bool stitchline) { if(pos.x < General.Map.Config.LeftBoundary || pos.x > General.Map.Config.RightBoundary || pos.y > General.Map.Config.TopBoundary || pos.y < General.Map.Config.BottomBoundary) return false; DrawnVertex newpoint = new DrawnVertex(); newpoint.pos = pos.GetRotated(-General.Map.Grid.GridRotate); newpoint.stitch = true; //stitch newpoint.stitchline = stitchline; points.Add(newpoint); if(points.Count == 1) { // Add labels labels.AddRange(new[] { new LineLengthLabel(false, true), new LineLengthLabel(false, true), new LineLengthLabel(false, true), new LineLengthLabel(false, true) }); hintlabel = new HintLabel(General.Colors.InfoLine); Update(); } else if(points[0].pos == points[1].pos) { // Nothing is drawn FinishDraw(); } else { // Create vertices for final shape. UpdateReferencePoints(points[0], newpoint); List shapes = GetShapes(start, end); foreach(Vector2D[] shape in shapes) { DrawnVertex[] verts = new DrawnVertex[shape.Length]; for(int i = 0; i < shape.Length; i++) { newpoint = new DrawnVertex { pos = shape[i].GetRotated(General.Map.Grid.GridRotate), // Take grid rotation into account stitch = true, stitchline = stitchline }; verts[i] = newpoint; } gridpoints.Add(verts); } FinishDraw(); } return true; } private List GetShapes(Vector2D s, Vector2D e) { // No shape if(s == e) return new List(); // Setup slices. Need to do some extra mathy stuff the make sure the rotated grid is taken into account switch(gridlockmode) { case GridLockMode.NONE: slicesH = horizontalslices; slicesV = verticalslices; break; case GridLockMode.HORIZONTAL: slicesH = Convert.ToInt32(Math.Ceiling(Math.Abs(width) / (double)General.Map.Grid.GridSize)); slicesV = verticalslices; break; case GridLockMode.VERTICAL: slicesH = horizontalslices; slicesV = Convert.ToInt32(Math.Ceiling(Math.Abs(height) / (double)General.Map.Grid.GridSize)); break; case GridLockMode.BOTH: slicesH = Convert.ToInt32(Math.Ceiling(Math.Abs(width) / (double)General.Map.Grid.GridSize)); slicesV = Convert.ToInt32(Math.Ceiling(Math.Abs(height) / (double)General.Map.Grid.GridSize)); break; } // Create a segmented line List shapes; if(width == 0 || height == 0) { if(slicesH > 0 && width > 0) { shapes = new List(); int step = width / slicesH; for(int w = 0; w < slicesH; w++) { shapes.Add(new[] { new Vector2D(s.x + step * w, s.y), new Vector2D(s.x + step * w + step, s.y) }); } return shapes; } if(slicesV > 0 && height > 0) { shapes = new List(); int step = height / slicesV; for(int h = 0; h < slicesV; h++) { shapes.Add(new[] { new Vector2D(s.x, s.y + step * h), new Vector2D(s.x, s.y + step * h + step) }); } return shapes; } // Create a line return new List {new[] {s, e}}; } // Create grid shape List rect = new List { s, new Vector2D(s.x, e.y), e, new Vector2D(e.x, s.y), s }; if(slicesH == 1 && slicesV == 1) { if(triangulate) rect.AddRange(new[] { s, e }); return new List { rect.ToArray() }; } // Create blocks shapes = new List { rect.ToArray() }; RectangleF[,] blocks = new RectangleF[slicesH, slicesV]; for(int w = 0; w < slicesH; w++) { for(int h = 0; h < slicesV; h++) { double left = (InterpolationTools.Interpolate(s.x, e.x, (double)w / slicesH, horizontalinterpolation)); double top = (InterpolationTools.Interpolate(s.y, e.y, (double)h / slicesV, verticalinterpolation)); double right = (InterpolationTools.Interpolate(s.x, e.x, (w + 1.0f) / slicesH, horizontalinterpolation)); double bottom = (InterpolationTools.Interpolate(s.y, e.y, (h + 1.0f) / slicesV, verticalinterpolation)); blocks[w, h] = RectangleF.FromLTRB((float)left, (float)top, (float)right, (float)bottom); } } // Add subdivisions if(slicesH > 1) { for(int w = 1; w < slicesH; w++) { float px = blocks[w, 0].X; shapes.Add(new[] { new Vector2D(px, s.y), new Vector2D(px, e.y) }); } } if(slicesV > 1) { for(int h = 1; h < slicesV; h++) { float py = blocks[0, h].Y; shapes.Add(new[] { new Vector2D(s.x, py), new Vector2D(e.x, py) }); } } // Triangulate? if(triangulate) { bool startflip = ((int)Math.Round(((s.x + e.y) / General.Map.Grid.GridSizeF) % 2) == 0); bool flip = startflip; for(int w = 0; w < slicesH; w++) { for(int h = slicesV - 1; h > -1; h--) { if(flip) shapes.Add(new[] { new Vector2D(blocks[w, h].X, blocks[w, h].Y), new Vector2D(blocks[w, h].Right, blocks[w, h].Bottom) }); else shapes.Add(new[] { new Vector2D(blocks[w, h].Right, blocks[w, h].Y), new Vector2D(blocks[w, h].X, blocks[w, h].Bottom) }); flip = !flip; } startflip = !startflip; flip = startflip; } } return shapes; } // Update bottom-left and top-right points, which define drawing shape private void UpdateReferencePoints(DrawnVertex p1, DrawnVertex p2) { if(!p1.pos.IsFinite() || !p2.pos.IsFinite()) return; // If relative interpolation is enabled the interpolation will use the point where the drawing // started as the origin, not always the top left corner if (relativeinterpolation) { start.x = p1.pos.x; start.y = p1.pos.y; end.x = p2.pos.x; end.y = p2.pos.y; } else { if (p1.pos.x < p2.pos.x) { start.x = p1.pos.x; end.x = p2.pos.x; } else { start.x = p2.pos.x; end.x = p1.pos.x; } if (p1.pos.y < p2.pos.y) { start.y = p1.pos.y; end.y = p2.pos.y; } else { start.y = p2.pos.y; end.y = p1.pos.y; } } width = (int)(end.x - start.x); height = (int)(end.y - start.y); } #endregion #region ================== Settings panel protected override void SetupInterface() { // Load stored settings triangulate = General.Settings.ReadPluginSetting("drawgridmode.triangulate", false); gridlockmode = (GridLockMode)General.Settings.ReadPluginSetting("drawgridmode.gridlockmode", 0); horizontalslices = Math.Max(General.Settings.ReadPluginSetting("drawgridmode.horizontalslices", 3), 1); verticalslices = Math.Max(General.Settings.ReadPluginSetting("drawgridmode.verticalslices", 3), 1); relativeinterpolation = General.Settings.ReadPluginSetting("drawgridmode.relativeinterpolation", true); horizontalinterpolation = (InterpolationTools.Mode)General.Settings.ReadPluginSetting("drawgridmode.horizontalinterpolation", 0); verticalinterpolation = (InterpolationTools.Mode)General.Settings.ReadPluginSetting("drawgridmode.verticalinterpolation", 0); // Create and setup settings panel panel = new DrawGridOptionsPanel(); panel.MaxHorizontalSlices = (int)General.Map.FormatInterface.MaxCoordinate; panel.MaxVerticalSlices = (int)General.Map.FormatInterface.MaxCoordinate; panel.Triangulate = triangulate; panel.GridLockMode = gridlockmode; panel.HorizontalSlices = horizontalslices - 1; panel.VerticalSlices = verticalslices - 1; panel.HorizontalInterpolationMode = horizontalinterpolation; panel.VerticalInterpolationMode = verticalinterpolation; panel.OnValueChanged += OptionsPanelOnValueChanged; panel.OnGridLockModeChanged += OptionsPanelOnGridLockChanged; panel.OnContinuousDrawingChanged += OnContinuousDrawingChanged; panel.OnShowGuidelinesChanged += OnShowGuidelinesChanged; panel.OnRelativeInterpolationChanged += OnRelativeInterpolationChanged; // Needs to be set after adding the OnContinuousDrawingChanged event... panel.ContinuousDrawing = General.Settings.ReadPluginSetting("drawgridmode.continuousdrawing", false); panel.ShowGuidelines = General.Settings.ReadPluginSetting("drawgridmode.showguidelines", false); panel.RelativeInterpolation = relativeinterpolation; } protected override void AddInterface() { // Add docker docker = new Docker("drawgrid", "Draw Grid", panel); General.Interface.AddDocker(docker, true); General.Interface.SelectDocker(docker); } protected override void RemoveInterface() { // Store settings General.Settings.WritePluginSetting("drawgridmode.triangulate", triangulate); General.Settings.WritePluginSetting("drawgridmode.gridlockmode", (int)gridlockmode); General.Settings.WritePluginSetting("drawgridmode.horizontalslices", horizontalslices); General.Settings.WritePluginSetting("drawgridmode.verticalslices", verticalslices); General.Settings.WritePluginSetting("drawgridmode.relativeinterpolation", relativeinterpolation); General.Settings.WritePluginSetting("drawgridmode.horizontalinterpolation", (int)horizontalinterpolation); General.Settings.WritePluginSetting("drawgridmode.verticalinterpolation", (int)verticalinterpolation); General.Settings.WritePluginSetting("drawgridmode.continuousdrawing", panel.ContinuousDrawing); General.Settings.WritePluginSetting("drawgridmode.showguidelines", panel.ShowGuidelines); // Remove docker General.Interface.RemoveDocker(docker); panel.Dispose(); panel = null; } protected void OnRelativeInterpolationChanged(object value, EventArgs e) { relativeinterpolation = (bool)value; Update(); } #endregion #region ================== Actions [BeginAction("increasebevel")] protected void IncreaseBevel() { if((gridlockmode == GridLockMode.NONE || gridlockmode == GridLockMode.VERTICAL) && (points.Count < 2 || horizontalslices < width - 2) && horizontalslices - 1 < panel.MaxHorizontalSlices) { horizontalslices++; panel.HorizontalSlices = horizontalslices - 1; Update(); } } [BeginAction("decreasebevel")] protected void DecreaseBevel() { if((gridlockmode == GridLockMode.NONE || gridlockmode == GridLockMode.VERTICAL) && horizontalslices > 1) { horizontalslices--; panel.HorizontalSlices = horizontalslices - 1; Update(); } } [BeginAction("increasesubdivlevel")] protected void IncreaseSubdivLevel() { if((gridlockmode == GridLockMode.NONE || gridlockmode == GridLockMode.HORIZONTAL) && (points.Count < 2 || verticalslices < height - 2) && verticalslices - 1 < panel.MaxVerticalSlices) { verticalslices++; panel.VerticalSlices = verticalslices - 1; Update(); } } [BeginAction("decreasesubdivlevel")] protected void DecreaseSubdivLevel() { if((gridlockmode == GridLockMode.NONE || gridlockmode == GridLockMode.HORIZONTAL) && verticalslices > 1) { verticalslices--; panel.VerticalSlices = verticalslices - 1; Update(); } } #endregion } }