diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs index 3ba39543..5fac4f5e 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using OtterGui; +using Penumbra.GameData; using Penumbra.GameData.Files; namespace Penumbra.UI.AdvancedWindow; @@ -10,53 +11,37 @@ public partial class ModEditWindow { public readonly MdlFile Mdl; - private List _materials; private List[] _attributes; public MdlTab(byte[] bytes) { Mdl = new MdlFile(bytes); - - _materials = Mdl.Meshes.Select(mesh => Mdl.Materials[mesh.MaterialIndex]).ToList(); - _attributes = HydrateAttributes(Mdl); + _attributes = PopulateAttributes(); } - private List[] HydrateAttributes(MdlFile mdl) + public void RemoveMaterial(int materialIndex) { - return mdl.SubMeshes.Select(submesh => - Enumerable.Range(0,32) - .Where(index => ((submesh.AttributeIndexMask >> index) & 1) == 1) - .Select(index => mdl.Attributes[index]) - .ToList() - ).ToArray(); - } - - public string GetMeshMaterial(int meshIndex) => _materials[meshIndex]; - - public void SetMeshMaterial(int meshIndex, string materialPath) - { - _materials[meshIndex] = materialPath; - - PersistMaterials(); - } - - private void PersistMaterials() - { - var allMaterials = new List(); - - foreach (var (material, meshIndex) in _materials.WithIndex()) + // Meshes using the removed material are redirected to material 0, and those after the index are corrected. + for (var meshIndex = 0; meshIndex < Mdl.Meshes.Length; meshIndex++) { - var materialIndex = allMaterials.IndexOf(material); - if (materialIndex == -1) - { - allMaterials.Add(material); - materialIndex = allMaterials.Count() - 1; - } - - Mdl.Meshes[meshIndex].MaterialIndex = (ushort)materialIndex; + var mesh = Mdl.Meshes[meshIndex]; + if (mesh.MaterialIndex == materialIndex) + mesh.MaterialIndex = 0; + else if (mesh.MaterialIndex > materialIndex) + mesh.MaterialIndex -= 1; } - Mdl.Materials = allMaterials.ToArray(); + Mdl.Materials = Mdl.Materials.RemoveItems(materialIndex); + } + + private List[] PopulateAttributes() + { + return Mdl.SubMeshes.Select(submesh => + Enumerable.Range(0,32) + .Where(index => ((submesh.AttributeIndexMask >> index) & 1) == 1) + .Select(index => Mdl.Attributes[index]) + .ToList() + ).ToArray(); } public IReadOnlyCollection GetSubmeshAttributes(int submeshIndex) => _attributes[submeshIndex]; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs index 0095ece8..fe3ca644 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs @@ -3,6 +3,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Widgets; +using Penumbra.GameData; using Penumbra.GameData.Files; using Penumbra.String.Classes; @@ -14,6 +15,7 @@ public partial class ModEditWindow private readonly FileEditor _modelTab; + private static string _modelNewMaterial = string.Empty; private static List _submeshAttributeTagWidgets = new(); private static bool DrawModelPanel(MdlTab tab, bool disabled) @@ -47,12 +49,13 @@ public partial class ModEditWindow if (!ImGui.CollapsingHeader("Materials")) return false; - var materials = tab.Mdl.Materials; - using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit); if (!table) return false; + var ret = false; + var materials = tab.Mdl.Materials; + ImGui.TableSetupColumn("index", ImGuiTableColumnFlags.WidthFixed, 80 * UiHelpers.Scale); ImGui.TableSetupColumn("path", ImGuiTableColumnFlags.WidthStretch, 1); ImGui.TableSetupColumn("actions", ImGuiTableColumnFlags.WidthFixed, UiHelpers.IconButtonSize.X); @@ -63,6 +66,8 @@ public partial class ModEditWindow for (var materialIndex = 0; materialIndex < materials.Length; materialIndex++) { + using var id = ImRaii.PushId(materialIndex); + ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.Text($"Material #{materialIndex + 1}"); @@ -70,44 +75,52 @@ public partial class ModEditWindow var temp = materials[materialIndex]; ImGui.TableNextColumn(); ImGui.SetNextItemWidth(-1); - ImGui.InputText($"##material{materialIndex}", ref temp, Utf8GamePath.MaxGamePathLength, inputFlags); - + if ( + ImGui.InputText($"##material{materialIndex}", ref temp, Utf8GamePath.MaxGamePathLength, inputFlags) + && temp.Length > 0 + && temp != materials[materialIndex] + ) { + materials[materialIndex] = temp; + ret = true; + } + ImGui.TableNextColumn(); - var todoDelete = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), UiHelpers.IconButtonSize, "description", disabled || !ImGui.GetIO().KeyCtrl, true); + + // Need to have at least one material. + if (materials.Length <= 1) + continue; + + if (ImGuiUtil.DrawDisabledButton( + FontAwesomeIcon.Trash.ToIconString(), + UiHelpers.IconButtonSize, + "Delete this material.\nAny meshes targeting this material will be updated to use material #1.\nHold Control while clicking to delete.", + disabled || !ImGui.GetIO().KeyCtrl, + true + )) { + tab.RemoveMaterial(materialIndex); + ret = true; + } } if (materials.Length < MdlMaterialMaximum) { ImGui.TableNextColumn(); - // todo: persist - var temp = ""; ImGui.TableNextColumn(); ImGui.SetNextItemWidth(-1); - ImGui.InputTextWithHint($"##newMaterial", "Add new material...", ref temp, Utf8GamePath.MaxGamePathLength, inputFlags); + ImGui.InputTextWithHint($"##newMaterial", "Add new material...", ref _modelNewMaterial, Utf8GamePath.MaxGamePathLength, inputFlags); - // todo: flesh out this validation - var validName = temp != ""; + var validName = _modelNewMaterial != ""; ImGui.TableNextColumn(); - var todoAdd = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), UiHelpers.IconButtonSize, "description", disabled || !validName, true); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), UiHelpers.IconButtonSize, "description", disabled || !validName, true)) + { + tab.Mdl.Materials = materials.AddItem(_modelNewMaterial); + _modelNewMaterial = string.Empty; + ret = true; + } } - // for (var index = 0; index < MdlMaterialMaximum; index++) - // { - // var temp = ""; - // ImGui.InputText($"Material {index}", ref temp, Utf8GamePath.MaxGamePathLength, inputFlags); - // } - - // var temp = tab.GetMeshMaterial(meshIndex); - // if ( - // ImGui.InputText("Material", ref temp, Utf8GamePath.MaxGamePathLength, disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None) - // && temp.Length > 0 - // && temp != tab.GetMeshMaterial(meshIndex) - // ) { - // tab.SetMeshMaterial(meshIndex, temp); - // ret = true; - // } - return false; + return ret; } private static bool DrawModelLodDetails(MdlTab tab, int lodIndex, bool disabled) @@ -160,7 +173,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); ImGui.SetNextItemWidth(-1); - using (var materialCombo = ImRaii.Combo("##material", tab.GetMeshMaterial(meshIndex))) + using (var materialCombo = ImRaii.Combo("##material", "TODO material")) { // todo }