/// <summary> /// Generates a convex hull mesh for a set of points. Also removes all faces that lie on the ExoMesh plates. /// </summary> /// <param name="nodeIndex">Index of node being hulled.</param> /// <param name="sides">Number of sides per strut.</param> /// <param name="tol">The tolerance (RhinoDoc.ActiveDoc.ModelAbsoluteTolerance is a good bet).</param> /// <param name="cleanPlates">If true, the plate faces will be removed from the hull, so that the sleeves can be directly attached.</param> /// <remarks> /// If a plate point is coplanar with another plate, the hull may be impossible to clean. /// This is because the hulling process may remove the coplanar point and create a new face. /// </remarks> public Mesh MakeConvexHull(int nodeIndex, int sides, double tol, bool cleanPlates) { Mesh hullMesh = new Mesh(); ExoHull node = this.Hulls[nodeIndex]; double radius = node.AvgRadius; double planeTolerance = tol * radius / 25; // Collect all hull points (i.e. all plate points at the node) List <Point3d> pts = new List <Point3d>(); foreach (int pIndex in node.PlateIndices) { pts.AddRange(this.Plates[pIndex].Vtc); } // 1. Create initial tetrahedron. // Form triangle from 3 first points (lie on same plate, thus, same plane) hullMesh.Vertices.Add(pts[0]); hullMesh.Vertices.Add(pts[1]); hullMesh.Vertices.Add(pts[2]); Plane planeStart = new Plane(pts[0], pts[1], pts[2]); // Form tetrahedron with a 4th point which does not lie on the same plane int nextIndex = sides + 1; while (Math.Abs(planeStart.DistanceTo(pts[nextIndex])) < planeTolerance) { nextIndex++; } hullMesh.Vertices.Add(pts[nextIndex]); // Stitch faces of tetrahedron hullMesh.Faces.AddFace(0, 2, 1); hullMesh.Faces.AddFace(0, 3, 2); hullMesh.Faces.AddFace(0, 1, 3); hullMesh.Faces.AddFace(1, 2, 3); // 2. Begin the incremental hulling process // Remove points already checked pts.RemoveAt(nextIndex); pts.RemoveRange(0, 3); // Loop through the remaining points for (int i = 0; i < pts.Count; i++) { MeshTools.NormaliseMesh(ref hullMesh); // Find visible faces List <int> seenFaces = new List <int>(); for (int faceIndex = 0; faceIndex < hullMesh.Faces.Count; faceIndex++) { Vector3d testVect = pts[i] - hullMesh.Faces.GetFaceCenter(faceIndex); double angle = Vector3d.VectorAngle(hullMesh.FaceNormals[faceIndex], testVect); Plane planeTest = new Plane(hullMesh.Faces.GetFaceCenter(faceIndex), hullMesh.FaceNormals[faceIndex]); if (angle < Math.PI * 0.5 || Math.Abs(planeTest.DistanceTo(pts[i])) < planeTolerance) { seenFaces.Add(faceIndex); } } // Remove visible faces hullMesh.Faces.DeleteFaces(seenFaces); // Add current point hullMesh.Vertices.Add(pts[i]); List <MeshFace> addFaces = new List <MeshFace>(); // Close open hull based on new vertex for (int edgeIndex = 0; edgeIndex < hullMesh.TopologyEdges.Count; edgeIndex++) { if (!hullMesh.TopologyEdges.IsSwappableEdge(edgeIndex)) { IndexPair V = hullMesh.TopologyEdges.GetTopologyVertices(edgeIndex); int I1 = hullMesh.TopologyVertices.MeshVertexIndices(V.I)[0]; int I2 = hullMesh.TopologyVertices.MeshVertexIndices(V.J)[0]; addFaces.Add(new MeshFace(I1, I2, hullMesh.Vertices.Count - 1)); } } hullMesh.Faces.AddFaces(addFaces); } MeshTools.NormaliseMesh(ref hullMesh); // 3. If requested, delete the hull faces that lie on the plates (so sleeves can connect directly to the hulls) if (cleanPlates) { List <int> deleteFaces = new List <int>(); foreach (int plateIndx in node.PlateIndices) { List <Point3f> plateVtc = MeshTools.Point3dToPoint3f(this.Plates[plateIndx].Vtc); // Recall that strut plates have 'sides+1' vertices. // If the plate has only 'sides' vertices, it is an extra plate (for acute nodes), so we should keep it if (plateVtc.Count < sides + 1) { continue; } for (int j = 0; j < hullMesh.Faces.Count; j++) { Point3f ptA, ptB, ptC, ptD; hullMesh.Faces.GetFaceVertices(j, out ptA, out ptB, out ptC, out ptD); // Check if the mesh face has vertices that belong to a single plate, if so we need to remove the face int matches = 0; foreach (Point3f testPt in plateVtc) { if (testPt.EpsilonEquals(ptA, (float)tol) || testPt.EpsilonEquals(ptB, (float)tol) || testPt.EpsilonEquals(ptC, (float)tol)) { matches++; } } // If all three face vertices are plate vertices, we should remove the face if (matches == 3) { deleteFaces.Add(j); } } } // Remove the faces. Reverse the list so that it is in decreasing order. deleteFaces.Reverse(); foreach (int faceIndx in deleteFaces) { hullMesh.Faces.RemoveAt(faceIndx); } } return(hullMesh); }