mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-15 05:04:15 +01:00
Fixups.
This commit is contained in:
parent
728a081419
commit
c8e859ae05
4 changed files with 28 additions and 1337 deletions
|
|
@ -33,6 +33,7 @@ public partial class MtrlTab
|
||||||
UpdateColorTableRowPreview(i);
|
UpdateColorTableRowPreview(i);
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,6 +57,7 @@ public partial class MtrlTab
|
||||||
UpdateColorTableRowPreview(i);
|
UpdateColorTableRowPreview(i);
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,8 +110,10 @@ public partial class MtrlTab
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
|
using (ImRaii.PushFont(UiBuilder.MonoFont))
|
||||||
ImUtf8.Text($"{(rowIdx >> 1) + 1,2:D}{"AB"[rowIdx & 1]}");
|
{
|
||||||
|
ImUtf8.Text($"{(rowIdx >> 1) + 1,2:D}{"AB"[rowIdx & 1]}");
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
using var dis = ImRaii.Disabled(disabled);
|
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,
|
ret |= CtApplyStainCheckbox("##dyeSpecular"u8, "Apply Specular Color on Dye"u8, dye.SpecularColor,
|
||||||
b => dyeTable[rowIdx].SpecularColor = b);
|
b => dyeTable[rowIdx].SpecularColor = b);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetNextItemWidth(pctSize);
|
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));
|
v => table[rowIdx].SpecularMask = (Half)(v * 0.01f));
|
||||||
if (dyeTable != null)
|
if (dyeTable != null)
|
||||||
{
|
{
|
||||||
|
|
@ -155,7 +161,8 @@ public partial class MtrlTab
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.SetNextItemWidth(floatSize);
|
ImGui.SetNextItemWidth(floatSize);
|
||||||
var glossStrengthMin = ImGui.GetIO().KeyCtrl ? 0.0f : HalfEpsilon;
|
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);
|
v => table[rowIdx].Shininess = v);
|
||||||
|
|
||||||
if (dyeTable != null)
|
if (dyeTable != null)
|
||||||
|
|
@ -197,7 +204,7 @@ public partial class MtrlTab
|
||||||
{
|
{
|
||||||
using var id = ImRaii.PushId(rowIdx);
|
using var id = ImRaii.PushId(rowIdx);
|
||||||
ref var row = ref table[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 floatSize = LegacyColorTableFloatSize * UiHelpers.Scale;
|
||||||
var pctSize = LegacyColorTablePercentageSize * UiHelpers.Scale;
|
var pctSize = LegacyColorTablePercentageSize * UiHelpers.Scale;
|
||||||
var intSize = LegacyColorTableIntegerSize * UiHelpers.Scale;
|
var intSize = LegacyColorTableIntegerSize * UiHelpers.Scale;
|
||||||
|
|
@ -213,8 +220,10 @@ public partial class MtrlTab
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
|
using (ImRaii.PushFont(UiBuilder.MonoFont))
|
||||||
|
{
|
||||||
ImUtf8.Text($"{(rowIdx >> 1) + 1,2:D}{"AB"[rowIdx & 1]}");
|
ImUtf8.Text($"{(rowIdx >> 1) + 1,2:D}{"AB"[rowIdx & 1]}");
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
using var dis = ImRaii.Disabled(disabled);
|
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,
|
ret |= CtApplyStainCheckbox("##dyeSpecular"u8, "Apply Specular Color on Dye"u8, dye.SpecularColor,
|
||||||
b => dyeTable[rowIdx].SpecularColor = b);
|
b => dyeTable[rowIdx].SpecularColor = b);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetNextItemWidth(pctSize);
|
ImGui.SetNextItemWidth(pctSize);
|
||||||
ret |= CtDragScalar("##SpecularMask"u8, "Specular Strength"u8, (float)row.Scalar7 * 100.0f, "%.0f%%"u8, 0f, HalfMaxValue * 100.0f, 1.0f,
|
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.TableNextColumn();
|
||||||
ImGui.SetNextItemWidth(floatSize);
|
ImGui.SetNextItemWidth(floatSize);
|
||||||
var glossStrengthMin = ImGui.GetIO().KeyCtrl ? 0.0f : HalfEpsilon;
|
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);
|
v => table[rowIdx].Scalar3 = v);
|
||||||
|
|
||||||
if (dyeTable != null)
|
if (dyeTable != null)
|
||||||
|
|
@ -307,7 +318,7 @@ public partial class MtrlTab
|
||||||
return ret;
|
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;
|
var stain = _stainService.StainCombo1.CurrentSelection.Key;
|
||||||
if (stain == 0 || !_stainService.LegacyStmFile.TryGetValue(dye.Template, stain, out var values))
|
if (stain == 0 || !_stainService.LegacyStmFile.TryGetValue(dye.Template, stain, out var values))
|
||||||
|
|
@ -326,7 +337,7 @@ public partial class MtrlTab
|
||||||
return ret;
|
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;
|
var stain = _stainService.GetStainCombo(dye.Channel).CurrentSelection.Key;
|
||||||
if (stain == 0 || !_stainService.LegacyStmFile.TryGetValue(dye.Template, stain, out var values))
|
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()),
|
var ret = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.PaintBrush.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
|
||||||
"Apply the selected dye to this row.", disabled, true);
|
"Apply the selected dye to this row.", disabled, true);
|
||||||
|
|
||||||
ret = ret && Mtrl.ApplyDyeToRow(_stainService.LegacyStmFile, [
|
ret = ret
|
||||||
_stainService.StainCombo1.CurrentSelection.Key,
|
&& Mtrl.ApplyDyeToRow(_stainService.LegacyStmFile, [
|
||||||
_stainService.StainCombo2.CurrentSelection.Key,
|
_stainService.StainCombo1.CurrentSelection.Key,
|
||||||
], rowIdx);
|
_stainService.StainCombo2.CurrentSelection.Key,
|
||||||
|
], rowIdx);
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
DrawLegacyDyePreview(values, floatSize);
|
DrawLegacyDyePreview(values, floatSize);
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,7 @@ public partial class MtrlTab
|
||||||
|
|
||||||
var row = Mtrl.Table switch
|
var row = Mtrl.Table switch
|
||||||
{
|
{
|
||||||
LegacyColorTable legacyTable => new ColorTable.Row(legacyTable[rowIdx]),
|
LegacyColorTable legacyTable => new ColorTableRow(legacyTable[rowIdx]),
|
||||||
ColorTable table => table[rowIdx],
|
ColorTable table => table[rowIdx],
|
||||||
_ => throw new InvalidOperationException($"Unsupported color table type {Mtrl.Table.GetType()}"),
|
_ => throw new InvalidOperationException($"Unsupported color table type {Mtrl.Table.GetType()}"),
|
||||||
};
|
};
|
||||||
|
|
@ -199,7 +199,7 @@ public partial class MtrlTab
|
||||||
{
|
{
|
||||||
var dyeRow = Mtrl.DyeTable switch
|
var dyeRow = Mtrl.DyeTable switch
|
||||||
{
|
{
|
||||||
LegacyColorDyeTable legacyDyeTable => new ColorDyeTable.Row(legacyDyeTable[rowIdx]),
|
LegacyColorDyeTable legacyDyeTable => new ColorDyeTableRow(legacyDyeTable[rowIdx]),
|
||||||
ColorDyeTable dyeTable => dyeTable[rowIdx],
|
ColorDyeTable dyeTable => dyeTable[rowIdx],
|
||||||
_ => throw new InvalidOperationException($"Unsupported color dye table type {Mtrl.DyeTable.GetType()}"),
|
_ => 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 level = (MathF.Sin(time * 2.0f * MathF.PI) + 2.0f) / 3.0f / 255.0f;
|
||||||
var baseColor = colorId.Value();
|
var baseColor = colorId.Value();
|
||||||
|
|
|
||||||
|
|
@ -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<byte>.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<ColorTable>())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
ref var rows = ref tab.Mtrl.Table;
|
|
||||||
fixed (void* ptr = data, output = &rows)
|
|
||||||
{
|
|
||||||
MemoryUtility.MemCpyUnchecked(output, ptr, Marshal.SizeOf<ColorTable>());
|
|
||||||
if (data.Length >= Marshal.SizeOf<ColorTable>() + Marshal.SizeOf<ColorDyeTable>()
|
|
||||||
&& tab.Mtrl.HasDyeTable)
|
|
||||||
{
|
|
||||||
ref var dyeRows = ref tab.Mtrl.DyeTable;
|
|
||||||
fixed (void* output2 = &dyeRows)
|
|
||||||
{
|
|
||||||
MemoryUtility.MemCpyUnchecked(output2, (byte*)ptr + Marshal.SizeOf<ColorTable>(),
|
|
||||||
Marshal.SizeOf<ColorDyeTable>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<byte> 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<Vector3> 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);
|
|
||||||
}
|
|
||||||
|
|
@ -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<int> VertexShaders = new(16);
|
|
||||||
public readonly HashSet<int> 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<int> UnfoldedTextures = new(4);
|
|
||||||
public readonly HashSet<uint> 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<LiveMaterialPreviewer> MaterialPreviewers = new(4);
|
|
||||||
public readonly List<LiveColorTablePreviewer> 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<string>(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<T>(string category, uint? id, bool mayVary) where T : class
|
|
||||||
=> TryGetShpkDevkitData<T>(AssociatedShpkDevkit, LoadedShpkDevkitPathName, category, id, mayVary)
|
|
||||||
?? TryGetShpkDevkitData<T>(AssociatedBaseDevkit, LoadedBaseDevkitPathName, category, id, mayVary);
|
|
||||||
|
|
||||||
private T? TryGetShpkDevkitData<T>(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<DevkitShaderKey>("ShaderKeys", key.Id, false);
|
|
||||||
var hasDkLabel = !string.IsNullOrEmpty(dkData?.Label);
|
|
||||||
|
|
||||||
var valueSet = new HashSet<uint>(key.Values);
|
|
||||||
if (dkData != null)
|
|
||||||
valueSet.UnionWith(dkData.Values.Keys);
|
|
||||||
|
|
||||||
var mtrlKeyIndex = Mtrl.FindOrAddShaderKey(key.Id, key.DefaultValue);
|
|
||||||
var values = valueSet.Select<uint, (string Label, uint Value, string Description)>(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<string>("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<DevkitSampler>("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<T> FindOrAddGroup<T>(List<(string, List<T>)> groups, string name)
|
|
||||||
{
|
|
||||||
foreach (var (groupName, group) in groups)
|
|
||||||
{
|
|
||||||
if (string.Equals(name, groupName, StringComparison.Ordinal))
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newGroup = new List<T>(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<DevkitConstant[]>("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<nint>();
|
|
||||||
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<float> 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<uint, DevkitShaderKeyValue> 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<DevkitConstantValue>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue