În tutorialul meu anterior despre detecția coliziunilor între un cerc și o linie, am acoperit proiecția pe o linie folosind produsul punct al unui vector. În acest tutorial, vom examina produsul punctual perpendicular și îl vom folosi pentru a prezice punctul de intersecție pentru două linii.
Să aruncăm o privire asupra rezultatului final la care vom lucra. Utilizați tastele săgeată stânga și dreapta pentru a direcționa nava (triunghi) și apăsați în sus pentru a mări temporar viteza. Dacă punctul viitor de coliziune proiectat este pe perete (linia), pe acesta va fi vopsit un punct roșu. Pentru o coliziune care sa întâmplat deja (adică ar fi trebuit să se întâmple în trecut, pe baza direcției actuale), un punct roșu va fi încă pictat, dar puțin transparent.
Puteți de asemenea să faceți clic pe mouse și să trageți punctele negre pentru a muta peretele. Rețineți că nu prezicem doar localizarea coliziunii, ci și timpul.
Înainte de a intra în subiect, să facem niște revizuiri. Iată ecuația produsului punct (acoperită anterior aici):
Iată definiția perpendiculară a produsului dotat extras din Wolfram:
Acum, pentru a ne ajuta să formăm o imagine mentală, am pregătit imaginea de mai jos. Sunt încrezător în acest moment că sunteți capabil să derivați componentele verticale și orizontale ale unui vector, astfel încât componentele care implică sine și cosinus nu ar trebui să fie o provocare.
Să înlocuim ambele componente cu echivalentul lor. Am folosit A cu o pălărie pentru a reprezenta vectorul unității A (adică un vector care indică aceeași direcție ca A, dar are o magnitudine de exact 1). Un alt detaliu este că perpendicularul lui B este de fapt normalul normal al lui B - mai mult pe normele următoare.
Din diagrama de mai sus vedem că proiecția B pe A va produce | B | * cos (theta)
. Dar de ce ar fi proiecția producției normale a lui B | B | * sin (theta)
?
Pentru a înțelege mai bine acest lucru, am inclus o demonstrație Flash mai jos. Faceți clic și trageți de capul săgeții negre. Pe măsură ce vă mișcați ușor, veți observa că și axa perpendiculară urmează. În timp ce se întorc, liniile roșii îndrăznețe vor fi, de asemenea, animate. Rețineți că aceste două lungimi sunt aceleași - de aici ecuația produsului punct perpendicular.
Normele, prin definiție, se află pe o linie perpendiculară ce intersectează linia dvs. de interes. Să încercăm să ne imaginăm aceste linii pe un plan geometric.
Sistemul de coordonate carteziene este folosit în diagrama de mai sus. B este normalul stâng și C este normalul normal. Vom vedea că componenta x a lui B este negativă (deoarece indică spre stânga) și componenta y a lui C este negativă (pentru că este îndreptată în jos).
Dar verificați asemănările între B și C. Componentele lor x și y sunt aceleași cu cele ale lui A, cu excepția celor swizzled. Diferența este numai poziția semnului. Așa că ajungem la o concluzie după imaginea de mai jos.
Rețineți că ne referim în mod specific la cartezian sistem de coordonate în acest exemplu. Axa y a spațiului de coordonate al Flash este o reflectare a celui din cartezian, rezultând o schimbare între norma stângă și dreapta.
Pentru a identifica punctul de coliziune al vectorului k în planul A, vom lega coadă de k cu un punct arbitrar pe planul A primul. Pentru cazul de mai jos, vectorul j este vectorul de legătură; atunci primim proiecția perpendiculară a k și j pe planul A.
Punctul roșu de pe imaginea de mai jos este punctul de coliziune. Și sper că puteți vedea triunghiul similar în diagrama de mai jos.
Astfel, având în vedere cele trei componente de mai sus, putem folosi conceptul de raport pentru a deduce lungimea dintre punctele roșii și albastre. În cele din urmă, am stabilit magnitudinea vectorului k la lungimea menționată și avem punctul nostru de coliziune!
Deci, vine implementarea ActionScript. Am inclus un demo de mai jos. Încercați să mutați vârfurile săgeților astfel încât ambele linii se intersectează. Un mic punct negru va marca punctul de intersecție al liniilor. Rețineți că aceste segmente nu se intersectează neapărat, dar liniile infinite pe care le reprezintă sunt.
Iată scriptul care face calculele. Verifică Basic.as
în descărcarea sursei pentru scenariul complet.
recalcularea funcției private (): void reorient (); / * Explicați: * v1 și v2 sunt vectori pentru a reprezenta ambele segmente de linie * v1 se alătură set1b (coada) la set1a (cap) - analogic vectorului k în diagrama * v2 se alătură set2b (coada) set2a (cap) * toV2b este vector analogic cu cel al vectorului j în diagrama * / var perp1: Number = v1.perpProduct (v2.normalise ()); var toV2b: Vector2D = Vector2D nou (set2b.x - set1b.x, set2b.y - set1b.y); var perp2: Număr = toV2b.perpProduct (v2.normalise ()); / * Explicați: * lungimea este calculată din raportul triunghiurilor similare * este folosit mai târziu ca magnitudine pentru un vector * care indică direcția lui v1 * / var lungime: Number = perp2 / perp1 * v1.getMagnitude (); Var length_v1: Vector2D = v1.clone (); length_v1.setMagnitude (lungime); / * Explicați * extindeți pentru a localiza locația exactă a punctului de coliziune * / intersec.x = set1b.x + length_v1.x; intersec.y = set1b.y + length_v1.y;
Sper că prima abordare pe care am prezentat-o a fost ușor de înțeles. Înțeleg performanța în obținerea punctului de intersecție este important, așa că în continuare voi oferi abordări alternative, deși acest lucru va necesita revizii de matematică. Poartă cu mine!
În primul rând, să vorbim despre ecuațiile liniei. Există mai multe forme de ecuație de linie, dar vom atinge doar două dintre ele în acest tutorial:
Am inclus imaginea de mai jos pentru a vă ajuta să vă amintiți. Cei interesați de acest lucru se pot referi la această intrare pe Wikipedia.
Înainte de a efectua orice manipulare pe două ecuații de linie, trebuie să obținem mai întâi aceste ecuații de linie. Să luăm în considerare scenariul în care ni se dau coordonatele a două puncte p1 (a, b)
. și p2 (c, d)
. Putem forma o ecuație de linie care leagă aceste două puncte de declivitățile:
Apoi, folosind această ecuație, putem extrage constantele A, B și C pentru formularul standard:
Apoi, putem proceda la rezolvarea ecuațiilor de linie simultane.
Acum, că putem forma ecuații de linie, permitem să luăm două ecuații de linie și să le rezolvăm simultan. Având în vedere aceste două linii de ecuații:
Voi depune acești coeficienți conform formei generale Ax + By = C.
A | B | C |
E | F | G |
P | Q | R |
Pentru a obține valoarea y, facem următoarele:
A | B | C | Înmulțit cu |
E | F | G | P |
P | Q | R | E |
Și ajungem la tabelul următor.
A | B | C |
EP | FP | GP |
PE | QE | RE |
După scăderea a două ecuații, ajungem la:
Deplasarea pentru obținerea lui x:
A | B | C | Înmulțit cu |
E | F | G | Q |
P | Q | R | F |
Ajungem la tabelul următor
A | B | C |
EQ | FQ | GQ |
PF | QF | RF |
După ce scăpăm cele două ecuații, ajungem la:
Să ne rearanjăm în continuare.
Ajungem la punctul de intersecție al lui x și y. Observăm că împărtășesc același numitor.
Acum, când am elaborat operațiile matematice și am obținut rezultatul, trebuie doar să scoatem valorile și avem punctul de intersecție.
Iată implementarea Actionscript. Deci, toate operațiile vectoriale sunt reduse la aritmetică simplă, dar va necesita inițial unele lucrări de algebră.
recalcularea funcției private (): void reorient (); var E: număr = set1b.y - set1a.y; var F: număr = set1a.x - set1b.x; var G: număr = set1a.x * set1b.y - set1a.y * set1b.x; var P: număr = set2b.y - set2a.y; var Q: număr = set2a.x - set2b.x; var R: număr = set2a.x * set2b.y - set2a.y * set2b.x; var numitor: Numărul = (E * Q - P * F); intersec.x = (G * Q - R * F) / numitor; intersec.y = (R * E - G * P) / numitor;
Bineînțeles că este același rezultat ca demo-ul anterioară, doar cu implicarea mai puțină a matematicii și fără folosirea lui Vector2D
clasă.
O altă alternativă la rezolvarea acestei probleme este utilizarea matematică matriceală. Din nou, invit cititorii interesați să se scufunde în prelegerea Prof. Wildberger privind ecuațiile de linii. Aici, vom purta rapid conceptul rapid.
Potrivit Prof. Wildberger, există două cadre pe care le putem adopta:
Să trecem mai întâi prin carteziană. Verificați imaginea de mai jos.
Rețineți că matricea T și S conțin valori constante. Ceea ce a rămas necunoscut este A. Așa că rearanjarea ecuației matricei în termenii lui A ne va da rezultatul. Totuși, trebuie să obținem matricea inversă a lui T.
Iată implementarea celor de mai sus cu ActionScript:
recalcularea funcției private (): void reorient (); var E: număr = set1b.y - set1a.y; var F: număr = set1a.x - set1b.x; var G: număr = set1a.x * set1b.y - set1a.y * set1b.x; var P: număr = set2b.y - set2a.y; var Q: număr = set2a.x - set2b.x; var R: număr = set2a.x * set2b.y - set2a.y * set2b.x; var T: Matrice = matrice nouă (E, P, F, Q); T.invert (); var S: matrice = matrice nouă (); S.a = G; S.b = R; S.concat (T); // multiplicarea matricei intersec.x = S.a; intersec.y = S.b;
În cele din urmă, există forma parametrică a ecuației liniei și vom încerca să o rezolvăm din nou prin matematica matematică.
Am vrea să obținem punctul de intersecție. Având toate informațiile cu excepția u
și v
pe care încercăm să o găsim, vom rescrie ambele ecuații în formă matricală și le vom rezolva.
Din nou, vom efectua manipulări matrice pentru a ajunge la rezultatul nostru.
Deci, aici este implementarea formei de matrice:
recalcularea funcției rivate (): void reorient (); / * Explicați: * r, s se referă de fapt la componentele v2 normalizate * p, q se referă efectiv la componentele v1 normalizate * / var norm_v2: Vector2D = v2.normalise (); var norm_v1: Vector2D = v1.normalise (); var a_c: Număr = set1b.x - set2b.x; var b_d: Numărul = set1b.y - set2b.y; var R: Matrice = matrice nouă; R.a = norm_v2.x; R.c = norm_v1.x; R.b = norm_v2.y; R.d = norm_v1.y; R.invert (); var L: Matrice = matrice nouă; 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;
Am abordat patru abordări pentru a rezolva această mică problemă. Și ce zici de performanță? Ei bine, cred că voi lăsa această problemă cititorilor să judece, deși cred că diferența este neglijabilă. Simțiți-vă liber să utilizați acest ham de testare a performanțelor de la Grant Skinner.
Deci, acum că am dobândit această înțelegere, ce urmează? Aplică-l!
Să presupunem că o particulă se mișcă pe o cale legată să se ciocnească cu un perete. Putem calcula timpul de impact prin ecuația simplă a:
Viteza = Deplasare / Timp
Imaginați-vă că vă aflați în interiorul acestei particule rotunde de culoare portocalie și pentru fiecare cadru de trecere și se face anunțul în momentul în care se colizează cu peretele. Veți auzi:
"Timp de impact: 1,5 cadre" - Cadrul 1
"Timp de impact: 0,5 cadre" - Cadrul 2
"Timp de impact: -0,5 cadre" - Cadrul 3
Când ajungem la rama 3, sa întâmplat deja o coliziune cu linia (așa cum indică semnul negativ). Trebuie să reveniți la timp pentru a ajunge la punctul de coliziune. Evident, coliziunea ar trebui să se întâmple ceva timp între cadrele 2 și 3, dar Flash se mișcă în incremente unice. Deci, dacă o coliziune a avut loc la jumătatea distanței dintre cadre, un semn de semn negativ va indica faptul că coliziunea sa întâmplat deja.
Pentru a obține timp negativ, vom folosi produsul dot vectorial. Știm că atunci când avem două vectori și direcția unuia nu este la 90 de grade nici una dintre celelalte, va produce un produs negativ. De asemenea, produsul dot este o măsură a cât de paralele sunt două vectori. Deci, atunci când sa întâmplat deja o coliziune, viteza și direcția unui vector la un punct pe perete vor fi negative - și invers.
Deci, aici este scenariul (inclus în CollisionTime.as
). De asemenea, am adăugat detectarea coliziunilor în segmentul de linie aici. Pentru cei care nu o cunosc, faceți referire la tutorialul meu privind detectarea coliziunilor între un cerc și un segment de linie, Pasul 6. Și pentru ajutor la navele de direcție, iată o altă referință.
// decide dacă în cadrul segmentului de perete var w2_collision: Vector2D = Vector2D nou (collision.x - w2.x, collision.y - w2.y); collision.alpha = 0; // când nava se îndreaptă spre stânga peretelui dacă (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 = "Nava se îndreaptă spre dreapta peretelui" atunci când nava se îndreaptă spre alt segment de perete var ship_collision: Vector2D = nou Vector2D (collision.x - ship.x, coliziune. y-ship.y); var displacement: Numărul = ship_collision.getMagnitude (); dacă (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; altfel collision.alpha = 0.5; t.appendText ("Coliziunea sa întâmplat deja")
Deci, iată un demo a ceea ce veți ajunge la. Utilizați tastele săgeată stânga și dreapta pentru a direcționa nava (triunghi) și apăsați Sus pentru a mări temporar viteza. Dacă punctul viitor de coliziune prevăzut este pe perete (linia), pe acesta va fi vopsit un punct roșu. Pentru o coliziune care sa întâmplat "deja", un punct roșu va fi încă pictat, dar puțin transparent. Puteți, de asemenea, să trageți punctele negre pe fiecare parte a peretelui pentru ao muta.
Deci, sper că acest tutorial a fost informativ. Distribuiți dacă ați aplicat această idee în altă parte decât cea pe care am menționat-o. Am de gând să scriu o scurtă descriere a aplicării pentru a picta ținte laser - ce crezi? Vă mulțumim pentru lectură și anunță-mă dacă există erori.