From 5ea779a34cd3f60f00490b3702ab85484975c70d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 9 Jan 2024 16:05:18 +0100 Subject: [PATCH] Add decal color, fix some bugs and improve logic and handling somewhat. --- Glamourer/Designs/DesignBase.cs | 36 ++- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/GameData/CustomizeParameterData.cs | 223 ++++++++++-------- Glamourer/GameData/CustomizeParameterFlag.cs | 18 +- Glamourer/GameData/CustomizeParameterValue.cs | 47 ++++ Glamourer/GameData/DecalParameters.cs | 8 + .../CustomizeParameterDrawData.cs | 12 +- .../Customization/CustomizeParameterDrawer.cs | 58 ++++- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 10 +- Glamourer/Interop/Structs/Model.cs | 61 +++++ Glamourer/State/StateApplier.cs | 19 +- Glamourer/State/StateEditor.cs | 2 +- Glamourer/State/StateListener.cs | 19 +- Glamourer/State/StateManager.cs | 10 +- 14 files changed, 351 insertions(+), 174 deletions(-) create mode 100644 Glamourer/GameData/CustomizeParameterValue.cs create mode 100644 Glamourer/GameData/DecalParameters.cs diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index e4ea085..cc037fb 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -371,7 +371,7 @@ public class DesignBase }; } - foreach (var flag in CustomizeParameterExtensions.TripleFlags) + foreach (var flag in CustomizeParameterExtensions.RgbFlags) { ret[flag.ToString()] = new JObject() { @@ -382,6 +382,18 @@ public class DesignBase }; } + foreach (var flag in CustomizeParameterExtensions.RgbaFlags) + { + ret[flag.ToString()] = new JObject() + { + ["Red"] = DesignData.Parameters[flag][0], + ["Green"] = DesignData.Parameters[flag][1], + ["Blue"] = DesignData.Parameters[flag][2], + ["Alpha"] = DesignData.Parameters[flag][3], + ["Apply"] = DoApplyParameter(flag), + }; + } + return ret; } @@ -424,7 +436,7 @@ public class DesignBase continue; var value = token["Value"]?.ToObject() ?? 0f; - design.GetDesignDataRef().Parameters[flag] = new Vector3(value); + design.GetDesignDataRef().Parameters[flag] = new CustomizeParameterValue(value); } foreach (var flag in CustomizeParameterExtensions.PercentageFlags) @@ -433,10 +445,10 @@ public class DesignBase continue; var value = Math.Clamp(token["Percentage"]?.ToObject() ?? 0f, 0f, 1f); - design.GetDesignDataRef().Parameters[flag] = new Vector3(value); + design.GetDesignDataRef().Parameters[flag] = new CustomizeParameterValue(value); } - foreach (var flag in CustomizeParameterExtensions.TripleFlags) + foreach (var flag in CustomizeParameterExtensions.RgbFlags) { if (!TryGetToken(flag, out var token)) continue; @@ -444,7 +456,19 @@ public class DesignBase var r = Math.Clamp(token["Red"]?.ToObject() ?? 0f, 0, 1); var g = Math.Clamp(token["Green"]?.ToObject() ?? 0f, 0, 1); var b = Math.Clamp(token["Blue"]?.ToObject() ?? 0f, 0, 1); - design.GetDesignDataRef().Parameters[flag] = new Vector3(r, g, b); + design.GetDesignDataRef().Parameters[flag] = new CustomizeParameterValue(r, g, b); + } + + foreach (var flag in CustomizeParameterExtensions.RgbaFlags) + { + if (!TryGetToken(flag, out var token)) + continue; + + var r = Math.Clamp(token["Red"]?.ToObject() ?? 0f, 0, 1); + var g = Math.Clamp(token["Green"]?.ToObject() ?? 0f, 0, 1); + var b = Math.Clamp(token["Blue"]?.ToObject() ?? 0f, 0, 1); + var a = Math.Clamp(token["Alpha"]?.ToObject() ?? 0f, 0, 1); + design.GetDesignDataRef().Parameters[flag] = new CustomizeParameterValue(r, g, b, a); } return; @@ -461,7 +485,7 @@ public class DesignBase } design.ApplyParameters &= ~flag; - design.GetDesignDataRef().Parameters[flag] = Vector3.Zero; + design.GetDesignDataRef().Parameters[flag] = CustomizeParameterValue.Zero; return false; } } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index d41f0fe..f16281a 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -418,7 +418,7 @@ public class DesignManager } /// Change a customize parameter. - public void ChangeCustomizeParameter(Design design, CustomizeParameterFlag flag, Vector3 value) + public void ChangeCustomizeParameter(Design design, CustomizeParameterFlag flag, CustomizeParameterValue value) { var old = design.DesignData.Parameters[flag]; if (!design.GetDesignDataRef().Parameters.Set(flag, value)) diff --git a/Glamourer/GameData/CustomizeParameterData.cs b/Glamourer/GameData/CustomizeParameterData.cs index 2d2b650..0ffbad6 100644 --- a/Glamourer/GameData/CustomizeParameterData.cs +++ b/Glamourer/GameData/CustomizeParameterData.cs @@ -13,55 +13,58 @@ public struct CustomizeParameterData public Vector3 LeftEye; public Vector3 RightEye; public Vector3 FeatureColor; + public Vector4 DecalColor; public float FacePaintUvMultiplier; public float FacePaintUvOffset; public float MuscleTone; public float LipOpacity; - public Vector3 this[CustomizeParameterFlag flag] + public CustomizeParameterValue this[CustomizeParameterFlag flag] { [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] readonly get { return flag switch { - CustomizeParameterFlag.SkinDiffuse => SkinDiffuse, - CustomizeParameterFlag.MuscleTone => new Vector3(MuscleTone, 0, 0), - CustomizeParameterFlag.SkinSpecular => SkinSpecular, - CustomizeParameterFlag.LipDiffuse => LipDiffuse, - CustomizeParameterFlag.LipOpacity => new Vector3(LipOpacity, 0, 0), - CustomizeParameterFlag.HairDiffuse => HairDiffuse, - CustomizeParameterFlag.HairSpecular => HairSpecular, - CustomizeParameterFlag.HairHighlight => HairHighlight, - CustomizeParameterFlag.LeftEye => LeftEye, - CustomizeParameterFlag.RightEye => RightEye, - CustomizeParameterFlag.FeatureColor => FeatureColor, - CustomizeParameterFlag.FacePaintUvMultiplier => new Vector3(FacePaintUvMultiplier, 0, 0), - CustomizeParameterFlag.FacePaintUvOffset => new Vector3(FacePaintUvOffset, 0, 0), - _ => Vector3.Zero, + CustomizeParameterFlag.SkinDiffuse => new CustomizeParameterValue(SkinDiffuse), + CustomizeParameterFlag.MuscleTone => new CustomizeParameterValue(MuscleTone), + CustomizeParameterFlag.SkinSpecular => new CustomizeParameterValue(SkinSpecular), + CustomizeParameterFlag.LipDiffuse => new CustomizeParameterValue(LipDiffuse), + CustomizeParameterFlag.LipOpacity => new CustomizeParameterValue(LipOpacity), + CustomizeParameterFlag.HairDiffuse => new CustomizeParameterValue(HairDiffuse), + CustomizeParameterFlag.HairSpecular => new CustomizeParameterValue(HairSpecular), + CustomizeParameterFlag.HairHighlight => new CustomizeParameterValue(HairHighlight), + CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye), + CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye), + CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(FeatureColor), + CustomizeParameterFlag.DecalColor => new CustomizeParameterValue(DecalColor), + CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(FacePaintUvMultiplier), + CustomizeParameterFlag.FacePaintUvOffset => new CustomizeParameterValue(FacePaintUvOffset), + _ => CustomizeParameterValue.Zero, }; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] set => Set(flag, value); } - public bool Set(CustomizeParameterFlag flag, Vector3 value) + public bool Set(CustomizeParameterFlag flag, CustomizeParameterValue value) { return flag switch { - CustomizeParameterFlag.SkinDiffuse => SetIfDifferent(ref SkinDiffuse, value), - CustomizeParameterFlag.MuscleTone => SetIfDifferent(ref MuscleTone, Math.Clamp(value[0], -100, 100)), - CustomizeParameterFlag.SkinSpecular => SetIfDifferent(ref SkinSpecular, value), - CustomizeParameterFlag.LipDiffuse => SetIfDifferent(ref LipDiffuse, value), - CustomizeParameterFlag.LipOpacity => SetIfDifferent(ref LipOpacity, Math.Clamp(value[0], -100, 100)), - CustomizeParameterFlag.HairDiffuse => SetIfDifferent(ref HairDiffuse, value), - CustomizeParameterFlag.HairSpecular => SetIfDifferent(ref HairSpecular, value), - CustomizeParameterFlag.HairHighlight => SetIfDifferent(ref HairHighlight, value), - CustomizeParameterFlag.LeftEye => SetIfDifferent(ref LeftEye, value), - CustomizeParameterFlag.RightEye => SetIfDifferent(ref RightEye, value), - CustomizeParameterFlag.FeatureColor => SetIfDifferent(ref FeatureColor, value), - CustomizeParameterFlag.FacePaintUvMultiplier => SetIfDifferent(ref FacePaintUvMultiplier, value[0]), - CustomizeParameterFlag.FacePaintUvOffset => SetIfDifferent(ref FacePaintUvOffset, value[0]), + CustomizeParameterFlag.SkinDiffuse => SetIfDifferent(ref SkinDiffuse, value.InternalTriple), + CustomizeParameterFlag.MuscleTone => SetIfDifferent(ref MuscleTone, value.Single), + CustomizeParameterFlag.SkinSpecular => SetIfDifferent(ref SkinSpecular, value.InternalTriple), + CustomizeParameterFlag.LipDiffuse => SetIfDifferent(ref LipDiffuse, value.InternalTriple), + CustomizeParameterFlag.LipOpacity => SetIfDifferent(ref LipOpacity, value.Single), + CustomizeParameterFlag.HairDiffuse => SetIfDifferent(ref HairDiffuse, value.InternalTriple), + CustomizeParameterFlag.HairSpecular => SetIfDifferent(ref HairSpecular, value.InternalTriple), + CustomizeParameterFlag.HairHighlight => SetIfDifferent(ref HairHighlight, value.InternalTriple), + CustomizeParameterFlag.LeftEye => SetIfDifferent(ref LeftEye, value.InternalTriple), + CustomizeParameterFlag.RightEye => SetIfDifferent(ref RightEye, value.InternalTriple), + CustomizeParameterFlag.FeatureColor => SetIfDifferent(ref FeatureColor, value.InternalTriple), + CustomizeParameterFlag.DecalColor => SetIfDifferent(ref DecalColor, value.InternalQuadruple), + CustomizeParameterFlag.FacePaintUvMultiplier => SetIfDifferent(ref FacePaintUvMultiplier, value.Single), + CustomizeParameterFlag.FacePaintUvOffset => SetIfDifferent(ref FacePaintUvOffset, value.Single), _ => false, }; } @@ -69,32 +72,55 @@ public struct CustomizeParameterData [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public readonly void Apply(ref CustomizeParameter parameters, CustomizeParameterFlag flags = CustomizeParameterExtensions.All) { - if (flags.HasFlag(CustomizeParameterFlag.SkinDiffuse)) - parameters.SkinColor = Convert(SkinDiffuse, parameters.SkinColor.W); - if (flags.HasFlag(CustomizeParameterFlag.MuscleTone)) - parameters.SkinColor.W = MuscleTone; + parameters.SkinColor = (flags & (CustomizeParameterFlag.SkinDiffuse | CustomizeParameterFlag.MuscleTone)) switch + { + 0 => parameters.SkinColor, + CustomizeParameterFlag.SkinDiffuse => new CustomizeParameterValue(SkinDiffuse, parameters.SkinColor.W).XivQuadruple, + CustomizeParameterFlag.MuscleTone => parameters.SkinColor with { W = MuscleTone }, + _ => new CustomizeParameterValue(SkinDiffuse, MuscleTone).XivQuadruple, + }; + + parameters.LipColor = (flags & (CustomizeParameterFlag.LipDiffuse | CustomizeParameterFlag.LipOpacity)) switch + { + 0 => parameters.LipColor, + CustomizeParameterFlag.LipDiffuse => new CustomizeParameterValue(LipDiffuse, parameters.LipColor.W).XivQuadruple, + CustomizeParameterFlag.LipOpacity => parameters.LipColor with { W = LipOpacity }, + _ => new CustomizeParameterValue(LipDiffuse, LipOpacity).XivQuadruple, + }; + + parameters.LeftColor = (flags & (CustomizeParameterFlag.LeftEye | CustomizeParameterFlag.FacePaintUvMultiplier)) switch + { + 0 => parameters.LeftColor, + CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple, + CustomizeParameterFlag.FacePaintUvMultiplier => parameters.LeftColor with { W = FacePaintUvMultiplier }, + _ => new CustomizeParameterValue(LeftEye, FacePaintUvMultiplier).XivQuadruple, + }; + + parameters.RightColor = (flags & (CustomizeParameterFlag.RightEye | CustomizeParameterFlag.FacePaintUvOffset)) switch + { + 0 => parameters.RightColor, + CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye, parameters.RightColor.W).XivQuadruple, + CustomizeParameterFlag.FacePaintUvOffset => parameters.RightColor with { W = FacePaintUvOffset }, + _ => new CustomizeParameterValue(RightEye, FacePaintUvOffset).XivQuadruple, + }; + if (flags.HasFlag(CustomizeParameterFlag.SkinSpecular)) - parameters.SkinFresnelValue0 = Convert(SkinSpecular, 0); - if (flags.HasFlag(CustomizeParameterFlag.LipDiffuse)) - parameters.LipColor = Convert(LipDiffuse, parameters.LipColor.W); - if (flags.HasFlag(CustomizeParameterFlag.LipOpacity)) - parameters.LipColor.W = LipOpacity; + parameters.SkinFresnelValue0 = new CustomizeParameterValue(SkinDiffuse).XivQuadruple; if (flags.HasFlag(CustomizeParameterFlag.HairDiffuse)) - parameters.MainColor = Convert(HairDiffuse); + parameters.MainColor = new CustomizeParameterValue(HairDiffuse).XivTriple; if (flags.HasFlag(CustomizeParameterFlag.HairSpecular)) - parameters.HairFresnelValue0 = Convert(HairSpecular); + parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple; if (flags.HasFlag(CustomizeParameterFlag.HairHighlight)) - parameters.MeshColor = Convert(HairHighlight); - if (flags.HasFlag(CustomizeParameterFlag.LeftEye)) - parameters.LeftColor = Convert(LeftEye, parameters.LeftColor.W); - if (flags.HasFlag(CustomizeParameterFlag.RightEye)) - parameters.RightColor = Convert(RightEye, parameters.RightColor.W); + parameters.MeshColor = new CustomizeParameterValue(HairHighlight).XivTriple; if (flags.HasFlag(CustomizeParameterFlag.FeatureColor)) - parameters.OptionColor = Convert(FeatureColor); - if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvMultiplier)) - parameters.LeftColor.W = FacePaintUvMultiplier; - if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvOffset)) - parameters.RightColor.W = FacePaintUvOffset; + parameters.OptionColor = new CustomizeParameterValue(FeatureColor).XivTriple; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public readonly void Apply(ref DecalParameters parameters, CustomizeParameterFlag flags = CustomizeParameterExtensions.All) + { + if (flags.HasFlag(CustomizeParameterFlag.DecalColor)) + parameters.Color = new CustomizeParameterValue(DecalColor).XivQuadruple; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -103,37 +129,37 @@ public struct CustomizeParameterData switch (flag) { case CustomizeParameterFlag.SkinDiffuse: - parameters.SkinColor = Convert(SkinDiffuse, parameters.SkinColor.W); + parameters.SkinColor = new CustomizeParameterValue(SkinDiffuse, parameters.SkinColor.W).XivQuadruple; break; case CustomizeParameterFlag.MuscleTone: parameters.SkinColor.W = MuscleTone; break; case CustomizeParameterFlag.SkinSpecular: - parameters.SkinFresnelValue0 = Convert(SkinSpecular, 0); + parameters.SkinFresnelValue0 = new CustomizeParameterValue(SkinSpecular).XivQuadruple; break; case CustomizeParameterFlag.LipDiffuse: - parameters.LipColor = Convert(LipDiffuse, parameters.LipColor.W); + parameters.LipColor = new CustomizeParameterValue(LipDiffuse, parameters.LipColor.W).XivQuadruple; break; case CustomizeParameterFlag.LipOpacity: parameters.LipColor.W = LipOpacity; break; case CustomizeParameterFlag.HairDiffuse: - parameters.MainColor = Convert(HairDiffuse); + parameters.MainColor = new CustomizeParameterValue(HairDiffuse).XivTriple; break; case CustomizeParameterFlag.HairSpecular: - parameters.HairFresnelValue0 = Convert(HairSpecular); + parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple; break; case CustomizeParameterFlag.HairHighlight: - parameters.MeshColor = Convert(HairHighlight); + parameters.MeshColor = new CustomizeParameterValue(HairHighlight).XivTriple; break; case CustomizeParameterFlag.LeftEye: - parameters.LeftColor = Convert(LeftEye, parameters.LeftColor.W); + parameters.LeftColor = new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple; break; case CustomizeParameterFlag.RightEye: - parameters.RightColor = Convert(RightEye, parameters.RightColor.W); + parameters.RightColor = new CustomizeParameterValue(RightEye, parameters.RightColor.W).XivQuadruple; break; case CustomizeParameterFlag.FeatureColor: - parameters.OptionColor = Convert(FeatureColor); + parameters.OptionColor = new CustomizeParameterValue(FeatureColor).XivTriple; break; case CustomizeParameterFlag.FacePaintUvMultiplier: parameters.LeftColor.W = FacePaintUvMultiplier; @@ -144,61 +170,49 @@ public struct CustomizeParameterData } } - public static CustomizeParameterData FromParameters(in CustomizeParameter parameter) + public static CustomizeParameterData FromParameters(in CustomizeParameter parameter, in DecalParameters decal) => new() { FacePaintUvOffset = parameter.RightColor.W, FacePaintUvMultiplier = parameter.LeftColor.W, MuscleTone = parameter.SkinColor.W, LipOpacity = parameter.LipColor.W, - SkinDiffuse = Convert(parameter.SkinColor), - SkinSpecular = Convert(parameter.SkinFresnelValue0), - LipDiffuse = Convert(parameter.LipColor), - HairDiffuse = Convert(parameter.MainColor), - HairSpecular = Convert(parameter.HairFresnelValue0), - HairHighlight = Convert(parameter.MeshColor), - LeftEye = Convert(parameter.LeftColor), - RightEye = Convert(parameter.RightColor), - FeatureColor = Convert(parameter.OptionColor), + SkinDiffuse = new CustomizeParameterValue(parameter.SkinColor).InternalTriple, + SkinSpecular = new CustomizeParameterValue(parameter.SkinFresnelValue0).InternalTriple, + LipDiffuse = new CustomizeParameterValue(parameter.LipColor).InternalTriple, + HairDiffuse = new CustomizeParameterValue(parameter.MainColor).InternalTriple, + HairSpecular = new CustomizeParameterValue(parameter.HairFresnelValue0).InternalTriple, + HairHighlight = new CustomizeParameterValue(parameter.MeshColor).InternalTriple, + LeftEye = new CustomizeParameterValue(parameter.LeftColor).InternalTriple, + RightEye = new CustomizeParameterValue(parameter.RightColor).InternalTriple, + FeatureColor = new CustomizeParameterValue(parameter.OptionColor).InternalTriple, + DecalColor = FromParameter(decal), }; - public static Vector3 FromParameter(in CustomizeParameter parameter, CustomizeParameterFlag flag) + public static CustomizeParameterValue FromParameter(in CustomizeParameter parameter, CustomizeParameterFlag flag) => flag switch { - CustomizeParameterFlag.SkinDiffuse => Convert(parameter.SkinColor), - CustomizeParameterFlag.MuscleTone => new Vector3(parameter.SkinColor.W), - CustomizeParameterFlag.SkinSpecular => Convert(parameter.SkinFresnelValue0), - CustomizeParameterFlag.LipDiffuse => Convert(parameter.LipColor), - CustomizeParameterFlag.LipOpacity => new Vector3(parameter.LipColor.W), - CustomizeParameterFlag.HairDiffuse => Convert(parameter.MainColor), - CustomizeParameterFlag.HairSpecular => Convert(parameter.HairFresnelValue0), - CustomizeParameterFlag.HairHighlight => Convert(parameter.MeshColor), - CustomizeParameterFlag.LeftEye => Convert(parameter.LeftColor), - CustomizeParameterFlag.RightEye => Convert(parameter.RightColor), - CustomizeParameterFlag.FeatureColor => Convert(parameter.OptionColor), - CustomizeParameterFlag.FacePaintUvMultiplier => new Vector3(parameter.LeftColor.W), - CustomizeParameterFlag.FacePaintUvOffset => new Vector3(parameter.RightColor.W), - _ => Vector3.Zero, + CustomizeParameterFlag.SkinDiffuse => new CustomizeParameterValue(parameter.SkinColor), + CustomizeParameterFlag.MuscleTone => new CustomizeParameterValue(parameter.SkinColor.W), + CustomizeParameterFlag.SkinSpecular => new CustomizeParameterValue(parameter.SkinFresnelValue0), + CustomizeParameterFlag.LipDiffuse => new CustomizeParameterValue(parameter.LipColor), + CustomizeParameterFlag.LipOpacity => new CustomizeParameterValue(parameter.LipColor.W), + CustomizeParameterFlag.HairDiffuse => new CustomizeParameterValue(parameter.MainColor), + CustomizeParameterFlag.HairSpecular => new CustomizeParameterValue(parameter.HairFresnelValue0), + CustomizeParameterFlag.HairHighlight => new CustomizeParameterValue(parameter.MeshColor), + CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(parameter.LeftColor), + CustomizeParameterFlag.RightEye => new CustomizeParameterValue(parameter.RightColor), + CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(parameter.OptionColor), + CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(parameter.LeftColor.W), + CustomizeParameterFlag.FacePaintUvOffset => new CustomizeParameterValue(parameter.RightColor.W), + _ => CustomizeParameterValue.Zero, }; - private static FFXIVClientStructs.FFXIV.Common.Math.Vector4 Convert(Vector3 value, float w) - => new(value.X * value.X, value.Y * value.Y, value.Z * value.Z, w); - - private static Vector3 Convert(FFXIVClientStructs.FFXIV.Common.Math.Vector3 value) - => new((float)Math.Sqrt(value.X), (float)Math.Sqrt(value.Y), (float)Math.Sqrt(value.Z)); - - private static Vector3 Convert(FFXIVClientStructs.FFXIV.Common.Math.Vector4 value) - => new((float)Math.Sqrt(value.X), (float)Math.Sqrt(value.Y), (float)Math.Sqrt(value.Z)); - - private static FFXIVClientStructs.FFXIV.Common.Math.Vector3 Convert(Vector3 value) - => new(value.X * value.X, value.Y * value.Y, value.Z * value.Z); + public static Vector4 FromParameter(in DecalParameters parameter) + => new CustomizeParameterValue(parameter.Color).InternalQuadruple; private static bool SetIfDifferent(ref Vector3 val, Vector3 @new) { - @new.X = Math.Clamp(@new.X, 0, 1); - @new.Y = Math.Clamp(@new.Y, 0, 1); - @new.Z = Math.Clamp(@new.Z, 0, 1); - if (@new == val) return false; @@ -206,7 +220,16 @@ public struct CustomizeParameterData return true; } - private static bool SetIfDifferent(ref T val, T @new) where T : IEqualityOperators + private static bool SetIfDifferent(ref float val, float @new) + { + if (@new == val) + return false; + + val = @new; + return true; + } + + private static bool SetIfDifferent(ref Vector4 val, Vector4 @new) { if (@new == val) return false; diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs index d2b2ff4..d52e6c9 100644 --- a/Glamourer/GameData/CustomizeParameterFlag.cs +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -16,28 +16,28 @@ public enum CustomizeParameterFlag : ushort FeatureColor = 0x0400, FacePaintUvMultiplier = 0x0800, FacePaintUvOffset = 0x1000, + DecalColor = 0x2000, } public static class CustomizeParameterExtensions { - public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x1FFF; + public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x3FFF; - public const CustomizeParameterFlag Triples = All - & ~(CustomizeParameterFlag.MuscleTone - | CustomizeParameterFlag.LipOpacity - | CustomizeParameterFlag.FacePaintUvOffset - | CustomizeParameterFlag.FacePaintUvMultiplier); + public const CustomizeParameterFlag RgbTriples = All + & ~(RgbaQuadruples | Percentages | Values); + public const CustomizeParameterFlag RgbaQuadruples = CustomizeParameterFlag.DecalColor; public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone | CustomizeParameterFlag.LipOpacity; - public const CustomizeParameterFlag Values = CustomizeParameterFlag.FacePaintUvOffset | CustomizeParameterFlag.FacePaintUvMultiplier; + public const CustomizeParameterFlag Values = CustomizeParameterFlag.FacePaintUvOffset | CustomizeParameterFlag.FacePaintUvMultiplier; public static readonly IReadOnlyList AllFlags = [.. Enum.GetValues()]; - public static readonly IReadOnlyList TripleFlags = AllFlags.Where(f => Triples.HasFlag(f)).ToArray(); + public static readonly IReadOnlyList RgbaFlags = AllFlags.Where(f => RgbaQuadruples.HasFlag(f)).ToArray(); + public static readonly IReadOnlyList RgbFlags = AllFlags.Where(f => RgbTriples.HasFlag(f)).ToArray(); public static readonly IReadOnlyList PercentageFlags = AllFlags.Where(f => Percentages.HasFlag(f)).ToArray(); public static readonly IReadOnlyList ValueFlags = AllFlags.Where(f => Values.HasFlag(f)).ToArray(); public static int Count(this CustomizeParameterFlag flag) - => Triples.HasFlag(flag) ? 3 : 1; + => RgbaQuadruples.HasFlag(flag) ? 4 : RgbTriples.HasFlag(flag) ? 3 : 1; public static IEnumerable Iterate(this CustomizeParameterFlag flags) => AllFlags.Where(f => flags.HasFlag(f)); diff --git a/Glamourer/GameData/CustomizeParameterValue.cs b/Glamourer/GameData/CustomizeParameterValue.cs new file mode 100644 index 0000000..74cf666 --- /dev/null +++ b/Glamourer/GameData/CustomizeParameterValue.cs @@ -0,0 +1,47 @@ +namespace Glamourer.GameData; + +public readonly struct CustomizeParameterValue +{ + public static readonly CustomizeParameterValue Zero = default; + + private readonly Vector4 _data; + + public CustomizeParameterValue(Vector4 data) + => _data = data; + + public CustomizeParameterValue(Vector3 data, float w = 0) + => _data = new Vector4(data, w); + + public CustomizeParameterValue(FFXIVClientStructs.FFXIV.Common.Math.Vector4 data) + => _data = new Vector4(Root(data.X), Root(data.Y), Root(data.Z), data.W); + + public CustomizeParameterValue(FFXIVClientStructs.FFXIV.Common.Math.Vector3 data) + => _data = new Vector4(Root(data.X), Root(data.Y), Root(data.Z), 0); + + public CustomizeParameterValue(float value, float y = 0, float z = 0, float w = 0) + => _data = new Vector4(value, y, z, w); + + public Vector3 InternalTriple + => new(_data.X, _data.Y, _data.Z); + + public float Single + => _data.X; + + public Vector4 InternalQuadruple + => _data; + + public FFXIVClientStructs.FFXIV.Common.Math.Vector4 XivQuadruple + => new(Square(_data.X), Square(_data.Y), Square(_data.Z), _data.W); + + public FFXIVClientStructs.FFXIV.Common.Math.Vector3 XivTriple + => new(Square(_data.X), Square(_data.Y), Square(_data.Z)); + + private static float Square(float x) + => x < 0 ? -x * x : x * x; + + private static float Root(float x) + => x < 0 ? -(float)Math.Sqrt(-x) : x; + + public float this[int idx] + => _data[idx]; +} diff --git a/Glamourer/GameData/DecalParameters.cs b/Glamourer/GameData/DecalParameters.cs new file mode 100644 index 0000000..de5231f --- /dev/null +++ b/Glamourer/GameData/DecalParameters.cs @@ -0,0 +1,8 @@ +using Vector4 = FFXIVClientStructs.FFXIV.Common.Math.Vector4; + +namespace Glamourer.GameData; + +public struct DecalParameters +{ + public Vector4 Color; +} diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs index becaef5..0bae022 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs @@ -10,11 +10,13 @@ public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in Des public readonly CustomizeParameterFlag Flag = flag; public bool Locked; public bool DisplayApplication; + public bool AllowRevert; - public Action ValueSetter = null!; - public Action ApplySetter = null!; - public Vector3 CurrentValue = data.Parameters[flag]; - public bool CurrentApply; + public Action ValueSetter = null!; + public Action ApplySetter = null!; + public CustomizeParameterValue CurrentValue = data.Parameters[flag]; + public CustomizeParameterValue GameValue; + public bool CurrentApply; public static CustomizeParameterDrawData FromDesign(DesignManager manager, Design design, CustomizeParameterFlag flag) => new(flag, design.DesignData) @@ -32,5 +34,7 @@ public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in Des Locked = state.IsLocked, DisplayApplication = false, ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, StateChanged.Source.Manual), + GameValue = state.BaseData.Parameters[flag], + AllowRevert = true, }; } diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index a97fbe7..cf1753c 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -3,6 +3,7 @@ using Glamourer.GameData; using Glamourer.State; using Dalamud.Interface.Utility.Raii; using ImGuiNET; +using OtterGui; using OtterGui.Services; namespace Glamourer.Gui.Customization; @@ -11,8 +12,11 @@ public class CustomizeParameterDrawer(Configuration config) : IService { public void Draw(DesignManager designManager, Design design) { - foreach (var flag in CustomizeParameterExtensions.TripleFlags) - DrawColorInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); + foreach (var flag in CustomizeParameterExtensions.RgbFlags) + DrawColorInput3(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); + + foreach (var flag in CustomizeParameterExtensions.RgbaFlags) + DrawColorInput4(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); foreach (var flag in CustomizeParameterExtensions.PercentageFlags) DrawPercentageInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); @@ -23,8 +27,11 @@ public class CustomizeParameterDrawer(Configuration config) : IService public void Draw(StateManager stateManager, ActorState state) { - foreach (var flag in CustomizeParameterExtensions.TripleFlags) - DrawColorInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + foreach (var flag in CustomizeParameterExtensions.RgbFlags) + DrawColorInput3(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + + foreach (var flag in CustomizeParameterExtensions.RgbaFlags) + DrawColorInput4(CustomizeParameterDrawData.FromState(stateManager, state, flag)); foreach (var flag in CustomizeParameterExtensions.PercentageFlags) DrawPercentageInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); @@ -33,15 +40,30 @@ public class CustomizeParameterDrawer(Configuration config) : IService DrawValueInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); } - private void DrawColorInput(in CustomizeParameterDrawData data) + private void DrawColorInput3(in CustomizeParameterDrawData data) { using var id = ImRaii.PushId((int)data.Flag); - var value = data.CurrentValue; + var value = data.CurrentValue.InternalTriple; using (_ = ImRaii.Disabled(data.Locked)) { - if (ImGui.ColorEdit3("##value", ref value, ImGuiColorEditFlags.Float)) - data.ValueSetter(value); + if (ImGui.ColorEdit3("##value", ref value, ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions)) + data.ValueSetter(new CustomizeParameterValue(value)); } + DrawRevert(data); + + DrawApplyAndLabel(data); + } + + private void DrawColorInput4(in CustomizeParameterDrawData data) + { + using var id = ImRaii.PushId((int)data.Flag); + var value = data.CurrentValue.InternalQuadruple; + using (_ = ImRaii.Disabled(data.Locked)) + { + if (ImGui.ColorEdit4("##value", ref value, ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions)) + data.ValueSetter(new CustomizeParameterValue(value)); + } + DrawRevert(data); DrawApplyAndLabel(data); } @@ -54,8 +76,9 @@ public class CustomizeParameterDrawer(Configuration config) : IService using (_ = ImRaii.Disabled(data.Locked)) { if (ImGui.InputFloat("##value", ref value, 0.1f, 0.5f)) - data.ValueSetter(new Vector3(value)); + data.ValueSetter(new CustomizeParameterValue(value)); } + DrawRevert(data); DrawApplyAndLabel(data); } @@ -67,13 +90,26 @@ public class CustomizeParameterDrawer(Configuration config) : IService using (_ = ImRaii.Disabled(data.Locked)) { - if (ImGui.SliderFloat("##value", ref value, 0, 100, "%.2f", ImGuiSliderFlags.AlwaysClamp)) - data.ValueSetter(new Vector3(value / 100f)); + if (ImGui.SliderFloat("##value", ref value, -1000f, 1000f, "%.2f")) + data.ValueSetter(new CustomizeParameterValue(value / 100f)); } + DrawRevert(data); + DrawApplyAndLabel(data); } + private static void DrawRevert(in CustomizeParameterDrawData data) + { + if (data.Locked || !data.AllowRevert) + return; + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) + data.ValueSetter(data.GameValue); + + ImGuiUtil.HoverTooltip("Hold Control and Right-click to revert to game values."); + } + private static void DrawApply(in CustomizeParameterDrawData data) { if (UiHelpers.DrawCheckbox("##apply", "Apply this custom parameter when applying the Design.", data.CurrentApply, out var enabled, diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index f751ef0..8e93877 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -85,19 +85,13 @@ public unsafe class ModelEvaluationPanel( { if (!model.IsHuman) return; - if (model.AsHuman->CustomizeParameterCBuffer == null) - return; - var ptr = (CustomizeParameter*)model.AsHuman->CustomizeParameterCBuffer->UnsafeSourcePointer; - if (ptr == null) - return; - - var convert = CustomizeParameterData.FromParameters(*ptr); + var convert = model.GetParameterData(); foreach (var flag in CustomizeParameterExtensions.AllFlags) { ImGuiUtil.DrawTableColumn(flag.ToString()); ImGuiUtil.DrawTableColumn(string.Empty); - ImGuiUtil.DrawTableColumn(convert[flag].ToString()); + ImGuiUtil.DrawTableColumn(convert[flag].InternalQuadruple.ToString()); ImGui.TableNextColumn(); } } diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs index d91ca58..fc7b880 100644 --- a/Glamourer/Interop/Structs/Model.cs +++ b/Glamourer/Interop/Structs/Model.cs @@ -1,5 +1,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Shader; +using Glamourer.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object; @@ -159,6 +161,65 @@ public readonly unsafe struct Model : IEquatable return (main, off, mainData, offData); } + public CustomizeParameterData GetParameterData() + { + if (!IsHuman) + return default; + + var cBuffer1 = AsHuman->CustomizeParameterCBuffer; + var cBuffer2 = AsHuman->DecalColorCBuffer; + var ptr1 = (CustomizeParameter*)(cBuffer1 == null ? null : cBuffer1->UnsafeSourcePointer); + var ptr2 = (DecalParameters*)(cBuffer2 == null ? null : cBuffer2->UnsafeSourcePointer); + return CustomizeParameterData.FromParameters(ptr1 != null ? *ptr1 : default, ptr2 != null ? *ptr2 : default); + } + + public void ApplyParameterData(CustomizeParameterFlag flags, in CustomizeParameterData data) + { + if (!IsHuman) + return; + + if (flags.HasFlag(CustomizeParameterFlag.DecalColor)) + { + var cBufferDecal = AsHuman->DecalColorCBuffer; + var ptrDecal = (DecalParameters*)(cBufferDecal == null ? null : cBufferDecal->UnsafeSourcePointer); + if (ptrDecal != null) + data.Apply(ref *ptrDecal); + } + + flags &= ~CustomizeParameterFlag.DecalColor; + var cBuffer = AsHuman->CustomizeParameterCBuffer; + var ptr = (CustomizeParameter*)(cBuffer == null ? null : cBuffer->UnsafeSourcePointer); + if (ptr != null) + data.Apply(ref *ptr, flags); + } + + public bool ApplySingleParameterData(CustomizeParameterFlag flag, in CustomizeParameterData data) + { + if (!IsHuman) + return false; + + if (flag is CustomizeParameterFlag.DecalColor) + { + var cBuffer = AsHuman->DecalColorCBuffer; + var ptr = (DecalParameters*)(cBuffer == null ? null : cBuffer->UnsafeSourcePointer); + if (ptr == null) + return false; + + data.Apply(ref *ptr); + return true; + } + else + { + var cBuffer = AsHuman->CustomizeParameterCBuffer; + var ptr = (CustomizeParameter*)(cBuffer == null ? null : cBuffer->UnsafeSourcePointer); + if (ptr == null) + return false; + + data.ApplySingle(ref *ptr, flag); + return true; + } + } + private (Model, Model, int) GetChildrenWeapons() { Span weapons = stackalloc Model[2]; diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index fbc7b6b..b737736 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -1,5 +1,4 @@ -using FFXIVClientStructs.FFXIV.Shader; -using Glamourer.Events; +using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -282,23 +281,13 @@ public class StateApplier( } /// Change the customize parameters on models. Can change multiple at once. - public unsafe void ChangeParameters(ActorData data, CustomizeParameterFlag flags, in CustomizeParameterData values) + public void ChangeParameters(ActorData data, CustomizeParameterFlag flags, in CustomizeParameterData values) { - if (!_config.UseAdvancedParameters) + if (!_config.UseAdvancedParameters || flags == 0) return; foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) - { - var buffer = actor.Model.AsHuman->CustomizeParameterCBuffer; - if (buffer == null) - continue; - - var ptr = (CustomizeParameter*)buffer->UnsafeSourcePointer; - if (ptr == null) - continue; - - values.Apply(ref *ptr, flags); - } + actor.Model.ApplyParameterData(flags, values); } /// diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 55ea1c1..1d5048e 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -210,7 +210,7 @@ public class StateEditor } /// Change the customize flags of a character. - public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, Vector3 value, StateChanged.Source source, out Vector3 oldValue, + public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateChanged.Source source, out CustomizeParameterValue oldValue, uint key = 0) { oldValue = state.ModelData.Parameters[flag]; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index f9684d8..3851871 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -729,30 +729,27 @@ public class StateListener : IDisposable if (!model.IsHuman) return; - var cBuffer = model.AsHuman->CustomizeParameterCBuffer; - if (cBuffer == null) - return; - - var ptr = (CustomizeParameter*)cBuffer->UnsafeSourcePointer; - if (ptr == null) - return; - + var data = model.GetParameterData(); foreach (var flag in CustomizeParameterExtensions.AllFlags) { - var newValue = CustomizeParameterData.FromParameter(*ptr, flag); - + var newValue = data[flag]; switch (state[flag]) { case StateChanged.Source.Game: + if (state.BaseData.Parameters.Set(flag, newValue)) + _manager.ChangeCustomizeParameter(state, flag, newValue, StateChanged.Source.Game); + break; case StateChanged.Source.Manual: if (state.BaseData.Parameters.Set(flag, newValue)) _manager.ChangeCustomizeParameter(state, flag, newValue, StateChanged.Source.Game); + else if (_config.UseAdvancedParameters) + model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; case StateChanged.Source.Fixed: case StateChanged.Source.Ipc: state.BaseData.Parameters.Set(flag, newValue); if (_config.UseAdvancedParameters) - state.ModelData.Parameters.ApplySingle(ref *ptr, flag); + model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index e39f30b..4d5098d 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -186,13 +186,7 @@ public class StateManager( // Weapon visibility could technically be inferred from the weapon draw objects, // but since we use hat visibility from the game object we can also use weapon visibility from it. ret.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden); - - if (model.IsHuman && model.AsHuman->CustomizeParameterCBuffer != null) - { - var ptr = model.AsHuman->CustomizeParameterCBuffer->UnsafeSourcePointer; - if (ptr != null) - ret.Parameters = CustomizeParameterData.FromParameters(*(CustomizeParameter*)ptr); - } + ret.Parameters = model.GetParameterData(); return ret; } @@ -315,7 +309,7 @@ public class StateManager( } /// Change the crest of an equipment piece. - public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, Vector3 value, StateChanged.Source source, uint key = 0) + public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateChanged.Source source, uint key = 0) { if (!_editor.ChangeParameter(state, flag, value, source, out var old, key)) return;