using System.Collections.Generic; using System.IO; using Godot; using KeepersCompound.LGS; namespace KeepersCompound.TMV; public class ModelLoader { private readonly Dictionary<(string, string), MeshInstance3D> _cache = new(); public MeshInstance3D Load(string campaignName, string modelName, bool forceLoad = false) { campaignName ??= ""; if (!forceLoad) { if (_cache.TryGetValue((campaignName, modelName), out var fmModel)) { return fmModel?.Duplicate() as MeshInstance3D; } else if (_cache.TryGetValue(("", modelName), out var omModel)) { return omModel?.Duplicate() as MeshInstance3D; } } // 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(ref campaignName, modelName); }); _cache[(campaignName, modelName)] = model; return model?.Duplicate() as MeshInstance3D; } public static MeshInstance3D LoadModel(ref string campaignName, string modelName) { var pathManager = Context.Instance.PathManager; var (newCampaignName, modelPath) = pathManager.GetResourcePath(ResourceType.Object, campaignName, modelName); campaignName = newCampaignName; if (modelPath == null) { return null; } var modelFile = new ModelFile(modelPath); if (modelFile == null) { GD.Print($"Failed to load model file: {modelPath}"); return null; } var materials = new List(); foreach (var material in modelFile.Materials) { if (material.Type == 0) { var convertedName = ResourcePathManager.ConvertSeparator(material.Name); var resName = Path.GetFileNameWithoutExtension(convertedName); var (_, path) = pathManager.GetResourcePath(ResourceType.ObjectTexture, campaignName, resName); if (path == null) { // Might fail in exported projects path = "res://project/jorge.png"; GD.Print($"Failed to load model texture: {material.Name}"); } var mat = new StandardMaterial3D { AlbedoTexture = TextureLoader.LoadTexture(path), Transparency = BaseMaterial3D.TransparencyEnum.AlphaDepthPrePass, }; 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 surfaceDataMap = new Dictionary(); foreach (var poly in modelFile.Polygons) { var vertices = new List(); var normal = modelFile.Normals[poly.Normal].ToGodotVec3(); var uvs = new List(); for (var i = 0; i < poly.VertexCount; i++) { var vertex = modelFile.Vertices[poly.VertexIndices[i]]; vertices.Add(vertex.ToGodotVec3()); if (i < poly.UvIndices.Length) { var uv = modelFile.Uvs[poly.UvIndices[i]]; 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 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; } }