mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Consider VertexElement's UsageIndex
Allows VertexDeclarations to have multiple VertexElements of the same Type but different UsageIndex
This commit is contained in:
parent
f9b163e7c5
commit
b5e3e02b06
2 changed files with 187 additions and 39 deletions
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -282,3 +282,112 @@ public struct VertexTexture2ColorFfxiv : IVertexCustom
|
|||
throw new ArgumentOutOfRangeException(nameof(FfxivColor));
|
||||
}
|
||||
}
|
||||
|
||||
public struct VertexTexture3ColorFfxiv : IVertexCustom
|
||||
{
|
||||
public IEnumerable<KeyValuePair<string, AttributeFormat>> GetEncodingAttributes()
|
||||
{
|
||||
yield return new KeyValuePair<string, AttributeFormat>("TEXCOORD_0",
|
||||
new AttributeFormat(DimensionType.VEC2, EncodingType.FLOAT, false));
|
||||
yield return new KeyValuePair<string, AttributeFormat>("TEXCOORD_1",
|
||||
new AttributeFormat(DimensionType.VEC2, EncodingType.FLOAT, false));
|
||||
yield return new KeyValuePair<string, AttributeFormat>("TEXCOORD_2",
|
||||
new AttributeFormat(DimensionType.VEC2, EncodingType.FLOAT, false));
|
||||
yield return new KeyValuePair<string, AttributeFormat>("_FFXIV_COLOR",
|
||||
new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true));
|
||||
}
|
||||
|
||||
public Vector2 TexCoord0;
|
||||
public Vector2 TexCoord1;
|
||||
public Vector2 TexCoord2;
|
||||
public Vector4 FfxivColor;
|
||||
|
||||
public int MaxColors
|
||||
=> 0;
|
||||
|
||||
public int MaxTextCoords
|
||||
=> 3;
|
||||
|
||||
private static readonly string[] CustomNames = ["_FFXIV_COLOR"];
|
||||
|
||||
public IEnumerable<string> CustomAttributes
|
||||
=> CustomNames;
|
||||
|
||||
public VertexTexture3ColorFfxiv(Vector2 texCoord0, Vector2 texCoord1, Vector2 texCoord2, Vector4 ffxivColor)
|
||||
{
|
||||
TexCoord0 = texCoord0;
|
||||
TexCoord1 = texCoord1;
|
||||
TexCoord2 = texCoord2;
|
||||
FfxivColor = ffxivColor;
|
||||
}
|
||||
|
||||
public void Add(in VertexMaterialDelta delta)
|
||||
{
|
||||
TexCoord0 += delta.TexCoord0Delta;
|
||||
TexCoord1 += delta.TexCoord1Delta;
|
||||
TexCoord2 += delta.TexCoord2Delta;
|
||||
}
|
||||
|
||||
public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
|
||||
=> new(Vector4.Zero, Vector4.Zero, TexCoord0 - baseValue.GetTexCoord(0), TexCoord1 - baseValue.GetTexCoord(1));
|
||||
|
||||
public Vector2 GetTexCoord(int index)
|
||||
=> index switch
|
||||
{
|
||||
0 => TexCoord0,
|
||||
1 => TexCoord1,
|
||||
2 => TexCoord2,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
|
||||
public void SetTexCoord(int setIndex, Vector2 coord)
|
||||
{
|
||||
if (setIndex == 0)
|
||||
TexCoord0 = coord;
|
||||
if (setIndex == 1)
|
||||
TexCoord1 = coord;
|
||||
if (setIndex == 2)
|
||||
TexCoord2 = coord;
|
||||
if (setIndex >= 3)
|
||||
throw new ArgumentOutOfRangeException(nameof(setIndex));
|
||||
}
|
||||
|
||||
public bool TryGetCustomAttribute(string attributeName, out object? value)
|
||||
{
|
||||
switch (attributeName)
|
||||
{
|
||||
case "_FFXIV_COLOR":
|
||||
value = FfxivColor;
|
||||
return true;
|
||||
|
||||
default:
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCustomAttribute(string attributeName, object value)
|
||||
{
|
||||
if (attributeName == "_FFXIV_COLOR" && value is Vector4 valueVector4)
|
||||
FfxivColor = valueVector4;
|
||||
}
|
||||
|
||||
public Vector4 GetColor(int index)
|
||||
=> throw new ArgumentOutOfRangeException(nameof(index));
|
||||
|
||||
public void SetColor(int setIndex, Vector4 color)
|
||||
{ }
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
var components = new[]
|
||||
{
|
||||
FfxivColor.X,
|
||||
FfxivColor.Y,
|
||||
FfxivColor.Z,
|
||||
FfxivColor.W,
|
||||
};
|
||||
if (components.Any(component => component < 0 || component > 1))
|
||||
throw new ArgumentOutOfRangeException(nameof(FfxivColor));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue