mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Material editor: live-preview changes
This commit is contained in:
parent
ccca2f1434
commit
f64fdd2b26
14 changed files with 1067 additions and 110 deletions
|
|
@ -65,26 +65,34 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
private ResourceNode CreateNodeFromGamePath(ResourceType type, nint sourceAddress, Utf8GamePath gamePath, bool @internal)
|
||||
=> new(null, type, sourceAddress, gamePath, FilterFullPath(Collection.ResolvePath(gamePath) ?? new FullPath(gamePath)), @internal);
|
||||
|
||||
public static unsafe FullPath GetResourceHandlePath(ResourceHandle* handle)
|
||||
{
|
||||
var name = handle->FileName();
|
||||
if (name.IsEmpty)
|
||||
return FullPath.Empty;
|
||||
|
||||
if (name[0] == (byte)'|')
|
||||
{
|
||||
var pos = name.IndexOf((byte)'|', 1);
|
||||
if (pos < 0)
|
||||
return FullPath.Empty;
|
||||
|
||||
name = name.Substring(pos + 1);
|
||||
}
|
||||
|
||||
return new FullPath(Utf8GamePath.FromByteString(name, out var p) ? p : Utf8GamePath.Empty);
|
||||
}
|
||||
|
||||
private unsafe ResourceNode? CreateNodeFromResourceHandle(ResourceType type, nint sourceAddress, ResourceHandle* handle, bool @internal,
|
||||
bool withName)
|
||||
{
|
||||
if (handle == null)
|
||||
return null;
|
||||
|
||||
var name = handle->FileName();
|
||||
if (name.IsEmpty)
|
||||
var fullPath = GetResourceHandlePath(handle);
|
||||
if (fullPath.InternalName.IsEmpty)
|
||||
return null;
|
||||
|
||||
if (name[0] == (byte)'|')
|
||||
{
|
||||
var pos = name.IndexOf((byte)'|', 1);
|
||||
if (pos < 0)
|
||||
return null;
|
||||
|
||||
name = name.Substring(pos + 1);
|
||||
}
|
||||
|
||||
var fullPath = new FullPath(Utf8GamePath.FromByteString(name, out var p) ? p : Utf8GamePath.Empty);
|
||||
var gamePaths = Collection.ReverseResolvePath(fullPath).ToList();
|
||||
fullPath = FilterFullPath(fullPath);
|
||||
|
||||
|
|
@ -161,7 +169,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (mtrl == null)
|
||||
return null;
|
||||
|
||||
var resource = (MtrlResource*)mtrl->ResourceHandle;
|
||||
var resource = mtrl->ResourceHandle;
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Mtrl, (nint) mtrl, &resource->Handle, false, WithNames);
|
||||
if (node == null)
|
||||
return null;
|
||||
|
|
|
|||
31
Penumbra/Interop/Structs/ConstantBuffer.cs
Normal file
31
Penumbra/Interop/Structs/ConstantBuffer.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Penumbra.Interop.Structs;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x70)]
|
||||
public unsafe struct ConstantBuffer
|
||||
{
|
||||
[FieldOffset(0x20)]
|
||||
public int Size;
|
||||
|
||||
[FieldOffset(0x24)]
|
||||
public int Flags;
|
||||
|
||||
[FieldOffset(0x28)]
|
||||
private void* _maybeSourcePointer;
|
||||
|
||||
public bool TryGetBuffer(out Span<float> buffer)
|
||||
{
|
||||
if ((Flags & 0x4003) == 0 && _maybeSourcePointer != null)
|
||||
{
|
||||
buffer = new Span<float>(_maybeSourcePointer, Size >> 2);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,17 +3,42 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
|||
|
||||
namespace Penumbra.Interop.Structs;
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
[StructLayout( LayoutKind.Explicit, Size = 0x40 )]
|
||||
public unsafe struct Material
|
||||
{
|
||||
[FieldOffset( 0x10 )]
|
||||
public ResourceHandle* ResourceHandle;
|
||||
public MtrlResource* ResourceHandle;
|
||||
|
||||
[FieldOffset( 0x18 )]
|
||||
public uint ShaderPackageFlags;
|
||||
|
||||
[FieldOffset( 0x20 )]
|
||||
public uint* ShaderKeys;
|
||||
|
||||
public int ShaderKeyCount
|
||||
=> (int)((uint*)Textures - ShaderKeys);
|
||||
|
||||
[FieldOffset( 0x28 )]
|
||||
public void* MaterialData;
|
||||
public ConstantBuffer* MaterialParameter;
|
||||
|
||||
[FieldOffset( 0x30 )]
|
||||
public void** Textures;
|
||||
public TextureEntry* Textures;
|
||||
|
||||
public Texture* Texture( int index ) => ( Texture* )Textures[3 * index + 1];
|
||||
[FieldOffset( 0x38 )]
|
||||
public ushort TextureCount;
|
||||
|
||||
public Texture* Texture( int index ) => Textures[index].ResourceHandle->KernelTexture;
|
||||
|
||||
[StructLayout( LayoutKind.Explicit, Size = 0x18 )]
|
||||
public struct TextureEntry
|
||||
{
|
||||
[FieldOffset( 0x00 )]
|
||||
public uint Id;
|
||||
|
||||
[FieldOffset( 0x08 )]
|
||||
public TextureResourceHandle* ResourceHandle;
|
||||
|
||||
[FieldOffset( 0x10 )]
|
||||
public uint SamplerFlags;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,11 @@ public unsafe struct MtrlResource
|
|||
[FieldOffset( 0x00 )]
|
||||
public ResourceHandle Handle;
|
||||
|
||||
[FieldOffset( 0xC8 )]
|
||||
public ShaderPackageResourceHandle* ShpkResourceHandle;
|
||||
|
||||
[FieldOffset( 0xD0 )]
|
||||
public ushort* TexSpace; // Contains the offsets for the tex files inside the string list.
|
||||
public TextureEntry* TexSpace; // Contains the offsets for the tex files inside the string list.
|
||||
|
||||
[FieldOffset( 0xE0 )]
|
||||
public byte* StringList;
|
||||
|
|
@ -24,8 +27,21 @@ public unsafe struct MtrlResource
|
|||
=> StringList + ShpkOffset;
|
||||
|
||||
public byte* TexString( int idx )
|
||||
=> StringList + *( TexSpace + 4 + idx * 8 );
|
||||
=> StringList + TexSpace[idx].PathOffset;
|
||||
|
||||
public bool TexIsDX11( int idx )
|
||||
=> *(TexSpace + 5 + idx * 8) >= 0x8000;
|
||||
=> TexSpace[idx].Flags >= 0x8000;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
|
||||
public struct TextureEntry
|
||||
{
|
||||
[FieldOffset( 0x00 )]
|
||||
public TextureResourceHandle* ResourceHandle;
|
||||
|
||||
[FieldOffset( 0x08 )]
|
||||
public ushort PathOffset;
|
||||
|
||||
[FieldOffset( 0x0A )]
|
||||
public ushort Flags;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -18,12 +20,22 @@ public unsafe struct TextureResourceHandle
|
|||
public IntPtr Unk;
|
||||
|
||||
[FieldOffset( 0x118 )]
|
||||
public IntPtr KernelTexture;
|
||||
public Texture* KernelTexture;
|
||||
|
||||
[FieldOffset( 0x20 )]
|
||||
public IntPtr NewKernelTexture;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct ShaderPackageResourceHandle
|
||||
{
|
||||
[FieldOffset( 0x0 )]
|
||||
public ResourceHandle Handle;
|
||||
|
||||
[FieldOffset( 0xB0 )]
|
||||
public ShaderPackage* ShaderPackage;
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public unsafe struct ResourceHandle
|
||||
{
|
||||
|
|
|
|||
19
Penumbra/Interop/Structs/ShaderPackageUtility.cs
Normal file
19
Penumbra/Interop/Structs/ShaderPackageUtility.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Penumbra.Interop.Structs;
|
||||
|
||||
public static class ShaderPackageUtility
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0xC)]
|
||||
public unsafe struct Sampler
|
||||
{
|
||||
[FieldOffset(0x0)]
|
||||
public uint Crc;
|
||||
|
||||
[FieldOffset(0x4)]
|
||||
public uint Id;
|
||||
|
||||
[FieldOffset(0xA)]
|
||||
public ushort Slot;
|
||||
}
|
||||
}
|
||||
36
Penumbra/Interop/Structs/TextureUtility.cs
Normal file
36
Penumbra/Interop/Structs/TextureUtility.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
|
||||
namespace Penumbra.Interop.Structs;
|
||||
|
||||
public unsafe static class TextureUtility
|
||||
{
|
||||
private static readonly Functions Funcs = new();
|
||||
|
||||
public static Texture* Create2D(Device* device, int* size, byte mipLevel, uint textureFormat, uint flags, uint unk)
|
||||
=> ((delegate* unmanaged<Device*, int*, byte, uint, uint, uint, Texture*>)Funcs.TextureCreate2D)(device, size, mipLevel, textureFormat, flags, unk);
|
||||
|
||||
public static bool InitializeContents(Texture* texture, void* contents)
|
||||
=> ((delegate* unmanaged<Texture*, void*, bool>)Funcs.TextureInitializeContents)(texture, contents);
|
||||
|
||||
public static void IncRef(Texture* texture)
|
||||
=> ((delegate* unmanaged<Texture*, void>)(*(void***)texture)[2])(texture);
|
||||
|
||||
public static void DecRef(Texture* texture)
|
||||
=> ((delegate* unmanaged<Texture*, void>)(*(void***)texture)[3])(texture);
|
||||
|
||||
private sealed class Functions
|
||||
{
|
||||
[Signature("E8 ?? ?? ?? ?? 8B 0F 48 8D 54 24")]
|
||||
public nint TextureCreate2D = nint.Zero;
|
||||
|
||||
[Signature("E9 ?? ?? ?? ?? 8B 02 25")]
|
||||
public nint TextureInitializeContents = nint.Zero;
|
||||
|
||||
public Functions()
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ using Penumbra.UI.Classes;
|
|||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
||||
public class FileEditor<T> where T : class, IWritable
|
||||
public class FileEditor<T> : IDisposable where T : class, IWritable
|
||||
{
|
||||
private readonly FileDialogService _fileDialog;
|
||||
private readonly IDataManager _gameData;
|
||||
|
|
@ -26,7 +26,7 @@ public class FileEditor<T> where T : class, IWritable
|
|||
|
||||
public FileEditor(ModEditWindow owner, IDataManager gameData, Configuration config, FileDialogService fileDialog, string tabName,
|
||||
string fileType, Func<IReadOnlyList<FileRegistry>> getFiles, Func<T, bool, bool> drawEdit, Func<string> getInitialPath,
|
||||
Func<byte[], T?> parseFile)
|
||||
Func<byte[], string, bool, T?> parseFile)
|
||||
{
|
||||
_owner = owner;
|
||||
_gameData = gameData;
|
||||
|
|
@ -39,6 +39,11 @@ public class FileEditor<T> where T : class, IWritable
|
|||
_combo = new Combo(config, getFiles);
|
||||
}
|
||||
|
||||
~FileEditor()
|
||||
{
|
||||
DoDispose();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var tab = ImRaii.TabItem(_tabName);
|
||||
|
|
@ -60,11 +65,23 @@ public class FileEditor<T> where T : class, IWritable
|
|||
DrawFilePanel();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DoDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DoDispose()
|
||||
{
|
||||
(_currentFile as IDisposable)?.Dispose();
|
||||
_currentFile = null;
|
||||
}
|
||||
|
||||
private readonly string _tabName;
|
||||
private readonly string _fileType;
|
||||
private readonly Func<T, bool, bool> _drawEdit;
|
||||
private readonly Func<string> _getInitialPath;
|
||||
private readonly Func<byte[], T?> _parseFile;
|
||||
private readonly Func<byte[], string, bool, T?> _parseFile;
|
||||
|
||||
private FileRegistry? _currentPath;
|
||||
private T? _currentFile;
|
||||
|
|
@ -99,7 +116,9 @@ public class FileEditor<T> where T : class, IWritable
|
|||
if (file != null)
|
||||
{
|
||||
_defaultException = null;
|
||||
_defaultFile = _parseFile(file.Data);
|
||||
(_defaultFile as IDisposable)?.Dispose();
|
||||
_defaultFile = null; // Avoid double disposal if an exception occurs during the parsing of the new file.
|
||||
_defaultFile = _parseFile(file.Data, _defaultPath, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -158,6 +177,7 @@ public class FileEditor<T> where T : class, IWritable
|
|||
{
|
||||
_currentException = null;
|
||||
_currentPath = null;
|
||||
(_currentFile as IDisposable)?.Dispose();
|
||||
_currentFile = null;
|
||||
_changed = false;
|
||||
}
|
||||
|
|
@ -181,10 +201,13 @@ public class FileEditor<T> where T : class, IWritable
|
|||
try
|
||||
{
|
||||
var bytes = File.ReadAllBytes(_currentPath.File.FullName);
|
||||
_currentFile = _parseFile(bytes);
|
||||
(_currentFile as IDisposable)?.Dispose();
|
||||
_currentFile = null; // Avoid double disposal if an exception occurs during the parsing of the new file.
|
||||
_currentFile = _parseFile(bytes, _currentPath.File.FullName, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
(_currentFile as IDisposable)?.Dispose();
|
||||
_currentFile = null;
|
||||
_currentException = e;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,20 +13,20 @@ namespace Penumbra.UI.AdvancedWindow;
|
|||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private bool DrawMaterialColorSetChange( MtrlFile file, bool disabled )
|
||||
private bool DrawMaterialColorSetChange( MtrlTab tab, bool disabled )
|
||||
{
|
||||
if( !file.ColorSets.Any( c => c.HasRows ) )
|
||||
if( !tab.Mtrl.ColorSets.Any( c => c.HasRows ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ColorSetCopyAllClipboardButton( file, 0 );
|
||||
ColorSetCopyAllClipboardButton( tab.Mtrl, 0 );
|
||||
ImGui.SameLine();
|
||||
var ret = ColorSetPasteAllClipboardButton( file, 0 );
|
||||
var ret = ColorSetPasteAllClipboardButton( tab, 0 );
|
||||
ImGui.SameLine();
|
||||
ImGui.Dummy( ImGuiHelpers.ScaledVector2( 20, 0 ) );
|
||||
ImGui.SameLine();
|
||||
ret |= DrawPreviewDye( file, disabled );
|
||||
ret |= DrawPreviewDye( tab, disabled );
|
||||
|
||||
using var table = ImRaii.Table( "##ColorSets", 11,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerV );
|
||||
|
|
@ -58,12 +58,12 @@ public partial class ModEditWindow
|
|||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( "Dye Preview" );
|
||||
|
||||
for( var j = 0; j < file.ColorSets.Length; ++j )
|
||||
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( file, j, i, disabled );
|
||||
ret |= DrawColorSetRow( tab, j, i, disabled );
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
}
|
||||
|
|
@ -95,33 +95,36 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
|
||||
private bool DrawPreviewDye( MtrlFile file, bool disabled )
|
||||
private bool DrawPreviewDye( MtrlTab tab, bool disabled )
|
||||
{
|
||||
var (dyeId, (name, dyeColor, gloss)) = _stainService.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 j = 0; j < tab.Mtrl.ColorDyeSets.Length; ++j )
|
||||
{
|
||||
for( var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i )
|
||||
{
|
||||
ret |= file.ApplyDyeTemplate( _stainService.StmFile, j, i, dyeId );
|
||||
ret |= tab.Mtrl.ApplyDyeTemplate( _stainService.StmFile, j, i, dyeId );
|
||||
}
|
||||
}
|
||||
|
||||
tab.UpdateColorSetPreview();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
var label = dyeId == 0 ? "Preview Dye###previewDye" : $"{name} (Preview)###previewDye";
|
||||
_stainService.StainCombo.Draw( label, dyeColor, string.Empty, true, gloss);
|
||||
if (_stainService.StainCombo.Draw(label, dyeColor, string.Empty, true, gloss))
|
||||
tab.UpdateColorSetPreview();
|
||||
return false;
|
||||
}
|
||||
|
||||
private static unsafe bool ColorSetPasteAllClipboardButton( MtrlFile file, int colorSetIdx )
|
||||
private static unsafe bool ColorSetPasteAllClipboardButton( MtrlTab tab, int colorSetIdx )
|
||||
{
|
||||
if( !ImGui.Button( "Import All Rows from Clipboard", ImGuiHelpers.ScaledVector2( 200, 0 ) ) || file.ColorSets.Length <= colorSetIdx )
|
||||
if( !ImGui.Button( "Import All Rows from Clipboard", ImGuiHelpers.ScaledVector2( 200, 0 ) ) || tab.Mtrl.ColorSets.Length <= colorSetIdx )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -135,14 +138,14 @@ public partial class ModEditWindow
|
|||
return false;
|
||||
}
|
||||
|
||||
ref var rows = ref file.ColorSets[ colorSetIdx ].Rows;
|
||||
ref var rows = ref tab.Mtrl.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 )
|
||||
&& tab.Mtrl.ColorDyeSets.Length > colorSetIdx )
|
||||
{
|
||||
ref var dyeRows = ref file.ColorDyeSets[ colorSetIdx ].Rows;
|
||||
ref var dyeRows = ref tab.Mtrl.ColorDyeSets[ colorSetIdx ].Rows;
|
||||
fixed( void* output2 = &dyeRows )
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( output2, ( byte* )ptr + Marshal.SizeOf< MtrlFile.ColorSet.RowArray >(), Marshal.SizeOf< MtrlFile.ColorDyeSet.RowArray >() );
|
||||
|
|
@ -150,6 +153,8 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
|
||||
tab.UpdateColorSetPreview();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
|
|
@ -182,7 +187,7 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
|
||||
private static unsafe bool ColorSetPasteFromClipboardButton( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled )
|
||||
private static unsafe bool ColorSetPasteFromClipboardButton( MtrlTab tab, 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 ) )
|
||||
|
|
@ -192,20 +197,22 @@ public partial class ModEditWindow
|
|||
var text = ImGui.GetClipboardText();
|
||||
var data = Convert.FromBase64String( text );
|
||||
if( data.Length != MtrlFile.ColorSet.Row.Size + 2
|
||||
|| file.ColorSets.Length <= colorSetIdx )
|
||||
|| tab.Mtrl.ColorSets.Length <= colorSetIdx )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fixed( byte* ptr = data )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ] = *( MtrlFile.ColorSet.Row* )ptr;
|
||||
if( colorSetIdx < file.ColorDyeSets.Length )
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ] = *( MtrlFile.ColorSet.Row* )ptr;
|
||||
if( colorSetIdx < tab.Mtrl.ColorDyeSets.Length )
|
||||
{
|
||||
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ] = *( MtrlFile.ColorDyeSet.Row* )( ptr + MtrlFile.ColorSet.Row.Size );
|
||||
tab.Mtrl.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ] = *( MtrlFile.ColorDyeSet.Row* )( ptr + MtrlFile.ColorSet.Row.Size );
|
||||
}
|
||||
}
|
||||
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
|
|
@ -217,7 +224,18 @@ public partial class ModEditWindow
|
|||
return false;
|
||||
}
|
||||
|
||||
private bool DrawColorSetRow( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled )
|
||||
private static void ColorSetHighlightButton( MtrlTab tab, int rowIdx, bool disabled )
|
||||
{
|
||||
ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Crosshairs.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Highlight this row on your character, if possible.", disabled || tab.ColorSetPreviewers.Count == 0, true );
|
||||
|
||||
if( ImGui.IsItemHovered() )
|
||||
tab.HighlightColorSetRow( rowIdx );
|
||||
else if( tab.HighlightedColorSetRow == rowIdx )
|
||||
tab.CancelColorSetHighlight();
|
||||
}
|
||||
|
||||
private bool DrawColorSetRow( MtrlTab tab, int colorSetIdx, int rowIdx, bool disabled )
|
||||
{
|
||||
static bool FixFloat( ref float val, float current )
|
||||
{
|
||||
|
|
@ -226,38 +244,41 @@ public partial class ModEditWindow
|
|||
}
|
||||
|
||||
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 row = tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ];
|
||||
var hasDye = 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;
|
||||
ImGui.TableNextColumn();
|
||||
ColorSetCopyClipboardButton( row, dye );
|
||||
ImGui.SameLine();
|
||||
var ret = ColorSetPasteFromClipboardButton( file, colorSetIdx, rowIdx, disabled );
|
||||
var ret = ColorSetPasteFromClipboardButton( tab, colorSetIdx, rowIdx, disabled );
|
||||
ImGui.SameLine();
|
||||
ColorSetHighlightButton( tab, 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 );
|
||||
ret |= ColorPicker( "##Diffuse", "Diffuse Color", row.Diffuse, c => { tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Diffuse = c; tab.UpdateColorSetRowPreview(rowIdx); } );
|
||||
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 );
|
||||
b => { tab.Mtrl.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Diffuse = b; tab.UpdateColorSetRowPreview(rowIdx); }, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ret |= ColorPicker( "##Specular", "Specular Color", row.Specular, c => file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Specular = c );
|
||||
ret |= ColorPicker( "##Specular", "Specular Color", row.Specular, c => { tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Specular = c; tab.UpdateColorSetRowPreview(rowIdx); } );
|
||||
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;
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].SpecularStrength = tmpFloat;
|
||||
ret = true;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Specular Strength", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
|
@ -266,19 +287,19 @@ public partial class ModEditWindow
|
|||
{
|
||||
ImGui.SameLine();
|
||||
ret |= ImGuiUtil.Checkbox( "##dyeSpecular", "Apply Specular Color on Dye", dye.Specular,
|
||||
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Specular = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
b => { tab.Mtrl.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Specular = b; tab.UpdateColorSetRowPreview(rowIdx); }, 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 );
|
||||
b => { tab.Mtrl.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].SpecularStrength = b; tab.UpdateColorSetRowPreview(rowIdx); }, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ret |= ColorPicker( "##Emissive", "Emissive Color", row.Emissive, c => file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Emissive = c );
|
||||
ret |= ColorPicker( "##Emissive", "Emissive Color", row.Emissive, c => { tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Emissive = c; tab.UpdateColorSetRowPreview(rowIdx); } );
|
||||
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 );
|
||||
b => { tab.Mtrl.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Emissive = b; tab.UpdateColorSetRowPreview(rowIdx); }, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
|
@ -286,8 +307,9 @@ public partial class ModEditWindow
|
|||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##GlossStrength", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.GlossStrength ) )
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].GlossStrength = tmpFloat;
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].GlossStrength = tmpFloat;
|
||||
ret = true;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Gloss Strength", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
|
@ -295,7 +317,7 @@ public partial class ModEditWindow
|
|||
{
|
||||
ImGui.SameLine();
|
||||
ret |= ImGuiUtil.Checkbox( "##dyeGloss", "Apply Gloss Strength on Dye", dye.Gloss,
|
||||
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Gloss = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
b => { tab.Mtrl.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Gloss = b; tab.UpdateColorSetRowPreview(rowIdx); }, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
|
@ -303,8 +325,9 @@ public partial class ModEditWindow
|
|||
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;
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].TileSet = ( ushort )tmpInt;
|
||||
ret = true;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Tile Set", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
|
@ -314,8 +337,9 @@ public partial class ModEditWindow
|
|||
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 };
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialRepeat = row.MaterialRepeat with { X = tmpFloat };
|
||||
ret = true;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Repeat X", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
|
@ -324,8 +348,9 @@ public partial class ModEditWindow
|
|||
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 };
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialRepeat = row.MaterialRepeat with { Y = tmpFloat };
|
||||
ret = true;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Repeat Y", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
|
@ -335,8 +360,9 @@ public partial class ModEditWindow
|
|||
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 };
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialSkew = row.MaterialSkew with { X = tmpFloat };
|
||||
ret = true;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Skew X", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
|
@ -346,8 +372,9 @@ public partial class ModEditWindow
|
|||
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 };
|
||||
tab.Mtrl.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialSkew = row.MaterialSkew with { Y = tmpFloat };
|
||||
ret = true;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Skew Y", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
|
@ -358,14 +385,15 @@ public partial class ModEditWindow
|
|||
if(_stainService.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize
|
||||
+ ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton ) )
|
||||
{
|
||||
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = _stainService.TemplateCombo.CurrentSelection;
|
||||
tab.Mtrl.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = _stainService.TemplateCombo.CurrentSelection;
|
||||
ret = true;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Dye Template", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ret |= DrawDyePreview( file, colorSetIdx, rowIdx, disabled, dye, floatSize );
|
||||
ret |= DrawDyePreview( tab, colorSetIdx, rowIdx, disabled, dye, floatSize );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -376,7 +404,7 @@ public partial class ModEditWindow
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawDyePreview( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled, MtrlFile.ColorDyeSet.Row dye, float floatSize )
|
||||
private bool DrawDyePreview( MtrlTab tab, int colorSetIdx, int rowIdx, bool disabled, MtrlFile.ColorDyeSet.Row dye, float floatSize )
|
||||
{
|
||||
var stain = _stainService.StainCombo.CurrentSelection.Key;
|
||||
if( stain == 0 || !_stainService.StmFile.Entries.TryGetValue( dye.Template, out var entry ) )
|
||||
|
|
@ -390,7 +418,9 @@ public partial class ModEditWindow
|
|||
var ret = ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.PaintBrush.ToIconString(), new Vector2( ImGui.GetFrameHeight() ),
|
||||
"Apply the selected dye to this row.", disabled, true );
|
||||
|
||||
ret = ret && file.ApplyDyeTemplate(_stainService.StmFile, colorSetIdx, rowIdx, stain );
|
||||
ret = ret && tab.Mtrl.ApplyDyeTemplate(_stainService.StmFile, colorSetIdx, rowIdx, stain );
|
||||
if (ret)
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
|
||||
ImGui.SameLine();
|
||||
ColorPicker( "##diffusePreview", string.Empty, values.Diffuse, _ => { }, "D" );
|
||||
|
|
|
|||
|
|
@ -0,0 +1,484 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Interop.ResourceTree;
|
||||
using Structs = Penumbra.Interop.Structs;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private static unsafe Character* FindLocalPlayer(IObjectTable objects)
|
||||
{
|
||||
var localPlayer = objects[0];
|
||||
if (localPlayer is not Dalamud.Game.ClientState.Objects.Types.Character)
|
||||
return null;
|
||||
|
||||
return (Character*)localPlayer.Address;
|
||||
}
|
||||
|
||||
private static unsafe Character* FindSubActor(Character* character, int subActorType)
|
||||
{
|
||||
if (character == null)
|
||||
return null;
|
||||
|
||||
switch (subActorType)
|
||||
{
|
||||
case -1:
|
||||
return character;
|
||||
case 0:
|
||||
return character->Mount.MountObject;
|
||||
case 1:
|
||||
var companion = character->Companion.CompanionObject;
|
||||
if (companion == null)
|
||||
return null;
|
||||
return &companion->Character;
|
||||
case 2:
|
||||
var ornament = character->Ornament.OrnamentObject;
|
||||
if (ornament == null)
|
||||
return null;
|
||||
return &ornament->Character;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe List<(int SubActorType, int ChildObjectIndex, int ModelSlot, int MaterialSlot)> FindMaterial(CharacterBase* drawObject, int subActorType, string materialPath)
|
||||
{
|
||||
static void CollectMaterials(List<(int, int, int, int)> result, int subActorType, int childObjectIndex, CharacterBase* drawObject, string materialPath)
|
||||
{
|
||||
for (var i = 0; i < drawObject->SlotCount; ++i)
|
||||
{
|
||||
var model = drawObject->Models[i];
|
||||
if (model == null)
|
||||
continue;
|
||||
|
||||
for (var j = 0; j < model->MaterialCount; ++j)
|
||||
{
|
||||
var material = model->Materials[j];
|
||||
if (material == null)
|
||||
continue;
|
||||
|
||||
var mtrlHandle = material->MaterialResourceHandle;
|
||||
if (mtrlHandle == null)
|
||||
continue;
|
||||
|
||||
var path = ResolveContext.GetResourceHandlePath((Structs.ResourceHandle*)mtrlHandle);
|
||||
if (path.ToString() == materialPath)
|
||||
result.Add((subActorType, childObjectIndex, i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result = new List<(int, int, int, int)>();
|
||||
|
||||
if (drawObject == null)
|
||||
return result;
|
||||
|
||||
materialPath = materialPath.Replace('/', '\\').ToLowerInvariant();
|
||||
|
||||
CollectMaterials(result, subActorType, -1, drawObject, materialPath);
|
||||
|
||||
var firstChildObject = (CharacterBase*)drawObject->DrawObject.Object.ChildObject;
|
||||
if (firstChildObject != null)
|
||||
{
|
||||
var childObject = firstChildObject;
|
||||
var childObjectIndex = 0;
|
||||
do
|
||||
{
|
||||
CollectMaterials(result, subActorType, childObjectIndex, childObject, materialPath);
|
||||
|
||||
childObject = (CharacterBase*)childObject->DrawObject.Object.NextSiblingObject;
|
||||
++childObjectIndex;
|
||||
}
|
||||
while (childObject != null && childObject != firstChildObject);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static unsafe CharacterBase* GetChildObject(CharacterBase* drawObject, int index)
|
||||
{
|
||||
if (drawObject == null)
|
||||
return null;
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
drawObject = (CharacterBase*)drawObject->DrawObject.Object.ChildObject;
|
||||
if (drawObject == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
var first = drawObject;
|
||||
while (index-- > 0)
|
||||
{
|
||||
drawObject = (CharacterBase*)drawObject->DrawObject.Object.NextSiblingObject;
|
||||
if (drawObject == null || drawObject == first)
|
||||
return null;
|
||||
}
|
||||
|
||||
return drawObject;
|
||||
}
|
||||
|
||||
private static unsafe Material* GetDrawObjectMaterial(CharacterBase* drawObject, int modelSlot, int materialSlot)
|
||||
{
|
||||
if (drawObject == null)
|
||||
return null;
|
||||
|
||||
if (modelSlot < 0 || modelSlot >= drawObject->SlotCount)
|
||||
return null;
|
||||
|
||||
var model = drawObject->Models[modelSlot];
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
if (materialSlot < 0 || materialSlot >= model->MaterialCount)
|
||||
return null;
|
||||
|
||||
return model->Materials[materialSlot];
|
||||
}
|
||||
|
||||
private abstract unsafe class LiveMaterialPreviewerBase : IDisposable
|
||||
{
|
||||
private readonly IObjectTable _objects;
|
||||
|
||||
protected readonly int SubActorType;
|
||||
protected readonly int ChildObjectIndex;
|
||||
protected readonly int ModelSlot;
|
||||
protected readonly int MaterialSlot;
|
||||
|
||||
protected readonly CharacterBase* DrawObject;
|
||||
protected readonly Material* Material;
|
||||
|
||||
protected bool Valid;
|
||||
|
||||
public LiveMaterialPreviewerBase(IObjectTable objects, int subActorType, int childObjectIndex, int modelSlot, int materialSlot)
|
||||
{
|
||||
_objects = objects;
|
||||
|
||||
SubActorType = subActorType;
|
||||
ChildObjectIndex = childObjectIndex;
|
||||
ModelSlot = modelSlot;
|
||||
MaterialSlot = materialSlot;
|
||||
|
||||
var localPlayer = FindLocalPlayer(objects);
|
||||
if (localPlayer == null)
|
||||
throw new InvalidOperationException("Cannot retrieve local player object");
|
||||
|
||||
var subActor = FindSubActor(localPlayer, subActorType);
|
||||
if (subActor == null)
|
||||
throw new InvalidOperationException("Cannot retrieve sub-actor (mount, companion or ornament)");
|
||||
|
||||
DrawObject = GetChildObject((CharacterBase*)subActor->GameObject.GetDrawObject(), childObjectIndex);
|
||||
if (DrawObject == null)
|
||||
throw new InvalidOperationException("Cannot retrieve draw object");
|
||||
|
||||
Material = GetDrawObjectMaterial(DrawObject, modelSlot, materialSlot);
|
||||
if (Material == null)
|
||||
throw new InvalidOperationException("Cannot retrieve material");
|
||||
|
||||
Valid = true;
|
||||
}
|
||||
|
||||
~LiveMaterialPreviewerBase()
|
||||
{
|
||||
if (Valid)
|
||||
Dispose(false, IsStillValid());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Valid)
|
||||
Dispose(true, IsStillValid());
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing, bool reset)
|
||||
{
|
||||
Valid = false;
|
||||
}
|
||||
|
||||
public bool CheckValidity()
|
||||
{
|
||||
if (Valid && !IsStillValid())
|
||||
Dispose(false, false);
|
||||
|
||||
return Valid;
|
||||
}
|
||||
|
||||
protected virtual bool IsStillValid()
|
||||
{
|
||||
var localPlayer = FindLocalPlayer(_objects);
|
||||
if (localPlayer == null)
|
||||
return false;
|
||||
|
||||
var subActor = FindSubActor(localPlayer, SubActorType);
|
||||
if (subActor == null)
|
||||
return false;
|
||||
|
||||
if (DrawObject != GetChildObject((CharacterBase*)subActor->GameObject.GetDrawObject(), ChildObjectIndex))
|
||||
return false;
|
||||
|
||||
if (Material != GetDrawObjectMaterial(DrawObject, ModelSlot, MaterialSlot))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed unsafe class LiveMaterialPreviewer : LiveMaterialPreviewerBase
|
||||
{
|
||||
private readonly ShaderPackage* _shaderPackage;
|
||||
|
||||
private readonly uint _originalShPkFlags;
|
||||
private readonly float[] _originalMaterialParameter;
|
||||
private readonly uint[] _originalSamplerFlags;
|
||||
|
||||
public LiveMaterialPreviewer(IObjectTable objects, int subActorType, int childObjectIndex, int modelSlot, int materialSlot) : base(objects, subActorType, childObjectIndex, modelSlot, materialSlot)
|
||||
{
|
||||
var mtrlHandle = Material->MaterialResourceHandle;
|
||||
if (mtrlHandle == null)
|
||||
throw new InvalidOperationException("Material doesn't have a resource handle");
|
||||
|
||||
var shpkHandle = ((Structs.MtrlResource*)mtrlHandle)->ShpkResourceHandle;
|
||||
if (shpkHandle == null)
|
||||
throw new InvalidOperationException("Material doesn't have a ShPk resource handle");
|
||||
|
||||
_shaderPackage = shpkHandle->ShaderPackage;
|
||||
if (_shaderPackage == null)
|
||||
throw new InvalidOperationException("Material doesn't have a shader package");
|
||||
|
||||
var material = (Structs.Material*)Material;
|
||||
|
||||
_originalShPkFlags = material->ShaderPackageFlags;
|
||||
|
||||
if (material->MaterialParameter->TryGetBuffer(out var materialParameter))
|
||||
_originalMaterialParameter = materialParameter.ToArray();
|
||||
else
|
||||
_originalMaterialParameter = Array.Empty<float>();
|
||||
|
||||
_originalSamplerFlags = new uint[material->TextureCount];
|
||||
for (var i = 0; i < _originalSamplerFlags.Length; ++i)
|
||||
_originalSamplerFlags[i] = material->Textures[i].SamplerFlags;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing, bool reset)
|
||||
{
|
||||
base.Dispose(disposing, reset);
|
||||
|
||||
if (reset)
|
||||
{
|
||||
var material = (Structs.Material*)Material;
|
||||
|
||||
material->ShaderPackageFlags = _originalShPkFlags;
|
||||
|
||||
if (material->MaterialParameter->TryGetBuffer(out var materialParameter))
|
||||
_originalMaterialParameter.AsSpan().CopyTo(materialParameter);
|
||||
|
||||
for (var i = 0; i < _originalSamplerFlags.Length; ++i)
|
||||
material->Textures[i].SamplerFlags = _originalSamplerFlags[i];
|
||||
}
|
||||
}
|
||||
|
||||
public void SetShaderPackageFlags(uint shPkFlags)
|
||||
{
|
||||
if (!CheckValidity())
|
||||
return;
|
||||
|
||||
((Structs.Material*)Material)->ShaderPackageFlags = shPkFlags;
|
||||
}
|
||||
|
||||
public void SetMaterialParameter(uint parameterCrc, Index offset, Span<float> value)
|
||||
{
|
||||
if (!CheckValidity())
|
||||
return;
|
||||
|
||||
var cbuffer = ((Structs.Material*)Material)->MaterialParameter;
|
||||
if (cbuffer == null)
|
||||
return;
|
||||
|
||||
if (!cbuffer->TryGetBuffer(out var buffer))
|
||||
return;
|
||||
|
||||
for (var i = 0; i < _shaderPackage->MaterialElementCount; ++i)
|
||||
{
|
||||
ref var parameter = ref _shaderPackage->MaterialElements[i];
|
||||
if (parameter.CRC == parameterCrc)
|
||||
{
|
||||
if ((parameter.Offset & 0x3) != 0 || (parameter.Size & 0x3) != 0 || (parameter.Offset + parameter.Size) >> 2 > buffer.Length)
|
||||
return;
|
||||
|
||||
value.TryCopyTo(buffer.Slice(parameter.Offset >> 2, parameter.Size >> 2)[offset..]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSamplerFlags(uint samplerCrc, uint samplerFlags)
|
||||
{
|
||||
if (!CheckValidity())
|
||||
return;
|
||||
|
||||
var id = 0u;
|
||||
var found = false;
|
||||
|
||||
var samplers = (Structs.ShaderPackageUtility.Sampler*)_shaderPackage->Samplers;
|
||||
for (var i = 0; i < _shaderPackage->SamplerCount; ++i)
|
||||
{
|
||||
if (samplers[i].Crc == samplerCrc)
|
||||
{
|
||||
id = samplers[i].Id;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
var material = (Structs.Material*)Material;
|
||||
for (var i = 0; i < material->TextureCount; ++i)
|
||||
{
|
||||
if (material->Textures[i].Id == id)
|
||||
{
|
||||
material->Textures[i].SamplerFlags = (samplerFlags & 0xFFFFFDFF) | 0x000001C0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool IsStillValid()
|
||||
{
|
||||
if (!base.IsStillValid())
|
||||
return false;
|
||||
|
||||
var mtrlHandle = Material->MaterialResourceHandle;
|
||||
if (mtrlHandle == null)
|
||||
return false;
|
||||
|
||||
var shpkHandle = ((Structs.MtrlResource*)mtrlHandle)->ShpkResourceHandle;
|
||||
if (shpkHandle == null)
|
||||
return false;
|
||||
|
||||
if (_shaderPackage != shpkHandle->ShaderPackage)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed unsafe class LiveColorSetPreviewer : LiveMaterialPreviewerBase
|
||||
{
|
||||
public const int TextureWidth = 4;
|
||||
public const int TextureHeight = MtrlFile.ColorSet.RowArray.NumRows;
|
||||
public const int TextureLength = TextureWidth * TextureHeight * 4;
|
||||
|
||||
private readonly Framework _framework;
|
||||
|
||||
private readonly Texture** _colorSetTexture;
|
||||
private readonly Texture* _originalColorSetTexture;
|
||||
|
||||
private Half[] _colorSet;
|
||||
private bool _updatePending;
|
||||
|
||||
public Half[] ColorSet => _colorSet;
|
||||
|
||||
public LiveColorSetPreviewer(IObjectTable objects, Framework framework, int subActorType, int childObjectIndex, int modelSlot, int materialSlot) : base(objects, subActorType, childObjectIndex, modelSlot, materialSlot)
|
||||
{
|
||||
_framework = framework;
|
||||
|
||||
var mtrlHandle = Material->MaterialResourceHandle;
|
||||
if (mtrlHandle == null)
|
||||
throw new InvalidOperationException("Material doesn't have a resource handle");
|
||||
|
||||
var colorSetTextures = *(Texture***)((nint)DrawObject + 0x258);
|
||||
if (colorSetTextures == null)
|
||||
throw new InvalidOperationException("Draw object doesn't have color set textures");
|
||||
|
||||
_colorSetTexture = colorSetTextures + (modelSlot * 4 + materialSlot);
|
||||
|
||||
_originalColorSetTexture = *_colorSetTexture;
|
||||
if (_originalColorSetTexture == null)
|
||||
throw new InvalidOperationException("Material doesn't have a color set");
|
||||
Structs.TextureUtility.IncRef(_originalColorSetTexture);
|
||||
|
||||
_colorSet = new Half[TextureLength];
|
||||
_updatePending = true;
|
||||
|
||||
framework.Update += OnFrameworkUpdate;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing, bool reset)
|
||||
{
|
||||
_framework.Update -= OnFrameworkUpdate;
|
||||
|
||||
base.Dispose(disposing, reset);
|
||||
|
||||
if (reset)
|
||||
{
|
||||
var oldTexture = (Texture*)Interlocked.Exchange(ref *(nint*)_colorSetTexture, (nint)_originalColorSetTexture);
|
||||
Structs.TextureUtility.DecRef(oldTexture);
|
||||
}
|
||||
else
|
||||
Structs.TextureUtility.DecRef(_originalColorSetTexture);
|
||||
}
|
||||
|
||||
public void ScheduleUpdate()
|
||||
{
|
||||
_updatePending = true;
|
||||
}
|
||||
|
||||
private void OnFrameworkUpdate(Framework _)
|
||||
{
|
||||
if (!_updatePending)
|
||||
return;
|
||||
_updatePending = false;
|
||||
|
||||
if (!CheckValidity())
|
||||
return;
|
||||
|
||||
var textureSize = stackalloc int[2];
|
||||
textureSize[0] = TextureWidth;
|
||||
textureSize[1] = TextureHeight;
|
||||
|
||||
var newTexture = Structs.TextureUtility.Create2D(Device.Instance(), textureSize, 1, 0x2460, 0x80000804, 7);
|
||||
if (newTexture == null)
|
||||
return;
|
||||
|
||||
bool success;
|
||||
lock (_colorSet)
|
||||
fixed (Half* colorSet = _colorSet)
|
||||
success = Structs.TextureUtility.InitializeContents(newTexture, colorSet);
|
||||
|
||||
if (success)
|
||||
{
|
||||
var oldTexture = (Texture*)Interlocked.Exchange(ref *(nint*)_colorSetTexture, (nint)newTexture);
|
||||
Structs.TextureUtility.DecRef(oldTexture);
|
||||
}
|
||||
else
|
||||
Structs.TextureUtility.DecRef(newTexture);
|
||||
}
|
||||
|
||||
protected override bool IsStillValid()
|
||||
{
|
||||
if (!base.IsStillValid())
|
||||
return false;
|
||||
|
||||
var colorSetTextures = *(Texture***)((nint)DrawObject + 0x258);
|
||||
if (colorSetTextures == null)
|
||||
return false;
|
||||
|
||||
if (_colorSetTexture != colorSetTextures + (ModelSlot * 4 + MaterialSlot))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,14 +2,19 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
|
|
@ -19,10 +24,12 @@ namespace Penumbra.UI.AdvancedWindow;
|
|||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private sealed class MtrlTab : IWritable
|
||||
private sealed class MtrlTab : IWritable, IDisposable
|
||||
{
|
||||
private readonly ModEditWindow _edit;
|
||||
public readonly MtrlFile Mtrl;
|
||||
public readonly string FilePath;
|
||||
public readonly bool Writable;
|
||||
|
||||
public uint NewKeyId;
|
||||
public uint NewKeyDefault;
|
||||
|
|
@ -57,12 +64,18 @@ public partial class ModEditWindow
|
|||
public bool HasMalformedMaterialConstants;
|
||||
|
||||
// Samplers
|
||||
public readonly List< (string Label, string FileName) > Samplers = new(4);
|
||||
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;
|
||||
|
||||
// Live-Previewers
|
||||
public readonly List<LiveMaterialPreviewer> MaterialPreviewers = new(4);
|
||||
public readonly List<LiveColorSetPreviewer> ColorSetPreviewers = new(4);
|
||||
public int HighlightedColorSetRow = -1;
|
||||
public int HighlightTime = -1;
|
||||
|
||||
public FullPath FindAssociatedShpk( out string defaultPath, out Utf8GamePath defaultGamePath )
|
||||
{
|
||||
defaultPath = GamePaths.Shader.ShpkPath( Mtrl.ShaderPackage.Name );
|
||||
|
|
@ -243,7 +256,7 @@ public partial class ModEditWindow
|
|||
? $"#{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 ) );
|
||||
Samplers.Add( ( label, fileName, sampler.SamplerId ) );
|
||||
}
|
||||
|
||||
MissingSamplers.Clear();
|
||||
|
|
@ -269,6 +282,220 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
|
||||
public unsafe void BindToMaterialInstances()
|
||||
{
|
||||
UnbindFromMaterialInstances();
|
||||
|
||||
var localPlayer = FindLocalPlayer(_edit._dalamud.Objects);
|
||||
if (null == localPlayer)
|
||||
return;
|
||||
|
||||
var drawObject = (CharacterBase*)localPlayer->GameObject.GetDrawObject();
|
||||
if (null == drawObject)
|
||||
return;
|
||||
|
||||
var instances = FindMaterial(drawObject, -1, FilePath);
|
||||
|
||||
var drawObjects = stackalloc CharacterBase*[4];
|
||||
drawObjects[0] = drawObject;
|
||||
|
||||
for (var i = 0; i < 3; ++i)
|
||||
{
|
||||
var subActor = FindSubActor(localPlayer, i);
|
||||
if (null == subActor)
|
||||
continue;
|
||||
|
||||
var subDrawObject = (CharacterBase*)subActor->GameObject.GetDrawObject();
|
||||
if (null == subDrawObject)
|
||||
continue;
|
||||
|
||||
instances.AddRange(FindMaterial(subDrawObject, i, FilePath));
|
||||
drawObjects[i + 1] = subDrawObject;
|
||||
}
|
||||
|
||||
var foundMaterials = new HashSet<nint>();
|
||||
foreach (var (subActorType, childObjectIndex, modelSlot, materialSlot) in instances)
|
||||
{
|
||||
var material = GetDrawObjectMaterial(drawObjects[subActorType + 1], modelSlot, materialSlot);
|
||||
if (foundMaterials.Contains((nint)material))
|
||||
continue;
|
||||
try
|
||||
{
|
||||
MaterialPreviewers.Add(new LiveMaterialPreviewer(_edit._dalamud.Objects, subActorType, childObjectIndex, modelSlot, materialSlot));
|
||||
foundMaterials.Add((nint)material);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Carry on without that previewer.
|
||||
}
|
||||
}
|
||||
|
||||
var colorSet = Mtrl.ColorSets.FirstOrNull(colorSet => colorSet.HasRows);
|
||||
|
||||
if (colorSet.HasValue)
|
||||
{
|
||||
foreach (var (subActorType, childObjectIndex, modelSlot, materialSlot) in instances)
|
||||
{
|
||||
try
|
||||
{
|
||||
ColorSetPreviewers.Add(new LiveColorSetPreviewer(_edit._dalamud.Objects, _edit._dalamud.Framework, subActorType, childObjectIndex, modelSlot, materialSlot));
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Carry on without that previewer.
|
||||
}
|
||||
}
|
||||
UpdateColorSetPreview();
|
||||
}
|
||||
}
|
||||
|
||||
public void UnbindFromMaterialInstances()
|
||||
{
|
||||
foreach (var previewer in MaterialPreviewers)
|
||||
previewer.Dispose();
|
||||
MaterialPreviewers.Clear();
|
||||
|
||||
foreach (var previewer in ColorSetPreviewers)
|
||||
previewer.Dispose();
|
||||
ColorSetPreviewers.Clear();
|
||||
}
|
||||
|
||||
public void SetShaderPackageFlags(uint shPkFlags)
|
||||
{
|
||||
foreach (var previewer in MaterialPreviewers)
|
||||
previewer.SetShaderPackageFlags(shPkFlags);
|
||||
}
|
||||
|
||||
public void SetMaterialParameter(uint parameterCrc, Index offset, Span<float> value)
|
||||
{
|
||||
foreach (var previewer in MaterialPreviewers)
|
||||
previewer.SetMaterialParameter(parameterCrc, offset, value);
|
||||
}
|
||||
|
||||
public void SetSamplerFlags(uint samplerCrc, uint samplerFlags)
|
||||
{
|
||||
foreach (var previewer in MaterialPreviewers)
|
||||
previewer.SetSamplerFlags(samplerCrc, samplerFlags);
|
||||
}
|
||||
|
||||
public void HighlightColorSetRow(int rowIdx)
|
||||
{
|
||||
var oldRowIdx = HighlightedColorSetRow;
|
||||
|
||||
HighlightedColorSetRow = rowIdx;
|
||||
HighlightTime = (HighlightTime + 1) % 32;
|
||||
|
||||
if (oldRowIdx >= 0)
|
||||
UpdateColorSetRowPreview(oldRowIdx);
|
||||
if (rowIdx >= 0)
|
||||
UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
public void CancelColorSetHighlight()
|
||||
{
|
||||
var rowIdx = HighlightedColorSetRow;
|
||||
|
||||
HighlightedColorSetRow = -1;
|
||||
HighlightTime = -1;
|
||||
|
||||
if (rowIdx >= 0)
|
||||
UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
public unsafe void UpdateColorSetRowPreview(int rowIdx)
|
||||
{
|
||||
if (ColorSetPreviewers.Count == 0)
|
||||
return;
|
||||
|
||||
var maybeColorSet = Mtrl.ColorSets.FirstOrNull(colorSet => colorSet.HasRows);
|
||||
if (!maybeColorSet.HasValue)
|
||||
return;
|
||||
|
||||
var colorSet = maybeColorSet.Value;
|
||||
var maybeColorDyeSet = Mtrl.ColorDyeSets.FirstOrNull(colorDyeSet => colorDyeSet.Index == colorSet.Index);
|
||||
|
||||
var row = colorSet.Rows[rowIdx];
|
||||
if (maybeColorDyeSet.HasValue)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
if (HighlightedColorSetRow == rowIdx)
|
||||
ApplyHighlight(ref row, HighlightTime);
|
||||
|
||||
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));
|
||||
previewer.ScheduleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void UpdateColorSetPreview()
|
||||
{
|
||||
if (ColorSetPreviewers.Count == 0)
|
||||
return;
|
||||
|
||||
var maybeColorSet = Mtrl.ColorSets.FirstOrNull(colorSet => colorSet.HasRows);
|
||||
if (!maybeColorSet.HasValue)
|
||||
return;
|
||||
|
||||
var colorSet = maybeColorSet.Value;
|
||||
var maybeColorDyeSet = Mtrl.ColorDyeSets.FirstOrNull(colorDyeSet => colorDyeSet.Index == colorSet.Index);
|
||||
|
||||
var rows = colorSet.Rows;
|
||||
if (maybeColorDyeSet.HasValue)
|
||||
{
|
||||
var stm = _edit._stainService.StmFile;
|
||||
var stainId = (StainId)_edit._stainService.StainCombo.CurrentSelection.Key;
|
||||
var colorDyeSet = maybeColorDyeSet.Value;
|
||||
for (var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (HighlightedColorSetRow >= 0)
|
||||
ApplyHighlight(ref rows[HighlightedColorSetRow], HighlightTime);
|
||||
|
||||
foreach (var previewer in ColorSetPreviewers)
|
||||
{
|
||||
fixed (Half* pDest = previewer.ColorSet)
|
||||
Buffer.MemoryCopy(&rows, pDest, LiveColorSetPreviewer.TextureLength * sizeof(Half), sizeof(MtrlFile.ColorSet.RowArray));
|
||||
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;
|
||||
var levelSq = (float)(level * level);
|
||||
|
||||
row.Diffuse = Vector3.Zero;
|
||||
row.Specular = Vector3.Zero;
|
||||
row.Emissive = new Vector3(levelSq);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
UpdateTextureLabels();
|
||||
|
|
@ -277,11 +504,31 @@ public partial class ModEditWindow
|
|||
UpdateSamplers();
|
||||
}
|
||||
|
||||
public MtrlTab( ModEditWindow edit, MtrlFile file )
|
||||
public MtrlTab( ModEditWindow edit, MtrlFile file, string filePath, bool writable )
|
||||
{
|
||||
_edit = edit;
|
||||
Mtrl = file;
|
||||
FilePath = filePath;
|
||||
Writable = writable;
|
||||
LoadShpk( FindAssociatedShpk( out _, out _ ) );
|
||||
if (writable)
|
||||
BindToMaterialInstances();
|
||||
}
|
||||
|
||||
~MtrlTab()
|
||||
{
|
||||
DoDispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DoDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DoDispose()
|
||||
{
|
||||
UnbindFromMaterialInstances();
|
||||
}
|
||||
|
||||
public bool Valid
|
||||
|
|
|
|||
|
|
@ -37,16 +37,17 @@ public partial class ModEditWindow
|
|||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawShaderFlagsInput(MtrlFile file, bool disabled)
|
||||
private static bool DrawShaderFlagsInput(MtrlTab tab, bool disabled)
|
||||
{
|
||||
var ret = false;
|
||||
var shpkFlags = (int)file.ShaderPackage.Flags;
|
||||
var shpkFlags = (int)tab.Mtrl.ShaderPackage.Flags;
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
if (ImGui.InputInt("Shader Package Flags", ref shpkFlags, 0, 0,
|
||||
ImGuiInputTextFlags.CharsHexadecimal | (disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None)))
|
||||
{
|
||||
file.ShaderPackage.Flags = (uint)shpkFlags;
|
||||
tab.Mtrl.ShaderPackage.Flags = (uint)shpkFlags;
|
||||
ret = true;
|
||||
tab.SetShaderPackageFlags((uint)shpkFlags);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
@ -221,6 +222,7 @@ public partial class ModEditWindow
|
|||
{
|
||||
ret = true;
|
||||
tab.UpdateConstantLabels();
|
||||
tab.SetMaterialParameter(constant.Id, valueIdx, values.Slice(valueIdx, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -247,6 +249,7 @@ public partial class ModEditWindow
|
|||
|
||||
ret = true;
|
||||
tab.UpdateConstantLabels();
|
||||
tab.SetMaterialParameter(constant.Id, 0, new float[constant.ByteSize >> 2]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
@ -336,7 +339,7 @@ public partial class ModEditWindow
|
|||
|
||||
private static bool DrawMaterialSampler(MtrlTab tab, bool disabled, ref int idx)
|
||||
{
|
||||
var (label, filename) = tab.Samplers[idx];
|
||||
var (label, filename, samplerCrc) = tab.Samplers[idx];
|
||||
using var tree = ImRaii.TreeNode(label);
|
||||
if (!tree)
|
||||
return false;
|
||||
|
|
@ -366,6 +369,7 @@ public partial class ModEditWindow
|
|||
{
|
||||
tab.Mtrl.ShaderPackage.Samplers[idx].Flags = (uint)samplerFlags;
|
||||
ret = true;
|
||||
tab.SetSamplerFlags(samplerCrc, (uint)samplerFlags);
|
||||
}
|
||||
|
||||
if (!disabled
|
||||
|
|
@ -410,9 +414,10 @@ public partial class ModEditWindow
|
|||
if (!ImGui.Button("Add Sampler"))
|
||||
return false;
|
||||
|
||||
var newSamplerId = tab.NewSamplerId;
|
||||
tab.Mtrl.ShaderPackage.Samplers = tab.Mtrl.ShaderPackage.Samplers.AddItem(new Sampler
|
||||
{
|
||||
SamplerId = tab.NewSamplerId,
|
||||
SamplerId = newSamplerId,
|
||||
TextureIndex = (byte)tab.Mtrl.Textures.Length,
|
||||
Flags = 0,
|
||||
});
|
||||
|
|
@ -423,6 +428,7 @@ public partial class ModEditWindow
|
|||
});
|
||||
tab.UpdateSamplers();
|
||||
tab.UpdateTextureLabels();
|
||||
tab.SetSamplerFlags(newSamplerId, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -467,7 +473,7 @@ public partial class ModEditWindow
|
|||
return ret;
|
||||
|
||||
ret |= DrawPackageNameInput(tab, disabled);
|
||||
ret |= DrawShaderFlagsInput(tab.Mtrl, disabled);
|
||||
ret |= DrawShaderFlagsInput(tab, disabled);
|
||||
DrawCustomAssociations(tab);
|
||||
ret |= DrawMaterialShaderKeys(tab, disabled);
|
||||
DrawMaterialShaders(tab);
|
||||
|
|
|
|||
|
|
@ -15,13 +15,16 @@ public partial class ModEditWindow
|
|||
|
||||
private bool DrawMaterialPanel( MtrlTab tab, bool disabled )
|
||||
{
|
||||
DrawMaterialLivePreviewRebind( tab, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
var ret = DrawMaterialTextureChange( tab, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawBackFaceAndTransparency( tab.Mtrl, disabled );
|
||||
ret |= DrawBackFaceAndTransparency( tab, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawMaterialColorSetChange( tab.Mtrl, disabled );
|
||||
ret |= DrawMaterialColorSetChange( tab, disabled );
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawMaterialShaderResources( tab, disabled );
|
||||
|
|
@ -32,6 +35,15 @@ public partial class ModEditWindow
|
|||
return !disabled && ret;
|
||||
}
|
||||
|
||||
private static void DrawMaterialLivePreviewRebind( MtrlTab tab, bool disabled )
|
||||
{
|
||||
if (disabled)
|
||||
return;
|
||||
|
||||
if (ImGui.Button("Reload live-preview"))
|
||||
tab.BindToMaterialInstances();
|
||||
}
|
||||
|
||||
private static bool DrawMaterialTextureChange( MtrlTab tab, bool disabled )
|
||||
{
|
||||
var ret = false;
|
||||
|
|
@ -62,7 +74,7 @@ public partial class ModEditWindow
|
|||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawBackFaceAndTransparency( MtrlFile file, bool disabled )
|
||||
private static bool DrawBackFaceAndTransparency( MtrlTab tab, bool disabled )
|
||||
{
|
||||
const uint transparencyBit = 0x10;
|
||||
const uint backfaceBit = 0x01;
|
||||
|
|
@ -71,19 +83,21 @@ public partial class ModEditWindow
|
|||
|
||||
using var dis = ImRaii.Disabled( disabled );
|
||||
|
||||
var tmp = ( file.ShaderPackage.Flags & transparencyBit ) != 0;
|
||||
var tmp = ( tab.Mtrl.ShaderPackage.Flags & transparencyBit ) != 0;
|
||||
if( ImGui.Checkbox( "Enable Transparency", ref tmp ) )
|
||||
{
|
||||
file.ShaderPackage.Flags = tmp ? file.ShaderPackage.Flags | transparencyBit : file.ShaderPackage.Flags & ~transparencyBit;
|
||||
tab.Mtrl.ShaderPackage.Flags = tmp ? tab.Mtrl.ShaderPackage.Flags | transparencyBit : tab.Mtrl.ShaderPackage.Flags & ~transparencyBit;
|
||||
ret = true;
|
||||
tab.SetShaderPackageFlags(tab.Mtrl.ShaderPackage.Flags);
|
||||
}
|
||||
|
||||
ImGui.SameLine( 200 * UiHelpers.Scale + ImGui.GetStyle().ItemSpacing.X + ImGui.GetStyle().WindowPadding.X );
|
||||
tmp = ( file.ShaderPackage.Flags & backfaceBit ) != 0;
|
||||
tmp = ( tab.Mtrl.ShaderPackage.Flags & backfaceBit ) != 0;
|
||||
if( ImGui.Checkbox( "Hide Backfaces", ref tmp ) )
|
||||
{
|
||||
file.ShaderPackage.Flags = tmp ? file.ShaderPackage.Flags | backfaceBit : file.ShaderPackage.Flags & ~backfaceBit;
|
||||
tab.Mtrl.ShaderPackage.Flags = tmp ? tab.Mtrl.ShaderPackage.Flags | backfaceBit : tab.Mtrl.ShaderPackage.Flags & ~backfaceBit;
|
||||
ret = true;
|
||||
tab.SetShaderPackageFlags(tab.Mtrl.ShaderPackage.Flags);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -137,6 +137,9 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
{
|
||||
_left.Dispose();
|
||||
_right.Dispose();
|
||||
_materialTab.Reset();
|
||||
_modelTab.Reset();
|
||||
_shaderPackageTab.Reset();
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
|
|
@ -541,12 +544,12 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
_fileDialog = fileDialog;
|
||||
_materialTab = new FileEditor<MtrlTab>(this, gameData, config, _fileDialog, "Materials", ".mtrl",
|
||||
() => _editor.Files.Mtrl, DrawMaterialPanel, () => _mod?.ModPath.FullName ?? string.Empty,
|
||||
bytes => new MtrlTab(this, new MtrlFile(bytes)));
|
||||
(bytes, path, writable) => new MtrlTab(this, new MtrlFile(bytes), path, writable));
|
||||
_modelTab = new FileEditor<MdlFile>(this, gameData, config, _fileDialog, "Models", ".mdl",
|
||||
() => _editor.Files.Mdl, DrawModelPanel, () => _mod?.ModPath.FullName ?? string.Empty, bytes => new MdlFile(bytes));
|
||||
() => _editor.Files.Mdl, DrawModelPanel, () => _mod?.ModPath.FullName ?? string.Empty, (bytes, _, _) => new MdlFile(bytes));
|
||||
_shaderPackageTab = new FileEditor<ShpkTab>(this, gameData, config, _fileDialog, "Shaders", ".shpk",
|
||||
() => _editor.Files.Shpk, DrawShaderPackagePanel, () => _mod?.ModPath.FullName ?? string.Empty,
|
||||
bytes => new ShpkTab(_fileDialog, bytes));
|
||||
(bytes, _, _) => new ShpkTab(_fileDialog, bytes));
|
||||
_center = new CombinedTexture(_left, _right);
|
||||
_textureSelectCombo = new TextureDrawer.PathSelectCombo(textures, editor);
|
||||
_quickImportViewer = new ResourceTreeViewer(_config, resourceTreeFactory, 2, OnQuickImportRefresh, DrawQuickImportActions);
|
||||
|
|
@ -557,6 +560,9 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
{
|
||||
_communicator.ModPathChanged.Unsubscribe(OnModPathChanged);
|
||||
_editor?.Dispose();
|
||||
_materialTab.Dispose();
|
||||
_modelTab.Dispose();
|
||||
_shaderPackageTab.Dispose();
|
||||
_left.Dispose();
|
||||
_right.Dispose();
|
||||
_center.Dispose();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue