diff --git a/Source/Builder.csproj b/Source/Builder.csproj
index 1141c642..9da11902 100644
--- a/Source/Builder.csproj
+++ b/Source/Builder.csproj
@@ -649,6 +649,7 @@
+
diff --git a/Source/BuilderModes/VisualModes/BaseVisualMode.cs b/Source/BuilderModes/VisualModes/BaseVisualMode.cs
index 6ca5420c..70c63015 100644
--- a/Source/BuilderModes/VisualModes/BaseVisualMode.cs
+++ b/Source/BuilderModes/VisualModes/BaseVisualMode.cs
@@ -50,12 +50,6 @@ namespace CodeImp.DoomBuilder.BuilderModes.Editing
#region ================== Variables
- // All constructed visual sectors
- private Dictionary allsectors;
-
- // List of visible sectors
- private Dictionary visiblesectors;
-
#endregion
#region ================== Properties
@@ -68,8 +62,6 @@ namespace CodeImp.DoomBuilder.BuilderModes.Editing
public BaseVisualMode()
{
// Initialize
- allsectors = new Dictionary(General.Map.Map.Sectors.Count);
- visiblesectors = new Dictionary();
// We have no destructor
GC.SuppressFinalize(this);
@@ -82,9 +74,6 @@ namespace CodeImp.DoomBuilder.BuilderModes.Editing
if(!isdisposed)
{
// Clean up
- foreach(KeyValuePair s in allsectors) s.Value.Dispose();
- visiblesectors = null;
- allsectors = null;
// Done
base.Dispose();
@@ -93,107 +82,12 @@ namespace CodeImp.DoomBuilder.BuilderModes.Editing
#endregion
- #region ================== Private Tools
-
- // This finds the nearest sector to the camera
- private Sector FindStartSector(Vector2D campos)
- {
- float side;
- Linedef l;
-
- // Get nearest linedef
- l = General.Map.Map.NearestLinedef(campos);
- if(l != null)
- {
- // Check if we are on front or back side
- side = l.SideOfLine(campos);
- if(side > 0)
- {
- // Is there a sidedef here?
- if(l.Back != null)
- return l.Back.Sector;
- else if(l.Front != null)
- return l.Front.Sector;
- else
- return null;
- }
- else
- {
- // Is there a sidedef here?
- if(l.Front != null)
- return l.Front.Sector;
- else if(l.Back != null)
- return l.Back.Sector;
- else
- return null;
- }
- }
- else
- return null;
- }
-
- // This recursively finds and adds visible sectors
- private void ProcessVisibleSectors(Sector start, Vector2D campos)
- {
- // Add the start sector
- AddVisibleSector(start);
-
- // Get a range of blocks
- List blocks = blockmap.GetSquareRange(campos, General.Settings.ViewDistance);
- foreach(VisualBlockEntry b in blocks)
- {
- // Go for all the linedefs in this block
- foreach(Linedef ld in b.Lines)
- {
- // Add sectors from both sides of the line
- if(ld.Front != null) AddVisibleSector(ld.Front.Sector);
- if(ld.Back != null) AddVisibleSector(ld.Back.Sector);
- }
- }
- }
-
- // This adds (and creates if needed) the BaseVisualSector for
- // the given sector to the visible sectors list
- private void AddVisibleSector(Sector s)
- {
- BaseVisualSector vs;
-
- // Find the basesector and make it if needed
- if(allsectors.ContainsKey(s))
- {
- // Take existing visualsector
- vs = allsectors[s];
- }
- else
- {
- // Make new visualsector
- vs = new BaseVisualSector(s);
- allsectors.Add(s, vs);
- }
-
- // Add sector to visibility list
- visiblesectors[s] = vs;
- }
-
- #endregion
-
#region ================== Methods
- [EndAction("reloadresources", BaseAction = true)]
- public void ReloadResources()
+ // This creates a visual sector
+ protected override VisualSector CreateVisualSector(Sector s)
{
- foreach(KeyValuePair s in allsectors) s.Value.Dispose();
- allsectors.Clear();
- visiblesectors.Clear();
- }
-
- // Mode engages
- public override void OnEngage()
- {
- // Update the used textures
- General.Map.Data.UpdateUsedTextures();
-
- base.OnEngage();
+ return new BaseVisualSector(s);
}
// This draws a frame
@@ -205,9 +99,8 @@ namespace CodeImp.DoomBuilder.BuilderModes.Editing
// Begin with geometry
renderer.StartGeometry();
- // Render all visible sectors
- foreach(KeyValuePair vs in visiblesectors)
- renderer.RenderGeometry(vs.Value);
+ // This renders all visible sectors
+ base.OnRedrawDisplay();
// Done rendering geometry
renderer.FinishGeometry();
@@ -215,27 +108,6 @@ namespace CodeImp.DoomBuilder.BuilderModes.Editing
// Present!
renderer.Finish();
}
-
- // Call base
- base.OnRedrawDisplay();
- }
-
- // This processes a frame
- public override void OnProcess()
- {
- Vector2D campos;
-
- // Process base class first
- base.OnProcess();
-
- // Get the 2D camera position
- campos = new Vector2D(base.CameraPosition.x, base.CameraPosition.y);
-
- // Make new visibility list
- visiblesectors = new Dictionary(General.Map.Map.Sectors.Count);
-
- // Process all visible sectors starting with the nearest
- ProcessVisibleSectors(FindStartSector(campos), campos);
}
#endregion
diff --git a/Source/VisualModes/Clipper.cs b/Source/VisualModes/Clipper.cs
new file mode 100644
index 00000000..60a9e382
--- /dev/null
+++ b/Source/VisualModes/Clipper.cs
@@ -0,0 +1,269 @@
+
+#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 System.Windows.Forms;
+using System.IO;
+using System.Reflection;
+using CodeImp.DoomBuilder.Windows;
+using CodeImp.DoomBuilder.IO;
+using CodeImp.DoomBuilder.Map;
+using CodeImp.DoomBuilder.Rendering;
+using CodeImp.DoomBuilder.Geometry;
+using CodeImp.DoomBuilder.Editing;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.VisualModes
+{
+ internal class Clipper
+ {
+ #region ================== Constants
+
+ private const float MULTIPLIER = 10000f;
+ private const int MAXRANGE = (int)(Angle2D.PI2 * MULTIPLIER);
+ private const int HALFRANGE = (int)(Angle2D.PI * MULTIPLIER);
+ private const int MINRANGE = 0;
+
+ #endregion
+
+ #region ================== ClipRange struct
+
+ // ClipRange structure
+ private struct ClipRange
+ {
+ // Variables
+ public int low;
+ public int high;
+
+ // Constructor
+ public ClipRange(int low, int high)
+ {
+ this.low = low;
+ this.high = high;
+ }
+ }
+
+ #endregion
+
+ #region ================== Variables
+
+ // Position where we are viewing from
+ private Vector2D position;
+
+ // Clipping ranges
+ private LinkedList ranges;
+
+ #endregion
+
+ #region ================== Constructor / Disposer
+
+ // Constructor
+ public Clipper(Vector2D pos)
+ {
+ // Initialize
+ this.position = pos;
+ this.ranges = new LinkedList();
+ }
+
+ // Constructor
+ public Clipper(Clipper copy)
+ {
+ // Initialize
+ this.position = copy.position;
+ this.ranges = new LinkedList(copy.ranges);
+ }
+
+ // Disposer
+ public void Dispose()
+ {
+ this.ranges.Clear();
+ }
+
+ #endregion
+
+ #region ================== Testing methods
+
+ // This tests a range to see if it is at least partially visible
+ public bool TestRange(Vector2D a, Vector2D b)
+ {
+ int i1, i2, c1, c2, m;
+ float a1, a2;
+
+ // Get angles
+ a1 = Angle2D.Normalized(Vector2D.GetAngle(position, a));
+ a2 = Angle2D.Normalized(Vector2D.GetAngle(position, b));
+
+ // Convert angles to ranges
+ i1 = (int)(a1 * MULTIPLIER);
+ i2 = (int)(a2 * MULTIPLIER);
+ c1 = Math.Min(i1, i2);
+ c2 = Math.Max(i1, i2);
+
+ // Determine rotation direction
+ m = c2 - c1;
+ if(m < MINRANGE) m += MAXRANGE;
+ if(m < HALFRANGE)
+ {
+ // Check if the range goes through zero point
+ if(c2 < c1)
+ {
+ // Test two ranges
+ return RangeVisible(new ClipRange(c1, MAXRANGE)) ||
+ RangeVisible(new ClipRange(MINRANGE, c2));
+ }
+ else
+ {
+ // Test a single range
+ return RangeVisible(new ClipRange(c1, c2));
+ }
+ }
+ else
+ {
+ // Check if the range goes through zero point
+ if(c2 > c1)
+ {
+ // Test two ranges
+ return RangeVisible(new ClipRange(MINRANGE, c1)) ||
+ RangeVisible(new ClipRange(c2, MAXRANGE));
+ }
+ else
+ {
+ // Test a single range
+ return RangeVisible(new ClipRange(c2, c1));
+ }
+ }
+ }
+
+ // This tests a single range for visibility
+ private bool RangeVisible(ClipRange r)
+ {
+ // Go for all clipped ranges
+ foreach(ClipRange c in ranges)
+ {
+ // Does clipped range completely hide the given range?
+ if((c.low <= r.low) && (c.high >= r.high))
+ {
+ // No further testing needed, range is clipped
+ return false;
+ }
+ }
+
+ // Not completely clipped
+ return true;
+ }
+
+ #endregion
+
+ #region ================== Clipping methods
+
+ // This tests a range to see if it is at least partially visible
+ public bool InsertRange(Vector2D a, Vector2D b)
+ {
+ int i1, i2, c1, c2, m;
+ float a1, a2;
+
+ // Get angles
+ a1 = Angle2D.Normalized(Vector2D.GetAngle(position, a));
+ a2 = Angle2D.Normalized(Vector2D.GetAngle(position, b));
+
+ // Convert angles to ranges
+ i1 = (int)(a1 * MULTIPLIER);
+ i2 = (int)(a2 * MULTIPLIER);
+ c1 = Math.Min(i1, i2);
+ c2 = Math.Max(i1, i2);
+
+ // Determine rotation direction
+ m = c2 - c1;
+ if(m < MINRANGE) m += MAXRANGE;
+ if(m < HALFRANGE)
+ {
+ // Check if the range goes through zero point
+ if(c2 < c1)
+ {
+ // Add two ranges
+ return AddRange(new ClipRange(c1, MAXRANGE)) ||
+ AddRange(new ClipRange(MINRANGE, c2));
+ }
+ else
+ {
+ // Add a single range
+ return AddRange(new ClipRange(c1, c2));
+ }
+ }
+ else
+ {
+ // Check if the range goes through zero point
+ if(c2 > c1)
+ {
+ // Add two ranges
+ return AddRange(new ClipRange(MINRANGE, c1)) ||
+ AddRange(new ClipRange(c2, MAXRANGE));
+ }
+ else
+ {
+ // Add a single range
+ return AddRange(new ClipRange(c2, c1));
+ }
+ }
+ }
+
+ // This tests a single range for visibility
+ // Returns true when the entire range has been clipped
+ private bool AddRange(ClipRange r)
+ {
+ LinkedListNode current, next;
+ ClipRange c;
+
+ // Go for all ranges to find overlappings
+ current = ranges.First;
+ while(current != null)
+ {
+ // Keep reference to the next
+ next = current.Next;
+ c = current.Value;
+
+ // Check if ranges overlap
+ if((c.low <= (r.high + 1)) && (c.high >= (r.low - 1)))
+ {
+ // Remove old range from list
+ ranges.Remove(current);
+
+ // Extend range with overlapping range
+ if(c.low < r.low) r.low = c.low;
+ if(c.high > r.high) r.high = c.high;
+ }
+
+ // Move to the next
+ current = next;
+ }
+
+ // Insert the new range
+ ranges.AddLast(r);
+
+ // Return true when entire range is now clipped
+ return (r.low == MINRANGE) && (r.high == MAXRANGE);
+ }
+
+ #endregion
+ }
+}
diff --git a/Source/VisualModes/VisualBlockMap.cs b/Source/VisualModes/VisualBlockMap.cs
index ede3132e..1d922843 100644
--- a/Source/VisualModes/VisualBlockMap.cs
+++ b/Source/VisualModes/VisualBlockMap.cs
@@ -125,11 +125,11 @@ namespace CodeImp.DoomBuilder.VisualModes
}
// This returns a range of blocks in a square
- public List GetSquareRange(Vector2D pos, float radius)
+ public List GetSquareRange(RectangleF rect)
{
// Calculate block coordinates
- Point lt = GetBlockCoordinates(pos - radius);
- Point rb = GetBlockCoordinates(pos + radius);
+ Point lt = GetBlockCoordinates(new Vector2D(rect.Left, rect.Top));
+ Point rb = GetBlockCoordinates(new Vector2D(rect.Right, rect.Bottom));
// Go through the range to make a list
int entriescount = (rb.X - lt.X) * (rb.Y - lt.Y);
diff --git a/Source/VisualModes/VisualMode.cs b/Source/VisualModes/VisualMode.cs
index 0866d6aa..1d92fb43 100644
--- a/Source/VisualModes/VisualMode.cs
+++ b/Source/VisualModes/VisualMode.cs
@@ -74,6 +74,8 @@ namespace CodeImp.DoomBuilder.VisualModes
// Map
protected VisualBlockMap blockmap;
+ protected Dictionary allsectors;
+ protected Dictionary visiblesectors;
#endregion
@@ -97,6 +99,8 @@ namespace CodeImp.DoomBuilder.VisualModes
this.campos = new Vector3D(0.0f, 0.0f, 96.0f);
this.camanglez = Angle2D.PI;
this.blockmap = new VisualBlockMap();
+ this.allsectors = new Dictionary(General.Map.Map.Sectors.Count);
+ this.visiblesectors = new Dictionary(50);
}
// Disposer
@@ -106,7 +110,11 @@ namespace CodeImp.DoomBuilder.VisualModes
if(!isdisposed)
{
// Clean up
+ foreach(KeyValuePair s in allsectors) s.Value.Dispose();
blockmap.Dispose();
+ visiblesectors = null;
+ allsectors = null;
+ blockmap = null;
// Done
base.Dispose();
@@ -121,7 +129,10 @@ namespace CodeImp.DoomBuilder.VisualModes
public override void OnEngage()
{
base.OnEngage();
-
+
+ // Update the used textures
+ General.Map.Data.UpdateUsedTextures();
+
// Fill the blockmap
FillBlockMap();
@@ -253,8 +264,160 @@ namespace CodeImp.DoomBuilder.VisualModes
#endregion
+ #region ================== Visibility Culling
+
+ // This preforms visibility culling
+ private void DoCulling()
+ {
+ Vector2D campos2d = (Vector2D)campos;
+ float viewdist = General.Settings.ViewDistance;
+
+ // Get the blocks within view range and make a collection of all nearby linedefs
+ RectangleF viewrect = new RectangleF(campos.x - viewdist, campos.y - viewdist, viewdist * 2, viewdist * 2);
+ List blocks = blockmap.GetSquareRange(viewrect);
+ List nearbylines = new List(blocks.Count);
+ foreach(VisualBlockEntry b in blocks) nearbylines.AddRange(b.Lines);
+
+ // Find the sector to begin with
+ Sector start = FindStartSector((Vector2D)campos, nearbylines);
+
+ // Find visible sectors
+ visiblesectors = new Dictionary(visiblesectors.Count);
+ if(start != null) ProcessVisibleSectors(start, (Vector2D)campos);
+ }
+
+ // This recursively finds and adds visible sectors
+ private void ProcessVisibleSectors(Sector start, Vector2D campos)
+ {
+ Stack todo = new Stack(50);
+ Dictionary stackedsectors = new Dictionary(50);
+ Clipper clipper = new Clipper(campos);
+ float viewdist2 = General.Settings.ViewDistance * General.Settings.ViewDistance;
+
+ // TODO: Use sector markings instead of the stackedsectors dictionary?
+
+ // This algorithm uses a breadth-first search for visible sectors
+
+ // Continue until no more sectors to process
+ todo.Push(start);
+ stackedsectors.Add(start, start);
+ while(todo.Count > 0)
+ {
+ Sector s = todo.Pop();
+ VisualSector vs;
+
+ // Find the basesector and make it if needed
+ if(allsectors.ContainsKey(s))
+ {
+ // Take existing visualsector
+ vs = allsectors[s];
+ }
+ else
+ {
+ // Make new visualsector
+ vs = CreateVisualSector(s);
+ allsectors.Add(s, vs);
+ }
+
+ // Add sector to visibility list
+ visiblesectors.Add(s, vs);
+
+ // Go for all sidedefs in the sector
+ foreach(Sidedef sd in s.Sidedefs)
+ {
+ // Camera on the front of this side?
+ float side = sd.Line.SideOfLine(campos);
+ if(((side > 0) && sd.IsFront) ||
+ ((side < 0) && !sd.IsFront))
+ {
+ // Sidedef blocking the view?
+ if((sd.Other == null) ||
+ (sd.Other.Sector.FloorHeight >= (sd.Sector.CeilHeight - 0.0001f)) ||
+ (sd.Other.Sector.CeilHeight <= (sd.Sector.FloorHeight + 0.0001f)) ||
+ (sd.Other.Sector.FloorHeight >= (sd.Other.Sector.CeilHeight - 0.0001f)))
+ {
+ // This blocks the view
+ clipper.InsertRange(sd.Line.Start.Position, sd.Line.End.Position);
+ }
+ }
+ }
+
+ // Go for all sidedefs in the sector
+ foreach(Sidedef sd in s.Sidedefs)
+ {
+ // Doublesided and not referring to same sector?
+ if((sd.Other != null) && (sd.Other.Sector != sd.Sector))
+ {
+ // Get the other sector
+ Sector os = sd.Other.Sector;
+
+ // Sector not added yet?
+ if(!stackedsectors.ContainsKey(os))
+ {
+ // Within view range?
+ if(sd.Line.DistanceToSq(campos, true) < viewdist2)
+ {
+ // Can we see this sector?
+ if(clipper.TestRange(sd.Line.Start.Position, sd.Line.End.Position))
+ {
+ // Process this sector as well
+ todo.Push(os);
+ stackedsectors.Add(os, os);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Done
+ clipper.Dispose();
+ }
+
+ // This finds the nearest sector to the camera
+ private Sector FindStartSector(Vector2D campos, List lines)
+ {
+ float side;
+ Linedef l;
+
+ // Get nearest linedef
+ l = MapSet.NearestLinedef(lines, campos);
+ if(l != null)
+ {
+ // Check if we are on front or back side
+ side = l.SideOfLine(campos);
+ if(side > 0)
+ {
+ // Is there a sidedef here?
+ if(l.Back != null)
+ return l.Back.Sector;
+ else if(l.Front != null)
+ return l.Front.Sector;
+ else
+ return null;
+ }
+ else
+ {
+ // Is there a sidedef here?
+ if(l.Front != null)
+ return l.Front.Sector;
+ else if(l.Back != null)
+ return l.Back.Sector;
+ else
+ return null;
+ }
+ }
+ else
+ return null;
+ }
+
+ #endregion
+
#region ================== Processing
+ // This creates a visual sector
+ protected abstract VisualSector CreateVisualSector(Sector s);
+
// This fills the blockmap
protected virtual void FillBlockMap()
{
@@ -284,11 +447,39 @@ namespace CodeImp.DoomBuilder.VisualModes
// Apply new camera matrices
renderer.PositionAndLookAt(campos, camtarget);
+
+ // Visibility culling
+ DoCulling();
// Now redraw
General.Interface.RedrawDisplay();
}
#endregion
+
+ #region ================== Rendering
+
+ // Call this to simply render all visible sectors
+ public override void OnRedrawDisplay()
+ {
+ // Render all visible sectors
+ foreach(KeyValuePair vs in visiblesectors)
+ renderer.RenderGeometry(vs.Value);
+ }
+
+ #endregion
+
+ #region ================== Actions
+
+ [EndAction("reloadresources", BaseAction = true)]
+ public virtual void ReloadResources()
+ {
+ // Trash all visual sectors, because they are no longer valid
+ foreach(KeyValuePair s in allsectors) s.Value.Dispose();
+ allsectors.Clear();
+ visiblesectors.Clear();
+ }
+
+ #endregion
}
}