วงกลมภายในการชนกันของวงกลม


9

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

ตัวอย่างพื้นฐาน

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

สิ่งที่ฉันต้องการจะทำคือก่อนที่จะย้ายวงกลมเล็ก ๆ ฉันทำนายตำแหน่งต่อไปของมันและถ้ามันอยู่นอกฉันจะหาเวลาของการชนกันระหว่าง t = 0 และ t = 1 (t = 1 เต็มเวลา) ถ้าฉันมีเวลาปะทะ t ฉันก็แค่ขยับวงกลมเล็ก ๆ ในช่วง t แทนขั้นตอนเต็มเวลา แต่อีกครั้งปัญหาคือฉันไม่รู้วิธีตรวจจับในเวลานั้นการชนเกิดขึ้นเมื่อมันมาถึงวงกลมสองวงและวงหนึ่งอยู่ภายในอีกวงหนึ่ง

แก้ไข:

ตัวอย่างจุดปะทะ (สีเขียว) ที่ฉันต้องการค้นหา บางทีภาพอาจจะออกมาเล็กน้อย แต่คุณก็เข้าใจ

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

คำตอบ:


10

สมมติว่าวงกลมขนาดใหญ่มีศูนย์AและรัศมีRและวงกลมขนาดเล็กมีศูนย์Bและรัศมีย้ายไปยังสถานที่ตั้งrC

มีวิธีที่สง่างามในการแก้ปัญหานี้โดยใช้เป็นเงินก้อน Minkovskiเปลี่ยนแผ่นดิสก์ของรัศมี (subtractions จริง) Rกับแผ่นดิสก์ของรัศมีR-rและแผ่นดิสก์ของรัศมีrกับแผ่นดิสก์ของรัศมี0, เช่น Bจุดที่เรียบง่ายอยู่ที่ ปัญหากลายเป็นปัญหาการตัดกันของเส้นวงกลม

จากนั้นคุณต้องตรวจสอบว่าระยะทางACนั้นเล็กกว่าR-rหรือไม่ ถ้าเป็นเช่นนั้นวงกลมจะไม่ชนกัน ถ้ามันมีขนาดใหญ่เพียงแค่หาจุดDบนBCที่ระยะห่างR-rของAและนี่คือสถานที่ใหม่ของศูนย์กลางของวงกลมขนาดเล็กของคุณ สิ่งนี้เทียบเท่ากับการค้นหาkสิ่งนั้น:

  vec(BD) = k*vec(BC)
and
  norm(vec(AD)) = R-r

แทนvec(AD)ด้วยการvec(AB) + vec(BD)ให้:

AB² + k² BC² + 2k vec(AB).vec(BC) = (R-r

หากตำแหน่งเริ่มต้นอยู่ในวงกลมขนาดใหญ่สมการกำลังสองในkนี้จะมีรากที่เป็นบวกหนึ่งอัน นี่คือวิธีแก้สมการใน pseudocode:

b = - vec(AB).vec(BC) / BC²    // dot product
c = (AB² - (R-r)²) / BC²
d = b*b - c
k = b - sqrt(d)
if (k < 0)
    k = b + sqrt(d)
if (k < 0)
    // no solution! we must be slightly out of the large circle

ที่มีค่านี้kที่วงกลมขนาดเล็กของศูนย์ใหม่ที่ดังกล่าวว่าDBD = kBC

แก้ไข : เพิ่มโซลูชันสมการกำลังสอง


ขอบคุณมันดูดี แต่ฉันไม่ค่อยแน่ใจว่าฉันจะเข้าใจ ตัวอย่างเช่น: "หาจุด D บน BC ที่ระยะ Rr ของ A" ฉันวาดรูป เพื่อพยายามทำความเข้าใจให้ดีขึ้น ดังนั้นถ้าเราเริ่มต้นที่ B (AX, AY- (Rr)) และ C คือที่ที่เราจะจบลงด้วยความเร็วปัจจุบัน วิธีที่ฉันเข้าใจข้อความที่ยกมา: หาจุด D ในส่วนของเส้นตรง BC ซึ่งเป็นระยะทาง Rr ห่างจาก A. แต่วิธีที่ฉันเห็นในภาพที่ฉันวาดคือว่า B เท่านั้นคือห่างจาก A. ทั้งหมด คะแนนอื่น ๆ จะ> Rr อยู่ห่างจาก A. ฉันหายไปไหน
dbostream

@dbostream คุณไม่ได้ทำอะไรเลย หากทั้งสองวงการที่มีอยู่แล้วในการติดต่อแล้วไม่มีการปะทะกันที่แท้จริงในการตรวจสอบ : การปะทะกันเกิดขึ้นในและB k=0ตอนนี้ถ้ามันเป็นความละเอียดการชนที่คุณต้องการฉันไม่ได้กล่าวถึงในคำตอบของฉันเพราะมันจะต้องมีความรู้เกี่ยวกับคุณสมบัติทางกายภาพของวัตถุ สิ่งที่ควรจะเกิดขึ้น? วงกลมด้านในควรเด้งข้างในหรือไม่ หรือม้วน? กวาด?
sam hocevar

ฉันต้องการวงกลมเล็ก ๆ เพื่อเริ่มเลื่อนไปตามส่วนโค้งของวงกลมใหญ่ ดังนั้นถ้าฉันไม่ผิดฉันต้องการจุดชนในส่วนโค้งของวงกลมใหญ่เพื่อให้ฉันสามารถใช้ปกติเพื่อปรับปรุงความเร็ว
dbostream

@ dbostream หากการเคลื่อนไหวควรถูก จำกัด ด้วยวิธีดังกล่าวฉันขอแนะนำให้คุณปฏิบัติตามข้อ จำกัด นั้นโดยเร็วที่สุด: ถ้าความเร็วนั้นVให้หมุนวงในV*tไปตามเส้นรอบวงของR-rวงกลม ซึ่งหมายความว่าการหมุนของมุมเรเดียนรอบจุดV*t/(R-r) Aและเวกเตอร์ความเร็วสามารถหมุนได้ในลักษณะเดียวกัน ไม่จำเป็นต้องรู้เรื่องปกติ (ซึ่งมักจะเน้นไปที่ศูนย์กลางของวงกลมอยู่เสมอ) หรือเพื่อปรับปรุงความเร็วด้วยวิธีอื่นใด
sam hocevar

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

4

สมมติว่าวงกลมใหญ่คือวงกลม A และวงกลมเล็กคือวงกลม B

ตรวจสอบว่า B อยู่ภายใน A หรือไม่:

distance = sqrt((B.x - A.x)^2 + (B.y - A.y)^2))
if(distance > A.Radius + B.Radius) { // B is outside A }

หากในเฟรมn-1B อยู่ภายใน A และในเฟรมnB อยู่นอก A และเวลาระหว่างเฟรมไม่ใหญ่เกินไป (aka B ไม่เคลื่อนที่เร็วเกินไป) เราสามารถประมาณจุดชนได้โดยเพียงแค่ค้นหาพิกัดคาร์ทีเซียนของญาติ B ถึง A:

collision.X = B.X - A.X;
collision.Y = B.Y - A.Y;

จากนั้นเราสามารถแปลงจุดนี้เป็นมุม:

collision.Normalize(); //not 100% certain if this step is necessary     
radians = atan2(collision.Y, collision.X)

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


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

อืมฉันคิดว่าสถานการณ์เป็นไปได้ อาจทดสอบกับวงกลมใหม่ C ที่มี B.Radius + การเคลื่อนไหวสูงสุดของ B เฟรมนี้ตรวจสอบว่าชนกับ A แล้วออกกำลังกายจุด C ที่ห่างจาก A. (ยังไม่ลอง btw นี้)
Roy ต

3
ใช้คำน้อย: ถ้า (ระยะทาง (A, B))> (Ra-Rb) การชนเกิดขึ้นและคุณเพียงแค่ย้ายวงกลมขนาดเล็กเพื่อให้ได้ระยะทางเท่ากับ Ra-Rb มิฉะนั้นคุณจะย้ายวงกลมขนาดเล็กตามปกติ โดยวิธีการที่ @ dbostream คุณกำลังใช้สิ่งที่คล้ายกับรูปแบบที่เรียบง่ายของผู้ติดต่อแบบเก็งกำไรให้ลองค้นหาสิ่งนั้น
Darkwings

@Darkwings +1 คุณพูดถูกและทำให้ฟังดูง่ายขึ้น!
รอยต

ฟังดูง่ายเพราะฉันถอดรูปเรขาคณิตพื้นฐานทั้งหมดที่จำเป็นออก แทนที่จะตั้งชื่อมันว่า 'การชน' คุณอาจตั้งชื่อมันว่า boundAB เพราะนั่นคือสิ่งที่มันเป็นจริง: เวกเตอร์ฟรี AB ที่ผูกกับ (0,0) เมื่อคุณทำให้มันเป็นมาตรฐานคุณจะได้ทั้งสมการสำหรับขนานกับมัด AB ของเส้นตรงและเวกเตอร์หน่วยที่มีประโยชน์ จากนั้นคุณสามารถคูณเวกเตอร์หน่วยนั้นสำหรับระยะทาง D และเพิ่ม paramenters ที่เพิ่งค้นพบใหม่ไปยัง A เพื่อหาจุดชนที่คุณต้องการ: C (Axe + Dx, Ay + Dy) ตอนนี้มันฟังดูซับซ้อนมากขึ้น แต่มันก็เหมือนกัน: P
Darkwings

0

ให้ (Xa, Ya) ตำแหน่งของวงกลมใหญ่และมันคือรัศมี R และ (Xb, Yb) ตำแหน่งของวงกลมขนาดเล็กและรัศมีของ r

คุณสามารถตรวจสอบว่าวงกลมสองวงนี้ชนกันหรือไม่

DistanceTest = sqrt(((Xa - Xb) ^ 2) + ((Ya - Yb) ^ 2)) >= (R - r)

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

left = 0 //the frame with no collision
right = 1 //the frame after collision
steps = 8 //or some other value, depending on how accurate you want it to be
while (steps > 0)
    checktime = left + (right - left) / 2
    if DistanceTest(checktime) is inside circle //if at the moment in the middle of the interval [left;right] the small circle is still inside the bigger one
        then left = checktime //the collision is after this moment of time
        else right = checktime //the collision is before
    steps -= 1
finaltime = left + (right - left) / 2 // the moment of time will be somewhere in between, so we take the moment in the middle of interval to have a small overall error

เมื่อคุณทราบเวลาปะทะกันให้คำนวณตำแหน่งของวงกลมสองวงในเวลาสุดท้ายและจุดชนสุดท้ายคือ

CollisionX = (Xb - Xa)*R/(R-r) + Xa
CollisionY = (Yb - Ya)*R/(R-r) + Ya

0

ฉันใช้ตัวอย่างลูกที่กระเด้งเป็นวงกลมบน jsfiddle โดยใช้อัลกอริทึมที่อธิบายโดยSam Hocevar :

http://jsfiddle.net/klenwell/3ZdXf/

นี่คือจาวาสคริปต์ที่ระบุจุดติดต่อ:

find_contact_point: function(world, ball) {
    // see https://gamedev.stackexchange.com/a/29658
    var A = world.point();
    var B = ball.point().subtract(ball.velocity());
    var C = ball.point();
    var R = world.r;
    var r = ball.r;

    var AB = B.subtract(A);
    var BC = C.subtract(B);
    var AB_len = AB.get_length();
    var BC_len = BC.get_length();

    var b = AB.dot(BC) / Math.pow(BC_len, 2) * -1;
    var c = (Math.pow(AB_len, 2) - Math.pow(R - r, 2)) / Math.pow(BC_len, 2);
    var d = b * b - c;
    var k = b - Math.sqrt(d);

    if ( k < 0 ) {
        k = b + Math.sqrt(d);
    }

    var BD = C.subtract(B);
    var BD_len = BC_len * k;
    BD.set_length(BD_len);

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