diff --git a/Glamourer/GameData/CustomizeParameterData.cs b/Glamourer/GameData/CustomizeParameterData.cs new file mode 100644 index 0000000..16a6dad --- /dev/null +++ b/Glamourer/GameData/CustomizeParameterData.cs @@ -0,0 +1,220 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using FFXIVClientStructs.FFXIV.Shader; + +namespace Glamourer.GameData; + +public struct CustomizeParameterData +{ + public Vector3 SkinDiffuse; + public Vector3 SkinSpecular; + public Vector3 LipDiffuse; + public Vector3 HairDiffuse; + public Vector3 HairSpecular; + public Vector3 HairHighlight; + public Vector3 LeftEye; + public Vector3 RightEye; + public Vector3 FeatureColor; + public float FacePaintUvMultiplier; + public float FacePaintUvOffset; + public float MuscleTone; + public float LipOpacity; + + public Vector3 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, + }; + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + set => Set(flag, value); + } + + public bool Set(CustomizeParameterFlag flag, Vector3 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]), + _ => false, + }; + } + + [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; + 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; + if (flags.HasFlag(CustomizeParameterFlag.HairDiffuse)) + parameters.MainColor = Convert(HairDiffuse); + if (flags.HasFlag(CustomizeParameterFlag.HairSpecular)) + parameters.HairFresnelValue0 = Convert(HairSpecular); + 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); + 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; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public readonly void ApplySingle(ref CustomizeParameter parameters, CustomizeParameterFlag flag) + { + switch (flag) + { + case CustomizeParameterFlag.SkinDiffuse: + parameters.SkinColor = Convert(SkinDiffuse, parameters.SkinColor.W); + break; + case CustomizeParameterFlag.MuscleTone: + parameters.SkinColor.W = MuscleTone; + break; + case CustomizeParameterFlag.SkinSpecular: + parameters.SkinFresnelValue0 = Convert(SkinSpecular, 0); + break; + case CustomizeParameterFlag.LipDiffuse: + parameters.LipColor = Convert(LipDiffuse, parameters.LipColor.W); + break; + case CustomizeParameterFlag.LipOpacity: + parameters.LipColor.W = LipOpacity; + break; + case CustomizeParameterFlag.HairDiffuse: + parameters.MainColor = Convert(HairDiffuse); + break; + case CustomizeParameterFlag.HairSpecular: + parameters.HairFresnelValue0 = Convert(HairSpecular); + break; + case CustomizeParameterFlag.HairHighlight: + parameters.MeshColor = Convert(HairHighlight); + break; + case CustomizeParameterFlag.LeftEye: + parameters.LeftColor = Convert(LeftEye, parameters.LeftColor.W); + break; + case CustomizeParameterFlag.RightEye: + parameters.RightColor = Convert(RightEye, parameters.RightColor.W); + break; + case CustomizeParameterFlag.FeatureColor: + parameters.OptionColor = Convert(FeatureColor); + break; + case CustomizeParameterFlag.FacePaintUvMultiplier: + parameters.LeftColor.W = FacePaintUvMultiplier; + break; + case CustomizeParameterFlag.FacePaintUvOffset: + parameters.RightColor.W = FacePaintUvOffset; + break; + } + } + + public static CustomizeParameterData FromParameters(in CustomizeParameter parameter) + => 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), + }; + + public static Vector3 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, + }; + + 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); + + 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; + + val = @new; + return true; + } + + private static bool SetIfDifferent(ref T val, T @new) where T : IEqualityOperators + { + if (@new == val) + return false; + + val = @new; + return true; + } +} diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs new file mode 100644 index 0000000..d874538 --- /dev/null +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace Glamourer.GameData; + +[Flags] +public enum CustomizeParameterFlag : ushort +{ + SkinDiffuse = 0x0001, + MuscleTone = 0x0002, + SkinSpecular = 0x0004, + LipDiffuse = 0x0008, + LipOpacity = 0x0010, + HairDiffuse = 0x0020, + HairSpecular = 0x0040, + HairHighlight = 0x0080, + LeftEye = 0x0100, + RightEye = 0x0200, + FeatureColor = 0x0400, + FacePaintUvMultiplier = 0x0800, + FacePaintUvOffset = 0x1000, +} + +public static class CustomizeParameterExtensions +{ + public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x1FFF; + + public const CustomizeParameterFlag Triples = All + & ~(CustomizeParameterFlag.MuscleTone + | CustomizeParameterFlag.LipOpacity + | CustomizeParameterFlag.FacePaintUvOffset + | CustomizeParameterFlag.FacePaintUvMultiplier); + + public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone | CustomizeParameterFlag.LipOpacity; + 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 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; + + public static IEnumerable Iterate(this CustomizeParameterFlag flags) + => AllFlags.Where(f => flags.HasFlag(f)); + + public static int ToInternalIndex(this CustomizeParameterFlag flag) + => BitOperations.TrailingZeroCount((uint)flag); +} diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs new file mode 100644 index 0000000..09426e2 --- /dev/null +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs @@ -0,0 +1,38 @@ +using System; +using System.Numerics; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.GameData; +using Glamourer.State; + +namespace Glamourer.Gui.Customization; + +public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data) +{ + public readonly CustomizeParameterFlag Flag = flag; + public bool Locked; + public bool DisplayApplication; + + public Action ValueSetter = null!; + public Action ApplySetter = null!; + public Vector3 CurrentValue = data.Parameters[flag]; + public bool CurrentApply; + + public static CustomizeParameterDrawData FromDesign(DesignManager manager, Design design, CustomizeParameterFlag flag) + => new(flag, design.DesignData) + { + Locked = design.WriteProtected(), + DisplayApplication = true, + CurrentApply = design.DoApplyParameter(flag), + ValueSetter = v => manager.ChangeCustomizeParameter(design, flag, v), + ApplySetter = v => manager.ChangeApplyParameter(design, flag, v), + }; + + public static CustomizeParameterDrawData FromState(StateManager manager, ActorState state, CustomizeParameterFlag flag) + => new(flag, state.ModelData) + { + Locked = state.IsLocked, + DisplayApplication = false, + ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, StateChanged.Source.Manual), + }; +} diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs new file mode 100644 index 0000000..41760ed --- /dev/null +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -0,0 +1,96 @@ +using Glamourer.Designs; +using Glamourer.GameData; +using Glamourer.State; +using System.Numerics; +using Dalamud.Interface.Utility.Raii; +using ImGuiNET; +using OtterGui.Services; + +namespace Glamourer.Gui.Customization; + +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.PercentageFlags) + DrawPercentageInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); + + foreach (var flag in CustomizeParameterExtensions.ValueFlags) + DrawValueInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); + } + + public void Draw(StateManager stateManager, ActorState state) + { + foreach (var flag in CustomizeParameterExtensions.TripleFlags) + DrawColorInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + + foreach (var flag in CustomizeParameterExtensions.PercentageFlags) + DrawPercentageInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + + foreach (var flag in CustomizeParameterExtensions.ValueFlags) + DrawValueInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + } + + private void DrawColorInput(in CustomizeParameterDrawData data) + { + using var id = ImRaii.PushId((int)data.Flag); + var value = data.CurrentValue; + using (_ = ImRaii.Disabled(data.Locked)) + { + if (ImGui.ColorEdit3("##value", ref value, ImGuiColorEditFlags.Float)) + data.ValueSetter(value); + } + + DrawApplyAndLabel(data); + } + + private void DrawValueInput(in CustomizeParameterDrawData data) + { + using var id = ImRaii.PushId((int)data.Flag); + var value = data.CurrentValue[0]; + + using (_ = ImRaii.Disabled(data.Locked)) + { + if (ImGui.InputFloat("##value", ref value, 0.1f, 0.5f)) + data.ValueSetter(new Vector3(value)); + } + + DrawApplyAndLabel(data); + } + + private void DrawPercentageInput(in CustomizeParameterDrawData data) + { + using var id = ImRaii.PushId((int)data.Flag); + var value = data.CurrentValue[0] * 100f; + + using (_ = ImRaii.Disabled(data.Locked)) + { + if (ImGui.SliderFloat("##value", ref value, 0, 100, "%.2f", ImGuiSliderFlags.AlwaysClamp)) + data.ValueSetter(new Vector3(value / 100f)); + } + + DrawApplyAndLabel(data); + } + + private static void DrawApply(in CustomizeParameterDrawData data) + { + if (UiHelpers.DrawCheckbox("##apply", "Apply this custom parameter when applying the Design.", data.CurrentApply, out var enabled, + data.Locked)) + data.ApplySetter(enabled); + } + + private void DrawApplyAndLabel(in CustomizeParameterDrawData data) + { + if (data.DisplayApplication && !config.HideApplyCheckmarks) + { + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + DrawApply(data); + } + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.TextUnformatted(data.Flag.ToString()); + } +}