क्या n वस्तुओं की प्रणाली की टक्कर की जांच दक्षता बढ़ाने का एक तरीका है?


9

मैं एक गेम बना रहा हूं जिसमें कई ऑनस्क्रीन ऑब्जेक्ट हैं, जिनमें से एक खिलाड़ी है। मुझे यह जानने की जरूरत है कि कौन सी वस्तुएं हर पुनरावृत्ति से टकरा रही हैं।

मैंने कुछ इस तरह बनाया:

for (o in objects)
{
   o.stuff();
   for (other in objects)
      if (collision(o, other))
          doStuff();

   bla.draw();
}

यह O (n ^ 2) है, जो मुझे बताया गया है कि बुरा है। मैं इसे और अधिक कुशलता से कैसे कर सकता हूं, क्या यह संभव है? मैं जावास्क्रिप्ट में यह कर रहा हूं, और n आमतौर पर 30 से कम होगा, अगर यह एक ही रहता है तो क्या यह एक समस्या होगी?


3
क्या आपने यह देखने के लिए कोड चलाने की कोशिश की है कि यह कैसा प्रदर्शन करता है?
थेडियन

नहीं, मैं हाँ करता हूँ, मैं यह मान रहा हूँ कि यह O (n ^ 2) के कारण बुरा है।
जेसीओए

1
केवल 30 वस्तुओं? मैंने स्थानिक विभाजन की सिफारिश की होगी, लेकिन यह केवल 30 वस्तुओं के साथ फलीभूत होगा। कुछ मामूली अनुकूलन हैं जो दूसरों ने इंगित किए हैं, लेकिन वे उस पैमाने पर सभी मामूली अनुकूलन हैं जिनके बारे में आप बात कर रहे हैं।
जॉन मैकडॉनल्ड्स

जवाबों:


16

अधिकतम 30 ऑब्जेक्ट्स के साथ, आपको एक ही फ्रेम में एक से अधिक बार एक दूसरे के खिलाफ एक ही दो जोड़े की जांच न करने के अलावा अन्य सभी पर बहुत अनुकूलन की आवश्यकता नहीं होनी चाहिए। नीचे दिए गए कोड नमूने को कवर किया जाएगा। लेकिन अगर आप अलग-अलग अनुकूलन में दिलचस्प हैं जो एक भौतिकी इंजन का उपयोग करेंगे तो इस पोस्ट के बाकी हिस्सों के माध्यम से पढ़ना जारी रखें।

आपको जो आवश्यकता होगी वह एक स्थानिक विभाजन कार्यान्वयन है, जैसे कि एक ओक्ट्री (3 डी गेम के लिए) या क्वाडट्री (2 डी गेम के लिए)। ये दुनिया को उप-वर्गों में विभाजित करते हैं, और फिर प्रत्येक उप-खंड को एक ही जागीर में विभाजित किया जाता है, जब तक कि वे न्यूनतम आकार में विभाजित नहीं हो जाते। यह आपको बहुत जल्दी जांच करने की अनुमति देता है कि दुनिया के समान क्षेत्र में कौन सी अन्य वस्तुएं हैं, जो उन टकरावों की मात्रा को सीमित करती हैं, जिनके खिलाफ आपको जांच करनी चाहिए।

स्थानिक विभाजन के अलावा, मैं आपकी प्रत्येक भौतिक वस्तुओं के लिए एक AABB ( एक्सिस-संरेखित बाउंडिंग बॉक्स ) बनाने की सलाह दूंगा । यह आपको किसी वस्तु के एएबीबी को दूसरे के खिलाफ जांचने की अनुमति देता है, जो वस्तुओं के बीच एक विस्तृत प्रति-पॉली चेक की तुलना में बहुत तेज है।

यह जटिल या बड़ी भौतिकी वस्तुओं के लिए एक और कदम उठाया जा सकता है, जहां आप भौतिकी जाल को स्वयं विभाजित कर सकते हैं, प्रत्येक उप-आकार को अपना एएबीबी दे सकते हैं जिसे आप केवल तभी जांच सकते हैं जब दो ऑब्जेक्ट के एएबीबी अतिव्यापी हो।

एक बार आराम करने पर अधिकांश भौतिकी इंजन भौतिकी निकायों पर सक्रिय भौतिकी सिमुलेशन को निष्क्रिय कर देंगे। जब एक भौतिकी निकाय को निष्क्रिय कर दिया जाता है, तो उसे प्रत्येक एएबीबी के खिलाफ टक्कर के लिए केवल जांच की आवश्यकता होती है, और यदि एएबीबी के साथ कुछ भी टकराता है, तो यह फिर से सक्रिय हो जाएगा और अधिक दानेदार टक्कर की जांच करेगा। इससे अनुकरण समय नीचे रहता है।

इसके अलावा, कई भौतिकी इंजन 'सिमुलेशन द्वीप' का उपयोग करते हैं, जो कि जहां भौतिकी निकायों का एक समूह होता है जो एक साथ समूहबद्ध होते हैं। यदि सिमुलेशन द्वीप में सब कुछ आराम पर है तो सिमुलेशन द्वीप स्वयं निष्क्रिय हो जाता है। सिमुलेशन द्वीप का लाभ यह है कि द्वीप के निष्क्रिय होने के बाद इसके अंदर के सभी पिंड टकरावों की जाँच करना बंद कर सकते हैं, और केवल प्रत्येक फ्रेम को यह देखना है कि क्या कुछ द्वीप के AABB में प्रवेश किया है। केवल एक बार कुछ द्वीप के AABB में प्रवेश करता है, द्वीप के भीतर के प्रत्येक निकायों को टकरावों की जांच करने की आवश्यकता होती है। सिमुलेशन द्वीप भी प्रतिक्रिया करता है अगर उसके अंदर कोई भी शरीर अपने आप फिर से चलना शुरू कर देता है। यदि कोई समूह समूह के केंद्र से काफी दूर चला जाता है, तो उसे द्वीप से हटा दिया जाता है।

अंत में आप कुछ इस तरह से छोड़ते हैं (छद्म कोड में):

