diff --git a/project/code/TMV/MeshSurfaceData.cs b/project/code/TMV/MeshSurfaceData.cs new file mode 100644 index 0000000..38fe28a --- /dev/null +++ b/project/code/TMV/MeshSurfaceData.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using Godot; +using GArray = Godot.Collections.Array; + +namespace KeepersCompound.TMV; + +// TODO: Add UV transform method. Should take a transform function and a range of UVs to apply it to +public class MeshSurfaceData +{ + const string MATERIAL_PATH = "res://project/materials/base.tres"; + + public bool Empty { get; private set; } + + private readonly Texture _texture; + private readonly List _vertices = new(); + private readonly List _normals = new(); + private readonly List _indices = new(); + private readonly List _textureUvs = new(); + private readonly List _lightmapUvs = new(); + + public MeshSurfaceData(Texture texture) + { + _texture = texture; + Empty = true; + } + + public void TransformLightmapUvs(int start, int end, Func f) + { + for (var i = start; i < end; i++) + { + _lightmapUvs[i] = f(_lightmapUvs[i]); + } + } + + // TODO: Guard against empty polygons being added + public (int, int) AddPolygon( + List vertices, + Vector3 normal, + List textureUvs, + List lightmapUvs) + { + Empty = false; + + var vertexCount = vertices.Count; + var indexOffset = _vertices.Count; + _vertices.AddRange(vertices); + + for (var i = 0; i < vertexCount; i++) + { + _normals.Add(normal); + } + + // Simple triangulation. Polys are always convex so we can just do a fan + for (int j = 1; j < vertexCount - 1; j++) + { + _indices.Add(indexOffset); + _indices.Add(indexOffset + j); + _indices.Add(indexOffset + j + 1); + } + + _textureUvs.AddRange(textureUvs); + _lightmapUvs.AddRange(lightmapUvs); + + var end = _lightmapUvs.Count; + var start = end - lightmapUvs.Count; + return (start, end); + } + + public GArray BuildSurfaceArray() + { + var array = new GArray(); + array.Resize((int)Mesh.ArrayType.Max); + array[(int)Mesh.ArrayType.Vertex] = _vertices.ToArray(); + array[(int)Mesh.ArrayType.Normal] = _normals.ToArray(); + array[(int)Mesh.ArrayType.Index] = _indices.ToArray(); + array[(int)Mesh.ArrayType.TexUV] = _textureUvs.ToArray(); + array[(int)Mesh.ArrayType.TexUV2] = _lightmapUvs.ToArray(); + + return array; + } + + public Material BuildMaterial(Texture lightmapTexture) + { + var material = ResourceLoader.Load(MATERIAL_PATH).Duplicate() as ShaderMaterial; + material.SetShaderParameter("texture_albedo", _texture); + material.SetShaderParameter("lightmap_albedo", lightmapTexture); + return material; + } +} \ No newline at end of file diff --git a/project/code/TMV/Mission.cs b/project/code/TMV/Mission.cs index c7a156f..f8aad49 100644 --- a/project/code/TMV/Mission.cs +++ b/project/code/TMV/Mission.cs @@ -13,6 +13,24 @@ namespace KeepersCompound.TMV; public partial class Mission : Node3D { + private readonly struct LightmapRectData + { + public readonly int cellIndex; + public readonly int lightmapIndex; + public readonly int textureId; + public readonly int uvStart; + public readonly int uvEnd; + + public LightmapRectData(int cellIndex, int lightmapIndex, int textureId, int uvStart, int uvEnd) + { + this.cellIndex = cellIndex; + this.lightmapIndex = lightmapIndex; + this.textureId = textureId; + this.uvStart = uvStart; + this.uvEnd = uvEnd; + } + } + [Export(PropertyHint.GlobalFile, "*.mis")] public string FileName { get; set; } [Export] @@ -83,67 +101,112 @@ public partial class Mission : Node3D var wr = (WorldRep)_file.Chunks["WREXT"]; - foreach (var cell in wr.Cells) - { - BuildCellMesh(cell); - } + BuildMeshes(wr.Cells); + + // foreach (var cell in wr.Cells) + // { + // BuildCellMesh(cell); + // } } - private void BuildCellMesh(WorldRep.Cell cell) + private void BuildMeshes(WorldRep.Cell[] cells) { - var numPolys = cell.PolyCount; - var numRenderPolys = cell.RenderPolyCount; - var numPortalPolys = cell.PortalPolyCount; + var packingRects = new List(); + var surfaceDataMap = new Dictionary(); + var rectDataMap = new Dictionary(); - // There's nothing to render - if (numRenderPolys == 0 || numPortalPolys >= numPolys) + for (var cellIdx = 0; cellIdx < cells.Length; cellIdx++) { - return; - } + var cell = cells[cellIdx]; + var numPolys = cell.PolyCount; + var numRenderPolys = cell.RenderPolyCount; + var numPortalPolys = cell.PortalPolyCount; - // 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 surfaceArrays = BuildSurfaceArrays(cell, maxPolyIdx, out var packingRects); - - Image lightmap = BuildLightmap(cell, packingRects, surfaceArrays); - - // !Hack: This should be somewhere else? - var materials = new List(); - for (var i = 0; i < maxPolyIdx; i++) - { - var textureId = cell.RenderPolys[i].TextureId; - // !HACK: Sky textures :) - if (textureId >= _textures.Count) + // There's nothing to render + if (numRenderPolys == 0 || numPortalPolys >= numPolys) { - textureId = 0; + continue; } - var material = ResourceLoader.Load("res://project/materials/base.tres").Duplicate() as ShaderMaterial; - material.SetShaderParameter("texture_albedo", _textures[textureId]); - material.SetShaderParameter("lightmap_albedo", ImageTexture.CreateFromImage(lightmap)); - materials.Add(material); + // 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 cellIdxOffset = 0; + for (int polyIdx = 0; polyIdx < maxPolyIdx; polyIdx++) + { + var lightmapRectData = ProcessCellSurfaceData(surfaceDataMap, cell, cellIdx, polyIdx, cellIdxOffset); + rectDataMap.Add(packingRects.Count, lightmapRectData); + + var light = cell.LightList[polyIdx]; + var rect = new PackingRectangle(0, 0, light.Width, light.Height, packingRects.Count); + packingRects.Add(rect); + + cellIdxOffset += cell.Polys[polyIdx].VertexCount; + } } - MeshInstance3D mesh = GenerateMesh(surfaceArrays, materials); - OccluderInstance3D occluderInstance = GenerateOccluder(surfaceArrays); + var lightmapTexture = BuildLightmapTexture(cells, packingRects.ToArray(), rectDataMap, surfaceDataMap); + foreach (var surface in surfaceDataMap.Values) + { + if (surface.Empty) + { + continue; + } - var cellNode = new Node3D(); - cellNode.AddChild(mesh); - cellNode.AddChild(occluderInstance); + var array = surface.BuildSurfaceArray(); + var material = surface.BuildMaterial(lightmapTexture); + var mesh = new ArrayMesh(); - AddChild(cellNode); + mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array); + mesh.SurfaceSetMaterial(0, material); + var meshInstance = new MeshInstance3D { Mesh = mesh }; + AddChild(meshInstance); + } } - private static Image BuildLightmap(WorldRep.Cell cell, PackingRectangle[] packingRects, List surfaceArrays) + private LightmapRectData ProcessCellSurfaceData(Dictionary surfaceDataMap, WorldRep.Cell cell, int cellIdx, int polyIdx, int indicesOffset) + { + var poly = cell.Polys[polyIdx]; + var normal = cell.Planes[poly.PlaneId].Normal.ToGodotVec3(); + var vertices = new List(); + var textureUvs = new List(); + var lightmapUvs = new List(); + + var numPolyVertices = poly.VertexCount; + for (var j = 0; j < numPolyVertices; j++) + { + var vertex = cell.Vertices[cell.Indices[indicesOffset + j]]; + vertices.Add(vertex.ToGodotVec3()); + } + + var renderPoly = cell.RenderPolys[polyIdx]; + var light = cell.LightList[polyIdx]; + var textureId = CalcBaseUV(cell, poly, renderPoly, light, textureUvs, lightmapUvs, indicesOffset); + + if (!surfaceDataMap.ContainsKey(textureId)) + { + surfaceDataMap.Add(textureId, new MeshSurfaceData(_textures[textureId])); + } + var surfaceData = surfaceDataMap[textureId]; + var (start, end) = surfaceData.AddPolygon(vertices, normal, textureUvs, lightmapUvs); + + if (polyIdx >= cell.Lightmaps.Length) GD.Print("HUH"); + + return new LightmapRectData(cellIdx, polyIdx, textureId, start, end); + } + + private static Texture BuildLightmapTexture(WorldRep.Cell[] cells, PackingRectangle[] packingRects, Dictionary rectDataMap, Dictionary surfaceDataMap) { 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 + if (!rectDataMap.ContainsKey(rect.Id)) GD.Print("Invalid rectDataMap key"); + var info = rectDataMap[rect.Id]; + + if (info.cellIndex >= cells.Length) GD.Print($"CellIndex too big: {info.cellIndex}/{cells.Length}"); + if (info.lightmapIndex >= cells[info.cellIndex].Lightmaps.Length) GD.Print($"LightmapIndex too big: {info.lightmapIndex}/{cells[info.cellIndex].Lightmaps.Length}"); + var lightmap = cells[info.cellIndex].Lightmaps[info.lightmapIndex]; var layers = (uint)lightmap.Pixels.GetLength(0); var height = (uint)lightmap.Pixels.GetLength(1); var width = (uint)lightmap.Pixels.GetLength(2); @@ -162,11 +225,9 @@ public partial class Mission : Node3D } } - // Transform UVs - var lightmapUvs = surfaceArrays[rect.Id][(int)Mesh.ArrayType.TexUV2].As(); - for (var i = 0; i < lightmapUvs.Length; i++) + if (!surfaceDataMap.ContainsKey(info.textureId)) GD.Print("Invalid SurfaceDataMap key"); + surfaceDataMap[info.textureId].TransformLightmapUvs(info.uvStart, info.uvEnd, (uv) => { - var uv = lightmapUvs[i]; var u = uv.X; var v = uv.Y; @@ -179,113 +240,14 @@ public partial class Mission : Node3D // Transform! u = (rect.X + rect.Width * u) / (int)bounds.Width; v = (rect.Y + rect.Height * v) / (int)bounds.Height; - lightmapUvs[i] = new Vector2(u, v); - } - surfaceArrays[rect.Id][(int)Mesh.ArrayType.TexUV2] = lightmapUvs; + return new Vector2(u, v); + }); } - return image; + return ImageTexture.CreateFromImage(image); } - private List BuildSurfaceArrays( - WorldRep.Cell cell, - int maxPolyIdx, - out PackingRectangle[] packingRects) - { - packingRects = new PackingRectangle[maxPolyIdx]; - - var surfacesArrays = new List(); - var cellIdxOffset = 0; - for (int i = 0; i < maxPolyIdx; i++) - { - var vertices = new List(); - var normals = new List(); - var indices = new List(); - var textureUvs = new List(); - var lightmapUvs = new List(); - - var poly = cell.Polys[i]; - 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(0); - indices.Add(j); - indices.Add(j + 1); - } - - // UVs - var renderPoly = cell.RenderPolys[i]; - var light = cell.LightList[i]; - packingRects[i] = new PackingRectangle(0, 0, light.Width, light.Height, i); - CalcBaseUV(cell, poly, renderPoly, light, textureUvs, lightmapUvs, cellIdxOffset); - - cellIdxOffset += poly.VertexCount; - - var array = new Godot.Collections.Array(); - array.Resize((int)Mesh.ArrayType.Max); - array[(int)Mesh.ArrayType.Vertex] = vertices.ToArray(); - array[(int)Mesh.ArrayType.Normal] = normals.ToArray(); - array[(int)Mesh.ArrayType.Index] = indices.ToArray(); - array[(int)Mesh.ArrayType.TexUV] = textureUvs.ToArray(); - array[(int)Mesh.ArrayType.TexUV2] = lightmapUvs.ToArray(); - surfacesArrays.Add(array); - } - - return surfacesArrays; - } - - // TODO: This is broke? - private static OccluderInstance3D GenerateOccluder(List surfaceArrays) - { - var vertices = new List(); - var indices = new List(); - foreach (var array in surfaceArrays) - { - var count = vertices.Count; - vertices.AddRange(array[(int)Mesh.ArrayType.Vertex].As()); - var surfaceIndices = array[(int)Mesh.ArrayType.Index].As(); - foreach (var idx in surfaceIndices) - { - indices.Add(count + idx); - } - } - - var occluder = new ArrayOccluder3D(); - occluder.SetArrays(vertices.ToArray(), indices.ToArray()); - var occluderInstance = new OccluderInstance3D - { - Occluder = occluder, - BakeSimplificationDistance = 0.0f - }; - return occluderInstance; - } - - private static MeshInstance3D GenerateMesh(List surfaceArrays, List materials) - { - var arrMesh = new ArrayMesh(); - for (var i = 0; i < surfaceArrays.Count; i++) - { - arrMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, surfaceArrays[i]); - arrMesh.SurfaceSetMaterial(i, materials[i]); - } - - var meshInstance = new MeshInstance3D - { - Mesh = arrMesh, - }; - return meshInstance; - } - - private void CalcBaseUV( + private int CalcBaseUV( WorldRep.Cell cell, WorldRep.Cell.Poly poly, WorldRep.Cell.RenderPoly renderPoly, @@ -364,6 +326,8 @@ public partial class Mission : Node3D lightmapUvs.Add(lmUV); } } + + return textureId; } private void LoadTextures(TxList textureList)