Browse Source

Merge pull request #120 from acecebov/features/Issue116

Added support for COLOR_n and TEXCOORD_n in morph targets - #116
Vicente Penades 3 years ago
parent
commit
7a85037342

+ 3 - 5
src/SharpGLTF.Toolkit/Geometry/MeshBuilderToolkit.cs

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

+ 117 - 39
src/SharpGLTF.Toolkit/Geometry/MorphTargetBuilder.cs

@@ -21,14 +21,14 @@ namespace SharpGLTF.Geometry
         /// </summary>
         /// <param name="vertexIndex">The index of the vertex.</param>
         /// <returns>If the given index has a morphed vertex, it will return it, else ir will return the base vertex.</returns>
-        IVertexGeometry GetVertex(int vertexIndex);
+        IVertexBuilder GetVertex(int vertexIndex);
 
         /// <summary>
         /// Gets the <see cref="VertexGeometryDelta"/> of a given vertex for a given morph target.
         /// </summary>
         /// <param name="vertexIndex">The index of the vertex.</param>
         /// <returns>A Vertex delta (Morphed vertex minus base vertex).</returns>
-        VertexGeometryDelta GetVertexDelta(int vertexIndex);
+        VertexBuilder<VertexGeometryDelta, VertexMaterialDelta, VertexEmpty> GetVertexDelta(int vertexIndex);
     }
 
     /// <summary>
@@ -36,30 +36,31 @@ namespace SharpGLTF.Geometry
     /// <see cref="PrimitiveBuilder{TMaterial, TvG, TvM, TvS}._UseMorphTarget(int)"/>
     /// </summary>
     /// <typeparam name="TvG">The vertex fragment type with Position, Normal and Tangent.</typeparam>
-    sealed class PrimitiveMorphTargetBuilder<TvG> : IPrimitiveMorphTargetReader
+    class PrimitiveMorphTargetBuilder<TvG, TvM> : IPrimitiveMorphTargetReader
         where TvG : struct, IVertexGeometry
+        where TvM : struct, IVertexMaterial
     {
         #region lifecycle
 
-        internal PrimitiveMorphTargetBuilder(Func<int, TvG> baseVertexFunc)
+        internal PrimitiveMorphTargetBuilder(Func<int, VertexBuilder<TvG, TvM, VertexEmpty>> baseVertexFunc)
         {
             this._BaseVertexFunc = baseVertexFunc;
-            this._MorphVertices = new Dictionary<int, TvG>();
+            this._MorphVertices = new Dictionary<int, VertexBuilder<TvG, TvM, VertexEmpty>>();
         }
 
-        internal PrimitiveMorphTargetBuilder(Func<int, TvG> baseVertexFunc, PrimitiveMorphTargetBuilder<TvG> other)
+        internal PrimitiveMorphTargetBuilder(Func<int, VertexBuilder<TvG, TvM, VertexEmpty>> baseVertexFunc, PrimitiveMorphTargetBuilder<TvG, TvM> other)
         {
             this._BaseVertexFunc = baseVertexFunc;
-            this._MorphVertices = new Dictionary<int, TvG>(other._MorphVertices);
+            this._MorphVertices = new Dictionary<int, VertexBuilder<TvG, TvM, VertexEmpty>>(other._MorphVertices);
         }
 
         #endregion
 
         #region data
 
-        private readonly Func<int, TvG> _BaseVertexFunc;
+        private readonly Func<int, VertexBuilder<TvG, TvM, VertexEmpty>> _BaseVertexFunc;
 
-        private readonly Dictionary<int, TvG> _MorphVertices;
+        private readonly Dictionary<int, VertexBuilder<TvG, TvM, VertexEmpty>> _MorphVertices;
 
         #endregion
 
@@ -70,54 +71,60 @@ namespace SharpGLTF.Geometry
             return _MorphVertices.Keys;
         }
 
-        public VertexGeometryDelta GetVertexDelta(int vertexIndex)
+        public VertexBuilder<VertexGeometryDelta, VertexMaterialDelta, VertexEmpty> GetVertexDelta(int vertexIndex)
         {
-            if (_MorphVertices.TryGetValue(vertexIndex, out TvG value))
+            if (_MorphVertices.TryGetValue(vertexIndex, out VertexBuilder<TvG, TvM, VertexEmpty> value))
             {
-                return value.Subtract(_BaseVertexFunc(vertexIndex));
+                var vertex = _BaseVertexFunc(vertexIndex);
+                return new VertexBuilder<VertexGeometryDelta, VertexMaterialDelta, VertexEmpty>(
+                    value.Geometry.Subtract(vertex.Geometry),
+                    value.Material.Subtract(vertex.Material));
             }
 
             return default;
         }
 
-        public void SetVertexDelta(int vertexIndex, VertexGeometryDelta value)
+        public void SetVertexDelta(int vertexIndex, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta)
         {
-            if (object.Equals(value, default(VertexGeometryDelta)))
+            if (object.Equals(geometryDelta, default(VertexGeometryDelta)))
             {
                 _RemoveVertex(vertexIndex);
                 return;
             }
 
             var vertex = _BaseVertexFunc(vertexIndex);
-            vertex.Add(value);
+
+            vertex.Geometry.Add(geometryDelta);
+            if (typeof(TvM) != typeof(VertexEmpty))
+                vertex.Material.Add(materialDelta);
 
             _SetVertex(vertexIndex, vertex);
         }
 
-        IVertexGeometry IPrimitiveMorphTargetReader.GetVertex(int vertexIndex)
+        IVertexBuilder IPrimitiveMorphTargetReader.GetVertex(int vertexIndex)
         {
-            return _MorphVertices.TryGetValue(vertexIndex, out TvG value) ? value : _BaseVertexFunc(vertexIndex);
+            return _MorphVertices.TryGetValue(vertexIndex, out VertexBuilder<TvG, TvM, VertexEmpty> value) ? value : _BaseVertexFunc(vertexIndex);
         }
 
-        public TvG GetVertex(int vertexIndex)
+        public VertexBuilder<TvG, TvM, VertexEmpty> GetVertex(int vertexIndex)
         {
-            return _MorphVertices.TryGetValue(vertexIndex, out TvG value) ? value : _BaseVertexFunc(vertexIndex);
+            return _MorphVertices.TryGetValue(vertexIndex, out VertexBuilder<TvG, TvM, VertexEmpty> value) ? value : _BaseVertexFunc(vertexIndex);
         }
 
-        public void SetVertex(int vertexIndex, TvG value)
+        public void SetVertex(int vertexIndex, VertexBuilder<TvG, TvM, VertexEmpty> vertex)
         {
-            if (object.Equals(value, _BaseVertexFunc(vertexIndex)))
+            if (object.Equals(vertex, _BaseVertexFunc(vertexIndex)))
             {
                 _RemoveVertex(vertexIndex);
                 return;
             }
 
-            _SetVertex(vertexIndex, value);
+            _SetVertex(vertexIndex, vertex);
         }
 
-        private void _SetVertex(int vertexIndex, TvG value)
+        private void _SetVertex(int vertexIndex, VertexBuilder<TvG, TvM, VertexEmpty> vertex)
         {
-            _MorphVertices[vertexIndex] = value;
+            _MorphVertices[vertexIndex] = vertex;
         }
 
         private void _RemoveVertex(int vertexIndex)
@@ -129,7 +136,7 @@ namespace SharpGLTF.Geometry
 
         #region internals
 
-        internal void TransformVertices(Func<TvG, TvG> vertexFunc)
+        internal void TransformVertices(Func<VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>> vertexFunc)
         {
             foreach (var vidx in _MorphVertices.Keys)
             {
@@ -141,7 +148,7 @@ namespace SharpGLTF.Geometry
             }
         }
 
-        internal void SetMorphTargets(IPrimitiveMorphTargetReader other, IReadOnlyDictionary<int, int> vertexMap, Func<IVertexGeometry, TvG> vertexFunc)
+        internal void SetMorphTargets(IPrimitiveMorphTargetReader other, IReadOnlyDictionary<int, int> vertexMap, Func<IVertexGeometry, VertexBuilder<TvG, TvM, VertexEmpty>> vertexFunc)
         {
             Guard.NotNull(vertexFunc, nameof(vertexFunc));
 
@@ -149,7 +156,7 @@ namespace SharpGLTF.Geometry
 
             foreach (var srcVidx in indices)
             {
-                var g = vertexFunc(other.GetVertex(srcVidx));
+                var g = vertexFunc(other.GetVertex(srcVidx).GetGeometry());
 
                 var dstVidx = srcVidx;
 
@@ -182,19 +189,43 @@ namespace SharpGLTF.Geometry
         /// <param name="morphVertex">The morphed vertex.</param>
         void SetVertex(IVertexGeometry meshVertex, IVertexGeometry morphVertex);
 
+        /// <summary>
+        /// Sets an absolute morph target.
+        /// </summary>
+        /// <param name="meshVertex">The base mesh vertex to morph.</param>
+        /// <param name="morphVertex">The morphed vertex.</param>
+        /// <param name="morphMaterial">The morphed vertex material.</param>
+        void SetVertex(IVertexGeometry meshVertex, IVertexGeometry morphVertex, IVertexMaterial morphMaterial);
+
         /// <summary>
         /// Sets a relative morph target
         /// </summary>
         /// <param name="meshVertex">The base mesh vertex to morph.</param>
-        /// <param name="delta">The offset from <paramref name="meshVertex"/> to morph.</param>
-        void SetVertexDelta(IVertexGeometry meshVertex, VertexGeometryDelta delta);
+        /// <param name="geometryDelta">The offset from <paramref name="meshVertex"/> to morph.</param>
+        void SetVertexDelta(IVertexGeometry meshVertex, VertexGeometryDelta geometryDelta);
+
+        /// <summary>
+        /// Sets a relative morph target
+        /// </summary>
+        /// <param name="meshVertex">The base mesh vertex to morph.</param>
+        /// <param name="geometryDelta">The offset from <paramref name="meshVertex"/> to morph.</param>
+        /// <param name="materialDelta">The offset from <paramref name="meshVertex"/> material to morph.</param>
+        void SetVertexDelta(IVertexGeometry meshVertex, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta);
 
         /// <summary>
         /// Sets a relative morph target to all base mesh vertices matching <paramref name="meshPosition"/>.
         /// </summary>
         /// <param name="meshPosition">The base vertex position.</param>
-        /// <param name="delta">The offset to apply to each matching vertex found.</param>
-        void SetVertexDelta(Vector3 meshPosition, VertexGeometryDelta delta);
+        /// <param name="geometryDelta">The offset to apply to each matching vertex found.</param>
+        void SetVertexDelta(Vector3 meshPosition, VertexGeometryDelta geometryDelta);
+
+        /// <summary>
+        /// Sets a relative morph target to all base mesh vertices matching <paramref name="meshPosition"/>.
+        /// </summary>
+        /// <param name="meshPosition">The base vertex position.</param>
+        /// <param name="geometryDelta">The offset to apply to each matching vertex found.</param>
+        /// <param name="materialDelta">The offset to apply to each matching vertex material found.</param>
+        void SetVertexDelta(Vector3 meshPosition, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta);
     }
 
     /// <summary>
@@ -273,15 +304,49 @@ namespace SharpGLTF.Geometry
             return _Positions.TryGetValue(position, out List<TvG> geos) ? (IReadOnlyList<TvG>)geos : Array.Empty<TvG>();
         }
 
-        public void SetVertexDelta(Vector3 key, VertexGeometryDelta delta)
+        public void SetVertexDelta(Vector3 key, VertexGeometryDelta geometryDelta)
         {
             if (_Positions.TryGetValue(key, out List<TvG> geos))
             {
-                foreach (var g in geos) SetVertexDelta(g, delta);
+                foreach (var g in geos) SetVertexDelta(g, geometryDelta, VertexMaterialDelta.Zero);
             }
         }
 
-        public void SetVertex(TvG meshVertex, TvG morphVertex)
+        public void SetVertexDelta(Vector3 key, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta)
+        {
+            if (_Positions.TryGetValue(key, out List<TvG> geos))
+            {
+                foreach (var g in geos) SetVertexDelta(g, geometryDelta, materialDelta);
+            }
+        }
+
+        public void SetVertexDelta(TvG meshVertex, VertexGeometryDelta geometryDelta)
+        {
+            if (_Vertices.TryGetValue(meshVertex, out List<(PrimitiveBuilder<TMaterial, TvG, TvM, TvS>, int)> val))
+            {
+                foreach (var entry in val)
+                {
+                    entry.Item1
+                        ._UseMorphTarget(_MorphTargetIndex)
+                        .SetVertexDelta(entry.Item2, geometryDelta, VertexMaterialDelta.Zero);
+                }
+            }
+        }
+
+        public void SetVertexDelta(TvG meshVertex, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta)
+        {
+            if (_Vertices.TryGetValue(meshVertex, out List<(PrimitiveBuilder<TMaterial, TvG, TvM, TvS>, int)> val))
+            {
+                foreach (var entry in val)
+                {
+                    entry.Item1
+                        ._UseMorphTarget(_MorphTargetIndex)
+                        .SetVertexDelta(entry.Item2, geometryDelta, materialDelta);
+                }
+            }
+        }
+
+        public void SetVertex(TvG meshVertex, VertexBuilder<TvG, TvM, VertexEmpty> morphVertex)
         {
             if (_Vertices.TryGetValue(meshVertex, out List<(PrimitiveBuilder<TMaterial, TvG, TvM, TvS>, int)> val))
             {
@@ -294,15 +359,16 @@ namespace SharpGLTF.Geometry
             }
         }
 
-        public void SetVertexDelta(TvG meshVertex, VertexGeometryDelta 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)
                 {
+                    var vertexMaterial = entry.Item1.Vertices[entry.Item2].Material;
                     entry.Item1
                         ._UseMorphTarget(_MorphTargetIndex)
-                        .SetVertexDelta(entry.Item2, delta);
+                        .SetVertex(entry.Item2, new VertexBuilder<TvG, TvM, VertexEmpty>(morphVertex, vertexMaterial));
                 }
             }
         }
@@ -322,12 +388,24 @@ namespace SharpGLTF.Geometry
 
         void IMorphTargetBuilder.SetVertex(IVertexGeometry meshVertex, IVertexGeometry morphVertex)
         {
-            SetVertex(meshVertex.ConvertToGeometry<TvG>(), morphVertex.ConvertToGeometry<TvG>());
+            SetVertex(meshVertex.ConvertToGeometry<TvG>(), 
+                new VertexBuilder<TvG, TvM, VertexEmpty>(morphVertex.ConvertToGeometry<TvG>(), default(VertexEmpty).ConvertToMaterial<TvM>()));
+        }
+
+        void IMorphTargetBuilder.SetVertex(IVertexGeometry meshVertex, IVertexGeometry morphVertex, IVertexMaterial morphMaterial)
+        {
+            SetVertex(meshVertex.ConvertToGeometry<TvG>(),
+                new VertexBuilder<TvG, TvM, VertexEmpty>(morphVertex.ConvertToGeometry<TvG>(), morphMaterial.ConvertToMaterial<TvM>()));
+        }
+
+        void IMorphTargetBuilder.SetVertexDelta(IVertexGeometry meshVertex, VertexGeometryDelta geometryDelta)
+        {
+            SetVertexDelta(meshVertex.ConvertToGeometry<TvG>(), geometryDelta, VertexMaterialDelta.Zero);
         }
 
-        void IMorphTargetBuilder.SetVertexDelta(IVertexGeometry meshVertex, VertexGeometryDelta delta)
+        void IMorphTargetBuilder.SetVertexDelta(IVertexGeometry meshVertex, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta)
         {
-            SetVertexDelta(meshVertex.ConvertToGeometry<TvG>(), delta);
+            SetVertexDelta(meshVertex.ConvertToGeometry<TvG>(), geometryDelta, materialDelta);
         }
 
         #endregion

+ 37 - 12
src/SharpGLTF.Toolkit/Geometry/Packed/PackedPrimitiveBuilder.cs

@@ -93,32 +93,57 @@ namespace SharpGLTF.Geometry
             bool hasNormals = _VertexAccessors.Any(item => item.Attribute.Name == "NORMAL");
             bool hasTangents = _VertexAccessors.Any(item => item.Attribute.Name == "TANGENT");
 
-            if (!hasPositions) throw new InvalidOperationException("Set vertices before morph targets.");
+            bool hasColors0 = _VertexAccessors.Any(item => item.Attribute.Name == "COLOR_0");
+            bool hasColors1 = _VertexAccessors.Any(item => item.Attribute.Name == "COLOR_1");
+            bool hasTextCoords0 = _VertexAccessors.Any(item => item.Attribute.Name == "TEXCOORD_0");
+            bool hasTextCoords1 = _VertexAccessors.Any(item => item.Attribute.Name == "TEXCOORD_1");
 
             for (int i = 0; i < srcPrim.MorphTargets.Count; ++i)
             {
                 var mtv = srcPrim.MorphTargets[i].GetMorphTargetVertices(srcPrim.Vertices.Count);
 
-                var pAccessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "POSITIONDELTA", vertexEncodings);
+                var pAccessor = !hasPositions ? null : VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "POSITIONDELTA", vertexEncodings);
+                // if delta is all 0s, then do not use the accessor
+                if (pAccessor != null && pAccessor.Data.All(b => b == 0))
+                    pAccessor = null;
 
                 var nAccessor = !hasNormals ? null : VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "NORMALDELTA", vertexEncodings);
+                // if delta is all 0s, then do not use the accessor
+                if (nAccessor != null && nAccessor.Data.All(b => b == 0))
+                    nAccessor = null;
 
                 var tAccessor = !hasTangents ? null : VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "TANGENTDELTA", vertexEncodings);
-
-                AddMorphTarget(pAccessor, nAccessor, tAccessor);
+                // if delta is all 0s, then do not use the accessor
+                if (tAccessor != null && tAccessor.Data.All(b => b == 0))
+                    tAccessor = null;
+
+                var c0Accessor = !hasColors0 ? null : VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "COLOR_0DELTA", vertexEncodings);
+                // if delta is all 0s, then do not use the accessor
+                if (c0Accessor != null && c0Accessor.Data.All(b => b == 0))
+                    c0Accessor = null;
+
+                var c1Accessor = !hasColors1 ? null : VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "COLOR_1DELTA", vertexEncodings);
+                // if delta is all 0s, then do not use the accessor
+                if (c1Accessor != null && c1Accessor.Data.All(b => b == 0))
+                    c1Accessor = null;
+
+                var uv0Accessor = !hasTextCoords0 ? null : VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "TEXCOORD_0DELTA", vertexEncodings);
+                // if delta is all 0s, then do not use the accessor
+                if (uv0Accessor != null && uv0Accessor.Data.All(b => b == 0))
+                    uv0Accessor = null;
+
+                var uv1Accessor = !hasTextCoords1 ? null : VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "TEXCOORD_1DELTA", vertexEncodings);
+                // if delta is all 0s, then do not use the accessor
+                if (uv1Accessor != null && uv1Accessor.Data.All(b => b == 0))
+                    uv1Accessor = null;
+
+                AddMorphTarget(pAccessor, nAccessor, tAccessor, c0Accessor, c1Accessor, uv0Accessor, uv1Accessor);
             }
         }
 
         private void AddMorphTarget(params Memory.MemoryAccessor[] morphTarget)
         {
-            morphTarget = morphTarget.Where(item => item != null).ToArray();
-
-            foreach (var accessor in morphTarget)
-            {
-                if (accessor.Attribute.Dimensions != DimensionType.VEC3) throw new InvalidOperationException();
-            }
-
-            morphTarget = morphTarget
+            morphTarget = morphTarget.Where(item => item != null)
                 .Select(item => _RemoveDelta(item))
                 .ToArray();
 

+ 17 - 8
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -62,7 +62,7 @@ namespace SharpGLTF.Geometry
 
             foreach (var otherMT in other._MorphTargets)
             {
-                var thisMT = new PrimitiveMorphTargetBuilder<TvG>(idx => this._Vertices[idx].Geometry, otherMT);
+                var thisMT = new PrimitiveMorphTargetBuilder<TvG, TvM>(idx => (this._Vertices[idx].Geometry, this._Vertices[idx].Material), otherMT);
                 this._MorphTargets.Add(otherMT);
             }
         }
@@ -79,7 +79,7 @@ namespace SharpGLTF.Geometry
 
         private readonly VertexListWrapper _Vertices = new VertexListWrapper();
 
-        private readonly List<PrimitiveMorphTargetBuilder<TvG>> _MorphTargets = new List<PrimitiveMorphTargetBuilder<TvG>>();
+        private readonly List<PrimitiveMorphTargetBuilder<TvG, TvM>> _MorphTargets = new List<PrimitiveMorphTargetBuilder<TvG, TvM>>();
 
         #endregion
 
@@ -141,9 +141,10 @@ namespace SharpGLTF.Geometry
 
         #region API - morph targets
 
-        internal PrimitiveMorphTargetBuilder<TvG> _UseMorphTarget(int morphTargetIndex)
+        internal PrimitiveMorphTargetBuilder<TvG, TvM> _UseMorphTarget(int morphTargetIndex)
         {
-            while (this._MorphTargets.Count <= morphTargetIndex) this._MorphTargets.Add(new PrimitiveMorphTargetBuilder<TvG>(idx => _Vertices[idx].Geometry));
+            while (this._MorphTargets.Count <= morphTargetIndex)
+                this._MorphTargets.Add(new PrimitiveMorphTargetBuilder<TvG, TvM>(idx => (_Vertices[idx].Geometry, _Vertices[idx].Material)));
 
             return this._MorphTargets[morphTargetIndex];
         }
@@ -185,9 +186,9 @@ namespace SharpGLTF.Geometry
             return _Vertices.Use(vertex);
         }
 
-        void IPrimitiveBuilder.SetVertexDelta(int morphTargetIndex, int vertexIndex, VertexGeometryDelta delta)
+        void IPrimitiveBuilder.SetVertexDelta(int morphTargetIndex, int vertexIndex, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta)
         {
-            _UseMorphTarget(morphTargetIndex).SetVertexDelta(vertexIndex, delta);
+            _UseMorphTarget(morphTargetIndex).SetVertexDelta(vertexIndex, geometryDelta, materialDelta);
         }
 
         /// <summary>
@@ -349,7 +350,11 @@ namespace SharpGLTF.Geometry
 
             if (vmap != null)
             {
-                TvG geoTransformFunc(IVertexGeometry g) => vertexTransformFunc(new VertexBuilder(g)).Geometry;
+                VertexBuilder<TvG, TvM, VertexEmpty> geoTransformFunc(IVertexGeometry g)
+                {
+                    var vertexData = vertexTransformFunc(new VertexBuilder(g));
+                    return (vertexData.Geometry, vertexData.Material);
+                }
 
                 for (int i = 0; i < primitive.MorphTargets.Count; ++i)
                 {
@@ -365,7 +370,11 @@ namespace SharpGLTF.Geometry
 
             _Vertices.ApplyTransform(vertexTransformFunc);
 
-            TvG geoFunc(TvG g) => vertexTransformFunc(new VertexBuilder<TvG, TvM, TvS>(g, default, default(TvS))).Geometry;
+            VertexBuilder<TvG, TvM, VertexEmpty> geoFunc(VertexBuilder<TvG, TvM, VertexEmpty> g)
+            {
+                var transformedVertex = vertexTransformFunc(new VertexBuilder<TvG, TvM, TvS>(g.Geometry, g.Material, default(TvS)));
+                return new VertexBuilder<TvG, TvM, VertexEmpty>(transformedVertex.Geometry, transformedVertex.Material);
+            }
 
             foreach (var mt in _MorphTargets) mt.TransformVertices(geoFunc);
         }

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

@@ -74,7 +74,7 @@ namespace SharpGLTF.Geometry
         /// </summary>
         Type VertexType { get; }
 
-        void SetVertexDelta(int morphTargetIndex, int vertexIndex, VertexGeometryDelta delta);
+        void SetVertexDelta(int morphTargetIndex, int vertexIndex, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta);
 
         int AddPoint(IVertexBuilder a);
 

+ 4 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexEmpty.cs

@@ -58,6 +58,10 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         void IVertexMaterial.SetTexCoord(int index, Vector2 coord) { throw new ArgumentOutOfRangeException(nameof(index)); }
 
+        VertexMaterialDelta IVertexMaterial.Subtract(IVertexMaterial baseValue) { return VertexMaterialDelta.Zero; }
+
+        void IVertexMaterial.Add(in VertexMaterialDelta delta) { /* do nothing */ }
+
         Vector4 IVertexMaterial.GetColor(int index) { throw new ArgumentOutOfRangeException(nameof(index)); }
 
         Vector2 IVertexMaterial.GetTexCoord(int index) { throw new ArgumentOutOfRangeException(nameof(index)); }

+ 395 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexMaterial.cs

@@ -66,6 +66,20 @@ namespace SharpGLTF.Geometry.VertexTypes
         /// <param name="setIndex">An index from 0 to <see cref="MaxTextCoords"/>.</param>
         /// <param name="coord">A <see cref="Vector2"/> UV texture coordinate.</param>
         void SetTexCoord(int setIndex, Vector2 coord);
+
+        /// <summary>
+        /// calculates the difference between this vertex and <paramref name="baseValue"/>
+        /// </summary>
+        /// <param name="baseValue">The other vertex.</param>
+        /// <returns>The <see cref="VertexMaterialDelta"/> value to subtract.</returns>
+        VertexMaterialDelta Subtract(IVertexMaterial baseValue);
+
+        /// <summary>
+        /// Adds a vertex delta to this value.
+        /// <para><b>⚠️ USE ONLY ON UNBOXED VALUES ⚠️</b></para>
+        /// </summary>
+        /// <param name="delta">The <see cref="VertexMaterialDelta"/> value to add.</param>
+        void Add(in VertexMaterialDelta delta);
     }
 
     /// <summary>
@@ -128,6 +142,18 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
+        /// <inheritdoc/>
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            return new VertexMaterialDelta((VertexColor1)baseValue, this);
+        }
+
+        /// <inheritdoc/>
+        public void Add(in VertexMaterialDelta delta)
+        {
+            this.Color += delta.Color0Delta;
+        }
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color) { if (setIndex == 0) this.Color = color; }
 
         void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord) { }
@@ -213,6 +239,19 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
+        /// <inheritdoc/>
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            return new VertexMaterialDelta((VertexColor2)baseValue, this);
+        }
+
+        /// <inheritdoc/>
+        public void Add(in VertexMaterialDelta delta)
+        {
+            this.Color0 += delta.Color0Delta;
+            this.Color1 += delta.Color1Delta;
+        }
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color)
         {
             if (setIndex == 0) this.Color0 = color;
@@ -294,6 +333,18 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
+        /// <inheritdoc/>
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            return new VertexMaterialDelta((VertexTexture1)baseValue, this);
+        }
+
+        /// <inheritdoc/>
+        public void Add(in VertexMaterialDelta delta)
+        {
+            this.TexCoord += delta.TexCoord0Delta;
+        }
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color) { }
 
         void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord) { if (setIndex == 0) this.TexCoord = coord; }
@@ -378,6 +429,19 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
+        /// <inheritdoc/>
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            return new VertexMaterialDelta((VertexTexture2)baseValue, this);
+        }
+
+        /// <inheritdoc/>
+        public void Add(in VertexMaterialDelta delta)
+        {
+            this.TexCoord0 += delta.TexCoord0Delta;
+            this.TexCoord1 += delta.TexCoord1Delta;
+        }
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color) { }
 
         void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord)
@@ -467,6 +531,18 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
+        /// <inheritdoc/>
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            return new VertexMaterialDelta((VertexColor1Texture1)baseValue, this);
+        }
+
+        /// <inheritdoc/>
+        public void Add(in VertexMaterialDelta delta)
+        {
+            this.Color += delta.Color0Delta;
+            this.TexCoord += delta.TexCoord0Delta;
+        }
         void IVertexMaterial.SetColor(int setIndex, Vector4 color) { if (setIndex == 0) this.Color = color; }
 
         void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord) { if (setIndex == 0) this.TexCoord = coord; }
@@ -557,6 +633,20 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
+        /// <inheritdoc/>
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            return new VertexMaterialDelta((VertexColor1Texture2)baseValue, this);
+        }
+
+        /// <inheritdoc/>
+        public void Add(in VertexMaterialDelta delta)
+        {
+            this.Color += delta.Color0Delta;
+            this.TexCoord0 += delta.TexCoord0Delta;
+            this.TexCoord1 += delta.TexCoord1Delta;
+        }
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color) { if (setIndex == 0) this.Color = color; }
 
         void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord)
@@ -656,6 +746,20 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
+        /// <inheritdoc/>
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            return new VertexMaterialDelta((VertexColor2Texture1)baseValue, this);
+        }
+
+        /// <inheritdoc/>
+        public void Add(in VertexMaterialDelta delta)
+        {
+            this.Color0 += delta.Color0Delta;
+            this.Color1 += delta.Color1Delta;
+            this.TexCoord += delta.TexCoord0Delta;
+        }
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color)
         {
             if (setIndex == 0) this.Color0 = color;
@@ -766,6 +870,21 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
+        /// <inheritdoc/>
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            return new VertexMaterialDelta((VertexColor2Texture2)baseValue, this);
+        }
+
+        /// <inheritdoc/>
+        public void Add(in VertexMaterialDelta delta)
+        {
+            this.Color0 += delta.Color0Delta;
+            this.Color1 += delta.Color1Delta;
+            this.TexCoord0 += delta.TexCoord0Delta;
+            this.TexCoord1 += delta.TexCoord1Delta;
+        }
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color)
         {
             if (setIndex == 0) this.Color0 = color;
@@ -802,4 +921,280 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #endregion
     }
