public record class MorphTargetDataOne blend shape: what a named expression does to the vertices it touches.
Remarks
Doc 33 § D4, which is where the shape of this was decided and why it is sparse: a brow-raise moves a few hundred vertices of a forty-thousand-vertex face, and the dense form would store thirty-nine thousand seven hundred zero deltas per target. Twenty targets stored densely is more bytes than the mesh.
An entry is a vertex index and two deltas, and the deltas are quantised against a range this target carries. Sixteen-bit signed normals against PositionScale/NormalScale rather than floats: a delta's magnitude is bounded by the shape's own extent, so the quantum is a ten-thousandth of the largest movement in this target — which for a face is a few micrometres — and the entry is sixteen bytes instead of twenty-eight.
⚠ A per-target scale, and not a per-mesh one. A model with a jaw-open worth ten centimetres and an eyelid worth two millimetres shares one range only by giving the eyelid fifty times the quantisation error it needs. The scale costs four bytes a target; sharing one costs precision on every small shape, which is every shape that matters on a face.
⚠ The normal delta is stored as a delta and is not renormalised anywhere. A morphed normal is n + Σ wᵢ Δnᵢ, whose length is not one and is not meant to be — see MorphKernel for why nothing in the pre-pass normalises it, and why that is the safe choice rather than the lazy one.
Sorted by index, ascending. The scatter writes in index order, so a sorted entry list is a coalesced write; and two targets sorted the same way are two runs a merge could walk in step, which is what the union optimisation named in MorphKernel's remarks would need.
Fields and properties (10)
public const int QuantumThe largest magnitude a quantised component can carry.
public string NameWhat the shape is called — browRaise, jawOpen, an ARKit name.
public int[] IndicesWhich vertices this target moves, ascending.
public float PositionScaleThe largest position-delta component in this target, in the mesh's units.
public short[] PositionsThree quantised components per entry, against PositionScale.
public float NormalScaleThe largest normal-delta component in this target.
public short[] NormalsThree quantised components per entry against NormalScale, or empty.
public int CountHow many vertices this target moves.
public bool HasNormalsWhether it carries normal deltas as well as position deltas.
public long SizeInBytesWhat this target costs, resident, not counting its name.
Methods (4)
public Vector3 PositionDelta(int entry)The position delta of one entry, dequantised.
public Vector3 NormalDelta(int entry)The normal delta of one entry, dequantised. Zero where there are none.
public static MorphTargetData Encode(string name, ReadOnlySpan<int> indices, ReadOnlySpan<Vector3> positions, ReadOnlySpan<Vector3> normals)Builds a target from deltas that are already sparse.
public static MorphTargetData Sparsify(string name, ReadOnlySpan<Vector3> positions, ReadOnlySpan<Vector3> normals, float threshold)Builds a target from one delta per vertex, dropping the ones that do not move.
Used by (18)
- CompositorImageTestsVixen.Graphics.Golden.Tests
- MeshDataVixen.Rendering
- ModelDataTestsVixen.Rendering.Tests
- MorphIndexVixen.Rendering
- MorphIndexTestsVixen.Rendering.Tests
- MorphKernelVixen.Rendering
- MorphRenderFeatureVixen.Rendering
- MorphRenderFeatureTestsVixen.Rendering.Tests
- MorphScatterDeviceTestsVixen.Graphics.Golden.Tests
- MorphTargetTestsVixen.Rendering.Tests
- MorphedClusterTestsVixen.Rendering.Tests
- ShapesVixen.Rendering
- TypeRegistrationVixen.Rendering
- VirtualGeometryGoldenTestsVixen.Graphics.Golden.Tests
- Vixen_Rendering_MeshDataSerializerVixen.Rendering
- Vixen_Rendering_MorphTargetDataSerializerVixen.Rendering
- ModelReaderVixen.Editor.Assets
- ModelReaderTestsVixen.Editor.Assets.Tests