Como calcular a distância entre um ponto e um retângulo alinhado ao eixo?

29

Eu tenho um retângulo 2D com posição x, y, altura e largura e um ponto posicionado aleatoriamente nas proximidades.

Existe uma maneira de verificar se esse ponto pode colidir com o retângulo se estiver mais próximo do que uma certa distância? Imagine um raio invisível fora desse ponto colidindo com o referido retângulo. Eu tenho problemas com isso simplesmente porque não é um quadrado!

john smith
fonte

Respostas:

26

Se (x,y)é o centro do retângulo, a distância ao quadrado de um ponto (px,py)até a borda do retângulo pode ser calculada da seguinte maneira:

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

Se essa distância ao quadrado é zero, significa que o ponto toca ou está dentro do retângulo.

sam hocevar
fonte
6
Para qualquer outra pessoa que está perguntando, (x, y) é o centro do retângulo, não o canto
Greg Rozmarynowycz
2
Desculpe pelo comentário antigo, mas essa equação supõe que o retângulo esteja alinhado ao eixo?
BitNinja 25/10/14
1
@ BitNinja sim, é isso que a pergunta assume. Se não estiver alinhado ao eixo, o algoritmo mais rápido / mais simples dependerá de como as informações do retângulo são armazenadas.
23414 # 13:
digamos, o ponto é (4: 4), o retângulo está em (5: 5) com largura / altura (5: 5). Seu código diria que os toques de ponto ou está dentro do retângulo, mas é obviamente fora
LRN
@LRN, um retângulo centrado em (5: 5) com largura / altura (5: 5) se estende de (2,5: 2,5) a (7,5: 7,5). O ponto (4: 4) está dentro desse retângulo.
Sam Hocevar
11

Presumo que seu retângulo esteja alinhado ao eixo.

Você apenas precisa "prender" o ponto no retângulo e depois calcular a distância do ponto preso.

Ponto = (px, py), Retângulo = (rx, ry, rwidth, rheight) // (canto superior esquerdo, dimensões)

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
fonte
3

Você deve usar colisões círculo-retângulo para isso. Há uma pergunta semelhante no Stack Overflow.

O centro do seu círculo seria o ponto em questão e o raio seria a distância que você deseja verificar.

Eric B
fonte
3

Se você está tentando descobrir a distância de um ponto até a aresta de um retângulo, trabalhar com cada uma das nove regiões criadas pelo retângulo pode ser a maneira mais 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
fonte
2

[Resposta modificada com base nos comentários]

Se você quiser ver se o ponto está dentro, por exemplo, 10 unidades, se o retângulo cinza na imagem abaixo, verifique se o ponto está em qualquer um dos

  1. retângulo vermelho
  2. Retângulo azul
  3. qualquer um dos círculos verdes (raio 10)

insira a descrição da imagem aqui

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

Essa abordagem é um pouco deselegante. Um método semelhante que evita a necessidade de testar todos os 4 cantos usando simetria de retângulo está documentado aqui no stackoverflow

Ken
fonte
Na direção diagonal, isso dará um falso positivo aos pontos que são, por exemplo. 11 unidades de distância.
Eric B
A imagem atualizada está flagrantemente errada, na verdade, ilustra o caso de erro e faz com que pareça correto. Esse ponto verde pode facilmente estar a mais de 10 unidades e estar dentro desse retângulo externo.
Eric B
Hey @EricB, corrigi o erro que você apontou, que tal desfazer seu voto negativo?
26712 Ken
Sua resposta não dará mais resultados estritamente incorretos, por isso removi o voto negativo, mas também não é o melhor caminho. Por que não testar apenas se o centro está dentro do retângulo e se os quatro segmentos de linha cruzam o círculo? A construção desses novos retângulos e círculos simplesmente não é necessária. Sua resposta também não fornece a distância real do ponto ao retângulo.
Eric B
Esta resposta é honestamente terrível. 12 adições, 4 construções de objetos, 12 testes, 4 raízes quadradas para uma tarefa que realmente requer 3 linhas de código?
28612 Sam sami hocevar
-2

Você pode usar algo como isto: insira a descrição da imagem aqui

AlexanderBrevig
fonte
Este método parece desnecessariamente complicado. Encontrar x1 e y1 não é necessário para resolver esse problema.
Eric B
De fato, isso nem sequer satisfaz a exigência de encontrar uma colisão a uma determinada distância. É apenas uma maneira ruim de detectar se o ponto está dentro do retângulo.
Eric B
Uma medida de distância já está implicitamente presente. if (d2 <10 * 10) {/ * dentro de 10 unidades de medida * /} #
AlexanderBrevig