Який найефективніший спосіб знайти барицентричні координати?


45

У моєму профіле знаходження барицентричних координат, мабуть, є вузьким місцем. Я хочу зробити це більш ефективним.

З цього методу слід в ширлі , де ви обчислюємо площу трикутників, утворених шляхом вбудовування точки Р всередині трикутника.

барі

Код:

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
Будьте уважні, що найефективніші рішення можуть бути найменш точними.
Пітер Тейлор

Я пропоную вам зробити одиничний тест, щоб викликати цей метод ~ 100 тис. Разів (або щось подібне) і виміряти продуктивність. Ви можете написати тест, який гарантує, що воно менше деякого значення (наприклад, 10s), або ви можете використовувати його просто для порівняння старого та нового впровадження.
ashes999

Відповіді:


54

Переписано з детектора зіткнень у реальному часі Крістера Ерікона (що, до речі, є чудовою книгою):

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

Це фактично правило Крамера для вирішення лінійної системи. Ви не отримаєте набагато ефективнішого, ніж це - якщо це все-таки вузьке місце (і це може бути: це не схоже на те, що це значно відрізняється від обчислень, ніж ваш поточний алгоритм), вам, ймовірно, потрібно буде знайти якесь інше місце щоб отримати швидкість.

Зауважте, що пристойна кількість значень тут не залежить від p - вони можуть бути кешовані трикутником, якщо це необхідно.


7
# операції може бути червоною оселедцем. Те, наскільки вони залежні і графіки, дуже важливі для сучасних процесорів. завжди тестуйте припущення та результати "покращення".
Шон Міддлічч

1
Ці дві версії мають майже однакову затримку на критичному шляху, якщо ви дивитесь лише на скалярні математичні операції. Що мені подобається в цьому, це те, що, заплативши простір лише за два поплавці, ви можете поголити одне віднімання та одне ділення з критичного шляху. Є чи, що стоїть? Тільки тест на працездатність точно знає…
Джон Calsbeek

1
Він описує, як він це отримав на сторінці 137-138 з розділом "Найближча точка від трикутника до точки"
bobobobo

1
Незначна примітка: аргументу pна цю функцію немає.
Барт

2
Примітка щодо незначної реалізації: Якщо всі 3 бали знаходяться один над одним, ви отримаєте помилку "ділити на 0", тому обов'язково перевірте, чи є цей випадок у фактичному коді.
frodo2975

9

Правило Крамера має бути найкращим способом його вирішення. Я не графік, але мені було цікаво, чому в книзі Виявлення зіткнень у реальному часі вони не роблять наступної простішої речі:

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

Це безпосередньо вирішує лінійну систему 2х2

v v0 + w v1 = v2

тоді як метод із книги вирішує систему

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

Чи не пропонується вашим рішенням припущення щодо третього ( .z) виміру (зокрема, його не існує)?
Cornstalks

1
Тут найкращий метод, якщо він працює в 2D. Лише незначне вдосконалення: слід обчислити зворотний знаменник, щоб використовувати два множення та одне ділення замість двох ділення.
rubik

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 внутрішнього для розділення, припускаючи, що ви добре обмежуєте себе Нехалемом та новішими процесами та обмеженою точністю.

Крім того, ви можете обчислити кілька барицентричних координат одночасно, використовуючи sse або avx для 4 або 8-кратного прискорення.


1

Ви можете перетворити свою 3D-проблему в двовимірну задачу, спроектувавши одну з вирівнюваних по осі площин і скориставшись методом, запропонованим user5302. Це призведе до таких самих барицентричних координат, якщо ви переконаєтесь, що ваш трикутник не виступає в лінію. Найкраще проектувати на площину, орієнтовану на вісь, максимально наближену до орієнтації вашого трикутника. Це дозволяє уникнути проблем з лінійністю та забезпечити максимальну точність.

По-друге, ви можете попередньо обчислити знаменник і зберегти його для кожного трикутника. Це економить обчислення після цього.


1

Я спробував скопіювати @ NielW-код на C ++, але не отримав правильних результатів.

Простіше було прочитати https://en.wikipedia.org/wiki/Barycentric_coordinate_system#Barycentric_coordinate_on_triangles та обчислити лямбда1 / 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

Для заданої точки N всередині трикутника ABC ви можете отримати барицентричну вагу точки C, поділивши площу підрядника ABN на загальну площу трикутника AB C.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.