In this section we discuss the mathematics behind the curves we provide in this package, and the operations that we perform on curves.
Before we get into the curves themselves, let us first discuss what we need from a parametric curve, i.e. for a curve subtyping AbstractParametricCurve
. We let denote a parametric curve, and we always assume the curve to be non-intersecting (except at the endpoints) and three times continuously differentiable. For the discussion below, there may be some curves which take a simpler approach than the general approach described, and this will be indicated later when we discuss these specific curves.
One important quantity to compute is the arc length over some interval along the curve. In general, this is given by the integral
which is typically computed using numerical integration. In this package, we use Gauss-Legendre quadrature so that
where are the nodes and are the weights of the Gauss-Legendre quadrature rule, and are the number of nodes in use. We use for all integrals in this package.
The curvature of a curve describes how much the curve deviates from a straight line at a given point. It is defined as
where denotes the cross product; in , we are defining . Writing , this can be written as
The total variation of over is defined as the total absolute change in the tangent angle, which we can write as
where is the tangent angle of the curve, and is the curve segment from to . This is also called the total absolute curvature, since (treating as the arc length parameter). Thus,
where we have used . We evaluate this using numerical integration just as we did for arc length, writing
The reason that total variation is important is that it gives a measure of how much the curve is bending over the interval , meaning we can use the total variation to assess how many edges are needed to give a reasonable piecewise linear approximation to the curve.
There is a way to evaluate without computing the integral. By default, any new AbstractParametricCurve
will do so, but for the curves we have implemented in this package we apply the following idea. First, note that the total variation over some curve where the curve's orientation is monotone is simply the angle between the tangent vectors at the endpoints. To see this, note that since the orientation is monotone the angle is monotone, and so the total change in the angle is simply the difference in the angles at the endpoints. With this idea, we take and write where for and is a curve segment where the orientation is monotone. Thus, the integral can be evaluated by simply summing the angles between the tangent vectors at the endpoints of each .
One complication with the above approach is that actually finding the can be difficult. To do so, we need to understand where the curve's orientation changes. There are five cases where this can occur that we list below, letting be a point where the orientation changes and :
- Horizontal turning point: .
- Vertical turning point: .
- Horizontal inflection point: and .
- Vertical inflection point: and .
- Inflection point: .
These cases do not have to occur simultaneously - any of them is sufficient to potentially cause a change in orientation; in our implementation, for cases 3 and 4 we do not check whether or is zero. Note that the first four conditions together are not necessarily needed for finding these monotone pieces, but they guarantee that the change in orientation is at most , making it easier to compute the total change in orientation (since a change in orientation of could also be if ). To find these points , we use Newton's method on all of the respective functions. So, when initialising a curve that uses this approach, we first find all where any of these changes occur, and order them so that . These values define the monotone pieces that the curve is split into. An example of these orientation markers is shown below, where we show the markers on the curve using red dots.

There are many orientation markers on this curve. Only two of these come from , with all the others coming from , and , as we can see from the graphs shown.
An important problem is finding the point on a curve that is closest to a given point . Our generic approach to this relies on there being a lookup table for which stores the values for , where to allow for binary search to be used.
The approach starts by computing , where is the index so that the point in the lookup table is closest to , and . We can then apply binary search in the following way to find an approximation to the closest point on closest to up to some tolerance ( in this package); in what follows, we assume , but these boundary cases can be handled in a similar way:
- First, let , , and , and compute . If , then we are done, otherwise we go to step 2.
- Compute , , , and . If , then we choose the left-middle to be the new center point, replacing with and with . Otherwise, we choose the right-middle to be the new center point, replacing with and with . We then go back to step 1 with .
Once the above procedure converges, the final value of gives the point on that is closest to , i.e. is the approximation to the closest point on to .
An example of this procedure is shown below: The point of interest is shown in red, and the lookup points are shown in blue. The lookup point closest to the point of interest is shown via the green line, which defines , and the magenta line shows the section of the curve corresponding to this interval . The black dot shows the closest point on the curve to the point of interest. The binary search for this problem would start by shrinking into the interval , replacing by , since is closer to that side of than to .

It is important that we know how to find the position of a point relative to some curve , meaning whether is left or right of . The approach we take for this is relatively simple:
- First, find so that is the closest point on to .
- Compute and construct the line segment that connects and .
- The position of is then determined by the position of relative to .
In the example below, would be right of the curve as it is to the right of the tangent vector .

We may want to split a curve between two points at a point such that . This is done using a simple bisection method:
- Compute , let , and set , where .
- For 100 iterations, do: Compute . If or , then break and return . Otherwise, replace with if , and replace with otherwise. Finally, let and continue iterating.
- Return as the equidistant split point.
Another important operation is the computation of a point that splits a given curve between two points at a point such that . This is called an equivariation split, and is useful as it gives a way to divide a curve into two halves in a more natural way than using, say, an equidistant split which might not be as appropriate a split for certain curves. The computation of is done using a simple bisection method that follows the exact same approach as for equidistant splits, except that instead of computing arc length we compute the total variation, and we initialise as the equidistant split rather than the midpoint of the interval.
We often need to find the point that corresponds to a point on a curve . This is done by simply finding the closest point on the curve to and returning the parameter value of that point.
Given two curves and , a problem of interest is to consider the angle between them at some point . This is done by computing the tangent vectors at the point for both curves, and then computing the angle between these two tangent vectors. Computing these angles is needed during mesh refinement, since we need to protect against small angles.
For mesh refinement we need to use concentric circular shells to protect against small angles. For curves, this means that we need to know how to compute the intersection of a circle with a given curve. In particular, given a circle of radius centered at , we need to find the first intersection of the curve with the circle, where , assuming that such an intersection exists.
The procedure for this problem starts by computing an interval to be used for computing using a bisection method. To find and , we initially set and . Then, for : Compute and let . If we set and return , else we set and continue. There is also a method for finding using a lookup table, but we do not describe that here. Once we have this interval , a bisection method is used to find , remembering that we are trying to solve . An example of such an intersection is shown below. The red curve shows the portion of the curve over an interval , and the green circle shows the circle of radius centered at a point . The blue point shows the first intersection of the curve with the circle. The other intersection is ignored as it is not on the red portion.

Now we discuss in detail all the curves we provide in this package, starting with LineSegment
. We represent line segments as oriented segments between points and parametrised by for . The implementation of the above operations for LineSegment
is straightforward, and we do not discuss them here.
The curve CircularArc
is for representing a circular arc, meaning a part of a circle with radius and center spanning some angle , parametrised over . We parametrise this arc using
where and . With this parametrisation, we also have
In addition:
- To determine the position of a point relative to the circle, we simply use the
in_circle
predicate. - The arc length is simply .
- The curvature is .
- The total variation is .
- The equidistant and equivariation splits are both .
- Given a point on the circle, its inverse is , where . If , the inverse is instead ; if , the inverse is instead .
An EllipticalArc
is used to represent an arc of an ellipse, defined by a center , horizontal radius , vertical radius , a rotation angle , a start angle (as measured from the center), and a final angle . We define . The parametrisation of this arc is
where . The derivatives of are given by
The curvature is given by
The total variation is easily computed by simply taking the angle between the tangent vectors at the endpoints since the orientation is monotone.
To determine the position of a point relative to the arc, we transform into coordinates where the ellipse becomes a unit circle and consider the position of the transformed coordinates. In particular, for a point , we transform it into
and then compare it to the unit circle.
The inverse of a point is computed as follows. First, compute
and then modulo . Finally, ; if , the inverse is instead ; if , the inverse is instead .
The next curve we discuss is the BezierCurve
. Bézier curves are curves that are defined by a set of control points. They are not interpolating curves, but their shape can be highly controlled using the control points. A great resource on Bézier curves is this primer. The precise definition of Bézier curves will not be delved into here.
To write down the parametrisation of a Bézier curve, let the control points be . Our parametrisation is then
This is not the form we use to evaluate , though. Instead, de Casteljau's algorithm is used to evaluate the curve, a recursive algorithm for evaluating .
To differentiate , we can use the fact that the derivative of an th degree Bézier curve (our curve is of degree ) is an th degree Bézier curve with control points . Thus, differentiating is the same as applying de Casteljau's algorithm to an th degree Bézier curve with control points . The same is true for twice and thrice differentiating ; for the second derivatives the control points are , and for the third derivatives the control points are .
For all the other operations, we use the generic approaches previously described, as Bézier curves do not lend themselves nicely to simple formulas for these operations.
The next curve we discuss is the BSpline
. B-splines are controlled by a set of control points and a set of knots. They are not interpolating curves. In our implementation, we do not allow for general knots to be provided, and the spline always goes through the first and last points, but not any of the intermediate points. Rather than go through the precise definition here, you are better off reading for example this section of the primer here, or The NURBs Book by Piegl and Tiller (1997).
For our implementation, you just need to know that we use de Boor's algorithm for evaluating a B-spline. Similarly, since the derivative of a B-spline is just another B-spline, we can use de Boor's algorithm to differentiate a B-spline, and similarly for its second and third derivatives. For all the other operations we use the generic approaches previously described just as we did for Bézier curves.
We now discuss Catmull-Rom splines, which is an interpolating spline defined by a set of piecewise cubic polynomials. To discuss these splines, we need to consider the Catmull-Rom spline defined by a set of four points . This is a cubic polynomial of the form over that interpolates the four points, but is drawn only between and with and . We also define two parameters and that define the type of the parametrisation and the tightness of the spline, respectively; is called a uniform parmetrisation, is called a centripetal parametrisation, and is called a chordal parametrisation. Increasing makes the spline tighter, and turns the spline into a piecewise linear curve. In this package, we only directly support and .
Using these control points and parameters, following this article, we can compute the coefficients of our polynomial as follows:
where and for .
Using this definition of a Catmull-Rom spline through four points, we can generalise it to a set of control points . Firstly, since a Catmull-Rom spline through four points only draws through the inner two points, if we want the spline to interpolate through all of the control points then we need to define two new control points and so that and are included in the interior of some spline segment (if the spline is closed so that , no new points are needed). Using ideas from CatmullRom.jl, we define as follows: We reflect , the coordinate of , left of to a point . We then need to find the point that best suits the spline at this point . We do this using rational interpolation, specifically Thiele's quartic rational interpolant. If the resulting is not finite, we use the cubic rational interpolant and, if that fails, we use quadratic interpolation. If , we instead apply this idea to . We then set , and apply the same ideas to .
We now have a set of control points , and are interested in a Catmull-Rom spline that interpolates through . To evlauate the spline , we start by defining the knots , where , and then set so that . We then find the knot interval that contains , and map into this interval using . Next, we construct the Catmull-Rom spline segment through , , , and , and evaluate this segment at to get .
To differentiate , we apply similar ideas as above except that we differentiate the Catmull-Rom spline segment through , , , and , and evaluate this derivative at to get (after multiplying by ). Similar ideas apply for computing and .
To compute the arc length , we store the arc lengths of each spline segment (computed using numerical integration) to quickly compute the arc length across any segments entirely covered in , and then use numerical integration for any remaining segments not completely covered.
For the remaining operations, we use the generic approaches previously described.