diff --git a/project/code/TMV/Mission.cs b/project/code/TMV/Mission.cs index 53b9f61..16bf15e 100644 --- a/project/code/TMV/Mission.cs +++ b/project/code/TMV/Mission.cs @@ -41,12 +41,10 @@ public partial class Mission : Node3D public bool Dump = false; DbFile _file; - List _textures; + TextureLoader _textureLoader; public override void _Ready() { - _textures = new List(); - var missionSelector = GetNode("%MissionSelector") as MissionSelector; missionSelector.LoadMission += (string path) => { @@ -82,8 +80,6 @@ public partial class Mission : Node3D public void ClearMap() { - _textures.Clear(); - foreach (var node in GetChildren()) { node.QueueFree(); @@ -94,6 +90,8 @@ public partial class Mission : Node3D { ClearMap(); + _textureLoader = new TextureLoader("", FileName.GetBaseDir()); + _file = new(FileName); var textureList = (TxList)_file.Chunks["TXLIST"]; LoadTextures(textureList); @@ -180,7 +178,7 @@ public partial class Mission : Node3D if (!surfaceDataMap.ContainsKey(textureId)) { - surfaceDataMap.Add(textureId, new MeshSurfaceData(_textures[textureId])); + surfaceDataMap.Add(textureId, new MeshSurfaceData(_textureLoader.Get(textureId))); } var surfaceData = surfaceDataMap[textureId]; var (start, end) = surfaceData.AddPolygon(vertices, normal, textureUvs, lightmapUvs); @@ -254,13 +252,8 @@ public partial class Mission : Node3D // TODO: This is slightly hardcoded for ND. Check other stuff at some point. Should be handled in LG side imo // TODO: This is a mess lol var textureId = renderPoly.TextureId; - // !HACK: Sky textures :) - if (textureId >= _textures.Count) - { - textureId = 0; - } - var texture = _textures[textureId]; + var texture = _textureLoader.Get(textureId); var texU = renderPoly.TextureVectors.Item1.ToGodotVec3(); var texV = renderPoly.TextureVectors.Item2.ToGodotVec3(); var baseU = renderPoly.TextureBases.Item1; @@ -327,36 +320,6 @@ public partial class Mission : Node3D private void LoadTextures(TxList textureList) { - static string PathToKey(string baseDir, string path) - { - return path.TrimPrefix(baseDir).GetBaseName().ToLower(); - } - - // TODO: This has hardcoded .png extension and relies on you placing extracted and converted images in godot user directory - - // Collect all the fm textures here to help with case sensitivity :) - // TODO: Only do this on case sensitive systems? - var baseDir = FileName.GetBaseDir(); - var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }; - var dirPaths = Directory.GetDirectories(baseDir, "fam", options); - options.RecurseSubdirectories = true; - var texturePaths = new Dictionary(); - - // Godot doesn't support runtime DDS :) - // TODO: Load DDS BMP PCX GIF CEL - string[] validExtensions = { "png", "tga" }; - foreach (var dirPath in dirPaths) - { - foreach (var path in Directory.EnumerateFiles(dirPath, "*", options)) - { - if (validExtensions.Contains(path.GetExtension().ToLower())) - { - // TODO: This only adds the first one found rather than the highest priority - texturePaths.TryAdd(PathToKey(baseDir, path), path); - } - } - } - // TODO: Use PathJoin var count = textureList.ItemCount; for (var i = 0; i < count; i++) @@ -375,22 +338,10 @@ public partial class Mission : Node3D } path += item.Name; - if (texturePaths.TryGetValue(path.ToLower(), out var newPath)) + if (!_textureLoader.Load(i, path)) { - path = newPath; + GD.Print($"Failed to load texture: {path}"); } - else if (File.Exists(ProjectSettings.GlobalizePath($"user://textures{path}.png"))) - { - path = ProjectSettings.GlobalizePath($"user://textures{path}.png"); - } - else - { - GD.Print($"Failed to find texture: {path}"); - path = "user://textures/jorge.png"; - } - - if (Dump) GD.Print($"Loading texture: {path}"); - _textures.Add(ImageTexture.CreateFromImage(Image.LoadFromFile(path))); } } diff --git a/project/code/TMV/TextureLoader.cs b/project/code/TMV/TextureLoader.cs new file mode 100644 index 0000000..978bcc9 --- /dev/null +++ b/project/code/TMV/TextureLoader.cs @@ -0,0 +1,100 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; + +namespace KeepersCompound.TMV; + +public class TextureLoader +{ + private readonly string _rootPath; // TODO: Load from installation resources + private readonly string _fmPath; + private readonly Dictionary _fmTexturePaths = new(); + + private readonly List _textureCache = new(); + private readonly Dictionary _idMap = new(); + private readonly Dictionary _pathMap = new(); + + public TextureLoader(string rootPath, string fmPath) + { + _rootPath = rootPath; + _fmPath = fmPath; + + LoadDefaultTexture(); + RegisterFmTexturePaths(); + } + + private void LoadDefaultTexture() + { + // TODO: This should be a resource loaded from RES + const string path = "user://textures/jorge.png"; + var texture = ImageTexture.CreateFromImage(Image.LoadFromFile(path)); + _textureCache.Add(texture); + } + + private void RegisterFmTexturePaths() + { + // TODO: Load DDS BMP PCX GIF CEL + string[] validExtensions = { "png", "tga" }; + + var famOptions = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }; + var textureOptions = new EnumerationOptions + { + MatchCasing = MatchCasing.CaseInsensitive, + RecurseSubdirectories = true, + }; + + foreach (var dirPath in Directory.EnumerateDirectories(_fmPath, "fam", famOptions)) + { + foreach (var path in Directory.EnumerateFiles(dirPath, "*", textureOptions)) + { + if (validExtensions.Contains(path.GetExtension().ToLower())) + { + // TODO: This only adds the first one found rather than the highest priority + var key = path.TrimPrefix(_fmPath).GetBaseName().ToLower(); + _fmTexturePaths.TryAdd(key, path); + } + } + } + } + + public bool Load(int id, string path) + { + var userTexturesPath = ProjectSettings.GlobalizePath($"user://textures{path}.png"); + + var loaded = false; + if (_fmTexturePaths.TryGetValue(path.ToLower(), out var filePath)) + { + _textureCache.Add(ImageTexture.CreateFromImage(Image.LoadFromFile(filePath))); + loaded = true; + } + else if (File.Exists(userTexturesPath)) + { + _textureCache.Add(ImageTexture.CreateFromImage(Image.LoadFromFile(userTexturesPath))); + loaded = true; + } + + var index = loaded ? _textureCache.Count - 1 : 0; + _idMap.TryAdd(id, index); + _pathMap.TryAdd(path, index); + return loaded; + } + + public ImageTexture Get(int id) + { + if (!_idMap.ContainsKey(id)) + { + return _textureCache[0]; + } + return _textureCache[_idMap[id]]; + } + + public ImageTexture Get(string path) + { + if (!_pathMap.ContainsKey(path)) + { + return _textureCache[0]; + } + return _textureCache[_pathMap[path]]; + } +} \ No newline at end of file