diff --git a/Penumbra.GameData b/Penumbra.GameData index fe9d563d..845d1f99 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit fe9d563d9845630673cf098f7a6bfbd26e600fb4 +Subproject commit 845d1f99a752f4d23288a316e42d4bfa32fa987f diff --git a/Penumbra/Import/Models/Export/MaterialExporter.cs b/Penumbra/Import/Models/Export/MaterialExporter.cs index 2fa4e1b2..73a5e725 100644 --- a/Penumbra/Import/Models/Export/MaterialExporter.cs +++ b/Penumbra/Import/Models/Export/MaterialExporter.cs @@ -1,5 +1,6 @@ using Lumina.Data.Parsing; using Penumbra.GameData.Files; +using Penumbra.GameData.Files.MaterialStructs; using SharpGLTF.Materials; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Advanced; @@ -102,7 +103,7 @@ public class MaterialExporter // TODO: It feels a little silly to request the entire normal here when extracting the normal only needs some of the components. // As a future refactor, it would be neat to accept a single-channel field here, and then do composition of other stuff later. - private readonly struct ProcessCharacterNormalOperation(Image normal, MtrlFile.ColorTable table) : IRowOperation + private readonly struct ProcessCharacterNormalOperation(Image normal, ColorTable table) : IRowOperation { public Image Normal { get; } = normal.Clone(); public Image BaseColor { get; } = new(normal.Width, normal.Height); diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs index d3ca87dc..f372f665 100644 --- a/Penumbra/Import/Models/Export/MeshExporter.cs +++ b/Penumbra/Import/Models/Export/MeshExporter.cs @@ -3,6 +3,7 @@ using Lumina.Data.Parsing; using Lumina.Extensions; using OtterGui; using Penumbra.GameData.Files; +using Penumbra.GameData.Files.ModelStructs; using SharpGLTF.Geometry; using SharpGLTF.Geometry.VertexTypes; using SharpGLTF.IO; @@ -55,7 +56,7 @@ public class MeshExporter private readonly byte _lod; private readonly ushort _meshIndex; - private MdlStructs.MeshStruct XivMesh + private MeshStruct XivMesh => _mdl.Meshes[_meshIndex]; private readonly MaterialBuilder _material; @@ -109,8 +110,8 @@ public class MeshExporter var xivBoneTable = _mdl.BoneTables[XivMesh.BoneTableIndex]; var indexMap = new Dictionary(); - - foreach (var (xivBoneIndex, tableIndex) in xivBoneTable.BoneIndex.Take(xivBoneTable.BoneCount).WithIndex()) + // #TODO @ackwell maybe fix for V6 Models, I think this works fine. + foreach (var (xivBoneIndex, tableIndex) in xivBoneTable.BoneIndex.Take((int)xivBoneTable.BoneCount).WithIndex()) { var boneName = _mdl.Bones[xivBoneIndex]; if (!skeleton.Names.TryGetValue(boneName, out var gltfBoneIndex)) @@ -238,19 +239,15 @@ public class MeshExporter { "targetNames", shapeNames }, }); - string[] attributes = []; - var maxAttribute = 31 - BitOperations.LeadingZeroCount(attributeMask); + string[] attributes = []; + var maxAttribute = 31 - BitOperations.LeadingZeroCount(attributeMask); if (maxAttribute < _mdl.Attributes.Length) - { attributes = Enumerable.Range(0, 32) .Where(index => ((attributeMask >> index) & 1) == 1) .Select(index => _mdl.Attributes[index]) .ToArray(); - } else - { _notifier.Warning("Invalid attribute data, ignoring."); - } return new MeshData { @@ -278,7 +275,7 @@ public class MeshExporter for (var streamIndex = 0; streamIndex < MaximumMeshBufferStreams; streamIndex++) { streams[streamIndex] = new BinaryReader(new MemoryStream(_mdl.RemainingData)); - streams[streamIndex].Seek(_mdl.VertexOffset[_lod] + XivMesh.VertexBufferOffset[streamIndex]); + streams[streamIndex].Seek(_mdl.VertexOffset[_lod] + XivMesh.VertexBufferOffset(streamIndex)); } var sortedElements = _mdl.VertexDeclarations[_meshIndex].VertexElements @@ -315,7 +312,7 @@ public class MeshExporter MdlFile.VertexType.Single3 => new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()), MdlFile.VertexType.Single4 => new Vector4(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()), MdlFile.VertexType.UByte4 => reader.ReadBytes(4), - MdlFile.VertexType.NByte4 => new Vector4(reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f, + MdlFile.VertexType.NByte4 => new Vector4(reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f), MdlFile.VertexType.Half2 => new Vector2((float)reader.ReadHalf(), (float)reader.ReadHalf()), MdlFile.VertexType.Half4 => new Vector4((float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf(), diff --git a/Penumbra/Import/Models/Import/MeshImporter.cs b/Penumbra/Import/Models/Import/MeshImporter.cs index 1d4b223d..3a11cb04 100644 --- a/Penumbra/Import/Models/Import/MeshImporter.cs +++ b/Penumbra/Import/Models/Import/MeshImporter.cs @@ -1,5 +1,6 @@ using Lumina.Data.Parsing; using OtterGui; +using Penumbra.GameData.Files.ModelStructs; using SharpGLTF.Schema2; namespace Penumbra.Import.Models.Import; @@ -8,7 +9,7 @@ public class MeshImporter(IEnumerable nodes, IoNotifier notifier) { public struct Mesh { - public MdlStructs.MeshStruct MeshStruct; + public MeshStruct MeshStruct; public List SubMeshStructs; public string? Material; @@ -69,10 +70,14 @@ public class MeshImporter(IEnumerable nodes, IoNotifier notifier) return new Mesh { - MeshStruct = new MdlStructs.MeshStruct + MeshStruct = new MeshStruct { - VertexBufferOffset = [0, (uint)_streams[0].Count, (uint)(_streams[0].Count + _streams[1].Count)], - VertexBufferStride = _strides, + VertexBufferOffset1 = 0, + VertexBufferOffset2 = (uint)_streams[0].Count, + VertexBufferOffset3 = (uint)(_streams[0].Count + _streams[1].Count), + VertexBufferStride1 = _strides[0], + VertexBufferStride2 = _strides[1], + VertexBufferStride3 = _strides[2], VertexCount = _vertexCount, VertexStreamCount = (byte)_vertexDeclaration.Value.VertexElements .Select(element => element.Stream + 1) diff --git a/Penumbra/Import/Models/Import/ModelImporter.cs b/Penumbra/Import/Models/Import/ModelImporter.cs index 8f917b0e..eedd12ab 100644 --- a/Penumbra/Import/Models/Import/ModelImporter.cs +++ b/Penumbra/Import/Models/Import/ModelImporter.cs @@ -1,6 +1,7 @@ using Lumina.Data.Parsing; using OtterGui; using Penumbra.GameData.Files; +using Penumbra.GameData.Files.ModelStructs; using SharpGLTF.Schema2; namespace Penumbra.Import.Models.Import; @@ -14,10 +15,11 @@ public partial class ModelImporter(ModelRoot model, IoNotifier notifier) } // NOTE: This is intended to match TexTool's grouping regex, ".*[_ ^]([0-9]+)[\\.\\-]?([0-9]+)?$" - [GeneratedRegex(@"[_ ^](?'Mesh'[0-9]+)[.-]?(?'SubMesh'[0-9]+)?$", RegexOptions.Compiled | RegexOptions.NonBacktracking | RegexOptions.ExplicitCapture)] + [GeneratedRegex(@"[_ ^](?'Mesh'[0-9]+)[.-]?(?'SubMesh'[0-9]+)?$", + RegexOptions.Compiled | RegexOptions.NonBacktracking | RegexOptions.ExplicitCapture)] private static partial Regex MeshNameGroupingRegex(); - private readonly List _meshes = []; + private readonly List _meshes = []; private readonly List _subMeshes = []; private readonly List _materials = []; @@ -27,10 +29,10 @@ public partial class ModelImporter(ModelRoot model, IoNotifier notifier) private readonly List _indices = []; - private readonly List _bones = []; - private readonly List _boneTables = []; + private readonly List _bones = []; + private readonly List _boneTables = []; - private readonly BoundingBox _boundingBox = new BoundingBox(); + private readonly BoundingBox _boundingBox = new(); private readonly List _metaAttributes = []; @@ -95,9 +97,7 @@ public partial class ModelImporter(ModelRoot model, IoNotifier notifier) IndexBufferSize = (uint)indexBuffer.Length, }, ], - - Materials = [.. materials], - + Materials = [.. materials], BoundingBoxes = _boundingBox.ToStruct(), // TODO: Would be good to calculate all of this up the tree. @@ -132,9 +132,9 @@ public partial class ModelImporter(ModelRoot model, IoNotifier notifier) private void BuildMeshForGroup(IEnumerable subMeshNodes, int index) { // Record some offsets we'll be using later, before they get mutated with mesh values. - var subMeshOffset = _subMeshes.Count; - var vertexOffset = _vertexBuffer.Count; - var indexOffset = _indices.Count; + var subMeshOffset = _subMeshes.Count; + var vertexOffset = _vertexBuffer.Count; + var indexOffset = _indices.Count; var mesh = MeshImporter.Import(subMeshNodes, notifier.WithContext($"Mesh {index}")); var meshStartIndex = (uint)(mesh.MeshStruct.StartIndex + indexOffset); @@ -154,9 +154,9 @@ public partial class ModelImporter(ModelRoot model, IoNotifier notifier) SubMeshIndex = (ushort)(mesh.MeshStruct.SubMeshIndex + subMeshOffset), BoneTableIndex = boneTableIndex, StartIndex = meshStartIndex, - VertexBufferOffset = mesh.MeshStruct.VertexBufferOffset - .Select(offset => (uint)(offset + vertexOffset)) - .ToArray(), + VertexBufferOffset1 = (uint)(mesh.MeshStruct.VertexBufferOffset1 + vertexOffset), + VertexBufferOffset2 = (uint)(mesh.MeshStruct.VertexBufferOffset2 + vertexOffset), + VertexBufferOffset3 = (uint)(mesh.MeshStruct.VertexBufferOffset3 + vertexOffset), }); _boundingBox.Merge(mesh.BoundingBox); @@ -196,7 +196,8 @@ public partial class ModelImporter(ModelRoot model, IoNotifier notifier) // arrays, values is practically guaranteed to be the highest of the // group, so a failure on any of them will be a failure on it. if (_shapeValues.Count > ushort.MaxValue) - throw notifier.Exception($"Importing this file would require more than the maximum of {ushort.MaxValue} shape values.\nTry removing or applying shape keys that do not need to be changed at runtime in-game."); + throw notifier.Exception( + $"Importing this file would require more than the maximum of {ushort.MaxValue} shape values.\nTry removing or applying shape keys that do not need to be changed at runtime in-game."); } private ushort GetMaterialIndex(string materialName) @@ -216,6 +217,7 @@ public partial class ModelImporter(ModelRoot model, IoNotifier notifier) return (ushort)count; } + // #TODO @ackwell fix for V6 Models private ushort BuildBoneTable(List boneNames) { var boneIndices = new List(); @@ -238,7 +240,7 @@ public partial class ModelImporter(ModelRoot model, IoNotifier notifier) Array.Copy(boneIndices.ToArray(), boneIndicesArray, boneIndices.Count); var boneTableIndex = _boneTables.Count; - _boneTables.Add(new MdlStructs.BoneTableStruct() + _boneTables.Add(new BoneTableStruct() { BoneIndex = boneIndicesArray, BoneCount = (byte)boneIndices.Count, diff --git a/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs b/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs index 4d35e68a..f211e0bc 100644 --- a/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs +++ b/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs @@ -1,6 +1,5 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using Penumbra.GameData.Files; using Penumbra.GameData.Interop; using Penumbra.Interop.SafeHandles; @@ -9,7 +8,7 @@ namespace Penumbra.Interop.MaterialPreview; public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase { public const int TextureWidth = 4; - public const int TextureHeight = MtrlFile.ColorTable.NumRows; + public const int TextureHeight = GameData.Files.MaterialStructs.ColorTable.NumUsedRows; public const int TextureLength = TextureWidth * TextureHeight * 4; private readonly IFramework _framework; @@ -17,7 +16,7 @@ public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase private readonly Texture** _colorTableTexture; private readonly SafeTextureHandle _originalColorTableTexture; - private bool _updatePending; + private bool _updatePending; public Half[] ColorTable { get; } @@ -40,7 +39,7 @@ public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase if (_originalColorTableTexture == null) throw new InvalidOperationException("Material doesn't have a color table"); - ColorTable = new Half[TextureLength]; + ColorTable = new Half[TextureLength]; _updatePending = true; framework.Update += OnFrameworkUpdate; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ColorTable.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ColorTable.cs index a4e25f77..54c0eff6 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ColorTable.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ColorTable.cs @@ -4,6 +4,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Files; +using Penumbra.GameData.Files.MaterialStructs; using Penumbra.String.Functions; namespace Penumbra.UI.AdvancedWindow; @@ -74,7 +75,7 @@ public partial class ModEditWindow ImGui.TableHeader("Dye Preview"); } - for (var i = 0; i < MtrlFile.ColorTable.NumRows; ++i) + for (var i = 0; i < ColorTable.NumUsedRows; ++i) { ret |= DrawColorTableRow(tab, i, disabled); ImGui.TableNextRow(); @@ -115,8 +116,8 @@ public partial class ModEditWindow { var ret = false; if (tab.Mtrl.HasDyeTable) - for (var i = 0; i < MtrlFile.ColorTable.NumRows; ++i) - ret |= tab.Mtrl.ApplyDyeTemplate(_stainService.StmFile, i, dyeId); + for (var i = 0; i < ColorTable.NumUsedRows; ++i) + ret |= tab.Mtrl.ApplyDyeTemplate(_stainService.StmFile, i, dyeId, 0); tab.UpdateColorTablePreview(); @@ -140,21 +141,21 @@ public partial class ModEditWindow { var text = ImGui.GetClipboardText(); var data = Convert.FromBase64String(text); - if (data.Length < Marshal.SizeOf()) + if (data.Length < Marshal.SizeOf()) return false; ref var rows = ref tab.Mtrl.Table; fixed (void* ptr = data, output = &rows) { - MemoryUtility.MemCpyUnchecked(output, ptr, Marshal.SizeOf()); - if (data.Length >= Marshal.SizeOf() + Marshal.SizeOf() + MemoryUtility.MemCpyUnchecked(output, ptr, Marshal.SizeOf()); + if (data.Length >= Marshal.SizeOf() + Marshal.SizeOf() && tab.Mtrl.HasDyeTable) { ref var dyeRows = ref tab.Mtrl.DyeTable; fixed (void* output2 = &dyeRows) { - MemoryUtility.MemCpyUnchecked(output2, (byte*)ptr + Marshal.SizeOf(), - Marshal.SizeOf()); + MemoryUtility.MemCpyUnchecked(output2, (byte*)ptr + Marshal.SizeOf(), + Marshal.SizeOf()); } } } @@ -169,7 +170,7 @@ public partial class ModEditWindow } } - private static unsafe void ColorTableCopyClipboardButton(MtrlFile.ColorTable.Row row, MtrlFile.ColorDyeTable.Row dye) + private static unsafe void ColorTableCopyClipboardButton(ColorTable.Row row, ColorDyeTable.Row dye) { if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), ImGui.GetFrameHeight() * Vector2.One, "Export this row to your clipboard.", false, true)) @@ -177,11 +178,11 @@ public partial class ModEditWindow try { - var data = new byte[MtrlFile.ColorTable.Row.Size + 2]; + var data = new byte[ColorTable.Row.Size + 2]; fixed (byte* ptr = data) { - MemoryUtility.MemCpyUnchecked(ptr, &row, MtrlFile.ColorTable.Row.Size); - MemoryUtility.MemCpyUnchecked(ptr + MtrlFile.ColorTable.Row.Size, &dye, 2); + MemoryUtility.MemCpyUnchecked(ptr, &row, ColorTable.Row.Size); + MemoryUtility.MemCpyUnchecked(ptr + ColorTable.Row.Size, &dye, 2); } var text = Convert.ToBase64String(data); @@ -217,15 +218,15 @@ public partial class ModEditWindow { var text = ImGui.GetClipboardText(); var data = Convert.FromBase64String(text); - if (data.Length != MtrlFile.ColorTable.Row.Size + 2 + if (data.Length != ColorTable.Row.Size + 2 || !tab.Mtrl.HasTable) return false; fixed (byte* ptr = data) { - tab.Mtrl.Table[rowIdx] = *(MtrlFile.ColorTable.Row*)ptr; + tab.Mtrl.Table[rowIdx] = *(ColorTable.Row*)ptr; if (tab.Mtrl.HasDyeTable) - tab.Mtrl.DyeTable[rowIdx] = *(MtrlFile.ColorDyeTable.Row*)(ptr + MtrlFile.ColorTable.Row.Size); + tab.Mtrl.DyeTable[rowIdx] = *(ColorDyeTable.Row*)(ptr + ColorTable.Row.Size); } tab.UpdateColorTableRowPreview(rowIdx); @@ -451,7 +452,7 @@ public partial class ModEditWindow return ret; } - private bool DrawDyePreview(MtrlTab tab, int rowIdx, bool disabled, MtrlFile.ColorDyeTable.Row dye, float floatSize) + private bool DrawDyePreview(MtrlTab tab, int rowIdx, bool disabled, ColorDyeTable.Row dye, float floatSize) { var stain = _stainService.StainCombo.CurrentSelection.Key; if (stain == 0 || !_stainService.StmFile.Entries.TryGetValue(dye.Template, out var entry)) @@ -463,7 +464,7 @@ public partial class ModEditWindow var ret = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.PaintBrush.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Apply the selected dye to this row.", disabled, true); - ret = ret && tab.Mtrl.ApplyDyeTemplate(_stainService.StmFile, rowIdx, stain); + ret = ret && tab.Mtrl.ApplyDyeTemplate(_stainService.StmFile, rowIdx, stain, 0); if (ret) tab.UpdateColorTableRowPreview(rowIdx); diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs index b4801f5f..9421493e 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs @@ -8,6 +8,7 @@ using OtterGui.Classes; using OtterGui.Raii; using Penumbra.GameData.Data; using Penumbra.GameData.Files; +using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Structs; using Penumbra.Interop.Hooks.Objects; using Penumbra.Interop.MaterialPreview; @@ -601,7 +602,7 @@ public partial class ModEditWindow var stm = _edit._stainService.StmFile; var dye = Mtrl.DyeTable[rowIdx]; if (stm.TryGetValue(dye.Template, _edit._stainService.StainCombo.CurrentSelection.Key, out var dyes)) - row.ApplyDyeTemplate(dye, dyes); + row.ApplyDyeTemplate(dye, dyes, default); } if (HighlightedColorTableRow == rowIdx) @@ -628,12 +629,12 @@ public partial class ModEditWindow { var stm = _edit._stainService.StmFile; var stainId = (StainId)_edit._stainService.StainCombo.CurrentSelection.Key; - for (var i = 0; i < MtrlFile.ColorTable.NumRows; ++i) + for (var i = 0; i < ColorTable.NumUsedRows; ++i) { ref var row = ref rows[i]; var dye = Mtrl.DyeTable[i]; if (stm.TryGetValue(dye.Template, stainId, out var dyes)) - row.ApplyDyeTemplate(dye, dyes); + row.ApplyDyeTemplate(dye, dyes, default); } } @@ -647,7 +648,7 @@ public partial class ModEditWindow } } - private static void ApplyHighlight(ref MtrlFile.ColorTable.Row row, float time) + private static void ApplyHighlight(ref ColorTable.Row row, float time) { var level = (MathF.Sin(time * 2.0f * MathF.PI) + 2.0f) / 3.0f / 255.0f; var baseColor = ColorId.InGameHighlight.Value(); diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs index 03f276ea..80b1a5d5 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs @@ -483,7 +483,7 @@ public partial class ModEditWindow if (table) { ImGuiUtil.DrawTableColumn("Version"); - ImGuiUtil.DrawTableColumn(_lastFile.Version.ToString()); + ImGuiUtil.DrawTableColumn($"0x{_lastFile.Version:X}"); ImGuiUtil.DrawTableColumn("Radius"); ImGuiUtil.DrawTableColumn(_lastFile.Radius.ToString(CultureInfo.InvariantCulture)); ImGuiUtil.DrawTableColumn("Model Clip Out Distance");