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.

This youtube video was what kicked off my curiosity here about bounded Bézier curves. Its animations are really high quality, which kicked off my desire to try using Reanimate for this project. Quite the inspiring video!↩︎

I found this Game Dev StackExchange post helpful as a way to explain this process and check my answers.↩︎