2009-04-19 18:07:22 +00:00
#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 ;
2015-12-14 12:34:31 +00:00
using System.Windows.Forms ;
2009-07-09 14:03:47 +00:00
using CodeImp.DoomBuilder.Config ;
2015-12-14 12:34:31 +00:00
using CodeImp.DoomBuilder.Data ;
using CodeImp.DoomBuilder.Map ;
using CodeImp.DoomBuilder.Rendering ;
2009-07-09 14:03:47 +00:00
using CodeImp.DoomBuilder.Types ;
2014-12-22 21:36:49 +00:00
using CodeImp.DoomBuilder.VisualModes ;
2009-04-19 18:07:22 +00:00
#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 ;
}
2016-05-12 11:32:10 +00:00
public struct SidedefFillJob
2009-04-19 18:07:22 +00:00
{
public Sidedef sidedef ;
// Moving forward along the sidedef?
public bool forward ;
}
#endregion
#region = = = = = = = = = = = = = = = = = = Constants
2016-09-06 19:14:49 +00:00
//mxd
private const float MINIMUM_INTERSECTION_DISTANCE = 0.25f ;
2009-04-19 18:07:22 +00:00
#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 & 0x00000001 UL ) ! = 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 ;
}
2013-12-23 08:00:19 +00:00
return null ;
2009-04-19 18:07:22 +00:00
}
// This finds the inner lines of the sector and adds them to the sector polygon
private static void FindInnerLines ( EarClipPolygon p , List < LinedefSide > alllines )
{
2015-12-28 15:01:53 +00:00
bool findmore ;
2009-04-19 18:07:22 +00:00
float foundangle = 0f ;
RectangleF bbox = p . CreateBBox ( ) ;
do
{
findmore = false ;
// Go for all vertices to find the right-most vertex inside the polygon
2015-12-28 15:01:53 +00:00
Vertex foundv = null ;
2009-04-19 18:07:22 +00:00
foreach ( Vertex v in General . Map . Map . Vertices )
{
// Inside the polygon bounding box?
2013-12-11 09:47:35 +00:00
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 ) )
2009-04-19 18:07:22 +00:00
{
2013-12-11 09:47:35 +00:00
// Vertex is inside the polygon?
if ( p . Intersect ( v . Position ) )
2009-04-19 18:07:22 +00:00
{
2013-12-11 09:47:35 +00:00
// Vertex has lines attached?
if ( v . Linedefs . Count > 0 )
2009-04-19 18:07:22 +00:00
{
2013-12-11 09:47:35 +00:00
// Go for all lines to see if the vertex is not of the polygon itsself
2015-12-28 15:01:53 +00:00
bool vvalid = true ;
2013-12-11 09:47:35 +00:00
foreach ( LinedefSide ls in alllines )
2009-04-19 18:07:22 +00:00
{
2013-12-11 09:47:35 +00:00
if ( ( ls . Line . Start = = v ) | | ( ls . Line . End = = v ) )
2009-04-19 18:07:22 +00:00
{
2013-12-11 09:47:35 +00:00
vvalid = false ;
break ;
2009-04-19 18:07:22 +00:00
}
}
2013-12-11 09:47:35 +00:00
// Valid vertex?
if ( vvalid ) foundv = v ;
2009-04-19 18:07:22 +00:00
}
}
}
}
// Found a vertex inside the polygon?
if ( foundv ! = null )
{
// Find the attached linedef with the smallest angle to the right
2015-12-28 15:01:53 +00:00
const float targetangle = Angle2D . PIHALF ;
Linedef foundline = null ;
2009-04-19 18:07:22 +00:00
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 ) ;
2015-12-28 15:01:53 +00:00
bool foundlinefront = ( foundline . SideOfLine ( foundv . Position + testpos ) < 0.0f ) ;
2009-04-19 18:07:22 +00:00
// 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 ) ;
2016-10-31 18:52:29 +00:00
//mxd. Check bbox first...
Vector2D foundsidepoint = foundline . GetSidePoint ( foundlinefront ) ;
RectangleF innerbbox = innerpoly . CreateBBox ( ) ;
bool outsidebbox = ( foundsidepoint . x < innerbbox . Left | | foundsidepoint . x > innerbbox . Right | | foundsidepoint . y < innerbbox . Top | | foundsidepoint . y > innerbbox . Bottom ) ;
2009-04-19 18:07:22 +00:00
// Check if the front of the line is outside the polygon
2016-10-31 18:52:29 +00:00
if ( outsidebbox | | ! innerpoly . Intersect ( foundsidepoint ) )
2009-04-19 18:07:22 +00:00
{
// 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 ;
2016-10-31 18:52:29 +00:00
Vector2D sidepoint = line . GetSidePoint ( front ) ; //mxd
2009-04-19 18:07:22 +00:00
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 ) ;
2016-10-31 18:52:29 +00:00
//mxd. Check bbox first...
RectangleF bbox = poly . CreateBBox ( ) ;
bool outsidebbox = ( sidepoint . x < bbox . Left | | sidepoint . x > bbox . Right | | sidepoint . y < bbox . Top | | sidepoint . y > bbox . Bottom ) ;
2009-04-19 18:07:22 +00:00
// Check if the front of the line is inside the polygon
2016-10-31 18:52:29 +00:00
if ( ! outsidebbox & & poly . Intersect ( sidepoint ) )
2009-04-19 18:07:22 +00:00
{
// 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!" ) ;
2016-11-21 13:03:19 +00:00
// 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.
//mxd. Intersection test is bounded, so extend end position x to the right map boundary
Line2D testline = new Line2D ( foundv . Position , new Vector2D ( General . Map . Config . RightBoundary , foundv . Position . y ) ) ;
2009-04-19 18:07:22 +00:00
scanline = null ;
float foundu = float . MaxValue ;
2013-12-10 12:19:27 +00:00
float px = foundv . Position . x ; //mxd
float py = foundv . Position . y ; //mxd
2014-12-03 23:15:26 +00:00
foreach ( Linedef ld in General . Map . Map . Linedefs )
{
2009-04-19 18:07:22 +00:00
// Line to the right of start point?
2014-12-03 23:15:26 +00:00
if ( ( ld . Start . Position . x > px ) | | ( ld . End . Position . x > px ) )
{
2009-04-19 18:07:22 +00:00
// Line intersecting the y axis?
2014-12-03 23:15:26 +00:00
if ( ( ld . Start . Position . y > = py & & ld . End . Position . y < = py )
| | ( ld . Start . Position . y < = py & & ld . End . Position . y > = py ) ) //mxd
{
2009-04-19 18:07:22 +00:00
// Check if this linedef intersects our test line at a closer range
float thisu ;
ld . Line . GetIntersection ( testline , out thisu ) ;
2016-11-21 13:03:19 +00:00
if ( ! float . IsNaN ( thisu ) & & ( thisu > 0.00001f ) )
2009-04-19 18:07:22 +00:00
{
2016-11-21 13:03:19 +00:00
if ( thisu < foundu )
{
scanline = ld ;
foundu = thisu ;
}
//mxd. Special cases: when foundv.y matches ld's start or end y,
// prefer the line, which is clser to being parallel to the x axis
else if ( scanline ! = null & & Math . Round ( thisu , 4 ) = = Math . Round ( foundu , 4 ) )
{
float ldanglerel , scanlineanglerel ;
if ( GetRelativeAngle ( ld , foundv . Position , out ldanglerel )
& & GetRelativeAngle ( scanline , foundv . Position , out scanlineanglerel )
& & ( ldanglerel < scanlineanglerel ) )
{
scanline = ld ; // foundu already matches
}
}
2009-04-19 18:07:22 +00:00
}
}
}
}
// 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 ) ;
}
2016-11-21 13:03:19 +00:00
//mxd. Gets angle between pos and l when pos.y matches l.Start.Position.y or l.End.Position.y
private static bool GetRelativeAngle ( Linedef l , Vector2D pos , out float result )
{
if ( l . Start . Position . y = = pos . y )
{
result = Angle2D . GetAngle ( pos , l . Start . Position , l . End . Position ) ;
return true ;
}
if ( l . End . Position . y = = pos . y )
{
result = Angle2D . GetAngle ( pos , l . End . Position , l . Start . Position ) ;
return true ;
}
// We just don't know...
result = float . MaxValue ;
return false ;
}
2009-04-19 18:07:22 +00:00
/// <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 ;
2013-12-10 12:19:27 +00:00
nextline = ( lines [ 0 ] = = nextline ? lines [ 1 ] : lines [ 0 ] ) ;
2009-04-19 18:07:22 +00:00
2016-05-11 23:26:58 +00:00
//mxd. Try to pick a line with lower tracecount, otherwise we will just walk the same path trise
2016-03-14 10:25:27 +00:00
int curcount = ( ! tracecount . ContainsKey ( nextline ) ? 0 : tracecount [ nextline ] ) ;
2016-05-11 23:26:58 +00:00
//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 )
2016-03-14 10:25:27 +00:00
{
foreach ( Linedef l in lines )
{
if ( l ! = nextline & & l ! = prevline & & ( ! tracecount . ContainsKey ( l ) | | tracecount [ l ] < curcount ) )
{
nextline = l ;
break ;
}
}
}
2009-04-19 18:07:22 +00:00
// Are we allowed to trace this line again?
if ( ! tracecount . ContainsKey ( nextline ) | | ( tracecount [ nextline ] < 3 ) )
{
// Check if front side changes
2016-05-11 23:26:58 +00:00
if ( prevline . Start = = nextline . Start | | prevline . End = = nextline . End )
nextfront = ! nextfront ;
2009-04-19 18:07:22 +00:00
}
else
{
// No more lines, trace ends here
path = null ;
}
}
2016-03-14 10:25:27 +00:00
//mxd. Increase trace count
if ( ! tracecount . ContainsKey ( nextline ) ) tracecount . Add ( nextline , 1 ) ; else tracecount [ nextline ] + + ;
2009-04-19 18:07:22 +00:00
}
// 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.
2010-08-15 19:43:00 +00:00
// Return null when no new sector could be made.
2013-11-21 10:53:11 +00:00
public static Sector MakeSector ( List < LinedefSide > alllines , List < Linedef > nearbylines , bool useOverrides )
2009-04-19 18:07:22 +00:00
{
Sector sourcesector = null ;
SidedefSettings sourceside = new SidedefSettings ( ) ;
bool foundsidedefaults = false ;
2010-08-15 19:43:00 +00:00
if ( General . Map . Map . Sectors . Count > = General . Map . FormatInterface . MaxSectors )
return null ;
Sector newsector = General . Map . Map . CreateSector ( ) ;
if ( newsector = = null ) return null ;
2009-04-19 18:07:22 +00:00
// 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 ;
}
}
}
2015-02-12 22:04:49 +00:00
// Use default settings from the nearest linedef, if settings have not been found yet
Sector nearestsector = null ; //mxd
2009-04-19 18:07:22 +00:00
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 ) ;
2013-12-10 12:19:27 +00:00
Sidedef defaultside = ( side < 0.0f ? nearest . Front : nearest . Back ) ;
2009-04-19 18:07:22 +00:00
if ( defaultside ! = null )
{
if ( sourcesector = = null ) sourcesector = defaultside . Sector ;
TakeSidedefSettings ( ref sourceside , defaultside ) ;
}
2015-02-12 22:04:49 +00:00
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 ;
}
}
2009-04-19 18:07:22 +00:00
}
}
// Use defaults where no settings could be found
2014-01-13 08:06:56 +00:00
TakeSidedefDefaults ( ref sourceside ) ;
2009-04-19 18:07:22 +00:00
// Found a source sector?
if ( sourcesector ! = null )
{
// Copy properties from source to new sector
sourcesector . CopyPropertiesTo ( newsector ) ;
2015-02-12 22:04:49 +00:00
}
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 ;
2009-04-19 18:07:22 +00:00
}
else
{
// No source sector, apply default sector properties
2015-02-12 22:04:49 +00:00
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. 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 ;
2009-04-19 18:07:22 +00:00
}
2016-11-10 00:35:29 +00:00
//mxd. Avoid invalid height
else if ( newsector . CeilHeight < newsector . FloorHeight )
{
newsector . CeilHeight = newsector . FloorHeight ;
}
2009-04-19 18:07:22 +00:00
// 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 ) ;
2010-08-15 19:43:00 +00:00
if ( ls . Line . Front = = null ) return null ;
2009-06-11 21:21:20 +00:00
if ( ls . Line . Front . Sector ! = newsector ) ls . Line . Front . SetSector ( newsector ) ;
2009-04-19 18:07:22 +00:00
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 ) ;
2010-08-15 19:43:00 +00:00
if ( ls . Line . Back = = null ) return null ;
2009-06-11 21:21:20 +00:00
if ( ls . Line . Back . Sector ! = newsector ) ls . Line . Back . SetSector ( newsector ) ;
2009-04-19 18:07:22 +00:00
ApplyDefaultsToSidedef ( ls . Line . Back , sourceside ) ;
}
// Update line
2014-03-19 13:03:47 +00:00
if ( ls . Line . Front ! = null ) ls . Line . Front . RemoveUnneededTextures ( wassinglesided , false , wassinglesided ) ;
if ( ls . Line . Back ! = null ) ls . Line . Back . RemoveUnneededTextures ( wassinglesided , false , wassinglesided ) ;
2009-04-19 18:07:22 +00:00
// 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 ;
}
2010-08-15 19:43:00 +00:00
// This joins a sector with the given lines and sides. Returns null when operation could not be completed.
2014-01-13 08:06:56 +00:00
public static Sector JoinSector ( List < LinedefSide > alllines , Sidedef original )
2009-04-19 18:07:22 +00:00
{
SidedefSettings sourceside = new SidedefSettings ( ) ;
// Take settings fro mthe original side
TakeSidedefSettings ( ref sourceside , original ) ;
// Use defaults where no settings could be found
2014-01-13 08:06:56 +00:00
TakeSidedefDefaults ( ref sourceside ) ;
2009-04-19 18:07:22 +00:00
// Go for all sides to make sidedefs
foreach ( LinedefSide ls in alllines )
{
if ( ls . Front )
{
// Create sidedef if needed
if ( ls . Line . Front = = null )
{
2010-08-15 19:43:00 +00:00
Sidedef sd = General . Map . Map . CreateSidedef ( ls . Line , true , original . Sector ) ;
if ( sd = = null ) return null ;
2009-04-19 18:07:22 +00:00
ApplyDefaultsToSidedef ( ls . Line . Front , sourceside ) ;
ls . Line . ApplySidedFlags ( ) ;
// We must remove the (now useless) middle texture on the other side
2014-03-19 13:03:47 +00:00
if ( ls . Line . Back ! = null ) ls . Line . Back . RemoveUnneededTextures ( true , true , true ) ;
2009-04-19 18:07:22 +00:00
}
// Added 23-9-08, can we do this or will it break things?
2013-07-05 09:07:49 +00:00
else if ( ! original . Sector . IsDisposed ) //mxd
2009-04-19 18:07:22 +00:00
{
// Link to the new sector
2009-06-11 21:21:20 +00:00
ls . Line . Front . SetSector ( original . Sector ) ;
2009-04-19 18:07:22 +00:00
}
}
else
{
// Create sidedef if needed
if ( ls . Line . Back = = null )
{
2010-08-15 19:43:00 +00:00
Sidedef sd = General . Map . Map . CreateSidedef ( ls . Line , false , original . Sector ) ;
if ( sd = = null ) return null ;
2009-04-19 18:07:22 +00:00
ApplyDefaultsToSidedef ( ls . Line . Back , sourceside ) ;
ls . Line . ApplySidedFlags ( ) ;
// We must remove the (now useless) middle texture on the other side
2014-03-19 13:03:47 +00:00
if ( ls . Line . Front ! = null ) ls . Line . Front . RemoveUnneededTextures ( true , true , true ) ;
2009-04-19 18:07:22 +00:00
}
// Added 23-9-08, can we do this or will it break things?
2013-07-05 09:07:49 +00:00
else if ( ! original . Sector . IsDisposed ) //mxd
2009-04-19 18:07:22 +00:00
{
// Link to the new sector
2009-06-11 21:21:20 +00:00
ls . Line . Back . SetSector ( original . Sector ) ;
2009-04-19 18:07:22 +00:00
}
}
}
// Return the new sector
return original . Sector ;
}
2013-03-22 12:53:34 +00:00
//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
2014-12-03 23:15:26 +00:00
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 )
{
2013-03-22 12:53:34 +00:00
group . Key . Dispose ( ) ;
List < LinedefSide > sides = Tools . FindPotentialSectorAt ( group . Value ) ;
2014-12-03 23:15:26 +00:00
if ( sides ! = null )
{
2013-03-22 12:53:34 +00:00
// Mark the lines we are going to use for this sector
General . Map . Map . ClearAllMarks ( true ) ;
2014-12-03 23:15:26 +00:00
foreach ( LinedefSide ls in sides ) ls . Line . Marked = false ;
2013-03-22 12:53:34 +00:00
List < Linedef > oldlines = General . Map . Map . GetMarkedLinedefs ( true ) ;
// Make the sector
2013-09-09 14:03:02 +00:00
Sector s = Tools . MakeSector ( sides , oldlines , false ) ;
2013-03-22 12:53:34 +00:00
2014-12-03 23:15:26 +00:00
if ( s ! = null )
{
2013-03-22 12:53:34 +00:00
// 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.
2014-12-03 23:15:26 +00:00
foreach ( Sidedef sd in s . Sidedefs )
{
if ( ( sd . Line . Front = = null ) & & ( sd . Line . Back ! = null ) )
{
2013-03-22 12:53:34 +00:00
// Flip linedef
sd . Line . FlipVertices ( ) ;
sd . Line . FlipSidedefs ( ) ;
}
}
General . Map . Data . UpdateUsedTextures ( ) ;
}
}
}
}
}
2009-04-19 18:07:22 +00:00
// This takes default settings if not taken yet
2014-01-13 08:06:56 +00:00
private static void TakeSidedefDefaults ( ref SidedefSettings settings )
2009-04-19 18:07:22 +00:00
{
// Use defaults where no settings could be found
2014-01-13 08:06:56 +00:00
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 ;
2009-04-19 18:07:22 +00:00
}
// 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 )
{
2014-03-05 09:21:28 +00:00
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
2009-04-19 18:07:22 +00:00
}
2014-01-13 08:06:56 +00:00
//mxd. This applies overrides to a sidedef
2014-12-03 23:15:26 +00:00
private static void ApplyOverridesToSidedef ( Sidedef sd )
{
2016-02-07 23:04:20 +00:00
if ( sd . HighRequired ( ) & & General . Map . Options . OverrideTopTexture ) sd . SetTextureHigh ( General . Map . Options . DefaultTopTexture ) ;
2014-01-13 08:06:56 +00:00
if ( sd . MiddleRequired ( ) & & General . Map . Options . OverrideMiddleTexture ) sd . SetTextureMid ( General . Map . Options . DefaultWallTexture ) ;
2016-02-07 23:04:20 +00:00
if ( sd . LowRequired ( ) & & General . Map . Options . OverrideBottomTexture ) sd . SetTextureLow ( General . Map . Options . DefaultBottomTexture ) ;
2014-01-13 08:06:56 +00:00
}
2009-04-19 18:07:22 +00:00
#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
2013-03-18 13:52:27 +00:00
//mxd
2014-11-12 11:22:14 +00:00
public static bool DrawLines ( IList < DrawnVertex > points )
{
2013-09-09 14:03:02 +00:00
return DrawLines ( points , false , false ) ;
2013-03-18 13:52:27 +00:00
}
2009-04-19 18:07:22 +00:00
/// <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.
2010-08-15 19:43:00 +00:00
/// Returns false when the drawing failed.
2009-04-19 18:07:22 +00:00
/// </summary>
2013-11-21 10:53:11 +00:00
public static bool DrawLines ( IList < DrawnVertex > points , bool useOverrides , bool autoAlignTextureOffsets )
2009-04-19 18:07:22 +00:00
{
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 ;
2016-10-31 18:52:29 +00:00
//mxd. Let's use a blockmap...
RectangleF area = MapSet . CreateArea ( oldlines ) ;
BlockMap < BlockEntry > oldlinesmap = new BlockMap < BlockEntry > ( area ) ;
oldlinesmap . AddLinedefsSet ( oldlines ) ;
2009-04-19 18:07:22 +00:00
General . Map . Map . ClearAllMarks ( false ) ;
// Any points to do?
if ( points . Count > 0 )
{
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * \
2009-05-06 14:50:17 +00:00
Create the drawing
2009-04-19 18:07:22 +00:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
// Make first vertex
Vertex v1 = map . CreateVertex ( points [ 0 ] . pos ) ;
2010-08-15 19:43:00 +00:00
if ( v1 = = null ) return false ;
2009-04-19 18:07:22 +00:00
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 ) ;
2010-08-15 19:43:00 +00:00
if ( v2 = = null ) return false ;
2009-04-19 18:07:22 +00:00
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 ) ;
2010-08-15 19:43:00 +00:00
if ( ld = = null ) return false ;
2009-04-19 18:07:22 +00:00
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 ;
2016-06-19 00:09:53 +00:00
HashSet < Linedef > processed = new HashSet < Linedef > ( ) ; //mxd
2013-12-17 13:18:44 +00:00
//mxd
2015-12-28 15:01:53 +00:00
foreach ( Sector s in map . Sectors )
2014-12-03 23:15:26 +00:00
{
2013-12-17 13:18:44 +00:00
//line intersects with sector's bounding box?
2014-12-03 23:15:26 +00:00
if ( ( MapSet . GetCSFieldBits ( measureline . v1 , s . BBox ) & MapSet . GetCSFieldBits ( measureline . v2 , s . BBox ) ) = = 0 )
{
2015-12-28 15:01:53 +00:00
foreach ( Sidedef side in s . Sidedefs )
2014-12-03 23:15:26 +00:00
{
2016-06-19 00:09:53 +00:00
if ( processed . Contains ( side . Line ) ) continue ;
2013-12-17 13:18:44 +00:00
if ( side . Line = = ld ) continue ;
float u ;
2014-12-03 23:15:26 +00:00
if ( side . Line . Line . GetIntersection ( measureline , out u ) )
{
2013-12-24 09:21:18 +00:00
if ( float . IsNaN ( u ) | | ( u < = 0.0f ) | | ( u > = 1.0f ) ) continue ;
2016-09-06 19:14:49 +00:00
//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 ) ;
2013-12-17 13:18:44 +00:00
}
2016-06-19 00:09:53 +00:00
processed . Add ( side . Line ) ;
2013-12-17 13:18:44 +00:00
}
2009-04-19 18:07:22 +00:00
}
}
// 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 ) ;
2010-08-15 19:43:00 +00:00
if ( splitvertex = = null ) return false ;
2009-04-19 18:07:22 +00:00
splitvertex . Marked = true ;
newverts . Add ( splitvertex ) ;
mergeverts . Add ( splitvertex ) ; // <-- add to merge?
intersectverts . Add ( splitvertex ) ;
2010-08-15 19:43:00 +00:00
2009-04-19 18:07:22 +00:00
// 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 ) ;
2010-08-15 19:43:00 +00:00
if ( splitline = = null ) return false ;
2009-04-19 18:07:22 +00:00
splitline . ApplySidedFlags ( ) ;
newlines . Add ( splitline ) ;
}
}
// Next
v1 = v2 ;
}
// Join merge vertices so that overlapping vertices in the draw become one.
2009-06-05 19:03:56 +00:00
map . BeginAddRemove ( ) ;
2013-12-17 13:18:44 +00:00
MapSet . JoinVertices ( mergeverts , MapSet . STITCH_DISTANCE ) ; //mxd
2009-06-05 19:03:56 +00:00
map . EndAddRemove ( ) ;
2009-05-06 14:50:17 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * \
Find a way to close the drawing
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2009-04-19 18:07:22 +00:00
// We prefer a closed polygon, because then we can determine the interior properly
// Check if the two ends of the polygon are closed
2009-05-13 20:07:33 +00:00
bool splittingonly = false ;
2014-01-13 08:06:56 +00:00
bool drawingclosed = false ; //mxd
2009-04-19 18:07:22 +00:00
if ( newlines . Count > 0 )
{
Linedef firstline = newlines [ 0 ] ;
Linedef lastline = newlines [ newlines . Count - 1 ] ;
2014-01-13 08:06:56 +00:00
drawingclosed = ( firstline . Start = = lastline . End ) ;
2009-04-19 18:07:22 +00:00
if ( ! drawingclosed )
{
2009-05-13 20:07:33 +00:00
// 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 )
2009-04-19 18:07:22 +00:00
{
2009-05-13 20:07:33 +00:00
Vector2D ldcp = ld . GetCenterPoint ( ) ;
2016-10-31 18:52:29 +00:00
Linedef nld = MapSet . NearestLinedef ( oldlinesmap , ldcp ) ; //mxd. Lines collection -> Blockmap
2009-05-13 20:07:33 +00:00
if ( nld ! = null )
2009-04-19 18:07:22 +00:00
{
2009-05-13 20:07:33 +00:00
float ldside = nld . SideOfLine ( ldcp ) ;
if ( ldside < 0.0f )
2009-04-19 18:07:22 +00:00
{
2009-05-13 20:07:33 +00:00
if ( nld . Front ! = null )
{
splittingonly = true ;
break ;
}
2009-04-19 18:07:22 +00:00
}
2009-05-13 20:07:33 +00:00
else if ( ldside > 0.0f )
{
if ( nld . Back ! = null )
{
splittingonly = true ;
break ;
}
}
2013-12-24 09:21:18 +00:00
/ * else
2009-04-19 18:07:22 +00:00
{
2009-05-13 20:07:33 +00:00
// We can't tell, so lets ignore this for now.
2013-12-24 09:21:18 +00:00
} * /
2009-04-19 18:07:22 +00:00
}
2009-05-13 20:07:33 +00:00
}
2009-04-19 18:07:22 +00:00
2009-05-13 20:07:33 +00:00
// Not splitting only?
if ( ! splittingonly )
{
// First and last vertex stitch with geometry?
if ( points [ 0 ] . stitch & & points [ points . Count - 1 ] . stitch )
2009-04-19 18:07:22 +00:00
{
2009-05-13 20:07:33 +00:00
List < LinedefSide > startpoints = new List < LinedefSide > ( ) ;
List < LinedefSide > endpoints = new List < LinedefSide > ( ) ;
2009-04-19 18:07:22 +00:00
2009-05-13 20:07:33 +00:00
// Find out where the start will stitch and create test points
2016-10-31 18:52:29 +00:00
Linedef l1 = MapSet . NearestLinedefRange ( oldlinesmap , firstline . Start . Position , MapSet . STITCH_DISTANCE ) ; //mxd. Lines collection -> Blockmap
2009-05-13 20:51:03 +00:00
Vertex vv1 = null ;
2009-05-13 20:07:33 +00:00
if ( l1 ! = null )
2009-04-19 18:07:22 +00:00
{
2009-05-13 20:07:33 +00:00
startpoints . Add ( new LinedefSide ( l1 , true ) ) ;
startpoints . Add ( new LinedefSide ( l1 , false ) ) ;
2009-04-19 18:07:22 +00:00
}
else
{
2009-05-13 20:07:33 +00:00
// Not stitched with a linedef, so check if it will stitch with a vertex
2009-05-13 20:51:03 +00:00
vv1 = MapSet . NearestVertexSquareRange ( nonmergeverts , firstline . Start . Position , MapSet . STITCH_DISTANCE ) ;
if ( ( vv1 ! = null ) & & ( vv1 . Linedefs . Count > 0 ) )
2009-04-19 18:07:22 +00:00
{
2009-05-13 20:07:33 +00:00
// Now we take the two linedefs with adjacent angles to the drawn line
2009-05-13 20:51:03 +00:00
List < Linedef > lines = new List < Linedef > ( vv1 . Linedefs ) ;
2009-05-13 20:07:33 +00:00
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 ) ) ;
2009-04-19 18:07:22 +00:00
}
}
2009-05-13 20:07:33 +00:00
// Find out where the end will stitch and create test points
2016-10-31 18:52:29 +00:00
Linedef l2 = MapSet . NearestLinedefRange ( oldlinesmap , lastline . End . Position , MapSet . STITCH_DISTANCE ) ; //mxd. Lines collection -> Blockmap
2009-05-13 20:51:03 +00:00
Vertex vv2 = null ;
2009-05-13 20:07:33 +00:00
if ( l2 ! = null )
2009-04-19 18:07:22 +00:00
{
2009-05-13 20:07:33 +00:00
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
2009-05-13 20:51:03 +00:00
vv2 = MapSet . NearestVertexSquareRange ( nonmergeverts , lastline . End . Position , MapSet . STITCH_DISTANCE ) ;
if ( ( vv2 ! = null ) & & ( vv2 . Linedefs . Count > 0 ) )
2009-04-19 18:07:22 +00:00
{
2009-05-13 20:07:33 +00:00
// Now we take the two linedefs with adjacent angles to the drawn line
2009-05-13 20:51:03 +00:00
List < Linedef > lines = new List < Linedef > ( vv2 . Linedefs ) ;
2009-05-13 20:07:33 +00:00
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 ) ) ;
2009-04-19 18:07:22 +00:00
}
2009-05-13 20:07:33 +00:00
}
2009-04-19 18:07:22 +00:00
2009-05-13 20:07:33 +00:00
// Found any start and end points?
if ( ( startpoints . Count > 0 ) & & ( endpoints . Count > 0 ) )
{
List < LinedefSide > shortestpath = null ;
2009-05-13 20:51:03 +00:00
// Both stitched to the same line?
2009-05-13 20:07:33 +00:00
if ( ( l1 = = l2 ) & & ( l1 ! = null ) )
{
// Then just connect the two
shortestpath = new List < LinedefSide > ( ) ;
shortestpath . Add ( new LinedefSide ( l1 , true ) ) ;
}
2009-05-13 20:51:03 +00:00
// 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 ) ) ;
}
2009-04-19 18:07:22 +00:00
else
{
2009-05-13 20:07:33 +00:00
// Find the shortest, closest path between start and end points
foreach ( LinedefSide startp in startpoints )
{
foreach ( LinedefSide endp in endpoints )
{
2015-12-27 21:54:50 +00:00
List < LinedefSide > p = Tools . FindClosestPath ( startp . Line , startp . Front , endp . Line , endp . Front , true ) ;
2009-05-13 20:07:33 +00:00
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 ;
}
}
2009-04-19 18:07:22 +00:00
}
2009-05-13 20:07:33 +00:00
// 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
2014-01-13 08:06:56 +00:00
v1 = ( pathforward ? firstline . Start : lastline . End ) ;
2009-05-13 20:07:33 +00:00
// 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 ) ;
2010-08-15 19:43:00 +00:00
if ( v2 = = null ) return false ;
2009-05-13 20:07:33 +00:00
v2 . Marked = true ;
mergeverts . Add ( v2 ) ;
// Make the line
Linedef ld = map . CreateLinedef ( v1 , v2 ) ;
2010-08-15 19:43:00 +00:00
if ( ld = = null ) return false ;
2009-05-13 20:07:33 +00:00
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 ) ;
2010-08-15 19:43:00 +00:00
if ( lld = = null ) return false ;
2009-05-13 20:07:33 +00:00
// 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.
2013-12-17 13:18:44 +00:00
MapSet . JoinVertices ( mergeverts , MapSet . STITCH_DISTANCE ) ; //mxd
2009-05-13 20:07:33 +00:00
}
2009-04-19 18:07:22 +00:00
}
}
}
}
}
// Merge intersetion vertices with the new lines. This completes the
// self intersections for which splits were made above.
map . Update ( true , false ) ;
2009-06-05 19:03:56 +00:00
map . BeginAddRemove ( ) ;
2016-06-02 09:55:01 +00:00
MapSet . SplitLinesByVertices ( newlines , intersectverts , MapSet . STITCH_DISTANCE , null ) ;
MapSet . SplitLinesByVertices ( newlines , mergeverts , MapSet . STITCH_DISTANCE , null ) ;
2009-06-05 19:03:56 +00:00
map . EndAddRemove ( ) ;
2009-04-19 18:07:22 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * \
2009-05-06 14:50:17 +00:00
Determine drawing interior
2009-04-19 18:07:22 +00:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2009-05-06 14:50:17 +00:00
// 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.
2009-04-19 18:07:22 +00:00
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 ) ;
2009-05-06 14:50:17 +00:00
2009-04-19 18:07:22 +00:00
// Check if the front of the line is outside the polygon
2009-05-06 14:50:17 +00:00
if ( ( pathpoly . CalculateArea ( ) > 0.001f ) & & ! pathpoly . Intersect ( ld . GetSidePoint ( true ) ) )
2009-04-19 18:07:22 +00:00
{
// 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 ) ;
2009-05-06 14:50:17 +00:00
// Check if the front of the line is inside the polygon
ld . FrontInterior = ( pathpoly . CalculateArea ( ) < 0.001f ) | | pathpoly . Intersect ( ld . GetSidePoint ( true ) ) ;
2009-04-19 18:07:22 +00:00
}
2009-05-06 14:50:17 +00:00
else
{
ld . FrontInterior = true ;
}
}
else
{
ld . FrontInterior = true ;
2009-04-19 18:07:22 +00:00
}
}
2009-05-06 14:50:17 +00:00
else
{
ld . FrontInterior = true ;
}
2009-04-19 18:07:22 +00:00
}
2009-05-06 14:50:17 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * \
Merge the new geometry
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2009-04-19 18:07:22 +00:00
// 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.
2016-06-02 09:55:01 +00:00
map . StitchGeometry ( ) ;
2009-04-19 18:07:22 +00:00
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 ) ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * \
2009-05-06 14:50:17 +00:00
Join and create new sectors
2009-04-19 18:07:22 +00:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2009-05-06 14:50:17 +00:00
// 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.
2009-04-19 18:07:22 +00:00
// 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 ] ;
2009-05-06 14:50:17 +00:00
// Interior not done yet?
if ( ( ld . FrontInterior & & ! frontsdone [ i ] ) | | ( ! ld . FrontInterior & & ! backsdone [ i ] ) )
2009-04-19 18:07:22 +00:00
{
// Find a way to create a sector here
2009-05-06 14:50:17 +00:00
List < LinedefSide > sectorlines = Tools . FindPotentialSectorAt ( ld , ld . FrontInterior ) ;
2009-04-19 18:07:22 +00:00
if ( sectorlines ! = null )
{
sidescreated = true ;
// When none of the linedef sides exist yet, this is a true new
2009-05-13 20:07:33 +00:00
// sector that will be created out of the void!
2009-04-19 18:07:22 +00:00
bool istruenewsector = true ;
foreach ( LinedefSide ls in sectorlines )
{
if ( ( ls . Front & & ( ls . Line . Front ! = null ) ) | |
( ! ls . Front & & ( ls . Line . Back ! = null ) ) )
{
istruenewsector = false ;
break ;
}
}
2009-05-13 20:07:33 +00:00
// 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 )
2009-04-19 18:07:22 +00:00
{
2009-05-13 20:07:33 +00:00
// Make the new sector
2014-01-13 08:06:56 +00:00
//mxd. Apply sector overrides only if a closed drawing is created
Sector newsector = Tools . MakeSector ( sectorlines , oldlines , ( useOverrides & & drawingclosed & & newlines . Count > 2 ) ) ;
2010-08-15 19:43:00 +00:00
if ( newsector = = null ) return false ;
2009-05-13 20:07:33 +00:00
if ( istruenewsector ) newsector . Marked = true ;
2009-04-19 18:07:22 +00:00
2009-05-13 20:07:33 +00:00
// Go for all sidedefs in this new sector
foreach ( Sidedef sd in newsector . Sidedefs )
2009-04-19 18:07:22 +00:00
{
2009-05-13 20:07:33 +00:00
// 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 ;
}
2009-04-19 18:07:22 +00:00
}
}
}
}
2009-05-06 14:50:17 +00:00
// Exterior not done yet?
if ( ( ld . FrontInterior & & ! backsdone [ i ] ) | | ( ! ld . FrontInterior & & ! frontsdone [ i ] ) )
2009-04-19 18:07:22 +00:00
{
// Find a way to create a sector here
2009-05-06 14:50:17 +00:00
List < LinedefSide > sectorlines = Tools . FindPotentialSectorAt ( ld , ! ld . FrontInterior ) ;
2009-04-19 18:07:22 +00:00
if ( sectorlines ! = null )
{
2009-05-06 14:50:17 +00:00
// Check if any of the surrounding lines originally have sidedefs we can join
2009-04-19 18:07:22 +00:00
Sidedef joinsidedef = null ;
foreach ( LinedefSide ls in sectorlines )
{
if ( ls . Front & & ( ls . Line . Front ! = null ) )
{
joinsidedef = ls . Line . Front ;
break ;
}
2014-01-13 08:06:56 +00:00
if ( ! ls . Front & & ( ls . Line . Back ! = null ) )
2009-04-19 18:07:22 +00:00
{
joinsidedef = ls . Line . Back ;
break ;
}
}
// Join?
if ( joinsidedef ! = null )
{
sidescreated = true ;
2009-05-09 06:49:01 +00:00
// 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 )
2009-04-19 18:07:22 +00:00
{
// Side matches with a side of our new lines?
int lineindex = newlines . IndexOf ( sd . Line ) ;
if ( lineindex > - 1 )
{
2009-05-09 06:49:01 +00:00
// Add to list
newsectorlines . Add ( sd ) ;
2009-04-19 18:07:22 +00:00
// Mark this side as done
2009-05-09 06:49:01 +00:00
if ( sd . Front )
2009-04-19 18:07:22 +00:00
frontsdone [ lineindex ] = true ;
else
backsdone [ lineindex ] = true ;
}
}
2009-05-09 06:49:01 +00:00
// Have our new lines join the existing sector
2014-01-13 08:06:56 +00:00
if ( Tools . JoinSector ( newsectorlines , joinsidedef ) = = null )
2010-08-15 19:43:00 +00:00
return false ;
2009-04-19 18:07:22 +00:00
}
}
}
}
2009-05-06 14:50:17 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * \
Corrections and clean up
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2009-04-19 18:07:22 +00:00
// 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
2013-12-17 13:18:44 +00:00
if ( ( newlines [ i ] . Front ! = null ) | | ( newlines [ i ] . Back ! = null ) ) continue ;
newlines [ i ] . Dispose ( ) ;
2009-04-19 18:07:22 +00:00
}
2014-01-13 08:06:56 +00:00
//mxd. Apply texture overrides
2016-02-07 23:04:20 +00:00
if ( useOverrides )
2014-12-03 23:15:26 +00:00
{
2016-02-07 23:04:20 +00:00
// If new sectors are created, apply overrides to the sides of these sectors, otherwise, apply overrides to all new lines
2015-12-28 15:01:53 +00:00
if ( insidesides . Count > 0 )
2014-12-03 23:15:26 +00:00
{
foreach ( Sidedef side in insidesides ) ApplyOverridesToSidedef ( side ) ;
}
else
{
foreach ( Linedef l in newlines )
{
2014-03-19 13:03:47 +00:00
if ( l . IsDisposed ) continue ;
if ( ! newverts . Contains ( l . Start ) | | ! newverts . Contains ( l . End ) ) continue ;
ApplyOverridesToSidedef ( l . Front ) ;
if ( l . Back ! = null ) ApplyOverridesToSidedef ( l . Back ) ;
}
2014-01-13 08:06:56 +00:00
}
}
2009-04-19 18:07:22 +00:00
2014-03-19 13:03:47 +00:00
//mxd. Auto-align new lines
2014-12-03 23:15:26 +00:00
if ( autoAlignTextureOffsets & & newlines . Count > 1 & & ! splittingonly )
{
2014-03-19 13:03:47 +00:00
List < List < Linedef > > strips = new List < List < Linedef > > ( ) ;
strips . Add ( new List < Linedef > { newlines [ 0 ] } ) ;
2014-12-03 23:15:26 +00:00
for ( int i = 1 ; i < newlines . Count ; i + + )
{
2014-03-19 13:03:47 +00:00
//skip double-sided line if it doesn't have lower or upper parts or they are not part of newly created sectors
2014-12-03 23:15:26 +00:00
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 ) ) ) )
2014-03-19 13:03:47 +00:00
continue ;
bool added = false ;
2014-12-03 23:15:26 +00:00
foreach ( List < Linedef > strip in strips )
{
if ( newlines [ i ] . Start = = strip [ 0 ] . Start | | newlines [ i ] . End = = strip [ 0 ] . Start )
{
2014-03-19 13:03:47 +00:00
strip . Insert ( 0 , newlines [ i ] ) ;
added = true ;
break ;
}
2014-12-03 23:15:26 +00:00
if ( newlines [ i ] . Start = = strip [ strip . Count - 1 ] . End | | newlines [ i ] . End = = strip [ strip . Count - 1 ] . End )
{
2014-03-19 13:03:47 +00:00
strip . Add ( newlines [ i ] ) ;
added = true ;
break ;
}
}
2013-03-18 13:52:27 +00:00
2014-03-19 13:03:47 +00:00
if ( ! added ) strips . Add ( new List < Linedef > { newlines [ i ] } ) ;
}
2014-12-03 23:15:26 +00:00
foreach ( List < Linedef > strip in strips )
{
2014-03-19 13:03:47 +00:00
if ( strip . Count < 2 ) continue ;
2014-12-03 23:15:26 +00:00
AutoAlignLinedefStrip ( strip ) ;
2014-03-19 13:03:47 +00:00
}
}
2013-03-18 13:52:27 +00:00
}
2013-04-02 12:19:25 +00:00
// 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 ;
}
2013-03-18 13:52:27 +00:00
2013-04-02 12:19:25 +00:00
return true ;
}
2013-03-18 13:52:27 +00:00
2014-03-19 13:03:47 +00:00
//mxd
2014-12-03 23:15:26 +00:00
private static void AutoAlignLinedefStrip ( List < Linedef > strip )
{
2015-12-28 15:01:53 +00:00
if ( strip . Count < 2 ) return ;
2014-03-19 13:03:47 +00:00
float totalLength = 0f ;
foreach ( Linedef l in strip ) totalLength + = l . Length ;
2016-09-05 18:36:22 +00:00
if ( General . Map . UDMF & & General . Map . Config . UseLocalSidedefTextureOffsets )
2014-12-03 23:15:26 +00:00
AutoAlignTexturesOnSidesUdmf ( strip , totalLength , ( strip [ 0 ] . End ! = strip [ 1 ] . Start ) ) ;
2014-03-19 13:03:47 +00:00
else
2014-12-03 23:15:26 +00:00
AutoAlignTexturesOnSides ( strip , totalLength , ( strip [ 0 ] . End ! = strip [ 1 ] . Start ) ) ;
2014-03-19 13:03:47 +00:00
}
2013-04-02 12:19:25 +00:00
//mxd
2014-12-03 23:15:26 +00:00
private static void AutoAlignTexturesOnSides ( List < Linedef > lines , float totalLength , bool reversed )
{
2013-04-02 12:19:25 +00:00
float curLength = 0f ;
2014-12-03 23:15:26 +00:00
foreach ( Linedef l in lines )
{
if ( l . Front ! = null )
{
2013-04-02 12:19:25 +00:00
ImageData texture = null ;
2014-12-03 23:15:26 +00:00
if ( l . Front . MiddleRequired ( ) & & l . Front . LongMiddleTexture ! = MapSet . EmptyLongName & & General . Map . Data . GetTextureExists ( l . Front . LongMiddleTexture ) )
2014-03-05 09:21:28 +00:00
texture = General . Map . Data . GetTextureImage ( l . Front . LongMiddleTexture ) ;
2014-12-03 23:15:26 +00:00
else if ( l . Front . HighRequired ( ) & & l . Front . LongHighTexture ! = MapSet . EmptyLongName & & General . Map . Data . GetTextureExists ( l . Front . LongHighTexture ) )
2014-03-05 09:21:28 +00:00
texture = General . Map . Data . GetTextureImage ( l . Front . LongHighTexture ) ;
2014-12-03 23:15:26 +00:00
else if ( l . Front . LowRequired ( ) & & l . Front . LongLowTexture ! = MapSet . EmptyLongName & & General . Map . Data . GetTextureExists ( l . Front . LongLowTexture ) )
2014-03-05 09:21:28 +00:00
texture = General . Map . Data . GetTextureImage ( l . Front . LongLowTexture ) ;
2013-03-18 13:52:27 +00:00
2016-09-02 19:18:37 +00:00
if ( texture ! = null & & texture . IsImageLoaded )
2013-04-02 12:19:25 +00:00
l . Front . OffsetX = ( int ) Math . Round ( ( reversed ? totalLength - curLength - l . Length : curLength ) ) % texture . Width ;
}
2013-03-18 13:52:27 +00:00
2014-12-03 23:15:26 +00:00
if ( l . Back ! = null )
{
2013-04-02 12:19:25 +00:00
ImageData texture = null ;
2013-03-18 13:52:27 +00:00
2014-12-03 23:15:26 +00:00
if ( l . Back . MiddleRequired ( ) & & l . Back . LongMiddleTexture ! = MapSet . EmptyLongName & & General . Map . Data . GetTextureExists ( l . Back . LongMiddleTexture ) )
2014-03-05 09:21:28 +00:00
texture = General . Map . Data . GetTextureImage ( l . Back . LongMiddleTexture ) ;
2014-12-03 23:15:26 +00:00
else if ( l . Back . HighRequired ( ) & & l . Back . LongHighTexture ! = MapSet . EmptyLongName & & General . Map . Data . GetTextureExists ( l . Back . LongHighTexture ) )
2014-03-05 09:21:28 +00:00
texture = General . Map . Data . GetTextureImage ( l . Back . LongHighTexture ) ;
2014-12-03 23:15:26 +00:00
else if ( l . Back . LowRequired ( ) & & l . Back . LongLowTexture ! = MapSet . EmptyLongName & & General . Map . Data . GetTextureExists ( l . Back . LongLowTexture ) )
2014-03-05 09:21:28 +00:00
texture = General . Map . Data . GetTextureImage ( l . Back . LongLowTexture ) ;
2013-04-02 12:19:25 +00:00
2016-09-02 19:18:37 +00:00
if ( texture ! = null & & texture . IsImageLoaded )
2013-04-02 12:19:25 +00:00
l . Back . OffsetX = ( int ) Math . Round ( ( reversed ? totalLength - curLength - l . Length : curLength ) ) % texture . Width ;
2013-03-18 13:52:27 +00:00
}
2013-04-02 12:19:25 +00:00
curLength + = l . Length ;
2009-04-19 18:07:22 +00:00
}
}
2013-04-02 13:42:55 +00:00
2013-04-08 09:01:27 +00:00
//mxd
2014-12-03 23:15:26 +00:00
private static void AutoAlignTexturesOnSidesUdmf ( List < Linedef > lines , float totalLength , bool reversed )
{
2013-04-02 13:42:55 +00:00
float curLength = 0f ;
2014-12-03 23:15:26 +00:00
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 ) )
{
2014-03-05 09:21:28 +00:00
ImageData texture = General . Map . Data . GetTextureImage ( l . Front . LongMiddleTexture ) ;
2016-09-02 19:18:37 +00:00
float offset = ( int ) Math . Round ( ( reversed ? totalLength - curLength - l . Length : curLength ) ) ;
if ( texture . IsImageLoaded ) offset % = texture . Width ;
Removed "Paste Properties Options" action.
Added "Paste Properties Special" actions in "Classic" and "Visual" categories. They work the same way as "Paste Special" action.
Added: "Copy Properties", "Paste Properties" and "Paste Properties Special" options are now shown in the Edit menu if current classic mode supports them.
Changed, Paste Properties Special window: only options relevant to current map format are now displayed.
Changed, Paste Properties Special window, UDMF: all UI-managed options are now available.
Fixed: MAPINFO parser was unable to process "include" directives.
Fixed, General interface: selection info was reset to "Nothing selected" after few seconds regardless of current selection.
Fixed, Visual mode: thing bounding boxes were not updated when changing things positions using Randomize mode.
Fixed, Visual mode: event lines were displayed at incorrect height when entering Visual mode for the first time.
Fixed, Texture Browser window: when MixTexturesFlats Game Configuration option is disabled, textures/flats are no longer shown in the Used group when flats/textures with the same names are used in the map.
Fixed(?): probably fixed an exception some users reported when trying to initialize a Classic mode after switching from Visual mode with "Sync cameras" option enabled.
Changed, Game configurations, Thing Categories: a block must have at least one thing category property to be recognized as a thing category.
Changed, Visplane Explorer: the plugin now outputs more info when it fails to initialize vpo.dll.
Cosmetic, Thing Edit window, Doom/Hexen map format: adjusted UI layout so thing flags control no longer displays scrollbars in Hexen map format.
Internal: merged methods from UDMFTools into UniFields, removed UDMFTools.
Updated Inno Setup script (added VC++ 2008 SP1 distributive).
Updated ZDoom_DECORATE.cfg (A_CheckBlock).
Updated documentation (added "System Requirements" page).
2015-10-09 12:38:12 +00:00
if ( offset > 0 ) UniFields . SetFloat ( l . Front . Fields , "offsetx_mid" , offset ) ;
2013-04-02 13:42:55 +00:00
}
2013-04-08 09:01:27 +00:00
2014-12-03 23:15:26 +00:00
if ( l . Front . HighRequired ( ) & & l . Front . LongHighTexture ! = MapSet . EmptyLongName & & General . Map . Data . GetTextureExists ( l . Front . LongHighTexture ) )
{
2014-03-05 09:21:28 +00:00
ImageData texture = General . Map . Data . GetTextureImage ( l . Front . LongHighTexture ) ;
2016-09-02 19:18:37 +00:00
float offset = ( int ) Math . Round ( ( reversed ? totalLength - curLength - l . Length : curLength ) ) ;
if ( texture . IsImageLoaded ) offset % = texture . Width ;
Removed "Paste Properties Options" action.
Added "Paste Properties Special" actions in "Classic" and "Visual" categories. They work the same way as "Paste Special" action.
Added: "Copy Properties", "Paste Properties" and "Paste Properties Special" options are now shown in the Edit menu if current classic mode supports them.
Changed, Paste Properties Special window: only options relevant to current map format are now displayed.
Changed, Paste Properties Special window, UDMF: all UI-managed options are now available.
Fixed: MAPINFO parser was unable to process "include" directives.
Fixed, General interface: selection info was reset to "Nothing selected" after few seconds regardless of current selection.
Fixed, Visual mode: thing bounding boxes were not updated when changing things positions using Randomize mode.
Fixed, Visual mode: event lines were displayed at incorrect height when entering Visual mode for the first time.
Fixed, Texture Browser window: when MixTexturesFlats Game Configuration option is disabled, textures/flats are no longer shown in the Used group when flats/textures with the same names are used in the map.
Fixed(?): probably fixed an exception some users reported when trying to initialize a Classic mode after switching from Visual mode with "Sync cameras" option enabled.
Changed, Game configurations, Thing Categories: a block must have at least one thing category property to be recognized as a thing category.
Changed, Visplane Explorer: the plugin now outputs more info when it fails to initialize vpo.dll.
Cosmetic, Thing Edit window, Doom/Hexen map format: adjusted UI layout so thing flags control no longer displays scrollbars in Hexen map format.
Internal: merged methods from UDMFTools into UniFields, removed UDMFTools.
Updated Inno Setup script (added VC++ 2008 SP1 distributive).
Updated ZDoom_DECORATE.cfg (A_CheckBlock).
Updated documentation (added "System Requirements" page).
2015-10-09 12:38:12 +00:00
if ( offset > 0 ) UniFields . SetFloat ( l . Front . Fields , "offsetx_top" , offset ) ;
2013-04-08 09:01:27 +00:00
}
2014-12-03 23:15:26 +00:00
if ( l . Front . LowRequired ( ) & & l . Front . LongLowTexture ! = MapSet . EmptyLongName & & General . Map . Data . GetTextureExists ( l . Front . LongLowTexture ) )
{
2014-03-05 09:21:28 +00:00
ImageData texture = General . Map . Data . GetTextureImage ( l . Front . LongLowTexture ) ;
2016-09-02 19:18:37 +00:00
float offset = ( int ) Math . Round ( ( reversed ? totalLength - curLength - l . Length : curLength ) ) ;
if ( texture . IsImageLoaded ) offset % = texture . Width ;
Removed "Paste Properties Options" action.
Added "Paste Properties Special" actions in "Classic" and "Visual" categories. They work the same way as "Paste Special" action.
Added: "Copy Properties", "Paste Properties" and "Paste Properties Special" options are now shown in the Edit menu if current classic mode supports them.
Changed, Paste Properties Special window: only options relevant to current map format are now displayed.
Changed, Paste Properties Special window, UDMF: all UI-managed options are now available.
Fixed: MAPINFO parser was unable to process "include" directives.
Fixed, General interface: selection info was reset to "Nothing selected" after few seconds regardless of current selection.
Fixed, Visual mode: thing bounding boxes were not updated when changing things positions using Randomize mode.
Fixed, Visual mode: event lines were displayed at incorrect height when entering Visual mode for the first time.
Fixed, Texture Browser window: when MixTexturesFlats Game Configuration option is disabled, textures/flats are no longer shown in the Used group when flats/textures with the same names are used in the map.
Fixed(?): probably fixed an exception some users reported when trying to initialize a Classic mode after switching from Visual mode with "Sync cameras" option enabled.
Changed, Game configurations, Thing Categories: a block must have at least one thing category property to be recognized as a thing category.
Changed, Visplane Explorer: the plugin now outputs more info when it fails to initialize vpo.dll.
Cosmetic, Thing Edit window, Doom/Hexen map format: adjusted UI layout so thing flags control no longer displays scrollbars in Hexen map format.
Internal: merged methods from UDMFTools into UniFields, removed UDMFTools.
Updated Inno Setup script (added VC++ 2008 SP1 distributive).
Updated ZDoom_DECORATE.cfg (A_CheckBlock).
Updated documentation (added "System Requirements" page).
2015-10-09 12:38:12 +00:00
if ( offset > 0 ) UniFields . SetFloat ( l . Front . Fields , "offsetx_bottom" , offset ) ;
2013-04-02 13:42:55 +00:00
}
}
2014-12-03 23:15:26 +00:00
if ( l . Back ! = null )
{
if ( l . Back . MiddleRequired ( ) & & l . Back . LongMiddleTexture ! = MapSet . EmptyLongName & & General . Map . Data . GetTextureExists ( l . Back . LongMiddleTexture ) )
{
2014-03-05 09:21:28 +00:00
ImageData texture = General . Map . Data . GetTextureImage ( l . Back . LongMiddleTexture ) ;
2016-09-02 19:18:37 +00:00
float offset = ( int ) Math . Round ( ( reversed ? totalLength - curLength - l . Length : curLength ) ) ;
if ( texture . IsImageLoaded ) offset % = texture . Width ;
Removed "Paste Properties Options" action.
Added "Paste Properties Special" actions in "Classic" and "Visual" categories. They work the same way as "Paste Special" action.
Added: "Copy Properties", "Paste Properties" and "Paste Properties Special" options are now shown in the Edit menu if current classic mode supports them.
Changed, Paste Properties Special window: only options relevant to current map format are now displayed.
Changed, Paste Properties Special window, UDMF: all UI-managed options are now available.
Fixed: MAPINFO parser was unable to process "include" directives.
Fixed, General interface: selection info was reset to "Nothing selected" after few seconds regardless of current selection.
Fixed, Visual mode: thing bounding boxes were not updated when changing things positions using Randomize mode.
Fixed, Visual mode: event lines were displayed at incorrect height when entering Visual mode for the first time.
Fixed, Texture Browser window: when MixTexturesFlats Game Configuration option is disabled, textures/flats are no longer shown in the Used group when flats/textures with the same names are used in the map.
Fixed(?): probably fixed an exception some users reported when trying to initialize a Classic mode after switching from Visual mode with "Sync cameras" option enabled.
Changed, Game configurations, Thing Categories: a block must have at least one thing category property to be recognized as a thing category.
Changed, Visplane Explorer: the plugin now outputs more info when it fails to initialize vpo.dll.
Cosmetic, Thing Edit window, Doom/Hexen map format: adjusted UI layout so thing flags control no longer displays scrollbars in Hexen map format.
Internal: merged methods from UDMFTools into UniFields, removed UDMFTools.
Updated Inno Setup script (added VC++ 2008 SP1 distributive).
Updated ZDoom_DECORATE.cfg (A_CheckBlock).
Updated documentation (added "System Requirements" page).
2015-10-09 12:38:12 +00:00
if ( offset > 0 ) UniFields . SetFloat ( l . Back . Fields , "offsetx_mid" , offset ) ;
2013-04-08 09:01:27 +00:00
}
2014-12-03 23:15:26 +00:00
if ( l . Back . HighRequired ( ) & & l . Back . LongHighTexture ! = MapSet . EmptyLongName & & General . Map . Data . GetTextureExists ( l . Back . LongHighTexture ) )
{
2014-03-05 09:21:28 +00:00
ImageData texture = General . Map . Data . GetTextureImage ( l . Back . LongHighTexture ) ;
2016-09-02 19:18:37 +00:00
float offset = ( int ) Math . Round ( ( reversed ? totalLength - curLength - l . Length : curLength ) ) ;
if ( texture . IsImageLoaded ) offset % = texture . Width ;
Removed "Paste Properties Options" action.
Added "Paste Properties Special" actions in "Classic" and "Visual" categories. They work the same way as "Paste Special" action.
Added: "Copy Properties", "Paste Properties" and "Paste Properties Special" options are now shown in the Edit menu if current classic mode supports them.
Changed, Paste Properties Special window: only options relevant to current map format are now displayed.
Changed, Paste Properties Special window, UDMF: all UI-managed options are now available.
Fixed: MAPINFO parser was unable to process "include" directives.
Fixed, General interface: selection info was reset to "Nothing selected" after few seconds regardless of current selection.
Fixed, Visual mode: thing bounding boxes were not updated when changing things positions using Randomize mode.
Fixed, Visual mode: event lines were displayed at incorrect height when entering Visual mode for the first time.
Fixed, Texture Browser window: when MixTexturesFlats Game Configuration option is disabled, textures/flats are no longer shown in the Used group when flats/textures with the same names are used in the map.
Fixed(?): probably fixed an exception some users reported when trying to initialize a Classic mode after switching from Visual mode with "Sync cameras" option enabled.
Changed, Game configurations, Thing Categories: a block must have at least one thing category property to be recognized as a thing category.
Changed, Visplane Explorer: the plugin now outputs more info when it fails to initialize vpo.dll.
Cosmetic, Thing Edit window, Doom/Hexen map format: adjusted UI layout so thing flags control no longer displays scrollbars in Hexen map format.
Internal: merged methods from UDMFTools into UniFields, removed UDMFTools.
Updated Inno Setup script (added VC++ 2008 SP1 distributive).
Updated ZDoom_DECORATE.cfg (A_CheckBlock).
Updated documentation (added "System Requirements" page).
2015-10-09 12:38:12 +00:00
if ( offset > 0 ) UniFields . SetFloat ( l . Back . Fields , "offsetx_top" , offset ) ;
2013-04-08 09:01:27 +00:00
}
2014-12-03 23:15:26 +00:00
if ( l . Back . LowRequired ( ) & & l . Back . LongLowTexture ! = MapSet . EmptyLongName & & General . Map . Data . GetTextureExists ( l . Back . LongLowTexture ) )
{
2014-03-05 09:21:28 +00:00
ImageData texture = General . Map . Data . GetTextureImage ( l . Back . LongLowTexture ) ;
2016-09-02 19:18:37 +00:00
float offset = ( int ) Math . Round ( ( reversed ? totalLength - curLength - l . Length : curLength ) ) ;
if ( texture . IsImageLoaded ) offset % = texture . Width ;
Removed "Paste Properties Options" action.
Added "Paste Properties Special" actions in "Classic" and "Visual" categories. They work the same way as "Paste Special" action.
Added: "Copy Properties", "Paste Properties" and "Paste Properties Special" options are now shown in the Edit menu if current classic mode supports them.
Changed, Paste Properties Special window: only options relevant to current map format are now displayed.
Changed, Paste Properties Special window, UDMF: all UI-managed options are now available.
Fixed: MAPINFO parser was unable to process "include" directives.
Fixed, General interface: selection info was reset to "Nothing selected" after few seconds regardless of current selection.
Fixed, Visual mode: thing bounding boxes were not updated when changing things positions using Randomize mode.
Fixed, Visual mode: event lines were displayed at incorrect height when entering Visual mode for the first time.
Fixed, Texture Browser window: when MixTexturesFlats Game Configuration option is disabled, textures/flats are no longer shown in the Used group when flats/textures with the same names are used in the map.
Fixed(?): probably fixed an exception some users reported when trying to initialize a Classic mode after switching from Visual mode with "Sync cameras" option enabled.
Changed, Game configurations, Thing Categories: a block must have at least one thing category property to be recognized as a thing category.
Changed, Visplane Explorer: the plugin now outputs more info when it fails to initialize vpo.dll.
Cosmetic, Thing Edit window, Doom/Hexen map format: adjusted UI layout so thing flags control no longer displays scrollbars in Hexen map format.
Internal: merged methods from UDMFTools into UniFields, removed UDMFTools.
Updated Inno Setup script (added VC++ 2008 SP1 distributive).
Updated ZDoom_DECORATE.cfg (A_CheckBlock).
Updated documentation (added "System Requirements" page).
2015-10-09 12:38:12 +00:00
if ( offset > 0 ) UniFields . SetFloat ( l . Back . Fields , "offsetx_bottom" , offset ) ;
2013-04-02 13:42:55 +00:00
}
}
curLength + = l . Length ;
}
}
2009-04-19 18:07:22 +00:00
#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)
2016-04-07 20:01:59 +00:00
public static void FloodfillFlats ( Sector start , bool fillceilings , HashSet < long > originalflats , string fillflat , bool resetsectormarks )
2009-04-19 18:07:22 +00:00
{
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
2016-04-07 20:01:59 +00:00
if ( ( originalflats . Contains ( start . LongFloorTexture ) & & ! fillceilings ) | |
( originalflats . Contains ( start . LongCeilTexture ) & & fillceilings ) )
2009-04-19 18:07:22 +00:00
{
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
2015-02-19 13:00:19 +00:00
if ( fillceilings ) s . SetCeilTexture ( fillflat ) ;
else s . SetFloorTexture ( fillflat ) ;
2009-04-19 18:07:22 +00:00
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
2016-04-07 20:01:59 +00:00
if ( ( originalflats . Contains ( os . LongFloorTexture ) & & ! fillceilings ) | |
( originalflats . Contains ( os . LongCeilTexture ) & & fillceilings ) )
2009-04-19 18:07:22 +00:00
{
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)
2016-04-07 20:01:59 +00:00
public static void FloodfillTextures ( Sidedef start , HashSet < long > originaltextures , string filltexture , bool resetsidemarks )
2009-04-19 18:07:22 +00:00
{
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
2016-04-07 20:01:59 +00:00
if ( SidedefTextureMatch ( start , originaltextures ) )
2009-04-19 18:07:22 +00:00
{
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
2016-04-07 20:01:59 +00:00
if ( j . sidedef . HighRequired ( ) & & originaltextures . Contains ( j . sidedef . LongHighTexture ) ) j . sidedef . SetTextureHigh ( filltexture ) ;
2014-03-05 09:21:28 +00:00
if ( ( j . sidedef . LongMiddleTexture ! = MapSet . EmptyLongName | | j . sidedef . MiddleRequired ( ) ) & &
2016-04-07 20:01:59 +00:00
originaltextures . Contains ( j . sidedef . LongMiddleTexture ) ) j . sidedef . SetTextureMid ( filltexture ) ;
if ( j . sidedef . LowRequired ( ) & & originaltextures . Contains ( j . sidedef . LongLowTexture ) ) j . sidedef . SetTextureLow ( filltexture ) ;
2015-02-19 13:00:19 +00:00
2009-04-19 18:07:22 +00:00
j . sidedef . Marked = true ;
if ( j . forward )
{
// Add sidedefs forward (connected to the right vertex)
2014-05-19 13:33:38 +00:00
Vertex v = j . sidedef . IsFront ? j . sidedef . Line . End : j . sidedef . Line . Start ;
2016-04-07 20:01:59 +00:00
AddSidedefsForFloodfill ( todo , v , true , originaltextures ) ;
2009-04-19 18:07:22 +00:00
// Add sidedefs backward (connected to the left vertex)
v = j . sidedef . IsFront ? j . sidedef . Line . Start : j . sidedef . Line . End ;
2016-04-07 20:01:59 +00:00
AddSidedefsForFloodfill ( todo , v , false , originaltextures ) ;
2009-04-19 18:07:22 +00:00
}
else
{
// Add sidedefs backward (connected to the left vertex)
2014-05-19 13:33:38 +00:00
Vertex v = j . sidedef . IsFront ? j . sidedef . Line . Start : j . sidedef . Line . End ;
2016-04-07 20:01:59 +00:00
AddSidedefsForFloodfill ( todo , v , false , originaltextures ) ;
2009-04-19 18:07:22 +00:00
// Add sidedefs forward (connected to the right vertex)
v = j . sidedef . IsFront ? j . sidedef . Line . End : j . sidedef . Line . Start ;
2016-04-07 20:01:59 +00:00
AddSidedefsForFloodfill ( todo , v , true , originaltextures ) ;
2009-04-19 18:07:22 +00:00
}
}
}
// This adds the matching, unmarked sidedefs from a vertex for texture alignment
2016-04-07 20:01:59 +00:00
private static void AddSidedefsForFloodfill ( Stack < SidedefFillJob > stack , Vertex v , bool forward , HashSet < long > texturelongnames )
2009-04-19 18:07:22 +00:00
{
foreach ( Linedef ld in v . Linedefs )
{
Sidedef side1 = forward ? ld . Front : ld . Back ;
Sidedef side2 = forward ? ld . Back : ld . Front ;
2017-01-26 13:33:07 +00:00
// [ZZ] don't iterate the same linedef twice.
//
if ( ( side1 ! = null & & side1 . Marked ) | |
( side2 ! = null & & side2 . Marked ) ) continue ;
2009-04-19 18:07:22 +00:00
if ( ( ld . Start = = v ) & & ( side1 ! = null ) & & ! side1 . Marked )
{
2016-04-07 20:01:59 +00:00
if ( SidedefTextureMatch ( side1 , texturelongnames ) )
2009-04-19 18:07:22 +00:00
{
SidedefFillJob nj = new SidedefFillJob ( ) ;
nj . forward = forward ;
nj . sidedef = side1 ;
stack . Push ( nj ) ;
}
}
else if ( ( ld . End = = v ) & & ( side2 ! = null ) & & ! side2 . Marked )
{
2016-04-07 20:01:59 +00:00
if ( SidedefTextureMatch ( side2 , texturelongnames ) )
2009-04-19 18:07:22 +00:00
{
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
2016-04-07 20:01:59 +00:00
/ * public static bool SidedefTextureMatch ( Sidedef sd , long texturelongname )
2009-04-19 18:07:22 +00:00
{
return ( ( sd . LongHighTexture = = texturelongname ) & & sd . HighRequired ( ) ) | |
( ( sd . LongLowTexture = = texturelongname ) & & sd . LowRequired ( ) ) | |
2014-03-05 09:21:28 +00:00
( ( sd . LongMiddleTexture = = texturelongname ) & & ( sd . MiddleRequired ( ) | | sd . LongMiddleTexture ! = MapSet . EmptyLongName ) ) ;
2016-04-07 20:01:59 +00:00
} * /
2014-09-23 21:34:50 +00:00
Sectors, Linedefs, Things modes: optimized text label rendering.
Fixed, Things mode: in some cases selection labels were not updated after editing a thing.
Fixed, Things mode: selection labels were positioned incorrectly on things with FixedSize setting.
Fixed, Sectors mode: fixed a crash when selecting self-referencing sector when selection labels were enabled.
Fixed, Visual mode: in some cases Auto-align texture actions were not working when "use long texture names" Map Options setting was enabled.
Fixed, MD2/MD3 loader: available animation frames upper bound check was performed incorrectly, which would cause a crash in some very special cases.
Fixed, Game configurations: most Hexen/ZDoom teleport actions use TeleportDests as teleport targets, not MapSpots.
2016-04-05 22:24:36 +00:00
//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 ) ) ;
}
2014-12-22 21:36:49 +00:00
//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 )
{
2015-12-28 15:01:53 +00:00
switch ( part )
2014-12-22 21:36:49 +00:00
{
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 :
2016-02-22 08:04:06 +00:00
throw new NotSupportedException ( "Tools.GetSidedefOffsetY: \"" + part + "\" geometry type is not supported!" ) ;
2014-12-22 21:36:49 +00:00
}
}
2014-09-23 21:34:50 +00:00
//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 ;
2020-01-02 00:32:55 +00:00
// Make sure the offset doesn't go in the wrong direction
scaleY = Math . Abs ( scaleY ) ;
2014-09-23 21:34:50 +00:00
//if we don't have UpperUnpegged flag, normalize offset
2014-12-22 21:36:49 +00:00
float surfaceHeight = side . GetHighHeight ( ) * scaleY ;
2015-04-16 13:03:12 +00:00
return ( float ) Math . Round ( ( fromNormalized ? offset + surfaceHeight : offset - surfaceHeight ) , General . Map . FormatInterface . VertexDecimals ) ;
2014-09-23 21:34:50 +00:00
}
//mxd. This converts offsetY from/to "normalized" offset for given middle wall
public static float GetSidedefMiddleOffsetY ( Sidedef side , float offset , float scaleY , bool fromNormalized )
{
2014-12-22 21:36:49 +00:00
if ( side . Sector = = null ) return offset ;
2014-09-23 21:34:50 +00:00
2020-01-02 00:32:55 +00:00
// Make sure the offset doesn't go in the wrong direction
scaleY = Math . Abs ( scaleY ) ;
2014-12-22 21:36:49 +00:00
// 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 ;
}
}
2014-09-23 21:34:50 +00:00
2015-04-16 13:03:12 +00:00
return ( float ) Math . Round ( ( fromNormalized ? offset + surfaceHeight : offset - surfaceHeight ) , General . Map . FormatInterface . VertexDecimals ) ;
2014-09-23 21:34:50 +00:00
}
//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 ;
2020-01-02 00:32:55 +00:00
// Make sure the offset doesn't go in the wrong direction
scaleY = Math . Abs ( scaleY ) ;
if ( side . Line . IsFlagSet ( General . Map . Config . LowerUnpeggedFlag ) )
2014-09-23 21:34:50 +00:00
{
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 ;
}
2015-04-16 13:03:12 +00:00
return ( float ) Math . Round ( ( fromNormalized ? offset + surfaceHeight : offset - surfaceHeight ) , General . Map . FormatInterface . VertexDecimals ) ;
2014-09-23 21:34:50 +00:00
}
2009-04-19 18:07:22 +00:00
#endregion
2009-07-09 14:03:47 +00:00
#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 > ( ) ;
2010-01-30 22:20:18 +00:00
2009-07-09 14:03:47 +00:00
// 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
2013-03-18 13:52:27 +00:00
2015-12-14 12:34:31 +00:00
#region = = = = = = = = = = = = = = = = = = Things ( mxd )
2013-03-18 13:52:27 +00:00
2014-12-03 23:15:26 +00:00
public static bool TryAlignThingToLine ( Thing t , Linedef l )
{
if ( l . Back = = null )
{
if ( CanAlignThingTo ( t , l . Front . Sector ) )
{
AlignThingToLine ( t , l , true ) ;
2013-03-18 13:52:27 +00:00
return true ;
}
return false ;
}
2014-12-03 23:15:26 +00:00
if ( l . Front = = null )
{
if ( CanAlignThingTo ( t , l . Back . Sector ) )
{
AlignThingToLine ( t , l , false ) ;
2013-03-18 13:52:27 +00:00
return true ;
}
return false ;
}
float side = l . SideOfLine ( t . Position ) ;
2014-12-03 23:15:26 +00:00
//already on line
if ( side = = 0 )
{
2013-03-18 13:52:27 +00:00
t . Rotate ( General . ClampAngle ( 180 + l . AngleDeg ) ) ;
return true ;
}
2014-12-03 23:15:26 +00:00
//thing is on front side of the line
if ( side < 0 )
{
2013-03-18 13:52:27 +00:00
//got any walls to align to?
2016-07-04 18:25:47 +00:00
if ( ( l . Front . LongMiddleTexture ! = MapSet . EmptyLongName & & CanAlignThingTo ( t , l . Front . Sector ) )
| | CanAlignThingTo ( t , l . Front . Sector , l . Back . Sector ) )
2014-12-03 23:15:26 +00:00
{
AlignThingToLine ( t , l , true ) ;
2013-03-18 13:52:27 +00:00
return true ;
}
return false ;
}
//thing is on back side of the line
//got any walls to align to?
2016-07-04 18:25:47 +00:00
if ( ( l . Back . LongMiddleTexture ! = MapSet . EmptyLongName & & CanAlignThingTo ( t , l . Back . Sector ) )
| | CanAlignThingTo ( t , l . Back . Sector , l . Front . Sector ) )
2014-12-03 23:15:26 +00:00
{
AlignThingToLine ( t , l , false ) ;
2013-03-18 13:52:27 +00:00
return true ;
}
return false ;
}
2014-02-07 09:10:55 +00:00
// Checks if there's a wall at appropriate height to align thing to
2014-12-03 23:15:26 +00:00
private static bool CanAlignThingTo ( Thing t , Sector front , Sector back )
{
2013-03-18 13:52:27 +00:00
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 ) ;
2014-12-03 23:15:26 +00:00
if ( front . FloorHeight < back . FloorHeight )
{
2013-03-18 13:52:27 +00:00
Rectangle lower = new Rectangle ( 0 , front . FloorHeight , 1 , back . FloorHeight - front . FloorHeight ) ;
2014-02-07 09:10:55 +00:00
if ( thing . IntersectsWith ( lower ) ) return true ;
2013-03-18 13:52:27 +00:00
}
2014-12-03 23:15:26 +00:00
if ( front . CeilHeight > back . CeilHeight )
{
2013-03-18 13:52:27 +00:00
Rectangle upper = new Rectangle ( 0 , back . CeilHeight , 1 , front . CeilHeight - back . CeilHeight ) ;
2014-02-07 09:10:55 +00:00
if ( thing . IntersectsWith ( upper ) ) return true ;
2013-03-18 13:52:27 +00:00
}
return false ;
}
2014-02-07 09:10:55 +00:00
// Checks if there's a wall at appropriate height to align thing to
2014-12-03 23:15:26 +00:00
private static bool CanAlignThingTo ( Thing t , Sector sector )
{
2013-03-18 13:52:27 +00:00
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 ) ;
}
2014-12-03 23:15:26 +00:00
private static void AlignThingToLine ( Thing t , Linedef l , bool front )
{
2013-03-18 13:52:27 +00:00
//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
2014-12-03 23:15:26 +00:00
if ( initialSector ! = t . Sector & & General . Map . FormatInterface . HasThingHeight )
{
2013-03-18 13:52:27 +00:00
ThingTypeInfo ti = General . Map . Data . GetThingInfo ( t . Type ) ;
if ( ti . AbsoluteZ ) return ;
2014-12-03 23:15:26 +00:00
if ( ti . Hangs & & initialSector . CeilHeight ! = t . Sector . CeilHeight )
{
2013-03-18 13:52:27 +00:00
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 ) ) ;
}
}
2014-12-03 23:15:26 +00:00
public static int GetThingAbsoluteZ ( Thing t , ThingTypeInfo ti )
{
2013-03-18 13:52:27 +00:00
// Determine z info
2014-02-07 09:10:55 +00:00
if ( ti . AbsoluteZ ) return ( int ) t . Position . z ;
2013-03-18 13:52:27 +00:00
2014-12-03 23:15:26 +00:00
if ( t . Sector ! = null )
{
2014-02-07 09:10:55 +00:00
// 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 ;
2013-03-18 13:52:27 +00:00
}
#endregion
2016-05-18 20:31:40 +00:00
#region = = = = = = = = = = = = = = = = = = Sectors ( mxd )
public static void SplitOuterSectors ( IEnumerable < Linedef > drawnlines )
{
Dictionary < Sector , HashSet < Sidedef > > sectorsidesref = new Dictionary < Sector , HashSet < Sidedef > > ( ) ;
2016-06-13 23:37:55 +00:00
HashSet < Sidedef > drawnsides = new HashSet < Sidedef > ( ) ;
2016-05-18 20:31:40 +00:00
// Create drawn lines per sector collection
foreach ( Linedef l in drawnlines )
{
2016-05-20 15:04:00 +00:00
if ( l . Front ! = null & & ( l . Front . Sector ! = null & & ! SectorWasInvalid ( l . Front . Sector ) ) )
2016-05-18 20:31:40 +00:00
{
2016-10-31 18:52:29 +00:00
// Add only multipart sectors
if ( l . Front . Sector . Triangles . IslandVertices . Count > 1 )
{
if ( ! sectorsidesref . ContainsKey ( l . Front . Sector ) ) sectorsidesref [ l . Front . Sector ] = new HashSet < Sidedef > ( ) ;
sectorsidesref [ l . Front . Sector ] . Add ( l . Front ) ;
}
2016-06-13 23:37:55 +00:00
drawnsides . Add ( l . Front ) ;
2016-05-18 20:31:40 +00:00
}
2016-05-20 15:04:00 +00:00
if ( l . Back ! = null & & ( l . Back . Sector ! = null & & ! SectorWasInvalid ( l . Back . Sector ) ) )
2016-05-18 20:31:40 +00:00
{
2016-10-31 18:52:29 +00:00
// Add only multipart sectors
if ( l . Back . Sector . Triangles . IslandVertices . Count > 1 )
{
if ( ! sectorsidesref . ContainsKey ( l . Back . Sector ) ) sectorsidesref [ l . Back . Sector ] = new HashSet < Sidedef > ( ) ;
sectorsidesref [ l . Back . Sector ] . Add ( l . Back ) ;
}
2016-06-13 23:37:55 +00:00
drawnsides . Add ( l . Back ) ;
2016-05-18 20:31:40 +00:00
}
}
// 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
2016-06-13 23:37:55 +00:00
List < LinedefSide > linedefsides = FindPotentialSectorAt ( side . Line , side . IsFront ) ;
2016-05-18 20:31:40 +00:00
// Number of potential sides fewer than the sector has?
2016-06-13 23:37:55 +00:00
if ( linedefsides ! = null & & linedefsides . Count > 0 & & linedefsides . Count < group . Key . Sidedefs . Count )
2016-05-18 20:31:40 +00:00
{
2016-06-13 23:37:55 +00:00
// Collect sidedefs from new sector shape...
HashSet < Sidedef > newsectorsides = new HashSet < Sidedef > ( ) ;
foreach ( LinedefSide ls in linedefsides )
2016-05-18 20:31:40 +00:00
{
2016-06-13 23:37:55 +00:00
Sidedef s = ( ls . Front ? ls . Line . Front : ls . Line . Back ) ;
if ( s ! = null ) newsectorsides . Add ( s ) ;
2016-05-18 20:31:40 +00:00
}
2016-05-20 15:04:00 +00:00
2016-06-13 23:37:55 +00:00
// 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 ;
}
}
2016-05-18 20:31:40 +00:00
}
}
}
}
2016-05-20 15:04:00 +00:00
private static bool SectorWasInvalid ( Sector s )
{
2016-09-10 20:45:41 +00:00
if ( s . Sidedefs = = null | | s . Sidedefs . Count < 3 | | s . FlatVertices . Length < 3 )
2016-05-20 15:04:00 +00:00
{
// Collect changed lines
HashSet < Linedef > changedlines = new HashSet < Linedef > ( ) ;
2016-09-10 20:45:41 +00:00
if ( s . Sidedefs ! = null )
{
foreach ( Sidedef side in s . Sidedefs ) changedlines . Add ( side . Line ) ;
}
2016-05-20 15:04:00 +00:00
// 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 ;
}
2016-05-18 20:31:40 +00:00
#endregion
2014-07-07 14:17:49 +00:00
#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 )
{
2016-03-08 20:37:44 +00:00
HashSet < Linedef > processed = new HashSet < Linedef > ( ) ;
2014-07-07 14:17:49 +00:00
foreach ( Sector s in sectors )
{
List < Linedef > frontlines = new List < Linedef > ( ) ;
List < Linedef > backlines = new List < Linedef > ( ) ;
2015-05-26 20:10:09 +00:00
int unselectedfrontlines = 0 ;
int unselectedbacklines = 0 ;
2014-07-07 14:17:49 +00:00
//sort lines
2015-05-26 20:10:09 +00:00
foreach ( Sidedef side in s . Sidedefs )
2014-07-07 14:17:49 +00:00
{
2016-03-08 20:37:44 +00:00
if ( processed . Contains ( side . Line ) ) continue ;
2015-05-26 20:10:09 +00:00
if ( selectedlinesonly & & ! side . Line . Selected )
{
if ( side = = side . Line . Front ) unselectedfrontlines + + ;
else unselectedbacklines + + ;
continue ;
}
2014-07-07 14:17:49 +00:00
2015-05-26 20:10:09 +00:00
if ( side = = side . Line . Front )
2014-07-07 14:17:49 +00:00
frontlines . Add ( side . Line ) ;
else
backlines . Add ( side . Line ) ;
2016-03-08 20:37:44 +00:00
processed . Add ( side . Line ) ;
2014-07-07 14:17:49 +00:00
}
//flip lines
2015-05-26 20:10:09 +00:00
if ( frontlines . Count = = 0 | | ( frontlines . Count + unselectedfrontlines > backlines . Count + unselectedbacklines & & backlines . Count > 0 ) )
2014-07-07 14:17:49 +00:00
{
2015-05-26 20:10:09 +00:00
foreach ( Linedef l in backlines )
2014-07-07 14:17:49 +00:00
{
l . FlipVertices ( ) ;
l . FlipSidedefs ( ) ;
}
}
else
{
foreach ( Linedef l in frontlines )
{
2016-07-20 19:17:31 +00:00
// Skip single-sided lines with only front side
if ( l . Back ! = null )
{
l . FlipVertices ( ) ;
l . FlipSidedefs ( ) ;
}
2014-07-07 14:17:49 +00:00
}
}
}
}
#endregion
2015-02-12 22:04:49 +00:00
#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 ;
}
#endregion
2010-01-30 20:46:51 +00:00
#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 )
{
2019-08-09 21:22:16 +00:00
return RenderDevice . V2D ( Vector2 . Hermite ( RenderDevice . V2 ( p1 ) , RenderDevice . V2 ( t1 ) , RenderDevice . V2 ( p2 ) , RenderDevice . V2 ( t2 ) , u ) ) ;
2010-01-30 20:46:51 +00:00
}
/// <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 )
{
2019-08-09 21:22:16 +00:00
return RenderDevice . V3D ( Vector3 . Hermite ( RenderDevice . V3 ( p1 ) , RenderDevice . V3 ( t1 ) , RenderDevice . V3 ( p2 ) , RenderDevice . V3 ( t2 ) , u ) ) ;
2010-01-30 20:46:51 +00:00
}
2013-08-13 09:49:29 +00:00
//mxd
2014-12-03 23:15:26 +00:00
public static int GetDropDownWidth ( ComboBox cb )
{
2015-12-28 15:01:53 +00:00
int maxwidth = 0 ;
foreach ( var obj in cb . Items )
2014-12-03 23:15:26 +00:00
{
2015-12-28 15:01:53 +00:00
int temp = TextRenderer . MeasureText ( obj . ToString ( ) , cb . Font ) . Width ;
if ( temp > maxwidth ) maxwidth = temp ;
2013-08-13 09:49:29 +00:00
}
2015-12-28 15:01:53 +00:00
return maxwidth > 0 ? maxwidth + 6 : 1 ;
2013-08-13 09:49:29 +00:00
}
2015-02-20 13:22:43 +00:00
//mxd
2016-06-03 20:22:07 +00:00
public static PixelColor GetSectorFadeColor ( Sector s )
2015-02-20 13:22:43 +00:00
{
2016-06-03 20:22:07 +00:00
if ( s . Fields . ContainsKey ( "fadecolor" ) ) return PixelColor . FromInt ( s . Fields . GetValue ( "fadecolor" , 0 ) ) ;
2015-02-20 13:22:43 +00:00
if ( General . Map . Data . MapInfo . HasOutsideFogColor & & s . CeilTexture = = General . Map . Config . SkyFlatName )
2016-06-03 20:22:07 +00:00
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 ) ;
2015-02-20 13:22:43 +00:00
}
2010-01-30 20:46:51 +00:00
#endregion
2009-04-19 18:07:22 +00:00
}
}