Clean up meshes

This commit is contained in:
ackwell 2024-01-06 20:40:30 +11:00
parent 6de3077afa
commit 1a1c662364
3 changed files with 260 additions and 239 deletions

View file

@ -221,23 +221,13 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
var idxOffset = indices.Count;
var shapeValueOffset = shapeValues.Count;
var (
vertexDeclaration,
// boneTable,
xivMesh,
xivSubmeshes,
meshVertexBuffer,
meshIndices,
meshShapeData,
meshBoneList
) = MeshThing(submeshnodes);
var meshthing = MeshImporter.Import(submeshnodes);
var boneTableIndex = 255;
// TODO: a better check than this would be real good
if (meshBoneList.Count() > 0)
if (meshthing.Bones != null)
{
var boneIndices = new List<ushort>();
foreach (var mb in meshBoneList)
foreach (var mb in meshthing.Bones)
{
var boneIndex = bones.IndexOf(mb);
if (boneIndex == -1)
@ -262,43 +252,43 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
});
}
vertexDeclarations.Add(vertexDeclaration);
var meshStartIndex = (uint)(xivMesh.StartIndex + idxOffset / sizeof(ushort));
meshes.Add(xivMesh with
vertexDeclarations.Add(meshthing.VertexDeclaration);
var meshStartIndex = (uint)(meshthing.MeshStruct.StartIndex + idxOffset / sizeof(ushort));
meshes.Add(meshthing.MeshStruct with
{
SubMeshIndex = (ushort)(xivMesh.SubMeshIndex + subOffset),
SubMeshIndex = (ushort)(meshthing.MeshStruct.SubMeshIndex + subOffset),
// TODO: should probably define a type for index type hey.
BoneTableIndex = (ushort)boneTableIndex,
StartIndex = meshStartIndex,
VertexBufferOffset = xivMesh.VertexBufferOffset
VertexBufferOffset = meshthing.MeshStruct.VertexBufferOffset
.Select(offset => (uint)(offset + vertOffset))
.ToArray(),
});
// TODO: could probably do this with linq cleaner
foreach (var xivSubmesh in xivSubmeshes)
foreach (var xivSubmesh in meshthing.SubMeshStructs)
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.SelectMany(index => BitConverter.GetBytes((ushort)index)));
foreach (var (key, (shapeMesh, meshShapeValues)) in meshShapeData)
vertexBuffer.AddRange(meshthing.VertexBuffer);
indices.AddRange(meshthing.Indicies.SelectMany(index => BitConverter.GetBytes((ushort)index)));
foreach (var shapeKey in meshthing.ShapeKeys)
{
List<MdlStructs.ShapeMeshStruct> keyshapedata;
if (!shapeData.TryGetValue(key, out keyshapedata))
if (!shapeData.TryGetValue(shapeKey.Name, out keyshapedata))
{
keyshapedata = new();
shapeData.Add(key, keyshapedata);
shapeData.Add(shapeKey.Name, keyshapedata);
}
keyshapedata.Add(shapeMesh with
keyshapedata.Add(shapeKey.ShapeMesh with
{
MeshIndexOffset = meshStartIndex,
ShapeValueOffset = (uint)shapeValueOffset,
});
shapeValues.AddRange(meshShapeValues);
shapeValues.AddRange(shapeKey.ShapeValues);
}
}
@ -392,215 +382,6 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
Out = mdl;
}
// this return type is an absolute meme, class that shit up.
private (
MdlStructs.VertexDeclarationStruct,
// MdlStructs.BoneTableStruct,
MdlStructs.MeshStruct,
IEnumerable<MdlStructs.SubmeshStruct>,
IEnumerable<byte>,
IEnumerable<ushort>,
IDictionary<string, (MdlStructs.ShapeMeshStruct, List<MdlStructs.ShapeValueStruct>)>,
IEnumerable<string>
) MeshThing(IEnumerable<Node> nodes)
{
var vertexDeclaration = new MdlStructs.VertexDeclarationStruct() { VertexElements = Array.Empty<MdlStructs.VertexElement>() };
var vertexCount = (ushort)0;
// there's gotta be a better way to do this with streams or enumerables or something, surely
var streams = new List<byte>[3];
for (var i = 0; i < 3; i++)
streams[i] = new List<byte>();
var indexCount = (uint)0;
var indices = new List<ushort>();
var strides = new byte[] { 0, 0, 0 };
var submeshes = new List<MdlStructs.SubmeshStruct>();
var morphData = new Dictionary<string, List<MdlStructs.ShapeValueStruct>>();
/*
THOUGHTS
per submesh node, before calling down to build the mesh, build a bone mapping of joint index -> bone name (not node index) - the joint indexes are what will be used in the vertices.
per submesh node, eagerly collect all blend indexes (joints) used before building anything - just as a set or something
the above means i can create a limited set and a mapping, i.e. if skeleton contains {0->a 1->b 2->c}, and mesh contains 0, 2, then i can output [a, c] + {0->0, 2->1}
(throw if >64 entries in that name array)
then for the second prim,
again get the joint-name mapping, and again get the joint set
then can extend the values. using the samme example, if skeleton2 contains {0->c 1->d, 2->e} and mesh contains [0,2] again, then bone array can be extended to [a, c, e] and the mesh-specific mapping would be {0->1, 2->2}
repeat, etc
*/
var usedBones = new List<string>();
// TODO: check that attrs/elems/strides match - we should be generating per-mesh stuff for sanity's sake, but we need to make sure they match if there's >1 node mesh in a mesh.
foreach (var node in nodes)
{
var vertOff = vertexCount;
var idxOff = indexCount;
Dictionary<ushort, ushort>? nodeBoneMap = null;
var bonething = WalkBoneThing(node);
if (bonething.HasValue)
{
var (boneNames, usedJoints) = bonething.Value;
nodeBoneMap = new();
// todo: probably linq this shit
foreach (var usedJoint in usedJoints)
{
// this is the 0,2
var boneName = boneNames[usedJoint];
var boneIdx = usedBones.IndexOf(boneName);
if (boneIdx == -1)
{
boneIdx = usedBones.Count;
usedBones.Add(boneName);
}
nodeBoneMap.Add(usedJoint, (ushort)boneIdx);
}
Penumbra.Log.Information($"nbm {string.Join(",", nodeBoneMap.Select(kv => $"{kv.Key}:{kv.Value}"))}");
}
var subMeshThingy = SubMeshImporter.Import(node, nodeBoneMap);
vertexDeclaration = subMeshThingy.VertexDeclaration; // TODO: CHECK EQUAL AFTER FIRST
strides = subMeshThingy.Strides; // ALSO CHECK EQUAL
vertexCount += subMeshThingy.VertexCount;
for (var i = 0; i < 3; i++)
streams[i].AddRange(subMeshThingy.Streams[i]);
indexCount += (uint)subMeshThingy.Indices.Length;
// we need to offset the indexes to point into the new stuff
indices.AddRange(subMeshThingy.Indices.Select(idx => (ushort)(idx + vertOff)));
submeshes.Add(subMeshThingy.Struct with
{
IndexOffset = subMeshThingy.Struct.IndexOffset + idxOff
// TODO: bone stuff probably
});
// TODO: HANDLE MORPHS, NEED TO ADJUST EVERY VALUE'S INDEX OFFSETS
foreach (var (key, shapeValues) in subMeshThingy.ShapeValues)
{
List<MdlStructs.ShapeValueStruct> valueList;
if (!morphData.TryGetValue(key, out valueList))
{
valueList = new();
morphData.Add(key, valueList);
}
valueList.AddRange(
shapeValues
.Select(value => value with
{
// but this is actually an index index
BaseIndicesIndex = (ushort)(value.BaseIndicesIndex + idxOff),
// this is a vert idx
ReplacingVertexIndex = (ushort)(value.ReplacingVertexIndex + vertOff),
})
);
}
}
// one of these per skinned mesh.
// TODO: check if mesh has skinning at all. (err if mixed?)
// 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 = vertexCount,
IndexCount = 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 = (ushort)submeshes.Count,
// 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),
VertexStreamCount = (byte)(vertexDeclaration.VertexElements.Select(element => element.Stream).Max() + 1)
};
// TODO: can probably get away with flattening the values and blindly setting offsets in parent - mesh matters above, but the values are already Dealt With at this point
var shapeData = morphData.ToDictionary(
(pair) => pair.Key,
pair => (
new MdlStructs.ShapeMeshStruct()
{
// TODO: this needs to be adjusted by the parent
MeshIndexOffset = 0,
ShapeValueCount = (uint)pair.Value.Count,
// TODO: Also update by parent
ShapeValueOffset = 0,
},
pair.Value
)
);
return (
vertexDeclaration,
// boneTable,
xivMesh,
submeshes,
streams[0].Concat(streams[1]).Concat(streams[2]),
indices,
shapeData,
usedBones
);
}
private (string[], ISet<ushort>)? WalkBoneThing(Node node)
{
//
if (node.Skin == null)
return null;
var jointNames = Enumerable.Range(0, node.Skin.JointsCount)
.Select(index => node.Skin.GetJoint(index).Joint.Name ?? $"UNNAMED")
.ToArray();
// it might make sense to do this in the submesh handling - i do need to maintain the mesh-wide bone list, but that can be passed in/out, perhaps?
var mesh = node.Mesh;
if (mesh.Primitives.Count != 1)
throw new Exception($"Mesh has {mesh.Primitives.Count} primitives, expected 1.");
var primitive = mesh.Primitives[0];
var jointsAccessor = primitive.GetVertexAccessor("JOINTS_0");
if (jointsAccessor == null)
throw new Exception($"Skinned meshes must contain a JOINTS_0 attribute.");
// var weightsAccssor = primitive.GetVertexAccessor("WEIGHTS_0");
// if (weightsAccssor == null)
// throw new Exception($"Skinned meshes must contain a WEIGHTS_0 attribute.");
var usedJoints = new HashSet<ushort>();
// TODO: would be neat to omit any joints that are only used in 0-weight positions, but doing so would require being a _little_ smarter in vertex attrs on how to fall back when mappings aren't found - or otherwise try to ensure that mappings for unused stuff always exists
// foreach (var (joints, weights) in jointsAccessor.AsVector4Array().Zip(weightsAccssor.AsVector4Array()))
// {
// for (var index = 0; index < 4; index++)
// if (weights[index] > 0)
// usedJoints.Add((ushort)joints[index]);
// }
foreach (var joints in jointsAccessor.AsVector4Array())
{
for (var index = 0; index < 4; index++)
usedJoints.Add((ushort)joints[index]);
}
return (jointNames, usedJoints);
}
public bool Equals(IAction? other)
{
if (other is not ImportGltfAction rhs)