Things are progressing at a satisfying rate.

This commit is contained in:
Ottermandias 2024-01-27 00:32:48 +01:00
parent 5e5ce4d234
commit cb45221be2
7 changed files with 312 additions and 103 deletions

View file

@ -0,0 +1,178 @@
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Interop.Material;
using Glamourer.Interop.Structs;
using ImGuiNET;
using OtterGui.Services;
using Penumbra.GameData.Files;
namespace Glamourer.Gui.Materials;
public unsafe class MaterialDrawer : IService
{
private static readonly IReadOnlyList<MaterialValueIndex.DrawObjectType> Types =
[
MaterialValueIndex.DrawObjectType.Human,
MaterialValueIndex.DrawObjectType.Mainhand,
MaterialValueIndex.DrawObjectType.Offhand,
];
public void DrawPanel(Actor actor)
{
if (!actor.IsCharacter)
return;
foreach (var type in Types)
{
var index = new MaterialValueIndex(type, 0, 0, 0, 0);
if (index.TryGetModel(actor, out var model))
DrawModelType(model, index);
}
}
private void DrawModelType(Model model, MaterialValueIndex sourceIndex)
{
using var tree = ImRaii.TreeNode(sourceIndex.DrawObject.ToString());
if (!tree)
return;
var names = model.AsCharacterBase->GetModelType() is CharacterBase.ModelType.Human
? SlotNamesHuman
: SlotNames;
for (byte i = 0; i < model.AsCharacterBase->SlotCount; ++i)
{
var index = sourceIndex with { SlotIndex = i };
DrawSlot(model, names, index);
}
}
private void DrawSlot(Model model, IReadOnlyList<string> names, MaterialValueIndex sourceIndex)
{
using var tree = ImRaii.TreeNode(names[sourceIndex.SlotIndex]);
if (!tree)
return;
for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i)
{
var index = sourceIndex with { MaterialIndex = i };
var texture = model.AsCharacterBase->ColorTableTextures + index.SlotIndex * MaterialService.MaterialsPerModel + i;
if (*texture == null)
continue;
if (!DirectXTextureHelper.TryGetColorTable(*texture, out var table))
continue;
DrawMaterial(ref table, texture, index);
}
}
private void DrawMaterial(ref MtrlFile.ColorTable table, Texture** texture, MaterialValueIndex sourceIndex)
{
using var tree = ImRaii.TreeNode($"Material {sourceIndex.MaterialIndex + 1}");
if (!tree)
return;
for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
{
var index = sourceIndex with { RowIndex = i };
ref var row = ref table[i];
DrawRow(ref table, ref row, texture, index);
}
}
private void DrawRow(ref MtrlFile.ColorTable table, ref MtrlFile.ColorTable.Row row, Texture** texture, MaterialValueIndex sourceIndex)
{
using var id = ImRaii.PushId(sourceIndex.RowIndex);
var diffuse = row.Diffuse;
var specular = row.Specular;
var emissive = row.Emissive;
var glossStrength = row.GlossStrength;
var specularStrength = row.SpecularStrength;
if (ImGui.ColorEdit3("Diffuse", ref diffuse, ImGuiColorEditFlags.NoInputs))
{
var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Diffuse };
row.Diffuse = diffuse;
MaterialService.ReplaceColorTable(texture, table);
}
ImGui.SameLine();
if (ImGui.ColorEdit3("Specular", ref specular, ImGuiColorEditFlags.NoInputs))
{
var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Specular };
row.Specular = specular;
MaterialService.ReplaceColorTable(texture, table);
}
ImGui.SameLine();
if (ImGui.ColorEdit3("Emissive", ref emissive, ImGuiColorEditFlags.NoInputs))
{
var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Emissive };
row.Emissive = emissive;
MaterialService.ReplaceColorTable(texture, table);
}
ImGui.SameLine();
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
if (ImGui.DragFloat("Gloss", ref glossStrength, 0.1f))
{
var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.GlossStrength };
row.GlossStrength = glossStrength;
MaterialService.ReplaceColorTable(texture, table);
}
ImGui.SameLine();
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
if (ImGui.DragFloat("Specular Strength", ref specularStrength, 0.1f))
{
var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.SpecularStrength };
row.SpecularStrength = specularStrength;
MaterialService.ReplaceColorTable(texture, table);
}
}
private static readonly IReadOnlyList<string> SlotNames =
[
"Slot 1",
"Slot 2",
"Slot 3",
"Slot 4",
"Slot 5",
"Slot 6",
"Slot 7",
"Slot 8",
"Slot 9",
"Slot 10",
"Slot 11",
"Slot 12",
"Slot 13",
"Slot 14",
"Slot 15",
"Slot 16",
"Slot 17",
"Slot 18",
"Slot 19",
"Slot 20",
];
private static readonly IReadOnlyList<string> SlotNamesHuman =
[
"Head",
"Body",
"Hands",
"Legs",
"Feet",
"Earrings",
"Neck",
"Wrists",
"Right Finger",
"Left Finger",
"Slot 11",
"Slot 12",
"Slot 13",
"Slot 14",
"Slot 15",
"Slot 16",
"Slot 17",
"Slot 18",
"Slot 19",
"Slot 20",
];
}

View file

@ -7,6 +7,7 @@ using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Gui.Materials;
using Glamourer.Interop;
using Glamourer.Interop.Material;
using Glamourer.Interop.Structs;
@ -34,7 +35,8 @@ public class ActorPanel(
ImportService _importService,
ICondition _conditions,
DictModelChara _modelChara,
CustomizeParameterDrawer _parameterDrawer)
CustomizeParameterDrawer _parameterDrawer,
MaterialDrawer _materialDrawer)
{
private ActorIdentifier _identifier;
private string _actorName = string.Empty;
@ -121,16 +123,36 @@ public class ActorPanel(
RevertButtons();
if (ImGui.CollapsingHeader("Material Shit"))
_materialDrawer.DrawPanel(_actor);
ImGui.InputInt("Row", ref _rowId);
ImGui.InputInt("Material", ref _materialId);
ImGui.InputInt("Slot", ref _slotId);
ImGuiUtil.GenericEnumCombo("Value", 300, _index, out _index);
var index = new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte) _slotId, (byte) _materialId, (byte)_rowId, _index);
index.TryGetValue(_actor, out _test);
index.TryGetValue(_actor, out var current);
_test = current;
if (ImGui.ColorPicker3("TestPicker", ref _test) && _actor.Valid)
MaterialService.Test(_actor, index, _test);
_state.Materials.AddOrUpdateValue(index, new MaterialValueState(current, _test, StateSource.Manual));
if (ImGui.ColorPicker3("TestPicker2", ref _test) && _actor.Valid)
_state.Materials.AddOrUpdateValue(index, new MaterialValueState(current, _test, StateSource.Fixed));
foreach (var value in _state.Materials.Values)
{
var id = MaterialValueIndex.FromKey(value.Key);
ImGui.TextUnformatted($"{id.DrawObject} {id.SlotIndex} {id.MaterialIndex} {id.RowIndex} {id.DataIndex} ");
ImGui.SameLine(0, 0);
var game = ImGui.ColorConvertFloat4ToU32(new Vector4(value.Value.Game, 1));
ImGuiUtil.DrawTextButton(" ", Vector2.Zero, game);
ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X);
var model = ImGui.ColorConvertFloat4ToU32(new Vector4(value.Value.Model, 1));
ImGuiUtil.DrawTextButton(" ", Vector2.Zero, model);
ImGui.SameLine(0, 0);
ImGui.TextUnformatted($" {value.Value.Source}");
}
using var disabled = ImRaii.Disabled(transformationId != 0);
if (_state.ModelData.IsHuman)

View file

@ -1,44 +1,12 @@
using Dalamud.Interface.Utility.Raii;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using Glamourer.Interop.Structs;
using ImGuiNET;
using Lumina.Data.Files;
using OtterGui;
using OtterGui.Services;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using static Penumbra.GameData.Files.MtrlFile;
using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture;
namespace Glamourer.Interop.Material;
public class MaterialServiceDrawer(ActorManager actors) : IService
{
private ActorIdentifier _openIdentifier;
private uint _openSlotIndex;
public unsafe void PopupButton(Actor actor, EquipSlot slot)
{
var slotIndex = slot.ToIndex();
var identifier = actor.GetIdentifier(actors);
var buttonActive = actor.Valid
&& identifier.IsValid
&& actor.Model.IsCharacterBase
&& slotIndex < actor.Model.AsCharacterBase->SlotCount
&& (actor.Model.AsCharacterBase->HasModelInSlotLoaded & (1 << (int)slotIndex)) != 0;
using var id = ImRaii.PushId((int)slot);
if (ImGuiUtil.DrawDisabledButton("Advanced", Vector2.Zero, "Open advanced window.", !buttonActive))
{
_openIdentifier = identifier;
_openSlotIndex = slotIndex;
ImGui.OpenPopup($"Popup{slot}");
}
}
}
public static unsafe class MaterialService
{
public const int TextureWidth = 4;
@ -49,7 +17,7 @@ public static unsafe class MaterialService
/// <param name="original"> The original texture that will be replaced with a new one. </param>
/// <param name="colorTable"> The input color table. </param>
/// <returns> Success or failure. </returns>
public static bool GenerateColorTable(Texture** original, in ColorTable colorTable)
public static bool ReplaceColorTable(Texture** original, in ColorTable colorTable)
{
if (original == null)
return false;
@ -63,7 +31,7 @@ public static unsafe class MaterialService
if (texture.IsInvalid)
return false;
fixed(ColorTable* ptr = &colorTable)
fixed (ColorTable* ptr = &colorTable)
{
if (!texture.Texture->InitializeContents(ptr))
return false;
@ -73,6 +41,22 @@ public static unsafe class MaterialService
return true;
}
public static bool GenerateNewColorTable(in ColorTable colorTable, out Texture* texture)
{
var textureSize = stackalloc int[2];
textureSize[0] = TextureWidth;
textureSize[1] = TextureHeight;
texture = Device.Instance()->CreateTexture2D(textureSize, 1, (uint)TexFile.TextureFormat.R16G16B16A16F,
(uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7);
if (texture == null)
return false;
fixed (ColorTable* ptr = &colorTable)
{
return texture->InitializeContents(ptr);
}
}
/// <summary> Obtain a pointer to the models pointer to a specific color table texture. </summary>
/// <param name="model"></param>
@ -112,19 +96,4 @@ public static unsafe class MaterialService
return (ColorTable*)material->ColorTable;
}
public static void Test(Actor actor, MaterialValueIndex index, Vector3 value)
{
if (!index.TryGetColorTable(actor, out var table))
return;
ref var row = ref table[index.RowIndex];
if (!index.DataIndex.SetValue(ref row, value))
return;
var texture = GetColorTableTexture(index.TryGetModel(actor, out var model) ? model : Model.Null, index.SlotIndex,
index.MaterialIndex);
if (texture != null)
GenerateColorTable(texture, table);
}
}

View file

@ -1,17 +1,27 @@
namespace Glamourer.Interop.Material;
global using StateMaterialManager = Glamourer.Interop.Material.MaterialValueManager<Glamourer.Interop.Material.MaterialValueState>;
global using DesignMaterialManager = Glamourer.Interop.Material.MaterialValueManager<System.Numerics.Vector3>;
using Glamourer.State;
public readonly struct MaterialValueManager
namespace Glamourer.Interop.Material;
public record struct MaterialValueState(Vector3 Game, Vector3 Model, StateSource Source);
public readonly struct MaterialValueManager<T>
{
private readonly List<(uint Key, Vector3 Value)> _values = [];
private readonly List<(uint Key, T Value)> _values = [];
public MaterialValueManager()
{ }
public bool TryGetValue(MaterialValueIndex index, out Vector3 value)
public void Clear()
=> _values.Clear();
public bool TryGetValue(MaterialValueIndex index, out T value)
{
if (_values.Count == 0)
{
value = Vector3.Zero;
value = default!;
return false;
}
@ -22,11 +32,11 @@ public readonly struct MaterialValueManager
return true;
}
value = Vector3.Zero;
value = default!;
return false;
}
public bool TryAddValue(MaterialValueIndex index, in Vector3 value)
public bool TryAddValue(MaterialValueIndex index, in T value)
{
var key = index.Key;
var idx = Search(key);
@ -50,7 +60,7 @@ public readonly struct MaterialValueManager
return true;
}
public void AddOrUpdateValue(MaterialValueIndex index, in Vector3 value)
public void AddOrUpdateValue(MaterialValueIndex index, in T value)
{
var key = index.Key;
var idx = Search(key);
@ -60,11 +70,11 @@ public readonly struct MaterialValueManager
_values[idx] = (key, value);
}
public bool UpdateValue(MaterialValueIndex index, in Vector3 value, out Vector3 oldValue)
public bool UpdateValue(MaterialValueIndex index, in T value, out T oldValue)
{
if (_values.Count == 0)
{
oldValue = Vector3.Zero;
oldValue = default!;
return false;
}
@ -72,7 +82,7 @@ public readonly struct MaterialValueManager
var idx = Search(key);
if (idx < 0)
{
oldValue = Vector3.Zero;
oldValue = default!;
return false;
}
@ -81,6 +91,9 @@ public readonly struct MaterialValueManager
return true;
}
public IReadOnlyList<(uint Key, T Value)> Values
=> _values;
public int RemoveValues(MaterialValueIndex min, MaterialValueIndex max)
{
var (minIdx, maxIdx) = GetMinMax(CollectionsMarshal.AsSpan(_values), min.Key, max.Key);
@ -92,21 +105,21 @@ public readonly struct MaterialValueManager
return count;
}
public ReadOnlySpan<(uint key, Vector3 Value)> GetValues(MaterialValueIndex min, MaterialValueIndex max)
public ReadOnlySpan<(uint key, T Value)> GetValues(MaterialValueIndex min, MaterialValueIndex max)
=> Filter(CollectionsMarshal.AsSpan(_values), min, max);
public static ReadOnlySpan<(uint Key, Vector3 Value)> Filter(ReadOnlySpan<(uint Key, Vector3 Value)> values, MaterialValueIndex min,
public static ReadOnlySpan<(uint Key, T Value)> Filter(ReadOnlySpan<(uint Key, T Value)> values, MaterialValueIndex min,
MaterialValueIndex max)
{
var (minIdx, maxIdx) = GetMinMax(values, min.Key, max.Key);
return minIdx < 0 ? [] : values[minIdx..(maxIdx - minIdx)];
return minIdx < 0 ? [] : values[minIdx..(maxIdx - minIdx + 1)];
}
/// <summary> Obtain the minimum index and maximum index for a minimum and maximum key. </summary>
private static (int MinIdx, int MaxIdx) GetMinMax(ReadOnlySpan<(uint Key, Vector3 Value)> values, uint minKey, uint maxKey)
private static (int MinIdx, int MaxIdx) GetMinMax(ReadOnlySpan<(uint Key, T Value)> values, uint minKey, uint maxKey)
{
// Find the minimum index by binary search.
var idx = values.BinarySearch((minKey, Vector3.Zero), Comparer.Instance);
var idx = values.BinarySearch((minKey, default!), Comparer.Instance);
var minIdx = idx;
// If the key does not exist, check if it is an invalid range or set it correctly.
@ -131,7 +144,7 @@ public readonly struct MaterialValueManager
// Do pretty much the same but in the other direction with the maximum key.
var maxIdx = values[idx..].BinarySearch((maxKey, Vector3.Zero), Comparer.Instance);
var maxIdx = values[idx..].BinarySearch((maxKey, default!), Comparer.Instance);
if (maxIdx < 0)
{
maxIdx = ~maxIdx;
@ -149,14 +162,14 @@ public readonly struct MaterialValueManager
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private int Search(uint key)
=> _values.BinarySearch((key, Vector3.Zero), Comparer.Instance);
=> _values.BinarySearch((key, default!), Comparer.Instance);
private class Comparer : IComparer<(uint Key, Vector3 Value)>
private class Comparer : IComparer<(uint Key, T Value)>
{
public static readonly Comparer Instance = new();
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public int Compare((uint Key, Vector3 Value) x, (uint Key, Vector3 Value) y)
int IComparer<(uint Key, T Value)>.Compare((uint Key, T Value) x, (uint Key, T Value) y)
=> x.Key.CompareTo(y.Key);
}
}

View file

@ -9,7 +9,6 @@ using Glamourer.State;
using OtterGui.Classes;
using OtterGui.Services;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.GameData.Structs;
@ -58,28 +57,18 @@ public sealed unsafe class PrepareColorSet
return _task.Result.Original(characterBase, material, stainId);
}
public static MtrlFile.ColorTable GetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId)
public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId, out MtrlFile.ColorTable table)
{
var table = new MtrlFile.ColorTable();
characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&table));
return table;
}
public static bool TryGetColorTable(Model model, byte slotIdx, out MtrlFile.ColorTable table)
{
var table2 = new MtrlFile.ColorTable();
if (!model.IsCharacterBase || slotIdx < model.AsCharacterBase->SlotCount)
return false;
var resource = (MaterialResourceHandle*)model.AsCharacterBase->Materials[slotIdx];
var stain = model.AsCharacterBase->GetModelType() switch
if (material->ColorTable == null)
{
CharacterBase.ModelType.Human => model.GetArmor(EquipSlotExtensions.ToEquipSlot(slotIdx)).Stain,
CharacterBase.ModelType.Weapon => (StainId)model.AsWeapon->ModelUnknown,
_ => (StainId)0,
};
model.AsCharacterBase->ReadStainingTemplate(resource, stain.Id, (Half*)(&table2));
table = table2;
table = default;
return false;
}
var newTable = *(MtrlFile.ColorTable*)material->ColorTable;
if(stainId.Id != 0)
characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable));
table = newTable;
return true;
}
}
@ -111,10 +100,6 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
var actor = _penumbra.GameObjectFromDrawObject(characterBase);
var validType = FindType(characterBase, actor, out var type);
var (slotId, materialId) = FindMaterial(characterBase, material);
Glamourer.Log.Information(
$" Triggered with 0x{(nint)characterBase:X} 0x{(nint)material:X} {stain.Id} --- Actor: 0x{actor.Address:X} Slot: {slotId} Material: {materialId} DrawObject: {type}.");
var table = PrepareColorSet.GetColorTable(characterBase, material, stain);
Glamourer.Log.Information($"{table[15].Diffuse}");
if (!validType
|| slotId == byte.MaxValue
@ -122,12 +107,49 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
|| !_stateManager.TryGetValue(identifier, out var state))
return;
var min = MaterialValueIndex.Min(type, slotId, materialId);
var max = MaterialValueIndex.Max(type, slotId, materialId);
var manager = new MaterialValueManager();
var values = manager.GetValues(min, max);
foreach (var (key, value) in values)
;
var values = state.Materials.GetValues(min, max);
if (values.Length == 0)
return;
if (!PrepareColorSet.TryGetColorTable(characterBase, material, stain, out var baseColorSet))
return;
for (var i = 0; i < values.Length; ++i)
{
var idx = MaterialValueIndex.FromKey(values[i].key);
var (oldGame, model, source) = values[i].Value;
ref var row = ref baseColorSet[idx.RowIndex];
if (!idx.DataIndex.TryGetValue(row, out var newGame))
continue;
if (newGame == oldGame)
{
idx.DataIndex.SetValue(ref row, model);
}
else
{
switch (source)
{
case StateSource.Manual:
case StateSource.Pending:
state.Materials.RemoveValue(idx);
--i;
break;
case StateSource.Ipc:
case StateSource.Fixed:
idx.DataIndex.SetValue(ref row, model);
state.Materials.UpdateValue(idx, new MaterialValueState(newGame, model, source), out _);
break;
}
}
}
if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture))
ret = (nint)texture;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]

View file

@ -30,6 +30,9 @@ public class ActorState
/// <summary> The territory the draw object was created last. </summary>
public ushort LastTerritory;
/// <summary> State for specific material values. </summary>
public readonly StateMaterialManager Materials = new();
/// <summary> Whether the State is locked at all. </summary>
public bool IsLocked
=> Combination != 0;

View file

@ -240,6 +240,8 @@ public sealed class StateManager(
foreach (var flag in CustomizeParameterExtensions.AllFlags)
state.Sources[flag] = StateSource.Game;
state.Materials.Clear();
var actors = ActorData.Invalid;
if (source is StateSource.Manual or StateSource.Ipc)
actors = Applier.ApplyAll(state, redraw, true);