Semi-fast coarse PVS

This commit is contained in:
Jarrod Doyle 2025-01-20 18:10:07 +00:00
parent 6a7ee922b5
commit b0e4c8dff3
Signed by: Jayrude
GPG Key ID: 38B57B16E7C0ADF7
1 changed files with 178 additions and 94 deletions

View File

@ -5,10 +5,18 @@ namespace KeepersCompound.Lightmapper;
public class PotentiallyVisibleSet public class PotentiallyVisibleSet
{ {
private struct Edge private struct Node(List<int> edgeIndices)
{ {
public int Destination; public bool VisibilityComputed = false;
public Poly Poly; public HashSet<int> VisibleNodes = [];
public readonly List<int> EdgeIndices = edgeIndices;
}
private readonly struct Edge(int destination, Poly poly)
{
public readonly HashSet<int> MightSee = [];
public readonly int Destination = destination;
public readonly Poly Poly = poly;
public override string ToString() public override string ToString()
{ {
@ -44,9 +52,8 @@ public class PotentiallyVisibleSet
} }
} }
private readonly List<int>[] _portalGraph; private readonly Node[] _graph;
private readonly List<Edge> _edges; private readonly List<Edge> _edges;
private readonly Dictionary<int, HashSet<int>> _visibilitySet;
private const float Epsilon = 0.1f; private const float Epsilon = 0.1f;
@ -68,19 +75,19 @@ public class PotentiallyVisibleSet
public PotentiallyVisibleSet(WorldRep.Cell[] cells) public PotentiallyVisibleSet(WorldRep.Cell[] cells)
{ {
_graph = new Node[cells.Length];
_edges = []; _edges = [];
_visibilitySet = new Dictionary<int, HashSet<int>>();
_portalGraph = new List<int>[cells.Length];
for (var i = 0; i < cells.Length; i++) for (var i = 0; i < cells.Length; i++)
{ {
_portalGraph[i] = [];
var cell = cells[i]; var cell = cells[i];
var edgeIndices = new List<int>(cell.PortalPolyCount);
// If a cell is "blocks vision" flagged, we can never see out of it // 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 // We can see into it though, so we still want the edges coming in
if ((cell.Flags & 8) != 0) if ((cell.Flags & 8) != 0)
{ {
_graph[i] = new Node(edgeIndices);
continue; continue;
} }
@ -96,8 +103,6 @@ public class PotentiallyVisibleSet
continue; 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 // 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 // the duplicated poly
var vs = new List<Vector3>(poly.VertexCount); var vs = new List<Vector3>(poly.VertexCount);
@ -106,33 +111,96 @@ public class PotentiallyVisibleSet
vs.Add(cell.Vertices[cell.Indices[indicesOffset + vIdx]]); vs.Add(cell.Vertices[cell.Indices[indicesOffset + vIdx]]);
} }
var edge = new Edge var edge = new Edge(poly.Destination, new Poly(vs, cell.Planes[poly.PlaneId]));
{ edgeIndices.Add(_edges.Count);
Destination = other,
Poly = new Poly(vs, cell.Planes[poly.PlaneId]),
};
_edges.Add(edge); _edges.Add(edge);
_portalGraph[i].Add(_edges.Count - 1);
indicesOffset += poly.VertexCount; indicesOffset += poly.VertexCount;
} }
_graph[i] = new Node(edgeIndices);
} }
Parallel.For(0, _edges.Count, ComputeEdgeMightSee);
} }
public int[] GetVisible(int cellIdx) public int[] GetVisible(int cellIdx)
{ {
if (_visibilitySet.TryGetValue(cellIdx, out var value)) // TODO: Handle out of range indices
var node = _graph[cellIdx];
if (node.VisibilityComputed)
{ {
return [..value]; return [..node.VisibleNodes];
} }
var visibleCells = ComputeVisibility(cellIdx); var visibleCells = ComputeVisibility(cellIdx);
_visibilitySet.Add(cellIdx, visibleCells); node.VisibilityComputed = true;
node.VisibleNodes = visibleCells;
_graph[cellIdx] = node;
return [..visibleCells]; return [..visibleCells];
} }
private void ComputeEdgeMightSee(int edgeIdx)
{
var source = _edges[edgeIdx];
var sourcePlane = source.Poly.Plane;
Flood(source.Destination);
return;
void Flood(int cellIdx)
{
if (!source.MightSee.Add(cellIdx))
{
return; // target is already explored
}
// Target must be partly behind source, source must be partly in front of target, and source and target cannot face each other
foreach (var targetEdgeIdx in _graph[cellIdx].EdgeIndices)
{
var target = _edges[targetEdgeIdx];
var targetPlane = target.Poly.Plane;
var validTarget = false;
foreach (var v in target.Poly.Vertices)
{
if (MathUtils.DistanceFromPlane(sourcePlane, v) < -MathUtils.Epsilon)
{
validTarget = true;
break;
}
}
if (!validTarget)
{
continue;
}
validTarget = false;
foreach (var v in source.Poly.Vertices)
{
if (MathUtils.DistanceFromPlane(targetPlane, v) > MathUtils.Epsilon)
{
validTarget = true;
break;
}
}
if (!validTarget)
{
continue;
}
if (Vector3.Dot(sourcePlane.Normal, targetPlane.Normal) > MathUtils.Epsilon - 1)
{
Flood(target.Destination);
}
}
}
}
private HashSet<int> ComputeVisibility(int cellIdx) private HashSet<int> ComputeVisibility(int cellIdx)
{ {
if (cellIdx >= _portalGraph.Length) if (cellIdx >= _graph.Length)
{ {
return []; return [];
} }
@ -141,86 +209,102 @@ public class PotentiallyVisibleSet
var visible = new HashSet<int>(); var visible = new HashSet<int>();
visible.Add(cellIdx); visible.Add(cellIdx);
// Additionally a cell can always see it's direct neighbours (obviously) foreach (var edgeIdx in _graph[cellIdx].EdgeIndices)
foreach (var edgeIndex in _portalGraph[cellIdx])
{ {
var edge = _edges[edgeIndex]; var edge = _edges[edgeIdx];
var neighbourIdx = edge.Destination; foreach (var mightSee in edge.MightSee)
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]; visible.Add(mightSee);
if (innerEdge.Destination == cellIdx || edge.Poly.IsCoplanar(innerEdge.Poly))
{
continue;
}
ExplorePortalRecursive(visible, edge.Poly, new Poly(innerEdge.Poly), neighbourIdx, innerEdge.Destination, 0);
} }
} }
return visible; return visible;
// if (cellIdx >= _portalGraph.Length)
// {
// return [];
// }
// 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;
// }
//
// ExplorePortalRecursive(visible, edge.Poly, new Poly(innerEdge.Poly), neighbourIdx, innerEdge.Destination, 0);
// }
// }
// return visible;
} }
private void ExplorePortalRecursive( // private void ExplorePortalRecursive(
HashSet<int> visible, // HashSet<int> visible,
Poly sourcePoly, // Poly sourcePoly,
Poly previousPoly, // Poly previousPoly,
int previousCellIdx, // int previousCellIdx,
int currentCellIdx, // int currentCellIdx,
int depth) // int depth)
{ // {
// TODO: Might need to lose this // // TODO: Might need to lose this
if (depth > 1024) // if (depth > 1024)
{ // {
return; // return;
} // }
//
visible.Add(currentCellIdx); // visible.Add(currentCellIdx);
//
// Only one edge out of the cell means we'd be going back on ourselves // // Only one edge out of the cell means we'd be going back on ourselves
if (_portalGraph[currentCellIdx].Count <= 1) // if (_portalGraph[currentCellIdx].Count <= 1)
{ // {
return; // return;
} // }
//
// TODO: If all neighbours are already in `visible` skip exploring? // // TODO: If all neighbours are already in `visible` skip exploring?
//
var separators = new List<Plane>(); // var separators = new List<Plane>();
GetSeparatingPlanes(separators, sourcePoly, previousPoly, false); // GetSeparatingPlanes(separators, sourcePoly, previousPoly, false);
GetSeparatingPlanes(separators, previousPoly, sourcePoly, true); // GetSeparatingPlanes(separators, previousPoly, sourcePoly, true);
//
// The case for this occuring is... interesting ( idk ) // // The case for this occuring is... interesting ( idk )
if (separators.Count == 0) // if (separators.Count == 0)
{ // {
return; // return;
} // }
//
// Clip all new polys and recurse // // Clip all new polys and recurse
foreach (var edgeIndex in _portalGraph[currentCellIdx]) // foreach (var edgeIndex in _portalGraph[currentCellIdx])
{ // {
var edge = _edges[edgeIndex]; // var edge = _edges[edgeIndex];
if (edge.Destination == previousCellIdx || previousPoly.IsCoplanar(edge.Poly) || sourcePoly.IsCoplanar(edge.Poly)) // if (edge.Destination == previousCellIdx || previousPoly.IsCoplanar(edge.Poly) || sourcePoly.IsCoplanar(edge.Poly))
{ // {
continue; // continue;
} // }
//
var poly = new Poly(edge.Poly); // var poly = new Poly(edge.Poly);
foreach (var separator in separators) // foreach (var separator in separators)
{ // {
ClipPolygonByPlane(ref poly, separator); // ClipPolygonByPlane(ref poly, separator);
} // }
//
if (poly.Vertices.Count == 0) // if (poly.Vertices.Count == 0)
{ // {
continue; // continue;
} // }
//
ExplorePortalRecursive(visible, sourcePoly, poly, currentCellIdx, edge.Destination, depth + 1); // ExplorePortalRecursive(visible, sourcePoly, poly, currentCellIdx, edge.Destination, depth + 1);
} // }
} // }
// TODO: We're getting multiple separating planes that are the same, let's not somehow? // TODO: We're getting multiple separating planes that are the same, let's not somehow?
private static void GetSeparatingPlanes(List<Plane> separators, Poly p0, Poly p1, bool flip) private static void GetSeparatingPlanes(List<Plane> separators, Poly p0, Poly p1, bool flip)