#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 CodeImp.DoomBuilder.Map; using CodeImp.DoomBuilder.Geometry; using System.Drawing; using System.Diagnostics; using System.Linq; #endregion namespace CodeImp.DoomBuilder.VisualModes { public sealed class VisualBlockMap { // This returns all blocks along the given line public List GetLineBlocks(Vector2D v1, Vector2D v2) { int x0 = (int)Math.Floor(Math.Min(v1.x, v2.x)); int y0 = (int)Math.Floor(Math.Min(v1.y, v2.y)); int x1 = (int)Math.Floor(Math.Max(v1.x, v2.x)) + 1; int y1 = (int)Math.Floor(Math.Max(v1.y, v2.y)) + 1; var result = new List(); root.GetBlocks(new Rectangle(x0, y0, x1 - x0, y1 - y0), ref result); return result; } public List GetBlocks(RectangleF box) { var result = new List(); root.GetBlocks(ToRectangle(box), ref result); return result; } public List GetBlocks(Vector2D pos) { var result = new List(); root.GetBlocks(new Point((int)Math.Floor(pos.x), (int)Math.Floor(pos.y)), ref result); return result; } // This returns a range of blocks in a frustum public List GetFrustumRange(ProjectedFrustum2D frustum2D) { var frustum = new Frustum(); frustum.planes = new Plane[4] { new Plane(frustum2D.Lines[0]), new Plane(frustum2D.Lines[1]), new Plane(frustum2D.Lines[2]), new Plane(frustum2D.Lines[3]) }; var result = new List(); root.GetBlocks(frustum, ref result); return result; } public Sector GetSectorAt(Vector2D pos) { List sectors = new List(1); foreach (VisualBlockEntry e in GetBlocks(pos)) foreach (Sector s in e.Sectors) if (s.Intersect(pos)) sectors.Add(s); if (sectors.Count == 0) { return null; } else if (sectors.Count == 1) { return sectors[0]; } else { // Having multiple intersections indicates that there are self-referencing sectors in this spot. // In this case we have to check which side of the nearest linedef pos is on, and then use that sector HashSet linedefs = new HashSet(sectors[0].Sidedefs.Count * sectors.Count); foreach (Sector s in sectors) foreach (Sidedef sd in s.Sidedefs) linedefs.Add(sd.Line); Linedef nearest = MapSet.NearestLinedef(linedefs, pos); double d = nearest.SideOfLine(pos); if (d <= 0.0 && nearest.Front != null) return nearest.Front.Sector; else if (nearest.Back != null) return nearest.Back.Sector; } return null; } public void Clear() { root = new Node(new Rectangle(General.Map.Config.LeftBoundary, General.Map.Config.BottomBoundary, General.Map.Config.RightBoundary - General.Map.Config.LeftBoundary, General.Map.Config.TopBoundary - General.Map.Config.BottomBoundary)); } public void AddSectorsSet(ICollection sectors) { foreach (Sector s in sectors) AddSector(s); } public void AddLinedefsSet(ICollection lines) { foreach (Linedef line in lines) AddLinedef(line); } public void AddThingsSet(ICollection things) { foreach (Thing t in things) AddThing(t); } public void AddSector(Sector sector) { root.GetEntry(ToRectangle(sector.BBox)).Sectors.Add(sector); } public void AddLinedef(Linedef line) { int x0 = (int)Math.Floor(Math.Min(line.Start.Position.x, line.End.Position.x)); int y0 = (int)Math.Floor(Math.Min(line.Start.Position.y, line.End.Position.y)); int x1 = (int)Math.Floor(Math.Max(line.Start.Position.x, line.End.Position.x)) + 1; int y1 = (int)Math.Floor(Math.Max(line.Start.Position.y, line.End.Position.y)) + 1; root.GetEntry(new Rectangle(x0, y0, x1 - x0, y1 - y0)).Lines.Add(line); } public void AddThing(Thing thing) { int x0 = (int)Math.Floor(thing.Position.x - thing.Size); int x1 = (int)Math.Floor(thing.Position.x + thing.Size) + 1; int y0 = (int)Math.Floor(thing.Position.y - thing.Size); int y1 = (int)Math.Floor(thing.Position.y + thing.Size) + 1; root.GetEntry(new Rectangle(x0, y0, x1 - x0, y1 - y0)).Things.Add(thing); } internal void Dispose() { Clear(); } static Rectangle ToRectangle(RectangleF bbox) { int x0 = (int)Math.Floor(bbox.Left); int y0 = (int)Math.Floor(bbox.Top); int x1 = (int)Math.Floor(bbox.Right) + 1; int y1 = (int)Math.Floor(bbox.Bottom) + 1; return new Rectangle(x0, y0, x1 - x0, y1 - y0); } const int MaxLevels = 8; Node root = new Node(new Rectangle(General.Map.Config.LeftBoundary, General.Map.Config.BottomBoundary, General.Map.Config.RightBoundary - General.Map.Config.LeftBoundary, General.Map.Config.TopBoundary - General.Map.Config.BottomBoundary)); struct Plane { public Plane(Line2D line) { Vector2D dir = line.v2 - line.v1; A = -dir.y; B = dir.x; D = -(line.v1.x * A + line.v1.y * B); } public double A, B, D; } class Frustum { public Plane[] planes; } class Node { enum Visibility { Inside, Intersecting, Outside }; public Node(Rectangle bbox) { this.bbox = bbox; extents = new Vector2D(bbox.Width * 0.5f, bbox.Height * 0.5f); center = new Vector2D(bbox.X + extents.x, bbox.Y + extents.y); } public void GetBlocks(Frustum frustum, ref List list) { Visibility vis = TestVisibility(frustum); if (vis == Visibility.Inside) { GetAllBlocks(ref list); } else if (vis == Visibility.Intersecting) { if (visualBlock != null) list.Add(visualBlock); if (topLeft != null) { topLeft.GetBlocks(frustum, ref list); topRight.GetBlocks(frustum, ref list); bottomLeft.GetBlocks(frustum, ref list); bottomRight.GetBlocks(frustum, ref list); } } } void GetAllBlocks(ref List list) { if (visualBlock != null) list.Add(visualBlock); if (topLeft != null) { topLeft.GetAllBlocks(ref list); topRight.GetAllBlocks(ref list); bottomLeft.GetAllBlocks(ref list); bottomRight.GetAllBlocks(ref list); } } Visibility TestVisibility(Frustum frustum) { Visibility result = Visibility.Inside; for (int i = 0; i < 4; i++) { Visibility vis = TestFrustumLineVisibility(frustum.planes[i]); if (vis == Visibility.Outside) return Visibility.Outside; else if (vis == Visibility.Intersecting) result = Visibility.Intersecting; } return result; } Visibility TestFrustumLineVisibility(Plane plane) { double e = extents.x * Math.Abs(plane.A) + extents.y * Math.Abs(plane.B); double s = center.x * plane.A + center.y * plane.B + plane.D; if (s - e > 0.0) return Visibility.Inside; else if (s + e < 0) return Visibility.Outside; else return Visibility.Intersecting; } public void GetBlocks(Point pos, ref List list) { if (visualBlock != null) list.Add(visualBlock); if (topLeft != null) { if (topLeft.bbox.Contains(pos)) topLeft.GetBlocks(pos, ref list); if (topRight.bbox.Contains(pos)) topRight.GetBlocks(pos, ref list); if (bottomLeft.bbox.Contains(pos)) bottomLeft.GetBlocks(pos, ref list); if (bottomRight.bbox.Contains(pos)) bottomRight.GetBlocks(pos, ref list); } } public void GetBlocks(Rectangle box, ref List list) { if (visualBlock != null) list.Add(visualBlock); if (topLeft != null) { if (topLeft.bbox.IntersectsWith(box)) topLeft.GetBlocks(box, ref list); if (topRight.bbox.IntersectsWith(box)) topRight.GetBlocks(box, ref list); if (bottomLeft.bbox.IntersectsWith(box)) bottomLeft.GetBlocks(box, ref list); if (bottomRight.bbox.IntersectsWith(box)) bottomRight.GetBlocks(box, ref list); } } public VisualBlockEntry GetEntry(Rectangle box, int level = 0) { if (level == MaxLevels) { if (visualBlock == null) visualBlock = new VisualBlockEntry(); return visualBlock; } if (topLeft == null) CreateChildren(); if (topLeft.bbox.Contains(box)) return topLeft.GetEntry(box, level + 1); if (topRight.bbox.Contains(box)) return topRight.GetEntry(box, level + 1); if (bottomLeft.bbox.Contains(box)) return bottomLeft.GetEntry(box, level + 1); if (bottomRight.bbox.Contains(box)) return bottomRight.GetEntry(box, level + 1); if (visualBlock == null) visualBlock = new VisualBlockEntry(); return visualBlock; } void CreateChildren() { int x0 = bbox.X; int x1 = bbox.X + bbox.Width / 2; int x2 = bbox.X + bbox.Width; int y0 = bbox.Y; int y1 = bbox.Y + bbox.Height / 2; int y2 = bbox.Y + bbox.Height; topLeft = new Node(new Rectangle(x0, y0, x1 - x0, y1 - y0)); topRight = new Node(new Rectangle(x1, y0, x2 - x1, y1 - y0)); bottomLeft = new Node(new Rectangle(x0, y1, x1 - x0, y2 - y1)); bottomRight = new Node(new Rectangle(x1, y1, x2 - x1, y2 - y1)); } Rectangle bbox; Vector2D extents; Vector2D center; Node topLeft; Node topRight; Node bottomLeft; Node bottomRight; VisualBlockEntry visualBlock; } } }