Kreis-Rechteck-Kollisionserkennung (Kreuzung)


192

Wie kann ich feststellen, ob sich ein Kreis und ein Rechteck im euklidischen 2D-Raum schneiden? (dh klassische 2D-Geometrie)


1
Ist das Rechteck immer mit den Achsen ausgerichtet oder kann es um einen beliebigen Winkel gedreht werden?
e.James

11
@eJames: Wie ist das wichtig? Sie überprüfen das Rechteck auf Schnittpunkte mit einem Kreis . Sie können Ihr Koordinatensystem immer so transformieren, dass das Rechteck ohne Änderung des Kreises
achsparallel ist

Sie sollten das als Antwort hinzufügen und sich durch -Θ und alles drehen ...
aib

2
@ShreevatsaR: Es ist wichtig, ob ich mich um diese Koordinatenübersetzung kümmern muss oder nicht. @aib: Oh je!
e.James

Antworten:


191

Es gibt nur zwei Fälle, in denen sich der Kreis mit dem Rechteck schneidet:

  • Entweder liegt der Mittelpunkt des Kreises innerhalb des Rechtecks ​​oder
  • Eine der Kanten des Rechtecks ​​hat einen Punkt im Kreis.

Beachten Sie, dass das Rechteck hierfür nicht achsparallel sein muss.

Einige verschiedene Arten, wie sich ein Kreis und ein Rechteck schneiden können

(Eine Möglichkeit, dies zu sehen: Wenn keine der Kanten einen Punkt im Kreis hat (wenn alle Kanten vollständig "außerhalb" des Kreises liegen), kann der Kreis das Polygon nur dann noch schneiden, wenn er vollständig innerhalb des Kreises liegt Polygon.)

Mit dieser Einsicht wird etwa wie folgt arbeiten, wo die Kreismitte hat Pund Radius R, und das Rechteck hat Ecken A, B, C, Din dieser Reihenfolge (nicht vollständiger Code):

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)))

Wenn Sie eine Geometrie schreiben, haben Sie wahrscheinlich bereits die oben genannten Funktionen in Ihrer Bibliothek. Andernfalls pointInRectangle()kann auf verschiedene Arten implementiert werden; Jeder der allgemeinen Punkte in Polygonmethoden funktioniert, aber für ein Rechteck können Sie einfach überprüfen, ob dies funktioniert:

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

Und intersectCircle()ist auch einfach zu implementieren: Eine Möglichkeit wäre zu prüfen, ob der Fuß der Senkrechten vonP zur Linie nahe genug und zwischen den Endpunkten liegt, und die Endpunkte ansonsten zu überprüfen.

Das Coole ist, dass die gleiche Idee nicht nur für Rechtecke funktioniert, sondern auch für den Schnittpunkt eines Kreises mit einem einfachen Polygon - muss nicht einmal konvex sein!


25
Für das, was es wert ist, denke ich wirklich, dass diese Antwort besser ist als meine. Zwei Hauptgründe: 1: Es ist keine Drehung erforderlich, wenn das Rechteck nicht achsparallel ist, und 2: Das Konzept erstreckt sich problemlos auf alle Polygone.
e.James

2
@paniq: Nun, beide sind zeitkonstant. :-) Aber ja, dies ist nützlicher als eine allgemeine Lösung, die Rechtecke mit beliebiger Ausrichtung und in der Tat mit jedem einfachen Polygon abdeckt.
ShreevatsaR

7
Was ist mit dem Fall, in dem sich das Rechteck vollständig innerhalb des Kreises befindet, der Mittelpunkt des Kreises jedoch nicht innerhalb des Rechtecks ​​liegt?
ericsoco

2
@ericsoco: Gute Beobachtung. :-) Ich denke, ich hätte sagen sollen "schneidet die Scheibe" in "einer der Kanten des Rechtecks ​​schneidet den Kreis", weil ich damit gemeint habe, dass er einen Punkt mit dem Kreis selbst teilt, nicht unbedingt die Grenze des Kreises. Die obige Beschreibung "Überprüfen Sie, ob der Fuß der Senkrechten von P [dem Mittelpunkt des Kreises] zur Linie nahe genug und zwischen den Endpunkten liegt, und überprüfen Sie die Endpunkte ansonsten" funktioniert weiterhin - z. B. liegen die Endpunkte innerhalb des Kreises ( Rabatt).
ShreevatsaR

2
@ DexD.Hunter Wenn der Mittelpunkt des Kreises außerhalb des Rechtecks ​​liegt, ein Teil davon jedoch innerhalb des Rechtecks ​​liegt, schneidet notwendigerweise eine der Kanten des Rechtecks ​​den Kreis.
ShreevatsaR

289

So würde ich es machen:

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));
}

So funktioniert das:

Illusration

  1. Das erste Linienpaar berechnet die absoluten Werte der x- und y-Differenz zwischen dem Mittelpunkt des Kreises und dem Mittelpunkt des Rechtecks. Dadurch werden die vier Quadranten zu einem zusammengefasst, sodass die Berechnungen nicht viermal durchgeführt werden müssen. Das Bild zeigt den Bereich, in dem der Mittelpunkt des Kreises liegen muss. Beachten Sie, dass nur der einzelne Quadrant angezeigt wird. Das Rechteck ist der graue Bereich, und der rote Rand umreißt den kritischen Bereich, der genau einen Radius von den Rändern des Rechtecks ​​entfernt ist. Der Mittelpunkt des Kreises muss innerhalb dieses roten Randes liegen, damit der Schnittpunkt auftritt.

  2. Das zweite Linienpaar eliminiert die einfachen Fälle, in denen der Kreis weit genug vom Rechteck entfernt ist (in beide Richtungen), dass kein Schnitt möglich ist. Dies entspricht dem grünen Bereich im Bild.

  3. Das dritte Linienpaar behandelt die einfachen Fälle, in denen der Kreis nahe genug am Rechteck (in beide Richtungen) liegt, sodass ein Schnittpunkt garantiert ist. Dies entspricht den orangefarbenen und grauen Abschnitten im Bild. Beachten Sie, dass dieser Schritt nach Schritt 2 ausgeführt werden muss, damit die Logik sinnvoll ist.

  4. Die verbleibenden Linien berechnen den schwierigen Fall, in dem der Kreis die Ecke des Rechtecks ​​schneiden kann. Berechnen Sie zum Lösen den Abstand vom Mittelpunkt des Kreises und der Ecke und stellen Sie dann sicher, dass der Abstand nicht größer als der Radius des Kreises ist. Diese Berechnung gibt false für alle Kreise zurück, deren Mittelpunkt innerhalb des rot schattierten Bereichs liegt, und gibt true für alle Kreise zurück, deren Mittelpunkt innerhalb des weiß schattierten Bereichs liegt.


4
Sehr schön! Anmerkungen: Anscheinend befindet sich rect.x / y hier in der oberen rechten Ecke des Rechtecks. Sie können auch die teure Quadratwurzel beseitigen, indem Sie sie stattdessen mit dem Quadrat des Radius vergleichen.
Luqui

2
Oh nein, mein schlechtes. rect.x / y befindet sich unten links im Rechteck. Ich hätte geschrieben: circleDistance.x = abs (circle.x - (rect.x + rect.width / 2));
Luqui

2
@Tanner: Los geht's. Hurra für Backups und OCD;)
e.James

11
Nur zur Verdeutlichung - diese Antwort gilt nur für achsenausgerichtete Rechtecke. Das geht aus dem Lesen von Kommentaren zu anderen Antworten hervor, ist aber aus dieser Antwort + Kommentaren allein nicht ersichtlich. (Gute Antwort für achsenausgerichtete Rechte!)
ericsoco

3
Toll! Für die Leser ist es wichtig zu wissen, dass hier meiner Meinung nach die Definition eines Rect Rect.x ist und Rect.y das Zentrum des Rect ist . In meiner Welt ist das xy eines Rect oben / links vom Rect und 0,0 ist oben / links vom Bildschirm, also habe ich verwendet:circleDistance_x = abs(circle.x - (rect.x-rect.w/2)); circleDistance_y = abs(circle.y - (rect.y-rect.h/2));
erco

123

Hier ist eine andere Lösung, die ziemlich einfach zu implementieren ist (und auch ziemlich schnell). Es werden alle Schnittpunkte erfasst, auch wenn die Kugel das Rechteck vollständig betreten hat.

// 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);

Mit jeder anständigen Mathematikbibliothek kann dies auf 3 oder 4 Zeilen gekürzt werden.


3
Sie haben einen Fehler darin, Sie suchen nach am nächsten mit Links und Rechts, nicht oben und unten, sonst schöne Lösung.
Manveru

8
Diese Antwort gefällt mir am besten. Es ist kurz, leicht zu verstehen und schnell.
John Kurlak

2
Ich denke, dass Ihre Lösung fehlschlägt, wenn das Rechteck schräg zur x- und y-Achse ist.
Leo

3
@Leo Ich denke, es ist nicht schwer, diesen Algorithmus zu modifizieren, um diesen Fall zu berücksichtigen. Man sollte einfach eine Koordinatentransformation anwenden, bei der der Ursprung in der Mitte des Rechtecks ​​liegt und das Rechteck nicht mehr schräg ist. Sie müssen die Transformation nur auf den Kreismittelpunkt anwenden.
Enobayram

1
Dies entspricht im Wesentlichen dem Code unter migapro.com/circle-and-rotated-rectangle-collision-detection, den ich auch auf Objective-C portiert habe. Funktioniert sehr gut; Es ist eine schöne Lösung für das Problem.
PKCLsoft

10

Ihre Kugel und Ihr Rechteck schneiden sich IIF ist
der Abstand zwischen dem Kreismittelpunkt und einem Scheitelpunkt Ihres Rechtecks ​​kleiner als der Radius Ihrer Kugel
ODER
der Abstand zwischen dem Kreismittelpunkt und einer Kante Ihres Rechtecks ​​ist kleiner als der Radius Ihrer Kugel ( [ Punkt-Linie-Abstand ])
ODER
der Kreismittelpunkt liegt innerhalb des Rechtecks

Punkt-Punkt-Abstandes:

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

Punkt-Linie-Abstand:

L1 = [x1, y1], L2 = [x2, y2] (zwei Punkte Ihrer Linie, dh die Scheitelpunkte)
P1 = [px, py] irgendwann

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


Kreismittelpunkt innerhalb von Rect:
Nehmen Sie eine Trennachse: Wenn eine Projektion auf eine Linie vorhanden ist, die das Rechteck vom Punkt trennt, schneiden sie sich nicht

Sie projizieren den Punkt auf Linien parallel zu den Seiten Ihres Rect und können dann leicht feststellen, ob sie sich schneiden. Wenn sie sich nicht auf allen 4 Projektionen schneiden, können sie (der Punkt und das Rechteck) nicht schneiden.

Sie brauchen nur das innere Produkt (x = [x1, x2], y = [y1, y2], x * y = x1 * y1 + x2 * y2)

Ihr Test würde so aussehen:

// Rechteckkanten: TL (oben links), TR (oben rechts), BL (unten links), BR (unten rechts)
// Testpunkt: POI

getrennt = falsch
für egde in {{TL, TR}, {BL, BR}, {TL, BL}, {TR-BR}}: // die Kanten
    D = Kante [0] - Kante [1]
    innerProd = D * POI
    Interval_min = min (D * Kante [0], D * Kante [1])
    Interval_max = max (D * Kante [0], D * Kante [1])
    wenn nicht (Interval_min ≤ innerProd ≤ Interval_max) 
           getrennt = wahr
           break // end for loop 
    ende wenn
Ende für
if (getrennt ist wahr)    
      Rückgabe "keine Kreuzung"
sonst 
      Rückgabe "Kreuzung"
ende wenn

Dies setzt kein achsenausgerichtetes Rechteck voraus und ist zum Testen von Schnittpunkten zwischen konvexen Mengen leicht erweiterbar.


1
Sollte der Punkt-zu-Punkt-Abstand nicht ein Quadrat verwenden, keine Bauchmuskeln?
Thomas

6

Dies ist die schnellste Lösung:

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;
}

Beachten Sie die Ausführungsreihenfolge, und die Hälfte der Breite / Höhe ist vorberechnet. Auch das Quadrieren erfolgt "manuell", um einige Taktzyklen zu sparen.


3
Ich glaube nicht, dass Sie behaupten können, dass fünf Tests / Vergleiche im teuersten Codepfad die „schnellste Lösung“ ohne Beweise sind.
Sam Hocevar


1
Nach meiner Erfahrung mit dieser Methode kommt es meistens nicht zu Kollisionen. Daher führen die Tests zu einem Exit, bevor der größte Teil des Codes ausgeführt wird.
Intrepidis

6

Die einfachste Lösung, die ich gefunden habe, ist ziemlich einfach.

Es funktioniert, indem der Punkt im Rechteck gefunden wird, der dem Kreis am nächsten liegt, und dann der Abstand verglichen wird.

All dies können Sie mit wenigen Vorgängen tun und sogar die Funktion sqrt vermeiden.

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;
}

Und das ist es! Die obige Lösung geht von einem Ursprung oben links auf der Welt aus, wobei die x-Achse nach unten zeigt.

Wenn Sie eine Lösung für die Behandlung von Kollisionen zwischen einem sich bewegenden Kreis und einem Rechteck suchen, ist dies weitaus komplizierter und wird in einer anderen Antwort von mir behandelt.


Dadurch werden Schnittpunkte nicht erkannt, wenn der Kreisradius zu klein ist und sein Mittelpunkt innerhalb des Rechtecks ​​liegt!
Yoav

2
Können Sie tatsächliche Eingaben machen, die dazu führen, dass dies fehlschlägt? Wenn sich der Kreis innerhalb befindet, beträgt der linke Teil des Tests 0,0. Sofern der Radius nicht Null ist, sollte der rechte Teil des Tests> 0,0 sein
ClickerMonkey

Funktioniert dies auch für gedrehte Rechtecke? wenn nicht, dann gib mir bitte einen Hinweis dazu .....
M Abdul Sami

4

Eigentlich ist das viel einfacher. Sie brauchen nur zwei Dinge.

Zunächst müssen Sie vier orthogonale Abstände vom Kreismittelpunkt zu jeder Linie des Rechtecks ​​ermitteln. Dann schneidet Ihr Kreis das Rechteck nicht, wenn drei davon größer als der Kreisradius sind.

Zweitens müssen Sie den Abstand zwischen dem Kreismittelpunkt und dem Rechteckmittelpunkt ermitteln. Der Kreis befindet sich dann nicht innerhalb des Rechtecks, wenn der Abstand größer als die Hälfte der diagonalen Länge des Rechtecks ​​ist.

Viel Glück!


3

Hier ist mein C-Code zum Auflösen einer Kollision zwischen einer Kugel und einem nicht achsenausgerichteten Feld. Es basiert auf einigen meiner eigenen Bibliotheksroutinen, kann sich aber für einige als nützlich erweisen. Ich benutze es in einem Spiel und es funktioniert perfekt.

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

Nehmen Sie zur Visualisierung den Nummernblock Ihrer Tastatur. Wenn die Taste '5' Ihr Rechteck darstellt, repräsentieren alle Tasten 1-9 die 9 Quadranten des Raums geteilt durch die Linien, aus denen Ihr Rechteck besteht (wobei 5 die Innenseite ist).

1) Wenn der Mittelpunkt des Kreises im Quadranten 5 liegt (dh innerhalb des Rechtecks), schneiden sich die beiden Formen.

Damit gibt es zwei mögliche Fälle: a) Der Kreis schneidet zwei oder mehr benachbarte Kanten des Rechtecks. b) Der Kreis schneidet mit einer Kante des Rechtecks.

Der erste Fall ist einfach. Wenn sich der Kreis mit zwei benachbarten Kanten des Rechtecks ​​schneidet, muss er die Ecke enthalten, die diese beiden Kanten verbindet. (Das oder sein Zentrum liegt in Quadrant 5, den wir bereits behandelt haben. Beachten Sie auch, dass der Fall, in dem sich der Kreis mit nur zwei gegenüberliegenden Kanten des Rechtecks schneidet, ebenfalls abgedeckt ist.)

2) Wenn eine der Ecken A, B, C, D des Rechtecks ​​innerhalb des Kreises liegt, schneiden sich die beiden Formen.

Der zweite Fall ist schwieriger. Wir sollten beachten, dass dies nur passieren kann, wenn der Mittelpunkt des Kreises in einem der Quadranten 2, 4, 6 oder 8 liegt Die entsprechende Ecke ist der nächstgelegene Punkt.)

Jetzt haben wir den Fall, dass der Mittelpunkt des Kreises in einem der 'Kanten'-Quadranten liegt und sich nur mit der entsprechenden Kante schneidet. Dann muss der Punkt an der Kante, der dem Mittelpunkt des Kreises am nächsten liegt, innerhalb des Kreises liegen.

3) Konstruieren Sie für jede Linie AB, BC, CD, DA senkrechte Linien p (AB, P), p (BC, P), p (CD, P), p (DA, P) durch den Mittelpunkt des Kreises P. For Wenn bei jeder senkrechten Linie der Schnittpunkt mit der ursprünglichen Kante innerhalb des Kreises liegt, schneiden sich die beiden Formen.

Für diesen letzten Schritt gibt es eine Verknüpfung. Wenn der Mittelpunkt des Kreises im Quadranten 8 liegt und die Kante AB die Oberkante ist, hat der Schnittpunkt die y-Koordinate von A und B und die x-Koordinate des Mittelpunkts P.

Sie können die vier Linienschnittpunkte konstruieren und prüfen, ob sie an den entsprechenden Kanten liegen, oder herausfinden, in welchem ​​Quadranten P sich befindet, und den entsprechenden Schnittpunkt überprüfen. Beide sollten sich zu derselben booleschen Gleichung vereinfachen. Seien Sie vorsichtig, dass der obige Schritt 2 nicht ausschloss, dass sich P in einem der 'Eck'-Quadranten befindet. es suchte nur nach einer Kreuzung.

Bearbeiten: Wie sich herausstellt, habe ich die einfache Tatsache übersehen, dass # 2 ein Unterfall von # 3 oben ist. Schließlich sind auch Ecken Punkte an den Kanten. Eine gute Erklärung finden Sie in der Antwort von @ ShreevatsaR unten. Vergessen Sie in der Zwischenzeit die Nummer 2 oben, es sei denn, Sie möchten eine schnelle, aber redundante Überprüfung.


2

Diese Funktion erkennt Kollisionen (Schnittpunkte) zwischen Kreis und Rechteck. Er arbeitet in seiner Antwort wie die e.James-Methode, aber diese erkennt Kollisionen für alle Winkel des Rechtecks ​​(nicht nur bis zur oberen Ecke).

HINWEIS:

aRect.origin.x und aRect.origin.y sind Koordinaten des linken unteren Rechteckwinkels!

aCircle.x und aCircle.y sind Koordinaten von Circle Center!

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

Ich habe eine Methode, die die teuren Pythagoras vermeidet, wenn sie nicht notwendig sind - dh. wenn sich Begrenzungsrahmen des Rechtecks ​​und des Kreises nicht schneiden.

Und es wird auch für nicht-euklidische funktionieren:

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 kann durch minY, maxY und das gleiche für minLon, maxLon ersetzt werden: Ersetzen Sie es durch minX, maxX
  • normDist ist nur eine etwas schnellere Methode als die Berechnung der vollen Entfernung. ZB ohne die Quadratwurzel im euklidischen Raum (oder ohne viele andere Dinge für Haversine) : dLat=(lat-circleY); dLon=(lon-circleX); normed=dLat*dLat+dLon*dLon. Wenn Sie diese normDist-Methode verwenden, müssen Sie natürlich eine normedDist = dist*dist;für den Kreis erstellen

Den vollständigen BBox- und Circle- Code meines GraphHopper- Projekts anzeigen .


1

Ich habe eine Klasse für die Arbeit mit Formen geschaffen, die Ihnen Spaß machen

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

Hier funktioniert der modifizierte Code zu 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

Hier ist ein schneller einzeiliger Test dafür:

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

Dies ist der achsenausgerichtete Fall, in dem rect_halvesein positiver Vektor von der Rechteckmitte zu einer Ecke zeigt. Der Ausdruck im Inneren length()ist ein Delta-Vektor von centerzu einem nächstgelegenen Punkt im Rechteck. Dies funktioniert in jeder Dimension.


1
  • Überprüfen Sie zunächst, ob sich das Rechteck und die Tangente an den Kreis überlappen (einfach). Wenn sie sich nicht überlappen, kollidieren sie nicht.
  • Überprüfen Sie, ob der Mittelpunkt des Kreises innerhalb des Rechtecks ​​liegt (einfach). Wenn es drinnen ist, kollidieren sie.
  • Berechnen Sie den minimalen quadratischen Abstand von den Rechteckseiten zum Mittelpunkt des Kreises (etwas hart). Wenn der quadratische Radius niedriger ist, kollidieren sie, sonst nicht.

Es ist effizient, weil:

  • Zuerst wird das häufigste Szenario mit einem billigen Algorithmus überprüft, und wenn sicher ist, dass sie nicht kollidieren, endet es.
  • Anschließend wird das nächsthäufigste Szenario mit einem billigen Algorithmus überprüft (Quadratwurzel nicht berechnen, Quadratwerte verwenden), und wenn sicher ist, dass sie kollidieren, endet es.
  • Dann führt es den teureren Algorithmus aus, um die Kollision mit den Rechteckrändern zu überprüfen.

1

arbeitete für mich (nur arbeiten, wenn der Winkel des Rechtecks ​​180 ist)

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 ... Ich habe das abgestimmt, dann aber richtig getestet und ich denke, es funktioniert zum Beispiel nicht an den Ecken. Es würde für zwei Rechtecke funktionieren.
Dan Zen

1

Die Antwort von e.James ein wenig verbessern:

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);

Dies subtrahiert rect.w / 2und rect.h / 2einmal statt bis zu dreimal.


0

Für diejenigen, die die Kreis- / Rechteckkollision in geografischen Koordinaten mit SQL berechnen müssen, ist
dies meine Implementierung in Oracle 11 des von e.James vorgeschlagenen Algorithmus .

Für die Eingabe sind Kreiskoordinaten, Kreisradius in km und zwei Eckpunktkoordinaten des Rechtecks ​​erforderlich:

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

Funktioniert, habe das erst vor einer Woche herausgefunden und muss es jetzt testen.

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)));

Könnte für Circle-Square funktionieren, aber die Frage betrifft 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

Angenommen, Sie haben die vier Kanten des Rechtecks. Überprüfen Sie den Abstand zwischen den Kanten und dem Mittelpunkt des Kreises. Wenn dieser kleiner als der Radius ist, schneiden sich die Formen.

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

Was ist mit dem Fall, in dem ein kleiner Kreis vollständig von einem großen Rechteck umschlossen ist? Sicherlich ist das eine Kreuzung und würde den Test in dieser Antwort nicht bestehen.
Ken Paul

Ah ja, daran habe ich nicht gedacht. Sie können einfach weitere Überprüfungen hinzufügen, z. B. wenn sqrt ((rechteckRight.x / 2 - circleCenter.x) ^ 2 + (rechteckBottom.y / 2 - circleCenter.y) ^ 2) <Radius, dann schneiden sie sich. Dies ist lang und langsam. Aber auf den ersten Blick ist das das Beste, was ich mir vorstellen kann.
ForYourOwnGood

Sie können sich an jedem [einzelnen] Punkt an einer beliebigen Kante schneiden. Sie sollten auch die Kantenmittenabstände finden. (Oh, und nenne deine Ecken "Ecken" :)
aib

Dies scheint nur zu erkennen, wenn sich eine Ecke innerhalb des Kreises befindet.
stark

-2

Wenn sich das Rechteck mit dem Kreis schneidet, sollten sich ein oder mehrere Eckpunkte des Rechtecks ​​innerhalb des Kreises befinden. Angenommen, die vier Punkte eines Rechtecks ​​sind A, B, C, D. Mindestens einer von ihnen sollte den Kreis schneiden. Wenn also der Abstand von einem Punkt zum Mittelpunkt des Kreises kleiner als der Radius des Kreises ist, sollte er den Kreis schneiden. Um die Entfernung zu ermitteln, können Sie den Satz von Pythagoras verwenden:

H^2 = A^2 + B^2

Diese Technik hat einige Grenzen. Aber es wird besser für die Spieleentwickler funktionieren. insbesondere Kollisionserkennung

Es ist ein gutes Update des Arvo-Algorithmus


Diese Antwort ist jedes Mal unglaublich falsch, wenn das Rechteck eine Seite hat, die größer als der Radius des Kreises ist.
Paul K
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.