Browse Source

Added a programable. VertexProcessor mechanism that can be used either to validate or sanitize vertices (WIP)

Vicente Penades 6 years ago
parent
commit
223fd4ae62

+ 2 - 0
examples/InfiniteSkinnedTentacle/Program.cs

@@ -114,6 +114,8 @@ namespace InfiniteSkinnedTentacle
         static MESH CreateMesh(int boneCount)
         static MESH CreateMesh(int boneCount)
         {
         {
             var mesh = new MESH("skinned mesh");
             var mesh = new MESH("skinned mesh");
+            mesh.VertexPreprocessor.SetDebugPreprocessors();
+
             var prim = mesh.UsePrimitive(new SharpGLTF.Materials.MaterialBuilder("Default"));
             var prim = mesh.UsePrimitive(new SharpGLTF.Materials.MaterialBuilder("Default"));
 
 
             var a0 = default(VERTEX);
             var a0 = default(VERTEX);

+ 1 - 1
src/Shared/Guard.cs

@@ -138,7 +138,7 @@ namespace SharpGLTF
         public static void MustBeBetweenOrEqualTo<TValue>(TValue value, TValue minInclusive, TValue maxInclusive, string parameterName)
         public static void MustBeBetweenOrEqualTo<TValue>(TValue value, TValue minInclusive, TValue maxInclusive, string parameterName)
             where TValue : IComparable<TValue>
             where TValue : IComparable<TValue>
         {
         {
-            if (value.CompareTo(minInclusive) >= 0 || value.CompareTo(maxInclusive) <= 0) return;
+            if (value.CompareTo(minInclusive) >= 0 && value.CompareTo(maxInclusive) <= 0) return;
 
 
             throw new ArgumentOutOfRangeException(parameterName, $"{parameterName} {value} must be greater than or equal to {minInclusive} and less than or equal to {maxInclusive}.");
             throw new ArgumentOutOfRangeException(parameterName, $"{parameterName} {value} must be greater than or equal to {minInclusive} and less than or equal to {maxInclusive}.");
         }
         }

+ 0 - 1
src/SharpGLTF.Toolkit/Debug/DebugViews.cs

@@ -7,5 +7,4 @@ using System.Text;
 
 
 namespace SharpGLTF.Debug
 namespace SharpGLTF.Debug
 {
 {
-
 }
 }

+ 17 - 5
src/SharpGLTF.Toolkit/Geometry/MeshBuilder.cs

@@ -40,6 +40,8 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexColor1"/>,
     /// <see cref="VertexColor1"/>,
     /// <see cref="VertexTexture1"/>,
     /// <see cref="VertexTexture1"/>,
     /// <see cref="VertexColor1Texture1"/>.
     /// <see cref="VertexColor1Texture1"/>.
+    /// <see cref="VertexColor1Texture2"/>.
+    /// <see cref="VertexColor2Texture2"/>.
     /// </typeparam>
     /// </typeparam>
     /// <typeparam name="TvS">
     /// <typeparam name="TvS">
     /// The vertex fragment type with Skin Joint Weights.
     /// The vertex fragment type with Skin Joint Weights.
@@ -61,7 +63,9 @@ namespace SharpGLTF.Geometry
         {
         {
             this.Name = name;
             this.Name = name;
 
 
-            _Preprocessor = VertexBuilder<TvG, TvM, TvS>.SanitizerPreprocessor;
+            // this is the recomended preprocesor for release/production
+            _VertexPreprocessor = new VertexPreprocessor<TvG, TvM, TvS>();
+            _VertexPreprocessor.SetSanitizerPreprocessors();
         }
         }
 
 
         #endregion
         #endregion
@@ -70,16 +74,20 @@ namespace SharpGLTF.Geometry
 
 
         private readonly Dictionary<(TMaterial, int), PrimitiveBuilder<TMaterial, TvG, TvM, TvS>> _Primitives = new Dictionary<(TMaterial, int), PrimitiveBuilder<TMaterial, TvG, TvM, TvS>>();
         private readonly Dictionary<(TMaterial, int), PrimitiveBuilder<TMaterial, TvG, TvM, TvS>> _Primitives = new Dictionary<(TMaterial, int), PrimitiveBuilder<TMaterial, TvG, TvM, TvS>>();
 
 
-        internal VertexBuilder<TvG, TvM, TvS>.Preprocessor _Preprocessor;
+        private VertexPreprocessor<TvG, TvM, TvS> _VertexPreprocessor;
 
 
         #endregion
         #endregion
 
 
         #region properties
         #region properties
 
 
-        public Boolean StrictMode { get; set; }
-
         public string Name { get; set; }
         public string Name { get; set; }
 
 
+        public VertexPreprocessor<TvG, TvM, TvS> VertexPreprocessor
+        {
+            get => _VertexPreprocessor;
+            set => _VertexPreprocessor = value;
+        }
+
         public IEnumerable<TMaterial> Materials => _Primitives.Keys.Select(item => item.Item1).Distinct();
         public IEnumerable<TMaterial> Materials => _Primitives.Keys.Select(item => item.Item1).Distinct();
 
 
         public IReadOnlyCollection<PrimitiveBuilder<TMaterial, TvG, TvM, TvS>> Primitives => _Primitives.Values;
         public IReadOnlyCollection<PrimitiveBuilder<TMaterial, TvG, TvM, TvS>> Primitives => _Primitives.Values;
@@ -94,7 +102,7 @@ namespace SharpGLTF.Geometry
         {
         {
             if (!_Primitives.TryGetValue(key, out PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive))
             if (!_Primitives.TryGetValue(key, out PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive))
             {
             {
-                primitive = new PrimitiveBuilder<TMaterial, TvG, TvM, TvS>(this, key.Item1, key.Item2, StrictMode);
+                primitive = new PrimitiveBuilder<TMaterial, TvG, TvM, TvS>(this, key.Item1, key.Item2);
                 _Primitives[key] = primitive;
                 _Primitives[key] = primitive;
             }
             }
 
 
@@ -165,6 +173,8 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexColor1"/>,
     /// <see cref="VertexColor1"/>,
     /// <see cref="VertexTexture1"/>,
     /// <see cref="VertexTexture1"/>,
     /// <see cref="VertexColor1Texture1"/>.
     /// <see cref="VertexColor1Texture1"/>.
+    /// <see cref="VertexColor1Texture2"/>.
+    /// <see cref="VertexColor2Texture2"/>.
     /// </typeparam>
     /// </typeparam>
     /// <typeparam name="TvS">
     /// <typeparam name="TvS">
     /// The vertex fragment type with Skin Joint Weights.
     /// The vertex fragment type with Skin Joint Weights.
@@ -201,6 +211,8 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexColor1"/>,
     /// <see cref="VertexColor1"/>,
     /// <see cref="VertexTexture1"/>,
     /// <see cref="VertexTexture1"/>,
     /// <see cref="VertexColor1Texture1"/>.
     /// <see cref="VertexColor1Texture1"/>.
+    /// <see cref="VertexColor1Texture2"/>.
+    /// <see cref="VertexColor2Texture2"/>.
     /// </typeparam>
     /// </typeparam>
     public class MeshBuilder<TvG, TvM> : MeshBuilder<Materials.MaterialBuilder, TvG, TvM, VertexEmpty>
     public class MeshBuilder<TvG, TvM> : MeshBuilder<Materials.MaterialBuilder, TvG, TvM, VertexEmpty>
         where TvG : struct, IVertexGeometry
         where TvG : struct, IVertexGeometry

+ 28 - 23
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -60,6 +60,8 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexColor1"/>,
     /// <see cref="VertexColor1"/>,
     /// <see cref="VertexTexture1"/>,
     /// <see cref="VertexTexture1"/>,
     /// <see cref="VertexColor1Texture1"/>.
     /// <see cref="VertexColor1Texture1"/>.
+    /// <see cref="VertexColor1Texture2"/>.
+    /// <see cref="VertexColor2Texture2"/>.
     /// </typeparam>
     /// </typeparam>
     /// <typeparam name="TvS">
     /// <typeparam name="TvS">
     /// The vertex fragment type with Skin Joint Weights.
     /// The vertex fragment type with Skin Joint Weights.
@@ -78,9 +80,8 @@ namespace SharpGLTF.Geometry
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        internal PrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material, int primitiveVertexCount, bool strict)
+        internal PrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material, int primitiveVertexCount)
         {
         {
-            this._Scrict = strict;
             this._Mesh = mesh;
             this._Mesh = mesh;
             this._Material = material;
             this._Material = material;
             this._PrimitiveVertexCount = primitiveVertexCount;
             this._PrimitiveVertexCount = primitiveVertexCount;
@@ -90,8 +91,6 @@ namespace SharpGLTF.Geometry
 
 
         #region data
         #region data
 
 
-        private readonly bool _Scrict;
-
         private readonly MeshBuilder<TMaterial, TvG, TvM, TvS> _Mesh;
         private readonly MeshBuilder<TMaterial, TvG, TvM, TvS> _Mesh;
 
 
         private readonly TMaterial _Material;
         private readonly TMaterial _Material;
@@ -145,10 +144,9 @@ namespace SharpGLTF.Geometry
         /// <returns>The index of the vertex.</returns>
         /// <returns>The index of the vertex.</returns>
         public int UseVertex(VertexBuilder<TvG, TvM, TvS> vertex)
         public int UseVertex(VertexBuilder<TvG, TvM, TvS> vertex)
         {
         {
-            if (!_Mesh._Preprocessor.PreprocessVertex(ref vertex))
+            if (_Mesh.VertexPreprocessor != null)
             {
             {
-                Guard.IsFalse(_Scrict, nameof(vertex));
-                return -1;
+                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref vertex)) return -1;
             }
             }
 
 
             return _Vertices.Use(vertex);
             return _Vertices.Use(vertex);
@@ -174,16 +172,18 @@ namespace SharpGLTF.Geometry
         {
         {
             Guard.IsTrue(_PrimitiveVertexCount == 2, nameof(VerticesPerPrimitive), "Lines are not supported for this primitive");
             Guard.IsTrue(_PrimitiveVertexCount == 2, nameof(VerticesPerPrimitive), "Lines are not supported for this primitive");
 
 
-            var aa = UseVertex(a);
-            var bb = UseVertex(b);
-
-            // check for degenerated triangles:
-            if (aa < 0 || bb < 0 || aa == bb)
+            if (_Mesh.VertexPreprocessor != null)
             {
             {
-                if (_Scrict) throw new ArgumentException($"Invalid triangle indices {aa} {bb}");
-                return;
+                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return;
+                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return;
             }
             }
 
 
+            var aa = _Vertices.Use(a);
+            var bb = _Vertices.Use(b);
+
+            // check for degenerated line
+            if (aa == bb) return;
+
             // TODO: check if a triangle with indices aa-bb-cc already exists.
             // TODO: check if a triangle with indices aa-bb-cc already exists.
 
 
             _Indices.Add(aa);
             _Indices.Add(aa);
@@ -200,18 +200,23 @@ namespace SharpGLTF.Geometry
         {
         {
             Guard.IsTrue(_PrimitiveVertexCount == 3, nameof(VerticesPerPrimitive), "Triangles are not supported for this primitive");
             Guard.IsTrue(_PrimitiveVertexCount == 3, nameof(VerticesPerPrimitive), "Triangles are not supported for this primitive");
 
 
-            var aa = UseVertex(a);
-            var bb = UseVertex(b);
-            var cc = UseVertex(c);
-
-            // check for degenerated triangles:
-            if (aa < 0 || bb < 0 || cc < 0 || aa == bb || aa == cc || bb == cc)
+            if (_Mesh.VertexPreprocessor != null)
             {
             {
-                if (_Scrict) throw new ArgumentException($"Invalid triangle indices {aa} {bb} {cc}");
-                return;
+                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return;
+                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return;
+                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref c)) return;
             }
             }
 
 
-            // TODO: check if a triangle with indices aa-bb-cc already exists.
+            // check for degenerated triangle
+            if (a.Equals(b) || a.Equals(c) || b.Equals(c)) return;
+
+            var aa = _Vertices.Use(a);
+            var bb = _Vertices.Use(b);
+            var cc = _Vertices.Use(c);
+
+            System.Diagnostics.Debug.Assert(aa != bb && aa != cc && bb != cc, "unexpected degenerated triangle");
+
+            // TODO: check if a triangle with indices aa-bb-cc already exists, since there's no point in having the same polygon twice.
 
 
             _Indices.Add(aa);
             _Indices.Add(aa);
             _Indices.Add(bb);
             _Indices.Add(bb);

+ 0 - 90
src/SharpGLTF.Toolkit/Geometry/VertexBuilder.Preprocessor.cs

@@ -1,90 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace SharpGLTF.Geometry
-{
-    public partial struct VertexBuilder<TvG, TvM, TvS>
-    {
-        public class Preprocessor
-        {
-            private readonly List<Func<TvG, TvG?>> _GeometryPreprocessor = new List<Func<TvG, TvG?>>();
-            private readonly List<Func<TvM, TvM?>> _MaterialPreprocessor = new List<Func<TvM, TvM?>>();
-            private readonly List<Func<TvS, TvS?>> _SkinningPreprocessor = new List<Func<TvS, TvS?>>();
-
-            public void Append(Func<TvG, TvG?> func)
-            {
-                _GeometryPreprocessor.Add(func);
-            }
-
-            public void Append(Func<TvM, TvM?> func)
-            {
-                _MaterialPreprocessor.Add(func);
-            }
-
-            public void Append(Func<TvS, TvS?> func)
-            {
-                _SkinningPreprocessor.Add(func);
-            }
-
-            public bool PreprocessVertex(ref VertexBuilder<TvG, TvM, TvS> vertex)
-            {
-                foreach (var f in _GeometryPreprocessor)
-                {
-                    var g = f(vertex.Geometry);
-                    if (!g.HasValue) return false;
-                    vertex.Geometry = g.Value;
-                }
-
-                foreach (var f in _MaterialPreprocessor)
-                {
-                    var m = f(vertex.Material);
-                    if (!m.HasValue) return false;
-                    vertex.Material = m.Value;
-                }
-
-                foreach (var f in _SkinningPreprocessor)
-                {
-                    var s = f(vertex.Skinning);
-                    if (!s.HasValue) return false;
-                    vertex.Skinning = s.Value;
-                }
-
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Gets a preprocessor that does a best effort to produce valid vertices
-        /// </summary>
-        public static Preprocessor SanitizerPreprocessor
-        {
-            get
-            {
-                var p = new Preprocessor();
-                p.Append(VertexTypes.VertexPreprocessors.SanitizeVertexGeometry);
-                p.Append(VertexTypes.VertexPreprocessors.SanitizeVertexMaterial);
-                p.Append(VertexTypes.VertexPreprocessors.SanitizeVertexGeometry);
-
-                return p;
-            }
-        }
-
-        /// <summary>
-        /// Gets a preprocessor that strictly validates a vertex
-        /// </summary>
-        public static Preprocessor ValidationPreprocessor
-        {
-            get
-            {
-                var p = new Preprocessor();
-                p.Append(VertexTypes.VertexPreprocessors.ValidateVertexGeometry);
-                p.Append(VertexTypes.VertexPreprocessors.ValidateVertexMaterial);
-                p.Append(VertexTypes.VertexPreprocessors.ValidateVertexSkinning);
-
-                return p;
-            }
-        }
-
-    }
-}

+ 108 - 1
src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs

@@ -24,6 +24,8 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexColor1"/>,
     /// <see cref="VertexColor1"/>,
     /// <see cref="VertexTexture1"/>,
     /// <see cref="VertexTexture1"/>,
     /// <see cref="VertexColor1Texture1"/>.
     /// <see cref="VertexColor1Texture1"/>.
+    /// <see cref="VertexColor1Texture2"/>.
+    /// <see cref="VertexColor2Texture2"/>.
     /// </typeparam>
     /// </typeparam>
     /// <typeparam name="TvS">
     /// <typeparam name="TvS">
     /// The vertex fragment type with Skin Joint Weights.
     /// The vertex fragment type with Skin Joint Weights.
@@ -33,7 +35,7 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexJoints8x8"/>,
     /// <see cref="VertexJoints8x8"/>,
     /// <see cref="VertexJoints16x4"/>,
     /// <see cref="VertexJoints16x4"/>,
     /// <see cref="VertexJoints16x8"/>.
     /// <see cref="VertexJoints16x8"/>.
-    /// </typeparam>    
+    /// </typeparam>
     [System.Diagnostics.DebuggerDisplay("Vertex 𝐏:{Position} {_GetDebugWarnings()}")]
     [System.Diagnostics.DebuggerDisplay("Vertex 𝐏:{Position} {_GetDebugWarnings()}")]
     public partial struct VertexBuilder<TvG, TvM, TvS>
     public partial struct VertexBuilder<TvG, TvM, TvS>
         where TvG : struct, IVertexGeometry
         where TvG : struct, IVertexGeometry
@@ -82,6 +84,18 @@ namespace SharpGLTF.Geometry
             Skinning = default;
             Skinning = default;
         }
         }
 
 
+        public VertexBuilder(TvG g, params (int, float)[] bindings)
+        {
+            Geometry = g;
+            Material = default;
+            Skinning = default;
+
+            for (int i = 0; i < bindings.Length; ++i)
+            {
+                Skinning.SetJointBinding(i, bindings[i].Item1, bindings[i].Item2);
+            }
+        }
+
         public static implicit operator VertexBuilder<TvG, TvM, TvS>((TvG, TvM, TvS) tuple)
         public static implicit operator VertexBuilder<TvG, TvM, TvS>((TvG, TvM, TvS) tuple)
         {
         {
             return new VertexBuilder<TvG, TvM, TvS>(tuple.Item1, tuple.Item2, tuple.Item3);
             return new VertexBuilder<TvG, TvM, TvS>(tuple.Item1, tuple.Item2, tuple.Item3);
@@ -102,6 +116,30 @@ namespace SharpGLTF.Geometry
             return new VertexBuilder<TvG, TvM, TvS>(g);
             return new VertexBuilder<TvG, TvM, TvS>(g);
         }
         }
 
 
+        public static VertexBuilder<TvG, TvM, TvS> Create(Vector3 position)
+        {
+            var v = default(VertexBuilder<TvG, TvM, TvS>);
+            v.Geometry.SetPosition(position);
+            return v;
+        }
+
+        public static VertexBuilder<TvG, TvM, TvS> Create(Vector3 position,Vector3 normal)
+        {
+            var v = default(VertexBuilder<TvG, TvM, TvS>);
+            v.Geometry.SetPosition(position);
+            v.Geometry.SetNormal(normal);
+            return v;
+        }
+
+        public static VertexBuilder<TvG, TvM, TvS> Create(Vector3 position, Vector3 normal, Vector4 tangent)
+        {
+            var v = default(VertexBuilder<TvG, TvM, TvS>);
+            v.Geometry.SetPosition(position);
+            v.Geometry.SetNormal(normal);
+            v.Geometry.SetTangent(tangent);
+            return v;
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data
@@ -125,6 +163,75 @@ namespace SharpGLTF.Geometry
 
 
         #region API
         #region API
 
 
+        public VertexBuilder<TvG, TvM, TvS> WithGeometry(Vector3 position)
+        {
+            var v = this;
+            v.Geometry.SetPosition(position);
+            return v;
+        }
+
+        public VertexBuilder<TvG, TvM, TvS> WithGeometry(Vector3 position, Vector3 normal)
+        {
+            var v = this;
+            v.Geometry.SetPosition(position);
+            v.Geometry.SetNormal(normal);
+            return v;
+        }
+
+        public VertexBuilder<TvG, TvM, TvS> WithGeometry(Vector3 position, Vector3 normal, Vector4 tangent)
+        {
+            var v = this;
+            v.Geometry.SetPosition(position);
+            v.Geometry.SetNormal(normal);
+            v.Geometry.SetTangent(tangent);
+            return v;
+        }
+
+        public VertexBuilder<TvG, TvM, TvS> WithMaterial(params Vector2[] uvs)
+        {
+            var v = this;
+            for (int i = 0; i < uvs.Length; ++i) v.Material.SetTexCoord(i, uvs[i]);
+            return v;
+        }
+
+        public VertexBuilder<TvG, TvM, TvS> WithMaterial(Vector4 color0, params Vector2[] uvs)
+        {
+            var v = this;
+            v.Material.SetColor(0, color0);
+            for (int i = 0; i < uvs.Length; ++i) v.Material.SetTexCoord(i, uvs[i]);
+            return v;
+        }
+
+        public VertexBuilder<TvG, TvM, TvS> WithMaterial(Vector4 color0, Vector4 color1, params Vector2[] uvs)
+        {
+            var v = this;
+            v.Material.SetColor(0, color0);
+            v.Material.SetColor(1, color1);
+            for (int i = 0; i < uvs.Length; ++i) v.Material.SetTexCoord(i, uvs[i]);
+            return v;
+        }
+
+        public VertexBuilder<TvG, TvM, TvS> WithSkinning(params (int, float)[] bindings)
+        {
+            var v = this;
+
+            int i = 0;
+
+            while (i < bindings.Length)
+            {
+                v.Skinning.SetJointBinding(i, bindings[i].Item1, bindings[i].Item2);
+                ++i;
+            }
+
+            while (i < bindings.Length)
+            {
+                v.Skinning.SetJointBinding(i, 0, 0);
+                ++i;
+            }
+
+            return v;
+        }
+
         public void Validate()
         public void Validate()
         {
         {
             Geometry.Validate();
             Geometry.Validate();

+ 3 - 15
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexGeometry.cs

@@ -76,10 +76,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             Position = Vector3.Transform(Position, xform);
             Position = Vector3.Transform(Position, xform);
         }
         }
 
 
-        public void Validate()
-        {
-            Position.Validate(nameof(Position));
-        }
+        public void Validate() { FragmentPreprocessors.ValidateVertexGeometry(this); }
 
 
         #endregion
         #endregion
     }
     }
@@ -142,11 +139,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             Normal = Vector3.Normalize(Vector3.TransformNormal(Normal, xform));
             Normal = Vector3.Normalize(Vector3.TransformNormal(Normal, xform));
         }
         }
 
 
-        public void Validate()
-        {
-            Position.Validate(nameof(Position));
-            Normal.ValidateNormal(nameof(Normal));
-        }
+        public void Validate() { FragmentPreprocessors.ValidateVertexGeometry(this); }
 
 
         #endregion
         #endregion
     }
     }
@@ -212,12 +205,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             Tangent = new Vector4(txyz, Tangent.W);
             Tangent = new Vector4(txyz, Tangent.W);
         }
         }
 
 
-        public void Validate()
-        {
-            Position.Validate(nameof(Position));
-            Normal.ValidateNormal(nameof(Normal));
-            Tangent.ValidateTangent(nameof(Tangent));
-        }
+        public void Validate() { FragmentPreprocessors.ValidateVertexGeometry(this); }
 
 
         #endregion
         #endregion
     }
     }

+ 5 - 35
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexMaterial.cs

@@ -74,11 +74,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             throw new NotSupportedException();
             throw new NotSupportedException();
         }
         }
 
 
-        public void Validate()
-        {
-            if (!Color._IsReal()) throw new NotFiniteNumberException(nameof(Color));
-            if (!Color.IsInRange(Vector4.Zero, Vector4.One)) throw new IndexOutOfRangeException(nameof(Color));
-        }
+        public void Validate() { FragmentPreprocessors.ValidateVertexMaterial(this); }
 
 
         #endregion
         #endregion
     }
     }
@@ -136,10 +132,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return TexCoord;
             return TexCoord;
         }
         }
 
 
-        public void Validate()
-        {
-            if (!TexCoord._IsReal()) throw new NotFiniteNumberException(nameof(TexCoord));
-        }
+        public void Validate() { FragmentPreprocessors.ValidateVertexMaterial(this); }
 
 
         #endregion
         #endregion
     }
     }
@@ -198,13 +191,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return TexCoord;
             return TexCoord;
         }
         }
 
 
-        public void Validate()
-        {
-            if (!Color._IsReal()) throw new NotFiniteNumberException(nameof(Color));
-            if (!Color.IsInRange(Vector4.Zero, Vector4.One)) throw new IndexOutOfRangeException(nameof(Color));
-
-            if (!TexCoord._IsReal()) throw new NotFiniteNumberException(nameof(TexCoord));
-        }
+        public void Validate() { FragmentPreprocessors.ValidateVertexMaterial(this); }
 
 
         #endregion
         #endregion
     }
     }
@@ -276,14 +263,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             }
             }
         }
         }
 
 
-        public void Validate()
-        {
-            if (!Color._IsReal()) throw new NotFiniteNumberException(nameof(Color));
-            if (!Color.IsInRange(Vector4.Zero, Vector4.One)) throw new IndexOutOfRangeException(nameof(Color));
-
-            if (!TexCoord0._IsReal()) throw new NotFiniteNumberException(nameof(TexCoord0));
-            if (!TexCoord1._IsReal()) throw new NotFiniteNumberException(nameof(TexCoord1));
-        }
+        public void Validate() { FragmentPreprocessors.ValidateVertexMaterial(this); }
 
 
         #endregion
         #endregion
     }
     }
