Barycentric koordinatlarını bulmanın en etkili yolu nedir?


45

Profilcimde, barycentric koordinatlarını bulmak görünüşte bir tıkanıklığı var. Daha verimli hale getirmek için arıyorum.

Shirley'deki metodu izler , burada P noktasını üçgen içine yerleştirerek oluşan üçgen alanını hesaplarsınız.

bary

Kod:

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 ;
}

Bu yöntem işe yarıyor ama ben daha verimli bir tane arıyorum!


2
En verimli çözümlerin en az doğru olabileceğine dikkat edin.
Peter Taylor,

Bu yöntemi ~ 100k kez (veya benzer bir şey) çağırmak ve performansı ölçmek için bir birim testi yapmanızı öneririm. Bunun bir değerden daha az olmasını sağlayan bir test yazabilirsiniz (örneğin, 10s) veya eski ile yeni uygulamaların karşılaştırılması için kullanabilirsiniz.
ashes999

Yanıtlar:


54

Christer Ericson'ın Gerçek Zamanlı Çarpışma Tespiti'nden (bu arada, mükemmel bir kitap) yazılmıştır:

// 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;
}

Bu etkili bir şekilde Cramer'in doğrusal bir sistemi çözme kuralıdır. Bundan daha verimli olmayacaksınız - eğer bu hala bir tıkanıklık ise (ve olabilir: mevcut algoritmanızdan çok farklı bir hesaplama bilgisine benzemiyor), muhtemelen başka bir yer bulmanız gerekecektir. hızlanmak için.

Buradaki sayısız değerin, p'den bağımsız olduğunu, gerekirse üçgenle önbelleklenebileceklerini unutmayın.


7
işlem sayısı kırmızı bir ringa balığı olabilir. Bağımlı olmaları ve zamanlamaları nasıl modern CPU'lar için çok önemlidir. daima varsayımları ve performansı "iyileştirmeleri" test edin .
Sean Middleditch

1
Söz konusu iki sürüm, yalnızca skaler matematik işlemlerine bakıyorsanız, kritik yoldaki neredeyse aynı gecikmeye sahip. Bundan hoşlandığım şey, sadece iki yüzen için yer ödeyerek, kritik yoldan bir çıkarma ve bir bölmeyi tıraş edebilmeniz. Mı o değer o? Sadece bir performans testi kesin olarak biliyor…
John Calsbeek

1
Bunu 137-138. Sayfada “üçgene noktaya en yakın nokta” bölümü ile
açıkladı

1
Küçük not: pBu fonksiyon için bir tartışma yoktur .
Bart

2
Küçük uygulama notu: 3 noktanın tümü üst üste gelirse, "0'a böl" hatası alırsınız, bu durumda gerçek koddaki durumu kontrol ettiğinizden emin olun.
frodo2975

9

Cramer'in kuralı, onu çözmenin en iyi yolu olmalı. Grafik bir adam değilim, ama Gerçek Zamanlı Çarpışma Tespiti kitabında neden aşağıdaki basit şeyi yapmadıklarını merak ediyordum:

// 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;
}

Bu doğrudan 2x2 doğrusal sistemi çözer

v v0 + w v1 = v2

Kitaptaki yöntem sistemi çözerken

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

Önerilen çözümünüz üçüncü ( .z) boyutla ilgili varsayımlarda bulunmuyor mu (özellikle var olmadığı)?
Mısır

1
Biri 2D olarak çalışıyorsa, bu en iyi yöntemdir. Sadece küçük bir gelişme: İki çarpma ve iki bölme yerine bir bölme kullanmak için, payda karşılığının hesaplanması gerekir.
rubik

8

Biraz daha hızlı: Paydayı önceden hesaplayın ve bölmek yerine çarpın. Bölünmeler çarpmalardan çok daha pahalıdır.

// 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;
}

Ancak benim uygulamamda, tüm bağımsız değişkenleri önbelleğe aldım. Aşağıdakileri yapıcıda önceden hesaplarım:

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

Böylece son kod şöyle görünür:

// 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

John'un yayınladığı çözümü kullanırdım, ancak kendinizi Nehalem ve daha yeni süreçlerle ve sınırlı hassasiyetle kısıtladığınızı varsayarsak, SSS 4.2 nokta içsel ve sse rcps'lerin içsel olarak kullandıklarını kullanırdım.

Alternatif olarak, 4 veya 8x hızlandırma için sse veya avx kullanarak birkaç barycentrik koordinatı bir kerede hesaplayabilirsiniz.


1

Eksen hizalı düzlemlerden birini yansıtarak 3B probleminizi 2B soruna dönüştürebilir ve user5302 tarafından önerilen yöntemi kullanabilirsiniz. Bu, üçgeninizin bir çizgiye yansıtmadığından emin olduğunuz sürece, tamamen aynı briccentric koordinatlarına neden olur. En iyisi, üçgeninizin yönüne mümkün olduğunca yakın olan eksen hizalı düzlemine yansıtmaktır. Bu birlikte doğrusallık problemlerini önler ve maksimum doğruluk sağlar.

İkincisi, paydayı önceden hesaplayabilir ve her üçgen için saklayabilirsiniz. Bu daha sonra hesaplamaları kaydeder.


1

@ NielW'nin kodunu C ++ 'a kopyalamaya çalıştım ama doğru sonuç alamadım.

Https://en.wikipedia.org/wiki/Barycentric_coordinate_system#Barycentric_coordinates_on_triangles sayfasını okumak ve lambda1 / 2/3 'ü verilen şekilde hesaplamak daha kolaydı (vektör işlevlerine gerek yok).

P (0..2), x / y / z ile üçgenin noktaları ise:

Üçgen için prekalc:

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));

o zaman bir "nokta" noktası için olan lambdalar

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 üçgeni içindeki belirli bir N noktası için, ABN alt bölgesini AB toplamının üç C bölgesi ile bölerek C noktasının barycentrik ağırlığını elde edebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.