Browse Source

+Progress on improving MeshBuilder and VertexBuilder
upgraded nuget packages

Vicente Penades 5 years ago
parent
commit
03a6f3edeb

+ 1 - 1
build/SharpGLTF.CodeGen/SharpGLTF.CodeGen.csproj

@@ -8,7 +8,7 @@
 
 
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="LibGit2Sharp" Version="0.26.2" />    
     <PackageReference Include="LibGit2Sharp" Version="0.26.2" />    
-    <PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="10.1.23" />
+    <PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="10.1.24" />
   </ItemGroup>
   </ItemGroup>
 
 
 </Project>
 </Project>

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

@@ -170,7 +170,41 @@ namespace SharpGLTF.Geometry
         public void AddMesh(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, Func<TMaterial, TMaterial> materialTransform, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
         public void AddMesh(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, Func<TMaterial, TMaterial> materialTransform, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
         {
         {
             if (mesh == null) return;
             if (mesh == null) return;
+
+            if (materialTransform == null) materialTransform = m => m;
+            if (vertexTransform == null) vertexTransform = v => v;
+
+            AddMesh<TMaterial>(mesh, materialTransform, v => vertexTransform(VertexBuilder<TvG, TvM, TvS>.CreateFrom(v)));
+        }
+
+        public void AddMesh(IMeshBuilder<TMaterial> mesh, Matrix4x4 vertexTransform)
+        {
+            if (mesh == null) return;
+
+            if (vertexTransform == Matrix4x4.Identity)
+            {
+                AddMesh<TMaterial>(mesh, m => m, null);
+                return;
+            }
+
+            AddMesh<TMaterial>(mesh, m => m, v => VertexBuilder<TvG, TvM, TvS>.CreateFrom(v).TransformedBy(vertexTransform));
+        }
+
+        public void AddMesh(IMeshBuilder<TMaterial> mesh, Func<TMaterial, TMaterial> materialTransform, Func<IVertexBuilder, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
+        {
+            if (mesh == null) return;
+
+            if (materialTransform == null) materialTransform = m => m;
+
+            AddMesh<TMaterial>(mesh, materialTransform, vertexTransform);
+        }
+
+        public void AddMesh<TSourceMaterial>(IMeshBuilder<TSourceMaterial> mesh, Func<TSourceMaterial, TMaterial> materialTransform, Func<IVertexBuilder, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
+        {
+            if (mesh == null) return;
+
             Guard.NotNull(materialTransform, nameof(materialTransform));
             Guard.NotNull(materialTransform, nameof(materialTransform));
+            if (vertexTransform == null) vertexTransform = v => VertexBuilder<TvG, TvM, TvS>.CreateFrom(v);
 
 
             foreach (var p in mesh.Primitives)
             foreach (var p in mesh.Primitives)
             {
             {

+ 19 - 12
src/SharpGLTF.Toolkit/Geometry/MorphTargetBuilder.cs

@@ -11,16 +11,23 @@ namespace SharpGLTF.Geometry
     public interface IPrimitiveMorphTargetReader
     public interface IPrimitiveMorphTargetReader
     {
     {
         /// <summary>
         /// <summary>
-        /// Gets the collection of vertex indices that have deltas.
+        /// Gets the collection of vertex indices that have morph target deltas.
         /// </summary>
         /// </summary>
         /// <returns>A collection of vertex indices.</returns>
         /// <returns>A collection of vertex indices.</returns>
         IReadOnlyCollection<int> GetTargetIndices();
         IReadOnlyCollection<int> GetTargetIndices();
 
 
+        /// <summary>
+        /// Gets the vertex for the given <paramref name="vertexIndex"/> morphed by the current morph target (if any).
+        /// </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);
+
         /// <summary>
         /// <summary>
         /// Gets the <see cref="VertexGeometryDelta"/> of a given vertex for a given morph target.
         /// Gets the <see cref="VertexGeometryDelta"/> of a given vertex for a given morph target.
         /// </summary>
         /// </summary>
         /// <param name="vertexIndex">The index of the vertex.</param>
         /// <param name="vertexIndex">The index of the vertex.</param>
-        /// <returns>A Vertex delta.</returns>
+        /// <returns>A Vertex delta (Morphed vertex minus base vertex).</returns>
         VertexGeometryDelta GetVertexDelta(int vertexIndex);
         VertexGeometryDelta GetVertexDelta(int vertexIndex);
     }
     }
 
 
@@ -82,14 +89,14 @@ namespace SharpGLTF.Geometry
             _SetVertex(vertexIndex, vertex);
             _SetVertex(vertexIndex, vertex);
         }
         }
 
 
-        public TvG GetVertex(int vertexIndex)
+        IVertexGeometry IPrimitiveMorphTargetReader.GetVertex(int vertexIndex)
         {
         {
-            if (_MorphVertices.TryGetValue(vertexIndex, out TvG value))
-            {
-                return value;
-            }
+            return _MorphVertices.TryGetValue(vertexIndex, out TvG value) ? value : _BaseVertexFunc(vertexIndex);
+        }
 
 
-            return _BaseVertexFunc(vertexIndex);
+        public TvG GetVertex(int vertexIndex)
+        {
+            return _MorphVertices.TryGetValue(vertexIndex, out TvG value) ? value : _BaseVertexFunc(vertexIndex);
         }
         }
 
 
         public void SetVertex(int vertexIndex, TvG value)
         public void SetVertex(int vertexIndex, TvG value)
@@ -129,15 +136,15 @@ namespace SharpGLTF.Geometry
             }
             }
         }
         }
 
 
-        internal void SetMorphTargets(PrimitiveMorphTargetBuilder<TvG> other, IReadOnlyDictionary<int, int> vertexMap, Func<TvG, TvG> vertexFunc)
+        internal void SetMorphTargets(IPrimitiveMorphTargetReader other, IReadOnlyDictionary<int, int> vertexMap, Func<IVertexGeometry, TvG> vertexFunc)
         {
         {
+            Guard.NotNull(vertexFunc, nameof(vertexFunc));
+
             var indices = other.GetTargetIndices();
             var indices = other.GetTargetIndices();
 
 
             foreach (var srcVidx in indices)
             foreach (var srcVidx in indices)
             {
             {
-                var g = other.GetVertex(srcVidx);
-
-                if (vertexFunc != null) g = vertexFunc(g);
+                var g = vertexFunc(other.GetVertex(srcVidx));
 
 
                 var dstVidx = srcVidx;
                 var dstVidx = srcVidx;
 
 

+ 54 - 36
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -200,6 +200,14 @@ namespace SharpGLTF.Geometry
 
 
         #region API
         #region API
 
 
+        public void Validate()
+        {
+            foreach (var v in _Vertices)
+            {
+                v.Validate();
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Checks if <paramref name="vertex"/> is a compatible vertex and casts it, or converts it if it is not.
         /// Checks if <paramref name="vertex"/> is a compatible vertex and casts it, or converts it if it is not.
         /// </summary>
         /// </summary>
@@ -207,12 +215,7 @@ namespace SharpGLTF.Geometry
         /// <returns>A vertex compatible with this primitive.</returns>
         /// <returns>A vertex compatible with this primitive.</returns>
         private static VertexBuilder<TvG, TvM, TvS> ConvertVertex(IVertexBuilder vertex)
         private static VertexBuilder<TvG, TvM, TvS> ConvertVertex(IVertexBuilder vertex)
         {
         {
-            Guard.NotNull(vertex, nameof(vertex));
-
-            var vv = vertex.ConvertTo<TvG, TvM, TvS>();
-            System.Diagnostics.Debug.Assert(vv.Position == vertex.GetGeometry().GetPosition());
-
-            return vv;
+            return VertexBuilder<TvG, TvM, TvS>.CreateFrom(vertex);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -299,31 +302,24 @@ namespace SharpGLTF.Geometry
             return AddQuadrangle(ConvertVertex(a), ConvertVertex(b), ConvertVertex(c), ConvertVertex(d));
             return AddQuadrangle(ConvertVertex(a), ConvertVertex(b), ConvertVertex(c), ConvertVertex(d));
         }
         }
 
 
-        public void Validate()
-        {
-            foreach (var v in _Vertices)
-            {
-                v.Validate();
-            }
-        }
-
-        public void TransformVertices(Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransformFunc)
+        internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransformFunc)
         {
         {
-            _Vertices.TransformVertices(vertexTransformFunc);
+            if (primitive == null) return;
 
 
-            TvG geoFunc(TvG g) => vertexTransformFunc(new VertexBuilder<TvG, TvM, TvS>(g, default, default(TvS))).Geometry;
+            if (vertexTransformFunc == null) vertexTransformFunc = v => v;
 
 
-            foreach (var mt in _MorphTargets) mt.TransformVertices(geoFunc);
+            AddPrimitive<TMaterial>(primitive, v => vertexTransformFunc((VertexBuilder<TvG, TvM, TvS>)v));
         }
         }
 
 
-        internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransformFunc)
+        internal void AddPrimitive<TAnyMaterial>(IPrimitiveReader<TAnyMaterial> primitive, Func<IVertexBuilder, VertexBuilder<TvG, TvM, TvS>> vertexTransformFunc)
         {
         {
             if (primitive == null) return;
             if (primitive == null) return;
 
 
-            if (vertexTransformFunc == null) vertexTransformFunc = v => v;
+            Guard.NotNull(vertexTransformFunc, nameof(vertexTransformFunc));
 
 
-            // vertex-vertex map so we can know where to set the morph targets.
-            var vmap = new Dictionary<int, int>();
+            // if the input primitive has morph targets, we need to keep track of the new index location
+            // of the vertices in the destination primitive, so we can retarget the mopth target indices.
+            var vmap = primitive.MorphTargets.Count == 0 ? null : new Dictionary<int, int>();
 
 
             if (this.VerticesPerPrimitive == 1)
             if (this.VerticesPerPrimitive == 1)
             {
             {
@@ -333,7 +329,7 @@ namespace SharpGLTF.Geometry
 
 
                     var idx = AddPoint(vrt);
                     var idx = AddPoint(vrt);
 
 
-                    vmap[src] = idx;
+                    if (vmap != null) vmap[src] = idx;
                 }
                 }
             }
             }
 
 
@@ -346,8 +342,11 @@ namespace SharpGLTF.Geometry
 
 
                     var (dstA, dstB) = AddLine(vrtA, vrtB);
                     var (dstA, dstB) = AddLine(vrtA, vrtB);
 
 
-                    vmap[srcA] = dstA;
-                    vmap[srcB] = dstB;
+                    if (vmap != null)
+                    {
+                        vmap[srcA] = dstA;
+                        vmap[srcB] = dstB;
+                    }
                 }
                 }
             }
             }
 
 
@@ -364,30 +363,49 @@ namespace SharpGLTF.Geometry
                         var vrtD = vertexTransformFunc(primitive.Vertices[srcD.Value]);
                         var vrtD = vertexTransformFunc(primitive.Vertices[srcD.Value]);
                         var (dstA, dstB, dstC, dstD) = AddQuadrangle(vrtA, vrtB, vrtC, vrtD);
                         var (dstA, dstB, dstC, dstD) = AddQuadrangle(vrtA, vrtB, vrtC, vrtD);
 
 
-                        vmap[srcA] = dstA;
-                        vmap[srcB] = dstB;
-                        vmap[srcC] = dstC;
-                        vmap[srcD.Value] = dstD;
+                        if (vmap != null)
+                        {
+                            vmap[srcA] = dstA;
+                            vmap[srcB] = dstB;
+                            vmap[srcC] = dstC;
+                            vmap[srcD.Value] = dstD;
+                        }
                     }
                     }
                     else
                     else
                     {
                     {
                         var (dstA, dstB, dstC) = AddTriangle(vrtA, vrtB, vrtC);
                         var (dstA, dstB, dstC) = AddTriangle(vrtA, vrtB, vrtC);
 
 
-                        vmap[srcA] = dstA;
-                        vmap[srcB] = dstB;
-                        vmap[srcC] = dstC;
+                        if (vmap != null)
+                        {
+                            vmap[srcA] = dstA;
+                            vmap[srcB] = dstB;
+                            vmap[srcC] = dstC;
+                        }
                     }
                     }
                 }
                 }
             }
             }
 
 
-            TvG geoFunc(TvG g) => vertexTransformFunc(new VertexBuilder<TvG, TvM, TvS>(g, default, default(TvS))).Geometry;
-
-            for (int i = 0; i < primitive._MorphTargets.Count; ++i)
+            if (vmap != null)
             {
             {
-                _UseMorphTarget(i).SetMorphTargets(primitive._MorphTargets[i], vmap, geoFunc);
+                TvG geoTransformFunc(IVertexGeometry g) => vertexTransformFunc(new VertexBuilder(g)).Geometry;
+
+                for (int i = 0; i < primitive.MorphTargets.Count; ++i)
+                {
+                    var smt = primitive.MorphTargets[i];
+                    _UseMorphTarget(i).SetMorphTargets(smt, vmap, geoTransformFunc);
+                }
             }
             }
         }
         }
 
 
+        public void TransformVertices(Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransformFunc)
+        {
+            _Vertices.TransformVertices(vertexTransformFunc);
+
+            TvG geoFunc(TvG g) => vertexTransformFunc(new VertexBuilder<TvG, TvM, TvS>(g, default, default(TvS))).Geometry;
+
+            foreach (var mt in _MorphTargets) mt.TransformVertices(geoFunc);
+        }
+
         #endregion
         #endregion
 
 
         #region abstract API
         #region abstract API

+ 27 - 29
src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs

@@ -311,13 +311,7 @@ namespace SharpGLTF.Geometry
             var m = GetVertexMaterial<VertexColor2Texture2>(index);
             var m = GetVertexMaterial<VertexColor2Texture2>(index);
             var s = GetVertexSkinning<VertexJoints8>(index);
             var s = GetVertexSkinning<VertexJoints8>(index);
 
 
