Browse Source

WIP vertex preprocessor for MeshBuilder.

Vicente Penades 6 years ago
parent
commit
a926de7891

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

@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+
+namespace SharpGLTF.Debug
+{
+
+}

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

@@ -60,6 +60,8 @@ namespace SharpGLTF.Geometry
         public MeshBuilder(string name = null)
         {
             this.Name = name;
+
+            _Preprocessor = VertexBuilder<TvG, TvM, TvS>.SanitizerPreprocessor;
         }
 
         #endregion
@@ -68,6 +70,8 @@ namespace SharpGLTF.Geometry
 
         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;
+
         #endregion
 
         #region properties

+ 3 - 6
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -145,13 +145,10 @@ namespace SharpGLTF.Geometry
         /// <returns>The index of the vertex.</returns>
         public int UseVertex(VertexBuilder<TvG, TvM, TvS> vertex)
         {
-            if (_Scrict)
+            if (!_Mesh._Preprocessor.PreprocessVertex(ref vertex))
             {
-                vertex.Validate();
-            }
-            else
-            {
-                if (!vertex.Geometry.SanitizeVertex(out vertex.Geometry)) return -1;
+                Guard.IsFalse(_Scrict, nameof(vertex));
+                return -1;
             }
 
             return _Vertices.Use(vertex);

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

@@ -0,0 +1,90 @@
+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;
+            }
+        }
+
+    }
+}

+ 39 - 3
src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs

@@ -33,9 +33,9 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexJoints8x8"/>,
     /// <see cref="VertexJoints16x4"/>,
     /// <see cref="VertexJoints16x8"/>.
-    /// </typeparam>
-    [System.Diagnostics.DebuggerDisplay("Vertex {Geometry} {Material} {Skinning}")]
-    public struct VertexBuilder<TvG, TvM, TvS>
+    /// </typeparam>    
+    [System.Diagnostics.DebuggerDisplay("Vertex 𝐏:{Position} {_GetDebugWarnings()}")]
+    public partial struct VertexBuilder<TvG, TvM, TvS>
         where TvG : struct, IVertexGeometry
         where TvM : struct, IVertexMaterial
         where TvS : struct, IVertexSkinning
@@ -114,6 +114,7 @@ namespace SharpGLTF.Geometry
 
         #region properties
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector3 Position
         {
             get => Geometry.GetPosition();
@@ -153,6 +154,41 @@ namespace SharpGLTF.Geometry
             return new MeshBuilder<TvG, TvM, TvS>(name);
         }
 
+        private String _GetDebugWarnings()
+        {
+            var sb = new StringBuilder();
+
+            if (Geometry.TryGetNormal(out Vector3 n))
+            {
+                if (!n.IsValidNormal()) sb.Append($" ❌𝚴:{n}");
+            }
+
+            if (Geometry.TryGetTangent(out Vector4 t))
+            {
+                if (!t.IsValidTangent()) sb.Append($" ❌𝚻:{t}");
+            }
+
+            for (int i = 0; i < Material.MaxColors; ++i)
+            {
+                var c = Material.GetColor(i);
+                if (!c._IsReal() | !c.IsInRange(Vector4.Zero, Vector4.One)) sb.Append($" ❌𝐂{i}:{c}");
+            }
+
+            for (int i = 0; i < Material.MaxTextures; ++i)
+            {
+                var uv = Material.GetTexCoord(i);
+                if (!uv._IsReal()) sb.Append($" ❌𝐔𝐕{i}:{uv}");
+            }
+
+            for (int i = 0; i < Skinning.MaxBindings; ++i)
+            {
+                var jw = Skinning.GetJointBinding(i);
+                if (!jw.Weight._IsReal() || jw.Weight < 0 || jw.Joint < 0) sb.Append($" ❌𝐉𝐖{i} {jw.Joint}:{jw.Weight}");
+            }
+
+            return sb.ToString();
+        }
+
         #endregion
     }
 }

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

