Solución:
Así es como lo haría:
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));
}
Así es como funciona:
-
El primer par de líneas calcula los valores absolutos de la diferencia xey entre el centro del círculo y el centro del rectángulo. Esto colapsa los cuatro cuadrantes en uno, de modo que los cálculos no tienen que realizarse cuatro veces. La imagen muestra el área en la que ahora debe estar el centro del círculo. Tenga en cuenta que solo se muestra el cuadrante único. El rectángulo es el área gris y el borde rojo delinea el área crítica que está exactamente a un radio de los bordes del rectángulo. El centro del círculo tiene que estar dentro de este borde rojo para que ocurra la intersección.
-
El segundo par de líneas elimina los casos fáciles en los que el círculo está lo suficientemente lejos del rectángulo (en cualquier dirección) como para que no sea posible una intersección. Esto corresponde al área verde de la imagen.
-
El tercer par de líneas maneja los casos fáciles en los que el círculo está lo suficientemente cerca del rectángulo (en cualquier dirección) como para garantizar una intersección. Esto corresponde a las secciones naranja y gris de la imagen. Tenga en cuenta que este paso debe realizarse después del paso 2 para que la lógica tenga sentido.
-
Las líneas restantes calculan el caso difícil en el que el círculo puede cruzarse con la esquina del rectángulo. Para resolverlo, calcule la distancia desde el centro del círculo y la esquina, y luego verifique que la distancia no sea mayor que el radio del círculo. Este cálculo devuelve falso para todos los círculos cuyo centro está dentro del área sombreada en rojo y devuelve verdadero para todos los círculos cuyo centro está dentro del área sombreada en blanco.
Solo hay dos casos en los que el círculo se cruza con el rectángulo:
- O el centro del círculo se encuentra dentro del rectángulo, o
- Uno de los bordes del rectángulo tiene un punto en el círculo.
Tenga en cuenta que esto no requiere que el rectángulo sea paralelo al eje.
(Una forma de ver esto: si ninguno de los bordes tiene un punto en el círculo (si todos los bordes están completamente “fuera” del círculo), entonces la única forma en que el círculo aún puede intersecar el polígono es si se encuentra completamente dentro del polígono.)
Con esa información, algo como lo siguiente funcionará, donde el círculo tiene el centro P
y radio R
y el rectángulo tiene vértices A
, B
, C
, D
en ese orden (código no 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)))
Si está escribiendo alguna geometría, probablemente ya tenga las funciones anteriores en su biblioteca. De lo contrario, pointInRectangle()
se puede implementar de varias formas; cualquiera de los métodos de punto general en polígono funcionará, pero para un rectángulo, puede verificar si esto funciona:
0 ≤ AP·AB ≤ AB·AB and 0 ≤ AP·AD ≤ AD·AD
Y intersectCircle()
También es fácil de implementar: una forma sería comprobar si el pie de la perpendicular de P
a la línea está lo suficientemente cerca y entre los puntos finales, y verifique los puntos finales en caso contrario.
Lo bueno es que el mismo La idea funciona no solo para rectángulos, sino también para la intersección de un círculo con cualquier polígono simple, ¡ni siquiera tiene que ser convexo!
Aquí hay otra solución que es bastante simple de implementar (y bastante rápida también). Capturará todas las intersecciones, incluso cuando la esfera haya entrado completamente en el rectángulo.
// 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 cualquier biblioteca matemática decente, eso se puede reducir a 3 o 4 líneas.