ฉันจะปรับเอ็นจิ้นการชนได้อย่างมีประสิทธิภาพซึ่งคำสั่งนั้นสำคัญและการชนกันแบบมีเงื่อนไขขึ้นอยู่กับกลุ่มวัตถุ?


14

หากนี่เป็นครั้งแรกของคุณสำหรับคำถามนี้ฉันขอแนะนำให้อ่านส่วนอัปเดตด้านล่างก่อนจากนั้นส่วนนี้ นี่คือการสังเคราะห์ปัญหาแม้ว่า:

โดยพื้นฐานแล้วฉันมีระบบตรวจจับการชนและการแก้ปัญหาด้วยระบบแบ่งพาร์ติชันแบบกริดซึ่งการเรียงลำดับการชนและการชนมีความสำคัญ ร่างกายหนึ่งคนในแต่ละครั้งจะต้องเคลื่อนที่จากนั้นตรวจจับการชนแล้วแก้ไขการชน ถ้าฉันย้ายร่างกายทั้งหมดในครั้งเดียวจากนั้นสร้างคู่การชนกันที่เป็นไปได้มันจะเร็วขึ้นอย่างเห็นได้ชัด แต่ตัวแบ่งความละเอียดเนื่องจากการเรียงลำดับของการชนกันไม่ได้รับการเคารพ ถ้าฉันขยับร่างกายหนึ่งครั้งฉันก็ต้องบังคับให้ร่างกายตรวจสอบการชนและมันจะกลายเป็นปัญหา ^ 2 ใส่กลุ่มในการผสมและคุณสามารถจินตนาการได้ว่าทำไมมันถึงช้ามากอย่างรวดเร็วด้วยร่างกายจำนวนมาก


อัปเดต:ฉันทำงานอย่างหนักกับสิ่งนี้ แต่ไม่สามารถจัดการเพื่อเพิ่มประสิทธิภาพอะไรได้เลย

ฉันประสบความสำเร็จในการนำ "ภาพวาด" ที่อธิบายโดย Will และเปลี่ยนกลุ่มเป็นบิตเซ็ต แต่มันเป็นการเร่งความเร็วที่น้อยมาก

ฉันค้นพบปัญหาใหญ่เช่นกัน: เครื่องยนต์ของฉันขึ้นอยู่กับการชนกันของข้อมูล

ฉันพยายามที่มีการใช้งานของคนรุ่นชนคู่ที่ไม่ซ้ำกันซึ่งแน่นอนเพิ่มความเร็วในการทุกอย่างด้วยเป็นจำนวนมาก แต่ยากจนชนการสั่งซื้อของ

ให้ฉันอธิบาย:

  • ในการออกแบบดั้งเดิมของฉัน (ไม่สร้างคู่) สิ่งนี้เกิดขึ้น:

    1. เคลื่อนไหวร่างกายเดียว
    2. หลังจากที่มันเคลื่อนไหวแล้วมันจะรีเฟรชเซลล์และทำให้ร่างกายได้รับการชนกัน
    3. หากมันทับร่างกายที่ต้องแก้ไขให้แก้ไขการชนกัน

    นี่หมายความว่าหากร่างกายเคลื่อนไหวและชนกับกำแพง (หรือร่างกายอื่น ๆ ) เฉพาะร่างกายที่เคลื่อนไหวเท่านั้นที่จะแก้ไขการชนและร่างกายอื่น ๆ จะไม่ได้รับผลกระทบ

    นี่คือความปรารถนาที่พฤติกรรมของฉัน

    ผมเข้าใจว่ามันไม่ธรรมดาสำหรับเครื่องยนต์ฟิสิกส์ แต่มันก็มีประโยชน์มากสำหรับสไตล์ย้อนยุคเกม

  • ในการออกแบบกริดปกติ (สร้างคู่ที่ไม่ซ้ำกัน) สิ่งนี้เกิดขึ้น:

    1. ทุกสิ่งเคลื่อนไหว
    2. หลังจากย้ายวัตถุทั้งหมดแล้วให้รีเฟรชเซลล์ทั้งหมด
    3. สร้างคู่การชนกันที่ไม่ซ้ำกัน
    4. สำหรับแต่ละคู่จัดการการตรวจจับการชนและความละเอียด

    ในกรณีนี้การเคลื่อนที่พร้อมกันอาจส่งผลให้มีสองวัตถุซ้อนทับกันและพวกมันจะแก้ไขในเวลาเดียวกัน - สิ่งนี้ทำให้ร่างกาย "ผลักกันอีกรอบ" อย่างมีประสิทธิภาพ

    ลักษณะการทำงานนี้เป็นเรื่องธรรมดาสำหรับเครื่องยนต์ฟิสิกส์แต่มันเป็นเรื่องที่ยอมรับไม่ได้ในกรณีของฉัน

ฉันยังพบปัญหาอื่นซึ่งสำคัญ (แม้ว่าจะไม่เกิดขึ้นในสถานการณ์จริง):

  • พิจารณาร่างของกลุ่ม A, B และ W
  • การชนกันและแก้ไขกับ W และ A
  • B ชนกันและแก้ไขกับ W และ B
  • A ไม่ทำอะไรเลยกับ B
  • B ไม่ทำอะไรเลยกับ A

อาจมีสถานการณ์ที่วัตถุ A และ B จำนวนมากครอบครองเซลล์เดียวกัน - ในกรณีนั้นมีการวนซ้ำที่ไม่จำเป็นระหว่างเนื้อความที่ไม่ต้องตอบสนองต่อกันและกัน (หรือตรวจจับการชนเท่านั้น แต่ไม่สามารถแก้ไขได้) .

สำหรับ 100 ศพที่ครอบครองเซลล์เดียวกันมันจะวนซ้ำ100 ^ 100 ! สิ่งนี้เกิดขึ้นเพราะคู่ที่ไม่ซ้ำกันไม่ได้ถูกสร้างขึ้น แต่ฉันไม่สามารถสร้างคู่ที่ไม่ซ้ำกันได้มิฉะนั้นฉันจะได้รับพฤติกรรมที่ฉันไม่ต้องการ

มีวิธีเพิ่มประสิทธิภาพของเอ็นจิ้นการชนประเภทนี้หรือไม่?

นี่คือแนวทางที่ต้องเคารพ:

  • ลำดับการปะทะนั้นสำคัญมาก!

    • ร่างกายจะต้องย้ายในเวลาหนึ่งแล้วตรวจสอบการชนหนึ่งที่เวลาและการแก้ปัญหาหลังจากเคลื่อนไหวในช่วงเวลาหนึ่ง
  • ร่างกายต้องมี 3 กลุ่มบิตเซ็ต

    • กลุ่ม : กลุ่มที่ร่างกายเป็นของ
    • GroupsToCheck : กลุ่มที่ร่างกายต้องตรวจจับการชนกัน
    • GroupsNoResolve : กลุ่มที่ร่างกายต้องไม่แก้ไขการชนกัน
    • อาจมีสถานการณ์ที่ฉันต้องการให้ตรวจพบการชนกัน แต่ไม่สามารถแก้ไขได้



Pre-UPDATE:

คำนำ : ฉันรู้ว่าการเพิ่มประสิทธิภาพคอขวดนี้ไม่จำเป็น - เครื่องยนต์นั้นเร็วมากแล้ว อย่างไรก็ตามเพื่อความสนุกสนานและเพื่อการศึกษาฉันชอบที่จะหาวิธีทำให้เครื่องยนต์ทำงานได้เร็วขึ้น


ฉันกำลังสร้างเอนจิ้นการตรวจจับ / ตอบสนองการชนกันของ C ++ 2D โดยเน้นที่ความยืดหยุ่นและความเร็ว

นี่เป็นแผนภาพพื้นฐานของสถาปัตยกรรม:

สถาปัตยกรรมเครื่องมือพื้นฐาน

โดยทั่วไปชั้นหลักคือWorldซึ่งเป็นเจ้าของ (จัดการหน่วยความจำ) ของResolverBase*เป็นและSpatialBase*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ทั้งหมดที่ร่างกายต้องตรวจสอบการชนกัน

ร่างกายสามารถครอบครองมากกว่าเซลล์เดียว GridInfo เก็บพอยน์เตอร์ที่ไม่ได้เป็นเจ้าของไปยังเซลล์ที่ถูกครอบครองเสมอ


หลังจากร่างกายเคลื่อนที่เพียงครั้งเดียวการตรวจจับการชนจะเกิดขึ้น ฉันคิดว่าวัตถุทั้งหมดเป็นกล่องที่มีขอบเขตเป็นแนวแกน

การตรวจจับการชนกันของเฟสกว้างทำงานอย่างไร:

ส่วนที่ 1: อัปเดตข้อมูลเชิงพื้นที่

สำหรับแต่ละBody body:

    • คำนวณเซลล์ที่ถูกครอบครองที่อยู่ด้านบนซ้ายสุดและเซลล์ที่ถูกครอบครองที่อยู่ทางขวาล่างซ้ายสุด
    • หากแตกต่างจากเซลล์ก่อนหน้าbody.gridInfo.cellsจะถูกล้างและเต็มไปด้วยเซลล์ทั้งหมดที่ร่างกายครอบครอง (2D สำหรับการวนซ้ำจากเซลล์บนซ้ายไปยังเซลล์ล่างขวาสุด)
  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

  • แค่นั้นแหละ.


ดังนั้นฉันจึงพยายามปรับการตรวจจับการชนกันของระยะกว้างให้เหมาะสมในขณะนี้ ทุกครั้งที่ฉันลองอย่างอื่นนอกเหนือจากสถาปัตยกรรม / การตั้งค่าปัจจุบันสิ่งที่ไม่เป็นไปตามแผนที่วางไว้หรือฉันสมมุติว่าการจำลองที่ภายหลังได้รับการพิสูจน์แล้วว่าเป็นเท็จ

คำถามของฉันคือ: ฉันจะเพิ่มประสิทธิภาพระยะกว้างของเอ็นจิ้นการชนได้อย่างไร

มีการเพิ่มประสิทธิภาพเวทมนต์ C ++ บางประเภทที่สามารถนำไปใช้ที่นี่ได้หรือไม่?

สามารถออกแบบสถาปัตยกรรมใหม่เพื่อให้มีประสิทธิภาพมากขึ้นได้หรือไม่


Callgrind output สำหรับรุ่นล่าสุด: http://txtup.co/rLJgz


โปรไฟล์และระบุคอขวด แจ้งให้เราทราบว่าพวกเขาอยู่ที่ไหนจากนั้นเรามีบางอย่างที่จะทำงาน
Maik Semder

@MaikSemder: ฉันทำอย่างนั้นและเขียนไว้ในโพสต์ มันเป็นข้อมูลโค้ดเท่านั้นที่เป็นคอขวด ขออภัยถ้ามันยาวและละเอียด แต่เป็นส่วนหนึ่งของคำถามเพราะฉันแน่ใจว่าคอขวดนี้สามารถแก้ไขได้โดยการเปลี่ยนบางอย่างในการออกแบบเครื่องยนต์
Vittorio Romeo

ขออภัยหายาก คุณสามารถให้ตัวเลขกับเราได้ไหม? เวลาฟังก์ชั่นและจำนวนของวัตถุที่ประมวลผลในฟังก์ชั่นนั้น?
Maik Semder

@MaikSemder: ทดสอบด้วย Callgrind บนไบนารีที่คอมไพล์ด้วย Clang 3.4 SVN -O3: 10,000 ศพแบบไดนามิก - ฟังก์ชั่นgetBodiesToCheck()ถูกเรียก 5462334 ครั้งและใช้เวลา 35,1% ของเวลาการทำโปรไฟล์ทั้งหมด (คำแนะนำเวลาอ่านเข้าถึง)
Vittorio Romeo

2
@Quonux: ไม่มีความผิด ฉันแค่รัก "การปรับแต่งวงล้อ" ฉันสามารถใช้ Bullet หรือ Box2D และสร้างเกมด้วยไลบรารีเหล่านั้น แต่นั่นไม่ใช่เป้าหมายของฉัน ฉันรู้สึกได้รับการเติมเต็มมากขึ้นและเรียนรู้มากขึ้นโดยการสร้างสิ่งต่าง ๆ ตั้งแต่เริ่มต้นและพยายามเอาชนะอุปสรรคที่ปรากฏ - แม้ว่านั่นหมายถึงความผิดหวังและขอความช่วยเหลือ นอกเหนือจากความเชื่อของฉันว่าการเขียนโค้ดตั้งแต่ต้นมีค่าเพื่อการเรียนรู้ฉันยังพบว่ามันสนุกมากและมีความสุขที่ได้ใช้เวลาว่างของฉัน
Vittorio Romeo

คำตอบ:


14

getBodiesToCheck()

getBodiesToCheck()ฟังก์ชันอาจมีปัญหาสองประการ ครั้งแรก:

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

ส่วนนี้คือ O (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;

คุณกำลังยกเลิกการลงทะเบียนตัวชี้ในช่วงการรวบรวม แต่คุณจะทำการยกเลิกการลงทะเบียนในขั้นตอนการทดสอบอยู่ดีดังนั้นถ้าคุณมี L1 มากพอมันก็ไม่ใช่เรื่องใหญ่ คุณสามารถปรับปรุงประสิทธิภาพได้โดยการเพิ่มคำแนะนำก่อนการรวบรวมลงในคอมไพเลอร์เช่น__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

สิ่งนี้หมายความว่าทุกสิ่งที่จำเป็นในการทำการชนจริงนั้นอยู่ในหน่วยความจำที่เป็นมิตรกับแคชเชิงเส้นและคุณจะออกไปที่บิตเฉพาะของการติดตั้งและติดตั้งลงในหนึ่งในสล็อตเหล่านี้หากมีความต้องการ

ในการติดตามการจัดสรรใน vector ของเนื้อความคุณสามารถใช้อาร์เรย์จำนวนเต็มเป็นบิตแมปและใช้bit twiddlingหรือ__builtin_ffsอื่น ๆ วิธีนี้มีประสิทธิภาพสูงสุดในการย้ายไปยังสล็อตที่ครอบครองอยู่ในปัจจุบันหรือค้นหาสล็อตว่างในอาเรย์ คุณยังสามารถกระชับอาร์เรย์บางครั้งถ้ามันมีขนาดใหญ่เกินสมควรแล้วจำนวนมากจะถูกทำเครื่องหมายว่าถูกลบโดยการย้ายอาร์เรย์เหล่านั้นในตอนท้ายเพื่อเติมลงในช่องว่าง

ตรวจสอบแต่ละการชนเพียงครั้งเดียวเท่านั้น

หากคุณได้รับการตรวจสอบถ้าชนกับคุณไม่จำเป็นต้องตรวจสอบว่าชนกับเกินไป

มันตามมาจากการใช้รหัสจำนวนเต็มที่คุณหลีกเลี่ยงการตรวจสอบเหล่านี้ด้วยคำสั่ง if- ง่าย หากรหัสของการชนกันที่อาจเกิดขึ้นน้อยกว่าหรือเท่ากับ ID ปัจจุบันที่กำลังตรวจสอบอยู่ก็สามารถข้ามได้! ด้วยวิธีนี้คุณจะตรวจสอบการจับคู่ที่เป็นไปได้เพียงครั้งเดียวเท่านั้น นั่นจะเกินกว่าครึ่งหนึ่งของการตรวจสอบการชนกันของข้อมูล

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ฮิต C 12ที่ 3

เห็นได้ชัดว่าหลังจากที่คุณรวบรวมการชนกันทั้งหมดแล้วคุณจะเริ่ม popping พวกเขาจากคิวลำดับความสำคัญโดยเร็วที่สุดก่อน ดังนั้นสิ่งแรกที่คุณจะได้คือ A 10 จะเข้าสู่ C 12ที่ 3 คุณเพิ่มหมายเลขลำดับของวัตถุ ( 10บิต) ประเมินการชนกันและคำนวณเส้นทางใหม่ของพวกเขาและเก็บการชนใหม่ในคิวเดียวกัน การปะทะกันใหม่คือ A 11 ถึง B 12ที่ 7 ตอนนี้คิวมี:

  • A 10เพลงฮิต B 12ที่ 6
  • A 11เข้าชม B 12ที่ 7

จากนั้นคุณป๊อปจากคิวลำดับความสำคัญและ A ของ10 B ฮิต12ที่ 6. แต่คุณเห็นว่า10คือเก่า ; A อยู่ที่ 11 ดังนั้นคุณสามารถยกเลิกการชนนี้ได้

เป็นเรื่องสำคัญที่จะไม่ต้องพยายามลบการชนที่เก่าเกินไปจากต้นไม้ การย้ายออกจากกองมีราคาแพง เพียงทิ้งพวกเขาเมื่อคุณป๊อปอัพพวกเขา

ตาราง

คุณควรพิจารณาใช้ควอดทรีแทน มันเป็นโครงสร้างข้อมูลที่ตรงไปตรงมามากที่จะใช้ บ่อยครั้งที่คุณเห็นการใช้งานที่เก็บคะแนน แต่ฉันชอบเก็บ rects และเก็บองค์ประกอบในโหนดที่มี ซึ่งหมายความว่าในการตรวจสอบการชนคุณจะต้องวนซ้ำเนื้อหาทั้งหมดและสำหรับแต่ละรายการให้ตรวจสอบกับเนื้อหาเหล่านั้นในโหนดแบบต้นไม้สี่ต้นเดียวกัน quad-tree เป็นรายการการชนกันที่เป็นไปได้

นี่คือ Quadtree ง่ายๆ:

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
  • ทุกวัตถุที่เคลื่อนย้ายและคงที่ในโหนดแม่ทั้งหมด

สิ่งนี้จะสร้างการชนที่เป็นไปได้ทั้งหมดซึ่งไม่ได้เรียงลำดับ จากนั้นคุณทำการเคลื่อนไหว คุณต้องจัดลำดับความสำคัญการเคลื่อนไหวเหล่านี้ตามระยะทางและ 'ใครเป็นคนแรก' (ซึ่งเป็นความต้องการพิเศษของคุณ) และดำเนินการตามลำดับนั้น ใช้กองนี้

คุณสามารถเพิ่มประสิทธิภาพแม่แบบควอดทรีนี้ คุณไม่จำเป็นต้องจัดเก็บขอบเขตและจุดศูนย์กลางจริงๆ มันเป็นไปได้ทั้งหมดเมื่อคุณเดินบนต้นไม้ คุณไม่จำเป็นต้องตรวจสอบว่าแบบจำลองอยู่ภายในขอบเขตหรือไม่ให้ตรวจสอบเฉพาะด้านที่เป็นจุดกึ่งกลาง (การทดสอบ "การแยกแกน")

ในการสร้างแบบจำลองการบินที่รวดเร็วเช่นขีปนาวุธแทนที่จะเคลื่อนที่ในแต่ละขั้นตอนหรือมีรายการ 'สัญลักษณ์แสดงหัวข้อย่อย' แยกต่างหากที่คุณตรวจสอบอยู่เสมอเพียงแค่วางพวกมันไว้ในควอดทรีพร้อมกับสี่เหลี่ยมของเที่ยวบินของพวกเขา ซึ่งหมายความว่าพวกมันเคลื่อนที่ในควอดทรีน้อยมาก แต่คุณไม่ได้ตรวจสอบกระสุนกับกำแพงที่อยู่ไกลออกไปดังนั้นมันเป็นการแลกเปลี่ยนที่ดี

วัตถุคงที่ขนาดใหญ่ควรแบ่งออกเป็นชิ้นส่วน ยกตัวอย่างเช่นลูกบาศก์ขนาดใหญ่ควรมีการจัดเก็บแต่ละหน้าแยกกัน


"ภาพวาด" ฟังดูดีฉันจะลองดูและรายงานผลลัพธ์โดยเร็วที่สุด ฉันไม่เข้าใจส่วนที่สองของคำตอบของคุณ - ฉันจะพยายามอ่านบางอย่างเกี่ยวกับการดึงข้อมูลล่วงหน้า
Vittorio Romeo

ฉันจะไม่แนะนำ QuadTree มันซับซ้อนกว่าการทำกริดและถ้าทำไม่ถูกต้องมันจะไม่ทำงานอย่างถูกต้องและจะสร้าง / ลบโหนดบ่อยเกินไป
ClickerMonkey

เกี่ยวกับกองของคุณ: ลำดับการเคลื่อนไหวเป็นที่เคารพหรือไม่ พิจารณาร่างกายและร่างกายB เคลื่อนที่ไปทางขวาสู่ B และ B เคลื่อนที่ไปทางขวาไปทาง A ทีนี้เมื่อพวกมันชนกันพร้อม ๆ กันสิ่งที่เคลื่อนที่ก่อนควรได้รับการแก้ไขก่อนและอีกอันจะไม่ได้รับผลกระทบ
Vittorio Romeo

@VittorioRomeo ถ้า A เคลื่อนไปทาง B และ B เคลื่อนที่ไปทาง A ในเห็บเดียวกันและด้วยความเร็วเดียวกันพวกเขาพบกันตรงกลางหรือไม่? หรือ A เคลื่อนก่อนเจอ B ที่ B เริ่มต้นหรือไม่
จะ


3

ฉันพนันได้เลยว่าคุณมีแคชที่หายไปเมื่อทำการวนซ้ำเนื้อหา คุณรวมทุกส่วนของร่างกายของคุณเข้าด้วยกันโดยใช้รูปแบบการออกแบบเชิงข้อมูลบางส่วนหรือไม่? ด้วย N ^ 2 broadphase ฉันสามารถจำลองหลายร้อยและร้อยในขณะที่บันทึกด้วย fraps ของร่างกายโดยไม่ต้องลดลง framerate ใด ๆ ลงในภูมิภาค nether (น้อยกว่า 60) และนี่คือทั้งหมดที่ไม่มีตัวจัดสรรแบบกำหนดเอง แค่คิดว่าจะทำอย่างไรกับการใช้แคชที่เหมาะสม

เบาะแสอยู่ที่นี่:

const std::vector<Body *>

สิ่งนี้จะยกธงสีแดงขนาดใหญ่ขึ้นทันที คุณจัดสรรร่างกายเหล่านี้ด้วยการโทรใหม่แบบดิบหรือไม่? มีการใช้ตัวจัดสรรแบบกำหนดเองหรือไม่ มันเป็นสิ่งสำคัญที่สุดที่คุณมีร่างกายของคุณทั้งหมดในแถวใหญ่ในที่ที่คุณสำรวจเส้นตรง หากการท่องไปตามหน่วยความจำแบบเส้นตรงไม่ใช่สิ่งที่คุณรู้สึกว่าคุณสามารถนำไปใช้ในการพิจารณาโดยใช้รายการที่เชื่อมโยงแบบโจมตีแทน

นอกจากนี้คุณดูเหมือนจะใช้ std :: map คุณรู้วิธีการจัดสรรหน่วยความจำภายใน std :: map อย่างไร? คุณจะมีความซับซ้อน O (lg (N)) สำหรับการสืบค้นแผนที่แต่ละครั้งและสิ่งนี้อาจเพิ่มเป็น O (1) ด้วยตารางแฮช ยิ่งไปกว่านั้นหน่วยความจำที่จัดสรรโดย std :: map นั้นกำลังจะทำการแคชของคุณอย่างน่ากลัวเช่นกัน

ทางออกของฉันคือการใช้ตารางแฮชล่วงล้ำแทน std :: map ตัวอย่างที่ดีของทั้งรายการที่เชื่อมโยงอย่างลวก ๆ และตารางแฮชล่วงล้ำนั้นอยู่ในฐานของ Patrick Wyatt ภายในโครงการโคโฮของเขา: https://github.com/webcoyote/coho

ดังนั้นในระยะสั้นคุณอาจต้องสร้างเครื่องมือที่กำหนดเองสำหรับตัวคุณเองนั่นคือตัวจัดสรรและคอนเทนเนอร์ที่ล่วงล้ำ นี่คือสิ่งที่ดีที่สุดที่ฉันสามารถทำได้โดยไม่ทำโปรไฟล์สำหรับตัวเอง


"คุณจัดสรรร่างกายเหล่านี้ด้วยการโทรใหม่แบบดิบหรือไม่" ฉันไม่ได้เรียกอย่างชัดเจนnewเมื่อผลักดันร่างกายไปยังgetBodiesToCheckเวกเตอร์ - คุณหมายถึงว่ามันเกิดขึ้นภายในหรือไม่ มีวิธีการป้องกันสิ่งนั้นหรือไม่ในขณะที่ยังคงมีคอลเลกชันขนาดไดนามิกอยู่ใช่ไหม
Vittorio Romeo

std::mapไม่ใช่คอขวด - ฉันจำได้ว่าพยายามdense_hash_setและไม่ได้รับประสิทธิภาพใด ๆ
Vittorio Romeo

@Vittorio แล้วส่วนไหนของgetBodiesToCheck ที่เป็นคอขวด? เราต้องการข้อมูลเพื่อช่วย
Maik Semder

@MaikSemder: ผู้สร้างโปรไฟล์ไม่ได้ลึกไปกว่าฟังก์ชั่นของตัวเอง ฟังก์ชั่นทั้งหมดเป็นคอขวดเพราะมันถูกเรียกหนึ่งครั้งต่อเฟรมต่อร่างกาย 10000 bodies = 10,000 การgetBodiesToCheckโทรต่อเฟรม ฉันสงสัยว่าการทำความสะอาด / ผลักอย่างต่อเนื่องในเวกเตอร์เป็นคอขวดของฟังก์ชั่นนั้น containsวิธีการยังเป็นส่วนหนึ่งของการชะลอตัว แต่เนื่องจากbodiesToCheckไม่เคยมีมากกว่า 8-10 ศพในนั้นมันควรจะเป็นที่ช้า
Vittorio Romeo

@Vittorio จะดีถ้าคุณใส่ข้อมูลนี้ลงในคำถามนั่นเป็นเกมเปลี่ยน;) โดยเฉพาะฉันหมายถึงส่วนที่ getBodiesToCheck ถูกเรียกสำหรับเนื้อหาทั้งหมดดังนั้น 10,000 ครั้งต่อเฟรม ฉันสงสัยว่าคุณพูดว่าพวกเขาอยู่ในกลุ่มดังนั้นทำไมพวกเขาใส่ลงในร่างกายเพื่อตรวจสอบอาร์เรย์ถ้าคุณมีข้อมูลกลุ่มแล้ว คุณอาจทำอย่างละเอียดในส่วนนั้นดูเหมือนว่าตัวเลือกเพิ่มประสิทธิภาพที่ดีมากสำหรับฉัน
Maik Semder

1

ลดจำนวนร่างกายเพื่อตรวจสอบแต่ละเฟรม:

ตรวจร่างกายเท่านั้นที่สามารถเคลื่อนย้ายได้จริง วัตถุคงที่จะต้องได้รับมอบหมายให้กับเซลล์ปะทะของคุณเพียงครั้งเดียวหลังจากที่ถูกสร้างขึ้น ตอนนี้ตรวจสอบการชนกันของกลุ่มที่มีวัตถุไดนามิกอย่างน้อยหนึ่งรายการ ควรลดจำนวนการตรวจสอบแต่ละเฟรม

ใช้ควอดทรี ดูคำตอบโดยละเอียดของฉันที่นี่

ลบการจัดสรรทั้งหมดจากรหัสฟิสิกส์ของคุณ คุณอาจต้องการใช้ตัวสร้างโปรไฟล์สำหรับสิ่งนี้ แต่ฉันได้วิเคราะห์การจัดสรรหน่วยความจำใน C # เท่านั้นดังนั้นฉันจึงไม่สามารถช่วยเหลือ C ++ ได้

โชคดี!


0

ฉันเห็นผู้สมัครสองคนที่มีปัญหาในฟังก์ชันคอขวดของคุณ:

แรกคือส่วน "บรรจุ" - นี่อาจเป็นสาเหตุหลักของปัญหาคอขวด มันวนซ้ำผ่านร่างกายที่ค้นพบแล้วสำหรับทุก ๆ ร่างกาย บางทีคุณควรใช้ hash_table / hash_map บางชนิดแทน vector จากนั้นการแทรกควรเร็วขึ้น (ด้วยการค้นหาความซ้ำซ้อน) แต่ฉันไม่รู้ตัวเลขเฉพาะใด ๆ - ฉันไม่รู้ว่ามีกี่ร่างซ้ำที่นี่

ปัญหาที่สองอาจเป็น vector :: clear และ push_back ล้างอาจหรือไม่ทำให้เกิดการจัดสรรใหม่ แต่คุณอาจต้องการหลีกเลี่ยง วิธีแก้ปัญหาอาจเป็นชุดค่าสถานะบางส่วน แต่คุณอาจมีวัตถุจำนวนมากดังนั้นหน่วยความจำจึงไม่มีประสิทธิภาพที่จะมีรายการวัตถุทั้งหมดสำหรับทุกวัตถุ วิธีการอื่นอาจดี แต่ฉันไม่รู้วิธีการใด: /


เกี่ยวกับปัญหาแรก: ฉันลองใช้ dense_hash_set แทนที่จะใช้เวกเตอร์ + และมันก็ช้ากว่า ฉันพยายามเติมเวกเตอร์แล้วลบข้อมูลที่ซ้ำกันทั้งหมดและมันก็ช้าลง
Vittorio Romeo

0

หมายเหตุ: ฉันไม่รู้ C ++ เพียง Java เท่านั้น แต่คุณควรจะสามารถเข้าใจรหัสได้ ฟิสิกส์เป็นภาษาสากลใช่มั้ย ฉันก็รู้ว่านี่เป็นโพสต์เก่าปี แต่ฉันแค่อยากจะแบ่งปันกับทุกคน

ฉันมีรูปแบบการสังเกตการณ์ที่โดยทั่วไปหลังจากเอนทิตีย้ายมันส่งคืนวัตถุที่มีการชนกันรวมถึงวัตถุ NULL ใส่เพียง:

( ฉันกำลังสร้าง Minecraft )

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

สมมติว่าคุณกำลังเดินไปรอบ ๆ ในโลกของคุณ เมื่อใดก็ตามที่คุณโทรหาmove(1)คุณแล้วโทรcollided()คุณแล้วโทรถ้าคุณได้บล็อกที่คุณต้องการบางทีอนุภาคก็ลอยและคุณสามารถเลื่อนไปทางซ้ายและขวา แต่ไม่ไปข้างหน้า

ใช้สิ่งนี้โดยทั่วไปมากกว่าแค่ minecraft เป็นตัวอย่าง:

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

เพียงแค่มีอาเรย์เพื่อชี้พิกัดที่แท้จริงของ Java ใช้พอยน์เตอร์

การใช้วิธีนี้ยังต้องใช้สิ่งอื่นที่ไม่ใช่นิรนัยวิธีการตรวจจับการชนกันของคุณสามารถวนซ้ำสิ่งนี้ได้ แต่นั่นเป็นการเอาชนะจุดประสงค์ คุณสามารถใช้สิ่งนี้กับเทคนิค Broad-, mid- และ collision แคบ ๆ แต่มันก็เป็นสัตว์ร้ายโดยเฉพาะอย่างยิ่งเมื่อมันทำงานกับเกม 3D และ 2D ได้เป็นอย่างดี

ตอนนี้ลองดูอีกครั้งซึ่งหมายความว่าตามวิธี minecraft collide () ของฉันฉันจะจบลงในบล็อกดังนั้นฉันจะต้องย้ายผู้เล่นออกไปข้างนอก แทนที่จะตรวจสอบผู้เล่นฉันต้องเพิ่มกล่องขอบเขตซึ่งตรวจสอบว่าบล็อกใดที่กดปุ่มแต่ละด้านของกล่อง แก้ไขปัญหาแล้ว

ย่อหน้าข้างต้นอาจไม่ง่ายหากมีรูปหลายเหลี่ยมหากคุณต้องการความแม่นยำ เพื่อความถูกต้องฉันขอแนะนำให้กำหนดกรอบรูปหลายเหลี่ยมที่ไม่ใช่รูปสี่เหลี่ยมจัตุรัส แต่ไม่ใช่แบบ tesselated ถ้าไม่เช่นนั้นรูปสี่เหลี่ยมผืนผ้าก็โอเค

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