web-dev-qa-db-fra.com

Algorithme pour détecter l'intersection de deux rectangles?

Je cherche un algorithme pour détecter si deux rectangles se croisent (l'un avec un angle arbitraire, l'autre avec uniquement des lignes verticales/horizontales).

Tester si un coin est dans l’autre fonctionne presque. Il échoue si les rectangles forment une forme en croix.

Il semble judicieux d’éviter d’utiliser les pentes des lignes, ce qui exigerait des cas particuliers pour les lignes verticales.

136
user20493

La méthode standard serait de faire le test d'axe de séparation (faire une recherche google sur cela).

En bref:

  • Deux objets ne se croisent pas si vous pouvez trouver une ligne séparant les deux objets. par exemple. les objets/tous les points d'un objet sont situés sur des côtés différents de la ligne.

Ce qui est amusant, c’est qu’il suffit de vérifier toutes les arêtes des deux rectangles. Si les rectangles ne se chevauchent pas, l’un des bords sera l’axe de séparation.

En 2D, vous pouvez le faire sans utiliser de pentes. Un bord est simplement défini comme la différence entre deux sommets, par ex.

  Edge = v(n) - v(n-1)

Vous pouvez obtenir une perpendiculaire à cela en le faisant pivoter de 90 °. En 2D, c'est facile comme:

  rotated.x = -unrotated.y
  rotated.y =  unrotated.x

Donc pas de trigonométrie ni de pentes impliquées. La normalisation du vecteur en unités de longueur n'est pas nécessaire non plus.

Si vous voulez tester si un point se trouve d'un côté ou de l'autre de la ligne, vous pouvez simplement utiliser le produit scalaire. le panneau vous indiquera de quel côté vous êtes:

  // rotated: your rotated Edge
  // v(n-1) any point from the Edge.
  // testpoint: the point you want to find out which side it's on.

  side = sign (rotated.x * (testpoint.x - v(n-1).x) + 
               rotated.y * (testpoint.y - v(n-1).y);

Testez maintenant tous les points du rectangle A contre les bords du rectangle B et inversement. Si vous trouvez un bord de séparation, les objets ne se croisent pas (à condition que tous les autres points de B soient de l’autre côté du bord à tester - voir dessin ci-dessous). Si vous ne trouvez aucun bord de séparation, les rectangles se coupent ou un rectangle est contenu dans l'autre.

Le test fonctionne avec tous les polygones convexes en fait. 

Amendement: Pour identifier un bord séparateur, il ne suffit pas de tester tous les points d'un rectangle contre chaque bord de l'autre. L'arête candidate E (ci-dessous) serait en tant que telle identifiée comme une arête de séparation, car tous les points de A se trouvent dans le même demi-plan de E. Toutefois, il ne s'agit pas d'une arête de séparation car les sommets Vb1 et Vb2 de B sont également dans ce demi-plan. Cela n'aurait été un bord séparateur que si cela n'avait pas été le cas http://www.iassess.com/collision.png

151
Nils Pipenbrinck

Fondamentalement, regardez l'image suivante: 


 

Si les deux boîtes se rencontrent, les lignes A et B se chevaucheront.

Notez que cela devra être fait à la fois sur les axes X et Y, et que les deux doivent se chevaucher pour que les rectangles entrent en collision.

Il existe un bon article dans gamasutra.com qui répond à la question (la photo provient de l'article) . J'ai utilisé un algorithme similaire il y a 5 ans et je dois trouver mon extrait de code pour pouvoir le poster ici plus tard.

Amendement: le théorème des axes de séparation stipule que deux formes convexes ne pas se chevauchent s'il existe un axe de séparation (c'est-à-dire, où les projections illustrées ne pas se recouvrent). Donc "Un axe de séparation existe" => "Pas de chevauchement". Ce n'est pas une double implication, vous ne pouvez donc pas conclure l'inverse. 

15
m_pGladiator

Dans Cocoa, vous pouvez facilement détecter si le champ selectedArea rectifie le cadre rect .. rotation de votre NSView pivoté. Vous n'avez même pas besoin de calculer des polygones, des normales, etc. Ajoutez simplement ces méthodes à votre sous-classe NSView . Par exemple, l'utilisateur sélectionne une zone sur la vue d'ensemble de NSView, puis vous appelez la méthode DoesThisRectSelectMe en passant l'objet selectedArea rect L'API convertRect: fera ce travail. La même astuce fonctionne lorsque vous cliquez sur NSView pour le sélectionner. Dans ce cas, substituez simplement la méthode hitTest comme ci-dessous. L'API convertPoint: fera ce travail ;-)

- (BOOL)DoesThisRectSelectMe:(NSRect)selectedArea
{
    NSRect localArea = [self convertRect:selectedArea fromView:self.superview];

    return NSIntersectsRect(localArea, self.bounds);
}


- (NSView *)hitTest:(NSPoint)aPoint
{
    NSPoint localPoint = [self convertPoint:aPoint fromView:self.superview];
    return NSPointInRect(localPoint, self.bounds) ? self : nil;
}
4
Leonardo

la réponse de m_pGladiator est correcte et je la préfère. .test de séparation des axes} est la méthode la plus simple et standard pour détecter le chevauchement des rectangles. Une ligne pour laquelle les intervalles de projection ne se chevauchent pas est appelée un axe séparateur. La solution de Nils Pipenbrinck est trop générale. Il utilise produit scalaire _ pour vérifier si une forme est totalement d'un côté du bord de l'autre. Cette solution est en réalité susceptible d'induire des polygones convexes à n-bord. Cependant, il n'est pas optimisé pour deux rectangles.

le point critique de la réponse de m_pGladiator est qu'il faut vérifier la projection de deux rectangles sur les deux axes (x et y). Si deux projections se chevauchent, on pourrait alors dire que ces deux rectangles se chevauchent. Donc, les commentaires ci-dessus à la réponse de m_pGladiator sont faux.

pour la situation simple, si deux rectangles ne sont pas pivotés, nous présentons un rectangle avec structure:

struct Rect {
    x, // the center in x axis
    y, // the center in y axis
    width,
    height
}

nous nommons rectangle A, B avec rectA, rectB.

    if Math.abs(rectA.x - rectB.x) < (Math.abs(rectA.width + rectB.width) / 2) 
&& (Math.abs(rectA.y - rectB.y) < (Math.abs(rectA.height + rectB.height) / 2))
    then
        // A and B collide
    end if

si un des deux rectangles est pivoté, .__, des efforts peuvent être nécessaires pour déterminer leur projection sur les axes x et y. Définissez struct RotatedRect comme suit:

struct RotatedRect : Rect {
    double angle; // the rotating angle oriented to its center
}

la différence est que la largeur 'est maintenant un peu différente: widthA' pour rectA: Math.sqrt(rectA.width*rectA.width + rectA.height*rectA.height) * Math.cos(rectA.angle) widthB 'pour rectB: Math.sqrt(rectB.width*rectB.width + rectB.height*rectB.height) * Math.cos(rectB.angle)

    if Math.abs(rectA.x - rectB.x) < (Math.abs(widthA' + widthB') / 2) 
&& (Math.abs(rectA.y - rectB.y) < (Math.abs(heightA' + heightB') / 2))
    then
        // A and B collide
    end if

Pourrait faire référence à une conférence GDC (Game Development Conference 2007) PPT www.realtimecollisiondetection.net/pubs/GDC07_Ericson_Physics_Tutorial_SAT.ppt

3
tristan

Vérifiez si l'une des lignes d'un rectangle intersecte l'une des lignes de l'autre. L'intersection de segment de ligne naïve est facile à coder.

Si vous avez besoin de plus de vitesse, il existe des algorithmes avancés pour l'intersection de segment de ligne (ligne de balayage). Voir http://en.wikipedia.org/wiki/Line_segment_intersection

2
Louis Brandy

Une solution consiste à utiliser un polygone non ajusté. Ce polygone est calculé à partir des deux polygones (conceptuellement en les faisant glisser l’un sur l’autre) et définit la zone pour laquelle les polygones se chevauchent compte tenu de leur décalage relatif. Une fois que vous avez ce NFP, vous devez simplement faire un test d'inclusion avec un point donné par le décalage relatif des deux polygones. Ce test d'inclusion est rapide et facile, mais vous devez d'abord créer le NFP.

Effectuez une recherche sur le polygone Aucun ajustement sur le Web et voyez si vous pouvez trouver un algorithme pour les polygones convexes (cela devient BEAUCOUP plus complexe si vous avez des polygones concaves). Si vous ne trouvez rien, alors écrivez-moi à howard point j dot peut gmail point com

2
Howard May

Voici ce qui, selon moi, s’occupera de tous les cas possibles .. Faites les tests suivants. 

  1. Cochez l'un des sommets du rectangle 1 dans le rectangle 2 et inversement. Chaque fois que vous trouvez un sommet qui réside dans l'autre rectangle, vous pouvez en conclure qu'il se croise et arrête la recherche. Cela prendra soin d'un rectangle résidant complètement à l'intérieur de l'autre.
  2. Si le test ci-dessus n'est pas concluant, trouvez les points d'intersection de chaque ligne d'un rectangle avec chaque ligne de l'autre rectangle. Une fois qu'un point d'intersection est trouvé, vérifiez s'il se trouve entre le rectangle imaginaire créé par les 4 points correspondants. Chaque fois qu'un tel point est trouvé, concluez qu'il se croisent et arrêtent la recherche.

Si les 2 tests ci-dessus retournent faux, alors ces 2 rectangles ne se chevauchent pas.

1
John Smith

Si vous utilisez Java, toutes les implémentations de l'interface Shape ont une méthode intersects qui prend un rectangle. 

0
Brendan Cashman

Soit je manque quelque chose d'autre, pourquoi rendre cela si compliqué?

si (x1, y1) et (X1, Y1) sont des coins des rectangles, alors pour trouver une intersection, faites:

    xIntersect = false;
    yIntersect = false;
    if (!(Math.min(x1, x2, x3, x4) > Math.max(X1, X2, X3, X4) || Math.max(x1, x2, x3, x4) < Math.min(X1, X2, X3, X4))) xIntersect = true;
    if (!(Math.min(y1, y2, y3, y4) > Math.max(Y1, Y2, Y3, Y4) || Math.max(y1, y2, y3, y4) < Math.min(Y1, Y2, Y3, Y4))) yIntersect = true;
    if (xIntersect && yIntersect) {alert("Intersect");}
0
user1517108

Eh bien, la méthode de la force brute consiste à parcourir les bords du rectangle horizontal et à vérifier chaque point le long du bord pour voir s’il tombe sur ou dans l’autre rectangle.

La réponse mathématique consiste à former des équations décrivant chaque bord des deux rectangles. Maintenant, vous pouvez simplement rechercher si l’une des quatre lignes du rectangle A intersecte l’une quelconque des lignes du rectangle B, ce qui devrait être un simple résolveur d’équations linéaires (rapide).

-Adam

0
Adam Davis

Vous pouvez trouver l'intersection de chaque côté du rectangle coudé avec chaque côté du rectangle aligné. Pour ce faire, recherchez l’équation de la ligne infinie sur laquelle se trouve chaque côté (c’est-à-dire v1 + t(v2-v1) et v'1 + t '(v'2-v'1)), en recherchant le point où les lignes se rejoignent en résolvant pour t lorsque ces deux équations sont égales (si elles sont parallèles, vous pouvez le tester) et en vérifiant ensuite si ce point se situe sur le segment de droite entre les deux sommets, c’est-à-dire que 0 <= t <= 1 et 0 <= t '<= 1.

Cependant, cela ne couvre pas le cas où un rectangle recouvre complètement l'autre. Que vous pouvez couvrir en vérifiant si les quatre points de l’un ou l’autre rectangle sont situés à l’intérieur de l’autre rectangle. 

0
HenryR

Voici ce que je ferais, pour la version 3D de ce problème:

Modélisez les 2 rectangles comme des plans décrits par les équations P1 et P2, écrivez ensuite P1 = P2 et dérivez-en la droite de l'équation d'intersection, qui n'existera pas si les plans sont parallèles (pas d'intersection) ou dans le même plan, dans ce cas, vous obtenez 0 = 0. Dans ce cas, vous devrez utiliser un algorithme d'intersection de rectangle 2D.

Ensuite, je verrais si cette ligne, qui est dans le plan des deux rectangles, passe à travers les deux rectangles. Si c'est le cas, alors vous avez une intersection de 2 rectangles, sinon vous ne le faites pas (ou ne devriez pas, je pourrais avoir oublié un cas de coin dans ma tête).

Pour trouver si une ligne traverse un rectangle dans le même plan, je trouverais les 2 points d'intersection de la ligne et les côtés du rectangle (en les modélisant à l'aide d'équations de ligne), puis je m'assurais que les points d'intersection sont dans intervalle.

Ce sont les descriptions mathématiques, malheureusement je n'ai pas de code pour faire ce qui précède.

0
freespace

Voici une implémentation matlab de la réponse acceptée:

function olap_flag = ol(A,B,sub)

%A and B should be 4 x 2 matrices containing the xy coordinates of the corners in clockwise order

if nargin == 2
  olap_flag = ol(A,B,1) && ol(B,A,1);
  return;
end

urdl = diff(A([1:4 1],:));
s = sum(urdl .* A, 2);
sdiff = B * urdl' - repmat(s,[1 4]);

olap_flag = ~any(max(sdiff)<0);
0
Jed

C'est la méthode conventionnelle, allez ligne par ligne et vérifiez si les lignes se croisent. C'est le code dans MATLAB. 

C1 = [0, 0];    % Centre of rectangle 1 (x,y)
C2 = [1, 1];    % Centre of rectangle 2 (x,y)
W1 = 5; W2 = 3; % Widths of rectangles 1 and 2
H1 = 2; H2 = 3; % Heights of rectangles 1 and 2
% Define the corner points of the rectangles using the above
R1 = [C1(1) + [W1; W1; -W1; -W1]/2, C1(2) + [H1; -H1; -H1; H1]/2];
R2 = [C2(1) + [W2; W2; -W2; -W2]/2, C2(2) + [H2; -H2; -H2; H2]/2];

R1 = [R1 ; R1(1,:)] ;
R2 = [R2 ; R2(1,:)] ;

plot(R1(:,1),R1(:,2),'r')
hold on
plot(R2(:,1),R2(:,2),'b')


%% lines of Rectangles 
L1 = [R1(1:end-1,:) R1(2:end,:)] ;
L2 = [R2(1:end-1,:) R2(2:end,:)] ;
%% GEt intersection points
P = zeros(2,[]) ;
count = 0 ;
for i = 1:4
    line1 = reshape(L1(i,:),2,2) ;
    for j = 1:4
        line2 = reshape(L2(j,:),2,2) ;
        point = InterX(line1,line2) ;
        if ~isempty(point)
            count = count+1 ;
            P(:,count) = point ;
        end
    end
end
%%
if ~isempty(P)
    fprintf('Given rectangles intersect at %d points:\n',size(P,2))
    plot(P(1,:),P(2,:),'*k')
end

la fonction InterX peut être téléchargée depuis: https://in.mathworks.com/matlabcentral/fileexchange/22441-curve-intersections?focused=5165138&tab=function

Une autre façon de réaliser le test, qui est légèrement plus rapide que le test de l’axe de séparation, consiste à utiliser l’algorithme des nombres sinueux (uniquement sur les quadrants - pas sommation horriblement lente) sur chaque sommet de chaque rectangle ( choisi). Si l'un des sommets a un nombre d'enroulement différent de zéro, les deux rectangles se chevauchent.

Cet algorithme est un peu plus long que le test d'axe de séparation, mais il est plus rapide car il ne nécessite qu'un test de demi-plan si les arêtes traversent deux quadrants (au lieu de 32 tests utilisant la méthode des axes de séparation).

L’algorithme présente l’avantage supplémentaire de pouvoir tester le chevauchement de any polygon (convexe ou concave). Autant que je sache, l'algorithme ne fonctionne que dans l'espace 2D.

0
Mads

J'ai une méthode plus simple, si nous avons 2 rectangles:

R1 = (min_x1, max_x1, min_y1, max_y1)

R2 = (min_x2, max_x2, min_y2, max_y2)

Ils se chevauchent si et seulement si:

Chevauchement = (max_x1> min_x2) et (max_x2> min_x1) et (max_y1> min_y2) et (max_y2> min_y1)

Vous pouvez aussi le faire pour les boîtes 3D, cela fonctionne pour un nombre quelconque de dimensions.

