Skip to content

Commit b2860c1

Browse files
committed
Merge branch 'refs/heads/adamm789/model-export'
2 parents a73dee8 + 1f172b4 commit b2860c1

File tree

3 files changed

+201
-73
lines changed

3 files changed

+201
-73
lines changed

Penumbra.GameData

Penumbra/Import/Models/Export/MeshExporter.cs

Lines changed: 90 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using Penumbra.GameData.Files.ModelStructs;
88
using SharpGLTF.Geometry;
99
using SharpGLTF.Geometry.VertexTypes;
10-
using SharpGLTF.IO;
1110
using SharpGLTF.Materials;
1211
using SharpGLTF.Scenes;
1312

@@ -84,9 +83,12 @@ private MeshExporter(in ExportConfig config, MdlFile mdl, byte lod, ushort meshI
8483
_boneIndexMap = BuildBoneIndexMap(skeleton.Value);
8584

8685
var usages = _mdl.VertexDeclarations[_meshIndex].VertexElements
86+
.GroupBy(ele => (MdlFile.VertexUsage)ele.Usage, ele => ele)
8787
.ToImmutableDictionary(
88-
element => (MdlFile.VertexUsage)element.Usage,
89-
element => (MdlFile.VertexType)element.Type
88+
g => g.Key,
89+
g => g.OrderBy(ele => ele.UsageIndex) // OrderBy UsageIndex is probably unnecessary as they're probably already be in order
90+
.Select(ele => (MdlFile.VertexType)ele.Type)
91+
.ToList()
9092
);
9193

9294
_geometryType = GetGeometryType(usages);
@@ -112,6 +114,7 @@ private MeshExporter(in ExportConfig config, MdlFile mdl, byte lod, ushort meshI
112114

113115
var indexMap = new Dictionary<ushort, int>();
114116
// #TODO @ackwell maybe fix for V6 Models, I think this works fine.
117+
115118
foreach (var (xivBoneIndex, tableIndex) in xivBoneTable.BoneIndex.Take((int)xivBoneTable.BoneCount).WithIndex())
116119
{
117120
var boneName = _mdl.Bones[xivBoneIndex];
@@ -278,18 +281,22 @@ private IReadOnlyList<IVertexBuilder> BuildVertices()
278281

279282
var sortedElements = _mdl.VertexDeclarations[_meshIndex].VertexElements
280283
.OrderBy(element => element.Offset)
281-
.Select(element => ((MdlFile.VertexUsage)element.Usage, element))
282284
.ToList();
283-
284285
var vertices = new List<IVertexBuilder>();
285286

286-
var attributes = new Dictionary<MdlFile.VertexUsage, object>();
287+
var attributes = new Dictionary<MdlFile.VertexUsage, List<object>>();
287288
for (var vertexIndex = 0; vertexIndex < XivMesh.VertexCount; vertexIndex++)
288289
{
289290
attributes.Clear();
290-
291-
foreach (var (usage, element) in sortedElements)
292-
attributes[usage] = ReadVertexAttribute((MdlFile.VertexType)element.Type, streams[element.Stream]);
291+
attributes = sortedElements
292+
.GroupBy(element => element.Usage)
293+
.ToDictionary(
294+
x => (MdlFile.VertexUsage)x.Key,
295+
x => x.OrderBy(ele => ele.UsageIndex) // Once again, OrderBy UsageIndex is probably unnecessary
296+
.Select(ele => ReadVertexAttribute((MdlFile.VertexType)ele.Type, streams[ele.Stream]))
297+
.ToList()
298+
);
299+
293300

294301
var vertexGeometry = BuildVertexGeometry(attributes);
295302
var vertexMaterial = BuildVertexMaterial(attributes);
@@ -320,7 +327,7 @@ private object ReadVertexAttribute(MdlFile.VertexType type, BinaryReader reader)
320327
}
321328

322329
/// <summary> Get the vertex geometry type for this mesh's vertex usages. </summary>
323-
private Type GetGeometryType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages)
330+
private Type GetGeometryType(IReadOnlyDictionary<MdlFile.VertexUsage, List<MdlFile.VertexType>> usages)
324331
{
325332
if (!usages.ContainsKey(MdlFile.VertexUsage.Position))
326333
throw _notifier.Exception("Mesh does not contain position vertex elements.");
@@ -335,28 +342,28 @@ private Type GetGeometryType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.Ve
335342
}
336343

337344
/// <summary> Build a geometry vertex from a vertex's attributes. </summary>
338-
private IVertexGeometry BuildVertexGeometry(IReadOnlyDictionary<MdlFile.VertexUsage, object> attributes)
345+
private IVertexGeometry BuildVertexGeometry(IReadOnlyDictionary<MdlFile.VertexUsage, List<object>> attributes)
339346
{
340347
if (_geometryType == typeof(VertexPosition))
341348
return new VertexPosition(
342-
ToVector3(attributes[MdlFile.VertexUsage.Position])
349+
ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Position))
343350
);
344351

345352
if (_geometryType == typeof(VertexPositionNormal))
346353
return new VertexPositionNormal(
347-
ToVector3(attributes[MdlFile.VertexUsage.Position]),
348-
ToVector3(attributes[MdlFile.VertexUsage.Normal])
354+
ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Position)),
355+
ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Normal))
349356
);
350357

351358
if (_geometryType == typeof(VertexPositionNormalTangent))
352359
{
353360
// (Bi)tangents are universally stored as ByteFloat4, which uses 0..1 to represent the full -1..1 range.
354361
// TODO: While this assumption is safe, it would be sensible to actually check.
355-
var bitangent = ToVector4(attributes[MdlFile.VertexUsage.Tangent1]) * 2 - Vector4.One;
362+
var bitangent = ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Tangent1)) * 2 - Vector4.One;
356363

357364
return new VertexPositionNormalTangent(
358-
ToVector3(attributes[MdlFile.VertexUsage.Position]),
359-
ToVector3(attributes[MdlFile.VertexUsage.Normal]),
365+
ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Position)),
366+
ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Normal)),
360367
bitangent
361368
);
362369
}
@@ -365,18 +372,23 @@ private IVertexGeometry BuildVertexGeometry(IReadOnlyDictionary<MdlFile.VertexUs
365372
}
366373

367374
/// <summary> Get the vertex material type for this mesh's vertex usages. </summary>
368-
private Type GetMaterialType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages)
375+
private Type GetMaterialType(IReadOnlyDictionary<MdlFile.VertexUsage, List<MdlFile.VertexType>> usages)
369376
{
370377
var uvCount = 0;
371-
if (usages.TryGetValue(MdlFile.VertexUsage.UV, out var type))
372-
uvCount = type switch
378+
if (usages.TryGetValue(MdlFile.VertexUsage.UV, out var list))
379+
{
380+
foreach (var type in list)
373381
{
374-
MdlFile.VertexType.Half2 => 1,
375-
MdlFile.VertexType.Half4 => 2,
376-
MdlFile.VertexType.Single2 => 1,
377-
MdlFile.VertexType.Single4 => 2,
378-
_ => throw _notifier.Exception($"Unexpected UV vertex type {type}."),
379-
};
382+
uvCount += type switch
383+
{
384+
MdlFile.VertexType.Half2 => 1,
385+
MdlFile.VertexType.Half4 => 2,
386+
MdlFile.VertexType.Single2 => 1,
387+
MdlFile.VertexType.Single4 => 2,
388+
_ => throw _notifier.Exception($"Unexpected UV vertex type {type}."),
389+
};
390+
}
391+
}
380392

