Basic multi mesh handling

This commit is contained in:
ackwell 2024-01-04 23:33:54 +11:00
parent b3fe538219
commit 79de6f1714
2 changed files with 194 additions and 99 deletions

View file

@ -72,7 +72,9 @@ public class VertexAttribute
return new VertexAttribute( return new VertexAttribute(
element, 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( return new VertexAttribute(
element, 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)
); );
} }

View file

@ -171,20 +171,151 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
public void Execute(CancellationToken cancel) public void Execute(CancellationToken cancel)
{ {
var model = Build(); var model = ModelRoot.Load("C:\\Users\\ackwell\\blender\\gltf-tests\\c0201e6180_top.gltf");
// --- // TODO: for grouping, should probably use `node.name ?? mesh.name`, as which are set seems to depend on the exporter.
var nodes = model.LogicalNodes
// 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
.Where(node => node.Mesh != null) .Where(node => node.Mesh != null)
.Select(node => node.Mesh) // TODO: I'm just grabbing the first 3, as that will contain 0.0, 0.1, and 1.0. testing, and all that.
.First(); .Take(3);
// todo check how many prims there are - maybe throw if more than one? not sure // this is a representation of a single LoD
var prim = mesh.Primitives[0]; var vertexDeclarations = new List<MdlStructs.VertexDeclarationStruct>();
var boneTables = new List<MdlStructs.BoneTableStruct>();
var meshes = new List<MdlStructs.MeshStruct>();
var submeshes = new List<MdlStructs.SubmeshStruct>();
var vertexBuffer = new List<byte>();
var indices = new List<byte>();
var accessors = prim.VertexAccessors; foreach (var node in nodes)
{
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 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 = [(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 = vertexDeclarations.ToArray(),
Meshes = meshes.ToArray(),
BoneTables = boneTables.ToArray(),
BoneBoundingBoxes = [
// new MdlStructs.BoundingBoxStruct()
// {
// Min = [
// -0.081672676f,
// -0.113717034f,
// -0.11905348f,
// 1.0f,
// ],
// Max = [
// 0.03941727f,
// 0.09845419f,
// 0.107391916f,
// 1.0f,
// ],
// },
// _would_ be nice if i didn't need to fill out this
new MdlStructs.BoundingBoxStruct()
{
Min = [0, 0, 0, 0],
Max = [0, 0, 0, 0],
}
],
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
SubMeshBoneMap = [0],
Lods = [new MdlStructs.LodStruct()
{
MeshIndex = 0,
MeshCount = (ushort)meshes.Count,
ModelLodRange = 0,
TextureLodRange = 0,
VertexBufferSize = (uint)vertexBuffer.Count,
VertexDataOffset = 0,
IndexBufferSize = (uint)indices.Count,
IndexDataOffset = (uint)vertexBuffer.Count,
},
],
Bones = [
"j_kosi",
],
Materials = [
"/mt_c0201e6180_top_a.mtrl",
],
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<byte>,
IEnumerable<byte>
) 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[] { var rawAttributes = new[] {
VertexAttribute.Position(accessors), VertexAttribute.Position(accessors),
@ -210,12 +341,14 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
} }
var strides = offsets; 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<byte>[3]; var streams = new List<byte>[3];
for (var i = 0; i < 3; i++) for (var i = 0; i < 3; i++)
streams[i] = new List<byte>(); streams[i] = new List<byte>();
// todo: this is a bit lmao but also... probably the most sane option? getting the count that is // todo: this is a bit lmao but also... probably the most sane option? getting the count that is
var vertexCount = prim.VertexAccessors["POSITION"].Count; var vertexCount = primitive.VertexAccessors["POSITION"].Count;
for (var vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) for (var vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++)
{ {
foreach (var attribute in attributes) foreach (var attribute in attributes)
@ -225,112 +358,70 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
} }
// indices // indices
var indexCount = prim.GetIndexAccessor().Count; var indexCount = primitive.GetIndexAccessor().Count;
var indices = prim.GetIndices() var indices = primitive.GetIndices()
.SelectMany(index => BitConverter.GetBytes((ushort)index)) .SelectMany(index => BitConverter.GetBytes((ushort)index))
.ToArray(); .ToArray();
var dataBuffer = streams[0].Concat(streams[1]).Concat(streams[2]).Concat(indices); // one of these per mesh
var vertexDeclaration = new MdlStructs.VertexDeclarationStruct()
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],
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(), VertexElements = attributes.Select(attribute => attribute.Element).ToArray(),
}], };
Meshes = [new MdlStructs.MeshStruct()
{ // one of these per skinned mesh.
VertexCount = (ushort)vertexCount, // TODO: check if mesh has skinning at all.
IndexCount = (uint)indexCount, var boneTable = new MdlStructs.BoneTableStruct()
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, 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. // 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], BoneIndex = new ushort[64],
}], };
BoneBoundingBoxes = [
// new MdlStructs.BoundingBoxStruct()
// {
// Min = [
// -0.081672676f,
// -0.113717034f,
// -0.11905348f,
// 1.0f,
// ],
// Max = [
// 0.03941727f,
// 0.09845419f,
// 0.107391916f,
// 1.0f,
// ],
// },
// _would_ be nice if i didn't need to fill out this // mesh
new MdlStructs.BoundingBoxStruct() var xivMesh = new MdlStructs.MeshStruct()
{ {
Min = [0, 0, 0, 0], // TODO: sum across submeshes.
Max = [0, 0, 0, 0], // 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,
SubMeshes = [new MdlStructs.SubmeshStruct() // 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, IndexOffset = 0,
IndexCount = (uint)indexCount, IndexCount = (uint)indexCount,
AttributeIndexMask = 0, AttributeIndexMask = 0,
// TODO: not sure how i want to handle these ones
BoneStartIndex = 0, BoneStartIndex = 0,
BoneCount = 1, BoneCount = 1,
}],
// 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
SubMeshBoneMap = [0],
Lods = [new MdlStructs.LodStruct()
{
MeshIndex = 0,
MeshCount = 1,
ModelLodRange = 0,
TextureLodRange = 0,
VertexBufferSize = lod1VertLen,
VertexDataOffset = 0,
IndexBufferSize = (uint)indexCount,
IndexDataOffset = lod1VertLen,
},
],
Bones = [
"j_kosi",
],
Materials = [
"/mt_c0201e6180_top_a.mtrl",
],
RemainingData = dataBuffer.ToArray(),
}; };
Out = mdl; var vertexBuffer = streams[0].Concat(streams[1]).Concat(streams[2]);
return (
vertexDeclaration,
boneTable,
xivMesh,
xivSubmesh,
vertexBuffer,
indices
);
} }
public bool Equals(IAction? other) public bool Equals(IAction? other)