Rilevamento collisione cerchio-rettangolo (intersezione)


192

Come posso sapere se un cerchio e un rettangolo si intersecano nello spazio euclideo 2D? (ovvero la classica geometria 2D)


1
Il rettangolo è sempre allineato con gli assi o può essere ruotato di un angolo arbitrario?
e.James

11
@eJames: che importanza ha? Stai controllando il rettangolo per l'intersezione con un cerchio ; puoi sempre trasformare il tuo sistema di coordinate in modo che il rettangolo sia parallelo all'asse senza alcun cambiamento nel cerchio :-)
ShreevatsaR

Dovresti aggiungerlo come una risposta, ruotando attraverso -Θ e tutto ...
aib

2
@ShreevatsaR: è importante in termini di tempo o meno, devo preoccuparmi o meno della traduzione coordinata. @aib: Oh caro!
e.James

Risposte:


191

Ci sono solo due casi in cui il cerchio si interseca con il rettangolo:

  • O il centro del cerchio si trova all'interno del rettangolo, oppure
  • Uno dei bordi del rettangolo ha un punto nel cerchio.

Si noti che ciò non richiede che il rettangolo sia parallelo all'asse.

In alcuni modi diversi un cerchio e un rettangolo possono intersecarsi

(Un modo per vedere questo: se nessuno dei bordi ha un punto nel cerchio (se tutti i bordi sono completamente "esterni" al cerchio), l'unico modo in cui il cerchio può ancora intersecare il poligono è se si trova completamente all'interno del poligono.)

Con questa intuizione, qualcosa di simile a quanto segue funzionerà, dove il cerchio ha centro Pe raggio R, e il rettangolo ha vertici A, B, C, Din questo ordine (codice non completo):

def intersect(Circle(P, R), Rectangle(A, B, C, D)):
    S = Circle(P, R)
    return (pointInRectangle(P, Rectangle(A, B, C, D)) or
            intersectCircle(S, (A, B)) or
            intersectCircle(S, (B, C)) or
            intersectCircle(S, (C, D)) or
            intersectCircle(S, (D, A)))

Se stai scrivendo una geometria probabilmente hai già le funzioni sopra nella tua libreria. Altrimenti, pointInRectangle()può essere implementato in diversi modi; qualsiasi punto generale nei metodi poligonali funzionerà, ma per un rettangolo puoi semplicemente verificare se funziona:

0 ≤ AP·AB ≤ AB·AB and 0 ≤ AP·AD ≤ AD·AD

Ed intersectCircle()è anche facile da implementare: un modo sarebbe quello di verificare se il piede della perpendicolare dalla Plinea è abbastanza vicino e tra gli endpoint e controllare gli endpoint altrimenti.

La cosa interessante è che la stessa idea funziona non solo per i rettangoli ma per l'intersezione di un cerchio con qualsiasi semplice poligono - non deve nemmeno essere convessa!


25
Per quello che vale, penso davvero che questa risposta sia migliore della mia. Due motivi principali: 1: non richiede una rotazione se il rettangolo non è parallelo all'asse e, 2: il concetto si estende facilmente a tutti i poligoni.
e.James

2
@paniq: bene, entrambi sono a tempo costante. :-) Ma sì, questo è più utile come soluzione generale, che copre i rettangoli con qualsiasi orientamento e in effetti qualsiasi semplice poligono.
ShreevatsaR,

7
che dire del caso in cui il rettangolo è completamente all'interno del cerchio, ma il centro del cerchio non è all'interno del rettangolo?
ericsoco,

2
@ericsoco: buona osservazione. :-) Immagino che avrei dovuto dire "interseca il disco" in "uno dei bordi del rettangolo interseca il cerchio", perché intendevo dire che condivide un punto con il cerchio stesso, non necessariamente il confine del cerchio. Ad ogni modo, la descrizione sopra, "controlla se il piede della perpendicolare da P [centro del cerchio] alla linea è abbastanza vicino e tra gli endpoint e controlla gli endpoint altrimenti" continuerà a funzionare, ad esempio gli endpoint si trovano all'interno del cerchio ( disco).
ShreevatsaR,

2
@ DexD.Hunter Se il centro del cerchio si trova all'esterno del rettangolo, ma una parte di esso si trova all'interno del rettangolo, allora necessariamente uno dei bordi del rettangolo interseca il cerchio.
ShreevatsaR,

289

Ecco come lo farei:

bool intersects(CircleType circle, RectType rect)
{
    circleDistance.x = abs(circle.x - rect.x);
    circleDistance.y = abs(circle.y - rect.y);

    if (circleDistance.x > (rect.width/2 + circle.r)) { return false; }
    if (circleDistance.y > (rect.height/2 + circle.r)) { return false; }

    if (circleDistance.x <= (rect.width/2)) { return true; } 
    if (circleDistance.y <= (rect.height/2)) { return true; }

    cornerDistance_sq = (circleDistance.x - rect.width/2)^2 +
                         (circleDistance.y - rect.height/2)^2;

    return (cornerDistance_sq <= (circle.r^2));
}

Ecco come funziona:

illusration

  1. La prima coppia di linee calcola i valori assoluti della differenza xey tra il centro del cerchio e il centro del rettangolo. In questo modo i quattro quadranti vengono compressi in uno, in modo che i calcoli non debbano essere eseguiti quattro volte. L'immagine mostra l'area in cui ora deve trovarsi il centro del cerchio. Si noti che viene mostrato solo il quadrante singolo. Il rettangolo è l'area grigia e il bordo rosso delinea l'area critica che si trova esattamente a un raggio di distanza dai bordi del rettangolo. Il centro del cerchio deve trovarsi all'interno di questo bordo rosso affinché si verifichi l'intersezione.

  2. La seconda coppia di linee elimina i casi semplici in cui il cerchio è abbastanza lontano dal rettangolo (in entrambe le direzioni) da non consentire intersezioni. Ciò corrisponde all'area verde nell'immagine.

  3. La terza coppia di linee gestisce i casi semplici in cui il cerchio è abbastanza vicino al rettangolo (in entrambe le direzioni) da garantire un'intersezione. Ciò corrisponde alle sezioni arancione e grigia nell'immagine. Si noti che questo passaggio deve essere eseguito dopo il passaggio 2 affinché la logica abbia un senso.

  4. Le linee rimanenti calcolano il caso difficile in cui il cerchio può intersecare l'angolo del rettangolo. Per risolvere, calcola la distanza dal centro del cerchio e dall'angolo, quindi verifica che la distanza non sia superiore al raggio del cerchio. Questo calcolo restituisce falso per tutti i cerchi il cui centro è all'interno dell'area ombreggiata rossa e restituisce vero per tutti i cerchi il cui centro è all'interno dell'area ombreggiata bianca.


4
Molto bella! Note: apparentemente qui, rect.x / y è nell'angolo in alto a destra del rettangolo. Inoltre puoi eliminare la costosa radice quadrata, confrontando invece con il quadrato del raggio.
Luqui,

2
Oh no, mia cattiva. rect.x / y si trova nella parte inferiore sinistra del rettangolo. Avrei scritto: circleDistance.x = abs (circle.x - (rect.x + rect.width / 2));
Luqui,

2
@Tanner: eccoci. Evviva backup e OCD;)
e.James

11
solo per chiarire: questa risposta si applica solo ai rettangoli allineati agli assi. questo è chiaro dalla lettura di commenti su altre risposte ma non ovvio da questa risposta + solo commenti. (ottima risposta per i rects allineati agli assi!)
ericsoco

3
Grande! È importante che i lettori sappiano che qui credo che la definizione di retto sia rect.x & rect.y siano il centro del retto. Nel mio mondo la xy di un retto è in alto / a sinistra di retto, e 0,0 è in alto / a sinistra dello schermo, quindi ho usato:circleDistance_x = abs(circle.x - (rect.x-rect.w/2)); circleDistance_y = abs(circle.y - (rect.y-rect.h/2));
erco,

123

Ecco un'altra soluzione che è abbastanza semplice da implementare (e anche abbastanza veloce). Catturerà tutte le intersezioni, anche quando la sfera è entrata completamente nel rettangolo.

// clamp(value, min, max) - limits value to the range min..max

// Find the closest point to the circle within the rectangle
float closestX = clamp(circle.X, rectangle.Left, rectangle.Right);
float closestY = clamp(circle.Y, rectangle.Top, rectangle.Bottom);

// Calculate the distance between the circle's center and this closest point
float distanceX = circle.X - closestX;
float distanceY = circle.Y - closestY;

// If the distance is less than the circle's radius, an intersection occurs
float distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
return distanceSquared < (circle.Radius * circle.Radius);

Con qualsiasi libreria matematica decente, che può essere ridotta a 3 o 4 righe.


3
Hai un bug lì dentro, cerchi il più vicinoY con Sinistra e Destra, non Alto e Basso, altrimenti bella soluzione.
manveru,

8
Mi piace questa risposta al meglio. È breve, facile da capire e veloce.
John Kurlak,

2
Penso che la tua soluzione fallisca se il rettangolo è obliquo rispetto agli assi X e Y.
Leo,

3
@Leo Penso che non sia difficile modificare questo algoritmo per adattarlo a quel caso, si dovrebbe semplicemente applicare una trasformazione delle coordinate in cui l'origine è al centro del rettangolo e il rettangolo non è più obliquo. Devi applicare la trasformazione solo al centro del cerchio.
enobayram,

1
Questo è fondamentalmente lo stesso del codice trovato su migapro.com/circle-and-rotated-rectangle-collision-detection che ho anche portato su Objective-C. Funziona molto bene; è una bella soluzione al problema.
PKCLsoft

10

la tua sfera e il tuo rettangolo si intersecano IIF
la distanza tra il centro del cerchio e un vertice del tuo rettangolo è minore del raggio della sfera
O
la distanza tra il centro del cerchio e un bordo del rettangolo è minore del raggio della sfera ( [ distanza punto-linea ])
OPPURE
il centro del cerchio si trova all'interno del rettangolo

:

P1 = [x1, y1]
P2 = [x2, y2]
Distanza = sqrt (abs (x1 - x2) + abs (y1-y2))

distanza punto-linea:

L1 = [x1, y1], L2 = [x2, y2] (due punti della linea, ovvero i punti del vertice)
P1 = [px, py] qualche punto

Distanza d = abs ((x2-x1) (y1-py) - (x1-px) (y2-y1)) / Distanza (L1, L2)


centro del cerchio all'interno
del rettangolo : prendere un approccio di asse separato: se esiste una proiezione su una linea che separa il rettangolo dal punto, non si intersecano

proietti il ​​punto su linee parallele ai lati del tuo rettangolo e puoi quindi determinare facilmente se si intersecano. se non si intersecano su tutte e 4 le proiezioni, loro (il punto e il rettangolo) non possono intersecarsi.

hai solo bisogno del prodotto interno (x = [x1, x2], y = [y1, y2], x * y = x1 * y1 + x2 * y2)

il tuo test sarebbe simile al seguente:

// bordi rettangolari: TL (in alto a sinistra), TR (in alto a destra), BL (in basso a sinistra), BR (in basso a destra)
// punta al test: POI

separato = falso
per esempio in {{TL, TR}, {BL, BR}, {TL, BL}, {TR-BR}}: // i bordi
    D = bordo [0] - bordo [1]
    innerProd = D * POI
    Interval_min = min (bordo D * [0], bordo D * [1])
    Interval_max = max (bordo D * [0], bordo D * [1])
    in caso contrario (Interval_min ≤ innerProd ≤ Interval_max) 
           separato = vero
           break // end per loop 
    finisci se
fine per
if (separato è vero)    
      ritorna "nessuna intersezione"
altro 
      ritorna "incrocio"
finisci se

ciò non presuppone un rettangolo allineato agli assi ed è facilmente estensibile per testare le intersezioni tra insiemi convessi.


1
La distanza punto-punto non dovrebbe usare un quadrato, non un addominale?
Thomas,

6

Questa è la soluzione più veloce:

public static boolean intersect(Rectangle r, Circle c)
{
    float cx = Math.abs(c.x - r.x - r.halfWidth);
    float xDist = r.halfWidth + c.radius;
    if (cx > xDist)
        return false;
    float cy = Math.abs(c.y - r.y - r.halfHeight);
    float yDist = r.halfHeight + c.radius;
    if (cy > yDist)
        return false;
    if (cx <= r.halfWidth || cy <= r.halfHeight)
        return true;
    float xCornerDist = cx - r.halfWidth;
    float yCornerDist = cy - r.halfHeight;
    float xCornerDistSq = xCornerDist * xCornerDist;
    float yCornerDistSq = yCornerDist * yCornerDist;
    float maxCornerDistSq = c.radius * c.radius;
    return xCornerDistSq + yCornerDistSq <= maxCornerDistSq;
}

Notare l'ordine di esecuzione e metà larghezza / altezza viene pre-calcolata. Anche la quadratura viene eseguita "manualmente" per salvare alcuni cicli di clock.


3
Non credo che si possa affermare che cinque test / confronti nel percorso del codice più costoso sono la "soluzione più veloce" senza alcuna prova.
Sam Hocevar,


1
Nella mia esperienza con questo metodo, la collisione non avviene la maggior parte delle volte. Pertanto i test causeranno un'uscita prima che venga eseguita la maggior parte del codice.
intrepidis,

6

La soluzione più semplice che ho trovato è piuttosto semplice.

Funziona trovando il punto nel rettangolo più vicino al cerchio, quindi confrontando la distanza.

Puoi fare tutto questo con poche operazioni e persino evitare la funzione sqrt.

public boolean intersects(float cx, float cy, float radius, float left, float top, float right, float bottom)
{
   float closestX = (cx < left ? left : (cx > right ? right : cx));
   float closestY = (cy < top ? top : (cy > bottom ? bottom : cy));
   float dx = closestX - cx;
   float dy = closestY - cy;

   return ( dx * dx + dy * dy ) <= radius * radius;
}

E questo è tutto! La soluzione di cui sopra presuppone un'origine nella parte superiore sinistra del mondo con l'asse x rivolto verso il basso.

Se vuoi una soluzione per gestire le collisioni tra un cerchio in movimento e un rettangolo, è molto più complicato e coperto da un'altra mia risposta.


Questo non riuscirà a rilevare le intersezioni se il raggio del cerchio è troppo piccolo e il suo centro è all'interno del rettangolo!
Yoav,

2
Potete fornire input effettivi che non riescono? Quando il cerchio è all'interno, la parte sinistra del test è 0,0. A meno che il raggio non sia zero, la parte destra del test dovrebbe essere> 0,0
ClickerMonkey

Funzionerà anche per i rettangoli ruotati? in caso contrario, per favore, dammi un suggerimento al riguardo .....
M Abdul Sami,

4

In realtà, questo è molto più semplice. Hai solo bisogno di due cose.

Innanzitutto, devi trovare quattro distanze ortogonali dal centro del cerchio a ciascuna linea del rettangolo. Quindi il tuo cerchio non intersecherà il rettangolo se tre di essi sono più grandi del raggio del cerchio.

In secondo luogo, è necessario trovare la distanza tra il centro del cerchio e il centro del rettangolo, quindi il cerchio non sarà all'interno del rettangolo se la distanza è maggiore della metà della lunghezza diagonale del rettangolo.

In bocca al lupo!


3

Ecco il mio codice C per risolvere una collisione tra una sfera e una scatola allineata senza asse. Si basa su un paio delle mie routine di libreria, ma potrebbe rivelarsi utile per alcuni. Lo sto usando in un gioco e funziona perfettamente.

float physicsProcessCollisionBetweenSelfAndActorRect(SPhysics *self, SPhysics *actor)
{
    float diff = 99999;

    SVector relative_position_of_circle = getDifference2DBetweenVectors(&self->worldPosition, &actor->worldPosition);
    rotateVector2DBy(&relative_position_of_circle, -actor->axis.angleZ); // This aligns the coord system so the rect becomes an AABB

    float x_clamped_within_rectangle = relative_position_of_circle.x;
    float y_clamped_within_rectangle = relative_position_of_circle.y;
    LIMIT(x_clamped_within_rectangle, actor->physicsRect.l, actor->physicsRect.r);
    LIMIT(y_clamped_within_rectangle, actor->physicsRect.b, actor->physicsRect.t);

    // Calculate the distance between the circle's center and this closest point
    float distance_to_nearest_edge_x = relative_position_of_circle.x - x_clamped_within_rectangle;
    float distance_to_nearest_edge_y = relative_position_of_circle.y - y_clamped_within_rectangle;

    // If the distance is less than the circle's radius, an intersection occurs
    float distance_sq_x = SQUARE(distance_to_nearest_edge_x);
    float distance_sq_y = SQUARE(distance_to_nearest_edge_y);
    float radius_sq = SQUARE(self->physicsRadius);
    if(distance_sq_x + distance_sq_y < radius_sq)   
    {
        float half_rect_w = (actor->physicsRect.r - actor->physicsRect.l) * 0.5f;
        float half_rect_h = (actor->physicsRect.t - actor->physicsRect.b) * 0.5f;

        CREATE_VECTOR(push_vector);         

        // If we're at one of the corners of this object, treat this as a circular/circular collision
        if(fabs(relative_position_of_circle.x) > half_rect_w && fabs(relative_position_of_circle.y) > half_rect_h)
        {
            SVector edges;
            if(relative_position_of_circle.x > 0) edges.x = half_rect_w; else edges.x = -half_rect_w;
            if(relative_position_of_circle.y > 0) edges.y = half_rect_h; else edges.y = -half_rect_h;   

            push_vector = relative_position_of_circle;
            moveVectorByInverseVector2D(&push_vector, &edges);

            // We now have the vector from the corner of the rect to the point.
            float delta_length = getVector2DMagnitude(&push_vector);
            float diff = self->physicsRadius - delta_length; // Find out how far away we are from our ideal distance

            // Normalise the vector
            push_vector.x /= delta_length;
            push_vector.y /= delta_length;
            scaleVector2DBy(&push_vector, diff); // Now multiply it by the difference
            push_vector.z = 0;
        }
        else // Nope - just bouncing against one of the edges
        {
            if(relative_position_of_circle.x > 0) // Ball is to the right
                push_vector.x = (half_rect_w + self->physicsRadius) - relative_position_of_circle.x;
            else
                push_vector.x = -((half_rect_w + self->physicsRadius) + relative_position_of_circle.x);

            if(relative_position_of_circle.y > 0) // Ball is above
                push_vector.y = (half_rect_h + self->physicsRadius) - relative_position_of_circle.y;
            else
                push_vector.y = -((half_rect_h + self->physicsRadius) + relative_position_of_circle.y);

            if(fabs(push_vector.x) < fabs(push_vector.y))
                push_vector.y = 0;
            else
                push_vector.x = 0;
        }

        diff = 0; // Cheat, since we don't do anything with the value anyway
        rotateVector2DBy(&push_vector, actor->axis.angleZ);
        SVector *from = &self->worldPosition;       
        moveVectorBy2D(from, push_vector.x, push_vector.y);
    }   
    return diff;
}

2

Per visualizzare, prendi il tastierino numerico della tastiera. Se la chiave '5' rappresenta il tuo rettangolo, allora tutte le chiavi 1-9 rappresentano i 9 quadranti dello spazio divisi per le linee che compongono il tuo rettangolo (con 5 all'interno).

1) Se il centro del cerchio si trova nel quadrante 5 (cioè all'interno del rettangolo), le due forme si intersecano.

A parte questo, ci sono due possibili casi: a) Il cerchio si interseca con due o più bordi adiacenti del rettangolo. b) Il cerchio si interseca con un bordo del rettangolo.

Il primo caso è semplice. Se il cerchio si interseca con due bordi adiacenti del rettangolo, deve contenere l'angolo che collega quei due bordi. (Quello, o il suo centro si trova nel quadrante 5, che abbiamo già coperto. Nota anche che è coperto anche il caso in cui il cerchio si interseca con solo due bordi opposti del rettangolo.)

2) Se uno degli angoli A, B, C, D del rettangolo si trova all'interno del cerchio, le due forme si intersecano.

Il secondo caso è più complicato. Dovremmo notare che può accadere solo quando il centro del cerchio si trova in uno dei quadranti 2, 4, 6 o 8. (In effetti, se il centro si trova su uno dei quadranti 1, 3, 7, 8, il l'angolo corrispondente sarà il punto più vicino ad esso.)

Ora abbiamo il caso che il centro del cerchio si trova in uno dei quadranti del "bordo" e si interseca solo con il bordo corrispondente. Quindi, il punto sul bordo più vicino al centro del cerchio deve trovarsi all'interno del cerchio.

3) Per ogni riga AB, BC, CD, DA, costruisci linee perpendicolari p (AB, P), p (BC, P), p (CD, P), p (DA, P) attraverso il centro del cerchio P. For ogni linea perpendicolare, se l'intersezione con il bordo originale si trova all'interno del cerchio, le due forme si intersecano.

C'è un collegamento per questo ultimo passaggio. Se il centro del cerchio si trova nel quadrante 8 e il bordo AB è il bordo superiore, il punto di intersezione avrà la coordinata y di A e B e la coordinata x del centro P.

È possibile costruire le quattro intersezioni di linea e verificare se si trovano sui loro bordi corrispondenti, oppure scoprire in quale quadrante P si trova e controllare l'intersezione corrispondente. Entrambi dovrebbero semplificare alla stessa equazione booleana. Diffidare dal fatto che il passaggio 2 sopra non ha escluso che P si trovi in ​​uno dei quadranti "angolari"; cercava solo un incrocio.

Modifica: A quanto pare, ho trascurato il semplice fatto che il n. 2 è una sottocassa del n. 3 sopra. Dopotutto, anche gli angoli sono punti sui bordi. Vedi la risposta di @ ShreevatsaR di seguito per una grande spiegazione. E nel frattempo, dimentica il n. 2 sopra a meno che non desideri un controllo rapido ma ridondante.


2

Questa funzione rileva le collisioni (intersezioni) tra Cerchio e Rettangolo. Funziona come il metodo e.James nella sua risposta, ma questo rileva le collisioni per tutti gli angoli del rettangolo (non solo nell'angolo in alto).

NOTA:

aRect.origin.x e aRect.origin.y sono coordinate dell'angolo inferiore sinistro del rettangolo!

aCircle.x e aCircle.y sono coordinate del centro del cerchio!

static inline BOOL RectIntersectsCircle(CGRect aRect, Circle aCircle) {

    float testX = aCircle.x;
    float testY = aCircle.y;

    if (testX < aRect.origin.x)
        testX = aRect.origin.x;
    if (testX > (aRect.origin.x + aRect.size.width))
        testX = (aRect.origin.x + aRect.size.width);
    if (testY < aRect.origin.y)
        testY = aRect.origin.y;
    if (testY > (aRect.origin.y + aRect.size.height))
        testY = (aRect.origin.y + aRect.size.height);

    return ((aCircle.x - testX) * (aCircle.x - testX) + (aCircle.y - testY) * (aCircle.y - testY)) < aCircle.radius * aCircle.radius;
}

1

Ho un metodo che evita i pitagorici costosi se non necessario - cioè. quando si delimitano le caselle del rettangolo e il cerchio non si intersecano.

E funzionerà anche per i non euclidei:

class Circle {
 // create the bounding box of the circle only once
 BBox bbox;

 public boolean intersect(BBox b) {
    // test top intersect
    if (lat > b.maxLat) {
        if (lon < b.minLon)
            return normDist(b.maxLat, b.minLon) <= normedDist;
        if (lon > b.maxLon)
            return normDist(b.maxLat, b.maxLon) <= normedDist;
        return b.maxLat - bbox.minLat > 0;
    }

    // test bottom intersect
    if (lat < b.minLat) {
        if (lon < b.minLon)
            return normDist(b.minLat, b.minLon) <= normedDist;
        if (lon > b.maxLon)
            return normDist(b.minLat, b.maxLon) <= normedDist;
        return bbox.maxLat - b.minLat > 0;
    }

    // test middle intersect
    if (lon < b.minLon)
        return bbox.maxLon - b.minLon > 0;
    if (lon > b.maxLon)
        return b.maxLon - bbox.minLon > 0;
    return true;
  }
}
  • minLat, maxLat può essere sostituito con minY, maxY e lo stesso per minLon, maxLon: sostituirlo con minX, maxX
  • normDist è un metodo leggermente più veloce del calcolo della distanza completa. Ad esempio, senza la radice quadrata nello spazio euclideo (o senza un sacco di altre cose per haversine): dLat=(lat-circleY); dLon=(lon-circleX); normed=dLat*dLat+dLon*dLon. Ovviamente se usi quel metodo normDist dovrai creare un normedDist = dist*dist;per il cerchio

Vedi il codice BBox e Circle completo del mio progetto GraphHopper .


1

Ho creato lezioni per lavorare con forme che spero ti piacciano

public class Geomethry {
  public static boolean intersectionCircleAndRectangle(int circleX, int circleY, int circleR, int rectangleX, int rectangleY, int rectangleWidth, int rectangleHeight){
    boolean result = false;

    float rectHalfWidth = rectangleWidth/2.0f;
    float rectHalfHeight = rectangleHeight/2.0f;

    float rectCenterX = rectangleX + rectHalfWidth;
    float rectCenterY = rectangleY + rectHalfHeight;

    float deltax = Math.abs(rectCenterX - circleX);
    float deltay = Math.abs(rectCenterY - circleY);

    float lengthHypotenuseSqure = deltax*deltax + deltay*deltay;

    do{
        // check that distance between the centerse is more than the distance between the circumcircle of rectangle and circle
        if(lengthHypotenuseSqure > ((rectHalfWidth+circleR)*(rectHalfWidth+circleR) + (rectHalfHeight+circleR)*(rectHalfHeight+circleR))){
            //System.out.println("distance between the centerse is more than the distance between the circumcircle of rectangle and circle");
            break;
        }

        // check that distance between the centerse is less than the distance between the inscribed circle
        float rectMinHalfSide = Math.min(rectHalfWidth, rectHalfHeight);
        if(lengthHypotenuseSqure < ((rectMinHalfSide+circleR)*(rectMinHalfSide+circleR))){
            //System.out.println("distance between the centerse is less than the distance between the inscribed circle");
            result=true;
            break;
        }

        // check that the squares relate to angles
        if((deltax > (rectHalfWidth+circleR)*0.9) && (deltay > (rectHalfHeight+circleR)*0.9)){
            //System.out.println("squares relate to angles");
            result=true;
        }
    }while(false);

    return result;
}

public static boolean intersectionRectangleAndRectangle(int rectangleX, int rectangleY, int rectangleWidth, int rectangleHeight, int rectangleX2, int rectangleY2, int rectangleWidth2, int rectangleHeight2){
    boolean result = false;

    float rectHalfWidth = rectangleWidth/2.0f;
    float rectHalfHeight = rectangleHeight/2.0f;
    float rectHalfWidth2 = rectangleWidth2/2.0f;
    float rectHalfHeight2 = rectangleHeight2/2.0f;

    float deltax = Math.abs((rectangleX + rectHalfWidth) - (rectangleX2 + rectHalfWidth2));
    float deltay = Math.abs((rectangleY + rectHalfHeight) - (rectangleY2 + rectHalfHeight2));

    float lengthHypotenuseSqure = deltax*deltax + deltay*deltay;

    do{
        // check that distance between the centerse is more than the distance between the circumcircle
        if(lengthHypotenuseSqure > ((rectHalfWidth+rectHalfWidth2)*(rectHalfWidth+rectHalfWidth2) + (rectHalfHeight+rectHalfHeight2)*(rectHalfHeight+rectHalfHeight2))){
            //System.out.println("distance between the centerse is more than the distance between the circumcircle");
            break;
        }

        // check that distance between the centerse is less than the distance between the inscribed circle
        float rectMinHalfSide = Math.min(rectHalfWidth, rectHalfHeight);
        float rectMinHalfSide2 = Math.min(rectHalfWidth2, rectHalfHeight2);
        if(lengthHypotenuseSqure < ((rectMinHalfSide+rectMinHalfSide2)*(rectMinHalfSide+rectMinHalfSide2))){
            //System.out.println("distance between the centerse is less than the distance between the inscribed circle");
            result=true;
            break;
        }

        // check that the squares relate to angles
        if((deltax > (rectHalfWidth+rectHalfWidth2)*0.9) && (deltay > (rectHalfHeight+rectHalfHeight2)*0.9)){
            //System.out.println("squares relate to angles");
            result=true;
        }
    }while(false);

    return result;
  } 
}

1

Ecco il codice modificato funzionante al 100%:

public static bool IsIntersected(PointF circle, float radius, RectangleF rectangle)
{
    var rectangleCenter = new PointF((rectangle.X +  rectangle.Width / 2),
                                     (rectangle.Y + rectangle.Height / 2));

    var w = rectangle.Width  / 2;
    var h = rectangle.Height / 2;

    var dx = Math.Abs(circle.X - rectangleCenter.X);
    var dy = Math.Abs(circle.Y - rectangleCenter.Y);

    if (dx > (radius + w) || dy > (radius + h)) return false;

    var circleDistance = new PointF
                             {
                                 X = Math.Abs(circle.X - rectangle.X - w),
                                 Y = Math.Abs(circle.Y - rectangle.Y - h)
                             };

    if (circleDistance.X <= (w))
    {
        return true;
    }

    if (circleDistance.Y <= (h))
    {
        return true;
    }

    var cornerDistanceSq = Math.Pow(circleDistance.X - w, 2) + 
                                    Math.Pow(circleDistance.Y - h, 2);

    return (cornerDistanceSq <= (Math.Pow(radius, 2)));
}

Bassam Alugili


1

Ecco un rapido test di una riga per questo:

if (length(max(abs(center - rect_mid) - rect_halves, 0)) <= radius ) {
  // They intersect.
}

Questo è il caso allineato all'asse in cui rect_halvesè un vettore positivo che punta dal rettangolo centrale a un angolo. L'espressione all'interno length()è un vettore delta da centerun punto più vicino nel rettangolo. Funziona in qualsiasi dimensione.


1
  • Prima controlla se il rettangolo e il quadrato tangenti al cerchio si sovrappongono (facile). Se non si sovrappongono, non si scontrano.
  • Controlla se il centro del cerchio si trova all'interno del rettangolo (facile). Se è dentro, si scontrano.
  • Calcola la distanza minima quadrata dai lati del rettangolo al centro del cerchio (leggermente duro). Se è inferiore al raggio quadrato, allora si scontrano, altrimenti no.

È efficiente perché:

  • Prima controlla lo scenario più comune con un algoritmo economico e quando è sicuro che non si scontrano, finisce.
  • Quindi controlla il prossimo scenario più comune con un algoritmo economico (non calcolare la radice quadrata, utilizzare i valori quadrati) e quando è sicuro che si scontrino, finisce.
  • Quindi esegue l'algoritmo più costoso per verificare la collisione con i bordi del rettangolo.

1

ha funzionato per me (funziona solo quando l'angolo del rettangolo è 180)

function intersects(circle, rect) {
  let left = rect.x + rect.width > circle.x - circle.radius;
  let right = rect.x < circle.x + circle.radius;
  let top = rect.y < circle.y + circle.radius;
  let bottom = rect.y + rect.height > circle.y - circle.radius;
  return left && right && bottom && top;
}

hmmm ... Ho votato a favore ma poi ho testato correttamente e penso che non funzioni agli angoli per esempio. Funzionerebbe per due rettangoli.
Dan Zen,

1

Migliorando un po 'la risposta di e.James :

double dx = abs(circle.x - rect.x) - rect.w / 2,
       dy = abs(circle.y - rect.y) - rect.h / 2;

if (dx > circle.r || dy > circle.r) { return false; }
if (dx <= 0 || dy <= 0) { return true; }

return (dx * dx + dy * dy <= circle.r * circle.r);

Questo sottrae rect.w / 2e rect.h / 2una volta invece che fino a tre volte.


Sospetto fortemente che la maggior parte dei compilatori moderni possa (o almeno potrebbe) ottimizzare automaticamente i calcoli ridondanti.
martineau,

0

Per coloro che devono calcolare la collisione Cerchio / Rettangolo in Coordinate geografiche con SQL,
questa è la mia implementazione nell'oracolo 11 dell'algoritmo suggerito da e.James .

In input richiede coordinate del cerchio, raggio del cerchio in km e due coordinate dei vertici del rettangolo:

CREATE OR REPLACE FUNCTION "DETECT_CIRC_RECT_COLLISION"
(
    circleCenterLat     IN NUMBER,      -- circle Center Latitude
    circleCenterLon     IN NUMBER,      -- circle Center Longitude
    circleRadius        IN NUMBER,      -- circle Radius in KM
    rectSWLat           IN NUMBER,      -- rectangle South West Latitude
    rectSWLon           IN NUMBER,      -- rectangle South West Longitude
    rectNELat           IN NUMBER,      -- rectangle North Est Latitude
    rectNELon           IN NUMBER       -- rectangle North Est Longitude
)
RETURN NUMBER
AS
    -- converts km to degrees (use 69 if miles)
    kmToDegreeConst     NUMBER := 111.045;

    -- Remaining rectangle vertices 
    rectNWLat   NUMBER;
    rectNWLon   NUMBER;
    rectSELat   NUMBER;
    rectSELon   NUMBER;

    rectHeight  NUMBER;
    rectWIdth   NUMBER;

    circleDistanceLat   NUMBER;
    circleDistanceLon   NUMBER;
    cornerDistanceSQ    NUMBER;

BEGIN
    -- Initialization of remaining rectangle vertices  
    rectNWLat := rectNELat;
    rectNWLon := rectSWLon;
    rectSELat := rectSWLat;
    rectSELon := rectNELon;

    -- Rectangle sides length calculation
    rectHeight := calc_distance(rectSWLat, rectSWLon, rectNWLat, rectNWLon);
    rectWidth := calc_distance(rectSWLat, rectSWLon, rectSELat, rectSELon);

    circleDistanceLat := abs( (circleCenterLat * kmToDegreeConst) - ((rectSWLat * kmToDegreeConst) + (rectHeight/2)) );
    circleDistanceLon := abs( (circleCenterLon * kmToDegreeConst) - ((rectSWLon * kmToDegreeConst) + (rectWidth/2)) );

    IF circleDistanceLon > ((rectWidth/2) + circleRadius) THEN
        RETURN -1;   --  -1 => NO Collision ; 0 => Collision Detected
    END IF;

    IF circleDistanceLat > ((rectHeight/2) + circleRadius) THEN
        RETURN -1;   --  -1 => NO Collision ; 0 => Collision Detected
    END IF;

    IF circleDistanceLon <= (rectWidth/2) THEN
        RETURN 0;   --  -1 => NO Collision ; 0 => Collision Detected
    END IF;

    IF circleDistanceLat <= (rectHeight/2) THEN
        RETURN 0;   --  -1 => NO Collision ; 0 => Collision Detected
    END IF;


    cornerDistanceSQ := POWER(circleDistanceLon - (rectWidth/2), 2) + POWER(circleDistanceLat - (rectHeight/2), 2);

    IF cornerDistanceSQ <=  POWER(circleRadius, 2) THEN
        RETURN 0;  --  -1 => NO Collision ; 0 => Collision Detected
    ELSE
        RETURN -1;  --  -1 => NO Collision ; 0 => Collision Detected
    END IF;

    RETURN -1;  --  -1 => NO Collision ; 0 => Collision Detected
END;    

0

Funziona, l'ho appena scoperto una settimana fa e ora ho avuto modo di provarlo.

double theta = Math.atan2(cir.getX()-sqr.getX()*1.0,
                          cir.getY()-sqr.getY()*1.0); //radians of the angle
double dBox; //distance from box to edge of box in direction of the circle

if((theta >  Math.PI/4 && theta <  3*Math.PI / 4) ||
   (theta < -Math.PI/4 && theta > -3*Math.PI / 4)) {
    dBox = sqr.getS() / (2*Math.sin(theta));
} else {
    dBox = sqr.getS() / (2*Math.cos(theta));
}
boolean touching = (Math.abs(dBox) >=
                    Math.sqrt(Math.pow(sqr.getX()-cir.getX(), 2) +
                              Math.pow(sqr.getY()-cir.getY(), 2)));

Potrebbe funzionare per Circle-Square, ma la domanda riguarda Circle-Rectangle.
martineau,

0
def colision(rect, circle):
dx = rect.x - circle.x
dy = rect.y - circle.y
distance = (dy**2 + dx**2)**0.5
angle_to = (rect.angle + math.atan2(dx, dy)/3.1415*180.0) % 360
if((angle_to>135 and angle_to<225) or (angle_to>0 and angle_to<45) or (angle_to>315 and angle_to<360)):
    if distance <= circle.rad/2.+((rect.height/2.0)*(1.+0.5*abs(math.sin(angle_to*math.pi/180.)))):
        return True
else:
    if distance <= circle.rad/2.+((rect.width/2.0)*(1.+0.5*abs(math.cos(angle_to*math.pi/180.)))):
        return True
return False

-2

Supponendo che i quattro bordi del rettangolo controllino la distanza dai bordi al centro del cerchio, se è inferiore al raggio, le forme si intersecano.

if sqrt((rectangleRight.x - circleCenter.x)^2 +
        (rectangleBottom.y - circleCenter.y)^2) < radius
// then they intersect

if sqrt((rectangleRight.x - circleCenter.x)^2 +
        (rectangleTop.y - circleCenter.y)^2) < radius
// then they intersect

if sqrt((rectangleLeft.x - circleCenter.x)^2 +
        (rectangleTop.y - circleCenter.y)^2) < radius
// then they intersect

if sqrt((rectangleLeft.x - circleCenter.x)^2 +
        (rectangleBottom.y - circleCenter.y)^2) < radius
// then they intersect

Che dire del caso in cui un piccolo cerchio è interamente racchiuso da un grande rettangolo? Sicuramente si tratta di un incrocio e fallirebbe il test in questa risposta.
Ken Paul,

Ah sì, non ci ho pensato. Potresti semplicemente aggiungere più controlli come se sqrt ((rectangleRight.x / 2 - circleCenter.x) ^ 2 + (rectangleBottom.y / 2 - circleCenter.y) ^ 2) <raggio poi si intersecano Questo sarà lungo e lento, ma dalla parte superiore della mia testa è il meglio che posso inventare.
ForYourOwnGood

Possono intersecarsi su qualsiasi punto [singolo] su uno dei bordi. Dovresti trovare anche le distanze dal centro del bordo. (Oh, e chiama i tuoi angoli "angoli" :)
aib

Questo sembra rilevare solo quando un angolo è all'interno del cerchio.
rigido

-2

Se il rettangolo si interseca con il cerchio, uno o più punti d'angolo del rettangolo dovrebbero trovarsi all'interno del cerchio. Supponiamo che i quattro punti di un rettangolo siano A, B, C, D. almeno uno di loro dovrebbe intersecare il cerchio. quindi se la distanza da un punto al centro del cerchio è inferiore al raggio del cerchio, dovrebbe intersecare il cerchio. Per ottenere la distanza puoi usare il teorema di Pitagora,

H^2 = A^2 + B^2

Questa tecnica ha alcuni limiti. Funzionerà meglio per gli sviluppatori di giochi. in particolare il rilevamento delle collisioni

È un buon aggiornamento all'algoritmo di Arvo


Questa risposta è incredibilmente sbagliata ogni volta che il rettangolo ha un lato più grande del raggio del cerchio.
Paul K
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.