thief-mission-viewer/project/code/TMV/ModelLoader.cs

194 lines
6.9 KiB
C#
Raw Normal View History

using System.Collections.Generic;
using System.IO;
using Godot;
using KeepersCompound.LGS;
namespace KeepersCompound.TMV;
2024-09-19 20:16:07 +00:00
// TODO: Work out a way to share base game models again in the cache
2024-08-26 11:38:13 +00:00
public class ModelLoader
{
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();
2024-08-26 11:38:13 +00:00
public MeshDetails[] Load(string modelName, bool forceLoad = false)
2024-08-26 11:38:13 +00:00
{
2024-09-19 20:16:07 +00:00
var campaignResources = Context.Instance.CampaignResources;
var campaignName = campaignResources.name;
2024-08-26 11:38:13 +00:00
campaignName ??= "";
2024-08-26 11:38:13 +00:00
if (!forceLoad)
{
if (_cache.TryGetValue((campaignName, modelName), out var fmDetails))
{
return fmDetails;
}
if (_cache.TryGetValue(("", modelName), out var omDetails))
{
return omDetails;
}
}
var details = Timing.TimeStage("Load Models", () => LoadModel(modelName));
_cache[(campaignName, modelName)] = details;
return details;
}
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)
2024-08-26 11:38:13 +00:00
{
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));
2024-08-26 11:38:13 +00:00
}
else
2024-08-26 11:38:13 +00:00
{
mesh.SetTransform(details.Transform);
2024-08-26 11:38:13 +00:00
}
meshes.Add(mesh);
2024-08-26 11:38:13 +00:00
}
return [..meshes];
2024-08-26 11:38:13 +00:00
}
public static MeshDetails[] LoadModel(string modelName)
{
2024-09-19 20:16:07 +00:00
var campaignResources = Context.Instance.CampaignResources;
var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName);
if (modelPath == null)
{
return [];
}
2024-09-19 20:16:07 +00:00
var modelFile = new ModelFile(modelPath);
if (modelFile == null)
{
GD.Print($"Failed to load model file: {modelPath}");
return [];
}
var materials = new List<StandardMaterial3D>();
foreach (var material in modelFile.Materials)
{
if (material.Type == 0)
{
2024-09-07 07:01:15 +00:00
var convertedName = ResourcePathManager.ConvertSeparator(material.Name);
var resName = Path.GetFileNameWithoutExtension(convertedName);
2024-09-19 20:16:07 +00:00
var path = campaignResources.GetResourcePath(ResourceType.ObjectTexture, resName);
2024-08-25 15:49:43 +00:00
if (path == null)
{
2024-09-07 12:43:11 +00:00
// Might fail in exported projects
path = "res://project/jorge.png";
2024-08-25 15:49:43 +00:00
GD.Print($"Failed to load model texture: {material.Name}");
}
2024-08-31 10:59:54 +00:00
var mat = new StandardMaterial3D
{
2024-09-01 12:51:05 +00:00
AlbedoTexture = TextureLoader.LoadTexture(path),
Transparency = BaseMaterial3D.TransparencyEnum.AlphaDepthPrePass,
2024-08-31 10:59:54 +00:00
};
var name = material.Name.ToLower();
for (var i = 0; i < 4; i++)
{
if (name.Contains($"replace{i}"))
{
mat.SetMeta($"TxtRepl{i}", true);
}
}
materials.Add(mat);
}
else
{
var b = (material.Handle) & 0xff;
var g = (material.Handle >> 8) & 0xff;
var r = (material.Handle >> 16) & 0xff;
var colour = new Color(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f);
materials.Add(new StandardMaterial3D
{
AlbedoColor = colour
});
}
}
var objCount = modelFile.Objects.Length;
var meshDetails = new MeshDetails[objCount];
for (var i = 0; i < objCount; i++)
{
var subObj = modelFile.Objects[i];
var jointIdx = subObj.Joint;
var transform = subObj.Type == 0 ? Transform3D.Identity : subObj.Transform.ToGodotTransform3D();
var surfaceDataMap = new Dictionary<int, MeshSurfaceData>();
foreach (var poly in modelFile.Polygons)
{
var v0 = poly.VertexIndices[0];
if (v0 < subObj.PointIdx || v0 >= subObj.PointIdx + subObj.PointCount)
{
continue;
}
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++)
{
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;
}
}
}
var pos = -modelFile.Header.Center.ToGodotVec3();
var meshInstance = new MeshInstance3D { Mesh = mesh, Position = pos };
meshDetails[i] = new MeshDetails(jointIdx, transform, meshInstance);
}
return meshDetails;
}
}