mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
More restructuring.
This commit is contained in:
parent
c4a4aec221
commit
397362caa5
7 changed files with 912 additions and 860 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit ebaedd64ed28032e4c9bc34c0c1ec3488b2f0937
|
||||
Subproject commit 2feb762d72c8717d8ece6a0bf72298776312b2c1
|
||||
|
|
@ -23,8 +23,6 @@ public partial class MtrlFile : IWritable
|
|||
public ShaderPackageData ShaderPackage;
|
||||
public byte[] AdditionalData;
|
||||
|
||||
public ShpkFile? AssociatedShpk;
|
||||
|
||||
public bool ApplyDyeTemplate(StmFile stm, int colorSetIdx, int rowIdx, StainId stainId)
|
||||
{
|
||||
if (colorSetIdx < 0 || colorSetIdx >= ColorDyeSets.Length || rowIdx is < 0 or >= ColorSet.RowArray.NumRows)
|
||||
|
|
@ -79,7 +77,7 @@ public partial class MtrlFile : IWritable
|
|||
|
||||
}
|
||||
|
||||
public List<(Sampler?, ShpkFile.Resource?)> GetSamplersByTexture()
|
||||
public List<(Sampler?, ShpkFile.Resource?)> GetSamplersByTexture(ShpkFile? shpk)
|
||||
{
|
||||
var samplers = new List<(Sampler?, ShpkFile.Resource?)>();
|
||||
for (var i = 0; i < Textures.Length; ++i)
|
||||
|
|
@ -88,7 +86,7 @@ public partial class MtrlFile : IWritable
|
|||
}
|
||||
foreach (var sampler in ShaderPackage.Samplers)
|
||||
{
|
||||
samplers[sampler.TextureIndex] = (sampler, AssociatedShpk?.GetSamplerById(sampler.SamplerId));
|
||||
samplers[sampler.TextureIndex] = (sampler, shpk?.GetSamplerById(sampler.SamplerId));
|
||||
}
|
||||
|
||||
return samplers;
|
||||
|
|
|
|||
436
Penumbra/UI/Classes/ModEditWindow.Materials.ColorSet.cs
Normal file
436
Penumbra/UI/Classes/ModEditWindow.Materials.ColorSet.cs
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
namespace Penumbra.UI.Classes;
|
||||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private static bool DrawMaterialColorSetChange( MtrlFile file, bool disabled )
|
||||
{
|
||||
if( !file.ColorSets.Any( c => c.HasRows ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ColorSetCopyAllClipboardButton( file, 0 );
|
||||
ImGui.SameLine();
|
||||
var ret = ColorSetPasteAllClipboardButton( file, 0 );
|
||||
ImGui.SameLine();
|
||||
ImGui.Dummy( ImGuiHelpers.ScaledVector2( 20, 0 ) );
|
||||
ImGui.SameLine();
|
||||
ret |= DrawPreviewDye( file, disabled );
|
||||
|
||||
using var table = ImRaii.Table( "##ColorSets", 11,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerV );
|
||||
if( !table )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( string.Empty );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Row" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Diffuse" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Specular" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Emissive" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Gloss" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Tile" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Repeat" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Skew" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Dye" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Dye Preview" );
|
||||
|
||||
for( var j = 0; j < file.ColorSets.Length; ++j )
|
||||
{
|
||||
using var _ = ImRaii.PushId( j );
|
||||
for( var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i )
|
||||
{
|
||||
ret |= DrawColorSetRow( file, j, i, disabled );
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private static void ColorSetCopyAllClipboardButton( MtrlFile file, int colorSetIdx )
|
||||
{
|
||||
if( !ImGui.Button( "Export All Rows to Clipboard", ImGuiHelpers.ScaledVector2( 200, 0 ) ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var data1 = file.ColorSets[ colorSetIdx ].Rows.AsBytes();
|
||||
var data2 = file.ColorDyeSets.Length > colorSetIdx ? file.ColorDyeSets[ colorSetIdx ].Rows.AsBytes() : ReadOnlySpan< byte >.Empty;
|
||||
var array = new byte[data1.Length + data2.Length];
|
||||
data1.TryCopyTo( array );
|
||||
data2.TryCopyTo( array.AsSpan( data1.Length ) );
|
||||
var text = Convert.ToBase64String( array );
|
||||
ImGui.SetClipboardText( text );
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private static bool DrawPreviewDye( MtrlFile file, bool disabled )
|
||||
{
|
||||
var (dyeId, (name, dyeColor, _)) = Penumbra.StainManager.StainCombo.CurrentSelection;
|
||||
var tt = dyeId == 0 ? "Select a preview dye first." : "Apply all preview values corresponding to the dye template and chosen dye where dyeing is enabled.";
|
||||
if( ImGuiUtil.DrawDisabledButton( "Apply Preview Dye", Vector2.Zero, tt, disabled || dyeId == 0 ) )
|
||||
{
|
||||
var ret = false;
|
||||
for( var j = 0; j < file.ColorDyeSets.Length; ++j )
|
||||
{
|
||||
for( var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i )
|
||||
{
|
||||
ret |= file.ApplyDyeTemplate( Penumbra.StainManager.StmFile, j, i, dyeId );
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
var label = dyeId == 0 ? "Preview Dye###previewDye" : $"{name} (Preview)###previewDye";
|
||||
Penumbra.StainManager.StainCombo.Draw( label, dyeColor, string.Empty, true );
|
||||
return false;
|
||||
}
|
||||
|
||||
private static unsafe bool ColorSetPasteAllClipboardButton( MtrlFile file, int colorSetIdx )
|
||||
{
|
||||
if( !ImGui.Button( "Import All Rows from Clipboard", ImGuiHelpers.ScaledVector2( 200, 0 ) ) || file.ColorSets.Length <= colorSetIdx )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var text = ImGui.GetClipboardText();
|
||||
var data = Convert.FromBase64String( text );
|
||||
if( data.Length < Marshal.SizeOf< MtrlFile.ColorSet.RowArray >() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ref var rows = ref file.ColorSets[ colorSetIdx ].Rows;
|
||||
fixed( void* ptr = data, output = &rows )
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( output, ptr, Marshal.SizeOf< MtrlFile.ColorSet.RowArray >() );
|
||||
if( data.Length >= Marshal.SizeOf< MtrlFile.ColorSet.RowArray >() + Marshal.SizeOf< MtrlFile.ColorDyeSet.RowArray >()
|
||||
&& file.ColorDyeSets.Length > colorSetIdx )
|
||||
{
|
||||
ref var dyeRows = ref file.ColorDyeSets[ colorSetIdx ].Rows;
|
||||
fixed( void* output2 = &dyeRows )
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( output2, ( byte* )ptr + Marshal.SizeOf< MtrlFile.ColorSet.RowArray >(), Marshal.SizeOf< MtrlFile.ColorDyeSet.RowArray >() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void ColorSetCopyClipboardButton( MtrlFile.ColorSet.Row row, MtrlFile.ColorDyeSet.Row dye )
|
||||
{
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Clipboard.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Export this row to your clipboard.", false, true ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = new byte[MtrlFile.ColorSet.Row.Size + 2];
|
||||
fixed( byte* ptr = data )
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( ptr, &row, MtrlFile.ColorSet.Row.Size );
|
||||
MemoryUtility.MemCpyUnchecked( ptr + MtrlFile.ColorSet.Row.Size, &dye, 2 );
|
||||
}
|
||||
|
||||
var text = Convert.ToBase64String( data );
|
||||
ImGui.SetClipboardText( text );
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe bool ColorSetPasteFromClipboardButton( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled )
|
||||
{
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Paste.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Import an exported row from your clipboard onto this row.", disabled, true ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = ImGui.GetClipboardText();
|
||||
var data = Convert.FromBase64String( text );
|
||||
if( data.Length != MtrlFile.ColorSet.Row.Size + 2
|
||||
|| file.ColorSets.Length <= colorSetIdx )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fixed( byte* ptr = data )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ] = *( MtrlFile.ColorSet.Row* )ptr;
|
||||
if( colorSetIdx < file.ColorDyeSets.Length )
|
||||
{
|
||||
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ] = *( MtrlFile.ColorDyeSet.Row* )( ptr + MtrlFile.ColorSet.Row.Size );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool DrawColorSetRow( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled )
|
||||
{
|
||||
static bool FixFloat( ref float val, float current )
|
||||
{
|
||||
val = ( float )( Half )val;
|
||||
return val != current;
|
||||
}
|
||||
|
||||
using var id = ImRaii.PushId( rowIdx );
|
||||
var row = file.ColorSets[ colorSetIdx ].Rows[ rowIdx ];
|
||||
var hasDye = file.ColorDyeSets.Length > colorSetIdx;
|
||||
var dye = hasDye ? file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ] : new MtrlFile.ColorDyeSet.Row();
|
||||
var floatSize = 70 * ImGuiHelpers.GlobalScale;
|
||||
var intSize = 45 * ImGuiHelpers.GlobalScale;
|
||||
ImGui.TableNextColumn();
|
||||
ColorSetCopyClipboardButton( row, dye );
|
||||
ImGui.SameLine();
|
||||
var ret = ColorSetPasteFromClipboardButton( file, colorSetIdx, rowIdx, disabled );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( $"#{rowIdx + 1:D2}" );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
using var dis = ImRaii.Disabled( disabled );
|
||||
ret |= ColorPicker( "##Diffuse", "Diffuse Color", row.Diffuse, c => file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Diffuse = c );
|
||||
if( hasDye )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ret |= ImGuiUtil.Checkbox( "##dyeDiffuse", "Apply Diffuse Color on Dye", dye.Diffuse,
|
||||
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Diffuse = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ret |= ColorPicker( "##Specular", "Specular Color", row.Specular, c => file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Specular = c );
|
||||
ImGui.SameLine();
|
||||
var tmpFloat = row.SpecularStrength;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##SpecularStrength", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.SpecularStrength ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].SpecularStrength = tmpFloat;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Specular Strength", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
if( hasDye )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ret |= ImGuiUtil.Checkbox( "##dyeSpecular", "Apply Specular Color on Dye", dye.Specular,
|
||||
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Specular = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
ImGui.SameLine();
|
||||
ret |= ImGuiUtil.Checkbox( "##dyeSpecularStrength", "Apply Specular Strength on Dye", dye.SpecularStrength,
|
||||
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].SpecularStrength = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ret |= ColorPicker( "##Emissive", "Emissive Color", row.Emissive, c => file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Emissive = c );
|
||||
if( hasDye )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ret |= ImGuiUtil.Checkbox( "##dyeEmissive", "Apply Emissive Color on Dye", dye.Emissive,
|
||||
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Emissive = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
tmpFloat = row.GlossStrength;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##GlossStrength", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.GlossStrength ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].GlossStrength = tmpFloat;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Gloss Strength", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
if( hasDye )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ret |= ImGuiUtil.Checkbox( "##dyeGloss", "Apply Gloss Strength on Dye", dye.Gloss,
|
||||
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Gloss = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].TileSet = ( ushort )tmpInt;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Tile Set", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
tmpFloat = row.MaterialRepeat.X;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##RepeatX", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.MaterialRepeat.X ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialRepeat = row.MaterialRepeat with { X = tmpFloat };
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Repeat X", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
ImGui.SameLine();
|
||||
tmpFloat = row.MaterialRepeat.Y;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##RepeatY", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.MaterialRepeat.Y ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialRepeat = row.MaterialRepeat with { Y = tmpFloat };
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Repeat Y", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
tmpFloat = row.MaterialSkew.X;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##SkewX", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.MaterialSkew.X ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialSkew = row.MaterialSkew with { X = tmpFloat };
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Skew X", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
ImGui.SameLine();
|
||||
tmpFloat = row.MaterialSkew.Y;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##SkewY", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.MaterialSkew.Y ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialSkew = row.MaterialSkew with { Y = tmpFloat };
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Skew Y", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if( hasDye )
|
||||
{
|
||||
if( Penumbra.StainManager.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize
|
||||
+ ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton ) )
|
||||
{
|
||||
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = Penumbra.StainManager.TemplateCombo.CurrentSelection;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Dye Template", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ret |= DrawDyePreview( file, colorSetIdx, rowIdx, disabled, dye, floatSize );
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawDyePreview( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled, MtrlFile.ColorDyeSet.Row dye, float floatSize )
|
||||
{
|
||||
var stain = Penumbra.StainManager.StainCombo.CurrentSelection.Key;
|
||||
if( stain == 0 || !Penumbra.StainManager.StmFile.Entries.TryGetValue( dye.Template, out var entry ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var values = entry[ ( int )stain ];
|
||||
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2 );
|
||||
|
||||
var ret = ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.PaintBrush.ToIconString(), new Vector2( ImGui.GetFrameHeight() ),
|
||||
"Apply the selected dye to this row.", disabled, true );
|
||||
|
||||
ret = ret && file.ApplyDyeTemplate( Penumbra.StainManager.StmFile, colorSetIdx, rowIdx, stain );
|
||||
|
||||
ImGui.SameLine();
|
||||
ColorPicker( "##diffusePreview", string.Empty, values.Diffuse, _ => { }, "D" );
|
||||
ImGui.SameLine();
|
||||
ColorPicker( "##specularPreview", string.Empty, values.Specular, _ => { }, "S" );
|
||||
ImGui.SameLine();
|
||||
ColorPicker( "##emissivePreview", string.Empty, values.Emissive, _ => { }, "E" );
|
||||
ImGui.SameLine();
|
||||
using var dis = ImRaii.Disabled();
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
ImGui.DragFloat( "##gloss", ref values.Gloss, 0, 0, 0, "%.2f G" );
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
ImGui.DragFloat( "##specularStrength", ref values.SpecularPower, 0, 0, 0, "%.2f S" );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool ColorPicker( string label, string tooltip, Vector3 input, Action< Vector3 > setter, string letter = "" )
|
||||
{
|
||||
var ret = false;
|
||||
var tmp = input;
|
||||
if( ImGui.ColorEdit3( label, ref tmp,
|
||||
ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.DisplayRGB | ImGuiColorEditFlags.InputRGB | ImGuiColorEditFlags.NoTooltip )
|
||||
&& tmp != input )
|
||||
{
|
||||
setter( tmp );
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if( letter.Length > 0 && ImGui.IsItemVisible() )
|
||||
{
|
||||
var textSize = ImGui.CalcTextSize( letter );
|
||||
var center = ImGui.GetItemRectMin() + ( ImGui.GetItemRectSize() - textSize ) / 2;
|
||||
var textColor = input.LengthSquared() < 0.25f ? 0x80FFFFFFu : 0x80000000u;
|
||||
ImGui.GetWindowDrawList().AddText( center, textColor, letter );
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( tooltip, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
159
Penumbra/UI/Classes/ModEditWindow.Materials.MtrlTab.cs
Normal file
159
Penumbra/UI/Classes/ModEditWindow.Materials.MtrlTab.cs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI.Classes;
|
||||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private sealed class MtrlTab : IWritable
|
||||
{
|
||||
private readonly ModEditWindow _edit;
|
||||
public readonly MtrlFile Mtrl;
|
||||
|
||||
public uint MaterialNewKeyId = 0;
|
||||
public uint MaterialNewKeyDefault = 0;
|
||||
public uint MaterialNewConstantId = 0;
|
||||
public uint MaterialNewSamplerId = 0;
|
||||
|
||||
|
||||
public ShpkFile? AssociatedShpk;
|
||||
public readonly List< string > TextureLabels = new(4);
|
||||
public FullPath LoadedShpkPath = FullPath.Empty;
|
||||
public string LoadedShpkPathName = string.Empty;
|
||||
public float TextureLabelWidth = 0f;
|
||||
|
||||
// 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 FullPath FindAssociatedShpk( out string defaultPath, out Utf8GamePath defaultGamePath )
|
||||
{
|
||||
defaultPath = GamePaths.Shader.ShpkPath( Mtrl.ShaderPackage.Name );
|
||||
if( !Utf8GamePath.FromString( defaultPath, out defaultGamePath, true ) )
|
||||
{
|
||||
return FullPath.Empty;
|
||||
}
|
||||
|
||||
return _edit.FindBestMatch( defaultGamePath );
|
||||
}
|
||||
|
||||
public void LoadShpk( FullPath path )
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadedShpkPath = path;
|
||||
var data = LoadedShpkPath.IsRooted
|
||||
? File.ReadAllBytes( LoadedShpkPath.FullName )
|
||||
: Dalamud.GameData.GetFile( LoadedShpkPath.InternalName.ToString() )?.Data;
|
||||
AssociatedShpk = data?.Length > 0 ? new ShpkFile( data ) : throw new Exception( "Failure to load file data." );
|
||||
LoadedShpkPathName = path.ToPath();
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
LoadedShpkPath = FullPath.Empty;
|
||||
LoadedShpkPathName = string.Empty;
|
||||
AssociatedShpk = null;
|
||||
ChatUtil.NotificationMessage( $"Could not load {LoadedShpkPath.ToPath()}:\n{e}", "Penumbra Advanced Editing", NotificationType.Error );
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
public void UpdateTextureLabels()
|
||||
{
|
||||
var samplers = Mtrl.GetSamplersByTexture( AssociatedShpk );
|
||||
TextureLabels.Clear();
|
||||
TextureLabelWidth = 50f * ImGuiHelpers.GlobalScale;
|
||||
using( var _ = ImRaii.PushFont( UiBuilder.MonoFont ) )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
TextureLabelWidth = TextureLabelWidth / ImGuiHelpers.GlobalScale + 4;
|
||||
}
|
||||
|
||||
public void UpdateShaderKeyLabels()
|
||||
{
|
||||
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 );
|
||||
}
|
||||
|
||||
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() );
|
||||
|
||||
if( MissingShaderKeyIndices.Count > 0 && MissingShaderKeyIndices.All( i => AssociatedShpk.MaterialKeys[ i ].Id != MaterialNewKeyId ) )
|
||||
{
|
||||
var key = AssociatedShpk.MaterialKeys[ MissingShaderKeyIndices[ 0 ] ];
|
||||
MaterialNewKeyId = key.Id;
|
||||
MaterialNewKeyDefault = key.DefaultValue;
|
||||
}
|
||||
|
||||
AvailableKeyValues.AddRange( AssociatedShpk.MaterialKeys.Select( k => DefinedShaderKeys.TryGetValue( k.Id, out var value ) ? value : k.DefaultValue ) );
|
||||
foreach( var node in AssociatedShpk.Nodes )
|
||||
{
|
||||
if( node.MaterialKeys.WithIndex().All( key => key.Value == AvailableKeyValues[ key.Index ] ) )
|
||||
{
|
||||
foreach( var pass in node.Passes )
|
||||
{
|
||||
vertexShaders.Add( ( int )pass.VertexShader );
|
||||
pixelShaders.Add( ( int )pass.PixelShader );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VertexShaders = $"Vertex Shaders: {( vertexShaders.Count > 0 ? string.Join( ", ", vertexShaders.Select( i => $"#{i}" ) ) : "???" )}";
|
||||
PixelShaders = $"Pixel Shaders: {( pixelShaders.Count > 0 ? string.Join( ", ", pixelShaders.Select( i => $"#{i}" ) ) : "???" )}";
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
UpdateTextureLabels();
|
||||
UpdateShaderKeyLabels();
|
||||
}
|
||||
|
||||
public MtrlTab( ModEditWindow edit, MtrlFile file )
|
||||
{
|
||||
_edit = edit;
|
||||
Mtrl = file;
|
||||
LoadShpk( FindAssociatedShpk( out _, out _ ) );
|
||||
}
|
||||
|
||||
public bool Valid
|
||||
=> Mtrl.Valid;
|
||||
|
||||
public byte[] Write()
|
||||
=> Mtrl.Write();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,17 +5,14 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.ImGuiFileDialog;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using ImGuiNET;
|
||||
using Lumina.Data.Parsing;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI.Classes;
|
||||
|
||||
|
|
@ -23,73 +20,20 @@ public partial class ModEditWindow
|
|||
{
|
||||
private readonly FileDialogManager _materialFileDialog = ConfigWindow.SetupFileManager();
|
||||
|
||||
private FullPath FindAssociatedShpk( MtrlFile mtrl, out string defaultPath, out Utf8GamePath defaultGamePath )
|
||||
{
|
||||
defaultPath = GamePaths.Shader.ShpkPath( mtrl.ShaderPackage.Name );
|
||||
if( !Utf8GamePath.FromString( defaultPath, out defaultGamePath, true ) )
|
||||
{
|
||||
return FullPath.Empty;
|
||||
}
|
||||
|
||||
return FindBestMatch( defaultGamePath );
|
||||
}
|
||||
|
||||
private void LoadAssociatedShpk( MtrlFile mtrl )
|
||||
{
|
||||
try
|
||||
{
|
||||
_mtrlTabState.LoadedShpkPath = FindAssociatedShpk( mtrl, out _, out _ );
|
||||
var data = _mtrlTabState.LoadedShpkPath.IsRooted
|
||||
? File.ReadAllBytes( _mtrlTabState.LoadedShpkPath.FullName )
|
||||
: Dalamud.GameData.GetFile( _mtrlTabState.LoadedShpkPath.InternalName.ToString() )?.Data;
|
||||
if( data?.Length > 0 )
|
||||
{
|
||||
mtrl.AssociatedShpk = new ShpkFile( data );
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Debug( $"Could not parse associated file {_mtrlTabState.LoadedShpkPath} to Shpk:\n{e}" );
|
||||
_mtrlTabState.LoadedShpkPath = FullPath.Empty;
|
||||
mtrl.AssociatedShpk = null;
|
||||
}
|
||||
|
||||
UpdateTextureLabels( mtrl );
|
||||
}
|
||||
|
||||
private void UpdateTextureLabels( MtrlFile file )
|
||||
{
|
||||
var samplers = file.GetSamplersByTexture();
|
||||
_mtrlTabState.TextureLabels.Clear();
|
||||
_mtrlTabState.TextureLabelWidth = 50f * ImGuiHelpers.GlobalScale;
|
||||
using( var _ = ImRaii.PushFont( UiBuilder.MonoFont ) )
|
||||
{
|
||||
for( var i = 0; i < file.Textures.Length; ++i )
|
||||
{
|
||||
var (sampler, shpkSampler) = samplers[ i ];
|
||||
var name = shpkSampler.HasValue ? shpkSampler.Value.Name : sampler.HasValue ? $"0x{sampler.Value.SamplerId:X8}" : $"#{i}";
|
||||
_mtrlTabState.TextureLabels.Add( name );
|
||||
_mtrlTabState.TextureLabelWidth = Math.Max( _mtrlTabState.TextureLabelWidth, ImGui.CalcTextSize( name ).X );
|
||||
}
|
||||
}
|
||||
|
||||
_mtrlTabState.TextureLabelWidth = _mtrlTabState.TextureLabelWidth / ImGuiHelpers.GlobalScale + 4;
|
||||
}
|
||||
|
||||
private bool DrawPackageNameInput( MtrlFile file, bool disabled )
|
||||
private bool DrawPackageNameInput( MtrlTab tab, bool disabled )
|
||||
{
|
||||
var ret = false;
|
||||
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
|
||||
if( ImGui.InputText( "Shader Package Name", ref file.ShaderPackage.Name, 63, disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) )
|
||||
if( ImGui.InputText( "Shader Package Name", ref tab.Mtrl.ShaderPackage.Name, 63, disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) )
|
||||
{
|
||||
ret = true;
|
||||
file.AssociatedShpk = null;
|
||||
_mtrlTabState.LoadedShpkPath = FullPath.Empty;
|
||||
ret = true;
|
||||
tab.AssociatedShpk = null;
|
||||
tab.LoadedShpkPath = FullPath.Empty;
|
||||
}
|
||||
|
||||
if( ImGui.IsItemDeactivatedAfterEdit() )
|
||||
{
|
||||
LoadAssociatedShpk( file );
|
||||
tab.LoadShpk( tab.FindAssociatedShpk( out _, out _ ) );
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
@ -114,20 +58,20 @@ public partial class ModEditWindow
|
|||
/// Show the currently associated shpk file, if any, and the buttons to associate
|
||||
/// a specific shpk from your drive, the modded shpk by path or the default shpk.
|
||||
/// </summary>
|
||||
private void DrawCustomAssociations( MtrlFile file )
|
||||
private void DrawCustomAssociations( MtrlTab tab )
|
||||
{
|
||||
var text = file.AssociatedShpk == null
|
||||
var text = tab.AssociatedShpk == null
|
||||
? "Associated .shpk file: None"
|
||||
: $"Associated .shpk file: {_mtrlTabState.LoadedShpkPath.ToPath()}";
|
||||
: $"Associated .shpk file: {tab.LoadedShpkPathName}";
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
|
||||
if( ImGui.Selectable( text ) )
|
||||
{
|
||||
ImGui.SetClipboardText( _mtrlTabState.LoadedShpkPath.IsRooted ? _mtrlTabState.LoadedShpkPath.FullName.Replace( "\\", "/" ) );
|
||||
ImGui.SetClipboardText( tab.LoadedShpkPathName );
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Click to copy file path to clipboard." );
|
||||
ImGuiUtil.HoverTooltip( "Click to copy file path to clipboard." );
|
||||
|
||||
if( ImGui.Button( "Associate Custom .shpk File" ) )
|
||||
{
|
||||
|
|
@ -138,358 +82,323 @@ public partial class ModEditWindow
|
|||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
file.AssociatedShpk = new ShpkFile( File.ReadAllBytes( name ) );
|
||||
_mtrlTabState.LoadedShpkPath = new FullPath( name );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
ChatUtil.NotificationMessage( $"Could not load {name}:\n{e.Message}", "Penumbra Advanced Editing", NotificationType.Error );
|
||||
}
|
||||
});
|
||||
tab.LoadShpk( new FullPath( name ) );
|
||||
} );
|
||||
}
|
||||
|
||||
var moddedPath = FindAssociatedShpk( file, out var defaultPath, out var gamePath );
|
||||
var moddedPath = tab.FindAssociatedShpk( out var defaultPath, out var gamePath );
|
||||
ImGui.SameLine();
|
||||
if( ImGuiUtil.DrawDisabledButton( "Associate Default .shpk File", Vector2.Zero, moddedPath.ToPath(), moddedPath.Equals( _mtrlTabState.LoadedShpkPath ) ) )
|
||||
if( ImGuiUtil.DrawDisabledButton( "Associate Default .shpk File", Vector2.Zero, moddedPath.ToPath(), moddedPath.Equals( tab.LoadedShpkPath ) ) )
|
||||
{
|
||||
LoadAssociatedShpk( file );
|
||||
if( file.AssociatedShpk == null )
|
||||
{
|
||||
ChatUtil.NotificationMessage( $"Could not load {moddedPath}.", "Penumbra Advanced Editing", NotificationType.Error );
|
||||
}
|
||||
tab.LoadShpk( moddedPath );
|
||||
}
|
||||
|
||||
if( !gamePath.Path.Equals( moddedPath.InternalName ) )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
if( ImGuiUtil.DrawDisabledButton( "Associate Unmodded .shpk File", Vector2.Zero, defaultPath, gamePath.Path.Equals( _mtrlTabState.LoadedShpkPath.InternalName ) ) )
|
||||
if( ImGuiUtil.DrawDisabledButton( "Associate Unmodded .shpk File", Vector2.Zero, defaultPath, gamePath.Path.Equals( tab.LoadedShpkPath.InternalName ) ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
file.AssociatedShpk = new ShpkFile( Dalamud.GameData.GetFile( defaultPath )?.Data ?? Array.Empty< byte >() );
|
||||
_mtrlTabState.LoadedShpkPath = new FullPath( defaultPath );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
ChatUtil.NotificationMessage( $"Could not load {defaultPath}:\n{e}", "Penumbra Advanced Editing", NotificationType.Error );
|
||||
}
|
||||
tab.LoadShpk( new FullPath( gamePath ) );
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
}
|
||||
|
||||
private bool DrawMaterialShaderKeys( MtrlFile file, bool disabled )
|
||||
|
||||
private static bool DrawShaderKey( MtrlTab tab, bool disabled, ShaderKey key, int idx )
|
||||
{
|
||||
var ret = false;
|
||||
if( file.ShaderPackage.ShaderKeys.Length > 0 || !disabled && file.AssociatedShpk != null && file.AssociatedShpk.MaterialKeys.Length > 0 )
|
||||
var ret = false;
|
||||
using var t2 = ImRaii.TreeNode( tab.ShaderKeyLabels[ idx ], disabled ? ImGuiTreeNodeFlags.Leaf : 0 );
|
||||
if( !t2 || disabled )
|
||||
{
|
||||
using var t = ImRaii.TreeNode( "Shader Keys" );
|
||||
if( t )
|
||||
return ret;
|
||||
}
|
||||
|
||||
var shpkKey = tab.AssociatedShpk?.GetMaterialKeyById( key.Category );
|
||||
if( shpkKey.HasValue )
|
||||
{
|
||||
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
|
||||
using var c = ImRaii.Combo( "Value", $"0x{key.Value:X8}" );
|
||||
if( c )
|
||||
{
|
||||
var definedKeys = new HashSet<uint>();
|
||||
|
||||
foreach( var (key, idx) in file.ShaderPackage.ShaderKeys.WithIndex() )
|
||||
foreach( var value in shpkKey.Value.Values )
|
||||
{
|
||||
definedKeys.Add( key.Category );
|
||||
using var t2 = ImRaii.TreeNode( $"#{idx}: 0x{key.Category:X8} = 0x{key.Value:X8}###{idx}: 0x{key.Category:X8}", disabled ? ImGuiTreeNodeFlags.Leaf : 0 );
|
||||
if( t2 )
|
||||
if( ImGui.Selectable( $"0x{value:X8}", value == key.Value ) )
|
||||
{
|
||||
if( !disabled )
|
||||
{
|
||||
var shpkKey = file.AssociatedShpk?.GetMaterialKeyById( key.Category );
|
||||
if( shpkKey.HasValue )
|
||||
{
|
||||
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 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 ) )
|
||||
{
|
||||
file.ShaderPackage.ShaderKeys[idx].Value = value;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( ImGui.Button( "Remove Key" ) )
|
||||
{
|
||||
file.ShaderPackage.ShaderKeys = file.ShaderPackage.ShaderKeys.RemoveItems( idx );
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( !disabled && file.AssociatedShpk != null )
|
||||
{
|
||||
var missingKeys = file.AssociatedShpk.MaterialKeys.Where( key => !definedKeys.Contains( key.Id ) ).ToArray();
|
||||
if( missingKeys.Length > 0 )
|
||||
{
|
||||
var selectedKey = Array.Find( missingKeys, key => key.Id == _mtrlTabState.MaterialNewKeyId );
|
||||
if( Array.IndexOf( missingKeys, selectedKey ) < 0 )
|
||||
{
|
||||
selectedKey = missingKeys[0];
|
||||
_mtrlTabState.MaterialNewKeyId = selectedKey.Id;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
|
||||
using( var c = ImRaii.Combo( "##NewConstantId", $"ID: 0x{selectedKey.Id:X8}" ) )
|
||||
{
|
||||
if( c )
|
||||
{
|
||||
foreach( var key in missingKeys )
|
||||
{
|
||||
if( ImGui.Selectable( $"ID: 0x{key.Id:X8}", key.Id == _mtrlTabState.MaterialNewKeyId ) )
|
||||
{
|
||||
selectedKey = key;
|
||||
_mtrlTabState.MaterialNewKeyId = key.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if( ImGui.Button( "Add Key" ) )
|
||||
{
|
||||
file.ShaderPackage.ShaderKeys = file.ShaderPackage.ShaderKeys.AddItem( new ShaderKey
|
||||
{
|
||||
Category = selectedKey.Id,
|
||||
Value = selectedKey.DefaultValue,
|
||||
} );
|
||||
ret = true;
|
||||
}
|
||||
tab.Mtrl.ShaderPackage.ShaderKeys[ idx ].Value = value;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( ImGui.Button( "Remove Key" ) )
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.ShaderKeys = tab.Mtrl.ShaderPackage.ShaderKeys.RemoveItems( idx );
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void DrawMaterialShaders( MtrlFile file )
|
||||
private static bool DrawNewShaderKey( MtrlTab tab )
|
||||
{
|
||||
if( file.AssociatedShpk != null )
|
||||
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
|
||||
using( var c = ImRaii.Combo( "##NewConstantId", $"ID: 0x{tab.MaterialNewKeyId:X8}" ) )
|
||||
{
|
||||
var definedKeys = new Dictionary<uint, uint>();
|
||||
foreach( var key in file.ShaderPackage.ShaderKeys )
|
||||
if( c )
|
||||
{
|
||||
definedKeys[key.Category] = key.Value;
|
||||
}
|
||||
|
||||
var materialKeys = Array.ConvertAll( file.AssociatedShpk.MaterialKeys, key =>
|
||||
{
|
||||
if( definedKeys.TryGetValue( key.Id, out var value ) )
|
||||
foreach( var idx in tab.MissingShaderKeyIndices )
|
||||
{
|
||||
return value;
|
||||
var key = tab.AssociatedShpk!.MaterialKeys[ idx ];
|
||||
|
||||
if( ImGui.Selectable( $"ID: 0x{key.Id:X8}", key.Id == tab.MaterialNewKeyId ) )
|
||||
{
|
||||
tab.MaterialNewKeyDefault = key.DefaultValue;
|
||||
tab.MaterialNewKeyId = key.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if( ImGui.Button( "Add Key" ) )
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.ShaderKeys = tab.Mtrl.ShaderPackage.ShaderKeys.AddItem( new ShaderKey
|
||||
{
|
||||
Category = tab.MaterialNewKeyId,
|
||||
Value = tab.MaterialNewKeyDefault,
|
||||
} );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var ret = false;
|
||||
foreach( var (key, idx) in tab.Mtrl.ShaderPackage.ShaderKeys.WithIndex() )
|
||||
{
|
||||
ret |= DrawShaderKey( tab, disabled, key, idx );
|
||||
}
|
||||
|
||||
if( !disabled && tab.AssociatedShpk != null && tab.MissingShaderKeyIndices.Count != 0 )
|
||||
{
|
||||
ret |= DrawNewShaderKey( tab );
|
||||
}
|
||||
|
||||
if( ret )
|
||||
{
|
||||
tab.UpdateShaderKeyLabels();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private static void DrawMaterialShaders( MtrlTab tab )
|
||||
{
|
||||
if( tab.AssociatedShpk == null )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImRaii.TreeNode( tab.VertexShaders, ImGuiTreeNodeFlags.Leaf ).Dispose();
|
||||
ImRaii.TreeNode( tab.PixelShaders, ImGuiTreeNodeFlags.Leaf ).Dispose();
|
||||
}
|
||||
|
||||
private bool DrawMaterialConstants( MtrlTab tab, bool disabled )
|
||||
{
|
||||
var ret = false;
|
||||
if( tab.Mtrl.ShaderPackage.Constants.Length <= 0
|
||||
&& tab.Mtrl.ShaderPackage.ShaderValues.Length <= 0
|
||||
&& ( disabled || tab.AssociatedShpk == null || tab.AssociatedShpk.Constants.Length <= 0 ) )
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
var materialParams = tab.AssociatedShpk?.GetConstantById( ShpkFile.MaterialParamsConstantId );
|
||||
|
||||
using var t = ImRaii.TreeNode( materialParams?.Name ?? "Constants" );
|
||||
if( t )
|
||||
{
|
||||
var orphanValues = new IndexSet( tab.Mtrl.ShaderPackage.ShaderValues.Length, true );
|
||||
var aliasedValueCount = 0;
|
||||
var definedConstants = new HashSet< uint >();
|
||||
var hasMalformedConstants = false;
|
||||
|
||||
foreach( var constant in tab.Mtrl.ShaderPackage.Constants )
|
||||
{
|
||||
definedConstants.Add( constant.Id );
|
||||
var values = tab.Mtrl.GetConstantValues( constant );
|
||||
if( tab.Mtrl.GetConstantValues( constant ).Length > 0 )
|
||||
{
|
||||
var unique = orphanValues.RemoveRange( constant.ByteOffset >> 2, values.Length );
|
||||
aliasedValueCount += values.Length - unique;
|
||||
}
|
||||
else
|
||||
{
|
||||
return key.DefaultValue;
|
||||
}
|
||||
} );
|
||||
var vertexShaders = new IndexSet( file.AssociatedShpk.VertexShaders.Length, false );
|
||||
var pixelShaders = new IndexSet( file.AssociatedShpk.PixelShaders.Length, false );
|
||||
foreach( var node in file.AssociatedShpk.Nodes )
|
||||
{
|
||||
if( node.MaterialKeys.WithIndex().All( key => key.Value == materialKeys[key.Index] ) )
|
||||
{
|
||||
foreach( var pass in node.Passes )
|
||||
{
|
||||
vertexShaders.Add( ( int )pass.VertexShader );
|
||||
pixelShaders.Add( ( int )pass.PixelShader );
|
||||
}
|
||||
hasMalformedConstants = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImRaii.TreeNode( $"Vertex Shaders: {( vertexShaders.Count > 0 ? string.Join( ", ", vertexShaders.Select( i => $"#{i}" ) ) : "???" )}", ImGuiTreeNodeFlags.Leaf )
|
||||
.Dispose();
|
||||
ImRaii.TreeNode( $"Pixel Shaders: {( pixelShaders.Count > 0 ? string.Join( ", ", pixelShaders.Select( i => $"#{i}" ) ) : "???" )}", ImGuiTreeNodeFlags.Leaf ).Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private bool DrawMaterialConstants( MtrlFile file, bool disabled )
|
||||
{
|
||||
var ret = false;
|
||||
if( file.ShaderPackage.Constants.Length > 0
|
||||
|| file.ShaderPackage.ShaderValues.Length > 0
|
||||
|| !disabled && file.AssociatedShpk != null && file.AssociatedShpk.Constants.Length > 0 )
|
||||
{
|
||||
var materialParams = file.AssociatedShpk?.GetConstantById( ShpkFile.MaterialParamsConstantId );
|
||||
|
||||
using var t = ImRaii.TreeNode( materialParams?.Name ?? "Constants" );
|
||||
if( t )
|
||||
foreach( var (constant, idx) in tab.Mtrl.ShaderPackage.Constants.WithIndex() )
|
||||
{
|
||||
var orphanValues = new IndexSet( file.ShaderPackage.ShaderValues.Length, true );
|
||||
var aliasedValueCount = 0;
|
||||
var definedConstants = new HashSet<uint>();
|
||||
var hasMalformedConstants = false;
|
||||
|
||||
foreach( var constant in file.ShaderPackage.Constants )
|
||||
var values = tab.Mtrl.GetConstantValues( constant );
|
||||
var paramValueOffset = -values.Length;
|
||||
if( values.Length > 0 )
|
||||
{
|
||||
definedConstants.Add( constant.Id );
|
||||
var values = file.GetConstantValues( constant );
|
||||
if( file.GetConstantValues( constant ).Length > 0 )
|
||||
var shpkParam = tab.AssociatedShpk?.GetMaterialParamById( constant.Id );
|
||||
var paramByteOffset = shpkParam.HasValue ? shpkParam.Value.ByteOffset : -1;
|
||||
if( ( paramByteOffset & 0x3 ) == 0 )
|
||||
{
|
||||
var unique = orphanValues.RemoveRange( constant.ByteOffset >> 2, values.Length );
|
||||
aliasedValueCount += values.Length - unique;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasMalformedConstants = true;
|
||||
paramValueOffset = paramByteOffset >> 2;
|
||||
}
|
||||
}
|
||||
|
||||
foreach( var (constant, idx) in file.ShaderPackage.Constants.WithIndex() )
|
||||
var (constantName, componentOnly) = MaterialParamRangeName( materialParams?.Name ?? "", paramValueOffset, values.Length );
|
||||
|
||||
using var t2 = ImRaii.TreeNode( $"#{idx}{( constantName != null ? ": " + constantName : "" )} (ID: 0x{constant.Id:X8})" );
|
||||
if( t2 )
|
||||
{
|
||||
var values = file.GetConstantValues( constant );
|
||||
var paramValueOffset = -values.Length;
|
||||
if( values.Length > 0 )
|
||||
{
|
||||
var shpkParam = file.AssociatedShpk?.GetMaterialParamById( constant.Id );
|
||||
var paramByteOffset = shpkParam.HasValue ? shpkParam.Value.ByteOffset : -1;
|
||||
if( ( paramByteOffset & 0x3 ) == 0 )
|
||||
var valueOffset = constant.ByteOffset >> 2;
|
||||
|
||||
for( var valueIdx = 0; valueIdx < values.Length; ++valueIdx )
|
||||
{
|
||||
paramValueOffset = paramByteOffset >> 2;
|
||||
}
|
||||
}
|
||||
|
||||
var (constantName, componentOnly) = MaterialParamRangeName( materialParams?.Name ?? "", paramValueOffset, values.Length );
|
||||
|
||||
using var t2 = ImRaii.TreeNode( $"#{idx}{( constantName != null ? ": " + constantName : "" )} (ID: 0x{constant.Id:X8})" );
|
||||
if( t2 )
|
||||
{
|
||||
if( values.Length > 0 )
|
||||
{
|
||||
var valueOffset = constant.ByteOffset >> 2;
|
||||
|
||||
for( var valueIdx = 0; valueIdx < values.Length; ++valueIdx )
|
||||
{
|
||||
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
|
||||
if( ImGui.InputFloat(
|
||||
$"{MaterialParamName( componentOnly, paramValueOffset + valueIdx ) ?? $"#{valueIdx}"} (at 0x{( valueOffset + valueIdx ) << 2:X4})",
|
||||
ref values[valueIdx], 0.0f, 0.0f, "%.3f",
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) )
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImRaii.TreeNode( $"Offset: 0x{constant.ByteOffset:X4}", ImGuiTreeNodeFlags.Leaf ).Dispose();
|
||||
ImRaii.TreeNode( $"Size: 0x{constant.ByteSize:X4}", ImGuiTreeNodeFlags.Leaf ).Dispose();
|
||||
}
|
||||
|
||||
if( !disabled
|
||||
&& !hasMalformedConstants
|
||||
&& orphanValues.Count == 0
|
||||
&& aliasedValueCount == 0
|
||||
&& ImGui.Button( "Remove Constant" ) )
|
||||
{
|
||||
file.ShaderPackage.ShaderValues = file.ShaderPackage.ShaderValues.RemoveItems( constant.ByteOffset >> 2, constant.ByteSize >> 2 );
|
||||
file.ShaderPackage.Constants = file.ShaderPackage.Constants.RemoveItems( idx );
|
||||
for( var i = 0; i < file.ShaderPackage.Constants.Length; ++i )
|
||||
{
|
||||
if( file.ShaderPackage.Constants[i].ByteOffset >= constant.ByteOffset )
|
||||
{
|
||||
file.ShaderPackage.Constants[i].ByteOffset -= constant.ByteSize;
|
||||
}
|
||||
}
|
||||
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( orphanValues.Count > 0 )
|
||||
{
|
||||
using var t2 = ImRaii.TreeNode( $"Orphan Values ({orphanValues.Count})" );
|
||||
if( t2 )
|
||||
{
|
||||
foreach( var idx in orphanValues )
|
||||
{
|
||||
ImGui.SetNextItemWidth( ImGui.GetFontSize() * 10.0f );
|
||||
if( ImGui.InputFloat( $"#{idx} (at 0x{idx << 2:X4})",
|
||||
ref file.ShaderPackage.ShaderValues[idx], 0.0f, 0.0f, "%.3f",
|
||||
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
|
||||
if( ImGui.InputFloat(
|
||||
$"{MaterialParamName( componentOnly, paramValueOffset + valueIdx ) ?? $"#{valueIdx}"} (at 0x{( valueOffset + valueIdx ) << 2:X4})",
|
||||
ref values[ valueIdx ], 0.0f, 0.0f, "%.3f",
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) )
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( !disabled && !hasMalformedConstants && file.AssociatedShpk != null )
|
||||
{
|
||||
var missingConstants = file.AssociatedShpk.MaterialParams.Where( constant
|
||||
=> ( constant.ByteOffset & 0x3 ) == 0 && ( constant.ByteSize & 0x3 ) == 0 && !definedConstants.Contains( constant.Id ) ).ToArray();
|
||||
if( missingConstants.Length > 0 )
|
||||
else
|
||||
{
|
||||
var selectedConstant = Array.Find( missingConstants, constant => constant.Id == _mtrlTabState.MaterialNewConstantId );
|
||||
if( selectedConstant.ByteSize == 0 )
|
||||
{
|
||||
selectedConstant = missingConstants[0];
|
||||
_mtrlTabState.MaterialNewConstantId = selectedConstant.Id;
|
||||
}
|
||||
ImRaii.TreeNode( $"Offset: 0x{constant.ByteOffset:X4}", ImGuiTreeNodeFlags.Leaf ).Dispose();
|
||||
ImRaii.TreeNode( $"Size: 0x{constant.ByteSize:X4}", ImGuiTreeNodeFlags.Leaf ).Dispose();
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 450.0f );
|
||||
var (selectedConstantName, _) = MaterialParamRangeName( materialParams?.Name ?? "", selectedConstant.ByteOffset >> 2, selectedConstant.ByteSize >> 2 );
|
||||
using( var c = ImRaii.Combo( "##NewConstantId", $"{selectedConstantName} (ID: 0x{selectedConstant.Id:X8})" ) )
|
||||
if( !disabled
|
||||
&& !hasMalformedConstants
|
||||
&& orphanValues.Count == 0
|
||||
&& aliasedValueCount == 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( c )
|
||||
if( tab.Mtrl.ShaderPackage.Constants[ i ].ByteOffset >= constant.ByteOffset )
|
||||
{
|
||||
foreach( var constant in missingConstants )
|
||||
{
|
||||
var (constantName, _) = MaterialParamRangeName( materialParams?.Name ?? "", constant.ByteOffset >> 2, constant.ByteSize >> 2 );
|
||||
if( ImGui.Selectable( $"{constantName} (ID: 0x{constant.Id:X8})", constant.Id == _mtrlTabState.MaterialNewConstantId ) )
|
||||
{
|
||||
selectedConstant = constant;
|
||||
_mtrlTabState.MaterialNewConstantId = constant.Id;
|
||||
}
|
||||
}
|
||||
tab.Mtrl.ShaderPackage.Constants[ i ].ByteOffset -= constant.ByteSize;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if( ImGui.Button( "Add Constant" ) )
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( orphanValues.Count > 0 )
|
||||
{
|
||||
using var t2 = ImRaii.TreeNode( $"Orphan Values ({orphanValues.Count})" );
|
||||
if( t2 )
|
||||
{
|
||||
foreach( var idx in orphanValues )
|
||||
{
|
||||
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 ) )
|
||||
{
|
||||
file.ShaderPackage.ShaderValues = file.ShaderPackage.ShaderValues.AddItem( 0.0f, selectedConstant.ByteSize >> 2 );
|
||||
file.ShaderPackage.Constants = file.ShaderPackage.Constants.AddItem( new MtrlFile.Constant
|
||||
{
|
||||
Id = _mtrlTabState.MaterialNewConstantId,
|
||||
ByteOffset = ( ushort )( file.ShaderPackage.ShaderValues.Length << 2 ),
|
||||
ByteSize = selectedConstant.ByteSize,
|
||||
} );
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( !disabled && !hasMalformedConstants && tab.AssociatedShpk != null )
|
||||
{
|
||||
var missingConstants = tab.AssociatedShpk.MaterialParams.Where( constant
|
||||
=> ( constant.ByteOffset & 0x3 ) == 0 && ( constant.ByteSize & 0x3 ) == 0 && !definedConstants.Contains( constant.Id ) ).ToArray();
|
||||
if( missingConstants.Length > 0 )
|
||||
{
|
||||
var selectedConstant = Array.Find( missingConstants, constant => constant.Id == tab.MaterialNewConstantId );
|
||||
if( selectedConstant.ByteSize == 0 )
|
||||
{
|
||||
selectedConstant = missingConstants[ 0 ];
|
||||
tab.MaterialNewConstantId = selectedConstant.Id;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 450.0f );
|
||||
var (selectedConstantName, _) = MaterialParamRangeName( materialParams?.Name ?? "", selectedConstant.ByteOffset >> 2, selectedConstant.ByteSize >> 2 );
|
||||
using( var c = ImRaii.Combo( "##NewConstantId", $"{selectedConstantName} (ID: 0x{selectedConstant.Id:X8})" ) )
|
||||
{
|
||||
if( c )
|
||||
{
|
||||
foreach( var constant in missingConstants )
|
||||
{
|
||||
var (constantName, _) = MaterialParamRangeName( materialParams?.Name ?? "", constant.ByteOffset >> 2, constant.ByteSize >> 2 );
|
||||
if( ImGui.Selectable( $"{constantName} (ID: 0x{constant.Id:X8})", constant.Id == tab.MaterialNewConstantId ) )
|
||||
{
|
||||
selectedConstant = constant;
|
||||
tab.MaterialNewConstantId = constant.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if( ImGui.Button( "Add Constant" ) )
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.ShaderValues = tab.Mtrl.ShaderPackage.ShaderValues.AddItem( 0.0f, selectedConstant.ByteSize >> 2 );
|
||||
tab.Mtrl.ShaderPackage.Constants = tab.Mtrl.ShaderPackage.Constants.AddItem( new MtrlFile.Constant
|
||||
{
|
||||
Id = tab.MaterialNewConstantId,
|
||||
ByteOffset = ( ushort )( tab.Mtrl.ShaderPackage.ShaderValues.Length << 2 ),
|
||||
ByteSize = selectedConstant.ByteSize,
|
||||
} );
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawMaterialSamplers( MtrlFile file, bool disabled )
|
||||
private bool DrawMaterialSamplers( MtrlTab tab, bool disabled )
|
||||
{
|
||||
var ret = false;
|
||||
if( file.ShaderPackage.Samplers.Length > 0
|
||||
|| file.Textures.Length > 0
|
||||
|| !disabled && file.AssociatedShpk != null && file.AssociatedShpk.Samplers.Any( sampler => sampler.Slot == 2 ) )
|
||||
if( tab.Mtrl.ShaderPackage.Samplers.Length > 0
|
||||
|| tab.Mtrl.Textures.Length > 0
|
||||
|| !disabled && tab.AssociatedShpk != null && tab.AssociatedShpk.Samplers.Any( sampler => sampler.Slot == 2 ) )
|
||||
{
|
||||
using var t = ImRaii.TreeNode( "Samplers" );
|
||||
if( t )
|
||||
{
|
||||
var orphanTextures = new IndexSet( file.Textures.Length, true );
|
||||
var orphanTextures = new IndexSet( tab.Mtrl.Textures.Length, true );
|
||||
var aliasedTextureCount = 0;
|
||||
var definedSamplers = new HashSet<uint>();
|
||||
var definedSamplers = new HashSet< uint >();
|
||||
|
||||
foreach( var sampler in file.ShaderPackage.Samplers )
|
||||
foreach( var sampler in tab.Mtrl.ShaderPackage.Samplers )
|
||||
{
|
||||
if( !orphanTextures.Remove( sampler.TextureIndex ) )
|
||||
{
|
||||
|
|
@ -499,13 +408,13 @@ public partial class ModEditWindow
|
|||
definedSamplers.Add( sampler.SamplerId );
|
||||
}
|
||||
|
||||
foreach( var (sampler, idx) in file.ShaderPackage.Samplers.WithIndex() )
|
||||
foreach( var (sampler, idx) in tab.Mtrl.ShaderPackage.Samplers.WithIndex() )
|
||||
{
|
||||
var shpkSampler = file.AssociatedShpk?.GetSamplerById( sampler.SamplerId );
|
||||
using var t2 = ImRaii.TreeNode( $"#{idx}{( shpkSampler.HasValue ? ": " + shpkSampler.Value.Name : "" )} (ID: 0x{sampler.SamplerId:X8})" );
|
||||
var shpkSampler = tab.AssociatedShpk?.GetSamplerById( sampler.SamplerId );
|
||||
using var t2 = ImRaii.TreeNode( $"#{idx}{( shpkSampler.HasValue ? ": " + shpkSampler.Value.Name : "" )} (ID: 0x{sampler.SamplerId:X8})" );
|
||||
if( t2 )
|
||||
{
|
||||
ImRaii.TreeNode( $"Texture: #{sampler.TextureIndex} - {Path.GetFileName( file.Textures[sampler.TextureIndex].Path )}", ImGuiTreeNodeFlags.Leaf )
|
||||
ImRaii.TreeNode( $"Texture: #{sampler.TextureIndex} - {Path.GetFileName( tab.Mtrl.Textures[ sampler.TextureIndex ].Path )}", ImGuiTreeNodeFlags.Leaf )
|
||||
.Dispose();
|
||||
|
||||
// FIXME this probably doesn't belong here
|
||||
|
|
@ -518,7 +427,8 @@ public partial class ModEditWindow
|
|||
}
|
||||
|
||||
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
|
||||
if( InputHexUInt16( "Texture Flags", ref file.Textures[sampler.TextureIndex].Flags, disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) )
|
||||
if( InputHexUInt16( "Texture Flags", ref tab.Mtrl.Textures[ sampler.TextureIndex ].Flags,
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) )
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
|
|
@ -528,22 +438,22 @@ public partial class ModEditWindow
|
|||
if( ImGui.InputInt( "Sampler Flags", ref sampFlags, 0, 0,
|
||||
ImGuiInputTextFlags.CharsHexadecimal | ( disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) ) )
|
||||
{
|
||||
file.ShaderPackage.Samplers[idx].Flags = ( uint )sampFlags;
|
||||
ret = true;
|
||||
tab.Mtrl.ShaderPackage.Samplers[ idx ].Flags = ( uint )sampFlags;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if( !disabled
|
||||
&& orphanTextures.Count == 0
|
||||
&& aliasedTextureCount == 0
|
||||
&& aliasedTextureCount == 0
|
||||
&& ImGui.Button( "Remove Sampler" ) )
|
||||
{
|
||||
file.Textures = file.Textures.RemoveItems( sampler.TextureIndex );
|
||||
file.ShaderPackage.Samplers = file.ShaderPackage.Samplers.RemoveItems( idx );
|
||||
for( var i = 0; i < file.ShaderPackage.Samplers.Length; ++i )
|
||||
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( file.ShaderPackage.Samplers[i].TextureIndex >= sampler.TextureIndex )
|
||||
if( tab.Mtrl.ShaderPackage.Samplers[ i ].TextureIndex >= sampler.TextureIndex )
|
||||
{
|
||||
--file.ShaderPackage.Samplers[i].TextureIndex;
|
||||
--tab.Mtrl.ShaderPackage.Samplers[ i ].TextureIndex;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -559,20 +469,21 @@ public partial class ModEditWindow
|
|||
{
|
||||
foreach( var idx in orphanTextures )
|
||||
{
|
||||
ImRaii.TreeNode( $"#{idx}: {Path.GetFileName( file.Textures[idx].Path )} - {file.Textures[idx].Flags:X4}", ImGuiTreeNodeFlags.Leaf ).Dispose();
|
||||
ImRaii.TreeNode( $"#{idx}: {Path.GetFileName( tab.Mtrl.Textures[ idx ].Path )} - {tab.Mtrl.Textures[ idx ].Flags:X4}", ImGuiTreeNodeFlags.Leaf )
|
||||
.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( !disabled && file.AssociatedShpk != null && aliasedTextureCount == 0 && file.Textures.Length < 255 )
|
||||
else if( !disabled && tab.AssociatedShpk != null && aliasedTextureCount == 0 && tab.Mtrl.Textures.Length < 255 )
|
||||
{
|
||||
var missingSamplers = file.AssociatedShpk.Samplers.Where( sampler => sampler.Slot == 2 && !definedSamplers.Contains( sampler.Id ) ).ToArray();
|
||||
var missingSamplers = tab.AssociatedShpk.Samplers.Where( sampler => sampler.Slot == 2 && !definedSamplers.Contains( sampler.Id ) ).ToArray();
|
||||
if( missingSamplers.Length > 0 )
|
||||
{
|
||||
var selectedSampler = Array.Find( missingSamplers, sampler => sampler.Id == _mtrlTabState.MaterialNewSamplerId );
|
||||
var selectedSampler = Array.Find( missingSamplers, sampler => sampler.Id == tab.MaterialNewSamplerId );
|
||||
if( selectedSampler.Name == null )
|
||||
{
|
||||
selectedSampler = missingSamplers[0];
|
||||
_mtrlTabState.MaterialNewSamplerId = selectedSampler.Id;
|
||||
selectedSampler = missingSamplers[ 0 ];
|
||||
tab.MaterialNewSamplerId = selectedSampler.Id;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 450.0f );
|
||||
|
|
@ -582,10 +493,10 @@ public partial class ModEditWindow
|
|||
{
|
||||
foreach( var sampler in missingSamplers )
|
||||
{
|
||||
if( ImGui.Selectable( $"{sampler.Name} (ID: 0x{sampler.Id:X8})", sampler.Id == _mtrlTabState.MaterialNewSamplerId ) )
|
||||
if( ImGui.Selectable( $"{sampler.Name} (ID: 0x{sampler.Id:X8})", sampler.Id == tab.MaterialNewSamplerId ) )
|
||||
{
|
||||
selectedSampler = sampler;
|
||||
_mtrlTabState.MaterialNewSamplerId = sampler.Id;
|
||||
selectedSampler = sampler;
|
||||
tab.MaterialNewSamplerId = sampler.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -594,16 +505,16 @@ public partial class ModEditWindow
|
|||
ImGui.SameLine();
|
||||
if( ImGui.Button( "Add Sampler" ) )
|
||||
{
|
||||
file.Textures = file.Textures.AddItem( new MtrlFile.Texture
|
||||
tab.Mtrl.Textures = tab.Mtrl.Textures.AddItem( new MtrlFile.Texture
|
||||
{
|
||||
Path = string.Empty,
|
||||
Path = string.Empty,
|
||||
Flags = 0,
|
||||
} );
|
||||
file.ShaderPackage.Samplers = file.ShaderPackage.Samplers.AddItem( new Sampler
|
||||
tab.Mtrl.ShaderPackage.Samplers = tab.Mtrl.ShaderPackage.Samplers.AddItem( new Sampler
|
||||
{
|
||||
SamplerId = _mtrlTabState.MaterialNewSamplerId,
|
||||
TextureIndex = ( byte )file.Textures.Length,
|
||||
Flags = 0,
|
||||
SamplerId = tab.MaterialNewSamplerId,
|
||||
TextureIndex = ( byte )tab.Mtrl.Textures.Length,
|
||||
Flags = 0,
|
||||
} );
|
||||
ret = true;
|
||||
}
|
||||
|
|
@ -615,7 +526,7 @@ public partial class ModEditWindow
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawMaterialShaderResources( MtrlFile file, bool disabled )
|
||||
private bool DrawMaterialShaderResources( MtrlTab tab, bool disabled )
|
||||
{
|
||||
var ret = false;
|
||||
if( !ImGui.CollapsingHeader( "Advanced Shader Resources" ) )
|
||||
|
|
@ -623,13 +534,13 @@ public partial class ModEditWindow
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret |= DrawPackageNameInput( file, disabled );
|
||||
ret |= DrawShaderFlagsInput( file, disabled );
|
||||
DrawCustomAssociations( file );
|
||||
ret |= DrawMaterialShaderKeys( file, disabled );
|
||||
DrawMaterialShaders( file );
|
||||
ret |= DrawMaterialConstants( file, disabled );
|
||||
ret |= DrawMaterialSamplers( file, disabled );
|
||||
ret |= DrawPackageNameInput( tab, disabled );
|
||||
ret |= DrawShaderFlagsInput( tab.Mtrl, disabled );
|
||||
DrawCustomAssociations( tab );
|
||||
ret |= DrawMaterialShaderKeys( tab, disabled );
|
||||
DrawMaterialShaders( tab );
|
||||
ret |= DrawMaterialConstants( tab, disabled );
|
||||
ret |= DrawMaterialSamplers( tab, disabled );
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -638,7 +549,7 @@ public partial class ModEditWindow
|
|||
{
|
||||
if( valueLength == 0 || valueOffset < 0 )
|
||||
{
|
||||
return (null, false);
|
||||
return ( null, false );
|
||||
}
|
||||
|
||||
var firstVector = valueOffset >> 2;
|
||||
|
|
@ -651,18 +562,18 @@ public partial class ModEditWindow
|
|||
|
||||
if( firstVector == lastVector )
|
||||
{
|
||||
return ($"{prefix}[{firstVector}]{VectorSwizzle( firstComponent, lastComponent + 1 - firstComponent )}", true);
|
||||
return ( $"{prefix}[{firstVector}]{VectorSwizzle( firstComponent, lastComponent + 1 - firstComponent )}", true );
|
||||
}
|
||||
|
||||
var parts = new string[lastVector + 1 - firstVector];
|
||||
parts[0] = $"{prefix}[{firstVector}]{VectorSwizzle( firstComponent, 4 - firstComponent )}";
|
||||
parts[^1] = $"[{lastVector}]{VectorSwizzle( 0, lastComponent + 1 )}";
|
||||
parts[ 0 ] = $"{prefix}[{firstVector}]{VectorSwizzle( firstComponent, 4 - firstComponent )}";
|
||||
parts[ ^1 ] = $"[{lastVector}]{VectorSwizzle( 0, lastComponent + 1 )}";
|
||||
for( var i = firstVector + 1; i < lastVector; ++i )
|
||||
{
|
||||
parts[i - firstVector] = $"[{i}]";
|
||||
parts[ i - firstVector ] = $"[{i}]";
|
||||
}
|
||||
|
||||
return (string.Join( ", ", parts ), false);
|
||||
return ( string.Join( ", ", parts ), false );
|
||||
}
|
||||
|
||||
private static string? MaterialParamName( bool componentOnly, int offset )
|
||||
|
|
@ -672,7 +583,7 @@ public partial class ModEditWindow
|
|||
return null;
|
||||
}
|
||||
|
||||
var component = "xyzw"[offset & 0x3];
|
||||
var component = "xyzw"[ offset & 0x3 ];
|
||||
|
||||
return componentOnly ? new string( component, 1 ) : $"[{offset >> 2}].{component}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,152 +1,64 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
namespace Penumbra.UI.Classes;
|
||||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private readonly FileEditor< MtrlFile > _materialTab;
|
||||
private readonly FileEditor< MtrlTab > _materialTab;
|
||||
|
||||
private struct MtrlTabState
|
||||
private bool DrawMaterialPanel( MtrlTab tab, bool disabled )
|
||||
{
|
||||
public uint MaterialNewKeyId = 0;
|
||||
public uint MaterialNewConstantId = 0;
|
||||
public uint MaterialNewSamplerId = 0;
|
||||
|
||||
public readonly List< string > TextureLabels = new(4);
|
||||
public FullPath LoadedShpkPath = FullPath.Empty;
|
||||
public float TextureLabelWidth = 0f;
|
||||
|
||||
public MtrlTabState()
|
||||
{ }
|
||||
}
|
||||
|
||||
private MtrlTabState _mtrlTabState = new();
|
||||
|
||||
|
||||
/// <summary> Load the material with an associated shader package if it can be found. See <seealso cref="FindBestMatch"/>. </summary>
|
||||
private MtrlFile LoadMtrl( byte[] bytes )
|
||||
{
|
||||
var mtrl = new MtrlFile( bytes );
|
||||
LoadAssociatedShpk( mtrl );
|
||||
return mtrl;
|
||||
}
|
||||
|
||||
|
||||
private bool DrawMaterialPanel( MtrlFile file, bool disabled )
|
||||
{
|
||||
var ret = DrawMaterialTextureChange( file, disabled );
|
||||
var ret = DrawMaterialTextureChange( tab, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawBackFaceAndTransparency( file, disabled );
|
||||
ret |= DrawBackFaceAndTransparency( tab.Mtrl, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawMaterialColorSetChange( file, disabled );
|
||||
ret |= DrawMaterialColorSetChange( tab.Mtrl, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawMaterialShaderResources( file, disabled );
|
||||
ret |= DrawMaterialShaderResources( tab, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawOtherMaterialDetails( file, disabled );
|
||||
DrawOtherMaterialDetails( tab.Mtrl, disabled );
|
||||
|
||||
_materialFileDialog.Draw();
|
||||
|
||||
return !disabled && ret;
|
||||
}
|
||||
|
||||
private bool DrawMaterialTextureChange( MtrlFile file, bool disabled )
|
||||
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, _mtrlTabState.TextureLabelWidth * ImGuiHelpers.GlobalScale );
|
||||
for( var i = 0; i < file.Textures.Length; ++i )
|
||||
ImGui.TableSetupColumn( "Name", ImGuiTableColumnFlags.WidthFixed, tab.TextureLabelWidth * ImGuiHelpers.GlobalScale );
|
||||
for( var i = 0; i < tab.Mtrl.Textures.Length; ++i )
|
||||
{
|
||||
using var _ = ImRaii.PushId( i );
|
||||
var tmp = file.Textures[ i ].Path;
|
||||
var tmp = tab.Mtrl.Textures[ i ].Path;
|
||||
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 != file.Textures[ i ].Path )
|
||||
&& tmp != tab.Mtrl.Textures[ i ].Path )
|
||||
{
|
||||
ret = true;
|
||||
file.Textures[ i ].Path = tmp;
|
||||
tab.Mtrl.Textures[ i ].Path = tmp;
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted( _mtrlTabState.TextureLabels[i] );
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialColorSetChange( MtrlFile file, bool disabled )
|
||||
{
|
||||
if( !file.ColorSets.Any( c => c.HasRows ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ColorSetCopyAllClipboardButton( file, 0 );
|
||||
ImGui.SameLine();
|
||||
var ret = ColorSetPasteAllClipboardButton( file, 0 );
|
||||
ImGui.SameLine();
|
||||
ImGui.Dummy( ImGuiHelpers.ScaledVector2( 20, 0 ) );
|
||||
ImGui.SameLine();
|
||||
ret |= DrawPreviewDye( file, disabled );
|
||||
|
||||
using var table = ImRaii.Table( "##ColorSets", 11,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerV );
|
||||
if( !table )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( string.Empty );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Row" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Diffuse" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Specular" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Emissive" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Gloss" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Tile" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Repeat" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Skew" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Dye" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Dye Preview" );
|
||||
|
||||
for( var j = 0; j < file.ColorSets.Length; ++j )
|
||||
{
|
||||
using var _ = ImRaii.PushId( j );
|
||||
for( var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i )
|
||||
{
|
||||
ret |= DrawColorSetRow( file, j, i, disabled );
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
ImGui.TextUnformatted( tab.TextureLabels[ i ] );
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
@ -179,13 +91,11 @@ public partial class ModEditWindow
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawOtherMaterialDetails( MtrlFile file, bool disabled )
|
||||
private static void DrawOtherMaterialDetails( MtrlFile file, bool _ )
|
||||
{
|
||||
var ret = false;
|
||||
|
||||
if( !ImGui.CollapsingHeader( "Further Content" ) )
|
||||
{
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
using( var sets = ImRaii.TreeNode( "UV Sets", ImGuiTreeNodeFlags.DefaultOpen ) )
|
||||
|
|
@ -199,378 +109,16 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
|
||||
if( file.AdditionalData.Length > 0 )
|
||||
{
|
||||
using var t = ImRaii.TreeNode( $"Additional Data (Size: {file.AdditionalData.Length})###AdditionalData" );
|
||||
if( t )
|
||||
{
|
||||
ImGuiUtil.TextWrapped( string.Join( ' ', file.AdditionalData.Select( c => $"{c:X2}" ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void ColorSetCopyAllClipboardButton( MtrlFile file, int colorSetIdx )
|
||||
{
|
||||
if( !ImGui.Button( "Export All Rows to Clipboard", ImGuiHelpers.ScaledVector2( 200, 0 ) ) )
|
||||
if( file.AdditionalData.Length <= 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
using var t = ImRaii.TreeNode( $"Additional Data (Size: {file.AdditionalData.Length})###AdditionalData" );
|
||||
if( t )
|
||||
{
|
||||
var data1 = file.ColorSets[ colorSetIdx ].Rows.AsBytes();
|
||||
var data2 = file.ColorDyeSets.Length > colorSetIdx ? file.ColorDyeSets[ colorSetIdx ].Rows.AsBytes() : ReadOnlySpan< byte >.Empty;
|
||||
var array = new byte[data1.Length + data2.Length];
|
||||
data1.TryCopyTo( array );
|
||||
data2.TryCopyTo( array.AsSpan( data1.Length ) );
|
||||
var text = Convert.ToBase64String( array );
|
||||
ImGui.SetClipboardText( text );
|
||||
ImGuiUtil.TextWrapped( string.Join( ' ', file.AdditionalData.Select( c => $"{c:X2}" ) ) );
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private static bool DrawPreviewDye( MtrlFile file, bool disabled )
|
||||
{
|
||||
var (dyeId, (name, dyeColor, _)) = Penumbra.StainManager.StainCombo.CurrentSelection;
|
||||
var tt = dyeId == 0 ? "Select a preview dye first." : "Apply all preview values corresponding to the dye template and chosen dye where dyeing is enabled.";
|
||||
if( ImGuiUtil.DrawDisabledButton( "Apply Preview Dye", Vector2.Zero, tt, disabled || dyeId == 0 ) )
|
||||
{
|
||||
var ret = false;
|
||||
for( var j = 0; j < file.ColorDyeSets.Length; ++j )
|
||||
{
|
||||
for( var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i )
|
||||
{
|
||||
ret |= file.ApplyDyeTemplate( Penumbra.StainManager.StmFile, j, i, dyeId );
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
var label = dyeId == 0 ? "Preview Dye###previewDye" : $"{name} (Preview)###previewDye";
|
||||
Penumbra.StainManager.StainCombo.Draw( label, dyeColor, string.Empty, true );
|
||||
return false;
|
||||
}
|
||||
|
||||
private static unsafe bool ColorSetPasteAllClipboardButton( MtrlFile file, int colorSetIdx )
|
||||
{
|
||||
if( !ImGui.Button( "Import All Rows from Clipboard", ImGuiHelpers.ScaledVector2( 200, 0 ) ) || file.ColorSets.Length <= colorSetIdx )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var text = ImGui.GetClipboardText();
|
||||
var data = Convert.FromBase64String( text );
|
||||
if( data.Length < Marshal.SizeOf< MtrlFile.ColorSet.RowArray >() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ref var rows = ref file.ColorSets[ colorSetIdx ].Rows;
|
||||
fixed( void* ptr = data, output = &rows )
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( output, ptr, Marshal.SizeOf< MtrlFile.ColorSet.RowArray >() );
|
||||
if( data.Length >= Marshal.SizeOf< MtrlFile.ColorSet.RowArray >() + Marshal.SizeOf< MtrlFile.ColorDyeSet.RowArray >()
|
||||
&& file.ColorDyeSets.Length > colorSetIdx )
|
||||
{
|
||||
ref var dyeRows = ref file.ColorDyeSets[ colorSetIdx ].Rows;
|
||||
fixed( void* output2 = &dyeRows )
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( output2, ( byte* )ptr + Marshal.SizeOf< MtrlFile.ColorSet.RowArray >(), Marshal.SizeOf< MtrlFile.ColorDyeSet.RowArray >() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void ColorSetCopyClipboardButton( MtrlFile.ColorSet.Row row, MtrlFile.ColorDyeSet.Row dye )
|
||||
{
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Clipboard.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Export this row to your clipboard.", false, true ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = new byte[MtrlFile.ColorSet.Row.Size + 2];
|
||||
fixed( byte* ptr = data )
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( ptr, &row, MtrlFile.ColorSet.Row.Size );
|
||||
MemoryUtility.MemCpyUnchecked( ptr + MtrlFile.ColorSet.Row.Size, &dye, 2 );
|
||||
}
|
||||
|
||||
var text = Convert.ToBase64String( data );
|
||||
ImGui.SetClipboardText( text );
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe bool ColorSetPasteFromClipboardButton( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled )
|
||||
{
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Paste.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Import an exported row from your clipboard onto this row.", disabled, true ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = ImGui.GetClipboardText();
|
||||
var data = Convert.FromBase64String( text );
|
||||
if( data.Length != MtrlFile.ColorSet.Row.Size + 2
|
||||
|| file.ColorSets.Length <= colorSetIdx )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fixed( byte* ptr = data )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ] = *( MtrlFile.ColorSet.Row* )ptr;
|
||||
if( colorSetIdx < file.ColorDyeSets.Length )
|
||||
{
|
||||
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ] = *( MtrlFile.ColorDyeSet.Row* )( ptr + MtrlFile.ColorSet.Row.Size );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool DrawColorSetRow( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled )
|
||||
{
|
||||
static bool FixFloat( ref float val, float current )
|
||||
{
|
||||
val = ( float )( Half )val;
|
||||
return val != current;
|
||||
}
|
||||
|
||||
using var id = ImRaii.PushId( rowIdx );
|
||||
var row = file.ColorSets[ colorSetIdx ].Rows[ rowIdx ];
|
||||
var hasDye = file.ColorDyeSets.Length > colorSetIdx;
|
||||
var dye = hasDye ? file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ] : new MtrlFile.ColorDyeSet.Row();
|
||||
var floatSize = 70 * ImGuiHelpers.GlobalScale;
|
||||
var intSize = 45 * ImGuiHelpers.GlobalScale;
|
||||
ImGui.TableNextColumn();
|
||||
ColorSetCopyClipboardButton( row, dye );
|
||||
ImGui.SameLine();
|
||||
var ret = ColorSetPasteFromClipboardButton( file, colorSetIdx, rowIdx, disabled );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( $"#{rowIdx + 1:D2}" );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
using var dis = ImRaii.Disabled( disabled );
|
||||
ret |= ColorPicker( "##Diffuse", "Diffuse Color", row.Diffuse, c => file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Diffuse = c );
|
||||
if( hasDye )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ret |= ImGuiUtil.Checkbox( "##dyeDiffuse", "Apply Diffuse Color on Dye", dye.Diffuse,
|
||||
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Diffuse = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ret |= ColorPicker( "##Specular", "Specular Color", row.Specular, c => file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Specular = c );
|
||||
ImGui.SameLine();
|
||||
var tmpFloat = row.SpecularStrength;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##SpecularStrength", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.SpecularStrength ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].SpecularStrength = tmpFloat;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Specular Strength", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
if( hasDye )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ret |= ImGuiUtil.Checkbox( "##dyeSpecular", "Apply Specular Color on Dye", dye.Specular,
|
||||
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Specular = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
ImGui.SameLine();
|
||||
ret |= ImGuiUtil.Checkbox( "##dyeSpecularStrength", "Apply Specular Strength on Dye", dye.SpecularStrength,
|
||||
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].SpecularStrength = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ret |= ColorPicker( "##Emissive", "Emissive Color", row.Emissive, c => file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Emissive = c );
|
||||
if( hasDye )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ret |= ImGuiUtil.Checkbox( "##dyeEmissive", "Apply Emissive Color on Dye", dye.Emissive,
|
||||
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Emissive = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
tmpFloat = row.GlossStrength;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##GlossStrength", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.GlossStrength ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].GlossStrength = tmpFloat;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Gloss Strength", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
if( hasDye )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ret |= ImGuiUtil.Checkbox( "##dyeGloss", "Apply Gloss Strength on Dye", dye.Gloss,
|
||||
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Gloss = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].TileSet = ( ushort )tmpInt;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Tile Set", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
tmpFloat = row.MaterialRepeat.X;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##RepeatX", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.MaterialRepeat.X ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialRepeat = row.MaterialRepeat with { X = tmpFloat };
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Repeat X", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
ImGui.SameLine();
|
||||
tmpFloat = row.MaterialRepeat.Y;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##RepeatY", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.MaterialRepeat.Y ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialRepeat = row.MaterialRepeat with { Y = tmpFloat };
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Repeat Y", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
tmpFloat = row.MaterialSkew.X;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##SkewX", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.MaterialSkew.X ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialSkew = row.MaterialSkew with { X = tmpFloat };
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Skew X", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
ImGui.SameLine();
|
||||
tmpFloat = row.MaterialSkew.Y;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##SkewY", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.MaterialSkew.Y ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialSkew = row.MaterialSkew with { Y = tmpFloat };
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Skew Y", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if( hasDye )
|
||||
{
|
||||
if( Penumbra.StainManager.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize
|
||||
+ ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton ) )
|
||||
{
|
||||
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = Penumbra.StainManager.TemplateCombo.CurrentSelection;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Dye Template", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ret |= DrawDyePreview( file, colorSetIdx, rowIdx, disabled, dye, floatSize );
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawDyePreview( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled, MtrlFile.ColorDyeSet.Row dye, float floatSize )
|
||||
{
|
||||
var stain = Penumbra.StainManager.StainCombo.CurrentSelection.Key;
|
||||
if( stain == 0 || !Penumbra.StainManager.StmFile.Entries.TryGetValue( dye.Template, out var entry ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var values = entry[ ( int )stain ];
|
||||
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2 );
|
||||
|
||||
var ret = ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.PaintBrush.ToIconString(), new Vector2( ImGui.GetFrameHeight() ),
|
||||
"Apply the selected dye to this row.", disabled, true );
|
||||
|
||||
ret = ret && file.ApplyDyeTemplate( Penumbra.StainManager.StmFile, colorSetIdx, rowIdx, stain );
|
||||
|
||||
ImGui.SameLine();
|
||||
ColorPicker( "##diffusePreview", string.Empty, values.Diffuse, _ => { }, "D" );
|
||||
ImGui.SameLine();
|
||||
ColorPicker( "##specularPreview", string.Empty, values.Specular, _ => { }, "S" );
|
||||
ImGui.SameLine();
|
||||
ColorPicker( "##emissivePreview", string.Empty, values.Emissive, _ => { }, "E" );
|
||||
ImGui.SameLine();
|
||||
using var dis = ImRaii.Disabled();
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
ImGui.DragFloat( "##gloss", ref values.Gloss, 0, 0, 0, "%.2f G" );
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
ImGui.DragFloat( "##specularStrength", ref values.SpecularPower, 0, 0, 0, "%.2f S" );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool ColorPicker( string label, string tooltip, Vector3 input, Action< Vector3 > setter, string letter = "" )
|
||||
{
|
||||
var ret = false;
|
||||
var tmp = input;
|
||||
if( ImGui.ColorEdit3( label, ref tmp,
|
||||
ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.DisplayRGB | ImGuiColorEditFlags.InputRGB | ImGuiColorEditFlags.NoTooltip )
|
||||
&& tmp != input )
|
||||
{
|
||||
setter( tmp );
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if( letter.Length > 0 && ImGui.IsItemVisible() )
|
||||
{
|
||||
var textSize = ImGui.CalcTextSize( letter );
|
||||
var center = ImGui.GetItemRectMin() + ( ImGui.GetItemRectSize() - textSize ) / 2;
|
||||
var textColor = input.LengthSquared() < 0.25f ? 0x80FFFFFFu : 0x80000000u;
|
||||
ImGui.GetWindowDrawList().AddText( center, textColor, letter );
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( tooltip, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void DrawMaterialReassignmentTab()
|
||||
|
|
|
|||
|
|
@ -570,11 +570,11 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
public ModEditWindow()
|
||||
: base( WindowBaseLabel )
|
||||
{
|
||||
_materialTab = new FileEditor< MtrlFile >( "Materials", ".mtrl",
|
||||
_materialTab = new FileEditor< MtrlTab >( "Materials", ".mtrl",
|
||||
() => _editor?.MtrlFiles ?? Array.Empty< Editor.FileRegistry >(),
|
||||
DrawMaterialPanel,
|
||||
() => _mod?.ModPath.FullName ?? string.Empty,
|
||||
LoadMtrl );
|
||||
bytes => new MtrlTab( this, new MtrlFile( bytes ) ) );
|
||||
_modelTab = new FileEditor< MdlFile >( "Models", ".mdl",
|
||||
() => _editor?.MdlFiles ?? Array.Empty< Editor.FileRegistry >(),
|
||||
DrawModelPanel,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue