mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Basic multi mesh handling
This commit is contained in:
parent
b3fe538219
commit
79de6f1714
2 changed files with 194 additions and 99 deletions
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue