- added SectorMaker (unfinished)

- changed a lot in data management
- less memory usage by texture browsers
This commit is contained in:
codeimp 2008-05-05 14:22:36 +00:00
parent 5a5f113855
commit 1ada9addf3
16 changed files with 814 additions and 408 deletions

View file

@ -95,6 +95,7 @@
<Compile Include="Geometry\Angle2D.cs" />
<Compile Include="Geometry\LinedefsTracePath.cs" />
<Compile Include="Geometry\LinedefAngleSorter.cs" />
<Compile Include="Geometry\SectorMaker.cs" />
<Compile Include="Geometry\Triangulator.cs" />
<Compile Include="Geometry\EarClipVertex.cs" />
<Compile Include="Geometry\Line2D.cs" />
@ -302,7 +303,7 @@
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Map\Linedef.cs" />
<Compile Include="Map\LinedefSide.cs" />
<Compile Include="Geometry\LinedefSide.cs" />
<Compile Include="Map\MapOptions.cs" />
<Compile Include="Map\MapSet.cs" />
<Compile Include="Data\DataLocation.cs" />

View file

@ -193,7 +193,8 @@ namespace CodeImp.DoomBuilder.BuilderModes.Editing
if(!frontsdone[i])
{
// Make sector here
Sector newsector = map.MakeSector(ld, true);
SectorMaker maker = new SectorMaker();
Sector newsector = maker.MakeAt(ld, true);
if(newsector != null)
{
// Go for all sidedefs in this new sector

View file

@ -51,7 +51,6 @@ namespace CodeImp.DoomBuilder.Config
private float visualmousesensy;
private float visualviewrange;
private int imagebrightness;
private bool backgroundload;
private bool qualitydisplay;
#endregion
@ -67,7 +66,6 @@ namespace CodeImp.DoomBuilder.Config
public float VisualMouseSensX { get { return visualmousesensx; } internal set { visualmousesensx = value; } }
public float VisualMouseSensY { get { return visualmousesensy; } internal set { visualmousesensy = value; } }
public float VisualViewRange { get { return visualviewrange; } internal set { visualviewrange = value; } }
public bool BackgroundLoading { get { return backgroundload; } internal set { backgroundload = value; } }
public bool QualityDisplay { get { return qualitydisplay; } internal set { qualitydisplay = value; } }
#endregion
@ -100,7 +98,6 @@ namespace CodeImp.DoomBuilder.Config
visualmousesensy = cfg.ReadSetting("visualmousesensy", 40f);
visualviewrange = cfg.ReadSetting("visualviewrange", 1000f);
imagebrightness = cfg.ReadSetting("imagebrightness", 3);
backgroundload = cfg.ReadSetting("backgroundload", true);
qualitydisplay = cfg.ReadSetting("qualitydisplay", true);
// Success
@ -125,7 +122,6 @@ namespace CodeImp.DoomBuilder.Config
cfg.WriteSetting("visualmousesensy", visualmousesensy);
cfg.WriteSetting("visualviewrange", visualviewrange);
cfg.WriteSetting("imagebrightness", imagebrightness);
cfg.WriteSetting("backgroundload", backgroundload);
cfg.WriteSetting("qualitydisplay", qualitydisplay);
// Save settings configuration

View file

@ -60,6 +60,7 @@ namespace CodeImp.DoomBuilder.Data
private Dictionary<long, ImageData> sprites;
// Background loading
private LinkedList<ImageData> loadlist;
private Thread backgroundloader;
// Special images
@ -78,9 +79,23 @@ namespace CodeImp.DoomBuilder.Data
public List<string> TextureNames { get { return texturenames; } }
public List<string> FlatNames { get { return flatnames; } }
public bool IsDisposed { get { return isdisposed; } }
public bool IsLoading { get { return (backgroundloader != null) && backgroundloader.IsAlive; } }
public ImageData MissingTexture3D { get { return missingtexture3d; } }
public bool IsLoading
{
get
{
if(loadlist != null)
{
return (backgroundloader != null) && backgroundloader.IsAlive && (loadlist.Count > 0);
}
else
{
return false;
}
}
}
#endregion
#region ================== Constructor / Disposer
@ -134,6 +149,7 @@ namespace CodeImp.DoomBuilder.Data
// This loads all data resources
internal void Load(DataLocationList locations)
{
int texcount, flatcount, spritecount;
DataReader c;
// Create collections
@ -143,6 +159,7 @@ namespace CodeImp.DoomBuilder.Data
sprites = new Dictionary<long, ImageData>();
texturenames = new List<string>();
flatnames = new List<string>();
loadlist = new LinkedList<ImageData>();
// Go for all locations
foreach(DataLocation dl in locations)
@ -187,17 +204,20 @@ namespace CodeImp.DoomBuilder.Data
}
// Load stuff
General.WriteLogLine("Loading palette...");
LoadPalette();
General.WriteLogLine("Loading textures...");
LoadTextures();
General.WriteLogLine("Loading flats...");
LoadFlats();
General.WriteLogLine("Loading sprites...");
LoadSprites();
texcount = LoadTextures();
flatcount = LoadFlats();
spritecount = LoadSprites();
// Sort names
texturenames.Sort();
flatnames.Sort();
// Start background loading
StartBackgroundLoader();
// Output info
General.WriteLogLine("Loaded " + texcount + " textures, " + flatcount + " flats, " + spritecount + " sprites");
}
// This unloads all data
@ -215,6 +235,15 @@ namespace CodeImp.DoomBuilder.Data
// Dispose containers
foreach(DataReader c in containers) c.Dispose();
containers.Clear();
// Trash collections
containers = null;
textures = null;
flats = null;
sprites = null;
texturenames = null;
flatnames = null;
loadlist = null;
}
#endregion
@ -269,21 +298,19 @@ namespace CodeImp.DoomBuilder.Data
// If a loader is already running, stop it first
if(backgroundloader != null) StopBackgroundLoader();
// Only do background loading when preferred
if(General.Settings.BackgroundLoading)
{
// Start a low priority thread to load images in background
General.WriteLogLine("Starting background resource loading...");
backgroundloader = new Thread(new ThreadStart(BackgroundLoad));
backgroundloader.Name = "BackgroundLoader";
backgroundloader.Priority = ThreadPriority.Lowest;
backgroundloader.Start();
}
// Start a low priority thread to load images in background
General.WriteLogLine("Starting background resource loading...");
backgroundloader = new Thread(new ThreadStart(BackgroundLoad));
backgroundloader.Name = "BackgroundLoader";
backgroundloader.Priority = ThreadPriority.Lowest;
backgroundloader.Start();
}
// This stops background loading
private void StopBackgroundLoader()
{
LinkedListNode<ImageData> n;
General.WriteLogLine("Stopping background resource loading...");
if(backgroundloader != null)
{
@ -291,83 +318,126 @@ namespace CodeImp.DoomBuilder.Data
backgroundloader.Interrupt();
backgroundloader.Join();
// Reset load states on all images in the list
n = loadlist.First;
while(n != null)
{
n.Value.LoadState = ImageData.LOADSTATE_NONE;
n.Value.LoadingTicket = null;
n = n.Next;
}
loadlist.Clear();
// Done
backgroundloader = null;
General.MainWindow.UpdateStatusIcon();
}
}
// The background loader
private void BackgroundLoad()
{
int starttime = General.Clock.GetCurrentTime();
int deltatime;
try
{
// Load all lists
LoadImagesList(textures);
LoadImagesList(flats);
LoadImagesList(sprites);
do
{
// Get next item
ImageData image = null;
lock(loadlist)
{
// Anything to do?
if(loadlist.Count > 0)
{
// Fetch image
image = loadlist.First.Value;
image.LoadingTicket = null;
loadlist.RemoveFirst();
// Load or unload this image?
switch(image.LoadState)
{
// Load image
case ImageData.LOADSTATE_LOAD:
image.LoadImage();
//image.CreateTexture(); // Impossible from different thread
break;
// Unload image
case ImageData.LOADSTATE_TRASH:
image.UnloadImage();
break;
}
}
}
// Did we do something?
if(image != null)
{
// Wait a bit and update icon
General.MainWindow.UpdateStatusIcon();
Thread.Sleep(1);
}
else
{
// Wait longer to release CPU resources
Thread.Sleep(50);
}
}
while(true);
}
catch(ThreadInterruptedException)
{
return;
}
}
// Done
deltatime = General.Clock.GetCurrentTime() - starttime;
General.WriteLogLine("Background resource loading completed in " + deltatime + "ms");
General.WriteLogLine("Loaded " + textures.Count + " textures, " + flats.Count + " flats, " + sprites.Count + " sprites");
backgroundloader = null;
// This adds an image for background loading or unloading
public void BackgroundLoadImage(ImageData img, bool load)
{
int loadstate = load ? ImageData.LOADSTATE_LOAD : ImageData.LOADSTATE_TRASH;
lock(loadlist)
{
// Already in the list?
if(img.LoadingTicket != null)
{
// Just change the state
img.LoadState = loadstate;
}
else
{
// Set load state and add to list
img.LoadState = loadstate;
img.LoadingTicket = loadlist.AddLast(img);
}
}
// Update icon
General.MainWindow.UpdateStatusIcon();
}
// This loads a list of ImageData
private void LoadImagesList(Dictionary<long, ImageData> list)
// This removes an image from background loading
// This does not work for images that are being unloaded!
public void BackgroundCancelImage(ImageData img)
{
Dictionary<long, ImageData>.Enumerator walker;
bool moveresult = false;
bool interrupted = false;
do
// Queued?
if(img.LoadingTicket != null)
{
// Get enumerator
lock(list)
// Not being trashed?
if(img.LoadState != ImageData.LOADSTATE_TRASH)
{
walker = list.GetEnumerator();
moveresult = walker.MoveNext();
}
// Continue until at end of list
while(moveresult)
{
lock(list)
lock(loadlist)
{
// Load image
walker.Current.Value.LoadImage();
//walker.Current.Value.CreateTexture(); // Impossible from different thread
}
// Wait a bit
Thread.Sleep(1);
lock(list)
{
try
{
// Move to next item
moveresult = walker.MoveNext();
}
catch(InvalidOperationException)
{
// List was modified, restart!
interrupted = true;
break;
}
// Remove it from queue
LinkedListNode<ImageData> ticket = img.LoadingTicket;
img.LoadingTicket = null;
loadlist.Remove(ticket);
}
// Update icon
General.MainWindow.UpdateStatusIcon();
}
}
while(interrupted);
}
#endregion
@ -398,11 +468,12 @@ namespace CodeImp.DoomBuilder.Data
#region ================== Textures
// This loads the textures
private void LoadTextures()
private int LoadTextures()
{
ICollection<ImageData> images;
PatchNames pnames = new PatchNames();
PatchNames newpnames;
int counter = 0;
// Go for all opened containers
foreach(DataReader dr in containers)
@ -425,6 +496,7 @@ namespace CodeImp.DoomBuilder.Data
if(!textures.ContainsKey(img.LongName)) texturenames.Add(img.Name);
textures.Remove(img.LongName);
textures.Add(img.LongName, img);
counter++;
// Also add as flat when using mixed resources
if(General.Map.Config.MixTexturesFlats)
@ -436,6 +508,9 @@ namespace CodeImp.DoomBuilder.Data
}
}
}
// Output info
return counter;
}
// This returns a specific patch stream
@ -466,19 +541,16 @@ namespace CodeImp.DoomBuilder.Data
// This returns an image by long
public ImageData GetTextureImage(long longname)
{
lock(textures)
// Does this texture exist?
if(textures.ContainsKey(longname))
{
// Does this texture exist?
if(textures.ContainsKey(longname))
{
// Return texture
return textures[longname];
}
else
{
// Return null image
return new NullImage();
}
// Return texture
return textures[longname];
}
else
{
// Return null image
return new NullImage();
}
}
@ -521,10 +593,11 @@ namespace CodeImp.DoomBuilder.Data
#region ================== Flats
// This loads the flats
private void LoadFlats()
private int LoadFlats()
{
ICollection<ImageData> images;
int counter = 0;
// Go for all opened containers
foreach(DataReader dr in containers)
{
@ -539,6 +612,7 @@ namespace CodeImp.DoomBuilder.Data
if(!flats.ContainsKey(img.LongName)) flatnames.Add(img.Name);
flats.Remove(img.LongName);
flats.Add(img.LongName, img);
counter++;
// Also add as texture when using mixed resources
if(General.Map.Config.MixTexturesFlats)
@ -550,6 +624,9 @@ namespace CodeImp.DoomBuilder.Data
}
}
}
// Output info
return counter;
}
// This returns a specific flat stream
@ -580,19 +657,16 @@ namespace CodeImp.DoomBuilder.Data
// This returns an image by long
public ImageData GetFlatImage(long longname)
{
lock(flats)
// Does this flat exist?
if(flats.ContainsKey(longname))
{
// Does this flat exist?
if(flats.ContainsKey(longname))
{
// Return flat
return flats[longname];
}
else
{
// Return null image
return new NullImage();
}
// Return flat
return flats[longname];
}
else
{
// Return null image
return new NullImage();
}
}
@ -635,7 +709,7 @@ namespace CodeImp.DoomBuilder.Data
#region ================== Sprites
// This loads the sprites
private void LoadSprites()
private int LoadSprites()
{
Stream spritedata = null;
SpriteImage image;
@ -665,6 +739,9 @@ namespace CodeImp.DoomBuilder.Data
}
}
}
// Output info
return sprites.Count;
}
// This returns an image by long
@ -674,41 +751,38 @@ namespace CodeImp.DoomBuilder.Data
long longname = Lump.MakeLongName(name);
SpriteImage image;
lock(sprites)
// Sprite already loaded?
if(sprites.ContainsKey(longname))
{
// Sprite already loaded?
if(sprites.ContainsKey(longname))
// Return exiting sprite
return sprites[longname];
}
else
{
// Go for all opened containers
for(int i = containers.Count - 1; i >= 0; i--)
{
// Return exiting sprite
return sprites[longname];
// This contain provides this sprite?
spritedata = containers[i].GetSpriteData(name);
if(spritedata != null) break;
}
// Found anything?
if(spritedata != null)
{
// Make new sprite image
image = new SpriteImage(name);
// Add to collection
sprites.Add(longname, image);
// Return result
return image;
}
else
{
// Go for all opened containers
for(int i = containers.Count - 1; i >= 0; i--)
{
// This contain provides this sprite?
spritedata = containers[i].GetSpriteData(name);
if(spritedata != null) break;
}
// Found anything?
if(spritedata != null)
{
// Make new sprite image
image = new SpriteImage(name);
// Add to collection
sprites.Add(longname, image);
// Return result
return image;
}
else
{
// Return null image
return new NullImage();
}
// Return null image
return new NullImage();
}
}
}

View file

@ -37,6 +37,10 @@ namespace CodeImp.DoomBuilder.Data
{
#region ================== Constants
internal const int LOADSTATE_NONE = 0;
internal const int LOADSTATE_LOAD = 1;
internal const int LOADSTATE_TRASH = 2;
#endregion
#region ================== Variables
@ -50,6 +54,11 @@ namespace CodeImp.DoomBuilder.Data
protected float scaledheight;
protected bool usecolorcorrection;
// Background loading
private LinkedListNode<ImageData> loadingticket;
private int loadstate; // true when loading, false when unloading
private bool temporary;
// GDI bitmap
protected Bitmap bitmap;
@ -74,6 +83,9 @@ namespace CodeImp.DoomBuilder.Data
public Texture Texture { get { lock(this) { return texture; } } }
public bool IsLoaded { get { return (bitmap != null); } }
public bool IsDisposed { get { return isdisposed; } }
internal bool Temporary { get { return temporary; } set { temporary = value; } }
internal int LoadState { get { return loadstate; } set { loadstate = value; } }
internal LinkedListNode<ImageData> LoadingTicket { get { return loadingticket; } set { loadingticket = value; } }
public int Width { get { return width; } }
public int Height { get { return height; } }
public float ScaledWidth { get { return scaledwidth; } }
@ -132,6 +144,7 @@ namespace CodeImp.DoomBuilder.Data
{
if(bitmap != null) bitmap.Dispose();
bitmap = null;
loadstate = ImageData.LOADSTATE_NONE;
}
}
@ -163,6 +176,9 @@ namespace CodeImp.DoomBuilder.Data
}
bitmap.UnlockBits(bmpdata);
}
// Done, reset load state
loadstate = ImageData.LOADSTATE_NONE;
}
// This creates the 2D pixel data

