mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-15 21:24:18 +01:00
Import attributes
This commit is contained in:
parent
182550ce15
commit
dada03905f
4 changed files with 95 additions and 14 deletions
|
|
@ -19,6 +19,8 @@ public class MeshImporter(IEnumerable<Node> nodes)
|
||||||
|
|
||||||
public List<string>? Bones;
|
public List<string>? Bones;
|
||||||
|
|
||||||
|
public List<string> MetaAttributes;
|
||||||
|
|
||||||
public List<MeshShapeKey> ShapeKeys;
|
public List<MeshShapeKey> ShapeKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,6 +50,8 @@ public class MeshImporter(IEnumerable<Node> nodes)
|
||||||
|
|
||||||
private List<string>? _bones;
|
private List<string>? _bones;
|
||||||
|
|
||||||
|
private readonly List<string> _metaAttributes = [];
|
||||||
|
|
||||||
private readonly Dictionary<string, List<MdlStructs.ShapeValueStruct>> _shapeValues = [];
|
private readonly Dictionary<string, List<MdlStructs.ShapeValueStruct>> _shapeValues = [];
|
||||||
|
|
||||||
private Mesh Create()
|
private Mesh Create()
|
||||||
|
|
@ -83,6 +87,7 @@ public class MeshImporter(IEnumerable<Node> nodes)
|
||||||
VertexBuffer = _streams[0].Concat(_streams[1]).Concat(_streams[2]),
|
VertexBuffer = _streams[0].Concat(_streams[1]).Concat(_streams[2]),
|
||||||
Indices = _indices,
|
Indices = _indices,
|
||||||
Bones = _bones,
|
Bones = _bones,
|
||||||
|
MetaAttributes = _metaAttributes,
|
||||||
ShapeKeys = _shapeValues
|
ShapeKeys = _shapeValues
|
||||||
.Select(pair => new MeshShapeKey()
|
.Select(pair => new MeshShapeKey()
|
||||||
{
|
{
|
||||||
|
|
@ -153,6 +158,8 @@ public class MeshImporter(IEnumerable<Node> nodes)
|
||||||
_subMeshes.Add(subMesh.SubMeshStruct with
|
_subMeshes.Add(subMesh.SubMeshStruct with
|
||||||
{
|
{
|
||||||
IndexOffset = (ushort)(subMesh.SubMeshStruct.IndexOffset + indexOffset),
|
IndexOffset = (ushort)(subMesh.SubMeshStruct.IndexOffset + indexOffset),
|
||||||
|
AttributeIndexMask = Utility.GetMergedAttributeMask(
|
||||||
|
subMesh.SubMeshStruct.AttributeIndexMask, subMesh.MetaAttributes, _metaAttributes),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ public partial class ModelImporter(ModelRoot _model)
|
||||||
private readonly List<string> _bones = [];
|
private readonly List<string> _bones = [];
|
||||||
private readonly List<MdlStructs.BoneTableStruct> _boneTables = [];
|
private readonly List<MdlStructs.BoneTableStruct> _boneTables = [];
|
||||||
|
|
||||||
|
private readonly List<string> _metaAttributes = [];
|
||||||
|
|
||||||
private readonly Dictionary<string, List<MdlStructs.ShapeMeshStruct>> _shapeMeshes = [];
|
private readonly Dictionary<string, List<MdlStructs.ShapeMeshStruct>> _shapeMeshes = [];
|
||||||
private readonly List<MdlStructs.ShapeValueStruct> _shapeValues = [];
|
private readonly List<MdlStructs.ShapeValueStruct> _shapeValues = [];
|
||||||
|
|
||||||
|
|
@ -71,6 +73,7 @@ public partial class ModelImporter(ModelRoot _model)
|
||||||
Bones = [.. _bones],
|
Bones = [.. _bones],
|
||||||
// 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 = [],
|
||||||
|
Attributes = [.. _metaAttributes],
|
||||||
Shapes = [.. shapes],
|
Shapes = [.. shapes],
|
||||||
ShapeMeshes = [.. shapeMeshes],
|
ShapeMeshes = [.. shapeMeshes],
|
||||||
ShapeValues = [.. _shapeValues],
|
ShapeValues = [.. _shapeValues],
|
||||||
|
|
@ -155,6 +158,8 @@ public partial class ModelImporter(ModelRoot _model)
|
||||||
|
|
||||||
_subMeshes.AddRange(mesh.SubMeshStructs.Select(m => m with
|
_subMeshes.AddRange(mesh.SubMeshStructs.Select(m => m with
|
||||||
{
|
{
|
||||||
|
AttributeIndexMask = Utility.GetMergedAttributeMask(
|
||||||
|
m.AttributeIndexMask, mesh.MetaAttributes, _metaAttributes),
|
||||||
IndexOffset = (uint)(m.IndexOffset + indexOffset),
|
IndexOffset = (uint)(m.IndexOffset + indexOffset),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Text.Json;
|
||||||
using Lumina.Data.Parsing;
|
using Lumina.Data.Parsing;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using SharpGLTF.Schema2;
|
using SharpGLTF.Schema2;
|
||||||
|
|
@ -20,6 +21,8 @@ public class SubMeshImporter
|
||||||
|
|
||||||
public ushort[] Indices;
|
public ushort[] Indices;
|
||||||
|
|
||||||
|
public string[] MetaAttributes;
|
||||||
|
|
||||||
public Dictionary<string, List<MdlStructs.ShapeValueStruct>> ShapeValues;
|
public Dictionary<string, List<MdlStructs.ShapeValueStruct>> ShapeValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,10 +32,11 @@ 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 readonly IDictionary<string, JsonElement>? _nodeExtras;
|
||||||
|
|
||||||
private List<VertexAttribute>? _attributes;
|
private List<VertexAttribute>? _vertexAttributes;
|
||||||
|
|
||||||
private ushort _vertexCount;
|
private ushort _vertexCount;
|
||||||
private byte[] _strides = [0, 0, 0];
|
private byte[] _strides = [0, 0, 0];
|
||||||
|
|
@ -40,6 +44,8 @@ public class SubMeshImporter
|
||||||
|
|
||||||
private ushort[]? _indices;
|
private ushort[]? _indices;
|
||||||
|
|
||||||
|
private string[]? _metaAttributes;
|
||||||
|
|
||||||
private readonly List<string>? _morphNames;
|
private readonly List<string>? _morphNames;
|
||||||
private Dictionary<string, List<MdlStructs.ShapeValueStruct>>? _shapeValues;
|
private Dictionary<string, List<MdlStructs.ShapeValueStruct>>? _shapeValues;
|
||||||
|
|
||||||
|
|
@ -57,6 +63,15 @@ public class SubMeshImporter
|
||||||
_primitive = mesh.Primitives[0];
|
_primitive = mesh.Primitives[0];
|
||||||
_nodeBoneMap = nodeBoneMap;
|
_nodeBoneMap = nodeBoneMap;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_nodeExtras = node.Extras.Deserialize<Dictionary<string, JsonElement>>();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_nodeExtras = null;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_morphNames = mesh.Extras.GetNode("targetNames").Deserialize<List<string>>();
|
_morphNames = mesh.Extras.GetNode("targetNames").Deserialize<List<string>>();
|
||||||
|
|
@ -76,24 +91,34 @@ public class SubMeshImporter
|
||||||
{
|
{
|
||||||
// Build all the data we'll need.
|
// Build all the data we'll need.
|
||||||
BuildIndices();
|
BuildIndices();
|
||||||
BuildAttributes();
|
BuildVertexAttributes();
|
||||||
BuildVertices();
|
BuildVertices();
|
||||||
|
BuildMetaAttributes();
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(_indices);
|
ArgumentNullException.ThrowIfNull(_indices);
|
||||||
ArgumentNullException.ThrowIfNull(_attributes);
|
ArgumentNullException.ThrowIfNull(_vertexAttributes);
|
||||||
ArgumentNullException.ThrowIfNull(_shapeValues);
|
ArgumentNullException.ThrowIfNull(_shapeValues);
|
||||||
|
ArgumentNullException.ThrowIfNull(_metaAttributes);
|
||||||
|
|
||||||
var material = _primitive.Material.Name;
|
var material = _primitive.Material.Name;
|
||||||
if (material == "")
|
if (material == "")
|
||||||
material = null;
|
material = null;
|
||||||
|
|
||||||
|
// At this level, we assume that attributes are wholly controlled by this sub-mesh.
|
||||||
|
var attributeMask = _metaAttributes.Length switch
|
||||||
|
{
|
||||||
|
< 32 => (1u << _metaAttributes.Length) - 1,
|
||||||
|
32 => uint.MaxValue,
|
||||||
|
> 32 => throw new Exception("Models may utilise a maximum of 32 attributes."),
|
||||||
|
};
|
||||||
|
|
||||||
return new SubMesh()
|
return new SubMesh()
|
||||||
{
|
{
|
||||||
SubMeshStruct = new MdlStructs.SubmeshStruct()
|
SubMeshStruct = new MdlStructs.SubmeshStruct()
|
||||||
{
|
{
|
||||||
IndexOffset = 0,
|
IndexOffset = 0,
|
||||||
IndexCount = (uint)_indices.Length,
|
IndexCount = (uint)_indices.Length,
|
||||||
AttributeIndexMask = 0,
|
AttributeIndexMask = attributeMask,
|
||||||
|
|
||||||
// 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,
|
||||||
|
|
@ -102,12 +127,13 @@ public class SubMeshImporter
|
||||||
Material = material,
|
Material = material,
|
||||||
VertexDeclaration = new MdlStructs.VertexDeclarationStruct()
|
VertexDeclaration = new MdlStructs.VertexDeclarationStruct()
|
||||||
{
|
{
|
||||||
VertexElements = _attributes.Select(attribute => attribute.Element).ToArray(),
|
VertexElements = _vertexAttributes.Select(attribute => attribute.Element).ToArray(),
|
||||||
},
|
},
|
||||||
VertexCount = _vertexCount,
|
VertexCount = _vertexCount,
|
||||||
Strides = _strides,
|
Strides = _strides,
|
||||||
Streams = _streams,
|
Streams = _streams,
|
||||||
Indices = _indices,
|
Indices = _indices,
|
||||||
|
MetaAttributes = _metaAttributes,
|
||||||
ShapeValues = _shapeValues,
|
ShapeValues = _shapeValues,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +143,7 @@ public class SubMeshImporter
|
||||||
_indices = _primitive.GetIndices().Select(idx => (ushort)idx).ToArray();
|
_indices = _primitive.GetIndices().Select(idx => (ushort)idx).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildAttributes()
|
private void BuildVertexAttributes()
|
||||||
{
|
{
|
||||||
var accessors = _primitive.VertexAccessors;
|
var accessors = _primitive.VertexAccessors;
|
||||||
|
|
||||||
|
|
@ -153,14 +179,14 @@ public class SubMeshImporter
|
||||||
offsets[attribute.Stream] += attribute.Size;
|
offsets[attribute.Stream] += attribute.Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
_attributes = attributes;
|
_vertexAttributes = attributes;
|
||||||
// After building the attributes, the resulting next offsets are our stream strides.
|
// After building the attributes, the resulting next offsets are our stream strides.
|
||||||
_strides = offsets;
|
_strides = offsets;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildVertices()
|
private void BuildVertices()
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_attributes);
|
ArgumentNullException.ThrowIfNull(_vertexAttributes);
|
||||||
|
|
||||||
// Lists of vertex indices that are effected by each morph target for this primitive.
|
// Lists of vertex indices that are effected by each morph target for this primitive.
|
||||||
var morphModifiedVertices = Enumerable.Range(0, _primitive.MorphTargetsCount)
|
var morphModifiedVertices = Enumerable.Range(0, _primitive.MorphTargetsCount)
|
||||||
|
|
@ -173,13 +199,13 @@ public class SubMeshImporter
|
||||||
for (var vertexIndex = 0; vertexIndex < _vertexCount; vertexIndex++)
|
for (var vertexIndex = 0; vertexIndex < _vertexCount; vertexIndex++)
|
||||||
{
|
{
|
||||||
// Write out vertex data to streams for each attribute.
|
// Write out vertex data to streams for each attribute.
|
||||||
foreach (var attribute in _attributes)
|
foreach (var attribute in _vertexAttributes)
|
||||||
_streams[attribute.Stream].AddRange(attribute.Build(vertexIndex));
|
_streams[attribute.Stream].AddRange(attribute.Build(vertexIndex));
|
||||||
|
|
||||||
// Record which morph targets have values for this vertex, if any.
|
// Record which morph targets have values for this vertex, if any.
|
||||||
var changedMorphs = morphModifiedVertices
|
var changedMorphs = morphModifiedVertices
|
||||||
.WithIndex()
|
.WithIndex()
|
||||||
.Where(pair => _attributes.Any(attribute => attribute.HasMorph(pair.Index, vertexIndex)))
|
.Where(pair => _vertexAttributes.Any(attribute => attribute.HasMorph(pair.Index, vertexIndex)))
|
||||||
.Select(pair => pair.Value);
|
.Select(pair => pair.Value);
|
||||||
foreach (var modifiedVertices in changedMorphs)
|
foreach (var modifiedVertices in changedMorphs)
|
||||||
modifiedVertices.Add(vertexIndex);
|
modifiedVertices.Add(vertexIndex);
|
||||||
|
|
@ -191,7 +217,7 @@ public class SubMeshImporter
|
||||||
private void BuildShapeValues(IEnumerable<List<int>> morphModifiedVertices)
|
private void BuildShapeValues(IEnumerable<List<int>> morphModifiedVertices)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_indices);
|
ArgumentNullException.ThrowIfNull(_indices);
|
||||||
ArgumentNullException.ThrowIfNull(_attributes);
|
ArgumentNullException.ThrowIfNull(_vertexAttributes);
|
||||||
|
|
||||||
var morphShapeValues = new Dictionary<string, List<MdlStructs.ShapeValueStruct>>();
|
var morphShapeValues = new Dictionary<string, List<MdlStructs.ShapeValueStruct>>();
|
||||||
|
|
||||||
|
|
@ -203,7 +229,7 @@ public class SubMeshImporter
|
||||||
foreach (var vertexIndex in modifiedVertices)
|
foreach (var vertexIndex in modifiedVertices)
|
||||||
{
|
{
|
||||||
// Write out the morphed vertex to the vertex streams.
|
// Write out the morphed vertex to the vertex streams.
|
||||||
foreach (var attribute in _attributes)
|
foreach (var attribute in _vertexAttributes)
|
||||||
_streams[attribute.Stream].AddRange(attribute.BuildMorph(morphIndex, vertexIndex));
|
_streams[attribute.Stream].AddRange(attribute.BuildMorph(morphIndex, vertexIndex));
|
||||||
|
|
||||||
// Find any indices that target this vertex index and create a mapping.
|
// Find any indices that target this vertex index and create a mapping.
|
||||||
|
|
@ -224,4 +250,13 @@ public class SubMeshImporter
|
||||||
|
|
||||||
_shapeValues = morphShapeValues;
|
_shapeValues = morphShapeValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BuildMetaAttributes()
|
||||||
|
{
|
||||||
|
// We consider any "extras" key with a boolean value set to `true` to be an attribute.
|
||||||
|
_metaAttributes = _nodeExtras?
|
||||||
|
.Where(pair => pair.Value.ValueKind == JsonValueKind.True)
|
||||||
|
.Select(pair => pair.Key)
|
||||||
|
.ToArray() ?? [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
Penumbra/Import/Models/Import/Utility.cs
Normal file
34
Penumbra/Import/Models/Import/Utility.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
namespace Penumbra.Import.Models.Import;
|
||||||
|
|
||||||
|
public static class Utility
|
||||||
|
{
|
||||||
|
/// <summary> Merge attributes into an existing attribute array, providing an updated submesh mask. </summary>
|
||||||
|
/// <param name="oldMask"> Old submesh attribute mask. </param>
|
||||||
|
/// <param name="oldAttributes"> Old attribute array that should be merged. </param>
|
||||||
|
/// <param name="newAttributes"> New attribute array. Will be mutated. </param>
|
||||||
|
/// <returns> New submesh attribute mask, updated to match the merged attribute array. </returns>
|
||||||
|
public static uint GetMergedAttributeMask(uint oldMask, IList<string> oldAttributes, List<string> newAttributes)
|
||||||
|
{
|
||||||
|
var metaAttributes = Enumerable.Range(0, 32)
|
||||||
|
.Where(index => ((oldMask >> index) & 1) == 1)
|
||||||
|
.Select(index => oldAttributes[index]);
|
||||||
|
|
||||||
|
var newMask = 0u;
|
||||||
|
|
||||||
|
foreach (var metaAttribute in metaAttributes)
|
||||||
|
{
|
||||||
|
var attributeIndex = newAttributes.IndexOf(metaAttribute);
|
||||||
|
if (attributeIndex == -1)
|
||||||
|
{
|
||||||
|
if (newAttributes.Count >= 32)
|
||||||
|
throw new Exception("Models may utilise a maximum of 32 attributes.");
|
||||||
|
|
||||||
|
newAttributes.Add(metaAttribute);
|
||||||
|
attributeIndex = newAttributes.Count - 1;
|
||||||
|
}
|
||||||
|
newMask |= 1u << attributeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMask;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue