Les courbes de Bézier Grand Oral · mathématiques, NSI, sciences de l'ingénieur Un support de connaissances pour construire sa propre question

← Tous les textes du Grand Oral

MathsNSISI

Chaque lettre de ce texte, sur votre écran, est dessinée par des courbes de Bézier. Le logo d'une marque, une icône, le contour d'une carrosserie ou d'une aile d'avion, la trajectoire d'un personnage de jeu vidéo, le ralentissement d'un menu qui s'ouvre sur un téléphone : derrière tout cela, on trouve la même idée. On donne quelques points, appelés points de contrôle, et un calcul très simple en déduit une courbe lisse, que l'on modifie en déplaçant ces points.

Ces courbes ont été mises au point dans les années 1960 par deux ingénieurs de l'automobile, Pierre Bézier chez Renault et Paul de Casteljau chez Citroën. Pour les comprendre, on a besoin de mathématiques de terminale : vecteurs, dérivation et étude de fonctions, récurrence, dénombrement et loi binomiale. Ce texte est construit comme un devoir : on avance pas à pas, avec des calculs à la main avant chaque cas général. À chacun d'y choisir une direction et d'y construire sa question.

Sommaire
  1. Un problème d'ingénieur
  2. Avec trois points
  3. Avec quatre points
  4. Étudier la courbe : dérivées et tangentes
  5. L'algorithme de de Casteljau, démontré
  6. Les polynômes de Bernstein et la loi binomiale
  7. Raccorder des morceaux : l'outil plume
  8. Des polices de caractères aux animations
  9. Bézier, de Casteljau et le secret industriel
  10. Des directions pour construire sa question
Un problème d'ingénieur

Au début des années 1960, une carrosserie naît sur la planche à dessin d'un styliste. Il faut ensuite passer du dessin à une maquette, puis aux outils qui emboutissent la tôle. À chaque étape, on recopie des courbes à la main, par des relevés de cotes, et de petites erreurs s'accumulent : deux pièces censées se raccorder ne se raccordent plus, et l'on corrige au marteau ou à la soudure à l'étain. Plus la forme est complexe, plus les retouches coûtent cher.

Pierre Bézier, ingénieur chez Renault, propose de faire table rase : décrire chaque courbe par des nombres, conservés dans un ordinateur, qui pilote directement une machine à dessiner et une fraiseuse. La même description sert à toutes les étapes, et aucune recopie n'est plus nécessaire. Il faut alors répondre à deux exigences qui semblent contradictoires :

L'ordinateur dont dispose l'équipe, installé en 1968, est petit. Calculer un sinus ou un cosinus lui demande trop d'opérations (voir le texte Comment est calculé un sinus ?) ; les seules fonctions qu'il évalue rapidement sont les polynômes, qui ne demandent que des additions et des multiplications. La réponse de Bézier tient en une phrase : on se donne quelques points, et chaque point de la courbe est une moyenne pondérée de ces points, avec des poids qui sont des polynômes. C'est ce que nous allons construire.

Avec trois points Un point qui glisse sur un segment

Tout repose sur une seule opération. Soient $A$ et $B$ deux points et $t$ un réel de $[0\,;1]$. On place le point $P$ du segment $[AB]$ tel que

$$\overrightarrow{AP} = t\,\overrightarrow{AB}.$$

Pour $t = 0$, $P$ est en $A$ ; pour $t = 1$, $P$ est en $B$ ; pour $t = \frac{1}{2}$, c'est le milieu. Avec un point $O$ quelconque (l'origine du repère), la relation de Chasles donne

$$\begin{aligned} \overrightarrow{OP} &= \overrightarrow{OA} + t\,\overrightarrow{AB}\\ &= \overrightarrow{OA} + t\left(\overrightarrow{OB} - \overrightarrow{OA}\right)\\ &= (1-t)\,\overrightarrow{OA} + t\,\overrightarrow{OB}. \end{aligned}$$

Les coordonnées de $P$ sont donc $(1-t)$ fois celles de $A$ plus $t$ fois celles de $B$. Les deux coefficients $1-t$ et $t$ sont positifs et leur somme vaut $1$ : $P$ est une moyenne pondérée de $A$ et $B$ (on dit aussi un barycentre). Pour alléger, on écrira simplement

$$P = (1-t)\,A + t\,B,$$

en sous-entendant qu'il s'agit d'une égalité entre coordonnées.

À la main, pour le milieu

Prenons trois points $A_0(0\,;0)$, $A_1(2\,;4)$ et $A_2(4\,;0)$. On construit le point de paramètre $t = \frac{1}{2}$ en deux étages :

  1. on prend le milieu $P_0$ de $[A_0A_1]$, soit $P_0(1\,;2)$, et le milieu $P_1$ de $[A_1A_2]$, soit $P_1(3\,;2)$ ;
  2. on prend le milieu $M$ de $[P_0P_1]$, soit $M(2\,;2)$.

Le point $M$ est le point de la courbe obtenu pour $t = \frac{1}{2}$. Il n'est pas sur le point $A_1$ : la courbe est « attirée » par $A_1$ sans l'atteindre. Avec $t = \frac{1}{4}$, on remplace les milieux par les points situés au quart de chaque segment : $P_0(0{,}5\,;1)$, $P_1(2{,}5\,;3)$, puis $M = \frac{3}{4}P_0 + \frac{1}{4}P_1$, soit $M(1\,;1{,}5)$.

Le cas général

Pour un réel $t$ quelconque de $[0\,;1]$, on pose

$$P_0 = (1-t)A_0 + tA_1,$$ $$P_1 = (1-t)A_1 + tA_2,$$ $$M = (1-t)P_0 + tP_1.$$

On remplace $P_0$ et $P_1$ par leurs expressions dans $M$ et on regroupe : $A_0$ reçoit le coefficient $(1-t)\times(1-t) = (1-t)^2$, $A_1$ reçoit $(1-t)t + t(1-t) = 2t(1-t)$, et $A_2$ reçoit $t\times t = t^2$. Donc

$$M = (1-t)^2A_0 + 2t(1-t)A_1 + t^2A_2.$$

Les trois poids $(1-t)^2$, $2t(1-t)$ et $t^2$ sont positifs sur $[0\,;1]$ et leur somme vaut $\big((1-t)+t\big)^2 = 1$ : $M$ est encore une moyenne pondérée des points de contrôle. Quand $t$ parcourt $[0\,;1]$, $M$ décrit une courbe de Bézier de degré 2. On vérifie avec $t = \frac{1}{4}$ : les poids valent $\frac{9}{16}$, $\frac{6}{16}$ et $\frac{1}{16}$, et l'abscisse vaut $\frac{6}{16}\times 2 + \frac{1}{16}\times 4 = 1$, l'ordonnée $\frac{6}{16}\times 4 = 1{,}5$, comme à la main.

Quelle courbe obtient-on ? Avec nos trois points, les coordonnées de $M$ sont

$$x(t) = 2\times 2t(1-t) + 4t^2 = 4t,$$ $$y(t) = 4\times 2t(1-t) = 8t(1-t).$$

Comme $t = \frac{x}{4}$, on obtient $y = 8\times\frac{x}{4}\left(1-\frac{x}{4}\right) = \frac{x(4-x)}{2}$ : c'est un arc de parabole. C'est un fait général : une courbe de Bézier de degré 2 est toujours un morceau de parabole (voir le texte La parabole et son foyer).

Avec quatre points Trois étages de segments

Une parabole ne peut pas changer de sens de courbure : elle ne sait pas dessiner un « S ». Bézier utilise donc quatre points de contrôle $A_0$, $A_1$, $A_2$, $A_3$. La courbe part de $A_0$, est attirée par $A_1$ puis par $A_2$, et arrive en $A_3$. On recommence la même construction, avec un étage de plus :

  1. sur les trois segments $[A_0A_1]$, $[A_1A_2]$, $[A_2A_3]$, on place $P_0$, $P_1$ et $P_2$ avec le même rapport $t$ ;
  2. sur $[P_0P_1]$ et $[P_1P_2]$, on place $Q_0$ et $Q_1$ ;
  3. sur $[Q_0Q_1]$, on place le point $M$ de la courbe.

À la main. Prenons $A_0(0\,;0)$, $A_1(1\,;3)$, $A_2(4\,;3)$, $A_3(5\,;0)$ et $t = \frac{1}{2}$.

ÉtagePoints (milieux)
1$P_0(0{,}5\,;1{,}5)$, $P_1(2{,}5\,;3)$, $P_2(4{,}5\,;1{,}5)$
2$Q_0(1{,}5\,;2{,}25)$, $Q_1(3{,}5\,;2{,}25)$
3$M(2{,}5\,;2{,}25)$

La figure ci-dessous refait cette construction pour toutes les valeurs de $t$ : déplacez le curseur, puis déplacez les points de contrôle bleus avec le doigt ou la souris.

var opt = {fontSize: 12, useKatex: false, useMathJax: false, parse: false, fixed: true, highlight: false}; function nombre(x, d) { return x.toFixed(d).replace('.', ','); } var st = board.create('slider', [[0.1, -1.2], [3.1, -1.2], [0, 0.5, 1]], {withLabel: false, snapWidth: 0.01}); board.create('text', [3.45, -1.2, function () { return 't = ' + nombre(st.Value(), 2); }], opt); var depart = [[0, 0], [1, 3], [4, 3], [5, 0]]; var A = []; for (var i = 0; i < 4; i++) { A.push(board.create('point', depart[i], {name: 'A' + i, size: 5, layer: 10, fillColor: '#1565c0', strokeColor: '#0d47a1', label: {offset: [8, 10], fontSize: 13, useKatex: false, parse: false}})); } function interp(p, q) { return [function () { var t = st.Value(); return (1 - t) * p.X() + t * q.X(); }, function () { var t = st.Value(); return (1 - t) * p.Y() + t * q.Y(); }]; } var styleP = {name: '', size: 2.5, fillColor: '#2e7d32', strokeColor: '#2e7d32', fixed: true, highlight: false, showInfobox: false}; var styleQ = {name: '', size: 2.5, fillColor: '#ef6c00', strokeColor: '#ef6c00', fixed: true, highlight: false, showInfobox: false}; for (var i = 0; i < 3; i++) { board.create('segment', [A[i], A[i + 1]], {strokeColor: '#90a4ae', strokeWidth: 1.2, dash: 2, fixed: true, highlight: false}); } var P = [interp(A[0], A[1]), interp(A[1], A[2]), interp(A[2], A[3])].map(function (c) { return board.create('point', c, styleP); }); board.create('segment', [P[0], P[1]], {strokeColor: '#2e7d32', strokeWidth: 1.2, fixed: true, highlight: false}); board.create('segment', [P[1], P[2]], {strokeColor: '#2e7d32', strokeWidth: 1.2, fixed: true, highlight: false}); var Q = [interp(P[0], P[1]), interp(P[1], P[2])].map(function (c) { return board.create('point', c, styleQ); }); board.create('segment', [Q[0], Q[1]], {strokeColor: '#ef6c00', strokeWidth: 1.2, fixed: true, highlight: false}); var M = board.create('point', interp(Q[0], Q[1]), {name: 'M', size: 4, fillColor: '#b71c1c', strokeColor: '#b71c1c', fixed: true, highlight: false, showInfobox: false, label: {offset: [6, -12], fontSize: 13, useKatex: false, parse: false}}); function bx(t) { var u = 1 - t; return u*u*u*A[0].X() + 3*t*u*u*A[1].X() + 3*t*t*u*A[2].X() + t*t*t*A[3].X(); } function by(t) { var u = 1 - t; return u*u*u*A[0].Y() + 3*t*u*u*A[1].Y() + 3*t*t*u*A[2].Y() + t*t*t*A[3].Y(); } board.create('curve', [bx, by, 0, 1], {strokeColor: '#ef9a9a', strokeWidth: 1.5, highlight: false}); board.create('curve', [bx, by, 0, function () { return st.Value(); }], {strokeColor: '#b71c1c', strokeWidth: 3, highlight: false}); board.create('text', [-0.55, -1.65, function () { return 'M(' + nombre(M.X(), 2) + ' ; ' + nombre(M.Y(), 2) + ')'; }], opt); Construction de de Casteljau. En pointillés, le polygone des points de contrôle ; en vert, le premier étage ; en orange, le deuxième ; en rouge, le point $M$ et la partie de la courbe déjà tracée. Les points bleus se déplacent.
La formule

Comme avec trois points, on remplace chaque étage par son expression. Le deuxième étage d'abord, avec le calcul fait pour trois points :

$$Q_0 = (1-t)^2A_0 + 2t(1-t)A_1 + t^2A_2,$$ $$Q_1 = (1-t)^2A_1 + 2t(1-t)A_2 + t^2A_3.$$

Puis $M = (1-t)Q_0 + tQ_1$. On regroupe les termes en $A_0$, $A_1$, $A_2$, $A_3$ :

  • $A_0$ n'apparaît que dans $Q_0$ : coefficient $(1-t)^3$ ;
  • $A_1$ apparaît dans les deux : $(1-t)\times 2t(1-t)$ $+\; t(1-t)^2$ $= 3t(1-t)^2$ ;
  • $A_2$ : $(1-t)t^2$ $+\; t\times 2t(1-t)$ $= 3t^2(1-t)$ ;
  • $A_3$ n'apparaît que dans $Q_1$ : coefficient $t^3$.

D'où la formule des courbes de Bézier de degré 3, celles que Bézier utilisait. En notant $b_0 = (1-t)^3$, $b_1 = 3t(1-t)^2$, $b_2 = 3t^2(1-t)$ et $b_3 = t^3$ :

$$M = b_0A_0 + b_1A_1 + b_2A_2 + b_3A_3.$$

En développant les poids, on retrouve les quatre polynômes que l'on attribue à Bézier : $-t^3 + 3t^2 - 3t + 1$, $3t^3 - 6t^2 + 3t$, $-3t^3 + 3t^2$ et $t^3$. On vérifie la formule sur l'exemple, avec $t = \frac{1}{2}$ : les poids valent $\frac{1}{8}$, $\frac{3}{8}$, $\frac{3}{8}$, $\frac{1}{8}$, donc

$$x_M = \frac{0 + 3 + 12 + 5}{8} = 2{,}5,$$ $$y_M = \frac{0 + 9 + 9 + 0}{8} = 2{,}25,$$

exactement le point trouvé avec les milieux.

La courbe reste dans le polygone

Les quatre poids sont positifs sur $[0\,;1]$ et leur somme vaut $\big((1-t)+t\big)^3 = 1$. Conséquence : chaque coordonnée de $M$ est comprise entre la plus petite et la plus grande coordonnée correspondante des points de contrôle. Par exemple $0 \leqslant y_M \leqslant 3$ sur notre exemple. Plus précisément, la courbe reste dans le plus petit polygone convexe qui contient les points de contrôle. C'est précieux pour un ingénieur : en regardant les points de contrôle, on sait d'avance dans quelle zone la courbe se trouvera, sans rien calculer.

Étudier la courbe : dérivées et tangentes Les deux fonctions coordonnées

Avec les quatre points de l'exemple, la formule donne, pour $t\in[0\,;1]$ :

$$\begin{aligned} x(t) &= 3t(1-t)^2 + 12t^2(1-t) + 5t^3\\ &= 3t + 6t^2 - 4t^3, \end{aligned}$$ $$\begin{aligned} y(t) &= 9t(1-t)^2 + 9t^2(1-t)\\ &= 9t(1-t)\big[(1-t) + t\big]\\ &= 9t(1-t). \end{aligned}$$

On étudie chacune comme une fonction de terminale.

  • $x'(t) = 3 + 12t - 12t^2$ $= 3 + 12t(1-t)$. Sur $[0\,;1]$, $t(1-t) \geqslant 0$, donc $x'(t) \geqslant 3 > 0$ : $x$ est strictement croissante, de $x(0) = 0$ à $x(1) = 5$. Le point avance toujours vers la droite.
  • $y'(t) = 9 - 18t$, positive sur $\left[0\,;\frac{1}{2}\right]$ et négative sur $\left[\frac{1}{2}\,;1\right]$ : $y$ croît de $0$ à $y\left(\frac{1}{2}\right) = \frac{9}{4}$, puis redescend à $0$.
$t$$0$$1$ $x'(t)$$+$ $5$ $x$increasing $0$ $t$$0$$\frac{1}{2}$$1$ $y'(t)$$+$bar0$-$ $\frac{9}{4}$ $y$increasingdecreasing $0$$0$

Le point le plus haut de la courbe est donc $M(2{,}5\,;2{,}25)$, obtenu pour $t = \frac{1}{2}$. Il est nettement plus bas que les points $A_1$ et $A_2$ (d'ordonnée $3$) : la courbe est attirée par eux sans les atteindre.

La direction du mouvement

Pour un petit nombre $h$, le nombre dérivé donne $x(t+h) \approx x(t) + h\,x'(t)$ et $y(t+h) \approx y(t) + h\,y'(t)$. Le point se déplace donc presque selon le vecteur de coordonnées $\big(h\,x'(t)\,;h\,y'(t)\big)$. On admet que le vecteur $\vec{v}(t)$ de coordonnées $\big(x'(t)\,;y'(t)\big)$, s'il n'est pas nul, dirige la tangente à la courbe au point de paramètre $t$. En physique, c'est le vecteur vitesse d'un mobile dont $t$ serait le temps.

Aux extrémités, sur l'exemple. $\vec{v}(0)$ a pour coordonnées $(3\,;9)$, c'est-à-dire $3\,\overrightarrow{A_0A_1}$ puisque $\overrightarrow{A_0A_1}(1\,;3)$. De même $\vec{v}(1)$ a pour coordonnées $(3\,;-9)$ $= 3\,\overrightarrow{A_2A_3}$.

Dans le cas général. Dérivons les poids : on note $b_0(t) = (1-t)^3$, $b_1(t) = 3t(1-t)^2$, $b_2(t) = 3t^2(1-t)$ et $b_3(t) = t^3$.

  • $b_0'(t) = -3(1-t)^2$, donc $b_0'(0) = -3$ ;
  • $b_1'(t) = 3(1-t)^2 - 6t(1-t)$, donc $b_1'(0) = 3$ ;
  • $b_2'(t) = 6t(1-t) - 3t^2$ et $b_3'(t) = 3t^2$ s'annulent en $0$.

Comme $x(t) = \sum_{k=0}^{3} b_k(t)\,x_k$, on obtient $x'(0) = 3(x_1 - x_0)$, et de même $y'(0) = 3(y_1 - y_0)$. Donc

$$\vec{v}(0) = 3\,\overrightarrow{A_0A_1}$$

et, de même avec les dérivées en $1$,

$$\vec{v}(1) = 3\,\overrightarrow{A_2A_3}.$$

La courbe part de $A_0$ en direction de $A_1$, et arrive en $A_3$ en venant de la direction de $A_2$. C'est ce qui rend la méthode si commode : les deux points intermédiaires sont des « poignées » qui règlent les tangentes au départ et à l'arrivée. Plus on éloigne une poignée, plus la vitesse au départ est grande, et plus la courbe « colle » longtemps à cette direction.

L'algorithme de de Casteljau, démontré L'algorithme pour $n + 1$ points

Avec $n + 1$ points de contrôle $A_0$, …, $A_n$, on recommence le même geste autant de fois qu'il le faut : chaque étage remplace une liste de points par la liste, plus courte d'un point, des points placés au rapport $t$ sur les segments consécutifs. Après $n$ étages, il ne reste qu'un point : c'est le point $M$ de la courbe. Paul de Casteljau utilisait cette construction chez Citroën dès la fin des années 1950 ; elle se fait à la règle et ne demande que des additions et des multiplications.

Animation : quatre points de contrôle reliés par des segments gris ; des points verts glissent sur ces segments, des points bleus sur les segments verts, et un point noir trace une courbe rouge. La construction de de Casteljau pour quatre points, animée. Animation : Phil Tregoning, Wikimedia Commons, domaine public.

Pour trois et quatre points, on a vu que le résultat est une moyenne pondérée des points de contrôle, avec les poids $1$, $2$, $1$ puis $1$, $3$, $3$, $1$ devant les puissances de $t$ et de $1-t$. On reconnaît des lignes du triangle de Pascal. Démontrons-le pour tout $n$.

L'énoncé Propriété

Pour tout entier $n \geqslant 1$, pour tous points $A_0$, …, $A_n$ et tout réel $t$ de $[0\,;1]$, le point $M$ donné par l'algorithme de de Casteljau vérifie

$$M = \sum_{k=0}^{n} \binom{n}{k}\,t^k(1-t)^{n-k}\,A_k.$$

On note $\mathcal{P}(n)$ cette propriété et on la démontre par récurrence.

La démonstration par récurrence

Initialisation. Pour $n = 1$, l'algorithme fait un seul étage : $M = (1-t)A_0 + tA_1$. La formule donne $\binom{1}{0}(1-t)A_0 + \binom{1}{1}tA_1$, c'est la même chose. $\mathcal{P}(1)$ est vraie.

Hérédité. Supposons $\mathcal{P}(n)$ vraie pour un entier $n \geqslant 1$, et prenons $n + 2$ points $A_0$, …, $A_{n+1}$.

Étape 1 : une remarque sur l'algorithme. Dans le schéma en triangle de l'algorithme, le dernier point $M$ est obtenu à partir des deux points de l'avant-dernier étage, $M = (1-t)M' + tM''$. Le point $M'$ ne dépend que des points $A_0$, …, $A_n$ (il est obtenu en appliquant l'algorithme à ces $n + 1$ points), et $M''$ ne dépend que de $A_1$, …, $A_{n+1}$. On le voit sur la figure à quatre points : $Q_0$ ne dépend que de $A_0$, $A_1$, $A_2$, et $Q_1$ que de $A_1$, $A_2$, $A_3$.

Étape 2 : on applique l'hypothèse de récurrence à ces deux paquets de $n + 1$ points :

$$M' = \sum_{k=0}^{n} \binom{n}{k}t^k(1-t)^{n-k}A_k,$$ $$M'' = \sum_{j=0}^{n} \binom{n}{j}t^j(1-t)^{n-j}A_{j+1}.$$

Étape 3 : on cherche le coefficient de $A_k$ dans $M = (1-t)M' + tM''$, pour $1 \leqslant k \leqslant n$. Dans $M'$ il vaut $\binom{n}{k}t^k(1-t)^{n-k}$, qu'on multiplie par $1-t$. Dans $M''$, $A_k$ correspond à $j = k-1$ : le coefficient vaut $\binom{n}{k-1}t^{k-1}(1-t)^{n-k+1}$, qu'on multiplie par $t$. On note $c = t^k(1-t)^{n+1-k}$ ; au total, le coefficient de $A_k$ vaut

$$\begin{aligned} &\binom{n}{k}c + \binom{n}{k-1}c\\ &= \left[\binom{n}{k} + \binom{n}{k-1}\right]c. \end{aligned}$$

Étape 4 : la relation de Pascal $\binom{n}{k-1} + \binom{n}{k} = \binom{n+1}{k}$ donne le coefficient $\binom{n+1}{k}t^k(1-t)^{n+1-k}$.

Étape 5 : les deux extrémités. $A_0$ n'apparaît que dans $M'$ : coefficient $(1-t)\times(1-t)^n = (1-t)^{n+1}$, qui est bien $\binom{n+1}{0}t^0(1-t)^{n+1}$. De même, $A_{n+1}$ n'apparaît que dans $M''$, avec le coefficient $t\times t^n = t^{n+1}$.

Ainsi $M = \sum_{k=0}^{n+1}\binom{n+1}{k}t^k(1-t)^{n+1-k}A_k$ : $\mathcal{P}(n+1)$ est vraie.

Conclusion. $\mathcal{P}(1)$ est vraie et la propriété est héréditaire : elle est vraie pour tout $n \geqslant 1$. L'algorithme géométrique de de Casteljau et la formule algébrique de Bézier donnent la même courbe.

Le triangle de Pascal (voir le texte Le triangle de Pascal) apparaît ainsi deux fois : dans les coefficients, et dans la forme même de l'algorithme, qui construit un triangle de points étage par étage.

Vérifier en Python

Le programme suivant calcule des points de la courbe des deux façons, par l'algorithme (une boucle qui raccourcit la liste) et par la formule, sur l'exemple à quatre points.

def casteljau(points, t): # chaque passage remplace la liste par les points au rapport t while len(points) > 1: nouveaux = [] for i in range(len(points) - 1): (xa, ya), (xb, yb) = points[i], points[i + 1] nouveaux.append(((1 - t) * xa + t * xb, (1 - t) * ya + t * yb)) points = nouveaux return points[0] def binom(n, k): c = 1 for i in range(k): c = c * (n - i) // (i + 1) return c def formule(points, t): n = len(points) - 1 x, y = 0, 0 for k in range(n + 1): poids = binom(n, k) * t**k * (1 - t)**(n - k) x = x + poids * points[k][0] y = y + poids * points[k][1] return (x, y) A = [(0, 0), (1, 3), (4, 3), (5, 0)] for t in [0, 0.25, 0.5, 0.75, 1]: xc, yc = casteljau(A, t) xf, yf = formule(A, t) print("t =", t, " algorithme :", round(xc, 4), round(yc, 4), " formule :", round(xf, 4), round(yf, 4))

Pour $t = 0{,}25$, les deux calculs donnent $(1{,}0625\,;1{,}6875)$ ; on le retrouve avec $x(t) = 3t + 6t^2 - 4t^3$ et $y(t) = 9t(1-t)$. En informatique, on préfère souvent l'algorithme : il est très stable vis-à-vis des erreurs d'arrondi (voir La théorie des erreurs), et il permet de couper une courbe en deux morceaux : les points $A_0$, $P_0$, $Q_0$, $M$ sont les points de contrôle de la première moitié, et $M$, $Q_1$, $P_2$, $A_3$ ceux de la seconde.

Les polynômes de Bernstein et la loi binomiale Les poids ont un nom

Les poids qui apparaissent dans la formule s'appellent les polynômes de Bernstein : pour $n$ entier et $0 \leqslant k \leqslant n$,

$$B_k^n(t) = \binom{n}{k}\,t^k(1-t)^{n-k}.$$

Ils portent le nom du mathématicien russe Sergueï Bernstein, qui les a introduits en 1912, bien avant les ordinateurs, pour démontrer qu'on peut approcher n'importe quelle fonction continue sur $[0\,;1]$ par des polynômes. Sa démonstration utilisait… le calcul des probabilités.

Une lecture probabiliste

Soit $X$ une variable aléatoire qui suit la loi binomiale de paramètres $n$ et $t$ : le nombre de succès quand on répète $n$ fois, de façon indépendante, une épreuve qui réussit avec la probabilité $t$. Le cours de terminale donne

$$P(X = k) = \binom{n}{k}t^k(1-t)^{n-k} = B_k^n(t).$$

Le polynôme de Bernstein $B_k^n(t)$ est exactement la probabilité d'obtenir $k$ succès. On en déduit sans calcul trois propriétés.

  1. Les poids sont positifs, ce sont des probabilités.
  2. Leur somme vaut $1$, puisque $X$ prend forcément l'une des valeurs $0$, …, $n$ : $$\sum_{k=0}^{n} B_k^n(t) = 1.$$ C'est aussi la formule du binôme de Newton appliquée à $\big(t + (1-t)\big)^n = 1^n = 1$.
  3. Le point de la courbe est une espérance. Tirons un indice $X$ au hasard selon cette loi binomiale et regardons le point de contrôle $A_X$. L'abscisse moyenne de ce point aléatoire est $\sum_k P(X = k)\,x_k = x_M$. Le point $M$ de la courbe est le point moyen de $A_X$.

Cette lecture explique la forme de la courbe. Quand $t$ est proche de $0$, $X$ vaut presque toujours $0$ et $M$ est près de $A_0$. Quand $t$ augmente, l'espérance $E(X) = nt$ augmente, et ce sont les points de contrôle d'indice proche de $nt$ qui « pèsent » le plus. Les points de contrôle attirent la courbe chacun à leur tour.

Une conséquence. Si les abscisses des points de contrôle sont régulièrement espacées, $x_k = \frac{k}{n}$, alors $x_M = \sum_k P(X=k)\frac{k}{n} = \frac{E(X)}{n} = \frac{nt}{n} = t$. L'abscisse du point de paramètre $t$ est alors $t$ lui-même : la courbe est le graphe de la fonction $t \mapsto \sum_k B_k^n(t)\,y_k$. C'est la construction de Bernstein : pour approcher une fonction $f$ continue, il prend $y_k = f\left(\frac{k}{n}\right)$. Plus $n$ est grand, plus il est probable que la fréquence $\frac{X}{n}$ soit proche de $t$ (loi des grands nombres), et plus la moyenne des $f\left(\frac{X}{n}\right)$ est proche de $f(t)$.

Où chaque poids est-il le plus grand ?

Pour $1 \leqslant k \leqslant n - 1$, dérivons $g(t) = t^k(1-t)^{n-k}$ sur $[0\,;1]$ (le coefficient binomial ne change pas les variations). Avec la dérivée d'un produit, et en notant $h(t) = t^{k-1}(1-t)^{n-k-1}$ :

Avec $u(t) = t^k$ et $v(t) = (1-t)^{n-k}$, on a $u'(t)v(t) = kt^{k-1}(1-t)^{n-k}$ $= k\,h(t)(1-t)$ et $u(t)v'(t)$ $= -(n-k)t^k(1-t)^{n-k-1}$ $= -(n-k)\,h(t)\,t$. Donc

$$\begin{aligned} g'(t) &= h(t)\big[k(1-t) - (n-k)t\big]\\ &= h(t)\,(k - nt). \end{aligned}$$

Sur $]0\,;1[$, le facteur $h(t)$ est strictement positif : $g'(t)$ a le signe de $k - nt$, positif avant $\frac{k}{n}$ et négatif après. Le poids $B_k^n$ est maximal en $t = \frac{k}{n}$ : c'est à ce moment-là que le point $A_k$ attire le plus la courbe. La figure montre les $n + 1$ polynômes ; pour chaque valeur de $t$, leurs valeurs s'ajoutent pour faire $1$.

var opt = {fontSize: 12, useKatex: false, useMathJax: false, parse: false, fixed: true, highlight: false}; function nombre(x, d) { return x.toFixed(d).replace('.', ','); } var sn = board.create('slider', [[0.05, -0.19], [0.62, -0.19], [1, 3, 8]], {withLabel: false, snapWidth: 1}); board.create('text', [0.7, -0.19, function () { return 'n = ' + Math.round(sn.Value()); }], opt); var st = board.create('slider', [[0.05, -0.31], [0.62, -0.31], [0, 0.3, 1]], {withLabel: false, snapWidth: 0.01}); board.create('text', [0.7, -0.31, function () { return 't = ' + nombre(st.Value(), 2); }], opt); board.create('segment', [[0, 0], [1.03, 0]], {strokeColor: '#455a64', strokeWidth: 1, fixed: true, highlight: false, lastArrow: {size: 5}}); board.create('segment', [[0, 0], [0, 1.08]], {strokeColor: '#455a64', strokeWidth: 1, fixed: true, highlight: false, lastArrow: {size: 5}}); board.create('segment', [[-0.015, 1], [0.015, 1]], {strokeColor: '#455a64', strokeWidth: 1, fixed: true, highlight: false}); board.create('segment', [[1, -0.015], [1, 0.015]], {strokeColor: '#455a64', strokeWidth: 1, fixed: true, highlight: false}); board.create('text', [-0.075, 1.0, '1'], opt); board.create('text', [-0.04, -0.05, '0'], opt); board.create('text', [0.985, -0.05, '1'], opt); var couleurs = ['#1565c0', '#b71c1c', '#2e7d32', '#ef6c00', '#6a1b9a', '#00838f', '#8d5524', '#c2185b', '#455a64']; function binom(n, k) { var c = 1; for (var i = 0; i < k; i++) { c = c * (n - i) / (i + 1); } return c; } function poids(n, k, t) { return binom(n, k) * Math.pow(t, k) * Math.pow(1 - t, n - k); } function courbe(k) { board.create('curve', [function (t) { return t; }, function (t) { var n = Math.round(sn.Value()); return k > n ? NaN : poids(n, k, t); }, 0, 1], {strokeColor: couleurs[k], strokeWidth: 2, highlight: false}); board.create('point', [function () { return st.Value(); }, function () { var n = Math.round(sn.Value()); return k > n ? NaN : poids(n, k, st.Value()); }], {name: '', size: 2.5, fillColor: couleurs[k], strokeColor: couleurs[k], fixed: true, highlight: false, showInfobox: false}); } for (var k = 0; k <= 8; k++) { courbe(k); } board.create('segment', [[function () { return st.Value(); }, 0], [function () { return st.Value(); }, 1.02]], {strokeColor: '#90a4ae', dash: 2, strokeWidth: 1, fixed: true, highlight: false}); board.create('text', [0.02, 1.13, function () { var n = Math.round(sn.Value()), t = st.Value(), s = 0, kmax = 0, m = -1; for (var k = 0; k <= n; k++) { var p = poids(n, k, t); s += p; if (p > m) { m = p; kmax = k; } } return 'somme des poids : ' + nombre(s, 3) + ' ; le plus grand : B' + kmax; }], opt); Les polynômes de Bernstein $B_0^n$, …, $B_n^n$ sur $[0\,;1]$. Le curseur $n$ fixe le degré, le curseur $t$ déplace la verticale en pointillés : les points colorés sont les poids des points de contrôle, et leur somme vaut toujours $1$.
Raccorder des morceaux : l'outil plume

Pour dessiner une forme compliquée, comme une lettre ou le profil d'une voiture, on n'utilise pas une seule courbe de degré élevé : elle serait difficile à régler, car chaque point de contrôle influence toute la courbe. On met bout à bout des courbes de degré 3, chacune avec ses quatre points. Le dernier point d'un morceau est le premier du suivant.

Encore faut-il que la jonction ne fasse pas d'angle. Soit un premier morceau de points $A_0$, $A_1$, $A_2$, $J$ et un second de points $J$, $B_1$, $B_2$, $B_3$. D'après l'étude des tangentes, le premier morceau arrive en $J$ avec le vecteur vitesse $3\,\overrightarrow{A_2J}$, et le second en repart avec $3\,\overrightarrow{JB_1}$.

C'est exactement ce que fait l'outil plume des logiciels de dessin (Illustrator, Inkscape, Photoshop, Figma…) : quand on clique pour poser un point et qu'on tire la souris, on fait sortir deux « poignées » symétriques par rapport au point posé. Ces poignées sont les points $A_2$ et $B_1$. Dans la figure, $B_1$ est calculé comme le symétrique de $A_2$ par rapport à $J$ : déplacez les points bleus et le point $J$, la jonction reste lisse.

var styleA = {size: 5, fillColor: '#1565c0', strokeColor: '#0d47a1', label: {offset: [8, 10], fontSize: 13, useKatex: false, parse: false}}; var A0 = board.create('point', [0, 0], Object.assign({name: 'A0'}, styleA)); var A1 = board.create('point', [0.5, 3], Object.assign({name: 'A1'}, styleA)); var A2 = board.create('point', [2.6, 3.3], Object.assign({name: 'A2'}, styleA)); var J = board.create('point', [3.5, 1.8], {name: 'J', size: 5, fillColor: '#b71c1c', strokeColor: '#7f0000', label: {offset: [10, 4], fontSize: 13, useKatex: false, parse: false}}); var B1 = board.create('point', [function () { return 2 * J.X() - A2.X(); }, function () { return 2 * J.Y() - A2.Y(); }], {name: 'B1', size: 4, fillColor: '#ffffff', strokeColor: '#1565c0', fixed: true, highlight: false, showInfobox: false, label: {offset: [8, -12], fontSize: 13, useKatex: false, parse: false}}); var B2 = board.create('point', [6.4, -0.6], Object.assign({name: 'B2'}, styleA)); var B3 = board.create('point', [7, 2.6], Object.assign({name: 'B3'}, styleA)); var g = {strokeColor: '#90a4ae', strokeWidth: 1.2, dash: 2, fixed: true, highlight: false}; board.create('segment', [A0, A1], g); board.create('segment', [A2, B1], {strokeColor: '#78909c', strokeWidth: 1.5, fixed: true, highlight: false}); board.create('segment', [B2, B3], g); function cub(P0, P1, P2, P3, c) { return function (t) { var u = 1 - t; var v = [P0, P1, P2, P3].map(function (p) { return c === 0 ? p.X() : p.Y(); }); return u*u*u*v[0] + 3*t*u*u*v[1] + 3*t*t*u*v[2] + t*t*t*v[3]; }; } board.create('curve', [cub(A0, A1, A2, J, 0), cub(A0, A1, A2, J, 1), 0, 1], {strokeColor: '#b71c1c', strokeWidth: 3, highlight: false}); board.create('curve', [cub(J, B1, B2, B3, 0), cub(J, B1, B2, B3, 1), 0, 1], {strokeColor: '#2e7d32', strokeWidth: 3, highlight: false}); Deux courbes de degré 3 raccordées en $J$ : le premier morceau en rouge, le second en vert. La « poignée » grise $[A_2B_1]$ est centrée sur $J$, comme dans l'outil plume : la tangente et la vitesse sont les mêmes des deux côtés.
Des polices de caractères aux animations Les lettres de l'écran

Une police de caractères moderne ne stocke pas des images des lettres, mais leurs contours, sous forme de morceaux de droites et de courbes de Bézier. C'est ce qui permet d'afficher la même lettre nette à toutes les tailles, d'une note de bas de page à une affiche : on multiplie les coordonnées des points de contrôle par l'échelle voulue, et l'on recalcule la courbe. Cela fonctionne parce qu'une courbe de Bézier se transforme bien : l'image par un agrandissement (ou une rotation, une translation) de la courbe est la courbe de Bézier des images des points de contrôle, puisque $M$ est une moyenne pondérée des points de contrôle.

  • Le langage PostScript d'Adobe (1984), puis le format PDF et les polices dites « Type 1 » et OpenType « CFF », utilisent des courbes de degré 3.
  • Le format TrueType, lancé par Apple au début des années 1990, utilise des courbes de degré 2 : des arcs de parabole, plus rapides à calculer, mais il en faut davantage pour une même lettre.
  • Le format d'images SVG du web propose les deux : dans un chemin, la commande Q trace une courbe de degré 2, et la commande C une courbe de degré 3 à partir de deux poignées et d'un point d'arrivée.

Quand l'ordinateur affiche la lettre, il doit transformer chaque courbe en pixels. L'algorithme de de Casteljau, appliqué avec $t = \frac{1}{2}$, coupe une courbe en deux moitiés dont il donne les points de contrôle. On recommence jusqu'à ce que chaque morceau soit presque droit (ses points de contrôle presque alignés), puis on trace des segments. C'est un algorithme récursif, un thème central en NSI.

Les animations : la courbe d'accélération

Sur un téléphone, une fenêtre qui s'ouvre ne se déplace pas à vitesse constante : elle démarre vite et ralentit en arrivant. Les feuilles de style du web (CSS) règlent ce mouvement par une courbe de Bézier de degré 3, notée cubic-bezier(x1, y1, x2, y2). Ses points de contrôle sont $A_0(0\,;0)$, $A_1(x_1\,;y_1)$, $A_2(x_2\,;y_2)$ et $A_3(1\,;1)$. L'abscisse représente la fraction de la durée écoulée, l'ordonnée la fraction du trajet parcourue. La courbe appelée ease, utilisée par défaut, est cubic-bezier(0.25, 0.1, 0.25, 1).

Avec ces points, la formule donne

$$x(t) = 0{,}75t - 0{,}75t^2 + t^3,$$ $$y(t) = 0{,}3t(1-t)^2 + 3t^2 - 2t^3.$$

Question : quelle fraction du trajet est faite à la moitié de la durée ? Il faut trouver $t$ tel que $x(t) = 0{,}5$, puis calculer $y(t)$.

  • $x'(t) = 3t^2 - 1{,}5t + 0{,}75$ a pour discriminant $2{,}25 - 9 < 0$ : $x'$ est toujours positive, $x$ est strictement croissante et continue sur $[0\,;1]$, de $0$ à $1$.
  • D'après le corollaire du théorème des valeurs intermédiaires, l'équation $x(t) = 0{,}5$ a une unique solution, que l'on approche par dichotomie (voir Approximation des solutions d'une équation).
def x(t): return 0.75 * t - 0.75 * t**2 + t**3 def y(t): return 0.3 * t * (1 - t)**2 + 3 * t**2 - 2 * t**3 def parametre(u): # dichotomie : x est croissante sur [0 ; 1] a, b = 0, 1 for etape in range(30): m = (a + b) / 2 if x(m) < u: a = m else: b = m return a for u in [0.25, 0.5, 0.75]: t = parametre(u) print("durée écoulée :", u, " t =", round(t, 4), " trajet fait :", round(y(t), 4))

Résultat : à la moitié de la durée, $t \approx 0{,}6996$ et $y \approx 0{,}802$ : l'élément a déjà parcouru 80 % de son trajet. Au quart de la durée, il en est à environ 41 %, aux trois quarts à 96 %. Le bouton ci-dessous lance trois billes sur la même durée, avec trois courbes différentes.

linear (vitesse constante)

ease, cubic-bezier(0.25, 0.1, 0.25, 1)

cubic-bezier(0.7, 0, 0.3, 1) (départ et arrivée en douceur)

Les trois billes partent et arrivent en même temps, mais pas de la même façon. La troisième a ses poignées horizontales : d'après l'étude des tangentes, sa vitesse est nulle au départ et à l'arrivée, d'où l'impression de douceur. Les jeux vidéo et les films d'animation règlent de la même façon le mouvement de leurs personnages entre deux positions clés.

Des courbes aux surfaces

Le but de Bézier était de décrire des surfaces de carrosserie. L'idée se prolonge avec deux paramètres $u$ et $v$ : on se donne une grille de $4\times 4$ points de contrôle. Pour une valeur de $u$, chacune des quatre lignes de la grille donne un point par la formule de degré 3 ; ces quatre points servent à leur tour de points de contrôle d'une courbe, parcourue avec le paramètre $v$. Quand $u$ et $v$ varient dans $[0\,;1]$, on obtient un « carreau » de surface, qu'on modifie en déplaçant les seize points. La modélisation en trois dimensions (conception assistée par ordinateur, impression 3D, images de synthèse) repose encore sur ces carreaux et sur leurs généralisations.

Bézier, de Casteljau et le secret industriel Pierre Bézier chez Renault Plaque de rue bleue : Boulogne-Billancourt, Passage Pierre Bézier, 1910-1999, directeur des méthodes mécaniques au sein de la Régie nationale des usines Renault. La plaque du passage Pierre-Bézier à Boulogne-Billancourt, où se trouvaient les usines Renault. Photo : Chabe01, Wikimedia Commons, licence CC BY-SA 4.0.

Pierre Bézier (1910-1999), ingénieur des Arts et Métiers et de Supélec, entre chez Renault en 1933 et y fait toute sa carrière. Après la guerre, il conçoit des machines-transfert qui déplacent automatiquement les pièces d'un poste d'usinage au suivant, une étape importante de l'automatisation de l'industrie. Mis à l'écart dans les années 1960, il lance presque seul, avec une petite équipe, le système de dessin et d'usinage numérique Unisurf, dont l'ordinateur est installé en 1968. Il soutient en 1977 une thèse de doctorat d'État sur la définition numérique des courbes et des surfaces.

Une anecdote, racontée par Bézier lui-même et rapportée avec des variantes : pour que ses collègues acceptent des formules venues d'un ingénieur maison, il les aurait présentées comme l'œuvre d'un mathématicien célèbre, un certain Onésime Durand… qui n'a jamais existé. Le nom de « courbes de Bézier » s'est imposé plus tard, d'abord dans les universités américaines et britanniques, où l'on a remarqué, au début des années 1970, que ses poids étaient les polynômes de Bernstein.

Paul de Casteljau chez Citroën

Paul de Casteljau (1930-2022), physicien et mathématicien de formation, entre chez Citroën en 1958 pour résoudre le même problème. Il met au point la construction par segments successifs et les « formes à pôles », décrites dans des rapports internes vers 1959 et 1963. Mais Citroën tient au secret : ses travaux restent confidentiels, et il ne publie un livre qu'en 1985. Bézier, qui avait publié ses travaux, a donné son nom aux courbes ; de Casteljau a donné le sien à l'algorithme. Les deux ingénieurs sont arrivés au même objet par deux chemins différents, l'un par une formule, l'autre par une construction géométrique, et la démonstration par récurrence de ce texte montre pourquoi ces deux chemins mènent au même endroit.

L'histoire illustre une tension réelle entre la recherche industrielle, qui protège ses découvertes pour garder un avantage sur ses concurrents, et la science, qui progresse en publiant. Elle montre aussi que des idées mathématiques importantes ne viennent pas toujours des universités.

Des directions pour construire sa question

Ce texte n'est pas un Grand Oral. Il rassemble des connaissances dans lesquelles chacun peut choisir une direction, la creuser et formuler sa propre question. Quelques directions possibles :

Pour l'échange avec le jury : les notions du cours à maîtriser

Le jury peut interroger sur les chapitres de spécialité mobilisés. Avec ce thème, il est utile de connaître parfaitement : les vecteurs, la relation de Chasles et la colinéarité ; la dérivation (produit, puissance) et l'étude des variations ; le raisonnement par récurrence ; les combinaisons, la relation de Pascal et le triangle de Pascal ; la loi binomiale et son espérance ; le théorème des valeurs intermédiaires et la dichotomie.

Pour réviser ces chapitres, l'application Maths FlashBac reprend tout le cours de spécialité en cartes.

Pour aller plus loin