UltimateZoneBuilder/Source/Plugins/BuilderModes/ClassicModes/DrawGridMode.cs

652 lines
20 KiB
C#
Raw Normal View History

#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<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 = 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<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...
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();
gridpoints.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<Vector2D[]> 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++)
2020-05-21 12:20:02 +00:00
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<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].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<Vector2D[]> GetShapes(Vector2D s, Vector2D e)
{
// No shape
if(s == e) return new List<Vector2D[]>();
// 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<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(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<Vector2D[]>();
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<Vector2D[]> {new[] {s, e}};
}
// Create grid shape
List<Vector2D> rect = new List<Vector2D> { 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<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++)
{
2020-05-21 12:20:02 +00:00
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
}
}