ค้นหาจุดติดต่อด้วย SAT


12

ทฤษฎีการแยกแกน (SAT) ทำให้ง่ายต่อการพิจารณา Vector Translation ขั้นต่ำเช่นเวกเตอร์ที่สั้นที่สุดที่สามารถแยกวัตถุสองชนกัน อย่างไรก็ตามสิ่งที่ฉันต้องการคือเวกเตอร์ที่แยกวัตถุตามเวกเตอร์ที่วัตถุที่กำลังเคลื่อนที่กำลังเคลื่อนที่ (เช่นจุดสัมผัส)

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

แผนภาพ SAT

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

มีวิธีที่ง่ายกว่าและ / หรือมีประสิทธิภาพมากกว่าในการค้นหาเวกเตอร์จุดติดต่อหรือไม่?


1
คุณตายแล้วหรือยังที่จะใช้งาน SAT? อัลกอริทึมเช่น MPR (การปรับแต่งพอร์ทัล Minkowski) สามารถค้นหารายชื่อผู้ติดต่อโดยตรง ด้วย SAT และ GJK คุณต้องมีอัลกอริทึมแยกต่างหากในการคำนวณจุดติดต่อ
Sean Middleditch

คำตอบ:


6

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

โชคดีที่การทดสอบแกนที่แยกออกมาสามารถช่วยคุณได้ที่นี่! นี่คือคำอธิบายของอัลกอริทึมโดย Ron Levine :

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

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

นี่คือ pseudocode แบบหยาบและไม่มีการตรวจสอบทั้งหมดสำหรับอัลกอริทึม:

t_min := +∞
t_max := -∞
foreach axis in potential_separating_axes
    a_min := +∞
    a_max := -∞
    foreach vertex in a.vertices
        a_min = min(a_min, vertex · axis)
        a_max = max(a_max, vertex · axis)
    b_min := +∞
    b_max := -∞
    foreach vertex in b.vertices
        b_min = min(b_min, vertex · axis)
        b_max = max(b_max, vertex · axis)
    v := b.velocity · axis
    if v > 0 then
        if a_max < b_min then
            return no_intersection
        else if (a_min < b_min < a_max) or (b_min < a_min < b_max) then
            t_min = min(t_min, (a_max - b_min) / v)
            t_max = max(t_max, 0)
        else
            t_min = min(t_min, (a_max - b_min) / v)
            t_max = max(t_max, (a_min - b_max) / v)
    else if v < 0 then
        // repeat the above case with a and b swapped
    else if v = 0 then
        if a_min < b_max and b_min < a_max then
            t_min = min(t_min, 0)
            t_max = max(t_max, 0)
        else
            return no_intersection
if t_max < t_min then
    // advance b by b.velocity * t_max
    return intersection
else
    return no_intersection

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

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


2
ทั้งหมดนี้ยอดเยี่ยมมาก แต่ก็ไม่ได้ตอบคำถามโดยตรงเกี่ยวกับการคำนวณรายชื่อผู้ติดต่อ นอกจากนี้หากฉันเข้าใจอย่างถูกต้องคำตอบนี้ใช้ได้กับความเร็วเชิงเส้นเท่านั้นดังนั้นจึงไม่สามารถรองรับวัตถุหมุนได้ ไม่แน่ใจว่าผู้ถามคำถามต้องการหรือไม่
Sean Middleditch

1
@ seanmiddleditch นั่นเป็นเรื่องจริงมันละเลยการหมุนของเฟรม คุณต้องหมุนทันทีที่จุดเริ่มต้น แต่ไม่มีวิธีใดที่ฉันรู้ว่าความก้าวหน้าเชิงอนุรักษ์นิยมสั้น ๆ นั้นเกี่ยวข้องกับการหมุนอย่างแม่นยำ แม้ว่าจะไม่มีการหมุน แต่มันจะสร้างจุดสัมผัสที่ดีขึ้น
John Calsbeek

2

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

http://www.codezealot.org/archives/394

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

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

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


คุณรู้หรือไม่ว่ามีวิธีการคำนวณ "เวกเตอร์สีน้ำเงิน" (อันที่จำเป็นในการผลักวัตถุกลับออกมาจากรูปร่างตามเวกเตอร์ความเร็ว) โดยใช้ SAT หรือไม่
Tara

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

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

@Dudeson: ขอบ / แกนของการเจาะน้อยที่สุดไม่จำเป็นต้องเป็นขอบของการติดต่อครั้งแรกดังนั้นฉันจึงยังไม่เห็นว่า SAT ช่วยได้อย่างไร ฉันไม่ได้เป็นผู้เชี่ยวชาญในหัวข้อนี้ดังนั้นฉันยอมรับว่าฉันอาจผิด :)
Sean Middleditch

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

0

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


1
"MAT Vector" คุณหมายถึง "MTV"
Tara

0

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

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

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

vec3 vertex;
float mindot = FLT_MAX;
for ( vert : vertices )
{
    if (dot(vert, MTV) < mindot)
    {
         mindot = dot(vert, MTV);
         vertex = vert;
    }
}

เมื่อคุณมีจุดสุดยอดที่ระบุแล้วขั้นตอนครึ่งหนึ่งของไบนารีจะกลายเป็นราคาถูกกว่ามาก:

//mindistance is the where the reference edge/plane intersects it's own normal. 
//The max dot product of all vertices in B along the MTV will get you this value.
halfstep = 1.0f;
vec3 cp = vertex;
vec3 v = A.velocity*framedurationSeconds;
float errorThreshold = 0.01f; //choose meaningful value here
//alternatively, set the while condition to be while halfstep > some minimum value
while (abs(dot(cp,normal)) > errorThreshold)
{            
    halfstep*=0.5f;
    if (dot(cp,normal) < mindistance) //cp is inside the object, move backward
    {
        cp += v*(-1*halfstep);
    }
    else if ( dot(cp,normal) > mindistance) //cp is outside, move it forward
    {
        cp += v*(halfstep);
    }
}

return cp;

นี่เป็นเหตุผลที่ถูกต้อง แต่จะให้จุดการชนเพียงครั้งเดียวในกรณีเดียว

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

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

ที่น่าสนใจวิธี halfstep นี้ยังให้เวลา (เกือบ) เวลาที่วัตถุเกิดขึ้นระหว่างเฟรม:

float collisionTime = frametimeSeconds * halfstep;

หากคุณทำการแก้ปัญหาการชนกันของฟิสิกส์บางประเภทคุณสามารถแก้ไขตำแหน่งของ A โดย:

v - (v*halfstep)

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

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