Compare commits

..

No commits in common. "d4761ca682f1e1f748bf161b85f271df136747a0" and "fafae43c5664a24ab56fd740143bc11ef7829160" have entirely different histories.

5 changed files with 341 additions and 282 deletions

View File

@ -56,24 +56,21 @@ public class Light
SpotlightDir = Vector3.Normalize(Vector3.Transform(vhotLightDir, scale * rotate));
}
public float StrengthAtPoint(Vector3 point, Plane plane, uint lightCutoff, float attenuation)
public float StrengthAtPoint(Vector3 point, Plane plane, uint lightCutoff)
{
// Calculate light strength at a given point. As far as I can tell
// this is exact to Dark (I'm a genius??).
// this is exact to Dark (I'm a genius??). It's just an inverse distance
// falloff with diffuse angle, except we have to scale the length.
var dir = Position - point;
var angle = Vector3.Dot(Vector3.Normalize(dir), plane.Normal);
var len = dir.Length();
dir = Vector3.Normalize(dir);
// Base strength is a scaled inverse falloff
var strength = 4.0f / MathF.Pow(len, attenuation);
// Diffuse light angle
strength *= 1.0f + MathF.Pow(Vector3.Dot(dir, plane.Normal), attenuation);
var slen = len / 4.0f;
var strength = (angle + 1.0f) / slen;
// Inner radius starts a linear falloff to 0 at the radius
if (InnerRadius != 0 && len > InnerRadius)
{
strength *= MathF.Pow((Radius - len) / (Radius - InnerRadius), attenuation);
strength *= (Radius - len) / (Radius - InnerRadius);
}
// Anim lights have a (configurable) minimum light cutoff. This is checked before
@ -105,7 +102,6 @@ public class Light
}
else
{
// Interestingly DromEd doesn't apply attenuation here
spotlightMultiplier = (spotAngle - outer) / (inner - outer);
}

View File

@ -22,19 +22,12 @@ public class LightMapper
{
public Vector3[] AmbientLight;
public bool Hdr;
public float Attenuation;
public float Saturation;
public SoftnessMode MultiSampling;
public float MultiSamplingCenterWeight;
public bool LightmappedWater;
public SunSettings Sunlight;
public uint AnimLightCutoff;
public bool FastPvs;
public override string ToString()
{
return $"Ambient Levels: {AmbientLight}, Hdr: {Hdr}, Attenuation: {Attenuation}, Saturation: {Saturation}";
}
public bool UsePvs;
}
private ResourcePathManager.CampaignResources _campaign;
@ -93,12 +86,12 @@ public class LightMapper
return;
}
var sunlightSettings = new SunSettings
var sunlightSettings = new SunSettings()
{
Enabled = rendParams.useSunlight,
QuadLit = rendParams.sunlightMode is RendParams.SunlightMode.QuadUnshadowed or RendParams.SunlightMode.QuadObjcastShadows,
Direction = Vector3.Normalize(rendParams.sunlightDirection),
Color = Utils.HsbToRgb(rendParams.sunlightHue, rendParams.sunlightSaturation * lmParams.Saturation, rendParams.sunlightBrightness),
Color = Utils.HsbToRgb(rendParams.sunlightHue, rendParams.sunlightSaturation, rendParams.sunlightBrightness),
};
var ambientLight = rendParams.ambientLightZones.ToList();
@ -113,17 +106,15 @@ public class LightMapper
{
Hdr = worldRep.DataHeader.LightmapFormat == 2,
AmbientLight = [..ambientLight],
Attenuation = lmParams.Attenuation,
Saturation = lmParams.Saturation,
MultiSampling = lmParams.ShadowSoftness,
MultiSamplingCenterWeight = lmParams.CenterWeight,
LightmappedWater = lmParams.LightmappedWater,
Sunlight = sunlightSettings,
AnimLightCutoff = lmParams.AnimLightCutoff,
FastPvs = pvs,
UsePvs = pvs,
};
Timing.TimeStage("Gather Lights", () => BuildLightList(settings));
Timing.TimeStage("Gather Lights", BuildLightList);
Timing.TimeStage("Set Light Indices", () => SetCellLightIndices(settings));
Timing.TimeStage("Trace Scene", () => TraceScene(settings));
Timing.TimeStage("Update AnimLight Cell Mapping", SetAnimLightCellMaps);
@ -220,7 +211,7 @@ public class LightMapper
return (noObjMesh, fullMesh);
}
private void BuildLightList(Settings settings)
private void BuildLightList()
{
_lights.Clear();
@ -240,50 +231,33 @@ public class LightMapper
switch (brush.media)
{
case BrList.Brush.Media.Light:
ProcessBrushLight(worldRep.LightingTable, brush, settings);
ProcessBrushLight(worldRep.LightingTable, brush);
break;
case BrList.Brush.Media.Object:
ProcessObjectLight(worldRep.LightingTable, brush, settings);
ProcessObjectLight(worldRep.LightingTable, brush);
break;
}
}
CheckLightConfigurations();
}
private void CheckLightConfigurations()
{
var infinite = 0;
foreach (var light in _lights)
{
if (light.Radius == float.MaxValue)
if (light.Radius != float.MaxValue)
{
continue;
}
if (light.ObjId != -1)
{
Log.Warning("Object {Id}: Infinite light radius.", light.ObjId);
Log.Warning("Infinite light from object {Id}", light.ObjId);
}
else
{
Log.Warning("Brush at {Position}: Infinite light radius.", light.Position);
Log.Warning("Infinite light from brush near {Position}", light.Position);
}
infinite++;
}
// TODO: Extract magic number
if (light.InnerRadius > 0 && light.Radius - light.InnerRadius > 4)
{
if (light.ObjId != -1)
{
Log.Warning("Object {Id}: High radius to inner-radius differential ({D}). Lightmap may not accurately represent lightgem.", light.ObjId, light.Radius - light.InnerRadius);
}
else
{
Log.Warning("Brush at {Position}: High radius to inner-radius differential ({D}). Lightmap may not accurately represent lightgem.", light.Position, light.Radius - light.InnerRadius);
}
}
}
if (infinite > 0)
{
Log.Warning("Mission contains {Count} infinite lights", infinite);
@ -291,7 +265,7 @@ public class LightMapper
}
// TODO: Check if this works (brush is a record type)
private void ProcessBrushLight(WorldRep.LightTable lightTable, BrList.Brush brush, Settings settings)
private void ProcessBrushLight(WorldRep.LightTable lightTable, BrList.Brush brush)
{
// For some reason the light table index on brush lights is 1 indexed
brush.brushInfo = (uint)lightTable.LightCount + 1;
@ -304,11 +278,10 @@ public class LightMapper
}
var brightness = Math.Min(sz.X, 255.0f);
var saturation = sz.Z * settings.Saturation;
var light = new Light
{
Position = brush.position,
Color = Utils.HsbToRgb(sz.Y, saturation, brightness),
Color = Utils.HsbToRgb(sz.Y, sz.Z, brightness),
Brightness = brightness,
Radius = float.MaxValue,
R2 = float.MaxValue,
@ -321,7 +294,7 @@ public class LightMapper
lightTable.AddLight(light.ToLightData(32.0f));
}
private void ProcessObjectLight(WorldRep.LightTable lightTable, BrList.Brush brush, Settings settings)
private void ProcessObjectLight(WorldRep.LightTable lightTable, BrList.Brush brush)
{
// TODO: Handle PropSpotlightAndAmbient
var id = (int)brush.brushInfo;
@ -335,8 +308,6 @@ public class LightMapper
var propJointPos = _hierarchy.GetProperty<PropJointPos>(id, "P$JointPos");
propLightColor ??= new PropLightColor { Hue = 0, Saturation = 0 };
propLightColor.Saturation *= settings.Saturation;
var joints = propJointPos?.Positions ?? [0, 0, 0, 0, 0, 0];
// Transform data
@ -404,11 +375,6 @@ public class LightMapper
lightTable.AddLight(light.ToLightData(32.0f), propAnimLight.Dynamic);
}
if (propLight != null && propLight.Brightness == 0)
{
Log.Warning("Concrete object {Id} has Light property with 0 brightness. Adjust brightness or remove property.", id);
}
if (propLight != null && propLight.Brightness != 0)
{
var light = new Light
@ -473,7 +439,11 @@ public class LightMapper
if (!_mission.TryGetChunk<WorldRep>("WREXT", out var worldRep))
return;
var lightVisibleCells = Timing.TimeStage("Light PVS", () =>
var lightVisibleCells = new List<int[]>();
if (settings.UsePvs)
{
lightVisibleCells = Timing.TimeStage("Light PVS", () =>
{
var cellCount = worldRep.Cells.Length;
var aabbs = new MathUtils.Aabb[worldRep.Cells.Length];
@ -510,54 +480,31 @@ public class LightMapper
break;
}
}
if (lightCellMap[i] == -1)
{
if (light.ObjId != -1)
{
Log.Warning("Object {Id}: Light is inside solid terrain.", light.ObjId);
}
else
{
Log.Warning("Brush at {Position}: Light is inside solid terrain.", light.Position);
}
}
});
Log.Information("Mission has {c} lights", _lights.Count);
var pvs = new PotentiallyVisibleSet(worldRep.Cells);
var visibleCellMap = new HashSet<int>[_lights.Count];
// Exact visibility doesn't use MightSee (yet?) so we only bother computing it if we're doing fast vis
if (settings.FastPvs)
{
Parallel.ForEach(lightCellMap, i =>
{
if (i != -1) pvs.ComputeCellMightSee(i);
if (i != -1) pvs.ComputeVisibility(i);
});
}
Parallel.For(0, _lights.Count, i =>
var visibleCellMap = new List<int[]>(_lights.Count);
for (var i = 0; i < _lights.Count; i++)
{
var cellIdx = lightCellMap[i];
if (cellIdx == -1)
{
visibleCellMap[i] = [];
return;
visibleCellMap.Add([]);
continue;
}
var visibleSet = settings.FastPvs switch {
true => pvs.ComputeVisibilityFast(lightCellMap[i]),
false => pvs.ComputeVisibilityExact(_lights[i].Position, lightCellMap[i], _lights[i].Radius)
};
// Log.Information("Light {i} sees {c} cells", i, visibleSet.Count);
visibleCellMap[i] = visibleSet;
});
var visibleSet = pvs.GetVisible(lightCellMap[i]);
visibleCellMap.Add(visibleSet);
}
return visibleCellMap;
});
}
// TODO: Move this functionality to the LGS library
// We set up light indices in separately from lighting because the actual
@ -601,7 +548,7 @@ public class LightMapper
continue;
}
if (!lightVisibleCells[j].Contains(i))
if (settings.UsePvs && !lightVisibleCells[j].Contains(i))
{
continue;
}
@ -635,14 +582,7 @@ public class LightMapper
if (overLit > 0)
{
if (settings.FastPvs)
{
Log.Warning("{Count}/{CellCount} cells are overlit. Overlit cells can cause Object/Light Gem lighting issues. Try running without the --fast-pvs flag.", overLit, worldRep.Cells.Length);
}
else
{
Log.Warning("{Count}/{CellCount} cells are overlit. Overlit cells can cause Object/Light Gem lighting issues.", overLit, worldRep.Cells.Length);
}
Log.Warning("{Count}/{CellCount} cells are overlit. Overlit cells can cause Object/Light Gem lighting issues. Try running with the --pvs flag.", overLit, worldRep.Cells.Length);
}
Log.Information("Max cell lights found ({Count}/96)", maxLights);
@ -834,7 +774,7 @@ public class LightMapper
if (!TraceOcclusion(_scene, light.Position, point))
{
strength += targetWeights[idx] * light.StrengthAtPoint(point, plane, settings.AnimLightCutoff, settings.Attenuation);
strength += targetWeights[idx] * light.StrengthAtPoint(point, plane, settings.AnimLightCutoff);
}
}