// Go through each leaf node in the octree. This could be more efficient
// by keeping a list of leaf nodes with objects in it.
for ( node in octreeLeafNodes )
{
    // We only need to check for collision if more than one object
    // or island is in the bounds of this octree node.
    if ( node.numAABBsInBounds > 1)
    {
        for ( int i = 0; i < AABBNodes.size(); ++i )
        {
           // Using i+1 here allows us to skip duplicate checks between AABBS
           // e.g (If there are 5 bodies, and i = 0, we only check i against
           //      indexes 1,2,3,4. Once i = 1, we only check i against indexes
           //      2,3,4)
           for ( int j = i + 1; j < AABBNodes.size(); ++j )
           {
               if ( AABBOverlaps( AABBNodes[i], AABBNodes[j] ) )
               {
                   // If the AABB we checked against was a simulation island
                   // then we now check against the nodes in the simulation island

                   // Once you find overlaps between two actual object AABBs
                   // you can now check sub-nodes with each object, if you went
                   // that far in optimizing physics meshes.
               {
           }
        }
    }
}

मैं इस तरह के छोरों के भीतर इतने सारे छोरों को नहीं रखने की भी सिफारिश करूंगा, ऊपर का नमूना सिर्फ इतना था कि आपको यह विचार मिला, मैं इसे कई कार्यों में तोड़ दूंगा जो आपको वही कार्यक्षमता प्रदान करते हैं जैसे कि ऊपर दिखाया गया है।

इसके अलावा, सुनिश्चित करें कि इसके माध्यम से लूप करते समय AABBNodes कंटेनर को बदलना न पड़े, क्योंकि इसका मतलब टकराव की जांच से चूक सकता है। यह सामान्य ज्ञान की तरह लग सकता है, लेकिन आप आश्चर्यचकित होंगे कि टकरावों पर प्रतिक्रिया देने वाली चीजें आपके द्वारा अनुमानित नहीं होने के कारण परिवर्तन करना कितना आसान है। उदाहरण के लिए यदि टकराव की वस्तुओं में से एक टकराव की स्थिति के कारण स्थिति को बदलने के लिए पर्याप्त हो तो उन्हें उस ऑक्ट्री नोड के एएबीबी से हटा दें जो आप चेक कर रहे थे तब यह उस कंटेनर को बदल सकता है। इसे हल करने के लिए मैं चेक के दौरान होने वाली सभी टक्कर की घटनाओं की एक सूची रखने की सलाह देता हूं, और फिर सभी जांच पूरी होने के बाद सूची के माध्यम से चला जाता हूं और किसी भी टकराव की घटनाओं को बाहर भेजता हूं।


4
मौजूदा तरीकों से पाठक के मन को खोलने के लिए अच्छे और उपयोगी तकनीकी उदाहरणों के साथ बहुत सुसंगत उत्तर। +1
वाल्किया

क्या होगा यदि मुझे टकराने वाली वस्तु को हटाने की आवश्यकता है? क्या मैं कंटेनर बदल सकता हूं? मेरा मतलब है कि कंटेनर से इसे हटा देना क्योंकि मुझे अब इस वस्तु की आवश्यकता नहीं है क्योंकि यह "नष्ट" है। अगर मैं इसे टक्कर का पता लगाने के दौरान नहीं हटाता हूं तो मुझे टक्कर की घटनाओं के माध्यम से चलने के लिए एक और लूप की आवश्यकता होती है।
newguy

टकराने वाली वस्तु को हटाना ठीक है लेकिन मैं इसे तब तक इंतजार करने की सलाह दूंगा जब तक कि पूरे सिमुलेशन पर टकराव पास नहीं हो जाता। आमतौर पर आप केवल उन वस्तुओं को चिह्नित करते हैं जिन्हें निकालने की आवश्यकता होती है, या हटाए जाने वाली वस्तुओं की एक सूची तैयार करते हैं, और फिर टक्कर सिमुलेशन होने के बाद आप उन परिवर्तनों को लागू करते हैं।
निक फोस्टर

4

आपका उदाहरण कई बार प्रत्येक जोड़ी वस्तुओं का परीक्षण करता है।

आइए 0,1,2,3 वाले एक सरणी के साथ एक बहुत ही सरल उदाहरण लेते हैं

आपके कोड के साथ आपको यह मिलता है:

  • लूप 0 पर आप 1, 2 और 3 के खिलाफ परीक्षण करते हैं
  • लूप 1 में आप 0, 2 और 3 ===> (0-1 पहले से परीक्षण किए गए) के खिलाफ परीक्षण करते हैं
  • लूप 2 पर आप 0, 1 और 3 ===> (0-2 / 1-2 पहले से परीक्षण किए गए) के खिलाफ परीक्षण करते हैं
  • लूप 3 में आप 0, 1 और 2 ===> (0-3 / 1-3 / 2-3 पहले से परीक्षण किए गए) के खिलाफ परीक्षण करते हैं

अब निम्नलिखित कोड देखते हैं:

for(i=0;i<=objects.length;i++)
{
    objects[i].stuff();

    for(j=i+1;j<=objects.length;j++)
    {
        if (collision(objects[i], objects[j]))
        doStuff();
    }

    bla.draw();
}

यदि हम एक बार फिर 0,1,2,3 युक्त सरणी का उपयोग करते हैं, तो हमारे पास निम्न व्यवहार है:

  • लूप 0 पर आप 1, 2, 3 के खिलाफ परीक्षण करते हैं
  • लूप 1 में आप 2, 3 के खिलाफ परीक्षण करते हैं
  • लूप 2 पर आप 3 के खिलाफ परीक्षण करते हैं
  • लूप 3 में आप कुछ भी नहीं के खिलाफ परीक्षण करते हैं

दूसरे एल्गोरिथ्म के साथ हमें 6 टक्कर के परीक्षण मिले हैं, जबकि पिछले वाले ने 12 टक्कर के परीक्षण के लिए कहा था।


यह एल्गोरिथ्म N(N-1)/2तुलना करता है जो अभी भी ओ (एन ^ 2) प्रदर्शन है।
काई

1
30 वस्तुओं के साथ अच्छी तरह से अनुरोध किया गया है कि 870 के खिलाफ 465 टकराव परीक्षण का मतलब है ... यह शायद आपके दृष्टिकोण से समान है, लेकिन मेरा नहीं। इसके अलावा, अन्य उत्तर में प्रस्तुत समाधान बिल्कुल वैसा ही एल्गोरिथ्म है :)
Valkea

1
@ वाल्किया: ठीक है, इसका हिस्सा है। :)
निक फोस्टर

@ न्यूफोस्टर: हाँ, आप सही हैं;) मैं चयनित ऑब्जेक्ट्स के बीच टकराव परीक्षण के बारे में सख्ती से बोल रहा था, एल्गोरिथम के विभाजन वाले हिस्से के बारे में नहीं, जो स्पष्ट रूप से एक बहुत ही मूल्यवान अतिरिक्त है, जिसे मैंने अपने उदाहरण में जोड़ने के लिए भी नहीं सोचा था। मैं इसे लिख रहा था।
वलकेआ

क्या इसे परिशोधन कहा जाता है? कोई बात नहीं धन्यवाद!
जकोरा

3

अपनी आवश्यकताओं के आसपास अपने एल्गोरिथ्म को डिज़ाइन करें, लेकिन इम्प्लांटेशन विवरण को संक्षिप्त रखें। जावास्क्रिप्ट में भी, बुनियादी ओओपी अवधारणाएं लागू होती हैं।

क्योंकि N =~ 30, O(N*N)यह चिंता का विषय नहीं है, और आपकी लीनियर खोज उतनी ही तेजी से होने की संभावना है, जितनी किसी विकल्प के रूप में। लेकिन आप अपने कोड में हार्ड-कोड मान्यताओं को नहीं चाहते हैं। स्यूडोकोड में, आपका एक इंटरफ़ेस होगा

interface itemContainer { 
    add(BoundingBox);
    remove(BoundingBox);
    BoundingBox[] getIntersections();
}

यह बताता है कि आपकी सूची में से क्या-क्या आइटम कर सकते हैं। फिर आप एक ArrayContainer वर्ग लिख सकते हैं जो इस इंटरफ़ेस को लागू करता है। जावास्क्रिप्ट में, कोड इस तरह दिखेगा:

function ArrayContainer() { ... } // this uses an array to store my objects
ArrayContainer.prototype.add = function(box) { ... };
ArrayContainer.prototype.remove = function(box) { ... };
ArrayContainer.prototype.getIntersections = function() { ... };

function QuadTreeContainer { ... } // this uses a quadtree to store my objects
... and implement in the add/remove/getIntersections for QuadTreeContainer too

और यहाँ उदाहरण कोड है जो 300 बाउंडिंग बॉक्स बनाता है और सभी चौराहों को प्राप्त करता है। यदि आपने ArrayContainer और QuadTreeContainer को सही तरीके से कार्यान्वित किया है, तो आपको अपने कोड में परिवर्तन var allMyObjects = new ArrayContainer()करने की आवश्यकता होगी var allMyObjects = QuadTreeContainer()

var r = Math.random;
var allMyObjects = new ArrayContainer();
for(var i=0; i<300; i++)
    allMyObjects.add(new BoundingBox(r(), r()));
var intersections = allMyObjects.getIntersections();

मैं आगे गया और यहाँ मानक ArrayContainer के लिए कार्यान्वयन को मार दिया:

http://jsfiddle.net/SKkN5/1/


नोट: यह उत्तर बैन की शिकायत से प्रेरित था कि उसका कोडबेस बहुत बड़ा, गन्दा और प्रबंधन करने में कठिन हो रहा था। हालाँकि यह एक ऐरे बनाम ट्री के उपयोग पर चर्चा में बहुत कुछ नहीं जोड़ता, लेकिन मुझे उम्मीद है कि यह एक प्रासंगिक जवाब है कि विशेष रूप से वह अपने कोड को बेहतर तरीके से व्यवस्थित करने के बारे में कैसे जा सकता है।
जिमी

2

आपको वस्तुओं के प्रकारों पर भी विचार करना चाहिए जो समझदारी से टकरा सकते हैं।

उदाहरण के लिए खिलाड़ी को संभवतः अपनी गोलियों को छोड़कर हर चीज से टकराने की जाँच करनी होगी। हालाँकि दुश्मनों को केवल खिलाड़ी की गोलियों के खिलाफ जाँच की आवश्यकता हो सकती है। गोलियों को लगभग निश्चित रूप से एक दूसरे से टकराने की जरूरत नहीं है।

इसे कुशलतापूर्वक कार्यान्वित करने के लिए, आप संभवतः वस्तुओं की अलग-अलग सूचियाँ, एक वस्तु प्रकार के अनुसार रखना चाहते हैं।

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