ゲーム開発における高度なベクトル数学

航空機

内積には、単位ベクトルを持つもう1つの興味深い特性があります。ベクトルに垂直な(そして原点を通過する)平面が平面を通過すると想像してください。平面は、空間全体を正の数(平面上)と負の数(平面の下)に分割します。(一般的な考えとは異なり)、2Dで数学演算を使用することもできます。

../../_images/tutovec10.png

表面に垂直な単位ベクトル(したがって、表面の方向を表します)は、単位法線ベクトルと呼ばれます。ただし、通常、それらは単に法線と呼ばれます。法線は、飛行機、3Dジオメトリ(各面または頂点の壁を決定するため)などに表示されます。通常は単位ベクトルですが、その使用法から通常と呼ばれます。((0,0)を原点と呼ぶように)。

とてもシンプルなようです。平面は原点を通過し、その表面は単位ベクトル(または法線)に垂直です。ポインティングベクトルの一方の側は正の半空間であり、もう一方の側は負の半空間です。3Dでは、これはまったく同じですが、平面が線ではなく無限のサーフェス(原点に向けて固定できる無限の平らな紙を想像してください)です。

飛行機までの距離

飛行機が何であるかが明確になったので、ドット積に戻りましょう。単位ベクトルと空間内の任意の点の間の内積(はい、今回はベクトルと位置の間の内積を行います)、点から平面までの距離を返します。

var distance = normal.Dot(point);

ただし、絶対距離だけでなく、ポイントが負の半空間にある場合、距離も負になります。

../../_images/tutovec11.png

これにより、ポイントが平面のどちら側にあるかを知ることができます。

原点から離れて

私はあなたが何を考えているか知っています!これまでのところ、これは悪いことではありませんが、実際の飛行機は、原点を通過するだけでなく、宇宙のいたるところにあります。あなたは本当の飛行機の行動を望んでいます、あなたは今それを望んでいます。

平面は空間を2つの部分に分割するだけでなく、極性も持っていることを忘れないでください。これは、完全に重なり合う平面が存在する可能性があることを意味しますが、それらの負の半空間と正の半空間は交換されます。

これを念頭に置いて、平面全体を原点からの法線NとスカラーDの間の距離として説明しましょう。したがって、平面はNとDで表されます。例えば:

../../_images/tutovec12.png

3D数学の場合、Godotは処理するプレーン組み込みタイプを提供します。

基本的に、NとDは、2Dか3Dか(Nの次元に応じて)、空間内の任意の平面を表すことができ、2つの数式は同じです。前と同じですが、Dは原点から平面までの距離であり、N方向に移動します。たとえば、平面上の特定のポイントに到達したい場合は、次のようにします。

var pointInPlane = N * D;
これにより、法線ベクトルが引き伸ばされ(サイズが変更され)、平面に接触します。この数学演算は紛らわしいように見えるかもしれませんが、実際には見た目よりもはるかに単純です。点から平面までの距離をもう一度言いたい場合は、同じことを行うことができます。距離を調整するだけです。

var distance = N.Dot(point) - D;

組み込み関数を使用した同じ操作:

var distance = plane.DistanceTo(point);

これにより、正または負の距離が再び返されます。

平面の極性は、NとDの両方を負にすることで反転できます。これにより、平面は同じ位置になりますが、負と正の半角反転が発生します。

N = -N;
D = -D;

もちろん、Godotはこの演算子をPlaneに実装することもできるので、次のようにしてください。

var invertedPlane = -plane;

期待どおりに動作します。

したがって、飛行機はこのようなものであり、その主な実用的な用途は飛行機までの距離を計算することであることを忘れないでください。では、なぜ点から平面までの距離を計算することが役立つのでしょうか。これはとても便利です!いくつかの簡単な例を見てみましょう。

2Dで平面を作成します

飛行機は明らかにどこからも出てこないので、建設する必要があります。それらを2Dで構築するのは、法線(単位ベクトル)と1つの点から、または空間内の2つの点から簡単です。

法線と点については、法線がすでに計算されているため、ほとんどの作業が完了しているため、法線と点の内積に基づいてDを計算するだけで済みます。

var N = normal;
var D = normal.Dot(point);

空間内の2つのポイントの場合、実際にはそれらを通過する2つの平面があり、それらは同じスペースを共有しますが、法線は反対方向を指します。2点から法線を計算するには、最初に方向ベクトルを取得してから、それをいずれかの側に90°回転させる必要があります。

// Calculate vector from `a` to `b`.
var dvec = (pointB - pointA).Normalized();
// Rotate 90 degrees.
var normal = new Vector2(dvec.y, -dvec.x);
// Alternatively (depending the desired side of the normal):
// var normal = new Vector2(-dvec.y, dvec.x);

point_aまたはpoint_bは両方とも同じ平面上にあり、両方とも機能するため、残りは前の例と同じです。

var N = normal;
var D = normal.Dot(pointA);
// this works the same
// var D = normal.Dot(pointB);

3Dモードで同じ操作を実行すると、少し複雑になります。これについては、後で詳しく説明します。

飛行機のいくつかの例

これは、平面が役立つ簡単な例です。凸多角形があるとします。たとえば、長方形、台形、三角形、または内側に湾曲していないポリゴンなどです。

ポリゴンの各セグメントについて、そのセグメントを通過する平面を計算します。平面のリストができたら、ポイントがポリゴンの内側にあるかどうかを確認するなど、きちんとしたことを行うことができます。

すべての平面をトラバースし、ポイントまでの距離が正の平面を見つけることができれば、そのポイントはポリゴンの外側にあります。できない場合は、ポイントは内側にあります。

../../_images/tutovec13.png

コードは次のようになります。

var inside = true;
foreach (var p in planes)
{
    
    
    // check if distance to plane is positive
    if (p.DistanceTo(point) > 0)
    {
    
    
        inside = false;
        break; // with one that fails, it's enough
    }
}

かっこいいじゃないですか。しかし、これは良くなるでしょう!少しの努力で、2つの凸多角形も重なると、同様のロジックで通知されます。これは分離軸定理(またはSAT)と呼ばれ、ほとんどの物理エンジンはこれを使用して衝突を検出します。

ポイントの場合、航空機が正の距離を返しているかどうかを確認するだけで、ポイントが外側にあるかどうかを判断できます。別のポリゴンの場合、他のすべてのポリゴンポイントが正の距離を返す平面を見つける必要があります。チェックは、Bの点を基準にしたAの平面を使用して実行され、次にAの点を基準にしたBの平面を使用して実行されます。

../../_images/tutovec14.png

コードは次のようになります。

var overlapping = true;

foreach (Plane plane in planesOfA)
{
    
    
    var allOut = true;
    foreach (Vector3 point in pointsOfB)
    {
    
    
        if (plane.DistanceTo(point) < 0)
        {
    
    
            allOut = false;
            break;
        }
    }

    if (allOut)
    {
    
    
        // a separating plane was found
        // do not continue testing
        overlapping = false;
        break;
    }
}

if (overlapping)
{
    
    
    // only do this check if no separating plane
    // was found in planes of A
    foreach (Plane plane in planesOfB)
    {
    
    
        var allOut = true;
        foreach (Vector3 point in pointsOfA)
        {
    
    
            if (plane.DistanceTo(point) < 0)
            {
    
    
                allOut = false;
                break;
            }
        }

        if (allOut)
        {
    
    
            overlapping = false;
            break;
        }
    }
}

if (overlapping)
{
    
    
    GD.Print("Polygons Collided!");
}

ご覧のとおり、飛行機はとても便利です。これは氷山の一角です。非凸多角形はどうなるのか疑問に思われるかもしれません。通常、凹多角形を小さな凸多角形に分割するか、BSP(現在はあまり使用されていません)などの手法を使用して処理できます。

3D衝突検出

