From 79de6f1714730efcde6cbf861d43d171d51d9f35 Mon Sep 17 00:00:00 2001 From: ackwell Date: Thu, 4 Jan 2024 23:33:54 +1100 Subject: [PATCH] Basic multi mesh handling --- .../Import/Models/Import/VertexAttribute.cs | 8 +- Penumbra/Import/Models/ModelManager.cs | 285 ++++++++++++------ 2 files changed, 194 insertions(+), 99 deletions(-) diff --git a/Penumbra/Import/Models/Import/VertexAttribute.cs b/Penumbra/Import/Models/Import/VertexAttribute.cs index 7c605ba8..430bc422 100644 --- a/Penumbra/Import/Models/Import/VertexAttribute.cs +++ b/Penumbra/Import/Models/Import/VertexAttribute.cs @@ -72,7 +72,9 @@ public class VertexAttribute return new VertexAttribute( element, - (index, bytes) => WriteByteFloat4(values[index], bytes) + // TODO: TEMP TESTING PINNED TO BONE 0 UNTIL I SET UP BONE MAPPINGS + // (index, bytes) => WriteByteFloat4(values[index], bytes) + (index, bytes) => WriteByteFloat4(Vector4.UnitX, bytes) ); } @@ -96,7 +98,9 @@ public class VertexAttribute return new VertexAttribute( element, - (index, bytes) => WriteUInt(values[index], bytes) + // TODO: TEMP TESTING PINNED TO BONE 0 UNTIL I SET UP BONE MAPPINGS + // (index, bytes) => WriteUInt(values[index], bytes) + (index, bytes) => WriteUInt(Vector4.Zero, bytes) ); } diff --git a/Penumbra/Import/Models/ModelManager.cs b/Penumbra/Import/Models/ModelManager.cs index e5349308..66f5202e 100644 --- a/Penumbra/Import/Models/ModelManager.cs +++ b/Penumbra/Import/Models/ModelManager.cs @@ -171,107 +171,75 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable public void Execute(CancellationToken cancel) { - var model = Build(); + var model = ModelRoot.Load("C:\\Users\\ackwell\\blender\\gltf-tests\\c0201e6180_top.gltf"); - // --- - - // todo this'll need to check names and such. also loop. i'm relying on a single mesh here which is Wrong:tm: - var mesh = model.LogicalNodes + // TODO: for grouping, should probably use `node.name ?? mesh.name`, as which are set seems to depend on the exporter. + var nodes = model.LogicalNodes .Where(node => node.Mesh != null) - .Select(node => node.Mesh) - .First(); + // TODO: I'm just grabbing the first 3, as that will contain 0.0, 0.1, and 1.0. testing, and all that. + .Take(3); - // todo check how many prims there are - maybe throw if more than one? not sure - var prim = mesh.Primitives[0]; - - var accessors = prim.VertexAccessors; - - var rawAttributes = new[] { - VertexAttribute.Position(accessors), - VertexAttribute.BlendWeight(accessors), - VertexAttribute.BlendIndex(accessors), - VertexAttribute.Normal(accessors), - VertexAttribute.Tangent1(accessors), - VertexAttribute.Color(accessors), - VertexAttribute.Uv(accessors), - }; - - var attributes = new List(); - var offsets = new byte[] {0, 0, 0}; - foreach (var attribute in rawAttributes) + // this is a representation of a single LoD + var vertexDeclarations = new List(); + var boneTables = new List(); + var meshes = new List(); + var submeshes = new List(); + var vertexBuffer = new List(); + var indices = new List(); + + foreach (var node in nodes) { - if (attribute == null) continue; - var element = attribute.Element; - attributes.Add(new VertexAttribute( - element with {Offset = offsets[element.Stream]}, - attribute.Write - )); - offsets[element.Stream] += attribute.Size; + var boneTableOffset = boneTables.Count; + var meshOffset = meshes.Count; + var subOffset = submeshes.Count; + var vertOffset = vertexBuffer.Count; + var idxOffset = indices.Count; + + var ( + vertexDeclaration, + boneTable, + xivMesh, + xivSubmesh, + meshVertexBuffer, + meshIndices + ) = MeshThing(node); + + vertexDeclarations.Add(vertexDeclaration); + boneTables.Add(boneTable); + meshes.Add(xivMesh with { + SubMeshIndex = (ushort)(xivMesh.SubMeshIndex + subOffset), + // TODO: should probably define a type for index type hey. + BoneTableIndex = (ushort)(xivMesh.BoneTableIndex + boneTableOffset), + StartIndex = (uint)(xivMesh.StartIndex + idxOffset / sizeof(ushort)), + VertexBufferOffset = xivMesh.VertexBufferOffset + .Select(offset => (uint)(offset + vertOffset)) + .ToArray(), + }); + submeshes.Add(xivSubmesh with { + // TODO: this will need to keep ticking up for each submesh in the same mesh + IndexOffset = (uint)(xivSubmesh.IndexOffset + idxOffset / sizeof(ushort)) + }); + vertexBuffer.AddRange(meshVertexBuffer); + indices.AddRange(meshIndices); } - var strides = offsets; - - var streams = new List[3]; - for (var i = 0; i < 3; i++) - streams[i] = new List(); - - // todo: this is a bit lmao but also... probably the most sane option? getting the count that is - var vertexCount = prim.VertexAccessors["POSITION"].Count; - for (var vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) - { - foreach (var attribute in attributes) - { - attribute.Write(vertexIndex, streams[attribute.Element.Stream]); - } - } - - // indices - var indexCount = prim.GetIndexAccessor().Count; - var indices = prim.GetIndices() - .SelectMany(index => BitConverter.GetBytes((ushort)index)) - .ToArray(); - - var dataBuffer = streams[0].Concat(streams[1]).Concat(streams[2]).Concat(indices); - - var lod1VertLen = (uint)(streams[0].Count + streams[1].Count + streams[2].Count); var mdl = new MdlFile() { Radius = 1, // todo: lod calcs... probably handled in penum? we probably only need to think about lod0 for actual import workflow. VertexOffset = [0, 0, 0], - IndexOffset = [lod1VertLen, 0, 0], - VertexBufferSize = [lod1VertLen, 0, 0], - IndexBufferSize = [(uint)indices.Length, 0, 0], + IndexOffset = [(uint)vertexBuffer.Count, 0, 0], + VertexBufferSize = [(uint)vertexBuffer.Count, 0, 0], + IndexBufferSize = [(uint)indices.Count, 0, 0], LodCount = 1, BoundingBoxes = new MdlStructs.BoundingBoxStruct() { Min = [-1, 0, -1, 1], Max = [1, 0, 1, 1], }, - VertexDeclarations = [new MdlStructs.VertexDeclarationStruct() - { - VertexElements = attributes.Select(attribute => attribute.Element).ToArray(), - }], - Meshes = [new MdlStructs.MeshStruct() - { - VertexCount = (ushort)vertexCount, - IndexCount = (uint)indexCount, - MaterialIndex = 0, - SubMeshIndex = 0, - SubMeshCount = 1, - BoneTableIndex = 0, - StartIndex = 0, - // todo: this will need to be composed down across multiple submeshes. given submeshes store contiguous buffers - VertexBufferOffset = [0, (uint)streams[0].Count, (uint)(streams[0].Count + streams[1].Count)], - VertexBufferStride = strides, - VertexStreamCount = 2, - }], - BoneTables = [new MdlStructs.BoneTableStruct() - { - BoneCount = 1, - // this needs to be the full 64. this should be fine _here_ with 0s because i only have one bone, but will need to be fully populated properly. in real files. - BoneIndex = new ushort[64], - }], + VertexDeclarations = vertexDeclarations.ToArray(), + Meshes = meshes.ToArray(), + BoneTables = boneTables.ToArray(), BoneBoundingBoxes = [ // new MdlStructs.BoundingBoxStruct() // { @@ -296,14 +264,7 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable Max = [0, 0, 0, 0], } ], - SubMeshes = [new MdlStructs.SubmeshStruct() - { - IndexOffset = 0, - IndexCount = (uint)indexCount, - AttributeIndexMask = 0, - BoneStartIndex = 0, - BoneCount = 1, - }], + SubMeshes = submeshes.ToArray(), // TODO pretty sure this is garbage data as far as textools functions // game clearly doesn't rely on this, but the "correct" values are a listing of the bones used by each submesh @@ -312,13 +273,13 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable Lods = [new MdlStructs.LodStruct() { MeshIndex = 0, - MeshCount = 1, + MeshCount = (ushort)meshes.Count, ModelLodRange = 0, TextureLodRange = 0, - VertexBufferSize = lod1VertLen, + VertexBufferSize = (uint)vertexBuffer.Count, VertexDataOffset = 0, - IndexBufferSize = (uint)indexCount, - IndexDataOffset = lod1VertLen, + IndexBufferSize = (uint)indices.Count, + IndexDataOffset = (uint)vertexBuffer.Count, }, ], Bones = [ @@ -327,12 +288,142 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable Materials = [ "/mt_c0201e6180_top_a.mtrl", ], - RemainingData = dataBuffer.ToArray(), + RemainingData = vertexBuffer.Concat(indices).ToArray(), }; Out = mdl; } + // this return type is an absolute meme, class that shit up. + public ( + MdlStructs.VertexDeclarationStruct, + MdlStructs.BoneTableStruct, + MdlStructs.MeshStruct, + MdlStructs.SubmeshStruct, + IEnumerable, + IEnumerable + ) MeshThing(Node node) + { + // BoneTable (mesh.btidx = 255 means unskinned) + // vertexdecl + + var mesh = node.Mesh; + + // TODO: should probably say _what_ mesh + // TODO: would be cool to support >1 primitive (esp. given they're effectively what submeshes are modeled as), but blender doesn't really use them, so not going to prio that at all. + if (mesh.Primitives.Count != 1) + throw new Exception($"Mesh has {mesh.Primitives.Count} primitives, expected 1."); + var primitive = mesh.Primitives[0]; + + var accessors = primitive.VertexAccessors; + + var rawAttributes = new[] { + VertexAttribute.Position(accessors), + VertexAttribute.BlendWeight(accessors), + VertexAttribute.BlendIndex(accessors), + VertexAttribute.Normal(accessors), + VertexAttribute.Tangent1(accessors), + VertexAttribute.Color(accessors), + VertexAttribute.Uv(accessors), + }; + + var attributes = new List(); + var offsets = new byte[] {0, 0, 0}; + foreach (var attribute in rawAttributes) + { + if (attribute == null) continue; + var element = attribute.Element; + attributes.Add(new VertexAttribute( + element with {Offset = offsets[element.Stream]}, + attribute.Write + )); + offsets[element.Stream] += attribute.Size; + } + var strides = offsets; + + // TODO: when merging submeshes, i'll need to check that vert els are the same for all of them, as xiv only stores verts at the mesh level and shares them. + + var streams = new List[3]; + for (var i = 0; i < 3; i++) + streams[i] = new List(); + + // todo: this is a bit lmao but also... probably the most sane option? getting the count that is + var vertexCount = primitive.VertexAccessors["POSITION"].Count; + for (var vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) + { + foreach (var attribute in attributes) + { + attribute.Write(vertexIndex, streams[attribute.Element.Stream]); + } + } + + // indices + var indexCount = primitive.GetIndexAccessor().Count; + var indices = primitive.GetIndices() + .SelectMany(index => BitConverter.GetBytes((ushort)index)) + .ToArray(); + + // one of these per mesh + var vertexDeclaration = new MdlStructs.VertexDeclarationStruct() + { + VertexElements = attributes.Select(attribute => attribute.Element).ToArray(), + }; + + // one of these per skinned mesh. + // TODO: check if mesh has skinning at all. + var boneTable = new MdlStructs.BoneTableStruct() + { + BoneCount = 1, + // this needs to be the full 64. this should be fine _here_ with 0s because i only have one bone, but will need to be fully populated properly. in real files. + BoneIndex = new ushort[64], + }; + + // mesh + var xivMesh = new MdlStructs.MeshStruct() + { + // TODO: sum across submeshes. + // TODO: would be cool to share verts on submesh boundaries but that's way out of scope for now. + VertexCount = (ushort)vertexCount, + IndexCount = (uint)indexCount, + // TODO: will have to think about how to represent this - materials can be named, so maybe adjust in parent? + MaterialIndex = 0, + // TODO: this will need adjusting by parent + SubMeshIndex = 0, + SubMeshCount = 1, + // TODO: update in parent + BoneTableIndex = 0, + // TODO: this is relative to the lod's index buffer, and is an index, not byte offset + StartIndex = 0, + // TODO: these are relative to the lod vertex buffer. these values are accurate for a 0 offset, but lod will need to adjust + VertexBufferOffset = [0, (uint)streams[0].Count, (uint)(streams[0].Count + streams[1].Count)], + VertexBufferStride = strides, + VertexStreamCount = /* 2 */ (byte)(attributes.Select(attribute => attribute.Element.Stream).Max() + 1), + }; + + // submesh + // TODO: once we have multiple submeshes, the _first_ should probably set an index offset of 0, and then further ones delta from there - and then they can be blindly adjusted by the parent that's laying out the meshes. + var xivSubmesh = new MdlStructs.SubmeshStruct() + { + IndexOffset = 0, + IndexCount = (uint)indexCount, + AttributeIndexMask = 0, + // TODO: not sure how i want to handle these ones + BoneStartIndex = 0, + BoneCount = 1, + }; + + var vertexBuffer = streams[0].Concat(streams[1]).Concat(streams[2]); + + return ( + vertexDeclaration, + boneTable, + xivMesh, + xivSubmesh, + vertexBuffer, + indices + ); + } + public bool Equals(IAction? other) { if (other is not ImportGltfAction rhs)