Преглед изворни кода

refactored normals and tangents generation code.

Vicente Penades пре 6 година
родитељ
комит
071b0fa453

+ 26 - 141
src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs

@@ -346,173 +346,58 @@ namespace SharpGLTF.Geometry
 
         #region utilites
 
-        public static void CalculateSmoothNormals(IReadOnlyList<(VertexBufferColumns Vertices, IEnumerable<(int A, int B, int C)> Indices)> primitives)
+        struct _NormalTangentAgent : VertexNormalsFactory.IMeshPrimitive, VertexTangentsFactory.IMeshPrimitive
         {
-            Guard.NotNull(primitives, nameof(primitives));
-
-            void addDirection(Dictionary<Vector3, Vector3> dict, Vector3 pos, Vector3 dir)
+            public _NormalTangentAgent(VertexBufferColumns vertices, IEnumerable<(int A, int B, int C)> indices)
             {
-                if (!dir._IsFinite()) return;
-                if (!dict.TryGetValue(pos, out Vector3 n)) n = Vector3.Zero;
-                dict[pos] = n + dir;
+                _Vertices = vertices;
+                _Indices = indices;
             }
 
-            // calculate
+            private readonly VertexBufferColumns _Vertices;
+            private readonly IEnumerable<(int A, int B, int C)> _Indices;
 
-            var normalMap = new Dictionary<Vector3, Vector3>();
+            public int VertexCount => _Vertices.Positions.Count;
 
-            foreach (var (vertices, indices) in primitives)
-            {
-                foreach (var (ta, tb, tc) in indices)
-                {
-                    var p1 = vertices.Positions[ta];
-                    var p2 = vertices.Positions[tb];
-                    var p3 = vertices.Positions[tc];
+            public IEnumerable<(int A, int B, int C)> GetTriangleIndices() { return _Indices; }
 
-                    var d = Vector3.Cross(p2 - p1, p3 - p1);
+            public Vector3 GetVertexPosition(int idx) { return _Vertices.Positions[idx]; }
 
-                    addDirection(normalMap, p1, d);
-                    addDirection(normalMap, p2, d);
-                    addDirection(normalMap, p3, d);
-                }
-            }
+            public Vector3 GetVertexNormal(int idx) { return _Vertices.Normals[idx]; }
 
-            // normalize
+            public Vector2 GetVertexTexCoord(int idx) { return _Vertices.TexCoords0[idx]; }
 
-            foreach (var pos in normalMap.Keys.ToList())
+            public void SetVertexNormal(int idx, Vector3 normal)
             {
-                var nrm = Vector3.Normalize(normalMap[pos]);
+                if (_Vertices.Normals == null) _Vertices.Normals = new Vector3[_Vertices.Positions.Count];
 
-                normalMap[pos] = nrm._IsFinite() && nrm.LengthSquared() > 0.5f ? nrm : Vector3.UnitZ;
+                _Vertices.Normals[idx] = normal;
             }
 
-            // apply
-
-            foreach (var (vertices, indices) in primitives)
+            public void SetVertexTangent(int idx, Vector4 tangent)
             {
-                vertices.Normals = new Vector3[vertices.Positions.Count];
+                if (_Vertices.Tangents == null) _Vertices.Tangents = new Vector4[_Vertices.Positions.Count];
 
-                for (int i = 0; i < vertices.Positions.Count; ++i)
-                {
-                    if (normalMap.TryGetValue(vertices.Positions[i],out Vector3 nrm))
-                    {
-                        vertices.Normals[i] = nrm;
-                    }
-                    else
-                    {
-                        vertices.Normals[i] = Vector3.UnitZ;
-                    }
-                }
+                _Vertices.Tangents[idx] = tangent;
             }
         }
 
-        public static void CalculateTangents(IReadOnlyList<(VertexBufferColumns Vertices, IEnumerable<(int A, int B, int C)> Indices)> primitives)
+        public static void CalculateSmoothNormals(IReadOnlyList<(VertexBufferColumns Vertices, IEnumerable<(int A, int B, int C)> Indices)> primitives)
         {
-            // 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://github.com/buildaworldnet/IrrlichtBAW/wiki/How-to-Normal-Detail-Bump-Derivative-Map,-why-Mikkelsen-is-slightly-wrong-and-why-you-should-give-up-on-calculating-per-vertex-tangents
-            // https://gamedev.stackexchange.com/questions/68612/how-to-compute-tangent-and-bitangent-vectors
-            // https://www.marti.works/calculating-tangents-for-your-mesh/
-            // https://www.html5gamedevs.com/topic/34364-gltf-support-and-mikkt-space/
-
             Guard.NotNull(primitives, nameof(primitives));
 
-            void addTangent(Dictionary<(Vector3 pos, Vector3 nrm, Vector2 uv), (Vector3, Vector3)> dict, (Vector3 pos, Vector3 nrm, Vector2 uv) key, (Vector3 tu, Vector3 tv) alpha)
-            {
-                dict.TryGetValue(key, out (Vector3 tu, Vector3 tv) beta);
-
-                dict[key] = (alpha.tu + beta.tu, alpha.tv + beta.tv);
-            }
-
-            // calculate
-
-            var tangentsMap = new Dictionary<(Vector3 pos, Vector3 nrm, Vector2 uv), (Vector3 u, Vector3 v)>();
-
-            foreach (var (vertices, indices) in primitives)
-            {
-                vertices.Tangents = new Vector4[vertices.Positions.Count];
-
-                foreach (var (i1, i2, i3) in indices)
-                {
-                    var p1 = vertices.Positions[i1];
-                    var p2 = vertices.Positions[i2];
-                    var p3 = vertices.Positions[i3];
-
-                    // check for degenerated triangle
-                    if (p1 == p2 || p1 == p3 || p2 == p3) continue;
-
-                    var uv1 = vertices.TexCoords0[i1];
-                    var uv2 = vertices.TexCoords0[i2];
-                    var uv3 = vertices.TexCoords0[i3];
+            var agents = primitives.Select(item => new _NormalTangentAgent(item.Vertices, item.Indices)).ToList();
 
-                    // check for degenerated triangle
-                    if (uv1 == uv2 || uv1 == uv3 || uv2 == uv3) continue;
-
-                    var n1 = vertices.Normals[i1];
-                    var n2 = vertices.Normals[i2];
-                    var n3 = vertices.Normals[i3];
-
-                    var svec = p2 - p1;
-                    var tvec = p3 - p1;
-
-                    var stex = uv2 - uv1;
-                    var ttex = uv3 - uv1;
-
-                    float sx = stex.X;
-                    float tx = ttex.X;
-                    float sy = stex.Y;
-                    float ty = ttex.Y;
-
-                    var r = 1.0F / ((sx * ty) - (tx * sy));
-
-                    if (!r._IsFinite()) continue;
-
-                    var sdir = new Vector3((ty * svec.X) - (sy * tvec.X), (ty * svec.Y) - (sy * tvec.Y), (ty * svec.Z) - (sy * tvec.Z) ) * r;
-                    var tdir = new Vector3((sx * tvec.X) - (tx * svec.X), (sx * tvec.Y) - (tx * svec.Y), (sx * tvec.Z) - (tx * svec.Z) ) * r;
-
-                    addTangent(tangentsMap, (p1, n1, uv1), (sdir, tdir));
-                    addTangent(tangentsMap, (p2, n2, uv2), (sdir, tdir));
-                    addTangent(tangentsMap, (p3, n3, uv3), (sdir, tdir));
-                }
-            }
-
-            // normalize
-
-            foreach (var key in tangentsMap.Keys.ToList())
-            {
-                var val = tangentsMap[key];
-
-                // Gram-Schmidt orthogonalize
-                val.u = Vector3.Normalize(val.u - (key.nrm * Vector3.Dot(key.nrm, val.u)));
-                val.v = Vector3.Normalize(val.v - (key.nrm * Vector3.Dot(key.nrm, val.v)));
-
-                tangentsMap[key] = val;
-            }
+            VertexNormalsFactory.CalculateSmoothNormals(agents);
+        }
 
-            // apply
+        public static void CalculateTangents(IReadOnlyList<(VertexBufferColumns Vertices, IEnumerable<(int A, int B, int C)> Indices)> primitives)
+        {
+            Guard.NotNull(primitives, nameof(primitives));
 
-            foreach (var (vertices, indices) in primitives)
-            {
-                vertices.Tangents = new Vector4[vertices.Tangents.Count];
+            var agents = primitives.Select(item => new _NormalTangentAgent(item.Vertices, item.Indices)).ToList();
 
-                for (int i = 0; i < vertices.Positions.Count; ++i)
-                {
-                    var p = vertices.Positions[i];
-                    var n = vertices.Normals[i];
-                    var t = vertices.TexCoords0[i];
-
-                    if (tangentsMap.TryGetValue((p, n, t), out (Vector3 u, Vector3 v) tangents))
-                    {
-                        var handedness = Vector3.Dot(Vector3.Cross(tangents.u, n), tangents.v) < 0 ? -1.0f : 1.0f;
-
-                        vertices.Tangents[i] = new Vector4(tangents.u, handedness);
-                    }
-                    else
-                    {
-                        vertices.Tangents[i] = new Vector4(1, 0, 0, 1);
-                    }
-                }
-            }
+            VertexTangentsFactory.CalculateTangents(agents);
         }
 
         #endregion

+ 85 - 0
src/SharpGLTF.Toolkit/Geometry/VertexNormalsFactory.cs

@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+
+namespace SharpGLTF.Geometry
+{
+    static class VertexNormalsFactory
+    {
+        #pragma warning disable CA1034 // Nested types should not be visible
+        public interface IMeshPrimitive
+        #pragma warning restore CA1034 // Nested types should not be visible
+        {
+            int VertexCount { get; }
+
+            Vector3 GetVertexPosition(int idx);
+
+            void SetVertexNormal(int idx, Vector3 normal);
+
+            IEnumerable<(int A, int B, int C)> GetTriangleIndices();
+        }
+
+        public static void CalculateSmoothNormals<T>(IReadOnlyList<T> primitives)
+            where T : IMeshPrimitive
+        {
+            Guard.NotNull(primitives, nameof(primitives));
+
+            var normalMap = new Dictionary<Vector3, Vector3>();
+
+            // calculate
+
+            foreach (var primitive in primitives)
+            {
+                foreach (var (ta, tb, tc) in primitive.GetTriangleIndices())
+                {
+                    var p1 = primitive.GetVertexPosition(ta);
+                    var p2 = primitive.GetVertexPosition(tb);
+                    var p3 = primitive.GetVertexPosition(tc);
+
+                    var d = Vector3.Cross(p2 - p1, p3 - p1);
+
+                    _AddDirection(normalMap, p1, d);
+                    _AddDirection(normalMap, p2, d);
+                    _AddDirection(normalMap, p3, d);
+                }
+            }
+
+            // normalize
+
+            foreach (var pos in normalMap.Keys.ToList())
+            {
+                var nrm = Vector3.Normalize(normalMap[pos]);
+
+                normalMap[pos] = nrm._IsFinite() && nrm.LengthSquared() > 0.5f ? nrm : Vector3.UnitZ;
+            }
+
+            // apply
+
+            foreach (var primitive in primitives)
+            {
+                for (int i = 0; i < primitive.VertexCount; ++i)
+                {
+                    var pos = primitive.GetVertexPosition(i);
+
+                    if (normalMap.TryGetValue(pos, out Vector3 nrm))
+                    {
+                        primitive.SetVertexNormal(i, nrm);
+                    }
+                    else
+                    {
+                        primitive.SetVertexNormal(i, Vector3.UnitZ);
+                    }
+                }
+            }
+        }
+
+        private static void _AddDirection(Dictionary<Vector3, Vector3> dict, Vector3 pos, Vector3 dir)
+        {
+            if (!dir._IsFinite()) return;
+            if (!dict.TryGetValue(pos, out Vector3 n)) n = Vector3.Zero;
+            dict[pos] = n + dir;
+        }
+    }
+}

+ 139 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTangentsFactory.cs

@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+
+namespace SharpGLTF.Geometry
+{
+    using VERTEXKEY = System.ValueTuple<System.Numerics.Vector3, System.Numerics.Vector3, System.Numerics.Vector2>;
+
+    class VertexTangentsFactory
+    {
+        // 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://github.com/buildaworldnet/IrrlichtBAW/wiki/How-to-Normal-Detail-Bump-Derivative-Map,-why-Mikkelsen-is-slightly-wrong-and-why-you-should-give-up-on-calculating-per-vertex-tangents
+        // https://gamedev.stackexchange.com/questions/68612/how-to-compute-tangent-and-bitangent-vectors
+        // https://www.marti.works/calculating-tangents-for-your-mesh/
+        // https://www.html5gamedevs.com/topic/34364-gltf-support-and-mikkt-space/
+
+        public interface IMeshPrimitive
+        {
+            int VertexCount { get; }
+
+            Vector3 GetVertexPosition(int idx);
+            Vector3 GetVertexNormal(int idx);
+            Vector2 GetVertexTexCoord(int idx);
+
+            void SetVertexTangent(int idx, Vector4 tangent);
+
+            IEnumerable<(int A, int B, int C)> GetTriangleIndices();
+        }
+
+        public static void CalculateTangents<T>(IReadOnlyList<T> primitives)
+            where T : IMeshPrimitive
+        {
+            Guard.NotNull(primitives, nameof(primitives));
+
+            var tangentsMap = new Dictionary<VERTEXKEY, (Vector3 u, Vector3 v)>();
+
+            // calculate
+
+            foreach (var primitive in primitives)
+            {
+                foreach (var (i1, i2, i3) in primitive.GetTriangleIndices())
+                {
+                    var p1 = primitive.GetVertexPosition(i1);
+                    var p2 = primitive.GetVertexPosition(i2);
+                    var p3 = primitive.GetVertexPosition(i3);
+
+                    // check for degenerated triangle
+                    if (p1 == p2 || p1 == p3 || p2 == p3) continue;
+
+                    var uv1 = primitive.GetVertexTexCoord(i1);
+                    var uv2 = primitive.GetVertexTexCoord(i2);
+                    var uv3 = primitive.GetVertexTexCoord(i3);
+
+                    // check for degenerated triangle
+                    if (uv1 == uv2 || uv1 == uv3 || uv2 == uv3) continue;
+
+                    var n1 = primitive.GetVertexNormal(i1);
+                    var n2 = primitive.GetVertexNormal(i2);
+                    var n3 = primitive.GetVertexNormal(i3);
+
+                    // calculate tangents
+
+                    var svec = p2 - p1;
+                    var tvec = p3 - p1;
+
+                    var stex = uv2 - uv1;
+                    var ttex = uv3 - uv1;
+
+                    float sx = stex.X;
+                    float tx = ttex.X;
+                    float sy = stex.Y;
+                    float ty = ttex.Y;
+
+                    var r = 1.0F / ((sx * ty) - (tx * sy));
+
+                    if (!r._IsFinite()) continue;
+
+                    var sdir = new Vector3((ty * svec.X) - (sy * tvec.X), (ty * svec.Y) - (sy * tvec.Y), (ty * svec.Z) - (sy * tvec.Z)) * r;
+                    var tdir = new Vector3((sx * tvec.X) - (tx * svec.X), (sx * tvec.Y) - (tx * svec.Y), (sx * tvec.Z) - (tx * svec.Z)) * r;
+
+                    if (!sdir._IsFinite()) continue;
+                    if (!tdir._IsFinite()) continue;
+
+                    // accumulate tangents
+
+                    _AddTangent(tangentsMap, (p1, n1, uv1), (sdir, tdir));
+                    _AddTangent(tangentsMap, (p2, n2, uv2), (sdir, tdir));
+                    _AddTangent(tangentsMap, (p3, n3, uv3), (sdir, tdir));
+                }
+            }
+
+            // normalize
+
+            foreach (var key in tangentsMap.Keys.ToList())
+            {
+                var val = tangentsMap[key];
+
+                // Gram-Schmidt orthogonalize
+                val.u = Vector3.Normalize(val.u - (key.Item2 * Vector3.Dot(key.Item2, val.u)));
+                val.v = Vector3.Normalize(val.v - (key.Item2 * Vector3.Dot(key.Item2, val.v)));
+
+                tangentsMap[key] = val;
+            }
+
+            // apply
+
+            foreach (var primitive in primitives)
+            {
+                for (int i = 0; i < primitive.VertexCount; ++i)
+                {
+                    var p = primitive.GetVertexPosition(i);
+                    var n = primitive.GetVertexNormal(i);
+                    var t = primitive.GetVertexTexCoord(i);
+
+                    if (tangentsMap.TryGetValue((p, n, t), out (Vector3 u, Vector3 v) tangents))
+                    {
+                        var handedness = Vector3.Dot(Vector3.Cross(tangents.u, n), tangents.v) < 0 ? -1.0f : 1.0f;
+
+                        primitive.SetVertexTangent(i, new Vector4(tangents.u, handedness));
+                    }
+                    else
+                    {
+                        primitive.SetVertexTangent(i, new Vector4(1, 0, 0, 1));
+                    }
+                }
+            }
+        }
+
+        private static void _AddTangent(Dictionary<VERTEXKEY, (Vector3, Vector3)> dict, VERTEXKEY key, (Vector3 tu, Vector3 tv) alpha)
+        {
+            dict.TryGetValue(key, out (Vector3 tu, Vector3 tv) beta);
+
+            dict[key] = (alpha.tu + beta.tu, alpha.tv + beta.tv);
+        }
+    }
+}