これは別の報酬であり、このチュートリアルの忍耐とコンプライアンスに対する報酬です。これは別の知恵です。これは直接的な使用例ではないかもしれませんが(Godotは衝突検出を非常にうまく行っています)、ほとんどすべての物理エンジンと衝突検出ライブラリがそれを使用しています:)

2Dの凸形状を2Dフラットアレイに変換すると、衝突検出に役立つことを覚えていますか?ポイントが任意の凸形状内にあるかどうか、または2つの2D凸形状が重なっているかどうかを検出できます。

これは3Dにも当てはまります。2つの3D多面体形状が衝突すると、分離面を見つけることができなくなります。分離面が見つかった場合、形状が衝突することはありません。

少しリフレッシュすると、分離平面とは、ポリゴンAのすべての頂点が平面の片側にあり、ポリゴンBのすべての頂点が反対側にあることを意味します。この平面は、常に面Aまたは面Bの端面の1つです。

しかし、3Dでは、分離面が見つからない場合があるため、この方法には問題があります。これはこの状況の例です:

../../_images/tutovec22.png

この状況を回避するには、セパレータとしていくつかの追加の平面をテストする必要があります。これらの平面は、面Aのエッジと面Bのエッジの間の外積です。

../../_images/tutovec23.png

したがって、最終的なアルゴリズムは次のとおりです。

var overlapping = true;

foreach (Plane plane in planesOfA)
{
    
    
    var allOut = true;
    foreach (Vector3 point in pointsOfB)
    {
    
    
        if (plane.DistanceTo(point) < 0)
        {
    
    
            allOut = false;
            break;
        }
    }

    if (allOut)
    {
    
    
        // a separating plane was found
        // do not continue testing
        overlapping = false;
        break;
    }
}

if (overlapping)
{
    
    
    // only do this check if no separating plane
    // was found in planes of A
    foreach (Plane plane in planesOfB)
    {
    
    
        var allOut = true;
        foreach (Vector3 point in pointsOfA)
        {
    
    
            if (plane.DistanceTo(point) < 0)
            {
    
    
                allOut = false;
                break;
            }
        }

        if (allOut)
        {
    
    
            overlapping = false;
            break;
        }
    }
}

if (overlapping)
{
    
    
    foreach (Vector3 edgeA in edgesOfA)
    {
    
    
        foreach (Vector3 edgeB in edgesOfB)
        {
    
    
            var normal = edgeA.Cross(edgeB);
            if (normal.Length() == 0)
            {
    
    
                continue;
            }

            var maxA = float.MinValue; // tiny number
            var minA = float.MaxValue; // huge number

            // we are using the dot product directly
            // so we can map a maximum and minimum range
            // for each polygon, then check if they
            // overlap.

            foreach (Vector3 point in pointsOfA)
            {
    
    
                var distance = normal.Dot(point);
                maxA = Mathf.Max(maxA, distance);
                minA = Mathf.Min(minA, distance);
            }

            var maxB = float.MinValue; // tiny number
            var minB = float.MaxValue; // huge number

            foreach (Vector3 point in pointsOfB)
            {
    
    
                var distance = normal.Dot(point);
                maxB = Mathf.Max(maxB, distance);
                minB = Mathf.Min(minB, distance);
            }

            if (minA > maxB || minB > maxA)
            {
    
    
                // not overlapping!
                overlapping = false;
                break;
            }
        }

        if (!overlapping)
        {
    
    
            break;
        }

    }
}

if (overlapping)
{
    
    
    GD.Print("Polygons Collided!");
}

詳しくは

Godotでのベクトル演算の使用の詳細については、次の記事を参照してください。

行列と変換
追加の説明が必要な場合は、3Blue1Brownのすばらしいビデオシリーズ「線形代数の本質」をご覧くださいhttps//www.youtube.com/watch?v = fNk_zzaMoSs&list = PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab

おすすめ

転載: blog.csdn.net/qq_44273429/article/details/111060722