Snabbtips Kollisionsdetektion mellan en cirkel och ett linjesegment

Vi täckte kollisionsdetektering mellan en oändlig linje och cirkel i vår tidigare Quick Tip. Emellertid var problemet som uppstod att linjen sträcker sig längre än det synliga linjesegmentet; i själva verket sträcker det sig till en hyperplan. I denna Snabba Tips ska vi begränsa vår kollisionsdetektering till den för en rad segmentet endast.


Slutresultatförhandsvisning

Vi ska arbeta mot detta resultat:

Klicka på Starta om för att placera om cirklarna högst upp på scenen.


Steg 1: Två tillvägagångssätt

Det finns många sätt att begränsa kollisionsdetektering till inom ett linjesegment. Vi ska titta på två sätt att komma till den här gången. Det första tillvägagångssättet är lite mer strikt matematiskt än det andra, men det är begrepp som, om du förstår framgångsrikt, säkert kommer att gynna dig i framtiden. Båda metoderna manipulerar punktproduktens kännetecken för att vara ett mått på hur parallell två givna vektorer är.

Låt oss ta en titt på det första tillvägagångssättet. Antag att A och B är vektorer. Om A och B är parallella - eller åtminstone pekar i samma riktning - kommer punktprodukten mellan A och B att producera ett positivt tal. Om A och B pekar direkt mot varandra - eller åtminstone pekar i motsatta riktningar - kommer punktprodukten mellan A och B att producera ett negativt tal. Om A och B är ortogonala (bildande 90 ° till varandra) kommer punktprodukten att producera 0.

Diagrammet nedan sammanfattar denna beskrivning.


Steg 2: Koppla primärprodukt till förhållanden

Vi måste bilda vektorer B och C från båda ändarna av linjesegmentet så att deras punktprodukt med linjesegmentets vektor, A, kan avgöra om cirkeln ligger inom segmentet.

Observera diagrammet nedan. Om cirkeln ligger inom segmentet är värdet på punktprodukten mellan A och B positivt och att mellan A och C är negativt.

Diagrammet nedan visar hur prickprodukten ändras beroende på om cirkeln är bortom eller inom linjesegmentet. Notera skillnaderna i värdet på punktprodukten.

Observera också att "inom linjesegmentet" inte betyder att cirkeln nödvändigtvis skär av linjesegmentet, bara att den faller inom de två tunna linjerna på diagrammet ovan.

Så när kollision uppstår mellan linje och cirkel, som vi har sett i den tidigare Quick Tip, måste vi ytterligare undersöka om cirkeln är placerad inom linjesegmentet. Om det är så vet vi säkert att det finns ett genuint skärningspunkt.


Steg 3: Implementering

Steg 2 förklarade det koncept vi använder för att begränsa kollisionsdetektering som ligger inom linjesegmentet. Det finns dock fortfarande en noggrannhet i precisionen. Du ser, det definierade området är lite lutat; vi bör sträva efter att använda det område som definieras enligt diagrammet nedan.

Det här är enkelt: vi beräknar enkelt D som den horisontella projektionen av A. Därefter använder vi D i stället för att använda A för att punktera produkten med B och C. Alla förhållandena som förklaras i steg 2 står fortfarande men i stället för ett lutat segment , vi har definierat ett vertikalt område.

Denna korrigering kan uppskattas visst om cirkeln är stor; om cirkeln var liten skulle dess centrum vara så nära linjen att den här visuella felet skulle vara svår att upptäcka, så vi kunde komma undan med att använda det något lutade området och spara oss lite bearbetningskraft.

Ändå försöker jag göra saker på rätt sätt. Du kan välja ditt tillvägagångssätt genom att ändra tillståndet något.


Steg 4: Implementering

Det första Actionscript-fragmentet här ställer in vektor D (v_line_onX)

 // Att2: att få den horisontella vektorn var line_onX: Number = line.projectionOn (new Vector2D (1, 0)); v_line_onX = ny Vector2D (1, 0); v_line_onX.setMagnitude (line_onX);

Notera: Vi använder klasser från mina tidigare handledning här. Vector2D introducerades i Gravity in Action, men du behöver inte läsa det för att använda klassen, det ingår i källans nedladdning.

Det andra Actionscript-fragmentet här ställer in B (c1_circle) och C (c2_circle) och kontrollerar kollisionen och om cirkeln är inuti segmentet eller inte.

 Uppdatering av privat funktion (e: Event): void for (var i: int = 0; i < circles.length; i++)  //calculating line's perpendicular distance to ball var c1_circle:Vector2D = new Vector2D(circles[i].x - x1, circles[i].y - y1); var c1_circle_onNormal:Number = c1_circle.projectionOn(leftNormal); //Att2: get vector from c2 to circle var c2_circle:Vector2D = new Vector2D(circles[i].x - x2, circles[i].y - y2); circles[i].y += 2; if ( c1_circle_onNormal <= circles[i].radius && v_line_onX.dotProduct(c1_circle) > 0 && v_line_onX.dotProdukt (c2_circle) < 0 ) //if collision happened, undo movement circles[i].y -= 2;   

Steg 5: Resultatet

Här är resultatet för det första tillvägagångssättet. Klicka på knappen för att återställa positioner för alla cirklar till toppen av scenen.


Steg 6: Andra metoden

Det andra tillvägagångssättet är mycket enklare. Jag ska försöka jobba bakåt från slutet denna gång.

Observera diagrammet nedan. Linjesegmentet är från c1 till c2. Det är klart det collide1 och collide3 är båda utanför linjesegmentet, och det enda collide2 ligger inom linjesegmentet.

Låt v1, v2 och v3 vara vektorer från C1 till respektive cirklar. Endast v2 och v3 är parallella - eller åtminstone pekar i liknande riktningar till linjevektorn (c1 till c2). Genom att söka efter ett positivt värde i punktprodukten mellan linjevektorn och vart och ett av dessa vektorer från c1 till motsvarande cirkelcentraler (v1, v2, v3) kan vi enkelt bestämma att collide1 ligger utanför linjesegmentet. Med andra ord, c1. v1 .

Därefter ska vi utforma en metod för att bestämma att collide3 ligger utanför linjesegmentet. Detta borde vara enkelt. Det är uppenbart att v3: s projektion längs linjeviktorn kommer att överskrida längden på linjesegmentet. Vi ska använda den här egenskapen för att slå av kollidera3.

Så låt mig sammanfatta det andra tillvägagångssättet:

  • Först kontrollerar vi för ett korsning mellan oändlig linje och cirkel.
  • Om det finns ett korsning, undersök ytterligare följande för att avgöra om det händer inom linjesegmentet:
    • Kontrollera att ett positivt värde produceras när vi tar punktens produkt från vektorn från c1 till cirkel och linjevektorn, och
    • Kontrollera att storleken på projektionen av vektorn längs linjärvektorn är kortare än linjesegmentets längd.

Steg 7: Implementering

Här är ActionScript-implementeringen av ovanstående:

 Uppdatering av privat funktion (e: Event): void for (var i: int = 0; i < circles.length; i++)  //calculating line's perpendicular distance to ball var c1_circle:Vector2D = new Vector2D(circles[i].x - x1, circles[i].y - y1); var c1_circle_onNormal:Number = c1_circle.projectionOn(leftNormal); //Att2: getting the relevant vectors var c1_circle_onLine:Number = c1_circle.projectionOn(line); circles[i].y += 2; if ( Math.abs(c1_circle_onNormal) <= circles[i].radius && line.dotProduct(c1_circle) > 0 && c1_circle_onLine < line.getMagnitude() ) //if collision happened, undo movement circles[i].y -= 2;   

Steg 8: Resultatet

I huvudsak kommer det att ge samma resultat som tidigare men eftersom det finns några rader kod kortare i det andra tillvägagångssättet antar jag att det är bättre.

Slutsats

Hoppas detta har hjälpt. Tack för att du läser. Nästa upp ser vi kollisionsreaktion.