¿Cómo calculo la distancia entre un punto y un rectángulo alineado con un eje?

29

Tengo un rectángulo 2D con posición x, y, altura y ancho, y un punto ubicado al azar cerca.

¿Hay alguna manera de verificar si este punto podría chocar con el rectángulo si está más cerca de cierta distancia? Imagine un radio invisible fuera de ese punto que choca con dicho rectángulo. ¡Tengo problemas con esto simplemente porque no es un cuadrado!

John Smith
fuente

Respuestas:

26

Si (x,y)es el centro del rectángulo, la distancia al cuadrado desde un punto (px,py)hasta el borde del rectángulo se puede calcular de esta manera:

dx = max(abs(px - x) - width / 2, 0);
dy = max(abs(py - y) - height / 2, 0);
return dx * dx + dy * dy;

Si esa distancia al cuadrado es cero, significa que el punto toca o está dentro del rectángulo.

sam hocevar
fuente
66
Para cualquiera que se pregunte, (x, y) es el centro del rectángulo, no la esquina
Greg Rozmarynowycz
2
Perdón por el comentario anterior, pero ¿esta ecuación supone que el rectángulo está alineado con el eje?
BitNinja
1
@ BitNinja sí, eso es lo que supone la pregunta. Si no está alineado con el eje, el algoritmo más rápido / más simple dependerá de cómo se almacene la información del rectángulo.
Sam Hocevar
digamos, el punto es (4: 4), el rectángulo está en (5: 5) con ancho / alto (5: 5). Su código afirmaría que el punto toca o está dentro del rectángulo, pero obviamente está afuera
LRN
@LRN un rectángulo centrado en (5: 5) con ancho / alto (5: 5) se extiende desde (2.5: 2.5) a (7.5: 7.5). El punto (4: 4) está dentro de ese rectángulo.
sam hocevar
11

Supongo que su rectángulo está alineado con el eje.

Solo tiene que "sujetar" el punto en el rectángulo y luego calcular la distancia desde el punto sujeto.

Punto = (px, py), Rectángulo = (rx, ry, rwidth, rheight) // (esquina superior izquierda, dimensiones)

function pointRectDist (px, py, rx, ry, rwidth, rheight)
{
    var cx = Math.max(Math.min(px, rx+rwidth ), rx);
    var cy = Math.max(Math.min(py, ry+rheight), ry);
    return Math.sqrt( (px-cx)*(px-cx) + (py-cy)*(py-cy) );
}
Ivan Kuckir
fuente
3

Debe usar colisiones círculo-rectángulo para esto. Hay un similar pregunta sobre Stack Overflow.

El centro de su círculo sería el punto en cuestión, y el radio sería la distancia que desea verificar.

Eric B
fuente
3

Si está tratando de calcular la distancia desde un punto hasta el borde de un rectángulo, trabajar con cada una de las nueve regiones creadas por el rectángulo podría ser la forma más rápida:

function pointRectangleDistance(x, y, x1, y1, x2, y2) {
    var dx, dy;
    if (x < x1) {
        dx = x1 - x;
        if (y < y1) {
            dy = y1 - y;
            return Math.sqrt(dx * dx + dy * dy);
        }
        else if (y > y2) {
            dy = y - y2;
            return Math.sqrt(dx * dx + dy * dy);
        }
        else {
            return dx;
        }
    }
    else if (x > x2) {
        dx = x - x2;
        if (y < y1) {
            dy = y1 - y;
            return Math.sqrt(dx * dx + dy * dy);
        }
        else if (y > y2) {
            dy = y - y2;
            return Math.sqrt(dx * dx + dy * dy);
        }
        else {
            return dx;
        }
    }
    else {
        if (y < y1) {
            return y1 - y;
        }
        else if (y > y2) {
            return y - y2;
        }
        else {
            return 0.0; // inside the rectangle or on the edge
        }
    }
}
Heliodor
fuente
2

[Respuesta modificada basada en comentarios]

Si desea ver si el punto está dentro de 10 unidades si el rectángulo gris en la imagen a continuación, verifique si el punto está en cualquiera de

  1. rectángulo rojo
  2. Rectángulo azul
  3. cualquiera de los círculos verdes (radio 10)

ingrese la descripción de la imagen aquí

inside=false;

bluerect.x=oldrect.x-10;
bluerect.y=oldrect.y;
bluerect.width=oldrect.width;
bluerect.height=oldrect.height+20;

if(  point.x >=bluerect && point.x <=redrect.x+bluerect.width &&
     point.y >=bluerect && point.y <=redrect.y+bluerect.height){
         //now point is side the blue rectangle
         inside=true;
}

redrect.x=oldrect.x;
redrect.y=oldrect.y-10;
redrect.width=oldrect.width+20;
redrect.height=oldrect.height;

if(  point.x >=redrect&& point.x <=redrect.x+redrect.width &&
     point.y >=redrect&& point.y <=redrect.y+redrect.height){
         //now point is side the redrectangle
         inside=true;
}


d1= distance(point, new point(oldrect.x, oldrect.y)) //calculate distance between point and (oldrect.x, oldrect.y)
d2= distance(point, new point(oldrect.x+10, oldrect.y))
d3= distance(point, new point(oldrect.x, oldrect.y+10))
d4= distance(point, new point(oldrect.x+10, oldrect.y+10))
if (d1 < 10 || d2 <10 || d3 < 10 || d4 <10){
    inside=true;
}

//inside is now true if the point is within 10 units of rectangle

Este enfoque es un poco poco elegante. Aquí se documenta un método similar que evita tener que probar las 4 esquinas utilizando la simetría del rectángulo en stackoverflow

Conocer
fuente
En la dirección diagonal, esto dará un falso positivo a los puntos que son, por ejemplo. 11 unidades de distancia.
Eric B
La imagen actualizada es descaradamente incorrecta, de hecho, ilustra el caso de error y hace que parezca correcto. Ese punto verde podría estar fácilmente a más de 10 unidades de distancia y estar dentro de ese rectángulo exterior.
Eric B
Hola @EricB, he solucionado el error que señalaste, ¿qué tal si deshaces tu voto negativo?
Ken
Su respuesta ya no dará resultados estrictamente incorrectos, por lo que eliminé el voto negativo, pero tampoco es la mejor manera. ¿Por qué no solo probar para ver si el centro está dentro del rectángulo y si los cuatro segmentos de línea se cruzan con el círculo? La construcción de estos nuevos rectángulos y círculos simplemente no es necesaria. Su respuesta tampoco proporciona la distancia real desde el punto hasta el rectángulo.
Eric B
Esta respuesta es sinceramente horrible. ¿12 adiciones, 4 construcciones de objetos, 12 pruebas, 4 raíces cuadradas para una tarea que realmente requiere 3 líneas de código?
sam hocevar
-2

Podrías usar algo como esto: ingrese la descripción de la imagen aquí

AlexanderBrevig
fuente
Este método parece innecesariamente complicado. Encontrar x1 e y1 no es necesario para resolver este problema.
Eric B
De hecho, esto ni siquiera satisface el requisito de encontrar una colisión dentro de una distancia dada. Es solo una mala forma de detectar si el punto está dentro del rectángulo.
Eric B
Una medida de distancia ya está implícitamente allí. if (d2 <10 * 10) {/ * dentro de 10 unidades de medida * /}
AlexanderBrevig