/// <summary> /// Adds a new face to the kernel /// </summary> /// <param name="positions"></param> public bool AddFace(IEnumerable <Vec3d> positions) { // No bad faces please :( if (positions.Count() < 3) { return(false); } // convert positions to vertices inside the mesh var vertices = VertexQuery.GetVerticesForPositions(_vertices, positions); // if the positions had stacked vertices, we might not be able to continue if (vertices.Count() < 3) { // iterate over position vertices foreach (var vertex in vertices) { // if the vertex was old, it will have an outgoing if (vertex.Outgoing != null) { continue; } // if it was new we need to remove it to restore state Remove(vertex); } return(false); } var edges = new List <HalfEdge>(); // iterate over all positions foreach (var vertex in vertices) { // create a new halfedge originating from the current vertex and linked to the new face var halfEdge = new HalfEdge { Origin = vertex, }; // test if the vertex already has an outgoing edge assigned if (vertex.Outgoing is null) { vertex.Outgoing = halfEdge; } edges.Add(halfEdge); } // Insert a face from the edges Insert(edges); // iterate over all edges and insert them foreach (var edge in edges) { // TODO: Maybe we should NOT skip linking checks here _halfEdges.Add(edge); } // test if first face if (this.FaceCount == 1) { var outerRing = new EdgeIterator( _halfEdges[0]) .Select(h => h.Pair) .Reverse() .ToList(); // establish circular link between outside half-edges EdgeLinker.LinkOrderedEdgeCollection(outerRing); } // TODO: Should be method of edgelinker instead // Make sure edge pairs either have a starting face or the ghost face // Iterators are lazy, calling `Count()` will execute the select body _ = edges.Where(e => e.Pair.Face == null).Select(e => e.Pair.Face = Kernel.Outside).Count(); return(true); // TODO: Make sure outer halfEdges are linked,too // TODO: Right now only inner halfEdges are linked circularly // TODO: This leads to crashes on faces with too many naked edges when creating vertexringiterators // TODO: For this we will need a 'ghost' face that encompasses all the space outside of the mesh faces // TODO: CLosed meshes will have an empty ghost face }