mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
DT material editor, supporting components
This commit is contained in:
parent
e8182f285e
commit
450751e43f
2 changed files with 248 additions and 0 deletions
71
Penumbra/UI/AdvancedWindow/Materials/ConstantEditors.cs
Normal file
71
Penumbra/UI/AdvancedWindow/Materials/ConstantEditors.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
using OtterGui.Text.Widget.Editors;
|
||||||
|
using Penumbra.GameData.Files.ShaderStructs;
|
||||||
|
|
||||||
|
namespace Penumbra.UI.AdvancedWindow.Materials;
|
||||||
|
|
||||||
|
public static class ConstantEditors
|
||||||
|
{
|
||||||
|
public static readonly IEditor<byte> DefaultFloat = Editors.DefaultFloat.AsByteEditor();
|
||||||
|
public static readonly IEditor<byte> DefaultInt = Editors.DefaultInt.AsByteEditor();
|
||||||
|
public static readonly IEditor<byte> DefaultIntAsFloat = Editors.DefaultInt.IntAsFloatEditor().AsByteEditor();
|
||||||
|
public static readonly IEditor<byte> DefaultColor = ColorEditor.HighDynamicRange.Reinterpreting<byte>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Material constants known to be encoded as native <see cref="int"/>s.
|
||||||
|
///
|
||||||
|
/// A <see cref="float"/> editor is nonfunctional for them, as typical values for these constants would fall into the IEEE 754 denormalized number range.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly FrozenSet<Name> KnownIntConstants;
|
||||||
|
|
||||||
|
static ConstantEditors()
|
||||||
|
{
|
||||||
|
IReadOnlyList<Name> knownIntConstants = [
|
||||||
|
"g_ToonIndex",
|
||||||
|
"g_ToonSpecIndex",
|
||||||
|
];
|
||||||
|
|
||||||
|
KnownIntConstants = knownIntConstants.ToFrozenSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEditor<byte> DefaultFor(Name name, MaterialTemplatePickers? materialTemplatePickers = null)
|
||||||
|
{
|
||||||
|
if (materialTemplatePickers != null)
|
||||||
|
{
|
||||||
|
if (name == Names.SphereMapIndexConstantName)
|
||||||
|
return materialTemplatePickers.SphereMapIndexPicker;
|
||||||
|
else if (name == Names.TileIndexConstantName)
|
||||||
|
return materialTemplatePickers.TileIndexPicker;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.Value != null && name.Value.EndsWith("Color"))
|
||||||
|
return DefaultColor;
|
||||||
|
|
||||||
|
if (KnownIntConstants.Contains(name))
|
||||||
|
return DefaultInt;
|
||||||
|
|
||||||
|
return DefaultFloat;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static IEditor<byte> AsByteEditor<T>(this IEditor<T> inner) where T : unmanaged
|
||||||
|
=> inner.Reinterpreting<byte>();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static IEditor<float> IntAsFloatEditor(this IEditor<int> inner)
|
||||||
|
=> inner.Converting<float>(value => int.CreateSaturating(MathF.Round(value)), value => value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static IEditor<T> WithExponent<T>(this IEditor<T> inner, T exponent)
|
||||||
|
where T : unmanaged, IPowerFunctions<T>, IComparisonOperators<T, T, bool>
|
||||||
|
=> exponent == T.MultiplicativeIdentity
|
||||||
|
? inner
|
||||||
|
: inner.Converting(value => value < T.Zero ? -T.Pow(-value, T.MultiplicativeIdentity / exponent) : T.Pow(value, T.MultiplicativeIdentity / exponent), value => value < T.Zero ? -T.Pow(-value, exponent) : T.Pow(value, exponent));
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static IEditor<T> WithFactorAndBias<T>(this IEditor<T> inner, T factor, T bias)
|
||||||
|
where T : unmanaged, IMultiplicativeIdentity<T, T>, IAdditiveIdentity<T, T>, IMultiplyOperators<T, T, T>, IAdditionOperators<T, T, T>, ISubtractionOperators<T, T, T>, IDivisionOperators<T, T, T>, IEqualityOperators<T, T, bool>
|
||||||
|
=> factor == T.MultiplicativeIdentity && bias == T.AdditiveIdentity
|
||||||
|
? inner
|
||||||
|
: inner.Converting(value => (value - bias) / factor, value => value * factor + bias);
|
||||||
|
}
|
||||||
177
Penumbra/UI/AdvancedWindow/Materials/MaterialTemplatePickers.cs
Normal file
177
Penumbra/UI/AdvancedWindow/Materials/MaterialTemplatePickers.cs
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using FFXIVClientStructs.Interop;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using OtterGui.Services;
|
||||||
|
using OtterGui.Text;
|
||||||
|
using OtterGui.Text.Widget.Editors;
|
||||||
|
using Penumbra.Interop.Services;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
|
||||||
|
namespace Penumbra.UI.AdvancedWindow.Materials;
|
||||||
|
|
||||||
|
public sealed unsafe class MaterialTemplatePickers : IUiService
|
||||||
|
{
|
||||||
|
private const float MaximumTextureSize = 64.0f;
|
||||||
|
|
||||||
|
private readonly TextureArraySlicer _textureArraySlicer;
|
||||||
|
private readonly CharacterUtility _characterUtility;
|
||||||
|
|
||||||
|
public readonly IEditor<byte> TileIndexPicker;
|
||||||
|
public readonly IEditor<byte> SphereMapIndexPicker;
|
||||||
|
|
||||||
|
public MaterialTemplatePickers(TextureArraySlicer textureArraySlicer, CharacterUtility characterUtility)
|
||||||
|
{
|
||||||
|
_textureArraySlicer = textureArraySlicer;
|
||||||
|
_characterUtility = characterUtility;
|
||||||
|
|
||||||
|
TileIndexPicker = new Editor(DrawTileIndexPicker).AsByteEditor();
|
||||||
|
SphereMapIndexPicker = new Editor(DrawSphereMapIndexPicker).AsByteEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DrawTileIndexPicker(ReadOnlySpan<byte> label, ReadOnlySpan<byte> description, ref ushort value, bool compact)
|
||||||
|
=> _characterUtility.Address != null
|
||||||
|
&& DrawTextureArrayIndexPicker(label, description, ref value, compact, [
|
||||||
|
_characterUtility.Address->TileOrbArrayTexResource,
|
||||||
|
_characterUtility.Address->TileNormArrayTexResource,
|
||||||
|
]);
|
||||||
|
|
||||||
|
public bool DrawSphereMapIndexPicker(ReadOnlySpan<byte> label, ReadOnlySpan<byte> description, ref ushort value, bool compact)
|
||||||
|
=> _characterUtility.Address != null
|
||||||
|
&& DrawTextureArrayIndexPicker(label, description, ref value, compact, [
|
||||||
|
_characterUtility.Address->SphereDArrayTexResource,
|
||||||
|
]);
|
||||||
|
|
||||||
|
public bool DrawTextureArrayIndexPicker(ReadOnlySpan<byte> label, ReadOnlySpan<byte> description, ref ushort value, bool compact, ReadOnlySpan<Pointer<TextureResourceHandle>> textureRHs)
|
||||||
|
{
|
||||||
|
TextureResourceHandle* firstNonNullTextureRH = null;
|
||||||
|
foreach (var texture in textureRHs)
|
||||||
|
{
|
||||||
|
if (texture.Value != null && texture.Value->CsHandle.Texture != null)
|
||||||
|
{
|
||||||
|
firstNonNullTextureRH = texture;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var firstNonNullTexture = firstNonNullTextureRH != null ? firstNonNullTextureRH->CsHandle.Texture : null;
|
||||||
|
|
||||||
|
var textureSize = firstNonNullTexture != null ? new Vector2(firstNonNullTexture->Width, firstNonNullTexture->Height).Contain(new Vector2(MaximumTextureSize)) : Vector2.Zero;
|
||||||
|
var count = firstNonNullTexture != null ? firstNonNullTexture->ArraySize : 0;
|
||||||
|
|
||||||
|
var ret = false;
|
||||||
|
|
||||||
|
var framePadding = ImGui.GetStyle().FramePadding;
|
||||||
|
var itemSpacing = ImGui.GetStyle().ItemSpacing;
|
||||||
|
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||||
|
{
|
||||||
|
var spaceSize = ImUtf8.CalcTextSize(" "u8).X;
|
||||||
|
var spaces = (int)((ImGui.CalcItemWidth() - framePadding.X * 2.0f - (compact ? 0.0f : (textureSize.X + itemSpacing.X) * textureRHs.Length)) / spaceSize);
|
||||||
|
using var padding = ImRaii.PushStyle(ImGuiStyleVar.FramePadding, framePadding + new Vector2(0.0f, Math.Max(textureSize.Y - ImGui.GetFrameHeight() + itemSpacing.Y, 0.0f) * 0.5f), !compact);
|
||||||
|
using var combo = ImUtf8.Combo(label, (value == ushort.MaxValue ? "-" : value.ToString()).PadLeft(spaces), ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.HeightLarge);
|
||||||
|
if (combo.Success && firstNonNullTextureRH != null)
|
||||||
|
{
|
||||||
|
var lineHeight = Math.Max(ImGui.GetTextLineHeightWithSpacing(), framePadding.Y * 2.0f + textureSize.Y);
|
||||||
|
var itemWidth = Math.Max(ImGui.GetContentRegionAvail().X, ImUtf8.CalcTextSize("MMM"u8).X + (itemSpacing.X + textureSize.X) * textureRHs.Length + framePadding.X * 2.0f);
|
||||||
|
using var center = ImRaii.PushStyle(ImGuiStyleVar.SelectableTextAlign, new Vector2(0, 0.5f));
|
||||||
|
using var clipper = ImUtf8.ListClipper(count, lineHeight);
|
||||||
|
while (clipper.Step())
|
||||||
|
{
|
||||||
|
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd && i < count; i++)
|
||||||
|
{
|
||||||
|
if (ImUtf8.Selectable($"{i,3}", i == value, size: new(itemWidth, lineHeight)))
|
||||||
|
{
|
||||||
|
ret = value != i;
|
||||||
|
value = (ushort)i;
|
||||||
|
}
|
||||||
|
var rectMin = ImGui.GetItemRectMin();
|
||||||
|
var rectMax = ImGui.GetItemRectMax();
|
||||||
|
var textureRegionStart = new Vector2(
|
||||||
|
rectMax.X - framePadding.X - textureSize.X * textureRHs.Length - itemSpacing.X * (textureRHs.Length - 1),
|
||||||
|
rectMin.Y + framePadding.Y);
|
||||||
|
var maxSize = new Vector2(textureSize.X, rectMax.Y - framePadding.Y - textureRegionStart.Y);
|
||||||
|
DrawTextureSlices(textureRegionStart, maxSize, itemSpacing.X, textureRHs, (byte)i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!compact && value != ushort.MaxValue)
|
||||||
|
{
|
||||||
|
var cbRectMin = ImGui.GetItemRectMin();
|
||||||
|
var cbRectMax = ImGui.GetItemRectMax();
|
||||||
|
var cbTextureRegionStart = new Vector2(cbRectMax.X - framePadding.X - textureSize.X * textureRHs.Length - itemSpacing.X * (textureRHs.Length - 1), cbRectMin.Y + framePadding.Y);
|
||||||
|
var cbMaxSize = new Vector2(textureSize.X, cbRectMax.Y - framePadding.Y - cbTextureRegionStart.Y);
|
||||||
|
DrawTextureSlices(cbTextureRegionStart, cbMaxSize, itemSpacing.X, textureRHs, (byte)value);
|
||||||
|
}
|
||||||
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) && (description.Length > 0 || compact && value != ushort.MaxValue))
|
||||||
|
{
|
||||||
|
using var disabled = ImRaii.Enabled();
|
||||||
|
using var tt = ImUtf8.Tooltip();
|
||||||
|
if (description.Length > 0)
|
||||||
|
ImUtf8.Text(description);
|
||||||
|
if (compact && value != ushort.MaxValue)
|
||||||
|
{
|
||||||
|
ImGui.Dummy(new Vector2(textureSize.X * textureRHs.Length + itemSpacing.X * (textureRHs.Length - 1), textureSize.Y));
|
||||||
|
var rectMin = ImGui.GetItemRectMin();
|
||||||
|
var rectMax = ImGui.GetItemRectMax();
|
||||||
|
DrawTextureSlices(rectMin, textureSize, itemSpacing.X, textureRHs, (byte)value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawTextureSlices(Vector2 regionStart, Vector2 itemSize, float itemSpacing, ReadOnlySpan<Pointer<TextureResourceHandle>> textureRHs, byte sliceIndex)
|
||||||
|
{
|
||||||
|
for (var j = 0; j < textureRHs.Length; ++j)
|
||||||
|
{
|
||||||
|
if (textureRHs[j].Value == null)
|
||||||
|
continue;
|
||||||
|
var texture = textureRHs[j].Value->CsHandle.Texture;
|
||||||
|
if (texture == null)
|
||||||
|
continue;
|
||||||
|
var handle = _textureArraySlicer.GetImGuiHandle(texture, sliceIndex);
|
||||||
|
if (handle == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var position = regionStart with { X = regionStart.X + (itemSize.X + itemSpacing) * j };
|
||||||
|
var size = new Vector2(texture->Width, texture->Height).Contain(itemSize);
|
||||||
|
position += (itemSize - size) * 0.5f;
|
||||||
|
ImGui.GetWindowDrawList().AddImage(handle, position, position + size, Vector2.Zero,
|
||||||
|
new Vector2(texture->Width / (float)texture->Width2, texture->Height / (float)texture->Height2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private delegate bool DrawEditor(ReadOnlySpan<byte> label, ReadOnlySpan<byte> description, ref ushort value, bool compact);
|
||||||
|
|
||||||
|
private sealed class Editor(DrawEditor draw) : IEditor<float>
|
||||||
|
{
|
||||||
|
public bool Draw(Span<float> values, bool disabled)
|
||||||
|
{
|
||||||
|
var helper = Editors.PrepareMultiComponent(values.Length);
|
||||||
|
var ret = false;
|
||||||
|
|
||||||
|
for (var valueIdx = 0; valueIdx < values.Length; ++valueIdx)
|
||||||
|
{
|
||||||
|
helper.SetupComponent(valueIdx);
|
||||||
|
|
||||||
|
var value = ushort.CreateSaturating(MathF.Round(values[valueIdx]));
|
||||||
|
if (disabled)
|
||||||
|
{
|
||||||
|
using var _ = ImRaii.Disabled();
|
||||||
|
draw(helper.Id, default, ref value, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (draw(helper.Id, default, ref value, true))
|
||||||
|
{
|
||||||
|
values[valueIdx] = value;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue