बेरेंट्रिक निर्देशांक खोजने का सबसे कुशल तरीका क्या है?


45

मेरे प्रोफाइलर में, बेरेंट्रिक निर्देशांक ढूंढना स्पष्ट रूप से कुछ हद तक अड़चन है। मैं इसे और अधिक कुशल बनाने के लिए देख रहा हूं।

यह शर्ली में विधि का पालन करता है , जहां आप त्रिकोण के अंदर बिंदु 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
सावधान रहें कि सबसे कुशल समाधान कम से कम सटीक हो सकता है।
पीटर टेलर

मेरा सुझाव है कि आप इस पद्धति को कॉल करने के लिए एक इकाई परीक्षण करें ~ 100k बार (या कुछ इसी तरह) और प्रदर्शन को मापें। आप एक परीक्षण लिख सकते हैं जो यह सुनिश्चित करता है कि यह कुछ मूल्य (जैसे। 10 एस) से कम है, या आप इसे पुराने बनाम नए कार्यान्वयन के लिए बस उपयोग कर सकते हैं।
3

जवाबों:


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

यह एक रैखिक प्रणाली को हल करने के लिए प्रभावी रूप से Cramer का नियम है। आपको इससे अधिक कुशल नहीं मिलेगा - अगर यह अभी भी एक अड़चन है (और यह हो सकता है: ऐसा नहीं लगता है कि यह आपके वर्तमान एल्गोरिथ्म की तुलना में बहुत अलग गणना-वार है), तो आपको शायद कुछ और जगह ढूंढनी होगी एक गति हासिल करने के लिए।

ध्यान दें कि यहाँ मानों की एक सभ्य संख्या p से स्वतंत्र है - यदि आवश्यक हो तो त्रिकोण के साथ कैश किया जा सकता है।


7
# ऑपरेशन एक लाल हेरिंग हो सकता है। वे कैसे निर्भर हैं और शेड्यूल आधुनिक सीपीयू पर बहुत मायने रखता है। हमेशा परीक्षण मान्यताओं और प्रदर्शन "सुधार।"
शॉन मिडिलडाइच

1
यदि आप केवल स्केलर गणित ऑप्स को देख रहे हैं, तो प्रश्न के दो संस्करणों में महत्वपूर्ण पथ पर लगभग समान विलंबता है। इस एक के बारे में मुझे जो बात पसंद है, वह यह है कि केवल दो फ्लोट्स के लिए जगह देकर, आप एक सबट्रैक्ट को और एक डिवीजन को महत्वपूर्ण पथ से शेव कर सकते हैं। क्या वह इसके लायक है? केवल एक प्रदर्शन परीक्षण सुनिश्चित करने के लिए जानता है ...
जॉन Calsbeek

1
वह वर्णन करता है कि उसे यह पेज 137-138 पर "त्रिकोण से बिंदु पर निकटतम बिंदु" पर अनुभाग के साथ कैसे मिला
bobobobo

1
माइनर नोट: pइस फ़ंक्शन का कोई तर्क नहीं है ।
बार्ट

2
माइनर कार्यान्वयन नोट: यदि सभी 3 अंक एक-दूसरे के ऊपर हैं, तो आपको "0 से विभाजित" त्रुटि मिलेगी, इसलिए वास्तविक कोड में उस मामले की जांच करना सुनिश्चित करें।
frodo2975 20

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) आयाम (विशेष रूप से, यह मौजूद नहीं है) के बारे में धारणाएं बनाता है ?
कॉर्नस्टालक्स

1
अगर यहां 2 डी में काम किया जाता है तो यह सबसे अच्छा तरीका है। बस एक मामूली सुधार: एक को दो विभाजनों के बजाय दो गुणा और एक विभाजन का उपयोग करने के लिए भाजक के पारस्परिक गणना करना चाहिए।
माणिक

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

मैं जॉन द्वारा पोस्ट किए गए समाधान का उपयोग करूंगा, लेकिन मैं एसएसएस 4.2 डॉट इंट्रिंसिक और एसएससी आरपीपीएस को आंतरिक फॉरेह डिवाइड का उपयोग करूंगा, यह मानते हुए कि आप खुद को नेहेलम और नई प्रक्रियाओं और सीमित परिशुद्धता तक सीमित कर रहे हैं।

वैकल्पिक रूप से आप 4 या 8x स्पीडअप के लिए एक बार sse या avx का उपयोग करते हुए कई barycentric निर्देशांक की गणना कर सकते हैं।


1

आप अपनी 3 डी समस्या को अक्ष-संरेखित विमानों में से किसी एक को प्रक्षेपित करके 2D समस्या में बदल सकते हैं और user5302 द्वारा प्रस्तावित विधि का उपयोग कर सकते हैं। यह बिल्कुल उसी तरह के बेरेंट्रिक निर्देशांक में परिणाम देगा जब तक आप यह सुनिश्चित करते हैं कि आपका त्रिकोण एक पंक्ति में प्रोजेक्ट नहीं करता है। अक्ष-संरेखित विमान के लिए सबसे अच्छा प्रोजेक्ट है जो आपके त्रिकोण के उन्मुखीकरण के जितना करीब हो सके। यह सह-रैखिकता की समस्याओं से बचने और अधिकतम सटीकता सुनिश्चित करता है।

दूसरे आप हर को पूर्व-गणना कर सकते हैं और इसे प्रत्येक त्रिकोण के लिए स्टोर कर सकते हैं। यह बाद में संगणना बचाता है।


1

मैंने C ++ में @ NielW के कोड को कॉपी करने की कोशिश की, लेकिन मुझे सही परिणाम नहीं मिले।

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

त्रिभुज ABC के अंदर दिए गए बिंदु N के लिए, आप त्रिभुज AB C के कुल क्षेत्रफल द्वारा उप-वर्ग ABN के क्षेत्र को विभाजित करके बिंदु C का द्विसंयोजक भार प्राप्त कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.