View File

@ -35,12 +35,38 @@ public class MeshBuilder
var polyVertices = new List<Vector3>();
foreach (var cell in worldRep.Cells)
{
// We only care about polys representing solid terrain. We can't use RenderPolyCount because that includes
// water surfaces.
var solidPolys = cell.PolyCount - cell.PortalPolyCount;
var numPolys = cell.PolyCount;
var numRenderPolys = cell.RenderPolyCount;
var numPortalPolys = cell.PortalPolyCount;
var solidPolys = numPolys - numPortalPolys;
var cellIdxOffset = 0;
for (var polyIdx = 0; polyIdx < solidPolys; polyIdx++)
for (var polyIdx = 0; polyIdx < numPolys; polyIdx++)
{
// There's 2 types of poly that we need to include in the mesh:
// - Terrain
// - Door vision blockers
//
// Door vision blockers are the interesting one. They're not RenderPolys at all, just flagged Polys.
SurfaceType primType;
if (polyIdx < solidPolys)
{
primType = cell.RenderPolys[polyIdx].TextureId == 249 ? SurfaceType.Sky : SurfaceType.Solid;
}
else if (polyIdx < numRenderPolys)
{
// we no longer want water polys :)
continue;
}
else if ((cell.Flags & 8) != 0)
{
primType = SurfaceType.Solid;
}
else
{
continue;
}
var poly = cell.Polys[polyIdx];
polyVertices.Clear();
polyVertices.EnsureCapacity(poly.VertexCount);
@ -49,7 +75,6 @@ public class MeshBuilder
polyVertices.Add(cell.Vertices[cell.Indices[cellIdxOffset + i]]);
}
var primType = cell.RenderPolys[polyIdx].TextureId == 249 ? SurfaceType.Sky : SurfaceType.Solid;
AddPolygon(polyVertices, primType);
cellIdxOffset += poly.VertexCount;
}

