วิธีที่มีประสิทธิภาพมากที่สุดในการค้นหาพิกัด barycentric คืออะไร


45

ใน profiler ของฉันการค้นหาพิกัด barycentric นั้นค่อนข้างจะเป็นปัญหาคอขวด ฉันกำลังมองหาเพื่อให้มีประสิทธิภาพมากขึ้น

มันเป็นไปตามวิธีการในเชอร์ลี่ย์ที่คุณคำนวณพื้นที่ของรูปสามเหลี่ยมที่เกิดขึ้นโดยการฝังจุด P ที่อยู่ภายในรูปสามเหลี่ยม

Bary

รหัส:

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
ระวังว่าวิธีแก้ปัญหาที่มีประสิทธิภาพที่สุดอาจมีความแม่นยำน้อยที่สุด
Peter Taylor

ฉันขอแนะนำให้คุณทำการทดสอบหน่วยเพื่อเรียกวิธีนี้ ~ 100k ครั้ง (หรือบางอย่างที่คล้ายกัน) และวัดประสิทธิภาพ คุณสามารถเขียนการทดสอบที่ทำให้แน่ใจว่ามันมีค่าน้อยกว่าค่า (เช่น 10s) หรือคุณสามารถใช้เพื่อเปรียบเทียบมาตรฐานเก่ากับการนำไปใช้ใหม่
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
# การดำเนินการอาจเป็นปลาเฮอริ่งแดง วิธีการทำงานและการกำหนดเวลามีความสำคัญมากสำหรับซีพียูสมัยใหม่ ทดสอบสมมติฐานและประสิทธิภาพการทำงาน "ปรับปรุง" เสมอ
Sean Middleditch

1
คำถามทั้งสองเวอร์ชันมีเวลาแฝงที่เหมือนกันเกือบเท่ากันบนเส้นทางวิกฤติหากคุณดูเฉพาะคณิตศาสตร์สเกลาร์ สิ่งที่ฉันชอบเกี่ยวกับอันนี้ก็คือโดยการจ่ายพื้นที่เพียงแค่สองลอยคุณสามารถโกนหนึ่งลบและหารหนึ่งจากเส้นทางที่สำคัญ คือว่าคุ้มค่าหรือไม่ มีเพียงการทดสอบประสิทธิภาพเท่านั้นที่รู้แน่นอน…
John Calsbeek

1
เขาอธิบายว่าเขาได้รับสิ่งนี้อย่างไรในหน้า 137-138 โดยมีหัวข้อที่ "จุดที่ใกล้ที่สุดบนจุดต่อรูปสามเหลี่ยม"
bobobobo

1
หมายเหตุเล็กน้อย: ไม่มีอาร์กิวเมนต์pสำหรับฟังก์ชันนี้
บาร์ต

2
หมายเหตุการใช้งานเล็กน้อย: หากทั้ง 3 คะแนนอยู่ด้านบนของกันและกันคุณจะได้รับข้อผิดพลาด "หารด้วย 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

โซลูชันที่คุณเสนอไม่ได้ตั้งสมมติฐานเกี่ยวกับ.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 โพสต์ แต่ฉันจะใช้ SSS 4.2 dot intrinsic และ sse rcpss หารภายในที่แท้จริงโดยสมมติว่าคุณตกลงกับ Nehalem และกระบวนการที่ใหม่กว่าและความแม่นยำที่ จำกัด

หรือคุณสามารถคำนวณพิกัด barycentric หลายตัวพร้อมกันโดยใช้ sse หรือ avx สำหรับการเร่งความเร็ว 4 หรือ 8x


1

คุณสามารถแปลงปัญหา 3 มิติของคุณให้เป็นปัญหา 2 มิติโดยการฉายหนึ่งในระนาบแนวแกนและใช้วิธีที่เสนอโดย user5302 สิ่งนี้จะส่งผลให้พิกัด barycentric เท่ากันทุกประการตราบใดที่คุณตรวจสอบให้แน่ใจว่าสามเหลี่ยมของคุณไม่ฉายเป็นเส้น ที่ดีที่สุดคือการฉายภาพให้กับระนาบแนวแกนที่ใกล้เคียงกับทิศทางของ 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:

Precalc สำหรับสามเหลี่ยม:

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 คุณสามารถรับน้ำหนัก barycentric ของจุด C โดยการหารพื้นที่ของคำย่อ ABN ด้วยพื้นที่ทั้งหมดของสามเหลี่ยม AB C

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.