@@ -368,17 +348,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             }
             }
         }
         }
 
 
-        public void Validate()
-        {
-            if (!Color0._IsReal()) throw new NotFiniteNumberException(nameof(Color0));
-            if (!Color0.IsInRange(Vector4.Zero, Vector4.One)) throw new IndexOutOfRangeException(nameof(Color0));
-
-            if (!Color1._IsReal()) throw new NotFiniteNumberException(nameof(Color1));
-            if (!Color1.IsInRange(Vector4.Zero, Vector4.One)) throw new IndexOutOfRangeException(nameof(Color1));
-
-            if (!TexCoord0._IsReal()) throw new NotFiniteNumberException(nameof(TexCoord0));
-            if (!TexCoord1._IsReal()) throw new NotFiniteNumberException(nameof(TexCoord1));
-        }
+        public void Validate() { FragmentPreprocessors.ValidateVertexMaterial(this); }
 
 
         #endregion
         #endregion
     }
     }

+ 141 - 6
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexPreprocessors.cs

@@ -5,10 +5,128 @@ using System.Text;
 
 
 namespace SharpGLTF.Geometry.VertexTypes
 namespace SharpGLTF.Geometry.VertexTypes
 {
 {
+    public delegate TvG? VertexGeometryPreprocessor<TvG>(TvG arg)
+        where TvG : struct, IVertexGeometry;
+
+    public delegate TvM? VertexMaterialPreprocessor<TvM>(TvM arg)
+        where TvM : struct, IVertexMaterial;
+
+    public delegate TvS? VertexSkinningPreprocessor<TvS>(TvS arg)
+        where TvS : struct, IVertexSkinning;
+
     /// <summary>
     /// <summary>
-    /// Defines a set of vertex preprocessors to be used with <see cref="MeshBuilder{TMaterial, TvG, TvM, TvS}"/>
+    /// Represents a <see cref="VertexBuilder{TvG, TvM, TvS}"/> preprocessor used by <see cref="MeshBuilder{TMaterial, TvG, TvM, TvS}.VertexPreprocessor"/>
     /// </summary>
     /// </summary>
-    static class VertexPreprocessors
+    /// <typeparam name="TvG">
+    /// The vertex fragment type with Position, Normal and Tangent.
+    /// Valid types are:
+    /// <see cref="VertexPosition"/>,
+    /// <see cref="VertexPositionNormal"/>,
+    /// <see cref="VertexPositionNormalTangent"/>.
+    /// </typeparam>
+    /// <typeparam name="TvM">
+    /// The vertex fragment type with Colors and Texture Coordinates.
+    /// Valid types are:
+    /// <see cref="VertexEmpty"/>,
+    /// <see cref="VertexColor1"/>,
+    /// <see cref="VertexTexture1"/>,
+    /// <see cref="VertexColor1Texture1"/>.
+    /// </typeparam>
+    /// <typeparam name="TvS">
+    /// The vertex fragment type with Skin Joint Weights.
+    /// Valid types are:
+    /// <see cref="VertexEmpty"/>,
+    /// <see cref="VertexJoints8x4"/>,
+    /// <see cref="VertexJoints8x8"/>,
+    /// <see cref="VertexJoints16x4"/>,
+    /// <see cref="VertexJoints16x8"/>.
+    /// </typeparam>
+    public sealed class VertexPreprocessor<TvG, TvM, TvS>
+        where TvG : struct, IVertexGeometry
+        where TvM : struct, IVertexMaterial
+        where TvS : struct, IVertexSkinning
+    {
+        #region data
+
+        private readonly List<VertexGeometryPreprocessor<TvG>> _GeometryPreprocessor = new List<VertexGeometryPreprocessor<TvG>>();
+        private readonly List<VertexMaterialPreprocessor<TvM>> _MaterialPreprocessor = new List<VertexMaterialPreprocessor<TvM>>();
+        private readonly List<VertexSkinningPreprocessor<TvS>> _SkinningPreprocessor = new List<VertexSkinningPreprocessor<TvS>>();
+
+        #endregion
+
+        #region API
+
+        public void Clear()
+        {
+            _GeometryPreprocessor.Clear();
+            _MaterialPreprocessor.Clear();
+            _SkinningPreprocessor.Clear();
+        }
+
+        public void Append(VertexGeometryPreprocessor<TvG> func)
+        {
+            _GeometryPreprocessor.Add(func);
+        }
+
+        public void Append(VertexMaterialPreprocessor<TvM> func)
+        {
+            _MaterialPreprocessor.Add(func);
+        }
+
+        public void Append(VertexSkinningPreprocessor<TvS> func)
+        {
+            _SkinningPreprocessor.Add(func);
+        }
+
+        public void SetDebugPreprocessors()
+        {
+            Clear();
+            Append(FragmentPreprocessors.ValidateVertexGeometry);
+            Append(FragmentPreprocessors.ValidateVertexMaterial);
+            Append(FragmentPreprocessors.ValidateVertexSkinning);
+        }
+
+        public void SetSanitizerPreprocessors()
+        {
+            Clear();
+            Append(FragmentPreprocessors.SanitizeVertexGeometry);
+            Append(FragmentPreprocessors.SanitizeVertexMaterial);
+            Append(FragmentPreprocessors.SanitizeVertexSkinning);
+        }
+
+        public bool PreprocessVertex(ref VertexBuilder<TvG, TvM, TvS> vertex)
+        {
+            foreach (var f in _GeometryPreprocessor)
+            {
+                var g = f(vertex.Geometry);
+                if (!g.HasValue) return false;
+                vertex.Geometry = g.Value;
+            }
+
+            foreach (var f in _MaterialPreprocessor)
+            {
+                var m = f(vertex.Material);
+                if (!m.HasValue) return false;
+                vertex.Material = m.Value;
+            }
+
+            foreach (var f in _SkinningPreprocessor)
+            {
+                var s = f(vertex.Skinning);
+                if (!s.HasValue) return false;
+                vertex.Skinning = s.Value;
+            }
+
+            return true;
+        }
+
+        #endregion
+    }
+
+    /// <summary>
+    /// Defines a set of vertex fragment preprocessors to be used with <see cref="VertexPreprocessor{TvG, TvM, TvS}"/>
+    /// </summary>
+    static class FragmentPreprocessors
     {
     {
         /// <summary>
         /// <summary>
         /// validates a vertex geometry, throwing exceptions if found invalid
         /// validates a vertex geometry, throwing exceptions if found invalid
@@ -32,14 +150,14 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (vertex.TryGetNormal(out Vector3 n))
             if (vertex.TryGetNormal(out Vector3 n))
             {
             {
                 Guard.IsTrue(n._IsReal(), "Normal", "Values are not finite.");
                 Guard.IsTrue(n._IsReal(), "Normal", "Values are not finite.");
-                Guard.MustBeBetweenOrEqualTo(n.Length(), -0.99f, 0.01f, "Normal.Length");
+                Guard.MustBeBetweenOrEqualTo(n.Length(), 0.99f, 1.01f, "Normal.Length");
             }
             }
 
 
             if (vertex.TryGetTangent(out Vector4 t))
             if (vertex.TryGetTangent(out Vector4 t))
             {
             {
                 Guard.IsTrue(t._IsReal(), "Tangent", "Values are not finite.");
                 Guard.IsTrue(t._IsReal(), "Tangent", "Values are not finite.");
                 Guard.IsTrue(t.W == 1 || t.W == -1, "Tangent.W", "Invalid value");
                 Guard.IsTrue(t.W == 1 || t.W == -1, "Tangent.W", "Invalid value");
-                Guard.MustBeBetweenOrEqualTo(new Vector3(t.X, t.Y, t.Z).Length(), -0.99f, 0.01f, "Tangent.XYZ.Length");
+                Guard.MustBeBetweenOrEqualTo(new Vector3(t.X, t.Y, t.Z).Length(), 0.99f, 1.01f, "Tangent.XYZ.Length");
             }
             }
 
 
             return vertex;
             return vertex;
@@ -99,6 +217,9 @@ namespace SharpGLTF.Geometry.VertexTypes
         {
         {
             if (vertex.MaxBindings == 0) return vertex;
             if (vertex.MaxBindings == 0) return vertex;
 
 
+            // Apparently the consensus is that weights are required to be normalized.
+            // More here: https://github.com/KhronosGroup/glTF/issues/1213
+
             float weightsSum = 0;
             float weightsSum = 0;
 
 
             for (int i = 0; i < vertex.MaxBindings; ++i)
             for (int i = 0; i < vertex.MaxBindings; ++i)
@@ -107,10 +228,13 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
                 Guard.MustBeGreaterThanOrEqualTo(pair.Joint, 0, $"Joint{i}");
                 Guard.MustBeGreaterThanOrEqualTo(pair.Joint, 0, $"Joint{i}");
                 Guard.IsTrue(pair.Weight._IsReal(), $"Weight{i}", "Values are not finite.");
                 Guard.IsTrue(pair.Weight._IsReal(), $"Weight{i}", "Values are not finite.");
+                if (pair.Weight == 0) Guard.IsTrue(pair.Joint == 0, "joints with weight zero must be set to zero");
 
 
                 weightsSum += pair.Weight;
                 weightsSum += pair.Weight;
             }
             }
 
 
+            // TODO: check that joints are unique
+
             Guard.MustBeBetweenOrEqualTo(weightsSum, 0.99f, 1.01f, "Weights SUM");
             Guard.MustBeBetweenOrEqualTo(weightsSum, 0.99f, 1.01f, "Weights SUM");
 
 
             return vertex;
             return vertex;
@@ -219,18 +343,29 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
             Span<JointBinding> pairs = stackalloc JointBinding[vertex.MaxBindings];
             Span<JointBinding> pairs = stackalloc JointBinding[vertex.MaxBindings];
 
 
+            // Apparently the consensus is that weights are required to be normalized.
+            // More here: https://github.com/KhronosGroup/glTF/issues/1213
+
+            float weightsSum = 0;
+
             for (int i = 0; i < pairs.Length; ++i)
             for (int i = 0; i < pairs.Length; ++i)
             {
             {
                 var pair = vertex.GetJointBinding(i);
                 var pair = vertex.GetJointBinding(i);
 
 
-                pairs[i] = pair;
+                pairs[i] = pair.Weight == 0 ? default : pair;
+
+                weightsSum += pair.Weight;
             }
             }
 
 
+            // TODO: check that joints are unique, and if not, do a merge.
+
+            if (weightsSum == 0) weightsSum = 1;
+
             JointBinding.InPlaceReverseBubbleSort(pairs);
             JointBinding.InPlaceReverseBubbleSort(pairs);
 
 
             for (int i = 0; i < pairs.Length; ++i)
             for (int i = 0; i < pairs.Length; ++i)
             {
             {
-                vertex.SetJointBinding(i, pairs[i].Joint, pairs[i].Weight);
+                vertex.SetJointBinding(i, pairs[i].Joint, pairs[i].Weight / weightsSum);
             }
             }
 
 
             return vertex;
             return vertex;

+ 4 - 36
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexSkinning.cs

@@ -197,13 +197,7 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         #region API
         #region API
 
 
-        public void Validate()
-        {
-            if (!Joints._IsReal()) throw new NotFiniteNumberException(nameof(Joints));
-            if (!Joints.IsRound() || !Joints.IsInRange(Vector4.Zero, new Vector4(255))) throw new IndexOutOfRangeException(nameof(Joints));
-
-            if (!Weights._IsReal()) throw new NotFiniteNumberException(nameof(Weights));
-        }
+        public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
 
 
         public JointBinding GetJointBinding(int index)
         public JointBinding GetJointBinding(int index)
         {
         {
@@ -302,13 +296,7 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         #region API
         #region API
 
 
-        public void Validate()
-        {
-            if (!Joints._IsReal()) throw new NotFiniteNumberException(nameof(Joints));
-            if (!Joints.IsRound() || !Joints.IsInRange(Vector4.Zero, new Vector4(65535))) throw new IndexOutOfRangeException(nameof(Joints));
-
-            if (!Weights._IsReal()) throw new NotFiniteNumberException(nameof(Weights));
-        }
+        public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
 
 
         public JointBinding GetJointBinding(int index)
         public JointBinding GetJointBinding(int index)
         {
         {
@@ -399,17 +387,7 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         #region API
         #region API
 
 
-        public void Validate()
-        {
-            if (!Joints0._IsReal()) throw new NotFiniteNumberException(nameof(Joints0));
-            if (!Joints1._IsReal()) throw new NotFiniteNumberException(nameof(Joints1));
-
-            if (!Joints0.IsRound() || !Joints0.IsInRange(Vector4.Zero, new Vector4(255))) throw new IndexOutOfRangeException(nameof(Joints0));
-            if (!Joints1.IsRound() || !Joints1.IsInRange(Vector4.Zero, new Vector4(255))) throw new IndexOutOfRangeException(nameof(Joints1));
-
-            if (!Weights0._IsReal()) throw new NotFiniteNumberException(nameof(Weights0));
-            if (!Weights1._IsReal()) throw new NotFiniteNumberException(nameof(Weights1));
-        }
+        public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
 
 
         public JointBinding GetJointBinding(int index)
         public JointBinding GetJointBinding(int index)
         {
         {
@@ -493,17 +471,7 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         #region API
         #region API
 
 
-        public void Validate()
-        {
-            if (!Joints0._IsReal()) throw new NotFiniteNumberException(nameof(Joints0));
-            if (!Joints1._IsReal()) throw new NotFiniteNumberException(nameof(Joints1));
-
-            if (!Joints0.IsRound() || !Joints0.IsInRange(Vector4.Zero, new Vector4(65535))) throw new IndexOutOfRangeException(nameof(Joints0));
-            if (!Joints1.IsRound() || !Joints1.IsInRange(Vector4.Zero, new Vector4(65535))) throw new IndexOutOfRangeException(nameof(Joints1));
-
-            if (!Weights0._IsReal()) throw new NotFiniteNumberException(nameof(Weights0));
-            if (!Weights1._IsReal()) throw new NotFiniteNumberException(nameof(Weights1));
-        }
+        public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
 
 
         public JointBinding GetJointBinding(int index)
         public JointBinding GetJointBinding(int index)
         {
         {

+ 102 - 0
tests/SharpGLTF.Tests/Geometry/MeshBuilderTests.cs

@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+
+using NUnit.Framework;
+using SharpGLTF.Geometry.VertexTypes;
+
+namespace SharpGLTF.Geometry
+{
+    using VERTEX1 = VertexBuilder<VertexPosition, VertexColor1Texture1, VertexJoints8x4>;
+    using VERTEX2 = VertexBuilder<VertexPositionNormal, VertexColor1Texture1, VertexJoints8x4>;
+
+    [Category("Toolkit.Geometry")]
+    public class MeshBuilderTests
+    {
+        [Description("Although a triangle with three corners at zero should be accounted as a degenerated triangle, if it has different skinning per vertex, it should not be discarded.")]
+        [Test]
+        public void CreatePseudoDegeneratedTriangle()
+        {
+            var m = new Materials.MaterialBuilder();
+
+            var mb = VERTEX1.CreateCompatibleMesh();
+
+            var a = new VERTEX1(Vector3.Zero, (0, 1));
+            var b = new VERTEX1(Vector3.Zero, (1, 1));
+            var c = new VERTEX1(Vector3.Zero, (2, 1));
+
+            mb.UsePrimitive(m).AddTriangle(a, b, c);
+
+            var triCount = mb.Primitives.Sum(item => item.Triangles.Count());
+
+            Assert.AreEqual(1, triCount);
+        }
+
+        [Test]
+        public void CreateInvalidTriangles()
+        {
+            var m = new Materials.MaterialBuilder();            
+
+            var mb = VERTEX2.CreateCompatibleMesh();
+
+            // replaces default preprocessor with a debug preprocessor that throws exceptions at the slightest issue.
+            mb.VertexPreprocessor.SetDebugPreprocessors();
+
+            int TriangleCounter() { return mb.Primitives.Sum(item => item.Triangles.Count()); }
+
+            var prim = mb.UsePrimitive(m);
+
+            var a = VERTEX2
+                .Create(Vector3.Zero,Vector3.UnitX)
+                .WithMaterial(Vector4.One,Vector2.Zero)
+                .WithSkinning((0,1));
+
+            var b = VERTEX2
+                .Create(Vector3.UnitX, Vector3.UnitX)
+                .WithMaterial(Vector4.One, Vector2.Zero)
+                .WithSkinning((0, 1));
+
+            var c = VERTEX2
+                .Create(Vector3.UnitY, Vector3.UnitX)
+                .WithMaterial(Vector4.One, Vector2.Zero)
+                .WithSkinning((0, 1));
+
+            prim.AddTriangle(a, b, c);
+            Assert.AreEqual(1, TriangleCounter());
+
+            var v2nan = new Vector2(float.NaN, float.NaN);
+            var v3nan = new Vector3(float.NaN, float.NaN, float.NaN);
+            var v4nan = new Vector4(float.NaN, float.NaN, float.NaN, float.NaN);
+
+            Assert.Throws(typeof(ArgumentException), () => prim.AddTriangle(a.WithGeometry(v3nan), b, c));
+            Assert.AreEqual(1, TriangleCounter());
+
+            Assert.Throws(typeof(ArgumentException), () => prim.AddTriangle(a.WithGeometry(Vector3.Zero, v3nan), b, c));
+            Assert.AreEqual(1, TriangleCounter());
+
+            Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithGeometry(Vector3.Zero, Vector3.Zero), b, c));
+            Assert.AreEqual(1, TriangleCounter());            
+            
+            Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithGeometry(Vector3.Zero, Vector3.UnitX * 0.8f), b, c));
+            Assert.AreEqual(1, TriangleCounter());
+
+            Assert.Throws(typeof(ArgumentException), () => prim.AddTriangle(a.WithMaterial(v2nan), b, c));
+            Assert.AreEqual(1, TriangleCounter());
+
+            Assert.Throws(typeof(ArgumentException), () => prim.AddTriangle(a.WithMaterial(v4nan), b, c));
+            Assert.AreEqual(1, TriangleCounter());
+
+            Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithMaterial(Vector4.One*2), b, c));
+            Assert.AreEqual(1, TriangleCounter());
+
+            Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithMaterial(-Vector4.One), b, c));
+            Assert.AreEqual(1, TriangleCounter());
+
+            Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithSkinning((0,0)), b, c));
+            Assert.AreEqual(1, TriangleCounter());
+        }
+
+    }
+}

+ 5 - 5
tests/SharpGLTF.Tests/Schema2/Authoring/BasicSceneCreationTests.cs

@@ -4,7 +4,7 @@ using NUnit.Framework;
 
 
 namespace SharpGLTF.Schema2.Authoring
 namespace SharpGLTF.Schema2.Authoring
 {
 {
-    using VPOSNRM = Geometry.VertexTypes.VertexPositionNormal;
+    using VPOSNRM = Geometry.VertexBuilder<Geometry.VertexTypes.VertexPositionNormal,Geometry.VertexTypes.VertexEmpty,Geometry.VertexTypes.VertexEmpty>;
 
 
 
 
     [TestFixture]
     [TestFixture]
@@ -166,10 +166,10 @@ namespace SharpGLTF.Schema2.Authoring
 
 
             var vertices = new[]
             var vertices = new[]
             {
             {
-                new VPOSNRM(-10,  10, 0, 0, 0, 1),
-                new VPOSNRM( 10,  10, 0, 0, 0, 1),
-                new VPOSNRM( 10, -10, 0, 0, 0, 1),
-                new VPOSNRM(-10, -10, 0, 0, 0, 1)
+                VPOSNRM.Create(new Vector3(-10,  10, 0), Vector3.UnitZ),
+                VPOSNRM.Create(new Vector3( 10,  10, 0), Vector3.UnitZ),
+                VPOSNRM.Create(new Vector3( 10, -10, 0), Vector3.UnitZ),
+                VPOSNRM.Create(new Vector3(-10, -10, 0), Vector3.UnitZ)
             };
             };
 
 
             var model = ModelRoot.CreateModel();
             var model = ModelRoot.CreateModel();

+ 17 - 7
tests/SharpGLTF.Tests/Schema2/Authoring/MeshBuilderCreationTests.cs

@@ -26,17 +26,18 @@ namespace SharpGLTF.Schema2.Authoring
             TestContext.CurrentContext.AttachGltfValidatorLinks();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             // define 4 vertices
             // define 4 vertices
-            var v1 = new VPOSNRM(-10, 10, 0, -10, 10, 15);
-            var v2 = new VPOSNRM(10, 10, 0, 10, 10, 15);
-            var v3 = new VPOSNRM(10, -10, 0, 10, -10, 15);
-            var v4 = new VPOSNRM(-10, -10, 0, -10, -10, 15);
+            var v1 = new VPOSNRM(-10, 10, 0, 0, 0, 1);
+            var v2 = new VPOSNRM(10, 10, 0, 0, 0, 1);
+            var v3 = new VPOSNRM(10, -10, 0, 0, 0, 1);
+            var v4 = new VPOSNRM(-10, -10, 0, 0, 0, 1);
 
 
             // create a material
             // create a material
             var material1 = new MaterialBuilder("material1").WithChannelParam(KnownChannels.BaseColor, Vector4.One);
             var material1 = new MaterialBuilder("material1").WithChannelParam(KnownChannels.BaseColor, Vector4.One);
 
 
             // create model
             // create model
             var meshBuilder = new MeshBuilder<VPOSNRM>("mesh1");
             var meshBuilder = new MeshBuilder<VPOSNRM>("mesh1");
-            
+            meshBuilder.VertexPreprocessor.SetDebugPreprocessors();
+
             // add a polygon to the primitive that uses material1 as key.
             // add a polygon to the primitive that uses material1 as key.
             meshBuilder.UsePrimitive(material1).AddConvexPolygon(v1, v2, v3, v4);
             meshBuilder.UsePrimitive(material1).AddConvexPolygon(v1, v2, v3, v4);
 
 
@@ -63,9 +64,13 @@ namespace SharpGLTF.Schema2.Authoring
 
 
             // create several meshes
             // create several meshes
             var meshBuilder1 = new MeshBuilder<VPOSNRM>("mesh1");
             var meshBuilder1 = new MeshBuilder<VPOSNRM>("mesh1");
+                meshBuilder1.VertexPreprocessor.SetDebugPreprocessors();
             var meshBuilder2 = new MeshBuilder<VPOSNRM>("mesh2");
             var meshBuilder2 = new MeshBuilder<VPOSNRM>("mesh2");
+                meshBuilder2.VertexPreprocessor.SetDebugPreprocessors();
             var meshBuilder3 = new MeshBuilder<VPOSNRM>("mesh3");
             var meshBuilder3 = new MeshBuilder<VPOSNRM>("mesh3");
+                meshBuilder3.VertexPreprocessor.SetDebugPreprocessors();
             var meshBuilder4 = new MeshBuilder<VPOSNRM>("mesh4");
             var meshBuilder4 = new MeshBuilder<VPOSNRM>("mesh4");
+                meshBuilder4.VertexPreprocessor.SetDebugPreprocessors();
 
 
             meshBuilder1.AddCube(material1, Matrix4x4.Identity);
             meshBuilder1.AddCube(material1, Matrix4x4.Identity);
             meshBuilder2.AddCube(material2, Matrix4x4.Identity);
             meshBuilder2.AddCube(material2, Matrix4x4.Identity);
@@ -123,6 +128,8 @@ namespace SharpGLTF.Schema2.Authoring
 
 
             // create a mesh
             // create a mesh
             var meshBuilder = new MeshBuilder<VPOSNRM>("mesh1");
             var meshBuilder = new MeshBuilder<VPOSNRM>("mesh1");
+            meshBuilder.VertexPreprocessor.SetDebugPreprocessors();
+
             meshBuilder.AddCube(material1, Matrix4x4.Identity);
             meshBuilder.AddCube(material1, Matrix4x4.Identity);
             meshBuilder.Validate();
             meshBuilder.Validate();
 
 
@@ -159,6 +166,7 @@ namespace SharpGLTF.Schema2.Authoring
 
 
             // create the mesh
             // create the mesh
             var meshBuilder = new MeshBuilder<VPOS, VEMPTY, VSKIN4>("mesh1");
             var meshBuilder = new MeshBuilder<VPOS, VEMPTY, VSKIN4>("mesh1");
+            meshBuilder.VertexPreprocessor.SetDebugPreprocessors();
 
 
             var v1 = (new VPOS(-10, 0, +10), new VSKIN4(0));
             var v1 = (new VPOS(-10, 0, +10), new VSKIN4(0));
             var v2 = (new VPOS(+10, 0, +10), new VSKIN4(0));
             var v2 = (new VPOS(+10, 0, +10), new VSKIN4(0));
@@ -262,7 +270,8 @@ namespace SharpGLTF.Schema2.Authoring
 
 
             var material = new MaterialBuilder("material1").WithUnlitShader();            
             var material = new MaterialBuilder("material1").WithUnlitShader();            
 
 
-            var mesh = new MeshBuilder<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexColor1>("points");
+            var mesh = new MeshBuilder<VPOS, Geometry.VertexTypes.VertexColor1>("points");
+            mesh.VertexPreprocessor.SetDebugPreprocessors();
 
 
             // create a point cloud primitive
             // create a point cloud primitive
             var pointCloud = mesh.UsePrimitive(material, 1);
             var pointCloud = mesh.UsePrimitive(material, 1);
@@ -325,8 +334,9 @@ namespace SharpGLTF.Schema2.Authoring
 
 
             // create a mesh
             // create a mesh
             var cubes = new MeshBuilder<VPOSNRM>("cube");
             var cubes = new MeshBuilder<VPOSNRM>("cube");
+            cubes.VertexPreprocessor.SetDebugPreprocessors();
 
 
-            for(int i=0; i < 100; ++i)
+            for (int i=0; i < 100; ++i)
             {
             {
                 var r = rnd.NextVector3() * 5;
                 var r = rnd.NextVector3() * 5;
                 var m = materials[rnd.Next(0, 10)];
                 var m = materials[rnd.Next(0, 10)];