Algoritmo para detectar la intersección de dos rectángulos?

143

Estoy buscando un algoritmo para detectar si dos rectángulos se cruzan (uno en un ángulo arbitrario, el otro con solo líneas verticales / horizontales).

Prueba si una esquina de uno está en el otro casi funciona. Falla si los rectángulos forman una forma de cruz.

Parece una buena idea evitar el uso de pendientes de las líneas, lo que requeriría casos especiales para las líneas verticales.

usuario20493
fuente
¿Qué pasa si agrega a su verificación de esquina, una verificación para ver si el segundo rectángulo está dentro de los límites (rectangular) del rectángulo angulado?
Wes P
¿En qué idioma vas a hacer esto? Porque en Java hay clases integradas que te permiten hacer esto.
Martijn
Creo que la API de gráficos y la mayoría de las bibliotecas GUI (como swing) tienen esto implementado.
l_39217_l
que pueden pasar por alto casos en los que se superponen pero no hay esquina dentro de ningún rectángulo
Florian Bösch
1
Esta pregunta es casi la misma que: stackoverflow.com/questions/306316/… . Aunque, esto está buscando una solución que sea particularmente para C ++. La respuesta aceptada también es bastante simple y directa.
Silver Gonzales

Respuestas:

162

El método estándar sería hacer la prueba del eje de separación (hacer una búsqueda en Google sobre eso).

En breve:

  • Dos objetos no se cruzan si puede encontrar una línea que separe los dos objetos. Por ejemplo, los objetos / todos los puntos de un objeto están en diferentes lados de la línea.

Lo divertido es que basta con verificar todos los bordes de los dos rectángulos. Si los rectángulos no se superponen, uno de los bordes será el eje de separación.

En 2D puedes hacer esto sin usar pendientes. Un borde se define simplemente como la diferencia entre dos vértices, p. Ej.

  edge = v(n) - v(n-1)

Puede obtener una perpendicular a esto girándola 90 °. En 2D esto es fácil como:

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

Entonces no hay trigonometría o pendientes involucradas. Tampoco se requiere normalizar el vector a la longitud de la unidad.

Si desea probar si un punto está en uno u otro lado de la línea, puede usar el producto de puntos. el letrero te dirá de qué lado estás:

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

Ahora pruebe todos los puntos del rectángulo A contra los bordes del rectángulo B y viceversa. Si encuentra un borde de separación, los objetos no se cruzan (siempre y cuando todos los demás puntos en B estén en el otro lado del borde que se está probando, vea el dibujo a continuación). Si no encuentra un borde de separación, los rectángulos se cruzan o un rectángulo está contenido en el otro.

La prueba funciona con cualquier polígono convexo por cierto.

Enmienda: para identificar un borde de separación, no es suficiente probar todos los puntos de un rectángulo contra cada borde del otro. El borde candidato E (abajo) se identificaría como un borde de separación, ya que todos los puntos en A están en el mismo semiplano de E. Sin embargo, no es un borde de separación porque los vértices Vb1 y Vb2 de B también están en ese medio plano. Solo habría sido un borde de separación si ese no hubiera sido el caso http://www.iassess.com/collision.png

Nils Pipenbrinck
fuente
2
Este algoritmo no funciona para todos los casos. Es posible colocar el segundo rectángulo rotado 45 grados con respecto al primer rectángulo y desplazarlo a lo largo de la diagonal para que cumpla con las pruebas de intersección anteriores pero no se interseque.
Skizz
66
Skizz, revisa los ocho bordes. Si los objetos no se cruzan, uno de los ocho bordes los separará. ¿Por qué no publicas una imagen que muestre tu caso? Puedo mostrarte el eje ..
Nils Pipenbrinck
2
Mi error, hace frente a esa condición.
Skizz
2
La imagen está muerta ahora (noviembre de 2012)
John Dvorak
2
Tuve muchos problemas para visualizar esto, así que recreé lo que creo que era la imagen a la que se hace referencia. imgur.com/bNwrzsv
Rjdlee
16

Básicamente mira la siguiente imagen:


Si las dos cajas chocan, las líneas A y B se superpondrán.

Tenga en cuenta que esto tendrá que hacerse tanto en el eje X como en el eje Y, y ambos deben superponerse para que los rectángulos colisionen.

Hay un buen artículo en gamasutra.com que responde a la pregunta (la imagen es del artículo). Hice un algoritmo similar hace 5 años y tengo que encontrar mi fragmento de código para publicarlo aquí más tarde

Enmienda : El teorema del eje de separación establece que dos formas convexas no se superponen si existe un eje de separación (es decir, uno donde las proyecciones como se muestra no se superponen). Entonces "existe un eje de separación" => "Sin superposición". Esto no es una implicación doble, por lo que no puede concluir lo contrario.

m_pGladiator
fuente
1
Obviamente, como dos cuadrados (0,0,1,1) y (0,3,1,4) no se superponen, pero sus proyecciones en el eje x se superponen por completo. Ambas pruebas son necesarias, la combinación es suficiente.
MSalters
18
No es suficiente que las proyecciones xey se superpongan: tome, por ejemplo, los rectángulos [(0,0), (0,3), (3,3), (3,0)] y [(2,5), (5,2), (7,4), (4,7)].
Joel en Gö
44
Estoy de acuerdo con @Joel en Gö. Este método pierde un gran conjunto de casos en los que los rectángulos no se superponen, pero sus radios proyectados sí en x e y.
Scottie T
55
Esta respuesta no es incorrecta, pero es engañosa. Es cierto que: si las dos cajas chocan, las líneas A y B se superpondrán. pero también es cierto que: si las líneas A y B se superponen, las dos cajas pueden o no estar colisionando
Matt se quema el
77
@floater: Diría que no solo está mal, sino que también es engañoso, lo que es aún peor.
BlueRaja - Danny Pflughoeft
4

La respuesta de m_pGladiator es correcta y lo prefiero. La prueba de eje de separación es el método más simple y estándar para detectar la superposición de rectángulos. Una línea para la cual los intervalos de proyección no se superponen, llamamos un eje de separación . La solución de Nils Pipenbrinck es demasiado general. Utiliza el producto de puntos para verificar si una forma está totalmente en un lado del borde del otro. Esta solución en realidad podría inducir a polígonos convexos de borde n. Sin embargo, no está optimizado para dos rectángulos.

El punto crítico de la respuesta de m_pGladiator es que debemos verificar la proyección de dos rectángulos en ambos ejes (x e y). Si dos proyecciones se superponen, entonces podríamos decir que estos dos rectángulos se superponen. Entonces, los comentarios anteriores a la respuesta de m_pGladiator son incorrectos.

para la situación simple, si no se giran dos rectángulos, presentamos un rectángulo con estructura:

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

Nombramos el rectángulo A, B con 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 se gira cualquiera de los dos rectángulos, es posible que se necesiten algunos esfuerzos para determinar su proyección en los ejes x e y. Defina la estructura RotatedRect de la siguiente manera:

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

la diferencia es cómo el ancho 'ahora es un poco diferente: anchoA' para rectA: Math.sqrt(rectA.width*rectA.width + rectA.height*rectA.height) * Math.cos(rectA.angle) anchoB 'para 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

Podría referirse a un GDC (Game Development Conference 2007) PPT www.realtimecollisiondetection.net/pubs/GDC07_Ericson_Physics_Tutorial_SAT.ppt

tristan
fuente
¿Por qué necesita Math.abs () en "Math.abs (rectA.width + rectB.width)", para manejar anchos negativos?
AlexWien
El eje de separación no es necesariamente una dirección de la brújula, puede tener cualquier ángulo.
Ben Voigt
Los rectángulos no rotados rectA (x = 0, y = 0, ancho = 1, altura = 1) y rectB (x = 2, y = 0, ancho = 100, altura = 1) no se cruzan pero su método dice que intersecarse. ¿Estoy haciendo algo mal?
Kagami Sascha Rosylight
4

En Cocoa, podría detectar fácilmente si el área de área seleccionada se cruza con el marco de su NSView girado. Ni siquiera necesita calcular polígonos, normales como tal. Simplemente agregue estos métodos a su subclase NSView. Por ejemplo, el usuario selecciona un área en la supervista de NSView, luego llama al método DoesThisRectSelectMe pasando el área de Área seleccionada. La API convertRect: hará ese trabajo. El mismo truco funciona cuando haces clic en NSView para seleccionarlo. En ese caso, simplemente anule el método hitTest como se muestra a continuación. El API convertPoint: hará ese trabajo ;-)

- (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;
}
Leonardo
fuente
2
Ese código solo funciona para rectángulos que son cuadrados a la pantalla. Ese es un caso trivial. La suposición es que estamos tratando con rectángulos que no están en ángulos de 90 grados con respecto a la pantalla o entre sí.
Duncan C
Como he comprobado y utilizado en mis aplicaciones, ese código funciona en cualquier rectángulo girado. No importa el grado de rotación.
Leonardo
Sin embargo, esto no describe el algoritmo, solo menciona una biblioteca que ya lo usa.
Ben Voigt
2

Verifique si alguna de las líneas de un rectángulo se cruza con alguna de las líneas del otro. La intersección ingenua de segmentos de línea es fácil de codificar.

Si necesita más velocidad, existen algoritmos avanzados para la intersección del segmento de línea (línea de barrido). Ver http://en.wikipedia.org/wiki/Line_segment_intersection

Louis Brandy
fuente
44
¡Cuidado! No olvide el caso en el que un rectángulo encierra completamente a otro
Pitarou
2

Una solución es usar algo llamado un polígono sin ajuste. Este polígono se calcula a partir de los dos polígonos (conceptualmente deslizando uno alrededor del otro) y define el área para la cual los polígonos se superponen dado su desplazamiento relativo. Una vez que tenga este PFN, simplemente tiene que hacer una prueba de inclusión con un punto dado por el desplazamiento relativo de los dos polígonos. Esta prueba de inclusión es rápida y fácil, pero primero debe crear el NFP.

Realice una búsqueda de No Fit Polygon en la web y vea si puede encontrar un algoritmo para polígonos convexos (se vuelve MUCHO más complejo si tiene polígonos cóncavos). Si no puede encontrar nada, envíeme un correo electrónico a howard dot J dot may gmail dot com

Howard May
fuente
1

Esto es lo que creo que se ocupará de todos los casos posibles. Haz las siguientes pruebas.

  1. Verifique que cualquiera de los vértices del rectángulo 1 resida dentro del rectángulo 2 y viceversa. Cada vez que encuentre un vértice que reside dentro del otro rectángulo, puede concluir que se cruzan y detienen la búsqueda. Esto se encargará de que un rectángulo resida completamente dentro del otro.
  2. Si la prueba anterior no es concluyente, encuentre los puntos de intersección de cada línea de 1 rectángulo con cada línea del otro rectángulo. Una vez que se encuentra un punto de intersección, verifique si reside dentro del rectángulo imaginario creado por los 4 puntos correspondientes. Cuando se encuentre tal punto, concluya que se cruzan y detienen la búsqueda.

Si las 2 pruebas anteriores devuelven falso, estos 2 rectángulos no se superponen.

John Smith
fuente
0

Si está utilizando Java, todas las implementaciones de la interfaz Shape tienen un método de intersección que toma un rectángulo.

Brendan Cashman
fuente
Lamentablemente estoy usando C #. La clase Rectangle tiene un método Contains (), pero es solo para rectángulos no rotados.
user20493
El método intersects () es bastante inútil ya que devuelve boolean en lugar de intersección, supongo.
ZZ 5
0

Bueno, el método de fuerza bruta es caminar por los bordes del rectángulo horizontal y verificar cada punto a lo largo del borde para ver si cae sobre el otro rectángulo.

La respuesta matemática es formar ecuaciones que describan cada borde de ambos rectángulos. Ahora simplemente puede encontrar si alguna de las cuatro líneas del rectángulo A se cruza con alguna de las líneas del rectángulo B, que debería ser un simple (rápido) solucionador de ecuaciones lineales.

-Adán

Adam Davis
fuente
2
El problema con las ecuaciones es cuando tienes una línea vertical, que tiene pendiente infinita.
user20493
Hay casos de esquina para cada solución.
Adam Davis el
2
y una plaza encerrando por completo a la otra.
Oliver Hallam el
0

Puedes encontrar la intersección de cada lado del rectángulo angulado con cada lado del eje alineado. Haga esto encontrando la ecuación de la línea infinita en la que se encuentra cada lado (es decir, v1 + t (v2-v1) y v'1 + t '(v'2-v'1) básicamente), encontrando el punto en el que las líneas se encuentran resolviendo t cuando esas dos ecuaciones son iguales (si son paralelas, puede probarlo) y luego si ese punto se encuentra en el segmento de línea entre los dos vértices, es decir, es cierto que 0 <= t <= 1 y 0 <= t '<= 1.

Sin embargo, esto no cubre el caso cuando un rectángulo cubre completamente el otro. Que puede cubrir probando si los cuatro puntos de cualquiera de los rectángulos se encuentran dentro del otro rectángulo.

HenryR
fuente
0

Esto es lo que haría para la versión 3D de este problema:

Modele los 2 rectángulos como planos descritos por la ecuación P1 y P2, luego escriba P1 = P2 y derive de eso la ecuación de línea de intersección, que no existirá si los planos son paralelos (sin intersección) o están en el mismo plano, en cuyo caso obtienes 0 = 0. En ese caso, deberá emplear un algoritmo de intersección de rectángulo 2D.

Entonces vería si esa línea, que está en el plano de ambos rectángulos, pasa a través de ambos rectángulos. Si es así, entonces tiene una intersección de 2 rectángulos, de lo contrario no lo hace (o no debería, podría haber perdido un caso de esquina en mi cabeza).

Para encontrar si una línea pasa a través de un rectángulo en el mismo plano, encontraría los 2 puntos de intersección de la línea y los lados del rectángulo (modelándolos usando ecuaciones de línea), y luego me aseguraría de que los puntos de intersección estén en rango.

Esas son las descripciones matemáticas, desafortunadamente no tengo código para hacer lo anterior.

espacio libre
fuente
Se perdió la parte en la que si encuentra la línea de intersección plana, debe asegurarse de que exista una parte dentro de ambos rectángulos.
Lee Louviere
0

Otra forma de hacer la prueba, que es ligeramente más rápida que usar la prueba de eje de separación, es usar el algoritmo de números de bobinado (solo en cuadrantes, no suma de ángulos que es horriblemente lento) en cada vértice de cualquier rectángulo (elegido arbitrariamente). Si alguno de los vértices tiene un número de devanado distinto de cero, los dos rectángulos se superponen.

Este algoritmo es algo más largo que la prueba de eje de separación, pero es más rápido porque solo requiere una prueba de medio plano si los bordes cruzan dos cuadrantes (en oposición a hasta 32 pruebas que utilizan el método de eje de separación)

El algoritmo tiene la ventaja adicional de que puede usarse para probar la superposición de cualquier polígono (convexo o cóncavo). Hasta donde yo sé, el algoritmo solo funciona en el espacio 2D.

Locos
fuente
3
Puedo estar equivocado, pero ¿eso no solo verifica si los vértices de un rectángulo están dentro de otro? En caso afirmativo, no es suficiente porque los rectángulos pueden superponerse sin vértices en su interior.
sinelaw
¿Pueden, con rectángulos? ¿Cómo? Me parece que para que 2 rectángulos se crucen, al menos un vértice de uno de los rectángulos debe estar en el otro rectángulo.
Duncan C
@ DuncanC: Sí, pueden. El contraejemplo es una cruz, e incluso se enumeró en la pregunta original.
Ben Voigt
@BenVoigt Este es un hilo muy antiguo, pero tienes toda la razón.
Duncan C
0

O me falta algo más, ¿por qué hacer esto tan complicado?

si (x1, y1) y (X1, Y1) son esquinas de los rectángulos, entonces para encontrar la intersección haz:

    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");}
usuario1517108
fuente
3
Te falta que él quiera que uno gire en un ángulo arbitrario.
Robotbugs
0

Lo implementé así:

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 matriz mB es cualquier matriz de transformación afín que convierte puntos en el espacio B en puntos en el espacio A. Esto incluye rotación y traslación simples, rotación más escalado y deformaciones afines completas, pero no deformaciones de perspectiva.

Puede que no sea lo más óptimo posible. La velocidad no era una gran preocupación. Sin embargo, parece funcionar bien para mí.

Robotbugs
fuente
0

Aquí hay una implementación matlab de la respuesta aceptada:

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);
Jed
fuente
0

Este es el método convencional, vaya línea por línea y verifique si las líneas se cruzan. Este es el código en 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 función InterX se puede descargar desde: https://in.mathworks.com/matlabcentral/fileexchange/22441-curve-intersections?focused=5165138&tab=function

Siva Srinivas Kolukula
fuente
0

Tengo un método más simple, si tenemos 2 rectángulos:

R1 = (min_x1, max_x1, min_y1, max_y1)

R2 = (min_x2, max_x2, min_y2, max_y2)

Se superponen si y solo si:

Superposición = (max_x1> min_x2) y (max_x2> min_x1) y (max_y1> min_y2) y (max_y2> min_y1)

También puede hacerlo para cajas 3D, en realidad funciona para cualquier cantidad de dimensiones.

BitFarmer
fuente
0

Ya se ha dicho lo suficiente en otras respuestas, por lo que solo agregaré pseudocódigo one-liner:

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