Move a few things to export subdir

This commit is contained in:
ackwell 2024-01-01 00:18:03 +11:00
parent 6a2b802196
commit 551c25a64c
5 changed files with 169 additions and 87 deletions

View file

@ -6,15 +6,39 @@ using SharpGLTF.Geometry;
using SharpGLTF.Geometry.VertexTypes; using SharpGLTF.Geometry.VertexTypes;
using SharpGLTF.IO; using SharpGLTF.IO;
using SharpGLTF.Materials; using SharpGLTF.Materials;
using SharpGLTF.Scenes;
namespace Penumbra.Import.Modules; namespace Penumbra.Import.Models.Export;
public sealed class MeshConverter public class MeshExporter
{ {
public static IMeshBuilder<MaterialBuilder>[] ToGltf(MdlFile mdl, byte lod, ushort meshIndex, Dictionary<string, int>? boneNameMap) public class Mesh
{ {
var self = new MeshConverter(mdl, lod, meshIndex, boneNameMap); private IMeshBuilder<MaterialBuilder>[] _meshes;
return self.BuildMesh(); private NodeBuilder[]? _joints;
public Mesh(IMeshBuilder<MaterialBuilder>[] meshes, NodeBuilder[]? joints)
{
_meshes = meshes;
_joints = joints;
}
public void AddToScene(SceneBuilder scene)
{
// TODO: throw if mesh has skinned vertices but no joints are available?
foreach (var mesh in _meshes)
if (_joints == null)
scene.AddRigidMesh(mesh, Matrix4x4.Identity);
else
scene.AddSkinnedMesh(mesh, Matrix4x4.Identity, _joints);
}
}
// TODO: replace bonenamemap with a gltfskeleton
public static Mesh Export(MdlFile mdl, byte lod, ushort meshIndex, GltfSkeleton? skeleton)
{
var self = new MeshExporter(mdl, lod, meshIndex, skeleton?.Names);
return new Mesh(self.BuildMeshes(), skeleton?.Joints);
} }
private const byte MaximumMeshBufferStreams = 3; private const byte MaximumMeshBufferStreams = 3;
@ -22,14 +46,14 @@ public sealed class MeshConverter
private readonly MdlFile _mdl; private readonly MdlFile _mdl;
private readonly byte _lod; private readonly byte _lod;
private readonly ushort _meshIndex; private readonly ushort _meshIndex;
private MdlStructs.MeshStruct Mesh => _mdl.Meshes[_meshIndex]; private MdlStructs.MeshStruct XivMesh => _mdl.Meshes[_meshIndex];
private readonly Dictionary<ushort, int>? _boneIndexMap; private readonly Dictionary<ushort, int>? _boneIndexMap;
private readonly Type _geometryType; private readonly Type _geometryType;
private readonly Type _skinningType; private readonly Type _skinningType;
private MeshConverter(MdlFile mdl, byte lod, ushort meshIndex, Dictionary<string, int>? boneNameMap) private MeshExporter(MdlFile mdl, byte lod, ushort meshIndex, Dictionary<string, int>? boneNameMap)
{ {
_mdl = mdl; _mdl = mdl;
_lod = lod; _lod = lod;
@ -49,7 +73,7 @@ public sealed class MeshConverter
private Dictionary<ushort, int> BuildBoneIndexMap(Dictionary<string, int> boneNameMap) private Dictionary<ushort, int> BuildBoneIndexMap(Dictionary<string, int> boneNameMap)
{ {
// todo: BoneTableIndex of 255 means null? if so, it should probably feed into the attributes we assign... // todo: BoneTableIndex of 255 means null? if so, it should probably feed into the attributes we assign...
var xivBoneTable = _mdl.BoneTables[Mesh.BoneTableIndex]; var xivBoneTable = _mdl.BoneTables[XivMesh.BoneTableIndex];
var indexMap = new Dictionary<ushort, int>(); var indexMap = new Dictionary<ushort, int>();
@ -66,16 +90,16 @@ public sealed class MeshConverter
return indexMap; return indexMap;
} }
private IMeshBuilder<MaterialBuilder>[] BuildMesh() private IMeshBuilder<MaterialBuilder>[] BuildMeshes()
{ {
var indices = BuildIndices(); var indices = BuildIndices();
var vertices = BuildVertices(); var vertices = BuildVertices();
// TODO: handle submeshCount = 0 // TODO: handle SubMeshCount = 0
return _mdl.SubMeshes return _mdl.SubMeshes
.Skip(Mesh.SubMeshIndex) .Skip(XivMesh.SubMeshIndex)
.Take(Mesh.SubMeshCount) .Take(XivMesh.SubMeshCount)
.Select(submesh => BuildSubMesh(submesh, indices, vertices)) .Select(submesh => BuildSubMesh(submesh, indices, vertices))
.ToArray(); .ToArray();
} }
@ -83,7 +107,7 @@ public sealed class MeshConverter
private IMeshBuilder<MaterialBuilder> BuildSubMesh(MdlStructs.SubmeshStruct submesh, IReadOnlyList<ushort> indices, IReadOnlyList<IVertexBuilder> vertices) private IMeshBuilder<MaterialBuilder> BuildSubMesh(MdlStructs.SubmeshStruct submesh, IReadOnlyList<ushort> indices, IReadOnlyList<IVertexBuilder> vertices)
{ {
// Index indices are specified relative to the LOD's 0, but we're reading chunks for each mesh. // Index indices are specified relative to the LOD's 0, but we're reading chunks for each mesh.
var startIndex = (int)(submesh.IndexOffset - Mesh.StartIndex); var startIndex = (int)(submesh.IndexOffset - XivMesh.StartIndex);
var meshBuilderType = typeof(MeshBuilder<,,,>).MakeGenericType( var meshBuilderType = typeof(MeshBuilder<,,,>).MakeGenericType(
typeof(MaterialBuilder), typeof(MaterialBuilder),
@ -124,7 +148,7 @@ public sealed class MeshConverter
var shapeValues = _mdl.ShapeMeshes var shapeValues = _mdl.ShapeMeshes
.Skip(shape.ShapeMeshStartIndex[_lod]) .Skip(shape.ShapeMeshStartIndex[_lod])
.Take(shape.ShapeMeshCount[_lod]) .Take(shape.ShapeMeshCount[_lod])
.Where(shapeMesh => shapeMesh.MeshIndexOffset == Mesh.StartIndex) .Where(shapeMesh => shapeMesh.MeshIndexOffset == XivMesh.StartIndex)
.SelectMany(shapeMesh => .SelectMany(shapeMesh =>
_mdl.ShapeValues _mdl.ShapeValues
.Skip((int)shapeMesh.ShapeValueOffset) .Skip((int)shapeMesh.ShapeValueOffset)
@ -158,8 +182,8 @@ public sealed class MeshConverter
private IReadOnlyList<ushort> BuildIndices() private IReadOnlyList<ushort> BuildIndices()
{ {
var reader = new BinaryReader(new MemoryStream(_mdl.RemainingData)); var reader = new BinaryReader(new MemoryStream(_mdl.RemainingData));
reader.Seek(_mdl.IndexOffset[_lod] + Mesh.StartIndex * sizeof(ushort)); reader.Seek(_mdl.IndexOffset[_lod] + XivMesh.StartIndex * sizeof(ushort));
return reader.ReadStructuresAsArray<ushort>((int)Mesh.IndexCount); return reader.ReadStructuresAsArray<ushort>((int)XivMesh.IndexCount);
} }
private IReadOnlyList<IVertexBuilder> BuildVertices() private IReadOnlyList<IVertexBuilder> BuildVertices()
@ -172,7 +196,7 @@ public sealed class MeshConverter
for (var streamIndex = 0; streamIndex < MaximumMeshBufferStreams; streamIndex++) for (var streamIndex = 0; streamIndex < MaximumMeshBufferStreams; streamIndex++)
{ {
streams[streamIndex] = new BinaryReader(new MemoryStream(_mdl.RemainingData)); streams[streamIndex] = new BinaryReader(new MemoryStream(_mdl.RemainingData));
streams[streamIndex].Seek(_mdl.VertexOffset[_lod] + Mesh.VertexBufferOffset[streamIndex]); streams[streamIndex].Seek(_mdl.VertexOffset[_lod] + XivMesh.VertexBufferOffset[streamIndex]);
} }
var sortedElements = _mdl.VertexDeclarations[_meshIndex].VertexElements var sortedElements = _mdl.VertexDeclarations[_meshIndex].VertexElements
@ -183,7 +207,7 @@ public sealed class MeshConverter
var vertices = new List<IVertexBuilder>(); var vertices = new List<IVertexBuilder>();
var attributes = new Dictionary<MdlFile.VertexUsage, object>(); var attributes = new Dictionary<MdlFile.VertexUsage, object>();
for (var vertexIndex = 0; vertexIndex < Mesh.VertexCount; vertexIndex++) for (var vertexIndex = 0; vertexIndex < XivMesh.VertexCount; vertexIndex++)
{ {
attributes.Clear(); attributes.Clear();

View file

@ -0,0 +1,100 @@
using Penumbra.GameData.Files;
using SharpGLTF.Scenes;
using SharpGLTF.Transforms;
namespace Penumbra.Import.Models.Export;
public class ModelExporter
{
public class Model
{
private List<MeshExporter.Mesh> _meshes;
private GltfSkeleton? _skeleton;
public Model(List<MeshExporter.Mesh> meshes, GltfSkeleton? skeleton)
{
_meshes = meshes;
_skeleton = skeleton;
}
public void AddToScene(SceneBuilder scene)
{
// If there's a skeleton, the root node should be added before we add any potentially skinned meshes.
var skeletonRoot = _skeleton?.Root;
if (skeletonRoot != null)
scene.AddNode(skeletonRoot);
// Add all the meshes to the scene.
foreach (var mesh in _meshes)
mesh.AddToScene(scene);
}
}
public static Model Export(MdlFile mdl, XivSkeleton? xivSkeleton)
{
var gltfSkeleton = xivSkeleton != null ? ConvertSkeleton(xivSkeleton) : null;
var meshes = ConvertMeshes(mdl, gltfSkeleton);
return new Model(meshes, gltfSkeleton);
}
private static List<MeshExporter.Mesh> ConvertMeshes(MdlFile mdl, GltfSkeleton? skeleton)
{
var meshes = new List<MeshExporter.Mesh>();
for (byte lodIndex = 0; lodIndex < mdl.LodCount; lodIndex++)
{
var lod = mdl.Lods[lodIndex];
// TODO: consider other types of mesh?
for (ushort meshOffset = 0; meshOffset < lod.MeshCount; meshOffset++)
{
var mesh = MeshExporter.Export(mdl, lodIndex, (ushort)(lod.MeshIndex + meshOffset), skeleton);
meshes.Add(mesh);
}
}
return meshes;
}
private static GltfSkeleton? ConvertSkeleton(XivSkeleton skeleton)
{
NodeBuilder? root = null;
var names = new Dictionary<string, int>();
var joints = new List<NodeBuilder>();
for (var boneIndex = 0; boneIndex < skeleton.Bones.Length; boneIndex++)
{
var bone = skeleton.Bones[boneIndex];
if (names.ContainsKey(bone.Name)) continue;
var node = new NodeBuilder(bone.Name);
names[bone.Name] = joints.Count;
joints.Add(node);
node.SetLocalTransform(new AffineTransform(
bone.Transform.Scale,
bone.Transform.Rotation,
bone.Transform.Translation
), false);
if (bone.ParentIndex == -1)
{
root = node;
continue;
}
var parent = joints[names[skeleton.Bones[bone.ParentIndex].Name]];
parent.AddNode(node);
}
if (root == null)
return null;
return new()
{
Root = root,
Joints = joints.ToArray(),
Names = names,
};
}
}

View file

@ -1,11 +1,12 @@
namespace Penumbra.Import.Models; using SharpGLTF.Scenes;
// TODO: this should almost certainly live in gamedata. if not, it should at _least_ be adjacent to the model handling. namespace Penumbra.Import.Models.Export;
public class Skeleton
public class XivSkeleton
{ {
public Bone[] Bones; public Bone[] Bones;
public Skeleton(Bone[] bones) public XivSkeleton(Bone[] bones)
{ {
Bones = bones; Bones = bones;
} }
@ -23,3 +24,10 @@ public class Skeleton
public Vector3 Translation; public Vector3 Translation;
} }
} }
public struct GltfSkeleton
{
public NodeBuilder Root;
public NodeBuilder[] Joints;
public Dictionary<string, int> Names;
}

View file

@ -2,7 +2,7 @@ using Dalamud.Plugin.Services;
using OtterGui.Tasks; using OtterGui.Tasks;
using Penumbra.Collections.Manager; using Penumbra.Collections.Manager;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.Import.Modules; using Penumbra.Import.Models.Export;
using SharpGLTF.Scenes; using SharpGLTF.Scenes;
using SharpGLTF.Transforms; using SharpGLTF.Transforms;
@ -73,36 +73,18 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
public void Execute(CancellationToken cancel) public void Execute(CancellationToken cancel)
{ {
var xivSkeleton = BuildSkeleton(cancel);
var model = ModelExporter.Export(_mdl, xivSkeleton);
var scene = new SceneBuilder(); var scene = new SceneBuilder();
model.AddToScene(scene);
var skeleton = BuildSkeleton(cancel); var gltfModel = scene.ToGltf2();
if (skeleton != null) gltfModel.SaveGLTF(_outputPath);
scene.AddNode(skeleton.Value.Root);
// TODO: group by LoD in output tree
for (byte lodIndex = 0; lodIndex < _mdl.LodCount; lodIndex++)
{
var lod = _mdl.Lods[lodIndex];
// TODO: consider other types of mesh?
for (ushort meshOffset = 0; meshOffset < lod.MeshCount; meshOffset++)
{
var meshBuilders = MeshConverter.ToGltf(_mdl, lodIndex, (ushort)(lod.MeshIndex + meshOffset), skeleton?.Names);
// TODO: use a value from the mesh converter for this check, rather than assuming that it has joints
foreach (var meshBuilder in meshBuilders)
if (skeleton == null)
scene.AddRigidMesh(meshBuilder, Matrix4x4.Identity);
else
scene.AddSkinnedMesh(meshBuilder, Matrix4x4.Identity, skeleton?.Joints);
}
}
var model = scene.ToGltf2();
model.SaveGLTF(_outputPath);
} }
// TODO: this should be moved to a seperate model converter or something // TODO: this should be moved to a seperate model converter or something
private (NodeBuilder Root, NodeBuilder[] Joints, Dictionary<string, int> Names)? BuildSkeleton(CancellationToken cancel) private XivSkeleton? BuildSkeleton(CancellationToken cancel)
{ {
if (_sklb == null) if (_sklb == null)
return null; return null;
@ -117,40 +99,7 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
var skeletonConverter = new SkeletonConverter(); var skeletonConverter = new SkeletonConverter();
var skeleton = skeletonConverter.FromXml(xml); var skeleton = skeletonConverter.FromXml(xml);
// this is (less) atrocious return skeleton;
NodeBuilder? root = null;
var names = new Dictionary<string, int>();
var joints = new List<NodeBuilder>();
for (var boneIndex = 0; boneIndex < skeleton.Bones.Length; boneIndex++)
{
var bone = skeleton.Bones[boneIndex];
if (names.ContainsKey(bone.Name)) continue;
var node = new NodeBuilder(bone.Name);
names[bone.Name] = joints.Count;
joints.Add(node);
node.SetLocalTransform(new AffineTransform(
bone.Transform.Scale,
bone.Transform.Rotation,
bone.Transform.Translation
), false);
if (bone.ParentIndex == -1)
{
root = node;
continue;
}
var parent = joints[names[skeleton.Bones[bone.ParentIndex].Name]];
parent.AddNode(node);
}
if (root == null)
return null;
return (root, joints.ToArray(), names);
} }
public bool Equals(IAction? other) public bool Equals(IAction? other)

View file

@ -1,12 +1,13 @@
using System.Xml; using System.Xml;
using OtterGui; using OtterGui;
using Penumbra.Import.Models.Export;
namespace Penumbra.Import.Models; namespace Penumbra.Import.Models;
// TODO: tempted to say that this living here is more okay? that or next to havok converter, wherever that ends up. // TODO: tempted to say that this living here is more okay? that or next to havok converter, wherever that ends up.
public class SkeletonConverter public class SkeletonConverter
{ {
public Skeleton FromXml(string xml) public XivSkeleton FromXml(string xml)
{ {
var document = new XmlDocument(); var document = new XmlDocument();
document.LoadXml(xml); document.LoadXml(xml);
@ -29,7 +30,7 @@ public class SkeletonConverter
.Select(values => .Select(values =>
{ {
var (transform, parentIndex, name) = values; var (transform, parentIndex, name) = values;
return new Skeleton.Bone() return new XivSkeleton.Bone()
{ {
Transform = transform, Transform = transform,
ParentIndex = parentIndex, ParentIndex = parentIndex,
@ -38,7 +39,7 @@ public class SkeletonConverter
}) })
.ToArray(); .ToArray();
return new Skeleton(bones); return new XivSkeleton(bones);
} }
/// <summary>Get the main skeleton ID for a given skeleton document.</summary> /// <summary>Get the main skeleton ID for a given skeleton document.</summary>
@ -57,14 +58,14 @@ public class SkeletonConverter
/// <summary>Read the reference pose transforms for a skeleton.</summary> /// <summary>Read the reference pose transforms for a skeleton.</summary>
/// <param name="node">XML node for the skeleton.</param> /// <param name="node">XML node for the skeleton.</param>
private Skeleton.Transform[] ReadReferencePose(XmlNode node) private XivSkeleton.Transform[] ReadReferencePose(XmlNode node)
{ {
return ReadArray( return ReadArray(
CheckExists(node.SelectSingleNode("array[@name='referencePose']")), CheckExists(node.SelectSingleNode("array[@name='referencePose']")),
node => node =>
{ {
var raw = ReadVec12(node); var raw = ReadVec12(node);
return new Skeleton.Transform() return new XivSkeleton.Transform()
{ {
Translation = new(raw[0], raw[1], raw[2]), Translation = new(raw[0], raw[1], raw[2]),
Rotation = new(raw[4], raw[5], raw[6], raw[7]), Rotation = new(raw[4], raw[5], raw[6], raw[7]),