Nigel Tao

Three Points (Two Opposing) Define an Ellipse

Update on 2021-06-21: three (or even four) points in general do not define an ellipse. But the additional information that the first and last of the three points are at opposite ends do define an ellipse. Equivalently, two on-curve points and the center point define an ellipse.

This blog post also primarily discusses a 4-piece cubic Bézier approximation to that ellipse. That ellipse (not just its approximation) is still well-defined, but this blog post does not derive a closed form for it. For vector graphic rasterization, the Bézier approximation is good enough.

In 2-Dimensional geometry, a sequence of three points A, B and C define a fourth point D = A - B + C of a parallelogram.

It also defines a center point X = (A + C) / 2, equivalent to X = (B + D) / 2. X is the center of an ellipse that passes through all of A, B, C and D. Define two axis vectors r = B - X and s = C - X. These aren’t necessarily the major (longest) and minor (shortest) axes of the ellipse. They’re just two axes, derived only from A, B and C.

For 2-D graphics libraries based on Bézier curves, we can approximate this ellipse by four cubic Bézier curves:

The 8 off-curve control points are defined by a scalar number k ≈ 0.552:

Many expositions of “the cubic Bézier approximation to the quarter circle” use k = (((√2 - 1) * 4) / 3) ≈ 0.552284749830793, and an ellipse is an affine transformed circle the same way that a parallelogram is an affine transformed square. “A Primer on Bézier Curves”, and specifically its “TL;DR: just tell me which value I should be using [for quarter circles]” section, argues that the slightly smaller 0.551784777779014 minimizes the average error term. The trade-off, compared to (((√2 - 1) * 4) / 3), being that the midpoint of the cubic Bézier curve is no longer on the circle (or ellipse).

Three Points animation

If you want to linger on individual animation frames:

The point (haha) of all of this is that, for 2-D graphics APIs, a “CubeTo” function usually requires 6 arguments: three explicit coordinate pairs e.g. A+, B- and B. The starting point A (also known as the “current point” or “pen location”) is implicitly part of the canvas state. Four cubics therefore generally require 24 explicit coordinates. In comparison, if you also know that the four cubics approximate a circle or an ellipse, this compresses to 4 explicit coordinates (just the B and C coordinate pairs).

Encoding a quarter-, half- or three-quarter-ellipse also only takes 4 explicit coordinates. Quarter here means, when starting from A, stopping at B instead of going all the way around: A to B to C to D to A. Quarter doesn’t necessarily mean sweeping out 90 degrees. It will do so if the r and s vectors are perpendicular, but they don’t have to be.

We could compress this further to just 2 explicit coordinates (e.g. the X coordinate pair) if we’re restricted to just full circles passing through A. However, it can be useful to be able to affine transform (e.g. rotate, scale, shear, translate, etc) a whole path just by transforming each node individually, without changing the type or number of component curves. While the affine transform of an ellipse remains an ellipse, the affine transform of a circle isn’t always a circle. For a vector graphics format, there’s no perfect representation, only different trade-offs.

No Trigonometry Required

While less general than an “ArcTo” function, calculating the 8 off-curve control points only requires basic arithmetic (addition, subtraction and multiplication). Rasterizing the (approximation to the) ellipse does not require trigonometry or other angular math.

To repeat, calculating an affine transformation of the “three points define an ellipse” form is trivial - just transform the three points individually. In comparison, with something like Cairo’s API:

void
cairo_arc (cairo_t *cr,
           double xc,
           double yc,
           double radius,
           double angle1,
           double angle2);

Calculating the new radius and angles after a general affine transformation is not so easy. Especially as the cairo_arc documentation goes on to say that “the arc is circular in user space. To achieve an elliptical arc… scale the current transformation matrix”.

Skia’s API similarly offers an addArc function that works in angles. There’s also an addOval function but that is for ‘upright ellipses’ whose major and minor axes are axis-aligned. In general, an affine transform (such as a rotation) of an ‘upgright ellipse’ preserves ‘ellipseness’ but not ‘uprightness’.

To repeat, the “three points” form (expressing quarter-, half-, three-quarter- or full-ellipses) is less general than an angle-based arc form. As always, it’s different trade-offs.


Published: 2021-06-20