KENTEM TechBlog

建設業のDXを実現するKENTEMの技術ブログです。

【Unity】多角形の内外判定を行う方法 #2 (角度を用いる)

前回の記事では指定された点が多角形の内部に存在するか否かを、ベクトルの外積を用いて判定しました。

本記事では角度を求めることで判定する仕組みについて紹介します。

本記事に関して

多角形の内外判定を行う方法のシリーズの2本目です。

UIや必要な事前準備は同じであるため、最初の記事からご覧いただけると理解しやすいと考えております。

この記事では1回目の記事でも用いたIsPolygonInsideメソッドで多角形の内外判定を行っている箇所のみを紹介します。

シリーズ目次

  1. 【Unity】多角形の内外判定を行う方法 #1 (外積を用いる) tech.kentem.jp
  2. 【Unity】多角形の内外判定を行う方法 #2 (角度を用いる) ←本記事 tech.kentem.jp
  3. 【Unity】多角形の内外判定を行う方法 #3 (平面を用いる) tech.kentem.jp
  4. 【Unity】多角形の内外判定を行う方法 #4 (Ray Casting Algorithmを用いる) tech.kentem.jp

角度を用いた多角形の内外判定

本記事では角度を求める方法を2つ説明します。

数値計算を行い角度を計算する方法

仕組みやアルゴリズムは末尾に示したURLを参考に行いました。

仕組み

① 判定対象の点を始点とし、多角形の各辺の両端の点を終点とするベクトルを置きます。 ここでは下の写真のように2本のベクトル  \boldsymbol{a} \boldsymbol{b} を置きます。

② 次にこの2本のベクトルが成す角を求めます。内積の式

 \boldsymbol{a} \cdot \boldsymbol{b} = \| \boldsymbol{a}  \|     \| \boldsymbol{b} \| \cos \theta

から \thetaを求めます。

③ 次に、角度の向きを求めます。

先ほどの方法では2本のベクトルが成す角度はわかりましたが、反時計回りなのか、時計回りなのかを求めることはできていません。

そのため、❶~❸の操作を行います。

❶ 2本のベクトルの外積を求める
❷ ❶で求めたベクトルとカメラの視点方向のベクトルの内積を求めます。

〇 点0、判定対象の点、点1の成す角を求める場合を考えます。
この場合、時計回りであるものとして計算してほしいです。
2本のベクトルが逆方向であり、内積は負の値であることが分かります。

〇 また、点1、判定対象の点、点2の成す角を求める場合を考えます。
下図のような配置の場合、反時計回りであるものとして計算してほしいです。
2本のベクトルが同じ方向であり、内積は正の値であることが分かります。
❸ ❷の結果から、内積の正負により角度に方向の情報を付ける。

④正負を考慮した角度の合計値を計算します。

このとき、多角形の内部にある場合は合計の角度が360度になります。

一方で上の写真の状態から下のように点を移動させ、多角形の外部に存在する場合を考えます。

③で角度に向きをつけたことにより下図のように合計の角度が0度になることが分かります。

そこで、360度ぴったりであれば多角形の内部、という判定が思いつきます。

しかし、そのように判定してしまうと浮動小数点の誤差が発生して正しく判定できないことがあります。

今回想定しているケースでは360度か0度にしかならないため、180度より大きければ多角形の内部、小さければ外部であると判定しました。

ソースコード

private bool IsPolygonInside(Vector3 mousePosition)
{
    var count = _polygon.Vertices.Count;

    var totalAngle = 0f;
    for (int i = 0; i < count; i++)
    {
        var currentIndex = i;
        var nextIndex = (i + 1) % count;

        // 上図のa,bのベクトル
        var a = _polygon.Vertices[nextIndex] - mousePosition;
        var b = _polygon.Vertices[currentIndex] - mousePosition;

       // aとbのベクトルの余弦
        var cosTheta = Vector3.Dot(a, b) / (a.magnitude * b.magnitude);

        // aとbのベクトルを挟んでいる角度
        var theta = Mathf.Acos(cosTheta) * Mathf.Rad2Deg;

        // aとbのベクトルの外積
        var cross = Vector3.Cross(a, b);

       // 正負がついた角度を得る
        var sign = Vector3.Dot(cross, _camera.transform.forward);

        if (sign > 0) theta = -theta;

        totalAngle += theta;
    }
    // 角度の合計が0度か360度のいずれかであり、360度の方であれば多角形の内部であるためtrueを返す
    return Mathf.Abs(totalAngle) >= 180;
}

Unityで標準搭載されている機能を用いる方法

Unityが用意しているVector3.SignedAngleを用いると正負を考慮した角度が得られるため、thetaをシンプルに求めることも可能です。

var theta = Vector3.SignedAngle(a, b, _camera.transform.forward);
totalAngle += theta;

参考URL

technical-notes.com

おわりに

KENTEMでは、様々な拠点でエンジニアを大募集しています! 建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。 recruit.kentem.jp career.kentem.jp