-            var v = (IVertexBuilder)Activator.CreateInstance(vertexType);
-
-            v.SetGeometry(g);
-            v.SetMaterial(m);
-            v.SetSkinning(s);
-
-            return v;
+            return new VertexBuilder(g, m, s).ConvertToType(vertexType);
         }
         }
 
 
         public VertexBuilder<TvG, TvM, VertexEmpty> GetVertex<TvG, TvM>(int index)
         public VertexBuilder<TvG, TvM, VertexEmpty> GetVertex<TvG, TvM>(int index)
@@ -346,6 +340,32 @@ namespace SharpGLTF.Geometry
 
 
         #region utilites
         #region utilites
 
 
+        public static void CalculateSmoothNormals(IReadOnlyList<(VertexBufferColumns Vertices, IEnumerable<(int A, int B, int C)> Indices)> primitives)
+        {
+            Guard.NotNull(primitives, nameof(primitives));
+
+            var agents = primitives
+                .Select(item => new _NormalTangentAgent(item.Vertices, item.Indices))
+                .ToList();
+
+            VertexNormalsFactory.CalculateSmoothNormals(agents);
+        }
+
+        public static void CalculateTangents(IReadOnlyList<(VertexBufferColumns Vertices, IEnumerable<(int A, int B, int C)> Indices)> primitives)
+        {
+            Guard.NotNull(primitives, nameof(primitives));
+
+            var agents = primitives
+                .Select(item => new _NormalTangentAgent(item.Vertices, item.Indices))
+                .ToList();
+
+            VertexTangentsFactory.CalculateTangents(agents);
+        }
+
+        #endregion
+
+        #region nested types
+
         struct _NormalTangentAgent : VertexNormalsFactory.IMeshPrimitive, VertexTangentsFactory.IMeshPrimitive
         struct _NormalTangentAgent : VertexNormalsFactory.IMeshPrimitive, VertexTangentsFactory.IMeshPrimitive
         {
         {
             public _NormalTangentAgent(VertexBufferColumns vertices, IEnumerable<(int A, int B, int C)> indices)
             public _NormalTangentAgent(VertexBufferColumns vertices, IEnumerable<(int A, int B, int C)> indices)
@@ -382,28 +402,6 @@ namespace SharpGLTF.Geometry
             }
             }
         }
         }
 
 
-        public static void CalculateSmoothNormals(IReadOnlyList<(VertexBufferColumns Vertices, IEnumerable<(int A, int B, int C)> Indices)> primitives)
-        {
-            Guard.NotNull(primitives, nameof(primitives));
-
-            var agents = primitives
-                .Select(item => new _NormalTangentAgent(item.Vertices, item.Indices))
-                .ToList();
-
-            VertexNormalsFactory.CalculateSmoothNormals(agents);
-        }
-
-        public static void CalculateTangents(IReadOnlyList<(VertexBufferColumns Vertices, IEnumerable<(int A, int B, int C)> Indices)> primitives)
-        {
-            Guard.NotNull(primitives, nameof(primitives));
-
-            var agents = primitives
-                .Select(item => new _NormalTangentAgent(item.Vertices, item.Indices))
-                .ToList();
-
-            VertexTangentsFactory.CalculateTangents(agents);
-        }
-
         #endregion
         #endregion
     }
     }
 }
 }

+ 88 - 24
src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs

@@ -15,29 +15,24 @@ namespace SharpGLTF.Geometry
 
 
         /// <summary>
         /// <summary>
         /// Applies a <see cref="IVertexGeometry"/> set to this <see cref="VertexBuilder{TvG, TvM, TvS}"/>
         /// Applies a <see cref="IVertexGeometry"/> set to this <see cref="VertexBuilder{TvG, TvM, TvS}"/>
-        /// Remember we work with struct types; <see href="https://blogs.msdn.microsoft.com/abhinaba/2005/10/05/c-structs-and-interface/"/>
+        /// Remember we're working with struct types; <see href="https://blogs.msdn.microsoft.com/abhinaba/2005/10/05/c-structs-and-interface/"/>
         /// </summary>
         /// </summary>
         /// <param name="geometry">A <see cref="IVertexGeometry"/> set.</param>
         /// <param name="geometry">A <see cref="IVertexGeometry"/> set.</param>
         void SetGeometry(IVertexGeometry geometry);
         void SetGeometry(IVertexGeometry geometry);
 
 
         /// <summary>
         /// <summary>
         /// Applies a <see cref="IVertexMaterial"/> set to this <see cref="VertexBuilder{TvG, TvM, TvS}"/>
         /// Applies a <see cref="IVertexMaterial"/> set to this <see cref="VertexBuilder{TvG, TvM, TvS}"/>
-        /// Remember we work with struct types; <see href="https://blogs.msdn.microsoft.com/abhinaba/2005/10/05/c-structs-and-interface/"/>
+        /// Remember we're working with struct types; <see href="https://blogs.msdn.microsoft.com/abhinaba/2005/10/05/c-structs-and-interface/"/>
         /// </summary>
         /// </summary>
         /// <param name="material">A <see cref="IVertexMaterial"/> set.</param>
         /// <param name="material">A <see cref="IVertexMaterial"/> set.</param>
         void SetMaterial(IVertexMaterial material);
         void SetMaterial(IVertexMaterial material);
 
 
         /// <summary>
         /// <summary>
         /// Applies a <see cref="IVertexSkinning"/> set to this <see cref="VertexBuilder{TvG, TvM, TvS}"/>
         /// Applies a <see cref="IVertexSkinning"/> set to this <see cref="VertexBuilder{TvG, TvM, TvS}"/>
-        /// Remember we work with struct types; <see href="https://blogs.msdn.microsoft.com/abhinaba/2005/10/05/c-structs-and-interface/"/>
+        /// Remember we're working with struct types; <see href="https://blogs.msdn.microsoft.com/abhinaba/2005/10/05/c-structs-and-interface/"/>
         /// </summary>
         /// </summary>
         /// <param name="skinning">A <see cref="IVertexSkinning"/> set.</param>
         /// <param name="skinning">A <see cref="IVertexSkinning"/> set.</param>
         void SetSkinning(IVertexSkinning skinning);
         void SetSkinning(IVertexSkinning skinning);
