Rilevazione di collisioni esagonali per oggetti in rapido movimento?


39

Un oggetto ha una posizione e un vettore di velocità. Di solito viene utilizzata solo la posizione per verificare se due oggetti si scontrano, ciò è problematico per oggetti in movimento molto veloce in quanto può accadere che l'oggetto si sposti così velocemente da trovarsi davanti al primo oggetto nel primo controllo di collisione e dietro di esso in il secondo controllo di collisione.

BoundingBox Collision Fail

Ora ci sono anche controlli di collisione basati su linee, in cui si controlla solo se il vettore di movimento di ciascun oggetto si interseca con il rettangolo di selezione dell'altro. Questo può essere visto come un'espansione di un punto. Funziona solo se l'oggetto in rapido movimento è davvero piccolo.

Hexagon Collision Win

Quindi la mia idea è, invece di espandere un punto, perché non espandere un rettangolo? Ciò si traduce in un esagono.

Adesso tutto bene. Ma come posso effettivamente verificare se due esagoni di questo tipo si intersecano? Nota che questi sono esagoni molto specifici.

Specifiche esagonali

Domanda bonus : è possibile calcolare esattamente dove (o meglio dopo quanto tempo) è avvenuta la collisione? Questo potrebbe essere molto utile per rilevare ciò che è realmente accaduto, come dove e con quanta potenza e per simulare il modo in cui si sono mossi nel tempo tra la collisione e la fine del frame.


per (linee in A) per (linee in B) se (linee incrociate) collisione - tranne per il fatto che non copre A in B o B in casi A. Hm. =)
Jari Komppa,

4
Sei impegnato in scatole? Le scatole che hai disegnato possono essere rappresentate da cerchi con una perdita di precisione minima ma un algoritmo di collisione relativamente semplice. Cerca il rilevamento delle collisioni del cerchio spazzato. Se il rapporto lunghezza / larghezza si allontana da 1, meno attraente sarebbe.
Steve H,

@SteveH Sto cercando la soluzione più flessibile, quindi il rapporto lunghezza / larghezza è un po 'un grosso problema.
API-Beast

1
Devi rendertene conto solo perché gli esagoni si intersecano non significa che si verifichi la collisione. Anche se potessi dire senza errori se si intersecano, avresti comunque del lavoro da fare per determinare se c'è una collisione e, ovviamente, dove e quando accade. Quindi non puoi ancora passare alla tua domanda bonus.
jrsala,

2
Non l'ho mai provato prima, ma sembra che invece di esagoni nello spazio 2d, si possa pensare al movimento in 2d come a volumi nello spazio 3d in cui un asse è il tempo. Quindi si intersecano due poliedri 3d con coordinate (x, y, t). Se i due oggetti solidi si intersecano, si desidera trovare il valore t minimo. Puoi semplificare un po 'convertendo tutte le coordinate di B in modo che siano nel frame di riferimento di A. Non l'ho implementato ma è da lì che avrei iniziato.
entro

Risposte:


34

La soluzione è in realtà più semplice del previsto. Il trucco è usare la sottrazione di Minkowski prima della tua tecnica esagonale.

Ecco i tuoi rettangoli A e B, con le loro velocità vAe vB. Nota che vAe vBnon sono effettivamente velocità, sono la distanza percorsa durante un fotogramma.

passo 1

Ora sostituisci il rettangolo B con un punto P e il rettangolo A con il rettangolo C = A + (- B), che ha dimensioni la somma delle dimensioni di A e B. Le proprietà di aggiunta di Minkowski affermano che si verificano collisioni tra il punto e il nuovo rettangolo se e solo se si verificano collisioni tra i due rettangoli originali:

passo 2

Ma se il rettangolo C si sposta lungo il vettore vAe il punto P si sposta lungo il vettore vB, un semplice cambiamento del riquadro di riferimento ci dice che è lo stesso che se il rettangolo C fosse fermo e il punto P spostato lungo il vettore vB-vA:

passaggio 3

È quindi possibile utilizzare una semplice formula di intersezione a segmenti di riquadro per indicare dove si verifica la collisione nel nuovo frame di riferimento.

L'ultimo passo è tornare al frame di riferimento corretto. Basta dividere la distanza percorsa dal punto fino all'intersezione cerchiata per la lunghezza del vettore vB-vAe otterrai un valore stale 0 < s < 1. La collisione avviene nel momento in s * Tcui Tè la durata della cornice.

Commento di madshogo :
Un enorme vantaggio di questa tecnica rispetto a quello della risposta di Mr Beast è che se non c'è rotazione, la "sottrazione di Minkowski" A + (- B) può essere calcolata una volta per tutte le fasi successive !

Quindi l'unico algoritmo che richiede tempo in tutto questo (somma di Minkowski, complessità O (mn) in cui m è il numero di vertici in A e n il numero di vertici in B ) può essere usato solo una volta, rendendo efficacemente il rilevamento delle collisioni- problema di tempo!

Più tardi, puoi buttare via la somma una volta che sai per certo che A e B sono in diverse parti della scena (del tuo quadrifoglio?) E non si scontreranno più.

Al contrario, il metodo di Mr Beast richiede parecchi calcoli in ogni fase.

Inoltre, per i rettangoli allineati agli assi, A + (- B) può essere calcolato molto più semplicemente che calcolando effettivamente tutte le somme, vertice per vertice. Basta espandere A aggiungendo l'altezza di B alla sua altezza e la larghezza di B alla sua larghezza (metà per lato).

Ma tutto ciò funziona solo se né AB ruotano e se entrambi sono convessi. In caso di rotazione o se si utilizzano forme concave, è necessario utilizzare volumi / aree spazzati.
fine del commento


4
Sembra un approccio piuttosto interessante, tuttavia, non lo afferro ancora al 100%, cosa succede quando l'oggetto è veramente piccolo e si muove tra le due linee? i.imgur.com/hRolvAF.png
API-Beast

-1: questo metodo non consente in alcun modo di accertarsi che si verifichino collisioni. Ti consente solo di essere sicuro che non accada, nel caso in cui il segmento e il volume estruso non si intersecano. Ma è del tutto possibile che si intersecino e che non vi siano collisioni. Ciò che è sbagliato è la parte "Ora puoi usare [...] l'intersezione segmento-segmento semplice per decidere dove si è verificata la collisione".
jrsala,

2
@madshogo Hai ragione. Supponevo che il timestep fosse abbastanza piccolo rispetto alle dimensioni degli oggetti che questo non sarebbe stato un problema, ma non è certamente molto robusto nel caso generale. Vedrò di risolverlo.
sam hocevar,

@SamHocevar Se potessi rivedere la risposta sarebbe grandiosa.
API-Beast

1
@LuisAlves sì e no ... tutta la logica funziona, ma dovrai sostituire vB-vAcon g(t)-f(t)dove fe gle posizioni di A e B nel tempo. Poiché questa non è più una linea retta, dovrai risolvere un problema di intersezione della curva parametrica.
Sam Hocevar,

17

Innanzitutto, nel caso dei rettangoli allineati agli assi, la risposta di Kevin Reid è la migliore e l'algoritmo è il più veloce.

In secondo luogo, per forme semplici, utilizzare le velocità relative (come mostrato di seguito) e il teorema degli assi di separazione per il rilevamento delle collisioni. Essa vi dirà se una collisione accade nel caso di movimento lineare (senza rotazione). E se c'è rotazione, è necessario un piccolo timestep per essere preciso. Ora, per rispondere alla domanda:


Come dire nel caso generale se due forme convesse si intersecano?

Ti darò un algoritmo che funziona per tutte le forme convesse e non solo per gli esagoni.

Supponiamo che X e Y siano due forme convesse. Si intersecano se e solo se hanno un punto in comune, cioè c'è un punto x X e un punto y ∈ Y tale che x = y . Se consideri lo spazio come uno spazio vettoriale, ciò equivale a dire x - y = 0 . E ora arriviamo a questo business di Minkowski:

La somma Minkowski di X e Y è l'insieme di tutte le x + y per x ∈ X e y ∈ Y .


Un esempio per X e Y


X, Y e la loro somma di Minkowski, X + Y

Supponendo che (-Y) sia l'insieme di tutti -y per y ∈ Y , quindi dato il paragrafo precedente, X e Y si intersecano se e solo se X + (-Y) contiene 0 , ovvero l'origine .

Nota laterale: perché scrivo X + (-Y) invece di X - Y ? Bene, perché in matematica esiste un'operazione chiamata la differenza di Minkowski di A e B che a volte è scritta X - Y ma non ha nulla a che fare con l'insieme di tutti x - y per x ∈ X e y ∈ Y (il vero Minkowski la differenza è un po 'più complessa).

Quindi vorremmo calcolare la somma di Minkowski di X e -Y e scoprire se contiene l'origine. L'origine non è speciale rispetto a qualsiasi altro punto, in modo che per scoprire se l'origine si trova in un determinato dominio, utilizziamo un algoritmo che potrebbe dirci se un determinato punto appartiene a quel dominio.

La somma Minkowski di X e Y ha una proprietà fresco, che è che se X e Y sono convesse, allora X + Y è troppo. E scoprire se un punto appartiene a un insieme convesso è molto più facile che se quell'insieme non fosse (noto per essere) convesso.

Non possiamo calcolare tutti i x - y per x ∈ X e Y ∈ Y , perché ci sono un'infinità di tali punti x ed y , così eventualmente, poiché X , Y e X + Y sono convesse, è sufficiente utilizzare i punti "più esterni" che definiscono le forme X e Y , che sono i loro vertici, e otterremo i punti più esterni di X + Y , e anche alcuni altri.

Questi punti aggiuntivi sono "circondati" da quelli più esterni di X + Y in modo che non contribuiscano a definire la forma convessa appena ottenuta. Diciamo che non definiscono lo " scafo convesso " dell'insieme di punti. Quindi quello che facciamo è che ci liberiamo di loro in preparazione dell'algoritmo finale che ci dice se l'origine è all'interno dello scafo convesso.


Lo scafo convesso di X + Y. Abbiamo rimosso i vertici "interni".

Quindi otteniamo

Un primo, ingenuo algoritmo

boolean intersect(Shape X, Shape Y) {

  SetOfVertices minkowski = new SetOfVertices();
  for (Vertice x in X) {
    for (Vertice y in Y) {
      minkowski.addVertice(x-y);
    }
  }
  return contains(convexHull(minkowski), Vector2D(0,0));

}

I loop ovviamente hanno complessità O (mn) dove m e n sono il numero di vertici di ciascuna forma. Il minkoswkiset contiene al massimo elementi mn . L' convexHullalgoritmo ha una complessità che dipende dall'algoritmo che hai usato e puoi mirare a O (k log (k)) dove k è la dimensione dell'insieme di punti, quindi nel nostro caso otteniamo O (mn log (mn) ) . L' containsalgoritmo ha una complessità che è lineare con il numero di spigoli (in 2D) o facce (in 3D) dello scafo convesso, quindi dipende davvero dalle forme di partenza, ma non sarà maggiore di O (mn) .

Ti lascerò google per l' containsalgoritmo per le forme convesse, è piuttosto comune. Posso metterlo qui se ho tempo.


Ma stiamo facendo il rilevamento delle collisioni, quindi possiamo ottimizzarlo molto

Inizialmente avevamo due corpi A e B che si muovevano senza rotazione durante un timestep dt (da quello che posso dire guardando le tue foto). Chiamiamo v A e v B le rispettive velocità di A e B , che sono costanti durante il nostro timestep di durata dt . Otteniamo quanto segue:

e, come fai notare nelle tue immagini, questi corpi attraversano aree (o volumi, in 3D) mentre si muovono:

e finiscono come A ' e B' dopo il timestep.

Per applicare il nostro algoritmo ingenuo qui, dovremmo solo calcolare i volumi spazzati. Ma non lo stiamo facendo.

Nel frame di riferimento di B , B non si muove (duh!). E A ha una certa velocità rispetto a B che si ottiene calcolando v A - v B (si può fare il contrario, calcolare la velocità relativa di B nel frame di riferimento di A ).

Moto relativo

Da sinistra a destra: velocità nel quadro di riferimento di base; velocità relative; calcolo delle velocità relative.

Per quanto riguarda B come immobile nel suo quadro di riferimento, devi solo calcolare il volume che A scorre mentre si muove durante dt con la sua velocità relativa v A - v B .

Ciò riduce il numero di vertici da utilizzare nel calcolo della somma di Minkowski (a volte notevolmente).

Un'altra possibile ottimizzazione è nel punto in cui si calcola il volume spazzato da uno dei corpi, diciamo A. Non è necessario tradurre tutti i vertici che compongono A. Solo quelli che appartengono ai bordi (facce in 3D) il cui "faccia" normale esterna la direzione dello spazzamento. Sicuramente lo avevi notato già quando hai calcolato le aree spazzate per i quadrati. Puoi dire se un normale è verso la direzione di spazzamento usando il suo prodotto punto con la direzione di spazzamento, che deve essere positivo.

L'ultima ottimizzazione, che non ha nulla a che fare con la tua domanda riguardo alle intersezioni, è davvero utile nel nostro caso. Utilizza quelle velocità relative che abbiamo menzionato e il cosiddetto metodo dell'asse di separazione. Sicuramente lo sai già.

Supponiamo di conoscere i raggi di A e B rispetto ai loro centri di massa (vale a dire, la distanza tra il centro di massa e il vertice più lontano da esso), in questo modo:

Una collisione può verificarsi solo se è possibile che il cerchio di delimitazione di un incontro che di B . Vediamo qui che non lo farà, e il modo di dire al computer che è di calcolare la distanza da C B a Ho come nella seguente immagine e assicurarsi che sia più grande della somma dei raggi di A e B . Se è più grande, nessuna collisione. Se è più piccolo, quindi la collisione.

Questo non funziona molto bene con forme piuttosto lunghe, ma nel caso di quadrati o altre forme simili, è un'ottima euristica escludere la collisione .

Il teorema dell'asse di separazione applicato a B e il volume spazzato da A , tuttavia, indicano se si verifica la collisione. La complessità dell'algoritmo associato è lineare con la somma dei numeri di vertici di ciascuna forma convessa, ma è meno magico quando arriva il momento di gestire effettivamente la collisione.

Il nostro nuovo e migliore algoritmo che utilizza le intersezioni per aiutare a rilevare le collisioni, ma non è ancora buono come il teorema dell'asse di separazione per dire effettivamente se si verifica una collisione

boolean mayCollide(Body A, Body B) {

  Vector2D relativeVelocity = A.velocity - B.velocity;
  if (radiiHeuristic(A, B, relativeVelocity)) {
    return false; // there is a separating axis between them
  }

  Volume sweptA = sweptVolume(A, relativeVelocity);
  return contains(convexHull(minkowskiMinus(sweptA, B)), Vector2D(0,0));

}

boolean radiiHeuristic(A, B, relativeVelocity)) {
  // the code here
}

