Béziers, courbes et chemins

Les courbes de Bézier sont une approximation mathématique des formes géométriques naturelles. Nous les utilisons pour représenter une courbe avec le moins d'informations possible et avec un haut niveau de flexibilité.

Contrairement aux concepts mathématiques plus abstraits, les courbes de Bézier ont été créées pour le design industriel. Ils sont un outil populaire dans l'industrie des logiciels graphiques.

Ils s'appuient sur l'interpolation, que nous avons vu dans l'article précédent, combinant plusieurs étapes pour créer des courbes lisses. Pour mieux comprendre le fonctionnement des courbes de Bézier, partons de sa forme la plus simple : Bézier quadratique.

Bézier quadratique

Prenez trois points, le minimum requis pour que le Bézier quadratique fonctionne :

../../_images/bezier_quadratic_points.png

Pour tracer une courbe entre eux, nous interpolons d'abord progressivement sur les deux sommets de chacun des deux segments formés par les trois points, en utilisant des valeurs allant de 0 à 1, ce qui nous donne deux points qui se déplacent le long des segments à mesure que nous changeons la valeur de t de 0 à 1.

func _quadratic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, t: float):
    var q0 = p0.linear_interpolate(p1, t)
    var q1 = p1.linear_interpolate(p2, t)

Nous interpolons ensuite q0 et q1 pour obtenir un point unique r qui se déplace le long d'une courbe.

var r = q0.linear_interpolate(q1, t)
return r

Ce type de courbe s'appelle une courbe de Bézier quadratique.

../../_images/bezier_quadratic_points2.gif

(Crédit image : Wikipedia)

Bézier cubique

En nous basant sur l'exemple précédent, nous pouvons obtenir plus de contrôle en interpolant entre quatre points.

../../_images/bezier_cubic_points.png

Nous utilisons d'abord une fonction à quatre paramètres pour prendre quatre points en entrée, p0, p1, p2 et p3 :

func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):

Nous appliquons une interpolation linéaire à chaque couple de points pour les réduire à trois :

var q0 = p0.linear_interpolate(p1, t)
var q1 = p1.linear_interpolate(p2, t)
var q2 = p2.linear_interpolate(p3, t)

Nous prenons alors nos trois points et les réduisons à deux :

var r0 = q0.linear_interpolate(q1, t)
var r1 = q1.linear_interpolate(q2, t)

Et à un :

var s = r0.linear_interpolate(r1, t)
return s

Voici la fonction complète :

func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
    var q0 = p0.linear_interpolate(p1, t)
    var q1 = p1.linear_interpolate(p2, t)
    var q2 = p2.linear_interpolate(p3, t)

    var r0 = q0.linear_interpolate(q1, t)
    var r1 = q1.linear_interpolate(q2, t)

    var s = r0.linear_interpolate(r1, t)
    return s

Le résultat sera une courbe lisse interpolant entre les quatre points :

../../_images/bezier_cubic_points.gif

(Crédit image : Wikipedia)

Note

L'interpolation Bezier cubique fonctionne de la même façon en 3D, il suffit d'utiliser Vector3 au lieu de Vector2.

Ajout de points de contrôle

En nous basant sur Bézier cubique, nous pouvons changer la façon dont deux des points fonctionnent pour contrôler librement la forme de notre courbe. Au lieu d'avoir p0, p1, p2 et p3, nous allons les stocker comme :

  • point0 = p0 : C'est le premier point, la source
  • control0 = p1 - p0 : Est un vecteur relatif au premier point de contrôle
  • control1 = p3 - p2 : Est un vecteur relatif au deuxième point de contrôle
  • point1 = p3 : C'est le deuxième point, la destination

De cette façon, nous avons deux points et deux points de contrôle qui sont des vecteurs relatifs aux points respectifs. Si vous avez déjà utilisé un logiciel de graphisme ou d'animation, cela peut vous sembler familier :

../../_images/bezier_cubic_handles.png

C'est ainsi que les logiciels graphiques présentent les courbes de Bézier aux utilisateurs, et comment elles fonctionnent et se présentent dans Godot.

Curve2D, Curve3D, Path et Path2D

Il y a deux objets qui contiennent des courbes : Curve3D et Curve2D (pour 3D et 2D respectivement).

Ils peuvent contenir plusieurs points, ce qui permet des trajectoires plus longues. Il est également possible de les définir sur les nœuds : Path et Path2D (également pour la 3D et la 2D respectivement) :

../../_images/bezier_path_2d.png

Cependant, leur utilisation n'est pas toujours évidente. Voici donc une description des cas d'utilisation les plus courants pour les courbes de Bézier.

Évaluer

Le simple fait de les évaluer peut être une option, mais dans la plupart des cas, ce n'est pas très utile. Le grand inconvénient des courbes de Bézier est que si vous les parcourez à vitesse constante, de t = 0 à t = 1, l'interpolation réelle ne se déplacera pas à vitesse constante. La vitesse est aussi une interpolation entre les distances entre les points p0, p1, p2 et p3 et il n'existe pas de méthode mathématique simple pour parcourir la courbe à vitesse constante.

Faisons un exemple simple avec le pseudo-code suivant :

var t = 0.0

func _process(delta):
    t += delta
    position = _cubic_bezier(p0, p1, p2, p3, t)
../../_images/bezier_interpolation_speed.gif

Comme vous pouvez le voir, la vitesse (en pixels par seconde) du cercle varie, même si t est augmenté à vitesse constante. Cela rend les Béziers difficiles à utiliser dans la pratique de but en blanc.

Dessin

Dessiner des Béziers (ou objets basés sur la courbe) est un cas d'utilisation très courant, mais ce n'est pas facile non plus. Pour presque tous les cas, les courbes de Bézier doivent être converties en sorte de segments. Cependant, cela est normalement difficile, sans en créer un très grand nombre.

La raison en est que certaines sections d'une courbe (en particulier les coins) peuvent nécessiter un nombre considérable de points, alors que d'autres sections peuvent ne pas en nécessiter :

../../_images/bezier_point_amount.png

De plus, si les deux points de contrôle étaient 0, 0 (rappelez-vous que ce sont des vecteurs relatifs), la courbe de Bézier ne serait qu'une ligne droite (donc dessiner un grand nombre de points serait un gaspillage).

Avant de tracer les courbes de Bézier, une tesselation est nécessaire. Ceci est souvent fait avec une fonction récursive ou une fonction diviser pour régner qui divise la courbe jusqu'à ce que la valeur de courbure devienne inférieure à un certain seuil.

Les classes Curve fournissent ceci via la fonction Curve2D.tessellate() (qui reçoit les arguments optionnels stages de récursion et angle tolerance). De cette façon, il est plus facile de dessiner quelque chose à partir d'une courbe.

Parcourir

Le dernier cas d'utilisation courant pour les courbes est de les parcourir. En raison de ce qui a été mentionné précédemment concernant la vitesse constante, c'est également difficile.

Pour faciliter cela, les courbes doivent être préparées en points équidistants. De cette façon, ils peuvent être approximés par interpolation régulière (qui peut être améliorée avec une option cubique). Pour ce faire, utilisez simplement la méthode Curve.interpolate_baked() avec la méthode Curve2D.get_baked_length(). Le premier appel à l'un ou l'autre va préparer la courbe en interne.

Le parcours à vitesse constante peut donc être effectué avec le pseudo-code suivant :

var t = 0.0

func _process(delta):
    t += delta
    position = curve.interpolate_baked(t * curve.get_baked_length(), true)

Et la sortie se déplacera alors à une vitesse constante :

../../_images/bezier_interpolation_baked.gif