-
-        VertexBuilder<TvPP, TvMM, TvSS> ConvertTo<TvPP, TvMM, TvSS>()
-            where TvPP : struct, IVertexGeometry
-            where TvMM : struct, IVertexMaterial
-            where TvSS : struct, IVertexSkinning;
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -77,7 +72,7 @@ namespace SharpGLTF.Geometry
 
 
         internal string _GetDebuggerDisplay()
         internal string _GetDebuggerDisplay()
         {
         {
-            var txt = "Vertex ";
+            var txt = "Vertex";
 
 
             txt += " " + _GetDebuggerDisplayTextFrom(Geometry);
             txt += " " + _GetDebuggerDisplayTextFrom(Geometry);
             txt += " " + _GetDebuggerDisplayTextFrom(Material);
             txt += " " + _GetDebuggerDisplayTextFrom(Material);
@@ -88,6 +83,8 @@ namespace SharpGLTF.Geometry
 
 
         private static string _GetDebuggerDisplayTextFrom(Object o)
         private static string _GetDebuggerDisplayTextFrom(Object o)
         {
         {
+            if (o is VertexEmpty) return string.Empty;
+
             var bindings = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance;
             var bindings = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance;
 
 
             var method = o.GetType().GetMethod("_GetDebuggerDisplay", bindings);
             var method = o.GetType().GetMethod("_GetDebuggerDisplay", bindings);
@@ -248,6 +245,21 @@ namespace SharpGLTF.Geometry
             return v;
             return v;
         }
         }
 
 
+        public static VertexBuilder<TvG, TvM, TvS> CreateFrom(IVertexBuilder src)
+        {
+            if (src is VertexBuilder<TvG, TvM, TvS> srcTyped) return srcTyped;
+
+            Guard.NotNull(src, nameof(src));
+
+            var dst = default(VertexBuilder<TvG, TvM, TvS>);
+
+            dst.Geometry = src.GetGeometry().ConvertToGeometry<TvG>();
+            dst.Material = src.GetMaterial().ConvertToMaterial<TvM>();
+            dst.Skinning = src.GetSkinning().ConvertToSkinning<TvS>();
+
+            return dst;
+        }
+
         #pragma warning restore CA1000 // Do not declare static members on generic types
         #pragma warning restore CA1000 // Do not declare static members on generic types
 
 
         #endregion
         #endregion
@@ -342,6 +354,13 @@ namespace SharpGLTF.Geometry
             return v;
             return v;
         }
         }
 
 
+        public VertexBuilder<TvG, TvM, TvS> TransformedBy(in Matrix4x4 transform)
+        {
+            var clone = this;
+            clone.Geometry.ApplyTransform(transform);
+            return clone;
+        }
+
         public void Validate()
         public void Validate()
         {
         {
             Geometry.Validate();
             Geometry.Validate();
@@ -349,18 +368,6 @@ namespace SharpGLTF.Geometry
             Skinning.Validate();
             Skinning.Validate();
         }
         }
 
 
-        public VertexBuilder<TvPP, TvMM, TvSS> ConvertTo<TvPP, TvMM, TvSS>()
-            where TvPP : struct, IVertexGeometry
-            where TvMM : struct, IVertexMaterial
-            where TvSS : struct, IVertexSkinning
-        {
-            var p = Geometry.ConvertToGeometry<TvPP>();
-            var m = Material.ConvertToMaterial<TvMM>();
-            var s = Skinning.ConvertToSkinning<TvSS>();
-
-            return new VertexBuilder<TvPP, TvMM, TvSS>(p, m, s);
-        }
-
         #pragma warning disable CA1000 // Do not declare static members on generic types
         #pragma warning disable CA1000 // Do not declare static members on generic types
 
 
         public static MeshBuilder<TMaterial, TvG, TvM, TvS> CreateCompatibleMesh<TMaterial>(string name = null)
         public static MeshBuilder<TMaterial, TvG, TvM, TvS> CreateCompatibleMesh<TMaterial>(string name = null)
@@ -384,21 +391,78 @@ namespace SharpGLTF.Geometry
         void IVertexBuilder.SetGeometry(IVertexGeometry geometry)
         void IVertexBuilder.SetGeometry(IVertexGeometry geometry)
         {
         {
             Guard.NotNull(geometry, nameof(geometry));
             Guard.NotNull(geometry, nameof(geometry));
-            this.Geometry = geometry.GetType() == typeof(TvG) ? (TvG)geometry : geometry.ConvertToGeometry<TvG>();
+            this.Geometry = geometry.ConvertToGeometry<TvG>();
         }
         }
 
 
         void IVertexBuilder.SetMaterial(IVertexMaterial material)
         void IVertexBuilder.SetMaterial(IVertexMaterial material)
         {
         {
             Guard.NotNull(material, nameof(material));
             Guard.NotNull(material, nameof(material));
-            this.Material = material.GetType() == typeof(TvM) ? (TvM)material : material.ConvertToMaterial<TvM>();
+            this.Material = material.ConvertToMaterial<TvM>();
         }
         }
 
 
         void IVertexBuilder.SetSkinning(IVertexSkinning skinning)
         void IVertexBuilder.SetSkinning(IVertexSkinning skinning)
         {
         {
             Guard.NotNull(skinning, nameof(skinning));
             Guard.NotNull(skinning, nameof(skinning));
-            this.Skinning = skinning.GetType() == typeof(TvS) ? (TvS)skinning : skinning.ConvertToSkinning<TvS>();
+            this.Skinning = skinning.ConvertToSkinning<TvS>();
         }
         }
 
 
         #endregion
         #endregion
     }
     }
+
+    struct VertexBuilder : IVertexBuilder
+    {
+        #region constructor
+
+        public VertexBuilder(IVertexGeometry g)
+        {
+            this.Geometry = g;
+            this.Material = null;
+            this.Skinning = null;
+        }
+
+        public VertexBuilder(IVertexGeometry g, IVertexMaterial m, IVertexSkinning s)
+        {
+            this.Geometry = g;
+            this.Material = m;
+            this.Skinning = s;
+        }
+
+        #endregion
+
+        #region data
+
+        public IVertexGeometry Geometry;
+        public IVertexMaterial Material;
+        public IVertexSkinning Skinning;
+
+        #endregion
+
+        #region API
+
+        public IVertexGeometry GetGeometry() { return Geometry; }
+
+        public IVertexMaterial GetMaterial() { return Material; }
+
+        public IVertexSkinning GetSkinning() { return Skinning; }
+
+        public void SetGeometry(IVertexGeometry geometry) { this.Geometry = geometry; }
+
+        public void SetMaterial(IVertexMaterial material) { this.Material = material; }
+
+        public void SetSkinning(IVertexSkinning skinning) { this.Skinning = skinning; }
+
+        public IVertexBuilder ConvertToType(Type vertexType)
+        {
+            var v = (IVertexBuilder)Activator.CreateInstance(vertexType);
+
+            v.SetGeometry(Geometry);
+            v.SetMaterial(Material);
+            v.SetSkinning(Skinning);
+
+            return v;
+        }
+
+        #endregion
+
+    }
 }
 }

+ 5 - 2
src/SharpGLTF.Toolkit/Geometry/VertexTangentsFactory.cs

