Compare commits
	
		
			No commits in common. "ceb86c0a979f85376d4b0a98270bb997c8788f9f" and "54c8176302a32c8203975dbaa6c9c990f7cbef68" have entirely different histories.
		
	
	
		
			ceb86c0a97
			...
			54c8176302
		
	
		|  | @ -1,311 +0,0 @@ | ||||||
| using System.Numerics; |  | ||||||
| using KeepersCompound.LGS.Database.Chunks; |  | ||||||
| 
 |  | ||||||
| namespace KeepersCompound.Lightmapper; |  | ||||||
| 
 |  | ||||||
| public class PotentiallyVisibleSet |  | ||||||
| { |  | ||||||
|     private class Edge |  | ||||||
|     { |  | ||||||
|         public int Destination; |  | ||||||
|         public Poly Poly; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private class Poly(Vector3[] vertices, Plane plane) |  | ||||||
|     { |  | ||||||
|         public Vector3[] Vertices = vertices; |  | ||||||
|         public Plane Plane = plane; |  | ||||||
| 
 |  | ||||||
|         public bool IsCoplanar(Poly other) |  | ||||||
|         { |  | ||||||
|             // TODO: should this be in mathutils? |  | ||||||
|             const float e = MathUtils.Epsilon; |  | ||||||
|             var m = Plane.D / other.Plane.D; |  | ||||||
| 
 |  | ||||||
|             var n0 = Plane.Normal; |  | ||||||
|             var n1 = other.Plane.Normal * m; |  | ||||||
|             return Math.Abs(n0.X - n1.X) < e && Math.Abs(n0.Y - n1.Y) < e && Math.Abs(n0.Z - n1.Z) < e; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     private readonly List<int>[] _portalGraph; |  | ||||||
|     private readonly List<Edge> _edges; |  | ||||||
|     private readonly Dictionary<int, HashSet<int>> _visibilitySet; |  | ||||||
| 
 |  | ||||||
|     // TODO: |  | ||||||
|     // - This is a conservative algorithm based on Matt's Ramblings Quake PVS video |  | ||||||
|     // - Build portal graph (or just use WR directly) |  | ||||||
|     // - A cell can always see it's self and any immediate neighbours |  | ||||||
|     // - The third depth cell is also visible unless the portal to it is coplanar with the second cells portal (do I need to think about this?) |  | ||||||
|     // - For all further cells: |  | ||||||
|     //   - Generate separating planes between the source cell portal and the previously passed (clipped) portal |  | ||||||
|     //   - Clip the target portal to the new cell using the separating planes |  | ||||||
|     //   - If anything is left of the clipped portal, we can see, otherwise we discard that cell |  | ||||||
|     // - The full process is a recursive depth first search |  | ||||||
| 
 |  | ||||||
|     public PotentiallyVisibleSet(WorldRep.Cell[] cells) |  | ||||||
|     { |  | ||||||
|         _edges = []; |  | ||||||
|         _visibilitySet = new Dictionary<int, HashSet<int>>(); |  | ||||||
|          |  | ||||||
|         _portalGraph = new List<int>[cells.Length]; |  | ||||||
|         for (var i = 0; i < cells.Length; i++) |  | ||||||
|         { |  | ||||||
|             _portalGraph[i] = []; |  | ||||||
|             var cell = cells[i]; |  | ||||||
| 
 |  | ||||||
|             // If a cell is "blocks vision" flagged, we can never see out of it |  | ||||||
|             // We can see into it though, so we still want the edges coming in |  | ||||||
|             if ((cell.Flags & 8) != 0) |  | ||||||
|             { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             // We have to cycle through *all* polys rather than just portals to calculate the correct poly vertex offsets |  | ||||||
|             var indicesOffset = 0; |  | ||||||
|             var portalStartIdx = cell.PolyCount - cell.PortalPolyCount; |  | ||||||
|             for (var j = 0; j < cell.PolyCount; j++) |  | ||||||
|             { |  | ||||||
|                 var poly = cell.Polys[j]; |  | ||||||
|                 if (j < portalStartIdx) |  | ||||||
|                 { |  | ||||||
|                     indicesOffset += poly.VertexCount; |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|                  |  | ||||||
|                 var other = poly.Destination; |  | ||||||
|                  |  | ||||||
|                 // Checking if there's already an edge is super slow. It's much faster to just add a new edge, even with |  | ||||||
|                 // the duplicated poly |  | ||||||
|                 var vs = new Vector3[poly.VertexCount]; |  | ||||||
|                 for (var vIdx = 0; vIdx < poly.VertexCount; vIdx++) |  | ||||||
|                 { |  | ||||||
|                     vs[vIdx] = cell.Vertices[cell.Indices[indicesOffset + vIdx]]; |  | ||||||
|                 } |  | ||||||
|                      |  | ||||||
|                 var edge = new Edge |  | ||||||
|                 { |  | ||||||
|                     Destination = other, |  | ||||||
|                     Poly = new Poly(vs, cell.Planes[poly.PlaneId]), |  | ||||||
|                 }; |  | ||||||
|                 _edges.Add(edge); |  | ||||||
|                 _portalGraph[i].Add(_edges.Count - 1); |  | ||||||
|                 indicesOffset += poly.VertexCount; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public int[] GetVisible(int cellIdx) |  | ||||||
|     { |  | ||||||
|         if (_visibilitySet.TryGetValue(cellIdx, out var value)) |  | ||||||
|         { |  | ||||||
|             return [..value]; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var visibleCells = ComputeVisibility(cellIdx); |  | ||||||
|         _visibilitySet.Add(cellIdx, visibleCells); |  | ||||||
|         return [..visibleCells]; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     private HashSet<int> ComputeVisibility(int cellIdx) |  | ||||||
|     { |  | ||||||
|         if (cellIdx >= _portalGraph.Length) |  | ||||||
|         { |  | ||||||
|             return []; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // A cell can always see itself, so we'll add that now |  | ||||||
|         var visible = new HashSet<int>(); |  | ||||||
|         visible.Add(cellIdx); |  | ||||||
| 
 |  | ||||||
|         // Additionally a cell can always see it's direct neighbours (obviously) |  | ||||||
|         foreach (var edgeIndex in _portalGraph[cellIdx]) |  | ||||||
|         { |  | ||||||
|             var edge = _edges[edgeIndex]; |  | ||||||
|             var neighbourIdx = edge.Destination; |  | ||||||
|             visible.Add(neighbourIdx); |  | ||||||
|              |  | ||||||
|             // Neighbours of our direct neighbour are always visible, unless they're coplanar |  | ||||||
|             foreach (var innerEdgeIndex in _portalGraph[neighbourIdx]) |  | ||||||
|             { |  | ||||||
|                 var innerEdge = _edges[innerEdgeIndex]; |  | ||||||
|                 if (innerEdge.Destination == cellIdx || edge.Poly.IsCoplanar(innerEdge.Poly)) |  | ||||||
|                 { |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 // Now we get to the recursive section |  | ||||||
|                 ComputeClippedVisibility(visible, edge.Poly, innerEdge.Poly, neighbourIdx, innerEdge.Destination, 0); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         return visible; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // TODO: Name this better |  | ||||||
|     // TODO: This *should* be poly's not edges |  | ||||||
|     private void ComputeClippedVisibility( |  | ||||||
|         HashSet<int> visible, |  | ||||||
|         Poly sourcePoly, |  | ||||||
|         Poly previousPoly, |  | ||||||
|         int previousCellIdx, |  | ||||||
|         int currentCellIdx, |  | ||||||
|         int depth) |  | ||||||
|     { |  | ||||||
|         if (depth > 2048) |  | ||||||
|         { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         visible.Add(currentCellIdx); |  | ||||||
| 
 |  | ||||||
|         // Generate separating planes |  | ||||||
|         var separators = new List<Plane>(); |  | ||||||
|         separators.AddRange(GetSeparatingPlanes(sourcePoly, previousPoly, false)); |  | ||||||
|         separators.AddRange(GetSeparatingPlanes(previousPoly, sourcePoly, true)); |  | ||||||
|          |  | ||||||
|         // Clip all new polys and recurse |  | ||||||
|         foreach (var edgeIndex in _portalGraph[currentCellIdx]) |  | ||||||
|         { |  | ||||||
|             var edge = _edges[edgeIndex]; |  | ||||||
|             if (edge.Destination == previousCellIdx || previousPoly.IsCoplanar(edge.Poly)) |  | ||||||
|             { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             var poly = separators.Aggregate(edge.Poly, ClipPolygonByPlane); |  | ||||||
|             if (poly.Vertices.Length == 0) |  | ||||||
|             { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             ComputeClippedVisibility(visible, sourcePoly, poly, currentCellIdx, edge.Destination, depth + 1); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static List<Plane> GetSeparatingPlanes(Poly p0, Poly p1, bool flip) |  | ||||||
|     { |  | ||||||
|         var separators = new List<Plane>(); |  | ||||||
|         for (var i = 0; i < p0.Vertices.Length; i++) |  | ||||||
|         { |  | ||||||
|             // brute force all combinations |  | ||||||
|             // there's probably some analytical way to choose the "correct" v2 but I couldn't find anything online |  | ||||||
|             var v0 = p0.Vertices[i]; |  | ||||||
|             var v1 = p0.Vertices[(i + 1) % p0.Vertices.Length]; |  | ||||||
|             for (var j = 0; j < p1.Vertices.Length; j++) |  | ||||||
|             { |  | ||||||
|                 var v2 = p1.Vertices[j]; |  | ||||||
|                  |  | ||||||
|                 var normal = Vector3.Normalize(Vector3.Cross(v1 - v0, v2 - v0)); |  | ||||||
|                 var d = Vector3.Dot(v2, normal); |  | ||||||
|                 var plane = new Plane(normal, d); |  | ||||||
|                  |  | ||||||
|                 // Depending on how the edges were built, the resulting plane might be facing the wrong way |  | ||||||
|                 if (MathUtils.DistanceFromPlane(p0.Plane, v2) < MathUtils.Epsilon) |  | ||||||
|                 { |  | ||||||
|                     plane.Normal = -plane.Normal; |  | ||||||
|                     plane.D = -plane.D; |  | ||||||
|                 } |  | ||||||
|                  |  | ||||||
|                 // All points should be behind/on the plane |  | ||||||
|                 var count = 0; |  | ||||||
|                 for (var k = 0; k < p1.Vertices.Length; k++) |  | ||||||
|                 { |  | ||||||
|                     if (k == j || MathUtils.DistanceFromPlane(plane, p1.Vertices[k]) > MathUtils.Epsilon) |  | ||||||
|                     { |  | ||||||
|                         count++; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 if (count != p1.Vertices.Length) |  | ||||||
|                 { |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 if (flip) |  | ||||||
|                 { |  | ||||||
|                     plane.Normal = -plane.Normal; |  | ||||||
|                     plane.D = -plane.D; |  | ||||||
|                 } |  | ||||||
|                  |  | ||||||
|                 separators.Add(plane); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return separators; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     private enum Side  |  | ||||||
|     { |  | ||||||
|         Front, |  | ||||||
|         On, |  | ||||||
|         Back |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // TODO: is this reference type poly going to fuck me? |  | ||||||
|     // TODO: Should this and Poly be in MathUtils? |  | ||||||
|     private static Poly ClipPolygonByPlane(Poly poly, Plane plane) |  | ||||||
|     { |  | ||||||
|         var vertexCount = poly.Vertices.Length; |  | ||||||
|          |  | ||||||
|         // Firstly we want to tally up what side of the plane each point of the poly is on |  | ||||||
|         // This is used both to early out if nothing/everything is clipped, and to aid the clipping |  | ||||||
|         var distances = new float[vertexCount]; |  | ||||||
|         var sides = new Side[vertexCount]; |  | ||||||
|         var counts = new int[3]; |  | ||||||
|         for (var i = 0; i < vertexCount; i++) |  | ||||||
|         { |  | ||||||
|             var distance = MathUtils.DistanceFromPlane(plane, poly.Vertices[i]); |  | ||||||
|             distances[i] = distance; |  | ||||||
|             sides[i] = distance switch { |  | ||||||
|                 > MathUtils.Epsilon => Side.Back, |  | ||||||
|                 <-MathUtils.Epsilon => Side.Front, |  | ||||||
|                 _ => Side.On, |  | ||||||
|             }; |  | ||||||
|             counts[(int)sides[i]]++; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Everything is within the half-space, so we don't need to clip anything |  | ||||||
|         if (counts[(int)Side.Back] == 0) |  | ||||||
|         { |  | ||||||
|             return new Poly(poly.Vertices, poly.Plane); |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         // Everything is outside the half-space, so we clip everything |  | ||||||
|         if (counts[(int)Side.Back] == vertexCount) |  | ||||||
|         { |  | ||||||
|             return new Poly([], poly.Plane); |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         var vertices = new List<Vector3>(); |  | ||||||
|         for (var i = 0; i < vertexCount; i++) |  | ||||||
|         { |  | ||||||
|             var i1 = (i + 1) % vertexCount; |  | ||||||
|             var v0 = poly.Vertices[i]; |  | ||||||
|             var v1 = poly.Vertices[i1]; |  | ||||||
|             var side = sides[i]; |  | ||||||
|             var nextSide = sides[i1]; |  | ||||||
|              |  | ||||||
|             // Vertices that are inside/on the half-space don't get clipped |  | ||||||
|             if (sides[i] != Side.Back) |  | ||||||
|             { |  | ||||||
|                 vertices.Add(v0); |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             // We only need to do any clipping if we've swapped from front-to-back or vice versa |  | ||||||
|             // If either the current or next side is On then that's where we would have clipped to |  | ||||||
|             // anyway so we also don't need to do anything |  | ||||||
|             if (side == Side.On || nextSide == Side.On || side != nextSide) |  | ||||||
|             { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // This is how far along the vector v0 -> v1 the front/back crossover occurs |  | ||||||
|             var frac = distances[i] / (distances[i] - distances[i1]); |  | ||||||
|             var splitVertex = v0 + frac * (v1 - v0); |  | ||||||
|             vertices.Add(splitVertex); |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         return new Poly([..vertices], poly.Plane); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
		Loading…
	
		Reference in New Issue