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;
namespace KeepersCompound.LGS.Database;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -50,7 +50,7 @@ public class WorldRep : IChunk
}
}
public struct Cell
public class Cell
{
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 PaddedWidth { get; set; }
@ -137,7 +137,7 @@ public class WorldRep : IChunk
AnimLightBitmask = reader.ReadUInt32();
}
public readonly void Write(BinaryWriter writer)
public void Write(BinaryWriter writer)
{
writer.Write(Bases.Item1);
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 Width;
@ -161,44 +161,50 @@ public class WorldRep : IChunk
public Lightmap(BinaryReader reader, byte width, byte height, uint bitmask, int bytesPerPixel)
{
var count = 1 + BitOperations.PopCount(bitmask);
var length = bytesPerPixel * width * height * count;
Pixels = reader.ReadBytes(length);
Layers = count;
var layers = 1 + BitOperations.PopCount(bitmask);
var length = bytesPerPixel * width * height;
Pixels = new List<byte[]>();
for (var i = 0; i < layers; i++)
{
Pixels.Add(reader.ReadBytes(length));
}
Layers = layers;
Width = width;
Height = height;
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)
{
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)
{
case 1:
var raw1 = Pixels[idx];
var raw1 = pLayer[idx];
return new Vector4(raw1, raw1, raw1, 255) / 255.0f;
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;
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:
return Vector4.Zero;
}
}
public readonly byte[] AsBytesRgba(int layer)
public byte[] AsBytesRgba(int layer)
{
ArgumentOutOfRangeException.ThrowIfLessThan(layer, 0, 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 bytes = new byte[length];
for (var i = 0; i < length; i += 4, pIdx += Bpp)
@ -206,24 +212,24 @@ public class WorldRep : IChunk
switch (Bpp)
{
case 1:
var raw1 = Pixels[pIdx];
var raw1 = pLayer[pIdx];
bytes[i] = raw1;
bytes[i + 1] = raw1;
bytes[i + 2] = raw1;
bytes[i + 3] = 255;
break;
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 + 1] = (byte)(255 * ((raw2 >> 5) & 31) / 31.0f);
bytes[i + 2] = (byte)(255 * ((raw2 >> 10) & 31) / 31.0f);
bytes[i + 3] = 255;
break;
case 4:
bytes[i] = Pixels[pIdx + 2];
bytes[i + 1] = Pixels[pIdx + 1];
bytes[i + 2] = Pixels[pIdx];
bytes[i + 3] = Pixels[pIdx + 3];
bytes[i] = pLayer[pIdx + 2];
bytes[i + 1] = pLayer[pIdx + 1];
bytes[i + 2] = pLayer[pIdx];
bytes[i + 3] = pLayer[pIdx + 3];
break;
}
}
@ -232,13 +238,14 @@ public class WorldRep : IChunk
}
// 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;
Pixels[idx] = (byte)Math.Min(Pixels[idx] + b, 255);
Pixels[idx + 1] = (byte)Math.Min(Pixels[idx + 1] + g, 255);
Pixels[idx + 2] = (byte)Math.Min(Pixels[idx + 2] + r, 255);
Pixels[idx + 3] = 255;
var idx = (x + y * Width) * Bpp;
var pLayer = Pixels[layer];
pLayer[idx] = (byte)Math.Clamp(pLayer[idx] + r, 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 + 3] = 255;
}
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;
}
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 byte[] Indices { get; set; }
public Plane[] Planes { get; set; }
public ushort[] AnimLights { get; set; }
public List<ushort> AnimLights { get; set; }
public LightmapInfo[] LightList { get; set; }
public Lightmap[] Lightmaps { get; set; }
public int LightIndexCount { get; set; }
@ -339,10 +375,10 @@ public class WorldRep : IChunk
{
Planes[i] = new Plane(reader.ReadVec3(), reader.ReadSingle());
}
AnimLights = new ushort[AnimLightCount];
AnimLights = new List<ushort>(AnimLightCount);
for (var i = 0; i < AnimLightCount; i++)
{
AnimLights[i] = reader.ReadUInt16();
AnimLights.Add(reader.ReadUInt16());
}
LightList = new LightmapInfo[RenderPolyCount];
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(PolyCount);
@ -484,16 +520,16 @@ public class WorldRep : IChunk
}
}
public struct LightTable
public class LightTable
{
public struct LightData
{
public Vector3 Location;
public Vector3 Direction;
public Vector3 Color;
float InnerAngle; // I'm pretty sure these are the spotlight angles
float OuterAngle;
float Radius;
public float InnerAngle; // I'm pretty sure these are the spotlight angles
public float OuterAngle;
public float Radius;
public LightData(BinaryReader reader)
{
@ -537,34 +573,35 @@ public class WorldRep : IChunk
public int LightCount;
public int DynamicLightCount;
public int AnimMapCount;
public LightData[] Lights;
public List<LightData> Lights;
public LightData[] ScratchpadLights;
public AnimCellMap[] AnimCellMaps;
public List<AnimCellMap> AnimCellMaps;
// TODO: Support olddark
public LightTable(BinaryReader reader)
{
LightCount = reader.ReadInt32();
DynamicLightCount = reader.ReadInt32();
Lights = new LightData[LightCount + DynamicLightCount];
for (var i = 0; i < Lights.Length; i++)
var totalLightCount = LightCount + DynamicLightCount;
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];
for (var i = 0; i < ScratchpadLights.Length; i++)
for (var i = 0; i < 32; i++)
{
ScratchpadLights[i] = new LightData(reader);
}
AnimMapCount = reader.ReadInt32();
AnimCellMaps = new AnimCellMap[AnimMapCount];
for (var i = 0; i < AnimCellMaps.Length; i++)
AnimCellMaps = new List<AnimCellMap>(AnimMapCount);
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(DynamicLightCount);
@ -582,6 +619,33 @@ public class WorldRep : IChunk
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; }

View File

@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using KeepersCompound.LGS.Database.Chunks;
@ -133,6 +129,17 @@ public class DbFile
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)
{
return entryName switch

View File

@ -1,6 +1,3 @@
using System;
using System.Collections.Generic;
using KeepersCompound.LGS.Database.Chunks;
namespace KeepersCompound.LGS.Database;
@ -38,19 +35,17 @@ public class ObjectHierarchy
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;
if (gam != null && gam.Chunks.TryGetValue(name, out var rawGamChunk))
{
var gamChunk = (T)rawGamChunk;
gamChunk.Merge(chunk);
return gamChunk;
}
return chunk;
throw new ArgumentException($"No chunk with name ({name}) found", nameof(name));
}
throw new ArgumentException($"No chunk with name ({name}) found", nameof(name));
if (gam != null && gam.TryGetChunk<T>(name, out var gamChunk))
{
gamChunk.Merge(chunk);
return gamChunk;
}
return chunk;
}
// Add parentages

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
using System.Diagnostics;
using System.Numerics;
using KeepersCompound.LGS;
using KeepersCompound.LGS.Database;
@ -24,6 +23,7 @@ class Program
public float spotlightOuterAngle;
public bool anim;
public int animObjId;
public int animLightTableIndex;
}
@ -57,12 +57,9 @@ class Program
var mis = Timing.TimeStage("Parse DB", () => new DbFile(misPath));
var hierarchy = Timing.TimeStage("Build Hierarchy", () => BuildHierarchy(misPath, mis));
var lights = Timing.TimeStage("Gather Lights", () => BuildLightList(mis, hierarchy, campaign));
// Build embree mesh
if (!mis.Chunks.TryGetValue("WREXT", out var wrRaw))
if (!mis.TryGetChunk<WorldRep>("WREXT", out var worldRep))
return;
var worldRep = (WorldRep)wrRaw;
var scene = Timing.TimeStage("Build Scene", () =>
{
var rt = new Raytracer();
@ -72,10 +69,12 @@ class Program
});
// 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;
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("Update Anim Mapping", () => SetAnimLightCellMaps(mis, worldRep, lights));
var dir = Path.GetDirectoryName(misPath);
var filename = Path.GetFileNameWithoutExtension(misPath);
@ -109,126 +108,226 @@ class Program
};
}
// Get list of brush lights, and object lights (ignore anim lights for now)
private static List<Light> BuildLightList(DbFile mis, ObjectHierarchy hierarchy, ResourcePathManager.CampaignResources campaign)
private static void SetAnimLightCellMaps(
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>();
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;
foreach (var brush in brList.Brushes)
return lights;
}
worldRep.LightingTable.Reset();
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 light = new Light
{
var sz = brush.size;
lights.Add(new Light
position = brush.position,
color = HsbToRgb(sz.Y, sz.Z, Math.Min(sz.X, 255.0f)),
radius = 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)
{
// TODO: Handle PropSpotlightAndAmbient
var id = (int)brush.brushInfo;
var propAnimLight = hierarchy.GetProperty<PropAnimLight>(id, "P$AnimLight", false);
var propLight = hierarchy.GetProperty<PropLight>(id, "P$Light", false);
var propLightColor = hierarchy.GetProperty<PropLightColor>(id, "P$LightColo");
var propSpotlight = hierarchy.GetProperty<PropSpotlight>(id, "P$Spotlight");
var propModelname = hierarchy.GetProperty<PropLabel>(id, "P$ModelName");
propLightColor ??= new PropLightColor { Hue = 0, Saturation = 0 };
var baseLight = new Light
{
position = brush.position,
spotlightDir = -Vector3.UnitZ,
spotlightInnerAngle = -1.0f,
};
if (propModelname != null)
{
var resName = $"{propModelname.value.ToLower()}.bin";
var modelPath = campaign.GetResourcePath(ResourceType.Object, resName);
if (modelPath != null)
{
position = brush.position,
color = HsbToRgb(sz.Y, sz.Z, Math.Min(sz.X, 255.0f)),
radius = float.MaxValue,
r2 = float.MaxValue,
});
// TODO: Handle failing to find model more gracefully
var model = new ModelFile(modelPath);
if (model.TryGetVhot(ModelFile.VhotId.LightPosition, out var vhot))
{
baseLight.position += vhot.Position;
}
if (model.TryGetVhot(ModelFile.VhotId.LightDirection, out vhot))
{
baseLight.spotlightDir = vhot.Position;
}
}
}
else if (brush.media == BrList.Brush.Media.Object)
if (propSpotlight != null)
{
// TODO: Handle PropSpotlightAndAmbient
var id = (int)brush.brushInfo;
var propAnimLight = hierarchy.GetProperty<PropAnimLight>(id, "P$AnimLight", false);
var propLight = hierarchy.GetProperty<PropLight>(id, "P$Light", false);
var propLightColor = hierarchy.GetProperty<PropLightColor>(id, "P$LightColo");
var propSpotlight = hierarchy.GetProperty<PropSpotlight>(id, "P$Spotlight");
var propModelname = hierarchy.GetProperty<PropLabel>(id, "P$ModelName");
var rot = Matrix4x4.Identity;
rot *= Matrix4x4.CreateRotationX(float.DegreesToRadians(brush.angle.X));
rot *= Matrix4x4.CreateRotationY(float.DegreesToRadians(brush.angle.Y));
rot *= Matrix4x4.CreateRotationZ(float.DegreesToRadians(brush.angle.Z));
propLightColor ??= new PropLightColor { Hue = 0, Saturation = 0 };
baseLight.spotlight = true;
baseLight.spotlightDir = Vector3.Transform(baseLight.spotlightDir, rot);
baseLight.spotlightInnerAngle = (float)Math.Cos(float.DegreesToRadians(propSpotlight.InnerAngle));
baseLight.spotlightOuterAngle = (float)Math.Cos(float.DegreesToRadians(propSpotlight.OuterAngle));
}
var baseLight = new Light
if (propLight != null)
{
var light = new Light
{
position = brush.position,
spotlightDir = -Vector3.UnitZ,
position = baseLight.position + propLight.Offset,
color = HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propLight.Brightness),
innerRadius = propLight.InnerRadius,
radius = propLight.Radius,
r2 = propLight.Radius * propLight.Radius,
spotlight = baseLight.spotlight,
spotlightDir = baseLight.spotlightDir,
spotlightInnerAngle = baseLight.spotlightInnerAngle,
spotlightOuterAngle = baseLight.spotlightOuterAngle,
};
if (propModelname != null)
if (propLight.Radius == 0)
{
var resName = $"{propModelname.value.ToLower()}.bin";
var modelPath = campaign.GetResourcePath(ResourceType.Object, resName);
if (modelPath != null)
{
// TODO: Handle failing to find model more gracefully
var model = new ModelFile(modelPath);
if (model.TryGetVhot(ModelFile.VhotId.LightPosition, out var vhot))
{
baseLight.position += vhot.Position;
}
if (model.TryGetVhot(ModelFile.VhotId.LightDirection, out vhot))
{
baseLight.spotlightDir = vhot.Position;
}
}
light.radius = float.MaxValue;
light.r2 = float.MaxValue;
}
if (propSpotlight != null)
lights.Add(light);
worldRep.LightingTable.AddLight(new WorldRep.LightTable.LightData
{
var rot = Matrix4x4.Identity;
rot *= Matrix4x4.CreateRotationX(float.DegreesToRadians(brush.angle.X));
rot *= Matrix4x4.CreateRotationY(float.DegreesToRadians(brush.angle.Y));
rot *= Matrix4x4.CreateRotationZ(float.DegreesToRadians(brush.angle.Z));
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,
});
}
baseLight.spotlight = true;
baseLight.spotlightDir = Vector3.Transform(baseLight.spotlightDir, rot);
baseLight.spotlightInnerAngle = (float)Math.Cos(float.DegreesToRadians(propSpotlight.InnerAngle));
baseLight.spotlightOuterAngle = (float)Math.Cos(float.DegreesToRadians(propSpotlight.OuterAngle));
if (propAnimLight != null)
{
var lightIndex = worldRep.LightingTable.LightCount;
propAnimLight.LightTableLightIndex = (ushort)lightIndex;
var light = new Light
{
position = baseLight.position + propAnimLight.Offset,
color = HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propAnimLight.MaxBrightness),
innerRadius = propAnimLight.InnerRadius,
radius = propAnimLight.Radius,
r2 = propAnimLight.Radius * propAnimLight.Radius,
spotlight = baseLight.spotlight,
spotlightDir = baseLight.spotlightDir,
spotlightInnerAngle = baseLight.spotlightInnerAngle,
spotlightOuterAngle = baseLight.spotlightOuterAngle,
anim = true,
animObjId = id,
animLightTableIndex = propAnimLight.LightTableLightIndex,
};
if (propAnimLight.Radius == 0)
{
light.radius = float.MaxValue;
light.r2 = float.MaxValue;
}
if (propLight != null)
lights.Add(light);
worldRep.LightingTable.AddLight(new WorldRep.LightTable.LightData
{
var light = new Light
{
position = baseLight.position + propLight.Offset,
color = HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propLight.Brightness),
innerRadius = propLight.InnerRadius,
radius = propLight.Radius,
r2 = propLight.Radius * propLight.Radius,
spotlight = baseLight.spotlight,
spotlightDir = baseLight.spotlightDir,
spotlightInnerAngle = baseLight.spotlightInnerAngle,
spotlightOuterAngle = baseLight.spotlightOuterAngle,
};
if (propLight.Radius == 0)
{
light.radius = float.MaxValue;
light.r2 = float.MaxValue;
}
lights.Add(light);
}
if (propAnimLight != null)
{
var light = new Light
{
position = baseLight.position + propAnimLight.Offset,
color = HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propAnimLight.MaxBrightness),
innerRadius = propAnimLight.InnerRadius,
radius = propAnimLight.Radius,
r2 = propAnimLight.Radius * propAnimLight.Radius,
spotlight = baseLight.spotlight,
spotlightDir = baseLight.spotlightDir,
spotlightInnerAngle = baseLight.spotlightInnerAngle,
spotlightOuterAngle = baseLight.spotlightOuterAngle,
anim = true,
animLightTableIndex = propAnimLight.LightTableLightIndex,
};
if (propAnimLight.Radius == 0)
{
light.radius = float.MaxValue;
light.r2 = float.MaxValue;
}
lights.Add(light);
}
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)
{
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 options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
var name = ((GamFile)gamFileChunk).fileName;
var name = gamFile.fileName;
var paths = Directory.GetFiles(dir!, name, options);
if (paths.Length > 0)
{
@ -314,6 +413,10 @@ class Program
Parallel.ForEach(wr.Cells, cell =>
{
// Reset cell AnimLight palette
cell.AnimLightCount = 0;
cell.AnimLights.Clear();
var numPolys = cell.PolyCount;
var numRenderPolys = cell.RenderPolyCount;
var numPortalPolys = cell.PortalPolyCount;
@ -335,7 +438,8 @@ class Program
var info = cell.LightList[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)
var topLeft = cell.Vertices[cell.Indices[cellIdxOffset]];
@ -365,23 +469,10 @@ class Program
var layer = 0;
if (light.anim)
{
var paletteIdx = -1;
for (var i = 0; i < cell.AnimLightCount; i++)
{
var id = cell.AnimLights[i];
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);
// 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
@ -451,6 +542,27 @@ class Program
var hit = hitResult && Math.Abs(hitResult.Distance - direction.Length()) < MathUtils.Epsilon;
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);
lightmap.AddLight(layer, x, y, light.color, strength, hdr);
}
@ -510,20 +622,4 @@ class Program
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);
}
}
}
}