From b5d3a3c27534d6ff5e5fc5c318b4d3abd58df4a2 Mon Sep 17 00:00:00 2001 From: Jarrod Doyle Date: Sat, 17 Aug 2024 20:53:16 +0100 Subject: [PATCH] Add initial basic .BIN model support --- project/code/LGS/ModelFile.cs | 182 ++++++++++++++++++++++++++ project/code/TMV/InstallPaths.cs | 2 + project/code/TMV/Model.cs | 59 +++++++++ project/code/TMV/UI/ModelSelector.cs | 94 +++++++++++++ project/scenes/model_viewer.tscn | 25 ++++ project/scenes/ui/model_selector.tscn | 99 ++++++++++++++ 6 files changed, 461 insertions(+) create mode 100644 project/code/LGS/ModelFile.cs create mode 100644 project/code/TMV/Model.cs create mode 100644 project/code/TMV/UI/ModelSelector.cs create mode 100644 project/scenes/model_viewer.tscn create mode 100644 project/scenes/ui/model_selector.tscn diff --git a/project/code/LGS/ModelFile.cs b/project/code/LGS/ModelFile.cs new file mode 100644 index 0000000..181b28a --- /dev/null +++ b/project/code/LGS/ModelFile.cs @@ -0,0 +1,182 @@ +using System; +using System.IO; +using System.Numerics; +using System.Text; + +namespace KeepersCompound.LGS; + +// TODO: Remove all the things that don't actually need to be stored +public class ModelFile +{ + public readonly struct BHeader + { + public string Signature { get; } + public int Version { get; } + + public BHeader(BinaryReader reader) + { + Signature = Encoding.UTF8.GetString(reader.ReadBytes(4)).Replace("\0", string.Empty); + Version = reader.ReadInt32(); + } + } + + public readonly struct MHeader + { + public string Name { get; } + public float Radius { get; } + public float MaxPolygonRadius { get; } + public Vector3 MaxBounds { get; } + public Vector3 MinBounds { get; } + public Vector3 Center { get; } + + public ushort PolygonCount { get; } + public ushort VertexCount { get; } + public ushort ParameterCount { get; } + public byte MaterialCount { get; } + public byte VCallCount { get; } + public byte VHotCount { get; } + public byte ObjectCount { get; } + + public uint ObjectOffset { get; } + public uint MaterialOffset { get; } + public uint UvOffset { get; } + public uint VHotOffset { get; } + public uint VertexOffset { get; } + public uint LightOffset { get; } + public uint NormalOffset { get; } + public uint PolygonOffset { get; } + public uint NodeOffset { get; } + + public uint ModelSize { get; } + + public uint AuxMaterialFlags { get; } + public uint AuxMaterialOffset { get; } + public uint AuxMaterialSize { get; } + + public MHeader(BinaryReader reader, int version) + { + var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(8)).Replace("\0", string.Empty); + Name = tmpName[..Math.Min(7, tmpName.Length)]; + Radius = reader.ReadSingle(); + MaxPolygonRadius = reader.ReadSingle(); + MaxBounds = reader.ReadVec3(); + MinBounds = reader.ReadVec3(); + Center = reader.ReadVec3(); + PolygonCount = reader.ReadUInt16(); + VertexCount = reader.ReadUInt16(); + ParameterCount = reader.ReadUInt16(); + MaterialCount = reader.ReadByte(); + VCallCount = reader.ReadByte(); + VHotCount = reader.ReadByte(); + ObjectCount = reader.ReadByte(); + ObjectOffset = reader.ReadUInt32(); + MaterialOffset = reader.ReadUInt32(); + UvOffset = reader.ReadUInt32(); + VHotOffset = reader.ReadUInt32(); + VertexOffset = reader.ReadUInt32(); + LightOffset = reader.ReadUInt32(); + NormalOffset = reader.ReadUInt32(); + PolygonOffset = reader.ReadUInt32(); + NodeOffset = reader.ReadUInt32(); + ModelSize = reader.ReadUInt32(); + + if (version == 4) + { + AuxMaterialFlags = reader.ReadUInt32(); + AuxMaterialOffset = reader.ReadUInt32(); + AuxMaterialSize = reader.ReadUInt32(); + } + else + { + AuxMaterialFlags = 0; + AuxMaterialOffset = 0; + AuxMaterialSize = 0; + } + } + } + + public struct Polygon + { + public ushort Index; + public ushort Data; + public byte Type; + public byte VertexCount; + public ushort Normal; + public float D; + public ushort[] VertexIndices; + public ushort[] LightIndices; + public ushort[] UvIndices; + public byte Material; + + public Polygon(BinaryReader reader, int version) + { + Index = reader.ReadUInt16(); + Data = reader.ReadUInt16(); + Type = reader.ReadByte(); + VertexCount = reader.ReadByte(); + Normal = reader.ReadUInt16(); + D = reader.ReadSingle(); + VertexIndices = new ushort[VertexCount]; + for (var i = 0; i < VertexCount; i++) + { + VertexIndices[i] = reader.ReadUInt16(); + } + LightIndices = new ushort[VertexCount]; + for (var i = 0; i < VertexCount; i++) + { + LightIndices[i] = reader.ReadUInt16(); + } + UvIndices = new ushort[Type == 0x1B ? VertexCount : 0]; + for (var i = 0; i < UvIndices.Length; i++) + { + LightIndices[i] = reader.ReadUInt16(); + } + + Material = version == 4 ? reader.ReadByte() : (byte)0; + } + } + + public BHeader BinHeader { get; set; } + public MHeader Header { get; set; } + public Vector3[] Vertices { get; } + public Vector2[] Uvs { get; } + public Vector3[] Normals { get; } + public Polygon[] Polygons { get; } + + public ModelFile(string filename) + { + if (!File.Exists(filename)) return; + + using MemoryStream stream = new(File.ReadAllBytes(filename)); + using BinaryReader reader = new(stream, Encoding.UTF8, false); + + BinHeader = new BHeader(reader); + if (BinHeader.Signature != "LGMD") return; + + Header = new MHeader(reader, BinHeader.Version); + stream.Seek(Header.VertexOffset, SeekOrigin.Begin); + Vertices = new Vector3[Header.VertexCount]; + for (var i = 0; i < Vertices.Length; i++) + { + Vertices[i] = reader.ReadVec3(); + } + stream.Seek(Header.UvOffset, SeekOrigin.Begin); + Uvs = new Vector2[(Header.VHotOffset - Header.UvOffset) / 8]; + for (var i = 0; i < Uvs.Length; i++) + { + Uvs[i] = reader.ReadVec2(); + } + stream.Seek(Header.NormalOffset, SeekOrigin.Begin); + Normals = new Vector3[(Header.PolygonOffset - Header.NormalOffset) / 12]; + for (var i = 0; i < Normals.Length; i++) + { + Normals[i] = reader.ReadVec3(); + } + stream.Seek(Header.PolygonOffset, SeekOrigin.Begin); + Polygons = new Polygon[Header.PolygonCount]; + for (var i = 0; i < Polygons.Length; i++) + { + Polygons[i] = new Polygon(reader, BinHeader.Version); + } + } +} \ No newline at end of file diff --git a/project/code/TMV/InstallPaths.cs b/project/code/TMV/InstallPaths.cs index 56f3298..8aa79c5 100644 --- a/project/code/TMV/InstallPaths.cs +++ b/project/code/TMV/InstallPaths.cs @@ -8,6 +8,7 @@ public class InstallPaths { public string rootPath; public string famPath; + public string objPath; public string omsPath; public string fmsPath; @@ -21,6 +22,7 @@ public class InstallPaths rootPath = root; famPath = Directory.GetFiles(rootPath, "fam.crf", searchOptions)[0]; + objPath = Directory.GetFiles(rootPath, "obj.crf", searchOptions)[0]; var paths = Directory.GetFiles(rootPath, "install.cfg", searchOptions); if (paths.Length == 0) { diff --git a/project/code/TMV/Model.cs b/project/code/TMV/Model.cs new file mode 100644 index 0000000..bbff099 --- /dev/null +++ b/project/code/TMV/Model.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using Godot; +using Godot.Collections; +using KeepersCompound.LGS; +using KeepersCompound.TMV.UI; + +namespace KeepersCompound.TMV; + +public partial class Model : Node3D +{ + public override void _Ready() + { + var modelSelector = GetNode("%ModelSelector") as ModelSelector; + modelSelector.LoadModel += BuildModel; + } + + private void BuildModel(string rootPath, string modelPath) + { + foreach (var node in GetChildren()) + { + node.QueueFree(); + } + + var modelFile = new ModelFile(modelPath); + if (modelFile == null) + { + GD.Print("UH OH"); + } + + var vertices = new List(); + var indices = new List(); + + foreach (var v in modelFile.Vertices) + { + vertices.Add(v.ToGodotVec3()); + } + + foreach (var poly in modelFile.Polygons) + { + for (var i = 1; i < poly.VertexCount - 1; i++) + { + indices.Add(poly.VertexIndices[0]); + indices.Add(poly.VertexIndices[i]); + indices.Add(poly.VertexIndices[i + 1]); + } + } + + var array = new Array(); + array.Resize((int)Mesh.ArrayType.Max); + array[(int)Mesh.ArrayType.Vertex] = vertices.ToArray(); + array[(int)Mesh.ArrayType.Index] = indices.ToArray(); + + var mesh = new ArrayMesh(); + mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array); + + var meshInstance = new MeshInstance3D { Mesh = mesh }; + AddChild(meshInstance); + } +} diff --git a/project/code/TMV/UI/ModelSelector.cs b/project/code/TMV/UI/ModelSelector.cs new file mode 100644 index 0000000..79fe286 --- /dev/null +++ b/project/code/TMV/UI/ModelSelector.cs @@ -0,0 +1,94 @@ +using System.IO; +using System.IO.Compression; +using System.Linq; +using Godot; + +namespace KeepersCompound.TMV.UI; + +public partial class ModelSelector : Control +{ + [Signal] + public delegate void LoadModelEventHandler(string rootPath, string modelPath); + + private InstallPaths _installPaths; + + private FileDialog _FolderSelect; + private LineEdit _FolderPath; + private Button _BrowseButton; + private ItemList _Models; + private Button _LoadButton; + private Button _CancelButton; + + private string _extractedObjectsPath = ProjectSettings.GlobalizePath($"user://objects/tmp"); + + public override void _Ready() + { + // TODO: Load initial folderpath from config and prefil everything + + _FolderSelect = GetNode("%FolderSelect"); + _FolderPath = GetNode("%FolderPath"); + _BrowseButton = GetNode