Update advanced dyes.

This commit is contained in:
Ottermandias 2024-08-07 17:55:23 +02:00
parent 61cb46a298
commit a1b455d9a5
7 changed files with 157 additions and 48 deletions

View file

@ -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<Pointer<Texture>> textures, ref bool firstAvailable)
private void DrawTabBar(ReadOnlySpan<Pointer<Texture>> textures, ReadOnlySpan<Pointer<Material>> 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<Pointer<Texture>> textures)
private void DrawContent(ReadOnlySpan<Pointer<Texture>> textures, ReadOnlySpan<Pointer<Material>> 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<Pointer<Texture>> textures)
private void DrawWindow(ReadOnlySpan<Pointer<Texture>> textures, ReadOnlySpan<Pointer<Material>> 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,

View file

@ -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
/// <summary> Update and apply the glamourer state of an actor according to the application sources when updated by the game. </summary>
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;

View file

@ -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<Pointer<Texture>> textures, out ReadOnlySpan<Pointer<CsMaterial>> 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<Pointer<Texture>> 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<Pointer<Texture>> textures, ReadOnlySpan<Pointer<CsMaterial>> 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<Texture>* ptr = textures)
{
texture = (Texture**)ptr + MaterialIndex;
}
return true;
}
public unsafe bool TryGetTexture(ReadOnlySpan<Pointer<Texture>> 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<uint>(reader), out var value) ? value : throw new Exception($"Invalid material key {value.Key}.");
}
}
}

View file

@ -13,6 +13,12 @@ namespace Glamourer.Interop.Material;
/// <summary> Values are not squared. </summary>
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)
{

View file

@ -91,11 +91,12 @@ public sealed unsafe class PrepareColorSet
}
/// <summary> Assumes the actor is valid. </summary>
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
}
}
/// <summary> Get the shader mode of the material. </summary>
public static ColorRow.Mode GetMode(MaterialResourceHandle* handle)
=> handle == null
? ColorRow.Mode.Dawntrail
: handle->ShpkNameSpan.SequenceEqual("characterlegacy.shpk"u8)
? ColorRow.Mode.Legacy
: ColorRow.Mode.Dawntrail;
/// <summary> Get the correct dye table for a material. </summary>
private static bool GetDyeTable(MaterialResourceHandle* material, out ushort* ptr)
{

View file

@ -75,7 +75,7 @@ public class StateApplier(
}
}
/// <inheritdoc cref="ChangeCustomize(ActorData, in CustomizeArray, ActorState?)"/>
/// <inheritdoc cref="ChangeCustomize(ActorData,in CustomizeArray,ActorState?)"/>
public ActorData ChangeCustomize(ActorState state, bool apply)
{
var data = GetData(state);
@ -177,7 +177,7 @@ public class StateApplier(
}
}
/// <inheritdoc cref="ChangeStain(ActorData,EquipSlot,StainId)"/>
/// <inheritdoc cref="ChangeStain(ActorData,EquipSlot,StainIds)"/>
public ActorData ChangeStain(ActorState state, EquipSlot slot, bool apply)
{
var data = GetData(state);
@ -197,7 +197,7 @@ public class StateApplier(
ChangeOffhand(data, item, stains);
}
/// <inheritdoc cref="ChangeWeapon(ActorData,EquipSlot,EquipItem,StainId)"/>
/// <inheritdoc cref="ChangeWeapon(ActorData,EquipSlot,EquipItem,StainIds)"/>
public ActorData ChangeWeapon(ActorState state, EquipSlot slot, bool apply, bool onlyGPose)
{
var data = GetData(state);
@ -229,7 +229,7 @@ public class StateApplier(
}
/// <summary> Change a meta state. </summary>
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);
}

@ -1 +1 @@
Subproject commit 51bab6dd1bd7d98cc468e8122f410e1c79e3c92d
Subproject commit d9486ae54b5a4b61cf74f79ed27daa659eb1ce5b