Come rilevare collisioni linea su linea 2D?


13

Sono uno sviluppatore di giochi d'azione flash che è un po 'arretrato con la matematica, anche se trovo che la fisica sia interessante e interessante.

Per riferimento questo è un gioco simile a quello che sto facendo: gioco flash districato

Ho reso questo gioco districato quasi al pieno completamento della logica. Ma quando due linee si intersecano, ho bisogno di quelle linee intersecate o "aggrovigliate" per mostrare un colore diverso; rosso.

Sarebbe davvero gentile da parte vostra gente se poteste suggerire un algoritmo per rilevare le collisioni del segmento di linea . Sono fondamentalmente una persona a cui piace pensare "visivamente" piuttosto che "aritmeticamente" :)

Modifica: vorrei aggiungere alcuni diagrammi per rendere più chiara l'idea

nessun incrocio nessun incrocio intersezione nessun incrocio

PS Sto provando a fare una funzione come

private function isIntersecting(A:Point, B:Point, C:Point, D:Point):Boolean

Grazie in anticipo.


6
Questa è una spiegazione deludentemente non visiva del problema, ma è un algoritmo e ha senso se puoi portarti a leggere i loro calcoli: local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d Potrebbe essere pesante se la tua matematica vettoriale è debole. Capisco - Preferisco anche spiegazioni visive. Proverò a trovare il tempo dopo per scarabocchiare questo, ma se qualcuno è artisticamente incline vede questo link e ha tempo prima di me, raggiungilo!
Anko,

Risposte:


18

Uso il seguente metodo che è praticamente solo un'implementazione di questo algoritmo . È in C # ma tradurlo in ActionScript dovrebbe essere banale.

bool IsIntersecting(Point a, Point b, Point c, Point d)
{
    float denominator = ((b.X - a.X) * (d.Y - c.Y)) - ((b.Y - a.Y) * (d.X - c.X));
    float numerator1 = ((a.Y - c.Y) * (d.X - c.X)) - ((a.X - c.X) * (d.Y - c.Y));
    float numerator2 = ((a.Y - c.Y) * (b.X - a.X)) - ((a.X - c.X) * (b.Y - a.Y));

    // Detect coincident lines (has a problem, read below)
    if (denominator == 0) return numerator1 == 0 && numerator2 == 0;

    float r = numerator1 / denominator;
    float s = numerator2 / denominator;

    return (r >= 0 && r <= 1) && (s >= 0 && s <= 1);
}

C'è comunque un sottile problema con l'algoritmo, che è il caso in cui due linee sono coincidenti ma non si sovrappongono. L'algoritmo restituisce comunque un'intersezione in quel caso. Se ti interessa quel caso, credo che questa risposta su StackOverflow abbia una versione più complessa che la risolve.

modificare

Non ho ottenuto risultati da questo algoritmo, scusa!

È strano, l'ho provato e funziona per me, tranne per quel singolo caso che ho descritto sopra. Utilizzando la stessa identica versione che ho pubblicato sopra ho ottenuto questi risultati quando l'ho preso per un test drive:

inserisci qui la descrizione dell'immagine


Non ho ottenuto risultati da questo algoritmo, scusa!
Vishnu,

4
@Vish Che problema hai avuto? Ho testato questa copia esatta dell'algoritmo prima della pubblicazione e ha funzionato perfettamente tranne il caso singolo descritto.
David Gouveia,

Quindi, lasciami riprovare, avrei potuto confondere un po 'di matematica in esso. Ti farò sapere presto. Grazie mille :)
Vishnu,

1
Ho ottenuto il risultato desiderato dal tuo algoritmo, grazie @DavidGouveia.
Vishnu,

1
Bene, ma ora ho un altro problema :)! Devo creare le linee intersecate con colore rosso e verde. L'intersezione funziona bene. Ma come ho capito ora, (non matematicamente però) che un semplice if-else non funzionerà come per mettere linee rosse e verdi per linee intersecate e non intersecate. Il nodo che sto trascinando ha sia una linea sinistra che una destra. Quindi, qualcosa è andato storto da qualche parte mentre cambiava di nuovo il colore delle linee non intersecate in verde. Immagino di aver bisogno anche di un'altra condizione. Hmmm, comunque grazie mille, lo segnerò come la risposta corretta.
Vishnu,

4

Senza divisioni! Quindi nessun problema con precisione né divisione per zero.

Il segmento di linea 1 va da A a B Il segmento di linea 2 va da C a D

Una linea è una linea infinita, il segmento di linea è una parte definita di quella linea.

Controlla se le due caselle di delimitazione si intersecano: se nessuna intersezione -> Nessuna croce! (calcolo eseguito, ritorno falso)

Controlla se la linea seg 1 è a cavallo della linea seg 2 e se la linea seg 2 è a cavallo della linea seg 1 (cioè la linea Segmento 1 si trova su entrambi i lati della Linea definita dalla linea Segmento 2).

Questo può essere fatto traducendo tutti i punti con -A (cioè muovendo le 2 linee in modo che A sia in origo (0,0))

Quindi si controlla se i punti C e D si trovano su lati diversi della linea definita da 0,0 a B

//Cross Product (hope I got it right here)
float fC= (B.x*C.y) - (B.y*C.x); //<0 == to the left, >0 == to the right
float fD= (B.x*D.y) - (B.y*D.x);

if( (fc<0) && (fd<0)) //both to the left  -> No Cross!
if( (fc>0) && (fd>0)) //both to the right -> No Cross!

Se non hai già ottenuto "Nessuna croce", continua a utilizzare non A, B contro C, D ma C, D contro A, B (stessi calcoli, basta scambiare A e C, B e D), se non ci sono "Nessuna croce!" allora hai un incrocio!

Ho cercato i calcoli esatti per il prodotto incrociato e ho trovato questo post sul blog che spiega anche il metodo.


1
Mi dispiace ma non sono abbastanza bravo con la matematica vettoriale, ho implementato questo algoritmo in quanto tale, ma non ho ottenuto risultati, scusa!
Vishnu,

1
Dovrebbe funzionare, quindi forse, se puoi mostrarci il tuo codice, possiamo aiutarti?
Valmond,

Bello! tuttavia il collegamento è interrotto
clabe45

C'è qualcosa che puoi aggiungere a questo per ottenere il punto di intersezione?
SeanRamey,

1

Voglio solo dirlo, ne avevo bisogno per il mio gioco Gamemaker Studio e funziona bene:

///scr_line_collision(x1,y1,x2,y2,x3,y3,x4,y4)

var denominator= ((argument2 - argument0) * (argument7 - argument5)) - ((argument3 - argument1) * (argument6 - argument4));
var numerator1 = ((argument1 - argument5) * (argument6 - argument4)) - ((argument0 - argument4) * (argument7 - argument5));
var numerator2 = ((argument1 - argument5) * (argument2 - argument0)) - ((argument0 - argument4) * (argument3 - argument1));

// Detect coincident lines
if (denominator == 0) {return (numerator1 == 0 && numerator2 == 0)}

var r = numerator1 / denominator;
var s = numerator2 / denominator;

return ((r >= 0 && r <= 1) && (s >= 0 && s <= 1));

Penso che questa risposta potrebbe davvero migliorare se spiegassi cosa fa il codice.
TomTsagk,

1

La risposta accettata ha dato una risposta errata in questo caso:

x1 = 0;
y1 = 0;
x2 = 10;
y2 = 10;

x3 = 10.1;
y3 = 10.1;
x4 = 15;
y4 = 15;

Queste linee ovviamente non si intersecano, ma secondo la funzione nella "risposta corretta" le linee si intersecano.

Questo è quello che uso:

function do_lines_intersect(px1,py1,px2,py2,px3,py3,px4,py4) {
  var ua = 0.0;
  var ub = 0.0;
  var ud = (py4 - py3) * (px2 - px1) - (px4 - px3) * (py2 - py1);


  if (ud != 0) {
    ua = ((px4 - px3) * (py1 - py3) - (py4 - py3) * (px1 - px3)) / ud;
    ub = ((px2 - px1) * (py1 - py3) - (py2 - py1) * (px1 - px3)) / ud;
        if (ua < 0.0 || ua > 1.0 || ub < 0.0 || ub > 1.0) ua = 0.0;
  }

  return ua;
}

restituisce 0 = le linee non si intersecano

restituisce> 0 = le linee si intersecano


Aggiorna per rispondere alla domanda:

Non ho creato questo codice da solo. Ha più di 5 anni e non so quale sia la fonte originale. Ma..

Penso che il valore di ritorno sia la posizione relativa della prima riga in cui si incrociano (per spiegarlo male). Per calcolare il punto di intersezione potresti probabilmente usare lerp in questo modo:

l = do_lines_intersect(...)
if (l > 0) {
    intersect_pos_x = l * (px2-px1);
    intersect_pos_y = l * (py2-py1);
} else {
    // lines do not cross
}

(NON HO TESTATO QUESTO)


Esiste una versione di questo che restituisce il punto di intersezione?
SeanRamey,
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.