Qual è il modo più veloce per elaborare l'intersezione del riquadro di delimitazione 2D?


62

Supponiamo che ogni oggetto Box abbia le proprietà x, y, larghezza, altezza e abbia la sua origine al centro e che né gli oggetti né i rettangoli ruotino.


Questi rettangoli allineati agli assi o agli oggetti?
tenpn,

3
Quando farai questa domanda, dovrai sicuramente testare altri tipi di intersezioni in futuro;). Pertanto suggerisco L'ELENCO sull'intersezione Oggetto / Oggetto. La tabella fornisce intersezioni tra tutti i tipi di oggetti più diffusi (scatole, sfere, triangoli, ciclizzatori, coni, ...) in situazioni statiche e dinamiche.
Dave O.

2
Ti preghiamo di riformulare la tua domanda per rect delimitare. Dal mio punto di vista la scatola implica un oggetto 3d.
Dave O.

Risposte:


55

(C-ish pseudocode - adatta le ottimizzazioni del linguaggio come appropriato)

bool DoBoxesIntersect(Box a, Box b) {
  return (abs(a.x - b.x) * 2 < (a.width + b.width)) &&
         (abs(a.y - b.y) * 2 < (a.height + b.height));
}

In inglese: su ciascun asse, controlla se i centri delle caselle sono abbastanza vicini da intersecarsi. Se si intersecano su entrambi gli assi, le caselle si intersecano. Se non lo fanno, non lo fanno.

È possibile modificare <'s in <= se si desidera contare il tocco del bordo come intersecanti. Se desideri una formula specifica solo per il tocco del bordo, non puoi utilizzare == - che ti dirà se gli angoli toccano, non se i bordi toccano. Vorresti fare qualcosa di logicamente equivalente a return DoBoxesIntersectOrTouch(a, b) && !DoBoxesIntersect(a, b).

Vale la pena ricordare che è possibile ottenere un piccolo ma significativo aumento di velocità memorizzando la metà larghezza e metà altezza oltre (o anziché) l'intera larghezza e altezza intera. D'altra parte, è raro che l'intersezione del riquadro di delimitazione 2d sia il collo di bottiglia delle prestazioni.


9
Ciò ovviamente presuppone che le caselle siano allineate agli assi.
tenpn,

1
Gli addominali non dovrebbero essere particolarmente lenti - non più lenti di un condizionale, almeno, e l'unico modo per farlo senza addominali (di cui sono a conoscenza) comporta condizioni extra.
Zorba THut,

4
Sì, presuppone caselle allineate agli assi. Le strutture descritte non hanno alcun modo di indicare la rotazione, quindi ho ritenuto che fosse sicuro.
Zorba THut,

3
Ecco alcuni buoni consigli per accelerare le calulazioni in Actionscript (principalmente calcoli interi): lab.polygonal.de/2007/05/10/bitwise-gems-fast-integer-math Sto pubblicando questo, perché contiene anche un più veloce sostituto di Math.abs (), che tende a rallentare le cose in Actionscript (parlando ovviamente di cose critiche per le prestazioni).
bummzack,

2
Ti manca che abbiano l'origine al centro, non sul bordo sinistro. Una casella che va da 0 a 10 avrà effettivamente "x = 5", mentre una casella che va da 8 a 12 avrà "x = 10". Si finisce con abs(5 - 10) * 2 < (10 + 4)=> 10 < 14. Dovrai fare alcune semplici modifiche per farlo funzionare con l'angolo e la dimensione di topleft.
ZorbaTHut

37

Funziona con due rettangoli allineati con gli assi X e Y.
Ogni rettangolo ha le proprietà:
"sinistra", la coordinata x del suo lato sinistro,
"top", la coordinata y del suo lato superiore,
"right", la coordinata x del suo lato destro,
"bottom", la coordinata y di il suo lato inferiore,

function IntersectRect(r1:Rectangle, r2:Rectangle):Boolean {
    return !(r2.left > r1.right
        || r2.right < r1.left
        || r2.top > r1.bottom
        || r2.bottom < r1.top);
}

Si noti che questo è progettato per un sistema di coordinate in cui l'asse + y punta verso il basso e l'asse + x è diretto a destra (cioè le coordinate tipiche dello schermo / pixel). Per adattare questo a un tipico sistema cartesiano in cui + y è diretto verso l'alto, i confronti lungo gli assi verticali sarebbero invertiti, ad esempio:

return !(r2.left > r1.right
    || r2.right < r1.left
    || r2.top < r1.bottom
    || r2.bottom > r1.top);

L'idea è di catturare tutte le possibili condizioni sulle quali i rettangoli non si sovrapporranno, quindi negare la risposta per vedere se sono sovrapposti. Indipendentemente dalla direzione degli assi, è facile vedere che due rettangoli non si sovrapporranno se:

  • il bordo sinistro di r2 è più a destra del bordo destro di r1

     ________     ________
    |        |   |        |
    |   r1   |   |   r2   |
    |        |   |        |
    |________|   |________|
    
  • oppure il bordo destro di r2 è più a sinistra del bordo sinistro di r1

     ________     ________
    |        |   |        |
    |   r2   |   |   r1   |
    |        |   |        |
    |________|   |________|
    
  • oppure il bordo superiore di r2 è sotto il bordo inferiore di r1

     ________ 
    |        |
    |   r1   |
    |        |
    |________|
     ________ 
    |        |
    |   r2   |
    |        |
    |________|
    
  • oppure il bordo inferiore di r2 è sopra il bordo superiore di r1

     ________ 
    |        |
    |   r2   |
    |        |
    |________|
     ________ 
    |        |
    |   r1   |
    |        |
    |________|
    

La funzione originale - e una descrizione alternativa del motivo per cui funziona - è disponibile qui: http://tekpool.wordpress.com/2006/10/11/rectangle-intersection-determine-if-two-given-rectangles-intersect- uno dall'altro-o-no /


1
Sorprendentemente intuitivo e mostra ancora una volta che quando trovare la risposta è troppo difficile, provare a trovare la risposta a una domanda opposta potrebbe aiutarti. Grazie!
Lodewijk,

1
Dovresti menzionare che l'asse y punta verso il basso (come in un'immagine). Altrimenti le disuguaglianze r2.top> r1.bottom e r2.bottom <r1.top devono essere invertite.
user1443778,

@ user1443778 buona cattura! Sono andato avanti e ho spiegato vagamente la logica alla base di questo algoritmo in modo indipendente dal sistema di coordinate.
Ponkadoodle,

11

Se vuoi riquadri di delimitazione allineati agli oggetti, prova questo tutorial sul teorema dell'asse di separazione per metanet: http://www.metanetsoftware.com/technique/tutorialA.html

SAT non è la soluzione più veloce, ma è relativamente semplice. Stai cercando di trovare una singola linea (o un piano se 3D) che separerà i tuoi oggetti. Se questa linea esiste, è garantito che è parallela al bordo di una delle caselle, quindi si esegue l'iterazione attraverso tutti i test dei bordi per vedere se separa le scatole.

Questo funziona anche per le caselle allineate agli assi limitando solo l'asse x / y.


Non stavo pensando alla rotazione, ma grazie è un link interessante.
Iain,

5

DoBoxesIntersect sopra è una buona soluzione a coppie. Tuttavia, se hai molte scatole, hai ancora un problema O (N ^ 2) e potresti scoprire che devi fare qualcosa in più come quello a cui fa riferimento Kaj. (Nella letteratura sul rilevamento delle collisioni 3D, questo è noto per avere un algoritmo sia a fase larga che a fase stretta. Faremo qualcosa di molto veloce per trovare tutte le possibili coppie di sovrapposizioni, e quindi qualcosa di più costoso per vedere se il nostro possibile le coppie sono coppie effettive.)

L'algoritmo a larga fase che ho usato prima è "sweep-and-prune"; per 2D, manterresti due elenchi ordinati di inizio e fine di ogni riquadro. Finché il movimento della casella non è >> scala della casella da un fotogramma all'altro, l'ordine di questi elenchi non cambierà molto e quindi è possibile utilizzare la bolla o l'ordinamento per inserimento per mantenerlo. Il libro "Real-Time Rendering" ha un bel resoconto sulle ottimizzazioni che puoi fare, ma si riduce al tempo O (N + K) nella fase ampia, per N caselle, K di cui si sovrappongono e con un eccellente mondo reale prestazioni se puoi permetterti N ^ 2 booleani per tenere traccia di quali coppie di scatole si intersecano da un fotogramma all'altro. Quindi hai un tempo complessivo di O (N + K ^ 2), che è << O (N ^ 2) se hai molte caselle ma solo poche sovrapposizioni.


5

Molta matematica qui per un problema molto semplice, supponiamo che abbiamo 4 punti determinati per un rettangolo, in alto, a sinistra, in basso, a destra ...

Nel caso di determinare se 2 rects si scontrano, dobbiamo solo guardare che tutti i possibili estremi che impedirebbero le collisioni, se nessuna di queste è soddisfatta, quindi le 2 rects DEVONO scontrarsi, se si desidera includere collisioni di confine, è sufficiente sostituire> e < con appropriati> = e = <.

struct aRect{
  float top;
  float left;
  float bottom;
  float right;
};

bool rectCollision(rect a, rect b)
{
  return ! ( b.left > a.right || b.right < a.left || b.top < a.bottom || b.bottom > a.top);
}

Onestamente non sono sicuro del perché questa non sia la risposta più votata. È semplice, corretto ed efficiente.
3Dave il

3

Versione alternativa della risposta di ZorbaTHut:

bool DoBoxesIntersect(Box a, Box b) {
     return (abs(a.x - b.x) < (a.width + b.width) / 2) &&
     (abs(a.y - b.y) < (a.height + b.height) / 2);
}

In realtà quell'aritmetica funziona bene in entrambi i modi. Puoi fare qualsiasi operazione aritmetica su entrambi i lati di <e non la modifica (moltiplicare per un negativo significa che devi cambiare il meno di, però). In questo esempio, le scatole non dovrebbero scontrarsi. Se il centro della casella A è 1, si estende da -4 a 6. La casella b è centrata su 10 e si estende da 7,5 a 12,5, non vi è alcuna collisione lì ... Ora, il metodo pubblicato da Wallacoloo non è solo corretto, ma funzionerà più velocemente su linguaggi che implementano il corto circuito, poiché la maggior parte dei controlli restituirà comunque false, il corto circuito può essere interrotto dopo come:
Deleter,

Sì, me ne sono reso conto quando mi sono svegliato questa mattina. Chris mi ha aiutato con la sua confusione.
Iain,

1
Due problemi: in primo luogo, la divisione tende ad essere significativamente più lenta della moltiplicazione. In secondo luogo, se i valori coinvolti sono numeri interi, ciò potrebbe comportare alcuni problemi di troncamento dei numeri interi (ax = 0, bx = 9, a.width = 9, b.width = 10: abs (0-9) <(9 + 10) / 2, 9 <19/2, 9 <9, la funzione restituisce false nonostante il fatto che le caselle si intersecano definitivamente.)
ZorbaTHut

2

