Browse Source

Added support for points and lines.

Vicente Penades 6 years ago
parent
commit
8d1e977dc2

+ 34 - 0
src/Shared/_Extensions.cs

@@ -410,6 +410,35 @@ namespace SharpGLTF
             }
             }
         }
         }
 
 
+        public static IEnumerable<(int, int)> GetLinesIndices(this PrimitiveType ptype, int count)
+        {
+            return ptype.GetLinesIndices(Enumerable.Range(0, count).Select(item => (UInt32)item));
+        }
+
+        public static IEnumerable<(int, int)> GetLinesIndices(this PrimitiveType ptype, IEnumerable<UInt32> sourceIndices)
+        {
+            switch (ptype)
+            {
+                case PrimitiveType.LINES:
+                    {
+                        using (var ptr = sourceIndices.GetEnumerator())
+                        {
+                            while (true)
+                            {
+                                if (!ptr.MoveNext()) break;
+                                var a = ptr.Current;
+                                if (!ptr.MoveNext()) break;
+                                var b = ptr.Current;
+
+                                if (!_IsDegenerated(a, b)) yield return ((int)a, (int)b);
+                            }
+                        }
+
+                        break;
+                    }
+            }
+        }
+
         public static IEnumerable<(int, int, int)> GetTrianglesIndices(this PrimitiveType ptype, int count)
         public static IEnumerable<(int, int, int)> GetTrianglesIndices(this PrimitiveType ptype, int count)
         {
         {
             return ptype.GetTrianglesIndices(Enumerable.Range(0, count).Select(item => (UInt32)item));
             return ptype.GetTrianglesIndices(Enumerable.Range(0, count).Select(item => (UInt32)item));
@@ -495,6 +524,11 @@ namespace SharpGLTF
             }
             }
         }
         }
 
 
+        private static bool _IsDegenerated(uint a, uint b)
+        {
+            return a == b;
+        }
+
         private static bool _IsDegenerated(uint a, uint b, uint c)
         private static bool _IsDegenerated(uint a, uint b, uint c)
         {
         {
             if (a == b) return true;
             if (a == b) return true;

+ 12 - 0
src/SharpGLTF.Toolkit/Collections/VertexList.cs

@@ -37,6 +37,18 @@ namespace SharpGLTF.Collections
             return index;
             return index;
         }
         }
 
 
+        internal void TransformVertices(Func<T, T> transformFunc)
+        {
+            _VertexCache.Clear();
+
+            for (int i = 0; i < _Vertices.Count; ++i)
+            {
+                var v = transformFunc(_Vertices[i]);
+                _Vertices[i] = v;
+                _VertexCache[v] = i;
+            }
+        }
+
         #endregion
         #endregion
     }
     }
 }
 }

+ 42 - 22
src/SharpGLTF.Toolkit/Geometry/MeshBuilder.cs

@@ -13,11 +13,11 @@ namespace SharpGLTF.Geometry
     {
     {
         string Name { get; set; }
         string Name { get; set; }
 
 
-        IReadOnlyCollection<TMaterial> Materials { get; }
+        IEnumerable<TMaterial> Materials { get; }
 
 
-        IPrimitiveBuilder UsePrimitive(TMaterial material);
+        IReadOnlyCollection<IPrimitive<TMaterial>> Primitives { get; }
 
 
-        IPrimitive<TMaterial> GetPrimitive(TMaterial material);
+        IPrimitiveBuilder UsePrimitive(TMaterial material, int primitiveVertexCount = 3);
 
 
         void Validate();
         void Validate();
     }
     }
@@ -66,7 +66,9 @@ namespace SharpGLTF.Geometry
 
 
         #region data
         #region data
 
 
-        private readonly Dictionary<TMaterial, PrimitiveBuilder<TMaterial, TvP, TvM, TvS>> _Primitives = new Dictionary<TMaterial, PrimitiveBuilder<TMaterial, TvP, TvM, TvS>>();
+        private readonly Dictionary<(TMaterial, int), PrimitiveBuilder<TMaterial, TvP, TvM, TvS>> _Primitives = new Dictionary<(TMaterial, int), PrimitiveBuilder<TMaterial, TvP, TvM, TvS>>();
+
+        internal IPolygonTriangulator _Triangulator = NaivePolygonTriangulation.Default;
 
 
         #endregion
         #endregion
 
 
@@ -74,53 +76,71 @@ namespace SharpGLTF.Geometry
 
 
         public Boolean StrictMode { get; set; }
         public Boolean StrictMode { get; set; }
 
 
+        public IPolygonTriangulator Triangulator
+        {
+            get => _Triangulator;
+            set => _Triangulator = value == null ? NaivePolygonTriangulation.Default : _Triangulator;
+        }
+
         public string Name { get; set; }
         public string Name { get; set; }
 
 
-        public IReadOnlyCollection<TMaterial> Materials => _Primitives.Keys;
+        public IEnumerable<TMaterial> Materials => _Primitives.Keys.Select(item => item.Item1).Distinct();
 
 
         public IReadOnlyCollection<PrimitiveBuilder<TMaterial, TvP, TvM, TvS>> Primitives => _Primitives.Values;
         public IReadOnlyCollection<PrimitiveBuilder<TMaterial, TvP, TvM, TvS>> Primitives => _Primitives.Values;
 
 
-        string IMeshBuilder<TMaterial>.Name { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
-
-        IReadOnlyCollection<TMaterial> IMeshBuilder<TMaterial>.Materials => throw new NotImplementedException();
+        IReadOnlyCollection<IPrimitive<TMaterial>> IMeshBuilder<TMaterial>.Primitives => _Primitives.Values;
 
 
         #endregion
         #endregion
 
 
         #region API
         #region API
 
 
-        private PrimitiveBuilder<TMaterial, TvP, TvM, TvS> _UsePrimitive(TMaterial material)
+        private PrimitiveBuilder<TMaterial, TvP, TvM, TvS> _UsePrimitive((TMaterial, int) key)
         {
         {
-            if (!_Primitives.TryGetValue(material, out PrimitiveBuilder<TMaterial, TvP, TvM, TvS> primitive))
+            if (!_Primitives.TryGetValue(key, out PrimitiveBuilder<TMaterial, TvP, TvM, TvS> primitive))
             {
             {
-                primitive = new PrimitiveBuilder<TMaterial, TvP, TvM, TvS>(this, material, StrictMode);
-                _Primitives[material] = primitive;
+                primitive = new PrimitiveBuilder<TMaterial, TvP, TvM, TvS>(this, key.Item1, key.Item2, StrictMode);
+                _Primitives[key] = primitive;
             }
             }
 
 
             return primitive;
             return primitive;
         }
         }
 
 
-        public PrimitiveBuilder<TMaterial, TvP, TvM, TvS> UsePrimitive(TMaterial material) { return _UsePrimitive(material); }
+        public PrimitiveBuilder<TMaterial, TvP, TvM, TvS> UsePrimitive(TMaterial material, int primitiveVertexCount = 3)
+        {
+            Guard.NotNull(material, nameof(material));
+            Guard.MustBeBetweenOrEqualTo(primitiveVertexCount, 1, 3, nameof(primitiveVertexCount));
 
 
-        IPrimitiveBuilder IMeshBuilder<TMaterial>.UsePrimitive(TMaterial material) { return _UsePrimitive(material); }
+            return _UsePrimitive((material, primitiveVertexCount));
+        }
 
 
-        IPrimitive<TMaterial> IMeshBuilder<TMaterial>.GetPrimitive(TMaterial material)
+        IPrimitiveBuilder IMeshBuilder<TMaterial>.UsePrimitive(TMaterial material, int primitiveVertexCount)
         {
         {
-            if (_Primitives.TryGetValue(material, out PrimitiveBuilder<TMaterial, TvP, TvM, TvS> primitive))
-            {
-                return primitive;
-            }
+            Guard.NotNull(material, nameof(material));
+            Guard.MustBeBetweenOrEqualTo(primitiveVertexCount, 1, 3, nameof(primitiveVertexCount));
 
 
-            return null;
+            return _UsePrimitive((material, primitiveVertexCount));
         }
         }
 
 
-        public void AddMesh(MeshBuilder<TMaterial, TvP, TvM, TvS> mesh, Matrix4x4 transform)
+        public void AddMesh(MeshBuilder<TMaterial, TvP, TvM, TvS> mesh, Func<TMaterial, TMaterial> materialTransform, Func<Vertex<TvP, TvM, TvS>, Vertex<TvP, TvM, TvS>> vertexTransform)
         {
         {
             foreach (var p in mesh.Primitives)
             foreach (var p in mesh.Primitives)
             {
             {
-                UsePrimitive(p.Material).AddPrimitive(p, transform);
+                var materialKey = materialTransform(p.Material);
+
+                UsePrimitive(materialKey).AddPrimitive(p, vertexTransform);
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Transforms all the points of all the <see cref="PrimitiveBuilder{TMaterial, TvP, TvM, TvS}"/>
+        /// of the this <see cref="MeshBuilder{TMaterial, TvP, TvM, TvS}"/> using the given lambfa function.
+        /// </summary>
+        /// <param name="vertexTransform">A lambda function to transform <see cref="Vertex{TvP, TvM, TvS}"/> vertices.</param>
+        public void TransformVertices(Func<Vertex<TvP, TvM, TvS>, Vertex<TvP, TvM, TvS>> vertexTransform)
+        {
+            foreach (var p in Primitives) p.TransformVertices(vertexTransform);
+        }
+
         public void Validate()
         public void Validate()
         {
         {
             foreach (var p in _Primitives.Values)
             foreach (var p in _Primitives.Values)

+ 22 - 5
src/SharpGLTF.Toolkit/Geometry/PackedMeshBuilder.cs

@@ -62,7 +62,7 @@ namespace SharpGLTF.Geometry
 
 
                 foreach (var primitiveBuilder in meshBuilder.Primitives)
                 foreach (var primitiveBuilder in meshBuilder.Primitives)
                 {
                 {
-                    dstMesh.AddPrimitive(primitiveBuilder.Material, vertexBlocks[idx], indexBlocks[idx]);
+                    dstMesh.AddPrimitive(primitiveBuilder.Material, primitiveBuilder.VerticesPerPrimitive, vertexBlocks[idx], indexBlocks[idx]);
 
 
                     ++idx;
                     ++idx;
                 }
                 }
@@ -85,9 +85,9 @@ namespace SharpGLTF.Geometry
 
 
         #region API
         #region API
 
 
-        public void AddPrimitive(TMaterial material, Memory.MemoryAccessor[] vrtAccessors, Memory.MemoryAccessor idxAccessor)
+        public void AddPrimitive(TMaterial material, int primitiveVertexCount, Memory.MemoryAccessor[] vrtAccessors, Memory.MemoryAccessor idxAccessor)
         {
         {
-            var p = new PackedPrimitiveBuilder<TMaterial>(material, vrtAccessors, idxAccessor);
+            var p = new PackedPrimitiveBuilder<TMaterial>(material, primitiveVertexCount, vrtAccessors, idxAccessor);
             _Primitives.Add(p);
             _Primitives.Add(p);
         }
         }
 
 
@@ -110,9 +110,10 @@ namespace SharpGLTF.Geometry
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        internal PackedPrimitiveBuilder(TMaterial material, Memory.MemoryAccessor[] vrtAccessors, Memory.MemoryAccessor idxAccessor)
+        internal PackedPrimitiveBuilder(TMaterial material, int primitiveVertexCount, Memory.MemoryAccessor[] vrtAccessors, Memory.MemoryAccessor idxAccessor)
         {
         {
             _Material = material;
             _Material = material;
+            _VerticesPerPrimitive = primitiveVertexCount;
             _VertexAccessors = vrtAccessors;
             _VertexAccessors = vrtAccessors;
             _IndexAccessors = idxAccessor;
             _IndexAccessors = idxAccessor;
         }
         }
@@ -122,6 +123,7 @@ namespace SharpGLTF.Geometry
         #region data
         #region data
 
 
         private readonly TMaterial _Material;
         private readonly TMaterial _Material;
+        private readonly int _VerticesPerPrimitive;
 
 
         private readonly Memory.MemoryAccessor[] _VertexAccessors;
         private readonly Memory.MemoryAccessor[] _VertexAccessors;
 
 
@@ -133,10 +135,25 @@ namespace SharpGLTF.Geometry
 
 
         internal void CopyToMesh(Mesh dstMesh, Func<TMaterial, Material> materialEvaluator)
         internal void CopyToMesh(Mesh dstMesh, Func<TMaterial, Material> materialEvaluator)
         {
         {
+            if (_VerticesPerPrimitive < 1 || _VerticesPerPrimitive > 3) return;
+
+            if (_VerticesPerPrimitive == 1)
+            {
+                dstMesh.CreatePrimitive()
+                        .WithMaterial(materialEvaluator(_Material))
+                        .WithVertexAccessors(_VertexAccessors)
+                        .WithIndicesAutomatic(PrimitiveType.POINTS);
+
+                return;
+            }
+
+            var pt = PrimitiveType.LINES;
+            if (_VerticesPerPrimitive == 3) pt = PrimitiveType.TRIANGLES;
+
             dstMesh.CreatePrimitive()
             dstMesh.CreatePrimitive()
                         .WithMaterial(materialEvaluator(_Material))
                         .WithMaterial(materialEvaluator(_Material))
                         .WithVertexAccessors(_VertexAccessors)
                         .WithVertexAccessors(_VertexAccessors)
-                        .WithIndicesAccessor(PrimitiveType.TRIANGLES, _IndexAccessors);
+                        .WithIndicesAccessor(pt, _IndexAccessors);
         }
         }
 
 
         #endregion
         #endregion

+ 149 - 30
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -15,17 +15,17 @@ namespace SharpGLTF.Geometry
 
 
         int VertexCount { get; }
         int VertexCount { get; }
 
 
-        TvPP GetVertexGeometry<TvPP>(int index)
-            where TvPP : struct, IVertexGeometry;
-
-        TvMM GetVertexMaterial<TvMM>(int index)
-            where TvMM : struct, IVertexMaterial;
-
-        TvSS GetVertexSkinning<TvSS>(int index)
+        Vertex<TvPP, TvMM, TvSS> GetVertex<TvPP, TvMM, TvSS>(int index)
+            where TvPP : struct, IVertexGeometry
+            where TvMM : struct, IVertexMaterial
             where TvSS : struct, IVertexSkinning;
             where TvSS : struct, IVertexSkinning;
 
 
         IReadOnlyList<int> Indices { get; }
         IReadOnlyList<int> Indices { get; }
 
 
+        IEnumerable<int> Points { get; }
+
+        IEnumerable<(int, int)> Lines { get; }
+
         IEnumerable<(int, int, int)> Triangles { get; }
         IEnumerable<(int, int, int)> Triangles { get; }
     }
     }
 
 
@@ -78,11 +78,12 @@ namespace SharpGLTF.Geometry
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        internal PrimitiveBuilder(MeshBuilder<TMaterial, TvP, TvM, TvS> mesh, TMaterial material, bool strict)
+        internal PrimitiveBuilder(MeshBuilder<TMaterial, TvP, TvM, TvS> mesh, TMaterial material, int primitiveVertexCount, bool strict)
         {
         {
             this._Scrict = strict;
             this._Scrict = strict;
             this._Mesh = mesh;
             this._Mesh = mesh;
             this._Material = material;
             this._Material = material;
+            this._PrimitiveVertexCount = primitiveVertexCount;
         }
         }
 
 
         #endregion
         #endregion
@@ -95,6 +96,8 @@ namespace SharpGLTF.Geometry
 
 
         private readonly TMaterial _Material;
         private readonly TMaterial _Material;
 
 
+        private readonly int _PrimitiveVertexCount;
+
         private readonly VertexList<Vertex<TvP, TvM, TvS>> _Vertices = new VertexList<Vertex<TvP, TvM, TvS>>();
         private readonly VertexList<Vertex<TvP, TvM, TvS>> _Vertices = new VertexList<Vertex<TvP, TvM, TvS>>();
         private readonly List<int> _Indices = new List<int>();
         private readonly List<int> _Indices = new List<int>();
 
 
@@ -106,13 +109,25 @@ namespace SharpGLTF.Geometry
 
 
         public TMaterial Material => _Material;
         public TMaterial Material => _Material;
 
 
-        public int VertexCount => _Vertices.Count;
+        /// <summary>
+        /// Gets the number of vertices used by each primitive:
+        /// 1 - Points
+        /// 2 - Lines
+        /// 3 - Triangles
+        /// </summary>
+        public int VerticesPerPrimitive => _PrimitiveVertexCount;
+
+        public int VertexCount => Vertices.Count;
 
 
         public IReadOnlyList<Vertex<TvP, TvM, TvS>> Vertices => _Vertices;
         public IReadOnlyList<Vertex<TvP, TvM, TvS>> Vertices => _Vertices;
 
 
         public IReadOnlyList<int> Indices => _Indices;
         public IReadOnlyList<int> Indices => _Indices;
 
 
-        public IEnumerable<(int, int, int)> Triangles => Schema2.PrimitiveType.TRIANGLES.GetTrianglesIndices(_Indices.Select(item => (uint)item));
+        public IEnumerable<int> Points => _GetPointIndices();
+
+        public IEnumerable<(int, int)> Lines => _GetLineIndices();
+
+        public IEnumerable<(int, int, int)> Triangles => _GetTriangleIndices();
 
 
         #endregion
         #endregion
 
 
@@ -135,6 +150,42 @@ namespace SharpGLTF.Geometry
             return _Vertices.Use(vertex);
             return _Vertices.Use(vertex);
         }
         }
 
 
+        /// <summary>
+        /// Adds a point.
+        /// </summary>
+        /// <param name="a">vertex for this point.</param>
+        public void AddPoint(Vertex<TvP, TvM, TvS> a)
+        {
+            Guard.IsTrue(_PrimitiveVertexCount == 1, nameof(VerticesPerPrimitive), "Points are not supported for this primitive");
+
+            UseVertex(a);
+        }
+
+        /// <summary>
+        /// Adds a line.
+        /// </summary>
+        /// <param name="a">First corner of the line.</param>
+        /// <param name="b">Second corner of the line.</param>
+        public void AddLine(Vertex<TvP, TvM, TvS> a, Vertex<TvP, TvM, TvS> b)
+        {
+            Guard.IsTrue(_PrimitiveVertexCount == 2, nameof(VerticesPerPrimitive), "Lines are not supported for this primitive");
+
+            var aa = UseVertex(a);
+            var bb = UseVertex(b);
+
+            // check for degenerated triangles:
+            if (aa == bb)
+            {
+                if (_Scrict) throw new ArgumentException($"Invalid triangle indices {aa} {bb}");
+                return;
+            }
+
+            // TODO: check if a triangle with indices aa-bb-cc already exists.
+
+            _Indices.Add(aa);
+            _Indices.Add(bb);
+        }
+
         /// <summary>
         /// <summary>
         /// Adds a triangle.
         /// Adds a triangle.
         /// </summary>
         /// </summary>
@@ -143,6 +194,8 @@ namespace SharpGLTF.Geometry
         /// <param name="c">Third corner of the triangle.</param>
         /// <param name="c">Third corner of the triangle.</param>
         public void AddTriangle(Vertex<TvP, TvM, TvS> a, Vertex<TvP, TvM, TvS> b, Vertex<TvP, TvM, TvS> c)
         public void AddTriangle(Vertex<TvP, TvM, TvS> a, Vertex<TvP, TvM, TvS> b, Vertex<TvP, TvM, TvS> c)
         {
         {
+            Guard.IsTrue(_PrimitiveVertexCount == 3, nameof(VerticesPerPrimitive), "Triangles are not supported for this primitive");
+
             var aa = UseVertex(a);
             var aa = UseVertex(a);
             var bb = UseVertex(b);
             var bb = UseVertex(b);
             var cc = UseVertex(c);
             var cc = UseVertex(c);
@@ -163,32 +216,80 @@ namespace SharpGLTF.Geometry
 
 
         /// <summary>
         /// <summary>
         /// Adds a polygon as a decomposed collection of triangles.
         /// Adds a polygon as a decomposed collection of triangles.
-        /// Currently only convex polygons are supported.
         /// </summary>
         /// </summary>
         /// <param name="points">The corners of the polygon.</param>
         /// <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 Vertex<TvP, TvM, TvS>[] points)
         public void AddPolygon(params Vertex<TvP, TvM, TvS>[] points)
         {
         {
-            for (int i = 2; i < points.Length; ++i)
+            Span<Vector3> vertices = stackalloc Vector3[points.Length];
+
+            for (int i = 0; i < vertices.Length; ++i)
             {
             {
-                AddTriangle(points[0], points[i - 1], points[i]);
+                vertices[i] = points[i].Position;
+            }
+
+            // TODO: remove duplicate vertices
+
+            if (vertices.Length < 3) return;
+
+            Span<int> indices = stackalloc int[(vertices.Length - 2) * 3];
+
+            _Mesh._Triangulator.Triangulate(indices, vertices);
+
+            for (int i = 0; i < indices.Length; i += 3)
+            {
+                var a = points[indices[i + 0]];
+                var b = points[indices[i + 1]];
+                var c = points[indices[i + 2]];
+
+                AddTriangle(a, b, c);
             }
             }
         }
         }
 
 
-        public void AddPrimitive(PrimitiveBuilder<TMaterial, TvP, TvM, TvS> primitive, Matrix4x4 transform)
+        internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvP, TvM, TvS> primitive, Func<Vertex<TvP, TvM, TvS>, Vertex<TvP, TvM, TvS>> vertexTransform)
         {
         {
             if (primitive == null) throw new ArgumentNullException(nameof(primitive));
             if (primitive == null) throw new ArgumentNullException(nameof(primitive));
 
 
-            foreach (var t in primitive.Triangles)
+            if (_PrimitiveVertexCount == 1)
             {
             {
-                var a = primitive.Vertices[t.Item1];
-                var b = primitive.Vertices[t.Item2];
-                var c = primitive.Vertices[t.Item3];
+                foreach (var p in primitive.Points)
+                {
+                    var a = vertexTransform(primitive.Vertices[p]);
 
 
-                var aa = a.Geometry; aa.Transform(transform); a.Geometry = aa;
-                var bb = b.Geometry; bb.Transform(transform); b.Geometry = bb;
-                var cc = c.Geometry; cc.Transform(transform); c.Geometry = cc;
+                    AddPoint(a);
+                }
 
 
-                AddTriangle(a, b, c);
+                return;
+            }
+
+            if (_PrimitiveVertexCount == 2)
+            {
+                foreach (var l in primitive.Lines)
+                {
+                    var a = vertexTransform(primitive.Vertices[l.Item1]);
+                    var b = vertexTransform(primitive.Vertices[l.Item2]);
+
+                    AddLine(a, b);
+                }
+
+                return;
+            }
+
+            if (_PrimitiveVertexCount == 3)
+            {
+                foreach (var t in primitive.Triangles)
+                {
+                    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);
+                }
+
+                return;
             }
             }
         }
         }
 
 
@@ -212,22 +313,40 @@ namespace SharpGLTF.Geometry
             AddTriangle(aa, bb, cc);
             AddTriangle(aa, bb, cc);
         }
         }
 
 
-        public TvPP GetVertexGeometry<TvPP>(int index)
+        public Vertex<TvPP, TvMM, TvSS> GetVertex<TvPP, TvMM, TvSS>(int index)
             where TvPP : struct, IVertexGeometry
             where TvPP : struct, IVertexGeometry
+            where TvMM : struct, IVertexMaterial
+            where TvSS : struct, IVertexSkinning
         {
         {
-            return _Vertices[index].Geometry.CloneAs<TvPP>();
+            var v = _Vertices[index];
+
+            return new Vertex<TvPP, TvMM, TvSS>(v.Geometry.CloneAs<TvPP>(), v.Material.CloneAs<TvMM>(), v.Skinning.CloneAs<TvSS>());
         }
         }
 
 
-        public TvMM GetVertexMaterial<TvMM>(int index)
-            where TvMM : struct, IVertexMaterial
+        private IEnumerable<int> _GetPointIndices()
+        {
+            if (_PrimitiveVertexCount != 1) return Enumerable.Empty<int>();
+
+            return Enumerable.Range(0, _Vertices.Count);
+        }
+
+        private IEnumerable<(int, int)> _GetLineIndices()
         {
         {
-            return _Vertices[index].Material.CloneAs<TvMM>();
+            if (_PrimitiveVertexCount != 2) return Enumerable.Empty<(int, int)>();
+
+            return Schema2.PrimitiveType.LINES.GetLinesIndices(_Indices.Select(item => (uint)item));
         }
         }
 
 
-        public TvSS GetVertexSkinning<TvSS>(int index)
-            where TvSS : struct, IVertexSkinning
+        private IEnumerable<(int, int, int)> _GetTriangleIndices()
+        {
+            if (_PrimitiveVertexCount != 3) return Enumerable.Empty<(int, int, int)>();
+
+            return Schema2.PrimitiveType.TRIANGLES.GetTrianglesIndices(_Indices.Select(item => (uint)item));
+        }
+
+        public void TransformVertices(Func<Vertex<TvP, TvM, TvS>, Vertex<TvP, TvM, TvS>> transformFunc)
         {
         {
-            return _Vertices[index].Skinning.CloneAs<TvSS>();
+            _Vertices.TransformVertices(transformFunc);
         }
         }
 
 
         #endregion
         #endregion

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

@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace SharpGLTF.Geometry
+{
+    public interface IPolygonTriangulator
+    {
+        void Triangulate(Span<int> outIndices, ReadOnlySpan<Vector3> inVertices);
+    }
+
+    /// <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)
+        {
+            int idx = 0;
+
+            for (int i = 2; i < inVertices.Length; ++i)
+            {
+                outIndices[idx++] = 0;
+                outIndices[idx++] = i - 1;
+                outIndices[idx++] = i;
+            }
+        }
+    }
+}

+ 3 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexEmpty.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;
 
 
@@ -27,5 +28,7 @@ namespace SharpGLTF.Geometry.VertexTypes
         void IVertexSkinning.SetJoint(int index, int joint, float weight) { }
         void IVertexSkinning.SetJoint(int index, int joint, float weight) { }
 
 
         JointWeightPair IVertexSkinning.GetJoint(int index) { throw new NotSupportedException(); }
         JointWeightPair IVertexSkinning.GetJoint(int index) { throw new NotSupportedException(); }
+
+        public IEnumerable<JointWeightPair> Joints => Enumerable.Empty<JointWeightPair>();
     }
     }
 }
 }

+ 19 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexSkinning.cs

@@ -77,6 +77,15 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
             return ww / w;
             return ww / w;
         }
         }
+
+        public static IEnumerable<JointWeightPair> GetJoints(IVertexSkinning vs)
+        {
+            for (int i = 0; i < vs.MaxJoints; ++i)
+            {
+                var jw = vs.GetJoint(i);
+                if (jw.Weight != 0) yield return jw;
+            }
+        }
     }
     }
 
 
     public interface IVertexSkinning
     public interface IVertexSkinning
@@ -93,6 +102,8 @@ namespace SharpGLTF.Geometry.VertexTypes
         JointWeightPair GetJoint(int index);
         JointWeightPair GetJoint(int index);
 
 
         void SetJoint(int index, int joint, float weight);
         void SetJoint(int index, int joint, float weight);
+
+        IEnumerable<JointWeightPair> Joints { get; }
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -195,6 +206,8 @@ namespace SharpGLTF.Geometry.VertexTypes
             Weights = new Vector4(pairs[0].Weight, pairs[1].Weight, pairs[2].Weight, pairs[3].Weight);
             Weights = new Vector4(pairs[0].Weight, pairs[1].Weight, pairs[2].Weight, pairs[3].Weight);
         }
         }
 
 
+        IEnumerable<JointWeightPair> IVertexSkinning.Joints => JointWeightPair.GetJoints(this);
+
         #endregion
         #endregion
     }
     }
 
 
@@ -298,6 +311,8 @@ namespace SharpGLTF.Geometry.VertexTypes
             Weights = new Vector4(pairs[0].Weight, pairs[1].Weight, pairs[2].Weight, pairs[3].Weight);
             Weights = new Vector4(pairs[0].Weight, pairs[1].Weight, pairs[2].Weight, pairs[3].Weight);
         }
         }
 
 
+        IEnumerable<JointWeightPair> IVertexSkinning.Joints => JointWeightPair.GetJoints(this);
+
         #endregion
         #endregion
     }
     }
 
 
@@ -390,6 +405,8 @@ namespace SharpGLTF.Geometry.VertexTypes
             }
             }
         }
         }
 
 
+        IEnumerable<JointWeightPair> IVertexSkinning.Joints => JointWeightPair.GetJoints(this);
+
         #endregion
         #endregion
     }
     }
 
 
@@ -482,6 +499,8 @@ namespace SharpGLTF.Geometry.VertexTypes
             }
             }
         }
         }
 
 
+        IEnumerable<JointWeightPair> IVertexSkinning.Joints => JointWeightPair.GetJoints(this);
+
         #endregion
         #endregion
     }
     }
 }
 }

+ 2 - 4
src/SharpGLTF.Toolkit/IO/WavefrontWriter.cs

@@ -5,16 +5,14 @@ using System.Linq;
 using System.Numerics;
 using System.Numerics;
 using System.Text;
 using System.Text;
 
 
-using static System.FormattableString;
-
 using SharpGLTF.Schema2;
 using SharpGLTF.Schema2;
 
 
+using static System.FormattableString;
+
 namespace SharpGLTF.IO
 namespace SharpGLTF.IO
 {
 {
     using BYTES = ArraySegment<Byte>;
     using BYTES = ArraySegment<Byte>;
-
     using VERTEX = ValueTuple<Geometry.VertexTypes.VertexPositionNormal, Geometry.VertexTypes.VertexTexture1, Geometry.VertexTypes.VertexEmpty>;
     using VERTEX = ValueTuple<Geometry.VertexTypes.VertexPositionNormal, Geometry.VertexTypes.VertexTexture1, Geometry.VertexTypes.VertexEmpty>;
-
     using VGEOMETRY = Geometry.VertexTypes.VertexPositionNormal;
     using VGEOMETRY = Geometry.VertexTypes.VertexPositionNormal;
     using VMATERIAL = Geometry.VertexTypes.VertexTexture1;
     using VMATERIAL = Geometry.VertexTypes.VertexTexture1;
     using VSKINNING = Geometry.VertexTypes.VertexEmpty;
     using VSKINNING = Geometry.VertexTypes.VertexEmpty;

+ 0 - 1
src/SharpGLTF.Toolkit/Materials/TextureBuilder.cs

@@ -185,6 +185,5 @@ namespace SharpGLTF.Materials
                 return false;
                 return false;
             }
             }
         }
         }
-
     }
     }
 }
 }

+ 55 - 0
tests/SharpGLTF.Tests/Schema2/Authoring/MeshBuilderCreationTests.cs

@@ -255,6 +255,61 @@ namespace SharpGLTF.Schema2.Authoring
             model.AttachToCurrentTest("terrain.glb");
             model.AttachToCurrentTest("terrain.glb");
         }
         }
 
 
+        [Test(Description = "Creates a scene with 1 million points cloud.")]        
+        public void CreateSceneWithPointCloud()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLink();
+
+            var material = new MaterialBuilder("material1").WithUnlitShader();            
+
+            var mesh = new MeshBuilder<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexColor1>("points");
+
+            // create a point cloud primitive
+            var pointCloud = mesh.UsePrimitive(material, 1);
+
+            var rnd = new Random(178);
+            for (int i = 0; i < 1000000; ++i)
+            {
+                var x = (float)(rnd.NextDouble() * 2 - 1);
+                var y = (float)(rnd.NextDouble() * 2 - 1);
+                var z = (float)(rnd.NextDouble() * 2 - 1);
+
+                var opacity = Math.Max(Math.Max(Math.Abs(x), Math.Abs(y)), Math.Abs(z));
+
+                opacity = opacity * opacity * opacity * opacity;
+
+                var r = (float)rnd.NextDouble() * opacity;
+                var g = (float)rnd.NextDouble() * opacity;
+                var b = (float)rnd.NextDouble() * opacity;
+
+                x *= 50;
+                y *= 50;
+                z *= 50;
+
+                pointCloud.AddPoint((new Vector3(x, y + 60, z), new Vector4(r, g, b, 1)));
+            }
+
+            // adds 4 lines as the base of the points
+            mesh.UsePrimitive(material, 2).AddLine((new Vector3(-50, 0, -50), Vector4.One), (new Vector3(+50, 0, -50), Vector4.UnitW));
+            mesh.UsePrimitive(material, 2).AddLine((new Vector3(+50, 0, -50), Vector4.One), (new Vector3(+50, 0, +50), Vector4.UnitW));
+            mesh.UsePrimitive(material, 2).AddLine((new Vector3(+50, 0, +50), Vector4.One), (new Vector3(-50, 0, +50), Vector4.UnitW));
+            mesh.UsePrimitive(material, 2).AddLine((new Vector3(-50, 0, +50), Vector4.One), (new Vector3(-50, 0, -50), Vector4.UnitW));
+
+            // create a new gltf model
+            var model = ModelRoot.CreateModel();
+
+            // add all meshes (just one in this case) to the model
+            model.CreateMeshes(mesh);
+
+            // create a scene, a node, and assign the first mesh (the terrain)
+            model.UseScene("Default")
+                .CreateNode().WithMesh(model.LogicalMeshes[0]);
+
+            // save the model as GLB
+            model.AttachToCurrentTest("PointCloud.glb");
+        }
+
         [Test]
         [Test]
         public void CreateSceneWithRandomCubes()
         public void CreateSceneWithRandomCubes()
         {
         {