UltimateZoneBuilder/Source/Plugins/BuilderModes/ClassicModes/DragGeometryMode.cs
MaxED 712dbd03e8 Cosmetic: renamed Drawing mode to Draw Lines mode.
Changed: current 2D position and zoom is now stored in the .dbs file when saving the map and restored when opening it (should this be turned into toggleable option?).
Draw Lines mode, Draw Curve mode: you can now hold Alt - Shift to snap to 8 cardinal directions.
Draw Rectangle mode, Draw Grid mode: you can now hold Alt - Shift to draw square shapes.
Draw Ellipse mode: you can now hold Alt - Shift to draw circle shapes.
You can now hold Alt - Shift to snap to 8 cardinal directions while dragging map objects.
Fixed, Draw Lines mode: line angle is now displayed in 0..359 range (was -45..314).
Fixed, Linedef info panel: line angle is now displayed in 0..359 range (was -45..314).
2015-07-09 22:32:12 +00:00

608 lines
20 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.Map;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Types;
#endregion
namespace CodeImp.DoomBuilder.BuilderModes
{
public abstract class DragGeometryMode : BaseClassicMode
{
#region ================== Constants
#endregion
#region ================== Variables
// Mouse position on map where dragging started
private Vector2D dragstartmappos;
//mxd. Offset from nearest grid intersection to dragstartmappos
private Vector2D dragstartoffset;
// Item used as reference for snapping to the grid
protected Vertex dragitem;
private Vector2D dragitemposition;
// List of old vertex positions
private List<Vector2D> oldpositions;
private List<Vector2D> oldthingpositions; //mxd
// List of selected items
protected ICollection<Vertex> selectedverts;
protected ICollection<Thing> selectedthings; //mxd
// List of non-selected items
protected ICollection<Vertex> unselectedverts;
protected ICollection<Thing> unselectedthings; //mxd
// List of things, which should be moved
protected ICollection<Thing> thingstodrag; //mxd
//mxd. List of sectors
private List<Sector> selectedsectors;
// List of unstable lines
protected ICollection<Linedef> unstablelines;
// List of unselected lines
protected ICollection<Linedef> snaptolines;
// Text labels for all unstable lines
protected LineLengthLabel[] labels;
// Keep track of view changes
private float lastoffsetx;
private float lastoffsety;
private float lastscale;
// Options
private bool snaptogrid; // SHIFT to toggle
private bool snaptonearest; // CTRL to enable
private bool snaptogridincrement; //mxd. ALT to toggle
private bool snaptocardinaldirection; //mxd. ALT-SHIFT 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
// 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
// Constructor to start dragging immediately
protected void StartDrag(Vector2D dragstartmappos)
{
// Initialize
this.dragstartmappos = dragstartmappos;
Cursor.Current = Cursors.AppStarting;
// We don't want to record this for undoing while we move the geometry around.
// This will be set back to normal when we're done.
General.Map.UndoRedo.IgnorePropChanges = true;
// Make list of selected vertices and things
selectedverts = General.Map.Map.GetMarkedVertices(true);
selectedthings = General.Map.Map.GetSelectedThings(true); //mxd
thingstodrag = GetThingsToDrag(); //mxd
// Make list of non-selected vertices and things
// Non-selected vertices will be used for snapping to nearest items
unselectedverts = General.Map.Map.GetMarkedVertices(false);
unselectedthings = new List<Thing>(); //mxd
foreach(Thing t in General.Map.ThingsFilter.VisibleThings) if(!t.Selected) unselectedthings.Add(t);
// Get the nearest vertex for snapping
dragitem = MapSet.NearestVertex(selectedverts, dragstartmappos);
//mxd. Get drag offset
dragstartoffset = General.Map.Grid.SnappedToGrid(dragitem.Position) - dragitem.Position;
// Lines to snap to
snaptolines = General.Map.Map.LinedefsFromMarkedVertices(true, false, false);
// Make old positions list
// We will use this as reference to move the vertices, or to move them back on cancel
oldpositions = new List<Vector2D>(selectedverts.Count);
foreach(Vertex v in selectedverts) oldpositions.Add(v.Position);
//mxd
oldthingpositions = new List<Vector2D>(thingstodrag.Count);
foreach(Thing t in thingstodrag) oldthingpositions.Add(t.Position);
// Also keep old position of the dragged item
dragitemposition = dragitem.Position;
// Keep view information
lastoffsetx = renderer.OffsetX;
lastoffsety = renderer.OffsetY;
lastscale = renderer.Scale;
// Make list of unstable lines only
// These will have their length displayed during the drag
unstablelines = MapSet.UnstableLinedefsFromVertices(selectedverts);
//mxd. Collect selected sectors
if(General.Map.UDMF)
{
ICollection<Linedef> selectedLines = General.Map.Map.LinedefsFromMarkedVertices(false, true, false);
List<Sector> affectedSectors = new List<Sector>();
foreach(Linedef l in selectedLines)
{
if(l.Front != null && l.Front.Sector != null && !affectedSectors.Contains(l.Front.Sector))
affectedSectors.Add(l.Front.Sector);
if(l.Back != null && l.Back.Sector != null && !affectedSectors.Contains(l.Back.Sector))
affectedSectors.Add(l.Back.Sector);
}
selectedsectors = new List<Sector>();
foreach(Sector s in affectedSectors)
{
bool selected = true;
foreach(Sidedef side in s.Sidedefs)
{
if(!selectedLines.Contains(side.Line))
{
selected = false;
break;
}
}
if(selected) selectedsectors.Add(s);
}
}
// Make text labels
labels = new LineLengthLabel[unstablelines.Count];
int index = 0;
foreach(Linedef l in unstablelines)
labels[index++] = new LineLengthLabel(l.Start.Position, l.End.Position);
Cursor.Current = Cursors.Default;
}
//mxd
protected virtual ICollection<Thing> GetThingsToDrag()
{
return selectedthings;
}
// This moves the selected geometry relatively
// Returns true when geometry has actually moved
private bool MoveGeometryRelative(Vector2D offset, bool snapgrid, bool snapgridincrement, bool snapnearest, bool snapcardinal)
{
//mxd. If snap to cardinal directions is enabled, modify the offset
if(snapcardinal)
{
float angle = Angle2D.DegToRad((General.ClampAngle((int)Angle2D.RadToDeg(offset.GetAngle()) + 22)) / 45 * 45);
offset = new Vector2D(0, -offset.GetLength()).GetRotated(angle);
}
Vector2D oldpos = dragitem.Position;
Vector2D anchorpos = dragitemposition + offset;
Vector2D tl, br;
// don't move if the offset contains invalid data
if (!offset.IsFinite()) return false;
// Find the outmost vertices
tl = br = oldpositions[0];
for (int i = 0; i < oldpositions.Count; i++)
{
if (oldpositions[i].x < tl.x) tl.x = (int)oldpositions[i].x;
if (oldpositions[i].x > br.x) br.x = (int)oldpositions[i].x;
if (oldpositions[i].y > tl.y) tl.y = (int)oldpositions[i].y;
if (oldpositions[i].y < br.y) br.y = (int)oldpositions[i].y;
}
// Snap to nearest?
if(snapnearest)
{
// Find nearest unselected vertex within range
Vertex nv = MapSet.NearestVertexSquareRange(unselectedverts, anchorpos, BuilderPlug.Me.StitchRange / renderer.Scale);
if(nv != null)
{
// Move the dragged item
dragitem.Move(nv.Position);
// Adjust the offset
offset = nv.Position - dragitemposition;
// Do not snap to grid!
snapgrid = false;
snaptogridincrement = false; //mxd
}
else
{
// Find the nearest unselected line within range
Linedef nl = MapSet.NearestLinedefRange(snaptolines, anchorpos, BuilderPlug.Me.StitchRange / renderer.Scale);
if(nl != null)
{
// Snap to grid?
if(snapgrid || snapgridincrement)
{
// Get grid intersection coordinates
List<Vector2D> coords = nl.GetGridIntersections(snapgridincrement ? dragstartoffset : new Vector2D());
// mxd. Do the rest only if we actually have some coordinates
if (coords.Count > 0)
{
// Find nearest grid intersection
float found_distance = float.MaxValue;
Vector2D found_coord = new Vector2D();
foreach (Vector2D v in coords)
{
Vector2D delta = anchorpos - v;
if (delta.GetLengthSq() < found_distance)
{
found_distance = delta.GetLengthSq();
found_coord = v;
}
}
// Move the dragged item
dragitem.Move(found_coord);
// Align to line here
offset = found_coord - dragitemposition;
// Do not snap to grid anymore
snapgrid = false;
snapgridincrement = false; //mxd
}
}
else
{
// Move the dragged item
dragitem.Move(nl.NearestOnLine(anchorpos));
// Align to line here
offset = nl.NearestOnLine(anchorpos) - dragitemposition;
}
}
}
}
// Snap to grid or grid increment?
if(snapgrid || snapgridincrement)
{
// Move the dragged item
dragitem.Move(anchorpos);
// Snap item to grid increment
if(snapgridincrement) //mxd
{
dragitem.Move(General.Map.Grid.SnappedToGrid(dragitem.Position) - dragstartoffset);
}
else // Or to the grid itself
{
dragitem.SnapToGrid();
}
// Adjust the offset
offset += dragitem.Position - anchorpos;
}
// Make sure the offset is inside the map boundaries
if (offset.x + tl.x < General.Map.Config.LeftBoundary) offset.x = General.Map.Config.LeftBoundary - tl.x;
if (offset.x + br.x > General.Map.Config.RightBoundary) offset.x = General.Map.Config.RightBoundary - br.x;
if (offset.y + tl.y > General.Map.Config.TopBoundary) offset.y = General.Map.Config.TopBoundary - tl.y;
if (offset.y + br.y < General.Map.Config.BottomBoundary) offset.y = General.Map.Config.BottomBoundary - br.y;
// Drag item moved?
if((!snapgrid && !snapgridincrement) || (dragitem.Position != oldpos))
{
int i = 0;
// Move selected geometry
foreach(Vertex v in selectedverts)
{
// Move vertex from old position relative to the mouse position change since drag start
v.Move(oldpositions[i++] + offset);
}
//mxd. Move selected things
i = 0;
foreach(Thing t in thingstodrag)
{
t.Move(oldthingpositions[i++] + offset);
}
// Update labels
int index = 0;
foreach(Linedef l in unstablelines)
labels[index++].Move(l.Start.Position, l.End.Position);
// Moved
return true;
}
// No changes
return false;
}
// Cancelled
public override void OnCancel()
{
// Move geometry back to original position
MoveGeometryRelative(new Vector2D(0f, 0f), false, false, false, false);
// Resume normal undo/redo recording
General.Map.UndoRedo.IgnorePropChanges = false;
// If only a single vertex was selected, deselect it now
if(selectedverts.Count == 1) General.Map.Map.ClearSelectedVertices();
// Update cached values
General.Map.Map.Update();
// Cancel base class
base.OnCancel();
// Return to vertices mode
General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name);
}
// Mode engages
public override void OnEngage()
{
base.OnEngage();
EnableAutoPanning();
renderer.SetPresentation(Presentation.Standard);
}
// Disenagaging
public override void OnDisengage()
{
base.OnDisengage();
DisableAutoPanning();
// When not cancelled
if(!cancelled)
{
Cursor.Current = Cursors.AppStarting;
// Move geometry back to original position
MoveGeometryRelative(new Vector2D(0f, 0f), false, false, false, false);
// Resume normal undo/redo recording
General.Map.UndoRedo.IgnorePropChanges = false;
// Make undo for the dragging
General.Map.UndoRedo.CreateUndo("Drag geometry");
// Move selected geometry to final position
MoveGeometryRelative(mousemappos - dragstartmappos, snaptogrid, snaptogridincrement, snaptonearest, snaptocardinaldirection);
// Stitch geometry
if(snaptonearest) General.Map.Map.StitchGeometry();
// Make corrections for backward linedefs
MapSet.FlipBackwardLinedefs(General.Map.Map.Linedefs);
// Snap to map format accuracy
General.Map.Map.SnapAllToAccuracy();
//mxd. Update floor/ceiling texture offsets and slopes?
if(General.Map.UDMF)
{
Vector2D offset = dragitem.Position - dragitemposition;
// Update floor/ceiling texture offsets?
if(BuilderPlug.Me.LockSectorTextureOffsetsWhileDragging)
{
foreach(Sector s in selectedsectors)
{
s.Fields.BeforeFieldsChange();
// Update ceiling offset
if(s.LongCeilTexture != MapSet.EmptyLongName)
{
ImageData texture = General.Map.Data.GetFlatImage(s.CeilTexture);
if(texture != null)
{
float scalex = s.Fields.GetValue("xscaleceiling", 1.0f);
float scaley = s.Fields.GetValue("yscaleceiling", 1.0f);
if(scalex != 0 && scaley != 0)
{
Vector2D ceiloffset = new Vector2D(-offset.x, offset.y).GetRotated(-Angle2D.DegToRad((int)s.Fields.GetValue("rotationceiling", 0f)));
ceiloffset.x += s.Fields.GetValue("xpanningceiling", 0f);
ceiloffset.y += s.Fields.GetValue("ypanningceiling", 0f);
int texturewidth = (int)Math.Round(texture.Width / scalex);
int textureheight = (int)Math.Round(texture.Height / scaley);
if(!s.Fields.ContainsKey("xpanningceiling")) s.Fields.Add("xpanningceiling", new UniValue(UniversalType.Float, (float)Math.Round(ceiloffset.x % texturewidth)));
else s.Fields["xpanningceiling"].Value = (float)Math.Round(ceiloffset.x % texturewidth);
if(!s.Fields.ContainsKey("ypanningceiling")) s.Fields.Add("ypanningceiling", new UniValue(UniversalType.Float, (float)Math.Round(ceiloffset.y % textureheight)));
else s.Fields["ypanningceiling"].Value = (float)Math.Round(ceiloffset.y % textureheight);
}
}
}
// Update floor offset
if(s.LongFloorTexture != MapSet.EmptyLongName)
{
ImageData texture = General.Map.Data.GetFlatImage(s.FloorTexture);
if(texture != null)
{
float scalex = s.Fields.GetValue("xscalefloor", 1.0f);
float scaley = s.Fields.GetValue("yscalefloor", 1.0f);
if(scalex != 0 && scaley != 0)
{
Vector2D flooroffset = new Vector2D(-offset.x, offset.y).GetRotated(-Angle2D.DegToRad((int)s.Fields.GetValue("rotationfloor", 0f)));
flooroffset.x += s.Fields.GetValue("xpanningfloor", 0f);
flooroffset.y += s.Fields.GetValue("ypanningfloor", 0f);
int texturewidth = (int)Math.Round(texture.Width / scalex);
int textureheight = (int)Math.Round(texture.Height / scaley);
if(!s.Fields.ContainsKey("xpanningfloor")) s.Fields.Add("xpanningfloor", new UniValue(UniversalType.Float, (float)Math.Round(flooroffset.x % texturewidth)));
else s.Fields["xpanningfloor"].Value = (float)Math.Round(flooroffset.x % texturewidth);
if(!s.Fields.ContainsKey("ypanningfloor")) s.Fields.Add("ypanningfloor", new UniValue(UniversalType.Float, (float)Math.Round(flooroffset.y % textureheight)));
else s.Fields["ypanningfloor"].Value = (float)Math.Round(flooroffset.y % textureheight);
}
}
}
}
}
// Update slopes
foreach(Sector s in selectedsectors)
{
// Update floor slope?
if(s.FloorSlope.GetLengthSq() > 0 && !float.IsNaN(s.FloorSlopeOffset / s.FloorSlope.z))
{
Plane floor = new Plane(s.FloorSlope, s.FloorSlopeOffset);
Vector2D center = new Vector2D(s.BBox.X + s.BBox.Width / 2, s.BBox.Y + s.BBox.Height / 2);
s.FloorSlopeOffset = -Vector3D.DotProduct(s.FloorSlope, new Vector3D(center + offset, floor.GetZ(center)));
}
// Update ceiling slope?
if(s.CeilSlope.GetLengthSq() > 0 && !float.IsNaN(s.CeilSlopeOffset / s.CeilSlope.z))
{
Plane ceiling = new Plane(s.CeilSlope, s.CeilSlopeOffset);
Vector2D center = new Vector2D(s.BBox.X + s.BBox.Width / 2, s.BBox.Y + s.BBox.Height / 2);
s.CeilSlopeOffset = -Vector3D.DotProduct(s.CeilSlope, new Vector3D(center + offset, ceiling.GetZ(center)));
}
}
}
// Update cached values
General.Map.Map.Update();
// Done
Cursor.Current = Cursors.Default;
General.Map.IsChanged = true;
}
}
// 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
private void Update()
{
snaptocardinaldirection = (General.Interface.ShiftState && General.Interface.AltState); //mxd
snaptogrid = (snaptocardinaldirection || General.Interface.ShiftState ^ General.Interface.SnapToGrid);
snaptonearest = General.Interface.CtrlState ^ General.Interface.AutoMerge;
snaptogridincrement = (!snaptocardinaldirection && General.Interface.AltState); //mxd
// Move selected geometry
if(MoveGeometryRelative(mousemappos - dragstartmappos, snaptogrid, snaptogridincrement, snaptonearest, snaptocardinaldirection))
{
// Update cached values
General.Map.Map.Update(true, false);
// Redraw
UpdateRedraw();
renderer.Present();
}
}
// This redraws only the required things
protected virtual void UpdateRedraw() { }
// When edit button is released
protected override void OnEditEnd()
{
// Just return to base mode, Disengage will be called automatically.
General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name);
base.OnEditEnd();
}
// 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)) ||
(snaptogridincrement != General.Interface.AltState) ||
(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)) ||
(snaptogridincrement != General.Interface.AltState) ||
(snaptocardinaldirection != (General.Interface.AltState && General.Interface.ShiftState))) Update();
}
#endregion
}
}