Compare commits

..

No commits in common. "ccd1c6ef82ca13046972080a1d49618c3a5056f7" and "1c2210dbabfde3bd14fde556ec8140cfe6a7806b" have entirely different histories.

2 changed files with 123 additions and 125 deletions

View File

@ -59,22 +59,22 @@ public class LightMapper
_campaign = pathManager.GetCampaign(campaignName); _campaign = pathManager.GetCampaign(campaignName);
_misPath = _campaign.GetResourcePath(ResourceType.Mission, missionName); _misPath = _campaign.GetResourcePath(ResourceType.Mission, missionName);
_mission = Timing.TimeStage("Load Mission File", () => new DbFile(_misPath)); _mission = Timing.TimeStage("Parse DB", () => new DbFile(_misPath));
_hierarchy = Timing.TimeStage("Build Object Hierarchy", BuildHierarchy); _hierarchy = Timing.TimeStage("Build Hierarchy", BuildHierarchy);
_lights = []; _lights = [];
VerifyRequiredChunksExist(); VerifyRequiredChunksExist();
var (noObjMesh, fullMesh) = Timing.TimeStage("Build Raytracing Meshes", BuildMeshes); var (noObjMesh, fullMesh) = Timing.TimeStage("Build Meshes", BuildMeshes);
_triangleTypeMap = fullMesh.TriangleSurfaceMap; _triangleTypeMap = fullMesh.TriangleSurfaceMap;
_sceneNoObj = Timing.TimeStage("Upload Raytracing Scenes", () => _sceneNoObj = Timing.TimeStage("Build RT NoObj Scene", () =>
{ {
var rt = new Raytracer(); var rt = new Raytracer();
rt.AddMesh(new TriangleMesh(noObjMesh.Vertices, noObjMesh.Indices)); rt.AddMesh(new TriangleMesh(noObjMesh.Vertices, noObjMesh.Indices));
rt.CommitScene(); rt.CommitScene();
return rt; return rt;
}); });
_scene = Timing.TimeStage("Upload Raytracing Scenes", () => _scene = Timing.TimeStage("Build RT Scene", () =>
{ {
var rt = new Raytracer(); var rt = new Raytracer();
rt.AddMesh(new TriangleMesh(fullMesh.Vertices, fullMesh.Indices)); rt.AddMesh(new TriangleMesh(fullMesh.Vertices, fullMesh.Indices));
@ -122,14 +122,9 @@ public class LightMapper
AnimLightCutoff = lmParams.AnimLightCutoff, AnimLightCutoff = lmParams.AnimLightCutoff,
FastPvs = pvs, FastPvs = pvs,
}; };
if (settings.AnimLightCutoff > 0)
{
Log.Warning("Non-zero anim_light_cutoff ({Cutoff}). AnimLight lightmap shadow radius may not match lightgem shadow radius.", settings.AnimLightCutoff);
}
Timing.TimeStage("Gather Lights", () => BuildLightList(settings)); Timing.TimeStage("Gather Lights", () => BuildLightList(settings));
Timing.TimeStage("Set Light Visibility", () => SetCellLightIndices(settings)); Timing.TimeStage("Set Light Indices", () => SetCellLightIndices(settings));
Timing.TimeStage("Trace Scene", () => TraceScene(settings)); Timing.TimeStage("Trace Scene", () => TraceScene(settings));
Timing.TimeStage("Update AnimLight Cell Mapping", SetAnimLightCellMaps); Timing.TimeStage("Update AnimLight Cell Mapping", SetAnimLightCellMaps);
@ -149,7 +144,7 @@ public class LightMapper
var ext = Path.GetExtension(_misPath); var ext = Path.GetExtension(_misPath);
var dir = Path.GetDirectoryName(_misPath); var dir = Path.GetDirectoryName(_misPath);
var savePath = Path.Join(dir, missionName + ext); var savePath = Path.Join(dir, missionName + ext);
Timing.TimeStage("Save Mission File", () => _mission.Save(savePath)); Timing.TimeStage("Save DB", () => _mission.Save(savePath));
} }
private bool VerifyRequiredChunksExist() private bool VerifyRequiredChunksExist()
@ -245,53 +240,22 @@ public class LightMapper
switch (brush.media) switch (brush.media)
{ {
case BrList.Brush.Media.Light: case BrList.Brush.Media.Light:
ProcessBrushLight(brush, settings); ProcessBrushLight(worldRep.LightingTable, brush, settings);
break; break;
case BrList.Brush.Media.Object: case BrList.Brush.Media.Object:
ProcessObjectLight(brush, settings); ProcessObjectLight(worldRep.LightingTable, brush, settings);
break; break;
} }
} }
ValidateLightConfigurations(); CheckLightConfigurations();
worldRep.LightingTable.Reset();
foreach (var light in _lights)
{
// TODO: Set brush light index
light.LightTableIndex = worldRep.LightingTable.LightCount;
if (light.Anim)
{
var propAnimLight = _hierarchy.GetProperty<PropAnimLight>(light.ObjId, "P$AnimLight", false);
propAnimLight!.LightTableLightIndex = (ushort)light.LightTableIndex;
}
worldRep.LightingTable.AddLight(light.ToLightData(32.0f));
}
} }
// TODO: Validate in-world here? Set cell idx on lights maybe? private void CheckLightConfigurations()
private void ValidateLightConfigurations()
{ {
var infinite = 0; var infinite = 0;
for (var i = _lights.Count - 1; i > 0; i--) foreach (var light in _lights)
{ {
var light = _lights[i];
if (light.Brightness == 0)
{
if (light.ObjId != -1)
{
Log.Warning("Object {Id}: Zero brightness static light. Adjust brightness or remove un-used Light property.", light.ObjId);
}
else
{
Log.Warning("Brush at {Id}: Zero brightness static light. Adjust brightness or remove light.", light.Position);
}
_lights.RemoveAt(i);
}
if (light.Radius == float.MaxValue) if (light.Radius == float.MaxValue)
{ {
@ -326,10 +290,19 @@ public class LightMapper
} }
} }
private void ProcessBrushLight(BrList.Brush brush, Settings settings) // TODO: Check if this works (brush is a record type)
private void ProcessBrushLight(WorldRep.LightTable lightTable, BrList.Brush brush, Settings settings)
{ {
// For some reason the light table index on brush lights is 1 indexed
brush.brushInfo = (uint)lightTable.LightCount + 1;
var sz = brush.size; var sz = brush.size;
// Ignore 0 brightness lights
if (sz.X == 0)
{
return;
}
var brightness = Math.Min(sz.X, 255.0f); var brightness = Math.Min(sz.X, 255.0f);
var saturation = sz.Z * settings.Saturation; var saturation = sz.Z * settings.Saturation;
var light = new Light var light = new Light
@ -339,14 +312,16 @@ public class LightMapper
Brightness = brightness, Brightness = brightness,
Radius = float.MaxValue, Radius = float.MaxValue,
R2 = float.MaxValue, R2 = float.MaxValue,
LightTableIndex = lightTable.LightCount,
SpotlightInnerAngle = -1f, SpotlightInnerAngle = -1f,
ObjId = -1, ObjId = -1,
}; };
_lights.Add(light); _lights.Add(light);
lightTable.AddLight(light.ToLightData(32.0f));
} }
private void ProcessObjectLight(BrList.Brush brush, Settings settings) private void ProcessObjectLight(WorldRep.LightTable lightTable, BrList.Brush brush, Settings settings)
{ {
// TODO: Handle PropSpotlightAndAmbient // TODO: Handle PropSpotlightAndAmbient
var id = (int)brush.brushInfo; var id = (int)brush.brushInfo;
@ -396,6 +371,9 @@ public class LightMapper
if (propAnimLight != null) if (propAnimLight != null)
{ {
var lightIndex = lightTable.LightCount;
propAnimLight.LightTableLightIndex = (ushort)lightIndex;
var light = new Light var light = new Light
{ {
Position = propAnimLight.Offset, Position = propAnimLight.Offset,
@ -406,6 +384,7 @@ public class LightMapper
R2 = propAnimLight.Radius * propAnimLight.Radius, R2 = propAnimLight.Radius * propAnimLight.Radius,
QuadLit = propAnimLight.QuadLit, QuadLit = propAnimLight.QuadLit,
ObjId = id, ObjId = id,
LightTableIndex = propAnimLight.LightTableLightIndex,
Anim = true, Anim = true,
Dynamic = propAnimLight.Dynamic, Dynamic = propAnimLight.Dynamic,
SpotlightInnerAngle = -1f, SpotlightInnerAngle = -1f,
@ -422,9 +401,15 @@ public class LightMapper
light.ApplyTransforms(vhotLightPos, vhotLightDir, translate, rotate, scale); light.ApplyTransforms(vhotLightPos, vhotLightDir, translate, rotate, scale);
_lights.Add(light); _lights.Add(light);
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) if (propLight != null && propLight.Brightness != 0)
{ {
var light = new Light var light = new Light
{ {
@ -436,6 +421,7 @@ public class LightMapper
R2 = propLight.Radius * propLight.Radius, R2 = propLight.Radius * propLight.Radius,
QuadLit = propLight.QuadLit, QuadLit = propLight.QuadLit,
ObjId = id, ObjId = id,
LightTableIndex = lightTable.LightCount,
SpotlightInnerAngle = -1f, SpotlightInnerAngle = -1f,
}; };
@ -454,12 +440,16 @@ public class LightMapper
SpotlightInnerAngle = (float)Math.Cos(float.DegreesToRadians(propSpotAmb.InnerAngle)), SpotlightInnerAngle = (float)Math.Cos(float.DegreesToRadians(propSpotAmb.InnerAngle)),
SpotlightOuterAngle = (float)Math.Cos(float.DegreesToRadians(propSpotAmb.OuterAngle)), SpotlightOuterAngle = (float)Math.Cos(float.DegreesToRadians(propSpotAmb.OuterAngle)),
ObjId = light.ObjId, ObjId = light.ObjId,
LightTableIndex = light.LightTableIndex,
}; };
light.LightTableIndex++; // Because we're inserting the spotlight part first
spot.FixRadius(); spot.FixRadius();
spot.ApplyTransforms(vhotLightPos, vhotLightDir, translate, rotate, scale); spot.ApplyTransforms(vhotLightPos, vhotLightDir, translate, rotate, scale);
_lights.Add(spot); _lights.Add(spot);
lightTable.AddLight(spot.ToLightData(32.0f));
} }
else if (propSpotlight != null) else if (propSpotlight != null)
{ {
@ -472,93 +462,102 @@ public class LightMapper
light.ApplyTransforms(vhotLightPos, vhotLightDir, translate, rotate, scale); light.ApplyTransforms(vhotLightPos, vhotLightDir, translate, rotate, scale);
_lights.Add(light); _lights.Add(light);
lightTable.AddLight(light.ToLightData(32.0f));
} }
} }
private void SetCellLightIndices(Settings settings) private void SetCellLightIndices(Settings settings)
{ {
// TODO: Doors aren't blocking lights. Need to do some cell traversal to remove light indices :(
if (!_mission.TryGetChunk<WorldRep>("WREXT", out var worldRep)) if (!_mission.TryGetChunk<WorldRep>("WREXT", out var worldRep))
return; return;
var cellCount = worldRep.Cells.Length; var lightVisibleCells = Timing.TimeStage("Light PVS", () =>
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 cellCount = worldRep.Cells.Length;
var light = _lights[i]; var aabbs = new MathUtils.Aabb[worldRep.Cells.Length];
for (var j = 0; j < cellCount; j++) Parallel.For(0, cellCount, i => aabbs[i] = new MathUtils.Aabb(worldRep.Cells[i].Vertices));
{
if (!MathUtils.Intersects(aabbs[j], light.Position))
{
continue;
}
// Half-space contained var lightCellMap = new int[_lights.Count];
var cell = worldRep.Cells[j]; Parallel.For(0, _lights.Count, i =>
var contained = true; {
for (var k = 0; k < cell.PlaneCount; k++) lightCellMap[i] = -1;
var light = _lights[i];
for (var j = 0; j < cellCount; j++)
{ {
var plane = cell.Planes[k]; if (!MathUtils.Intersects(aabbs[j], light.Position))
if (MathUtils.DistanceFromPlane(plane, light.Position) < -MathUtils.Epsilon)
{ {
contained = false; 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; break;
} }
} }
if (contained) if (lightCellMap[i] == -1)
{ {
lightCellMap[i] = j; if (light.ObjId != -1)
break; {
Log.Warning("Object {Id}: Light is inside solid terrain.", light.ObjId);
}
else
{
Log.Warning("Brush at {Position}: Light is inside solid terrain.", light.Position);
}
} }
}
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);
}); });
} Log.Information("Mission has {c} lights", _lights.Count);
Parallel.For(0, _lights.Count, i =>
{
var cellIdx = lightCellMap[i];
if (cellIdx == -1)
{
visibleCellMap[i] = [];
return;
}
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); var pvs = new PotentiallyVisibleSet(worldRep.Cells);
visibleCellMap[i] = visibleSet; 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);
});
}
Parallel.For(0, _lights.Count, i =>
{
var cellIdx = lightCellMap[i];
if (cellIdx == -1)
{
visibleCellMap[i] = [];
return;
}
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;
});
return visibleCellMap;
}); });
// 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
@ -602,7 +601,7 @@ public class LightMapper
continue; continue;
} }
if (!visibleCellMap[j].Contains(i)) if (!lightVisibleCells[j].Contains(i))
{ {
continue; continue;
} }

View File

@ -17,7 +17,7 @@ internal static class Program
Log.Logger = config Log.Logger = config
.WriteTo.Console(theme: AnsiConsoleTheme.Sixteen, outputTemplate: outputTemplate) .WriteTo.Console(theme: AnsiConsoleTheme.Sixteen, outputTemplate: outputTemplate)
.WriteTo.File(logPath, outputTemplate: outputTemplate) .WriteTo.File(logPath, outputTemplate: outputTemplate, buffered: true)
.CreateLogger(); .CreateLogger();
} }
@ -49,12 +49,11 @@ public class LightCommand
public void Run() public void Run()
{ {
Timing.Reset(); Timing.Reset();
Timing.TimeStage("Total", () =>
{ var lightMapper = new LightMapper(InstallPath, CampaignName, MissionName);
var lightMapper = new LightMapper(InstallPath, CampaignName, MissionName); lightMapper.Light(FastPvs);
lightMapper.Light(FastPvs); lightMapper.Save(OutputName);
lightMapper.Save(OutputName);
});
Timing.LogAll(); Timing.LogAll();
} }
} }