重心座標を見つける最も効率的な方法は何ですか?


45

私のプロファイラーでは、重心座標を見つけることは明らかにボトルネックです。私はそれをより効率的にしたいと思っています。

shirleyの方法に従い、三角形内に点Pを埋め込むことにより形成される三角形の面積を計算します。

バリー

コード:

Vector Triangle::getBarycentricCoordinatesAt( const Vector & P ) const
{
  Vector bary ;

  // The area of a triangle is 
  real areaABC = DOT( normal, CROSS( (b - a), (c - a) )  ) ;
  real areaPBC = DOT( normal, CROSS( (b - P), (c - P) )  ) ;
  real areaPCA = DOT( normal, CROSS( (c - P), (a - P) )  ) ;

  bary.x = areaPBC / areaABC ; // alpha
  bary.y = areaPCA / areaABC ; // beta
  bary.z = 1.0f - bary.x - bary.y ; // gamma

  return bary ;
}

この方法は機能しますが、より効率的な方法を探しています!


2
最も効率的なソリューションは、最も精度が低い可能性があることに注意してください。
ピーターテイラー

このメソッドを100k回(または同様の)呼び出してパフォーマンスを測定する単体テストを作成することをお勧めします。ある値(10秒など)未満であることを確認するテストを作成するか、単に古い実装と新しい実装のベンチマークに使用できます。
-ashes999

回答:


54

Christer EricsonのReal-Time Collision Detection(偶然にも素晴らしい本です)から転記:

// Compute barycentric coordinates (u, v, w) for
// point p with respect to triangle (a, b, c)
void Barycentric(Point p, Point a, Point b, Point c, float &u, float &v, float &w)
{
    Vector v0 = b - a, v1 = c - a, v2 = p - a;
    float d00 = Dot(v0, v0);
    float d01 = Dot(v0, v1);
    float d11 = Dot(v1, v1);
    float d20 = Dot(v2, v0);
    float d21 = Dot(v2, v1);
    float denom = d00 * d11 - d01 * d01;
    v = (d11 * d20 - d01 * d21) / denom;
    w = (d00 * d21 - d01 * d20) / denom;
    u = 1.0f - v - w;
}

これは事実上、線形システムを解くためのCramerのルールです。これよりもはるかに効率が良くなることはありません。これがまだボトルネックである場合(そして、現在のアルゴリズムとは計算上それほど異なっていないように見えるかもしれません)、おそらく他の場所を見つける必要がありますスピードアップを得るために。

ここでの適切な数の値はpに依存しないことに注意してください。必要に応じて三角形でキャッシュできます。


7
操作の数は赤いニシンにすることができます。それらがどのように依存し、スケジュールが最新のCPUで重要であるか。 仮定とパフォーマンスの「改善」を常にテストします。
ショーンミドルディッチ

1
問題の2つのバージョンは、スカラーの数学演算のみを見ている場合、クリティカルパスでほぼ同一のレイテンシを持ちます。これについて私が気に入っているのは、2つのフロートだけにスペースを支払うことで、クリティカルパスから1つの減算と1つの除算を剃ることができるということです。であることの価値は?パフォーマンステストだけが確実に知っている…
ジョンカルスビーク

1
彼はこれをどのようにして137-138ページで「三角形からポイントへの最も近いポイント」
-bobobobo

1
マイナーノート:pこの関数には引数はありません。
バート14

2
マイナー実装ノート:3つのポイントすべてが互いに重なり合っている場合、「0で除算」エラーが発生するため、実際のコードでそのケースを必ず確認してください。
frodo2975

9

Cramerのルールは、それを解決する最良の方法であるはずです。私はグラフィックの男ではありませんが、「Real-Time Collision Detection」の本で、なぜ次のような単純なことをしないのか疑問に思っていました。

// Compute barycentric coordinates (u, v, w) for
// point p with respect to triangle (a, b, c)
void Barycentric(Point p, Point a, Point b, Point c, float &u, float &v, float &w)
{
    Vector v0 = b - a, v1 = c - a, v2 = p - a;
    den = v0.x * v1.y - v1.x * v0.y;
    v = (v2.x * v1.y - v1.x * v2.y) / den;
    w = (v0.x * v2.y - v2.x * v0.y) / den;
    u = 1.0f - v - w;
}

これは2x2線形システムを直接解決します

v v0 + w v1 = v2

本のメソッドはシステムを解決します

(v v0 + w v1) dot v0 = v2 dot v0
(v v0 + w v1) dot v1 = v2 dot v1

提案されたソリューションは、3番目の(.z)ディメンション(具体的には、それが存在しないということ)について仮定していませんか?
Cornstalks 14

1
2Dで作業している場合、これが最良の方法です。ほんのわずかな改善:2つの除算の代わりに2つの乗算と1つの除算を使用するには、分母の逆数を計算する必要があります。
ルービック

8

わずかに高速:分母を事前計算し、除算の代わりに乗算します。除算は乗算よりもはるかに高価です。

// Compute barycentric coordinates (u, v, w) for
// point p with respect to triangle (a, b, c)
void Barycentric(Point a, Point b, Point c, float &u, float &v, float &w)
{
    Vector v0 = b - a, v1 = c - a, v2 = p - a;
    float d00 = Dot(v0, v0);
    float d01 = Dot(v0, v1);
    float d11 = Dot(v1, v1);
    float d20 = Dot(v2, v0);
    float d21 = Dot(v2, v1);
    float invDenom = 1.0 / (d00 * d11 - d01 * d01);
    v = (d11 * d20 - d01 * d21) * invDenom;
    w = (d00 * d21 - d01 * d20) * invDenom;
    u = 1.0f - v - w;
}

ただし、実装では、すべての独立変数をキャッシュしました。コンストラクターで以下を事前計算します。

Vector v0;
Vector v1;
float d00;
float d01;
float d11;
float invDenom;

したがって、最終的なコードは次のようになります。

// Compute barycentric coordinates (u, v, w) for
// point p with respect to triangle (a, b, c)
void Barycentric(Point a, Point b, Point c, float &u, float &v, float &w)
{
    Vector v2 = p - a;
    float d20 = Dot(v2, v0);
    float d21 = Dot(v2, v1);
    v = (d11 * d20 - d01 * d21) * invDenom;
    w = (d00 * d21 - d01 * d20) * invDenom;
    u = 1.0f - v - w;
}

2

ジョンが投稿したソリューションを使用しますが、除算にはSSS 4.2ドット組み込み関数とsse rcpss組み込み関数を使用します。これは、Nehalem以降のプロセスと限定された精度に制限することで問題ないことを前提としています。

あるいは、sseまたはavxを使用して4倍または8倍の速度向上を実現するために、複数の重心座標を一度に計算できます。


1

軸に整列した平面の1つを投影し、user5302が提案する方法を使用することにより、3D問題を2D問題に変換できます。これにより、三角形が線に投影されないことを確認する限り、まったく同じ重心座標になります。最良の方法は、トライアグルの方向にできるだけ近い軸に沿った平面に投影することです。これにより、共直線性の問題が回避され、最大の精度が確保されます。

次に、分母を事前に計算し、各三角形に保存できます。これにより、後で計算が保存されます。


1

@NielWのコードをC ++にコピーしようとしましたが、正しい結果が得られませんでした。

https://en.wikipedia.org/wiki/Barycentric_coordinate_system#Barycentric_coordinates_on_trianglesを読み、そこに記載されているようにlambda1 / 2/3を計算するのが簡単 でした(ベクトル関数は不要です)。

p(0..2)がx / y / zの三角形のポイントの場合:

三角形の事前計算:

double invDET = 1./((p(1).y-p(2).y) * (p(0).x-p(2).x) + 
                   (p(2).x-p(1).x) * (p(0).y-p(2).y));

次に、ポイント「ポイント」のラムダは

double l1 = ((p(1).y-p(2).y) * (point.x-p(2).x) + (p(2).x-p(1).x) * (point.y-p(2).y)) * invDET; 
double l2 = ((p(2).y-p(0).y) * (point.x-p(2).x) + (p(0).x-p(2).x) * (point.y-p(2).y)) * invDET; 
double l3 = 1. - l1 - l2;

0

三角形ABC内の特定のポイントNについて、サブ三角形ABNの面積を三角形AB Cの合計面積で割ることにより、ポイントCの重心重量を取得できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.