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(),
|
["Equipment"] = SerializeEquipment(),
|
||||||
["Customize"] = SerializeCustomize(),
|
["Customize"] = SerializeCustomize(),
|
||||||
["Parameters"] = SerializeParameters(),
|
["Parameters"] = SerializeParameters(),
|
||||||
|
["Materials"] = SerializeMaterials(),
|
||||||
["Mods"] = SerializeMods(),
|
["Mods"] = SerializeMods(),
|
||||||
["Links"] = Links.Serialize(),
|
["Links"] = Links.Serialize(),
|
||||||
};
|
};
|
||||||
|
|
@ -136,6 +137,7 @@ public sealed class Design : DesignBase, ISavable
|
||||||
LoadEquip(items, json["Equipment"], design, design.Name, true);
|
LoadEquip(items, json["Equipment"], design, design.Name, true);
|
||||||
LoadMods(json["Mods"], design);
|
LoadMods(json["Mods"], design);
|
||||||
LoadParameters(json["Parameters"], design, design.Name);
|
LoadParameters(json["Parameters"], design, design.Name);
|
||||||
|
LoadMaterials(json["Materials"], design, design.Name);
|
||||||
LoadLinks(linkLoader, json["Links"], design);
|
LoadLinks(linkLoader, json["Links"], design);
|
||||||
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
|
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
|
||||||
return design;
|
return design;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public class DesignBase
|
||||||
{
|
{
|
||||||
public const int FileVersion = 1;
|
public const int FileVersion = 1;
|
||||||
|
|
||||||
private DesignData _designData = new();
|
private DesignData _designData = new();
|
||||||
private readonly DesignMaterialManager _materials = new();
|
private readonly DesignMaterialManager _materials = new();
|
||||||
|
|
||||||
/// <summary> For read-only information about custom material color changes. </summary>
|
/// <summary> For read-only information about custom material color changes. </summary>
|
||||||
|
|
@ -86,9 +86,9 @@ public class DesignBase
|
||||||
internal CustomizeFlag ApplyCustomizeRaw
|
internal CustomizeFlag ApplyCustomizeRaw
|
||||||
=> _applyCustomize;
|
=> _applyCustomize;
|
||||||
|
|
||||||
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
|
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
|
||||||
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
|
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
|
||||||
internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
|
internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
|
||||||
private bool _writeProtected;
|
private bool _writeProtected;
|
||||||
|
|
||||||
public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize)
|
public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize)
|
||||||
|
|
@ -124,7 +124,6 @@ public class DesignBase
|
||||||
|
|
||||||
_writeProtected = value;
|
_writeProtected = value;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DoApplyEquip(EquipSlot slot)
|
public bool DoApplyEquip(EquipSlot slot)
|
||||||
|
|
@ -244,6 +243,7 @@ public class DesignBase
|
||||||
["Equipment"] = SerializeEquipment(),
|
["Equipment"] = SerializeEquipment(),
|
||||||
["Customize"] = SerializeCustomize(),
|
["Customize"] = SerializeCustomize(),
|
||||||
["Parameters"] = SerializeParameters(),
|
["Parameters"] = SerializeParameters(),
|
||||||
|
["Materials"] = SerializeMaterials(),
|
||||||
};
|
};
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -362,6 +362,45 @@ public class DesignBase
|
||||||
return ret;
|
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
|
#endregion
|
||||||
|
|
||||||
#region Deserialization
|
#region Deserialization
|
||||||
|
|
@ -382,6 +421,7 @@ public class DesignBase
|
||||||
LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true);
|
LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true);
|
||||||
LoadEquip(items, json["Equipment"], ret, "Temporary Design", true);
|
LoadEquip(items, json["Equipment"], ret, "Temporary Design", true);
|
||||||
LoadParameters(json["Parameters"], ret, "Temporary Design");
|
LoadParameters(json["Parameters"], ret, "Temporary Design");
|
||||||
|
LoadMaterials(json["Materials"], ret, "Temporary Design");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Glamourer.Designs.Links;
|
using Glamourer.Designs.Links;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Glamourer.Utility;
|
using Glamourer.Utility;
|
||||||
|
|
@ -6,6 +7,7 @@ using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.GameData.DataContainers;
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Designs;
|
namespace Glamourer.Designs;
|
||||||
|
|
@ -38,22 +40,23 @@ public class DesignConverter(
|
||||||
=> ShareBase64(ShareJObject(design));
|
=> ShareBase64(ShareJObject(design));
|
||||||
|
|
||||||
public string ShareBase64(ActorState state, in ApplicationRules rules)
|
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));
|
return ShareBase64(ShareJObject(design));
|
||||||
}
|
}
|
||||||
|
|
||||||
public DesignBase Convert(ActorState state, in ApplicationRules rules)
|
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();
|
var design = _designs.CreateTemporary();
|
||||||
rules.Apply(design);
|
rules.Apply(design);
|
||||||
design.SetDesignData(_customize, data);
|
design.SetDesignData(_customize, data);
|
||||||
|
ComputeMaterials(design.GetMaterialDataRef(), materials, rules.Equip);
|
||||||
return design;
|
return design;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,4 +184,29 @@ public class DesignConverter(
|
||||||
|
|
||||||
yield return (EquipSlot.OffHand, oh, offhand.Stain);
|
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.Designs.Links;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -250,6 +251,14 @@ public class DesignEditor(
|
||||||
|
|
||||||
foreach (var parameter in CustomizeParameterExtensions.AllFlags.Where(other.DoApplyParameter))
|
foreach (var parameter in CustomizeParameterExtensions.AllFlags.Where(other.DoApplyParameter))
|
||||||
ChangeCustomizeParameter(design, parameter, other.DesignData.Parameters[parameter]);
|
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>
|
/// <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)]
|
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool NearEqual(this CustomizeParameterValue lhs, CustomizeParameterValue rhs, float eps = 1e-9f)
|
public static bool NearEqual(this CustomizeParameterValue lhs, CustomizeParameterValue rhs, float eps = 1e-9f)
|
||||||
=> NearEqual(lhs.InternalQuadruple, rhs.InternalQuadruple, eps);
|
=> 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)
|
private static bool CaptureMouseWheel(ref int value, int offset, int cap)
|
||||||
{
|
{
|
||||||
if (!ImGui.IsItemHovered())
|
if (!ImGui.IsItemHovered() || !ImGui.GetIO().KeyCtrl)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ImGuiInternal.ItemSetUsingMouseWheel();
|
ImGuiInternal.ItemSetUsingMouseWheel();
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ using Penumbra.GameData.Structs;
|
||||||
namespace Glamourer.Gui.Equipment;
|
namespace Glamourer.Gui.Equipment;
|
||||||
|
|
||||||
public sealed class GlamourerColorCombo(float _comboWidth, DictStain _stains, FavoriteManager _favorites)
|
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)
|
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 Variant CustomVariant { get; private set; }
|
||||||
|
|
||||||
public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites)
|
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;
|
_favorites = favorites;
|
||||||
Label = GetLabel(gameData, slot);
|
Label = GetLabel(gameData, slot);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
|
||||||
private float _innerWidth;
|
private float _innerWidth;
|
||||||
|
|
||||||
public WeaponCombo(ItemManager items, FullEquipType type, Logger log)
|
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);
|
Label = GetLabel(type);
|
||||||
SearchByParts = true;
|
SearchByParts = true;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Interop.Material;
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Materials;
|
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 =
|
private static readonly IReadOnlyList<MaterialValueIndex.DrawObjectType> Types =
|
||||||
[
|
[
|
||||||
|
|
@ -23,135 +25,94 @@ public unsafe class MaterialDrawer(StateManager _stateManager) : IService
|
||||||
|
|
||||||
private ActorState? _state;
|
private ActorState? _state;
|
||||||
|
|
||||||
public void DrawPanel(Actor actor)
|
public void DrawActorPanel(Actor actor)
|
||||||
{
|
{
|
||||||
if (!actor.IsCharacter || !_stateManager.GetOrCreate(actor, out _state))
|
if (!actor.IsCharacter || !_stateManager.GetOrCreate(actor, out _state))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var type in Types)
|
var model = actor.Model;
|
||||||
{
|
if (!model.IsHuman)
|
||||||
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)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var names = model.AsCharacterBase->GetModelType() is CharacterBase.ModelType.Human
|
if (model.AsCharacterBase->SlotCount < 10)
|
||||||
? 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)
|
|
||||||
return;
|
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 item = model.GetArmor(slot).ToWeapon(0);
|
||||||
var texture = model.AsCharacterBase->ColorTableTextures + index.SlotIndex * MaterialService.MaterialsPerModel + i;
|
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)
|
if (*texture == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!DirectXTextureHelper.TryGetColorTable(*texture, out var table))
|
if (!DirectXTextureHelper.TryGetColorTable(*texture, out var table))
|
||||||
continue;
|
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)
|
for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
|
||||||
{
|
{
|
||||||
var index = sourceIndex with { RowIndex = i };
|
var index = sourceIndex with { RowIndex = i };
|
||||||
ref var row = ref table[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(
|
using var id = ImRaii.PushId(index.RowIndex);
|
||||||
MaterialValueIndex.Min(sourceIndex.DrawObject, sourceIndex.SlotIndex, sourceIndex.MaterialIndex, sourceIndex.RowIndex),
|
var changed = _state!.Materials.TryGetValue(index, out var value);
|
||||||
MaterialValueIndex.Max(sourceIndex.DrawObject, sourceIndex.SlotIndex, sourceIndex.MaterialIndex, sourceIndex.RowIndex));
|
if (!changed)
|
||||||
|
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
if (ImGui.ColorEdit3("Diffuse", ref diffuse, ImGuiColorEditFlags.NoInputs))
|
var internalRow = new ColorRow(row);
|
||||||
_stateManager.ChangeMaterialValue(_state!, index, diffuse, diffuseGame, ApplySettings.Manual);
|
value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var applied = ImGui.ColorEdit3("Diffuse", ref value.Model.Diffuse, ImGuiColorEditFlags.NoInputs);
|
||||||
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);
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed))
|
applied |= ImGui.ColorEdit3("Specular", ref value.Model.Specular, ImGuiColorEditFlags.NoInputs);
|
||||||
{
|
|
||||||
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);
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed))
|
applied |= ImGui.ColorEdit3("Emissive", ref value.Model.Emissive, ImGuiColorEditFlags.NoInputs);
|
||||||
{
|
|
||||||
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);
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
|
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed))
|
applied |= ImGui.DragFloat("Gloss", ref value.Model.GlossStrength, 0.1f);
|
||||||
{
|
|
||||||
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);
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
|
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))
|
ImGui.SameLine();
|
||||||
_stateManager.ChangeMaterialValue(_state!, index, new Vector3(specularStrength), new Vector3(specularStrengthGame),
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||||
ApplySettings.Manual);
|
{
|
||||||
|
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"))
|
if (ImGui.CollapsingHeader("Material Shit"))
|
||||||
_materialDrawer.DrawPanel(_actor);
|
_materialDrawer.DrawActorPanel(_actor);
|
||||||
using var disabled = ImRaii.Disabled(transformationId != 0);
|
using var disabled = ImRaii.Disabled(transformationId != 0);
|
||||||
if (_state.ModelData.IsHuman)
|
if (_state.ModelData.IsHuman)
|
||||||
DrawHumanPanel();
|
DrawHumanPanel();
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ public sealed class DesignColorCombo(DesignColors _designColors, bool _skipAutom
|
||||||
FilterComboCache<string>(_skipAutomatic
|
FilterComboCache<string>(_skipAutomatic
|
||||||
? _designColors.Keys.OrderBy(k => k)
|
? _designColors.Keys.OrderBy(k => k)
|
||||||
: _designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName),
|
: _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)
|
protected override void OnMouseWheel(string preview, ref int current, int steps)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,25 @@ public class DesignPanel(
|
||||||
_parameterDrawer.Draw(_manager, _selector.Selected!);
|
_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()
|
private void DrawCustomizeApplication()
|
||||||
{
|
{
|
||||||
using var id = ImRaii.PushId("Customizations");
|
using var id = ImRaii.PushId("Customizations");
|
||||||
|
|
@ -365,6 +384,7 @@ public class DesignPanel(
|
||||||
DrawCustomize();
|
DrawCustomize();
|
||||||
DrawEquipment();
|
DrawEquipment();
|
||||||
DrawCustomizeParameters();
|
DrawCustomizeParameters();
|
||||||
|
DrawMaterialValues();
|
||||||
_designDetails.Draw();
|
_designDetails.Draw();
|
||||||
DrawApplicationRules();
|
DrawApplicationRules();
|
||||||
_modAssociations.Draw();
|
_modAssociations.Draw();
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ public class NpcPanel(
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = ToDesignData();
|
var data = ToDesignData();
|
||||||
var text = _converter.ShareBase64(data, ApplicationRules.NpcFromModifiers());
|
var text = _converter.ShareBase64(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
|
||||||
ImGui.SetClipboardText(text);
|
ImGui.SetClipboardText(text);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -101,7 +101,7 @@ public class NpcPanel(
|
||||||
ImGui.OpenPopup("Save as Design");
|
ImGui.OpenPopup("Save as Design");
|
||||||
_newName = _selector.Selection.Name;
|
_newName = _selector.Selection.Name;
|
||||||
var data = ToDesignData();
|
var data = ToDesignData();
|
||||||
_newDesign = _converter.Convert(data, ApplicationRules.NpcFromModifiers());
|
_newDesign = _converter.Convert(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveDesignDrawPopup()
|
private void SaveDesignDrawPopup()
|
||||||
|
|
@ -195,7 +195,7 @@ public class NpcPanel(
|
||||||
|
|
||||||
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
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);
|
_state.ApplyDesign(state, design, ApplySettings.Manual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -213,7 +213,7 @@ public class NpcPanel(
|
||||||
|
|
||||||
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
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);
|
_state.ApplyDesign(state, design, ApplySettings.Manual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -247,7 +247,9 @@ public class NpcPanel(
|
||||||
var colorName = color.Length == 0 ? DesignColors.AutomaticName : color;
|
var colorName = color.Length == 0 ? DesignColors.AutomaticName : color;
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (_colorCombo.Draw("##colorCombo", colorName,
|
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())
|
width - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight())
|
||||||
&& _colorCombo.CurrentSelection != null)
|
&& _colorCombo.CurrentSelection != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ using Glamourer.Interop.Structs;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Interop.Material;
|
namespace Glamourer.Interop.Material;
|
||||||
|
|
@ -19,6 +21,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
|
||||||
|
|
||||||
private int _lastSlot;
|
private int _lastSlot;
|
||||||
|
|
||||||
|
private readonly ThreadLocal<List<MaterialValueIndex>> _deleteList = new(() => []);
|
||||||
|
|
||||||
public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra)
|
public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra)
|
||||||
{
|
{
|
||||||
_stateManager = stateManager;
|
_stateManager = stateManager;
|
||||||
|
|
@ -39,7 +43,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
|
||||||
var (slotId, materialId) = FindMaterial(characterBase, material);
|
var (slotId, materialId) = FindMaterial(characterBase, material);
|
||||||
|
|
||||||
if (!validType
|
if (!validType
|
||||||
|| slotId == byte.MaxValue
|
|| slotId > 9
|
||||||
|
|| type is not MaterialValueIndex.DrawObjectType.Human && slotId > 0
|
||||||
|| !actor.Identifier(_actors, out var identifier)
|
|| !actor.Identifier(_actors, out var identifier)
|
||||||
|| !_stateManager.TryGetValue(identifier, out var state))
|
|| !_stateManager.TryGetValue(identifier, out var state))
|
||||||
return;
|
return;
|
||||||
|
|
@ -53,38 +58,60 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
|
||||||
if (!PrepareColorSet.TryGetColorTable(characterBase, material, stain, out var baseColorSet))
|
if (!PrepareColorSet.TryGetColorTable(characterBase, material, stain, out var baseColorSet))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (var i = 0; i < values.Length; ++i)
|
var drawData = type switch
|
||||||
{
|
{
|
||||||
var idx = MaterialValueIndex.FromKey(values[i].key);
|
MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId),
|
||||||
var (oldGame, model, source) = values[i].Value;
|
_ => GetTempSlot((Weapon*)characterBase),
|
||||||
ref var row = ref baseColorSet[idx.RowIndex];
|
};
|
||||||
if (!idx.DataIndex.TryGetValue(row, out var newGame))
|
UpdateMaterialValues(state, values, drawData, ref baseColorSet);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture))
|
if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture))
|
||||||
ret = (nint)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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
private (byte SlotId, byte MaterialId) FindMaterial(CharacterBase* characterBase, MaterialResourceHandle* material)
|
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);
|
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)
|
private static bool FindType(CharacterBase* characterBase, Actor actor, out MaterialValueIndex.DrawObjectType type)
|
||||||
{
|
{
|
||||||
type = MaterialValueIndex.DrawObjectType.Human;
|
type = MaterialValueIndex.DrawObjectType.Human;
|
||||||
|
|
@ -145,4 +173,26 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
|
||||||
|
|
||||||
return false;
|
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,
|
MaterialValueIndex.DrawObjectType DrawObject,
|
||||||
byte SlotIndex,
|
byte SlotIndex,
|
||||||
byte MaterialIndex,
|
byte MaterialIndex,
|
||||||
byte RowIndex,
|
byte RowIndex)
|
||||||
MaterialValueIndex.ColorTableIndex DataIndex)
|
|
||||||
{
|
{
|
||||||
public uint Key
|
public uint Key
|
||||||
=> ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex, DataIndex);
|
=> ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex);
|
||||||
|
|
||||||
public bool Valid
|
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)
|
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)
|
public unsafe bool TryGetColorTable(Texture** texture, out MtrlFile.ColorTable table)
|
||||||
=> DirectXTextureHelper.TryGetColorTable(*texture, out 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))
|
if (!TryGetColorTable(actor, out var table))
|
||||||
{
|
{
|
||||||
|
|
@ -108,40 +107,16 @@ public readonly record struct MaterialValueIndex(
|
||||||
return true;
|
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)
|
public static MaterialValueIndex FromKey(uint key)
|
||||||
=> new(key);
|
=> new(key);
|
||||||
|
|
||||||
public static MaterialValueIndex Min(DrawObjectType drawObject = 0, byte slotIndex = 0, byte materialIndex = 0, byte rowIndex = 0,
|
public static MaterialValueIndex Min(DrawObjectType drawObject = 0, byte slotIndex = 0, byte materialIndex = 0, byte rowIndex = 0)
|
||||||
ColorTableIndex dataIndex = 0)
|
=> new(drawObject, slotIndex, materialIndex, rowIndex);
|
||||||
=> new(drawObject, slotIndex, materialIndex, rowIndex, dataIndex);
|
|
||||||
|
|
||||||
public static MaterialValueIndex Max(DrawObjectType drawObject = (DrawObjectType)byte.MaxValue, byte slotIndex = byte.MaxValue,
|
public static MaterialValueIndex Max(DrawObjectType drawObject = (DrawObjectType)byte.MaxValue, byte slotIndex = byte.MaxValue,
|
||||||
byte materialIndex = byte.MaxValue, byte rowIndex = byte.MaxValue,
|
byte materialIndex = byte.MaxValue, byte rowIndex = byte.MaxValue)
|
||||||
ColorTableIndex dataIndex = (ColorTableIndex)byte.MaxValue)
|
=> new(drawObject, slotIndex, materialIndex, rowIndex);
|
||||||
=> new(drawObject, slotIndex, materialIndex, rowIndex, dataIndex);
|
|
||||||
|
|
||||||
public enum DrawObjectType : byte
|
public enum DrawObjectType : byte
|
||||||
{
|
{
|
||||||
|
|
@ -150,18 +125,6 @@ public readonly record struct MaterialValueIndex(
|
||||||
Offhand,
|
Offhand,
|
||||||
};
|
};
|
||||||
|
|
||||||
public enum ColorTableIndex : byte
|
|
||||||
{
|
|
||||||
Diffuse,
|
|
||||||
Specular,
|
|
||||||
SpecularStrength,
|
|
||||||
Emissive,
|
|
||||||
GlossStrength,
|
|
||||||
TileSet,
|
|
||||||
MaterialRepeat,
|
|
||||||
MaterialSkew,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool Validate(DrawObjectType type)
|
public static bool Validate(DrawObjectType type)
|
||||||
=> Enum.IsDefined(type);
|
=> Enum.IsDefined(type);
|
||||||
|
|
||||||
|
|
@ -174,22 +137,17 @@ public readonly record struct MaterialValueIndex(
|
||||||
public static bool ValidateRow(byte rowIndex)
|
public static bool ValidateRow(byte rowIndex)
|
||||||
=> rowIndex < MtrlFile.ColorTable.NumRows;
|
=> rowIndex < MtrlFile.ColorTable.NumRows;
|
||||||
|
|
||||||
public static bool Validate(ColorTableIndex dataIndex)
|
private static uint ToKey(DrawObjectType type, byte slotIndex, byte materialIndex, byte rowIndex)
|
||||||
=> Enum.IsDefined(dataIndex);
|
|
||||||
|
|
||||||
private static uint ToKey(DrawObjectType type, byte slotIndex, byte materialIndex, byte rowIndex, ColorTableIndex index)
|
|
||||||
{
|
{
|
||||||
var result = (uint)index & 0xFF;
|
var result = (uint)rowIndex;
|
||||||
result |= (uint)(rowIndex & 0xFF) << 8;
|
result |= (uint)materialIndex << 8;
|
||||||
result |= (uint)(materialIndex & 0xF) << 16;
|
result |= (uint)slotIndex << 16;
|
||||||
result |= (uint)(slotIndex & 0xFF) << 20;
|
result |= (uint)((byte)type << 24);
|
||||||
result |= (uint)((byte)type & 0xF) << 28;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MaterialValueIndex(uint key)
|
private MaterialValueIndex(uint key)
|
||||||
: this((DrawObjectType)((key >> 28) & 0xF), (byte)(key >> 20), (byte)((key >> 16) & 0xF), (byte)(key >> 8),
|
: this((DrawObjectType)(key >> 24), (byte)(key >> 16), (byte)(key >> 8), (byte)key)
|
||||||
(ColorTableIndex)(key & 0xFF))
|
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
private class Converter : JsonConverter<MaterialValueIndex>
|
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}.");
|
=> 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 StateMaterialManager = Glamourer.Interop.Material.MaterialValueManager<Glamourer.Interop.Material.MaterialValueState>;
|
||||||
global using DesignMaterialManager = Glamourer.Interop.Material.MaterialValueManager<Glamourer.Interop.Material.MaterialValueDesign>;
|
global using DesignMaterialManager = Glamourer.Interop.Material.MaterialValueManager<Glamourer.Interop.Material.MaterialValueDesign>;
|
||||||
|
using Glamourer.GameData;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
|
||||||
namespace Glamourer.Interop.Material;
|
namespace Glamourer.Interop.Material;
|
||||||
|
|
||||||
public record struct MaterialValueDesign(Vector3 Value, bool Enabled);
|
[JsonConverter(typeof(Converter))]
|
||||||
public record struct MaterialValueState(Vector3 Game, Vector3 Model, StateSource Source);
|
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>
|
public readonly struct MaterialValueManager<T>
|
||||||
{
|
{
|
||||||
|
|
@ -113,7 +343,7 @@ public readonly struct MaterialValueManager<T>
|
||||||
return count;
|
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);
|
=> MaterialValueManager.Filter<T>(CollectionsMarshal.AsSpan(_values), min, max);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
|
@ -187,6 +417,7 @@ public static class MaterialValueManager
|
||||||
maxIdx = ~maxIdx + idx;
|
maxIdx = ~maxIdx + idx;
|
||||||
return maxIdx > minIdx ? (minIdx, maxIdx - 1) : (-1, -1);
|
return maxIdx > minIdx ? (minIdx, maxIdx - 1) : (-1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
maxIdx += idx;
|
maxIdx += idx;
|
||||||
|
|
||||||
while (maxIdx < values.Length - 1 && values[maxIdx + 1].Key <= maxKey)
|
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>
|
/// <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)
|
uint key = 0)
|
||||||
{
|
{
|
||||||
// We already have an existing value.
|
// We already have an existing value.
|
||||||
|
|
@ -240,18 +240,18 @@ public class InternalStateEditor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update if edited.
|
// Update if edited.
|
||||||
state.Materials.UpdateValue(index, new MaterialValueState(gameValue, value, source), out _);
|
state.Materials.UpdateValue(index, newValue, out _);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We do not have an existing value.
|
// 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.
|
// 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)
|
if (!state.CanUnlock(key) || source is StateSource.Game)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Only add an entry if it is sufficiently different from the game value.
|
// Only add an entry if it is different from the game value.
|
||||||
return !value.NearEqual(gameValue) && state.Materials.TryAddValue(index, new MaterialValueState(gameValue, value, source));
|
return state.Materials.TryAddValue(index, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue,
|
public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue,
|
||||||
|
|
|
||||||
|
|
@ -276,7 +276,7 @@ public class StateApplier(
|
||||||
return data;
|
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)
|
if (!force && !_config.UseAdvancedParameters)
|
||||||
return;
|
return;
|
||||||
|
|
@ -289,14 +289,11 @@ public class StateApplier(
|
||||||
if (!index.TryGetColorTable(texture, out var table))
|
if (!index.TryGetColorTable(texture, out var table))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Vector3 actualValue;
|
|
||||||
if (value.HasValue)
|
if (value.HasValue)
|
||||||
actualValue = value.Value;
|
value.Value.Apply(ref table[index.RowIndex]);
|
||||||
else if (!PrepareColorSet.TryGetColorTable(actor, index, out var baseTable)
|
else if (PrepareColorSet.TryGetColorTable(actor, index, out var baseTable))
|
||||||
|| !index.DataIndex.TryGetValue(baseTable[index.RowIndex], out actualValue))
|
table[index.RowIndex] = baseTable[index.RowIndex];
|
||||||
continue;
|
else
|
||||||
|
|
||||||
if (!index.DataIndex.SetValue(ref table[index.RowIndex], actualValue))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
MaterialService.ReplaceColorTable(texture, table);
|
MaterialService.ReplaceColorTable(texture, table);
|
||||||
|
|
|
||||||
|
|
@ -165,15 +165,15 @@ public class StateEditor(
|
||||||
StateChanged.Invoke(StateChanged.Type.Parameter, settings.Source, state, actors, (old, @new, flag));
|
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;
|
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;
|
return;
|
||||||
|
|
||||||
var actors = Applier.ChangeMaterialValue(state, index, settings.Source.RequiresChange());
|
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")}.]");
|
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, value, index));
|
StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -282,6 +282,20 @@ public class StateEditor(
|
||||||
if (!settings.RespectManual || !state.Sources[meta].IsManual())
|
if (!settings.RespectManual || !state.Sources[meta].IsManual())
|
||||||
Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key);
|
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()
|
var actors = settings.Source.RequiresChange()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue