using Godot; using KeepersCompound.LGS; using KeepersCompound.LGS.Database; using KeepersCompound.LGS.Database.Chunks; using KeepersCompound.TMV.UI; using RectpackSharp; using System; using System.Collections.Generic; namespace KeepersCompound; [Tool] public partial class Mission : Node3D { [Export(PropertyHint.GlobalFile, "*.mis")] public string FileName { get; set; } [Export] public bool Build = false; [Export] public bool Clear = false; DbFile _file; public override void _Ready() { var missionSelector = GetNode("%MissionSelector") as MissionSelector; missionSelector.LoadMission += (string path) => { FileName = path; Build = true; }; } public override void _Process(double delta) { if (Build) { RebuildMap(); Build = false; } if (Clear) { ClearMap(); Clear = false; } } public override void _Input(InputEvent @event) { if (@event is InputEventKey keyEvent && keyEvent.Pressed) { if (keyEvent.Keycode == Key.R) { Build = true; } } } public void ClearMap() { foreach (var node in GetChildren()) { node.QueueFree(); } } public void RebuildMap() { ClearMap(); _file = new(FileName); var wr = (WorldRep)_file.Chunks["WREXT"]; foreach (var cell in wr.Cells) { BuildCellMesh(cell); } } private void BuildCellMesh(WorldRep.Cell cell) { var numPolys = cell.PolyCount; var numRenderPolys = cell.RenderPolyCount; var numPortalPolys = cell.PortalPolyCount; // There's nothing to render if (numRenderPolys == 0 || numPortalPolys >= numPolys) { return; } 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 meshIdxOffset = vertices.Count; var normal = cell.Planes[poly.PlaneId].Normal.ToGodotVec3(); var numPolyVertices = poly.VertexCount; for (var j = 0; j < numPolyVertices; j++) { var vertex = cell.Vertices[cell.Indices[cellIdxOffset + j]]; vertices.Add(vertex.ToGodotVec3()); normals.Add(normal); } // Simple triangulation. Polys are always convex so we can just do a fan for (int j = 1; j < numPolyVertices - 1; j++) { indices.Add(meshIdxOffset); indices.Add(meshIdxOffset + j); 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 layers = (uint)lightmap.Pixels.GetLength(0); var height = (uint)lightmap.Pixels.GetLength(1); var width = (uint)lightmap.Pixels.GetLength(2); for (uint y = 0; y < height; y++) { for (uint x = 0; x < width; x++) { var rawColour = System.Numerics.Vector4.Zero; for (uint l = 0; l < layers; l++) { rawColour += lightmap.GetPixel(l, x, y); } var colour = new Color(MathF.Min(rawColour.X, 1.0f), MathF.Min(rawColour.Y, 1.0f), MathF.Min(rawColour.Z, 1.0f), MathF.Min(rawColour.W, 1.0f)); image.SetPixel((int)(rect.X + x), (int)(rect.Y + y), colour); } } // 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 { Mesh = arrMesh, CastShadow = GeometryInstance3D.ShadowCastingSetting.On }; cellNode.AddChild(meshInstance); var occluder = new ArrayOccluder3D(); occluder.SetArrays(vertices.ToArray(), indices.ToArray()); var occluderInstance = new OccluderInstance3D { Occluder = occluder, BakeSimplificationDistance = 0.0f }; cellNode.AddChild(occluderInstance); var r = new Random(); if (r.NextSingle() > 0.9 && cell.SphereRadius > 5.0) { var light = new OmniLight3D { Position = cell.SphereCenter.ToGodotVec3(), OmniRange = cell.SphereRadius * (r.NextSingle() + 1.0f) * 0.5f, }; // 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(); var texV = renderPoly.TextureVectors.Item2.ToGodotVec3(); 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(); // 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(); 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(); 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; } }