Compare commits

...

16 Commits

13 changed files with 369 additions and 230 deletions

View File

@ -1,5 +1,3 @@
using System;
using System.IO;
using System.Text; using System.Text;
namespace KeepersCompound.LGS.Database; namespace KeepersCompound.LGS.Database;

View File

@ -1,5 +1,3 @@
using System.IO;
namespace KeepersCompound.LGS.Database.Chunks; namespace KeepersCompound.LGS.Database.Chunks;
class AiConverseChunk : IChunk class AiConverseChunk : IChunk

View File

@ -1,5 +1,3 @@
using System.IO;
namespace KeepersCompound.LGS.Database.Chunks; namespace KeepersCompound.LGS.Database.Chunks;
class AiRoomDb : IChunk class AiRoomDb : IChunk

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics; using System.Numerics;
using System.Text;
namespace KeepersCompound.LGS.Database.Chunks; namespace KeepersCompound.LGS.Database.Chunks;

View File

@ -1,7 +1,3 @@
using System.Collections.Generic;
using System.IO;
namespace KeepersCompound.LGS.Database.Chunks; namespace KeepersCompound.LGS.Database.Chunks;
public record LinkId public record LinkId

View File

@ -1,4 +1,3 @@
using System.Numerics; using System.Numerics;
namespace KeepersCompound.LGS.Database.Chunks; namespace KeepersCompound.LGS.Database.Chunks;

View File

@ -50,7 +50,7 @@ public class WorldRep : IChunk
} }
} }
public struct Cell public class Cell
{ {
public struct Poly public struct Poly
{ {
@ -116,7 +116,7 @@ public class WorldRep : IChunk
} }
} }
public struct LightmapInfo public class LightmapInfo
{ {
public (short, short) Bases { get; set; } public (short, short) Bases { get; set; }
public short PaddedWidth { get; set; } public short PaddedWidth { get; set; }
@ -137,7 +137,7 @@ public class WorldRep : IChunk
AnimLightBitmask = reader.ReadUInt32(); AnimLightBitmask = reader.ReadUInt32();
} }
public readonly void Write(BinaryWriter writer) public void Write(BinaryWriter writer)
{ {
writer.Write(Bases.Item1); writer.Write(Bases.Item1);
writer.Write(Bases.Item2); writer.Write(Bases.Item2);
@ -150,9 +150,9 @@ public class WorldRep : IChunk
} }
} }
public struct Lightmap public class Lightmap
{ {
public byte[] Pixels { get; set; } public List<byte[]> Pixels { get; set; }
public int Layers; public int Layers;
public int Width; public int Width;
@ -161,44 +161,50 @@ public class WorldRep : IChunk
public Lightmap(BinaryReader reader, byte width, byte height, uint bitmask, int bytesPerPixel) public Lightmap(BinaryReader reader, byte width, byte height, uint bitmask, int bytesPerPixel)
{ {
var count = 1 + BitOperations.PopCount(bitmask); var layers = 1 + BitOperations.PopCount(bitmask);
var length = bytesPerPixel * width * height * count; var length = bytesPerPixel * width * height;
Pixels = reader.ReadBytes(length); Pixels = new List<byte[]>();
Layers = count; for (var i = 0; i < layers; i++)
{
Pixels.Add(reader.ReadBytes(length));
}
Layers = layers;
Width = width; Width = width;
Height = height; Height = height;
Bpp = bytesPerPixel; Bpp = bytesPerPixel;
} }
public readonly Vector4 GetPixel(uint layer, uint x, uint y) public Vector4 GetPixel(uint layer, uint x, uint y)
{ {
if (layer >= Layers || x >= Width || y >= Height) if (layer >= Layers || x >= Width || y >= Height)
{ {
return Vector4.Zero; return Vector4.Zero;
} }
var idx = 0 + x * Bpp + y * Bpp * Width + layer * Bpp * Width * Height; var pLayer = Pixels[(int)layer];
var idx = x * Bpp + y * Bpp * Width;
switch (Bpp) switch (Bpp)
{ {
case 1: case 1:
var raw1 = Pixels[idx]; var raw1 = pLayer[idx];
return new Vector4(raw1, raw1, raw1, 255) / 255.0f; return new Vector4(raw1, raw1, raw1, 255) / 255.0f;
case 2: case 2:
var raw2 = Pixels[idx] + (Pixels[idx + 1] << 8); var raw2 = pLayer[idx] + (pLayer[idx + 1] << 8);
return new Vector4(raw2 & 31, (raw2 >> 5) & 31, (raw2 >> 10) & 31, 31) / 31.0f; return new Vector4(raw2 & 31, (raw2 >> 5) & 31, (raw2 >> 10) & 31, 31) / 31.0f;
case 4: case 4:
return new Vector4(Pixels[idx + 2], Pixels[idx + 1], Pixels[idx], Pixels[idx + 3]) / 255.0f; return new Vector4(pLayer[idx + 2], pLayer[idx + 1], pLayer[idx], pLayer[idx + 3]) / 255.0f;
default: default:
return Vector4.Zero; return Vector4.Zero;
} }
} }
public readonly byte[] AsBytesRgba(int layer) public byte[] AsBytesRgba(int layer)
{ {
ArgumentOutOfRangeException.ThrowIfLessThan(layer, 0, nameof(layer)); ArgumentOutOfRangeException.ThrowIfLessThan(layer, 0, nameof(layer));
ArgumentOutOfRangeException.ThrowIfGreaterThan(layer, Layers, nameof(layer)); ArgumentOutOfRangeException.ThrowIfGreaterThan(layer, Layers, nameof(layer));
var pIdx = layer * Bpp * Width * Height; var pLayer = Pixels[layer];
var pIdx = 0;
var length = 4 * Width * Height; var length = 4 * Width * Height;
var bytes = new byte[length]; var bytes = new byte[length];
for (var i = 0; i < length; i += 4, pIdx += Bpp) for (var i = 0; i < length; i += 4, pIdx += Bpp)
@ -206,24 +212,24 @@ public class WorldRep : IChunk
switch (Bpp) switch (Bpp)
{ {
case 1: case 1:
var raw1 = Pixels[pIdx]; var raw1 = pLayer[pIdx];
bytes[i] = raw1; bytes[i] = raw1;
bytes[i + 1] = raw1; bytes[i + 1] = raw1;
bytes[i + 2] = raw1; bytes[i + 2] = raw1;
bytes[i + 3] = 255; bytes[i + 3] = 255;
break; break;
case 2: case 2:
var raw2 = Pixels[pIdx] + (Pixels[pIdx + 1] << 8); var raw2 = pLayer[pIdx] + (pLayer[pIdx + 1] << 8);
bytes[i] = (byte)(255 * (raw2 & 31) / 31.0f); bytes[i] = (byte)(255 * (raw2 & 31) / 31.0f);
bytes[i + 1] = (byte)(255 * ((raw2 >> 5) & 31) / 31.0f); bytes[i + 1] = (byte)(255 * ((raw2 >> 5) & 31) / 31.0f);
bytes[i + 2] = (byte)(255 * ((raw2 >> 10) & 31) / 31.0f); bytes[i + 2] = (byte)(255 * ((raw2 >> 10) & 31) / 31.0f);
bytes[i + 3] = 255; bytes[i + 3] = 255;
break; break;
case 4: case 4:
bytes[i] = Pixels[pIdx + 2]; bytes[i] = pLayer[pIdx + 2];
bytes[i + 1] = Pixels[pIdx + 1]; bytes[i + 1] = pLayer[pIdx + 1];
bytes[i + 2] = Pixels[pIdx]; bytes[i + 2] = pLayer[pIdx];
bytes[i + 3] = Pixels[pIdx + 3]; bytes[i + 3] = pLayer[pIdx + 3];
break; break;
} }
} }
@ -232,13 +238,14 @@ public class WorldRep : IChunk
} }
// TODO: This ONLY works for rgba (bpp = 4)!!! // TODO: This ONLY works for rgba (bpp = 4)!!!
public void AddLight(int layer, int x, int y, byte r, byte g, byte b) public void AddLight(int layer, int x, int y, float r, float g, float b)
{ {
var idx = (x + y * Width + layer * Width * Height) * Bpp; var idx = (x + y * Width) * Bpp;
Pixels[idx] = (byte)Math.Min(Pixels[idx] + b, 255); var pLayer = Pixels[layer];
Pixels[idx + 1] = (byte)Math.Min(Pixels[idx + 1] + g, 255); pLayer[idx] = (byte)Math.Clamp(pLayer[idx] + r, 0, 255);
Pixels[idx + 2] = (byte)Math.Min(Pixels[idx + 2] + r, 255); pLayer[idx + 1] = (byte)Math.Clamp(pLayer[idx + 1] + g, 0, 255);
Pixels[idx + 3] = 255; pLayer[idx + 2] = (byte)Math.Clamp(pLayer[idx + 2] + b, 0, 255);
pLayer[idx + 3] = 255;
} }
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)
@ -264,12 +271,41 @@ public class WorldRep : IChunk
c /= ratio; c /= ratio;
} }
AddLight(layer, x, y, (byte)c.X, (byte)c.Y, (byte)c.Z); AddLight(layer, x, y, c.Z, c.Y, c.X);
} }
public readonly void Write(BinaryWriter writer) public void Reset(Vector3 ambientLight, bool hdr)
{ {
writer.Write(Pixels); Layers = 0;
Pixels.Clear();
AddLayer();
for (var y = 0; y < Height; y++)
{
for (var x = 0; x < Width; x++)
{
AddLight(0, x, y, ambientLight, 1.0f, hdr);
}
}
}
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)
{
foreach (var layer in Pixels)
{
writer.Write(layer);
}
} }
} }
@ -292,7 +328,7 @@ public class WorldRep : IChunk
public uint IndexCount { get; set; } public uint IndexCount { get; set; }
public byte[] Indices { get; set; } public byte[] Indices { get; set; }
public Plane[] Planes { get; set; } public Plane[] Planes { get; set; }
public ushort[] AnimLights { get; set; } public List<ushort> AnimLights { get; set; }
public LightmapInfo[] LightList { get; set; } public LightmapInfo[] LightList { get; set; }
public Lightmap[] Lightmaps { get; set; } public Lightmap[] Lightmaps { get; set; }
public int LightIndexCount { get; set; } public int LightIndexCount { get; set; }
@ -339,10 +375,10 @@ public class WorldRep : IChunk
{ {
Planes[i] = new Plane(reader.ReadVec3(), reader.ReadSingle()); Planes[i] = new Plane(reader.ReadVec3(), reader.ReadSingle());
} }
AnimLights = new ushort[AnimLightCount]; AnimLights = new List<ushort>(AnimLightCount);
for (var i = 0; i < AnimLightCount; i++) for (var i = 0; i < AnimLightCount; i++)
{ {
AnimLights[i] = reader.ReadUInt16(); AnimLights.Add(reader.ReadUInt16());
} }
LightList = new LightmapInfo[RenderPolyCount]; LightList = new LightmapInfo[RenderPolyCount];
for (var i = 0; i < RenderPolyCount; i++) for (var i = 0; i < RenderPolyCount; i++)
@ -363,7 +399,7 @@ public class WorldRep : IChunk
} }
} }
public readonly void Write(BinaryWriter writer) public void Write(BinaryWriter writer)
{ {
writer.Write(VertexCount); writer.Write(VertexCount);
writer.Write(PolyCount); writer.Write(PolyCount);
@ -484,16 +520,16 @@ public class WorldRep : IChunk
} }
} }
public struct LightTable public class LightTable
{ {
public struct LightData public struct LightData
{ {
public Vector3 Location; public Vector3 Location;
public Vector3 Direction; public Vector3 Direction;
public Vector3 Color; public Vector3 Color;
float InnerAngle; // I'm pretty sure these are the spotlight angles public float InnerAngle; // I'm pretty sure these are the spotlight angles
float OuterAngle; public float OuterAngle;
float Radius; public float Radius;
public LightData(BinaryReader reader) public LightData(BinaryReader reader)
{ {
@ -537,34 +573,35 @@ public class WorldRep : IChunk
public int LightCount; public int LightCount;
public int DynamicLightCount; public int DynamicLightCount;
public int AnimMapCount; public int AnimMapCount;
public LightData[] Lights; public List<LightData> Lights;
public LightData[] ScratchpadLights; public LightData[] ScratchpadLights;
public AnimCellMap[] AnimCellMaps; public List<AnimCellMap> AnimCellMaps;
// TODO: Support olddark // TODO: Support olddark
public LightTable(BinaryReader reader) public LightTable(BinaryReader reader)
{ {
LightCount = reader.ReadInt32(); LightCount = reader.ReadInt32();
DynamicLightCount = reader.ReadInt32(); DynamicLightCount = reader.ReadInt32();
Lights = new LightData[LightCount + DynamicLightCount]; var totalLightCount = LightCount + DynamicLightCount;
for (var i = 0; i < Lights.Length; i++) Lights = new List<LightData>(totalLightCount);
for (var i = 0; i < totalLightCount; i++)
{ {
Lights[i] = new LightData(reader); Lights.Add(new LightData(reader));
} }
ScratchpadLights = new LightData[32]; ScratchpadLights = new LightData[32];
for (var i = 0; i < ScratchpadLights.Length; i++) for (var i = 0; i < 32; i++)
{ {
ScratchpadLights[i] = new LightData(reader); ScratchpadLights[i] = new LightData(reader);
} }
AnimMapCount = reader.ReadInt32(); AnimMapCount = reader.ReadInt32();
AnimCellMaps = new AnimCellMap[AnimMapCount]; AnimCellMaps = new List<AnimCellMap>(AnimMapCount);
for (var i = 0; i < AnimCellMaps.Length; i++) for (var i = 0; i < AnimMapCount; i++)
{ {
AnimCellMaps[i] = new AnimCellMap(reader); AnimCellMaps.Add(new AnimCellMap(reader));
} }
} }
public readonly void Write(BinaryWriter writer) public void Write(BinaryWriter writer)
{ {
writer.Write(LightCount); writer.Write(LightCount);
writer.Write(DynamicLightCount); writer.Write(DynamicLightCount);
@ -582,6 +619,33 @@ public class WorldRep : IChunk
map.Write(writer); map.Write(writer);
} }
} }
public void Reset()
{
// Seems light we store something for sunlight at index 0 even if sunlight isn't being used
// TODO: Work out what to actually *put* here
LightCount = 1;
Lights.Clear();
Lights.Add(new LightData());
DynamicLightCount = 0;
AnimMapCount = 0;
AnimCellMaps.Clear();
}
public void AddLight(LightData data, bool dynamicLight = false)
{
if (dynamicLight)
{
DynamicLightCount++;
}
else
{
LightCount++;
}
Lights.Add(data);
}
} }
public ChunkHeader Header { get; set; } public ChunkHeader Header { get; set; }

View File

@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using KeepersCompound.LGS.Database.Chunks; using KeepersCompound.LGS.Database.Chunks;
@ -133,6 +129,17 @@ public class DbFile
writer.Write(tocOffset); writer.Write(tocOffset);
} }
public bool TryGetChunk<T>(string name, out T chunk)
{
if (Chunks.TryGetValue(name, out var rawChunk))
{
chunk = (T)rawChunk;
return true;
}
chunk = default;
return false;
}
private static IChunk NewChunk(string entryName) private static IChunk NewChunk(string entryName)
{ {
return entryName switch return entryName switch

View File

@ -1,6 +1,3 @@
using System;
using System.Collections.Generic;
using KeepersCompound.LGS.Database.Chunks; using KeepersCompound.LGS.Database.Chunks;
namespace KeepersCompound.LGS.Database; namespace KeepersCompound.LGS.Database;
@ -38,21 +35,19 @@ public class ObjectHierarchy
T GetMergedChunk<T>(string name) where T : IMergable T GetMergedChunk<T>(string name) where T : IMergable
{ {
if (db.Chunks.TryGetValue(name, out var rawChunk)) if (!db.TryGetChunk<T>(name, out var chunk))
{ {
var chunk = (T)rawChunk; throw new ArgumentException($"No chunk with name ({name}) found", nameof(name));
if (gam != null && gam.Chunks.TryGetValue(name, out var rawGamChunk)) }
if (gam != null && gam.TryGetChunk<T>(name, out var gamChunk))
{ {
var gamChunk = (T)rawGamChunk;
gamChunk.Merge(chunk); gamChunk.Merge(chunk);
return gamChunk; return gamChunk;
} }
return chunk; return chunk;
} }
throw new ArgumentException($"No chunk with name ({name}) found", nameof(name));
}
// Add parentages // Add parentages
var metaPropLinks = GetMergedChunk<LinkChunk>("L$MetaProp"); var metaPropLinks = GetMergedChunk<LinkChunk>("L$MetaProp");
var metaPropLinkData = GetMergedChunk<LinkDataMetaProp>("LD$MetaProp"); var metaPropLinkData = GetMergedChunk<LinkDataMetaProp>("LD$MetaProp");

View File

@ -1,5 +1,3 @@
using System.IO;
namespace KeepersCompound.LGS.Database; namespace KeepersCompound.LGS.Database;
public struct Version public struct Version

View File

@ -1,5 +1,3 @@
using System;
using System.IO;
using System.Numerics; using System.Numerics;
using System.Text; using System.Text;

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq;
namespace KeepersCompound.LGS; namespace KeepersCompound.LGS;

View File

@ -1,4 +1,3 @@
using System.Diagnostics;
using System.Numerics; using System.Numerics;
using KeepersCompound.LGS; using KeepersCompound.LGS;
using KeepersCompound.LGS.Database; using KeepersCompound.LGS.Database;
@ -24,6 +23,7 @@ class Program
public float spotlightOuterAngle; public float spotlightOuterAngle;
public bool anim; public bool anim;
public int animObjId;
public int animLightTableIndex; public int animLightTableIndex;
} }
@ -57,12 +57,9 @@ class Program
var mis = Timing.TimeStage("Parse DB", () => new DbFile(misPath)); var mis = Timing.TimeStage("Parse DB", () => new DbFile(misPath));
var hierarchy = Timing.TimeStage("Build Hierarchy", () => BuildHierarchy(misPath, mis)); var hierarchy = Timing.TimeStage("Build Hierarchy", () => BuildHierarchy(misPath, mis));
var lights = Timing.TimeStage("Gather Lights", () => BuildLightList(mis, hierarchy, campaign));
// Build embree mesh // Build embree mesh
if (!mis.Chunks.TryGetValue("WREXT", out var wrRaw)) if (!mis.TryGetChunk<WorldRep>("WREXT", out var worldRep))
return; return;
var worldRep = (WorldRep)wrRaw;
var scene = Timing.TimeStage("Build Scene", () => var scene = Timing.TimeStage("Build Scene", () =>
{ {
var rt = new Raytracer(); var rt = new Raytracer();
@ -72,10 +69,12 @@ class Program
}); });
// For each lightmap pixel cast against all the brush and object lights // For each lightmap pixel cast against all the brush and object lights
if (!mis.Chunks.TryGetValue("RENDPARAMS", out var rendParamsRaw)) if (!mis.TryGetChunk<RendParams>("RENDPARAMS", out var rendParams))
return; return;
var ambient = ((RendParams)rendParamsRaw).ambientLight * 255; var ambient = rendParams.ambientLight * 255;
var lights = Timing.TimeStage("Gather Lights", () => BuildLightList(mis, hierarchy, campaign));
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));
var dir = Path.GetDirectoryName(misPath); var dir = Path.GetDirectoryName(misPath);
var filename = Path.GetFileNameWithoutExtension(misPath); var filename = Path.GetFileNameWithoutExtension(misPath);
@ -109,25 +108,103 @@ class Program
}; };
} }
// Get list of brush lights, and object lights (ignore anim lights for now) private static void SetAnimLightCellMaps(
private static List<Light> BuildLightList(DbFile mis, ObjectHierarchy hierarchy, ResourcePathManager.CampaignResources campaign) DbFile mis,
WorldRep worldRep,
List<Light> lights)
{
// Now that we've set all the per-cell stuff we need to aggregate the cell mappings
// We can't do this in parallel which is why it's being done afterwards rather than
// as we go
var map = new Dictionary<ushort, List<WorldRep.LightTable.AnimCellMap>>();
for (var i = 0; i < worldRep.Cells.Length; i++)
{
var cell = worldRep.Cells[i];
for (var j = 0; j < cell.AnimLightCount; j++)
{
var animLightIdx = cell.AnimLights[j];
if (!map.TryGetValue(animLightIdx, out var value))
{
value = [];
map[animLightIdx] = value;
}
value.Add(new WorldRep.LightTable.AnimCellMap
{
CellIndex = (ushort)i,
LightIndex = (ushort)j,
});
}
}
if (!mis.TryGetChunk<PropertyChunk<PropAnimLight>>("P$AnimLight", out var animLightChunk))
{
return;
}
foreach (var (lightIdx, animCellMaps) in map)
{
// Get the appropriate property!!
var light = lights.Find((l) => l.anim && l.animLightTableIndex == lightIdx);
foreach (var prop in animLightChunk.properties)
{
if (prop.objectId == light.animObjId)
{
prop.LightTableLightIndex = lightIdx;
prop.LightTableMapIndex = (ushort)worldRep.LightingTable.AnimMapCount;
prop.CellsReached = (ushort)animCellMaps.Count;
break;
}
}
foreach (var animCellMap in animCellMaps)
{
worldRep.LightingTable.AnimCellMaps.Add(animCellMap);
worldRep.LightingTable.AnimMapCount++;
}
}
}
// Gather all the brush, object, and anim ligths. Resets the lighting table
// TODO: Handle dynamic lights
private static List<Light> BuildLightList(
DbFile mis,
ObjectHierarchy hierarchy,
ResourcePathManager.CampaignResources campaign)
{ {
var lights = new List<Light>(); var lights = new List<Light>();
if (mis.Chunks.TryGetValue("BRLIST", out var brListRaw)) // Get the chunks we need
if (!mis.TryGetChunk<WorldRep>("WREXT", out var worldRep) ||
!mis.TryGetChunk<BrList>("BRLIST", out var brList))
{ {
var brList = (BrList)brListRaw; return lights;
}
worldRep.LightingTable.Reset();
foreach (var brush in brList.Brushes) foreach (var brush in brList.Brushes)
{ {
if (brush.media == BrList.Brush.Media.Light) if (brush.media == BrList.Brush.Media.Light)
{ {
// For some reason the light table index on brush lights is 1 indexed
brush.brushInfo = (uint)worldRep.LightingTable.LightCount + 1;
var sz = brush.size; var sz = brush.size;
lights.Add(new Light var light = new Light
{ {
position = brush.position, position = brush.position,
color = 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,
};
lights.Add(light);
worldRep.LightingTable.AddLight(new WorldRep.LightTable.LightData
{
Location = light.position,
Direction = light.spotlightDir,
Color = light.color / 32.0f, // TODO: This is based on light_scale config var
InnerAngle = -1.0f,
Radius = 0,
}); });
} }
else if (brush.media == BrList.Brush.Media.Object) else if (brush.media == BrList.Brush.Media.Object)
@ -146,6 +223,7 @@ class Program
{ {
position = brush.position, position = brush.position,
spotlightDir = -Vector3.UnitZ, spotlightDir = -Vector3.UnitZ,
spotlightInnerAngle = -1.0f,
}; };
if (propModelname != null) if (propModelname != null)
@ -203,10 +281,22 @@ class Program
} }
lights.Add(light); lights.Add(light);
worldRep.LightingTable.AddLight(new WorldRep.LightTable.LightData
{
Location = light.position,
Direction = light.spotlightDir,
Color = light.color / 32.0f, // TODO: This is based on light_scale config var
InnerAngle = light.spotlightInnerAngle,
OuterAngle = light.spotlightOuterAngle,
Radius = propLight.Radius,
});
} }
if (propAnimLight != null) if (propAnimLight != null)
{ {
var lightIndex = worldRep.LightingTable.LightCount;
propAnimLight.LightTableLightIndex = (ushort)lightIndex;
var light = new Light var light = new Light
{ {
position = baseLight.position + propAnimLight.Offset, position = baseLight.position + propAnimLight.Offset,
@ -219,6 +309,7 @@ class Program
spotlightInnerAngle = baseLight.spotlightInnerAngle, spotlightInnerAngle = baseLight.spotlightInnerAngle,
spotlightOuterAngle = baseLight.spotlightOuterAngle, spotlightOuterAngle = baseLight.spotlightOuterAngle,
anim = true, anim = true,
animObjId = id,
animLightTableIndex = propAnimLight.LightTableLightIndex, animLightTableIndex = propAnimLight.LightTableLightIndex,
}; };
if (propAnimLight.Radius == 0) if (propAnimLight.Radius == 0)
@ -228,7 +319,15 @@ class Program
} }
lights.Add(light); lights.Add(light);
} worldRep.LightingTable.AddLight(new WorldRep.LightTable.LightData
{
Location = light.position,
Direction = light.spotlightDir,
Color = light.color / 32.0f, // TODO: This is based on light_scale config var
InnerAngle = light.spotlightInnerAngle,
OuterAngle = light.spotlightOuterAngle,
Radius = propAnimLight.Radius,
});
} }
} }
} }
@ -239,11 +338,11 @@ class Program
private static ObjectHierarchy BuildHierarchy(string misPath, DbFile misFile) private static ObjectHierarchy BuildHierarchy(string misPath, DbFile misFile)
{ {
ObjectHierarchy objHierarchy; ObjectHierarchy objHierarchy;
if (misFile.Chunks.TryGetValue("GAM_FILE", out var gamFileChunk)) if (misFile.TryGetChunk<GamFile>("GAM_FILE", out var gamFile))
{ {
var dir = Path.GetDirectoryName(misPath); var dir = Path.GetDirectoryName(misPath);
var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }; var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
var name = ((GamFile)gamFileChunk).fileName; var name = gamFile.fileName;
var paths = Directory.GetFiles(dir!, name, options); var paths = Directory.GetFiles(dir!, name, options);
if (paths.Length > 0) if (paths.Length > 0)
{ {
@ -314,6 +413,10 @@ class Program
Parallel.ForEach(wr.Cells, cell => Parallel.ForEach(wr.Cells, cell =>
{ {
// Reset cell AnimLight palette
cell.AnimLightCount = 0;
cell.AnimLights.Clear();
var numPolys = cell.PolyCount; var numPolys = cell.PolyCount;
var numRenderPolys = cell.RenderPolyCount; var numRenderPolys = cell.RenderPolyCount;
var numPortalPolys = cell.PortalPolyCount; var numPortalPolys = cell.PortalPolyCount;
@ -335,7 +438,8 @@ class Program
var info = cell.LightList[polyIdx]; var info = cell.LightList[polyIdx];
var lightmap = cell.Lightmaps[polyIdx]; var lightmap = cell.Lightmaps[polyIdx];
ResetLightmap(ambientLight, lightmap, hdr); info.AnimLightBitmask = 0;
lightmap.Reset(ambientLight, hdr);
// Get world position of lightmap (0, 0) (+0.5 so we cast from the center of a pixel) // Get world position of lightmap (0, 0) (+0.5 so we cast from the center of a pixel)
var topLeft = cell.Vertices[cell.Indices[cellIdxOffset]]; var topLeft = cell.Vertices[cell.Indices[cellIdxOffset]];
@ -365,23 +469,10 @@ class Program
var layer = 0; var layer = 0;
if (light.anim) if (light.anim)
{ {
var paletteIdx = -1; // Because we're building the AnimLightBitmask in this loop we
for (var i = 0; i < cell.AnimLightCount; i++) // know there aren't any layers set above us. So the target layer
{ // is just the number of set bits + 1.
var id = cell.AnimLights[i]; layer = BitOperations.PopCount(info.AnimLightBitmask) + 1;
if (id == light.animLightTableIndex)
{
paletteIdx = i;
}
}
if (paletteIdx == -1 || (info.AnimLightBitmask & (1 << paletteIdx)) == 0)
{
continue;
}
var mask = info.AnimLightBitmask & ((1 << (paletteIdx + 1)) - 1);
layer = BitOperations.PopCount((uint)mask);
} }
// Check if plane normal is facing towards the light // Check if plane normal is facing towards the light
@ -451,6 +542,27 @@ class Program
var hit = hitResult && Math.Abs(hitResult.Distance - direction.Length()) < MathUtils.Epsilon; var hit = hitResult && Math.Abs(hitResult.Distance - direction.Length()) < MathUtils.Epsilon;
if (hit) if (hit)
{ {
// If we're an anim light there's a lot of stuff we need to update
// Firstly we need to add the light to the cells anim light palette
// Secondly we need to set the appropriate bit of the lightmap's
// bitmask. Finally we need to check if the lightmap needs another layer
if (light.anim)
{
// TODO: Don't recalculate this for every point lol
var paletteIdx = cell.AnimLights.IndexOf((ushort)light.animLightTableIndex);
if (paletteIdx == -1)
{
paletteIdx = cell.AnimLightCount;
cell.AnimLightCount++;
cell.AnimLights.Add((ushort)light.animLightTableIndex);
}
info.AnimLightBitmask |= 1u << paletteIdx;
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);
} }
@ -510,20 +622,4 @@ class Program
return strength; return strength;
} }
private static void ResetLightmap(Vector3 ambientLight, WorldRep.Cell.Lightmap lightmap, bool hdr)
{
for (var i = 0; i < lightmap.Pixels.Length; i++)
{
lightmap.Pixels[i] = 0;
}
for (var y = 0; y < lightmap.Height; y++)
{
for (var x = 0; x < lightmap.Width; x++)
{
lightmap.AddLight(0, x, y, ambientLight, 1.0f, hdr);
}
}
}
} }