查找重心坐标的最有效方法是什么?


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次(或类似的时间)并衡量性能。您可以编写一个确保它小于某个值(例如10s)的测试,也可以仅使用它来对新旧实现进行基准测试。
ashes999 '02

Answers:


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

这实际上是克莱默求解线性系统的法则。您将不会比这效率更高—如果这仍然是一个瓶颈(可能是:它看起来与当前算法在计算方式上没有太大不同),则可能需要找到其他地方获得加速。

请注意,此处的许多值与p无关-必要时可以将其与三角形一起缓存。


7
操作次数可能会变成红色鲱鱼。在现代CPU上,它们如何依赖和计划很重要。 始终测试假设和性能“改进”。
肖恩·米德迪奇

1
如果仅查看标量数学运算,则上述两个版本在关键路径上的延迟几乎相同。我喜欢这一点的地方是,只需为两个浮点数支付空间,就可以从关键路径上刮除一个减法和一个除法。是那个值得吗?只有性能测试才能肯定知道…
John Calsbeek

1
他描述了如何在第137-138页上获得“关于三角形到点的最近点”部分的内容
bobobobo

1
小注释:p此函数没有参数。
巴特2014年

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

这直接解决了2x2线性系统

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 2014年

1
如果是2D模式,这是最好的方法。只是一个小改进:应该计算分母的倒数,以便使用两个乘法和一个除法而不是两个除法。
rubik '16

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

我将使用John发布的解决方案,但我将使用SSS 4.2点内在函数和sse rcpss内在函数进行划分,前提是您可以将自己限制在Nehalem和更新的流程上并且精度有限。

或者,您可以使用sse或avx一次计算4个或8倍加速来计算多个重心坐标。


1

您可以通过投影其中一个轴对齐的平面并使用user5302提出的方法将3D问题转换为2D问题。只要您确保三角形不投影到一条直线上,这将导致重心坐标完全相同。最好是投影到与您的Triagle方向尽可能接近的轴对齐平面。这样可以避免共线性问题,并确保最大的精度。

其次,您可以预先计算分母并为每个三角形存储它。这样可以节省以后的计算。


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

那么一个点“点”的lambda

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的重心权重。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.