@@ -6,9 +6,9 @@ using System.Text;
 
 
 namespace SharpGLTF.Geometry
 namespace SharpGLTF.Geometry
 {
 {
-    using VERTEXKEY = System.ValueTuple<System.Numerics.Vector3, System.Numerics.Vector3, System.Numerics.Vector2>;
+    using VERTEXKEY = System.ValueTuple<Vector3, Vector3, Vector2>;
 
 
-    class VertexTangentsFactory
+    static class VertexTangentsFactory
     {
     {
         // https://gamedev.stackexchange.com/questions/128023/how-does-mikktspace-work-for-calculating-the-tangent-space-during-normal-mapping
         // https://gamedev.stackexchange.com/questions/128023/how-does-mikktspace-work-for-calculating-the-tangent-space-during-normal-mapping
         // https://stackoverflow.com/questions/25349350/calculating-per-vertex-tangents-for-glsl
         // https://stackoverflow.com/questions/25349350/calculating-per-vertex-tangents-for-glsl
@@ -17,6 +17,9 @@ namespace SharpGLTF.Geometry
         // https://www.marti.works/calculating-tangents-for-your-mesh/
         // https://www.marti.works/calculating-tangents-for-your-mesh/
         // https://www.html5gamedevs.com/topic/34364-gltf-support-and-mikkt-space/
         // https://www.html5gamedevs.com/topic/34364-gltf-support-and-mikkt-space/
 
 
+        /// <summary>
+        /// this interface must be defined by the input primitive to which we want to add tangents
+        /// </summary>
         public interface IMeshPrimitive
         public interface IMeshPrimitive
         {
         {
             int VertexCount { get; }
             int VertexCount { get; }

+ 34 - 8
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexEmpty.cs

@@ -7,17 +7,50 @@ using SharpGLTF.Transforms;
 
 
 namespace SharpGLTF.Geometry.VertexTypes
 namespace SharpGLTF.Geometry.VertexTypes
 {
 {
+    /// <summary>
+    /// Represents an empty vertex attribute that can be used as an
+    /// empty <see cref="IVertexMaterial"/> or empty <see cref="IVertexSkinning"/>
+    /// in a <see cref="VertexBuilder{TvG, TvM, TvS}"/> structure.
+    /// </summary>
     [System.Diagnostics.DebuggerDisplay("Empty")]
     [System.Diagnostics.DebuggerDisplay("Empty")]
     public readonly struct VertexEmpty : IVertexMaterial, IVertexSkinning
     public readonly struct VertexEmpty : IVertexMaterial, IVertexSkinning
     {
     {
+        #region constructor
         public void Validate() { }
         public void Validate() { }
 
 
+        #endregion
+
+        #region data
+
+        public override bool Equals(object obj) { return obj is VertexEmpty; }
+        public bool Equals(VertexEmpty other) { return true; }
+        public static bool operator ==(in VertexEmpty a, in VertexEmpty b) { return true; }
+        public static bool operator !=(in VertexEmpty a, in VertexEmpty b) { return false; }
+        public override int GetHashCode() { return 0; }
+
+        #endregion
+
+        #region properties
+
         public int MaxBindings => 0;
         public int MaxBindings => 0;
 
 
         public int MaxColors => 0;
         public int MaxColors => 0;
 
 
         public int MaxTextCoords => 0;
         public int MaxTextCoords => 0;
 
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        Vector4 IVertexSkinning.JointsLow => Vector4.Zero;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        Vector4 IVertexSkinning.JointsHigh => Vector4.Zero;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        Vector4 IVertexSkinning.WeightsLow => Vector4.Zero;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        Vector4 IVertexSkinning.WeightsHigh => Vector4.Zero;
+
+        #endregion
+
+        #region API
+
         void IVertexMaterial.SetColor(int index, Vector4 color) { throw new ArgumentOutOfRangeException(nameof(index)); }
         void IVertexMaterial.SetColor(int index, Vector4 color) { throw new ArgumentOutOfRangeException(nameof(index)); }
 
 
         void IVertexMaterial.SetTexCoord(int index, Vector2 coord) { throw new ArgumentOutOfRangeException(nameof(index)); }
         void IVertexMaterial.SetTexCoord(int index, Vector2 coord) { throw new ArgumentOutOfRangeException(nameof(index)); }
@@ -36,13 +69,6 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public object GetCustomAttribute(string attributeName) { return null; }
         public object GetCustomAttribute(string attributeName) { return null; }
 
 
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        Vector4 IVertexSkinning.JointsLow => Vector4.Zero;
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        Vector4 IVertexSkinning.JointsHigh => Vector4.Zero;
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        Vector4 IVertexSkinning.WeightsLow => Vector4.Zero;
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        Vector4 IVertexSkinning.WeightsHigh => Vector4.Zero;
+        #endregion
     }
     }
 }
 }

+ 48 - 4
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexGeometry.cs

@@ -1,6 +1,7 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Numerics;
 using System.Numerics;
+using System.Runtime.Serialization;
 using System.Text;
 using System.Text;
 
 
 namespace SharpGLTF.Geometry.VertexTypes
 namespace SharpGLTF.Geometry.VertexTypes
@@ -28,7 +29,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// Defines a Vertex attribute with a Position.
     /// Defines a Vertex attribute with a Position.
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
-    public struct VertexPosition : IVertexGeometry
+    public struct VertexPosition : IVertexGeometry, IEquatable<VertexPosition>
     {
     {
         #region debug
         #region debug
 
 
@@ -65,6 +66,16 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         [VertexAttribute("POSITION")]
         [VertexAttribute("POSITION")]
         public Vector3 Position;
         public Vector3 Position;
+        public override bool Equals(object obj) { return obj is VertexPosition other && AreEqual(this, other); }
+        public bool Equals(VertexPosition other) { return AreEqual(this, other); }
+        public static bool operator ==(in VertexPosition a, in VertexPosition b) { return AreEqual(a, b); }
+        public static bool operator !=(in VertexPosition a, in VertexPosition b) { return !AreEqual(a, b); }
+        public static bool AreEqual(in VertexPosition a, in VertexPosition b)
+        {
+            return a.Position == b.Position;
+        }
+
+        public override int GetHashCode() { return Position.GetHashCode(); }
 
 
         #endregion
         #endregion
 
 
@@ -106,7 +117,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// Defines a Vertex attribute with a Position and a Normal.
     /// Defines a Vertex attribute with a Position and a Normal.
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
-    public struct VertexPositionNormal : IVertexGeometry
+    public struct VertexPositionNormal : IVertexGeometry, IEquatable<VertexPositionNormal>
     {
     {
         #region debug
         #region debug
 
 
@@ -151,6 +162,17 @@ namespace SharpGLTF.Geometry.VertexTypes
         [VertexAttribute("NORMAL")]
         [VertexAttribute("NORMAL")]
         public Vector3 Normal;
         public Vector3 Normal;
 
 
+        public override bool Equals(object obj) { return obj is VertexPositionNormal other && AreEqual(this, other); }
+        public bool Equals(VertexPositionNormal other) { return AreEqual(this, other); }
+        public static bool operator ==(in VertexPositionNormal a, in VertexPositionNormal b) { return AreEqual(a, b); }
+        public static bool operator !=(in VertexPositionNormal a, in VertexPositionNormal b) { return !AreEqual(a, b); }
+        public static bool AreEqual(in VertexPositionNormal a, in VertexPositionNormal b)
+        {
+            return a.Position == b.Position && a.Normal == b.Normal;
+        }
+
+        public override int GetHashCode() { return Position.GetHashCode(); }
+
         #endregion
         #endregion
 
 
         #region API
         #region API
@@ -193,7 +215,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// Defines a Vertex attribute with a Position, a Normal and a Tangent.
     /// Defines a Vertex attribute with a Position, a Normal and a Tangent.
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
-    public struct VertexPositionNormalTangent : IVertexGeometry
+    public struct VertexPositionNormalTangent : IVertexGeometry, IEquatable<VertexPositionNormalTangent>
     {
     {
         #region debug
         #region debug
 
 
@@ -237,6 +259,17 @@ namespace SharpGLTF.Geometry.VertexTypes
         [VertexAttribute("TANGENT")]
         [VertexAttribute("TANGENT")]
         public Vector4 Tangent;
         public Vector4 Tangent;
 
 
+        public override bool Equals(object obj) { return obj is VertexPositionNormalTangent other && AreEqual(this, other); }
+        public bool Equals(VertexPositionNormalTangent other) { return AreEqual(this, other); }
+        public static bool operator ==(in VertexPositionNormalTangent a, in VertexPositionNormalTangent b) { return AreEqual(a, b); }
+        public static bool operator !=(in VertexPositionNormalTangent a, in VertexPositionNormalTangent b) { return !AreEqual(a, b); }
+        public static bool AreEqual(in VertexPositionNormalTangent a, in VertexPositionNormalTangent b)
+        {
+            return a.Position == b.Position && a.Normal == b.Normal && a.Tangent == b.Tangent;
+        }
+
+        public override int GetHashCode() { return Position.GetHashCode(); }
+
         #endregion
         #endregion
 
 
         #region API
         #region API
@@ -284,7 +317,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// Defines a Vertex attribute with a Position, a Normal and a Tangent.
     /// Defines a Vertex attribute with a Position, a Normal and a Tangent.
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
-    public struct VertexGeometryDelta : IVertexGeometry
+    public struct VertexGeometryDelta : IVertexGeometry, IEquatable<VertexGeometryDelta>
     {
     {
         #region debug
         #region debug
 
 
@@ -370,6 +403,17 @@ namespace SharpGLTF.Geometry.VertexTypes
         [VertexAttribute("TANGENTDELTA")]
         [VertexAttribute("TANGENTDELTA")]
         public Vector3 TangentDelta;
         public Vector3 TangentDelta;
 
 
+        public override bool Equals(object obj) { return obj is VertexGeometryDelta other && AreEqual(this, other); }
+        public bool Equals(VertexGeometryDelta other) { return AreEqual(this, other); }
+        public static bool operator ==(in VertexGeometryDelta a, in VertexGeometryDelta b) { return AreEqual(a, b); }
+        public static bool operator !=(in VertexGeometryDelta a, in VertexGeometryDelta b) { return !AreEqual(a, b); }
+        public static bool AreEqual(in VertexGeometryDelta a, in VertexGeometryDelta b)
+        {
+            return a.PositionDelta == b.PositionDelta && a.NormalDelta == b.NormalDelta && a.TangentDelta == b.TangentDelta;
+        }
+
+        public override int GetHashCode() { return PositionDelta.GetHashCode(); }
+
         #endregion
         #endregion
 
 
         #region API
         #region API

+ 94 - 14
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexMaterial.cs

@@ -27,8 +27,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// <summary>
     /// Defines a Vertex attribute with a material Color.
     /// Defines a Vertex attribute with a material Color.
     /// </summary>
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("𝐂:{Color}")]
-    public struct VertexColor1 : IVertexMaterial
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
+    public struct VertexColor1 : IVertexMaterial, IEquatable<VertexColor1>
     {
     {
         #region debug
         #region debug
 
 
@@ -66,6 +66,18 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public int MaxTextCoords => 0;
         public int MaxTextCoords => 0;
 
 
+        public override bool Equals(object obj) { return obj is VertexColor1 other && AreEqual(this, other); }
+        public bool Equals(VertexColor1 other) { return AreEqual(this, other); }
+        public static bool operator ==(in VertexColor1 a, in VertexColor1 b) { return AreEqual(a, b); }
+        public static bool operator !=(in VertexColor1 a, in VertexColor1 b) { return !AreEqual(a, b); }
+
+        public static bool AreEqual(in VertexColor1 a, in VertexColor1 b)
+        {
+            return a.Color == b.Color;
+        }
+
+        public override int GetHashCode() { return Color.GetHashCode(); }
+
         #endregion
         #endregion
 
 
         #region API
         #region API
@@ -95,8 +107,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// <summary>
     /// Defines a Vertex attribute with a two material Colors.
     /// Defines a Vertex attribute with a two material Colors.
     /// </summary>
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("𝐂₀:{Color0} 𝐂₁:{Color1}")]
-    public struct VertexColor2 : IVertexMaterial
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
+    public struct VertexColor2 : IVertexMaterial, IEquatable<VertexColor2>
     {
     {
         #region debug
         #region debug
 
 
@@ -139,6 +151,18 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public int MaxTextCoords => 0;
         public int MaxTextCoords => 0;
 
 
+        public override bool Equals(object obj) { return obj is VertexColor2 other && AreEqual(this, other); }
+        public bool Equals(VertexColor2 other) { return AreEqual(this, other); }
+        public static bool operator ==(in VertexColor2 a, in VertexColor2 b) { return AreEqual(a, b); }
+        public static bool operator !=(in VertexColor2 a, in VertexColor2 b) { return !AreEqual(a, b); }
+
+        public static bool AreEqual(in VertexColor2 a, in VertexColor2 b)
+        {
+            return a.Color0 == b.Color0 && a.Color1 == b.Color1;
+        }
+
+        public override int GetHashCode() { return Color0.GetHashCode() ^ Color1.GetHashCode(); }
+
         #endregion
         #endregion
 
 
         #region API
         #region API
@@ -170,8 +194,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// <summary>
     /// Defines a Vertex attribute with a Texture Coordinate.
     /// Defines a Vertex attribute with a Texture Coordinate.
     /// </summary>
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("𝐔𝐕:{TexCoord}")]
-    public struct VertexTexture1 : IVertexMaterial
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
+    public struct VertexTexture1 : IVertexMaterial, IEquatable<VertexTexture1>
     {
     {
         #region debug
         #region debug
 
 
@@ -209,6 +233,17 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public int MaxTextCoords => 1;
         public int MaxTextCoords => 1;
 
 
+        public override bool Equals(object obj) { return obj is VertexTexture1 other && AreEqual(this, other); }
+        public bool Equals(VertexTexture1 other) { return AreEqual(this, other); }
+        public static bool operator ==(in VertexTexture1 a, in VertexTexture1 b) { return AreEqual(a, b); }
+        public static bool operator !=(in VertexTexture1 a, in VertexTexture1 b) { return !AreEqual(a, b); }
+        public static bool AreEqual(in VertexTexture1 a, in VertexTexture1 b)
+        {
+            return a.TexCoord == b.TexCoord;
+        }
+
+        public override int GetHashCode() { return TexCoord.GetHashCode(); }
+
         #endregion
         #endregion
 
 
         #region API
         #region API
@@ -238,8 +273,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// <summary>
     /// Defines a Vertex attribute with two Texture Coordinates.
     /// Defines a Vertex attribute with two Texture Coordinates.
     /// </summary>
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("𝐔𝐕₀:{TexCoord0} 𝐔𝐕₁:{TexCoord1}")]
-    public struct VertexTexture2 : IVertexMaterial
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
+    public struct VertexTexture2 : IVertexMaterial, IEquatable<VertexTexture2>
     {
     {
         #region debug
         #region debug
 
 
@@ -282,6 +317,17 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public int MaxTextCoords => 2;
         public int MaxTextCoords => 2;
 
 
+        public override bool Equals(object obj) { return obj is VertexTexture2 other && AreEqual(this, other); }
+        public bool Equals(VertexTexture2 other) { return AreEqual(this, other); }
+        public static bool operator ==(in VertexTexture2 a, in VertexTexture2 b) { return AreEqual(a, b); }
+        public static bool operator !=(in VertexTexture2 a, in VertexTexture2 b) { return !AreEqual(a, b); }
+        public static bool AreEqual(in VertexTexture2 a, in VertexTexture2 b)
+        {
+            return a.TexCoord0 == b.TexCoord0 && a.TexCoord1 == b.TexCoord1;
+        }
+
+        public override int GetHashCode() { return TexCoord0.GetHashCode() ^ TexCoord1.GetHashCode(); }
+
         #endregion
         #endregion
 
 
         #region API
         #region API
@@ -316,8 +362,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// <summary>
     /// Defines a Vertex attribute with a Color material and a Texture Coordinate.
     /// Defines a Vertex attribute with a Color material and a Texture Coordinate.
     /// </summary>
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("𝐂:{Color} 𝐔𝐕:{TexCoord}")]
-    public struct VertexColor1Texture1 : IVertexMaterial
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
+    public struct VertexColor1Texture1 : IVertexMaterial, IEquatable<VertexColor1Texture1>
     {
     {
         #region debug
         #region debug
 
 
@@ -360,6 +406,17 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public int MaxTextCoords => 1;
         public int MaxTextCoords => 1;
 
 
+        public override bool Equals(object obj) { return obj is VertexColor1Texture1 other && AreEqual(this, other); }
+        public bool Equals(VertexColor1Texture1 other) { return AreEqual(this, other); }
+        public static bool operator ==(in VertexColor1Texture1 a, in VertexColor1Texture1 b) { return AreEqual(a, b); }
+        public static bool operator !=(in VertexColor1Texture1 a, in VertexColor1Texture1 b) { return !AreEqual(a, b); }
+        public static bool AreEqual(in VertexColor1Texture1 a, in VertexColor1Texture1 b)
+        {
+            return a.TexCoord == b.TexCoord && a.Color == b.Color;
+        }
+
+        public override int GetHashCode() { return TexCoord.GetHashCode() ^ Color.GetHashCode(); }
+
         #endregion
         #endregion
 
 
         #region API
         #region API
@@ -390,8 +447,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// <summary>
     /// Defines a Vertex attribute with a material Colors and two Texture Coordinates.
     /// Defines a Vertex attribute with a material Colors and two Texture Coordinates.
     /// </summary>
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("𝐂:{Color} 𝐔𝐕₀:{TexCoord0} 𝐔𝐕₁:{TexCoord1}")]
-    public struct VertexColor1Texture2 : IVertexMaterial
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
+    public struct VertexColor1Texture2 : IVertexMaterial, IEquatable<VertexColor1Texture2>
     {
     {
         #region debug
         #region debug
 
 
@@ -439,6 +496,17 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public int MaxTextCoords => 2;
         public int MaxTextCoords => 2;
 
 
+        public override bool Equals(object obj) { return obj is VertexColor1Texture2 other && AreEqual(this, other); }
+        public bool Equals(VertexColor1Texture2 other) { return AreEqual(this, other); }
+        public static bool operator ==(in VertexColor1Texture2 a, in VertexColor1Texture2 b) { return AreEqual(a, b); }
+        public static bool operator !=(in VertexColor1Texture2 a, in VertexColor1Texture2 b) { return !AreEqual(a, b); }
+        public static bool AreEqual(in VertexColor1Texture2 a, in VertexColor1Texture2 b)
+        {
+            return a.Color == b.Color && a.TexCoord0 == b.TexCoord0 && a.TexCoord1 == b.TexCoord1;
+        }
+
+        public override int GetHashCode() { return Color.GetHashCode() ^ TexCoord0.GetHashCode() ^ TexCoord1.GetHashCode(); }
+
         #endregion
         #endregion
 
 
         #region API
         #region API
@@ -477,8 +545,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// <summary>
     /// Defines a Vertex attribute with two material Colors and two Texture Coordinates.
     /// Defines a Vertex attribute with two material Colors and two Texture Coordinates.
     /// </summary>
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("𝐂₀:{Color0} 𝐂₁:{Color1} 𝐔𝐕₀:{TexCoord0} 𝐔𝐕₁:{TexCoord1}")]
-    public struct VertexColor2Texture2 : IVertexMaterial
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
+    public struct VertexColor2Texture2 : IVertexMaterial, IEquatable<VertexColor2Texture2>
     {
     {
         #region debug
         #region debug
 
 
@@ -531,6 +599,18 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public int MaxTextCoords => 2;
         public int MaxTextCoords => 2;
 
 
+        public override bool Equals(object obj) { return obj is VertexColor2Texture2 other && AreEqual(this, other); }
+        public bool Equals(VertexColor2Texture2 other) { return AreEqual(this, other); }
+        public static bool operator ==(in VertexColor2Texture2 a, in VertexColor2Texture2 b) { return AreEqual(a, b); }
+        public static bool operator !=(in VertexColor2Texture2 a, in VertexColor2Texture2 b) { return !AreEqual(a, b); }
+
+        public static bool AreEqual(in VertexColor2Texture2 a, in VertexColor2Texture2 b)
+        {
+            return a.Color0 == b.Color0 && a.Color1 == b.Color1 && a.TexCoord0 == b.TexCoord0 && a.TexCoord1 == b.TexCoord1;
+        }
+
+        public override int GetHashCode() { return Color0.GetHashCode() ^ Color1.GetHashCode() ^ TexCoord0.GetHashCode() ^ TexCoord1.GetHashCode(); }
+
         #endregion
         #endregion
 
 
         #region API
         #region API

+ 3 - 3
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -104,7 +104,7 @@ namespace SharpGLTF.Geometry.VertexTypes
         public static TvP ConvertToGeometry<TvP>(this IVertexGeometry src)
         public static TvP ConvertToGeometry<TvP>(this IVertexGeometry src)
             where TvP : struct, IVertexGeometry
             where TvP : struct, IVertexGeometry
         {
         {
-            if (src.GetType() == typeof(TvP)) return (TvP)src;
+            if (src is TvP srcTyped) return srcTyped;
 
 
             var dst = default(TvP);
             var dst = default(TvP);
 
 
@@ -118,7 +118,7 @@ namespace SharpGLTF.Geometry.VertexTypes
         public static TvM ConvertToMaterial<TvM>(this IVertexMaterial src)
         public static TvM ConvertToMaterial<TvM>(this IVertexMaterial src)
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
         {
         {
-            if (src.GetType() == typeof(TvM)) return (TvM)src;
+            if (src is TvM srcTyped) return srcTyped;
 
 
             var dst = default(TvM);
             var dst = default(TvM);
 
 
@@ -156,7 +156,7 @@ namespace SharpGLTF.Geometry.VertexTypes
         public static TvS ConvertToSkinning<TvS>(this IVertexSkinning src)
         public static TvS ConvertToSkinning<TvS>(this IVertexSkinning src)
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
         {
         {
-            if (src.GetType() == typeof(TvS)) return (TvS)src;
+            if (src is TvS srcTyped) return srcTyped;
 
 
             var sparse = src.MaxBindings > 0 ? src.GetWeights() : default;
             var sparse = src.MaxBindings > 0 ? src.GetWeights() : default;
 
 

+ 17 - 20
tests/SharpGLTF.NUnit/TestFiles.cs

@@ -64,9 +64,7 @@ namespace SharpGLTF
         private static readonly string _UniVRMModelsDir;
         private static readonly string _UniVRMModelsDir;
         private static readonly string _BabylonJsMeshesDir;
         private static readonly string _BabylonJsMeshesDir;
         private static readonly string _BabylonJsPlaygroundDir;
         private static readonly string _BabylonJsPlaygroundDir;
-        private static readonly string _GeneratedModelsDir;
-
-        private static readonly string[] _BabylonJsInvalidFiles = { };        
+        private static readonly string _GeneratedModelsDir;        
 
 
         #endregion
         #endregion
 
 
@@ -187,33 +185,32 @@ namespace SharpGLTF
                 .ToList();
                 .ToList();
         }
         }
 
 
-        public static IReadOnlyList<string> GetBabylonJSValidModelsPaths()
+        public static IReadOnlyList<string> GetBabylonJSModelsPaths(bool validOrInvalid = true)
         {
         {
             _Check();
             _Check();
 
 
-            var files = GetModelPathsInDirectory(_BabylonJsMeshesDir);
-
-            return files
-                .OrderBy(item => item)
-                .Where(item => !item.Contains("\\AssetGenerator\\0.6\\"))
-                .Where(item => !item.Contains("\\Sheen\\"))
-                .Where(item => !item.Contains("\\Demos\\retargeting\\riggedMesh.glb"))
-                .Where(item => !_BabylonJsInvalidFiles.Any(ff => item.EndsWith(ff)))                
-                .ToList();
-        }
+            var skipAlways = new string[]
+            {
+                "\\Sheen\\Cloth.gltf", // still a pull request https://github.com/KhronosGroup/glTF/pull/1688
+                "\\Tests\\AssetGenerator\\",
+                "\\Demos\\retargeting\\riggedMesh.glb",
+                "\\Demos\\retargeting\\riggedMesh-recycled.glb",
+                "\\Demos\\retargeting\\riggedMesh-source.glb"
+            };
 
 
-        public static IReadOnlyList<string> GetBabylonJSInvalidModelsPaths()
-        {
-            _Check();
+            var invalid = new string[]
+            {                
+                
+            };
 
 
             var files = GetModelPathsInDirectory(_BabylonJsMeshesDir);
             var files = GetModelPathsInDirectory(_BabylonJsMeshesDir);
 
 
             return files
             return files
                 .OrderBy(item => item)
                 .OrderBy(item => item)
-                .Where(item => !item.Contains("\\AssetGenerator\\0.6\\"))
-                .Where(item => _BabylonJsInvalidFiles.Any(ff => item.EndsWith(ff)))
+                .Where(item => skipAlways.All(f => !item.Contains(f)))
+                .Where(item => validOrInvalid == invalid.All(f => !item.EndsWith(f)))
                 .ToList();
                 .ToList();
-        }
+        }        
 
 
         public static string GetPollyFileModelPath()
         public static string GetPollyFileModelPath()
         {
         {

+ 9 - 6
tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSampleTests.cs

@@ -26,7 +26,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
 
 
         #region helpers
         #region helpers
 
 
-        private static void _LoadModel(string f, bool tryFix = false)
+        private static ModelRoot _LoadModel(string f, bool tryFix = false)
         {
         {
             var perf = System.Diagnostics.Stopwatch.StartNew();
             var perf = System.Diagnostics.Stopwatch.StartNew();
 
 
@@ -70,6 +70,8 @@ namespace SharpGLTF.Schema2.LoadAndSave
             var perf_glb = perf.ElapsedMilliseconds;
             var perf_glb = perf.ElapsedMilliseconds;
 
 
             TestContext.Progress.WriteLine($"processed {f.ToShortDisplayPath()} - Load:{perf_load}ms Clone:{perf_clone}ms S.obj:{perf_wavefront}ms S.glb:{perf_glb}ms");
             TestContext.Progress.WriteLine($"processed {f.ToShortDisplayPath()} - Load:{perf_load}ms Clone:{perf_clone}ms S.obj:{perf_wavefront}ms S.glb:{perf_glb}ms");
+
+            return model;
         }
         }
 
 
         private static void _AssertAreEqual(ModelRoot a, ModelRoot b)
         private static void _AssertAreEqual(ModelRoot a, ModelRoot b)
@@ -113,7 +115,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
-            foreach (var f in TestFiles.GetBabylonJSValidModelsPaths())
+            foreach (var f in TestFiles.GetBabylonJSModelsPaths())
             {
             {
                 TestContext.Progress.WriteLine(f);
                 TestContext.Progress.WriteLine(f);
 
 
@@ -127,14 +129,15 @@ namespace SharpGLTF.Schema2.LoadAndSave
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
-            foreach (var f in TestFiles.GetBabylonJSInvalidModelsPaths())
+            foreach (var f in TestFiles.GetBabylonJSModelsPaths(false))
             {
             {
                 TestContext.Progress.WriteLine(f);
                 TestContext.Progress.WriteLine(f);
 
 
                 try
                 try
                 {
                 {
-                    var model = ModelRoot.Load(f);
-                    Assert.Fail("Should throw");
+                    var model = ModelRoot.Load(f, Validation.ValidationMode.Strict);
+                    
+                    Assert.Fail($"{f} Should throw");
                 }
                 }
                 catch(Exception ex)
                 catch(Exception ex)
                 {
                 {
@@ -356,7 +359,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
-            foreach (var f in TestFiles.GetBabylonJSValidModelsPaths())
+            foreach (var f in TestFiles.GetBabylonJSModelsPaths())
             {
             {
                 TestContext.WriteLine(f);
                 TestContext.WriteLine(f);
 
 

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

@@ -22,7 +22,7 @@
     <PackageReference Include="Mono.ApiTools" Version="5.14.0.2" />
     <PackageReference Include="Mono.ApiTools" Version="5.14.0.2" />
     -->
     -->
     
     
-    <PackageReference Include="NUnit3TestAdapter" Version="3.16.1">
+    <PackageReference Include="NUnit3TestAdapter" Version="3.17.0">
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
     </PackageReference>

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

@@ -22,7 +22,7 @@
     <PackageReference Include="Mono.ApiTools" Version="5.14.0.2" />
     <PackageReference Include="Mono.ApiTools" Version="5.14.0.2" />
     -->
     -->
 
 
-    <PackageReference Include="NUnit3TestAdapter" Version="3.16.1">
+    <PackageReference Include="NUnit3TestAdapter" Version="3.17.0">
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
     </PackageReference>