mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Add shape key support
This commit is contained in:
parent
4e8695e7a4
commit
acaa49fec5
2 changed files with 260 additions and 48 deletions
|
|
@ -4,7 +4,9 @@ using SharpGLTF.Schema2;
|
||||||
|
|
||||||
namespace Penumbra.Import.Models.Import;
|
namespace Penumbra.Import.Models.Import;
|
||||||
|
|
||||||
using Writer = Action<int, List<byte>>;
|
using BuildFn = Func<int, byte[]>;
|
||||||
|
using HasMorphFn = Func<int, int, bool>;
|
||||||
|
using BuildMorphFn = Func<int, int, byte[]>;
|
||||||
using Accessors = IReadOnlyDictionary<string, Accessor>;
|
using Accessors = IReadOnlyDictionary<string, Accessor>;
|
||||||
|
|
||||||
public class VertexAttribute
|
public class VertexAttribute
|
||||||
|
|
@ -12,7 +14,11 @@ 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> Write this vertex attribute's value at the specified index to the provided byte array. </summary>
|
/// <summary> Write this vertex attribute's value at the specified index to the provided byte array. </summary>
|
||||||
public readonly Writer Write;
|
public readonly BuildFn Build;
|
||||||
|
|
||||||
|
public readonly HasMorphFn HasMorph;
|
||||||
|
|
||||||
|
public readonly BuildMorphFn BuildMorph;
|
||||||
|
|
||||||
/// <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
|
||||||
|
|
@ -27,13 +33,32 @@ public class VertexAttribute
|
||||||
_ => throw new Exception($"Unhandled vertex type {(MdlFile.VertexType)Element.Type}"),
|
_ => throw new Exception($"Unhandled vertex type {(MdlFile.VertexType)Element.Type}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
public VertexAttribute(MdlStructs.VertexElement element, Writer write)
|
public VertexAttribute(
|
||||||
|
MdlStructs.VertexElement element,
|
||||||
|
BuildFn write,
|
||||||
|
HasMorphFn? hasMorph = null,
|
||||||
|
BuildMorphFn? buildMorph = null
|
||||||
|
)
|
||||||
{
|
{
|
||||||
Element = element;
|
Element = element;
|
||||||
Write = write;
|
Build = write;
|
||||||
|
HasMorph = hasMorph ?? DefaultHasMorph;
|
||||||
|
BuildMorph = buildMorph ?? DefaultBuildMorph;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VertexAttribute Position(Accessors accessors)
|
// todo: this is per-shape at the moment - consider if it should do them all at once (i mean we always want to check all of them, it's mostly a semantics question on who owns the loop)
|
||||||
|
private static bool DefaultHasMorph(int morphIndex, int vertexIndex)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// xiv stores shapes as full vertex replacements, so the default value for a morph attribute is simply it's built state (rather than a delta or w/e)
|
||||||
|
private byte[] DefaultBuildMorph(int morphIndex, int vertexIndex)
|
||||||
|
{
|
||||||
|
return Build(vertexIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VertexAttribute Position(Accessors accessors, IEnumerable<Accessors> morphAccessors)
|
||||||
{
|
{
|
||||||
if (!accessors.TryGetValue("POSITION", out var accessor))
|
if (!accessors.TryGetValue("POSITION", out var accessor))
|
||||||
throw new Exception("Meshes must contain a POSITION attribute.");
|
throw new Exception("Meshes must contain a POSITION attribute.");
|
||||||
|
|
@ -47,9 +72,32 @@ public class VertexAttribute
|
||||||
|
|
||||||
var values = accessor.AsVector3Array();
|
var values = accessor.AsVector3Array();
|
||||||
|
|
||||||
|
var foo = morphAccessors
|
||||||
|
.Select(ma => ma.GetValueOrDefault("POSITION")?.AsVector3Array())
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
return new VertexAttribute(
|
return new VertexAttribute(
|
||||||
element,
|
element,
|
||||||
(index, bytes) => WriteSingle3(values[index], bytes)
|
index => BuildSingle3(values[index]),
|
||||||
|
// TODO: at the moment this is only defined for position - is it worth setting one up for normal, too?
|
||||||
|
(morphIndex, vertexIndex) =>
|
||||||
|
{
|
||||||
|
var deltas = foo[morphIndex];
|
||||||
|
if (deltas == null) return false;
|
||||||
|
var delta = deltas[vertexIndex];
|
||||||
|
return delta != Vector3.Zero;
|
||||||
|
},
|
||||||
|
// TODO: this will _need_ to be defined for any values that appear in morphs, i.e. geom and maybe mats
|
||||||
|
(morphIndex, vertexIndex) =>
|
||||||
|
{
|
||||||
|
var value = values[vertexIndex];
|
||||||
|
|
||||||
|
var delta = foo[morphIndex]?[vertexIndex];
|
||||||
|
if (delta != null)
|
||||||
|
value += delta.Value;
|
||||||
|
|
||||||
|
return BuildSingle3(value);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,8 +121,7 @@ public class VertexAttribute
|
||||||
return new VertexAttribute(
|
return new VertexAttribute(
|
||||||
element,
|
element,
|
||||||
// TODO: TEMP TESTING PINNED TO BONE 0 UNTIL I SET UP BONE MAPPINGS
|
// TODO: TEMP TESTING PINNED TO BONE 0 UNTIL I SET UP BONE MAPPINGS
|
||||||
// (index, bytes) => WriteByteFloat4(values[index], bytes)
|
index => BuildByteFloat4(Vector4.UnitX)
|
||||||
(index, bytes) => WriteByteFloat4(Vector4.UnitX, bytes)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,8 +146,7 @@ public class VertexAttribute
|
||||||
return new VertexAttribute(
|
return new VertexAttribute(
|
||||||
element,
|
element,
|
||||||
// TODO: TEMP TESTING PINNED TO BONE 0 UNTIL I SET UP BONE MAPPINGS
|
// TODO: TEMP TESTING PINNED TO BONE 0 UNTIL I SET UP BONE MAPPINGS
|
||||||
// (index, bytes) => WriteUInt(values[index], bytes)
|
index => BuildUInt(Vector4.Zero)
|
||||||
(index, bytes) => WriteUInt(Vector4.Zero, bytes)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,7 +166,7 @@ public class VertexAttribute
|
||||||
|
|
||||||
return new VertexAttribute(
|
return new VertexAttribute(
|
||||||
element,
|
element,
|
||||||
(index, bytes) => WriteHalf4(new Vector4(values[index], 0), bytes)
|
index => BuildHalf4(new Vector4(values[index], 0))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,18 +187,18 @@ public class VertexAttribute
|
||||||
if (!accessors.TryGetValue("TEXCOORD_1", out var accessor2))
|
if (!accessors.TryGetValue("TEXCOORD_1", out var accessor2))
|
||||||
return new VertexAttribute(
|
return new VertexAttribute(
|
||||||
element with { Type = (byte)MdlFile.VertexType.Half2 },
|
element with { Type = (byte)MdlFile.VertexType.Half2 },
|
||||||
(index, bytes) => WriteHalf2(values1[index], bytes)
|
index => BuildHalf2(values1[index])
|
||||||
);
|
);
|
||||||
|
|
||||||
var values2 = accessor2.AsVector2Array();
|
var values2 = accessor2.AsVector2Array();
|
||||||
|
|
||||||
return new VertexAttribute(
|
return new VertexAttribute(
|
||||||
element with { Type = (byte)MdlFile.VertexType.Half4 },
|
element with { Type = (byte)MdlFile.VertexType.Half4 },
|
||||||
(index, bytes) =>
|
index =>
|
||||||
{
|
{
|
||||||
var value1 = values1[index];
|
var value1 = values1[index];
|
||||||
var value2 = values2[index];
|
var value2 = values2[index];
|
||||||
WriteHalf4(new Vector4(value1.X, value1.Y, value2.X, value2.Y), bytes);
|
return BuildHalf4(new Vector4(value1.X, value1.Y, value2.X, value2.Y));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +219,7 @@ public class VertexAttribute
|
||||||
|
|
||||||
return new VertexAttribute(
|
return new VertexAttribute(
|
||||||
element,
|
element,
|
||||||
(index, bytes) => WriteByteFloat4(values[index], bytes)
|
index => BuildByteFloat4(values[index])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,44 +239,54 @@ public class VertexAttribute
|
||||||
|
|
||||||
return new VertexAttribute(
|
return new VertexAttribute(
|
||||||
element,
|
element,
|
||||||
(index, bytes) => WriteByteFloat4(values[index], bytes)
|
index => BuildByteFloat4(values[index])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteSingle3(Vector3 input, List<byte> bytes)
|
private static byte[] BuildSingle3(Vector3 input)
|
||||||
{
|
{
|
||||||
bytes.AddRange(BitConverter.GetBytes(input.X));
|
return [
|
||||||
bytes.AddRange(BitConverter.GetBytes(input.Y));
|
..BitConverter.GetBytes(input.X),
|
||||||
bytes.AddRange(BitConverter.GetBytes(input.Z));
|
..BitConverter.GetBytes(input.Y),
|
||||||
|
..BitConverter.GetBytes(input.Z),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteUInt(Vector4 input, List<byte> bytes)
|
private static byte[] BuildUInt(Vector4 input)
|
||||||
{
|
{
|
||||||
bytes.Add((byte)input.X);
|
return [
|
||||||
bytes.Add((byte)input.Y);
|
(byte)input.X,
|
||||||
bytes.Add((byte)input.Z);
|
(byte)input.Y,
|
||||||
bytes.Add((byte)input.W);
|
(byte)input.Z,
|
||||||
|
(byte)input.W,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteByteFloat4(Vector4 input, List<byte> bytes)
|
private static byte[] BuildByteFloat4(Vector4 input)
|
||||||
{
|
{
|
||||||
bytes.Add((byte)Math.Round(input.X * 255f));
|
return [
|
||||||
bytes.Add((byte)Math.Round(input.Y * 255f));
|
(byte)Math.Round(input.X * 255f),
|
||||||
bytes.Add((byte)Math.Round(input.Z * 255f));
|
(byte)Math.Round(input.Y * 255f),
|
||||||
bytes.Add((byte)Math.Round(input.W * 255f));
|
(byte)Math.Round(input.Z * 255f),
|
||||||
|
(byte)Math.Round(input.W * 255f),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteHalf2(Vector2 input, List<byte> bytes)
|
private static byte[] BuildHalf2(Vector2 input)
|
||||||
{
|
{
|
||||||
bytes.AddRange(BitConverter.GetBytes((Half)input.X));
|
return [
|
||||||
bytes.AddRange(BitConverter.GetBytes((Half)input.Y));
|
..BitConverter.GetBytes((Half)input.X),
|
||||||
|
..BitConverter.GetBytes((Half)input.Y),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteHalf4(Vector4 input, List<byte> bytes)
|
private static byte[] BuildHalf4(Vector4 input)
|
||||||
{
|
{
|
||||||
bytes.AddRange(BitConverter.GetBytes((Half)input.X));
|
return [
|
||||||
bytes.AddRange(BitConverter.GetBytes((Half)input.Y));
|
..BitConverter.GetBytes((Half)input.X),
|
||||||
bytes.AddRange(BitConverter.GetBytes((Half)input.Z));
|
..BitConverter.GetBytes((Half)input.Y),
|
||||||
bytes.AddRange(BitConverter.GetBytes((Half)input.W));
|
..BitConverter.GetBytes((Half)input.Z),
|
||||||
|
..BitConverter.GetBytes((Half)input.W),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,9 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
var submeshes = new List<MdlStructs.SubmeshStruct>();
|
var submeshes = new List<MdlStructs.SubmeshStruct>();
|
||||||
var vertexBuffer = new List<byte>();
|
var vertexBuffer = new List<byte>();
|
||||||
var indices = new List<byte>();
|
var indices = new List<byte>();
|
||||||
|
|
||||||
|
var shapeData = new Dictionary<string, List<MdlStructs.ShapeMeshStruct>>();
|
||||||
|
var shapeValues = new List<MdlStructs.ShapeValueStruct>();
|
||||||
|
|
||||||
foreach (var submeshnodes in nodes)
|
foreach (var submeshnodes in nodes)
|
||||||
{
|
{
|
||||||
|
|
@ -214,6 +217,7 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
var subOffset = submeshes.Count;
|
var subOffset = submeshes.Count;
|
||||||
var vertOffset = vertexBuffer.Count;
|
var vertOffset = vertexBuffer.Count;
|
||||||
var idxOffset = indices.Count;
|
var idxOffset = indices.Count;
|
||||||
|
var shapeValueOffset = shapeValues.Count;
|
||||||
|
|
||||||
var (
|
var (
|
||||||
vertexDeclaration,
|
vertexDeclaration,
|
||||||
|
|
@ -221,16 +225,18 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
xivMesh,
|
xivMesh,
|
||||||
xivSubmeshes,
|
xivSubmeshes,
|
||||||
meshVertexBuffer,
|
meshVertexBuffer,
|
||||||
meshIndices
|
meshIndices,
|
||||||
|
meshShapeData // fasdfasd
|
||||||
) = MeshThing(submeshnodes);
|
) = MeshThing(submeshnodes);
|
||||||
|
|
||||||
vertexDeclarations.Add(vertexDeclaration);
|
vertexDeclarations.Add(vertexDeclaration);
|
||||||
boneTables.Add(boneTable);
|
boneTables.Add(boneTable);
|
||||||
|
var meshStartIndex = (uint)(xivMesh.StartIndex + idxOffset / sizeof(ushort));
|
||||||
meshes.Add(xivMesh with {
|
meshes.Add(xivMesh with {
|
||||||
SubMeshIndex = (ushort)(xivMesh.SubMeshIndex + subOffset),
|
SubMeshIndex = (ushort)(xivMesh.SubMeshIndex + subOffset),
|
||||||
// TODO: should probably define a type for index type hey.
|
// TODO: should probably define a type for index type hey.
|
||||||
BoneTableIndex = (ushort)(xivMesh.BoneTableIndex + boneTableOffset),
|
BoneTableIndex = (ushort)(xivMesh.BoneTableIndex + boneTableOffset),
|
||||||
StartIndex = (uint)(xivMesh.StartIndex + idxOffset / sizeof(ushort)),
|
StartIndex = meshStartIndex,
|
||||||
VertexBufferOffset = xivMesh.VertexBufferOffset
|
VertexBufferOffset = xivMesh.VertexBufferOffset
|
||||||
.Select(offset => (uint)(offset + vertOffset))
|
.Select(offset => (uint)(offset + vertOffset))
|
||||||
.ToArray(),
|
.ToArray(),
|
||||||
|
|
@ -243,6 +249,39 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
});
|
});
|
||||||
vertexBuffer.AddRange(meshVertexBuffer);
|
vertexBuffer.AddRange(meshVertexBuffer);
|
||||||
indices.AddRange(meshIndices.SelectMany(index => BitConverter.GetBytes((ushort)index)));
|
indices.AddRange(meshIndices.SelectMany(index => BitConverter.GetBytes((ushort)index)));
|
||||||
|
foreach (var (key, (shapeMesh, meshShapeValues)) in meshShapeData)
|
||||||
|
{
|
||||||
|
List<MdlStructs.ShapeMeshStruct> keyshapedata;
|
||||||
|
if (!shapeData.TryGetValue(key, out keyshapedata))
|
||||||
|
{
|
||||||
|
keyshapedata = new();
|
||||||
|
shapeData.Add(key, keyshapedata);
|
||||||
|
}
|
||||||
|
|
||||||
|
keyshapedata.Add(shapeMesh with {
|
||||||
|
MeshIndexOffset = meshStartIndex,
|
||||||
|
ShapeValueOffset = (uint)shapeValueOffset,
|
||||||
|
});
|
||||||
|
|
||||||
|
shapeValues.AddRange(meshShapeValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var shapes = new List<MdlFile.Shape>();
|
||||||
|
var shapeMeshes = new List<MdlStructs.ShapeMeshStruct>();
|
||||||
|
|
||||||
|
foreach (var (name, sms) in shapeData)
|
||||||
|
{
|
||||||
|
var smOff = shapeMeshes.Count;
|
||||||
|
|
||||||
|
shapeMeshes.AddRange(sms);
|
||||||
|
shapes.Add(new MdlFile.Shape()
|
||||||
|
{
|
||||||
|
ShapeName = name,
|
||||||
|
// TODO: THESE IS PER LOD
|
||||||
|
ShapeMeshStartIndex = [(ushort)smOff, 0, 0],
|
||||||
|
ShapeMeshCount = [(ushort)sms.Count, 0, 0],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var mdl = new MdlFile()
|
var mdl = new MdlFile()
|
||||||
|
|
@ -292,6 +331,10 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
// game clearly doesn't rely on this, but the "correct" values are a listing of the bones used by each submesh
|
// game clearly doesn't rely on this, but the "correct" values are a listing of the bones used by each submesh
|
||||||
SubMeshBoneMap = [0],
|
SubMeshBoneMap = [0],
|
||||||
|
|
||||||
|
Shapes = shapes.ToArray(),
|
||||||
|
ShapeMeshes = shapeMeshes.ToArray(),
|
||||||
|
ShapeValues = shapeValues.ToArray(),
|
||||||
|
|
||||||
Lods = [new MdlStructs.LodStruct()
|
Lods = [new MdlStructs.LodStruct()
|
||||||
{
|
{
|
||||||
MeshIndex = 0,
|
MeshIndex = 0,
|
||||||
|
|
@ -323,7 +366,8 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
MdlStructs.MeshStruct,
|
MdlStructs.MeshStruct,
|
||||||
IEnumerable<MdlStructs.SubmeshStruct>,
|
IEnumerable<MdlStructs.SubmeshStruct>,
|
||||||
IEnumerable<byte>,
|
IEnumerable<byte>,
|
||||||
IEnumerable<ushort>
|
IEnumerable<ushort>,
|
||||||
|
IDictionary<string, (MdlStructs.ShapeMeshStruct, List<MdlStructs.ShapeValueStruct>)>
|
||||||
) MeshThing(IEnumerable<Node> nodes)
|
) MeshThing(IEnumerable<Node> nodes)
|
||||||
{
|
{
|
||||||
var vertexDeclaration = new MdlStructs.VertexDeclarationStruct() { VertexElements = Array.Empty<MdlStructs.VertexElement>()};
|
var vertexDeclaration = new MdlStructs.VertexDeclarationStruct() { VertexElements = Array.Empty<MdlStructs.VertexElement>()};
|
||||||
|
|
@ -336,6 +380,7 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
var indices = new List<ushort>();
|
var indices = new List<ushort>();
|
||||||
var strides = new byte[] {0, 0, 0};
|
var strides = new byte[] {0, 0, 0};
|
||||||
var submeshes = new List<MdlStructs.SubmeshStruct>();
|
var submeshes = new List<MdlStructs.SubmeshStruct>();
|
||||||
|
var morphData = new Dictionary<string, List<MdlStructs.ShapeValueStruct>>();
|
||||||
|
|
||||||
// TODO: check that attrs/elems/strides match - we should be generating per-mesh stuff for sanity's sake, but we need to make sure they match if there's >1 node mesh in a mesh.
|
// TODO: check that attrs/elems/strides match - we should be generating per-mesh stuff for sanity's sake, but we need to make sure they match if there's >1 node mesh in a mesh.
|
||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
|
|
@ -343,7 +388,7 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
var vertOff = vertexCount;
|
var vertOff = vertexCount;
|
||||||
var idxOff = indexCount;
|
var idxOff = indexCount;
|
||||||
|
|
||||||
var (vertDecl, newStrides, submesh, vertCount, vertStreams, idxCount, idxs) = NodeMeshThing(node);
|
var (vertDecl, newStrides, submesh, vertCount, vertStreams, idxCount, idxs, subMorphData) = NodeMeshThing(node);
|
||||||
vertexDeclaration = vertDecl; // TODO: CHECK EQUAL AFTER FIRST
|
vertexDeclaration = vertDecl; // TODO: CHECK EQUAL AFTER FIRST
|
||||||
strides = newStrides; // ALSO CHECK EQUAL
|
strides = newStrides; // ALSO CHECK EQUAL
|
||||||
vertexCount += vertCount;
|
vertexCount += vertCount;
|
||||||
|
|
@ -356,6 +401,25 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
IndexOffset = submesh.IndexOffset + idxOff
|
IndexOffset = submesh.IndexOffset + idxOff
|
||||||
// TODO: bone stuff probably
|
// TODO: bone stuff probably
|
||||||
});
|
});
|
||||||
|
// TODO: HANDLE MORPHS, NEED TO ADJUST EVERY VALUE'S INDEX OFFSETS
|
||||||
|
foreach (var (key, shapeValues) in subMorphData)
|
||||||
|
{
|
||||||
|
List<MdlStructs.ShapeValueStruct> valueList;
|
||||||
|
if (!morphData.TryGetValue(key, out valueList))
|
||||||
|
{
|
||||||
|
valueList = new();
|
||||||
|
morphData.Add(key, valueList);
|
||||||
|
}
|
||||||
|
valueList.AddRange(
|
||||||
|
shapeValues
|
||||||
|
.Select(value => value with {
|
||||||
|
// but this is actually an index index
|
||||||
|
BaseIndicesIndex = (ushort)(value.BaseIndicesIndex + idxOff),
|
||||||
|
// this is a vert idx
|
||||||
|
ReplacingVertexIndex = (ushort)(value.ReplacingVertexIndex + vertOff),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// one of these per skinned mesh.
|
// one of these per skinned mesh.
|
||||||
|
|
@ -390,13 +454,30 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
VertexStreamCount = (byte)(vertexDeclaration.VertexElements.Select(element => element.Stream).Max() + 1)
|
VertexStreamCount = (byte)(vertexDeclaration.VertexElements.Select(element => element.Stream).Max() + 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: can probably get away with flattening the values and blindly setting offsets in parent - mesh matters above, but the values are already Dealt With at this point
|
||||||
|
var shapeData = morphData.ToDictionary(
|
||||||
|
(pair) => pair.Key,
|
||||||
|
pair => (
|
||||||
|
new MdlStructs.ShapeMeshStruct()
|
||||||
|
{
|
||||||
|
// TODO: this needs to be adjusted by the parent
|
||||||
|
MeshIndexOffset = 0,
|
||||||
|
ShapeValueCount = (uint)pair.Value.Count,
|
||||||
|
// TODO: Also update by parent
|
||||||
|
ShapeValueOffset = 0,
|
||||||
|
},
|
||||||
|
pair.Value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
vertexDeclaration,
|
vertexDeclaration,
|
||||||
boneTable,
|
boneTable,
|
||||||
xivMesh,
|
xivMesh,
|
||||||
submeshes,
|
submeshes,
|
||||||
streams[0].Concat(streams[1]).Concat(streams[2]),
|
streams[0].Concat(streams[1]).Concat(streams[2]),
|
||||||
indices
|
indices,
|
||||||
|
shapeData
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -408,7 +489,8 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
ushort,
|
ushort,
|
||||||
IEnumerable<byte>[],
|
IEnumerable<byte>[],
|
||||||
uint,
|
uint,
|
||||||
IEnumerable<ushort>
|
IEnumerable<ushort>,
|
||||||
|
IDictionary<string, List<MdlStructs.ShapeValueStruct>>
|
||||||
) NodeMeshThing(Node node)
|
) NodeMeshThing(Node node)
|
||||||
{
|
{
|
||||||
// BoneTable (mesh.btidx = 255 means unskinned)
|
// BoneTable (mesh.btidx = 255 means unskinned)
|
||||||
|
|
@ -423,9 +505,22 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
var primitive = mesh.Primitives[0];
|
var primitive = mesh.Primitives[0];
|
||||||
|
|
||||||
var accessors = primitive.VertexAccessors;
|
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[] {
|
var rawAttributes = new[] {
|
||||||
VertexAttribute.Position(accessors),
|
VertexAttribute.Position(accessors, morphAccessors),
|
||||||
VertexAttribute.BlendWeight(accessors),
|
VertexAttribute.BlendWeight(accessors),
|
||||||
VertexAttribute.BlendIndex(accessors),
|
VertexAttribute.BlendIndex(accessors),
|
||||||
VertexAttribute.Normal(accessors),
|
VertexAttribute.Normal(accessors),
|
||||||
|
|
@ -440,9 +535,12 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
{
|
{
|
||||||
if (attribute == null) continue;
|
if (attribute == null) continue;
|
||||||
var element = attribute.Element;
|
var element = attribute.Element;
|
||||||
|
// recreating this here really sucks - add a "withstream" or something.
|
||||||
attributes.Add(new VertexAttribute(
|
attributes.Add(new VertexAttribute(
|
||||||
element with {Offset = offsets[element.Stream]},
|
element with {Offset = offsets[element.Stream]},
|
||||||
attribute.Write
|
attribute.Build,
|
||||||
|
attribute.HasMorph,
|
||||||
|
attribute.BuildMorph
|
||||||
));
|
));
|
||||||
offsets[element.Stream] += attribute.Size;
|
offsets[element.Stream] += attribute.Size;
|
||||||
}
|
}
|
||||||
|
|
@ -460,7 +558,18 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
{
|
{
|
||||||
foreach (var attribute in attributes)
|
foreach (var attribute in attributes)
|
||||||
{
|
{
|
||||||
attribute.Write(vertexIndex, streams[attribute.Element.Stream]);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -471,6 +580,52 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
// .ToArray();
|
// .ToArray();
|
||||||
var indices = primitive.GetIndices().Select(idx => (ushort)idx).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
|
// one of these per mesh
|
||||||
var vertexDeclaration = new MdlStructs.VertexDeclarationStruct()
|
var vertexDeclaration = new MdlStructs.VertexDeclarationStruct()
|
||||||
{
|
{
|
||||||
|
|
@ -521,7 +676,8 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
||||||
(ushort)vertexCount,
|
(ushort)vertexCount,
|
||||||
streams,
|
streams,
|
||||||
(uint)indices.Length,
|
(uint)indices.Length,
|
||||||
indices
|
indices,
|
||||||
|
morphData
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue