UltimateZoneBuilder/Source/Plugins/BuilderModes/ClassicModes/DrawGeometryMode.cs
MaxED 0369c969d1 According to dotnetperls.com, "new Dictionary<string, [anything]>(StringComparer.Ordinal)" works 17% faster than "new Dictionary<string, [anything]>()", so let's stick that everywhere and see what happens :)
Draw Curve Mode: added settings panel.
Sectors mode: added "Make Door" button to the toolbar.
Swapped Side panel and Info panel z-order. 
Interface: split toolbar into 3 separate toolbars. All toolbar buttons are now viewable at 1024x768.
Interface: grouped stuff in "Modes" menu a bit better.
Interface: added "Draw [stuff]" buttons to modes toolbar.
Interface: reorganized main menu. Hope it makes more sense now.
API: added General.Interface.AddModesButton() and General.Interface.AddModesMenu(), which can be used to add buttons to specific group in "Modes" toolbar and menu items to specific group in "Modes" menu, so actions, which behave like an editing mode, but are not part of one can be added there.
2014-02-26 14:11:06 +00:00

603 lines
17 KiB
C#

#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* 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
#region ================== Namespaces
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Windows;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.Geometry;
using System.Drawing;
using CodeImp.DoomBuilder.Editing;
using CodeImp.DoomBuilder.Actions;
#endregion
namespace CodeImp.DoomBuilder.BuilderModes
{
[EditMode(DisplayName = "Drawing Mode",
SwitchAction = "drawlinesmode",
ButtonImage = "DrawLinesMode.png", //mxd
ButtonOrder = int.MinValue + 1, //mxd
ButtonGroup = "000_drawing", //mxd
AllowCopyPaste = false,
Volatile = true,
UseByDefault = true,
Optional = false)]
public class DrawGeometryMode : BaseClassicMode
{
#region ================== Constants
protected const float LINE_THICKNESS = 0.8f;
#endregion
#region ================== Variables
// Drawing points
protected List<DrawnVertex> points;
protected List<LineLengthLabel> labels;
// Keep track of view changes
protected float lastoffsetx;
protected float lastoffsety;
protected float lastscale;
// Options
protected bool snaptogrid; // SHIFT to toggle
protected bool snaptonearest; // CTRL to enable
#endregion
#region ================== Properties
// Just keep the base mode button checked
public override string EditModeButtonName { get { return General.Editing.PreviousStableMode.Name; } }
#endregion
#region ================== Constructor / Disposer
// Constructor
public DrawGeometryMode()
{
// Initialize
points = new List<DrawnVertex>();
labels = new List<LineLengthLabel>();
// No selection in this mode
General.Map.Map.ClearAllSelected();
General.Map.Map.ClearAllMarks(false);
// We have no destructor
GC.SuppressFinalize(this);
}
// Disposer
public override void Dispose()
{
// Not already disposed?
if(!isdisposed)
{
// Clean up
if(labels != null)
foreach(LineLengthLabel l in labels) l.Dispose();
// Done
base.Dispose();
}
}
#endregion
#region ================== Methods
// This checks if the view offset/zoom changed and updates the check
/*protected bool CheckViewChanged()
{
// View changed?
bool viewchanged = (renderer.OffsetX != lastoffsetx || renderer.OffsetY != lastoffsety || renderer.Scale != lastscale);
// Keep view information
lastoffsetx = renderer.OffsetX;
lastoffsety = renderer.OffsetY;
lastscale = renderer.Scale;
// Return result
return viewchanged;
}*/
// This updates the dragging
protected virtual void Update()
{
PixelColor stitchcolor = General.Colors.Highlight;
PixelColor losecolor = General.Colors.Selection;
PixelColor color;
snaptogrid = General.Interface.ShiftState ^ General.Interface.SnapToGrid;
snaptonearest = General.Interface.CtrlState ^ General.Interface.AutoMerge;
DrawnVertex lastp = new DrawnVertex();
DrawnVertex curp = GetCurrentPosition();
float vsize = (renderer.VertexSize + 1.0f) / renderer.Scale;
// The last label's end must go to the mouse cursor
if(labels.Count > 0) labels[labels.Count - 1].End = curp.pos;
// Render drawing lines
if(renderer.StartOverlay(true))
{
// Go for all points to draw lines
if(points.Count > 0)
{
// Render lines
lastp = points[0];
for(int i = 1; i < points.Count; i++)
{
// Determine line color
if(lastp.stitchline && points[i].stitchline) color = stitchcolor;
else color = losecolor;
// Render line
renderer.RenderLine(lastp.pos, points[i].pos, LINE_THICKNESS, color, true);
RenderLinedefDirectionIndicator(lastp.pos, points[i].pos, color); //mxd
lastp = points[i];
}
// Determine line color
if(lastp.stitchline && snaptonearest) color = stitchcolor;
else color = losecolor;
// Render line to cursor
renderer.RenderLine(lastp.pos, curp.pos, LINE_THICKNESS, color, true);
RenderLinedefDirectionIndicator(lastp.pos, curp.pos, color); //mxd
// Render vertices
for(int i = 0; i < points.Count; i++)
{
// Determine vertex color
color = points[i].stitch ? stitchcolor : losecolor;
// Render vertex
renderer.RenderRectangleFilled(new RectangleF(points[i].pos.x - vsize, points[i].pos.y - vsize, vsize * 2.0f, vsize * 2.0f), color, true);
}
}
// Determine point color
color = snaptonearest ? stitchcolor : losecolor;
// Render vertex at cursor
renderer.RenderRectangleFilled(new RectangleF(curp.pos.x - vsize, curp.pos.y - vsize, vsize * 2.0f, vsize * 2.0f), color, true);
// Go for all labels
foreach(LineLengthLabel l in labels) renderer.RenderText(l.TextLabel);
// Done
renderer.Finish();
}
// Done
renderer.Present();
}
//mxd
protected void RenderLinedefDirectionIndicator(Vector2D start, Vector2D end, PixelColor color) {
Vector2D delta = end - start;
Vector2D middlePoint = new Vector2D(start.x + delta.x / 2, start.y + delta.y / 2);
Vector2D scaledPerpendicular = delta.GetPerpendicular().GetNormal().GetScaled(18f / renderer.Scale);
renderer.RenderLine(middlePoint, new Vector2D(middlePoint.x - scaledPerpendicular.x, middlePoint.y - scaledPerpendicular.y), LINE_THICKNESS, color, true);
}
// This returns the aligned and snapped draw position
public static DrawnVertex GetCurrentPosition(Vector2D mousemappos, bool snaptonearest, bool snaptogrid, IRenderer2D renderer, List<DrawnVertex> points)
{
DrawnVertex p = new DrawnVertex();
p.stitch = true; //mxd. Setting these to false seems to be a good way to create invalid geometry...
p.stitchline = true; //mxd
Vector2D vm = mousemappos;
float vrange = BuilderPlug.Me.StitchRange / renderer.Scale;
// Snap to nearest?
if(snaptonearest)
{
// Go for all drawn points
foreach(DrawnVertex v in points)
{
if(Vector2D.DistanceSq(mousemappos, v.pos) < (vrange * vrange))
{
p.pos = v.pos;
//p.stitch = true;
//p.stitchline = true;
return p;
}
}
// Try the nearest vertex
Vertex nv = General.Map.Map.NearestVertexSquareRange(mousemappos, vrange);
if(nv != null)
{
p.pos = nv.Position;
//p.stitch = true;
//p.stitchline = true;
return p;
}
// Try the nearest linedef
Linedef nl = General.Map.Map.NearestLinedefRange(mousemappos, BuilderPlug.Me.StitchRange / renderer.Scale);
if(nl != null)
{
// Snap to grid?
if(snaptogrid)
{
// Get grid intersection coordinates
List<Vector2D> coords = nl.GetGridIntersections();
// Find nearest grid intersection
bool found = false;
float found_distance = float.MaxValue;
Vector2D found_coord = new Vector2D();
foreach(Vector2D v in coords)
{
Vector2D delta = mousemappos - v;
if(delta.GetLengthSq() < found_distance)
{
found_distance = delta.GetLengthSq();
found_coord = v;
found = true;
}
}
if(found)
{
// Align to the closest grid intersection
p.pos = found_coord;
//p.stitch = true;
//p.stitchline = true;
return p;
}
}
else
{
// Aligned to line
p.pos = nl.NearestOnLine(mousemappos);
//p.stitch = true;
//p.stitchline = true;
return p;
}
}
}
else
{
// Always snap to the first drawn vertex so that the user can finish a complete sector without stitching
if(points.Count > 0)
{
if(Vector2D.DistanceSq(mousemappos, points[0].pos) < (vrange * vrange))
{
p.pos = points[0].pos;
//p.stitch = true;
//p.stitchline = false;
return p;
}
}
}
// if the mouse cursor is outside the map bondaries check if the line between the last set point and the
// mouse cursor intersect any of the boundary lines. If it does, set the position to this intersection
if (points.Count > 0 &&
(mousemappos.x < General.Map.Config.LeftBoundary || mousemappos.x > General.Map.Config.RightBoundary ||
mousemappos.y > General.Map.Config.TopBoundary || mousemappos.y < General.Map.Config.BottomBoundary))
{
Line2D dline = new Line2D(mousemappos, points[points.Count - 1].pos);
bool foundintersection = false;
float u = 0.0f;
List<Line2D> blines = new List<Line2D>();
// lines for left, top, right and bottom bondaries
blines.Add(new Line2D(General.Map.Config.LeftBoundary, General.Map.Config.BottomBoundary, General.Map.Config.LeftBoundary, General.Map.Config.TopBoundary));
blines.Add(new Line2D(General.Map.Config.LeftBoundary, General.Map.Config.TopBoundary, General.Map.Config.RightBoundary, General.Map.Config.TopBoundary));
blines.Add(new Line2D(General.Map.Config.RightBoundary, General.Map.Config.TopBoundary, General.Map.Config.RightBoundary, General.Map.Config.BottomBoundary));
blines.Add(new Line2D(General.Map.Config.RightBoundary, General.Map.Config.BottomBoundary, General.Map.Config.LeftBoundary, General.Map.Config.BottomBoundary));
// check for intersections with boundaries
for (int i = 0; i < blines.Count; i++)
{
if (!foundintersection)
{
// only check for intersection if the last set point is not on the
// line we are checking against
if (blines[i].GetSideOfLine(points[points.Count - 1].pos) != 0.0f)
{
foundintersection = blines[i].GetIntersection(dline, out u);
}
}
}
// if there was no intersection set the position to the last set point
if (!foundintersection)
vm = points[points.Count - 1].pos;
else
vm = dline.GetCoordinatesAt(u);
}
// Snap to grid?
if(snaptogrid)
{
// Aligned to grid
p.pos = General.Map.Grid.SnappedToGrid(vm);
// special handling
if (p.pos.x > General.Map.Config.RightBoundary) p.pos.x = General.Map.Config.RightBoundary;
if (p.pos.y < General.Map.Config.BottomBoundary) p.pos.y = General.Map.Config.BottomBoundary;
//p.stitch = snaptonearest;
//p.stitchline = snaptonearest;
return p;
}
else
{
// Normal position
p.pos = vm;
//p.stitch = snaptonearest;
//p.stitchline = snaptonearest;
return p;
}
}
// This gets the aligned and snapped draw position
protected DrawnVertex GetCurrentPosition()
{
return GetCurrentPosition(mousemappos, snaptonearest, snaptogrid, renderer, points);
}
// This draws a point at a specific location
public bool DrawPointAt(DrawnVertex p)
{
return DrawPointAt(p.pos, p.stitch, p.stitchline);
}
// This draws a point at a specific location
public virtual 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;
newpoint.stitch = stitch;
newpoint.stitchline = stitchline;
points.Add(newpoint);
labels.Add(new LineLengthLabel(true));
labels[labels.Count - 1].Start = newpoint.pos;
if(labels.Count > 1) labels[labels.Count - 2].End = newpoint.pos;
Update();
// Check if point stitches with the first
if((points.Count > 1) && points[points.Count - 1].stitch)
{
Vector2D p1 = points[0].pos;
Vector2D p2 = points[points.Count - 1].pos;
Vector2D delta = p1 - p2;
if((Math.Abs(delta.x) <= 0.001f) && (Math.Abs(delta.y) <= 0.001f))
{
//mxd. Seems... logical?
if (points.Count == 2) {
OnCancel();
return true;
}
// Finish drawing
FinishDraw();
}
}
return true;
}
#endregion
#region ================== Events
public override void OnHelp()
{
General.ShowHelp("e_drawgeometry.html");
}
// Engaging
public override void OnEngage()
{
base.OnEngage();
EnableAutoPanning();
renderer.SetPresentation(Presentation.Standard);
// Set cursor
General.Interface.SetCursor(Cursors.Cross);
}
// Disengaging
public override void OnDisengage()
{
base.OnDisengage();
DisableAutoPanning();
}
// Cancelled
public override void OnCancel()
{
// Cancel base class
base.OnCancel();
// Return to original mode
General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name);
}
// Accepted
public override void OnAccept()
{
Cursor.Current = Cursors.AppStarting;
General.Settings.FindDefaultDrawSettings();
// When points have been drawn
if(points.Count > 0)
{
// Make undo for the draw
General.Map.UndoRedo.CreateUndo("Line draw");
// Make an analysis and show info
string[] adjectives = new string[]
{ "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[points.Count % adjectives.Length];
word = (points.Count > adjectives.Length) ? "very " + word : word;
string a = ((word[0] == 'a') || (word[0] == 'e') || (word[0] == 'o') || (word[0] == 'u')) ? "an " : "a ";
General.Interface.DisplayStatus(StatusType.Action, "Created " + a + word + " drawing.");
// Make the drawing
if(!Tools.DrawLines(points, true, BuilderPlug.Me.AutoAlignTextureOffsetsOnCreate)) //mxd
{
// 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();
return;
}
// Snap to map format accuracy
General.Map.Map.SnapAllToAccuracy();
// Clear selection
General.Map.Map.ClearAllSelected();
// Update cached values
General.Map.Map.Update();
// 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();
// Map is changed
General.Map.IsChanged = true;
}
// Done
Cursor.Current = Cursors.Default;
// Return to original mode
General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name);
}
// This redraws the display
public override void OnRedrawDisplay()
{
renderer.RedrawSurface();
// Render lines
if(renderer.StartPlotter(true))
{
renderer.PlotLinedefSet(General.Map.Map.Linedefs);
renderer.PlotVerticesSet(General.Map.Map.Vertices);
renderer.Finish();
}
// Render things
if(renderer.StartThings(true))
{
renderer.RenderThingSet(General.Map.Map.Things, 1.0f);
renderer.Finish();
}
// Normal update
Update();
}
// Mouse moving
public override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if(panning) return; //mxd. Skip all this jass while panning
Update();
}
// When a key is released
public override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
if((snaptogrid != (General.Interface.ShiftState ^ General.Interface.SnapToGrid)) ||
(snaptonearest != (General.Interface.CtrlState ^ General.Interface.AutoMerge))) Update();
}
// When a key is pressed
public override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if((snaptogrid != (General.Interface.ShiftState ^ General.Interface.SnapToGrid)) ||
(snaptonearest != (General.Interface.CtrlState ^ General.Interface.AutoMerge))) Update();
}
#endregion
#region ================== Actions
// Drawing a point
[BeginAction("drawpoint")]
public void DrawPoint()
{
// Mouse inside window?
if(General.Interface.MouseInDisplay)
{
DrawnVertex newpoint = GetCurrentPosition();
if(!DrawPointAt(newpoint)) General.Interface.DisplayStatus(StatusType.Warning, "Failed to draw point: outside of map boundaries.");
}
}
// Remove a point
[BeginAction("removepoint")]
public virtual void RemovePoint()
{
if(points.Count > 0) points.RemoveAt(points.Count - 1);
if(labels.Count > 0)
{
labels[labels.Count - 1].Dispose();
labels.RemoveAt(labels.Count - 1);
}
Update();
}
// Finish drawing
[BeginAction("finishdraw")]
public void FinishDraw()
{
// Accept the changes
General.Editing.AcceptMode();
}
#endregion
}
}