public static PolyCurve Offset(this PolyCurve curve, double offset, Vector normal = null, bool tangentExtensions = false, double tolerance = Tolerance.Distance) { if (curve == null || curve.Length() < tolerance) { return(null); } //if there are only Line segmensts switching to polyline method which is more reliable if (curve.Curves.All(x => x is Line)) { Polyline polyline = ((Polyline)curve).Offset(offset, normal, tangentExtensions, tolerance); if (polyline == null) { return(null); } return(new PolyCurve { Curves = polyline.SubParts().Cast <ICurve>().ToList() }); } List <ICurve> subParts = curve.SubParts(); //Check if contains any circles, if so, handle them explicitly, and offset any potential leftovers by backcalling this method if (subParts.Any(x => x is Circle)) { IEnumerable <Circle> circles = subParts.Where(x => x is Circle).Cast <Circle>().Select(x => x.Offset(offset, normal, tangentExtensions, tolerance)); PolyCurve nonCirclePolyCurve = new PolyCurve { Curves = curve.Curves.Where(x => !(x is Circle)).ToList() }; if (nonCirclePolyCurve.Curves.Count != 0) { nonCirclePolyCurve = nonCirclePolyCurve.Offset(offset, normal, tangentExtensions, tolerance); } nonCirclePolyCurve.Curves.AddRange(circles); return(nonCirclePolyCurve); } if (!curve.IsPlanar(tolerance)) { BH.Engine.Reflection.Compute.RecordError("Offset works only on planar curves"); return(null); } if (curve.IsSelfIntersecting(tolerance)) { BH.Engine.Reflection.Compute.RecordError("Offset works only on non-self intersecting curves"); return(null); } if (offset == 0) { return(curve); } bool isClosed = curve.IsClosed(tolerance); if (normal == null) { if (!isClosed) { BH.Engine.Reflection.Compute.RecordError("Normal is missing. Normal vector is not needed only for closed curves"); return(null); } else { normal = curve.Normal(); } } if (offset > 0.05 * curve.Length()) { return((curve.Offset(offset / 2, normal, tangentExtensions, tolerance))?.Offset(offset / 2, normal, tangentExtensions, tolerance)); } PolyCurve result = new PolyCurve(); Vector normalNormalised = normal.Normalise(); //First - offseting each individual element List <ICurve> offsetCurves = new List <ICurve>(); foreach (ICurve crv in subParts) { if (crv.IOffset(offset, normal, false, tolerance) != null) { offsetCurves.Add(crv.IOffset(offset, normal, false, tolerance)); } } int counter = 0; //removing curves that are on a wrong side of the main curve for (int i = 0; i < offsetCurves.Count; i++) { Point sp = offsetCurves[i].IStartPoint(); Point ep = offsetCurves[i].IEndPoint(); Point mp = offsetCurves[i].IPointAtParameter(0.5); Point spOnCurve = curve.ClosestPoint(sp); Point epOnCurve = curve.ClosestPoint(ep); Point mpOnCurve = curve.ClosestPoint(mp); Vector sTan = curve.TangentAtPoint(spOnCurve, tolerance); Vector eTan = curve.TangentAtPoint(epOnCurve, tolerance); Vector mTan = curve.TangentAtPoint(mpOnCurve, tolerance); Vector sCheck = sp - spOnCurve; Vector eCheck = ep - epOnCurve; Vector mCheck = mp - mpOnCurve; Vector sCP = sTan.CrossProduct(sCheck).Normalise(); Vector eCP = eTan.CrossProduct(eCheck).Normalise(); Vector mCP = mTan.CrossProduct(mCheck).Normalise(); if (offset > 0) { if (sCP.IsEqual(normalNormalised, tolerance) && eCP.IsEqual(normalNormalised, tolerance) && mCP.IsEqual(normalNormalised, tolerance)) { offsetCurves.RemoveAt(i); i--; counter++; } } else { if (!sCP.IsEqual(normalNormalised, tolerance) && !eCP.IsEqual(normalNormalised, tolerance) && !mCP.IsEqual(normalNormalised, tolerance)) { offsetCurves.RemoveAt(i); i--; counter++; } } } //Again if there are only Line segments switching to polyline method as it is more reliable if (offsetCurves.All(x => x is Line)) { Polyline polyline = new Polyline { ControlPoints = curve.DiscontinuityPoints() }; result.Curves.AddRange(polyline.Offset(offset, normal, tangentExtensions, tolerance).SubParts()); return(result); } bool connectingError = false; //Filleting offset curves to create continuous curve for (int i = 0; i < offsetCurves.Count; i++) { int j; if (i == offsetCurves.Count - 1) { if (isClosed) { j = 0; } else { break; } } else { j = i + 1; } PolyCurve temp = offsetCurves[i].Fillet(offsetCurves[j], tangentExtensions, true, false, tolerance); if (temp == null) //trying to fillet with next curve { offsetCurves.RemoveAt(j); if (j == 0) { i--; } if (j == offsetCurves.Count) { j = 0; } temp = offsetCurves[i].Fillet(offsetCurves[j], tangentExtensions, true, false, tolerance); } if (!(temp == null)) //inserting filetted curves { if (j != 0) { offsetCurves.RemoveRange(i, 2); offsetCurves.InsertRange(i, temp.Curves); } else { offsetCurves.RemoveAt(i); offsetCurves.RemoveAt(0); offsetCurves.InsertRange(i - 1, temp.Curves); } i = i + temp.Curves.Count - 2; } else { connectingError = true; } } //removing curves that are to close to the main curve for (int i = 0; i < offsetCurves.Count; i++) { if ((offsetCurves[i].IPointAtParameter(0.5).Distance(curve) + tolerance < Math.Abs(offset) && (offsetCurves[i].IStartPoint().Distance(curve) + tolerance < Math.Abs(offset) || offsetCurves[i].IEndPoint().Distance(curve) + tolerance < Math.Abs(offset)))) { PolyCurve temp = offsetCurves[((i - 1) + offsetCurves.Count) % offsetCurves.Count].Fillet(offsetCurves[(i + 1) % offsetCurves.Count], tangentExtensions, true, false, tolerance); if (temp != null) { if (i == 0) { offsetCurves.RemoveRange(0, 2); offsetCurves.RemoveAt(offsetCurves.Count - 1); offsetCurves.InsertRange(0, temp.Curves); i = temp.Curves.Count - 1; } else if (i == offsetCurves.Count - 1) { offsetCurves.RemoveRange(i - 1, 2); offsetCurves.RemoveAt(0); offsetCurves.InsertRange(offsetCurves.Count - 1, temp.Curves); i = offsetCurves.Count - 1; } else { offsetCurves.RemoveRange(i - 1, 3); offsetCurves.InsertRange(i - 1, temp.Curves); i = i - 3 + temp.Curves.Count; } } if (offsetCurves.Count < 1) { Reflection.Compute.ClearCurrentEvents(); Reflection.Compute.RecordError("Method failed to produce correct offset. Returning null."); return(null); } counter++; } } Reflection.Compute.ClearCurrentEvents(); if (connectingError) { Reflection.Compute.RecordWarning("Couldn't connect offset subCurves properly."); } if (offsetCurves.Count == 0) { Reflection.Compute.RecordError("Method failed to produce correct offset. Returning null."); return(null); } List <PolyCurve> resultList = Compute.IJoin(offsetCurves, tolerance); if (resultList.Count == 1) { result = resultList[0]; } else { result.Curves = offsetCurves; Reflection.Compute.RecordWarning("Offset may be wrong. Please inspect the results."); } if (counter > 0) { Reflection.Compute.RecordWarning("Reduced " + counter + " line(s). Please inspect the results."); } if (result.IsSelfIntersecting(tolerance) || result.CurveIntersections(curve, tolerance).Count != 0) { Reflection.Compute.RecordWarning("Intersections occured. Please inspect the results."); } if (isClosed && !result.IsClosed(tolerance)) { Reflection.Compute.RecordError("Final curve is not closed. Please inspect the results."); } return(result); }
/***************************************************/ public static double Distance(this Point point, PolyCurve curve) { return(point.Distance(curve.ClosestPoint(point))); }
/***************************************************/ public static List <Point> SortAlongCurve(this List <Point> points, PolyCurve curve, double tolerance = Tolerance.Distance, double angleTolerance = Tolerance.Angle) { List <Tuple <Point, double> > cData = points.Select(p => new Tuple <Point, double>(p.Clone(), curve.ParameterAtPoint(curve.ClosestPoint(p)))).ToList(); cData.Sort(delegate(Tuple <Point, double> d1, Tuple <Point, double> d2) { return(d1.Item2.CompareTo(d2.Item2)); }); return(cData.Select(d => d.Item1).ToList()); }
/***************************************************/ public static bool IsContaining(this PolyCurve curve, List <Point> points, bool acceptOnEdge = true, double tolerance = Tolerance.Distance) { // Todo: // - to be replaced with a general method for a nurbs curve? // - this is very problematic for edge cases (cutting line going through a sharp corner, to be superseded? BoundingBox box = curve.Bounds(); if (points.Any(x => !box.IsContaining(x, true, tolerance))) { return(false); } if (!curve.IsClosed(tolerance)) { return(false); } if (curve.Curves.Count == 1 && curve.Curves[0] is Circle) { return(IsContaining(curve.Curves[0] as Circle, points, acceptOnEdge, tolerance)); } Plane p = curve.FitPlane(tolerance); double sqTol = tolerance * tolerance; if (p == null) { if (acceptOnEdge) { foreach (Point pt in points) { if (curve.ClosestPoint(pt).SquareDistance(pt) > sqTol) { return(false); } } return(true); } else { return(false); } } List <ICurve> subParts = curve.SubParts(); List <Vector> edgeDirections = subParts.Where(s => s is Line).Select(c => (c as Line).Direction()).ToList(); foreach (Point pt in points) { Point pPt = pt.Project(p); if (pPt.SquareDistance(pt) > sqTol) // not on the same plane { return(false); } Point end = p.Origin; // Avrage of control points Vector direction = (end - pPt).Normalise(); // Gets a line cutting through the curves and the point while (direction.SquareLength() <= 0.5 || edgeDirections.Any(e => 1 - Math.Abs(e.DotProduct(direction)) <= Tolerance.Angle)) // not zeroa or parallel to edges { end = end.Translate(Create.RandomVectorInPlane(p, true)); direction = (end - pPt).Normalise(); } Line ray = new Line { Start = pPt, End = end }; ray.Infinite = true; List <Point> intersects = new List <Point>(); List <Point> extraIntersects = new List <Point>(); foreach (ICurve subPart in subParts) { List <Point> iPts = subPart.ILineIntersections(ray, false, tolerance); // LineIntersection ignores the `false` foreach (Point iPt in iPts) { double signedAngle = direction.SignedAngle(subPart.ITangentAtPoint(iPt, tolerance), p.Normal); if ((subPart.IStartPoint().SquareDistance(iPt) <= sqTol)) // Keep intersections from beeing counted twice? { if (signedAngle >= -Tolerance.Angle) // tangent is to the left of the direction { intersects.Add(iPt); } else { extraIntersects.Add(iPt); } } else if ((subPart.IEndPoint().SquareDistance(iPt) <= sqTol)) { if (signedAngle <= Tolerance.Angle) // tangent is to the rigth of the direction { intersects.Add(iPt); } else { extraIntersects.Add(iPt); } } else if (Math.Abs(signedAngle) <= Tolerance.Angle) // They are parallel { extraIntersects.Add(iPt); } else { intersects.Add(iPt); } } } if (intersects.Count == 0) // did not intersect the curve (strange) { return(false); } if ((pPt.ClosestPoint(intersects.Union(extraIntersects)).SquareDistance(pPt) <= sqTol)) // if any intersection point is the point { if (acceptOnEdge) { continue; } else { return(false); } } intersects.Add(pPt); intersects = intersects.SortCollinear(tolerance); for (int j = 0; j < intersects.Count; j++) // Even indecies on a colinerar sort is outside the region { if (j % 2 == 0 && intersects[j] == pPt) { return(false); } } } return(true); }
/***************************************************/ public static bool IsContaining(this PolyCurve curve, List <Point> points, bool acceptOnEdge = true, double tolerance = Tolerance.Distance) { // Todo: // - to be replaced with a general method for a nurbs curve? // - this is very problematic for edge cases (cutting line going through a sharp corner, to be superseded? if (curve.IsClosed(tolerance)) { Plane p = curve.FitPlane(tolerance); double sqTol = tolerance * tolerance; if (p == null) { if (acceptOnEdge) { foreach (Point pt in points) { if (curve.ClosestPoint(pt).SquareDistance(pt) > sqTol) { return(false); } } return(true); } else { return(false); } } else { List <ICurve> subParts = curve.SubParts(); List <Vector> edgeDirections = subParts.Where(s => s is Line).Select(c => (c as Line).Direction()).ToList(); foreach (Point pt in points) { Point pPt = pt.Project(p); if (pPt.SquareDistance(pt) <= sqTol) { Point end = p.Origin; Vector direction = (end - pPt).Normalise(); while (direction.SquareLength() <= sqTol || edgeDirections.Any(e => 1 - Math.Abs(e.DotProduct(direction)) <= Tolerance.Angle)) { end = end.Translate(Create.RandomVectorInPlane(p, true)); direction = (end - pPt).Normalise(); } Line ray = new Line { Start = pPt, End = end }; ray.Infinite = true; List <Point> intersects = new List <Point>(); List <Point> extraIntersects = new List <Point>(); foreach (ICurve subPart in subParts) { List <Point> iPts = subPart.ILineIntersections(ray, false, tolerance); foreach (Point iPt in iPts) { double signedAngle = direction.SignedAngle(subPart.ITangentAtPoint(iPt, tolerance), p.Normal); if ((subPart.IStartPoint().SquareDistance(iPt) <= sqTol)) { if (signedAngle >= -Tolerance.Angle) { intersects.Add(iPt); } else { extraIntersects.Add(iPt); } } else if ((subPart.IEndPoint().SquareDistance(iPt) <= sqTol)) { if (signedAngle <= Tolerance.Angle) { intersects.Add(iPt); } else { extraIntersects.Add(iPt); } } else if (Math.Abs(signedAngle) <= Tolerance.Angle) { extraIntersects.Add(iPt); } else { intersects.Add(iPt); } } } if (intersects.Count == 0) { return(false); } if ((pPt.ClosestPoint(intersects.Union(extraIntersects)).SquareDistance(pPt) <= sqTol)) { if (acceptOnEdge) { continue; } else { return(false); } } intersects.Add(pPt); intersects = intersects.SortCollinear(tolerance); for (int j = 0; j < intersects.Count; j++) { if (j % 2 == 0 && intersects[j] == pPt) { return(false); } } } else { return(false); } } return(true); } } return(false); }