Add DT material parameters to Advanced Dyes

This commit is contained in:
Exter-N 2026-02-13 02:54:32 +01:00
parent 4646fd57ab
commit 5efd8c5c88
12 changed files with 626 additions and 121 deletions

View file

@ -82,6 +82,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool AllowDoubleClickToApply { get; set; } = false;
public bool RespectManualOnAutomationUpdate { get; set; } = false;
public bool PreventRandomRepeats { get; set; } = false;
public bool? AlwaysEditAsRoughness { get; set; } = null;
public string PcpFolder { get; set; } = "PCP";
public string PcpColor { get; set; } = "";

View file

@ -256,7 +256,21 @@ public class DesignEditor(
DesignChanged.Invoke(DesignChanged.Type.MaterialRevert, design, new MaterialRevertTransaction(index, !revert, revert));
}
public void ChangeMaterialValue(Design design, MaterialValueIndex index, ColorRow? row)
public void ChangeMaterialMode(Design design, MaterialValueIndex index, ColorRow.Mode mode)
{
var materials = design.GetMaterialDataRef();
if (!materials.TryGetValue(index, out var oldValue))
return;
var oldMode = oldValue.Mode;
materials.AddOrUpdateValue(index, oldValue with { Mode = mode });
Glamourer.Log.Debug($"Changed advanced dye value for {index} from {oldMode} to {mode} mode.");
design.LastEdit = DateTimeOffset.UtcNow;
SaveService.QueueSave(design);
DesignChanged.Invoke(DesignChanged.Type.MaterialMode, design, new MaterialModeTransaction(index, oldMode, mode));
}
public void ChangeMaterialValue(Design design, MaterialValueIndex index, ColorRow? row, ColorRow.Mode? mode = null)
{
var materials = design.GetMaterialDataRef();
if (materials.TryGetValue(index, out var oldValue))
@ -268,9 +282,14 @@ public class DesignEditor(
}
else if (!row.Value.NearEqual(oldValue.Value))
{
materials.UpdateValue(index, new MaterialValueDesign(row.Value, oldValue.Enabled, oldValue.Revert), out _);
materials.UpdateValue(index, new MaterialValueDesign(row.Value, oldValue.Enabled, oldValue.Revert, mode ?? oldValue.Mode), out _);
Glamourer.Log.Debug($"Updated advanced dye value for {index} to new value.");
}
else if (mode.HasValue && mode.Value != oldValue.Mode)
{
ChangeMaterialMode(design, index, mode.Value);
return;
}
else
{
return;
@ -280,7 +299,7 @@ public class DesignEditor(
{
if (!row.HasValue)
return;
if (!materials.TryAddValue(index, new MaterialValueDesign(row.Value, true, false)))
if (!materials.TryAddValue(index, new MaterialValueDesign(row.Value, true, false, mode ?? ColorRow.Mode.Legacy)))
return;
Glamourer.Log.Debug($"Added new advanced dye value for {index}.");
@ -288,7 +307,8 @@ public class DesignEditor(
design.LastEdit = DateTimeOffset.UtcNow;
SaveService.DelaySave(design);
DesignChanged.Invoke(DesignChanged.Type.Material, design, new MaterialTransaction(index, oldValue.Value, row));
DesignChanged.Invoke(DesignChanged.Type.Material, design,
new MaterialTransaction(index, oldValue.Value, row, mode.HasValue ? oldValue.Mode : null, mode));
}
public void ChangeApplyMaterialValue(Design design, MaterialValueIndex index, bool value)

View file

@ -119,16 +119,18 @@ public readonly record struct ModUpdatedTransaction(Mod Mod, ModSettings Old, Mo
}
/// <remarks> Only Designs. </remarks>
public readonly record struct MaterialTransaction(MaterialValueIndex Index, ColorRow? Old, ColorRow? New)
public readonly record struct MaterialTransaction(MaterialValueIndex Index, ColorRow? Old, ColorRow? New, ColorRow.Mode? OldMode, ColorRow.Mode? NewMode)
: ITransaction
{
public ITransaction? Merge(ITransaction older)
=> older is MaterialTransaction other && Index == other.Index ? new MaterialTransaction(Index, other.Old, New) : null;
=> older is MaterialTransaction other && Index == other.Index
? new MaterialTransaction(Index, other.Old, New, other.OldMode ?? OldMode, NewMode ?? other.NewMode)
: null;
public void Revert(IDesignEditor editor, object data)
{
if (editor is DesignManager e)
e.ChangeMaterialValue((Design)data, Index, Old);
e.ChangeMaterialValue((Design)data, Index, Old, OldMode);
}
}
@ -143,6 +145,17 @@ public readonly record struct MaterialRevertTransaction(MaterialValueIndex Index
=> ((DesignManager)editor).ChangeMaterialRevert((Design)data, Index, Old);
}
/// <remarks> Only Designs. </remarks>
public readonly record struct MaterialModeTransaction(MaterialValueIndex Index, ColorRow.Mode Old, ColorRow.Mode New)
: ITransaction
{
public ITransaction? Merge(ITransaction other)
=> null;
public void Revert(IDesignEditor editor, object data)
=> ((DesignManager)editor).ChangeMaterialMode((Design)data, Index, Old);
}
/// <remarks> Only Designs. </remarks>
public readonly record struct ApplicationTransaction(object Index, bool Old, bool New)
: ITransaction

View file

@ -87,6 +87,9 @@ public sealed class DesignChanged()
/// <summary> An existing design had an advanced dye rows Revert state changed. </summary>
MaterialRevert,
/// <summary> An existing design had an advanced dye rows mode changed. </summary>
MaterialMode,
/// <summary> An existing design had changed whether it always forces a redraw or not. </summary>
ForceRedraw,

View file

@ -31,8 +31,9 @@ public sealed unsafe class AdvancedDyePopup(
private bool _anyChanged;
private bool _forceFocus;
private const int RowsPerPage = 16;
private int _rowOffset;
private const int RowsPerPage = 16;
private int _rowOffset;
private bool _editSheen;
private bool ShouldBeDrawn()
{
@ -149,28 +150,51 @@ public sealed unsafe class AdvancedDyePopup(
if ((tab.Success || select is TabItemFlags.SetSelected) && available)
{
_selectedMaterial = i;
DrawToggle();
DrawToggles();
DrawTable(index, table);
}
}
}
private void DrawToggle()
private void DrawToggles()
{
var buttonWidth = new Vector2(Im.ContentRegion.Available.X / 2, 0);
using var font = Im.Font.PushMono();
using var hoverColor = ImGuiColor.ButtonHovered.Push(Im.Style[ImGuiColor.TabHovered]);
var layerButtonWidth = _mode is ColorRow.Mode.Dawntrail ? Im.Font.Mono.GetCharacterAdvance(' ') * 5 + Im.Style.FramePadding.X * 2 : 0;
var buttonWidth =
(Im.ContentRegion.Available.X - (_mode is ColorRow.Mode.Dawntrail ? layerButtonWidth * 2 + Im.Style.ItemSpacing.X : 0)) / 2;
var buttonSize = new Vector2(buttonWidth, 0);
using var font = Im.Font.PushMono();
using var hoverColor = ImGuiColor.ButtonHovered.Push(Im.Style[ImGuiColor.TabHovered]);
hoverColor.Push(ImGuiColor.Button, Im.Style[_rowOffset is 0 ? ImGuiColor.TabSelected : ImGuiColor.Tab]);
if (ImEx.ButtonCorners("Row Pairs 1-8 "u8, buttonWidth, ButtonFlags.MouseButtonLeft, Corners.Left))
if (ImEx.ButtonCorners("Row Pairs 1-8 "u8, buttonSize, ButtonFlags.MouseButtonLeft, Corners.Left))
_rowOffset = 0;
hoverColor.Pop();
Im.Line.NoSpacing();
hoverColor.Push(ImGuiColor.Button, Im.Style[_rowOffset is RowsPerPage ? ImGuiColor.TabSelected : ImGuiColor.Tab]);
if (ImEx.ButtonCorners("Row Pairs 9-16"u8, buttonWidth, ButtonFlags.MouseButtonLeft, Corners.Right))
if (ImEx.ButtonCorners("Row Pairs 9-16"u8, buttonSize, ButtonFlags.MouseButtonLeft, Corners.Right))
_rowOffset = RowsPerPage;
hoverColor.Pop();
if (_mode is ColorRow.Mode.Dawntrail)
{
Im.Line.Same();
buttonSize = new Vector2(layerButtonWidth, 0);
hoverColor.Push(ImGuiColor.Button, Im.Style[!_editSheen ? ImGuiColor.TabSelected : ImGuiColor.Tab]);
if (ImEx.ButtonCorners("Base"u8, buttonSize, ButtonFlags.MouseButtonLeft, Corners.Left))
_editSheen = false;
hoverColor.Pop();
Im.Line.NoSpacing();
hoverColor.Push(ImGuiColor.Button, Im.Style[_editSheen ? ImGuiColor.TabSelected : ImGuiColor.Tab]);
if (ImEx.ButtonCorners("Sheen"u8, buttonSize, ButtonFlags.MouseButtonLeft, Corners.Right))
_editSheen = true;
hoverColor.Pop();
}
}
private void DrawContent(ReadOnlySpan<FFXIVClientStructs.Interop.Pointer<Texture>> textures,
@ -323,7 +347,8 @@ public sealed unsafe class AdvancedDyePopup(
Im.Line.Same(Im.Window.Size.X - 3 * Im.Style.FrameHeight - 2 * spacing - Im.Style.WindowPadding.X);
if (ImEx.Icon.Button(LunaStyle.ToClipboardIcon, "Export this table to your clipboard."u8))
{
ColorRowClipboard.Table = table;
ColorRowClipboard.Table = table;
ColorRowClipboard.TableMode = _mode;
CopyToClipboard(table);
}
@ -333,7 +358,7 @@ public sealed unsafe class AdvancedDyePopup(
for (var idx = 0; idx < ColorTable.NumRows; ++idx)
{
var row = newTable[idx];
var internalRow = new ColorRow(row);
var internalRow = ColorRow.From(row, _mode);
var slot = materialIndex.ToEquipSlot();
var weapon = slot is EquipSlot.MainHand or EquipSlot.OffHand
? _state.ModelData.Weapon(slot)
@ -354,7 +379,7 @@ public sealed unsafe class AdvancedDyePopup(
var changed = _state.Materials.TryGetValue(index, out var value);
if (!changed)
{
var internalRow = new ColorRow(row);
var internalRow = ColorRow.From(row, _mode);
var slot = index.ToEquipSlot();
var weapon = slot switch
{
@ -369,7 +394,9 @@ public sealed unsafe class AdvancedDyePopup(
else
{
_anyChanged = true;
value = new MaterialValueState(value.Game, value.Model, value.DrawData, StateSource.Manual);
value = new MaterialValueState(value.Game,
value.Model.IsPartial(_mode) ? value.Model.MergeOnto(ColorRow.From(row, _mode)) : value.Model,
value.DrawData, StateSource.Manual);
}
ImEx.Icon.Button(LunaStyle.OnHoverIcon, "Highlight the affected colors on the character."u8);
@ -385,45 +412,79 @@ public sealed unsafe class AdvancedDyePopup(
}
Im.Line.Same(0, Im.Style.ItemSpacing.X * 2);
var applied = ImEx.ColorPickerButton("##diffuse"u8, "Change the diffuse value for this row."u8, value.Model.Diffuse,
var applied = ImEx.ColorPickerButton("##diffuse"u8, "Change the diffuse color for this row."u8, value.Model.Diffuse,
out value.Model.Diffuse, 'D');
var spacing = Im.Style.ItemInnerSpacing;
Im.Line.Same(0, spacing.X);
applied |= ImEx.ColorPickerButton("##specular"u8, "Change the specular value for this row."u8, value.Model.Specular,
applied |= ImEx.ColorPickerButton("##specular"u8, "Change the specular color for this row."u8, value.Model.Specular,
out value.Model.Specular, 'S');
Im.Line.Same(0, spacing.X);
applied |= ImEx.ColorPickerButton("##emissive"u8, "Change the emissive value for this row."u8, value.Model.Emissive,
applied |= ImEx.ColorPickerButton("##emissive"u8, "Change the emissive color for this row."u8, value.Model.Emissive,
out value.Model.Emissive, 'E');
Im.Line.Same(0, spacing.X);
if (_mode is not ColorRow.Mode.Dawntrail)
if (_mode is ColorRow.Mode.Dawntrail && _editSheen)
{
Im.Item.SetNextWidthScaled(100);
applied |= DragGloss(ref value.Model.GlossStrength);
Im.Tooltip.OnHover("Change the gloss strength for this row."u8);
// The other layout has 2 items of width 100*sc and one spacing, for a total of 200*sc + 1*sp.
// This layout has 3 items and two spacings: 3*w + 2*sp = 200*sc + 1*sp.
var allItemsWidth = 200 * Im.Style.GlobalScale - spacing.X;
var itemWidth = MathF.Floor(allItemsWidth / 3);
Im.Item.SetNextWidth(allItemsWidth - itemWidth * 2);
applied |= DragSheen(ref value.Model.Sheen, false);
Im.Tooltip.OnHover("Change the sheen strength for this row."u8);
Im.Line.Same(0, spacing.X);
Im.Item.SetNextWidth(itemWidth);
applied |= DragSheenTint(ref value.Model.SheenTint, false);
Im.Tooltip.OnHover("Change the sheen tint for this row."u8);
Im.Line.Same(0, spacing.X);
Im.Item.SetNextWidth(itemWidth);
applied |= DragSheenRoughness(ref value.Model.SheenAperture, false);
Im.Tooltip.OnHover("Change the sheen roughness for this row."u8);
}
else
{
Im.Dummy(new Vector2(100 * Im.Style.GlobalScale, 0));
}
Im.Line.Same(0, spacing.X);
if (_mode is not ColorRow.Mode.Dawntrail)
{
Im.Item.SetNextWidthScaled(100);
applied |= DragSpecularStrength(ref value.Model.SpecularStrength);
Im.Tooltip.OnHover("Change the specular strength for this row."u8);
}
else
{
Im.Dummy(new Vector2(100 * Im.Style.GlobalScale, 0));
var editAsRoughness = config.AlwaysEditAsRoughness ?? _mode is ColorRow.Mode.Dawntrail;
applied |= (_mode, editAsRoughness) switch
{
(ColorRow.Mode.Legacy, false) => DragGloss(ref value.Model.GlossStrength, false),
(ColorRow.Mode.Legacy, true) => DragGlossAsRoughness(ref value.Model.GlossStrength, false),
(ColorRow.Mode.Dawntrail, false) => DragRoughnessAsGloss(ref value.Model.Roughness, false),
(ColorRow.Mode.Dawntrail, true) => DragRoughness(ref value.Model.Roughness, false),
_ => throw new NotImplementedException(),
};
Im.Tooltip.OnHover(editAsRoughness ? "Change the roughness for this row."u8 : "Change the gloss strength for this row."u8);
Im.Line.Same(0, spacing.X);
if (_mode is not ColorRow.Mode.Dawntrail)
{
Im.Item.SetNextWidthScaled(100);
applied |= DragSpecularStrength(ref value.Model.SpecularStrength, false);
Im.Tooltip.OnHover("Change the specular strength for this row."u8);
}
else
{
Im.Item.SetNextWidthScaled(100);
applied |= DragMetalness(ref value.Model.Metalness, false);
Im.Tooltip.OnHover("Change the metalness for this row."u8);
}
}
Im.Line.Same(0, spacing.X);
if (ImEx.Icon.Button(LunaStyle.ToClipboardIcon, "Export this row to your clipboard."u8))
ColorRowClipboard.Row = value.Model;
{
ColorRowClipboard.Row = value.Model;
ColorRowClipboard.RowMode = _mode;
}
Im.Line.Same(0, spacing.X);
if (ImEx.Icon.Button(LunaStyle.FromClipboardIcon, "Import an exported row from your clipboard onto this row."u8,
!ColorRowClipboard.IsSet))
@ -440,12 +501,12 @@ public sealed unsafe class AdvancedDyePopup(
stateManager.ChangeMaterialValue(_state, index, value, ApplySettings.Manual);
}
public static bool DragGloss(ref float value)
public static bool DragGloss(ref float value, bool canUnset)
{
var tmp = value;
var tmp = float.IsNaN(value) ? ColorRow.DefaultGlossStrength : value;
var minValue = Im.Io.KeyControl ? 0f : (float)Half.Epsilon;
if (!Im.Drag("##Gloss"u8, ref tmp, "%.1f G"u8, 0.001f, minValue, Math.Max(0.01f, 0.005f * value), SliderFlags.AlwaysClamp))
return false;
if (!Im.Drag("##Gloss"u8, ref tmp, float.IsNaN(value) ? "\u2014 G"u8 : "%.1f G"u8, 0.001f, minValue, Math.Max(0.01f, 0.005f * value), SliderFlags.AlwaysClamp))
return UnsetBehavior(ref value, canUnset);
var tmp2 = Math.Clamp(tmp, minValue, (float)Half.MaxValue);
if (tmp2 == value)
@ -455,12 +516,27 @@ public sealed unsafe class AdvancedDyePopup(
return true;
}
public static bool DragSpecularStrength(ref float value)
public static bool DragGlossAsRoughness(ref float value, bool canUnset)
{
var tmp = value * 100f;
if (!Im.Drag("##SpecularStrength"u8, ref tmp, "%.0f%% SS"u8, 0f, (float)Half.MaxValue * 100f, 0.05f, SliderFlags.AlwaysClamp))
var roughness = ColorTableRow.RoughnessFromShininess(float.IsNaN(value) ? ColorRow.DefaultGlossStrength : value);
var tmp = roughness * 100f;
if (!Im.Drag("##Gloss"u8, ref tmp, float.IsNaN(value) ? "\u2014 Rg"u8 : "%.0f%% Rg"u8, 0f, 100f, 0.25f, SliderFlags.AlwaysClamp))
return UnsetBehavior(ref value, canUnset);
var tmp2 = Math.Clamp(tmp, 0f, 100f) / 100f;
if (tmp2 == roughness)
return false;
value = ColorTableRow.ShininessFromRoughness(tmp2);
return true;
}
public static bool DragSpecularStrength(ref float value, bool canUnset)
{
var tmp = (float.IsNaN(value) ? ColorRow.DefaultSpecularStrength : value) * 100f;
if (!Im.Drag("##SpecularStrength"u8, ref tmp, float.IsNaN(value) ? "\u2014 SS"u8 : "%.0f%% SS"u8, 0f, (float)Half.MaxValue * 100f, 0.05f, SliderFlags.AlwaysClamp))
return UnsetBehavior(ref value, canUnset);
var tmp2 = Math.Clamp(tmp, 0f, (float)Half.MaxValue * 100f) / 100f;
if (tmp2 == value)
return false;
@ -469,6 +545,102 @@ public sealed unsafe class AdvancedDyePopup(
return true;
}
public static bool DragRoughness(ref float value, bool canUnset)
{
var tmp = (float.IsNaN(value) ? ColorRow.DefaultRoughness : value) * 100f;
if (!Im.Drag("##Roughness"u8, ref tmp, float.IsNaN(value) ? "\u2014 Rg"u8 : "%.0f%% Rg"u8, 0f, 100f, 0.25f, SliderFlags.AlwaysClamp))
return UnsetBehavior(ref value, canUnset);
var tmp2 = Math.Clamp(tmp, 0f, 100f) / 100f;
if (tmp2 == value)
return false;
value = tmp2;
return true;
}
public static bool DragRoughnessAsGloss(ref float value, bool canUnset)
{
var gloss = ColorTableRow.ShininessFromRoughness(float.IsNaN(value) ? ColorRow.DefaultRoughness : value);
var tmp = gloss;
if (!Im.Drag("##Roughness"u8, ref tmp, float.IsNaN(value) ? "\u2014 G"u8 : "%.1f G"u8, 0.001f, (float)Half.Epsilon, Math.Max(0.01f, 0.005f * gloss), SliderFlags.AlwaysClamp))
return UnsetBehavior(ref value, canUnset);
var tmp2 = Math.Clamp(tmp, (float)Half.Epsilon, (float)Half.MaxValue);
if (tmp2 == gloss)
return false;
value = ColorTableRow.RoughnessFromShininess(tmp2);
return true;
}
public static bool DragMetalness(ref float value, bool canUnset)
{
var tmp = (float.IsNaN(value) ? ColorRow.DefaultMetalness : value) * 100f;
if (!Im.Drag("##Metalness"u8, ref tmp, float.IsNaN(value) ? "\u2014 Mt"u8 : "%.0f%% Mt"u8, 0f, 100f, 0.25f, SliderFlags.AlwaysClamp))
return UnsetBehavior(ref value, canUnset);
var tmp2 = Math.Clamp(tmp, 0f, 100f) / 100f;
if (tmp2 == value)
return false;
value = tmp2;
return true;
}
public static bool DragSheen(ref float value, bool canUnset)
{
var tmp = (float.IsNaN(value) ? ColorRow.DefaultSheen : value) * 100f;
if (!Im.Drag("##Sheen"u8, ref tmp, float.IsNaN(value) ? "\u2014 Sh"u8 : "%.0f%% Sh"u8, 0f, 100f * (float)Half.MaxValue, 0.25f, SliderFlags.AlwaysClamp))
return UnsetBehavior(ref value, canUnset);
var tmp2 = Math.Clamp(tmp, 0f, 100f * (float)Half.MaxValue) / 100f;
if (tmp2 == value)
return false;
value = tmp2;
return true;
}
public static bool DragSheenTint(ref float value, bool canUnset)
{
var tmp = (float.IsNaN(value) ? ColorRow.DefaultSheenTint : value) * 100f;
if (!Im.Drag("##SheenTint"u8, ref tmp, float.IsNaN(value) ? "\u2014 ST"u8 : "%.0f%% ST"u8, -100f * (float)Half.MaxValue, 100f * (float)Half.MaxValue, 0.25f,
SliderFlags.AlwaysClamp))
return UnsetBehavior(ref value, canUnset);
var tmp2 = Math.Clamp(tmp, -100f * (float)Half.MaxValue, 100f * (float)Half.MaxValue) / 100f;
if (tmp2 == value)
return false;
value = tmp2;
return true;
}
public static bool DragSheenRoughness(ref float value, bool canUnset)
{
var tmp = 100f / (float.IsNaN(value) ? ColorRow.DefaultSheenAperture : value);
if (!Im.Drag("##SheenAperture"u8, ref tmp, float.IsNaN(value) ? "\u2014 SR"u8 : "%.0f%% SR"u8, 100f / (float)Half.MaxValue, 100f / (float)Half.Epsilon, 0.25f,
SliderFlags.AlwaysClamp))
return UnsetBehavior(ref value, canUnset);
var tmp2 = Math.Clamp(100f / tmp, (float)Half.Epsilon, (float)Half.MaxValue);
if (tmp2 == value)
return false;
value = tmp2;
return true;
}
private static bool UnsetBehavior(ref float value, bool canUnset)
{
if (!(canUnset && Im.Item.RightClicked() && Im.Io.KeyControl))
return false;
value = float.NaN;
return true;
}
private LabelStruct _label = new();
private struct LabelStruct

View file

@ -18,6 +18,8 @@ public static class ColorRowClipboard
field = value;
}
}
public static ColorRow.Mode TableMode { get; set; }
public static ColorRow Row
{
@ -28,4 +30,6 @@ public static class ColorRowClipboard
field = value;
}
}
public static ColorRow.Mode RowMode { get; set; }
}

View file

@ -9,8 +9,9 @@ namespace Glamourer.Gui.Materials;
public class MaterialDrawer(DesignManager designManager, Configuration config) : IService
{
public const float GlossWidth = 100;
public const float SpecularStrengthWidth = 125;
public const float SliderWidth = 90;
public const float ModeWidth = 45;
public const float SheenSliderWidth = 75; // Should satisfy 2*Slider + Mode = 3*SheenSlider
private int _newMaterialIdx;
private int _newRowIdx;
@ -25,17 +26,17 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) :
_spacing = Im.Style.ItemInnerSpacing.X;
_buttonSize = new Vector2(Im.Style.FrameHeight);
var colorWidth = 4 * _buttonSize.X
+ (GlossWidth + SpecularStrengthWidth) * Im.Style.GlobalScale
+ 6 * _spacing
+ (SliderWidth * 2 + ModeWidth) * Im.Style.GlobalScale
+ 7 * _spacing
+ Im.Font.CalculateSize("Revert"u8).X;
DrawMultiButtons(design);
Im.Dummy(0);
Im.Separator();
Im.Dummy(0);
if (available > 1.95 * colorWidth)
if (available > 2.6f * colorWidth)
DrawSingleRow(design);
else
DrawTwoRow(design);
DrawMultipleRow(design);
DrawNew(design);
}
@ -75,7 +76,7 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) :
private void DrawName(MaterialValueIndex index)
{
using var style = ImStyleDouble.ButtonTextAlign.Push(new Vector2(0.05f, 0.5f));
ImEx.TextFramed($"{index}", new Vector2((GlossWidth + SpecularStrengthWidth) * Im.Style.GlobalScale + _spacing, 0),
ImEx.TextFramed($"{index}", new Vector2((SliderWidth * 2 + ModeWidth) * Im.Style.GlobalScale + _spacing * 2, 0),
borderColor: ImGuiColor.Text.Get());
}
@ -91,20 +92,21 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) :
Im.Line.Same(0, _spacing);
DeleteButton(design, key, ref i);
Im.Line.Same(0, _spacing);
CopyButton(value.Value);
CopyButton(value.Value, value.Mode);
Im.Line.Same(0, _spacing);
PasteButton(design, key);
Im.Line.Same(0, _spacing);
using var disabled = Im.Disabled(design.WriteProtected());
EnabledToggle(design, key, value.Enabled);
Im.Line.Same(0, _spacing);
DrawRow(design, key, value.Value, value.Revert);
DrawRow(design, key, value.Value, value.Revert, value.Mode);
DrawRowExtra(design, key, value.Value, value.Revert, value.Mode, true);
Im.Line.Same(0, _spacing);
RevertToggle(design, key, value.Revert);
}
}
private void DrawTwoRow(Design design)
private void DrawMultipleRow(Design design)
{
for (var i = 0; i < design.Materials.Count; ++i)
{
@ -116,16 +118,18 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) :
Im.Line.Same(0, _spacing);
DeleteButton(design, key, ref i);
Im.Line.Same(0, _spacing);
CopyButton(value.Value);
CopyButton(value.Value, value.Mode);
Im.Line.Same(0, _spacing);
PasteButton(design, key);
Im.Line.Same(0, _spacing);
using var disabled = Im.Disabled(design.WriteProtected());
EnabledToggle(design, key, value.Enabled);
DrawRow(design, key, value.Value, value.Revert);
DrawRow(design, key, value.Value, value.Revert, value.Mode);
Im.Line.Same(0, _spacing);
RevertToggle(design, key, value.Revert);
DrawRowExtra(design, key, value.Value, value.Revert, value.Mode, false);
Im.Separator();
}
}
@ -142,17 +146,20 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) :
--idx;
}
private void CopyButton(in ColorRow row)
private void CopyButton(in ColorRow row, ColorRow.Mode mode)
{
if (ImEx.Icon.Button(LunaStyle.ToClipboardIcon, "Export this row to your clipboard."u8))
ColorRowClipboard.Row = row;
{
ColorRowClipboard.Row = row;
ColorRowClipboard.RowMode = mode;
}
}
private void PasteButton(Design design, MaterialValueIndex index)
{
if (ImEx.Icon.Button(LunaStyle.FromClipboardIcon, "Import an exported row from your clipboard onto this row."u8,
!ColorRowClipboard.IsSet || design.WriteProtected()))
designManager.ChangeMaterialValue(design, index, ColorRowClipboard.Row);
designManager.ChangeMaterialValue(design, index, ColorRowClipboard.Row, ColorRowClipboard.RowMode);
}
private void EnabledToggle(Design design, MaterialValueIndex index, bool enabled)
@ -169,6 +176,39 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) :
"If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row."u8);
}
private void ModeToggle(Design design, MaterialValueIndex index, ColorRow.Mode mode)
{
if (Im.Button(ToCallsignString(mode), new Vector2(ModeWidth * Im.Style.GlobalScale, 0)))
designManager.ChangeMaterialMode(design, index, GetNextMode(mode));
Im.Tooltip.OnHover(ToTooltipString(mode));
return;
static ReadOnlySpan<byte> ToCallsignString(ColorRow.Mode mode)
=> mode switch
{
ColorRow.Mode.Legacy => "Lgc###mode"u8,
ColorRow.Mode.Dawntrail => "DT###mode"u8,
_ => throw new NotImplementedException(),
};
static ColorRow.Mode GetNextMode(ColorRow.Mode mode)
=> mode switch
{
ColorRow.Mode.Legacy => ColorRow.Mode.Dawntrail,
ColorRow.Mode.Dawntrail => ColorRow.Mode.Legacy,
_ => throw new NotImplementedException(),
};
static ReadOnlySpan<byte> ToTooltipString(ColorRow.Mode mode)
=> mode switch
{
ColorRow.Mode.Legacy => "This color row currently contains Legacy material parameters.\nClick this button to switch it to Dawntrail parameters."u8,
ColorRow.Mode.Dawntrail => "This color row currently contains Dawntrail material parameters.\nClick this button to switch it to Legacy parameters."u8,
_ => throw new NotImplementedException(),
};
}
public sealed class MaterialSlotCombo;
private void DrawSlotCombo()
@ -245,23 +285,76 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) :
Im.Tooltip.OnHover("Drag this to the left or right to change its value."u8);
}
private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row, bool disabled)
private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row, bool disabled, ColorRow.Mode mode)
{
var tmp = row;
using var _ = Im.Disabled(disabled);
var applied = ImEx.ColorPickerButton("##diffuse"u8, "Change the diffuse value for this row."u8, row.Diffuse, out tmp.Diffuse, 'D');
var applied = ImEx.ColorPickerButton("##diffuse"u8, "Change the diffuse color for this row."u8, row.Diffuse, out tmp.Diffuse, 'D');
Im.Line.SameInner();
applied |= ImEx.ColorPickerButton("##specular"u8, "Change the specular value for this row."u8, row.Specular, out tmp.Specular, 'S');
applied |= ImEx.ColorPickerButton("##specular"u8, "Change the specular color for this row."u8, row.Specular, out tmp.Specular, 'S');
Im.Line.SameInner();
applied |= ImEx.ColorPickerButton("##emissive"u8, "Change the emissive value for this row."u8, row.Emissive, out tmp.Emissive, 'E');
applied |= ImEx.ColorPickerButton("##emissive"u8, "Change the emissive color for this row."u8, row.Emissive, out tmp.Emissive, 'E');
Im.Line.SameInner();
Im.Item.SetNextWidth(GlossWidth * Im.Style.GlobalScale);
applied |= AdvancedDyePopup.DragGloss(ref tmp.GlossStrength);
Im.Tooltip.OnHover("Change the gloss strength for this row."u8);
ModeToggle(design, index, mode);
Im.Line.SameInner();
Im.Item.SetNextWidth(SpecularStrengthWidth * Im.Style.GlobalScale);
applied |= AdvancedDyePopup.DragSpecularStrength(ref tmp.SpecularStrength);
Im.Tooltip.OnHover("Change the specular strength for this row."u8);
Im.Item.SetNextWidth(SliderWidth * Im.Style.GlobalScale);
var editAsRoughness = config.AlwaysEditAsRoughness ?? mode is ColorRow.Mode.Dawntrail;
applied |= (mode, editAsRoughness) switch
{
(ColorRow.Mode.Legacy, false) => AdvancedDyePopup.DragGloss(ref tmp.GlossStrength, true),
(ColorRow.Mode.Legacy, true) => AdvancedDyePopup.DragGlossAsRoughness(ref tmp.GlossStrength, true),
(ColorRow.Mode.Dawntrail, false) => AdvancedDyePopup.DragRoughnessAsGloss(ref tmp.Roughness, true),
(ColorRow.Mode.Dawntrail, true) => AdvancedDyePopup.DragRoughness(ref tmp.Roughness, true),
_ => throw new NotImplementedException(),
};
Im.Tooltip.OnHover(editAsRoughness
? "Change the roughness for this row.\nControl and Right-Click to unset."u8
: "Change the gloss strength for this row.\nControl and Right-Click to unset."u8);
if (mode is ColorRow.Mode.Dawntrail)
{
Im.Line.SameInner();
Im.Item.SetNextWidth(SliderWidth * Im.Style.GlobalScale);
applied |= AdvancedDyePopup.DragMetalness(ref tmp.Metalness, true);
Im.Tooltip.OnHover("Change the metalness for this row.\nControl and Right-Click to unset."u8);
}
else
{
Im.Line.SameInner();
Im.Item.SetNextWidth(SliderWidth * Im.Style.GlobalScale);
applied |= AdvancedDyePopup.DragSpecularStrength(ref tmp.SpecularStrength, true);
Im.Tooltip.OnHover("Change the specular strength for this row.\nControl and Right-Click to unset."u8);
}
if (applied)
designManager.ChangeMaterialValue(design, index, tmp);
}
private void DrawRowExtra(Design design, MaterialValueIndex index, in ColorRow row, bool disabled, ColorRow.Mode mode, bool compact)
{
if (mode is not ColorRow.Mode.Dawntrail)
return;
var tmp = row;
using var _ = Im.Disabled(disabled);
if (!compact)
Im.Dummy(new Vector2(_buttonSize.X * 3 + _spacing * 2, _buttonSize.Y));
Im.Line.SameInner();
Im.Item.SetNextWidth(SheenSliderWidth * Im.Style.GlobalScale);
var applied = AdvancedDyePopup.DragSheen(ref tmp.Sheen, true);
Im.Tooltip.OnHover("Change the sheen strength for this row.\nControl and Right-Click to unset."u8);
Im.Line.SameInner();
Im.Item.SetNextWidth(SheenSliderWidth * Im.Style.GlobalScale);
applied |= AdvancedDyePopup.DragSheenTint(ref tmp.SheenTint, true);
Im.Tooltip.OnHover("Change the sheen tint for this row.\nControl and Right-Click to unset."u8);
Im.Line.SameInner();
Im.Item.SetNextWidth(SheenSliderWidth * Im.Style.GlobalScale);
applied |= AdvancedDyePopup.DragSheenRoughness(ref tmp.SheenAperture, true);
Im.Tooltip.OnHover("Change the sheen roughness for this row.\nControl and Right-Click to unset."u8);
if (applied)
designManager.ChangeMaterialValue(design, index, tmp);
}

View file

@ -235,6 +235,7 @@ public sealed class SettingsTab(
Checkbox("Smaller Equip Display"u8, "Use single-line display without icons and small dye buttons instead of double-line display."u8,
config.SmallEquip, v => config.SmallEquip = v);
DrawHeightUnitSettings();
DrawRoughnessSettings();
Checkbox("Show Application Checkboxes"u8,
"Show the application checkboxes in the Customization and Equipment panels of the design tab, instead of only showing them under Application Rules."u8,
!config.HideApplyCheckmarks, v => config.HideApplyCheckmarks = !v);
@ -502,4 +503,34 @@ public sealed class SettingsTab(
LunaStyle.DrawAlignedHelpMarkerLabel("Character Height Display Type"u8,
"Select how to display the height of characters in real-world units, if at all."u8);
}
private void DrawRoughnessSettings()
{
Im.Item.SetNextWidthScaled(300);
using (var combo = Im.Combo.Begin("##alwaysEditAsRoughness"u8, ToRoughnessSettingString(config.AlwaysEditAsRoughness)))
{
if (combo)
foreach (var type in (IEnumerable<bool?>)[null, true, false,])
{
if (Im.Selectable(ToRoughnessSettingString(type), config.AlwaysEditAsRoughness == type))
{
config.AlwaysEditAsRoughness = type;
config.Save();
}
}
}
LunaStyle.DrawAlignedHelpMarkerLabel("Gloss Strength and Roughness Display Type"u8,
"Select how to display and edit Gloss Strength and Roughness values.\nThe conversion formula used is an approximation and does not account for all the subtleties of legacy vs PBR shaders."u8);
return;
static ReadOnlySpan<byte> ToRoughnessSettingString(bool? alwaysEditAsRoughness)
=> alwaysEditAsRoughness switch
{
null => "As-Is"u8,
true => "Always Roughness"u8,
false => "Always Gloss Strength"u8,
};
}
}

View file

@ -80,7 +80,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
var idx = MaterialValueIndex.FromKey(values[i].Key);
var materialValue = values[i].Value;
ref var row = ref colorTable[idx.RowIndex];
var newGame = new ColorRow(row);
var newGame = ColorRow.From(row, mode);
if (materialValue.EqualGame(newGame, drawData))
materialValue.Model.Apply(ref row, mode);
else

View file

@ -11,7 +11,7 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Interop.Material;
/// <summary> Values are not squared. </summary>
public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, float specularStrength, float glossStrength)
public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, float specularStrength, float glossStrength, float roughness, float metalness, float sheen, float sheenTint, float sheenAperture)
{
public enum Mode
{
@ -19,26 +19,50 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa
Dawntrail,
}
public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, 1f, 1f);
public const float DefaultSpecularStrength = 1f;
public const float DefaultGlossStrength = 20f;
public const float DefaultRoughness = 0.5f;
public const float DefaultMetalness = 0f;
public const float DefaultSheen = 0.1f;
public const float DefaultSheenTint = 0.2f;
public const float DefaultSheenAperture = 5f;
public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, float.NaN, float.NaN, float.NaN, float.NaN, float.NaN,
float.NaN, float.NaN);
public Vector3 Diffuse = diffuse;
public Vector3 Specular = specular;
public Vector3 Emissive = emissive;
public float SpecularStrength = specularStrength;
public float GlossStrength = glossStrength;
public float Roughness = roughness;
public float Metalness = metalness;
public float Sheen = sheen;
public float SheenTint = sheenTint;
public float SheenAperture = sheenAperture;
public ColorRow(in ColorTableRow row)
: this(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor),
(float)row.LegacySpecularStrength(),
(float)row.LegacyGloss())
{ }
public static ColorRow From(in ColorTableRow row, Mode mode)
=> mode switch
{
Mode.Legacy => new(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor),
(float)row.LegacySpecularStrength(), (float)row.LegacyGloss(), float.NaN, float.NaN, float.NaN, float.NaN, float.NaN),
Mode.Dawntrail => new(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor),
float.NaN, float.NaN, (float)row.DawntrailRoughness(), (float)row.DawntrailMetalness(), (float)row.DawntrailSheen(),
(float)row.DawntrailSheenTint(), (float)row.DawntrailSheenAperture()),
_ => throw new NotImplementedException(),
};
public readonly bool NearEqual(in ColorRow rhs)
public readonly bool NearEqual(in ColorRow rhs, bool skipEmpty = false)
=> Diffuse.NearEqual(rhs.Diffuse)
&& Specular.NearEqual(rhs.Specular)
&& Emissive.NearEqual(rhs.Emissive)
&& SpecularStrength.NearEqual(rhs.SpecularStrength)
&& GlossStrength.NearEqual(rhs.GlossStrength);
&& (float.IsNaN(SpecularStrength) ? skipEmpty || float.IsNaN(rhs.SpecularStrength) : SpecularStrength.NearEqual(rhs.SpecularStrength))
&& (float.IsNaN(GlossStrength) ? skipEmpty || float.IsNaN(rhs.GlossStrength) : GlossStrength.NearEqual(rhs.GlossStrength))
&& (float.IsNaN(Roughness) ? skipEmpty || float.IsNaN(rhs.Roughness) : Roughness.NearEqual(rhs.Roughness))
&& (float.IsNaN(Metalness) ? skipEmpty || float.IsNaN(rhs.Metalness) : Metalness.NearEqual(rhs.Metalness))
&& (float.IsNaN(Sheen) ? skipEmpty || float.IsNaN(rhs.Sheen) : Sheen.NearEqual(rhs.Sheen))
&& (float.IsNaN(SheenAperture) ? skipEmpty || float.IsNaN(rhs.SheenAperture) : SheenAperture.NearEqual(rhs.SheenAperture))
&& (float.IsNaN(SheenTint) ? skipEmpty || float.IsNaN(rhs.SheenTint) : SheenTint.NearEqual(rhs.SheenTint));
private static Vector3 Square(Vector3 value)
=> new(Square(value.X), Square(value.Y), Square(value.Z));
@ -76,23 +100,85 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa
ret = true;
}
if (mode is Mode.Legacy)
switch (mode)
{
if (!((float)row.LegacySpecularStrength()).NearEqual(SpecularStrength))
{
row.LegacySpecularStrengthWrite() = (Half)SpecularStrength;
ret = true;
}
case Mode.Legacy:
if (!float.IsNaN(SpecularStrength) && !((float)row.LegacySpecularStrength()).NearEqual(SpecularStrength))
{
row.LegacySpecularStrengthWrite() = (Half)SpecularStrength;
ret = true;
}
if (!((float)row.LegacyGloss()).NearEqual(GlossStrength))
{
row.LegacyGlossWrite() = (Half)GlossStrength;
ret = true;
}
if (!float.IsNaN(GlossStrength) && !((float)row.LegacyGloss()).NearEqual(GlossStrength))
{
row.LegacyGlossWrite() = (Half)GlossStrength;
ret = true;
}
break;
case Mode.Dawntrail:
if (!float.IsNaN(Roughness) && !((float)row.DawntrailRoughness()).NearEqual(Roughness))
{
row.DawntrailRoughnessWrite() = (Half)Roughness;
ret = true;
}
if (!float.IsNaN(Metalness) && !((float)row.DawntrailMetalness()).NearEqual(Metalness))
{
row.DawntrailMetalnessWrite() = (Half)Metalness;
ret = true;
}
if (!float.IsNaN(Sheen) && !((float)row.DawntrailSheen()).NearEqual(Sheen))
{
row.DawntrailSheenWrite() = (Half)Sheen;
ret = true;
}
if (!float.IsNaN(SheenAperture) && !((float)row.DawntrailSheenAperture()).NearEqual(SheenAperture))
{
row.DawntrailSheenApertureWrite() = (Half)SheenAperture;
ret = true;
}
if (!float.IsNaN(SheenTint) && !((float)row.DawntrailSheenTint()).NearEqual(SheenTint))
{
row.DawntrailSheenTintWrite() = (Half)SheenTint;
ret = true;
}
break;
default: throw new NotImplementedException();
}
return ret;
}
public readonly ColorRow MergeOnto(ColorRow previous)
=> new(Diffuse, Specular, Emissive, float.IsNaN(SpecularStrength) ? previous.SpecularStrength : SpecularStrength,
float.IsNaN(GlossStrength) ? previous.GlossStrength : GlossStrength, float.IsNaN(Roughness) ? previous.Roughness : Roughness,
float.IsNaN(Metalness) ? previous.Metalness : Metalness, float.IsNaN(Sheen) ? previous.Sheen : Sheen,
float.IsNaN(SheenTint) ? previous.SheenTint : SheenTint, float.IsNaN(SheenAperture) ? previous.SheenAperture : SheenAperture);
public readonly bool IsPartial(Mode mode)
=> mode switch
{
Mode.Legacy => float.IsNaN(SpecularStrength) || float.IsNaN(GlossStrength),
Mode.Dawntrail => float.IsNaN(Roughness)
|| float.IsNaN(Metalness)
|| float.IsNaN(Sheen)
|| float.IsNaN(SheenTint)
|| float.IsNaN(SheenAperture),
_ => throw new NotImplementedException(),
};
public readonly Mode GuessMode()
=> float.IsNaN(Roughness) && float.IsNaN(Metalness) && float.IsNaN(Sheen) && float.IsNaN(SheenTint) && float.IsNaN(SheenAperture)
? Mode.Legacy
: Mode.Dawntrail;
public override readonly string ToString()
=> $"[ColorRow Diffuse={Diffuse} Specular={Specular} Emissive={Emissive} SpecularStrength={SpecularStrength} GlossStrength={GlossStrength} Roughness={Roughness} Metalness={Metalness} Sheen={Sheen} SheenTint={SheenTint} SheenAperture={SheenAperture}]";
}
internal static class ColorTableRowExtensions
@ -103,19 +189,50 @@ internal static class ColorTableRowExtensions
internal static Half LegacyGloss(this in ColorTableRow row)
=> row[3];
internal static Half DawntrailSheen(this in ColorTableRow row)
=> row[12];
internal static Half DawntrailSheenTint(this in ColorTableRow row)
=> row[13];
internal static Half DawntrailSheenAperture(this in ColorTableRow row)
=> row[14];
internal static Half DawntrailRoughness(this in ColorTableRow row)
=> row[16];
internal static Half DawntrailMetalness(this in ColorTableRow row)
=> row[18];
internal static ref Half LegacySpecularStrengthWrite(this ref ColorTableRow row)
=> ref row[7];
internal static ref Half LegacyGlossWrite(this ref ColorTableRow row)
=> ref row[3];
internal static ref Half DawntrailSheenWrite(this ref ColorTableRow row)
=> ref row[12];
internal static ref Half DawntrailSheenTintWrite(this ref ColorTableRow row)
=> ref row[13];
internal static ref Half DawntrailSheenApertureWrite(this ref ColorTableRow row)
=> ref row[14];
internal static ref Half DawntrailRoughnessWrite(this ref ColorTableRow row)
=> ref row[16];
internal static ref Half DawntrailMetalnessWrite(this ref ColorTableRow row)
=> ref row[18];
}
[JsonConverter(typeof(Converter))]
public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert)
public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert, ColorRow.Mode mode)
{
public ColorRow Value = value;
public bool Enabled = enabled;
public bool Revert = revert;
public ColorRow Value = value;
public bool Enabled = enabled;
public bool Revert = revert;
public ColorRow.Mode Mode = mode;
public readonly bool Apply(ref MaterialValueState state)
{
@ -145,6 +262,8 @@ public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert)
writer.WriteStartObject();
writer.WritePropertyName("Revert");
writer.WriteValue(value.Revert);
writer.WritePropertyName("Mode");
writer.WriteValue(value.Mode.ToString());
writer.WritePropertyName("DiffuseR");
writer.WriteValue(value.Value.Diffuse.X);
writer.WritePropertyName("DiffuseG");
@ -157,16 +276,54 @@ public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert)
writer.WriteValue(value.Value.Specular.Y);
writer.WritePropertyName("SpecularB");
writer.WriteValue(value.Value.Specular.Z);
writer.WritePropertyName("SpecularA");
writer.WriteValue(value.Value.SpecularStrength);
if (!float.IsNaN(value.Value.SpecularStrength))
{
writer.WritePropertyName("SpecularA");
writer.WriteValue(value.Value.SpecularStrength);
}
writer.WritePropertyName("EmissiveR");
writer.WriteValue(value.Value.Emissive.X);
writer.WritePropertyName("EmissiveG");
writer.WriteValue(value.Value.Emissive.Y);
writer.WritePropertyName("EmissiveB");
writer.WriteValue(value.Value.Emissive.Z);
writer.WritePropertyName("Gloss");
writer.WriteValue(value.Value.GlossStrength);
if (!float.IsNaN(value.Value.GlossStrength))
{
writer.WritePropertyName("Gloss");
writer.WriteValue(value.Value.GlossStrength);
}
if (!float.IsNaN(value.Value.Roughness))
{
writer.WritePropertyName("Roughness");
writer.WriteValue(value.Value.Roughness);
}
if (!float.IsNaN(value.Value.Metalness))
{
writer.WritePropertyName("Metalness");
writer.WriteValue(value.Value.Metalness);
}
if (!float.IsNaN(value.Value.Sheen))
{
writer.WritePropertyName("Sheen");
writer.WriteValue(value.Value.Sheen);
}
if (!float.IsNaN(value.Value.SheenTint))
{
writer.WritePropertyName("SheenTint");
writer.WriteValue(value.Value.SheenTint);
}
if (!float.IsNaN(value.Value.SheenAperture))
{
writer.WritePropertyName("SheenAperture");
writer.WriteValue(value.Value.SheenAperture);
}
writer.WritePropertyName("Enabled");
writer.WriteValue(value.Enabled);
writer.WriteEndObject();
@ -177,19 +334,26 @@ public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert)
JsonSerializer serializer)
{
var obj = JObject.Load(reader);
Set(ref existingValue.Revert, obj["Revert"]?.Value<bool>());
Set(ref existingValue.Value.Diffuse.X, obj["DiffuseR"]?.Value<float>());
Set(ref existingValue.Value.Diffuse.Y, obj["DiffuseG"]?.Value<float>());
Set(ref existingValue.Value.Diffuse.Z, obj["DiffuseB"]?.Value<float>());
Set(ref existingValue.Value.Specular.X, obj["SpecularR"]?.Value<float>());
Set(ref existingValue.Value.Specular.Y, obj["SpecularG"]?.Value<float>());
Set(ref existingValue.Value.Specular.Z, obj["SpecularB"]?.Value<float>());
Set(ref existingValue.Value.SpecularStrength, obj["SpecularA"]?.Value<float>());
Set(ref existingValue.Value.Emissive.X, obj["EmissiveR"]?.Value<float>());
Set(ref existingValue.Value.Emissive.Y, obj["EmissiveG"]?.Value<float>());
Set(ref existingValue.Value.Emissive.Z, obj["EmissiveB"]?.Value<float>());
Set(ref existingValue.Value.GlossStrength, obj["Gloss"]?.Value<float>());
existingValue.Enabled = obj["Enabled"]?.Value<bool>() ?? false;
if (obj["Mode"]?.Value<string>() is { } mode)
Enum.TryParse(mode, true, out existingValue.Mode);
Set(ref existingValue.Revert, obj["Revert"]?.Value<bool>());
Set(ref existingValue.Value.Diffuse.X, obj["DiffuseR"]?.Value<float>());
Set(ref existingValue.Value.Diffuse.Y, obj["DiffuseG"]?.Value<float>());
Set(ref existingValue.Value.Diffuse.Z, obj["DiffuseB"]?.Value<float>());
Set(ref existingValue.Value.Specular.X, obj["SpecularR"]?.Value<float>());
Set(ref existingValue.Value.Specular.Y, obj["SpecularG"]?.Value<float>());
Set(ref existingValue.Value.Specular.Z, obj["SpecularB"]?.Value<float>());
Set(ref existingValue.Value.Emissive.X, obj["EmissiveR"]?.Value<float>());
Set(ref existingValue.Value.Emissive.Y, obj["EmissiveG"]?.Value<float>());
Set(ref existingValue.Value.Emissive.Z, obj["EmissiveB"]?.Value<float>());
existingValue.Value.SpecularStrength = obj["SpecularA"]?.Value<float>() ?? float.NaN;
existingValue.Value.GlossStrength = obj["Gloss"]?.Value<float>() ?? float.NaN;
existingValue.Value.Roughness = obj["Roughness"]?.Value<float>() ?? float.NaN;
existingValue.Value.Metalness = obj["Metalness"]?.Value<float>() ?? float.NaN;
existingValue.Value.Sheen = obj["Sheen"]?.Value<float>() ?? float.NaN;
existingValue.Value.SheenTint = obj["SheenTint"]?.Value<float>() ?? float.NaN;
existingValue.Value.SheenAperture = obj["SheenAperture"]?.Value<float>() ?? float.NaN;
existingValue.Enabled = obj["Enabled"]?.Value<bool>() ?? false;
return existingValue;
static void Set<T>(ref T target, T? value)
@ -222,10 +386,13 @@ public struct MaterialValueState(
&& DrawData.Weapon == rhsData.Weapon
&& DrawData.Variant == rhsData.Variant
&& DrawData.Stains == rhsData.Stains
&& Game.NearEqual(rhsRow);
&& rhsRow.NearEqual(Game, true);
public readonly MaterialValueDesign Convert()
=> new(Model, true, false);
=> new(Model, true, false, Model.GuessMode());
public readonly MaterialValueState MergeOnto(in ColorRow previous)
=> new(Game, Model.MergeOnto(previous), DrawData, Source);
}
public readonly struct MaterialValueManager<T>

View file

@ -253,7 +253,7 @@ public class InternalStateEditor(
}
// Update if edited.
state.Materials.UpdateValue(index, newValue, out _);
state.Materials.UpdateValue(index, newValue.MergeOnto(old.Model), out _);
return true;
}

View file

@ -231,7 +231,7 @@ public class StateEditor(
Glamourer.Log.Verbose(
$"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors,
new MaterialTransaction(index, oldValue, newValue.Game));
new MaterialTransaction(index, oldValue, newValue.Game, null, null));
}
public void ResetMaterialValue(object data, MaterialValueIndex index, ApplySettings settings)
@ -243,7 +243,8 @@ public class StateEditor(
var actors = Applier.ChangeMaterialValue(state, index, true);
Glamourer.Log.Verbose(
$"Reset material value in state {state.Identifier.Incognito(null)} to game value. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, new MaterialTransaction(index, null, null));
StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors,
new MaterialTransaction(index, null, null, null, null));
}
/// <inheritdoc/>