mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Split vertex attribute logic into seperate file
This commit is contained in:
parent
b7edf521b6
commit
b3fe538219
2 changed files with 253 additions and 226 deletions
232
Penumbra/Import/Models/Import/VertexAttribute.cs
Normal file
232
Penumbra/Import/Models/Import/VertexAttribute.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue