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