From 7dc1912390dccaa5adb0789d14d507c9f4ae0426 Mon Sep 17 00:00:00 2001 From: Jarrod Doyle Date: Sat, 7 Dec 2024 12:39:25 +0000 Subject: [PATCH] Model SubObjects are now separate meshes and can have joints applied correctly --- project/code/TMV/Mission.cs | 56 +++++----- project/code/TMV/Model.cs | 8 +- project/code/TMV/ModelLoader.cs | 185 +++++++++++++++++--------------- 3 files changed, 139 insertions(+), 110 deletions(-) diff --git a/project/code/TMV/Mission.cs b/project/code/TMV/Mission.cs index fb7af0f..e14d673 100644 --- a/project/code/TMV/Mission.cs +++ b/project/code/TMV/Mission.cs @@ -261,16 +261,17 @@ public partial class Mission : Node3D var pos = brush.position.ToGodotVec3(); var rot = brush.angle.ToGodotVec3(false); var scale = scaleProp == null ? Vector3.One : scaleProp.value.ToGodotVec3(false); - var model = Timing.TimeStage("Get Models", () => - { - return Context.Instance.ModelLoader.Load(modelName); - }); - if (model != null) + var meshDetails = Timing.TimeStage("Get Models", () => Context.Instance.ModelLoader.Load(modelName)); + if (meshDetails.Length != 0) { + var model = new Node3D(); model.Position = pos; model.RotationDegrees = rot; model.Scale = scale; - + + // TODO: Apply real joints + var meshes = ModelLoader.TransformMeshes([45, 180, 0, 0, 0, 0], meshDetails); + bool GetTextReplPath(PropString prop, out string path) { path = ""; @@ -299,35 +300,40 @@ public partial class Mission : Node3D } var repls = new PropString[] { txtRepl0, txtRepl1, txtRepl2, txtRepl3 }; - for (var i = 0; i < 4; i++) + foreach (var meshInstance in meshes) { - if (GetTextReplPath(repls[i], out var path)) + for (var i = 0; i < 4; i++) { - var overrideMat = new StandardMaterial3D + if (GetTextReplPath(repls[i], out var path)) { - AlbedoTexture = TextureLoader.LoadTexture(path), - Transparency = BaseMaterial3D.TransparencyEnum.AlphaDepthPrePass, - }; - - var surfaceCount = model.Mesh.GetSurfaceCount(); - for (var idx = 0; idx < surfaceCount; idx++) - { - var surfaceMat = model.Mesh.SurfaceGetMaterial(idx); - if (surfaceMat.HasMeta($"TxtRepl{i}")) + var overrideMat = new StandardMaterial3D { - model.SetSurfaceOverrideMaterial(idx, overrideMat); + AlbedoTexture = TextureLoader.LoadTexture(path), + Transparency = BaseMaterial3D.TransparencyEnum.AlphaDepthPrePass, + }; + + var surfaceCount = meshInstance.Mesh.GetSurfaceCount(); + for (var idx = 0; idx < surfaceCount; idx++) + { + var surfaceMat = meshInstance.Mesh.SurfaceGetMaterial(idx); + if (surfaceMat.HasMeta($"TxtRepl{i}")) + { + meshInstance.SetSurfaceOverrideMaterial(idx, overrideMat); + } } } } - } - if (renderAlpha != null) - { - model.Transparency = 1.0f - renderAlpha.value; + if (renderAlpha != null) + { + meshInstance.Transparency = 1.0f - renderAlpha.value; + } + + model.AddChild(meshInstance); } - + model.AddToGroup(OBJECT_MODELS_GROUP); - + AddChild(model); } } diff --git a/project/code/TMV/Model.cs b/project/code/TMV/Model.cs index 944e24e..b6ddfa7 100644 --- a/project/code/TMV/Model.cs +++ b/project/code/TMV/Model.cs @@ -19,7 +19,13 @@ public partial class Model : Node3D } Context.Instance.SetCampaign(campaignName); - var model = Context.Instance.ModelLoader.Load(modelPath); + var model = new Node3D(); + var meshDetails = Context.Instance.ModelLoader.Load(modelPath); + var meshes = ModelLoader.TransformMeshes([0, 0, 0, 0, 0, 0], meshDetails); + foreach (var meshInstance in meshes) + { + model.AddChild(meshInstance); + } AddChild(model); } } diff --git a/project/code/TMV/ModelLoader.cs b/project/code/TMV/ModelLoader.cs index 3595595..ee2cc74 100644 --- a/project/code/TMV/ModelLoader.cs +++ b/project/code/TMV/ModelLoader.cs @@ -2,82 +2,83 @@ using System.Collections.Generic; using System.IO; using Godot; using KeepersCompound.LGS; -using Quaternion = System.Numerics.Quaternion; namespace KeepersCompound.TMV; // TODO: Work out a way to share base game models again in the cache public class ModelLoader { - private readonly Dictionary<(string, string), MeshInstance3D> _cache = new(); + public struct MeshDetails(int jointIdx, Transform3D transform, MeshInstance3D mesh) + { + public readonly int JointIdx = jointIdx; + public readonly Transform3D Transform = transform; + public readonly MeshInstance3D Mesh = mesh; + } + + private readonly Dictionary<(string, string), MeshDetails[]> _cache = new(); - public MeshInstance3D Load(string modelName, bool forceLoad = false) + public MeshDetails[] Load(string modelName, bool forceLoad = false) { var campaignResources = Context.Instance.CampaignResources; var campaignName = campaignResources.name; campaignName ??= ""; - + if (!forceLoad) { - if (_cache.TryGetValue((campaignName, modelName), out var fmModel)) + if (_cache.TryGetValue((campaignName, modelName), out var fmDetails)) { - return fmModel?.Duplicate() as MeshInstance3D; + return fmDetails; } - else if (_cache.TryGetValue(("", modelName), out var omModel)) + if (_cache.TryGetValue(("", modelName), out var omDetails)) { - return omModel?.Duplicate() as MeshInstance3D; + return omDetails; } } - - // We don't care if this is null actually, we'll still cache that it's null lol - var model = Timing.TimeStage("Load Models", () => { return LoadModel(modelName); }); - _cache[(campaignName, modelName)] = model; - return model?.Duplicate() as MeshInstance3D; + + var details = Timing.TimeStage("Load Models", () => LoadModel(modelName)); + _cache[(campaignName, modelName)] = details; + return details; } - public static MeshInstance3D LoadModel(string modelName) + public static MeshInstance3D[] TransformMeshes(float[] joints, MeshDetails[] meshDetails) + { + var meshes = new List(); + foreach (var details in meshDetails) + { + var mesh = details.Mesh.Duplicate() as MeshInstance3D; + if (details.JointIdx != -1) + { + var ang = float.DegreesToRadians(joints[details.JointIdx]); + var r1 = new Quaternion(new Vector3(0, 0, 1), ang); + var r2 = details.Transform.Basis.GetRotationQuaternion(); + var basis = new Basis(r2 * r1); + mesh.SetTransform(new Transform3D(basis, details.Transform.Origin)); + } + else + { + mesh.SetTransform(details.Transform); + } + + meshes.Add(mesh); + } + + return [..meshes]; + } + + public static MeshDetails[] LoadModel(string modelName) { var campaignResources = Context.Instance.CampaignResources; var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName); if (modelPath == null) { - return null; + return []; } var modelFile = new ModelFile(modelPath); if (modelFile == null) { GD.Print($"Failed to load model file: {modelPath}"); - return null; - } - - // Transform subobjs - // TODO: Traverse children properly - // TODO: Apply to normals - // TODO: Apply joints(??) - // TODO: Handle Slide joints - // !HACK: Hardcoded joint :)) - var ang = float.DegreesToRadians(45); - foreach (var subObj in modelFile.Objects) - { - if (subObj.Type == 0) - { - continue; - } - - var translation = subObj.Transform.Translation; - var rotation = subObj.Transform; - rotation.Translation = System.Numerics.Vector3.Zero; - var jointRotation = Quaternion.CreateFromYawPitchRoll(0, ang, 0); - - for (var i = subObj.PointIdx; i < subObj.PointIdx + subObj.PointCount; i++) - { - var v = modelFile.Vertices[i]; - v = System.Numerics.Vector3.Transform(v, jointRotation); - v = System.Numerics.Vector3.Transform(v, rotation); - v += translation; - modelFile.Vertices[i] = v; - } + return []; } var materials = new List(); @@ -124,54 +125,70 @@ public class ModelLoader } } - var surfaceDataMap = new Dictionary(); - foreach (var poly in modelFile.Polygons) + var objCount = modelFile.Objects.Length; + var meshDetails = new MeshDetails[objCount]; + for (var i = 0; i < objCount; i++) { - var vertices = new List(); - var normal = modelFile.Normals[poly.Normal].ToGodotVec3(); - var uvs = new List(); - for (var i = 0; i < poly.VertexCount; i++) + var subObj = modelFile.Objects[i]; + var jointIdx = subObj.Joint; + var transform = subObj.Type == 0 ? Transform3D.Identity : subObj.Transform.ToGodotTransform3D(); + var surfaceDataMap = new Dictionary(); + foreach (var poly in modelFile.Polygons) { - var vertex = modelFile.Vertices[poly.VertexIndices[i]]; - vertices.Add(vertex.ToGodotVec3()); - if (i < poly.UvIndices.Length) + var v0 = poly.VertexIndices[0]; + if (v0 < subObj.PointIdx || v0 >= subObj.PointIdx + subObj.PointCount) { - var uv = modelFile.Uvs[poly.UvIndices[i]]; - uvs.Add(new Vector2(uv.X, uv.Y)); + continue; } - else + + var vertices = new List(); + var normal = modelFile.Normals[poly.Normal].ToGodotVec3(); + var uvs = new List(); + for (var j = 0; j < poly.VertexCount; j++) { - uvs.Add(Vector2.Zero); + var vertex = modelFile.Vertices[poly.VertexIndices[j]]; + vertices.Add(vertex.ToGodotVec3()); + if (j < poly.UvIndices.Length) + { + var uv = modelFile.Uvs[poly.UvIndices[j]]; + uvs.Add(new Vector2(uv.X, uv.Y)); + } + else + { + uvs.Add(Vector2.Zero); + } + } + + if (!surfaceDataMap.ContainsKey(poly.Data)) + { + surfaceDataMap.Add(poly.Data, new MeshSurfaceData()); + } + + surfaceDataMap[poly.Data].AddPolygon(vertices, normal, uvs, uvs); + } + + var mesh = new ArrayMesh(); + foreach (var (materialId, surfaceData) in surfaceDataMap) + { + var array = surfaceData.BuildSurfaceArray(); + var surfaceIdx = mesh.GetSurfaceCount(); + mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array); + for (var j = 0; j < materials.Count; j++) + { + var m = modelFile.Materials[j]; + if (m.Slot == materialId) + { + mesh.SurfaceSetMaterial(surfaceIdx, materials[j]); + break; + } } } - if (!surfaceDataMap.ContainsKey(poly.Data)) - { - surfaceDataMap.Add(poly.Data, new MeshSurfaceData()); - } - - surfaceDataMap[poly.Data].AddPolygon(vertices, normal, uvs, uvs); + var pos = -modelFile.Header.Center.ToGodotVec3(); + var meshInstance = new MeshInstance3D { Mesh = mesh, Position = pos }; + meshDetails[i] = new MeshDetails(jointIdx, transform, meshInstance); } - var mesh = new ArrayMesh(); - foreach (var (materialId, surfaceData) in surfaceDataMap) - { - var array = surfaceData.BuildSurfaceArray(); - var surfaceIdx = mesh.GetSurfaceCount(); - mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array); - for (var i = 0; i < materials.Count; i++) - { - var m = modelFile.Materials[i]; - if (m.Slot == materialId) - { - mesh.SurfaceSetMaterial(surfaceIdx, materials[i]); - break; - } - } - } - - var pos = -modelFile.Header.Center.ToGodotVec3(); - var meshInstance = new MeshInstance3D { Mesh = mesh, Position = pos }; - return meshInstance; + return meshDetails; } } \ No newline at end of file