Come determinare se un punto si trova in un triangolo 2D? [chiuso]


266

C'è un modo semplice per determinare se un punto è all'interno di un triangolo? È 2D, non 3D.


17
Ho scritto un articolo completo sul punto nel test del triangolo. Mostra i metodi basati sui prodotti baricentrici, parametrici e puntuali. Quindi si occupa del problema di precisione che si verifica quando un punto si trova esattamente su un bordo (con esempi). Infine espone un metodo completamente nuovo basato sulla distanza da punto a bordo. totologic.blogspot.fr/2014/01/… Buon divertimento!
Logica


1
Vale la pena notare che tutti i metodi discussi qui sono validi anche nello spazio 3D. Devono solo essere preceduti da una trasformazione di coordinate (e una proiezione appropriata del punto sul piano del triangolo). Un triangolo è un oggetto bidimensionale.
andreasdr

Per una soluzione indipendente dall'ordine di avvolgimento. Ecco un violino funzionante: jsfiddle.net/ibowankenobi/oex3pzq2
ibrahim tanyalcin

2
Voto per chiudere questa domanda perché riguarda la matematica piuttosto che la programmazione, ed è basata sull'opinione (cosa è "facile" per te?).
TylerH

Risposte:


270

In generale, l'algoritmo più semplice (e abbastanza ottimale) sta controllando su quale lato del semipiano creato dai bordi si trova il punto.

Ecco alcune informazioni di alta qualità in questo argomento su GameDev , inclusi i problemi di prestazioni.

Ed ecco un po 'di codice per iniziare:

float sign (fPoint p1, fPoint p2, fPoint p3)
{
    return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
}

bool PointInTriangle (fPoint pt, fPoint v1, fPoint v2, fPoint v3)
{
    float d1, d2, d3;
    bool has_neg, has_pos;

    d1 = sign(pt, v1, v2);
    d2 = sign(pt, v2, v3);
    d3 = sign(pt, v3, v1);

    has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
    has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);

    return !(has_neg && has_pos);
}

12
È comunemente usato in 2D. Le coordinate baricentriche tendono a confondere le persone. Inoltre, date le coordinate del triangolo e la coordinata del punto, non sono sicuro dell'efficienza dell'uso dei baricentrici.
Kornel Kisielewicz

7
@Kornel La versione baricentrica è più efficiente anche in 2D. La tua soluzione ha anche il problema che riporterà un risultato diverso per i punti esattamente sui bordi del triangolo a seconda che il triangolo sia specificato in senso orario o antiorario.
Andreas Brinck

9
Per i miei scopi (il motivo per cui ho trovato questo sito) la risposta originale proposta da Kornel Kisielewicz è molto più efficiente. Sto lavorando con un display LCD con coordinate di dimensione BYTE e un microprocessore molto tipico in cui la moltiplicazione di numeri interi è un'istruzione molto veloce e la divisione è molto, molto, più lenta. Anche i problemi numerici sono molto più piccoli, a causa dell'assenza di divisione! tutti i calcoli sono esatti. Grazie, Rick

4
Quindi la funzione sign () ti dice quale lato del semipiano (formato dalla linea tra p2 e p3) p1 è?
David Doria

1
Nota che se assumi un certo ordine dei vertici (diciamo in senso antiorario), non è necessario calcolare tutti questi determinanti tutto il tempo. Infatti, nel migliore dei casi, 1 determinante è sufficiente per scoprire che il punto non è all'interno del triangolo.
Thash

180

Risolvi il seguente sistema di equazioni:

p = p0 + (p1 - p0) * s + (p2 - p0) * t

Il punto pè all'interno del triangolo se 0 <= s <= 1e 0 <= t <= 1e s + t <= 1.

s, te 1 - s - tsono chiamate coordinate baricentriche del punto p.


1
Questo è più veloce del controllo del mezzo piano, ma forse un po 'più difficile da capire se sei nuovo alle coordinate baricentriche.
Daniel Rikowski

8
Con banali uscite (non implementate) nel metodo di Kornel, il suo può effettivamente essere molto più efficiente del tuo. Se provi davvero a calcolare s e t saprai cosa intendo.

87
Volevo testarlo, quindi ho creato un jsfiddle, basandomi sulla soluzione @andreasdr e sul commento di coproc
urraka

5
Ottimizzazione: s + t <= 1implica s <= 1e t <= 1se s >= 0e t >= 0.
Thomas Eding

7
L'articolo totologic.blogspot.fr/2014/01/… proposto da @Logic post mi ha aiutato a capire meglio questa soluzione
Flayn

116

Sono d'accordo con Andreas Brinck , le coordinate baricentriche sono molto convenienti per questo compito. Si noti che non è necessario risolvere ogni volta un sistema di equazioni: basta valutare la soluzione analitica. Usando la notazione di Andreas , la soluzione è:

s = 1/(2*Area)*(p0y*p2x - p0x*p2y + (p2y - p0y)*px + (p0x - p2x)*py);
t = 1/(2*Area)*(p0x*p1y - p0y*p1x + (p0y - p1y)*px + (p1x - p0x)*py);

dov'è Areal'area (con segno) del triangolo:

Area = 0.5 *(-p1y*p2x + p0y*(-p1x + p2x) + p0x*(p1y - p2y) + p1x*p2y);

Basta valutare s, te 1-s-t. Il punto pè all'interno del triangolo se e solo se sono tutti positivi.

EDIT: nota che l'espressione sopra per l'area presume che la numerazione del nodo del triangolo sia in senso antiorario. Se la numerazione è in senso orario, questa espressione restituirà un'area negativa (ma con la grandezza corretta). Il test stesso ( s>0 && t>0 && 1-s-t>0) non dipende però dalla direzione della numerazione, poiché anche le espressioni sopra che vengono moltiplicate 1/(2*Area)cambiano segno se cambia l'orientamento del nodo triangolare.

EDIT 2: Per un'efficienza computazionale ancora migliore, vedere il commento di coproc di seguito (che sottolinea che se l'orientamento dei nodi del triangolo (in senso orario o antiorario) è noto in anticipo, la divisione per 2*Areanelle espressioni per se tpuò essere evitato). Vedi anche jsfiddle-code di Perro Azul nei commenti sotto la risposta di Andreas Brinck .


6
Questo sta risolvendo il sistema di equazioni :)
Andreas Brinck

1
Sì, il punto è che qualsiasi critica al tuo metodo basato sul costo computazionale della risoluzione del sistema di equazioni è infondata, poiché ciò non deve essere fatto come parte dell'algoritmo.
andreasdr

13
L'efficienza può essere migliorata non dividendo 2*Area, cioè calcolando s´=2*|Area|*se t´=2*|Area|*t(se l'orientamento dei punti - orario o antiorario - non è noto, il segno di Areadeve essere verificato, ovviamente, ma altrimenti forse non lo fa nemmeno deve essere calcolato), poiché per il controllo s>0è sufficiente controllare s´>0. E invece di controllare 1-s-t>0è sufficiente controllare s´+t´<2*|Area|.
coproc

1
Posso aggiungere che se p0->p1->p2è in senso antiorario in cartesiano (che di solito è in senso orario nelle coordinate dello schermo ), il Areacalcolato con questo metodo sarà positivo.
rhgb

1
@ user2600366 Quando viaggi lungo il confine del triangolo nella direzione p0 -> p1 -> p2 -> p0, e così via, avrai l'interno del triangolo sempre alla tua destra o sempre alla tua sinistra. Nel primo caso la numerazione è in senso orario, nel secondo caso è in senso antiorario.
andreasdr

49

Ho scritto questo codice prima di un ultimo tentativo con Google e di trovare questa pagina, quindi ho pensato di condividerlo. È fondamentalmente una versione ottimizzata della risposta di Kisielewicz. Ho esaminato anche il metodo baricentrico, ma a giudicare dall'articolo di Wikipedia ho difficoltà a vedere come sia più efficiente (immagino che ci sia un'equivalenza più profonda). Comunque, questo algoritmo ha il vantaggio di non utilizzare la divisione; un potenziale problema è il comportamento del rilevamento dei bordi a seconda dell'orientamento.

bool intpoint_inside_trigon(intPoint s, intPoint a, intPoint b, intPoint c)
{
    int as_x = s.x-a.x;
    int as_y = s.y-a.y;

    bool s_ab = (b.x-a.x)*as_y-(b.y-a.y)*as_x > 0;

    if((c.x-a.x)*as_y-(c.y-a.y)*as_x > 0 == s_ab) return false;

    if((c.x-b.x)*(s.y-b.y)-(c.y-b.y)*(s.x-b.x) > 0 != s_ab) return false;

    return true;
}

In parole, l'idea è questa: il punto s è a sinistra oa destra di entrambe le linee AB e AC? Se è vero, non può essere dentro. Se falso, è almeno all'interno dei "coni" che soddisfano la condizione. Ora poiché sappiamo che un punto all'interno di un trigone (triangolo) deve essere sullo stesso lato di AB di BC (e anche di CA), controlliamo se differiscono. Se lo fanno, le s non possono essere dentro, altrimenti le s devono essere dentro.

Alcune parole chiave nei calcoli sono semipiani lineari e determinante (prodotto incrociato 2x2). Forse un modo più pedagogico è probabilmente pensarlo come un punto che si trova all'interno se e solo se è dalla stessa parte (sinistra o destra) di ciascuna delle linee AB, BC e CA. Tuttavia, il modo sopra descritto sembrava più adatto per alcune ottimizzazioni.


2
Questo test è circa il 140-180% più veloce del primo fornito (grazie a entrambi btw :). Ho eseguito il codice qui: paste.ubuntu.com/p/k5w7ywH4p8 utilizzando il motore nodejs v8 con ottimizzazioni disabilitate e ho ottenuto i seguenti risultati:: w! Node -p --minimal test1: 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 perché dovresti eseguirlo senza ottimizzazioni? Non è quindi più lontano dalla realtà?
xuiqzy

@xuiqzy Bene, il mio programma contiene le due diverse soluzioni. Devo ancora amministrare il metodo più veloce per farlo. Forse quel commento dovrebbe essere rimosso e sostituito con i miei lavori completati riguardo a questo ..
surgemcgee

33

Versione C # del metodo baricentrico pubblicato da andreasdr e Perro Azul. Si noti che il calcolo dell'area può essere evitato se se thanno segni opposti. Ho verificato il comportamento corretto con uno unit test piuttosto approfondito.

public static bool PointInTriangle(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))
        return false;

    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);
}

[ modifica ] ha
accettato la modifica suggerita da @Pierre; vedere i commenti


La soluzione con l'istruzione if finale funziona per i punti triangolari in senso orario e antiorario.
Luke Dupin

@LukeDupin Non sono sicuro di aver capito il tuo commento. Questa risposta funziona come pubblicata per qualsiasi ordine fornito dei 3 punti.
Glenn Slayden

12

Versione Java del metodo baricentrico:

class Triangle {
    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)
            return false;
        double b = y31 * dx + x13 * dy;
        if (b < minD || b > maxD)
            return false;
        double c = det - a - b;
        if (c < minD || c > maxD)
            return false;
        return true;
    }

    private final double x3, y3;
    private final double y23, x32, y31, x13;
    private final double det, minD, maxD;
}

Il codice precedente funzionerà accuratamente con i numeri interi, assumendo che non vi siano overflow. Funzionerà anche con triangoli in senso orario e antiorario. Non funzionerà con i triangoli collineari (ma puoi verificarlo testando det == 0).

La versione baricentrica è più veloce se testerai punti diversi con lo stesso triangolo.

La versione baricentrica non è simmetrica nei 3 punti del triangolo, quindi è probabile che sia meno coerente della versione semipiano di bordo di Kornel Kisielewicz, a causa di errori di arrotondamento in virgola mobile.

Credito: ho creato il codice sopra dall'articolo di Wikipedia sulle coordinate baricentriche.


Bello ! Può anche essere migliorato per utilizzare le tuple Point3f / Point2f di javax.vecmath, al fine di gestire meglio l'input dei dati.
Alex Byrth

10

Un modo semplice è:

trova i vettori che collegano il punto a ciascuno dei tre vertici del triangolo e somma gli angoli tra quei vettori. Se la somma degli angoli è 2 * pi, il punto è all'interno del triangolo.

Due buoni siti che spiegano le alternative sono:

blackpawn e wolfram


3
Ehm, quel metodo non è esattamente efficiente ed è molto soggetto a errori numerici ...
Kornel Kisielewicz

È esattamente l'opposto, è molto inefficiente :-) Tuttavia, è solo un modo semplice, facile da implementare. Puoi fare un esempio di un errore numerico che ciò causerebbe?
Simon P Stevens

Anche se a me questa sembra semplicemente essere la migliore di tutte le risposte sotto questo argomento, immagino che i punti sui bordi del triangolo siano calcolati per essere inclusi nel triangolo e non hai un controllo solido su questo.
Redu

controllare se è esattamente 2 ppi è numericamente impossibile dato l'irrazionale di pi greco. Tuttavia, devi solo controllare se gli angoli si sommano a qualcosa di più grande di pi greco.
lonewarrior556

10

Utilizzando la soluzione analitica per le coordinate baricentriche (indicate da Andreas Brinck ) e:

  • non distribuire la moltiplicazione sui termini tra parentesi
  • evitando di calcolare più volte gli stessi termini memorizzandoli
  • riducendo i confronti (come sottolineato da coproc e Thomas Eding )

Si può ridurre al minimo il numero di operazioni "costose":

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;
}

Il codice può essere incollato in Perro Azul jsfiddle o provalo facendo clic su "Esegui snippet di codice" di seguito

var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;

var point = { x: W / 2, y: H / 2 };
var triangle = randomTriangle();

$("canvas").click(function(evt) {
    point.x = evt.pageX - $(this).offset().left;
    point.y = evt.pageY - $(this).offset().top;
    test();
});

$("canvas").dblclick(function(evt) {
    triangle = randomTriangle();
    test();
});

test();

function test() {
    var result = ptInTriangle(point, triangle.a, triangle.b, triangle.c);
    
    var info = "point = (" + point.x + "," + point.y + ")\n";
    info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
    info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
    info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
    info += "result = " + (result ? "true" : "false");

    $("#result").text(info);
    render();
}

function ptInTriangle(p, p0, p1, p2) {
    var A = 1/2 * (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
    var sign = A < 0 ? -1 : 1;
    var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y) * sign;
    var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y) * sign;
    
    return s > 0 && t > 0 && (s + t) < 2 * A * sign;
}

function render() {
    ctx.fillStyle = "#CCC";
    ctx.fillRect(0, 0, 500, 500);
    drawTriangle(triangle.a, triangle.b, triangle.c);
    drawPoint(point);
}

function drawTriangle(p0, p1, p2) {
    ctx.fillStyle = "#999";
    ctx.beginPath();
    ctx.moveTo(p0.x, p0.y);
    ctx.lineTo(p1.x, p1.y);
    ctx.lineTo(p2.x, p2.y);
    ctx.closePath();
    ctx.fill();
    ctx.fillStyle = "#000";
    ctx.font = "12px monospace";
    ctx.fillText("1", p0.x, p0.y);
    ctx.fillText("2", p1.x, p1.y);
    ctx.fillText("3", p2.x, p2.y);
}

function drawPoint(p) {
    ctx.fillStyle = "#F00";
    ctx.beginPath();
    ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
    ctx.fill();
}

function rand(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomTriangle() {
    return {
        a: { x: rand(0, W), y: rand(0, H) },
        b: { x: rand(0, W), y: rand(0, H) },
        c: { x: rand(0, W), y: rand(0, H) }
    };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<pre>Click: place the point.
Double click: random triangle.</pre>
<pre id="result"></pre>
<canvas width="500" height="500"></canvas>

Portando a:

  • variabile "richiami": 30
  • memoria variabile: 7
  • aggiunte: 4
  • sottrazioni: 8
  • moltiplicazioni: 6
  • divisioni: nessuna
  • confronti: 4

Questo si confronta abbastanza bene con la soluzione Kornel Kisielewicz (25 richiami, 1 archiviazione, 15 sottrazioni, 6 moltiplicazioni, 5 confronti), e potrebbe essere ancora migliore se è necessario il rilevamento in senso orario / antiorario (che richiede 6 richiami, 1 addizione, 2 sottrazioni , 2 moltiplicazioni e 1 confronto in sé, utilizzando il determinante della soluzione analitica, come sottolineato da rhgb ).


Bella soluzione. Penso che sia abbastanza equivalente al mio ultimo approccio qui su MSE: math.stackexchange.com/questions/51326/…
Jack D'Aurizio

Ho appena testato il codice così com'è e non funziona per me (esempio p -4.69317198, -6.99191951 p0 -7.05846786 0.596718192 p1 -6.8703599 -2.36565161 p2 -4.69317198, -6.99191951)
Giovanni Funchal

@GiovanniFunchal Strano, il tuo esempio funziona per me, sia nel jsfiddle (sostituisci le definizioni iniziali "punto" e "triangolo") e nella mia implementazione Python locale. Problemi di precisione numerica (prova a rimuovere alcuni decimali)?
Cédric Dufour

1
Il tuo sembra il più veloce nel mio test: jsfiddle.net/eyal/gxw3632c/27 . Tuttavia, la differenza tra tutti i metodi è piuttosto ridotta.
Eyal

Prova il triangolo (-1, -1), (1, -1), (0,1) e il punto (0, -1). Restituisce falso quando dovrebbe restituire vero perché s (2) + t (2)> d (2). Qualcosa non va con la matematica sui bordi del triangolo, a quanto pare, poiché il punto p è proprio sul confine tra p0 e p1 e non è una semplice questione di convertire un <in un <= o qualcosa del genere.
devnullicus

5

Quello che faccio è precalcolare le tre normali della faccia,

  • in 3D per prodotto incrociato del vettore laterale e del vettore normale della faccia.

  • in 2D semplicemente scambiando i componenti e negandone uno,

quindi dentro / fuori per ogni lato è quando un prodotto punto del lato normale e il vertice per punto vettore, cambia segno. Ripeti per altri due (o più) lati.

Benefici:

  • molto è precalcolato, quindi ottimo per test su più punti sullo stesso triangolo.

  • rifiuto precoce del caso comune di più punti esterni che interni. (anche se la distribuzione dei punti è ponderata su un lato, è possibile testare prima quel lato.)


5

Ecco un'implementazione efficiente di Python :

def PointInsideTriangle2(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: return False
    else: 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))

e un esempio di output:

inserisci qui la descrizione dell'immagine


Non sono stato in grado di farlo funzionare, ad esempio per il punto nel triangolo [(0,0), (3,0), (3,4)], né i punti (1,1) o (0 , 0) risulta positivo. Ho provato con entrambi i punti triangolari in senso orario e antiorario.
ThorSummoner

3

Se stai cercando velocità, ecco una procedura che potrebbe aiutarti.

Ordina i vertici del triangolo sulle loro ordinate. Ciò richiede al massimo tre confronti. Siano Y0, Y1, Y2 i tre valori ordinati. Disegnando tre orizzontali attraverso di loro, si divide l'aereo in due mezzi piani e due lastre. Sia Y l'ordinata del punto di interrogazione.

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

Costa altri due confronti. Come puoi vedere, il rifiuto rapido si ottiene per i punti al di fuori della "lastra di delimitazione".

Facoltativamente, è possibile fornire un test sulle ascisse per un rapido rigetto a sinistra ea destra ( X <= X0' or X >= X2'). Ciò implementerà un rapido test del riquadro di delimitazione allo stesso tempo, ma dovrai anche ordinare le ascisse.

Eventualmente sarà necessario calcolare il segno del punto dato rispetto ai due lati del triangolo che delimitano la relativa lastra (superiore o inferiore). Il test ha la forma:

((X - Xi) * (Y - Yj) > (X - Xi) * (Y - Yj)) == ((X - Xi) * (Y - Yk) > (X - Xi) * (Y - Yk))

La discussione completa delle i, j, kcombinazioni (ce ne sono sei, in base al risultato dell'ordinamento) è fuori dallo scopo di questa risposta e "lasciata come esercizio al lettore"; per l'efficienza, dovrebbero essere hard-coded.

Se pensi che questa soluzione sia complessa, osserva che si tratta principalmente di confronti semplici (alcuni dei quali possono essere precalcolati), più 6 sottrazioni e 4 moltiplicazioni nel caso in cui il test del riquadro di delimitazione fallisca. Quest'ultimo costo è difficile da battere perché nel peggiore dei casi non puoi evitare di confrontare il punto di prova con due lati (nessun metodo in altre risposte ha un costo inferiore, alcuni lo peggiorano, come 15 sottrazioni e 6 moltiplicazioni, a volte divisioni).

AGGIORNAMENTO: più veloce con una trasformazione di taglio

Come spiegato appena sopra, è possibile individuare rapidamente il punto all'interno di una delle quattro bande orizzontali delimitate dalle tre ordinate di vertice, utilizzando due confronti.

Facoltativamente, è possibile eseguire uno o due test X aggiuntivi per verificare l'interno del riquadro di delimitazione (linee tratteggiate).

Quindi si consideri la trasformazione "taglio" data da X'= X - m Y, Y' = Y, dove mè la pendenza DX/DYper il bordo più alto. Questa trasformazione renderà verticale questo lato del triangolo. E poiché sai da che parte dell'orizzontale medio ti trovi, è sufficiente testare il segno rispetto ad un solo lato del triangolo.

inserisci qui la descrizione dell'immagine

Supponendo che tu abbia precalcolato la pendenza m, così come X'per i vertici del triangolo tranciato e i coefficienti delle equazioni dei lati come X = m Y + p, avrai bisogno nel caso peggiore

  • due confronti di ordinate per la classificazione verticale;
  • opzionalmente uno o due confronti di ascisse per il rifiuto del riquadro di delimitazione;
  • calcolo di X' = X - m Y;
  • uno o due confronti con le ascisse del triangolo tranciato;
  • un segno di prova X >< m' Y + p'contro il lato rilevante del triangolo tranciato.

3

Se conosci le coordinate dei tre vertici e le coordinate del punto specifico, puoi ottenere l'area del triangolo completo. Successivamente, calcola l'area dei tre segmenti del triangolo (un punto è il punto dato e gli altri due sono due vertici qualsiasi del triangolo). Quindi, otterrai l'area dei tre segmenti triangolari. Se la somma di queste aree è uguale all'area totale (ottenuta in precedenza), il punto dovrebbe trovarsi all'interno del triangolo. Altrimenti, il punto non è all'interno del triangolo. Questo dovrebbe funzionare. Se ci sono problemi, fammelo sapere. Grazie.


3

Ecco una soluzione in Python che è efficiente, documentata e contiene tre unittest. È di qualità professionale ed è pronto per essere inserito nel tuo progetto sotto forma di modulo così com'è.

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 negative
    return (side_1 < 0.0) == (side_2 < 0.0) == (side_3 < 0.0)

###############################################################################
class TestPointInTriangle(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)

C'è un ulteriore test grafico opzionale per l'algoritmo di cui sopra per confermare la sua validità:

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 import Polygon
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")

Produrre la seguente grafica:

Testare la funzione point_in_triangle


3

Altra funzione in python , più veloce del metodo Developer (almeno per me) e ispirata alla soluzione di Cédric Dufour :

def ptInTriang(p_test, p0, p1, p2):       
     dX = p_test[0] - p0[0]
     dY = p_test[1] - p0[1]
     dX20 = p2[0] - p0[0]
     dY20 = p2[1] - p0[1]
     dX10 = p1[0] - p0[0]
     dY10 = p1[1] - p0[1]

     s_p = (dY20*dX) - (dX20*dY)
     t_p = (dX10*dY) - (dY10*dX)
     D = (dX10*dY20) - (dY10*dX20)

     if D > 0:
         return (  (s_p >= 0) and (t_p >= 0) and (s_p + t_p) <= D  )
     else:
         return (  (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= D  )

Puoi testarlo con:

X_size = 64
Y_size = 64
ax_x = np.arange(X_size).astype(np.float32)
ax_y = np.arange(Y_size).astype(np.float32)
coords=np.meshgrid(ax_x,ax_y)
points_unif = (coords[0].reshape(X_size*Y_size,),coords[1].reshape(X_size*Y_size,))
p_test = np.array([0 , 0])
p0 = np.array([22 , 8]) 
p1 = np.array([12 , 55]) 
p2 = np.array([7 , 19]) 
fig = plt.figure(dpi=300)
for i in range(0,X_size*Y_size):
    p_test[0] = points_unif[0][i]
    p_test[1] = points_unif[1][i]
    if ptInTriang(p_test, p0, p1, p2):
        plt.plot(p_test[0], p_test[1], '.g')
    else:
        plt.plot(p_test[0], p_test[1], '.r')

Ci vuole molto per tracciare, ma quella griglia viene testata in 0,0195319652557 secondi contro 0,0844349861145 secondi del codice dello sviluppatore .

Infine il commento sul codice:

# 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

Questa funzione non funziona. Dai ptInTriang([11,45],[45, 45],[45, 45] ,[44, 45])e tornerà trueanche se è falso
Code Pope

3

Poiché non esiste una risposta JS, soluzione in
senso orario e antiorario:

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 

}

EDIT: c'era un errore di battitura per il calcolo det ( cy - ayinvece di cx - ax), questo è stato risolto.

https://jsfiddle.net/jniac/rctb3gfL/

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

// clockwise
let triangle1 = {

	A : { x: 10, y: -10 },
	C : { x: 20, y: 100 },
	B : { x: -90, y: 10 },
	
	color: '#f00',

}

// counter clockwise
let triangle2 = {

	A : { x: 20, y: -60 },
	B : { x: 90, y: 20 },
	C : { x: 20, y: 60 },

	color: '#00f',
	
}


let scale = 2
let 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 = 1000

	for (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'
		
		} else if (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()
		
	} else if (contains(triangle2, mouse)) {
	
		ctx.fillStyle = triangle2.color + 'a'
		ctx.fill()
		
	} else {
	
		ctx.strokeStyle = 'black'
		ctx.stroke()
		
	}
	
}

drawDots()
drawInteractive()










// trigo

function add(...points) {
	
	let x = 0, y = 0
	
	for (let point of points) {
	
		x += point.x
		y += point.y
	
	}
	
	return { x, y }

}

function center(...points) {
	
	let x = 0, y = 0
	
	for (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><span class="red">red triangle</span> is clockwise</p>
<p><span class="blue">blue triangle</span> is couter clockwise</p>
<br>
<div class="wrapper">
	<canvas id="dots"></canvas>
	<canvas id="interactive"></canvas>
</div>

inserisci qui la descrizione dell'immagine

Sto usando qui lo stesso metodo descritto sopra: un punto è all'interno di ABC se è rispettivamente sullo "stesso" lato di ciascuna linea AB, BC, CA.

esempio di inclusione di triangolo


Ho stancato questo codice e non funziona. Restituisce sempre False.
xApple

hmmm ... probabilmente hai fatto un errore. Ecco un violino con quella funzione in esecuzione: jsfiddle.net/jniac/rctb3gfL
Joseph Merdrignac

Ho visto la tua risposta Python, stiamo usando lo stesso metodo, se uso un'altra riga ( let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)), questo serve per determinare l'ordine di avvolgimento del triangolo, quindi il metodo funzionerà con triangoli CW e CCW (vedi jsFiddle).
Joseph Merdrignac

1
hm ho fatto un errore, ho scritto: let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)invece di let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)così questo è risolto, grazie per la segnalazione
Joseph Merdrignac

2

Voglio solo usare una semplice matematica vettoriale per spiegare la soluzione delle coordinate baricentriche che Andreas aveva dato, sarà molto più facile da capire.

  1. L'area A è definita come qualsiasi vettore dato da s * v02 + t * v01, con la condizione s> = 0 et> = 0. Se un qualsiasi punto all'interno del triangolo v0, v1, v2, deve essere all'interno dell'Area A.

inserisci qui la descrizione dell'immagine

  1. Se limita ulteriormente s e t appartiene a [0, 1]. Otteniamo l'Area B che contiene tutti i vettori di s * v02 + t * v01, con la condizione s, t appartiene a [0, 1]. Vale la pena notare che la parte bassa dell'Area B è lo specchio del Triangolo v0, v1, v2. Il problema nasce se possiamo dare certe condizioni di se t, escludendo ulteriormente la parte bassa dell'Area B.

inserisci qui la descrizione dell'immagine

  1. Supponiamo di dare un valore se t sta cambiando in [0, 1]. Nella foto seguente, il punto p è al limite di v1v2. Tutti i vettori di s * v02 + t * v01 che si trovano lungo la linea tratteggiata per semplice somma vettoriale. In v1v2 e punto di incrocio della linea tratteggiata p, abbiamo:

(1-s) | v0v2 | / | v0v2 | = tp | v0v1 | / | v0v1 |

otteniamo 1 - s = tp, quindi 1 = s + tp. Se qualsiasi t> tp, che 1 <s + t dove si trova sulla doppia linea tratteggiata, il vettore è esterno al triangolo, qualsiasi t <= tp, quale 1> = s + t dove si trova sulla singola linea tratteggiata, il vettore è all'interno del triangolo.

Allora se diamo una s in [0, 1], la t corrispondente deve incontrare 1> = s + t, per il vettore all'interno del triangolo.

inserisci qui la descrizione dell'immagine

Quindi finalmente otteniamo v = s * v02 + t * v01, v è all'interno del triangolo con la condizione s, t, s + t appartiene a [0, 1]. Quindi traduci in punto, abbiamo

p - p0 = s * (p1 - p0) + t * (p2 - p0), con s, t, s + t in [0, 1]

che è la stessa della soluzione di Andreas per risolvere il sistema di equazioni p = p0 + s * (p1 - p0) + t * (p2 - p0), con s, t, s + t appartengono a [0, 1].


Puoi semplicemente dire che usi il frame locale definito dai tre vertici in modo che i lati diventino s = 0, t = 0 e s + t = 1. La trasformazione di coordinate affini è un'operazione ben nota di algebra lineare.
Yves Daoust

2

Questo è il concetto più semplice per determinare se un punto è all'interno o all'esterno del triangolo o su un braccio di un triangolo.

La determinazione di un punto è all'interno di un triangolo da determinanti:

La determinazione di un punto è all'interno di un triangolo da determinanti

Il codice funzionante più semplice:

#-*- 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 == 0 or M2 == 0 or M3 ==0):
            print("Point: ",point," lies on the arms of Triangle")
    elif((M1 > 0 and M2 > 0 and M3 > 0)or(M1 < 0 and M2 < 0 and M3 < 0)):
            #if products is non 0 check if all of their sign is same
            print("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)

1

Ci sono condizioni di bordo fastidiose in cui un punto si trova esattamente sul bordo comune di due triangoli adiacenti. Il punto non può essere in entrambi o in nessuno dei triangoli. Hai bisogno di un modo arbitrario ma coerente per assegnare il punto. Ad esempio, traccia una linea orizzontale attraverso il punto. Se la linea si interseca con l'altro lato del triangolo a destra, il punto viene considerato come se fosse all'interno del triangolo. Se l'intersezione è a sinistra, il punto è esterno.

Se la linea su cui giace il punto è orizzontale, usa sopra / sotto.

Se il punto si trova sul vertice comune di più triangoli, utilizzare il triangolo il cui centro forma l'angolo più piccolo.

Più divertente: tre punti possono essere in linea retta (zero gradi), ad esempio (0,0) - (0,10) - (0,5). In un algoritmo di triangolazione, l '"orecchio" (0,10) deve essere tagliato, il "triangolo" generato è il caso degenere di una linea retta.



0

Onestamente è semplice come la risposta di Simon P Steven, tuttavia con questo approccio non hai un solido controllo sul fatto che i punti sui bordi del triangolo siano inclusi o meno.

Il mio approccio è un po 'diverso ma molto semplice. Considera il triangolo seguente;

inserisci qui la descrizione dell'immagine

Per avere il punto nel triangolo dobbiamo soddisfare 3 condizioni

  1. L'angolo ACE (verde) dovrebbe essere inferiore all'angolo ACB (rosso)
  2. L'angolo ECB (blu) dovrebbe essere minore dell'angolo ACB (rosso)
  3. Il punto E e il punto C dovrebbero avere lo stesso segno quando i loro valori xey sono applicati all'equazione di | AB | linea.

In questo metodo hai il pieno controllo per includere o escludere il punto sui bordi individualmente. Quindi puoi controllare se un punto è nel triangolo includendo solo | AC | edge per esempio.

Quindi la mia soluzione in JavaScript sarebbe la seguente;

function isInTriangle(t,p){

  function isInBorder(a,b,c,p){
    var m = (a.y - b.y) / (a.x - b.x);                     // calculate the slope
    return Math.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 length
    return Math.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));


0
bool isInside( float x, float y, float x1, float y1, float x2, float y2, float x3, float y3 ) {
  float l1 = (x-x1)*(y3-y1) - (x3-x1)*(y-y1), 
    l2 = (x-x2)*(y1-y2) - (x1-x2)*(y-y2), 
    l3 = (x-x3)*(y2-y3) - (x2-x3)*(y-y3);
  return (l1>0 && l2>0  && l3>0) || (l1<0 && l2<0 && l3<0);
}

Non può essere più efficiente di così! Ogni lato di un triangolo può avere posizione e orientamento indipendenti, quindi tre calcoli: l1, l2 e l3 sono sicuramente necessari che coinvolgono 2 moltiplicazioni ciascuno. Una volta che l1, l2 e l3 sono noti, il risultato è solo pochi confronti di base e operazioni booleane di distanza.


0

Codice presumibilmente ad alte prestazioni che ho adattato in JavaScript (articolo sotto):

function pointInTriangle (p, p0, p1, p2) {
  return (((p1.y - p0.y) * (p.x - p0.x) - (p1.x - p0.x) * (p.y - p0.y)) | ((p2.y - p1.y) * (p.x - p1.x) - (p2.x - p1.x) * (p.y - p1.y)) | ((p0.y - p2.y) * (p.x - p2.x) - (p0.x - p2.x) * (p.y - p2.y))) >= 0;
}
  • pointInTriangle(p, p0, p1, p2) - per triangoli in senso antiorario
  • pointInTriangle(p, p0, p1, p2) - per triangoli in senso orario

Guarda in jsFiddle (test delle prestazioni incluso), c'è anche il controllo dell'avvolgimento in una funzione separata. Oppure premi "Esegui snippet di codice" di seguito

var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;

var point = { x: W / 2, y: H / 2 };
var triangle = randomTriangle();

$("canvas").click(function(evt) {
    point.x = evt.pageX - $(this).offset().left;
    point.y = evt.pageY - $(this).offset().top;
    test();
});

$("canvas").dblclick(function(evt) {
    triangle = randomTriangle();
    test();
});

document.querySelector('#performance').addEventListener('click', _testPerformance);

test();

function test() {
    var result = checkClockwise(triangle.a, triangle.b, triangle.c) ? pointInTriangle(point, triangle.a, triangle.c, triangle.b) : pointInTriangle(point, triangle.a, triangle.b, triangle.c);
    
    var info = "point = (" + point.x + "," + point.y + ")\n";
    info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
    info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
    info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
    info += "result = " + (result ? "true" : "false");

    $("#result").text(info);
    render();
}

function _testPerformance () {
	var px = [], py = [], p0x = [], p0y = [], p1x = [], p1y = [], p2x = [], p2y = [], p = [], p0 = [], p1 = [], p2 = [];
    
	for(var i = 0; i < 1000000; i++) {
    p[i] = {x: Math.random() * 100, y: Math.random() * 100};
    p0[i] = {x: Math.random() * 100, y: Math.random() * 100};
    p1[i] = {x: Math.random() * 100, y: Math.random() * 100};
    p2[i] = {x: Math.random() * 100, y: Math.random() * 100};
  }
  console.time('optimal: pointInTriangle');
  for(var i = 0; i < 1000000; i++) {
    pointInTriangle(p[i], p0[i], p1[i], p2[i]);
  }
  console.timeEnd('optimal: pointInTriangle');

  console.time('original: ptInTriangle');
  for(var i = 0; i < 1000000; i++) {
  	ptInTriangle(p[i], p0[i], p1[i], p2[i]);
  }
  console.timeEnd('original: ptInTriangle');
}

function pointInTriangle (p, p0, p1, p2) {
	return (((p1.y - p0.y) * (p.x - p0.x) - (p1.x - p0.x) * (p.y - p0.y)) | ((p2.y - p1.y) * (p.x - p1.x) - (p2.x - p1.x) * (p.y - p1.y)) | ((p0.y - p2.y) * (p.x - p2.x) - (p0.x - p2.x) * (p.y - p2.y))) >= 0;
}

function ptInTriangle(p, p0, p1, 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) return false;

    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
    return (s + t) < A;
}

function render() {
    ctx.fillStyle = "#CCC";
    ctx.fillRect(0, 0, 500, 500);
    drawTriangle(triangle.a, triangle.b, triangle.c);
    drawPoint(point);
}

function checkClockwise(p0, p1, p2) {
    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
    return A > 0;
}

function drawTriangle(p0, p1, p2) {
    ctx.fillStyle = "#999";
    ctx.beginPath();
    ctx.moveTo(p0.x, p0.y);
    ctx.lineTo(p1.x, p1.y);
    ctx.lineTo(p2.x, p2.y);
    ctx.closePath();
    ctx.fill();
    ctx.fillStyle = "#000";
    ctx.font = "12px monospace";
    ctx.fillText("1", p0.x, p0.y);
    ctx.fillText("2", p1.x, p1.y);
    ctx.fillText("3", p2.x, p2.y);
}

function drawPoint(p) {
    ctx.fillStyle = "#F00";
    ctx.beginPath();
    ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
    ctx.fill();
}

function rand(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomTriangle() {
    return {
        a: { x: rand(0, W), y: rand(0, H) },
        b: { x: rand(0, W), y: rand(0, H) },
        c: { x: rand(0, W), y: rand(0, H) }
    };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="performance">Run performance test (open console)</button>
<pre>Click: place the point.
Double click: random triangle.</pre>
<pre id="result"></pre>
<canvas width="500" height="500"></canvas>

Ispirato da questo: http://www.phatcode.net/articles.php?id=459


0

Avevo bisogno di un punto nel triangolo in "ambiente controllabile" quando sei assolutamente sicuro che i triangoli saranno in senso orario. Quindi, ho preso jsfiddle di Perro Azul e l'ho modificato come suggerito da coproc per questi casi; ha anche rimosso le moltiplicazioni ridondanti 0,5 e 2 perché si annullano a vicenda.

http://jsfiddle.net/dog_funtom/H7D7g/

var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;

var point = {
    x: W / 2,
    y: H / 2
};
var triangle = randomTriangle();

$("canvas").click(function (evt) {
    point.x = evt.pageX - $(this).offset().left;
    point.y = evt.pageY - $(this).offset().top;
    test();
});

$("canvas").dblclick(function (evt) {
    triangle = randomTriangle();
    test();
});

test();

function test() {
    var result = ptInTriangle(point, triangle.a, triangle.b, triangle.c);

    var info = "point = (" + point.x + "," + point.y + ")\n";
    info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
    info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
    info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
    info += "result = " + (result ? "true" : "false");

    $("#result").text(info);
    render();
}

function ptInTriangle(p, p0, p1, 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) return false;

    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);

    return (s + t) < A;
}

function checkClockwise(p0, p1, p2) {
    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
    return A > 0;
}

function render() {
    ctx.fillStyle = "#CCC";
    ctx.fillRect(0, 0, 500, 500);
    drawTriangle(triangle.a, triangle.b, triangle.c);
    drawPoint(point);
}

function drawTriangle(p0, p1, p2) {
    ctx.fillStyle = "#999";
    ctx.beginPath();
    ctx.moveTo(p0.x, p0.y);
    ctx.lineTo(p1.x, p1.y);
    ctx.lineTo(p2.x, p2.y);
    ctx.closePath();
    ctx.fill();
    ctx.fillStyle = "#000";
    ctx.font = "12px monospace";
    ctx.fillText("1", p0.x, p0.y);
    ctx.fillText("2", p1.x, p1.y);
    ctx.fillText("3", p2.x, p2.y);
}

function drawPoint(p) {
    ctx.fillStyle = "#F00";
    ctx.beginPath();
    ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
    ctx.fill();
}

function rand(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomTriangle() {
    while (true) {
        var result = {
            a: {
                x: rand(0, W),
                y: rand(0, H)
            },
            b: {
                x: rand(0, W),
                y: rand(0, H)
            },
            c: {
                x: rand(0, W),
                y: rand(0, H)
            }
        };
        if (checkClockwise(result.a, result.b, result.c)) return result;
    }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<pre>Click: place the point.
Double click: random triangle.</pre>

<pre id="result"></pre>

<canvas width="500" height="500"></canvas>

Ecco il codice C # equivalente per Unity: Here is equivalent C # code for Unity:

public static bool IsPointInClockwiseTriangle(Vector2 p, Vector2 p0, Vector2 p1, Vector2 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)
        return false;

    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);

    return (s + t) < A;
}

-1
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) return true;//is inside
    else return false;//is outside
    return 0;
} 

coordinate cartesiane quasi perfette convertite da baricentriche vengono esportate all'interno dei doppi * v (x) e * w (y). Entrambi i doppi di esportazione dovrebbero avere un * carattere prima in ogni caso, probabilmente: * v e * w Code possono essere usati anche per l'altro triangolo di un quadrilatero. Con la presente firmato ha scritto solo il triangolo abc dal quad abcd in senso orario.

A---B
|..\\.o|  
|....\\.| 
D---C 

il punto o è all'interno del triangolo ABC per il test con il secondo triangolo chiama questa funzione direzione CDA, ei risultati dovrebbero essere corretti dopo *v=1-*v;e *w=1-*w;per il quadrilatero


-3

Uno dei modi più semplici per verificare se l'area formata dai vertici del triangolo (x1, y1), (x2, y2), (x3, y3) è positiva o meno.

L'area può essere calcolata con la formula:

1/2 [x1 (y2 – y3) + x2 (y3 – y1) + x3 (y1 – y2)]

o il codice Python può essere scritto come:

def triangleornot(p1,p2,p3):
    return (1/ 2) [p1[0](p2[1]–p3[1]) + p2[0] (p3[1]–p1[1]) + p3[0] (p1[0]–p2[0])]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.