UltimateZoneBuilder/Source/Core/Geometry/Tools.cs
MaxED bafb8ed511 UDMF: sidedef flags are now defined in game configuration instead of being hardcoded.
Texture scale controls now have "Link" button. 
Thing, Sector (UDMF) and Linedef edit forms now work in realtime mode.
Some more fixes in Edit form realtime update stuff.
Removed some unused variables and functions.
2013-07-19 15:30:58 +00:00

1963 lines
67 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;
using System.Collections.Generic;
using CodeImp.DoomBuilder.Rendering;
using SlimDX;
using System.Drawing;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.Types;
#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;
}
private struct SidedefFillJob
{
public Sidedef sidedef;
// Moving forward along the sidedef?
public bool forward;
}
#endregion
#region ================== Constants
#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;
}
else
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)
{
Vertex foundv;
bool vvalid, findmore;
Linedef foundline;
float foundangle = 0f;
bool foundlinefront;
RectangleF bbox = p.CreateBBox();
do
{
findmore = false;
// Go for all vertices to find the right-most vertex inside the polygon
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))
{
// 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
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
float targetangle = Angle2D.PIHALF;
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);
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;
foreach(Linedef ld in General.Map.Map.Linedefs)
{
// Line to the right of start point?
if((ld.Start.Position.x > foundv.Position.x) ||
(ld.End.Position.x > foundv.Position.x))
{
// Line intersecting the y axis?
if( !((ld.Start.Position.y > foundv.Position.y) &&
(ld.End.Position.y > foundv.Position.y)) &&
!((ld.Start.Position.y < foundv.Position.y) &&
(ld.End.Position.y < foundv.Position.y)))
{
// 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));
if(!tracecount.ContainsKey(nextline)) tracecount.Add(nextline, 1); else tracecount[nextline]++;
// 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;
if(lines[0] == nextline) nextline = lines[1]; else nextline = lines[0];
// 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;
}
}
}
// 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)
{
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 neares linedef, if settings have been found yet
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)
{
Sidedef defaultside;
float side = nearest.SideOfLine(testpoint);
if(side < 0.0f)
defaultside = nearest.Front;
else
defaultside = nearest.Back;
if(defaultside != null)
{
if(sourcesector == null) sourcesector = defaultside.Sector;
TakeSidedefSettings(ref sourceside, defaultside);
}
}
}
// 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);
//mxd
if(General.Settings.GZForceDefaultTextures) {
newsector.SetCeilTexture(General.Settings.DefaultCeilingTexture);
newsector.SetFloorTexture(General.Settings.DefaultFloorTexture);
}
}
else
{
// No source sector, apply default sector properties
ApplyDefaultsToSector(newsector);
}
// 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);
if(ls.Line.Back != null) ls.Line.Back.RemoveUnneededTextures(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);
}
// 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);
}
// 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);
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 || General.Settings.GZForceDefaultTextures) settings.newtexhigh = General.Settings.DefaultTexture;
if(settings.newtexmid == null || General.Settings.GZForceDefaultTextures) settings.newtexmid = General.Settings.DefaultTexture;
if(settings.newtexlow == null || General.Settings.GZForceDefaultTextures) settings.newtexlow = General.Settings.DefaultTexture;
}
// 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.HighTexture.StartsWith("-")) sd.SetTextureHigh(defaults.newtexhigh);
if(sd.MiddleRequired() && sd.MiddleTexture.StartsWith("-")) sd.SetTextureMid(defaults.newtexmid);
if(sd.LowRequired() && sd.LowTexture.StartsWith("-")) sd.SetTextureLow(defaults.newtexlow);
}
// This applies defaults to a sector
private static void ApplyDefaultsToSector(Sector s)
{
s.SetFloorTexture(General.Settings.DefaultFloorTexture);
s.SetCeilTexture(General.Settings.DefaultCeilingTexture);
s.FloorHeight = General.Settings.DefaultFloorHeight;
s.CeilHeight = General.Settings.DefaultCeilingHeight;
s.Brightness = General.Settings.DefaultBrightness;
}
#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);
}
/// <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 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;
foreach(Linedef ld2 in map.Linedefs)
{
// Intersecting?
// We only keep the unit length from the start of the line and
// do the real splitting later, when all intersections are known
float u;
if(ld2.Line.GetIntersection(measureline, out u))
{
if(!float.IsNaN(u) && (u > 0.0f) && (u < 1.0f) && (ld2 != ld))
intersections.Add(u);
}
}
// 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, mergeverts, false, MapSet.STITCH_DISTANCE);
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 drawingclosed = false;
bool splittingonly = false;
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;
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
if(pathforward)
v1 = firstline.Start;
else
v1 = 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, mergeverts, false, MapSet.STITCH_DISTANCE);
}
}
}
}
}
}
// 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
Sector newsector = Tools.MakeSector(sectorlines, oldlines);
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;
}
else 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)) newlines[i].Dispose();
}
}
//mxd
if(autoAlignTextureOffsets) {
//Auto-align new lines
if(newlines.Count > 1) {
float totalLength = 0f;
foreach(Linedef l in newlines)
totalLength += l.Length;
if(General.Map.UDMF)
autoAlignTexturesOnSidesUDMF(newlines, totalLength, (newlines[0].End != newlines[1].Start));
else
autoAlignTexturesOnSides(newlines, totalLength, (newlines[0].End != newlines[1].Start));
}
}
//mxd. Clamp texture offsets of changed lines
List<Linedef> changedLines = new List<Linedef>();
foreach(Vertex v in newverts) {
if(v.Linedefs == null)
continue;
foreach(Linedef l in v.Linedefs) {
if(!changedLines.Contains(l))
changedLines.Add(l);
}
}
// 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 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.MiddleTexture.Length > 1 && General.Map.Data.GetFlatExists(l.Front.MiddleTexture)) {
texture = General.Map.Data.GetFlatImage(l.Front.MiddleTexture);
} else if(l.Front.HighRequired() && l.Front.HighTexture.Length > 1 && General.Map.Data.GetFlatExists(l.Front.HighTexture)) {
texture = General.Map.Data.GetFlatImage(l.Front.HighTexture);
} else if(l.Front.LowRequired() && l.Front.LowTexture.Length > 1 && General.Map.Data.GetFlatExists(l.Front.LowTexture)) {
texture = General.Map.Data.GetFlatImage(l.Front.LowTexture);
}
if(texture != null)
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.MiddleTexture.Length > 1 && General.Map.Data.GetFlatExists(l.Back.MiddleTexture)) {
texture = General.Map.Data.GetFlatImage(l.Back.MiddleTexture);
} else if(l.Back.HighRequired() && l.Back.HighTexture.Length > 1 && General.Map.Data.GetFlatExists(l.Back.HighTexture)) {
texture = General.Map.Data.GetFlatImage(l.Back.HighTexture);
} else if(l.Back.LowRequired() && l.Back.LowTexture.Length > 1 && General.Map.Data.GetFlatExists(l.Back.LowTexture)) {
texture = General.Map.Data.GetFlatImage(l.Back.LowTexture);
}
if(texture != null)
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.Fields.ContainsKey("offsetx_mid") && l.Front.MiddleRequired() && l.Front.MiddleTexture.Length > 1 && General.Map.Data.GetFlatExists(l.Front.MiddleTexture)) {
ImageData texture = General.Map.Data.GetFlatImage(l.Front.MiddleTexture);
float offset = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength)) % texture.Width;
if(offset > 0)
l.Front.Fields.Add("offsetx_mid", new UniValue(UniversalType.Float, offset));
}
if(!l.Front.Fields.ContainsKey("offsetx_top") && l.Front.HighRequired() && l.Front.HighTexture.Length > 1 && General.Map.Data.GetFlatExists(l.Front.HighTexture)) {
ImageData texture = General.Map.Data.GetFlatImage(l.Front.HighTexture);
float offset = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength)) % texture.Width;
if(offset > 0)
l.Front.Fields.Add("offsetx_top", new UniValue(UniversalType.Float, offset));
}
if(!l.Front.Fields.ContainsKey("offsetx_bottom") && l.Front.LowRequired() && l.Front.LowTexture.Length > 1 && General.Map.Data.GetFlatExists(l.Front.LowTexture)) {
ImageData texture = General.Map.Data.GetFlatImage(l.Front.LowTexture);
float offset = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength)) % texture.Width;
if(offset > 0)
l.Front.Fields.Add("offsetx_bottom", new UniValue(UniversalType.Float, offset));
}
}
if(l.Back != null) {
if(!l.Back.Fields.ContainsKey("offsetx_mid") && l.Back.MiddleRequired() && l.Back.MiddleTexture.Length > 1 && General.Map.Data.GetFlatExists(l.Back.MiddleTexture)) {
ImageData texture = General.Map.Data.GetFlatImage(l.Back.MiddleTexture);
float offset = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength)) % texture.Width;
if(offset > 0)
l.Back.Fields.Add("offsetx_mid", new UniValue(UniversalType.Float, offset));
}
if(!l.Back.Fields.ContainsKey("offsetx_top") && l.Back.HighRequired() && l.Back.HighTexture.Length > 1 && General.Map.Data.GetFlatExists(l.Back.HighTexture)) {
ImageData texture = General.Map.Data.GetFlatImage(l.Back.HighTexture);
float offset = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength)) % texture.Width;
if(offset > 0)
l.Back.Fields.Add("offsetx_top", new UniValue(UniversalType.Float, offset));
}
if(!l.Back.Fields.ContainsKey("offsetx_bottom") && l.Back.LowRequired() && l.Back.LowTexture.Length > 1 && General.Map.Data.GetFlatExists(l.Back.LowTexture)) {
ImageData texture = General.Map.Data.GetFlatImage(l.Back.LowTexture);
float offset = (int)Math.Round((reversed ? totalLength - curLength - l.Length : curLength)) % texture.Width;
if(offset > 0)
l.Back.Fields.Add("offsetx_bottom", new UniValue(UniversalType.Float, 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, long originalflat, ImageData 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(((start.LongFloorTexture == originalflat) && !fillceilings) ||
((start.LongCeilTexture == originalflat) && 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.Name);
else
s.SetFloorTexture(fillflat.Name);
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(((os.LongFloorTexture == originalflat) && !fillceilings) ||
((os.LongCeilTexture == originalflat) && 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, long originaltexture, ImageData 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, originaltexture))
{
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() && j.sidedef.LongHighTexture == originaltexture) j.sidedef.SetTextureHigh(filltexture.Name);
if((((j.sidedef.MiddleTexture.Length > 0) && (j.sidedef.MiddleTexture[0] != '-')) || j.sidedef.MiddleRequired()) &&
(j.sidedef.LongMiddleTexture == originaltexture)) j.sidedef.SetTextureMid(filltexture.Name);
if(j.sidedef.LowRequired() && j.sidedef.LongLowTexture == originaltexture) j.sidedef.SetTextureLow(filltexture.Name);
j.sidedef.Marked = true;
if(j.forward)
{
Vertex v;
// Add sidedefs forward (connected to the right vertex)
v = j.sidedef.IsFront ? j.sidedef.Line.End : j.sidedef.Line.Start;
AddSidedefsForFloodfill(todo, v, true, originaltexture);
// Add sidedefs backward (connected to the left vertex)
v = j.sidedef.IsFront ? j.sidedef.Line.Start : j.sidedef.Line.End;
AddSidedefsForFloodfill(todo, v, false, originaltexture);
}
else
{
Vertex v;
// Add sidedefs backward (connected to the left vertex)
v = j.sidedef.IsFront ? j.sidedef.Line.Start : j.sidedef.Line.End;
AddSidedefsForFloodfill(todo, v, false, originaltexture);
// Add sidedefs forward (connected to the right vertex)
v = j.sidedef.IsFront ? j.sidedef.Line.End : j.sidedef.Line.Start;
AddSidedefsForFloodfill(todo, v, true, originaltexture);
}
}
}
// This adds the matching, unmarked sidedefs from a vertex for texture alignment
private static void AddSidedefsForFloodfill(Stack<SidedefFillJob> stack, Vertex v, bool forward, long texturelongname)
{
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, texturelongname))
{
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, texturelongname))
{
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.MiddleTexture.Length > 0) && (sd.MiddleTexture[0] != '-')))) ;
}
#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 ================== Thing Alignment (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);
if(side == 0) { //already on line
t.Rotate(General.ClampAngle(180 + l.AngleDeg));
return true;
}
if(side < 0) { //thing is on front side of the line
//got any walls to align to?
if(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(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);
}
//performs thing alignment
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) {
int absz = 0;
// Determine z info
if(ti.AbsoluteZ) {
absz = (int)t.Position.z;
} else {
if(t.Sector != null) {
// Hangs from ceiling?
if(ti.Hangs)
absz = (int)(t.Sector.CeilHeight - t.Position.z - ti.Height);
else
absz = (int)(t.Sector.FloorHeight + t.Position.z);
} else {
absz = (int)t.Position.z;
}
}
return absz;
}
#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));
}
#endregion
}
}