/// <summary> /// Updates surface by adding face /// </summary> /// <param name="face">The face.</param> public virtual void UpdateWith(PolygonalFace face) { Area += face.Area; foreach (var v in face.Vertices.Where(v => !Vertices.Contains(v))) { Vertices.Add(v); } foreach (var e in face.Edges.Where(e => !InnerEdges.Contains(e))) { if (_outerEdges.Contains(e)) { _outerEdges.Remove(e); _innerEdges.Add(e); } else { _outerEdges.Add(e); } } Faces.Add(face); }
/// <summary> /// Updates the with. /// </summary> /// <param name="face">The face.</param> public bool BuildIfCylinderIsHole(bool isPositive) { if (isPositive) { throw new Exception("BuildIfCylinderIsHole assumes that the faces have already been collected, " + "such that the cylinder is negative"); } IsPositive = false; //To truly be a hole, there should be two loops of vertices that form circles on either ends of the faces. //These are easy to capture because all the edges between them should be shared by two of the faces //Start by collecting the edges at either end. Each edge belongs to only two faces, so any edge that only //comes up once, must be at the edge of the cylinder (assuming it is a cylinder). var edges = new HashSet <Edge>(); foreach (var face in Faces) { foreach (var edge in face.Edges) { if (edges.Contains(edge)) { edges.Remove(edge); } else { edges.Add(edge); } } } //Now we can loop through the edges to form two loops if (edges.Count < 5) { return(false); //5 is the minimum number of vertices to look remotely circular (8 is more likely) } var(allLoopsClosed, edgeLoops, loops) = GetLoops(edges, true); if (loops.Count != 2) { return(false); //There must be two and only two loops. } Loop1 = new HashSet <Vertex>(loops[0]); Loop2 = new HashSet <Vertex>(loops[1]); EdgeLoop1 = edgeLoops[0]; EdgeLoop2 = edgeLoops[1]; //Next, we need to get the central axis. //The ends of the cylinder could be any shape (flat, curved, angled) and the shapes //on each end do not need to match. This rules out using the vertex loops to form //a plane (most accurate) and creating a plane from edge midpoints (next most accurate). //The next most accurate thing is to use the edge vectors to set the axis. //This is more precise than taking a bunch of cross products with the faces. //And it is more universal than creating a plane from the loops, since it works //for holes that enter and exit at an angle. var throughEdgeVectors = new Dictionary <Vertex, double[]>(); var dotFromSharpestEdgesConnectedToVertex = new Dictionary <Vertex, double>(); foreach (var edge in InnerEdges) { //Skip those edges that are on "flat" surfaces var dot = edge.OwnedFace.Normal.dotProduct(edge.OtherFace.Normal); if (dot.IsPracticallySame(1.0, Constants.ErrorForFaceInSurface)) { continue; } //This uses a for loop to remove duplicate code, to decide which vertex to check with which loop for (var i = 0; i < 2; i++) { var A = i == 0 ? edge.To : edge.From; var B = i == 0 ? edge.From : edge.To; var direction = edge.Vector.normalize(); //Positive if B is further along var previousDistance = direction.dotProduct(B.Position); var sign = Math.Sign(direction.dotProduct(B.Position) - direction.dotProduct(A.Position)); if (Loop1.Contains(A)) { bool reachedEnd = Loop2.Contains(B); if (!reachedEnd) { //Check if this edge needs to "extended" to reach the end of the cylinder var previousEdge = edge; var previousVertex = B; while (!reachedEnd) { var maxDot = 0.0; Edge extensionEdge = null; foreach (var otherEdge in previousVertex.Edges.Where(e => e != previousEdge)) { //This other edge must be contained in the InnerEdges and along the same direction if (!InnerEdges.Contains(otherEdge)) { continue; } var edgeDot = Math.Abs(otherEdge.Vector.normalize().dotProduct(previousEdge.Vector.normalize())); if (!edgeDot.IsPracticallySame(1.0, Constants.ErrorForFaceInSurface)) { continue; } var distance = sign * (direction.dotProduct(otherEdge.OtherVertex(previousVertex).Position) - previousDistance); if (!distance.IsGreaterThanNonNegligible()) { continue; //This vertex is not any further along } //Choose the edge that is most along the previous edge if (edgeDot > maxDot) { maxDot = edgeDot; extensionEdge = otherEdge; } } if (extensionEdge == null) { break; //go to the next edge } if (Loop2.Contains(extensionEdge.OtherVertex(previousVertex))) { reachedEnd = true; B = extensionEdge.OtherVertex(previousVertex); } else { previousVertex = extensionEdge.OtherVertex(previousVertex); previousEdge = extensionEdge; } } } //If there was a vertex from the edge or edges in the second loop. if (reachedEnd) { if (!dotFromSharpestEdgesConnectedToVertex.ContainsKey(A)) { throughEdgeVectors.Add(A, B.Position.subtract(A.Position)); dotFromSharpestEdgesConnectedToVertex.Add(A, edge.InternalAngle); } else if (dot < dotFromSharpestEdgesConnectedToVertex[A]) { throughEdgeVectors[A] = B.Position.subtract(A.Position); dotFromSharpestEdgesConnectedToVertex[A] = dot; } break; //Go to the next edge } } } } if (throughEdgeVectors.Count < 3) { return(false); } //Estimate the axis from the sum of the through edge vectors //The axis points from loop 1 to loop 2, since we always start the edge vector from Loop1 var edgeVectors = new List <double[]>(throughEdgeVectors.Values); var numEdges = edgeVectors.Count; var axis = new double[] { 0.0, 0.0, 0.0 }; foreach (var edgeVector in edgeVectors) { axis = axis.add(edgeVector); } Axis = axis.normalize(); /* to adjust the Axis, we will average the cross products of the new face with all the old faces */ //Since we will be taking cross products, we need to be sure not to have faces along the same normal var faces = MiscFunctions.FacesWithDistinctNormals(Faces.ToList()); var n = faces.Count; //Check if the loops are circular along the axis var path1 = MiscFunctions.Get2DProjectionPointsAsLight(Loop1, Axis, out var backTransform); var poly = new PolygonLight(path1); if (!PolygonOperations.IsCircular(new Polygon(poly), out var centerCircle, Constants.MediumConfidence)) { return(false); } var path2 = MiscFunctions.Get2DProjectionPointsAsLight(Loop2, Axis, out var backTransform2); var poly2 = new PolygonLight(path2); if (!PolygonOperations.IsCircular(new Polygon(poly2), out var centerCircle2, Constants.MediumConfidence)) { return(false); } Radius = (centerCircle.Radius + centerCircle2.Radius) / 2; //Average //Set the Anchor/Center point var center = centerCircle.Center.Add(centerCircle2.Center).divide(2); Anchor = MiscFunctions.Convert2DVectorTo3DVector(center, backTransform); return(true); }