@@ -23,7 +23,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// Defines a Vertex attribute with a Position.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("{Position}")]
+    [System.Diagnostics.DebuggerDisplay("𝐏:{Position}")]
     public struct VertexPosition : IVertexGeometry
     {
         #region constructors
@@ -87,7 +87,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// Defines a Vertex attribute with a Position and a Normal.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("{Position} {Normal}")]
+    [System.Diagnostics.DebuggerDisplay("𝐏:{Position} 𝚴:{Normal}")]
     public struct VertexPositionNormal : IVertexGeometry
     {
         #region constructors
@@ -154,7 +154,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// Defines a Vertex attribute with a Position, a Normal and a Tangent.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("{Position} {Normal} {Tangent}")]
+    [System.Diagnostics.DebuggerDisplay("𝐏:{Position} 𝚴:{Normal} 𝚻:{Tangent}")]
     public struct VertexPositionNormalTangent : IVertexGeometry
     {
         #region constructors

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

@@ -8,6 +8,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     public interface IVertexMaterial
     {
         int MaxColors { get; }
+
+        // TODO: rename to MaxTexCoords
         int MaxTextures { get; }
 
         void Validate();
@@ -22,7 +24,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// Defines a Vertex attribute with a Color material.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("{Color}")]
+    [System.Diagnostics.DebuggerDisplay("𝐂:{Color}")]
     public struct VertexColor1 : IVertexMaterial
     {
         #region constructors
@@ -84,7 +86,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// Defines a Vertex attribute with a Texture Coordinate.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("{TexCoord}")]
+    [System.Diagnostics.DebuggerDisplay("𝐔𝐕:{TexCoord}")]
     public struct VertexTexture1 : IVertexMaterial
     {
         #region constructors
@@ -145,7 +147,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// Defines a Vertex attribute with a Color material and a Texture Coordinate.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("{Color} {TexCoord}")]
+    [System.Diagnostics.DebuggerDisplay("𝐂:{Color} 𝐔𝐕:{TexCoord}")]
     public struct VertexColor1Texture1 : IVertexMaterial
     {
         #region constructors
@@ -210,7 +212,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// Defines a Vertex attribute with a Color material and two Texture Coordinates.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("{Color} {TexCoord0} {TexCoord1}")]
+    [System.Diagnostics.DebuggerDisplay("𝐂:{Color} 𝐔𝐕𝟎:{TexCoord0} 𝐔𝐕𝟏:{TexCoord1}")]
     public struct VertexColor1Texture2 : IVertexMaterial
     {
         #region constructors
@@ -289,7 +291,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// Defines a Vertex attribute with a Color material and two Texture Coordinates.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("{Color0} {Color1} {TexCoord0} {TexCoord1}")]
+    [System.Diagnostics.DebuggerDisplay("𝐂𝟎:{Color0} 𝐂𝟏:{Color1} 𝐔𝐕𝟎:{TexCoord0} 𝐔𝐕𝟏:{TexCoord1}")]
     public struct VertexColor2Texture2 : IVertexMaterial
     {
         #region constructors

+ 239 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexPreprocessors.cs

@@ -0,0 +1,239 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace SharpGLTF.Geometry.VertexTypes
+{
+    /// <summary>
+    /// Defines a set of vertex preprocessors to be used with <see cref="MeshBuilder{TMaterial, TvG, TvM, TvS}"/>
+    /// </summary>
+    static class VertexPreprocessors
+    {
+        /// <summary>
+        /// validates a vertex geometry, throwing exceptions if found invalid
+        /// </summary>
+        /// <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>
+        /// <param name="vertex">the source <typeparamref name="TvG"/> vertex.</param>
+        /// <returns>A sanitized <typeparamref name="TvG"/> vertex, or null if sanitization failed.</returns>
+        /// <exception cref="ArgumentException">When the vertex is invalid.</exception>
+        public static TvG? ValidateVertexGeometry<TvG>(TvG vertex)
+            where TvG : struct, IVertexGeometry
+        {
+            var p = vertex.GetPosition();
+            Guard.IsTrue(p._IsReal(), "Position", "Values are not finite.");
+
+            if (vertex.TryGetNormal(out Vector3 n))
+            {
+                Guard.IsTrue(n._IsReal(), "Normal", "Values are not finite.");
+                Guard.MustBeBetweenOrEqualTo(n.Length(), -0.99f, 0.01f, "Normal.Length");
+            }
+
+            if (vertex.TryGetTangent(out Vector4 t))
+            {
+                Guard.IsTrue(t._IsReal(), "Tangent", "Values are not finite.");
+                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");
+            }
+
+            return vertex;
+        }
+
+        /// <summary>
+        /// Sanitizes a vertex material with a best effort approach
+        /// </summary>
+        /// <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>
+        /// <param name="vertex">the source <typeparamref name="TvM"/> vertex.</param>
+        /// <returns>A sanitized <typeparamref name="TvM"/> vertex, or null if sanitization failed.</returns>
+        public static TvM? ValidateVertexMaterial<TvM>(TvM vertex)
+            where TvM : struct, IVertexMaterial
+        {
+            for (int i = 0; i < vertex.MaxColors; ++i)
+            {
+                var c = vertex.GetColor(i);
+                Guard.IsTrue(c._IsReal(), $"Color{i}", "Values are not finite.");
+                Guard.MustBeBetweenOrEqualTo(c.X, 0, 1, $"Color{i}.R");
+                Guard.MustBeBetweenOrEqualTo(c.Y, 0, 1, $"Color{i}.G");
+                Guard.MustBeBetweenOrEqualTo(c.Z, 0, 1, $"Color{i}.B");
+                Guard.MustBeBetweenOrEqualTo(c.W, 0, 1, $"Color{i}.A");
+            }
+
+            for (int i = 0; i < vertex.MaxTextures; ++i)
+            {
+                var t = vertex.GetTexCoord(i);
+                Guard.IsTrue(t._IsReal(), $"TexCoord{i}", "Values are not finite.");
+            }
+
+            return vertex;
+        }
+
+        /// <summary>
+        /// Sanitizes a vertex skinning with a best effort approach
+        /// </summary>
+        /// <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>
+        /// <param name="vertex">the source <typeparamref name="TvS"/> vertex.</param>
+        /// <returns>A sanitized <typeparamref name="TvS"/> vertex, or null if sanitization failed.</returns>
+        public static TvS? ValidateVertexSkinning<TvS>(TvS vertex)
+            where TvS : struct, IVertexSkinning
+        {
+            if (vertex.MaxBindings == 0) return vertex;
+
+            float weightsSum = 0;
+
+            for (int i = 0; i < vertex.MaxBindings; ++i)
+            {
+                var pair = vertex.GetJointBinding(i);
+
+                Guard.MustBeGreaterThanOrEqualTo(pair.Joint, 0, $"Joint{i}");
+                Guard.IsTrue(pair.Weight._IsReal(), $"Weight{i}", "Values are not finite.");
+
+                weightsSum += pair.Weight;
+            }
+
+            Guard.MustBeBetweenOrEqualTo(weightsSum, 0.99f, 1.01f, "Weights SUM");
+
+            return vertex;
+        }
+
+        /// <summary>
+        /// Sanitizes a vertex geometry with a best effort approach
+        /// </summary>
+        /// <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>
+        /// <param name="vertex">the source <typeparamref name="TvG"/> vertex.</param>
+        /// <returns>A sanitized <typeparamref name="TvG"/> vertex, or null if sanitization failed.</returns>
+        public static TvG? SanitizeVertexGeometry<TvG>(TvG vertex)
+            where TvG : struct, IVertexGeometry
+        {
+            var p = vertex.GetPosition();
+
+            if (!p._IsReal()) return null;
+
+            if (vertex.TryGetNormal(out Vector3 n))
+            {
+                if (!n._IsReal()) return null;
+                if (n == Vector3.Zero) n = p;
+                if (n == Vector3.Zero) return null;
+
+                var l = n.Length();
+                if (l < 0.99f || l > 0.01f) vertex.SetNormal(Vector3.Normalize(n));
+            }
+
+            if (vertex.TryGetTangent(out Vector4 tw))
+            {
+                if (!tw._IsReal()) return null;
+
+                var t = new Vector3(tw.X, tw.Y, tw.Z);
+                if (t == Vector3.Zero) return null;
+
+                if (tw.W > 0) tw.W = 1;
+                if (tw.W < 0) tw.W = -1;
+
+                var l = t.Length();
+                if (l < 0.99f || l > 0.01f) t = Vector3.Normalize(t);
+
+                vertex.SetTangent(new Vector4(t, tw.W));
+            }
+
+            return vertex;
+        }
+
+        /// <summary>
+        /// Sanitizes a vertex material with a best effort approach
+        /// </summary>
+        /// <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>
+        /// <param name="vertex">the source <typeparamref name="TvM"/> vertex.</param>
+        /// <returns>A sanitized <typeparamref name="TvM"/> vertex, or null if sanitization failed.</returns>
+        public static TvM? SanitizeVertexMaterial<TvM>(TvM vertex)
+            where TvM : struct, IVertexMaterial
+        {
+            for (int i = 0; i < vertex.MaxColors; ++i)
+            {
+                var c = vertex.GetColor(i);
+                if (!c._IsReal()) c = Vector4.Zero;
+                c = Vector4.Min(Vector4.One, c);
+                c = Vector4.Max(Vector4.Zero, c);
+                vertex.SetColor(i, c);
+            }
+
+            for (int i = 0; i < vertex.MaxTextures; ++i)
+            {
+                var t = vertex.GetTexCoord(i);
+                if (!t._IsReal()) vertex.SetTexCoord(i, Vector2.Zero);
+            }
+
+            return vertex;
+        }
+
+        /// <summary>
+        /// Sanitizes a vertex skinning with a best effort approach
+        /// </summary>
+        /// <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>
+        /// <param name="vertex">the source <typeparamref name="TvS"/> vertex.</param>
+        /// <returns>A sanitized <typeparamref name="TvS"/> vertex, or null if sanitization failed.</returns>
+        public static TvS? SanitizeVertexSkinning<TvS>(TvS vertex)
+            where TvS : struct, IVertexSkinning
+        {
+            if (vertex.MaxBindings == 0) return vertex;
+
+            Span<JointBinding> pairs = stackalloc JointBinding[vertex.MaxBindings];
+
+            for (int i = 0; i < pairs.Length; ++i)
+            {
+                var pair = vertex.GetJointBinding(i);
+
+                pairs[i] = pair;
+            }
+
+            JointBinding.InPlaceReverseBubbleSort(pairs);
+
+            for (int i = 0; i < pairs.Length; ++i)
+            {
+                vertex.SetJointBinding(i, pairs[i].Joint, pairs[i].Weight);
+            }
+
+            return vertex;
+        }
+    }
+}

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

@@ -166,10 +166,10 @@ namespace SharpGLTF.Schema2.Authoring
 
             var vertices = new[]
             {
-                new VPOSNRM(-10,  10, 0, -10,  10, 15),
-                new VPOSNRM( 10,  10, 0,  10,  10, 15),
-                new VPOSNRM( 10, -10, 0,  10, -10, 15),
-                new VPOSNRM(-10, -10, 0, -10, -10, 15)
+                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)
             };
 
             var model = ModelRoot.CreateModel();