From e8b77b42bb0354e9b6480344e2ca13df05e6f5f9 Mon Sep 17 00:00:00 2001 From: Jarrod Doyle Date: Tue, 30 Jul 2024 21:50:34 +0100 Subject: [PATCH] Add working lightmaps --- Thief Mission Viewer.csproj | 3 + project/code/LGS/Database/Chunks/WorldRep.cs | 38 +++++ project/code/Mission.cs | 142 +++++++++++++++++-- project/scenes/main.tscn | 4 +- 4 files changed, 177 insertions(+), 10 deletions(-) diff --git a/Thief Mission Viewer.csproj b/Thief Mission Viewer.csproj index 18b2d7c..4bd40ee 100644 --- a/Thief Mission Viewer.csproj +++ b/Thief Mission Viewer.csproj @@ -6,4 +6,7 @@ true ThiefMissionViewer + + + \ No newline at end of file diff --git a/project/code/LGS/Database/Chunks/WorldRep.cs b/project/code/LGS/Database/Chunks/WorldRep.cs index 899b368..18b0d51 100644 --- a/project/code/LGS/Database/Chunks/WorldRep.cs +++ b/project/code/LGS/Database/Chunks/WorldRep.cs @@ -117,6 +117,44 @@ public class WorldRep : IChunk } } } + + public readonly Vector4[] AsRgba(uint layer) + { + if (layer >= Pixels.GetLength(0)) + { + + return System.Array.Empty(); + } + + var height = Pixels.GetLength(1); + var width = Pixels.GetLength(2); + var bpp = Pixels.GetLength(3); + var colours = new Vector4[height * width]; + for (var y = 0; y < height; y++) + { + for (var x = 0; x < width; x++) + { + var idx = x + y * width; + + switch (bpp) + { + case 1: + var raw1 = Pixels[layer, y, x, 0]; + colours[idx] = new Vector4(raw1, raw1, raw1, 255) / 255.0f; + break; + case 2: + var raw2 = Pixels[layer, y, x, 0] + (Pixels[layer, y, x, 1] << 8); + colours[idx] = new Vector4(raw2 & 31, (raw2 >> 5) & 31, (raw2 >> 10) & 31, 32) / 32.0f; + break; + case 4: + colours[idx] = new Vector4(Pixels[layer, y, x, 0], Pixels[layer, y, x, 1], Pixels[layer, y, x, 2], Pixels[layer, y, x, 3]) / 255.0f; + break; + } + } + } + + return colours; + } } public byte VertexCount { get; set; } diff --git a/project/code/Mission.cs b/project/code/Mission.cs index 806869e..a94e117 100644 --- a/project/code/Mission.cs +++ b/project/code/Mission.cs @@ -2,7 +2,7 @@ using Godot; using KeepersCompound.LGS; using KeepersCompound.LGS.Database; using KeepersCompound.LGS.Database.Chunks; -using Microsoft.VisualBasic; +using RectpackSharp; using System; using System.Collections.Generic; @@ -81,6 +81,7 @@ public partial class Mission : Node3D var numRenderPolys = cell.RenderPolyCount; var numPortalPolys = cell.PortalPolyCount; + // There's nothing to render if (numRenderPolys == 0 || numPortalPolys >= numPolys) { return; @@ -89,26 +90,29 @@ public partial class Mission : Node3D var vertices = new List(); var normals = new List(); var indices = new List(); + var lightmapUvs = new List(); var cellIdxOffset = 0; + // You'd think these would be the same number, but apparently not + // I think it's because water counts as a render poly and a portal poly var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys); + var packingRects = new PackingRectangle[maxPolyIdx]; + var rectIdToUvIdxMap = new List(); for (int i = 0; i < maxPolyIdx; i++) { var poly = cell.Polys[i]; - var plane = cell.Planes[poly.PlaneId]; - var normal = plane.Normal; - var gNormal = normal.ToGodotVec3(4.0f); - - var numPolyVertices = poly.VertexCount; var meshIdxOffset = vertices.Count; + + var normal = cell.Planes[poly.PlaneId].Normal.ToGodotVec3(4.0f); + var numPolyVertices = poly.VertexCount; for (var j = 0; j < numPolyVertices; j++) { var vertex = cell.Vertices[cell.Indices[cellIdxOffset + j]]; vertices.Add(vertex.ToGodotVec3(4.0f)); - normals.Add(gNormal); + normals.Add(normal); } - // Simple triangulation + // Simple triangulation. Polys are always convex so we can just do a fan for (int j = 1; j < numPolyVertices - 1; j++) { indices.Add(meshIdxOffset); @@ -116,18 +120,75 @@ public partial class Mission : Node3D indices.Add(meshIdxOffset + j + 1); } + // UVs + var renderPoly = cell.RenderPolys[i]; + var light = cell.LightList[i]; + packingRects[i] = new PackingRectangle(0, 0, light.Width, light.Height, i); + var uvIdxs = CalcBaseUV(cell, poly, renderPoly, light, lightmapUvs, cellIdxOffset); + rectIdToUvIdxMap.Add(uvIdxs); + cellIdxOffset += poly.VertexCount; } + RectanglePacker.Pack(packingRects, out var bounds); + var image = Image.Create((int)bounds.Width, (int)bounds.Height, false, Image.Format.Rgba8); + foreach (var rect in packingRects) + { + // Build lightmap + var lightmap = cell.Lightmaps[rect.Id]; + // TODO: Handle animlight layers + var colours = lightmap.AsRgba(0); + var height = lightmap.Pixels.GetLength(1); + var width = lightmap.Pixels.GetLength(2); + for (var y = 0; y < height; y++) + { + for (var x = 0; x < width; x++) + { + var rawColour = colours[x + y * width]; + image.SetPixel((int)rect.X + x, (int)rect.Y + y, new Color(rawColour.X, rawColour.Y, rawColour.Z, rawColour.W)); + } + } + + // Transform UVs + var lmUvIdxs = rectIdToUvIdxMap[rect.Id]; + foreach (var idx in lmUvIdxs) + { + var uv = lightmapUvs[idx]; + var u = uv.X; + var v = uv.Y; + + // Clamp uv range to [0..1] + u %= 1; + v %= 1; + if (u < 0) u = Math.Abs(u); + if (v < 0) v = Math.Abs(v); + + // Transform! + u = (rect.X + rect.Width * u) / (int)bounds.Width; + v = (rect.Y + rect.Height * v) / (int)bounds.Height; + lightmapUvs[idx] = new Vector2(u, v); + } + } + + var cellNode = new Node3D(); + var material = new StandardMaterial3D + { + AlbedoTexture = ImageTexture.CreateFromImage(image), + TextureFilter = BaseMaterial3D.TextureFilterEnum.Nearest, + }; + var arrMesh = new ArrayMesh(); var arrays = new Godot.Collections.Array(); arrays.Resize((int)Mesh.ArrayType.Max); arrays[(int)Mesh.ArrayType.Vertex] = vertices.ToArray(); arrays[(int)Mesh.ArrayType.Normal] = normals.ToArray(); arrays[(int)Mesh.ArrayType.Index] = indices.ToArray(); + arrays[(int)Mesh.ArrayType.TexUV] = lightmapUvs.ToArray(); + arrMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, arrays); + arrMesh.SurfaceSetMaterial(0, material); var meshInstance = new MeshInstance3D { @@ -153,9 +214,72 @@ public partial class Mission : Node3D Position = cell.SphereCenter.ToGodotVec3(4.0f), OmniRange = cell.SphereRadius * (r.NextSingle() + 1.0f) * 0.5f, }; - cellNode.AddChild(light); + // cellNode.AddChild(light); } AddChild(cellNode); } + + private static int[] CalcBaseUV( + WorldRep.Cell cell, + WorldRep.Cell.Poly poly, + WorldRep.Cell.RenderPoly renderPoly, + WorldRep.Cell.LightmapInfo light, + List lightmapUvs, + int cellIdxOffset) + { + // TODO: This is slightly hardcoded for ND. Check other stuff at some point. Should be handled in LG side imo + // TODO: This is a mess lol + var texU = renderPoly.TextureVectors.Item1.ToGodotVec3(4.0f); + var texV = renderPoly.TextureVectors.Item2.ToGodotVec3(4.0f); + + var uu = texU.Dot(texU); + var vv = texV.Dot(texV); + var uv = texU.Dot(texV); + var lmUScale = 4.0f / light.Width; + var lmVScale = 4.0f / light.Height; + + var baseU = renderPoly.TextureBases.Item1; + var baseV = renderPoly.TextureBases.Item2; + var lmUBase = lmUScale * (baseU + (0.5f - light.Bases.Item1) / 4.0f); + var lmVBase = lmVScale * (baseV + (0.5f - light.Bases.Item2) / 4.0f); + var anchor = cell.Vertices[cell.Indices[cellIdxOffset + 0]].ToGodotVec3(4.0f); // TODO: This probably shouldn't be hardcoded idx 0 + + var uvIdxs = new int[poly.VertexCount]; + if (uv == 0.0) + { + var lmUVec = texU * lmUScale / uu; + var lmVVec = texV * lmVScale / vv; + for (var i = 0; i < poly.VertexCount; i++) + { + uvIdxs[i] = lightmapUvs.Count; + + var v = cell.Vertices[cell.Indices[cellIdxOffset + i]].ToGodotVec3(4.0f); + var delta = new Vector3(v.X - anchor.X, v.Y - anchor.Y, v.Z - anchor.Z); + var lmUV = new Vector2(delta.Dot(lmUVec) + lmUBase, delta.Dot(lmVVec) + lmVBase); + lightmapUvs.Add(lmUV); + } + } + else + { + var denom = 1.0f / (uu * vv - uv * uv); + var lmUu = uu * lmVScale * denom; + var lmVv = vv * lmUScale * denom; + var lmUvu = lmUScale * denom * uv; + var lmUvv = lmVScale * denom * uv; + for (var i = 0; i < poly.VertexCount; i++) + { + uvIdxs[i] = lightmapUvs.Count; + + var v = cell.Vertices[cell.Indices[cellIdxOffset + i]].ToGodotVec3(4.0f); + var delta = new Vector3(v.X - anchor.X, v.Y - anchor.Y, v.Z - anchor.Z); + var du = delta.Dot(texU); + var dv = delta.Dot(texV); + var lmUV = new Vector2(lmUBase + lmVv * du - lmUvu * dv, lmVBase + lmUu * dv - lmUvv * du); + lightmapUvs.Add(lmUV); + } + } + + return uvIdxs; + } } \ No newline at end of file diff --git a/project/scenes/main.tscn b/project/scenes/main.tscn index fc453d2..e6fafee 100644 --- a/project/scenes/main.tscn +++ b/project/scenes/main.tscn @@ -8,8 +8,9 @@ [node name="Main" type="Node3D"] [node name="Mission" type="Node3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.110309, 0.187101, -0.461656) script = ExtResource("1_xhqt7") -FileName = "/home/jarrod/Dev/thief/de-specs/test_data/rose-garden.mis" +FileName = "/home/jarrod/Dev/thief/de-specs/test_data/lm-test.cow" [node name="Camera3D" type="Camera3D" parent="."] script = ExtResource("2_w5otl") @@ -36,6 +37,7 @@ layout_mode = 2 [node name="FileNameLineEdit" type="LineEdit" parent="UI/PanelContainer/MarginContainer/VBoxContainer"] unique_name_in_owner = true layout_mode = 2 +text = "/home/jarrod/Dev/thief/de-specs/test_data/rose-garden.mis" [node name="HBoxContainer" type="HBoxContainer" parent="UI/PanelContainer/MarginContainer/VBoxContainer"] layout_mode = 2