mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-13 12:14:18 +01:00
Change mousewheel to ctrl, current material state.
This commit is contained in:
parent
42ac507b86
commit
b5b9289dc2
20 changed files with 537 additions and 296 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class DesignBase
|
|||
{
|
||||
public const int FileVersion = 1;
|
||||
|
||||
private DesignData _designData = new();
|
||||
private DesignData _designData = new();
|
||||
private readonly DesignMaterialManager _materials = new();
|
||||
|
||||
/// <summary> For read-only information about custom material color changes. </summary>
|
||||
|
|
@ -86,9 +86,9 @@ public class DesignBase
|
|||
internal CustomizeFlag ApplyCustomizeRaw
|
||||
=> _applyCustomize;
|
||||
|
||||
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
|
||||
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
|
||||
internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
|
||||
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
|
||||
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
|
||||
internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
|
||||
private bool _writeProtected;
|
||||
|
||||
public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize)
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
using var tree = ImRaii.TreeNode($"{name} Material #{drawnMaterial++}###{name}{materialIndex}");
|
||||
if (!tree)
|
||||
continue;
|
||||
|
||||
DrawMaterial(ref table, drawData, index with { MaterialIndex = materialIndex} );
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMaterial(ref MtrlFile.ColorTable table, MaterialValueIndex sourceIndex)
|
||||
private void DrawMaterial(ref MtrlFile.ColorTable table, CharacterWeapon drawData, MaterialValueIndex sourceIndex)
|
||||
{
|
||||
using var tree = ImRaii.TreeNode($"Material {sourceIndex.MaterialIndex + 1}");
|
||||
if (!tree)
|
||||
return;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue