무게 중심 좌표를 찾는 가장 효율적인 방법은 무엇입니까?


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의 실시간 충돌 감지 ( 실수로 훌륭한 책임) 에서 발췌 :

// 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에서 중요합니다. 항상 가정과 성능 "개선"을 테스트하십시오 .
Sean Middleditch

1
문제의 두 버전은 스칼라 연산 연산 만보고있는 경우 임계 경로에서 거의 동일한 대기 시간을 갖습니다. 이것에 대해 내가 좋아하는 것은 단지 두 개의 플로트에 대한 공간을 지불함으로써 임계 경로에서 하나의 빼기 및 하나의 나누기를 면도 할 수 있다는 것입니다. 가 가치는? 단지 성능 테스트는 확실히 알고 ...
존 Calsbeek

1
그는 "삼각형에서 가장 가까운 점"에 대한 섹션과 함께 137-138 페이지에이 정보를 어떻게 얻었는지 설명합니다.
bobobobo

1
사소한 참고 : p이 기능에 대한 논쟁은 없습니다 .
Bart

2
사소한 구현 참고 사항 : 세 지점 모두가 서로 위에 있으면 "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

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

John이 게시 한 솔루션을 사용하지만 Nehalem과 최신 프로세스 및 제한된 정밀도로 자신을 제한한다고 가정하면 SSS 4.2 도트 내장 및 sse rcpss를 나누기에 본질적으로 사용합니다.

또는 4 배 또는 8 배 속도 향상을 위해 sse 또는 avx를 사용하여 여러 중심 중심 좌표를 한 번에 계산할 수 있습니다.


1

축 정렬 평면 중 하나를 투영하여 3D 문제를 2D 문제로 변환하고 user5302가 제안한 방법을 사용할 수 있습니다. 삼각형이 선으로 투영되지 않는 한 정확히 같은 무게 중심 좌표가됩니다. 가장 좋은 것은 트라이앵글의 방향에 최대한 가까운 축 정렬 평면에 투영하는 것입니다. 이렇게하면 공선 성 문제를 피하고 최대 정확도를 보장 할 수 있습니다.

두 번째로 분모를 미리 계산하여 각 삼각형에 저장할 수 있습니다. 이렇게하면 나중에 계산이 저장됩니다.


1

@NielW의 코드를 C ++에 복사하려고 시도했지만 올바른 결과를 얻지 못했습니다.

https://en.wikipedia.org/wiki/Barycentric_coordinate_system#Barycentric_coordinates_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

삼각형 ABC 내부의 주어진 점 N에 대해, 서브 트라이앵글 ABN의 면적을 삼각형 AB C의 총 면적으로 나눔으로써 점 C의 무게 중심 무게를 얻을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.