Długość łuku krzywej Beziera

23

Zobacz także: to samo pytanie na Math.SE

Jak znaleźć długość łuku krzywej Beziera? Na przykład liniowa krzywa Beziera ma długość:

length = sqrt(pow(x[1] - x[0], 2) + pow(y[1] - y[0], 2));

Ale co z kwadratowymi, sześciennymi lub n-stopniowymi krzywymi Beziera?

(Moim celem było wcześniejsze oszacowanie rozdzielczości próbkowania, więc nie muszę tracić czasu na sprawdzanie, czy następny punkt dotyka poprzedniego punktu.)

Mateen Ulhaq
źródło
1
Powinieneś przeredagować pytanie, aby odnosić się do długości krzywej, która jest znacznie prostszym (i możliwym do przeszukiwania) terminem.
Sparr
proponuję opublikować to na matematyce, jestem pewien, że jakaś sprytna twarz da ci odpowiedź w jednej z nich sprytnych czcionek internetowych: p
Tor Valamo
2
@Tor zrobiłem (wczoraj), ale powiedziano mi, że jest to bardzo skomplikowane, a zatem niepraktyczne. [ math.stackexchange.com/q/12186/2736 ]
Mateen
Podobno krzywe / splajny typu kloidoidy są alternatywą dla Beziers i mają wyrażenia o zamkniętej formie, ale jeszcze niewiele o tym wiem. (Próbujesz wygenerować punkty o równej odległości wzdłuż krzywej.) Sieci trakcyjne mają również wyrażenia długości łuku w postaci zamkniętej?
endolith

Odpowiedzi:

9

Prostym sposobem na sześcienne Beziers jest podzielenie krzywej na N segmentów i zsumowanie długości segmentów.

Jednak gdy tylko potrzebujesz długości tylko części krzywej (np. Do 30% długości wzdłuż), rozpocznie się parametryzacja długości łuku . Na jedno z moich własnych pytań na temat Béziersa zamieściłem dość długą odpowiedź , z prostym przykładowym kodem.

Ivo Wetzel
źródło
Robię to dla LEGO Mindstorms NXT, który ma naprawdę słaby procesor (48 MHz), więc potrzebuję jak największej prędkości. Podejdę do dzielenia, aby zaoszczędzić trochę prędkości i uzyskać ją wystarczająco dokładną (do renderowania „w czasie rzeczywistym”). Mam również opcję, w której możesz ustawić wartość 1.0/t(wywoływana resolution), więc to jest dla „czasu rzeczywistego” (czyli co najwyżej 10 fps na wolnym NXT). Rysowana jest każda iteracja t += resolutionoraz nowy punkt / linia. W każdym razie dzięki za pomysł.
Mateen Ulhaq
4

Chociaż jestem z odpowiedziami, które już otrzymałeś, chcę dodać prosty, ale potężny mechanizm aproksymacji, którego możesz użyć dla krzywych Béziera o dowolnym stopniu: Ciągle dzielisz krzywą za pomocą podziału de Casteljau, aż do maksymalnej odległości punktów kontrolnych pod-krzywej do linii bazowej pod-krzywej jest poniżej pewnego stałego epsilon . W takim przypadku pod-krzywą można aproksymować za pomocą linii podstawowej.

W rzeczywistości uważam, że takie podejście jest zwykle stosowane, gdy podsystem graficzny musi narysować krzywą Béziera. Ale nie cytuj mnie na ten temat, w tej chwili nie mam pod ręką referencji.

W praktyce będzie to wyglądać tak: (z wyjątkiem tego, że język nie ma znaczenia)

public static Line[] toLineStrip(BezierCurve bezierCurve, double epsilon) {
    ArrayList<Line> lines = new ArrayList<Line>();

    Stack<BezierCurve> parts = new Stack<BezierCurve>();
    parts.push(bezierCurve);

    while (!parts.isEmpty()) {
        BezierCurve curve = parts.pop();
        if (distanceToBaseline(curve) < epsilon) {
            lines.add(new Line(curve.get(0), curve.get(1)));
        } else {
            parts.addAll(curve.split(0.5));
        }
    }

    return lines.toArray(new Line[0]);
}
Matthias
źródło
Chociaż jest to dobre podejście, słyszałem o niestabilności numerycznej przy wysokich krzywych Beziera, które wymagają innego pomysłu: podzielenie krzywych wyższego rzędu na mniejsze krzywe sześcienne.
Mateen Ulhaq,
Ponadto, jeśli celem końcowym jest dokładne oszacowanie, dobrym pomysłem może być przybliżenie za pomocą kwadratów zamiast linii, aby upewnić się, że nie zaniżamy naszych oszacowań w miejscach o wysokiej krzywiźnie.
Mateen Ulhaq,
2

Długości łuku dla krzywych Beziera są tylko zamknięte dla krzywych liniowych i kwadratowych. W przypadku sześciennych nie ma gwarancji posiadania zamkniętego rozwiązania. Powodem jest to, że długość łuku jest zdefiniowana przez całkę radykalną, dla której jest zamknięty tylko dla wielomianów drugiego stopnia.

Tylko w celach informacyjnych: Długość kwadratowego Beziera dla punktów (a, p) (b, q) i (c, r) wynosi

(a ^ 2 · (q ^ 2 - 2 · q · r + r ^ 2) + 2 · a · (r - q) · (b · (p - r) + c · (q - p)) + ( b · (p - r) + c · (q - p)) ^ 2) · LN ((√ (a ^ 2 - 2 · a · b + b ^ 2 + p ^ 2 - 2 · p · q + q ^ 2) · √ (a ^ 2 + 2 · a · (c - 2 · b) + 4 · b ^ 2 - 4 · b · c + c ^ 2 + (p - 2 · q + r) ^ 2) + a ^ 2 + a · (c - 3 · b) + 2 · b ^ 2 - b · c + (p - q) · (p - 2 · q + r)) / (√ (a ^ 2 + 2 · A · (c - 2 · b) + 4 · b ^ 2 - 4 · b · c + c ^ 2 + (p - 2 · q + r) ^ 2) · √ (b ^ 2 - 2 · b · c + c ^ 2 + q ^ 2 - 2 · q · r + r ^ 2) + a · (b - c) - 2 · b ^ 2 + 3 · b · c - c ^ 2 + (p - 2 · q + r) · (q - r))) / (a ​​^ 2 + 2 · a · (c - 2 · b) + 4 · b ^ 2 - 4 · b · c + c ^ 2 + (p - 2 · Q + r) ^ 2) ^ (3/2) + (√ (a ^ 2 - 2 · a · b + b ^ 2 + p ^ 2 - 2 · p · q + q ^ 2) · (a ^ 2 + a · (c - 3 · b) + 2 · b ^ 2 - b · c + (p - q) · (p - 2 · q + r)) - √ (b ^ 2 - 2 · b · c + c ^ 2 + q ^ 2 - 2 · q · r + r ^ 2) · (a · (b - c) - 2 · b ^ 2 + 3 · b · c - c ^ 2 + (p - 2 · q + r) · (q - r))) / (a ​​^ 2 + 2 · a · (c - 2 · b) + 4 · b ^ 2 - 4 · b · c + c ^ 2 + (p - 2 · Q + r) ^ 2)

Gdzie LN jest logarytmem naturalnym, a ^ oznacza moc i root pierwiastek kwadratowy.

Dlatego przybliżenie łuku powinno być łatwiejsze i tańsze według innej reguły, takiej jak wielokąt lub schemat integracji, taki jak reguła Simpsona, ponieważ pierwiastki kwadratowe LN są kosztownymi operacjami.

Robert
źródło
2

Opracowałem wyrażenie długości w postaci zamkniętej dla 3-punktowego Beziera (poniżej). Nie próbowałem wypracować zamkniętego formularza dla 4+ punktów. Reprezentacja i obsługa najprawdopodobniej byłaby trudna lub skomplikowana. Jednak numeryczna technika aproksymacji, taka jak algorytm całkowania Runge-Kutta, działałaby całkiem dobrze, integrując przy użyciu wzoru długości łuku . Moje pytania i odpowiedzi na temat RK45 na MSE mogą pomóc w implementacji RK45.

Oto niektóre kodu Java dla długości łuku 3 pkt Beziera z punktów a, bi c.

    v.x = 2*(b.x - a.x);
    v.y = 2*(b.y - a.y);
    w.x = c.x - 2*b.x + a.x;
    w.y = c.y - 2*b.y + a.y;

    uu = 4*(w.x*w.x + w.y*w.y);

    if(uu < 0.00001)
    {
        return (float) Math.sqrt((c.x - a.x)*(c.x - a.x) + (c.y - a.y)*(c.y - a.y));
    }

    vv = 4*(v.x*w.x + v.y*w.y);
    ww = v.x*v.x + v.y*v.y;

    t1 = (float) (2*Math.sqrt(uu*(uu + vv + ww)));
    t2 = 2*uu+vv;
    t3 = vv*vv - 4*uu*ww;
    t4 = (float) (2*Math.sqrt(uu*ww));

    return (float) ((t1*t2 - t3*Math.log(t2+t1) -(vv*t4 - t3*Math.log(vv+t4))) / (8*Math.pow(uu, 1.5)));
Piksel
źródło