Die überprüfung, ob zwei kubische Bézier-Kurven kreuzen
Für ein persönliches Projekt, ich brauche, um herauszufinden, ob zwei kubische Bézier-Kurven sich schneiden. Ich brauche nicht zu wissen, wo: ich brauche nur zu wissen, wenn Sie tun. Allerdings würde ich brauchen, um es zu tun schnell.
Ich habe Aufräumvorgang den Platz, und ich fand mehrere Ressourcen. Meist gibt es diese Frage hier, die hatte eine viel versprechende Antwort.
So, nachdem ich dachte, was ist ein Sylvester matrix, was ist ein Determinante, was ist ein daraus resultierende und warum es sinnvoll ist, ich dachte, ich dachte, wie die Lösung funktioniert. Doch die Realität stellt sich voneinander zu unterscheiden, und es funktioniert nicht so gut.
Herumspielen
Ich habe mein Funktionsplotter zum zeichnen von zwei Bézier-splines (das nennen wir B0 - und B1), die sich überschneiden. Deren Koordinaten lauten wie folgt (P0, P1, P2, P3):
(1, 1) (2, 4) (3, 4) (4, 3)
(3, 5) (3, 6) (0, 1) (3, 1)
Das Ergebnis ist die folgende, B0 wird durch die "horizontale" Kurve und B1 der andere:
Folgenden Richtungen von der vorgenannten Frage die top-Antwort gestimmt, ich habe subtrahiert B0 B1. Sie verließ mich mit zwei Gleichungen (die X-und die Y-Achse), die, laut meinem Rechner sind:
x = 9t^3 - 9t^2 - 3t + 2
y = 9t^3 - 9t^2 - 6t + 4
Die Sylvester-Matrix
Und ich gebaut haben die folgenden Sylvester matrix:
9 -9 -3 2 0 0
0 9 -9 -3 2 0
0 0 9 -9 -3 2
9 -9 -6 4 0 0
0 9 -9 -6 4 0
0 0 9 -9 -6 4
Danach, habe ich eine C++ - Funktion zur Berechnung von Determinanten von Matrizen mit Laplace-expansion:
template<int size>
float determinant(float* matrix)
{
float total = 0;
float sign = 1;
float temporaryMatrix[(size - 1) * (size - 1)];
for (int i = 0; i < size; i++)
{
if (matrix[i] != 0)
{
for (int j = 1; j < size; j++)
{
float* targetOffset = temporaryMatrix + (j - 1) * (size - 1);
float* sourceOffset = matrix + j * size;
int firstCopySize = i * sizeof *matrix;
int secondCopySize = (size - i - 1) * sizeof *matrix;
memcpy(targetOffset, sourceOffset, firstCopySize);
memcpy(targetOffset + i, sourceOffset + i + 1, secondCopySize);
}
float subdeterminant = determinant<size - 1>(temporaryMatrix);
total += matrix[i] * subdeterminant * sign;
}
sign *= -1;
}
return total;
}
template<>
float determinant<1>(float* matrix)
{
return matrix[0];
}
Scheint es ziemlich gut zu funktionieren, die sich auf relativ kleine Matrizen (2x2, 3x3 und 4x4), also würde ich erwarten, dass es funktioniert auf 6x6-Matrizen zu. Ich wusste nicht, führen umfangreiche tests jedoch und gibt es eine Möglichkeit, dass es kaputt ist.
Das Problem
Wenn ich das richtig verstanden, die Antwort aus der anderen Frage, die Determinante muss 0 sein. da die Kurven überschneiden. Allerdings, Fütterung mein Programm die Sylvester-matrix, die ich, wie oben bereits erwähnt, ist es -2916.
Ist es ein Fehler an meinem Ende oder an Ihrem Ende? Was ist der richtige Weg, um herauszufinden, ob zwei kubische Bézier-Kurven schneiden?
- Bitte nicht berechnen Determinanten, wenn Sie nicht haben, um. Wenn Sie prüfen wollen, für eine Singularität, die Berechnung des niedrigsten und höchsten singulären Wert. Und wenn man die Determinante aus irgendeinem Grund, verwenden Sie nicht die Laplace-Expansion! Es hat exponentielle Zeitkomplexität. Sie können es auf O(n^3)!
- Stecken Sie die Sylvester-Matrix in die matrix Rechner unter bluebit.gr/matrix-Rechner gab -2916 für die Determinante. Sie müssen möglicherweise zu beheben Ihre bestimmende Funktion.
- Lutz Ja, ich fand, dass etwa 5 Minuten nach meinem post, und ich meine Feste Determinante Funktion.
- Ich werde gerne legen Sie es einmal jemand erklärt, einen anderen Weg zu finden, Bézier-Kurven, Kreuzungen.
- Wie haben Sie daraus die Gleichungen "x = 9t^3 - 9Z^2 - 3t + 2; y = 9Z^3 - 9Z^2 - 6t + 4" von der Kontrollpunkte der Kurven?
- Baker ich nicht, meine Rechner haben. Es überarbeitete die kubische Bézier-Gleichung wir wissen, in
(-P0 + 3*P1 - 3*P2 + P4) * t^3 + 3*(P0 - 2*P1 + P2) * t^2 - 3*(P0 - P1) * t + P0
; und die beiden Gleichungen, die ich gezeigt habe sind einfach die Funktionen X und Y der beiden Kurven subtrahiert. WolframAlpha bestätigt, Sie sind identisch. - Dies ist eine perfekte Algorithmus, aber es findet, wenn zwei parametrische Kurven schneiden sich auf die exakt gleichen parameter Wert (Es ist das, was @PaulBaker Antwort Punkte zu). Das eigentliche problem ("Do Kurven schneiden überhaupt ?") ist ein bi-variadic polynom, für das Sie finden möchten, die Wurzeln ein problem, für das ich weiß nicht, ob es eine analytische Lösung. Ich denke, dass sollte die Frage bearbeitet werden, um auch diese Bemerkung.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Kreuzung von Bézier-Kurven erfolgt durch die (sehr cool) Asymptote vector graphics language: suchen Sie nach
intersect()
hier.Obwohl Sie nicht erklären, der Algorithmus, die Sie tatsächlich verwenden, gibt es, außer zu sagen, dass es von p aus. 137 "Die Metafont-Buch", es scheint, dass der Schlüssel dazu ist das zwei wichtige Eigenschaften der Bézier-Kurven (welche an anderer Stelle erklärt werden auf dieser Website aber ich kann nicht finden die Seite jetzt):
Mit diesen zwei Eigenschaften und ein Algorithmus für die sich schneidenden Polygone, Sie können den Parameter recurse auf beliebiger Genauigkeit:
bezInt(B1, B2):
bezInt(B1a, B2b) ||
bezInt(B1b, B2a) ||
bezInt(B1b, B2b).
Diese schnell, wenn die Kurven nicht schneiden-ist das üblich?
[BEARBEITEN] Es sieht aus wie der Algorithmus für die Aufteilung einer Bezier-Kurve in zwei genannt de Casteljau-Algorithmus.
Wenn du tust dies für die Produktion code, würde ich vorschlagen, dass die Bézier-clipping-Algorithmus. Es ist gut erklärt in Abschnitt 7.7 von diesem kostenlosen online-CAGD text (pdf), funktioniert für jeden Grad der Bézier-Kurve, und ist schnell und robust.
Während unter Verwendung von standard rootfinders oder Matrizen sein könnte mehr einfach aus mathematischer Sicht, Bézier-clipping ist relativ einfach zu implementieren und zu Debuggen, und tatsächlich haben weniger floating-point-Fehler. Dies ist, weil, wenn es darum geht neue zahlen, es tut gewichtete Durchschnitte (konvex-Kombination), so gibt es keine chance auf eine Extrapolation basierend auf verrauschten Eingaben.
Sind Sie stützen Sie Ihre interpretation der Determinante auf der 4. Kommentar diese Antwort? Wenn ja, ich glaube, dass ' s, wo der Fehler liegt. Die Reproduktion der Kommentar hier:
Sehe ich keine Probleme mit diesem Teil, aber der Autor geht auf zu sagen:
Ich glaube nicht, dass das korrekt ist. Es ist durchaus möglich für die beiden Kurven schneiden sich in einem Ort, wo die t-Werte unterscheiden sich, und in diesem Fall gibt es eine Kreuzung, auch wenn die matrix einen nicht-null-Determinante. Ich glaube, dass dies ist, was passiert in Ihrem Fall.
Ich weiß nicht, wie schnell es sein wird, aber wenn Sie zwei Kurven C1(t) und C2(k) Sie überschneiden sich, wenn C1(t) == C2(k). Sie haben also zwei Gleichungen (pro x und pro y) für zwei Variablen (t, k). Lösen können Sie das system mit Hilfe numerischer Methoden mit genug Genauigkeit. Wenn Sie gefunden haben, t,k Parameter sollten Sie überprüfen, ob die parameter auf [0, 1]. Wenn es ist, Sie schneidet auf [0, 1].
Ich bin kein Experte für diese Art der Sache, aber ich Folge einer schönen blog, dass die Gespräche viel über Kurven. Er hat Verbindung zu zwei nette Artikel im Gespräch über Ihr problem (der zweite link hat eine interaktive demonstration und einige source-code). Andere Menschen haben viel besseren Einblick in das problem, aber ich hoffe, das hilft!
http://cagd.cs.byu.edu/~557/text/ch7.pdf
Dies ist ein hartes problem. Ich würde split jeweils den 2 Bezier-Kurven in sagen wir 5-10 diskrete line-Segmente, und nur tun, Linie-line-Kreuzungen.
Ich würde sagen, dass die einfachste und wahrscheinlich Schnellste Antwort ist, unterteilen Sie Sie in sehr kleine lines und finden Sie die Punkte, wo die Kurven sich schneiden, wenn Sie es tatsächlich tun.
Natürlich die brute-force-Antwort ist schlecht zu Sehen, bo{4}'s Antwort, und es gibt eine Menge von linearen geometrie und Kollisionsabfrage, die tatsächlich helfen, eine ganze Menge.
Wählen Sie die Anzahl der Scheiben, die Sie wollen, für die Kurven. So etwas wie 100 sollte groß sein.
Nehmen wir alle Segmente und Sortieren Sie basierend auf den größten Wert von y, die Sie haben. Wir fügen Sie dann eine zweite Flagge in der Liste für den kleinsten Wert von y für dieses segment.
Wir halten eine Liste von aktiven Kanten.
Durchlaufen wir die y-sortierte Liste von segment -, wenn wir begegnen, ein führender segment fügen wir es in die aktive Liste. Wenn wir auf dem kleinen y-Wert gekennzeichnet wird, entfernen wir das betreffende segment aus der aktiven Liste.
Wir dann einfach Durchlaufen den gesamten Satz von Segmenten mit welchen Mengen um eine scan-Linie, die Erhöhung unserer y-monoton, da wir einfach nur Durchlaufen der Liste. Wir iterieren über die Werte in der sortierten Liste wird in der Regel entfernen Sie ein segment und eine neue hinzufügen-segment (oder für die split-und merge-Knoten, hinzufügen von zwei Segmenten oder entfernen Sie die beiden Segmente). Dabei halten Sie eine aktuelle Liste der relevanten Segmente.
Laufen wir schnell scheitern intersect prüfen, wie wir hinzufügen eines neuen aktiven segment zu der Liste der aktiven Segmente, gegen die nur das segment und die gerade aktiven Segmente.
Also zu allen Zeiten wissen wir genau, welche linienabschnitte sind relevant, da wir Durchlaufen den in die Stichprobe einbezogenen Segmente der Kurven. Wir wissen, dass diese Segmente teilen überschneiden sich in der y-coords. Wir können schnell fehl, neues segment, das überschneidet sich nicht mit Bezug auf Ihren x-Koordinaten. In dem seltenen Fall, dass Sie überschneiden sich in die x-coords, dann überprüfen wir, ob diese Segmente überschneiden.
Dies wird wahrscheinlich reduzieren Sie die Anzahl der Zeile die Schnittmenge prüft grundsätzlich die Anzahl der Kreuzungen.
checkAgainstActive() würde einfach prüfen, ob das segment und ein segment der aktiven Liste überschneiden sich Ihre x-Koordinaten, wenn Sie es tun, dann führen Sie die Linie sich überschneiden überprüfen, und ergreifen Sie die entsprechenden Maßnahmen.
Beachten Sie auch, das funktioniert für eine beliebige Anzahl von Kurven, jede Art von Kurven beliebiger Reihenfolge der Kurven, in beliebiger Mischung. Und wenn wir Durchlaufen die gesamte Liste der Segmente findet jeder Kreuzung. Es findet jeden Punkt einer Bezier-schneidet einen Kreis oder jeder Kreuzung, dass ein Dutzend quadratische Bezier-Kurven haben mit einander (oder sich selbst), und alle in der gleichen Sekunde.
Den oft zitierten Kapitel 7 Dokument Noten, für die Unterteilung Algorithmus:
"Einmal ein paar Kurven wurde unterteilt genug sind, dass Sie jeweils angenähert werden durch ein Liniensegment innerhalb einer Toleranz"
Können wir im wahrsten Sinne des Wortes überspringen der Zwischenhändler. Wir können dies tun, schnell genug, so einfach vergleichen Liniensegmenten, die Sie mit einem tolerierbaren Betrag der Fehler aus der Kurve. Am Ende, das ist, was die typische Antwort nicht.
Zweitens, beachten Sie, dass der Großteil der Steigerung der Geschwindigkeit für die Kollisionserkennung hier, nämlich der geordneten Liste der Segmente sortiert, basierend auf Ihrer höchsten y so fügen Sie die aktive Liste und die niedrigsten y, entfernen Sie aus der aktiven Liste, ebenso kann getan werden, für den Rumpf Polygone der Bézier-Kurve direkt. Unsere Strecke ist einfach ein polygon der Ordnung 2, aber wir konnten eine beliebige Anzahl der Punkte gerade als trivial, und die überprüfung der bounding box alle Kontrollpunkte in der Reihenfolge der Kurve wir es zu tun haben. Also anstatt eine Liste der aktiven Segmente, würden wir eine Liste der aktiven hull Polygone Punkte. Wir könnten einfach auf De Casteljau-Algorithmus zum aufteilen der Kurven, wodurch die Probenahme, da diese als unterteilt Kurven eher als Liniensegmente. Anstatt also die 2 Punkte hätten wir 4 oder 7 oder so weiter, und führen Sie die gleiche routine wird ganz leicht Richtung schnell scheitern.
Hinzufügen der entsprechenden Gruppe der Punkte bei max y, entfernen Sie es auf min y, und Vergleich nur die aktive Liste. So können wir schnell und eine bessere Umsetzung der Bezier-subdivision-Algorithmus. Durch einfach Suche nach bounding box überlappen, und dann die Unterteilung diejenigen Kurven, die sich überlappen, und entfernen Sie diejenigen, die nicht. Wie, wieder, wir können eine beliebige Anzahl von Kurven, auch diejenigen, unterteilt von Kurven in der vorherigen iteration. Und wie unsere segment-Annäherung schnell lösen Sie für jede Kreuzung position zwischen Hunderten von verschiedenen Kurven (auch aus verschiedenen Bestellungen) sehr schnell. Überprüfen Sie einfach alle Kurven zu sehen, wenn die bounding boxes überlappen, wenn Sie es tun, unterteilen wir diese, bis unsere Kurven sind klein genug, oder wir liefen aus Ihnen heraus.
Ja, ich weiß, dieser thread angenommen wird und für lange Zeit geschlossen, aber...
Erstmal möchte ich Euch danken, zneak, für eine inspiration. Ihre Bemühungen können den richtigen Weg zu finden.
Zweiten, ich war nicht ganz glücklich mit der akzeptierten Lösung. Diese Art ist in meiner Lieblings-freeware-IPE und seine bugzilla ist viel klagt, für die eine niedrige Genauigkeit und Zuverlässigkeit über eine Kreuzung Problem, meine unter Ihnen.
Den fehlenden trick, der es ermöglicht, das problem zu lösen in einer Weise, die vorgeschlagen zneak : Es ist genug, um zu verkürzen, eine der Kurven um einen Faktor k>0, dann ist die Determinante der Sylvester-matrix gleich null. Es ist offensichtlich, wenn eine verkürzte Kurve schneidet, dann original auch. Nun, die Aufgabe ist der Wechsel in die Suche nach einem geeigneten Wert für k Faktor. Dies hat dazu geführt, das problem zu lösen, wird ein univariates Polynom 9. Grades. Eine echte und positive Wurzeln dieses Polynoms sind verantwortlich für Potenzial Punkte der Kreuzung. (Dies sollte keine überraschung sein, zwei kubische Bezier-Kurven schneiden bis zu 9 mal.) Die endgültige Auswahl erfolgt nach den k Faktoren, welche für 0<=t<=1 für beide Kurven.
Nun die Maxima-code, wo der Ausgangspunkt ist ein Satz von 8 Punkten, die von zneak :
Zu diesem Zeitpunkt Maxima antwortete:
Lassen Maxima lösen dieser Gleichung:
Die Antwort ist:
Nun den code wählen Sie einen richtigen Wert k:
Maxima Antwort:
Es gibt nicht nur einen Honig, obwohl. Die Vorgestellte Methode ist unbrauchbar, wenn:
Kann man sich Fragen, warum eine Verkürzung ist nur einmal durchgeführt. Es ist genug, weil der reverse-umgekehrte Gesetz, das wurde vor en passant, aber das ist eine andere Geschichte.