Compare commits
No commits in common. "e64448f7e82dcc7a193d0012a5c5d1029bedde4f" and "477eb692c582a466c3e88a5799a87ccca9fb878f" have entirely different histories.
e64448f7e8
...
477eb692c5
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<configuration>
|
||||
<packageSources>
|
||||
<add key="KeepersCompound.Lightmapper Local" value="./Packages" />
|
||||
</packageSources>
|
||||
</configuration>
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"version": 2,
|
||||
"contentHash": "Uhc3bpe44l6oPT7XPAWg8MBJRZc4iZP8nZpaAM3+35tvwLxjspcuBIMzFatr124Zfm71HQxDf+K1o5Tic/FQYw==",
|
||||
"source": null
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
Uhc3bpe44l6oPT7XPAWg8MBJRZc4iZP8nZpaAM3+35tvwLxjspcuBIMzFatr124Zfm71HQxDf+K1o5Tic/FQYw==
|
|
@ -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>
|
Loading…
Reference in New Issue