View File

@ -9,6 +9,7 @@ public class PotentiallyVisibleSet
{
private readonly struct Node(List<int> edgeIndices)
{
public readonly HashSet<int> VisibleNodes = [];
public readonly List<int> EdgeIndices = edgeIndices;
}
@ -74,9 +75,15 @@ public class PotentiallyVisibleSet
}
private readonly Node[] _graph;
private readonly bool[] _computedMap;
private readonly List<Edge> _edges;
private const float Epsilon = MathUtils.Epsilon;
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:
// - This is a conservative algorithm based on Matt's Ramblings Quake PVS video
@ -92,10 +99,12 @@ public class PotentiallyVisibleSet
public PotentiallyVisibleSet(WorldRep.Cell[] cells)
{
_graph = new Node[cells.Length];
_computedMap = new bool[cells.Length];
var portalCount = 0;
for (var i = 0; i < cells.Length; i++)
{
_computedMap[i] = false;
portalCount += cells[i].PortalPolyCount;
}
@ -147,119 +156,18 @@ public class PotentiallyVisibleSet
// Parallel.ForEach(_edges, ComputeEdgeMightSee);
}
public HashSet<int> ComputeVisibilityFast(int cellIdx)
public int[] GetVisible(int cellIdx)
{
if (cellIdx >= _graph.Length)
// TODO: Handle out of range indices
var node = _graph[cellIdx];
if (_computedMap[cellIdx])
{
return [];
return [..node.VisibleNodes];
}
var visibleCells = new HashSet<int>();
foreach (var edgeIdx in _graph[cellIdx].EdgeIndices)
{
var edge = _edges[edgeIdx];
for (var i = 0; i < edge.MightSee.Length; i++)
{
if (edge.MightSee[i])
{
visibleCells.Add(i);
}
}
}
return visibleCells;
}
public HashSet<int> ComputeVisibilityExact(Vector3 pos, int cellIdx, float maxRange)
{
if (cellIdx >= _graph.Length)
{
return [];
}
var visibleCells = new HashSet<int> { cellIdx };
var visited = new Stack<int>();
visited.Push(cellIdx);
foreach (var edgeIdx in _graph[cellIdx].EdgeIndices)
{
var edge = _edges[edgeIdx];
ComputeVisibilityExactRecursive(pos, maxRange, visibleCells, visited, edge.Destination, edge.Poly);
}
return visibleCells;
}
private void ComputeVisibilityExactRecursive(
Vector3 lightPos,
float maxRange,
HashSet<int> visibleCells,
Stack<int> visited,
int currentCellIdx,
Poly passPoly)
{
visited.Push(currentCellIdx);
visibleCells.Add(currentCellIdx);
var clipPlanes = new List<Plane>(passPoly.Vertices.Count);
clipPlanes.Clear();
for (var i = 0; i < passPoly.Vertices.Count; i++)
{
var v0 = passPoly.Vertices[i];
var v1 = passPoly.Vertices[(i + 1) % passPoly.Vertices.Count];
var normal = Vector3.Cross(v0 - lightPos, v1 - lightPos);
if (normal.LengthSquared() < Epsilon)
{
continue;
}
normal = Vector3.Normalize(normal);
var d = -Vector3.Dot(v1, normal);
var plane = new Plane(normal, d);
clipPlanes.Add(plane);
}
foreach (var targetEdgeIdx in _graph[currentCellIdx].EdgeIndices)
{
// This only checks is there is a point on the plane in range.
// Could probably use poly center + radius to get an even better early out.
var targetEdge = _edges[targetEdgeIdx];
if (visited.Contains(targetEdge.Destination) ||
passPoly.IsCoplanar(targetEdge.Poly) ||
Math.Abs(MathUtils.DistanceFromNormalizedPlane(targetEdge.Poly.Plane, lightPos)) > maxRange)
{
continue;
}
var poly = new Poly(targetEdge.Poly);
foreach (var clipPlane in clipPlanes)
{
ClipPolygonByPlane(ref poly, clipPlane);
}
if (poly.Vertices.Count == 0)
{
continue;
}
ComputeVisibilityExactRecursive(lightPos, maxRange, visibleCells, visited, targetEdge.Destination, poly);
}
visited.Pop();
}
public void ComputeCellMightSee(int cellIdx)
{
if (cellIdx >= _graph.Length)
{
return;
}
foreach (var edgeIdx in _graph[cellIdx].EdgeIndices)
{
ComputeEdgeMightSee(_edges[edgeIdx]);
}
ComputeVisibility(cellIdx);
_graph[cellIdx] = node;
return [.._graph[cellIdx].VisibleNodes];
}
private void ComputeEdgeMightSee(Edge source)
@ -331,6 +239,192 @@ public class PotentiallyVisibleSet
}
}
public void ComputeVisibility(int cellIdx)
{
if (cellIdx >= _graph.Length)
{
return;
}
// A cell can always see itself, so we'll add that now
_graph[cellIdx].VisibleNodes.Add(cellIdx);
foreach (var edgeIdx in _graph[cellIdx].EdgeIndices)
{
var edge = _edges[edgeIdx];
ComputeEdgeMightSee(edge);
for (var i = 0; i < edge.MightSee.Length; i++)
{
if (edge.MightSee[i])
{
_graph[cellIdx].VisibleNodes.Add(i);
}
}
}
_computedMap[cellIdx] = true;
// 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);
// }
// }
// 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)
{
for (var i = 0; i < p0.Vertices.Count; 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.Count];
for (var j = 0; j < p1.Vertices.Count; j++)
{
var v2 = p1.Vertices[j];
var normal = Vector3.Cross(v1 - v0, v2 - v0);
if (normal.LengthSquared() < Epsilon)
{
// 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);
if (MathUtils.IsCoplanar(plane, flip ? p0.Plane : p1.Plane))
{
continue;
}
// All points should be in front of the plane (except for the point used to create it)
var invalid = false;
var count = 0;
for (var k = 0; k < p1.Vertices.Count; k++)
{
if (k == j)
{
continue;
}
var dist = MathUtils.DistanceFromPlane(plane, p1.Vertices[k]);
if (dist > Epsilon)
{
count++;
}
else if (dist < -Epsilon)
{
invalid = true;
break;
}
}
if (invalid || count == 0)
{
continue;
}
if (flip)
{
plane.Normal = -normal;
plane.D = -d;
}
separators.Add(plane);
}
}
}
private enum Side
{
Front,
@ -340,7 +434,7 @@ public class PotentiallyVisibleSet
// TODO: is this reference type poly going to fuck me?
// TODO: Should this and Poly be in MathUtils?
private static void ClipPolygonByPlane(ref Poly poly, Plane plane)
private void ClipPolygonByPlane(ref Poly poly, Plane plane)
{
var vertexCount = poly.Vertices.Count;
if (vertexCount == 0)
@ -350,30 +444,34 @@ public class PotentiallyVisibleSet
// 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[] {0, 0, 0};
// var distances = new float[vertexCount];
// var sides = new Side[vertexCount];
// 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++)
{
var distance = MathUtils.DistanceFromPlane(plane, poly.Vertices[i]);
distances[i] = distance;
sides[i] = distance switch
{
_clipDistances.Add(distance);
_clipSides.Add(distance switch {
> Epsilon => Side.Front,
< -Epsilon => Side.Back,
<-Epsilon => Side.Back,
_ => Side.On,
};
counts[(int)sides[i]]++;
});
_clipCounts[(int)_clipSides[i]]++;
}
// Everything is within the half-space, so we don't need to clip anything
if (counts[(int)Side.Back] == 0 && counts[(int)Side.On] != vertexCount)
if (_clipCounts[(int)Side.Back] == 0 && _clipCounts[(int)Side.On] != vertexCount)
{
return;
}
// Everything is outside the half-space, so we clip everything
if (counts[(int)Side.Front] == 0)
if (_clipCounts[(int)Side.Front] == 0)
{
poly.Vertices.Clear();
return;
@ -385,11 +483,11 @@ public class PotentiallyVisibleSet
var i1 = (i + 1) % vertexCount;
var v0 = poly.Vertices[i];
var v1 = poly.Vertices[i1];
var side = sides[i];
var nextSide = sides[i1];
var side = _clipSides[i];
var nextSide = _clipSides[i1];
// Vertices that are inside/on the half-space don't get clipped
if (sides[i] != Side.Back)
if (_clipSides[i] != Side.Back)
{
vertices.Add(v0);
}
@ -403,7 +501,7 @@ public class PotentiallyVisibleSet
}
// This is how far along the vector v0 -> v1 the front/back crossover occurs
var frac = distances[i] / (distances[i] - distances[i1]);
var frac = _clipDistances[i] / (_clipDistances[i] - _clipDistances[i1]);
var splitVertex = v0 + frac * (v1 - v0);
vertices.Add(splitVertex);
}

View File

@ -40,8 +40,8 @@ public class LightCommand
[CliArgument(Description = "The name of the mission file including extension.")]
public required string MissionName { get; set; }
[CliOption(Description = "Use a fast PVS calculation with looser cell light indices.")]
public bool FastPvs { get; set; } = false;
[CliOption(Description = "Use a coarse PVS for tighter cell light indices.")]
public bool Pvs { get; set; } = false;
[CliOption(Description = "Name of output file excluding extension.")]
public string OutputName { get; set; } = "kc_lit";
@ -51,7 +51,7 @@ public class LightCommand
Timing.Reset();
var lightMapper = new LightMapper(InstallPath, CampaignName, MissionName);
lightMapper.Light(FastPvs);
lightMapper.Light(Pvs);
lightMapper.Save(OutputName);
Timing.LogAll();