mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-14 20:54:16 +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(
|
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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
VertexElements = attributes.Select(attribute => attribute.Element).ToArray(),
|
||||||
// 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(),
|
|
||||||
}],
|
|
||||||
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],
|
|
||||||
}],
|
|
||||||
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 = [new MdlStructs.SubmeshStruct()
|
|
||||||
{
|
|
||||||
IndexOffset = 0,
|
|
||||||
IndexCount = (uint)indexCount,
|
|
||||||
AttributeIndexMask = 0,
|
|
||||||
BoneStartIndex = 0,
|
|
||||||
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;
|
// 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)
|
public bool Equals(IAction? other)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue