/// <summary> /// Adds a plate to the node if it is a 'sharp' node, to improve convex hull shape. /// </summary> /// <param name="nodeIndex"> Index of the node we want to check/fix. </param> /// <param name="sides"> Number of sides on the sleeve meshes. </param> public void FixSharpNodes(int nodeIndex, int sides) { ExoHull node = this.Hulls[nodeIndex]; // The extra plate is in the direction of the negative sum of all normals // We use the new plate normal to check if the node struts are contained // within a 180deg peripheral (i.e. the node is 'sharp') bool isSharp = true; // Sum of all normals Vector3d extraNormal = new Vector3d(); foreach (int plateIndex in node.PlateIndices) { extraNormal += this.Plates[plateIndex].Normal; } foreach (int plateIndex in node.PlateIndices) { if (Vector3d.VectorAngle(-extraNormal, this.Plates[plateIndex].Normal) < Math.PI / 4) { isSharp = false; } } // If struts form a sharp corner, add an extra plate for a better convex hull shape if (isSharp) { // Plane offset from node slightly Plane plane = new Plane(node.Point3d - extraNormal * node.AvgRadius / node.PlateIndices.Count, -extraNormal); // Compute the vertices List <Point3d> Vtc = MeshTools.CreateKnuckle(plane, sides, node.AvgRadius, 0); // Add new plate and its vertices this.Plates.Add(new ExoPlate(nodeIndex, -extraNormal)); int newPlateIndx = this.Plates.Count - 1; this.Plates[newPlateIndx].Vtc.AddRange(Vtc); node.PlateIndices.Add(newPlateIndx); } }
/// <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); }
/// <summary> /// Computes plate offsets required to avoid mesh overlaps. /// A robust, incremental approach is used. /// </summary> /// <param name="nodeIndex">Index of the node who's plates we are computing offsets for.</param> /// <param name="tol">Tolerance for point locations (RhinoDoc.ActiveDoc.ModelAbsoluteTolerance is a good bet).</param> /// <returns>True if offsets are valid, false if struts are engulfed by their nodes.</returns> public bool ComputeOffsets(int nodeIndex, double tol) { ExoHull node = Hulls[nodeIndex]; List <Curve> paths = new List <Curve>(); List <double> radii = new List <double>(); // Parameter offset (path domains are unitized) List <double> offsets = new List <double>(); // Prepare all struts and initialize offsets foreach (int strutIndex in node.SleeveIndices) { Curve curve = Sleeves[strutIndex].Curve.DuplicateCurve(); // If curve doesn't start at this node, reverse the curve and save end radius if (curve.PointAtEnd.EpsilonEquals(node.Point3d, 100 * tol)) { // Reverse direction of curve to start at this node curve.Reverse(); curve.Domain = new Interval(0, 1); radii.Add(Sleeves[strutIndex].EndRadius); } else { radii.Add(Sleeves[strutIndex].StartRadius); } paths.Add(curve); // We start at an offset equal to the strut radius at the node (this is our minimum offset). // Get the starting parameter at this offset double offsetParam; curve.LengthParameter(radii[radii.Count - 1], out offsetParam); offsets.Add(offsetParam); } // Compute avg radius at the node (mainly used for sharp node extra plate) double sumRadii = 0; foreach (double radius in radii) { sumRadii += radius; } node.AvgRadius = sumRadii / radii.Count; bool convexFound = false; bool[] travel; int iteration = 0; double paramIncrement = offsets[0] / 10; // Iterate until a suitable plate layout is found: // - Sleeves won't overlap // - Hulls won't engulf any of the plate points (all points must lie ON the convex hull) while (!convexFound && iteration < 500) { // Prepare list of circles representing plates List <Circle> circles = new List <Circle>(); for (int i = 0; i < paths.Count; i++) { Plane plane; paths[i].PerpendicularFrameAt(offsets[i], out plane); circles.Add(new Circle(plane, radii[i])); } travel = new bool[paths.Count]; // Loop over all pairs of struts for (int a = 0; a < paths.Count; a++) { for (int b = a + 1; b < paths.Count; b++) { double p1, p2; var intAB = Intersection.PlaneCircle(circles[a].Plane, circles[b], out p1, out p2); var intBA = Intersection.PlaneCircle(circles[b].Plane, circles[a], out p1, out p2); // If a planeA intersects circleB, we need to increment offset on pathA if (intAB == PlaneCircleIntersection.Secant || intAB == PlaneCircleIntersection.Tangent) { travel[a] = true; } // If a planeB intersects circleA, we need to increment offset on pathB if (intBA == PlaneCircleIntersection.Secant || intBA == PlaneCircleIntersection.Tangent) { travel[b] = true; } } } // Increment offset of plates that intersected, if no intersections, we have a suitable convex layout convexFound = true; for (int i = 0; i < paths.Count; i++) { if (travel[i]) { offsets[i] += paramIncrement; convexFound = false; } } iteration++; } // Save the final offsets for (int i = 0; i < paths.Count; i++) { int plateIndex = node.PlateIndices[i]; this.Plates[plateIndex].Offset = 1.05 * offsets[i]; } return(true); }