Split vertex attribute logic into seperate file

This commit is contained in:
ackwell 2024-01-04 21:47:48 +11:00
parent b7edf521b6
commit b3fe538219
2 changed files with 253 additions and 226 deletions

View file

@ -0,0 +1,232 @@
using Lumina.Data.Parsing;
using Penumbra.GameData.Files;
using SharpGLTF.Schema2;
namespace Penumbra.Import.Models.Import;
using Writer = Action<int, List<byte>>;
using Accessors = IReadOnlyDictionary<string, Accessor>;
public class VertexAttribute
{
/// <summary> XIV vertex element metadata structure. </summary>
public readonly MdlStructs.VertexElement Element;
/// <summary> Write this vertex attribute's value at the specified index to the provided byte array. </summary>
public readonly Writer Write;
/// <summary> Size in bytes of a single vertex's attribute value. </summary>
public byte Size => (MdlFile.VertexType)Element.Type switch
{
MdlFile.VertexType.Single3 => 12,
MdlFile.VertexType.Single4 => 16,
MdlFile.VertexType.UInt => 4,
MdlFile.VertexType.ByteFloat4 => 4,
MdlFile.VertexType.Half2 => 4,
MdlFile.VertexType.Half4 => 8,
_ => throw new Exception($"Unhandled vertex type {(MdlFile.VertexType)Element.Type}"),
};
public VertexAttribute(MdlStructs.VertexElement element, Writer write)
{
Element = element;
Write = write;
}
public static VertexAttribute Position(Accessors accessors)
{
if (!accessors.TryGetValue("POSITION", out var accessor))
throw new Exception("Meshes must contain a POSITION attribute.");
var element = new MdlStructs.VertexElement()
{
Stream = 0,
Type = (byte)MdlFile.VertexType.Single3,
Usage = (byte)MdlFile.VertexUsage.Position,
};
var values = accessor.AsVector3Array();
return new VertexAttribute(
element,
(index, bytes) => WriteSingle3(values[index], bytes)
);
}
public static VertexAttribute? BlendWeight(Accessors accessors)
{
if (!accessors.TryGetValue("WEIGHTS_0", out var accessor))
return null;
if (!accessors.ContainsKey("JOINTS_0"))
throw new Exception("Mesh contained WEIGHTS_0 attribute but no corresponding JOINTS_0 attribute.");
var element = new MdlStructs.VertexElement()
{
Stream = 0,
Type = (byte)MdlFile.VertexType.ByteFloat4,
Usage = (byte)MdlFile.VertexUsage.BlendWeights,
};
var values = accessor.AsVector4Array();
return new VertexAttribute(
element,
(index, bytes) => WriteByteFloat4(values[index], bytes)
);
}
// TODO: this will need to take in a skeleton mapping of some kind so i can persist the bones used and wire up the joints correctly. hopefully by the "write vertex buffer" stage of building, we already know something about the skeleton.
public static VertexAttribute? BlendIndex(Accessors accessors)
{
if (!accessors.TryGetValue("JOINTS_0", out var accessor))
return null;
if (!accessors.ContainsKey("WEIGHTS_0"))
throw new Exception("Mesh contained JOINTS_0 attribute but no corresponding WEIGHTS_0 attribute.");
var element = new MdlStructs.VertexElement()
{
Stream = 0,
Type = (byte)MdlFile.VertexType.UInt,
Usage = (byte)MdlFile.VertexUsage.BlendIndices,
};
var values = accessor.AsVector4Array();
return new VertexAttribute(
element,
(index, bytes) => WriteUInt(values[index], bytes)
);
}
public static VertexAttribute? Normal(Accessors accessors)
{
if (!accessors.TryGetValue("NORMAL", out var accessor))
return null;
var element = new MdlStructs.VertexElement()
{
Stream = 1,
Type = (byte)MdlFile.VertexType.Half4,
Usage = (byte)MdlFile.VertexUsage.Normal,
};
var values = accessor.AsVector3Array();
return new VertexAttribute(
element,
(index, bytes) => WriteHalf4(new Vector4(values[index], 0), bytes)
);
}
public static VertexAttribute? Uv(Accessors accessors)
{
if (!accessors.TryGetValue("TEXCOORD_0", out var accessor1))
return null;
// We're omitting type here, and filling it in on return, as there's two different types we might use.
var element = new MdlStructs.VertexElement()
{
Stream = 1,
Usage = (byte)MdlFile.VertexUsage.UV,
};
var values1 = accessor1.AsVector2Array();
if (!accessors.TryGetValue("TEXCOORD_1", out var accessor2))
return new VertexAttribute(
element with { Type = (byte)MdlFile.VertexType.Half2 },
(index, bytes) => WriteHalf2(values1[index], bytes)
);
var values2 = accessor2.AsVector2Array();
return new VertexAttribute(
element with { Type = (byte)MdlFile.VertexType.Half4 },
(index, bytes) =>
{
var value1 = values1[index];
var value2 = values2[index];
WriteHalf4(new Vector4(value1.X, value1.Y, value2.X, value2.Y), bytes);
}
);
}
public static VertexAttribute? Tangent1(Accessors accessors)
{
if (!accessors.TryGetValue("TANGENT", out var accessor))
return null;
var element = new MdlStructs.VertexElement()
{
Stream = 1,
Type = (byte)MdlFile.VertexType.ByteFloat4,
Usage = (byte)MdlFile.VertexUsage.Tangent1,
};
var values = accessor.AsVector4Array();
return new VertexAttribute(
element,
(index, bytes) => WriteByteFloat4(values[index], bytes)
);
}
public static VertexAttribute? Color(Accessors accessors)
{
if (!accessors.TryGetValue("COLOR_0", out var accessor))
return null;
var element = new MdlStructs.VertexElement()
{
Stream = 1,
Type = (byte)MdlFile.VertexType.ByteFloat4,
Usage = (byte)MdlFile.VertexUsage.Color,
};
var values = accessor.AsVector4Array();
return new VertexAttribute(
element,
(index, bytes) => WriteByteFloat4(values[index], bytes)
);
}
private static void WriteSingle3(Vector3 input, List<byte> bytes)
{
bytes.AddRange(BitConverter.GetBytes(input.X));
bytes.AddRange(BitConverter.GetBytes(input.Y));
bytes.AddRange(BitConverter.GetBytes(input.Z));
}
private static void WriteUInt(Vector4 input, List<byte> bytes)
{
bytes.Add((byte)input.X);
bytes.Add((byte)input.Y);
bytes.Add((byte)input.Z);
bytes.Add((byte)input.W);
}
private static void WriteByteFloat4(Vector4 input, List<byte> bytes)
{
bytes.Add((byte)Math.Round(input.X * 255f));
bytes.Add((byte)Math.Round(input.Y * 255f));
bytes.Add((byte)Math.Round(input.Z * 255f));
bytes.Add((byte)Math.Round(input.W * 255f));
}
private static void WriteHalf2(Vector2 input, List<byte> bytes)
{
bytes.AddRange(BitConverter.GetBytes((Half)input.X));
bytes.AddRange(BitConverter.GetBytes((Half)input.Y));
}
private static void WriteHalf4(Vector4 input, List<byte> bytes)
{
bytes.AddRange(BitConverter.GetBytes((Half)input.X));
bytes.AddRange(BitConverter.GetBytes((Half)input.Y));
bytes.AddRange(BitConverter.GetBytes((Half)input.Z));
bytes.AddRange(BitConverter.GetBytes((Half)input.W));
}
}

