Compare commits
No commits in common. "1d32b3ef7b7212841dd8a17c95f0e464aa546822" and "bc01e91dbce607806ef4c3831bf943dff995f5aa" have entirely different histories.
1d32b3ef7b
...
bc01e91dbc
|
@ -1,3 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace KeepersCompound.LGS.Database;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System.IO;
|
||||
|
||||
namespace KeepersCompound.LGS.Database.Chunks;
|
||||
|
||||
class AiConverseChunk : IChunk
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System.IO;
|
||||
|
||||
namespace KeepersCompound.LGS.Database.Chunks;
|
||||
|
||||
class AiRoomDb : IChunk
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace KeepersCompound.LGS.Database.Chunks;
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace KeepersCompound.LGS.Database.Chunks;
|
||||
|
||||
public record LinkId
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
using System.Numerics;
|
||||
|
||||
namespace KeepersCompound.LGS.Database.Chunks;
|
||||
|
|
|
@ -50,7 +50,7 @@ public class WorldRep : IChunk
|
|||
}
|
||||
}
|
||||
|
||||
public class Cell
|
||||
public struct Cell
|
||||
{
|
||||
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 PaddedWidth { get; set; }
|
||||
|
@ -137,7 +137,7 @@ public class WorldRep : IChunk
|
|||
AnimLightBitmask = reader.ReadUInt32();
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
public readonly void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(Bases.Item1);
|
||||
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 Width;
|
||||
|
@ -161,50 +161,44 @@ public class WorldRep : IChunk
|
|||
|
||||
public Lightmap(BinaryReader reader, byte width, byte height, uint bitmask, int bytesPerPixel)
|
||||
{
|
||||
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;
|
||||
var count = 1 + BitOperations.PopCount(bitmask);
|
||||
var length = bytesPerPixel * width * height * count;
|
||||
Pixels = reader.ReadBytes(length);
|
||||
Layers = count;
|
||||
Width = width;
|
||||
Height = height;
|
||||
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)
|
||||
{
|
||||
return Vector4.Zero;
|
||||
}
|
||||
|
||||
var pLayer = Pixels[(int)layer];
|
||||
var idx = x * Bpp + y * Bpp * Width;
|
||||
var idx = 0 + x * Bpp + y * Bpp * Width + layer * Bpp * Width * Height;
|
||||
switch (Bpp)
|
||||
{
|
||||
case 1:
|
||||
var raw1 = pLayer[idx];
|
||||
var raw1 = Pixels[idx];
|
||||
return new Vector4(raw1, raw1, raw1, 255) / 255.0f;
|
||||
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;
|
||||
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:
|
||||
return Vector4.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] AsBytesRgba(int layer)
|
||||
public readonly byte[] AsBytesRgba(int layer)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(layer, 0, nameof(layer));
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(layer, Layers, nameof(layer));
|
||||
|
||||
var pLayer = Pixels[layer];
|
||||
var pIdx = 0;
|
||||
var pIdx = layer * Bpp * Width * Height;
|
||||
var length = 4 * Width * Height;
|
||||
var bytes = new byte[length];
|
||||
for (var i = 0; i < length; i += 4, pIdx += Bpp)
|
||||
|
@ -212,24 +206,24 @@ public class WorldRep : IChunk
|
|||
switch (Bpp)
|
||||
{
|
||||
case 1:
|
||||
var raw1 = pLayer[pIdx];
|
||||
var raw1 = Pixels[pIdx];
|
||||
bytes[i] = raw1;
|
||||
bytes[i + 1] = raw1;
|
||||
bytes[i + 2] = raw1;
|
||||
bytes[i + 3] = 255;
|
||||
break;
|
||||
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 + 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] = pLayer[pIdx + 2];
|
||||
bytes[i + 1] = pLayer[pIdx + 1];
|
||||
bytes[i + 2] = pLayer[pIdx];
|
||||
bytes[i + 3] = pLayer[pIdx + 3];
|
||||
bytes[i] = Pixels[pIdx + 2];
|
||||
bytes[i + 1] = Pixels[pIdx + 1];
|
||||
bytes[i + 2] = Pixels[pIdx];
|
||||
bytes[i + 3] = Pixels[pIdx + 3];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -238,14 +232,13 @@ public class WorldRep : IChunk
|
|||
}
|
||||
|
||||
// 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 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;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
writer.Write(Pixels);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,7 +292,7 @@ public class WorldRep : IChunk
|
|||
public uint IndexCount { get; set; }
|
||||
public byte[] Indices { get; set; }
|
||||
public Plane[] Planes { get; set; }
|
||||
public List<ushort> AnimLights { get; set; }
|
||||
public ushort[] AnimLights { get; set; }
|
||||
public LightmapInfo[] LightList { get; set; }
|
||||
public Lightmap[] Lightmaps { get; set; }
|
||||
public int LightIndexCount { get; set; }
|
||||
|
@ -375,10 +339,10 @@ public class WorldRep : IChunk
|
|||
{
|
||||
Planes[i] = new Plane(reader.ReadVec3(), reader.ReadSingle());
|
||||
}
|
||||
AnimLights = new List<ushort>(AnimLightCount);
|
||||
AnimLights = new ushort[AnimLightCount];
|
||||
for (var i = 0; i < AnimLightCount; i++)
|
||||
{
|
||||
AnimLights.Add(reader.ReadUInt16());
|
||||
AnimLights[i] = reader.ReadUInt16();
|
||||
}
|
||||
LightList = new LightmapInfo[RenderPolyCount];
|
||||
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(PolyCount);
|
||||
|
@ -520,16 +484,16 @@ public class WorldRep : IChunk
|
|||
}
|
||||
}
|
||||
|
||||
public class LightTable
|
||||
public struct LightTable
|
||||
{
|
||||
public struct LightData
|
||||
{
|
||||
public Vector3 Location;
|
||||
public Vector3 Direction;
|
||||
public Vector3 Color;
|
||||
public float InnerAngle; // I'm pretty sure these are the spotlight angles
|
||||
public float OuterAngle;
|
||||
public float Radius;
|
||||
float InnerAngle; // I'm pretty sure these are the spotlight angles
|
||||
float OuterAngle;
|
||||
float Radius;
|
||||
|
||||
public LightData(BinaryReader reader)
|
||||
{
|
||||
|
@ -573,35 +537,34 @@ public class WorldRep : IChunk
|
|||
public int LightCount;
|
||||
public int DynamicLightCount;
|
||||
public int AnimMapCount;
|
||||
public List<LightData> Lights;
|
||||
public LightData[] Lights;
|
||||
public LightData[] ScratchpadLights;
|
||||
public List<AnimCellMap> AnimCellMaps;
|
||||
public AnimCellMap[] AnimCellMaps;
|
||||
|
||||
// TODO: Support olddark
|
||||
public LightTable(BinaryReader reader)
|
||||
{
|
||||
LightCount = reader.ReadInt32();
|
||||
DynamicLightCount = reader.ReadInt32();
|
||||
var totalLightCount = LightCount + DynamicLightCount;
|
||||
Lights = new List<LightData>(totalLightCount);
|
||||
for (var i = 0; i < totalLightCount; i++)
|
||||
Lights = new LightData[LightCount + DynamicLightCount];
|
||||
for (var i = 0; i < Lights.Length; i++)
|
||||
{
|
||||
Lights.Add(new LightData(reader));
|
||||
Lights[i] = new LightData(reader);
|
||||
}
|
||||
ScratchpadLights = new LightData[32];
|
||||
for (var i = 0; i < 32; i++)
|
||||
for (var i = 0; i < ScratchpadLights.Length; i++)
|
||||
{
|
||||
ScratchpadLights[i] = new LightData(reader);
|
||||
}
|
||||
AnimMapCount = reader.ReadInt32();
|
||||
AnimCellMaps = new List<AnimCellMap>(AnimMapCount);
|
||||
for (var i = 0; i < AnimMapCount; i++)
|
||||
AnimCellMaps = new AnimCellMap[AnimMapCount];
|
||||
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(DynamicLightCount);
|
||||
|
@ -619,33 +582,6 @@ 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; }
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using KeepersCompound.LGS.Database.Chunks;
|
||||
|
||||
|
@ -129,17 +133,6 @@ 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
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using KeepersCompound.LGS.Database.Chunks;
|
||||
|
||||
namespace KeepersCompound.LGS.Database;
|
||||
|
@ -35,17 +38,19 @@ public class ObjectHierarchy
|
|||
|
||||
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))
|
||||
{
|
||||
gamChunk.Merge(chunk);
|
||||
return gamChunk;
|
||||
}
|
||||
return chunk;
|
||||
throw new ArgumentException($"No chunk with name ({name}) found", nameof(name));
|
||||
}
|
||||
|
||||
// Add parentages
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System.IO;
|
||||
|
||||
namespace KeepersCompound.LGS.Database;
|
||||
|
||||
public struct Version
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
|
||||
namespace KeepersCompound.LGS;
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using KeepersCompound.LGS;
|
||||
using KeepersCompound.LGS.Database;
|
||||
|
@ -23,7 +24,6 @@ class Program
|
|||
public float spotlightOuterAngle;
|
||||
|
||||
public bool anim;
|
||||
public int animObjId;
|
||||
public int animLightTableIndex;
|
||||
}
|
||||
|
||||
|
@ -57,9 +57,12 @@ 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.TryGetChunk<WorldRep>("WREXT", out var worldRep))
|
||||
if (!mis.Chunks.TryGetValue("WREXT", out var wrRaw))
|
||||
return;
|
||||
var worldRep = (WorldRep)wrRaw;
|
||||
var scene = Timing.TimeStage("Build Scene", () =>
|
||||
{
|
||||
var rt = new Raytracer();
|
||||
|
@ -69,12 +72,10 @@ class Program
|
|||
});
|
||||
|
||||
// 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;
|
||||
var ambient = rendParams.ambientLight * 255;
|
||||
var lights = Timing.TimeStage("Gather Lights", () => BuildLightList(mis, hierarchy, campaign));
|
||||
var ambient = ((RendParams)rendParamsRaw).ambientLight * 255;
|
||||
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);
|
||||
|
@ -108,226 +109,126 @@ class Program
|
|||
};
|
||||
}
|
||||
|
||||
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)
|
||||
// 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)
|
||||
{
|
||||
var lights = new List<Light>();
|
||||
|
||||
// Get the chunks we need
|
||||
if (!mis.TryGetChunk<WorldRep>("WREXT", out var worldRep) ||
|
||||
!mis.TryGetChunk<BrList>("BRLIST", out var brList))
|
||||
if (mis.Chunks.TryGetValue("BRLIST", out var brListRaw))
|
||||
{
|
||||
return lights;
|
||||
}
|
||||
|
||||
worldRep.LightingTable.Reset();
|
||||
|
||||
foreach (var brush in brList.Brushes)
|
||||
{
|
||||
if (brush.media == BrList.Brush.Media.Light)
|
||||
var brList = (BrList)brListRaw;
|
||||
foreach (var brush in brList.Brushes)
|
||||
{
|
||||
// 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
|
||||
if (brush.media == BrList.Brush.Media.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)
|
||||
var sz = brush.size;
|
||||
lights.Add(new Light
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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,
|
||||
position = brush.position,
|
||||
color = HsbToRgb(sz.Y, sz.Z, Math.Min(sz.X, 255.0f)),
|
||||
radius = float.MaxValue,
|
||||
r2 = float.MaxValue,
|
||||
});
|
||||
}
|
||||
|
||||
if (propAnimLight != null)
|
||||
else if (brush.media == BrList.Brush.Media.Object)
|
||||
{
|
||||
var lightIndex = worldRep.LightingTable.LightCount;
|
||||
propAnimLight.LightTableLightIndex = (ushort)lightIndex;
|
||||
// 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 light = new Light
|
||||
propLightColor ??= new PropLightColor { Hue = 0, Saturation = 0 };
|
||||
|
||||
var baseLight = 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,
|
||||
position = brush.position,
|
||||
spotlightDir = -Vector3.UnitZ,
|
||||
};
|
||||
if (propAnimLight.Radius == 0)
|
||||
|
||||
if (propModelname != null)
|
||||
{
|
||||
light.radius = float.MaxValue;
|
||||
light.r2 = float.MaxValue;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
lights.Add(light);
|
||||
worldRep.LightingTable.AddLight(new WorldRep.LightTable.LightData
|
||||
if (propSpotlight != null)
|
||||
{
|
||||
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,
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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 options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
|
||||
var name = gamFile.fileName;
|
||||
var name = ((GamFile)gamFileChunk).fileName;
|
||||
var paths = Directory.GetFiles(dir!, name, options);
|
||||
if (paths.Length > 0)
|
||||
{
|
||||
|
@ -413,10 +314,6 @@ 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;
|
||||
|
@ -438,8 +335,7 @@ class Program
|
|||
var info = cell.LightList[polyIdx];
|
||||
var lightmap = cell.Lightmaps[polyIdx];
|
||||
|
||||
info.AnimLightBitmask = 0;
|
||||
lightmap.Reset(ambientLight, hdr);
|
||||
ResetLightmap(ambientLight, lightmap, 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]];
|
||||
|
@ -469,10 +365,23 @@ class Program
|
|||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
|
@ -622,4 +510,20 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue