मैं टकराव इंजन को कैसे अनुकूलित कर सकता हूं जहां ऑर्डर महत्वपूर्ण है और वस्तु समूह के आधार पर टकराव सशर्त है?


14

यदि इस प्रश्न पर आपका यह पहली बार है, तो मेरा सुझाव है कि पहले से नीचे के अद्यतन को पहले पढ़ें, फिर इस भाग को। यहाँ समस्या का संश्लेषण है, हालांकि:

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


अद्यतन: मैंने इस पर वास्तव में कड़ी मेहनत की है, लेकिन कुछ भी अनुकूलन करने का प्रबंधन नहीं कर सका।

मैंने विल द्वारा वर्णित "पेंटिंग" को सफलतापूर्वक लागू किया और समूहों को बिटसेट में बदल दिया, लेकिन यह एक बहुत ही मामूली गति है।

मैंने एक बड़ा मुद्दा भी खोजा : मेरा इंजन ऑर्डर-ऑफ-टकराव पर निर्भर है।

मैंने अद्वितीय टक्कर जोड़ी पीढ़ी के कार्यान्वयन की कोशिश की , जिसने निश्चित रूप से बहुत कुछ करके सब कुछ गति प्रदान की, लेकिन टकराव के क्रम को तोड़ दिया ।

मुझे समझाने दो:

  • मेरे मूल डिजाइन में (जोड़े पैदा नहीं), ऐसा होता है:

    1. एक एकल शरीर चलता है
    2. चले जाने के बाद, यह अपनी कोशिकाओं को ताज़ा करता है और उन शरीरों को प्राप्त करता है जो इसके खिलाफ टकराते हैं
    3. यदि यह एक निकाय को ओवरलैप करता है, तो इसके खिलाफ समाधान, टकराव को हल करने की आवश्यकता होती है

    इसका मतलब यह है कि अगर कोई शरीर हिलता है, और एक दीवार (या किसी अन्य शरीर) से टकराता है, तो केवल शरीर जो स्थानांतरित हो गया है, उसकी टक्कर को हल करेगा और दूसरा शरीर अप्रभावित रहेगा।

    यह वह व्यवहार है जिसकी मैं इच्छा करता हूं

    मैं समझता हूं कि यह भौतिकी इंजनों के लिए सामान्य नहीं है, लेकिन रेट्रो शैली के खेल के लिए इसके बहुत सारे फायदे हैं ।

  • सामान्य ग्रिड डिजाइन में (अद्वितीय जोड़े पैदा करते हुए), ऐसा होता है:

    1. सभी निकाय चलते हैं
    2. सभी शरीर चले जाने के बाद , सभी कोशिकाओं को ताज़ा करें
    3. अद्वितीय टक्कर जोड़े उत्पन्न करते हैं
    4. प्रत्येक जोड़ी के लिए, टकराव का पता लगाने और संकल्प को संभालें

    इस मामले में एक साथ चलने के परिणामस्वरूप दो निकायों का अतिव्यापी हो सकता है, और वे एक ही समय में हल करेंगे - यह प्रभावी रूप से निकायों को "एक दूसरे के चारों ओर धक्का" देता है, और कई निकायों के साथ टकराव की स्थिरता को तोड़ता है

    यह व्यवहार भौतिकी इंजनों के लिए सामान्य है, लेकिन यह मेरे मामले में स्वीकार्य नहीं है

मुझे एक और मुद्दा भी मिला, जो प्रमुख है (भले ही यह वास्तविक दुनिया की स्थिति में होने की संभावना न हो):

  • समूह ए, बी और डब्ल्यू के निकायों पर विचार करें
  • एक टकराता है और डब्ल्यू और ए के खिलाफ हल करता है
  • B टकराता है और W और B के विरुद्ध हल करता है
  • A, B के विरुद्ध कुछ भी नहीं करता है
  • B, A के विरुद्ध कुछ भी नहीं करता है

ऐसी स्थिति हो सकती है जहां बहुत सारे ए शरीर और बी बॉडी एक ही सेल पर कब्जा कर लेते हैं - उस स्थिति में, निकायों के बीच बहुत अधिक अनावश्यक पुनरावृत्ति होती है जो एक दूसरे पर प्रतिक्रिया नहीं करना चाहिए (या केवल टकराव का पता लगाएं, लेकिन उन्हें हल न करें) ।

एक ही सेल पर कब्जा करने वाले 100 निकायों के लिए, यह 100 ^ 100 पुनरावृत्तियों है! ऐसा इसलिए होता है क्योंकि अद्वितीय जोड़े उत्पन्न नहीं हो रहे हैं - लेकिन मैं अद्वितीय जोड़े उत्पन्न नहीं कर सकता , अन्यथा मुझे ऐसा व्यवहार मिलेगा जिसकी मुझे इच्छा नहीं है।

क्या इस तरह के टक्कर इंजन को अनुकूलित करने का एक तरीका है?

ये दिशानिर्देश हैं जिनका सम्मान किया जाना चाहिए:

  • टकराव का आदेश अत्यंत महत्वपूर्ण है!

    • निकायों स्थानांतरित करना होगा एक समय में एक है, तो टकराव के लिए जाँच एक समय में एक , और संकल्प आंदोलन के बाद एक समय में एक
  • निकायों में 3 समूह बिटसेट होना चाहिए

    • समूह : समूह जिनका संबंध शरीर से है
    • GroupToCheck : समूहों को शरीर के खिलाफ टकराव का पता लगाना चाहिए
    • GroupNoResolve : समूहों को शरीर के खिलाफ टकराव को हल नहीं करना चाहिए
    • ऐसी परिस्थितियाँ हो सकती हैं जहाँ मैं केवल एक टकराव का पता लगाना चाहता हूँ लेकिन हल नहीं किया जाता है



पूर्व अद्यतन:

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


मैं लचीलापन और गति पर जोर देने के साथ एक सामान्य-उद्देश्य C ++ 2D टकराव का पता लगाने / प्रतिक्रिया इंजन बना रहा हूं।

यहाँ इसकी वास्तुकला का एक बहुत ही मूल चित्र है:

बुनियादी इंजन वास्तुकला

मूल रूप से, मुख्य वर्ग है World, जो (a ResolverBase*, a SpatialBase*और a ) की स्मृति का प्रबंधन करता है vector<Body*>

SpatialBase एक शुद्ध आभासी वर्ग है जो व्यापक-चरण टक्कर का पता लगाने से संबंधित है।

ResolverBase एक शुद्ध आभासी वर्ग है जो टकराव के समाधान से संबंधित है।

निकाय वस्तुओं के World::SpatialBase*साथ संचार करते SpatialInfoहैं, स्वयं निकायों द्वारा स्वामित्व में होते हैं।


वस्तुतः एक स्थानिक वर्ग है: Grid : SpatialBaseजो एक मूल निश्चित 2D ग्रिड है। यह अपने आप की जानकारी वर्ग है, GridInfo : SpatialInfo

यहां बताया गया है कि इसकी वास्तुकला कैसी है:

ग्रिड स्थानिक के साथ इंजन वास्तुकला

Gridवर्ग के एक 2 डी सरणी का मालिक है Cell*Cellवर्ग का एक संग्रह (स्वामित्व में नहीं) शामिल हैं Body*: एक vector<Body*>है जो सभी निकायों है कि सेल में होते हैं।

GridInfo वस्तुओं में गैर-मालिक बिंदु भी होते हैं जो शरीर में होते हैं।


जैसा कि मैंने पहले कहा था, इंजन समूहों पर आधारित है।

  • Body::getGroups()std::bitsetउन सभी समूहों का रिटर्न देता है , जो शरीर का हिस्सा है।
  • Body::getGroupsToCheck()std::bitsetउन सभी समूहों को लौटाता है , जिनके शरीर को टक्कर के खिलाफ जाँच करनी है।

निकाय एकल कोशिका से अधिक पर कब्जा कर सकते हैं। ग्रिडइंफो हमेशा गैर-स्वामित्व वाले पॉइंटर्स को कब्ज़े वाली कोशिकाओं में संग्रहीत करता है।


एक एकल शरीर के हिलने के बाद, टकराव का पता चलता है। मैं मानता हूं कि सभी निकाय अक्ष-संरेखित बाउंडिंग बॉक्स हैं।

कैसे व्यापक चरण टकराव का पता लगाने काम करता है:

भाग 1: स्थानिक जानकारी अद्यतन

प्रत्येक के लिए Body body:

    • शीर्ष-बाईं ओर स्थित कक्ष और नीचे-दाईं ओर स्थित कोशिकाओं की गणना की जाती है।
    • यदि वे पिछले कोशिकाओं से भिन्न होते हैं, तो उन्हें body.gridInfo.cellsसाफ़ कर दिया जाता है, और उन सभी कोशिकाओं से भर जाता है, जो शरीर में व्याप्त होती हैं (2 डी के लिए लूप शीर्ष-बाएं सेल से निचले-दाएं सेल में)।
  1. body अब यह जानने के लिए गारंटी दी जाती है कि यह किन कोशिकाओं पर कब्जा करता है।

भाग 2: वास्तविक टक्कर की जाँच

प्रत्येक के लिए Body body:

  • body.gridInfo.handleCollisions कहा जाता है:

void GridInfo::handleCollisions(float mFrameTime)
{
    static int paint{-1};
    ++paint;

    for(const auto& c : cells)
        for(const auto& b : c->getBodies())
        {
            if(b->paint == paint) continue;
            base.handleCollision(mFrameTime, b);
            b->paint = paint;
        }
}

void Body::handleCollision(float mFrameTime, Body* mBody)
    {
        if(mBody == this || !mustCheck(*mBody) || !shape.isOverlapping(mBody->getShape())) return;

        auto intersection(getMinIntersection(shape, mBody->getShape()));

        onDetection({*mBody, mFrameTime, mBody->getUserData(), intersection});
        mBody->onDetection({*this, mFrameTime, userData, -intersection});

        if(!resolve || mustIgnoreResolution(*mBody)) return;
        bodiesToResolve.push_back(mBody);
    }

  • टक्कर तो हर शरीर के लिए हल है bodiesToResolve

  • बस।


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

मेरा सवाल है: मैं अपने टक्कर इंजन के व्यापक-चरण को कैसे अनुकूलित कर सकता हूं ?

क्या किसी प्रकार का जादू सी ++ अनुकूलन है जिसे यहां लागू किया जा सकता है?

क्या अधिक प्रदर्शन की अनुमति देने के लिए वास्तुकला को फिर से डिज़ाइन किया जा सकता है?


नवीनतम संस्करण के लिए Callgrind आउटपुट: http://txtup.co/rLJgz


प्रोफाइल और बाधाओं की पहचान करें। हमें बताएं कि वे कहां हैं, फिर हमारे पास काम करने के लिए कुछ है।
माईक सेमर

@MaikSemder: मैंने ऐसा किया है, और इसे पोस्ट में लिखा है। यह केवल कोड स्निपेट है जो अड़चन है। क्षमा करें यदि यह लंबा और विस्तृत है, लेकिन यह सवाल का हिस्सा है क्योंकि मुझे यकीन है कि यह अड़चन केवल इंजन के डिजाइन में कुछ बदलकर हल की जा सकती है।
विटोरियो रोमियो

क्षमा करें, खोजना मुश्किल था। क्या आप हमें कुछ नंबर दे सकते हैं? उस फंक्शन में प्रोसेस की गई वस्तुओं का कार्य समय और संख्या?
माईक सेमर

@MikSemder: Callgrind के साथ परीक्षण किया गया, एक क्लेंग 3.4 SVN -O3 के साथ संकलित बाइनरी पर: 10000 गतिशील निकायों - फ़ंक्शन getBodiesToCheck()को 5462334 बार कहा गया, और पूरे प्रोफाइलिंग समय का 35,1% लिया (निर्देश पढ़ने का समय)
विटोरियो रोमियो

2
@Quonux: कोई अपराध नहीं हुआ। मैं सिर्फ "पहिया को सुदृढ़ करना" पसंद करता हूं । मैं बुलेट या बॉक्स 2 डी ले सकता था और उन पुस्तकालयों के साथ एक खेल बना सकता था, लेकिन यह वास्तव में मेरा लक्ष्य नहीं है। मैं बहुत अधिक तृप्त महसूस करता हूं और खरोंच से चीजें बनाकर और प्रकट होने वाली बाधाओं को दूर करने के लिए बहुत कुछ सीखता हूं - भले ही इसका मतलब है कि निराश हो और मदद मांगे। मेरे विश्वास के अलावा कि खरोंच से कोडिंग सीखने के उद्देश्यों के लिए अमूल्य है, मुझे यह बहुत मज़ा भी आता है, और अपना खाली समय बिताने के लिए बहुत खुशी मिलती है।
विटोरियो रोमियो

