Compare commits
12 Commits
50c4d06f19
...
b2fc789734
Author | SHA1 | Date |
---|---|---|
Jarrod Doyle | b2fc789734 | |
Jarrod Doyle | 74f4d0e7c6 | |
Jarrod Doyle | da3f9b9203 | |
Jarrod Doyle | 79f7ebd7eb | |
Jarrod Doyle | 770f4e22d5 | |
Jarrod Doyle | 714c9804d8 | |
Jarrod Doyle | 114997f720 | |
Jarrod Doyle | 5dceedeaf1 | |
Jarrod Doyle | 91bddd5c12 | |
Jarrod Doyle | c9353f0100 | |
Jarrod Doyle | d405a9b5db | |
Jarrod Doyle | fe5d2fb12f |
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
public class TxList : IChunk
|
||||||
|
{
|
||||||
|
public struct Item
|
||||||
|
{
|
||||||
|
public byte[] Tokens { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public Item(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Tokens = reader.ReadBytes(4);
|
||||||
|
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(16)).Replace("\0", string.Empty);
|
||||||
|
Name = tmpName[..Math.Min(15, tmpName.Length)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
|
||||||
|
public int BlockSize { get; set; }
|
||||||
|
public int ItemCount { get; set; }
|
||||||
|
public int TokenCount { get; set; }
|
||||||
|
public string[] Tokens { get; set; }
|
||||||
|
public Item[] Items { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
BlockSize = reader.ReadInt32();
|
||||||
|
ItemCount = reader.ReadInt32();
|
||||||
|
TokenCount = reader.ReadInt32();
|
||||||
|
Tokens = new string[TokenCount];
|
||||||
|
for (var i = 0; i < TokenCount; i++)
|
||||||
|
{
|
||||||
|
var tmpToken = Encoding.UTF8.GetString(reader.ReadBytes(16)).Replace("\0", string.Empty);
|
||||||
|
Tokens[i] = tmpToken[..Math.Min(16, tmpToken.Length)];
|
||||||
|
}
|
||||||
|
Items = new Item[ItemCount];
|
||||||
|
for (var i = 0; i < ItemCount; i++)
|
||||||
|
{
|
||||||
|
Items[i] = new Item(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -98,6 +98,7 @@ public class DbFile
|
||||||
{
|
{
|
||||||
// "AI_ROOM_DB" => new AiRoomDb(),
|
// "AI_ROOM_DB" => new AiRoomDb(),
|
||||||
// "AICONVERSE" => new AiConverseChunk(),
|
// "AICONVERSE" => new AiConverseChunk(),
|
||||||
|
"TXLIST" => new TxList(),
|
||||||
"WREXT" => new WorldRep(),
|
"WREXT" => new WorldRep(),
|
||||||
_ => new GenericChunk(),
|
_ => new GenericChunk(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
using Godot.NativeInterop;
|
||||||
using KeepersCompound.LGS;
|
using KeepersCompound.LGS;
|
||||||
using KeepersCompound.LGS.Database;
|
using KeepersCompound.LGS.Database;
|
||||||
using KeepersCompound.LGS.Database.Chunks;
|
using KeepersCompound.LGS.Database.Chunks;
|
||||||
|
using KeepersCompound.TMV.UI;
|
||||||
using RectpackSharp;
|
using RectpackSharp;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace KeepersCompound;
|
namespace KeepersCompound;
|
||||||
|
|
||||||
|
@ -17,16 +20,22 @@ public partial class Mission : Node3D
|
||||||
public bool Build = false;
|
public bool Build = false;
|
||||||
[Export]
|
[Export]
|
||||||
public bool Clear = false;
|
public bool Clear = false;
|
||||||
|
[Export]
|
||||||
|
public bool Dump = false;
|
||||||
|
|
||||||
DbFile _file;
|
DbFile _file;
|
||||||
|
List<ImageTexture> _textures;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
var fileNameLineEdit = GetNode<LineEdit>("%FileNameLineEdit");
|
_textures = new List<ImageTexture>();
|
||||||
fileNameLineEdit.Text = FileName;
|
|
||||||
fileNameLineEdit.TextSubmitted += (string text) => FileName = text;
|
var missionSelector = GetNode<Control>("%MissionSelector") as MissionSelector;
|
||||||
GetNode<Button>("%BuildButton").Pressed += () => RebuildMap();
|
missionSelector.LoadMission += (string path) =>
|
||||||
GetNode<Button>("%ClearButton").Pressed += () => ClearMap();
|
{
|
||||||
|
FileName = path;
|
||||||
|
Build = true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
|
@ -67,6 +76,10 @@ public partial class Mission : Node3D
|
||||||
ClearMap();
|
ClearMap();
|
||||||
|
|
||||||
_file = new(FileName);
|
_file = new(FileName);
|
||||||
|
var textureList = (TxList)_file.Chunks["TXLIST"];
|
||||||
|
LoadTextures(textureList);
|
||||||
|
if (Dump) DumpTextureList(textureList);
|
||||||
|
|
||||||
var wr = (WorldRep)_file.Chunks["WREXT"];
|
var wr = (WorldRep)_file.Chunks["WREXT"];
|
||||||
|
|
||||||
foreach (var cell in wr.Cells)
|
foreach (var cell in wr.Cells)
|
||||||
|
@ -87,49 +100,42 @@ public partial class Mission : Node3D
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var vertices = new List<Vector3>();
|
|
||||||
var normals = new List<Vector3>();
|
|
||||||
var indices = new List<int>();
|
|
||||||
var lightmapUvs = new List<Vector2>();
|
|
||||||
var cellIdxOffset = 0;
|
|
||||||
|
|
||||||
// You'd think these would be the same number, but apparently not
|
// 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
|
// I think it's because water counts as a render poly and a portal poly
|
||||||
var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys);
|
var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys);
|
||||||
var packingRects = new PackingRectangle[maxPolyIdx];
|
var surfaceArrays = BuildSurfaceArrays(cell, maxPolyIdx, out var packingRects);
|
||||||
var rectIdToUvIdxMap = new List<int[]>();
|
|
||||||
for (int i = 0; i < maxPolyIdx; i++)
|
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 poly = cell.Polys[i];
|
var textureId = cell.RenderPolys[i].TextureId;
|
||||||
var meshIdxOffset = vertices.Count;
|
// !HACK: Sky textures :)
|
||||||
|
if (textureId >= _textures.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]];
|
textureId = 0;
|
||||||
vertices.Add(vertex.ToGodotVec3());
|
|
||||||
normals.Add(normal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple triangulation. Polys are always convex so we can just do a fan
|
var material = ResourceLoader.Load<ShaderMaterial>("res://project/materials/base.tres").Duplicate() as ShaderMaterial;
|
||||||
for (int j = 1; j < numPolyVertices - 1; j++)
|
material.SetShaderParameter("texture_albedo", _textures[textureId]);
|
||||||
{
|
material.SetShaderParameter("lightmap_albedo", ImageTexture.CreateFromImage(lightmap));
|
||||||
indices.Add(meshIdxOffset);
|
materials.Add(material);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MeshInstance3D mesh = GenerateMesh(surfaceArrays, materials);
|
||||||
|
OccluderInstance3D occluderInstance = GenerateOccluder(surfaceArrays);
|
||||||
|
|
||||||
|
var cellNode = new Node3D();
|
||||||
|
cellNode.AddChild(mesh);
|
||||||
|
cellNode.AddChild(occluderInstance);
|
||||||
|
|
||||||
|
AddChild(cellNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Image BuildLightmap(WorldRep.Cell cell, PackingRectangle[] packingRects, List<Godot.Collections.Array> surfaceArrays)
|
||||||
|
{
|
||||||
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)
|
||||||
|
@ -149,16 +155,17 @@ public partial class Mission : Node3D
|
||||||
{
|
{
|
||||||
rawColour += lightmap.GetPixel(l, x, y);
|
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));
|
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);
|
image.SetPixel((int)(rect.X + x), (int)(rect.Y + y), colour);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform UVs
|
// Transform UVs
|
||||||
var lmUvIdxs = rectIdToUvIdxMap[rect.Id];
|
var lightmapUvs = surfaceArrays[rect.Id][(int)Mesh.ArrayType.TexUV2].As<Vector2[]>();
|
||||||
foreach (var idx in lmUvIdxs)
|
for (var i = 0; i < lightmapUvs.Length; i++)
|
||||||
{
|
{
|
||||||
var uv = lightmapUvs[idx];
|
var uv = lightmapUvs[i];
|
||||||
var u = uv.X;
|
var u = uv.X;
|
||||||
var v = uv.Y;
|
var v = uv.Y;
|
||||||
|
|
||||||
|
@ -171,36 +178,85 @@ 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;
|
||||||
lightmapUvs[idx] = new Vector2(u, v);
|
lightmapUvs[i] = new Vector2(u, v);
|
||||||
}
|
}
|
||||||
|
surfaceArrays[rect.Id][(int)Mesh.ArrayType.TexUV2] = lightmapUvs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
var cellNode = new Node3D();
|
private List<Godot.Collections.Array> BuildSurfaceArrays(
|
||||||
|
WorldRep.Cell cell,
|
||||||
|
int maxPolyIdx,
|
||||||
|
out PackingRectangle[] packingRects)
|
||||||
|
{
|
||||||
|
packingRects = new PackingRectangle[maxPolyIdx];
|
||||||
|
|
||||||
var material = new StandardMaterial3D
|
var surfacesArrays = new List<Godot.Collections.Array>();
|
||||||
|
var cellIdxOffset = 0;
|
||||||
|
for (int i = 0; i < maxPolyIdx; i++)
|
||||||
{
|
{
|
||||||
AlbedoTexture = ImageTexture.CreateFromImage(image),
|
var vertices = new List<Vector3>();
|
||||||
TextureFilter = BaseMaterial3D.TextureFilterEnum.Nearest,
|
var normals = new List<Vector3>();
|
||||||
};
|
var indices = new List<int>();
|
||||||
|
var textureUvs = new List<Vector2>();
|
||||||
|
var lightmapUvs = new List<Vector2>();
|
||||||
|
|
||||||
var arrMesh = new ArrayMesh();
|
var poly = cell.Polys[i];
|
||||||
var arrays = new Godot.Collections.Array();
|
var normal = cell.Planes[poly.PlaneId].Normal.ToGodotVec3();
|
||||||
arrays.Resize((int)Mesh.ArrayType.Max);
|
var numPolyVertices = poly.VertexCount;
|
||||||
arrays[(int)Mesh.ArrayType.Vertex] = vertices.ToArray();
|
for (var j = 0; j < numPolyVertices; j++)
|
||||||
arrays[(int)Mesh.ArrayType.Normal] = normals.ToArray();
|
{
|
||||||
arrays[(int)Mesh.ArrayType.Index] = indices.ToArray();
|
var vertex = cell.Vertices[cell.Indices[cellIdxOffset + j]];
|
||||||
arrays[(int)Mesh.ArrayType.TexUV] = lightmapUvs.ToArray();
|
vertices.Add(vertex.ToGodotVec3());
|
||||||
|
normals.Add(normal);
|
||||||
|
}
|
||||||
|
|
||||||
arrMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, arrays);
|
// Simple triangulation. Polys are always convex so we can just do a fan
|
||||||
arrMesh.SurfaceSetMaterial(0, material);
|
for (int j = 1; j < numPolyVertices - 1; j++)
|
||||||
|
{
|
||||||
|
indices.Add(0);
|
||||||
|
indices.Add(j);
|
||||||
|
indices.Add(j + 1);
|
||||||
|
}
|
||||||
|
|
||||||
var meshInstance = new MeshInstance3D
|
// 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)
|
||||||
{
|
{
|
||||||
Mesh = arrMesh,
|
var count = vertices.Count;
|
||||||
CastShadow = GeometryInstance3D.ShadowCastingSetting.On
|
vertices.AddRange(array[(int)Mesh.ArrayType.Vertex].As<Vector3[]>());
|
||||||
};
|
var surfaceIndices = array[(int)Mesh.ArrayType.Index].As<int[]>();
|
||||||
cellNode.AddChild(meshInstance);
|
foreach (var idx in surfaceIndices)
|
||||||
|
{
|
||||||
|
indices.Add(count + idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var occluder = new ArrayOccluder3D();
|
var occluder = new ArrayOccluder3D();
|
||||||
occluder.SetArrays(vertices.ToArray(), indices.ToArray());
|
occluder.SetArrays(vertices.ToArray(), indices.ToArray());
|
||||||
|
@ -209,82 +265,157 @@ public partial class Mission : Node3D
|
||||||
Occluder = occluder,
|
Occluder = occluder,
|
||||||
BakeSimplificationDistance = 0.0f
|
BakeSimplificationDistance = 0.0f
|
||||||
};
|
};
|
||||||
cellNode.AddChild(occluderInstance);
|
return 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(
|
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,
|
||||||
|
CastShadow = GeometryInstance3D.ShadowCastingSetting.On
|
||||||
|
};
|
||||||
|
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,
|
||||||
WorldRep.Cell.LightmapInfo light,
|
WorldRep.Cell.LightmapInfo light,
|
||||||
|
List<Vector2> textureUvs,
|
||||||
List<Vector2> lightmapUvs,
|
List<Vector2> lightmapUvs,
|
||||||
int cellIdxOffset)
|
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 slightly hardcoded for ND. Check other stuff at some point. Should be handled in LG side imo
|
||||||
// TODO: This is a mess lol
|
// TODO: This is a mess lol
|
||||||
|
var textureId = renderPoly.TextureId;
|
||||||
|
// !HACK: Sky textures :)
|
||||||
|
if (textureId >= _textures.Count)
|
||||||
|
{
|
||||||
|
textureId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var texture = _textures[textureId];
|
||||||
var texU = renderPoly.TextureVectors.Item1.ToGodotVec3();
|
var texU = renderPoly.TextureVectors.Item1.ToGodotVec3();
|
||||||
var texV = renderPoly.TextureVectors.Item2.ToGodotVec3();
|
var texV = renderPoly.TextureVectors.Item2.ToGodotVec3();
|
||||||
|
var baseU = renderPoly.TextureBases.Item1;
|
||||||
|
var baseV = renderPoly.TextureBases.Item2;
|
||||||
|
|
||||||
|
var txUScale = 64.0f / texture.GetWidth();
|
||||||
|
var txVScale = 64.0f / texture.GetHeight();
|
||||||
|
var lmUScale = 4.0f / light.Width;
|
||||||
|
var lmVScale = 4.0f / light.Height;
|
||||||
|
|
||||||
|
var txUBase = baseU * txUScale;
|
||||||
|
var txVBase = baseV * txVScale;
|
||||||
|
var lmUBase = lmUScale * (baseU + (0.5f - light.Bases.Item1) / 4.0f);
|
||||||
|
var lmVBase = lmVScale * (baseV + (0.5f - light.Bases.Item2) / 4.0f);
|
||||||
|
|
||||||
var uu = texU.Dot(texU);
|
var uu = texU.Dot(texU);
|
||||||
var vv = texV.Dot(texV);
|
var vv = texV.Dot(texV);
|
||||||
var uv = texU.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 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)
|
if (uv == 0.0)
|
||||||
{
|
{
|
||||||
|
var txUVec = texU * txUScale / uu;
|
||||||
|
var txVVec = texV * txVScale / vv;
|
||||||
var lmUVec = texU * lmUScale / uu;
|
var lmUVec = texU * lmUScale / uu;
|
||||||
var lmVVec = texV * lmVScale / vv;
|
var lmVVec = texV * lmVScale / vv;
|
||||||
for (var i = 0; i < poly.VertexCount; i++)
|
for (var i = 0; i < poly.VertexCount; i++)
|
||||||
{
|
{
|
||||||
uvIdxs[i] = lightmapUvs.Count;
|
|
||||||
|
|
||||||
var v = cell.Vertices[cell.Indices[cellIdxOffset + i]].ToGodotVec3();
|
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 delta = new Vector3(v.X - anchor.X, v.Y - anchor.Y, v.Z - anchor.Z);
|
||||||
|
var txUV = new Vector2(delta.Dot(txUVec) + txUBase, delta.Dot(txVVec) + txVBase);
|
||||||
var lmUV = new Vector2(delta.Dot(lmUVec) + lmUBase, delta.Dot(lmVVec) + lmVBase);
|
var lmUV = new Vector2(delta.Dot(lmUVec) + lmUBase, delta.Dot(lmVVec) + lmVBase);
|
||||||
|
textureUvs.Add(txUV);
|
||||||
lightmapUvs.Add(lmUV);
|
lightmapUvs.Add(lmUV);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var denom = 1.0f / (uu * vv - uv * uv);
|
var denom = 1.0f / (uu * vv - uv * uv);
|
||||||
|
|
||||||
|
var txUu = uu * txVScale * denom;
|
||||||
|
var txVv = vv * txUScale * denom;
|
||||||
|
var txUvu = txUScale * denom * uv;
|
||||||
|
var txUvv = txVScale * denom * uv;
|
||||||
var lmUu = uu * lmVScale * denom;
|
var lmUu = uu * lmVScale * denom;
|
||||||
var lmVv = vv * lmUScale * denom;
|
var lmVv = vv * lmUScale * denom;
|
||||||
var lmUvu = lmUScale * denom * uv;
|
var lmUvu = lmUScale * denom * uv;
|
||||||
var lmUvv = lmVScale * denom * uv;
|
var lmUvv = lmVScale * denom * uv;
|
||||||
for (var i = 0; i < poly.VertexCount; i++)
|
for (var i = 0; i < poly.VertexCount; i++)
|
||||||
{
|
{
|
||||||
uvIdxs[i] = lightmapUvs.Count;
|
|
||||||
|
|
||||||
var v = cell.Vertices[cell.Indices[cellIdxOffset + i]].ToGodotVec3();
|
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 delta = new Vector3(v.X - anchor.X, v.Y - anchor.Y, v.Z - anchor.Z);
|
||||||
var du = delta.Dot(texU);
|
var du = delta.Dot(texU);
|
||||||
var dv = delta.Dot(texV);
|
var dv = delta.Dot(texV);
|
||||||
|
|
||||||
|
var txUV = new Vector2(txUBase + txVv * du - txUvu * dv, txVBase + txUu * dv - txUvv * du);
|
||||||
var lmUV = new Vector2(lmUBase + lmVv * du - lmUvu * dv, lmVBase + lmUu * dv - lmUvv * du);
|
var lmUV = new Vector2(lmUBase + lmVv * du - lmUvu * dv, lmVBase + lmUu * dv - lmUvv * du);
|
||||||
|
textureUvs.Add(txUV);
|
||||||
lightmapUvs.Add(lmUV);
|
lightmapUvs.Add(lmUV);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return uvIdxs;
|
private void LoadTextures(TxList textureList)
|
||||||
|
{
|
||||||
|
// TODO: This has hardcoded .png extension and relies on you placing extracted and converted images in godot user directory
|
||||||
|
var count = textureList.ItemCount;
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var item = textureList.Items[i];
|
||||||
|
var path = "/";
|
||||||
|
for (var j = 0; j < item.Tokens.Length; j++)
|
||||||
|
{
|
||||||
|
var token = item.Tokens[j];
|
||||||
|
if (token == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
path += $"{textureList.Tokens[token - 1]}/";
|
||||||
|
}
|
||||||
|
path += item.Name + ".png"; // Hardcoded extension!
|
||||||
|
|
||||||
|
if (File.Exists(FileName + path))
|
||||||
|
{
|
||||||
|
path = FileName + path;
|
||||||
|
}
|
||||||
|
else if (File.Exists(ProjectSettings.GlobalizePath($"user://textures{path}")))
|
||||||
|
{
|
||||||
|
path = ProjectSettings.GlobalizePath($"user://textures{path}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
path = "user://textures/jorge.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Dump) GD.Print($"Loading texture: {path}");
|
||||||
|
_textures.Add(ImageTexture.CreateFromImage(Image.LoadFromFile(path)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DumpTextureList(TxList textureList)
|
||||||
|
{
|
||||||
|
GD.Print($"TXLIST:\n BlockSize: {textureList.BlockSize}\n ItemCount: {textureList.ItemCount}\n TokenCount: {textureList.TokenCount}\n Tokens:");
|
||||||
|
for (var i = 0; i < textureList.TokenCount; i++)
|
||||||
|
{
|
||||||
|
GD.Print($" {i}: {textureList.Tokens[i]}");
|
||||||
|
}
|
||||||
|
GD.Print($" Items:");
|
||||||
|
for (var i = 0; i < textureList.ItemCount; i++)
|
||||||
|
{
|
||||||
|
var item = textureList.Items[i];
|
||||||
|
GD.Print($" {i}:\n Tokens: [{item.Tokens[0]}, {item.Tokens[1]}, {item.Tokens[2]}, {item.Tokens[3]}]\n Name: {item.Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
using System.IO;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace KeepersCompound.TMV.UI;
|
||||||
|
|
||||||
|
public partial class MissionSelector : Control
|
||||||
|
{
|
||||||
|
[Signal]
|
||||||
|
public delegate void LoadMissionEventHandler(string path);
|
||||||
|
|
||||||
|
private FileDialog _FolderSelect;
|
||||||
|
private LineEdit _FolderPath;
|
||||||
|
private Button _BrowseButton;
|
||||||
|
private ItemList _Missions;
|
||||||
|
private Button _LoadButton;
|
||||||
|
private Button _CancelButton;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
_FolderSelect = GetNode<FileDialog>("%FolderSelect");
|
||||||
|
_FolderPath = GetNode<LineEdit>("%FolderPath");
|
||||||
|
_BrowseButton = GetNode<Button>("%BrowseButton");
|
||||||
|
_Missions = GetNode<ItemList>("%Missions");
|
||||||
|
_LoadButton = GetNode<Button>("%LoadButton");
|
||||||
|
_CancelButton = GetNode<Button>("%CancelButton");
|
||||||
|
|
||||||
|
_BrowseButton.Pressed += () => _FolderSelect.Visible = true;
|
||||||
|
_FolderSelect.DirSelected += (string dir) => { _FolderPath.Text = dir; BuildMissionList(dir); };
|
||||||
|
_FolderPath.TextSubmitted += BuildMissionList;
|
||||||
|
_Missions.ItemSelected += (long _) => _LoadButton.Disabled = false;
|
||||||
|
_LoadButton.Pressed += EmitLoadMission;
|
||||||
|
_CancelButton.Pressed += () => Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Input(InputEvent @event)
|
||||||
|
{
|
||||||
|
if (@event is InputEventKey keyEvent && keyEvent.Pressed)
|
||||||
|
{
|
||||||
|
if (keyEvent.Keycode == Key.Escape)
|
||||||
|
{
|
||||||
|
Visible = !Visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildMissionList(string path)
|
||||||
|
{
|
||||||
|
_Missions.Clear();
|
||||||
|
_LoadButton.Disabled = true;
|
||||||
|
|
||||||
|
// TODO: Use install config to select paths better?
|
||||||
|
var mis = Directory.GetFiles(path, "*.mis", SearchOption.AllDirectories);
|
||||||
|
foreach (var m in mis)
|
||||||
|
{
|
||||||
|
_Missions.AddItem(m.TrimPrefix(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EmitLoadMission()
|
||||||
|
{
|
||||||
|
var selected = _Missions.GetSelectedItems();
|
||||||
|
if (selected.IsEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = _Missions.GetItemText(selected[0]);
|
||||||
|
var basePath = _FolderPath.Text;
|
||||||
|
var path = basePath + item;
|
||||||
|
EmitSignal(SignalName.LoadMission, path);
|
||||||
|
|
||||||
|
Visible = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://ck8h6dvojuegj"]
|
||||||
|
|
||||||
|
[sub_resource type="Shader" id="Shader_eumy4"]
|
||||||
|
code = "shader_type spatial;
|
||||||
|
render_mode blend_mix,depth_draw_opaque,cull_back,unshaded;
|
||||||
|
uniform sampler2D texture_albedo : source_color,filter_linear_mipmap,repeat_enable;
|
||||||
|
uniform sampler2D lightmap_albedo : source_color,filter_linear_mipmap,repeat_enable;
|
||||||
|
uniform float lightmap_modulation;
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
vec4 albedo_tex = texture(texture_albedo,UV);
|
||||||
|
vec4 lightmap_tex = texture(lightmap_albedo,UV2) * lightmap_modulation;
|
||||||
|
ALBEDO = albedo_tex.rgb * lightmap_tex.rgb;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
render_priority = 0
|
||||||
|
shader = SubResource("Shader_eumy4")
|
||||||
|
shader_parameter/lightmap_modulation = 1.0
|
|
@ -1,7 +1,9 @@
|
||||||
[gd_scene load_steps=4 format=3 uid="uid://boxi211q3kx6c"]
|
[gd_scene load_steps=6 format=3 uid="uid://boxi211q3kx6c"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://project/code/Mission.cs" id="1_xhqt7"]
|
[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="Material" uid="uid://ck8h6dvojuegj" path="res://project/materials/base.tres" id="4_vxwmj"]
|
||||||
|
|
||||||
[sub_resource type="Environment" id="Environment_oxkvl"]
|
[sub_resource type="Environment" id="Environment_oxkvl"]
|
||||||
ambient_light_source = 2
|
ambient_light_source = 2
|
||||||
|
@ -13,7 +15,7 @@ ssao_enabled = true
|
||||||
[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_xhqt7")
|
script = ExtResource("1_xhqt7")
|
||||||
FileName = "/home/jarrod/Dev/thief/de-specs/test_data/lm-test.cow"
|
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")
|
||||||
|
@ -23,34 +25,8 @@ environment = SubResource("Environment_oxkvl")
|
||||||
|
|
||||||
[node name="UI" type="CanvasLayer" parent="."]
|
[node name="UI" type="CanvasLayer" parent="."]
|
||||||
|
|
||||||
[node name="PanelContainer" type="PanelContainer" parent="UI"]
|
[node name="MissionSelector" parent="UI" instance=ExtResource("3_hwfcj")]
|
||||||
offset_right = 436.0
|
|
||||||
offset_bottom = 74.0
|
|
||||||
|
|
||||||
[node name="MarginContainer" type="MarginContainer" parent="UI/PanelContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/margin_left = 8
|
|
||||||
theme_override_constants/margin_top = 8
|
|
||||||
theme_override_constants/margin_right = 8
|
|
||||||
theme_override_constants/margin_bottom = 8
|
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="UI/PanelContainer/MarginContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="FileNameLineEdit" type="LineEdit" parent="UI/PanelContainer/MarginContainer/VBoxContainer"]
|
|
||||||
unique_name_in_owner = true
|
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"]
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||||
layout_mode = 2
|
material_overlay = ExtResource("4_vxwmj")
|
||||||
|
|
||||||
[node name="BuildButton" type="Button" parent="UI/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Build"
|
|
||||||
|
|
||||||
[node name="ClearButton" type="Button" parent="UI/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Clear"
|
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
[gd_scene load_steps=3 format=3 uid="uid://cekg1xb5f0ux1"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://project/code/TMV/UI/MissionSelector.cs" id="1_kl8qm"]
|
||||||
|
|
||||||
|
[sub_resource type="LabelSettings" id="LabelSettings_4v24o"]
|
||||||
|
font_size = 20
|
||||||
|
|
||||||
|
[node name="MissionSelector" type="Control"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 8
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_kl8qm")
|
||||||
|
|
||||||
|
[node name="FolderSelect" type="FileDialog" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
title = "Open a Directory"
|
||||||
|
initial_position = 2
|
||||||
|
size = Vector2i(636, 159)
|
||||||
|
ok_button_text = "Select Current Folder"
|
||||||
|
file_mode = 2
|
||||||
|
access = 2
|
||||||
|
use_native_dialog = true
|
||||||
|
|
||||||
|
[node name="PanelContainer" type="PanelContainer" parent="."]
|
||||||
|
custom_minimum_size = Vector2(460, 0)
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 8
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = -144.0
|
||||||
|
offset_top = -37.0
|
||||||
|
offset_right = 144.0
|
||||||
|
offset_bottom = 37.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_left = 8
|
||||||
|
theme_override_constants/margin_top = 8
|
||||||
|
theme_override_constants/margin_right = 8
|
||||||
|
theme_override_constants/margin_bottom = 8
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Mission Selector"
|
||||||
|
label_settings = SubResource("LabelSettings_4v24o")
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="FolderSelect" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/FolderSelect"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Folder"
|
||||||
|
|
||||||
|
[node name="FolderPath" type="LineEdit" parent="PanelContainer/MarginContainer/VBoxContainer/FolderSelect"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="BrowseButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/FolderSelect"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Browse"
|
||||||
|
|
||||||
|
[node name="Missions" type="ItemList" parent="PanelContainer/MarginContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 480)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Buttons" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
|
||||||
|
[node name="LoadButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/Buttons"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
disabled = true
|
||||||
|
text = "Load"
|
||||||
|
|
||||||
|
[node name="CancelButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/Buttons"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Cancel"
|
Loading…
Reference in New Issue