Change mousewheel to ctrl, current material state.

This commit is contained in:
Ottermandias 2024-02-06 16:42:43 +01:00
parent 42ac507b86
commit b5b9289dc2
20 changed files with 537 additions and 296 deletions

View file

@ -72,6 +72,7 @@ public sealed class Design : DesignBase, ISavable
["Equipment"] = SerializeEquipment(),
["Customize"] = SerializeCustomize(),
["Parameters"] = SerializeParameters(),
["Materials"] = SerializeMaterials(),
["Mods"] = SerializeMods(),
["Links"] = Links.Serialize(),
};
@ -136,6 +137,7 @@ public sealed class Design : DesignBase, ISavable
LoadEquip(items, json["Equipment"], design, design.Name, true);
LoadMods(json["Mods"], design);
LoadParameters(json["Parameters"], design, design.Name);
LoadMaterials(json["Materials"], design, design.Name);
LoadLinks(linkLoader, json["Links"], design);
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
return design;

View file

@ -124,7 +124,6 @@ public class DesignBase
_writeProtected = value;
return true;
}
public bool DoApplyEquip(EquipSlot slot)
@ -244,6 +243,7 @@ public class DesignBase
["Equipment"] = SerializeEquipment(),
["Customize"] = SerializeCustomize(),
["Parameters"] = SerializeParameters(),
["Materials"] = SerializeMaterials(),
};
return ret;
}
@ -362,6 +362,45 @@ public class DesignBase
return ret;
}
protected JObject SerializeMaterials()
{
var ret = new JObject();
foreach (var (key, value) in Materials)
ret[key.ToString("X16")] = JToken.FromObject(value);
return ret;
}
protected static void LoadMaterials(JToken? materials, DesignBase design, string name)
{
if (materials is not JObject obj)
return;
design.GetMaterialDataRef().Clear();
foreach (var (key, value) in obj.Properties().Zip(obj.PropertyValues()))
{
try
{
var k = uint.Parse(key.Name, NumberStyles.HexNumber);
var v = value.ToObject<MaterialValueDesign>();
if (!MaterialValueIndex.FromKey(k, out var idx))
{
Glamourer.Messager.NotificationMessage($"Invalid material value key {k} for design {name}, skipped.",
NotificationType.Warning);
continue;
}
if (!design.GetMaterialDataRef().TryAddValue(MaterialValueIndex.FromKey(k), v))
Glamourer.Messager.NotificationMessage($"Duplicate material value key {k} for design {name}, skipped.",
NotificationType.Warning);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Error parsing material value for design {name}, skipped",
NotificationType.Warning);
}
}
}
#endregion
#region Deserialization
@ -382,6 +421,7 @@ public class DesignBase
LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true);
LoadEquip(items, json["Equipment"], ret, "Temporary Design", true);
LoadParameters(json["Parameters"], ret, "Temporary Design");
LoadMaterials(json["Materials"], ret, "Temporary Design");
return ret;
}

View file

@ -1,4 +1,5 @@
using Glamourer.Designs.Links;
using Glamourer.Interop.Material;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Utility;
@ -6,6 +7,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.GameData.Structs;
namespace Glamourer.Designs;
@ -38,22 +40,23 @@ public class DesignConverter(
=> ShareBase64(ShareJObject(design));
public string ShareBase64(ActorState state, in ApplicationRules rules)
=> ShareBase64(state.ModelData, rules);
=> ShareBase64(state.ModelData, state.Materials, rules);
public string ShareBase64(in DesignData data, in ApplicationRules rules)
public string ShareBase64(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules)
{
var design = Convert(data, rules);
var design = Convert(data, materials, rules);
return ShareBase64(ShareJObject(design));
}
public DesignBase Convert(ActorState state, in ApplicationRules rules)
=> Convert(state.ModelData, rules);
=> Convert(state.ModelData, state.Materials, rules);
public DesignBase Convert(in DesignData data, in ApplicationRules rules)
public DesignBase Convert(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules)
{
var design = _designs.CreateTemporary();
rules.Apply(design);
design.SetDesignData(_customize, data);
ComputeMaterials(design.GetMaterialDataRef(), materials, rules.Equip);
return design;
}
@ -181,4 +184,29 @@ public class DesignConverter(
yield return (EquipSlot.OffHand, oh, offhand.Stain);
}
private static void ComputeMaterials(DesignMaterialManager manager, in StateMaterialManager materials,
EquipFlag equipFlags = EquipFlagExtensions.All)
{
foreach (var (key, value) in materials.Values)
{
var idx = MaterialValueIndex.FromKey(key);
if (idx.RowIndex >= MtrlFile.ColorTable.NumRows)
continue;
if (idx.MaterialIndex >= MaterialService.MaterialsPerModel)
continue;
var slot = idx.DrawObject switch
{
MaterialValueIndex.DrawObjectType.Human => idx.SlotIndex < 10 ? ((uint)idx.SlotIndex).ToEquipSlot() : EquipSlot.Unknown,
MaterialValueIndex.DrawObjectType.Mainhand when idx.SlotIndex == 0 => EquipSlot.MainHand,
MaterialValueIndex.DrawObjectType.Offhand when idx.SlotIndex == 0 => EquipSlot.OffHand,
_ => EquipSlot.Unknown,
};
if (slot is EquipSlot.Unknown || (slot.ToBothFlags() & equipFlags) == 0)
continue;
manager.AddOrUpdateValue(idx, value.Convert());
}
}
}

View file

@ -1,6 +1,7 @@
using Glamourer.Designs.Links;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop.Material;
using Glamourer.Services;
using Glamourer.State;
using Penumbra.GameData.Enums;
@ -250,6 +251,14 @@ public class DesignEditor(
foreach (var parameter in CustomizeParameterExtensions.AllFlags.Where(other.DoApplyParameter))
ChangeCustomizeParameter(design, parameter, other.DesignData.Parameters[parameter]);
foreach (var (key, value) in other.Materials)
{
if (!value.Enabled)
continue;
design.GetMaterialDataRef().AddOrUpdateValue(MaterialValueIndex.FromKey(key), value);
}
}
/// <summary> Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. </summary>

View file

@ -62,4 +62,11 @@ public static class VectorExtensions
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
public static bool NearEqual(this CustomizeParameterValue lhs, CustomizeParameterValue rhs, float eps = 1e-9f)
=> NearEqual(lhs.InternalQuadruple, rhs.InternalQuadruple, eps);
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
public static bool NearEqual(this float lhs, float rhs, float eps = 1e-5f)
{
var diff = lhs - rhs;
return diff < 0 ? diff > -eps : diff < eps;
}
}

View file

@ -207,7 +207,7 @@ public partial class CustomizationDrawer
private static bool CaptureMouseWheel(ref int value, int offset, int cap)
{
if (!ImGui.IsItemHovered())
if (!ImGui.IsItemHovered() || !ImGui.GetIO().KeyCtrl)
return false;
ImGuiInternal.ItemSetUsingMouseWheel();

View file

@ -10,7 +10,7 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Equipment;
public sealed class GlamourerColorCombo(float _comboWidth, DictStain _stains, FavoriteManager _favorites)
: FilterComboColors(_comboWidth, MouseWheelType.Unmodified, CreateFunc(_stains, _favorites), Glamourer.Log)
: FilterComboColors(_comboWidth, MouseWheelType.Control, CreateFunc(_stains, _favorites), Glamourer.Log)
{
protected override bool DrawSelectable(int globalIdx, bool selected)
{

View file

@ -24,7 +24,7 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
public Variant CustomVariant { get; private set; }
public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites)
: base(() => GetItems(favorites, items, slot), MouseWheelType.Unmodified, log)
: base(() => GetItems(favorites, items, slot), MouseWheelType.Control, log)
{
_favorites = favorites;
Label = GetLabel(gameData, slot);

View file

@ -17,7 +17,7 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
private float _innerWidth;
public WeaponCombo(ItemManager items, FullEquipType type, Logger log)
: base(() => GetWeapons(items, type), MouseWheelType.Unmodified, log)
: base(() => GetWeapons(items, type), MouseWheelType.Control, log)
{
Label = GetLabel(type);
SearchByParts = true;

View file

@ -1,18 +1,20 @@
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Designs;
using Glamourer.Interop.Material;
using Glamourer.Interop.Structs;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
using OtterGui.Services;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Materials;
public unsafe class MaterialDrawer(StateManager _stateManager) : IService
public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _designManager) : IService
{
private static readonly IReadOnlyList<MaterialValueIndex.DrawObjectType> Types =
[
@ -23,135 +25,94 @@ public unsafe class MaterialDrawer(StateManager _stateManager) : IService
private ActorState? _state;
public void DrawPanel(Actor actor)
public void DrawActorPanel(Actor actor)
{
if (!actor.IsCharacter || !_stateManager.GetOrCreate(actor, out _state))
return;
foreach (var type in Types)
{
var index = new MaterialValueIndex(type, 0, 0, 0, 0);
if (index.TryGetModel(actor, out var model))
DrawModelType(model, index);
}
}
private void DrawModelType(Model model, MaterialValueIndex sourceIndex)
{
using var tree = ImRaii.TreeNode(sourceIndex.DrawObject.ToString());
if (!tree)
var model = actor.Model;
if (!model.IsHuman)
return;
var names = model.AsCharacterBase->GetModelType() is CharacterBase.ModelType.Human
? SlotNamesHuman
: SlotNames;
for (byte i = 0; i < model.AsCharacterBase->SlotCount; ++i)
{
var index = sourceIndex with { SlotIndex = i };
DrawSlot(model, names, index);
}
}
private void DrawSlot(Model model, IReadOnlyList<string> names, MaterialValueIndex sourceIndex)
{
using var tree = ImRaii.TreeNode(names[sourceIndex.SlotIndex]);
if (!tree)
if (model.AsCharacterBase->SlotCount < 10)
return;
for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i)
// Humans should have at least 10 slots for the equipment types. Technically more.
foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex())
{
var index = sourceIndex with { MaterialIndex = i };
var texture = model.AsCharacterBase->ColorTableTextures + index.SlotIndex * MaterialService.MaterialsPerModel + i;
var item = model.GetArmor(slot).ToWeapon(0);
DrawSlotMaterials(model, slot.ToName(), item, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte) idx, 0, 0));
}
var (mainhand, offhand, mh, oh) = actor.Model.GetWeapons(actor);
if (mainhand.IsWeapon && mainhand.AsCharacterBase->SlotCount > 0)
DrawSlotMaterials(mainhand, EquipSlot.MainHand.ToName(), mh, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, 0, 0));
if (offhand.IsWeapon && offhand.AsCharacterBase->SlotCount > 0)
DrawSlotMaterials(offhand, EquipSlot.OffHand.ToName(), oh, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, 0, 0));
}
private void DrawSlotMaterials(Model model, string name, CharacterWeapon drawData, MaterialValueIndex index)
{
var drawnMaterial = 1;
for (byte materialIndex = 0; materialIndex < MaterialService.MaterialsPerModel; ++materialIndex)
{
var texture = model.AsCharacterBase->ColorTableTextures + index.SlotIndex * MaterialService.MaterialsPerModel + materialIndex;
if (*texture == null)
continue;
if (!DirectXTextureHelper.TryGetColorTable(*texture, out var table))
continue;
DrawMaterial(ref table, index);
}
}
private void DrawMaterial(ref MtrlFile.ColorTable table, MaterialValueIndex sourceIndex)
{
using var tree = ImRaii.TreeNode($"Material {sourceIndex.MaterialIndex + 1}");
using var tree = ImRaii.TreeNode($"{name} Material #{drawnMaterial++}###{name}{materialIndex}");
if (!tree)
return;
continue;
DrawMaterial(ref table, drawData, index with { MaterialIndex = materialIndex} );
}
}
private void DrawMaterial(ref MtrlFile.ColorTable table, CharacterWeapon drawData, MaterialValueIndex sourceIndex)
{
for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
{
var index = sourceIndex with { RowIndex = i };
ref var row = ref table[i];
DrawRow(ref row, index);
DrawRow(ref row, drawData, index);
}
}
private void DrawRow(ref MtrlFile.ColorTable.Row row, MaterialValueIndex sourceIndex)
private void DrawRow(ref MtrlFile.ColorTable.Row row, CharacterWeapon drawData, MaterialValueIndex index)
{
var r = _state!.Materials.GetValues(
MaterialValueIndex.Min(sourceIndex.DrawObject, sourceIndex.SlotIndex, sourceIndex.MaterialIndex, sourceIndex.RowIndex),
MaterialValueIndex.Max(sourceIndex.DrawObject, sourceIndex.SlotIndex, sourceIndex.MaterialIndex, sourceIndex.RowIndex));
var highlightColor = ColorId.FavoriteStarOn.Value();
using var id = ImRaii.PushId(sourceIndex.RowIndex);
var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Diffuse };
var (diffuse, diffuseGame, changed) = MaterialValueManager.GetSpecific(r, index, out var d)
? (d.Model, d.Game, true)
: (row.Diffuse, row.Diffuse, false);
using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed))
using var id = ImRaii.PushId(index.RowIndex);
var changed = _state!.Materials.TryGetValue(index, out var value);
if (!changed)
{
if (ImGui.ColorEdit3("Diffuse", ref diffuse, ImGuiColorEditFlags.NoInputs))
_stateManager.ChangeMaterialValue(_state!, index, diffuse, diffuseGame, ApplySettings.Manual);
var internalRow = new ColorRow(row);
value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual);
}
index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Specular };
(var specular, var specularGame, changed) = MaterialValueManager.GetSpecific(r, index, out var s)
? (s.Model, s.Game, true)
: (row.Specular, row.Specular, false);
var applied = ImGui.ColorEdit3("Diffuse", ref value.Model.Diffuse, ImGuiColorEditFlags.NoInputs);
ImGui.SameLine();
using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed))
{
if (ImGui.ColorEdit3("Specular", ref specular, ImGuiColorEditFlags.NoInputs))
_stateManager.ChangeMaterialValue(_state!, index, specular, specularGame, ApplySettings.Manual);
}
index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Emissive };
(var emissive, var emissiveGame, changed) = MaterialValueManager.GetSpecific(r, index, out var e)
? (e.Model, e.Game, true)
: (row.Emissive, row.Emissive, false);
applied |= ImGui.ColorEdit3("Specular", ref value.Model.Specular, ImGuiColorEditFlags.NoInputs);
ImGui.SameLine();
using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed))
{
if (ImGui.ColorEdit3("Emissive", ref emissive, ImGuiColorEditFlags.NoInputs))
_stateManager.ChangeMaterialValue(_state!, index, emissive, emissiveGame, ApplySettings.Manual);
}
index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.GlossStrength };
(var glossStrength, var glossStrengthGame, changed) = MaterialValueManager.GetSpecific(r, index, out var g)
? (g.Model.X, g.Game.X, true)
: (row.GlossStrength, row.GlossStrength, false);
applied |= ImGui.ColorEdit3("Emissive", ref value.Model.Emissive, ImGuiColorEditFlags.NoInputs);
ImGui.SameLine();
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed))
{
if (ImGui.DragFloat("Gloss", ref glossStrength, 0.1f))
_stateManager.ChangeMaterialValue(_state!, index, new Vector3(glossStrength), new Vector3(glossStrengthGame),
ApplySettings.Manual);
}
index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.SpecularStrength };
(var specularStrength, var specularStrengthGame, changed) = MaterialValueManager.GetSpecific(r, index, out var ss)
? (ss.Model.X, ss.Game.X, true)
: (row.SpecularStrength, row.SpecularStrength, false);
applied |= ImGui.DragFloat("Gloss", ref value.Model.GlossStrength, 0.1f);
ImGui.SameLine();
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed))
applied |= ImGui.DragFloat("Specular Strength", ref value.Model.SpecularStrength, 0.1f);
if (applied)
_stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual);
if (changed)
{
if (ImGui.DragFloat("Specular Strength", ref specularStrength, 0.1f))
_stateManager.ChangeMaterialValue(_state!, index, new Vector3(specularStrength), new Vector3(specularStrengthGame),
ApplySettings.Manual);
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.IconFont))
{
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value());
ImGui.TextUnformatted(FontAwesomeIcon.UserEdit.ToIconString());
}
}
}

View file

@ -119,7 +119,7 @@ public class ActorPanel(
if (ImGui.CollapsingHeader("Material Shit"))
_materialDrawer.DrawPanel(_actor);
_materialDrawer.DrawActorPanel(_actor);
using var disabled = ImRaii.Disabled(transformationId != 0);
if (_state.ModelData.IsHuman)
DrawHumanPanel();

View file

@ -10,7 +10,7 @@ public sealed class DesignColorCombo(DesignColors _designColors, bool _skipAutom
FilterComboCache<string>(_skipAutomatic
? _designColors.Keys.OrderBy(k => k)
: _designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName),
MouseWheelType.Shift, Glamourer.Log)
MouseWheelType.Control, Glamourer.Log)
{
protected override void OnMouseWheel(string preview, ref int current, int steps)
{

View file

@ -174,6 +174,25 @@ public class DesignPanel(
_parameterDrawer.Draw(_manager, _selector.Selected!);
}
private void DrawMaterialValues()
{
if (!_config.UseAdvancedParameters)
return;
using var h = ImRaii.CollapsingHeader("Advanced Dyes");
if (!h)
return;
foreach (var ((key, value), i) in _selector.Selected!.Materials.WithIndex())
{
using var id = ImRaii.PushId(i);
ImGui.TextUnformatted($"{key:X16}");
ImGui.SameLine();
var enabled = value.Enabled;
ImGui.Checkbox("Enabled", ref enabled);
}
}
private void DrawCustomizeApplication()
{
using var id = ImRaii.PushId("Customizations");
@ -365,6 +384,7 @@ public class DesignPanel(
DrawCustomize();
DrawEquipment();
DrawCustomizeParameters();
DrawMaterialValues();
_designDetails.Draw();
DrawApplicationRules();
_modAssociations.Draw();

View file

@ -85,7 +85,7 @@ public class NpcPanel(
try
{
var data = ToDesignData();
var text = _converter.ShareBase64(data, ApplicationRules.NpcFromModifiers());
var text = _converter.ShareBase64(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
ImGui.SetClipboardText(text);
}
catch (Exception ex)
@ -101,7 +101,7 @@ public class NpcPanel(
ImGui.OpenPopup("Save as Design");
_newName = _selector.Selection.Name;
var data = ToDesignData();
_newDesign = _converter.Convert(data, ApplicationRules.NpcFromModifiers());
_newDesign = _converter.Convert(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
}
private void SaveDesignDrawPopup()
@ -195,7 +195,7 @@ public class NpcPanel(
if (_state.GetOrCreate(id, data.Objects[0], out var state))
{
var design = _converter.Convert(ToDesignData(), ApplicationRules.NpcFromModifiers());
var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
_state.ApplyDesign(state, design, ApplySettings.Manual);
}
}
@ -213,7 +213,7 @@ public class NpcPanel(
if (_state.GetOrCreate(id, data.Objects[0], out var state))
{
var design = _converter.Convert(ToDesignData(), ApplicationRules.NpcFromModifiers());
var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
_state.ApplyDesign(state, design, ApplySettings.Manual);
}
}
@ -247,7 +247,9 @@ public class NpcPanel(
var colorName = color.Length == 0 ? DesignColors.AutomaticName : color;
ImGui.TableNextColumn();
if (_colorCombo.Draw("##colorCombo", colorName,
"Associate a color with this NPC appearance. Right-Click to revert to automatic coloring.",
"Associate a color with this NPC appearance.\n"
+ "Right-Click to revert to automatic coloring.\n"
+ "Hold Control and scroll the mousewheel to scroll.",
width - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight())
&& _colorCombo.CurrentSelection != null)
{

View file

@ -6,6 +6,8 @@ using Glamourer.Interop.Structs;
using Glamourer.State;
using OtterGui.Services;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop.Material;
@ -19,6 +21,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
private int _lastSlot;
private readonly ThreadLocal<List<MaterialValueIndex>> _deleteList = new(() => []);
public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra)
{
_stateManager = stateManager;
@ -39,7 +43,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
var (slotId, materialId) = FindMaterial(characterBase, material);
if (!validType
|| slotId == byte.MaxValue
|| slotId > 9
|| type is not MaterialValueIndex.DrawObjectType.Human && slotId > 0
|| !actor.Identifier(_actors, out var identifier)
|| !_stateManager.TryGetValue(identifier, out var state))
return;
@ -53,38 +58,60 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
if (!PrepareColorSet.TryGetColorTable(characterBase, material, stain, out var baseColorSet))
return;
for (var i = 0; i < values.Length; ++i)
var drawData = type switch
{
var idx = MaterialValueIndex.FromKey(values[i].key);
var (oldGame, model, source) = values[i].Value;
ref var row = ref baseColorSet[idx.RowIndex];
if (!idx.DataIndex.TryGetValue(row, out var newGame))
continue;
if (newGame == oldGame)
{
idx.DataIndex.SetValue(ref row, model);
}
else
{
switch (source.Base())
{
case StateSource.Manual:
_stateManager.ChangeMaterialValue(state, idx, Vector3.Zero, Vector3.Zero, ApplySettings.Game);
--i;
break;
case StateSource.Fixed:
idx.DataIndex.SetValue(ref row, model);
state.Materials.UpdateValue(idx, new MaterialValueState(newGame, model, source), out _);
break;
}
}
}
MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId),
_ => GetTempSlot((Weapon*)characterBase),
};
UpdateMaterialValues(state, values, drawData, ref baseColorSet);
if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture))
ret = (nint)texture;
}
/// <summary> Update and apply the glamourer state of an actor according to the application sources when updated by the game. </summary>
private void UpdateMaterialValues(ActorState state, ReadOnlySpan<(uint Key, MaterialValueState Value)> values, CharacterWeapon drawData,
ref MtrlFile.ColorTable colorTable)
{
var deleteList = _deleteList.Value!;
deleteList.Clear();
for (var i = 0; i < values.Length; ++i)
{
var idx = MaterialValueIndex.FromKey(values[i].Key);
var materialValue = values[i].Value;
ref var row = ref colorTable[idx.RowIndex];
var newGame = new ColorRow(row);
if (materialValue.EqualGame(newGame, drawData))
materialValue.Model.Apply(ref row);
else
switch (materialValue.Source)
{
case StateSource.Pending:
materialValue.Model.Apply(ref row);
state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.Manual),
out _);
break;
case StateSource.IpcManual:
case StateSource.Manual:
deleteList.Add(idx);
break;
case StateSource.Fixed:
case StateSource.IpcFixed:
materialValue.Model.Apply(ref row);
state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, materialValue.Source),
out _);
break;
}
}
foreach (var idx in deleteList)
_stateManager.ChangeMaterialValue(state, idx, default, ApplySettings.Game);
}
/// <summary>
/// Find the index of a material by searching through a draw objects pointers.
/// Tries to take shortcuts for consecutive searches like when a character is newly created.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private (byte SlotId, byte MaterialId) FindMaterial(CharacterBase* characterBase, MaterialResourceHandle* material)
{
@ -119,6 +146,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
return (byte.MaxValue, byte.MaxValue);
}
/// <summary> Find the type of the given draw object by checking the actors pointers. </summary>
private static bool FindType(CharacterBase* characterBase, Actor actor, out MaterialValueIndex.DrawObjectType type)
{
type = MaterialValueIndex.DrawObjectType.Human;
@ -145,4 +173,26 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
return false;
}
/// <summary> We need to get the temporary set, variant and stain that is currently being set if it is available. </summary>
private CharacterWeapon GetTempSlot(Human* human, byte slotId)
{
if (human->ChangedEquipData == null)
return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0);
return ((CharacterArmor*)human->ChangedEquipData + slotId * 3)->ToWeapon(0);
}
/// <summary>
/// We need to get the temporary set, variant and stain that is currently being set if it is available.
/// Weapons do not change in skeleton id without being reconstructed, so this is not changeable data.
/// </summary>
private CharacterWeapon GetTempSlot(Weapon* weapon)
{
var changedData = *(void**)((byte*)weapon + 0x918);
if (changedData == null)
return new CharacterWeapon(weapon->ModelSetId, weapon->SecondaryId, (Variant)weapon->Variant, (StainId)weapon->ModelUnknown);
return new CharacterWeapon(weapon->ModelSetId, *(SecondaryId*)changedData, ((Variant*)changedData)[2], ((StainId*)changedData)[3]);
}
}

