UltimateZoneBuilder/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs
MaxED 2458ea3d32 Changed, internal, UDMF: local texture offsets and scale are now copied when merging sidedefs.
Changed, internal: made TextLabel and IRenderer2D public properties compatible with DB2 implementation.
Fixed some more cases when sidedefs belonging to linedefs, which were moved on top of existing linedefs, were incorrectly reassigned when applying Edit Selection and Drag Geometry modes.
Fixed, Map Analysis mode: error checks must be updated after switching map format.
2016-05-19 21:44:39 +00:00

1938 lines
60 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.Collections.ObjectModel;
using System.Drawing;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Actions;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.Controls;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Editing;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.Types;
using CodeImp.DoomBuilder.Windows;
#endregion
namespace CodeImp.DoomBuilder.BuilderModes
{
[EditMode(DisplayName = "Edit Selection Mode",
SwitchAction = "editselectionmode",
ButtonImage = "Selection3.png",
ButtonOrder = 1,
ButtonGroup = "002_modify",
Volatile = true,
UseByDefault = true,
Optional = false)]
public class EditSelectionMode : BaseClassicMode
{
#region ================== Enums
private enum ModifyMode
{
None,
Dragging,
Resizing,
Rotating
}
private enum Grip
{
None,
Main,
SizeN,
SizeS,
SizeE,
SizeW,
RotateLT,
RotateRT,
RotateRB,
RotateLB
}
#endregion
#region ================== Structs (mxd)
private struct SectorTextureInfo
{
public readonly SurfaceTextureInfo Floor;
public readonly SurfaceTextureInfo Ceiling;
public SectorTextureInfo(Sector s)
{
// Get transform properties
Floor.Offset = new Vector2D(UniFields.GetFloat(s.Fields, "xpanningfloor", 0f), UniFields.GetFloat(s.Fields, "ypanningfloor", 0f));
Ceiling.Offset = new Vector2D(UniFields.GetFloat(s.Fields, "xpanningceiling", 0f), UniFields.GetFloat(s.Fields, "ypanningceiling", 0f));
Floor.Scale = new Vector2D(UniFields.GetFloat(s.Fields, "xscalefloor", 1.0f), -UniFields.GetFloat(s.Fields, "yscalefloor", 1.0f));
Ceiling.Scale = new Vector2D(UniFields.GetFloat(s.Fields, "xscaleceiling", 1.0f), -UniFields.GetFloat(s.Fields, "yscaleceiling", 1.0f));
Floor.Rotation = Angle2D.DegToRad(UniFields.GetFloat(s.Fields, "rotationfloor", 0f));
Ceiling.Rotation = Angle2D.DegToRad(UniFields.GetFloat(s.Fields, "rotationceiling", 0f));
// Get texture sizes
Floor.TextureSize = GetTextureSize(s.LongFloorTexture);
Ceiling.TextureSize = GetTextureSize(s.LongCeilTexture);
// Surface name
Floor.Part = "floor";
Ceiling.Part = "ceiling";
}
private static Size GetTextureSize(long hash)
{
ImageData texture = General.Map.Data.GetFlatImage(hash);
if((texture == null) || (texture == General.Map.Data.WhiteTexture) ||
(texture.Width <= 0) || (texture.Height <= 0) || !texture.IsImageLoaded)
{
return new Size();
}
return new Size((int)Math.Round(texture.ScaledWidth), (int)Math.Round(texture.ScaledHeight));
}
}
private struct SurfaceTextureInfo
{
public Vector2D Offset;
public Vector2D Scale;
public Size TextureSize;
public float Rotation;
public string Part;
}
#endregion
#region ================== Constants
private const float GRIP_SIZE = 9.0f;
private const float ZERO_SIZE_ADDITION = 20.0f;
private const byte RECTANGLE_ALPHA = 60;
private const byte EXTENSION_LINE_ALPHA = 150;
private readonly Cursor[] RESIZE_CURSORS = { Cursors.SizeNS, Cursors.SizeNWSE, Cursors.SizeWE, Cursors.SizeNESW };
#endregion
#region ================== Variables
// Modes
private bool modealreadyswitching;
private bool clearselection; //mxd
private bool pasting;
private bool autodrag; //mxd
private PasteOptions pasteoptions;
// Docker
private EditSelectionPanel panel;
private Docker docker;
// Highlighted vertex
private MapElement highlighted;
private Vector2D highlightedpos;
// Selection
private ICollection<Vertex> selectedvertices;
private ICollection<Thing> selectedthings;
private Dictionary<Sector, SectorTextureInfo> selectedsectors; //mxd
private List<int> fixedrotationthingtypes; //mxd
private ICollection<Linedef> selectedlines;
private List<Vector2D> vertexpos;
private List<Vector2D> thingpos;
private List<float> thingangle;
private ICollection<Vertex> unselectedvertices;
private ICollection<Linedef> unselectedlines;
private ICollection<Linedef> unstablelines; //mxd
// Modification
private float rotation;
private Vector2D offset;
private Vector2D size;
private Vector2D scale = new Vector2D(1.0f, 1.0f); //mxd
private Vector2D baseoffset;
private Vector2D basesize;
private bool linesflipped;
private bool usepreciseposition; //mxd
//mxd. Texture modification
private static bool transformflooroffsets;
private static bool transformceiloffsets;
private static bool rotateflooroffsets;
private static bool rotateceiloffsets;
private static bool scaleflooroffsets;
private static bool scaleceiloffsets;
private Vector2D selectioncenter;
private Vector2D selectionbasecenter;
// Modifying Modes
private ModifyMode mode;
private Vector2D dragoffset;
private Vector2D resizefilter;
private Vector2D resizevector;
private Vector2D edgevector;
private Line2D resizeaxis;
private int stickcorner;
private float rotategripangle;
private bool autopanning;
// Rectangle components
private Vector2D[] originalcorners; // lefttop, righttop, rightbottom, leftbottom
private Vector2D[] corners;
private FlatVertex[] cornerverts;
private RectangleF[] resizegrips; // top, right, bottom, left
private RectangleF[] rotategrips; // lefttop, righttop, rightbottom, leftbottom
private Line2D extensionline;
// Options
private bool snaptogrid; // SHIFT to toggle
private bool snaptonearest; // CTRL to enable
#endregion
#region ================== Properties
public override object HighlightedObject { get { return highlighted; } }
public bool Pasting { get { return pasting; } set { pasting = value; } }
public PasteOptions PasteOptions { get { return pasteoptions; } set { pasteoptions = value.Copy(); } }
//mxd. Modification
internal bool UsePrecisePosition { get { return usepreciseposition; } set { usepreciseposition = value; } }
//mxd. Texture offset properties
internal bool TransformFloorOffsets { get { return transformflooroffsets; } set { transformflooroffsets = value; UpdateAllChanges(); } }
internal bool TransformCeilingOffsets { get { return transformceiloffsets; } set { transformceiloffsets = value; UpdateAllChanges(); } }
internal bool RotateFloorOffsets { get { return rotateflooroffsets; } set { rotateflooroffsets = value; UpdateAllChanges(); } }
internal bool RotateCeilingOffsets { get { return rotateceiloffsets; } set { rotateceiloffsets = value; UpdateAllChanges(); } }
internal bool ScaleFloorOffsets { get { return scaleflooroffsets; } set { scaleflooroffsets = value; UpdateAllChanges(); } }
internal bool ScaleCeilingOffsets { get { return scaleceiloffsets; } set { scaleceiloffsets = value; UpdateAllChanges(); } }
#endregion
#region ================== Constructor / Disposer
// Constructor
public EditSelectionMode()
{
// Initialize
mode = ModifyMode.None;
}
//mxd. Another constructor. Used indirectly from ImportObjAsTerrainMode.OnAccept.
public EditSelectionMode(bool pasting)
{
// Initialize
this.pasting = pasting;
this.mode = ModifyMode.None;
}
// Disposer
public override void Dispose()
{
// Not already disposed?
if(!isdisposed)
{
// Clean up
// Dispose base
base.Dispose();
}
}
#endregion
#region ================== Methods
// The following functions set different properties and update
public void SetAbsPosX(float posx)
{
offset.x = posx;
UpdateAllChanges();
}
public void SetAbsPosY(float posy)
{
offset.y = posy;
UpdateAllChanges();
}
public void SetRelPosX(float posx)
{
offset.x = posx + baseoffset.x;
UpdateAllChanges();
}
public void SetRelPosY(float posy)
{
offset.y = posy + baseoffset.y;
UpdateAllChanges();
}
public void SetAbsSizeX(float sizex)
{
size.x = sizex;
UpdateAllChanges();
}
public void SetAbsSizeY(float sizey)
{
size.y = sizey;
UpdateAllChanges();
}
public void SetRelSizeX(float sizex)
{
size.x = basesize.x * (sizex / 100.0f);
UpdateAllChanges();
}
public void SetRelSizeY(float sizey)
{
size.y = basesize.y * (sizey / 100.0f);
UpdateAllChanges();
}
public void SetAbsRotation(float absrot)
{
rotation = absrot;
UpdateAllChanges();
}
// This updates all after changes were made
private void UpdateAllChanges()
{
UpdateGeometry();
UpdateRectangleComponents();
if(General.Map.UDMF) UpdateTextureTransform(); //mxd
General.Map.Map.Update();
General.Interface.RedrawDisplay();
}
// This highlights a new vertex
private void Highlight(MapElement h)
{
// Undraw previous highlight
if(highlighted != null && !highlighted.IsDisposed)
{
if(highlighted is Vertex)
{
if(renderer.StartPlotter(false))
{
renderer.PlotVertex((Vertex)highlighted, renderer.DetermineVertexColor((Vertex)highlighted));
renderer.Finish();
}
}
else
{
if(renderer.StartThings(false))
{
renderer.RenderThing((Thing)highlighted, renderer.DetermineThingColor((Thing)highlighted), General.Settings.ActiveThingsAlpha);
renderer.Finish();
}
}
}
// Set new highlight
highlighted = h;
// Render highlighted item
if(highlighted != null && !highlighted.IsDisposed)
{
if(highlighted is Vertex)
{
if(renderer.StartPlotter(false))
{
renderer.PlotVertex((Vertex)highlighted, ColorCollection.HIGHLIGHT);
renderer.Finish();
}
}
else
{
if(renderer.StartThings(false))
{
renderer.RenderThing((Thing)highlighted, General.Colors.Highlight, General.Settings.ActiveThingsAlpha);
renderer.Finish();
}
}
}
// Done
renderer.Present();
}
// This updates the selection
private void Update()
{
// Not in any modifying mode?
if(mode == ModifyMode.None)
{
// Check what grip the mouse is over
// and change cursor accordingly
Grip mousegrip = (autodrag ? Grip.Main : CheckMouseGrip()); //mxd. We only want to move when starting auto-dragging
switch(mousegrip)
{
case Grip.Main:
// Find the nearest vertex within highlight range
Vertex v = MapSet.NearestVertex(selectedvertices, mousemappos);
// Find the nearest thing within range
Thing t = MapSet.NearestThing(selectedthings, mousemappos);
// Highlight the one that is closer
if((v != null) && (t != null))
{
if(v.DistanceToSq(mousemappos) < t.DistanceToSq(mousemappos))
{
if(v != highlighted) Highlight(v);
}
else
{
if(t != highlighted) Highlight(t);
}
}
else if(v != null)
{
if(v != highlighted) Highlight(v);
}
else
{
if(t != highlighted) Highlight(t);
}
General.Interface.SetCursor(Cursors.Hand);
break;
case Grip.RotateLB:
case Grip.RotateLT:
case Grip.RotateRB:
case Grip.RotateRT:
Highlight(null);
General.Interface.SetCursor(Cursors.Cross);
break;
case Grip.SizeE:
case Grip.SizeS:
case Grip.SizeW:
case Grip.SizeN:
// Pick the best matching cursor depending on rotation and side
float resizeangle = rotation;
if((mousegrip == Grip.SizeE) || (mousegrip == Grip.SizeW)) resizeangle += Angle2D.PIHALF;
resizeangle = Angle2D.Normalized(resizeangle);
if(resizeangle > Angle2D.PI) resizeangle -= Angle2D.PI;
resizeangle = Math.Abs(resizeangle + Angle2D.PI / 8.000001f);
int cursorindex = (int)Math.Floor((resizeangle / Angle2D.PI) * 4.0f) % 4;
General.Interface.SetCursor(RESIZE_CURSORS[cursorindex]);
Highlight(null);
break;
default:
Highlight(null);
General.Interface.SetCursor(Cursors.Default);
break;
}
}
else
{
Vector2D snappedmappos = mousemappos;
bool dosnaptogrid = snaptogrid;
// Options
snaptogrid = General.Interface.ShiftState ^ General.Interface.SnapToGrid;
snaptonearest = General.Interface.CtrlState ^ General.Interface.AutoMerge;
// Change to crosshair cursor so we can clearly see around the mouse cursor
General.Interface.SetCursor(Cursors.Cross);
// Check what modifying mode we are in
switch(mode)
{
// Dragging
case ModifyMode.Dragging:
// Change offset without snapping
offset = mousemappos - dragoffset;
// Calculate transformed position of highlighted vertex
Vector2D transformedpos = TransformedPoint(highlightedpos);
// Snap to nearest vertex?
if(snaptonearest && (highlighted != null))
{
float vrange = BuilderPlug.Me.StitchRange / renderer.Scale;
// Try the nearest vertex
Vertex nv = MapSet.NearestVertexSquareRange(unselectedvertices, transformedpos, vrange);
if(nv != null)
{
// Change offset to snap to target
offset += nv.Position - transformedpos;
dosnaptogrid = false;
}
else
{
// Find the nearest unselected line within range
Linedef nl = MapSet.NearestLinedefRange(unselectedlines, transformedpos, BuilderPlug.Me.StitchRange / renderer.Scale);
if(nl != null)
{
// Snap to grid?
if(dosnaptogrid)
{
// Get grid intersection coordinates
List<Vector2D> coords = nl.GetGridIntersections();
// Find nearest grid intersection
float found_distance = float.MaxValue;
Vector2D found_pos = new Vector2D(float.NaN, float.NaN);
foreach(Vector2D v in coords)
{
Vector2D dist = transformedpos - v;
if(dist.GetLengthSq() < found_distance)
{
// Found a better match
found_distance = dist.GetLengthSq();
found_pos = v;
// Do not snap to grid anymore
dosnaptogrid = false;
}
}
// Found something?
if(!float.IsNaN(found_pos.x))
{
// Change offset to snap to target
offset += found_pos - transformedpos;
}
}
else
{
// Change offset to snap onto the line
offset += nl.NearestOnLine(transformedpos) - transformedpos;
}
}
}
}
// Snap to grid?
if(dosnaptogrid && (highlighted != null))
{
// Change offset to align to grid
offset += General.Map.Grid.SnappedToGrid(transformedpos) - transformedpos;
}
// Update
UpdateGeometry();
UpdateRectangleComponents();
General.Interface.RedrawDisplay();
break;
// Resizing
case ModifyMode.Resizing:
// Snap to nearest vertex?
if(snaptonearest)
{
float vrange = BuilderPlug.Me.StitchRange / renderer.Scale;
// Try the nearest vertex
Vertex nv = MapSet.NearestVertexSquareRange(unselectedvertices, snappedmappos, vrange);
if(nv != null)
{
snappedmappos = nv.Position;
dosnaptogrid = false;
}
}
// Snap to grid?
if(dosnaptogrid)
{
// Aligned to grid
snappedmappos = General.Map.Grid.SnappedToGrid(snappedmappos);
}
// Keep corner position
Vector2D oldcorner = corners[stickcorner];
// Change size with the scale from the ruler
float newscale = resizeaxis.GetNearestOnLine(snappedmappos);
size = (basesize * resizefilter) * newscale + size * (1.0f - resizefilter);
//mxd. Update scale
newscale = 1f / newscale;
if(float.IsInfinity(newscale) || float.IsNaN(newscale)) newscale = 99999f;
scale = (newscale * resizefilter) + scale * (1.0f - resizefilter);
if(float.IsInfinity(scale.x) || float.IsNaN(scale.x)) scale.x = 99999f;
if(float.IsInfinity(scale.y) || float.IsNaN(scale.y)) scale.y = 99999f;
// Adjust corner position
Vector2D newcorner = TransformedPoint(originalcorners[stickcorner]);
offset -= newcorner - oldcorner;
// Show the extension line so that the user knows what it is aligning to
Vector2D sizefiltered = (size * resizefilter);
float sizelength = sizefiltered.x + sizefiltered.y;
Line2D edgeline = new Line2D(resizeaxis.v1 + resizevector * sizelength, resizeaxis.v1 + resizevector * sizelength - edgevector);
float nearestonedge = edgeline.GetNearestOnLine(snappedmappos);
if(nearestonedge > 0.5f)
extensionline = new Line2D(edgeline.v1, snappedmappos);
else
extensionline = new Line2D(edgeline.v2, snappedmappos);
// Update
UpdateGeometry();
UpdateRectangleComponents();
General.Interface.RedrawDisplay();
break;
// Rotating
case ModifyMode.Rotating:
// Get angle from mouse to center
Vector2D center = offset + size * 0.5f;
Vector2D delta = snappedmappos - center;
rotation = delta.GetAngle() - rotategripangle;
// Snap rotation to grip?
if(dosnaptogrid)
{
// We make 24 vectors that the rotation can snap to
float founddistance = float.MaxValue;
float foundrotation = rotation;
Vector3D rotvec = Vector2D.FromAngle(rotation);
for(int i = 0; i < 24; i++)
{
// Make the vectors
float angle = i * Angle2D.PI * 0.08333333333f; //mxd. 15-degree increments
Vector2D gridvec = Vector2D.FromAngle(angle);
// Check distance
float dist = 2.0f - Vector2D.DotProduct(gridvec, rotvec);
if(dist < founddistance)
{
foundrotation = angle;
founddistance = dist;
}
}
// Keep rotation
rotation = foundrotation;
}
// Update
UpdateGeometry();
UpdateRectangleComponents();
General.Interface.RedrawDisplay();
break;
}
}
}
// This checks and returns the grip the mouse pointer is in
private Grip CheckMouseGrip()
{
if(PointInRectF(resizegrips[0], mousemappos)) return Grip.SizeN;
if(PointInRectF(resizegrips[2], mousemappos)) return Grip.SizeS;
if(PointInRectF(resizegrips[1], mousemappos)) return Grip.SizeE;
if(PointInRectF(resizegrips[3], mousemappos)) return Grip.SizeW;
if(PointInRectF(rotategrips[0], mousemappos)) return Grip.RotateLT;
if(PointInRectF(rotategrips[1], mousemappos)) return Grip.RotateRT;
if(PointInRectF(rotategrips[2], mousemappos)) return Grip.RotateRB;
if(PointInRectF(rotategrips[3], mousemappos)) return Grip.RotateLB;
if(Tools.PointInPolygon(corners, mousemappos)) return Grip.Main;
return Grip.None;
}
// This applies the current rotation and resize to a point
private Vector2D TransformedPoint(Vector2D p)
{
// Resize
p = (p - baseoffset) * (size / basesize) + baseoffset;
// Rotate
Vector2D center = baseoffset + size * 0.5f;
Vector2D po = p - center;
p = po.GetRotated(rotation);
p += center;
// Translate
p += offset - baseoffset;
return p;
}
// This applies the current rotation and resize to a point
private Vector2D TransformedPointNoScale(Vector2D p)
{
// Rotate
Vector2D center = baseoffset + size * 0.5f;
Vector2D po = p - center;
p = po.GetRotated(rotation);
p += center;
// Translate
p += offset - baseoffset;
return p;
}
// This applies the current rotation and resize to a point
private Vector2D TransformedPointNoRotate(Vector2D p)
{
// Resize
p = (p - baseoffset) * (size / basesize) + baseoffset;
// Translate
p += offset - baseoffset;
return p;
}
// This applies the current rotation and resize to a point
private Vector2D TransformedPointNoRotateNoScale(Vector2D p)
{
// Translate
p += offset - baseoffset;
return p;
}
// This checks if a point is in a rect
private static bool PointInRectF(RectangleF rect, Vector2D point)
{
return !(point.x < rect.Left || point.x > rect.Right || point.y < rect.Top || point.y > rect.Bottom); //mxd
}
// This updates the values in the panel
private void UpdatePanel()
{
Vector2D relsize = (size / basesize) * 100.0f;
if(panel != null)
panel.ShowCurrentValues(offset, offset - baseoffset, size, relsize, rotation);
}
// This moves all things and vertices to match the current transformation
private void UpdateGeometry()
{
float[] newthingangle = thingangle.ToArray();
int index;
// Flip things horizontally
if(size.x < 0.0f)
{
for(index = 0; index < newthingangle.Length; index++)
{
// Check quadrant
if((newthingangle[index] >= 0f) && (newthingangle[index] < Angle2D.PIHALF))
newthingangle[index] = newthingangle[index] - (newthingangle[index] * 2);
else if((newthingangle[index] >= Angle2D.PIHALF) && (newthingangle[index] <= Angle2D.PI))
newthingangle[index] = newthingangle[index] + (Angle2D.PI - newthingangle[index]) * 2;
else if((newthingangle[index] >= Angle2D.PI) && (newthingangle[index] <= Angle2D.PI + Angle2D.PIHALF))
newthingangle[index] = newthingangle[index] - (newthingangle[index] - Angle2D.PI) * 2;
else
newthingangle[index] = newthingangle[index] + (Angle2D.PI2 - newthingangle[index]) * 2;
}
}
// Flip things vertically
if(size.y < 0.0f)
{
for(index = 0; index < newthingangle.Length; index++)
{
// Check quadrant
if((newthingangle[index] >= 0f) && (newthingangle[index] < Angle2D.PIHALF))
newthingangle[index] = newthingangle[index] + (Angle2D.PI - newthingangle[index] * 2);
else if((newthingangle[index] >= Angle2D.PIHALF) && (newthingangle[index] <= Angle2D.PI))
newthingangle[index] = newthingangle[index] - (newthingangle[index] - Angle2D.PIHALF) * 2;
else if((newthingangle[index] >= Angle2D.PI) && (newthingangle[index] <= Angle2D.PI + Angle2D.PIHALF))
newthingangle[index] = newthingangle[index] + (Angle2D.PI - (newthingangle[index] - Angle2D.PI) * 2);
else
newthingangle[index] = newthingangle[index] - (newthingangle[index] - (Angle2D.PI + Angle2D.PIHALF)) * 2;
}
}
// We use optimized versions of the TransformedPoint depending on what needs to be done.
// This is mainly done because 0.0 rotation and 1.0 scale may still give slight inaccuracies.
bool norotate = Math.Abs(rotation) < 0.0001f;
bool noscale = Math.Abs(size.x - basesize.x) + Math.Abs(size.y - basesize.y) < 0.0001f;
if(norotate && noscale)
{
index = 0;
foreach(Vertex v in selectedvertices)
{
v.Move(TransformedPointNoRotateNoScale(vertexpos[index++]));
}
index = 0;
foreach(Thing t in selectedthings)
{
t.Move(TransformedPointNoRotateNoScale(thingpos[index++]));
}
}
else if(norotate)
{
index = 0;
foreach(Vertex v in selectedvertices)
{
v.Move(TransformedPointNoRotate(vertexpos[index++]));
}
index = 0;
foreach(Thing t in selectedthings)
{
t.Move(TransformedPointNoRotate(thingpos[index++]));
}
}
else if(noscale)
{
index = 0;
foreach(Vertex v in selectedvertices)
{
v.Move(TransformedPointNoScale(vertexpos[index++]));
}
index = 0;
foreach(Thing t in selectedthings)
{
newthingangle[index] = Angle2D.Normalized(newthingangle[index] + rotation);
t.Move(TransformedPointNoScale(thingpos[index++]));
}
}
else
{
index = 0;
foreach(Vertex v in selectedvertices)
{
v.Move(TransformedPoint(vertexpos[index++]));
}
index = 0;
foreach(Thing t in selectedthings)
{
newthingangle[index] = Angle2D.Normalized(newthingangle[index] + rotation);
t.Move(TransformedPoint(thingpos[index++]));
}
}
// This checks if the lines should be flipped
bool shouldbeflipped = (size.x < 0.0f) ^ (size.y < 0.0f);
if(shouldbeflipped != linesflipped) FlipLinedefs();
// Apply new thing rotations
index = 0;
foreach(Thing t in selectedthings)
{
if(!fixedrotationthingtypes.Contains(t.Type)) //mxd. Polyobject Anchors, I hate you!
t.Rotate(Angle2D.Normalized(newthingangle[index]));
index++;
}
UpdatePanel();
General.Map.Map.Update(true, false);
}
//mxd. This updates texture transforms for all sectors
private void UpdateTextureTransform()
{
foreach(KeyValuePair<Sector, SectorTextureInfo> group in selectedsectors)
{
group.Key.Fields.BeforeFieldsChange();
// Apply transforms
UpdateTextureTransform(group.Key.Fields, group.Value.Ceiling, transformceiloffsets, rotateceiloffsets, scaleceiloffsets);
UpdateTextureTransform(group.Key.Fields, group.Value.Floor, transformflooroffsets, rotateflooroffsets, scaleflooroffsets);
// Update cache
group.Key.UpdateNeeded = true;
group.Key.UpdateCache();
}
// Map was changed
General.Map.IsChanged = true;
}
//mxd. This updates texture transforms in given UniFields
private void UpdateTextureTransform(UniFields fields, SurfaceTextureInfo si, bool transformoffsets, bool rotateoffsets, bool scaleoffsets)
{
// Get offset-ready values
float texrotation = Angle2D.PI2 - rotation;
// Update texture offsets
if(transformoffsets)
{
Vector2D toffset = (selectionbasecenter - selectioncenter).GetRotated((texrotation + si.Rotation));
Vector2D soffset = si.Offset.GetRotated(texrotation + si.Rotation);
fields["xpanning" + si.Part] = new UniValue(UniversalType.Float, (float)Math.Round(soffset.x + toffset.x, General.Map.FormatInterface.VertexDecimals) % si.TextureSize.Width);
fields["ypanning" + si.Part] = new UniValue(UniversalType.Float, (float)Math.Round(-(soffset.y + toffset.y), General.Map.FormatInterface.VertexDecimals) % si.TextureSize.Height);
}
// Restore texture offsets
else
{
fields["xpanning" + si.Part] = new UniValue(UniversalType.Float, si.Offset.x);
fields["ypanning" + si.Part] = new UniValue(UniversalType.Float, si.Offset.y);
}
// Update rotation
if(rotateoffsets)
fields["rotation" + si.Part] = new UniValue(UniversalType.AngleDegreesFloat, General.ClampAngle((float)Math.Round(Angle2D.RadToDeg(si.Rotation + texrotation), General.Map.FormatInterface.VertexDecimals)));
// Restore rotation
else
fields["rotation" + si.Part] = new UniValue(UniversalType.AngleDegreesFloat, Angle2D.RadToDeg(si.Rotation));
// Update scale
if(scaleoffsets)
{
fields["xscale" + si.Part] = new UniValue(UniversalType.Float, (float) Math.Round(si.Scale.x * scale.x, General.Map.FormatInterface.VertexDecimals));
fields["yscale" + si.Part] = new UniValue(UniversalType.Float, (float) Math.Round(si.Scale.y * scale.y, General.Map.FormatInterface.VertexDecimals));
}
// Restore scale
else
{
fields["xscale" + si.Part] = new UniValue(UniversalType.Float, si.Scale.x);
fields["yscale" + si.Part] = new UniValue(UniversalType.Float, -si.Scale.y);
}
}
//mxd. This restores texture transforms for all sectors
private void RestoreTextureTransform()
{
foreach(KeyValuePair<Sector, SectorTextureInfo> group in selectedsectors)
{
group.Key.Fields.BeforeFieldsChange();
// Revert transforms
RestoreTextureTransform(group.Key.Fields, group.Value.Ceiling);
RestoreTextureTransform(group.Key.Fields, group.Value.Floor);
// Update cache
group.Key.UpdateNeeded = true;
group.Key.UpdateCache();
}
}
//mxd. This restores texture transforms in given UniFields
private static void RestoreTextureTransform(UniFields fields, SurfaceTextureInfo si)
{
fields["rotation" + si.Part] = new UniValue(UniversalType.AngleDegreesFloat, Angle2D.RadToDeg(si.Rotation));
fields["xscale" + si.Part] = new UniValue(UniversalType.Float, si.Scale.x);
fields["yscale" + si.Part] = new UniValue(UniversalType.Float, -si.Scale.y);
fields["xpanning" + si.Part] = new UniValue(UniversalType.Float, si.Offset.x);
fields["ypanning" + si.Part] = new UniValue(UniversalType.Float, si.Offset.y);
}
// This updates the selection rectangle components
private void UpdateRectangleComponents()
{
float gripsize = GRIP_SIZE / renderer.Scale;
PixelColor rectcolor = General.Colors.Highlight.WithAlpha(RECTANGLE_ALPHA);
// Original (untransformed) corners
originalcorners = new Vector2D[4];
originalcorners[0] = new Vector2D(baseoffset.x, baseoffset.y);
originalcorners[1] = new Vector2D(baseoffset.x + basesize.x, baseoffset.y);
originalcorners[2] = new Vector2D(baseoffset.x + basesize.x, baseoffset.y + basesize.y);
originalcorners[3] = new Vector2D(baseoffset.x, baseoffset.y + basesize.y);
// Corners
corners = new Vector2D[4];
for(int i = 0; i < 4; i++)
corners[i] = TransformedPoint(originalcorners[i]);
// Vertices
cornerverts = new FlatVertex[6];
for(int i = 0; i < 6; i++)
{
cornerverts[i] = new FlatVertex();
cornerverts[i].z = 1.0f;
cornerverts[i].c = rectcolor.ToInt();
}
cornerverts[0].x = corners[0].x;
cornerverts[0].y = corners[0].y;
cornerverts[1].x = corners[1].x;
cornerverts[1].y = corners[1].y;
cornerverts[2].x = corners[2].x;
cornerverts[2].y = corners[2].y;
cornerverts[3].x = corners[0].x;
cornerverts[3].y = corners[0].y;
cornerverts[4].x = corners[2].x;
cornerverts[4].y = corners[2].y;
cornerverts[5].x = corners[3].x;
cornerverts[5].y = corners[3].y;
// Middle points between corners
Vector2D middle01 = corners[0] + (corners[1] - corners[0]) * 0.5f;
Vector2D middle12 = corners[1] + (corners[2] - corners[1]) * 0.5f;
Vector2D middle23 = corners[2] + (corners[3] - corners[2]) * 0.5f;
Vector2D middle30 = corners[3] + (corners[0] - corners[3]) * 0.5f;
// Resize grips
resizegrips = new RectangleF[4];
resizegrips[0] = new RectangleF(middle01.x - gripsize * 0.5f,
middle01.y - gripsize * 0.5f,
gripsize, gripsize);
resizegrips[1] = new RectangleF(middle12.x - gripsize * 0.5f,
middle12.y - gripsize * 0.5f,
gripsize, gripsize);
resizegrips[2] = new RectangleF(middle23.x - gripsize * 0.5f,
middle23.y - gripsize * 0.5f,
gripsize, gripsize);
resizegrips[3] = new RectangleF(middle30.x - gripsize * 0.5f,
middle30.y - gripsize * 0.5f,
gripsize, gripsize);
// Rotate grips
rotategrips = new RectangleF[4];
rotategrips[0] = new RectangleF(corners[0].x - gripsize * 0.5f,
corners[0].y - gripsize * 0.5f,
gripsize, gripsize);
rotategrips[1] = new RectangleF(corners[1].x - gripsize * 0.5f,
corners[1].y - gripsize * 0.5f,
gripsize, gripsize);
rotategrips[2] = new RectangleF(corners[2].x - gripsize * 0.5f,
corners[2].y - gripsize * 0.5f,
gripsize, gripsize);
rotategrips[3] = new RectangleF(corners[3].x - gripsize * 0.5f,
corners[3].y - gripsize * 0.5f,
gripsize, gripsize);
//mxd. Update selection center
selectioncenter = new Vector2D(offset.x + size.x * 0.5f, offset.y + size.y * 0.5f);
}
// This flips all linedefs in the selection (used for mirroring)
private void FlipLinedefs()
{
// Flip linedefs
foreach(Linedef ld in selectedlines)
ld.FlipVertices();
// Done
linesflipped = !linesflipped;
}
#endregion
#region ================== Events
public override void OnHelp()
{
General.ShowHelp("e_editselection.html");
}
// Mode engages
public override void OnEngage()
{
base.OnEngage();
autodrag = (pasting && mouseinside && BuilderPlug.Me.AutoDragOnPaste);
snaptonearest = General.Interface.AutoMerge; //mxd
// Add toolbar buttons
General.Interface.AddButton(BuilderPlug.Me.MenusForm.FlipSelectionH);
General.Interface.AddButton(BuilderPlug.Me.MenusForm.FlipSelectionV);
//mxd. Get EditPanel-related settings
usepreciseposition = General.Settings.ReadPluginSetting("editselectionusespreciseposition", true);
// Add docker
panel = new EditSelectionPanel(this);
docker = new Docker("editselection", "Edit Selection", panel);
General.Interface.AddDocker(docker, true);
General.Interface.SelectDocker(docker);
// 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;
// Convert geometry selection
General.Map.Map.ClearAllMarks(false);
General.Map.Map.MarkSelectedVertices(true, true);
General.Map.Map.MarkSelectedThings(true, true);
General.Map.Map.MarkSelectedLinedefs(true, true);
General.Map.Map.MarkSelectedSectors(true, true);
ICollection<Vertex> verts = General.Map.Map.GetVerticesFromLinesMarks(true);
foreach(Vertex v in verts) v.Marked = true;
ICollection<Sector> sectors = General.Map.Map.GetSelectedSectors(true); //mxd
selectedsectors = new Dictionary<Sector, SectorTextureInfo>(); //mxd
foreach(Sector s in sectors)
{
foreach(Sidedef sd in s.Sidedefs)
{
sd.Line.Marked = true;
sd.Line.Start.Marked = true;
sd.Line.End.Marked = true;
}
if(General.Map.UDMF) selectedsectors.Add(s, new SectorTextureInfo(s));
}
selectedvertices = General.Map.Map.GetMarkedVertices(true);
selectedthings = General.Map.Map.GetMarkedThings(true);
unselectedvertices = General.Map.Map.GetMarkedVertices(false);
// Make sure everything is selected so that it turns up red
foreach(Vertex v in selectedvertices) v.Selected = true;
ICollection<Linedef> markedlines = General.Map.Map.LinedefsFromMarkedVertices(false, true, false);
foreach(Linedef l in markedlines) l.Selected = true;
selectedlines = General.Map.Map.LinedefsFromMarkedVertices(false, true, false);
unselectedlines = General.Map.Map.LinedefsFromMarkedVertices(true, false, false);
unstablelines = (pasting ? new Collection<Linedef>() : General.Map.Map.LinedefsFromMarkedVertices(false, false, true)); //mxd
// Array to keep original coordinates
vertexpos = new List<Vector2D>(selectedvertices.Count);
thingpos = new List<Vector2D>(selectedthings.Count);
thingangle = new List<float>(selectedthings.Count);
fixedrotationthingtypes = new List<int>(); //mxd
// A selection must be made!
if((selectedvertices.Count > 0) || (selectedthings.Count > 0))
{
// Initialize offset and size
offset.x = float.MaxValue;
offset.y = float.MaxValue;
Vector2D right;
right.x = float.MinValue;
right.y = float.MinValue;
foreach(Vertex v in selectedvertices)
{
// Find left-top and right-bottom
if(v.Position.x < offset.x) offset.x = v.Position.x;
if(v.Position.y < offset.y) offset.y = v.Position.y;
if(v.Position.x > right.x) right.x = v.Position.x;
if(v.Position.y > right.y) right.y = v.Position.y;
// Keep original coordinates
vertexpos.Add(v.Position);
}
foreach(Thing t in selectedthings)
{
// Find left-top and right-bottom
if((t.Position.x - t.Size) < offset.x) offset.x = t.Position.x - t.Size;
if((t.Position.y - t.Size) < offset.y) offset.y = t.Position.y - t.Size;
if((t.Position.x + t.Size) > right.x) right.x = t.Position.x + t.Size;
if((t.Position.y + t.Size) > right.y) right.y = t.Position.y + t.Size;
if(!fixedrotationthingtypes.Contains(t.Type)) //mxd
{
ThingTypeInfo tti = General.Map.Data.GetThingInfoEx(t.Type);
if(tti != null && tti.FixedRotation) fixedrotationthingtypes.Add(t.Type);
}
// Keep original coordinates
thingpos.Add(t.Position);
thingangle.Add(t.Angle);
}
// Calculate size
size = right - offset;
// If the width of a dimension is zero, add a little
if(Math.Abs(size.x) < 1.0f)
{
size.x += ZERO_SIZE_ADDITION;
offset.x -= ZERO_SIZE_ADDITION / 2;
}
if(Math.Abs(size.y) < 1.0f)
{
size.y += ZERO_SIZE_ADDITION;
offset.y -= ZERO_SIZE_ADDITION / 2;
}
basesize = size;
baseoffset = offset;
selectionbasecenter = new Vector2D(offset.x + size.x * 0.5f, offset.y + size.y * 0.5f); //mxd
// When pasting, we want to move the geometry so it is visible
if(pasting)
{
// Mouse in screen?
if(mouseinside)
{
offset = mousemappos - size / 2;
}
else
{
Vector2D viewmappos = new Vector2D(renderer.OffsetX, renderer.OffsetY);
offset = viewmappos - size / 2;
}
if(General.Interface.SnapToGrid) //mxd
offset = General.Map.Grid.SnappedToGrid(offset);
UpdateGeometry();
General.Map.Data.UpdateUsedTextures();
if(!autodrag) General.Map.Map.Update();
}
// Set presentation
if(selectedthings.Count > 0)
renderer.SetPresentation(Presentation.Things);
else
renderer.SetPresentation(Presentation.Standard);
// Update
panel.ShowOriginalValues(baseoffset, basesize);
panel.SetTextureTransformSettings(General.Map.UDMF); //mxd
UpdateRectangleComponents();
UpdatePanel();
Update();
// When pasting and mouse is in screen, drag selection immediately
if(autodrag)
{
OnSelectBegin();
autodrag = false; //mxd. Don't need this any more
}
}
else
{
General.Interface.MessageBeep(MessageBeepType.Default);
General.Interface.DisplayStatus(StatusType.Info, "A selection is required for this action.");
// Cancel now
General.Editing.CancelMode();
}
}
// Cancel mode
public override void OnCancel()
{
base.OnCancel();
// Paste operation?
if(pasting)
{
// Resume normal undo/redo recording
General.Map.UndoRedo.IgnorePropChanges = false;
General.Map.Map.BeginAddRemove(); //mxd
// Remove the geometry
foreach(Vertex v in selectedvertices) v.Dispose();
foreach(Thing t in selectedthings) t.Dispose();
General.Map.Map.EndAddRemove(); //mxd
// Withdraw the undo
if(General.Map.UndoRedo.NextUndo != null)
General.Map.UndoRedo.WithdrawUndo();
}
else
{
// Reset geometry in original position
int index = 0;
foreach(Vertex v in selectedvertices)
v.Move(vertexpos[index++]);
index = 0;
foreach(Thing t in selectedthings)
{
t.Rotate(thingangle[index]);
t.Move(thingpos[index++]);
}
//mxd. Reset texture offsets to original values
if(General.Map.UDMF) RestoreTextureTransform();
// Resume normal undo/redo recording
General.Map.UndoRedo.IgnorePropChanges = false;
}
General.Map.Map.Update(true, true);
// Return to previous stable mode
General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name);
}
// When accepted
public override void OnAccept()
{
base.OnAccept();
// Anything to do?
if((selectedthings.Count > 0) || (selectedvertices.Count > 0))
{
Vector2D tl = new Vector2D(General.Map.Config.RightBoundary, General.Map.Config.BottomBoundary);
Vector2D br = new Vector2D(General.Map.Config.LeftBoundary, General.Map.Config.RightBoundary);
foreach(Vertex v in selectedvertices)
{
if(v.Position.x < tl.x) tl.x = (int)v.Position.x;
if(v.Position.x > br.x) br.x = (int)v.Position.x;
if(v.Position.y > tl.y) tl.y = (int)v.Position.y;
if(v.Position.y < br.y) br.y = (int)v.Position.y;
}
foreach(Thing t in selectedthings)
{
if(t.Position.x < tl.x) tl.x = (int)t.Position.x;
if(t.Position.x > br.x) br.x = (int)t.Position.x;
if(t.Position.y > tl.y) tl.y = (int)t.Position.y;
if(t.Position.y < br.y) br.y = (int)t.Position.y;
}
// Check if the selection is outside the map boundaries
if(tl.x < General.Map.Config.LeftBoundary || br.x > General.Map.Config.RightBoundary ||
tl.y > General.Map.Config.TopBoundary || br.y < General.Map.Config.BottomBoundary)
{
General.Interface.DisplayStatus(StatusType.Warning, "Error: selection out of map boundaries.");
// If we're in the process of switching to another mode, reset to selection
// to its old position
if(modealreadyswitching)
{
// Reset geometry in original position
int index = 0;
foreach(Vertex v in selectedvertices)
v.Move(vertexpos[index++]);
index = 0;
foreach(Thing t in selectedthings)
{
t.Rotate(thingangle[index]);
t.Move(thingpos[index++]);
}
//mxd. Reset texture offsets to their original position
if(General.Map.UDMF) RestoreTextureTransform();
// Resume normal undo/redo recording
General.Map.UndoRedo.IgnorePropChanges = false;
General.Map.Map.Update(true, true);
}
return;
}
Cursor.Current = Cursors.AppStarting;
if(!pasting)
{
// Reset geometry in original position to create an undo
if(linesflipped) FlipLinedefs(); // Flip linedefs back if they were flipped
int index = 0;
foreach(Vertex v in selectedvertices)
v.Move(vertexpos[index++]);
index = 0;
foreach(Thing t in selectedthings)
{
t.Rotate(thingangle[index]);
t.Move(thingpos[index++]);
}
//mxd. Reset texture offsets to their original position
if(General.Map.UDMF) RestoreTextureTransform();
General.Map.Map.Update(true, true);
// Make undo
General.Map.UndoRedo.CreateUndo("Edit selection");
}
// Resume normal undo/redo recording
General.Map.UndoRedo.IgnorePropChanges = false;
//mxd. Update sector slopes?
if(General.Map.UDMF)
{
// We need a different kind of offset...
Vector2D relativeoffset = offset - baseoffset;
foreach(Sector s in selectedsectors.Keys)
{
// 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.FloorSlope = new Vector3D(new Vector2D(s.FloorSlope.x, s.FloorSlope.y).GetRotated(rotation), s.FloorSlope.z);
s.FloorSlopeOffset = -Vector3D.DotProduct(s.FloorSlope, new Vector3D(center + relativeoffset, 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.CeilSlope = new Vector3D(new Vector2D(s.CeilSlope.x, s.CeilSlope.y).GetRotated(rotation), s.CeilSlope.z);
s.CeilSlopeOffset = -Vector3D.DotProduct(s.CeilSlope, new Vector3D(center + relativeoffset, ceiling.GetZ(center)));
}
}
}
// Mark selected geometry
General.Map.Map.ClearAllMarks(false);
General.Map.Map.MarkAllSelectedGeometry(true, true, true, true, false);
// Move geometry to new position
UpdateGeometry();
//mxd. Update floor/ceiling texture settings
if(General.Map.UDMF) UpdateTextureTransform();
General.Map.Map.Update(true, true);
// When pasting, we want to join with the parent sector
// where the sidedefs are referencing a virtual sector
if(pasting)
{
Sector parent = null;
Sector vsector = null;
General.Settings.FindDefaultDrawSettings();
// Go for all sidedes in the new geometry
List<Sidedef> newsides = General.Map.Map.GetMarkedSidedefs(true);
foreach(Sidedef s in newsides)
{
// Connected to a virtual sector?
if(s.Marked && s.Sector.Fields.ContainsKey(MapSet.VirtualSectorField))
{
bool joined = false;
// Keep reference to virtual sector
vsector = s.Sector;
// Not virtual on both sides?
// Pascal 3-1-08: I can't remember why I have this check here, but it causes problems when
// pasting a single linedef that refers to the same sector on both sides (the line then
// loses both its sidedefs because it doesn't join any sector)
//if((s.Other != null) && !s.Other.Sector.Fields.ContainsKey(MapSet.VirtualSectorField))
{
// Find out in which sector this was pasted
Vector2D testpoint = s.Line.GetSidePoint(!s.IsFront);
Linedef nl = MapSet.NearestLinedef(General.Map.Map.GetMarkedLinedefs(false), testpoint);
if(nl != null)
{
Sidedef joinsidedef = (nl.SideOfLine(testpoint) <= 0 ? nl.Front : nl.Back);
// Join?
if(joinsidedef != null)
{
// Join!
s.SetSector(joinsidedef.Sector);
s.Marked = false;
joined = true;
// If we have no parent sector yet, then this is it!
if(parent == null) parent = joinsidedef.Sector;
}
}
}
// Not joined any sector?
if(!joined)
{
Linedef l = s.Line;
// Remove the sidedef
s.Dispose();
// Correct the linedef
if((l.Front == null) && (l.Back != null))
{
l.FlipVertices();
l.FlipSidedefs();
}
// Correct the sided flags
l.ApplySidedFlags();
}
}
}
// Do we have a virtual and parent sector?
if((vsector != null) && (parent != null))
{
// Adjust the floor and ceiling heights of all new sectors
if(pasteoptions.AdjustHeights)
{
ICollection<Sector> newsectors = General.Map.Map.GetMarkedSectors(true);
foreach(Sector s in newsectors)
{
s.CeilHeight += parent.CeilHeight - vsector.CeilHeight;
s.FloorHeight += parent.FloorHeight - vsector.FloorHeight;
}
}
}
// Remove any virtual sectors
General.Map.Map.RemoveVirtualSectors();
}
// 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(General.Map.UDMF && usepreciseposition);
//mxd. Update cached values
General.Map.Map.Update();
//mxd. Get new lines from linedef marks...
HashSet<Linedef> newlines = new HashSet<Linedef>(General.Map.Map.GetMarkedLinedefs(true));
//mxd. Marked lines were created during linedef splitting
HashSet<Linedef> changedlines = new HashSet<Linedef>(selectedlines);
changedlines.UnionWith(newlines);
//mxd. Update outer sides of the selection
if(changedlines.Count > 0)
{
// Get affected sectors
HashSet<Sector> affectedsectors = new HashSet<Sector>(General.Map.Map.GetSelectedSectors(true));
affectedsectors.UnionWith(General.Map.Map.GetUnselectedSectorsFromLinedefs(changedlines));
// Reattach/add/remove outer sidedefs
Tools.AdjustOuterSidedefs(affectedsectors, new HashSet<Linedef>(changedlines));
// Split outer sectors
Tools.SplitOuterSectors(changedlines);
}
// Update cached values
General.Map.Data.UpdateUsedTextures();
General.Map.Map.Update();
General.Map.ThingsFilter.Update();
// Make normal selection?
General.Map.Map.ClearAllSelected();
if(!clearselection) //mxd
{
foreach(Vertex v in selectedvertices) if(!v.IsDisposed) v.Selected = true;
foreach(Linedef l in selectedlines) { if(!l.IsDisposed) { l.Start.Selected = true; l.End.Selected = true; } }
foreach(Thing t in selectedthings) if(!t.IsDisposed) t.Selected = true;
}
General.Map.Map.SelectionType = SelectionType.Vertices | SelectionType.Things;
// Done
selectedvertices = new List<Vertex>();
selectedthings = new List<Thing>();
selectedlines = new List<Linedef>();
Cursor.Current = Cursors.Default;
General.Map.IsChanged = true;
}
if(!modealreadyswitching)
{
// Return to previous stable mode
General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name);
}
}
// Mode disengages
public override void OnDisengage()
{
base.OnDisengage();
// Remove toolbar buttons
General.Interface.RemoveButton(BuilderPlug.Me.MenusForm.FlipSelectionH);
General.Interface.RemoveButton(BuilderPlug.Me.MenusForm.FlipSelectionV);
//mxd. Save EditPanel-related settings
General.Settings.WritePluginSetting("editselectionusespreciseposition", usepreciseposition);
// Remove docker
General.Interface.RemoveDocker(docker);
panel.Dispose();
panel = null;
// When not cancelled manually, we assume it is accepted
if(!cancelled)
{
modealreadyswitching = true;
//this.OnAccept(); // BAD! Any other plugins won't know this mode was accepted
General.Editing.AcceptMode();
}
// Update
General.Map.ThingsFilter.Update();
General.Interface.RedrawDisplay();
// Hide highlight info
General.Interface.HideInfo();
General.Interface.SetCursor(Cursors.Default);
}
// This redraws the display
public override void OnRedrawDisplay()
{
UpdateRectangleComponents();
renderer.RedrawSurface();
// Render lines
if(renderer.StartPlotter(true))
{
renderer.PlotLinedefSet(General.Map.Map.Linedefs);
renderer.PlotVerticesSet(General.Map.Map.Vertices);
if(highlighted is Vertex) renderer.PlotVertex((Vertex)highlighted, ColorCollection.HIGHLIGHT);
renderer.Finish();
}
// Render things
if(renderer.StartThings(true))
{
renderer.RenderThingSet(General.Map.ThingsFilter.HiddenThings, General.Settings.HiddenThingsAlpha);
renderer.RenderThingSet(General.Map.ThingsFilter.VisibleThings, General.Settings.ActiveThingsAlpha);
if(highlighted is Thing) renderer.RenderThing((Thing)highlighted, General.Colors.Highlight, General.Settings.ActiveThingsAlpha);
renderer.Finish();
}
// Render selection
if(renderer.StartOverlay(true))
{
// Rectangle
PixelColor rectcolor = General.Colors.Highlight.WithAlpha(RECTANGLE_ALPHA);
renderer.RenderGeometry(cornerverts, null, true);
renderer.RenderLine(corners[0], corners[1], 4, rectcolor, true);
renderer.RenderLine(corners[1], corners[2], 4, rectcolor, true);
renderer.RenderLine(corners[2], corners[3], 4, rectcolor, true);
renderer.RenderLine(corners[3], corners[0], 4, rectcolor, true);
// Extension line
if(extensionline.GetLengthSq() > 0.0f)
renderer.RenderLine(extensionline.v1, extensionline.v2, 1, General.Colors.Indication.WithAlpha(EXTENSION_LINE_ALPHA), true);
// Grips
for(int i = 0; i < 4; i++)
{
renderer.RenderRectangleFilled(resizegrips[i], General.Colors.Background, true);
renderer.RenderRectangle(resizegrips[i], 2, General.Colors.Highlight, true);
renderer.RenderRectangleFilled(rotategrips[i], General.Colors.Background, true);
renderer.RenderRectangle(rotategrips[i], 2, General.Colors.Indication, true);
}
renderer.Finish();
}
renderer.Present();
}
// Mouse moves
public override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if(panning) return; //mxd. Skip all this jazz while panning
Update();
}
// Mouse leaves the display
public override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
// Reset cursor
General.Interface.SetCursor(Cursors.Default);
}
// When edit button is pressed
protected override void OnEditBegin()
{
base.OnEditBegin();
OnSelectBegin();
}
// When edit button is released
protected override void OnEditEnd()
{
base.OnEditEnd();
OnSelectEnd();
}
// When select button is pressed
protected override void OnSelectBegin()
{
base.OnSelectBegin();
if(mode != ModifyMode.None) return;
// Used in many cases:
Vector2D center = offset + size * 0.5f;
Vector2D delta;
// Check what grip the mouse is over
Grip mousegrip = (autodrag ? Grip.Main : CheckMouseGrip()); //mxd. We only want to move when starting auto-dragging
switch(mousegrip)
{
// Drag main rectangle
case Grip.Main:
// Find the original position of the highlighted element
if(highlighted is Vertex)
{
int index = 0;
foreach(Vertex v in selectedvertices)
{
if(v == highlighted)
{
highlightedpos = vertexpos[index];
break;
}
index++;
}
}
else if(highlighted is Thing)
{
int index = 0;
foreach(Thing t in selectedthings)
{
if(t == highlighted)
{
highlightedpos = thingpos[index];
break;
}
index++;
}
}
dragoffset = mousemappos - offset;
mode = ModifyMode.Dragging;
EnableAutoPanning();
autopanning = true;
break;
// Resize
case Grip.SizeN:
// The resize vector is a unit vector in the direction of the resize.
// We multiply this with the sign of the current size, because the
// corners may be reversed when the selection is flipped.
resizevector = corners[1] - corners[2];
resizevector = resizevector.GetNormal() * Math.Sign(size.y);
// The edgevector is a vector with length and direction of the edge perpendicular to the resizevector
edgevector = corners[1] - corners[0];
// Make the resize axis. This is a line with the length and direction
// of basesize used to calculate the resize percentage.
resizeaxis = new Line2D(corners[2], corners[2] + resizevector * basesize.y);
// Original axis filter
resizefilter = new Vector2D(0.0f, 1.0f);
// This is the corner that must stay in the same position
stickcorner = 2;
Highlight(null);
mode = ModifyMode.Resizing;
EnableAutoPanning();
autopanning = true;
break;
// Resize
case Grip.SizeE:
// See description above
resizevector = corners[1] - corners[0];
resizevector = resizevector.GetNormal() * Math.Sign(size.x);
edgevector = corners[1] - corners[2];
resizeaxis = new Line2D(corners[0], corners[0] + resizevector * basesize.x);
resizefilter = new Vector2D(1.0f, 0.0f);
stickcorner = 0;
Highlight(null);
mode = ModifyMode.Resizing;
EnableAutoPanning();
autopanning = true;
break;
// Resize
case Grip.SizeS:
// See description above
resizevector = corners[2] - corners[1];
resizevector = resizevector.GetNormal() * Math.Sign(size.y);
edgevector = corners[2] - corners[3];
resizeaxis = new Line2D(corners[1], corners[1] + resizevector * basesize.y);
resizefilter = new Vector2D(0.0f, 1.0f);
stickcorner = 0;
Highlight(null);
mode = ModifyMode.Resizing;
EnableAutoPanning();
autopanning = true;
break;
// Resize
case Grip.SizeW:
// See description above
resizevector = corners[0] - corners[1];
resizevector = resizevector.GetNormal() * Math.Sign(size.x);
edgevector = corners[0] - corners[3];
resizeaxis = new Line2D(corners[1], corners[1] + resizevector * basesize.x);
resizefilter = new Vector2D(1.0f, 0.0f);
stickcorner = 1;
Highlight(null);
mode = ModifyMode.Resizing;
EnableAutoPanning();
autopanning = true;
break;
// Rotate
case Grip.RotateLB:
delta = corners[3] - center;
rotategripangle = delta.GetAngle() - rotation;
Highlight(null);
mode = ModifyMode.Rotating;
EnableAutoPanning();
autopanning = true;
break;
// Rotate
case Grip.RotateLT:
delta = corners[0] - center;
rotategripangle = delta.GetAngle() - rotation;
Highlight(null);
mode = ModifyMode.Rotating;
EnableAutoPanning();
autopanning = true;
break;
// Rotate
case Grip.RotateRB:
delta = corners[2] - center;
rotategripangle = delta.GetAngle() - rotation;
Highlight(null);
mode = ModifyMode.Rotating;
EnableAutoPanning();
autopanning = true;
break;
// Rotate
case Grip.RotateRT:
delta = corners[1] - center;
rotategripangle = delta.GetAngle() - rotation;
Highlight(null);
mode = ModifyMode.Rotating;
EnableAutoPanning();
autopanning = true;
break;
// Outside the selection?
default:
// Accept and be done with it
General.Editing.AcceptMode();
break;
}
}
// When selected button is released
protected override void OnSelectEnd()
{
base.OnSelectEnd();
// Remove extension line
extensionline = new Line2D();
if(autopanning)
{
DisableAutoPanning();
autopanning = false;
}
// No modifying mode
mode = ModifyMode.None;
//mxd. Update floor/ceiling texture settings
if(General.Map.UDMF) UpdateTextureTransform();
// Redraw
General.Map.Map.Update();
General.Interface.RedrawDisplay();
}
// 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
// This clears the selection
[BeginAction("clearselection", BaseAction = true)]
public void ClearSelection()
{
//mxd. Accept changes
clearselection = true;
General.Editing.AcceptMode();
//mxd. Clear selection info
General.Interface.DisplayStatus(StatusType.Selection, string.Empty);
}
// Flip vertically
[BeginAction("flipselectionv")]
public void FlipVertically()
{
// Flip the selection
offset.y += size.y;
size.y = -size.y;
// Update
UpdateGeometry();
UpdateRectangleComponents();
General.Map.Map.Update();
General.Interface.RedrawDisplay();
}
// Flip horizontally
[BeginAction("flipselectionh")]
public void FlipHorizontally()
{
// Flip the selection
offset.x += size.x;
size.x = -size.x;
// Update
UpdateGeometry();
UpdateRectangleComponents();
General.Map.Map.Update();
General.Interface.RedrawDisplay();
}
#endregion
}
}