UltimateZoneBuilder/Source/Plugins/BuilderModes/ClassicModes/DragGeometryMode.cs
MaxED 77611bd8d3 Added: sectors are now split into multiple sectors when their shape is divided into several closed shapes by drawing new geometry or dragging existing geometry using Edit Selection and Drag Geometry modes.
Fixed: in some cases sidedefs belonging to linedefs, which were moved on top of existing linedefs, were incorrectly reassigned when applying Edit Selection and Drag Geometry modes.
Fixed: Tag Explorer update fix from R2630 was accidentally placed in an UDMF-only if-block.
Updated ZDoom_DECORATE.cfg (A_WeaponOffset).
Updated documentation ("(G)ZDoom text lumps support" page).
2016-05-18 20:31:40 +00:00

623 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
private 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;
//mxd. Undo description
protected string undodescription = "Drag geometry";
// 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 = (BuilderPlug.Me.SyncronizeThingEdit ? selectedthings : new List<Thing>()); //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;
}
// 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()) + 44)) / 90 * 90);
offset = new Vector2D(0, -offset.GetLength()).GetRotated(angle);
snapgridincrement = true; // We don't want to move the geometry away from the cardinal directions
}
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(undodescription);
// Move selected geometry to final position
MoveGeometryRelative(mousemappos - dragstartmappos, snaptogrid, snaptogridincrement, snaptonearest, snaptocardinaldirection);
//mxd. Used in Linedef.Join()...
General.Map.Map.MarkSelectedSectors(true, true);
// Stitch geometry
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();
//mxd. Let the plugins know
General.Editing.AcceptMode();
// 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);
//mxd. Skip most of update jazz while panning
if(panning)
{
// Update labels
int index = 0;
foreach(Linedef l in unstablelines)
labels[index++].Move(l.Start.Position, l.End.Position);
}
else
{
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
}
}