UltimateZoneBuilder/Source/Plugins/BuilderModes/ClassicModes/DrawGridMode.cs
MaxED 77611bd8d3 Added: sectors are now split into multiple sectors when their shape is divided into several closed shapes by drawing new geometry or dragging existing geometry using Edit Selection and Drag Geometry modes.
Fixed: in some cases sidedefs belonging to linedefs, which were moved on top of existing linedefs, were incorrectly reassigned when applying Edit Selection and Drag Geometry modes.
Fixed: Tag Explorer update fix from R2630 was accidentally placed in an UDMF-only if-block.
Updated ZDoom_DECORATE.cfg (A_WeaponOffset).
Updated documentation ("(G)ZDoom text lumps support" page).
2016-05-18 20:31:40 +00:00

598 lines
18 KiB
C#

#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 GridLockMode gridlockmode;
private InterpolationTools.Mode horizontalinterpolation;
private InterpolationTools.Mode verticalinterpolation;
// Drawing
private readonly List<DrawnVertex[]> 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<DrawnVertex[]>();
}
#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 = { "gloomy", "sad", "unhappy", "lonely", "troubled", "depressed", "heartsick", "glum", "pessimistic", "bitter", "downcast" }; // aaand my english vocabulary ends here :)
string word = adjectives[new Random().Next(adjectives.Length - 1)];
string a = (word[0] == 'u' ? "an " : "a ");
General.Interface.DisplayStatus(StatusType.Action, "Created " + a + word + " grid.");
List<Sector> newsectors = new List<Sector>();
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...
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;
if(points.Count == 1)
{
// Handle the case when start point is not on current grid.
Vector2D gridoffset = General.Map.Grid.SnappedToGrid(points[0].pos) - points[0].pos;
curp = GetCurrentPosition(mousemappos + gridoffset, snaptonearest, snaptogrid, snaptocardinaldirection, usefourcardinaldirections, renderer, points);
curp.pos -= gridoffset;
}
else
{
curp = GetCurrentPosition();
}
float vsize = (renderer.VertexSize + 1.0f) / renderer.Scale;
// Render drawing lines
if(renderer.StartOverlay(true))
{
PixelColor color = snaptonearest ? stitchcolor : losecolor;
if(points.Count == 1)
{
UpdateReferencePoints(points[0], curp);
List<Vector2D[]> shapes = GetShapes(start, end);
//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(shape[i].x - vsize, shape[i].y - vsize, vsize * 2.0f, vsize * 2.0f), color, true);
}
//and labels
Vector2D[] labelCoords = new[] { start, new Vector2D(end.x, start.y), end, new Vector2D(start.x, end.y), start };
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(width > text.Length * vsize && height > 16 * vsize)
{
hintlabel.Text = text;
hintlabel.Move(start, end);
renderer.RenderText(hintlabel.TextLabel);
}
}
}
else
{
// Render vertex at cursor
renderer.RenderRectangleFilled(new RectangleF(curp.pos.x - vsize, curp.pos.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 { pos = pos, stitch = true, 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
{
// Handle the case when start point is not on current grid.
Vector2D gridoffset = General.Map.Grid.SnappedToGrid(points[0].pos) - points[0].pos;
newpoint = GetCurrentPosition(mousemappos + gridoffset, snaptonearest, snaptogrid, snaptocardinaldirection, usefourcardinaldirections, renderer, new List<DrawnVertex> { points[0] });
newpoint.pos -= gridoffset;
// Create vertices for final shape.
UpdateReferencePoints(points[0], newpoint);
List<Vector2D[]> 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], stitch = true, stitchline = stitchline };
verts[i] = newpoint;
}
gridpoints.Add(verts);
}
FinishDraw();
}
return true;
}
private List<Vector2D[]> GetShapes(Vector2D s, Vector2D e)
{
// No shape
if(s == e) return new List<Vector2D[]>();
// Setup slices
switch(gridlockmode)
{
case GridLockMode.NONE:
slicesH = horizontalslices;
slicesV = verticalslices;
break;
case GridLockMode.HORIZONTAL:
slicesH = width / General.Map.Grid.GridSize;
slicesV = verticalslices;
break;
case GridLockMode.VERTICAL:
slicesH = horizontalslices;
slicesV = height / General.Map.Grid.GridSize;
break;
case GridLockMode.BOTH:
slicesH = width / General.Map.Grid.GridSize;
slicesV = height / General.Map.Grid.GridSize;
break;
}
// Create a segmented line
List<Vector2D[]> shapes;
if(width == 0 || height == 0)
{
if(slicesH > 0 && width > 0)
{
shapes = new List<Vector2D[]>();
int step = width / slicesH;
for(int w = 0; w < slicesH; w++)
{
shapes.Add(new[] { new Vector2D((int)s.x + step * w, (int)s.y), new Vector2D((int)s.x + step * w + step, (int)s.y) });
}
return shapes;
}
if(slicesV > 0 && height > 0)
{
shapes = new List<Vector2D[]>();
int step = height / slicesV;
for(int h = 0; h < slicesV; h++)
{
shapes.Add(new[] {new Vector2D((int) s.x, (int) s.y + step * h), new Vector2D((int) s.x, (int) s.y + step * h + step)});
}
return shapes;
}
// Create a line
return new List<Vector2D[]> {new[] {s, e}};
}
// Create shape
List<Vector2D> rect = new List<Vector2D> { s, new Vector2D((int)s.x, (int)e.y), e, new Vector2D((int)e.x, (int)s.y), s };
if(slicesH == 1 && slicesV == 1)
{
if(triangulate) rect.AddRange(new[] { s, e });
return new List<Vector2D[]> { rect.ToArray() };
}
// Create blocks
shapes = new List<Vector2D[]> { rect.ToArray() };
RectangleF[,] blocks = new RectangleF[slicesH, slicesV];
for(int w = 0; w < slicesH; w++)
{
for(int h = 0; h < slicesV; h++)
{
float left = InterpolationTools.Interpolate(s.x, e.x, (float)w / slicesH, horizontalinterpolation);
float top = InterpolationTools.Interpolate(s.y, e.y, (float)h / slicesV, verticalinterpolation);
float right = InterpolationTools.Interpolate(s.x, e.x, (w + 1.0f) / slicesH, horizontalinterpolation);
float bottom = InterpolationTools.Interpolate(s.y, e.y, (h + 1.0f)/ slicesV, verticalinterpolation);
blocks[w, h] = RectangleF.FromLTRB(left, top, right, bottom);
}
}
// Add subdivisions
if(slicesH > 1)
{
for(int w = 1; w < slicesH; w++)
{
int px = (int) Math.Round(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++)
{
int py = (int) Math.Round(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.GridSize) % 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(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), 3);
verticalslices = Math.Max(General.Settings.ReadPluginSetting("drawgridmode.verticalslices", 3), 3);
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;
// Needs to be set after adding the OnContinuousDrawingChanged event...
panel.ContinuousDrawing = General.Settings.ReadPluginSetting("drawgridmode.continuousdrawing", false);
}
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.horizontalinterpolation", (int)horizontalinterpolation);
General.Settings.WritePluginSetting("drawgridmode.verticalinterpolation", (int)verticalinterpolation);
General.Settings.WritePluginSetting("drawgridmode.continuousdrawing", panel.ContinuousDrawing);
// Remove docker
General.Interface.RemoveDocker(docker);
panel.Dispose();
panel = null;
}
#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
}
}