We hebben botsingsdetectie tussen een oneindige lijn en cirkel gedekt in onze vorige Quick Tip. Het probleem dat ontstond was echter dat de lijn verder reikt dan het zichtbare lijnsegment; in feite strekt het zich uit tot een hyperplane. In deze Quick Tip beperken we onze botsingsdetectie tot die van een lijn segment enkel en alleen.
We zullen werken aan dit resultaat:
Klik op de knop Opnieuw opstarten om de cirkels boven in het werkgebied te verplaatsen.
Er zijn talloze manieren om botsingdetectie te beperken tot binnen een lijnsegment. We zullen deze keer naar twee benaderingen kijken. De eerste benadering is mathematisch een beetje rigoureuzer dan de tweede, maar dit zijn concepten die, als je ze met succes begrijpt, zeker in de toekomst van je zullen profiteren. Beide benaderingen manipuleren het kenmerk van het puntproduct dat het een maat is voor hoe parallel twee gegeven vectoren zijn.
Laten we de eerste aanpak eens bekijken. Stel dat A en B vectoren zijn. Als A en B evenwijdig zijn - of in elk geval in dezelfde richting wijzen - levert het puntproduct tussen A en B een positief getal op. Als A en B direct tegenover elkaar wijzen - of ten minste in tegenovergestelde richting wijzen - zal het puntproduct tussen A en B een negatief getal produceren. Als A en B orthogonaal zijn (vormen zich 90 ° ten opzichte van elkaar), dan produceert het puntproduct 0.
Het onderstaande schema vat deze beschrijving samen.
We moeten de vectoren B en C vanaf beide uiteinden van het lijnsegment vormen, zodat hun puntproduct met de vector van het lijnsegment, A, kan bepalen of de cirkel binnen het segment valt.
Bekijk het diagram hieronder. Als de cirkel zich binnen het segment bevindt, is de waarde van het puntproduct tussen A en B positief en tussen A en C negatief.
Het onderstaande diagram laat zien hoe het puntproduct verandert afhankelijk van of de cirkel zich buiten het lijnsegment bevindt of binnen het lijnsegment. Let op de verschillen in de waarde van het puntproduct.
Merk ook op dat "binnen het lijnsegment" niet betekent dat de cirkel noodzakelijkerwijs het lijnsegment snijdt, alleen dat het binnen de twee dunne lijnen op het diagram hierboven valt.
Dus wanneer een botsing plaatsvindt tussen lijn en cirkel, zoals we hebben gezien in de vorige Quick Tip, moeten we verder onderzoeken of de cirkel zich binnen het lijnsegment bevindt. Als dat zo is, weten we zeker dat er een echte kruising is.
In stap 2 werd uitgelegd welk concept we gebruiken om de botsingsdetectie te beperken tot het lijnsegment. Er is echter nog steeds een fout in de precisie. Zie je, het gedefinieerde gebied is een beetje gekanteld; we moeten ernaar streven om het gebied te gebruiken dat is gedefinieerd volgens het onderstaande schema.
Dit is eenvoudig: we berekenen D gewoon als de horizontale projectie van A. Dan gebruiken we D in plaats van A om het product met B en C te dotten. Alle voorwaarden zoals uitgelegd in stap 2 staan nog steeds, maar in plaats van een gekanteld segment , we hebben een verticaal gebied gedefinieerd.
Deze correctie kan visueel worden gewaardeerd als de cirkel groot is; als de cirkel klein was, zou het midden ervan zo dicht bij de lijn zijn dat deze visuele fout moeilijk te detecteren zou zijn, zodat we weg zouden kunnen komen met gebruik van dat enigszins gekantelde gebied en onszelf wat verwerkingskracht besparen.
Toch zal ik proberen de dingen op de juiste manier te doen. U kunt uw nadering kiezen door de toestand enigszins aan te passen.
Het eerste Actionscript-fragment hier stelt vector D in (v_line_onX
)
// Att2: krijgt de horizontale vector var line_onX: Number = line.projectionOn (new Vector2D (1, 0)); v_line_onX = nieuwe Vector2D (1, 0); v_line_onX.setMagnitude (line_onX);
Notitie: We gebruiken hier lessen uit mijn vorige tutorials. Vector2D is geïntroduceerd in Gravity in Action, maar je hoeft dat niet te lezen om het te gebruiken, het is opgenomen in de brondownload.
Het tweede Actionscript-fragment hier stelt B in (c1_circle
) en C (c2_circle
) en controleert op de botsing en of de cirkel binnen het segment valt of niet.
persoonlijke functie vernieuwen (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.dotProduct (c2_circle) < 0 ) //if collision happened, undo movement circles[i].y -= 2;
Dit is het resultaat voor de eerste benadering. Klik op de knop om de posities van alle cirkels naar de bovenkant van het podium te resetten.
De tweede benadering is veel eenvoudiger. Ik zal proberen deze keer achteruit te werken vanaf het einde.
Bekijk het diagram hieronder. Het lijnsegment is van c1 tot c2. Het is duidelijk dat collide1
en collide3
zijn beide buiten het lijnsegment en alleen dat collide2
bevindt zich binnen het lijnsegment.
Laat v1, v2 en v3 vectoren zijn van c1 naar respectieve cirkels. Alleen v2 en v3 zijn parallel - of wijzen minstens in dezelfde richting als de lijnvector (c1 tot c2). Door te controleren op een positieve waarde in het puntproduct tussen de lijnvector en elk van die vectoren van c1 naar de overeenkomstige cirkelcentra (v1, v2, v3), kunnen we gemakkelijk bepalen dat collide1 voorbij het lijnsegment ligt. Met andere woorden, c1. v1 .
Vervolgens zullen we een methode bedenken om te bepalen dat collide3 buiten het lijnsegment valt. Dit zou gemakkelijk moeten zijn. Het is duidelijk dat de projectie van v3 langs de lijnvector de lengte van het lijnsegment overschrijdt. We zullen dit kenmerk gebruiken om collide3 te verwijderen.
Dus laat me de tweede benadering samenvatten:
Dit is de ActionScript-implementatie van het bovenstaande:
persoonlijke functie vernieuwen (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;
In wezen zal het hetzelfde resultaat opleveren als het vorige, maar aangezien er een paar regels code korter is in de tweede benadering, denk ik dat het beter is.
Ik hoop dat dit heeft geholpen. Bedankt voor het lezen. Vervolgens zullen we de botsingsreactie bekijken.