Browse Source

Redesigned internals of MeshPrimitiveBuilder for better handling of quads.

Vicente Penades 6 years ago
parent
commit
f22549b249

+ 6 - 1
src/SharpGLTF.Toolkit/Geometry/MeshBuilder.cs

@@ -89,7 +89,12 @@ namespace SharpGLTF.Geometry
         {
             if (!_Primitives.TryGetValue(key, out PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive))
             {
-                primitive = new PrimitiveBuilder<TMaterial, TvG, TvM, TvS>(this, key.Item1, key.Item2);
+                if (key.Item2 == 1) primitive = new PointsPrimitiveBuilder<TMaterial, TvG, TvM, TvS>(this, key.Item1);
+                if (key.Item2 == 2) primitive = new LinesPrimitiveBuilder<TMaterial, TvG, TvM, TvS>(this, key.Item1);
+                if (key.Item2 == 3) primitive = new TrianglesPrimitiveBuilder<TMaterial, TvG, TvM, TvS>(this, key.Item1);
+
+                Guard.NotNull(primitive, nameof(key));
+
                 _Primitives[key] = primitive;
             }
 

+ 1 - 1
src/SharpGLTF.Toolkit/Geometry/PackedMeshBuilder.cs

@@ -49,7 +49,7 @@ namespace SharpGLTF.Geometry
                 .ToList();
 
             var indexBlocks = VertexTypes.VertexUtils
-                .CreateIndexMemoryAccessors( meshPrimitives.Select(item => item.Indices) )
+                .CreateIndexMemoryAccessors( meshPrimitives.Select(item => item.GetIndices()) )
                 .ToList();
 
             if (vertexBlocks.Count != indexBlocks.Count) throw new InvalidOperationException("Vertex and index blocks count mismatch");

+ 400 - 253
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -8,7 +8,6 @@ using System.Numerics;
 using SharpGLTF.Collections;
 using SharpGLTF.Geometry.VertexTypes;
 
-
 namespace SharpGLTF.Geometry
 {
     public interface IPrimitiveReader<TMaterial>
@@ -28,11 +27,6 @@ namespace SharpGLTF.Geometry
         /// </summary>
         IReadOnlyList<IVertexBuilder> Vertices { get; }
 
-        /// <summary>
-        /// Gets the plain list of indices.
-        /// </summary>
-        IReadOnlyList<int> Indices { get; }
-
         /// <summary>
         /// Gets the indices of all points, given that <see cref="VerticesPerPrimitive"/> is 1.
         /// </summary>
@@ -44,9 +38,20 @@ namespace SharpGLTF.Geometry
         IReadOnlyList<(int, int)> Lines { get; }
 
         /// <summary>
-        /// Gets the indices of all triangles, given that <see cref="VerticesPerPrimitive"/> is 3.
+        /// Gets the indices of all the surfaces as triangles, given that <see cref="VerticesPerPrimitive"/> is 3.
         /// </summary>
         IReadOnlyList<(int, int, int)> Triangles { get; }
+
+        /// <summary>
+        /// Gets the indices of all the surfaces, given that <see cref="VerticesPerPrimitive"/> is 3.
+        /// </summary>
+        IReadOnlyList<(int, int, int, int?)> Surfaces { get; }
+
+        /// <summary>
+        /// Calculates the raw list of indices to use for this primitive.
+        /// </summary>
+        /// <returns>a list of indices.</returns>
+        IReadOnlyList<int> GetIndices();
     }
 
     public interface IPrimitiveBuilder
@@ -95,22 +100,17 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexJoints16x4"/>,
     /// <see cref="VertexJoints16x8"/>.
     /// </typeparam>
-    [System.Diagnostics.DebuggerDisplay("Primitive {_Material}")]
-    public class PrimitiveBuilder<TMaterial, TvG, TvM, TvS> : IPrimitiveBuilder, IPrimitiveReader<TMaterial>
+    public abstract class PrimitiveBuilder<TMaterial, TvG, TvM, TvS> : IPrimitiveBuilder, IPrimitiveReader<TMaterial>
         where TvG : struct, IVertexGeometry
         where TvM : struct, IVertexMaterial
         where TvS : struct, IVertexSkinning
     {
         #region lifecycle
 
-        internal PrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material, int primitiveVertexCount)
+        internal PrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material)
         {
             this._Mesh = mesh;
             this._Material = material;
-            this._PrimitiveVertexCount = primitiveVertexCount;
-
-            if (this._PrimitiveVertexCount == 2) _LinesIndices = new LineListWrapper(_Indices);
-            if (this._PrimitiveVertexCount == 3) _TrianglesIndices = new TriangleListWrapper(_Indices);
         }
 
         #endregion
@@ -121,14 +121,8 @@ namespace SharpGLTF.Geometry
 
         private readonly TMaterial _Material;
 
-        private readonly int _PrimitiveVertexCount;
-
         private readonly VertexListWrapper _Vertices = new VertexListWrapper();
 
-        private readonly List<int> _Indices = new List<int>();
-        private readonly LineListWrapper _LinesIndices;
-        private readonly TriangleListWrapper _TrianglesIndices;
-
         #endregion
 
         #region properties
@@ -143,26 +137,42 @@ namespace SharpGLTF.Geometry
         /// 2 - Lines
         /// 3 - Triangles
         /// </summary>
-        public int VerticesPerPrimitive => _PrimitiveVertexCount;
+        public abstract int VerticesPerPrimitive { get; }
+
+        public Type VertexType => typeof(VertexBuilder<TvG, TvM, TvS>);
 
         public IReadOnlyList<VertexBuilder<TvG, TvM, TvS>> Vertices => _Vertices;
 
         IReadOnlyList<IVertexBuilder> IPrimitiveReader<TMaterial>.Vertices => _Vertices;
 
-        public IReadOnlyList<int> Indices => _Indices;
-
-        public IReadOnlyList<int> Points => _GetPointIndices();
+        public virtual IReadOnlyList<int> Points => Array.Empty<int>();
 
-        public IReadOnlyList<(int, int)> Lines => _GetLineIndices();
+        public virtual IReadOnlyList<(int, int)> Lines => Array.Empty<(int, int)>();
 
-        public IReadOnlyList<(int, int, int)> Triangles => _GetTriangleIndices();
+        public virtual IReadOnlyList<(int, int, int)> Triangles => Array.Empty<(int, int, int)>();
 
-        public Type VertexType => typeof(VertexBuilder<TvG, TvM, TvS>);
+        public virtual IReadOnlyList<(int, int, int, int?)> Surfaces => Array.Empty<(int, int, int, int?)>();
 
         #endregion
 
         #region API
 
+        /// <summary>
+        /// Checks if <paramref name="v"/> is a compatible vertex and casts it, or converts it if it is not.
+        /// </summary>
+        /// <param name="v">Any vertex</param>
+        /// <returns>A vertex compatible with this primitive.</returns>
+        private static VertexBuilder<TvG, TvM, TvS> ConvertVertex(IVertexBuilder v)
+        {
+            Guard.NotNull(v, nameof(v));
+
+            var vv = v.ConvertTo<TvG, TvM, TvS>();
+
+            System.Diagnostics.Debug.Assert(vv.Position == v.GetGeometry().GetPosition());
+
+            return vv;
+        }
+
         /// <summary>
         /// Adds or reuses a vertex.
         /// </summary>
@@ -173,13 +183,8 @@ namespace SharpGLTF.Geometry
         /// <typeparamref name="TvS"/> fragments.
         /// </param>
         /// <returns>The index of the vertex.</returns>
-        public int UseVertex(VertexBuilder<TvG, TvM, TvS> vertex)
+        protected int UseVertex(VertexBuilder<TvG, TvM, TvS> vertex)
         {
-            if (_Mesh.VertexPreprocessor != null)
-            {
-                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref vertex)) return -1;
-            }
-
             return _Vertices.Use(vertex);
         }
 
@@ -190,25 +195,7 @@ namespace SharpGLTF.Geometry
         /// <returns>The indices of the vertices.</returns>
         public int AddPoint(IVertexBuilder a)
         {
-            Guard.NotNull(a, nameof(a));
-
-            var aa = a.ConvertTo<TvG, TvM, TvS>();
-
-            System.Diagnostics.Debug.Assert(aa.Position == a.GetGeometry().GetPosition());
-
-            return AddPoint(aa);
-        }
-
-        /// <summary>
-        /// Adds a point.
-        /// </summary>
-        /// <param name="a">vertex for this point.</param>
-        /// <returns>The index of the vertex.</returns>
-        public int AddPoint(VertexBuilder<TvG, TvM, TvS> a)
-        {
-            Guard.IsTrue(_PrimitiveVertexCount == 1, nameof(VerticesPerPrimitive), "Points are not supported for this primitive");
-
-            return UseVertex(a);
+            return AddPoint(ConvertVertex(a));
         }
 
         /// <summary>
@@ -219,46 +206,7 @@ namespace SharpGLTF.Geometry
         /// <returns>The indices of the vertices, or, in case the line is degenerated, (-1,-1).</returns>
         public (int, int) AddLine(IVertexBuilder a, IVertexBuilder b)
         {
-            Guard.NotNull(a, nameof(a));
-            Guard.NotNull(b, nameof(b));
-
-            var aa = a.ConvertTo<TvG, TvM, TvS>();
-            var bb = b.ConvertTo<TvG, TvM, TvS>();
-
-            System.Diagnostics.Debug.Assert(aa.Position == a.GetGeometry().GetPosition());
-            System.Diagnostics.Debug.Assert(bb.Position == b.GetGeometry().GetPosition());
-
-            return AddLine(aa, bb);
-        }
-
-        /// <summary>
-        /// Adds a line.
-        /// </summary>
-        /// <param name="a">First corner of the line.</param>
-        /// <param name="b">Second corner of the line.</param>
-        /// <returns>The indices of the vertices, or, in case the line is degenerated, (-1,-1).</returns>
-        public (int, int) AddLine(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b)
-        {
-            Guard.IsTrue(_PrimitiveVertexCount == 2, nameof(VerticesPerPrimitive), "Lines are not supported for this primitive");
-
-            if (_Mesh.VertexPreprocessor != null)
-            {
-                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return (-1, -1);
-                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return (-1, -1);
-            }
-
-            var aa = _Vertices.Use(a);
-            var bb = _Vertices.Use(b);
-
-            // check for degenerated line
-            if (aa == bb) return (-1, -1);
-
-            // TODO: check if a triangle with indices aa-bb-cc already exists.
-
-            _Indices.Add(aa);
-            _Indices.Add(bb);
-
-            return (aa, bb);
+            return AddLine(ConvertVertex(a), ConvertVertex(b));
         }
 
         /// <summary>
@@ -270,40 +218,7 @@ namespace SharpGLTF.Geometry
         /// <returns>The indices of the vertices, or, in case the triangle is degenerated, (-1,-1,-1).</returns>
         public (int, int, int) AddTriangle(IVertexBuilder a, IVertexBuilder b, IVertexBuilder c)
         {
-            Guard.NotNull(a, nameof(a));
-            Guard.NotNull(b, nameof(b));
-            Guard.NotNull(c, nameof(c));
-
-            var aa = a.ConvertTo<TvG, TvM, TvS>();
-            var bb = b.ConvertTo<TvG, TvM, TvS>();
-            var cc = c.ConvertTo<TvG, TvM, TvS>();
-
-            System.Diagnostics.Debug.Assert(aa.Position == a.GetGeometry().GetPosition());
-            System.Diagnostics.Debug.Assert(bb.Position == b.GetGeometry().GetPosition());
-            System.Diagnostics.Debug.Assert(cc.Position == c.GetGeometry().GetPosition());
-
-            return AddTriangle(aa, bb, cc);
-        }
-
-        /// <summary>
-        /// Adds a triangle.
-        /// </summary>
-        /// <param name="a">First corner of the triangle.</param>
-        /// <param name="b">Second corner of the triangle.</param>
-        /// <param name="c">Third corner of the triangle.</param>
-        /// <returns>The indices of the vertices, or, in case the triangle is degenerated, (-1,-1,-1).</returns>
-        public (int, int, int) AddTriangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c)
-        {
-            Guard.IsTrue(_PrimitiveVertexCount == 3, nameof(VerticesPerPrimitive), "Triangles are not supported for this primitive");
-
-            if (_Mesh.VertexPreprocessor != null)
-            {
-                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return (-1, -1, -1);
-                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return (-1, -1, -1);
-                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref c)) return (-1, -1, -1);
-            }
-
-            return _AddTriangle(a, b, c);
+            return AddTriangle(ConvertVertex(a), ConvertVertex(b), ConvertVertex(c));
         }
 
         /// <summary>
@@ -320,100 +235,14 @@ namespace SharpGLTF.Geometry
         /// </remarks>
         public (int, int, int, int) AddQuadrangle(IVertexBuilder a, IVertexBuilder b, IVertexBuilder c, IVertexBuilder d)
         {
-            Guard.NotNull(a, nameof(a));
-            Guard.NotNull(b, nameof(b));
-            Guard.NotNull(c, nameof(c));
-            Guard.NotNull(d, nameof(d));
-
-            var aa = a.ConvertTo<TvG, TvM, TvS>();
-            var bb = b.ConvertTo<TvG, TvM, TvS>();
-            var cc = c.ConvertTo<TvG, TvM, TvS>();
-            var dd = d.ConvertTo<TvG, TvM, TvS>();
-
-            System.Diagnostics.Debug.Assert(aa.Position == a.GetGeometry().GetPosition());
-            System.Diagnostics.Debug.Assert(bb.Position == b.GetGeometry().GetPosition());
-            System.Diagnostics.Debug.Assert(cc.Position == c.GetGeometry().GetPosition());
-            System.Diagnostics.Debug.Assert(dd.Position == d.GetGeometry().GetPosition());
-
-            return AddQuadrangle(aa, bb, cc, dd);
-        }
-
-        /// <summary>
-        /// Adds a quadrangle.
-        /// </summary>
-        /// <param name="a">First corner of the quadrangle.</param>
-        /// <param name="b">Second corner of the quadrangle.</param>
-        /// <param name="c">Third corner of the quadrangle.</param>
-        /// <param name="d">Fourth corner of the quadrangle.</param>
-        /// <returns>The indices of the vertices, or, in case the quadrangle is degenerated, (-1,-1,-1,-1).</returns>
-        public (int, int, int, int) AddQuadrangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c, VertexBuilder<TvG, TvM, TvS> d)
-        {
-            Guard.IsTrue(_PrimitiveVertexCount == 3, nameof(VerticesPerPrimitive), "Quadrangles are not supported for this primitive");
-
-            if (_Mesh.VertexPreprocessor != null)
-            {
-                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return (-1, -1, -1, -1);
-                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return (-1, -1, -1, -1);
-                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref c)) return (-1, -1, -1, -1);
-                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref d)) return (-1, -1, -1, -1);
-            }
-
-            // at some points it could be interesting to comply with https://github.com/KhronosGroup/glTF/pull/1620
-
-            var oddEven = MeshBuilderToolkit.GetQuadrangleDiagonal(a.Position, b.Position, c.Position, d.Position);
-
-            if (oddEven)
-            {
-                var f = _AddTriangle(a, b, c);
-                var s = _AddTriangle(a, c, d);
-                return
-                    (
-                    f.Item1 > s.Item1 ? f.Item1 : s.Item1,
-                    f.Item2,
-                    f.Item3 > s.Item2 ? f.Item3 : s.Item2,
-                    s.Item3
-                    );
-            }
-            else
-            {
-                var f = _AddTriangle(b, c, d);
-                var s = _AddTriangle(b, d, a);
-
-                return
-                    (
-                    s.Item3,
-                    f.Item1 > s.Item1 ? f.Item1 : s.Item1,
-                    f.Item2,
-                    f.Item3 > s.Item2 ? f.Item3 : s.Item2
-                    );
-            }
-        }
-
-        private (int, int, int) _AddTriangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c)
-        {
-            // check for degenerated triangle
-            if (a.Equals(b) || a.Equals(c) || b.Equals(c)) return (-1, -1, -1);
-
-            var aa = _Vertices.Use(a);
-            var bb = _Vertices.Use(b);
-            var cc = _Vertices.Use(c);
-
-            System.Diagnostics.Debug.Assert(aa != bb && aa != cc && bb != cc, "unexpected degenerated triangle");
-
-            // TODO: check if a triangle with indices aa-bb-cc already exists, since there's no point in having the same polygon twice.
-
-            _Indices.Add(aa);
-            _Indices.Add(bb);
-            _Indices.Add(cc);
-
-            return (aa, bb, cc);
+            return AddQuadrangle(ConvertVertex(a), ConvertVertex(b), ConvertVertex(c), ConvertVertex(d));
         }
 
         internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
         {
             if (primitive == null) return;
 
-            if (_PrimitiveVertexCount == 1)
+            if (this.VerticesPerPrimitive == 1)
             {
                 foreach (var p in primitive.Points)
                 {
@@ -425,7 +254,7 @@ namespace SharpGLTF.Geometry
                 return;
             }
 
-            if (_PrimitiveVertexCount == 2)
+            if (this.VerticesPerPrimitive == 2)
             {
                 foreach (var l in primitive.Lines)
                 {
@@ -438,15 +267,23 @@ namespace SharpGLTF.Geometry
                 return;
             }
 
-            if (_PrimitiveVertexCount == 3)
+            if (this.VerticesPerPrimitive == 3)
             {
-                foreach (var t in primitive.Triangles)
+                foreach (var s in primitive.Surfaces)
                 {
-                    var a = vertexTransform(primitive.Vertices[t.Item1]);
-                    var b = vertexTransform(primitive.Vertices[t.Item2]);
-                    var c = vertexTransform(primitive.Vertices[t.Item3]);
-
-                    AddTriangle(a, b, c);
+                    var a = vertexTransform(primitive.Vertices[s.Item1]);
+                    var b = vertexTransform(primitive.Vertices[s.Item2]);
+                    var c = vertexTransform(primitive.Vertices[s.Item3]);
+
+                    if (s.Item4.HasValue)
+                    {
+                        var d = vertexTransform(primitive.Vertices[s.Item4.Value]);
+                        AddQuadrangle(a, b, c, d);
+                    }
+                    else
+                    {
+                        AddTriangle(a, b, c);
+                    }
                 }
 
                 return;
@@ -461,33 +298,68 @@ namespace SharpGLTF.Geometry
             }
         }
 
-        private IReadOnlyList<int> _GetPointIndices()
+        public void TransformVertices(Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> transformFunc)
         {
-            if (_PrimitiveVertexCount != 1) return Array.Empty<int>();
+            _Vertices.TransformVertices(transformFunc);
+        }
+
+        #endregion
+
+        #region abstract API
 
-            return new PointListWrapper(_Vertices);
+        public abstract IReadOnlyList<int> GetIndices();
+
+        /// <summary>
+        /// Adds a point.
+        /// </summary>
+        /// <param name="a">vertex for this point.</param>
+        /// <returns>The index of the vertex.</returns>
+        public virtual int AddPoint(VertexBuilder<TvG, TvM, TvS> a)
+        {
+            throw new NotSupportedException("Points are not supported for this primitive");
         }
 
-        private IReadOnlyList<(int, int)> _GetLineIndices()
+        /// <summary>
+        /// Adds a line.
+        /// </summary>
+        /// <param name="a">First corner of the line.</param>
+        /// <param name="b">Second corner of the line.</param>
+        /// <returns>The indices of the vertices, or, in case the line is degenerated, (-1,-1).</returns>
+        public virtual (int, int) AddLine(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b)
         {
-            return _LinesIndices ?? (IReadOnlyList<(int, int)>)Array.Empty<(int, int)>();
+            throw new NotSupportedException("Lines are not supported for this primitive");
         }
 
-        private IReadOnlyList<(int, int, int)> _GetTriangleIndices()
+        /// <summary>
+        /// Adds a triangle.
+        /// </summary>
+        /// <param name="a">First corner of the triangle.</param>
+        /// <param name="b">Second corner of the triangle.</param>
+        /// <param name="c">Third corner of the triangle.</param>
+        /// <returns>The indices of the vertices, or, in case the triangle is degenerated, (-1,-1,-1).</returns>
+        public virtual (int, int, int) AddTriangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c)
         {
-            return _TrianglesIndices ?? (IReadOnlyList<(int, int, int)>)Array.Empty<(int, int, int)>();
+            throw new NotSupportedException("Triangles are not supported for this primitive");
         }
 
-        public void TransformVertices(Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> transformFunc)
+        /// <summary>
+        /// Adds a quadrangle.
+        /// </summary>
+        /// <param name="a">First corner of the quadrangle.</param>
+        /// <param name="b">Second corner of the quadrangle.</param>
+        /// <param name="c">Third corner of the quadrangle.</param>
+        /// <param name="d">Fourth corner of the quadrangle.</param>
+        /// <returns>The indices of the vertices, or, in case the quadrangle is degenerated, (-1,-1,-1,-1).</returns>
+        public virtual (int, int, int, int) AddQuadrangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c, VertexBuilder<TvG, TvM, TvS> d)
         {
-            _Vertices.TransformVertices(transformFunc);
+            throw new NotSupportedException("Quadrangles are not supported for this primitive");
         }
 
         #endregion
 
         #region helper types
 
-        sealed class VertexListWrapper : VertexList<VertexBuilder<TvG, TvM, TvS>>, IReadOnlyList<IVertexBuilder>
+        private sealed class VertexListWrapper : VertexList<VertexBuilder<TvG, TvM, TvS>>, IReadOnlyList<IVertexBuilder>
         {
             #pragma warning disable SA1100 // Do not prefix calls with base unless local implementation exists
             IVertexBuilder IReadOnlyList<IVertexBuilder>.this[int index] => base[index];
@@ -499,14 +371,58 @@ namespace SharpGLTF.Geometry
             }
         }
 
-        struct PointListWrapper : IReadOnlyList<int>
+        #endregion
+    }
+
+    [System.Diagnostics.DebuggerDisplay("Points[{Points.Count}] {_Material}")]
+    sealed class PointsPrimitiveBuilder<TMaterial, TvG, TvM, TvS> : PrimitiveBuilder<TMaterial, TvG, TvM, TvS>
+        where TvG : struct, IVertexGeometry
+        where TvM : struct, IVertexMaterial
+        where TvS : struct, IVertexSkinning
+    {
+        #region lifecycle
+
+        internal PointsPrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material)
+            : base(mesh, material)
         {
-            public PointListWrapper(IReadOnlyList<IVertexBuilder> vertices)
+        }
+
+        #endregion
+
+        #region properties
+
+        public override int VerticesPerPrimitive => 1;
+
+        public override IReadOnlyList<int> Points => new PointListWrapper<VertexBuilder<TvG, TvM, TvS>>(this.Vertices);
+
+        #endregion
+
+        #region API
+
+        public override int AddPoint(VertexBuilder<TvG, TvM, TvS> a)
+        {
+            if (Mesh.VertexPreprocessor != null)
+            {
+                if (!Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return -1;
+            }
+
+            return UseVertex(a);
+        }
+
+        public override IReadOnlyList<int> GetIndices() { return Array.Empty<int>(); }
+
+        #endregion
+
+        #region types
+
+        struct PointListWrapper<T> : IReadOnlyList<int>
+        {
+            public PointListWrapper(IReadOnlyList<T> vertices)
             {
                 _Vertices = vertices;
             }
 
-            private readonly IReadOnlyList<IVertexBuilder> _Vertices;
+            private readonly IReadOnlyList<T> _Vertices;
 
             public int this[int index] => index;
 
@@ -517,24 +433,242 @@ namespace SharpGLTF.Geometry
             IEnumerator IEnumerable.GetEnumerator() { return Enumerable.Range(0, _Vertices.Count).GetEnumerator(); }
         }
 
-        sealed class LineListWrapper : IReadOnlyList<(int, int)>
+        #endregion
+    }
+
+    [System.Diagnostics.DebuggerDisplay("Lines[{Lines.Count}] {_Material}")]
+    sealed class LinesPrimitiveBuilder<TMaterial, TvG, TvM, TvS> : PrimitiveBuilder<TMaterial, TvG, TvM, TvS>
+        where TvG : struct, IVertexGeometry
+        where TvM : struct, IVertexMaterial
+        where TvS : struct, IVertexSkinning
+    {
+        #region lifecycle
+
+        internal LinesPrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material)
+            : base(mesh, material)
         {
-            public LineListWrapper(List<int> source) { _Indices = source; }
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly List<(int, int)> _Indices = new List<(int, int)>();
+
+        #endregion
+
+        #region properties
+
+        public override int VerticesPerPrimitive => 2;
 
-            private readonly IList<int> _Indices;
+        public override IReadOnlyList<(int, int)> Lines => _Indices;
 
-            public (int, int) this[int index]
+        #endregion
+
+        #region API
+
+        public override (int, int) AddLine(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b)
+        {
+            if (Mesh.VertexPreprocessor != null)
+            {
+                if (!Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return (-1, -1);
+                if (!Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return (-1, -1);
+            }
+
+            // check for degenerated line
+            if (a.Position == b.Position) return (-1, -1);
+
+            var aa = UseVertex(a);
+            var bb = UseVertex(b);
+
+            System.Diagnostics.Debug.Assert(aa != bb, "unexpected degenerated line");
+
+            _Indices.Add((aa, bb));
+
+            return (aa, bb);
+        }
+
+        public override IReadOnlyList<int> GetIndices()
+        {
+            return _Indices
+                .SelectMany(item => new[] { item.Item1, item.Item2 })
+                .ToList();
+        }
+
+        #endregion
+    }
+
+    [System.Diagnostics.DebuggerDisplay("Triangles[{Triangles.Count}] {_Material}")]
+    sealed class TrianglesPrimitiveBuilder<TMaterial, TvG, TvM, TvS> : PrimitiveBuilder<TMaterial, TvG, TvM, TvS>
+        where TvG : struct, IVertexGeometry
+        where TvM : struct, IVertexMaterial
+        where TvS : struct, IVertexSkinning
+    {
+        #region lifecycle
+
+        internal TrianglesPrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material)
+            : base(mesh, material)
+        {
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly List<(int, int, int)> _TriIndices = new List<(int, int, int)>();
+        private readonly List<(int, int, int, int)> _QuadIndices = new List<(int, int, int, int)>();
+
+        #endregion
+
+        #region properties
+
+        public override int VerticesPerPrimitive => 3;
+
+        public override IReadOnlyList<(int, int, int)> Triangles => new TriangleList(_TriIndices, _QuadIndices);
+
+        public override IReadOnlyList<(int, int, int, int?)> Surfaces => new SurfaceList(_TriIndices, _QuadIndices);
+
+        #endregion
+
+        #region API
+
+        public override (int, int, int) AddTriangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c)
+        {
+            if (Mesh.VertexPreprocessor != null)
+            {
+                if (!Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return (-1, -1, -1);
+                if (!Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return (-1, -1, -1);
+                if (!Mesh.VertexPreprocessor.PreprocessVertex(ref c)) return (-1, -1, -1);
+            }
+
+            return _AddTriangle(a, b, c);
+        }
+
+        public override (int, int, int, int) AddQuadrangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c, VertexBuilder<TvG, TvM, TvS> d)
+        {
+            if (Mesh.VertexPreprocessor != null)
+            {
+                if (!Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return (-1, -1, -1, -1);
+                if (!Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return (-1, -1, -1, -1);
+                if (!Mesh.VertexPreprocessor.PreprocessVertex(ref c)) return (-1, -1, -1, -1);
+                if (!Mesh.VertexPreprocessor.PreprocessVertex(ref d)) return (-1, -1, -1, -1);
+            }
+
+            // if diagonals are degenerated, the whole quad is degenerated.
+            if (a.Position == c.Position || b.Position == d.Position) return (-1, -1, -1, -1);
+
+            // A-B degenerated
+            if (a.Position == b.Position)
+            {
+                var tri = _AddTriangle(b, c, d);
+                return (-1, tri.Item1, tri.Item2, tri.Item3);
+            }
+
+            // B-C degenerated
+            if (b.Position == c.Position)
+            {
+                var tri = _AddTriangle(a, c, d);
+                return (tri.Item1, -1, tri.Item2, tri.Item3);
+            }
+
+            // C-D degenerated
+            if (c.Position == d.Position)
+            {
+                var tri = _AddTriangle(a, b, d);
+                return (tri.Item1, tri.Item2, -1, tri.Item3);
+            }
+
+            // D-A degenerated
+            if (d.Position == a.Position)
+            {
+                var tri = _AddTriangle(a, b, c);
+                return (tri.Item1, tri.Item2, tri.Item3, -1);
+            }
+
+            // at some points it could be interesting to comply with https://github.com/KhronosGroup/glTF/pull/1620
+
+            var aa = UseVertex(a);
+            var bb = UseVertex(b);
+            var cc = UseVertex(c);
+            var dd = UseVertex(d);
+
+            System.Diagnostics.Debug.Assert(aa != bb && aa != cc && bb != cc && cc != dd, "unexpected degenerated triangle");
+
+            var oddEven = MeshBuilderToolkit.GetQuadrangleDiagonal(a.Position, b.Position, c.Position, d.Position);
+
+            if (oddEven)
+            {
+                _QuadIndices.Add((aa, bb, cc, dd));
+                return (aa, bb, cc, dd);
+            }
+            else
+            {
+                _QuadIndices.Add((bb, cc, dd, aa));
+                return (aa, bb, cc, dd); // notice that we return the indices in the same order we got them.
+            }
+        }
+
+        private (int, int, int) _AddTriangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c)
+        {
+            // check for degenerated triangle
+            if (a.Position == b.Position || a.Position == c.Position || b.Position == c.Position) return (-1, -1, -1);
+
+            var aa = UseVertex(a);
+            var bb = UseVertex(b);
+            var cc = UseVertex(c);
+
+            System.Diagnostics.Debug.Assert(aa != bb && aa != cc && bb != cc, "unexpected degenerated triangle");
+
+            // TODO: check if a triangle with indices aa-bb-cc, bb-cc-aa or cc-aa-bb already exists, since there's no point in having the same polygon twice.
+
+            _TriIndices.Add((aa, bb, cc));
+
+            return (aa, bb, cc);
+        }
+
+        public override IReadOnlyList<int> GetIndices()
+        {
+            return Triangles
+                .SelectMany(item => new[] { item.Item1, item.Item2, item.Item3 })
+                .ToList();
+        }
+
+        #endregion
+
+        #region Types
+
+        private struct TriangleList : IReadOnlyList<(int, int, int)>
+        {
+            public TriangleList(IReadOnlyList<(int, int, int)> tris, IReadOnlyList<(int, int, int, int)> quads)
+            {
+                _Tris = tris;
+                _Quads = quads;
+            }
+
+            private readonly IReadOnlyList<(int, int, int)> _Tris;
+            private readonly IReadOnlyList<(int, int, int, int)> _Quads;
+
+            public int Count => _Tris.Count + (_Quads.Count * 2);
+
+            public (int, int, int) this[int index]
             {
                 get
                 {
-                    index *= 2;
-                    return (_Indices[index + 0], _Indices[index + 1]);
+                    if (index < _Tris.Count)
+                    {
+                        var tri = _Tris[index];
+                        return tri;
+                    }
+
+                    index -= _Tris.Count;
+
+                    var quad = _Quads[index / 2];
+
+                    return (index & 1) == 0 ? (quad.Item1, quad.Item2, quad.Item3) : (quad.Item1, quad.Item3, quad.Item4);
                 }
             }
 
-            public int Count => _Indices.Count / 2;
-
-            public IEnumerator<(int, int)> GetEnumerator()
+            public IEnumerator<(int, int, int)> GetEnumerator()
             {
                 var c = this.Count;
                 for (int i = 0; i < c; ++i) yield return this[i];
@@ -547,24 +681,37 @@ namespace SharpGLTF.Geometry
             }
         }
 
-        sealed class TriangleListWrapper : IReadOnlyList<(int, int, int)>
+        private struct SurfaceList : IReadOnlyList<(int, int, int, int?)>
         {
-            public TriangleListWrapper(List<int> source) { _Indices = source; }
+            public SurfaceList(IReadOnlyList<(int, int, int)> tris, IReadOnlyList<(int, int, int, int)> quads)
+            {
+                _Tris = tris;
+                _Quads = quads;
+            }
 
-            private readonly List<int> _Indices;
+            private readonly IReadOnlyList<(int, int, int)> _Tris;
+            private readonly IReadOnlyList<(int, int, int, int)> _Quads;
 
-            public (int, int, int) this[int index]
+            public int Count => _Tris.Count + _Quads.Count;
+
+            public (int, int, int, int?) this[int index]
             {
                 get
                 {
-                    index *= 3;
-                    return (_Indices[index + 0], _Indices[index + 1], _Indices[index + 2]);
+                    if (index < _Tris.Count)
+                    {
+                        var tri = _Tris[index];
+                        return (tri.Item1, tri.Item2, tri.Item3, null);
+                    }
+
+                    index -= _Tris.Count;
+
+                    var quad = _Quads[index];
+                    return (quad.Item1, quad.Item2, quad.Item3, quad.Item4);
                 }
             }
 
-            public int Count => _Indices.Count / 3;
-
-            public IEnumerator<(int, int, int)> GetEnumerator()
+            public IEnumerator<(int, int, int, int?)> GetEnumerator()
             {
                 var c = this.Count;
                 for (int i = 0; i < c; ++i) yield return this[i];

+ 21 - 26
tests/SharpGLTF.Tests/Geometry/MeshBuilderTests.cs

@@ -15,25 +15,6 @@ namespace SharpGLTF.Geometry
     [Category("Toolkit.Geometry")]
     public class MeshBuilderTests
     {
-        [Description("Although a triangle with three corners at zero should be accounted as a degenerated triangle, if it has different skinning per vertex, it should not be discarded.")]
-        [Test]
-        public void CreatePseudoDegeneratedTriangle()
-        {
-            var m = new Materials.MaterialBuilder();
-
-            var mb = VERTEX1.CreateCompatibleMesh();
-
-            var a = new VERTEX1(Vector3.Zero, (0, 1));
-            var b = new VERTEX1(Vector3.Zero, (1, 1));
-            var c = new VERTEX1(Vector3.Zero, (2, 1));
-
-            mb.UsePrimitive(m).AddTriangle(a, b, c);
-
-            var triCount = mb.Primitives.Sum(item => item.Triangles.Count());
-
-            Assert.AreEqual(1, triCount);
-        }
-
         [Test]
         public void CreateInvalidTriangles()
         {
@@ -139,13 +120,7 @@ namespace SharpGLTF.Geometry
 
             Assert.AreEqual(null, mmmmm[0]);
         }
-
-        [Test]
-        public void CreatePartiallyEmptyMesh()
-        {
-            
-        }
-
+        
         [Test]
         public static void CreateWithDegeneratedTriangle()
         {
@@ -244,5 +219,25 @@ namespace SharpGLTF.Geometry
             Assert.AreEqual(4, model.LogicalMaterials.Count);
 
         }
+
+        [Test]
+        public void CreateMeshWithTriangleAndQuad()
+        {
+            var dmat = Materials.MaterialBuilder.CreateDefault();
+            var mesh = VERTEX1.CreateCompatibleMesh();
+            var prim = mesh.UsePrimitive(dmat);
+
+            var triIdx = prim.AddTriangle(new VERTEX1(Vector3.Zero), new VERTEX1(Vector3.UnitX * 2) , new VERTEX1(Vector3.UnitY * 2));
+            var qadIdx = prim.AddQuadrangle(new VERTEX1(-Vector3.UnitX), new VERTEX1(Vector3.UnitY), new VERTEX1(Vector3.UnitX), new VERTEX1(-Vector3.UnitY));
+
+            Assert.AreEqual(7, prim.Vertices.Count);
+            Assert.AreEqual(3, prim.Triangles.Count);
+            Assert.AreEqual(2, prim.Surfaces.Count);
+
+            Assert.AreEqual((0, 1, 2), triIdx);
+            Assert.AreEqual((3, 4, 5, 6), qadIdx);
+
+            CollectionAssert.AreEqual(new[] { 0, 1, 2, 3, 4, 5, 3, 5, 6 }, prim.GetIndices());
+        }
     }
 }

+ 8 - 2
tests/SharpGLTF.Tests/Scenes/SceneBuilderTests.cs

@@ -50,7 +50,7 @@ namespace SharpGLTF.Scenes
             Assert.AreEqual((0, 1, 2, 3), idx);
 
             idx = prim.AddQuadrangle(new VertexPosition(0, -1, 1), new VertexPosition(1, 0, 1), new VertexPosition(0, 1, 1), new VertexPosition(0.5f, 0, 1));
-            Assert.AreEqual((7,4,5,6), idx);
+            Assert.AreEqual((4, 5, 6, 7), idx);
 
             idx = prim.AddQuadrangle(new VertexPosition(0, 0.5f, 2), new VertexPosition(1, 0, 2), new VertexPosition(0, 1, 2), new VertexPosition(-1, 0, 2));
             Assert.AreEqual((8,9,10,11), idx);
@@ -59,11 +59,17 @@ namespace SharpGLTF.Scenes
             Assert.AreEqual((12,13,14,15), idx);
 
             idx = prim.AddQuadrangle(new VertexPosition(1, 0, 4), new VertexPosition(1, 0, 4), new VertexPosition(0, 1, 4), new VertexPosition(-1, 0, 4));
-            Assert.AreEqual((16, -1, 17, 18), idx);
+            Assert.AreEqual((-1, 16, 17, 18), idx);
 
             idx = prim.AddQuadrangle(new VertexPosition(1, 0, 4), new VertexPosition(1, 0, 4), new VertexPosition(0, 1, 4), new VertexPosition(0, 1, 4));
             Assert.AreEqual((-1, -1, -1, -1), idx);
 
+            idx = prim.AddQuadrangle(new VertexPosition(0, 0, 5), new VertexPosition(10, -1, 5), new VertexPosition(9, 0, 5), new VertexPosition(10, 1, 5));
+            Assert.AreEqual((19,20,21,22), idx);
+
+            idx = prim.AddQuadrangle(new VertexPosition(10, -1, 6), new VertexPosition(9, 0, 6), new VertexPosition(10, 1, 6), new VertexPosition(0, 0, 6));
+            Assert.AreEqual((23, 24, 25, 26), idx);
+
             var scene = new SceneBuilder();
 
             scene.AddMesh(mesh, Matrix4x4.Identity);