diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LegacyColorTable.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LegacyColorTable.cs index f3ec5307..a2165760 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LegacyColorTable.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LegacyColorTable.cs @@ -33,6 +33,7 @@ public partial class MtrlTab UpdateColorTableRowPreview(i); ret = true; } + ImGui.TableNextRow(); } @@ -56,6 +57,7 @@ public partial class MtrlTab UpdateColorTableRowPreview(i); ret = true; } + ImGui.TableNextRow(); } @@ -108,8 +110,10 @@ public partial class MtrlTab } ImGui.TableNextColumn(); - using (var font = ImRaii.PushFont(UiBuilder.MonoFont)) - ImUtf8.Text($"{(rowIdx >> 1) + 1,2:D}{"AB"[rowIdx & 1]}"); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImUtf8.Text($"{(rowIdx >> 1) + 1,2:D}{"AB"[rowIdx & 1]}"); + } ImGui.TableNextColumn(); using var dis = ImRaii.Disabled(disabled); @@ -131,9 +135,11 @@ public partial class MtrlTab ret |= CtApplyStainCheckbox("##dyeSpecular"u8, "Apply Specular Color on Dye"u8, dye.SpecularColor, b => dyeTable[rowIdx].SpecularColor = b); } + ImGui.SameLine(); ImGui.SetNextItemWidth(pctSize); - ret |= CtDragScalar("##SpecularMask"u8, "Specular Strength"u8, (float)row.SpecularMask * 100.0f, "%.0f%%"u8, 0f, HalfMaxValue * 100.0f, 1.0f, + ret |= CtDragScalar("##SpecularMask"u8, "Specular Strength"u8, (float)row.SpecularMask * 100.0f, "%.0f%%"u8, 0f, HalfMaxValue * 100.0f, + 1.0f, v => table[rowIdx].SpecularMask = (Half)(v * 0.01f)); if (dyeTable != null) { @@ -155,7 +161,8 @@ public partial class MtrlTab ImGui.TableNextColumn(); ImGui.SetNextItemWidth(floatSize); var glossStrengthMin = ImGui.GetIO().KeyCtrl ? 0.0f : HalfEpsilon; - ret |= CtDragHalf("##Shininess"u8, "Gloss Strength"u8, row.Shininess, "%.1f"u8, glossStrengthMin, HalfMaxValue, Math.Max(0.1f, (float)row.Shininess * 0.025f), + ret |= CtDragHalf("##Shininess"u8, "Gloss Strength"u8, row.Shininess, "%.1f"u8, glossStrengthMin, HalfMaxValue, + Math.Max(0.1f, (float)row.Shininess * 0.025f), v => table[rowIdx].Shininess = v); if (dyeTable != null) @@ -197,7 +204,7 @@ public partial class MtrlTab { using var id = ImRaii.PushId(rowIdx); ref var row = ref table[rowIdx]; - var dye = dyeTable != null ? dyeTable[rowIdx] : default; + var dye = dyeTable?[rowIdx] ?? default; var floatSize = LegacyColorTableFloatSize * UiHelpers.Scale; var pctSize = LegacyColorTablePercentageSize * UiHelpers.Scale; var intSize = LegacyColorTableIntegerSize * UiHelpers.Scale; @@ -213,8 +220,10 @@ public partial class MtrlTab } ImGui.TableNextColumn(); - using (var font = ImRaii.PushFont(UiBuilder.MonoFont)) + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { ImUtf8.Text($"{(rowIdx >> 1) + 1,2:D}{"AB"[rowIdx & 1]}"); + } ImGui.TableNextColumn(); using var dis = ImRaii.Disabled(disabled); @@ -236,6 +245,7 @@ public partial class MtrlTab ret |= CtApplyStainCheckbox("##dyeSpecular"u8, "Apply Specular Color on Dye"u8, dye.SpecularColor, b => dyeTable[rowIdx].SpecularColor = b); } + ImGui.SameLine(); ImGui.SetNextItemWidth(pctSize); ret |= CtDragScalar("##SpecularMask"u8, "Specular Strength"u8, (float)row.Scalar7 * 100.0f, "%.0f%%"u8, 0f, HalfMaxValue * 100.0f, 1.0f, @@ -260,7 +270,8 @@ public partial class MtrlTab ImGui.TableNextColumn(); ImGui.SetNextItemWidth(floatSize); var glossStrengthMin = ImGui.GetIO().KeyCtrl ? 0.0f : HalfEpsilon; - ret |= CtDragHalf("##Shininess"u8, "Gloss Strength"u8, row.Scalar3, "%.1f"u8, glossStrengthMin, HalfMaxValue, Math.Max(0.1f, (float)row.Scalar3 * 0.025f), + ret |= CtDragHalf("##Shininess"u8, "Gloss Strength"u8, row.Scalar3, "%.1f"u8, glossStrengthMin, HalfMaxValue, + Math.Max(0.1f, (float)row.Scalar3 * 0.025f), v => table[rowIdx].Scalar3 = v); if (dyeTable != null) @@ -307,7 +318,7 @@ public partial class MtrlTab return ret; } - private bool DrawLegacyDyePreview(int rowIdx, bool disabled, LegacyColorDyeTable.Row dye, float floatSize) + private bool DrawLegacyDyePreview(int rowIdx, bool disabled, LegacyColorDyeTableRow dye, float floatSize) { var stain = _stainService.StainCombo1.CurrentSelection.Key; if (stain == 0 || !_stainService.LegacyStmFile.TryGetValue(dye.Template, stain, out var values)) @@ -326,7 +337,7 @@ public partial class MtrlTab return ret; } - private bool DrawLegacyDyePreview(int rowIdx, bool disabled, ColorDyeTable.Row dye, float floatSize) + private bool DrawLegacyDyePreview(int rowIdx, bool disabled, ColorDyeTableRow dye, float floatSize) { var stain = _stainService.GetStainCombo(dye.Channel).CurrentSelection.Key; if (stain == 0 || !_stainService.LegacyStmFile.TryGetValue(dye.Template, stain, out var values)) @@ -337,10 +348,11 @@ public partial class MtrlTab var ret = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.PaintBrush.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Apply the selected dye to this row.", disabled, true); - ret = ret && Mtrl.ApplyDyeToRow(_stainService.LegacyStmFile, [ - _stainService.StainCombo1.CurrentSelection.Key, - _stainService.StainCombo2.CurrentSelection.Key, - ], rowIdx); + ret = ret + && Mtrl.ApplyDyeToRow(_stainService.LegacyStmFile, [ + _stainService.StainCombo1.CurrentSelection.Key, + _stainService.StainCombo2.CurrentSelection.Key, + ], rowIdx); ImGui.SameLine(); DrawLegacyDyePreview(values, floatSize); diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LivePreview.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LivePreview.cs index bb346534..3482e581 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LivePreview.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LivePreview.cs @@ -191,7 +191,7 @@ public partial class MtrlTab var row = Mtrl.Table switch { - LegacyColorTable legacyTable => new ColorTable.Row(legacyTable[rowIdx]), + LegacyColorTable legacyTable => new ColorTableRow(legacyTable[rowIdx]), ColorTable table => table[rowIdx], _ => throw new InvalidOperationException($"Unsupported color table type {Mtrl.Table.GetType()}"), }; @@ -199,7 +199,7 @@ public partial class MtrlTab { var dyeRow = Mtrl.DyeTable switch { - LegacyColorDyeTable legacyDyeTable => new ColorDyeTable.Row(legacyDyeTable[rowIdx]), + LegacyColorDyeTable legacyDyeTable => new ColorDyeTableRow(legacyDyeTable[rowIdx]), ColorDyeTable dyeTable => dyeTable[rowIdx], _ => throw new InvalidOperationException($"Unsupported color dye table type {Mtrl.DyeTable.GetType()}"), }; @@ -258,7 +258,7 @@ public partial class MtrlTab } } - private static void ApplyHighlight(ref ColorTable.Row row, ColorId colorId, float time) + private static void ApplyHighlight(ref ColorTableRow row, ColorId colorId, float time) { var level = (MathF.Sin(time * 2.0f * MathF.PI) + 2.0f) / 3.0f / 255.0f; var baseColor = colorId.Value(); diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ColorTable.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ColorTable.cs deleted file mode 100644 index cb04dc0a..00000000 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ColorTable.cs +++ /dev/null @@ -1,538 +0,0 @@ -using Dalamud.Interface; -using Dalamud.Interface.Utility; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; -using Penumbra.GameData.Files; -using Penumbra.GameData.Files.MaterialStructs; -using Penumbra.String.Functions; - -namespace Penumbra.UI.AdvancedWindow; - -public partial class ModEditWindow -{ - private static readonly float HalfMinValue = (float)Half.MinValue; - private static readonly float HalfMaxValue = (float)Half.MaxValue; - private static readonly float HalfEpsilon = (float)Half.Epsilon; - - private bool DrawMaterialColorTableChange(MtrlTab tab, bool disabled) - { - if (!tab.SamplerIds.Contains(ShpkFile.TableSamplerId) || !tab.Mtrl.HasTable) - return false; - - ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); - if (!ImGui.CollapsingHeader("Color Table", ImGuiTreeNodeFlags.DefaultOpen)) - return false; - - ColorTableCopyAllClipboardButton(tab.Mtrl); - ImGui.SameLine(); - var ret = ColorTablePasteAllClipboardButton(tab, disabled); - if (!disabled) - { - ImGui.SameLine(); - ImGui.Dummy(ImGuiHelpers.ScaledVector2(20, 0)); - ImGui.SameLine(); - ret |= ColorTableDyeableCheckbox(tab); - } - - var hasDyeTable = tab.Mtrl.HasDyeTable; - if (hasDyeTable) - { - ImGui.SameLine(); - ImGui.Dummy(ImGuiHelpers.ScaledVector2(20, 0)); - ImGui.SameLine(); - ret |= DrawPreviewDye(tab, disabled); - } - - using var table = ImRaii.Table("##ColorTable", hasDyeTable ? 11 : 9, - ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerV); - if (!table) - return false; - - ImGui.TableNextColumn(); - ImGui.TableHeader(string.Empty); - ImGui.TableNextColumn(); - ImGui.TableHeader("Row"); - ImGui.TableNextColumn(); - ImGui.TableHeader("Diffuse"); - ImGui.TableNextColumn(); - ImGui.TableHeader("Specular"); - ImGui.TableNextColumn(); - ImGui.TableHeader("Emissive"); - ImGui.TableNextColumn(); - ImGui.TableHeader("Gloss"); - ImGui.TableNextColumn(); - ImGui.TableHeader("Tile"); - ImGui.TableNextColumn(); - ImGui.TableHeader("Repeat"); - ImGui.TableNextColumn(); - ImGui.TableHeader("Skew"); - if (hasDyeTable) - { - ImGui.TableNextColumn(); - ImGui.TableHeader("Dye"); - ImGui.TableNextColumn(); - ImGui.TableHeader("Dye Preview"); - } - - for (var i = 0; i < ColorTable.NumRows; ++i) - { - ret |= DrawColorTableRow(tab, i, disabled); - ImGui.TableNextRow(); - } - - return ret; - } - - - private static void ColorTableCopyAllClipboardButton(MtrlFile file) - { - if (!ImGui.Button("Export All Rows to Clipboard", ImGuiHelpers.ScaledVector2(200, 0))) - return; - - try - { - var data1 = file.Table.AsBytes(); - var data2 = file.HasDyeTable ? file.DyeTable.AsBytes() : ReadOnlySpan.Empty; - var array = new byte[data1.Length + data2.Length]; - data1.TryCopyTo(array); - data2.TryCopyTo(array.AsSpan(data1.Length)); - var text = Convert.ToBase64String(array); - ImGui.SetClipboardText(text); - } - catch - { - // ignored - } - } - - private bool DrawPreviewDye(MtrlTab tab, bool disabled) - { - var (dyeId, (name, dyeColor, gloss)) = _stainService.StainCombo.CurrentSelection; - var tt = dyeId == 0 - ? "Select a preview dye first." - : "Apply all preview values corresponding to the dye template and chosen dye where dyeing is enabled."; - if (ImGuiUtil.DrawDisabledButton("Apply Preview Dye", Vector2.Zero, tt, disabled || dyeId == 0)) - { - var ret = false; - if (tab.Mtrl.HasDyeTable) - for (var i = 0; i < LegacyColorTable.NumUsedRows; ++i) - ret |= tab.Mtrl.ApplyDyeTemplate(_stainService.StmFile, i, dyeId, 0); - - tab.UpdateColorTablePreview(); - - return ret; - } - - ImGui.SameLine(); - var label = dyeId == 0 ? "Preview Dye###previewDye" : $"{name} (Preview)###previewDye"; - if (_stainService.StainCombo.Draw(label, dyeColor, string.Empty, true, gloss)) - tab.UpdateColorTablePreview(); - return false; - } - - private static unsafe bool ColorTablePasteAllClipboardButton(MtrlTab tab, bool disabled) - { - if (!ImGuiUtil.DrawDisabledButton("Import All Rows from Clipboard", ImGuiHelpers.ScaledVector2(200, 0), string.Empty, disabled) - || !tab.Mtrl.HasTable) - return false; - - try - { - var text = ImGui.GetClipboardText(); - var data = Convert.FromBase64String(text); - if (data.Length < Marshal.SizeOf()) - return false; - - ref var rows = ref tab.Mtrl.Table; - fixed (void* ptr = data, output = &rows) - { - MemoryUtility.MemCpyUnchecked(output, ptr, Marshal.SizeOf()); - if (data.Length >= Marshal.SizeOf() + Marshal.SizeOf() - && tab.Mtrl.HasDyeTable) - { - ref var dyeRows = ref tab.Mtrl.DyeTable; - fixed (void* output2 = &dyeRows) - { - MemoryUtility.MemCpyUnchecked(output2, (byte*)ptr + Marshal.SizeOf(), - Marshal.SizeOf()); - } - } - } - - tab.UpdateColorTablePreview(); - - return true; - } - catch - { - return false; - } - } - - [SkipLocalsInit] - private static unsafe void ColorTableCopyClipboardButton(ColorTableRow row, ColorDyeTableRow dye) - { - if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), ImGui.GetFrameHeight() * Vector2.One, - "Export this row to your clipboard.", false, true)) - return; - - try - { - Span data = stackalloc byte[ColorTableRow.Size + ColorDyeTableRow.Size]; - fixed (byte* ptr = data) - { - MemoryUtility.MemCpyUnchecked(ptr, &row, ColorTableRow.Size); - MemoryUtility.MemCpyUnchecked(ptr + ColorTableRow.Size, &dye, ColorDyeTableRow.Size); - } - - var text = Convert.ToBase64String(data); - ImGui.SetClipboardText(text); - } - catch - { - // ignored - } - } - - private static bool ColorTableDyeableCheckbox(MtrlTab tab) - { - var dyeable = tab.Mtrl.HasDyeTable; - var ret = ImGui.Checkbox("Dyeable", ref dyeable); - - if (ret) - { - tab.Mtrl.HasDyeTable = dyeable; - tab.UpdateColorTablePreview(); - } - - return ret; - } - - private static unsafe bool ColorTablePasteFromClipboardButton(MtrlTab tab, int rowIdx, bool disabled) - { - if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), ImGui.GetFrameHeight() * Vector2.One, - "Import an exported row from your clipboard onto this row.", disabled, true)) - return false; - - try - { - var text = ImGui.GetClipboardText(); - var data = Convert.FromBase64String(text); - if (data.Length != ColorTableRow.Size + ColorDyeTableRow.Size - || !tab.Mtrl.HasTable) - return false; - - fixed (byte* ptr = data) - { - tab.Mtrl.Table[rowIdx] = *(ColorTableRow*)ptr; - if (tab.Mtrl.HasDyeTable) - tab.Mtrl.DyeTable[rowIdx] = *(ColorDyeTableRow*)(ptr + ColorTableRow.Size); - } - - tab.UpdateColorTableRowPreview(rowIdx); - - return true; - } - catch - { - return false; - } - } - - private static void ColorTableHighlightButton(MtrlTab tab, int rowIdx, bool disabled) - { - ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), ImGui.GetFrameHeight() * Vector2.One, - "Highlight this row on your character, if possible.", disabled || tab.ColorTablePreviewers.Count == 0, true); - - if (ImGui.IsItemHovered()) - tab.HighlightColorTableRow(rowIdx); - else if (tab.HighlightedColorTableRow == rowIdx) - tab.CancelColorTableHighlight(); - } - - private bool DrawColorTableRow(MtrlTab tab, int rowIdx, bool disabled) - { - static bool FixFloat(ref float val, float current) - { - val = (float)(Half)val; - return val != current; - } - - using var id = ImRaii.PushId(rowIdx); - ref var row = ref tab.Mtrl.Table[rowIdx]; - var hasDye = tab.Mtrl.HasDyeTable; - ref var dye = ref tab.Mtrl.DyeTable[rowIdx]; - var floatSize = 70 * UiHelpers.Scale; - var intSize = 45 * UiHelpers.Scale; - ImGui.TableNextColumn(); - ColorTableCopyClipboardButton(row, dye); - ImGui.SameLine(); - var ret = ColorTablePasteFromClipboardButton(tab, rowIdx, disabled); - ImGui.SameLine(); - ColorTableHighlightButton(tab, rowIdx, disabled); - - ImGui.TableNextColumn(); - ImGui.TextUnformatted($"#{rowIdx + 1:D2}"); - - ImGui.TableNextColumn(); - using var dis = ImRaii.Disabled(disabled); - ret |= ColorPicker("##Diffuse", "Diffuse Color", row.Diffuse, c => - { - tab.Mtrl.Table[rowIdx].Diffuse = c; - tab.UpdateColorTableRowPreview(rowIdx); - }); - if (hasDye) - { - ImGui.SameLine(); - ret |= ImGuiUtil.Checkbox("##dyeDiffuse", "Apply Diffuse Color on Dye", dye.Diffuse, - b => - { - tab.Mtrl.DyeTable[rowIdx].Diffuse = b; - tab.UpdateColorTableRowPreview(rowIdx); - }, ImGuiHoveredFlags.AllowWhenDisabled); - } - - ImGui.TableNextColumn(); - ret |= ColorPicker("##Specular", "Specular Color", row.Specular, c => - { - tab.Mtrl.Table[rowIdx].Specular = c; - tab.UpdateColorTableRowPreview(rowIdx); - }); - ImGui.SameLine(); - var tmpFloat = row.SpecularStrength; - ImGui.SetNextItemWidth(floatSize); - if (ImGui.DragFloat("##SpecularStrength", ref tmpFloat, 0.01f, 0f, HalfMaxValue, "%.2f") - && FixFloat(ref tmpFloat, row.SpecularStrength)) - { - row.SpecularStrength = tmpFloat; - ret = true; - tab.UpdateColorTableRowPreview(rowIdx); - } - - ImGuiUtil.HoverTooltip("Specular Strength", ImGuiHoveredFlags.AllowWhenDisabled); - - if (hasDye) - { - ImGui.SameLine(); - ret |= ImGuiUtil.Checkbox("##dyeSpecular", "Apply Specular Color on Dye", dye.Specular, - b => - { - tab.Mtrl.DyeTable[rowIdx].Specular = b; - tab.UpdateColorTableRowPreview(rowIdx); - }, ImGuiHoveredFlags.AllowWhenDisabled); - ImGui.SameLine(); - ret |= ImGuiUtil.Checkbox("##dyeSpecularStrength", "Apply Specular Strength on Dye", dye.SpecularStrength, - b => - { - tab.Mtrl.DyeTable[rowIdx].SpecularStrength = b; - tab.UpdateColorTableRowPreview(rowIdx); - }, ImGuiHoveredFlags.AllowWhenDisabled); - } - - ImGui.TableNextColumn(); - ret |= ColorPicker("##Emissive", "Emissive Color", row.Emissive, c => - { - tab.Mtrl.Table[rowIdx].Emissive = c; - tab.UpdateColorTableRowPreview(rowIdx); - }); - if (hasDye) - { - ImGui.SameLine(); - ret |= ImGuiUtil.Checkbox("##dyeEmissive", "Apply Emissive Color on Dye", dye.Emissive, - b => - { - tab.Mtrl.DyeTable[rowIdx].Emissive = b; - tab.UpdateColorTableRowPreview(rowIdx); - }, ImGuiHoveredFlags.AllowWhenDisabled); - } - - ImGui.TableNextColumn(); - tmpFloat = row.GlossStrength; - ImGui.SetNextItemWidth(floatSize); - var glossStrengthMin = ImGui.GetIO().KeyCtrl ? 0.0f : HalfEpsilon; - if (ImGui.DragFloat("##GlossStrength", ref tmpFloat, Math.Max(0.1f, tmpFloat * 0.025f), glossStrengthMin, HalfMaxValue, "%.1f") - && FixFloat(ref tmpFloat, row.GlossStrength)) - { - row.GlossStrength = Math.Max(tmpFloat, glossStrengthMin); - ret = true; - tab.UpdateColorTableRowPreview(rowIdx); - } - - ImGuiUtil.HoverTooltip("Gloss Strength", ImGuiHoveredFlags.AllowWhenDisabled); - if (hasDye) - { - ImGui.SameLine(); - ret |= ImGuiUtil.Checkbox("##dyeGloss", "Apply Gloss Strength on Dye", dye.Gloss, - b => - { - tab.Mtrl.DyeTable[rowIdx].Gloss = b; - tab.UpdateColorTableRowPreview(rowIdx); - }, ImGuiHoveredFlags.AllowWhenDisabled); - } - - ImGui.TableNextColumn(); - int tmpInt = row.TileSet; - ImGui.SetNextItemWidth(intSize); - if (ImGui.DragInt("##TileSet", ref tmpInt, 0.25f, 0, 63) && tmpInt != row.TileSet && tmpInt is >= 0 and <= ushort.MaxValue) - { - row.TileSet = (ushort)Math.Clamp(tmpInt, 0, 63); - ret = true; - tab.UpdateColorTableRowPreview(rowIdx); - } - - ImGuiUtil.HoverTooltip("Tile Set", ImGuiHoveredFlags.AllowWhenDisabled); - - ImGui.TableNextColumn(); - tmpFloat = row.MaterialRepeat.X; - ImGui.SetNextItemWidth(floatSize); - if (ImGui.DragFloat("##RepeatX", ref tmpFloat, 0.1f, HalfMinValue, HalfMaxValue, "%.2f") - && FixFloat(ref tmpFloat, row.MaterialRepeat.X)) - { - row.MaterialRepeat = row.MaterialRepeat with { X = tmpFloat }; - ret = true; - tab.UpdateColorTableRowPreview(rowIdx); - } - - ImGuiUtil.HoverTooltip("Repeat X", ImGuiHoveredFlags.AllowWhenDisabled); - ImGui.SameLine(); - tmpFloat = row.MaterialRepeat.Y; - ImGui.SetNextItemWidth(floatSize); - if (ImGui.DragFloat("##RepeatY", ref tmpFloat, 0.1f, HalfMinValue, HalfMaxValue, "%.2f") - && FixFloat(ref tmpFloat, row.MaterialRepeat.Y)) - { - row.MaterialRepeat = row.MaterialRepeat with { Y = tmpFloat }; - ret = true; - tab.UpdateColorTableRowPreview(rowIdx); - } - - ImGuiUtil.HoverTooltip("Repeat Y", ImGuiHoveredFlags.AllowWhenDisabled); - - ImGui.TableNextColumn(); - tmpFloat = row.MaterialSkew.X; - ImGui.SetNextItemWidth(floatSize); - if (ImGui.DragFloat("##SkewX", ref tmpFloat, 0.1f, HalfMinValue, HalfMaxValue, "%.2f") && FixFloat(ref tmpFloat, row.MaterialSkew.X)) - { - row.MaterialSkew = row.MaterialSkew with { X = tmpFloat }; - ret = true; - tab.UpdateColorTableRowPreview(rowIdx); - } - - ImGuiUtil.HoverTooltip("Skew X", ImGuiHoveredFlags.AllowWhenDisabled); - - ImGui.SameLine(); - tmpFloat = row.MaterialSkew.Y; - ImGui.SetNextItemWidth(floatSize); - if (ImGui.DragFloat("##SkewY", ref tmpFloat, 0.1f, HalfMinValue, HalfMaxValue, "%.2f") && FixFloat(ref tmpFloat, row.MaterialSkew.Y)) - { - row.MaterialSkew = row.MaterialSkew with { Y = tmpFloat }; - ret = true; - tab.UpdateColorTableRowPreview(rowIdx); - } - - ImGuiUtil.HoverTooltip("Skew Y", ImGuiHoveredFlags.AllowWhenDisabled); - - if (hasDye) - { - ImGui.TableNextColumn(); - if (_stainService.TemplateCombo.Draw("##dyeTemplate", dye.Template.ToString(), string.Empty, intSize - + ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton)) - { - dye.Template = _stainService.TemplateCombo.CurrentSelection; - ret = true; - tab.UpdateColorTableRowPreview(rowIdx); - } - - ImGuiUtil.HoverTooltip("Dye Template", ImGuiHoveredFlags.AllowWhenDisabled); - - ImGui.TableNextColumn(); - ret |= DrawDyePreview(tab, rowIdx, disabled, dye, floatSize); - } - - - return ret; - } - - private bool DrawDyePreview(MtrlTab tab, int rowIdx, bool disabled, ColorDyeTableRow dye, float floatSize) - { - var stain = _stainService.StainCombo.CurrentSelection.Key; - if (stain == 0 || !_stainService.StmFile.Entries.TryGetValue(dye.Template, out var entry)) - return false; - - var values = entry[(int)stain]; - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2); - - var ret = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.PaintBrush.ToIconString(), new Vector2(ImGui.GetFrameHeight()), - "Apply the selected dye to this row.", disabled, true); - - ret = ret && tab.Mtrl.ApplyDyeTemplate(_stainService.StmFile, rowIdx, stain, 0); - if (ret) - tab.UpdateColorTableRowPreview(rowIdx); - - ImGui.SameLine(); - ColorPicker("##diffusePreview", string.Empty, values.Diffuse, _ => { }, "D"); - ImGui.SameLine(); - ColorPicker("##specularPreview", string.Empty, values.Specular, _ => { }, "S"); - ImGui.SameLine(); - ColorPicker("##emissivePreview", string.Empty, values.Emissive, _ => { }, "E"); - ImGui.SameLine(); - using var dis = ImRaii.Disabled(); - ImGui.SetNextItemWidth(floatSize); - ImGui.DragFloat("##gloss", ref values.Gloss, 0, values.Gloss, values.Gloss, "%.1f G"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(floatSize); - ImGui.DragFloat("##specularStrength", ref values.SpecularPower, 0, values.SpecularPower, values.SpecularPower, "%.2f S"); - - return ret; - } - - private static bool ColorPicker(string label, string tooltip, Vector3 input, Action setter, string letter = "") - { - var ret = false; - var inputSqrt = PseudoSqrtRgb(input); - var tmp = inputSqrt; - if (ImGui.ColorEdit3(label, ref tmp, - ImGuiColorEditFlags.NoInputs - | ImGuiColorEditFlags.DisplayRGB - | ImGuiColorEditFlags.InputRGB - | ImGuiColorEditFlags.NoTooltip - | ImGuiColorEditFlags.HDR) - && tmp != inputSqrt) - { - setter(PseudoSquareRgb(tmp)); - ret = true; - } - - if (letter.Length > 0 && ImGui.IsItemVisible()) - { - var textSize = ImGui.CalcTextSize(letter); - var center = ImGui.GetItemRectMin() + (ImGui.GetItemRectSize() - textSize) / 2; - var textColor = input.LengthSquared() < 0.25f ? 0x80FFFFFFu : 0x80000000u; - ImGui.GetWindowDrawList().AddText(center, textColor, letter); - } - - ImGuiUtil.HoverTooltip(tooltip, ImGuiHoveredFlags.AllowWhenDisabled); - - return ret; - } - - // Functions to deal with squared RGB values without making negatives useless. - - private static float PseudoSquareRgb(float x) - => x < 0.0f ? -(x * x) : x * x; - - private static Vector3 PseudoSquareRgb(Vector3 vec) - => new(PseudoSquareRgb(vec.X), PseudoSquareRgb(vec.Y), PseudoSquareRgb(vec.Z)); - - private static Vector4 PseudoSquareRgb(Vector4 vec) - => new(PseudoSquareRgb(vec.X), PseudoSquareRgb(vec.Y), PseudoSquareRgb(vec.Z), vec.W); - - private static float PseudoSqrtRgb(float x) - => x < 0.0f ? -MathF.Sqrt(-x) : MathF.Sqrt(x); - - internal static Vector3 PseudoSqrtRgb(Vector3 vec) - => new(PseudoSqrtRgb(vec.X), PseudoSqrtRgb(vec.Y), PseudoSqrtRgb(vec.Z)); - - private static Vector4 PseudoSqrtRgb(Vector4 vec) - => new(PseudoSqrtRgb(vec.X), PseudoSqrtRgb(vec.Y), PseudoSqrtRgb(vec.Z), vec.W); -} diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs deleted file mode 100644 index b95eca9d..00000000 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs +++ /dev/null @@ -1,783 +0,0 @@ -using Dalamud.Interface; -using Dalamud.Interface.ImGuiNotification; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using ImGuiNET; -using Newtonsoft.Json.Linq; -using OtterGui; -using OtterGui.Classes; -using OtterGui.Raii; -using Penumbra.GameData.Data; -using Penumbra.GameData.Files; -using Penumbra.GameData.Files.MaterialStructs; -using Penumbra.GameData.Structs; -using Penumbra.Interop.Hooks.Objects; -using Penumbra.Interop.MaterialPreview; -using Penumbra.String; -using Penumbra.String.Classes; -using Penumbra.UI.Classes; -using static Penumbra.GameData.Files.ShpkFile; - -namespace Penumbra.UI.AdvancedWindow; - -public partial class ModEditWindow -{ - private sealed class MtrlTab : IWritable, IDisposable - { - private const int ShpkPrefixLength = 16; - - private static readonly CiByteString ShpkPrefix = CiByteString.FromSpanUnsafe("shader/sm5/shpk/"u8, true, true, true); - - private readonly ModEditWindow _edit; - public readonly MtrlFile Mtrl; - public readonly string FilePath; - public readonly bool Writable; - - private string[]? _shpkNames; - - public string ShaderHeader = "Shader###Shader"; - public FullPath LoadedShpkPath = FullPath.Empty; - public string LoadedShpkPathName = string.Empty; - public string LoadedShpkDevkitPathName = string.Empty; - public string ShaderComment = string.Empty; - public ShpkFile? AssociatedShpk; - public JObject? AssociatedShpkDevkit; - - public readonly string LoadedBaseDevkitPathName; - public readonly JObject? AssociatedBaseDevkit; - - // Shader Key State - public readonly - List<(string Label, int Index, string Description, bool MonoFont, IReadOnlyList<(string Label, uint Value, string Description)> - Values)> ShaderKeys = new(16); - - public readonly HashSet VertexShaders = new(16); - public readonly HashSet PixelShaders = new(16); - public bool ShadersKnown; - public string VertexShadersString = "Vertex Shaders: ???"; - public string PixelShadersString = "Pixel Shaders: ???"; - - // Textures & Samplers - public readonly List<(string Label, int TextureIndex, int SamplerIndex, string Description, bool MonoFont)> Textures = new(4); - - public readonly HashSet UnfoldedTextures = new(4); - public readonly HashSet SamplerIds = new(16); - public float TextureLabelWidth; - - // Material Constants - public readonly - List<(string Header, List<(string Label, int ConstantIndex, Range Slice, string Description, bool MonoFont, IConstantEditor Editor)> - Constants)> Constants = new(16); - - // Live-Previewers - public readonly List MaterialPreviewers = new(4); - public readonly List ColorTablePreviewers = new(4); - public int HighlightedColorTableRow = -1; - public readonly Stopwatch HighlightTime = new(); - - public FullPath FindAssociatedShpk(out string defaultPath, out Utf8GamePath defaultGamePath) - { - defaultPath = GamePaths.Shader.ShpkPath(Mtrl.ShaderPackage.Name); - if (!Utf8GamePath.FromString(defaultPath, out defaultGamePath)) - return FullPath.Empty; - - return _edit.FindBestMatch(defaultGamePath); - } - - public string[] GetShpkNames() - { - if (null != _shpkNames) - return _shpkNames; - - var names = new HashSet(StandardShaderPackages); - names.UnionWith(_edit.FindPathsStartingWith(ShpkPrefix).Select(path => path.ToString()[ShpkPrefixLength..])); - - _shpkNames = names.ToArray(); - Array.Sort(_shpkNames); - - return _shpkNames; - } - - public void LoadShpk(FullPath path) - { - ShaderHeader = $"Shader ({Mtrl.ShaderPackage.Name})###Shader"; - - try - { - LoadedShpkPath = path; - var data = LoadedShpkPath.IsRooted - ? File.ReadAllBytes(LoadedShpkPath.FullName) - : _edit._gameData.GetFile(LoadedShpkPath.InternalName.ToString())?.Data; - AssociatedShpk = data?.Length > 0 ? new ShpkFile(data) : throw new Exception("Failure to load file data."); - LoadedShpkPathName = path.ToPath(); - } - catch (Exception e) - { - LoadedShpkPath = FullPath.Empty; - LoadedShpkPathName = string.Empty; - AssociatedShpk = null; - Penumbra.Messager.NotificationMessage(e, $"Could not load {LoadedShpkPath.ToPath()}.", NotificationType.Error, false); - } - - if (LoadedShpkPath.InternalName.IsEmpty) - { - AssociatedShpkDevkit = null; - LoadedShpkDevkitPathName = string.Empty; - } - else - { - AssociatedShpkDevkit = - TryLoadShpkDevkit(Path.GetFileNameWithoutExtension(Mtrl.ShaderPackage.Name), out LoadedShpkDevkitPathName); - } - - UpdateShaderKeys(); - Update(); - } - - private JObject? TryLoadShpkDevkit(string shpkBaseName, out string devkitPathName) - { - try - { - if (!Utf8GamePath.FromString("penumbra/shpk_devkit/" + shpkBaseName + ".json", out var devkitPath)) - throw new Exception("Could not assemble ShPk dev-kit path."); - - var devkitFullPath = _edit.FindBestMatch(devkitPath); - if (!devkitFullPath.IsRooted) - throw new Exception("Could not resolve ShPk dev-kit path."); - - devkitPathName = devkitFullPath.FullName; - return JObject.Parse(File.ReadAllText(devkitFullPath.FullName)); - } - catch - { - devkitPathName = string.Empty; - return null; - } - } - - private T? TryGetShpkDevkitData(string category, uint? id, bool mayVary) where T : class - => TryGetShpkDevkitData(AssociatedShpkDevkit, LoadedShpkDevkitPathName, category, id, mayVary) - ?? TryGetShpkDevkitData(AssociatedBaseDevkit, LoadedBaseDevkitPathName, category, id, mayVary); - - private T? TryGetShpkDevkitData(JObject? devkit, string devkitPathName, string category, uint? id, bool mayVary) where T : class - { - if (devkit == null) - return null; - - try - { - var data = devkit[category]; - if (id.HasValue) - data = data?[id.Value.ToString()]; - - if (mayVary && (data as JObject)?["Vary"] != null) - { - var selector = BuildSelector(data!["Vary"]! - .Select(key => (uint)key) - .Select(key => Mtrl.GetShaderKey(key)?.Value ?? AssociatedShpk!.GetMaterialKeyById(key)!.Value.DefaultValue)); - var index = (int)data["Selectors"]![selector.ToString()]!; - data = data["Items"]![index]; - } - - return data?.ToObject(typeof(T)) as T; - } - catch (Exception e) - { - // Some element in the JSON was undefined or invalid (wrong type, key that doesn't exist in the ShPk, index out of range, …) - Penumbra.Log.Error($"Error while traversing the ShPk dev-kit file at {devkitPathName}: {e}"); - return null; - } - } - - private void UpdateShaderKeys() - { - ShaderKeys.Clear(); - if (AssociatedShpk != null) - foreach (var key in AssociatedShpk.MaterialKeys) - { - var dkData = TryGetShpkDevkitData("ShaderKeys", key.Id, false); - var hasDkLabel = !string.IsNullOrEmpty(dkData?.Label); - - var valueSet = new HashSet(key.Values); - if (dkData != null) - valueSet.UnionWith(dkData.Values.Keys); - - var mtrlKeyIndex = Mtrl.FindOrAddShaderKey(key.Id, key.DefaultValue); - var values = valueSet.Select(value => - { - if (dkData != null && dkData.Values.TryGetValue(value, out var dkValue)) - return (dkValue.Label.Length > 0 ? dkValue.Label : $"0x{value:X8}", value, dkValue.Description); - - return ($"0x{value:X8}", value, string.Empty); - }).ToArray(); - Array.Sort(values, (x, y) => - { - if (x.Value == key.DefaultValue) - return -1; - if (y.Value == key.DefaultValue) - return 1; - - return string.Compare(x.Label, y.Label, StringComparison.Ordinal); - }); - ShaderKeys.Add((hasDkLabel ? dkData!.Label : $"0x{key.Id:X8}", mtrlKeyIndex, dkData?.Description ?? string.Empty, - !hasDkLabel, values)); - } - else - foreach (var (key, index) in Mtrl.ShaderPackage.ShaderKeys.WithIndex()) - ShaderKeys.Add(($"0x{key.Category:X8}", index, string.Empty, true, Array.Empty<(string, uint, string)>())); - } - - private void UpdateShaders() - { - VertexShaders.Clear(); - PixelShaders.Clear(); - if (AssociatedShpk == null) - { - ShadersKnown = false; - } - else - { - ShadersKnown = true; - var systemKeySelectors = AllSelectors(AssociatedShpk.SystemKeys).ToArray(); - var sceneKeySelectors = AllSelectors(AssociatedShpk.SceneKeys).ToArray(); - var subViewKeySelectors = AllSelectors(AssociatedShpk.SubViewKeys).ToArray(); - var materialKeySelector = - BuildSelector(AssociatedShpk.MaterialKeys.Select(key => Mtrl.GetOrAddShaderKey(key.Id, key.DefaultValue).Value)); - foreach (var systemKeySelector in systemKeySelectors) - { - foreach (var sceneKeySelector in sceneKeySelectors) - { - foreach (var subViewKeySelector in subViewKeySelectors) - { - var selector = BuildSelector(systemKeySelector, sceneKeySelector, materialKeySelector, subViewKeySelector); - var node = AssociatedShpk.GetNodeBySelector(selector); - if (node.HasValue) - foreach (var pass in node.Value.Passes) - { - VertexShaders.Add((int)pass.VertexShader); - PixelShaders.Add((int)pass.PixelShader); - } - else - ShadersKnown = false; - } - } - } - } - - var vertexShaders = VertexShaders.OrderBy(i => i).Select(i => $"#{i}"); - var pixelShaders = PixelShaders.OrderBy(i => i).Select(i => $"#{i}"); - - VertexShadersString = $"Vertex Shaders: {string.Join(", ", ShadersKnown ? vertexShaders : vertexShaders.Append("???"))}"; - PixelShadersString = $"Pixel Shaders: {string.Join(", ", ShadersKnown ? pixelShaders : pixelShaders.Append("???"))}"; - - ShaderComment = TryGetShpkDevkitData("Comment", null, true) ?? string.Empty; - } - - private void UpdateTextures() - { - Textures.Clear(); - SamplerIds.Clear(); - if (AssociatedShpk == null) - { - SamplerIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId)); - if (Mtrl.HasTable) - SamplerIds.Add(TableSamplerId); - - foreach (var (sampler, index) in Mtrl.ShaderPackage.Samplers.WithIndex()) - Textures.Add(($"0x{sampler.SamplerId:X8}", sampler.TextureIndex, index, string.Empty, true)); - } - else - { - foreach (var index in VertexShaders) - SamplerIds.UnionWith(AssociatedShpk.VertexShaders[index].Samplers.Select(sampler => sampler.Id)); - foreach (var index in PixelShaders) - SamplerIds.UnionWith(AssociatedShpk.PixelShaders[index].Samplers.Select(sampler => sampler.Id)); - if (!ShadersKnown) - { - SamplerIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId)); - if (Mtrl.HasTable) - SamplerIds.Add(TableSamplerId); - } - - foreach (var samplerId in SamplerIds) - { - var shpkSampler = AssociatedShpk.GetSamplerById(samplerId); - if (shpkSampler is not { Slot: 2 }) - continue; - - var dkData = TryGetShpkDevkitData("Samplers", samplerId, true); - var hasDkLabel = !string.IsNullOrEmpty(dkData?.Label); - - var sampler = Mtrl.GetOrAddSampler(samplerId, dkData?.DefaultTexture ?? string.Empty, out var samplerIndex); - Textures.Add((hasDkLabel ? dkData!.Label : shpkSampler.Value.Name, sampler.TextureIndex, samplerIndex, - dkData?.Description ?? string.Empty, !hasDkLabel)); - } - - if (SamplerIds.Contains(TableSamplerId)) - Mtrl.HasTable = true; - } - - Textures.Sort((x, y) => string.CompareOrdinal(x.Label, y.Label)); - - TextureLabelWidth = 50f * UiHelpers.Scale; - - float helpWidth; - using (var _ = ImRaii.PushFont(UiBuilder.IconFont)) - { - helpWidth = ImGui.GetStyle().ItemSpacing.X + ImGui.CalcTextSize(FontAwesomeIcon.InfoCircle.ToIconString()).X; - } - - foreach (var (label, _, _, description, monoFont) in Textures) - { - if (!monoFont) - TextureLabelWidth = Math.Max(TextureLabelWidth, ImGui.CalcTextSize(label).X + (description.Length > 0 ? helpWidth : 0.0f)); - } - - using (var _ = ImRaii.PushFont(UiBuilder.MonoFont)) - { - foreach (var (label, _, _, description, monoFont) in Textures) - { - if (monoFont) - TextureLabelWidth = Math.Max(TextureLabelWidth, - ImGui.CalcTextSize(label).X + (description.Length > 0 ? helpWidth : 0.0f)); - } - } - - TextureLabelWidth = TextureLabelWidth / UiHelpers.Scale + 4; - } - - private void UpdateConstants() - { - static List FindOrAddGroup(List<(string, List)> groups, string name) - { - foreach (var (groupName, group) in groups) - { - if (string.Equals(name, groupName, StringComparison.Ordinal)) - return group; - } - - var newGroup = new List(16); - groups.Add((name, newGroup)); - return newGroup; - } - - Constants.Clear(); - if (AssociatedShpk == null) - { - var fcGroup = FindOrAddGroup(Constants, "Further Constants"); - foreach (var (constant, index) in Mtrl.ShaderPackage.Constants.WithIndex()) - { - var values = Mtrl.GetConstantValues(constant); - for (var i = 0; i < values.Length; i += 4) - { - fcGroup.Add(($"0x{constant.Id:X8}", index, i..Math.Min(i + 4, values.Length), string.Empty, true, - FloatConstantEditor.Default)); - } - } - } - else - { - var prefix = AssociatedShpk.GetConstantById(MaterialParamsConstantId)?.Name ?? string.Empty; - foreach (var shpkConstant in AssociatedShpk.MaterialParams) - { - if ((shpkConstant.ByteSize & 0x3) != 0) - continue; - - var constant = Mtrl.GetOrAddConstant(shpkConstant.Id, shpkConstant.ByteSize >> 2, out var constantIndex); - var values = Mtrl.GetConstantValues(constant); - var handledElements = new IndexSet(values.Length, false); - - var dkData = TryGetShpkDevkitData("Constants", shpkConstant.Id, true); - if (dkData != null) - foreach (var dkConstant in dkData) - { - var offset = (int)dkConstant.Offset; - var length = values.Length - offset; - if (dkConstant.Length.HasValue) - length = Math.Min(length, (int)dkConstant.Length.Value); - if (length <= 0) - continue; - - var editor = dkConstant.CreateEditor(); - if (editor != null) - FindOrAddGroup(Constants, dkConstant.Group.Length > 0 ? dkConstant.Group : "Further Constants") - .Add((dkConstant.Label, constantIndex, offset..(offset + length), dkConstant.Description, false, editor)); - handledElements.AddRange(offset, length); - } - - var fcGroup = FindOrAddGroup(Constants, "Further Constants"); - foreach (var (start, end) in handledElements.Ranges(complement:true)) - { - if ((shpkConstant.ByteOffset & 0x3) == 0) - { - var offset = shpkConstant.ByteOffset >> 2; - for (int i = (start & ~0x3) - (offset & 0x3), j = offset >> 2; i < end; i += 4, ++j) - { - var rangeStart = Math.Max(i, start); - var rangeEnd = Math.Min(i + 4, end); - if (rangeEnd > rangeStart) - fcGroup.Add(( - $"{prefix}[{j:D2}]{VectorSwizzle((offset + rangeStart) & 0x3, (offset + rangeEnd - 1) & 0x3)} (0x{shpkConstant.Id:X8})", - constantIndex, rangeStart..rangeEnd, string.Empty, true, FloatConstantEditor.Default)); - } - } - else - { - for (var i = start; i < end; i += 4) - { - fcGroup.Add(($"0x{shpkConstant.Id:X8}", constantIndex, i..Math.Min(i + 4, end), string.Empty, true, - FloatConstantEditor.Default)); - } - } - } - } - } - - Constants.RemoveAll(group => group.Constants.Count == 0); - Constants.Sort((x, y) => - { - if (string.Equals(x.Header, "Further Constants", StringComparison.Ordinal)) - return 1; - if (string.Equals(y.Header, "Further Constants", StringComparison.Ordinal)) - return -1; - - return string.Compare(x.Header, y.Header, StringComparison.Ordinal); - }); - // HACK the Replace makes w appear after xyz, for the cbuffer-location-based naming scheme - foreach (var (_, group) in Constants) - { - group.Sort((x, y) => string.CompareOrdinal( - x.MonoFont ? x.Label.Replace("].w", "].{") : x.Label, - y.MonoFont ? y.Label.Replace("].w", "].{") : y.Label)); - } - } - - public unsafe void BindToMaterialInstances() - { - UnbindFromMaterialInstances(); - - var instances = MaterialInfo.FindMaterials(_edit._resourceTreeFactory.GetLocalPlayerRelatedCharacters().Select(ch => ch.Address), - FilePath); - - var foundMaterials = new HashSet(); - foreach (var materialInfo in instances) - { - var material = materialInfo.GetDrawObjectMaterial(_edit._objects); - if (foundMaterials.Contains((nint)material)) - continue; - - try - { - MaterialPreviewers.Add(new LiveMaterialPreviewer(_edit._objects, materialInfo)); - foundMaterials.Add((nint)material); - } - catch (InvalidOperationException) - { - // Carry on without that previewer. - } - } - - UpdateMaterialPreview(); - - if (!Mtrl.HasTable) - return; - - foreach (var materialInfo in instances) - { - try - { - ColorTablePreviewers.Add(new LiveColorTablePreviewer(_edit._objects, _edit._framework, materialInfo)); - } - catch (InvalidOperationException) - { - // Carry on without that previewer. - } - } - - UpdateColorTablePreview(); - } - - private void UnbindFromMaterialInstances() - { - foreach (var previewer in MaterialPreviewers) - previewer.Dispose(); - MaterialPreviewers.Clear(); - - foreach (var previewer in ColorTablePreviewers) - previewer.Dispose(); - ColorTablePreviewers.Clear(); - } - - private unsafe void UnbindFromDrawObjectMaterialInstances(CharacterBase* characterBase) - { - for (var i = MaterialPreviewers.Count; i-- > 0;) - { - var previewer = MaterialPreviewers[i]; - if (previewer.DrawObject != characterBase) - continue; - - previewer.Dispose(); - MaterialPreviewers.RemoveAt(i); - } - - for (var i = ColorTablePreviewers.Count; i-- > 0;) - { - var previewer = ColorTablePreviewers[i]; - if (previewer.DrawObject != characterBase) - continue; - - previewer.Dispose(); - ColorTablePreviewers.RemoveAt(i); - } - } - - public void SetShaderPackageFlags(uint shPkFlags) - { - foreach (var previewer in MaterialPreviewers) - previewer.SetShaderPackageFlags(shPkFlags); - } - - public void SetMaterialParameter(uint parameterCrc, Index offset, Span value) - { - foreach (var previewer in MaterialPreviewers) - previewer.SetMaterialParameter(parameterCrc, offset, value); - } - - public void SetSamplerFlags(uint samplerCrc, uint samplerFlags) - { - foreach (var previewer in MaterialPreviewers) - previewer.SetSamplerFlags(samplerCrc, samplerFlags); - } - - private void UpdateMaterialPreview() - { - SetShaderPackageFlags(Mtrl.ShaderPackage.Flags); - foreach (var constant in Mtrl.ShaderPackage.Constants) - { - var values = Mtrl.GetConstantValues(constant); - if (values != null) - SetMaterialParameter(constant.Id, 0, values); - } - - foreach (var sampler in Mtrl.ShaderPackage.Samplers) - SetSamplerFlags(sampler.SamplerId, sampler.Flags); - } - - public void HighlightColorTableRow(int rowIdx) - { - var oldRowIdx = HighlightedColorTableRow; - - if (HighlightedColorTableRow != rowIdx) - { - HighlightedColorTableRow = rowIdx; - HighlightTime.Restart(); - } - - if (oldRowIdx >= 0) - UpdateColorTableRowPreview(oldRowIdx); - if (rowIdx >= 0) - UpdateColorTableRowPreview(rowIdx); - } - - public void CancelColorTableHighlight() - { - var rowIdx = HighlightedColorTableRow; - - HighlightedColorTableRow = -1; - HighlightTime.Reset(); - - if (rowIdx >= 0) - UpdateColorTableRowPreview(rowIdx); - } - - public void UpdateColorTableRowPreview(int rowIdx) - { - if (ColorTablePreviewers.Count == 0) - return; - - if (!Mtrl.HasTable) - return; - - var row = new LegacyColorTableRow(Mtrl.Table[rowIdx]); - if (Mtrl.HasDyeTable) - { - var stm = _edit._stainService.StmFile; - var dye = new LegacyColorDyeTableRow(Mtrl.DyeTable[rowIdx]); - if (stm.TryGetValue(dye.Template, _edit._stainService.StainCombo.CurrentSelection.Key, out var dyes)) - row.ApplyDyeTemplate(dye, dyes); - } - - if (HighlightedColorTableRow == rowIdx) - ApplyHighlight(ref row, (float)HighlightTime.Elapsed.TotalSeconds); - - foreach (var previewer in ColorTablePreviewers) - { - row.AsHalves().CopyTo(previewer.ColorTable.AsSpan() - .Slice(LiveColorTablePreviewer.TextureWidth * 4 * rowIdx, LiveColorTablePreviewer.TextureWidth * 4)); - previewer.ScheduleUpdate(); - } - } - - public void UpdateColorTablePreview() - { - if (ColorTablePreviewers.Count == 0) - return; - - if (!Mtrl.HasTable) - return; - - var rows = new LegacyColorTable(Mtrl.Table); - var dyeRows = new LegacyColorDyeTable(Mtrl.DyeTable); - if (Mtrl.HasDyeTable) - { - var stm = _edit._stainService.StmFile; - var stainId = (StainId)_edit._stainService.StainCombo.CurrentSelection.Key; - for (var i = 0; i < LegacyColorTable.NumUsedRows; ++i) - { - ref var row = ref rows[i]; - var dye = dyeRows[i]; - if (stm.TryGetValue(dye.Template, stainId, out var dyes)) - row.ApplyDyeTemplate(dye, dyes); - } - } - - if (HighlightedColorTableRow >= 0) - ApplyHighlight(ref rows[HighlightedColorTableRow], (float)HighlightTime.Elapsed.TotalSeconds); - - foreach (var previewer in ColorTablePreviewers) - { - // TODO: Dawntrail - rows.AsHalves().CopyTo(previewer.ColorTable); - previewer.ScheduleUpdate(); - } - } - - private static void ApplyHighlight(ref LegacyColorTableRow row, float time) - { - var level = (MathF.Sin(time * 2.0f * MathF.PI) + 2.0f) / 3.0f / 255.0f; - var baseColor = ColorId.InGameHighlight.Value(); - var color = level * new Vector3(baseColor & 0xFF, (baseColor >> 8) & 0xFF, (baseColor >> 16) & 0xFF); - - row.Diffuse = Vector3.Zero; - row.Specular = Vector3.Zero; - row.Emissive = color * color; - } - - public void Update() - { - UpdateShaders(); - UpdateTextures(); - UpdateConstants(); - } - - public unsafe MtrlTab(ModEditWindow edit, MtrlFile file, string filePath, bool writable) - { - _edit = edit; - Mtrl = file; - FilePath = filePath; - Writable = writable; - AssociatedBaseDevkit = TryLoadShpkDevkit("_base", out LoadedBaseDevkitPathName); - LoadShpk(FindAssociatedShpk(out _, out _)); - if (writable) - { - _edit._characterBaseDestructor.Subscribe(UnbindFromDrawObjectMaterialInstances, CharacterBaseDestructor.Priority.MtrlTab); - BindToMaterialInstances(); - } - } - - public unsafe void Dispose() - { - UnbindFromMaterialInstances(); - if (Writable) - _edit._characterBaseDestructor.Unsubscribe(UnbindFromDrawObjectMaterialInstances); - } - - // TODO Readd ShadersKnown - public bool Valid - => (true || ShadersKnown) && Mtrl.Valid; - - public byte[] Write() - { - var output = Mtrl.Clone(); - output.GarbageCollect(AssociatedShpk, SamplerIds); - - return output.Write(); - } - - private sealed class DevkitShaderKeyValue - { - public string Label = string.Empty; - public string Description = string.Empty; - } - - private sealed class DevkitShaderKey - { - public string Label = string.Empty; - public string Description = string.Empty; - public Dictionary Values = new(); - } - - private sealed class DevkitSampler - { - public string Label = string.Empty; - public string Description = string.Empty; - public string DefaultTexture = string.Empty; - } - - private enum DevkitConstantType - { - Hidden = -1, - Float = 0, - Integer = 1, - Color = 2, - Enum = 3, - } - - private sealed class DevkitConstantValue - { - public string Label = string.Empty; - public string Description = string.Empty; - public float Value = 0; - } - - private sealed class DevkitConstant - { - public uint Offset = 0; - public uint? Length = null; - public string Group = string.Empty; - public string Label = string.Empty; - public string Description = string.Empty; - public DevkitConstantType Type = DevkitConstantType.Float; - - public float? Minimum = null; - public float? Maximum = null; - public float? Speed = null; - public float RelativeSpeed = 0.0f; - public float Factor = 1.0f; - public float Bias = 0.0f; - public byte Precision = 3; - public string Unit = string.Empty; - - public bool SquaredRgb = false; - public bool Clamped = false; - - public DevkitConstantValue[] Values = Array.Empty(); - - public IConstantEditor? CreateEditor() - => Type switch - { - DevkitConstantType.Hidden => null, - DevkitConstantType.Float => new FloatConstantEditor(Minimum, Maximum, Speed ?? 0.1f, RelativeSpeed, Factor, Bias, Precision, - Unit), - DevkitConstantType.Integer => new IntConstantEditor(ToInteger(Minimum), ToInteger(Maximum), Speed ?? 0.25f, RelativeSpeed, - Factor, Bias, Unit), - DevkitConstantType.Color => new ColorConstantEditor(SquaredRgb, Clamped), - DevkitConstantType.Enum => new EnumConstantEditor(Array.ConvertAll(Values, - value => (value.Label, value.Value, value.Description))), - _ => FloatConstantEditor.Default, - }; - - private static int? ToInteger(float? value) - => value.HasValue ? (int)Math.Clamp(MathF.Round(value.Value), int.MinValue, int.MaxValue) : null; - } - } -}