Compare commits

..

No commits in common. "e64448f7e82dcc7a193d0012a5c5d1029bedde4f" and "477eb692c582a466c3e88a5799a87ccca9fb878f" have entirely different histories.

8 changed files with 113 additions and 263 deletions

View File

@ -16,7 +16,7 @@
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="TinyEmbree" Version="1.1.0.1" />
<PackageReference Include="TinyEmbree" Version="1.1.0" />
</ItemGroup>
<ItemGroup>

View File

@ -34,7 +34,6 @@ public class LightMapper
private DbFile _mission;
private ObjectHierarchy _hierarchy;
private Raytracer _scene;
private Raytracer _sceneNoObj;
private List<Light> _lights;
private SurfaceType[] _triangleTypeMap;
@ -57,19 +56,12 @@ public class LightMapper
VerifyRequiredChunksExist();
var (noObjMesh, fullMesh) = Timing.TimeStage("Build Meshes", BuildMeshes);
_triangleTypeMap = fullMesh.TriangleSurfaceMap;
_sceneNoObj = Timing.TimeStage("Build RT NoObj Scene", () =>
{
var rt = new Raytracer();
rt.AddMesh(new TriangleMesh(noObjMesh.Vertices, noObjMesh.Indices));
rt.CommitScene();
return rt;
});
var mesh = Timing.TimeStage("Build Mesh", BuildMesh);
_triangleTypeMap = mesh.TriangleSurfaceMap;
_scene = Timing.TimeStage("Build RT Scene", () =>
{
var rt = new Raytracer();
rt.AddMesh(new TriangleMesh(fullMesh.Vertices, fullMesh.Indices));
rt.AddMesh(new TriangleMesh(mesh.Vertices, mesh.Indices));
rt.CommitScene();
return rt;
});
@ -188,7 +180,7 @@ public class LightMapper
return new ObjectHierarchy(_mission);
}
private (Mesh, Mesh) BuildMeshes()
private Mesh BuildMesh()
{
var meshBuilder = new MeshBuilder();
@ -197,16 +189,12 @@ public class LightMapper
if (!_mission.TryGetChunk<WorldRep>("WREXT", out var worldRep) ||
!_mission.TryGetChunk<BrList>("BRLIST", out var brList))
{
return (meshBuilder.Build(), meshBuilder.Build());
return meshBuilder.Build();
}
meshBuilder.AddWorldRepPolys(worldRep);
var noObjMesh = meshBuilder.Build();
meshBuilder.AddObjectPolys(brList, _hierarchy, _campaign);
var fullMesh = meshBuilder.Build();
return (noObjMesh, fullMesh);
return meshBuilder.Build();
}
private void BuildLightList()
@ -742,7 +730,7 @@ public class LightMapper
continue;
}
if (!TraceOcclusion(_scene, light.Position, point))
if (!TraceOcclusion(light.Position, point))
{
strength += targetWeights[idx] * light.StrengthAtPoint(point, plane, settings.AnimLightCutoff);
}
@ -824,23 +812,11 @@ public class LightMapper
MathUtils.PlanePointMapper planeMapper,
Vector2[] v2ds)
{
polyCenter += planeMapper.Normal * 0.25f;
// All of the traces here are done using the no object scene. We just want to find a point in-world, we don't
// care about if an object is in the way
var tracePoints = new Vector3[offsets.Length];
for (var i = 0; i < offsets.Length; i++)
{
var offset = offsets[i];
var pos = basePosition + offset + planeMapper.Normal * MathUtils.Epsilon;
// If the target lightmap point is in view of the center
// then we can use it as-is. Using it straight fixes seams and such.
if (!TraceOcclusion(_sceneNoObj, polyCenter, pos))
{
tracePoints[i] = pos;
continue;
}
var pos = basePosition + offset;
// If we can't see our target point from the center of the poly
// then we need to clip the point to slightly inside the poly
@ -849,24 +825,12 @@ public class LightMapper
// the polygon but is partially contained in the polygon
// 2. Darkened spots from linear filtering of points outside the
// polygon which have missed
var occluded = TraceOcclusion(polyCenter + planeMapper.Normal * 0.25f, pos);
if (occluded)
{
var p2d = planeMapper.MapTo2d(pos);
p2d = MathUtils.ClipPointToPoly2d(p2d, v2ds);
pos = planeMapper.MapTo3d(p2d);
pos += planeMapper.Normal * MathUtils.Epsilon;
// If the clipping fails, just say screw it and cast :(
if (TraceOcclusion(_sceneNoObj, polyCenter, pos))
{
var hitResult = _sceneNoObj.Trace(new Ray
{
Origin = polyCenter,
Direction = Vector3.Normalize(pos - polyCenter),
});
if (hitResult)
{
pos = hitResult.Position;
}
}
tracePoints[i] = pos;
@ -875,7 +839,7 @@ public class LightMapper
return tracePoints;
}
private static bool TraceOcclusion(Raytracer scene, Vector3 origin, Vector3 target, float epsilon = MathUtils.Epsilon)
private bool TraceOcclusion(Vector3 origin, Vector3 target)
{
var direction = target - origin;
var ray = new Ray
@ -885,7 +849,7 @@ public class LightMapper
};
// Epsilon is used here to avoid occlusion when origin lies exactly on a poly
return scene.IsOccluded(new ShadowRay(ray, direction.Length() - epsilon));
return _scene.IsOccluded(new ShadowRay(ray, direction.Length() - MathUtils.Epsilon));
}
// TODO: direction should already be normalised here

View File

@ -11,7 +11,7 @@ public enum SurfaceType
{
Solid,
Sky,
Object,
Water,
Air,
}
@ -144,7 +144,7 @@ public class MeshBuilder
polyVertices.Add(vertex);
}
AddPolygon(polyVertices, SurfaceType.Object);
AddPolygon(polyVertices, SurfaceType.Solid);
}
}
}
@ -155,13 +155,12 @@ public class MeshBuilder
var indexOffset = _vertices.Count;
// Polygons are n-sided, but fortunately they're convex so we can just do a fan triangulation
// Embree triangle winding order is reverse of LGS winding order, so we go (0, i+1, i) instead of (0, i+1, i)
_vertices.AddRange(vertices);
for (var i = 1; i < vertexCount - 1; i++)
{
_indices.Add(indexOffset);
_indices.Add(indexOffset + i + 1);
_indices.Add(indexOffset + i);
_indices.Add(indexOffset + i + 1);
_primSurfaceMap.Add(surfaceType);
_triangleCount++;
}

View File

@ -5,18 +5,10 @@ namespace KeepersCompound.Lightmapper;
public class PotentiallyVisibleSet
{
private struct Node(List<int> edgeIndices)
private struct Edge
{
public bool VisibilityComputed = false;
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 int Destination;
public Poly Poly;
public override string ToString()
{
@ -52,8 +44,9 @@ public class PotentiallyVisibleSet
}
}
private readonly Node[] _graph;
private readonly List<int>[] _portalGraph;
private readonly List<Edge> _edges;
private readonly Dictionary<int, HashSet<int>> _visibilitySet;
private const float Epsilon = 0.1f;
@ -75,19 +68,19 @@ public class PotentiallyVisibleSet
public PotentiallyVisibleSet(WorldRep.Cell[] cells)
{
_graph = new Node[cells.Length];
_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];
var edgeIndices = new List<int>(cell.PortalPolyCount);
// 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)
{
_graph[i] = new Node(edgeIndices);
continue;
}
@ -103,6 +96,8 @@ public class PotentiallyVisibleSet
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 List<Vector3>(poly.VertexCount);
@ -111,96 +106,33 @@ public class PotentiallyVisibleSet
vs.Add(cell.Vertices[cell.Indices[indicesOffset + vIdx]]);
}
var edge = new Edge(poly.Destination, new Poly(vs, cell.Planes[poly.PlaneId]));
edgeIndices.Add(_edges.Count);
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;
}
_graph[i] = new Node(edgeIndices);
}
Parallel.ForEach(_edges, ComputeEdgeMightSee);
}
public int[] GetVisible(int cellIdx)
{
// TODO: Handle out of range indices
var node = _graph[cellIdx];
if (node.VisibilityComputed)
if (_visibilitySet.TryGetValue(cellIdx, out var value))
{
return [..node.VisibleNodes];
return [..value];
}
var visibleCells = ComputeVisibility(cellIdx);
node.VisibilityComputed = true;
node.VisibleNodes = visibleCells;
_graph[cellIdx] = node;
_visibilitySet.Add(cellIdx, visibleCells);
return [..visibleCells];
}
private void ComputeEdgeMightSee(Edge source)
{
var sourcePlane = source.Poly.Plane;
var unexploredCells = new Stack<int>();
unexploredCells.Push(source.Destination);
while (unexploredCells.Count > 0)
{
var cellIdx = unexploredCells.Pop();
if (!source.MightSee.Add(cellIdx))
{
continue; // 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)
{
unexploredCells.Push(target.Destination);
}
}
}
}
private HashSet<int> ComputeVisibility(int cellIdx)
{
if (cellIdx >= _graph.Length)
if (cellIdx >= _portalGraph.Length)
{
return [];
}
@ -209,102 +141,86 @@ public class PotentiallyVisibleSet
var visible = new HashSet<int>();
visible.Add(cellIdx);
foreach (var edgeIdx in _graph[cellIdx].EdgeIndices)
// Additionally a cell can always see it's direct neighbours (obviously)
foreach (var edgeIndex in _portalGraph[cellIdx])
{
var edge = _edges[edgeIdx];
foreach (var mightSee in edge.MightSee)
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])
{
visible.Add(mightSee);
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;
// 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(
// HashSet<int> visible,
// Poly sourcePoly,
// Poly previousPoly,
// int previousCellIdx,
// int currentCellIdx,
// int depth)
// {
// // TODO: Might need to lose this
// if (depth > 1024)
// {
// return;
// }
//
// visible.Add(currentCellIdx);
//
// // Only one edge out of the cell means we'd be going back on ourselves
// if (_portalGraph[currentCellIdx].Count <= 1)
// {
// return;
// }
//
// // TODO: If all neighbours are already in `visible` skip exploring?
//
// var separators = new List<Plane>();
// GetSeparatingPlanes(separators, sourcePoly, previousPoly, false);
// GetSeparatingPlanes(separators, previousPoly, sourcePoly, true);
//
// // The case for this occuring is... interesting ( idk )
// if (separators.Count == 0)
// {
// return;
// }
//
// // Clip all new polys and recurse
// foreach (var edgeIndex in _portalGraph[currentCellIdx])
// {
// var edge = _edges[edgeIndex];
// if (edge.Destination == previousCellIdx || previousPoly.IsCoplanar(edge.Poly) || sourcePoly.IsCoplanar(edge.Poly))
// {
// continue;
// }
//
// var poly = new Poly(edge.Poly);
// foreach (var separator in separators)
// {
// ClipPolygonByPlane(ref poly, separator);
// }
//
// if (poly.Vertices.Count == 0)
// {
// continue;
// }
//
// ExplorePortalRecursive(visible, sourcePoly, poly, currentCellIdx, edge.Destination, depth + 1);
// }
// }
private void ExplorePortalRecursive(
HashSet<int> visible,
Poly sourcePoly,
Poly previousPoly,
int previousCellIdx,
int currentCellIdx,
int depth)
{
// TODO: Might need to lose this
if (depth > 1024)
{
return;
}
visible.Add(currentCellIdx);
// Only one edge out of the cell means we'd be going back on ourselves
if (_portalGraph[currentCellIdx].Count <= 1)
{
return;
}
// TODO: If all neighbours are already in `visible` skip exploring?
var separators = new List<Plane>();
GetSeparatingPlanes(separators, sourcePoly, previousPoly, false);
GetSeparatingPlanes(separators, previousPoly, sourcePoly, true);
// The case for this occuring is... interesting ( idk )
if (separators.Count == 0)
{
return;
}
// Clip all new polys and recurse
foreach (var edgeIndex in _portalGraph[currentCellIdx])
{
var edge = _edges[edgeIndex];
if (edge.Destination == previousCellIdx || previousPoly.IsCoplanar(edge.Poly) || sourcePoly.IsCoplanar(edge.Poly))
{
continue;
}
var poly = new Poly(edge.Poly);
foreach (var separator in separators)
{
ClipPolygonByPlane(ref poly, separator);
}
if (poly.Vertices.Count == 0)
{
continue;
}
ExplorePortalRecursive(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 void GetSeparatingPlanes(List<Plane> separators, Poly p0, Poly p1, bool flip)

View File

@ -1,5 +0,0 @@
<configuration>
<packageSources>
<add key="KeepersCompound.Lightmapper Local" value="./Packages" />
</packageSources>
</configuration>

View File

@ -1,5 +0,0 @@
{
"version": 2,
"contentHash": "Uhc3bpe44l6oPT7XPAWg8MBJRZc4iZP8nZpaAM3+35tvwLxjspcuBIMzFatr124Zfm71HQxDf+K1o5Tic/FQYw==",
"source": null
}

View File

@ -1 +0,0 @@
Uhc3bpe44l6oPT7XPAWg8MBJRZc4iZP8nZpaAM3+35tvwLxjspcuBIMzFatr124Zfm71HQxDf+K1o5Tic/FQYw==

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>TinyEmbree</id>
<version>1.1.0.1</version>
<title>TinyEmbree</title>
<authors>Pascal Grittmann</authors>
<license type="file">LICENSE</license>
<licenseUrl>https://aka.ms/deprecateLicenseUrl</licenseUrl>
<description>A very simple C# wrapper around the Embree ray tracing kernels.</description>
<copyright>(c) Pascal Grittmann</copyright>
<tags>ray tracing Embree</tags>
<repository type="git" url="https://github.com/pgrit/TinyEmbree" commit="66e46513777d3046b684f4d6fe796e9f3558b6b0" />
<dependencies>
<group targetFramework="net9.0" />
</dependencies>
</metadata>
</package>