/// <summary> /// This function gets cuts shape [List(List(Point))] with a give direction and distance. If returnFurtherThanSlice = false, /// it will return the partial shape before the cutting line, otherwise those beyond the cutting line. /// The returned partial shape is properly closed and ordered CCW+, CW-. /// OffsetAtLine allows the use to offset the intersection line a given distance in a direction opposite to the /// returned partial shape (i.e., if returnFurtherThanSlice == true, a positive offsetAtLine value moves the /// intersection points before the line). /// </summary> public static List <List <PointLight> > OnLine(List <List <Point> > shape, double[] direction2D, double distanceAlongDirection, bool returnFurtherThanSlice, out List <Point> intersectionPoints, double offsetAtLine = 0.0, List <Tuple <Point, double> > sortedPoints = null) { var partialShape = new List <List <PointLight> >(); intersectionPoints = new List <Point>(); var shallowPolygonsTrees = ShallowPolygonTree.GetShallowPolygonTrees(shape); foreach (var shallowPolygonTree in shallowPolygonsTrees) //There is usually only one, but do them all { //Set the lines in all the polygons. These are needed for Slice.OnLine() foreach (var polygon in shallowPolygonTree.AllPolygons) { polygon.SetPathLines(); } //Get the sorted points var allPoints = new List <Point>(); foreach (var path in shallowPolygonTree.AllPaths) { allPoints.AddRange(path); } if (sortedPoints == null) { MiscFunctions.SortAlongDirection(direction2D, allPoints, out sortedPoints); } //Get the paths for each partial shape and add them together. The paths should not overlap, given //that they are from non-overlapping shallow polygon trees. var paths = OnLine(shallowPolygonTree, direction2D, distanceAlongDirection, returnFurtherThanSlice, sortedPoints, out var localSortedIntersectionOffsetPoints, offsetAtLine).Select(p => p.Path).ToList(); partialShape.AddRange(paths); intersectionPoints.AddRange(localSortedIntersectionOffsetPoints); } return(partialShape); }
/// <summary> /// This function gets cuts ShallowPoygonTree with a give direction and distance. If returnFurtherThanSlice = false, /// it will return the partial shape [List(List(Point))] before the cutting line, otherwise those beyond the cutting line. /// The returned partial shape is properly closed and ordered CCW+, CW-. /// OffsetAtLine allows the use to offset the intersection line a given distance in a direction opposite to the /// returned partial shape (i.e., if returnFurtherThanSlice == true, a positive offsetAtLine value moves the intersection /// intersection points before the line). /// </summary> /// <param name="polyTree"></param> /// <param name="direction2D"></param> /// <param name="distanceAlongDirection"></param> /// <param name="returnFurtherThanSlice"></param> /// <param name="sortedPoints"></param> /// <param name="sortedIntersectionPoints"></param> /// <param name="offsetAtLine"></param> /// <returns></returns> public static List <PolygonLight> OnLine(ShallowPolygonTree polyTree, double[] direction2D, double distanceAlongDirection, bool returnFurtherThanSlice, IEnumerable <Tuple <Point, double> > sortedPoints, out List <Point> sortedIntersectionPoints, double offsetAtLine = 0.0) { if (direction2D.Length != 2) { throw new Exception("2D direction must have exactly 2 dimensions"); } /* First (1), a line hash is used to find all the lines to the left and the intersection lines. * Second (2), the intersection point for each of the intersection points is found. * Third (3), these intersection points are ordered in the perpendicular direction to the search direction * Fourth (4), a smart slicing algorithm is used to cut the full shape into a partial shape, using * the intersection points and lines.*/ //(1) Find the intersection lines and the lines to the left of the current distance var lineHash = new HashSet <Line>(); var linesToLeft = new HashSet <Line>(); foreach (var pair in sortedPoints) { var distanceAlong = pair.Item2; var point = pair.Item1; //If the search direction is forward, then the partial shape is defined with any lines //prior to the given distance. If reverse, then it is with lines further than the current distance. var furtherThanSlice = distanceAlong > distanceAlongDirection; if (returnFurtherThanSlice == furtherThanSlice) { foreach (var line in point.Lines) { if (lineHash.Contains(line)) { lineHash.Remove(line); linesToLeft.Add(line); } else { lineHash.Add(line); } } } //else break; } if (!linesToLeft.Any()) { throw new Exception("There must be some lines to the left"); } //(2) Create the intersection points var intersectionLines = new HashSet <Line>(); var intersectionLinesByRef = new Dictionary <int, Line>(); var intersectionPointsByRef = new Dictionary <int, Point>(); //Any line that is left in line hash, must be an intersection line. var refIndex = 0; foreach (var line in lineHash) { intersectionLines.Add(line); var intersectionPoint = MiscFunctions.PointOnPlaneFromIntersectingLine(direction2D, distanceAlongDirection, line); line.ReferenceIndex = refIndex; intersectionPoint.ReferenceIndex = refIndex; intersectionLinesByRef.Add(refIndex, line); intersectionPointsByRef.Add(refIndex, intersectionPoint); refIndex++; } if (intersectionLines.Count == 0 || intersectionLines.Count % 2 != 0) { throw new Exception("There must be a non-zero, even number of intersection lines"); } //(3) Sort the intersection points var searchDirectionPerpendicular = new[] { -direction2D[1], direction2D[0] }; MiscFunctions.SortAlongDirection(searchDirectionPerpendicular, intersectionPointsByRef.Values.ToList(), out var sortedIntersectionPointTuples); if (sortedIntersectionPointTuples.Count % 2 != 0) { throw new Exception("There must be an even number of intersection points"); } //Set each points position in this sorted list //The points are paired together, such that sortedIntersectionPoints[0] has a line to //sortedIntersectionPoints[1], [2]<=>[3], [4]<=>[5], and so on. for (var i = 0; i < sortedIntersectionPointTuples.Count; i++) { var intersectionPoint = sortedIntersectionPointTuples[i].Item1; intersectionPoint.IndexInPath = i; } //(4) Build the partial 2D shape sortedIntersectionPoints = new List <Point>(); var partialShape = new List <PolygonLight>(); while (linesToLeft.Any()) { //Note the line index is the same as the line.FromPoint.IndexInPath var endLine = linesToLeft.First(); linesToLeft.Remove(endLine); //Get the corresponding polygon //Use copies of points, since creating a polygon will erase the //point's line references var endPoint = endLine.FromPoint; var currentPolygon = polyTree.AllPolygons[endPoint.PolygonIndex]; var endPolygonIndex = currentPolygon.Index; var endLineIndex = endLine.IndexInPath; var path = new List <PointLight> { endPoint.Light }; var nextLineIndex = currentPolygon.NextLineIndex(endLineIndex); //Since the line index can be duplicated between polygons, we also need to check the polygon index //It is the same line if its index and polygon index are the same. Then we stop. while (nextLineIndex != endLineIndex || currentPolygon.Index != endPolygonIndex) { var currentLineIndex = nextLineIndex; var currentLine = currentPolygon.PathLines[currentLineIndex]; path.Add(currentLine.FromPoint.Light); if (intersectionLines.Contains(currentLine)) { //Add the paired intersection points. //Starting from the nextLine's intersection point, then //going to its pair. From there, we may have switched polygons. var intersectionPoint = intersectionPointsByRef[currentLine.ReferenceIndex]; var intersectionPointIndexInVerticalSort = intersectionPoint.IndexInPath; var pairedIntersectionPoint = intersectionPointIndexInVerticalSort % 2 != 0 ? sortedIntersectionPointTuples[intersectionPointIndexInVerticalSort - 1].Item1 : sortedIntersectionPointTuples[intersectionPointIndexInVerticalSort + 1].Item1; //Add four points to the path, both intersection points and their offsets //(1) the current intersection point. path.Add(intersectionPoint.Light); //(2) the offset point of the current intersection point //Each intersection point corresponds to an intersection offset point. This point //is equal to the current point + the offsetAtLine added along the search direction. var position = returnFurtherThanSlice ? intersectionPoint.Position.subtract(direction2D.multiply(offsetAtLine), 2) : intersectionPoint.Position.add(direction2D.multiply(offsetAtLine), 2); var intersectionOffsetPoint = new Point(position[0], position[1]); path.Add(intersectionOffsetPoint.Light); sortedIntersectionPoints.Add(intersectionOffsetPoint); //(3) the offset point of the next intersection point position = returnFurtherThanSlice ? pairedIntersectionPoint.Position.subtract(direction2D.multiply(offsetAtLine), 2) : pairedIntersectionPoint.Position.add(direction2D.multiply(offsetAtLine), 2); var pairedIntersectionOffsetPoint = new Point(position[0], position[1]); path.Add(pairedIntersectionOffsetPoint.Light); sortedIntersectionPoints.Add(pairedIntersectionOffsetPoint); //(4) the next intersection point. path.Add(pairedIntersectionPoint.Light); //Update the current polygons, since it may have changed, and then update the next line index var nextLine = intersectionLinesByRef[pairedIntersectionPoint.ReferenceIndex]; currentPolygon = polyTree.AllPolygons[nextLine.FromPoint.PolygonIndex]; nextLineIndex = currentPolygon.NextLineIndex(nextLine.IndexInPath); } else { linesToLeft.Remove(currentLine); nextLineIndex = currentPolygon.NextLineIndex(nextLineIndex); } } //Because of the intelligent slicing operation, the paths should be ordered correctly CCW+ or CW- //A negative hole could be an entire path, as long as all its lines where to the left of the sweep. partialShape.Add(new PolygonLight(path)); } return(partialShape); }