Compare commits
11 Commits
d7fcf7a2d8
...
7ec7f71239
Author | SHA1 | Date |
---|---|---|
Jarrod Doyle | 7ec7f71239 | |
Jarrod Doyle | 47d95307b5 | |
Jarrod Doyle | 69b29c15f6 | |
Jarrod Doyle | 93410a46d5 | |
Jarrod Doyle | 6efceff852 | |
Jarrod Doyle | 067cd2cc00 | |
Jarrod Doyle | b57d15f6f0 | |
Jarrod Doyle | bd72554b9a | |
Jarrod Doyle | 46fe99bbfc | |
Jarrod Doyle | 8b7ffcd7a3 | |
Jarrod Doyle | bcc60eff52 |
|
@ -330,6 +330,112 @@ public class PropLight : Property
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PropAnimLight : Property
|
||||||
|
{
|
||||||
|
public enum AnimMode
|
||||||
|
{
|
||||||
|
FlipMinMax,
|
||||||
|
SlideSmoothly,
|
||||||
|
Random,
|
||||||
|
MinBrightness,
|
||||||
|
MaxBrightness,
|
||||||
|
ZeroBrightness,
|
||||||
|
SmoothlyBrighten,
|
||||||
|
SmoothlyDim,
|
||||||
|
RandomButCoherent,
|
||||||
|
FlickerMinMax,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard light props
|
||||||
|
public float Brightness;
|
||||||
|
public Vector3 Offset;
|
||||||
|
public float Radius;
|
||||||
|
public float InnerRadius;
|
||||||
|
public bool QuadLit;
|
||||||
|
public bool Dynamic;
|
||||||
|
|
||||||
|
// Animation
|
||||||
|
public AnimMode Mode;
|
||||||
|
public int MsToBrighten;
|
||||||
|
public int MsToDim;
|
||||||
|
public float MinBrightness;
|
||||||
|
public float MaxBrightness;
|
||||||
|
|
||||||
|
// Start state
|
||||||
|
public float CurrentBrightness;
|
||||||
|
public bool Rising;
|
||||||
|
public int Timer;
|
||||||
|
public bool Inactive;
|
||||||
|
|
||||||
|
// World rep info
|
||||||
|
public bool Refresh; // Not relevant to us. It's used to tell dromed it needs to relight
|
||||||
|
public ushort LightTableMapIndex;
|
||||||
|
public ushort LightTableLightIndex;
|
||||||
|
public ushort CellsReached;
|
||||||
|
|
||||||
|
private int _unknown;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
Brightness = reader.ReadSingle();
|
||||||
|
Offset = reader.ReadVec3();
|
||||||
|
Refresh = reader.ReadBoolean();
|
||||||
|
reader.ReadBytes(3);
|
||||||
|
LightTableMapIndex = reader.ReadUInt16();
|
||||||
|
CellsReached = reader.ReadUInt16();
|
||||||
|
LightTableLightIndex = reader.ReadUInt16();
|
||||||
|
Mode = (AnimMode)reader.ReadUInt16();
|
||||||
|
MsToBrighten = reader.ReadInt32();
|
||||||
|
MsToDim = reader.ReadInt32();
|
||||||
|
MinBrightness = reader.ReadSingle();
|
||||||
|
MaxBrightness = reader.ReadSingle();
|
||||||
|
CurrentBrightness = reader.ReadSingle();
|
||||||
|
Rising = reader.ReadBoolean();
|
||||||
|
reader.ReadBytes(3);
|
||||||
|
Timer = reader.ReadInt32();
|
||||||
|
Inactive = reader.ReadBoolean();
|
||||||
|
reader.ReadBytes(3);
|
||||||
|
Radius = reader.ReadSingle();
|
||||||
|
_unknown = reader.ReadInt32();
|
||||||
|
QuadLit = reader.ReadBoolean();
|
||||||
|
reader.ReadBytes(3);
|
||||||
|
InnerRadius = reader.ReadSingle();
|
||||||
|
Dynamic = reader.ReadBoolean();
|
||||||
|
reader.ReadBytes(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
base.Write(writer);
|
||||||
|
writer.Write(Brightness);
|
||||||
|
writer.WriteVec3(Offset);
|
||||||
|
writer.Write(Refresh);
|
||||||
|
writer.Write(new byte[3]);
|
||||||
|
writer.Write(LightTableMapIndex);
|
||||||
|
writer.Write(CellsReached);
|
||||||
|
writer.Write(LightTableLightIndex);
|
||||||
|
writer.Write((ushort)Mode);
|
||||||
|
writer.Write(MsToBrighten);
|
||||||
|
writer.Write(MsToDim);
|
||||||
|
writer.Write(MinBrightness);
|
||||||
|
writer.Write(MaxBrightness);
|
||||||
|
writer.Write(CurrentBrightness);
|
||||||
|
writer.Write(Rising);
|
||||||
|
writer.Write(new byte[3]);
|
||||||
|
writer.Write(Timer);
|
||||||
|
writer.Write(Inactive);
|
||||||
|
writer.Write(new byte[3]);
|
||||||
|
writer.Write(Radius);
|
||||||
|
writer.Write(_unknown);
|
||||||
|
writer.Write(QuadLit);
|
||||||
|
writer.Write(new byte[3]);
|
||||||
|
writer.Write(InnerRadius);
|
||||||
|
writer.Write(Dynamic);
|
||||||
|
writer.Write(new byte[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class PropLightColor : Property
|
public class PropLightColor : Property
|
||||||
{
|
{
|
||||||
public float Hue;
|
public float Hue;
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace KeepersCompound.LGS.Database.Chunks;
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
@ -419,9 +417,179 @@ public class WorldRep : IChunk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct BspTree
|
||||||
|
{
|
||||||
|
public struct Node
|
||||||
|
{
|
||||||
|
int parentIndex; // TODO: Split the flags out of this
|
||||||
|
int cellId;
|
||||||
|
int planeId;
|
||||||
|
uint insideIndex;
|
||||||
|
uint outsideIndex;
|
||||||
|
|
||||||
|
public Node(BinaryReader reader)
|
||||||
|
{
|
||||||
|
parentIndex = reader.ReadInt32();
|
||||||
|
cellId = reader.ReadInt32();
|
||||||
|
planeId = reader.ReadInt32();
|
||||||
|
insideIndex = reader.ReadUInt32();
|
||||||
|
outsideIndex = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(parentIndex);
|
||||||
|
writer.Write(cellId);
|
||||||
|
writer.Write(planeId);
|
||||||
|
writer.Write(insideIndex);
|
||||||
|
writer.Write(outsideIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint PlaneCount;
|
||||||
|
public uint NodeCount;
|
||||||
|
public Plane[] Planes;
|
||||||
|
public Node[] Nodes;
|
||||||
|
|
||||||
|
public BspTree(BinaryReader reader)
|
||||||
|
{
|
||||||
|
PlaneCount = reader.ReadUInt32();
|
||||||
|
Planes = new Plane[PlaneCount];
|
||||||
|
for (var i = 0; i < PlaneCount; i++)
|
||||||
|
{
|
||||||
|
Planes[i] = new Plane(reader.ReadVec3(), reader.ReadSingle());
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeCount = reader.ReadUInt32();
|
||||||
|
Nodes = new Node[NodeCount];
|
||||||
|
for (var i = 0; i < NodeCount; i++)
|
||||||
|
{
|
||||||
|
Nodes[i] = new Node(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(PlaneCount);
|
||||||
|
foreach (var plane in Planes)
|
||||||
|
{
|
||||||
|
writer.WriteVec3(plane.Normal);
|
||||||
|
writer.Write(plane.D);
|
||||||
|
}
|
||||||
|
writer.Write(NodeCount);
|
||||||
|
foreach (var node in Nodes)
|
||||||
|
{
|
||||||
|
node.Write(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct 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 LightData(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Location = reader.ReadVec3();
|
||||||
|
Direction = reader.ReadVec3();
|
||||||
|
Color = reader.ReadVec3();
|
||||||
|
InnerAngle = reader.ReadSingle();
|
||||||
|
OuterAngle = reader.ReadSingle();
|
||||||
|
Radius = reader.ReadSingle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteVec3(Location);
|
||||||
|
writer.WriteVec3(Direction);
|
||||||
|
writer.WriteVec3(Color);
|
||||||
|
writer.Write(InnerAngle);
|
||||||
|
writer.Write(OuterAngle);
|
||||||
|
writer.Write(Radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AnimCellMap
|
||||||
|
{
|
||||||
|
public ushort CellIndex;
|
||||||
|
public ushort LightIndex;
|
||||||
|
|
||||||
|
public AnimCellMap(BinaryReader reader)
|
||||||
|
{
|
||||||
|
CellIndex = reader.ReadUInt16();
|
||||||
|
LightIndex = reader.ReadUInt16();
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(CellIndex);
|
||||||
|
writer.Write(LightIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int LightCount;
|
||||||
|
public int DynamicLightCount;
|
||||||
|
public int AnimMapCount;
|
||||||
|
public LightData[] Lights;
|
||||||
|
public LightData[] ScratchpadLights;
|
||||||
|
public 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++)
|
||||||
|
{
|
||||||
|
Lights[i] = new LightData(reader);
|
||||||
|
}
|
||||||
|
ScratchpadLights = new LightData[32];
|
||||||
|
for (var i = 0; i < ScratchpadLights.Length; i++)
|
||||||
|
{
|
||||||
|
ScratchpadLights[i] = new LightData(reader);
|
||||||
|
}
|
||||||
|
AnimMapCount = reader.ReadInt32();
|
||||||
|
AnimCellMaps = new AnimCellMap[AnimMapCount];
|
||||||
|
for (var i = 0; i < AnimCellMaps.Length; i++)
|
||||||
|
{
|
||||||
|
AnimCellMaps[i] = new AnimCellMap(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(LightCount);
|
||||||
|
writer.Write(DynamicLightCount);
|
||||||
|
foreach (var light in Lights)
|
||||||
|
{
|
||||||
|
light.Write(writer);
|
||||||
|
}
|
||||||
|
foreach (var light in ScratchpadLights)
|
||||||
|
{
|
||||||
|
light.Write(writer);
|
||||||
|
}
|
||||||
|
writer.Write(AnimMapCount);
|
||||||
|
foreach (var map in AnimCellMaps)
|
||||||
|
{
|
||||||
|
map.Write(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ChunkHeader Header { get; set; }
|
public ChunkHeader Header { get; set; }
|
||||||
public WrHeader DataHeader { get; set; }
|
public WrHeader DataHeader { get; set; }
|
||||||
public Cell[] Cells { get; set; }
|
public Cell[] Cells { get; set; }
|
||||||
|
public BspTree Bsp { get; set; }
|
||||||
|
public LightTable LightingTable { get; set; }
|
||||||
|
private byte[] _unknown;
|
||||||
private byte[] _unreadData;
|
private byte[] _unreadData;
|
||||||
|
|
||||||
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
@ -435,6 +603,12 @@ public class WorldRep : IChunk
|
||||||
Cells[i] = new Cell(reader, bpp);
|
Cells[i] = new Cell(reader, bpp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bsp = new BspTree(reader);
|
||||||
|
|
||||||
|
// TODO: Work out what this is
|
||||||
|
_unknown = reader.ReadBytes(Cells.Length);
|
||||||
|
LightingTable = new LightTable(reader);
|
||||||
|
|
||||||
// TODO: All the other info lol
|
// TODO: All the other info lol
|
||||||
var length = entry.Offset + entry.Size + 24 - reader.BaseStream.Position;
|
var length = entry.Offset + entry.Size + 24 - reader.BaseStream.Position;
|
||||||
_unreadData = reader.ReadBytes((int)length);
|
_unreadData = reader.ReadBytes((int)length);
|
||||||
|
@ -447,6 +621,9 @@ public class WorldRep : IChunk
|
||||||
{
|
{
|
||||||
cell.Write(writer);
|
cell.Write(writer);
|
||||||
}
|
}
|
||||||
|
Bsp.Write(writer);
|
||||||
|
writer.Write(_unknown);
|
||||||
|
LightingTable.Write(writer);
|
||||||
writer.Write(_unreadData);
|
writer.Write(_unreadData);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -32,13 +32,13 @@ public class DbFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct TableOfContents
|
public struct TableOfContents
|
||||||
{
|
{
|
||||||
public readonly struct Entry
|
public struct Entry
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
public string Name;
|
||||||
public uint Offset { get; }
|
public uint Offset;
|
||||||
public uint Size { get; }
|
public uint Size;
|
||||||
|
|
||||||
public Entry(BinaryReader reader)
|
public Entry(BinaryReader reader)
|
||||||
{
|
{
|
||||||
|
@ -47,10 +47,9 @@ public class DbFile
|
||||||
Size = reader.ReadUInt32();
|
Size = reader.ReadUInt32();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override readonly string ToString()
|
||||||
{
|
{
|
||||||
// return $"Name: {Name}, Offset: {O}"
|
return $"Name: {Name}, Offset: {Offset}, Size: {Size}";
|
||||||
return base.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly void Write(BinaryWriter writer)
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
@ -110,18 +109,28 @@ public class DbFile
|
||||||
|
|
||||||
public void Save(string filename)
|
public void Save(string filename)
|
||||||
{
|
{
|
||||||
// !HACK: Right now we don't need to adjust TOC offset or anything because we're only
|
|
||||||
// overwriting data, not writing new lengths of data
|
|
||||||
|
|
||||||
using var stream = File.Open(filename, FileMode.Create);
|
using var stream = File.Open(filename, FileMode.Create);
|
||||||
using var writer = new BinaryWriter(stream, Encoding.UTF8, false);
|
using var writer = new BinaryWriter(stream, Encoding.UTF8, false);
|
||||||
|
|
||||||
Header.Write(writer);
|
Header.Write(writer);
|
||||||
foreach (var (name, chunk) in Chunks)
|
for (var i = 0; i < Toc.ItemCount; i++)
|
||||||
{
|
{
|
||||||
|
var item = Toc.Items[i];
|
||||||
|
var pos = stream.Position;
|
||||||
|
|
||||||
|
var chunk = Chunks[item.Name];
|
||||||
chunk.Write(writer);
|
chunk.Write(writer);
|
||||||
|
var size = stream.Position - pos - 24;
|
||||||
|
|
||||||
|
item.Offset = (uint)pos;
|
||||||
|
item.Size = (uint)size;
|
||||||
|
Toc.Items[i] = item;
|
||||||
}
|
}
|
||||||
|
var tocOffset = (uint)stream.Position;
|
||||||
Toc.Write(writer);
|
Toc.Write(writer);
|
||||||
|
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
writer.Write(tocOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IChunk NewChunk(string entryName)
|
private static IChunk NewChunk(string entryName)
|
||||||
|
@ -143,6 +152,7 @@ public class DbFile
|
||||||
"P$OTxtRepr2" => new PropertyChunk<PropString>(),
|
"P$OTxtRepr2" => new PropertyChunk<PropString>(),
|
||||||
"P$OTxtRepr3" => new PropertyChunk<PropString>(),
|
"P$OTxtRepr3" => new PropertyChunk<PropString>(),
|
||||||
"P$Light" => new PropertyChunk<PropLight>(),
|
"P$Light" => new PropertyChunk<PropLight>(),
|
||||||
|
"P$AnimLight" => new PropertyChunk<PropAnimLight>(),
|
||||||
"P$LightColo" => new PropertyChunk<PropLightColor>(),
|
"P$LightColo" => new PropertyChunk<PropLightColor>(),
|
||||||
"P$Spotlight" => new PropertyChunk<PropSpotlight>(),
|
"P$Spotlight" => new PropertyChunk<PropSpotlight>(),
|
||||||
"P$RenderAlp" => new PropertyChunk<PropFloat>(),
|
"P$RenderAlp" => new PropertyChunk<PropFloat>(),
|
||||||
|
|
|
@ -101,11 +101,13 @@ public class ObjectHierarchy
|
||||||
AddProp<PropString>("P$OTxtRepr3");
|
AddProp<PropString>("P$OTxtRepr3");
|
||||||
AddProp<PropFloat>("P$RenderAlp");
|
AddProp<PropFloat>("P$RenderAlp");
|
||||||
AddProp<PropLight>("P$Light");
|
AddProp<PropLight>("P$Light");
|
||||||
|
AddProp<PropAnimLight>("P$AnimLight");
|
||||||
AddProp<PropLightColor>("P$LightColo");
|
AddProp<PropLightColor>("P$LightColo");
|
||||||
AddProp<PropSpotlight>("P$Spotlight");
|
AddProp<PropSpotlight>("P$Spotlight");
|
||||||
}
|
}
|
||||||
|
|
||||||
public T GetProperty<T>(int objectId, string propName) where T : Property
|
// TODO: Work out if there's some nice way to automatically decide if we inherit
|
||||||
|
public T GetProperty<T>(int objectId, string propName, bool inherit = true) where T : Property
|
||||||
{
|
{
|
||||||
if (!_objects.ContainsKey(objectId))
|
if (!_objects.ContainsKey(objectId))
|
||||||
{
|
{
|
||||||
|
@ -121,7 +123,7 @@ public class ObjectHierarchy
|
||||||
}
|
}
|
||||||
|
|
||||||
var prop = obj.GetProperty<T>(propName);
|
var prop = obj.GetProperty<T>(propName);
|
||||||
if (prop != null)
|
if (prop != null || !inherit)
|
||||||
{
|
{
|
||||||
return prop;
|
return prop;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,9 @@ class Program
|
||||||
public Vector3 spotlightDir;
|
public Vector3 spotlightDir;
|
||||||
public float spotlightInnerAngle;
|
public float spotlightInnerAngle;
|
||||||
public float spotlightOuterAngle;
|
public float spotlightOuterAngle;
|
||||||
|
|
||||||
|
public bool anim;
|
||||||
|
public int animLightTableIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
|
@ -33,18 +36,16 @@ class Program
|
||||||
var campaignName = "JAYRUDE_Tests";
|
var campaignName = "JAYRUDE_Tests";
|
||||||
var missionName = "lm_test.cow";
|
var missionName = "lm_test.cow";
|
||||||
|
|
||||||
|
// campaignName = "TDP20AC_a_burrick_in_a_room";
|
||||||
|
// missionName = "miss20.mis";
|
||||||
|
|
||||||
// Setup extract path
|
// Setup extract path
|
||||||
var tmpDir = Directory.CreateTempSubdirectory("KCLightmapper");
|
var tmpDir = Directory.CreateTempSubdirectory("KCLightmapper");
|
||||||
Console.WriteLine(tmpDir.FullName);
|
|
||||||
var resPathManager = new ResourcePathManager(tmpDir.FullName);
|
var resPathManager = new ResourcePathManager(tmpDir.FullName);
|
||||||
resPathManager.Init(installPath);
|
resPathManager.Init(installPath);
|
||||||
|
|
||||||
var campaign = resPathManager.GetCampaign(campaignName);
|
var campaign = resPathManager.GetCampaign(campaignName);
|
||||||
var misPath = campaign.GetResourcePath(ResourceType.Mission, missionName);
|
var misPath = campaign.GetResourcePath(ResourceType.Mission, missionName);
|
||||||
|
|
||||||
// misPath = "/stuff/Games/thief/drive_c/GOG Games/TG ND 1.27 (MAPPING)/FMs/JAYRUDE_Tests/lm_test.cow";
|
|
||||||
// misPath = "/stuff/Games/thief/drive_c/GOG Games/TG ND 1.27 (MAPPING)/FMs/AtdV/miss20.mis";
|
|
||||||
// misPath = "/stuff/Games/thief/drive_c/GOG Games/TG ND 1.27 (MAPPING)/FMs/TDP20AC_a_burrick_in_a_room/miss20.mis";
|
|
||||||
Timing.TimeStage("Total", () => LightmapMission(campaign, misPath));
|
Timing.TimeStage("Total", () => LightmapMission(campaign, misPath));
|
||||||
|
|
||||||
Timing.LogAll();
|
Timing.LogAll();
|
||||||
|
@ -132,56 +133,64 @@ class Program
|
||||||
{
|
{
|
||||||
// TODO: Handle PropSpotlightAndAmbient
|
// TODO: Handle PropSpotlightAndAmbient
|
||||||
var id = (int)brush.brushInfo;
|
var id = (int)brush.brushInfo;
|
||||||
var propLight = hierarchy.GetProperty<PropLight>(id, "P$Light");
|
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 propLightColor = hierarchy.GetProperty<PropLightColor>(id, "P$LightColo");
|
||||||
var propSpotlight = hierarchy.GetProperty<PropSpotlight>(id, "P$Spotlight");
|
var propSpotlight = hierarchy.GetProperty<PropSpotlight>(id, "P$Spotlight");
|
||||||
var propModelname = hierarchy.GetProperty<PropLabel>(id, "P$ModelName");
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
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)
|
if (propLight != null)
|
||||||
{
|
{
|
||||||
propLightColor ??= new PropLightColor { Hue = 0, Saturation = 0 };
|
|
||||||
|
|
||||||
// TODO: There's still some lights that aren't positioned right such as Streetlamp.
|
|
||||||
// Perhaps there's a light point specified in model files?
|
|
||||||
var light = new Light
|
var light = new Light
|
||||||
{
|
{
|
||||||
position = brush.position + propLight.Offset,
|
position = baseLight.position + propLight.Offset,
|
||||||
color = HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propLight.Brightness),
|
color = HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propLight.Brightness),
|
||||||
innerRadius = propLight.InnerRadius,
|
innerRadius = propLight.InnerRadius,
|
||||||
radius = propLight.Radius,
|
radius = propLight.Radius,
|
||||||
r2 = propLight.Radius * propLight.Radius,
|
r2 = propLight.Radius * propLight.Radius,
|
||||||
spotlightDir = -Vector3.UnitZ,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (propModelname != null)
|
|
||||||
{
|
|
||||||
var resName = $"{propModelname.value.ToLower()}.bin";
|
|
||||||
var modelPath = campaign.GetResourcePath(ResourceType.Object, resName);
|
|
||||||
var model = new ModelFile(modelPath);
|
|
||||||
if (model.TryGetVhot(ModelFile.VhotId.LightPosition, out var vhot))
|
|
||||||
{
|
|
||||||
light.position += vhot.Position;
|
|
||||||
}
|
|
||||||
if (model.TryGetVhot(ModelFile.VhotId.LightDirection, out vhot))
|
|
||||||
{
|
|
||||||
light.spotlightDir = vhot.Position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propSpotlight != null)
|
|
||||||
{
|
|
||||||
// TODO: Some objects seem to have spotlight direction embedded in the model file
|
|
||||||
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));
|
|
||||||
|
|
||||||
light.spotlight = true;
|
|
||||||
light.spotlightDir = Vector3.Transform(light.spotlightDir, rot);
|
|
||||||
light.spotlightInnerAngle = (float)Math.Cos(float.DegreesToRadians(propSpotlight.InnerAngle));
|
|
||||||
light.spotlightOuterAngle = (float)Math.Cos(float.DegreesToRadians(propSpotlight.OuterAngle));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propLight.Radius == 0)
|
if (propLight.Radius == 0)
|
||||||
{
|
{
|
||||||
light.radius = float.MaxValue;
|
light.radius = float.MaxValue;
|
||||||
|
@ -190,6 +199,27 @@ class Program
|
||||||
|
|
||||||
lights.Add(light);
|
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,
|
||||||
|
anim = true,
|
||||||
|
animLightTableIndex = propAnimLight.LightTableLightIndex,
|
||||||
|
};
|
||||||
|
if (propAnimLight.Radius == 0)
|
||||||
|
{
|
||||||
|
light.radius = float.MaxValue;
|
||||||
|
light.r2 = float.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
lights.Add(light);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -323,6 +353,28 @@ class Program
|
||||||
|
|
||||||
foreach (var light in lights)
|
foreach (var light in lights)
|
||||||
{
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if plane normal is facing towards the light
|
// Check if plane normal is facing towards the light
|
||||||
// If it's not then we're never going to be (directly) lit by this
|
// If it's not then we're never going to be (directly) lit by this
|
||||||
// light.
|
// light.
|
||||||
|
@ -391,7 +443,7 @@ class Program
|
||||||
if (hit)
|
if (hit)
|
||||||
{
|
{
|
||||||
var strength = CalculateLightStrengthAtPoint(light, pos, plane);
|
var strength = CalculateLightStrengthAtPoint(light, pos, plane);
|
||||||
lightmap.AddLight(0, x, y, light.color, strength, hdr);
|
lightmap.AddLight(layer, x, y, light.color, strength, hdr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue