mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-15 04:17:51 +01:00
277 lines
11 KiB
C#
277 lines
11 KiB
C#
using ImSharp;
|
|
using Penumbra.Collections.Cache;
|
|
using Penumbra.GameData.Enums;
|
|
using Penumbra.GameData.Interop;
|
|
using Penumbra.Interop.PathResolving;
|
|
using Penumbra.Meta;
|
|
using Penumbra.Meta.Manipulations;
|
|
|
|
namespace Penumbra.UI.Tabs.Debug;
|
|
|
|
public class ShapeInspector(ObjectManager objects, CollectionResolver resolver) : Luna.IUiService
|
|
{
|
|
private int _objectIndex;
|
|
|
|
public void Draw()
|
|
{
|
|
Im.Input.Scalar("Object Index"u8, ref _objectIndex);
|
|
var actor = objects[0];
|
|
if (!actor.IsCharacter)
|
|
{
|
|
Im.Text("No valid character."u8);
|
|
return;
|
|
}
|
|
|
|
var human = actor.Model;
|
|
if (!human.IsHuman)
|
|
{
|
|
Im.Text("No valid character."u8);
|
|
return;
|
|
}
|
|
|
|
DrawCollectionShapeCache(actor);
|
|
DrawCharacterShapes(human);
|
|
DrawCollectionAttributeCache(actor);
|
|
DrawCharacterAttributes(human);
|
|
}
|
|
|
|
private unsafe void DrawCollectionAttributeCache(Actor actor)
|
|
{
|
|
var data = resolver.IdentifyCollection(actor.AsObject, true);
|
|
using var treeNode1 = Im.Tree.Node($"Collection Attribute Cache ({data.ModCollection})");
|
|
if (!treeNode1.Success || !data.ModCollection.HasCache)
|
|
return;
|
|
|
|
using var table = Im.Table.Begin("##aCache"u8, 2, TableFlags.RowBackground);
|
|
if (!table)
|
|
return;
|
|
|
|
table.SetupColumn("Attribute"u8, TableColumnFlags.WidthFixed, 150 * Im.Style.GlobalScale);
|
|
table.SetupColumn("State"u8, TableColumnFlags.WidthStretch);
|
|
table.HeaderRow();
|
|
|
|
foreach (var (attribute, set) in data.ModCollection.MetaCache!.Atr.Data.OrderBy(a => a.Key))
|
|
{
|
|
table.DrawColumn(attribute.AsSpan);
|
|
DrawValues(table, attribute, set);
|
|
}
|
|
}
|
|
|
|
private unsafe void DrawCollectionShapeCache(Actor actor)
|
|
{
|
|
var data = resolver.IdentifyCollection(actor.AsObject, true);
|
|
using var treeNode1 = Im.Tree.Node($"Collection Shape Cache ({data.ModCollection})");
|
|
if (!treeNode1.Success || !data.ModCollection.HasCache)
|
|
return;
|
|
|
|
using var table = Im.Table.Begin("##sCache"u8, 3, TableFlags.RowBackground);
|
|
if (!table)
|
|
return;
|
|
|
|
table.SetupColumn("Condition"u8, TableColumnFlags.WidthFixed, 150 * Im.Style.GlobalScale);
|
|
table.SetupColumn("Shape"u8, TableColumnFlags.WidthFixed, 150 * Im.Style.GlobalScale);
|
|
table.SetupColumn("State"u8, TableColumnFlags.WidthStretch);
|
|
table.HeaderRow();
|
|
|
|
foreach (var condition in ShapeConnectorCondition.Values)
|
|
{
|
|
foreach (var (shape, set) in data.ModCollection.MetaCache!.Shp.State(condition).OrderBy(shp => shp.Key))
|
|
{
|
|
table.DrawColumn($"{condition}");
|
|
table.DrawColumn(shape.AsSpan);
|
|
DrawValues(table, shape, set);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void DrawValues(in Im.TableDisposable table, in ShapeAttributeString _, ShapeAttributeHashSet set)
|
|
{
|
|
table.NextColumn();
|
|
var disabledColor = Im.Style[ImGuiColor.TextDisabled];
|
|
if (set.All is { } value)
|
|
{
|
|
using var color = ImGuiColor.Text.Push(disabledColor, !value);
|
|
Im.Text("All, "u8);
|
|
Im.Line.NoSpacing();
|
|
}
|
|
|
|
foreach (var slot in ShapeAttributeManager.UsedModels)
|
|
{
|
|
if (set[slot] is not { } value2)
|
|
continue;
|
|
|
|
using var color = ImGuiColor.Text.Push(disabledColor, !value2);
|
|
Im.Text($"All {slot.ToNameU8()}, ");
|
|
Im.Line.NoSpacing();
|
|
}
|
|
|
|
foreach (var gr in ShapeAttributeHashSet.GenderRaceValues.Skip(1))
|
|
{
|
|
if (set[gr] is { } value3)
|
|
{
|
|
using var color = ImGuiColor.Text.Push(disabledColor, !value3);
|
|
Im.Text($"All {gr.ToNameU8()}, ");
|
|
Im.Line.NoSpacing();
|
|
}
|
|
else
|
|
{
|
|
foreach (var slot in ShapeAttributeManager.UsedModels)
|
|
{
|
|
if (set[slot, gr] is not { } value4)
|
|
continue;
|
|
|
|
using var color = ImGuiColor.Text.Push(disabledColor, !value4);
|
|
Im.Text($"All {gr.ToNameU8()} {slot.ToNameU8()}, ");
|
|
Im.Line.NoSpacing();
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var ((slot, id), flags) in set)
|
|
{
|
|
if ((flags & 3) is not 0)
|
|
{
|
|
var enabled = (flags & 1) is 1;
|
|
|
|
if (set[slot, GenderRace.Unknown] != enabled)
|
|
{
|
|
using var color = ImGuiColor.Text.Push(disabledColor, !enabled);
|
|
Im.Text($"{slot.ToNameU8()} {id.Id:D4}, ");
|
|
Im.Line.NoSpacing();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var currentIndex = BitOperations.TrailingZeroCount(flags) / 2;
|
|
var currentFlags = flags >> (2 * currentIndex);
|
|
while (currentIndex < ShapeAttributeHashSet.GenderRaceValues.Count)
|
|
{
|
|
var enabled = (currentFlags & 1) is 1;
|
|
var gr = ShapeAttributeHashSet.GenderRaceValues[currentIndex];
|
|
if (set[slot, gr] != enabled)
|
|
{
|
|
using var color = ImGuiColor.Text.Push(disabledColor, !enabled);
|
|
Im.Text($"{gr.ToNameU8()} {slot.ToNameU8()} #{id.Id:D4}, ");
|
|
Im.Line.NoSpacing();
|
|
}
|
|
|
|
currentFlags &= ~0x3u;
|
|
currentIndex += BitOperations.TrailingZeroCount(currentFlags) / 2;
|
|
currentFlags = flags >> (2 * currentIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private unsafe void DrawCharacterShapes(Model human)
|
|
{
|
|
using var treeNode2 = Im.Tree.Node("Character Model Shapes"u8);
|
|
if (!treeNode2)
|
|
return;
|
|
|
|
using var table = Im.Table.Begin("##shapes"u8, 7, TableFlags.RowBackground);
|
|
if (!table)
|
|
return;
|
|
|
|
table.SetupColumn("#"u8, TableColumnFlags.WidthFixed, 25 * Im.Style.GlobalScale);
|
|
table.SetupColumn("Slot"u8, TableColumnFlags.WidthFixed, 150 * Im.Style.GlobalScale);
|
|
table.SetupColumn("Address"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 14);
|
|
table.SetupColumn("Mask"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 8);
|
|
table.SetupColumn("ID"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 4);
|
|
table.SetupColumn("Count"u8, TableColumnFlags.WidthFixed, 30 * Im.Style.GlobalScale);
|
|
table.SetupColumn("Shapes"u8, TableColumnFlags.WidthStretch);
|
|
table.HeaderRow();
|
|
|
|
var disabledColor = Im.Style[ImGuiColor.TextDisabled];
|
|
for (var i = 0; i < human.AsHuman->SlotCount; ++i)
|
|
{
|
|
table.DrawColumn($"{(uint)i:D2}");
|
|
table.DrawColumn(((HumanSlot)i).ToNameU8());
|
|
|
|
table.NextColumn();
|
|
var model = human.AsHuman->Models[i];
|
|
Penumbra.Dynamis.DrawPointer((nint)model);
|
|
if (model is not null)
|
|
{
|
|
var mask = model->EnabledShapeKeyIndexMask;
|
|
table.DrawColumn($"{mask:X8}");
|
|
table.DrawColumn($"{human.GetModelId((HumanSlot)i):D4}");
|
|
table.DrawColumn($"{model->ModelResourceHandle->Shapes.Count}");
|
|
table.NextColumn();
|
|
foreach (var (idx, (shape, flag)) in model->ModelResourceHandle->Shapes.Index())
|
|
{
|
|
var disabled = (mask & (1u << flag)) is 0;
|
|
using var color = ImGuiColor.Text.Push(disabledColor, disabled);
|
|
Im.Text(shape.AsSpan());
|
|
Im.Line.NoSpacing();
|
|
Im.Text(", "u8);
|
|
if (idx % 8 < 7)
|
|
Im.Line.NoSpacing();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
table.NextColumn();
|
|
table.NextColumn();
|
|
table.NextColumn();
|
|
table.NextColumn();
|
|
}
|
|
}
|
|
}
|
|
|
|
private unsafe void DrawCharacterAttributes(Model human)
|
|
{
|
|
using var treeNode2 = Im.Tree.Node("Character Model Attributes"u8);
|
|
if (!treeNode2)
|
|
return;
|
|
|
|
using var table = Im.Table.Begin("##attributes"u8, 7, TableFlags.RowBackground);
|
|
if (!table)
|
|
return;
|
|
|
|
table.SetupColumn("#"u8, TableColumnFlags.WidthFixed, 25 * Im.Style.GlobalScale);
|
|
table.SetupColumn("Slot"u8, TableColumnFlags.WidthFixed, 150 * Im.Style.GlobalScale);
|
|
table.SetupColumn("Address"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 14);
|
|
table.SetupColumn("Mask"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 8);
|
|
table.SetupColumn("ID"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 4);
|
|
table.SetupColumn("Count"u8, TableColumnFlags.WidthFixed, 30 * Im.Style.GlobalScale);
|
|
table.SetupColumn("Attributes"u8, TableColumnFlags.WidthStretch);
|
|
table.HeaderRow();
|
|
|
|
var disabledColor = Im.Style[ImGuiColor.TextDisabled];
|
|
for (var i = 0; i < human.AsHuman->SlotCount; ++i)
|
|
{
|
|
table.DrawColumn($"{(uint)i:D2}");
|
|
table.DrawColumn(((HumanSlot)i).ToNameU8());
|
|
|
|
table.NextColumn();
|
|
var model = human.AsHuman->Models[i];
|
|
Penumbra.Dynamis.DrawPointer((nint)model);
|
|
if (model is not null)
|
|
{
|
|
var mask = model->EnabledAttributeIndexMask;
|
|
table.DrawColumn($"{mask:X8}");
|
|
table.DrawColumn($"{human.GetModelId((HumanSlot)i):D4}");
|
|
table.DrawColumn($"{model->ModelResourceHandle->Attributes.Count}");
|
|
table.NextColumn();
|
|
foreach (var (idx, (attribute, flag)) in model->ModelResourceHandle->Attributes.Index())
|
|
{
|
|
var disabled = (mask & (1u << flag)) is 0;
|
|
using var color = ImGuiColor.Text.Push(disabledColor, disabled);
|
|
Im.Text(attribute.AsSpan());
|
|
Im.Line.NoSpacing();
|
|
Im.Text(", "u8);
|
|
if (idx % 8 < 7)
|
|
Im.Line.NoSpacing();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
table.NextColumn();
|
|
table.NextColumn();
|
|
table.NextColumn();
|
|
table.NextColumn();
|
|
}
|
|
}
|
|
}
|
|
}
|