ポイントが回転した長方形の内側にあるかどうかを効率的に確認するにはどうすればよいですか?


11

最適化のための部分、学習目的のための部分、私は敢えて質問します:C#またはC ++を使用して、2DポイントPが2D回転長方形の中にあるかどうかを最も効率的に確認するにはどうすればよいXYZWですか?

現在、私がやっていることは、本の「リアルタイム衝突検出」にある「ポイントイントライアングル」アルゴリズムを使用して、それを2回実行しています(長方形を構成する2つの三角形、たとえばXYZとXZW)。

bool PointInTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P)
{
 // Compute vectors        
 Vector2 v0 = C - A;
 Vector2 v1 = B - A;
 Vector2 v2 = P - A;

 // Compute dot products
 float dot00 = Vector2.Dot(v0, v0);
 float dot01 = Vector2.Dot(v0, v1);
 float dot02 = Vector2.Dot(v0, v2);
 float dot11 = Vector2.Dot(v1, v1);
 float dot12 = Vector2.Dot(v1, v2);

 // Compute barycentric coordinates
 float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
 float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
 float v = (dot00 * dot12 - dot01 * dot02) * invDenom;

 // Check if point is in triangle
 if(u >= 0 && v >= 0 && (u + v) < 1)
    { return true; } else { return false; }
}


bool PointInRectangle(Vector2 X, Vector2 Y, Vector2 Z, Vector2 W, Vector2 P)
{
 if(PointInTriangle(X,Y,Z,P)) return true;
 if(PointInTriangle(X,Z,W,P)) return true;
 return false;
}

ただ、もっとすっきりした、速い方法があるのではないかと感じています。具体的には、数学演算の数を減らすため。


あなたは多くの点を持っていますか、それともあなたは多くの長方形を持っていますか?これは、このような小さなタスクを最適化する前に自問すべき最初の質問です。
sam hocevar 2015年

いい視点ね。非常に多くのポイントがありますが、チェックする長方形はさらに多くなります。
Louis15、2015年

回転した長方形までの点の距離を見つけることに関する関連質問。これはその退化したケースです(距離が0の場合のみチェック)。もちろん、ここには適用されない最適化があります。
あんこ

ポイントを長方形の参照フレームに回転させることを検討しましたか?
Richard Tingle 2015年

@RichardTingle実は私は最初はしませんでした。後で私はそうしました、それは私がそれが以下に与えられる答えの一つに関連していると思うので。しかし、明確にするために:あなたが提案していることでは、ポイントを長方形の参照フレームに回転させた後、max.x、min.xなどの間の論理比較によって包含をチェックする必要がありますか?
Louis15 '10 / 10/26

回答:


2

簡単で簡単な最適化は、の最終条件を変更することですPointInTriangle

bool PointInRectangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P) {
  ...
  if(u >= 0 && v >= 0 && u <= 1 && v <= 1)
      { return true; } else { return false; }
  }
}

コードはPointInRectangleすでにほとんど(u + v) < 1ありましたが、四角形の「2番目の」三角形にないかどうかを確認するための条件がありました。

または、isLeft(ページの最初のコード例、大幅に説明されている)テストを4回実行して、すべてが同じ符号で結果を返すことを確認することもできます(これは、ポイントが時計回りまたは反時計回りのどちらで与えられたかによって異なります)。内側になるポイント。これは、他の凸多角形でも機能します。

float isLeft( Point P0, Point P1, Point P2 )
{
    return ( (P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y) );
}
bool PointInRectangle(Vector2 X, Vector2 Y, Vector2 Z, Vector2 W, Vector2 P)
{
    return (isLeft(X, Y, P) > 0 && isLeft(Y, Z, P) > 0 && isLeft(Z, W, P) > 0 && isLeft(W, X, p) > 0);
}

見事。私の提案よりもずっと速くてエレガントな提案が好きかどうか、または私のPointInTriコードが簡単にPointInRecになることに気付いた方がいいかどうかはわかりません。ありがとう
Louis15

isLeftメソッドの+1 。トリガー関数を必要としないため(必要Vector2.Dot)、物事を大幅に高速化します。
Anko、

ところで、メイン関数内にisLeftを直接含め、 "&&"演算子を "||"で置き換えることにより、コードを調整できませんでした(テストしていませんでした。このコンピューターではできません)。逆論理を介して? public static bool PointInRectangle(Vector2 P, Vector2 X, Vector2 Y, Vector2 Z, Vector2 W) { return !(( (Y.x - X.x) * (P.y - X.y) - (P.x - X.x) * (Y.y - X.y) ) < 0 || ( (Z.x - Y.x) * (P.y - Y.y) - (P.x - Y.x) * (Z.y - Y.y) ) < 0 || ( (W.x - Z.x) * (P.y - Z.y) - (P.x - Z.x) * (W.y - Z.y) ) < 0 || ( (X.x - W.x) * (P.y - W.y) - (P.x - W.x) * (X.y - W.y) ) < 0 ); }
Louis15、2015年

1
@ Louis15あなたがする必要があるとは思いません-&&と||の両方 1つの否定/肯定が見つかった場合(または別の理由があった場合)、以降のステートメントの実行を停止します。isLeftインラインとして宣言すると、コンパイラーは同様のことを行います(おそらく、コンパイラーを作成しているエンジニアがCPUを最もよく知っていて、最速のオプションを選択しているため、可能な限り優れています)。
ウォンドラ、2015年

8

編集: OPのコメントは、任意の2Dポイントが回転および/または移動する長方形内にあるかどうかをチェックするためにアルゴリズムを改善するために提案された負の円形境界チェックの効率について懐疑的です。私の2Dゲームエンジン(OpenGL / C ++)を少しいじって、OPの現在のポイントインレクタングルチェックアルゴリズム(およびバリエーション)に対するアルゴリズムのパフォーマンスベンチマークを提供することで、私の答えを補足します。

当初はアルゴリズムをそのままにしておくことをお勧めしました(ほぼ最適であるため)が、単なるゲームロジックによって単純化します。(2)距離チェックを行い、ポイントが指定された円内にあるかどうかを確認します。(3)OPまたはその他の簡単なアルゴリズムを使用します(別の回答で提供されているisLeftアルゴリズムをお勧めします)。私の提案の背後にある論理は、ポイントが円内にあるかどうかをチェックすることは、回転した長方形やその他のポリゴンの境界チェックよりもかなり効率的であるということです。

ベンチマークテストの最初のシナリオは、約20の回転/移動する四角形で満たされた制約されたスペースで、出現および非表示の多数のドット(ゲームループごとに位置が変わる)を実行することです。説明のためにビデオ(YouTubeリンク)を公開しました。パラメータに注意してください:ランダムに出現するドットの数、数または長方形。次のパラメーターでベンチマークを行います。

OFF:円境界否定チェックなしでOPによって提供される単純なアルゴリズム

オン:最初の除外チェックとして、長方形の周りの処理済み(境界)の円を使用します

ON +スタックスタック上のループ内で実行時に円の境界を作成します

ON +平方距離:平方距離をさらに最適化して使用し、より高価な平方根アルゴリズム(Pieter Geerkens)を使用しないようにします。

ここで要約、ループを反復処理するのにかかる時間を示すことによって、異なるアルゴリズムの諸性能のは。

ここに画像の説明を入力してください

X軸は、ドットを追加することで複雑さが増していることを示しています(したがって、ループの速度が低下しています)。(たとえば、ランダムに現れるポイントチェックで、20の四角形の限られたスペースでポイントチェックを行うと、ループが反復し、アルゴリズムを20000回呼び出します。)y軸は、高解像度を使用してループ全体を完了するのにかかる時間(ミリ秒)を示します。パフォーマンスタイマー。高fpsを利用してスムーズなアニメーションを補間しないため、まともなゲームでは20ミリ秒を超えると問題が発生し、ゲームが「荒れている」ように見える場合があります。

結果1:ループ内で高速ネガティブチェックを行う前処理された循環バインドアルゴリズムにより、通常のアルゴリズム(チェックなしの元のループ時間の5%)と比較してパフォーマンスが1900%向上します。結果は、ループ内の反復回数にほぼ比例するため、ランダムに出現するポイントを10個または10000個チェックするかどうかは重要ではありません。したがって、この図では、パフォーマンスの低下を感じることなく、オブジェクトの数を安全に10kまで増やすことができます。

結果2:以前のコメントで、アルゴリズムは高速であるがメモリを大量に消費する可能性があることが示唆されています。ただし、前処理された円のサイズの浮動小数点数を格納するには、わずか4バイトしかかかりません。OPが100000以上のオブジェクトを同時に実行することを計画していない限り、これは実際の問題にはなりません。別のメモリ効率的なアプローチは、ループ内のスタックの円の最大サイズを計算し、反復ごとにスコープから外すことで、速度の未知の代償としてメモリ使用量を実質的になくします。実際、結果は、このアプローチが実際に前処理された円のサイズを使用するよりも遅いことを示していますが、それでも約1150%(つまり、元の処理時間の8%)の大幅なパフォーマンスの向上を示しています。

結果3:実際の距離の代わりに平方距離を使用し、計算コストの高い平方根演算を行うことで、結果1アルゴリズムをさらに改善します。これにより、パフォーマンスがわずかに向上します(2400%)。(注:平方根近似の前処理された配列のハッシュテーブルも試しますが、結果は似ていますが少し悪い結果になります)

結果4:長方形の移動/衝突をさらにチェックします。ただし、論理チェックは基本的に同じままなので、これは基本的な結果(予想どおり)を変更しません。

結果5:四角形の数を変化させ、スペースが埋まるほど混雑していなければ、アルゴリズムがさらに効率的になることを発見しました(デモには示されていません)。円とオブジェクトの境界の間の小さなスペース内に点が現れる確率が低下するため、結果もある程度予想されます。もう1つの極端な例として、同じ閉じ込められた小さなスペース内で長方形の数を100に増やし、実行時にループ内でサイズを動的に変化させます(sin(iterator))。これは依然としてパフォーマンスが非常によく、パフォーマンスは570%(または元のループ時間の15%)向上しています。

結果6:ここで提案された代替アルゴリズムをテストしたところ、パフォーマンスにわずかな違いはありますが、有意差はありません(2%)。興味深く、より単純なIsLeftアルゴリズムは、パフォーマンスが17%(元の計算時間の85%)向上しますが、クイックネガティブチェックアルゴリズムの効率にはほど遠いです。

私のポイントは、特に境界と衝突イベントを扱う場合、最初に無駄のない設計とゲームロジックを検討することです。OPの現在のアルゴリズムはすでにかなり効率的であり、さらに最適化することは、基礎となる概念自体を最適化することほど重要ではありません。さらに、アルゴリズムの効率はそれらに大きく依存するため、ゲームの範囲と目的を伝えることは良いことです。

単純なコードを見ただけでは実際のランタイムパフォーマンスの真実が明らかにならない場合があるため、ゲーム設計段階では常に複雑なアルゴリズムのベンチマークを試みることをお勧めします。たとえば、マウスカーソルが長方形内にあるかどうかをテストしたい場合、またはオブジェクトの大部分がすでに接触している場合に、提案されたアルゴリズムはここでも必要ないかもしれません。ポイントチェックの大部分が長方形内にある場合、アルゴリズムの効率は低下します。(ただし、「内側の円」の境界を二次的なネガティブチェックとして確立することは可能です。)円/球の境界チェックは、間にスペースがある自然な多数のオブジェクトの適切な衝突検出に非常に役立ちます。 。

Rec Points  Iter    OFF     ON     ON_Stack     ON_SqrDist  Ileft Algorithm (Wondra)
            (ms)    (ms)    (ms)    (ms)        (ms)        (ms)
20  10      200     0.29    0.02    0.04        0.02        0.17
20  100     2000    2.23    0.10    0.20        0.09        1.69
20  1000    20000   24.48   1.25    1.99        1.05        16.95
20  10000   200000  243.85  12.54   19.61       10.85       160.58

私は変わったアプローチが好きで、ダヴィンチのリファレンスが好きでしたが、半径を言うまでもなく、円を扱うことはそれほど効率的だとは思いません。また、その解決策は、すべての長方形が固定され、事前に既知である場合にのみ妥当です
Louis15

長方形の位置を固定する必要はありません。相対座標を使用します。このようにも考えてください。その半径は、回転に関係なく同じままです。
Majte 2015年

これは素晴らしい答えです。私はそれを考えていなかったので、なお良いでしょう。実際の距離の代わりに距離の2乗を使用すれば十分であり、平方根を計算する必要がなくなることに注意してください。
Pieter Geerkens、2015年

迅速なポジティブ/ネガティブテストのための興味深いアルゴリズム!問題は、前処理されたバウンディングサークル(および幅)を保存するための余分なメモリである可能性があります。ヒューリスティックになる可能性がありますが、使用が制限されることにも注意してください。前処理する時間があります。
2015年

編集+ベンチマークテストを追加。
Majte、2015年

2

4点の長方形を定義すると、台形を作成できます。ただし、x、y、幅、高さ、およびその中央を中心とする回転で定義する場合は、長方形の逆回転(同じ原点の周り)で確認している点を回転させ、それが元の長方形に。


うーん、提案に感謝しますが、回転して逆回転を取得することはそれほど効率的ではないようです。実際、それは私のソリューションほど効率的ではありません-言うまでもなく、Wondraの
Louis15 Oct15

マトリックスを使用して3Dポイントを回転すると、6つの乗算と3つの加算、および1つの関数呼び出しになります。@wondraのソリューションはせいぜい同等ですが、意図がはるかに明確ではありません。さらに、DRY違反によるメンテナンスエラーの影響を受けやすい
Pieter Geerkens、2015年

@Pieter Geerkensの主張は、私のソリューションのどれがDRYにどのように違反しているのか(そしてDRYは主要なプログラミング原則の1つですか?今まで聞いたことがない)?そして最も重要なことに、これらのソリューションにはどのようなエラーがありますか?常に学ぶ準備ができています。
2015年

@wondra:DRY =自分を繰り返さないでください。コードスニペットは、標準のmatrix-application-to-Vectorメソッドを呼び出すのではなく、コード内の機能が現れるすべての場所でベクトル乗算によってマトリックスの詳細をコーディングすることを提案します。
Pieter Geerkens、2015年

@PieterGeerkensもちろん、それはその一部のみを提案します-1)明示的にマトリックスを持っていません(各クエリに新しいマトリックスを割り当てると、パフォーマンスに大きな影響を与えます)2)ジェネリックの膨らみを落とすために最適化された特定のケースの乗算のみを使用します1。これは低レベルの操作であり、予期しない動作を防ぐためにカプセル化されたままにする必要があります。
2015年

1

私はこれをベンチマークする時間はありませんでしたが、私の提案は、長方形を0から1までのxとyの範囲で軸に揃えられた正方形に変換する変換行列を保存することです。長方形の1つのコーナーを(0,0)に変換し、反対側のコーナーを(1,1)に変換します。

もちろん、長方形が頻繁に移動され、衝突がめったにチェックされない場合、これはよりコストがかかりますが、長方形の更新よりも多くのチェックがある場合、少なくとも2つの三角形に対してテストする元のアプローチよりも高速になります。 6つのドット積が1つの行列乗算に置き換えられるためです。

しかし、いつものように、このアルゴリズムの速度は、実行する予定のチェックの種類に大きく依存します。ほとんどのポイントが長方形に近づいていない場合でも、単純な距離チェック(たとえば(point.x-firstCorner.x)> aLargeDistance)を実行すると、速度が大幅に向上する可能性があります。ポイントは長方形の内側にあります。

編集:これは私の長方形クラスがどのようになるかです:

class Rectangle
{
public:
    Matrix3x3 _transform;

    Rectangle()
    {}

    void setCorners(Vector2 p_a, Vector2 p_b, Vector2 p_c)
    {
        // create a matrix from the two edges of the rectangle
        Vector2 edgeX = p_b - p_a;
        Vector2 edgeY = p_c - p_a;

        // and then create the inverse of that matrix because we want to 
        // transform points from world coordinates into "rectangle coordinates".
        float scaling = 1/(edgeX._x*edgeY._y - edgeY._x*edgeX._y);

        _transform._columns[0]._x = scaling * edgeY._y;
        _transform._columns[0]._y = - scaling * edgeX._y;
        _transform._columns[1]._x = - scaling * edgeY._x;
        _transform._columns[1]._y = scaling * edgeX._x;

        // the third column is the translation, which also has to be transformed into "rectangle space"
        _transform._columns[2]._x = -p_a._x * _transform._columns[0]._x - p_a._y * _transform._columns[1]._x;
        _transform._columns[2]._y = -p_a._x * _transform._columns[0]._y - p_a._y * _transform._columns[1]._y;
    }

    bool isInside(Vector2 p_point)
    {
        Vector2 test = _transform.transform(p_point);
        return  (test._x>=0)
                && (test._x<=1)
                && (test._y>=0)
                && (test._y<=1);
    }
};

これは私のベンチマークの完全なリストです:

#include <cstdlib>
#include <math.h>
#include <iostream>

#include <sys/time.h>

using namespace std;

class Vector2
{
public:
    float _x;
    float _y;

    Vector2()
    :_x(0)
    ,_y(0)
    {}

    Vector2(float p_x, float p_y)
        : _x (p_x)
        , _y (p_y)
        {}

    Vector2 operator-(const Vector2& p_other) const
    {
        return Vector2(_x-p_other._x, _y-p_other._y);
    }

    Vector2 operator+(const Vector2& p_other) const
    {
        return Vector2(_x+p_other._x, _y+p_other._y);
    }

    Vector2 operator*(float p_factor) const
    {
        return Vector2(_x*p_factor, _y*p_factor);
    }

    static float Dot(Vector2 p_a, Vector2 p_b)
    {
        return (p_a._x*p_b._x + p_a._y*p_b._y);
    }
};

bool PointInTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P)
{
 // Compute vectors        
 Vector2 v0 = C - A;
 Vector2 v1 = B - A;
 Vector2 v2 = P - A;

 // Compute dot products
 float dot00 = Vector2::Dot(v0, v0);
 float dot01 = Vector2::Dot(v0, v1);
 float dot02 = Vector2::Dot(v0, v2);
 float dot11 = Vector2::Dot(v1, v1);
 float dot12 = Vector2::Dot(v1, v2);

 // Compute barycentric coordinates
 float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
 float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
 float v = (dot00 * dot12 - dot01 * dot02) * invDenom;

 // Check if point is in triangle
 if(u >= 0 && v >= 0 && (u + v) < 1)
    { return true; } else { return false; }
}


bool PointInRectangle(Vector2 X, Vector2 Y, Vector2 Z, Vector2 W, Vector2 P)
{
 if(PointInTriangle(X,Y,Z,P)) return true;
 if(PointInTriangle(X,Z,W,P)) return true;
 return false;
}

class Matrix3x3
{
public:
    Vector2 _columns[3];

    Vector2 transform(Vector2 p_in)
    {
        return _columns[0] * p_in._x + _columns[1] * p_in._y + _columns[2];
    }
};

class Rectangle
{
public:
    Matrix3x3 _transform;

    Rectangle()
    {}

    void setCorners(Vector2 p_a, Vector2 p_b, Vector2 p_c)
    {
        // create a matrix from the two edges of the rectangle
        Vector2 edgeX = p_b - p_a;
        Vector2 edgeY = p_c - p_a;

        // and then create the inverse of that matrix because we want to 
        // transform points from world coordinates into "rectangle coordinates".
        float scaling = 1/(edgeX._x*edgeY._y - edgeY._x*edgeX._y);

        _transform._columns[0]._x = scaling * edgeY._y;
        _transform._columns[0]._y = - scaling * edgeX._y;
        _transform._columns[1]._x = - scaling * edgeY._x;
        _transform._columns[1]._y = scaling * edgeX._x;

        // the third column is the translation, which also has to be transformed into "rectangle space"
        _transform._columns[2]._x = -p_a._x * _transform._columns[0]._x - p_a._y * _transform._columns[1]._x;
        _transform._columns[2]._y = -p_a._x * _transform._columns[0]._y - p_a._y * _transform._columns[1]._y;
    }

    bool isInside(Vector2 p_point)
    {
        Vector2 test = _transform.transform(p_point);
        return  (test._x>=0)
                && (test._x<=1)
                && (test._y>=0)
                && (test._y<=1);
    }
};

void runTest(float& outA, float& outB)
{
    Rectangle r;
    r.setCorners(Vector2(0,0.5), Vector2(0.5,1), Vector2(0.5,0));

    int numTests = 10000;

    Vector2 points[numTests];

    Vector2 cornerA[numTests];
    Vector2 cornerB[numTests];
    Vector2 cornerC[numTests];
    Vector2 cornerD[numTests];

    bool results[numTests];
    bool resultsB[numTests];

    for (int i=0; i<numTests; ++i)
    {
        points[i]._x = rand() / ((float)RAND_MAX);
        points[i]._y = rand() / ((float)RAND_MAX);

        cornerA[i]._x = rand() / ((float)RAND_MAX);
        cornerA[i]._y = rand() / ((float)RAND_MAX);

        Vector2 edgeA;
        edgeA._x = rand() / ((float)RAND_MAX);
        edgeA._y = rand() / ((float)RAND_MAX);

        Vector2 edgeB;
        edgeB._x = rand() / ((float)RAND_MAX);
        edgeB._y = rand() / ((float)RAND_MAX);

        cornerB[i] = cornerA[i] + edgeA;
        cornerC[i] = cornerA[i] + edgeB;
        cornerD[i] = cornerA[i] + edgeA + edgeB;
    }

    struct timeval start, end;

    gettimeofday(&start, NULL);
    for (int i=0; i<numTests; ++i)
    {
        r.setCorners(cornerA[i], cornerB[i], cornerC[i]);
        results[i] = r.isInside(points[i]);
    }
    gettimeofday(&end, NULL);
    float elapsed = (end.tv_sec - start.tv_sec)*1000;
    elapsed += (end.tv_usec - start.tv_usec)*0.001;
    outA += elapsed;

    gettimeofday(&start, NULL);
    for (int i=0; i<numTests; ++i)
    {
        resultsB[i] = PointInRectangle(cornerA[i], cornerB[i], cornerC[i], cornerD[i], points[i]);
    }
    gettimeofday(&end, NULL);
    elapsed = (end.tv_sec - start.tv_sec)*1000;
    elapsed += (end.tv_usec - start.tv_usec)*0.001;
    outB += elapsed;
}

/*
 * 
 */
int main(int argc, char** argv) 
{
    float a = 0;
    float b = 0;

    for (int i=0; i<5000; i++)
    {
        runTest(a, b);
    }

    std::cout << "Result: " << a << " / " << b << std::endl;

    return 0;
}

コードは確かに美しくありませんが、大きなバグはすぐにはわかりません。そのコードを使用すると、各チェック間で長方形を移動すると、私のソリューションが約2倍速くなるという結果が得られます。それが動かない場合、私のコードは5倍以上速いようです。

コードの使用方法がわかっている場合は、変換とチェックを2つの次元に分離することで、コードをもう少し高速化することもできます。たとえば、レーシングゲームでは、多くの障害物が車の前または後ろにありますが、車の右側または左側にはほとんど障害物がないため、最初に運転方向を指す座標を確認する方が速くなります。


興味深いですが、マトリックスの回転をドットにも適用する必要があることを忘れないでください。ゲームエンジンに行列の腐敗演算があり、後でアルゴリズムをベンチマークできます。あなたの最後のコメントに関して。次に、「内側の円」も定義し、ドットが上記のように内側の円の外側と外側の円の内側にあるかどうかを二重否定チェックします。
Majte

はい、ほとんどのポイントが三角形の中央に近いと予想される場合に役立ちます。長方形のレーストラックのような状況を想像していました。たとえば、キャラクターが内側にとどまる必要のある外側の長方形と、外側に留まる必要のある小さい内側の長方形を使用して、長方形のパスを定義します。その場合、すべてのチェックが長方形の境界に近くなり、それらの円のチェックはおそらくパフォーマンスを悪化させるだけです。確かに、これは具体的な例ですが、実際に起こり得ることだと思います。
Lars Kokemohr、2015年

そういうことが起こりえますそうです アルゴリズムに対抗するためのスイートスポットは何でしょうか。最後にそれはあなたの目的に要約されます。時間がある場合は、OPポストを使用してコードをポストできます。アルゴリズムをベンチマークできますか?あなたの直感が正しいかどうか見てみましょう。IsLeftアルゴリズムに対するあなたのアイデアのパフォーマンスに興味があります。
Majte、2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.