Refactor model viewer to use new Context and support custom models

This commit is contained in:
Jarrod Doyle 2024-09-05 17:32:29 +01:00
parent 9ae75a693e
commit b89a4d6154
Signed by: Jayrude
GPG Key ID: 38B57B16E7C0ADF7
4 changed files with 60 additions and 127 deletions

View File

@ -1,7 +1,4 @@
using System.Collections.Generic;
using System.IO;
using Godot; using Godot;
using KeepersCompound.LGS;
using KeepersCompound.TMV.UI; using KeepersCompound.TMV.UI;
namespace KeepersCompound.TMV; namespace KeepersCompound.TMV;
@ -11,104 +8,17 @@ public partial class Model : Node3D
public override void _Ready() public override void _Ready()
{ {
var modelSelector = GetNode<Control>("%ModelSelector") as ModelSelector; var modelSelector = GetNode<Control>("%ModelSelector") as ModelSelector;
modelSelector.LoadModel += BuildModel; modelSelector.ModelSelected += BuildModel;
} }
public void BuildModel(string rootPath, string modelPath) public void BuildModel(string campaignName, string modelPath)
{ {
foreach (var node in GetChildren()) foreach (var node in GetChildren())
{ {
node.QueueFree(); node.QueueFree();
} }
var modelFile = new ModelFile(modelPath); var model = Context.Instance.ModelLoader.Load(campaignName, modelPath);
if (modelFile == null) AddChild(model);
{
GD.Print($"Failed to load model file: {modelPath}");
return;
}
// TODO: Remove this disgusting hack. Not only is it a hack, it doesn't support custom models
var baseDir = Path.GetDirectoryName(modelPath);
var options = new EnumerationOptions
{
MatchCasing = MatchCasing.CaseInsensitive,
RecurseSubdirectories = true,
};
var materials = new List<StandardMaterial3D>();
foreach (var material in modelFile.Materials)
{
if (material.Type == 0)
{
var paths = Directory.GetFiles(baseDir, material.Name, options);
if (paths.IsEmpty()) continue;
materials.Add(new StandardMaterial3D
{
AlbedoTexture = TextureLoader.LoadTexture(paths[0])
});
}
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<int, MeshSurfaceData>();
foreach (var poly in modelFile.Polygons)
{
var vertices = new List<Vector3>();
var normal = modelFile.Normals[poly.Normal].ToGodotVec3();
var uvs = new List<Vector2>();
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 };
AddChild(meshInstance);
} }
} }

View File

@ -6,15 +6,15 @@ using Godot;
namespace KeepersCompound.TMV.UI; namespace KeepersCompound.TMV.UI;
public partial class ModelSelector : Control public partial class ModelSelector : Control
{
[Signal]
public delegate void LoadModelEventHandler(string rootPath, string modelPath);
private InstallPaths _installPaths; {
public event ModelSelectedEventHandler ModelSelected;
public delegate void ModelSelectedEventHandler(string campaign, string mission);
private FileDialog _FolderSelect; private FileDialog _FolderSelect;
private LineEdit _FolderPath; private LineEdit _FolderPath;
private Button _BrowseButton; private Button _BrowseButton;
private ItemList _Campaigns;
private ItemList _Models; private ItemList _Models;
private Button _LoadButton; private Button _LoadButton;
private Button _CancelButton; private Button _CancelButton;
@ -28,13 +28,15 @@ public partial class ModelSelector : Control
_FolderSelect = GetNode<FileDialog>("%FolderSelect"); _FolderSelect = GetNode<FileDialog>("%FolderSelect");
_FolderPath = GetNode<LineEdit>("%FolderPath"); _FolderPath = GetNode<LineEdit>("%FolderPath");
_BrowseButton = GetNode<Button>("%BrowseButton"); _BrowseButton = GetNode<Button>("%BrowseButton");
_Campaigns = GetNode<ItemList>("%Campaigns");
_Models = GetNode<ItemList>("%Models"); _Models = GetNode<ItemList>("%Models");
_LoadButton = GetNode<Button>("%LoadButton"); _LoadButton = GetNode<Button>("%LoadButton");
_CancelButton = GetNode<Button>("%CancelButton"); _CancelButton = GetNode<Button>("%CancelButton");
_BrowseButton.Pressed += () => _FolderSelect.Visible = true; _BrowseButton.Pressed += () => _FolderSelect.Visible = true;
_FolderSelect.DirSelected += (string dir) => { _FolderPath.Text = dir; BuildModelList(dir); }; _FolderSelect.DirSelected += SetInstallPath;
_FolderPath.TextSubmitted += BuildModelList; _FolderPath.TextSubmitted += SetInstallPath;
_Campaigns.ItemSelected += BuildModelList;
_Models.ItemSelected += (long _) => _LoadButton.Disabled = false; _Models.ItemSelected += (long _) => _LoadButton.Disabled = false;
_LoadButton.Pressed += EmitLoadModel; _LoadButton.Pressed += EmitLoadModel;
_CancelButton.Pressed += () => Visible = false; _CancelButton.Pressed += () => Visible = false;
@ -51,44 +53,57 @@ public partial class ModelSelector : Control
} }
} }
private void BuildModelList(string path) private void SetInstallPath(string path)
{ {
_installPaths = new InstallPaths(path); _FolderPath.Text = path;
ExtractObjFiles(); if (Context.Instance.PathManager.Init(path))
_Models.Clear();
_LoadButton.Disabled = true;
var paths = Directory.GetFiles(_extractedObjectsPath, "*.bin", SearchOption.AllDirectories);
foreach (var m in paths.OrderBy(s => s))
{ {
_Models.AddItem(m.TrimPrefix(_extractedObjectsPath)); BuildCampaignList();
} }
} }
// TODO: Move this to a resource manager private void BuildCampaignList()
private void ExtractObjFiles()
{ {
var dir = new DirectoryInfo(_extractedObjectsPath); _Campaigns.Clear();
if (dir.Exists) _Models.Clear();
{ _LoadButton.Disabled = true;
dir.Delete(true);
}
var zip = ZipFile.OpenRead(_installPaths.objPath); var pathManager = Context.Instance.PathManager;
zip.ExtractToDirectory(_extractedObjectsPath); _Campaigns.AddItem("Original Missions");
foreach (var campaign in pathManager.GetCampaignNames())
{
_Campaigns.AddItem(campaign);
}
}
private void BuildModelList(long idx)
{
_Models.Clear();
_LoadButton.Disabled = true;
var pathManager = Context.Instance.PathManager;
var campaignName = idx == 0 ? null : _Campaigns.GetItemText((int)idx);
var modelNames = pathManager.GetModelNames(campaignName);
foreach (var model in modelNames)
{
_Models.AddItem(model);
}
} }
private void EmitLoadModel() private void EmitLoadModel()
{ {
var selected = _Models.GetSelectedItems(); var campaignIdxs = _Campaigns.GetSelectedItems();
if (selected.IsEmpty()) var modelIdxs = _Models.GetSelectedItems();
if (campaignIdxs.IsEmpty() || modelIdxs.IsEmpty())
{ {
return; return;
} }
var path = _extractedObjectsPath + _Models.GetItemText(selected[0]); var campaignIdx = campaignIdxs[0];
EmitSignal(SignalName.LoadModel, _installPaths.rootPath, path); var modelIdx = modelIdxs[0];
var campaignName = campaignIdx == 0 ? null : _Campaigns.GetItemText(campaignIdx);
var modelName = _Models.GetItemText(modelIdx);
ModelSelected(campaignName, modelName);
Visible = false; Visible = false;
} }

View File

@ -15,13 +15,15 @@ ssao_enabled = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.110309, 0.187101, -0.461656) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.110309, 0.187101, -0.461656)
script = ExtResource("1_dax7s") script = ExtResource("1_dax7s")
[node name="Camera3D" type="Camera3D" parent="."]
script = ExtResource("2_ov7rc")
[node name="UI" type="CanvasLayer" parent="."] [node name="UI" type="CanvasLayer" parent="."]
[node name="ModelSelector" parent="UI" instance=ExtResource("3_ovrmo")] [node name="ModelSelector" parent="UI" instance=ExtResource("3_ovrmo")]
unique_name_in_owner = true unique_name_in_owner = true
[node name="WorldEnvironment" type="WorldEnvironment" parent="."] [node name="Environment" type="Node3D" parent="."]
[node name="WorldEnvironment" type="WorldEnvironment" parent="Environment"]
environment = SubResource("Environment_e4172") environment = SubResource("Environment_e4172")
[node name="Camera3D" type="Camera3D" parent="Environment"]
script = ExtResource("2_ov7rc")

View File

@ -77,6 +77,12 @@ text = "Browse"
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"] [node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2 layout_mode = 2
[node name="Campaigns" type="ItemList" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 480)
layout_mode = 2
size_flags_horizontal = 3
[node name="Models" type="ItemList" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] [node name="Models" type="ItemList" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 480) custom_minimum_size = Vector2(0, 480)