Volume convexHull(SetOfVertices s) {
  // the code here
}

boolean contains(Volume v, Vector2D p) {
  // the code here
}

SetOfVertices minkowskiMinus(Body X, Body Y) {

  SetOfVertices result = new SetOfVertices();
  for (Vertice x in X) {
    for (Vertice y in Y) {
      result.addVertice(x-y);
    }
  }
  return result;

}

2

Non credo che usare l'esagono sia di grande aiuto. Ecco uno schizzo di un modo per ottenere esatte collisioni per rettangoli allineati agli assi:

Due rettangoli allineati agli assi si sovrappongono se e solo se le loro gamme di coordinate X si sovrappongono e le loro gamme di coordinate Y si sovrappongono. (Questo può essere visto come un caso speciale del teorema degli assi di separazione.) Cioè, se proiettate i rettangoli sugli assi X e Y avete ridotto il problema a due intersezioni linea-linea.

Calcola l' intervallo di tempo su cui si intersecano le due linee su un asse (ad es. Inizia nel tempo (separazione attuale degli oggetti / velocità relativa di avvicinamento degli oggetti)) e fai lo stesso per l'altro asse. Se tali intervalli di tempo si sovrappongono, il primo tempo all'interno della sovrapposizione è il tempo di collisione.


3
Hai dimenticato il tuo schizzo.
MichaelHouse

2
@ Byte56 No, intendo dire che è uno schizzo di un algoritmo, nemmeno uno pseudocodice.
Kevin Reid,

Oh, capisco. Errore mio.
MichaelHouse

Questo è in realtà il metodo più semplice. Ho aggiunto il codice corrispondente per implementarlo.
Pasha,

1

Non penso che ci sia un modo semplice per calcolare la collisione di poligoni con più lati di un rettangolo. Lo scomporrei in forme primitive come linee e quadrati:

function objectsWillCollide(object1,object2) {
    var lineA, lineB, lineC, lineD;
    //get projected paths of objects and store them in the 'line' variables

    var AC = lineCollision(lineA,lineC);
    var AD = lineCollision(lineA,lineD);
    var BC = lineCollision(lineB,lineC);
    var BD = lineCollision(lineB,lineD);
    var objectToObjectCollision = rectangleCollision(object1.getRectangle(), object2.getRectangle());

    return (AC || AD || BC || BD || objectToObjectCollision);
}

illustrazione dei percorsi e degli stati finali degli oggetti

Nota come ignoro lo stato iniziale di ciascun oggetto, come avrebbe dovuto essere verificato durante il calcolo precedente.


3
Il problema è che se le dimensioni degli oggetti sono molto diverse, l'oggetto più piccolo può spostarsi all'interno del percorso dell'oggetto grande senza innescare una collisione.
API-Beast,

0

Teorema dell'asse separato

Il teorema dell'asse separato dice "Se riusciamo a trovare un asse su cui due forme convesse non si intersecano, le due forme non si intersecano" o più praticabile per l'IT:

"Due forme convesse si intersecano solo se si intersecano su tutti gli assi possibili."

Per i rettangoli allineati agli assi ci sono esattamente 2 assi possibili: xe y. Ma il teorema non è limitato ai rettangoli, può essere applicato a qualsiasi forma convessa semplicemente aggiungendo gli altri assi su cui le forme potrebbero intersecarsi. Per maggiori dettagli sull'argomento consulta questo tutorial dallo sviluppatore di N: http://www.metanetsoftware.com/technique/tutorialA.html#section1

Implementato assomiglia a questo:

axes = [... possible axes ...];
collision = true;
for every index i of axes
{
  range1[i] = shape1.getRangeOnAxis(axes[i]);
  range2[i] = shape2.getRangeOnAxis(axes[i]);
  rangeIntersection[i] = range1[i].intersectionWith(range2[i]);
  if(rangeIntersection[i].length() <= 0)
  {
    collision = false;
    break;
  }
}

Gli assi possono essere rappresentati come vettori normalizzati.

Un intervallo è una linea monodimensionale. L'inizio dovrebbe essere impostato sul punto proiettato più piccolo, la fine sul punto proiettato più grande.

Applicandolo al rettangolo "spazzato"

L'esagono nella domanda viene prodotto "spazzando" l'AABB dell'oggetto. Lo sweep aggiunge esattamente un possibile asse di collisione a qualsiasi forma: il vettore di movimento.

shape1 = sweep(originalShape1, movementVectorOfShape1);
shape2 = sweep(originalShape2, movementVectorOfShape2);

axes[0] = vector2f(1.0, 0.0); // X-Axis
axes[1] = vector2f(0.0, 1.0); // Y-Axis
axes[2] = movementVectorOfShape1.normalized();
axes[3] = movementVectorOfShape2.normalized();

Fin qui tutto bene, ora possiamo già verificare se i due esagoni si intersecano. Ma va ancora meglio.

Questa soluzione funzionerà per qualsiasi forma convessa (ad esempio triangoli) e qualsiasi forma convessa spazzata (ad esempio ottagoni spazzati). Tuttavia, più complessa è la forma, meno efficace sarà.


Bonus: dove accade la magia.

Come ho detto, gli unici assi aggiuntivi sono i vettori di movimento. Il movimento è tempo moltiplicato per la velocità, quindi in un certo senso non sono solo assi spaziali, sono assi spazio-tempo.

Ciò significa che possiamo derivare il tempo in cui la collisione potrebbe essere avvenuta da questi due assi. Per questo abbiamo bisogno di trovare l'intersezione tra le due intersezioni sugli assi di movimento. Prima di poterlo fare, però, dobbiamo normalizzare entrambi gli intervalli, in modo da poterli effettivamente confrontare.

shapeRange1 = originalShape1.getRangeOnAxis(axes[2]);
shapeRange2 = originalShape2.getRangeOnAxis(axes[3]);
// Project them on a scale from 0-1 so we can compare the time ranges
timeFrame1 = (rangeIntersection[2] - shapeRange1.center())/movementVectorOfShape1.project(axes[2]);
timeFrame2 = (rangeIntersection[3] - shapeRange2.center())/movementVectorOfShape2.project(axes[3]);
timeIntersection = timeFrame1.intersectionWith(timeFrame2);

Quando ho posto questa domanda ho già accettato il compromesso che ci saranno alcuni rari falsi positivi con questo metodo. Ma ho sbagliato, controllando questa intersezione temporale possiamo verificare se la collisione è "effettivamente" avvenuta e possiamo risolvere quei falsi positivi con esso:

if(collision)
{
  [... timeIntersection = see above ...]
  if(timeIntersection.length() <= 0)
    collision = false;
  else
    collisionTime = timeIntersection.start; // 0: Start of the frame, 1: End of the frame
}

Se noti degli errori negli esempi di codice fammi sapere, non l'ho ancora implementato e quindi non sono stato in grado di testarlo.


1
Congratulazioni per aver trovato una soluzione! Ma come ho detto prima: solo perché gli esagoni si intersecano non significa che ci sarà una collisione. Puoi usare il tuo metodo per calcolare il tempo di collisione tutto ciò che vuoi, se non c'è collisione, non è molto utile. In secondo luogo, è possibile utilizzare le velocità relative in modo da calcolare solo 1 volume di sweep e semplificare i calcoli quando si utilizza SAT. Infine, ho solo una vaga idea di come funziona il tuo trucco "tempo di intersezione", perché forse hai confuso i tuoi indici, visto come shapeRange1 == shapeRange2con il tuo codice, non è vero?
jrsala,

@madshogo Dovrebbe avere più senso ora.
API-Beast

Continuo a non capire come funzioni la normalizzazione della portata, ma credo sia perché ho bisogno di un'immagine. Spero che funzioni per te.
jrsala,

-2

Finché le aree spazzate sono entrambe chiuse (senza spazi vuoti nel confine formato dalle linee di bordo), funzionerà quanto segue (basta ridurre i test di collisione a linea-linea e punto-retto / punto-tri):

  1. I loro bordi si toccano? (collisioni linea-linea) Controllare se una linea di bordo di un'area spazzata si interseca con una linea di bordo dell'altra area spazzata. Ogni area spazzata ha 6 lati.

  2. Quello piccolo è dentro quello grande? (Usa forme allineate agli assi (point-rect & point-tri)) Riorienta (ruota) le aree spazzate in modo che quella più grande sia allineata all'asse e verifica se quella più piccola è interna (testando se ci sono punti d'angolo ( dovrebbe essere tutto o nessuno) sono all'interno dell'area spazzata allineata sull'asse). Questo è fatto scomporre il tuo esagono in tris e rect.

Il test che fai per primo dipende dalla probabilità di ciascuno (esegui prima quello che si verifica più comunemente).

Potresti trovare più facile usare un cerchio delimitato (capsula anziché esadecimale) perché è più facile dividerlo in due semicerchi e un rettangolo quando è allineato all'asse. .. Ti lascerò disegnare la soluzione


Non funziona se uno dei rettangoli è molto piccolo e si sposta all'interno dello spazio tra due linee di bordo.
jrsala,

@madshogo Ho appena aggiunto alla mia risposta. Dovrebbe essere una soluzione completa ora.
assone,

1
"Usa forme allineate agli assi (point-rect & point-tri)": Come allineare un triangolo o un "triangolo-punto" (qualunque cosa significhi) con gli assi? "in modo che quello più grande sia allineato sull'asse": come si può sapere quale è più grande dell'altro? Calcoli le loro aree? "Questo è fatto decomponendo il tuo esagono in tris e rects.": Quale esagono? Ci sono due. "(o vota questa risposta se vuoi che te la illustri per te)": Sei serio ??
jrsala,

"Come si fa ad allineare un triangolo con gli assi?" A: Allinea il percorso dell'obj creando l'area spazzata. Scegli un bordo e usa il trig. "come puoi dire quale è più grande dell'altro?" A: Ad esempio, usa la distanza tra due punti diagonalmente opposti del retto (al centro dell'esagono). "quale esagono?" A: Quello grande.
assone
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.