जवाबों:


14

getBodiesToCheck()

getBodiesToCheck()फ़ंक्शन के साथ दो समस्याएं हो सकती हैं ; प्रथम:

if(!contains(bodiesToCheck, b)) bodiesToCheck.push_back(b);

यह हिस्सा हे (n 2 ) है ना?

यह देखने के लिए जांचने के बजाय कि क्या शरीर पहले से ही सूची में है, इसके बजाय पेंटिंग का उपयोग करें ।

loop_count++;
if(!loop_count) { // if loop_count can wrap,
    // you just need to iterate all bodies to reset it here
}
bodiesToCheck.clear();
for(const auto& q : queries)
    for(const auto& b : *q)
        if(b->paint != loop_count) {
            bodiesToCheck.push_back(b);
            b->paint = loop_count;
        }
return bodiesToCheck;

आप एकत्रित चरण में पॉइंटर को डीरफेर कर रहे हैं, लेकिन आप इसे टेस्ट चरण में वैसे भी डिरेल कर रहे हैं, यदि आपके पास पर्याप्त एल 1 इसकी कोई बड़ी बात नहीं है। आप संकलक के लिए पूर्व-भ्रूण के संकेत जोड़कर प्रदर्शन में सुधार कर सकते हैं __builtin_prefetch, जैसे कि क्लासिक for(int i=q->length; i-->0; )छोरों और इस तरह से आसान है ।

यह एक आसान मोड़ है, लेकिन मेरा दूसरा विचार यह है कि इसे व्यवस्थित करने का एक तेज़ तरीका हो सकता है:

आप इसके बजाय बिटमैप का उपयोग करने के लिए आगे बढ़ सकते हैं , हालांकि, और पूरे bodiesToCheckवेक्टर से बच सकते हैं । यहाँ एक दृष्टिकोण है:

आप पहले से ही निकायों के लिए पूर्णांक कुंजियों का उपयोग कर रहे हैं, लेकिन फिर उन्हें नक्शे और चीजों में देख रहे हैं और उनकी सूचियों के आसपास रख रहे हैं। आप एक स्लॉट एलोकेटर में जा सकते हैं, जो मूल रूप से एक सरणी या वेक्टर है। उदाहरण के लिए:

class TBodyImpl {
   public:
       virtual ~TBodyImpl() {}
       virtual void onHit(int other) {}
       virtual ....
       const int slot;
   protected:
      TBodyImpl(int slot): slot(slot_) {}
};

struct TBodyBase {
    enum ... type;
    ...
    rect_t rect;
    TQuadTreeNode *quadTreeNode; // see below
    TBodyImpl* imp; // often null
};

std::vector<TBodyBase> bodies; // not pointers to them

इसका मतलब यह है कि वास्तविक टकराव करने के लिए आवश्यक सभी सामान रैखिक कैश-फ्रेंडली मेमोरी में है, और आप केवल कार्यान्वयन-विशिष्ट बिट के लिए बाहर जाते हैं और इन स्लॉट्स में से एक को संलग्न करते हैं यदि कुछ की आवश्यकता होती है।

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

केवल एक बार प्रत्येक टक्कर के लिए जाँच करें

आप अगर चेक किया है एक से टकरा , आप अगर जाँच करने के लिए की जरूरत नहीं है से टकरा एक भी।

यह पूर्णांक आईडी का उपयोग करने से होता है, जिसे आप इन चेकों को एक साधारण इफ-स्टेटमेंट से बचते हैं। यदि संभावित टक्कर की आईडी वर्तमान आईडी के लिए जाँच की जा रही से कम-या-बराबर है, तो इसे छोड़ दिया जा सकता है! इस तरह, आप केवल एक बार प्रत्येक संभावित जोड़ी की जाँच करेंगे; कि टक्कर की जांच के आधे से अधिक संख्या होगी।

unsigned * bitmap;
int bitmap_len;
...

for(int i=0; i<bitmap_len; i++) {
  unsigned mask = bitmap[i];
  while(mask) {
      const int j = __builtin_ffs(mask);
      const int slot = i*sizeof(unsigned)*8+j;
      for(int neighbour: get_neighbours(slot))
          if(neighbour > slot)
              check_for_collision(slot,neighbour);
      mask >>= j;
  }

टकराव के आदेश का सम्मान करें

एक जोड़ी मिलते ही टक्कर का मूल्यांकन करने के बजाय, एक बाइनरी हीप में हिट और स्टोर करने के लिए दूरी की गणना करें । ये ढेर हैं कि आप आम तौर पर पथ-खोज में प्राथमिकता कतार में हैं, इसलिए बहुत उपयोगी उपयोगिता कोड है।

एक क्रम संख्या के साथ प्रत्येक नोड को चिह्नित करें, ताकि आप कह सकें:

  • A 10 ने B 12 को 6 पर हिट किया
  • एक 10 सी 3 पर 3 हिट

जाहिर है जब आप सभी टकरावों को इकट्ठा कर लेते हैं, तो आप उन्हें प्राथमिकता कतार से, जल्द ही सबसे पहले पॉपिंग करना शुरू करते हैं। इसलिए आपको जो पहला मिलता है वह है A 10 हिट C 12 at 3. आप प्रत्येक ऑब्जेक्ट के अनुक्रम संख्या ( 10 बिट) को बढ़ाते हैं , टकराव का मूल्यांकन करते हैं, और उनके नए रास्तों की गणना करते हैं, और अपनी नई टक्करों को उसी कतार में संग्रहीत करते हैं। नई टक्कर A 11 हिट B 12 पर 7. 7. अब कतार में है:

  • A 10 ने B 12 को 6 पर हिट किया
  • A 11 ने B 12 को 7 पर हिट किया

तो फिर तुम प्राथमिकता कतार और उसके एक से पॉप 10 हिट बी 12 6. पर लेकिन आप देखते हैं कि एक 10 है बासी ; A वर्तमान में 11. है, इसलिए आप इस टक्कर को छोड़ सकते हैं।

यह महत्वपूर्ण है कि पेड़ से सभी बासी टकरावों को हटाने की कोशिश न करें; एक ढेर से निकालना महंगा है। जब आप उन्हें पॉप करते हैं तो उन्हें त्याग दें।

ग्रिड

आपको इसके बजाय एक क्वाडट्री का उपयोग करने पर विचार करना चाहिए। इसे लागू करने के लिए एक बहुत ही सरल डेटा-संरचना है। अक्सर आप कार्यान्वयन को देखते हैं जो पॉइंट को स्टोर करते हैं लेकिन मैं रेक्टर्स को स्टोर करना पसंद करता हूं, और उस नोड में तत्व को स्टोर करना चाहता हूं जिसमें यह शामिल है। इसका मतलब यह है कि टकरावों की जांच करने के लिए आपको केवल सभी निकायों पर पुनरावृत्त करना होगा, और प्रत्येक के लिए, एक ही क्वाड-ट्री नोड में उन निकायों के खिलाफ जांच करें (ऊपर उल्लिखित छंटाई चाल का उपयोग करके) और उन सभी को मूल क्वाड-ट्री नोड्स में। क्वाड-ट्री स्वयं संभावित टक्कर सूची है।

यहाँ एक सरल चतुर्थी है:

struct Object {
    Rect bounds;
    Point pos;
    Object * prev, * next;
    QuadTreeNode * parent;
};

struct QuadTreeNode {
    Rect bounds;
    Point centre;
    Object * staticObjects;
    Object * movableObjects;
    QuadTreeNode * parent; // null if root
    QuadTreeNode * children[4]; // null for unallocated children
};

हम चल वस्तुओं को अलग से संग्रहीत करते हैं क्योंकि हमें यह जांचने की ज़रूरत नहीं है कि स्थिर वस्तुएं किसी चीज़ से टकरा रही हैं या नहीं।

हम सभी वस्तुओं को अक्ष-संरेखित बाउंडिंग बॉक्स (AABB) के रूप में मॉडलिंग कर रहे हैं और हम उन्हें सबसे छोटे QuadTreeNode में शामिल करते हैं। जब एक QuadTreeNode बच्चों का एक बहुत है, तो आप इसे और अधिक कर सकते हैं (यदि उन वस्तुओं को अच्छी तरह से बच्चों में वितरित करें)।

प्रत्येक गेम टिक, आपको क्वाडट्री में पुनरावृत्ति करने और प्रत्येक चल वस्तु की - और टकराव की गणना करने की आवश्यकता है। इसके साथ टकराव के लिए जाँच की जानी है:

  • इसके नोड में प्रत्येक स्थिर वस्तु
  • इसके चलन में हर चल योग्य वस्तु जो इसके पहले है (या इसके बाद; बस एक दिशा चुनें) movableObjects सूची में
  • सभी मूल नोड्स में हर चल और स्थिर वस्तु

यह सभी संभव टकराव उत्पन्न करेगा, अनियंत्रित। आप फिर चालें करते हैं। आपको इन चालों को दूरी और 'जो पहले चलता है' (जो आपकी विशेष आवश्यकता है) को प्राथमिकता देनी होगी, और उन्हें उसी क्रम में निष्पादित करना होगा। इसके लिए ढेर का इस्तेमाल करें।

आप इस quadtree टेम्पलेट को अनुकूलित कर सकते हैं; आपको वास्तव में सीमा और केंद्र-बिंदु को संग्रहीत करने की आवश्यकता नहीं है; यह पूरी तरह से व्युत्पन्न है जब आप पेड़ पर चलते हैं। आपको यह जाँचने की आवश्यकता नहीं है कि क्या कोई मॉडल सीमा के भीतर है, केवल जाँच करें कि वह किस बिंदु पर केंद्र-बिंदु ("पृथक्करण का अक्ष" परीक्षण) है।

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

बड़ी स्थिर वस्तुओं को घटक भागों में विभाजित किया जाना चाहिए; एक बड़े घन का प्रत्येक चेहरा अलग से संग्रहित होना चाहिए, उदाहरण के लिए।


"पेंटिंग" अच्छा लगता है, मैं इसे जल्द से जल्द आजमाऊंगा और परिणाम दूंगा। हालांकि मुझे आपके उत्तर का दूसरा भाग समझ में नहीं आता - मैं पूर्व-प्राप्ति के बारे में कुछ पढ़ने की कोशिश करूँगा।
विटोरियो रोमियो

मैं क्वाडट्री की सिफारिश नहीं करूंगा, यह एक ग्रिड करने की तुलना में अधिक जटिल है, और अगर ठीक से नहीं किया गया तो यह सही ढंग से काम नहीं करेगा और नोड्स को भी अक्सर बना / हटा देगा।
क्लिकरमनकी

अपने ढेर के बारे में: आंदोलन के आदेश का सम्मान किया जाता है? बॉडी A और बॉडी B पर विचार करें । A, B की ओर दाईं ओर जाता है, और B, A की ओर दाईं ओर बढ़ता है - जब वे एक साथ टकराते हैं, तो जो पहले स्थानांतरित हुआ , उसे पहले हल करना चाहिए , और दूसरा अप्रभावित रहेगा।
विटोरियो रोमियो

@VittorioRomeo यदि A, B और B की ओर एक ही टिक में A की ओर बढ़ता है, और उसी गति से, तो क्या वे बीच में मिलते हैं? या A, पहले घूमता है, B से मिलता है जहां B शुरू होता है?
विल विल


3

मुझे यकीन है कि आपके पास शरीर पर पुनरावृत्ति होने पर सिर्फ एक टन कैश की याद आती है। क्या आप कुछ डेटा उन्मुख डिजाइन योजना का उपयोग करके अपने सभी निकायों को एक साथ जोड़ रहे हैं? N ^ 2 ब्रॉडपेज़ के साथ, मैं सैकड़ों और सैकड़ों का अनुकरण कर सकता हूं , जबकि फ्रैड के साथ रिकॉर्डिंग करते हुए, बिना किसी फ्रैमरेट ड्रॉप्स के बॉडी को निथर क्षेत्रों (60 से कम) में छोड़ देता है, और यह सब एक कस्टम एलोकेटर के बिना होता है। बस कल्पना करें कि उचित कैश उपयोग के साथ क्या किया जा सकता है।

सुराग यहाँ है:

const std::vector<Body *>

यह तुरंत एक विशाल लाल झंडा उठाता है। क्या आप इन निकायों को कच्ची नई कॉल के साथ आवंटित कर रहे हैं? क्या उपयोग में कोई कस्टम आवंटनकर्ता है? यह सबसे महत्वपूर्ण है कि आपके पास एक विशाल सरणी में आपके सभी शरीर हैं जिसमें आप रैखिक रूप से आगे बढ़ते हैं । यदि मेमोरी को ट्रेंकुलाइज़ करना कुछ ऐसा नहीं है, जिसे आप महसूस करते हैं कि आप इसके बजाय एक घुसपैठ से जुड़ी सूची का उपयोग करने पर विचार कर सकते हैं।

इसके अतिरिक्त आप std :: map का प्रयोग करते हैं। क्या आप जानते हैं कि std :: map के भीतर मेमोरी कैसे आवंटित की जाती है? आपके पास प्रत्येक मैप क्वेरी के लिए O (lg (N)) जटिलता होगी, और इसे हैश तालिका के साथ O (1) तक बढ़ाया जा सकता है। इसके शीर्ष पर std :: map द्वारा आवंटित मेमोरी आपके कैश को बहुत बुरी तरह से थ्रश करने वाली है।

मेरा समाधान std :: map के स्थान पर एक घुसपैठ हैश तालिका का उपयोग करना है। आंतरिक रूप से लिंक की गई सूचियों और दखल देने वाली हैश टेबल दोनों का एक अच्छा उदाहरण पैट्रिक व्याट के अपने कोहो प्रोजेक्ट में है: https://github.com/webcoyote/coho

तो संक्षेप में आपको शायद अपने लिए कुछ कस्टम टूल बनाने की आवश्यकता होगी, अर्थात् एक एलोकेटर और कुछ घुसपैठिए कंटेनर। यह सबसे अच्छा मैं अपने लिए कोड को प्रोफाइल किए बिना कर सकता हूं।


"क्या आप इन निकायों को कच्ची नई कॉल के साथ आवंटित कर रहे हैं?" मैं स्पष्ट रूप से कॉल नहीं कर रहा हूं newजब निकायों को getBodiesToCheckवेक्टर पर धकेल दिया जाता है - क्या इसका मतलब है कि यह आंतरिक रूप से हो रहा है? वहाँ एक तरीका है कि अभी भी शरीर के एक गतिशील आकार संग्रह होने से रोकने के लिए है?
विटोरियो रोमियो

std::mapएक अड़चन नहीं है - मुझे यह भी याद है कि मैं dense_hash_setकिसी भी तरह के प्रदर्शन की कोशिश नहीं कर रहा हूं ।
विटोरियो रोमियो

@ विटोरियो , तो getBodiesToCheck के किस हिस्से में अड़चन है? हमें मदद करने के लिए जानकारी चाहिए।
माईक सेमर

@MaikSemder: प्रोफाइलर फंक्शन की तुलना में अधिक गहरा नहीं होता है। पूरा कार्य अड़चन है, क्योंकि इसे प्रति शरीर प्रति फ्रेम एक बार कहा जा रहा है। 10000 निकाय = getBodiesToCheckप्रति फ्रेम 10000 कॉल। मुझे संदेह है कि वेक्टर में निरंतर सफाई / धक्का कार्य की अड़चन है। containsविधि को भी मंदी का हिस्सा है, लेकिन जब से bodiesToCheckयह से अधिक 8-10 शरीर है कभी नहीं, यह है कि धीमी गति से किया जाना चाहिए
विटोरियो रोमियो

@Vittorio अच्छा होगा यदि आप इस जानकारी को प्रश्नों में रखते हैं, तो यह एक गेम-चेंजर है;) विशेष रूप से मेरा मतलब यह है कि getBodiesToCheck को सभी निकायों के लिए कहा जाता है , इसलिए प्रत्येक फ्रेम में 10000 गुना। मुझे आश्चर्य है, आपने कहा कि वे समूहों में थे, इसलिए उन्हें निकायों में डाल दिया। चेक-सरणी, यदि आपके पास पहले से ही समूह की जानकारी है। आप उस हिस्से को विस्तृत कर सकते हैं, मेरे लिए एक बहुत अच्छा अनुकूलन उम्मीदवार की तरह दिखता है।
माईक सेमर

1

प्रत्येक फ्रेम की जाँच करने के लिए निकायों की गिनती कम करें:

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

एक क्वाडट्री का उपयोग करें। मेरा विस्तृत जवाब यहां देखें

अपने भौतिकी कोड से सभी आवंटन निकालें। आप इसके लिए प्रोफाइलर का उपयोग कर सकते हैं। लेकिन मैंने केवल C # में मेमोरी एलोकेशन का विश्लेषण किया है, इसलिए मैं C ++ की मदद नहीं कर सकता।

सौभाग्य!


0

मुझे आपके टोंटी समारोह में दो समस्या वाले उम्मीदवार दिखाई दे रहे हैं:

पहला "शामिल" भाग है - यह शायद अड़चन का मुख्य कारण है। यह हर शरीर के लिए पहले से ही पाए गए शरीर के माध्यम से पुनरावृत्ति कर रहा है। हो सकता है कि आपको वेक्टर के बजाय कुछ प्रकार के हैश_टेबल / हैश_मैप का उपयोग करना चाहिए। फिर सम्मिलित करना तेज होना चाहिए (नकल की खोज के साथ)। लेकिन मुझे कोई विशेष संख्या नहीं पता है - मुझे नहीं पता कि यहां कितने निकाय हैं।

दूसरी समस्या वेक्टर :: स्पष्ट और push_back हो सकती है। स्पष्ट हो सकता है या वास्तविक उत्थान नहीं हो सकता है। लेकिन आप इससे बचना चाह सकते हैं। समाधान कुछ झंडे सरणी हो सकता है। लेकिन आपके पास शायद बहुत सी वस्तुएं हो सकती हैं, इसलिए यह स्मृति अप्रभावी है कि प्रत्येक वस्तु के लिए सभी वस्तुओं की सूची होनी चाहिए। कुछ अन्य दृष्टिकोण अच्छा हो सकता है, लेकिन मुझे नहीं पता कि क्या दृष्टिकोण: /


पहली समस्या के बारे में: मैंने वेक्टर + सम्‍मिलित के बजाय dense_hash_set का उपयोग करने का प्रयास किया है, और यह धीमा था। मैंने वेक्टर को भरने और फिर सभी डुप्लिकेट को हटाने की कोशिश की, और यह धीमा था।
विटोरियो रोमियो

0

नोट: मुझे C ++ का कुछ भी नहीं पता है, केवल जावा, लेकिन आपको कोड का पता लगाने में सक्षम होना चाहिए। भौतिक विज्ञान सार्वभौमिक भाषा सही है? मुझे यह भी एहसास है कि यह एक साल पुरानी पोस्ट है, फिर भी मैं बस इसे सभी के साथ साझा करना चाहता था।

मेरे पास एक पर्यवेक्षक पैटर्न है जो मूल रूप से, इकाई के चलने के बाद, यह उस वस्तु को लौटाता है जिससे यह टकरा गया है, जिसमें एक पूर्ण वस्तु शामिल है। सीधे शब्दों में कहें:

( मैं मिनीक्राफ्ट का रीमेक बना रहा हूं )

public Block collided(){
   return World.getBlock(getLocation());
}

तो कहते हैं कि आप अपनी दुनिया में घूम रहे हैं। जब भी तुम बुलाओ move(1)तब फोन करना collided()। यदि आपको मनचाहा ब्लॉक मिल जाता है, तो शायद कण उड़ जाते हैं और आप दाएं और बाएं बाएं घूम सकते हैं, लेकिन आगे नहीं।

उदाहरण के रूप में केवल मिनीक्राफ्ट की तुलना में इसे अधिक उदारता से उपयोग करना:

public Object collided(){
   return threeDarray[3][2][3];
}

बस, निर्देशांक को इंगित करने के लिए एक सरणी है, जिसका शाब्दिक अर्थ है कि जावा कैसे करता है, पॉइंटर्स का उपयोग करता है।

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

अब एक और नज़र डालते हैं, इसका मतलब है कि, मेरे मिनीक्राफ्ट टकराने () विधि के अनुसार, मैं ब्लॉक के अंदर समाप्त हो जाऊंगा, इसलिए मुझे खिलाड़ी को इसके बाहर ले जाना होगा। खिलाड़ी की जांच करने के बजाय, मुझे एक बाउंडिंग बॉक्स जोड़ना होगा जो यह जांचता है कि बॉक्स के प्रत्येक तरफ कौन सा ब्लॉक मार रहा है। निर्धारित समस्या।

यदि आप सटीकता चाहते हैं तो उपरोक्त अनुच्छेद बहुभुज के साथ इतना आसान नहीं हो सकता है। सटीकता के लिए मैं एक बहुभुज बाउंडिंग बॉक्स को परिभाषित करने का सुझाव दूंगा जो कि एक वर्ग नहीं है, लेकिन टसेलेटेड नहीं है। यदि नहीं, तो एक आयत ठीक है।

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