+ 0 - 42
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -343,48 +343,6 @@ namespace SharpGLTF.Schema2
 
         #region evaluation
 
-        /// <summary>
-        /// Calculates a default set of smooth normals for the given mesh.
-        /// </summary>
-        /// <param name="mesh">A <see cref="Mesh"/> instance.</param>
-        /// <returns>A <see cref="Dictionary{TKey, TValue}"/> where the keys represent positions and the values represent Normals.</returns>
-        public static Dictionary<Vector3, Vector3> GetSmoothNormals(this Mesh mesh)
-        {
-            if (mesh == null) return null;
-
-            var posnrm = new Dictionary<Vector3, Vector3>();
-
-            void addDirection(Dictionary<Vector3, Vector3> dict, Vector3 pos, Vector3 dir)
-            {
-                if (!dir._IsFinite()) return;
-                if (!dict.TryGetValue(pos, out Vector3 n)) n = Vector3.Zero;
-                dict[pos] = n + dir;
-            }
-
-            foreach (var p in mesh.Primitives)
-            {
-                var positions = p.GetVertexAccessor("POSITION").AsVector3Array();
-
-                foreach (var (ta, tb, tc) in p.GetTriangleIndices())
-                {
-                    var p1 = positions[ta];
-                    var p2 = positions[tb];
-                    var p3 = positions[tc];
-                    var d = Vector3.Cross(p2 - p1, p3 - p1);
-                    addDirection(posnrm, p1, d);
-                    addDirection(posnrm, p2, d);
-                    addDirection(posnrm, p3, d);
-                }
-            }
-
-            foreach (var pos in posnrm.Keys.ToList())
-            {
-                posnrm[pos] = Vector3.Normalize(posnrm[pos]);
-            }
-
-            return posnrm;
-        }
-
         public static IEnumerable<(IVertexBuilder A, Material Material)> EvaluatePoints(this Mesh mesh, MESHXFORM xform = null)
         {
             if (mesh == null) return Enumerable.Empty<(IVertexBuilder, Material)>();