Quellcode durchsuchen

Improving morph targets build API

Vicente Penades vor 6 Jahren
Ursprung
Commit
bb5b461bcb

+ 7 - 0
src/SharpGLTF.Core/Schema2/gltf.Mesh.cs

@@ -96,6 +96,13 @@ namespace SharpGLTF.Schema2
             {
                 p.Validate(result);
             }
+
+            var morphTargetsCount = this.Primitives
+                .Select(item => item.MorphTargetsCount)
+                .Distinct();
+
+            if (morphTargetsCount.Count() != 1) result.AddSemanticError(this, Validation.SemanticErrors.MESH_PRIMITIVES_UNEQUAL_TARGETS_COUNT);
+            if (_weights.Count != 0 && morphTargetsCount.First() != _weights.Count) result.AddSemanticError(this, Validation.SemanticErrors.MESH_INVALID_WEIGHTS_COUNT, _weights.Count, morphTargetsCount.First());
         }
 
         internal void ValidateSkinning(Validation.ValidationContext result, int jointsCount)

+ 15 - 2
src/SharpGLTF.Core/Schema2/gltf.Serialization.Read.cs

@@ -45,10 +45,14 @@ namespace SharpGLTF.Schema2
         /// Gets or sets the <see cref="AssetReader"/> delegate used to read satellite files.
         /// </summary>
         public AssetReader FileReader { get; set; }
+
+        public Boolean SkipValidation { get; set; }
     }
 
     partial class ModelRoot
     {
+        #region read / load methods
+
         /// <summary>
         /// Reads a <see cref="MODEL"/> instance from a path pointing to a GLB or a GLTF file
         /// </summary>
@@ -183,6 +187,10 @@ namespace SharpGLTF.Schema2
             }
         }
 
+        #endregion
+
+        #region reading core
+
         private static MODEL _Read(TextReader textReader, ReadSettings settings)
         {
             Guard.NotNull(textReader, nameof(textReader));
@@ -212,11 +220,16 @@ namespace SharpGLTF.Schema2
                     image._ResolveUri(settings.FileReader);
                 }
 
-                var ex = root.Validate().FirstOrDefault();
-                if (ex != null) throw ex;
+                if (!settings.SkipValidation)
+                {
+                    var ex = root.Validate().FirstOrDefault();
+                    if (ex != null) throw ex;
+                }
 
                 return root;
             }
         }
+
+        #endregion
     }
 }

+ 58 - 0
src/SharpGLTF.Core/Validation/SemanticErrors.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SharpGLTF.Validation
+{
+    static class SemanticErrors
+    {
+        #pragma warning disable SA1310 // Field names should not contain underscore
+
+        // INFORMATION
+
+        public const string EXTRA_PROPERTY = "This property should not be defined as it will not be used.";
+        public const string NODE_EMPTY = "Empty node encountered.";
+        public const string NODE_MATRIX_DEFAULT = "Do not specify default transform matrix.";
+        public const string NON_OBJECT_EXTRAS = "Prefer JSON Objects for extras.";
+
+        // WARNINGS
+
+        public const string ASSET_MIN_VERSION_GREATER_THAN_VERSION = "Asset minVersion '{0}' is greater than version '{1}'.";
+        public const string CAMERA_XMAG_YMAG_ZERO = "xmag and ymag must not be zero.";
+        public const string INTEGER_WRITTEN_AS_FLOAT = "Integer value is written with fractional part: {0}.";
+        public const string MATERIAL_ALPHA_CUTOFF_INVALID_MODE = "Alpha cutoff is supported only for 'MASK' alpha mode.";
+        public const string MESH_PRIMITIVES_UNEQUAL_JOINTS_COUNT = "All primitives should contain the same number of 'JOINTS' and 'WEIGHTS' attribute sets.";
+        public const string MESH_PRIMITIVE_TANGENT_POINTS = "TANGENT attribute defined for POINTS rendering mode.";
+        public const string MESH_PRIMITIVE_TANGENT_WITHOUT_NORMAL = "TANGENT attribute without NORMAL found.";
+        public const string MULTIPLE_EXTENSIONS = "Multiple extensions are defined for this object: ('%a', '%b', '%c').";
+        public const string MESH_PRIMITIVE_NO_POSITION = "No POSITION attribute found.";
+        public const string NON_RELATIVE_URI = "Non-relative URI found: {0}.";
+        public const string UNKNOWN_ASSET_MINOR_VERSION = "Unknown glTF minor asset version: {0}.";
+        public const string UNRESERVED_EXTENSION_PREFIX = "Extension uses unreserved extension prefix '{0}'.";
+
+        // ERRORS
+
+        public const string ACCESSOR_MATRIX_ALIGNMENT = "Matrix accessors must be aligned to 4-byte boundaries.";
+        public const string ACCESSOR_NORMALIZED_INVALID = "Only (u)byte and (u)short accessors can be normalized.";
+        public const string ACCESSOR_OFFSET_ALIGNMENT = "Offset {0} is not a multiple of componentType length {1}.";
+        public const string ACCESSOR_SPARSE_COUNT_OUT_OF_RANGE = "Sparse accessor overrides more elements ({0}) than the base accessor contains({1}).";
+        public const string BUFFER_DATA_URI_MIME_TYPE_INVALID = "Buffer's Data URI MIME-Type must be 'application/octet-stream' or 'application/gltf-buffer'. Found '{0}' instead.";
+        public const string BUFFER_VIEW_INVALID_BYTE_STRIDE = "Only buffer views with raw vertex data can have byteStride.";
+        public const string BUFFER_VIEW_TOO_BIG_BYTE_STRIDE = "Buffer view's byteStride ({0}) is smaller than byteLength ({1}).";
+        public const string CAMERA_ZFAR_LEQUAL_ZNEAR = "zfar must be greater than znear.";
+        public const string INVALID_GL_VALUE = "Invalid value {0} for GL type '{1}'.";
+        public const string KHR_LIGHTS_PUNCTUAL_LIGHT_SPOT_ANGLES = "outerConeAngle ({1}) is less than or equal to innerConeAngle({0}).";
+        public const string MESH_INVALID_WEIGHTS_COUNT = "The length of weights array ({0}) does not match the number of morph targets({1}).";
+        public const string MESH_PRIMITIVES_UNEQUAL_TARGETS_COUNT = "All primitives must have the same number of morph targets.";
+        public const string MESH_PRIMITIVE_INDEXED_SEMANTIC_CONTINUITY = "Indices for indexed attribute semantic '{0}' must start with 0 and be continuous.Total expected indices: {1}, total provided indices: {2}.";
+        public const string MESH_PRIMITIVE_INVALID_ATTRIBUTE = "Invalid attribute name.";
+        public const string MESH_PRIMITIVE_JOINTS_WEIGHTS_MISMATCH = "Number of JOINTS attribute semantics must match number of WEIGHTS.";
+        public const string NODE_MATRIX_NON_TRS = "Matrix must be decomposable to TRS.";
+        public const string NODE_MATRIX_TRS = "A node can have either a matrix or any combination of translation/rotation/scale (TRS) properties.";
+        public const string ROTATION_NON_UNIT = "Rotation quaternion must be normalized.";
+        public const string UNKNOWN_ASSET_MAJOR_VERSION = "Unknown glTF major asset version: {0}.";
+        public const string UNUSED_EXTENSION_REQUIRED = "Unused extension '{0}' cannot be required.";
+
+        #pragma warning restore SA1310 // Field names should not contain underscore
+    }
+}

+ 5 - 0
src/SharpGLTF.Core/Validation/ValidationContext.cs

@@ -54,6 +54,11 @@ namespace SharpGLTF.Validation
             _Exceptions.Add(new SemanticException(target, message));
         }
 
+        public void AddSemanticError(TARGET target, String format, params object[] args)
+        {
+            _Exceptions.Add(new SemanticException(target, String.Format(format, args)));
+        }
+
         #endregion
 
         #region data errors

+ 0 - 4
src/SharpGLTF.Toolkit/Geometry/MeshBuilder.cs

@@ -34,8 +34,6 @@ namespace SharpGLTF.Geometry
     /// The vertex fragment type with Skin Joint Weights.
     /// Valid types are:
     /// <see cref="VertexEmpty"/>,
-    /// <see cref="VertexJoints8x4"/>,
-    /// <see cref="VertexJoints8x8"/>,
     /// <see cref="VertexJoints4"/>,
     /// <see cref="VertexJoints8"/>.
     /// </typeparam>
@@ -180,8 +178,6 @@ namespace SharpGLTF.Geometry
     /// The vertex fragment type with Skin Joint Weights.
     /// Valid types are:
     /// <see cref="VertexEmpty"/>,
-    /// <see cref="VertexJoints8x4"/>,
-    /// <see cref="VertexJoints8x8"/>,
     /// <see cref="VertexJoints4"/>,
     /// <see cref="VertexJoints8"/>.
     /// </typeparam>

+ 2 - 2
src/SharpGLTF.Toolkit/Geometry/MeshBuilderToolkit.cs

@@ -23,13 +23,13 @@ namespace SharpGLTF.Geometry
 
     static class MeshBuilderToolkit
     {
-        public static VertexBuilder<VertexGeometryDelta, VertexEmpty, VertexEmpty>[] GetMorphTargetVertices(this IPrimitiveMorphTargetReader morphTarget, int vertexCount, int targetIndex)
+        public static VertexBuilder<VertexGeometryDelta, VertexEmpty, VertexEmpty>[] GetMorphTargetVertices(this IPrimitiveMorphTargetReader morphTarget, int vertexCount)
         {
             var c = new VertexBuilder<VertexGeometryDelta, VertexEmpty, VertexEmpty>[vertexCount];
 
             for (int i = 0; i < vertexCount; ++i)
             {
-                var delta = morphTarget.GetVertexDelta(targetIndex, i);
+                var delta = morphTarget.GetVertexDelta(i);
 
                 c[i] = new VertexBuilder<VertexGeometryDelta, VertexEmpty, VertexEmpty>(delta);
             }

+ 81 - 72
src/SharpGLTF.Toolkit/Geometry/MorphTargetBuilder.cs

@@ -10,28 +10,25 @@ namespace SharpGLTF.Geometry
 {
     public interface IPrimitiveMorphTargetReader
     {
-        int TargetsCount { get; }
-
         /// <summary>
         /// Gets the collection of vertex indices that have deltas.
         /// </summary>
-        /// <param name="morphTargetIndex">The morph target to query.</param>
         /// <returns>A collection of vertex indices.</returns>
-        IReadOnlyCollection<int> GetTargetIndices(int morphTargetIndex);
+        IReadOnlyCollection<int> GetTargetIndices();
 
         /// <summary>
         /// Gets the <see cref="VertexGeometryDelta"/> of a given vertex for a given morph target.
         /// </summary>
-        /// <param name="morphTargetIndex"></param>
-        /// <param name="vertexIndex"></param>
-        /// <returns></returns>
-        VertexGeometryDelta GetVertexDelta(int morphTargetIndex, int vertexIndex);
+        /// <param name="vertexIndex">The index of the vertex.</param>
+        /// <returns>A Vertex delta.</returns>
+        VertexGeometryDelta GetVertexDelta(int vertexIndex);
     }
 
-    public class PrimitiveMorphTargetBuilder<TvG> : IPrimitiveMorphTargetReader
+    sealed class PrimitiveMorphTargetBuilder<TvG> : IPrimitiveMorphTargetReader
         where TvG : struct, IVertexGeometry
     {
         #region lifecycle
+
         internal PrimitiveMorphTargetBuilder(Func<int, TvG> baseVertexFunc)
         {
             _BaseVertexFunc = baseVertexFunc;
@@ -43,28 +40,20 @@ namespace SharpGLTF.Geometry
 
         private readonly Func<int, TvG> _BaseVertexFunc;
 
-        private readonly List<Dictionary<int, TvG>> _Targets = new List<Dictionary<int, TvG>>();
-
-        #endregion
-
-        #region properties
-
-        public int TargetsCount => _Targets.Count;
+        private readonly Dictionary<int, TvG> _MorphVertices = new Dictionary<int, TvG>();
 
         #endregion
 
         #region API
 
-        public IReadOnlyCollection<int> GetTargetIndices(int morphTargetIndex)
+        public IReadOnlyCollection<int> GetTargetIndices()
         {
-            return morphTargetIndex < _Targets.Count ? _Targets[morphTargetIndex].Keys : (IReadOnlyCollection<int>)Array.Empty<int>();
+            return _MorphVertices.Keys;
         }
 
-        public VertexGeometryDelta GetVertexDelta(int morphTargetIndex, int vertexIndex)
+        public VertexGeometryDelta GetVertexDelta(int vertexIndex)
         {
-            var target = _Targets[morphTargetIndex];
-
-            if (target.TryGetValue(vertexIndex, out TvG value))
+            if (_MorphVertices.TryGetValue(vertexIndex, out TvG value))
             {
                 return value.Subtract(_BaseVertexFunc(vertexIndex));
             }
@@ -72,25 +61,23 @@ namespace SharpGLTF.Geometry
             return default;
         }
 
-        public void SetVertexDelta(int morphTargetIndex, int vertexIndex, VertexGeometryDelta value)
+        public void SetVertexDelta(int vertexIndex, VertexGeometryDelta value)
         {
             if (object.Equals(value, default(VertexGeometryDelta)))
             {
-                _RemoveVertex(morphTargetIndex, vertexIndex);
+                _RemoveVertex(vertexIndex);
                 return;
             }
 
             var vertex = _BaseVertexFunc(vertexIndex);
             vertex.Add(value);
 
-            _SetVertex(morphTargetIndex, vertexIndex, vertex);
+            _SetVertex(vertexIndex, vertex);
         }
 
-        public TvG GetVertex(int morphTargetIndex, int vertexIndex)
+        public TvG GetVertex(int vertexIndex)
         {
-            var target = _Targets[morphTargetIndex];
-
-            if (target.TryGetValue(vertexIndex, out TvG value))
+            if (_MorphVertices.TryGetValue(vertexIndex, out TvG value))
             {
                 return value;
             }
@@ -98,32 +85,25 @@ namespace SharpGLTF.Geometry
             return _BaseVertexFunc(vertexIndex);
         }
 
-        public void SetVertex(int morphTargetIndex, int vertexIndex, TvG value)
+        public void SetVertex(int vertexIndex, TvG value)
         {
             if (object.Equals(value, _BaseVertexFunc(vertexIndex)))
             {
-                _RemoveVertex(morphTargetIndex, vertexIndex);
+                _RemoveVertex(vertexIndex);
                 return;
             }
 
-            _SetVertex(morphTargetIndex, vertexIndex, value);
+            _SetVertex(vertexIndex, value);
         }
 
-        private void _SetVertex(int morphTargetIndex, int vertexIndex, TvG value)
+        private void _SetVertex(int vertexIndex, TvG value)
         {
-            while (_Targets.Count <= morphTargetIndex)
-            {
-                _Targets.Add(new Dictionary<int, TvG>());
-            }
-
-            _Targets[morphTargetIndex][vertexIndex] = value;
+            _MorphVertices[vertexIndex] = value;
         }
 
-        private void _RemoveVertex(int morphTargetIndex, int vertexIndex)
+        private void _RemoveVertex(int vertexIndex)
         {
-            if (morphTargetIndex >= _Targets.Count) return;
-
-            _Targets[morphTargetIndex].Remove(vertexIndex);
+            _MorphVertices.Remove(vertexIndex);
         }
 
         #endregion
@@ -132,53 +112,50 @@ namespace SharpGLTF.Geometry
 
         internal void TransformVertices(Func<TvG, TvG> vertexFunc)
         {
-            for (int tidx = 0; tidx < _Targets.Count; ++tidx)
+            foreach (var vidx in _MorphVertices.Keys)
             {
-                var target = _Targets[tidx];
+                var g = GetVertex(vidx);
 
-                foreach (var vidx in target.Keys)
-                {
-                    var g = GetVertex(tidx, vidx);
-
-                    g = vertexFunc(g);
+                g = vertexFunc(g);
 
-                    SetVertex(tidx, vidx, g);
-                }
+                SetVertex(vidx, g);
             }
         }
 
         internal void SetMorphTargets(PrimitiveMorphTargetBuilder<TvG> other, IReadOnlyDictionary<int, int> vertexMap, Func<TvG, TvG> vertexFunc)
         {
-            for (int tidx = 0; tidx < other.TargetsCount; ++tidx)
-            {
-                var indices = other.GetTargetIndices(tidx);
+            var indices = other.GetTargetIndices();
 
-                foreach (var srcVidx in indices)
-                {
-                    var g = other.GetVertex(tidx, srcVidx);
+            foreach (var srcVidx in indices)
+            {
+                var g = other.GetVertex(srcVidx);
 
-                    if (vertexFunc != null) g = vertexFunc(g);
+                if (vertexFunc != null) g = vertexFunc(g);
 
-                    var dstVidx = srcVidx;
+                var dstVidx = srcVidx;
 
-                    if (vertexMap != null)
-                    {
-                        if (!vertexMap.TryGetValue(srcVidx, out dstVidx)) dstVidx = -1;
-                    }
-
-                    if (dstVidx >= 0) this.SetVertex(tidx, dstVidx, g);
+                if (vertexMap != null)
+                {
+                    if (!vertexMap.TryGetValue(srcVidx, out dstVidx)) dstVidx = -1;
                 }
+
+                if (dstVidx >= 0) this.SetVertex(dstVidx, g);
             }
         }
 
         #endregion
     }
 
-    public class MorphTargetBuilder<TMaterial, TvG, TvS, TvM>
+    /// <summary>
+    /// Utility class to edit the Morph targets of a mesh.
+    /// </summary>
+    public sealed class MorphTargetBuilder<TMaterial, TvG, TvS, TvM>
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
     {
+        #region lifecycle
+
         internal MorphTargetBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, int morphTargetIndex)
         {
             _Mesh = mesh;
@@ -205,9 +182,12 @@ namespace SharpGLTF.Geometry
                     geos.Add(key);
                 }
             }
-
         }
 
+        #endregion
+
+        #region data
+
         private readonly MeshBuilder<TMaterial, TvG, TvM, TvS> _Mesh;
         private readonly int _MorphTargetIndex;
 
@@ -215,28 +195,57 @@ namespace SharpGLTF.Geometry
 
         private readonly Dictionary<Vector3, List<TvG>> _Positions = new Dictionary<Vector3, List<TvG>>();
 
+        #endregion
+
+        #region properties
+
         public IReadOnlyCollection<Vector3> Positions => _Positions.Keys;
 
         public IReadOnlyCollection<TvG> Vertices => _Vertices.Keys;
 
+        #endregion
+
+        #region API
+
+        public IReadOnlyList<TvG> GetVertices(Vector3 position)
+        {
+            return _Positions.TryGetValue(position, out List<TvG> geos) ? (IReadOnlyList<TvG>)geos : Array.Empty<TvG>();
+        }
+
         public void SetVertexDelta(Vector3 key, VertexGeometryDelta delta)
         {
             if (_Positions.TryGetValue(key, out List<TvG> geos))
             {
-                foreach (var g in geos) SetVertexDisplacement(g, delta);
+                foreach (var g in geos) SetVertexDelta(g, delta);
+            }
+        }
+
+        public void SetVertex(TvG meshVertex, TvG morphVertex)
+        {
+            if (_Vertices.TryGetValue(meshVertex, out List<(PrimitiveBuilder<TMaterial, TvG, TvM, TvS>, int)> val))
+            {
+                foreach (var entry in val)
+                {
+                    entry.Item1
+                        ._UseMorphTarget(_MorphTargetIndex)
+                        .SetVertex(entry.Item2, morphVertex);
+                }
             }
         }
 
-        public void SetVertexDisplacement(TvG vertex, VertexGeometryDelta delta)
+        public void SetVertexDelta(TvG meshVertex, VertexGeometryDelta delta)
         {
-            if (_Vertices.TryGetValue(vertex, out List<(PrimitiveBuilder<TMaterial, TvG, TvM, TvS>, int)> val))
+            if (_Vertices.TryGetValue(meshVertex, out List<(PrimitiveBuilder<TMaterial, TvG, TvM, TvS>, int)> val))
             {
                 foreach (var entry in val)
                 {
-                    entry.Item1.MorphTargets.SetVertexDelta(_MorphTargetIndex, entry.Item2, delta);
+                    entry.Item1
+                        ._UseMorphTarget(_MorphTargetIndex)
+                        .SetVertexDelta(entry.Item2, delta);
                 }
             }
         }
 
+        #endregion
     }
 }

+ 1 - 1
src/SharpGLTF.Toolkit/Geometry/PackedMeshBuilder.cs

@@ -41,7 +41,7 @@ namespace SharpGLTF.Geometry
                     var dstPrim = dstMesh.AddPrimitive(srcPrim.Material, srcPrim.VerticesPerPrimitive);
 
                     bool useStrided = prefferStrided;
-                    if (srcPrim.MorphTargets.TargetsCount > 0) useStrided = false;
+                    if (srcPrim.MorphTargets.Count > 0) useStrided = false;
 
                     if (useStrided) dstPrim.SetStridedVertices(srcPrim, jointEncoding);
                     else dstPrim.SetStreamedVertices(srcPrim, jointEncoding);

+ 2 - 2
src/SharpGLTF.Toolkit/Geometry/PackedPrimitiveBuilder.cs

@@ -94,9 +94,9 @@ namespace SharpGLTF.Geometry
 
             if (!hasPositions) throw new InvalidOperationException("Set vertices before morph targets.");
 
-            for (int i = 0; i < srcPrim.MorphTargets.TargetsCount; ++i)
+            for (int i = 0; i < srcPrim.MorphTargets.Count; ++i)
             {
-                var mtv = srcPrim.MorphTargets.GetMorphTargetVertices(srcPrim.Vertices.Count, i);
+                var mtv = srcPrim.MorphTargets[i].GetMorphTargetVertices(srcPrim.Vertices.Count);
 
                 var pAccessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "POSITIONDELTA", EncodingType.UNSIGNED_SHORT);
 

+ 22 - 11
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -32,7 +32,10 @@ namespace SharpGLTF.Geometry
         /// </summary>
         IReadOnlyList<IVertexBuilder> Vertices { get; }
 
-        IPrimitiveMorphTargetReader MorphTargets { get; }
+        /// <summary>
+        /// Gets the list of <see cref="IPrimitiveMorphTargetReader"/>.
+        /// </summary>
+        IReadOnlyList<IPrimitiveMorphTargetReader> MorphTargets { get; }
 
         /// <summary>
         /// Gets the indices of all points, given that <see cref="VerticesPerPrimitive"/> is 1.
@@ -104,8 +107,6 @@ namespace SharpGLTF.Geometry
     /// The vertex fragment type with Skin Joint Weights.
     /// Valid types are:
     /// <see cref="VertexEmpty"/>,
-    /// <see cref="VertexJoints8x4"/>,
-    /// <see cref="VertexJoints8x8"/>,
     /// <see cref="VertexJoints4"/>,
     /// <see cref="VertexJoints8"/>.
     /// </typeparam>
@@ -120,8 +121,6 @@ namespace SharpGLTF.Geometry
         {
             this._Mesh = mesh;
             this._Material = material;
-
-            this._MorphTargets = new PrimitiveMorphTargetBuilder<TvG>( idx => _Vertices[idx].Geometry );
         }
 
         #endregion
@@ -134,7 +133,7 @@ namespace SharpGLTF.Geometry
 
         private readonly VertexListWrapper _Vertices = new VertexListWrapper();
 
-        private readonly PrimitiveMorphTargetBuilder<TvG> _MorphTargets;
+        private readonly List<PrimitiveMorphTargetBuilder<TvG>> _MorphTargets = new List<PrimitiveMorphTargetBuilder<TvG>>();
 
         #endregion
 
@@ -158,6 +157,8 @@ namespace SharpGLTF.Geometry
 
         IReadOnlyList<IVertexBuilder> IPrimitiveReader<TMaterial>.Vertices => _Vertices;
 
+        IReadOnlyList<IPrimitiveMorphTargetReader> IPrimitiveReader<TMaterial>.MorphTargets => _MorphTargets;
+
         public virtual IReadOnlyList<int> Points => Array.Empty<int>();
 
         public virtual IReadOnlyList<(int, int)> Lines => Array.Empty<(int, int)>();
@@ -166,9 +167,16 @@ namespace SharpGLTF.Geometry
 
         public virtual IReadOnlyList<(int, int, int, int?)> Surfaces => Array.Empty<(int, int, int, int?)>();
 
-        public PrimitiveMorphTargetBuilder<TvG> MorphTargets => _MorphTargets;
+        #endregion
 
-        IPrimitiveMorphTargetReader IPrimitiveReader<TMaterial>.MorphTargets => _MorphTargets;
+        #region API - morph targets
+
+        internal PrimitiveMorphTargetBuilder<TvG> _UseMorphTarget(int morphTargetIndex)
+        {
+            while (this._MorphTargets.Count <= morphTargetIndex) this._MorphTargets.Add(new PrimitiveMorphTargetBuilder<TvG>(idx => _Vertices[idx].Geometry));
+
+            return this._MorphTargets[morphTargetIndex];
+        }
 
         #endregion
 
@@ -206,7 +214,7 @@ namespace SharpGLTF.Geometry
 
         void IPrimitiveBuilder.SetVertexDelta(int morphTargetIndex, int vertexIndex, VertexGeometryDelta delta)
         {
-            this._MorphTargets.SetVertexDelta(morphTargetIndex, vertexIndex, delta);
+            _UseMorphTarget(morphTargetIndex).SetVertexDelta(vertexIndex, delta);
         }
 
         /// <summary>
@@ -273,7 +281,7 @@ namespace SharpGLTF.Geometry
 
             TvG geoFunc(TvG g) => vertexTransformFunc(new VertexBuilder<TvG, TvM, TvS>(g, default, default(TvS))).Geometry;
 
-            _MorphTargets.TransformVertices(geoFunc);
+            foreach (var mt in _MorphTargets) mt.TransformVertices(geoFunc);
         }
 
         internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransformFunc)
@@ -340,7 +348,10 @@ namespace SharpGLTF.Geometry
 
             TvG geoFunc(TvG g) => vertexTransformFunc(new VertexBuilder<TvG, TvM, TvS>(g, default, default(TvS))).Geometry;
 
-            _MorphTargets.SetMorphTargets(primitive._MorphTargets, vmap, geoFunc);
+            for (int i = 0; i < primitive._MorphTargets.Count; ++i)
+            {
+                _UseMorphTarget(i).SetMorphTargets(primitive._MorphTargets[i], vmap, geoFunc);
+            }
         }
 
         #endregion

+ 1 - 1
tests/SharpGLTF.Tests/SharpGLTF.Tests.csproj

@@ -13,7 +13,7 @@
   <ItemGroup>
     <PackageReference Include="LibGit2Sharp" Version="0.26.1" />
     <PackageReference Include="nunit" Version="3.12.0" />
-    <PackageReference Include="NUnit3TestAdapter" Version="3.15.0" />
+    <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
     <PackageReference Include="PLplot" Version="5.13.7" />    
   </ItemGroup>