mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Move a few things to export subdir
This commit is contained in:
parent
6a2b802196
commit
551c25a64c
5 changed files with 169 additions and 87 deletions
|
|
@ -6,15 +6,39 @@ using SharpGLTF.Geometry;
|
|||
using SharpGLTF.Geometry.VertexTypes;
|
||||
using SharpGLTF.IO;
|
||||
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);
|
||||
return self.BuildMesh();
|
||||
private IMeshBuilder<MaterialBuilder>[] _meshes;
|
||||
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;
|
||||
|
|
@ -22,14 +46,14 @@ public sealed class MeshConverter
|
|||
private readonly MdlFile _mdl;
|
||||
private readonly byte _lod;
|
||||
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 Type _geometryType;
|
||||
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;
|
||||
_lod = lod;
|
||||
|
|
@ -49,7 +73,7 @@ public sealed class MeshConverter
|
|||
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...
|
||||
var xivBoneTable = _mdl.BoneTables[Mesh.BoneTableIndex];
|
||||
var xivBoneTable = _mdl.BoneTables[XivMesh.BoneTableIndex];
|
||||
|
||||
var indexMap = new Dictionary<ushort, int>();
|
||||
|
||||
|
|
@ -66,16 +90,16 @@ public sealed class MeshConverter
|
|||
return indexMap;
|
||||
}
|
||||
|
||||
private IMeshBuilder<MaterialBuilder>[] BuildMesh()
|
||||
private IMeshBuilder<MaterialBuilder>[] BuildMeshes()
|
||||
{
|
||||
var indices = BuildIndices();
|
||||
var vertices = BuildVertices();
|
||||
|
||||
// TODO: handle submeshCount = 0
|
||||
// TODO: handle SubMeshCount = 0
|
||||
|
||||
return _mdl.SubMeshes
|
||||
.Skip(Mesh.SubMeshIndex)
|
||||
.Take(Mesh.SubMeshCount)
|
||||
.Skip(XivMesh.SubMeshIndex)
|
||||
.Take(XivMesh.SubMeshCount)
|
||||
.Select(submesh => BuildSubMesh(submesh, indices, vertices))
|
||||
.ToArray();
|
||||
}
|
||||
|
|
@ -83,7 +107,7 @@ public sealed class MeshConverter
|
|||
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.
|
||||
var startIndex = (int)(submesh.IndexOffset - Mesh.StartIndex);
|
||||
var startIndex = (int)(submesh.IndexOffset - XivMesh.StartIndex);
|
||||
|
||||
var meshBuilderType = typeof(MeshBuilder<,,,>).MakeGenericType(
|
||||
typeof(MaterialBuilder),
|
||||
|
|
@ -124,7 +148,7 @@ public sealed class MeshConverter
|
|||
var shapeValues = _mdl.ShapeMeshes
|
||||
.Skip(shape.ShapeMeshStartIndex[_lod])
|
||||
.Take(shape.ShapeMeshCount[_lod])
|
||||
.Where(shapeMesh => shapeMesh.MeshIndexOffset == Mesh.StartIndex)
|
||||
.Where(shapeMesh => shapeMesh.MeshIndexOffset == XivMesh.StartIndex)
|
||||
.SelectMany(shapeMesh =>
|
||||
_mdl.ShapeValues
|
||||
.Skip((int)shapeMesh.ShapeValueOffset)
|
||||
|
|
@ -158,8 +182,8 @@ public sealed class MeshConverter
|
|||
private IReadOnlyList<ushort> BuildIndices()
|
||||
{
|
||||
var reader = new BinaryReader(new MemoryStream(_mdl.RemainingData));
|
||||
reader.Seek(_mdl.IndexOffset[_lod] + Mesh.StartIndex * sizeof(ushort));
|
||||
return reader.ReadStructuresAsArray<ushort>((int)Mesh.IndexCount);
|
||||
reader.Seek(_mdl.IndexOffset[_lod] + XivMesh.StartIndex * sizeof(ushort));
|
||||
return reader.ReadStructuresAsArray<ushort>((int)XivMesh.IndexCount);
|
||||
}
|
||||
|
||||
private IReadOnlyList<IVertexBuilder> BuildVertices()
|
||||
|
|
@ -172,7 +196,7 @@ public sealed class MeshConverter
|
|||
for (var streamIndex = 0; streamIndex < MaximumMeshBufferStreams; streamIndex++)
|
||||
{
|
||||
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
|
||||
|
|
@ -183,7 +207,7 @@ public sealed class MeshConverter
|
|||
var vertices = new List<IVertexBuilder>();
|
||||
|
||||
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();
|
||||
|
||||
100
Penumbra/Import/Models/Export/ModelExporter.cs
Normal file
100
Penumbra/Import/Models/Export/ModelExporter.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
public class Skeleton
|
||||
namespace Penumbra.Import.Models.Export;
|
||||
|
||||
public class XivSkeleton
|
||||
{
|
||||
public Bone[] Bones;
|
||||
|
||||
public Skeleton(Bone[] bones)
|
||||
public XivSkeleton(Bone[] bones)
|
||||
{
|
||||
Bones = bones;
|
||||
}
|
||||
|
|
@ -23,3 +24,10 @@ public class Skeleton
|
|||
public Vector3 Translation;
|
||||
}
|
||||
}
|
||||
|
||||
public struct GltfSkeleton
|
||||
{
|
||||
public NodeBuilder Root;
|
||||
public NodeBuilder[] Joints;
|
||||
public Dictionary<string, int> Names;
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ using Dalamud.Plugin.Services;
|
|||
using OtterGui.Tasks;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Import.Modules;
|
||||
using Penumbra.Import.Models.Export;
|
||||
using SharpGLTF.Scenes;
|
||||
using SharpGLTF.Transforms;
|
||||
|
||||
|
|
@ -73,36 +73,18 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
|
|||
|
||||
public void Execute(CancellationToken cancel)
|
||||
{
|
||||
var xivSkeleton = BuildSkeleton(cancel);
|
||||
var model = ModelExporter.Export(_mdl, xivSkeleton);
|
||||
|
||||
var scene = new SceneBuilder();
|
||||
model.AddToScene(scene);
|
||||
|
||||
var skeleton = BuildSkeleton(cancel);
|
||||
if (skeleton != null)
|
||||
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);
|
||||
var gltfModel = scene.ToGltf2();
|
||||
gltfModel.SaveGLTF(_outputPath);
|
||||
}
|
||||
|
||||
// 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)
|
||||
return null;
|
||||
|
|
@ -117,40 +99,7 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
|
|||
var skeletonConverter = new SkeletonConverter();
|
||||
var skeleton = skeletonConverter.FromXml(xml);
|
||||
|
||||
// this is (less) atrocious
|
||||
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);
|
||||
return skeleton;
|
||||
}
|
||||
|
||||
public bool Equals(IAction? other)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
using System.Xml;
|
||||
using OtterGui;
|
||||
using Penumbra.Import.Models.Export;
|
||||
|
||||
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.
|
||||
public class SkeletonConverter
|
||||
{
|
||||
public Skeleton FromXml(string xml)
|
||||
public XivSkeleton FromXml(string xml)
|
||||
{
|
||||
var document = new XmlDocument();
|
||||
document.LoadXml(xml);
|
||||
|
|
@ -29,7 +30,7 @@ public class SkeletonConverter
|
|||
.Select(values =>
|
||||
{
|
||||
var (transform, parentIndex, name) = values;
|
||||
return new Skeleton.Bone()
|
||||
return new XivSkeleton.Bone()
|
||||
{
|
||||
Transform = transform,
|
||||
ParentIndex = parentIndex,
|
||||
|
|
@ -38,7 +39,7 @@ public class SkeletonConverter
|
|||
})
|
||||
.ToArray();
|
||||
|
||||
return new Skeleton(bones);
|
||||
return new XivSkeleton(bones);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="node">XML node for the skeleton.</param>
|
||||
private Skeleton.Transform[] ReadReferencePose(XmlNode node)
|
||||
private XivSkeleton.Transform[] ReadReferencePose(XmlNode node)
|
||||
{
|
||||
return ReadArray(
|
||||
CheckExists(node.SelectSingleNode("array[@name='referencePose']")),
|
||||
node =>
|
||||
{
|
||||
var raw = ReadVec12(node);
|
||||
return new Skeleton.Transform()
|
||||
return new XivSkeleton.Transform()
|
||||
{
|
||||
Translation = new(raw[0], raw[1], raw[2]),
|
||||
Rotation = new(raw[4], raw[5], raw[6], raw[7]),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue