UltimateZoneBuilder/Source/Core/Geometry/Tools.cs
MaxED bfd57379bb Added, Map Analysis mode: added "Check unknown ACS scripts" error check.
Added, Map Analysis mode: added "Edit Thing..." option to "Unknown Thing" and "Obsolete Thing" error check results.
Added: a warning is now displayed when a thing has both voxel and model attached.
Changed, Thing/Linedef Info panels: unknown ACS script names/numbers are now shown in red.
Fixed, Edit Things window, UDMF: unneeded undo was created when opening the window.
Re-fixed: in some cases invalid sectors were created after dragging map elements when using "Merge Dragged Geometry" and "Replace with Dragged Geometry" drag modes, when at least one of dragged linedef was facing into an enclosed void area (previous fix entirely disabled the associated logic).
2016-09-12 14:08:45 +00:00

2460 lines
85 KiB
C#

#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.Types;
using CodeImp.DoomBuilder.VisualModes;
using SlimDX;
#endregion
namespace CodeImp.DoomBuilder.Geometry
{
/// <summary>
/// Tools to work with geometry.
/// </summary>
public static class Tools
{
#region ================== Structures
private struct SidedefSettings
{
public string newtexhigh;
public string newtexmid;
public string newtexlow;
}
public struct SidedefFillJob
{
public Sidedef sidedef;
// Moving forward along the sidedef?
public bool forward;
}
#endregion
#region ================== Constants
//mxd
private const float MINIMUM_INTERSECTION_DISTANCE = 0.25f;
#endregion
#region ================== Polygons and Triangles
// Point inside the polygon?
// See: http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/
public static bool PointInPolygon(ICollection<Vector2D> polygon, Vector2D point)
{
Vector2D v1 = General.GetByIndex(polygon, polygon.Count - 1);
uint c = 0;
// Go for all vertices
foreach(Vector2D v2 in polygon)
{
// 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((point.y > miny) && (point.y <= maxy))
{
if(point.x <= maxx)
{
if(v1.y != v2.y)
{
float xint = (point.y - v1.y) * (v2.x - v1.x) / (v2.y - v1.y) + v1.x;
if((v1.x == v2.x) || (point.x <= xint)) c++;
}
}
}
// Move to next
v1 = v2;
}
// Inside this polygon?
return (c & 0x00000001UL) != 0;
}
#endregion
#region ================== Pathfinding
/// <summary>
/// This finds a potential sector at the given coordinates,
/// or returns null when a sector is not possible there.
/// </summary>
public static List<LinedefSide> FindPotentialSectorAt(Vector2D pos)
{
// Find the nearest line and determine side, then use the other method to create the sector
Linedef l = General.Map.Map.NearestLinedef(pos);
return FindPotentialSectorAt(l, (l.SideOfLine(pos) <= 0));
}
/// <summary>
/// This finds a potential sector starting at the given line and side,
/// or returns null when sector is not possible.
/// </summary>
public static List<LinedefSide> FindPotentialSectorAt(Linedef line, bool front)
{
List<LinedefSide> alllines = new List<LinedefSide>();
// Find the outer lines
EarClipPolygon p = FindOuterLines(line, front, alllines);
if(p != null)
{
// Find the inner lines
FindInnerLines(p, alllines);
return alllines;
}
return null;
}
// This finds the inner lines of the sector and adds them to the sector polygon
private static void FindInnerLines(EarClipPolygon p, List<LinedefSide> alllines)
{
bool findmore;
float foundangle = 0f;
RectangleF bbox = p.CreateBBox();
do
{
findmore = false;
// Go for all vertices to find the right-most vertex inside the polygon
Vertex foundv = null;
foreach(Vertex v in General.Map.Map.Vertices)
{
// Inside the polygon bounding box?
if(v.Position.x < bbox.Left || v.Position.x > bbox.Right || v.Position.y < bbox.Top || v.Position.y > bbox.Bottom)
continue;
// More to the right?
if((foundv == null) || (v.Position.x >= foundv.Position.x))
{
// Vertex is inside the polygon?
if(p.Intersect(v.Position))
{
// Vertex has lines attached?
if(v.Linedefs.Count > 0)
{
// Go for all lines to see if the vertex is not of the polygon itsself
bool vvalid = true;
foreach(LinedefSide ls in alllines)
{
if((ls.Line.Start == v) || (ls.Line.End == v))
{
vvalid = false;
break;
}
}
// Valid vertex?
if(vvalid) foundv = v;
}
}
}
}
// Found a vertex inside the polygon?
if(foundv != null)
{
// Find the attached linedef with the smallest angle to the right
const float targetangle = Angle2D.PIHALF;
Linedef foundline = null;
foreach(Linedef l in foundv.Linedefs)
{
// We need an angle unrelated to line direction, so correct for that
float lineangle = l.Angle;
if(l.End == foundv) lineangle += Angle2D.PI;
// Better result?
float deltaangle = Angle2D.Difference(targetangle, lineangle);
if((foundline == null) || (deltaangle < foundangle))
{
foundline = l;
foundangle = deltaangle;
}
}
// We already know that each linedef will go from this vertex
// to the left, because this is the right-most vertex in this area.
// If the line would go to the right, that means the other vertex of
// that line must lie outside this area and the mapper made an error.
// Should I check for this error and fail to create a sector in
// that case or ignore it and create a malformed sector (possibly
// breaking another sector also)?
// Find the side at which to start pathfinding
Vector2D testpos = new Vector2D(100.0f, 0.0f);
bool foundlinefront = (foundline.SideOfLine(foundv.Position + testpos) < 0.0f);
// Find inner path
List<LinedefSide> innerlines = FindClosestPath(foundline, foundlinefront, true);
if(innerlines != null)
{
// Make polygon
LinedefTracePath tracepath = new LinedefTracePath(innerlines);
EarClipPolygon innerpoly = tracepath.MakePolygon(true);
// Check if the front of the line is outside the polygon
if(!innerpoly.Intersect(foundline.GetSidePoint(foundlinefront)))
{
// Valid hole found!
alllines.AddRange(innerlines);
p.InsertChild(innerpoly);
findmore = true;
}
}
}
}
// Continue until no more holes found
while(findmore);
}
// This finds the outer lines of the sector as a polygon
// Returns null when no valid outer polygon can be found
private static EarClipPolygon FindOuterLines(Linedef line, bool front, List<LinedefSide> alllines)
{
Linedef scanline = line;
bool scanfront = front;
do
{
// Find closest path
List<LinedefSide> pathlines = FindClosestPath(scanline, scanfront, true);
if(pathlines != null)
{
// Make polygon
LinedefTracePath tracepath = new LinedefTracePath(pathlines);
EarClipPolygon poly = tracepath.MakePolygon(true);
// Check if the front of the line is inside the polygon
if(poly.Intersect(line.GetSidePoint(front)))
{
// Outer lines found!
alllines.AddRange(pathlines);
return poly;
}
else
{
// Inner lines found. This is not what we need, we want the outer lines.
// Find the right-most vertex to start a scan from there towards the outer lines.
Vertex foundv = null;
foreach(LinedefSide ls in pathlines)
{
if((foundv == null) || (ls.Line.Start.Position.x > foundv.Position.x))
foundv = ls.Line.Start;
if((foundv == null) || (ls.Line.End.Position.x > foundv.Position.x))
foundv = ls.Line.End;
}
// If foundv is null then something is horribly wrong with the
// path we received from FindClosestPath!
if(foundv == null) throw new Exception("FAIL!");
// From the right-most vertex trace outward to the right to
// find the next closest linedef, this is based on the idea that
// all sectors are closed.
Vector2D lineoffset = new Vector2D(100.0f, 0.0f);
Line2D testline = new Line2D(foundv.Position, foundv.Position + lineoffset);
scanline = null;
float foundu = float.MaxValue;
float px = foundv.Position.x; //mxd
float py = foundv.Position.y; //mxd
foreach(Linedef ld in General.Map.Map.Linedefs)
{
// Line to the right of start point?
if((ld.Start.Position.x > px) || (ld.End.Position.x > px))
{
// Line intersecting the y axis?
if((ld.Start.Position.y >= py && ld.End.Position.y <= py)
|| (ld.Start.Position.y <= py && ld.End.Position.y >= py)) //mxd
{
// Check if this linedef intersects our test line at a closer range
float thisu;
ld.Line.GetIntersection(testline, out thisu);
if((thisu > 0.00001f) && (thisu < foundu) && !float.IsNaN(thisu))
{
scanline = ld;
foundu = thisu;
}
}
}
}
// Did we meet another line?
if(scanline != null)
{
// Determine on which side we should start the next pathfind
scanfront = (scanline.SideOfLine(foundv.Position) < 0.0f);
}
else
{
// Appearently we reached the end of the map, no sector possible here
return null;
}
}
}
else
{
// Can't find a path
return null;
}
}
while(true);
}
/// <summary>
/// This finds the closest path from the beginning of a line to the end of the line.
/// When turnatends is true, the algorithm will continue at the other side of the
/// line when a dead end has been reached. Returns null when no path could be found.
/// </summary>
public static List<LinedefSide> FindClosestPath(Linedef startline, bool startfront, bool turnatends)
{
return FindClosestPath(startline, startfront, startline, startfront, turnatends);
}
/// <summary>
/// This finds the closest path from the beginning of a line to the end of the line.
/// When turnatends is true, the algorithm will continue at the other side of the
/// line when a dead end has been reached. Returns null when no path could be found.
/// </summary>
public static List<LinedefSide> FindClosestPath(Linedef startline, bool startfront, Linedef endline, bool endfront, bool turnatends)
{
List<LinedefSide> path = new List<LinedefSide>();
Dictionary<Linedef, int> tracecount = new Dictionary<Linedef, int>();
Linedef nextline = startline;
bool nextfront = startfront;
do
{
// Add line to path
path.Add(new LinedefSide(nextline, nextfront));
// Determine next vertex to use
Vertex v = nextfront ? nextline.End : nextline.Start;
// Get list of linedefs and sort by angle
List<Linedef> lines = new List<Linedef>(v.Linedefs);
LinedefAngleSorter sorter = new LinedefAngleSorter(nextline, nextfront, v);
lines.Sort(sorter);
// Source line is the only one?
if(lines.Count == 1)
{
// Are we allowed to trace along this line again?
if(turnatends && (!tracecount.ContainsKey(nextline) || (tracecount[nextline] < 3)))
{
// Turn around and go back along the other side of the line
nextfront = !nextfront;
}
else
{
// No more lines, trace ends here
path = null;
}
}
else
{
// Trace along the next line
Linedef prevline = nextline;
nextline = (lines[0] == nextline ? lines[1] : lines[0]);
//mxd. Try to pick a line with lower tracecount, otherwise we will just walk the same path trise
int curcount = (!tracecount.ContainsKey(nextline) ? 0 : tracecount[nextline]);
//mxd. Don't pick a different line for start and end lines, otherwise the path can go away from it instead of closing the path
//mxd. Also don't pick a different line for marked lines (these are newly drawn lines, and we don't want to skip them)
if(curcount > 0 && !nextline.Marked && nextline != startline && nextline != endline)
{
foreach(Linedef l in lines)
{
if(l != nextline && l != prevline && (!tracecount.ContainsKey(l) || tracecount[l] < curcount))
{
nextline = l;
break;
}
}
}
// Are we allowed to trace this line again?
if(!tracecount.ContainsKey(nextline) || (tracecount[nextline] < 3))
{
// Check if front side changes
if(prevline.Start == nextline.Start || prevline.End == nextline.End)
nextfront = !nextfront;
}
else
{
// No more lines, trace ends here
path = null;
}
}
//mxd. Increase trace count
if(!tracecount.ContainsKey(nextline)) tracecount.Add(nextline, 1); else tracecount[nextline]++;
}
// Continue as long as we have not reached the start yet
// or we have no next line to trace
while((path != null) && ((nextline != endline) || (nextfront != endfront)));
// If start and front are not the same, add the end to the list also
if((path != null) && ((startline != endline) || (startfront != endfront)))
path.Add(new LinedefSide(endline, endfront));
// Return path (null when trace failed)
return path;
}
#endregion
#region ================== Sector Making
// This makes the sector from the given lines and sides
// If nearbylines is not null, then this method will find the default
// properties from the nearest line in this collection when the
// default properties can't be found in the alllines collection.
// Return null when no new sector could be made.
public static Sector MakeSector(List<LinedefSide> alllines, List<Linedef> nearbylines, bool useOverrides)
{
Sector sourcesector = null;
SidedefSettings sourceside = new SidedefSettings();
bool foundsidedefaults = false;
if(General.Map.Map.Sectors.Count >= General.Map.FormatInterface.MaxSectors)
return null;
Sector newsector = General.Map.Map.CreateSector();
if(newsector == null) return null;
// Check if any of the sides already has a sidedef
// Then we use information from that sidedef to make the others
foreach(LinedefSide ls in alllines)
{
if(ls.Front)
{
if(ls.Line.Front != null)
{
// Copy sidedef information if not already found
if(sourcesector == null) sourcesector = ls.Line.Front.Sector;
TakeSidedefSettings(ref sourceside, ls.Line.Front);
foundsidedefaults = true;
break;
}
}
else
{
if(ls.Line.Back != null)
{
// Copy sidedef information if not already found
if(sourcesector == null) sourcesector = ls.Line.Back.Sector;
TakeSidedefSettings(ref sourceside, ls.Line.Back);
foundsidedefaults = true;
break;
}
}
}
// Now do the same for the other sides
// Note how information is only copied when not already found
// so this won't override information from the sides searched above
foreach(LinedefSide ls in alllines)
{
if(ls.Front)
{
if(ls.Line.Back != null)
{
// Copy sidedef information if not already found
if(sourcesector == null) sourcesector = ls.Line.Back.Sector;
TakeSidedefSettings(ref sourceside, ls.Line.Back);
foundsidedefaults = true;
break;
}
}
else
{
if(ls.Line.Front != null)
{
// Copy sidedef information if not already found
if(sourcesector == null) sourcesector = ls.Line.Front.Sector;
TakeSidedefSettings(ref sourceside, ls.Line.Front);
foundsidedefaults = true;
break;
}
}
}
// Use default settings from the nearest linedef, if settings have not been found yet
Sector nearestsector = null; //mxd
if( (nearbylines != null) && (alllines.Count > 0) && (!foundsidedefaults || (sourcesector == null)) )
{
Vector2D testpoint = alllines[0].Line.GetSidePoint(alllines[0].Front);
Linedef nearest = MapSet.NearestLinedef(nearbylines, testpoint);
if(nearest != null)
{
float side = nearest.SideOfLine(testpoint);
Sidedef defaultside = (side < 0.0f ? nearest.Front : nearest.Back);
if(defaultside != null)
{
if(sourcesector == null) sourcesector = defaultside.Sector;
TakeSidedefSettings(ref sourceside, defaultside);
}
else
{
//mxd. Any side is better than no side (but we'll want only basic settings from that)...
defaultside = (side < 0.0f ? nearest.Back : nearest.Front);
if(defaultside != null)
{
TakeSidedefSettings(ref sourceside, defaultside);
nearestsector = defaultside.Sector;
}
}
}
}
// Use defaults where no settings could be found
TakeSidedefDefaults(ref sourceside);
// Found a source sector?
if(sourcesector != null)
{
// Copy properties from source to new sector
sourcesector.CopyPropertiesTo(newsector);
}
else if(nearestsector != null)
{
//mxd. Apply basic properties from the nearest sector
newsector.SetFloorTexture(nearestsector.FloorTexture);
newsector.SetCeilTexture(nearestsector.CeilTexture);
newsector.FloorHeight = nearestsector.FloorHeight;
newsector.CeilHeight = nearestsector.CeilHeight;
newsector.Brightness = nearestsector.Brightness;
}
else
{
// No source sector, apply default sector properties
newsector.SetFloorTexture(General.Map.Options.DefaultFloorTexture);
newsector.SetCeilTexture(General.Map.Options.DefaultCeilingTexture);
newsector.FloorHeight = General.Settings.DefaultFloorHeight;
newsector.CeilHeight = General.Settings.DefaultCeilingHeight;
newsector.Brightness = General.Settings.DefaultBrightness;
}
//mxd. Better any height than none
if(newsector.CeilHeight - newsector.FloorHeight <= 0)
{
newsector.CeilHeight = newsector.FloorHeight + (General.Settings.DefaultCeilingHeight - General.Settings.DefaultFloorHeight);
}
//mxd. Apply overrides?
if(useOverrides)
{
if(General.Map.Options.OverrideCeilingTexture) newsector.SetCeilTexture(General.Map.Options.DefaultCeilingTexture);
if(General.Map.Options.OverrideFloorTexture) newsector.SetFloorTexture(General.Map.Options.DefaultFloorTexture);
if(General.Map.Options.OverrideCeilingHeight) newsector.CeilHeight = General.Map.Options.CustomCeilingHeight;
if(General.Map.Options.OverrideFloorHeight) newsector.FloorHeight = General.Map.Options.CustomFloorHeight;
if(General.Map.Options.OverrideBrightness) newsector.Brightness = General.Map.Options.CustomBrightness;
}
// Go for all sides to make sidedefs
foreach(LinedefSide ls in alllines)
{
// We may only remove a useless middle texture when
// the line was previously singlesided
bool wassinglesided = (ls.Line.Back == null) || (ls.Line.Front == null);
if(ls.Front)
{
// Create sidedef is needed and ensure it points to the new sector
if(ls.Line.Front == null) General.Map.Map.CreateSidedef(ls.Line, true, newsector);
if(ls.Line.Front == null) return null;
if(ls.Line.Front.Sector != newsector) ls.Line.Front.SetSector(newsector);
ApplyDefaultsToSidedef(ls.Line.Front, sourceside);
}
else
{
// Create sidedef is needed and ensure it points to the new sector
if(ls.Line.Back == null) General.Map.Map.CreateSidedef(ls.Line, false, newsector);
if(ls.Line.Back == null) return null;
if(ls.Line.Back.Sector != newsector) ls.Line.Back.SetSector(newsector);
ApplyDefaultsToSidedef(ls.Line.Back, sourceside);
}
// Update line
if(ls.Line.Front != null)ls.Line.Front.RemoveUnneededTextures(wassinglesided, false, wassinglesided);
if(ls.Line.Back != null) ls.Line.Back.RemoveUnneededTextures(wassinglesided, false, wassinglesided);
// Apply single/double sided flags if the double-sided-ness changed
if( (wassinglesided && ((ls.Line.Front != null) && (ls.Line.Back != null))) ||
(!wassinglesided && ((ls.Line.Front == null) || (ls.Line.Back == null))))
ls.Line.ApplySidedFlags();
}
// Return the new sector
return newsector;
}
// This joins a sector with the given lines and sides. Returns null when operation could not be completed.
public static Sector JoinSector(List<LinedefSide> alllines, Sidedef original)
{
SidedefSettings sourceside = new SidedefSettings();
// Take settings fro mthe original side
TakeSidedefSettings(ref sourceside, original);
// Use defaults where no settings could be found
TakeSidedefDefaults(ref sourceside);
// Go for all sides to make sidedefs
foreach(LinedefSide ls in alllines)
{
if(ls.Front)
{
// Create sidedef if needed
if(ls.Line.Front == null)
{
Sidedef sd = General.Map.Map.CreateSidedef(ls.Line, true, original.Sector);
if(sd == null) return null;
ApplyDefaultsToSidedef(ls.Line.Front, sourceside);
ls.Line.ApplySidedFlags();
// We must remove the (now useless) middle texture on the other side
if(ls.Line.Back != null) ls.Line.Back.RemoveUnneededTextures(true, true, true);
}
// Added 23-9-08, can we do this or will it break things?
else if(!original.Sector.IsDisposed) //mxd
{
// Link to the new sector
ls.Line.Front.SetSector(original.Sector);
}
}
else
{
// Create sidedef if needed
if(ls.Line.Back == null)
{
Sidedef sd = General.Map.Map.CreateSidedef(ls.Line, false, original.Sector);
if(sd == null) return null;
ApplyDefaultsToSidedef(ls.Line.Back, sourceside);
ls.Line.ApplySidedFlags();
// We must remove the (now useless) middle texture on the other side
if(ls.Line.Front != null) ls.Line.Front.RemoveUnneededTextures(true, true, true);
}
// Added 23-9-08, can we do this or will it break things?
else if(!original.Sector.IsDisposed) //mxd
{
// Link to the new sector
ls.Line.Back.SetSector(original.Sector);
}
}
}
// Return the new sector
return original.Sector;
}
//mxd. This merges sectors, which have less than 3 sides, with surrounding sectors.
//Most of the logic is taken from MakeSectorsMode.
//Vector2D is sector's center BEFORE sides were removed.
//See VerticesMode.DeleteItem() for usage example
public static void MergeInvalidSectors(Dictionary<Sector, Vector2D> toMerge)
{
foreach(KeyValuePair<Sector, Vector2D> group in toMerge)
{
if(!group.Key.IsDisposed && group.Key.Sidedefs.Count > 0 && group.Key.Sidedefs.Count < 3)
{
group.Key.Dispose();
List<LinedefSide> sides = Tools.FindPotentialSectorAt(group.Value);
if(sides != null)
{
// Mark the lines we are going to use for this sector
General.Map.Map.ClearAllMarks(true);
foreach(LinedefSide ls in sides) ls.Line.Marked = false;
List<Linedef> oldlines = General.Map.Map.GetMarkedLinedefs(true);
// Make the sector
Sector s = Tools.MakeSector(sides, oldlines, false);
if(s != null)
{
// Now we go for all the lines along the sector to
// see if they only have a back side. In that case we want
// to flip the linedef to that it only has a front side.
foreach(Sidedef sd in s.Sidedefs)
{
if((sd.Line.Front == null) && (sd.Line.Back != null))
{
// Flip linedef
sd.Line.FlipVertices();
sd.Line.FlipSidedefs();
}
}
General.Map.Data.UpdateUsedTextures();
}
}
}
}
}
// This takes default settings if not taken yet
private static void TakeSidedefDefaults(ref SidedefSettings settings)
{
// Use defaults where no settings could be found
if(settings.newtexhigh == null) settings.newtexhigh = General.Map.Options.DefaultTopTexture;
if(settings.newtexmid == null) settings.newtexmid = General.Map.Options.DefaultWallTexture;
if(settings.newtexlow == null) settings.newtexlow = General.Map.Options.DefaultBottomTexture;
}
// This takes sidedef settings if not taken yet
private static void TakeSidedefSettings(ref SidedefSettings settings, Sidedef side)
{
if((side.LongHighTexture != MapSet.EmptyLongName) && (settings.newtexhigh == null))
settings.newtexhigh = side.HighTexture;
if((side.LongMiddleTexture != MapSet.EmptyLongName) && (settings.newtexmid == null))
settings.newtexmid = side.MiddleTexture;
if((side.LongLowTexture != MapSet.EmptyLongName) && (settings.newtexlow == null))
settings.newtexlow = side.LowTexture;
}
// This applies defaults to a sidedef
private static void ApplyDefaultsToSidedef(Sidedef sd, SidedefSettings defaults)
{
if(sd.HighRequired() && sd.LongHighTexture == MapSet.EmptyLongName) sd.SetTextureHigh(defaults.newtexhigh); //mxd
if(sd.MiddleRequired() && sd.LongMiddleTexture == MapSet.EmptyLongName) sd.SetTextureMid(defaults.newtexmid); //mxd
if(sd.LowRequired() && sd.LongLowTexture == MapSet.EmptyLongName) sd.SetTextureLow(defaults.newtexlow); //mxd
}
//mxd. This applies overrides to a sidedef
private static void ApplyOverridesToSidedef(Sidedef sd)
{
if(sd.HighRequired() && General.Map.Options.OverrideTopTexture) sd.SetTextureHigh(General.Map.Options.DefaultTopTexture);
if(sd.MiddleRequired() && General.Map.Options.OverrideMiddleTexture) sd.SetTextureMid(General.Map.Options.DefaultWallTexture);
if(sd.LowRequired() && General.Map.Options.OverrideBottomTexture) sd.SetTextureLow(General.Map.Options.DefaultBottomTexture);
}
#endregion
#region ================== Sector Labels
// This finds the ideal label positions for a sector
public static List<LabelPositionInfo> FindLabelPositions(Sector s)
{
List<LabelPositionInfo> positions = new List<LabelPositionInfo>(2);
int islandoffset = 0;
// Do we have a triangulation?
Triangulation triangles = s.Triangles;
if(triangles != null)
{
// Go for all islands
for(int i = 0; i < triangles.IslandVertices.Count; i++)
{
Dictionary<Sidedef, Linedef> sides = new Dictionary<Sidedef, Linedef>(triangles.IslandVertices[i] >> 1);
List<Vector2D> candidatepositions = new List<Vector2D>(triangles.IslandVertices[i] >> 1);
float founddistance = float.MinValue;
Vector2D foundposition = new Vector2D();
float minx = float.MaxValue;
float miny = float.MaxValue;
float maxx = float.MinValue;
float maxy = float.MinValue;
// Make candidate lines that are not along sidedefs
// We do this before testing the candidate against the sidedefs so that
// we can collect the relevant sidedefs first in the same run
for(int t = 0; t < triangles.IslandVertices[i]; t += 3)
{
int triangleoffset = islandoffset + t;
Vector2D v1 = triangles.Vertices[triangleoffset + 2];
Sidedef sd = triangles.Sidedefs[triangleoffset + 2];
for(int v = 0; v < 3; v++)
{
Vector2D v2 = triangles.Vertices[triangleoffset + v];
// Not along a sidedef? Then this line is across the sector
// and guaranteed to be inside the sector!
if(sd == null)
{
// Make the line
candidatepositions.Add(v1 + (v2 - v1) * 0.5f);
}
else
{
// This sidedefs is part of this island and must be checked
// so add it to the dictionary
sides[sd] = sd.Line;
}
// Make bbox of this island
minx = Math.Min(minx, v1.x);
miny = Math.Min(miny, v1.y);
maxx = Math.Max(maxx, v1.x);
maxy = Math.Max(maxy, v1.y);
// Next
sd = triangles.Sidedefs[triangleoffset + v];
v1 = v2;
}
}
// Any candidate lines found at all?
if(candidatepositions.Count > 0)
{
// Start with the first line
foreach(Vector2D candidatepos in candidatepositions)
{
// Check distance against other lines
float smallestdist = int.MaxValue;
foreach(KeyValuePair<Sidedef, Linedef> sd in sides)
{
// Check the distance
float distance = sd.Value.DistanceToSq(candidatepos, true);
smallestdist = Math.Min(smallestdist, distance);
}
// Keep this candidate if it is better than previous
if(smallestdist > founddistance)
{
foundposition = candidatepos;
founddistance = smallestdist;
}
}
// No cceptable line found, just use the first!
positions.Add(new LabelPositionInfo(foundposition, (float)Math.Sqrt(founddistance)));
}
else
{
// No candidate lines found.
// Check to see if the island is a triangle
if(triangles.IslandVertices[i] == 3)
{
// Use the center of the triangle
// TODO: Use the 'incenter' instead, see http://mathworld.wolfram.com/Incenter.html
Vector2D v = (triangles.Vertices[islandoffset] + triangles.Vertices[islandoffset + 1] + triangles.Vertices[islandoffset + 2]) / 3.0f;
float d = Line2D.GetDistanceToLineSq(triangles.Vertices[islandoffset], triangles.Vertices[islandoffset + 1], v, false);
d = Math.Min(d, Line2D.GetDistanceToLineSq(triangles.Vertices[islandoffset + 1], triangles.Vertices[islandoffset + 2], v, false));
d = Math.Min(d, Line2D.GetDistanceToLineSq(triangles.Vertices[islandoffset + 2], triangles.Vertices[islandoffset], v, false));
positions.Add(new LabelPositionInfo(v, (float)Math.Sqrt(d)));
}
else
{
// Use the center of this island.
float d = Math.Min((maxx - minx) * 0.5f, (maxy - miny) * 0.5f);
positions.Add(new LabelPositionInfo(new Vector2D(minx + (maxx - minx) * 0.5f, miny + (maxy - miny) * 0.5f), d));
}
}
// Done with this island
islandoffset += triangles.IslandVertices[i];
}
}
else
{
// No triangulation was made. FAIL!
General.Fail("No triangulation exists for sector " + s + " Triangulation is required to create label positions for a sector.");
}
// Done
return positions;
}
#endregion
#region ================== Drawing
//mxd
public static bool DrawLines(IList<DrawnVertex> points)
{
return DrawLines(points, false, false);
}
/// <summary>
/// This draws lines with the given points. Note that this tool removes any existing geometry
/// marks and marks the new lines and vertices when done. Also marks the sectors that were added.
/// Returns false when the drawing failed.
/// </summary>
public static bool DrawLines(IList<DrawnVertex> points, bool useOverrides, bool autoAlignTextureOffsets)
{
List<Vertex> newverts = new List<Vertex>();
List<Vertex> intersectverts = new List<Vertex>();
List<Linedef> newlines = new List<Linedef>();
List<Linedef> oldlines = new List<Linedef>(General.Map.Map.Linedefs);
List<Sidedef> insidesides = new List<Sidedef>();
List<Vertex> mergeverts = new List<Vertex>();
List<Vertex> nonmergeverts = new List<Vertex>(General.Map.Map.Vertices);
MapSet map = General.Map.Map;
General.Map.Map.ClearAllMarks(false);
// Any points to do?
if(points.Count > 0)
{
/***************************************************\
Create the drawing
\***************************************************/
// Make first vertex
Vertex v1 = map.CreateVertex(points[0].pos);
if(v1 == null) return false;
v1.Marked = true;
// Keep references
newverts.Add(v1);
if(points[0].stitch) mergeverts.Add(v1); else nonmergeverts.Add(v1);
// Go for all other points
for(int i = 1; i < points.Count; i++)
{
// Create vertex for point
Vertex v2 = map.CreateVertex(points[i].pos);
if(v2 == null) return false;
v2.Marked = true;
// Keep references
newverts.Add(v2);
if(points[i].stitch) mergeverts.Add(v2); else nonmergeverts.Add(v2);
// Create line between point and previous
Linedef ld = map.CreateLinedef(v1, v2);
if(ld == null) return false;
ld.Marked = true;
ld.ApplySidedFlags();
ld.UpdateCache();
newlines.Add(ld);
// Should we split this line to merge with intersecting lines?
if(points[i - 1].stitchline && points[i].stitchline)
{
// Check if any other lines intersect this line
List<float> intersections = new List<float>();
Line2D measureline = ld.Line;
HashSet<Linedef> processed = new HashSet<Linedef>(); //mxd
//mxd
foreach(Sector s in map.Sectors)
{
//line intersects with sector's bounding box?
if((MapSet.GetCSFieldBits(measureline.v1, s.BBox) & MapSet.GetCSFieldBits(measureline.v2, s.BBox)) == 0)
{
foreach(Sidedef side in s.Sidedefs)
{
if(processed.Contains(side.Line)) continue;
if(side.Line == ld) continue;
float u;
if(side.Line.Line.GetIntersection(measureline, out u))
{
if(float.IsNaN(u) || (u <= 0.0f) || (u >= 1.0f)) continue;
//mxd. Skip intersection if both start and end of one line are closer than given distance from the other line.
// This allows to avoid creating "unexpected" splits when drawing on top of non-cardinal lines.
//mxd. Check if both ends of measureline are too close to side.Line.Line
bool valid = (side.Line.Line.GetDistanceToLineSq(measureline.v1, true) > MINIMUM_INTERSECTION_DISTANCE ||
side.Line.Line.GetDistanceToLineSq(measureline.v2, true) > MINIMUM_INTERSECTION_DISTANCE);
//mxd. Check if both ends of side.Line.Line are too close to measureline
valid = (valid && (measureline.GetDistanceToLineSq(side.Line.Line.v1, true) > MINIMUM_INTERSECTION_DISTANCE ||
measureline.GetDistanceToLineSq(side.Line.Line.v2, true) > MINIMUM_INTERSECTION_DISTANCE));
// Store inersection
if(valid) intersections.Add(u);
}
processed.Add(side.Line);
}
}
}
// Sort the intersections
intersections.Sort();
// Go for all found intersections
Linedef splitline = ld;
foreach(float u in intersections)
{
// Calculate exact coordinates where to split
// We use measureline for this, because the original line
// may already have changed in length due to a previous split
Vector2D splitpoint = measureline.GetCoordinatesAt(u);
// Make the vertex
Vertex splitvertex = map.CreateVertex(splitpoint);
if(splitvertex == null) return false;
splitvertex.Marked = true;
newverts.Add(splitvertex);
mergeverts.Add(splitvertex); // <-- add to merge?
intersectverts.Add(splitvertex);
// The Split method ties the end of the original line to the given
// vertex and starts a new line at the given vertex, so continue
// splitting with the new line, because the intersections are sorted
// from low to high (beginning at the original line start)
splitline = splitline.Split(splitvertex);
if(splitline == null) return false;
splitline.ApplySidedFlags();
newlines.Add(splitline);
}
}
// Next
v1 = v2;
}
// Join merge vertices so that overlapping vertices in the draw become one.
map.BeginAddRemove();
MapSet.JoinVertices(mergeverts, MapSet.STITCH_DISTANCE); //mxd
map.EndAddRemove();
/***************************************************\
Find a way to close the drawing
\***************************************************/
// We prefer a closed polygon, because then we can determine the interior properly
// Check if the two ends of the polygon are closed
bool splittingonly = false;
bool drawingclosed = false; //mxd
if(newlines.Count > 0)
{
Linedef firstline = newlines[0];
Linedef lastline = newlines[newlines.Count - 1];
drawingclosed = (firstline.Start == lastline.End);
if(!drawingclosed)
{
// When not closed, we will try to find a path to close it.
// But first we check if any of our new lines are inside existing sectors, because
// if they are then we are splitting sectors and cannot accurately find a closed path
// to close our polygon. In that case, we want to do sector splits only.
foreach(Linedef ld in newlines)
{
Vector2D ldcp = ld.GetCenterPoint();
Linedef nld = MapSet.NearestLinedef(oldlines, ldcp);
if(nld != null)
{
float ldside = nld.SideOfLine(ldcp);
if(ldside < 0.0f)
{
if(nld.Front != null)
{
splittingonly = true;
break;
}
}
else if(ldside > 0.0f)
{
if(nld.Back != null)
{
splittingonly = true;
break;
}
}
/*else
{
// We can't tell, so lets ignore this for now.
}*/
}
}
// Not splitting only?
if(!splittingonly)
{
// First and last vertex stitch with geometry?
if(points[0].stitch && points[points.Count - 1].stitch)
{
List<LinedefSide> startpoints = new List<LinedefSide>();
List<LinedefSide> endpoints = new List<LinedefSide>();
// Find out where the start will stitch and create test points
Linedef l1 = MapSet.NearestLinedefRange(oldlines, firstline.Start.Position, MapSet.STITCH_DISTANCE);
Vertex vv1 = null;
if(l1 != null)
{
startpoints.Add(new LinedefSide(l1, true));
startpoints.Add(new LinedefSide(l1, false));
}
else
{
// Not stitched with a linedef, so check if it will stitch with a vertex
vv1 = MapSet.NearestVertexSquareRange(nonmergeverts, firstline.Start.Position, MapSet.STITCH_DISTANCE);
if((vv1 != null) && (vv1.Linedefs.Count > 0))
{
// Now we take the two linedefs with adjacent angles to the drawn line
List<Linedef> lines = new List<Linedef>(vv1.Linedefs);
lines.Sort(new LinedefAngleSorter(firstline, true, firstline.Start));
startpoints.Add(new LinedefSide(lines[0], true));
startpoints.Add(new LinedefSide(lines[0], false));
lines.Sort(new LinedefAngleSorter(firstline, false, firstline.Start));
startpoints.Add(new LinedefSide(lines[0], true));
startpoints.Add(new LinedefSide(lines[0], false));
}
}
// Find out where the end will stitch and create test points
Linedef l2 = MapSet.NearestLinedefRange(oldlines, lastline.End.Position, MapSet.STITCH_DISTANCE);
Vertex vv2 = null;
if(l2 != null)
{
endpoints.Add(new LinedefSide(l2, true));
endpoints.Add(new LinedefSide(l2, false));
}
else
{
// Not stitched with a linedef, so check if it will stitch with a vertex
vv2 = MapSet.NearestVertexSquareRange(nonmergeverts, lastline.End.Position, MapSet.STITCH_DISTANCE);
if((vv2 != null) && (vv2.Linedefs.Count > 0))
{
// Now we take the two linedefs with adjacent angles to the drawn line
List<Linedef> lines = new List<Linedef>(vv2.Linedefs);
lines.Sort(new LinedefAngleSorter(firstline, true, lastline.End));
endpoints.Add(new LinedefSide(lines[0], true));
endpoints.Add(new LinedefSide(lines[0], false));
lines.Sort(new LinedefAngleSorter(firstline, false, lastline.End));
endpoints.Add(new LinedefSide(lines[0], true));
endpoints.Add(new LinedefSide(lines[0], false));
}
}
// Found any start and end points?
if((startpoints.Count > 0) && (endpoints.Count > 0))
{
List<LinedefSide> shortestpath = null;
// Both stitched to the same line?
if((l1 == l2) && (l1 != null))
{
// Then just connect the two
shortestpath = new List<LinedefSide>();
shortestpath.Add(new LinedefSide(l1, true));
}
// One stitched to a line and the other to a vertex of that line?
else if((l1 != null) && (vv2 != null) && ((l1.Start == vv2) || (l1.End == vv2)))
{
// Then just connect the two
shortestpath = new List<LinedefSide>();
shortestpath.Add(new LinedefSide(l1, true));
}
// The other stitched to a line and the first to a vertex of that line?
else if((l2 != null) && (vv1 != null) && ((l2.Start == vv1) || (l2.End == vv1)))
{
// Then just connect the two
shortestpath = new List<LinedefSide>();
shortestpath.Add(new LinedefSide(l2, true));
}
else
{
// Find the shortest, closest path between start and end points
foreach(LinedefSide startp in startpoints)
{
foreach(LinedefSide endp in endpoints)
{
List<LinedefSide> p = Tools.FindClosestPath(startp.Line, startp.Front, endp.Line, endp.Front, true);
if((p != null) && ((shortestpath == null) || (p.Count < shortestpath.Count))) shortestpath = p;
p = Tools.FindClosestPath(endp.Line, endp.Front, startp.Line, startp.Front, true);
if((p != null) && ((shortestpath == null) || (p.Count < shortestpath.Count))) shortestpath = p;
}
}
}
// Found a path?
if(shortestpath != null)
{
// Check which direction the path goes in
bool pathforward = false;
foreach(LinedefSide startp in startpoints)
{
if(shortestpath[0].Line == startp.Line)
{
pathforward = true;
break;
}
}
// TEST
/*
General.Map.Renderer2D.StartOverlay(true);
foreach(LinedefSide lsd in shortestpath)
{
General.Map.Renderer2D.RenderLine(lsd.Line.Start.Position, lsd.Line.End.Position, 2, new PixelColor(255, 0, 255, 0), true);
}
General.Map.Renderer2D.Finish();
General.Map.Renderer2D.Present();
Thread.Sleep(1000);
*/
// Begin at first vertex in path
v1 = (pathforward ? firstline.Start : lastline.End);
// Go for all vertices in the path to make additional lines
for(int i = 1; i < shortestpath.Count; i++)
{
// Get the next position
Vector2D v2pos = shortestpath[i].Front ? shortestpath[i].Line.Start.Position : shortestpath[i].Line.End.Position;
// Make the new vertex
Vertex v2 = map.CreateVertex(v2pos);
if(v2 == null) return false;
v2.Marked = true;
mergeverts.Add(v2);
// Make the line
Linedef ld = map.CreateLinedef(v1, v2);
if(ld == null) return false;
ld.Marked = true;
ld.ApplySidedFlags();
ld.UpdateCache();
newlines.Add(ld);
// Next
v1 = v2;
}
// Make the final line
Linedef lld;
if(pathforward)
lld = map.CreateLinedef(v1, lastline.End);
else
lld = map.CreateLinedef(v1, firstline.Start);
if(lld == null) return false;
// Setup line
lld.Marked = true;
lld.ApplySidedFlags();
lld.UpdateCache();
newlines.Add(lld);
// Drawing is now closed
drawingclosed = true;
// Join merge vertices so that overlapping vertices in the draw become one.
MapSet.JoinVertices(mergeverts, MapSet.STITCH_DISTANCE); //mxd
}
}
}
}
}
}
// Merge intersetion vertices with the new lines. This completes the
// 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);
map.EndAddRemove();
/***************************************************\
Determine drawing interior
\***************************************************/
// In step 3 we will make sectors on the interior sides and join sectors on the
// exterior sides, but because the user could have drawn counterclockwise or just
// some weird polygon. The following code figures out the interior side of all
// new lines.
map.Update(true, false);
foreach(Linedef ld in newlines)
{
// Find closest path starting with the front of this linedef
List<LinedefSide> pathlines = Tools.FindClosestPath(ld, true, true);
if(pathlines != null)
{
// Make polygon
LinedefTracePath tracepath = new LinedefTracePath(pathlines);
EarClipPolygon pathpoly = tracepath.MakePolygon(true);
// Check if the front of the line is outside the polygon
if((pathpoly.CalculateArea() > 0.001f) && !pathpoly.Intersect(ld.GetSidePoint(true)))
{
// Now trace from the back side of the line to see if
// the back side lies in the interior. I don't want to
// flip the line if it is not helping.
// Find closest path starting with the back of this linedef
pathlines = Tools.FindClosestPath(ld, false, true);
if(pathlines != null)
{
// Make polygon
tracepath = new LinedefTracePath(pathlines);
pathpoly = tracepath.MakePolygon(true);
// Check if the front of the line is inside the polygon
ld.FrontInterior = (pathpoly.CalculateArea() < 0.001f) || pathpoly.Intersect(ld.GetSidePoint(true));
}
else
{
ld.FrontInterior = true;
}
}
else
{
ld.FrontInterior = true;
}
}
else
{
ld.FrontInterior = true;
}
}
/***************************************************\
Merge the new geometry
\***************************************************/
// Mark only the vertices that should be merged
map.ClearMarkedVertices(false);
foreach(Vertex v in mergeverts) v.Marked = true;
// 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.Update(true, false);
// Find our new lines again, because they have been merged with the other geometry
// but their Marked property is copied where they have joined.
newlines = map.GetMarkedLinedefs(true);
// Remove any disposed old lines
List<Linedef> prevoldlines = oldlines;
oldlines = new List<Linedef>(prevoldlines.Count);
foreach(Linedef ld in prevoldlines)
if(!ld.IsDisposed) oldlines.Add(ld);
/***************************************************\
Join and create new sectors
\***************************************************/
// The code below atempts to create sectors on the interior sides of the drawn
// geometry and joins sectors on the other sides of the drawn geometry.
// This code does not change any geometry, it only makes/updates sidedefs.
bool sidescreated = false;
bool[] frontsdone = new bool[newlines.Count];
bool[] backsdone = new bool[newlines.Count];
for(int i = 0; i < newlines.Count; i++)
{
Linedef ld = newlines[i];
// Interior not done yet?
if((ld.FrontInterior && !frontsdone[i]) || (!ld.FrontInterior && !backsdone[i]))
{
// Find a way to create a sector here
List<LinedefSide> sectorlines = Tools.FindPotentialSectorAt(ld, ld.FrontInterior);
if(sectorlines != null)
{
sidescreated = true;
// When none of the linedef sides exist yet, this is a true new
// sector that will be created out of the void!
bool istruenewsector = true;
foreach(LinedefSide ls in sectorlines)
{
if((ls.Front && (ls.Line.Front != null)) ||
(!ls.Front && (ls.Line.Back != null)))
{
istruenewsector = false;
break;
}
}
// But we don't want to create sectors out of the void when we
// decided that we only want to split sectors.
if(!istruenewsector || !splittingonly)
{
// Make the new sector
//mxd. Apply sector overrides only if a closed drawing is created
Sector newsector = Tools.MakeSector(sectorlines, oldlines, (useOverrides && drawingclosed && newlines.Count > 2));
if(newsector == null) return false;
if(istruenewsector) newsector.Marked = true;
// Go for all sidedefs in this new sector
foreach(Sidedef sd in newsector.Sidedefs)
{
// Keep list of sides inside created sectors
insidesides.Add(sd);
// Side matches with a side of our new lines?
int lineindex = newlines.IndexOf(sd.Line);
if(lineindex > -1)
{
// Mark this side as done
if(sd.IsFront)
frontsdone[lineindex] = true;
else
backsdone[lineindex] = true;
}
}
}
}
}
// Exterior not done yet?
if((ld.FrontInterior && !backsdone[i]) || (!ld.FrontInterior && !frontsdone[i]))
{
// Find a way to create a sector here
List<LinedefSide> sectorlines = Tools.FindPotentialSectorAt(ld, !ld.FrontInterior);
if(sectorlines != null)
{
// Check if any of the surrounding lines originally have sidedefs we can join
Sidedef joinsidedef = null;
foreach(LinedefSide ls in sectorlines)
{
if(ls.Front && (ls.Line.Front != null))
{
joinsidedef = ls.Line.Front;
break;
}
if(!ls.Front && (ls.Line.Back != null))
{
joinsidedef = ls.Line.Back;
break;
}
}
// Join?
if(joinsidedef != null)
{
sidescreated = true;
// We only want to modify our new lines when joining a sector
// (or it may break nearby self-referencing sectors)
List<LinedefSide> newsectorlines = new List<LinedefSide>(sectorlines.Count);
foreach(LinedefSide sd in sectorlines)
{
// Side matches with a side of our new lines?
int lineindex = newlines.IndexOf(sd.Line);
if(lineindex > -1)
{
// Add to list
newsectorlines.Add(sd);
// Mark this side as done
if(sd.Front)
frontsdone[lineindex] = true;
else
backsdone[lineindex] = true;
}
}
// Have our new lines join the existing sector
if(Tools.JoinSector(newsectorlines, joinsidedef) == null)
return false;
}
}
}
}
/***************************************************\
Corrections and clean up
\***************************************************/
// Make corrections for backward linedefs
MapSet.FlipBackwardLinedefs(newlines);
// Check if any of our new lines have sides
if(sidescreated)
{
// Then remove the lines which have no sides at all
for(int i = newlines.Count - 1; i >= 0; i--)
{
// Remove the line if it has no sides
if((newlines[i].Front != null) || (newlines[i].Back != null)) continue;
newlines[i].Dispose();
}
//mxd. Apply texture overrides
if(useOverrides)
{
// If new sectors are created, apply overrides to the sides of these sectors, otherwise, apply overrides to all new lines
if(insidesides.Count > 0)
{
foreach(Sidedef side in insidesides) ApplyOverridesToSidedef(side);
}
else
{
foreach(Linedef l in newlines)
{
if(l.IsDisposed) continue;
if(!newverts.Contains(l.Start) || !newverts.Contains(l.End)) continue;
ApplyOverridesToSidedef(l.Front);
if(l.Back != null) ApplyOverridesToSidedef(l.Back);
}
}
}
//mxd. Auto-align new lines
if(autoAlignTextureOffsets && newlines.Count > 1 && !splittingonly)
{
List<List<Linedef>> strips = new List<List<Linedef>>();
strips.Add(new List<Linedef> { newlines[0] });
for(int i = 1; i < newlines.Count; i++)
{
//skip double-sided line if it doesn't have lower or upper parts or they are not part of newly created sectors
if(newlines[i].Back != null
&& (((!newlines[i].Front.LowRequired() && !newlines[i].Front.HighRequired()) || !insidesides.Contains(newlines[i].Front))
&& ((!newlines[i].Back.LowRequired() && !newlines[i].Back.HighRequired()) || !insidesides.Contains(newlines[i].Back))))
continue;
bool added = false;
foreach(List<Linedef> strip in strips)
{
if(newlines[i].Start == strip[0].Start || newlines[i].End == strip[0].Start)
{
strip.Insert(0, newlines[i]);
added = true;
break;
}
if(newlines[i].Start == strip[strip.Count - 1].End || newlines[i].End == strip[strip.Count - 1].End)
{
strip.Add(newlines[i]);
added = true;
break;
}
}
if(!added) strips.Add(new List<Linedef> { newlines[i] });
}
foreach(List<Linedef> strip in strips)
{
if(strip.Count < 2) continue;
AutoAlignLinedefStrip(strip);
}
}
}
// Mark new geometry only
General.Map.Map.ClearMarkedLinedefs(false);
General.Map.Map.ClearMarkedVertices(false);
foreach(Vertex v in newverts) v.Marked = true;
foreach(Linedef l in newlines) l.Marked = true;
}
return true;
}
//mxd
private static void AutoAlignLinedefStrip(List<Linedef> strip)
{
if(strip.Count < 2) return;
float totalLength = 0f;
foreach(Linedef l in strip) totalLength += l.Length;
if(General.Map.UDMF && General.Map.Config.UseLocalSidedefTextureOffsets)
AutoAlignTexturesOnSidesUdmf(strip, totalLength, (strip[0].End != strip[1].Start));
else
AutoAlignTexturesOnSides(strip, totalLength, (strip[0].End != strip[1].Start));
}
//mxd
private static void AutoAlignTexturesOnSides(List<Linedef> lines, float totalLength, bool reversed)
{
float curLength = 0f;
foreach(Linedef l in lines)
{
if(l.Front != null)
{
ImageData texture = null;
if(l.Front.MiddleRequired() && l.Front.LongMiddleTexture != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(l.Front.LongMiddleTexture))
texture = General.Map.Data.GetTextureImage(l.Front.LongMiddleTexture);
else if(l.Front.HighRequired() && l.Front.LongHighTexture != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(l.Front.LongHighTexture))
texture = General.Map.Data.GetTextureImage(l.Front.LongHighTexture);
else if(l.Front.LowRequired() && l.Front.LongLowTexture != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(l.Front.LongLowTexture))
texture = General.Map.Data.GetTextureImage(l.Front.LongLowTexture);
if(texture != null && texture.IsImageLoaded)
l.Front.OffsetX = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength)) % texture.Width;
}
if(l.Back != null)
{
ImageData texture = null;
if(l.Back.MiddleRequired() && l.Back.LongMiddleTexture != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(l.Back.LongMiddleTexture))
texture = General.Map.Data.GetTextureImage(l.Back.LongMiddleTexture);
else if(l.Back.HighRequired() && l.Back.LongHighTexture != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(l.Back.LongHighTexture))
texture = General.Map.Data.GetTextureImage(l.Back.LongHighTexture);
else if(l.Back.LowRequired() && l.Back.LongLowTexture != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(l.Back.LongLowTexture))
texture = General.Map.Data.GetTextureImage(l.Back.LongLowTexture);
if(texture != null && texture.IsImageLoaded)
l.Back.OffsetX = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength)) % texture.Width;
}
curLength += l.Length;
}
}
//mxd
private static void AutoAlignTexturesOnSidesUdmf(List<Linedef> lines, float totalLength, bool reversed)
{
float curLength = 0f;
foreach(Linedef l in lines)
{
if(l.Front != null)
{
if(l.Front.MiddleRequired() && l.Front.LongMiddleTexture != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(l.Front.LongMiddleTexture))
{
ImageData texture = General.Map.Data.GetTextureImage(l.Front.LongMiddleTexture);
float offset = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength));
if(texture.IsImageLoaded) offset %= texture.Width;
if(offset > 0) UniFields.SetFloat(l.Front.Fields, "offsetx_mid", offset);
}
if(l.Front.HighRequired() && l.Front.LongHighTexture != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(l.Front.LongHighTexture))
{
ImageData texture = General.Map.Data.GetTextureImage(l.Front.LongHighTexture);
float offset = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength));
if(texture.IsImageLoaded) offset %= texture.Width;
if(offset > 0) UniFields.SetFloat(l.Front.Fields, "offsetx_top", offset);
}
if(l.Front.LowRequired() && l.Front.LongLowTexture != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(l.Front.LongLowTexture))
{
ImageData texture = General.Map.Data.GetTextureImage(l.Front.LongLowTexture);
float offset = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength));
if(texture.IsImageLoaded) offset %= texture.Width;
if(offset > 0) UniFields.SetFloat(l.Front.Fields, "offsetx_bottom", offset);
}
}
if(l.Back != null)
{
if(l.Back.MiddleRequired() && l.Back.LongMiddleTexture != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(l.Back.LongMiddleTexture))
{
ImageData texture = General.Map.Data.GetTextureImage(l.Back.LongMiddleTexture);
float offset = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength));
if(texture.IsImageLoaded) offset %= texture.Width;
if(offset > 0) UniFields.SetFloat(l.Back.Fields, "offsetx_mid", offset);
}
if(l.Back.HighRequired() && l.Back.LongHighTexture != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(l.Back.LongHighTexture))
{
ImageData texture = General.Map.Data.GetTextureImage(l.Back.LongHighTexture);
float offset = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength));
if(texture.IsImageLoaded) offset %= texture.Width;
if(offset > 0) UniFields.SetFloat(l.Back.Fields, "offsetx_top", offset);
}
if(l.Back.LowRequired() && l.Back.LongLowTexture != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(l.Back.LongLowTexture))
{
ImageData texture = General.Map.Data.GetTextureImage(l.Back.LongLowTexture);
float offset = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength));
if(texture.IsImageLoaded) offset %= texture.Width;
if(offset > 0) UniFields.SetFloat(l.Back.Fields, "offsetx_bottom", offset);
}
}
curLength += l.Length;
}
}
#endregion
#region ================== Flat Floodfill
// This performs flat floodfill over sector floors or ceilings that match with the same flat
// NOTE: This method uses the sectors marking to indicate which sides have been filled
// When resetsectormarks is set to true, all sectors will first be marked false (not aligned).
// Setting resetsectormarks to false is usefull to fill only within a specific selection
// (set the marked property to true for the sectors outside the selection)
public static void FloodfillFlats(Sector start, bool fillceilings, HashSet<long> originalflats, string fillflat, bool resetsectormarks)
{
Stack<Sector> todo = new Stack<Sector>(50);
// Mark all sectors false (they will be marked true when the flat is modified)
if(resetsectormarks) General.Map.Map.ClearMarkedSectors(false);
// Begin with first sector
if((originalflats.Contains(start.LongFloorTexture) && !fillceilings) ||
(originalflats.Contains(start.LongCeilTexture) && fillceilings))
{
todo.Push(start);
}
// Continue until nothing more to align
while(todo.Count > 0)
{
// Get the sector to do
Sector s = todo.Pop();
// Apply new flat
if(fillceilings) s.SetCeilTexture(fillflat);
else s.SetFloorTexture(fillflat);
s.Marked = true;
// Go for all sidedefs to add neighbouring sectors
foreach(Sidedef sd in s.Sidedefs)
{
// Sector on the other side of the line that we haven't checked yet?
if((sd.Other != null) && !sd.Other.Sector.Marked)
{
Sector os = sd.Other.Sector;
// Check if texture matches
if((originalflats.Contains(os.LongFloorTexture) && !fillceilings) ||
(originalflats.Contains(os.LongCeilTexture) && fillceilings))
{
todo.Push(os);
}
}
}
}
}
#endregion
#region ================== Texture Floodfill
// This performs texture floodfill along all walls that match with the same texture
// NOTE: This method uses the sidedefs marking to indicate which sides have been filled
// When resetsidemarks is set to true, all sidedefs will first be marked false (not aligned).
// Setting resetsidemarks to false is usefull to fill only within a specific selection
// (set the marked property to true for the sidedefs outside the selection)
public static void FloodfillTextures(Sidedef start, HashSet<long> originaltextures, string filltexture, bool resetsidemarks)
{
Stack<SidedefFillJob> todo = new Stack<SidedefFillJob>(50);
// Mark all sidedefs false (they will be marked true when the texture is aligned)
if(resetsidemarks) General.Map.Map.ClearMarkedSidedefs(false);
// Begin with first sidedef
if(SidedefTextureMatch(start, originaltextures))
{
SidedefFillJob first = new SidedefFillJob();
first.sidedef = start;
first.forward = true;
todo.Push(first);
}
// Continue until nothing more to align
while(todo.Count > 0)
{
// Get the align job to do
SidedefFillJob j = todo.Pop();
// Apply texturing
if(j.sidedef.HighRequired() && originaltextures.Contains(j.sidedef.LongHighTexture)) j.sidedef.SetTextureHigh(filltexture);
if((j.sidedef.LongMiddleTexture != MapSet.EmptyLongName || j.sidedef.MiddleRequired()) &&
originaltextures.Contains(j.sidedef.LongMiddleTexture)) j.sidedef.SetTextureMid(filltexture);
if(j.sidedef.LowRequired() && originaltextures.Contains(j.sidedef.LongLowTexture)) j.sidedef.SetTextureLow(filltexture);
j.sidedef.Marked = true;
if(j.forward)
{
// Add sidedefs forward (connected to the right vertex)
Vertex v = j.sidedef.IsFront ? j.sidedef.Line.End : j.sidedef.Line.Start;
AddSidedefsForFloodfill(todo, v, true, originaltextures);
// Add sidedefs backward (connected to the left vertex)
v = j.sidedef.IsFront ? j.sidedef.Line.Start : j.sidedef.Line.End;
AddSidedefsForFloodfill(todo, v, false, originaltextures);
}
else
{
// Add sidedefs backward (connected to the left vertex)
Vertex v = j.sidedef.IsFront ? j.sidedef.Line.Start : j.sidedef.Line.End;
AddSidedefsForFloodfill(todo, v, false, originaltextures);
// Add sidedefs forward (connected to the right vertex)
v = j.sidedef.IsFront ? j.sidedef.Line.End : j.sidedef.Line.Start;
AddSidedefsForFloodfill(todo, v, true, originaltextures);
}
}
}
// This adds the matching, unmarked sidedefs from a vertex for texture alignment
private static void AddSidedefsForFloodfill(Stack<SidedefFillJob> stack, Vertex v, bool forward, HashSet<long> texturelongnames)
{
foreach(Linedef ld in v.Linedefs)
{
Sidedef side1 = forward ? ld.Front : ld.Back;
Sidedef side2 = forward ? ld.Back : ld.Front;
if((ld.Start == v) && (side1 != null) && !side1.Marked)
{
if(SidedefTextureMatch(side1, texturelongnames))
{
SidedefFillJob nj = new SidedefFillJob();
nj.forward = forward;
nj.sidedef = side1;
stack.Push(nj);
}
}
else if((ld.End == v) && (side2 != null) && !side2.Marked)
{
if(SidedefTextureMatch(side2, texturelongnames))
{
SidedefFillJob nj = new SidedefFillJob();
nj.forward = forward;
nj.sidedef = side2;
stack.Push(nj);
}
}
}
}
#endregion
#region ================== Texture Alignment
// This checks if any of the sidedef texture match the given texture
/*public static bool SidedefTextureMatch(Sidedef sd, long texturelongname)
{
return ((sd.LongHighTexture == texturelongname) && sd.HighRequired()) ||
((sd.LongLowTexture == texturelongname) && sd.LowRequired()) ||
((sd.LongMiddleTexture == texturelongname) && (sd.MiddleRequired() || sd.LongMiddleTexture != MapSet.EmptyLongName)) ;
}*/
//mxd. This checks if any of the sidedef texture match the given textures
public static bool SidedefTextureMatch(Sidedef sd, HashSet<long> texturelongnames)
{
return (texturelongnames.Contains(sd.LongHighTexture) && sd.HighRequired()) ||
(texturelongnames.Contains(sd.LongLowTexture) && sd.LowRequired()) ||
(texturelongnames.Contains(sd.LongMiddleTexture) && (sd.MiddleRequired() || sd.LongMiddleTexture != MapSet.EmptyLongName));
}
//mxd. This converts offsetY from/to "normalized" offset for given wall part
public static float GetSidedefOffsetY(Sidedef side, VisualGeometryType part, float offset, float scaleY, bool fromNormalized)
{
switch(part)
{
case VisualGeometryType.WALL_UPPER:
return GetSidedefTopOffsetY(side, offset, scaleY, fromNormalized);
case VisualGeometryType.WALL_MIDDLE:
case VisualGeometryType.WALL_MIDDLE_3D:
return GetSidedefMiddleOffsetY(side, offset, scaleY, fromNormalized);
case VisualGeometryType.WALL_LOWER:
return GetSidedefBottomOffsetY(side, offset, scaleY, fromNormalized);
default:
throw new NotSupportedException("Tools.GetSidedefOffsetY: \"" + part + "\" geometry type is not supported!");
}
}
//mxd. This converts offsetY from/to "normalized" offset for given upper wall
public static float GetSidedefTopOffsetY(Sidedef side, float offset, float scaleY, bool fromNormalized)
{
if(side.Line.IsFlagSet(General.Map.Config.UpperUnpeggedFlag) || side.Other == null || side.Other.Sector == null)
return offset;
//if we don't have UpperUnpegged flag, normalize offset
float surfaceHeight = side.GetHighHeight() * scaleY;
return (float)Math.Round((fromNormalized ? offset + surfaceHeight : offset - surfaceHeight), General.Map.FormatInterface.VertexDecimals);
}
//mxd. This converts offsetY from/to "normalized" offset for given middle wall
public static float GetSidedefMiddleOffsetY(Sidedef side, float offset, float scaleY, bool fromNormalized)
{
if(side.Sector == null) return offset;
// Normalize offset
float surfaceHeight;
if(side.Other != null && side.Other.Sector != null)
{
if(side.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag))
{
// Double-sided with LowerUnpeggedFlag set
surfaceHeight = (side.Sector.CeilHeight - Math.Max(side.Sector.FloorHeight, side.Other.Sector.FloorHeight)) * scaleY;
}
else
{
// Double-sided without LowerUnpeggedFlag
surfaceHeight = Math.Abs(side.Sector.CeilHeight - side.Other.Sector.CeilHeight) * scaleY;
}
}
else
{
if(side.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag))
{
// Single-sided with LowerUnpeggedFlag set
// Absolute value is used because ceiling height of vavoom-type 3d floors
// is lower than floor height
surfaceHeight = (Math.Abs(side.Sector.CeilHeight - side.Sector.FloorHeight)) * scaleY;
}
else
{
// Single-sided without LowerUnpeggedFlag
return offset;
}
}
return (float)Math.Round((fromNormalized ? offset + surfaceHeight : offset - surfaceHeight), General.Map.FormatInterface.VertexDecimals);
}
//mxd. This converts offsetY from/to "normalized" offset for given lower wall
public static float GetSidedefBottomOffsetY(Sidedef side, float offset, float scaleY, bool fromNormalized)
{
float surfaceHeight;
if(side.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag))
{
if(side.Other == null || side.Other.Sector == null || side.Sector.CeilTexture != General.Map.Config.SkyFlatName ||
side.Other.Sector.CeilTexture != General.Map.Config.SkyFlatName)
return offset;
//normalize offset the way Doom does it when front and back sector's ceiling is sky
surfaceHeight = (side.Sector.CeilHeight - side.Other.Sector.CeilHeight) * scaleY;
}
else
{
//normalize offset
surfaceHeight = (side.Sector.CeilHeight - side.Other.Sector.FloorHeight) * scaleY;
}
return (float)Math.Round((fromNormalized ? offset + surfaceHeight : offset - surfaceHeight), General.Map.FormatInterface.VertexDecimals);
}
#endregion
#region ================== Tags and Actions
/// <summary>
/// This removes all tags on the marked geometry.
/// </summary>
public static void RemoveMarkedTags()
{
General.Map.Map.ForAllTags<object>(RemoveTagHandler, true, null);
}
// This removes tags
private static void RemoveTagHandler(MapElement element, bool actionargument, UniversalType type, ref int value, object obj)
{
value = 0;
}
/// <summary>
/// This renumbers all tags on the marked geometry.
/// </summary>
public static void RenumberMarkedTags()
{
Dictionary<int, int> tagsmap = new Dictionary<int, int>();
// Collect the tag numbers used in the marked geometry
General.Map.Map.ForAllTags(CollectTagNumbersHandler, true, tagsmap);
// Get new tags that are unique within unmarked geometry
List<int> newtags = General.Map.Map.GetMultipleNewTags(tagsmap.Count, false);
// Map the old tags with the new tags
int index = 0;
List<int> oldkeys = new List<int>(tagsmap.Keys);
foreach(int ot in oldkeys) tagsmap[ot] = newtags[index++];
// Now renumber the old tags with the new ones
General.Map.Map.ForAllTags(RenumberTagsHandler, true, tagsmap);
}
// This collects tags in a dictionary
private static void CollectTagNumbersHandler(MapElement element, bool actionargument, UniversalType type, ref int value, Dictionary<int, int> tagsmap)
{
if(value != 0)
tagsmap[value] = value;
}
// This remaps tags from a dictionary
private static void RenumberTagsHandler(MapElement element, bool actionargument, UniversalType type, ref int value, Dictionary<int, int> tagsmap)
{
if(value != 0)
value = tagsmap[value];
}
/// <summary>
/// This removes all actions on the marked geometry.
/// </summary>
public static void RemoveMarkedActions()
{
// Remove actions from things
foreach(Thing t in General.Map.Map.Things)
{
if(t.Marked)
{
t.Action = 0;
for(int i = 0; i < Thing.NUM_ARGS; i++) t.Args[i] = 0;
}
}
// Remove actions from linedefs
foreach(Linedef l in General.Map.Map.Linedefs)
{
if(l.Marked)
{
l.Action = 0;
for(int i = 0; i < Linedef.NUM_ARGS; i++) l.Args[i] = 0;
}
}
}
#endregion
#region ================== Things (mxd)
public static bool TryAlignThingToLine(Thing t, Linedef l)
{
if(l.Back == null)
{
if(CanAlignThingTo(t, l.Front.Sector))
{
AlignThingToLine(t, l, true);
return true;
}
return false;
}
if(l.Front == null )
{
if(CanAlignThingTo(t, l.Back.Sector))
{
AlignThingToLine(t, l, false);
return true;
}
return false;
}
float side = l.SideOfLine(t.Position);
//already on line
if(side == 0)
{
t.Rotate(General.ClampAngle(180 + l.AngleDeg));
return true;
}
//thing is on front side of the line
if(side < 0)
{
//got any walls to align to?
if((l.Front.LongMiddleTexture != MapSet.EmptyLongName && CanAlignThingTo(t, l.Front.Sector))
|| CanAlignThingTo(t, l.Front.Sector, l.Back.Sector))
{
AlignThingToLine(t, l, true);
return true;
}
return false;
}
//thing is on back side of the line
//got any walls to align to?
if((l.Back.LongMiddleTexture != MapSet.EmptyLongName && CanAlignThingTo(t, l.Back.Sector))
|| CanAlignThingTo(t, l.Back.Sector, l.Front.Sector))
{
AlignThingToLine(t, l, false);
return true;
}
return false;
}
// Checks if there's a wall at appropriate height to align thing to
private static bool CanAlignThingTo(Thing t, Sector front, Sector back)
{
ThingTypeInfo ti = General.Map.Data.GetThingInfo(t.Type);
int absz = GetThingAbsoluteZ(t, ti);
int height = ti.Height == 0 ? 1 : (int)ti.Height;
Rectangle thing = new Rectangle(0, ti.Hangs ? absz - height : absz, 1, height);
if(front.FloorHeight < back.FloorHeight)
{
Rectangle lower = new Rectangle(0, front.FloorHeight, 1, back.FloorHeight - front.FloorHeight);
if(thing.IntersectsWith(lower)) return true;
}
if(front.CeilHeight > back.CeilHeight)
{
Rectangle upper = new Rectangle(0, back.CeilHeight, 1, front.CeilHeight - back.CeilHeight);
if(thing.IntersectsWith(upper)) return true;
}
return false;
}
// Checks if there's a wall at appropriate height to align thing to
private static bool CanAlignThingTo(Thing t, Sector sector)
{
ThingTypeInfo ti = General.Map.Data.GetThingInfo(t.Type);
int absz = GetThingAbsoluteZ(t, ti);
Rectangle thing = new Rectangle(0, absz, 1, ti.Height == 0 ? 1 : (int)ti.Height);
Rectangle middle = new Rectangle(0, sector.FloorHeight, 1, sector.CeilHeight - sector.FloorHeight);
return thing.IntersectsWith(middle);
}
private static void AlignThingToLine(Thing t, Linedef l, bool front)
{
//get aligned position
Vector2D pos = l.NearestOnLine(t.Position);
Sector initialSector = t.Sector;
//add a small offset so we don't end up moving thing into void
if(front)
t.Move(new Vector2D(pos.x - (float)Math.Cos(l.Angle), pos.y - (float)Math.Sin(l.Angle)));
else
t.Move(new Vector2D(pos.x + (float)Math.Cos(l.Angle), pos.y + (float)Math.Sin(l.Angle)));
//apply new settings
t.SnapToAccuracy();
t.DetermineSector();
t.Rotate(General.ClampAngle(front ? 180 + l.AngleDeg : l.AngleDeg));
//keep thing height constant
if(initialSector != t.Sector && General.Map.FormatInterface.HasThingHeight)
{
ThingTypeInfo ti = General.Map.Data.GetThingInfo(t.Type);
if(ti.AbsoluteZ) return;
if(ti.Hangs && initialSector.CeilHeight != t.Sector.CeilHeight)
{
t.Move(t.Position.x, t.Position.y, t.Position.z - (initialSector.CeilHeight - t.Sector.CeilHeight));
return;
}
if(initialSector.FloorHeight != t.Sector.FloorHeight)
t.Move(t.Position.x, t.Position.y, t.Position.z + (initialSector.FloorHeight - t.Sector.FloorHeight));
}
}
public static int GetThingAbsoluteZ(Thing t, ThingTypeInfo ti)
{
// Determine z info
if(ti.AbsoluteZ) return (int)t.Position.z;
if(t.Sector != null)
{
// Hangs from ceiling?
if(ti.Hangs) return (int)(t.Sector.CeilHeight - t.Position.z - ti.Height);
return (int)(t.Sector.FloorHeight + t.Position.z);
}
return (int)t.Position.z;
}
#endregion
#region ================== Sectors (mxd)
public static void SplitOuterSectors(IEnumerable<Linedef> drawnlines)
{
Dictionary<Sector, HashSet<Sidedef>> sectorsidesref = new Dictionary<Sector, HashSet<Sidedef>>();
HashSet<Sidedef> drawnsides = new HashSet<Sidedef>();
// Create drawn lines per sector collection
foreach(Linedef l in drawnlines)
{
if(l.Front != null && (l.Front.Sector != null && !SectorWasInvalid(l.Front.Sector)))
{
if(!sectorsidesref.ContainsKey(l.Front.Sector)) sectorsidesref.Add(l.Front.Sector, new HashSet<Sidedef>());
sectorsidesref[l.Front.Sector].Add(l.Front);
drawnsides.Add(l.Front);
}
if(l.Back != null && (l.Back.Sector != null && !SectorWasInvalid(l.Back.Sector)))
{
if(!sectorsidesref.ContainsKey(l.Back.Sector)) sectorsidesref.Add(l.Back.Sector, new HashSet<Sidedef>());
sectorsidesref[l.Back.Sector].Add(l.Back);
drawnsides.Add(l.Back);
}
}
// Split sectors
foreach(KeyValuePair<Sector, HashSet<Sidedef>> group in sectorsidesref)
{
// Sector has all sides selected?
if(group.Key.Sidedefs.Count == group.Value.Count)
{
group.Key.Marked = true; // Sometimes those are not marked...
continue;
}
// Process all sides
foreach(Sidedef side in group.Value)
{
// Sector was already split?
if(side.Sector != group.Key) continue;
// Find drawing interior
List<LinedefSide> linedefsides = FindPotentialSectorAt(side.Line, side.IsFront);
// Number of potential sides fewer than the sector has?
if(linedefsides != null && linedefsides.Count > 0 && linedefsides.Count < group.Key.Sidedefs.Count)
{
// Collect sidedefs from new sector shape...
HashSet<Sidedef> newsectorsides = new HashSet<Sidedef>();
foreach(LinedefSide ls in linedefsides)
{
Sidedef s = (ls.Front ? ls.Line.Front : ls.Line.Back);
if(s != null) newsectorsides.Add(s);
}
// Make new sector only if one of the remaining sector sides was also drawn...
foreach(Sidedef s in group.Key.Sidedefs)
{
if(newsectorsides.Contains(s)) continue;
if(drawnsides.Contains(s))
{
Sector newsector = MakeSector(linedefsides, null, false);
if(newsector != null)
{
newsector.UpdateCache();
group.Key.UpdateCache();
}
// Existing sector may've become invalid
SectorWasInvalid(group.Key);
break;
}
}
}
}
}
}
private static bool SectorWasInvalid(Sector s)
{
if(s.Sidedefs == null || s.Sidedefs.Count < 3 || s.FlatVertices.Length < 3)
{
// Collect changed lines
HashSet<Linedef> changedlines = new HashSet<Linedef>();
if(s.Sidedefs != null)
{
foreach(Sidedef side in s.Sidedefs) changedlines.Add(side.Line);
}
// Delete sector
s.Dispose();
// Correct lines
foreach(Linedef l in changedlines)
{
l.ApplySidedFlags();
if(l.Front == null)
{
l.FlipVertices();
l.FlipSidedefs();
}
}
return true;
}
return false;
}
#endregion
#region ================== Linedefs (mxd)
/// <summary>Flips sector linedefs so they all face either inward or outward.</summary>
public static void FlipSectorLinedefs(ICollection<Sector> sectors, bool selectedlinesonly)
{
HashSet<Linedef> processed = new HashSet<Linedef>();
foreach(Sector s in sectors)
{
List<Linedef> frontlines = new List<Linedef>();
List<Linedef> backlines = new List<Linedef>();
int unselectedfrontlines = 0;
int unselectedbacklines = 0;
//sort lines
foreach(Sidedef side in s.Sidedefs)
{
if(processed.Contains(side.Line)) continue;
if(selectedlinesonly && !side.Line.Selected)
{
if(side == side.Line.Front) unselectedfrontlines++;
else unselectedbacklines++;
continue;
}
if(side == side.Line.Front)
frontlines.Add(side.Line);
else
backlines.Add(side.Line);
processed.Add(side.Line);
}
//flip lines
if(frontlines.Count == 0 || (frontlines.Count + unselectedfrontlines > backlines.Count + unselectedbacklines && backlines.Count > 0))
{
foreach(Linedef l in backlines)
{
l.FlipVertices();
l.FlipSidedefs();
}
}
else
{
foreach(Linedef l in frontlines)
{
// Skip single-sided lines with only front side
if(l.Back != null)
{
l.FlipVertices();
l.FlipSidedefs();
}
}
}
}
}
#endregion
#region ================== Sidedefs (mxd)
/// <summary>Updates the 'lightfog' UDMF flag to display sidedef brightness on fogged walls. Returns 1 if flag was added, -1 if it was removed, 0 if flag wasn't changed</summary>
public static int UpdateLightFogFlag(Sidedef side)
{
//Side requires the flag?
if(side.Sector == null) return 0;
if(!side.Fields.ContainsKey("light"))
{
//Unset the flag
if(side.IsFlagSet("lightfog"))
{
side.SetFlag("lightfog", false);
return -1;
}
return 0;
}
//Update the flag
if(General.Map.Data.MapInfo.HasFadeColor ||
(General.Map.Data.MapInfo.HasOutsideFogColor && side.Sector.CeilTexture == General.Map.Config.SkyFlatName) ||
side.Sector.Fields.ContainsKey("fadecolor"))
{
//Set the flag
if(!side.IsFlagSet("lightfog"))
{
side.SetFlag("lightfog", true);
return 1;
}
}
else
{
//Unset the flag
if(side.IsFlagSet("lightfog"))
{
side.SetFlag("lightfog", false);
return -1;
}
}
return 0;
}
//mxd
public static Sector FindSectorContaining(Linedef line, bool front)
{
List<LinedefSide> sectorsides = FindPotentialSectorAt(line, front);
if(sectorsides == null) return null;
// Check potential sectors
foreach(LinedefSide sectorside in sectorsides)
{
Sidedef target = (sectorside.Front ? sectorside.Line.Front : sectorside.Line.Back);
if(target != null && target.Sector != null)
{
// Check if target line is inside the found sector
if(target.Sector.Intersect(line.Start.Position) && target.Sector.Intersect(line.End.Position))
return target.Sector;
}
}
// No dice...
return null;
}
#endregion
#region ================== Misc Exported Functions
/// <summary>
/// This performs a Hermite spline interpolation and returns the result position.
/// Where u (0 - 1) is the wanted position on the curve between p1 (using tangent t1) and p2 (using tangent t2).
/// </summary>
public static Vector2D HermiteSpline(Vector2D p1, Vector2D t1, Vector2D p2, Vector2D t2, float u)
{
return D3DDevice.V2D(Vector2.Hermite(D3DDevice.V2(p1), D3DDevice.V2(t1), D3DDevice.V2(p2), D3DDevice.V2(t2), u));
}
/// <summary>
/// This performs a Hermite spline interpolation and returns the result position.
/// Where u (0 - 1) is the wanted position on the curve between p1 (using tangent t1) and p2 (using tangent t2).
/// </summary>
public static Vector3D HermiteSpline(Vector3D p1, Vector3D t1, Vector3D p2, Vector3D t2, float u)
{
return D3DDevice.V3D(Vector3.Hermite(D3DDevice.V3(p1), D3DDevice.V3(t1), D3DDevice.V3(p2), D3DDevice.V3(t2), u));
}
//mxd
public static int GetDropDownWidth(ComboBox cb)
{
int maxwidth = 0;
foreach(var obj in cb.Items)
{
int temp = TextRenderer.MeasureText(obj.ToString(), cb.Font).Width;
if(temp > maxwidth) maxwidth = temp;
}
return maxwidth > 0 ? maxwidth + 6 : 1;
}
//mxd
public static PixelColor GetSectorFadeColor(Sector s)
{
if(s.Fields.ContainsKey("fadecolor")) return PixelColor.FromInt(s.Fields.GetValue("fadecolor", 0));
if(General.Map.Data.MapInfo.HasOutsideFogColor && s.CeilTexture == General.Map.Config.SkyFlatName)
return PixelColor.FromColor(General.Map.Data.MapInfo.OutsideFogColor.ToColor());
return PixelColor.FromColor(General.Map.Data.MapInfo.HasFadeColor ? General.Map.Data.MapInfo.FadeColor.ToColor() : Color.Black);
}
#endregion
}
}