diff --git a/Build/Scripting/ZDoom_DECORATE.cfg b/Build/Scripting/ZDoom_DECORATE.cfg index f91c586..253b1f9 100644 --- a/Build/Scripting/ZDoom_DECORATE.cfg +++ b/Build/Scripting/ZDoom_DECORATE.cfg @@ -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"; diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj index f8b39d7..0460f17 100644 --- a/Source/Core/Builder.csproj +++ b/Source/Core/Builder.csproj @@ -917,6 +917,7 @@ + diff --git a/Source/Core/Controls/DebugConsole.cs b/Source/Core/Controls/DebugConsole.cs index 20ddb05..9713117 100644 --- a/Source/Core/Controls/DebugConsole.cs +++ b/Source/Core/Controls/DebugConsole.cs @@ -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; } diff --git a/Source/Core/Geometry/Angle2D.cs b/Source/Core/Geometry/Angle2D.cs index 36a8bb1..bbf84ee 100644 --- a/Source/Core/Geometry/Angle2D.cs +++ b/Source/Core/Geometry/Angle2D.cs @@ -82,6 +82,53 @@ namespace CodeImp.DoomBuilder.Geometry // Return result 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 } diff --git a/Source/Core/Geometry/EarClipPolygon.cs b/Source/Core/Geometry/EarClipPolygon.cs index c237948..838d7d3 100644 --- a/Source/Core/Geometry/EarClipPolygon.cs +++ b/Source/Core/Geometry/EarClipPolygon.cs @@ -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 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,11 +149,9 @@ namespace CodeImp.DoomBuilder.Geometry // Inside polygon! return true; } - else - { - // Not inside the polygon - return false; - } + + // Not inside the polygon + return false; } // This inserts a polygon if it is a child of this one diff --git a/Source/Core/Geometry/Line2D.cs b/Source/Core/Geometry/Line2D.cs index c3b2a9c..61e4db9 100644 --- a/Source/Core/Geometry/Line2D.cs +++ b/Source/Core/Geometry/Line2D.cs @@ -17,6 +17,7 @@ #region ================== Namespaces using System; +using CodeImp.DoomBuilder.Map; #endregion @@ -65,6 +66,13 @@ namespace CodeImp.DoomBuilder.Geometry this.v1 = new Vector2D(x1, y1); this.v2 = new Vector2D(x2, y2); } + + //mxd. Constructor + public Line2D(Linedef line) + { + this.v1 = line.Start.Position; + this.v2 = line.End.Position; + } #endregion diff --git a/Source/Core/Geometry/LinedefSide.cs b/Source/Core/Geometry/LinedefSide.cs index 2a49641..3704865 100644 --- a/Source/Core/Geometry/LinedefSide.cs +++ b/Source/Core/Geometry/LinedefSide.cs @@ -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 diff --git a/Source/Core/Geometry/Tools.cs b/Source/Core/Geometry/Tools.cs index 140833b..f8c8f86 100644 --- a/Source/Core/Geometry/Tools.cs +++ b/Source/Core/Geometry/Tools.cs @@ -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 AdjustOuterSidedefs(HashSet selectedsectors, HashSet selectedlines) - { - HashSet adjustedsides = new HashSet(); - HashSet outersides = new HashSet(); - HashSet innersides = new HashSet(); - HashSet singlesidedlines = new HashSet(); - HashSet lineswithoutsides = new HashSet(); - - // 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 linesectorref = new Dictionary(); - 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 sidesectorref = new Dictionary(); - 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 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 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 sidestoexclude) + public static Sector FindPotentialSector(Linedef line, bool front) { List 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 sectorsidesset = new HashSet(); - 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... diff --git a/Source/Core/Map/Linedef.cs b/Source/Core/Map/Linedef.cs index 691e385..22a4e86 100644 --- a/Source/Core/Map/Linedef.cs +++ b/Source/Core/Map/Linedef.cs @@ -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 diff --git a/Source/Core/Map/MapSet.cs b/Source/Core/Map/MapSet.cs index 8c0e0d6..406e9b9 100644 --- a/Source/Core/Map/MapSet.cs +++ b/Source/Core/Map/MapSet.cs @@ -1989,9 +1989,9 @@ namespace CodeImp.DoomBuilder.Map } /// This filters lines by a rectangular area. - public static ICollection FilterByArea(ICollection lines, ref RectangleF area) + public static List FilterByArea(ICollection lines, ref RectangleF area) { - ICollection newlines = new List(lines.Count); + List newlines = new List(lines.Count); // Go for all lines foreach(Linedef l in lines) @@ -2046,17 +2046,18 @@ namespace CodeImp.DoomBuilder.Map /// /// Stitches marked geometry with non-marked geometry. Returns false when the operation failed. /// - public bool StitchGeometry() + public bool StitchGeometry() { return StitchGeometry(false); } //mxd. Compatibility + public bool StitchGeometry(bool correctsectorrefs) { // Find vertices ICollection movingverts = General.Map.Map.GetMarkedVertices(true); ICollection fixedverts = General.Map.Map.GetMarkedVertices(false); // Find lines that moved during the drag - ICollection movinglines = LinedefsFromMarkedVertices(false, true, true); + List movinglines = LinedefsFromMarkedVertices(false, true, true); // Find all non-moving lines - ICollection fixedlines = LinedefsFromMarkedVertices(true, false, false); + List fixedlines = LinedefsFromMarkedVertices(true, false, false); // Determine area in which we are editing RectangleF editarea = CreateArea(movinglines); @@ -2075,25 +2076,383 @@ namespace CodeImp.DoomBuilder.Map // Split moving lines with unselected vertices ICollection 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 changedlines = LinedefsFromMarkedVertices(false, true, true); + CorrectSectorReferences(changedlines, true); + CorrectOuterSides(new HashSet(changedlines)); + } return true; } + + //mxd. Shameless SLADEMap::correctSectors ripoff... Corrects/builds sectors for all lines in [lines] + private static void CorrectSectorReferences(List lines, bool existing_only) + { + // Create a list of sidedefs to perform sector creation with + List edges = new List(); + 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 sides_correct = new HashSet(); + 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 affectedsectors = new HashSet(General.Map.Map.GetSelectedSectors(true)); + affectedsectors.UnionWith(General.Map.Map.GetUnselectedSectorsFromLinedefs(lines)); + + //mxd. Collect their lines + HashSet sectorlines = new HashSet(); + 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 sectors_reused = new List(); + + 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 edges_in_sector = new List(); + 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 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 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 changedlines) + { + HashSet linesmissingfront = new HashSet(); + HashSet linesmissingback = new HashSet(); + + // 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 linefrontsectorref = new Dictionary(); + 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 linebacksectorref = new Dictionary(); + 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 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 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 @@ -2422,10 +2781,11 @@ namespace CodeImp.DoomBuilder.Map /// This splits the given lines with the given vertices. All affected lines /// will be added to changedlines. Returns false when the operation failed. - public static bool SplitLinesByVertices(ICollection lines, ICollection verts, float splitdist, ICollection changedlines) + public static bool SplitLinesByVertices(ICollection lines, ICollection verts, float splitdist, ICollection changedlines) { return SplitLinesByVertices(lines, verts, splitdist, changedlines, false); } + public static bool SplitLinesByVertices(ICollection lines, ICollection verts, float splitdist, ICollection 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; //mxd. Create blockmap @@ -2492,7 +2852,127 @@ namespace CodeImp.DoomBuilder.Map } } } + } + } + } + return true; + } + + /// Splits lines by lines. Adds new lines to the second collection. Returns false when the operation failed. + public static bool SplitLinesByLines(IList lines, IList changedlines, bool removeinnerlines) //mxd + { + if(lines.Count == 0 || changedlines.Count == 0) return true; + + // Create blockmap + RectangleF area = RectangleF.Union(CreateArea(lines), CreateArea(changedlines)); + BlockMap blockmap = new BlockMap(area); + blockmap.AddLinedefsSet(lines); + blockmap.AddLinedefsSet(changedlines); + int bmWidth = blockmap.Size.Width; + int bmHeight = blockmap.Size.Height; + BlockEntry[,] bmap = blockmap.Map; + + //mxd + HashSet splitverts = new HashSet(); + HashSet changedsectors = (removeinnerlines ? General.Map.Map.GetSectorsFromLinedefs(changedlines) : new HashSet()); + HashSet initialchanedlines = new HashSet(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 alllines = new HashSet(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 /// This makes a list of lines related to marked vertices. /// A line is unstable when one vertex is marked and the other isn't. - public ICollection LinedefsFromMarkedVertices(bool includeunselected, bool includestable, bool includeunstable) + public List LinedefsFromMarkedVertices(bool includeunmarked, bool includestable, bool includeunstable) { List list = new List((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 + /// Gets sectors, which have all their linedefs selected + public HashSet GetSectorsFromLinedefs(IEnumerable lines) + { + HashSet result = new HashSet(); + Dictionary> sectorsbysides = new Dictionary>(); + + // 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()); + 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()); + 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; + } + /// This finds the line closest to the specified position. public Linedef NearestLinedef(Vector2D pos) { return MapSet.NearestLinedef(linedefs, pos); } diff --git a/Source/Core/Map/Sector.cs b/Source/Core/Map/Sector.cs index 259ca97..f6c1533 100644 --- a/Source/Core/Map/Sector.cs +++ b/Source/Core/Map/Sector.cs @@ -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 @@ -605,6 +597,12 @@ namespace CodeImp.DoomBuilder.Map // Return rectangle 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 diff --git a/Source/Core/Map/SectorBuilder.cs b/Source/Core/Map/SectorBuilder.cs new file mode 100644 index 0000000..94adc13 --- /dev/null +++ b/Source/Core/Map/SectorBuilder.cs @@ -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 sector_edges; + private HashSet vertex_valid; + + // Current outline + private List o_edges; + private bool o_clockwise; + private RectangleF o_bbox; + private Vertex vertex_right; + + #endregion + + #region ================== Properties + + public List SectorEdges { get { return sector_edges; } } + + #endregion + + #region ================== Constructor + + public SectorBuilder() + { + sector_edges = new List(); + vertex_valid = new HashSet(); + o_edges = new List(); + } + + #endregion + + #region ================== Methods + + ///Traces all edges to build a closed sector starting from [line] + 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(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; + } + + ///Traces the sector outline from lines beginning at [line], + /// on either the front or back side ([front]) + 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 visited_lines = new Dictionary(); + + // 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; + } + + /// Finds the next closest edge outside of the current outline (that isn't part of the current outline) + 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... :( + } + + /// Find the closest edge within the current outline (that isn't part of the current outline) + 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)); + } + + ///Finds the next adjacent edge to [edge], ie the adjacent edge that creates the smallest angle + private static LinedefSide NextEdge(LinedefSide edge, Dictionary 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; + } + + /// Returns true if the vertex is outside the current outline + 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; + } + + /// Checks if the traced sector is valid (ie. all edges are currently referencing the same (existing) sector) + 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); + } + + /// Finds any existing sector that is already part of the traced new sector + internal Sector FindExistingSector(HashSet 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); + } + + /// Sets all traced edges to [sector], or creates a new sector using properties + /// from [sector_copy] if none given + 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 + } + } + } + + /// Finds an appropriate existing sector to copy properties from, for the new sector being built + 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 + } +} diff --git a/Source/Core/Map/Sidedef.cs b/Source/Core/Map/Sidedef.cs index 9aeb6cf..cd94be8 100644 --- a/Source/Core/Map/Sidedef.cs +++ b/Source/Core/Map/Sidedef.cs @@ -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 { @@ -365,8 +365,8 @@ namespace CodeImp.DoomBuilder.Map this.texnamelow = "-"; 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; diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragGeometryMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragGeometryMode.cs index 998e61c..7ba62d2 100644 --- a/Source/Plugins/BuilderModes/ClassicModes/DragGeometryMode.cs +++ b/Source/Plugins/BuilderModes/ClassicModes/DragGeometryMode.cs @@ -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(); @@ -435,11 +429,16 @@ namespace CodeImp.DoomBuilder.BuilderModes if(General.Map.UDMF) { Vector2D offset = dragitem.Position - dragitemposition; + + // Sectors may've been created/removed when applying dragging... + HashSet draggedsectors = new HashSet(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)) diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragLinedefsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragLinedefsMode.cs index 8448171..4ab6b69 100644 --- a/Source/Plugins/BuilderModes/ClassicModes/DragLinedefsMode.cs +++ b/Source/Plugins/BuilderModes/ClassicModes/DragLinedefsMode.cs @@ -102,10 +102,6 @@ namespace CodeImp.DoomBuilder.BuilderModes ICollection verts = General.Map.Map.GetVerticesFromLinesMarks(true); foreach(Vertex v in verts) v.Selected = true; - //mxd. Mark moved sectors (used in Linedef.Join()) - HashSet 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 newlines = new HashSet(General.Map.Map.GetMarkedLinedefs(true)); - - //mxd. Marked lines were created during linedef splitting - HashSet changedlines = new HashSet(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 toadjust = General.Map.Map.GetUnselectedSectorsFromLinedefs(changedlines); - - //mxd. Reattach/add/remove outer sidedefs - HashSet 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(); } diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragSectorsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragSectorsMode.cs index 33a59e6..6d7b76e 100644 --- a/Source/Plugins/BuilderModes/ClassicModes/DragSectorsMode.cs +++ b/Source/Plugins/BuilderModes/ClassicModes/DragSectorsMode.cs @@ -118,31 +118,6 @@ namespace CodeImp.DoomBuilder.BuilderModes // When not cancelled if(!cancelled) { - //mxd. Collect changed lines - HashSet changedlines = new HashSet(selectedlines); - foreach(Linedef l in unstablelines) if(!l.IsDisposed) changedlines.Add(l); - - //mxd. Collect changed sectors - HashSet toadjust = new HashSet(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 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) { diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragVerticesMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragVerticesMode.cs index 66e31d8..a2e8983 100644 --- a/Source/Plugins/BuilderModes/ClassicModes/DragVerticesMode.cs +++ b/Source/Plugins/BuilderModes/ClassicModes/DragVerticesMode.cs @@ -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 stablelines = (!cancelled ? new HashSet(General.Map.Map.LinedefsFromMarkedVertices(false, true, false)) : new HashSet()); - foreach(Linedef l in stablelines) l.Marked = true; - - //mxd. Mark moved sectors (used in Linedef.Join()) - HashSet draggeddsectors = (!cancelled ? General.Map.Map.GetUnselectedSectorsFromLinedefs(stablelines) : new HashSet()); - 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 newlines = new HashSet(General.Map.Map.GetMarkedLinedefs(true)); - - //mxd. Marked lines were created during linedef splitting - HashSet changedlines = new HashSet(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 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 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(); } diff --git a/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs b/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs index a3c2527..8a52da0 100644 --- a/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs +++ b/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs @@ -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 sectors) + //x = floor height, y = ceiling height + private static Point GetOutsideHeights(HashSet 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 toadjust, HeightAdjustMode adjustmode, int oldfloorheight, int oldceilheight) + private static void AdjustSectorsHeight(HashSet 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: - foreach(Sector s in toadjust) AdjustSectorHeight(s, floorheightdiff, int.MinValue); + if(floorheightdiff != int.MinValue) + { + foreach(Sector s in toadjust) AdjustSectorHeight(s, floorheightdiff, int.MinValue); + } break; case HeightAdjustMode.ADJUST_CEILINGS: - foreach(Sector s in toadjust) AdjustSectorHeight(s, int.MinValue, ceilheightdiff); + 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() : General.Map.Map.LinedefsFromMarkedVertices(false, false, true)); //mxd + unstablelines = (pasting ? new List() : General.Map.Map.LinedefsFromMarkedVertices(false, false, true)); //mxd // Array to keep original coordinates vertexpos = new List(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 affectedsectors = new HashSet(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 newlines = new HashSet(General.Map.Map.GetMarkedLinedefs(true)); //mxd. Marked lines were created during linedef splitting HashSet changedlines = new HashSet(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 affectedsectors = new HashSet(General.Map.Map.GetSelectedSectors(true)); - affectedsectors.UnionWith(General.Map.Map.GetUnselectedSectorsFromLinedefs(changedlines)); + // Sectors may've been created/removed when applying dragging... + HashSet draggedsectors = new HashSet(General.Map.Map.GetMarkedSectors(true)); + foreach(Sector ss in selectedsectors.Keys) if(!ss.IsDisposed) draggedsectors.Add(ss); - // Reattach/add/remove outer sidedefs - HashSet 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