View file

@ -11,14 +11,13 @@ public readonly record struct MaterialValueIndex(
MaterialValueIndex.DrawObjectType DrawObject,
byte SlotIndex,
byte MaterialIndex,
byte RowIndex,
MaterialValueIndex.ColorTableIndex DataIndex)
byte RowIndex)
{
public uint Key
=> ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex, DataIndex);
=> ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex);
public bool Valid
=> Validate(DrawObject) && ValidateSlot(SlotIndex) && ValidateMaterial(MaterialIndex) && ValidateRow(RowIndex) && Validate(DataIndex);
=> Validate(DrawObject) && ValidateSlot(SlotIndex) && ValidateMaterial(MaterialIndex) && ValidateRow(RowIndex);
public static bool FromKey(uint key, out MaterialValueIndex index)
{
@ -96,7 +95,7 @@ public readonly record struct MaterialValueIndex(
public unsafe bool TryGetColorTable(Texture** texture, out MtrlFile.ColorTable table)
=> DirectXTextureHelper.TryGetColorTable(*texture, out table);
public unsafe bool TryGetColorRow(Actor actor, out MtrlFile.ColorTable.Row row)
public bool TryGetColorRow(Actor actor, out MtrlFile.ColorTable.Row row)
{
if (!TryGetColorTable(actor, out var table))
{
@ -108,40 +107,16 @@ public readonly record struct MaterialValueIndex(
return true;
}
public unsafe bool TryGetValue(Actor actor, out Vector3 value)
{
if (!TryGetColorRow(actor, out var row))
{
value = Vector3.Zero;
return false;
}
value = DataIndex switch
{
ColorTableIndex.Diffuse => row.Diffuse,
ColorTableIndex.Specular => row.Specular,
ColorTableIndex.SpecularStrength => new Vector3(row.SpecularStrength, 0, 0),
ColorTableIndex.Emissive => row.Emissive,
ColorTableIndex.GlossStrength => new Vector3(row.GlossStrength, 0, 0),
ColorTableIndex.TileSet => new Vector3(row.TileSet),
ColorTableIndex.MaterialRepeat => new Vector3(row.MaterialRepeat, 0),
ColorTableIndex.MaterialSkew => new Vector3(row.MaterialSkew, 0),
_ => new Vector3(float.NaN),
};
return !float.IsNaN(value.X);
}
public static MaterialValueIndex FromKey(uint key)
=> new(key);
public static MaterialValueIndex Min(DrawObjectType drawObject = 0, byte slotIndex = 0, byte materialIndex = 0, byte rowIndex = 0,
ColorTableIndex dataIndex = 0)
=> new(drawObject, slotIndex, materialIndex, rowIndex, dataIndex);
public static MaterialValueIndex Min(DrawObjectType drawObject = 0, byte slotIndex = 0, byte materialIndex = 0, byte rowIndex = 0)
=> new(drawObject, slotIndex, materialIndex, rowIndex);
public static MaterialValueIndex Max(DrawObjectType drawObject = (DrawObjectType)byte.MaxValue, byte slotIndex = byte.MaxValue,
byte materialIndex = byte.MaxValue, byte rowIndex = byte.MaxValue,
ColorTableIndex dataIndex = (ColorTableIndex)byte.MaxValue)
=> new(drawObject, slotIndex, materialIndex, rowIndex, dataIndex);
byte materialIndex = byte.MaxValue, byte rowIndex = byte.MaxValue)
=> new(drawObject, slotIndex, materialIndex, rowIndex);
public enum DrawObjectType : byte
{
@ -150,18 +125,6 @@ public readonly record struct MaterialValueIndex(
Offhand,
};
public enum ColorTableIndex : byte
{
Diffuse,
Specular,
SpecularStrength,
Emissive,
GlossStrength,
TileSet,
MaterialRepeat,
MaterialSkew,
}
public static bool Validate(DrawObjectType type)
=> Enum.IsDefined(type);
@ -174,22 +137,17 @@ public readonly record struct MaterialValueIndex(
public static bool ValidateRow(byte rowIndex)
=> rowIndex < MtrlFile.ColorTable.NumRows;
public static bool Validate(ColorTableIndex dataIndex)
=> Enum.IsDefined(dataIndex);
private static uint ToKey(DrawObjectType type, byte slotIndex, byte materialIndex, byte rowIndex, ColorTableIndex index)
private static uint ToKey(DrawObjectType type, byte slotIndex, byte materialIndex, byte rowIndex)
{
var result = (uint)index & 0xFF;
result |= (uint)(rowIndex & 0xFF) << 8;
result |= (uint)(materialIndex & 0xF) << 16;
result |= (uint)(slotIndex & 0xFF) << 20;
result |= (uint)((byte)type & 0xF) << 28;
var result = (uint)rowIndex;
result |= (uint)materialIndex << 8;
result |= (uint)slotIndex << 16;
result |= (uint)((byte)type << 24);
return result;
}
private MaterialValueIndex(uint key)
: this((DrawObjectType)((key >> 28) & 0xF), (byte)(key >> 20), (byte)((key >> 16) & 0xF), (byte)(key >> 8),
(ColorTableIndex)(key & 0xFF))
: this((DrawObjectType)(key >> 24), (byte)(key >> 16), (byte)(key >> 8), (byte)key)
{ }
private class Converter : JsonConverter<MaterialValueIndex>
@ -202,81 +160,3 @@ public readonly record struct MaterialValueIndex(
=> FromKey(serializer.Deserialize<uint>(reader), out var value) ? value : throw new Exception($"Invalid material key {value.Key}.");
}
}
public static class MaterialExtensions
{
public static bool TryGetValue(this MaterialValueIndex.ColorTableIndex index, in MtrlFile.ColorTable.Row row, out Vector3 value)
{
value = index switch
{
MaterialValueIndex.ColorTableIndex.Diffuse => row.Diffuse,
MaterialValueIndex.ColorTableIndex.Specular => row.Specular,
MaterialValueIndex.ColorTableIndex.SpecularStrength => new Vector3(row.SpecularStrength, 0, 0),
MaterialValueIndex.ColorTableIndex.Emissive => row.Emissive,
MaterialValueIndex.ColorTableIndex.GlossStrength => new Vector3(row.GlossStrength, 0, 0),
MaterialValueIndex.ColorTableIndex.TileSet => new Vector3(row.TileSet),
MaterialValueIndex.ColorTableIndex.MaterialRepeat => new Vector3(row.MaterialRepeat, 0),
MaterialValueIndex.ColorTableIndex.MaterialSkew => new Vector3(row.MaterialSkew, 0),
_ => new Vector3(float.NaN),
};
return !float.IsNaN(value.X);
}
public static bool SetValue(this MaterialValueIndex.ColorTableIndex index, ref MtrlFile.ColorTable.Row row, in Vector3 value)
{
switch (index)
{
case MaterialValueIndex.ColorTableIndex.Diffuse:
if (value == row.Diffuse)
return false;
row.Diffuse = value;
return true;
case MaterialValueIndex.ColorTableIndex.Specular:
if (value == row.Specular)
return false;
row.Specular = value;
return true;
case MaterialValueIndex.ColorTableIndex.SpecularStrength:
if (value.X == row.SpecularStrength)
return false;
row.SpecularStrength = value.X;
return true;
case MaterialValueIndex.ColorTableIndex.Emissive:
if (value == row.Emissive)
return false;
row.Emissive = value;
return true;
case MaterialValueIndex.ColorTableIndex.GlossStrength:
if (value.X == row.GlossStrength)
return false;
row.GlossStrength = value.X;
return true;
case MaterialValueIndex.ColorTableIndex.TileSet:
var @ushort = (ushort)(value.X + 0.5f);
if (@ushort == row.TileSet)
return false;
row.TileSet = @ushort;
return true;
case MaterialValueIndex.ColorTableIndex.MaterialRepeat:
if (value.X == row.MaterialRepeat.X && value.Y == row.MaterialRepeat.Y)
return false;
row.MaterialRepeat = new Vector2(value.X, value.Y);
return true;
case MaterialValueIndex.ColorTableIndex.MaterialSkew:
if (value.X == row.MaterialSkew.X && value.Y == row.MaterialSkew.Y)
return false;
row.MaterialSkew = new Vector2(value.X, value.Y);
return true;
default: return false;
}
}
}

View file

@ -1,12 +1,242 @@
global using StateMaterialManager = Glamourer.Interop.Material.MaterialValueManager<Glamourer.Interop.Material.MaterialValueState>;
global using DesignMaterialManager = Glamourer.Interop.Material.MaterialValueManager<Glamourer.Interop.Material.MaterialValueDesign>;
using Glamourer.GameData;
using Glamourer.State;
using Penumbra.GameData.Files;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop.Material;
public record struct MaterialValueDesign(Vector3 Value, bool Enabled);
public record struct MaterialValueState(Vector3 Game, Vector3 Model, StateSource Source);
[JsonConverter(typeof(Converter))]
public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, float specularStrength, float glossStrength)
{
public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, 0, 0);
public Vector3 Diffuse = diffuse;
public Vector3 Specular = specular;
public Vector3 Emissive = emissive;
public float SpecularStrength = specularStrength;
public float GlossStrength = glossStrength;
public ColorRow(in MtrlFile.ColorTable.Row row)
: this(row.Diffuse, row.Specular, row.Emissive, row.SpecularStrength, row.GlossStrength)
{ }
public readonly bool NearEqual(in ColorRow rhs)
=> Diffuse.NearEqual(rhs.Diffuse)
&& Specular.NearEqual(rhs.Specular)
&& Emissive.NearEqual(rhs.Emissive)
&& SpecularStrength.NearEqual(rhs.SpecularStrength)
&& GlossStrength.NearEqual(rhs.GlossStrength);
public readonly bool Apply(ref MtrlFile.ColorTable.Row row)
{
var ret = false;
if (!row.Diffuse.NearEqual(Diffuse))
{
row.Diffuse = Diffuse;
ret = true;
}
if (!row.Specular.NearEqual(Specular))
{
row.Specular = Specular;
ret = true;
}
if (!row.Emissive.NearEqual(Emissive))
{
row.Emissive = Emissive;
ret = true;
}
if (!row.SpecularStrength.NearEqual(SpecularStrength))
{
row.SpecularStrength = SpecularStrength;
ret = true;
}
if (!row.GlossStrength.NearEqual(GlossStrength))
{
row.GlossStrength = GlossStrength;
ret = true;
}
return ret;
}
private class Converter : JsonConverter<ColorRow>
{
public override void WriteJson(JsonWriter writer, ColorRow value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("DiffuseR");
writer.WriteValue(value.Diffuse.X);
writer.WritePropertyName("DiffuseG");
writer.WriteValue(value.Diffuse.Y);
writer.WritePropertyName("DiffuseB");
writer.WriteValue(value.Diffuse.Z);
writer.WritePropertyName("SpecularR");
writer.WriteValue(value.Specular.X);
writer.WritePropertyName("SpecularG");
writer.WriteValue(value.Specular.Y);
writer.WritePropertyName("SpecularB");
writer.WriteValue(value.Specular.Z);
writer.WritePropertyName("SpecularA");
writer.WriteValue(value.SpecularStrength);
writer.WritePropertyName("EmissiveR");
writer.WriteValue(value.Emissive.X);
writer.WritePropertyName("EmissiveG");
writer.WriteValue(value.Emissive.Y);
writer.WritePropertyName("EmissiveB");
writer.WriteValue(value.Emissive.Z);
writer.WritePropertyName("Gloss");
writer.WriteValue(value.GlossStrength);
writer.WriteEndObject();
}
public override ColorRow ReadJson(JsonReader reader, Type objectType, ColorRow existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
var obj = JObject.Load(reader);
Set(ref existingValue.Diffuse.X, obj["DiffuseR"]?.Value<float>());
Set(ref existingValue.Diffuse.Y, obj["DiffuseG"]?.Value<float>());
Set(ref existingValue.Diffuse.Z, obj["DiffuseB"]?.Value<float>());
Set(ref existingValue.Specular.X, obj["SpecularR"]?.Value<float>());
Set(ref existingValue.Specular.Y, obj["SpecularG"]?.Value<float>());
Set(ref existingValue.Specular.Z, obj["SpecularB"]?.Value<float>());
Set(ref existingValue.SpecularStrength, obj["SpecularA"]?.Value<float>());
Set(ref existingValue.Emissive.X, obj["EmissiveR"]?.Value<float>());
Set(ref existingValue.Emissive.Y, obj["EmissiveG"]?.Value<float>());
Set(ref existingValue.Emissive.Z, obj["EmissiveB"]?.Value<float>());
Set(ref existingValue.GlossStrength, obj["Gloss"]?.Value<float>());
return existingValue;
static void Set<T>(ref T target, T? value)
where T : struct
{
if (value.HasValue)
target = value.Value;
}
}
}
}
[JsonConverter(typeof(Converter))]
public struct MaterialValueDesign(ColorRow value, bool enabled)
{
public ColorRow Value = value;
public bool Enabled = enabled;
public readonly bool Apply(ref MaterialValueState state)
{
if (!Enabled)
return false;
if (state.Model.NearEqual(Value))
return false;
state.Model = Value;
return true;
}
private class Converter : JsonConverter<MaterialValueDesign>
{
public override void WriteJson(JsonWriter writer, MaterialValueDesign value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("DiffuseR");
writer.WriteValue(value.Value.Diffuse.X);
writer.WritePropertyName("DiffuseG");
writer.WriteValue(value.Value.Diffuse.Y);
writer.WritePropertyName("DiffuseB");
writer.WriteValue(value.Value.Diffuse.Z);
writer.WritePropertyName("SpecularR");
writer.WriteValue(value.Value.Specular.X);
writer.WritePropertyName("SpecularG");
writer.WriteValue(value.Value.Specular.Y);
writer.WritePropertyName("SpecularB");
writer.WriteValue(value.Value.Specular.Z);
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);
writer.WritePropertyName("Enabled");
writer.WriteValue(value.Enabled);
writer.WriteEndObject();
}
public override MaterialValueDesign ReadJson(JsonReader reader, Type objectType, MaterialValueDesign existingValue,
bool hasExistingValue,
JsonSerializer serializer)
{
var obj = JObject.Load(reader);
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;
return existingValue;
static void Set<T>(ref T target, T? value)
where T : struct
{
if (value.HasValue)
target = value.Value;
}
}
}
}
[StructLayout(LayoutKind.Explicit)]
public struct MaterialValueState(
in ColorRow game,
in ColorRow model,
CharacterWeapon drawData,
StateSource source)
{
public MaterialValueState(in ColorRow gameRow, in ColorRow modelRow, CharacterArmor armor, StateSource source)
: this(gameRow, modelRow, armor.ToWeapon(0), source)
{ }
[FieldOffset(0)]
public ColorRow Game = game;
[FieldOffset(44)]
public ColorRow Model = model;
[FieldOffset(88)]
public readonly CharacterWeapon DrawData = drawData;
[FieldOffset(95)]
public readonly StateSource Source = source;
public readonly bool EqualGame(in ColorRow rhsRow, CharacterWeapon rhsData)
=> DrawData.Skeleton == rhsData.Skeleton
&& DrawData.Weapon == rhsData.Weapon
&& DrawData.Variant == rhsData.Variant
&& DrawData.Stain == rhsData.Stain
&& Game.NearEqual(rhsRow);
public readonly MaterialValueDesign Convert()
=> new(Model, true);
}
public readonly struct MaterialValueManager<T>
{
@ -113,7 +343,7 @@ public readonly struct MaterialValueManager<T>
return count;
}
public ReadOnlySpan<(uint key, T Value)> GetValues(MaterialValueIndex min, MaterialValueIndex max)
public ReadOnlySpan<(uint Key, T Value)> GetValues(MaterialValueIndex min, MaterialValueIndex max)
=> MaterialValueManager.Filter<T>(CollectionsMarshal.AsSpan(_values), min, max);
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@ -187,6 +417,7 @@ public static class MaterialValueManager
maxIdx = ~maxIdx + idx;
return maxIdx > minIdx ? (minIdx, maxIdx - 1) : (-1, -1);
}
maxIdx += idx;
while (maxIdx < values.Length - 1 && values[maxIdx + 1].Key <= maxKey)

