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;
|
||||
|
||||
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>;
|
||||
|
||||
public class VertexAttribute
|
||||
|
|
@ -12,7 +14,11 @@ public class VertexAttribute
|
|||
/// <summary> XIV vertex element metadata structure. </summary>
|
||||
public readonly MdlStructs.VertexElement Element;
|
||||
/// <summary> Write this vertex attribute's value at the specified index to the provided byte array. </summary>
|
||||
public readonly Writer Write;
|
||||
public readonly BuildFn Build;
|
||||
|
||||
public readonly HasMorphFn HasMorph;
|
||||
|
||||
public readonly BuildMorphFn BuildMorph;
|
||||
|
||||
/// <summary> Size in bytes of a single vertex's attribute value. </summary>
|
||||
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}"),
|
||||
};
|
||||
|
||||
public VertexAttribute(MdlStructs.VertexElement element, Writer write)
|
||||
public VertexAttribute(
|
||||
MdlStructs.VertexElement element,
|
||||
BuildFn write,
|
||||
HasMorphFn? hasMorph = null,
|
||||
BuildMorphFn? buildMorph = null
|
||||
)
|
||||
{
|
||||
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))
|
||||
throw new Exception("Meshes must contain a POSITION attribute.");
|
||||
|
|
@ -47,9 +72,32 @@ public class VertexAttribute
|
|||
|
||||
var values = accessor.AsVector3Array();
|
||||
|
||||
var foo = morphAccessors
|
||||
.Select(ma => ma.GetValueOrDefault("POSITION")?.AsVector3Array())
|
||||
.ToArray();
|
||||
|
||||
return new VertexAttribute(
|
||||
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(
|
||||
element,
|
||||
// TODO: TEMP TESTING PINNED TO BONE 0 UNTIL I SET UP BONE MAPPINGS
|
||||
// (index, bytes) => WriteByteFloat4(values[index], bytes)
|
||||
(index, bytes) => WriteByteFloat4(Vector4.UnitX, bytes)
|
||||
index => BuildByteFloat4(Vector4.UnitX)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -99,8 +146,7 @@ public class VertexAttribute
|
|||
return new VertexAttribute(
|
||||
element,
|
||||
// TODO: TEMP TESTING PINNED TO BONE 0 UNTIL I SET UP BONE MAPPINGS
|
||||
// (index, bytes) => WriteUInt(values[index], bytes)
|
||||
(index, bytes) => WriteUInt(Vector4.Zero, bytes)
|
||||
index => BuildUInt(Vector4.Zero)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +166,7 @@ public class VertexAttribute
|
|||
|
||||
return new VertexAttribute(
|
||||
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))
|
||||
return new VertexAttribute(
|
||||
element with { Type = (byte)MdlFile.VertexType.Half2 },
|
||||
(index, bytes) => WriteHalf2(values1[index], bytes)
|
||||
index => BuildHalf2(values1[index])
|
||||
);
|
||||
|
||||
var values2 = accessor2.AsVector2Array();
|
||||
|
||||
return new VertexAttribute(
|
||||
element with { Type = (byte)MdlFile.VertexType.Half4 },
|
||||
(index, bytes) =>
|
||||
index =>
|
||||
{
|
||||
var value1 = values1[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(
|
||||
element,
|
||||
(index, bytes) => WriteByteFloat4(values[index], bytes)
|
||||
index => BuildByteFloat4(values[index])
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -193,44 +239,54 @@ public class VertexAttribute
|
|||
|
||||
return new VertexAttribute(
|
||||
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));
|
||||
bytes.AddRange(BitConverter.GetBytes(input.Y));
|
||||
bytes.AddRange(BitConverter.GetBytes(input.Z));
|
||||
return [
|
||||
..BitConverter.GetBytes(input.X),
|
||||
..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);
|
||||
bytes.Add((byte)input.Y);
|
||||
bytes.Add((byte)input.Z);
|
||||
bytes.Add((byte)input.W);
|
||||
return [
|
||||
(byte)input.X,
|
||||
(byte)input.Y,
|
||||
(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));
|
||||
bytes.Add((byte)Math.Round(input.Y * 255f));
|
||||
bytes.Add((byte)Math.Round(input.Z * 255f));
|
||||
bytes.Add((byte)Math.Round(input.W * 255f));
|
||||
return [
|
||||
(byte)Math.Round(input.X * 255f),
|
||||
(byte)Math.Round(input.Y * 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));
|
||||
bytes.AddRange(BitConverter.GetBytes((Half)input.Y));
|
||||
return [
|
||||
..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));
|
||||
bytes.AddRange(BitConverter.GetBytes((Half)input.Y));
|
||||
bytes.AddRange(BitConverter.GetBytes((Half)input.Z));
|
||||
bytes.AddRange(BitConverter.GetBytes((Half)input.W));
|
||||
return [
|
||||
..BitConverter.GetBytes((Half)input.X),
|
||||
..BitConverter.GetBytes((Half)input.Y),
|
||||
..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 vertexBuffer = 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)
|
||||
{
|
||||
|
|
@ -214,6 +217,7 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
|||
var subOffset = submeshes.Count;
|
||||
var vertOffset = vertexBuffer.Count;
|
||||
var idxOffset = indices.Count;
|
||||
var shapeValueOffset = shapeValues.Count;
|
||||
|
||||
var (
|
||||
vertexDeclaration,
|
||||
|
|
@ -221,16 +225,18 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
|||
xivMesh,
|
||||
xivSubmeshes,
|
||||
meshVertexBuffer,
|
||||
meshIndices
|
||||
meshIndices,
|
||||
meshShapeData // fasdfasd
|
||||
) = MeshThing(submeshnodes);
|
||||
|
||||
vertexDeclarations.Add(vertexDeclaration);
|
||||
boneTables.Add(boneTable);
|
||||
var meshStartIndex = (uint)(xivMesh.StartIndex + idxOffset / sizeof(ushort));
|
||||
meshes.Add(xivMesh with {
|
||||
SubMeshIndex = (ushort)(xivMesh.SubMeshIndex + subOffset),
|
||||
// TODO: should probably define a type for index type hey.
|
||||
BoneTableIndex = (ushort)(xivMesh.BoneTableIndex + boneTableOffset),
|
||||
StartIndex = (uint)(xivMesh.StartIndex + idxOffset / sizeof(ushort)),
|
||||
StartIndex = meshStartIndex,
|
||||
VertexBufferOffset = xivMesh.VertexBufferOffset
|
||||
.Select(offset => (uint)(offset + vertOffset))
|
||||
.ToArray(),
|
||||
|
|
@ -243,6 +249,39 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
|||
});
|
||||
vertexBuffer.AddRange(meshVertexBuffer);
|
||||
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()
|
||||
|
|
@ -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
|
||||
SubMeshBoneMap = [0],
|
||||
|
||||
Shapes = shapes.ToArray(),
|
||||
ShapeMeshes = shapeMeshes.ToArray(),
|
||||
ShapeValues = shapeValues.ToArray(),
|
||||
|
||||
Lods = [new MdlStructs.LodStruct()
|
||||
{
|
||||
MeshIndex = 0,
|
||||
|
|
@ -323,7 +366,8 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
|||
MdlStructs.MeshStruct,
|
||||
IEnumerable<MdlStructs.SubmeshStruct>,
|
||||
IEnumerable<byte>,
|
||||
IEnumerable<ushort>
|
||||
IEnumerable<ushort>,
|
||||
IDictionary<string, (MdlStructs.ShapeMeshStruct, List<MdlStructs.ShapeValueStruct>)>
|
||||
) MeshThing(IEnumerable<Node> nodes)
|
||||
{
|
||||
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 strides = new byte[] {0, 0, 0};
|
||||
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.
|
||||
foreach (var node in nodes)
|
||||
|
|
@ -343,7 +388,7 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
|||
var vertOff = vertexCount;
|
||||
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
|
||||
strides = newStrides; // ALSO CHECK EQUAL
|
||||
vertexCount += vertCount;
|
||||
|
|
@ -356,6 +401,25 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
|||
IndexOffset = submesh.IndexOffset + idxOff
|
||||
// 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.
|
||||
|
|
@ -390,13 +454,30 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
|||
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 (
|
||||
vertexDeclaration,
|
||||
boneTable,
|
||||
xivMesh,
|
||||
submeshes,
|
||||
streams[0].Concat(streams[1]).Concat(streams[2]),
|
||||
indices
|
||||
indices,
|
||||
shapeData
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -408,7 +489,8 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
|||
ushort,
|
||||
IEnumerable<byte>[],
|
||||
uint,
|
||||
IEnumerable<ushort>
|
||||
IEnumerable<ushort>,
|
||||
IDictionary<string, List<MdlStructs.ShapeValueStruct>>
|
||||
) NodeMeshThing(Node node)
|
||||
{
|
||||
// BoneTable (mesh.btidx = 255 means unskinned)
|
||||
|
|
@ -423,9 +505,22 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
|||
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),
|
||||
VertexAttribute.Position(accessors, morphAccessors),
|
||||
VertexAttribute.BlendWeight(accessors),
|
||||
VertexAttribute.BlendIndex(accessors),
|
||||
VertexAttribute.Normal(accessors),
|
||||
|
|
@ -440,9 +535,12 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
|||
{
|
||||
if (attribute == null) continue;
|
||||
var element = attribute.Element;
|
||||
// recreating this here really sucks - add a "withstream" or something.
|
||||
attributes.Add(new VertexAttribute(
|
||||
element with {Offset = offsets[element.Stream]},
|
||||
attribute.Write
|
||||
attribute.Build,
|
||||
attribute.HasMorph,
|
||||
attribute.BuildMorph
|
||||
));
|
||||
offsets[element.Stream] += attribute.Size;
|
||||
}
|
||||
|
|
@ -460,7 +558,18 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
|||
{
|
||||
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();
|
||||
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()
|
||||
{
|
||||
|
|
@ -521,7 +676,8 @@ public sealed partial class ModelManager : SingleTaskQueue, IDisposable
|
|||
(ushort)vertexCount,
|
||||
streams,
|
||||
(uint)indices.Length,
|
||||
indices
|
||||
indices,
|
||||
morphData
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue