Compare commits

..

No commits in common. "2ec121e056701a7a2055b5a8d875e8f5eb0e2693" and "1508ed111d1b4ba43811672173fffdd2892590ae" have entirely different histories.

3 changed files with 153 additions and 206 deletions

View File

@ -9,28 +9,10 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
namespace KeepersCompound.TMV; namespace KeepersCompound;
public partial class Mission : Node3D 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")] [Export(PropertyHint.GlobalFile, "*.mis")]
public string FileName { get; set; } public string FileName { get; set; }
[Export] [Export]
@ -101,112 +83,67 @@ public partial class Mission : Node3D
var wr = (WorldRep)_file.Chunks["WREXT"]; var wr = (WorldRep)_file.Chunks["WREXT"];
BuildMeshes(wr.Cells); foreach (var cell in wr.Cells)
{
// foreach (var cell in wr.Cells) BuildCellMesh(cell);
// { }
// BuildCellMesh(cell);
// }
} }
private void BuildMeshes(WorldRep.Cell[] cells) private void BuildCellMesh(WorldRep.Cell cell)
{ {
var packingRects = new List<PackingRectangle>(); var numPolys = cell.PolyCount;
var surfaceDataMap = new Dictionary<int, MeshSurfaceData>(); var numRenderPolys = cell.RenderPolyCount;
var rectDataMap = new Dictionary<int, LightmapRectData>(); var numPortalPolys = cell.PortalPolyCount;
for (var cellIdx = 0; cellIdx < cells.Length; cellIdx++) // There's nothing to render
if (numRenderPolys == 0 || numPortalPolys >= numPolys)
{ {
var cell = cells[cellIdx]; return;
var numPolys = cell.PolyCount;
var numRenderPolys = cell.RenderPolyCount;
var numPortalPolys = cell.PortalPolyCount;
// There's nothing to render
if (numRenderPolys == 0 || numPortalPolys >= numPolys)
{
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 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;
}
} }
var lightmapTexture = BuildLightmapTexture(cells, packingRects.ToArray(), rectDataMap, surfaceDataMap); // You'd think these would be the same number, but apparently not
foreach (var surface in surfaceDataMap.Values) // 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++)
{ {
if (surface.Empty) var textureId = cell.RenderPolys[i].TextureId;
// !HACK: Sky textures :)
if (textureId >= _textures.Count)
{ {
continue; textureId = 0;
} }
var array = surface.BuildSurfaceArray(); var material = ResourceLoader.Load<ShaderMaterial>("res://project/materials/base.tres").Duplicate() as ShaderMaterial;
var material = surface.BuildMaterial(lightmapTexture); material.SetShaderParameter("texture_albedo", _textures[textureId]);
var mesh = new ArrayMesh(); material.SetShaderParameter("lightmap_albedo", ImageTexture.CreateFromImage(lightmap));
materials.Add(material);
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);
var cellNode = new Node3D();
cellNode.AddChild(mesh);
cellNode.AddChild(occluderInstance);
AddChild(cellNode);
} }
private LightmapRectData ProcessCellSurfaceData(Dictionary<int, MeshSurfaceData> surfaceDataMap, WorldRep.Cell cell, int cellIdx, int polyIdx, int indicesOffset) private static Image BuildLightmap(WorldRep.Cell cell, PackingRectangle[] packingRects, List<Godot.Collections.Array> surfaceArrays)
{
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 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<int, LightmapRectData> rectDataMap, Dictionary<int, MeshSurfaceData> surfaceDataMap)
{ {
RectanglePacker.Pack(packingRects, out var bounds); RectanglePacker.Pack(packingRects, out var bounds);
var image = Image.Create((int)bounds.Width, (int)bounds.Height, false, Image.Format.Rgba8); var image = Image.Create((int)bounds.Width, (int)bounds.Height, false, Image.Format.Rgba8);
foreach (var rect in packingRects) foreach (var rect in packingRects)
{ {
if (!rectDataMap.ContainsKey(rect.Id)) GD.Print("Invalid rectDataMap key"); // Build lightmap
var info = rectDataMap[rect.Id]; var lightmap = cell.Lightmaps[rect.Id];
// TODO: Handle animlight layers
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 layers = (uint)lightmap.Pixels.GetLength(0);
var height = (uint)lightmap.Pixels.GetLength(1); var height = (uint)lightmap.Pixels.GetLength(1);
var width = (uint)lightmap.Pixels.GetLength(2); var width = (uint)lightmap.Pixels.GetLength(2);
@ -225,9 +162,11 @@ public partial class Mission : Node3D
} }
} }
if (!surfaceDataMap.ContainsKey(info.textureId)) GD.Print("Invalid SurfaceDataMap key"); // Transform UVs
surfaceDataMap[info.textureId].TransformLightmapUvs(info.uvStart, info.uvEnd, (uv) => var lightmapUvs = surfaceArrays[rect.Id][(int)Mesh.ArrayType.TexUV2].As<Vector2[]>();
for (var i = 0; i < lightmapUvs.Length; i++)
{ {
var uv = lightmapUvs[i];
var u = uv.X; var u = uv.X;
var v = uv.Y; var v = uv.Y;
@ -240,14 +179,113 @@ public partial class Mission : Node3D
// Transform! // Transform!
u = (rect.X + rect.Width * u) / (int)bounds.Width; u = (rect.X + rect.Width * u) / (int)bounds.Width;
v = (rect.Y + rect.Height * v) / (int)bounds.Height; v = (rect.Y + rect.Height * v) / (int)bounds.Height;
return new Vector2(u, v); lightmapUvs[i] = new Vector2(u, v);
}); }
surfaceArrays[rect.Id][(int)Mesh.ArrayType.TexUV2] = lightmapUvs;
} }
return ImageTexture.CreateFromImage(image); return image;
} }
private int CalcBaseUV( 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(
WorldRep.Cell cell, WorldRep.Cell cell,
WorldRep.Cell.Poly poly, WorldRep.Cell.Poly poly,
WorldRep.Cell.RenderPoly renderPoly, WorldRep.Cell.RenderPoly renderPoly,
@ -326,8 +364,6 @@ public partial class Mission : Node3D
lightmapUvs.Add(lmUV); lightmapUvs.Add(lmUV);
} }
} }
return textureId;
} }
private void LoadTextures(TxList textureList) private void LoadTextures(TxList textureList)

View File

@ -1,90 +0,0 @@
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<Vector3> _vertices = new();
private readonly List<Vector3> _normals = new();
private readonly List<int> _indices = new();
private readonly List<Vector2> _textureUvs = new();
private readonly List<Vector2> _lightmapUvs = new();
public MeshSurfaceData(Texture texture)
{
_texture = texture;
Empty = true;
}
public void TransformLightmapUvs(int start, int end, Func<Vector2, Vector2> 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<Vector3> vertices,
Vector3 normal,
List<Vector2> textureUvs,
List<Vector2> 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<ShaderMaterial>(MATERIAL_PATH).Duplicate() as ShaderMaterial;
material.SetShaderParameter("texture_albedo", _texture);
material.SetShaderParameter("lightmap_albedo", lightmapTexture);
return material;
}
}

View File

@ -1,6 +1,6 @@
[gd_scene load_steps=4 format=3 uid="uid://boxi211q3kx6c"] [gd_scene load_steps=4 format=3 uid="uid://boxi211q3kx6c"]
[ext_resource type="Script" path="res://project/code/TMV/Mission.cs" id="1_3gnqe"] [ext_resource type="Script" path="res://project/code/Mission.cs" id="1_xhqt7"]
[ext_resource type="Script" path="res://project/code/camera.gd" id="2_w5otl"] [ext_resource type="Script" path="res://project/code/camera.gd" id="2_w5otl"]
[ext_resource type="PackedScene" uid="uid://cekg1xb5f0ux1" path="res://project/scenes/ui/mission_selector.tscn" id="3_hwfcj"] [ext_resource type="PackedScene" uid="uid://cekg1xb5f0ux1" path="res://project/scenes/ui/mission_selector.tscn" id="3_hwfcj"]
@ -8,7 +8,8 @@
[node name="Mission" type="Node3D" parent="."] [node name="Mission" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.110309, 0.187101, -0.461656) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.110309, 0.187101, -0.461656)
script = ExtResource("1_3gnqe") script = ExtResource("1_xhqt7")
FileName = "/home/jarrod/Dev/thief/de-specs/test_data/rose-garden.mis"
[node name="Camera3D" type="Camera3D" parent="."] [node name="Camera3D" type="Camera3D" parent="."]
script = ExtResource("2_w5otl") script = ExtResource("2_w5otl")