/// <summary> /// Try to merge the two given faces. /// For this to work, the edges belonging to both faces must be able /// to form a closed loop. /// The first face will be grown and the second face removed on success /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <returns>True on success, False on failure</returns> internal static bool MergeFaces(this Kernel kernel, Face a, Face b) { // try to find the shared edge if (!ConnectivityQuery.TryFindSharedEdge(a, b, out var shared)) { return(false); } // get edge iterator for face a, starting at shared edge var firstEdges = new EdgeIterator(shared).ToArray(); // get edge iterator for face b starting at shared edge var otherEdges = new EdgeIterator(shared.Pair).ToArray(); // remove the shared edge from the kernel kernel.Remove(shared); // establish circular link between both face halfes EdgeLinker.LinkOrderedEdgeCollection( firstEdges .Skip(1) .Concat(otherEdges.Skip(1)) .ToList()); // update face references otherEdges.Skip(1).Select(e => e.Face = a); // remove face b b.Start = null; kernel.Remove(b); return(true); }
/// <summary> /// Insert a new face from a counter-clockwise ordered list of halfedges /// </summary> /// <param name="edges"></param> /// <returns></returns> public bool Insert(IReadOnlyList <HalfEdge> edges) { if (edges.Count < 3) { return(false); } // TODO: Check if ccw - ordered // establish circular link between edges EdgeLinker.LinkOrderedEdgeCollection(edges); // assign face starting edge var face = new Face { Start = edges[0] }; // assign face to edges foreach (var edge in edges) { edge.Face = face; } // insert the new face return(Insert(face)); }
public override bool Insert(HalfEdge edge) { // try to link up the edge if (!EdgeLinker.TryLinkEdge(edge)) { return(false); } Add(edge); return(true); }
/// <summary> /// Create a new kernel from a list of positions and a nested list of indices representing faces /// This has no checks, the given input needs to represent a valid mesh to produce a meaningful output /// </summary> /// <param name="positions"></param> /// <param name="faces"></param> /// <returns></returns> internal static Kernel CreateFromPositionsUnchecked(IReadOnlyList <Vec3d> positions, IEnumerable <IEnumerable <int> > faces) { // create new kernel var kernel = new Kernel(); // insert a vertex into the kernel for each position var vertices = (from position in positions select kernel.GetVertexForPosition(position)).ToArray(); var edges = new List <HalfEdge>(); // iterate over number of faces foreach (var face in faces) { // empty list to hold halfedges of new face var faceEdges = new List <HalfEdge>(); // get vertex indices and iterate over them var indices = face; foreach (var vertexIndex in indices) { // create a new halfedge originating from the current vertexindex var edge = new HalfEdge { Origin = vertices[vertexIndex], }; // assure outgoing edge on current vertex if (vertices[vertexIndex].Outgoing is null) { vertices[vertexIndex].Outgoing = edge; } faceEdges.Add(edge); } // insert a new face from the face edges kernel.Insert(faceEdges); edges.AddRange(faceEdges); } // link all edges to their pairs EdgeLinker.LinkEdgePairs(ref edges); // iterate over all edges and add to kernel foreach (var edge in edges) { kernel.Insert(edge); } // return the kernel return(kernel); }
internal static void CollapseEdge(this Kernel kernel, HalfEdge edge) { if (EdgeLinker.IsDummyPairEdge(edge)) { edge = edge.Pair; } // if the edge is naked, we don't have to change the other face if (EdgeLinker.IsDummyPairEdge(edge.Pair)) { CollapseNakedEdge(edge, kernel); return; } CollapseEdgeBetweenFaces(edge, kernel); }
/// <summary> /// Removes a face from the kernel, by cutting a hole in the mesh /// All Edges belonging to this face will become unlinked, naked edges /// </summary> /// <param name="face"></param> /// <returns></returns> public override bool Remove(Face face) { if (face.Start is null) { return(_elements.Remove(face)); } // iterate over face edges foreach (var edge in new EdgeIterator(face.Start)) { // unlink the current edge EdgeLinker.UnlinkEdge(edge); } // remove face from inner collection return(_elements.Remove(face)); }
/// <summary> /// Removes all unused Edges from the kernel /// Unused edges are edge-pairs, where both pairs are naked /// </summary> /// <param name="kernel"></param> internal static void RemoveUnusedEdges(this Kernel kernel) { foreach (var edge in kernel.Edges) { if (!EdgeLinker.IsDummyPairEdge(edge)) { continue; } if (!EdgeLinker.IsDummyPairEdge(edge.Pair)) { continue; } kernel.Remove(edge); } }
/// <summary> /// Removes a <see cref="HalfEdge"/>. This also removes its pair, /// as HalfEdges are not allowed to be single ;) /// </summary> /// <param name="edge"></param> /// <returns></returns> public override bool Remove(HalfEdge edge) { // handle active references to edge if (!EdgeLinker.IsDummyPairEdge(edge)) { RemoveReferences(edge); } // handle active references to its pair if (!EdgeLinker.IsDummyPairEdge(edge.Pair)) { RemoveReferences(edge.Pair); } // unlink the edges EdgeLinker.UnlinkEdge(edge); EdgeLinker.UnlinkEdge(edge.Pair); // remove edges from inner collection _elements.Remove(edge); _elements.Remove(edge.Pair); return(true); }
internal static bool TrySplitEdge(this Kernel kernel, HalfEdge edge, double t, out (HalfEdge, HalfEdge) parts) { parts = (null, null); // TODO: Error checks if (EdgeLinker.IsDummyPairEdge(edge)) { edge = edge.Pair; } // test valid param if (0 > t || t > 1) { return(false); } // calculate vec at param var vec = edge.Origin.Position.VecAtParameter(edge.Target.Position, t); // convert to a vertex var vertex = kernel.GetVertexForPosition(vec); // store edge pair, and conditionals var pair = edge.Pair; var isDummy = EdgeLinker.IsDummyPairEdge(pair); var isStart = edge.Face.Start == edge; // create new edges var firstHalf = new HalfEdge { Face = edge.Face, Previous = edge.Previous, Origin = edge.Origin }; var secondHalf = new HalfEdge { Face = edge.Face, Next = edge.Next, Origin = vertex }; var pairFirstHalf = new HalfEdge { Face = pair.Face, Previous = pair.Previous, Origin = pair.Origin, Pair = secondHalf, }; var pairSecondHalf = new HalfEdge { Face = pair.Face, Next = pair.Next, Origin = vertex, Pair = firstHalf }; // link halves firstHalf.Next = secondHalf; pairFirstHalf.Next = pairSecondHalf; // remove original edges kernel.Remove(edge); // add new halves if (!isDummy) { kernel.Insert(firstHalf); kernel.Insert(secondHalf); kernel.Insert(pairFirstHalf); kernel.Insert(pairSecondHalf); } else { firstHalf.Pair = null; secondHalf.Pair = null; kernel.Insert(firstHalf); kernel.Insert(secondHalf); } // set start to first half if (isStart) { firstHalf.Face.Start = firstHalf; } parts = (firstHalf, secondHalf); return(true); }
internal static bool TrySplitFace(this Kernel kernel, HalfEdge start, HalfEdge end, out (Face, Face) parts) { parts = (null, null); // TODO: Error checks // get edges for face of both halfedges var edges = new EdgeIterator(start).ToArray(); // can't split triangles if (edges.Length <= 3) { return(false); } // get the index of the end edge inside the edges array var endIndex = Array.IndexOf(edges, end); // create the new ending halfedge for the start half var newEnd = new HalfEdge { Face = start.Face, Next = start, Previous = edges[endIndex - 1], Origin = end.Origin }; // create the new Face for the end half var newFace = new Face { Start = end }; // create a new starting edge for the end half var newStart = new HalfEdge { Face = newFace, Next = end, Previous = edges.Last(), Origin = start.Origin, Pair = newEnd }; // pair up the new end newEnd.Pair = newStart; // establish circular link for first face var firstFaceEdges = edges.Take(endIndex).ToList(); firstFaceEdges.Add(newEnd); EdgeLinker.LinkOrderedEdgeCollection(firstFaceEdges); // hacky re-assignment newEnd.Face.Start = newEnd.Next; // establish circular link for second face var secondFaceEdges = edges.Skip(endIndex).ToList(); secondFaceEdges.Insert(0, newStart); EdgeLinker.LinkOrderedEdgeCollection(secondFaceEdges); secondFaceEdges.ForEach(e => e.Face = newFace); // Add new edges to kernel kernel.Add(newEnd); kernel.Add(newStart); // add new face kernel.Insert(newFace); // prepare output parts = (start.Face, newFace); return(true); }
/// <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 }
internal static void Kis(this Kernel kernel) { var initialFaceCount = kernel.FaceCount; for (int i = 0; i < initialFaceCount; i++) { var face = kernel.Faces[i]; // get face center // TODO: We could move the center vertex by face normal direction to build a n - pyramid var center = face.GetFaceCenter(); // get vertex for center var centerVertex = kernel.GetVertexForPosition(center); var newCenter = true; // iterate over face edges foreach (var edge in new EdgeIterator(face.Start)) { // new face that will be created var newFace = new Face { Start = edge }; // link up current edge edge.Face = newFace; // create an edge from center to origin of current edge if (!kernel.TryGetHalfEdgeBetweenVertices(centerVertex, edge.Origin, out var incoming)) { incoming = new HalfEdge { Face = newFace, Origin = centerVertex }; } // create an edge from current edge target to center if (!kernel.TryGetHalfEdgeBetweenVertices(edge.Target, centerVertex, out var outgoing)) { outgoing = new HalfEdge { Face = newFace, Origin = edge.Target, }; } // link up center vertex on first iteration if (newCenter) { centerVertex.Outgoing = incoming; newCenter = false; } // link up edges EdgeLinker.LinkOrderedEdgeCollection(new[] { incoming, edge, outgoing }); // add new edges to kernel kernel.Add(incoming); kernel.Add(outgoing); // insert new face into kernel kernel.Insert(newFace); } // unlink face face.Start = null; } for (int i = initialFaceCount - 1; i >= 0; i--) { kernel.Remove(kernel.Faces[i]); } }