public static List <Polyline> DelaunayTriangulation(List <Point> points, Plane plane = null, double tolerance = Tolerance.Distance) { //Preform check all inputs that triangulation can be done if (points == null || points.Count < 3) { Base.Compute.RecordError("Insufficient points for triangulation. Please provide at least 3 Points."); return(new List <Polyline>()); } if (points.IsCollinear(tolerance)) { Base.Compute.RecordError("Points are colinear and can not be triangulated."); return(new List <Polyline>()); } //Basic edge case, simply create a triangle if (points.Count == 3) { Polyline pLine = new Polyline { ControlPoints = points }; pLine.ControlPoints.Add(points[0]); return(new List <Polyline> { pLine }); } //Try fit plane if no is provided if (plane == null) { plane = points.FitPlane(tolerance); } if (plane == null) { Engine.Base.Compute.RecordError("Could not fit a plane through the Points and no plane was provided."); return(new List <Polyline>()); } //Check all points within distance of the plane if (points.Any(x => x.Distance(plane) > tolerance)) { BH.Engine.Base.Compute.RecordError("Can only handle coplanar points. Please make sure all your points lie in the same plane."); return(new List <Polyline>()); } //Calculate the local coordinates Vector localX = points[1] - points[0]; Vector localY = plane.Normal.CrossProduct(localX); Point min = points.Min(); Point refPt = new Point { X = min.X, Y = min.Y }; Cartesian localSystem = Create.CartesianCoordinateSystem(min, localX, localY); Cartesian globalSystem = Create.CartesianCoordinateSystem(refPt, Vector.XAxis, Vector.YAxis); //Transform to xy-plane List <Point> xyPoints = points.Select(x => x.Orient(localSystem, globalSystem)).ToList(); //Convert to TringleNet vertecies List <TriangleNet.Geometry.Vertex> vertecies = xyPoints.Select(p => new TriangleNet.Geometry.Vertex(p.X, p.Y)).ToList(); // Triangulate TriangleNet.Meshing.ITriangulator triangulator = new TriangleNet.Meshing.Algorithm.Dwyer(); TriangleNet.Configuration config = new TriangleNet.Configuration(); TriangleNet.Mesh mesh = (TriangleNet.Mesh)triangulator.Triangulate(vertecies, config); // Convert triangulations back to BHoM geometry List <Polyline> translatedPolylines = new List <Polyline>(); foreach (var face in mesh.Triangles) { // List points defining the triangle List <Point> pts = new List <Point>(); pts.Add(BH.Engine.Geometry.Create.Point(face.GetVertex(0).X, face.GetVertex(0).Y)); pts.Add(BH.Engine.Geometry.Create.Point(face.GetVertex(1).X, face.GetVertex(1).Y)); pts.Add(BH.Engine.Geometry.Create.Point(face.GetVertex(2).X, face.GetVertex(2).Y)); pts.Add(pts.First()); translatedPolylines.Add(BH.Engine.Geometry.Create.Polyline(pts)); } return(translatedPolylines.Select(x => x.Orient(globalSystem, localSystem)).ToList()); }
public static List <Polyline> VoronoiRegions(List <Point> points, Plane plane = null, double boundarySize = -1, double tolerance = Tolerance.Distance) { List <Point> uniquePoints = points.CullDuplicates(tolerance); if (points.Count != uniquePoints.Count) { Base.Compute.RecordError("Some points are overlapping with others. Duplicates need to be culled out to create voronoi regions."); return(new List <Polyline>()); } //Preform check all inputs that triangulation can be done if (points == null || points.Count < 2) { Base.Compute.RecordError("Insuffient points for generating the diagram. Please provide at least 2 Points."); return(new List <Polyline>()); } //Special case for colinear points. No need to triangulate if (points.IsCollinear(tolerance)) { return(ColinearVoronoiRegions(points, plane, boundarySize, tolerance)); } //Try fit plane if no is provided if (plane == null) { plane = points.FitPlane(tolerance); } if (plane == null) { Engine.Base.Compute.RecordError("Could not fit a plane through the Points and no plane was provided."); return(new List <Polyline>()); } //Check all points within distance of the plane if (points.Any(x => x.Distance(plane) > tolerance)) { BH.Engine.Base.Compute.RecordError("Can only handle coplanar points. Please make sure all your points lie in the same plane."); return(new List <Polyline>()); } //Calculate the local coordinates Vector localX = points[1] - points[0]; Vector localY = plane.Normal.CrossProduct(localX); Point min = points.Min(); Point refPt = new Point { X = min.X, Y = min.Y }; Cartesian localSystem = Create.CartesianCoordinateSystem(min, localX, localY); Cartesian globalSystem = Create.CartesianCoordinateSystem(refPt, Vector.XAxis, Vector.YAxis); //Transform to xy-plane List <Point> xyPoints = points.Select(x => x.Orient(localSystem, globalSystem)).ToList(); //Add point at all corners. This is to try to handle weirdness at the boundaries BoundingBox bounds = xyPoints.Bounds(); double lengthX; double lengthY; //If boundary size is or equal to 0, calculate based on the bounds if (boundarySize <= 0) { lengthX = (bounds.Max.X - bounds.Min.X); lengthY = (bounds.Max.X - bounds.Min.X); } else { //if positive, use value provided lengthX = boundarySize; lengthY = boundarySize; } double minX = bounds.Min.X - lengthX; double minY = bounds.Min.Y - lengthY; double maxX = bounds.Max.X + lengthX; double maxY = bounds.Max.Y + lengthY; xyPoints.Add(new Point { X = minX, Y = minY }); xyPoints.Add(new Point { X = minX, Y = maxY }); xyPoints.Add(new Point { X = maxX, Y = maxY }); xyPoints.Add(new Point { X = maxX, Y = minY }); //Convert to TringleNet vertecies List <TriangleNet.Geometry.Vertex> vertecies = xyPoints.Select(p => new TriangleNet.Geometry.Vertex(p.X, p.Y)).ToList(); // Triangulate TriangleNet.Meshing.ITriangulator triangulator = new TriangleNet.Meshing.Algorithm.Dwyer(); TriangleNet.Configuration config = new TriangleNet.Configuration(); TriangleNet.Mesh mesh = (TriangleNet.Mesh)triangulator.Triangulate(vertecies, config); TriangleNet.Voronoi.StandardVoronoi voronoi = new TriangleNet.Voronoi.StandardVoronoi(mesh); List <Polyline> translatedPolylines = new List <Polyline>(); double sqTol = tolerance * tolerance; // Convert regions to BHoMGeometry. Skip the last 4 faces as they correspond to the added boundary points. for (int i = 0; i < voronoi.Faces.Count - 4; i++) { var face = voronoi.Faces[i]; try { // List points defining the region List <Point> pts = new List <Point>(); HashSet <int> visitedIds = new HashSet <int>(); int bailOut = 100000; int counter = 0; TriangleNet.Topology.DCEL.HalfEdge halfEdge = face.Edge; int nextId; do { visitedIds.Add(halfEdge.ID); Point pt = new Point { X = halfEdge.Origin.X, Y = halfEdge.Origin.Y }; //Make sure two of the same points are not added. This could maybe be done in a more cleaver way with the half edge if (pts.Count == 0 || pt.SquareDistance(pts.Last()) > sqTol) { pts.Add(pt); } halfEdge = halfEdge.Next; if (halfEdge == null) { break; } nextId = halfEdge.ID; counter++; } while (!visitedIds.Contains(nextId) && counter < bailOut); if (pts.Count > 0 && pts.First().SquareDistance(pts.Last()) > sqTol) { pts.Add(pts.First()); } translatedPolylines.Add(BH.Engine.Geometry.Create.Polyline(pts)); } catch (Exception) { Engine.Base.Compute.RecordWarning("Failed to generate the region for at least one cell. An empty polyline as been added in its place."); translatedPolylines.Add(new Polyline()); } } //Orient back the regions to the local plane return(translatedPolylines.Select(x => x.Orient(globalSystem, localSystem)).ToList()); }