A seconda del problema che si tenta di risolvere, potrebbe essere meglio tenere traccia del proprio oggetto mentre lo si sposta, ovvero mantenere un elenco di posizioni x di inizio e fine ordinate e una per le posizioni di inizio e fine y. Se devi fare MOLTI controlli di sovrapposizione e quindi devi ottimizzare, puoi usarlo a tuo vantaggio, poiché puoi immediatamente cercare chi termina si chiude alla tua sinistra, tutti quelli che stanno alla sinistra possono essere potati subito. Lo stesso vale per la parte superiore, inferiore e destra.
La contabilità ovviamente costa tempo, quindi è più adatta per una situazione con pochi oggetti in movimento ma molti controlli di sovrapposizione.
Un'altra opzione è l'hash spaziale, in cui si raggruppano gli oggetti in base alla posizione approssimativa (la dimensione potrebbe inserirli in più secchi), ma di nuovo lì, solo se ci sono molti oggetti, con relativamente pochi che si muovono per iterazione a causa del costo di contabilità.
Fondamentalmente tutto ciò che evita (n * n) / 2 (se si controlla l'oggetto a contro b non sarà necessario controllare b contro a ovviamente) aiuta più dell'ottimizzazione dei controlli del riquadro di delimitazione. Se i check box delimitati sono un collo di bottiglia, consiglio vivamente di cercare soluzioni alternative al problema.


2

La distanza tra i centri non è uguale alla distanza tra gli angoli (quando una scatola è all'interno dell'altra per esempio), quindi IN GENERALE, questa soluzione è quella corretta (penso io).

distanza tra i centri (per, diciamo, x): abs(x1+1/2*w1 - x2+1/2*w2)oppure1/2 * abs(2*(x1-x2)+(w1-w2)

La distanza minima è 1/2 w1 + 1/2 w2 or 1/2 (w1+w2). le metà si annullano così ..

return 
ABS(2*(x1 - x2) + (w1-w2) ) < (w1+w2)) &&
ABS(2*(y1 - y2) + (h1-h2) ) < (h1+h2));

1
Cosa c'è con l'istruzione "return" lì dentro?
doppelgreener,

1

Ecco la mia implementazione in Java ipotizzando un'architettura a due complementi . Se non sei su complemento a due, usa invece una chiamata di funzione Math.abs standard :

boolean intersects(IntAxisAlignedBox left, IntAxisAlignedBox right) {
    return
        (
            lineDeltaFactor(left.min.x, left.max.x, right.min.x, right.max.x) |
            lineDeltaFactor(left.min.y, left.max.y, right.min.y, right.max.y) |
            lineDeltaFactor(left.min.z, left.max.z, right.min.z, right.max.z)
        ) == 0;
}

int lineDeltaFactor(int leftMin, int leftMax, int rightMin, int rightMax) {
    final int
            leftWidth = leftMax - leftMin,
            rightWidth = rightMax - rightMin,

            leftMid = leftMin + ((leftMax - leftMin) >> 1),
            rightMid = rightMin + ((rightMax - rightMin) >> 1);

    return (abs(leftMid - rightMid) << 1) / (leftWidth + rightWidth + 1);
}

int abs(int value) {
    final int mask = value >> (Integer.SIZE - 1);

    value ^= mask;
    value += mask & 1;
    return value;
}

Supponendo che un compilatore decente / LLVM inline espanda queste funzioni per evitare costose giocolette di stack e ricerche di v-table. Ciò non funzionerà per valori di input vicini a valori estremi di 32 bit (ie Integer.MAX_VALUEe Integer.MIN_VALUE).


0

Il modo più veloce è combinare tutti e 4 i valori in un unico registro vettoriale.

Conservare le scatole in un vettore con i seguenti valori [ min.x, min.y, -max.x, -max.y ]. Se si memorizzano scatole come questa, il test di intersezione richiede solo 3 istruzioni CPU:

_mm_shuffle_ps per riordinare la seconda casella lanciando le metà min e max.

_mm_xor_pscon il numero magico _mm_set1_ps(-0.0f)per capovolgere i segni di tutti e 4 i valori nella seconda casella.

_mm_cmple_ps per confrontare tutti e 4 i valori tra loro, confrontando i seguenti due registri:

[ a.min.x, a.min.y, -a.max.x, -a.max.y ] < [ b.max.x, b.max.y, -b.min.x, -b.min.y ]

Infine, se necessario, _mm_movemask_psper ottenere il risultato dall'unità vettoriale in un registro scalare. Valore 0 indica le caselle intersecate. Oppure, se hai più di 2 caselle, non è necessario, lascia i valori nei registri vettoriali e usa le operazioni bit a bit per combinare i risultati di più caselle.

Non hai specificato la lingua né la piattaforma, ma il supporto per SIMD come questo, o molto simile, è disponibile in tutte le piattaforme e lingue. Su cellulare, ARM ha NEON SIMD con roba molto simile. .NET ha Vector128 nello spazio dei nomi System.Runtime.Intrinsics e così via.

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.