Comment calculer la distance entre un point et un rectangle aligné sur l'axe?

29

J'ai un rectangle 2D avec la position x, y, la hauteur et la largeur, et un point positionné au hasard à proximité.

Existe-t-il un moyen de vérifier si ce point peut entrer en collision avec le rectangle s'il est plus proche d'une certaine distance? Imaginez un rayon invisible à l'extérieur de ce point entrant en collision avec ledit rectangle. J'ai des problèmes avec ça simplement parce que ce n'est pas un carré!

John Smith
la source

Réponses:

26

Si (x,y)est le centre du rectangle, la distance au carré d'un point (px,py)à la bordure du rectangle peut être calculée de cette façon:

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

Si cette distance au carré est nulle, cela signifie que le point touche ou se trouve à l'intérieur du rectangle.

sam hocevar
la source
6
Pour tous ceux qui se demandent, (x, y) est le centre du rectangle, pas le coin
Greg Rozmarynowycz
2
Désolé pour l'ancien commentaire, mais cette équation suppose-t-elle que le rectangle est aligné sur l'axe?
BitNinja
1
@BitNinja oui, c'est ce que suppose la question. S'il n'est pas aligné sur l'axe, l'algorithme le plus rapide / le plus simple dépendra de la façon dont les informations du rectangle sont stockées.
sam hocevar
disons, le point est (4: 4), le rectangle est à (5: 5) avec la largeur / hauteur (5: 5). Votre code prétendrait que le point touche ou se trouve à l'intérieur du rectangle, mais il est évidemment à l'extérieur
LRN
@LRN un rectangle centré sur (5: 5) avec une largeur / hauteur (5: 5) s'étend de (2,5: 2,5) à (7,5: 7,5). Le point (4: 4) se trouve à l'intérieur de ce rectangle.
sam hocevar
11

Je suppose que votre rectangle est aligné sur l'axe.

Il vous suffit de "fixer" le point dans le rectangle, puis de calculer la distance à partir du point fixé.

Point = (px, py), Rectangle = (rx, ry, rwidth, rheight) // (coin supérieur gauche, dimensions)

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
la source
3

Pour cela, vous devez utiliser des collisions cercle-rectangle. Il y a une question similaire sur Stack Overflow.

Le centre de votre cercle serait le point en question et le rayon serait la distance que vous souhaitez vérifier.

Eric B
la source
3

Si vous essayez de déterminer la distance entre un point et le bord d'un rectangle, travailler avec chacune des neuf régions créées par le rectangle peut être le moyen le plus rapide:

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
la source
2

[Réponse modifiée sur la base des commentaires]

Si vous voulez voir si le point est à moins de 10 unités si le rectangle gris dans l'image ci-dessous, vous vérifiez si le point est dans l'un des

  1. rectangle rouge
  2. Rectangle bleu
  3. l'un des cercles verts (rayon 10)

entrez la description de l'image ici

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

Cette approche est un peu inélégante. Une méthode similaire qui évite d'avoir à tester les 4 coins en utilisant la symétrie rectangle est documentée ici sur stackoverflow

Ken
la source
Dans la direction diagonale, cela donnera un faux positif aux points qui sont par exemple. 11 unités loin.
Eric B
L'image mise à jour est manifestement fausse, en fait, elle illustre en fait le cas d'erreur et la fait apparaître correcte. Ce point vert pourrait facilement être à plus de 10 unités et être à l'intérieur de ce rectangle extérieur.
Eric B
Hé @EricB, j'ai corrigé l'erreur que vous avez signalée, que diriez-vous d'annuler votre downvote?
Ken
Votre réponse ne donnera plus de résultats strictement incorrects, j'ai donc supprimé le downvote, mais ce n'est pas non plus la meilleure façon du tout. Pourquoi ne pas simplement tester pour voir si le centre se trouve dans le rectangle et si les quatre segments de ligne coupent le cercle? La construction de ces nouveaux rectangles et cercles n'est tout simplement pas nécessaire. Votre réponse ne fournit pas non plus la distance réelle entre le point et le rectangle.
Eric B
Cette réponse est vraiment horrible. 12 ajouts, 4 constructions d'objets, 12 tests, 4 racines carrées pour une tâche qui nécessite en fait 3 lignes de code?
sam hocevar
-2

Vous pouvez utiliser quelque chose comme ceci: entrez la description de l'image ici

AlexanderBrevig
la source
Cette méthode semble inutilement compliquée. Trouver x1 et y1 n'est pas nécessaire pour résoudre ce problème.
Eric B
En fait, cela ne satisfait même pas l'exigence de trouver une collision à une distance donnée. C'est juste une mauvaise façon de détecter si le point se trouve dans le rectangle.
Eric B
Une mesure de la distance est déjà implicitement là. si (d2 <10 * 10) {/ * dans les 10 unités de mesure * /}
AlexanderBrevig