I min tidligere veiledning om kollisjonsdetektering mellom en sirkel og en linje dekket jeg projeksjon på en linje ved hjelp av punktproduktet til en vektor. I denne opplæringen skal vi se på det vinkelrette punktproduktet og bruke det til å forutsi skjæringspunktet for to linjer.
La oss se på det endelige resultatet vi skal jobbe for. Bruk venstre og høyre piltast til å styre skipet (trekanten), og trykk på for å øke hastigheten midlertidig. Hvis det projiserte kollisjonspunktet er på veggen (linjen), vil en rød prikk males på den. For en kollisjon som "allerede" skjedde (dvs. ville ha skjedd tidligere, basert på nåværende retning), vil en rød prikk fortsatt være malt, men litt gjennomsiktig.
Du kan også klikke med musen og dra de svarte punktene for å flytte veggen. Vær oppmerksom på at vi ikke bare forutsier plasseringen av kollisjonen, men også tiden.
Før vi kommer inn i emnet, la oss gjøre noen revisjoner. Her er punktproduktligningen (tidligere dekket her):
Og her er den vinkelrette punktproduktdefinisjonen som hentet fra Wolfram:
Nå for å hjelpe oss til å danne et mentalt bilde, har jeg forberedt bildet nedenfor. Jeg er sikker på at du er i stand til å utlede de vertikale og horisontale komponentene i en vektor, så komponentene som involverer sinus og cosinus burde ikke være en utfordring.
La oss erstatte begge komponentene med tilsvarende. Jeg har brukt A med en lue til å representere vektoren til A (det vil si en vektor som peker i samme retning som A, men har en størrelsesorden på nøyaktig 1). En annen detalj er at den vinkelrettede av B faktisk er den riktige normal for B - mer på normals neste trinn.
Fra diagrammet ovenfor kan vi se at projeksjonen av B på A vil produsere | B | * cos (theta)
. Men hvorfor skulle projeksjonen av Bs normale produsere | B | * synd (theta)
?
For bedre å forstå dette har jeg tatt med en Flash-demo nedenfor. Klikk og dra den svarte pilen. Når du beveger deg forsiktig, vil du legge merke til at dens vinkelrette akse følger også. Når de vender seg, vil de dristige røde linjene også bli animert. Merk at disse to lengdene er de samme - dermed ligningen for vinkelrett prikkprodukt.
Normaler ligger per definisjon på en vinkelrett linje som skjærer din interesselinje. La oss prøve å forestille seg disse linjene på et geometrisk plan.
Det kartesiske koordinatsystemet brukes i diagrammet ovenfor. B er venstre normal og C er riktig normal. Vi ser at x-komponenten til B er negativ (fordi den peker mot venstre) og y-komponenten til C er negativ (fordi den peker ned).
Men sjekk ut likhetene mellom B og C. Deres x- og y-komponenter er de samme som A, unntatt swizzled. Forskjellen er bare posisjonen til tegnet. Så vi kommer til en konklusjon av bildet nedenfor.
Vær oppmerksom på at vi refererer spesifikt til kartesiske koordinatsystem i dette eksemplet. Y-aksen til Flashs koordinatrom er en refleksjon av den i Cartesian, noe som resulterer i en bytte mellom venstre og høyre normal.
For å finne ut punktet for kollisjon av vektor k på fly A, skal vi koble sammen hale av k med et vilkårlig punkt på planet A først. For saken nedenfor er vektor j koblingsvektoren; da får vi den vinkelrette projeksjonen av k og j på planet A.
Den røde prikken på bildet under er kollisjonspunktet. Og jeg håper du kan se den tilsvarende trekanten i diagrammet nedenfor.
Så gitt de tre komponentene ovenfor, kan vi bruke begrepet forhold for å utlede lengden mellom de røde og blå punktene. Til slutt bestemmer vi størrelsen på vektor k til nevnte lengde og vi har vårt kollisjonspunkt!
Så her kommer ActionScript-implementeringen. Jeg har tatt med en demo under. Prøv å flytte pilespissene slik at begge linjene krysser. En liten svart prikk markerer krysspunktet for linjene. Merk at disse segmentene ikke nødvendigvis krysser, men de uendelige linjene de representerer er.
Her er skriptet som gjør beregningene. Sjekk ut Basic.as
i kilden nedlasting for hele skriptet.
Tilbakekalkulering av privat funksjon (): void reorient (); / * Forklar: * v1 og v2 er vektorer for å representere begge linjesegmentene * v1 forbinder set1b (hale) til set1a (hodet) - analog til vektor k i diagram * v2 forbinder set2b (hale) til set2a (hodet) * til V2b er vektor analog med det for vektor j i diagram * / var perp1: Nummer = v1.perpProdukt (v2.normalise ()); var toV2b: Vector2D = ny Vector2D (set2b.x - set1b.x, set2b.y - set1b.y); var perp2: Number = toV2b.perpProdukt (v2.normalise ()); / * Forklar: * Lengden beregnes ut fra de tilsvarende trekanter-forholdet * Den brukes senere som størrelsesorden for en vektor * som peker i v1s retning * / varelengde: Nummer = perp2 / perp1 * v1.getMagnitude (); var length_v1: Vector2D = v1.clone (); length_v1.setMagnitude (lengde); / * Forklar * utvide for å finne den nøyaktige plasseringen av kollisjonspunktet * / intersec.x = set1b.x + length_v1.x; intersec.y = set1b.y + length_v1.y;
Så jeg håper den første tilnærmingen jeg har presentert, ble lett forstått. Jeg forstår at ytelsen ved å få skjæringspunktet er viktig, så neste skal jeg gi alternative tilnærminger, selv om det vil kreve noen matteendringer. Bær med meg!
La oss først snakke om linjeekvasjoner. Det er flere former for linjekvasjon, men vi berører bare to av dem i denne opplæringen:
Jeg har tatt med bildet nedenfor for å hjelpe deg med å huske. De som er interessert i dette kan referere til denne oppføringen på Wikipedia.
Før vi utfører manipulasjoner på to linje-ligninger, må vi først oppnå disse linjekvotene. La oss vurdere scenariet der vi får koordinater for to punkter p1 (a, b)
. og p2 (c, d)
. Vi kan danne en linjekvasjon som forbinder disse to punktene fra gradienter:
Ved hjelp av denne ligningen kan vi derfor utlede konstantene A, B og C for standardformularen:
Deretter kan vi fortsette å løse samtidige linjeekvasjoner.
Nå som vi kan danne linjeekvasjoner, kan vi fortsette å ta to linjekvasjoner og løse dem samtidig. Gitt disse to linje ligningene:
Jeg vil skrive disse koeffisientene i henhold til generell form Ax + By = C.
EN | B | C |
E | F | G |
P | Q | R |
For å få verdien av y, gjør vi følgende:
EN | B | C | Multipliser med |
E | F | G | P |
P | Q | R | E |
Og vi kommer til neste tabell.
EN | B | C |
EP | FP | GP |
PE | QE | RE |
Etter at vi trekker to likninger ut, kommer vi til:
Fortsett å skaffe x:
EN | B | C | Multipliser med |
E | F | G | Q |
P | Q | R | F |
Vi kommer til neste tabell
EN | B | C |
EQ | FQ | GQ |
PF | QF | RF |
Etter at vi trekker de to ligningene ut, kommer vi til:
La oss omorganisere y.
Så vi kommer til krysspunktet mellom x og y. Vi legger merke til at de deler samme nevner.
Nå som vi har utarbeidet matteoperasjonen og fått resultatet, bare plukk verdier inn og vi har skjæringspunktet.
Her er ActionScript-implementeringen. Så alle vektoroperasjoner blir redusert til enkel aritmetikk, men det vil kreve noen algebraarbeid i utgangspunktet.
Tilbakekalkulering av privat funksjon (): void reorient (); var E: Number = set1b.y - set1a.y; var F: tall = set1a.x - set1b.x; var G: Nummer = set1a.x * set1b.y - set1a.y * set1b.x; var P: tall = set2b.y - set2a.y; var Q: Number = set2a.x - set2b.x; var R: tall = set2a.x * set2b.y - set2a.y * set2b.x; var nevner: Nummer = (E * Q - P * F); intersec.x = (G * Q - R * F) / nevner; intersec.y = (R * E - G * P) / nevner;
Selvfølgelig er det samme resultat som tidligere demo, bare med mindre matte involvert, og uten bruk av Vector2D
klasse.
Et annet alternativ til å løse dette problemet er ved bruk av matematikkmatematikk. Igjen inviterer jeg interesserte lesere til å dykke inn i Prof. Wildbergers foredrag om linjeledninger. Her vil vi bare bris gjennom konseptet raskt.
Ifølge prof wildberger er det to rammer vi kan vedta:
La oss gå gjennom den kartesiske en først. Sjekk ut bildet nedenfor.
Merk at matrise T og S inneholder konstante verdier. Det som er igjen ukjent er A. Så omarrangering av matrisekvasjonen i form av A vil gi oss resultatet. Imidlertid må vi få den inverse matrisen til T.
Her er implementeringen av ovenstående med ActionScript:
Tilbakekalkulering av privat funksjon (): void reorient (); var E: Number = set1b.y - set1a.y; var F: tall = set1a.x - set1b.x; var G: Nummer = set1a.x * set1b.y - set1a.y * set1b.x; var P: tall = set2b.y - set2a.y; var Q: Number = set2a.x - set2b.x; var R: tall = set2a.x * set2b.y - set2a.y * set2b.x; var T: Matrise = ny matrise (E, P, F, Q); T.invert (); var S: Matrise = ny matrise (); S.a = G; S.b = R; S.concat (T); // multiplisere matrisen intersec.x = S.a; intersec.y = S.b;
Til slutt er det den parametriske formen for linjens ligning, og vi skal forsøke å løse det gjennom matematikkmatematikk igjen.
Vi ønsker å få kryssetpunktet. Gitt all informasjon bortsett fra u
og v
som vi forsøker å finne, skal vi omskrive begge ligningene i matriseform og løse dem.
Så igjen, utfører vi matrise manipulasjoner for å komme til vårt resultat.
Så her er implementeringen av matriseskjemaet:
rivat funksjon omberegning (): void reorient (); / * Forklar: * r, s refererer faktisk til komponenter av v2 normalisert * p, q refererer faktisk til komponenter av v1 normalisert * / var norm_v2: Vector2D = v2.normalise (); var norm_v1: Vector2D = v1.normalise (); var a_c: tall = set1b.x - set2b.x; var b_d: tall = set1b.y - set2b.y; var R: Matrise = ny Matrix; R.a = norm_v2.x; R.c = norm_v1.x; R.b = norm_v2.y; R.d = norm_v1.y; R.invert (); var L: Matrise = ny Matrix; L.a = a_c; L.b = b_d; L.concat (R); intersec.x = set2b.x + L.a * norm_v2.x; intersec.y = set2b.y + L.a * norm_v2.y;
Vi har dekket fire tilnærminger for å løse dette lille problemet. Så hva med ytelse? Vel, jeg tror jeg vil bare forlate dette problemet til leserne å dømme, selv om jeg tror forskjellen er ubetydelig. Du er velkommen til å utnytte denne ytelsestest fra Grant Skinner.
Så nå som vi har fått denne forståelsen, hva er neste? Bruk det!
Anta at en partikkel beveger seg i en bane som er bundet til å kollidere med en vegg. Vi kan beregne tiden til å påvirke av den enkle ligningen av:
Hastighet = forskyvning / tid
Tenk deg at du er inne i denne oransje runde partikkelen og for hver passerende ramme og kunngjøringen er laget på tiden for å kollidere med veggen. Du hører:
"Tid til å påvirke: 1,5 rammer" - Ramme 1
"Tid til å påvirke: 0,5 rammer" - Ramme 2
"Tid til å påvirke: -0,5 rammer" - Ramme 3
Når vi når ramme 3, har kollisjon med linje allerede skjedd (som indikert av negativt tegn). Du må spole tiden for å nå kollisjonspunktet. Selvfølgelig bør kollisjon skje litt mellom rammene 2 og 3, men Flash beveger seg i enkeltrammeventiler. Så hvis kollisjon skjedde halvveis mellom rammer, vil en vende på skiltet til negativ indikere at kollisjonen allerede har skjedd.
For å få negativ tid bruker vi vektorpunktsproduktet. Vi vet at når vi har to vektorer, og retningen til en ikke er innenfor 90 grader, hver side av den andre, vil de produsere et negativt prikkprodukt. Også punktproduktet er et mål på hvor parallelle to vektorer er. Så når kollisjonen allerede har skjedd, vil hastigheten og retningen til en vektor til et punkt på veggen være negativ - og omvendt.
Så her er skriptet (inkludert i CollisionTime.as
). Jeg har også lagt til kollisjonsdeteksjon innenfor linjesegmentet her. For de som finner det ukjent, vennligst referer til veiledningen min om kollisjonsdeteksjon mellom en sirkel og et linjesegment, trinn 6. Og for hjelp med styring av skip, her er en annen referanse.
// bestemmer om i veggsegmentet var w2_collision: Vector2D = ny Vector2D (collision.x - w2.x, collision.y - w2.y); collision.alpha = 0; // når skipet går til venstre for veggen hvis (w2_collision.dotProduct (v1) < 0) t.text = "Ship is heading to left of wall"; else //when ship is heading to right of wall if (w2_collision.getMagnitude() > v1.getMagnitude ()) t.text = "Skipet går rett til veggen" // når skipet går til veggsegmentet annet var ship_collision: Vector2D = ny Vector2D (collision.x - ship.x, kollisjon. y - ship.y); var forskyvning: Nummer = ship_collision.getMagnitude (); hvis (ship_collision.dotProduct (velo) < 0) displacement *= -1; //showing text var time:Number = displacement / velo.getMagnitude(); t.text = "Frames to impact: " + time.toPrecision(3) + " frames.\n"; time /= stage.frameRate; t.appendText("Time to impact: " + time.toPrecision(3) + " seconds.\n"); //drop down alpha if collision had happened if (displacement > 0) collision.alpha = 1; ellers collision.alpha = 0.5; t.appendText ("Kollisjonen hadde allerede skjedd.")
Så her er en demonstrasjon av hva du kommer til. Bruk venstre og høyre piltast til å styre skipet (trekanten), og trykk på Opp for å øke hastigheten midlertidig. Hvis det forutsagte fremtidige kollisjonspunktet er på veggen (linjen), vil en rød prikk bli malt på den. For kollisjon som allerede har skjedd, vil en rød prikk fortsatt være malt, men litt gjennomsiktig. Du kan også dra de sorte prikkene på hver side av veggen for å flytte den.
Så jeg håper denne opplæringen har vært informativ. Del hvis du faktisk har brukt denne ideen andre steder enn den jeg har nevnt. Jeg planlegger en kort skriving om å bruke den til å male lasermål - hva synes du? Takk for at du leser og fortell meg om det er noen feil.