Consider VertexElement's UsageIndex

Allows VertexDeclarations to have multiple VertexElements of the same Type but different UsageIndex
This commit is contained in:
Adam Moy 2025-02-05 11:25:09 -06:00 committed by Ottermandias
parent a73dee83b3
commit ef26049c53
2 changed files with 187 additions and 39 deletions

View file

@ -82,13 +82,16 @@ public class MeshExporter
if (skeleton != null)
_boneIndexMap = BuildBoneIndexMap(skeleton.Value);
var usages = new Dictionary<MdlFile.VertexUsage, IDictionary<byte, MdlFile.VertexType>>();
var usages = _mdl.VertexDeclarations[_meshIndex].VertexElements
.ToImmutableDictionary(
element => (MdlFile.VertexUsage)element.Usage,
element => (MdlFile.VertexType)element.Type
);
foreach (var element in _mdl.VertexDeclarations[_meshIndex].VertexElements)
{
if (!usages.ContainsKey((MdlFile.VertexUsage)element.Usage))
{
usages.Add((MdlFile.VertexUsage)element.Usage, new Dictionary<byte, MdlFile.VertexType>());
}
usages[(MdlFile.VertexUsage)element.Usage][element.UsageIndex] = (MdlFile.VertexType)element.Type;
}
_geometryType = GetGeometryType(usages);
_materialType = GetMaterialType(usages);
_skinningType = GetSkinningType(usages);
@ -104,14 +107,20 @@ public class MeshExporter
/// <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(GltfSkeleton skeleton)
{
Penumbra.Log.Debug("Building Bone Index Map");
// A BoneTableIndex of 255 means that this mesh is not skinned.
if (XivMesh.BoneTableIndex == 255)
{
Penumbra.Log.Debug("BoneTableIndex was 255");
return null;
}
var xivBoneTable = _mdl.BoneTables[XivMesh.BoneTableIndex];
var indexMap = new Dictionary<ushort, int>();
// #TODO @ackwell maybe fix for V6 Models, I think this works fine.
Penumbra.Log.Debug($"Version is 5 {_mdl.Version == MdlFile.V5}");
foreach (var (xivBoneIndex, tableIndex) in xivBoneTable.BoneIndex.Take((int)xivBoneTable.BoneCount).WithIndex())
{
var boneName = _mdl.Bones[xivBoneIndex];
@ -283,13 +292,20 @@ public class MeshExporter
var vertices = new List<IVertexBuilder>();
var attributes = new Dictionary<MdlFile.VertexUsage, object>();
var attributes = new Dictionary<MdlFile.VertexUsage, Dictionary<byte, object>>();
for (var vertexIndex = 0; vertexIndex < XivMesh.VertexCount; vertexIndex++)
{
attributes.Clear();
foreach (var (usage, element) in sortedElements)
attributes[usage] = ReadVertexAttribute((MdlFile.VertexType)element.Type, streams[element.Stream]);
{
if (!attributes.TryGetValue(usage, out var value))
{
value = new Dictionary<byte, object>();
attributes[usage] = value;
}
value[element.UsageIndex] = ReadVertexAttribute((MdlFile.VertexType)element.Type, streams[element.Stream]);
}
var vertexGeometry = BuildVertexGeometry(attributes);
var vertexMaterial = BuildVertexMaterial(attributes);
@ -320,7 +336,7 @@ public class MeshExporter
}
/// <summary> Get the vertex geometry type for this mesh's vertex usages. </summary>
private Type GetGeometryType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages)
private Type GetGeometryType(IReadOnlyDictionary<MdlFile.VertexUsage, IDictionary<byte, MdlFile.VertexType>> usages)
{
if (!usages.ContainsKey(MdlFile.VertexUsage.Position))
throw _notifier.Exception("Mesh does not contain position vertex elements.");
@ -335,28 +351,28 @@ public class MeshExporter
}
/// <summary> Build a geometry vertex from a vertex's attributes. </summary>
private IVertexGeometry BuildVertexGeometry(IReadOnlyDictionary<MdlFile.VertexUsage, object> attributes)
private IVertexGeometry BuildVertexGeometry(IReadOnlyDictionary<MdlFile.VertexUsage, Dictionary<byte, object>> attributes)
{
if (_geometryType == typeof(VertexPosition))
return new VertexPosition(
ToVector3(attributes[MdlFile.VertexUsage.Position])
ToVector3(attributes[MdlFile.VertexUsage.Position][0])
);
if (_geometryType == typeof(VertexPositionNormal))
return new VertexPositionNormal(
ToVector3(attributes[MdlFile.VertexUsage.Position]),
ToVector3(attributes[MdlFile.VertexUsage.Normal])
ToVector3(attributes[MdlFile.VertexUsage.Position][0]),
ToVector3(attributes[MdlFile.VertexUsage.Normal][0])
);
if (_geometryType == typeof(VertexPositionNormalTangent))
{
// (Bi)tangents are universally stored as ByteFloat4, which uses 0..1 to represent the full -1..1 range.
// TODO: While this assumption is safe, it would be sensible to actually check.
var bitangent = ToVector4(attributes[MdlFile.VertexUsage.Tangent1]) * 2 - Vector4.One;
var bitangent = ToVector4(attributes[MdlFile.VertexUsage.Tangent1][0]) * 2 - Vector4.One;
return new VertexPositionNormalTangent(
ToVector3(attributes[MdlFile.VertexUsage.Position]),
ToVector3(attributes[MdlFile.VertexUsage.Normal]),
ToVector3(attributes[MdlFile.VertexUsage.Position][0]),
ToVector3(attributes[MdlFile.VertexUsage.Normal][0]),
bitangent
);
}
@ -365,18 +381,23 @@ public class MeshExporter
}
/// <summary> Get the vertex material type for this mesh's vertex usages. </summary>
private Type GetMaterialType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages)
private Type GetMaterialType(IReadOnlyDictionary<MdlFile.VertexUsage, IDictionary<byte, MdlFile.VertexType>> usages)
{
var uvCount = 0;
if (usages.TryGetValue(MdlFile.VertexUsage.UV, out var type))
uvCount = type switch
if (usages.TryGetValue(MdlFile.VertexUsage.UV, out var dict))
{
foreach (var type in dict.Values)
{
MdlFile.VertexType.Half2 => 1,
MdlFile.VertexType.Half4 => 2,
MdlFile.VertexType.Single2 => 1,
MdlFile.VertexType.Single4 => 2,
_ => throw _notifier.Exception($"Unexpected UV vertex type {type}."),
};
uvCount += type switch
{
MdlFile.VertexType.Half2 => 1,
MdlFile.VertexType.Half4 => 2,
MdlFile.VertexType.Single2 => 1,
MdlFile.VertexType.Single4 => 2,
_ => throw _notifier.Exception($"Unexpected UV vertex type {type}."),
};
}
}
var materialUsages = (
uvCount,
@ -385,6 +406,8 @@ public class MeshExporter
return materialUsages switch
{
(3, true) => typeof(VertexTexture3ColorFfxiv),
(3, false) => typeof(VertexTexture3),
(2, true) => typeof(VertexTexture2ColorFfxiv),
(2, false) => typeof(VertexTexture2),
(1, true) => typeof(VertexTexture1ColorFfxiv),
@ -397,28 +420,28 @@ public class MeshExporter
}
/// <summary> Build a material vertex from a vertex's attributes. </summary>
private IVertexMaterial BuildVertexMaterial(IReadOnlyDictionary<MdlFile.VertexUsage, object> attributes)
private IVertexMaterial BuildVertexMaterial(IReadOnlyDictionary<MdlFile.VertexUsage, Dictionary<byte, object>> attributes)
{
if (_materialType == typeof(VertexEmpty))
return new VertexEmpty();
if (_materialType == typeof(VertexColorFfxiv))
return new VertexColorFfxiv(ToVector4(attributes[MdlFile.VertexUsage.Color]));
return new VertexColorFfxiv(ToVector4(attributes[MdlFile.VertexUsage.Color][0]));
if (_materialType == typeof(VertexTexture1))
return new VertexTexture1(ToVector2(attributes[MdlFile.VertexUsage.UV]));
return new VertexTexture1(ToVector2(attributes[MdlFile.VertexUsage.UV][0]));
if (_materialType == typeof(VertexTexture1ColorFfxiv))
return new VertexTexture1ColorFfxiv(
ToVector2(attributes[MdlFile.VertexUsage.UV]),
ToVector4(attributes[MdlFile.VertexUsage.Color])
ToVector2(attributes[MdlFile.VertexUsage.UV][0]),
ToVector4(attributes[MdlFile.VertexUsage.Color][0])
);
// XIV packs two UVs into a single vec4 attribute.
if (_materialType == typeof(VertexTexture2))
{
var uv = ToVector4(attributes[MdlFile.VertexUsage.UV]);
var uv = ToVector4(attributes[MdlFile.VertexUsage.UV][0]);
return new VertexTexture2(
new Vector2(uv.X, uv.Y),
new Vector2(uv.Z, uv.W)
@ -427,11 +450,27 @@ public class MeshExporter
if (_materialType == typeof(VertexTexture2ColorFfxiv))
{
var uv = ToVector4(attributes[MdlFile.VertexUsage.UV]);
var uv = ToVector4(attributes[MdlFile.VertexUsage.UV][0]);
return new VertexTexture2ColorFfxiv(
new Vector2(uv.X, uv.Y),
new Vector2(uv.Z, uv.W),
ToVector4(attributes[MdlFile.VertexUsage.Color])
ToVector4(attributes[MdlFile.VertexUsage.Color][0])
);
}
if (_materialType == typeof(VertexTexture3))
{
throw _notifier.Exception("Unimplemented: Material Type is VertexTexture3");
}
if (_materialType == typeof(VertexTexture3ColorFfxiv))
{
var uv0 = ToVector4(attributes[MdlFile.VertexUsage.UV][0]);
var uv1 = ToVector4(attributes[MdlFile.VertexUsage.UV][1]);
return new VertexTexture3ColorFfxiv(
new Vector2(uv0.X, uv0.Y),
new Vector2(uv0.Z, uv0.W),
new Vector2(uv1.X, uv1.Y),
ToVector4(attributes[MdlFile.VertexUsage.Color][0])
);
}
@ -439,11 +478,11 @@ public class MeshExporter
}
/// <summary> Get the vertex skinning type for this mesh's vertex usages. </summary>
private static Type GetSkinningType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages)
private static Type GetSkinningType(IReadOnlyDictionary<MdlFile.VertexUsage, IDictionary<byte, MdlFile.VertexType>> usages)
{
if (usages.ContainsKey(MdlFile.VertexUsage.BlendWeights) && usages.ContainsKey(MdlFile.VertexUsage.BlendIndices))
{
if (usages[MdlFile.VertexUsage.BlendWeights] == MdlFile.VertexType.UShort4)
if (usages[MdlFile.VertexUsage.BlendWeights][0] == MdlFile.VertexType.UShort4)
{
return typeof(VertexJoints8);
}
@ -457,7 +496,7 @@ public class MeshExporter
}
/// <summary> Build a skinning vertex from a vertex's attributes. </summary>
private IVertexSkinning BuildVertexSkinning(IReadOnlyDictionary<MdlFile.VertexUsage, object> attributes)
private IVertexSkinning BuildVertexSkinning(IReadOnlyDictionary<MdlFile.VertexUsage, Dictionary<byte, object>> attributes)
{
if (_skinningType == typeof(VertexEmpty))
return new VertexEmpty();
@ -467,8 +506,8 @@ public class MeshExporter
if (_boneIndexMap == null)
throw _notifier.Exception("Tried to build skinned vertex but no bone mappings are available.");
var indiciesData = attributes[MdlFile.VertexUsage.BlendIndices];
var weightsData = attributes[MdlFile.VertexUsage.BlendWeights];
var indiciesData = attributes[MdlFile.VertexUsage.BlendIndices][0];
var weightsData = attributes[MdlFile.VertexUsage.BlendWeights][0];
var indices = ToByteArray(indiciesData);
var weights = ToFloatArray(weightsData);