Rewritten vertex/linedef/sector dragging logic. Now it processes line-line intersections. And handles more corner cases. And has less bugs. Probably.

Fixed, Edit Selection mode: texture scale was flipped when "Floor/Ceiling Transform" -> "Scale" was enabled.
Changed: a warning is no longer displayed when trying to compile an empty SCRIPTS lump.
Updated ZDoom_DECORATE.cfg.
This commit is contained in:
MaxED 2016-05-29 00:38:55 +00:00 committed by spherallic
parent d45e71ce21
commit e69163a646
18 changed files with 1257 additions and 500 deletions

View file

@ -282,7 +282,7 @@ keywords
A_DropInventory = "A_DropInventory(str type)";
A_DropItem = "A_DropItem(str item[, int dropamount = -1[, int chance = 256]])\nThe calling actor drops the specified item.\nThis works in a similar way to the DropItem actor property.";
A_SelectWeapon = "bool A_SelectWeapon(str type)";
A_RadiusGive = "int A_RadiusGive(str item, float distance, int flags[, int amount = 0[, str filter = \"None\"[, str species = \"None\"[, float mindist = 0]]]])\nflags: RGF flags.";
A_RadiusGive = "int A_RadiusGive(str item, float distance, int flags[, int amount = 0[, str filter = \"None\"[, str species = \"None\"[, int mindist = 0[, int limit = 0]]]]])\nflags: RGF flags.";
//Weapon functions
A_WeaponReady = "A_WeaponReady[(int flags = 0)]\nflags: WRF flags.";
A_Lower = "A_Lower";

View file

@ -917,6 +917,7 @@
<Compile Include="GZBuilder\Rendering\SizelessVisualThingCage.cs" />
<Compile Include="GZBuilder\Rendering\ThingBoundingBox.cs" />
<Compile Include="GZBuilder\Data\ThingCopyData.cs" />
<Compile Include="Map\SectorBuilder.cs" />
<Compile Include="GZBuilder\Rendering\VisualVertexHandle.cs" />
<Compile Include="GZBuilder\Geometry\Line3D.cs" />
<Compile Include="GZBuilder\GZDoom\DecorateParserSE.cs" />

View file

@ -156,7 +156,11 @@ namespace CodeImp.DoomBuilder
else
message = message.TrimEnd() + " " + duration + " ms.";
#if DEBUG
WriteLine(DebugMessageType.SPECIAL, message);
#else
General.ShowErrorMessage(message, MessageBoxButtons.OK, false);
#endif
starttime = -1;
}

View file

@ -83,6 +83,53 @@ namespace CodeImp.DoomBuilder.Geometry
return d;
}
//mxd. Slade 3 MathStuff::angle2DRad ripoff...
//Returns the angle between the 2d points [p1], [p2] and [p3]
public static float GetAngle(Vector2D p1, Vector2D p2, Vector2D p3)
{
// From: http://stackoverflow.com/questions/3486172/angle-between-3-points
// modified not to bother converting to degrees
Vector2D ab = new Vector2D(p2.x - p1.x, p2.y - p1.y);
Vector2D cb = new Vector2D(p2.x - p3.x, p2.y - p3.y);
// dot product
float dot = (ab.x * cb.x + ab.y * cb.y);
// length square of both vectors
float abSqr = ab.x * ab.x + ab.y * ab.y;
float cbSqr = cb.x * cb.x + cb.y * cb.y;
// square of cosine of the needed angle
float cosSqr = dot * dot / abSqr / cbSqr;
// this is a known trigonometric equality:
// cos(alpha * 2) = [ cos(alpha) ]^2 * 2 - 1
float cos2 = 2.0f * cosSqr - 1.0f;
// Here's the only invocation of the heavy function.
// It's a good idea to check explicitly if cos2 is within [-1 .. 1] range
float alpha2 =
(cos2 <= -1) ? PI :
(cos2 >= 1) ? 0.0f :
(float)Math.Acos(cos2);
float rs = alpha2 * 0.5f;
// Now revolve the ambiguities.
// 1. If dot product of two vectors is negative - the angle is definitely
// above 90 degrees. Still we have no information regarding the sign of the angle.
// NOTE: This ambiguity is the consequence of our method: calculating the cosine
// of the double angle. This allows us to get rid of calling sqrt.
if(dot < 0) rs = PI - rs;
// 2. Determine the sign. For this we'll use the Determinant of two vectors.
float det = (ab.x * cb.y - ab.y * cb.x);
if(det < 0) rs = (2.0f * PI) - rs;
return rs;
}
#endregion
}
}

View file

@ -109,44 +109,35 @@ namespace CodeImp.DoomBuilder.Geometry
}
// Point inside the polygon?
// See: http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/
// See: http://paulbourke.net/geometry/polygonmesh/index.html#insidepoly
public bool Intersect(Vector2D p)
{
Vector2D v1 = base.Last.Value.Position;
LinkedListNode<EarClipVertex> n = base.First;
uint c = 0;
Vector2D v2;
// Go for all vertices
while(n != null)
{
// Get next vertex
Vector2D v2 = n.Value.Position;
// Determine min/max values
float miny = Math.Min(v1.y, v2.y);
float maxy = Math.Max(v1.y, v2.y);
float maxx = Math.Max(v1.x, v2.x);
v2 = n.Value.Position;
// Check for intersection
if((p.y > miny) && (p.y <= maxy))
{
if(p.x <= maxx)
{
if(v1.y != v2.y)
{
float xint = (p.y - v1.y) * (v2.x - v1.x) / (v2.y - v1.y) + v1.x;
if((v1.x == v2.x) || (p.x <= xint)) c++;
}
}
}
if(v1.y != v2.y //mxd. If line is not horizontal...
&& p.y > (v1.y < v2.y ? v1.y : v2.y) //mxd. ...And test point y intersects with the line y bounds...
&& p.y <= (v1.y > v2.y ? v1.y : v2.y) //mxd
&& (p.x < (v1.x < v2.x ? v1.x : v2.x) || (p.x <= (v1.x > v2.x ? v1.x : v2.x) //mxd. ...And test point x is to the left of the line, or is inside line x bounds and intersects it
&& (v1.x == v2.x || p.x <= ((p.y - v1.y) * (v2.x - v1.x) / (v2.y - v1.y) + v1.x)))))
c++; //mxd. ...Count the line as crossed
// Move to next
v1 = v2;
n = n.Next;
}
// Inside this polygon?
if((c & 0x00000001UL) != 0)
// Inside this polygon when we crossed odd number of polygon lines
if(c % 2 != 0)
{
// Check if not inside the children
foreach(EarClipPolygon child in children)
@ -158,12 +149,10 @@ namespace CodeImp.DoomBuilder.Geometry
// Inside polygon!
return true;
}
else
{
// Not inside the polygon
return false;
}
}
// This inserts a polygon if it is a child of this one
public bool InsertChild(EarClipPolygon p)

View file

@ -17,6 +17,7 @@
#region ================== Namespaces
using System;
using CodeImp.DoomBuilder.Map;
#endregion
@ -66,6 +67,13 @@ namespace CodeImp.DoomBuilder.Geometry
this.v2 = new Vector2D(x2, y2);
}
//mxd. Constructor
public Line2D(Linedef line)
{
this.v1 = line.Start.Position;
this.v2 = line.End.Position;
}
#endregion
#region ================== Statics

View file

@ -35,6 +35,7 @@ namespace CodeImp.DoomBuilder.Geometry
private Linedef line;
private bool front;
private bool ignore; //mxd
#endregion
@ -42,6 +43,7 @@ namespace CodeImp.DoomBuilder.Geometry
public Linedef Line { get { return line; } set { line = value; } }
public bool Front { get { return front; } set { front = value; } }
public bool Ignore { get { return ignore; } set { ignore = value; } } //mxd
#endregion
@ -109,7 +111,7 @@ namespace CodeImp.DoomBuilder.Geometry
{
Sidedef side = (front ? line.Front : line.Back);
Sector sector = (side != null ? side.Sector : null);
return line + " (" + (front ? "front" : "back") + ")" + (sector != null ? ", Sector " + sector.Index : "");
return line + " (" + (front ? "front" : "back") + ")" + (sector != null ? ", Sector " + sector.Index : ", no sector");
}
#endif

View file