View file

@ -25,10 +25,11 @@ using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Rendering;
using SlimDX.Direct3D;
using System.Drawing;
using CodeImp.DoomBuilder.Map;
#endregion
namespace CodeImp.DoomBuilder.Map
namespace CodeImp.DoomBuilder.Geometry
{
/// <summary>
/// This is used to indicate a side of a line without the need for a sidedef.

View file

@ -69,6 +69,13 @@ namespace CodeImp.DoomBuilder.Geometry
#region ================== Methods
// This merges a polygon into this one
public void Add(Polygon p)
{
// Initialize
foreach(EarClipVertex v in p) base.AddLast(v);
}
// Point inside the polygon?
// See: http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/
public bool Intersect(Vector2D p)

View file

@ -0,0 +1,376 @@
#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Rendering;
using SlimDX.Direct3D;
using System.Drawing;
using CodeImp.DoomBuilder.Map;
#endregion
namespace CodeImp.DoomBuilder.Geometry
{
/// <summary>
/// This makes a sector from all surrounding lines from a given coordinate.
/// Automatically finds the sidedef/sector properties from surrounding sectors/sidedefs.
/// </summary>
public class SectorMaker
{
#region ================== Constants
#endregion
#region ================== Variables
#endregion
#region ================== Properties
#endregion
#region ================== Constructor / Destructor
/// <summary>
/// This makes a sector from all surrounding lines from a given coordinate.
/// Automatically finds the sidedef/sector properties from surrounding sectors/sidedefs.
/// </summary>
public SectorMaker()
{
// Initialize
// We have no destructor
GC.SuppressFinalize(this);
}
#endregion
#region ================== Methods
/// <summary>
/// This makes a sector at the given coordinates, or returns null
/// when sector could not be created.
/// </summary>
public Sector MakeAt(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 MakeAt(l, (l.SideOfLine(pos) <= 0));
}
/// <summary>
/// This makes a sector starting at the given line and side, or
/// returns null when sector could not be created.
/// </summary>
public Sector MakeAt(Linedef line, bool front)
{
List<LinedefSide> alllines = new List<LinedefSide>();
// STEP 1: Find the outer lines
Polygon p = FindOuterLines(line, front, alllines);
if(p != null)
{
// STEP 2: Find the inner lines
FindInnerLines(p, alllines);
// STEP 3: Make the sector
return MakeSector(alllines);
}
else
return null;
}
#endregion
#region ================== Pathfinding
// This finds the inner lines of the sector and adds them to the sector polygon
private void FindInnerLines(Polygon p, List<LinedefSide> alllines)
{
Vertex foundv;
bool vvalid, findmore;
Linedef foundline;
float foundangle = 0f;
bool foundlinefront;
do
{
findmore = false;
// Go for all vertices to find the right-most vertex inside the polygon
foundv = null;
foreach(Vertex v in General.Map.Map.Vertices)
{
// More to the right?
if((foundv == null) || (v.X >= foundv.X))
{
// Vertex is inside the polygon?
if(p.Intersect(v.Position))
{
// Vertex has lines attached?
if(v.Linedefs.Count > 0)
{
// Go for all lines to see if the vertex is not of the polygon itsself
vvalid = true;
foreach(LinedefSide ls in alllines)
{
if((ls.Line.Start == v) || (ls.Line.End == v))
{
vvalid = false;
break;
}
}
// Valid vertex?
if(vvalid) foundv = v;
}
}
}
}
// Found a vertex inside the polygon?
if(foundv != null)
{
// Find the attached linedef with the smallest angle to the right
float targetangle = Angle2D.PIHALF;
foundline = null;
foreach(Linedef l in foundv.Linedefs)
{
// We need an angle unrelated to line direction, so correct for that
float lineangle = l.Angle;
if(l.End == foundv) lineangle += Angle2D.PI;
// Better result?
float deltaangle = Angle2D.Difference(targetangle, lineangle);
if((foundline == null) || (deltaangle < foundangle))
{
foundline = l;
foundangle = deltaangle;
}
}
// We already know that each linedef will go from this vertex
// to the left, because this is the right-most vertex in this area.
// If the line goes to the right, that means the other vertex of that
// line must lie outside this area and the mapper made an error.
// Should I check for this error and fail to create a sector in
// that case or ignore it and create a malformed sector (possibly
// breaking another sector also)?
// Find the side at which to start pathfinding
Vector2D testpos = new Vector2D(100.0f, 0.0f);
foundlinefront = (foundline.SideOfLine(foundv.Position + testpos) < 0.0f);
// Find inner path
List<LinedefSide> innerlines = FindInnerMostPath(foundline, foundlinefront);
if(innerlines != null)
{
// Make polygon
LinedefTracePath tracepath = new LinedefTracePath(innerlines);
Polygon innerpoly = tracepath.MakePolygon();
// Check if the front of the line is outside the polygon
if(!innerpoly.Intersect(foundline.GetSidePoint(foundlinefront)))
{
// Valid island found!
alllines.AddRange(innerlines);
p.Add(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 Polygon FindOuterLines(Linedef line, bool front, List<LinedefSide> alllines)
{
// Find inner path
alllines = FindInnerMostPath(line, front);
if(alllines != null)
{
// Make polygon
LinedefTracePath tracepath = new LinedefTracePath(alllines);
Polygon poly = tracepath.MakePolygon();
// Check if the front of the line is inside the polygon
if(poly.Intersect(line.GetSidePoint(front)))
{
// Valid polygon!
return poly;
}
}
// Path is invalid for sector outer lines
return null;
}
// This finds the inner path from the beginning of a line to the end of the line.
// Returns null when no path could be found.
private List<LinedefSide> FindInnerMostPath(Linedef start, bool front)
{
List<LinedefSide> path = new List<LinedefSide>();
Dictionary<Linedef, int> tracecount = new Dictionary<Linedef, int>();
Linedef nextline = start;
bool nextfront = front;
do
{
// Add line to path
path.Add(new LinedefSide(nextline, nextfront));
if(!tracecount.ContainsKey(nextline)) tracecount.Add(nextline, 1); else tracecount[nextline]++;
// Determine next vertex to use
Vertex v = nextfront ? nextline.End : nextline.Start;
// Get list of linedefs and sort by angle
List<Linedef> lines = new List<Linedef>(v.Linedefs);
LinedefAngleSorter sorter = new LinedefAngleSorter(nextline, nextfront, v);
lines.Sort(sorter);
// Source line is the only one?
if(lines.Count == 1)
{
// Are we allowed to trace along this line again?
if(tracecount[nextline] < 2)
{
// Turn around and go back along the other side of the line
nextfront = !nextfront;
}
else
{
// No more lines, trace ends here
path = null;
}
}
else
{
// Trace along the next line
Linedef prevline = nextline;
if(lines[0] == nextline) nextline = lines[1]; else nextline = lines[0];
// Check if front side changes
if((prevline.Start == nextline.Start) ||
(prevline.End == nextline.End)) nextfront = !nextfront;
}
}
// Continue as long as we have not reached the start yet
// or we have no next line to trace
while((path != null) && (nextline != start));
// Return path (null when trace failed)
return path;
}
#endregion
#region ================== Sector Making
// This makes the sector from the given lines and sides
private Sector MakeSector(List<LinedefSide> alllines)
{
Sidedef source = null;
Sector newsector = General.Map.Map.CreateSector();
// 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)
{
source = ls.Line.Front;
source.Sector.CopyPropertiesTo(newsector);
break;
}
}
else
{
if(ls.Line.Back != null)
{
source = ls.Line.Back;
source.Sector.CopyPropertiesTo(newsector);
break;
}
}
}
// If we couldn't find anything, try the other sides
if(source == null)
{
foreach(LinedefSide ls in alllines)
{
if(ls.Front)
{
if(ls.Line.Back != null)
{
source = ls.Line.Back;
source.Sector.CopyPropertiesTo(newsector);
break;
}
}
else
{
if(ls.Line.Front != null)
{
source = ls.Line.Front;
source.Sector.CopyPropertiesTo(newsector);
break;
}
}
}
}
// Go for all sides to make sidedefs
foreach(LinedefSide ls in alllines)
{
if(ls.Front)
{
// Create sidedef is needed and ensure it points to the new sector
if(ls.Line.Front == null) General.Map.Map.CreateSidedef(ls.Line, true, newsector);
if(ls.Line.Front.Sector != newsector) ls.Line.Front.ChangeSector(newsector);
if(source != null) source.CopyPropertiesTo(ls.Line.Front); else source = ls.Line.Front;
}
else
{
// Create sidedef is needed and ensure it points to the new sector
if(ls.Line.Back == null) General.Map.Map.CreateSidedef(ls.Line, false, newsector);
if(ls.Line.Back.Sector != newsector) ls.Line.Back.ChangeSector(newsector);
if(source != null) source.CopyPropertiesTo(ls.Line.Back); else source = ls.Line.Back;
}
// Update line
ls.Line.ApplySidedFlags();
}
// Return the new sector
return newsector;
}
#endregion
}
}

View file

@ -199,6 +199,9 @@ namespace CodeImp.DoomBuilder.Interface
General.Settings.WriteSetting("browserwindow.sizewidth", lastsize.Width);
General.Settings.WriteSetting("browserwindow.sizeheight", lastsize.Height);
General.Settings.WriteSetting("browserwindow.windowstate", windowstate);
// Clean up
browser.CleanUp();
}
// Static method to browse for flats

View file

@ -30,11 +30,11 @@ namespace CodeImp.DoomBuilder.Interface
{
this.components = new System.ComponentModel.Container();
this.splitter = new System.Windows.Forms.SplitContainer();
this.list = new CodeImp.DoomBuilder.Interface.OptimizedListView();
this.images = new System.Windows.Forms.ImageList(this.components);
this.objectname = new System.Windows.Forms.TextBox();
this.label = new System.Windows.Forms.Label();
this.refreshtimer = new System.Windows.Forms.Timer(this.components);
this.list = new CodeImp.DoomBuilder.Interface.OptimizedListView();
this.splitter.Panel1.SuspendLayout();
this.splitter.Panel2.SuspendLayout();
this.splitter.SuspendLayout();
@ -62,24 +62,9 @@ namespace CodeImp.DoomBuilder.Interface
this.splitter.TabIndex = 0;
this.splitter.TabStop = false;
//
// list
//
this.list.Dock = System.Windows.Forms.DockStyle.Fill;
this.list.LargeImageList = this.images;
this.list.Location = new System.Drawing.Point(0, 0);
this.list.MultiSelect = false;
this.list.Name = "list";
this.list.OwnerDraw = true;
this.list.Size = new System.Drawing.Size(518, 312);
this.list.Sorting = System.Windows.Forms.SortOrder.Ascending;
this.list.TabIndex = 1;
this.list.UseCompatibleStateImageBehavior = false;
this.list.DrawItem += new System.Windows.Forms.DrawListViewItemEventHandler(this.list_DrawItem);
this.list.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.list_ItemSelectionChanged);
//
// images
//
this.images.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit;
this.images.ColorDepth = System.Windows.Forms.ColorDepth.Depth8Bit;
this.images.ImageSize = new System.Drawing.Size(64, 64);
this.images.TransparentColor = System.Drawing.Color.Transparent;
//
@ -107,13 +92,27 @@ namespace CodeImp.DoomBuilder.Interface
this.refreshtimer.Interval = 500;
this.refreshtimer.Tick += new System.EventHandler(this.refreshtimer_Tick);
//
// ImageBrowser
// list
//
this.list.Dock = System.Windows.Forms.DockStyle.Fill;
this.list.LargeImageList = this.images;
this.list.Location = new System.Drawing.Point(0, 0);
this.list.MultiSelect = false;
this.list.Name = "list";
this.list.OwnerDraw = true;
this.list.Size = new System.Drawing.Size(518, 312);
this.list.TabIndex = 1;
this.list.UseCompatibleStateImageBehavior = false;
this.list.DrawItem += new System.Windows.Forms.DrawListViewItemEventHandler(this.list_DrawItem);
this.list.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.list_ItemSelectionChanged);
//
// ImageBrowserControl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 14F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.splitter);
this.Font = new System.Drawing.Font("Arial", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.Name = "ImageBrowser";
this.Name = "ImageBrowserControl";
this.Size = new System.Drawing.Size(518, 346);
this.splitter.Panel1.ResumeLayout(false);
this.splitter.Panel2.ResumeLayout(false);

View file

@ -38,6 +38,13 @@ namespace CodeImp.DoomBuilder.Interface
{
internal partial class ImageBrowserControl : UserControl
{
#region ================== Constants
// Maximum loaded items
private const int MAX_LOADED_ITEMS = 200;
#endregion
#region ================== Delegates / Events
public delegate void SelectedItemChangedDelegate();
@ -54,6 +61,9 @@ namespace CodeImp.DoomBuilder.Interface
// All items
private List<ImageBrowserItem> items;
// Loaded items
private LinkedList<ImageBrowserItem> loadeditems;
#endregion
#region ================== Properties
@ -71,11 +81,12 @@ namespace CodeImp.DoomBuilder.Interface
// Initialize
InitializeComponent();
items = new List<ImageBrowserItem>();
loadeditems = new LinkedList<ImageBrowserItem>();
// Move textbox with label
objectname.Left = label.Right + label.Margin.Right + objectname.Margin.Left;
}
// This applies the color settings
public void ApplyColorSettings()
{
@ -87,6 +98,37 @@ namespace CodeImp.DoomBuilder.Interface
}
}
// This cleans everything up (we can't override Dispose?)
public virtual void CleanUp()
{
// Stop refresh timer
refreshtimer.Enabled = false;
// Begin updating list
updating = true;
list.SuspendLayout();
list.BeginUpdate();
// Go for all items
foreach(ImageBrowserItem i in list.Items)
{
// Queue image for unloading if only temporary
if(i.icon.IsLoaded && i.icon.Temporary) General.Map.Data.BackgroundLoadImage(i.icon, false);
// Dispose item
i.Dispose();
}
// Trash list items
list.Clear();
loadeditems.Clear();
// Done updating list
updating = false;
list.EndUpdate();
list.ResumeLayout();
}
#endregion
#region ================== Rendering
@ -97,18 +139,25 @@ namespace CodeImp.DoomBuilder.Interface
if(!updating) e.Graphics.DrawImageUnscaled((e.Item as ImageBrowserItem).GetImage(e.Bounds), e.Bounds);
}
// Resfresher
// Refresher
private void refreshtimer_Tick(object sender, EventArgs e)
{
// Continue refreshing only when still loading data
refreshtimer.Enabled = General.Map.Data.IsLoading;
// Go for all items
foreach(ImageBrowserItem i in list.Items)
{
// Bounds within view?
if(i.Bounds.IntersectsWith(list.ClientRectangle))
{
// Remove from loaded list if in there
if(i.LoadedTicket != null) loadeditems.Remove(i.LoadedTicket);
// Image not loaded?
if(!i.icon.IsLoaded && !i.IsImageLoaded)
{
// Queue for background loading
General.Map.Data.BackgroundLoadImage(i.icon, true);
}
// Items needs to be redrawn?
if(i.CheckRedrawNeeded(i.Bounds))
{
@ -118,10 +167,35 @@ namespace CodeImp.DoomBuilder.Interface
// Refresh item in list
list.RedrawItems(i.Index, i.Index, false);
}
else
{
// Queue for unloading if only temporary
if(i.icon.IsLoaded && i.icon.Temporary) General.Map.Data.BackgroundLoadImage(i.icon, false);
}
// Add to loaded list
i.LoadedTicket = loadeditems.AddLast(i);
}
else
{
// When queued for loading, remove it from queue
General.Map.Data.BackgroundCancelImage(i.icon);
}
}
// More items laoded than allowed?
if(loadeditems.Count > MAX_LOADED_ITEMS)
{
// Unload items
for(int i = 0; i < (loadeditems.Count - MAX_LOADED_ITEMS); i++)
{
loadeditems.First.Value.ReleaseImage();
loadeditems.First.Value.LoadedTicket = null;
loadeditems.RemoveFirst();
}
}
}
#endregion
#region ================== Events
@ -130,7 +204,7 @@ namespace CodeImp.DoomBuilder.Interface
private void objectname_TextChanged(object sender, EventArgs e)
{
// Update list
RefillList();
RefillList(false);
// No item selected?
if(list.SelectedItems.Count == 0)
@ -236,7 +310,8 @@ namespace CodeImp.DoomBuilder.Interface
if(list.Items.Count > 0)
{
list.SelectedItems.Clear();
lvi = list.FindNearestItem(SearchDirectionHint.Down, new Point(1, -100000));
//lvi = list.FindNearestItem(SearchDirectionHint.Down, new Point(1, -100000));
lvi = list.Items[0];
if(lvi != null)
{
lvi.Selected = true;
@ -264,13 +339,10 @@ namespace CodeImp.DoomBuilder.Interface
public void EndAdding()
{
// Fill list with items
RefillList();
RefillList(true);
// Start updating if needed
refreshtimer.Enabled = General.Map.Data.IsLoading;
// Select first item
SelectFirstItem();
// Start updating
refreshtimer.Enabled = true;
}
// This adds an item
@ -283,7 +355,7 @@ namespace CodeImp.DoomBuilder.Interface
}
// This fills the list based on the objectname filter
private void RefillList()
private void RefillList(bool selectfirst)
{
List<ListViewItem> showitems = new List<ListViewItem>();
@ -296,7 +368,7 @@ namespace CodeImp.DoomBuilder.Interface
// Group property of items will be set to null, we will restore it later
list.Items.Clear();
// Go for all items NOT in the list
// Go for all items
foreach(ImageBrowserItem i in items)
{
// Add item if valid
@ -311,6 +383,9 @@ namespace CodeImp.DoomBuilder.Interface
// Fill list
list.Items.AddRange(showitems.ToArray());
// Select first item?
if(selectfirst) SelectFirstItem();
// Done updating list
updating = false;
list.EndUpdate();

View file

@ -45,17 +45,21 @@ namespace CodeImp.DoomBuilder.Interface
// Group
private ListViewGroup listgroup;
private LinkedListNode<ImageBrowserItem> loadedticked;
// Image cache
private Image image;
private Image normalimage;
private Image selectedimage;
private bool imageloaded;
private bool imageselected;
#endregion
#region ================== Properties
public ListViewGroup ListGroup { get { return listgroup; } set { listgroup = value; } }
public LinkedListNode<ImageBrowserItem> LoadedTicket { get { return loadedticked; } set { loadedticked = value; } }
public bool IsImageLoaded { get { return imageloaded; } }
public bool HasImage { get { return (normalimage != null); } }
#endregion
@ -70,6 +74,15 @@ namespace CodeImp.DoomBuilder.Interface
this.Tag = tag;
}
// Disposer
public void Dispose()
{
ReleaseImage();
loadedticked = null;
icon = null;
listgroup = null;
}
#endregion
#region ================== Methods
@ -77,67 +90,82 @@ namespace CodeImp.DoomBuilder.Interface
// This checks if a redraw is needed
public bool CheckRedrawNeeded(Rectangle bounds)
{
return ((image == null) || (image.Size != bounds.Size) ||
(this.Selected != imageselected) || (icon.IsLoaded && !imageloaded));
return (normalimage == null) || (selectedimage == null) || (icon.IsLoaded && !imageloaded);
}
// This draws the images
private Image DrawImage(Rectangle bounds, bool selected)
{
Brush forecolor;
Brush backcolor;
// Make a new image and graphics to draw with
Image image = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(image);
g.CompositingQuality = CompositingQuality.HighSpeed;
g.InterpolationMode = InterpolationMode.Bilinear;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.None;
// Determine coordinates
SizeF textsize = g.MeasureString(this.Text, this.ListView.Font, bounds.Width);
Size bordersize = new Size((bounds.Width - 64) >> 1, (bounds.Height - 64 - (int)textsize.Height) >> 1);
Rectangle imagerect = new Rectangle(bordersize.Width, bordersize.Height, 64, 64);
PointF textpos = new PointF(((float)bounds.Width - textsize.Width) * 0.5f, bounds.Height - textsize.Height - 2);
// Determine colors
if(selected)
{
// Highlighted
backcolor = new LinearGradientBrush(new Point(0, 0), new Point(0, bounds.Height),
AdjustedColor(SystemColors.Highlight, 0.2f),
AdjustedColor(SystemColors.Highlight, -0.1f));
forecolor = SystemBrushes.HighlightText;
}
else
{
// Normal
backcolor = new SolidBrush(base.ListView.BackColor);
forecolor = new SolidBrush(base.ListView.ForeColor);
}
// Draw!
g.FillRectangle(backcolor, 0, 0, bounds.Width, bounds.Height);
g.DrawImage(icon.Bitmap, General.MakeZoomedRect(icon.Bitmap.Size, imagerect));
g.DrawString(this.Text, this.ListView.Font, forecolor, textpos);
// Done
g.Dispose();
return image;
}
// This requests the cached image and redraws it if needed
public Image GetImage(Rectangle bounds)
{
Brush forecolor;
Brush backcolor;
// Do we need to redraw?
if(CheckRedrawNeeded(bounds))
{
// Keep settings
this.imageloaded = icon.IsLoaded;
this.imageselected = this.Selected;
// Trash old image
if(image != null) image.Dispose();
// Keep image loaded state
imageloaded = icon.IsLoaded;
// Make a new image and graphics to draw with
image = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(image);
g.CompositingQuality = CompositingQuality.HighSpeed;
g.InterpolationMode = InterpolationMode.Bilinear;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.None;
// Determine coordinates
SizeF textsize = g.MeasureString(this.Text, this.ListView.Font, bounds.Width);
Size bordersize = new Size((bounds.Width - 64) >> 1, (bounds.Height - 64 - (int)textsize.Height) >> 1);
Rectangle imagerect = new Rectangle(bordersize.Width, bordersize.Height, 64, 64);
PointF textpos = new PointF(((float)bounds.Width - textsize.Width) * 0.5f, bounds.Height - textsize.Height - 2);
// Determine colors
if(this.Selected)
{
// Highlighted
backcolor = new LinearGradientBrush(new Point(0, 0), new Point(0, bounds.Height),
AdjustedColor(SystemColors.Highlight, 0.2f),
AdjustedColor(SystemColors.Highlight, -0.1f));
forecolor = SystemBrushes.HighlightText;
}
else
{
// Normal
backcolor = new SolidBrush(base.ListView.BackColor);
forecolor = new SolidBrush(base.ListView.ForeColor);
}
// Draw!
g.FillRectangle(backcolor, 0, 0, bounds.Width, bounds.Height);
g.DrawImage(icon.Bitmap, General.MakeZoomedRect(icon.Bitmap.Size, imagerect));
g.DrawString(this.Text, this.ListView.Font, forecolor, textpos);
// Done
g.Dispose();
// Redraw both images
if(normalimage != null) normalimage.Dispose();
if(selectedimage != null) selectedimage.Dispose();
normalimage = DrawImage(bounds, false);
selectedimage = DrawImage(bounds, true);
}
// Return image
return image;
if(this.Selected) return selectedimage; else return normalimage;
}
// This releases image resources
public void ReleaseImage()
{
if(normalimage != null) normalimage.Dispose();
if(selectedimage != null) selectedimage.Dispose();
normalimage = null;
selectedimage = null;
}
// This brightens or darkens a color

View file

@ -34,7 +34,6 @@ namespace CodeImp.DoomBuilder.Interface
System.Windows.Forms.GroupBox groupBox1;
System.Windows.Forms.Label label1;
this.qualitydisplay = new System.Windows.Forms.CheckBox();
this.backgroundload = new System.Windows.Forms.CheckBox();
this.imagebrightnesslabel = new System.Windows.Forms.Label();
this.imagebrightness = new System.Windows.Forms.TrackBar();
this.colorsgroup1 = new System.Windows.Forms.GroupBox();
@ -123,7 +122,6 @@ namespace CodeImp.DoomBuilder.Interface
// groupBox1
//
groupBox1.Controls.Add(this.qualitydisplay);
groupBox1.Controls.Add(this.backgroundload);
groupBox1.Controls.Add(this.imagebrightnesslabel);
groupBox1.Controls.Add(this.imagebrightness);
groupBox1.Controls.Add(label1);
@ -144,16 +142,6 @@ namespace CodeImp.DoomBuilder.Interface
this.qualitydisplay.Text = "High quality display";
this.qualitydisplay.UseVisualStyleBackColor = true;
//
// backgroundload
//
this.backgroundload.AutoSize = true;
this.backgroundload.Location = new System.Drawing.Point(25, 102);
this.backgroundload.Name = "backgroundload";
this.backgroundload.Size = new System.Drawing.Size(197, 18);
this.backgroundload.TabIndex = 9;
this.backgroundload.Text = "Load all texture and flats in memory";
this.backgroundload.UseVisualStyleBackColor = true;
//
// imagebrightnesslabel
//
this.imagebrightnesslabel.Location = new System.Drawing.Point(17, 75);
@ -743,7 +731,6 @@ namespace CodeImp.DoomBuilder.Interface
private System.Windows.Forms.GroupBox colorsgroup1;
private System.Windows.Forms.CheckBox blackbrowsers;
private System.Windows.Forms.CheckBox qualitydisplay;
private System.Windows.Forms.CheckBox backgroundload;
private System.Windows.Forms.Label imagebrightnesslabel;
private System.Windows.Forms.TrackBar imagebrightness;
}

View file

@ -51,7 +51,6 @@ namespace CodeImp.DoomBuilder.Interface
// Interface
imagebrightness.Value = General.Settings.ImageBrightness;
backgroundload.Checked = General.Settings.BackgroundLoading;
qualitydisplay.Checked = General.Settings.QualityDisplay;
// Fill list of actions
@ -292,7 +291,6 @@ namespace CodeImp.DoomBuilder.Interface
{
// Apply interface
General.Settings.ImageBrightness = imagebrightness.Value;
General.Settings.BackgroundLoading = backgroundload.Checked;
General.Settings.QualityDisplay = qualitydisplay.Checked;
// Apply control keys to actions

View file

@ -88,15 +88,23 @@ namespace CodeImp.DoomBuilder.Interface
// Start adding
browser.BeginAdding();
// Add all used textures
foreach(ImageData img in General.Map.Data.Textures)
if(useditems.ContainsKey(img.LongName))
browser.Add(img.Name, img, img, used);
// Add all available textures
// Add all available textures and mark the images for temporary loading
foreach(ImageData img in General.Map.Data.Textures)
{
browser.Add(img.Name, img, img, avail);
img.Temporary = true;
}
// Add all used textures and mark the images for permanent loading
foreach(ImageData img in General.Map.Data.Textures)
{
if(useditems.ContainsKey(img.LongName))
{
browser.Add(img.Name, img, img, used);
img.Temporary = false;
}
}
// Done adding
browser.EndAdding();
@ -199,6 +207,9 @@ namespace CodeImp.DoomBuilder.Interface
General.Settings.WriteSetting("browserwindow.sizewidth", lastsize.Width);
General.Settings.WriteSetting("browserwindow.sizeheight", lastsize.Height);
General.Settings.WriteSetting("browserwindow.windowstate", windowstate);
// Clean up
browser.CleanUp();
}
// Static method to browse for texture

View file

@ -777,114 +777,6 @@ namespace CodeImp.DoomBuilder.Map
#region ================== Geometry Tools
/// <summary>
/// This automagically makes a sector, starting at one side of a line.
/// Returns the sector reference when created, return null when not created.
/// </summary>
public Sector MakeSector(Linedef line, bool front)
{
// Find inner path
List<LinedefSide> path = FindInnerMostPath(line, front);
if(path != null)
{
// Make polygon
LinedefTracePath tracepath = new LinedefTracePath(path);
Polygon poly = tracepath.MakePolygon();
// Check if the front of the line is inside the polygon
if(poly.Intersect(line.GetSidePoint(front)))
{
Sidedef source = null;
Sector newsector = CreateSector();
// 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 path)
{
if(ls.Front)
{
if(ls.Line.Front != null)
{
source = ls.Line.Front;
source.Sector.CopyPropertiesTo(newsector);
break;
}
}
else
{
if(ls.Line.Back != null)
{
source = ls.Line.Back;
source.Sector.CopyPropertiesTo(newsector);
break;
}
}
}
// If we couldn't find anything, try the other sides
if(source == null)
{
foreach(LinedefSide ls in path)
{
if(ls.Front)
{
if(ls.Line.Back != null)
{
source = ls.Line.Back;
source.Sector.CopyPropertiesTo(newsector);
break;
}
}
else
{
if(ls.Line.Front != null)
{
source = ls.Line.Front;
source.Sector.CopyPropertiesTo(newsector);
break;
}
}
}
}
// Go for all sides to make sidedefs
foreach(LinedefSide ls in path)
{
if(ls.Front)
{
// Create sidedef is needed and ensure it points to the new sector
if(ls.Line.Front == null) CreateSidedef(ls.Line, true, newsector);
if(ls.Line.Front.Sector != newsector) ls.Line.Front.ChangeSector(newsector);
if(source != null) source.CopyPropertiesTo(ls.Line.Front); else source = ls.Line.Front;
}
else
{
// Create sidedef is needed and ensure it points to the new sector
if(ls.Line.Back == null) CreateSidedef(ls.Line, false, newsector);
if(ls.Line.Back.Sector != newsector) ls.Line.Back.ChangeSector(newsector);
if(source != null) source.CopyPropertiesTo(ls.Line.Back); else source = ls.Line.Back;
}
// Update line
ls.Line.ApplySidedFlags();
}
// Return the new sector
return newsector;
}
else
{
// Outside the map, can't create a sector
return null;
}
}
else
{
// Impossible to find a path!
return null;
}
}
// This joins overlapping lines together
// Returns the number of joins made
public static int JoinOverlappingLines(ICollection<Linedef> lines)
@ -1445,65 +1337,6 @@ namespace CodeImp.DoomBuilder.Map
if(vc.Value.Linedefs.Count == 0) vertices.Remove(vc);
}
}
/// <summary>
/// This finds the inner path from the beginning of a line to the end of the line.
/// Returns null when no path could be found.
/// </summary>
public List<LinedefSide> FindInnerMostPath(Linedef start, bool front)
{
List<LinedefSide> path = new List<LinedefSide>();
Dictionary<Linedef, int> tracecount = new Dictionary<Linedef, int>(linedefs.Count);
Linedef nextline = start;
bool nextfront = front;
do
{
// Add line to path
path.Add(new LinedefSide(nextline, nextfront));
if(!tracecount.ContainsKey(nextline)) tracecount.Add(nextline, 1); else tracecount[nextline]++;
// Determine next vertex to use
Vertex v = nextfront ? nextline.End : nextline.Start;
// Get list of linedefs and sort by angle
List<Linedef> lines = new List<Linedef>(v.Linedefs);
LinedefAngleSorter sorter = new LinedefAngleSorter(nextline, nextfront, v);
lines.Sort(sorter);
// Source line is the only one?
if(lines.Count == 1)
{
// Are we allowed to trace along this line again?
if(tracecount[nextline] < 2)
{
// Turn around and go back along the other side of the line
nextfront = !nextfront;
}
else
{
// No more lines, trace ends here
path = null;
}
}
else
{
// Trace along the next line
Linedef prevline = nextline;
if(lines[0] == nextline) nextline = lines[1]; else nextline = lines[0];
// Check if front side changes
if((prevline.Start == nextline.Start) ||
(prevline.End == nextline.End)) nextfront = !nextfront;
}
}
// Continue as long as we have not reached the start yet
// or we have no next line to trace
while((path != null) && (nextline != start));
// Return path (null when trace failed)
return path;
}
#endregion
}