Compare commits

..

No commits in common. "b8c16a1ffccf62d57d305a60cc494626bd5c57ef" and "317b17ab02a1890f5762a68e569c83be7ed31c86" have entirely different histories.

3 changed files with 146 additions and 222 deletions

View File

@ -371,63 +371,71 @@ public class LightMapper
if (!_mission.TryGetChunk<WorldRep>("WREXT", out var worldRep)) if (!_mission.TryGetChunk<WorldRep>("WREXT", out var worldRep))
return; return;
// var lightVisibleCells = Timing.TimeStage("Light PVS", () =>
var lightVisibleCells = Timing.TimeStage("Light PVS", () =>
{
var cellCount = worldRep.Cells.Length;
var aabbs = new MathUtils.Aabb[worldRep.Cells.Length];
Parallel.For(0, cellCount, i => aabbs[i] = new MathUtils.Aabb(worldRep.Cells[i].Vertices));
var lightCellMap = new int[_lights.Count];
Parallel.For(0, _lights.Count, i =>
{
lightCellMap[i] = -1;
var light = _lights[i];
for (var j = 0; j < cellCount; j++)
{
if (!MathUtils.Intersects(aabbs[j], light.Position))
{
continue;
}
// Half-space contained
var cell = worldRep.Cells[j];
var contained = true;
for (var k = 0; k < cell.PlaneCount; k++)
{
var plane = cell.Planes[k];
if (MathUtils.DistanceFromPlane(plane, light.Position) < -MathUtils.Epsilon)
{
contained = false;
break;
}
}
if (contained)
{
lightCellMap[i] = j;
break;
}
}
});
var lightVisibleCells = new List<int[]>(_lights.Count);
var pvs = new PotentiallyVisibleSet(worldRep.Cells);
for (var i = 0; i < _lights.Count; i++)
{
var cellIdx = lightCellMap[i];
if (cellIdx == -1)
{
lightVisibleCells.Add([]);
continue;
}
var visibleSet = pvs.GetVisible(lightCellMap[i]);
lightVisibleCells.Add(visibleSet);
}
return lightVisibleCells;
// TODO: This isn't actually thread safe :)
// Parallel.For(0, worldRep.Cells.Length, i => pvs.GetVisible(i));
// for (var i = 0; i < worldRep.Cells.Length; i++)
// { // {
// var cellCount = worldRep.Cells.Length; // pvs.GetVisible(i);
// var aabbs = new MathUtils.Aabb[worldRep.Cells.Length]; // // var visible = pvs.GetVisible(i);
// Parallel.For(0, cellCount, i => aabbs[i] = new MathUtils.Aabb(worldRep.Cells[i].Vertices)); // // Console.WriteLine($"Cell {i}: Count({visible.Length}), Visible[{string.Join(" ", visible)}]");
//
// var lightCellMap = new int[_lights.Count];
// Parallel.For(0, _lights.Count, i =>
// {
// lightCellMap[i] = -1;
// var light = _lights[i];
// for (var j = 0; j < cellCount; j++)
// {
// if (!MathUtils.Intersects(aabbs[j], light.Position))
// {
// continue;
// } // }
// });
// // Half-space contained
// var cell = worldRep.Cells[j];
// var contained = true;
// for (var k = 0; k < cell.PlaneCount; k++)
// {
// var plane = cell.Planes[k];
// if (MathUtils.DistanceFromPlane(plane, light.Position) < -MathUtils.Epsilon)
// {
// contained = false;
// break;
// }
// }
//
// if (contained)
// {
// lightCellMap[i] = j;
// break;
// }
// }
// });
//
// var lightVisibleCells = new List<int[]>(_lights.Count);
// var pvs = new PotentiallyVisibleSet(worldRep.Cells);
// for (var i = 0; i < _lights.Count; i++)
// {
// var cellIdx = lightCellMap[i];
// if (cellIdx == -1)
// {
// lightVisibleCells.Add([]);
// continue;
// }
// var visibleSet = pvs.GetVisible(lightCellMap[i]);
// lightVisibleCells.Add(visibleSet);
// }
//
// Console.WriteLine($"17: [{string.Join(", ", pvs.GetVisible(17))}]");
//
// return lightVisibleCells;
// });
// TODO: Move this functionality to the LGS library // TODO: Move this functionality to the LGS library
// We set up light indices in separately from lighting because the actual // We set up light indices in separately from lighting because the actual
@ -471,10 +479,10 @@ public class LightMapper
continue; continue;
} }
// if (!lightVisibleCells[j].Contains(i)) if (!lightVisibleCells[j].Contains(i))
// { {
// continue; continue;
// } }
cell.LightIndexCount++; cell.LightIndexCount++;
cell.LightIndices.Add((ushort)light.LightTableIndex); cell.LightIndices.Add((ushort)light.LightTableIndex);
@ -483,7 +491,7 @@ public class LightMapper
if (cell.LightIndexCount > 97) if (cell.LightIndexCount > 97)
{ {
// Console.WriteLine($"WARNING: Too many lights in cell at ({cell.SphereCenter}): {cell.LightIndexCount - 1} / 96"); Console.WriteLine($"WARNING: Too many lights in cell at ({cell.SphereCenter}): {cell.LightIndexCount - 1} / 96");
} }
}); });
@ -633,15 +641,12 @@ public class LightMapper
} }
} }
// foreach (var lightIdx in cell.LightIndices) foreach (var lightIdx in cell.LightIndices)
for (var i = 0; i < cell.LightIndexCount; i++)
{ {
var lightIdx = cell.LightIndices[i]; if (lightIdx == 0)
if (i == 0 || lightIdx == 0)
{ {
continue; continue;
} }
var light = _lights[lightIdx - 1]; var light = _lights[lightIdx - 1];
// Check if plane normal is facing towards the light // Check if plane normal is facing towards the light
@ -656,7 +661,7 @@ public class LightMapper
// If there aren't *any* points on the plane that are in range of the light // If there aren't *any* points on the plane that are in range of the light
// then none of the lightmap points will be so we can discard. // then none of the lightmap points will be so we can discard.
// The more compact a map is the less effective this is // The more compact a map is the less effective this is
var planeDist = Math.Abs(MathUtils.DistanceFromPlane(plane, light.Position)); var planeDist = MathUtils.DistanceFromPlane(plane, light.Position);
if (planeDist > light.Radius) if (planeDist > light.Radius)
{ {
continue; continue;

View File

@ -5,42 +5,26 @@ namespace KeepersCompound.Lightmapper;
public class PotentiallyVisibleSet public class PotentiallyVisibleSet
{ {
private struct Edge private class Edge
{ {
public int Destination; public int Destination;
public Poly Poly; public Poly Poly;
public override string ToString()
{
return $"<Destination: {Destination}, Poly: {Poly}";
}
} }
private struct Poly private class Poly(Vector3[] vertices, Plane plane)
{ {
public List<Vector3> Vertices; public Vector3[] Vertices = vertices;
public readonly Plane Plane; public Plane Plane = plane;
public Poly(List<Vector3> vertices, Plane plane)
{
Vertices = vertices;
Plane = plane;
}
public Poly(Poly other)
{
Vertices = [..other.Vertices];
Plane = other.Plane;
}
public bool IsCoplanar(Poly other) public bool IsCoplanar(Poly other)
{ {
return MathUtils.IsCoplanar(Plane, other.Plane); // TODO: should this be in mathutils?
} const float e = MathUtils.Epsilon;
var m = Plane.D / other.Plane.D;
public override string ToString() var n0 = Plane.Normal;
{ var n1 = other.Plane.Normal * m;
return $"<Plane: {Plane}, Vertices: [{string.Join(", ", Vertices)}]"; return Math.Abs(n0.X - n1.X) < e && Math.Abs(n0.Y - n1.Y) < e && Math.Abs(n0.Z - n1.Z) < e;
} }
} }
@ -48,13 +32,6 @@ public class PotentiallyVisibleSet
private readonly List<Edge> _edges; private readonly List<Edge> _edges;
private readonly Dictionary<int, HashSet<int>> _visibilitySet; private readonly Dictionary<int, HashSet<int>> _visibilitySet;
private const float Epsilon = 0.1f;
// This is yucky and means we're not thread safe
private readonly List<float> _clipDistances = new(32);
private readonly List<Side> _clipSides = new(32);
private readonly int[] _clipCounts = [0, 0, 0];
// TODO: // TODO:
// - This is a conservative algorithm based on Matt's Ramblings Quake PVS video // - This is a conservative algorithm based on Matt's Ramblings Quake PVS video
// - Build portal graph (or just use WR directly) // - Build portal graph (or just use WR directly)
@ -100,10 +77,10 @@ public class PotentiallyVisibleSet
// 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 Vector3[poly.VertexCount];
for (var vIdx = 0; vIdx < poly.VertexCount; vIdx++) for (var vIdx = 0; vIdx < poly.VertexCount; vIdx++)
{ {
vs.Add(cell.Vertices[cell.Indices[indicesOffset + vIdx]]); vs[vIdx] = cell.Vertices[cell.Indices[indicesOffset + vIdx]];
} }
var edge = new Edge var edge = new Edge
@ -157,14 +134,17 @@ public class PotentiallyVisibleSet
continue; continue;
} }
ExplorePortalRecursive(visible, edge.Poly, new Poly(innerEdge.Poly), neighbourIdx, innerEdge.Destination, 0); // Now we get to the recursive section
ComputeClippedVisibility(visible, edge.Poly, innerEdge.Poly, neighbourIdx, innerEdge.Destination, 0);
} }
} }
return visible; return visible;
} }
private void ExplorePortalRecursive( // TODO: Name this better
// TODO: This *should* be poly's not edges
private void ComputeClippedVisibility(
HashSet<int> visible, HashSet<int> visible,
Poly sourcePoly, Poly sourcePoly,
Poly previousPoly, Poly previousPoly,
@ -172,130 +152,87 @@ public class PotentiallyVisibleSet
int currentCellIdx, int currentCellIdx,
int depth) int depth)
{ {
// TODO: Might need to lose this if (depth > 2048)
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 // Generate separating planes
if (_portalGraph[currentCellIdx].Count <= 1)
{
return;
}
// 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); separators.AddRange(GetSeparatingPlanes(sourcePoly, previousPoly, false));
GetSeparatingPlanes(separators, previousPoly, sourcePoly, true); separators.AddRange(GetSeparatingPlanes(previousPoly, sourcePoly, true));
// The case for this occuring is... interesting ( idk )
if (separators.Count == 0)
{
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))
{ {
continue; continue;
} }
var poly = new Poly(edge.Poly); var poly = separators.Aggregate(edge.Poly, ClipPolygonByPlane);
foreach (var separator in separators) if (poly.Vertices.Length == 0)
{
ClipPolygonByPlane(ref poly, separator);
}
if (poly.Vertices.Count == 0)
{ {
continue; continue;
} }
ExplorePortalRecursive(visible, sourcePoly, poly, currentCellIdx, edge.Destination, depth + 1); ComputeClippedVisibility(visible, sourcePoly, poly, currentCellIdx, edge.Destination, depth + 1);
} }
} }
// TODO: We're getting multiple separating planes that are the same, let's not somehow? private static List<Plane> GetSeparatingPlanes(Poly p0, Poly p1, bool flip)
private static void GetSeparatingPlanes(List<Plane> separators, Poly p0, Poly p1, bool flip)
{ {
for (var i = 0; i < p0.Vertices.Count; i++) var separators = new List<Plane>();
for (var i = 0; i < p0.Vertices.Length; i++)
{ {
// brute force all combinations // brute force all combinations
// there's probably some analytical way to choose the "correct" v2 but I couldn't find anything online // there's probably some analytical way to choose the "correct" v2 but I couldn't find anything online
var v0 = p0.Vertices[i]; var v0 = p0.Vertices[i];
var v1 = p0.Vertices[(i + 1) % p0.Vertices.Count]; var v1 = p0.Vertices[(i + 1) % p0.Vertices.Length];
for (var j = 0; j < p1.Vertices.Count; j++) for (var j = 0; j < p1.Vertices.Length; j++)
{ {
var v2 = p1.Vertices[j]; var v2 = p1.Vertices[j];
var normal = Vector3.Cross(v1 - v0, v2 - v0); var normal = Vector3.Normalize(Vector3.Cross(v1 - v0, v2 - v0));
if (normal.LengthSquared() < Epsilon) var d = Vector3.Dot(v2, normal);
{
// colinear (or near colinear) points will produce an invalid plane
continue;
}
normal = Vector3.Normalize(normal);
var d = -Vector3.Dot(v2, normal);
// Depending on how the edges were built, the resulting plane might be facing the wrong way
var distanceToSource = MathUtils.DistanceFromPlane(p0.Plane, v2);
if (distanceToSource > Epsilon)
{
normal = -normal;
d = -d;
}
var plane = new Plane(normal, d); var plane = new Plane(normal, d);
if (MathUtils.IsCoplanar(plane, flip ? p0.Plane : p1.Plane)) // Depending on how the edges were built, the resulting plane might be facing the wrong way
if (MathUtils.DistanceFromPlane(p0.Plane, v2) < MathUtils.Epsilon)
{ {
continue; plane.Normal = -plane.Normal;
plane.D = -plane.D;
} }
// All points should be in front of the plane (except for the point used to create it) // All points should be behind/on the plane
var invalid = false;
var count = 0; var count = 0;
for (var k = 0; k < p1.Vertices.Count; k++) for (var k = 0; k < p1.Vertices.Length; k++)
{ {
if (k == j) if (k == j || MathUtils.DistanceFromPlane(plane, p1.Vertices[k]) > MathUtils.Epsilon)
{
continue;
}
var dist = MathUtils.DistanceFromPlane(plane, p1.Vertices[k]);
if (dist > Epsilon)
{ {
count++; count++;
} }
else if (dist < -Epsilon)
{
invalid = true;
break;
}
} }
if (invalid || count == 0) if (count != p1.Vertices.Length)
{ {
continue; continue;
} }
if (flip) if (flip)
{ {
plane.Normal = -normal; plane.Normal = -plane.Normal;
plane.D = -d; plane.D = -plane.D;
} }
separators.Add(plane); separators.Add(plane);
} }
} }
return separators;
} }
private enum Side private enum Side
@ -307,47 +244,37 @@ public class PotentiallyVisibleSet
// TODO: is this reference type poly going to fuck me? // TODO: is this reference type poly going to fuck me?
// TODO: Should this and Poly be in MathUtils? // TODO: Should this and Poly be in MathUtils?
private void ClipPolygonByPlane(ref Poly poly, Plane plane) private static Poly ClipPolygonByPlane(Poly poly, Plane plane)
{ {
var vertexCount = poly.Vertices.Count; var vertexCount = poly.Vertices.Length;
if (vertexCount == 0)
{
return;
}
// Firstly we want to tally up what side of the plane each point of the poly is on // 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 // This is used both to early out if nothing/everything is clipped, and to aid the clipping
// var distances = new float[vertexCount]; var distances = new float[vertexCount];
// var sides = new Side[vertexCount]; var sides = new Side[vertexCount];
// var counts = new int[3]; var counts = new int[3];
_clipDistances.Clear();
_clipSides.Clear();
_clipCounts[0] = 0;
_clipCounts[1] = 0;
_clipCounts[2] = 0;
for (var i = 0; i < vertexCount; i++) for (var i = 0; i < vertexCount; i++)
{ {
var distance = MathUtils.DistanceFromPlane(plane, poly.Vertices[i]); var distance = MathUtils.DistanceFromPlane(plane, poly.Vertices[i]);
_clipDistances.Add(distance); distances[i] = distance;
_clipSides.Add(distance switch { sides[i] = distance switch {
> Epsilon => Side.Front, > MathUtils.Epsilon => Side.Back,
<-Epsilon => Side.Back, <-MathUtils.Epsilon => Side.Front,
_ => Side.On, _ => Side.On,
}); };
_clipCounts[(int)_clipSides[i]]++; counts[(int)sides[i]]++;
} }
// Everything is within the half-space, so we don't need to clip anything // Everything is within the half-space, so we don't need to clip anything
if (_clipCounts[(int)Side.Back] == 0 && _clipCounts[(int)Side.On] != vertexCount) if (counts[(int)Side.Back] == 0)
{ {
return; return new Poly(poly.Vertices, poly.Plane);
} }
// Everything is outside the half-space, so we clip everything // Everything is outside the half-space, so we clip everything
if (_clipCounts[(int)Side.Front] == 0) if (counts[(int)Side.Back] == vertexCount)
{ {
poly.Vertices.Clear(); return new Poly([], poly.Plane);
return;
} }
var vertices = new List<Vector3>(); var vertices = new List<Vector3>();
@ -356,11 +283,11 @@ public class PotentiallyVisibleSet
var i1 = (i + 1) % vertexCount; var i1 = (i + 1) % vertexCount;
var v0 = poly.Vertices[i]; var v0 = poly.Vertices[i];
var v1 = poly.Vertices[i1]; var v1 = poly.Vertices[i1];
var side = _clipSides[i]; var side = sides[i];
var nextSide = _clipSides[i1]; var nextSide = sides[i1];
// Vertices that are inside/on the half-space don't get clipped // Vertices that are inside/on the half-space don't get clipped
if (_clipSides[i] != Side.Back) if (sides[i] != Side.Back)
{ {
vertices.Add(v0); vertices.Add(v0);
} }
@ -368,17 +295,17 @@ public class PotentiallyVisibleSet
// We only need to do any clipping if we've swapped from front-to-back or vice versa // 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 // 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 // anyway so we also don't need to do anything
if (side == Side.On || nextSide == Side.On || side == nextSide) if (side == Side.On || nextSide == Side.On || side != nextSide)
{ {
continue; continue;
} }
// This is how far along the vector v0 -> v1 the front/back crossover occurs // This is how far along the vector v0 -> v1 the front/back crossover occurs
var frac = _clipDistances[i] / (_clipDistances[i] - _clipDistances[i1]); var frac = distances[i] / (distances[i] - distances[i1]);
var splitVertex = v0 + frac * (v1 - v0); var splitVertex = v0 + frac * (v1 - v0);
vertices.Add(splitVertex); vertices.Add(splitVertex);
} }
poly.Vertices = vertices; return new Poly([..vertices], poly.Plane);
} }
} }

View File

@ -90,15 +90,7 @@ public static class MathUtils
public static float DistanceFromPlane(Plane plane, Vector3 point) public static float DistanceFromPlane(Plane plane, Vector3 point)
{ {
return (Vector3.Dot(plane.Normal, point) + plane.D) / plane.Normal.Length(); return Math.Abs(Vector3.Dot(plane.Normal, point) + plane.D) / plane.Normal.Length();
}
public static bool IsCoplanar(Plane p0, Plane p1)
{
var m = p0.D / p1.D;
var n0 = p0.Normal;
var n1 = p1.Normal * m;
return Math.Abs(n0.X - n1.X) < Epsilon && Math.Abs(n0.Y - n1.Y) < Epsilon && Math.Abs(n0.Z - n1.Z) < Epsilon;
} }
public record PlanePointMapper public record PlanePointMapper