ต้นไม้รูปสี่เหลี่ยมเทียบกับการตรวจจับการชนกันของระบบกริด


27

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

ดีกว่าอีกไหม? หรือสง่างามมากขึ้น? ฉันไม่แน่ใจว่าฉันควรใช้อันไหน

คำแนะนำใด ๆ ยินดีต้อนรับ ขอบคุณ

คำตอบ:


31

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

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

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

ถ้าฉันต้องเผชิญกับทางเลือก? ฉันไปกับการติดตั้งกริดเพราะมันง่ายที่จะทำโง่ใช้เวลาของฉันกับปัญหาอื่นที่น่าสนใจกว่า ถ้าฉันสังเกตเห็นว่าเกมของฉันทำงานช้าลงเล็กน้อยฉันจะทำโปรไฟล์และดูว่าจะใช้ความช่วยเหลือได้อย่างไร หากดูเหมือนว่าเกมใช้เวลามากในการตรวจจับการชนฉันจะลองใช้งานอื่นเช่น quadtree (หลังจากหมดการแก้ไขที่ง่ายทั้งหมดก่อน) และดูว่าช่วยได้หรือไม่

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

foreach actor in actorList:
    foreach target in actorList:
        if (actor != target) and actor.boundingbox intersects target.boundingbox:
            actor.doCollision(target)

เห็นได้ชัดว่ามีการแสดงรอบ O (n ^ 2) โดยมีจำนวนนักแสดงที่ยังมีชีวิตอยู่ในเกมรวมถึงกระสุนและยานอวกาศและมนุษย์ต่างดาว นอกจากนี้ยังสามารถรวมถึงสิ่งกีดขวางเคลื่อนที่เล็ก ๆ

วิธีนี้ใช้งานได้ดีอย่างน่าอัศจรรย์ตราบใดที่จำนวนของรายการดังกล่าวมีขนาดเล็กพอสมควร แต่เริ่มดูไม่ดีนักเมื่อมีวัตถุหลายร้อยชิ้นที่ต้องตรวจสอบ ผลลัพธ์ 10 วัตถุในการตรวจสอบการชนเพียง 100 ครั้งและ 100 รายการในการตรวจสอบ 10,000 ครั้ง 1,000 ผลลัพธ์ในหนึ่งล้านเช็ค

ดัชนีเชิงพื้นที่ (เช่นควอดทรี) สามารถระบุรายการที่รวบรวมได้อย่างมีประสิทธิภาพตามความสัมพันธ์ทางเรขาคณิต สิ่งนี้จะเปลี่ยนอัลกอริทึมการชนเป็นดังนี้:

foreach actor in actorList:
    foreach target in actorIndex.neighbors(actor.boundingbox):
       if (actor != target) and actor.boundingbox intersects target.boundingbox:
            actor.doCollision(target)

ประสิทธิภาพของสิ่งนี้ (สมมติว่ามีการแจกแจงแบบเอนทิตีแบบสม่ำเสมอ): โดยปกติคือ O (n ^ 1.5 log (n)) เนื่องจากดัชนีใช้เวลาประมาณ log (n) การเปรียบเทียบกับการสำรวจจะมีเพื่อนบ้าน sqrt (n) เพื่อเปรียบเทียบ และมีนักแสดงอีก n คนที่ต้องตรวจสอบ แม้ว่าตามความเป็นจริงแล้วจำนวนเพื่อนบ้านนั้นค่อนข้าง จำกัด อยู่เสมอเนื่องจากหากเกิดการชนส่วนใหญ่ของวัตถุส่วนใหญ่จะถูกลบหรือย้ายออกจากการปะทะกัน ดังนั้นคุณจะได้รับเพียง O (n log (n)) สำหรับ 10 เอนทิตีคุณทำการเปรียบเทียบ 10 รายการต่อ 100 คุณทำ 200 และ 1,000 คุณ 3000

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


ฉันไม่แน่ใจว่าฉันรู้ว่าสิ่งที่คุณอ้างถึงเมื่อคุณพูดว่า "พื้นหลังคงที่" สิ่งที่ฉันจัดการคือฐานยิง 2D ดังนั้นมันจึงตรวจจับการชนกับยานอวกาศและมนุษย์ต่างดาวกระสุนและกำแพง
dotminic

2
คุณเพิ่งได้รับตรา "คำตอบที่ยอดเยี่ยม" ส่วนตัวของฉัน!
Felixyz

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

2
ต้นไม้ของควอตนั้นเหมาะที่สุดสำหรับการค้นหากล่องขอบที่ทับซ้อนกัน ทางเลือกที่ดีที่สุดคือ R-Tree สำหรับต้นไม้รูปสี่เหลี่ยมถ้าวัตถุส่วนใหญ่มีลักษณะคล้าย ๆ กับจุด ๆ ก็ใช่มันมีเหตุผลที่จะทำให้วัตถุอยู่ในโหนดด้านในและทำการทดสอบการชนกันอย่างแม่นยำในการค้นหาเพื่อนบ้านที่คลุมเครือ หากวัตถุส่วนใหญ่ในดัชนีนั้นมีขนาดใหญ่และทับซ้อนกันโดยไม่มีการชนกันต้นไม้รูปสี่เหลี่ยมอาจเป็นตัวเลือกที่ไม่ดี หากคุณมีคำถามทางเทคนิคเพิ่มเติมเกี่ยวกับเรื่องนี้คุณควรลองพาพวกเขาไปที่ stackoverflow.com
SingleNegationElimination

ทั้งหมดนี้ค่อนข้างสับสน! ขอบคุณสำหรับข้อมูล.
dotminic

3

ขออภัยสำหรับการคืนค่าเธรดโบราณ แต่ IMHO แบบกริดธรรมดาไม่เพียงพอสำหรับกรณีเหล่านี้ มีข้อได้เปรียบมากมายสำหรับกริดในการแทรก / กำจัดเซลล์นั่นคือสิ่งสกปรกราคาถูก คุณไม่ต้องกังวลกับการเพิ่มเซลล์เนื่องจากกริดไม่มีเป้าหมายที่จะเพิ่มประสิทธิภาพสำหรับการแสดงแบบเบาบาง ฉันบอกว่าการลดเวลาลงในการวิ่งเลือกชิ้นส่วนต่างๆใน codebase ดั้งเดิมจากกว่า 1200ms ลงไปเป็น 20ms เพียงแค่แทนที่ quad-tree ด้วย grid ในความเป็นธรรมแม้ว่า quad-tree นั้นถูกนำไปใช้งานได้ไม่ดีจริง ๆ การเก็บอาเรย์แบบไดนามิกแยกจากกันต่อโหนดสำหรับองค์ประกอบ

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

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

struct Node
{
    int32_t next;
    ...
};

struct Grid
{
     vector<int32_t> cells;
     vector<Node> nodes;
};

ชอบมาก

ป้อนคำอธิบายรูปภาพที่นี่

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

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

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

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

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

จากทั้งหมดที่กล่าวมาฉันคิดว่ามันขึ้นอยู่กับโปรแกรมเมอร์ ด้วยสิ่งต่างๆเช่นกริดกับควอดทรีหรือออคทรีเทียบกับ kd-tree เทียบกับ BVH การลงคะแนนของฉันเป็นนักพัฒนาที่มีผลงานมากที่สุดพร้อมบันทึกสำหรับการสร้างโซลูชันที่มีประสิทธิภาพมากไม่ว่าเขาจะใช้โครงสร้างข้อมูลแบบใดก็ตาม มีจำนวนมากทั้งในระดับไมโครเช่น multithreading, SIMD, รูปแบบหน่วยความจำที่เป็นมิตรกับแคชและรูปแบบการเข้าถึง บางคนอาจพิจารณาไมโครเหล่านั้น แต่พวกเขาไม่จำเป็นต้องมีผลกระทบขนาดเล็ก สิ่งต่าง ๆ เหล่านี้สามารถสร้างความแตกต่างได้ 100x จากโซลูชันหนึ่งไปอีกโซลูชันหนึ่ง ทั้งๆที่สิ่งนี้ถ้าฉันได้รับส่วนตัวไม่กี่วันและบอกว่าฉันต้องใช้โครงสร้างข้อมูลเพื่อเร่งการตรวจสอบการชนกันขององค์ประกอบที่เคลื่อนไหวรอบ ๆ เฟรมทุกเฟรมฉันจะทำได้ดีกว่าในช่วงเวลาสั้น ๆ ที่ใช้กริดมากกว่ารูปสี่เหลี่ยม -ต้นไม้.

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