Compare commits

..

No commits in common. "1d32b3ef7b7212841dd8a17c95f0e464aa546822" and "bc01e91dbce607806ef4c3831bf943dff995f5aa" have entirely different histories.

13 changed files with 235 additions and 374 deletions

View File

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

View File

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

View File

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

View File

@ -1,4 +1,8 @@
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,3 +1,7 @@
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,3 +1,4 @@
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 class Cell public struct Cell
{ {
public struct Poly public struct Poly
{ {
@ -116,7 +116,7 @@ public class WorldRep : IChunk
} }
} }
public class LightmapInfo public struct 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 void Write(BinaryWriter writer) public readonly 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 class Lightmap public struct Lightmap
{ {
public List<byte[]> Pixels { get; set; } public byte[] Pixels { get; set; }
public int Layers; public int Layers;
public int Width; public int Width;
@ -161,50 +161,44 @@ 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 layers = 1 + BitOperations.PopCount(bitmask); var count = 1 + BitOperations.PopCount(bitmask);
var length = bytesPerPixel * width * height; var length = bytesPerPixel * width * height * count;
Pixels = new List<byte[]>(); Pixels = reader.ReadBytes(length);
for (var i = 0; i < layers; i++) Layers = count;
{
Pixels.Add(reader.ReadBytes(length));
}
Layers = layers;
Width = width; Width = width;
Height = height; Height = height;
Bpp = bytesPerPixel; Bpp = bytesPerPixel;
} }
public Vector4 GetPixel(uint layer, uint x, uint y) public readonly 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 pLayer = Pixels[(int)layer]; var idx = 0 + x * Bpp + y * Bpp * Width + layer * Bpp * Width * Height;
var idx = x * Bpp + y * Bpp * Width;
switch (Bpp) switch (Bpp)
{ {
case 1: case 1:
var raw1 = pLayer[idx]; var raw1 = Pixels[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 = pLayer[idx] + (pLayer[idx + 1] << 8); var raw2 = Pixels[idx] + (Pixels[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(pLayer[idx + 2], pLayer[idx + 1], pLayer[idx], pLayer[idx + 3]) / 255.0f; return new Vector4(Pixels[idx + 2], Pixels[idx + 1], Pixels[idx], Pixels[idx + 3]) / 255.0f;
default: default:
return Vector4.Zero; return Vector4.Zero;
} }
} }
public byte[] AsBytesRgba(int layer) public readonly 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 pLayer = Pixels[layer]; var pIdx = layer * Bpp * Width * Height;
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)
@ -212,24 +206,24 @@ public class WorldRep : IChunk
switch (Bpp) switch (Bpp)
{ {
case 1: case 1:
var raw1 = pLayer[pIdx]; var raw1 = Pixels[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 = pLayer[pIdx] + (pLayer[pIdx + 1] << 8); var raw2 = Pixels[pIdx] + (Pixels[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] = pLayer[pIdx + 2]; bytes[i] = Pixels[pIdx + 2];
bytes[i + 1] = pLayer[pIdx + 1]; bytes[i + 1] = Pixels[pIdx + 1];
bytes[i + 2] = pLayer[pIdx]; bytes[i + 2] = Pixels[pIdx];
bytes[i + 3] = pLayer[pIdx + 3]; bytes[i + 3] = Pixels[pIdx + 3];
break; break;
} }
} }
@ -238,14 +232,13 @@ 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, float r, float g, float b) public void AddLight(int layer, int x, int y, byte r, byte g, byte b)
{ {
var idx = (x + y * Width) * Bpp; var idx = (x + y * Width + layer * Width * Height) * Bpp;
var pLayer = Pixels[layer]; Pixels[idx] = (byte)Math.Min(Pixels[idx] + b, 255);
pLayer[idx] = (byte)Math.Clamp(pLayer[idx] + r, 0, 255); Pixels[idx + 1] = (byte)Math.Min(Pixels[idx + 1] + g, 255);
pLayer[idx + 1] = (byte)Math.Clamp(pLayer[idx + 1] + g, 0, 255); Pixels[idx + 2] = (byte)Math.Min(Pixels[idx + 2] + r, 255);
pLayer[idx + 2] = (byte)Math.Clamp(pLayer[idx + 2] + b, 0, 255); Pixels[idx + 3] = 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)
@ -271,41 +264,12 @@ public class WorldRep : IChunk
c /= ratio; c /= ratio;
} }
AddLight(layer, x, y, c.Z, c.Y, c.X); AddLight(layer, x, y, (byte)c.X, (byte)c.Y, (byte)c.Z);
} }
public void Reset(Vector3 ambientLight, bool hdr) public readonly void Write(BinaryWriter writer)
{ {
Layers = 0; writer.Write(Pixels);
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);
}
} }
} }
@ -328,7 +292,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 List<ushort> AnimLights { get; set; } public 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; }
@ -375,10 +339,10 @@ public class WorldRep : IChunk
{ {
Planes[i] = new Plane(reader.ReadVec3(), reader.ReadSingle()); Planes[i] = new Plane(reader.ReadVec3(), reader.ReadSingle());
} }
AnimLights = new List<ushort>(AnimLightCount); AnimLights = new ushort[AnimLightCount];
for (var i = 0; i < AnimLightCount; i++) for (var i = 0; i < AnimLightCount; i++)
{ {
AnimLights.Add(reader.ReadUInt16()); AnimLights[i] = reader.ReadUInt16();
} }
LightList = new LightmapInfo[RenderPolyCount]; LightList = new LightmapInfo[RenderPolyCount];
for (var i = 0; i < RenderPolyCount; i++) for (var i = 0; i < RenderPolyCount; i++)
@ -399,7 +363,7 @@ public class WorldRep : IChunk
} }
} }
public void Write(BinaryWriter writer) public readonly void Write(BinaryWriter writer)
{ {
writer.Write(VertexCount); writer.Write(VertexCount);
writer.Write(PolyCount); writer.Write(PolyCount);
@ -520,16 +484,16 @@ public class WorldRep : IChunk
} }
} }
public class LightTable public struct 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;
public float InnerAngle; // I'm pretty sure these are the spotlight angles float InnerAngle; // I'm pretty sure these are the spotlight angles
public float OuterAngle; float OuterAngle;
public float Radius; float Radius;
public LightData(BinaryReader reader) public LightData(BinaryReader reader)
{ {
@ -573,35 +537,34 @@ public class WorldRep : IChunk
public int LightCount; public int LightCount;
public int DynamicLightCount; public int DynamicLightCount;
public int AnimMapCount; public int AnimMapCount;
public List<LightData> Lights; public LightData[] Lights;
public LightData[] ScratchpadLights; public LightData[] ScratchpadLights;
public List<AnimCellMap> AnimCellMaps; public 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();
var totalLightCount = LightCount + DynamicLightCount; Lights = new LightData[LightCount + DynamicLightCount];
Lights = new List<LightData>(totalLightCount); for (var i = 0; i < Lights.Length; i++)
for (var i = 0; i < totalLightCount; i++)
{ {
Lights.Add(new LightData(reader)); Lights[i] = new LightData(reader);
} }
ScratchpadLights = new LightData[32]; ScratchpadLights = new LightData[32];
for (var i = 0; i < 32; i++) for (var i = 0; i < ScratchpadLights.Length; i++)
{ {
ScratchpadLights[i] = new LightData(reader); ScratchpadLights[i] = new LightData(reader);
} }
AnimMapCount = reader.ReadInt32(); AnimMapCount = reader.ReadInt32();
AnimCellMaps = new List<AnimCellMap>(AnimMapCount); AnimCellMaps = new AnimCellMap[AnimMapCount];
for (var i = 0; i < AnimMapCount; i++) for (var i = 0; i < AnimCellMaps.Length; i++)
{ {
AnimCellMaps.Add(new AnimCellMap(reader)); AnimCellMaps[i] = new AnimCellMap(reader);
} }
} }
public void Write(BinaryWriter writer) public readonly void Write(BinaryWriter writer)
{ {
writer.Write(LightCount); writer.Write(LightCount);
writer.Write(DynamicLightCount); writer.Write(DynamicLightCount);
@ -619,33 +582,6 @@ 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,3 +1,7 @@
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;
@ -129,17 +133,6 @@ 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,3 +1,6 @@
using System;
using System.Collections.Generic;
using KeepersCompound.LGS.Database.Chunks; using KeepersCompound.LGS.Database.Chunks;
namespace KeepersCompound.LGS.Database; namespace KeepersCompound.LGS.Database;
@ -35,17 +38,19 @@ public class ObjectHierarchy
T GetMergedChunk<T>(string name) where T : IMergable T GetMergedChunk<T>(string name) where T : IMergable
{ {
if (!db.TryGetChunk<T>(name, out var chunk)) if (db.Chunks.TryGetValue(name, out var rawChunk))
{ {
throw new ArgumentException($"No chunk with name ({name}) found", nameof(name)); 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;
} }
if (gam != null && gam.TryGetChunk<T>(name, out var gamChunk)) throw new ArgumentException($"No chunk with name ({name}) found", nameof(name));
{
gamChunk.Merge(chunk);
return gamChunk;
}
return chunk;
} }
// Add parentages // Add parentages

View File

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

View File

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

View File

@ -1,4 +1,8 @@
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,3 +1,4 @@
using System.Diagnostics;
using System.Numerics; using System.Numerics;
using KeepersCompound.LGS; using KeepersCompound.LGS;
using KeepersCompound.LGS.Database; using KeepersCompound.LGS.Database;
@ -23,7 +24,6 @@ class Program
public float spotlightOuterAngle; public float spotlightOuterAngle;
public bool anim; public bool anim;
public int animObjId;
public int animLightTableIndex; public int animLightTableIndex;
} }
@ -57,9 +57,12 @@ 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.TryGetChunk<WorldRep>("WREXT", out var worldRep)) if (!mis.Chunks.TryGetValue("WREXT", out var wrRaw))
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();
@ -69,12 +72,10 @@ 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.TryGetChunk<RendParams>("RENDPARAMS", out var rendParams)) if (!mis.Chunks.TryGetValue("RENDPARAMS", out var rendParamsRaw))
return; return;
var ambient = rendParams.ambientLight * 255; var ambient = ((RendParams)rendParamsRaw).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);
@ -108,226 +109,126 @@ class Program
}; };
} }
private static void SetAnimLightCellMaps( // Get list of brush lights, and object lights (ignore anim lights for now)
DbFile mis, private static List<Light> BuildLightList(DbFile mis, ObjectHierarchy hierarchy, ResourcePathManager.CampaignResources campaign)
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>();
// Get the chunks we need if (mis.Chunks.TryGetValue("BRLIST", out var brListRaw))
if (!mis.TryGetChunk<WorldRep>("WREXT", out var worldRep) ||
!mis.TryGetChunk<BrList>("BRLIST", out var brList))
{ {
return lights; var brList = (BrList)brListRaw;
} foreach (var brush in brList.Brushes)
worldRep.LightingTable.Reset();
foreach (var brush in brList.Brushes)
{
if (brush.media == BrList.Brush.Media.Light)
{ {
// For some reason the light table index on brush lights is 1 indexed if (brush.media == BrList.Brush.Media.Light)
brush.brushInfo = (uint)worldRep.LightingTable.LightCount + 1;
var sz = brush.size;
var light = new Light
{ {
position = brush.position, var sz = brush.size;
color = HsbToRgb(sz.Y, sz.Z, Math.Min(sz.X, 255.0f)), lights.Add(new Light
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)
{ {
// TODO: Handle failing to find model more gracefully position = brush.position,
var model = new ModelFile(modelPath); color = HsbToRgb(sz.Y, sz.Z, Math.Min(sz.X, 255.0f)),
if (model.TryGetVhot(ModelFile.VhotId.LightPosition, out var vhot)) radius = float.MaxValue,
{ r2 = float.MaxValue,
baseLight.position += vhot.Position;
}
if (model.TryGetVhot(ModelFile.VhotId.LightDirection, out vhot))
{
baseLight.spotlightDir = vhot.Position;
}
}
}
if (propSpotlight != null)
{
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));
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 (propLight != null)
{
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);
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,
}); });
} }
else if (brush.media == BrList.Brush.Media.Object)
if (propAnimLight != null)
{ {
var lightIndex = worldRep.LightingTable.LightCount; // TODO: Handle PropSpotlightAndAmbient
propAnimLight.LightTableLightIndex = (ushort)lightIndex; 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 light = new Light propLightColor ??= new PropLightColor { Hue = 0, Saturation = 0 };
var baseLight = new Light
{ {
position = baseLight.position + propAnimLight.Offset, position = brush.position,
color = HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propAnimLight.MaxBrightness), spotlightDir = -Vector3.UnitZ,
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)
if (propModelname != null)
{ {
light.radius = float.MaxValue; var resName = $"{propModelname.value.ToLower()}.bin";
light.r2 = float.MaxValue; 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;
}
}
} }
lights.Add(light); if (propSpotlight != null)
worldRep.LightingTable.AddLight(new WorldRep.LightTable.LightData
{ {
Location = light.position, var rot = Matrix4x4.Identity;
Direction = light.spotlightDir, rot *= Matrix4x4.CreateRotationX(float.DegreesToRadians(brush.angle.X));
Color = light.color / 32.0f, // TODO: This is based on light_scale config var rot *= Matrix4x4.CreateRotationY(float.DegreesToRadians(brush.angle.Y));
InnerAngle = light.spotlightInnerAngle, rot *= Matrix4x4.CreateRotationZ(float.DegreesToRadians(brush.angle.Z));
OuterAngle = light.spotlightOuterAngle,
Radius = propAnimLight.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 (propLight != null)
{
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);
}
} }
} }
} }
@ -338,11 +239,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.TryGetChunk<GamFile>("GAM_FILE", out var gamFile)) if (misFile.Chunks.TryGetValue("GAM_FILE", out var gamFileChunk))
{ {
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.fileName; var name = ((GamFile)gamFileChunk).fileName;
var paths = Directory.GetFiles(dir!, name, options); var paths = Directory.GetFiles(dir!, name, options);
if (paths.Length > 0) if (paths.Length > 0)
{ {
@ -413,10 +314,6 @@ 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;
@ -438,8 +335,7 @@ class Program
var info = cell.LightList[polyIdx]; var info = cell.LightList[polyIdx];
var lightmap = cell.Lightmaps[polyIdx]; var lightmap = cell.Lightmaps[polyIdx];
info.AnimLightBitmask = 0; ResetLightmap(ambientLight, lightmap, hdr);
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]];
@ -469,10 +365,23 @@ class Program
var layer = 0; var layer = 0;
if (light.anim) if (light.anim)
{ {
// Because we're building the AnimLightBitmask in this loop we var paletteIdx = -1;
// know there aren't any layers set above us. So the target layer for (var i = 0; i < cell.AnimLightCount; i++)
// is just the number of set bits + 1. {
layer = BitOperations.PopCount(info.AnimLightBitmask) + 1; 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);
} }
// Check if plane normal is facing towards the light // Check if plane normal is facing towards the light
@ -542,27 +451,6 @@ 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);
} }
@ -622,4 +510,20 @@ 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);
}
}
}
} }