mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Material editor 2099
This commit is contained in:
parent
f64fdd2b26
commit
b8d09ab660
15 changed files with 1221 additions and 651 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit 863d08bd83381bb7fe4a8d5c514f0ba55379336f
|
||||
Subproject commit 1e172ee9a0f5946d67b848a36b2be97f6541453f
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 97643cad67b6981c3ee510d1ca12c4321e6a80bf
|
||||
Subproject commit 07c001c5b2b35b2dba2b8428389d3ed375728616
|
||||
|
|
@ -190,7 +190,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
|
||||
if (WithNames)
|
||||
{
|
||||
var name = samplers != null && i < samplers.Count ? samplers[i].Item2?.Name : null;
|
||||
var name = samplers != null && i < samplers.Length ? samplers[i].ShpkSampler?.Name : null;
|
||||
node.Children.Add(texNode.WithName(name ?? $"Texture #{i}"));
|
||||
}
|
||||
else
|
||||
|
|
|
|||
15
Penumbra/Interop/Structs/CharacterBaseExt.cs
Normal file
15
Penumbra/Interop/Structs/CharacterBaseExt.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
|
||||
namespace Penumbra.Interop.Structs;
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public unsafe struct CharacterBaseExt
|
||||
{
|
||||
[FieldOffset( 0x0 )]
|
||||
public CharacterBase CharacterBase;
|
||||
|
||||
[FieldOffset( 0x258 )]
|
||||
public Texture** ColorSetTextures;
|
||||
}
|
||||
|
|
@ -9,6 +9,9 @@ public unsafe struct HumanExt
|
|||
[FieldOffset( 0x0 )]
|
||||
public Human Human;
|
||||
|
||||
[FieldOffset( 0x0 )]
|
||||
public CharacterBaseExt CharacterBase;
|
||||
|
||||
[FieldOffset( 0x9E8 )]
|
||||
public ResourceHandle* Decal;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,22 +13,44 @@ namespace Penumbra.UI.AdvancedWindow;
|
|||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private static readonly float HalfMinValue = (float)Half.MinValue;
|
||||
private static readonly float HalfMaxValue = (float)Half.MaxValue;
|
||||
private static readonly float HalfEpsilon = (float)Half.Epsilon;
|
||||
|
||||
private bool DrawMaterialColorSetChange( MtrlTab tab, bool disabled )
|
||||
{
|
||||
if( !tab.Mtrl.ColorSets.Any( c => c.HasRows ) )
|
||||
if( !tab.SamplerIds.Contains( ShpkFile.TableSamplerId ) || !tab.Mtrl.ColorSets.Any( c => c.HasRows ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
if( !ImGui.CollapsingHeader( "Color Set", ImGuiTreeNodeFlags.DefaultOpen ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var hasAnyDye = tab.UseColorDyeSet;
|
||||
|
||||
ColorSetCopyAllClipboardButton( tab.Mtrl, 0 );
|
||||
ImGui.SameLine();
|
||||
var ret = ColorSetPasteAllClipboardButton( tab, 0 );
|
||||
ImGui.SameLine();
|
||||
ImGui.Dummy( ImGuiHelpers.ScaledVector2( 20, 0 ) );
|
||||
ImGui.SameLine();
|
||||
ret |= DrawPreviewDye( tab, disabled );
|
||||
var ret = ColorSetPasteAllClipboardButton( tab, 0, disabled );
|
||||
if( !disabled )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.Dummy( ImGuiHelpers.ScaledVector2( 20, 0 ) );
|
||||
ImGui.SameLine();
|
||||
ret |= ColorSetDyeableCheckbox( tab, ref hasAnyDye );
|
||||
}
|
||||
if( hasAnyDye )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.Dummy( ImGuiHelpers.ScaledVector2( 20, 0 ) );
|
||||
ImGui.SameLine();
|
||||
ret |= DrawPreviewDye( tab, disabled );
|
||||
}
|
||||
|
||||
using var table = ImRaii.Table( "##ColorSets", 11,
|
||||
using var table = ImRaii.Table( "##ColorSets", hasAnyDye ? 11 : 9,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerV );
|
||||
if( !table )
|
||||
{
|
||||
|
|
@ -53,17 +75,20 @@ public partial class ModEditWindow
|
|||
ImGui.TableHeader( "Repeat" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Skew" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Dye" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Dye Preview" );
|
||||
if( hasAnyDye )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader("Dye");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader("Dye Preview");
|
||||
}
|
||||
|
||||
for( var j = 0; j < tab.Mtrl.ColorSets.Length; ++j )
|
||||
{
|
||||
using var _ = ImRaii.PushId( j );
|
||||
for( var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i )
|
||||
{
|
||||
ret |= DrawColorSetRow( tab, j, i, disabled );
|
||||
ret |= DrawColorSetRow( tab, j, i, disabled, hasAnyDye );
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
}
|
||||
|
|
@ -122,9 +147,9 @@ public partial class ModEditWindow
|
|||
return false;
|
||||
}
|
||||
|
||||
private static unsafe bool ColorSetPasteAllClipboardButton( MtrlTab tab, int colorSetIdx )
|
||||
private static unsafe bool ColorSetPasteAllClipboardButton( MtrlTab tab, int colorSetIdx, bool disabled )
|
||||
{
|
||||
if( !ImGui.Button( "Import All Rows from Clipboard", ImGuiHelpers.ScaledVector2( 200, 0 ) ) || tab.Mtrl.ColorSets.Length <= colorSetIdx )
|
||||
if( !ImGuiUtil.DrawDisabledButton( "Import All Rows from Clipboard", ImGuiHelpers.ScaledVector2( 200, 0 ), string.Empty, disabled ) || tab.Mtrl.ColorSets.Length <= colorSetIdx )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -187,6 +212,21 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
|
||||
private static bool ColorSetDyeableCheckbox( MtrlTab tab, ref bool dyeable )
|
||||
{
|
||||
var ret = ImGui.Checkbox( "Dyeable", ref dyeable );
|
||||
|
||||
if( ret )
|
||||
{
|
||||
tab.UseColorDyeSet = dyeable;
|
||||
if( dyeable )
|
||||
tab.Mtrl.FindOrAddColorDyeSet();
|
||||
tab.UpdateColorSetPreview();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static unsafe bool ColorSetPasteFromClipboardButton( MtrlTab tab, int colorSetIdx, int rowIdx, bool disabled )
|
||||
{
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Paste.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
|
|
@ -235,7 +275,7 @@ public partial class ModEditWindow
|
|||
tab.CancelColorSetHighlight();
|
||||
}
|
||||
|
||||
private bool DrawColorSetRow( MtrlTab tab, int colorSetIdx, int rowIdx, bool disabled )
|
||||
private bool DrawColorSetRow( MtrlTab tab, int colorSetIdx, int rowIdx, bool disabled, bool hasAnyDye )
|
||||
{
|
||||
static bool FixFloat( ref float val, float current )
|
||||
{
|
||||
|
|
@ -245,7 +285,7 @@ public partial class ModEditWindow
|
|||
|
||||
using var id = ImRaii.PushId( rowIdx );
|
||||
var row = tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ];
|
||||
var hasDye = tab.Mtrl.ColorDyeSets.Length > colorSetIdx;
|
||||
var hasDye = hasAnyDye && tab.Mtrl.ColorDyeSets.Length > colorSetIdx;
|
||||
var dye = hasDye ? tab.Mtrl.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ] : new MtrlFile.ColorDyeSet.Row();
|
||||
var floatSize = 70 * UiHelpers.Scale;
|
||||
var intSize = 45 * UiHelpers.Scale;
|
||||
|
|
@ -274,7 +314,7 @@ public partial class ModEditWindow
|
|||
ImGui.SameLine();
|
||||
var tmpFloat = row.SpecularStrength;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##SpecularStrength", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.SpecularStrength ) )
|
||||
if( ImGui.DragFloat( "##SpecularStrength", ref tmpFloat, 0.1f, 0f, HalfMaxValue, "%.2f" ) && FixFloat( ref tmpFloat, row.SpecularStrength ) )
|
||||
{
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].SpecularStrength = tmpFloat;
|
||||
ret = true;
|
||||
|
|
@ -305,9 +345,9 @@ public partial class ModEditWindow
|
|||
ImGui.TableNextColumn();
|
||||
tmpFloat = row.GlossStrength;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##GlossStrength", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.GlossStrength ) )
|
||||
if( ImGui.DragFloat( "##GlossStrength", ref tmpFloat, Math.Max( 0.1f, tmpFloat * 0.025f ), HalfEpsilon, HalfMaxValue, "%.1f" ) && FixFloat( ref tmpFloat, row.GlossStrength ) )
|
||||
{
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].GlossStrength = tmpFloat;
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].GlossStrength = Math.Max(tmpFloat, HalfEpsilon);
|
||||
ret = true;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
|
@ -323,9 +363,9 @@ public partial class ModEditWindow
|
|||
ImGui.TableNextColumn();
|
||||
int tmpInt = row.TileSet;
|
||||
ImGui.SetNextItemWidth( intSize );
|
||||
if( ImGui.InputInt( "##TileSet", ref tmpInt, 0, 0 ) && tmpInt != row.TileSet && tmpInt is >= 0 and <= ushort.MaxValue )
|
||||
if( ImGui.DragInt( "##TileSet", ref tmpInt, 0.25f, 0, 63 ) && tmpInt != row.TileSet && tmpInt is >= 0 and <= ushort.MaxValue )
|
||||
{
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].TileSet = ( ushort )tmpInt;
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].TileSet = ( ushort )Math.Clamp(tmpInt, 0, 63);
|
||||
ret = true;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
|
@ -335,7 +375,7 @@ public partial class ModEditWindow
|
|||
ImGui.TableNextColumn();
|
||||
tmpFloat = row.MaterialRepeat.X;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##RepeatX", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.MaterialRepeat.X ) )
|
||||
if( ImGui.DragFloat( "##RepeatX", ref tmpFloat, 0.1f, HalfMinValue, HalfMaxValue, "%.2f" ) && FixFloat( ref tmpFloat, row.MaterialRepeat.X ) )
|
||||
{
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialRepeat = row.MaterialRepeat with { X = tmpFloat };
|
||||
ret = true;
|
||||
|
|
@ -346,7 +386,7 @@ public partial class ModEditWindow
|
|||
ImGui.SameLine();
|
||||
tmpFloat = row.MaterialRepeat.Y;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##RepeatY", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.MaterialRepeat.Y ) )
|
||||
if( ImGui.DragFloat( "##RepeatY", ref tmpFloat, 0.1f, HalfMinValue, HalfMaxValue, "%.2f" ) && FixFloat( ref tmpFloat, row.MaterialRepeat.Y ) )
|
||||
{
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialRepeat = row.MaterialRepeat with { Y = tmpFloat };
|
||||
ret = true;
|
||||
|
|
@ -358,7 +398,7 @@ public partial class ModEditWindow
|
|||
ImGui.TableNextColumn();
|
||||
tmpFloat = row.MaterialSkew.X;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##SkewX", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.MaterialSkew.X ) )
|
||||
if( ImGui.DragFloat( "##SkewX", ref tmpFloat, 0.1f, HalfMinValue, HalfMaxValue, "%.2f" ) && FixFloat( ref tmpFloat, row.MaterialSkew.X ) )
|
||||
{
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialSkew = row.MaterialSkew with { X = tmpFloat };
|
||||
ret = true;
|
||||
|
|
@ -370,7 +410,7 @@ public partial class ModEditWindow
|
|||
ImGui.SameLine();
|
||||
tmpFloat = row.MaterialSkew.Y;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##SkewY", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.MaterialSkew.Y ) )
|
||||
if( ImGui.DragFloat( "##SkewY", ref tmpFloat, 0.1f, HalfMinValue, HalfMaxValue, "%.2f" ) && FixFloat( ref tmpFloat, row.MaterialSkew.Y ) )
|
||||
{
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialSkew = row.MaterialSkew with { Y = tmpFloat };
|
||||
ret = true;
|
||||
|
|
@ -379,10 +419,10 @@ public partial class ModEditWindow
|
|||
|
||||
ImGuiUtil.HoverTooltip( "Skew Y", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if( hasDye )
|
||||
{
|
||||
if(_stainService.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize
|
||||
ImGui.TableNextColumn();
|
||||
if (_stainService.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize
|
||||
+ ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton ) )
|
||||
{
|
||||
tab.Mtrl.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = _stainService.TemplateCombo.CurrentSelection;
|
||||
|
|
@ -395,9 +435,10 @@ public partial class ModEditWindow
|
|||
ImGui.TableNextColumn();
|
||||
ret |= DrawDyePreview( tab, colorSetIdx, rowIdx, disabled, dye, floatSize );
|
||||
}
|
||||
else
|
||||
else if ( hasAnyDye )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -431,10 +472,10 @@ public partial class ModEditWindow
|
|||
ImGui.SameLine();
|
||||
using var dis = ImRaii.Disabled();
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
ImGui.DragFloat( "##gloss", ref values.Gloss, 0, 0, 0, "%.2f G" );
|
||||
ImGui.DragFloat( "##gloss", ref values.Gloss, 0, values.Gloss, values.Gloss, "%.1f G" );
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
ImGui.DragFloat( "##specularStrength", ref values.SpecularPower, 0, 0, 0, "%.2f S" );
|
||||
ImGui.DragFloat( "##specularStrength", ref values.SpecularPower, 0, values.SpecularPower, values.SpecularPower, "%.2f S" );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,235 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private interface IConstantEditor
|
||||
{
|
||||
bool Draw(Span<float> values, bool disabled, float editorWidth);
|
||||
}
|
||||
|
||||
private sealed class FloatConstantEditor : IConstantEditor
|
||||
{
|
||||
public static readonly FloatConstantEditor Default = new(null, null, 0.1f, 0.0f, 1.0f, 0.0f, 3, string.Empty);
|
||||
|
||||
private readonly float? _minimum;
|
||||
private readonly float? _maximum;
|
||||
private readonly float _speed;
|
||||
private readonly float _relativeSpeed;
|
||||
private readonly float _factor;
|
||||
private readonly float _bias;
|
||||
private readonly string _format;
|
||||
|
||||
public FloatConstantEditor(float? minimum, float? maximum, float speed, float relativeSpeed, float factor, float bias, byte precision, string unit)
|
||||
{
|
||||
_minimum = minimum;
|
||||
_maximum = maximum;
|
||||
_speed = speed;
|
||||
_relativeSpeed = relativeSpeed;
|
||||
_factor = factor;
|
||||
_bias = bias;
|
||||
_format = $"%.{Math.Min(precision, (byte)9)}f";
|
||||
if (unit.Length > 0)
|
||||
_format = $"{_format} {unit.Replace("%", "%%")}";
|
||||
}
|
||||
|
||||
public bool Draw(Span<float> values, bool disabled, float editorWidth)
|
||||
{
|
||||
var fieldWidth = (editorWidth - (values.Length - 1) * ImGui.GetStyle().ItemSpacing.X) / values.Length;
|
||||
|
||||
var ret = false;
|
||||
|
||||
for (var valueIdx = 0; valueIdx < values.Length; ++valueIdx)
|
||||
{
|
||||
if (valueIdx > 0)
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.SetNextItemWidth(MathF.Round(fieldWidth * (valueIdx + 1)) - MathF.Round(fieldWidth * valueIdx));
|
||||
|
||||
var value = (values[valueIdx] - _bias) / _factor;
|
||||
if (disabled)
|
||||
ImGui.DragFloat($"##{valueIdx}", ref value, Math.Max(_speed, value * _relativeSpeed), value, value, _format);
|
||||
else
|
||||
{
|
||||
if (ImGui.DragFloat($"##{valueIdx}", ref value, Math.Max(_speed, value * _relativeSpeed), _minimum ?? 0.0f, _maximum ?? 0.0f, _format))
|
||||
{
|
||||
values[valueIdx] = Clamp(value) * _factor + _bias;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private float Clamp(float value)
|
||||
=> Math.Clamp(value, _minimum ?? float.NegativeInfinity, _maximum ?? float.PositiveInfinity);
|
||||
}
|
||||
|
||||
private sealed class IntConstantEditor : IConstantEditor
|
||||
{
|
||||
private readonly int? _minimum;
|
||||
private readonly int? _maximum;
|
||||
private readonly float _speed;
|
||||
private readonly float _relativeSpeed;
|
||||
private readonly float _factor;
|
||||
private readonly float _bias;
|
||||
private readonly string _format;
|
||||
|
||||
public IntConstantEditor(int? minimum, int? maximum, float speed, float relativeSpeed, float factor, float bias, string unit)
|
||||
{
|
||||
_minimum = minimum;
|
||||
_maximum = maximum;
|
||||
_speed = speed;
|
||||
_relativeSpeed = relativeSpeed;
|
||||
_factor = factor;
|
||||
_bias = bias;
|
||||
_format = "%d";
|
||||
if (unit.Length > 0)
|
||||
_format = $"{_format} {unit.Replace("%", "%%")}";
|
||||
}
|
||||
|
||||
public bool Draw(Span<float> values, bool disabled, float editorWidth)
|
||||
{
|
||||
var fieldWidth = (editorWidth - (values.Length - 1) * ImGui.GetStyle().ItemSpacing.X) / values.Length;
|
||||
|
||||
var ret = false;
|
||||
|
||||
for (var valueIdx = 0; valueIdx < values.Length; ++valueIdx)
|
||||
{
|
||||
if (valueIdx > 0)
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.SetNextItemWidth(MathF.Round(fieldWidth * (valueIdx + 1)) - MathF.Round(fieldWidth * valueIdx));
|
||||
|
||||
var value = (int)Math.Clamp(MathF.Round((values[valueIdx] - _bias) / _factor), int.MinValue, int.MaxValue);
|
||||
if (disabled)
|
||||
ImGui.DragInt($"##{valueIdx}", ref value, Math.Max(_speed, value * _relativeSpeed), value, value, _format);
|
||||
else
|
||||
{
|
||||
if (ImGui.DragInt($"##{valueIdx}", ref value, Math.Max(_speed, value * _relativeSpeed), _minimum ?? 0, _maximum ?? 0, _format))
|
||||
{
|
||||
values[valueIdx] = Clamp(value) * _factor + _bias;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private int Clamp(int value)
|
||||
=> Math.Clamp(value, _minimum ?? int.MinValue, _maximum ?? int.MaxValue);
|
||||
}
|
||||
|
||||
private sealed class ColorConstantEditor : IConstantEditor
|
||||
{
|
||||
private readonly bool _squaredRgb;
|
||||
private readonly bool _clamped;
|
||||
|
||||
public ColorConstantEditor(bool squaredRgb, bool clamped)
|
||||
{
|
||||
_squaredRgb = squaredRgb;
|
||||
_clamped = clamped;
|
||||
}
|
||||
|
||||
public bool Draw(Span<float> values, bool disabled, float editorWidth)
|
||||
{
|
||||
if (values.Length == 3)
|
||||
{
|
||||
ImGui.SetNextItemWidth(editorWidth);
|
||||
var value = new Vector3(values);
|
||||
if (_squaredRgb)
|
||||
value = Vector3.SquareRoot(value);
|
||||
if (ImGui.ColorEdit3("##0", ref value) && !disabled)
|
||||
{
|
||||
if (_squaredRgb)
|
||||
value *= value;
|
||||
if (_clamped)
|
||||
value = Vector3.Clamp(value, Vector3.Zero, Vector3.One);
|
||||
value.CopyTo(values);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (values.Length == 4)
|
||||
{
|
||||
ImGui.SetNextItemWidth(editorWidth);
|
||||
var value = new Vector4(values);
|
||||
if (_squaredRgb)
|
||||
value = new Vector4(MathF.Sqrt(value.X), MathF.Sqrt(value.Y), MathF.Sqrt(value.Z), value.W);
|
||||
if (ImGui.ColorEdit4("##0", ref value) && !disabled)
|
||||
{
|
||||
if (_squaredRgb)
|
||||
value *= new Vector4(value.X, value.Y, value.Z, 1.0f);
|
||||
if (_clamped)
|
||||
value = Vector4.Clamp(value, Vector4.Zero, Vector4.One);
|
||||
value.CopyTo(values);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return FloatConstantEditor.Default.Draw(values, disabled, editorWidth);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EnumConstantEditor : IConstantEditor
|
||||
{
|
||||
private readonly IReadOnlyList<(string Label, float Value, string Description)> _values;
|
||||
|
||||
public EnumConstantEditor(IReadOnlyList<(string Label, float Value, string Description)> values)
|
||||
{
|
||||
_values = values;
|
||||
}
|
||||
|
||||
public bool Draw(Span<float> values, bool disabled, float editorWidth)
|
||||
{
|
||||
var fieldWidth = (editorWidth - (values.Length - 1) * ImGui.GetStyle().ItemSpacing.X) / values.Length;
|
||||
|
||||
var ret = false;
|
||||
|
||||
for (var valueIdx = 0; valueIdx < values.Length; ++valueIdx)
|
||||
{
|
||||
if (valueIdx > 0)
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.SetNextItemWidth(MathF.Round(fieldWidth * (valueIdx + 1)) - MathF.Round(fieldWidth * valueIdx));
|
||||
|
||||
var currentValue = values[valueIdx];
|
||||
var (currentLabel, _, currentDescription) = _values.FirstOrNull(v => v.Value == currentValue) ?? (currentValue.ToString(), currentValue, string.Empty);
|
||||
if (disabled)
|
||||
ImGui.InputText($"##{valueIdx}", ref currentLabel, (uint)currentLabel.Length, ImGuiInputTextFlags.ReadOnly);
|
||||
else
|
||||
{
|
||||
using var c = ImRaii.Combo($"##{valueIdx}", currentLabel);
|
||||
{
|
||||
if (c)
|
||||
foreach (var (valueLabel, value, valueDescription) in _values)
|
||||
{
|
||||
if (ImGui.Selectable(valueLabel, value == currentValue))
|
||||
{
|
||||
values[valueIdx] = value;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (valueDescription.Length > 0)
|
||||
ImGuiUtil.SelectableHelpMarker(valueDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -398,7 +398,7 @@ public partial class ModEditWindow
|
|||
if (mtrlHandle == null)
|
||||
throw new InvalidOperationException("Material doesn't have a resource handle");
|
||||
|
||||
var colorSetTextures = *(Texture***)((nint)DrawObject + 0x258);
|
||||
var colorSetTextures = ((Structs.CharacterBaseExt*)DrawObject)->ColorSetTextures;
|
||||
if (colorSetTextures == null)
|
||||
throw new InvalidOperationException("Draw object doesn't have color set textures");
|
||||
|
||||
|
|
@ -424,7 +424,8 @@ public partial class ModEditWindow
|
|||
if (reset)
|
||||
{
|
||||
var oldTexture = (Texture*)Interlocked.Exchange(ref *(nint*)_colorSetTexture, (nint)_originalColorSetTexture);
|
||||
Structs.TextureUtility.DecRef(oldTexture);
|
||||
if (oldTexture != null)
|
||||
Structs.TextureUtility.DecRef(oldTexture);
|
||||
}
|
||||
else
|
||||
Structs.TextureUtility.DecRef(_originalColorSetTexture);
|
||||
|
|
@ -460,7 +461,8 @@ public partial class ModEditWindow
|
|||
if (success)
|
||||
{
|
||||
var oldTexture = (Texture*)Interlocked.Exchange(ref *(nint*)_colorSetTexture, (nint)newTexture);
|
||||
Structs.TextureUtility.DecRef(oldTexture);
|
||||
if (oldTexture != null)
|
||||
Structs.TextureUtility.DecRef(oldTexture);
|
||||
}
|
||||
else
|
||||
Structs.TextureUtility.DecRef(newTexture);
|
||||
|
|
@ -471,7 +473,7 @@ public partial class ModEditWindow
|
|||
if (!base.IsStillValid())
|
||||
return false;
|
||||
|
||||
var colorSetTextures = *(Texture***)((nint)DrawObject + 0x258);
|
||||
var colorSetTextures = ((Structs.CharacterBaseExt*)DrawObject)->ColorSetTextures;
|
||||
if (colorSetTextures == null)
|
||||
return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Dalamud.Interface.Internal.Notifications;
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
|
|
@ -16,6 +17,7 @@ using Penumbra.GameData.Data;
|
|||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
using static Penumbra.GameData.Files.ShpkFile;
|
||||
|
|
@ -26,49 +28,47 @@ public partial class ModEditWindow
|
|||
{
|
||||
private sealed class MtrlTab : IWritable, IDisposable
|
||||
{
|
||||
private const int ShpkPrefixLength = 16;
|
||||
|
||||
private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk/"u8, true, true, true);
|
||||
|
||||
private readonly ModEditWindow _edit;
|
||||
public readonly MtrlFile Mtrl;
|
||||
public readonly string FilePath;
|
||||
public readonly bool Writable;
|
||||
|
||||
public uint NewKeyId;
|
||||
public uint NewKeyDefault;
|
||||
public uint NewConstantId;
|
||||
public int NewConstantIdx;
|
||||
public uint NewSamplerId;
|
||||
public int NewSamplerIdx;
|
||||
private string[]? _shpkNames;
|
||||
|
||||
public string ShaderHeader = "Shader###Shader";
|
||||
public FullPath LoadedShpkPath = FullPath.Empty;
|
||||
public string LoadedShpkPathName = string.Empty;
|
||||
public string LoadedShpkDevkitPathName = string.Empty;
|
||||
public string ShaderComment = string.Empty;
|
||||
public ShpkFile? AssociatedShpk;
|
||||
public JObject? AssociatedShpkDevkit;
|
||||
|
||||
public ShpkFile? AssociatedShpk;
|
||||
public readonly List< string > TextureLabels = new(4);
|
||||
public FullPath LoadedShpkPath = FullPath.Empty;
|
||||
public string LoadedShpkPathName = string.Empty;
|
||||
public float TextureLabelWidth;
|
||||
public readonly string LoadedBaseDevkitPathName = string.Empty;
|
||||
public readonly JObject? AssociatedBaseDevkit;
|
||||
|
||||
// Shader Key State
|
||||
public readonly List< string > ShaderKeyLabels = new(16);
|
||||
public readonly Dictionary< uint, uint > DefinedShaderKeys = new(16);
|
||||
public readonly List< int > MissingShaderKeyIndices = new(16);
|
||||
public readonly List< uint > AvailableKeyValues = new(16);
|
||||
public string VertexShaders = "Vertex Shaders: ???";
|
||||
public string PixelShaders = "Pixel Shaders: ???";
|
||||
public readonly List< (string Label, int Index, string Description, bool MonoFont, IReadOnlyList< (string Label, uint Value, string Description) > Values) > ShaderKeys = new(16);
|
||||
|
||||
public readonly HashSet< int > VertexShaders = new(16);
|
||||
public readonly HashSet< int > PixelShaders = new(16);
|
||||
public bool ShadersKnown = false;
|
||||
public string VertexShadersString = "Vertex Shaders: ???";
|
||||
public string PixelShadersString = "Pixel Shaders: ???";
|
||||
|
||||
// Textures & Samplers
|
||||
public readonly List< (string Label, int TextureIndex, int SamplerIndex, string Description, bool MonoFont) > Textures = new(4);
|
||||
|
||||
public readonly HashSet< int > UnfoldedTextures = new(4);
|
||||
public readonly HashSet< uint > SamplerIds = new(16);
|
||||
public float TextureLabelWidth;
|
||||
public bool UseColorDyeSet;
|
||||
|
||||
// Material Constants
|
||||
public readonly List< (string Name, bool ComponentOnly, int ParamValueOffset) > MaterialConstants = new(16);
|
||||
public readonly List< (string Name, uint Id, ushort ByteSize) > MissingMaterialConstants = new(16);
|
||||
public readonly HashSet< uint > DefinedMaterialConstants = new(16);
|
||||
|
||||
public string MaterialConstantLabel = "Constants###Constants";
|
||||
public IndexSet OrphanedMaterialValues = new(0, false);
|
||||
public int AliasedMaterialValueCount;
|
||||
public bool HasMalformedMaterialConstants;
|
||||
|
||||
// Samplers
|
||||
public readonly List< (string Label, string FileName, uint Id) > Samplers = new(4);
|
||||
public readonly List< (string Name, uint Id) > MissingSamplers = new(4);
|
||||
public readonly HashSet< uint > DefinedSamplers = new(4);
|
||||
public IndexSet OrphanedSamplers = new(0, false);
|
||||
public int AliasedSamplerCount;
|
||||
public readonly List< (string Header, List< (string Label, int ConstantIndex, Range Slice, string Description, bool MonoFont, IConstantEditor Editor) > Constants) > Constants = new(16);
|
||||
|
||||
// Live-Previewers
|
||||
public readonly List<LiveMaterialPreviewer> MaterialPreviewers = new(4);
|
||||
|
|
@ -87,8 +87,24 @@ public partial class ModEditWindow
|
|||
return _edit.FindBestMatch( defaultGamePath );
|
||||
}
|
||||
|
||||
public string[] GetShpkNames()
|
||||
{
|
||||
if (null != _shpkNames)
|
||||
return _shpkNames;
|
||||
|
||||
var names = new HashSet<string>(StandardShaderPackages);
|
||||
names.UnionWith(_edit.FindPathsStartingWith(ShpkPrefix).Select(path => path.ToString()[ShpkPrefixLength..]));
|
||||
|
||||
_shpkNames = names.ToArray();
|
||||
Array.Sort(_shpkNames);
|
||||
|
||||
return _shpkNames;
|
||||
}
|
||||
|
||||
public void LoadShpk( FullPath path )
|
||||
{
|
||||
ShaderHeader = $"Shader ({Mtrl.ShaderPackage.Name})###Shader";
|
||||
|
||||
try
|
||||
{
|
||||
LoadedShpkPath = path;
|
||||
|
|
@ -106,180 +122,314 @@ public partial class ModEditWindow
|
|||
Penumbra.Chat.NotificationMessage( $"Could not load {LoadedShpkPath.ToPath()}:\n{e}", "Penumbra Advanced Editing", NotificationType.Error );
|
||||
}
|
||||
|
||||
if( LoadedShpkPath.InternalName.IsEmpty )
|
||||
{
|
||||
AssociatedShpkDevkit = null;
|
||||
LoadedShpkDevkitPathName = string.Empty;
|
||||
}
|
||||
else
|
||||
AssociatedShpkDevkit = TryLoadShpkDevkit( Path.GetFileNameWithoutExtension( Mtrl.ShaderPackage.Name ), out LoadedShpkDevkitPathName );
|
||||
|
||||
UpdateShaderKeys();
|
||||
Update();
|
||||
}
|
||||
|
||||
public void UpdateTextureLabels()
|
||||
private JObject? TryLoadShpkDevkit(string shpkBaseName, out string devkitPathName)
|
||||
{
|
||||
var samplers = Mtrl.GetSamplersByTexture( AssociatedShpk );
|
||||
TextureLabels.Clear();
|
||||
TextureLabelWidth = 50f * UiHelpers.Scale;
|
||||
using( var _ = ImRaii.PushFont( UiBuilder.MonoFont ) )
|
||||
try
|
||||
{
|
||||
for( var i = 0; i < Mtrl.Textures.Length; ++i )
|
||||
{
|
||||
var (sampler, shpkSampler) = samplers[ i ];
|
||||
var name = shpkSampler.HasValue ? shpkSampler.Value.Name : sampler.HasValue ? $"0x{sampler.Value.SamplerId:X8}" : $"#{i}";
|
||||
TextureLabels.Add( name );
|
||||
TextureLabelWidth = Math.Max( TextureLabelWidth, ImGui.CalcTextSize( name ).X );
|
||||
}
|
||||
}
|
||||
if (!Utf8GamePath.FromString("penumbra/shpk_devkit/" + shpkBaseName + ".json", out var devkitPath))
|
||||
throw new Exception("Could not assemble ShPk dev-kit path.");
|
||||
|
||||
TextureLabelWidth = TextureLabelWidth / UiHelpers.Scale + 4;
|
||||
var devkitFullPath = _edit.FindBestMatch(devkitPath);
|
||||
if (!devkitFullPath.IsRooted)
|
||||
throw new Exception("Could not resolve ShPk dev-kit path.");
|
||||
|
||||
devkitPathName = devkitFullPath.FullName;
|
||||
return JObject.Parse(File.ReadAllText(devkitFullPath.FullName));
|
||||
}
|
||||
catch
|
||||
{
|
||||
devkitPathName = string.Empty;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateShaderKeyLabels()
|
||||
private T? TryGetShpkDevkitData<T>(string category, uint? id, bool mayVary) where T : class
|
||||
{
|
||||
ShaderKeyLabels.Clear();
|
||||
DefinedShaderKeys.Clear();
|
||||
foreach( var (key, idx) in Mtrl.ShaderPackage.ShaderKeys.WithIndex() )
|
||||
{
|
||||
ShaderKeyLabels.Add( $"#{idx}: 0x{key.Category:X8} = 0x{key.Value:X8}###{idx}: 0x{key.Category:X8}" );
|
||||
DefinedShaderKeys.Add( key.Category, key.Value );
|
||||
}
|
||||
return TryGetShpkDevkitData<T>(AssociatedShpkDevkit, LoadedShpkDevkitPathName, category, id, mayVary)
|
||||
?? TryGetShpkDevkitData<T>(AssociatedBaseDevkit, LoadedBaseDevkitPathName, category, id, mayVary);
|
||||
}
|
||||
|
||||
MissingShaderKeyIndices.Clear();
|
||||
AvailableKeyValues.Clear();
|
||||
var vertexShaders = new IndexSet( AssociatedShpk?.VertexShaders.Length ?? 0, false );
|
||||
var pixelShaders = new IndexSet( AssociatedShpk?.PixelShaders.Length ?? 0, false );
|
||||
if( AssociatedShpk != null )
|
||||
{
|
||||
MissingShaderKeyIndices.AddRange( AssociatedShpk.MaterialKeys.WithIndex().Where( k => !DefinedShaderKeys.ContainsKey( k.Value.Id ) ).WithoutValue() );
|
||||
private T? TryGetShpkDevkitData<T>(JObject? devkit, string devkitPathName, string category, uint? id, bool mayVary) where T : class
|
||||
{
|
||||
if (devkit == null)
|
||||
return null;
|
||||
|
||||
if( MissingShaderKeyIndices.Count > 0 && MissingShaderKeyIndices.All( i => AssociatedShpk.MaterialKeys[ i ].Id != NewKeyId ) )
|
||||
try
|
||||
{
|
||||
var data = devkit[category];
|
||||
if (id.HasValue)
|
||||
data = data?[id.Value.ToString()];
|
||||
|
||||
if (mayVary && (data as JObject)?["Vary"] != null)
|
||||
{
|
||||
var key = AssociatedShpk.MaterialKeys[ MissingShaderKeyIndices[ 0 ] ];
|
||||
NewKeyId = key.Id;
|
||||
NewKeyDefault = key.DefaultValue;
|
||||
var selector = BuildSelector(data!["Vary"]!
|
||||
.Select(key => (uint)key)
|
||||
.Select(key => Mtrl.GetShaderKey(key)?.Value ?? AssociatedShpk!.GetMaterialKeyById(key)!.Value.DefaultValue));
|
||||
var index = (int)data["Selectors"]![selector.ToString()]!;
|
||||
data = data["Items"]![index];
|
||||
}
|
||||
|
||||
AvailableKeyValues.AddRange( AssociatedShpk.MaterialKeys.Select( k => DefinedShaderKeys.TryGetValue( k.Id, out var value ) ? value : k.DefaultValue ) );
|
||||
foreach( var node in AssociatedShpk.Nodes )
|
||||
return data?.ToObject(typeof(T)) as T;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Some element in the JSON was undefined or invalid (wrong type, key that doesn't exist in the ShPk, index out of range, …)
|
||||
Penumbra.Log.Error($"Error while traversing the ShPk dev-kit file at {devkitPathName}: {e}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateShaderKeys()
|
||||
{
|
||||
ShaderKeys.Clear();
|
||||
if (AssociatedShpk != null)
|
||||
{
|
||||
foreach (var key in AssociatedShpk.MaterialKeys)
|
||||
{
|
||||
if( node.MaterialKeys.WithIndex().All( key => key.Value == AvailableKeyValues[ key.Index ] ) )
|
||||
var dkData = TryGetShpkDevkitData<DevkitShaderKey>("ShaderKeys", key.Id, false);
|
||||
var hasDkLabel = !string.IsNullOrEmpty(dkData?.Label);
|
||||
|
||||
var valueSet = new HashSet<uint>(key.Values);
|
||||
if (dkData != null)
|
||||
valueSet.UnionWith(dkData.Values.Keys);
|
||||
|
||||
var mtrlKeyIndex = Mtrl.FindOrAddShaderKey(key.Id, key.DefaultValue);
|
||||
var values = valueSet.Select<uint, (string Label, uint Value, string Description)>(value =>
|
||||
{
|
||||
foreach( var pass in node.Passes )
|
||||
if (dkData != null && dkData.Values.TryGetValue(value, out var dkValue))
|
||||
return (dkValue.Label.Length > 0 ? dkValue.Label : $"0x{value:X8}", value, dkValue.Description);
|
||||
else
|
||||
return ($"0x{value:X8}", value, string.Empty);
|
||||
}).ToArray();
|
||||
Array.Sort(values, (x, y) =>
|
||||
{
|
||||
if (x.Value == key.DefaultValue)
|
||||
return -1;
|
||||
if (y.Value == key.DefaultValue)
|
||||
return 1;
|
||||
return x.Label.CompareTo(y.Label);
|
||||
});
|
||||
ShaderKeys.Add((hasDkLabel ? dkData!.Label : $"0x{key.Id:X8}", mtrlKeyIndex, dkData?.Description ?? string.Empty, !hasDkLabel, values));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var (key, index) in Mtrl.ShaderPackage.ShaderKeys.WithIndex())
|
||||
ShaderKeys.Add(($"0x{key.Category:X8}", index, string.Empty, true, Array.Empty<(string, uint, string)>()));
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateShaders()
|
||||
{
|
||||
VertexShaders.Clear();
|
||||
PixelShaders.Clear();
|
||||
if (AssociatedShpk == null)
|
||||
ShadersKnown = false;
|
||||
else
|
||||
{
|
||||
ShadersKnown = true;
|
||||
var systemKeySelectors = AllSelectors(AssociatedShpk.SystemKeys).ToArray();
|
||||
var sceneKeySelectors = AllSelectors(AssociatedShpk.SceneKeys).ToArray();
|
||||
var subViewKeySelectors = AllSelectors(AssociatedShpk.SubViewKeys).ToArray();
|
||||
var materialKeySelector = BuildSelector(AssociatedShpk.MaterialKeys.Select(key => Mtrl.GetOrAddShaderKey(key.Id, key.DefaultValue).Value));
|
||||
foreach (var systemKeySelector in systemKeySelectors)
|
||||
{
|
||||
foreach (var sceneKeySelector in sceneKeySelectors)
|
||||
{
|
||||
foreach (var subViewKeySelector in subViewKeySelectors)
|
||||
{
|
||||
vertexShaders.Add( ( int )pass.VertexShader );
|
||||
pixelShaders.Add( ( int )pass.PixelShader );
|
||||
var selector = BuildSelector(systemKeySelector, sceneKeySelector, materialKeySelector, subViewKeySelector);
|
||||
var node = AssociatedShpk.GetNodeBySelector(selector);
|
||||
if (node.HasValue)
|
||||
{
|
||||
foreach (var pass in node.Value.Passes)
|
||||
{
|
||||
VertexShaders.Add((int)pass.VertexShader);
|
||||
PixelShaders.Add((int)pass.PixelShader);
|
||||
}
|
||||
}
|
||||
else
|
||||
ShadersKnown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VertexShaders = $"Vertex Shaders: {( vertexShaders.Count > 0 ? string.Join( ", ", vertexShaders.Select( i => $"#{i}" ) ) : "???" )}";
|
||||
PixelShaders = $"Pixel Shaders: {( pixelShaders.Count > 0 ? string.Join( ", ", pixelShaders.Select( i => $"#{i}" ) ) : "???" )}";
|
||||
var vertexShaders = VertexShaders.OrderBy(i => i).Select(i => $"#{i}");
|
||||
var pixelShaders = PixelShaders.OrderBy(i => i).Select(i => $"#{i}");
|
||||
|
||||
VertexShadersString = $"Vertex Shaders: {string.Join(", ", ShadersKnown ? vertexShaders : vertexShaders.Append("???"))}";
|
||||
PixelShadersString = $"Pixel Shaders: {string.Join(", ", ShadersKnown ? pixelShaders : pixelShaders.Append("???"))}";
|
||||
|
||||
ShaderComment = TryGetShpkDevkitData<string>("Comment", null, true) ?? string.Empty;
|
||||
}
|
||||
|
||||
public void UpdateConstantLabels()
|
||||
public void UpdateTextures()
|
||||
{
|
||||
var prefix = AssociatedShpk?.GetConstantById( MaterialParamsConstantId )?.Name ?? string.Empty;
|
||||
MaterialConstantLabel = prefix.Length == 0 ? "Constants###Constants" : prefix + "###Constants";
|
||||
|
||||
DefinedMaterialConstants.Clear();
|
||||
MaterialConstants.Clear();
|
||||
HasMalformedMaterialConstants = false;
|
||||
AliasedMaterialValueCount = 0;
|
||||
OrphanedMaterialValues = new IndexSet( Mtrl.ShaderPackage.ShaderValues.Length, true );
|
||||
foreach( var (constant, idx) in Mtrl.ShaderPackage.Constants.WithIndex() )
|
||||
Textures.Clear();
|
||||
SamplerIds.Clear();
|
||||
if (AssociatedShpk == null)
|
||||
{
|
||||
DefinedMaterialConstants.Add( constant.Id );
|
||||
var values = Mtrl.GetConstantValues( constant );
|
||||
var paramValueOffset = -values.Length;
|
||||
if( values.Length > 0 )
|
||||
SamplerIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId));
|
||||
if (Mtrl.ColorSets.Any(c => c.HasRows))
|
||||
SamplerIds.Add(TableSamplerId);
|
||||
|
||||
foreach (var (sampler, index) in Mtrl.ShaderPackage.Samplers.WithIndex())
|
||||
Textures.Add(($"0x{sampler.SamplerId:X8}", sampler.TextureIndex, index, string.Empty, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var index in VertexShaders)
|
||||
SamplerIds.UnionWith(AssociatedShpk.VertexShaders[index].Samplers.Select(sampler => sampler.Id));
|
||||
foreach (var index in PixelShaders)
|
||||
SamplerIds.UnionWith(AssociatedShpk.PixelShaders[index].Samplers.Select(sampler => sampler.Id));
|
||||
if (!ShadersKnown)
|
||||
{
|
||||
var shpkParam = AssociatedShpk?.GetMaterialParamById( constant.Id );
|
||||
var paramByteOffset = shpkParam?.ByteOffset ?? -1;
|
||||
if( ( paramByteOffset & 0x3 ) == 0 )
|
||||
{
|
||||
paramValueOffset = paramByteOffset >> 2;
|
||||
}
|
||||
|
||||
var unique = OrphanedMaterialValues.RemoveRange( constant.ByteOffset >> 2, values.Length );
|
||||
AliasedMaterialValueCount += values.Length - unique;
|
||||
SamplerIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId));
|
||||
if (Mtrl.ColorSets.Any(c => c.HasRows))
|
||||
SamplerIds.Add(TableSamplerId);
|
||||
}
|
||||
else
|
||||
foreach (var samplerId in SamplerIds)
|
||||
{
|
||||
HasMalformedMaterialConstants = true;
|
||||
var shpkSampler = AssociatedShpk.GetSamplerById(samplerId);
|
||||
if (!shpkSampler.HasValue || shpkSampler.Value.Slot != 2)
|
||||
continue;
|
||||
|
||||
var dkData = TryGetShpkDevkitData<DevkitSampler>("Samplers", samplerId, true);
|
||||
var hasDkLabel = !string.IsNullOrEmpty(dkData?.Label);
|
||||
|
||||
var sampler = Mtrl.GetOrAddSampler(samplerId, dkData?.DefaultTexture ?? string.Empty, out var samplerIndex);
|
||||
Textures.Add((hasDkLabel ? dkData!.Label : shpkSampler.Value.Name, sampler.TextureIndex, samplerIndex, dkData?.Description ?? string.Empty, !hasDkLabel));
|
||||
}
|
||||
if (SamplerIds.Contains(TableSamplerId))
|
||||
Mtrl.FindOrAddColorSet();
|
||||
}
|
||||
Textures.Sort((x, y) => string.CompareOrdinal(x.Label, y.Label));
|
||||
|
||||
var (name, componentOnly) = MaterialParamRangeName( prefix, paramValueOffset, values.Length );
|
||||
var label = name == null
|
||||
? $"#{idx:D2} (ID: 0x{constant.Id:X8})###{constant.Id}"
|
||||
: $"#{idx:D2}: {name} (ID: 0x{constant.Id:X8})###{constant.Id}";
|
||||
TextureLabelWidth = 50f * UiHelpers.Scale;
|
||||
|
||||
MaterialConstants.Add( ( label, componentOnly, paramValueOffset ) );
|
||||
float helpWidth;
|
||||
using (var _ = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
helpWidth = ImGui.GetStyle().ItemSpacing.X + ImGui.CalcTextSize(FontAwesomeIcon.InfoCircle.ToIconString()).X;
|
||||
|
||||
foreach (var (label, _, _, description, monoFont) in Textures)
|
||||
if (!monoFont)
|
||||
TextureLabelWidth = Math.Max(TextureLabelWidth, ImGui.CalcTextSize(label).X + (description.Length > 0 ? helpWidth : 0.0f));
|
||||
|
||||
using (var _ = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
foreach (var (label, _, _, description, monoFont) in Textures)
|
||||
if (monoFont)
|
||||
TextureLabelWidth = Math.Max(TextureLabelWidth, ImGui.CalcTextSize(label).X + (description.Length > 0 ? helpWidth : 0.0f));
|
||||
}
|
||||
|
||||
MissingMaterialConstants.Clear();
|
||||
if( AssociatedShpk != null )
|
||||
{
|
||||
var setIdx = false;
|
||||
foreach( var param in AssociatedShpk.MaterialParams.Where( m => !DefinedMaterialConstants.Contains( m.Id ) ) )
|
||||
{
|
||||
var (name, _) = MaterialParamRangeName( prefix, param.ByteOffset >> 2, param.ByteSize >> 2 );
|
||||
var label = name == null
|
||||
? $"(ID: 0x{param.Id:X8})"
|
||||
: $"{name} (ID: 0x{param.Id:X8})";
|
||||
if( NewConstantId == param.Id )
|
||||
{
|
||||
setIdx = true;
|
||||
NewConstantIdx = MissingMaterialConstants.Count;
|
||||
}
|
||||
|
||||
MissingMaterialConstants.Add( ( label, param.Id, param.ByteSize ) );
|
||||
}
|
||||
|
||||
if( !setIdx && MissingMaterialConstants.Count > 0 )
|
||||
{
|
||||
NewConstantIdx = 0;
|
||||
NewConstantId = MissingMaterialConstants[ 0 ].Id;
|
||||
}
|
||||
}
|
||||
TextureLabelWidth = TextureLabelWidth / UiHelpers.Scale + 4;
|
||||
}
|
||||
|
||||
public void UpdateSamplers()
|
||||
public void UpdateConstants()
|
||||
{
|
||||
Samplers.Clear();
|
||||
DefinedSamplers.Clear();
|
||||
OrphanedSamplers = new IndexSet( Mtrl.Textures.Length, true );
|
||||
foreach( var (sampler, idx) in Mtrl.ShaderPackage.Samplers.WithIndex() )
|
||||
static List<T> FindOrAddGroup<T>(List<(string, List<T>)> groups, string name)
|
||||
{
|
||||
DefinedSamplers.Add( sampler.SamplerId );
|
||||
if( !OrphanedSamplers.Remove( sampler.TextureIndex ) )
|
||||
{
|
||||
++AliasedSamplerCount;
|
||||
}
|
||||
foreach (var (groupName, group) in groups)
|
||||
if (string.Equals(name, groupName, StringComparison.Ordinal))
|
||||
return group;
|
||||
|
||||
var shpk = AssociatedShpk?.GetSamplerById( sampler.SamplerId );
|
||||
var label = shpk.HasValue
|
||||
? $"#{idx}: {shpk.Value.Name} (ID: 0x{sampler.SamplerId:X8})##{sampler.SamplerId}"
|
||||
: $"#{idx} (ID: 0x{sampler.SamplerId:X8})##{sampler.SamplerId}";
|
||||
var fileName = $"Texture #{sampler.TextureIndex} - {Path.GetFileName( Mtrl.Textures[ sampler.TextureIndex ].Path )}";
|
||||
Samplers.Add( ( label, fileName, sampler.SamplerId ) );
|
||||
var newGroup = new List<T>(16);
|
||||
groups.Add((name, newGroup));
|
||||
return newGroup;
|
||||
}
|
||||
|
||||
MissingSamplers.Clear();
|
||||
if( AssociatedShpk != null )
|
||||
Constants.Clear();
|
||||
if (AssociatedShpk == null)
|
||||
{
|
||||
var setSampler = false;
|
||||
foreach( var sampler in AssociatedShpk.Samplers.Where( s => s.Slot == 2 && !DefinedSamplers.Contains( s.Id ) ) )
|
||||
var fcGroup = FindOrAddGroup(Constants, "Further Constants");
|
||||
foreach (var (constant, index) in Mtrl.ShaderPackage.Constants.WithIndex())
|
||||
{
|
||||
if( sampler.Id == NewSamplerId )
|
||||
var values = Mtrl.GetConstantValues(constant);
|
||||
for (var i = 0; i < values.Length; i += 4)
|
||||
fcGroup.Add(($"0x{constant.Id:X8}", index, i..Math.Min(i + 4, values.Length), string.Empty, true, FloatConstantEditor.Default));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var prefix = AssociatedShpk.GetConstantById(MaterialParamsConstantId)?.Name ?? string.Empty;
|
||||
foreach (var shpkConstant in AssociatedShpk.MaterialParams)
|
||||
{
|
||||
if ((shpkConstant.ByteSize & 0x3) != 0)
|
||||
continue;
|
||||
|
||||
var constant = Mtrl.GetOrAddConstant(shpkConstant.Id, shpkConstant.ByteSize >> 2, out var constantIndex);
|
||||
var values = Mtrl.GetConstantValues(constant);
|
||||
var handledElements = new IndexSet(values.Length, false);
|
||||
|
||||
var dkData = TryGetShpkDevkitData<DevkitConstant[]>("Constants", shpkConstant.Id, true);
|
||||
if (dkData != null)
|
||||
{
|
||||
setSampler = true;
|
||||
NewSamplerIdx = MissingSamplers.Count;
|
||||
foreach (var dkConstant in dkData)
|
||||
{
|
||||
var offset = (int)dkConstant.Offset;
|
||||
var length = values.Length - offset;
|
||||
if (dkConstant.Length.HasValue)
|
||||
length = Math.Min(length, (int)dkConstant.Length.Value);
|
||||
if (length <= 0)
|
||||
continue;
|
||||
var editor = dkConstant.CreateEditor();
|
||||
if (editor != null)
|
||||
FindOrAddGroup(Constants, dkConstant.Group.Length > 0 ? dkConstant.Group : "Further Constants")
|
||||
.Add((dkConstant.Label, constantIndex, offset..(offset + length), dkConstant.Description, false, editor));
|
||||
handledElements.AddRange(offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
MissingSamplers.Add( ( sampler.Name, sampler.Id ) );
|
||||
}
|
||||
|
||||
if( !setSampler && MissingSamplers.Count > 0 )
|
||||
{
|
||||
NewSamplerIdx = 0;
|
||||
NewSamplerId = MissingSamplers[ 0 ].Id;
|
||||
var fcGroup = FindOrAddGroup(Constants, "Further Constants");
|
||||
foreach (var (start, end) in handledElements.Ranges(true))
|
||||
{
|
||||
if ((shpkConstant.ByteOffset & 0x3) == 0)
|
||||
{
|
||||
var offset = shpkConstant.ByteOffset >> 2;
|
||||
for (int i = (start & ~0x3) - (offset & 0x3), j = offset >> 2; i < end; i += 4, ++j)
|
||||
{
|
||||
var rangeStart = Math.Max(i, start);
|
||||
var rangeEnd = Math.Min(i + 4, end);
|
||||
if (rangeEnd > rangeStart)
|
||||
fcGroup.Add(($"{prefix}[{j:D2}]{VectorSwizzle((offset + rangeStart) & 0x3, (offset + rangeEnd - 1) & 0x3)} (0x{shpkConstant.Id:X8})", constantIndex, rangeStart..rangeEnd, string.Empty, true, FloatConstantEditor.Default));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = start; i < end; i += 4)
|
||||
fcGroup.Add(($"0x{shpkConstant.Id:X8}", constantIndex, i..Math.Min(i + 4, end), string.Empty, true, FloatConstantEditor.Default));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Constants.RemoveAll(group => group.Constants.Count == 0);
|
||||
Constants.Sort((x, y) =>
|
||||
{
|
||||
if (string.Equals(x.Header, "Further Constants", StringComparison.Ordinal))
|
||||
return 1;
|
||||
if (string.Equals(y.Header, "Further Constants", StringComparison.Ordinal))
|
||||
return -1;
|
||||
return string.Compare(x.Header, y.Header, StringComparison.Ordinal);
|
||||
});
|
||||
// HACK the Replace makes w appear after xyz, for the cbuffer-location-based naming scheme
|
||||
foreach (var (_, group) in Constants)
|
||||
group.Sort((x, y) => string.CompareOrdinal(
|
||||
x.MonoFont ? x.Label.Replace("].w", "].{") : x.Label,
|
||||
y.MonoFont ? y.Label.Replace("].w", "].{") : y.Label));
|
||||
}
|
||||
|
||||
public unsafe void BindToMaterialInstances()
|
||||
|
|
@ -329,6 +479,7 @@ public partial class ModEditWindow
|
|||
// Carry on without that previewer.
|
||||
}
|
||||
}
|
||||
UpdateMaterialPreview();
|
||||
|
||||
var colorSet = Mtrl.ColorSets.FirstOrNull(colorSet => colorSet.HasRows);
|
||||
|
||||
|
|
@ -378,6 +529,19 @@ public partial class ModEditWindow
|
|||
previewer.SetSamplerFlags(samplerCrc, samplerFlags);
|
||||
}
|
||||
|
||||
public void UpdateMaterialPreview()
|
||||
{
|
||||
SetShaderPackageFlags(Mtrl.ShaderPackage.Flags);
|
||||
foreach (var constant in Mtrl.ShaderPackage.Constants)
|
||||
{
|
||||
var values = Mtrl.GetConstantValues(constant);
|
||||
if (values != null)
|
||||
SetMaterialParameter(constant.Id, 0, values);
|
||||
}
|
||||
foreach (var sampler in Mtrl.ShaderPackage.Samplers)
|
||||
SetSamplerFlags(sampler.SamplerId, sampler.Flags);
|
||||
}
|
||||
|
||||
public void HighlightColorSetRow(int rowIdx)
|
||||
{
|
||||
var oldRowIdx = HighlightedColorSetRow;
|
||||
|
|
@ -402,7 +566,7 @@ public partial class ModEditWindow
|
|||
UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
public unsafe void UpdateColorSetRowPreview(int rowIdx)
|
||||
public void UpdateColorSetRowPreview(int rowIdx)
|
||||
{
|
||||
if (ColorSetPreviewers.Count == 0)
|
||||
return;
|
||||
|
|
@ -415,12 +579,12 @@ public partial class ModEditWindow
|
|||
var maybeColorDyeSet = Mtrl.ColorDyeSets.FirstOrNull(colorDyeSet => colorDyeSet.Index == colorSet.Index);
|
||||
|
||||
var row = colorSet.Rows[rowIdx];
|
||||
if (maybeColorDyeSet.HasValue)
|
||||
if (maybeColorDyeSet.HasValue && UseColorDyeSet)
|
||||
{
|
||||
var stm = _edit._stainService.StmFile;
|
||||
var dye = maybeColorDyeSet.Value.Rows[rowIdx];
|
||||
if (stm.TryGetValue(dye.Template, (StainId)_edit._stainService.StainCombo.CurrentSelection.Key, out var dyes))
|
||||
ApplyDye(ref row, dye, dyes);
|
||||
row.ApplyDyeTemplate(dye, dyes);
|
||||
}
|
||||
|
||||
if (HighlightedColorSetRow == rowIdx)
|
||||
|
|
@ -428,13 +592,12 @@ public partial class ModEditWindow
|
|||
|
||||
foreach (var previewer in ColorSetPreviewers)
|
||||
{
|
||||
fixed (Half* pDest = previewer.ColorSet)
|
||||
Buffer.MemoryCopy(&row, pDest + LiveColorSetPreviewer.TextureWidth * 4 * rowIdx, LiveColorSetPreviewer.TextureWidth * 4 * sizeof(Half), sizeof(MtrlFile.ColorSet.Row));
|
||||
row.AsHalves().CopyTo(previewer.ColorSet.AsSpan().Slice(LiveColorSetPreviewer.TextureWidth * 4 * rowIdx, LiveColorSetPreviewer.TextureWidth * 4));
|
||||
previewer.ScheduleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void UpdateColorSetPreview()
|
||||
public void UpdateColorSetPreview()
|
||||
{
|
||||
if (ColorSetPreviewers.Count == 0)
|
||||
return;
|
||||
|
|
@ -447,7 +610,7 @@ public partial class ModEditWindow
|
|||
var maybeColorDyeSet = Mtrl.ColorDyeSets.FirstOrNull(colorDyeSet => colorDyeSet.Index == colorSet.Index);
|
||||
|
||||
var rows = colorSet.Rows;
|
||||
if (maybeColorDyeSet.HasValue)
|
||||
if (maybeColorDyeSet.HasValue && UseColorDyeSet)
|
||||
{
|
||||
var stm = _edit._stainService.StmFile;
|
||||
var stainId = (StainId)_edit._stainService.StainCombo.CurrentSelection.Key;
|
||||
|
|
@ -457,7 +620,7 @@ public partial class ModEditWindow
|
|||
ref var row = ref rows[i];
|
||||
var dye = colorDyeSet.Rows[i];
|
||||
if (stm.TryGetValue(dye.Template, stainId, out var dyes))
|
||||
ApplyDye(ref row, dye, dyes);
|
||||
row.ApplyDyeTemplate(dye, dyes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -466,26 +629,11 @@ public partial class ModEditWindow
|
|||
|
||||
foreach (var previewer in ColorSetPreviewers)
|
||||
{
|
||||
fixed (Half* pDest = previewer.ColorSet)
|
||||
Buffer.MemoryCopy(&rows, pDest, LiveColorSetPreviewer.TextureLength * sizeof(Half), sizeof(MtrlFile.ColorSet.RowArray));
|
||||
rows.AsHalves().CopyTo(previewer.ColorSet);
|
||||
previewer.ScheduleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyDye(ref MtrlFile.ColorSet.Row row, MtrlFile.ColorDyeSet.Row dye, StmFile.DyePack dyes)
|
||||
{
|
||||
if (dye.Diffuse)
|
||||
row.Diffuse = dyes.Diffuse;
|
||||
if (dye.Specular)
|
||||
row.Specular = dyes.Specular;
|
||||
if (dye.SpecularStrength)
|
||||
row.SpecularStrength = dyes.SpecularPower;
|
||||
if (dye.Emissive)
|
||||
row.Emissive = dyes.Emissive;
|
||||
if (dye.Gloss)
|
||||
row.GlossStrength = dyes.Gloss;
|
||||
}
|
||||
|
||||
private static void ApplyHighlight(ref MtrlFile.ColorSet.Row row, int time)
|
||||
{
|
||||
var level = Math.Sin(time * Math.PI / 16) * 0.5 + 0.5;
|
||||
|
|
@ -498,18 +646,19 @@ public partial class ModEditWindow
|
|||
|
||||
public void Update()
|
||||
{
|
||||
UpdateTextureLabels();
|
||||
UpdateShaderKeyLabels();
|
||||
UpdateConstantLabels();
|
||||
UpdateSamplers();
|
||||
UpdateShaders();
|
||||
UpdateTextures();
|
||||
UpdateConstants();
|
||||
}
|
||||
|
||||
public MtrlTab( ModEditWindow edit, MtrlFile file, string filePath, bool writable )
|
||||
{
|
||||
_edit = edit;
|
||||
Mtrl = file;
|
||||
FilePath = filePath;
|
||||
Writable = writable;
|
||||
_edit = edit;
|
||||
Mtrl = file;
|
||||
FilePath = filePath;
|
||||
Writable = writable;
|
||||
UseColorDyeSet = file.ColorDyeSets.Length > 0;
|
||||
AssociatedBaseDevkit = TryLoadShpkDevkit( "_base", out LoadedBaseDevkitPathName );
|
||||
LoadShpk( FindAssociatedShpk( out _, out _ ) );
|
||||
if (writable)
|
||||
BindToMaterialInstances();
|
||||
|
|
@ -532,9 +681,96 @@ public partial class ModEditWindow
|
|||
}
|
||||
|
||||
public bool Valid
|
||||
=> Mtrl.Valid;
|
||||
=> ShadersKnown && Mtrl.Valid;
|
||||
|
||||
public byte[] Write()
|
||||
=> Mtrl.Write();
|
||||
{
|
||||
var output = Mtrl.Clone();
|
||||
output.GarbageCollect(AssociatedShpk, SamplerIds, UseColorDyeSet);
|
||||
|
||||
return output.Write();
|
||||
}
|
||||
|
||||
private sealed class DevkitShaderKeyValue
|
||||
{
|
||||
public string Label = string.Empty;
|
||||
public string Description = string.Empty;
|
||||
}
|
||||
|
||||
private sealed class DevkitShaderKey
|
||||
{
|
||||
public string Label = string.Empty;
|
||||
public string Description = string.Empty;
|
||||
public Dictionary<uint, DevkitShaderKeyValue> Values = new();
|
||||
}
|
||||
|
||||
private sealed class DevkitSampler
|
||||
{
|
||||
public string Label = string.Empty;
|
||||
public string Description = string.Empty;
|
||||
public string DefaultTexture = string.Empty;
|
||||
}
|
||||
|
||||
private enum DevkitConstantType
|
||||
{
|
||||
Hidden = -1,
|
||||
Float = 0,
|
||||
Integer = 1,
|
||||
Color = 2,
|
||||
Enum = 3,
|
||||
}
|
||||
|
||||
private sealed class DevkitConstantValue
|
||||
{
|
||||
public string Label = string.Empty;
|
||||
public string Description = string.Empty;
|
||||
public float Value = 0.0f;
|
||||
}
|
||||
|
||||
private sealed class DevkitConstant
|
||||
{
|
||||
public uint Offset = 0;
|
||||
public uint? Length = null;
|
||||
public string Group = string.Empty;
|
||||
public string Label = string.Empty;
|
||||
public string Description = string.Empty;
|
||||
public DevkitConstantType Type = DevkitConstantType.Float;
|
||||
|
||||
public float? Minimum = null;
|
||||
public float? Maximum = null;
|
||||
public float? Speed = null;
|
||||
public float RelativeSpeed = 0.0f;
|
||||
public float Factor = 1.0f;
|
||||
public float Bias = 0.0f;
|
||||
public byte Precision = 3;
|
||||
public string Unit = string.Empty;
|
||||
|
||||
public bool SquaredRgb = false;
|
||||
public bool Clamped = false;
|
||||
|
||||
public DevkitConstantValue[] Values = Array.Empty<DevkitConstantValue>();
|
||||
|
||||
public IConstantEditor? CreateEditor()
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case DevkitConstantType.Hidden:
|
||||
return null;
|
||||
case DevkitConstantType.Float:
|
||||
return new FloatConstantEditor(Minimum, Maximum, Speed ?? 0.1f, RelativeSpeed, Factor, Bias, Precision, Unit);
|
||||
case DevkitConstantType.Integer:
|
||||
return new IntConstantEditor(ToInteger(Minimum), ToInteger(Maximum), Speed ?? 0.25f, RelativeSpeed, Factor, Bias, Unit);
|
||||
case DevkitConstantType.Color:
|
||||
return new ColorConstantEditor(SquaredRgb, Clamped);
|
||||
case DevkitConstantType.Enum:
|
||||
return new EnumConstantEditor(Array.ConvertAll(Values, value => (value.Label, value.Value, value.Description)));
|
||||
default:
|
||||
return FloatConstantEditor.Default;
|
||||
}
|
||||
}
|
||||
|
||||
private int? ToInteger(float? value)
|
||||
=> value.HasValue ? (int)Math.Clamp(MathF.Round(value.Value), int.MinValue, int.MaxValue) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
|
@ -7,6 +8,7 @@ using Dalamud.Interface;
|
|||
using Dalamud.Interface.ImGuiFileDialog;
|
||||
using ImGuiNET;
|
||||
using Lumina.Data.Parsing;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData;
|
||||
|
|
@ -19,20 +21,92 @@ public partial class ModEditWindow
|
|||
{
|
||||
private readonly FileDialogService _fileDialog;
|
||||
|
||||
private bool DrawPackageNameInput(MtrlTab tab, bool disabled)
|
||||
// strings path/to/the.exe | grep --fixed-strings '.shpk' | sort -u | sed -e 's#^shader/sm5/shpk/##'
|
||||
// Apricot shader packages are unlisted because
|
||||
// 1. they cause performance/memory issues when calculating the effective shader set
|
||||
// 2. they probably aren't intended for use with materials anyway
|
||||
private static readonly IReadOnlyList<string> StandardShaderPackages = new string[]
|
||||
{
|
||||
var ret = false;
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
if (ImGui.InputText("Shader Package Name", ref tab.Mtrl.ShaderPackage.Name, 63,
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))
|
||||
"3dui.shpk",
|
||||
// "apricot_decal_dummy.shpk",
|
||||
// "apricot_decal_ring.shpk",
|
||||
// "apricot_decal.shpk",
|
||||
// "apricot_lightmodel.shpk",
|
||||
// "apricot_model_dummy.shpk",
|
||||
// "apricot_model_morph.shpk",
|
||||
// "apricot_model.shpk",
|
||||
// "apricot_powder_dummy.shpk",
|
||||
// "apricot_powder.shpk",
|
||||
// "apricot_shape_dummy.shpk",
|
||||
// "apricot_shape.shpk",
|
||||
"bgcolorchange.shpk",
|
||||
"bgcrestchange.shpk",
|
||||
"bgdecal.shpk",
|
||||
"bg.shpk",
|
||||
"bguvscroll.shpk",
|
||||
"channeling.shpk",
|
||||
"characterglass.shpk",
|
||||
"character.shpk",
|
||||
"cloud.shpk",
|
||||
"createviewposition.shpk",
|
||||
"crystal.shpk",
|
||||
"directionallighting.shpk",
|
||||
"directionalshadow.shpk",
|
||||
"grass.shpk",
|
||||
"hair.shpk",
|
||||
"iris.shpk",
|
||||
"lightshaft.shpk",
|
||||
"linelighting.shpk",
|
||||
"planelighting.shpk",
|
||||
"pointlighting.shpk",
|
||||
"river.shpk",
|
||||
"shadowmask.shpk",
|
||||
"skin.shpk",
|
||||
"spotlighting.shpk",
|
||||
"verticalfog.shpk",
|
||||
"water.shpk",
|
||||
"weather.shpk",
|
||||
};
|
||||
|
||||
private enum TextureAddressMode : uint
|
||||
{
|
||||
Wrap = 0,
|
||||
Mirror = 1,
|
||||
Clamp = 2,
|
||||
Border = 3,
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyList<string> TextureAddressModeTooltips = new string[]
|
||||
{
|
||||
"Tile the texture at every UV integer junction.\n\nFor example, for U values between 0 and 3, the texture is repeated three times.",
|
||||
"Flip the texture at every UV integer junction.\n\nFor U values between 0 and 1, for example, the texture is addressed normally; between 1 and 2, the texture is mirrored; between 2 and 3, the texture is normal again; and so on.",
|
||||
"Texture coordinates outside the range [0.0, 1.0] are set to the texture color at 0.0 or 1.0, respectively.",
|
||||
"Texture coordinates outside the range [0.0, 1.0] are set to the border color (generally black).",
|
||||
};
|
||||
|
||||
private static bool DrawPackageNameInput(MtrlTab tab, bool disabled)
|
||||
{
|
||||
if (disabled)
|
||||
{
|
||||
ret = true;
|
||||
tab.AssociatedShpk = null;
|
||||
tab.LoadedShpkPath = FullPath.Empty;
|
||||
ImGui.TextUnformatted("Shader Package: " + tab.Mtrl.ShaderPackage.Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemDeactivatedAfterEdit())
|
||||
tab.LoadShpk(tab.FindAssociatedShpk(out _, out _));
|
||||
var ret = false;
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 250.0f);
|
||||
using var c = ImRaii.Combo("Shader Package", tab.Mtrl.ShaderPackage.Name);
|
||||
if (c)
|
||||
foreach (var value in tab.GetShpkNames())
|
||||
{
|
||||
if (ImGui.Selectable(value, value == tab.Mtrl.ShaderPackage.Name))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.Name = value;
|
||||
ret = true;
|
||||
tab.AssociatedShpk = null;
|
||||
tab.LoadedShpkPath = FullPath.Empty;
|
||||
tab.LoadShpk(tab.FindAssociatedShpk(out _, out _));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -41,8 +115,8 @@ public partial class ModEditWindow
|
|||
{
|
||||
var ret = false;
|
||||
var shpkFlags = (int)tab.Mtrl.ShaderPackage.Flags;
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
if (ImGui.InputInt("Shader Package Flags", ref shpkFlags, 0, 0,
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 250.0f);
|
||||
if (ImGui.InputInt("Shader Flags", ref shpkFlags, 0, 0,
|
||||
ImGuiInputTextFlags.CharsHexadecimal | (disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None)))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.Flags = (uint)shpkFlags;
|
||||
|
|
@ -62,6 +136,12 @@ public partial class ModEditWindow
|
|||
var text = tab.AssociatedShpk == null
|
||||
? "Associated .shpk file: None"
|
||||
: $"Associated .shpk file: {tab.LoadedShpkPathName}";
|
||||
var devkitText = tab.AssociatedShpkDevkit == null
|
||||
? "Associated dev-kit file: None"
|
||||
: $"Associated dev-kit file: {tab.LoadedShpkDevkitPathName}";
|
||||
var baseDevkitText = tab.AssociatedBaseDevkit == null
|
||||
? "Base dev-kit file: None"
|
||||
: $"Base dev-kit file: {tab.LoadedBaseDevkitPathName}";
|
||||
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
|
||||
|
|
@ -70,6 +150,16 @@ public partial class ModEditWindow
|
|||
|
||||
ImGuiUtil.HoverTooltip("Click to copy file path to clipboard.");
|
||||
|
||||
if (ImGui.Selectable(devkitText))
|
||||
ImGui.SetClipboardText(tab.LoadedShpkDevkitPathName);
|
||||
|
||||
ImGuiUtil.HoverTooltip("Click to copy file path to clipboard.");
|
||||
|
||||
if (ImGui.Selectable(baseDevkitText))
|
||||
ImGui.SetClipboardText(tab.LoadedBaseDevkitPathName);
|
||||
|
||||
ImGuiUtil.HoverTooltip("Click to copy file path to clipboard.");
|
||||
|
||||
if (ImGui.Button("Associate Custom .shpk File"))
|
||||
_fileDialog.OpenFilePicker("Associate Custom .shpk File...", ".shpk", (success, name) =>
|
||||
{
|
||||
|
|
@ -94,94 +184,50 @@ public partial class ModEditWindow
|
|||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
}
|
||||
|
||||
|
||||
private static bool DrawShaderKey(MtrlTab tab, bool disabled, ref int idx)
|
||||
{
|
||||
var ret = false;
|
||||
using var t2 = ImRaii.TreeNode(tab.ShaderKeyLabels[idx], disabled ? ImGuiTreeNodeFlags.Leaf : 0);
|
||||
if (!t2 || disabled)
|
||||
return ret;
|
||||
|
||||
var key = tab.Mtrl.ShaderPackage.ShaderKeys[idx];
|
||||
var shpkKey = tab.AssociatedShpk?.GetMaterialKeyById(key.Category);
|
||||
if (shpkKey.HasValue)
|
||||
{
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
using var c = ImRaii.Combo("Value", $"0x{key.Value:X8}");
|
||||
if (c)
|
||||
foreach (var value in shpkKey.Value.Values)
|
||||
{
|
||||
if (ImGui.Selectable($"0x{value:X8}", value == key.Value))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.ShaderKeys[idx].Value = value;
|
||||
ret = true;
|
||||
tab.UpdateShaderKeyLabels();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.Button("Remove Key"))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.ShaderKeys = tab.Mtrl.ShaderPackage.ShaderKeys.RemoveItems(idx--);
|
||||
ret = true;
|
||||
tab.UpdateShaderKeyLabels();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawNewShaderKey(MtrlTab tab)
|
||||
{
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
var ret = false;
|
||||
using (var c = ImRaii.Combo("##NewConstantId", $"ID: 0x{tab.NewKeyId:X8}"))
|
||||
{
|
||||
if (c)
|
||||
foreach (var idx in tab.MissingShaderKeyIndices)
|
||||
{
|
||||
var key = tab.AssociatedShpk!.MaterialKeys[idx];
|
||||
|
||||
if (ImGui.Selectable($"ID: 0x{key.Id:X8}", key.Id == tab.NewKeyId))
|
||||
{
|
||||
tab.NewKeyDefault = key.DefaultValue;
|
||||
tab.NewKeyId = key.Id;
|
||||
ret = true;
|
||||
tab.UpdateShaderKeyLabels();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Add Key"))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.ShaderKeys = tab.Mtrl.ShaderPackage.ShaderKeys.AddItem(new ShaderKey
|
||||
{
|
||||
Category = tab.NewKeyId,
|
||||
Value = tab.NewKeyDefault,
|
||||
});
|
||||
ret = true;
|
||||
tab.UpdateShaderKeyLabels();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialShaderKeys(MtrlTab tab, bool disabled)
|
||||
{
|
||||
if (tab.Mtrl.ShaderPackage.ShaderKeys.Length <= 0
|
||||
&& (disabled || tab.AssociatedShpk == null || tab.AssociatedShpk.MaterialKeys.Length <= 0))
|
||||
return false;
|
||||
|
||||
using var t = ImRaii.TreeNode("Shader Keys");
|
||||
if (!t)
|
||||
if (tab.ShaderKeys.Count == 0)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
for (var idx = 0; idx < tab.Mtrl.ShaderPackage.ShaderKeys.Length; ++idx)
|
||||
ret |= DrawShaderKey(tab, disabled, ref idx);
|
||||
foreach (var (label, index, description, monoFont, values) in tab.ShaderKeys)
|
||||
{
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont, monoFont);
|
||||
ref var key = ref tab.Mtrl.ShaderPackage.ShaderKeys[index];
|
||||
var shpkKey = tab.AssociatedShpk?.GetMaterialKeyById(key.Category);
|
||||
var currentValue = key.Value;
|
||||
var (currentLabel, _, currentDescription) = values.FirstOrNull(v => v.Value == currentValue) ?? ($"0x{currentValue:X8}", currentValue, string.Empty);
|
||||
if (!disabled && shpkKey.HasValue)
|
||||
{
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 250.0f);
|
||||
using (var c = ImRaii.Combo($"##{key.Category:X8}", currentLabel))
|
||||
{
|
||||
if (c)
|
||||
foreach (var (valueLabel, value, valueDescription) in values)
|
||||
{
|
||||
if (ImGui.Selectable(valueLabel, value == currentValue))
|
||||
{
|
||||
key.Value = value;
|
||||
ret = true;
|
||||
tab.Update();
|
||||
}
|
||||
|
||||
if (!disabled && tab.AssociatedShpk != null && tab.MissingShaderKeyIndices.Count != 0)
|
||||
ret |= DrawNewShaderKey(tab);
|
||||
if (valueDescription.Length > 0)
|
||||
ImGuiUtil.SelectableHelpMarker(valueDescription);
|
||||
}
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (description.Length > 0)
|
||||
ImGuiUtil.LabeledHelpMarker(label, description);
|
||||
else
|
||||
ImGui.TextUnformatted(label);
|
||||
}
|
||||
else if (description.Length > 0 || currentDescription.Length > 0)
|
||||
ImGuiUtil.LabeledHelpMarker($"{label}: {currentLabel}",
|
||||
description + ((description.Length > 0 && currentDescription.Length > 0) ? "\n\n" : string.Empty) + currentDescription);
|
||||
else
|
||||
ImGui.TextUnformatted($"{label}: {currentLabel}");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -191,162 +237,64 @@ public partial class ModEditWindow
|
|||
if (tab.AssociatedShpk == null)
|
||||
return;
|
||||
|
||||
ImRaii.TreeNode(tab.VertexShaders, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
ImRaii.TreeNode(tab.PixelShaders, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
}
|
||||
ImRaii.TreeNode(tab.VertexShadersString, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
ImRaii.TreeNode(tab.PixelShadersString, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
|
||||
|
||||
private static bool DrawMaterialConstantValues(MtrlTab tab, bool disabled, ref int idx)
|
||||
{
|
||||
var (name, componentOnly, paramValueOffset) = tab.MaterialConstants[idx];
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
using var t2 = ImRaii.TreeNode(name);
|
||||
if (!t2)
|
||||
return false;
|
||||
|
||||
font.Dispose();
|
||||
|
||||
var constant = tab.Mtrl.ShaderPackage.Constants[idx];
|
||||
var ret = false;
|
||||
var values = tab.Mtrl.GetConstantValues(constant);
|
||||
if (values.Length > 0)
|
||||
if (tab.ShaderComment.Length > 0)
|
||||
{
|
||||
var valueOffset = constant.ByteOffset >> 2;
|
||||
|
||||
for (var valueIdx = 0; valueIdx < values.Length; ++valueIdx)
|
||||
{
|
||||
var paramName = MaterialParamName(componentOnly, paramValueOffset + valueIdx) ?? $"#{valueIdx}";
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
if (ImGui.InputFloat($"{paramName} (at 0x{(valueOffset + valueIdx) << 2:X4})", ref values[valueIdx], 0.0f, 0.0f, "%.3f",
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))
|
||||
{
|
||||
ret = true;
|
||||
tab.UpdateConstantLabels();
|
||||
tab.SetMaterialParameter(constant.Id, valueIdx, values.Slice(valueIdx, 1));
|
||||
}
|
||||
}
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
ImGui.TextUnformatted(tab.ShaderComment);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImRaii.TreeNode($"Offset: 0x{constant.ByteOffset:X4}", ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
ImRaii.TreeNode($"Size: 0x{constant.ByteSize:X4}", ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
}
|
||||
|
||||
if (!disabled
|
||||
&& !tab.HasMalformedMaterialConstants
|
||||
&& tab.OrphanedMaterialValues.Count == 0
|
||||
&& tab.AliasedMaterialValueCount == 0
|
||||
&& ImGui.Button("Remove Constant"))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.ShaderValues =
|
||||
tab.Mtrl.ShaderPackage.ShaderValues.RemoveItems(constant.ByteOffset >> 2, constant.ByteSize >> 2);
|
||||
tab.Mtrl.ShaderPackage.Constants = tab.Mtrl.ShaderPackage.Constants.RemoveItems(idx--);
|
||||
for (var i = 0; i < tab.Mtrl.ShaderPackage.Constants.Length; ++i)
|
||||
{
|
||||
if (tab.Mtrl.ShaderPackage.Constants[i].ByteOffset >= constant.ByteOffset)
|
||||
tab.Mtrl.ShaderPackage.Constants[i].ByteOffset -= constant.ByteSize;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
tab.UpdateConstantLabels();
|
||||
tab.SetMaterialParameter(constant.Id, 0, new float[constant.ByteSize >> 2]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialOrphans(MtrlTab tab, bool disabled)
|
||||
{
|
||||
using var t2 = ImRaii.TreeNode($"Orphan Values ({tab.OrphanedMaterialValues.Count})");
|
||||
if (!t2)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
foreach (var idx in tab.OrphanedMaterialValues)
|
||||
{
|
||||
ImGui.SetNextItemWidth(ImGui.GetFontSize() * 10.0f);
|
||||
if (ImGui.InputFloat($"#{idx} (at 0x{idx << 2:X4})",
|
||||
ref tab.Mtrl.ShaderPackage.ShaderValues[idx], 0.0f, 0.0f, "%.3f",
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))
|
||||
{
|
||||
ret = true;
|
||||
tab.UpdateConstantLabels();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawNewMaterialParam(MtrlTab tab)
|
||||
{
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 450.0f);
|
||||
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
using var c = ImRaii.Combo("##NewConstantId", tab.MissingMaterialConstants[tab.NewConstantIdx].Name);
|
||||
if (c)
|
||||
foreach (var (constant, idx) in tab.MissingMaterialConstants.WithIndex())
|
||||
{
|
||||
if (ImGui.Selectable(constant.Name, constant.Id == tab.NewConstantId))
|
||||
{
|
||||
tab.NewConstantIdx = idx;
|
||||
tab.NewConstantId = constant.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Add Constant"))
|
||||
{
|
||||
var (_, _, byteSize) = tab.MissingMaterialConstants[tab.NewConstantIdx];
|
||||
tab.Mtrl.ShaderPackage.Constants = tab.Mtrl.ShaderPackage.Constants.AddItem(new MtrlFile.Constant
|
||||
{
|
||||
Id = tab.NewConstantId,
|
||||
ByteOffset = (ushort)(tab.Mtrl.ShaderPackage.ShaderValues.Length << 2),
|
||||
ByteSize = byteSize,
|
||||
});
|
||||
tab.Mtrl.ShaderPackage.ShaderValues = tab.Mtrl.ShaderPackage.ShaderValues.AddItem(0.0f, byteSize >> 2);
|
||||
tab.UpdateConstantLabels();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialConstants(MtrlTab tab, bool disabled)
|
||||
{
|
||||
if (tab.Mtrl.ShaderPackage.Constants.Length == 0
|
||||
&& tab.Mtrl.ShaderPackage.ShaderValues.Length == 0
|
||||
&& (disabled || tab.AssociatedShpk == null || tab.AssociatedShpk.MaterialParams.Length == 0))
|
||||
if (tab.Constants.Count == 0)
|
||||
return false;
|
||||
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
using var t = ImRaii.TreeNode(tab.MaterialConstantLabel);
|
||||
if (!t)
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
if (!ImGui.CollapsingHeader("Material Constants"))
|
||||
return false;
|
||||
|
||||
font.Dispose();
|
||||
using var _ = ImRaii.PushId("MaterialConstants");
|
||||
|
||||
var ret = false;
|
||||
for (var idx = 0; idx < tab.Mtrl.ShaderPackage.Constants.Length; ++idx)
|
||||
ret |= DrawMaterialConstantValues(tab, disabled, ref idx);
|
||||
foreach (var (header, group) in tab.Constants)
|
||||
{
|
||||
using var t = ImRaii.TreeNode(header, ImGuiTreeNodeFlags.DefaultOpen);
|
||||
if (!t)
|
||||
continue;
|
||||
|
||||
if (tab.OrphanedMaterialValues.Count > 0)
|
||||
ret |= DrawMaterialOrphans(tab, disabled);
|
||||
else if (!disabled && !tab.HasMalformedMaterialConstants && tab.MissingMaterialConstants.Count > 0)
|
||||
ret |= DrawNewMaterialParam(tab);
|
||||
foreach (var (label, constantIndex, slice, description, monoFont, editor) in group)
|
||||
{
|
||||
var constant = tab.Mtrl.ShaderPackage.Constants[constantIndex];
|
||||
var buffer = tab.Mtrl.GetConstantValues(constant);
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
using var id = ImRaii.PushId($"##{constant.Id:X8}:{slice.Start}");
|
||||
if (editor.Draw(buffer[slice], disabled, 250.0f))
|
||||
{
|
||||
ret = true;
|
||||
tab.SetMaterialParameter(constant.Id, slice.Start, buffer[slice]);
|
||||
}
|
||||
ImGui.SameLine();
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont, monoFont);
|
||||
if (description.Length > 0)
|
||||
ImGuiUtil.LabeledHelpMarker(label, description);
|
||||
else
|
||||
ImGui.TextUnformatted(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialSampler(MtrlTab tab, bool disabled, ref int idx)
|
||||
private static bool DrawMaterialSampler(MtrlTab tab, bool disabled, int textureIdx, int samplerIdx)
|
||||
{
|
||||
var (label, filename, samplerCrc) = tab.Samplers[idx];
|
||||
using var tree = ImRaii.TreeNode(label);
|
||||
if (!tree)
|
||||
return false;
|
||||
|
||||
ImRaii.TreeNode(filename, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
var ret = false;
|
||||
var sampler = tab.Mtrl.ShaderPackage.Samplers[idx];
|
||||
var ret = false;
|
||||
ref var texture = ref tab.Mtrl.Textures[textureIdx];
|
||||
ref var sampler = ref tab.Mtrl.ShaderPackage.Samplers[samplerIdx];
|
||||
|
||||
// FIXME this probably doesn't belong here
|
||||
static unsafe bool InputHexUInt16(string label, ref ushort v, ImGuiInputTextFlags flags)
|
||||
|
|
@ -357,128 +305,123 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
if (InputHexUInt16("Texture Flags", ref tab.Mtrl.Textures[sampler.TextureIndex].Flags,
|
||||
static bool ComboTextureAddressMode(string label, ref uint samplerFlags, int bitOffset)
|
||||
{
|
||||
var current = (TextureAddressMode)((samplerFlags >> bitOffset) & 0x3u);
|
||||
using var c = ImRaii.Combo(label, current.ToString());
|
||||
if (!c)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
foreach (var value in Enum.GetValues<TextureAddressMode>())
|
||||
{
|
||||
if (ImGui.Selectable(value.ToString(), value == current))
|
||||
{
|
||||
samplerFlags = (samplerFlags & ~(0x3u << bitOffset)) | ((uint)value << bitOffset);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.SelectableHelpMarker(TextureAddressModeTooltips[(int)value]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
var dx11 = texture.DX11;
|
||||
if (ImGui.Checkbox("Prepend -- to the file name on DirectX 11", ref dx11))
|
||||
{
|
||||
texture.DX11 = dx11;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
|
||||
if (ComboTextureAddressMode("##UAddressMode", ref sampler.Flags, 2))
|
||||
{
|
||||
ret = true;
|
||||
tab.SetSamplerFlags(sampler.SamplerId, sampler.Flags);
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGuiUtil.LabeledHelpMarker("U Address Mode", "Method to use for resolving a U texture coordinate that is outside the 0 to 1 range.");
|
||||
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
|
||||
if (ComboTextureAddressMode("##VAddressMode", ref sampler.Flags, 0))
|
||||
{
|
||||
ret = true;
|
||||
tab.SetSamplerFlags(sampler.SamplerId, sampler.Flags);
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGuiUtil.LabeledHelpMarker("V Address Mode", "Method to use for resolving a V texture coordinate that is outside the 0 to 1 range.");
|
||||
|
||||
var lodBias = ((int)(sampler.Flags << 12) >> 22) / 64.0f;
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
|
||||
if (ImGui.DragFloat("##LoDBias", ref lodBias, 0.1f, -8.0f, 7.984375f))
|
||||
{
|
||||
sampler.Flags = (uint)((sampler.Flags & ~0x000FFC00) | (uint)((int)Math.Round(Math.Clamp(lodBias, -8.0f, 7.984375f) * 64.0f) & 0x3FF) << 10);
|
||||
ret = true;
|
||||
tab.SetSamplerFlags(sampler.SamplerId, sampler.Flags);
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGuiUtil.LabeledHelpMarker("Level of Detail Bias", "Offset from the calculated mipmap level.\n\nHigher means that the texture will start to lose detail nearer.\nLower means that the texture will keep its detail until farther.");
|
||||
|
||||
var minLod = (int)((sampler.Flags >> 20) & 0xF);
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
|
||||
if (ImGui.DragInt("##MinLoD", ref minLod, 0.1f, 0, 15))
|
||||
{
|
||||
sampler.Flags = (uint)((sampler.Flags & ~0x00F00000) | ((uint)Math.Clamp(minLod, 0, 15) << 20));
|
||||
ret = true;
|
||||
tab.SetSamplerFlags(sampler.SamplerId, sampler.Flags);
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGuiUtil.LabeledHelpMarker("Minimum Level of Detail", "Most detailed mipmap level to use.\n\n0 is the full-sized texture, 1 is the half-sized texture, 2 is the quarter-sized texture, and so on.\n15 will forcibly reduce the texture to its smallest mipmap.");
|
||||
|
||||
using var t = ImRaii.TreeNode("Advanced Settings");
|
||||
if (!t)
|
||||
return ret;
|
||||
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
|
||||
if (InputHexUInt16("Texture Flags", ref texture.Flags,
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))
|
||||
ret = true;
|
||||
|
||||
var samplerFlags = (int)sampler.Flags;
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
|
||||
if (ImGui.InputInt("Sampler Flags", ref samplerFlags, 0, 0,
|
||||
ImGuiInputTextFlags.CharsHexadecimal | (disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None)))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.Samplers[idx].Flags = (uint)samplerFlags;
|
||||
ret = true;
|
||||
tab.SetSamplerFlags(samplerCrc, (uint)samplerFlags);
|
||||
}
|
||||
|
||||
if (!disabled
|
||||
&& tab.OrphanedSamplers.Count == 0
|
||||
&& tab.AliasedSamplerCount == 0
|
||||
&& ImGui.Button("Remove Sampler"))
|
||||
{
|
||||
tab.Mtrl.Textures = tab.Mtrl.Textures.RemoveItems(sampler.TextureIndex);
|
||||
tab.Mtrl.ShaderPackage.Samplers = tab.Mtrl.ShaderPackage.Samplers.RemoveItems(idx--);
|
||||
for (var i = 0; i < tab.Mtrl.ShaderPackage.Samplers.Length; ++i)
|
||||
{
|
||||
if (tab.Mtrl.ShaderPackage.Samplers[i].TextureIndex >= sampler.TextureIndex)
|
||||
--tab.Mtrl.ShaderPackage.Samplers[i].TextureIndex;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
tab.UpdateSamplers();
|
||||
tab.UpdateTextureLabels();
|
||||
sampler.Flags = (uint)samplerFlags;
|
||||
ret = true;
|
||||
tab.SetSamplerFlags(sampler.SamplerId, (uint)samplerFlags);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialNewSampler(MtrlTab tab)
|
||||
{
|
||||
var (name, id) = tab.MissingSamplers[tab.NewSamplerIdx];
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 450.0f);
|
||||
using (var c = ImRaii.Combo("##NewSamplerId", $"{name} (ID: 0x{id:X8})"))
|
||||
{
|
||||
if (c)
|
||||
foreach (var (sampler, idx) in tab.MissingSamplers.WithIndex())
|
||||
{
|
||||
if (ImGui.Selectable($"{sampler.Name} (ID: 0x{sampler.Id:X8})", sampler.Id == tab.NewSamplerId))
|
||||
{
|
||||
tab.NewSamplerIdx = idx;
|
||||
tab.NewSamplerId = sampler.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (!ImGui.Button("Add Sampler"))
|
||||
return false;
|
||||
|
||||
var newSamplerId = tab.NewSamplerId;
|
||||
tab.Mtrl.ShaderPackage.Samplers = tab.Mtrl.ShaderPackage.Samplers.AddItem(new Sampler
|
||||
{
|
||||
SamplerId = newSamplerId,
|
||||
TextureIndex = (byte)tab.Mtrl.Textures.Length,
|
||||
Flags = 0,
|
||||
});
|
||||
tab.Mtrl.Textures = tab.Mtrl.Textures.AddItem(new MtrlFile.Texture
|
||||
{
|
||||
Path = string.Empty,
|
||||
Flags = 0,
|
||||
});
|
||||
tab.UpdateSamplers();
|
||||
tab.UpdateTextureLabels();
|
||||
tab.SetSamplerFlags(newSamplerId, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialSamplers(MtrlTab tab, bool disabled)
|
||||
{
|
||||
if (tab.Mtrl.ShaderPackage.Samplers.Length == 0
|
||||
&& tab.Mtrl.Textures.Length == 0
|
||||
&& (disabled || (tab.AssociatedShpk?.Samplers.All(sampler => sampler.Slot != 2) ?? false)))
|
||||
return false;
|
||||
|
||||
using var t = ImRaii.TreeNode("Samplers");
|
||||
if (!t)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
for (var idx = 0; idx < tab.Mtrl.ShaderPackage.Samplers.Length; ++idx)
|
||||
ret |= DrawMaterialSampler(tab, disabled, ref idx);
|
||||
|
||||
if (tab.OrphanedSamplers.Count > 0)
|
||||
{
|
||||
using var t2 = ImRaii.TreeNode($"Orphan Textures ({tab.OrphanedSamplers.Count})");
|
||||
if (t2)
|
||||
foreach (var idx in tab.OrphanedSamplers)
|
||||
{
|
||||
ImRaii.TreeNode($"#{idx}: {Path.GetFileName(tab.Mtrl.Textures[idx].Path)} - {tab.Mtrl.Textures[idx].Flags:X4}",
|
||||
ImGuiTreeNodeFlags.Leaf)
|
||||
.Dispose();
|
||||
}
|
||||
}
|
||||
else if (!disabled && tab.MissingSamplers.Count > 0 && tab.AliasedSamplerCount == 0 && tab.Mtrl.Textures.Length < 255)
|
||||
{
|
||||
ret |= DrawMaterialNewSampler(tab);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawMaterialShaderResources(MtrlTab tab, bool disabled)
|
||||
private bool DrawMaterialShader(MtrlTab tab, bool disabled)
|
||||
{
|
||||
var ret = false;
|
||||
if (!ImGui.CollapsingHeader("Advanced Shader Resources"))
|
||||
return ret;
|
||||
if (ImGui.CollapsingHeader(tab.ShaderHeader))
|
||||
{
|
||||
ret |= DrawPackageNameInput(tab, disabled);
|
||||
ret |= DrawShaderFlagsInput(tab, disabled);
|
||||
DrawCustomAssociations(tab);
|
||||
ret |= DrawMaterialShaderKeys(tab, disabled);
|
||||
DrawMaterialShaders(tab);
|
||||
}
|
||||
|
||||
if (tab.AssociatedShpkDevkit == null)
|
||||
{
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
GC.KeepAlive(tab);
|
||||
|
||||
var textColor = ImGui.GetColorU32(ImGuiCol.Text);
|
||||
var textColorWarning = (textColor & 0xFF000000u) | ((textColor & 0x00FEFEFE) >> 1) | (tab.AssociatedShpk == null ? 0x80u : 0x8080u); // Half red or yellow
|
||||
|
||||
using var c = ImRaii.PushColor(ImGuiCol.Text, textColorWarning);
|
||||
|
||||
ImGui.TextUnformatted(tab.AssociatedShpk == null
|
||||
? "Unable to find a suitable .shpk file for cross-references. Some functionality will be missing."
|
||||
: "No dev-kit file found for this material's shaders. Please install one for optimal editing experience, such as actual constant names instead of hexadecimal identifiers.");
|
||||
}
|
||||
|
||||
ret |= DrawPackageNameInput(tab, disabled);
|
||||
ret |= DrawShaderFlagsInput(tab, disabled);
|
||||
DrawCustomAssociations(tab);
|
||||
ret |= DrawMaterialShaderKeys(tab, disabled);
|
||||
DrawMaterialShaders(tab);
|
||||
ret |= DrawMaterialConstants(tab, disabled);
|
||||
ret |= DrawMaterialSamplers(tab, disabled);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -500,26 +443,25 @@ public partial class ModEditWindow
|
|||
_ => null,
|
||||
};
|
||||
}
|
||||
private static string VectorSwizzle(int firstComponent, int lastComponent)
|
||||
=> (firstComponent, lastComponent) switch
|
||||
{
|
||||
(0, 4) => " ",
|
||||
(0, 0) => ".x ",
|
||||
(0, 1) => ".xy ",
|
||||
(0, 2) => ".xyz ",
|
||||
(0, 3) => " ",
|
||||
(1, 1) => ".y ",
|
||||
(1, 2) => ".yz ",
|
||||
(1, 3) => ".yzw ",
|
||||
(2, 2) => ".z ",
|
||||
(2, 3) => ".zw ",
|
||||
(3, 3) => ".w ",
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
private static (string? Name, bool ComponentOnly) MaterialParamRangeName(string prefix, int valueOffset, int valueLength)
|
||||
{
|
||||
static string VectorSwizzle(int firstComponent, int lastComponent)
|
||||
=> (firstComponent, lastComponent) switch
|
||||
{
|
||||
(0, 4) => " ",
|
||||
(0, 0) => ".x ",
|
||||
(0, 1) => ".xy ",
|
||||
(0, 2) => ".xyz ",
|
||||
(0, 3) => " ",
|
||||
(1, 1) => ".y ",
|
||||
(1, 2) => ".yz ",
|
||||
(1, 3) => ".yzw ",
|
||||
(2, 2) => ".z ",
|
||||
(2, 3) => ".zw ",
|
||||
(3, 3) => ".w ",
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
if (valueLength == 0 || valueOffset < 0)
|
||||
return (null, false);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,16 +18,14 @@ public partial class ModEditWindow
|
|||
DrawMaterialLivePreviewRebind( tab, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
var ret = DrawMaterialTextureChange( tab, disabled );
|
||||
var ret = DrawBackFaceAndTransparency( tab, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawBackFaceAndTransparency( tab, disabled );
|
||||
ret |= DrawMaterialShader( tab, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawMaterialTextureChange( tab, disabled );
|
||||
ret |= DrawMaterialColorSetChange( tab, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawMaterialShaderResources( tab, disabled );
|
||||
ret |= DrawMaterialConstants( tab, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
DrawOtherMaterialDetails( tab.Mtrl, disabled );
|
||||
|
|
@ -40,35 +38,87 @@ public partial class ModEditWindow
|
|||
if (disabled)
|
||||
return;
|
||||
|
||||
if (ImGui.Button("Reload live-preview"))
|
||||
if (ImGui.Button("Reload live preview"))
|
||||
tab.BindToMaterialInstances();
|
||||
|
||||
if (tab.MaterialPreviewers.Count == 0 && tab.ColorSetPreviewers.Count == 0)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
|
||||
var textColor = ImGui.GetColorU32(ImGuiCol.Text);
|
||||
var textColorWarning = (textColor & 0xFF000000u) | ((textColor & 0x00FEFEFE) >> 1) | 0x80u; // Half red
|
||||
|
||||
using var c = ImRaii.PushColor(ImGuiCol.Text, textColorWarning);
|
||||
|
||||
ImGui.TextUnformatted("The current material has not been found on your character. Please check the Import from Screen tab for more information.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool DrawMaterialTextureChange( MtrlTab tab, bool disabled )
|
||||
{
|
||||
var ret = false;
|
||||
using var table = ImRaii.Table( "##Textures", 2 );
|
||||
ImGui.TableSetupColumn( "Path", ImGuiTableColumnFlags.WidthStretch );
|
||||
ImGui.TableSetupColumn( "Name", ImGuiTableColumnFlags.WidthFixed, tab.TextureLabelWidth * UiHelpers.Scale );
|
||||
for( var i = 0; i < tab.Mtrl.Textures.Length; ++i )
|
||||
if( tab.Textures.Count == 0 )
|
||||
{
|
||||
using var _ = ImRaii.PushId( i );
|
||||
var tmp = tab.Mtrl.Textures[ i ].Path;
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
if( !ImGui.CollapsingHeader( "Textures and Samplers", ImGuiTreeNodeFlags.DefaultOpen ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var frameHeight = ImGui.GetFrameHeight();
|
||||
var ret = false;
|
||||
using var table = ImRaii.Table( "##Textures", 3 );
|
||||
|
||||
ImGui.TableSetupColumn( string.Empty, ImGuiTableColumnFlags.WidthFixed, frameHeight );
|
||||
ImGui.TableSetupColumn( "Path" , ImGuiTableColumnFlags.WidthStretch );
|
||||
ImGui.TableSetupColumn( "Name" , ImGuiTableColumnFlags.WidthFixed, tab.TextureLabelWidth * UiHelpers.Scale );
|
||||
for( var i = 0; i < tab.Textures.Count; ++i )
|
||||
{
|
||||
var (label, textureI, samplerI, description, monoFont) = tab.Textures[i];
|
||||
|
||||
using var _ = ImRaii.PushId( samplerI );
|
||||
var tmp = tab.Mtrl.Textures[ textureI ].Path;
|
||||
var unfolded = tab.UnfoldedTextures.Contains( samplerI );
|
||||
ImGui.TableNextColumn();
|
||||
if( ImGuiUtil.DrawDisabledButton( ( unfolded ? FontAwesomeIcon.CaretDown : FontAwesomeIcon.CaretRight ).ToIconString(), new Vector2( frameHeight ),
|
||||
"Settings for this texture and the associated sampler", false, true ) )
|
||||
{
|
||||
unfolded = !unfolded;
|
||||
if( unfolded )
|
||||
tab.UnfoldedTextures.Add( samplerI );
|
||||
else
|
||||
tab.UnfoldedTextures.Remove( samplerI );
|
||||
}
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth( ImGui.GetContentRegionAvail().X );
|
||||
if( ImGui.InputText( string.Empty, ref tmp, Utf8GamePath.MaxGamePathLength,
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None )
|
||||
&& tmp.Length > 0
|
||||
&& tmp != tab.Mtrl.Textures[ i ].Path )
|
||||
&& tmp != tab.Mtrl.Textures[ textureI ].Path )
|
||||
{
|
||||
ret = true;
|
||||
tab.Mtrl.Textures[ i ].Path = tmp;
|
||||
ret = true;
|
||||
tab.Mtrl.Textures[ textureI ].Path = tmp;
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted( tab.TextureLabels[ i ] );
|
||||
using( var font = ImRaii.PushFont( UiBuilder.MonoFont, monoFont ) )
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
if( description.Length > 0 )
|
||||
ImGuiUtil.LabeledHelpMarker( label, description );
|
||||
else
|
||||
ImGui.TextUnformatted( label );
|
||||
}
|
||||
|
||||
if( unfolded )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ret |= DrawMaterialSampler( tab, disabled, textureI, samplerI );
|
||||
ImGui.TableNextColumn();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ using Penumbra.GameData;
|
|||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.String;
|
||||
using Penumbra.UI.AdvancedWindow;
|
||||
using static Penumbra.GameData.Files.ShpkFile;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
|
@ -40,7 +39,13 @@ public partial class ModEditWindow
|
|||
ret |= DrawShaderPackageMaterialParamLayout( file, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawOtherShaderPackageDetails( file, disabled );
|
||||
ret |= DrawShaderPackageResources( file, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
DrawShaderPackageSelection( file );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
DrawOtherShaderPackageDetails( file );
|
||||
|
||||
file.FileDialog.Draw();
|
||||
|
||||
|
|
@ -50,7 +55,18 @@ public partial class ModEditWindow
|
|||
}
|
||||
|
||||
private static void DrawShaderPackageSummary( ShpkTab tab )
|
||||
=> ImGui.TextUnformatted( tab.Header );
|
||||
{
|
||||
ImGui.TextUnformatted( tab.Header );
|
||||
if( !tab.Shpk.Disassembled )
|
||||
{
|
||||
var textColor = ImGui.GetColorU32( ImGuiCol.Text );
|
||||
var textColorWarning = ( textColor & 0xFF000000u ) | ( ( textColor & 0x00FEFEFE ) >> 1 ) | 0x80u; // Half red
|
||||
|
||||
using var c = ImRaii.PushColor( ImGuiCol.Text, textColorWarning );
|
||||
|
||||
ImGui.TextUnformatted( "Your system doesn't support disassembling shaders. Some functionality will be missing." );
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawShaderExportButton( ShpkTab tab, string objectName, Shader shader, int idx )
|
||||
{
|
||||
|
|
@ -163,7 +179,7 @@ public partial class ModEditWindow
|
|||
}
|
||||
|
||||
DrawShaderExportButton( tab, objectName, shader, idx );
|
||||
if( !disabled )
|
||||
if( !disabled && tab.Shpk.Disassembled )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
DrawShaderImportButton( tab, objectName, shaders, idx );
|
||||
|
|
@ -182,7 +198,8 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
|
||||
DrawRawDisassembly( shader );
|
||||
if( tab.Shpk.Disassembled )
|
||||
DrawRawDisassembly( shader );
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
@ -276,7 +293,9 @@ public partial class ModEditWindow
|
|||
|
||||
private static bool DrawShaderPackageMaterialMatrix( ShpkTab tab, bool disabled )
|
||||
{
|
||||
ImGui.TextUnformatted( "Parameter positions (continuations are grayed out, unused values are red):" );
|
||||
ImGui.TextUnformatted( tab.Shpk.Disassembled
|
||||
? "Parameter positions (continuations are grayed out, unused values are red):"
|
||||
: "Parameter positions (continuations are grayed out):" );
|
||||
|
||||
using var table = ImRaii.Table( "##MaterialParamLayout", 5,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg );
|
||||
|
|
@ -471,6 +490,22 @@ public partial class ModEditWindow
|
|||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawShaderPackageResources( ShpkTab tab, bool disabled )
|
||||
{
|
||||
var ret = false;
|
||||
|
||||
if( !ImGui.CollapsingHeader( "Shader Resources" ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ret |= DrawShaderPackageResourceArray( "Constant Buffers", "type", true, tab.Shpk.Constants, disabled );
|
||||
ret |= DrawShaderPackageResourceArray( "Samplers", "type", false, tab.Shpk.Samplers, disabled );
|
||||
ret |= DrawShaderPackageResourceArray( "Unordered Access Views", "type", false, tab.Shpk.Uavs, disabled );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void DrawKeyArray( string arrayName, bool withId, IReadOnlyCollection< Key > keys )
|
||||
{
|
||||
if( keys.Count == 0 )
|
||||
|
|
@ -513,7 +548,7 @@ public partial class ModEditWindow
|
|||
foreach( var (node, idx) in tab.Shpk.Nodes.WithIndex() )
|
||||
{
|
||||
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
using var t2 = ImRaii.TreeNode( $"#{idx:D4}: ID: 0x{node.Id:X8}" );
|
||||
using var t2 = ImRaii.TreeNode( $"#{idx:D4}: Selector: 0x{node.Selector:X8}" );
|
||||
if( !t2 )
|
||||
{
|
||||
continue;
|
||||
|
|
@ -549,39 +584,38 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
|
||||
private static bool DrawOtherShaderPackageDetails( ShpkTab tab, bool disabled )
|
||||
private static void DrawShaderPackageSelection( ShpkTab tab )
|
||||
{
|
||||
var ret = false;
|
||||
|
||||
if( !ImGui.CollapsingHeader( "Further Content" ) )
|
||||
if( !ImGui.CollapsingHeader( "Shader Selection" ) )
|
||||
{
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
ImRaii.TreeNode( $"Version: 0x{tab.Shpk.Version:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
|
||||
ret |= DrawShaderPackageResourceArray( "Constant Buffers", "type", true, tab.Shpk.Constants, disabled );
|
||||
ret |= DrawShaderPackageResourceArray( "Samplers", "type", false, tab.Shpk.Samplers, disabled );
|
||||
ret |= DrawShaderPackageResourceArray( "Unordered Access Views", "type", false, tab.Shpk.Uavs, disabled );
|
||||
|
||||
DrawKeyArray( "System Keys", true, tab.Shpk.SystemKeys );
|
||||
DrawKeyArray( "Scene Keys", true, tab.Shpk.SceneKeys );
|
||||
DrawKeyArray( "Material Keys", true, tab.Shpk.MaterialKeys );
|
||||
DrawKeyArray( "Sub-View Keys", false, tab.Shpk.SubViewKeys );
|
||||
|
||||
DrawShaderPackageNodes( tab );
|
||||
if( tab.Shpk.Items.Length > 0 )
|
||||
using var t = ImRaii.TreeNode( $"Node Selectors ({tab.Shpk.NodeSelectors.Count})###NodeSelectors" );
|
||||
if( t )
|
||||
{
|
||||
using var t = ImRaii.TreeNode( $"Items ({tab.Shpk.Items.Length})###Items" );
|
||||
if( t )
|
||||
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
foreach( var selector in tab.Shpk.NodeSelectors )
|
||||
{
|
||||
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
foreach( var (item, idx) in tab.Shpk.Items.WithIndex() )
|
||||
{
|
||||
ImRaii.TreeNode( $"#{idx:D4}: ID: 0x{item.Id:X8}, node: {item.Node}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
}
|
||||
ImRaii.TreeNode( $"#{selector.Value:D4}: Selector: 0x{selector.Key:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawOtherShaderPackageDetails( ShpkTab tab )
|
||||
{
|
||||
if( !ImGui.CollapsingHeader( "Further Content" ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImRaii.TreeNode( $"Version: 0x{tab.Shpk.Version:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
|
||||
if( tab.Shpk.AdditionalData.Length > 0 )
|
||||
{
|
||||
|
|
@ -591,8 +625,6 @@ public partial class ModEditWindow
|
|||
ImGuiUtil.TextWrapped( string.Join( ' ', tab.Shpk.AdditionalData.Select( c => $"{c:X2}" ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static string UsedComponentString( bool withSize, in Resource resource )
|
||||
|
|
|
|||
|
|
@ -27,8 +27,16 @@ public partial class ModEditWindow
|
|||
public ShpkTab(FileDialogService fileDialog, byte[] bytes)
|
||||
{
|
||||
FileDialog = fileDialog;
|
||||
Shpk = new ShpkFile(bytes, true);
|
||||
Header = $"Shader Package for DirectX {(int)Shpk.DirectXVersion}";
|
||||
try
|
||||
{
|
||||
Shpk = new ShpkFile(bytes, true);
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
Shpk = new ShpkFile(bytes, false);
|
||||
}
|
||||
|
||||
Header = $"Shader Package for DirectX {(int)Shpk.DirectXVersion}";
|
||||
Extension = Shpk.DirectXVersion switch
|
||||
{
|
||||
ShpkFile.DxVersion.DirectX9 => ".cso",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
|
@ -21,6 +23,7 @@ using Penumbra.Meta;
|
|||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.UI.Classes;
|
||||
using Penumbra.Util;
|
||||
|
|
@ -523,6 +526,23 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
return new FullPath(path);
|
||||
}
|
||||
|
||||
private HashSet<Utf8GamePath> FindPathsStartingWith(ByteString prefix)
|
||||
{
|
||||
var ret = new HashSet<Utf8GamePath>();
|
||||
|
||||
foreach (var path in _activeCollections.Current.ResolvedFiles.Keys)
|
||||
if (path.Path.StartsWith(prefix))
|
||||
ret.Add(path);
|
||||
|
||||
if (_mod != null)
|
||||
foreach (var option in _mod.Groups.SelectMany(g => g).Append(_mod.Default))
|
||||
foreach (var path in option.Files.Keys)
|
||||
if (path.Path.StartsWith(prefix))
|
||||
ret.Add(path);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialog, ItemSwapTab itemSwapTab, IDataManager gameData,
|
||||
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, MetaFileManager metaFileManager,
|
||||
StainService stainService, ActiveCollections activeCollections, DalamudServices dalamud, ModMergeTab modMergeTab,
|
||||
|
|
|
|||
|
|
@ -195,21 +195,7 @@ public class ModPanelSettingsTab : ITab
|
|||
_collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, (uint)idx2);
|
||||
|
||||
if (option.Description.Length > 0)
|
||||
{
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
ImGui.SameLine();
|
||||
using (var _ = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled));
|
||||
ImGuiUtil.RightAlign(FontAwesomeIcon.InfoCircle.ToIconString(), ImGui.GetStyle().ItemSpacing.X);
|
||||
}
|
||||
|
||||
if (hovered)
|
||||
{
|
||||
using var tt = ImRaii.Tooltip();
|
||||
ImGui.TextUnformatted(option.Description);
|
||||
}
|
||||
}
|
||||
ImGuiUtil.SelectableHelpMarker(option.Description);
|
||||
|
||||
id.Pop();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue