Compare commits

..

No commits in common. "c162a5028d3ef0eb2a8cb27106d63032333473f5" and "940c78e1cfc76124d11edfe8fb0639db013dacd4" have entirely different histories.

4 changed files with 107 additions and 92 deletions

View File

@ -152,7 +152,6 @@ public class WorldRep : IChunk
public class Lightmap public class Lightmap
{ {
private readonly bool[] _litLayers;
public List<byte[]> Pixels { get; set; } public List<byte[]> Pixels { get; set; }
public int Layers; public int Layers;
@ -164,12 +163,10 @@ public class WorldRep : IChunk
{ {
var layers = 1 + BitOperations.PopCount(bitmask); var layers = 1 + BitOperations.PopCount(bitmask);
var length = bytesPerPixel * width * height; var length = bytesPerPixel * width * height;
_litLayers = new bool[33];
Pixels = new List<byte[]>(); Pixels = new List<byte[]>();
for (var i = 0; i < layers; i++) for (var i = 0; i < layers; i++)
{ {
Pixels.Add(reader.ReadBytes(length)); Pixels.Add(reader.ReadBytes(length));
_litLayers[i] = true;
} }
Layers = layers; Layers = layers;
Width = width; Width = width;
@ -249,8 +246,6 @@ public class WorldRep : IChunk
pLayer[idx + 1] = (byte)Math.Clamp(pLayer[idx + 1] + g, 0, 255); pLayer[idx + 1] = (byte)Math.Clamp(pLayer[idx + 1] + g, 0, 255);
pLayer[idx + 2] = (byte)Math.Clamp(pLayer[idx + 2] + b, 0, 255); pLayer[idx + 2] = (byte)Math.Clamp(pLayer[idx + 2] + b, 0, 255);
pLayer[idx + 3] = 255; pLayer[idx + 3] = 255;
_litLayers[layer] = true;
} }
public void AddLight(int layer, int x, int y, Vector3 color, float strength, bool hdr) public void AddLight(int layer, int x, int y, Vector3 color, float strength, bool hdr)
@ -265,7 +260,12 @@ public class WorldRep : IChunk
// the hue/saturation of coloured lights. Got to make sure we // the hue/saturation of coloured lights. Got to make sure we
// maintain the colour ratios. // maintain the colour ratios.
var c = color * strength; var c = color * strength;
var ratio = Math.Max(Math.Max(Math.Max(0.0f, c.X), c.Y), c.Z) / 255.0f; var ratio = 0.0f;
foreach (var e in new float[] { c.X, c.Y, c.Z })
{
ratio = Math.Max(ratio, e / 255.0f);
}
if (ratio > 1.0f) if (ratio > 1.0f)
{ {
c /= ratio; c /= ratio;
@ -276,18 +276,9 @@ public class WorldRep : IChunk
public void Reset(Vector3 ambientLight, bool hdr) public void Reset(Vector3 ambientLight, bool hdr)
{ {
Layers = 33; Layers = 0;
Pixels.Clear(); Pixels.Clear();
var bytesPerLayer = Width * Height * Bpp; AddLayer();
for (var i = 0; i < Layers; i++)
{
Pixels.Add(new byte[bytesPerLayer]);
for (var j = 0; j < bytesPerLayer; j++)
{
Pixels[i][j] = 0;
}
_litLayers[i] = false;
}
for (var y = 0; y < Height; y++) for (var y = 0; y < Height; y++)
{ {
@ -298,14 +289,22 @@ public class WorldRep : IChunk
} }
} }
public void AddLayer()
{
var bytesPerLayer = Width * Height * Bpp;
Pixels.Add(new byte[bytesPerLayer]);
for (var j = 0; j < bytesPerLayer; j++)
{
Pixels[Layers][j] = 0;
}
Layers++;
}
public void Write(BinaryWriter writer) public void Write(BinaryWriter writer)
{ {
for (var i = 0; i < Layers; i++) foreach (var layer in Pixels)
{ {
if (_litLayers[i]) writer.Write(layer);
{
writer.Write(Pixels[i]);
}
} }
} }
} }

View File

@ -4,7 +4,7 @@ namespace KeepersCompound.LGS.Database;
public class ObjectHierarchy public class ObjectHierarchy
{ {
private class DarkObject public class DarkObject
{ {
public int objectId; public int objectId;
public int parentId; public int parentId;
@ -17,7 +17,7 @@ public class ObjectHierarchy
properties = new Dictionary<string, Property>(); properties = new Dictionary<string, Property>();
} }
public T? GetProperty<T>(string propName) where T : Property public T GetProperty<T>(string propName) where T : Property
{ {
if (properties.TryGetValue(propName, out var prop)) if (properties.TryGetValue(propName, out var prop))
{ {
@ -79,12 +79,11 @@ public class ObjectHierarchy
foreach (var prop in chunk.properties) foreach (var prop in chunk.properties)
{ {
var id = prop.objectId; var id = prop.objectId;
if (!_objects.TryGetValue(id, out var value)) if (!_objects.ContainsKey(id))
{ {
value = new DarkObject(id); _objects.Add(id, new DarkObject(id));
_objects.Add(id, value);
} }
value.properties.TryAdd(name, prop); _objects[id].properties.TryAdd(name, prop);
} }
} }
@ -103,7 +102,7 @@ public class ObjectHierarchy
} }
// TODO: Work out if there's some nice way to automatically decide if we inherit // TODO: Work out if there's some nice way to automatically decide if we inherit
public T? GetProperty<T>(int objectId, string propName, bool inherit = true) where T : Property public T GetProperty<T>(int objectId, string propName, bool inherit = true) where T : Property
{ {
if (!_objects.ContainsKey(objectId)) if (!_objects.ContainsKey(objectId))
{ {

View File

@ -2,33 +2,6 @@ using System.Numerics;
namespace KeepersCompound.Lightmapper; namespace KeepersCompound.Lightmapper;
public static class Utils
{
// Expects Hue and Saturation are 0-1, Brightness 0-255
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
public static Vector3 HsbToRgb(float hue, float saturation, float brightness)
{
hue *= 360;
var hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
var f = hue / 60 - Math.Floor(hue / 60);
var v = Convert.ToInt32(brightness);
var p = Convert.ToInt32(brightness * (1 - saturation));
var q = Convert.ToInt32(brightness * (1 - f * saturation));
var t = Convert.ToInt32(brightness * (1 - (1 - f) * saturation));
return hi switch
{
0 => new Vector3(v, t, p),
1 => new Vector3(q, v, p),
2 => new Vector3(p, v, t),
3 => new Vector3(p, q, v),
4 => new Vector3(t, p, v),
_ => new Vector3(v, p, q),
};
}
}
public static class MathUtils public static class MathUtils
{ {
public const float Epsilon = 0.001f; public const float Epsilon = 0.001f;
@ -76,7 +49,6 @@ public static class MathUtils
return d2 < r2; return d2 < r2;
} }
// Should automagically handle max float radii
public static bool Intersects(Sphere sphere, Sphere other) public static bool Intersects(Sphere sphere, Sphere other)
{ {
var rsum = sphere.Radius + other.Radius; var rsum = sphere.Radius + other.Radius;

View File

@ -74,7 +74,6 @@ class Program
return; return;
var ambient = rendParams.ambientLight * 255; var ambient = rendParams.ambientLight * 255;
var lights = Timing.TimeStage("Gather Lights", () => BuildLightList(mis, hierarchy, campaign)); var lights = Timing.TimeStage("Gather Lights", () => BuildLightList(mis, hierarchy, campaign));
Timing.TimeStage("Set Light Indices", () => SetCellLightIndices(worldRep, [.. lights]));
Timing.TimeStage("Light", () => CastSceneParallel(scene, worldRep, [.. lights], ambient)); Timing.TimeStage("Light", () => CastSceneParallel(scene, worldRep, [.. lights], ambient));
Timing.TimeStage("Update Anim Mapping", () => SetAnimLightCellMaps(mis, worldRep, lights)); Timing.TimeStage("Update Anim Mapping", () => SetAnimLightCellMaps(mis, worldRep, lights));
@ -86,6 +85,30 @@ class Program
Console.WriteLine($"Lit {lights.Count} light"); Console.WriteLine($"Lit {lights.Count} light");
} }
// Expects Hue and Saturation are 0-1, Brightness 0-255
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
private static Vector3 HsbToRgb(float hue, float saturation, float brightness)
{
hue *= 360;
var hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
var f = hue / 60 - Math.Floor(hue / 60);
var v = Convert.ToInt32(brightness);
var p = Convert.ToInt32(brightness * (1 - saturation));
var q = Convert.ToInt32(brightness * (1 - f * saturation));
var t = Convert.ToInt32(brightness * (1 - (1 - f) * saturation));
return hi switch
{
0 => new Vector3(v, t, p),
1 => new Vector3(q, v, p),
2 => new Vector3(p, v, t),
3 => new Vector3(p, q, v),
4 => new Vector3(t, p, v),
_ => new Vector3(v, p, q),
};
}
private static void SetAnimLightCellMaps( private static void SetAnimLightCellMaps(
DbFile mis, DbFile mis,
WorldRep worldRep, WorldRep worldRep,
@ -170,7 +193,7 @@ class Program
var light = new Light var light = new Light
{ {
position = brush.position, position = brush.position,
color = Utils.HsbToRgb(sz.Y, sz.Z, Math.Min(sz.X, 255.0f)), color = HsbToRgb(sz.Y, sz.Z, Math.Min(sz.X, 255.0f)),
radius = float.MaxValue, radius = float.MaxValue,
r2 = float.MaxValue, r2 = float.MaxValue,
lightTableIndex = worldRep.LightingTable.LightCount, lightTableIndex = worldRep.LightingTable.LightCount,
@ -243,7 +266,7 @@ class Program
var light = new Light var light = new Light
{ {
position = baseLight.position + propLight.Offset, position = baseLight.position + propLight.Offset,
color = Utils.HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propLight.Brightness), color = HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propLight.Brightness),
innerRadius = propLight.InnerRadius, innerRadius = propLight.InnerRadius,
radius = propLight.Radius, radius = propLight.Radius,
r2 = propLight.Radius * propLight.Radius, r2 = propLight.Radius * propLight.Radius,
@ -280,7 +303,7 @@ class Program
var light = new Light var light = new Light
{ {
position = baseLight.position + propAnimLight.Offset, position = baseLight.position + propAnimLight.Offset,
color = Utils.HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propAnimLight.MaxBrightness), color = HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propAnimLight.MaxBrightness),
innerRadius = propAnimLight.InnerRadius, innerRadius = propAnimLight.InnerRadius,
radius = propAnimLight.Radius, radius = propAnimLight.Radius,
r2 = propAnimLight.Radius * propAnimLight.Radius, r2 = propAnimLight.Radius * propAnimLight.Radius,
@ -346,8 +369,9 @@ class Program
var indices = new List<int>(); var indices = new List<int>();
var cells = worldRep.Cells; var cells = worldRep.Cells;
foreach (var cell in cells) for (var cellIdx = 0; cellIdx < cells.Length; cellIdx++)
{ {
var cell = cells[cellIdx];
var numPolys = cell.PolyCount; var numPolys = cell.PolyCount;
var numRenderPolys = cell.RenderPolyCount; var numRenderPolys = cell.RenderPolyCount;
var numPortalPolys = cell.PortalPolyCount; var numPortalPolys = cell.PortalPolyCount;
@ -360,7 +384,7 @@ class Program
var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys); var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys);
var cellIdxOffset = 0; var cellIdxOffset = 0;
for (var polyIdx = 0; polyIdx < maxPolyIdx; polyIdx++) for (int polyIdx = 0; polyIdx < maxPolyIdx; polyIdx++)
{ {
var poly = cell.Polys[polyIdx]; var poly = cell.Polys[polyIdx];
@ -372,7 +396,7 @@ class Program
vertices.Add(vertex); vertices.Add(vertex);
} }
for (var j = 1; j < numPolyVertices - 1; j++) for (int j = 1; j < numPolyVertices - 1; j++)
{ {
indices.Add(meshIndexOffset); indices.Add(meshIndexOffset);
indices.Add(meshIndexOffset + j); indices.Add(meshIndexOffset + j);
@ -389,6 +413,42 @@ class Program
private static void CastSceneParallel(Raytracer scene, WorldRep wr, Light[] lights, Vector3 ambientLight) private static void CastSceneParallel(Raytracer scene, WorldRep wr, Light[] lights, Vector3 ambientLight)
{ {
var hdr = wr.DataHeader.LightmapFormat == 2; var hdr = wr.DataHeader.LightmapFormat == 2;
// We set up light indices in a separate loop because the actual lighting
// phase takes a lot of shortcuts that we don't want
Parallel.ForEach(wr.Cells, cell =>
{
cell.LightIndexCount = 0;
cell.LightIndices.Clear();
// The OG lightmapper uses the cell traversal to work out all the cells that
// are actually visited. We're a lot more coarse and just say if a cell is
// in range then we potentially affect the lighting in the cell and add it
// to the list. Cells already contain their sphere bounds so we just use
// that for now, but a tighter AABB is another option.
var cellSphere = new MathUtils.Sphere(cell.SphereCenter, cell.SphereRadius);
foreach (var light in lights)
{
// If the light had radius 0 (represented here with max float) then we
// always add it to the list
// TODO: Neaten this up
if (light.radius == float.MaxValue)
{
cell.LightIndexCount++;
cell.LightIndices.Add((ushort)light.lightTableIndex);
}
else
{
var lightSphere = new MathUtils.Sphere(light.position, light.radius);
if (MathUtils.Intersects(cellSphere, lightSphere))
{
cell.LightIndexCount++;
cell.LightIndices.Add((ushort)light.lightTableIndex);
}
}
}
});
Parallel.ForEach(wr.Cells, cell => Parallel.ForEach(wr.Cells, cell =>
{ {
// Reset cell AnimLight palette // Reset cell AnimLight palette
@ -408,7 +468,7 @@ class Program
var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys); var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys);
var cellIdxOffset = 0; var cellIdxOffset = 0;
for (var polyIdx = 0; polyIdx < maxPolyIdx; polyIdx++) for (int polyIdx = 0; polyIdx < maxPolyIdx; polyIdx++)
{ {
var poly = cell.Polys[polyIdx]; var poly = cell.Polys[polyIdx];
var plane = cell.Planes[poly.PlaneId]; var plane = cell.Planes[poly.PlaneId];
@ -445,6 +505,13 @@ class Program
foreach (var light in lights) foreach (var light in lights)
{ {
var layer = 0; var layer = 0;
if (light.anim)
{
// Because we're building the AnimLightBitmask in this loop we
// know there aren't any layers set above us. So the target layer
// is just the number of set bits + 1.
layer = BitOperations.PopCount(info.AnimLightBitmask) + 1;
}
// Check if plane normal is facing towards the light // Check if plane normal is facing towards the light
// If it's not then we're never going to be (directly) lit by this // If it's not then we're never going to be (directly) lit by this
@ -528,7 +595,11 @@ class Program
cell.AnimLights.Add((ushort)light.lightTableIndex); cell.AnimLights.Add((ushort)light.lightTableIndex);
} }
info.AnimLightBitmask |= 1u << paletteIdx; info.AnimLightBitmask |= 1u << paletteIdx;
layer = paletteIdx + 1;
if (layer >= lightmap.Layers)
{
lightmap.AddLayer();
}
} }
var strength = CalculateLightStrengthAtPoint(light, pos, plane); var strength = CalculateLightStrengthAtPoint(light, pos, plane);
lightmap.AddLight(layer, x, y, light.color, strength, hdr); lightmap.AddLight(layer, x, y, light.color, strength, hdr);
@ -542,32 +613,6 @@ class Program
}); });
} }
private static void SetCellLightIndices(WorldRep wr, Light[] lights)
{
// We set up light indices in separately from lighting because the actual
// lighting phase takes a lot of shortcuts that we don't want
Parallel.ForEach(wr.Cells, cell =>
{
cell.LightIndexCount = 0;
cell.LightIndices.Clear();
// The OG lightmapper uses the cell traversal to work out all the cells that
// are actually visited. We're a lot more coarse and just say if a cell is
// in range then we potentially affect the lighting in the cell and add it
// to the list. Cells already contain their sphere bounds so we just use
// that for now, but a tighter AABB is another option.
var cellSphere = new MathUtils.Sphere(cell.SphereCenter, cell.SphereRadius);
foreach (var light in lights)
{
if (MathUtils.Intersects(cellSphere, new MathUtils.Sphere(light.position, light.radius)))
{
cell.LightIndexCount++;
cell.LightIndices.Add((ushort)light.lightTableIndex);
}
}
});
}
private static float CalculateLightStrengthAtPoint(Light light, Vector3 point, Plane plane) private static float CalculateLightStrengthAtPoint(Light light, Vector3 point, Plane plane)
{ {
// Calculate light strength at a given point. As far as I can tell // Calculate light strength at a given point. As far as I can tell