View file

@ -222,7 +222,7 @@ public class InternalStateEditor(
}
/// <summary> Change the value of a single material color table entry. </summary>
public bool ChangeMaterialValue(ActorState state, MaterialValueIndex index, Vector3 value, Vector3 gameValue, StateSource source, out Vector3 oldValue,
public bool ChangeMaterialValue(ActorState state, MaterialValueIndex index, in MaterialValueState newValue, StateSource source, out ColorRow? oldValue,
uint key = 0)
{
// We already have an existing value.
@ -240,18 +240,18 @@ public class InternalStateEditor(
}
// Update if edited.
state.Materials.UpdateValue(index, new MaterialValueState(gameValue, value, source), out _);
state.Materials.UpdateValue(index, newValue, out _);
return true;
}
// We do not have an existing value.
oldValue = gameValue;
oldValue = null;
// Do not do anything if locked or if the game value updates, because then we do not need to add an entry.
if (!state.CanUnlock(key) || source is StateSource.Game)
return false;
// Only add an entry if it is sufficiently different from the game value.
return !value.NearEqual(gameValue) && state.Materials.TryAddValue(index, new MaterialValueState(gameValue, value, source));
// Only add an entry if it is different from the game value.
return state.Materials.TryAddValue(index, newValue);
}
public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue,

View file

@ -276,7 +276,7 @@ public class StateApplier(
return data;
}
public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, Vector3? value, bool force)
public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, ColorRow? value, bool force)
{
if (!force && !_config.UseAdvancedParameters)
return;
@ -289,14 +289,11 @@ public class StateApplier(
if (!index.TryGetColorTable(texture, out var table))
continue;
Vector3 actualValue;
if (value.HasValue)
actualValue = value.Value;
else if (!PrepareColorSet.TryGetColorTable(actor, index, out var baseTable)
|| !index.DataIndex.TryGetValue(baseTable[index.RowIndex], out actualValue))
continue;
if (!index.DataIndex.SetValue(ref table[index.RowIndex], actualValue))
value.Value.Apply(ref table[index.RowIndex]);
else if (PrepareColorSet.TryGetColorTable(actor, index, out var baseTable))
table[index.RowIndex] = baseTable[index.RowIndex];
else
continue;
MaterialService.ReplaceColorTable(texture, table);

View file

@ -165,15 +165,15 @@ public class StateEditor(
StateChanged.Invoke(StateChanged.Type.Parameter, settings.Source, state, actors, (old, @new, flag));
}
public void ChangeMaterialValue(object data, MaterialValueIndex index, Vector3 value, Vector3 gameValue, ApplySettings settings)
public void ChangeMaterialValue(object data, MaterialValueIndex index, in MaterialValueState newValue, ApplySettings settings)
{
var state = (ActorState)data;
if (!Editor.ChangeMaterialValue(state, index, value, gameValue, settings.Source, out var oldValue, settings.Key))
if (!Editor.ChangeMaterialValue(state, index, newValue, settings.Source, out var oldValue, settings.Key))
return;
var actors = Applier.ChangeMaterialValue(state, index, settings.Source.RequiresChange());
Glamourer.Log.Verbose($"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, value, index));
Glamourer.Log.Verbose($"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index));
}
/// <inheritdoc/>
@ -282,6 +282,20 @@ public class StateEditor(
if (!settings.RespectManual || !state.Sources[meta].IsManual())
Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key);
}
foreach (var (key, value) in mergedDesign.Design.Materials)
{
if (!value.Enabled)
continue;
var idx = MaterialValueIndex.FromKey(key);
// TODO
//if (state.Materials.TryGetValue(idx, out var materialState))
//{
// if (!settings.RespectManual || materialState.Source.IsManual())
// Editor.ChangeMaterialValue(state, idx, new MaterialValueState(materialState.Game, value.Value, materialState.DrawData));
//}
}
}
var actors = settings.Source.RequiresChange()