@ -1262,8 +1262,8 @@ namespace CodeImp.DoomBuilder.Geometry
// self intersections for which splits were made above.
map.Update(true, false);
map.BeginAddRemove();
MapSet.SplitLinesByVertices(newlines, intersectverts, MapSet.STITCH_DISTANCE, null);
MapSet.SplitLinesByVertices(newlines, mergeverts, MapSet.STITCH_DISTANCE, null);
MapSet.SplitLinesByVertices(newlines, intersectverts, MapSet.STITCH_DISTANCE, null, false);
MapSet.SplitLinesByVertices(newlines, mergeverts, MapSet.STITCH_DISTANCE, null, false);
map.EndAddRemove();
/***************************************************\
@ -1330,7 +1330,7 @@ namespace CodeImp.DoomBuilder.Geometry
// Before this point, the new geometry is not linked with the existing geometry.
// Now perform standard geometry stitching to merge the new geometry with the rest
// of the map. The marked vertices indicate the new geometry.
map.StitchGeometry();
map.StitchGeometry(false);
map.Update(true, false);
// Find our new lines again, because they have been merged with the other geometry
@ -2230,7 +2230,6 @@ namespace CodeImp.DoomBuilder.Geometry
}
}
//mxd
private static bool SectorWasInvalid(Sector s)
{
if(s.Sidedefs.Count < 3 || s.FlatVertices.Length < 3)
@ -2359,263 +2358,18 @@ namespace CodeImp.DoomBuilder.Geometry
return 0;
}
//mxd. Try to create/remove/reassign outer sidedefs. Selected linedefs and verts are marked
public static HashSet<Sidedef> AdjustOuterSidedefs(HashSet<Sector> selectedsectors, HashSet<Linedef> selectedlines)
{
HashSet<Sidedef> adjustedsides = new HashSet<Sidedef>();
HashSet<Sidedef> outersides = new HashSet<Sidedef>();
HashSet<Sidedef> innersides = new HashSet<Sidedef>();
HashSet<Linedef> singlesidedlines = new HashSet<Linedef>();
HashSet<Linedef> lineswithoutsides = new HashSet<Linedef>();
// Collect lines without sidedefs and inner and outer sides
foreach(Linedef line in selectedlines)
{
if(line.Front == null && line.Back == null)
{
lineswithoutsides.Add(line);
}
else
{
if(line.Back != null && line.Back.Sector != null)
{
if(!selectedsectors.Contains(line.Back.Sector)) outersides.Add(line.Back);
else innersides.Add(line.Back);
}
if(line.Front != null && line.Front.Sector != null)
{
if(!selectedsectors.Contains(line.Front.Sector)) outersides.Add(line.Front);
else innersides.Add(line.Front);
}
}
}
// Collect inner and outer sides and single-sided lines
foreach(Sector sector in selectedsectors)
{
foreach(Sidedef side in sector.Sidedefs)
{
if(side.Other == null) singlesidedlines.Add(side.Line);
else if(!selectedsectors.Contains(side.Other.Sector)) outersides.Add(side.Other);
innersides.Add(side);
}
}
// Check lines without sidedefs. Add new sidedefs if necessary
foreach(Linedef line in lineswithoutsides)
{
bool sideschanged = false;
// Add front side?
Vector2D testpoint = line.GetSidePoint(true);
Linedef nl = General.Map.Map.NearestLinedef(testpoint, selectedlines);
if(nl != null)
{
Sidedef ns = (nl.SideOfLine(testpoint) <= 0 ? nl.Front : nl.Back);
if(ns != null)
{
// Create new sidedef
Sidedef newside = General.Map.Map.CreateSidedef(line, true, ns.Sector);
// Copy props from the other side
ns.CopyPropertiesTo(newside);
// Store
adjustedsides.Add(newside);
sideschanged = true;
}
}
// Add back side?
testpoint = line.GetSidePoint(false);
nl = General.Map.Map.NearestLinedef(testpoint, selectedlines);
if(nl != null)
{
Sidedef ns = (nl.SideOfLine(testpoint) <= 0 ? nl.Front : nl.Back);
if(ns != null)
{
// Create new sidedef
Sidedef newside = General.Map.Map.CreateSidedef(line, false, ns.Sector);
// Copy props from the other side
ns.CopyPropertiesTo(newside);
// Store
adjustedsides.Add(newside);
sideschanged = true;
}
}
// Correct the sided flags
if(sideschanged)
{
// Correct the linedef
if((line.Front == null) && (line.Back != null))
{
line.FlipVertices();
line.FlipSidedefs();
}
// Correct the sided flags
line.ApplySidedFlags();
}
}
// These steps must be done in 2 phases, otherwise we'll end up getting sidedefs modified in a previous adjustment loop step
// Find sidedefs to join singlesided lines
Dictionary<Linedef, Sector> linesectorref = new Dictionary<Linedef, Sector>();
foreach(Linedef line in singlesidedlines)
{
// Line is now inside a sector? (check from the missing side!)
Sector nearest = FindPotentialSector(line, (line.Front == null), outersides);
// We can reattach our line!
if(nearest != null) linesectorref[line] = nearest;
}
// Find sidedefs to join outer sides
Dictionary<Sidedef, Sector> sidesectorref = new Dictionary<Sidedef, Sector>();
foreach(Sidedef side in outersides)
{
// Side is inside a sector?
Sector nearest = FindPotentialSector(side.Line, side.IsFront, outersides);
// We can reattach our side!
if(nearest != null)
{
if(side.Sector != nearest) sidesectorref[side] = nearest; // This side will be reattached in phase 2
else sidesectorref.Remove(side); // This side is already attached where it needs to be
// Store
adjustedsides.Add(side);
}
else
{
sidesectorref[side] = null; // This side will be removed in phase 2
}
}
// Check single-sided lines. Add new sidedefs if necessary
// Key is dragged single-sided line, value is a sector dragged line ended up in.
foreach(KeyValuePair<Linedef, Sector> group in linesectorref)
{
Linedef line = group.Key;
// Create new sidedef
Sidedef newside = General.Map.Map.CreateSidedef(line, line.Front == null, group.Value);
// Copy props from the other side
Sidedef propssource = (line.Front ?? line.Back);
propssource.CopyPropertiesTo(newside);
// Store
adjustedsides.Add(newside);
// Correct the linedef
if((line.Front == null) && (line.Back != null))
{
line.FlipVertices();
line.FlipSidedefs();
}
// Correct the sided flags
line.ApplySidedFlags();
}
// Check outer sidedefs. Remove/change sector if necessary
// Key is outer sidedef of dragged geometry, value is a sector dragged side ended up in.
foreach(KeyValuePair<Sidedef, Sector> group in sidesectorref)
{
if(group.Value == null)
{
// Other side textures may require updating...
if(group.Key.Other != null) adjustedsides.Add(group.Key.Other);
// Side points nowhere. Remove it
Linedef l = group.Key.Line;
group.Key.Dispose();
// Correct the linedef
if((l.Front == null) && (l.Back != null))
{
l.FlipVertices();
l.FlipSidedefs();
}
// Correct the sided flags
l.ApplySidedFlags();
}
else
{
// Reattach side
group.Key.SetSector(group.Value);
// Store
adjustedsides.Add(group.Key);
}
}
// Inner side textures may need updating
foreach(Sidedef s in innersides)
{
if(!s.IsDisposed) s.RemoveUnneededTextures(s.Other != null, false, true);
}
// Update map geometry
General.Map.Map.Update();
// Done
return adjustedsides;
}
//mxd
private static Sector FindPotentialSector(Linedef line, bool front, HashSet<Sidedef> sidestoexclude)
public static Sector FindPotentialSector(Linedef line, bool front)
{
List<LinedefSide> sectorsides = FindPotentialSectorAt(line, front);
if(sectorsides == null) return null;
Sector result = null;
// Special case: if sectorsides match sidestoexclude and all sidestoexclude reference the same sector, return that sector
if(sidestoexclude.Count > 2 && sectorsides.Count == sidestoexclude.Count)
{
bool allsidesmatch = true;
// Check if all sidestoexclude reference the same sector...
foreach(Sidedef s in sidestoexclude)
{
if(result == null) result = s.Sector;
else if(result != s.Sector)
{
allsidesmatch = false;
break;
}
}
// Check if sidestoexclude match sectorsides...
if(allsidesmatch)
{
HashSet<Sidedef> sectorsidesset = new HashSet<Sidedef>();
foreach(LinedefSide ls in sectorsides)
{
sectorsidesset.Add(ls.Front ? ls.Line.Front : ls.Line.Back);
}
allsidesmatch = sectorsidesset.SetEquals(sidestoexclude);
}
// Sides are already where they need to be
if(allsidesmatch) return result;
}
// Filter outersides from the list, proceed only if all sectorsides reference the same sector
// Proceed only if all sectorsides reference the same sector
foreach(LinedefSide sectorside in sectorsides)
{
Sidedef target = (sectorside.Front ? sectorside.Line.Front : sectorside.Line.Back);
if(target != null && !sidestoexclude.Contains(target))
if(target != null)
{
if(result == null) result = target.Sector;
else if(result != target.Sector) return null; // Fial...

View file

@ -1255,7 +1255,7 @@ namespace CodeImp.DoomBuilder.Map
{
// Check which lines were 2 sided
bool otherwas2s = ((other.Front != null) && (other.Back != null));
//bool thiswas2s = ((this.Front != null) && (this.Back != null));
bool thiswas2s = ((this.Front != null) && (this.Back != null));
// Get sector references
Sector otherfs = (other.front != null ? other.front.Sector : null);
@ -1359,8 +1359,7 @@ namespace CodeImp.DoomBuilder.Map
// Other line with its back to this?
if(other.start == this.end)
{
//mxd. Marked sector means other side belongs to a sector being moved...
if(otherbs == null || !otherbs.Marked)
if(otherbs == null)
{
// Copy textures
if(other.back != null) other.back.AddTexturesTo(this.front);
@ -1371,8 +1370,7 @@ namespace CodeImp.DoomBuilder.Map
}
else
{
//mxd. Marked sector means other side belongs to a sector being moved...
if(otherfs == null || !otherfs.Marked)
if(otherfs == null)
{
// Copy textures
if(other.front != null) other.front.AddTexturesTo(this.front);
@ -1387,9 +1385,8 @@ namespace CodeImp.DoomBuilder.Map
// This line with its back to the other?
if(this.start == other.end)
{
//mxd. Marked sector means other side belongs to a sector being moved...
//mxd. Attach our front to other back?
if(otherbs == null || !otherbs.Marked)
if(otherbs == null)
{
// Copy textures
if(other.back != null) other.back.AddTexturesTo(this.front);
@ -1399,7 +1396,7 @@ namespace CodeImp.DoomBuilder.Map
if(front != null && !JoinChangeSidedefs(other, false, front)) return false;
}
//mxd. Attach our back to other front?
else if(otherfs == null || !otherfs.Marked)
else if(otherfs == null)
{
// Copy textures
if(other.front != null) other.front.AddTexturesTo(this.back);
@ -1412,9 +1409,8 @@ namespace CodeImp.DoomBuilder.Map
// Both lines face the same way
else
{
//mxd. Marked sector means other side belongs to a sector being moved...
//mxd. Attach our back to other back?
if(otherbs == null || !otherbs.Marked)
if(otherbs == null)
{
// Copy textures
if(other.back != null) other.back.AddTexturesTo(this.back);
@ -1424,7 +1420,7 @@ namespace CodeImp.DoomBuilder.Map
if(back != null && !JoinChangeSidedefs(other, false, back)) return false;
}
//mxd. Attach our front to other front?
else if(otherfs == null || !otherfs.Marked)
else if(otherfs == null)
{
// Copy textures
if(other.front != null) other.front.AddTexturesTo(this.front);
@ -1443,8 +1439,8 @@ namespace CodeImp.DoomBuilder.Map
other.ApplySidedFlags();
// Remove unneeded textures
//if(other.front != null) other.front.RemoveUnneededTextures(!(otherwas2s && thiswas2s));
//if(other.back != null) other.back.RemoveUnneededTextures(!(otherwas2s && thiswas2s));
if(other.front != null) other.front.RemoveUnneededTextures(!(otherwas2s && thiswas2s));
if(other.back != null) other.back.RemoveUnneededTextures(!(otherwas2s && thiswas2s));
}
// If either of the two lines was selected, keep the other selected

View file

@ -1989,9 +1989,9 @@ namespace CodeImp.DoomBuilder.Map
}
/// <summary>This filters lines by a rectangular area.</summary>
public static ICollection<Linedef> FilterByArea(ICollection<Linedef> lines, ref RectangleF area)
public static List<Linedef> FilterByArea(ICollection<Linedef> lines, ref RectangleF area)
{
ICollection<Linedef> newlines = new List<Linedef>(lines.Count);
List<Linedef> newlines = new List<Linedef>(lines.Count);
// Go for all lines
foreach(Linedef l in lines)
@ -2046,17 +2046,18 @@ namespace CodeImp.DoomBuilder.Map
/// <summary>
/// Stitches marked geometry with non-marked geometry. Returns false when the operation failed.
/// </summary>
public bool StitchGeometry()
public bool StitchGeometry() { return StitchGeometry(false); } //mxd. Compatibility
public bool StitchGeometry(bool correctsectorrefs)
{
// Find vertices
ICollection<Vertex> movingverts = General.Map.Map.GetMarkedVertices(true);
ICollection<Vertex> fixedverts = General.Map.Map.GetMarkedVertices(false);
// Find lines that moved during the drag
ICollection<Linedef> movinglines = LinedefsFromMarkedVertices(false, true, true);
List<Linedef> movinglines = LinedefsFromMarkedVertices(false, true, true);
// Find all non-moving lines
ICollection<Linedef> fixedlines = LinedefsFromMarkedVertices(true, false, false);
List<Linedef> fixedlines = LinedefsFromMarkedVertices(true, false, false);
// Determine area in which we are editing
RectangleF editarea = CreateArea(movinglines);
@ -2075,26 +2076,384 @@ namespace CodeImp.DoomBuilder.Map
// Split moving lines with unselected vertices
ICollection<Vertex> nearbyfixedverts = FilterByArea(fixedverts, ref editarea);
if(!SplitLinesByVertices(movinglines, nearbyfixedverts, STITCH_DISTANCE, movinglines))
if(!SplitLinesByVertices(movinglines, nearbyfixedverts, STITCH_DISTANCE, movinglines, correctsectorrefs))
return false;
// Split non-moving lines with selected vertices
fixedlines = FilterByArea(fixedlines, ref editarea);
if(!SplitLinesByVertices(fixedlines, movingverts, STITCH_DISTANCE, movinglines))
if(!SplitLinesByVertices(fixedlines, movingverts, STITCH_DISTANCE, movinglines, correctsectorrefs))
return false;
//mxd. Split moving lines with fixed lines
if(!SplitLinesByLines(fixedlines, movinglines, correctsectorrefs)) return false;
// Remove looped linedefs
RemoveLoopedLinedefs(movinglines);
// Join overlapping lines
if(!JoinOverlappingLines(movinglines))
return false;
if(!JoinOverlappingLines(movinglines)) return false;
EndAddRemove();
//mxd. Correct sector references
if(correctsectorrefs)
{
// Linedefs cache needs to be up to date...
Update(true, false);
// Fix stuff...
List<Linedef> changedlines = LinedefsFromMarkedVertices(false, true, true);
CorrectSectorReferences(changedlines, true);
CorrectOuterSides(new HashSet<Linedef>(changedlines));
}
return true;
}
//mxd. Shameless SLADEMap::correctSectors ripoff... Corrects/builds sectors for all lines in [lines]
private static void CorrectSectorReferences(List<Linedef> lines, bool existing_only)
{
// Create a list of sidedefs to perform sector creation with
List<LinedefSide> edges = new List<LinedefSide>();
if(existing_only)
{
foreach(Linedef l in lines)
{
// Add only existing sides as edges (or front side if line has none)
if(l.Front != null || l.Back == null)
edges.Add(new LinedefSide(l, true));
if(l.Back != null)
edges.Add(new LinedefSide(l, false));
}
}
else
{
foreach(Linedef l in lines)
{
// Add front side
edges.Add(new LinedefSide(l, true));
// Add back side if there's a sector
if(General.Map.Map.GetSectorByCoordinates(l.GetSidePoint(false)) != null)
edges.Add(new LinedefSide(l, false));
}
}
HashSet<Sidedef> sides_correct = new HashSet<Sidedef>();
foreach(LinedefSide ls in edges)
{
if(ls.Front && ls.Line.Front != null)
sides_correct.Add(ls.Line.Front);
else if(!ls.Front && ls.Line.Back != null)
sides_correct.Add(ls.Line.Back);
}
//mxd. Get affected sectors
HashSet<Sector> affectedsectors = new HashSet<Sector>(General.Map.Map.GetSelectedSectors(true));
affectedsectors.UnionWith(General.Map.Map.GetUnselectedSectorsFromLinedefs(lines));
//mxd. Collect their lines
HashSet<Linedef> sectorlines = new HashSet<Linedef>();
foreach(Sector s in affectedsectors)
{
foreach(Sidedef side in s.Sidedefs)
{
if(side.Line != null) sectorlines.Add(side.Line);
}
}
// Build sectors
SectorBuilder builder = new SectorBuilder();
List<Sector> sectors_reused = new List<Sector>();
foreach(LinedefSide ls in edges)
{
// Skip if edge is ignored
if(ls.Ignore) continue;
// Run sector builder on current edge
if(!builder.TraceSector(ls.Line, ls.Front)) continue; // Don't create sector if trace failed
// Find any subsequent edges that were part of the sector created
bool has_existing_lines = false;
bool has_existing_sides = false;
bool has_zero_sided_lines = false;
bool has_sides_belonging_to_dragged_sectors = false; //mxd
List<LinedefSide> edges_in_sector = new List<LinedefSide>();
foreach(LinedefSide edge in builder.SectorEdges)
{
bool line_is_ours = false;
if(sectorlines.Contains(edge.Line)) has_sides_belonging_to_dragged_sectors = true; //mxd
foreach(LinedefSide ls2 in edges)
{
if(ls2.Line == edge.Line)
{
line_is_ours = true;
if(ls2.Front == edge.Front)
{
edges_in_sector.Add(ls2);
break;
}
}
}
if(line_is_ours)
{
if(edge.Line.Front == null && edge.Line.Back == null)
has_zero_sided_lines = true;
}
else
{
has_existing_lines = true;
if(edge.Front ? edge.Line.Front != null : edge.Line.Back != null)
has_existing_sides = true;
}
}
// Pasting or moving a two-sided line into an enclosed void should NOT
// create a new sector out of the entire void.
// Heuristic: if the traced sector includes any edges that are NOT
// "ours", and NONE of those edges already exist, that sector must be
// in an enclosed void, and should not be drawn.
// However, if existing_only is false, the caller expects us to create
// new sides anyway; skip this check.
if(existing_only && has_existing_lines && !has_existing_sides && !has_sides_belonging_to_dragged_sectors) continue;
// Ignore traced edges when trying to create any further sectors
foreach(LinedefSide ls3 in edges_in_sector) ls3.Ignore = true;
// Check if sector traced is already valid
if(builder.IsValidSector()) continue;
// Check if we traced over an existing sector (or part of one)
Sector sector = builder.FindExistingSector(sides_correct);
if(sector != null)
{
// Check if it's already been (re)used
bool reused = false;
foreach(Sector s in sectors_reused)
{
if(s == sector)
{
reused = true;
break;
}
}
// If we can reuse the sector, do so
if(!reused)
sectors_reused.Add(sector);
else
sector = null;
}
// Create sector
builder.CreateSector(sector, null);
}
// Remove any sides that weren't part of a sector
foreach(LinedefSide ls in edges)
{
if(ls.Ignore || ls.Line == null) continue;
if(ls.Front)
{
if(ls.Line.Front != null)
{
ls.Line.Front.Dispose();
// Update doublesided flag
ls.Line.ApplySidedFlags();
}
}
else
{
if(ls.Line.Back != null)
{
ls.Line.Back.Dispose();
// Update doublesided flag
ls.Line.ApplySidedFlags();
}
}
}
// Check if any lines need to be flipped
FlipBackwardLinedefs(lines);
// Find an adjacent sector to copy properties from
Sector sector_copy = null;
foreach(Linedef l in lines)
{
// Check front sector
Sector sector = (l.Front != null ? l.Front.Sector : null);
if(sector != null && !sector.Marked)
{
// Copy this sector if it isn't newly created
sector_copy = sector;
break;
}
// Check back sector
sector = (l.Back != null ? l.Back.Sector : null);
if(sector != null && !sector.Marked)
{
// Copy this sector if it isn't newly created
sector_copy = sector;
break;
}
}
// Go through newly created sectors
List<Sector> newsectors = General.Map.Map.GetMarkedSectors(true); //mxd
foreach(Sector s in newsectors)
{
// Skip if sector already has properties
if(s.CeilTexture != "-") continue;
// Copy from adjacent sector if any
if(sector_copy != null)
{
sector_copy.CopyPropertiesTo(s);
continue;
}
// Otherwise, use defaults from game configuration
s.SetFloorTexture(General.Map.Options.DefaultFloorTexture);
s.SetCeilTexture(General.Map.Options.DefaultCeilingTexture);
s.FloorHeight = General.Settings.DefaultFloorHeight;
s.CeilHeight = General.Settings.DefaultCeilingHeight;
s.Brightness = General.Settings.DefaultBrightness;
}
// Update line textures
List<Sidedef> newsides = General.Map.Map.GetMarkedSidedefs(true);
foreach(Sidedef side in newsides)
{
// Clear any unneeded textures
side.RemoveUnneededTextures(side.Other != null);
// Set middle texture if needed
if(side.MiddleRequired() && side.MiddleTexture == "-")
{
// Find adjacent texture (any)
string tex = GetAdjacentMiddleTexture(side.Line.Start);
if(tex == "-") tex = GetAdjacentMiddleTexture(side.Line.End);
// If no adjacent texture, get default from game configuration
if(tex == "-") tex = General.Settings.DefaultTexture;
// Set texture
side.SetTextureMid(tex);
}
// Update sided flags
side.Line.ApplySidedFlags();
}
// Remove any extra sectors
General.Map.Map.RemoveUnusedSectors(false);
}
//mxd. Try to create outer sidedefs if needed
private static void CorrectOuterSides(HashSet<Linedef> changedlines)
{
HashSet<Linedef> linesmissingfront = new HashSet<Linedef>();
HashSet<Linedef> linesmissingback = new HashSet<Linedef>();
// Collect lines without front/back sides
foreach(Linedef line in changedlines)
{
if(line.Back == null) linesmissingback.Add(line);
if(line.Front == null) linesmissingfront.Add(line);
}
// Find sectors to join singlesided lines
Dictionary<Linedef, Sector> linefrontsectorref = new Dictionary<Linedef, Sector>();
foreach(Linedef line in linesmissingfront)
{
// Line is now inside a sector? (check from the missing side!)
Sector nearest = Tools.FindPotentialSector(line, true);
// We can reattach our line!
if(nearest != null) linefrontsectorref[line] = nearest;
}
Dictionary<Linedef, Sector> linebacksectorref = new Dictionary<Linedef, Sector>();
foreach(Linedef line in linesmissingback)
{
// Line is now inside a sector? (check from the missing side!)
Sector nearest = Tools.FindPotentialSector(line, false);
// We can reattach our line!
if(nearest != null) linebacksectorref[line] = nearest;
}
// Check single-sided lines. Add new sidedefs if necessary
// Key is dragged single-sided line, value is a sector dragged line ended up in.
foreach(KeyValuePair<Linedef, Sector> group in linefrontsectorref)
{
Linedef line = group.Key;
// Create new sidedef
Sidedef newside = General.Map.Map.CreateSidedef(line, true, group.Value);
// Copy props from the other side
Sidedef propssource = (line.Front ?? line.Back);
propssource.CopyPropertiesTo(newside);
// Correct the linedef
if((line.Front == null) && (line.Back != null))
{
line.FlipVertices();
line.FlipSidedefs();
}
}
foreach(KeyValuePair<Linedef, Sector> group in linebacksectorref)
{
Linedef line = group.Key;
// Create new sidedef
Sidedef newside = General.Map.Map.CreateSidedef(line, false, group.Value);
// Copy props from the other side
Sidedef propssource = (line.Front ?? line.Back);
propssource.CopyPropertiesTo(newside);
// Correct the linedef
if((line.Front == null) && (line.Back != null))
{
line.FlipVertices();
line.FlipSidedefs();
}
}
// Adjust textures
foreach(Linedef l in changedlines)
{
if(l.Front != null) l.Front.RemoveUnneededTextures(l.Back != null);
if(l.Back != null) l.Back.RemoveUnneededTextures(l.Front != null);
// Correct the sided flags
l.ApplySidedFlags();
}
}
//mxd
private static string GetAdjacentMiddleTexture(Vertex v)
{
// Go through adjacent lines
foreach(Linedef l in v.Linedefs)
{
if(l.Front != null && l.Front.MiddleTexture != "-") return l.Front.MiddleTexture;
if(l.Back != null && l.Back.MiddleTexture != "-") return l.Back.MiddleTexture;
}
return "-";
}
#endregion
#region ================== Geometry Tools
@ -2422,9 +2781,10 @@ namespace CodeImp.DoomBuilder.Map
/// <summary>This splits the given lines with the given vertices. All affected lines
/// will be added to changedlines. Returns false when the operation failed.</summary>
public static bool SplitLinesByVertices(ICollection<Linedef> lines, ICollection<Vertex> verts, float splitdist, ICollection<Linedef> changedlines)
public static bool SplitLinesByVertices(ICollection<Linedef> lines, ICollection<Vertex> verts, float splitdist, ICollection<Linedef> changedlines) { return SplitLinesByVertices(lines, verts, splitdist, changedlines, false); }
public static bool SplitLinesByVertices(ICollection<Linedef> lines, ICollection<Vertex> verts, float splitdist, ICollection<Linedef> changedlines, bool removeinnerlines)
{
if(verts.Count == 0 || lines.Count == 0) return true; //mxd
if (verts.Count == 0 || lines.Count == 0) return true; //mxd
float splitdist2 = splitdist * splitdist;
@ -2492,7 +2852,127 @@ namespace CodeImp.DoomBuilder.Map
}
}
}
}
}
}
return true;
}
/// <summary>Splits lines by lines. Adds new lines to the second collection. Returns false when the operation failed.</summary>
public static bool SplitLinesByLines(IList<Linedef> lines, IList<Linedef> changedlines, bool removeinnerlines) //mxd
{
if(lines.Count == 0 || changedlines.Count == 0) return true;
// Create blockmap
RectangleF area = RectangleF.Union(CreateArea(lines), CreateArea(changedlines));
BlockMap<BlockEntry> blockmap = new BlockMap<BlockEntry>(area);
blockmap.AddLinedefsSet(lines);
blockmap.AddLinedefsSet(changedlines);
int bmWidth = blockmap.Size.Width;
int bmHeight = blockmap.Size.Height;
BlockEntry[,] bmap = blockmap.Map;
//mxd
HashSet<Vertex> splitverts = new HashSet<Vertex>();
HashSet<Sector> changedsectors = (removeinnerlines ? General.Map.Map.GetSectorsFromLinedefs(changedlines) : new HashSet<Sector>());
HashSet<Linedef> initialchanedlines = new HashSet<Linedef>(changedlines);
// Check for intersections
for(int w = 0; w < bmWidth; w++)
{
for(int h = 0; h < bmHeight; h++)
{
BlockEntry block = bmap[w, h];
if(block.Lines.Count == 0) continue;
for(int i = 0; i < block.Lines.Count; i++)
{
Linedef l1 = block.Lines[i];
for(int c = 0; c < block.Lines.Count; c++)
{
if(i == c) continue;
Linedef l2 = block.Lines[c];
if(l1 == l2
|| l1.Start.Position == l2.Start.Position
|| l1.Start.Position == l2.End.Position
|| l1.End.Position == l2.Start.Position
|| l1.End.Position == l2.End.Position) continue;
// Check for intersection
Vector2D intersection = Line2D.GetIntersectionPoint(new Line2D(l1), new Line2D(l2), true);
if(!float.IsNaN(intersection.x))
{
// Create split vertex
Vertex splitvertex = General.Map.Map.CreateVertex(intersection);
if(splitvertex == null) return false;
// Split both lines
Linedef nl1 = l1.Split(splitvertex);
if(nl1 == null) return false;
Linedef nl2 = l2.Split(splitvertex);
if(nl2 == null) return false;
// Mark split vertex
splitvertex.Marked = true;
splitverts.Add(splitvertex); //mxd
// Add to the second collection
changedlines.Add(nl1);
changedlines.Add(nl2);
// And to the block entry
blockmap.AddLinedef(nl1);
blockmap.AddLinedef(nl2);
}
}
}
}
}
//mxd. Remove lines, which are inside affected sectors
if(removeinnerlines)
{
HashSet<Linedef> alllines = new HashSet<Linedef>(lines);
alllines.UnionWith(changedlines);
foreach(Linedef l in alllines) l.UpdateCache();
foreach(Sector s in changedsectors) s.UpdateBBox();
foreach(Linedef l in alllines)
{
// Remove line when both it's start and end are inside a changed sector and neither side references it
if(l.Start != null && l.End != null && !initialchanedlines.Contains(l) &&
(l.Front == null || !changedsectors.Contains(l.Front.Sector)) &&
(l.Back == null || !changedsectors.Contains(l.Back.Sector)))
{
foreach(Sector s in changedsectors)
{
if(s.Intersect(l.Start.Position) && s.Intersect(l.End.Position))
{
Vertex[] tocheck = new[] { l.Start, l.End };
l.Dispose();
foreach(Vertex v in tocheck)
{
// If the vertex only has 2 linedefs attached, then merge the linedefs
if(!v.IsDisposed && v.Linedefs.Count == 2)
{
Linedef ld1 = General.GetByIndex(v.Linedefs, 0);
Linedef ld2 = General.GetByIndex(v.Linedefs, 1);
Vertex v2 = (ld2.Start == v) ? ld2.End : ld2.Start;
if(ld1.Start == v) ld1.SetStartVertex(v2); else ld1.SetEndVertex(v2);
ld2.Dispose();
// Trash vertex
v.Dispose();
}
}
break;
}
}
}
}
}
@ -3028,7 +3508,7 @@ namespace CodeImp.DoomBuilder.Map
/// <summary>This makes a list of lines related to marked vertices.
/// A line is unstable when one vertex is marked and the other isn't.</summary>
public ICollection<Linedef> LinedefsFromMarkedVertices(bool includeunselected, bool includestable, bool includeunstable)
public List<Linedef> LinedefsFromMarkedVertices(bool includeunmarked, bool includestable, bool includeunstable)
{
List<Linedef> list = new List<Linedef>((numlinedefs / 2) + 1);
@ -3038,7 +3518,7 @@ namespace CodeImp.DoomBuilder.Map
// Check if this is to be included
if((includestable && (l.Start.Marked && l.End.Marked)) ||
(includeunstable && (l.Start.Marked ^ l.End.Marked)) ||
(includeunselected && (!l.Start.Marked && !l.End.Marked)))
(includeunmarked && (!l.Start.Marked && !l.End.Marked)))
{
// Add to list
list.Add(l);
@ -3137,6 +3617,37 @@ namespace CodeImp.DoomBuilder.Map
return result;
}
//mxd
/// <summary>Gets sectors, which have all their linedefs selected</summary>
public HashSet<Sector> GetSectorsFromLinedefs(IEnumerable<Linedef> lines)
{
HashSet<Sector> result = new HashSet<Sector>();
Dictionary<Sector, HashSet<Sidedef>> sectorsbysides = new Dictionary<Sector, HashSet<Sidedef>>();
// Collect unselected sectors, which sidedefs belong to selected lines
foreach(Linedef line in lines)
{
if(line.Front != null && line.Front.Sector != null)
{
if(!sectorsbysides.ContainsKey(line.Front.Sector)) sectorsbysides.Add(line.Front.Sector, new HashSet<Sidedef>());
sectorsbysides[line.Front.Sector].Add(line.Front);
}
if(line.Back != null && line.Back.Sector != null)
{
if(!sectorsbysides.ContainsKey(line.Back.Sector)) sectorsbysides.Add(line.Back.Sector, new HashSet<Sidedef>());
sectorsbysides[line.Back.Sector].Add(line.Back);
}
}
// Add sectors, which have all their lines selected
foreach(var group in sectorsbysides)
{
if(group.Key.Sidedefs.Count == group.Value.Count) result.Add(group.Key);
}
return result;
}
/// <summary>This finds the line closest to the specified position.</summary>
public Linedef NearestLinedef(Vector2D pos) { return MapSet.NearestLinedef(linedefs, pos); }

View file

@ -524,44 +524,36 @@ namespace CodeImp.DoomBuilder.Map
}
// This checks if the given point is inside the sector polygon
// See: http://paulbourke.net/geometry/polygonmesh/index.html#insidepoly
public bool Intersect(Vector2D p)
{
//mxd. Check bounding box first
if(p.x < bbox.Left || p.x > bbox.Right || p.y < bbox.Top || p.y > bbox.Bottom) return false;
uint c = 0;
Vector2D v1, v2;
// Go for all sidedefs
foreach(Sidedef sd in sidedefs)
{
// Get vertices
Vector2D v1 = sd.Line.Start.Position;
Vector2D v2 = sd.Line.End.Position;
v1 = sd.Line.Start.Position;
v2 = sd.Line.End.Position;
//mxd. On top of a vertex?
if(p == v1 || p == v2) return true;
// Determine min/max values
float miny = Math.Min(v1.y, v2.y);
float maxy = Math.Max(v1.y, v2.y);
float maxx = Math.Max(v1.x, v2.x);
// Check for intersection
if((p.y > miny) && (p.y <= maxy))
{
if(p.x <= maxx)
{
if(v1.y != v2.y)
{
float xint = (p.y - v1.y) * (v2.x - v1.x) / (v2.y - v1.y) + v1.x;
if((v1.x == v2.x) || (p.x <= xint)) c++;
}
}
}
if(v1.y != v2.y //mxd. If line is not horizontal...
&& p.y > (v1.y < v2.y ? v1.y : v2.y) //mxd. ...And test point y intersects with the line y bounds...
&& p.y <= (v1.y > v2.y ? v1.y : v2.y) //mxd
&& (p.x < (v1.x < v2.x ? v1.x : v2.x) || (p.x <= (v1.x > v2.x ? v1.x : v2.x) //mxd. ...And test point x is to the left of the line, or is inside line x bounds and intersects it
&& (v1.x == v2.x || p.x <= ((p.y - v1.y) * (v2.x - v1.x) / (v2.y - v1.y) + v1.x)))))
c++; //mxd. ...Count the line as crossed
}
// Inside this polygon?
return ((c & 0x00000001UL) != 0);
// Inside this polygon when we crossed odd number of polygon lines
return (c % 2 != 0);
}
// This creates a bounding box rectangle
@ -606,6 +598,12 @@ namespace CodeImp.DoomBuilder.Map
return new RectangleF(left, top, right - left, bottom - top);
}
//mxd
internal void UpdateBBox()
{
bbox = CreateBBox();
}
// This joins the sector with another sector
// This sector will be disposed
public void Join(Sector other)

View file

@ -0,0 +1,549 @@
#region ================== Namespaces
using System;
using System.Collections.Generic;
using System.Drawing;
using CodeImp.DoomBuilder.Geometry;
#endregion
namespace CodeImp.DoomBuilder.Map
{
//mxd. Shameless Slade 3 SectorBuilder::SectorBuilder ripoff...
//TODO: There are lots of overlaps with already existing code.
//TODO: Replace with existing implementations if results are the same & existing code performs faster
internal sealed class SectorBuilder
{
#region ================== Variables
private List<LinedefSide> sector_edges;
private HashSet<Vertex> vertex_valid;
// Current outline
private List<LinedefSide> o_edges;
private bool o_clockwise;
private RectangleF o_bbox;
private Vertex vertex_right;
#endregion
#region ================== Properties
public List<LinedefSide> SectorEdges { get { return sector_edges; } }
#endregion
#region ================== Constructor
public SectorBuilder()
{
sector_edges = new List<LinedefSide>();
vertex_valid = new HashSet<Vertex>();
o_edges = new List<LinedefSide>();
}
#endregion
#region ================== Methods
///<summary>Traces all edges to build a closed sector starting from [line]</summary>
internal bool TraceSector(Linedef line, bool front)
{
if(line == null) return false;
//DebugConsole.WriteLine(" ");
//DebugConsole.WriteLine("TraceSector for line " + line.Index + (front ? " (front)" : " (back)"));
// Init
sector_edges.Clear();
// Create valid vertices list
vertex_valid = new HashSet<Vertex>(General.Map.Map.Vertices);
// Find outmost outline
for(int a = 0; a < 10000; a++)
{
// Trace outline
if(!TraceOutline(line, front)) break;
// Discard any vertices outside the traced outline
vertex_valid.RemoveWhere(PointOutsideOutline);
// If it is clockwise, we've found the outmost outline
if(o_clockwise) break;
// Otherwise, find the next edge outside the outline
LinedefSide next = FindOuterEdge();
// If none was found, we're outside the map
if(next == null) return false;
// Repeat with this edge
line = next.Line;
front = next.Front;
}
//DebugConsole.WriteLine("FindOuterEdge: " + o_edges.Count + " lines");
// Trace all inner outlines, by tracing from the rightmost vertex
// until all vertices have been discarded
for(int a = 0; a < 10000; a++)
{
// Get inner edge
LinedefSide edge = FindInnerEdge();
// Check if we're done
if(edge == null) break;
// Trace outline from edge
if(!TraceOutline(edge.Line, edge.Front)) break;
// Discard any vertices outside the traced outline
vertex_valid.RemoveWhere(PointOutsideOutline);
}
//DebugConsole.WriteLine("FindInnerEdge: " + o_edges.Count + " lines");
return true;
}
///<summary>Traces the sector outline from lines beginning at [line],
/// on either the front or back side ([front])</summary>
private bool TraceOutline(Linedef line, bool front)
{
// Check line was given
if(line == null) return false;
//DebugConsole.WriteLine(" ");
//DebugConsole.WriteLine("Tracing line " + line.Index + (front ? " (front)" : " (back)"));
// Init outline
o_edges.Clear();
LinedefSide start = new LinedefSide(line, front);
o_edges.Add(start);
int edge_sum = 0;
Dictionary<Linedef, int> visited_lines = new Dictionary<Linedef, int>();
// Begin tracing
LinedefSide edge = new LinedefSide(line, front);
vertex_right = edge.Line.Start;
for(int a = 0; a < 10000; a++)
{
// Update edge sum (for clockwise detection)
if(edge.Front)
edge_sum += (int)(edge.Line.Start.Position.x * edge.Line.End.Position.y - edge.Line.End.Position.x * edge.Line.Start.Position.y);
else
edge_sum += (int)(edge.Line.End.Position.x * edge.Line.Start.Position.y - edge.Line.Start.Position.x * edge.Line.End.Position.y);
// Update rightmost vertex
if(edge.Line.Start.Position.x > vertex_right.Position.x)
vertex_right = edge.Line.Start;
if(edge.Line.End.Position.x > vertex_right.Position.x)
vertex_right = edge.Line.End;
// Get next edge. If no valid next edge was found, go back along the current line
LinedefSide edge_next = (NextEdge(edge, visited_lines) ?? new LinedefSide(edge.Line, !edge.Front));
//DebugConsole.WriteLine("Next line " + edge_next.Line.Index + (edge_next.Front ? " (front)" : " (back)"));
// Discard edge vertices
vertex_valid.Remove(edge_next.Line.Start);
vertex_valid.Remove(edge_next.Line.End);
// Check if we're back to the start
if(edge_next.Line == start.Line && edge_next.Front == start.Front)
break;
// Add edge to outline
o_edges.Add(edge_next);
edge.Line = edge_next.Line;
edge.Front = edge_next.Front;
// Update bounding box
RectangleF l_bbox = RectangleF.FromLTRB(
Math.Min(edge.Line.Start.Position.x, edge.Line.End.Position.x), // left
Math.Min(edge.Line.Start.Position.y, edge.Line.End.Position.y), // top
Math.Max(edge.Line.Start.Position.x, edge.Line.End.Position.x), // right
Math.Max(edge.Line.Start.Position.y, edge.Line.End.Position.y)); // bottom
o_bbox = (o_bbox.IsEmpty ? l_bbox : RectangleF.Union(o_bbox, l_bbox));
}
// Check if outline is clockwise
o_clockwise = (edge_sum < 0);
// Add outline edges to sector edge list
sector_edges.AddRange(o_edges);
// Trace complete
return true;
}
/// <summary>Finds the next closest edge outside of the current outline (that isn't part of the current outline)</summary>
private LinedefSide FindOuterEdge()
{
// Check we have a rightmost vertex
if(vertex_right == null) return null;
// Init
float vr_x = vertex_right.Position.x;
float vr_y = vertex_right.Position.y;
float min_dist = float.MaxValue;
Linedef nearest = null;
// Go through map lines
foreach(Linedef line in General.Map.Map.Linedefs)
{
// Ignore if the line is completely left of the vertex
if(line.Start.Position.x <= vr_x && line.End.Position.x <= vr_x) continue;
// Ignore horizontal lines
if(line.Start.Position.y == line.End.Position.y) continue;
// Ignore if the line doesn't intersect the y value
if((line.Start.Position.y < vr_y && line.End.Position.y < vr_y) ||
(line.Start.Position.y > vr_y && line.End.Position.y > vr_y))
continue;
// Get x intercept
float int_frac = (vr_y - line.Start.Position.y) / (line.End.Position.y - line.Start.Position.y);
float int_x = line.Start.Position.x + ((line.End.Position.x - line.Start.Position.x) * int_frac);
float dist = Math.Abs(int_x - vr_x);
// Check if closest
if(dist < min_dist)
{
min_dist = dist;
nearest = line;
}
}
// Check for valid line
if(nearest == null) return null;
// Determine the edge side
float side = -nearest.SideOfLine(vertex_right.Position); //mxd. SideOfLine logic is inverted in Slade 3
return new LinedefSide(nearest, side > 0); //mxd. The meaning of 0.0 is also inverted!!!
//mxd. I've spent 2 days figuring this out... :(
}
/// <summary>Find the closest edge within the current outline (that isn't part of the current outline)</summary>
private LinedefSide FindInnerEdge()
{
// Find rightmost non-discarded vertex
vertex_right = null;
foreach(Vertex v in vertex_valid)
{
// Set rightmost if no current rightmost vertex
if(vertex_right == null)
{
vertex_right = v;
continue;
}
// Check if the vertex is rightmost
if(v.Position.x > vertex_right.Position.x)
vertex_right = v;
}
// If no vertex was found, we're done
if(vertex_right == null) return null;
// Go through vertex's connected lines, to find
// the line with the smallest angle parallel with
// the right side of the bbox
Linedef eline = null;
float min_angle = float.MaxValue;
foreach(Linedef line in vertex_right.Linedefs)
{
// Ignore if zero-length
if(line.Start == line.End) continue;
// Get opposite vertex
Vertex opposite = (line.Start == vertex_right ? line.End : line.Start);
// Determine angle
float angle = Angle2D.GetAngle(new Vector2D(vertex_right.Position.x + 32, vertex_right.Position.y),
new Vector2D(vertex_right.Position.x, vertex_right.Position.y),
new Vector2D(opposite.Position.x, opposite.Position.y));
// Check if minimum
if(angle < min_angle)
{
min_angle = angle;
eline = line;
}
}
// If no line was found, something is wrong (the vertex may have no attached lines)
if(eline == null)
{
// Discard vertex and try again
vertex_valid.Remove(vertex_right);
return FindInnerEdge();
}
// Determine appropriate side
return new LinedefSide(eline, (vertex_right == eline.Start));
}
///<summary>Finds the next adjacent edge to [edge], ie the adjacent edge that creates the smallest angle</summary>
private static LinedefSide NextEdge(LinedefSide edge, Dictionary<Linedef, int> visited_lines)
{
// Get relevant vertices
Vertex vertex; // Vertex to be tested
Vertex vertex_prev; // 'Previous' vertex
if(edge.Front)
{
vertex = edge.Line.End;
vertex_prev = edge.Line.Start;
}
else
{
vertex = edge.Line.Start;
vertex_prev = edge.Line.End;
}
// Find next connected line with the lowest angle
float min_angle = Angle2D.PI2;
LinedefSide next = null;
foreach(Linedef line in vertex.Linedefs)
{
// Ignore original line
if(line == edge.Line) continue;
// Ignore if zero-length
if(line.Start.Position == line.End.Position) continue;
// Get next vertex
Vertex vertex_next;
bool front = true;
if(line.Start == vertex)
{
vertex_next = line.End;
}
else
{
vertex_next = line.Start;
front = false;
}
// Ignore already-traversed lines
int side = (front ? 1 : 2);
if(visited_lines.ContainsKey(line) && (visited_lines[line] & side) == side) continue;
// Determine angle between lines
float angle = Angle2D.GetAngle(new Vector2D(vertex_prev.Position.x, vertex_prev.Position.y),
new Vector2D(vertex.Position.x, vertex.Position.y),
new Vector2D(vertex_next.Position.x, vertex_next.Position.y));
// Check if minimum angle
if(angle < min_angle)
{
min_angle = angle;
if(next == null)
{
next = new LinedefSide(line, front);
}
else
{
next.Line = line;
next.Front = front;
}
}
}
// Return the next edge found
if(next == null) return null;
if(!visited_lines.ContainsKey(next.Line)) visited_lines.Add(next.Line, 0);
visited_lines[next.Line] |= (next.Front ? 1 : 2);
return next;
}
/// <summary>Returns true if the vertex is outside the current outline</summary>
private bool PointOutsideOutline(Vertex v)
{
// Check with bounding box
Vector2D point = v.Position;
bool pointwithin = (point.x >= o_bbox.Left && point.x <= o_bbox.Right && point.y >= o_bbox.Top && point.y <= o_bbox.Bottom);
if(!pointwithin)
{
// If the point is not within the bbox and the outline is clockwise, it can't be within the outline
// On the other hand, if the outline is anticlockwise, the point *must* be 'within' the outline
return o_clockwise;
}
// Find nearest edge
int nearest = NearestEdge(point);
if(nearest >= 0)
{
// Check what side of the edge the point is on
float side = -o_edges[nearest].Line.SideOfLine(point); //mxd. SideOfLine logic is inverted in Slade 3
//mxd. The meaning of 0.0 is also inverted!!!
// Return false if it is on the correct side
if(side > 0 && o_edges[nearest].Front) return false;
if(side <= 0 && !o_edges[nearest].Front) return false;
}
// Not within the outline
return true;
}
private int NearestEdge(Vector2D point)
{
// Init variables
float min_dist = float.MaxValue;
int nearest = -1;
// Go through edges
for(int i = 0; i < o_edges.Count; i++)
{
// Get distance to edge
float dist = o_edges[i].Line.SafeDistanceToSq(point, true);
// Check if minimum
if(dist < min_dist)
{
min_dist = dist;
nearest = i;
}
}
// Return nearest edge index
return nearest;
}
/// <summary>Checks if the traced sector is valid (ie. all edges are currently referencing the same (existing) sector)</summary>
public bool IsValidSector()
{
if(sector_edges.Count == 0) return false;
// Get first edge's sector
Sector sector = (sector_edges[0].Front ?
(sector_edges[0].Line.Front != null ? sector_edges[0].Line.Front.Sector : null) :
(sector_edges[0].Line.Back != null ? sector_edges[0].Line.Back.Sector : null));
// Sector is invalid if any edge has no current sector
if(sector == null) return false;
// Go through subsequent edges
for(int a = 1; a < sector_edges.Count; a++)
{
// Get edge sector
Sector ssector = (sector_edges[a].Front ?
(sector_edges[a].Line.Front != null ? sector_edges[a].Line.Front.Sector : null) :
(sector_edges[a].Line.Back != null ? sector_edges[a].Line.Back.Sector : null));
// Check if different
if(sector != ssector) return false;
}
// Return true if the entire sector was traced
return (sector.Sidedefs.Count == sector_edges.Count);
}
/// <summary>Finds any existing sector that is already part of the traced new sector</summary>
internal Sector FindExistingSector(HashSet<Sidedef> sides_ignore)
{
// Go through new sector edges
Sector sector = null;
Sector sector_priority = null;
foreach(LinedefSide edge in sector_edges)
{
// Check if the edge's corresponding MapSide has a front sector
if(edge.Front && edge.Line.Front != null && edge.Line.Front.Sector != null)
{
if(sides_ignore.Contains(edge.Line.Front))
sector = edge.Line.Front.Sector;
else
sector_priority = edge.Line.Front.Sector;
}
// Check if the edge's corresponding MapSide has a back sector
if(!edge.Front && edge.Line.Back != null && edge.Line.Back.Sector != null)
{
if(sides_ignore.Contains(edge.Line.Back))
sector = edge.Line.Back.Sector;
else
sector_priority = edge.Line.Back.Sector;
}
}
return (sector_priority ?? sector);
}
/// <summary>Sets all traced edges to [sector], or creates a new sector using properties
/// from [sector_copy] if none given</summary>
internal void CreateSector(Sector sector, Sector sector_copy)
{
// Create the sector if needed
if(sector == null)
{
sector = General.Map.Map.CreateSector();
if(sector == null) return;
sector.Marked = true; //mxd
// Find potential sector to copy if none specified
if(sector_copy == null) sector_copy = FindCopySector();
if(sector_copy != null) sector_copy.CopyPropertiesTo(sector);
}
//DebugConsole.WriteLine("Creating sector " + sector.Index + " from " + sector_edges.Count + " lines");
// Set sides to new sector
foreach(LinedefSide edge in sector_edges)
{
Sidedef target = (edge.Front ? edge.Line.Front : edge.Line.Back);
if(target != null)
{
if(target.Sector != sector)
{
target.SetSector(sector); //mxd. Reattach side
target.Marked = true; //mxd. Mark it
}
}
else
{
target = General.Map.Map.CreateSidedef(edge.Line, edge.Front, sector); //mxd. Create new side
target.Marked = true; //mxd. Mark it
}
}
}
/// <summary>Finds an appropriate existing sector to copy properties from, for the new sector being built</summary>
private Sector FindCopySector()
{
// Go through new sector edges
Sector sector_copy = null;
foreach(LinedefSide edge in sector_edges)
{
// Check if the edge's corresponding MapSide has a front sector
if(edge.Line.Front != null && edge.Line.Front.Sector != null)
{
// Set sector to copy
sector_copy = edge.Line.Front.Sector;
// If the edge is a front edge, use this sector and ignore all else
if(edge.Front) break;
}
// Check if the edge's corresponding MapSide has a back sector
if(edge.Line.Back != null && edge.Line.Back.Sector != null)
{
// Set sector to copy
sector_copy = edge.Line.Back.Sector;
// If the edge is a back edge, use this sector and ignore all else
if(!edge.Front) break;
}
}
return sector_copy;
}
#endregion
}
}

View file

@ -341,7 +341,7 @@ namespace CodeImp.DoomBuilder.Map
if(force || ((linedef.Tag == 0) && (linedef.Action == 0) && (sector.Tag == 0) &&
((Other == null) || (Other.sector.Tag == 0))))
{
if(!HighRequired())
/*if(!HighRequired())
{
BeforePropsChange(); //mxd
changed = true;
@ -349,13 +349,13 @@ namespace CodeImp.DoomBuilder.Map
this.longtexnamehigh = MapSet.EmptyLongName;
General.Map.IsChanged = true;
}
else if(shiftmiddle && this.longtexnamehigh == MapSet.EmptyLongName) //mxd
else*/ if(shiftmiddle && this.longtexnamehigh == MapSet.EmptyLongName && HighRequired()) //mxd
{
SetTextureHigh(this.texnamemid);
changed = true;
}
if(!LowRequired())
/*if(!LowRequired())
{
if(!changed) //mxd
{
@ -366,7 +366,7 @@ namespace CodeImp.DoomBuilder.Map
this.longtexnamelow = MapSet.EmptyLongName;
General.Map.IsChanged = true;
}
else if(shiftmiddle && this.longtexnamelow == MapSet.EmptyLongName) //mxd
else*/ if(shiftmiddle && this.longtexnamelow == MapSet.EmptyLongName && LowRequired()) //mxd
{
SetTextureLow(this.texnamemid);
changed = true;

View file

@ -419,14 +419,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
// 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);
General.Map.Map.StitchGeometry(true);
// Snap to map format accuracy
General.Map.Map.SnapAllToAccuracy();
@ -436,10 +430,15 @@ namespace CodeImp.DoomBuilder.BuilderModes
{
Vector2D offset = dragitem.Position - dragitemposition;
// Sectors may've been created/removed when applying dragging...
HashSet<Sector> draggedsectors = new HashSet<Sector>(General.Map.Map.GetMarkedSectors(true));
foreach(Sector ss in selectedsectors) if(!ss.IsDisposed) draggedsectors.Add(ss);
// Update floor/ceiling texture offsets?
if(BuilderPlug.Me.LockSectorTextureOffsetsWhileDragging)
{
foreach(Sector s in selectedsectors)
foreach(Sector s in draggedsectors)
{
s.Fields.BeforeFieldsChange();
@ -501,7 +500,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
}
// Update slopes
foreach(Sector s in selectedsectors)
foreach(Sector s in draggedsectors)
{
// Update floor slope?
if(s.FloorSlope.GetLengthSq() > 0 && !float.IsNaN(s.FloorSlopeOffset / s.FloorSlope.z))

View file

@ -102,10 +102,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
ICollection<Vertex> verts = General.Map.Map.GetVerticesFromLinesMarks(true);
foreach(Vertex v in verts) v.Selected = true;
//mxd. Mark moved sectors (used in Linedef.Join())
HashSet<Sector> draggeddsectors = General.Map.Map.GetUnselectedSectorsFromLinedefs(selectedlines);
foreach(Sector s in draggeddsectors) s.Marked = true;
// Perform normal disengage
base.OnDisengage();
@ -115,31 +111,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
// When not cancelled
if(!cancelled)
{
//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);
foreach(Linedef l in unstablelines) if(!l.IsDisposed) changedlines.Add(l);
//mxd. Add sectors, which have all their linedefs selected (otherwise those would be destroyed after moving the selection)
HashSet<Sector> toadjust = General.Map.Map.GetUnselectedSectorsFromLinedefs(changedlines);
//mxd. Reattach/add/remove outer sidedefs
HashSet<Sidedef> adjustedsides = Tools.AdjustOuterSidedefs(toadjust, changedlines);
//mxd. Split outer sectors
Tools.SplitOuterSectors(changedlines);
//mxd. Remove unneeded textures
foreach(Sidedef side in adjustedsides)
{
if(side.IsDisposed) continue;
side.RemoveUnneededTextures(true, true, true);
if(side.Other != null) side.Other.RemoveUnneededTextures(true, true, true);
}
// If only a single linedef was selected, deselect it now
if(selectedlines.Count == 1) General.Map.Map.ClearSelectedLinedefs();
}

View file

@ -118,31 +118,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
// When not cancelled
if(!cancelled)
{
//mxd. Collect changed lines
HashSet<Linedef> changedlines = new HashSet<Linedef>(selectedlines);
foreach(Linedef l in unstablelines) if(!l.IsDisposed) changedlines.Add(l);
//mxd. Collect changed sectors
HashSet<Sector> toadjust = new HashSet<Sector>(selectedsectors);
//mxd. Add sectors, which are not selected, but have all their linedefs selected
// (otherwise those would be destroyed after moving the selection)
toadjust.UnionWith(General.Map.Map.GetUnselectedSectorsFromLinedefs(changedlines));
//mxd. Process outer sidedefs
HashSet<Sidedef> adjustedsides = Tools.AdjustOuterSidedefs(toadjust, changedlines);
//mxd. Split outer sectors
Tools.SplitOuterSectors(changedlines);
//mxd. Remove unneeded textures
foreach(Sidedef side in adjustedsides)
{
if(side.IsDisposed) continue;
side.RemoveUnneededTextures(true, true, true);
if(side.Other != null) side.Other.RemoveUnneededTextures(true, true, true);
}
// If only a single sector was selected, deselect it now
if(selectedsectors.Count == 1)
{

View file

@ -92,62 +92,12 @@ namespace CodeImp.DoomBuilder.BuilderModes
General.Map.Map.ClearSelectedVertices();
General.Map.Map.SelectMarkedVertices(true, true);
//mxd. Mark stable lines now (marks will be carried to split lines by MapSet.StitchGeometry())
HashSet<Linedef> stablelines = (!cancelled ? new HashSet<Linedef>(General.Map.Map.LinedefsFromMarkedVertices(false, true, false)) : new HashSet<Linedef>());
foreach(Linedef l in stablelines) l.Marked = true;
//mxd. Mark moved sectors (used in Linedef.Join())
HashSet<Sector> draggeddsectors = (!cancelled ? General.Map.Map.GetUnselectedSectorsFromLinedefs(stablelines) : new HashSet<Sector>());
foreach(Sector s in draggeddsectors) s.Marked = true;
// Perform normal disengage
base.OnDisengage();
// When not cancelled
if(!cancelled)
{
//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>(stablelines);
changedlines.UnionWith(newlines);
foreach(Linedef l in unstablelines) if(!l.IsDisposed) changedlines.Add(l);
//mxd. Get sectors, which have all their linedefs selected (otherwise those would be destroyed after moving the selection)
HashSet<Sector> toadjust = General.Map.Map.GetUnselectedSectorsFromLinedefs(changedlines);
//mxd. If linedefs were dragged, reattach/add/remove sidedefs
if(changedlines.Count > 0)
{
// Reattach/add/remove outer sidedefs
HashSet<Sidedef> adjustedsides = Tools.AdjustOuterSidedefs(toadjust, changedlines);
// Split outer sectors
Tools.SplitOuterSectors(changedlines);
// Remove unneeded textures
foreach(Sidedef side in adjustedsides)
{
if(side.IsDisposed) continue;
side.RemoveUnneededTextures(true, true, true);
if(side.Other != null) side.Other.RemoveUnneededTextures(true, true, true);
}
// Additional verts may've been created
if(selectedverts.Count > 1)
{
foreach(Linedef l in changedlines)
{
if(!unstablelines.Contains(l))
{
l.Start.Selected = true;
l.End.Selected = true;
}
}
}
}
// If only a single vertex was selected, deselect it now
if(selectedverts.Count == 1) General.Map.Map.ClearSelectedVertices();
}

View file

@ -911,7 +911,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
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));
fields["yscale" + si.Part] = new UniValue(UniversalType.Float, (float) Math.Round(-si.Scale.y * scale.y, General.Map.FormatInterface.VertexDecimals));
}
// Restore scale
else
@ -1041,41 +1041,66 @@ namespace CodeImp.DoomBuilder.BuilderModes
#region ================== Sector height adjust methods (mxd)
private static Sector GetOutsideSector(IEnumerable<Sector> sectors)
//x = floor height, y = ceiling height
private static Point GetOutsideHeights(HashSet<Sector> sectors)
{
Sector result = null;
Sector target = null;
Point result = new Point { X = int.MinValue, Y = int.MinValue };
foreach(Sector s in sectors)
{
foreach(Sidedef side in s.Sidedefs)
{
if(side.Other == null || side.Other.Sector == null) continue;
if(result == null) result = side.Other.Sector;
else if(result != side.Other.Sector) return null;
// Don't compare with our own stuff, among other things
if(side.Other == null || side.Other.Sector == null || sectors.Contains(side.Other.Sector)) continue;
if(target == null)
{
target = side.Other.Sector;
result.X = target.FloorHeight;
result.Y = target.CeilHeight;
}
else if(target != side.Other.Sector)
{
// Compare heights
if(target.FloorHeight != side.Other.Sector.FloorHeight)
result.X = int.MinValue;
if(target.CeilHeight != side.Other.Sector.CeilHeight)
result.Y = int.MinValue;
// We can stop now...
if(result.X == int.MinValue && result.Y == int.MinValue)
return result;
}
}
}
return result;
}
private static void AdjustSectorsHeight(ICollection<Sector> toadjust, HeightAdjustMode adjustmode, int oldfloorheight, int oldceilheight)
private static void AdjustSectorsHeight(HashSet<Sector> toadjust, HeightAdjustMode adjustmode, int oldfloorheight, int oldceilheight)
{
// Adjust only when selection is inside a single sector
if(adjustmode == HeightAdjustMode.NONE || oldfloorheight == int.MinValue || oldceilheight == int.MinValue) return;
Sector outsidesector = GetOutsideSector(toadjust);
if(outsidesector == null) return;
Point outsideheights = GetOutsideHeights(toadjust);
if(outsideheights.X == int.MinValue && outsideheights.Y == int.MinValue) return;
// Height differences
int floorheightdiff = outsidesector.FloorHeight - oldfloorheight;
int ceilheightdiff = outsidesector.CeilHeight - oldceilheight;
int floorheightdiff = (outsideheights.X == int.MinValue ? int.MinValue : outsideheights.X - oldfloorheight);
int ceilheightdiff = (outsideheights.Y == int.MinValue ? int.MinValue : outsideheights.Y - oldceilheight);
switch(adjustmode)
{
case HeightAdjustMode.ADJUST_FLOORS:
if(floorheightdiff != int.MinValue)
{
foreach(Sector s in toadjust) AdjustSectorHeight(s, floorheightdiff, int.MinValue);
}
break;
case HeightAdjustMode.ADJUST_CEILINGS:
if(ceilheightdiff != int.MinValue)
{
foreach(Sector s in toadjust) AdjustSectorHeight(s, int.MinValue, ceilheightdiff);
}
break;
case HeightAdjustMode.ADJUST_BOTH:
@ -1222,7 +1247,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
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
unstablelines = (pasting ? new List<Linedef>() : General.Map.Map.LinedefsFromMarkedVertices(false, false, true)); //mxd
// Array to keep original coordinates
vertexpos = new List<Vector2D>(selectedvertices.Count);
@ -1601,8 +1626,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
// Do we have a virtual and parent sector?
if((vsector != null) && (parent != null))
{
//mxd. Apply HeightAdjustMode
AdjustSectorsHeight(General.Map.Map.GetMarkedSectors(true), heightadjustmode, vsector.FloorHeight, vsector.CeilHeight);
//mxd. Store floor/ceiling height
oldoutsidefloorheight = vsector.FloorHeight;
oldoutsideceilingheight = vsector.CeilHeight;
}
// Remove any virtual sectors
@ -1610,64 +1636,41 @@ namespace CodeImp.DoomBuilder.BuilderModes
}
else
{
//mxd. Get floor/ceiling height from outside sector
//mxd. Get floor/ceiling height from outside sectors
if(unstablelines.Count == 0 && heightadjustmode != HeightAdjustMode.NONE)
{
// Get affected sectors
HashSet<Sector> affectedsectors = new HashSet<Sector>(General.Map.Map.GetSelectedSectors(true));
Sector curoutsidesector = GetOutsideSector(affectedsectors);
if(curoutsidesector != null)
{
oldoutsidefloorheight = curoutsidesector.FloorHeight;
oldoutsideceilingheight = curoutsidesector.CeilHeight;
}
Point outsideheights = GetOutsideHeights(affectedsectors);
oldoutsidefloorheight = outsideheights.X;
oldoutsideceilingheight = outsideheights.Y;
}
}
// Stitch geometry
General.Map.Map.StitchGeometry();
// Make corrections for backward linedefs
MapSet.FlipBackwardLinedefs(General.Map.Map.Linedefs);
General.Map.Map.StitchGeometry(true);
// 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);
foreach(Linedef l in unstablelines) if(!l.IsDisposed) changedlines.Add(l);
//mxd. Update outer sides of the selection
if(changedlines.Count > 0)
//mxd. Update sector height?
if(changedlines.Count > 0 && heightadjustmode != HeightAdjustMode.NONE
&& oldoutsidefloorheight != int.MinValue && oldoutsideceilingheight != int.MinValue)
{
// Get affected sectors
HashSet<Sector> affectedsectors = new HashSet<Sector>(General.Map.Map.GetSelectedSectors(true));
affectedsectors.UnionWith(General.Map.Map.GetUnselectedSectorsFromLinedefs(changedlines));
// Sectors may've been created/removed when applying dragging...
HashSet<Sector> draggedsectors = new HashSet<Sector>(General.Map.Map.GetMarkedSectors(true));
foreach(Sector ss in selectedsectors.Keys) if(!ss.IsDisposed) draggedsectors.Add(ss);
// Reattach/add/remove outer sidedefs
HashSet<Sidedef> adjustedsides = Tools.AdjustOuterSidedefs(affectedsectors, changedlines);
// Change floor/ceiling height?
if(!pasting) AdjustSectorsHeight(affectedsectors, heightadjustmode, oldoutsidefloorheight, oldoutsideceilingheight);
// Split outer sectors
Tools.SplitOuterSectors(changedlines);
// Remove unneeded textures (needs to be done AFTER adjusting floor/ceiling height)
foreach(Sidedef side in adjustedsides)
{
if(side.IsDisposed) continue;
side.RemoveUnneededTextures(true, true, true);
if(side.Other != null) side.Other.RemoveUnneededTextures(true, true, true);
}
// Change floor/ceiling height
AdjustSectorsHeight(draggedsectors, heightadjustmode, oldoutsidefloorheight, oldoutsideceilingheight);
}
// Update cached values