Browse Source

Removed "AddPolygon" support due to maintenance costs, and being out of scope of the library

Vicente Penades 6 years ago
parent
commit
c8a451bfdf

+ 20 - 1
examples/Example1/Program.cs

@@ -2,6 +2,7 @@
 using System.Numerics;
 
 using SharpGLTF.Geometry;
+using SharpGLTF.Geometry.VertexTypes;
 using SharpGLTF.Materials;
 using SharpGLTF.Schema2;
 
@@ -34,7 +35,7 @@ namespace Example1
             prim.AddTriangle(new VERTEX(10, 0, 0), new VERTEX(-10, 0, 0), new VERTEX(0, -10, 0));
 
             prim = mesh.UsePrimitive(material2);
-            prim.AddPolygon(new VERTEX(-5, 0, 3), new VERTEX(0, -5, 3), new VERTEX(5, 0, 3), new VERTEX(0, 5, 3));
+            prim.AddConvexPolygon(new VERTEX(-5, 0, 3), new VERTEX(0, -5, 3), new VERTEX(5, 0, 3), new VERTEX(0, 5, 3));
             
             // create a new gltf model
             var model = ModelRoot.CreateModel();
@@ -53,4 +54,22 @@ namespace Example1
             model.SaveGLTF("mesh.gltf");
         }
     }
+
+    static class ToolkitUtils
+    {
+        public static void AddConvexPolygon<TMaterial, TvG, TvM, TvS>(this PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, params VertexBuilder<TvG, TvM, TvS>[] vertices)
+        where TvG : struct, IVertexGeometry
+        where TvM : struct, IVertexMaterial
+        where TvS : struct, IVertexSkinning
+        {
+            for (int i = 2; i < vertices.Length; ++i)
+            {
+                var a = vertices[0];
+                var b = vertices[i - 1];
+                var c = vertices[i];
+
+                primitive.AddTriangle(a, b, c);
+            }
+        }
+    }
 }

+ 24 - 6
examples/InfiniteSkinnedTentacle/Program.cs

@@ -130,14 +130,14 @@ namespace InfiniteSkinnedTentacle
 
                 if (i == 0)
                 {
-                    prim.AddPolygon(b0, b1, b2, b3);
+                    prim.AddConvexPolygon(b0, b1, b2, b3);
                 }
                 else
                 {
-                    prim.AddPolygon(b0, b1, a1, a0);
-                    prim.AddPolygon(b1, b2, a2, a1);
-                    prim.AddPolygon(b2, b3, a3, a2);
-                    prim.AddPolygon(b3, b0, a0, a3);
+                    prim.AddConvexPolygon(b0, b1, a1, a0);
+                    prim.AddConvexPolygon(b1, b2, a2, a1);
+                    prim.AddConvexPolygon(b2, b3, a3, a2);
+                    prim.AddConvexPolygon(b3, b0, a0, a3);
                 }
 
                 a0 = b0;
@@ -146,9 +146,27 @@ namespace InfiniteSkinnedTentacle
                 a3 = b3;
             }
 
-            prim.AddPolygon(a3, a2, a1, a0);
+            prim.AddConvexPolygon(a3, a2, a1, a0);
 
             return mesh;
         }
     }
+
+    static class ToolkitUtils
+    {
+        public static void AddConvexPolygon<TMaterial, TvG, TvM, TvS>(this PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, params VertexBuilder<TvG, TvM, TvS>[] vertices)
+        where TvG : struct, IVertexGeometry
+        where TvM : struct, IVertexMaterial
+        where TvS : struct, IVertexSkinning
+        {
+            for (int i = 2; i < vertices.Length; ++i)
+            {
+                var a = vertices[0];
+                var b = vertices[i - 1];
+                var c = vertices[i];
+
+                primitive.AddTriangle(a, b, c);
+            }
+        }
+    }
 }

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

@@ -68,22 +68,12 @@ namespace SharpGLTF.Geometry
 
         private readonly Dictionary<(TMaterial, int), PrimitiveBuilder<TMaterial, TvG, TvM, TvS>> _Primitives = new Dictionary<(TMaterial, int), PrimitiveBuilder<TMaterial, TvG, TvM, TvS>>();
 
-        #pragma warning disable SA1401 // Fields should be private
-        internal IPolygonTriangulator _Triangulator = BasicEarClippingTriangulation.Default;
-        #pragma warning restore SA1401 // Fields should be private
-
         #endregion
 
         #region properties
 
         public Boolean StrictMode { get; set; }
 
-        public IPolygonTriangulator Triangulator
-        {
-            get => _Triangulator;
-            set => _Triangulator = value == null ? BasicEarClippingTriangulation.Default : value;
-        }
-
         public string Name { get; set; }
 
         public IEnumerable<TMaterial> Materials => _Primitives.Keys.Select(item => item.Item1).Distinct();

+ 0 - 50
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -214,56 +214,6 @@ namespace SharpGLTF.Geometry
             _Indices.Add(cc);
         }
 
-        /// <summary>
-        /// Adds a polygon as a decomposed collection of triangles.
-        /// </summary>
-        /// <param name="points">The corners of the polygon.</param>
-        /// <remarks>
-        /// Polygon triangulation is performed by a <see cref="IPolygonTriangulator"/>
-        /// instance defined in <see cref="MeshBuilder{TMaterial, TvP, TvM, TvS}.Triangulator"/>
-        /// </remarks>
-        public void AddPolygon(params VertexBuilder<TvG, TvM, TvS>[] points)
-        {
-            Guard.IsTrue(_PrimitiveVertexCount == 3, nameof(VerticesPerPrimitive), "Triangles are not supported for this primitive");
-
-            Guard.NotNull(points, nameof(points));
-            Guard.MustBeGreaterThanOrEqualTo(points.Length, 3, nameof(points));
-
-            // prepare vertices and indices
-            Span<Vector3> vertices = stackalloc Vector3[points.Length];
-            Span<int> inIndices = stackalloc int[points.Length];
-
-            for (int i = 0; i < vertices.Length; ++i)
-            {
-                vertices[i] = points[i].Position;
-                inIndices[i] = i;
-            }
-
-            if (_Scrict) PolygonToolkit.CheckIndices(vertices, inIndices);
-            else PolygonToolkit.SanitizeIndices(vertices, ref inIndices);
-
-            if (inIndices.Length < 3) return;
-            if (inIndices.Length == 3)
-            {
-                AddTriangle(points[inIndices[0]], points[inIndices[1]], points[inIndices[2]]);
-                return;
-            }
-
-            // tessellate
-            Span<int> outIndices = stackalloc int[(vertices.Length - 2) * 3];
-
-            _Mesh._Triangulator.Triangulate(outIndices, vertices, inIndices);
-
-            for (int i = 0; i < outIndices.Length; i += 3)
-            {
-                var a = points[outIndices[i + 0]];
-                var b = points[outIndices[i + 1]];
-                var c = points[outIndices[i + 2]];
-
-                AddTriangle(a, b, c);
-            }
-        }
-
         internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
         {
             if (primitive == null) throw new ArgumentNullException(nameof(primitive));

+ 0 - 236
src/SharpGLTF.Toolkit/Geometry/Triangulation.cs

@@ -1,236 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Numerics;
-using System.Text;
-
-namespace SharpGLTF.Geometry
-{
-
-    static class PolygonToolkit
-    {
-        /// <summary>
-        /// Checks if all the indices are valid
-        /// </summary>
-        /// <param name="inVertices">A list of <see cref="Vector3"/> position points.</param>
-        /// <param name="indices">A list of vertex indices pointing to <paramref name="inVertices"/>.</param>
-        public static void CheckIndices(this ReadOnlySpan<Vector3> inVertices, ReadOnlySpan<int> indices)
-        {
-            Guard.IsFalse(inVertices.IsEmpty, nameof(inVertices));
-            Guard.IsFalse(indices.IsEmpty, nameof(indices));
-
-            int a = 0;
-            while (indices.Length > 1 && a < indices.Length)
-            {
-                // first vertex
-                var aa = inVertices[indices[a]];
-
-                // check invalid values
-                Guard.IsTrue(aa._IsReal(), nameof(inVertices));
-
-                // second vertex
-                var b = a + 1; if (b >= indices.Length) b -= inVertices.Length;
-                var bb = inVertices[indices[b]];
-
-                // check collapsed points (would produce degenerated triangles)
-                Guard.IsFalse(aa == bb, nameof(indices));
-
-                // third vertex
-                var c = b + 1; if (c >= indices.Length) c -= inVertices.Length;
-                var cc = inVertices[indices[c]];
-
-                // check collapsed segments (would produce degenerated triangles)
-                Guard.IsFalse(aa == cc, nameof(indices));
-
-                a++;
-            }
-        }
-
-        /// <summary>
-        /// Removes the invalid points of a polygon.
-        /// </summary>
-        /// <param name="inVertices">A list of <see cref="Vector3"/> position points.</param>
-        /// <param name="indices">A list of vertex indices pointing to <paramref name="inVertices"/>.</param>
-        public static void SanitizeIndices(this ReadOnlySpan<Vector3> inVertices, ref Span<int> indices)
-        {
-            Guard.IsFalse(inVertices.IsEmpty, nameof(inVertices));
-            Guard.IsFalse(indices.IsEmpty, nameof(indices));
-
-            int a = 0;
-            while (indices.Length > 1 && a < indices.Length)
-            {
-                // first vertex
-                var aa = inVertices[indices[a]];
-
-                // remove invalid values
-                if (!aa._IsReal())
-                {
-                    RemoveElementAt(ref indices, a);
-                    continue;
-                }
-
-                // second vertex
-                var b = a + 1; if (b >= indices.Length) b -= indices.Length;
-                var bb = inVertices[indices[b]];
-
-                // remove collapsed points (would produce degenerated triangles)
-                if (aa == bb)
-                {
-                    RemoveElementAt(ref indices, a);
-                    continue;
-                }
-
-                // third vertex
-                var c = b + 1; if (c >= indices.Length) c -= indices.Length;
-                var cc = inVertices[indices[c]];
-
-                // remove collapsed segments (would produce degenerated triangles)
-                if (aa == cc)
-                {
-                    RemoveElementAt(ref indices, b);
-                    continue;
-                }
-
-                a++;
-            }
-        }
-
-        internal static void RemoveElementAt<T>(ref Span<T> collection, int index)
-        {
-            // tri.Item2 is the index of the ear's triangle.
-            // remove the ear from the array:
-            for (int i = index + 1; i < collection.Length; ++i)
-            {
-                collection[i - 1] = collection[i];
-            }
-
-            collection = collection.Slice(0, collection.Length - 1);
-        }
-    }
-
-    public interface IPolygonTriangulator
-    {
-        void Triangulate(Span<int> outIndices, ReadOnlySpan<Vector3> inVertices, ReadOnlySpan<int> inIndices);
-    }
-
-    /// <summary>
-    /// Naive triangulator that assumes the polygon to be convex
-    /// </summary>
-    class NaivePolygonTriangulation : IPolygonTriangulator
-    {
-        static NaivePolygonTriangulation() { }
-
-        private NaivePolygonTriangulation() { }
-
-        private static readonly NaivePolygonTriangulation _Instance = new NaivePolygonTriangulation();
-
-        public static IPolygonTriangulator Default => _Instance;
-
-        public void Triangulate(Span<int> outIndices, ReadOnlySpan<Vector3> inVertices, ReadOnlySpan<int> inIndices)
-        {
-            int idx = 0;
-
-            for (int i = 2; i < inIndices.Length; ++i)
-            {
-                var a = inIndices[idx++];
-                var b = inIndices[idx++];
-                var c = inIndices[idx++];
-
-                outIndices[a] = 0;
-                outIndices[b] = i - 1;
-                outIndices[c] = i;
-            }
-        }
-    }
-
-    class BasicEarClippingTriangulation : IPolygonTriangulator
-    {
-        static BasicEarClippingTriangulation() { }
-
-        private BasicEarClippingTriangulation() { }
-
-        private static readonly BasicEarClippingTriangulation _Instance = new BasicEarClippingTriangulation();
-
-        public static IPolygonTriangulator Default => _Instance;
-
-        public void Triangulate(Span<int> outIndices, ReadOnlySpan<Vector3> inVertices, ReadOnlySpan<int> inIndices)
-        {
-            System.Diagnostics.Debug.Assert(outIndices.Length == (inVertices.Length - 2) * 3, $"{nameof(outIndices)} has invalid length");
-
-            // define master direction
-            var masterDirection = Vector3.Zero;
-            for (int i = 2; i < inVertices.Length; ++i)
-            {
-                var ab = inVertices[i - 1] - inVertices[0];
-                var ac = inVertices[i - 0] - inVertices[0];
-                masterDirection += Vector3.Cross(ab, ac);
-            }
-
-            // setup source Indices
-            Span<int> tmpIndices = stackalloc int[inIndices.Length];
-            inIndices.CopyTo(tmpIndices);
-
-            int triIdx = 0;
-
-            // begin clipping ears
-            while (true)
-            {
-                if (tmpIndices.Length < 3) break;
-
-                PolygonToolkit.SanitizeIndices(inVertices, ref tmpIndices);
-
-                if (tmpIndices.Length < 3) break;
-
-                var tri = _FindEarTriangle(inVertices, tmpIndices, masterDirection);
-                if (tri.Item1 < 0) throw new ArgumentException("failed to triangulate", nameof(inVertices));
-
-                outIndices[triIdx++] = tmpIndices[tri.Item1];
-                outIndices[triIdx++] = tmpIndices[tri.Item2];
-                outIndices[triIdx++] = tmpIndices[tri.Item3];
-
-                // tri.Item2 is the index of the ear's triangle.
-                // remove the ear from the array:
-
-                PolygonToolkit.RemoveElementAt(ref tmpIndices, tri.Item2);
-            }
-
-            if (tmpIndices.Length >= 3)
-            {
-                // add last triangle.
-                outIndices[triIdx++] = tmpIndices[0];
-                outIndices[triIdx++] = tmpIndices[1];
-                outIndices[triIdx++] = tmpIndices[2];
-            }
-
-            System.Diagnostics.Debug.Assert(outIndices.Length == triIdx, $"{nameof(outIndices)} has invalid length");
-        }
-
-        private static (int, int, int) _FindEarTriangle(ReadOnlySpan<Vector3> inVertices, ReadOnlySpan<int> inIndices, Vector3 masterDirection)
-        {
-            for (int i = 0; i < inIndices.Length; ++i)
-            {
-                // define indices of the ear.
-                var a = i + 0;
-                var b = i + 1; if (b >= inIndices.Length) b -= inIndices.Length;
-                var c = i + 2; if (c >= inIndices.Length) c -= inIndices.Length;
-
-                // map to vertex indices
-                var aa = inIndices[a];
-                var bb = inIndices[b];
-                var cc = inIndices[c];
-
-                var aaa = inVertices[aa];
-                var bbb = inVertices[bb];
-                var ccc = inVertices[cc];
-
-                var dir = Vector3.Cross(bbb - aaa, ccc - aaa);
-
-                // determine the winding of the ear, and skip it if it's reversed.
-                if (Vector3.Dot(masterDirection, dir) <= 0) continue;
-
-                return (a, b, c);
-            }
-
-            return (-1, -1, -1);
-        }
-    }
-}

+ 0 - 148
tests/SharpGLTF.Tests/Geometry/TriangulationTests.cs

@@ -1,148 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-using System.Text;
-
-using NUnit.Framework;
-
-namespace SharpGLTF.Geometry
-{
-    using VERTEX = VertexBuilder<VertexTypes.VertexPosition, VertexTypes.VertexEmpty, VertexTypes.VertexEmpty>;
-
-    [Category("Model Authoring")]
-    public class TriangulationTests
-    {
-        private static readonly Vector2[] _BowTiePolygon = new[]
-        {
-            new Vector2(-10, -10),
-            new Vector2(0, -5),
-            new Vector2(10, -10),
-            new Vector2(10, 10),
-            new Vector2(0, 5),
-            new Vector2(-10, 10)
-        };
-
-        private static readonly Vector2[] _BridgePolygon = new[]
-        {            
-            new Vector2(-10, 2),
-            new Vector2(-10, -10),
-            new Vector2(-8, -10),
-            new Vector2(-8, -5),
-            new Vector2(-4, -1),
-            new Vector2(0, 0),
-            new Vector2(+4, -1),
-            new Vector2(+8, -5),
-            new Vector2(+8, -10),
-            new Vector2(+10, -10),
-            new Vector2(+10, 2),
-        };
-
-        private static readonly Vector2[] _CoLinearBaseTriangle = new[]
-        {
-            new Vector2(0,10),
-            new Vector2(-10,-10),
-            new Vector2(-5,-10),
-            new Vector2(0,-10),
-            new Vector2(5,-10),
-            new Vector2(10,-10),            
-        };
-
-        // polygon with a hole, connected to the outside with a "vertex bridge"
-        private static readonly Vector2[] _PoygonWithHole = new[]
-        {
-            new Vector2(0,4),
-            new Vector2(4,0),            
-
-            // inner "hole"            
-            new Vector2(2,0),
-            new Vector2(0,2),
-            new Vector2(-2,0),
-            new Vector2(0,-2),
-            new Vector2(2,0),
-
-            new Vector2(4,0),
-            new Vector2(0,-4),
-            new Vector2(-4,0),
-        };
-
-        
-        private static readonly Vector2[] _SnakePolygon = new[]
-        {
-            new Vector2(0,1),
-            new Vector2(2,3),
-            new Vector2(4,1),
-
-            new Vector2(4,0),
-            new Vector2(2,2),
-            new Vector2(0,0),
-            new Vector2(-2,2),
-            new Vector2(-4,0),
-
-            new Vector2(-4,1),
-            new Vector2(-2,3),
-        };
-
-        // this is a pretty messy polygon with lots of degenerated triangles, and holes
-        private static readonly Vector2[] _SahaquielPolygon = new[]
-        {
-            new Vector2(0,0),
-
-            new Vector2(1,0),
-            new Vector2(2,1),
-            new Vector2(3,0),
-
-            new Vector2(2.5f,0.0f),
-            new Vector2(2.0f,0.5f),
-            new Vector2(1.5f,0.0f),
-            new Vector2(2.0f,-0.5f),
-            new Vector2(2.5f,0.0f),
-
-            new Vector2(2,-1),
-            new Vector2(1, 0),
-
-            new Vector2(0,0),
-            
-            new Vector2(-1,0),
-            new Vector2(-2,-1),
-            new Vector2(-3,0),
-
-            new Vector2(-2.5f,0.0f),
-            new Vector2(-2.0f,-0.5f),
-            new Vector2(-1.5f,0.0f),
-            new Vector2(-2.0f,0.5f),
-            new Vector2(-2.5f,0.0f),
-
-            new Vector2(-2,1),
-            new Vector2(-1, 0),
-        };
-
-        [Test]
-        public void CreateEarClippingPolygons()
-        {
-            var material = new Materials.MaterialBuilder()
-                .WithUnlitShader();
-
-            var mesh = VERTEX.CreateCompatibleMesh();
-            mesh.Triangulator = BasicEarClippingTriangulation.Default;
-
-            var polygon = _SnakePolygon
-                .Select(item => new Vector3(item, 0))
-                .ToArray();
-
-            for (int i=0; i < 10; ++i)
-            {
-                var xform = Matrix4x4.CreateFromYawPitchRoll(i, i *2, i);
-                xform = Matrix4x4.CreateTranslation(0, 0, 20) * xform;
-
-                var vertices = polygon
-                    .Select(item => new VERTEX(Vector3.Transform(item,xform)) )
-                    .ToArray();
-
-                mesh.UsePrimitive(material).AddPolygon(vertices);
-            }            
-
-            mesh.AttachToCurrentTest("BasicEarClippingTriangulation.glb");
-        }
-    }
-}

+ 3 - 3
tests/SharpGLTF.Tests/Schema2/Authoring/ExtensionsCreationTests.cs

@@ -61,7 +61,7 @@ namespace SharpGLTF.Schema2.Authoring
                 .WithChannelImage(Materials.KnownChannels.SpecularGlossiness, System.IO.Path.Combine(basePath, "WaterBottle_specularGlossiness.png"));
 
             var mesh = new Geometry.MeshBuilder<VPOS, VTEX>("mesh1");
-            mesh.UsePrimitive(material).AddPolygon
+            mesh.UsePrimitive(material).AddConvexPolygon
                 ((new Vector3(-10, 10, 0), new Vector2(1, 0))
                 , (new Vector3(10, 10, 0), new Vector2(0, 0))
                 , (new Vector3(10, -10, 0), new Vector2(0, 1))
@@ -99,7 +99,7 @@ namespace SharpGLTF.Schema2.Authoring
 
             mesh
                 .UsePrimitive(material)
-                .AddPolygon
+                .AddConvexPolygon
                 ((new Vector3(-10, 10, 0), new Vector2(1, 0))
                 , (new Vector3(10, 10, 0), new Vector2(0, 0))
                 , (new Vector3(10, -10, 0), new Vector2(0, 1))
@@ -139,7 +139,7 @@ namespace SharpGLTF.Schema2.Authoring
 
             mesh
                 .UsePrimitive(material)
-                .AddPolygon
+                .AddConvexPolygon
                 ((new Vector3(-10, 10, 0), new Vector2(1, 0))
                 , (new Vector3(10, 10, 0), new Vector2(0, 0))
                 , (new Vector3(10, -10, 0), new Vector2(0, 1))

+ 9 - 9
tests/SharpGLTF.Tests/Schema2/Authoring/MeshBuilderCreationTests.cs

@@ -38,7 +38,7 @@ namespace SharpGLTF.Schema2.Authoring
             var meshBuilder = new MeshBuilder<VPOSNRM>("mesh1");
             
             // add a polygon to the primitive that uses material1 as key.
-            meshBuilder.UsePrimitive(material1).AddPolygon(v1, v2, v3, v4);
+            meshBuilder.UsePrimitive(material1).AddConvexPolygon(v1, v2, v3, v4);
 
             // create a gltf scene
             var model = ModelRoot.CreateModel();
@@ -175,15 +175,15 @@ namespace SharpGLTF.Schema2.Authoring
             var v11 = (new VPOS(+5, 80, -5), new VSKIN4(2));
             var v12 = (new VPOS(-5, 80, -5), new VSKIN4(2));
 
-            meshBuilder.UsePrimitive(pink).AddPolygon(v1, v2, v6, v5);
-            meshBuilder.UsePrimitive(pink).AddPolygon(v2, v3, v7, v6);
-            meshBuilder.UsePrimitive(pink).AddPolygon(v3, v4, v8, v7);
-            meshBuilder.UsePrimitive(pink).AddPolygon(v4, v1, v5, v8);
+            meshBuilder.UsePrimitive(pink).AddConvexPolygon(v1, v2, v6, v5);
+            meshBuilder.UsePrimitive(pink).AddConvexPolygon(v2, v3, v7, v6);
+            meshBuilder.UsePrimitive(pink).AddConvexPolygon(v3, v4, v8, v7);
+            meshBuilder.UsePrimitive(pink).AddConvexPolygon(v4, v1, v5, v8);
 
-            meshBuilder.UsePrimitive(yellow).AddPolygon(v5, v6, v10, v9);
-            meshBuilder.UsePrimitive(yellow).AddPolygon(v6, v7, v11, v10);
-            meshBuilder.UsePrimitive(yellow).AddPolygon(v7, v8, v12, v11);
-            meshBuilder.UsePrimitive(yellow).AddPolygon(v8, v5, v9, v12);
+            meshBuilder.UsePrimitive(yellow).AddConvexPolygon(v5, v6, v10, v9);
+            meshBuilder.UsePrimitive(yellow).AddConvexPolygon(v6, v7, v11, v10);
+            meshBuilder.UsePrimitive(yellow).AddConvexPolygon(v7, v8, v12, v11);
+            meshBuilder.UsePrimitive(yellow).AddConvexPolygon(v8, v5, v9, v12);
 
             meshBuilder.Validate();
 

+ 2 - 2
tests/SharpGLTF.Tests/Schema2/Authoring/SolidMeshUtils.cs

@@ -35,7 +35,7 @@ namespace SharpGLTF.Schema2.Authoring
             var n = Vector3.Normalize(Vector3.TransformNormal(origin, xform));
 
             meshBuilder.UsePrimitive(material)
-                .AddPolygon
+                .AddConvexPolygon
                 (
                 new VPOSNRM(p1, n),
                 new VPOSNRM(p2, n),
@@ -153,7 +153,7 @@ namespace SharpGLTF.Schema2.Authoring
 
                     terrainMesh
                         .UsePrimitive(material)
-                        .AddPolygon
+                        .AddConvexPolygon
                         (
                             (a, at),
                             (b, bt),

+ 28 - 0
tests/SharpGLTF.Tests/ToolkitUtils.cs

@@ -0,0 +1,28 @@
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using SharpGLTF.Geometry;
+using SharpGLTF.Geometry.VertexTypes;
+
+namespace SharpGLTF
+{
+    static class ToolkitUtils
+    {
+        public static void AddConvexPolygon<TMaterial, TvG, TvM, TvS>(this PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, params VertexBuilder<TvG, TvM, TvS>[] vertices)
+        where TvG : struct, IVertexGeometry
+        where TvM : struct, IVertexMaterial
+        where TvS : struct, IVertexSkinning
+        {
+            for (int i = 2; i < vertices.Length; ++i)
+            {
+                var a = vertices[0];
+                var b = vertices[i - 1];
+                var c = vertices[i];
+
+                primitive.AddTriangle(a, b, c);
+            }
+        }
+    }
+}