/// <summary> /// Populate another polycurve with the portions of this one which lie within /// the specified subDomain /// </summary> /// <param name="subDomain"></param> /// <param name="result"></param> private void PopulateWithSubCurves(Interval subDomain, PolyCurve result) { double segCount = SegmentCount; double segStart = 0; foreach (Curve subCrv in SubCurves) { double subSC = subCrv.SegmentCount; double segEnd = segStart + subSC; Interval crvDom = new Interval(segStart / segCount, segEnd / segCount); if (subDomain.Contains(crvDom)) { result.Add(subCrv.Duplicate()); } else if (subDomain.Overlaps(crvDom)) { Curve subSubCrv = subCrv.Extract(crvDom.ParameterOf(subDomain.Overlap(crvDom))); if (subSubCrv != null) { result.Add(subSubCrv); } } segStart = segEnd; } }
/// <summary> /// Get a closed polycurve containing the edges of this section of the path. /// The edges of the path must have been generated first. /// </summary> /// <returns></returns> public static PolyCurve GetBoundaryCurve <TPath>(this TPath path) where TPath : IWidePath { var result = new PolyCurve(); if (path.StartCapLeft != null) { result.Add(path.StartCapLeft); } if (path.LeftEdge != null) { result.Add(path.LeftEdge, true); } if (path.EndCapLeft != null) { result.Add(path.EndCapLeft, true); } if (path.EndCapRight != null) { result.Add(path.EndCapRight.Reversed(), true); } if (path.RightEdge != null) { result.Add(path.RightEdge.Reversed(), true); } if (path.StartCapRight != null) { result.Add(path.StartCapRight.Reversed(), true); } result.Close(); return(result); }
/// <summary> /// Split this region into two (or more) sub-regions along a straight line /// </summary> /// <param name="splitPt">A point on the splitting line</param> /// <param name="splitDir">The direction of the line</param> /// <param name="splitWidth">Optional. The width of the split.</param> /// <returns>The resultant list of regions. If the line does not bisect /// this region and the region could not be split, this collection will contain /// only the original region.</returns> public IList <PlanarRegion> SplitByLineXY(Vector splitPt, Vector splitDir, double splitWidth = 0) { var result = new List <PlanarRegion>(); var lineInts = new List <double>(); var outerInts = Intersect.CurveLineXY(Perimeter, splitPt, splitDir, null, 0, 1, false, lineInts); if (outerInts.Count > 1) { // Sort intersections by position along curve: var sortedInts = new SortedList <double, double>(outerInts.Count); for (int i = 0; i < outerInts.Count; i++) { sortedInts.Add(outerInts[i], lineInts[i]); } outerInts = sortedInts.Keys.ToList(); lineInts = sortedInts.Values.ToList(); int offset = lineInts.IndexOfMin(); outerInts.Shift(offset); lineInts.Shift(offset); // Create segments data structure var segments = new List <PerimeterSegment>(outerInts.Count - 1); for (int i = 0; i < outerInts.Count; i++) { double t0 = outerInts[i]; double t1 = outerInts.GetWrapped(i + 1); double tC0 = lineInts[i]; double tC1 = lineInts.GetWrapped(i + 1); var segment = new PerimeterSegment(Perimeter, t0, t1, tC0, tC1); segments.Add(segment); } //TODO: void intersections bool backwards = true; while (segments.Count > 0) { var offsets = new List <double>(); PerimeterSegment segment = segments.First(); PolyCurve newPerimeter = segment.Extract().ToPolyCurve(); for (int i = 0; i < newPerimeter.SegmentCount; i++) { offsets.Add(0); } PerimeterSegment nextSegment = FindNextPerimeterSegment(segments, segment.CutterDomain.End, backwards); while (nextSegment != null && nextSegment != segment) { Curve nextCurve = nextSegment.Extract(); newPerimeter.AddLine(nextCurve.StartPoint); offsets.Add(splitWidth / 2); newPerimeter.Add(nextCurve); for (int i = 0; i < nextCurve.SegmentCount; i++) { offsets.Add(0); } segments.Remove(nextSegment); nextSegment = FindNextPerimeterSegment(segments, nextSegment.CutterDomain.End, backwards); } segments.RemoveAt(0); if (!newPerimeter.Closed) { /*if (splitWidth > 0) * { * // Temporary bodge to get rid of 'blades' at ends of split * Vector endToEnd = (newPerimeter.StartPoint - newPerimeter.EndPoint).Unitize(); * var line = new Line( * newPerimeter.EndPoint - endToEnd * splitWidth / 4, * newPerimeter.StartPoint + endToEnd * splitWidth / 4); * newPerimeter.AddLine(line.StartPoint); * offsets.Add(0); * newPerimeter.Add(line); * offsets.Add(splitWidth / 2); * newPerimeter.Close(); * offsets.Add(0); * } * else * {*/ newPerimeter.Close(); offsets.Add(splitWidth / 2); //} } backwards = !backwards; if (splitWidth > 0) { var newNewPerimeter = newPerimeter.OffsetInwards(offsets); // Check offset has not inverted perimeter: // TODO: Do this automatically when offsetting? if (newNewPerimeter != null && newNewPerimeter.IsClockwiseXY() == newPerimeter.IsClockwiseXY()) { newPerimeter = newNewPerimeter.ToPolyCurve(); } else { newPerimeter = null; } } if (newPerimeter != null) { result.Add(new PlanarRegion(newPerimeter, Attributes?.Duplicate())); } } // OLD VERSION: /*for (int i = 0; i < outerInts.Count; i++) * { * double t0 = outerInts[i]; * double t1 = outerInts.GetWrapped(i + 1); * Curve newPerimeter = Perimeter.Extract(new Interval(t0, t1))?.ToPolyCurve(); * //TODO: Cut through and include voids * if (!newPerimeter.Closed) * { * ((PolyCurve)newPerimeter).Close(); * if (splitWidth > 0) * { * var offsets = new double[newPerimeter.SegmentCount]; * offsets[offsets.Length - 1] = splitWidth / 2; * var newNewPerimeter = newPerimeter.OffsetInwards(offsets); * // Check offset has not inverted perimeter: * // TODO: Do this automatically when offsetting? * if (newNewPerimeter != null && newNewPerimeter.IsClockwiseXY() == newPerimeter.IsClockwiseXY()) * { * newPerimeter = newNewPerimeter; * } * else newPerimeter = null; * } * } * if (newPerimeter != null) result.Add(new PlanarRegion(newPerimeter, Attributes?.Duplicate())); * }*/ } else { result.Add(this); //Return the original } return(result); }
/// <summary> /// Offset this curve on the XY plane by varying distances for /// each span. /// </summary> /// <param name="distances">The offset distance. /// Positive numbers will result in the offset curve being to the right-hand /// side, looking along the curve. Negative numbers to the left.</param> /// <param name="tidy">If true (default) collapsed segments will be removed.</param> /// <returns></returns> public override Curve Offset(IList <double> distances, bool tidy = true, bool copyAttributes = true) { //TODO: Implement collapsed segments tidying var result = new PolyCurve(); int distIndex = 0; // Offset sub curves: foreach (Curve crv in SubCurves) { Curve offsetCrv = crv.Offset(distances.SubListFrom(distIndex)); distIndex += crv.SegmentCount; if (distIndex > distances.Count - 1) { distIndex = distances.Count - 1; } if (offsetCrv != null) { if (result.SubCurves.Count > 0) { // Adjust offset curve ends to node out Curve prevCrv = result.SubCurves.Last(); MatchEnds(prevCrv.End, offsetCrv.Start); } result.Add(offsetCrv); if (copyAttributes && offsetCrv.Attributes == null) { offsetCrv.Attributes = Attributes; } } } // Match end to start if (Closed && result.SubCurves.Count > 1) { MatchEnds(result.SubCurves.Last().End, result.SubCurves.First().Start); } if (tidy) { List <Curve> originals = new List <Curve>(); originals.AddRange(SubCurves); // Removed flipped segments int j = 0; while (j < result.SubCurves.Count) { Curve offset = result.SubCurves[j]; Curve original = originals[j]; if (IsOffsetCurveFlipped(original, offset)) { if (result.SubCurves.Count > 0) { Curve previous = result.SubCurves.GetWrapped(j - 1); Curve subsequent = result.SubCurves.GetWrapped(j + 1); bool worked = MatchEnds(previous.End, subsequent.Start); //TODO: Deal with failed matches (for e.g. when adjacent edges are parallel) //if (!worked) //return null; //TEMP: prevents invalid offsets, but a bit overkilly! } result.SubCurves.RemoveAt(j); originals.RemoveAt(j); //j--; } else { j++; } } if (!result.IsValid) { return(null); } } return(result); }