Come si rileva dove si intersecano due segmenti di linea? [chiuso]


518

Come faccio a determinare se due linee si intersecano o meno, e se lo fanno, in quale punto x, y?


Potrebbe essere utile pensare ai bordi del rettangolo come linee separate anziché al poligono completo.
Ryan Graham,

Nota del moderatore : discussione sulla questione se questo post è sull'argomento o meno su Overflow Meta Stack Ulteriori commenti su questo qui verranno eliminati.
Martijn Pieters

Risposte:


659

C'è un buon approccio a questo problema che utilizza prodotti incrociati vettoriali. Definire il prodotto incrociato vettoriale bidimensionale v  ×  w per essere v x  w y  -  v y  w x .

Supponiamo che i due segmenti di linea corrano da p a p  +  r e da q a q  +  s . Quindi qualsiasi punto sulla prima riga è rappresentabile come p  +  t  r (per un parametro scalare  t ) e qualsiasi punto sulla seconda riga come q  +  u  s (per un parametro scalare  u ).

Due segmenti di linea che si intersecano

Le due linee si intersecano se possiamo trovare t e u tale che:

p + t  r = q + u  s

Formule per il punto di intersezione

Attraversare entrambi i lati con s , ottenendo

( p + t  r ) × s = ( q + u  s ) × s

E poiché s  ×  s = 0, questo significa

t  ( r × s ) = ( q - p ) × s

E quindi, risolvendo per t :

t = ( q - p ) × s / ( r × s )

Allo stesso modo, siamo in grado di risolvere per u :

( p + t  r ) × r = ( q + u  s ) × r

u  ( s × r ) = ( p - q ) × r

u = ( p - q ) × r / ( s × r )

Per ridurre il numero di passaggi di calcolo, è conveniente riscriverlo come segue (ricordando che s  ×  r = -  r  ×  s ):

u = ( q - p ) × r / ( r × s )

Ora ci sono quattro casi:

  1. Se r  ×  s  = 0 e ( q  -  p ) ×  r  = 0, le due linee sono collineari.

    In questo caso, esprimere gli endpoint del secondo segmento ( q e q  +  s ) in termini di equazione del primo segmento di linea ( p + t r ):

    t 0 = ( q - p ) ·  r / ( r  ·  r )

    t 1 = ( q + s - p ) ·  r / ( r  ·  r ) = t 0 + s  ·  r / ( r  ·  r )

    Se l'intervallo tra t 0 e t 1 interseca l'intervallo [0, 1], i segmenti di linea sono collineari e sovrapposti; altrimenti sono collineari e disgiunti.

    Notare che se s e r puntano in direzioni opposte, allora s  ·  r <0 e quindi l'intervallo da controllare è [ t 1 , t 0 ] anziché [ t 0 , t 1 ].

  2. Se r  ×  s  = 0 e ( q  -  p ) ×  r  ≠ 0, le due linee sono parallele e non intersecanti.

  3. Se r  ×  s  ≠ 0 e 0 ≤  t  ≤ 1 e 0 ≤  u  ≤ 1, i due segmenti di linea si incontrano nel punto p + t  r = q + u  s .

  4. Altrimenti, i due segmenti di linea non sono paralleli ma non si intersecano.

Credito: questo metodo è la specializzazione bidimensionale dell'algoritmo di intersezione di linee 3D dall'articolo "Intersezione di due linee in tre spazi" di Ronald Goldman, pubblicato su Graphics Gems , pagina 304. In tre dimensioni, il solito caso è che le linee sono inclinate (né parallele né intersecanti) nel qual caso il metodo fornisce i punti di approccio più vicino delle due linee.


5
@myrkos: No. Il primo segmento di riga viene eseguito "da p a p + r", quindi quando viene rappresentato in termini parametrici come "p + tr", il segmento corrisponde a 0 ≤ t ≤ 1. Analogamente per l'altro segmento.
Gareth Rees,

7
Gareth, mi sento come se dovessi perdere qualcosa, ma come dividi (un vettore) per un vettore? Le vostre soluzioni per t e u terminano con / (r × s), ma (r × s)è un vettore, giusto? Un vettore (0, 0, rx * sy - ry * sx). E il lato sinistro è similmente un vettore parallelo all'asse z. Quindi ... divido semplicemente il componente z per l'altro componente z? La formula per t è in realtà |(q − p) × s| / |(r × s)|?
LarsH

7
@LarsH: vedi il primo paragrafo.
Gareth Rees,

35
Per gli interessati, ecco una semplice implementazione in C #, che prende le coordinate di inizio e fine di PointF per le linee, che sembra funzionare: ideone.com/PnPJgb
Matt,

24
Ho messo insieme un'implementazione JavaScript seguendo @Matt. Ho apportato correzioni per gli errori segnalati da Tekito.
pgkelley,

230

FWIW, la seguente funzione (in C) rileva entrambe le intersezioni di linea e determina il punto di intersezione. Si basa su un algoritmo in " Tricks of the Windows Game Programming Gurus " di Andre LeMothe " di . Non è dissimile da alcuni degli algoritmi in altre risposte (ad es. Di Gareth). LeMothe utilizza quindi Cramer's Rule (non chiedermelo) per risolvere le equazioni stesse.

Posso attestare che funziona nel mio debole clone di asteroidi e sembra trattare correttamente i casi limite descritti in altre risposte da Elemental, Dan e Wodzu. Probabilmente è anche più veloce del codice pubblicato da KingNestor perché è tutto moltiplicazione e divisione, nessuna radice quadrata!

Immagino che ci sia un certo potenziale per dividere per zero lì dentro, anche se non è stato un problema nel mio caso. Abbastanza facile da modificare per evitare comunque l'incidente.

// Returns 1 if the lines intersect, otherwise 0. In addition, if the lines 
// intersect the intersection point may be stored in the floats i_x and i_y.
char get_line_intersection(float p0_x, float p0_y, float p1_x, float p1_y, 
    float p2_x, float p2_y, float p3_x, float p3_y, float *i_x, float *i_y)
{
    float s1_x, s1_y, s2_x, s2_y;
    s1_x = p1_x - p0_x;     s1_y = p1_y - p0_y;
    s2_x = p3_x - p2_x;     s2_y = p3_y - p2_y;

    float s, t;
    s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
    t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);

    if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
    {
        // Collision detected
        if (i_x != NULL)
            *i_x = p0_x + (t * s1_x);
        if (i_y != NULL)
            *i_y = p0_y + (t * s1_y);
        return 1;
    }

    return 0; // No collision
}

A proposito, devo dire che nel libro di LeMothe, anche se apparentemente corregge l'algoritmo, l'esempio concreto mostra che inserisce i numeri sbagliati e fa calcoli sbagliati. Per esempio:

(4 * (4 - 1) + 12 * (7 - 1)) / (17 * 4 + 12 * 10)

= 844 / 0,88

= 0.44

Questo mi ha confuso per ore . :(


9
funzione getLineIntersection (p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y) {var s1_x, s1_y, s2_x, s2_y; s1_x = p1_x - p0_x; s1_y = p1_y - p0_y; s2_x = p3_x - p2_x; s2_y = p3_y - p2_y; var s, t; s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y); t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
cortijon,

5
if (s> = 0 && s <= 1 && t> = 0 && t <= 1) {// Rilevata collisione var intX = p0_x + (t * s1_x); var intY = p0_y + (t * s1_y); return [intX, intY]; } restituisce null; // Nessuna collisione}
cortijon

13
buon algoritmo, tuttavia non gestisce i casi in cui il determinante è 0. (-s2_x * s1_y + s1_x * s2_y sopra). Se è 0 (o vicino a 0) le linee sono parallele o collineari. Se è collineare, l'intersezione potrebbe essere un altro segmento di linea.
sabato

16
Le due operazioni di divisione possono essere evitate per la velocità (i costi di divisione superano la moltiplicazione); se le linee si intersecano hai bisogno di una divisione, se non si intersecano hai bisogno di zero. Si dovrebbe prima calcolare il denominatore e fermarsi presto se è zero (eventualmente aggiungendo codice per rilevare la colinearità). Successivamente, invece di calcolare se tdirettamente, testare la relazione tra i due numeratori e il denominatore. Solo se si conferma che le linee si intersecano, in realtà è necessario calcolare il valore di t(ma non s).
Qwertie,

18
Ho eseguito i test delle prestazioni su tutti gli algoritmi pubblicati qui, e questo è almeno due volte più veloce degli altri. Grazie per la pubblicazione!
lajos,

63

Il problema si riduce a questa domanda: si intersecano due linee da A a B e da C a D? Quindi puoi chiederlo quattro volte (tra la linea e ciascuno dei quattro lati del rettangolo).

Ecco la matematica vettoriale per farlo. Suppongo che la linea da A a B sia la linea in questione e la linea da C a D sia una delle linee rettangolari. La mia notazione è che Axè la "coordinata x di A" ed Cyè la "coordinata y di C." E " *" significa punto-prodotto, quindi ad es A*B = Ax*Bx + Ay*By.

E = B-A = ( Bx-Ax, By-Ay )
F = D-C = ( Dx-Cx, Dy-Cy ) 
P = ( -Ey, Ex )
h = ( (A-C) * P ) / ( F * P )

Questo hnumero è la chiave. Se hè compreso tra 0e 1, le linee si intersecano, altrimenti non lo fanno. Se F*Pè zero, ovviamente non è possibile effettuare il calcolo, ma in questo caso le linee sono parallele e quindi si intersecano solo nei casi ovvi.

Il punto esatto di intersezione è C + F*h.

Più divertimento:

If hè esattamente 0 o 1le linee toccano in corrispondenza di un punto finale. Puoi considerarlo un "incrocio" o meno come ritieni opportuno.

In particolare, hè quanto devi moltiplicare la lunghezza della linea per toccare esattamente l'altra linea.

Pertanto, If h<0, significa che la linea del rettangolo è "dietro" la linea data (con "direzione" essendo "da A a B"), e seh>1 la linea del rettangolo è "davanti" alla linea data.

Derivazione:

A e C sono vettori che indicano l'inizio della linea; E e F sono i vettori dalle estremità di A e C che formano la linea.

Per due linee non parallele nel piano, deve esserci esattamente una coppia di scalari ge htale che questa equazione valga:

A + E*g = C + F*h

Perché? Perché due linee non parallele devono intersecarsi, il che significa che è possibile ridimensionare entrambe le linee di una certa quantità ciascuna e toccarsi.

( All'inizio sembra una singola equazione con due incognite! Ma non è quando si considera che si tratta di un'equazione vettoriale 2D, il che significa che in realtà è una coppia di equazioni in xe y.)

Dobbiamo eliminare una di queste variabili. Un modo semplice è rendere il Etermine zero. Per fare ciò, prendi il punto-prodotto di entrambi i lati dell'equazione usando un vettore che andrà a zero con E. Quel vettore che ho chiamato Psopra, e ho fatto l'ovvia trasformazione di E.

Ora hai:

A*P = C*P + F*P*h
(A-C)*P = (F*P)*h
( (A-C)*P ) / (F*P) = h

29
Questo algoritmo è carino. Ma c'è un buco in esso, come indicato da Dan @ stackoverflow.com/questions/563198/… & Elemental @ stackoverflow.com/questions/563198/… Sarebbe bello se aggiornassi la tua risposta per riferimento futuro. Grazie.
Chantz,

2
Questo algoritmo è numericamente stabile? Ho provato un approccio simile e si è scoperto che ha dato risultati strani quando si lavora su galleggianti.
milosz,

3
Sembra esserci un altro problema con questo algoritmo. Quando viene alimentato i punti A = {1, 0} B = {2, 0} C = {0, 0} D = {1,0}, anche se i segmenti di linea toccano chiaramente un'estremità, F P (e anche E Q, in linea con la correzione dell'utente sotto) sono entrambi 0, facendo sì che la divisione per 0 trovi h e g. Sto ancora lavorando sulla soluzione per questo, ma ho pensato che valesse la pena sottolineare il problema.
candrews,

12
Questa risposta è semplicemente errata. Prova A = {0,0}, B = {0,1}, C = {0,2} D = {2,0}
Tim Cooper,

6
A + E*g = C + F*hLe due linee si intersecano se e solo se la soluzione a tale equazione (supponendo che non siano parallele) ha entrambi geh tra 0 e 1 (interno o esclusivo, a seconda che si contenga toccando in corrispondenza di un punto finale).
Daniel Fischer,

46

Ho cercato di implementare l'algoritmo così elegantemente descritto da Jason sopra; sfortunatamente mentre lavoravo alla matematica nel debug ho trovato molti casi per i quali non funziona.

Ad esempio, consideriamo i punti A (10,10) B (20,20) C (10,1) D (1,10) che danno h = .5 e tuttavia è chiaro dall'esame che questi segmenti non sono vicini a ciascuno altro.

La rappresentazione grafica di questo chiarisce che i criteri 0 <h <1 indicano solo che il punto di intercettazione si troverebbe su CD se esistesse, ma non dice nulla se quel punto si trova su AB. Per assicurarsi che sia presente un punto croce, è necessario eseguire il calcolo simmetrico per la variabile g e il requisito per l'intercettazione è: 0 <g <1 AND 0 <h <1


2
Mi sono strappato i capelli cercando di capire perché la risposta accettata non funzionava per me. Grazie mille!
Matt Bridges,

1
Notare anche che le condizioni al contorno funzionano in questo caso (cioè per h = 0 o h = 1 o g = 0 o g = 1 le linee 'solo' toccano
Elementale

Per le persone che hanno problemi a visualizzare il risultato, ho implementato questo in Javascript: jsfiddle.net/ferrybig/eokwL9mp
Ferrybig

45

Ecco un miglioramento alla risposta di Gavin. Anche la soluzione di marcp è simile, ma non rinvia la divisione.

Questa in realtà risulta essere un'applicazione pratica anche della risposta di Gareth Rees, poiché l'equivalente del prodotto incrociato in 2D è il prodotto perp-dot, che è ciò di cui questo codice utilizza tre. Passando al 3D e usando il prodotto incrociato, interpolando sia s che t alla fine, si ottengono i due punti più vicini tra le linee in 3D. Comunque, la soluzione 2D:

int get_line_intersection(float p0_x, float p0_y, float p1_x, float p1_y, 
    float p2_x, float p2_y, float p3_x, float p3_y, float *i_x, float *i_y)
{
    float s02_x, s02_y, s10_x, s10_y, s32_x, s32_y, s_numer, t_numer, denom, t;
    s10_x = p1_x - p0_x;
    s10_y = p1_y - p0_y;
    s32_x = p3_x - p2_x;
    s32_y = p3_y - p2_y;

    denom = s10_x * s32_y - s32_x * s10_y;
    if (denom == 0)
        return 0; // Collinear
    bool denomPositive = denom > 0;

    s02_x = p0_x - p2_x;
    s02_y = p0_y - p2_y;
    s_numer = s10_x * s02_y - s10_y * s02_x;
    if ((s_numer < 0) == denomPositive)
        return 0; // No collision

    t_numer = s32_x * s02_y - s32_y * s02_x;
    if ((t_numer < 0) == denomPositive)
        return 0; // No collision

    if (((s_numer > denom) == denomPositive) || ((t_numer > denom) == denomPositive))
        return 0; // No collision
    // Collision detected
    t = t_numer / denom;
    if (i_x != NULL)
        *i_x = p0_x + (t * s10_x);
    if (i_y != NULL)
        *i_y = p0_y + (t * s10_y);

    return 1;
}

Fondamentalmente rimanda la divisione fino all'ultimo momento e sposta la maggior parte dei test fino a quando non vengono eseguiti determinati calcoli, aggiungendo quindi anticipazioni. Infine, evita anche la divisione per zero case che si verifica quando le linee sono parallele.

Potresti anche prendere in considerazione l'utilizzo di un test epsilon piuttosto che un confronto con zero. Linee estremamente vicine al parallelo possono produrre risultati leggermente spenti. Questo non è un bug, è una limitazione con la matematica in virgola mobile.


1
Non riesce se alcuni punti hanno un valore di 0 .. ciò non dovrebbe accadere giusto?
hfossli,

1
Ho apportato una correzione per un bug introdotto durante il differimento della divisione. t potrebbe essere positivo quando il numero e il denom erano entrambi negativi.
iMalc,

2
Non funziona se p0-p1 è verticale e p2-p3 è orizzontale e i due segmenti si incrociano. (viene eseguito il primo ritorno)
Fabio Dalla Libera,

La custodia coolinear ha due possibilità: non sovrapposte e sovrapposte. Il primo ritorno restituisce falso il secondo vero. Nel tuo codice questo non è testato. restituisce sempre false come la maggior parte delle risposte qui. È un peccato che nessuna soluzione sembri davvero funzionare.
AlexWien,

3
Puoi illuminarmi perché tutti questi usano nomi di variabili così vaghi come s32_yinvece di qualcosa che descrive com'è point2YDifference?
Supuhstar,

40

Domanda C: Come si rileva se due segmenti di linea si intersecano o no?

Ho cercato lo stesso argomento e non ero contento delle risposte. Quindi ho scritto un articolo che spiega molto dettagliatamente come verificare se due segmenti di linea si intersecano con molte immagini. Esiste un codice Java completo (e testato).

Ecco l'articolo, ritagliato nelle parti più importanti:

L'algoritmo, che controlla se il segmento di linea a si interseca con il segmento di linea b, è simile al seguente:

Inserisci qui la descrizione dell'immagine

Cosa sono i riquadri? Ecco due riquadri di delimitazione di due segmenti di linea:

inserisci qui la descrizione dell'immagine

Se entrambe le caselle di delimitazione hanno un'intersezione, si sposta il segmento di linea in modo che un punto si trovi su (0 | 0). Ora hai una linea attraverso l'origine definita da a. Ora sposta il segmento di linea b allo stesso modo e controlla se i nuovi punti del segmento di linea b si trovano su lati diversi della linea a. In questo caso, verificalo viceversa. Se questo è anche il caso, i segmenti di linea si intersecano. In caso contrario, non si intersecano.

Domanda A: Dove si intersecano due segmenti di linea?

Sai che due segmenti di linea aeb si intersecano. Se non lo sai, controlla con gli strumenti che ti ho dato in "Domanda C".

Ora puoi esaminare alcuni casi e ottenere la soluzione con matematica di settima elementare (vedi codice ed esempio interattivo ).

Domanda B: Come si rileva se due linee si intersecano o no?

Diciamo che il vostro punto A = (x1, y1), punto B = (x2, y2), C = (x_3, y_3), D = (x_4, y_4). La tua prima riga è definita da AB (con A! = B) e la seconda da CD (con C! = D).

function doLinesIntersect(AB, CD) {
    if (x1 == x2) {
        return !(x3 == x4 && x1 != x3);
    } else if (x3 == x4) {
        return true;
    } else {
        // Both lines are not parallel to the y-axis
        m1 = (y1-y2)/(x1-x2);
        m2 = (y3-y4)/(x3-x4);
        return m1 != m2;
    }
}

Domanda D: Dove si intersecano due linee?

Verificare con la domanda B se si intersecano affatto.

Le linee a e b sono definite da due punti per ogni linea. In pratica puoi applicare la stessa logica usata nella domanda A.


15
Per essere chiari, la domanda B in questa risposta riguarda veramente due linee che si intersecano, non segmenti di linea. Non mi sto lamentando; non è errato. Solo, non voglio che qualcuno venga ingannato.
phord,

1
Non c'è "domanda C". E la domanda D ritorna solo sulla domanda A.
Konrad Viltersten,

21

La risposta una volta accettata qui è errata (da allora non è stata accettata, quindi evviva!). Non elimina correttamente tutte le non intersezioni. In pratica può sembrare che funzioni ma può fallire, specialmente nel caso in cui 0 e 1 siano considerati validi per h.

Considera il caso seguente:

Linee a (4,1) - (5,1) e (0,0) - (0,2)

Queste sono linee perpendicolari che chiaramente non si sovrappongono.

A = (4,1)
B = (5,1)
C = (0,0)
D = (0,2)
E = (5,1) - (4,1) = (- 1,0)
F = (0,2) - (0,0) = (0, -2)
P = (0,1)
h = ((4,1) - (0,0)) punto (0,1) / ((0 , -2) punto (0,1)) = 0

Secondo la risposta sopra, questi due segmenti di linea si incontrano in corrispondenza di un endpoint (valori di 0 e 1). Tale endpoint sarebbe:

(0,0) + (0, -2) * 0 = (0,0)

Quindi, apparentemente i due segmenti di linea si incontrano in (0,0), che è sulla linea CD, ma non sulla linea AB. Quindi cosa non va? La risposta è che i valori di 0 e 1 non sono validi e solo a volte HAPPEN per prevedere correttamente l'intersezione dell'endpoint. Quando l'estensione di una linea (ma non l'altra) incontrerebbe il segmento di linea, l'algoritmo prevede un'intersezione di segmenti di linea, ma ciò non è corretto. Immagino che provando a partire da AB vs CD e quindi anche testando con CD vs AB, questo problema verrebbe eliminato. Solo se entrambi cadono tra 0 e 1 inclusivamente si può dire che si intersecano.

Consiglio di utilizzare il metodo del prodotto incrociato vettoriale se è necessario prevedere i punti finali.

-Dan


4
La risposta "accettata" può cambiare, quindi dovresti chiamarla qualcos'altro. (In effetti, penso che sia cambiato dal tuo commento)
Johannes Hoff,

14

Versione Python della risposta di iMalc:

def find_intersection( p0, p1, p2, p3 ) :

    s10_x = p1[0] - p0[0]
    s10_y = p1[1] - p0[1]
    s32_x = p3[0] - p2[0]
    s32_y = p3[1] - p2[1]

    denom = s10_x * s32_y - s32_x * s10_y

    if denom == 0 : return None # collinear

    denom_is_positive = denom > 0

    s02_x = p0[0] - p2[0]
    s02_y = p0[1] - p2[1]

    s_numer = s10_x * s02_y - s10_y * s02_x

    if (s_numer < 0) == denom_is_positive : return None # no collision

    t_numer = s32_x * s02_y - s32_y * s02_x

    if (t_numer < 0) == denom_is_positive : return None # no collision

    if (s_numer > denom) == denom_is_positive or (t_numer > denom) == denom_is_positive : return None # no collision


    # collision detected

    t = t_numer / denom

    intersection_point = [ p0[0] + (t * s10_x), p0[1] + (t * s10_y) ]


    return intersection_point

Ricorda che devi usare i denom = float(...)
numeri in virgola mobile

11

Trovare l'intersezione corretta di due segmenti di linea è un'attività non banale con molti casi limite. Ecco una soluzione ben documentata, funzionante e testata in Java.

In sostanza, ci sono tre cose che possono accadere quando si trova l'intersezione di due segmenti di linea:

  1. I segmenti non si intersecano

  2. C'è un punto di intersezione unico

  3. L'intersezione è un altro segmento

NOTA : Nel codice, suppongo che un segmento di linea (x1, y1), (x2, y2) con x1 = x2 e y1 = y2 sia un segmento di linea valido. Matematicamente parlando, un segmento di linea è costituito da punti distinti, ma sto permettendo ai segmenti di essere punti in questa implementazione per completezza.

Il codice è preso dal mio repository github

/**
 * This snippet finds the intersection of two line segments.
 * The intersection may either be empty, a single point or the
 * intersection is a subsegment there's an overlap.
 */

import static java.lang.Math.abs;
import static java.lang.Math.max;
import static java.lang.Math.min;

import java.util.ArrayList;
import java.util.List;

public class LineSegmentLineSegmentIntersection {

  // Small epsilon used for double value comparison.
  private static final double EPS = 1e-5;

  // 2D Point class.
  public static class Pt {
    double x, y;
    public Pt(double x, double y) {
      this.x = x; 
      this.y = y;
    }
    public boolean equals(Pt pt) {
      return abs(x - pt.x) < EPS && abs(y - pt.y) < EPS;
    }
  }

  // Finds the orientation of point 'c' relative to the line segment (a, b)
  // Returns  0 if all three points are collinear.
  // Returns -1 if 'c' is clockwise to segment (a, b), i.e right of line formed by the segment.
  // Returns +1 if 'c' is counter clockwise to segment (a, b), i.e left of line
  // formed by the segment.
  public static int orientation(Pt a, Pt b, Pt c) {
    double value = (b.y - a.y) * (c.x - b.x) - 
                   (b.x - a.x) * (c.y - b.y);
    if (abs(value) < EPS) return 0;
    return (value > 0) ? -1 : +1;
  }

  // Tests whether point 'c' is on the line segment (a, b).
  // Ensure first that point c is collinear to segment (a, b) and
  // then check whether c is within the rectangle formed by (a, b)
  public static boolean pointOnLine(Pt a, Pt b, Pt c) {
    return orientation(a, b, c) == 0 && 
           min(a.x, b.x) <= c.x && c.x <= max(a.x, b.x) && 
           min(a.y, b.y) <= c.y && c.y <= max(a.y, b.y);
  }

  // Determines whether two segments intersect.
  public static boolean segmentsIntersect(Pt p1, Pt p2, Pt p3, Pt p4) {

    // Get the orientation of points p3 and p4 in relation
    // to the line segment (p1, p2)
    int o1 = orientation(p1, p2, p3);
    int o2 = orientation(p1, p2, p4);
    int o3 = orientation(p3, p4, p1);
    int o4 = orientation(p3, p4, p2);

    // If the points p1, p2 are on opposite sides of the infinite
    // line formed by (p3, p4) and conversly p3, p4 are on opposite
    // sides of the infinite line formed by (p1, p2) then there is
    // an intersection.
    if (o1 != o2 && o3 != o4) return true;

    // Collinear special cases (perhaps these if checks can be simplified?)
    if (o1 == 0 && pointOnLine(p1, p2, p3)) return true;
    if (o2 == 0 && pointOnLine(p1, p2, p4)) return true;
    if (o3 == 0 && pointOnLine(p3, p4, p1)) return true;
    if (o4 == 0 && pointOnLine(p3, p4, p2)) return true;

    return false;
  }

  public static List<Pt> getCommonEndpoints(Pt p1, Pt p2, Pt p3, Pt p4) {

    List<Pt> points = new ArrayList<>();

    if (p1.equals(p3)) {
      points.add(p1);
      if (p2.equals(p4)) points.add(p2);

    } else if (p1.equals(p4)) {
      points.add(p1);
      if (p2.equals(p3)) points.add(p2);

    } else if (p2.equals(p3)) {
      points.add(p2);
      if (p1.equals(p4)) points.add(p1);

    } else if (p2.equals(p4)) {
      points.add(p2);
      if (p1.equals(p3)) points.add(p1);
    }

    return points;
  }

  // Finds the intersection point(s) of two line segments. Unlike regular line 
  // segments, segments which are points (x1 = x2 and y1 = y2) are allowed.
  public static Pt[] lineSegmentLineSegmentIntersection(Pt p1, Pt p2, Pt p3, Pt p4) {

    // No intersection.
    if (!segmentsIntersect(p1, p2, p3, p4)) return new Pt[]{};

    // Both segments are a single point.
    if (p1.equals(p2) && p2.equals(p3) && p3.equals(p4))
      return new Pt[]{p1};

    List<Pt> endpoints = getCommonEndpoints(p1, p2, p3, p4);
    int n = endpoints.size();

    // One of the line segments is an intersecting single point.
    // NOTE: checking only n == 1 is insufficient to return early
    // because the solution might be a sub segment.
    boolean singleton = p1.equals(p2) || p3.equals(p4);
    if (n == 1 && singleton) return new Pt[]{endpoints.get(0)};

    // Segments are equal.
    if (n == 2) return new Pt[]{endpoints.get(0), endpoints.get(1)};

    boolean collinearSegments = (orientation(p1, p2, p3) == 0) && 
                                (orientation(p1, p2, p4) == 0);

    // The intersection will be a sub-segment of the two
    // segments since they overlap each other.
    if (collinearSegments) {

      // Segment #2 is enclosed in segment #1
      if (pointOnLine(p1, p2, p3) && pointOnLine(p1, p2, p4))
        return new Pt[]{p3, p4};

      // Segment #1 is enclosed in segment #2
      if (pointOnLine(p3, p4, p1) && pointOnLine(p3, p4, p2))
        return new Pt[]{p1, p2};

      // The subsegment is part of segment #1 and part of segment #2.
      // Find the middle points which correspond to this segment.
      Pt midPoint1 = pointOnLine(p1, p2, p3) ? p3 : p4;
      Pt midPoint2 = pointOnLine(p3, p4, p1) ? p1 : p2;

      // There is actually only one middle point!
      if (midPoint1.equals(midPoint2)) return new Pt[]{midPoint1};

      return new Pt[]{midPoint1, midPoint2};
    }

    /* Beyond this point there is a unique intersection point. */

    // Segment #1 is a vertical line.
    if (abs(p1.x - p2.x) < EPS) {
      double m = (p4.y - p3.y) / (p4.x - p3.x);
      double b = p3.y - m * p3.x;
      return new Pt[]{new Pt(p1.x, m * p1.x + b)};
    }

    // Segment #2 is a vertical line.
    if (abs(p3.x - p4.x) < EPS) {
      double m = (p2.y - p1.y) / (p2.x - p1.x);
      double b = p1.y - m * p1.x;
      return new Pt[]{new Pt(p3.x, m * p3.x + b)};
    }

    double m1 = (p2.y - p1.y) / (p2.x - p1.x);
    double m2 = (p4.y - p3.y) / (p4.x - p3.x);
    double b1 = p1.y - m1 * p1.x;
    double b2 = p3.y - m2 * p3.x;
    double x = (b2 - b1) / (m1 - m2);
    double y = (m1 * b2 - m2 * b1) / (m1 - m2);

    return new Pt[]{new Pt(x, y)};
  }

}

Ecco un semplice esempio di utilizzo:

  public static void main(String[] args) {

    // Segment #1 is (p1, p2), segment #2 is (p3, p4)
    Pt p1, p2, p3, p4;

    p1 = new Pt(-2, 4); p2 = new Pt(3, 3);
    p3 = new Pt(0, 0);  p4 = new Pt(2, 4);
    Pt[] points = lineSegmentLineSegmentIntersection(p1, p2, p3, p4);
    Pt point = points[0];

    // Prints: (1.636, 3.273)
    System.out.printf("(%.3f, %.3f)\n", point.x, point.y);

    p1 = new Pt(-10, 0); p2 = new Pt(+10, 0);
    p3 = new Pt(-5, 0);  p4 = new Pt(+5, 0);
    points = lineSegmentLineSegmentIntersection(p1, p2, p3, p4);
    Pt point1 = points[0], point2 = points[1];

    // Prints: (-5.000, 0.000) (5.000, 0.000)
    System.out.printf("(%.3f, %.3f) (%.3f, %.3f)\n", point1.x, point1.y, point2.x, point2.y);
  }

ha funzionato per il mio sistema di coordinate geografiche! Grazie! Ma è per intersezione di linee infinite e io sono più alla ricerca di intersezione di linee finite.
M. Usman Khan,

8

Volevo solo menzionare che una buona spiegazione e una soluzione esplicita si possono trovare nella serie Ricette numeriche. Ho la terza edizione e la risposta è a pagina 1117, sezione 21.4. Un'altra soluzione con una nomenclatura diversa può essere trovata in un articolo di Marina Gavrilova Reliable Line Section Interection Testing . La sua soluzione è, a mio avviso, un po 'più semplice.

La mia implementazione è di seguito:

bool NuGeometry::IsBetween(const double& x0, const double& x, const double& x1){
   return (x >= x0) && (x <= x1);
}

bool NuGeometry::FindIntersection(const double& x0, const double& y0, 
     const double& x1, const double& y1,
     const double& a0, const double& b0, 
     const double& a1, const double& b1, 
     double& xy, double& ab) {
   // four endpoints are x0, y0 & x1,y1 & a0,b0 & a1,b1
   // returned values xy and ab are the fractional distance along xy and ab
   // and are only defined when the result is true

   bool partial = false;
   double denom = (b0 - b1) * (x0 - x1) - (y0 - y1) * (a0 - a1);
   if (denom == 0) {
      xy = -1;
      ab = -1;
   } else {
      xy = (a0 * (y1 - b1) + a1 * (b0 - y1) + x1 * (b1 - b0)) / denom;
      partial = NuGeometry::IsBetween(0, xy, 1);
      if (partial) {
         // no point calculating this unless xy is between 0 & 1
         ab = (y1 * (x0 - a1) + b1 * (x1 - x0) + y0 * (a1 - x1)) / denom; 
      }
   }
   if ( partial && NuGeometry::IsBetween(0, ab, 1)) {
      ab = 1-ab;
      xy = 1-xy;
      return true;
   }  else return false;
}

Non funziona per p1 = (0,0), p2 = (10,0), p3 = (9,0), p4 = (20,0)
padmalcom

Dipende dalla tua definizione di "non funziona" credo. Denom è 0, quindi restituirà false che mi sembra corretto in quanto non si intersecano. Colinear non è uguale all'intersezione.
marcp,

8

Molte soluzioni sono disponibili sopra, ma penso che la soluzione seguente sia piuttosto semplice e facile da capire.

Due segmenti Vector AB e Vector CD si intersecano se e solo se

  1. I punti finali aeb si trovano sui lati opposti del segmento CD.
  2. Gli endpoint c ed d si trovano sul lato opposto del segmento AB.

Più specificamente aeb si trovano sul lato opposto del segmento CD se e solo se esattamente una delle due triple a, c, d e b, c, d è in senso antiorario.

Intersect(a, b, c, d)
 if CCW(a, c, d) == CCW(b, c, d)
    return false;
 else if CCW(a, b, c) == CCW(a, b, d)
    return false;
 else
    return true;

Qui CCW rappresentano in senso antiorario che restituisce vero / falso in base all'orientamento dei punti.

Fonte: http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf Pagina 2


2
Penso che dovresti essere un po 'più specifico: come viene CCWdefinito il test? Con il segno del prodotto esterno?
Ocramz,

Grazie; questo pseudo-codice ha permesso un'implementazione molto semplice in Scratch; vedi questo progetto: scratch.mit.edu/projects/129319027
Ruud Helderman

8

C e Objective-C

Basato sulla risposta di Gareth Rees

const AGKLine AGKLineZero = (AGKLine){(CGPoint){0.0, 0.0}, (CGPoint){0.0, 0.0}};

AGKLine AGKLineMake(CGPoint start, CGPoint end)
{
    return (AGKLine){start, end};
}

double AGKLineLength(AGKLine l)
{
    return CGPointLengthBetween_AGK(l.start, l.end);
}

BOOL AGKLineIntersection(AGKLine l1, AGKLine l2, CGPoint *out_pointOfIntersection)
{
    // http://stackoverflow.com/a/565282/202451

    CGPoint p = l1.start;
    CGPoint q = l2.start;
    CGPoint r = CGPointSubtract_AGK(l1.end, l1.start);
    CGPoint s = CGPointSubtract_AGK(l2.end, l2.start);

    double s_r_crossProduct = CGPointCrossProductZComponent_AGK(r, s);
    double t = CGPointCrossProductZComponent_AGK(CGPointSubtract_AGK(q, p), s) / s_r_crossProduct;
    double u = CGPointCrossProductZComponent_AGK(CGPointSubtract_AGK(q, p), r) / s_r_crossProduct;

    if(t < 0 || t > 1.0 || u < 0 || u > 1.0)
    {
        if(out_pointOfIntersection != NULL)
        {
            *out_pointOfIntersection = CGPointZero;
        }
        return NO;
    }
    else
    {
        if(out_pointOfIntersection != NULL)
        {
            CGPoint i = CGPointAdd_AGK(p, CGPointMultiply_AGK(r, t));
            *out_pointOfIntersection = i;
        }
        return YES;
    }
}

CGFloat CGPointCrossProductZComponent_AGK(CGPoint v1, CGPoint v2)
{
    return v1.x * v2.y - v1.y * v2.x;
}

CGPoint CGPointSubtract_AGK(CGPoint p1, CGPoint p2)
{
    return (CGPoint){p1.x - p2.x, p1.y - p2.y};
}

CGPoint CGPointAdd_AGK(CGPoint p1, CGPoint p2)
{
    return (CGPoint){p1.x + p2.x, p1.y + p2.y};
}

CGFloat CGPointCrossProductZComponent_AGK(CGPoint v1, CGPoint v2)
{
    return v1.x * v2.y - v1.y * v2.x;
}

CGPoint CGPointMultiply_AGK(CGPoint p1, CGFloat factor)
{
    return (CGPoint){p1.x * factor, p1.y * factor};
}

Molte delle funzioni e delle strutture sono private, ma dovresti essere abbastanza facile sapere cosa sta succedendo. Questo è pubblico su questo repository https://github.com/hfossli/AGGeometryKit/


Da dove proviene AGPointZero in questo codice?
seanicus,

1
@seanicus ha aggiornato l'esempio per utilizzare invece
CGPoint

6

Questo funziona bene per me. Tratto da qui .

 // calculates intersection and checks for parallel lines.  
 // also checks that the intersection point is actually on  
 // the line segment p1-p2  
 Point findIntersection(Point p1,Point p2,  
   Point p3,Point p4) {  
   float xD1,yD1,xD2,yD2,xD3,yD3;  
   float dot,deg,len1,len2;  
   float segmentLen1,segmentLen2;  
   float ua,ub,div;  

   // calculate differences  
   xD1=p2.x-p1.x;  
   xD2=p4.x-p3.x;  
   yD1=p2.y-p1.y;  
   yD2=p4.y-p3.y;  
   xD3=p1.x-p3.x;  
   yD3=p1.y-p3.y;    

   // calculate the lengths of the two lines  
   len1=sqrt(xD1*xD1+yD1*yD1);  
   len2=sqrt(xD2*xD2+yD2*yD2);  

   // calculate angle between the two lines.  
   dot=(xD1*xD2+yD1*yD2); // dot product  
   deg=dot/(len1*len2);  

   // if abs(angle)==1 then the lines are parallell,  
   // so no intersection is possible  
   if(abs(deg)==1) return null;  

   // find intersection Pt between two lines  
   Point pt=new Point(0,0);  
   div=yD2*xD1-xD2*yD1;  
   ua=(xD2*yD3-yD2*xD3)/div;  
   ub=(xD1*yD3-yD1*xD3)/div;  
   pt.x=p1.x+ua*xD1;  
   pt.y=p1.y+ua*yD1;  

   // calculate the combined length of the two segments  
   // between Pt-p1 and Pt-p2  
   xD1=pt.x-p1.x;  
   xD2=pt.x-p2.x;  
   yD1=pt.y-p1.y;  
   yD2=pt.y-p2.y;  
   segmentLen1=sqrt(xD1*xD1+yD1*yD1)+sqrt(xD2*xD2+yD2*yD2);  

   // calculate the combined length of the two segments  
   // between Pt-p3 and Pt-p4  
   xD1=pt.x-p3.x;  
   xD2=pt.x-p4.x;  
   yD1=pt.y-p3.y;  
   yD2=pt.y-p4.y;  
   segmentLen2=sqrt(xD1*xD1+yD1*yD1)+sqrt(xD2*xD2+yD2*yD2);  

   // if the lengths of both sets of segments are the same as  
   // the lenghts of the two lines the point is actually  
   // on the line segment.  

   // if the point isn’t on the line, return null  
   if(abs(len1-segmentLen1)>0.01 || abs(len2-segmentLen2)>0.01)  
     return null;  

   // return the valid intersection  
   return pt;  
 }  

 class Point{  
   float x,y;  
   Point(float x, float y){  
     this.x = x;  
     this.y = y;  
   }  

   void set(float x, float y){  
     this.x = x;  
     this.y = y;  
   }  
 }  

8
Esistono diversi problemi con questo codice. Può sollevare un'eccezione a causa della divisione per zero; è lento perché prende radici quadrate; e talvolta restituisce falsi positivi perché utilizza un fattore di sfumatura. Puoi fare meglio di così!
Gareth Rees,

Va bene come soluzione, ma quella data da Jason è decisamente più veloce dal punto di vista computazionale ed evita molti problemi con questa soluzione
Elementale,

6

Ho provato alcune di queste risposte, ma non hanno funzionato per me (scusate ragazzi); dopo qualche altra ricerca in rete ho trovato questo .

Con una piccola modifica al suo codice ora ho questa funzione che restituirà il punto di intersezione o se non viene trovata alcuna intersezione restituirà -1, -1.

    Public Function intercetion(ByVal ax As Integer, ByVal ay As Integer, ByVal bx As Integer, ByVal by As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal dx As Integer, ByVal dy As Integer) As Point
    '//  Determines the intersection point of the line segment defined by points A and B
    '//  with the line segment defined by points C and D.
    '//
    '//  Returns YES if the intersection point was found, and stores that point in X,Y.
    '//  Returns NO if there is no determinable intersection point, in which case X,Y will
    '//  be unmodified.

    Dim distAB, theCos, theSin, newX, ABpos As Double

    '//  Fail if either line segment is zero-length.
    If ax = bx And ay = by Or cx = dx And cy = dy Then Return New Point(-1, -1)

    '//  Fail if the segments share an end-point.
    If ax = cx And ay = cy Or bx = cx And by = cy Or ax = dx And ay = dy Or bx = dx And by = dy Then Return New Point(-1, -1)

    '//  (1) Translate the system so that point A is on the origin.
    bx -= ax
    by -= ay
    cx -= ax
    cy -= ay
    dx -= ax
    dy -= ay

    '//  Discover the length of segment A-B.
    distAB = Math.Sqrt(bx * bx + by * by)

    '//  (2) Rotate the system so that point B is on the positive X axis.
    theCos = bx / distAB
    theSin = by / distAB
    newX = cx * theCos + cy * theSin
    cy = cy * theCos - cx * theSin
    cx = newX
    newX = dx * theCos + dy * theSin
    dy = dy * theCos - dx * theSin
    dx = newX

    '//  Fail if segment C-D doesn't cross line A-B.
    If cy < 0 And dy < 0 Or cy >= 0 And dy >= 0 Then Return New Point(-1, -1)

    '//  (3) Discover the position of the intersection point along line A-B.
    ABpos = dx + (cx - dx) * dy / (dy - cy)

    '//  Fail if segment C-D crosses line A-B outside of segment A-B.
    If ABpos < 0 Or ABpos > distAB Then Return New Point(-1, -1)

    '//  (4) Apply the discovered position to line A-B in the original coordinate system.
    '*X=Ax+ABpos*theCos
    '*Y=Ay+ABpos*theSin

    '//  Success.
    Return New Point(ax + ABpos * theCos, ay + ABpos * theSin)
End Function

6

Sembra esserci un certo interesse per la risposta di Gavin per la quale cortijon ha proposto una versione javascript nei commenti e iMalc ha fornito una versione con un numero leggermente inferiore di calcoli . Alcuni hanno sottolineato carenze con varie proposte di codice e altri hanno commentato l'efficienza di alcune proposte di codice.

L'algoritmo fornito da iMalc tramite la risposta di Gavin è quello che sto attualmente usando in un progetto javascript e volevo solo fornire una versione pulita qui se può aiutare qualcuno.

// Some variables for reuse, others may do this differently
var p0x, p1x, p2x, p3x, ix,
    p0y, p1y, p2y, p3y, iy,
    collisionDetected;

// do stuff, call other functions, set endpoints...

// note: for my purpose I use |t| < |d| as opposed to
// |t| <= |d| which is equivalent to 0 <= t < 1 rather than
// 0 <= t <= 1 as in Gavin's answer - results may vary

var lineSegmentIntersection = function(){
    var d, dx1, dx2, dx3, dy1, dy2, dy3, s, t;

    dx1 = p1x - p0x;      dy1 = p1y - p0y;
    dx2 = p3x - p2x;      dy2 = p3y - p2y;
    dx3 = p0x - p2x;      dy3 = p0y - p2y;

    collisionDetected = 0;

    d = dx1 * dy2 - dx2 * dy1;

    if(d !== 0){
        s = dx1 * dy3 - dx3 * dy1;
        if((s <= 0 && d < 0 && s >= d) || (s >= 0 && d > 0 && s <= d)){
            t = dx2 * dy3 - dx3 * dy2;
            if((t <= 0 && d < 0 && t > d) || (t >= 0 && d > 0 && t < d)){
                t = t / d;
                collisionDetected = 1;
                ix = p0x + t * dx1;
                iy = p0y + t * dy1;
            }
        }
    }
};

Non capisco come puoi capire cosa sta succedendo con linee come t = dx2 * dy3 - dx3 * dy2;...
Supuhstar

@Supuhstar Ha a che fare con la matematica vettoriale e la definizione di punto prodotto e prodotto incrociato. Ad esempio il codice che hai pubblicato rappresenta un'operazione tra prodotti. È un modo di proiettare un segmento di linea su un altro per determinare dove cade sull'altro segmento di linea, prima del punto iniziale da qualche parte nel mezzo o dopo la linea. Quindi t è un valore normalizzato. Se è compreso tra 0 e 1, i due segmenti si intersecano. Se è inferiore a 0 o maggiore di uno, non lo fanno.
Nolo,

@Supuhstar Si noti inoltre che affinché la proiezione trovi il punto effettivo, il risultato deve essere ridimensionato. Ecco dove t/dentra in gioco.
Nolo,

1
Voglio dire come capisci cosa sta succedendo a colpo d'occhio con nomi di variabili del genere? Perché non qualcosa del genere crossProduct = (line1XDifference * line2YDifference) - (line2XDifference * line1YDifference)e scaledResult = crossProduct / dotProduct?
Supuhstar,

1
@Supuhstar Ah, capisco cosa intendi. Beh, suppongo che non ci sia davvero una buona ragione per parlare oltre l'ossessione per l'efficienza, ma questa non è una ragione molto buona in sé perché i compilatori fanno un ottimo lavoro nel prendere la maggior parte del codice che gli dai e renderlo efficiente come possibile senza cambiare ciò che è dovrebbe calcolare. D'altra parte, i nomi p1x, p1yecc. Hanno lo scopo di descrivere i punti con i loro valori xey, quindi p1xun'abbreviazione per point1x, allo stesso modo d1x, nella mia mente è un'abbreviazione per la lettera greca deltaXo si potrebbe dire differenceInX. (altro)
Nolo

5

Penso che ci sia una soluzione molto più semplice per questo problema. Ho avuto un'altra idea oggi e sembra funzionare bene (almeno in 2D per ora). Tutto quello che devi fare è calcolare l'intersezione tra due linee, quindi verificare se il punto di intersezione calcolato si trova all'interno delle caselle di confine di entrambi i segmenti di linea. In tal caso, i segmenti di linea si intersecano. Questo è tutto.

MODIFICARE:

Ecco come calcolo l'intersezione (non so più dove ho trovato questo frammento di codice)

Point3D

viene da

System.Windows.Media.Media3D

public static Point3D? Intersection(Point3D start1, Point3D end1, Point3D start2, Point3D end2) {

        double a1 = end1.Y - start1.Y;
        double b1 = start1.X - end1.X;
        double c1 = a1 * start1.X + b1 * start1.Y;

        double a2 = end2.Y - start2.Y;
        double b2 = start2.X - end2.X;
        double c2 = a2 * start2.X + b2 * start2.Y;

        double det = a1 * b2 - a2 * b1;
        if (det == 0) { // lines are parallel
            return null;
        }

        double x = (b2 * c1 - b1 * c2) / det;
        double y = (a1 * c2 - a2 * c1) / det;

        return new Point3D(x, y, 0.0);
    }

e questa è la mia classe BoundingBox (semplificata ai fini della risposta):

public class BoundingBox {
    private Point3D min = new Point3D();
    private Point3D max = new Point3D();

    public BoundingBox(Point3D point) {
        min = point;
        max = point;
    }

    public Point3D Min {
        get { return min; }
        set { min = value; }
    }

    public Point3D Max {
        get { return max; }
        set { max = value; }
    }

    public bool Contains(BoundingBox box) {
        bool contains =
            min.X <= box.min.X && max.X >= box.max.X &&
            min.Y <= box.min.Y && max.Y >= box.max.Y &&
            min.Z <= box.min.Z && max.Z >= box.max.Z;
        return contains;
    }

    public bool Contains(Point3D point) {
        return Contains(new BoundingBox(point));
    }

}

3

Questa soluzione può aiutare

public static float GetLineYIntesept(PointF p, float slope)
    {
        return p.Y - slope * p.X;
    }

    public static PointF FindIntersection(PointF line1Start, PointF line1End, PointF line2Start, PointF line2End)
    {

        float slope1 = (line1End.Y - line1Start.Y) / (line1End.X - line1Start.X);
        float slope2 = (line2End.Y - line2Start.Y) / (line2End.X - line2Start.X);

        float yinter1 = GetLineYIntesept(line1Start, slope1);
        float yinter2 = GetLineYIntesept(line2Start, slope2);

        if (slope1 == slope2 && yinter1 != yinter2)
            return PointF.Empty;

        float x = (yinter2 - yinter1) / (slope1 - slope2);

        float y = slope1 * x + yinter1;

        return new PointF(x, y);
    }

3

Ho portato la risposta di Kris sopra a JavaScript. Dopo aver provato numerose risposte diverse, ha fornito i punti corretti. Pensavo di impazzire che non stavo ottenendo i punti di cui avevo bisogno.

function getLineLineCollision(p0, p1, p2, p3) {
    var s1, s2;
    s1 = {x: p1.x - p0.x, y: p1.y - p0.y};
    s2 = {x: p3.x - p2.x, y: p3.y - p2.y};

    var s10_x = p1.x - p0.x;
    var s10_y = p1.y - p0.y;
    var s32_x = p3.x - p2.x;
    var s32_y = p3.y - p2.y;

    var denom = s10_x * s32_y - s32_x * s10_y;

    if(denom == 0) {
        return false;
    }

    var denom_positive = denom > 0;

    var s02_x = p0.x - p2.x;
    var s02_y = p0.y - p2.y;

    var s_numer = s10_x * s02_y - s10_y * s02_x;

    if((s_numer < 0) == denom_positive) {
        return false;
    }

    var t_numer = s32_x * s02_y - s32_y * s02_x;

    if((t_numer < 0) == denom_positive) {
        return false;
    }

    if((s_numer > denom) == denom_positive || (t_numer > denom) == denom_positive) {
        return false;
    }

    var t = t_numer / denom;

    var p = {x: p0.x + (t * s10_x), y: p0.y + (t * s10_y)};
    return p;
}

2

Ho provato molti modi e poi ho deciso di scrivere il mio. Quindi eccolo qui:

bool IsBetween (float x, float b1, float b2)
{
   return ( ((x >= (b1 - 0.1f)) && 
        (x <= (b2 + 0.1f))) || 
        ((x >= (b2 - 0.1f)) &&
        (x <= (b1 + 0.1f))));
}

bool IsSegmentsColliding(   POINTFLOAT lineA,
                POINTFLOAT lineB,
                POINTFLOAT line2A,
                POINTFLOAT line2B)
{
    float deltaX1 = lineB.x - lineA.x;
    float deltaX2 = line2B.x - line2A.x;
    float deltaY1 = lineB.y - lineA.y;
    float deltaY2 = line2B.y - line2A.y;

    if (abs(deltaX1) < 0.01f && 
        abs(deltaX2) < 0.01f) // Both are vertical lines
        return false;
    if (abs((deltaY1 / deltaX1) -
        (deltaY2 / deltaX2)) < 0.001f) // Two parallel line
        return false;

    float xCol = (  (   (deltaX1 * deltaX2) * 
                        (line2A.y - lineA.y)) - 
                    (line2A.x * deltaY2 * deltaX1) + 
                    (lineA.x * deltaY1 * deltaX2)) / 
                 ((deltaY1 * deltaX2) - (deltaY2 * deltaX1));
    float yCol = 0;
    if (deltaX1 < 0.01f) // L1 is a vertical line
        yCol = ((xCol * deltaY2) + 
                (line2A.y * deltaX2) - 
                (line2A.x * deltaY2)) / deltaX2;
    else // L1 is acceptable
        yCol = ((xCol * deltaY1) +
                (lineA.y * deltaX1) -
                (lineA.x * deltaY1)) / deltaX1;

    bool isCol =    IsBetween(xCol, lineA.x, lineB.x) &&
            IsBetween(yCol, lineA.y, lineB.y) &&
            IsBetween(xCol, line2A.x, line2B.x) &&
            IsBetween(yCol, line2A.y, line2B.y);
    return isCol;
}

Basato su queste due formule: (le ho semplificate dall'equazione di linee e altre formule)

formula per x

formula per y


Funziona ma prova a inserire questa coordinata (se è colineare / sovrapposta restituirà un risultato falso): PointA1 = (0,0) PointA2 = (0,2) e PointB1 = (0,1) PointB2 = (0,5)
dns,

@dns Bene, questo perché il codice restituisce false per le linee parallele. Vedo il problema, tuttavia, non so ancora quale funzione dovrebbe restituire in quanto vi è un numero infinito di risposte.
Soroush Falahati,

2

Questo si basa sulla risposta di Gareth Ree. Restituisce anche la sovrapposizione dei segmenti di linea se lo fanno. Codificato in C ++, V è una semplice classe vettoriale. Dove il prodotto incrociato di due vettori in 2D restituisce un singolo scalare. È stato testato e superato dal mio sistema di test automatico delle mie scuole.

//Required input point must be colinear with the line
bool on_segment(const V& p, const LineSegment& l)
{
    //If a point is on the line, the sum of the vectors formed by the point to the line endpoints must be equal
    V va = p - l.pa;
    V vb = p - l.pb;
    R ma = va.magnitude();
    R mb = vb.magnitude();
    R ml = (l.pb - l.pa).magnitude();
    R s = ma + mb;
    bool r = s <= ml + epsilon;
    return r;
}

//Compute using vector math
// Returns 0 points if the lines do not intersect or overlap
// Returns 1 point if the lines intersect
//  Returns 2 points if the lines overlap, contain the points where overlapping start starts and stop
std::vector<V> intersect(const LineSegment& la, const LineSegment& lb)
{
    std::vector<V> r;

    //http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
    V oa, ob, da, db; //Origin and direction vectors
    R sa, sb; //Scalar values
    oa = la.pa;
    da = la.pb - la.pa;
    ob = lb.pa;
    db = lb.pb - lb.pa;

    if (da.cross(db) == 0 && (ob - oa).cross(da) == 0) //If colinear
    {
        if (on_segment(lb.pa, la) && on_segment(lb.pb, la))
        {
            r.push_back(lb.pa);
            r.push_back(lb.pb);
            dprintf("colinear, overlapping\n");
            return r;
        }

        if (on_segment(la.pa, lb) && on_segment(la.pb, lb))
        {
            r.push_back(la.pa);
            r.push_back(la.pb);
            dprintf("colinear, overlapping\n");
            return r;
        }

        if (on_segment(la.pa, lb))
            r.push_back(la.pa);

        if (on_segment(la.pb, lb))
            r.push_back(la.pb);

        if (on_segment(lb.pa, la))
            r.push_back(lb.pa);

        if (on_segment(lb.pb, la))
            r.push_back(lb.pb);

        if (r.size() == 0)
            dprintf("colinear, non-overlapping\n");
        else
            dprintf("colinear, overlapping\n");

        return r;
    }

    if (da.cross(db) == 0 && (ob - oa).cross(da) != 0)
    {
        dprintf("parallel non-intersecting\n");
        return r;
    }

    //Math trick db cross db == 0, which is a single scalar in 2D.
    //Crossing both sides with vector db gives:
    sa = (ob - oa).cross(db) / da.cross(db);

    //Crossing both sides with vector da gives
    sb = (oa - ob).cross(da) / db.cross(da);

    if (0 <= sa && sa <= 1 && 0 <= sb && sb <= 1)
    {
        dprintf("intersecting\n");
        r.push_back(oa + da * sa);
        return r;
    }

    dprintf("non-intersecting, non-parallel, non-colinear, non-overlapping\n");
    return r;
}

2

Ecco un'implementazione di base di un segmento di linea in C #, con il corrispondente codice di rilevamento delle intersezioni. Richiede una struttura vettoriale / punto 2D chiamata Vector2f, sebbene sia possibile sostituirla con qualsiasi altro tipo con proprietà X / Y. Puoi anche sostituirlo floatcon doublese si adatta meglio alle tue esigenze.

Questo codice viene utilizzato nella mia libreria di fisica .NET, Boing .

public struct LineSegment2f
{
    public Vector2f From { get; }
    public Vector2f To { get; }

    public LineSegment2f(Vector2f @from, Vector2f to)
    {
        From = @from;
        To = to;
    }

    public Vector2f Delta => new Vector2f(To.X - From.X, To.Y - From.Y);

    /// <summary>
    /// Attempt to intersect two line segments.
    /// </summary>
    /// <remarks>
    /// Even if the line segments do not intersect, <paramref name="t"/> and <paramref name="u"/> will be set.
    /// If the lines are parallel, <paramref name="t"/> and <paramref name="u"/> are set to <see cref="float.NaN"/>.
    /// </remarks>
    /// <param name="other">The line to attempt intersection of this line with.</param>
    /// <param name="intersectionPoint">The point of intersection if within the line segments, or empty..</param>
    /// <param name="t">The distance along this line at which intersection would occur, or NaN if lines are collinear/parallel.</param>
    /// <param name="u">The distance along the other line at which intersection would occur, or NaN if lines are collinear/parallel.</param>
    /// <returns><c>true</c> if the line segments intersect, otherwise <c>false</c>.</returns>
    public bool TryIntersect(LineSegment2f other, out Vector2f intersectionPoint, out float t, out float u)
    {
        var p = From;
        var q = other.From;
        var r = Delta;
        var s = other.Delta;

        // t = (q − p) × s / (r × s)
        // u = (q − p) × r / (r × s)

        var denom = Fake2DCross(r, s);

        if (denom == 0)
        {
            // lines are collinear or parallel
            t = float.NaN;
            u = float.NaN;
            intersectionPoint = default(Vector2f);
            return false;
        }

        var tNumer = Fake2DCross(q - p, s);
        var uNumer = Fake2DCross(q - p, r);

        t = tNumer / denom;
        u = uNumer / denom;

        if (t < 0 || t > 1 || u < 0 || u > 1)
        {
            // line segments do not intersect within their ranges
            intersectionPoint = default(Vector2f);
            return false;
        }

        intersectionPoint = p + r * t;
        return true;
    }

    private static float Fake2DCross(Vector2f a, Vector2f b)
    {
        return a.X * b.Y - a.Y * b.X;
    }
}

1

Un programma C ++ per verificare se due segmenti di linea dati si intersecano

#include <iostream>
using namespace std;

struct Point
{
    int x;
    int y;
};

// Given three colinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
bool onSegment(Point p, Point q, Point r)
{
    if (q.x <= max(p.x, r.x) && q.x >= min(p.x, r.x) &&
        q.y <= max(p.y, r.y) && q.y >= min(p.y, r.y))
       return true;

    return false;
}

// To find orientation of ordered triplet (p, q, r).
// The function returns following values
// 0 --> p, q and r are colinear
// 1 --> Clockwise
// 2 --> Counterclockwise
int orientation(Point p, Point q, Point r)
{
    // See 10th slides from following link for derivation of the formula
    // http://www.dcs.gla.ac.uk/~pat/52233/slides/Geometry1x1.pdf
    int val = (q.y - p.y) * (r.x - q.x) -
              (q.x - p.x) * (r.y - q.y);

    if (val == 0) return 0;  // colinear

    return (val > 0)? 1: 2; // clock or counterclock wise
}

// The main function that returns true if line segment 'p1q1'
// and 'p2q2' intersect.
bool doIntersect(Point p1, Point q1, Point p2, Point q2)
{
    // Find the four orientations needed for general and
    // special cases
    int o1 = orientation(p1, q1, p2);
    int o2 = orientation(p1, q1, q2);
    int o3 = orientation(p2, q2, p1);
    int o4 = orientation(p2, q2, q1);

    // General case
    if (o1 != o2 && o3 != o4)
        return true;

    // Special Cases
    // p1, q1 and p2 are colinear and p2 lies on segment p1q1
    if (o1 == 0 && onSegment(p1, p2, q1)) return true;

    // p1, q1 and p2 are colinear and q2 lies on segment p1q1
    if (o2 == 0 && onSegment(p1, q2, q1)) return true;

    // p2, q2 and p1 are colinear and p1 lies on segment p2q2
    if (o3 == 0 && onSegment(p2, p1, q2)) return true;

     // p2, q2 and q1 are colinear and q1 lies on segment p2q2
    if (o4 == 0 && onSegment(p2, q1, q2)) return true;

    return false; // Doesn't fall in any of the above cases
}

// Driver program to test above functions
int main()
{
    struct Point p1 = {1, 1}, q1 = {10, 1};
    struct Point p2 = {1, 2}, q2 = {10, 2};

    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    p1 = {10, 0}, q1 = {0, 10};
    p2 = {0, 0}, q2 = {10, 10};
    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    p1 = {-5, -5}, q1 = {0, 0};
    p2 = {1, 1}, q2 = {10, 10};
    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    return 0;
}

1

Basato sulla risposta di @Gareth Rees, versione per Python:

import numpy as np

def np_perp( a ) :
    b = np.empty_like(a)
    b[0] = a[1]
    b[1] = -a[0]
    return b

def np_cross_product(a, b):
    return np.dot(a, np_perp(b))

def np_seg_intersect(a, b, considerCollinearOverlapAsIntersect = False):
    # /programming/563198/how-do-you-detect-where-two-line-segments-intersect/565282#565282
    # http://www.codeproject.com/Tips/862988/Find-the-intersection-point-of-two-line-segments
    r = a[1] - a[0]
    s = b[1] - b[0]
    v = b[0] - a[0]
    num = np_cross_product(v, r)
    denom = np_cross_product(r, s)
    # If r x s = 0 and (q - p) x r = 0, then the two lines are collinear.
    if np.isclose(denom, 0) and np.isclose(num, 0):
        # 1. If either  0 <= (q - p) * r <= r * r or 0 <= (p - q) * s <= * s
        # then the two lines are overlapping,
        if(considerCollinearOverlapAsIntersect):
            vDotR = np.dot(v, r)
            aDotS = np.dot(-v, s)
            if (0 <= vDotR  and vDotR <= np.dot(r,r)) or (0 <= aDotS  and aDotS <= np.dot(s,s)):
                return True
        # 2. If neither 0 <= (q - p) * r = r * r nor 0 <= (p - q) * s <= s * s
        # then the two lines are collinear but disjoint.
        # No need to implement this expression, as it follows from the expression above.
        return None
    if np.isclose(denom, 0) and not np.isclose(num, 0):
        # Parallel and non intersecting
        return None
    u = num / denom
    t = np_cross_product(v, s) / denom
    if u >= 0 and u <= 1 and t >= 0 and t <= 1:
        res = b[0] + (s*u)
        return res
    # Otherwise, the two line segments are not parallel but do not intersect.
    return None

0

Se ogni lato del rettangolo è un segmento di linea e la parte disegnata dall'utente è un segmento di linea, è necessario semplicemente controllare il segmento disegnato dall'utente per l'intersezione con i quattro segmenti di linea laterale. Questo dovrebbe essere un esercizio abbastanza semplice dati i punti di inizio e fine di ciascun segmento.


3
Si noti che questa era una risposta ragionevole alla domanda come originariamente inquadrata, ma ora che la domanda è stata modificata pesantemente non ha molto senso.
GS - Scusati con Monica il

0

Basato sulla risposta di t3chb0t:

int intersezione_linee(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int& p_x, int& p_y)
{
   //L1: estremi (x1,y1)(x2,y2) L2: estremi (x3,y3)(x3,y3)
   int d;
   d = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4);
   if(!d)
       return 0;
   p_x = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4))/d;
   p_y = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4))/d;
   return 1;
}

int in_bounding_box(int x1, int y1, int x2, int y2, int p_x, int p_y)
{
    return p_x>=x1 && p_x<=x2 && p_y>=y1 && p_y<=y2;

}

int intersezione_segmenti(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int& p_x, int& p_y)
{
    if (!intersezione_linee(x1,y1,x2,y2,x3,y3,x4,y4,p_x,p_y))
        return 0;

    return in_bounding_box(x1,y1,x2,y2,p_x,p_y) && in_bounding_box(x3,y3,x4,y4,p_x,p_y);
}

0

Ho letto questi algoritmi dal libro "Geometria a viste multiple"

testo seguente utilizzando

"come segno di trasposizione

* come prodotto punto

x come prodotto incrociato, quando si utilizza come operatore

1. definizione della linea

un punto x_vec = (x, y) 'si trova sull'asse della linea + di + c = 0

denotiamo L = (a, b, c) ', il punto come (x, y, 1)' come coordinate omogenee

l'equazione di riga può essere scritta come

(x, y, 1) (a, b, c) '= 0 o x' * L = 0

2. intersezione di linee

abbiamo due righe L1 = (a1, b1, c1) ', L2 = (a2, b2, c2)'

supponiamo che x sia un punto, un vettore e x = L1 x L2 (L1 prodotto incrociato L2).

fai attenzione, x è sempre un punto 2D, leggi le coordinate omogenee se sei confuso (L1xL2) è un vettore di tre elementi e x è una coordinate 2D.

secondo il triplo prodotto, lo sappiamo

L1 * (L1 x L2) = 0 e L2 * (L1 x L2) = 0, a causa del co-piano L1, L2

sostituiamo (L1xL2) con il vettore x, quindi abbiamo L1 * x = 0, L2 * x = 0, il che significa che x si trova su L1 e L2, x è il punto di intersezione.

fai attenzione, qui x sono coordinate omogenee, se l'ultimo elemento di x è zero, significa che L1 e L2 sono paralleli.


0

Molte risposte hanno racchiuso tutti i calcoli in un'unica funzione. Se devi calcolare le pendenze di linea, le intercettazioni y o le intercettazioni x da utilizzare altrove nel codice, eseguirai questi calcoli in modo ridondante. Ho separato le rispettive funzioni, usato nomi di variabili ovvi e commentato il mio codice per facilitarne il seguito. Avevo bisogno di sapere se le linee si intersecano all'infinito oltre i loro punti finali, quindi in JavaScript:

http://jsfiddle.net/skibulk/evmqq00u/

var point_a = {x:0, y:10},
    point_b = {x:12, y:12},
    point_c = {x:10, y:0},
    point_d = {x:0, y:0},
    slope_ab = slope(point_a, point_b),
    slope_bc = slope(point_b, point_c),
    slope_cd = slope(point_c, point_d),
    slope_da = slope(point_d, point_a),
    yint_ab = y_intercept(point_a, slope_ab),
    yint_bc = y_intercept(point_b, slope_bc),
    yint_cd = y_intercept(point_c, slope_cd),
    yint_da = y_intercept(point_d, slope_da),
    xint_ab = x_intercept(point_a, slope_ab, yint_ab),
    xint_bc = x_intercept(point_b, slope_bc, yint_bc),
    xint_cd = x_intercept(point_c, slope_cd, yint_cd),
    xint_da = x_intercept(point_d, slope_da, yint_da),
    point_aa = intersect(slope_da, yint_da, xint_da, slope_ab, yint_ab, xint_ab),
    point_bb = intersect(slope_ab, yint_ab, xint_ab, slope_bc, yint_bc, xint_bc),
    point_cc = intersect(slope_bc, yint_bc, xint_bc, slope_cd, yint_cd, xint_cd),
    point_dd = intersect(slope_cd, yint_cd, xint_cd, slope_da, yint_da, xint_da);

console.log(point_a, point_b, point_c, point_d);
console.log(slope_ab, slope_bc, slope_cd, slope_da);
console.log(yint_ab, yint_bc, yint_cd, yint_da);
console.log(xint_ab, xint_bc, xint_cd, xint_da);
console.log(point_aa, point_bb, point_cc, point_dd);

function slope(point_a, point_b) {
  var i = (point_b.y - point_a.y) / (point_b.x - point_a.x);
  if (i === -Infinity) return Infinity;
  if (i === -0) return 0;
  return i;
}

function y_intercept(point, slope) {
    // Horizontal Line
    if (slope == 0) return point.y;
  // Vertical Line
    if (slope == Infinity)
  {
    // THE Y-Axis
    if (point.x == 0) return Infinity;
    // No Intercept
    return null;
  }
  // Angled Line
  return point.y - (slope * point.x);
}

function x_intercept(point, slope, yint) {
    // Vertical Line
    if (slope == Infinity) return point.x;
  // Horizontal Line
    if (slope == 0)
  {
    // THE X-Axis
    if (point.y == 0) return Infinity;
    // No Intercept
    return null;
  }
  // Angled Line
  return -yint / slope;
}

// Intersection of two infinite lines
function intersect(slope_a, yint_a, xint_a, slope_b, yint_b, xint_b) {
  if (slope_a == slope_b)
  {
    // Equal Lines
    if (yint_a == yint_b && xint_a == xint_b) return Infinity;
    // Parallel Lines
    return null;
  }
  // First Line Vertical
    if (slope_a == Infinity)
  {
    return {
        x: xint_a,
      y: (slope_b * xint_a) + yint_b
    };
  }
  // Second Line Vertical
    if (slope_b == Infinity)
  {
    return {
        x: xint_b,
      y: (slope_a * xint_b) + yint_a
    };
  }
  // Not Equal, Not Parallel, Not Vertical
  var i = (yint_b - yint_a) / (slope_a - slope_b);
  return {
    x: i,
    y: (slope_a * i) + yint_a
  };
}
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.