mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-27 14:12:16 +00:00
984 lines
32 KiB
C#
Executable file
984 lines
32 KiB
C#
Executable file
|
|
#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.Drawing;
|
|
using System.Windows.Forms;
|
|
using CodeImp.DoomBuilder.Actions;
|
|
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 Lines Mode",
|
|
SwitchAction = "drawlinesmode",
|
|
ButtonImage = "DrawGeometryMode.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;
|
|
private LineLengthLabel[] guidelabels; //mxd
|
|
|
|
// Options
|
|
protected bool snaptogrid; // SHIFT to toggle
|
|
protected bool snaptonearest; // CTRL to enable
|
|
protected bool snaptocardinaldirection; //mxd. ALT-SHIFT to enable
|
|
protected bool usefourcardinaldirections;
|
|
protected bool continuousdrawing; //mxd. Restart after finishing drawing?
|
|
protected bool autoclosedrawing; //mxd. Finish drawing when new points and existing geometry form a closed shape
|
|
protected bool drawingautoclosed; //mxd
|
|
protected bool showguidelines; //mxd
|
|
|
|
//mxd. Map area bounds
|
|
private Line2D top, bottom, left, right;
|
|
|
|
//mxd. Labels display style
|
|
protected bool labelshowangle = true;
|
|
protected bool labeluseoffset = true;
|
|
|
|
//mxd. Interface
|
|
private DrawLineOptionsPanel panel;
|
|
|
|
#endregion
|
|
|
|
#region ================== Properties
|
|
|
|
#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);
|
|
|
|
//mxd
|
|
SetupInterface();
|
|
|
|
// 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();
|
|
if(guidelabels != null) foreach(LineLengthLabel l in guidelabels) l.Dispose();
|
|
|
|
// Done
|
|
base.Dispose();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Methods
|
|
|
|
// This checks if the view offset/zoom changed and updates the check (never used. mxd)
|
|
/*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;
|
|
|
|
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();
|
|
float vsize = (renderer.VertexSize + 1.0f) / renderer.Scale;
|
|
|
|
// Update label positions (mxd)
|
|
if(labels.Count > 0)
|
|
{
|
|
// Update labels for already drawn lines
|
|
for(int i = 0; i < labels.Count - 1; i++)
|
|
{
|
|
labels[i].ShowAngle = showguidelines;
|
|
labels[i].Move(points[i].pos, points[i + 1].pos);
|
|
}
|
|
|
|
// Update label for active line
|
|
labels[labels.Count - 1].ShowAngle = showguidelines;
|
|
labels[labels.Count - 1].Move(points[points.Count - 1].pos, curp.pos);
|
|
}
|
|
|
|
// Render drawing lines
|
|
if(renderer.StartOverlay(true))
|
|
{
|
|
// Go for all points to draw lines
|
|
PixelColor color;
|
|
if(points.Count > 0)
|
|
{
|
|
//mxd
|
|
bool renderguidelabels = false;
|
|
if(showguidelines)
|
|
{
|
|
Vector2D prevp = points[points.Count - 1].pos;
|
|
renderguidelabels = (curp.pos.x != prevp.x && curp.pos.y != prevp.y);
|
|
RenderGuidelines(prevp, curp.pos, General.Colors.Guideline.WithAlpha(80));
|
|
}
|
|
|
|
// Render lines
|
|
DrawnVertex 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
|
|
color = (lastp.stitchline && snaptonearest ? stitchcolor : 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((float)(points[i].pos.x - vsize), (float)(points[i].pos.y - vsize), vsize * 2.0f, vsize * 2.0f), color, true);
|
|
}
|
|
|
|
//mxd. Render guide labels?
|
|
if(renderguidelabels) renderer.RenderText(guidelabels);
|
|
|
|
// Render labels
|
|
renderer.RenderText(labels.ToArray());
|
|
}
|
|
|
|
// Determine point color
|
|
color = snaptonearest ? stitchcolor : losecolor;
|
|
|
|
// Render vertex at cursor
|
|
renderer.RenderRectangleFilled(new RectangleF((float)(curp.pos.x - vsize), (float)(curp.pos.y - vsize), vsize * 2.0f, vsize * 2.0f), color, true);
|
|
|
|
// Done
|
|
renderer.Finish();
|
|
}
|
|
|
|
// Done
|
|
renderer.Present();
|
|
}
|
|
|
|
protected void RenderGuidelines(Vector2D start, Vector2D end, PixelColor c)
|
|
{
|
|
RenderGuidelines(start, end, c, 0.0);
|
|
}
|
|
|
|
//mxd
|
|
protected void RenderGuidelines(Vector2D start, Vector2D end, PixelColor c, double angle)
|
|
{
|
|
start = start.GetRotated(angle);
|
|
end = end.GetRotated(angle);
|
|
|
|
if(end.x != start.x && end.y != start.y)
|
|
{
|
|
Vector2D tr = new Vector2D(Math.Max(end.x, start.x), Math.Max(end.y, start.y));
|
|
Vector2D bl = new Vector2D(Math.Min(end.x, start.x), Math.Min(end.y, start.y));
|
|
|
|
// Create guidelines
|
|
Line3D[] lines = new Line3D[5];
|
|
lines[0] = new Line3D(new Vector2D(tr.x, General.Map.Config.TopBoundary).GetRotated(-angle), new Vector2D(tr.x, General.Map.Config.BottomBoundary).GetRotated(-angle), c, false);
|
|
lines[1] = new Line3D(new Vector2D(bl.x, General.Map.Config.TopBoundary).GetRotated(-angle), new Vector2D(bl.x, General.Map.Config.BottomBoundary).GetRotated(-angle), c, false);
|
|
lines[2] = new Line3D(new Vector2D(General.Map.Config.LeftBoundary, tr.y).GetRotated(-angle), new Vector2D(General.Map.Config.RightBoundary, tr.y).GetRotated(-angle), c, false);
|
|
lines[3] = new Line3D(new Vector2D(General.Map.Config.LeftBoundary, bl.y).GetRotated(-angle), new Vector2D(General.Map.Config.RightBoundary, bl.y).GetRotated(-angle), c, false);
|
|
|
|
// Create current line extent. Make sure v1 is to the left of v2
|
|
Line2D current = (end.x < start.x ? new Line2D(end, start) : new Line2D(start, end));
|
|
|
|
Vector2D extentstart, extentend;
|
|
if(current.v1.y < current.v2.y) // Start is lower
|
|
{
|
|
// Start point can hit left or bottom boundaries
|
|
extentstart = Line2D.GetIntersectionPoint(left, current, false);
|
|
if(extentstart.y < General.Map.Config.BottomBoundary)
|
|
extentstart = Line2D.GetIntersectionPoint(bottom, current, false);
|
|
|
|
// End point can hit right or top boundaries
|
|
extentend = Line2D.GetIntersectionPoint(right, current, false);
|
|
if(extentend.y > General.Map.Config.TopBoundary)
|
|
extentend = Line2D.GetIntersectionPoint(top, current, false);
|
|
}
|
|
else // Start is higher
|
|
{
|
|
// Start point can hit left or top boundaries
|
|
extentstart = Line2D.GetIntersectionPoint(left, current, false);
|
|
if(extentstart.y > General.Map.Config.TopBoundary)
|
|
extentstart = Line2D.GetIntersectionPoint(top, current, false);
|
|
|
|
// End point can hit right or bottom boundaries
|
|
extentend = Line2D.GetIntersectionPoint(right, current, false);
|
|
if(extentend.y < General.Map.Config.BottomBoundary)
|
|
extentend = Line2D.GetIntersectionPoint(bottom, current, false);
|
|
}
|
|
|
|
lines[4] = new Line3D(extentstart, extentend, c, false);
|
|
|
|
// Render them
|
|
renderer.RenderArrows(lines);
|
|
|
|
// Update horiz/vert length labels
|
|
if(guidelabels != null)
|
|
{
|
|
guidelabels[0].Move(tr, new Vector2D(tr.x, bl.y));
|
|
guidelabels[1].Move(new Vector2D(bl.x, tr.y), tr);
|
|
guidelabels[2].Move(new Vector2D(tr.x, bl.y), bl);
|
|
guidelabels[3].Move(bl, new Vector2D(bl.x, tr.y));
|
|
}
|
|
}
|
|
// Render horizontal line + 2 vertical guidelines
|
|
else if(end.x != start.x)
|
|
{
|
|
Line3D l = new Line3D(new Vector2D(General.Map.Config.LeftBoundary, end.y), new Vector2D(General.Map.Config.RightBoundary, end.y), c, false);
|
|
Line3D gs = new Line3D(new Vector2D(start.x, General.Map.Config.TopBoundary), new Vector2D(start.x, General.Map.Config.BottomBoundary), c, false);
|
|
Line3D ge = new Line3D(new Vector2D(end.x, General.Map.Config.TopBoundary), new Vector2D(end.x, General.Map.Config.BottomBoundary), c, false);
|
|
renderer.RenderArrows(new List<Line3D> { l, gs, ge });
|
|
}
|
|
// Render vertical line + 2 horizontal guidelines
|
|
else if(end.y != start.y)
|
|
{
|
|
Line3D l = new Line3D(new Vector2D(end.x, General.Map.Config.TopBoundary), new Vector2D(end.x, General.Map.Config.BottomBoundary), c, false);
|
|
Line3D gs = new Line3D(new Vector2D(General.Map.Config.LeftBoundary, start.y), new Vector2D(General.Map.Config.RightBoundary, start.y), c, false);
|
|
Line3D ge = new Line3D(new Vector2D(General.Map.Config.LeftBoundary, end.y), new Vector2D(General.Map.Config.RightBoundary, end.y), c, false);
|
|
renderer.RenderArrows(new List<Line3D> {l, gs, ge});
|
|
}
|
|
// Start and end match. Render a cross
|
|
else
|
|
{
|
|
Line3D gs = new Line3D(new Vector2D(General.Map.Config.LeftBoundary, start.y), new Vector2D(General.Map.Config.RightBoundary, start.y), c, false);
|
|
Line3D ge = new Line3D(new Vector2D(start.x, General.Map.Config.TopBoundary), new Vector2D(start.x, General.Map.Config.BottomBoundary), c, false);
|
|
renderer.RenderArrows(new List<Line3D> { gs, ge });
|
|
}
|
|
}
|
|
|
|
//mxd
|
|
private 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, bool snaptocardinal, bool usefourcardinaldirections, 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
|
|
snaptocardinal = (snaptocardinal && points.Count > 0); //mxd. Don't snap to cardinal when there are no points
|
|
|
|
//mxd. If snap to cardinal directions is enabled and we have points, modify mouse position
|
|
Vector2D vm, gridoffset;
|
|
if(snaptocardinal)
|
|
{
|
|
Vector2D offset = mousemappos - points[points.Count - 1].pos;
|
|
|
|
double angle;
|
|
if(usefourcardinaldirections)
|
|
angle = Angle2D.DegToRad((General.ClampAngle((int)Angle2D.RadToDeg(offset.GetAngle()))) / 90 * 90 + 45);
|
|
else
|
|
angle = Angle2D.DegToRad((General.ClampAngle((int)Angle2D.RadToDeg(offset.GetAngle()) + 22)) / 45 * 45);
|
|
|
|
offset = new Vector2D(0, -offset.GetLength()).GetRotated(angle);
|
|
vm = points[points.Count - 1].pos + offset;
|
|
|
|
//mxd. We need to be snapped relative to initial position
|
|
Vector2D prev = points[points.Count - 1].pos;
|
|
gridoffset = prev - General.Map.Grid.SnappedToGrid(prev);
|
|
}
|
|
else
|
|
{
|
|
vm = mousemappos;
|
|
gridoffset = new Vector2D();
|
|
}
|
|
|
|
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(vm, v.pos) < (vrange * vrange))
|
|
{
|
|
p.pos = v.pos;
|
|
return p;
|
|
}
|
|
}
|
|
|
|
// Try the nearest vertex
|
|
Vertex nv = General.Map.Map.NearestVertexSquareRange(vm, vrange);
|
|
if(nv != null)
|
|
{
|
|
//mxd. Line angle must stay the same
|
|
if(snaptocardinal)
|
|
{
|
|
Line2D ourline = new Line2D(points[points.Count - 1].pos, vm);
|
|
if(Math.Round(ourline.GetSideOfLine(nv.Position), 1) == 0)
|
|
{
|
|
p.pos = nv.Position;
|
|
return p;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
p.pos = nv.Position;
|
|
return p;
|
|
}
|
|
}
|
|
|
|
// Try the nearest linedef. mxd. We'll need much bigger stitch distance when snapping to cardinal directions
|
|
Linedef nl = General.Map.Map.NearestLinedefRange(vm, BuilderPlug.Me.StitchRange / renderer.Scale);
|
|
if(nl != null)
|
|
{
|
|
//mxd. Line angle must stay the same
|
|
if(snaptocardinal)
|
|
{
|
|
Line2D ourline = new Line2D(points[points.Count - 1].pos, vm);
|
|
Line2D nearestline = new Line2D(nl.Start.Position, nl.End.Position);
|
|
Vector2D intersection = Line2D.GetIntersectionPoint(nearestline, ourline, false);
|
|
if(!double.IsNaN(intersection.x))
|
|
{
|
|
// Intersection is on nearestline?
|
|
double u = Line2D.GetNearestOnLine(nearestline.v1, nearestline.v2, intersection);
|
|
|
|
if(u < 0f || u > 1f) { }
|
|
else
|
|
{
|
|
p.pos = new Vector2D(Math.Round(intersection.x, General.Map.FormatInterface.VertexDecimals),
|
|
Math.Round(intersection.y, General.Map.FormatInterface.VertexDecimals));
|
|
return p;
|
|
}
|
|
}
|
|
}
|
|
// Snap to grid?
|
|
else if(snaptogrid)
|
|
{
|
|
// Get grid intersection coordinates
|
|
List<Vector2D> coords = nl.GetGridIntersections(General.Map.Grid.GridRotate,
|
|
General.Map.Grid.GridOriginX, General.Map.Grid.GridOriginY);
|
|
|
|
// Find nearest grid intersection
|
|
bool found = false;
|
|
double found_distance = double.MaxValue;
|
|
Vector2D found_coord = new Vector2D();
|
|
foreach(Vector2D v in coords)
|
|
{
|
|
Vector2D delta = vm - 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;
|
|
return p;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Aligned to line
|
|
p.pos = nl.NearestOnLine(vm);
|
|
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(vm, points[0].pos) < (vrange * vrange))
|
|
{
|
|
p.pos = points[0].pos;
|
|
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;
|
|
double u = 0.0;
|
|
List<Line2D> blines = new List<Line2D>();
|
|
|
|
// lines for left, top, right and bottom boundaries
|
|
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.0)
|
|
{
|
|
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 - gridoffset) + gridoffset;
|
|
|
|
// 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;
|
|
|
|
return p;
|
|
}
|
|
else
|
|
{
|
|
// Normal position
|
|
p.pos.x = Math.Round(vm.x); //mxd
|
|
p.pos.y = Math.Round(vm.y); //mxd
|
|
|
|
return p;
|
|
}
|
|
}
|
|
|
|
// This gets the aligned and snapped draw position
|
|
protected DrawnVertex GetCurrentPosition()
|
|
{
|
|
return GetCurrentPosition(mousemappos, snaptonearest, snaptogrid, snaptocardinaldirection, usefourcardinaldirections, 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;
|
|
|
|
//mxd. Avoid zero-length lines...
|
|
if(points.Count > 0)
|
|
{
|
|
Vector2D delta = points[points.Count - 1].pos - pos;
|
|
if((Math.Abs(delta.x) <= 0.001f) && (Math.Abs(delta.y) <= 0.001f))
|
|
return true;
|
|
}
|
|
|
|
DrawnVertex newpoint = new DrawnVertex();
|
|
newpoint.pos = pos;
|
|
newpoint.stitch = stitch;
|
|
newpoint.stitchline = stitchline;
|
|
points.Add(newpoint);
|
|
labels.Add(new LineLengthLabel(labelshowangle, labeluseoffset));
|
|
Update();
|
|
|
|
if(points.Count > 1)
|
|
{
|
|
// Check if point stitches with the first
|
|
if(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;
|
|
}
|
|
}
|
|
|
|
//mxd. Points and existing geometry form a closed shape?
|
|
if(autoclosedrawing)
|
|
{
|
|
// Determive center point
|
|
double minx = float.MaxValue;
|
|
double maxx = float.MinValue;
|
|
double miny = float.MaxValue;
|
|
double maxy = float.MinValue;
|
|
|
|
foreach(DrawnVertex v in points)
|
|
{
|
|
if(v.pos.x < minx) minx = v.pos.x;
|
|
if(v.pos.x > maxx) maxx = v.pos.x;
|
|
if(v.pos.y < miny) miny = v.pos.y;
|
|
if(v.pos.y > maxy) maxy = v.pos.y;
|
|
}
|
|
|
|
Vector2D shapecenter = new Vector2D(minx + (maxx - minx) / 2, miny + (maxy - miny) / 2);
|
|
|
|
// Determine center point between start and end points
|
|
minx = Math.Min(points[0].pos.x, points[points.Count - 1].pos.x);
|
|
maxx = Math.Max(points[0].pos.x, points[points.Count - 1].pos.x);
|
|
miny = Math.Min(points[0].pos.y, points[points.Count - 1].pos.y);
|
|
maxy = Math.Max(points[0].pos.y, points[points.Count - 1].pos.y);
|
|
|
|
Vector2D startendcenter = new Vector2D(minx + (maxx - minx) / 2, miny + (maxy - miny) / 2);
|
|
|
|
// Offset the center perpendicular to the start -> end line direction...
|
|
if(shapecenter == startendcenter)
|
|
{
|
|
shapecenter -= new Line2D(points[0].pos, points[points.Count - 1].pos).GetPerpendicular().GetNormal();
|
|
}
|
|
|
|
// Do the check
|
|
if(CanFinishDrawing(points[0].pos, points[points.Count - 1].pos, shapecenter))
|
|
{
|
|
drawingautoclosed = true;
|
|
FinishDraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//mxd
|
|
private static bool CanFinishDrawing(Vector2D start, Vector2D end, Vector2D center)
|
|
{
|
|
Linedef startline = FindPotentialLine(start, center);
|
|
if(startline == null) return false;
|
|
|
|
Linedef endline = FindPotentialLine(end, center);
|
|
if(endline == null) return false;
|
|
|
|
// Can finish drawing if a path between startline and endline exists
|
|
return Tools.FindClosestPath(startline, startline.SideOfLine(center) < 0.0f, endline, endline.SideOfLine(center) < 0.0f, true) != null;
|
|
}
|
|
|
|
//mxd
|
|
private static Linedef FindPotentialLine(Vector2D target, Vector2D center)
|
|
{
|
|
// Target position on top of existing vertex?
|
|
Vertex v = General.Map.Map.NearestVertex(target);
|
|
if(v == null) return null;
|
|
|
|
Linedef result = null;
|
|
if(v.Position == target)
|
|
{
|
|
double mindistance = double.MaxValue;
|
|
foreach(Linedef l in v.Linedefs)
|
|
{
|
|
if(result == null)
|
|
{
|
|
result = l;
|
|
mindistance = Vector2D.DistanceSq(l.GetCenterPoint(), center);
|
|
}
|
|
else
|
|
{
|
|
double curdistance = Vector2D.DistanceSq(l.GetCenterPoint(), center);
|
|
if(curdistance < mindistance)
|
|
{
|
|
mindistance = curdistance;
|
|
result = l;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Result position will split a line?
|
|
result = General.Map.Map.NearestLinedef(target);
|
|
if(result.SideOfLine(target) != 0) return null;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== mxd. Settings panel
|
|
|
|
protected virtual void SetupInterface()
|
|
{
|
|
//Add options docker
|
|
panel = new DrawLineOptionsPanel();
|
|
panel.OnContinuousDrawingChanged += OnContinuousDrawingChanged;
|
|
panel.OnAutoCloseDrawingChanged += OnAutoCloseDrawingChanged;
|
|
panel.OnShowGuidelinesChanged += OnShowGuidelinesChanged;
|
|
|
|
// Needs to be set after adding the events...
|
|
panel.ContinuousDrawing = General.Settings.ReadPluginSetting("drawlinesmode.continuousdrawing", false);
|
|
panel.AutoCloseDrawing = General.Settings.ReadPluginSetting("drawlinesmode.autoclosedrawing", false);
|
|
panel.ShowGuidelines = General.Settings.ReadPluginSetting("drawlinesmode.showguidelines", false);
|
|
|
|
// Create guide labels
|
|
guidelabels = new LineLengthLabel[4];
|
|
for(int i = 0; i < guidelabels.Length; i++)
|
|
{
|
|
guidelabels[i] = new LineLengthLabel { ShowAngle = false, Color = General.Colors.InfoLine };
|
|
}
|
|
|
|
// Create map boudary lines
|
|
Vector2D btl = new Vector2D(General.Map.Config.LeftBoundary, General.Map.Config.TopBoundary);
|
|
Vector2D btr = new Vector2D(General.Map.Config.RightBoundary, General.Map.Config.TopBoundary);
|
|
Vector2D bbl = new Vector2D(General.Map.Config.LeftBoundary, General.Map.Config.BottomBoundary);
|
|
Vector2D bbr = new Vector2D(General.Map.Config.RightBoundary, General.Map.Config.BottomBoundary);
|
|
top = new Line2D(btl, btr);
|
|
right = new Line2D(btr, bbr);
|
|
bottom = new Line2D(bbl, bbr);
|
|
left = new Line2D(btl, bbl);
|
|
}
|
|
|
|
protected virtual void AddInterface()
|
|
{
|
|
panel.Register();
|
|
}
|
|
|
|
protected virtual void RemoveInterface()
|
|
{
|
|
General.Settings.WritePluginSetting("drawlinesmode.continuousdrawing", panel.ContinuousDrawing);
|
|
General.Settings.WritePluginSetting("drawlinesmode.autoclosedrawing", panel.AutoCloseDrawing);
|
|
General.Settings.WritePluginSetting("drawlinesmode.showguidelines", panel.ShowGuidelines);
|
|
panel.Unregister();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Events
|
|
|
|
public override void OnHelp()
|
|
{
|
|
General.ShowHelp("e_drawgeometry.html");
|
|
}
|
|
|
|
// Engaging
|
|
public override void OnEngage()
|
|
{
|
|
base.OnEngage();
|
|
EnableAutoPanning();
|
|
AddInterface(); //mxd
|
|
renderer.SetPresentation(Presentation.Standard);
|
|
|
|
// Set cursor
|
|
General.Interface.SetCursor(Cursors.Cross);
|
|
}
|
|
|
|
// Disengaging
|
|
public override void OnDisengage()
|
|
{
|
|
RemoveInterface(); //mxd
|
|
base.OnDisengage();
|
|
DisableAutoPanning();
|
|
}
|
|
|
|
// Cancelled
|
|
public override void OnCancel()
|
|
{
|
|
//mxd. Cannot leave this way when continuous drawing is enabled
|
|
if(continuousdrawing)
|
|
{
|
|
drawingautoclosed = false;
|
|
return;
|
|
}
|
|
|
|
// 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[]
|
|
{ "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
|
|
{
|
|
// 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)
|
|
{
|
|
//mxd. Reset settings
|
|
points.Clear();
|
|
labels.Clear();
|
|
drawingautoclosed = false;
|
|
|
|
//mxd. Redraw display
|
|
General.Interface.RedrawDisplay();
|
|
}
|
|
else
|
|
{
|
|
// 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, General.Settings.ActiveThingsAlpha);
|
|
renderer.Finish();
|
|
}
|
|
|
|
// Normal update
|
|
Update();
|
|
}
|
|
|
|
// Mouse moving
|
|
public override void OnMouseMove(MouseEventArgs e)
|
|
{
|
|
base.OnMouseMove(e);
|
|
if(panning) return; //mxd. Skip all this jazz 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)) ||
|
|
(snaptocardinaldirection != (General.Interface.AltState && General.Interface.ShiftState))) 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)) ||
|
|
(snaptocardinaldirection != (General.Interface.AltState && General.Interface.ShiftState))) Update();
|
|
}
|
|
|
|
//mxd
|
|
protected void OnContinuousDrawingChanged(object value, EventArgs e)
|
|
{
|
|
continuousdrawing = (bool)value;
|
|
}
|
|
|
|
//mxd
|
|
protected void OnAutoCloseDrawingChanged(object value, EventArgs e)
|
|
{
|
|
autoclosedrawing = (bool)value;
|
|
}
|
|
|
|
//mxd
|
|
protected void OnShowGuidelinesChanged(object value, EventArgs e)
|
|
{
|
|
showguidelines = (bool)value;
|
|
General.Interface.RedrawDisplay();
|
|
}
|
|
|
|
#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 last point
|
|
[BeginAction("removepoint")]
|
|
public virtual void RemovePoint() { RemovePointAt(points.Count - 1); }
|
|
|
|
//mxd. Remove first point
|
|
[BeginAction("removefirstpoint")]
|
|
public virtual void RemoveFirstPoint() { RemovePointAt(0); }
|
|
|
|
//mxd
|
|
private void RemovePointAt(int index)
|
|
{
|
|
if(points.Count > 0 && points.Count > index) points.RemoveAt(index);
|
|
if(labels.Count > 0 && labels.Count > index)
|
|
{
|
|
labels[index].Dispose();
|
|
labels.RemoveAt(index);
|
|
}
|
|
|
|
Update();
|
|
}
|
|
|
|
// Finish drawing
|
|
[BeginAction("finishdraw")]
|
|
public void FinishDraw()
|
|
{
|
|
// Accept the changes
|
|
General.Editing.AcceptMode();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|