|
|
|
@ -9,10 +9,28 @@ using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
|
|
namespace KeepersCompound;
|
|
|
|
|
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,14 +101,23 @@ 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 packingRects = new List<PackingRectangle>();
|
|
|
|
|
var surfaceDataMap = new Dictionary<int, MeshSurfaceData>();
|
|
|
|
|
var rectDataMap = new Dictionary<int, LightmapRectData>();
|
|
|
|
|
|
|
|
|
|
for (var cellIdx = 0; cellIdx < cells.Length; cellIdx++)
|
|
|
|
|
{
|
|
|
|
|
var cell = cells[cellIdx];
|
|
|
|
|
var numPolys = cell.PolyCount;
|
|
|
|
|
var numRenderPolys = cell.RenderPolyCount;
|
|
|
|
|
var numPortalPolys = cell.PortalPolyCount;
|
|
|
|
@ -98,52 +125,88 @@ public partial class Mission : Node3D
|
|
|
|
|
// There's nothing to render
|
|
|
|
|
if (numRenderPolys == 0 || numPortalPolys >= numPolys)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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<Material>();
|
|
|
|
|
for (var i = 0; i < maxPolyIdx; i++)
|
|
|
|
|
var cellIdxOffset = 0;
|
|
|
|
|
for (int polyIdx = 0; polyIdx < maxPolyIdx; polyIdx++)
|
|
|
|
|
{
|
|
|
|
|
var textureId = cell.RenderPolys[i].TextureId;
|
|
|
|
|
// !HACK: Sky textures :)
|
|
|
|
|
if (textureId >= _textures.Count)
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var lightmapTexture = BuildLightmapTexture(cells, packingRects.ToArray(), rectDataMap, surfaceDataMap);
|
|
|
|
|
foreach (var surface in surfaceDataMap.Values)
|
|
|
|
|
{
|
|
|
|
|
textureId = 0;
|
|
|
|
|
if (surface.Empty)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var material = ResourceLoader.Load<ShaderMaterial>("res://project/materials/base.tres").Duplicate() as ShaderMaterial;
|
|
|
|
|
material.SetShaderParameter("texture_albedo", _textures[textureId]);
|
|
|
|
|
material.SetShaderParameter("lightmap_albedo", ImageTexture.CreateFromImage(lightmap));
|
|
|
|
|
materials.Add(material);
|
|
|
|
|
var array = surface.BuildSurfaceArray();
|
|
|
|
|
var material = surface.BuildMaterial(lightmapTexture);
|
|
|
|
|
var mesh = new ArrayMesh();
|
|
|
|
|
|
|
|
|
|
mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array);
|
|
|
|
|
mesh.SurfaceSetMaterial(0, material);
|
|
|
|
|
var meshInstance = new MeshInstance3D { Mesh = mesh };
|
|
|
|
|
AddChild(meshInstance);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MeshInstance3D mesh = GenerateMesh(surfaceArrays, materials);
|
|
|
|
|
OccluderInstance3D occluderInstance = GenerateOccluder(surfaceArrays);
|
|
|
|
|
private LightmapRectData ProcessCellSurfaceData(Dictionary<int, MeshSurfaceData> 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<Vector3>();
|
|
|
|
|
var textureUvs = new List<Vector2>();
|
|
|
|
|
var lightmapUvs = new List<Vector2>();
|
|
|
|
|
|
|
|
|
|
var cellNode = new Node3D();
|
|
|
|
|
cellNode.AddChild(mesh);
|
|
|
|
|
cellNode.AddChild(occluderInstance);
|
|
|
|
|
|
|
|
|
|
AddChild(cellNode);
|
|
|
|
|
var numPolyVertices = poly.VertexCount;
|
|
|
|
|
for (var j = 0; j < numPolyVertices; j++)
|
|
|
|
|
{
|
|
|
|
|
var vertex = cell.Vertices[cell.Indices[indicesOffset + j]];
|
|
|
|
|
vertices.Add(vertex.ToGodotVec3());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Image BuildLightmap(WorldRep.Cell cell, PackingRectangle[] packingRects, List<Godot.Collections.Array> surfaceArrays)
|
|
|
|
|
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<int, LightmapRectData> rectDataMap, Dictionary<int, MeshSurfaceData> 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<Vector2[]>();
|
|
|
|
|
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<Godot.Collections.Array> BuildSurfaceArrays(
|
|
|
|
|
WorldRep.Cell cell,
|
|
|
|
|
int maxPolyIdx,
|
|
|
|
|
out PackingRectangle[] packingRects)
|
|
|
|
|
{
|
|
|
|
|
packingRects = new PackingRectangle[maxPolyIdx];
|
|
|
|
|
|
|
|
|
|
var surfacesArrays = new List<Godot.Collections.Array>();
|
|
|
|
|
var cellIdxOffset = 0;
|
|
|
|
|
for (int i = 0; i < maxPolyIdx; i++)
|
|
|
|
|
{
|
|
|
|
|
var vertices = new List<Vector3>();
|
|
|
|
|
var normals = new List<Vector3>();
|
|
|
|
|
var indices = new List<int>();
|
|
|
|
|
var textureUvs = new List<Vector2>();
|
|
|
|
|
var lightmapUvs = new List<Vector2>();
|
|
|
|
|
|
|
|
|
|
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<Godot.Collections.Array> surfaceArrays)
|
|
|
|
|
{
|
|
|
|
|
var vertices = new List<Vector3>();
|
|
|
|
|
var indices = new List<int>();
|
|
|
|
|
foreach (var array in surfaceArrays)
|
|
|
|
|
{
|
|
|
|
|
var count = vertices.Count;
|
|
|
|
|
vertices.AddRange(array[(int)Mesh.ArrayType.Vertex].As<Vector3[]>());
|
|
|
|
|
var surfaceIndices = array[(int)Mesh.ArrayType.Index].As<int[]>();
|
|
|
|
|
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<Godot.Collections.Array> surfaceArrays, List<Material> 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)
|