จะค้นหาเอนทิตีทั้งหมดภายในรัศมีได้อย่างมีประสิทธิภาพได้อย่างไร


14

ฉันมีเอนทิตีจำนวนมาก (หน่วย) ในแต่ละขั้นตอนแต่ละหน่วยจำเป็นต้องทราบตำแหน่งของหน่วยทั้งหมดที่อยู่ใกล้กับมัน (ระยะทางน้อยกว่าเมื่อกำหนดค่าคงที่R ) ทุกหน่วยเคลื่อนที่อย่างต่อเนื่อง นี่เป็นแบบ 3 มิติ

โดยเฉลี่ยจะมี 1% ของจำนวนหน่วยทั้งหมดที่อยู่ใกล้กับหน่วยอื่น ๆ ที่มีข้อ จำกัด ที่กำหนด

ฉันจะทำสิ่งนี้ได้อย่างมีประสิทธิภาพโดยปราศจากการหักห้ามใจ?


7
คุณจะต้องการระบบแบ่งพาร์ติชันแบบพิเศษ: en.wikipedia.org/wiki/Space_partitioning
Tetrad

คำตอบ:


15

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

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

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

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

สมมติว่าโลกของคุณอยู่ในช่วง (-1000, -1000) ถึง (1,000, 1000) ในระนาบ XZ คุณสามารถแบ่งมันเป็นกริด 10 × 10 ได้เช่น:

var grid = new List<Entity>[10, 10];

จากนั้นคุณจะวางเอนทิตีลงในเซลล์ที่เหมาะสมในตาราง ตัวอย่างเช่นเอนทิตีที่มี XZ (-1000, -1000) จะตกอยู่ในเซลล์ (0,0) ในขณะที่เอนทิตีที่มี XZ (1000, 1000) จะตกอยู่ในเซลล์ (9, 9) จากนั้นเมื่อได้รับตำแหน่งและรัศมีในโลกคุณสามารถกำหนดว่าเซลล์ใดที่ถูกตัดกันโดย "วงกลม" นี้และวนซ้ำไปเรื่อย ๆ กับเซลล์เหล่านั้น

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

แก้ไขพบสิ่งนี้ในฟอรัมอื่นและอาจช่วยคุณตัดสินใจ:

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

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


ขอบคุณสำหรับคำตอบรายละเอียด ใช่ดูเหมือนว่าโซลูชันกริดแบบง่ายๆนั้นดีพอสำหรับฉัน
OCyril

0

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

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


0

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

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

ครั้งแรก: คลิปกล่องขอบ 2D

// returns true if the circle supplied is completely OUTSIDE the bounding box, rectClip
bool canTrivialRejectCircle(Vertex2D& vCentre, WorldUnit radius, Rect& rectClip) {
  if (vCentre.x + radius < rectClip.l ||
    vCentre.x - radius > rectClip.r ||
    vCentre.y + radius < rectClip.b ||
    vCentre.y - radius > rectClip.t)
    return true;
  else
    return false;
}

เปรียบเทียบกับสิ่งนี้ (ใน 3D):

BOOL bSphereTest(CObject3D* obj1, CObject3D* obj2 )
{
  D3DVECTOR relPos = obj1->prPosition - obj2->prPosition;
  float dist = relPos.x * relPos.x + relPos.y * relPos.y + relPos.z * relPos.z;
  float minDist = obj1->fRadius + obj2->fRadius;
  return dist <= minDist * minDist;
}.

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

บทความนี้สนับสนุนกล่องสำหรับการปฏิเสธเล็กน้อย http://www.h3xed.com/programming/bounding-box-vs-bounding-circle-collision-detection-performance-as3

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

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

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


1
ผู้ปฏิบัติการกล่าวว่าเขาต้องการหลีกเลี่ยงการใช้กำลังดุร้ายซึ่งเป็นสิ่งที่คุณอธิบายไว้ในย่อหน้าแรก นอกจากนี้คุณคิดว่าการตรวจสอบกล่องมีราคาถูกกว่าการตรวจสอบทรงกลมขอบเขต! นั่นเป็นเพียงความผิด
notlesh

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

เชื่อมโยงไปยัง GDnet เสีย แต่การทดสอบรูปทรงกลมที่ยอมรับเป็นอย่างเรียบง่ายราคาถูกมากและไม่ได้สาขา:inside = (dot(p-p0, p-p0) <= r*r)
ลาร์ส Viklund

ฉันได้วางโค้ดด้านบนแทน มันดูอะไรก็ได้ แต่ราคาถูกกว่าเมื่อเทียบกับกล่องผูก
Ciaran

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

0

ฉันต้องตอบคำถามนี้เพราะฉันไม่มีคะแนนที่จะแสดงความคิดเห็นหรือ upvote สำหรับ 99% ของคนที่ถามคำถามนี้จะมีวิธีแก้ปัญหาตามที่ Ciaran อธิบายไว้ ในภาษาที่คอมไพล์มันจะปฏิเสธ 100,000 หน่วยที่ไม่เกี่ยวข้องในพริบตา มีค่าใช้จ่ายจำนวนมากที่เกี่ยวข้องกับการแก้ปัญหาที่ไม่ใช่สัตว์เดียรัจฉาน ด้วยตัวเลขที่น้อยกว่า (พูดน้อยกว่า 1,000) พวกเขาจะมีราคาแพงกว่าในแง่ของเวลาในการประมวลผลมากกว่าการตรวจสอบกำลังแบบดุร้าย และพวกเขาจะใช้เวลาเขียนโปรแกรมมากขึ้นอย่างมากมาย

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

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


ในขณะที่ไม่มีอะไรผิดปกติกับคำตอบของคุณคุณไม่ได้ตอบคำถาม มันถูกถามโดยเฉพาะสำหรับวิธีการที่ "ไม่ดุร้าย" นอกจากนี้คุณดูเหมือนจะทำซ้ำสิ่งที่ Ciaran เขียนไว้แล้วและเรามีการอภิปรายความคิดเห็นยาวเกี่ยวกับการทดสอบ AABB กับการทดสอบแบบวงกลม ความแตกต่างด้านประสิทธิภาพนั้นไม่เกี่ยวข้องเลย เลือกปริมาณขอบเขตที่เหมาะสมกับผู้สมัครส่วนใหญ่ของคุณเนื่องจากจะลดปริมาณการทดสอบระยะแคบจริง .. ซึ่งจะส่งผลกระทบต่อประสิทธิภาพโดยรวมมากขึ้น
bummzack
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.