381393
var materialUsages = (
382394
uvCount,
@@ -385,40 +397,42 @@ private Type GetMaterialType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.Ve
385397

386398
return materialUsages switch
387399
{
400+
(3, true) => typeof(VertexTexture3ColorFfxiv),
401+
(3, false) => typeof(VertexTexture3),
388402
(2, true) => typeof(VertexTexture2ColorFfxiv),
389403
(2, false) => typeof(VertexTexture2),
390404
(1, true) => typeof(VertexTexture1ColorFfxiv),
391405
(1, false) => typeof(VertexTexture1),
392406
(0, true) => typeof(VertexColorFfxiv),
393407
(0, false) => typeof(VertexEmpty),
394408

395-
_ => throw new Exception("Unreachable."),
409+
_ => throw _notifier.Exception($"Unhandled UV count of {uvCount} encountered."),
396410
};
397411
}
398412

399413
/// <summary> Build a material vertex from a vertex's attributes. </summary>
400-
private IVertexMaterial BuildVertexMaterial(IReadOnlyDictionary<MdlFile.VertexUsage, object> attributes)
414+
private IVertexMaterial BuildVertexMaterial(IReadOnlyDictionary<MdlFile.VertexUsage, List<object>> attributes)
401415
{
402416
if (_materialType == typeof(VertexEmpty))
403417
return new VertexEmpty();
404418

405419
if (_materialType == typeof(VertexColorFfxiv))
406-
return new VertexColorFfxiv(ToVector4(attributes[MdlFile.VertexUsage.Color]));
420+
return new VertexColorFfxiv(ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Color)));
407421

408422
if (_materialType == typeof(VertexTexture1))
409-
return new VertexTexture1(ToVector2(attributes[MdlFile.VertexUsage.UV]));
423+
return new VertexTexture1(ToVector2(GetFirstSafe(attributes, MdlFile.VertexUsage.UV)));
410424

411425
if (_materialType == typeof(VertexTexture1ColorFfxiv))
412426
return new VertexTexture1ColorFfxiv(
413-
ToVector2(attributes[MdlFile.VertexUsage.UV]),
414-
ToVector4(attributes[MdlFile.VertexUsage.Color])
427+
ToVector2(GetFirstSafe(attributes, MdlFile.VertexUsage.UV)),
428+
ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Color))
415429
);
416430

417431
// XIV packs two UVs into a single vec4 attribute.
418432

419433
if (_materialType == typeof(VertexTexture2))
420434
{
421-
var uv = ToVector4(attributes[MdlFile.VertexUsage.UV]);
435+
var uv = ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.UV));
422436
return new VertexTexture2(
423437
new Vector2(uv.X, uv.Y),
424438
new Vector2(uv.Z, uv.W)
@@ -427,37 +441,55 @@ private IVertexMaterial BuildVertexMaterial(IReadOnlyDictionary<MdlFile.VertexUs
427441

428442
if (_materialType == typeof(VertexTexture2ColorFfxiv))
429443
{
430-
var uv = ToVector4(attributes[MdlFile.VertexUsage.UV]);
444+
var uv = ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.UV));
431445
return new VertexTexture2ColorFfxiv(
432446
new Vector2(uv.X, uv.Y),
433447
new Vector2(uv.Z, uv.W),
434-
ToVector4(attributes[MdlFile.VertexUsage.Color])
448+
ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Color))
449+
);
450+
}
451+
if (_materialType == typeof(VertexTexture3))
452+
{
453+
// Not 100% sure about this
454+
var uv0 = ToVector4(attributes[MdlFile.VertexUsage.UV][0]);
455+
var uv1 = ToVector4(attributes[MdlFile.VertexUsage.UV][1]);
456+
return new VertexTexture3(
457+
new Vector2(uv0.X, uv0.Y),
458+
new Vector2(uv0.Z, uv0.W),
459+
new Vector2(uv1.X, uv1.Y)
460+
);
461+
}
462+
463+
if (_materialType == typeof(VertexTexture3ColorFfxiv))
464+
{
465+
var uv0 = ToVector4(attributes[MdlFile.VertexUsage.UV][0]);
466+
var uv1 = ToVector4(attributes[MdlFile.VertexUsage.UV][1]);
467+
return new VertexTexture3ColorFfxiv(
468+
new Vector2(uv0.X, uv0.Y),
469+
new Vector2(uv0.Z, uv0.W),
470+
new Vector2(uv1.X, uv1.Y),
471+
ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Color))
435472
);
436473
}
437474

