Prevedere i punti di collisione con la matematica in AS3

Nel mio precedente tutorial sul rilevamento delle collisioni tra un cerchio e una linea, ho coperto la proiezione su una linea utilizzando il prodotto punto di un vettore. In questo tutorial, esamineremo il prodotto con punto perpendicolare e lo useremo per prevedere il punto di intersezione per due linee.


Anteprima del risultato finale

Diamo un'occhiata al risultato finale su cui lavoreremo. Utilizzare i tasti freccia sinistra e destra per guidare la nave (triangolo) e premere verso l'alto per aumentare temporaneamente la velocità. Se il punto di collisione futuro proiettato è sul muro (la linea), su di esso verrà dipinto un punto rosso. Per una collisione che "già" è accaduta (cioè sarebbe successo nel passato, in base alla direzione corrente), un punto rosso sarà ancora dipinto ma leggermente trasparente.

Puoi anche fare clic con il mouse e trascinare i punti neri per spostare il muro. Si noti che non prevediamo solo la posizione della collisione, ma anche il tempo.


Passaggio 1: revisione

Prima di entrare nell'argomento, facciamo qualche revisione. Ecco l'equazione di prodotto punto (precedentemente coperto qui):

Ed ecco la definizione del prodotto perpendicolare come estratto da Wolfram:


Passaggio 2: Prodotto con punto perpendicolare

Ora per aiutarci a formare un'immagine mentale, ho preparato l'immagine qui sotto. Sono sicuro che a questo punto sei in grado di ricavare le componenti verticali e orizzontali di un vettore, quindi i componenti che coinvolgono seno e coseno non dovrebbero essere una sfida.

Sostituiamo entrambi i componenti con il loro equivalente. Ho usato A con un cappello per rappresentare il vettore unitario di A (ovvero, un vettore che punta nella stessa direzione di A, ma ha una magnitudine esattamente 1). Un altro dettaglio è che la perpendicolare di B è in realtà la normale normale di B - più sulle normali il prossimo passo.

Dal diagramma sopra possiamo vedere che la proiezione di B su A produrrà | B | * cos (theta). Ma perché dovrebbe essere la proiezione dei prodotti normali di B. | B | * sin (theta)?

Per capire meglio, ho incluso una demo Flash di seguito. Clicca e trascina la freccia nera. Mentre la muovi delicatamente, noterai che anche l'asse perpendicolare segue. Mentre girano, le linee rosse in grassetto saranno anche animate. Nota che queste due lunghezze sono le stesse - da qui l'equazione del prodotto con punto perpendicolare.


Passaggio 3: Normali

Le normali, per definizione, giacciono su una linea perpendicolare che interseca la tua linea di interesse. Proviamo ad immaginare queste linee su un piano geometrico.

Il sistema di coordinate cartesiane è utilizzato nello schema sopra. B è la sinistra normale e C è la giusta normale. Vediamo che la componente x di B è negativa (perché punta a sinistra) e la componente y di C è negativa (perché è rivolta verso il basso).

Ma controlla le somiglianze tra B e C. Le loro componenti x e y sono le stesse di A, eccetto la frizione. La differenza è solo la posizione del segno. Quindi arriviamo a una conclusione dall'immagine qui sotto.

Si noti che ci riferiamo specificamente al cartesiano sistema di coordinate in questo esempio. L'asse y dello spazio delle coordinate di Flash è un riflesso di quello in cartesiano, risultante in uno scambio tra la normale sinistra e destra.


Passaggio 4: Proiezione del punto di collisione

Per capire il punto di collisione del vettore k sul piano A, collegheremo il punto coda di k con un punto arbitrario sul piano A per primo. Per il caso qui sotto, il vettore j è il vettore di collegamento; quindi otteniamo la proiezione perpendicolare di kej sul piano A.

Il punto rosso sull'immagine sottostante è il punto di collisione. E spero che tu possa vedere il triangolo simile nel diagramma qui sotto.

  • | k |, grandezza del vettore k
  • Lunghezza di j sulla perpendicolare del piano A
  • Lunghezza di k sulla perpendicolare del piano A

Quindi, date le tre componenti sopra, possiamo usare il concetto di rapporto per dedurre la lunghezza tra i punti rosso e blu. Infine, impostiamo la grandezza del vettore k per la lunghezza indicata e abbiamo il nostro punto di collisione!


Passaggio 5: implementazione di ActionScript

Quindi ecco l'implementazione di ActionScript. Ho incluso una demo qui sotto. Prova a spostare le punte delle frecce in modo che entrambe le linee si intersechino. Un piccolo punto nero segnerà il punto di intersezione delle linee. Si noti che questi segmenti non si intersecano necessariamente, ma le linee infinite che rappresentano sono.

Ecco lo script che fa i calcoli. Check-out Basic.as nel download di origine per lo script completo.

 ricalcolo della funzione privata (): void reorient (); / * Spiega: * v1 e v2 sono vettori per rappresentare entrambi i segmenti di linea * v1 unisce set1b (coda) a set1a (testa) - analogo al vettore k nel diagramma * v2 unisce set2b (coda) a set2a (testa) * aV2b è vettoriale analogo a quello del vettore j nel diagramma * / var perp1: Number = v1.perpProduct (v2.normalise ()); var toV2b: Vector2D = new Vector2D (set2b.x - set1b.x, set2b.y - set1b.y); var perp2: Number = toV2b.perpProduct (v2.normalise ()); / * Spiega: * la lunghezza è calcolata dal rapporto triangoli simile * è successivamente usata come grandezza per un vettore * che punta nella direzione di v1 * / var length: Number = perp2 / perp1 * v1.getMagnitude (); var length_v1: Vector2D = v1.clone (); length_v1.setMagnitude (lunghezza); / * Spiega * estendi per individuare la posizione esatta del punto di collisione * / intersec.x = set1b.x + length_v1.x; intersec.y = set1b.y + length_v1.y; 

Passaggio 6: equazioni di linea

Quindi spero che il primo approccio che ho presentato sia stato facilmente compreso. Capisco che le prestazioni nell'ottenere il punto di intersezione siano importanti, quindi dopo fornirò approcci alternativi, anche se ciò richiederà alcune revisioni matematiche. Sopportami!

Innanzitutto, parliamo di equazioni di linea. Esistono diverse forme di equazioni di linea, ma ne terremo semplicemente due in questo tutorial:

  • Forma generale
  • Forma parametrica

Ho incluso l'immagine qui sotto per aiutarti a ricordare. Chi è interessato a questo può fare riferimento a questa voce su Wikipedia.


Passaggio 7: derivare modulo standard

Prima di eseguire qualsiasi manipolazione su equazioni a due linee, dobbiamo prima ricavare queste equazioni di linea. Consideriamo lo scenario in cui vengono fornite le coordinate di due punti p1 (a, b). e p2 (c, d). Possiamo formare un'equazione di linea che collega questi due punti ai gradienti:

Quindi, usando questa equazione, possiamo ricavare le costanti A, B e C per la forma standard:

Successivamente, possiamo procedere alla risoluzione di equazioni di linea simultanee.


Passaggio 8: risoluzione delle equazioni simultanee

Ora che possiamo formare equazioni di linea, passiamo a prendere due equazioni di linea e risolverle simultaneamente. Date queste equazioni a due linee:

  • Ex + Fy = G
  • Px + Qy = R

Riporterò questi coefficienti secondo la forma generale Ax + By = C.

UN B C
E F sol
P Q R

Per ottenere il valore di y, facciamo quanto segue:

  1. Moltiplicare i coefficienti reciproci di x per l'intera equazione.
  2. Eseguire l'operazione di sottrazione (da sopra) su entrambe le equazioni.
  3. Riorganizzare l'equazione ottenuta in termini di y.
UN B C Moltiplicato per
E F sol P
P Q R E

E arriviamo alla seguente tabella.

UN B C
EP FP GP
PE QE RI

Dopo aver sottratto due equazioni, arriviamo a:

  • y (FP - QE) = (GP - RE), che riorganizza a:
  • y = (GP - RE) / (FP - QE)

Passando per ottenere x:

UN B C Moltiplicato per
E F sol Q
P Q R F

Arriviamo alla seguente tabella

UN B C
EQ FQ GQ
PF QF RF

Dopo aver sottratto le due equazioni, arriviamo a:

  • x (EQ - PF) = (GQ - RF), che riorganizza a:
  • x = (GQ - RF) / (EQ - PF)

Riorganizziamo ulteriormente y.

  • y = (GP - RE) / (FP - QE)
  • y = (GP - RE) / - (QE - FP)
  • y = (RE - GP) / (QE - FP)

Quindi arriviamo al punto di intersezione di xey. Notiamo che condividono lo stesso denominatore.

  • x = (GQ - RF) / (EQ - PF)
  • y = (RE - GP) / (QE - FP)

Ora che abbiamo elaborato le operazioni matematiche e ottenuto il risultato, basta cogliere i valori e abbiamo il punto di intersezione.


Passaggio 9: Applicazione su ActionScript

Ecco l'implementazione di Actionscript. Quindi tutte le operazioni vettoriali sono ridotte a semplici operazioni aritmetiche, ma inizialmente richiederà alcuni funzionamenti di algebra.

 ricalcolo della funzione privata (): void reorient (); var E: Number = set1b.y - set1a.y; var F: Number = set1a.x - set1b.x; var G: Number = set1a.x * set1b.y - set1a.y * set1b.x; var P: Number = set2b.y - set2a.y; var Q: Number = set2a.x - set2b.x; var R: Number = set2a.x * set2b.y - set2a.y * set2b.x; denominatore var: Number = (E * Q - P * F); intersec.x = (G * Q - R * F) / denominatore; intersec.y = (R * E - G * P) / denominatore; 

Ovviamente è lo stesso risultato della demo precedente, solo con meno calcoli matematici coinvolti e senza l'uso del Vector2D classe.


Passaggio 10: alternativa matrice

Un'altra alternativa alla risoluzione di questo problema è l'uso della matematica a matrice. Di nuovo, invito i lettori interessati a immergersi nella lezione del Prof. Wildberger sulle equazioni di linee. Qui, passeremo rapidamente in rassegna il concetto.

Secondo il prof. Wildberger, ci sono due strutture che possiamo adottare:

  • La struttura cartesiana
  • La struttura vettoriale parametrizzata

Passiamo prima a quello cartesiano. Guarda l'immagine qui sotto.

Si noti che le matrici T e S contengono valori costanti. Ciò che rimane sconosciuto è A. Quindi riordinare l'equazione della matrice in termini di A ci darà il risultato. Tuttavia, dobbiamo ottenere la matrice inversa di T.


Passaggio 11: implementazione con AS3

Ecco l'implementazione di quanto sopra con ActionScript:

 ricalcolo della funzione privata (): void reorient (); var E: Number = set1b.y - set1a.y; var F: Number = set1a.x - set1b.x; var G: Number = set1a.x * set1b.y - set1a.y * set1b.x; var P: Number = set2b.y - set2a.y; var Q: Number = set2a.x - set2b.x; var R: Number = set2a.x * set2b.y - set2a.y * set2b.x; var T: Matrix = new Matrix (E, P, F, Q); T.invert (); var S: Matrix = new Matrix (); S.a = G; S.b = R; S.concat (T); // moltiplicando la matrice intersec.x = S.a; intersec.y = S.b; 

Passaggio 12: modulo parametrico

Infine, c'è la forma parametrica dell'equazione di linea e tenteremo di risolverlo nuovamente con la matematica matriciale.

Vorremmo ottenere il punto di intersezione. Dato tutte le informazioni tranne per u e v che cerchiamo di trovare, riscriveremo entrambe le equazioni in forma di matrice e risolverle.


Passaggio 13: Manipolazione della matrice

Quindi, di nuovo, eseguiamo manipolazioni a matrice per arrivare al nostro risultato.


Passaggio 14: implementazione con AS3

Quindi ecco l'implementazione del modulo matrix:

 rivalutazione della funzione rivale (): void reorient (); / * Spiega: * r, s in realtà si riferiscono a componenti di v2 normalizzati * p, q in realtà si riferiscono a componenti di v1 normalizzati * / var norm_v2: Vector2D = v2.normalise (); var norm_v1: Vector2D = v1.normalise (); var a_c: Number = set1b.x - set2b.x; var b_d: Number = set1b.y - set2b.y; var R: Matrix = new 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: Matrix = new 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; 

Step 15: Performance

Abbiamo coperto quattro approcci per risolvere questo piccolo problema. Quindi per quanto riguarda le prestazioni? Beh, penso che lascerò questo problema ai lettori per giudicare, anche se credo che la differenza sia trascurabile. Sentiti libero di utilizzare questa imbracatura per test delle prestazioni di Grant Skinner.

Quindi ora che abbiamo capito questa cosa, qual è il prossimo? Applicalo!


Passo 16: Previsione del tempo di collisione

Supponiamo che una particella si muova in un percorso destinato a scontrarsi con un muro. Possiamo calcolare il tempo di impatto dalla semplice equazione di:

Velocity = Displacement / Time

Immagina di essere all'interno di questa particella rotonda arancione e per ogni fotogramma passante e l'annuncio viene fatto sul momento di scontrarsi con il muro. Sentirai:

"Tempo di impatto: 1.5 fotogrammi" - Fotogramma 1

"Tempo di impatto: 0,5 fotogrammi" - Fotogramma 2

"Tempo di impatto: -0.5 frame" - Frame 3

Quando raggiungiamo il fotogramma 3, la collisione con la linea è già avvenuta (come indicato dal segno negativo). Devi riavvolgere il tempo per raggiungere il punto di collisione. Ovviamente la collisione dovrebbe passare un po 'di tempo tra i frame 2 e 3, ma il flash si muove con incrementi di singolo frame. Quindi, se la collisione si verifica a metà strada tra i frame, un capovolgimento del segno in negativo indica che la collisione è già avvenuta.


Passaggio 17: tempo negativo

Per ottenere un tempo negativo, utilizzeremo il prodotto punto vettoriale. Sappiamo che quando abbiamo due vettori e la direzione di uno non è a 90 gradi da una parte all'altra dell'altra, produrranno un punto negativo. Inoltre, il prodotto punto è una misura di come due vettori paralleli sono. Quindi, quando la collisione è già avvenuta, la velocità e la direzione di un vettore in un punto sul muro saranno negative e viceversa.


Passaggio 18: Implementazione di ActionScript

Quindi ecco la sceneggiatura (inclusa in CollisionTime.as). Ho anche aggiunto il rilevamento delle collisioni all'interno del segmento di linea qui. Per coloro che non lo conoscono, fai riferimento al mio tutorial sul rilevamento delle collisioni tra un cerchio e un segmento di linea, Passaggio 6. E per l'aiuto sul governo delle navi, ecco un altro riferimento.

 // decidere se all'interno del segmento di muro var w2_collision: Vector2D = new Vector2D (collision.x - w2.x, collision.y - w2.y); collision.alpha = 0; // quando la nave è diretta a sinistra del muro se (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 = "La nave sta andando a destra del muro" // quando la nave si dirige verso il segmento di muro else var ship_collision: Vector2D = new Vector2D (collision.x - ship.x, collision. y - ship.y); var dislocamento: Number = ship_collision.getMagnitude (); if (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; else collision.alpha = 0.5; t.appendText ("La collisione era già avvenuta.")

Step 19: Risultato finale

Quindi, ecco una demo di ciò che arriverà. Utilizzare i tasti freccia sinistra e destra per guidare la nave (triangolo) e premere Su per aumentare temporaneamente la velocità. Se il punto di collisione futuro previsto è sul muro (la linea), su di esso verrà dipinto un punto rosso. Per una collisione che è già "accaduta", un punto rosso verrà comunque dipinto ma leggermente trasparente. Puoi anche trascinare i punti neri su entrambi i lati del muro per spostarlo.

Conclusione

Quindi spero che questo tutorial sia stato informativo. Condividi se hai effettivamente applicato questa idea altrove rispetto a quella che ho menzionato. Sto pianificando una breve descrizione sull'applicarlo per dipingere obiettivi laser - cosa ne pensi? Grazie per la lettura e fammi sapere se ci sono errori.