Some cleanup and using new features / intellisense recommendations.

This commit is contained in:
Ottermandias 2024-01-06 18:37:52 +01:00
parent 51bb9cf7cd
commit 677c9bd801
5 changed files with 117 additions and 128 deletions

View file

@ -13,24 +13,17 @@ namespace Penumbra.Import.Models.Export;
public class MeshExporter public class MeshExporter
{ {
public class Mesh public class Mesh(IEnumerable<IMeshBuilder<MaterialBuilder>> meshes, NodeBuilder[]? joints)
{ {
private IMeshBuilder<MaterialBuilder>[] _meshes;
private NodeBuilder[]? _joints;
public Mesh(IMeshBuilder<MaterialBuilder>[] meshes, NodeBuilder[]? joints)
{
_meshes = meshes;
_joints = joints;
}
public void AddToScene(SceneBuilder scene) public void AddToScene(SceneBuilder scene)
{ {
foreach (var mesh in _meshes) foreach (var mesh in meshes)
if (_joints == null) {
if (joints == null)
scene.AddRigidMesh(mesh, Matrix4x4.Identity); scene.AddRigidMesh(mesh, Matrix4x4.Identity);
else else
scene.AddSkinnedMesh(mesh, Matrix4x4.Identity, _joints); scene.AddSkinnedMesh(mesh, Matrix4x4.Identity, joints);
}
} }
} }
@ -43,9 +36,11 @@ public class MeshExporter
private const byte MaximumMeshBufferStreams = 3; private const byte MaximumMeshBufferStreams = 3;
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 XivMesh => _mdl.Meshes[_meshIndex];
private MdlStructs.MeshStruct XivMesh
=> _mdl.Meshes[_meshIndex];
private readonly Dictionary<ushort, int>? _boneIndexMap; private readonly Dictionary<ushort, int>? _boneIndexMap;
@ -53,10 +48,10 @@ public class MeshExporter
private readonly Type _materialType; private readonly Type _materialType;
private readonly Type _skinningType; private readonly Type _skinningType;
private MeshExporter(MdlFile mdl, byte lod, ushort meshIndex, Dictionary<string, int>? boneNameMap) private MeshExporter(MdlFile mdl, byte lod, ushort meshIndex, IReadOnlyDictionary<string, int>? boneNameMap)
{ {
_mdl = mdl; _mdl = mdl;
_lod = lod; _lod = lod;
_meshIndex = meshIndex; _meshIndex = meshIndex;
if (boneNameMap != null) if (boneNameMap != null)
@ -76,11 +71,12 @@ public class MeshExporter
if (_skinningType != typeof(VertexEmpty) && _boneIndexMap == null) if (_skinningType != typeof(VertexEmpty) && _boneIndexMap == null)
Penumbra.Log.Warning($"Mesh {meshIndex} has skinned vertex usages but no bone information was provided."); Penumbra.Log.Warning($"Mesh {meshIndex} has skinned vertex usages but no bone information was provided.");
Penumbra.Log.Debug($"Mesh {meshIndex} using vertex types geometry: {_geometryType.Name}, material: {_materialType.Name}, skinning: {_skinningType.Name}"); Penumbra.Log.Debug(
$"Mesh {meshIndex} using vertex types geometry: {_geometryType.Name}, material: {_materialType.Name}, skinning: {_skinningType.Name}");
} }
/// <summary> Build a mapping between indices in this mesh's bone table (if any), and the glTF joint indices provdied. </summary> /// <summary> Build a mapping between indices in this mesh's bone table (if any), and the glTF joint indices provided. </summary>
private Dictionary<ushort, int>? BuildBoneIndexMap(Dictionary<string, int> boneNameMap) private Dictionary<ushort, int>? BuildBoneIndexMap(IReadOnlyDictionary<string, int> boneNameMap)
{ {
// A BoneTableIndex of 255 means that this mesh is not skinned. // A BoneTableIndex of 255 means that this mesh is not skinned.
if (XivMesh.BoneTableIndex == 255) if (XivMesh.BoneTableIndex == 255)
@ -105,11 +101,10 @@ public class MeshExporter
/// <summary> Build glTF meshes for this XIV mesh. </summary> /// <summary> Build glTF meshes for this XIV mesh. </summary>
private IMeshBuilder<MaterialBuilder>[] BuildMeshes() private IMeshBuilder<MaterialBuilder>[] BuildMeshes()
{ {
var indices = BuildIndices(); var indices = BuildIndices();
var vertices = BuildVertices(); var vertices = BuildVertices();
// NOTE: Index indices are specified relative to the LOD's 0, but we're reading chunks for each mesh, so we're specifying the index base relative to the mesh's base. // NOTE: Index indices are specified relative to the LOD's 0, but we're reading chunks for each mesh, so we're specifying the index base relative to the mesh's base.
if (XivMesh.SubMeshCount == 0) if (XivMesh.SubMeshCount == 0)
return [BuildMesh($"mesh {_meshIndex}", indices, vertices, 0, (int)XivMesh.IndexCount)]; return [BuildMesh($"mesh {_meshIndex}", indices, vertices, 0, (int)XivMesh.IndexCount)];
@ -117,7 +112,8 @@ public class MeshExporter
.Skip(XivMesh.SubMeshIndex) .Skip(XivMesh.SubMeshIndex)
.Take(XivMesh.SubMeshCount) .Take(XivMesh.SubMeshCount)
.WithIndex() .WithIndex()
.Select(submesh => BuildMesh($"mesh {_meshIndex}.{submesh.Index}", indices, vertices, (int)(submesh.Value.IndexOffset - XivMesh.StartIndex), (int)submesh.Value.IndexCount)) .Select(subMesh => BuildMesh($"mesh {_meshIndex}.{subMesh.Index}", indices, vertices,
(int)(subMesh.Value.IndexOffset - XivMesh.StartIndex), (int)subMesh.Value.IndexCount))
.ToArray(); .ToArray();
} }
@ -161,7 +157,7 @@ public class MeshExporter
} }
var primitiveVertices = meshBuilder.Primitives.First().Vertices; var primitiveVertices = meshBuilder.Primitives.First().Vertices;
var shapeNames = new List<string>(); var shapeNames = new List<string>();
foreach (var shape in _mdl.Shapes) foreach (var shape in _mdl.Shapes)
{ {
@ -177,24 +173,28 @@ public class MeshExporter
) )
.Where(shapeValue => .Where(shapeValue =>
shapeValue.BaseIndicesIndex >= indexBase shapeValue.BaseIndicesIndex >= indexBase
&& shapeValue.BaseIndicesIndex < indexBase + indexCount && shapeValue.BaseIndicesIndex < indexBase + indexCount
) )
.ToList(); .ToList();
if (shapeValues.Count == 0) continue; if (shapeValues.Count == 0)
continue;
var morphBuilder = meshBuilder.UseMorphTarget(shapeNames.Count); var morphBuilder = meshBuilder.UseMorphTarget(shapeNames.Count);
shapeNames.Add(shape.ShapeName); shapeNames.Add(shape.ShapeName);
foreach (var shapeValue in shapeValues) foreach (var shapeValue in shapeValues)
{
morphBuilder.SetVertex( morphBuilder.SetVertex(
primitiveVertices[gltfIndices[shapeValue.BaseIndicesIndex - indexBase]].GetGeometry(), primitiveVertices[gltfIndices[shapeValue.BaseIndicesIndex - indexBase]].GetGeometry(),
vertices[shapeValue.ReplacingVertexIndex].GetGeometry() vertices[shapeValue.ReplacingVertexIndex].GetGeometry()
); );
}
} }
meshBuilder.Extras = JsonContent.CreateFrom(new Dictionary<string, object>() { meshBuilder.Extras = JsonContent.CreateFrom(new Dictionary<string, object>()
{"targetNames", shapeNames} {
{ "targetNames", shapeNames },
}); });
return meshBuilder; return meshBuilder;
@ -249,23 +249,25 @@ public class MeshExporter
} }
/// <summary> Read a vertex attribute of the specified type from a vertex buffer stream. </summary> /// <summary> Read a vertex attribute of the specified type from a vertex buffer stream. </summary>
private object ReadVertexAttribute(MdlFile.VertexType type, BinaryReader reader) private static object ReadVertexAttribute(MdlFile.VertexType type, BinaryReader reader)
{ {
return type switch return type switch
{ {
MdlFile.VertexType.Single3 => new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()), MdlFile.VertexType.Single3 => new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()),
MdlFile.VertexType.Single4 => new Vector4(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()), MdlFile.VertexType.Single4 => new Vector4(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()),
MdlFile.VertexType.UInt => reader.ReadBytes(4), MdlFile.VertexType.UInt => reader.ReadBytes(4),
MdlFile.VertexType.ByteFloat4 => new Vector4(reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f), MdlFile.VertexType.ByteFloat4 => new Vector4(reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f,
reader.ReadByte() / 255f),
MdlFile.VertexType.Half2 => new Vector2((float)reader.ReadHalf(), (float)reader.ReadHalf()), MdlFile.VertexType.Half2 => new Vector2((float)reader.ReadHalf(), (float)reader.ReadHalf()),
MdlFile.VertexType.Half4 => new Vector4((float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf()), MdlFile.VertexType.Half4 => new Vector4((float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf(),
(float)reader.ReadHalf()),
_ => throw new ArgumentOutOfRangeException() _ => throw new ArgumentOutOfRangeException(),
}; };
} }
/// <summary> Get the vertex geometry type for this mesh's vertex usages. </summary> /// <summary> Get the vertex geometry type for this mesh's vertex usages. </summary>
private Type GetGeometryType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages) private static Type GetGeometryType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages)
{ {
if (!usages.ContainsKey(MdlFile.VertexUsage.Position)) if (!usages.ContainsKey(MdlFile.VertexUsage.Position))
throw new Exception("Mesh does not contain position vertex elements."); throw new Exception("Mesh does not contain position vertex elements.");
@ -304,16 +306,16 @@ public class MeshExporter
} }
/// <summary> Get the vertex material type for this mesh's vertex usages. </summary> /// <summary> Get the vertex material type for this mesh's vertex usages. </summary>
private Type GetMaterialType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages) private static Type GetMaterialType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages)
{ {
var uvCount = 0; var uvCount = 0;
if (usages.TryGetValue(MdlFile.VertexUsage.UV, out var type)) if (usages.TryGetValue(MdlFile.VertexUsage.UV, out var type))
uvCount = type switch uvCount = type switch
{ {
MdlFile.VertexType.Half2 => 1, MdlFile.VertexType.Half2 => 1,
MdlFile.VertexType.Half4 => 2, MdlFile.VertexType.Half4 => 2,
MdlFile.VertexType.Single4 => 2, MdlFile.VertexType.Single4 => 2,
_ => throw new Exception($"Unexpected UV vertex type {type}.") _ => throw new Exception($"Unexpected UV vertex type {type}."),
}; };
var materialUsages = ( var materialUsages = (
@ -323,11 +325,11 @@ public class MeshExporter
return materialUsages switch return materialUsages switch
{ {
(2, true) => typeof(VertexColor1Texture2), (2, true) => typeof(VertexColor1Texture2),
(2, false) => typeof(VertexTexture2), (2, false) => typeof(VertexTexture2),
(1, true) => typeof(VertexColor1Texture1), (1, true) => typeof(VertexColor1Texture1),
(1, false) => typeof(VertexTexture1), (1, false) => typeof(VertexTexture1),
(0, true) => typeof(VertexColor1), (0, true) => typeof(VertexColor1),
(0, false) => typeof(VertexEmpty), (0, false) => typeof(VertexEmpty),
_ => throw new Exception("Unreachable."), _ => throw new Exception("Unreachable."),
@ -377,7 +379,7 @@ public class MeshExporter
} }
/// <summary> Get the vertex skinning type for this mesh's vertex usages. </summary> /// <summary> Get the vertex skinning type for this mesh's vertex usages. </summary>
private Type GetSkinningType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages) private static Type GetSkinningType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages)
{ {
if (usages.ContainsKey(MdlFile.VertexUsage.BlendWeights) && usages.ContainsKey(MdlFile.VertexUsage.BlendIndices)) if (usages.ContainsKey(MdlFile.VertexUsage.BlendWeights) && usages.ContainsKey(MdlFile.VertexUsage.BlendIndices))
return typeof(VertexJoints4); return typeof(VertexJoints4);
@ -400,7 +402,8 @@ public class MeshExporter
var weights = ToVector4(attributes[MdlFile.VertexUsage.BlendWeights]); var weights = ToVector4(attributes[MdlFile.VertexUsage.BlendWeights]);
var bindings = Enumerable.Range(0, 4) var bindings = Enumerable.Range(0, 4)
.Select(bindingIndex => { .Select(bindingIndex =>
{
// NOTE: I've not seen any files that throw this error that aren't completely broken. // NOTE: I've not seen any files that throw this error that aren't completely broken.
var xivBoneIndex = indices[bindingIndex]; var xivBoneIndex = indices[bindingIndex];
if (!_boneIndexMap.TryGetValue(xivBoneIndex, out var jointIndex)) if (!_boneIndexMap.TryGetValue(xivBoneIndex, out var jointIndex))
@ -417,44 +420,44 @@ public class MeshExporter
/// <summary> Clamps any tangent W value other than 1 to -1. </summary> /// <summary> Clamps any tangent W value other than 1 to -1. </summary>
/// <remarks> Some XIV models seemingly store -1 as 0, this patches over that. </remarks> /// <remarks> Some XIV models seemingly store -1 as 0, this patches over that. </remarks>
private Vector4 FixTangentVector(Vector4 tangent) private static Vector4 FixTangentVector(Vector4 tangent)
=> tangent with { W = tangent.W == 1 ? 1 : -1 }; => tangent with { W = tangent.W == 1 ? 1 : -1 };
/// <summary> Convert a vertex attribute value to a Vector2. Supported inputs are Vector2, Vector3, and Vector4. </summary> /// <summary> Convert a vertex attribute value to a Vector2. Supported inputs are Vector2, Vector3, and Vector4. </summary>
private Vector2 ToVector2(object data) private static Vector2 ToVector2(object data)
=> data switch => data switch
{ {
Vector2 v2 => v2, Vector2 v2 => v2,
Vector3 v3 => new Vector2(v3.X, v3.Y), Vector3 v3 => new Vector2(v3.X, v3.Y),
Vector4 v4 => new Vector2(v4.X, v4.Y), Vector4 v4 => new Vector2(v4.X, v4.Y),
_ => throw new ArgumentOutOfRangeException($"Invalid Vector2 input {data}") _ => throw new ArgumentOutOfRangeException($"Invalid Vector2 input {data}"),
}; };
/// <summary> Convert a vertex attribute value to a Vector3. Supported inputs are Vector2, Vector3, and Vector4. </summary> /// <summary> Convert a vertex attribute value to a Vector3. Supported inputs are Vector2, Vector3, and Vector4. </summary>
private Vector3 ToVector3(object data) private static Vector3 ToVector3(object data)
=> data switch => data switch
{ {
Vector2 v2 => new Vector3(v2.X, v2.Y, 0), Vector2 v2 => new Vector3(v2.X, v2.Y, 0),
Vector3 v3 => v3, Vector3 v3 => v3,
Vector4 v4 => new Vector3(v4.X, v4.Y, v4.Z), Vector4 v4 => new Vector3(v4.X, v4.Y, v4.Z),
_ => throw new ArgumentOutOfRangeException($"Invalid Vector3 input {data}") _ => throw new ArgumentOutOfRangeException($"Invalid Vector3 input {data}"),
}; };
/// <summary> Convert a vertex attribute value to a Vector4. Supported inputs are Vector2, Vector3, and Vector4. </summary> /// <summary> Convert a vertex attribute value to a Vector4. Supported inputs are Vector2, Vector3, and Vector4. </summary>
private Vector4 ToVector4(object data) private static Vector4 ToVector4(object data)
=> data switch => data switch
{ {
Vector2 v2 => new Vector4(v2.X, v2.Y, 0, 0), Vector2 v2 => new Vector4(v2.X, v2.Y, 0, 0),
Vector3 v3 => new Vector4(v3.X, v3.Y, v3.Z, 1), Vector3 v3 => new Vector4(v3.X, v3.Y, v3.Z, 1),
Vector4 v4 => v4, Vector4 v4 => v4,
_ => throw new ArgumentOutOfRangeException($"Invalid Vector3 input {data}") _ => throw new ArgumentOutOfRangeException($"Invalid Vector3 input {data}"),
}; };
/// <summary> Convert a vertex attribute value to a byte array. </summary> /// <summary> Convert a vertex attribute value to a byte array. </summary>
private byte[] ToByteArray(object data) private static byte[] ToByteArray(object data)
=> data switch => data switch
{ {
byte[] value => value, byte[] value => value,
_ => throw new ArgumentOutOfRangeException($"Invalid byte[] input {data}") _ => throw new ArgumentOutOfRangeException($"Invalid byte[] input {data}"),
}; };
} }

View file

@ -6,26 +6,17 @@ namespace Penumbra.Import.Models.Export;
public class ModelExporter public class ModelExporter
{ {
public class Model public class Model(List<MeshExporter.Mesh> meshes, GltfSkeleton? skeleton)
{ {
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) public void AddToScene(SceneBuilder scene)
{ {
// If there's a skeleton, the root node should be added before we add any potentially skinned meshes. // If there's a skeleton, the root node should be added before we add any potentially skinned meshes.
var skeletonRoot = _skeleton?.Root; var skeletonRoot = skeleton?.Root;
if (skeletonRoot != null) if (skeletonRoot != null)
scene.AddNode(skeletonRoot); scene.AddNode(skeletonRoot);
// Add all the meshes to the scene. // Add all the meshes to the scene.
foreach (var mesh in _meshes) foreach (var mesh in meshes)
mesh.AddToScene(scene); mesh.AddToScene(scene);
} }
} }
@ -64,10 +55,8 @@ public class ModelExporter
NodeBuilder? root = null; NodeBuilder? root = null;
var names = new Dictionary<string, int>(); var names = new Dictionary<string, int>();
var joints = new List<NodeBuilder>(); var joints = new List<NodeBuilder>();
for (var boneIndex = 0; boneIndex < skeleton.Bones.Length; boneIndex++) foreach (var bone in skeleton.Bones)
{ {
var bone = skeleton.Bones[boneIndex];
if (names.ContainsKey(bone.Name)) continue; if (names.ContainsKey(bone.Name)) continue;
var node = new NodeBuilder(bone.Name); var node = new NodeBuilder(bone.Name);
@ -93,10 +82,10 @@ public class ModelExporter
if (root == null) if (root == null)
return null; return null;
return new() return new GltfSkeleton
{ {
Root = root, Root = root,
Joints = joints.ToArray(), Joints = [.. joints],
Names = names, Names = names,
}; };
} }

View file

@ -3,14 +3,9 @@ using SharpGLTF.Scenes;
namespace Penumbra.Import.Models.Export; namespace Penumbra.Import.Models.Export;
/// <summary> Representation of a skeleton within XIV. </summary> /// <summary> Representation of a skeleton within XIV. </summary>
public class XivSkeleton public class XivSkeleton(XivSkeleton.Bone[] bones)
{ {
public Bone[] Bones; public Bone[] Bones = bones;
public XivSkeleton(Bone[] bones)
{
Bones = bones;
}
public struct Bone public struct Bone
{ {

View file

@ -16,17 +16,18 @@ public static unsafe class HavokConverter
/// <param name="hkx"> A byte array representing the .hkx file. </param> /// <param name="hkx"> A byte array representing the .hkx file. </param>
public static string HkxToXml(byte[] hkx) public static string HkxToXml(byte[] hkx)
{ {
const hkSerializeUtil.SaveOptionBits options = hkSerializeUtil.SaveOptionBits.SerializeIgnoredMembers
| hkSerializeUtil.SaveOptionBits.TextFormat
| hkSerializeUtil.SaveOptionBits.WriteAttributes;
var tempHkx = CreateTempFile(); var tempHkx = CreateTempFile();
File.WriteAllBytes(tempHkx, hkx); File.WriteAllBytes(tempHkx, hkx);
var resource = Read(tempHkx); var resource = Read(tempHkx);
File.Delete(tempHkx); File.Delete(tempHkx);
if (resource == null) throw new Exception("Failed to read havok file."); if (resource == null)
throw new Exception("Failed to read havok file.");
var options = hkSerializeUtil.SaveOptionBits.SerializeIgnoredMembers
| hkSerializeUtil.SaveOptionBits.TextFormat
| hkSerializeUtil.SaveOptionBits.WriteAttributes;
var file = Write(resource, options); var file = Write(resource, options);
file.Close(); file.Close();
@ -41,17 +42,19 @@ public static unsafe class HavokConverter
/// <param name="xml"> A string representing the .xml file. </param> /// <param name="xml"> A string representing the .xml file. </param>
public static byte[] XmlToHkx(string xml) public static byte[] XmlToHkx(string xml)
{ {
const hkSerializeUtil.SaveOptionBits options = hkSerializeUtil.SaveOptionBits.SerializeIgnoredMembers
| hkSerializeUtil.SaveOptionBits.WriteAttributes;
var tempXml = CreateTempFile(); var tempXml = CreateTempFile();
File.WriteAllText(tempXml, xml); File.WriteAllText(tempXml, xml);
var resource = Read(tempXml); var resource = Read(tempXml);
File.Delete(tempXml); File.Delete(tempXml);
if (resource == null) throw new Exception("Failed to read havok file."); if (resource == null)
throw new Exception("Failed to read havok file.");
var options = hkSerializeUtil.SaveOptionBits.SerializeIgnoredMembers
| hkSerializeUtil.SaveOptionBits.WriteAttributes;
g
var file = Write(resource, options); var file = Write(resource, options);
file.Close(); file.Close();
@ -74,7 +77,7 @@ public static unsafe class HavokConverter
var builtinTypeRegistry = hkBuiltinTypeRegistry.Instance(); var builtinTypeRegistry = hkBuiltinTypeRegistry.Instance();
var loadOptions = stackalloc hkSerializeUtil.LoadOptions[1]; var loadOptions = stackalloc hkSerializeUtil.LoadOptions[1];
loadOptions->Flags = new() { Storage = (int)hkSerializeUtil.LoadOptionBits.Default }; loadOptions->Flags = new hkFlags<hkSerializeUtil.LoadOptionBits, int> { Storage = (int)hkSerializeUtil.LoadOptionBits.Default };
loadOptions->ClassNameRegistry = builtinTypeRegistry->GetClassNameRegistry(); loadOptions->ClassNameRegistry = builtinTypeRegistry->GetClassNameRegistry();
loadOptions->TypeInfoRegistry = builtinTypeRegistry->GetTypeInfoRegistry(); loadOptions->TypeInfoRegistry = builtinTypeRegistry->GetTypeInfoRegistry();
@ -92,37 +95,42 @@ public static unsafe class HavokConverter
) )
{ {
var tempFile = CreateTempFile(); var tempFile = CreateTempFile();
var path = Marshal.StringToHGlobalAnsi(tempFile); var path = Marshal.StringToHGlobalAnsi(tempFile);
var oStream = new hkOstream(); var oStream = new hkOstream();
oStream.Ctor((byte*)path); oStream.Ctor((byte*)path);
var result = stackalloc hkResult[1]; var result = stackalloc hkResult[1];
var saveOptions = new hkSerializeUtil.SaveOptions() var saveOptions = new hkSerializeUtil.SaveOptions()
{ {
Flags = new() { Storage = (int)optionBits } Flags = new hkFlags<hkSerializeUtil.SaveOptionBits, int> { Storage = (int)optionBits },
}; };
var builtinTypeRegistry = hkBuiltinTypeRegistry.Instance(); var builtinTypeRegistry = hkBuiltinTypeRegistry.Instance();
var classNameRegistry = builtinTypeRegistry->GetClassNameRegistry(); var classNameRegistry = builtinTypeRegistry->GetClassNameRegistry();
var typeInfoRegistry = builtinTypeRegistry->GetTypeInfoRegistry(); var typeInfoRegistry = builtinTypeRegistry->GetTypeInfoRegistry();
try try
{ {
var name = "hkRootLevelContainer"; const string name = "hkRootLevelContainer";
var resourcePtr = (hkRootLevelContainer*)resource->GetContentsPointer(name, typeInfoRegistry); var resourcePtr = (hkRootLevelContainer*)resource->GetContentsPointer(name, typeInfoRegistry);
if (resourcePtr == null) throw new Exception("Failed to retrieve havok root level container resource."); if (resourcePtr == null)
throw new Exception("Failed to retrieve havok root level container resource.");
var hkRootLevelContainerClass = classNameRegistry->GetClassByName(name); var hkRootLevelContainerClass = classNameRegistry->GetClassByName(name);
if (hkRootLevelContainerClass == null) throw new Exception("Failed to retrieve havok root level container type."); if (hkRootLevelContainerClass == null)
throw new Exception("Failed to retrieve havok root level container type.");
hkSerializeUtil.Save(result, resourcePtr, hkRootLevelContainerClass, oStream.StreamWriter.ptr, saveOptions); hkSerializeUtil.Save(result, resourcePtr, hkRootLevelContainerClass, oStream.StreamWriter.ptr, saveOptions);
} }
finally { oStream.Dtor(); } finally
{
oStream.Dtor();
}
if (result->Result == hkResult.hkResultEnum.Failure) throw new Exception("Failed to serialize havok file."); if (result->Result == hkResult.hkResultEnum.Failure)
throw new Exception("Failed to serialize havok file.");
return new FileStream(tempFile, FileMode.Open); return new FileStream(tempFile, FileMode.Open);
} }

View file

@ -15,16 +15,15 @@ public static class SkeletonConverter
var mainSkeletonId = GetMainSkeletonId(document); var mainSkeletonId = GetMainSkeletonId(document);
var skeletonNode = document.SelectSingleNode($"/hktagfile/object[@type='hkaSkeleton'][@id='{mainSkeletonId}']"); var skeletonNode = document.SelectSingleNode($"/hktagfile/object[@type='hkaSkeleton'][@id='{mainSkeletonId}']")
if (skeletonNode == null) ?? throw new InvalidDataException($"Failed to find skeleton with id {mainSkeletonId}.");
throw new InvalidDataException($"Failed to find skeleton with id {mainSkeletonId}.");
var referencePose = ReadReferencePose(skeletonNode); var referencePose = ReadReferencePose(skeletonNode);
var parentIndices = ReadParentIndices(skeletonNode); var parentIndices = ReadParentIndices(skeletonNode);
var boneNames = ReadBoneNames(skeletonNode); var boneNames = ReadBoneNames(skeletonNode);
if (boneNames.Length != parentIndices.Length || boneNames.Length != referencePose.Length) if (boneNames.Length != parentIndices.Length || boneNames.Length != referencePose.Length)
throw new InvalidDataException($"Mismatch in bone value array lengths: names({boneNames.Length}) parents({parentIndices.Length}) pose({referencePose.Length})"); throw new InvalidDataException(
$"Mismatch in bone value array lengths: names({boneNames.Length}) parents({parentIndices.Length}) pose({referencePose.Length})");
var bones = referencePose var bones = referencePose
.Zip(parentIndices, boneNames) .Zip(parentIndices, boneNames)
@ -33,9 +32,9 @@ public static class SkeletonConverter
var (transform, parentIndex, name) = values; var (transform, parentIndex, name) = values;
return new XivSkeleton.Bone() return new XivSkeleton.Bone()
{ {
Transform = transform, Transform = transform,
ParentIndex = parentIndex, ParentIndex = parentIndex,
Name = name, Name = name,
}; };
}) })
.ToArray(); .ToArray();
@ -63,14 +62,14 @@ public static class SkeletonConverter
{ {
return ReadArray( return ReadArray(
CheckExists(node.SelectSingleNode("array[@name='referencePose']")), CheckExists(node.SelectSingleNode("array[@name='referencePose']")),
node => n =>
{ {
var raw = ReadVec12(node); var raw = ReadVec12(n);
return new XivSkeleton.Transform() return new XivSkeleton.Transform()
{ {
Translation = new(raw[0], raw[1], raw[2]), Translation = new Vector3(raw[0], raw[1], raw[2]),
Rotation = new(raw[4], raw[5], raw[6], raw[7]), Rotation = new Quaternion(raw[4], raw[5], raw[6], raw[7]),
Scale = new(raw[8], raw[9], raw[10]), Scale = new Vector3(raw[8], raw[9], raw[10]),
}; };
} }
); );
@ -82,11 +81,11 @@ public static class SkeletonConverter
{ {
var array = node.ChildNodes var array = node.ChildNodes
.Cast<XmlNode>() .Cast<XmlNode>()
.Where(node => node.NodeType != XmlNodeType.Comment) .Where(n => n.NodeType != XmlNodeType.Comment)
.Select(node => .Select(n =>
{ {
var text = node.InnerText.Trim()[1..]; var text = n.InnerText.Trim()[1..];
// TODO: surely there's a less shit way to do this i mean seriously // TODO: surely there's a less shit way to do this I mean seriously
return BitConverter.ToSingle(BitConverter.GetBytes(int.Parse(text, NumberStyles.HexNumber))); return BitConverter.ToSingle(BitConverter.GetBytes(int.Parse(text, NumberStyles.HexNumber)));
}) })
.ToArray(); .ToArray();
@ -100,24 +99,20 @@ public static class SkeletonConverter
/// <summary> Read the bone parent relations for a skeleton. </summary> /// <summary> Read the bone parent relations for a skeleton. </summary>
/// <param name="node"> XML node for the skeleton. </param> /// <param name="node"> XML node for the skeleton. </param>
private static int[] ReadParentIndices(XmlNode node) private static int[] ReadParentIndices(XmlNode node)
{
// todo: would be neat to genericise array between bare and children // todo: would be neat to genericise array between bare and children
return CheckExists(node.SelectSingleNode("array[@name='parentIndices']")) => CheckExists(node.SelectSingleNode("array[@name='parentIndices']"))
.InnerText .InnerText
.Split(new char[] { ' ', '\n' }, StringSplitOptions.RemoveEmptyEntries) .Split((char[]) [' ', '\n'], StringSplitOptions.RemoveEmptyEntries)
.Select(int.Parse) .Select(int.Parse)
.ToArray(); .ToArray();
}
/// <summary> Read the names of bones in a skeleton. </summary> /// <summary> Read the names of bones in a skeleton. </summary>
/// <param name="node"> XML node for the skeleton. </param> /// <param name="node"> XML node for the skeleton. </param>
private static string[] ReadBoneNames(XmlNode node) private static string[] ReadBoneNames(XmlNode node)
{ => ReadArray(
return ReadArray(
CheckExists(node.SelectSingleNode("array[@name='bones']")), CheckExists(node.SelectSingleNode("array[@name='bones']")),
node => CheckExists(node.SelectSingleNode("string[@name='name']")).InnerText n => CheckExists(n.SelectSingleNode("string[@name='name']")).InnerText
); );
}
/// <summary> Read an XML tagfile array, converting it via the provided conversion function. </summary> /// <summary> Read an XML tagfile array, converting it via the provided conversion function. </summary>
/// <param name="node"> Tagfile XML array node. </param> /// <param name="node"> Tagfile XML array node. </param>
@ -125,10 +120,9 @@ public static class SkeletonConverter
private static T[] ReadArray<T>(XmlNode node, Func<XmlNode, T> convert) private static T[] ReadArray<T>(XmlNode node, Func<XmlNode, T> convert)
{ {
var element = (XmlElement)node; var element = (XmlElement)node;
var size = int.Parse(element.GetAttribute("size"));
var array = new T[size];
var size = int.Parse(element.GetAttribute("size"));
var array = new T[size];
foreach (var (childNode, index) in element.ChildNodes.Cast<XmlElement>().WithIndex()) foreach (var (childNode, index) in element.ChildNodes.Cast<XmlElement>().WithIndex())
array[index] = convert(childNode); array[index] = convert(childNode);