From 5f74f4b4d9c3f459ade0747a43a8cdb8e1d83681 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 16 Feb 2024 15:11:59 +0100 Subject: [PATCH] Add preview stuff. --- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 25 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 224 ++++++++++++++++++ Glamourer/Gui/Materials/MaterialDrawer.cs | 3 - Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 34 +-- Glamourer/Gui/Tabs/ActorTab/ActorTab.cs | 15 +- .../Material/LiveColorTablePreviewer.cs | 115 +++++++++ .../Interop/Material/MaterialValueIndex.cs | 4 +- 7 files changed, 370 insertions(+), 50 deletions(-) create mode 100644 Glamourer/Gui/Materials/AdvancedDyePopup.cs create mode 100644 Glamourer/Interop/Material/LiveColorTablePreviewer.cs diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index c56e56e..25732e0 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -34,15 +34,16 @@ public class EquipmentDrawer public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures, Configuration config, GPoseService gPose) { - _items = items; - _codes = codes; - _textures = textures; - _config = config; - _gPose = gPose; - _stainData = items.Stains; - _stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites); - _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); - _weaponCombo = new Dictionary(FullEquipTypeExtensions.WeaponTypes.Count * 2); + _items = items; + _codes = codes; + _textures = textures; + _config = config; + _gPose = gPose; + _advancedDyes = advancedDyes; + _stainData = items.Stains; + _stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites); + _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); + _weaponCombo = new Dictionary(FullEquipTypeExtensions.WeaponTypes.Count * 2); foreach (var type in Enum.GetValues()) { if (type.ToSlot() is EquipSlot.MainHand) @@ -465,8 +466,10 @@ public class EquipmentDrawer (var tt, item, var valid) = (allowRevert && !revertItem.Equals(currentItem), allowClear && !clearItem.Equals(currentItem), ImGui.GetIO().KeyCtrl) switch { - (true, true, true) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", revertItem, true), - (true, true, false) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", clearItem, true), + (true, true, true) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", + revertItem, true), + (true, true, false) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", + clearItem, true), (true, false, true) => ("Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", revertItem, true), (true, false, false) => ("Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", default, false), (false, true, _) => ("Right-click to clear.\nControl and mouse wheel to scroll.", clearItem, true), diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs new file mode 100644 index 0000000..4f4c76f --- /dev/null +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -0,0 +1,224 @@ +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Glamourer.Designs; +using Glamourer.Gui.Tabs.ActorTab; +using Glamourer.Interop.Material; +using Glamourer.Interop.Structs; +using Glamourer.State; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Services; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Files; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Materials; + +public sealed unsafe class AdvancedDyePopup( + MainWindowPosition mainPosition, + Configuration config, + StateManager stateManager, + ActorSelector actorSelector, + MaterialDrawer materials, + LiveColorTablePreviewer preview) : IService +{ + private MaterialValueIndex? _drawIndex; + private ActorIdentifier _identifier; + private ActorState? _state; + private Actor _actor; + private byte _selectedMaterial = byte.MaxValue; + + private bool ShouldBeDrawn() + { + if (!mainPosition.IsOpen) + return false; + + if (!config.UseAdvancedDyes) + return false; + + if (config.Ephemeral.SelectedTab is not MainWindow.TabType.Actors) + return false; + + if (!_drawIndex.HasValue) + return false; + + if (actorSelector.Selection.Identifier != _identifier || !_identifier.IsValid) + return false; + + if (_state == null) + return false; + + _actor = actorSelector.Selection.Data.Valid ? actorSelector.Selection.Data.Objects[0] : Actor.Null; + if (!_actor.Valid || !_actor.Model.IsCharacterBase) + return false; + + return true; + } + + public void DrawButton(ActorIdentifier identifier, ActorState state, MaterialValueIndex index) + { + using var id = ImRaii.PushId(index.SlotIndex | ((int)index.DrawObject << 8)); + var isOpen = identifier == _identifier && state == _state && index == _drawIndex; + using (ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), isOpen)) + { + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Palette.ToIconString(), new Vector2(ImGui.GetFrameHeight()), + "Open advanced dyes for this slot.", false, true)) + { + if (isOpen) + { + _drawIndex = null; + _identifier = ActorIdentifier.Invalid; + _state = null; + _selectedMaterial = byte.MaxValue; + } + else + { + _drawIndex = index; + _identifier = identifier; + _state = state; + _selectedMaterial = byte.MaxValue; + } + } + } + } + + public unsafe void Draw() + { + if (!ShouldBeDrawn()) + return; + + var position = mainPosition.Position; + position.X += mainPosition.Size.X; + position.Y += ImGui.GetFrameHeightWithSpacing() * 3; + var size = new Vector2(3 * ImGui.GetFrameHeight() + 300 * ImGuiHelpers.GlobalScale, 18.5f * ImGui.GetFrameHeightWithSpacing()); + ImGui.SetNextWindowPos(position); + ImGui.SetNextWindowSize(size); + var window = ImGui.Begin("###Glamourer Advanced Dyes", + ImGuiWindowFlags.NoFocusOnAppearing + | ImGuiWindowFlags.NoCollapse + | ImGuiWindowFlags.NoDecoration + | ImGuiWindowFlags.NoMove + | ImGuiWindowFlags.NoResize); + try + { + if (!window) + return; + + using var bar = ImRaii.TabBar("tabs"); + if (!bar) + return; + + var model = _actor.Model.AsCharacterBase; + var firstAvailable = true; + Span label = stackalloc byte[12]; + label[0] = (byte)'M'; + label[1] = (byte)'a'; + label[2] = (byte)'t'; + label[3] = (byte)'e'; + label[4] = (byte)'r'; + label[5] = (byte)'i'; + label[6] = (byte)'a'; + label[7] = (byte)'l'; + label[8] = (byte)' '; + label[9] = (byte)'#'; + label[11] = 0; + + for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) + { + var texture = model->ColorTableTextures + _drawIndex!.Value.SlotIndex * MaterialService.MaterialsPerModel + i; + var index = _drawIndex!.Value with { MaterialIndex = i }; + var available = *texture != null && DirectXTextureHelper.TryGetColorTable(*texture, out var table); + if (index == preview.LastValueIndex with {RowIndex = 0}) + table = preview.LastOriginalColorTable; + + using var disable = ImRaii.Disabled(!available); + label[10] = (byte)('1' + i); + var select = available && (_selectedMaterial == i || firstAvailable && _selectedMaterial == byte.MaxValue) + ? ImGuiTabItemFlags.SetSelected + : ImGuiTabItemFlags.None; + + if (available) + firstAvailable = false; + if (select is ImGuiTabItemFlags.SetSelected) + _selectedMaterial = i; + + + fixed (byte* labelPtr = label) + { + using var tab = ImRaii.TabItem(labelPtr, select); + if (tab.Success && available) + DrawTable(index, table); + } + } + } + finally + { + ImGui.End(); + } + } + + private void DrawTable(MaterialValueIndex materialIndex, in MtrlFile.ColorTable table) + { + for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i) + { + var index = materialIndex with { RowIndex = i }; + ref var row = ref table[i]; + DrawRow(ref row, CharacterWeapon.Empty, index, table); + } + } + + private void DrawRow(ref MtrlFile.ColorTable.Row row, CharacterWeapon drawData, MaterialValueIndex index, in MtrlFile.ColorTable table) + { + using var id = ImRaii.PushId(index.RowIndex); + var changed = _state!.Materials.TryGetValue(index, out var value); + if (!changed) + { + var internalRow = new ColorRow(row); + value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual); + } + + ImGui.AlignTextToFramePadding(); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImGui.TextUnformatted($"Row {index.RowIndex + 1:D2}"); + } + + ImGui.SameLine(); + ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Locate", false, true); + if (ImGui.IsItemHovered()) + preview.OnHover(index, _actor.Index, table); + + ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); + var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse, + v => value.Model.Diffuse = v, "D"); + + var spacing = ImGui.GetStyle().ItemInnerSpacing; + ImGui.SameLine(0, spacing.X); + applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular, + v => value.Model.Specular = v, "S"); + ImGui.SameLine(0, spacing.X); + applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive, + 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."); + 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 (applied) + stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual); + if (changed) + { + ImGui.SameLine(0, spacing.X); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value()); + ImGui.TextUnformatted(FontAwesomeIcon.UserEdit.ToIconString()); + } + } + } +} diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 2840dc5..c3efa1f 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -32,15 +32,12 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de if (!table) return; - ImGui.TableSetupColumn("button", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); ImGui.TableSetupColumn("enabled", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); ImGui.TableSetupColumn("values", ImGuiTableColumnFlags.WidthFixed, ImGui.GetStyle().ItemInnerSpacing.X * 4 + 3 * buttonSize.X + 220 * ImGuiHelpers.GlobalScale); ImGui.TableSetupColumn("revert", ImGuiTableColumnFlags.WidthFixed, buttonSize.X + ImGui.CalcTextSize("Revertm").X); ImGui.TableSetupColumn("slot", ImGuiTableColumnFlags.WidthStretch); - - for (var i = 0; i < design.Materials.Count; ++i) { var (idx, value) = design.Materials[i]; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index 8bace46..3c23fbc 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -1,7 +1,6 @@ using Dalamud.Interface; using Glamourer.Interop; using Glamourer.Interop.Structs; -using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -10,28 +9,17 @@ using Penumbra.GameData.Actors; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorSelector +public class ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config) { - private readonly EphemeralConfig _config; - private readonly ObjectManager _objects; - private readonly ActorManager _actors; - private ActorIdentifier _identifier = ActorIdentifier.Invalid; - public ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config) - { - _objects = objects; - _actors = actors; - _config = config; - } - public bool IncognitoMode { - get => _config.IncognitoMode; + get => config.IncognitoMode; set { - _config.IncognitoMode = value; - _config.Save(); + config.IncognitoMode = value; + config.Save(); } } @@ -40,7 +28,7 @@ public class ActorSelector private float _width; public (ActorIdentifier Identifier, ActorData Data) Selection - => _objects.TryGetValue(_identifier, out var data) ? (_identifier, data) : (_identifier, ActorData.Invalid); + => objects.TryGetValue(_identifier, out var data) ? (_identifier, data) : (_identifier, ActorData.Invalid); public bool HasSelection => _identifier.IsValid; @@ -65,10 +53,10 @@ public class ActorSelector if (!child) return; - _objects.Update(); + objects.Update(); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); - var remainder = ImGuiClip.FilteredClippedDraw(_objects, skips, CheckFilter, DrawSelectable); + var remainder = ImGuiClip.FilteredClippedDraw(objects, skips, CheckFilter, DrawSelectable); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); } @@ -89,14 +77,14 @@ public class ActorSelector var buttonWidth = new Vector2(_width / 2, 0); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth - , "Select the local player character.", !_objects.Player, true)) - _identifier = _objects.Player.GetIdentifier(_actors); + , "Select the local player character.", !objects.Player, true)) + _identifier = objects.Player.GetIdentifier(actors); ImGui.SameLine(); - var (id, data) = _objects.TargetData; + var (id, data) = objects.TargetData; var tt = data.Valid ? $"Select the current target {id} in the list." : id.IsValid ? $"The target {id} is not in the list." : "No target selected."; - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth, tt, _objects.IsInGPose || !data.Valid, true)) + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth, tt, objects.IsInGPose || !data.Valid, true)) _identifier = id; } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs index 0d4584d..4e5e15c 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs @@ -4,24 +4,15 @@ using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorTab : ITab +public class ActorTab(ActorSelector selector, ActorPanel panel) : ITab { - private readonly ActorSelector _selector; - private readonly ActorPanel _panel; - public ReadOnlySpan Label => "Actors"u8; public void DrawContent() { - _selector.Draw(200 * ImGuiHelpers.GlobalScale); + selector.Draw(200 * ImGuiHelpers.GlobalScale); ImGui.SameLine(); - _panel.Draw(); - } - - public ActorTab(ActorSelector selector, ActorPanel panel) - { - _selector = selector; - _panel = panel; + panel.Draw(); } } diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs new file mode 100644 index 0000000..752101a --- /dev/null +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -0,0 +1,115 @@ +using Dalamud.Plugin.Services; +using Glamourer.Interop.Structs; +using ImGuiNET; +using OtterGui.Services; +using Penumbra.GameData.Files; +using Penumbra.GameData.Structs; + +namespace Glamourer.Interop.Material; + +public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable +{ + private readonly IObjectTable _objects; + private readonly IFramework _framework; + + public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid; + public MtrlFile.ColorTable LastOriginalColorTable { get; private set; } + private MaterialValueIndex _valueIndex = MaterialValueIndex.Invalid; + private ObjectIndex _lastObjectIndex = ObjectIndex.AnyIndex; + private ObjectIndex _objectIndex = ObjectIndex.AnyIndex; + private MtrlFile.ColorTable _originalColorTable; + + + public LiveColorTablePreviewer(IObjectTable objects, IFramework framework) + { + _objects = objects; + _framework = framework; + _framework.Update += OnFramework; + } + + private void Reset() + { + if (!LastValueIndex.Valid || _lastObjectIndex == ObjectIndex.AnyIndex) + return; + + var actor = (Actor)_objects.GetObjectAddress(_lastObjectIndex.Index); + if (actor.IsCharacter && LastValueIndex.TryGetTexture(actor, out var texture)) + MaterialService.ReplaceColorTable(texture, LastOriginalColorTable); + + Glamourer.Log.Information($"Reset {_lastObjectIndex} {LastValueIndex}"); + LastValueIndex = MaterialValueIndex.Invalid; + _lastObjectIndex = ObjectIndex.AnyIndex; + } + + private void OnFramework(IFramework _) + { + if (!_valueIndex.Valid || _objectIndex == ObjectIndex.AnyIndex) + { + Reset(); + _valueIndex = MaterialValueIndex.Invalid; + _objectIndex = ObjectIndex.AnyIndex; + return; + } + + var actor = (Actor)_objects.GetObjectAddress(_objectIndex.Index); + if (!actor.IsCharacter) + { + _valueIndex = MaterialValueIndex.Invalid; + _objectIndex = ObjectIndex.AnyIndex; + return; + } + + if (_valueIndex != LastValueIndex || _lastObjectIndex != _objectIndex) + { + Reset(); + LastValueIndex = _valueIndex; + _lastObjectIndex = _objectIndex; + LastOriginalColorTable = _originalColorTable; + } + + if (_valueIndex.TryGetTexture(actor, out var texture)) + { + Glamourer.Log.Information($"Set {_objectIndex} {_valueIndex}"); + var diffuse = CalculateDiffuse(); + var table = LastOriginalColorTable; + table[_valueIndex.RowIndex].Diffuse = diffuse; + table[_valueIndex.RowIndex].Emissive = diffuse / 8; + MaterialService.ReplaceColorTable(texture, table); + } + + _valueIndex = MaterialValueIndex.Invalid; + _objectIndex = ObjectIndex.AnyIndex; + } + + public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, MtrlFile.ColorTable table) + { + if (_valueIndex.Valid) + return; + + _valueIndex = index; + _objectIndex = objectIndex; + if (!LastValueIndex.Valid + || _lastObjectIndex == ObjectIndex.AnyIndex + || LastValueIndex.MaterialIndex != _valueIndex.MaterialIndex + || LastValueIndex.DrawObject != _valueIndex.DrawObject + || LastValueIndex.SlotIndex != _valueIndex.SlotIndex) + _originalColorTable = table; + } + + private static Vector3 CalculateDiffuse() + { + const int frameLength = 1; + const int steps = 64; + var frame = ImGui.GetFrameCount(); + var hueByte = frame % (steps * frameLength) / frameLength; + var hue = (float)hueByte / steps; + ImGui.ColorConvertHSVtoRGB(hue, 1, 1, out var r, out var g, out var b); + return new Vector3(r, g, b); + } + + public void Dispose() + { + Reset(); + _framework.Update -= OnFramework; + } +} diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 0facba4..13c7577 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -14,6 +14,7 @@ public readonly record struct MaterialValueIndex( byte MaterialIndex, byte RowIndex) { + public static readonly MaterialValueIndex Invalid = new(DrawObjectType.Invalid, 0, 0, 0); public uint Key => ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex); @@ -121,13 +122,14 @@ public readonly record struct MaterialValueIndex( public enum DrawObjectType : byte { + Invalid, Human, Mainhand, Offhand, }; public static bool Validate(DrawObjectType type) - => Enum.IsDefined(type); + => type is not DrawObjectType.Invalid && Enum.IsDefined(type); public static bool ValidateSlot(byte slotIndex) => slotIndex < 10;