Ich habe einen vollständigen Artikel über Punkt im Dreieckstest geschrieben. Es zeigt die baryzentrischen, parametrischen und punktproduktbasierten Methoden. Dann wird das Genauigkeitsproblem behandelt, das auftritt, wenn ein Punkt genau auf einer Kante liegt (mit Beispielen). Schließlich wird eine völlig neue Methode vorgestellt, die auf dem Abstand von Punkt zu Kante basiert. totologic.blogspot.fr/2014/01/… Viel Spaß!
Es ist erwähnenswert, dass alle hier diskutierten Methoden auch im 3D-Raum gültig sind. Ihnen muss lediglich eine Koordinatentransformation (und eine entsprechende Projektion des Punktes auf der Ebene des Dreiecks) vorausgehen. Ein Dreieck ist ein zweidimensionales Objekt.
Andreasdr
Für eine Lösung, die unabhängig von der Wicklungsreihenfolge ist. Hier ist eine funktionierende Geige: jsfiddle.net/ibowankenobi/oex3pzq2
ibrahim tanyalcin
2
Ich stimme dafür, diese Frage zu schließen, weil es eher um Mathematik als um Programmierung geht und meinungsbasiert ist (was ist für Sie "einfach"?).
TylerH
Antworten:
264
Im Allgemeinen prüft der einfachste (und recht optimale) Algorithmus, auf welcher Seite der Halbebene, die durch die Kanten erzeugt wird, der Punkt liegt.
Hier finden Sie einige hochwertige Informationen in diesem Thema zu GameDev , einschließlich Leistungsproblemen.
Und hier ist ein Code, mit dem Sie beginnen können:
Es wird häufig in 2D verwendet. Baryzentrische Koordinaten neigen dazu, Menschen zu verwirren. Angesichts der Koordinaten des Dreiecks und der Punktkoordinate bin ich mir auch nicht sicher, wie effizient die Verwendung von Schwerpunkt ist.
Kornel Kisielewicz
7
@Kornel Die baryzentrische Version ist auch in 2D effizienter. Ihre Lösung hat auch das Problem, dass für Punkte genau an den Kanten des Dreiecks ein anderes Ergebnis angezeigt wird, je nachdem, ob das Dreieck im oder gegen den Uhrzeigersinn angegeben ist.
Andreas Brinck
9
Für meine Zwecke (der Grund, warum ich diese Seite gefunden habe) ist die von Kornel Kisielewicz vorgeschlagene ursprüngliche Antwort viel effizienter. Ich arbeite mit einem LCD-Display mit BYTE-Größenkoordinaten und einem sehr typischen Mikroprozessor, bei dem die Ganzzahlmultiplikation eine sehr schnelle Anweisung ist und die Division viel, viel langsamer ist. Numerische Probleme sind auch viel kleiner, da keine Unterteilung erfolgt! Alle Berechnungen sind genau. Vielen Dank, Rick
4
Die Funktion sign () sagt Ihnen also, welche Seite der Halbebene (gebildet durch die Linie zwischen p2 und p3) p1 ist?
David Doria
1
Beachten Sie, dass Sie nicht immer alle diese Determinanten berechnen müssen, wenn Sie eine bestimmte Reihenfolge der Scheitelpunkte annehmen (z. B. gegen den Uhrzeigersinn). Tatsächlich reicht im besten Fall eine Determinante aus, um festzustellen, dass sich der Punkt nicht innerhalb des Dreiecks befindet.
Thash
176
Lösen Sie das folgende Gleichungssystem:
p = p0 + (p1 - p0) * s + (p2 - p0) * t
Der Punkt pliegt innerhalb des Dreiecks, wenn 0 <= s <= 1und 0 <= t <= 1und s + t <= 1.
Dies ist schneller als die Überprüfung der halben Ebene, aber möglicherweise etwas schwieriger zu erfassen, wenn Sie noch keine Erfahrung mit Schwerpunktkoordinaten haben.
Daniel Rikowski
8
Mit trivialen Exits (nicht implementiert) in Kornels Methode kann seine tatsächlich weitaus effizienter sein als Ihre. Wenn Sie tatsächlich versuchen, s und t zu berechnen, wissen Sie, was ich meine.
85
Ich wollte dies testen, also machte ich eine jsfiddle, wobei ich mich auf die @ andreasdr-Lösung und den Coproc-Kommentar stützte
urraka
5
Optimierung: s + t <= 1impliziert s <= 1und t <= 1ob s >= 0und t >= 0.
Thomas Eding
7
Der von @Logic post vorgeschlagene Artikel totologic.blogspot.fr/2014/01/… hat mir geholfen, diese Lösung besser zu verstehen
Flayn
112
Ich stimme Andreas Brinck zu , baryzentrische Koordinaten sind für diese Aufgabe sehr praktisch. Beachten Sie, dass nicht jedes Mal ein Gleichungssystem gelöst werden muss: Bewerten Sie einfach die analytische Lösung. Unter Verwendung der Andreas -Notation lautet die Lösung:
Nur bewerten s, tund 1-s-t. Der Punkt pliegt genau dann innerhalb des Dreiecks, wenn alle positiv sind.
BEARBEITEN: Beachten Sie, dass der obige Ausdruck für den Bereich davon ausgeht, dass die Nummerierung des Dreiecksknotens gegen den Uhrzeigersinn erfolgt. Wenn die Nummerierung im Uhrzeigersinn erfolgt, gibt dieser Ausdruck einen negativen Bereich zurück (jedoch mit der richtigen Größe). Der Test selbst ( s>0 && t>0 && 1-s-t>0) hängt jedoch nicht von der Richtung der Nummerierung ab, da die obigen Ausdrücke, die mit multipliziert werden, 1/(2*Area)auch das Vorzeichen ändern, wenn sich die Ausrichtung des Dreiecksknotens ändert.
EDIT 2: Für eine noch bessere Recheneffizienz siehe Coprocs Kommentar unten (der darauf hinweist , dass, wenn die Ausrichtung der Dreiecksknoten (im oder gegen den Uhrzeigersinn) vorher bekannt ist, die Division durch 2*Areain den Ausdrücken für sund sein tkann vermieden). Siehe auch Perro Azul 's jsfiddle-Code in den Kommentaren unter Andreas Brincks Antwort.
Ja, mein Punkt ist, dass jede Kritik an Ihrer Methode, die auf den Rechenkosten für die Lösung des Gleichungssystems basiert, unbegründet ist, da dies nicht als Teil des Algorithmus erfolgen muss.
Andreasdr
13
Die Effizienz kann verbessert werden, indem nicht geteilt wird 2*Area, dh indem berechnet wird s´=2*|Area|*sund t´=2*|Area|*t(wenn die Ausrichtung der Punkte - im oder gegen den Uhrzeigersinn - nicht bekannt ist, das Vorzeichen Areavon natürlich überprüft werden muss, aber ansonsten vielleicht nicht einmal müssen berechnet werden), da es zur Überprüfung s>0ausreicht, zu überprüfen s´>0. Und anstatt zu überprüfen 1-s-t>0, reicht es aus, zu überprüfen s´+t´<2*|Area|.
Coproc
1
Ich kann hinzufügen , dass , wenn p0->p1->p2ist , gegen den Uhrzeigersinn in kartesischen (die in der Regel im Uhrzeigersinn in Bildschirmkoordinaten ), die Areadurch dieses Verfahren berechnet positiv sein wird.
Rhgb
1
@ user2600366 Wenn Sie entlang der Grenze des Dreiecks in Richtung p0 -> p1 -> p2 -> p0 usw. fahren, befindet sich das Innere des Dreiecks entweder immer rechts oder immer links. Im ersteren Fall erfolgt die Nummerierung im Uhrzeigersinn, im letzteren Fall gegen den Uhrzeigersinn.
Andreasdr
47
Ich habe diesen Code vor einem letzten Versuch mit Google geschrieben und diese Seite gefunden, also dachte ich, ich würde ihn teilen. Es ist im Grunde eine optimierte Version der Kisielewicz-Antwort. Ich habe mich auch mit der Barycentric-Methode befasst, aber nach dem Wikipedia-Artikel fällt es mir schwer zu erkennen, wie effizient sie ist (ich vermute, es gibt eine tiefere Äquivalenz). Auf jeden Fall hat dieser Algorithmus den Vorteil, dass keine Division verwendet wird. Ein mögliches Problem ist das Verhalten der Kantenerkennung in Abhängigkeit von der Ausrichtung.
In Worten lautet die Idee: Befindet sich der Punkt s links oder rechts von den Linien AB und AC? Wenn das stimmt, kann es nicht drinnen sein. Wenn falsch, ist es zumindest innerhalb der "Zapfen", die die Bedingung erfüllen. Da wir nun wissen, dass ein Punkt innerhalb eines Trigons (Dreiecks) auf derselben Seite von AB liegen muss wie BC (und auch CA), prüfen wir, ob sie sich unterscheiden. Wenn ja, kann s unmöglich drinnen sein, sonst muss s drinnen sein.
Einige Schlüsselwörter in den Berechnungen sind Linienhalbflächen und die Determinante (2x2-Kreuzprodukt). Vielleicht ist es pädagogischer, es als einen Punkt zu betrachten, der sich innerhalb derselben Linie (links oder rechts) zu jeder der Linien AB, BC und CA befindet. Der obige Weg schien jedoch für einige Optimierungen besser geeignet zu sein.
Dieser Test ist ungefähr 140-180% schneller als der erste (danke euch beiden übrigens :). Ich habe den Code hier ausgeführt: paste.ubuntu.com/p/k5w7ywH4p8 mit der NodeJS V8-Engine mit deaktivierten Optimierungen und habe die folgenden Ergebnisse erhalten :: w! Node -p - Minimaltest1: 114.852ms Test2: 64.330ms Test1: 115.650ms Test2: 63,491 ms Test1: 117,671 ms Test2: 65,353 ms Test1: 119,146 ms Test2: 63,871 ms Test1: 118,271 ms Test1: 118,670 ms Test2: 63,352 ms
Surgemcgee
@surgemcgee warum sollten Sie es ohne Optimierungen ausführen? Ist das dann nicht mehr von der Realität entfernt?
xuiqzy
@xuiqzy Nun, mein Programm enthält die zwei verschiedenen Lösungen. Ich muss noch die schnellste Methode dafür anwenden. Vielleicht sollte dieser Kommentar entfernt und durch meine abgeschlossenen Arbeiten zu diesem
Thema ersetzt werden
33
C # -Version der baryzentrischen Methode von andreasdr und Perro Azul. Beachten Sie, dass die Flächenberechnung , wenn vermieden werden können sund tentgegengesetzte Vorzeichen haben. Ich habe das korrekte Verhalten mit einem ziemlich gründlichen Unit-Test überprüft.
publicstaticboolPointInTriangle(Point p,Point p0,Point p1,Point p2){var s = p0.Y * p2.X - p0.X * p2.Y +(p2.Y - p0.Y)* p.X +(p0.X - p2.X)* p.Y;var t = p0.X * p1.Y - p0.Y * p1.X +(p0.Y - p1.Y)* p.X +(p1.X - p0.X)* p.Y;if((s <0)!=(t <0))returnfalse;var A =-p1.Y * p2.X + p0.Y *(p2.X - p1.X)+ p0.X *(p1.Y - p2.Y)+ p1.X * p2.Y;return A <0?(s <=0&& s + t >= A):(s >=0&& s + t <= A);}
[ Bearbeiten ] akzeptierte Änderungsvorschläge von @Pierre; Zeige Kommentare
Die Lösung mit der endenden if-Anweisung funktioniert für Dreieckspunkte im Uhrzeigersinn und gegen den Uhrzeigersinn.
Luke Dupin
@ LukeDupin Ich bin mir nicht sicher, ob ich deinen Kommentar verstehe. Diese Antwort funktioniert wie angegeben für jede gelieferte Bestellung der 3 Punkte.
Glenn Slayden
12
Java-Version der baryzentrischen Methode:
classTriangle{Triangle(double x1,double y1,double x2,double y2,double x3,double y3){this.x3 = x3;this.y3 = y3;
y23 = y2 - y3;
x32 = x3 - x2;
y31 = y3 - y1;
x13 = x1 - x3;
det = y23 * x13 - x32 * y31;
minD =Math.min(det,0);
maxD =Math.max(det,0);}boolean contains(double x,double y){double dx = x - x3;double dy = y - y3;double a = y23 * dx + x32 * dy;if(a < minD || a > maxD)returnfalse;double b = y31 * dx + x13 * dy;if(b < minD || b > maxD)returnfalse;double c = det - a - b;if(c < minD || c > maxD)returnfalse;returntrue;}privatefinaldouble x3, y3;privatefinaldouble y23, x32, y31, x13;privatefinaldouble det, minD, maxD;}
Der obige Code funktioniert genau mit ganzen Zahlen, sofern keine Überläufe auftreten. Es funktioniert auch mit Dreiecken im Uhrzeigersinn und gegen den Uhrzeigersinn. Es funktioniert nicht mit kollinearen Dreiecken (aber Sie können dies überprüfen, indem Sie det == 0 testen).
Die baryzentrische Version ist am schnellsten, wenn Sie verschiedene Punkte mit demselben Dreieck testen möchten.
Die baryzentrische Version ist in den 3 Dreieckspunkten nicht symmetrisch, daher ist sie aufgrund von Gleitkomma-Rundungsfehlern wahrscheinlich weniger konsistent als die Rand-Halbebenen-Version von Kornel Kisielewicz.
Bildnachweis: Ich habe den obigen Code aus dem Wikipedia-Artikel über Schwerpunktkoordinaten erstellt.
Nett ! Es kann sogar verbessert werden, die Point3f / Point2f-Tupel von javax.vecmath zu verwenden, um die Dateneingabe besser zu handhaben.
Alex Byrth
10
Ein einfacher Weg ist:
Finden Sie die Vektoren, die den Punkt mit jedem der drei Eckpunkte des Dreiecks verbinden, und addieren Sie die Winkel zwischen diesen Vektoren. Wenn die Summe der Winkel 2 * pi ist, liegt der Punkt innerhalb des Dreiecks.
Zwei gute Websites, die Alternativen erklären, sind:
Ähm, diese Methode ist nicht gerade effizient und sehr anfällig für numerische Fehler ...
Kornel Kisielewicz
Es ist genau das Gegenteil, es ist sehr ineffizient :-) Es ist jedoch nur ein einfacher Weg, der einfach zu implementieren ist. Können Sie ein Beispiel für einen numerischen Fehler nennen, den dies verursachen würde?
Simon P Stevens
Während mir dies einfach die beste aller Antworten unter diesem Thema zu sein scheint, schätze ich, dass die Punkte an den Rändern des Dreiecks so berechnet sind, dass sie in das Dreieck aufgenommen werden, und Sie haben keine solide Kontrolle darüber.
Reduzieren Sie den
zu überprüfen, ob es genau 2pi ist, ist numerisch unmöglich, da pi irrational ist. Sie müssen jedoch nur prüfen, ob sich die Winkel zu etwas Größerem als pi addieren.
lonewarrior556
10
Durch Verwendung der analytischen Lösung für die Schwerpunktkoordinaten (von Andreas Brinck hervorgehoben ) und:
Verteilen der Multiplikation nicht auf die in Klammern gesetzten Begriffe
Vermeiden Sie es, mehrmals dieselben Begriffe zu berechnen, indem Sie sie speichern
Reduzierung der Vergleiche (wie von Coproc und Thomas Eding hervorgehoben )
Man kann die Anzahl der "kostspieligen" Operationen minimieren:
function ptInTriangle(p, p0, p1, p2){var dX = p.x-p2.x;var dY = p.y-p2.y;var dX21 = p2.x-p1.x;var dY12 = p1.y-p2.y;var D = dY12*(p0.x-p2.x)+ dX21*(p0.y-p2.y);var s = dY12*dX + dX21*dY;var t =(p2.y-p0.y)*dX +(p0.x-p2.x)*dY;if(D<0)return s<=0&& t<=0&& s+t>=D;return s>=0&& t>=0&& s+t<=D;}
Code kann in Perro Azul jsfiddle eingefügt werden oder versuchen Sie es, indem Sie unten auf "Code-Snippet ausführen" klicken
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script><pre>Click: place the point.
Double click: random triangle.</pre><preid="result"></pre><canvaswidth="500"height="500"></canvas>
Dies ist ein guter Vergleich mit der Kornel Kisielewicz- Lösung (25 Rückrufe, 1 Speicher, 15 Subtraktionen, 6 Multiplikationen, 5 Vergleiche) und könnte sogar noch besser sein, wenn eine Erkennung im Uhrzeigersinn / gegen den Uhrzeigersinn erforderlich ist (was 6 Rückrufe, 1 Addition, 2 Subtraktionen erfordert , 2 Multiplikationen und 1 Vergleich an sich unter Verwendung der analytischen Lösungsdeterminante, wie durch rhgb ) hervorgehoben.
Ich habe den Code so wie er ist getestet und er funktioniert bei mir nicht (Beispiel p -4.69317198, -6.99191951 p0 -7.05846786 0.596718192 p1 -6.8703599 -2.36565161 p2 -4.69317198, -6.99191951)
Giovanni Funchal
@GiovanniFunchal Seltsam, Ihr Beispiel funktioniert für mich sowohl in der jsfiddle (ersetzen Sie die anfänglichen "Punkt" - und "Dreieck" -Definitionen) als auch in meiner lokalen Python-Implementierung. Probleme mit der numerischen Genauigkeit (versuchen Sie, einige Dezimalstellen zu entfernen)?
Cédric Dufour
1
Ihr scheint der schnellste in meinem Test zu sein: jsfiddle.net/eyal/gxw3632c/27 . Der Unterschied zwischen allen Methoden ist jedoch recht gering.
Eyal
Versuchen Sie es mit Dreieck (-1, -1), (1, -1), (0,1) und Punkt (0, -1). Gibt false zurück, wenn true zurückgegeben werden soll, da s (2) + t (2)> d (2). Es scheint, dass etwas mit der Mathematik an den Kanten des Dreiecks nicht stimmt, da der Punkt p genau an der Grenze zwischen p0 und p1 liegt und es nicht einfach ist, ein <in ein <= oder so etwas umzuwandeln.
Devnullicus
5
Was ich tue, ist die drei Gesichtsnormalen vorberechnen,
in 3D durch Kreuzprodukt des Seitenvektors und des Gesichtsnormalenvektors.
in 2D durch einfaches Austauschen und Negieren von Komponenten,
dann ist innen / außen für eine Seite, wenn ein Punktprodukt der Seitennormalen und des Scheitelpunkt-zu-Punkt-Vektors das Vorzeichen ändert. Wiederholen Sie dies für zwei andere (oder mehr) Seiten.
Leistungen:
Vieles ist vorberechnet und eignet sich daher hervorragend für Mehrpunkttests an demselben Dreieck.
frühzeitige Ablehnung des allgemeinen Falls von mehr äußeren als inneren Punkten. (Auch wenn die Punktverteilung auf eine Seite gewichtet ist, kann diese Seite zuerst getestet werden.)
defPointInsideTriangle2(pt,tri):'''checks if point pt(2) is inside triangle tri(3x2). @Developer'''
a =1/(-tri[1,1]*tri[2,0]+tri[0,1]*(-tri[1,0]+tri[2,0])+ \
tri[0,0]*(tri[1,1]-tri[2,1])+tri[1,0]*tri[2,1])
s = a*(tri[2,0]*tri[0,1]-tri[0,0]*tri[2,1]+(tri[2,1]-tri[0,1])*pt[0]+ \
(tri[0,0]-tri[2,0])*pt[1])if s<0:returnFalseelse: t = a*(tri[0,0]*tri[1,1]-tri[1,0]*tri[0,1]+(tri[0,1]-tri[1,1])*pt[0]+ \
(tri[1,0]-tri[0,0])*pt[1])return((t>0)and(1-s-t>0))
Ich konnte diese Arbeit nicht ausführen, zum Beispiel für den Punkt im Dreieck [(0,0), (3,0), (3,4)], weder für die Punkte (1,1) noch für (0) , 0) positiv testen. Ich habe es mit Dreieckspunkten im Uhrzeigersinn und gegen den Uhrzeigersinn versucht.
ThorSummoner
3
Wenn Sie auf der Suche nach Geschwindigkeit sind, finden Sie hier ein Verfahren, das Ihnen helfen kann.
Sortieren Sie die Dreiecksscheitelpunkte nach ihren Ordinaten. Dies erfordert im schlimmsten Fall drei Vergleiche. Sei Y0, Y1, Y2 die drei sortierten Werte. Indem Sie drei Horizontale durchziehen, teilen Sie die Ebene in zwei Halbebenen und zwei Platten. Sei Y die Ordinate des Abfragepunktes.
if Y < Y1
if Y <= Y0 -> the point lies in the upper half plane, outside the triangle; you are done
else Y > Y0 -> the point lies in the upper slab
else
if Y >= Y2 -> the point lies in the lower half plane, outside the triangle; you are done
else Y < Y2 -> the point lies in the lower slab
Kostet zwei weitere Vergleiche. Wie Sie sehen, wird eine schnelle Zurückweisung für Punkte außerhalb der "Begrenzungsplatte" erreicht.
Optional können Sie links und rechts einen Test für die Abszissen zur schnellen Ablehnung durchführen (X <= X0' or X >= X2' ). Dadurch wird gleichzeitig ein schneller Bounding-Box-Test durchgeführt, aber Sie müssen auch die Abszissen sortieren.
Schließlich müssen Sie das Vorzeichen des angegebenen Punkts in Bezug auf die beiden Seiten des Dreiecks berechnen, die die betreffende Platte (oben oder unten) begrenzen. Der Test hat die Form:
Die vollständige Erörterung von i, j, kKombinationen (es gibt sechs davon, basierend auf dem Ergebnis der Sortierung) liegt außerhalb des Rahmens dieser Antwort und wird "dem Leser als Übung überlassen". Aus Gründen der Effizienz sollten sie fest codiert sein.
Wenn Sie der Meinung sind, dass diese Lösung komplex ist, beachten Sie, dass es sich hauptsächlich um einfache Vergleiche (von denen einige vorberechnet werden können) sowie um 6 Subtraktionen und 4 Multiplikationen handelt, falls der Bounding-Box-Test fehlschlägt. Die letztgenannten Kosten sind schwer zu übertreffen, da Sie im schlimmsten Fall nicht vermeiden können, den Testpunkt mit zwei Seiten zu vergleichen (keine Methode in anderen Antworten hat geringere Kosten, einige verschlimmern sie, wie 15 Subtraktionen und 6 Multiplikationen, manchmal Divisionen).
UPDATE: Schneller mit einer Schertransformation
Wie oben erläutert, können Sie den Punkt innerhalb eines der vier horizontalen Bänder, die durch die drei Scheitelpunkt-Ordinaten begrenzt sind, mithilfe von zwei Vergleichen schnell lokalisieren.
Optional können Sie ein oder zwei zusätzliche X-Tests durchführen, um die Unversehrtheit des Begrenzungsrahmens (gepunktete Linien) zu überprüfen.
Betrachten Sie dann die "Scher" -Transformation, die durch gegeben ist X'= X - m Y, Y' = Y, wobei mdie Steigung DX/DYfür die höchste Kante ist. Diese Transformation macht diese Seite des Dreiecks vertikal. Und da Sie wissen, auf welcher Seite der mittleren Horizontalen Sie sich befinden, reicht es aus, das Zeichen in Bezug auf eine einzelne Seite des Dreiecks zu testen.
Angenommen, Sie haben die Steigung msowie die X'Eckpunkte für die gescherten Dreiecke und die Koeffizienten der Gleichungen der Seiten als vorberechnet , X = m Y + pbenötigen Sie im schlimmsten Fall
zwei Ordinatenvergleiche zur vertikalen Klassifizierung;
optional ein oder zwei Abszissenvergleiche zur Begrenzung des Begrenzungsrahmens;
Berechnung von X' = X - m Y ;
ein oder zwei Vergleiche mit den Abszissen des gescherten Dreiecks;
Ein-Zeichen-Test X >< m' Y + p'gegen die relevante Seite des gescherten Dreiecks.
Wenn Sie die Koordinaten der drei Eckpunkte und die Koordinaten des spezifischen Punkts kennen, können Sie die Fläche des gesamten Dreiecks ermitteln. Berechnen Sie anschließend die Fläche der drei Dreiecksegmente (ein Punkt ist der angegebene Punkt und die anderen beiden sind zwei beliebige Eckpunkte des Dreiecks). So erhalten Sie die Fläche der drei Dreiecksegmente. Wenn die Summe dieser Flächen gleich der Gesamtfläche ist (die Sie zuvor erhalten haben), sollte sich der Punkt innerhalb des Dreiecks befinden. Andernfalls befindet sich der Punkt nicht innerhalb des Dreiecks. Das sollte funktionieren. Wenn es irgendwelche Probleme gibt, lass es mich wissen. Danke dir.
Das Plotten erfordert viel Zeit, aber dieses Raster wird in 0,0195319652557 Sekunden gegen 0,0844349861145 Sekunden des Entwicklercodes getestet .
Zum Schluss der Codekommentar:
# Using barycentric coordintes, any point inside can be described as:# X = p0.x * r + p1.x * s + p2.x * t# Y = p0.y * r + p1.y * s + p2.y * t# with:# r + s + t = 1 and 0 < r,s,t < 1# then: r = 1 - s - t# and then:# X = p0.x * (1 - s - t) + p1.x * s + p2.x * t# Y = p0.y * (1 - s - t) + p1.y * s + p2.y * t## X = p0.x + (p1.x-p0.x) * s + (p2.x-p0.x) * t# Y = p0.y + (p1.y-p0.y) * s + (p2.y-p0.y) * t## X - p0.x = (p1.x-p0.x) * s + (p2.x-p0.x) * t# Y - p0.y = (p1.y-p0.y) * s + (p2.y-p0.y) * t## we have to solve:## [ X - p0.x ] = [(p1.x-p0.x) (p2.x-p0.x)] * [ s ]# [ Y - p0.Y ] [(p1.y-p0.y) (p2.y-p0.y)] [ t ]## ---> b = A*x ; ---> x = A^-1 * b# # [ s ] = A^-1 * [ X - p0.x ]# [ t ] [ Y - p0.Y ]## A^-1 = 1/D * adj(A)## The adjugate of A:## adj(A) = [(p2.y-p0.y) -(p2.x-p0.x)]# [-(p1.y-p0.y) (p1.x-p0.x)]## The determinant of A:## D = (p1.x-p0.x)*(p2.y-p0.y) - (p1.y-p0.y)*(p2.x-p0.x)## Then:## s_p = { (p2.y-p0.y)*(X - p0.x) - (p2.x-p0.x)*(Y - p0.Y) }# t_p = { (p1.x-p0.x)*(Y - p0.Y) - (p1.y-p0.y)*(X - p0.x) }## s = s_p / D# t = t_p / D## Recovering r:## r = 1 - (s_p + t_p)/D## Since we only want to know if it is insidem not the barycentric coordinate:## 0 < 1 - (s_p + t_p)/D < 1# 0 < (s_p + t_p)/D < 1# 0 < (s_p + t_p) < D## The condition is:# if D > 0:# s_p > 0 and t_p > 0 and (s_p + t_p) < D# else:# s_p < 0 and t_p < 0 and (s_p + t_p) > D## s_p = { dY20*dX - dX20*dY }# t_p = { dX10*dY - dY10*dX }# D = dX10*dY20 - dY10*dX20
function triangleContains(ax, ay, bx, by, cx, cy, x, y){let det =(bx - ax)*(cy - ay)-(by - ay)*(cx - ax)return det *((bx - ax)*(y - ay)-(by - ay)*(x - ax))>0&&
det *((cx - bx)*(y - by)-(cy - by)*(x - bx))>0&&
det *((ax - cx)*(y - cy)-(ay - cy)*(x - cx))>0}let width =500, height =500// clockwiselet triangle1 ={
A :{ x:10, y:-10},
C :{ x:20, y:100},
B :{ x:-90, y:10},
color:'#f00',}// counter clockwiselet triangle2 ={
A :{ x:20, y:-60},
B :{ x:90, y:20},
C :{ x:20, y:60},
color:'#00f',}let scale =2let mouse ={ x:0, y:0}// DRAW >let wrapper = document.querySelector('div.wrapper')
wrapper.onmousemove =({ layerX:x, layerY:y })=>{
x -= width /2
y -= height /2
x /= scale
y /= scale
mouse.x = x
mouse.y = y
drawInteractive()}function drawArrow(ctx, A, B){let v = normalize(sub(B, A),3)let I = center(A, B)let p
p = add(I, rotate(v,90), v)
ctx.moveTo(p.x, p.y)
ctx.lineTo(I.x, I .y)
p = add(I, rotate(v,-90), v)
ctx.lineTo(p.x, p.y)}function drawTriangle(ctx,{ A, B, C, color }){
ctx.beginPath()
ctx.moveTo(A.x, A.y)
ctx.lineTo(B.x, B.y)
ctx.lineTo(C.x, C.y)
ctx.closePath()
ctx.fillStyle = color +'6'
ctx.strokeStyle = color
ctx.fill()
drawArrow(ctx, A, B)
drawArrow(ctx, B, C)
drawArrow(ctx, C, A)
ctx.stroke()}function contains({ A, B, C }, P){return triangleContains(A.x, A.y, B.x, B.y, C.x, C.y, P.x, P.y)}function resetCanvas(canvas){
canvas.width = width
canvas.height = height
let ctx = canvas.getContext('2d')
ctx.resetTransform()
ctx.clearRect(0,0, width, height)
ctx.setTransform(scale,0,0, scale, width/2, height/2)}function drawDots(){let canvas = document.querySelector('canvas#dots')let ctx = canvas.getContext('2d')
resetCanvas(canvas)let count =1000for(let i =0; i < count; i++){let x = width *(Math.random()-.5)let y = width *(Math.random()-.5)
ctx.beginPath()
ctx.ellipse(x, y,1,1,0,0,2*Math.PI)if(contains(triangle1,{ x, y })){
ctx.fillStyle ='#f00'}elseif(contains(triangle2,{ x, y })){
ctx.fillStyle ='#00f'}else{
ctx.fillStyle ='#0003'}
ctx.fill()}}function drawInteractive(){let canvas = document.querySelector('canvas#interactive')let ctx = canvas.getContext('2d')
resetCanvas(canvas)
ctx.beginPath()
ctx.moveTo(0,-height/2)
ctx.lineTo(0, height/2)
ctx.moveTo(-width/2,0)
ctx.lineTo(width/2,0)
ctx.strokeStyle ='#0003'
ctx.stroke()
drawTriangle(ctx, triangle1)
drawTriangle(ctx, triangle2)
ctx.beginPath()
ctx.ellipse(mouse.x, mouse.y,4,4,0,0,2*Math.PI)if(contains(triangle1, mouse)){
ctx.fillStyle = triangle1.color +'a'
ctx.fill()}elseif(contains(triangle2, mouse)){
ctx.fillStyle = triangle2.color +'a'
ctx.fill()}else{
ctx.strokeStyle ='black'
ctx.stroke()}}
drawDots()
drawInteractive()// trigofunction add(...points){let x =0, y =0for(let point of points){
x += point.x
y += point.y
}return{ x, y }}function center(...points){let x =0, y =0for(let point of points){
x += point.x
y += point.y
}
x /= points.length
y /= points.length
return{ x, y }}function sub(A, B){let x = A.x - B.x
let y = A.y - B.y
return{ x, y }}function normalize({ x, y }, length =10){let r = length /Math.sqrt(x * x + y * y)
x *= r
y *= r
return{ x, y }}function rotate({ x, y }, angle =90){let length =Math.sqrt(x * x + y * y)
angle *=Math.PI /180
angle +=Math.atan2(y, x)
x = length *Math.cos(angle)
y = length *Math.sin(angle)return{ x, y }}
*{margin:0;}
html {font-family: monospace;}
body {padding:32px;}
span.red {color:#f00;}
span.blue {color:#00f;}
canvas {position: absolute;border: solid 1px#ddd;}
<p><spanclass="red">red triangle</span> is clockwise</p><p><spanclass="blue">blue triangle</span> is couter clockwise</p><br><divclass="wrapper"><canvasid="dots"></canvas><canvasid="interactive"></canvas></div>
Ich verwende hier die gleiche Methode wie oben beschrieben: Ein Punkt befindet sich innerhalb von ABC, wenn er sich jeweils auf der "gleichen" Seite jeder Linie AB, BC, CA befindet.
Ich habe diesen Code müde und es funktioniert nicht. Es wird immer False zurückgegeben.
xApple
hmmm ... du hast wahrscheinlich einen Fehler gemacht. Hier ist eine Geige mit dieser Funktion: jsfiddle.net/jniac/rctb3gfL
Joseph Merdrignac
Ich habe Ihre Python-Antwort gesehen. Wir verwenden dieselbe Methode, wenn ich noch eine Zeile verwende.let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay) ) verwende, dient dies zur Bestimmung der Dreieckswicklungsreihenfolge, sodass die Methode mit CW- und CCW-Dreiecken funktioniert (siehe jsFiddle).
Joseph Merdrignac
1
hm ich habe einen Fehler gemacht, schrieb ich: let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)stattdessen let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)ist dies behoben, danke für die Berichterstattung
Joseph Merdrignac
2
Ich möchte nur eine einfache Vektormathematik verwenden, um die Lösung der Schwerpunktkoordinaten zu erklären, die Andreas gegeben hat. Es wird viel einfacher zu verstehen sein.
Bereich A ist definiert als ein beliebiger Vektor, der durch s * v02 + t * v01 gegeben ist, mit der Bedingung s> = 0 und t> = 0. Wenn sich ein Punkt innerhalb des Dreiecks v0, v1, v2 befindet, muss er innerhalb von Bereich A liegen.
Wenn weiter eingeschränkt, gehört s und t zu [0, 1]. Wir erhalten den Bereich B, der alle Vektoren von s * v02 + t * v01 enthält, wobei die Bedingung s, t zu [0, 1] gehört. Es ist anzumerken, dass der untere Teil des Bereichs B der Spiegel des Dreiecks v0, v1, v2 ist. Das Problem tritt auf, wenn wir bestimmte Bedingungen von s und t angeben können, um den unteren Teil von Bereich B weiter auszuschließen.
Angenommen, wir geben einen Wert s an und t ändert sich in [0, 1]. Im folgenden Bild befindet sich Punkt p am Rand von v1v2. Alle Vektoren von s * v02 + t * v01, die durch einfache Vektorsumme entlang der Strichlinie liegen. Bei v1v2 und dem Strichlinienkreuzungspunkt p haben wir:
wir erhalten 1 - s = tp, dann 1 = s + tp. Wenn irgendein t> tp, welches 1 <s + t, wo sich auf der doppelten Strichlinie befindet, der Vektor außerhalb des Dreiecks liegt, jedes t <= tp, welches 1> = s + t, wo sich auf der einzelnen Strichlinie befindet, ist der Vektor innerhalb des Dreiecks.
Wenn wir dann in [0, 1] ein s angeben, muss das entsprechende t für den Vektor innerhalb des Dreiecks 1> = s + t erfüllen.
Schließlich erhalten wir v = s * v02 + t * v01, v befindet sich innerhalb des Dreiecks mit der Bedingung s, t, s + t gehört zu [0, 1]. Dann übersetzen wir auf Punkt, den wir haben
p - p0 = s * (p1 - p0) + t * (p2 - p0), mit s, t, s + t in [0, 1]
Dies ist die gleiche Lösung wie Andreas 'Lösung zur Lösung des Gleichungssystems. p = p0 + s * (p1 - p0) + t * (p2 - p0), wobei s, t, s + t zu [0, 1] gehören.
Sie können einfach sagen, dass Sie den durch die drei Eckpunkte definierten lokalen Rahmen verwenden, sodass die Seiten zu s = 0, t = 0 und s + t = 1 werden. Die affine Koordinatentransformation ist eine bekannte Operation der linearen Algebra.
Yves Daoust
2
Hier ist eine Lösung in Python, die effizient und dokumentiert ist und drei Unittests enthält. Es ist von professioneller Qualität und kann so wie es ist in Form eines Moduls in Ihr Projekt eingefügt werden.
import unittest
###############################################################################def point_in_triangle(point, triangle):"""Returns True if the point is inside the triangle
and returns False if it falls outside.
- The argument *point* is a tuple with two elements
containing the X,Y coordinates respectively.
- The argument *triangle* is a tuple with three elements each
element consisting of a tuple of X,Y coordinates.
It works like this:
Walk clockwise or counterclockwise around the triangle
and project the point onto the segment we are crossing
by using the dot product.
Finally, check that the vector created is on the same side
for each of the triangle's segments.
"""# Unpack arguments
x, y = point
ax, ay = triangle[0]
bx, by = triangle[1]
cx, cy = triangle[2]# Segment A to B
side_1 =(x - bx)*(ay - by)-(ax - bx)*(y - by)# Segment B to C
side_2 =(x - cx)*(by - cy)-(bx - cx)*(y - cy)# Segment C to A
side_3 =(x - ax)*(cy - ay)-(cx - ax)*(y - ay)# All the signs must be positive or all negativereturn(side_1 <0.0)==(side_2 <0.0)==(side_3 <0.0)###############################################################################classTestPointInTriangle(unittest.TestCase):
triangle =((22,8),(12,55),(7,19))def test_inside(self):
point =(15,20)
self.assertTrue(point_in_triangle(point, self.triangle))def test_outside(self):
point =(1,7)
self.assertFalse(point_in_triangle(point, self.triangle))def test_border_case(self):"""If the point is exactly on one of the triangle's edges,
we consider it is inside."""
point =(7,19)
self.assertTrue(point_in_triangle(point, self.triangle))###############################################################################if __name__ =="__main__":
suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestPointInTriangle)
unittest.TextTestRunner().run(suite)
Es gibt einen zusätzlichen optionalen grafischen Test für den obigen Algorithmus, um seine Gültigkeit zu bestätigen:
import random
from matplotlib import pyplot
from triangle_test import point_in_triangle
################################################################################ The area #
size_x =64
size_y =64# The triangle #
triangle =((22,8),(12,55),(7,19))# Number of random points #
count_points =10000# Prepare the figure #
figure = pyplot.figure()
axes = figure.add_subplot(111, aspect='equal')
axes.set_title("Test the 'point_in_triangle' function")
axes.set_xlim(0, size_x)
axes.set_ylim(0, size_y)# Plot the triangle #from matplotlib.patches importPolygon
axes.add_patch(Polygon(triangle, linewidth=1, edgecolor='k', facecolor='none'))# Plot the points #for i in range(count_points):
x = random.uniform(0, size_x)
y = random.uniform(0, size_y)if point_in_triangle((x,y), triangle): pyplot.plot(x, y,'.g')else: pyplot.plot(x, y,'.b')# Save it #
figure.savefig("point_in_triangle.pdf")
Es gibt lästige Kantenbedingungen, bei denen ein Punkt genau auf der gemeinsamen Kante zweier benachbarter Dreiecke liegt. Der Punkt kann nicht in beiden oder keinem der Dreiecke liegen. Sie benötigen eine willkürliche, aber konsistente Art, den Punkt zuzuweisen. Zeichnen Sie beispielsweise eine horizontale Linie durch den Punkt. Wenn sich die Linie mit der anderen Seite des Dreiecks auf der rechten Seite schneidet, wird der Punkt so behandelt, als ob er sich innerhalb des Dreiecks befindet. Wenn sich der Schnittpunkt links befindet, befindet sich der Punkt außerhalb.
Wenn die Linie, auf der der Punkt liegt, horizontal ist, verwenden Sie oben / unten.
Wenn sich der Punkt auf dem gemeinsamen Scheitelpunkt mehrerer Dreiecke befindet, verwenden Sie das Dreieck, mit dessen Mittelpunkt der Punkt den kleinsten Winkel bildet.
Mehr Spaß: Drei Punkte können in einer geraden Linie (null Grad) liegen, zum Beispiel (0,0) - (0,10) - (0,5). Bei einem Triangulationsalgorithmus muss das "Ohr" (0,10) abgeschnitten werden, wobei das erzeugte "Dreieck" der entartete Fall einer geraden Linie ist.
Dies ist das einfachste Konzept, um festzustellen, ob sich ein Punkt innerhalb oder außerhalb des Dreiecks oder auf einem Arm eines Dreiecks befindet.
Die Bestimmung eines Punktes erfolgt innerhalb eines Dreiecks durch Determinanten:
Der einfachste Arbeitscode:
#-*- coding: utf-8 -*-import numpy as np
tri_points =[(1,1),(2,3),(3,1)]def pisinTri(point,tri_points):Dx,Dy= point
A,B,C = tri_points
Ax,Ay= A
Bx,By= B
Cx,Cy= C
M1 = np.array([[Dx-Bx,Dy-By,0],[Ax-Bx,Ay-By,0],[1,1,1]])
M2 = np.array([[Dx-Ax,Dy-Ay,0],[Cx-Ax,Cy-Ay,0],[1,1,1]])
M3 = np.array([[Dx-Cx,Dy-Cy,0],[Bx-Cx,By-Cy,0],[1,1,1]])
M1 = np.linalg.det(M1)
M2 = np.linalg.det(M2)
M3 = np.linalg.det(M3)print(M1,M2,M3)if(M1 ==0or M2 ==0or M3 ==0):print("Point: ",point," lies on the arms of Triangle")elif((M1 >0and M2 >0and M3 >0)or(M1 <0and M2 <0and M3 <0)):#if products is non 0 check if all of their sign is sameprint("Point: ",point," lies inside the Triangle")else:print("Point: ",point," lies outside the Triangle")print("Vertices of Triangle: ",tri_points)
points =[(0,0),(1,1),(2,3),(3,1),(2,2),(4,4),(1,0),(0,4)]for c in points:
pisinTri(c,tri_points)
Der einfachste Weg und es funktioniert mit allen Arten von Dreiecken ist einfach die Winkel der P-Punkt A-, B-, C-Punktwinkel zu bestimmen. Wenn eine des Winkels größer als 180,0 Grad , dann ist es draußen, wenn 180.0 dann ist es auf dem Umfang und wenn acos auf Sie zu betrügen und weniger als 180,0 dann ist es inside.Take ein Blick für das Verständnis http: // Mathe-Physik -psychology.blogspot.hu/2015/01/earlish-determination-that-point-is.html
Ehrlich gesagt ist es so einfach wie die Antwort von Simon P. Steven aber mit diesem Ansatz haben Sie keine solide Kontrolle darüber, ob die Punkte an den Kanten des Dreiecks enthalten sein sollen oder nicht.
Mein Ansatz ist etwas anders, aber sehr einfach. Betrachten Sie das folgende Dreieck;
Um den Punkt im Dreieck zu haben, müssen wir 3 Bedingungen erfüllen
Der ACE-Winkel (grün) sollte kleiner sein als der ACB-Winkel (rot).
Der EZB-Winkel (blau) sollte kleiner sein als der ACB-Winkel (rot).
Punkt E und Punkt C sollten das gleiche Vorzeichen haben, wenn ihre x- und y-Werte auf die Gleichung von | AB | angewendet werden Linie.
Bei dieser Methode haben Sie die volle Kontrolle darüber, den Punkt an den Kanten einzeln einzuschließen oder auszuschließen. Sie können also überprüfen, ob sich ein Punkt im Dreieck befindet, der nur | AC | enthält Kante zum Beispiel.
Meine Lösung in JavaScript wäre also wie folgt:
function isInTriangle(t,p){function isInBorder(a,b,c,p){var m =(a.y - b.y)/(a.x - b.x);// calculate the slopereturnMath.sign(p.y - m*p.x + m*a.x - a.y)===Math.sign(c.y - m*c.x + m*a.x - a.y);}function findAngle(a,b,c){// calculate the C angle from 3 points.var ca =Math.hypot(c.x-a.x, c.y-a.y),// ca edge length
cb =Math.hypot(c.x-b.x, c.y-b.y),// cb edge length
ab =Math.hypot(a.x-b.x, a.y-b.y);// ab edge lengthreturnMath.acos((ca*ca + cb*cb - ab*ab)/(2*ca*cb));// return the C angle}var pas = t.slice(1).map(tp => findAngle(p,tp,t[0])),// find the angle between (p,t[0]) with (t[1],t[0]) & (t[2],t[0])
ta = findAngle(t[1],t[2],t[0]);return pas[0]< ta && pas[1]< ta && isInBorder(t[1],t[2],t[0],p);}var triangle =[{x:3, y:4},{x:10, y:8},{x:6, y:10}],
point1 ={x:3, y:9},
point2 ={x:7, y:9};
console.log(isInTriangle(triangle,point1));
console.log(isInTriangle(triangle,point2));
Effizienter geht es nicht! Jede Seite eines Dreiecks kann eine unabhängige Position und Ausrichtung haben, daher sind drei Berechnungen erforderlich: l1, l2 und l3 werden definitiv mit jeweils 2 Multiplikationen benötigt. Sobald l1, l2 und l3 bekannt sind, sind nur wenige grundlegende Vergleiche und boolesche Operationen entfernt.
pointInTriangle(p, p0, p1, p2) - für Dreiecke gegen den Uhrzeigersinn
pointInTriangle(p, p0, p1, p2) - für Dreiecke im Uhrzeigersinn
Schauen Sie in jsFiddle (Leistungstest eingeschlossen), es gibt auch Wicklungsprüfung in einer separaten Funktion. Oder klicken Sie unten auf "Code-Snippet ausführen"
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script><buttonid="performance">Run performance test (open console)</button><pre>Click: place the point.
Double click: random triangle.</pre><preid="result"></pre><canvaswidth="500"height="500"></canvas>
bool point2Dtriangle(double e,double f,double a,double b,double c,double g,double h,double i,double v,double w){/* inputs: e=point.x, f=point.y
a=triangle.Ax, b=triangle.Bx, c=triangle.Cx
g=triangle.Ay, h=triangle.By, i=triangle.Cy */
v =1-(f *(b - c)+ h *(c - e)+ i *(e - b))/(g *(b - c)+ h *(c - a)+ i *(a - b));
w =(f *(a - b)+ g *(b - e)+ h *(e - a))/(g *(b - c)+ h *(c - a)+ i *(a - b));if(*v >-0.0&&*v <1.0000001&&*w >-0.0&&*w <*v)returntrue;//is insideelsereturnfalse;//is outsidereturn0;}
Nahezu perfekte kartesische Koordinaten, die aus dem Schwerpunkt konvertiert wurden, werden in * v (x) - und * w (y) -Doppelwerten exportiert. Beide Export-Doubles sollten in jedem Fall vorher ein * Zeichen haben, wahrscheinlich: * v und * w Code können auch für das andere Dreieck eines Vierecks verwendet werden. Hiermit signiert schrieb nur Dreieck abc aus dem Uhrzeigersinn abcd Quad.
A---B
|..\\.o|
|....\\.|
D---C
Der o-Punkt befindet sich innerhalb des ABC-Dreiecks zum Testen mit dem zweiten Dreieck. Rufen Sie diese Funktion in CDA-Richtung auf. Die Ergebnisse sollten nach *v=1-*v;und *w=1-*w;für das Viereck korrekt sein
Ich brauchte einen Punkt in der Dreiecksprüfung in einer "kontrollierbaren Umgebung", wenn Sie absolut sicher sind, dass die Dreiecke im Uhrzeigersinn sind. Also nahm ich Perro Azul 's jsfiddle und modifizierte es, wie von coproc für solche Fälle vorgeschlagen; Außerdem wurden redundante 0,5- und 2-Multiplikationen entfernt, da sie sich nur gegenseitig aufheben.
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script><pre>Click: place the point.
Double click: random triangle.</pre><preid="result"></pre><canvaswidth="500"height="500"></canvas>
Eine der einfachsten Möglichkeiten, um zu überprüfen, ob der durch die Eckpunkte des Dreiecks (x1, y1), (x2, y2), (x3, y3) gebildete Bereich positiv ist oder nicht.
Die Fläche kann nach folgender Formel berechnet werden:
Antworten:
Im Allgemeinen prüft der einfachste (und recht optimale) Algorithmus, auf welcher Seite der Halbebene, die durch die Kanten erzeugt wird, der Punkt liegt.
Hier finden Sie einige hochwertige Informationen in diesem Thema zu GameDev , einschließlich Leistungsproblemen.
Und hier ist ein Code, mit dem Sie beginnen können:
quelle
Lösen Sie das folgende Gleichungssystem:
Der Punkt
p
liegt innerhalb des Dreiecks, wenn0 <= s <= 1
und0 <= t <= 1
unds + t <= 1
.s
,t
Und1 - s - t
sind , um die genannten baryzentrischen Koordinaten des Punktesp
.quelle
s + t <= 1
implizierts <= 1
undt <= 1
obs >= 0
undt >= 0
.Ich stimme Andreas Brinck zu , baryzentrische Koordinaten sind für diese Aufgabe sehr praktisch. Beachten Sie, dass nicht jedes Mal ein Gleichungssystem gelöst werden muss: Bewerten Sie einfach die analytische Lösung. Unter Verwendung der Andreas -Notation lautet die Lösung:
Wo
Area
ist der (signierte) Bereich des Dreiecks:Nur bewerten
s
,t
und1-s-t
. Der Punktp
liegt genau dann innerhalb des Dreiecks, wenn alle positiv sind.BEARBEITEN: Beachten Sie, dass der obige Ausdruck für den Bereich davon ausgeht, dass die Nummerierung des Dreiecksknotens gegen den Uhrzeigersinn erfolgt. Wenn die Nummerierung im Uhrzeigersinn erfolgt, gibt dieser Ausdruck einen negativen Bereich zurück (jedoch mit der richtigen Größe). Der Test selbst (
s>0 && t>0 && 1-s-t>0
) hängt jedoch nicht von der Richtung der Nummerierung ab, da die obigen Ausdrücke, die mit multipliziert werden,1/(2*Area)
auch das Vorzeichen ändern, wenn sich die Ausrichtung des Dreiecksknotens ändert.EDIT 2: Für eine noch bessere Recheneffizienz siehe Coprocs Kommentar unten (der darauf hinweist , dass, wenn die Ausrichtung der Dreiecksknoten (im oder gegen den Uhrzeigersinn) vorher bekannt ist, die Division durch
2*Area
in den Ausdrücken fürs
und seint
kann vermieden). Siehe auch Perro Azul 's jsfiddle-Code in den Kommentaren unter Andreas Brincks Antwort.quelle
2*Area
, dh indem berechnet wirds´=2*|Area|*s
undt´=2*|Area|*t
(wenn die Ausrichtung der Punkte - im oder gegen den Uhrzeigersinn - nicht bekannt ist, das VorzeichenArea
von natürlich überprüft werden muss, aber ansonsten vielleicht nicht einmal müssen berechnet werden), da es zur Überprüfungs>0
ausreicht, zu überprüfens´>0
. Und anstatt zu überprüfen1-s-t>0
, reicht es aus, zu überprüfens´+t´<2*|Area|
.p0->p1->p2
ist , gegen den Uhrzeigersinn in kartesischen (die in der Regel im Uhrzeigersinn in Bildschirmkoordinaten ), dieArea
durch dieses Verfahren berechnet positiv sein wird.Ich habe diesen Code vor einem letzten Versuch mit Google geschrieben und diese Seite gefunden, also dachte ich, ich würde ihn teilen. Es ist im Grunde eine optimierte Version der Kisielewicz-Antwort. Ich habe mich auch mit der Barycentric-Methode befasst, aber nach dem Wikipedia-Artikel fällt es mir schwer zu erkennen, wie effizient sie ist (ich vermute, es gibt eine tiefere Äquivalenz). Auf jeden Fall hat dieser Algorithmus den Vorteil, dass keine Division verwendet wird. Ein mögliches Problem ist das Verhalten der Kantenerkennung in Abhängigkeit von der Ausrichtung.
In Worten lautet die Idee: Befindet sich der Punkt s links oder rechts von den Linien AB und AC? Wenn das stimmt, kann es nicht drinnen sein. Wenn falsch, ist es zumindest innerhalb der "Zapfen", die die Bedingung erfüllen. Da wir nun wissen, dass ein Punkt innerhalb eines Trigons (Dreiecks) auf derselben Seite von AB liegen muss wie BC (und auch CA), prüfen wir, ob sie sich unterscheiden. Wenn ja, kann s unmöglich drinnen sein, sonst muss s drinnen sein.
Einige Schlüsselwörter in den Berechnungen sind Linienhalbflächen und die Determinante (2x2-Kreuzprodukt). Vielleicht ist es pädagogischer, es als einen Punkt zu betrachten, der sich innerhalb derselben Linie (links oder rechts) zu jeder der Linien AB, BC und CA befindet. Der obige Weg schien jedoch für einige Optimierungen besser geeignet zu sein.
quelle
C # -Version der baryzentrischen Methode von andreasdr und Perro Azul. Beachten Sie, dass die Flächenberechnung , wenn vermieden werden können
s
undt
entgegengesetzte Vorzeichen haben. Ich habe das korrekte Verhalten mit einem ziemlich gründlichen Unit-Test überprüft.[ Bearbeiten ]
akzeptierte Änderungsvorschläge von @Pierre; Zeige Kommentare
quelle
Java-Version der baryzentrischen Methode:
Der obige Code funktioniert genau mit ganzen Zahlen, sofern keine Überläufe auftreten. Es funktioniert auch mit Dreiecken im Uhrzeigersinn und gegen den Uhrzeigersinn. Es funktioniert nicht mit kollinearen Dreiecken (aber Sie können dies überprüfen, indem Sie det == 0 testen).
Die baryzentrische Version ist am schnellsten, wenn Sie verschiedene Punkte mit demselben Dreieck testen möchten.
Die baryzentrische Version ist in den 3 Dreieckspunkten nicht symmetrisch, daher ist sie aufgrund von Gleitkomma-Rundungsfehlern wahrscheinlich weniger konsistent als die Rand-Halbebenen-Version von Kornel Kisielewicz.
Bildnachweis: Ich habe den obigen Code aus dem Wikipedia-Artikel über Schwerpunktkoordinaten erstellt.
quelle
Ein einfacher Weg ist:
Zwei gute Websites, die Alternativen erklären, sind:
Blackpawn und Wolfram
quelle
Durch Verwendung der analytischen Lösung für die Schwerpunktkoordinaten (von Andreas Brinck hervorgehoben ) und:
Man kann die Anzahl der "kostspieligen" Operationen minimieren:
Code kann in Perro Azul jsfiddle eingefügt werden oder versuchen Sie es, indem Sie unten auf "Code-Snippet ausführen" klicken
Führend zu:
Dies ist ein guter Vergleich mit der Kornel Kisielewicz- Lösung (25 Rückrufe, 1 Speicher, 15 Subtraktionen, 6 Multiplikationen, 5 Vergleiche) und könnte sogar noch besser sein, wenn eine Erkennung im Uhrzeigersinn / gegen den Uhrzeigersinn erforderlich ist (was 6 Rückrufe, 1 Addition, 2 Subtraktionen erfordert , 2 Multiplikationen und 1 Vergleich an sich unter Verwendung der analytischen Lösungsdeterminante, wie durch rhgb ) hervorgehoben.
quelle
Was ich tue, ist die drei Gesichtsnormalen vorberechnen,
in 3D durch Kreuzprodukt des Seitenvektors und des Gesichtsnormalenvektors.
in 2D durch einfaches Austauschen und Negieren von Komponenten,
dann ist innen / außen für eine Seite, wenn ein Punktprodukt der Seitennormalen und des Scheitelpunkt-zu-Punkt-Vektors das Vorzeichen ändert. Wiederholen Sie dies für zwei andere (oder mehr) Seiten.
Leistungen:
Vieles ist vorberechnet und eignet sich daher hervorragend für Mehrpunkttests an demselben Dreieck.
frühzeitige Ablehnung des allgemeinen Falls von mehr äußeren als inneren Punkten. (Auch wenn die Punktverteilung auf eine Seite gewichtet ist, kann diese Seite zuerst getestet werden.)
quelle
Hier ist eine effiziente Python- Implementierung:
und eine Beispielausgabe:
quelle
Wenn Sie auf der Suche nach Geschwindigkeit sind, finden Sie hier ein Verfahren, das Ihnen helfen kann.
Sortieren Sie die Dreiecksscheitelpunkte nach ihren Ordinaten. Dies erfordert im schlimmsten Fall drei Vergleiche. Sei Y0, Y1, Y2 die drei sortierten Werte. Indem Sie drei Horizontale durchziehen, teilen Sie die Ebene in zwei Halbebenen und zwei Platten. Sei Y die Ordinate des Abfragepunktes.
Kostet zwei weitere Vergleiche. Wie Sie sehen, wird eine schnelle Zurückweisung für Punkte außerhalb der "Begrenzungsplatte" erreicht.
Optional können Sie links und rechts einen Test für die Abszissen zur schnellen Ablehnung durchführen (
X <= X0' or X >= X2'
). Dadurch wird gleichzeitig ein schneller Bounding-Box-Test durchgeführt, aber Sie müssen auch die Abszissen sortieren.Schließlich müssen Sie das Vorzeichen des angegebenen Punkts in Bezug auf die beiden Seiten des Dreiecks berechnen, die die betreffende Platte (oben oder unten) begrenzen. Der Test hat die Form:
Die vollständige Erörterung von
i, j, k
Kombinationen (es gibt sechs davon, basierend auf dem Ergebnis der Sortierung) liegt außerhalb des Rahmens dieser Antwort und wird "dem Leser als Übung überlassen". Aus Gründen der Effizienz sollten sie fest codiert sein.Wenn Sie der Meinung sind, dass diese Lösung komplex ist, beachten Sie, dass es sich hauptsächlich um einfache Vergleiche (von denen einige vorberechnet werden können) sowie um 6 Subtraktionen und 4 Multiplikationen handelt, falls der Bounding-Box-Test fehlschlägt. Die letztgenannten Kosten sind schwer zu übertreffen, da Sie im schlimmsten Fall nicht vermeiden können, den Testpunkt mit zwei Seiten zu vergleichen (keine Methode in anderen Antworten hat geringere Kosten, einige verschlimmern sie, wie 15 Subtraktionen und 6 Multiplikationen, manchmal Divisionen).
UPDATE: Schneller mit einer Schertransformation
Wie oben erläutert, können Sie den Punkt innerhalb eines der vier horizontalen Bänder, die durch die drei Scheitelpunkt-Ordinaten begrenzt sind, mithilfe von zwei Vergleichen schnell lokalisieren.
Optional können Sie ein oder zwei zusätzliche X-Tests durchführen, um die Unversehrtheit des Begrenzungsrahmens (gepunktete Linien) zu überprüfen.
Betrachten Sie dann die "Scher" -Transformation, die durch gegeben ist
X'= X - m Y, Y' = Y
, wobeim
die SteigungDX/DY
für die höchste Kante ist. Diese Transformation macht diese Seite des Dreiecks vertikal. Und da Sie wissen, auf welcher Seite der mittleren Horizontalen Sie sich befinden, reicht es aus, das Zeichen in Bezug auf eine einzelne Seite des Dreiecks zu testen.Angenommen, Sie haben die Steigung
m
sowie dieX'
Eckpunkte für die gescherten Dreiecke und die Koeffizienten der Gleichungen der Seiten als vorberechnet ,X = m Y + p
benötigen Sie im schlimmsten FallX' = X - m Y
;X >< m' Y + p'
gegen die relevante Seite des gescherten Dreiecks.quelle
Wenn Sie die Koordinaten der drei Eckpunkte und die Koordinaten des spezifischen Punkts kennen, können Sie die Fläche des gesamten Dreiecks ermitteln. Berechnen Sie anschließend die Fläche der drei Dreiecksegmente (ein Punkt ist der angegebene Punkt und die anderen beiden sind zwei beliebige Eckpunkte des Dreiecks). So erhalten Sie die Fläche der drei Dreiecksegmente. Wenn die Summe dieser Flächen gleich der Gesamtfläche ist (die Sie zuvor erhalten haben), sollte sich der Punkt innerhalb des Dreiecks befinden. Andernfalls befindet sich der Punkt nicht innerhalb des Dreiecks. Das sollte funktionieren. Wenn es irgendwelche Probleme gibt, lass es mich wissen. Danke dir.
quelle
Andere Funktion in Python , schneller als die Entwicklermethode (zumindest für mich) und inspiriert von der Cédric Dufour- Lösung:
Sie können es testen mit:
Das Plotten erfordert viel Zeit, aber dieses Raster wird in 0,0195319652557 Sekunden gegen 0,0844349861145 Sekunden des Entwicklercodes getestet .
Zum Schluss der Codekommentar:
quelle
ptInTriang([11,45],[45, 45],[45, 45] ,[44, 45])
und es wird zurückkehren,true
obwohl es falsch istDa es keine JS-Antwort gibt,
Lösung im Uhrzeigersinn und gegen den Uhrzeigersinn:
BEARBEITEN: Es gab einen Tippfehler für die Det-Berechnung (
cy - ay
anstelle voncx - ax
), dies ist behoben.https://jsfiddle.net/jniac/rctb3gfL/
Ich verwende hier die gleiche Methode wie oben beschrieben: Ein Punkt befindet sich innerhalb von ABC, wenn er sich jeweils auf der "gleichen" Seite jeder Linie AB, BC, CA befindet.
quelle
let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)
) verwende, dient dies zur Bestimmung der Dreieckswicklungsreihenfolge, sodass die Methode mit CW- und CCW-Dreiecken funktioniert (siehe jsFiddle).let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)
stattdessenlet det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
ist dies behoben, danke für die BerichterstattungIch möchte nur eine einfache Vektormathematik verwenden, um die Lösung der Schwerpunktkoordinaten zu erklären, die Andreas gegeben hat. Es wird viel einfacher zu verstehen sein.
(1-s) | v0v2 | / | v0v2 | = tp | v0v1 | / | v0v1 |
wir erhalten 1 - s = tp, dann 1 = s + tp. Wenn irgendein t> tp, welches 1 <s + t, wo sich auf der doppelten Strichlinie befindet, der Vektor außerhalb des Dreiecks liegt, jedes t <= tp, welches 1> = s + t, wo sich auf der einzelnen Strichlinie befindet, ist der Vektor innerhalb des Dreiecks.
Wenn wir dann in [0, 1] ein s angeben, muss das entsprechende t für den Vektor innerhalb des Dreiecks 1> = s + t erfüllen.
Schließlich erhalten wir v = s * v02 + t * v01, v befindet sich innerhalb des Dreiecks mit der Bedingung s, t, s + t gehört zu [0, 1]. Dann übersetzen wir auf Punkt, den wir haben
p - p0 = s * (p1 - p0) + t * (p2 - p0), mit s, t, s + t in [0, 1]
Dies ist die gleiche Lösung wie Andreas 'Lösung zur Lösung des Gleichungssystems. p = p0 + s * (p1 - p0) + t * (p2 - p0), wobei s, t, s + t zu [0, 1] gehören.
quelle
Hier ist eine Lösung in Python, die effizient und dokumentiert ist und drei Unittests enthält. Es ist von professioneller Qualität und kann so wie es ist in Form eines Moduls in Ihr Projekt eingefügt werden.
Es gibt einen zusätzlichen optionalen grafischen Test für den obigen Algorithmus, um seine Gültigkeit zu bestätigen:
Erstellen der folgenden Grafik:
quelle
Es gibt lästige Kantenbedingungen, bei denen ein Punkt genau auf der gemeinsamen Kante zweier benachbarter Dreiecke liegt. Der Punkt kann nicht in beiden oder keinem der Dreiecke liegen. Sie benötigen eine willkürliche, aber konsistente Art, den Punkt zuzuweisen. Zeichnen Sie beispielsweise eine horizontale Linie durch den Punkt. Wenn sich die Linie mit der anderen Seite des Dreiecks auf der rechten Seite schneidet, wird der Punkt so behandelt, als ob er sich innerhalb des Dreiecks befindet. Wenn sich der Schnittpunkt links befindet, befindet sich der Punkt außerhalb.
Wenn die Linie, auf der der Punkt liegt, horizontal ist, verwenden Sie oben / unten.
Wenn sich der Punkt auf dem gemeinsamen Scheitelpunkt mehrerer Dreiecke befindet, verwenden Sie das Dreieck, mit dessen Mittelpunkt der Punkt den kleinsten Winkel bildet.
Mehr Spaß: Drei Punkte können in einer geraden Linie (null Grad) liegen, zum Beispiel (0,0) - (0,10) - (0,5). Bei einem Triangulationsalgorithmus muss das "Ohr" (0,10) abgeschnitten werden, wobei das erzeugte "Dreieck" der entartete Fall einer geraden Linie ist.
quelle
Dies ist das einfachste Konzept, um festzustellen, ob sich ein Punkt innerhalb oder außerhalb des Dreiecks oder auf einem Arm eines Dreiecks befindet.
Die Bestimmung eines Punktes erfolgt innerhalb eines Dreiecks durch Determinanten:
Der einfachste Arbeitscode:
quelle
Der einfachste Weg und es funktioniert mit allen Arten von Dreiecken ist einfach die Winkel der P-Punkt A-, B-, C-Punktwinkel zu bestimmen. Wenn eine des Winkels größer als 180,0 Grad , dann ist es draußen, wenn 180.0 dann ist es auf dem Umfang und wenn acos auf Sie zu betrügen und weniger als 180,0 dann ist es inside.Take ein Blick für das Verständnis http: // Mathe-Physik -psychology.blogspot.hu/2015/01/earlish-determination-that-point-is.html
quelle
Ehrlich gesagt ist es so einfach wie die Antwort von Simon P. Steven aber mit diesem Ansatz haben Sie keine solide Kontrolle darüber, ob die Punkte an den Kanten des Dreiecks enthalten sein sollen oder nicht.
Mein Ansatz ist etwas anders, aber sehr einfach. Betrachten Sie das folgende Dreieck;
Um den Punkt im Dreieck zu haben, müssen wir 3 Bedingungen erfüllen
Bei dieser Methode haben Sie die volle Kontrolle darüber, den Punkt an den Kanten einzeln einzuschließen oder auszuschließen. Sie können also überprüfen, ob sich ein Punkt im Dreieck befindet, der nur | AC | enthält Kante zum Beispiel.
Meine Lösung in JavaScript wäre also wie folgt:
quelle
Effizienter geht es nicht! Jede Seite eines Dreiecks kann eine unabhängige Position und Ausrichtung haben, daher sind drei Berechnungen erforderlich: l1, l2 und l3 werden definitiv mit jeweils 2 Multiplikationen benötigt. Sobald l1, l2 und l3 bekannt sind, sind nur wenige grundlegende Vergleiche und boolesche Operationen entfernt.
quelle
Angeblich Hochleistungscode, den ich in JavaScript angepasst habe (Artikel unten):
pointInTriangle(p, p0, p1, p2)
- für Dreiecke gegen den UhrzeigersinnpointInTriangle(p, p0, p1, p2)
- für Dreiecke im UhrzeigersinnSchauen Sie in jsFiddle (Leistungstest eingeschlossen), es gibt auch Wicklungsprüfung in einer separaten Funktion. Oder klicken Sie unten auf "Code-Snippet ausführen"
Inspiriert davon: http://www.phatcode.net/articles.php?id=459
quelle
Nahezu perfekte kartesische Koordinaten, die aus dem Schwerpunkt konvertiert wurden, werden in * v (x) - und * w (y) -Doppelwerten exportiert. Beide Export-Doubles sollten in jedem Fall vorher ein * Zeichen haben, wahrscheinlich: * v und * w Code können auch für das andere Dreieck eines Vierecks verwendet werden. Hiermit signiert schrieb nur Dreieck abc aus dem Uhrzeigersinn abcd Quad.
Der o-Punkt befindet sich innerhalb des ABC-Dreiecks zum Testen mit dem zweiten Dreieck. Rufen Sie diese Funktion in CDA-Richtung auf. Die Ergebnisse sollten nach
*v=1-*v;
und*w=1-*w;
für das Viereck korrekt seinquelle
Ich brauchte einen Punkt in der Dreiecksprüfung in einer "kontrollierbaren Umgebung", wenn Sie absolut sicher sind, dass die Dreiecke im Uhrzeigersinn sind. Also nahm ich Perro Azul 's jsfiddle und modifizierte es, wie von coproc für solche Fälle vorgeschlagen; Außerdem wurden redundante 0,5- und 2-Multiplikationen entfernt, da sie sich nur gegenseitig aufheben.
http://jsfiddle.net/dog_funtom/H7D7g/
Hier ist der entsprechende C # -Code für Unity:
quelle
Eine der einfachsten Möglichkeiten, um zu überprüfen, ob der durch die Eckpunkte des Dreiecks (x1, y1), (x2, y2), (x3, y3) gebildete Bereich positiv ist oder nicht.
Die Fläche kann nach folgender Formel berechnet werden:
oder Python-Code kann geschrieben werden als:
quelle