View file

@ -4,6 +4,7 @@ using OtterGui.Tasks;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Files;
using Penumbra.Import.Models.Export;
using Penumbra.Import.Models.Import;
using SharpGLTF.Geometry;
using SharpGLTF.Geometry.VertexTypes;
using SharpGLTF.Materials;
@ -127,7 +128,7 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
public ImportGltfAction()
{
//
//
}
private ModelRoot Build()
@ -168,212 +169,6 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
return model;
}
private (MdlStructs.VertexElement, Action<int, List<byte>>) GetPositionWriter(IReadOnlyDictionary<string, Accessor> accessors)
{
if (!accessors.TryGetValue("POSITION", out var accessor))
throw new Exception("todo: some error about position being hard required");
var element = new MdlStructs.VertexElement()
{
Stream = 0,
Type = (byte)MdlFile.VertexType.Single3,
Usage = (byte)MdlFile.VertexUsage.Position,
};
IList<Vector3> values = accessor.AsVector3Array();
return (
element,
(index, bytes) => WriteSingle3(values[index], bytes)
);
}
// TODO: probably should sanity check that if there's weights or indexes, both are available? game is always symmetric
private (MdlStructs.VertexElement, Action<int, List<byte>>)? GetBlendWeightWriter(IReadOnlyDictionary<string, Accessor> accessors)
{
if (!accessors.TryGetValue("WEIGHTS_0", out var accessor))
return null;
var element = new MdlStructs.VertexElement()
{
Stream = 0,
Type = (byte)MdlFile.VertexType.ByteFloat4,
Usage = (byte)MdlFile.VertexUsage.BlendWeights,
};
var values = accessor.AsVector4Array();
return (
element,
(index, bytes) => WriteByteFloat4(values[index], bytes)
);
}
// TODO: this will need to take in a skeleton mapping of some kind so i can persist the bones used and wire up the joints correctly. hopefully by the "write vertex buffer" stage of building, we already know something about the skeleton.
private (MdlStructs.VertexElement, Action<int, List<byte>>)? GetBlendIndexWriter(IReadOnlyDictionary<string, Accessor> accessors)
{
if (!accessors.TryGetValue("JOINTS_0", out var accessor))
return null;
var element = new MdlStructs.VertexElement()
{
Stream = 0,
Type = (byte)MdlFile.VertexType.UInt,
Usage = (byte)MdlFile.VertexUsage.BlendIndices,
};
var values = accessor.AsVector4Array();
return (
element,
(index, bytes) => WriteUInt(values[index], bytes)
);
}
private (MdlStructs.VertexElement, Action<int, List<byte>>)? GetNormalWriter(IReadOnlyDictionary<string, Accessor> accessors)
{
if (!accessors.TryGetValue("NORMAL", out var accessor))
return null;
var element = new MdlStructs.VertexElement()
{
Stream = 1,
Type = (byte)MdlFile.VertexType.Half4,
Usage = (byte)MdlFile.VertexUsage.Normal,
};
var values = accessor.AsVector3Array();
return (
element,
(index, bytes) => WriteHalf4(new Vector4(values[index], 0), bytes)
);
}
private (MdlStructs.VertexElement, Action<int, List<byte>>)? GetUvWriter(IReadOnlyDictionary<string, Accessor> accessors)
{
if (!accessors.TryGetValue("TEXCOORD_0", out var accessor1))
return null;
// We're omitting type here, and filling it in on return, as there's two different types we might use.
var element = new MdlStructs.VertexElement()
{
Stream = 1,
Usage = (byte)MdlFile.VertexUsage.UV,
};
var values1 = accessor1.AsVector2Array();
if (!accessors.TryGetValue("TEXCOORD_1", out var accessor2))
return (
element with {Type = (byte)MdlFile.VertexType.Half2},
(index, bytes) => WriteHalf2(values1[index], bytes)
);
var values2 = accessor2.AsVector2Array();
return (
element with {Type = (byte)MdlFile.VertexType.Half4},
(index, bytes) => {
var value1 = values1[index];
var value2 = values2[index];
WriteHalf4(new Vector4(value1.X, value1.Y, value2.X, value2.Y), bytes);
}
);
}
private (MdlStructs.VertexElement, Action<int, List<byte>>)? GetTangent1Writer(IReadOnlyDictionary<string, Accessor> accessors)
{
if (!accessors.TryGetValue("TANGENT", out var accessor))
return null;
var element = new MdlStructs.VertexElement()
{
Stream = 1,
Type = (byte)MdlFile.VertexType.ByteFloat4,
Usage = (byte)MdlFile.VertexUsage.Tangent1,
};
var values = accessor.AsVector4Array();
return (
element,
(index, bytes) => WriteByteFloat4(values[index], bytes)
);
}
private (MdlStructs.VertexElement, Action<int, List<byte>>)? GetColorWriter(IReadOnlyDictionary<string, Accessor> accessors)
{
if (!accessors.TryGetValue("COLOR_0", out var accessor))
return null;
var element = new MdlStructs.VertexElement()
{
Stream = 1,
Type = (byte)MdlFile.VertexType.ByteFloat4,
Usage = (byte)MdlFile.VertexUsage.Color,
};
var values = accessor.AsVector4Array();
return (
element,
(index, bytes) => WriteByteFloat4(values[index], bytes)
);
}
private void WriteSingle3(Vector3 input, List<byte> bytes)
{
bytes.AddRange(BitConverter.GetBytes(input.X));
bytes.AddRange(BitConverter.GetBytes(input.Y));
bytes.AddRange(BitConverter.GetBytes(input.Z));
}
private void WriteUInt(Vector4 input, List<byte> bytes)
{
bytes.Add((byte)input.X);
bytes.Add((byte)input.Y);
bytes.Add((byte)input.Z);
bytes.Add((byte)input.W);
}
private void WriteByteFloat4(Vector4 input, List<byte> bytes)
{
bytes.Add((byte)Math.Round(input.X * 255f));
bytes.Add((byte)Math.Round(input.Y * 255f));
bytes.Add((byte)Math.Round(input.Z * 255f));
bytes.Add((byte)Math.Round(input.W * 255f));
}
private void WriteHalf2(Vector2 input, List<byte> bytes)
{
bytes.AddRange(BitConverter.GetBytes((Half)input.X));
bytes.AddRange(BitConverter.GetBytes((Half)input.Y));
}
private void WriteHalf4(Vector4 input, List<byte> bytes)
{
bytes.AddRange(BitConverter.GetBytes((Half)input.X));
bytes.AddRange(BitConverter.GetBytes((Half)input.Y));
bytes.AddRange(BitConverter.GetBytes((Half)input.Z));
bytes.AddRange(BitConverter.GetBytes((Half)input.W));
}
private byte TypeSize(MdlFile.VertexType type)
{
return type switch
{
MdlFile.VertexType.Single3 => 12,
MdlFile.VertexType.Single4 => 16,
MdlFile.VertexType.UInt => 4,
MdlFile.VertexType.ByteFloat4 => 4,
MdlFile.VertexType.Half2 => 4,
MdlFile.VertexType.Half4 => 8,
_ => throw new Exception($"Unhandled vertex type {type}"),
};
}
public void Execute(CancellationToken cancel)
{
var model = Build();
@ -391,27 +186,27 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
var accessors = prim.VertexAccessors;
var rawWriters = new[] {
GetPositionWriter(accessors),
GetBlendWeightWriter(accessors),
GetBlendIndexWriter(accessors),
GetNormalWriter(accessors),
GetTangent1Writer(accessors),
GetColorWriter(accessors),
GetUvWriter(accessors),
var rawAttributes = new[] {
VertexAttribute.Position(accessors),
VertexAttribute.BlendWeight(accessors),
VertexAttribute.BlendIndex(accessors),
VertexAttribute.Normal(accessors),
VertexAttribute.Tangent1(accessors),
VertexAttribute.Color(accessors),
VertexAttribute.Uv(accessors),
};
var writers = new List<(MdlStructs.VertexElement, Action<int, List<byte>>)>();
var attributes = new List<VertexAttribute>();
var offsets = new byte[] {0, 0, 0};
foreach (var writer in rawWriters)
foreach (var attribute in rawAttributes)
{
if (writer == null) continue;
var element = writer.Value.Item1;
writers.Add((
if (attribute == null) continue;
var element = attribute.Element;
attributes.Add(new VertexAttribute(
element with {Offset = offsets[element.Stream]},
writer.Value.Item2
attribute.Write
));
offsets[element.Stream] += TypeSize((MdlFile.VertexType)element.Type);
offsets[element.Stream] += attribute.Size;
}
var strides = offsets;
@ -423,9 +218,9 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
var vertexCount = prim.VertexAccessors["POSITION"].Count;
for (var vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++)
{
foreach (var (element, writer) in writers)
foreach (var attribute in attributes)
{
writer(vertexIndex, streams[element.Stream]);
attribute.Write(vertexIndex, streams[attribute.Element.Stream]);
}
}
@ -455,7 +250,7 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
},
VertexDeclarations = [new MdlStructs.VertexDeclarationStruct()
{
VertexElements = writers.Select(x => x.Item1).ToArray(),
VertexElements = attributes.Select(attribute => attribute.Element).ToArray(),
}],
Meshes = [new MdlStructs.MeshStruct()
{
@ -530,7 +325,7 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
"j_kosi",
],
Materials = [
"/mt_c0201e6180_top_b.mtrl",
"/mt_c0201e6180_top_a.mtrl",
],
RemainingData = dataBuffer.ToArray(),
};