Update StainService for DT

This commit is contained in:
Exter-N 2024-08-03 17:47:22 +02:00
parent 59b3859f11
commit e8182f285e
3 changed files with 124 additions and 39 deletions

View file

@ -5,10 +5,15 @@ namespace Penumbra.Interop.Structs;
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
public unsafe struct CharacterUtilityData public unsafe struct CharacterUtilityData
{ {
public const int IndexHumanPbd = 63; public const int IndexHumanPbd = 63;
public const int IndexTransparentTex = 79; public const int IndexTransparentTex = 79;
public const int IndexDecalTex = 80; public const int IndexDecalTex = 80;
public const int IndexSkinShpk = 83; public const int IndexTileOrbArrayTex = 81;
public const int IndexTileNormArrayTex = 82;
public const int IndexSkinShpk = 83;
public const int IndexGudStm = 94;
public const int IndexLegacyStm = 95;
public const int IndexSphereDArrayTex = 96;
public static readonly MetaIndex[] EqdpIndices = Enum.GetNames<MetaIndex>() public static readonly MetaIndex[] EqdpIndices = Enum.GetNames<MetaIndex>()
.Zip(Enum.GetValues<MetaIndex>()) .Zip(Enum.GetValues<MetaIndex>())
@ -97,8 +102,23 @@ public unsafe struct CharacterUtilityData
[FieldOffset(8 + IndexDecalTex * 8)] [FieldOffset(8 + IndexDecalTex * 8)]
public TextureResourceHandle* DecalTexResource; public TextureResourceHandle* DecalTexResource;
[FieldOffset(8 + IndexTileOrbArrayTex * 8)]
public TextureResourceHandle* TileOrbArrayTexResource;
[FieldOffset(8 + IndexTileNormArrayTex * 8)]
public TextureResourceHandle* TileNormArrayTexResource;
[FieldOffset(8 + IndexSkinShpk * 8)] [FieldOffset(8 + IndexSkinShpk * 8)]
public ResourceHandle* SkinShpkResource; public ResourceHandle* SkinShpkResource;
[FieldOffset(8 + IndexGudStm * 8)]
public ResourceHandle* GudStmResource;
[FieldOffset(8 + IndexLegacyStm * 8)]
public ResourceHandle* LegacyStmResource;
[FieldOffset(8 + IndexSphereDArrayTex * 8)]
public TextureResourceHandle* SphereDArrayTexResource;
// not included resources have no known use case. // not included resources have no known use case.
} }

View file

@ -6,19 +6,25 @@ using OtterGui.Services;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.GameData.DataContainers; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.UI.AdvancedWindow; using Penumbra.GameData.Files.StainMapStructs;
using Penumbra.Interop.Services;
using Penumbra.Interop.Structs;
using Penumbra.UI.AdvancedWindow.Materials;
namespace Penumbra.Services; namespace Penumbra.Services;
public class StainService : IService public class StainService : IService
{ {
public sealed class StainTemplateCombo(FilterComboColors stainCombo, StmFile stmFile) public sealed class StainTemplateCombo<TDyePack>(FilterComboColors[] stainCombos, StmFile<TDyePack> stmFile)
: FilterComboCache<ushort>(stmFile.Entries.Keys.Prepend((ushort)0), MouseWheelType.None, Penumbra.Log) : FilterComboCache<ushort>(stmFile.Entries.Keys.Prepend((ushort)0), MouseWheelType.None, Penumbra.Log) where TDyePack : unmanaged, IDyePack<TDyePack>
{ {
// FIXME There might be a better way to handle that.
public int CurrentDyeChannel = 0;
protected override float GetFilterWidth() protected override float GetFilterWidth()
{ {
var baseSize = ImGui.CalcTextSize("0000").X + ImGui.GetStyle().ScrollbarSize + ImGui.GetStyle().ItemInnerSpacing.X; var baseSize = ImGui.CalcTextSize("0000").X + ImGui.GetStyle().ScrollbarSize + ImGui.GetStyle().ItemInnerSpacing.X;
if (stainCombo.CurrentSelection.Key == 0) if (stainCombos[CurrentDyeChannel].CurrentSelection.Key == 0)
return baseSize; return baseSize;
return baseSize + ImGui.GetTextLineHeight() * 3 + ImGui.GetStyle().ItemInnerSpacing.X * 3; return baseSize + ImGui.GetTextLineHeight() * 3 + ImGui.GetStyle().ItemInnerSpacing.X * 3;
@ -47,33 +53,73 @@ public class StainService : IService
protected override bool DrawSelectable(int globalIdx, bool selected) protected override bool DrawSelectable(int globalIdx, bool selected)
{ {
var ret = base.DrawSelectable(globalIdx, selected); var ret = base.DrawSelectable(globalIdx, selected);
var selection = stainCombo.CurrentSelection.Key; var selection = stainCombos[CurrentDyeChannel].CurrentSelection.Key;
if (selection == 0 || !stmFile.TryGetValue(Items[globalIdx], selection, out var colors)) if (selection == 0 || !stmFile.TryGetValue(Items[globalIdx], selection, out var colors))
return ret; return ret;
ImGui.SameLine(); ImGui.SameLine();
var frame = new Vector2(ImGui.GetTextLineHeight()); var frame = new Vector2(ImGui.GetTextLineHeight());
ImGui.ColorButton("D", new Vector4(ModEditWindow.PseudoSqrtRgb(colors.Diffuse), 1), 0, frame); ImGui.ColorButton("D", new Vector4(MtrlTab.PseudoSqrtRgb((Vector3)colors.DiffuseColor), 1), 0, frame);
ImGui.SameLine(); ImGui.SameLine();
ImGui.ColorButton("S", new Vector4(ModEditWindow.PseudoSqrtRgb(colors.Specular), 1), 0, frame); ImGui.ColorButton("S", new Vector4(MtrlTab.PseudoSqrtRgb((Vector3)colors.SpecularColor), 1), 0, frame);
ImGui.SameLine(); ImGui.SameLine();
ImGui.ColorButton("E", new Vector4(ModEditWindow.PseudoSqrtRgb(colors.Emissive), 1), 0, frame); ImGui.ColorButton("E", new Vector4(MtrlTab.PseudoSqrtRgb((Vector3)colors.EmissiveColor), 1), 0, frame);
return ret; return ret;
} }
} }
public readonly DictStain StainData; public const int ChannelCount = 2;
public readonly FilterComboColors StainCombo;
public readonly StmFile StmFile;
public readonly StainTemplateCombo TemplateCombo;
public StainService(IDataManager dataManager, DictStain stainData) public readonly DictStain StainData;
public readonly FilterComboColors StainCombo1;
public readonly FilterComboColors StainCombo2; // FIXME is there a better way to handle this?
public readonly StmFile<LegacyDyePack> LegacyStmFile;
public readonly StmFile<DyePack> GudStmFile;
public readonly StainTemplateCombo<LegacyDyePack> LegacyTemplateCombo;
public readonly StainTemplateCombo<DyePack> GudTemplateCombo;
public unsafe StainService(IDataManager dataManager, CharacterUtility characterUtility, DictStain stainData)
{ {
StainData = stainData; StainData = stainData;
StainCombo = new FilterComboColors(140, MouseWheelType.None, StainCombo1 = CreateStainCombo();
() => StainData.Value.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))).ToList(), StainCombo2 = CreateStainCombo();
Penumbra.Log); LegacyStmFile = LoadStmFile<LegacyDyePack>(characterUtility.Address->LegacyStmResource, dataManager);
StmFile = new StmFile(dataManager); GudStmFile = LoadStmFile<DyePack>(characterUtility.Address->GudStmResource, dataManager);
TemplateCombo = new StainTemplateCombo(StainCombo, StmFile);
FilterComboColors[] stainCombos = [StainCombo1, StainCombo2];
LegacyTemplateCombo = new StainTemplateCombo<LegacyDyePack>(stainCombos, LegacyStmFile);
GudTemplateCombo = new StainTemplateCombo<DyePack>(stainCombos, GudStmFile);
} }
/// <summary> Retrieves the <see cref="FilterComboColors"/> instance for the given channel. Indexing is zero-based. </summary>
public FilterComboColors GetStainCombo(int channel)
=> channel switch
{
0 => StainCombo1,
1 => StainCombo2,
_ => throw new ArgumentOutOfRangeException(nameof(channel), channel, $"Unsupported dye channel {channel} (supported values are 0 and 1)")
};
/// <summary> Loads a STM file. Opportunistically attempts to re-use the file already read by the game, with Lumina fallback. </summary>
private static unsafe StmFile<TDyePack> LoadStmFile<TDyePack>(ResourceHandle* stmResourceHandle, IDataManager dataManager) where TDyePack : unmanaged, IDyePack<TDyePack>
{
if (stmResourceHandle != null)
{
var stmData = stmResourceHandle->CsHandle.GetDataSpan();
if (stmData.Length > 0)
{
Penumbra.Log.Debug($"[StainService] Loading StmFile<{typeof(TDyePack)}> from ResourceHandle 0x{(nint)stmResourceHandle:X}");
return new StmFile<TDyePack>(stmData);
}
}
Penumbra.Log.Debug($"[StainService] Loading StmFile<{typeof(TDyePack)}> from Lumina");
return new StmFile<TDyePack>(dataManager);
}
private FilterComboColors CreateStainCombo()
=> new(140, MouseWheelType.None,
() => StainData.Value.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))).ToList(),
Penumbra.Log);
} }

View file

@ -42,6 +42,9 @@ using ImGuiClip = OtterGui.ImGuiClip;
using Penumbra.Api.IpcTester; using Penumbra.Api.IpcTester;
using Penumbra.Interop.Hooks.PostProcessing; using Penumbra.Interop.Hooks.PostProcessing;
using Penumbra.Interop.Hooks.ResourceLoading; using Penumbra.Interop.Hooks.ResourceLoading;
using Penumbra.GameData.Files.StainMapStructs;
using Penumbra.UI.AdvancedWindow;
using Penumbra.UI.AdvancedWindow.Materials;
namespace Penumbra.UI.Tabs.Debug; namespace Penumbra.UI.Tabs.Debug;
@ -697,32 +700,48 @@ public class DebugTab : Window, ITab, IUiService
if (!mainTree) if (!mainTree)
return; return;
foreach (var (key, data) in _stains.StmFile.Entries) using (var legacyTree = TreeNode("stainingtemplate.stm"))
{
if (legacyTree)
DrawStainTemplatesFile(_stains.LegacyStmFile);
}
using (var gudTree = TreeNode("stainingtemplate_gud.stm"))
{
if (gudTree)
DrawStainTemplatesFile(_stains.GudStmFile);
}
}
private static void DrawStainTemplatesFile<TDyePack>(StmFile<TDyePack> stmFile) where TDyePack : unmanaged, IDyePack<TDyePack>
{
foreach (var (key, data) in stmFile.Entries)
{ {
using var tree = TreeNode($"Template {key}"); using var tree = TreeNode($"Template {key}");
if (!tree) if (!tree)
continue; continue;
using var table = Table("##table", 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); using var table = Table("##table", data.Colors.Length + data.Scalars.Length, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
if (!table) if (!table)
continue; continue;
for (var i = 0; i < StmFile.StainingTemplateEntry.NumElements; ++i) for (var i = 0; i < StmFile<TDyePack>.StainingTemplateEntry.NumElements; ++i)
{ {
var (r, g, b) = data.DiffuseEntries[i]; foreach (var list in data.Colors)
ImGuiUtil.DrawTableColumn($"{r:F6} | {g:F6} | {b:F6}"); {
var color = list[i];
ImGui.TableNextColumn();
var frame = new Vector2(ImGui.GetTextLineHeight());
ImGui.ColorButton("###color", new Vector4(MtrlTab.PseudoSqrtRgb((Vector3)color), 1), 0, frame);
ImGui.SameLine();
ImGui.TextUnformatted($"{color.Red:F6} | {color.Green:F6} | {color.Blue:F6}");
}
(r, g, b) = data.SpecularEntries[i]; foreach (var list in data.Scalars)
ImGuiUtil.DrawTableColumn($"{r:F6} | {g:F6} | {b:F6}"); {
var scalar = list[i];
(r, g, b) = data.EmissiveEntries[i]; ImGuiUtil.DrawTableColumn($"{scalar:F6}");
ImGuiUtil.DrawTableColumn($"{r:F6} | {g:F6} | {b:F6}"); }
var a = data.SpecularPowerEntries[i];
ImGuiUtil.DrawTableColumn($"{a:F6}");
a = data.GlossEntries[i];
ImGuiUtil.DrawTableColumn($"{a:F6}");
} }
} }
} }