mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-15 05:04:15 +01:00
Auto formatting, some cleanup, some initialization changes.
This commit is contained in:
parent
a2b92f1296
commit
b0f61e6929
5 changed files with 213 additions and 233 deletions
|
|
@ -3,17 +3,17 @@ using SharpGLTF.Schema2;
|
||||||
|
|
||||||
namespace Penumbra.Import.Models.Import;
|
namespace Penumbra.Import.Models.Import;
|
||||||
|
|
||||||
public class MeshImporter
|
public class MeshImporter(IEnumerable<Node> nodes)
|
||||||
{
|
{
|
||||||
public struct Mesh
|
public struct Mesh
|
||||||
{
|
{
|
||||||
public MdlStructs.MeshStruct MeshStruct;
|
public MdlStructs.MeshStruct MeshStruct;
|
||||||
public List<MdlStructs.SubmeshStruct> SubMeshStructs;
|
public List<MdlStructs.SubmeshStruct> SubMeshStructs;
|
||||||
|
|
||||||
public MdlStructs.VertexDeclarationStruct VertexDeclaration;
|
public MdlStructs.VertexDeclarationStruct VertexDeclaration;
|
||||||
public IEnumerable<byte> VertexBuffer;
|
public IEnumerable<byte> VertexBuffer;
|
||||||
|
|
||||||
public List<ushort> Indicies;
|
public List<ushort> Indices;
|
||||||
|
|
||||||
public List<string>? Bones;
|
public List<string>? Bones;
|
||||||
|
|
||||||
|
|
@ -22,8 +22,8 @@ public class MeshImporter
|
||||||
|
|
||||||
public struct MeshShapeKey
|
public struct MeshShapeKey
|
||||||
{
|
{
|
||||||
public string Name;
|
public string Name;
|
||||||
public MdlStructs.ShapeMeshStruct ShapeMesh;
|
public MdlStructs.ShapeMeshStruct ShapeMesh;
|
||||||
public List<MdlStructs.ShapeValueStruct> ShapeValues;
|
public List<MdlStructs.ShapeValueStruct> ShapeValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,79 +33,60 @@ public class MeshImporter
|
||||||
return importer.Create();
|
return importer.Create();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<Node> _nodes;
|
private readonly List<MdlStructs.SubmeshStruct> _subMeshes = [];
|
||||||
|
|
||||||
private List<MdlStructs.SubmeshStruct> _subMeshes = new();
|
private MdlStructs.VertexDeclarationStruct? _vertexDeclaration;
|
||||||
|
private byte[]? _strides;
|
||||||
|
private ushort _vertexCount;
|
||||||
|
private readonly List<byte>[] _streams = [[], [], []];
|
||||||
|
|
||||||
private MdlStructs.VertexDeclarationStruct? _vertexDeclaration;
|
private readonly List<ushort> _indices = [];
|
||||||
private byte[]? _strides;
|
|
||||||
private ushort _vertexCount = 0;
|
|
||||||
private List<byte>[] _streams;
|
|
||||||
|
|
||||||
private List<ushort> _indices = new();
|
|
||||||
|
|
||||||
private List<string>? _bones;
|
private List<string>? _bones;
|
||||||
|
|
||||||
private readonly Dictionary<string, List<MdlStructs.ShapeValueStruct>> _shapeValues = new();
|
private readonly Dictionary<string, List<MdlStructs.ShapeValueStruct>> _shapeValues = [];
|
||||||
|
|
||||||
private MeshImporter(IEnumerable<Node> nodes)
|
|
||||||
{
|
|
||||||
_nodes = nodes;
|
|
||||||
|
|
||||||
// All meshes may use up to 3 byte streams.
|
|
||||||
_streams = new List<byte>[3];
|
|
||||||
for (var streamIndex = 0; streamIndex < 3; streamIndex++)
|
|
||||||
_streams[streamIndex] = new List<byte>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Mesh Create()
|
private Mesh Create()
|
||||||
{
|
{
|
||||||
foreach (var node in _nodes)
|
foreach (var node in nodes)
|
||||||
BuildSubMeshForNode(node);
|
BuildSubMeshForNode(node);
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(_strides);
|
ArgumentNullException.ThrowIfNull(_strides);
|
||||||
ArgumentNullException.ThrowIfNull(_vertexDeclaration);
|
ArgumentNullException.ThrowIfNull(_vertexDeclaration);
|
||||||
|
|
||||||
return new Mesh()
|
return new Mesh
|
||||||
{
|
{
|
||||||
MeshStruct = new MdlStructs.MeshStruct()
|
MeshStruct = new MdlStructs.MeshStruct
|
||||||
{
|
{
|
||||||
VertexBufferOffset = [0, (uint)_streams[0].Count, (uint)(_streams[0].Count + _streams[1].Count)],
|
VertexBufferOffset = [0, (uint)_streams[0].Count, (uint)(_streams[0].Count + _streams[1].Count)],
|
||||||
VertexBufferStride = _strides,
|
VertexBufferStride = _strides,
|
||||||
VertexCount = _vertexCount,
|
VertexCount = _vertexCount,
|
||||||
VertexStreamCount = (byte)_vertexDeclaration.Value.VertexElements
|
VertexStreamCount = (byte)_vertexDeclaration.Value.VertexElements
|
||||||
.Select(element => element.Stream + 1)
|
.Select(element => element.Stream + 1)
|
||||||
.Max(),
|
.Max(),
|
||||||
|
|
||||||
StartIndex = 0,
|
StartIndex = 0,
|
||||||
IndexCount = (uint)_indices.Count,
|
IndexCount = (uint)_indices.Count,
|
||||||
|
|
||||||
// TODO: import material names
|
// TODO: import material names
|
||||||
MaterialIndex = 0,
|
MaterialIndex = 0,
|
||||||
|
SubMeshIndex = 0,
|
||||||
SubMeshIndex = 0,
|
SubMeshCount = (ushort)_subMeshes.Count,
|
||||||
SubMeshCount = (ushort)_subMeshes.Count,
|
|
||||||
|
|
||||||
BoneTableIndex = 0,
|
BoneTableIndex = 0,
|
||||||
},
|
},
|
||||||
SubMeshStructs = _subMeshes,
|
SubMeshStructs = _subMeshes,
|
||||||
|
|
||||||
VertexDeclaration = _vertexDeclaration.Value,
|
VertexDeclaration = _vertexDeclaration.Value,
|
||||||
VertexBuffer = _streams[0].Concat(_streams[1]).Concat(_streams[2]),
|
VertexBuffer = _streams[0].Concat(_streams[1]).Concat(_streams[2]),
|
||||||
|
Indices = _indices,
|
||||||
Indicies = _indices,
|
Bones = _bones,
|
||||||
|
|
||||||
Bones = _bones,
|
|
||||||
|
|
||||||
ShapeKeys = _shapeValues
|
ShapeKeys = _shapeValues
|
||||||
.Select(pair => new MeshShapeKey()
|
.Select(pair => new MeshShapeKey()
|
||||||
{
|
{
|
||||||
Name = pair.Key,
|
Name = pair.Key,
|
||||||
ShapeMesh = new MdlStructs.ShapeMeshStruct()
|
ShapeMesh = new MdlStructs.ShapeMeshStruct()
|
||||||
{
|
{
|
||||||
MeshIndexOffset = 0,
|
MeshIndexOffset = 0,
|
||||||
ShapeValueOffset = 0,
|
ShapeValueOffset = 0,
|
||||||
ShapeValueCount = (uint)pair.Value.Count,
|
ShapeValueCount = (uint)pair.Value.Count,
|
||||||
},
|
},
|
||||||
ShapeValues = pair.Value,
|
ShapeValues = pair.Value,
|
||||||
})
|
})
|
||||||
|
|
@ -117,10 +98,10 @@ public class MeshImporter
|
||||||
{
|
{
|
||||||
// Record some offsets we'll be using later, before they get mutated with sub-mesh values.
|
// Record some offsets we'll be using later, before they get mutated with sub-mesh values.
|
||||||
var vertexOffset = _vertexCount;
|
var vertexOffset = _vertexCount;
|
||||||
var indexOffset = _indices.Count;
|
var indexOffset = _indices.Count;
|
||||||
|
|
||||||
var nodeBoneMap = CreateNodeBoneMap(node);
|
var nodeBoneMap = CreateNodeBoneMap(node);
|
||||||
var subMesh = SubMeshImporter.Import(node, nodeBoneMap);
|
var subMesh = SubMeshImporter.Import(node, nodeBoneMap);
|
||||||
|
|
||||||
var subMeshName = node.Name ?? node.Mesh.Name;
|
var subMeshName = node.Name ?? node.Mesh.Name;
|
||||||
|
|
||||||
|
|
@ -128,18 +109,18 @@ public class MeshImporter
|
||||||
if (_vertexDeclaration == null)
|
if (_vertexDeclaration == null)
|
||||||
_vertexDeclaration = subMesh.VertexDeclaration;
|
_vertexDeclaration = subMesh.VertexDeclaration;
|
||||||
else if (VertexDeclarationMismatch(subMesh.VertexDeclaration, _vertexDeclaration.Value))
|
else if (VertexDeclarationMismatch(subMesh.VertexDeclaration, _vertexDeclaration.Value))
|
||||||
throw new Exception($"Sub-mesh \"{subMeshName}\" vertex declaration mismatch. All sub-meshes of a mesh must have equivalent vertex declarations.");
|
throw new Exception(
|
||||||
|
$"Sub-mesh \"{subMeshName}\" vertex declaration mismatch. All sub-meshes of a mesh must have equivalent vertex declarations.");
|
||||||
|
|
||||||
// Given that strides are derived from declarations, a lack of mismatch in declarations means the strides are fine.
|
// Given that strides are derived from declarations, a lack of mismatch in declarations means the strides are fine.
|
||||||
// TODO: I mean, given that strides are derivable, might be worth dropping strides from the submesh return structure and computing when needed.
|
// TODO: I mean, given that strides are derivable, might be worth dropping strides from the sub mesh return structure and computing when needed.
|
||||||
if (_strides == null)
|
_strides ??= subMesh.Strides;
|
||||||
_strides = subMesh.Strides;
|
|
||||||
|
|
||||||
// Merge the sub-mesh streams into the main mesh stream bodies.
|
// Merge the sub-mesh streams into the main mesh stream bodies.
|
||||||
_vertexCount += subMesh.VertexCount;
|
_vertexCount += subMesh.VertexCount;
|
||||||
|
|
||||||
for (var streamIndex = 0; streamIndex < 3; streamIndex++)
|
foreach (var (stream, subStream) in _streams.Zip(subMesh.Streams))
|
||||||
_streams[streamIndex].AddRange(subMesh.Streams[streamIndex]);
|
stream.AddRange(subStream);
|
||||||
|
|
||||||
// As we're appending vertex data to the buffers, we need to update indices to point into that later block.
|
// As we're appending vertex data to the buffers, we need to update indices to point into that later block.
|
||||||
_indices.AddRange(subMesh.Indices.Select(index => (ushort)(index + vertexOffset)));
|
_indices.AddRange(subMesh.Indices.Select(index => (ushort)(index + vertexOffset)));
|
||||||
|
|
@ -149,7 +130,7 @@ public class MeshImporter
|
||||||
{
|
{
|
||||||
if (!_shapeValues.TryGetValue(name, out var meshShapeValues))
|
if (!_shapeValues.TryGetValue(name, out var meshShapeValues))
|
||||||
{
|
{
|
||||||
meshShapeValues = new();
|
meshShapeValues = [];
|
||||||
_shapeValues.Add(name, meshShapeValues);
|
_shapeValues.Add(name, meshShapeValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,19 +148,20 @@ public class MeshImporter
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool VertexDeclarationMismatch(MdlStructs.VertexDeclarationStruct a, MdlStructs.VertexDeclarationStruct b)
|
private static bool VertexDeclarationMismatch(MdlStructs.VertexDeclarationStruct a, MdlStructs.VertexDeclarationStruct b)
|
||||||
{
|
{
|
||||||
var elA = a.VertexElements;
|
var elA = a.VertexElements;
|
||||||
var elB = b.VertexElements;
|
var elB = b.VertexElements;
|
||||||
|
|
||||||
if (elA.Length != elB.Length) return true;
|
if (elA.Length != elB.Length)
|
||||||
|
return true;
|
||||||
|
|
||||||
// NOTE: This assumes that elements will always be in the same order. Under the current implementation, that's guaranteed.
|
// NOTE: This assumes that elements will always be in the same order. Under the current implementation, that's guaranteed.
|
||||||
return elA.Zip(elB).Any(pair =>
|
return elA.Zip(elB).Any(pair =>
|
||||||
pair.First.Usage != pair.Second.Usage
|
pair.First.Usage != pair.Second.Usage
|
||||||
|| pair.First.Type != pair.Second.Type
|
|| pair.First.Type != pair.Second.Type
|
||||||
|| pair.First.Offset != pair.Second.Offset
|
|| pair.First.Offset != pair.Second.Offset
|
||||||
|| pair.First.Stream != pair.Second.Stream
|
|| pair.First.Stream != pair.Second.Stream
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,31 +177,30 @@ public class MeshImporter
|
||||||
.Select(index => node.Skin.GetJoint(index).Joint.Name ?? "unnamed_joint")
|
.Select(index => node.Skin.GetJoint(index).Joint.Name ?? "unnamed_joint")
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
// TODO: This is duplicated with the submesh importer - would be good to avoid (not that it's a huge issue).
|
// TODO: This is duplicated with the sub mesh importer - would be good to avoid (not that it's a huge issue).
|
||||||
var mesh = node.Mesh;
|
var mesh = node.Mesh;
|
||||||
var meshName = node.Name ?? mesh.Name ?? "(no name)";
|
var meshName = node.Name ?? mesh.Name ?? "(no name)";
|
||||||
var primitiveCount = mesh.Primitives.Count;
|
var primitiveCount = mesh.Primitives.Count;
|
||||||
if (primitiveCount != 1)
|
if (primitiveCount != 1)
|
||||||
{
|
|
||||||
throw new Exception($"Mesh \"{meshName}\" has {primitiveCount} primitives, expected 1.");
|
throw new Exception($"Mesh \"{meshName}\" has {primitiveCount} primitives, expected 1.");
|
||||||
}
|
|
||||||
var primitive = mesh.Primitives[0];
|
var primitive = mesh.Primitives[0];
|
||||||
|
|
||||||
// Per glTF specification, an asset with a skin MUST contain skinning attributes on its mesh.
|
// Per glTF specification, an asset with a skin MUST contain skinning attributes on its mesh.
|
||||||
var jointsAccessor = primitive.GetVertexAccessor("JOINTS_0");
|
var jointsAccessor = primitive.GetVertexAccessor("JOINTS_0")
|
||||||
if (jointsAccessor == null)
|
?? throw new Exception($"Skinned mesh \"{meshName}\" is skinned but does not contain skinning vertex attributes.");
|
||||||
throw new Exception($"Skinned mesh \"{meshName}\" is skinned but does not contain skinning vertex attributes.");
|
|
||||||
|
|
||||||
// Build a set of joints that are referenced by this mesh.
|
// Build a set of joints that are referenced by this mesh.
|
||||||
// TODO: Would be neat to omit 0-weighted joints here, but doing so will require some further work on bone mapping behavior to ensure the unweighted joints can still be resolved to valid bone indices during vertex data construction.
|
// TODO: Would be neat to omit 0-weighted joints here, but doing so will require some further work on bone mapping behavior to ensure the unweighted joints can still be resolved to valid bone indices during vertex data construction.
|
||||||
var usedJoints = new HashSet<ushort>();
|
var usedJoints = new HashSet<ushort>();
|
||||||
foreach (var joints in jointsAccessor.AsVector4Array())
|
foreach (var joints in jointsAccessor.AsVector4Array())
|
||||||
|
{
|
||||||
for (var index = 0; index < 4; index++)
|
for (var index = 0; index < 4; index++)
|
||||||
usedJoints.Add((ushort)joints[index]);
|
usedJoints.Add((ushort)joints[index]);
|
||||||
|
}
|
||||||
|
|
||||||
// Only initialise the bones list if we're actually going to put something in it.
|
// Only initialise the bones list if we're actually going to put something in it.
|
||||||
if (_bones == null)
|
_bones ??= [];
|
||||||
_bones = new();
|
|
||||||
|
|
||||||
// Build a dictionary of node-specific joint indices mesh-wide bone indices.
|
// Build a dictionary of node-specific joint indices mesh-wide bone indices.
|
||||||
var nodeBoneMap = new Dictionary<ushort, ushort>();
|
var nodeBoneMap = new Dictionary<ushort, ushort>();
|
||||||
|
|
@ -232,6 +213,7 @@ public class MeshImporter
|
||||||
boneIndex = _bones.Count;
|
boneIndex = _bones.Count;
|
||||||
_bones.Add(jointName);
|
_bones.Add(jointName);
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeBoneMap.Add(usedJoint, (ushort)boneIndex);
|
nodeBoneMap.Add(usedJoint, (ushort)boneIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ using SharpGLTF.Schema2;
|
||||||
|
|
||||||
namespace Penumbra.Import.Models.Import;
|
namespace Penumbra.Import.Models.Import;
|
||||||
|
|
||||||
public partial class ModelImporter
|
public partial class ModelImporter(ModelRoot _model)
|
||||||
{
|
{
|
||||||
public static MdlFile Import(ModelRoot model)
|
public static MdlFile Import(ModelRoot model)
|
||||||
{
|
{
|
||||||
|
|
@ -13,29 +13,22 @@ public partial class ModelImporter
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: This is intended to match TexTool's grouping regex, ".*[_ ^]([0-9]+)[\\.\\-]?([0-9]+)?$"
|
// NOTE: This is intended to match TexTool's grouping regex, ".*[_ ^]([0-9]+)[\\.\\-]?([0-9]+)?$"
|
||||||
[GeneratedRegex(@"[_ ^](?'Mesh'[0-9]+)[.-]?(?'SubMesh'[0-9]+)?$", RegexOptions.Compiled)]
|
[GeneratedRegex(@"[_ ^](?'Mesh'[0-9]+)[.-]?(?'SubMesh'[0-9]+)?$", RegexOptions.Compiled | RegexOptions.NonBacktracking | RegexOptions.ExplicitCapture)]
|
||||||
private static partial Regex MeshNameGroupingRegex();
|
private static partial Regex MeshNameGroupingRegex();
|
||||||
|
|
||||||
private readonly ModelRoot _model;
|
private readonly List<MdlStructs.MeshStruct> _meshes = [];
|
||||||
|
private readonly List<MdlStructs.SubmeshStruct> _subMeshes = [];
|
||||||
|
|
||||||
private List<MdlStructs.MeshStruct> _meshes = new();
|
private readonly List<MdlStructs.VertexDeclarationStruct> _vertexDeclarations = [];
|
||||||
private List<MdlStructs.SubmeshStruct> _subMeshes = new();
|
private readonly List<byte> _vertexBuffer = [];
|
||||||
|
|
||||||
private List<MdlStructs.VertexDeclarationStruct> _vertexDeclarations = new();
|
private readonly List<ushort> _indices = [];
|
||||||
private List<byte> _vertexBuffer = new();
|
|
||||||
|
|
||||||
private List<ushort> _indices = new();
|
private readonly List<string> _bones = [];
|
||||||
|
private readonly List<MdlStructs.BoneTableStruct> _boneTables = [];
|
||||||
|
|
||||||
private List<string> _bones = new();
|
private readonly Dictionary<string, List<MdlStructs.ShapeMeshStruct>> _shapeMeshes = [];
|
||||||
private List<MdlStructs.BoneTableStruct> _boneTables = new();
|
private readonly List<MdlStructs.ShapeValueStruct> _shapeValues = [];
|
||||||
|
|
||||||
private Dictionary<string, List<MdlStructs.ShapeMeshStruct>> _shapeMeshes = new();
|
|
||||||
private List<MdlStructs.ShapeValueStruct> _shapeValues = new();
|
|
||||||
|
|
||||||
private ModelImporter(ModelRoot model)
|
|
||||||
{
|
|
||||||
_model = model;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MdlFile Create()
|
private MdlFile Create()
|
||||||
{
|
{
|
||||||
|
|
@ -43,8 +36,8 @@ public partial class ModelImporter
|
||||||
foreach (var subMeshNodes in GroupedMeshNodes())
|
foreach (var subMeshNodes in GroupedMeshNodes())
|
||||||
BuildMeshForGroup(subMeshNodes);
|
BuildMeshForGroup(subMeshNodes);
|
||||||
|
|
||||||
// Now that all of the meshes have been built, we can build some of the model-wide metadata.
|
// Now that all the meshes have been built, we can build some of the model-wide metadata.
|
||||||
var shapes = new List<MdlFile.Shape>();
|
var shapes = new List<MdlFile.Shape>();
|
||||||
var shapeMeshes = new List<MdlStructs.ShapeMeshStruct>();
|
var shapeMeshes = new List<MdlStructs.ShapeMeshStruct>();
|
||||||
foreach (var (keyName, keyMeshes) in _shapeMeshes)
|
foreach (var (keyName, keyMeshes) in _shapeMeshes)
|
||||||
{
|
{
|
||||||
|
|
@ -53,75 +46,64 @@ public partial class ModelImporter
|
||||||
ShapeName = keyName,
|
ShapeName = keyName,
|
||||||
// NOTE: these values are per-LoD.
|
// NOTE: these values are per-LoD.
|
||||||
ShapeMeshStartIndex = [(ushort)shapeMeshes.Count, 0, 0],
|
ShapeMeshStartIndex = [(ushort)shapeMeshes.Count, 0, 0],
|
||||||
ShapeMeshCount = [(ushort)keyMeshes.Count, 0, 0],
|
ShapeMeshCount = [(ushort)keyMeshes.Count, 0, 0],
|
||||||
});
|
});
|
||||||
shapeMeshes.AddRange(keyMeshes);
|
shapeMeshes.AddRange(keyMeshes);
|
||||||
}
|
}
|
||||||
|
|
||||||
var indexBuffer = _indices.SelectMany(BitConverter.GetBytes).ToArray();
|
var indexBuffer = _indices.SelectMany(BitConverter.GetBytes).ToArray();
|
||||||
|
|
||||||
var emptyBoundingBox = new MdlStructs.BoundingBoxStruct()
|
|
||||||
{
|
|
||||||
Min = [0, 0, 0, 0],
|
|
||||||
Max = [0, 0, 0, 0],
|
|
||||||
};
|
|
||||||
|
|
||||||
// And finally, the MdlFile itself.
|
// And finally, the MdlFile itself.
|
||||||
return new MdlFile()
|
return new MdlFile
|
||||||
{
|
{
|
||||||
VertexOffset = [0, 0, 0],
|
VertexOffset = [0, 0, 0],
|
||||||
VertexBufferSize = [(uint)_vertexBuffer.Count, 0, 0],
|
VertexBufferSize = [(uint)_vertexBuffer.Count, 0, 0],
|
||||||
IndexOffset = [(uint)_vertexBuffer.Count, 0, 0],
|
IndexOffset = [(uint)_vertexBuffer.Count, 0, 0],
|
||||||
IndexBufferSize = [(uint)indexBuffer.Length, 0, 0],
|
IndexBufferSize = [(uint)indexBuffer.Length, 0, 0],
|
||||||
|
VertexDeclarations = [.. _vertexDeclarations],
|
||||||
VertexDeclarations = _vertexDeclarations.ToArray(),
|
Meshes = [.. _meshes],
|
||||||
Meshes = _meshes.ToArray(),
|
SubMeshes = [.. _subMeshes],
|
||||||
SubMeshes = _subMeshes.ToArray(),
|
BoneTables = [.. _boneTables],
|
||||||
|
Bones = [.. _bones],
|
||||||
BoneTables = _boneTables.ToArray(),
|
|
||||||
Bones = _bones.ToArray(),
|
|
||||||
// TODO: Game doesn't seem to rely on this, but would be good to populate.
|
// TODO: Game doesn't seem to rely on this, but would be good to populate.
|
||||||
SubMeshBoneMap = [],
|
SubMeshBoneMap = [],
|
||||||
|
Shapes = [.. shapes],
|
||||||
Shapes = shapes.ToArray(),
|
ShapeMeshes = [.. shapeMeshes],
|
||||||
ShapeMeshes = shapeMeshes.ToArray(),
|
ShapeValues = [.. _shapeValues],
|
||||||
ShapeValues = _shapeValues.ToArray(),
|
LodCount = 1,
|
||||||
|
Lods =
|
||||||
LodCount = 1,
|
[
|
||||||
|
new MdlStructs.LodStruct
|
||||||
Lods = [new MdlStructs.LodStruct()
|
{
|
||||||
{
|
MeshIndex = 0,
|
||||||
MeshIndex = 0,
|
MeshCount = (ushort)_meshes.Count,
|
||||||
MeshCount = (ushort)_meshes.Count,
|
ModelLodRange = 0,
|
||||||
|
TextureLodRange = 0,
|
||||||
ModelLodRange = 0,
|
VertexDataOffset = 0,
|
||||||
TextureLodRange = 0,
|
VertexBufferSize = (uint)_vertexBuffer.Count,
|
||||||
|
IndexDataOffset = (uint)_vertexBuffer.Count,
|
||||||
VertexDataOffset = 0,
|
IndexBufferSize = (uint)indexBuffer.Length,
|
||||||
VertexBufferSize = (uint)_vertexBuffer.Count,
|
},
|
||||||
IndexDataOffset = (uint)_vertexBuffer.Count,
|
],
|
||||||
IndexBufferSize = (uint)indexBuffer.Length,
|
|
||||||
}],
|
|
||||||
|
|
||||||
// TODO: Would be good to populate from gltf material names.
|
// TODO: Would be good to populate from gltf material names.
|
||||||
Materials = ["/NO_MATERIAL"],
|
Materials = ["/NO_MATERIAL"],
|
||||||
|
|
||||||
// TODO: Would be good to calculate all of this up the tree.
|
// TODO: Would be good to calculate all of this up the tree.
|
||||||
Radius = 1,
|
Radius = 1,
|
||||||
BoundingBoxes = emptyBoundingBox,
|
BoundingBoxes = MdlFile.EmptyBoundingBox,
|
||||||
BoneBoundingBoxes = Enumerable.Repeat(emptyBoundingBox, _bones.Count).ToArray(),
|
BoneBoundingBoxes = Enumerable.Repeat(MdlFile.EmptyBoundingBox, _bones.Count).ToArray(),
|
||||||
|
RemainingData = [.._vertexBuffer, ..indexBuffer],
|
||||||
RemainingData = [.._vertexBuffer, ..indexBuffer],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Returns an iterator over sorted, grouped mesh nodes. </summary>
|
/// <summary> Returns an iterator over sorted, grouped mesh nodes. </summary>
|
||||||
private IEnumerable<IEnumerable<Node>> GroupedMeshNodes() =>
|
private IEnumerable<IEnumerable<Node>> GroupedMeshNodes()
|
||||||
_model.LogicalNodes
|
=> _model.LogicalNodes
|
||||||
.Where(node => node.Mesh != null)
|
.Where(node => node.Mesh != null)
|
||||||
.Select(node =>
|
.Select(node =>
|
||||||
{
|
{
|
||||||
var name = node.Name ?? node.Mesh.Name ?? "NOMATCH";
|
var name = node.Name ?? node.Mesh.Name ?? "NOMATCH";
|
||||||
var match = MeshNameGroupingRegex().Match(name);
|
var match = MeshNameGroupingRegex().Match(name);
|
||||||
return (node, match);
|
return (node, match);
|
||||||
})
|
})
|
||||||
|
|
@ -140,12 +122,12 @@ public partial class ModelImporter
|
||||||
private void BuildMeshForGroup(IEnumerable<Node> subMeshNodes)
|
private void BuildMeshForGroup(IEnumerable<Node> subMeshNodes)
|
||||||
{
|
{
|
||||||
// Record some offsets we'll be using later, before they get mutated with mesh values.
|
// Record some offsets we'll be using later, before they get mutated with mesh values.
|
||||||
var subMeshOffset = _subMeshes.Count;
|
var subMeshOffset = _subMeshes.Count;
|
||||||
var vertexOffset = _vertexBuffer.Count;
|
var vertexOffset = _vertexBuffer.Count;
|
||||||
var indexOffset = _indices.Count;
|
var indexOffset = _indices.Count;
|
||||||
var shapeValueOffset = _shapeValues.Count;
|
var shapeValueOffset = _shapeValues.Count;
|
||||||
|
|
||||||
var mesh = MeshImporter.Import(subMeshNodes);
|
var mesh = MeshImporter.Import(subMeshNodes);
|
||||||
var meshStartIndex = (uint)(mesh.MeshStruct.StartIndex + indexOffset);
|
var meshStartIndex = (uint)(mesh.MeshStruct.StartIndex + indexOffset);
|
||||||
|
|
||||||
// If no bone table is used for a mesh, the index is set to 255.
|
// If no bone table is used for a mesh, the index is set to 255.
|
||||||
|
|
@ -163,22 +145,21 @@ public partial class ModelImporter
|
||||||
.ToArray(),
|
.ToArray(),
|
||||||
});
|
});
|
||||||
|
|
||||||
foreach (var subMesh in mesh.SubMeshStructs)
|
_subMeshes.AddRange(mesh.SubMeshStructs.Select(m => m with
|
||||||
_subMeshes.Add(subMesh with
|
{
|
||||||
{
|
IndexOffset = (uint)(m.IndexOffset + indexOffset),
|
||||||
IndexOffset = (uint)(subMesh.IndexOffset + indexOffset),
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
_vertexDeclarations.Add(mesh.VertexDeclaration);
|
_vertexDeclarations.Add(mesh.VertexDeclaration);
|
||||||
_vertexBuffer.AddRange(mesh.VertexBuffer);
|
_vertexBuffer.AddRange(mesh.VertexBuffer);
|
||||||
|
|
||||||
_indices.AddRange(mesh.Indicies);
|
_indices.AddRange(mesh.Indices);
|
||||||
|
|
||||||
foreach (var meshShapeKey in mesh.ShapeKeys)
|
foreach (var meshShapeKey in mesh.ShapeKeys)
|
||||||
{
|
{
|
||||||
if (!_shapeMeshes.TryGetValue(meshShapeKey.Name, out var shapeMeshes))
|
if (!_shapeMeshes.TryGetValue(meshShapeKey.Name, out var shapeMeshes))
|
||||||
{
|
{
|
||||||
shapeMeshes = new();
|
shapeMeshes = [];
|
||||||
_shapeMeshes.Add(meshShapeKey.Name, shapeMeshes);
|
_shapeMeshes.Add(meshShapeKey.Name, shapeMeshes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,6 +184,7 @@ public partial class ModelImporter
|
||||||
boneIndex = _bones.Count;
|
boneIndex = _bones.Count;
|
||||||
_bones.Add(boneName);
|
_bones.Add(boneName);
|
||||||
}
|
}
|
||||||
|
|
||||||
boneIndices.Add((ushort)boneIndex);
|
boneIndices.Add((ushort)boneIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ public class SubMeshImporter
|
||||||
|
|
||||||
public MdlStructs.VertexDeclarationStruct VertexDeclaration;
|
public MdlStructs.VertexDeclarationStruct VertexDeclaration;
|
||||||
|
|
||||||
public ushort VertexCount;
|
public ushort VertexCount;
|
||||||
public byte[] Strides;
|
public byte[] Strides;
|
||||||
public List<byte>[] Streams;
|
public List<byte>[] Streams;
|
||||||
|
|
||||||
public ushort[] Indices;
|
public ushort[] Indices;
|
||||||
|
|
@ -27,19 +27,19 @@ public class SubMeshImporter
|
||||||
return importer.Create();
|
return importer.Create();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly MeshPrimitive _primitive;
|
private readonly MeshPrimitive _primitive;
|
||||||
private readonly IDictionary<ushort, ushort>? _nodeBoneMap;
|
private readonly IDictionary<ushort, ushort>? _nodeBoneMap;
|
||||||
|
|
||||||
private List<VertexAttribute>? _attributes;
|
private List<VertexAttribute>? _attributes;
|
||||||
|
|
||||||
private ushort _vertexCount = 0;
|
private ushort _vertexCount;
|
||||||
private byte[] _strides = [0, 0, 0];
|
private byte[] _strides = [0, 0, 0];
|
||||||
private readonly List<byte>[] _streams;
|
private readonly List<byte>[] _streams;
|
||||||
|
|
||||||
private ushort[]? _indices;
|
private ushort[]? _indices;
|
||||||
|
|
||||||
private readonly List<string>? _morphNames;
|
private readonly List<string>? _morphNames;
|
||||||
private Dictionary<string, List<MdlStructs.ShapeValueStruct>>? _shapeValues;
|
private Dictionary<string, List<MdlStructs.ShapeValueStruct>>? _shapeValues;
|
||||||
|
|
||||||
private SubMeshImporter(Node node, IDictionary<ushort, ushort>? nodeBoneMap)
|
private SubMeshImporter(Node node, IDictionary<ushort, ushort>? nodeBoneMap)
|
||||||
{
|
{
|
||||||
|
|
@ -52,7 +52,7 @@ public class SubMeshImporter
|
||||||
throw new Exception($"Mesh \"{name}\" has {primitiveCount} primitives, expected 1.");
|
throw new Exception($"Mesh \"{name}\" has {primitiveCount} primitives, expected 1.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_primitive = mesh.Primitives[0];
|
_primitive = mesh.Primitives[0];
|
||||||
_nodeBoneMap = nodeBoneMap;
|
_nodeBoneMap = nodeBoneMap;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -67,7 +67,7 @@ public class SubMeshImporter
|
||||||
// All meshes may use up to 3 byte streams.
|
// All meshes may use up to 3 byte streams.
|
||||||
_streams = new List<byte>[3];
|
_streams = new List<byte>[3];
|
||||||
for (var streamIndex = 0; streamIndex < 3; streamIndex++)
|
for (var streamIndex = 0; streamIndex < 3; streamIndex++)
|
||||||
_streams[streamIndex] = new List<byte>();
|
_streams[streamIndex] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private SubMesh Create()
|
private SubMesh Create()
|
||||||
|
|
@ -85,26 +85,22 @@ public class SubMeshImporter
|
||||||
{
|
{
|
||||||
SubMeshStruct = new MdlStructs.SubmeshStruct()
|
SubMeshStruct = new MdlStructs.SubmeshStruct()
|
||||||
{
|
{
|
||||||
IndexOffset = 0,
|
IndexOffset = 0,
|
||||||
IndexCount = (uint)_indices.Length,
|
IndexCount = (uint)_indices.Length,
|
||||||
AttributeIndexMask = 0,
|
AttributeIndexMask = 0,
|
||||||
|
|
||||||
// TODO: Flesh these out. Game doesn't seem to rely on them existing, though.
|
// TODO: Flesh these out. Game doesn't seem to rely on them existing, though.
|
||||||
BoneStartIndex = 0,
|
BoneStartIndex = 0,
|
||||||
BoneCount = 0,
|
BoneCount = 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
VertexDeclaration = new MdlStructs.VertexDeclarationStruct()
|
VertexDeclaration = new MdlStructs.VertexDeclarationStruct()
|
||||||
{
|
{
|
||||||
VertexElements = _attributes.Select(attribute => attribute.Element).ToArray(),
|
VertexElements = _attributes.Select(attribute => attribute.Element).ToArray(),
|
||||||
},
|
},
|
||||||
|
|
||||||
VertexCount = _vertexCount,
|
VertexCount = _vertexCount,
|
||||||
Strides = _strides,
|
Strides = _strides,
|
||||||
Streams = _streams,
|
Streams = _streams,
|
||||||
|
Indices = _indices,
|
||||||
Indices = _indices,
|
|
||||||
|
|
||||||
ShapeValues = _shapeValues,
|
ShapeValues = _shapeValues,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -119,11 +115,12 @@ public class SubMeshImporter
|
||||||
var accessors = _primitive.VertexAccessors;
|
var accessors = _primitive.VertexAccessors;
|
||||||
|
|
||||||
var morphAccessors = Enumerable.Range(0, _primitive.MorphTargetsCount)
|
var morphAccessors = Enumerable.Range(0, _primitive.MorphTargetsCount)
|
||||||
.Select(index => _primitive.GetMorphTargetAccessors(index));
|
.Select(index => _primitive.GetMorphTargetAccessors(index)).ToList();
|
||||||
|
|
||||||
// Try to build all the attributes the mesh might use.
|
// Try to build all the attributes the mesh might use.
|
||||||
// The order here is chosen to match a typical model's element order.
|
// The order here is chosen to match a typical model's element order.
|
||||||
var rawAttributes = new[] {
|
var rawAttributes = new[]
|
||||||
|
{
|
||||||
VertexAttribute.Position(accessors, morphAccessors),
|
VertexAttribute.Position(accessors, morphAccessors),
|
||||||
VertexAttribute.BlendWeight(accessors),
|
VertexAttribute.BlendWeight(accessors),
|
||||||
VertexAttribute.BlendIndex(accessors, _nodeBoneMap),
|
VertexAttribute.BlendIndex(accessors, _nodeBoneMap),
|
||||||
|
|
@ -134,10 +131,17 @@ public class SubMeshImporter
|
||||||
};
|
};
|
||||||
|
|
||||||
var attributes = new List<VertexAttribute>();
|
var attributes = new List<VertexAttribute>();
|
||||||
var offsets = new byte[] { 0, 0, 0 };
|
var offsets = new byte[]
|
||||||
|
{
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
};
|
||||||
foreach (var attribute in rawAttributes)
|
foreach (var attribute in rawAttributes)
|
||||||
{
|
{
|
||||||
if (attribute == null) continue;
|
if (attribute == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
attributes.Add(attribute.WithOffset(offsets[attribute.Stream]));
|
attributes.Add(attribute.WithOffset(offsets[attribute.Stream]));
|
||||||
offsets[attribute.Stream] += attribute.Size;
|
offsets[attribute.Stream] += attribute.Size;
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +181,7 @@ public class SubMeshImporter
|
||||||
BuildShapeValues(morphModifiedVertices);
|
BuildShapeValues(morphModifiedVertices);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildShapeValues(List<int>[] morphModifiedVertices)
|
private void BuildShapeValues(IEnumerable<List<int>> morphModifiedVertices)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_indices);
|
ArgumentNullException.ThrowIfNull(_indices);
|
||||||
ArgumentNullException.ThrowIfNull(_attributes);
|
ArgumentNullException.ThrowIfNull(_attributes);
|
||||||
|
|
@ -198,12 +202,11 @@ public class SubMeshImporter
|
||||||
// Find any indices that target this vertex index and create a mapping.
|
// Find any indices that target this vertex index and create a mapping.
|
||||||
var targetingIndices = _indices.WithIndex()
|
var targetingIndices = _indices.WithIndex()
|
||||||
.SelectWhere(pair => (pair.Value == vertexIndex, pair.Index));
|
.SelectWhere(pair => (pair.Value == vertexIndex, pair.Index));
|
||||||
foreach (var targetingIndex in targetingIndices)
|
shapeValues.AddRange(targetingIndices.Select(targetingIndex => new MdlStructs.ShapeValueStruct
|
||||||
shapeValues.Add(new MdlStructs.ShapeValueStruct()
|
{
|
||||||
{
|
BaseIndicesIndex = (ushort)targetingIndex,
|
||||||
BaseIndicesIndex = (ushort)targetingIndex,
|
ReplacingVertexIndex = _vertexCount,
|
||||||
ReplacingVertexIndex = _vertexCount,
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
_vertexCount++;
|
_vertexCount++;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,27 +13,32 @@ public class VertexAttribute
|
||||||
{
|
{
|
||||||
/// <summary> XIV vertex element metadata structure. </summary>
|
/// <summary> XIV vertex element metadata structure. </summary>
|
||||||
public readonly MdlStructs.VertexElement Element;
|
public readonly MdlStructs.VertexElement Element;
|
||||||
|
|
||||||
/// <summary> Build a byte array containing this vertex attribute's data for the specified vertex index. </summary>
|
/// <summary> Build a byte array containing this vertex attribute's data for the specified vertex index. </summary>
|
||||||
public readonly BuildFn Build;
|
public readonly BuildFn Build;
|
||||||
|
|
||||||
/// <summary> Check if the specified morph target index contains a morph for the specified vertex index. </summary>
|
/// <summary> Check if the specified morph target index contains a morph for the specified vertex index. </summary>
|
||||||
public readonly HasMorphFn HasMorph;
|
public readonly HasMorphFn HasMorph;
|
||||||
|
|
||||||
/// <summary> Build a byte array containing this vertex attribute's data, as modified by the specified morph target, for the specified vertex index. </summary>
|
/// <summary> Build a byte array containing this vertex attribute's data, as modified by the specified morph target, for the specified vertex index. </summary>
|
||||||
public readonly BuildMorphFn BuildMorph;
|
public readonly BuildMorphFn BuildMorph;
|
||||||
|
|
||||||
public byte Stream => Element.Stream;
|
public byte Stream
|
||||||
|
=> Element.Stream;
|
||||||
|
|
||||||
/// <summary> Size in bytes of a single vertex's attribute value. </summary>
|
/// <summary> Size in bytes of a single vertex's attribute value. </summary>
|
||||||
public byte Size => (MdlFile.VertexType)Element.Type switch
|
public byte Size
|
||||||
{
|
=> (MdlFile.VertexType)Element.Type switch
|
||||||
MdlFile.VertexType.Single3 => 12,
|
{
|
||||||
MdlFile.VertexType.Single4 => 16,
|
MdlFile.VertexType.Single3 => 12,
|
||||||
MdlFile.VertexType.UInt => 4,
|
MdlFile.VertexType.Single4 => 16,
|
||||||
MdlFile.VertexType.ByteFloat4 => 4,
|
MdlFile.VertexType.UInt => 4,
|
||||||
MdlFile.VertexType.Half2 => 4,
|
MdlFile.VertexType.ByteFloat4 => 4,
|
||||||
MdlFile.VertexType.Half4 => 8,
|
MdlFile.VertexType.Half2 => 4,
|
||||||
|
MdlFile.VertexType.Half4 => 8,
|
||||||
|
|
||||||
_ => throw new Exception($"Unhandled vertex type {(MdlFile.VertexType)Element.Type}"),
|
_ => throw new Exception($"Unhandled vertex type {(MdlFile.VertexType)Element.Type}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
private VertexAttribute(
|
private VertexAttribute(
|
||||||
MdlStructs.VertexElement element,
|
MdlStructs.VertexElement element,
|
||||||
|
|
@ -42,25 +47,30 @@ public class VertexAttribute
|
||||||
BuildMorphFn? buildMorph = null
|
BuildMorphFn? buildMorph = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Element = element;
|
Element = element;
|
||||||
Build = write;
|
Build = write;
|
||||||
HasMorph = hasMorph ?? DefaultHasMorph;
|
HasMorph = hasMorph ?? DefaultHasMorph;
|
||||||
BuildMorph = buildMorph ?? DefaultBuildMorph;
|
BuildMorph = buildMorph ?? DefaultBuildMorph;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VertexAttribute WithOffset(byte offset) => new VertexAttribute(
|
public VertexAttribute WithOffset(byte offset)
|
||||||
Element with { Offset = offset },
|
=> new(
|
||||||
Build,
|
Element with { Offset = offset },
|
||||||
HasMorph,
|
Build,
|
||||||
BuildMorph
|
HasMorph,
|
||||||
);
|
BuildMorph
|
||||||
|
);
|
||||||
|
|
||||||
// We assume that attributes don't have morph data unless explicitly configured.
|
/// <remarks> We assume that attributes don't have morph data unless explicitly configured. </remarks>
|
||||||
private static bool DefaultHasMorph(int morphIndex, int vertexIndex) => false;
|
private static bool DefaultHasMorph(int morphIndex, int vertexIndex)
|
||||||
|
=> false;
|
||||||
|
|
||||||
// XIV stores shapes as full vertex replacements, so all attributes need to output something for a morph.
|
/// <remarks>
|
||||||
// As a fallback, we're just building the normal vertex data for the index.
|
/// XIV stores shapes as full vertex replacements, so all attributes need to output something for a morph.
|
||||||
private byte[] DefaultBuildMorph(int morphIndex, int vertexIndex) => Build(vertexIndex);
|
/// As a fallback, we're just building the normal vertex data for the index.
|
||||||
|
/// </remarks>>
|
||||||
|
private byte[] DefaultBuildMorph(int morphIndex, int vertexIndex)
|
||||||
|
=> Build(vertexIndex);
|
||||||
|
|
||||||
public static VertexAttribute Position(Accessors accessors, IEnumerable<Accessors> morphAccessors)
|
public static VertexAttribute Position(Accessors accessors, IEnumerable<Accessors> morphAccessors)
|
||||||
{
|
{
|
||||||
|
|
@ -70,29 +80,29 @@ public class VertexAttribute
|
||||||
var element = new MdlStructs.VertexElement()
|
var element = new MdlStructs.VertexElement()
|
||||||
{
|
{
|
||||||
Stream = 0,
|
Stream = 0,
|
||||||
Type = (byte)MdlFile.VertexType.Single3,
|
Type = (byte)MdlFile.VertexType.Single3,
|
||||||
Usage = (byte)MdlFile.VertexUsage.Position,
|
Usage = (byte)MdlFile.VertexUsage.Position,
|
||||||
};
|
};
|
||||||
|
|
||||||
var values = accessor.AsVector3Array();
|
var values = accessor.AsVector3Array();
|
||||||
|
|
||||||
var morphValues = morphAccessors
|
var morphValues = morphAccessors
|
||||||
.Select(accessors => accessors.GetValueOrDefault("POSITION")?.AsVector3Array())
|
.Select(a => a.GetValueOrDefault("POSITION")?.AsVector3Array())
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return new VertexAttribute(
|
return new VertexAttribute(
|
||||||
element,
|
element,
|
||||||
index => BuildSingle3(values[index]),
|
index => BuildSingle3(values[index]),
|
||||||
|
(morphIndex, vertexIndex) =>
|
||||||
hasMorph: (morphIndex, vertexIndex) =>
|
|
||||||
{
|
{
|
||||||
var deltas = morphValues[morphIndex];
|
var deltas = morphValues[morphIndex];
|
||||||
if (deltas == null) return false;
|
if (deltas == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
var delta = deltas[vertexIndex];
|
var delta = deltas[vertexIndex];
|
||||||
return delta != Vector3.Zero;
|
return delta != Vector3.Zero;
|
||||||
},
|
},
|
||||||
|
(morphIndex, vertexIndex) =>
|
||||||
buildMorph: (morphIndex, vertexIndex) =>
|
|
||||||
{
|
{
|
||||||
var value = values[vertexIndex];
|
var value = values[vertexIndex];
|
||||||
|
|
||||||
|
|
@ -116,8 +126,8 @@ public class VertexAttribute
|
||||||
var element = new MdlStructs.VertexElement()
|
var element = new MdlStructs.VertexElement()
|
||||||
{
|
{
|
||||||
Stream = 0,
|
Stream = 0,
|
||||||
Type = (byte)MdlFile.VertexType.ByteFloat4,
|
Type = (byte)MdlFile.VertexType.ByteFloat4,
|
||||||
Usage = (byte)MdlFile.VertexUsage.BlendWeights,
|
Usage = (byte)MdlFile.VertexUsage.BlendWeights,
|
||||||
};
|
};
|
||||||
|
|
||||||
var values = accessor.AsVector4Array();
|
var values = accessor.AsVector4Array();
|
||||||
|
|
@ -142,8 +152,8 @@ public class VertexAttribute
|
||||||
var element = new MdlStructs.VertexElement()
|
var element = new MdlStructs.VertexElement()
|
||||||
{
|
{
|
||||||
Stream = 0,
|
Stream = 0,
|
||||||
Type = (byte)MdlFile.VertexType.UInt,
|
Type = (byte)MdlFile.VertexType.UInt,
|
||||||
Usage = (byte)MdlFile.VertexUsage.BlendIndices,
|
Usage = (byte)MdlFile.VertexUsage.BlendIndices,
|
||||||
};
|
};
|
||||||
|
|
||||||
var values = accessor.AsVector4Array();
|
var values = accessor.AsVector4Array();
|
||||||
|
|
@ -171,20 +181,19 @@ public class VertexAttribute
|
||||||
var element = new MdlStructs.VertexElement()
|
var element = new MdlStructs.VertexElement()
|
||||||
{
|
{
|
||||||
Stream = 1,
|
Stream = 1,
|
||||||
Type = (byte)MdlFile.VertexType.Half4,
|
Type = (byte)MdlFile.VertexType.Half4,
|
||||||
Usage = (byte)MdlFile.VertexUsage.Normal,
|
Usage = (byte)MdlFile.VertexUsage.Normal,
|
||||||
};
|
};
|
||||||
|
|
||||||
var values = accessor.AsVector3Array();
|
var values = accessor.AsVector3Array();
|
||||||
|
|
||||||
var morphValues = morphAccessors
|
var morphValues = morphAccessors
|
||||||
.Select(accessors => accessors.GetValueOrDefault("NORMAL")?.AsVector3Array())
|
.Select(a => a.GetValueOrDefault("NORMAL")?.AsVector3Array())
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return new VertexAttribute(
|
return new VertexAttribute(
|
||||||
element,
|
element,
|
||||||
index => BuildHalf4(new Vector4(values[index], 0)),
|
index => BuildHalf4(new Vector4(values[index], 0)),
|
||||||
|
|
||||||
buildMorph: (morphIndex, vertexIndex) =>
|
buildMorph: (morphIndex, vertexIndex) =>
|
||||||
{
|
{
|
||||||
var value = values[vertexIndex];
|
var value = values[vertexIndex];
|
||||||
|
|
@ -207,7 +216,7 @@ public class VertexAttribute
|
||||||
var element = new MdlStructs.VertexElement()
|
var element = new MdlStructs.VertexElement()
|
||||||
{
|
{
|
||||||
Stream = 1,
|
Stream = 1,
|
||||||
Usage = (byte)MdlFile.VertexUsage.UV,
|
Usage = (byte)MdlFile.VertexUsage.UV,
|
||||||
};
|
};
|
||||||
|
|
||||||
var values1 = accessor1.AsVector2Array();
|
var values1 = accessor1.AsVector2Array();
|
||||||
|
|
@ -241,21 +250,20 @@ public class VertexAttribute
|
||||||
var element = new MdlStructs.VertexElement()
|
var element = new MdlStructs.VertexElement()
|
||||||
{
|
{
|
||||||
Stream = 1,
|
Stream = 1,
|
||||||
Type = (byte)MdlFile.VertexType.ByteFloat4,
|
Type = (byte)MdlFile.VertexType.ByteFloat4,
|
||||||
Usage = (byte)MdlFile.VertexUsage.Tangent1,
|
Usage = (byte)MdlFile.VertexUsage.Tangent1,
|
||||||
};
|
};
|
||||||
|
|
||||||
var values = accessor.AsVector4Array();
|
var values = accessor.AsVector4Array();
|
||||||
|
|
||||||
// Per glTF specification, TANGENT morph values are stored as vec3, with the W component always considered to be 0.
|
// Per glTF specification, TANGENT morph values are stored as vec3, with the W component always considered to be 0.
|
||||||
var morphValues = morphAccessors
|
var morphValues = morphAccessors
|
||||||
.Select(accessors => accessors.GetValueOrDefault("TANGENT")?.AsVector3Array())
|
.Select(a => a.GetValueOrDefault("TANGENT")?.AsVector3Array())
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return new VertexAttribute(
|
return new VertexAttribute(
|
||||||
element,
|
element,
|
||||||
index => BuildByteFloat4(values[index]),
|
index => BuildByteFloat4(values[index]),
|
||||||
|
|
||||||
buildMorph: (morphIndex, vertexIndex) =>
|
buildMorph: (morphIndex, vertexIndex) =>
|
||||||
{
|
{
|
||||||
var value = values[vertexIndex];
|
var value = values[vertexIndex];
|
||||||
|
|
@ -277,8 +285,8 @@ public class VertexAttribute
|
||||||
var element = new MdlStructs.VertexElement()
|
var element = new MdlStructs.VertexElement()
|
||||||
{
|
{
|
||||||
Stream = 1,
|
Stream = 1,
|
||||||
Type = (byte)MdlFile.VertexType.ByteFloat4,
|
Type = (byte)MdlFile.VertexType.ByteFloat4,
|
||||||
Usage = (byte)MdlFile.VertexUsage.Color,
|
Usage = (byte)MdlFile.VertexUsage.Color,
|
||||||
};
|
};
|
||||||
|
|
||||||
var values = accessor.AsVector4Array();
|
var values = accessor.AsVector4Array();
|
||||||
|
|
@ -289,14 +297,16 @@ public class VertexAttribute
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] BuildSingle3(Vector3 input) =>
|
private static byte[] BuildSingle3(Vector3 input)
|
||||||
|
=>
|
||||||
[
|
[
|
||||||
..BitConverter.GetBytes(input.X),
|
..BitConverter.GetBytes(input.X),
|
||||||
..BitConverter.GetBytes(input.Y),
|
..BitConverter.GetBytes(input.Y),
|
||||||
..BitConverter.GetBytes(input.Z),
|
..BitConverter.GetBytes(input.Z),
|
||||||
];
|
];
|
||||||
|
|
||||||
private static byte[] BuildUInt(Vector4 input) =>
|
private static byte[] BuildUInt(Vector4 input)
|
||||||
|
=>
|
||||||
[
|
[
|
||||||
(byte)input.X,
|
(byte)input.X,
|
||||||
(byte)input.Y,
|
(byte)input.Y,
|
||||||
|
|
@ -304,7 +314,8 @@ public class VertexAttribute
|
||||||
(byte)input.W,
|
(byte)input.W,
|
||||||
];
|
];
|
||||||
|
|
||||||
private static byte[] BuildByteFloat4(Vector4 input) =>
|
private static byte[] BuildByteFloat4(Vector4 input)
|
||||||
|
=>
|
||||||
[
|
[
|
||||||
(byte)Math.Round(input.X * 255f),
|
(byte)Math.Round(input.X * 255f),
|
||||||
(byte)Math.Round(input.Y * 255f),
|
(byte)Math.Round(input.Y * 255f),
|
||||||
|
|
@ -312,13 +323,15 @@ public class VertexAttribute
|
||||||
(byte)Math.Round(input.W * 255f),
|
(byte)Math.Round(input.W * 255f),
|
||||||
];
|
];
|
||||||
|
|
||||||
private static byte[] BuildHalf2(Vector2 input) =>
|
private static byte[] BuildHalf2(Vector2 input)
|
||||||
|
=>
|
||||||
[
|
[
|
||||||
..BitConverter.GetBytes((Half)input.X),
|
..BitConverter.GetBytes((Half)input.X),
|
||||||
..BitConverter.GetBytes((Half)input.Y),
|
..BitConverter.GetBytes((Half)input.Y),
|
||||||
];
|
];
|
||||||
|
|
||||||
private static byte[] BuildHalf4(Vector4 input) =>
|
private static byte[] BuildHalf4(Vector4 input)
|
||||||
|
=>
|
||||||
[
|
[
|
||||||
..BitConverter.GetBytes((Half)input.X),
|
..BitConverter.GetBytes((Half)input.X),
|
||||||
..BitConverter.GetBytes((Half)input.Y),
|
..BitConverter.GetBytes((Half)input.Y),
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ public class MultiModPanel(ModFileSystemSelector _selector, ModDataEditor _edito
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
var icon = (path is ModFileSystem.Leaf ? FontAwesomeIcon.FileCircleMinus : FontAwesomeIcon.FolderMinus).ToIconString();
|
var icon = (path is ModFileSystem.Leaf ? FontAwesomeIcon.FileCircleMinus : FontAwesomeIcon.FolderMinus).ToIconString();
|
||||||
if (ImGuiUtil.DrawDisabledButton(icon, new Vector2(sizeType), "Remove from selection.", false, true))
|
if (ImGuiUtil.DrawDisabledButton(icon, new Vector2(sizeType), "Remove from selection.", false, true))
|
||||||
_selector.RemovePathFromMultiselection(path);
|
_selector.RemovePathFromMultiSelection(path);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue