Browse Source

improved morph targets support in multi primitive scenarios

Vicente Penades 3 years ago
parent
commit
094bea879f

+ 12 - 5
src/SharpGLTF.Core/Schema2/gltf.MeshPrimitive.cs

@@ -227,22 +227,29 @@ namespace SharpGLTF.Schema2
 
 
         #region API - Morph Targets
         #region API - Morph Targets
 
 
-        public IReadOnlyDictionary<String, Accessor> GetMorphTargetAccessors(int idx)
+        public IReadOnlyDictionary<String, Accessor> GetMorphTargetAccessors(int targetIdx)
         {
         {
-            return new ReadOnlyLinqDictionary<String, int, Accessor>(_targets[idx], alidx => this.LogicalParent.LogicalParent.LogicalAccessors[alidx]);
+            Guard.MustBeGreaterThanOrEqualTo(targetIdx, 0, nameof(targetIdx));
+            return new ReadOnlyLinqDictionary<String, int, Accessor>(_targets[targetIdx], alidx => this.LogicalParent.LogicalParent.LogicalAccessors[alidx]);
         }
         }
 
 
-        public void SetMorphTargetAccessors(int idx, IReadOnlyDictionary<String, Accessor> accessors)
+        public void SetMorphTargetAccessors(int targetIdx, IReadOnlyDictionary<String, Accessor> accessors)
         {
         {
+            Guard.MustBeGreaterThanOrEqualTo(targetIdx, 0, nameof(targetIdx));
             Guard.NotNull(accessors, nameof(accessors));
             Guard.NotNull(accessors, nameof(accessors));
+
+            // morph targets must have at least one valid accessor.
+            // See https://github.com/KhronosGroup/glTF/issues/2154
+            Guard.MustBeGreaterThan(accessors.Count, 0, nameof(accessors));
+
             foreach (var kvp in accessors)
             foreach (var kvp in accessors)
             {
             {
                 Guard.MustShareLogicalParent(this.LogicalParent, kvp.Value, nameof(accessors));
                 Guard.MustShareLogicalParent(this.LogicalParent, kvp.Value, nameof(accessors));
             }
             }
 
 
-            while (_targets.Count <= idx) _targets.Add(new Dictionary<string, int>());
+            while (_targets.Count <= targetIdx) _targets.Add(new Dictionary<string, int>());
 
 
-            var target = _targets[idx];
+            var target = _targets[targetIdx];
 
 
             target.Clear();
             target.Clear();
 
 

+ 34 - 18
src/SharpGLTF.Toolkit/Geometry/Packed/PackedMeshBuilder.cs

@@ -42,40 +42,56 @@ namespace SharpGLTF.Geometry
 
 
             foreach (var srcMesh in meshBuilders)
             foreach (var srcMesh in meshBuilders)
             {
             {
+                // Gather all the primitives of the mesh
+
                 var srcPrims = srcMesh
                 var srcPrims = srcMesh
                     .Primitives
                     .Primitives
-                    .Where(item => item.Vertices.Count > 0);
+                    .Where(item => item.Vertices.Count > 0)
+                    .ToList();                
 
 
-                var dstMesh = new PackedMeshBuilder<TMaterial>(srcMesh.Name, srcMesh.Extras);
+                // identify morph target attributes in use                
 
 
-                bool useStrided = settings.UseStridedBuffers;
-                vertexEncodings.ColorEncoding = null;
+                var morphTargetsAttributes = new HashSet<string>();
 
 
                 foreach (var srcPrim in srcPrims)
                 foreach (var srcPrim in srcPrims)
                 {
                 {
-                    if (srcPrim.MorphTargets.Count > 0)
-                    {
-                        // if the primitive has morphing, it is better not to use strided vertex buffers.
-                        useStrided = false;
-
-                        // if the primitive has color morphing, we need to ensure the vertex
-                        // color attribute encoding is FLOAT to allow negative delta values.
-                        if (PackedPrimitiveBuilder<TMaterial>._HasColorMorphTargets(srcPrim))
-                        {
-                            vertexEncodings.ColorEncoding = EncodingType.FLOAT;
-                        }
-                    }
+                    srcPrim._GatherMorphTargetAttributes(morphTargetsAttributes);                    
                 }
                 }
 
 
+                // adjust vertex encoding
+
+                if (morphTargetsAttributes.Count > 0)
+                {
+                    // if any primitive has morph targets, it is better not to use strided vertex buffers.
+                    settings.UseStridedBuffers = false;
+                }
+
+                bool hasColorMorph = morphTargetsAttributes.Contains("COLOR_0DELTA")
+                    || morphTargetsAttributes.Contains("COLOR_1DELTA")
+                    || morphTargetsAttributes.Contains("COLOR_2DELTA")
+                    || morphTargetsAttributes.Contains("COLOR_3DELTA");
+
+                // if any primitive has color morphing, we need to ensure the vertex
+                // color attribute encoding is FLOAT to allow negative delta values.
+
+                vertexEncodings.ColorEncoding = hasColorMorph
+                    ? EncodingType.FLOAT
+                    : (EncodingType?)null;
+
+                // Create a packed mesh
+
+                var dstMesh = new PackedMeshBuilder<TMaterial>(srcMesh.Name, srcMesh.Extras);
+
                 foreach (var srcPrim in srcPrims)
                 foreach (var srcPrim in srcPrims)
                 {
                 {
                     var dstPrim = dstMesh.AddPrimitive(srcPrim.Material, srcPrim.VerticesPerPrimitive);
                     var dstPrim = dstMesh.AddPrimitive(srcPrim.Material, srcPrim.VerticesPerPrimitive);
 
 
-                    if (useStrided) dstPrim.SetStridedVertices(srcPrim, vertexEncodings);
+                    if (settings.UseStridedBuffers) dstPrim.SetStridedVertices(srcPrim, vertexEncodings);
                     else dstPrim.SetStreamedVertices(srcPrim, vertexEncodings);
                     else dstPrim.SetStreamedVertices(srcPrim, vertexEncodings);
 
 
                     dstPrim.SetIndices(srcPrim, indexEncoding);
                     dstPrim.SetIndices(srcPrim, indexEncoding);
-                    dstPrim.SetMorphTargets(srcPrim, vertexEncodings);
+
+                    if (morphTargetsAttributes.Count > 0) dstPrim.SetMorphTargets(srcPrim, vertexEncodings, morphTargetsAttributes);
                 }
                 }
 
 
                 yield return dstMesh;
                 yield return dstMesh;

+ 50 - 84
src/SharpGLTF.Toolkit/Geometry/Packed/PackedPrimitiveBuilder.cs

@@ -5,6 +5,8 @@ using System.Text;
 
 
 using SharpGLTF.Schema2;
 using SharpGLTF.Schema2;
 
 
+using MACCESSOR = SharpGLTF.Memory.MemoryAccessor;
+
 namespace SharpGLTF.Geometry
 namespace SharpGLTF.Geometry
 {
 {
     sealed class PackedPrimitiveBuilder<TMaterial>
     sealed class PackedPrimitiveBuilder<TMaterial>
@@ -27,11 +29,12 @@ namespace SharpGLTF.Geometry
         private readonly int _VerticesPerPrimitive;
         private readonly int _VerticesPerPrimitive;
 
 
         private Type _StridedVertexType;
         private Type _StridedVertexType;
-        private Memory.MemoryAccessor[] _VertexAccessors;
 
 
-        private Memory.MemoryAccessor _IndexAccessors;
+        private MACCESSOR[] _VertexAccessors;
+
+        private MACCESSOR _IndexAccessors;
 
 
-        private readonly List<Memory.MemoryAccessor[]> _MorphTargets = new List<Memory.MemoryAccessor[]>();
+        private readonly List<MACCESSOR[]> _MorphTargets = new List<MACCESSOR[]>();
 
 
         #endregion
         #endregion
 
 
@@ -58,7 +61,7 @@ namespace SharpGLTF.Geometry
                         .Select(item => item.Name)
                         .Select(item => item.Name)
                         .ToList();
                         .ToList();
 
 
-            var vAccessors = new List<Memory.MemoryAccessor>();
+            var vAccessors = new List<MACCESSOR>();
             GuardAll.MustBeEqualTo(vAccessors.Select(item => item.Attribute.ByteOffset), 0, nameof(vAccessors));
             GuardAll.MustBeEqualTo(vAccessors.Select(item => item.Attribute.ByteOffset), 0, nameof(vAccessors));
             GuardAll.MustBeEqualTo(vAccessors.Select(item => item.Attribute.ByteStride), 0, nameof(vAccessors));
             GuardAll.MustBeEqualTo(vAccessors.Select(item => item.Attribute.ByteStride), 0, nameof(vAccessors));
 
 
@@ -72,7 +75,7 @@ namespace SharpGLTF.Geometry
 
 
             _VertexAccessors = vAccessors.ToArray();
             _VertexAccessors = vAccessors.ToArray();
 
 
-            Memory.MemoryAccessor.SanitizeVertexAttributes(_VertexAccessors);
+            MACCESSOR.SanitizeVertexAttributes(_VertexAccessors);
         }
         }
 
 
         public void SetIndices(IPrimitiveReader<TMaterial> srcPrim, EncodingType encoding)
         public void SetIndices(IPrimitiveReader<TMaterial> srcPrim, EncodingType encoding)
@@ -85,28 +88,9 @@ namespace SharpGLTF.Geometry
             else Guard.NotNull(iAccessor, nameof(iAccessor));
             else Guard.NotNull(iAccessor, nameof(iAccessor));
 
 
             _IndexAccessors = iAccessor;
             _IndexAccessors = iAccessor;
-        }
-
-        public static bool _HasColorMorphTargets(IPrimitiveReader<TMaterial> srcPrim)
-        {
-            var vertexEncodings = new PackedEncoding();
-            vertexEncodings.ColorEncoding = EncodingType.FLOAT;
-
-            for (int i = 0; i < srcPrim.MorphTargets.Count; ++i)
-            {
-                var mtv = srcPrim.MorphTargets[i].GetMorphTargetVertices(srcPrim.Vertices.Count);
-
-                var c0Accessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "COLOR_0DELTA", vertexEncodings);
-                if (c0Accessor != null && c0Accessor.Data.Any(b => b != 0)) return true;
-
-                var c1Accessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "COLOR_1DELTA", vertexEncodings);
-                if (c1Accessor != null && c1Accessor.Data.Any(b => b != 0)) return true;
-            }
-
-            return false;
-        }
+        }        
 
 
-        public void SetMorphTargets(IPrimitiveReader<TMaterial> srcPrim, PackedEncoding vertexEncodings)
+        public void SetMorphTargets(IPrimitiveReader<TMaterial> srcPrim, PackedEncoding vertexEncodings, ISet<string> morphTargetAttributes)
         {
         {
             bool hasPositions = _VertexAccessors.Any(item => item.Attribute.Name == "POSITION");
             bool hasPositions = _VertexAccessors.Any(item => item.Attribute.Name == "POSITION");
             bool hasNormals = _VertexAccessors.Any(item => item.Attribute.Name == "NORMAL");
             bool hasNormals = _VertexAccessors.Any(item => item.Attribute.Name == "NORMAL");
@@ -114,78 +98,58 @@ namespace SharpGLTF.Geometry
 
 
             bool hasColors0 = _VertexAccessors.Any(item => item.Attribute.Name == "COLOR_0");
             bool hasColors0 = _VertexAccessors.Any(item => item.Attribute.Name == "COLOR_0");
             bool hasColors1 = _VertexAccessors.Any(item => item.Attribute.Name == "COLOR_1");
             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");
+
+            bool hasTexCoords0 = _VertexAccessors.Any(item => item.Attribute.Name == "TEXCOORD_0");
+            bool hasTexCoords1 = _VertexAccessors.Any(item => item.Attribute.Name == "TEXCOORD_1");
+            bool hasTexCoords2 = _VertexAccessors.Any(item => item.Attribute.Name == "TEXCOORD_2");
+            bool hasTexCoords3 = _VertexAccessors.Any(item => item.Attribute.Name == "TEXCOORD_3");
 
 
             for (int i = 0; i < srcPrim.MorphTargets.Count; ++i)
             for (int i = 0; i < srcPrim.MorphTargets.Count; ++i)
             {
             {
-                var mtv = srcPrim.MorphTargets[i].GetMorphTargetVertices(srcPrim.Vertices.Count);
-
-                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);
-                // 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);
+                var (pAccessor,nAccessor,tAccessor,c0Accessor, c1Accessor, uv0Accessor, uv1Accessor, uv2Accessor, uv3Accessor) = srcPrim._GetMorphTargetAccessors(i, vertexEncodings, morphTargetAttributes);
+
+                if (!hasPositions) pAccessor = null;
+                if (!hasNormals) nAccessor = null;
+                if (!hasTangents) tAccessor = null;
+                if (!hasColors0) c0Accessor = null;
+                if (!hasColors1) c1Accessor = null;
+                if (!hasTexCoords0) uv0Accessor = null;
+                if (!hasTexCoords1) uv1Accessor = null;
+                if (!hasTexCoords2) uv2Accessor = null;
+                if (!hasTexCoords3) uv3Accessor = null;
+
+                AddMorphTarget(pAccessor, nAccessor, tAccessor, c0Accessor, c1Accessor, uv0Accessor, uv1Accessor, uv2Accessor, uv3Accessor);
             }
             }
         }
         }
 
 
-        private void AddMorphTarget(params Memory.MemoryAccessor[] morphTarget)
+        private void AddMorphTarget(params MACCESSOR[] morphTarget)
         {
         {
-            morphTarget = morphTarget.Where(item => item != null)
-                .Select(item => _RemoveDelta(item))
-                .ToArray();
+            MACCESSOR _removeDeltaSuffix(MACCESSOR accessor)
+            {
+                var name = accessor.Attribute.Name;
+                if (!name.EndsWith("DELTA", StringComparison.Ordinal)) throw new InvalidOperationException();
 
 
-            _MorphTargets.Add(morphTarget);
-        }
+                name = name.Replace("DELTA", string.Empty);
 
 
-        private static Memory.MemoryAccessor _RemoveDelta(Memory.MemoryAccessor accessor)
-        {
-            var name = accessor.Attribute.Name;
-            if (!name.EndsWith("DELTA", StringComparison.Ordinal)) throw new InvalidOperationException();
+                var attr = accessor.Attribute;
+                attr.Name = name;
 
 
-            name = name.Replace("DELTA", string.Empty);
+                return new Memory.MemoryAccessor(accessor.Data, attr);
+            }
 
 
-            var attr = accessor.Attribute;
-            attr.Name = name;
+            morphTarget = morphTarget
+                .Where(item => item != null)
+                .Select(item => _removeDeltaSuffix(item))
+                .ToArray();
 
 
-            return new Memory.MemoryAccessor(accessor.Data, attr);
-        }
+            _MorphTargets.Add(morphTarget);
+        }        
 
 
         internal void CopyToMesh(Mesh dstMesh, Converter<TMaterial, Material> materialEvaluator)
         internal void CopyToMesh(Mesh dstMesh, Converter<TMaterial, Material> materialEvaluator)
         {
         {
-            if (_VerticesPerPrimitive < 1 || _VerticesPerPrimitive > 3) return;
+            if (_VerticesPerPrimitive < 1 || _VerticesPerPrimitive > 3) return;            
 
 
+            // points
             if (_VerticesPerPrimitive == 1)
             if (_VerticesPerPrimitive == 1)
             {
             {
                 var dstPrim = dstMesh.CreatePrimitive()
                 var dstPrim = dstMesh.CreatePrimitive()
@@ -197,7 +161,7 @@ namespace SharpGLTF.Geometry
 
 
                 return;
                 return;
             }
             }
-            else
+            else // lines or triangles
             {
             {
                 var pt = PrimitiveType.LINES;
                 var pt = PrimitiveType.LINES;
                 if (_VerticesPerPrimitive == 3) pt = PrimitiveType.TRIANGLES;
                 if (_VerticesPerPrimitive == 3) pt = PrimitiveType.TRIANGLES;
@@ -215,6 +179,8 @@ namespace SharpGLTF.Geometry
         {
         {
             for (int i = 0; i < _MorphTargets.Count; ++i)
             for (int i = 0; i < _MorphTargets.Count; ++i)
             {
             {
+                if (_MorphTargets[i] == null || _MorphTargets[i].Length == 0) throw new InvalidOperationException("all morph targets must have at least one accessor");
+
                 dstPrim.WithMorphTargetAccessors(i, _MorphTargets[i]);
                 dstPrim.WithMorphTargetAccessors(i, _MorphTargets[i]);
             }
             }
         }
         }
@@ -231,7 +197,7 @@ namespace SharpGLTF.Geometry
             _MergeSequentialVertices(primitives.SelectMany(p => p._MorphTargets));
             _MergeSequentialVertices(primitives.SelectMany(p => p._MorphTargets));
         }
         }
 
 
-        private static void _MergeSequentialVertices(IEnumerable<Memory.MemoryAccessor[]> primitives)
+        private static void _MergeSequentialVertices(IEnumerable<MACCESSOR[]> primitives)
         {
         {
             var vertexBuffers = new Dictionary<string, PackedBuffer>();
             var vertexBuffers = new Dictionary<string, PackedBuffer>();
 
 

+ 67 - 0
src/SharpGLTF.Toolkit/Geometry/Packed/_PrimitiveHelpers.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using SharpGLTF.Schema2;
+
+using MACCESSOR = SharpGLTF.Memory.MemoryAccessor;
+
+namespace SharpGLTF.Geometry
+{
+    internal static class _PackedPrimitiveHelpers
+    {
+        public static void _GatherMorphTargetAttributes<TMaterial>(this IPrimitiveReader<TMaterial> srcPrim, HashSet<string> attributes)
+        {
+            var vertexEncodings = new PackedEncoding();
+            vertexEncodings.ColorEncoding = EncodingType.FLOAT;
+
+            for (int i = 0; i < srcPrim.MorphTargets.Count; ++i)
+            {
+                var accessors = srcPrim._GetMorphTargetAccessors(i, vertexEncodings, new HashSet<string>());
+
+                if (accessors.Pos != null) attributes.Add("POSITIONDELTA");
+                if (accessors.Nrm != null) attributes.Add("NORMALDELTA");
+                if (accessors.Tgt != null) attributes.Add("TANGENTDELTA");
+
+                if (accessors.Col0 != null) attributes.Add("COLOR_0DELTA");
+                if (accessors.Col1 != null) attributes.Add("COLOR_1DELTA");
+
+                if (accessors.Tuv0 != null) attributes.Add("TEXCOORD_0DELTA");
+                if (accessors.Tuv1 != null) attributes.Add("TEXCOORD_1DELTA");
+                if (accessors.Tuv2 != null) attributes.Add("TEXCOORD_2DELTA");
+                if (accessors.Tuv3 != null) attributes.Add("TEXCOORD_3DELTA");
+            }
+        }
+
+        public static (MACCESSOR Pos, MACCESSOR Nrm, MACCESSOR Tgt, MACCESSOR Col0, MACCESSOR Col1, MACCESSOR Tuv0, MACCESSOR Tuv1, MACCESSOR Tuv2, MACCESSOR Tuv3) _GetMorphTargetAccessors<TMaterial>(this IPrimitiveReader<TMaterial> srcPrim, int morphTargetIdx, PackedEncoding vertexEncodings, ISet<string> requiredAttributes)
+        {
+            var mtv = srcPrim.MorphTargets[morphTargetIdx].GetMorphTargetVertices(srcPrim.Vertices.Count);
+
+            MACCESSOR _createAccessor(string attributeName)
+            {
+                var accessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, attributeName, vertexEncodings);
+                if (accessor == null) return null;
+
+                if (requiredAttributes.Contains(attributeName)) return accessor; // required attribute, even if all deltas are zero
+
+                // if delta is all 0s, and it's not required, then do not use the accessor
+                return accessor.Data.All(b => b == 0) ? null : accessor;
+            }
+
+            var pAccessor = _createAccessor("POSITIONDELTA");
+            var nAccessor = _createAccessor("NORMALDELTA");
+            var tAccessor = _createAccessor("TANGENTDELTA");
+
+            var c0Accessor = _createAccessor("COLOR_0DELTA");
+            var c1Accessor = _createAccessor("COLOR_1DELTA");
+
+            var uv0Accessor = _createAccessor("TEXCOORD_0DELTA");
+            var uv1Accessor = _createAccessor("TEXCOORD_1DELTA");
+            var uv2Accessor = _createAccessor("TEXCOORD_2DELTA");
+            var uv3Accessor = _createAccessor("TEXCOORD_3DELTA");
+
+            return (pAccessor, nAccessor, tAccessor, c0Accessor, c1Accessor, uv0Accessor, uv1Accessor, uv2Accessor, uv3Accessor);
+        }
+    }
+}

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

@@ -193,6 +193,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
             if (attributeName == "COLOR_0DELTA") return v => { var m = v.GetMaterial(); return m.MaxColors <= 0 ? Vector4.Zero : m.GetColor(0); };
             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 == "COLOR_1DELTA") return v => { var m = v.GetMaterial(); return m.MaxColors <= 1 ? Vector4.Zero : m.GetColor(1); };
+            if (attributeName == "COLOR_2DELTA") return v => { var m = v.GetMaterial(); return m.MaxColors <= 2 ? Vector4.Zero : m.GetColor(2); };
+            if (attributeName == "COLOR_3DELTA") return v => { var m = v.GetMaterial(); return m.MaxColors <= 3 ? Vector4.Zero : m.GetColor(3); };
 
 
             if (attributeName == "TEXCOORD_0") return v => { var m = v.GetMaterial(); return m.MaxTextCoords <= 0 ? Vector2.Zero : m.GetTexCoord(0); };
             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_1") return v => { var m = v.GetMaterial(); return m.MaxTextCoords <= 1 ? Vector2.Zero : m.GetTexCoord(1); };
@@ -201,6 +203,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
             if (attributeName == "TEXCOORD_0DELTA") return v => { var m = v.GetMaterial(); return m.MaxTextCoords <= 0 ? Vector2.Zero : m.GetTexCoord(0); };
             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 == "TEXCOORD_1DELTA") return v => { var m = v.GetMaterial(); return m.MaxTextCoords <= 1 ? Vector2.Zero : m.GetTexCoord(1); };
+            if (attributeName == "TEXCOORD_2DELTA") return v => { var m = v.GetMaterial(); return m.MaxTextCoords <= 2 ? Vector2.Zero : m.GetTexCoord(2); };
+            if (attributeName == "TEXCOORD_3DELTA") return v => { var m = v.GetMaterial(); return m.MaxTextCoords <= 3 ? Vector2.Zero : m.GetTexCoord(3); };
 
 
             if (attributeName == "JOINTS_0") return v => v.GetSkinning().JointsLow;
             if (attributeName == "JOINTS_0") return v => v.GetSkinning().JointsLow;
             if (attributeName == "JOINTS_1") return v => v.GetSkinning().JointsHigh;
             if (attributeName == "JOINTS_1") return v => v.GetSkinning().JointsHigh;

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

@@ -315,7 +315,7 @@ namespace SharpGLTF.Schema2
             var root = primitive.LogicalParent.LogicalParent;
             var root = primitive.LogicalParent.LogicalParent;
 
 
             var accessors = memAccessors.ToDictionary(item => item.Attribute.Name, item => root.CreateVertexAccessor(item));
             var accessors = memAccessors.ToDictionary(item => item.Attribute.Name, item => root.CreateVertexAccessor(item));
-
+            
             primitive.SetMorphTargetAccessors(targetIndex, accessors);
             primitive.SetMorphTargetAccessors(targetIndex, accessors);
 
 
             return primitive;
             return primitive;

+ 102 - 0
tests/SharpGLTF.ThirdParty.Tests/SandboxTests.cs

@@ -7,11 +7,16 @@ using System.Threading.Tasks;
 
 
 using NUnit.Framework;
 using NUnit.Framework;
 
 
+using SharpGLTF.Geometry;
+using SharpGLTF.Geometry.VertexTypes;
 using SharpGLTF.Materials;
 using SharpGLTF.Materials;
 using SharpGLTF.Schema2;
 using SharpGLTF.Schema2;
+using SharpGLTF.Validation;
 
 
 namespace SharpGLTF.ThirdParty
 namespace SharpGLTF.ThirdParty
 {
 {
+    using VBColor1 = VertexBuilder<VertexPosition, VertexColor1, VertexEmpty>;
+
     [ResourcePathFormat("*\\Assets")]
     [ResourcePathFormat("*\\Assets")]
     [AttachmentPathFormat("*\\?")]
     [AttachmentPathFormat("*\\?")]
     internal class SandboxTests
     internal class SandboxTests
@@ -45,5 +50,102 @@ namespace SharpGLTF.ThirdParty
 
 
             TestContext.WriteLine(model.GetJsonPreview());
             TestContext.WriteLine(model.GetJsonPreview());
         }
         }
+
+        [Test]
+        public void TestMorphColorTargets2()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+
+            // create material
+            var material = new MaterialBuilder("mat1")
+                .WithDoubleSide(true)
+                .WithMetallicRoughnessShader();
+            var material2 = new MaterialBuilder("mat2")
+                .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 VBColor1(new VertexPosition(-10, 0, 0), redColor),
+                new VBColor1(new VertexPosition(10, 0, 0), redColor),
+                new VBColor1(new VertexPosition(0, 10, 0), redColor));
+
+            var prim2 = triangle.UsePrimitive(material2);
+            prim2.AddTriangle(new VBColor1(new VertexPosition(-10, 0, 0), redColor),
+                new VBColor1(new VertexPosition(10, 0, 0), redColor),
+                new VBColor1(new VertexPosition(0, 10, 0), redColor));
+
+            // create a morph target that will change the color from red to green only for prim2
+            var greenColor = new Vector4(0f, 1f, 0f, 1f);
+            foreach (var p in triangle.Primitives)
+            {
+                for (var i = 0; i < p.Vertices.Count; ++i)
+                {
+                    var oldVertexPosition = p.Vertices[i];
+                    var greenMat = new VertexColor1(greenColor);
+
+                    ((IPrimitiveBuilder)p).SetVertexDelta(0, i, default,
+                        ReferenceEquals(p, prim2)
+                            ? greenMat.Subtract(oldVertexPosition.Material)
+                            : VertexMaterialDelta.Zero);
+                }
+            }
+
+            // create a scene
+            var scene = new Scenes.SceneBuilder();
+            scene.AddRigidMesh(triangle, Matrix4x4.Identity);
+
+            // save the model in different formats
+            var model = scene.ToGltf2();
+
+            var json = model.GetJsonPreview();
+            AttachmentInfo.From("model.json").WriteAllText(json);
+            TestContext.WriteLine(json);
+
+            model.DeepClone();
+
+
+            var animation = model.CreateAnimation();
+
+            
+
+            // create a morph channel
+            animation.CreateMorphChannel(model.LogicalNodes[0],
+                new Dictionary<float, float[]>
+                {
+                    { 0f, new[] { 0f } },
+                    { 1f, new[] { 1f } }
+                }, 1);
+
+            // evaluate triangles at animation 0.5, and get the color of the first pixel of the first triangle
+            var triangles = Schema2.Toolkit
+                .EvaluateTriangles(model.DefaultScene, null, model.LogicalAnimations[0], 0.5f)
+                .ToArray();
+
+            // Assert
+            var morphedColor = triangles[1].A.GetMaterial().GetColor(0);
+            Assert.AreEqual(0.5f, morphedColor.X);
+            Assert.AreEqual(0.5f, morphedColor.Y);
+            Assert.AreEqual(0, morphedColor.Z);
+            Assert.AreEqual(1, morphedColor.W);
+
+            morphedColor = triangles[0].A.GetMaterial().GetColor(0);
+            Assert.AreEqual(redColor, morphedColor);
+
+
+            
+
+            var fF= AttachmentInfo
+                .From("ColorMorphingMultiPrim.gltf")
+                .WriteFile(f => model.Save(f.FullName));
+
+            AttachmentInfo.From("ColorMorphingMultiPrim.glb")
+                .WriteFile(f => model.Save(f.FullName));
+            
+        }
     }
     }
 }
 }