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

601 lines
18 KiB
C#
Raw Normal View History

#region ================== Namespaces
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.Editing;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Actions;
using CodeImp.DoomBuilder.Windows;
using CodeImp.DoomBuilder.Map;
#endregion
namespace CodeImp.DoomBuilder.BuilderModes
{
[EditMode(DisplayName = "Draw Rectangle Mode",
SwitchAction = "drawrectanglemode",
ButtonImage = "DrawRectangleMode.png", //mxd
ButtonOrder = int.MinValue + 3, //mxd
ButtonGroup = "000_drawing", //mxd
AllowCopyPaste = false,
Volatile = true,
Optional = false)]
public class DrawRectangleMode : DrawGeometryMode
{
#region ================== Variables
protected HintLabel hintlabel;
protected int bevelwidth;
protected int currentbevelwidth;
protected int subdivisions;
protected int maxsubdivisions;
protected int minsubdivisions;
protected string undoname = "Rectangle draw";
protected string shapename = "rectangle";
protected Vector2D start;
protected Vector2D end;
protected int width;
protected int height;
protected int minpointscount;
protected bool alwaysrendershapehints;
protected bool radialdrawing;
private bool blockupdate;
// Interface
private DrawRectangleOptionsPanel panel;
#endregion
#region ================== Constructor/Disposer
public DrawRectangleMode()
{
snaptogrid = true;
usefourcardinaldirections = true;
autoclosedrawing = false;
}
public override void Dispose()
{
Added, Texture Browser: added "Show textures in subdirectories" checkbox (enabled by default). When enabled, textures from current PK3/PK7/Directory resource directory and it's subdirectories will be shown. Otherwise, only textures from current directory will be shown. Removed, Texture Browser: removed "Show image sizes" checkbox. "Show texture and flat sizes in browsers" preferences setting is now used instead. Fixed, Things mode: event line between pre-last and the last PatrolPoint was not drawn. Fixed, Things mode: highlight range for sizeless things (things with "fixedsize" game configuration property) was calculated incorrectly. Fixed: fixed a crash when opening Script Editor after using "Open map in current wad" command to switch to UDMF map with SCRIPTS lump when current script configuration was not saved in the wad's .dbs file. Fixed: map closing events were not triggered when using "Open map in current wad" command, which could potentially result in plugin crashes/incorrect behavior. Fixed: Sector Drawing overrides panel could trigger an exception when closing the map during resource loading. Internal: added "Debug + Profiler" solution configuration, added 2 profiling methods to DebugConsole. Internal: rewrote MainForm.DisplayStatus() / StatusInfo to handle selection info in a more structured way. Fixed, internal: some destructors could potentially be executed more than once potentially leading to exceptions. Other destructors were not called at all. Updated ZDoom_DECORATE.cfg.
2015-09-16 12:10:43 +00:00
// Not already disposed?
if(!isdisposed)
{
// Clean up
if(hintlabel != null) hintlabel.Dispose();
// Done
base.Dispose();
}
}
#endregion
#region ================== Settings panel
protected override void SetupInterface()
{
2017-07-25 17:16:15 +00:00
minsubdivisions = 0;
maxsubdivisions = 16;
minpointscount = 4;
// Load stored settings
subdivisions = General.Clamp(General.Settings.ReadPluginSetting("drawrectanglemode.subdivisions", 0), minsubdivisions, maxsubdivisions);
bevelwidth = General.Settings.ReadPluginSetting("drawrectanglemode.bevelwidth", 0);
currentbevelwidth = bevelwidth;
//Add options docker
panel = new DrawRectangleOptionsPanel();
panel.MaxSubdivisions = maxsubdivisions;
panel.MinSubdivisions = minsubdivisions;
panel.MaxBevelWidth = (int)General.Map.FormatInterface.MaxCoordinate;
panel.MinBevelWidth = (int)General.Map.FormatInterface.MinCoordinate;
panel.BevelWidth = bevelwidth;
panel.Subdivisions = subdivisions;
panel.OnValueChanged += OptionsPanelOnValueChanged;
panel.OnContinuousDrawingChanged += OnContinuousDrawingChanged;
panel.OnShowGuidelinesChanged += OnShowGuidelinesChanged;
panel.OnRadialDrawingChanged += OnRadialDrawingChanged;
panel.OnPlaceThingsAtVerticesChanged += OnPlaceThingsAtVerticesChanged;
// Needs to be set after adding the OnContinuousDrawingChanged event...
panel.ContinuousDrawing = General.Settings.ReadPluginSetting("drawrectanglemode.continuousdrawing", false);
panel.ShowGuidelines = General.Settings.ReadPluginSetting("drawrectanglemode.showguidelines", false);
panel.RadialDrawing = General.Settings.ReadPluginSetting("drawrectanglemode.radialdrawing", false);
panel.PlaceThingsAtVertices = General.Settings.ReadPluginSetting("drawrectanglemode.placethingsatvertices", false);
}
protected override void AddInterface()
{
panel.Register();
}
protected override void RemoveInterface()
{
// Store settings
General.Settings.WritePluginSetting("drawrectanglemode.subdivisions", subdivisions);
General.Settings.WritePluginSetting("drawrectanglemode.bevelwidth", bevelwidth);
General.Settings.WritePluginSetting("drawrectanglemode.continuousdrawing", panel.ContinuousDrawing);
General.Settings.WritePluginSetting("drawrectanglemode.showguidelines", panel.ShowGuidelines);
General.Settings.WritePluginSetting("drawrectanglemode.radialdrawing", panel.RadialDrawing);
General.Settings.WritePluginSetting("drawrectanglemode.placethingsatvertices", panel.PlaceThingsAtVertices);
// Remove the buttons
panel.Unregister();
}
#endregion
#region ================== Methods
override protected void Update()
{
if(blockupdate) return;
PixelColor stitchcolor = General.Colors.Highlight;
PixelColor losecolor = General.Colors.Selection;
snaptocardinaldirection = General.Interface.ShiftState && General.Interface.AltState; //mxd
snaptogrid = (snaptocardinaldirection || General.Interface.ShiftState ^ General.Interface.SnapToGrid);
snaptonearest = 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);
Vector2D[] shape = GetShape(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
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
if (!placethingsatvertices)
{
for (int i = 1; i < shape.Length; i++)
renderer.RenderLine(shape[i - 1], shape[i], LINE_THICKNESS, color, true);
}
//vertices
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(shape.Length == 2)
{
// Render label for line
labels[0].Move(startrotated, endrotated);
renderer.RenderText(labels[0].TextLabel);
}
else if(shape.Length > 3)
{
// Render labels for rectangle
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);
}
//got beveled corners?
if(alwaysrendershapehints || shape.Length > minpointscount + 1)
{
//render hint
if(width > 64 * vsize && height > 16 * vsize)
{
hintlabel.Move(startrotated, endrotated);
hintlabel.Text = GetHintText();
renderer.RenderText(hintlabel.TextLabel);
}
//and shape corners
for(int i = 0; i < 4; i++)
2020-05-21 12:20:02 +00:00
renderer.RenderRectangleFilled(new RectangleF((float)(labelCoords[i].x - vsize), (float)(labelCoords[i].y - vsize), vsize * 2.0f, vsize * 2.0f), General.Colors.InfoLine, true);
}
}
else
{
// Render vertex at points[0]
2020-05-21 12:20:02 +00:00
renderer.RenderRectangleFilled(new RectangleF((float)(start.x - vsize), (float)(start.y - vsize), vsize * 2.0f, vsize * 2.0f), General.Colors.InfoLine, true);
}
}
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();
}
protected virtual Vector2D[] GetShape(Vector2D pStart, Vector2D pEnd)
{
//no shape
if(pStart == pEnd)
{
currentbevelwidth = 0;
return new Vector2D[0];
}
2013-03-18 13:52:27 +00:00
//line
if(pEnd.x == pStart.x || pEnd.y == pStart.y)
{
currentbevelwidth = 0;
return new[] { pStart, pEnd };
2013-03-18 13:52:27 +00:00
}
//no corners
if(bevelwidth == 0)
{
currentbevelwidth = 0;
return new[] { pStart, new Vector2D(pStart.x, pEnd.y), pEnd, new Vector2D(pEnd.x, pStart.y), pStart };
}
//got corners. TODO: check point order
bool reverse = false;
currentbevelwidth = Math.Min(Math.Abs(bevelwidth), Math.Min(width, height) / 2);
if(bevelwidth < 0)
{
currentbevelwidth *= -1;
reverse = true;
}
List<Vector2D> shape = new List<Vector2D>();
//top-left corner
shape.AddRange(GetCornerPoints(pStart, currentbevelwidth, currentbevelwidth, !reverse));
//top-right corner
shape.AddRange(GetCornerPoints(new Vector2D(pEnd.x, pStart.y), -currentbevelwidth, currentbevelwidth, reverse));
//bottom-right corner
shape.AddRange(GetCornerPoints(pEnd, -currentbevelwidth, -currentbevelwidth, !reverse));
//bottom-left corner
shape.AddRange(GetCornerPoints(new Vector2D(pStart.x, pEnd.y), currentbevelwidth, -currentbevelwidth, reverse));
//closing point
shape.Add(shape[0]);
return shape.ToArray();
}
private Vector2D[] GetCornerPoints(Vector2D startPoint, int bevel_width, int bevel_height, bool reverse)
{
Vector2D[] points;
Vector2D center = (bevelwidth > 0 ? new Vector2D(startPoint.x + bevel_width, startPoint.y + bevel_height) : startPoint);
2020-05-21 12:20:02 +00:00
double curAngle = Angle2D.PI;
int steps = subdivisions + 2;
points = new Vector2D[steps];
2020-05-21 12:20:02 +00:00
double stepAngle = Angle2D.PIHALF / (subdivisions + 1);
for(int i = 0; i < steps; i++)
{
2020-05-21 12:20:02 +00:00
points[i] = new Vector2D(center.x + Math.Sin(curAngle) * bevel_width, center.y + Math.Cos(curAngle) * bevel_height);
curAngle += stepAngle;
}
if(reverse) Array.Reverse(points);
return points;
}
protected virtual string GetHintText()
{
List<string> result = new List<string>();
if(bevelwidth != 0) result.Add("BVL: " + bevelwidth);
if(subdivisions != 0) result.Add("SUB: " + subdivisions);
return string.Join("; ", result.ToArray());
}
// Update top-left and bottom-right points, which define drawing shape
private void UpdateReferencePoints(DrawnVertex p1, DrawnVertex p2)
{
if(!p1.pos.IsFinite() || !p2.pos.IsFinite()) return;
if (radialdrawing)
{
Vector2D delta = p2.pos - p1.pos;
p1.pos -= delta;
}
// Make sure start always stays at left and up from the end
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;
}
// Update size
width = (int)Math.Round(end.x - start.x);
height = (int)Math.Round(end.y - start.y);
}
// 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 point and 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
{
points = new List<DrawnVertex>();
FinishDraw();
}
else
{
//create vertices for final shape.
UpdateReferencePoints(points[0], newpoint);
points = new List<DrawnVertex>(); //clear points
Vector2D[] shape = GetShape(start, end);
// Rotate the shape to fit the grid rotation
for (int i = 0; i < shape.Length; i++)
shape[i] = shape[i].GetRotated(General.Map.Grid.GridRotate);
// We don't want base.DrawPointAt to call Update() here, because it will mess labels[]
// and trigger shape.Count number of display redraws...
blockupdate = true;
foreach(Vector2D t in shape) base.DrawPointAt(t, true, true);
blockupdate = false;
FinishDraw();
}
return true;
}
override public void RemovePoint()
{
if(points.Count > 0) points.RemoveAt(points.Count - 1);
if(labels.Count > 0) labels = new List<LineLengthLabel>();
Update();
}
public override void RemoveFirstPoint()
{
RemovePoint();
}
#endregion
#region ================== Events
override public void OnAccept()
{
Cursor.Current = Cursors.AppStarting;
General.Settings.FindDefaultDrawSettings();
// When we have a rectangle or a line
if(points.Count > minpointscount || points.Count == 2)
{
// Make undo for the draw
General.Map.UndoRedo.CreateUndo(undoname);
// 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 + " " + shapename + ".");
// Make the drawing
if (placethingsatvertices)
{
List<Vector2D> verts = new List<Vector2D>();
for (int i = 0; i < points.Count; i++)
if (!verts.Contains(new Vector2D(points[i].pos.x, points[i].pos.y))) verts.Add(new Vector2D(points[i].pos.x, points[i].pos.y));
PlaceThingsAtPositions(verts);
// Snap to map format accuracy
General.Map.Map.SnapAllToAccuracy();
// Clear selection
General.Map.Map.ClearAllSelected();
// Update cached values
General.Map.Map.Update();
// Map is changed
General.Map.IsChanged = true;
}
else if (Tools.DrawLines(points, true, BuilderPlug.Me.AutoAlignTextureOffsetsOnCreate))
{
// Snap to map format accuracy
General.Map.Map.SnapAllToAccuracy();
// Clear selection
General.Map.Map.ClearAllSelected();
// Update cached values
General.Map.Map.Update();
//mxd. Outer sectors may require some splittin...
if(General.Settings.SplitJoinedSectors) Tools.SplitOuterSectors(General.Map.Map.GetMarkedLinedefs(true));
// Edit new sectors?
List<Sector> newsectors = General.Map.Map.GetMarkedSectors(true);
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();
}
public override void OnHelp()
{
General.ShowHelp("/gzdb/features/classic_modes/mode_drawrect.html");
}
private void OptionsPanelOnValueChanged(object sender, EventArgs eventArgs)
{
bevelwidth = panel.BevelWidth;
subdivisions = panel.Subdivisions;
Update();
}
protected void OnRadialDrawingChanged(object value, EventArgs e)
{
radialdrawing = (bool)value;
}
#endregion
#region ================== Actions
[BeginAction("increasesubdivlevel")]
protected virtual void IncreaseSubdivLevel()
{
if(subdivisions < maxsubdivisions)
{
subdivisions++;
panel.Subdivisions = subdivisions;
Update();
}
}
[BeginAction("decreasesubdivlevel")]
protected virtual void DecreaseSubdivLevel()
{
if(subdivisions > minsubdivisions)
{
subdivisions--;
panel.Subdivisions = subdivisions;
Update();
}
}
[BeginAction("increasebevel")]
protected virtual void IncreaseBevel()
{
if(points.Count < 2 || currentbevelwidth == bevelwidth || bevelwidth < 0)
{
bevelwidth = Math.Min(bevelwidth + General.Map.Grid.GridSize, panel.MaxBevelWidth);
panel.BevelWidth = bevelwidth;
Update();
}
}
[BeginAction("decreasebevel")]
protected virtual void DecreaseBevel()
{
if(currentbevelwidth == bevelwidth || bevelwidth > 0)
{
bevelwidth = Math.Max(bevelwidth - General.Map.Grid.GridSize, panel.MinBevelWidth);
panel.BevelWidth = bevelwidth;
Update();
}
}
#endregion
}
}