mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Clean up submeshes
This commit is contained in:
parent
b5b3e1b1f2
commit
6de3077afa
3 changed files with 229 additions and 205 deletions
217
Penumbra/Import/Models/Import/SubMeshImporter.cs
Normal file
217
Penumbra/Import/Models/Import/SubMeshImporter.cs
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
using Lumina.Data.Parsing;
|
||||||
|
using OtterGui;
|
||||||
|
using SharpGLTF.Schema2;
|
||||||
|
|
||||||
|
namespace Penumbra.Import.Models.Import;
|
||||||
|
|
||||||
|
public class SubMeshImporter
|
||||||
|
{
|
||||||
|
public struct SubMesh
|
||||||
|
{
|
||||||
|
public MdlStructs.SubmeshStruct Struct;
|
||||||
|
|
||||||
|
public MdlStructs.VertexDeclarationStruct VertexDeclaration;
|
||||||
|
|
||||||
|
public ushort VertexCount;
|
||||||
|
public byte[] Strides;
|
||||||
|
public List<byte>[] Streams;
|
||||||
|
|
||||||
|
public ushort[] Indices;
|
||||||
|
|
||||||
|
public Dictionary<string, List<MdlStructs.ShapeValueStruct>> ShapeValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SubMesh Import(Node node, IDictionary<ushort, ushort>? nodeBoneMap)
|
||||||
|
{
|
||||||
|
var importer = new SubMeshImporter(node, nodeBoneMap);
|
||||||
|
return importer.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly MeshPrimitive _primitive;
|
||||||
|
private readonly IDictionary<ushort, ushort>? _nodeBoneMap;
|
||||||
|
|
||||||
|
private List<VertexAttribute>? _attributes;
|
||||||
|
|
||||||
|
private ushort _vertexCount = 0;
|
||||||
|
private byte[] _strides = [0, 0, 0];
|
||||||
|
private readonly List<byte>[] _streams;
|
||||||
|
|
||||||
|
private ushort[]? _indices;
|
||||||
|
|
||||||
|
private readonly List<string>? _morphNames;
|
||||||
|
private Dictionary<string, List<MdlStructs.ShapeValueStruct>>? _shapeValues;
|
||||||
|
|
||||||
|
private SubMeshImporter(Node node, IDictionary<ushort, ushort>? nodeBoneMap)
|
||||||
|
{
|
||||||
|
var mesh = node.Mesh;
|
||||||
|
|
||||||
|
var primitiveCount = mesh.Primitives.Count;
|
||||||
|
if (primitiveCount != 1)
|
||||||
|
{
|
||||||
|
var name = node.Name ?? mesh.Name ?? "(no name)";
|
||||||
|
throw new Exception($"Mesh \"{name}\" has {primitiveCount} primitives, expected 1.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_primitive = mesh.Primitives[0];
|
||||||
|
_nodeBoneMap = nodeBoneMap;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_morphNames = mesh.Extras.GetNode("targetNames").Deserialize<List<string>>();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_morphNames = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All meshes may use up to 3 byte streams.
|
||||||
|
_streams = new List<byte>[3];
|
||||||
|
for (var i = 0; i < 3; i++)
|
||||||
|
_streams[i] = new List<byte>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubMesh Create()
|
||||||
|
{
|
||||||
|
// Build all the data we'll need.
|
||||||
|
BuildIndices();
|
||||||
|
BuildAttributes();
|
||||||
|
BuildVertices();
|
||||||
|
|
||||||
|
ArgumentNullException.ThrowIfNull(_indices);
|
||||||
|
ArgumentNullException.ThrowIfNull(_attributes);
|
||||||
|
ArgumentNullException.ThrowIfNull(_shapeValues);
|
||||||
|
|
||||||
|
return new SubMesh()
|
||||||
|
{
|
||||||
|
Struct = new MdlStructs.SubmeshStruct()
|
||||||
|
{
|
||||||
|
IndexOffset = 0,
|
||||||
|
IndexCount = (uint)_indices.Length,
|
||||||
|
AttributeIndexMask = 0,
|
||||||
|
|
||||||
|
// TODO: Flesh these out. Game doesn't seem to rely on them existing, though.
|
||||||
|
BoneStartIndex = 0,
|
||||||
|
BoneCount = 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
VertexDeclaration = new MdlStructs.VertexDeclarationStruct()
|
||||||
|
{
|
||||||
|
VertexElements = _attributes.Select(attribute => attribute.Element).ToArray(),
|
||||||
|
},
|
||||||
|
|
||||||
|
VertexCount = _vertexCount,
|
||||||
|
Strides = _strides,
|
||||||
|
Streams = _streams,
|
||||||
|
|
||||||
|
Indices = _indices,
|
||||||
|
|
||||||
|
ShapeValues = _shapeValues,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildIndices()
|
||||||
|
{
|
||||||
|
_indices = _primitive.GetIndices().Select(idx => (ushort)idx).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildAttributes()
|
||||||
|
{
|
||||||
|
var accessors = _primitive.VertexAccessors;
|
||||||
|
|
||||||
|
var morphAccessors = Enumerable.Range(0, _primitive.MorphTargetsCount)
|
||||||
|
.Select(index => _primitive.GetMorphTargetAccessors(index));
|
||||||
|
|
||||||
|
// Try to build all the attributes the mesh might use.
|
||||||
|
// The order here is chosen to match a typical model's element order.
|
||||||
|
var rawAttributes = new[] {
|
||||||
|
VertexAttribute.Position(accessors, morphAccessors),
|
||||||
|
VertexAttribute.BlendWeight(accessors),
|
||||||
|
VertexAttribute.BlendIndex(accessors, _nodeBoneMap),
|
||||||
|
VertexAttribute.Normal(accessors, morphAccessors),
|
||||||
|
VertexAttribute.Tangent1(accessors, morphAccessors),
|
||||||
|
VertexAttribute.Color(accessors),
|
||||||
|
VertexAttribute.Uv(accessors),
|
||||||
|
};
|
||||||
|
|
||||||
|
var attributes = new List<VertexAttribute>();
|
||||||
|
var offsets = new byte[] { 0, 0, 0 };
|
||||||
|
foreach (var attribute in rawAttributes)
|
||||||
|
{
|
||||||
|
if (attribute == null) continue;
|
||||||
|
attributes.Add(attribute.WithOffset(offsets[attribute.Stream]));
|
||||||
|
offsets[attribute.Stream] += attribute.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
_attributes = attributes;
|
||||||
|
// After building the attributes, the resulting next offsets are our stream strides.
|
||||||
|
_strides = offsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildVertices()
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(_attributes);
|
||||||
|
|
||||||
|
// Lists of vertex indices that are effected by each morph target for this primitive.
|
||||||
|
var morphModifiedVertices = Enumerable.Range(0, _primitive.MorphTargetsCount)
|
||||||
|
.Select(_ => new List<int>())
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
// We can safely assume that POSITION exists by this point - and if, by some bizarre chance, it doesn't, failing out is sane.
|
||||||
|
_vertexCount = (ushort)_primitive.VertexAccessors["POSITION"].Count;
|
||||||
|
|
||||||
|
for (var vertexIndex = 0; vertexIndex < _vertexCount; vertexIndex++)
|
||||||
|
{
|
||||||
|
// Write out vertex data to streams for each attribute.
|
||||||
|
foreach (var attribute in _attributes)
|
||||||
|
_streams[attribute.Stream].AddRange(attribute.Build(vertexIndex));
|
||||||
|
|
||||||
|
// Record which morph targets have values for this vertex, if any.
|
||||||
|
var changedMorphs = morphModifiedVertices
|
||||||
|
.WithIndex()
|
||||||
|
.Where(pair => _attributes.Any(attribute => attribute.HasMorph(pair.Index, vertexIndex)))
|
||||||
|
.Select(pair => pair.Value);
|
||||||
|
foreach (var modifiedVertices in changedMorphs)
|
||||||
|
modifiedVertices.Add(vertexIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildShapeValues(morphModifiedVertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildShapeValues(List<int>[] morphModifiedVertices)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(_indices);
|
||||||
|
ArgumentNullException.ThrowIfNull(_attributes);
|
||||||
|
|
||||||
|
var morphShapeValues = new Dictionary<string, List<MdlStructs.ShapeValueStruct>>();
|
||||||
|
|
||||||
|
foreach (var (modifiedVertices, morphIndex) in morphModifiedVertices.WithIndex())
|
||||||
|
{
|
||||||
|
// Each for a given mesh, each shape key contains a list of shape value mappings.
|
||||||
|
var shapeValues = new List<MdlStructs.ShapeValueStruct>();
|
||||||
|
|
||||||
|
foreach (var vertexIndex in modifiedVertices)
|
||||||
|
{
|
||||||
|
// Write out the morphed vertex to the vertex streams.
|
||||||
|
foreach (var attribute in _attributes)
|
||||||
|
_streams[attribute.Stream].AddRange(attribute.BuildMorph(morphIndex, vertexIndex));
|
||||||
|
|
||||||
|
// Find any indices that target this vertex index and create a mapping.
|
||||||
|
var targetingIndices = _indices.WithIndex()
|
||||||
|
.SelectWhere(pair => (pair.Value == vertexIndex, pair.Index));
|
||||||
|
foreach (var targetingIndex in targetingIndices)
|
||||||
|
shapeValues.Add(new MdlStructs.ShapeValueStruct()
|
||||||
|
{
|
||||||
|
BaseIndicesIndex = (ushort)targetingIndex,
|
||||||
|
ReplacingVertexIndex = _vertexCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
_vertexCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = _morphNames != null ? _morphNames[morphIndex] : $"unnamed_shape_{morphIndex}";
|
||||||
|
morphShapeValues.Add(name, shapeValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
_shapeValues = morphShapeValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,8 @@ public class VertexAttribute
|
||||||
/// <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;
|
||||||
|
|
||||||
/// <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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -462,22 +462,22 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
Penumbra.Log.Information($"nbm {string.Join(",", nodeBoneMap.Select(kv => $"{kv.Key}:{kv.Value}"))}");
|
Penumbra.Log.Information($"nbm {string.Join(",", nodeBoneMap.Select(kv => $"{kv.Key}:{kv.Value}"))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var (vertDecl, newStrides, submesh, vertCount, vertStreams, idxCount, idxs, subMorphData) = NodeMeshThing(node, nodeBoneMap);
|
var subMeshThingy = SubMeshImporter.Import(node, nodeBoneMap);
|
||||||
vertexDeclaration = vertDecl; // TODO: CHECK EQUAL AFTER FIRST
|
vertexDeclaration = subMeshThingy.VertexDeclaration; // TODO: CHECK EQUAL AFTER FIRST
|
||||||
strides = newStrides; // ALSO CHECK EQUAL
|
strides = subMeshThingy.Strides; // ALSO CHECK EQUAL
|
||||||
vertexCount += vertCount;
|
vertexCount += subMeshThingy.VertexCount;
|
||||||
for (var i = 0; i < 3; i++)
|
for (var i = 0; i < 3; i++)
|
||||||
streams[i].AddRange(vertStreams[i]);
|
streams[i].AddRange(subMeshThingy.Streams[i]);
|
||||||
indexCount += idxCount;
|
indexCount += (uint)subMeshThingy.Indices.Length;
|
||||||
// we need to offset the indexes to point into the new stuff
|
// we need to offset the indexes to point into the new stuff
|
||||||
indices.AddRange(idxs.Select(idx => (ushort)(idx + vertOff)));
|
indices.AddRange(subMeshThingy.Indices.Select(idx => (ushort)(idx + vertOff)));
|
||||||
submeshes.Add(submesh with
|
submeshes.Add(subMeshThingy.Struct with
|
||||||
{
|
{
|
||||||
IndexOffset = submesh.IndexOffset + idxOff
|
IndexOffset = subMeshThingy.Struct.IndexOffset + idxOff
|
||||||
// TODO: bone stuff probably
|
// TODO: bone stuff probably
|
||||||
});
|
});
|
||||||
// TODO: HANDLE MORPHS, NEED TO ADJUST EVERY VALUE'S INDEX OFFSETS
|
// TODO: HANDLE MORPHS, NEED TO ADJUST EVERY VALUE'S INDEX OFFSETS
|
||||||
foreach (var (key, shapeValues) in subMorphData)
|
foreach (var (key, shapeValues) in subMeshThingy.ShapeValues)
|
||||||
{
|
{
|
||||||
List<MdlStructs.ShapeValueStruct> valueList;
|
List<MdlStructs.ShapeValueStruct> valueList;
|
||||||
if (!morphData.TryGetValue(key, out valueList))
|
if (!morphData.TryGetValue(key, out valueList))
|
||||||
|
|
@ -601,201 +601,6 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
return (jointNames, usedJoints);
|
return (jointNames, usedJoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
private (
|
|
||||||
MdlStructs.VertexDeclarationStruct,
|
|
||||||
byte[],
|
|
||||||
// MdlStructs.MeshStruct,
|
|
||||||
MdlStructs.SubmeshStruct,
|
|
||||||
ushort,
|
|
||||||
IEnumerable<byte>[],
|
|
||||||
uint,
|
|
||||||
IEnumerable<ushort>,
|
|
||||||
IDictionary<string, List<MdlStructs.ShapeValueStruct>>
|
|
||||||
) NodeMeshThing(Node node, IDictionary<ushort, ushort>? nodeBoneMap)
|
|
||||||
{
|
|
||||||
// BoneTable (mesh.btidx = 255 means unskinned)
|
|
||||||
// vertexdecl
|
|
||||||
|
|
||||||
var mesh = node.Mesh;
|
|
||||||
|
|
||||||
// TODO: should probably say _what_ mesh
|
|
||||||
// TODO: would be cool to support >1 primitive (esp. given they're effectively what submeshes are modeled as), but blender doesn't really use them, so not going to prio that at all.
|
|
||||||
if (mesh.Primitives.Count != 1)
|
|
||||||
throw new Exception($"Mesh has {mesh.Primitives.Count} primitives, expected 1.");
|
|
||||||
var primitive = mesh.Primitives[0];
|
|
||||||
|
|
||||||
var accessors = primitive.VertexAccessors;
|
|
||||||
|
|
||||||
// var foo = primitive.GetMorphTargetAccessors(0);
|
|
||||||
// var bar = foo["POSITION"];
|
|
||||||
// var baz = bar.AsVector3Array();
|
|
||||||
|
|
||||||
var morphAccessors = Enumerable.Range(0, primitive.MorphTargetsCount)
|
|
||||||
// todo: map by name, probably? or do that later (probably later)
|
|
||||||
.Select(index => primitive.GetMorphTargetAccessors(index));
|
|
||||||
|
|
||||||
// TODO: name
|
|
||||||
var morphChangedVerts = Enumerable.Range(0, primitive.MorphTargetsCount)
|
|
||||||
.Select(_ => new List<int>())
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
var rawAttributes = new[] {
|
|
||||||
VertexAttribute.Position(accessors, morphAccessors),
|
|
||||||
VertexAttribute.BlendWeight(accessors),
|
|
||||||
VertexAttribute.BlendIndex(accessors, nodeBoneMap),
|
|
||||||
VertexAttribute.Normal(accessors, morphAccessors),
|
|
||||||
VertexAttribute.Tangent1(accessors, morphAccessors),
|
|
||||||
VertexAttribute.Color(accessors),
|
|
||||||
VertexAttribute.Uv(accessors),
|
|
||||||
};
|
|
||||||
|
|
||||||
var attributes = new List<VertexAttribute>();
|
|
||||||
var offsets = new byte[] { 0, 0, 0 };
|
|
||||||
foreach (var attribute in rawAttributes)
|
|
||||||
{
|
|
||||||
if (attribute == null) continue;
|
|
||||||
var element = attribute.Element;
|
|
||||||
attributes.Add(attribute.WithOffset(offsets[element.Stream]));
|
|
||||||
offsets[element.Stream] += attribute.Size;
|
|
||||||
}
|
|
||||||
var strides = offsets;
|
|
||||||
|
|
||||||
// TODO: when merging submeshes, i'll need to check that vert els are the same for all of them, as xiv only stores verts at the mesh level and shares them.
|
|
||||||
|
|
||||||
var streams = new List<byte>[3];
|
|
||||||
for (var i = 0; i < 3; i++)
|
|
||||||
streams[i] = new List<byte>();
|
|
||||||
|
|
||||||
// todo: this is a bit lmao but also... probably the most sane option? getting the count that is
|
|
||||||
var vertexCount = primitive.VertexAccessors["POSITION"].Count;
|
|
||||||
for (var vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++)
|
|
||||||
{
|
|
||||||
foreach (var attribute in attributes)
|
|
||||||
{
|
|
||||||
streams[attribute.Element.Stream].AddRange(attribute.Build(vertexIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is a meme but idk maybe it's the best approach? it's not like the attr array is ever long
|
|
||||||
foreach (var (list, morphIndex) in morphChangedVerts.WithIndex())
|
|
||||||
{
|
|
||||||
var hasMorph = attributes.Aggregate(false, (cur, attr) => cur || attr.HasMorph(morphIndex, vertexIndex));
|
|
||||||
// Penumbra.Log.Information($"eh? {vertexIndex} {morphIndex}: {hasMorph}");
|
|
||||||
if (hasMorph)
|
|
||||||
{
|
|
||||||
list.Add(vertexIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// indices
|
|
||||||
// var indexCount = primitive.GetIndexAccessor().Count;
|
|
||||||
// var indices = primitive.GetIndices()
|
|
||||||
// .SelectMany(index => BitConverter.GetBytes((ushort)index))
|
|
||||||
// .ToArray();
|
|
||||||
var indices = primitive.GetIndices().Select(idx => (ushort)idx).ToArray();
|
|
||||||
|
|
||||||
// BLAH
|
|
||||||
// foreach (var (list, morphIndex) in morphChangedVerts.WithIndex())
|
|
||||||
// {
|
|
||||||
// Penumbra.Log.Information($"morph {morphIndex}: {string.Join(",", list)}");
|
|
||||||
// }
|
|
||||||
// TODO BUILD THE MORPH VERTS
|
|
||||||
// (source, target)
|
|
||||||
var morphmappingstuff = new List<MdlStructs.ShapeValueStruct>[morphChangedVerts.Length];
|
|
||||||
foreach (var (list, morphIndex) in morphChangedVerts.WithIndex())
|
|
||||||
{
|
|
||||||
var morphmaplist = morphmappingstuff[morphIndex] = new();
|
|
||||||
foreach (var vertIdx in list)
|
|
||||||
{
|
|
||||||
foreach (var attribute in attributes)
|
|
||||||
{
|
|
||||||
streams[attribute.Element.Stream].AddRange(attribute.BuildMorph(morphIndex, vertIdx));
|
|
||||||
}
|
|
||||||
|
|
||||||
var fuck = indices.WithIndex()
|
|
||||||
.Where(pair => pair.Value == vertIdx)
|
|
||||||
.Select(pair => pair.Index);
|
|
||||||
|
|
||||||
foreach (var something in fuck)
|
|
||||||
{
|
|
||||||
morphmaplist.Add(new MdlStructs.ShapeValueStruct()
|
|
||||||
{
|
|
||||||
BaseIndicesIndex = (ushort)something,
|
|
||||||
ReplacingVertexIndex = (ushort)vertexCount,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
vertexCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: HANDLE THIS BEING MISSING - probably warn or something, it's not the end of the world
|
|
||||||
var morphData = new Dictionary<string, List<MdlStructs.ShapeValueStruct>>();
|
|
||||||
if (morphmappingstuff.Length > 0)
|
|
||||||
{
|
|
||||||
var morphnames = mesh.Extras.GetNode("targetNames").Deserialize<List<string>>();
|
|
||||||
morphData = morphmappingstuff
|
|
||||||
.Zip(morphnames)
|
|
||||||
.ToDictionary(
|
|
||||||
(pair) => pair.Second,
|
|
||||||
(pair) => pair.First
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// one of these per mesh
|
|
||||||
var vertexDeclaration = new MdlStructs.VertexDeclarationStruct()
|
|
||||||
{
|
|
||||||
VertexElements = attributes.Select(attribute => attribute.Element).ToArray(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// mesh
|
|
||||||
// var xivMesh = new MdlStructs.MeshStruct()
|
|
||||||
// {
|
|
||||||
// // TODO: sum across submeshes.
|
|
||||||
// // TODO: would be cool to share verts on submesh boundaries but that's way out of scope for now.
|
|
||||||
// VertexCount = (ushort)vertexCount,
|
|
||||||
// IndexCount = (uint)indexCount,
|
|
||||||
// // TODO: will have to think about how to represent this - materials can be named, so maybe adjust in parent?
|
|
||||||
// MaterialIndex = 0,
|
|
||||||
// // TODO: this will need adjusting by parent
|
|
||||||
// SubMeshIndex = 0,
|
|
||||||
// SubMeshCount = 1,
|
|
||||||
// // TODO: update in parent
|
|
||||||
// BoneTableIndex = 0,
|
|
||||||
// // TODO: this is relative to the lod's index buffer, and is an index, not byte offset
|
|
||||||
// StartIndex = 0,
|
|
||||||
// // TODO: these are relative to the lod vertex buffer. these values are accurate for a 0 offset, but lod will need to adjust
|
|
||||||
// VertexBufferOffset = [0, (uint)streams[0].Count, (uint)(streams[0].Count + streams[1].Count)],
|
|
||||||
// VertexBufferStride = strides,
|
|
||||||
// VertexStreamCount = /* 2 */ (byte)(attributes.Select(attribute => attribute.Element.Stream).Max() + 1),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// submesh
|
|
||||||
// TODO: once we have multiple submeshes, the _first_ should probably set an index offset of 0, and then further ones delta from there - and then they can be blindly adjusted by the parent that's laying out the meshes.
|
|
||||||
var xivSubmesh = new MdlStructs.SubmeshStruct()
|
|
||||||
{
|
|
||||||
IndexOffset = 0,
|
|
||||||
IndexCount = (uint)indices.Length,
|
|
||||||
AttributeIndexMask = 0,
|
|
||||||
// TODO: not sure how i want to handle these ones
|
|
||||||
BoneStartIndex = 0,
|
|
||||||
BoneCount = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
// var vertexBuffer = streams[0].Concat(streams[1]).Concat(streams[2]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
vertexDeclaration,
|
|
||||||
strides,
|
|
||||||
// xivMesh,
|
|
||||||
xivSubmesh,
|
|
||||||
(ushort)vertexCount,
|
|
||||||
streams,
|
|
||||||
(uint)indices.Length,
|
|
||||||
indices,
|
|
||||||
morphData
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(IAction? other)
|
public bool Equals(IAction? other)
|
||||||
{
|
{
|
||||||
if (other is not ImportGltfAction rhs)
|
if (other is not ImportGltfAction rhs)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue