Extend or trim two curves so that then ends meet

Developing a C# Component for grasshopper, I am finding myself often in the situation where I need to extend or trim two curves such that their ends meet.

Both curves were segments of a closed curve. Each segment is offset on the XY plane by a different value. Whether the curved need to be extended or trimmed depends on curvature, angle, and offset distance.

My code currently checks for 4 possible conditions:

  • curveA intersects curveB? trim both curves.
  • curveA would intersect curveB if extended? extend curveA, trim curveB
  • curveB would intersect curveA if extended? extend curveB, trim curveA
  • otherwise: calculate the linear function of the tangent at the endpoint of both curves, find the point where both lines would intersect, calulated distance to that point, extend both curves.

It works but feels needlessly complex. Is there a simpler way?

My code:
(It is known that the curves both are in the same direction, so we want to trim or extend from the end of crvA and the start of crvB)
(It can also be take for granted that the curves are not parallel)

private static (Curve crvAn, Curve crvBn) ConnectCurves(Curve crvA, Curve crvB)
 {
     Vector3d tgtA = crvA.TangentAt(crvA.Domain.T1);
     Vector3d tgtB = crvB.TangentAt(crvB.Domain.T0);
     // curves intersect
     CurveIntersections ccinter = Intersection.CurveCurve(crvA, crvB, DocumentTolerance(), DocumentTolerance());
     if (ccinter.Count > 0)
     {
         IntersectionEvent inter = ccinter[0];
         return (
             crvA.Trim(crvA.Domain.T0, inter.ParameterA),
             crvB.Trim(inter.ParameterB, crvB.Domain.T1)
         );
     }

     // extending one curve will intersect another
     Line lineA = new Line(crvA.PointAtEnd, tgtA);
     CurveIntersections clinterA = Intersection.CurveLine(crvB, lineA, DocumentTolerance(), DocumentTolerance());
     if (clinterA.Count > 0)
     {
         IntersectionEvent inter = clinterA[0];
         
         var a = crvA.Extend(CurveEnd.End, new Line(crvA.PointAtEnd, inter.PointB).Length, CurveExtensionStyle.Line);
         var b = crvB.Trim(inter.ParameterA, crvB.Domain.T1);
         return (a, b);
     }
     Line lineB = new Line(crvB.PointAtStart, tgtB);
     CurveIntersections clinterB = Intersection.CurveLine(crvA, lineB, DocumentTolerance(), DocumentTolerance());
     if (clinterA.Count > 0)
     {
         IntersectionEvent inter = clinterB[0];
         var a = crvA.Trim(crvA.Domain.T0, inter.ParameterA);
         var b = crvB.Extend(CurveEnd.Start, new Line(crvB.PointAtStart, inter.PointB).Length, CurveExtensionStyle.Line);
         return (a, b);
     }

     // basic math to find the intersection point
     double slopeA = tgtA.Y / tgtA.X;
     double slopeB = tgtB.Y / tgtB.X;
     double offA = slopeA * crvA.PointAtEnd.X - crvA.PointAtEnd.Y;
     double offB = slopeB * crvB.PointAtStart.X - crvB.PointAtStart.Y;
     double ix = (offB - offA) / (slopeA - slopeB);
     double iy = slopeA * ix + offA;
     Point3d ipoint = new Point3d(ix, iy, crvA.PointAtEnd.Z);
     // extend both lines until they intersect
     return (
         crvA.Extend(CurveEnd.End, new Line(crvA.PointAtEnd, ipoint).Length, CurveExtensionStyle.Line),
         crvB.Extend(CurveEnd.Start, new Line(crvB.PointAtStart, ipoint).Length, CurveExtensionStyle.Line)
     );

 }

Hi Isaac, welcome to the forum! Thank you for your clear question :slight_smile:

I think you may want to take a look at Curve.MakeEndsMeet, if you the curves do not intersect. Trimming when the curves do intersect is, I think, unavoidable.

https://developer.rhino3d.com/api/rhinocommon/rhino.geometry.curve/makeendsmeet

Also, all RhinoCommon documentation can be found at https://developer.rhino3d.com/api/rhinocommon/

Maybe CreateFilletwould cover most of your scenarios

I tried MakeEndsMeet, but it moves the curves. I don’t want the curves to move, just extend or trim as necessary. Is there an option to MakeEndsMeet that will not move the curves?

Actually yes, CreateFilletCurves with a radius of 0 seems to do the trick. I never would have thought of that.

Is it reliable that the index 0 and 1 from the result array will always correspond to the first and third arguments to the function?