Model SubObjects are now separate meshes and can have joints applied correctly

This commit is contained in:
Jarrod Doyle 2024-12-07 12:39:25 +00:00
parent a35bdb8ce3
commit 7dc1912390
Signed by: Jayrude
GPG Key ID: 38B57B16E7C0ADF7
3 changed files with 139 additions and 110 deletions

View File

@ -261,16 +261,17 @@ public partial class Mission : Node3D
var pos = brush.position.ToGodotVec3(); var pos = brush.position.ToGodotVec3();
var rot = brush.angle.ToGodotVec3(false); var rot = brush.angle.ToGodotVec3(false);
var scale = scaleProp == null ? Vector3.One : scaleProp.value.ToGodotVec3(false); var scale = scaleProp == null ? Vector3.One : scaleProp.value.ToGodotVec3(false);
var model = Timing.TimeStage("Get Models", () => var meshDetails = Timing.TimeStage("Get Models", () => Context.Instance.ModelLoader.Load(modelName));
{ if (meshDetails.Length != 0)
return Context.Instance.ModelLoader.Load(modelName);
});
if (model != null)
{ {
var model = new Node3D();
model.Position = pos; model.Position = pos;
model.RotationDegrees = rot; model.RotationDegrees = rot;
model.Scale = scale; 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) bool GetTextReplPath(PropString prop, out string path)
{ {
path = ""; path = "";
@ -299,31 +300,36 @@ public partial class Mission : Node3D
} }
var repls = new PropString[] { txtRepl0, txtRepl1, txtRepl2, txtRepl3 }; 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), var overrideMat = new StandardMaterial3D
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}"))
{ {
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) if (renderAlpha != null)
{ {
model.Transparency = 1.0f - renderAlpha.value; meshInstance.Transparency = 1.0f - renderAlpha.value;
}
model.AddChild(meshInstance);
} }
model.AddToGroup(OBJECT_MODELS_GROUP); model.AddToGroup(OBJECT_MODELS_GROUP);

View File

@ -19,7 +19,13 @@ public partial class Model : Node3D
} }
Context.Instance.SetCampaign(campaignName); 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); AddChild(model);
} }
} }

View File

@ -2,16 +2,22 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using Godot; using Godot;
using KeepersCompound.LGS; using KeepersCompound.LGS;
using Quaternion = System.Numerics.Quaternion;
namespace KeepersCompound.TMV; namespace KeepersCompound.TMV;
// TODO: Work out a way to share base game models again in the cache // TODO: Work out a way to share base game models again in the cache
public class ModelLoader 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;
}
public MeshInstance3D Load(string modelName, bool forceLoad = false) private readonly Dictionary<(string, string), MeshDetails[]> _cache = new();
public MeshDetails[] Load(string modelName, bool forceLoad = false)
{ {
var campaignResources = Context.Instance.CampaignResources; var campaignResources = Context.Instance.CampaignResources;
var campaignName = campaignResources.name; var campaignName = campaignResources.name;
@ -19,65 +25,60 @@ public class ModelLoader
if (!forceLoad) 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 details = Timing.TimeStage("Load Models", () => LoadModel(modelName));
var model = Timing.TimeStage("Load Models", () => { return LoadModel(modelName); }); _cache[(campaignName, modelName)] = details;
_cache[(campaignName, modelName)] = model; return details;
return model?.Duplicate() as MeshInstance3D;
} }
public static MeshInstance3D LoadModel(string modelName) public static MeshInstance3D[] TransformMeshes(float[] joints, MeshDetails[] meshDetails)
{
var meshes = new List<MeshInstance3D>();
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 campaignResources = Context.Instance.CampaignResources;
var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName); var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName);
if (modelPath == null) if (modelPath == null)
{ {
return null; return [];
} }
var modelFile = new ModelFile(modelPath); var modelFile = new ModelFile(modelPath);
if (modelFile == null) if (modelFile == null)
{ {
GD.Print($"Failed to load model file: {modelPath}"); GD.Print($"Failed to load model file: {modelPath}");
return null; return [];
}
// 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;
}
} }
var materials = new List<StandardMaterial3D>(); var materials = new List<StandardMaterial3D>();
@ -124,54 +125,70 @@ public class ModelLoader
} }
} }
var surfaceDataMap = new Dictionary<int, MeshSurfaceData>(); var objCount = modelFile.Objects.Length;
foreach (var poly in modelFile.Polygons) var meshDetails = new MeshDetails[objCount];
for (var i = 0; i < objCount; i++)
{ {
var vertices = new List<Vector3>(); var subObj = modelFile.Objects[i];
var normal = modelFile.Normals[poly.Normal].ToGodotVec3(); var jointIdx = subObj.Joint;
var uvs = new List<Vector2>(); var transform = subObj.Type == 0 ? Transform3D.Identity : subObj.Transform.ToGodotTransform3D();
for (var i = 0; i < poly.VertexCount; i++) var surfaceDataMap = new Dictionary<int, MeshSurfaceData>();
foreach (var poly in modelFile.Polygons)
{ {
var vertex = modelFile.Vertices[poly.VertexIndices[i]]; var v0 = poly.VertexIndices[0];
vertices.Add(vertex.ToGodotVec3()); if (v0 < subObj.PointIdx || v0 >= subObj.PointIdx + subObj.PointCount)
if (i < poly.UvIndices.Length)
{ {
var uv = modelFile.Uvs[poly.UvIndices[i]]; continue;
uvs.Add(new Vector2(uv.X, uv.Y));
} }
else
var vertices = new List<Vector3>();
var normal = modelFile.Normals[poly.Normal].ToGodotVec3();
var uvs = new List<Vector2>();
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)) var pos = -modelFile.Header.Center.ToGodotVec3();
{ var meshInstance = new MeshInstance3D { Mesh = mesh, Position = pos };
surfaceDataMap.Add(poly.Data, new MeshSurfaceData()); meshDetails[i] = new MeshDetails(jointIdx, transform, meshInstance);
}
surfaceDataMap[poly.Data].AddPolygon(vertices, normal, uvs, uvs);
} }
var mesh = new ArrayMesh(); return meshDetails;
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;
} }
} }