Browse Source

progress with Evaluation

Vicente Penades 6 years ago
parent
commit
67db6a244b

+ 93 - 0
src/Shared/_Extensions.cs

@@ -316,6 +316,99 @@ namespace SharpGLTF
             }
             }
         }
         }
 
 
+        public static IEnumerable<(int, int, int)> GetTrianglesIndices(this PrimitiveType ptype, int count)
+        {
+            return ptype.GetTrianglesIndices(Enumerable.Range(0, count).Select(item => (UInt32)item));
+        }
+
+        public static IEnumerable<(int, int, int)> GetTrianglesIndices(this PrimitiveType ptype, IEnumerable<UInt32> indices)
+        {
+            switch (ptype)
+            {
+                case PrimitiveType.TRIANGLES:
+                    {
+                        using (var ator = indices.GetEnumerator())
+                        {
+                            while (true)
+                            {
+                                if (!ator.MoveNext()) break;
+                                var a = ator.Current;
+                                if (!ator.MoveNext()) break;
+                                var b = ator.Current;
+                                if (!ator.MoveNext()) break;
+                                var c = ator.Current;
+
+                                if (!_IsDegenerated(a,b,c)) yield return ((int)a, (int)b, (int)c);
+                            }
+                        }
+
+                        break;
+                    }
+
+                case PrimitiveType.TRIANGLE_FAN:
+                    {
+                        using (var ator = indices.GetEnumerator())
+                        {
+                            if (!ator.MoveNext()) break;
+                            var a = ator.Current;
+                            if (!ator.MoveNext()) break;
+                            var b = ator.Current;
+
+                            while (true)
+                            {
+                                if (!ator.MoveNext()) break;
+                                var c = ator.Current;
+
+                                if (!_IsDegenerated(a, b, c)) yield return ((int)a, (int)b, (int)c);
+
+                                b = c;
+                            }
+                        }
+
+                        break;
+                    }
+
+                case PrimitiveType.TRIANGLE_STRIP:
+                    {
+                        using (var ator = indices.GetEnumerator())
+                        {
+                            if (!ator.MoveNext()) break;
+                            var a = ator.Current;
+                            if (!ator.MoveNext()) break;
+                            var b = ator.Current;
+
+                            bool reversed = false;
+
+                            while (true)
+                            {
+                                if (!ator.MoveNext()) break;
+                                var c = ator.Current;
+
+                                if (!_IsDegenerated(a, b, c))
+                                {
+                                    if (reversed) yield return ((int)b, (int)a, (int)c);
+                                    else yield return ((int)a, (int)b, (int)c);
+                                }
+
+                                a = b;
+                                b = c;
+                                reversed = !reversed;
+                            }
+                        }
+
+                        break;
+                    }
+            }
+        }
+
+        private static bool _IsDegenerated(uint a, uint b, uint c)
+        {
+            if (a == b) return true;
+            if (a == c) return true;
+            if (b == c) return true;
+            return false;
+        }
+
         #endregion
         #endregion
 
 
         #region serialization
         #region serialization

+ 11 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexColumns.cs

@@ -67,6 +67,17 @@ namespace SharpGLTF.Geometry.VertexTypes
             return cctt;
             return cctt;
         }
         }
 
 
+        public TvJ GetJointsFragment<TvJ>(int index)
+            where TvJ : struct, IVertexJoints
+        {
+            var jjjj = default(TvJ);
+
+            if (Joints0 != null && Weights0 != null) jjjj.SetJoints(0, Joints0[index], Weights0[index]);
+            if (Joints1 != null && Weights1 != null) jjjj.SetJoints(1, Joints1[index], Weights1[index]);
+
+            return jjjj;
+        }
+
         #endregion
         #endregion
     }
     }
 }
 }

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

@@ -15,6 +15,10 @@ namespace SharpGLTF.Geometry.VertexTypes
         {
         {
         }
         }
 
 
+        void IVertexJoints.SetJoints(int jointSet, Vector4 joints, Vector4 weights)
+        {
+        }
+
         public void Validate()
         public void Validate()
         {
         {
         }
         }

+ 72 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexJoints.cs

@@ -7,6 +7,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 {
 {
     public interface IVertexJoints
     public interface IVertexJoints
     {
     {
+        void SetJoints(int jointSet, Vector4 joints, Vector4 weights);
+
         void Validate();
         void Validate();
     }
     }
 
 
@@ -15,6 +17,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// </summary>
     /// </summary>
     public struct VertexJoints8x4 : IVertexJoints
     public struct VertexJoints8x4 : IVertexJoints
     {
     {
+        #region constructors
+
         public VertexJoints8x4(int jointIndex)
         public VertexJoints8x4(int jointIndex)
         {
         {
             Joints = new Vector4(jointIndex);
             Joints = new Vector4(jointIndex);
@@ -27,12 +31,25 @@ namespace SharpGLTF.Geometry.VertexTypes
             Weights = new Vector4(0.5f, 0.5f, 0, 0);
             Weights = new Vector4(0.5f, 0.5f, 0, 0);
         }
         }
 
 
+        #endregion
+
+        #region data
+
         [VertexAttribute("JOINTS_0", Schema2.EncodingType.UNSIGNED_BYTE, false)]
         [VertexAttribute("JOINTS_0", Schema2.EncodingType.UNSIGNED_BYTE, false)]
         public Vector4 Joints;
         public Vector4 Joints;
 
 
         [VertexAttribute("WEIGHTS_0", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         [VertexAttribute("WEIGHTS_0", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         public Vector4 Weights;
         public Vector4 Weights;
 
 
+        #endregion
+
+        #region API
+
+        void IVertexJoints.SetJoints(int jointSet, Vector4 joints, Vector4 weights)
+        {
+            if (jointSet == 0) { this.Joints = joints; this.Weights = weights; }
+        }
+
         public void Validate()
         public void Validate()
         {
         {
             if (!Joints._IsReal()) throw new NotFiniteNumberException(nameof(Joints));
             if (!Joints._IsReal()) throw new NotFiniteNumberException(nameof(Joints));
@@ -40,6 +57,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
             if (!Weights._IsReal()) throw new NotFiniteNumberException(nameof(Weights));
             if (!Weights._IsReal()) throw new NotFiniteNumberException(nameof(Weights));
         }
         }
+
+        #endregion
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -47,6 +66,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// </summary>
     /// </summary>
     public struct VertexJoints16x4 : IVertexJoints
     public struct VertexJoints16x4 : IVertexJoints
     {
     {
+        #region constructors
+
         public VertexJoints16x4(int jointIndex)
         public VertexJoints16x4(int jointIndex)
         {
         {
             Joints = new Vector4(jointIndex);
             Joints = new Vector4(jointIndex);
@@ -59,12 +80,25 @@ namespace SharpGLTF.Geometry.VertexTypes
             Weights = new Vector4(0.5f, 0.5f, 0, 0);
             Weights = new Vector4(0.5f, 0.5f, 0, 0);
         }
         }
 
 
+        #endregion
+
+        #region data
+
         [VertexAttribute("JOINTS_0", Schema2.EncodingType.UNSIGNED_SHORT, false)]
         [VertexAttribute("JOINTS_0", Schema2.EncodingType.UNSIGNED_SHORT, false)]
         public Vector4 Joints;
         public Vector4 Joints;
 
 
         [VertexAttribute("WEIGHTS_0", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         [VertexAttribute("WEIGHTS_0", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         public Vector4 Weights;
         public Vector4 Weights;
 
 
+        #endregion
+
+        #region API
+
+        void IVertexJoints.SetJoints(int jointSet, Vector4 joints, Vector4 weights)
+        {
+            if (jointSet == 0) { this.Joints = joints; this.Weights = weights; }
+        }
+
         public void Validate()
         public void Validate()
         {
         {
             if (!Joints._IsReal()) throw new NotFiniteNumberException(nameof(Joints));
             if (!Joints._IsReal()) throw new NotFiniteNumberException(nameof(Joints));
@@ -72,6 +106,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
             if (!Weights._IsReal()) throw new NotFiniteNumberException(nameof(Weights));
             if (!Weights._IsReal()) throw new NotFiniteNumberException(nameof(Weights));
         }
         }
+
+        #endregion
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -79,6 +115,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// </summary>
     /// </summary>
     public struct VertexJoints8x8 : IVertexJoints
     public struct VertexJoints8x8 : IVertexJoints
     {
     {
+        #region constructors
+
         public VertexJoints8x8(int jointIndex)
         public VertexJoints8x8(int jointIndex)
         {
         {
             Joints0 = new Vector4(jointIndex);
             Joints0 = new Vector4(jointIndex);
@@ -95,6 +133,10 @@ namespace SharpGLTF.Geometry.VertexTypes
             Weights1 = Vector4.Zero;
             Weights1 = Vector4.Zero;
         }
         }
 
 
+        #endregion
+
+        #region data
+
         [VertexAttribute("JOINTS_0", Schema2.EncodingType.UNSIGNED_BYTE, false)]
         [VertexAttribute("JOINTS_0", Schema2.EncodingType.UNSIGNED_BYTE, false)]
         public Vector4 Joints0;
         public Vector4 Joints0;
 
 
@@ -107,6 +149,16 @@ namespace SharpGLTF.Geometry.VertexTypes
         [VertexAttribute("WEIGHTS_1", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         [VertexAttribute("WEIGHTS_1", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         public Vector4 Weights1;
         public Vector4 Weights1;
 
 
+        #endregion
+
+        #region API
+
+        void IVertexJoints.SetJoints(int jointSet, Vector4 joints, Vector4 weights)
+        {
+            if (jointSet == 0) { this.Joints0 = joints; this.Weights0 = weights; }
+            if (jointSet == 1) { this.Joints1 = joints; this.Weights1 = weights; }
+        }
+
         public void Validate()
         public void Validate()
         {
         {
             if (!Joints0._IsReal()) throw new NotFiniteNumberException(nameof(Joints0));
             if (!Joints0._IsReal()) throw new NotFiniteNumberException(nameof(Joints0));
@@ -118,6 +170,8 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!Weights0._IsReal()) throw new NotFiniteNumberException(nameof(Weights0));
             if (!Weights0._IsReal()) throw new NotFiniteNumberException(nameof(Weights0));
             if (!Weights1._IsReal()) throw new NotFiniteNumberException(nameof(Weights1));
             if (!Weights1._IsReal()) throw new NotFiniteNumberException(nameof(Weights1));
         }
         }
+
+        #endregion
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -125,6 +179,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// </summary>
     /// </summary>
     public struct VertexJoints16x8 : IVertexJoints
     public struct VertexJoints16x8 : IVertexJoints
     {
     {
+        #region constructors
+
         public VertexJoints16x8(int jointIndex)
         public VertexJoints16x8(int jointIndex)
         {
         {
             Joints0 = new Vector4(jointIndex);
             Joints0 = new Vector4(jointIndex);
@@ -141,6 +197,10 @@ namespace SharpGLTF.Geometry.VertexTypes
             Weights1 = Vector4.Zero;
             Weights1 = Vector4.Zero;
         }
         }
 
 
+        #endregion
+
+        #region data
+
         [VertexAttribute("JOINTS_0", Schema2.EncodingType.UNSIGNED_SHORT, false)]
         [VertexAttribute("JOINTS_0", Schema2.EncodingType.UNSIGNED_SHORT, false)]
         public Vector4 Joints0;
         public Vector4 Joints0;
 
 
@@ -153,6 +213,16 @@ namespace SharpGLTF.Geometry.VertexTypes
         [VertexAttribute("WEIGHTS_1", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         [VertexAttribute("WEIGHTS_1", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         public Vector4 Weights1;
         public Vector4 Weights1;
 
 
+        #endregion
+
+        #region API
+
+        void IVertexJoints.SetJoints(int jointSet, Vector4 joints, Vector4 weights)
+        {
+            if (jointSet == 0) { this.Joints0 = joints; this.Weights0 = weights; }
+            if (jointSet == 1) { this.Joints1 = joints; this.Weights1 = weights; }
+        }
+
         public void Validate()
         public void Validate()
         {
         {
             if (!Joints0._IsReal()) throw new NotFiniteNumberException(nameof(Joints0));
             if (!Joints0._IsReal()) throw new NotFiniteNumberException(nameof(Joints0));
@@ -164,5 +234,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!Weights0._IsReal()) throw new NotFiniteNumberException(nameof(Weights0));
             if (!Weights0._IsReal()) throw new NotFiniteNumberException(nameof(Weights0));
             if (!Weights1._IsReal()) throw new NotFiniteNumberException(nameof(Weights1));
             if (!Weights1._IsReal()) throw new NotFiniteNumberException(nameof(Weights1));
         }
         }
+
+        #endregion
     }
     }
 }
 }

+ 0 - 88
src/SharpGLTF.Toolkit/IO/EvaluationUtils.cs

@@ -1,88 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-using System.Text;
-
-namespace SharpGLTF.IO
-{
-    using Schema2;
-
-    using VERTEX = ValueTuple<Geometry.VertexTypes.VertexPositionNormal, Geometry.VertexTypes.VertexTexture1>;
-
-    /// <summary>
-    /// Utility class that hierarchicaly traverses a gltf model, evaluates the geometry and yields a collection of worldspace triangles.
-    /// </summary>
-    static class EvaluationUtils
-    {
-        /// <summary>
-        /// Yields all the triangles in the model's default scene, in world space
-        /// </summary>
-        /// <param name="model">The input model</param>
-        /// <returns>A collection of worldspace triangles</returns>
-        public static IEnumerable<(VERTEX, VERTEX, VERTEX, Material)> Triangulate(this ModelRoot model)
-        {
-            return model.DefaultScene.Triangulate();
-        }
-
-        /// <summary>
-        /// Yields all the triangles in the scene, in world space
-        /// </summary>
-        /// <param name="scene">The input scene</param>
-        /// <returns>A collection of worldspace triangles</returns>
-        public static IEnumerable<(VERTEX, VERTEX, VERTEX, Material)> Triangulate(this Scene scene)
-        {
-            return Node.Flatten(scene).SelectMany(item => item.Triangulate(true));
-        }
-
-        /// <summary>
-        /// Yields all the triangles in the node
-        /// </summary>
-        /// <param name="node">The input node</param>
-        /// <param name="inWorldSpace">true if we want to transform the local triangles to worldspace</param>
-        /// <returns>A collection of triangles in the node's space, or in worldspace</returns>
-        public static IEnumerable<(VERTEX, VERTEX, VERTEX, Material)> Triangulate(this Node node, bool inWorldSpace)
-        {
-            var mesh = node.Mesh;
-            if (mesh == null) return Enumerable.Empty<(VERTEX, VERTEX, VERTEX, Material)>();
-
-            var xform = inWorldSpace ? node.WorldMatrix : Matrix4x4.Identity;
-
-            var normals = mesh.ComputeNormals();
-
-            return mesh.Primitives.SelectMany(item => item.Triangulate(normals, xform));
-        }
-
-        /// <summary>
-        /// Yields all the triangles in the mesh
-        /// </summary>
-        /// <param name="prim">The input primitive</param>
-        /// <param name="normals">A dictionary mapping positions to normals</param>
-        /// <param name="xform">The transform matrix</param>
-        /// <returns>A collection of triangles transformed by <paramref name="xform"/> </returns>
-        public static IEnumerable<(VERTEX, VERTEX, VERTEX, Material)> Triangulate(this MeshPrimitive prim, IReadOnlyDictionary<Vector3, Vector3> normals, Matrix4x4 xform)
-        {
-            var vertices = prim.GetVertexColumns();
-            if (vertices.Normals == null) vertices.SetNormals(normals);
-
-            var triangles = prim.GetTriangleIndices();
-
-            foreach (var t in triangles)
-            {
-                var ap = vertices.GetPositionFragment<Geometry.VertexTypes.VertexPositionNormal>(t.Item1);
-                var bp = vertices.GetPositionFragment<Geometry.VertexTypes.VertexPositionNormal>(t.Item2);
-                var cp = vertices.GetPositionFragment<Geometry.VertexTypes.VertexPositionNormal>(t.Item3);
-
-                ap.Transform(xform);
-                bp.Transform(xform);
-                cp.Transform(xform);
-
-                var at = vertices.GetMaterialFragment<Geometry.VertexTypes.VertexTexture1>(t.Item1);
-                var bt = vertices.GetMaterialFragment<Geometry.VertexTypes.VertexTexture1>(t.Item2);
-                var ct = vertices.GetMaterialFragment<Geometry.VertexTypes.VertexTexture1>(t.Item3);
-
-                yield return ((ap, at), (bp, bt), (cp, ct), prim.Material);
-            }
-        }
-    }
-}

+ 23 - 9
src/SharpGLTF.Toolkit/IO/WavefrontWriter.cs

@@ -13,8 +13,9 @@ namespace SharpGLTF.IO
 
 
     using POSITION = Geometry.VertexTypes.VertexPositionNormal;
     using POSITION = Geometry.VertexTypes.VertexPositionNormal;
     using TEXCOORD = Geometry.VertexTypes.VertexTexture1;
     using TEXCOORD = Geometry.VertexTypes.VertexTexture1;
+    using VEMPTY = Geometry.VertexTypes.VertexEmpty;
 
 
-    using VERTEX = ValueTuple<Geometry.VertexTypes.VertexPositionNormal, Geometry.VertexTypes.VertexTexture1>;
+    using VERTEX = ValueTuple<Geometry.VertexTypes.VertexPositionNormal, Geometry.VertexTypes.VertexTexture1, Geometry.VertexTypes.VertexEmpty>;
 
 
     /// <summary>
     /// <summary>
     /// Tiny wavefront object writer
     /// Tiny wavefront object writer
@@ -26,7 +27,8 @@ namespace SharpGLTF.IO
 
 
         public struct Material
         public struct Material
         {
         {
-            public Vector4 DiffuseColor;
+            public Vector3 DiffuseColor;
+            public Vector3 SpecularColor;
             public BYTES DiffuseTexture;
             public BYTES DiffuseTexture;
         }
         }
 
 
@@ -70,16 +72,21 @@ namespace SharpGLTF.IO
         private IReadOnlyDictionary<Material, string> _WriteMaterials(IDictionary<String, BYTES> files, string baseName, IEnumerable<Material> materials)
         private IReadOnlyDictionary<Material, string> _WriteMaterials(IDictionary<String, BYTES> files, string baseName, IEnumerable<Material> materials)
         {
         {
             // write all image files
             // write all image files
-            var images = materials.Select(item => item.DiffuseTexture);
+            var images = materials
+                .Select(item => item.DiffuseTexture)
+                .Where(item => item.Array != null)
+                .Distinct();
 
 
-            foreach (var img in images.Distinct())
-            {
-                if (img.Array == null) continue;
+            bool firstImg = true;
 
 
-                var imgName = $"{baseName}_{files.Count}";
+            foreach (var img in images)
+            {
+                var imgName = firstImg ? baseName : $"{baseName}_{files.Count}";
 
 
                 if (_IsPng(img)) files[imgName + ".png"] = img;
                 if (_IsPng(img)) files[imgName + ".png"] = img;
                 if (_IsJpeg(img)) files[imgName + ".jpg"] = img;
                 if (_IsJpeg(img)) files[imgName + ".jpg"] = img;
+
+                firstImg = false;
             }
             }
 
 
             // write materials
             // write materials
@@ -96,6 +103,7 @@ namespace SharpGLTF.IO
                 sb.AppendLine("illum 2");
                 sb.AppendLine("illum 2");
                 sb.AppendLine(Invariant($"Ka {m.DiffuseColor.X} {m.DiffuseColor.Y} {m.DiffuseColor.Z}"));
                 sb.AppendLine(Invariant($"Ka {m.DiffuseColor.X} {m.DiffuseColor.Y} {m.DiffuseColor.Z}"));
                 sb.AppendLine(Invariant($"Kd {m.DiffuseColor.X} {m.DiffuseColor.Y} {m.DiffuseColor.Z}"));
                 sb.AppendLine(Invariant($"Kd {m.DiffuseColor.X} {m.DiffuseColor.Y} {m.DiffuseColor.Z}"));
+                sb.AppendLine(Invariant($"Ks {m.SpecularColor.X} {m.SpecularColor.Y} {m.SpecularColor.Z}"));
 
 
                 if (m.DiffuseTexture.Array != null)
                 if (m.DiffuseTexture.Array != null)
                 {
                 {
@@ -219,7 +227,7 @@ namespace SharpGLTF.IO
 
 
         public void AddModel(Schema2.ModelRoot model)
         public void AddModel(Schema2.ModelRoot model)
         {
         {
-            foreach (var triangle in model.Triangulate())
+            foreach (var triangle in Schema2.Toolkit.Triangulate<POSITION, TEXCOORD, VEMPTY>(model.DefaultScene))
             {
             {
                 var dstMaterial = new Material();
                 var dstMaterial = new Material();
 
 
@@ -227,7 +235,13 @@ namespace SharpGLTF.IO
                 if (srcMaterial != null)
                 if (srcMaterial != null)
                 {
                 {
                     var baseColor = srcMaterial.FindChannel("BaseColor");
                     var baseColor = srcMaterial.FindChannel("BaseColor");
-                    dstMaterial.DiffuseColor = baseColor.Factor;
+
+                    var clr = new Vector3(baseColor.Factor.X, baseColor.Factor.Y, baseColor.Factor.Z);
+
+                    // https://stackoverflow.com/questions/36510170/how-to-calculate-specular-contribution-in-pbr
+                    dstMaterial.DiffuseColor = clr;
+                    dstMaterial.SpecularColor = new Vector3(0.2f);
+
                     dstMaterial.DiffuseTexture = baseColor.Image?.GetImageContent() ?? default;
                     dstMaterial.DiffuseTexture = baseColor.Image?.GetImageContent() ?? default;
                 }
                 }
 
 

+ 69 - 19
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -239,6 +239,48 @@ namespace SharpGLTF.Schema2
 
 
         #region evaluation
         #region evaluation
 
 
+        public static IEnumerable<((TvP, TvM, TvJ), (TvP, TvM, TvJ), (TvP, TvM, TvJ), Material)> Triangulate<TvP, TvM, TvJ>(this Mesh mesh, Matrix4x4 xform)
+            where TvP : struct, Geometry.VertexTypes.IVertexPosition
+            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
+            where TvJ : struct, Geometry.VertexTypes.IVertexJoints
+        {
+            var normals = mesh.GetComputedNormals();
+
+            return mesh.Primitives.SelectMany(item => item.Triangulate<TvP, TvM, TvJ>(xform, normals));
+        }
+
+        public static IEnumerable<((TvP, TvM, TvJ), (TvP, TvM, TvJ), (TvP, TvM, TvJ), Material)> Triangulate<TvP, TvM, TvJ>(this MeshPrimitive prim, Matrix4x4 xform, IReadOnlyDictionary<Vector3, Vector3> defaultNormals)
+            where TvP : struct, Geometry.VertexTypes.IVertexPosition
+            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
+            where TvJ : struct, Geometry.VertexTypes.IVertexJoints
+        {
+            var vertices = prim.GetVertexColumns();
+            if (vertices.Normals == null && defaultNormals != null) vertices.SetNormals(defaultNormals);
+
+            var triangles = prim.GetTriangleIndices();
+
+            foreach (var t in triangles)
+            {
+                var ap = vertices.GetPositionFragment<TvP>(t.Item1);
+                var bp = vertices.GetPositionFragment<TvP>(t.Item2);
+                var cp = vertices.GetPositionFragment<TvP>(t.Item3);
+
+                ap.Transform(xform);
+                bp.Transform(xform);
+                cp.Transform(xform);
+
+                var am = vertices.GetMaterialFragment<TvM>(t.Item1);
+                var bm = vertices.GetMaterialFragment<TvM>(t.Item2);
+                var cm = vertices.GetMaterialFragment<TvM>(t.Item3);
+
+                var aj = vertices.GetJointsFragment<TvJ>(t.Item1);
+                var bj = vertices.GetJointsFragment<TvJ>(t.Item2);
+                var cj = vertices.GetJointsFragment<TvJ>(t.Item3);
+
+                yield return ((ap, am, aj), (bp, bm, bj), (cp, cm, cj), prim.Material);
+            }
+        }
+
         public static Geometry.VertexTypes.VertexColumns GetVertexColumns(this MeshPrimitive primitive)
         public static Geometry.VertexTypes.VertexColumns GetVertexColumns(this MeshPrimitive primitive)
         {
         {
             var vertexAccessors = primitive.VertexAccessors;
             var vertexAccessors = primitive.VertexAccessors;
@@ -266,27 +308,17 @@ namespace SharpGLTF.Schema2
 
 
         public static IEnumerable<(int, int, int)> GetTriangleIndices(this MeshPrimitive primitive)
         public static IEnumerable<(int, int, int)> GetTriangleIndices(this MeshPrimitive primitive)
         {
         {
-            if (primitive.DrawPrimitiveType == PrimitiveType.POINTS) yield break;
-            if (primitive.DrawPrimitiveType == PrimitiveType.LINES) yield break;
-            if (primitive.DrawPrimitiveType == PrimitiveType.LINE_LOOP) yield break;
-            if (primitive.DrawPrimitiveType == PrimitiveType.LINE_STRIP) yield break;
-
-            var indices = primitive.IndexAccessor != null
-                ?
-                primitive.IndexAccessor.AsIndicesArray()
-                :
-                EncodedArrayUtils.IndicesRange(0, primitive.GetVertexAccessor("POSITION").Count);
-
-            if (primitive.DrawPrimitiveType == PrimitiveType.TRIANGLES)
-            {
-                for (int i = 2; i < indices.Count; i += 3)
-                {
-                    yield return ((int)indices[i - 2], (int)indices[i - 1], (int)indices[i]);
-                }
-            }
+            if (primitive.IndexAccessor == null) return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.GetVertexAccessor("POSITION").Count);
+
+            return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.IndexAccessor.AsIndicesArray());
         }
         }
 
 
-        public static Dictionary<Vector3, Vector3> ComputeNormals(this Mesh mesh)
+        /// <summary>
+        /// Calculates a default set of 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> GetComputedNormals(this Mesh mesh)
         {
         {
             var posnrm = new Dictionary<Vector3, Vector3>();
             var posnrm = new Dictionary<Vector3, Vector3>();
 
 
@@ -321,6 +353,24 @@ namespace SharpGLTF.Schema2
             return posnrm;
             return posnrm;
         }
         }
 
 
+        public static void AddMesh<TMaterial, TvP, TvM, TvJ>(this Geometry.MeshBuilder<TMaterial, TvP, TvM, TvJ> meshBuilder, Mesh srcMesh, Matrix4x4 xform, Func<Material, TMaterial> materialFunc)
+            where TvP : struct, Geometry.VertexTypes.IVertexPosition
+            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
+            where TvJ : struct, Geometry.VertexTypes.IVertexJoints
+        {
+            var normals = srcMesh.GetComputedNormals();
+
+            foreach (var srcPrim in srcMesh.Primitives)
+            {
+                var dstPrim = meshBuilder.UsePrimitive(materialFunc(srcPrim.Material));
+
+                foreach (var tri in srcPrim.Triangulate<TvP, TvM, TvJ>(xform, normals))
+                {
+                    dstPrim.AddTriangle(tri.Item1, tri.Item2, tri.Item3);
+                }
+            }
+        }
+
         public static void SaveAsWavefront(this ModelRoot model, string filePath)
         public static void SaveAsWavefront(this ModelRoot model, string filePath)
         {
         {
             var wf = new IO.WavefrontWriter();
             var wf = new IO.WavefrontWriter();

+ 30 - 0
src/SharpGLTF.Toolkit/Schema2/SceneExtensions.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Linq;
 using System.Numerics;
 using System.Numerics;
 using System.Text;
 using System.Text;
 
 
@@ -7,6 +8,8 @@ namespace SharpGLTF.Schema2
 {
 {
     public static partial class Toolkit
     public static partial class Toolkit
     {
     {
+        #region fluent creation
+
         public static Node WithLocalTranslation(this Node node, Vector3 translation)
         public static Node WithLocalTranslation(this Node node, Vector3 translation)
         {
         {
             var xform = node.LocalTransform;
             var xform = node.LocalTransform;
@@ -39,5 +42,32 @@ namespace SharpGLTF.Schema2
             node.Mesh = mesh;
             node.Mesh = mesh;
             return node;
             return node;
         }
         }
+
+        #endregion
+
+        #region evaluation
+
+        public static IEnumerable<((TvP, TvM, TvJ), (TvP, TvM, TvJ), (TvP, TvM, TvJ), Material)> Triangulate<TvP, TvM, TvJ>(this Scene scene)
+            where TvP : struct, Geometry.VertexTypes.IVertexPosition
+            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
+            where TvJ : struct, Geometry.VertexTypes.IVertexJoints
+        {
+            return Node.Flatten(scene).SelectMany(item => item.Triangulate<TvP, TvM, TvJ>(true));
+        }
+
+        public static IEnumerable<((TvP, TvM, TvJ), (TvP, TvM, TvJ), (TvP, TvM, TvJ), Material)> Triangulate<TvP, TvM, TvJ>(this Node node, bool inWorldSpace)
+            where TvP : struct, Geometry.VertexTypes.IVertexPosition
+            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
+            where TvJ : struct, Geometry.VertexTypes.IVertexJoints
+        {
+            var mesh = node.Mesh;
+            if (mesh == null) return Enumerable.Empty<((TvP, TvM, TvJ), (TvP, TvM, TvJ), (TvP, TvM, TvJ), Material)>();
+
+            var xform = inWorldSpace ? node.WorldMatrix : Matrix4x4.Identity;
+
+            return mesh.Triangulate<TvP, TvM, TvJ>(xform);
+        }
+
+        #endregion
     }
     }
 }
 }