+
+    /// <summary>
+    /// Defines a Vertex attribute with two material Colors and two Texture Coordinates.
+    /// </summary>
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
+    public struct VertexMaterialDelta : IVertexMaterial, IEquatable<VertexMaterialDelta>
+    {
+        #region debug
+
+        private string _GetDebuggerDisplay() => $"ΔC₀:{Color0Delta} ΔC₁:{Color1Delta} ΔUV₀:{TexCoord0Delta}  ΔUV₁:{TexCoord1Delta}";
+
+        #endregion
+
+        #region constructors
+
+        public static implicit operator VertexMaterialDelta(in (Vector4 Color0Delta, Vector4 Color1Delta, Vector2 TextCoord0Delta, Vector2 TextCoord1Delta) tuple)
+        {
+            return new VertexMaterialDelta(tuple.Color0Delta, tuple.Color1Delta, tuple.TextCoord0Delta, tuple.TextCoord1Delta);
+        }
+
+        public VertexMaterialDelta(IVertexMaterial src)
+        {
+            Guard.NotNull(src, nameof(src));
+
+            MaxColors = src.MaxColors;
+            MaxTextCoords = src.MaxTextCoords;
+
+            if (src.MaxColors == 0)
+            {
+                Color0Delta = Vector4.Zero;
+                Color1Delta = Vector4.Zero;
+            }
+            else if (src.MaxColors == 1)
+            {
+                Color0Delta = src.GetColor(0);
+                Color1Delta = Vector4.Zero;
+            }
+            else
+            {
+                Color0Delta = src.GetColor(0);
+                Color1Delta = src.GetColor(1);
+            }
+
+            if (src.MaxTextCoords == 0)
+            {
+                TexCoord0Delta = Vector2.Zero;
+                TexCoord1Delta = Vector2.Zero;
+            }
+            else if (src.MaxTextCoords == 1)
+            {
+                TexCoord0Delta = src.GetTexCoord(0);
+                TexCoord1Delta = Vector2.Zero;
+            }
+            else
+            {
+                TexCoord0Delta = src.GetTexCoord(0);
+                TexCoord1Delta = src.GetTexCoord(1);
+            }
+        }
+
+        public VertexMaterialDelta(in Vector4 color0Delta, in Vector4 color1Delta, in Vector2 texCoord0Delta, in Vector2 texCoord1Delta)
+        {
+            MaxColors = 2;
+            MaxTextCoords = 2;
+
+            Color0Delta = color0Delta;
+            Color1Delta = color1Delta;
+            TexCoord0Delta = texCoord0Delta;
+            TexCoord1Delta = texCoord1Delta;
+        }
+
+        internal VertexMaterialDelta(in VertexColor1 rootVal, in VertexColor1 morphVal)
+        {
+            MaxColors = 1;
+            MaxTextCoords = 0;
+
+            Color0Delta = morphVal.Color - rootVal.Color;
+            Color1Delta = Vector4.Zero;
+            TexCoord0Delta = Vector2.Zero;
+            TexCoord1Delta = Vector2.Zero;
+        }
+
+        internal VertexMaterialDelta(in VertexColor2 rootVal, in VertexColor2 morphVal)
+        {
+            MaxColors = 2;
+            MaxTextCoords = 0;
+
+            Color0Delta = morphVal.Color0 - rootVal.Color0;
+            Color1Delta = morphVal.Color1 - rootVal.Color1;
+            TexCoord0Delta = Vector2.Zero;
+            TexCoord1Delta = Vector2.Zero;
+        }
+
+        internal VertexMaterialDelta(in VertexTexture1 rootVal, in VertexTexture1 morphVal)
+        {
+            MaxColors = 0;
+            MaxTextCoords = 1;
+
+            Color0Delta = Vector4.Zero;
+            Color1Delta = Vector4.Zero;
+            TexCoord0Delta = morphVal.TexCoord - rootVal.TexCoord;
+            TexCoord1Delta = Vector2.Zero;
+        }
+
+        internal VertexMaterialDelta(in VertexTexture2 rootVal, in VertexTexture2 morphVal)
+        {
+            MaxColors = 0;
+            MaxTextCoords = 2;
+
+            Color0Delta = Vector4.Zero;
+            Color1Delta = Vector4.Zero;
+            TexCoord0Delta = morphVal.TexCoord0 - rootVal.TexCoord0;
+            TexCoord1Delta = morphVal.TexCoord1 - rootVal.TexCoord1;
+        }
+
+        internal VertexMaterialDelta(in VertexColor1Texture1 rootVal, in VertexColor1Texture1 morphVal)
+        {
+            MaxColors = 1;
+            MaxTextCoords = 1;
+
+            Color0Delta = morphVal.Color - rootVal.Color;
+            Color1Delta = Vector4.Zero;
+            TexCoord0Delta = morphVal.TexCoord - rootVal.TexCoord;
+            TexCoord1Delta = Vector2.Zero;
+        }
+
+        internal VertexMaterialDelta(in VertexColor2Texture1 rootVal, in VertexColor2Texture1 morphVal)
+        {
+            MaxColors = 2;
+            MaxTextCoords = 1;
+
+            Color0Delta = morphVal.Color0 - rootVal.Color0;
+            Color1Delta = morphVal.Color1 - rootVal.Color1;
+            TexCoord0Delta = morphVal.TexCoord - rootVal.TexCoord;
+            TexCoord1Delta = Vector2.Zero;
+        }
+
+        internal VertexMaterialDelta(in VertexColor1Texture2 rootVal, in VertexColor1Texture2 morphVal)
+        {
+            MaxColors = 1;
+            MaxTextCoords = 2;
+
+            Color0Delta = morphVal.Color - rootVal.Color;
+            Color1Delta = Vector4.Zero;
+            TexCoord0Delta = morphVal.TexCoord0 - rootVal.TexCoord0;
+            TexCoord1Delta = morphVal.TexCoord1 - rootVal.TexCoord1;
+        }
+
+        internal VertexMaterialDelta(in VertexColor2Texture2 rootVal, in VertexColor2Texture2 morphVal)
+        {
+            MaxColors = 2;
+            MaxTextCoords = 2;
+
+            Color0Delta = morphVal.Color0 - rootVal.Color0;
+            Color1Delta = morphVal.Color1 - rootVal.Color1;
+            TexCoord0Delta = morphVal.TexCoord0 - rootVal.TexCoord0;
+            TexCoord1Delta = morphVal.TexCoord1 - rootVal.TexCoord1;
+        }
+
+        internal VertexMaterialDelta(in VertexMaterialDelta rootVal, in VertexMaterialDelta morphVal)
+        {
+            if (rootVal.MaxColors != morphVal.MaxColors)
+                throw new ArgumentException("MaxColors do not match!");
+            if (rootVal.MaxTextCoords != morphVal.MaxTextCoords)
+                throw new ArgumentException("MaxTextCoords do not match!");
+
+            MaxColors = rootVal.MaxColors;
+            MaxTextCoords = rootVal.MaxTextCoords;
+
+            Color0Delta = morphVal.Color0Delta - rootVal.Color0Delta;
+            Color1Delta = morphVal.Color1Delta - rootVal.Color1Delta;
+            TexCoord0Delta = morphVal.TexCoord0Delta - rootVal.TexCoord0Delta;
+            TexCoord1Delta = morphVal.TexCoord1Delta - rootVal.TexCoord1Delta;
+        }
+
+        #endregion
+
+        #region data
+
+        public static VertexMaterialDelta Zero => new VertexMaterialDelta(Vector4.Zero, Vector4.Zero, Vector2.Zero, Vector2.Zero);
+
+        [VertexAttribute("COLOR_0DELTA", ENCODING.UNSIGNED_BYTE, true)]
+        public Vector4 Color0Delta;
+
+        [VertexAttribute("COLOR_1DELTA", ENCODING.UNSIGNED_BYTE, true)]
+        public Vector4 Color1Delta;
+
+        [VertexAttribute("TEXCOORD_0DELTA")]
+        public Vector2 TexCoord0Delta;
+
+        [VertexAttribute("TEXCOORD_1DELTA")]
+        public Vector2 TexCoord1Delta;
+
+        /// <inheritdoc/>
+        public int MaxColors { get; }
+
+        /// <inheritdoc/>
+        public int MaxTextCoords { get; }
+
+        public override bool Equals(object obj) { return obj is VertexMaterialDelta other && AreEqual(this, other); }
+        public bool Equals(VertexMaterialDelta other) { return AreEqual(this, other); }
+        public static bool operator ==(in VertexMaterialDelta a, in VertexMaterialDelta b) { return AreEqual(a, b); }
+        public static bool operator !=(in VertexMaterialDelta a, in VertexMaterialDelta b) { return !AreEqual(a, b); }
+
+        public static bool AreEqual(in VertexMaterialDelta a, in VertexMaterialDelta b)
+        {
+            return a.Color0Delta == b.Color0Delta && a.Color1Delta == b.Color1Delta && a.TexCoord0Delta == b.TexCoord0Delta && a.TexCoord1Delta == b.TexCoord1Delta;
+        }
+
+        public override int GetHashCode() { return Color0Delta.GetHashCode() ^ Color1Delta.GetHashCode() ^ TexCoord0Delta.GetHashCode() ^ TexCoord1Delta.GetHashCode(); }
+
+        #endregion
+
+        #region API
+
+        /// <inheritdoc/>
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            return new VertexMaterialDelta((VertexMaterialDelta)baseValue, this);
+        }
+
+        /// <inheritdoc/>
+        public void Add(in VertexMaterialDelta delta)
+        {
+            this.Color0Delta += delta.Color0Delta;
+            this.Color1Delta += delta.Color1Delta;
+            this.TexCoord0Delta += delta.TexCoord0Delta;
+            this.TexCoord1Delta += delta.TexCoord1Delta;
+        }
+
+        void IVertexMaterial.SetColor(int setIndex, Vector4 color)
+        {
+            SetColor(setIndex, color);
+        }
+
+        void SetColor(int setIndex, Vector4 color)
+        {
+            if (setIndex == 0) this.Color0Delta = color;
+            if (setIndex == 1) this.Color1Delta = color;
+        }
+
+        void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord)
+        {
+            SetTexCoord(setIndex, coord);
+        }
+
+        void SetTexCoord(int setIndex, Vector2 coord)
+        {
+            if (setIndex == 0) this.TexCoord0Delta = coord;
+            if (setIndex == 1) this.TexCoord1Delta = coord;
+        }
+
+        /// <inheritdoc/>
+        public Vector4 GetColor(int index)
+        {
+            switch (index)
+            {
+                case 0: return this.Color0Delta;
+                case 1: return this.Color1Delta;
+                default: throw new ArgumentOutOfRangeException(nameof(index));
+            }
+        }
+
+        /// <inheritdoc/>
+        public Vector2 GetTexCoord(int index)
+        {
+            switch (index)
+            {
+                case 0: return this.TexCoord0Delta;
+                case 1: return this.TexCoord1Delta;
+                default: throw new ArgumentOutOfRangeException(nameof(index));
+            }
+        }
+
+        #endregion
+    }
 }

+ 6 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.Accessors.cs

@@ -178,11 +178,17 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (attributeName == "COLOR_2") return v => { var m = v.GetMaterial(); return m.MaxColors <= 2 ? Vector4.One : m.GetColor(2); };
             if (attributeName == "COLOR_3") return v => { var m = v.GetMaterial(); return m.MaxColors <= 3 ? Vector4.One : m.GetColor(3); };
 
+            if (attributeName == "COLOR_0DELTA") return v => { var m = v.GetMaterial(); return m.MaxColors <= 0 ? Vector4.Zero : m.GetColor(0); };
+            if (attributeName == "COLOR_1DELTA") return v => { var m = v.GetMaterial(); return m.MaxColors <= 1 ? Vector4.Zero : m.GetColor(1); };
+
             if (attributeName == "TEXCOORD_0") return v => { var m = v.GetMaterial(); return m.MaxTextCoords <= 0 ? Vector2.Zero : m.GetTexCoord(0); };
             if (attributeName == "TEXCOORD_1") return v => { var m = v.GetMaterial(); return m.MaxTextCoords <= 1 ? Vector2.Zero : m.GetTexCoord(1); };
             if (attributeName == "TEXCOORD_2") return v => { var m = v.GetMaterial(); return m.MaxTextCoords <= 2 ? Vector2.Zero : m.GetTexCoord(2); };
             if (attributeName == "TEXCOORD_3") return v => { var m = v.GetMaterial(); return m.MaxTextCoords <= 3 ? Vector2.Zero : m.GetTexCoord(3); };
 
+            if (attributeName == "TEXCOORD_0DELTA") return v => { var m = v.GetMaterial(); return m.MaxTextCoords <= 0 ? Vector2.Zero : m.GetTexCoord(0); };
+            if (attributeName == "TEXCOORD_1DELTA") return v => { var m = v.GetMaterial(); return m.MaxTextCoords <= 1 ? Vector2.Zero : m.GetTexCoord(1); };
+
             if (attributeName == "JOINTS_0") return v => v.GetSkinning().JointsLow;
             if (attributeName == "JOINTS_1") return v => v.GetSkinning().JointsHigh;
 

+ 2 - 1
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -873,7 +873,8 @@ namespace SharpGLTF.Schema2
 
                     var v = srcTarget.GetVertex(dstPrim.VertexType, kvp.Key);
 
-                    dstPrim.SetVertexDelta(tidx, kvp.Value, new VertexGeometryDelta(v.GetGeometry()) );
+                    dstPrim.SetVertexDelta(tidx, kvp.Value,
+                        new VertexGeometryDelta(v.GetGeometry()), new VertexMaterialDelta(v.GetMaterial()));
                 }
             }
         }

+ 79 - 0
tests/SharpGLTF.ThirdParty.Tests/AceCebovTests.cs

@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using SharpGLTF.Geometry;
+using SharpGLTF.Geometry.VertexTypes;
+using SharpGLTF.Materials;
+using VB = SharpGLTF.Geometry.VertexBuilder<SharpGLTF.Geometry.VertexTypes.VertexPosition,
+    SharpGLTF.Geometry.VertexTypes.VertexColor1,
+    SharpGLTF.Geometry.VertexTypes.VertexEmpty>;
+
+namespace SharpGLTF.ThirdParty
+{
+    internal class AceCebovTests
+    {
+        [Test]
+        public void TestMorphTargets()
+        {
+            // create material
+            var material = new MaterialBuilder()
+                .WithDoubleSide(true)
+                .WithMetallicRoughnessShader();
+
+            // create a mesh with two primitives, one for each material
+
+            var triangle = new MeshBuilder<VertexPosition, VertexColor1>("mesh");
+
+            var prim = triangle.UsePrimitive(material);
+            var redColor = new Vector4(1f, 0f, 0f, 1f);
+            prim.AddTriangle(new VB(new VertexPosition(-10, 0, 0), redColor),
+                new VB(new VertexPosition(10, 0, 0), redColor),
+                new VB(new VertexPosition(0, 10, 0), redColor));
+
+            // create a scene
+            var scene = new SharpGLTF.Scenes.SceneBuilder();
+
+            scene.AddRigidMesh(triangle, Matrix4x4.Identity);
+
+            var greenColor = new Vector4(0f, 1f, 0f, 1f);
+
+            // create a morph target that will move the triangle in X axis by 1 unit
+            // and change the color from red to green
+            var morphTargetBuilder = triangle.UseMorphTarget(0);
+            foreach (var vertexPosition in morphTargetBuilder.Vertices)
+            {
+                var newVertexPosition = vertexPosition;
+
+                // new vertex position is moved in X direction by 1 unit
+                newVertexPosition.Position.X += 1;
+
+                morphTargetBuilder.SetVertex(vertexPosition, new VB(newVertexPosition,
+                    // morph to green color
+                    greenColor));
+            }
+
+            Assert.AreEqual(3, morphTargetBuilder.Vertices.Count);
+
+            // save the model in different formats
+            var model = scene.ToGltf2();
+
+            var animation = model.CreateAnimation();
+
+            // create a morph channel
+            animation.CreateMorphChannel(model.LogicalNodes[0],
+                new Dictionary<float, float[]>
+                {
+                    { 0f, new[] { 0f } },
+                    { 1f, new[] { 1f } }
+                }, 1);
+
+            // save the model in different formats
+            model.SaveGLB("mesh.glb");
+            model.SaveGLTF("mesh.gltf");
+        }
+    }
+}

+ 37 - 0
tests/SharpGLTF.Toolkit.Tests/Geometry/VertexTypes/CustomVertices.cs

@@ -65,6 +65,26 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
+        /// <inheritdoc/>
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            return this.Subtract((VertexColor1Texture1Custom1)baseValue);
+        }
+
+        /// <inheritdoc cref="Subtract(IVertexMaterial)"/>
+        public VertexMaterialDelta Subtract(in VertexColor1Texture1Custom1 baseValue)
+        {
+            return new VertexMaterialDelta(this.Color - baseValue.Color, Vector4.Zero,
+                this.TexCoord - baseValue.TexCoord, Vector2.Zero);
+        }
+
+        /// <inheritdoc/>
+        public void Add(in VertexMaterialDelta delta)
+        {
+            this.Color += delta.Color0Delta;
+            this.TexCoord += delta.TexCoord0Delta;
+        }
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color) { if (setIndex == 0) this.Color = color; }
 
         void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord) { if (setIndex == 0) this.TexCoord = coord; }
@@ -164,6 +184,23 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
+        /// <inheritdoc/>
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            return this.Subtract((VertexColor1Texture1Custom1)baseValue);
+        }
+
+        /// <inheritdoc cref="Subtract(IVertexMaterial)"/>
+        public VertexMaterialDelta Subtract(in VertexColor1Texture1Custom1 baseValue)
+        {
+            return new VertexMaterialDelta(Vector4.Zero, Vector4.Zero, Vector2.Zero, Vector2.Zero);
+        }
+
+        /// <inheritdoc/>
+        public void Add(in VertexMaterialDelta delta)
+        {
+        }
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color) { }
 
         void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord) { }