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.
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.
Risposte:
(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.
abs(5 - 10) * 2 < (10 + 4)
=> 10 < 14
. Dovrai fare alcune semplici modifiche per farlo funzionare con l'angolo e la dimensione di topleft.
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 /
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.
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.
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);
}
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);
}
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.
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));
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_VALUE
e Integer.MIN_VALUE
).
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_ps
con 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_ps
per 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.