0
BitFarmer

On en a assez dit dans d'autres réponses, je vais donc simplement ajouter le pseudocode one-liner:

!(a.left > b.right || b.left > a.right || a.top > b.bottom || b.top > a.bottom);
0
Przemek

Je l'ai implémenté comme ceci:

bool rectCollision(const CGRect &boundsA, const Matrix3x3 &mB, const CGRect &boundsB)
{
    float Axmin = boundsA.Origin.x;
    float Axmax = Axmin + boundsA.size.width;
    float Aymin = boundsA.Origin.y;
    float Aymax = Aymin + boundsA.size.height;

    float Bxmin = boundsB.Origin.x;
    float Bxmax = Bxmin + boundsB.size.width;
    float Bymin = boundsB.Origin.y;
    float Bymax = Bymin + boundsB.size.height;

    // find location of B corners in A space
    float B0x = mB(0,0) * Bxmin + mB(0,1) * Bymin + mB(0,2);
    float B0y = mB(1,0) * Bxmin + mB(1,1) * Bymin + mB(1,2);

    float B1x = mB(0,0) * Bxmax + mB(0,1) * Bymin + mB(0,2);
    float B1y = mB(1,0) * Bxmax + mB(1,1) * Bymin + mB(1,2);

    float B2x = mB(0,0) * Bxmin + mB(0,1) * Bymax + mB(0,2);
    float B2y = mB(1,0) * Bxmin + mB(1,1) * Bymax + mB(1,2);

    float B3x = mB(0,0) * Bxmax + mB(0,1) * Bymax + mB(0,2);
    float B3y = mB(1,0) * Bxmax + mB(1,1) * Bymax + mB(1,2);

    if(B0x<Axmin && B1x<Axmin && B2x<Axmin && B3x<Axmin)
        return false;
    if(B0x>Axmax && B1x>Axmax && B2x>Axmax && B3x>Axmax)
        return false;
    if(B0y<Aymin && B1y<Aymin && B2y<Aymin && B3y<Aymin)
        return false;
    if(B0y>Aymax && B1y>Aymax && B2y>Aymax && B3y>Aymax)
        return false;

    float det = mB(0,0)*mB(1,1) - mB(0,1)*mB(1,0);
    float dx = mB(1,2)*mB(0,1) - mB(0,2)*mB(1,1);
    float dy = mB(0,2)*mB(1,0) - mB(1,2)*mB(0,0);

    // find location of A corners in B space
    float A0x = (mB(1,1) * Axmin - mB(0,1) * Aymin + dx)/det;
    float A0y = (-mB(1,0) * Axmin + mB(0,0) * Aymin + dy)/det;

    float A1x = (mB(1,1) * Axmax - mB(0,1) * Aymin + dx)/det;
    float A1y = (-mB(1,0) * Axmax + mB(0,0) * Aymin + dy)/det;

    float A2x = (mB(1,1) * Axmin - mB(0,1) * Aymax + dx)/det;
    float A2y = (-mB(1,0) * Axmin + mB(0,0) * Aymax + dy)/det;

    float A3x = (mB(1,1) * Axmax - mB(0,1) * Aymax + dx)/det;
    float A3y = (-mB(1,0) * Axmax + mB(0,0) * Aymax + dy)/det;

    if(A0x<Bxmin && A1x<Bxmin && A2x<Bxmin && A3x<Bxmin)
        return false;
    if(A0x>Bxmax && A1x>Bxmax && A2x>Bxmax && A3x>Bxmax)
        return false;
    if(A0y<Bymin && A1y<Bymin && A2y<Bymin && A3y<Bymin)
        return false;
    if(A0y>Bymax && A1y>Bymax && A2y>Bymax && A3y>Bymax)
        return false;

    return true;
}

La matrice mB est une matrice de transformation affine qui convertit des points de l'espace B en points de l'espace A. Cela inclut la rotation et la translation simples, la rotation plus la mise à l'échelle et les conversions affines complètes, mais pas les conversions en perspective.

Ce n'est peut-être pas aussi optimal que possible. La vitesse n'était pas une préoccupation majeure. Cependant, cela semble bien fonctionner pour moi.

0
Robotbugs