diff --git a/KeepersCompound.LGS/Database/Chunks/BrList.cs b/KeepersCompound.LGS/Database/Chunks/BrList.cs index 5d65316..456c627 100644 --- a/KeepersCompound.LGS/Database/Chunks/BrList.cs +++ b/KeepersCompound.LGS/Database/Chunks/BrList.cs @@ -46,6 +46,15 @@ public class BrList : IChunk x = reader.ReadUInt16(); y = reader.ReadUInt16(); } + + public void Write(BinaryWriter writer) + { + writer.Write(id); + writer.Write(rot); + writer.Write(scale); + writer.Write(x); + writer.Write(y); + } }; public short id; @@ -104,6 +113,34 @@ public class BrList : IChunk txs = Array.Empty(); } } + + public void Write(BinaryWriter writer) + { + writer.Write(id); + writer.Write(time); + writer.Write(brushInfo); + writer.Write(textureId); + writer.Write((byte)media); + writer.Write(flags); + writer.WriteVec3(position); + writer.WriteVec3(size); + writer.WriteRotation(angle); + writer.Write(currentFaceIndex); + writer.Write(gridLineSpacing); + writer.WriteVec3(gridPhaseShift); + writer.WriteRotation(gridOrientation); + writer.Write(gridEnabled); + writer.Write(numFaces); + writer.Write(edgeSelected); + writer.Write(pointSelected); + writer.Write(useFlag); + writer.Write(groupId); + writer.Write(new byte[4]); + foreach (var info in txs) + { + info.Write(writer); + } + } } public ChunkHeader Header { get; set; } @@ -120,6 +157,9 @@ public class BrList : IChunk public void WriteData(BinaryWriter writer) { - throw new System.NotImplementedException(); + foreach (var brush in Brushes) + { + brush.Write(writer); + } } } \ No newline at end of file diff --git a/KeepersCompound.LGS/Database/Chunks/GamFile.cs b/KeepersCompound.LGS/Database/Chunks/GamFile.cs index 9d6e9aa..880b865 100644 --- a/KeepersCompound.LGS/Database/Chunks/GamFile.cs +++ b/KeepersCompound.LGS/Database/Chunks/GamFile.cs @@ -1,7 +1,3 @@ -using System; -using System.IO; -using System.Text; - namespace KeepersCompound.LGS.Database.Chunks; public class GamFile : IChunk @@ -16,6 +12,6 @@ public class GamFile : IChunk public void WriteData(BinaryWriter writer) { - throw new System.NotImplementedException(); + writer.WriteNullString(fileName, 256); } } \ No newline at end of file diff --git a/KeepersCompound.LGS/Database/Chunks/Link.cs b/KeepersCompound.LGS/Database/Chunks/Link.cs index b079e64..a01e8e0 100644 --- a/KeepersCompound.LGS/Database/Chunks/Link.cs +++ b/KeepersCompound.LGS/Database/Chunks/Link.cs @@ -32,6 +32,11 @@ public record LinkId { return _data; } + + public void Write(BinaryWriter writer) + { + writer.Write(_data); + } } public class LinkChunk : IChunk, IMergable @@ -50,6 +55,14 @@ public class LinkChunk : IChunk, IMergable destination = reader.ReadInt32(); relation = reader.ReadUInt16(); } + + public void Write(BinaryWriter writer) + { + linkId.Write(writer); + writer.Write(source); + writer.Write(destination); + writer.Write(relation); + } } public ChunkHeader Header { get; set; } @@ -66,7 +79,10 @@ public class LinkChunk : IChunk, IMergable public void WriteData(BinaryWriter writer) { - throw new System.NotImplementedException(); + foreach (var link in links) + { + link.Write(writer); + } } public void Merge(IMergable other) @@ -88,6 +104,12 @@ public class LinkDataMetaProp : IChunk, IMergable linkId = new LinkId(reader.ReadUInt32()); priority = reader.ReadInt32(); } + + public void Write(BinaryWriter writer) + { + linkId.Write(writer); + writer.Write(priority); + } } public ChunkHeader Header { get; set; } @@ -106,7 +128,11 @@ public class LinkDataMetaProp : IChunk, IMergable public void WriteData(BinaryWriter writer) { - throw new System.NotImplementedException(); + writer.Write(DataSize); + foreach (var data in linkData) + { + data.Write(writer); + } } public void Merge(IMergable other) diff --git a/KeepersCompound.LGS/Database/Chunks/Property.cs b/KeepersCompound.LGS/Database/Chunks/Property.cs index 56aa7d8..5e1c231 100644 --- a/KeepersCompound.LGS/Database/Chunks/Property.cs +++ b/KeepersCompound.LGS/Database/Chunks/Property.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.IO; using System.Numerics; namespace KeepersCompound.LGS.Database.Chunks; @@ -14,6 +12,12 @@ public class Property objectId = reader.ReadInt32(); length = (int)reader.ReadUInt32(); } + + public virtual void Write(BinaryWriter writer) + { + writer.Write(objectId); + writer.Write((uint)length); + } } public class PropertyChunk : IChunk, IMergable where T : Property, new() @@ -34,7 +38,10 @@ public class PropertyChunk : IChunk, IMergable where T : Property, new() public void WriteData(BinaryWriter writer) { - throw new System.NotImplementedException(); + foreach (var prop in properties) + { + prop.Write(writer); + } } public void Merge(IMergable other) @@ -52,6 +59,12 @@ public class PropGeneric : Property base.Read(reader); data = reader.ReadBytes(length); } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.Write(data); + } } public class PropBool : Property @@ -63,6 +76,12 @@ public class PropBool : Property base.Read(reader); value = reader.ReadInt32() != 0; } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.Write(value ? 1 : 0); + } } public class PropInt : Property @@ -74,6 +93,12 @@ public class PropInt : Property base.Read(reader); value = reader.ReadInt32(); } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.Write(value); + } } public class PropLabel : Property @@ -85,6 +110,12 @@ public class PropLabel : Property base.Read(reader); value = reader.ReadNullString(length); } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.WriteNullString(value, length); + } } public class PropString : Property @@ -98,6 +129,13 @@ public class PropString : Property stringLength = reader.ReadInt32(); value = reader.ReadNullString(stringLength); } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.Write(stringLength); + writer.WriteNullString(value, stringLength); + } } public class PropFloat : Property @@ -109,6 +147,12 @@ public class PropFloat : Property base.Read(reader); value = reader.ReadSingle(); } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.Write(value); + } } public class PropVector : Property @@ -120,6 +164,12 @@ public class PropVector : Property base.Read(reader); value = reader.ReadVec3(); } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.WriteVec3(value); + } } public class PropRenderType : Property @@ -140,6 +190,12 @@ public class PropRenderType : Property base.Read(reader); mode = (Mode)reader.ReadUInt32(); } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.Write((uint)mode); + } } public class PropSlayResult : Property @@ -156,6 +212,12 @@ public class PropSlayResult : Property base.Read(reader); effect = (Effect)reader.ReadUInt32(); } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.Write((uint)effect); + } } public class PropInventoryType : Property @@ -172,6 +234,12 @@ public class PropInventoryType : Property base.Read(reader); type = (Slot)reader.ReadUInt32(); } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.Write((uint)type); + } } public class PropCollisionType : Property @@ -194,6 +262,19 @@ public class PropCollisionType : Property NoResult = (flags & (0x1 << 4)) != 0; FullCollisionSound = (flags & (0x1 << 5)) != 0; } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + var flags = 0u; + if (Bounce) flags += 1; + if (DestroyOnImpact) flags += 2; + if (SlayOnImpact) flags += 4; + if (NoCollisionSound) flags += 8; + if (NoResult) flags += 16; + if (FullCollisionSound) flags += 32; + writer.Write(flags); + } } public class PropPosition : Property @@ -208,6 +289,14 @@ public class PropPosition : Property reader.ReadBytes(4); // Runtime Cell/Hint in editor Rotation = reader.ReadRotation(); } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.WriteVec3(Location); + writer.Write(new byte[4]); + writer.WriteRotation(Rotation); + } } public class PropLight : Property @@ -228,6 +317,17 @@ public class PropLight : Property reader.ReadBytes(3); InnerRadius = reader.ReadSingle(); } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.Write(Brightness); + writer.WriteVec3(Offset); + writer.Write(Radius); + writer.Write(QuadLit); + writer.Write(new byte[3]); + writer.Write(InnerRadius); + } } public class PropLightColor : Property @@ -241,6 +341,13 @@ public class PropLightColor : Property Hue = reader.ReadSingle(); Saturation = reader.ReadSingle(); } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.Write(Hue); + writer.Write(Saturation); + } } public class PropSpotlight : Property @@ -255,6 +362,14 @@ public class PropSpotlight : Property OuterAngle = reader.ReadSingle(); reader.ReadBytes(4); // Z is unused } + + public override void Write(BinaryWriter writer) + { + base.Write(writer); + writer.Write(InnerAngle); + writer.Write(OuterAngle); + writer.Write(new byte[4]); + } } public class PropSpotlightAndAmbient : Property @@ -270,21 +385,12 @@ public class PropSpotlightAndAmbient : Property OuterAngle = reader.ReadSingle(); SpotBrightness = reader.ReadSingle(); } -} -// TODO: Work out what this property actually does -public class PropLightBasedAlpha : Property -{ - public bool Enabled; - public Vector2 AlphaRange; - public Vector2 LightRange; - - public override void Read(BinaryReader reader) + public override void Write(BinaryWriter writer) { - base.Read(reader); - Enabled = reader.ReadBoolean(); - reader.ReadBytes(3); - AlphaRange = reader.ReadVec2(); - LightRange = new Vector2(reader.ReadInt32(), reader.ReadInt32()); + base.Write(writer); + writer.Write(InnerAngle); + writer.Write(OuterAngle); + writer.Write(SpotBrightness); } } diff --git a/KeepersCompound.LGS/Database/Chunks/RendParams.cs b/KeepersCompound.LGS/Database/Chunks/RendParams.cs new file mode 100644 index 0000000..8ab01ae --- /dev/null +++ b/KeepersCompound.LGS/Database/Chunks/RendParams.cs @@ -0,0 +1,80 @@ + +using System.Numerics; + +namespace KeepersCompound.LGS.Database.Chunks; + +public class RendParams : IChunk +{ + public enum SunlightMode + { + SingleUnshadowed, + QuadObjcastShadows, + QuadUnshadowed, + SingleObjcastShadows, + } + + public ChunkHeader Header { get; set; } + + public string palette; + public Vector3 ambientLight; + public int useSunlight; + public SunlightMode sunlightMode; + public Vector3 sunlightDirection; + public float sunlightHue; + public float sunlightSaturation; + public float sunlightBrightness; + public float viewDistance; + public Vector3[] ambientLightZones; + public float globalAiVisBias; + public float[] ambientZoneAiVisBiases; + + public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry) + { + palette = reader.ReadNullString(16); + ambientLight = reader.ReadVec3(); + useSunlight = reader.ReadInt32(); + sunlightMode = (SunlightMode)reader.ReadUInt32(); + sunlightDirection = reader.ReadVec3(); + sunlightHue = reader.ReadSingle(); + sunlightSaturation = reader.ReadSingle(); + sunlightBrightness = reader.ReadSingle(); + reader.ReadBytes(24); + viewDistance = reader.ReadSingle(); + reader.ReadBytes(12); + ambientLightZones = new Vector3[8]; + for (var i = 0; i < ambientLightZones.Length; i++) + { + ambientLightZones[i] = reader.ReadVec3(); + } + globalAiVisBias = reader.ReadSingle(); + ambientZoneAiVisBiases = new float[8]; + for (var i = 0; i < ambientZoneAiVisBiases.Length; i++) + { + ambientZoneAiVisBiases[i] = reader.ReadSingle(); + } + } + + public void WriteData(BinaryWriter writer) + { + writer.WriteNullString(palette, 16); + writer.WriteVec3(ambientLight); + writer.Write(useSunlight); + writer.Write((uint)sunlightMode); + writer.WriteVec3(sunlightDirection); + writer.Write(sunlightHue); + writer.Write(sunlightSaturation); + writer.Write(sunlightBrightness); + writer.Write(new byte[24]); + writer.Write(viewDistance); + writer.Write(new byte[12]); + foreach (var lightZone in ambientLightZones) + { + writer.WriteVec3(lightZone); + } + writer.Write(globalAiVisBias); + foreach (var visBias in ambientZoneAiVisBiases) + { + writer.Write(visBias); + } + } +} \ No newline at end of file diff --git a/KeepersCompound.LGS/Database/Chunks/TxList.cs b/KeepersCompound.LGS/Database/Chunks/TxList.cs index 5aaafd7..21d148d 100644 --- a/KeepersCompound.LGS/Database/Chunks/TxList.cs +++ b/KeepersCompound.LGS/Database/Chunks/TxList.cs @@ -1,7 +1,3 @@ -using System; -using System.IO; -using System.Text; - namespace KeepersCompound.LGS.Database.Chunks; public class TxList : IChunk @@ -16,6 +12,12 @@ public class TxList : IChunk Tokens = reader.ReadBytes(4); Name = reader.ReadNullString(16); } + + public readonly void Write(BinaryWriter writer) + { + writer.Write(Tokens); + writer.WriteNullString(Name, 16); + } } @@ -27,7 +29,6 @@ public class TxList : IChunk public string[] Tokens { get; set; } public Item[] Items { get; set; } - public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry) { BlockSize = reader.ReadInt32(); @@ -47,6 +48,16 @@ public class TxList : IChunk public void WriteData(BinaryWriter writer) { - throw new System.NotImplementedException(); + writer.Write(BlockSize); + writer.Write(ItemCount); + writer.Write(TokenCount); + foreach (var token in Tokens) + { + writer.WriteNullString(token, 16); + } + foreach (var item in Items) + { + item.Write(writer); + } } } \ No newline at end of file diff --git a/KeepersCompound.LGS/Database/Chunks/WorldRep.cs b/KeepersCompound.LGS/Database/Chunks/WorldRep.cs index ef442e4..20eae0e 100644 --- a/KeepersCompound.LGS/Database/Chunks/WorldRep.cs +++ b/KeepersCompound.LGS/Database/Chunks/WorldRep.cs @@ -39,6 +39,17 @@ public class WorldRep : IChunk _ => 1.0f, }; } + + public readonly void Write(BinaryWriter writer) + { + writer.Write(Size); + writer.Write(Version); + writer.Write(Flags); + writer.Write(LightmapFormat); + writer.Write(LightmapScale); + writer.Write(DataSize); + writer.Write(CellCount); + } } public struct Cell @@ -62,6 +73,17 @@ public class WorldRep : IChunk MotionIndex = reader.ReadByte(); reader.ReadByte(); } + + public readonly void Write(BinaryWriter writer) + { + writer.Write(Flags); + writer.Write(VertexCount); + writer.Write(PlaneId); + writer.Write(ClutId); + writer.Write(Destination); + writer.Write(MotionIndex); + writer.Write((byte)0); + } } public struct RenderPoly @@ -82,6 +104,18 @@ public class WorldRep : IChunk TextureMagnitude = reader.ReadSingle(); Center = reader.ReadVec3(); } + + public readonly void Write(BinaryWriter writer) + { + writer.WriteVec3(TextureVectors.Item1); + writer.WriteVec3(TextureVectors.Item2); + writer.Write(TextureBases.Item1); + writer.Write(TextureBases.Item2); + writer.Write(TextureId); + writer.Write(CachedSurface); + writer.Write(TextureMagnitude); + writer.WriteVec3(Center); + } } public struct LightmapInfo @@ -104,6 +138,18 @@ public class WorldRep : IChunk DynamicLightPtr = reader.ReadUInt32(); AnimLightBitmask = reader.ReadUInt32(); } + + public readonly void Write(BinaryWriter writer) + { + writer.Write(Bases.Item1); + writer.Write(Bases.Item2); + writer.Write(PaddedWidth); + writer.Write(Height); + writer.Write(Width); + writer.Write(DataPtr); + writer.Write(DynamicLightPtr); + writer.Write(AnimLightBitmask); + } } public struct Lightmap @@ -186,6 +232,21 @@ public class WorldRep : IChunk return bytes; } + + // TODO: This ONLY works for rgba (bpp = 4)!!! + public void AddLight(int layer, int x, int y, byte r, byte g, byte 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; + } + + public readonly void Write(BinaryWriter writer) + { + writer.Write(Pixels); + } } public byte VertexCount { get; set; } @@ -277,11 +338,65 @@ public class WorldRep : IChunk LightIndices[i] = reader.ReadUInt16(); } } + + public readonly void Write(BinaryWriter writer) + { + writer.Write(VertexCount); + writer.Write(PolyCount); + writer.Write(RenderPolyCount); + writer.Write(PortalPolyCount); + writer.Write(PlaneCount); + writer.Write(Medium); + writer.Write(Flags); + writer.Write(PortalVertices); + writer.Write(NumVList); + writer.Write(AnimLightCount); + writer.Write(MotionIndex); + writer.WriteVec3(SphereCenter); + writer.Write(SphereRadius); + foreach (var vertex in Vertices) + { + writer.WriteVec3(vertex); + } + foreach (var poly in Polys) + { + poly.Write(writer); + } + foreach (var renderPoly in RenderPolys) + { + renderPoly.Write(writer); + } + writer.Write(IndexCount); + writer.Write(Indices); + foreach (var plane in Planes) + { + writer.WriteVec3(plane.Normal); + writer.Write(plane.D); + } + foreach (var animLight in AnimLights) + { + writer.Write(animLight); + } + foreach (var lightmapInfo in LightList) + { + lightmapInfo.Write(writer); + } + foreach (var lightmap in Lightmaps) + { + lightmap.Write(writer); + } + writer.Write(LightIndexCount); + foreach (var lightIndex in LightIndices) + { + writer.Write(lightIndex); + } + } } public ChunkHeader Header { get; set; } public WrHeader DataHeader { get; set; } public Cell[] Cells { get; set; } + private byte[] _unreadData; public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry) { @@ -295,10 +410,17 @@ public class WorldRep : IChunk } // TODO: All the other info lol + var length = entry.Offset + entry.Size + 24 - reader.BaseStream.Position; + _unreadData = reader.ReadBytes((int)length); } public void WriteData(BinaryWriter writer) { - throw new System.NotImplementedException(); + DataHeader.Write(writer); + foreach (var cell in Cells) + { + cell.Write(writer); + } + writer.Write(_unreadData); } } \ No newline at end of file diff --git a/KeepersCompound.LGS/Database/File.cs b/KeepersCompound.LGS/Database/File.cs index 89ed448..2d4bb2e 100644 --- a/KeepersCompound.LGS/Database/File.cs +++ b/KeepersCompound.LGS/Database/File.cs @@ -52,6 +52,13 @@ public class DbFile // return $"Name: {Name}, Offset: {O}" return base.ToString(); } + + public readonly void Write(BinaryWriter writer) + { + writer.WriteNullString(Name, 12); + writer.Write(Offset); + writer.Write(Size); + } } public uint ItemCount { get; } @@ -65,6 +72,15 @@ public class DbFile Items.Add(new Entry(reader)); Items.Sort((a, b) => a.Offset.CompareTo(b.Offset)); } + + public readonly void Write(BinaryWriter writer) + { + writer.Write(ItemCount); + foreach (var entry in Items) + { + entry.Write(writer); + } + } } public FHeader Header { get; private set; } @@ -73,6 +89,7 @@ public class DbFile public DbFile(string filename) { + // TODO: Throw rather than return if (!File.Exists(filename)) return; using MemoryStream stream = new(File.ReadAllBytes(filename)); @@ -91,6 +108,22 @@ public class DbFile } } + 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 writer = new BinaryWriter(stream, Encoding.UTF8, false); + + Header.Write(writer); + foreach (var (name, chunk) in Chunks) + { + chunk.Write(writer); + } + Toc.Write(writer); + } + private static IChunk NewChunk(string entryName) { return entryName switch @@ -101,6 +134,7 @@ public class DbFile "TXLIST" => new TxList(), "WREXT" => new WorldRep(), "BRLIST" => new BrList(), + "RENDPARAMS" => new RendParams(), "P$ModelName" => new PropertyChunk(), "P$Scale" => new PropertyChunk(), "P$RenderTyp" => new PropertyChunk(), diff --git a/KeepersCompound.Lightmapper/Program.cs b/KeepersCompound.Lightmapper/Program.cs index 3751555..9c938a7 100644 --- a/KeepersCompound.Lightmapper/Program.cs +++ b/KeepersCompound.Lightmapper/Program.cs @@ -1,2 +1,243 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +using System.Numerics; +using KeepersCompound.LGS.Database; +using KeepersCompound.LGS.Database.Chunks; +using TinyEmbree; + +namespace KeepersCompound.Lightmapper; + +class Program +{ + // Super simple for now + private record Light + { + public Vector3 position; + public Vector3 color; + public float radius; + } + + static void Main(string[] args) + { + var misPath = "/stuff/Games/thief/drive_c/GOG Games/TG ND 1.27 (MAPPING)/FMs/JAYRUDE_Tests/lm_test.cow"; + var mis = new DbFile(misPath); + var hierarchy = BuildHierarchy(misPath, mis); + + // Get list of brush lights, and object lights (ignore anim lights for now) + var lights = new List(); + if (mis.Chunks.TryGetValue("BRLIST", out var brListRaw)) + { + var brList = (BrList)brListRaw; + foreach (var brush in brList.Brushes) + { + if (brush.media == BrList.Brush.Media.Light) + { + var sz = brush.size; + lights.Add(new Light + { + position = brush.position, + color = HsbToRgb(360 * sz.Y, sz.Z, Math.Min(sz.X, 255.0f)), + radius = float.MaxValue + }); + } + } + + // TODO: object lights + } + + // Build embree mesh + if (!mis.Chunks.TryGetValue("WREXT", out var wrRaw)) + return; + var worldRep = (WorldRep)wrRaw; + var scene = new Raytracer(); + scene.AddMesh(BuildWrMesh(worldRep)); + scene.CommitScene(); + + // For each lightmap pixel cast against all the brush and object lights + if (!mis.Chunks.TryGetValue("RENDPARAMS", out var rendParamsRaw)) + return; + var ambient = ((RendParams)rendParamsRaw).ambientLight * 255; + CastScene(scene, worldRep, [.. lights], ambient); + + var dir = Path.GetDirectoryName(misPath); + var filename = Path.GetFileNameWithoutExtension(misPath); + mis.Save(Path.Join(dir, $"{filename}-lit.cow")); + } + + // Expects Hue to be 0-360, saturation 0-1, brightness 0-255 + // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB + private static Vector3 HsbToRgb(float hue, float saturation, float brightness) + { + var hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6; + var f = hue / 60 - Math.Floor(hue / 60); + + var v = Convert.ToInt32(brightness); + var p = Convert.ToInt32(brightness * (1 - saturation)); + var q = Convert.ToInt32(brightness * (1 - f * saturation)); + var t = Convert.ToInt32(brightness * (1 - (1 - f) * saturation)); + + return hi switch + { + 0 => new Vector3(v, t, p), + 1 => new Vector3(q, v, p), + 2 => new Vector3(p, v, t), + 3 => new Vector3(p, q, v), + 4 => new Vector3(t, p, v), + _ => new Vector3(v, p, q), + }; + } + + private static ObjectHierarchy BuildHierarchy(string misPath, DbFile misFile) + { + ObjectHierarchy objHierarchy; + if (misFile.Chunks.TryGetValue("GAM_FILE", out var gamFileChunk)) + { + var dir = Path.GetDirectoryName(misPath); + var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }; + var name = ((GamFile)gamFileChunk).fileName; + var paths = Directory.GetFiles(dir!, name, options); + if (paths.Length > 0) + { + objHierarchy = new ObjectHierarchy(misFile, new DbFile(paths[0])); + } + else + { + objHierarchy = new ObjectHierarchy(misFile); + } + } + else + { + objHierarchy = new ObjectHierarchy(misFile); + } + return objHierarchy; + } + + private static TriangleMesh BuildWrMesh(WorldRep worldRep) + { + var vertices = new List(); + var indices = new List(); + + var cells = worldRep.Cells; + for (var cellIdx = 0; cellIdx < cells.Length; cellIdx++) + { + var cell = cells[cellIdx]; + var numPolys = cell.PolyCount; + var numRenderPolys = cell.RenderPolyCount; + var numPortalPolys = cell.PortalPolyCount; + + // There's nothing to render + if (numRenderPolys == 0 || numPortalPolys >= numPolys) + { + continue; + } + + var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys); + var cellIdxOffset = 0; + for (int polyIdx = 0; polyIdx < maxPolyIdx; polyIdx++) + { + var poly = cell.Polys[polyIdx]; + + var meshIndexOffset = vertices.Count; + var numPolyVertices = poly.VertexCount; + for (var j = 0; j < numPolyVertices; j++) + { + var vertex = cell.Vertices[cell.Indices[cellIdxOffset + j]]; + // Console.WriteLine($"Cell: {cellIdx}, Poly: {polyIdx}, V: {j}, Vert: {vertex}"); + vertices.Add(vertex); + } + + for (int j = 1; j < numPolyVertices - 1; j++) + { + indices.Add(meshIndexOffset); + indices.Add(meshIndexOffset + j); + indices.Add(meshIndexOffset + j + 1); + } + + cellIdxOffset += cell.Polys[polyIdx].VertexCount; + } + } + + return new TriangleMesh([.. vertices], [.. indices]); + } + + private static void CastScene(Raytracer scene, WorldRep wr, Light[] lights, Vector3 ambientLight) + { + var cells = wr.Cells; + for (var cellIdx = 0; cellIdx < cells.Length; cellIdx++) + { + var cell = cells[cellIdx]; + var numPolys = cell.PolyCount; + var numRenderPolys = cell.RenderPolyCount; + var numPortalPolys = cell.PortalPolyCount; + + // There's nothing to render + if (numRenderPolys == 0 || numPortalPolys >= numPolys) + { + continue; + } + + var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys); + for (int polyIdx = 0; polyIdx < maxPolyIdx; polyIdx++) + { + var poly = cell.Polys[polyIdx]; + var plane = cell.Planes[poly.PlaneId]; + var renderPoly = cell.RenderPolys[polyIdx]; + var info = cell.LightList[polyIdx]; + var lightmap = cell.Lightmaps[polyIdx]; + + // Clear existing lightmap data + 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, (byte)ambientLight.X, (byte)ambientLight.Y, (byte)ambientLight.Z); + } + } + + foreach (var light in lights) + { + Console.WriteLine("Doing a light..."); + // Check if plane normal is facing towards the light + var direction = renderPoly.Center - light.position; + Console.WriteLine($"Light Pos: {light.position}, poly center: {renderPoly.Center}"); + Console.WriteLine($"Dir: {direction}"); + // if (Vector3.Dot(plane.Normal, direction) < 0) + { + // Cast from the light to the center (later each pixel) + var hit = scene.Trace(new Ray + { + Origin = light.position, + Direction = Vector3.Normalize(direction) + }); + + // cheeky epsilon + var goodHit = hit && Math.Abs(hit.Distance - direction.Length()) < 0.001; + Console.WriteLine($"Did we hit? {goodHit}"); + Console.WriteLine($"Distance: {hit.Distance}, target dist: {direction.Length()}"); + Console.WriteLine($"Pos: {hit.Position}, Target Pos: {renderPoly.Center}"); + + // Iterate all pixels + var color = goodHit ? light.color : ambientLight; + for (var y = 0; y < lightmap.Height; y++) + { + for (var x = 0; x < lightmap.Width; x++) + { + lightmap.AddLight(0, x, y, (byte)color.X, (byte)color.Y, (byte)color.Z); + } + } + } + } + + + // // TODO: Get world position of each pixel? + + // var poly = cell.Polys[polyIdx]; + + // poly. + } + } + } +} \ No newline at end of file