From a1b455d9a599bacd48c161a160f6662d7bc615f7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 7 Aug 2024 17:55:23 +0200 Subject: [PATCH] Update advanced dyes. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 50 ++++++++++----- Glamourer/Interop/Material/MaterialManager.cs | 14 +++-- .../Interop/Material/MaterialValueIndex.cs | 61 +++++++++++++++++-- .../Interop/Material/MaterialValueManager.cs | 47 ++++++++++---- Glamourer/Interop/Material/PrepareColorSet.cs | 13 +++- Glamourer/State/StateApplier.cs | 18 +++--- OtterGui | 2 +- 7 files changed, 157 insertions(+), 48 deletions(-) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 3ba2a1b..b857bf8 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -1,6 +1,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using Glamourer.Designs; @@ -27,6 +28,7 @@ public sealed unsafe class AdvancedDyePopup( private MaterialValueIndex? _drawIndex; private ActorState _state = null!; private Actor _actor; + private ColorRow.Mode _mode; private byte _selectedMaterial = byte.MaxValue; private bool _anyChanged; private bool _forceFocus; @@ -96,7 +98,7 @@ public sealed unsafe class AdvancedDyePopup( return (path, gamePath); } - private void DrawTabBar(ReadOnlySpan> textures, ref bool firstAvailable) + private void DrawTabBar(ReadOnlySpan> textures, ReadOnlySpan> materials, ref bool firstAvailable) { using var bar = ImRaii.TabBar("tabs"); if (!bar) @@ -105,8 +107,9 @@ public sealed unsafe class AdvancedDyePopup( var table = new ColorTable.Table(); for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) { - var index = _drawIndex!.Value with { MaterialIndex = i }; - var available = index.TryGetTexture(textures, out var texture) && directX.TryGetColorTable(*texture, out table); + var index = _drawIndex!.Value with { MaterialIndex = i }; + var available = index.TryGetTexture(textures, materials, out var texture, out _mode) + && directX.TryGetColorTable(*texture, out table); if (index == preview.LastValueIndex with { RowIndex = 0 }) @@ -163,16 +166,16 @@ public sealed unsafe class AdvancedDyePopup( } } - private void DrawContent(ReadOnlySpan> textures) + private void DrawContent(ReadOnlySpan> textures, ReadOnlySpan> materials) { var firstAvailable = true; - DrawTabBar(textures, ref firstAvailable); + DrawTabBar(textures, materials, ref firstAvailable); if (firstAvailable) ImGui.TextUnformatted("No Editable Materials available."); } - private void DrawWindow(ReadOnlySpan> textures) + private void DrawWindow(ReadOnlySpan> textures, ReadOnlySpan> materials) { var flags = ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoCollapse @@ -208,7 +211,7 @@ public sealed unsafe class AdvancedDyePopup( try { if (window) - DrawContent(textures); + DrawContent(textures, materials); } finally { @@ -223,8 +226,8 @@ public sealed unsafe class AdvancedDyePopup( if (!ShouldBeDrawn()) return; - if (_drawIndex!.Value.TryGetTextures(actor, out var textures)) - DrawWindow(textures); + if (_drawIndex!.Value.TryGetTextures(actor, out var textures, out var materials)) + DrawWindow(textures, materials); } private void DrawTable(MaterialValueIndex materialIndex, ColorTable.Table table) @@ -340,15 +343,30 @@ public sealed unsafe class AdvancedDyePopup( v => value.Model.Emissive = v, "E"); ImGui.SameLine(0, spacing.X); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") - && value.Model.GlossStrength > 0; - ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); + if (_mode is not ColorRow.Mode.Dawntrail) + { + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") + && value.Model.GlossStrength > 0; + ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); + } + else + { + ImGui.Dummy(new Vector2(100 * ImGuiHelpers.GlobalScale, 0)); + } ImGui.SameLine(0, spacing.X); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f%% SS"); - ImGuiUtil.HoverTooltip("Change the specular strength for this row."); + if (_mode is not ColorRow.Mode.Dawntrail) + { + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, + "%.3f%% SS"); + ImGuiUtil.HoverTooltip("Change the specular strength for this row."); + } + else + { + ImGui.Dummy(new Vector2(100 * ImGuiHelpers.GlobalScale, 0)); + } ImGui.SameLine(0, spacing.X); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.", false, diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 3e0e35b..7f13c2d 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -1,5 +1,6 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using FFXIVClientStructs.Havok.Animation.Rig; using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; @@ -67,7 +68,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId), _ => GetTempSlot((Weapon*)characterBase), }; - UpdateMaterialValues(state, values, drawData, ref baseColorSet); + var mode = PrepareColorSet.GetMode(material); + UpdateMaterialValues(state, values, drawData, ref baseColorSet, mode); if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture)) ret = (nint)texture; @@ -75,7 +77,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// Update and apply the glamourer state of an actor according to the application sources when updated by the game. private void UpdateMaterialValues(ActorState state, ReadOnlySpan<(uint Key, MaterialValueState Value)> values, CharacterWeapon drawData, - ref ColorTable.Table colorTable) + ref ColorTable.Table colorTable, ColorRow.Mode mode) { var deleteList = _deleteList.Value!; deleteList.Clear(); @@ -86,17 +88,17 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable ref var row = ref colorTable[idx.RowIndex]; var newGame = new ColorRow(row); if (materialValue.EqualGame(newGame, drawData)) - materialValue.Model.Apply(ref row); + materialValue.Model.Apply(ref row, mode); else switch (materialValue.Source) { case StateSource.Pending: - materialValue.Model.Apply(ref row); + materialValue.Model.Apply(ref row, mode); state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.Manual), out _); break; case StateSource.IpcPending: - materialValue.Model.Apply(ref row); + materialValue.Model.Apply(ref row, mode); state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.IpcManual), out _); break; @@ -106,7 +108,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable break; case StateSource.Fixed: case StateSource.IpcFixed: - materialValue.Model.Apply(ref row); + materialValue.Model.Apply(ref row, mode); state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, materialValue.Source), out _); break; diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 5104713..30f2e68 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -1,9 +1,11 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using Newtonsoft.Json; using Penumbra.GameData.Enums; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; +using CsMaterial = FFXIVClientStructs.FFXIV.Client.Graphics.Render.Material; namespace Glamourer.Interop.Material; @@ -75,21 +77,39 @@ public readonly record struct MaterialValueIndex( return model.IsCharacterBase; } + public unsafe bool TryGetTextures(Actor actor, out ReadOnlySpan> textures, out ReadOnlySpan> materials) + { + if (!TryGetModel(actor, out var model) + || SlotIndex >= model.AsCharacterBase->SlotCount + || model.AsCharacterBase->ColorTableTexturesSpan.Length < (SlotIndex + 1) * MaterialService.MaterialsPerModel) + { + textures = []; + materials = []; + return false; + } + + var from = SlotIndex * MaterialService.MaterialsPerModel; + textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(from, MaterialService.MaterialsPerModel); + materials = model.AsCharacterBase->MaterialsSpan.Slice(from, MaterialService.MaterialsPerModel); + return true; + } + public unsafe bool TryGetTextures(Actor actor, out ReadOnlySpan> textures) { if (!TryGetModel(actor, out var model) || SlotIndex >= model.AsCharacterBase->SlotCount || model.AsCharacterBase->ColorTableTexturesSpan.Length < (SlotIndex + 1) * MaterialService.MaterialsPerModel) { - textures = []; + textures = []; return false; } - textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(SlotIndex * MaterialService.MaterialsPerModel, - MaterialService.MaterialsPerModel); + var from = SlotIndex * MaterialService.MaterialsPerModel; + textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(from, MaterialService.MaterialsPerModel); return true; } + public unsafe bool TryGetTexture(Actor actor, out Texture** texture) { if (TryGetTextures(actor, out var textures)) @@ -99,6 +119,38 @@ public readonly record struct MaterialValueIndex( return false; } + public unsafe bool TryGetTexture(Actor actor, out Texture** texture, out ColorRow.Mode mode) + { + if (TryGetTextures(actor, out var textures, out var materials)) + return TryGetTexture(textures, materials, out texture, out mode); + + mode = ColorRow.Mode.Dawntrail; + texture = null; + return false; + } + + public unsafe bool TryGetTexture(ReadOnlySpan> textures, ReadOnlySpan> materials, + out Texture** texture, out ColorRow.Mode mode) + { + mode = MaterialIndex >= materials.Length + ? ColorRow.Mode.Dawntrail + : PrepareColorSet.GetMode((MaterialResourceHandle*)materials[MaterialIndex].Value); + + + if (MaterialIndex >= textures.Length || textures[MaterialIndex].Value == null) + { + texture = null; + return false; + } + + fixed (Pointer* ptr = textures) + { + texture = (Texture**)ptr + MaterialIndex; + } + + return true; + } + public unsafe bool TryGetTexture(ReadOnlySpan> textures, out Texture** texture) { if (MaterialIndex >= textures.Length || textures[MaterialIndex].Value == null) @@ -115,6 +167,7 @@ public readonly record struct MaterialValueIndex( return true; } + public static MaterialValueIndex FromKey(uint key) => new(key); @@ -190,4 +243,4 @@ public readonly record struct MaterialValueIndex( JsonSerializer serializer) => FromKey(serializer.Deserialize(reader), out var value) ? value : throw new Exception($"Invalid material key {value.Key}."); } -} \ No newline at end of file +} diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 79338a0..cb3a7e2 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -13,6 +13,12 @@ namespace Glamourer.Interop.Material; /// Values are not squared. public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, float specularStrength, float glossStrength) { + public enum Mode + { + Legacy, + Dawntrail, + } + public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, 0, 0); public Vector3 Diffuse = diffuse; @@ -22,8 +28,9 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa public float GlossStrength = glossStrength; public ColorRow(in ColorTableRow row) - : this(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor), (float)row.SheenRate, - (float)row.Metalness) + : this(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor), + (float)row.LegacySpecularStrength(), + (float)row.LegacyGloss()) { } public readonly bool NearEqual(in ColorRow rhs) @@ -45,7 +52,7 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa private static float Root(float value) => value < 0 ? MathF.Sqrt(-value) : MathF.Sqrt(value); - public readonly bool Apply(ref ColorTableRow row) + public readonly bool Apply(ref ColorTableRow row, Mode mode) { var ret = false; var d = Square(Diffuse); @@ -69,22 +76,40 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa ret = true; } - if (!((float)row.SheenRate).NearEqual(SpecularStrength)) + if (mode is Mode.Legacy) { - row.SheenRate = (Half)SpecularStrength; - ret = true; - } + if (!((float)row.LegacySpecularStrength()).NearEqual(SpecularStrength)) + { + row.LegacySpecularStrengthWrite() = (Half)SpecularStrength; + ret = true; + } - if (!((float)row.Metalness).NearEqual(GlossStrength)) - { - row.Metalness = (Half)GlossStrength; - ret = true; + if (!((float)row.LegacyGloss()).NearEqual(GlossStrength)) + { + row.LegacyGlossWrite() = (Half)GlossStrength; + ret = true; + } } return ret; } } +internal static class ColorTableRowExtensions +{ + internal static Half LegacySpecularStrength(this in ColorTableRow row) + => row[7]; + + internal static Half LegacyGloss(this in ColorTableRow row) + => row[3]; + + internal static ref Half LegacySpecularStrengthWrite(this ref ColorTableRow row) + => ref row[7]; + + internal static ref Half LegacyGlossWrite(this ref ColorTableRow row) + => ref row[3]; +} + [JsonConverter(typeof(Converter))] public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert) { diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 6d65a6b..b44246b 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -91,11 +91,12 @@ public sealed unsafe class PrepareColorSet } /// Assumes the actor is valid. - public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out ColorTable.Table table) + public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out ColorTable.Table table, out ColorRow.Mode mode) { var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex; if (!index.TryGetModel(actor, out var model)) { + mode = ColorRow.Mode.Dawntrail; table = default; return false; } @@ -103,10 +104,12 @@ public sealed unsafe class PrepareColorSet var handle = (MaterialResourceHandle*)model.AsCharacterBase->Materials[idx]; if (handle == null) { + mode = ColorRow.Mode.Dawntrail; table = default; return false; } + mode = GetMode(handle); return TryGetColorTable(handle, GetStains(), out table); StainIds GetStains() @@ -126,6 +129,14 @@ public sealed unsafe class PrepareColorSet } } + /// Get the shader mode of the material. + public static ColorRow.Mode GetMode(MaterialResourceHandle* handle) + => handle == null + ? ColorRow.Mode.Dawntrail + : handle->ShpkNameSpan.SequenceEqual("characterlegacy.shpk"u8) + ? ColorRow.Mode.Legacy + : ColorRow.Mode.Dawntrail; + /// Get the correct dye table for a material. private static bool GetDyeTable(MaterialResourceHandle* material, out ushort* ptr) { diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 66b83fb..d6d5bde 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -75,7 +75,7 @@ public class StateApplier( } } - /// + /// public ActorData ChangeCustomize(ActorState state, bool apply) { var data = GetData(state); @@ -177,7 +177,7 @@ public class StateApplier( } } - /// + /// public ActorData ChangeStain(ActorState state, EquipSlot slot, bool apply) { var data = GetData(state); @@ -197,7 +197,7 @@ public class StateApplier( ChangeOffhand(data, item, stains); } - /// + /// public ActorData ChangeWeapon(ActorState state, EquipSlot slot, bool apply, bool onlyGPose) { var data = GetData(state); @@ -229,7 +229,7 @@ public class StateApplier( } /// Change a meta state. - public unsafe void ChangeMetaState(ActorData data, MetaIndex index, bool value) + public void ChangeMetaState(ActorData data, MetaIndex index, bool value) { switch (index) { @@ -311,15 +311,15 @@ public class StateApplier( foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) { - if (!index.TryGetTexture(actor, out var texture)) + if (!index.TryGetTexture(actor, out var texture, out var mode)) continue; if (!_directX.TryGetColorTable(*texture, out var table)) continue; if (value.HasValue) - value.Value.Apply(ref table[index.RowIndex]); - else if (PrepareColorSet.TryGetColorTable(actor, index, out var baseTable)) + value.Value.Apply(ref table[index.RowIndex], mode); + else if (PrepareColorSet.TryGetColorTable(actor, index, out var baseTable, out _)) table[index.RowIndex] = baseTable[index.RowIndex]; else continue; @@ -353,11 +353,11 @@ public class StateApplier( if (!mainKey.TryGetTexture(actor, out var texture)) continue; - if (!PrepareColorSet.TryGetColorTable(actor, mainKey, out var table)) + if (!PrepareColorSet.TryGetColorTable(actor, mainKey, out var table, out var mode)) continue; foreach (var (key, value) in values) - value.Model.Apply(ref table[key.RowIndex]); + value.Model.Apply(ref table[key.RowIndex], mode); _directX.ReplaceColorTable(texture, table); } diff --git a/OtterGui b/OtterGui index 51bab6d..d9486ae 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 51bab6dd1bd7d98cc468e8122f410e1c79e3c92d +Subproject commit d9486ae54b5a4b61cf74f79ed27daa659eb1ce5b