Table des matières

Spline de Catmull-Rom

Principe

Dans la Spline de Hermite on a vu comment définir une spline en fournissant la liste des points et la liste des tangentes.

Cela peut-être ennuyeux de devoir fournir les tangentes. Dans certains cas, on voudrait seulement fournir la liste des points et obtenir une spline passant par tous les points.

On utilise alors l'astuce suivante :

Quand on considère le point $P_i$, on lui attribue un vecteur tangent égal à $\overrightarrow{P_{i-1}P_{i+1}}$.

À l'usage, on réalise que cette méthode donne de bons résultats mais les courbes obtenues sont un peu trop tordues. On bricole un peu et on décide de choisir comme vecteur tangent $\tau\overrightarrow{P_{i-1}P_{i+1}}$ où $\tau$ est un réglage à choisir entre $0$ et $1$. $\tau = 0,5$ est par exemple un bon choix.

$\tau$ est appelé tension.

Problème des extrémités

On est ennuyé pour $P_0$ et $P_{n-1}$.

Pour ces cas on décide de construite un point fictif par symétrie. Par exemple, pour $P_0$, on construit un point $P_{-1}$ symétrique de $P_1$ par rapport à $P_0$. Ainsi, même pour $i = 0$ on aura un $P_{i-1}$ et un $P_{i+1}$.

Formules

Considérons un arc de spline délimité par les points $P_i$ et $P_{i+1}$. On utilise les points $P_{i-1}$ et $P_{i+2}$ éventuellement construits par symétrie comme expliqué ci-dessus.

On veut construire l'arc de spline de degré 3 tel que :

Tous calculs faits on obtient $M(t) = c_0 + c_1\cdot t + c_2 \cdot t^2 + c_3 \cdot t^3$ avec

Implémentation

On souhaite faire une implémentation semblable à celle faite dans la fiche Courbe de Bézier.

Une classe Point

On reprend la classe Point identique à celle faite dans Courbe de Bézier.

La classe Point sert surtout à faciliter les calculs sur les paires (x,y), car tous les calculs que l'on fait sur x doivent être faits à l'identique sur y. On pourra utiliser la classe Point pour les vecteurs, le fonctionnement est le même.

Les données fournies

Pour produire la spline de Catmull-Rom, on devra lui fournir une liste de paires (x,y) pour les points. Rien de plus.

Exemple :

pts = [(10,50), (200, 400), (300, 300), (500, 200)]

Une classe Catmullrom

Cette classe aura la responsabilité des calculs liés à la spline de Catmull-Rom.

Ajoutez le module suivant à votre projet et complétez les fonctions.

# carmullrom.py

from point import Point

class Catmullrom:
    def __init__(self, pts, tau = 0.5):
        '''
        pts: liste de paires (x,y) représentant les points
        tau: tension de la spline. Par défaut 0.5
        '''
        assert len(pts) >= 2, "Il faut au moins deux points dans la spline."
        self.points = [Point(x,y) for x,y in pts]
        assert 0 <= tau <= 1
        self.tau = tau
    
    def get_point(self, i):
        '''
        renvoie le point d'indice i. Si i =0 ou len(pts), calcule un symétrique
        '''
        n = len(self.pts)
        assert -1 <= i <= n
        if i == -1:
            return self.pts[0] * 2 - self.pts[1]
        if i == n:
            return self.pts[n-1] * 2 - self.pts[n-2]
        return self.pts[i] 
       

    def calc_coeffs(self, i:int):
        '''
        i:indice de l'arc considéré
        la courbe de Bézier est calculée avec les points P(i-1), P(i), P(i+1), P(i+2)
        on calcule 4 coefficients :
        c0 = P(i)
        c1 = tau (P(i+1) - P(i-1))
        c2 = -tau P(i+2) + (3 - 2 tau) P(i+1) + (tau -3) P(i) + 2 tau P(i-1)
        c3 = tau P(i+2) + (tau - 2) P(i+1) +  (2 - tau) P(i) - tau P(i-1)
        La fonction renvoie c0, c1, c2, c3
        remarque: chaque coefficient est de type Point.
        '''
        # vérifie que i est bien une valeur valide : 0 <= i <= n-2
        # récupérer les P(i-1), P(i), P(i+1) et P(i+2)
        # calculer les coefficients et les renvoie
        
    def M(self, t:float, i:int):
        '''
        t: flottant entre 0 et 1, compris
        i: rang de l'arc considéré
        renvoie c0 + c1*t + c2*t**2 + c3*t**3 avec c0, c1, c2, c3
        les coeffs de la fonction précédente.
        '''
        # obtenir les valeurs des coefficients
        # renvoie le résultat du calcul

    def tangent(self, t:float, i:int):
        t: flottant entre 0 et 1, compris
        i: rang de l'arc considéré
        renvoie c1 + 2*c2*t + 3*c3*t**2 avec c0, c1, c2, c3
        '''
        # obtenir les valeurs des coefficients
        # renvoie le résultat du calcul
    
    def plot_points(self, N=100):
        '''
        renvoie la liste des points (type Point) constituant la courbe.
        N représente le nombre de points à calculer pour chaque arc.
        '''
        # préparer une liste vide
        # pour chaque arc i,
        #    pour chaque t allant de 0/N à (N-1)/N
        #        calculer M(t, i) et l'ajouter à la liste
        # compléter la liste avec une copie du dernier point de self.points
        # renvoyer la liste

Lors de l'utilisation, pour un arc donné, on calculera M(t) une bonne centaine de fois. Il serait donc bienvenu de ne pas devoir recalculer les coefficients c0, c1, c2, c3 à chaque fois. Le mieux serait de calculer les coefficients de chaque arc dès le début (__init__) et les placer dans un tableau de façon à ne plus avoir à refaire le calcul ensuite.

Utilisation avec matplotlib

On se propose d'expérimenter le module précédent avec un tracer dans matplotlib.

# democatmullrom.py
# démonstration en utilisant matplotlib

import matplotlib.pyplot as plt
from catmullrom import Catmullrom

pts = [(10,50), (200, 400), (300, 300), (500, 200)]

spline = Catmullrom(pts)

# tracé de la courbe
plot_pts = spline.plot_points()
# dans matplotlib, il faut placer les x et les y dans deux listes séparées
x = [pt.x for pt in plot_pts ]
y = [pt.y for pt in plot_pts ]
plt.plot(x, y)

# tracé des points :
x = [x for x, y in pts]
y = [y for x, y in pts]
plt.scatter(x, y, c='b')

plt.show()