438475
throw _notifier.Exception($"Unknown material type {_skinningType}");
439476
}
440477

441478
/// <summary> Get the vertex skinning type for this mesh's vertex usages. </summary>
442-
private static Type GetSkinningType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages)
479+
private Type GetSkinningType(IReadOnlyDictionary<MdlFile.VertexUsage, List<MdlFile.VertexType>> usages)
443480
{
444481
if (usages.ContainsKey(MdlFile.VertexUsage.BlendWeights) && usages.ContainsKey(MdlFile.VertexUsage.BlendIndices))
445482
{
446-
if (usages[MdlFile.VertexUsage.BlendWeights] == MdlFile.VertexType.UShort4)
447-
{
448-
return typeof(VertexJoints8);
449-
}
450-
else
451-
{
452-
return typeof(VertexJoints4);
453-
}
483+
return GetFirstSafe(usages, MdlFile.VertexUsage.BlendWeights) == MdlFile.VertexType.UShort4
484+
? typeof(VertexJoints8)
485+
: typeof(VertexJoints4);
454486
}
455487

456488
return typeof(VertexEmpty);
457489
}
458490

459491
/// <summary> Build a skinning vertex from a vertex's attributes. </summary>
460-
private IVertexSkinning BuildVertexSkinning(IReadOnlyDictionary<MdlFile.VertexUsage, object> attributes)
492+
private IVertexSkinning BuildVertexSkinning(IReadOnlyDictionary<MdlFile.VertexUsage, List<object>> attributes)
461493
{
462494
if (_skinningType == typeof(VertexEmpty))
463495
return new VertexEmpty();
@@ -467,8 +499,8 @@ private IVertexSkinning BuildVertexSkinning(IReadOnlyDictionary<MdlFile.VertexUs
467499
if (_boneIndexMap == null)
468500
throw _notifier.Exception("Tried to build skinned vertex but no bone mappings are available.");
469501

470-
var indiciesData = attributes[MdlFile.VertexUsage.BlendIndices];
471-
var weightsData = attributes[MdlFile.VertexUsage.BlendWeights];
502+
var indiciesData = GetFirstSafe(attributes, MdlFile.VertexUsage.BlendIndices);
503+
var weightsData = GetFirstSafe(attributes, MdlFile.VertexUsage.BlendWeights);
472504
var indices = ToByteArray(indiciesData);
473505
var weights = ToFloatArray(weightsData);
474506

@@ -495,6 +527,17 @@ private IVertexSkinning BuildVertexSkinning(IReadOnlyDictionary<MdlFile.VertexUs
495527
throw _notifier.Exception($"Unknown skinning type {_skinningType}");
496528
}
497529

530+
/// <summary> Check that the list has length 1 for any case where this is expected and return the one entry. </summary>
531+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
532+
private T GetFirstSafe<T>(IReadOnlyDictionary<MdlFile.VertexUsage, List<T>> attributes, MdlFile.VertexUsage usage)
533+
{
534+
var list = attributes[usage];
535+
if (list.Count != 1)
536+
throw _notifier.Exception($"Multiple usage indices encountered for {usage}.");
537+
538+
return list[0];
539+
}
540+
498541
/// <summary> Convert a vertex attribute value to a Vector2. Supported inputs are Vector2, Vector3, and Vector4. </summary>
499542
private static Vector2 ToVector2(object data)
500543
=> data switch

0 commit comments

Comments
 (0)