It’s possible to analytically find an axis-aligned bounding box
(AABB) for Bézier curves. I wanted to try doing that math for myself,^{1} and ended up writing a solver in
Haskell, along with a few animations to show it off. All animations here
were built with Reanimate.

In Reanimate, we can tween the value of a `Double`

variable `x`

over the course of time `t`

using
`tweenVar`

and a `Signal`

.

`$ tweenVar x t $ \orig -> fromToS orig new . curveS 2 fork `

This lets us cycle through a bunch of different curves by interpolating between the sets of variables that define them.

A cubic Bézier curve is defined by four points—call them
`start`

, `end`

, `control start`

, and
`control end`

. We can use these points to define an SVG that
will draw our Bézier curve for us. In Reanimate this is done with the
`CurveTo`

instruction on a `Path`

.

We can also draw the control points as little circles if we want to
visualize them. Defining these as sprites lets us use `Var`

s
to control them, and then Reanimate will handle all the variable
updating behind the scenes for us, which is pretty nice for focusing on
the math.

```
cubicBezier :: V2 Double
-> V2 Double
-> V2 Double
-> V2 Double
-> SVG
cubicBezierV2 startX startY)
(V2 controlStartX controlStartY)
(V2 controlEndX controlEndY)
(V2 endX endY)
(=
PathTree $ Path mempty
MoveTo OriginAbsolute [V2 startX startY]
[ CurveTo OriginAbsolute
,
[V2 controlStartX controlStartY
( V2 controlEndX controlEndY
, V2 endX endY
,
)
]
]
point :: Double -> Double -> SVG
=
point x y CircleTree $ Circle mempty (Num x, Num y) (Num 0.1)
```

In two dimensions, an axis-aligned bounding box is defined by four
numbers—`min X`

, `min Y`

, `max X`

and
`max Y`

(this is equivalent to using two corner points).

We can find these numbers by solving the quadratic formula to end up
with time values `t`

to test, then plugging these
`t`

s back into our cubic Bézier to find minima and
maxima.

These equations give us the `a`

`b`

and
`c`

that we’ll be plugging into the quadratic formula, and
the overall strategy comes from taking the derivative of our cubic curve
to find its roots (the derivative of a cubic is a quadratic). We can
apply these equations independently to the X and Y parts of our four
points that define the Bézier curve.^{2}

```
=
sol_a start controlStart controlEnd end 3 * (-start + 3 * controlStart - 3 * controlEnd + end)
=
sol_b start controlStart controlEnd 6 * (start - 2 * controlStart + controlEnd)
=
sol_c start controlStart 3 * (controlStart - start)
```

We need to be slightly careful here to return only valid roots and
only valid values of `t`

. Since `t`

ranges from 0
to 1, we throw out anything outside that range, as well as ignoring
cases where we would otherwise perform division by zero.

```
=
quadratic a b c if a == 0 then []
else filter (\t -> t > 0 && t < 1)
-b + radicand) / (2 * a)
[ (-b - radicand) / (2 * a)
, (
]where radicand = sqrt (b * b - 4 * a * c)
```

We now have a bunch of options for `t`

values to try,
including `0`

and `1`

(the start and end points of
the curve, respectively).

`= [0, 1] <> xRoots <> yRoots allTsToTry `

We can plug that list back into the cubic Bézier equation to find extreme points, e.g. the minimum X value. Again we can treat X and Y separately.

```
=
solveCubicBezierX t * (1 - t) * (1 - t) * (1 - t) + 3 * controlStart * t * (1 - t) * (1 - t) + 3 * controlEnd * t * t * (1 - t) + end * t * t * t
start
= minimum $ map solveCubicBezierX allTsToTry minX
```

Typing out that formula was where I made this project’s mistake that
was hardest to debug: I should have written the second line here instead
of the top for `controlStart`

.

```
* (1 - t) * (1 - t)
t * t * (1 - t) t
```

After spotting that, we now have a nice Bézier-in-a-box, solved entirely analytically, and complete with animations.

Because of the amount of math involved, I think Haskell was an
excellent choice for this little project. Animating by hand would have
been painful. Reanimate took some getting used to, but once I realized I
could write my math functions as plainly as possible, but make use of
the `Applicative`

instance for `Frame`

s to get
tweening almost “for free”, it got a lot nicer.