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(
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)
);
}

View file

@ -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<VertexAttribute>();
var offsets = new byte[] {0, 0, 0};
foreach (var attribute in rawAttributes)
// this is a representation of a single LoD
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>();
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<byte>[3];
for (var i = 0; i < 3; i++)
streams[i] = new List<byte>();
// 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<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[] {
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<VertexAttribute>();
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<byte>[3];
for (var i = 0; i < 3; i++)
streams[i] = new List<byte>();
// 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)