web-dev-qa-db-fra.com

calcul de la distance entre un point et une boîte rectangulaire

existe-t-il une formule simple pour calculer cela? J'ai travaillé sur des calculs, mais je ne peux que trouver un moyen de calculer la distance vers le centre de la boîte, pas vers le point le plus proche. Existe-t-il des ressources sur ce problème?

25
Daniel

Voici une formule unique qui évite toute la logique de cas. (Je travaille actuellement dans JS, voici donc une implémentation de JS). Soit rect = {max:{x:_, y:_}, min:{x:_, y:_}} et p={x:_, y:_}

function distance(rect, p) {
  var dx = Math.max(rect.min.x - p.x, 0, p.x - rect.max.x);
  var dy = Math.max(rect.min.y - p.y, 0, p.y - rect.max.y);
  return Math.sqrt(dx*dx + dy*dy);
}

Explication: Ceci décompose le problème en calculant la distance x dx et la distance y dy. Il utilise ensuite la formule de distance.

Pour calculer dx, voici comment cela fonctionne. (dy est analogue)

Regardez le tuple fourni à la fonction max: (min-p, 0, p-max). Désignons ce tuple (a,b,c).

Si p est laissé de min, alors nous avons p <min <max, ce qui signifie que le tuple sera évalué à (+,0,-) et que la fonction max renverra donc correctement a = min - p.

Si p est compris entre min et max, alors nous avons min <p <max, ce qui signifie que le nuplet sera évalué à (-,0,-). Encore une fois, la fonction max renverra correctement b = 0.

Enfin, si p est à droite de max, alors nous avons, min <max <p, et le tuple est évalué à (-,0,+). Encore une fois, Math.max renvoie correctement c = p - max.

Il s’avère que toute la logique de cas est prise en charge par Math.max, ce qui conduit à une fonction Nice à 3 lignes, sans contrôle de flux.

45
MultiRRomero

Je pense que vous devez analyser des cas; il n'y a pas de formule unique. Il est plus facile de l'illustrer en deux dimensions:

1          2          3
    +-------------+
    |             |
4   |      0      |   5
    |             |
    +-------------+
6          7          8

Les bords de la boîte (étendue) divisent l'extérieur en 9 régions. La région 0 (à l'intérieur de la boîte) est résolue en calculant la distance entre chaque bord et en prenant le minimum. Chaque point de la région 1 est le plus proche du sommet en haut à gauche, et de la même manière pour les régions 3, 6 et 8. Pour les régions 2, 4, 5 et 7, vous devez rechercher la distance entre le point et le bord le plus proche. C'est un problème assez simple. Vous pouvez déterminer la région dans laquelle se trouve un point en le classant par rapport à chaque bord. (Il est plus facile de voir comment faire cela en dirigeant les bords, par exemple, dans le sens inverse des aiguilles d'une montre.) Cela vous indiquera également si le point est à l'intérieur de la boîte.

En 3D, la logique est exactement la même sauf que vous classifiez par rapport aux six faces et que vous avez plus de cas.

Le problème est plus simple si les bords de la boîte sont parallèles aux axes de coordonnées.

7
Ted Hopp

Disons que le point s'appelle P et ABCD est notre rectangle. Ensuite, le problème peut être décomposé en l'ensemble de sous-problèmes suivant:

(1) Développe une fonction dist(P, AB) qui calcule la distance entre un point P et un segment - arbitraire AB.

(2) Calculez quatre distances entre votre point P et chaque côté du rectangle (chaque côté est un segment) et prenez la plus courte des quatre

  distance = min(dist(P, AB), dist(P,BC), dist(P, CD), dist(P, DA))

C'est ta réponse.

Maintenant, nous devons savoir comment calculer la distance entre le point P et un segment arbitraire AB, c’est-à-dire comment calculer dist(P, AB). Cela se fait comme suit

(1) Effectuer une projection perpendiculaire du point P à la droite AB. Vous obtenez le nouveau point P' sur AB.

(2) Si P' est compris entre A et B, alors dist(P, AB) est la distance entre P et P'.

(3) Sinon, dist(P, AB) est la distance entre P et A ou B, la valeur la plus courte étant retenue.

C'est tout. Il existe des moyens évidents d'optimiser la procédure, mais même si elle est mise en œuvre à la lettre, elle fonctionnera déjà très bien.

P.S. Bien sûr, on peut se demander comment effectuer une projection d’un point sur une ligne. Je vais le laisser comme exercice au lecteur :)

5
AnT

La réponse de Kikito n'est pas correcte. En fait, si P est dans les régions 2, 4, 5 ou 7 du schéma de Ted Hopp, il renvoie la distance minimale par rapport aux sommets, qui est différente (plus grande) de la distance minimale par rapport aux bords.

Je corrigerais la fonction distance_aux de kikito en renvoyant 0 au lieu de min (p - inférieur, supérieur - p), et tout fonctionne en dehors de la région 0 où P est à l'intérieur de la boîte. À mon avis, cette région devrait être gérée séparément, en fonction de ce que vous voulez atteindre, qu'il s'agisse de la distance de la zone ou de la distance du périmètre de la boîte. Si vous voulez obtenir la distance de la zone de la boîte, je dirais que c'est zéro lorsque le point est à l'intérieur de la boîte.

function inside(point, box)
    return (point.x > box.left AND point.x < box.right AND point.y > box.top AND point.y < box.bottom)
end

function distance_aux(p, lower, upper)
    if p < lower then return lower - p end
    if p > upper then return p - upper end
    return 0
end

function distance(point, box)
    local dx = distance_aux(point.x, box.left, box.right)
    local dy = distance_aux(point.y, box.top, box.bottom)
    if (inside(point, box))
        return min(dx, dy)    // or 0 in case of distance from the area
    else
        return sqrt(dx * dx + dy * dy)
    endif
end
1
Gianluca

Variante C # légèrement optimisée (même s’il devrait y avoir une certaine tolérance lorsqu’on compare les doublons à 0). Je recommande également de créer une sorte de méthode d'extension Rect ou Point pour ces applications.

public static class GeometryUtils
{
    public static double Distance(Point point, Rect rect)
    {
        var xDist = MinXDistance(point, rect);
        var yDist = MinYDistance(point, rect);
        if (xDist == 0)
        {
            return yDist;
        }
        else if (yDist == 0)
        {
            return xDist;
        }

        return Math.Sqrt(Math.Pow(xDist, 2) + Math.Pow(yDist, 2));
    }

    private static double MinXDistance(Point point, Rect rect)
    {
        if (rect.Left > point.X)
        {
            return rect.Left - point.X;
        }
        else if (rect.Right < point.X)
        {
            return point.X - rect.Right;
        }
        else
        {
            return 0;
        }
    }

    private static double MinYDistance(Point point, Rect rect)
    {
        if (rect.Bottom < point.Y)
        {
            return point.Y - rect.Bottom;
        }
        else if (rect.Top > point.Y)
        {
            return rect.Top - point.Y;
        }
        else
        {
            return 0;
        }
    }
}
1
Mo0gles

Pour les AABB:

Peut-être pas la meilleure performance, mais certainement la méthode la plus simple:

p = votre point

c = centre du cube

s = demi-taille du cube

r = le point que nous recherchons

v = p - c;
m = max_abs(v);
r = c + ( v / m * s );
0
Addi

Ceci est facile à réaliser avec les produits scalaires. En fait, il a déjà été répondu en 3D pour le cas non aligné alignement.

https://stackoverflow.com/a/44824522/158285

Mais en 2D, vous pouvez réaliser la même chose

public struct Vector2 {
   double X; double Y

   // Vector dot product
   double Dot(Vector2 other)=>X*other.X+Y*other.Y;


   // Length squared of the vector
   double LengthSquared()=>Dot(this,this);

   // Plus other methods for multiplying by a scalar
   // adding and subtracting vectors etc
}

La fonction pour retourner le point le plus proche

public Vector2 ClosestPointTo
    (Vector2 q, Vector2 Origin, Vector3 v10, Vector3 v01)
{
    var px = v10;
    var py = v01;


    var vx = (px - Origin);
    var vy = (py - Origin);


    var tx = Vector2.Dot( q - Origin, vx ) / vx.LengthSquared();
    var ty = Vector3.Dot( q - Origin, vy ) / vy.LengthSquared();


    tx = tx < 0 ? 0 : tx > 1 ? 1 : tx;
    ty = ty < 0 ? 0 : ty > 1 ? 1 : ty;

    var p = tx * vx + ty * vy + Origin;

    return p;
}
0
bradgonesurfing

Je cherchais cela et je pense avoir une solution, pour le cas sur lequel la case est alignée (un cas assez commun)

Je crois que dans ce cas, vous pouvez calculer la distance comme ceci:

function distance_aux(p, lower, upper)
  if p < lower then return lower - p end
  if p > upper then return p - upper end
  return min(p - lower, upper - p)
end

function distance(point, box)
  local dx = distance_aux(point.x, box.left, box.right)
  local dy = distance_aux(point.y, box.top, box.bottom)
  return sqrt(dx * dx + dy * dy)
end

Cela peut être étendu à z, bien sûr.

0
kikito

Est-ce une boîte 3D ou un rectangle 2D? Quoi qu'il en soit, il vaut mieux que vous obteniez la distance point-ligne (pour 2D) ou point-plan (3D) pour chaque côté, puis la sélection de la distance minimale.

Edit: Il existe une bien meilleure façon de décrire ici (dernier post). Cela implique de transformer vos coordonnées de point en case, puis de "saturer" les coordonnées avec la taille de la case pour trouver le point sur la case la plus proche du point. Je n'ai pas essayé, mais cela me semble juste.

0
Rob Agar