UltimateZoneBuilder/Source/Plugins/BuilderModes/ClassicModes/DrawGeometryMode.cs
MaxED c7696b0464 Fixed, Draw Lines/Rectangle/Circle/Curve modes: line length labels displayed incorrect length.
Changed, Drag Linedefs/Vertices/Sectors/Things modes: line length labels are now displayed the same way as in Draw modes.
Changed, Drag Linedefs/Vertices/Sectors/Things modes: "lock movement to cardinal directions" mode (Alt-Shift-Drag) now locks movement in 4 directions instead of 8 and doesn't snap map elements to nearest grid intersections when they are not aligned to it. 
Added, Visual mode, GZDoom, DECORATE: FORCEXYBILLBOARD flag is now supported.
Added, Visual mode, GLOOME, DECORATE: FLOORSPRITE, CEILSPRITE, WALLSPRITE, ROLLSPRITE and STICKTOPLANE flags are now supported (implementation is somewhat broken ATM and probably doesn't work the same way as in GLOOME, because Windows build with most these features is nowhere to be found...).
Fixed, Visual mode: in some cases Thing brightness was calculated incorrectly.
Updated ZDoom_DECORATE.cfg.
2015-08-25 22:05:14 +00:00

635 lines
19 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 = "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;
// Keep track of view changes (never used. mxd)
//protected float lastoffsetx;
//protected float lastoffsety;
//protected float lastscale;
// Options
protected bool snaptogrid; // SHIFT to toggle
protected bool snaptonearest; // CTRL to enable
protected bool snaptocardinaldirection; //mxd. ALT-SHIFT to enable
protected static bool usefourcardinaldirections;
//mxd. Labels display style
protected bool labelshowangle = true;
protected bool labeluseoffset = true;
#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);
// 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 (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].Move(points[i].pos, points[i + 1].pos);
// Update label for active line
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)
{
// 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(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
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, 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
//mxd. If snap to cardinal directions is enabled and we have points, modify mouse position
Vector2D vm;
if(snaptocardinal && points.Count > 0)
{
Vector2D offset = mousemappos - points[points.Count - 1].pos;
float 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;
}
else
{
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 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.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
vm.x = (float)Math.Round(vm.x); //mxd
vm.y = (float)Math.Round(vm.y); //mxd
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, snaptocardinaldirection, 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(labelshowangle, labeluseoffset));
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[]
{ "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, Presentation.THINGS_ALPHA);
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();
}
#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
}
}