Qual è il modo più efficiente per trovare coordinate baricentriche?


45

Nel mio profiler, trovare coordinate baricentriche è apparentemente un po 'un collo di bottiglia. Sto cercando di renderlo più efficiente.

Segue il metodo in shirley , dove si calcola l'area dei triangoli formati incorporando il punto P all'interno del triangolo.

Bary

Codice:

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

Questo metodo funziona, ma ne sto cercando uno più efficiente!


2
Attenzione che le soluzioni più efficienti potrebbero essere le meno accurate.
Peter Taylor,

Ti suggerisco di effettuare un test unitario per chiamare questo metodo ~ 100k volte (o qualcosa di simile) e misurare le prestazioni. Puoi scrivere un test che assicuri che sia inferiore a un certo valore (ad es. 10s), oppure puoi usarlo semplicemente per confrontare la vecchia implementazione con quella nuova.
ashes999,

Risposte:


54

Trascritto dal rilevamento delle collisioni in tempo reale di Christer Ericson (che, per inciso, è un libro eccellente):

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

Questa è effettivamente la regola di Cramer per risolvere un sistema lineare. Non otterrai molto più efficiente di questo — se questo è ancora un collo di bottiglia (e potrebbe essere: non sembra che sia molto diverso dal punto di vista computazionale rispetto al tuo attuale algoritmo), probabilmente dovrai trovare un altro posto per ottenere uno speedup.

Nota che qui un numero decente di valori è indipendente da p: possono essere memorizzati nella cache con il triangolo, se necessario.


7
Il numero di operazioni può essere un'aringa rossa. Il modo in cui dipendono e le pianificazioni conta molto sulle CPU moderne. testare sempre ipotesi e "miglioramenti" delle prestazioni.
Sean Middleditch,

1
Le due versioni in questione hanno una latenza quasi identica sul percorso critico, se stai solo guardando operazioni matematiche scalari. La cosa che mi piace di questo è che pagando spazio per due soli galleggianti, puoi radere una sottrazione e una divisione dal percorso critico. E che vale la pena? Solo un test delle prestazioni lo sa per certo ...
John Calsbeek,

1
Descrive come l'ha ottenuto a pagina 137-138 con la sezione "punto più vicino sul triangolo da puntare"
bobobobo

1
Nota minore: non ci sono argomenti pper questa funzione.
Bart,

2
Nota di implementazione minore: se tutti e 3 i punti sono uno sopra l'altro, verrà visualizzato un errore di "divisione per 0", quindi assicurati di verificare il caso nel codice effettivo.
frodo2975,

9

La regola di Cramer dovrebbe essere il modo migliore per risolverlo. Non sono un ragazzo grafico, ma mi chiedevo perché nel libro Rilevamento delle collisioni in tempo reale non abbiano fatto la seguente cosa più semplice:

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

Questo risolve direttamente il sistema lineare 2x2

v v0 + w v1 = v2

mentre il metodo del libro risolve il sistema

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

La tua soluzione proposta non fa ipotesi sulla terza ( .z) dimensione (in particolare, che non esiste)?
Cornstalks,

1
Questo è il metodo migliore qui se si lavora in 2D. Solo un piccolo miglioramento: si dovrebbe calcolare il reciproco del denominatore per usare due moltiplicazioni e una divisione anziché due divisioni.
Rubik,

8

Leggermente più veloce: pre-calcola il denominatore e moltiplica invece di dividere. Le divisioni sono molto più costose delle moltiplicazioni.

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

Nella mia implementazione, tuttavia, ho memorizzato nella cache tutte le variabili indipendenti. Ho precalcolato quanto segue nel costruttore:

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

Quindi il codice finale è simile al seguente:

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

Vorrei usare la soluzione che John ha pubblicato, ma vorrei usare il punto SSS 4.2 intrinseco e ss rcpss intrinseco per la divisione, supponendo che tu ti stia limitando a Nehalem e ai processi più recenti e alla precisione limitata.

In alternativa, è possibile calcolare contemporaneamente più coordinate baricentriche utilizzando sse o avx per uno speedup 4 o 8x.


1

È possibile convertire il problema 3D in un problema 2D proiettando uno dei piani allineati agli assi e utilizzare il metodo proposto dall'utente5302. Ciò comporterà esattamente le stesse coordinate baricentriche purché tu ti assicuri che il tuo triangolo non si proietti in una linea. La cosa migliore è proiettare sul piano allineato all'asse il più vicino possibile all'orientamento del triagle. Ciò evita problemi di co-linearità e garantisce la massima precisione.

In secondo luogo è possibile pre-calcolare il denominatore e memorizzarlo per ciascun triangolo. Ciò consente di salvare i calcoli in seguito.


1

Ho provato a copiare il codice di @ NielW in C ++, ma non ho ottenuto risultati corretti.

È stato più facile leggere https://en.wikipedia.org/wiki/Barycentric_coordinate_system#Barycentric_coordinates_on_triangles e calcolare lambda1 / 2/3 come indicato lì (non sono necessarie funzioni vettoriali).

Se p (0..2) sono i punti del triangolo con x / y / z:

Precalc per triangolo:

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

allora le lambda per un "punto" punto sono

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

Per un dato punto N all'interno del triangolo ABC, è possibile ottenere il peso baricentrico del punto C dividendo l'area del sottotiangolo ABN per l'area totale del triangolo AB C.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.