web-dev-qa-db-ja.com

点と長方形のボックス(最近点)の間の距離を計算する

これを計算する簡単な式はありますか?私はいくつかの数学に取り組んできましたが、最も近い点に向けられていない、ボックスの中心に向けられた距離を計算する方法しか見つけることができません。この問題にはいくつかのリソースがありますか?

32
Daniel

以下は、すべてのケースロジックを回避する単一の式です。 (私はたまたまJSで働いているので、これがJSの実装です)。 rect = {max:{x:_, y:_}, min:{x:_, y:_}}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);
}

説明:これは、問題をx距離dxとy距離dyの計算に分解します。次に、距離の式を使用します。

dxの計算については、次のようになります。 (dyは類似しています)

Max関数に提供されているタプルを見てください:(min-p, 0, p-max)。このタプル(a,b,c)を指定しましょう。

Pがminの左にある場合、p <min <maxとなります。つまり、タプルは(+,0,-)に評価されるため、max関数はa = min - pを正しく返します。

Pがminとmaxの間にある場合、min <p <maxとなります。これは、タプルが(-,0,-)に評価されることを意味します。そのため、max関数は正しくb = 0を返します。

最後に、pがmaxの右側にある場合、min <max <pとなり、Tupleは(-,0,+)と評価されます。ここでも、Math.maxはc = p - maxを正しく返します。

したがって、すべてのケースロジックがMath.maxによって処理されることがわかります。これは、ニースの3行の制御フローのない関数につながります。

51
MultiRRomero

ケースを分析する必要があると思います。単一の公式はありません。 2次元で説明する方が簡単です。

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

ボックス(拡張)のエッジは、外側を9つの領域に分割します。領域0(ボックス内)は、各エッジまでの距離を計算し、最小値をとることによって解決されます。リージョン1のすべてのポイントは左上の頂点に最も近く、リージョン3、6、および8の場合も同様です。リージョン2、4、5、および7の場合、ポイントから最も近いエッジまでの距離を見つける必要があります。かなり単純な問題です。各エッジに関して分類することにより、ポイントがどの領域にあるかを判断できます。 (これを行う方法は、エッジを反時計回りに向けることで簡単に確認できます。)これにより、ポイントがボックス内にあるかどうかもわかります。

3Dでは、ロジックはまったく同じですが、6つの面に関して分類し、さらに多くのケースがある点が異なります。

ボックスのエッジが座標軸に平行である場合、問題はより簡単です。

8
Ted Hopp

ポイントがPという名前で、ABCDが長方形であるとしましょう。次に、問題を次の一連の副問題に分解できます。

(1)ポイントPと任意のセグメント間の距離を計算する関数dist(P, AB)を開発しますAB

(2)ポイントPと長方形の各辺(各辺がセグメント)との間の4つの距離を計算し、4つの中で最も短い距離をとります

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

それがあなたの答えです。

ここで、ポイントPと任意のセグメントABの間の距離を計算する方法、つまりdist(P, AB)を計算する方法を知る必要があります。これは次のように行われます

(1)Pを線ABに垂直に投影します。 ABの新しいポイント_P'_を取得します。

(2) _P'_がABの間にある場合、dist(P, AB)P間の距離ですおよび_P'_。

(3)それ以外の場合、dist(P, AB)PAまたはBのどちらか短い方の距離です。

それでおしまい。プロシージャを最適化する明らかな方法がいくつかありますが、文字どおりに実装されたとしても、それはすでに非常にうまく機能します。

追伸もちろん、点を線に投影する方法を尋ねることができます。読者への演習として残しておきます:)

5
AnT

AABBの場合:

多分最高のパフォーマンスではないかもしれませんが、確かに最も簡単な方法です:

p =あなたのポイント

c =立方体の中心

s =立方体の半分のサイズ

r =探している点

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

Kikitoの答えは正しくありません。実際、PがTed Hoppのスキームの2、4、5、または7の領域にある場合、頂点からの最小距離を返します。これは、エッジからの最小距離とは異なる(大きい)ものです。

私はmin(p-lower、upper-p)の代わりに0を返すことによってkikitoの関数distance_auxを修正し、Pがボックス内にある0領域以外はすべて機能します。領域は、エリアからの距離であるか、ボックスの周囲からの距離であるかにかかわらず、達成したいことに応じて、個別に管理する必要があると私は考えています。ボックスの領域からの距離を取得する場合、ポイントがボックスの内側にある場合はゼロであると言えます。

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

わずかに最適化されたC#の代替(ただし、doubleと0を比較する場合は、ある程度の許容差があるはずです)また、これらに対して何らかのRectまたはPoint拡張メソッドを作成することをお勧めします。

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

これは、ドット積で簡単に実現できます。実際、非軸合わせの場合については、すでに3dで回答されています。

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

しかし、2Dでは同じことを実現できます

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
}

最も近い点を返す関数

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

私はこれを探していましたが、ボックスが軸合わせされている場合(かなり一般的なケース)の解決策があると思います

その場合、次のように距離を計算できると思います。

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

もちろん、これをzに拡張できます。

0
kikito

これは3Dボックスまたは2D長方形ですか? どちらの方法でも、各側の point-line (2Dの場合)または point-plane (3D)の距離を取得してから、最小値を選択するのが最善の方法です。

編集:説明されているはるかに良い方法があります ここ (最後の投稿)。これは、ポイント座標をボックススペースに変換し、次にボックスサイズで座標を「飽和」させて、ポイントに最も近いボックス上のポイントを見つけます。私は試したことはありませんが、私には正しいように見えます。

0
Rob Agar