mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-14 20:54:16 +01:00
Merge branch 'feature/material-editor-2099'
This commit is contained in:
commit
82cecdaf7d
24 changed files with 2750 additions and 1321 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit c8394607addd29cb7f8ae3257f635a4486c40a63
|
||||
Subproject commit 728dd8c33f8b43f7a2725ac7c8886fe7cb3f04a9
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 97643cad67b6981c3ee510d1ca12c4321e6a80bf
|
||||
Subproject commit 1c68fd5efb23798d13154c1de0ad010db319abe2
|
||||
131
Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs
Normal file
131
Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
using Penumbra.GameData.Files;
|
||||
|
||||
namespace Penumbra.Interop.MaterialPreview;
|
||||
|
||||
public 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, MaterialInfo materialInfo)
|
||||
: base(objects, materialInfo)
|
||||
{
|
||||
_framework = framework;
|
||||
|
||||
var mtrlHandle = Material->MaterialResourceHandle;
|
||||
if (mtrlHandle == null)
|
||||
throw new InvalidOperationException("Material doesn't have a resource handle");
|
||||
|
||||
var colorSetTextures = ((Structs.CharacterBaseExt*)DrawObject)->ColorSetTextures;
|
||||
if (colorSetTextures == null)
|
||||
throw new InvalidOperationException("Draw object doesn't have color set textures");
|
||||
|
||||
_colorSetTexture = colorSetTextures + (MaterialInfo.ModelSlot * 4 + MaterialInfo.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 Clear(bool disposing, bool reset)
|
||||
{
|
||||
_framework.Update -= OnFrameworkUpdate;
|
||||
|
||||
base.Clear(disposing, reset);
|
||||
|
||||
if (reset)
|
||||
{
|
||||
var oldTexture = (Texture*)Interlocked.Exchange(ref *(nint*)_colorSetTexture, (nint)_originalColorSetTexture);
|
||||
if (oldTexture != null)
|
||||
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);
|
||||
if (oldTexture != null)
|
||||
Structs.TextureUtility.DecRef(oldTexture);
|
||||
}
|
||||
else
|
||||
{
|
||||
Structs.TextureUtility.DecRef(newTexture);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool IsStillValid()
|
||||
{
|
||||
if (!base.IsStillValid())
|
||||
return false;
|
||||
|
||||
var colorSetTextures = ((Structs.CharacterBaseExt*)DrawObject)->ColorSetTextures;
|
||||
if (colorSetTextures == null)
|
||||
return false;
|
||||
|
||||
if (_colorSetTexture != colorSetTextures + (MaterialInfo.ModelSlot * 4 + MaterialInfo.MaterialSlot))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
149
Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs
Normal file
149
Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
using System;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
|
||||
namespace Penumbra.Interop.MaterialPreview;
|
||||
|
||||
public 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, MaterialInfo materialInfo)
|
||||
: base(objects, materialInfo)
|
||||
{
|
||||
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 Clear(bool disposing, bool reset)
|
||||
{
|
||||
base.Clear(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 constantBuffer = ((Structs.Material*)Material)->MaterialParameter;
|
||||
if (constantBuffer == null)
|
||||
return;
|
||||
|
||||
if (!constantBuffer->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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
|
||||
namespace Penumbra.Interop.MaterialPreview;
|
||||
|
||||
public abstract unsafe class LiveMaterialPreviewerBase : IDisposable
|
||||
{
|
||||
private readonly IObjectTable _objects;
|
||||
|
||||
public readonly MaterialInfo MaterialInfo;
|
||||
public readonly CharacterBase* DrawObject;
|
||||
protected readonly Material* Material;
|
||||
|
||||
protected bool Valid;
|
||||
|
||||
public LiveMaterialPreviewerBase(IObjectTable objects, MaterialInfo materialInfo)
|
||||
{
|
||||
_objects = objects;
|
||||
|
||||
MaterialInfo = materialInfo;
|
||||
var gameObject = MaterialInfo.GetCharacter(objects);
|
||||
if (gameObject == nint.Zero)
|
||||
throw new InvalidOperationException("Cannot retrieve game object.");
|
||||
|
||||
DrawObject = (CharacterBase*)MaterialInfo.GetDrawObject(gameObject);
|
||||
if (DrawObject == null)
|
||||
throw new InvalidOperationException("Cannot retrieve draw object.");
|
||||
|
||||
Material = MaterialInfo.GetDrawObjectMaterial(DrawObject);
|
||||
if (Material == null)
|
||||
throw new InvalidOperationException("Cannot retrieve material.");
|
||||
|
||||
Valid = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Valid)
|
||||
Clear(true, IsStillValid());
|
||||
}
|
||||
|
||||
public bool CheckValidity()
|
||||
{
|
||||
if (Valid && !IsStillValid())
|
||||
Clear(false, false);
|
||||
return Valid;
|
||||
}
|
||||
|
||||
protected virtual void Clear(bool disposing, bool reset)
|
||||
{
|
||||
Valid = false;
|
||||
}
|
||||
|
||||
protected virtual bool IsStillValid()
|
||||
{
|
||||
var gameObject = MaterialInfo.GetCharacter(_objects);
|
||||
if (gameObject == nint.Zero)
|
||||
return false;
|
||||
|
||||
if ((nint)DrawObject != MaterialInfo.GetDrawObject(gameObject))
|
||||
return false;
|
||||
|
||||
if (Material != MaterialInfo.GetDrawObjectMaterial(DrawObject))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
120
Penumbra/Interop/MaterialPreview/MaterialInfo.cs
Normal file
120
Penumbra/Interop/MaterialPreview/MaterialInfo.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.Interop.ResourceTree;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Penumbra.Interop.MaterialPreview;
|
||||
|
||||
public enum DrawObjectType
|
||||
{
|
||||
PlayerCharacter,
|
||||
PlayerMainhand,
|
||||
PlayerOffhand,
|
||||
PlayerVfx,
|
||||
MinionCharacter,
|
||||
MinionUnk1,
|
||||
MinionUnk2,
|
||||
MinionUnk3,
|
||||
};
|
||||
|
||||
public readonly record struct MaterialInfo(DrawObjectType Type, int ModelSlot, int MaterialSlot)
|
||||
{
|
||||
public nint GetCharacter(IObjectTable objects)
|
||||
=> GetCharacter(Type, objects);
|
||||
|
||||
public static nint GetCharacter(DrawObjectType type, IObjectTable objects)
|
||||
=> type switch
|
||||
{
|
||||
DrawObjectType.PlayerCharacter => objects.GetObjectAddress(0),
|
||||
DrawObjectType.PlayerMainhand => objects.GetObjectAddress(0),
|
||||
DrawObjectType.PlayerOffhand => objects.GetObjectAddress(0),
|
||||
DrawObjectType.PlayerVfx => objects.GetObjectAddress(0),
|
||||
DrawObjectType.MinionCharacter => objects.GetObjectAddress(1),
|
||||
DrawObjectType.MinionUnk1 => objects.GetObjectAddress(1),
|
||||
DrawObjectType.MinionUnk2 => objects.GetObjectAddress(1),
|
||||
DrawObjectType.MinionUnk3 => objects.GetObjectAddress(1),
|
||||
_ => nint.Zero,
|
||||
};
|
||||
|
||||
public nint GetDrawObject(nint address)
|
||||
=> GetDrawObject(Type, address);
|
||||
|
||||
public static nint GetDrawObject(DrawObjectType type, IObjectTable objects)
|
||||
=> GetDrawObject(type, GetCharacter(type, objects));
|
||||
|
||||
public static unsafe nint GetDrawObject(DrawObjectType type, nint address)
|
||||
{
|
||||
var gameObject = (Character*)address;
|
||||
if (gameObject == null)
|
||||
return nint.Zero;
|
||||
|
||||
return type switch
|
||||
{
|
||||
DrawObjectType.PlayerCharacter => (nint)gameObject->GameObject.GetDrawObject(),
|
||||
DrawObjectType.PlayerMainhand => *((nint*)&gameObject->DrawData.MainHand + 1),
|
||||
DrawObjectType.PlayerOffhand => *((nint*)&gameObject->DrawData.OffHand + 1),
|
||||
DrawObjectType.PlayerVfx => *((nint*)&gameObject->DrawData.UnkF0 + 1),
|
||||
DrawObjectType.MinionCharacter => (nint)gameObject->GameObject.GetDrawObject(),
|
||||
DrawObjectType.MinionUnk1 => *((nint*)&gameObject->DrawData.MainHand + 1),
|
||||
DrawObjectType.MinionUnk2 => *((nint*)&gameObject->DrawData.OffHand + 1),
|
||||
DrawObjectType.MinionUnk3 => *((nint*)&gameObject->DrawData.UnkF0 + 1),
|
||||
_ => nint.Zero,
|
||||
};
|
||||
}
|
||||
|
||||
public unsafe Material* GetDrawObjectMaterial(CharacterBase* drawObject)
|
||||
{
|
||||
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];
|
||||
}
|
||||
|
||||
public static unsafe List<MaterialInfo> FindMaterials(IObjectTable objects, string materialPath)
|
||||
{
|
||||
var needle = ByteString.FromString(materialPath.Replace('\\', '/'), out var m, true) ? m : ByteString.Empty;
|
||||
|
||||
var result = new List<MaterialInfo>(Enum.GetValues<DrawObjectType>().Length);
|
||||
foreach (var type in Enum.GetValues<DrawObjectType>())
|
||||
{
|
||||
var drawObject = (CharacterBase*)GetDrawObject(type, objects);
|
||||
if (drawObject == null)
|
||||
continue;
|
||||
|
||||
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;
|
||||
var path = ResolveContext.GetResourceHandlePath((Structs.ResourceHandle*)mtrlHandle);
|
||||
if (path == needle)
|
||||
result.Add(new MaterialInfo(type, i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -65,26 +65,13 @@ 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);
|
||||
|
||||
private unsafe ResourceNode? CreateNodeFromResourceHandle(ResourceType type, nint sourceAddress, ResourceHandle* handle, bool @internal,
|
||||
bool withName)
|
||||
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)
|
||||
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 fullPath = Utf8GamePath.FromByteString(GetResourceHandlePath(handle), out var p) ? new FullPath(p) : FullPath.Empty;
|
||||
if (fullPath.InternalName.IsEmpty)
|
||||
return null;
|
||||
|
||||
var gamePaths = Collection.ReverseResolvePath(fullPath).ToList();
|
||||
fullPath = FilterFullPath(fullPath);
|
||||
|
||||
|
|
@ -161,7 +148,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;
|
||||
|
|
@ -182,7 +169,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
|
||||
if (WithNames)
|
||||
{
|
||||
var name = samplers != null && i < samplers.Count ? samplers[i].Item2?.Name : null;
|
||||
var name = samplers != null && i < samplers.Length ? samplers[i].ShpkSampler?.Name : null;
|
||||
node.Children.Add(texNode.WithName(name ?? $"Texture #{i}"));
|
||||
}
|
||||
else
|
||||
|
|
@ -286,4 +273,25 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
var i = index.GetOffset(array.Length);
|
||||
return i >= 0 && i < array.Length ? array[i] : null;
|
||||
}
|
||||
|
||||
internal static unsafe ByteString GetResourceHandlePath(ResourceHandle* handle)
|
||||
{
|
||||
if (handle == null)
|
||||
return ByteString.Empty;
|
||||
|
||||
var name = handle->FileName();
|
||||
if (name.IsEmpty)
|
||||
return ByteString.Empty;
|
||||
|
||||
if (name[0] == (byte)'|')
|
||||
{
|
||||
var pos = name.IndexOf((byte)'|', 1);
|
||||
if (pos < 0)
|
||||
return ByteString.Empty;
|
||||
|
||||
name = name.Substring(pos + 1);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
15
Penumbra/Interop/Structs/CharacterBaseExt.cs
Normal file
15
Penumbra/Interop/Structs/CharacterBaseExt.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
|
||||
namespace Penumbra.Interop.Structs;
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public unsafe struct CharacterBaseExt
|
||||
{
|
||||
[FieldOffset( 0x0 )]
|
||||
public CharacterBase CharacterBase;
|
||||
|
||||
[FieldOffset( 0x258 )]
|
||||
public Texture** ColorSetTextures;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,9 @@ public unsafe struct HumanExt
|
|||
[FieldOffset( 0x0 )]
|
||||
public Human Human;
|
||||
|
||||
[FieldOffset( 0x0 )]
|
||||
public CharacterBaseExt CharacterBase;
|
||||
|
||||
[FieldOffset( 0x9E8 )]
|
||||
public ResourceHandle* Decal;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
|
@ -60,11 +60,19 @@ public class FileEditor<T> where T : class, IWritable
|
|||
DrawFilePanel();
|
||||
}
|
||||
|
||||
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;
|
||||
public void Dispose()
|
||||
{
|
||||
(_currentFile as IDisposable)?.Dispose();
|
||||
_currentFile = null;
|
||||
(_defaultFile as IDisposable)?.Dispose();
|
||||
_defaultFile = 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[], string, bool, T?> _parseFile;
|
||||
|
||||
private FileRegistry? _currentPath;
|
||||
private T? _currentFile;
|
||||
|
|
@ -99,7 +107,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 +168,7 @@ public class FileEditor<T> where T : class, IWritable
|
|||
{
|
||||
_currentException = null;
|
||||
_currentPath = null;
|
||||
(_currentFile as IDisposable)?.Dispose();
|
||||
_currentFile = null;
|
||||
_changed = false;
|
||||
}
|
||||
|
|
@ -181,10 +192,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,57 +13,77 @@ namespace Penumbra.UI.AdvancedWindow;
|
|||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private bool DrawMaterialColorSetChange( MtrlFile file, bool disabled )
|
||||
private static readonly float HalfMinValue = (float)Half.MinValue;
|
||||
private static readonly float HalfMaxValue = (float)Half.MaxValue;
|
||||
private static readonly float HalfEpsilon = (float)Half.Epsilon;
|
||||
|
||||
private bool DrawMaterialColorSetChange(MtrlTab tab, bool disabled)
|
||||
{
|
||||
if( !file.ColorSets.Any( c => c.HasRows ) )
|
||||
{
|
||||
if (!tab.SamplerIds.Contains(ShpkFile.TableSamplerId) || !tab.Mtrl.ColorSets.Any(c => c.HasRows))
|
||||
return false;
|
||||
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
if (!ImGui.CollapsingHeader("Color Set", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
return false;
|
||||
|
||||
var hasAnyDye = tab.UseColorDyeSet;
|
||||
|
||||
ColorSetCopyAllClipboardButton(tab.Mtrl, 0);
|
||||
ImGui.SameLine();
|
||||
var ret = ColorSetPasteAllClipboardButton(tab, 0, disabled);
|
||||
if (!disabled)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.Dummy(ImGuiHelpers.ScaledVector2(20, 0));
|
||||
ImGui.SameLine();
|
||||
ret |= ColorSetDyeableCheckbox(tab, ref hasAnyDye);
|
||||
}
|
||||
|
||||
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 )
|
||||
if (hasAnyDye)
|
||||
{
|
||||
return false;
|
||||
ImGui.SameLine();
|
||||
ImGui.Dummy(ImGuiHelpers.ScaledVector2(20, 0));
|
||||
ImGui.SameLine();
|
||||
ret |= DrawPreviewDye(tab, disabled);
|
||||
}
|
||||
|
||||
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" );
|
||||
using var table = ImRaii.Table("##ColorSets", hasAnyDye ? 11 : 9,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerV);
|
||||
if (!table)
|
||||
return false;
|
||||
|
||||
for( var j = 0; j < file.ColorSets.Length; ++j )
|
||||
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");
|
||||
if (hasAnyDye)
|
||||
{
|
||||
using var _ = ImRaii.PushId( j );
|
||||
for( var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i )
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader("Dye");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader("Dye Preview");
|
||||
}
|
||||
|
||||
for (var j = 0; j < tab.Mtrl.ColorSets.Length; ++j)
|
||||
{
|
||||
using var _ = ImRaii.PushId(j);
|
||||
for (var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i)
|
||||
{
|
||||
ret |= DrawColorSetRow( file, j, i, disabled );
|
||||
ret |= DrawColorSetRow(tab, j, i, disabled, hasAnyDye);
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
}
|
||||
|
|
@ -72,22 +92,20 @@ public partial class ModEditWindow
|
|||
}
|
||||
|
||||
|
||||
private static void ColorSetCopyAllClipboardButton( MtrlFile file, int colorSetIdx )
|
||||
private static void ColorSetCopyAllClipboardButton(MtrlFile file, int colorSetIdx)
|
||||
{
|
||||
if( !ImGui.Button( "Export All Rows to Clipboard", ImGuiHelpers.ScaledVector2( 200, 0 ) ) )
|
||||
{
|
||||
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 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 );
|
||||
data1.TryCopyTo(array);
|
||||
data2.TryCopyTo(array.AsSpan(data1.Length));
|
||||
var text = Convert.ToBase64String(array);
|
||||
ImGui.SetClipboardText(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -95,61 +113,64 @@ 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 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 );
|
||||
}
|
||||
for (var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i)
|
||||
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, bool disabled)
|
||||
{
|
||||
if( !ImGui.Button( "Import All Rows from Clipboard", ImGuiHelpers.ScaledVector2( 200, 0 ) ) || file.ColorSets.Length <= colorSetIdx )
|
||||
{
|
||||
if (!ImGuiUtil.DrawDisabledButton("Import All Rows from Clipboard", ImGuiHelpers.ScaledVector2(200, 0), string.Empty, disabled)
|
||||
|| tab.Mtrl.ColorSets.Length <= colorSetIdx)
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var text = ImGui.GetClipboardText();
|
||||
var data = Convert.FromBase64String( text );
|
||||
if( data.Length < Marshal.SizeOf< MtrlFile.ColorSet.RowArray >() )
|
||||
{
|
||||
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 )
|
||||
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 )
|
||||
MemoryUtility.MemCpyUnchecked(output, ptr, Marshal.SizeOf<MtrlFile.ColorSet.RowArray>());
|
||||
if (data.Length >= Marshal.SizeOf<MtrlFile.ColorSet.RowArray>() + Marshal.SizeOf<MtrlFile.ColorDyeSet.RowArray>()
|
||||
&& tab.Mtrl.ColorDyeSets.Length > colorSetIdx)
|
||||
{
|
||||
ref var dyeRows = ref file.ColorDyeSets[ colorSetIdx ].Rows;
|
||||
fixed( void* output2 = &dyeRows )
|
||||
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 >() );
|
||||
MemoryUtility.MemCpyUnchecked(output2, (byte*)ptr + Marshal.SizeOf<MtrlFile.ColorSet.RowArray>(),
|
||||
Marshal.SizeOf<MtrlFile.ColorDyeSet.RowArray>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tab.UpdateColorSetPreview();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
|
|
@ -158,279 +179,372 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
|
||||
private static unsafe void ColorSetCopyClipboardButton( MtrlFile.ColorSet.Row row, MtrlFile.ColorDyeSet.Row dye )
|
||||
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 );
|
||||
}
|
||||
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Export this row to your clipboard.", false, true))
|
||||
return;
|
||||
|
||||
var text = Convert.ToBase64String( data );
|
||||
ImGui.SetClipboardText( text );
|
||||
}
|
||||
catch
|
||||
try
|
||||
{
|
||||
var data = new byte[MtrlFile.ColorSet.Row.Size + 2];
|
||||
fixed (byte* ptr = data)
|
||||
{
|
||||
// ignored
|
||||
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 )
|
||||
private static bool ColorSetDyeableCheckbox(MtrlTab tab, ref bool dyeable)
|
||||
{
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Paste.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Import an exported row from your clipboard onto this row.", disabled, true ) )
|
||||
var ret = ImGui.Checkbox("Dyeable", ref dyeable);
|
||||
|
||||
if (ret)
|
||||
{
|
||||
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
|
||||
}
|
||||
tab.UseColorDyeSet = dyeable;
|
||||
if (dyeable)
|
||||
tab.Mtrl.FindOrAddColorDyeSet();
|
||||
tab.UpdateColorSetPreview();
|
||||
}
|
||||
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawColorSetRow( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled )
|
||||
private static unsafe bool ColorSetPasteFromClipboardButton(MtrlTab tab, int colorSetIdx, int rowIdx, bool disabled)
|
||||
{
|
||||
static bool FixFloat( ref float val, float current )
|
||||
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Import an exported row from your clipboard onto this row.", disabled, true))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
val = ( float )( Half )val;
|
||||
var text = ImGui.GetClipboardText();
|
||||
var data = Convert.FromBase64String(text);
|
||||
if (data.Length != MtrlFile.ColorSet.Row.Size + 2
|
||||
|| tab.Mtrl.ColorSets.Length <= colorSetIdx)
|
||||
return false;
|
||||
|
||||
fixed (byte* ptr = data)
|
||||
{
|
||||
tab.Mtrl.ColorSets[colorSetIdx].Rows[rowIdx] = *(MtrlFile.ColorSet.Row*)ptr;
|
||||
if (colorSetIdx < tab.Mtrl.ColorDyeSets.Length)
|
||||
tab.Mtrl.ColorDyeSets[colorSetIdx].Rows[rowIdx] = *(MtrlFile.ColorDyeSet.Row*)(ptr + MtrlFile.ColorSet.Row.Size);
|
||||
}
|
||||
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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, bool hasAnyDye)
|
||||
{
|
||||
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();
|
||||
using var id = ImRaii.PushId(rowIdx);
|
||||
var row = tab.Mtrl.ColorSets[colorSetIdx].Rows[rowIdx];
|
||||
var hasDye = hasAnyDye && tab.Mtrl.ColorDyeSets.Length > colorSetIdx;
|
||||
var dye = hasDye ? tab.Mtrl.ColorDyeSets[colorSetIdx].Rows[rowIdx] : new MtrlFile.ColorDyeSet.Row();
|
||||
var floatSize = 70 * UiHelpers.Scale;
|
||||
var intSize = 45 * UiHelpers.Scale;
|
||||
ImGui.TableNextColumn();
|
||||
ColorSetCopyClipboardButton( row, dye );
|
||||
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.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 )
|
||||
using var dis = ImRaii.Disabled(disabled);
|
||||
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 );
|
||||
ret |= ImGuiUtil.Checkbox("##dyeDiffuse", "Apply Diffuse Color on Dye", dye.Diffuse,
|
||||
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 ) )
|
||||
ImGui.SetNextItemWidth(floatSize);
|
||||
if (ImGui.DragFloat("##SpecularStrength", ref tmpFloat, 0.1f, 0f, HalfMaxValue, "%.2f") && 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 );
|
||||
ImGuiUtil.HoverTooltip("Specular Strength", ImGuiHoveredFlags.AllowWhenDisabled);
|
||||
|
||||
if( hasDye )
|
||||
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 );
|
||||
ret |= ImGuiUtil.Checkbox("##dyeSpecular", "Apply Specular Color on Dye", dye.Specular,
|
||||
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 );
|
||||
ret |= ImGuiUtil.Checkbox("##dyeSpecularStrength", "Apply Specular Strength on Dye", dye.SpecularStrength,
|
||||
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 );
|
||||
if( hasDye )
|
||||
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 );
|
||||
ret |= ImGuiUtil.Checkbox("##dyeEmissive", "Apply Emissive Color on Dye", dye.Emissive,
|
||||
b =>
|
||||
{
|
||||
tab.Mtrl.ColorDyeSets[colorSetIdx].Rows[rowIdx].Emissive = b;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}, ImGuiHoveredFlags.AllowWhenDisabled);
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
tmpFloat = row.GlossStrength;
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
if( ImGui.DragFloat( "##GlossStrength", ref tmpFloat, 0.1f, 0f ) && FixFloat( ref tmpFloat, row.GlossStrength ) )
|
||||
ImGui.SetNextItemWidth(floatSize);
|
||||
if (ImGui.DragFloat("##GlossStrength", ref tmpFloat, Math.Max(0.1f, tmpFloat * 0.025f), HalfEpsilon, HalfMaxValue, "%.1f")
|
||||
&& FixFloat(ref tmpFloat, row.GlossStrength))
|
||||
{
|
||||
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].GlossStrength = tmpFloat;
|
||||
tab.Mtrl.ColorSets[colorSetIdx].Rows[rowIdx].GlossStrength = Math.Max(tmpFloat, HalfEpsilon);
|
||||
ret = true;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Gloss Strength", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
if( hasDye )
|
||||
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 );
|
||||
ret |= ImGuiUtil.Checkbox("##dyeGloss", "Apply Gloss Strength on Dye", dye.Gloss,
|
||||
b =>
|
||||
{
|
||||
tab.Mtrl.ColorDyeSets[colorSetIdx].Rows[rowIdx].Gloss = b;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}, 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 )
|
||||
ImGui.SetNextItemWidth(intSize);
|
||||
if (ImGui.DragInt("##TileSet", ref tmpInt, 0.25f, 0, 63) && 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)Math.Clamp(tmpInt, 0, 63);
|
||||
ret = true;
|
||||
tab.UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Tile Set", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
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 ) )
|
||||
ImGui.SetNextItemWidth(floatSize);
|
||||
if (ImGui.DragFloat("##RepeatX", ref tmpFloat, 0.1f, HalfMinValue, HalfMaxValue, "%.2f")
|
||||
&& 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 );
|
||||
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 ) )
|
||||
ImGui.SetNextItemWidth(floatSize);
|
||||
if (ImGui.DragFloat("##RepeatY", ref tmpFloat, 0.1f, HalfMinValue, HalfMaxValue, "%.2f")
|
||||
&& 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 );
|
||||
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 ) )
|
||||
ImGui.SetNextItemWidth(floatSize);
|
||||
if (ImGui.DragFloat("##SkewX", ref tmpFloat, 0.1f, HalfMinValue, HalfMaxValue, "%.2f") && 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 );
|
||||
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 ) )
|
||||
ImGui.SetNextItemWidth(floatSize);
|
||||
if (ImGui.DragFloat("##SkewY", ref tmpFloat, 0.1f, HalfMinValue, HalfMaxValue, "%.2f") && 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 );
|
||||
ImGuiUtil.HoverTooltip("Skew Y", ImGuiHoveredFlags.AllowWhenDisabled);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if( hasDye )
|
||||
if (hasDye)
|
||||
{
|
||||
if(_stainService.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize
|
||||
+ ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton ) )
|
||||
ImGui.TableNextColumn();
|
||||
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 );
|
||||
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
|
||||
else if (hasAnyDye)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
}
|
||||
|
||||
|
||||
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 ) )
|
||||
{
|
||||
if (stain == 0 || !_stainService.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 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 );
|
||||
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" );
|
||||
ColorPicker("##diffusePreview", string.Empty, values.Diffuse, _ => { }, "D");
|
||||
ImGui.SameLine();
|
||||
ColorPicker( "##specularPreview", string.Empty, values.Specular, _ => { }, "S" );
|
||||
ColorPicker("##specularPreview", string.Empty, values.Specular, _ => { }, "S");
|
||||
ImGui.SameLine();
|
||||
ColorPicker( "##emissivePreview", string.Empty, values.Emissive, _ => { }, "E" );
|
||||
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.SetNextItemWidth(floatSize);
|
||||
ImGui.DragFloat("##gloss", ref values.Gloss, 0, values.Gloss, values.Gloss, "%.1f G");
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth( floatSize );
|
||||
ImGui.DragFloat( "##specularStrength", ref values.SpecularPower, 0, 0, 0, "%.2f S" );
|
||||
ImGui.SetNextItemWidth(floatSize);
|
||||
ImGui.DragFloat("##specularStrength", ref values.SpecularPower, 0, values.SpecularPower, values.SpecularPower, "%.2f S");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool ColorPicker( string label, string tooltip, Vector3 input, Action< Vector3 > setter, string letter = "" )
|
||||
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 )
|
||||
var ret = false;
|
||||
var inputSqrt = PseudoSqrtRgb(input);
|
||||
var tmp = inputSqrt;
|
||||
if (ImGui.ColorEdit3(label, ref tmp,
|
||||
ImGuiColorEditFlags.NoInputs
|
||||
| ImGuiColorEditFlags.DisplayRGB
|
||||
| ImGuiColorEditFlags.InputRGB
|
||||
| ImGuiColorEditFlags.NoTooltip
|
||||
| ImGuiColorEditFlags.HDR)
|
||||
&& tmp != inputSqrt)
|
||||
{
|
||||
setter( tmp );
|
||||
setter(PseudoSquareRgb(tmp));
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if( letter.Length > 0 && ImGui.IsItemVisible() )
|
||||
if (letter.Length > 0 && ImGui.IsItemVisible())
|
||||
{
|
||||
var textSize = ImGui.CalcTextSize( letter );
|
||||
var center = ImGui.GetItemRectMin() + ( ImGui.GetItemRectSize() - textSize ) / 2;
|
||||
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 );
|
||||
ImGui.GetWindowDrawList().AddText(center, textColor, letter);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( tooltip, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||
ImGuiUtil.HoverTooltip(tooltip, ImGuiHoveredFlags.AllowWhenDisabled);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// Functions to deal with squared RGB values without making negatives useless.
|
||||
|
||||
private static float PseudoSquareRgb(float x)
|
||||
=> x < 0.0f ? -(x * x) : x * x;
|
||||
|
||||
private static Vector3 PseudoSquareRgb(Vector3 vec)
|
||||
=> new(PseudoSquareRgb(vec.X), PseudoSquareRgb(vec.Y), PseudoSquareRgb(vec.Z));
|
||||
|
||||
private static Vector4 PseudoSquareRgb(Vector4 vec)
|
||||
=> new(PseudoSquareRgb(vec.X), PseudoSquareRgb(vec.Y), PseudoSquareRgb(vec.Z), vec.W);
|
||||
|
||||
private static float PseudoSqrtRgb(float x)
|
||||
=> x < 0.0f ? -MathF.Sqrt(-x) : MathF.Sqrt(x);
|
||||
|
||||
private static Vector3 PseudoSqrtRgb(Vector3 vec)
|
||||
=> new(PseudoSqrtRgb(vec.X), PseudoSqrtRgb(vec.Y), PseudoSqrtRgb(vec.Z));
|
||||
|
||||
private static Vector4 PseudoSqrtRgb(Vector4 vec)
|
||||
=> new(PseudoSqrtRgb(vec.X), PseudoSqrtRgb(vec.Y), PseudoSqrtRgb(vec.Z), vec.W);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,248 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private interface IConstantEditor
|
||||
{
|
||||
bool Draw(Span<float> values, bool disabled, float editorWidth);
|
||||
}
|
||||
|
||||
private sealed class FloatConstantEditor : IConstantEditor
|
||||
{
|
||||
public static readonly FloatConstantEditor Default = new(null, null, 0.1f, 0.0f, 1.0f, 0.0f, 3, string.Empty);
|
||||
|
||||
private readonly float? _minimum;
|
||||
private readonly float? _maximum;
|
||||
private readonly float _speed;
|
||||
private readonly float _relativeSpeed;
|
||||
private readonly float _factor;
|
||||
private readonly float _bias;
|
||||
private readonly string _format;
|
||||
|
||||
public FloatConstantEditor(float? minimum, float? maximum, float speed, float relativeSpeed, float factor, float bias, byte precision,
|
||||
string unit)
|
||||
{
|
||||
_minimum = minimum;
|
||||
_maximum = maximum;
|
||||
_speed = speed;
|
||||
_relativeSpeed = relativeSpeed;
|
||||
_factor = factor;
|
||||
_bias = bias;
|
||||
_format = $"%.{Math.Min(precision, (byte)9)}f";
|
||||
if (unit.Length > 0)
|
||||
_format = $"{_format} {unit.Replace("%", "%%")}";
|
||||
}
|
||||
|
||||
public bool Draw(Span<float> values, bool disabled, float editorWidth)
|
||||
{
|
||||
var fieldWidth = (editorWidth - (values.Length - 1) * ImGui.GetStyle().ItemSpacing.X) / values.Length;
|
||||
|
||||
var ret = false;
|
||||
|
||||
for (var valueIdx = 0; valueIdx < values.Length; ++valueIdx)
|
||||
{
|
||||
if (valueIdx > 0)
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.SetNextItemWidth(MathF.Round(fieldWidth * (valueIdx + 1)) - MathF.Round(fieldWidth * valueIdx));
|
||||
|
||||
var value = (values[valueIdx] - _bias) / _factor;
|
||||
if (disabled)
|
||||
{
|
||||
ImGui.DragFloat($"##{valueIdx}", ref value, Math.Max(_speed, value * _relativeSpeed), value, value, _format);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui.DragFloat($"##{valueIdx}", ref value, Math.Max(_speed, value * _relativeSpeed), _minimum ?? 0.0f,
|
||||
_maximum ?? 0.0f, _format))
|
||||
{
|
||||
values[valueIdx] = Clamp(value) * _factor + _bias;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private float Clamp(float value)
|
||||
=> Math.Clamp(value, _minimum ?? float.NegativeInfinity, _maximum ?? float.PositiveInfinity);
|
||||
}
|
||||
|
||||
private sealed class IntConstantEditor : IConstantEditor
|
||||
{
|
||||
private readonly int? _minimum;
|
||||
private readonly int? _maximum;
|
||||
private readonly float _speed;
|
||||
private readonly float _relativeSpeed;
|
||||
private readonly float _factor;
|
||||
private readonly float _bias;
|
||||
private readonly string _format;
|
||||
|
||||
public IntConstantEditor(int? minimum, int? maximum, float speed, float relativeSpeed, float factor, float bias, string unit)
|
||||
{
|
||||
_minimum = minimum;
|
||||
_maximum = maximum;
|
||||
_speed = speed;
|
||||
_relativeSpeed = relativeSpeed;
|
||||
_factor = factor;
|
||||
_bias = bias;
|
||||
_format = "%d";
|
||||
if (unit.Length > 0)
|
||||
_format = $"{_format} {unit.Replace("%", "%%")}";
|
||||
}
|
||||
|
||||
public bool Draw(Span<float> values, bool disabled, float editorWidth)
|
||||
{
|
||||
var fieldWidth = (editorWidth - (values.Length - 1) * ImGui.GetStyle().ItemSpacing.X) / values.Length;
|
||||
|
||||
var ret = false;
|
||||
|
||||
for (var valueIdx = 0; valueIdx < values.Length; ++valueIdx)
|
||||
{
|
||||
if (valueIdx > 0)
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.SetNextItemWidth(MathF.Round(fieldWidth * (valueIdx + 1)) - MathF.Round(fieldWidth * valueIdx));
|
||||
|
||||
var value = (int)Math.Clamp(MathF.Round((values[valueIdx] - _bias) / _factor), int.MinValue, int.MaxValue);
|
||||
if (disabled)
|
||||
{
|
||||
ImGui.DragInt($"##{valueIdx}", ref value, Math.Max(_speed, value * _relativeSpeed), value, value, _format);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui.DragInt($"##{valueIdx}", ref value, Math.Max(_speed, value * _relativeSpeed), _minimum ?? 0, _maximum ?? 0,
|
||||
_format))
|
||||
{
|
||||
values[valueIdx] = Clamp(value) * _factor + _bias;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private int Clamp(int value)
|
||||
=> Math.Clamp(value, _minimum ?? int.MinValue, _maximum ?? int.MaxValue);
|
||||
}
|
||||
|
||||
private sealed class ColorConstantEditor : IConstantEditor
|
||||
{
|
||||
private readonly bool _squaredRgb;
|
||||
private readonly bool _clamped;
|
||||
|
||||
public ColorConstantEditor(bool squaredRgb, bool clamped)
|
||||
{
|
||||
_squaredRgb = squaredRgb;
|
||||
_clamped = clamped;
|
||||
}
|
||||
|
||||
public bool Draw(Span<float> values, bool disabled, float editorWidth)
|
||||
{
|
||||
switch (values.Length)
|
||||
{
|
||||
case 3:
|
||||
{
|
||||
ImGui.SetNextItemWidth(editorWidth);
|
||||
var value = new Vector3(values);
|
||||
if (_squaredRgb)
|
||||
value = PseudoSqrtRgb(value);
|
||||
if (!ImGui.ColorEdit3("##0", ref value, ImGuiColorEditFlags.Float | (_clamped ? 0 : ImGuiColorEditFlags.HDR)) || disabled)
|
||||
return false;
|
||||
|
||||
if (_squaredRgb)
|
||||
value = PseudoSquareRgb(value);
|
||||
if (_clamped)
|
||||
value = Vector3.Clamp(value, Vector3.Zero, Vector3.One);
|
||||
value.CopyTo(values);
|
||||
return true;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
ImGui.SetNextItemWidth(editorWidth);
|
||||
var value = new Vector4(values);
|
||||
if (_squaredRgb)
|
||||
value = PseudoSqrtRgb(value);
|
||||
if (!ImGui.ColorEdit4("##0", ref value,
|
||||
ImGuiColorEditFlags.Float | ImGuiColorEditFlags.AlphaPreviewHalf | (_clamped ? 0 : ImGuiColorEditFlags.HDR))
|
||||
|| disabled)
|
||||
return false;
|
||||
|
||||
if (_squaredRgb)
|
||||
value = PseudoSquareRgb(value);
|
||||
if (_clamped)
|
||||
value = Vector4.Clamp(value, Vector4.Zero, Vector4.One);
|
||||
value.CopyTo(values);
|
||||
return true;
|
||||
}
|
||||
default: return FloatConstantEditor.Default.Draw(values, disabled, editorWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EnumConstantEditor : IConstantEditor
|
||||
{
|
||||
private readonly IReadOnlyList<(string Label, float Value, string Description)> _values;
|
||||
|
||||
public EnumConstantEditor(IReadOnlyList<(string Label, float Value, string Description)> values)
|
||||
=> _values = values;
|
||||
|
||||
public bool Draw(Span<float> values, bool disabled, float editorWidth)
|
||||
{
|
||||
var fieldWidth = (editorWidth - (values.Length - 1) * ImGui.GetStyle().ItemSpacing.X) / values.Length;
|
||||
|
||||
var ret = false;
|
||||
|
||||
for (var valueIdx = 0; valueIdx < values.Length; ++valueIdx)
|
||||
{
|
||||
using var id = ImRaii.PushId(valueIdx);
|
||||
if (valueIdx > 0)
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.SetNextItemWidth(MathF.Round(fieldWidth * (valueIdx + 1)) - MathF.Round(fieldWidth * valueIdx));
|
||||
|
||||
var currentValue = values[valueIdx];
|
||||
var currentLabel = _values.FirstOrNull(v => v.Value == currentValue)?.Label
|
||||
?? currentValue.ToString(CultureInfo.CurrentCulture);
|
||||
ret = disabled
|
||||
? ImGui.InputText(string.Empty, ref currentLabel, (uint)currentLabel.Length, ImGuiInputTextFlags.ReadOnly)
|
||||
: DrawCombo(currentLabel, ref values[valueIdx]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawCombo(string label, ref float currentValue)
|
||||
{
|
||||
using var c = ImRaii.Combo(string.Empty, label);
|
||||
if (!c)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
foreach (var (valueLabel, value, valueDescription) in _values)
|
||||
{
|
||||
if (ImGui.Selectable(valueLabel, value == currentValue))
|
||||
{
|
||||
currentValue = value;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (valueDescription.Length > 0)
|
||||
ImGuiUtil.SelectableHelpMarker(valueDescription);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,293 +1,783 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.MaterialPreview;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
using static Penumbra.GameData.Files.ShpkFile;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private sealed class MtrlTab : IWritable
|
||||
private sealed class MtrlTab : IWritable, IDisposable
|
||||
{
|
||||
private const int ShpkPrefixLength = 16;
|
||||
|
||||
private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk/"u8, true, true, true);
|
||||
|
||||
private readonly ModEditWindow _edit;
|
||||
public readonly MtrlFile Mtrl;
|
||||
public readonly string FilePath;
|
||||
public readonly bool Writable;
|
||||
|
||||
public uint NewKeyId;
|
||||
public uint NewKeyDefault;
|
||||
public uint NewConstantId;
|
||||
public int NewConstantIdx;
|
||||
public uint NewSamplerId;
|
||||
public int NewSamplerIdx;
|
||||
private string[]? _shpkNames;
|
||||
|
||||
public string ShaderHeader = "Shader###Shader";
|
||||
public FullPath LoadedShpkPath = FullPath.Empty;
|
||||
public string LoadedShpkPathName = string.Empty;
|
||||
public string LoadedShpkDevkitPathName = string.Empty;
|
||||
public string ShaderComment = string.Empty;
|
||||
public ShpkFile? AssociatedShpk;
|
||||
public JObject? AssociatedShpkDevkit;
|
||||
|
||||
public ShpkFile? AssociatedShpk;
|
||||
public readonly List< string > TextureLabels = new(4);
|
||||
public FullPath LoadedShpkPath = FullPath.Empty;
|
||||
public string LoadedShpkPathName = string.Empty;
|
||||
public float TextureLabelWidth;
|
||||
public readonly string LoadedBaseDevkitPathName;
|
||||
public readonly JObject? AssociatedBaseDevkit;
|
||||
|
||||
// Shader Key State
|
||||
public readonly List< string > ShaderKeyLabels = new(16);
|
||||
public readonly Dictionary< uint, uint > DefinedShaderKeys = new(16);
|
||||
public readonly List< int > MissingShaderKeyIndices = new(16);
|
||||
public readonly List< uint > AvailableKeyValues = new(16);
|
||||
public string VertexShaders = "Vertex Shaders: ???";
|
||||
public string PixelShaders = "Pixel Shaders: ???";
|
||||
public readonly
|
||||
List<(string Label, int Index, string Description, bool MonoFont, IReadOnlyList<(string Label, uint Value, string Description)>
|
||||
Values)> ShaderKeys = new(16);
|
||||
|
||||
public readonly HashSet<int> VertexShaders = new(16);
|
||||
public readonly HashSet<int> PixelShaders = new(16);
|
||||
public bool ShadersKnown;
|
||||
public string VertexShadersString = "Vertex Shaders: ???";
|
||||
public string PixelShadersString = "Pixel Shaders: ???";
|
||||
|
||||
// Textures & Samplers
|
||||
public readonly List<(string Label, int TextureIndex, int SamplerIndex, string Description, bool MonoFont)> Textures = new(4);
|
||||
|
||||
public readonly HashSet<int> UnfoldedTextures = new(4);
|
||||
public readonly HashSet<uint> SamplerIds = new(16);
|
||||
public float TextureLabelWidth;
|
||||
public bool UseColorDyeSet;
|
||||
|
||||
// Material Constants
|
||||
public readonly List< (string Name, bool ComponentOnly, int ParamValueOffset) > MaterialConstants = new(16);
|
||||
public readonly List< (string Name, uint Id, ushort ByteSize) > MissingMaterialConstants = new(16);
|
||||
public readonly HashSet< uint > DefinedMaterialConstants = new(16);
|
||||
public readonly
|
||||
List<(string Header, List<(string Label, int ConstantIndex, Range Slice, string Description, bool MonoFont, IConstantEditor Editor)>
|
||||
Constants)> Constants = new(16);
|
||||
|
||||
public string MaterialConstantLabel = "Constants###Constants";
|
||||
public IndexSet OrphanedMaterialValues = new(0, false);
|
||||
public int AliasedMaterialValueCount;
|
||||
public bool HasMalformedMaterialConstants;
|
||||
// Live-Previewers
|
||||
public readonly List<LiveMaterialPreviewer> MaterialPreviewers = new(4);
|
||||
public readonly List<LiveColorSetPreviewer> ColorSetPreviewers = new(4);
|
||||
public int HighlightedColorSetRow = -1;
|
||||
public readonly Stopwatch HighlightTime = new();
|
||||
|
||||
// Samplers
|
||||
public readonly List< (string Label, string FileName) > Samplers = new(4);
|
||||
public readonly List< (string Name, uint Id) > MissingSamplers = new(4);
|
||||
public readonly HashSet< uint > DefinedSamplers = new(4);
|
||||
public IndexSet OrphanedSamplers = new(0, false);
|
||||
public int AliasedSamplerCount;
|
||||
|
||||
public FullPath FindAssociatedShpk( out string defaultPath, out Utf8GamePath defaultGamePath )
|
||||
public FullPath FindAssociatedShpk(out string defaultPath, out Utf8GamePath defaultGamePath)
|
||||
{
|
||||
defaultPath = GamePaths.Shader.ShpkPath( Mtrl.ShaderPackage.Name );
|
||||
if( !Utf8GamePath.FromString( defaultPath, out defaultGamePath, true ) )
|
||||
{
|
||||
defaultPath = GamePaths.Shader.ShpkPath(Mtrl.ShaderPackage.Name);
|
||||
if (!Utf8GamePath.FromString(defaultPath, out defaultGamePath, true))
|
||||
return FullPath.Empty;
|
||||
}
|
||||
|
||||
return _edit.FindBestMatch( defaultGamePath );
|
||||
return _edit.FindBestMatch(defaultGamePath);
|
||||
}
|
||||
|
||||
public void LoadShpk( FullPath path )
|
||||
public string[] GetShpkNames()
|
||||
{
|
||||
if (null != _shpkNames)
|
||||
return _shpkNames;
|
||||
|
||||
var names = new HashSet<string>(StandardShaderPackages);
|
||||
names.UnionWith(_edit.FindPathsStartingWith(ShpkPrefix).Select(path => path.ToString()[ShpkPrefixLength..]));
|
||||
|
||||
_shpkNames = names.ToArray();
|
||||
Array.Sort(_shpkNames);
|
||||
|
||||
return _shpkNames;
|
||||
}
|
||||
|
||||
public void LoadShpk(FullPath path)
|
||||
{
|
||||
ShaderHeader = $"Shader ({Mtrl.ShaderPackage.Name})###Shader";
|
||||
|
||||
try
|
||||
{
|
||||
LoadedShpkPath = path;
|
||||
var data = LoadedShpkPath.IsRooted
|
||||
? File.ReadAllBytes( LoadedShpkPath.FullName )
|
||||
: _edit._dalamud.GameData.GetFile( LoadedShpkPath.InternalName.ToString() )?.Data;
|
||||
AssociatedShpk = data?.Length > 0 ? new ShpkFile( data ) : throw new Exception( "Failure to load file data." );
|
||||
? File.ReadAllBytes(LoadedShpkPath.FullName)
|
||||
: _edit._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 )
|
||||
catch (Exception e)
|
||||
{
|
||||
LoadedShpkPath = FullPath.Empty;
|
||||
LoadedShpkPathName = string.Empty;
|
||||
AssociatedShpk = null;
|
||||
Penumbra.Chat.NotificationMessage( $"Could not load {LoadedShpkPath.ToPath()}:\n{e}", "Penumbra Advanced Editing", NotificationType.Error );
|
||||
Penumbra.Chat.NotificationMessage($"Could not load {LoadedShpkPath.ToPath()}:\n{e}", "Penumbra Advanced Editing",
|
||||
NotificationType.Error);
|
||||
}
|
||||
|
||||
if (LoadedShpkPath.InternalName.IsEmpty)
|
||||
{
|
||||
AssociatedShpkDevkit = null;
|
||||
LoadedShpkDevkitPathName = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
AssociatedShpkDevkit =
|
||||
TryLoadShpkDevkit(Path.GetFileNameWithoutExtension(Mtrl.ShaderPackage.Name), out LoadedShpkDevkitPathName);
|
||||
}
|
||||
|
||||
UpdateShaderKeys();
|
||||
Update();
|
||||
}
|
||||
|
||||
public void UpdateTextureLabels()
|
||||
private JObject? TryLoadShpkDevkit(string shpkBaseName, out string devkitPathName)
|
||||
{
|
||||
var samplers = Mtrl.GetSamplersByTexture( AssociatedShpk );
|
||||
TextureLabels.Clear();
|
||||
TextureLabelWidth = 50f * UiHelpers.Scale;
|
||||
using( var _ = ImRaii.PushFont( UiBuilder.MonoFont ) )
|
||||
try
|
||||
{
|
||||
for( var i = 0; i < Mtrl.Textures.Length; ++i )
|
||||
if (!Utf8GamePath.FromString("penumbra/shpk_devkit/" + shpkBaseName + ".json", out var devkitPath))
|
||||
throw new Exception("Could not assemble ShPk dev-kit path.");
|
||||
|
||||
var devkitFullPath = _edit.FindBestMatch(devkitPath);
|
||||
if (!devkitFullPath.IsRooted)
|
||||
throw new Exception("Could not resolve ShPk dev-kit path.");
|
||||
|
||||
devkitPathName = devkitFullPath.FullName;
|
||||
return JObject.Parse(File.ReadAllText(devkitFullPath.FullName));
|
||||
}
|
||||
catch
|
||||
{
|
||||
devkitPathName = string.Empty;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private T? TryGetShpkDevkitData<T>(string category, uint? id, bool mayVary) where T : class
|
||||
=> TryGetShpkDevkitData<T>(AssociatedShpkDevkit, LoadedShpkDevkitPathName, category, id, mayVary)
|
||||
?? TryGetShpkDevkitData<T>(AssociatedBaseDevkit, LoadedBaseDevkitPathName, category, id, mayVary);
|
||||
|
||||
private T? TryGetShpkDevkitData<T>(JObject? devkit, string devkitPathName, string category, uint? id, bool mayVary) where T : class
|
||||
{
|
||||
if (devkit == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var data = devkit[category];
|
||||
if (id.HasValue)
|
||||
data = data?[id.Value.ToString()];
|
||||
|
||||
if (mayVary && (data as JObject)?["Vary"] != null)
|
||||
{
|
||||
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 );
|
||||
var selector = BuildSelector(data["Vary"]!
|
||||
.Select(key => (uint)key)
|
||||
.Select(key => Mtrl.GetShaderKey(key)?.Value ?? AssociatedShpk!.GetMaterialKeyById(key)!.Value.DefaultValue));
|
||||
var index = (int)data["Selectors"]![selector.ToString()]!;
|
||||
data = data["Items"]![index];
|
||||
}
|
||||
|
||||
return data?.ToObject(typeof(T)) as T;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Some element in the JSON was undefined or invalid (wrong type, key that doesn't exist in the ShPk, index out of range, …)
|
||||
Penumbra.Log.Error($"Error while traversing the ShPk dev-kit file at {devkitPathName}: {e}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateShaderKeys()
|
||||
{
|
||||
ShaderKeys.Clear();
|
||||
if (AssociatedShpk != null)
|
||||
foreach (var key in AssociatedShpk.MaterialKeys)
|
||||
{
|
||||
var dkData = TryGetShpkDevkitData<DevkitShaderKey>("ShaderKeys", key.Id, false);
|
||||
var hasDkLabel = !string.IsNullOrEmpty(dkData?.Label);
|
||||
|
||||
var valueSet = new HashSet<uint>(key.Values);
|
||||
if (dkData != null)
|
||||
valueSet.UnionWith(dkData.Values.Keys);
|
||||
|
||||
var mtrlKeyIndex = Mtrl.FindOrAddShaderKey(key.Id, key.DefaultValue);
|
||||
var values = valueSet.Select<uint, (string Label, uint Value, string Description)>(value =>
|
||||
{
|
||||
if (dkData != null && dkData.Values.TryGetValue(value, out var dkValue))
|
||||
return (dkValue.Label.Length > 0 ? dkValue.Label : $"0x{value:X8}", value, dkValue.Description);
|
||||
|
||||
return ($"0x{value:X8}", value, string.Empty);
|
||||
}).ToArray();
|
||||
Array.Sort(values, (x, y) =>
|
||||
{
|
||||
if (x.Value == key.DefaultValue)
|
||||
return -1;
|
||||
if (y.Value == key.DefaultValue)
|
||||
return 1;
|
||||
|
||||
return string.Compare(x.Label, y.Label, StringComparison.Ordinal);
|
||||
});
|
||||
ShaderKeys.Add((hasDkLabel ? dkData!.Label : $"0x{key.Id:X8}", mtrlKeyIndex, dkData?.Description ?? string.Empty,
|
||||
!hasDkLabel, values));
|
||||
}
|
||||
else
|
||||
foreach (var (key, index) in Mtrl.ShaderPackage.ShaderKeys.WithIndex())
|
||||
ShaderKeys.Add(($"0x{key.Category:X8}", index, string.Empty, true, Array.Empty<(string, uint, string)>()));
|
||||
}
|
||||
|
||||
private void UpdateShaders()
|
||||
{
|
||||
VertexShaders.Clear();
|
||||
PixelShaders.Clear();
|
||||
if (AssociatedShpk == null)
|
||||
{
|
||||
ShadersKnown = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShadersKnown = true;
|
||||
var systemKeySelectors = AllSelectors(AssociatedShpk.SystemKeys).ToArray();
|
||||
var sceneKeySelectors = AllSelectors(AssociatedShpk.SceneKeys).ToArray();
|
||||
var subViewKeySelectors = AllSelectors(AssociatedShpk.SubViewKeys).ToArray();
|
||||
var materialKeySelector =
|
||||
BuildSelector(AssociatedShpk.MaterialKeys.Select(key => Mtrl.GetOrAddShaderKey(key.Id, key.DefaultValue).Value));
|
||||
foreach (var systemKeySelector in systemKeySelectors)
|
||||
{
|
||||
foreach (var sceneKeySelector in sceneKeySelectors)
|
||||
{
|
||||
foreach (var subViewKeySelector in subViewKeySelectors)
|
||||
{
|
||||
var selector = BuildSelector(systemKeySelector, sceneKeySelector, materialKeySelector, subViewKeySelector);
|
||||
var node = AssociatedShpk.GetNodeBySelector(selector);
|
||||
if (node.HasValue)
|
||||
foreach (var pass in node.Value.Passes)
|
||||
{
|
||||
VertexShaders.Add((int)pass.VertexShader);
|
||||
PixelShaders.Add((int)pass.PixelShader);
|
||||
}
|
||||
else
|
||||
ShadersKnown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var vertexShaders = VertexShaders.OrderBy(i => i).Select(i => $"#{i}");
|
||||
var pixelShaders = PixelShaders.OrderBy(i => i).Select(i => $"#{i}");
|
||||
|
||||
VertexShadersString = $"Vertex Shaders: {string.Join(", ", ShadersKnown ? vertexShaders : vertexShaders.Append("???"))}";
|
||||
PixelShadersString = $"Pixel Shaders: {string.Join(", ", ShadersKnown ? pixelShaders : pixelShaders.Append("???"))}";
|
||||
|
||||
ShaderComment = TryGetShpkDevkitData<string>("Comment", null, true) ?? string.Empty;
|
||||
}
|
||||
|
||||
private void UpdateTextures()
|
||||
{
|
||||
Textures.Clear();
|
||||
SamplerIds.Clear();
|
||||
if (AssociatedShpk == null)
|
||||
{
|
||||
SamplerIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId));
|
||||
if (Mtrl.ColorSets.Any(c => c.HasRows))
|
||||
SamplerIds.Add(TableSamplerId);
|
||||
|
||||
foreach (var (sampler, index) in Mtrl.ShaderPackage.Samplers.WithIndex())
|
||||
Textures.Add(($"0x{sampler.SamplerId:X8}", sampler.TextureIndex, index, string.Empty, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var index in VertexShaders)
|
||||
SamplerIds.UnionWith(AssociatedShpk.VertexShaders[index].Samplers.Select(sampler => sampler.Id));
|
||||
foreach (var index in PixelShaders)
|
||||
SamplerIds.UnionWith(AssociatedShpk.PixelShaders[index].Samplers.Select(sampler => sampler.Id));
|
||||
if (!ShadersKnown)
|
||||
{
|
||||
SamplerIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId));
|
||||
if (Mtrl.ColorSets.Any(c => c.HasRows))
|
||||
SamplerIds.Add(TableSamplerId);
|
||||
}
|
||||
|
||||
foreach (var samplerId in SamplerIds)
|
||||
{
|
||||
var shpkSampler = AssociatedShpk.GetSamplerById(samplerId);
|
||||
if (shpkSampler is not { Slot: 2 })
|
||||
continue;
|
||||
|
||||
var dkData = TryGetShpkDevkitData<DevkitSampler>("Samplers", samplerId, true);
|
||||
var hasDkLabel = !string.IsNullOrEmpty(dkData?.Label);
|
||||
|
||||
var sampler = Mtrl.GetOrAddSampler(samplerId, dkData?.DefaultTexture ?? string.Empty, out var samplerIndex);
|
||||
Textures.Add((hasDkLabel ? dkData!.Label : shpkSampler.Value.Name, sampler.TextureIndex, samplerIndex,
|
||||
dkData?.Description ?? string.Empty, !hasDkLabel));
|
||||
}
|
||||
|
||||
if (SamplerIds.Contains(TableSamplerId))
|
||||
Mtrl.FindOrAddColorSet();
|
||||
}
|
||||
|
||||
Textures.Sort((x, y) => string.CompareOrdinal(x.Label, y.Label));
|
||||
|
||||
TextureLabelWidth = 50f * UiHelpers.Scale;
|
||||
|
||||
float helpWidth;
|
||||
using (var _ = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
helpWidth = ImGui.GetStyle().ItemSpacing.X + ImGui.CalcTextSize(FontAwesomeIcon.InfoCircle.ToIconString()).X;
|
||||
}
|
||||
|
||||
foreach (var (label, _, _, description, monoFont) in Textures)
|
||||
{
|
||||
if (!monoFont)
|
||||
TextureLabelWidth = Math.Max(TextureLabelWidth, ImGui.CalcTextSize(label).X + (description.Length > 0 ? helpWidth : 0.0f));
|
||||
}
|
||||
|
||||
using (var _ = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
foreach (var (label, _, _, description, monoFont) in Textures)
|
||||
{
|
||||
if (monoFont)
|
||||
TextureLabelWidth = Math.Max(TextureLabelWidth,
|
||||
ImGui.CalcTextSize(label).X + (description.Length > 0 ? helpWidth : 0.0f));
|
||||
}
|
||||
}
|
||||
|
||||
TextureLabelWidth = TextureLabelWidth / UiHelpers.Scale + 4;
|
||||
}
|
||||
|
||||
public void UpdateShaderKeyLabels()
|
||||
private void UpdateConstants()
|
||||
{
|
||||
ShaderKeyLabels.Clear();
|
||||
DefinedShaderKeys.Clear();
|
||||
foreach( var (key, idx) in Mtrl.ShaderPackage.ShaderKeys.WithIndex() )
|
||||
static List<T> FindOrAddGroup<T>(List<(string, List<T>)> groups, string name)
|
||||
{
|
||||
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 != NewKeyId ) )
|
||||
foreach (var (groupName, group) in groups)
|
||||
{
|
||||
var key = AssociatedShpk.MaterialKeys[ MissingShaderKeyIndices[ 0 ] ];
|
||||
NewKeyId = key.Id;
|
||||
NewKeyDefault = key.DefaultValue;
|
||||
if (string.Equals(name, groupName, StringComparison.Ordinal))
|
||||
return group;
|
||||
}
|
||||
|
||||
AvailableKeyValues.AddRange( AssociatedShpk.MaterialKeys.Select( k => DefinedShaderKeys.TryGetValue( k.Id, out var value ) ? value : k.DefaultValue ) );
|
||||
foreach( var node in AssociatedShpk.Nodes )
|
||||
var newGroup = new List<T>(16);
|
||||
groups.Add((name, newGroup));
|
||||
return newGroup;
|
||||
}
|
||||
|
||||
Constants.Clear();
|
||||
if (AssociatedShpk == null)
|
||||
{
|
||||
var fcGroup = FindOrAddGroup(Constants, "Further Constants");
|
||||
foreach (var (constant, index) in Mtrl.ShaderPackage.Constants.WithIndex())
|
||||
{
|
||||
if( node.MaterialKeys.WithIndex().All( key => key.Value == AvailableKeyValues[ key.Index ] ) )
|
||||
var values = Mtrl.GetConstantValues(constant);
|
||||
for (var i = 0; i < values.Length; i += 4)
|
||||
{
|
||||
foreach( var pass in node.Passes )
|
||||
fcGroup.Add(($"0x{constant.Id:X8}", index, i..Math.Min(i + 4, values.Length), string.Empty, true,
|
||||
FloatConstantEditor.Default));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var prefix = AssociatedShpk.GetConstantById(MaterialParamsConstantId)?.Name ?? string.Empty;
|
||||
foreach (var shpkConstant in AssociatedShpk.MaterialParams)
|
||||
{
|
||||
if ((shpkConstant.ByteSize & 0x3) != 0)
|
||||
continue;
|
||||
|
||||
var constant = Mtrl.GetOrAddConstant(shpkConstant.Id, shpkConstant.ByteSize >> 2, out var constantIndex);
|
||||
var values = Mtrl.GetConstantValues(constant);
|
||||
var handledElements = new IndexSet(values.Length, false);
|
||||
|
||||
var dkData = TryGetShpkDevkitData<DevkitConstant[]>("Constants", shpkConstant.Id, true);
|
||||
if (dkData != null)
|
||||
foreach (var dkConstant in dkData)
|
||||
{
|
||||
vertexShaders.Add( ( int )pass.VertexShader );
|
||||
pixelShaders.Add( ( int )pass.PixelShader );
|
||||
var offset = (int)dkConstant.Offset;
|
||||
var length = values.Length - offset;
|
||||
if (dkConstant.Length.HasValue)
|
||||
length = Math.Min(length, (int)dkConstant.Length.Value);
|
||||
if (length <= 0)
|
||||
continue;
|
||||
|
||||
var editor = dkConstant.CreateEditor();
|
||||
if (editor != null)
|
||||
FindOrAddGroup(Constants, dkConstant.Group.Length > 0 ? dkConstant.Group : "Further Constants")
|
||||
.Add((dkConstant.Label, constantIndex, offset..(offset + length), dkConstant.Description, false, editor));
|
||||
handledElements.AddRange(offset, length);
|
||||
}
|
||||
|
||||
var fcGroup = FindOrAddGroup(Constants, "Further Constants");
|
||||
foreach (var (start, end) in handledElements.Ranges(true))
|
||||
{
|
||||
if ((shpkConstant.ByteOffset & 0x3) == 0)
|
||||
{
|
||||
var offset = shpkConstant.ByteOffset >> 2;
|
||||
for (int i = (start & ~0x3) - (offset & 0x3), j = offset >> 2; i < end; i += 4, ++j)
|
||||
{
|
||||
var rangeStart = Math.Max(i, start);
|
||||
var rangeEnd = Math.Min(i + 4, end);
|
||||
if (rangeEnd > rangeStart)
|
||||
fcGroup.Add((
|
||||
$"{prefix}[{j:D2}]{VectorSwizzle((offset + rangeStart) & 0x3, (offset + rangeEnd - 1) & 0x3)} (0x{shpkConstant.Id:X8})",
|
||||
constantIndex, rangeStart..rangeEnd, string.Empty, true, FloatConstantEditor.Default));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = start; i < end; i += 4)
|
||||
{
|
||||
fcGroup.Add(($"0x{shpkConstant.Id:X8}", constantIndex, i..Math.Min(i + 4, end), string.Empty, true,
|
||||
FloatConstantEditor.Default));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 UpdateConstantLabels()
|
||||
{
|
||||
var prefix = AssociatedShpk?.GetConstantById( MaterialParamsConstantId )?.Name ?? string.Empty;
|
||||
MaterialConstantLabel = prefix.Length == 0 ? "Constants###Constants" : prefix + "###Constants";
|
||||
|
||||
DefinedMaterialConstants.Clear();
|
||||
MaterialConstants.Clear();
|
||||
HasMalformedMaterialConstants = false;
|
||||
AliasedMaterialValueCount = 0;
|
||||
OrphanedMaterialValues = new IndexSet( Mtrl.ShaderPackage.ShaderValues.Length, true );
|
||||
foreach( var (constant, idx) in Mtrl.ShaderPackage.Constants.WithIndex() )
|
||||
Constants.RemoveAll(group => group.Constants.Count == 0);
|
||||
Constants.Sort((x, y) =>
|
||||
{
|
||||
DefinedMaterialConstants.Add( constant.Id );
|
||||
var values = Mtrl.GetConstantValues( constant );
|
||||
var paramValueOffset = -values.Length;
|
||||
if( values.Length > 0 )
|
||||
{
|
||||
var shpkParam = AssociatedShpk?.GetMaterialParamById( constant.Id );
|
||||
var paramByteOffset = shpkParam?.ByteOffset ?? -1;
|
||||
if( ( paramByteOffset & 0x3 ) == 0 )
|
||||
{
|
||||
paramValueOffset = paramByteOffset >> 2;
|
||||
}
|
||||
if (string.Equals(x.Header, "Further Constants", StringComparison.Ordinal))
|
||||
return 1;
|
||||
if (string.Equals(y.Header, "Further Constants", StringComparison.Ordinal))
|
||||
return -1;
|
||||
|
||||
var unique = OrphanedMaterialValues.RemoveRange( constant.ByteOffset >> 2, values.Length );
|
||||
AliasedMaterialValueCount += values.Length - unique;
|
||||
}
|
||||
else
|
||||
{
|
||||
HasMalformedMaterialConstants = true;
|
||||
}
|
||||
|
||||
var (name, componentOnly) = MaterialParamRangeName( prefix, paramValueOffset, values.Length );
|
||||
var label = name == null
|
||||
? $"#{idx:D2} (ID: 0x{constant.Id:X8})###{constant.Id}"
|
||||
: $"#{idx:D2}: {name} (ID: 0x{constant.Id:X8})###{constant.Id}";
|
||||
|
||||
MaterialConstants.Add( ( label, componentOnly, paramValueOffset ) );
|
||||
}
|
||||
|
||||
MissingMaterialConstants.Clear();
|
||||
if( AssociatedShpk != null )
|
||||
return string.Compare(x.Header, y.Header, StringComparison.Ordinal);
|
||||
});
|
||||
// HACK the Replace makes w appear after xyz, for the cbuffer-location-based naming scheme
|
||||
foreach (var (_, group) in Constants)
|
||||
{
|
||||
var setIdx = false;
|
||||
foreach( var param in AssociatedShpk.MaterialParams.Where( m => !DefinedMaterialConstants.Contains( m.Id ) ) )
|
||||
{
|
||||
var (name, _) = MaterialParamRangeName( prefix, param.ByteOffset >> 2, param.ByteSize >> 2 );
|
||||
var label = name == null
|
||||
? $"(ID: 0x{param.Id:X8})"
|
||||
: $"{name} (ID: 0x{param.Id:X8})";
|
||||
if( NewConstantId == param.Id )
|
||||
{
|
||||
setIdx = true;
|
||||
NewConstantIdx = MissingMaterialConstants.Count;
|
||||
}
|
||||
|
||||
MissingMaterialConstants.Add( ( label, param.Id, param.ByteSize ) );
|
||||
}
|
||||
|
||||
if( !setIdx && MissingMaterialConstants.Count > 0 )
|
||||
{
|
||||
NewConstantIdx = 0;
|
||||
NewConstantId = MissingMaterialConstants[ 0 ].Id;
|
||||
}
|
||||
group.Sort((x, y) => string.CompareOrdinal(
|
||||
x.MonoFont ? x.Label.Replace("].w", "].{") : x.Label,
|
||||
y.MonoFont ? y.Label.Replace("].w", "].{") : y.Label));
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSamplers()
|
||||
public unsafe void BindToMaterialInstances()
|
||||
{
|
||||
Samplers.Clear();
|
||||
DefinedSamplers.Clear();
|
||||
OrphanedSamplers = new IndexSet( Mtrl.Textures.Length, true );
|
||||
foreach( var (sampler, idx) in Mtrl.ShaderPackage.Samplers.WithIndex() )
|
||||
{
|
||||
DefinedSamplers.Add( sampler.SamplerId );
|
||||
if( !OrphanedSamplers.Remove( sampler.TextureIndex ) )
|
||||
{
|
||||
++AliasedSamplerCount;
|
||||
}
|
||||
UnbindFromMaterialInstances();
|
||||
|
||||
var shpk = AssociatedShpk?.GetSamplerById( sampler.SamplerId );
|
||||
var label = shpk.HasValue
|
||||
? $"#{idx}: {shpk.Value.Name} (ID: 0x{sampler.SamplerId:X8})##{sampler.SamplerId}"
|
||||
: $"#{idx} (ID: 0x{sampler.SamplerId:X8})##{sampler.SamplerId}";
|
||||
var fileName = $"Texture #{sampler.TextureIndex} - {Path.GetFileName( Mtrl.Textures[ sampler.TextureIndex ].Path )}";
|
||||
Samplers.Add( ( label, fileName ) );
|
||||
var instances = MaterialInfo.FindMaterials(_edit._dalamud.Objects, FilePath);
|
||||
|
||||
var foundMaterials = new HashSet<nint>();
|
||||
foreach (var materialInfo in instances)
|
||||
{
|
||||
var drawObject = (CharacterBase*)MaterialInfo.GetDrawObject(materialInfo.Type, _edit._dalamud.Objects);
|
||||
var material = materialInfo.GetDrawObjectMaterial(drawObject);
|
||||
if (foundMaterials.Contains((nint)material))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
MaterialPreviewers.Add(new LiveMaterialPreviewer(_edit._dalamud.Objects, materialInfo));
|
||||
foundMaterials.Add((nint)material);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Carry on without that previewer.
|
||||
}
|
||||
}
|
||||
|
||||
MissingSamplers.Clear();
|
||||
if( AssociatedShpk != null )
|
||||
UpdateMaterialPreview();
|
||||
|
||||
var colorSet = Mtrl.ColorSets.FirstOrNull(colorSet => colorSet.HasRows);
|
||||
|
||||
if (!colorSet.HasValue)
|
||||
return;
|
||||
|
||||
foreach (var materialInfo in instances)
|
||||
{
|
||||
var setSampler = false;
|
||||
foreach( var sampler in AssociatedShpk.Samplers.Where( s => s.Slot == 2 && !DefinedSamplers.Contains( s.Id ) ) )
|
||||
try
|
||||
{
|
||||
if( sampler.Id == NewSamplerId )
|
||||
{
|
||||
setSampler = true;
|
||||
NewSamplerIdx = MissingSamplers.Count;
|
||||
}
|
||||
|
||||
MissingSamplers.Add( ( sampler.Name, sampler.Id ) );
|
||||
ColorSetPreviewers.Add(new LiveColorSetPreviewer(_edit._dalamud.Objects, _edit._dalamud.Framework, materialInfo));
|
||||
}
|
||||
|
||||
if( !setSampler && MissingSamplers.Count > 0 )
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
NewSamplerIdx = 0;
|
||||
NewSamplerId = MissingSamplers[ 0 ].Id;
|
||||
// Carry on without that previewer.
|
||||
}
|
||||
}
|
||||
|
||||
UpdateColorSetPreview();
|
||||
}
|
||||
|
||||
private void UnbindFromMaterialInstances()
|
||||
{
|
||||
foreach (var previewer in MaterialPreviewers)
|
||||
previewer.Dispose();
|
||||
MaterialPreviewers.Clear();
|
||||
|
||||
foreach (var previewer in ColorSetPreviewers)
|
||||
previewer.Dispose();
|
||||
ColorSetPreviewers.Clear();
|
||||
}
|
||||
|
||||
private unsafe void UnbindFromDrawObjectMaterialInstances(nint characterBase)
|
||||
{
|
||||
for (var i = MaterialPreviewers.Count; i-- > 0;)
|
||||
{
|
||||
var previewer = MaterialPreviewers[i];
|
||||
if ((nint)previewer.DrawObject != characterBase)
|
||||
continue;
|
||||
|
||||
previewer.Dispose();
|
||||
MaterialPreviewers.RemoveAt(i);
|
||||
}
|
||||
|
||||
for (var i = ColorSetPreviewers.Count; i-- > 0;)
|
||||
{
|
||||
var previewer = ColorSetPreviewers[i];
|
||||
if ((nint)previewer.DrawObject != characterBase)
|
||||
continue;
|
||||
|
||||
previewer.Dispose();
|
||||
ColorSetPreviewers.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void UpdateMaterialPreview()
|
||||
{
|
||||
SetShaderPackageFlags(Mtrl.ShaderPackage.Flags);
|
||||
foreach (var constant in Mtrl.ShaderPackage.Constants)
|
||||
{
|
||||
var values = Mtrl.GetConstantValues(constant);
|
||||
if (values != null)
|
||||
SetMaterialParameter(constant.Id, 0, values);
|
||||
}
|
||||
|
||||
foreach (var sampler in Mtrl.ShaderPackage.Samplers)
|
||||
SetSamplerFlags(sampler.SamplerId, sampler.Flags);
|
||||
}
|
||||
|
||||
public void HighlightColorSetRow(int rowIdx)
|
||||
{
|
||||
var oldRowIdx = HighlightedColorSetRow;
|
||||
|
||||
if (HighlightedColorSetRow != rowIdx)
|
||||
{
|
||||
HighlightedColorSetRow = rowIdx;
|
||||
HighlightTime.Restart();
|
||||
}
|
||||
|
||||
if (oldRowIdx >= 0)
|
||||
UpdateColorSetRowPreview(oldRowIdx);
|
||||
if (rowIdx >= 0)
|
||||
UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
public void CancelColorSetHighlight()
|
||||
{
|
||||
var rowIdx = HighlightedColorSetRow;
|
||||
|
||||
HighlightedColorSetRow = -1;
|
||||
HighlightTime.Reset();
|
||||
|
||||
if (rowIdx >= 0)
|
||||
UpdateColorSetRowPreview(rowIdx);
|
||||
}
|
||||
|
||||
public 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 && UseColorDyeSet)
|
||||
{
|
||||
var stm = _edit._stainService.StmFile;
|
||||
var dye = maybeColorDyeSet.Value.Rows[rowIdx];
|
||||
if (stm.TryGetValue(dye.Template, _edit._stainService.StainCombo.CurrentSelection.Key, out var dyes))
|
||||
row.ApplyDyeTemplate(dye, dyes);
|
||||
}
|
||||
|
||||
if (HighlightedColorSetRow == rowIdx)
|
||||
ApplyHighlight(ref row, (float)HighlightTime.Elapsed.TotalSeconds);
|
||||
|
||||
foreach (var previewer in ColorSetPreviewers)
|
||||
{
|
||||
row.AsHalves().CopyTo(previewer.ColorSet.AsSpan()
|
||||
.Slice(LiveColorSetPreviewer.TextureWidth * 4 * rowIdx, LiveColorSetPreviewer.TextureWidth * 4));
|
||||
previewer.ScheduleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public 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 && UseColorDyeSet)
|
||||
{
|
||||
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))
|
||||
row.ApplyDyeTemplate(dye, dyes);
|
||||
}
|
||||
}
|
||||
|
||||
if (HighlightedColorSetRow >= 0)
|
||||
ApplyHighlight(ref rows[HighlightedColorSetRow], (float)HighlightTime.Elapsed.TotalSeconds);
|
||||
|
||||
foreach (var previewer in ColorSetPreviewers)
|
||||
{
|
||||
rows.AsHalves().CopyTo(previewer.ColorSet);
|
||||
previewer.ScheduleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyHighlight(ref MtrlFile.ColorSet.Row row, float time)
|
||||
{
|
||||
var level = Math.Sin(time * 2.0 * Math.PI) * 0.25 + 0.5;
|
||||
var levelSq = (float)(level * level);
|
||||
|
||||
row.Diffuse = Vector3.Zero;
|
||||
row.Specular = Vector3.Zero;
|
||||
row.Emissive = new Vector3(levelSq);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
UpdateTextureLabels();
|
||||
UpdateShaderKeyLabels();
|
||||
UpdateConstantLabels();
|
||||
UpdateSamplers();
|
||||
UpdateShaders();
|
||||
UpdateTextures();
|
||||
UpdateConstants();
|
||||
}
|
||||
|
||||
public MtrlTab( ModEditWindow edit, MtrlFile file )
|
||||
public MtrlTab(ModEditWindow edit, MtrlFile file, string filePath, bool writable)
|
||||
{
|
||||
_edit = edit;
|
||||
Mtrl = file;
|
||||
LoadShpk( FindAssociatedShpk( out _, out _ ) );
|
||||
_edit = edit;
|
||||
Mtrl = file;
|
||||
FilePath = filePath;
|
||||
Writable = writable;
|
||||
UseColorDyeSet = file.ColorDyeSets.Length > 0;
|
||||
AssociatedBaseDevkit = TryLoadShpkDevkit("_base", out LoadedBaseDevkitPathName);
|
||||
LoadShpk(FindAssociatedShpk(out _, out _));
|
||||
if (writable)
|
||||
{
|
||||
_edit._gameEvents.CharacterBaseDestructor += UnbindFromDrawObjectMaterialInstances;
|
||||
BindToMaterialInstances();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UnbindFromMaterialInstances();
|
||||
if (Writable)
|
||||
_edit._gameEvents.CharacterBaseDestructor -= UnbindFromDrawObjectMaterialInstances;
|
||||
}
|
||||
|
||||
public bool Valid
|
||||
=> Mtrl.Valid;
|
||||
=> ShadersKnown && Mtrl.Valid;
|
||||
|
||||
public byte[] Write()
|
||||
=> Mtrl.Write();
|
||||
{
|
||||
var output = Mtrl.Clone();
|
||||
output.GarbageCollect(AssociatedShpk, SamplerIds, UseColorDyeSet);
|
||||
|
||||
return output.Write();
|
||||
}
|
||||
|
||||
private sealed record DevkitShaderKeyValue(string Label = "", string Description = "");
|
||||
|
||||
private sealed class DevkitShaderKey
|
||||
{
|
||||
public string Label = string.Empty;
|
||||
public string Description = string.Empty;
|
||||
public Dictionary<uint, DevkitShaderKeyValue> Values = new();
|
||||
}
|
||||
|
||||
private sealed record DevkitSampler(string Label = "", string Description = "", string DefaultTexture = "");
|
||||
|
||||
private enum DevkitConstantType
|
||||
{
|
||||
Hidden = -1,
|
||||
Float = 0,
|
||||
Integer = 1,
|
||||
Color = 2,
|
||||
Enum = 3,
|
||||
}
|
||||
|
||||
private sealed record DevkitConstantValue(string Label = "", string Description = "", float Value = 0);
|
||||
|
||||
private sealed class DevkitConstant
|
||||
{
|
||||
public uint Offset = 0;
|
||||
public uint? Length = null;
|
||||
public string Group = string.Empty;
|
||||
public string Label = string.Empty;
|
||||
public string Description = string.Empty;
|
||||
public DevkitConstantType Type = DevkitConstantType.Float;
|
||||
|
||||
public float? Minimum = null;
|
||||
public float? Maximum = null;
|
||||
public float? Speed = null;
|
||||
public float RelativeSpeed = 0.0f;
|
||||
public float Factor = 1.0f;
|
||||
public float Bias = 0.0f;
|
||||
public byte Precision = 3;
|
||||
public string Unit = string.Empty;
|
||||
|
||||
public bool SquaredRgb = false;
|
||||
public bool Clamped = false;
|
||||
|
||||
public DevkitConstantValue[] Values = Array.Empty<DevkitConstantValue>();
|
||||
|
||||
public IConstantEditor? CreateEditor()
|
||||
=> Type switch
|
||||
{
|
||||
DevkitConstantType.Hidden => null,
|
||||
DevkitConstantType.Float => new FloatConstantEditor(Minimum, Maximum, Speed ?? 0.1f, RelativeSpeed, Factor, Bias, Precision,
|
||||
Unit),
|
||||
DevkitConstantType.Integer => new IntConstantEditor(ToInteger(Minimum), ToInteger(Maximum), Speed ?? 0.25f, RelativeSpeed,
|
||||
Factor, Bias, Unit),
|
||||
DevkitConstantType.Color => new ColorConstantEditor(SquaredRgb, Clamped),
|
||||
DevkitConstantType.Enum => new EnumConstantEditor(Array.ConvertAll(Values,
|
||||
value => (value.Label, value.Value, value.Description))),
|
||||
_ => FloatConstantEditor.Default,
|
||||
};
|
||||
|
||||
private static int? ToInteger(float? value)
|
||||
=> value.HasValue ? (int)Math.Clamp(MathF.Round(value.Value), int.MinValue, int.MaxValue) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.ImGuiFileDialog;
|
||||
using ImGuiNET;
|
||||
using Lumina.Data.Parsing;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
|
@ -19,37 +15,107 @@ public partial class ModEditWindow
|
|||
{
|
||||
private readonly FileDialogService _fileDialog;
|
||||
|
||||
private bool DrawPackageNameInput(MtrlTab tab, bool disabled)
|
||||
// strings path/to/the.exe | grep --fixed-strings '.shpk' | sort -u | sed -e 's#^shader/sm5/shpk/##'
|
||||
// Apricot shader packages are unlisted because
|
||||
// 1. they cause performance/memory issues when calculating the effective shader set
|
||||
// 2. they probably aren't intended for use with materials anyway
|
||||
private static readonly IReadOnlyList<string> StandardShaderPackages = new[]
|
||||
{
|
||||
var ret = false;
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
if (ImGui.InputText("Shader Package Name", ref tab.Mtrl.ShaderPackage.Name, 63,
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))
|
||||
"3dui.shpk",
|
||||
// "apricot_decal_dummy.shpk",
|
||||
// "apricot_decal_ring.shpk",
|
||||
// "apricot_decal.shpk",
|
||||
// "apricot_lightmodel.shpk",
|
||||
// "apricot_model_dummy.shpk",
|
||||
// "apricot_model_morph.shpk",
|
||||
// "apricot_model.shpk",
|
||||
// "apricot_powder_dummy.shpk",
|
||||
// "apricot_powder.shpk",
|
||||
// "apricot_shape_dummy.shpk",
|
||||
// "apricot_shape.shpk",
|
||||
"bgcolorchange.shpk",
|
||||
"bgcrestchange.shpk",
|
||||
"bgdecal.shpk",
|
||||
"bg.shpk",
|
||||
"bguvscroll.shpk",
|
||||
"channeling.shpk",
|
||||
"characterglass.shpk",
|
||||
"character.shpk",
|
||||
"cloud.shpk",
|
||||
"createviewposition.shpk",
|
||||
"crystal.shpk",
|
||||
"directionallighting.shpk",
|
||||
"directionalshadow.shpk",
|
||||
"grass.shpk",
|
||||
"hair.shpk",
|
||||
"iris.shpk",
|
||||
"lightshaft.shpk",
|
||||
"linelighting.shpk",
|
||||
"planelighting.shpk",
|
||||
"pointlighting.shpk",
|
||||
"river.shpk",
|
||||
"shadowmask.shpk",
|
||||
"skin.shpk",
|
||||
"spotlighting.shpk",
|
||||
"verticalfog.shpk",
|
||||
"water.shpk",
|
||||
"weather.shpk",
|
||||
};
|
||||
|
||||
private enum TextureAddressMode : uint
|
||||
{
|
||||
Wrap = 0,
|
||||
Mirror = 1,
|
||||
Clamp = 2,
|
||||
Border = 3,
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyList<string> TextureAddressModeTooltips = new[]
|
||||
{
|
||||
"Tile the texture at every UV integer junction.\n\nFor example, for U values between 0 and 3, the texture is repeated three times.",
|
||||
"Flip the texture at every UV integer junction.\n\nFor U values between 0 and 1, for example, the texture is addressed normally; between 1 and 2, the texture is mirrored; between 2 and 3, the texture is normal again; and so on.",
|
||||
"Texture coordinates outside the range [0.0, 1.0] are set to the texture color at 0.0 or 1.0, respectively.",
|
||||
"Texture coordinates outside the range [0.0, 1.0] are set to the border color (generally black).",
|
||||
};
|
||||
|
||||
private static bool DrawPackageNameInput(MtrlTab tab, bool disabled)
|
||||
{
|
||||
if (disabled)
|
||||
{
|
||||
ret = true;
|
||||
tab.AssociatedShpk = null;
|
||||
tab.LoadedShpkPath = FullPath.Empty;
|
||||
ImGui.TextUnformatted("Shader Package: " + tab.Mtrl.ShaderPackage.Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemDeactivatedAfterEdit())
|
||||
tab.LoadShpk(tab.FindAssociatedShpk(out _, out _));
|
||||
var ret = false;
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 250.0f);
|
||||
using var c = ImRaii.Combo("Shader Package", tab.Mtrl.ShaderPackage.Name);
|
||||
if (c)
|
||||
foreach (var value in tab.GetShpkNames())
|
||||
{
|
||||
if (ImGui.Selectable(value, value == tab.Mtrl.ShaderPackage.Name))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.Name = value;
|
||||
ret = true;
|
||||
tab.AssociatedShpk = null;
|
||||
tab.LoadedShpkPath = FullPath.Empty;
|
||||
tab.LoadShpk(tab.FindAssociatedShpk(out _, out _));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
if (ImGui.InputInt("Shader Package Flags", ref shpkFlags, 0, 0,
|
||||
var shpkFlags = (int)tab.Mtrl.ShaderPackage.Flags;
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 250.0f);
|
||||
if (!ImGui.InputInt("Shader Flags", ref shpkFlags, 0, 0,
|
||||
ImGuiInputTextFlags.CharsHexadecimal | (disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None)))
|
||||
{
|
||||
file.ShaderPackage.Flags = (uint)shpkFlags;
|
||||
ret = true;
|
||||
}
|
||||
return false;
|
||||
|
||||
return ret;
|
||||
tab.Mtrl.ShaderPackage.Flags = (uint)shpkFlags;
|
||||
tab.SetShaderPackageFlags((uint)shpkFlags);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -58,16 +124,22 @@ public partial class ModEditWindow
|
|||
/// </summary>
|
||||
private void DrawCustomAssociations(MtrlTab tab)
|
||||
{
|
||||
const string tooltip = "Click to copy file path to clipboard.";
|
||||
var text = tab.AssociatedShpk == null
|
||||
? "Associated .shpk file: None"
|
||||
: $"Associated .shpk file: {tab.LoadedShpkPathName}";
|
||||
var devkitText = tab.AssociatedShpkDevkit == null
|
||||
? "Associated dev-kit file: None"
|
||||
: $"Associated dev-kit file: {tab.LoadedShpkDevkitPathName}";
|
||||
var baseDevkitText = tab.AssociatedBaseDevkit == null
|
||||
? "Base dev-kit file: None"
|
||||
: $"Base dev-kit file: {tab.LoadedBaseDevkitPathName}";
|
||||
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
|
||||
if (ImGui.Selectable(text))
|
||||
ImGui.SetClipboardText(tab.LoadedShpkPathName);
|
||||
|
||||
ImGuiUtil.HoverTooltip("Click to copy file path to clipboard.");
|
||||
ImGuiUtil.CopyOnClickSelectable(text, tab.LoadedShpkPathName, tooltip);
|
||||
ImGuiUtil.CopyOnClickSelectable(devkitText, tab.LoadedShpkDevkitPathName, tooltip);
|
||||
ImGuiUtil.CopyOnClickSelectable(baseDevkitText, tab.LoadedBaseDevkitPathName, tooltip);
|
||||
|
||||
if (ImGui.Button("Associate Custom .shpk File"))
|
||||
_fileDialog.OpenFilePicker("Associate Custom .shpk File...", ".shpk", (success, name) =>
|
||||
|
|
@ -93,94 +165,56 @@ public partial class ModEditWindow
|
|||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
}
|
||||
|
||||
|
||||
private static bool DrawShaderKey(MtrlTab tab, bool disabled, ref int idx)
|
||||
{
|
||||
var ret = false;
|
||||
using var t2 = ImRaii.TreeNode(tab.ShaderKeyLabels[idx], disabled ? ImGuiTreeNodeFlags.Leaf : 0);
|
||||
if (!t2 || disabled)
|
||||
return ret;
|
||||
|
||||
var key = tab.Mtrl.ShaderPackage.ShaderKeys[idx];
|
||||
var shpkKey = tab.AssociatedShpk?.GetMaterialKeyById(key.Category);
|
||||
if (shpkKey.HasValue)
|
||||
{
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
using var c = ImRaii.Combo("Value", $"0x{key.Value:X8}");
|
||||
if (c)
|
||||
foreach (var value in shpkKey.Value.Values)
|
||||
{
|
||||
if (ImGui.Selectable($"0x{value:X8}", value == key.Value))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.ShaderKeys[idx].Value = value;
|
||||
ret = true;
|
||||
tab.UpdateShaderKeyLabels();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.Button("Remove Key"))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.ShaderKeys = tab.Mtrl.ShaderPackage.ShaderKeys.RemoveItems(idx--);
|
||||
ret = true;
|
||||
tab.UpdateShaderKeyLabels();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawNewShaderKey(MtrlTab tab)
|
||||
{
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
var ret = false;
|
||||
using (var c = ImRaii.Combo("##NewConstantId", $"ID: 0x{tab.NewKeyId:X8}"))
|
||||
{
|
||||
if (c)
|
||||
foreach (var idx in tab.MissingShaderKeyIndices)
|
||||
{
|
||||
var key = tab.AssociatedShpk!.MaterialKeys[idx];
|
||||
|
||||
if (ImGui.Selectable($"ID: 0x{key.Id:X8}", key.Id == tab.NewKeyId))
|
||||
{
|
||||
tab.NewKeyDefault = key.DefaultValue;
|
||||
tab.NewKeyId = key.Id;
|
||||
ret = true;
|
||||
tab.UpdateShaderKeyLabels();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Add Key"))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.ShaderKeys = tab.Mtrl.ShaderPackage.ShaderKeys.AddItem(new ShaderKey
|
||||
{
|
||||
Category = tab.NewKeyId,
|
||||
Value = tab.NewKeyDefault,
|
||||
});
|
||||
ret = true;
|
||||
tab.UpdateShaderKeyLabels();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialShaderKeys(MtrlTab tab, bool disabled)
|
||||
{
|
||||
if (tab.Mtrl.ShaderPackage.ShaderKeys.Length <= 0
|
||||
&& (disabled || tab.AssociatedShpk == null || tab.AssociatedShpk.MaterialKeys.Length <= 0))
|
||||
return false;
|
||||
|
||||
using var t = ImRaii.TreeNode("Shader Keys");
|
||||
if (!t)
|
||||
if (tab.ShaderKeys.Count == 0)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
for (var idx = 0; idx < tab.Mtrl.ShaderPackage.ShaderKeys.Length; ++idx)
|
||||
ret |= DrawShaderKey(tab, disabled, ref idx);
|
||||
foreach (var (label, index, description, monoFont, values) in tab.ShaderKeys)
|
||||
{
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont, monoFont);
|
||||
ref var key = ref tab.Mtrl.ShaderPackage.ShaderKeys[index];
|
||||
var shpkKey = tab.AssociatedShpk?.GetMaterialKeyById(key.Category);
|
||||
var currentValue = key.Value;
|
||||
var (currentLabel, _, currentDescription) =
|
||||
values.FirstOrNull(v => v.Value == currentValue) ?? ($"0x{currentValue:X8}", currentValue, string.Empty);
|
||||
if (!disabled && shpkKey.HasValue)
|
||||
{
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 250.0f);
|
||||
using (var c = ImRaii.Combo($"##{key.Category:X8}", currentLabel))
|
||||
{
|
||||
if (c)
|
||||
foreach (var (valueLabel, value, valueDescription) in values)
|
||||
{
|
||||
if (ImGui.Selectable(valueLabel, value == currentValue))
|
||||
{
|
||||
key.Value = value;
|
||||
ret = true;
|
||||
tab.Update();
|
||||
}
|
||||
|
||||
if (!disabled && tab.AssociatedShpk != null && tab.MissingShaderKeyIndices.Count != 0)
|
||||
ret |= DrawNewShaderKey(tab);
|
||||
if (valueDescription.Length > 0)
|
||||
ImGuiUtil.SelectableHelpMarker(valueDescription);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (description.Length > 0)
|
||||
ImGuiUtil.LabeledHelpMarker(label, description);
|
||||
else
|
||||
ImGui.TextUnformatted(label);
|
||||
}
|
||||
else if (description.Length > 0 || currentDescription.Length > 0)
|
||||
{
|
||||
ImGuiUtil.LabeledHelpMarker($"{label}: {currentLabel}",
|
||||
description + (description.Length > 0 && currentDescription.Length > 0 ? "\n\n" : string.Empty) + currentDescription);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted($"{label}: {currentLabel}");
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -190,160 +224,65 @@ public partial class ModEditWindow
|
|||
if (tab.AssociatedShpk == null)
|
||||
return;
|
||||
|
||||
ImRaii.TreeNode(tab.VertexShaders, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
ImRaii.TreeNode(tab.PixelShaders, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
}
|
||||
ImRaii.TreeNode(tab.VertexShadersString, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
ImRaii.TreeNode(tab.PixelShadersString, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
|
||||
|
||||
private static bool DrawMaterialConstantValues(MtrlTab tab, bool disabled, ref int idx)
|
||||
{
|
||||
var (name, componentOnly, paramValueOffset) = tab.MaterialConstants[idx];
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
using var t2 = ImRaii.TreeNode(name);
|
||||
if (!t2)
|
||||
return false;
|
||||
|
||||
font.Dispose();
|
||||
|
||||
var constant = tab.Mtrl.ShaderPackage.Constants[idx];
|
||||
var ret = false;
|
||||
var values = tab.Mtrl.GetConstantValues(constant);
|
||||
if (values.Length > 0)
|
||||
if (tab.ShaderComment.Length > 0)
|
||||
{
|
||||
var valueOffset = constant.ByteOffset >> 2;
|
||||
|
||||
for (var valueIdx = 0; valueIdx < values.Length; ++valueIdx)
|
||||
{
|
||||
var paramName = MaterialParamName(componentOnly, paramValueOffset + valueIdx) ?? $"#{valueIdx}";
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
if (ImGui.InputFloat($"{paramName} (at 0x{(valueOffset + valueIdx) << 2:X4})", ref values[valueIdx], 0.0f, 0.0f, "%.3f",
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))
|
||||
{
|
||||
ret = true;
|
||||
tab.UpdateConstantLabels();
|
||||
}
|
||||
}
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
ImGui.TextUnformatted(tab.ShaderComment);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImRaii.TreeNode($"Offset: 0x{constant.ByteOffset:X4}", ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
ImRaii.TreeNode($"Size: 0x{constant.ByteSize:X4}", ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
}
|
||||
|
||||
if (!disabled
|
||||
&& !tab.HasMalformedMaterialConstants
|
||||
&& tab.OrphanedMaterialValues.Count == 0
|
||||
&& tab.AliasedMaterialValueCount == 0
|
||||
&& ImGui.Button("Remove Constant"))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.ShaderValues =
|
||||
tab.Mtrl.ShaderPackage.ShaderValues.RemoveItems(constant.ByteOffset >> 2, constant.ByteSize >> 2);
|
||||
tab.Mtrl.ShaderPackage.Constants = tab.Mtrl.ShaderPackage.Constants.RemoveItems(idx--);
|
||||
for (var i = 0; i < tab.Mtrl.ShaderPackage.Constants.Length; ++i)
|
||||
{
|
||||
if (tab.Mtrl.ShaderPackage.Constants[i].ByteOffset >= constant.ByteOffset)
|
||||
tab.Mtrl.ShaderPackage.Constants[i].ByteOffset -= constant.ByteSize;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
tab.UpdateConstantLabels();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialOrphans(MtrlTab tab, bool disabled)
|
||||
{
|
||||
using var t2 = ImRaii.TreeNode($"Orphan Values ({tab.OrphanedMaterialValues.Count})");
|
||||
if (!t2)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
foreach (var idx in tab.OrphanedMaterialValues)
|
||||
{
|
||||
ImGui.SetNextItemWidth(ImGui.GetFontSize() * 10.0f);
|
||||
if (ImGui.InputFloat($"#{idx} (at 0x{idx << 2:X4})",
|
||||
ref tab.Mtrl.ShaderPackage.ShaderValues[idx], 0.0f, 0.0f, "%.3f",
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))
|
||||
{
|
||||
ret = true;
|
||||
tab.UpdateConstantLabels();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawNewMaterialParam(MtrlTab tab)
|
||||
{
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 450.0f);
|
||||
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
using var c = ImRaii.Combo("##NewConstantId", tab.MissingMaterialConstants[tab.NewConstantIdx].Name);
|
||||
if (c)
|
||||
foreach (var (constant, idx) in tab.MissingMaterialConstants.WithIndex())
|
||||
{
|
||||
if (ImGui.Selectable(constant.Name, constant.Id == tab.NewConstantId))
|
||||
{
|
||||
tab.NewConstantIdx = idx;
|
||||
tab.NewConstantId = constant.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Add Constant"))
|
||||
{
|
||||
var (_, _, byteSize) = tab.MissingMaterialConstants[tab.NewConstantIdx];
|
||||
tab.Mtrl.ShaderPackage.Constants = tab.Mtrl.ShaderPackage.Constants.AddItem(new MtrlFile.Constant
|
||||
{
|
||||
Id = tab.NewConstantId,
|
||||
ByteOffset = (ushort)(tab.Mtrl.ShaderPackage.ShaderValues.Length << 2),
|
||||
ByteSize = byteSize,
|
||||
});
|
||||
tab.Mtrl.ShaderPackage.ShaderValues = tab.Mtrl.ShaderPackage.ShaderValues.AddItem(0.0f, byteSize >> 2);
|
||||
tab.UpdateConstantLabels();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialConstants(MtrlTab tab, bool disabled)
|
||||
{
|
||||
if (tab.Mtrl.ShaderPackage.Constants.Length == 0
|
||||
&& tab.Mtrl.ShaderPackage.ShaderValues.Length == 0
|
||||
&& (disabled || tab.AssociatedShpk == null || tab.AssociatedShpk.MaterialParams.Length == 0))
|
||||
if (tab.Constants.Count == 0)
|
||||
return false;
|
||||
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
using var t = ImRaii.TreeNode(tab.MaterialConstantLabel);
|
||||
if (!t)
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
if (!ImGui.CollapsingHeader("Material Constants"))
|
||||
return false;
|
||||
|
||||
font.Dispose();
|
||||
using var _ = ImRaii.PushId("MaterialConstants");
|
||||
|
||||
var ret = false;
|
||||
for (var idx = 0; idx < tab.Mtrl.ShaderPackage.Constants.Length; ++idx)
|
||||
ret |= DrawMaterialConstantValues(tab, disabled, ref idx);
|
||||
foreach (var (header, group) in tab.Constants)
|
||||
{
|
||||
using var t = ImRaii.TreeNode(header, ImGuiTreeNodeFlags.DefaultOpen);
|
||||
if (!t)
|
||||
continue;
|
||||
|
||||
if (tab.OrphanedMaterialValues.Count > 0)
|
||||
ret |= DrawMaterialOrphans(tab, disabled);
|
||||
else if (!disabled && !tab.HasMalformedMaterialConstants && tab.MissingMaterialConstants.Count > 0)
|
||||
ret |= DrawNewMaterialParam(tab);
|
||||
foreach (var (label, constantIndex, slice, description, monoFont, editor) in group)
|
||||
{
|
||||
var constant = tab.Mtrl.ShaderPackage.Constants[constantIndex];
|
||||
var buffer = tab.Mtrl.GetConstantValues(constant);
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
using var id = ImRaii.PushId($"##{constant.Id:X8}:{slice.Start}");
|
||||
if (editor.Draw(buffer[slice], disabled, 250.0f))
|
||||
{
|
||||
ret = true;
|
||||
tab.SetMaterialParameter(constant.Id, slice.Start, buffer[slice]);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont, monoFont);
|
||||
if (description.Length > 0)
|
||||
ImGuiUtil.LabeledHelpMarker(label, description);
|
||||
else
|
||||
ImGui.TextUnformatted(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialSampler(MtrlTab tab, bool disabled, ref int idx)
|
||||
private static bool DrawMaterialSampler(MtrlTab tab, bool disabled, int textureIdx, int samplerIdx)
|
||||
{
|
||||
var (label, filename) = tab.Samplers[idx];
|
||||
using var tree = ImRaii.TreeNode(label);
|
||||
if (!tree)
|
||||
return false;
|
||||
|
||||
ImRaii.TreeNode(filename, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
var ret = false;
|
||||
var sampler = tab.Mtrl.ShaderPackage.Samplers[idx];
|
||||
var ret = false;
|
||||
ref var texture = ref tab.Mtrl.Textures[textureIdx];
|
||||
ref var sampler = ref tab.Mtrl.ShaderPackage.Samplers[samplerIdx];
|
||||
|
||||
// FIXME this probably doesn't belong here
|
||||
static unsafe bool InputHexUInt16(string label, ref ushort v, ImGuiInputTextFlags flags)
|
||||
|
|
@ -354,125 +293,134 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
if (InputHexUInt16("Texture Flags", ref tab.Mtrl.Textures[sampler.TextureIndex].Flags,
|
||||
static bool ComboTextureAddressMode(string label, ref uint samplerFlags, int bitOffset)
|
||||
{
|
||||
var current = (TextureAddressMode)((samplerFlags >> bitOffset) & 0x3u);
|
||||
using var c = ImRaii.Combo(label, current.ToString());
|
||||
if (!c)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
foreach (var value in Enum.GetValues<TextureAddressMode>())
|
||||
{
|
||||
if (ImGui.Selectable(value.ToString(), value == current))
|
||||
{
|
||||
samplerFlags = (samplerFlags & ~(0x3u << bitOffset)) | ((uint)value << bitOffset);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.SelectableHelpMarker(TextureAddressModeTooltips[(int)value]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
var dx11 = texture.DX11;
|
||||
if (ImGui.Checkbox("Prepend -- to the file name on DirectX 11", ref dx11))
|
||||
{
|
||||
texture.DX11 = dx11;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
|
||||
if (ComboTextureAddressMode("##UAddressMode", ref sampler.Flags, 2))
|
||||
{
|
||||
ret = true;
|
||||
tab.SetSamplerFlags(sampler.SamplerId, sampler.Flags);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiUtil.LabeledHelpMarker("U Address Mode", "Method to use for resolving a U texture coordinate that is outside the 0 to 1 range.");
|
||||
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
|
||||
if (ComboTextureAddressMode("##VAddressMode", ref sampler.Flags, 0))
|
||||
{
|
||||
ret = true;
|
||||
tab.SetSamplerFlags(sampler.SamplerId, sampler.Flags);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiUtil.LabeledHelpMarker("V Address Mode", "Method to use for resolving a V texture coordinate that is outside the 0 to 1 range.");
|
||||
|
||||
var lodBias = ((int)(sampler.Flags << 12) >> 22) / 64.0f;
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
|
||||
if (ImGui.DragFloat("##LoDBias", ref lodBias, 0.1f, -8.0f, 7.984375f))
|
||||
{
|
||||
sampler.Flags = (uint)((sampler.Flags & ~0x000FFC00)
|
||||
| ((uint)((int)Math.Round(Math.Clamp(lodBias, -8.0f, 7.984375f) * 64.0f) & 0x3FF) << 10));
|
||||
ret = true;
|
||||
tab.SetSamplerFlags(sampler.SamplerId, sampler.Flags);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiUtil.LabeledHelpMarker("Level of Detail Bias",
|
||||
"Offset from the calculated mipmap level.\n\nHigher means that the texture will start to lose detail nearer.\nLower means that the texture will keep its detail until farther.");
|
||||
|
||||
var minLod = (int)((sampler.Flags >> 20) & 0xF);
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
|
||||
if (ImGui.DragInt("##MinLoD", ref minLod, 0.1f, 0, 15))
|
||||
{
|
||||
sampler.Flags = (uint)((sampler.Flags & ~0x00F00000) | ((uint)Math.Clamp(minLod, 0, 15) << 20));
|
||||
ret = true;
|
||||
tab.SetSamplerFlags(sampler.SamplerId, sampler.Flags);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiUtil.LabeledHelpMarker("Minimum Level of Detail",
|
||||
"Most detailed mipmap level to use.\n\n0 is the full-sized texture, 1 is the half-sized texture, 2 is the quarter-sized texture, and so on.\n15 will forcibly reduce the texture to its smallest mipmap.");
|
||||
|
||||
using var t = ImRaii.TreeNode("Advanced Settings");
|
||||
if (!t)
|
||||
return ret;
|
||||
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
|
||||
if (InputHexUInt16("Texture Flags", ref texture.Flags,
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))
|
||||
ret = true;
|
||||
|
||||
var samplerFlags = (int)sampler.Flags;
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
|
||||
if (ImGui.InputInt("Sampler Flags", ref samplerFlags, 0, 0,
|
||||
ImGuiInputTextFlags.CharsHexadecimal | (disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None)))
|
||||
{
|
||||
tab.Mtrl.ShaderPackage.Samplers[idx].Flags = (uint)samplerFlags;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (!disabled
|
||||
&& tab.OrphanedSamplers.Count == 0
|
||||
&& tab.AliasedSamplerCount == 0
|
||||
&& ImGui.Button("Remove Sampler"))
|
||||
{
|
||||
tab.Mtrl.Textures = tab.Mtrl.Textures.RemoveItems(sampler.TextureIndex);
|
||||
tab.Mtrl.ShaderPackage.Samplers = tab.Mtrl.ShaderPackage.Samplers.RemoveItems(idx--);
|
||||
for (var i = 0; i < tab.Mtrl.ShaderPackage.Samplers.Length; ++i)
|
||||
{
|
||||
if (tab.Mtrl.ShaderPackage.Samplers[i].TextureIndex >= sampler.TextureIndex)
|
||||
--tab.Mtrl.ShaderPackage.Samplers[i].TextureIndex;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
tab.UpdateSamplers();
|
||||
tab.UpdateTextureLabels();
|
||||
sampler.Flags = (uint)samplerFlags;
|
||||
ret = true;
|
||||
tab.SetSamplerFlags(sampler.SamplerId, (uint)samplerFlags);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialNewSampler(MtrlTab tab)
|
||||
{
|
||||
var (name, id) = tab.MissingSamplers[tab.NewSamplerIdx];
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 450.0f);
|
||||
using (var c = ImRaii.Combo("##NewSamplerId", $"{name} (ID: 0x{id:X8})"))
|
||||
{
|
||||
if (c)
|
||||
foreach (var (sampler, idx) in tab.MissingSamplers.WithIndex())
|
||||
{
|
||||
if (ImGui.Selectable($"{sampler.Name} (ID: 0x{sampler.Id:X8})", sampler.Id == tab.NewSamplerId))
|
||||
{
|
||||
tab.NewSamplerIdx = idx;
|
||||
tab.NewSamplerId = sampler.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (!ImGui.Button("Add Sampler"))
|
||||
return false;
|
||||
|
||||
tab.Mtrl.ShaderPackage.Samplers = tab.Mtrl.ShaderPackage.Samplers.AddItem(new Sampler
|
||||
{
|
||||
SamplerId = tab.NewSamplerId,
|
||||
TextureIndex = (byte)tab.Mtrl.Textures.Length,
|
||||
Flags = 0,
|
||||
});
|
||||
tab.Mtrl.Textures = tab.Mtrl.Textures.AddItem(new MtrlFile.Texture
|
||||
{
|
||||
Path = string.Empty,
|
||||
Flags = 0,
|
||||
});
|
||||
tab.UpdateSamplers();
|
||||
tab.UpdateTextureLabels();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialSamplers(MtrlTab tab, bool disabled)
|
||||
{
|
||||
if (tab.Mtrl.ShaderPackage.Samplers.Length == 0
|
||||
&& tab.Mtrl.Textures.Length == 0
|
||||
&& (disabled || (tab.AssociatedShpk?.Samplers.All(sampler => sampler.Slot != 2) ?? false)))
|
||||
return false;
|
||||
|
||||
using var t = ImRaii.TreeNode("Samplers");
|
||||
if (!t)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
for (var idx = 0; idx < tab.Mtrl.ShaderPackage.Samplers.Length; ++idx)
|
||||
ret |= DrawMaterialSampler(tab, disabled, ref idx);
|
||||
|
||||
if (tab.OrphanedSamplers.Count > 0)
|
||||
{
|
||||
using var t2 = ImRaii.TreeNode($"Orphan Textures ({tab.OrphanedSamplers.Count})");
|
||||
if (t2)
|
||||
foreach (var idx in tab.OrphanedSamplers)
|
||||
{
|
||||
ImRaii.TreeNode($"#{idx}: {Path.GetFileName(tab.Mtrl.Textures[idx].Path)} - {tab.Mtrl.Textures[idx].Flags:X4}",
|
||||
ImGuiTreeNodeFlags.Leaf)
|
||||
.Dispose();
|
||||
}
|
||||
}
|
||||
else if (!disabled && tab.MissingSamplers.Count > 0 && tab.AliasedSamplerCount == 0 && tab.Mtrl.Textures.Length < 255)
|
||||
{
|
||||
ret |= DrawMaterialNewSampler(tab);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawMaterialShaderResources(MtrlTab tab, bool disabled)
|
||||
private bool DrawMaterialShader(MtrlTab tab, bool disabled)
|
||||
{
|
||||
var ret = false;
|
||||
if (!ImGui.CollapsingHeader("Advanced Shader Resources"))
|
||||
return ret;
|
||||
if (ImGui.CollapsingHeader(tab.ShaderHeader))
|
||||
{
|
||||
ret |= DrawPackageNameInput(tab, disabled);
|
||||
ret |= DrawShaderFlagsInput(tab, disabled);
|
||||
DrawCustomAssociations(tab);
|
||||
ret |= DrawMaterialShaderKeys(tab, disabled);
|
||||
DrawMaterialShaders(tab);
|
||||
}
|
||||
|
||||
if (tab.AssociatedShpkDevkit == null)
|
||||
{
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
GC.KeepAlive(tab);
|
||||
|
||||
var textColor = ImGui.GetColorU32(ImGuiCol.Text);
|
||||
var textColorWarning =
|
||||
(textColor & 0xFF000000u)
|
||||
| ((textColor & 0x00FEFEFE) >> 1)
|
||||
| (tab.AssociatedShpk == null ? 0x80u : 0x8080u); // Half red or yellow
|
||||
|
||||
using var c = ImRaii.PushColor(ImGuiCol.Text, textColorWarning);
|
||||
|
||||
ImGui.TextUnformatted(tab.AssociatedShpk == null
|
||||
? "Unable to find a suitable .shpk file for cross-references. Some functionality will be missing."
|
||||
: "No dev-kit file found for this material's shaders. Please install one for optimal editing experience, such as actual constant names instead of hexadecimal identifiers.");
|
||||
}
|
||||
|
||||
ret |= DrawPackageNameInput(tab, disabled);
|
||||
ret |= DrawShaderFlagsInput(tab.Mtrl, disabled);
|
||||
DrawCustomAssociations(tab);
|
||||
ret |= DrawMaterialShaderKeys(tab, disabled);
|
||||
DrawMaterialShaders(tab);
|
||||
ret |= DrawMaterialConstants(tab, disabled);
|
||||
ret |= DrawMaterialSamplers(tab, disabled);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -495,25 +443,25 @@ public partial class ModEditWindow
|
|||
};
|
||||
}
|
||||
|
||||
private static string VectorSwizzle(int firstComponent, int lastComponent)
|
||||
=> (firstComponent, lastComponent) switch
|
||||
{
|
||||
(0, 4) => " ",
|
||||
(0, 0) => ".x ",
|
||||
(0, 1) => ".xy ",
|
||||
(0, 2) => ".xyz ",
|
||||
(0, 3) => " ",
|
||||
(1, 1) => ".y ",
|
||||
(1, 2) => ".yz ",
|
||||
(1, 3) => ".yzw ",
|
||||
(2, 2) => ".z ",
|
||||
(2, 3) => ".zw ",
|
||||
(3, 3) => ".w ",
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
private static (string? Name, bool ComponentOnly) MaterialParamRangeName(string prefix, int valueOffset, int valueLength)
|
||||
{
|
||||
static string VectorSwizzle(int firstComponent, int lastComponent)
|
||||
=> (firstComponent, lastComponent) switch
|
||||
{
|
||||
(0, 4) => " ",
|
||||
(0, 0) => ".x ",
|
||||
(0, 1) => ".xy ",
|
||||
(0, 2) => ".xyz ",
|
||||
(0, 3) => " ",
|
||||
(1, 1) => ".y ",
|
||||
(1, 2) => ".yz ",
|
||||
(1, 3) => ".yzw ",
|
||||
(2, 2) => ".z ",
|
||||
(2, 3) => ".zw ",
|
||||
(3, 3) => ".w ",
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
if (valueLength == 0 || valueOffset < 0)
|
||||
return (null, false);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,189 +6,221 @@ using OtterGui;
|
|||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private readonly FileEditor< MtrlTab > _materialTab;
|
||||
private readonly FileEditor<MtrlTab> _materialTab;
|
||||
|
||||
private bool DrawMaterialPanel( MtrlTab tab, bool disabled )
|
||||
private bool DrawMaterialPanel(MtrlTab tab, bool disabled)
|
||||
{
|
||||
var ret = DrawMaterialTextureChange( tab, disabled );
|
||||
DrawMaterialLivePreviewRebind(tab, disabled);
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawBackFaceAndTransparency( tab.Mtrl, disabled );
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
var ret = DrawBackFaceAndTransparency(tab, disabled);
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawMaterialColorSetChange( tab.Mtrl, disabled );
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
ret |= DrawMaterialShader(tab, disabled);
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawMaterialShaderResources( tab, disabled );
|
||||
ret |= DrawMaterialTextureChange(tab, disabled);
|
||||
ret |= DrawMaterialColorSetChange(tab, disabled);
|
||||
ret |= DrawMaterialConstants(tab, disabled);
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
DrawOtherMaterialDetails( tab.Mtrl, disabled );
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
DrawOtherMaterialDetails(tab.Mtrl, disabled);
|
||||
|
||||
return !disabled && ret;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialTextureChange( MtrlTab tab, bool disabled )
|
||||
private static void DrawMaterialLivePreviewRebind(MtrlTab tab, bool disabled)
|
||||
{
|
||||
var ret = false;
|
||||
using var table = ImRaii.Table( "##Textures", 2 );
|
||||
ImGui.TableSetupColumn( "Path", ImGuiTableColumnFlags.WidthStretch );
|
||||
ImGui.TableSetupColumn( "Name", ImGuiTableColumnFlags.WidthFixed, tab.TextureLabelWidth * UiHelpers.Scale );
|
||||
for( var i = 0; i < tab.Mtrl.Textures.Length; ++i )
|
||||
if (disabled)
|
||||
return;
|
||||
|
||||
if (ImGui.Button("Reload live preview"))
|
||||
tab.BindToMaterialInstances();
|
||||
|
||||
if (tab.MaterialPreviewers.Count != 0 || tab.ColorSetPreviewers.Count != 0)
|
||||
return;
|
||||
|
||||
ImGui.SameLine();
|
||||
using var c = ImRaii.PushColor(ImGuiCol.Text, Colors.RegexWarningBorder);
|
||||
ImGui.TextUnformatted(
|
||||
"The current material has not been found on your character. Please check the Import from Screen tab for more information.");
|
||||
}
|
||||
|
||||
private static bool DrawMaterialTextureChange(MtrlTab tab, bool disabled)
|
||||
{
|
||||
if (tab.Textures.Count == 0)
|
||||
return false;
|
||||
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
if (!ImGui.CollapsingHeader("Textures and Samplers", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
return false;
|
||||
|
||||
var frameHeight = ImGui.GetFrameHeight();
|
||||
var ret = false;
|
||||
using var table = ImRaii.Table("##Textures", 3);
|
||||
|
||||
ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, frameHeight);
|
||||
ImGui.TableSetupColumn("Path", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, tab.TextureLabelWidth * UiHelpers.Scale);
|
||||
foreach (var (label, textureI, samplerI, description, monoFont) in tab.Textures)
|
||||
{
|
||||
using var _ = ImRaii.PushId( i );
|
||||
var tmp = tab.Mtrl.Textures[ i ].Path;
|
||||
using var _ = ImRaii.PushId(samplerI);
|
||||
var tmp = tab.Mtrl.Textures[textureI].Path;
|
||||
var unfolded = tab.UnfoldedTextures.Contains(samplerI);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth( ImGui.GetContentRegionAvail().X );
|
||||
if( ImGui.InputText( string.Empty, ref tmp, Utf8GamePath.MaxGamePathLength,
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None )
|
||||
&& tmp.Length > 0
|
||||
&& tmp != tab.Mtrl.Textures[ i ].Path )
|
||||
if (ImGuiUtil.DrawDisabledButton((unfolded ? FontAwesomeIcon.CaretDown : FontAwesomeIcon.CaretRight).ToIconString(),
|
||||
new Vector2(frameHeight),
|
||||
"Settings for this texture and the associated sampler", false, true))
|
||||
{
|
||||
ret = true;
|
||||
tab.Mtrl.Textures[ i ].Path = tmp;
|
||||
unfolded = !unfolded;
|
||||
if (unfolded)
|
||||
tab.UnfoldedTextures.Add(samplerI);
|
||||
else
|
||||
tab.UnfoldedTextures.Remove(samplerI);
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted( tab.TextureLabels[ i ] );
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
if (ImGui.InputText(string.Empty, ref tmp, Utf8GamePath.MaxGamePathLength,
|
||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None)
|
||||
&& tmp.Length > 0
|
||||
&& tmp != tab.Mtrl.Textures[textureI].Path)
|
||||
{
|
||||
ret = true;
|
||||
tab.Mtrl.Textures[textureI].Path = tmp;
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
using (var font = ImRaii.PushFont(UiBuilder.MonoFont, monoFont))
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
if (description.Length > 0)
|
||||
ImGuiUtil.LabeledHelpMarker(label, description);
|
||||
else
|
||||
ImGui.TextUnformatted(label);
|
||||
}
|
||||
|
||||
if (unfolded)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ret |= DrawMaterialSampler(tab, disabled, textureI, samplerI);
|
||||
ImGui.TableNextColumn();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawBackFaceAndTransparency( MtrlFile file, bool disabled )
|
||||
private static bool DrawBackFaceAndTransparency(MtrlTab tab, bool disabled)
|
||||
{
|
||||
const uint transparencyBit = 0x10;
|
||||
const uint backfaceBit = 0x01;
|
||||
|
||||
var ret = false;
|
||||
|
||||
using var dis = ImRaii.Disabled( disabled );
|
||||
using var dis = ImRaii.Disabled(disabled);
|
||||
|
||||
var tmp = ( file.ShaderPackage.Flags & transparencyBit ) != 0;
|
||||
if( ImGui.Checkbox( "Enable Transparency", ref tmp ) )
|
||||
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;
|
||||
ret = true;
|
||||
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;
|
||||
if( ImGui.Checkbox( "Hide Backfaces", ref tmp ) )
|
||||
ImGui.SameLine(200 * UiHelpers.Scale + ImGui.GetStyle().ItemSpacing.X + ImGui.GetStyle().WindowPadding.X);
|
||||
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;
|
||||
ret = true;
|
||||
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;
|
||||
}
|
||||
|
||||
private static void DrawOtherMaterialDetails( MtrlFile file, bool _ )
|
||||
private static void DrawOtherMaterialDetails(MtrlFile file, bool _)
|
||||
{
|
||||
if( !ImGui.CollapsingHeader( "Further Content" ) )
|
||||
{
|
||||
if (!ImGui.CollapsingHeader("Further Content"))
|
||||
return;
|
||||
|
||||
using (var sets = ImRaii.TreeNode("UV Sets", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
{
|
||||
if (sets)
|
||||
foreach (var set in file.UvSets)
|
||||
ImRaii.TreeNode($"#{set.Index:D2} - {set.Name}", ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
}
|
||||
|
||||
using( var sets = ImRaii.TreeNode( "UV Sets", ImGuiTreeNodeFlags.DefaultOpen ) )
|
||||
{
|
||||
if( sets )
|
||||
{
|
||||
foreach( var set in file.UvSets )
|
||||
{
|
||||
ImRaii.TreeNode( $"#{set.Index:D2} - {set.Name}", ImGuiTreeNodeFlags.Leaf ).Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( file.AdditionalData.Length <= 0 )
|
||||
{
|
||||
if (file.AdditionalData.Length <= 0)
|
||||
return;
|
||||
}
|
||||
|
||||
using var t = ImRaii.TreeNode( $"Additional Data (Size: {file.AdditionalData.Length})###AdditionalData" );
|
||||
if( t )
|
||||
{
|
||||
ImGuiUtil.TextWrapped( string.Join( ' ', file.AdditionalData.Select( c => $"{c:X2}" ) ) );
|
||||
}
|
||||
using var t = ImRaii.TreeNode($"Additional Data (Size: {file.AdditionalData.Length})###AdditionalData");
|
||||
if (t)
|
||||
ImGuiUtil.TextWrapped(string.Join(' ', file.AdditionalData.Select(c => $"{c:X2}")));
|
||||
}
|
||||
|
||||
private void DrawMaterialReassignmentTab()
|
||||
{
|
||||
if( _editor.Files.Mdl.Count == 0 )
|
||||
{
|
||||
if (_editor.Files.Mdl.Count == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
using var tab = ImRaii.TabItem( "Material Reassignment" );
|
||||
if( !tab )
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Material Reassignment");
|
||||
if (!tab)
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.NewLine();
|
||||
MaterialSuffix.Draw( _editor, ImGuiHelpers.ScaledVector2( 175, 0 ) );
|
||||
MaterialSuffix.Draw(_editor, ImGuiHelpers.ScaledVector2(175, 0));
|
||||
|
||||
ImGui.NewLine();
|
||||
using var child = ImRaii.Child( "##mdlFiles", -Vector2.One, true );
|
||||
if( !child )
|
||||
{
|
||||
using var child = ImRaii.Child("##mdlFiles", -Vector2.One, true);
|
||||
if (!child)
|
||||
return;
|
||||
}
|
||||
|
||||
using var table = ImRaii.Table( "##files", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.One );
|
||||
if( !table )
|
||||
{
|
||||
using var table = ImRaii.Table("##files", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.One);
|
||||
if (!table)
|
||||
return;
|
||||
}
|
||||
|
||||
var iconSize = ImGui.GetFrameHeight() * Vector2.One;
|
||||
foreach( var (info, idx) in _editor.MdlMaterialEditor.ModelFiles.WithIndex() )
|
||||
foreach (var (info, idx) in _editor.MdlMaterialEditor.ModelFiles.WithIndex())
|
||||
{
|
||||
using var id = ImRaii.PushId( idx );
|
||||
using var id = ImRaii.PushId(idx);
|
||||
ImGui.TableNextColumn();
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Save.ToIconString(), iconSize,
|
||||
"Save the changed mdl file.\nUse at own risk!", !info.Changed, true ) )
|
||||
{
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Save.ToIconString(), iconSize,
|
||||
"Save the changed mdl file.\nUse at own risk!", !info.Changed, true))
|
||||
info.Save();
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Recycle.ToIconString(), iconSize,
|
||||
"Restore current changes to default.", !info.Changed, true ) )
|
||||
{
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Recycle.ToIconString(), iconSize,
|
||||
"Restore current changes to default.", !info.Changed, true))
|
||||
info.Restore();
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( info.Path.FullName[ ( _mod!.ModPath.FullName.Length + 1 ).. ] );
|
||||
ImGui.TextUnformatted(info.Path.FullName[(_mod!.ModPath.FullName.Length + 1)..]);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth( 400 * UiHelpers.Scale );
|
||||
var tmp = info.CurrentMaterials[ 0 ];
|
||||
if( ImGui.InputText( "##0", ref tmp, 64 ) )
|
||||
{
|
||||
info.SetMaterial( tmp, 0 );
|
||||
}
|
||||
ImGui.SetNextItemWidth(400 * UiHelpers.Scale);
|
||||
var tmp = info.CurrentMaterials[0];
|
||||
if (ImGui.InputText("##0", ref tmp, 64))
|
||||
info.SetMaterial(tmp, 0);
|
||||
|
||||
for( var i = 1; i < info.Count; ++i )
|
||||
for (var i = 1; i < info.Count; ++i)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth( 400 * UiHelpers.Scale );
|
||||
tmp = info.CurrentMaterials[ i ];
|
||||
if( ImGui.InputText( $"##{i}", ref tmp, 64 ) )
|
||||
{
|
||||
info.SetMaterial( tmp, i );
|
||||
}
|
||||
ImGui.SetNextItemWidth(400 * UiHelpers.Scale);
|
||||
tmp = info.CurrentMaterials[i];
|
||||
if (ImGui.InputText($"##{i}", ref tmp, 64))
|
||||
info.SetMaterial(tmp, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using Penumbra.GameData.Files;
|
|||
using Penumbra.String.Classes;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Penumbra.UI.AdvancedWindow;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,33 +14,38 @@ using Penumbra.GameData;
|
|||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.String;
|
||||
using Penumbra.UI.AdvancedWindow;
|
||||
using static Penumbra.GameData.Files.ShpkFile;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private static readonly ByteString DisassemblyLabel = ByteString.FromSpanUnsafe( "##disassembly"u8, true, true, true );
|
||||
private static readonly ByteString DisassemblyLabel = ByteString.FromSpanUnsafe("##disassembly"u8, true, true, true);
|
||||
|
||||
private readonly FileEditor< ShpkTab > _shaderPackageTab;
|
||||
private readonly FileEditor<ShpkTab> _shaderPackageTab;
|
||||
|
||||
private static bool DrawShaderPackagePanel( ShpkTab file, bool disabled )
|
||||
private static bool DrawShaderPackagePanel(ShpkTab file, bool disabled)
|
||||
{
|
||||
DrawShaderPackageSummary( file );
|
||||
DrawShaderPackageSummary(file);
|
||||
|
||||
var ret = false;
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawShaderPackageShaderArray( file, "Vertex Shader", file.Shpk.VertexShaders, disabled );
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
ret |= DrawShaderPackageShaderArray(file, "Vertex Shader", file.Shpk.VertexShaders, disabled);
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawShaderPackageShaderArray( file, "Pixel Shader", file.Shpk.PixelShaders, disabled );
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
ret |= DrawShaderPackageShaderArray(file, "Pixel Shader", file.Shpk.PixelShaders, disabled);
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawShaderPackageMaterialParamLayout( file, disabled );
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
ret |= DrawShaderPackageMaterialParamLayout(file, disabled);
|
||||
|
||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||
ret |= DrawOtherShaderPackageDetails( file, disabled );
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
ret |= DrawShaderPackageResources(file, disabled);
|
||||
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
DrawShaderPackageSelection(file);
|
||||
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
DrawOtherShaderPackageDetails(file);
|
||||
|
||||
file.FileDialog.Draw();
|
||||
|
||||
|
|
@ -49,17 +54,26 @@ public partial class ModEditWindow
|
|||
return !disabled && ret;
|
||||
}
|
||||
|
||||
private static void DrawShaderPackageSummary( ShpkTab tab )
|
||||
=> ImGui.TextUnformatted( tab.Header );
|
||||
|
||||
private static void DrawShaderExportButton( ShpkTab tab, string objectName, Shader shader, int idx )
|
||||
private static void DrawShaderPackageSummary(ShpkTab tab)
|
||||
{
|
||||
if( !ImGui.Button( $"Export Shader Program Blob ({shader.Blob.Length} bytes)" ) )
|
||||
ImGui.TextUnformatted(tab.Header);
|
||||
if (!tab.Shpk.Disassembled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var textColor = ImGui.GetColorU32(ImGuiCol.Text);
|
||||
var textColorWarning = (textColor & 0xFF000000u) | ((textColor & 0x00FEFEFE) >> 1) | 0x80u; // Half red
|
||||
|
||||
var defaultName = objectName[ 0 ] switch
|
||||
using var c = ImRaii.PushColor(ImGuiCol.Text, textColorWarning);
|
||||
|
||||
ImGui.TextUnformatted("Your system doesn't support disassembling shaders. Some functionality will be missing.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawShaderExportButton(ShpkTab tab, string objectName, Shader shader, int idx)
|
||||
{
|
||||
if (!ImGui.Button($"Export Shader Program Blob ({shader.Blob.Length} bytes)"))
|
||||
return;
|
||||
|
||||
var defaultName = objectName[0] switch
|
||||
{
|
||||
'V' => $"vs{idx}",
|
||||
'P' => $"ps{idx}",
|
||||
|
|
@ -67,244 +81,225 @@ public partial class ModEditWindow
|
|||
};
|
||||
|
||||
var blob = shader.Blob;
|
||||
tab.FileDialog.OpenSavePicker( $"Export {objectName} #{idx} Program Blob to...", tab.Extension, defaultName, tab.Extension, ( success, name ) =>
|
||||
{
|
||||
if( !success )
|
||||
tab.FileDialog.OpenSavePicker($"Export {objectName} #{idx} Program Blob to...", tab.Extension, defaultName, tab.Extension,
|
||||
(success, name) =>
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes( name, blob );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Chat.NotificationMessage( $"Could not export {defaultName}{tab.Extension} to {name}:\n{e.Message}", "Penumbra Advanced Editing",
|
||||
NotificationType.Error );
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(name, blob);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Chat.NotificationMessage($"Could not export {defaultName}{tab.Extension} to {name}:\n{e.Message}",
|
||||
"Penumbra Advanced Editing",
|
||||
NotificationType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
Penumbra.Chat.NotificationMessage( $"Shader Program Blob {defaultName}{tab.Extension} exported successfully to {Path.GetFileName( name )}",
|
||||
"Penumbra Advanced Editing", NotificationType.Success );
|
||||
}, null, false );
|
||||
Penumbra.Chat.NotificationMessage(
|
||||
$"Shader Program Blob {defaultName}{tab.Extension} exported successfully to {Path.GetFileName(name)}",
|
||||
"Penumbra Advanced Editing", NotificationType.Success);
|
||||
}, null, false);
|
||||
}
|
||||
|
||||
private static void DrawShaderImportButton( ShpkTab tab, string objectName, Shader[] shaders, int idx )
|
||||
private static void DrawShaderImportButton(ShpkTab tab, string objectName, Shader[] shaders, int idx)
|
||||
{
|
||||
if( !ImGui.Button( "Replace Shader Program Blob" ) )
|
||||
{
|
||||
if (!ImGui.Button("Replace Shader Program Blob"))
|
||||
return;
|
||||
}
|
||||
|
||||
tab.FileDialog.OpenFilePicker( $"Replace {objectName} #{idx} Program Blob...", "Shader Program Blobs{.o,.cso,.dxbc,.dxil}", ( success, name ) =>
|
||||
{
|
||||
if( !success )
|
||||
tab.FileDialog.OpenFilePicker($"Replace {objectName} #{idx} Program Blob...", "Shader Program Blobs{.o,.cso,.dxbc,.dxil}",
|
||||
(success, name) =>
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
shaders[ idx ].Blob = File.ReadAllBytes(name[0] );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Chat.NotificationMessage( $"Could not import {name}:\n{e.Message}", "Penumbra Advanced Editing", NotificationType.Error );
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
shaders[idx].Blob = File.ReadAllBytes(name[0]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Chat.NotificationMessage($"Could not import {name}:\n{e.Message}", "Penumbra Advanced Editing",
|
||||
NotificationType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
shaders[ idx ].UpdateResources( tab.Shpk );
|
||||
tab.Shpk.UpdateResources();
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
tab.Shpk.SetInvalid();
|
||||
Penumbra.Chat.NotificationMessage( $"Failed to update resources after importing {name}:\n{e.Message}", "Penumbra Advanced Editing",
|
||||
NotificationType.Error );
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
shaders[idx].UpdateResources(tab.Shpk);
|
||||
tab.Shpk.UpdateResources();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
tab.Shpk.SetInvalid();
|
||||
Penumbra.Chat.NotificationMessage($"Failed to update resources after importing {name}:\n{e.Message}",
|
||||
"Penumbra Advanced Editing",
|
||||
NotificationType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
tab.Shpk.SetChanged();
|
||||
}, 1, null, false );
|
||||
tab.Shpk.SetChanged();
|
||||
}, 1, null, false);
|
||||
}
|
||||
|
||||
private static unsafe void DrawRawDisassembly( Shader shader )
|
||||
private static unsafe void DrawRawDisassembly(Shader shader)
|
||||
{
|
||||
using var t2 = ImRaii.TreeNode( "Raw Program Disassembly" );
|
||||
if( !t2 )
|
||||
{
|
||||
using var t2 = ImRaii.TreeNode("Raw Program Disassembly");
|
||||
if (!t2)
|
||||
return;
|
||||
}
|
||||
|
||||
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
var size = new Vector2( ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight() * 20 );
|
||||
ImGuiNative.igInputTextMultiline( DisassemblyLabel.Path, shader.Disassembly!.RawDisassembly.Path, ( uint )shader.Disassembly!.RawDisassembly.Length + 1, size,
|
||||
ImGuiInputTextFlags.ReadOnly, null, null );
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
var size = new Vector2(ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight() * 20);
|
||||
ImGuiNative.igInputTextMultiline(DisassemblyLabel.Path, shader.Disassembly!.RawDisassembly.Path,
|
||||
(uint)shader.Disassembly!.RawDisassembly.Length + 1, size,
|
||||
ImGuiInputTextFlags.ReadOnly, null, null);
|
||||
}
|
||||
|
||||
private static bool DrawShaderPackageShaderArray( ShpkTab tab, string objectName, Shader[] shaders, bool disabled )
|
||||
private static bool DrawShaderPackageShaderArray(ShpkTab tab, string objectName, Shader[] shaders, bool disabled)
|
||||
{
|
||||
if( shaders.Length == 0 || !ImGui.CollapsingHeader( $"{objectName}s" ) )
|
||||
{
|
||||
if (shaders.Length == 0 || !ImGui.CollapsingHeader($"{objectName}s"))
|
||||
return false;
|
||||
}
|
||||
|
||||
var ret = false;
|
||||
for( var idx = 0; idx < shaders.Length; ++idx )
|
||||
for (var idx = 0; idx < shaders.Length; ++idx)
|
||||
{
|
||||
var shader = shaders[ idx ];
|
||||
using var t = ImRaii.TreeNode( $"{objectName} #{idx}" );
|
||||
if( !t )
|
||||
{
|
||||
var shader = shaders[idx];
|
||||
using var t = ImRaii.TreeNode($"{objectName} #{idx}");
|
||||
if (!t)
|
||||
continue;
|
||||
}
|
||||
|
||||
DrawShaderExportButton( tab, objectName, shader, idx );
|
||||
if( !disabled )
|
||||
DrawShaderExportButton(tab, objectName, shader, idx);
|
||||
if (!disabled && tab.Shpk.Disassembled)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
DrawShaderImportButton( tab, objectName, shaders, idx );
|
||||
DrawShaderImportButton(tab, objectName, shaders, idx);
|
||||
}
|
||||
|
||||
ret |= DrawShaderPackageResourceArray( "Constant Buffers", "slot", true, shader.Constants, true );
|
||||
ret |= DrawShaderPackageResourceArray( "Samplers", "slot", false, shader.Samplers, true );
|
||||
ret |= DrawShaderPackageResourceArray( "Unordered Access Views", "slot", true, shader.Uavs, true );
|
||||
ret |= DrawShaderPackageResourceArray("Constant Buffers", "slot", true, shader.Constants, true);
|
||||
ret |= DrawShaderPackageResourceArray("Samplers", "slot", false, shader.Samplers, true);
|
||||
ret |= DrawShaderPackageResourceArray("Unordered Access Views", "slot", true, shader.Uavs, true);
|
||||
|
||||
if( shader.AdditionalHeader.Length > 0 )
|
||||
if (shader.AdditionalHeader.Length > 0)
|
||||
{
|
||||
using var t2 = ImRaii.TreeNode( $"Additional Header (Size: {shader.AdditionalHeader.Length})###AdditionalHeader" );
|
||||
if( t2 )
|
||||
{
|
||||
ImGuiUtil.TextWrapped( string.Join( ' ', shader.AdditionalHeader.Select( c => $"{c:X2}" ) ) );
|
||||
}
|
||||
using var t2 = ImRaii.TreeNode($"Additional Header (Size: {shader.AdditionalHeader.Length})###AdditionalHeader");
|
||||
if (t2)
|
||||
ImGuiUtil.TextWrapped(string.Join(' ', shader.AdditionalHeader.Select(c => $"{c:X2}")));
|
||||
}
|
||||
|
||||
DrawRawDisassembly( shader );
|
||||
if (tab.Shpk.Disassembled)
|
||||
DrawRawDisassembly(shader);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawShaderPackageResource( string slotLabel, bool withSize, ref Resource resource, bool disabled )
|
||||
private static bool DrawShaderPackageResource(string slotLabel, bool withSize, ref Resource resource, bool disabled)
|
||||
{
|
||||
var ret = false;
|
||||
if( !disabled )
|
||||
if (!disabled)
|
||||
{
|
||||
ImGui.SetNextItemWidth( UiHelpers.Scale * 150.0f );
|
||||
if( ImGuiUtil.InputUInt16( $"{char.ToUpper( slotLabel[ 0 ] )}{slotLabel[ 1.. ].ToLower()}", ref resource.Slot, ImGuiInputTextFlags.None ) )
|
||||
{
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
|
||||
if (ImGuiUtil.InputUInt16($"{char.ToUpper(slotLabel[0])}{slotLabel[1..].ToLower()}", ref resource.Slot, ImGuiInputTextFlags.None))
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
if( resource.Used == null )
|
||||
{
|
||||
if (resource.Used == null)
|
||||
return ret;
|
||||
}
|
||||
|
||||
var usedString = UsedComponentString( withSize, resource );
|
||||
if( usedString.Length > 0 )
|
||||
{
|
||||
ImRaii.TreeNode( $"Used: {usedString}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
}
|
||||
var usedString = UsedComponentString(withSize, resource);
|
||||
if (usedString.Length > 0)
|
||||
ImRaii.TreeNode($"Used: {usedString}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet).Dispose();
|
||||
else
|
||||
{
|
||||
ImRaii.TreeNode( "Unused", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
}
|
||||
ImRaii.TreeNode("Unused", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet).Dispose();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawShaderPackageResourceArray( string arrayName, string slotLabel, bool withSize, Resource[] resources, bool disabled )
|
||||
private static bool DrawShaderPackageResourceArray(string arrayName, string slotLabel, bool withSize, Resource[] resources, bool disabled)
|
||||
{
|
||||
if( resources.Length == 0 )
|
||||
{
|
||||
if (resources.Length == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
using var t = ImRaii.TreeNode( arrayName );
|
||||
if( !t )
|
||||
{
|
||||
using var t = ImRaii.TreeNode(arrayName);
|
||||
if (!t)
|
||||
return false;
|
||||
}
|
||||
|
||||
var ret = false;
|
||||
for( var idx = 0; idx < resources.Length; ++idx )
|
||||
for (var idx = 0; idx < resources.Length; ++idx)
|
||||
{
|
||||
ref var buf = ref resources[ idx ];
|
||||
ref var buf = ref resources[idx];
|
||||
var name = $"#{idx}: {buf.Name} (ID: 0x{buf.Id:X8}), {slotLabel}: {buf.Slot}"
|
||||
+ ( withSize ? $", size: {buf.Size} registers###{idx}: {buf.Name} (ID: 0x{buf.Id:X8})" : string.Empty );
|
||||
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
using var t2 = ImRaii.TreeNode( name, !disabled || buf.Used != null ? 0 : ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet );
|
||||
+ (withSize ? $", size: {buf.Size} registers###{idx}: {buf.Name} (ID: 0x{buf.Id:X8})" : string.Empty);
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
using var t2 = ImRaii.TreeNode(name, !disabled || buf.Used != null ? 0 : ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet);
|
||||
font.Dispose();
|
||||
if( t2 )
|
||||
{
|
||||
ret |= DrawShaderPackageResource( slotLabel, withSize, ref buf, disabled );
|
||||
}
|
||||
if (t2)
|
||||
ret |= DrawShaderPackageResource(slotLabel, withSize, ref buf, disabled);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialParamLayoutHeader( string label )
|
||||
private static bool DrawMaterialParamLayoutHeader(string label)
|
||||
{
|
||||
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
var pos = ImGui.GetCursorScreenPos()
|
||||
+ new Vector2( ImGui.CalcTextSize( label ).X + 3 * ImGui.GetStyle().ItemInnerSpacing.X + ImGui.GetFrameHeight(), ImGui.GetStyle().FramePadding.Y );
|
||||
+ new Vector2(ImGui.CalcTextSize(label).X + 3 * ImGui.GetStyle().ItemInnerSpacing.X + ImGui.GetFrameHeight(),
|
||||
ImGui.GetStyle().FramePadding.Y);
|
||||
|
||||
var ret = ImGui.CollapsingHeader( label );
|
||||
ImGui.GetWindowDrawList().AddText( UiBuilder.DefaultFont, UiBuilder.DefaultFont.FontSize, pos, ImGui.GetColorU32( ImGuiCol.Text ), "Layout" );
|
||||
var ret = ImGui.CollapsingHeader(label);
|
||||
ImGui.GetWindowDrawList()
|
||||
.AddText(UiBuilder.DefaultFont, UiBuilder.DefaultFont.FontSize, pos, ImGui.GetColorU32(ImGuiCol.Text), "Layout");
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMaterialParamLayoutBufferSize( ShpkFile file, Resource? materialParams )
|
||||
private static bool DrawMaterialParamLayoutBufferSize(ShpkFile file, Resource? materialParams)
|
||||
{
|
||||
var isSizeWellDefined = ( file.MaterialParamsSize & 0xF ) == 0 && ( !materialParams.HasValue || file.MaterialParamsSize == materialParams.Value.Size << 4 );
|
||||
if( isSizeWellDefined )
|
||||
{
|
||||
var isSizeWellDefined = (file.MaterialParamsSize & 0xF) == 0
|
||||
&& (!materialParams.HasValue || file.MaterialParamsSize == materialParams.Value.Size << 4);
|
||||
if (isSizeWellDefined)
|
||||
return true;
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted( materialParams.HasValue
|
||||
ImGui.TextUnformatted(materialParams.HasValue
|
||||
? $"Buffer size mismatch: {file.MaterialParamsSize} bytes ≠ {materialParams.Value.Size} registers ({materialParams.Value.Size << 4} bytes)"
|
||||
: $"Buffer size mismatch: {file.MaterialParamsSize} bytes, not a multiple of 16" );
|
||||
: $"Buffer size mismatch: {file.MaterialParamsSize} bytes, not a multiple of 16");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool DrawShaderPackageMaterialMatrix( ShpkTab tab, bool disabled )
|
||||
private static bool DrawShaderPackageMaterialMatrix(ShpkTab tab, bool disabled)
|
||||
{
|
||||
ImGui.TextUnformatted( "Parameter positions (continuations are grayed out, unused values are red):" );
|
||||
ImGui.TextUnformatted(tab.Shpk.Disassembled
|
||||
? "Parameter positions (continuations are grayed out, unused values are red):"
|
||||
: "Parameter positions (continuations are grayed out):");
|
||||
|
||||
using var table = ImRaii.Table( "##MaterialParamLayout", 5,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg );
|
||||
if( !table )
|
||||
{
|
||||
using var table = ImRaii.Table("##MaterialParamLayout", 5,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||
if (!table)
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGui.TableSetupColumn( string.Empty, ImGuiTableColumnFlags.WidthFixed, 25 * UiHelpers.Scale );
|
||||
ImGui.TableSetupColumn( "x", ImGuiTableColumnFlags.WidthFixed, 100 * UiHelpers.Scale );
|
||||
ImGui.TableSetupColumn( "y", ImGuiTableColumnFlags.WidthFixed, 100 * UiHelpers.Scale );
|
||||
ImGui.TableSetupColumn( "z", ImGuiTableColumnFlags.WidthFixed, 100 * UiHelpers.Scale );
|
||||
ImGui.TableSetupColumn( "w", ImGuiTableColumnFlags.WidthFixed, 100 * UiHelpers.Scale );
|
||||
ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, 25 * UiHelpers.Scale);
|
||||
ImGui.TableSetupColumn("x", ImGuiTableColumnFlags.WidthFixed, 100 * UiHelpers.Scale);
|
||||
ImGui.TableSetupColumn("y", ImGuiTableColumnFlags.WidthFixed, 100 * UiHelpers.Scale);
|
||||
ImGui.TableSetupColumn("z", ImGuiTableColumnFlags.WidthFixed, 100 * UiHelpers.Scale);
|
||||
ImGui.TableSetupColumn("w", ImGuiTableColumnFlags.WidthFixed, 100 * UiHelpers.Scale);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
var textColorStart = ImGui.GetColorU32( ImGuiCol.Text );
|
||||
var textColorCont = ( textColorStart & 0x00FFFFFFu ) | ( ( textColorStart & 0xFE000000u ) >> 1 ); // Half opacity
|
||||
var textColorUnusedStart = ( textColorStart & 0xFF000000u ) | ( ( textColorStart & 0x00FEFEFE ) >> 1 ) | 0x80u; // Half red
|
||||
var textColorUnusedCont = ( textColorUnusedStart & 0x00FFFFFFu ) | ( ( textColorUnusedStart & 0xFE000000u ) >> 1 );
|
||||
var textColorStart = ImGui.GetColorU32(ImGuiCol.Text);
|
||||
var textColorCont = (textColorStart & 0x00FFFFFFu) | ((textColorStart & 0xFE000000u) >> 1); // Half opacity
|
||||
var textColorUnusedStart = (textColorStart & 0xFF000000u) | ((textColorStart & 0x00FEFEFE) >> 1) | 0x80u; // Half red
|
||||
var textColorUnusedCont = (textColorUnusedStart & 0x00FFFFFFu) | ((textColorUnusedStart & 0xFE000000u) >> 1);
|
||||
|
||||
var ret = false;
|
||||
for( var i = 0; i < tab.Matrix.GetLength( 0 ); ++i )
|
||||
for (var i = 0; i < tab.Matrix.GetLength(0); ++i)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader( $" [{i}]" );
|
||||
for( var j = 0; j < 4; ++j )
|
||||
ImGui.TableHeader($" [{i}]");
|
||||
for (var j = 0; j < 4; ++j)
|
||||
{
|
||||
var (name, tooltip, idx, colorType) = tab.Matrix[ i, j ];
|
||||
var (name, tooltip, idx, colorType) = tab.Matrix[i, j];
|
||||
var color = colorType switch
|
||||
{
|
||||
ShpkTab.ColorType.Unused => textColorUnusedStart,
|
||||
|
|
@ -313,354 +308,307 @@ public partial class ModEditWindow
|
|||
ShpkTab.ColorType.Continuation | ShpkTab.ColorType.Used => textColorCont,
|
||||
_ => textColorStart,
|
||||
};
|
||||
using var _ = ImRaii.PushId( i * 4 + j );
|
||||
using var _ = ImRaii.PushId(i * 4 + j);
|
||||
var deletable = !disabled && idx >= 0;
|
||||
using( var font = ImRaii.PushFont( UiBuilder.MonoFont, tooltip.Length > 0 ) )
|
||||
using (var font = ImRaii.PushFont(UiBuilder.MonoFont, tooltip.Length > 0))
|
||||
{
|
||||
using( var c = ImRaii.PushColor( ImGuiCol.Text, color ) )
|
||||
using (var c = ImRaii.PushColor(ImGuiCol.Text, color))
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Selectable( name );
|
||||
if( deletable && ImGui.IsItemClicked( ImGuiMouseButton.Right ) && ImGui.GetIO().KeyCtrl )
|
||||
ImGui.Selectable(name);
|
||||
if (deletable && ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl)
|
||||
{
|
||||
tab.Shpk.MaterialParams = tab.Shpk.MaterialParams.RemoveItems( idx );
|
||||
tab.Shpk.MaterialParams = tab.Shpk.MaterialParams.RemoveItems(idx);
|
||||
ret = true;
|
||||
tab.Update();
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( tooltip );
|
||||
ImGuiUtil.HoverTooltip(tooltip);
|
||||
}
|
||||
|
||||
if( deletable )
|
||||
{
|
||||
ImGuiUtil.HoverTooltip( "\nControl + Right-Click to remove." );
|
||||
}
|
||||
if (deletable)
|
||||
ImGuiUtil.HoverTooltip("\nControl + Right-Click to remove.");
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void DrawShaderPackageMisalignedParameters( ShpkTab tab )
|
||||
private static void DrawShaderPackageMisalignedParameters(ShpkTab tab)
|
||||
{
|
||||
using var t = ImRaii.TreeNode( "Misaligned / Overflowing Parameters" );
|
||||
if( !t )
|
||||
{
|
||||
using var t = ImRaii.TreeNode("Misaligned / Overflowing Parameters");
|
||||
if (!t)
|
||||
return;
|
||||
}
|
||||
|
||||
using var _ = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
foreach( var name in tab.MalformedParameters )
|
||||
{
|
||||
ImRaii.TreeNode( name, ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
}
|
||||
using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
foreach (var name in tab.MalformedParameters)
|
||||
ImRaii.TreeNode(name, ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet).Dispose();
|
||||
}
|
||||
|
||||
private static void DrawShaderPackageStartCombo( ShpkTab tab )
|
||||
private static void DrawShaderPackageStartCombo(ShpkTab tab)
|
||||
{
|
||||
using var s = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing );
|
||||
using( var _ = ImRaii.PushFont( UiBuilder.MonoFont ) )
|
||||
using var s = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing);
|
||||
using (var _ = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
ImGui.SetNextItemWidth( UiHelpers.Scale * 400 );
|
||||
using var c = ImRaii.Combo( "##Start", tab.Orphans[ tab.NewMaterialParamStart ].Name );
|
||||
if( c )
|
||||
{
|
||||
foreach( var (start, idx) in tab.Orphans.WithIndex() )
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 400);
|
||||
using var c = ImRaii.Combo("##Start", tab.Orphans[tab.NewMaterialParamStart].Name);
|
||||
if (c)
|
||||
foreach (var (start, idx) in tab.Orphans.WithIndex())
|
||||
{
|
||||
if( ImGui.Selectable( start.Name, idx == tab.NewMaterialParamStart ) )
|
||||
{
|
||||
tab.UpdateOrphanStart( idx );
|
||||
}
|
||||
if (ImGui.Selectable(start.Name, idx == tab.NewMaterialParamStart))
|
||||
tab.UpdateOrphanStart(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted( "Start" );
|
||||
ImGui.TextUnformatted("Start");
|
||||
}
|
||||
|
||||
private static void DrawShaderPackageEndCombo( ShpkTab tab )
|
||||
private static void DrawShaderPackageEndCombo(ShpkTab tab)
|
||||
{
|
||||
using var s = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing );
|
||||
using( var _ = ImRaii.PushFont( UiBuilder.MonoFont ) )
|
||||
using var s = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing);
|
||||
using (var _ = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
ImGui.SetNextItemWidth( UiHelpers.Scale * 400 );
|
||||
using var c = ImRaii.Combo( "##End", tab.Orphans[ tab.NewMaterialParamEnd ].Name );
|
||||
if( c )
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 400);
|
||||
using var c = ImRaii.Combo("##End", tab.Orphans[tab.NewMaterialParamEnd].Name);
|
||||
if (c)
|
||||
{
|
||||
var current = tab.Orphans[ tab.NewMaterialParamStart ].Index;
|
||||
for( var i = tab.NewMaterialParamStart; i < tab.Orphans.Count; ++i )
|
||||
var current = tab.Orphans[tab.NewMaterialParamStart].Index;
|
||||
for (var i = tab.NewMaterialParamStart; i < tab.Orphans.Count; ++i)
|
||||
{
|
||||
var next = tab.Orphans[ i ];
|
||||
if( current++ != next.Index )
|
||||
{
|
||||
var next = tab.Orphans[i];
|
||||
if (current++ != next.Index)
|
||||
break;
|
||||
}
|
||||
|
||||
if( ImGui.Selectable( next.Name, i == tab.NewMaterialParamEnd ) )
|
||||
{
|
||||
if (ImGui.Selectable(next.Name, i == tab.NewMaterialParamEnd))
|
||||
tab.NewMaterialParamEnd = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted( "End" );
|
||||
ImGui.TextUnformatted("End");
|
||||
}
|
||||
|
||||
private static bool DrawShaderPackageNewParameter( ShpkTab tab )
|
||||
private static bool DrawShaderPackageNewParameter(ShpkTab tab)
|
||||
{
|
||||
if( tab.Orphans.Count == 0 )
|
||||
{
|
||||
if (tab.Orphans.Count == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
DrawShaderPackageStartCombo( tab );
|
||||
DrawShaderPackageEndCombo( tab );
|
||||
DrawShaderPackageStartCombo(tab);
|
||||
DrawShaderPackageEndCombo(tab);
|
||||
|
||||
ImGui.SetNextItemWidth( UiHelpers.Scale * 400 );
|
||||
if( ImGui.InputText( "Name", ref tab.NewMaterialParamName, 63 ) )
|
||||
{
|
||||
tab.NewMaterialParamId = Crc32.Get( tab.NewMaterialParamName, 0xFFFFFFFFu );
|
||||
}
|
||||
ImGui.SetNextItemWidth(UiHelpers.Scale * 400);
|
||||
if (ImGui.InputText("Name", ref tab.NewMaterialParamName, 63))
|
||||
tab.NewMaterialParamId = Crc32.Get(tab.NewMaterialParamName, 0xFFFFFFFFu);
|
||||
|
||||
var tooltip = tab.UsedIds.Contains( tab.NewMaterialParamId )
|
||||
var tooltip = tab.UsedIds.Contains(tab.NewMaterialParamId)
|
||||
? "The ID is already in use. Please choose a different name."
|
||||
: string.Empty;
|
||||
if( !ImGuiUtil.DrawDisabledButton( $"Add ID 0x{tab.NewMaterialParamId:X8}", new Vector2( 400 * UiHelpers.Scale, ImGui.GetFrameHeight() ), tooltip,
|
||||
tooltip.Length > 0 ) )
|
||||
{
|
||||
if (!ImGuiUtil.DrawDisabledButton($"Add ID 0x{tab.NewMaterialParamId:X8}", new Vector2(400 * UiHelpers.Scale, ImGui.GetFrameHeight()),
|
||||
tooltip,
|
||||
tooltip.Length > 0))
|
||||
return false;
|
||||
}
|
||||
|
||||
tab.Shpk.MaterialParams = tab.Shpk.MaterialParams.AddItem( new MaterialParam
|
||||
tab.Shpk.MaterialParams = tab.Shpk.MaterialParams.AddItem(new MaterialParam
|
||||
{
|
||||
Id = tab.NewMaterialParamId,
|
||||
ByteOffset = ( ushort )( tab.Orphans[ tab.NewMaterialParamStart ].Index << 2 ),
|
||||
ByteSize = ( ushort )( ( tab.NewMaterialParamEnd - tab.NewMaterialParamStart + 1 ) << 2 ),
|
||||
} );
|
||||
ByteOffset = (ushort)(tab.Orphans[tab.NewMaterialParamStart].Index << 2),
|
||||
ByteSize = (ushort)((tab.NewMaterialParamEnd - tab.NewMaterialParamStart + 1) << 2),
|
||||
});
|
||||
tab.Update();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool DrawShaderPackageMaterialParamLayout( ShpkTab tab, bool disabled )
|
||||
private static bool DrawShaderPackageMaterialParamLayout(ShpkTab tab, bool disabled)
|
||||
{
|
||||
var ret = false;
|
||||
|
||||
var materialParams = tab.Shpk.GetConstantById( MaterialParamsConstantId );
|
||||
if( !DrawMaterialParamLayoutHeader( materialParams?.Name ?? "Material Parameter" ) )
|
||||
{
|
||||
var materialParams = tab.Shpk.GetConstantById(MaterialParamsConstantId);
|
||||
if (!DrawMaterialParamLayoutHeader(materialParams?.Name ?? "Material Parameter"))
|
||||
return false;
|
||||
}
|
||||
|
||||
var sizeWellDefined = DrawMaterialParamLayoutBufferSize( tab.Shpk, materialParams );
|
||||
var sizeWellDefined = DrawMaterialParamLayoutBufferSize(tab.Shpk, materialParams);
|
||||
|
||||
ret |= DrawShaderPackageMaterialMatrix( tab, disabled );
|
||||
ret |= DrawShaderPackageMaterialMatrix(tab, disabled);
|
||||
|
||||
if( tab.MalformedParameters.Count > 0 )
|
||||
{
|
||||
DrawShaderPackageMisalignedParameters( tab );
|
||||
}
|
||||
else if( !disabled && sizeWellDefined )
|
||||
{
|
||||
ret |= DrawShaderPackageNewParameter( tab );
|
||||
}
|
||||
if (tab.MalformedParameters.Count > 0)
|
||||
DrawShaderPackageMisalignedParameters(tab);
|
||||
else if (!disabled && sizeWellDefined)
|
||||
ret |= DrawShaderPackageNewParameter(tab);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void DrawKeyArray( string arrayName, bool withId, IReadOnlyCollection< Key > keys )
|
||||
private static bool DrawShaderPackageResources(ShpkTab tab, bool disabled)
|
||||
{
|
||||
if( keys.Count == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
var ret = false;
|
||||
|
||||
using var t = ImRaii.TreeNode( arrayName );
|
||||
if( !t )
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!ImGui.CollapsingHeader("Shader Resources"))
|
||||
return false;
|
||||
|
||||
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
foreach( var (key, idx) in keys.WithIndex() )
|
||||
ret |= DrawShaderPackageResourceArray("Constant Buffers", "type", true, tab.Shpk.Constants, disabled);
|
||||
ret |= DrawShaderPackageResourceArray("Samplers", "type", false, tab.Shpk.Samplers, disabled);
|
||||
ret |= DrawShaderPackageResourceArray("Unordered Access Views", "type", false, tab.Shpk.Uavs, disabled);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void DrawKeyArray(string arrayName, bool withId, IReadOnlyCollection<Key> keys)
|
||||
{
|
||||
if (keys.Count == 0)
|
||||
return;
|
||||
|
||||
using var t = ImRaii.TreeNode(arrayName);
|
||||
if (!t)
|
||||
return;
|
||||
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
foreach (var (key, idx) in keys.WithIndex())
|
||||
{
|
||||
using var t2 = ImRaii.TreeNode( withId ? $"#{idx}: ID: 0x{key.Id:X8}" : $"#{idx}" );
|
||||
if( t2 )
|
||||
using var t2 = ImRaii.TreeNode(withId ? $"#{idx}: ID: 0x{key.Id:X8}" : $"#{idx}");
|
||||
if (t2)
|
||||
{
|
||||
ImRaii.TreeNode( $"Default Value: 0x{key.DefaultValue:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
ImRaii.TreeNode( $"Known Values: {string.Join( ", ", Array.ConvertAll( key.Values, value => $"0x{value:X8}" ) )}",
|
||||
ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
ImRaii.TreeNode($"Default Value: 0x{key.DefaultValue:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet).Dispose();
|
||||
ImRaii.TreeNode($"Known Values: {string.Join(", ", Array.ConvertAll(key.Values, value => $"0x{value:X8}"))}",
|
||||
ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet).Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawShaderPackageNodes( ShpkTab tab )
|
||||
private static void DrawShaderPackageNodes(ShpkTab tab)
|
||||
{
|
||||
if( tab.Shpk.Nodes.Length <= 0 )
|
||||
{
|
||||
if (tab.Shpk.Nodes.Length <= 0)
|
||||
return;
|
||||
}
|
||||
|
||||
using var t = ImRaii.TreeNode( $"Nodes ({tab.Shpk.Nodes.Length})###Nodes" );
|
||||
if( !t )
|
||||
{
|
||||
using var t = ImRaii.TreeNode($"Nodes ({tab.Shpk.Nodes.Length})###Nodes");
|
||||
if (!t)
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var (node, idx) in tab.Shpk.Nodes.WithIndex() )
|
||||
foreach (var (node, idx) in tab.Shpk.Nodes.WithIndex())
|
||||
{
|
||||
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
using var t2 = ImRaii.TreeNode( $"#{idx:D4}: ID: 0x{node.Id:X8}" );
|
||||
if( !t2 )
|
||||
{
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
using var t2 = ImRaii.TreeNode($"#{idx:D4}: Selector: 0x{node.Selector:X8}");
|
||||
if (!t2)
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach( var (key, keyIdx) in node.SystemKeys.WithIndex() )
|
||||
{
|
||||
ImRaii.TreeNode( $"System Key 0x{tab.Shpk.SystemKeys[ keyIdx ].Id:X8} = 0x{key:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
}
|
||||
foreach (var (key, keyIdx) in node.SystemKeys.WithIndex())
|
||||
ImRaii.TreeNode($"System Key 0x{tab.Shpk.SystemKeys[keyIdx].Id:X8} = 0x{key:X8}",
|
||||
ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet).Dispose();
|
||||
|
||||
foreach( var (key, keyIdx) in node.SceneKeys.WithIndex() )
|
||||
{
|
||||
ImRaii.TreeNode( $"Scene Key 0x{tab.Shpk.SceneKeys[ keyIdx ].Id:X8} = 0x{key:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
}
|
||||
foreach (var (key, keyIdx) in node.SceneKeys.WithIndex())
|
||||
ImRaii.TreeNode($"Scene Key 0x{tab.Shpk.SceneKeys[keyIdx].Id:X8} = 0x{key:X8}",
|
||||
ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet).Dispose();
|
||||
|
||||
foreach( var (key, keyIdx) in node.MaterialKeys.WithIndex() )
|
||||
{
|
||||
ImRaii.TreeNode( $"Material Key 0x{tab.Shpk.MaterialKeys[ keyIdx ].Id:X8} = 0x{key:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
}
|
||||
foreach (var (key, keyIdx) in node.MaterialKeys.WithIndex())
|
||||
ImRaii.TreeNode($"Material Key 0x{tab.Shpk.MaterialKeys[keyIdx].Id:X8} = 0x{key:X8}",
|
||||
ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet).Dispose();
|
||||
|
||||
foreach( var (key, keyIdx) in node.SubViewKeys.WithIndex() )
|
||||
{
|
||||
ImRaii.TreeNode( $"Sub-View Key #{keyIdx} = 0x{key:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
}
|
||||
foreach (var (key, keyIdx) in node.SubViewKeys.WithIndex())
|
||||
ImRaii.TreeNode($"Sub-View Key #{keyIdx} = 0x{key:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet).Dispose();
|
||||
|
||||
ImRaii.TreeNode( $"Pass Indices: {string.Join( ' ', node.PassIndices.Select( c => $"{c:X2}" ) )}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
foreach( var (pass, passIdx) in node.Passes.WithIndex() )
|
||||
ImRaii.TreeNode($"Pass Indices: {string.Join(' ', node.PassIndices.Select(c => $"{c:X2}"))}",
|
||||
ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet).Dispose();
|
||||
foreach (var (pass, passIdx) in node.Passes.WithIndex())
|
||||
{
|
||||
ImRaii.TreeNode( $"Pass #{passIdx}: ID: 0x{pass.Id:X8}, Vertex Shader #{pass.VertexShader}, Pixel Shader #{pass.PixelShader}",
|
||||
ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet )
|
||||
.Dispose();
|
||||
ImRaii.TreeNode($"Pass #{passIdx}: ID: 0x{pass.Id:X8}, Vertex Shader #{pass.VertexShader}, Pixel Shader #{pass.PixelShader}",
|
||||
ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet)
|
||||
.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool DrawOtherShaderPackageDetails( ShpkTab tab, bool disabled )
|
||||
private static void DrawShaderPackageSelection(ShpkTab tab)
|
||||
{
|
||||
var ret = false;
|
||||
if (!ImGui.CollapsingHeader("Shader Selection"))
|
||||
return;
|
||||
|
||||
if( !ImGui.CollapsingHeader( "Further Content" ) )
|
||||
DrawKeyArray("System Keys", true, tab.Shpk.SystemKeys);
|
||||
DrawKeyArray("Scene Keys", true, tab.Shpk.SceneKeys);
|
||||
DrawKeyArray("Material Keys", true, tab.Shpk.MaterialKeys);
|
||||
DrawKeyArray("Sub-View Keys", false, tab.Shpk.SubViewKeys);
|
||||
|
||||
DrawShaderPackageNodes(tab);
|
||||
using var t = ImRaii.TreeNode($"Node Selectors ({tab.Shpk.NodeSelectors.Count})###NodeSelectors");
|
||||
if (t)
|
||||
{
|
||||
return false;
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
foreach (var selector in tab.Shpk.NodeSelectors)
|
||||
ImRaii.TreeNode($"#{selector.Value:D4}: Selector: 0x{selector.Key:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet)
|
||||
.Dispose();
|
||||
}
|
||||
|
||||
ImRaii.TreeNode( $"Version: 0x{tab.Shpk.Version:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
|
||||
ret |= DrawShaderPackageResourceArray( "Constant Buffers", "type", true, tab.Shpk.Constants, disabled );
|
||||
ret |= DrawShaderPackageResourceArray( "Samplers", "type", false, tab.Shpk.Samplers, disabled );
|
||||
ret |= DrawShaderPackageResourceArray( "Unordered Access Views", "type", false, tab.Shpk.Uavs, disabled );
|
||||
|
||||
DrawKeyArray( "System Keys", true, tab.Shpk.SystemKeys );
|
||||
DrawKeyArray( "Scene Keys", true, tab.Shpk.SceneKeys );
|
||||
DrawKeyArray( "Material Keys", true, tab.Shpk.MaterialKeys );
|
||||
DrawKeyArray( "Sub-View Keys", false, tab.Shpk.SubViewKeys );
|
||||
|
||||
DrawShaderPackageNodes( tab );
|
||||
if( tab.Shpk.Items.Length > 0 )
|
||||
{
|
||||
using var t = ImRaii.TreeNode( $"Items ({tab.Shpk.Items.Length})###Items" );
|
||||
if( t )
|
||||
{
|
||||
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
|
||||
foreach( var (item, idx) in tab.Shpk.Items.WithIndex() )
|
||||
{
|
||||
ImRaii.TreeNode( $"#{idx:D4}: ID: 0x{item.Id:X8}, node: {item.Node}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ).Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( tab.Shpk.AdditionalData.Length > 0 )
|
||||
{
|
||||
using var t = ImRaii.TreeNode( $"Additional Data (Size: {tab.Shpk.AdditionalData.Length})###AdditionalData" );
|
||||
if( t )
|
||||
{
|
||||
ImGuiUtil.TextWrapped( string.Join( ' ', tab.Shpk.AdditionalData.Select( c => $"{c:X2}" ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static string UsedComponentString( bool withSize, in Resource resource )
|
||||
private static void DrawOtherShaderPackageDetails(ShpkTab tab)
|
||||
{
|
||||
var sb = new StringBuilder( 256 );
|
||||
if( withSize )
|
||||
if (!ImGui.CollapsingHeader("Further Content"))
|
||||
return;
|
||||
|
||||
ImRaii.TreeNode($"Version: 0x{tab.Shpk.Version:X8}", ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet).Dispose();
|
||||
|
||||
if (tab.Shpk.AdditionalData.Length > 0)
|
||||
{
|
||||
foreach( var (components, i) in ( resource.Used ?? Array.Empty< DisassembledShader.VectorComponents >() ).WithIndex() )
|
||||
using var t = ImRaii.TreeNode($"Additional Data (Size: {tab.Shpk.AdditionalData.Length})###AdditionalData");
|
||||
if (t)
|
||||
ImGuiUtil.TextWrapped(string.Join(' ', tab.Shpk.AdditionalData.Select(c => $"{c:X2}")));
|
||||
}
|
||||
}
|
||||
|
||||
private static string UsedComponentString(bool withSize, in Resource resource)
|
||||
{
|
||||
var sb = new StringBuilder(256);
|
||||
if (withSize)
|
||||
{
|
||||
foreach (var (components, i) in (resource.Used ?? Array.Empty<DisassembledShader.VectorComponents>()).WithIndex())
|
||||
{
|
||||
switch( components )
|
||||
switch (components)
|
||||
{
|
||||
case 0: break;
|
||||
case DisassembledShader.VectorComponents.All:
|
||||
sb.Append( $"[{i}], " );
|
||||
sb.Append($"[{i}], ");
|
||||
break;
|
||||
default:
|
||||
sb.Append( $"[{i}]." );
|
||||
foreach( var c in components.ToString().Where( char.IsUpper ) )
|
||||
{
|
||||
sb.Append( char.ToLower( c ) );
|
||||
}
|
||||
sb.Append($"[{i}].");
|
||||
foreach (var c in components.ToString().Where(char.IsUpper))
|
||||
sb.Append(char.ToLower(c));
|
||||
|
||||
sb.Append( ", " );
|
||||
sb.Append(", ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch( resource.UsedDynamically ?? 0 )
|
||||
switch (resource.UsedDynamically ?? 0)
|
||||
{
|
||||
case 0: break;
|
||||
case DisassembledShader.VectorComponents.All:
|
||||
sb.Append( "[*], " );
|
||||
sb.Append("[*], ");
|
||||
break;
|
||||
default:
|
||||
sb.Append( "[*]." );
|
||||
foreach( var c in resource.UsedDynamically!.Value.ToString().Where( char.IsUpper ) )
|
||||
{
|
||||
sb.Append( char.ToLower( c ) );
|
||||
}
|
||||
sb.Append("[*].");
|
||||
foreach (var c in resource.UsedDynamically!.Value.ToString().Where(char.IsUpper))
|
||||
sb.Append(char.ToLower(c));
|
||||
|
||||
sb.Append( ", " );
|
||||
sb.Append(", ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var components = ( resource.Used is { Length: > 0 } ? resource.Used[ 0 ] : 0 ) | ( resource.UsedDynamically ?? 0 );
|
||||
if( ( components & DisassembledShader.VectorComponents.X ) != 0 )
|
||||
{
|
||||
sb.Append( "Red, " );
|
||||
}
|
||||
var components = (resource.Used is { Length: > 0 } ? resource.Used[0] : 0) | (resource.UsedDynamically ?? 0);
|
||||
if ((components & DisassembledShader.VectorComponents.X) != 0)
|
||||
sb.Append("Red, ");
|
||||
|
||||
if( ( components & DisassembledShader.VectorComponents.Y ) != 0 )
|
||||
{
|
||||
sb.Append( "Green, " );
|
||||
}
|
||||
if ((components & DisassembledShader.VectorComponents.Y) != 0)
|
||||
sb.Append("Green, ");
|
||||
|
||||
if( ( components & DisassembledShader.VectorComponents.Z ) != 0 )
|
||||
{
|
||||
sb.Append( "Blue, " );
|
||||
}
|
||||
if ((components & DisassembledShader.VectorComponents.Z) != 0)
|
||||
sb.Append("Blue, ");
|
||||
|
||||
if( ( components & DisassembledShader.VectorComponents.W ) != 0 )
|
||||
{
|
||||
sb.Append( "Alpha, " );
|
||||
}
|
||||
if ((components & DisassembledShader.VectorComponents.W) != 0)
|
||||
sb.Append("Alpha, ");
|
||||
}
|
||||
|
||||
return sb.Length == 0 ? string.Empty : sb.ToString( 0, sb.Length - 2 );
|
||||
return sb.Length == 0 ? string.Empty : sb.ToString(0, sb.Length - 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,16 @@ public partial class ModEditWindow
|
|||
public ShpkTab(FileDialogService fileDialog, byte[] bytes)
|
||||
{
|
||||
FileDialog = fileDialog;
|
||||
Shpk = new ShpkFile(bytes, true);
|
||||
Header = $"Shader Package for DirectX {(int)Shpk.DirectXVersion}";
|
||||
try
|
||||
{
|
||||
Shpk = new ShpkFile(bytes, true);
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
Shpk = new ShpkFile(bytes, false);
|
||||
}
|
||||
|
||||
Header = $"Shader Package for DirectX {(int)Shpk.DirectXVersion}";
|
||||
Extension = Shpk.DirectXVersion switch
|
||||
{
|
||||
ShpkFile.DxVersion.DirectX9 => ".cso",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
|
@ -17,10 +19,12 @@ using Penumbra.GameData.Enums;
|
|||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Import.Textures;
|
||||
using Penumbra.Interop.ResourceTree;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.UI.Classes;
|
||||
using Penumbra.Util;
|
||||
|
|
@ -42,6 +46,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
private readonly ModMergeTab _modMergeTab;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly IDragDropManager _dragDropManager;
|
||||
private readonly GameEventManager _gameEvents;
|
||||
|
||||
private Mod? _mod;
|
||||
private Vector2 _iconSize = Vector2.Zero;
|
||||
|
|
@ -137,6 +142,9 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
{
|
||||
_left.Dispose();
|
||||
_right.Dispose();
|
||||
_materialTab.Reset();
|
||||
_modelTab.Reset();
|
||||
_shaderPackageTab.Reset();
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
|
|
@ -520,10 +528,33 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
return new FullPath(path);
|
||||
}
|
||||
|
||||
private HashSet<Utf8GamePath> FindPathsStartingWith(ByteString prefix)
|
||||
{
|
||||
var ret = new HashSet<Utf8GamePath>();
|
||||
|
||||
foreach (var path in _activeCollections.Current.ResolvedFiles.Keys)
|
||||
{
|
||||
if (path.Path.StartsWith(prefix))
|
||||
ret.Add(path);
|
||||
}
|
||||
|
||||
if (_mod != null)
|
||||
foreach (var option in _mod.Groups.SelectMany(g => g).Append(_mod.Default))
|
||||
{
|
||||
foreach (var path in option.Files.Keys)
|
||||
{
|
||||
if (path.Path.StartsWith(prefix))
|
||||
ret.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialog, ItemSwapTab itemSwapTab, IDataManager gameData,
|
||||
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, MetaFileManager metaFileManager,
|
||||
StainService stainService, ActiveCollections activeCollections, DalamudServices dalamud, ModMergeTab modMergeTab,
|
||||
CommunicatorService communicator, TextureManager textures, IDragDropManager dragDropManager)
|
||||
CommunicatorService communicator, TextureManager textures, IDragDropManager dragDropManager, GameEventManager gameEvents)
|
||||
: base(WindowBaseLabel)
|
||||
{
|
||||
_performance = performance;
|
||||
|
|
@ -539,14 +570,15 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
_dragDropManager = dragDropManager;
|
||||
_textures = textures;
|
||||
_fileDialog = fileDialog;
|
||||
_gameEvents = gameEvents;
|
||||
_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 +589,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();
|
||||
|
|
|
|||
|
|
@ -195,21 +195,7 @@ public class ModPanelSettingsTab : ITab
|
|||
_collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, (uint)idx2);
|
||||
|
||||
if (option.Description.Length > 0)
|
||||
{
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
ImGui.SameLine();
|
||||
using (var _ = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled));
|
||||
ImGuiUtil.RightAlign(FontAwesomeIcon.InfoCircle.ToIconString(), ImGui.GetStyle().ItemSpacing.X);
|
||||
}
|
||||
|
||||
if (hovered)
|
||||
{
|
||||
using var tt = ImRaii.Tooltip();
|
||||
ImGui.TextUnformatted(option.Description);
|
||||
}
|
||||
}
|
||||
ImGuiUtil.SelectableHelpMarker(option.Description);
|
||||
|
||||
id.Pop();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue