mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-17 22:24:22 +01:00
Clean up meshes
This commit is contained in:
parent
6de3077afa
commit
1a1c662364
3 changed files with 260 additions and 239 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue