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 } }