Points régulièrement espacés le long d'une courbe de Bézier

10

Je regarde autour de moi depuis un moment et je ne trouve pas de solution à ce problème. Disons que j'ai une courbe de Bézier cubique (définie par 4 points) et que je veux obtenir un ensemble de points qui sont espacés uniformément le long de la courbe. Pensez à placer un texte le long d'une courbe pour un exemple.

Maintenant, le problème est que si j'entre t(valeur d'interpolation de 0-1) avec un incrément constant, les points ne sont pas espacés uniformément. La distance le long de la courbe est plus petite lorsque la courbe fait un virage et plus longue lorsque la courbe est droite.

Alors, comment puis-je placer des points uniformément le long d'une courbe de Bézier?

Foaly
la source
4
Vous recherchez une solution "purement mathématique" (ou particulièrement efficace)? Sinon, l'approche simple est la suivante: convertissez la courbe en polyligne, en marchant le long de la courbe, en augmentant les t, disons, 100 pas, et mesurez les distances entre les points résultants. Ensuite, interpolez le long de cette polyligne comme vous le souhaitez.
Marco13
Je pense que vous recherchez le mot-clé "paramétrage de la longueur d'arc", qui a été répondu par exemple dans cette question .
wondra
Qu'est-ce que @ Marco13 a dit!
david van brink
Selon les réponses / commentaires, l'approche que j'ai mentionnée est non seulement simple, mais aussi assez courante. Est-ce pour une langue particulière? Peut-être que quelqu'un
posterait

Réponses:

3

C'est plus une question mathématique. Ainsi, une courbe de Bézier a la formule suivante , à la fois dans le composant xet y.

B_x(t) = (1-t)^3 * P0_x + (1-t)^2 * t * P1_x + (1-t) * t^2 * P2_x + t^3 * P3_x
B_y(t) = (1-t)^3 * P0_y + (1-t)^2 * t * P1_y + (1-t) * t^2 * P2_x + t^3 * P3_y

La longueur parcourue le tlong d'une courbe gammaest donnée par:

length_gamma(t) = integration( sqrt( derivative(  gamma_x(s)  ) ^2 + derivative(  gamma_y(s)  ) ^2 ) )

Il n'y a pas de solution inscriptible par l'homme à l'intégrale, vous devez donc approximer.

Remplacez l' gamma(t)expression par B(t)pour obtenir la longueur length_Bparcourue le tlong du segment de Bézier. Disons que ça voyage de 0à L.

Choisissez maintenant des nvaleurs comprises entre 0et Lqui correspondent aux points régulièrement espacés. Par exemple, les longueurs du formulaire k*L/npour kde 0à n.

Vous devez maintenant inverser la fonction length_B, afin de pouvoir calculer le tdos à partir de la longueur l. C'est beaucoup de mathématiques et je suis paresseux comme l'enfer, essayez de le faire vous-même. Si vous ne le pouvez pas, vous pouvez accéder à l' échange de pile mathématique . Pour une réponse plus complète, vous pouvez quand même vous y rendre.

Une fois que vous avez cette length_Bfonction inverse (ou une approximation raisonnable), votre processus est assez simple.

  • Créez des longueurs l[k]de distance de chemin données à partir de l'origine (P0_x,P1_x).
  • Calculez leur t[k]utilisation correspondante length_B_inverse.
  • Positionner les points en utilisant (B_x(t[k]),B_y(t[k])).
Lærne
la source
1
Je vous remercie! Il s'avère que le calcul dont vous avez besoin pour intégrer le polynôme de Bernstein est un cauchemar. J'ai pu utiliser cette solution
Foaly le
0

Pour développer ce que Marco a dit, une technique courante consiste à descendre la courbe par incréments beaucoup plus petits que les pas de longueur fixe que vous souhaitez prendre et à stocker le point de sortie résultant (et peut-être la distance?) Dans un tableau.

Ensuite, vous parcourez le tableau et supprimez toutes les entrées, à l'exception des points les plus proches des multiples entiers des distances que vous souhaitez parcourir.

Il vous reste alors une table que vous pouvez indexer directement au moment de l'exécution très rapidement. Si vous voulez aller à l'endroit qui est 5 fois la taille de votre distance, vous regardez dans votre tableau à l'index [5].

Notez que vous pouvez effectuer les deux étapes en une et ne pas réellement stocker les éléments supplémentaires dans le tableau pour commencer, mais il est plus facile de visualiser et de comprendre en deux étapes.

J'ai vu une fois une technique pour calculer cela à la volée sans précalculer une table (il n'a pas non plus utilisé d'itération / recherche de racine!), Mais malheureusement je ne me souviens pas du tout des détails):

Si je m'en souviens ou le trouve, je posterai les informations!

Alan Wolfe
la source
1
cela pourrait également vous intéresser: math.stackexchange.com/questions/15896/…
Alan Wolfe
0

Étape 1 - Générez N + 1 points en interpolant la courbe par incréments de 1 / N. N doit être suffisamment grand pour de bons résultats visuels mais suffisamment petit pour être facilement calculé. Une valeur de 50 devrait être OK pour la plupart des situations, mais elle doit être réglée pour votre cas spécifique. J'appellerai cela les "points interpolés".

Alternativement, vous pouvez générer une courte liste de segments et rompre récursivement chaque segment qui est plus long que la longueur de segment maximale souhaitée (initialement, vous devez générer au moins quatre segments pour tenir compte des courbes S où le début est très proche de la fin).

Étape 2 - "Parcourez la ligne" en utilisant les points interpolés et l'espacement souhaité entre chaque point.

Je laisse ici mon code Unity:

public static Vector2[] InterpolateBezier(Vector2 start, Vector2 startControlPoint,
    Vector2 end, Vector2 endControlPoint, int segments)
{
    Vector2[] interpolated = new Vector2[segments + 1];
    interpolated[0] = start;
    interpolated[segments] = end;

    float step = 1f / segments;
    for (int i = 1; i < segments; i++)
    {
        interpolated[i] = GetBezierPosition(start, startControlPoint, end,
            endControlPoint, i * step);
    }

    return interpolated;
}

public static Vector2 GetBezierPosition(Vector2 start, Vector2 startControlPoint,
    Vector2 end, Vector2 endControlPoint, float t)
{
    float omt = 1f - t;
    float omt2 = omt * omt;
    float t2 = t * t;

    return
        start * (omt2 * omt) +
        startControlPoint * (3f * omt2 * t) +
        endControlPoint * (3f * omt * t2) +
        end * (t2 * t);
}

public static List<Vector2> WalkLine(Vector2[] points, float spacing, float offset = 0)
{
    List<Vector2> result = new List<Vector2>();

    spacing = spacing > 0.00001f ? spacing : 0.00001f;

    float distanceNeeded = offset;
    while (distanceNeeded < 0)
    {
        distanceNeeded += spacing;
    }

    Vector2 current = points[0];
    Vector2 next = points[1];
    int i = 1;
    int last = points.Length - 1;
    while (true)
    {
        Vector2 diff = next - current;
        float dist = diff.magnitude;

        if (dist >= distanceNeeded)
        {
            current += diff * (distanceNeeded / dist);
            result.Add(current);
            distanceNeeded = spacing;
        }
        else if (i != last)
        {
            distanceNeeded -= dist;
            current = next;
            next = points[++i];
        }
        else
        {
            break;
        }
    }

    return result;
}
Jorge Galvão
la source
-1

Voici un algorithme qui donne des résultats assez corrects:

  Point Index(float pos) const
  {
    int count = p.NumPoints();
    Vector val(0.0,0.0,0.0);
    for(int i=0;i<count;i++)
      { 
        val += bin(pos,i,count-1)*Vector(p.Points(i));
      }
    return Point(val);
  }
  float bin(float pos, int i, int n) const
  { 
    return float(ni(n,i)) * pow(double(pos), double(i))*pow(double(1.0-pos), double(n-i));
  }
  int ni(int n, int i) const
  {
    if (i==0) { return 1; }
    if (n==i) { return 1; }
    return ni(n-1,i-1)+ni(n-1,i);
  }
tp1
la source