Exemplo n.º 1
0
        /// <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);
            }
        }
Exemplo n.º 2
0
        /// <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);
        }
Exemplo n.º 3
0
        /// <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);
        }