Comprensione delle trasformazioni affini con Matrix Mathematics

Ispirato dal Prof. Wildberger nelle sue serie di conferenze sull'algebra lineare, intendo implementare le sue idee matematiche con Flash. Non ci addentreremo nella manipolazione matematica delle matrici attraverso l'algebra lineare: solo attraverso i vettori. Questa comprensione, pur diluendo l'eleganza dell'algebra lineare, è sufficiente per lanciarci in alcune interessanti possibilità di manipolazione della matrice 2x2. In particolare, lo useremo per applicare vari effetti di taglio, inclinazione, capovolgimento e ridimensionamento alle immagini in fase di esecuzione.


Anteprima del risultato finale

Diamo un'occhiata al risultato finale su cui lavoreremo. Premi i quattro tasti direzionali - su, giù, sinistra, destra - per vedere alcuni effetti che possiamo ottenere con le trasformazioni affini.

Se si usano solo i tasti freccia sinistra e destra, il pesce sembra nuotare in uno spazio isometrico pseudo-3D.


Passaggio 1: diversi spazi di coordinate

La grafica è disegnata su spazi coordinati. Quindi, per manipolarli, soprattutto per tradurre, ruotare, ridimensionare, riflettere e distorcere la grafica, è fondamentale comprendere gli spazi delle coordinate. Generalmente utilizziamo non solo uno, ma più spazi di coordinate in un singolo progetto: questo è vero non solo per i progettisti che utilizzano Flash IDE, ma anche per i programmatori che scrivono ActionScript.

In Flash IDE ciò accade ogni volta che converti i tuoi disegni in simboli MovieClip: ogni simbolo ha la sua origine.

L'immagine sopra mostra l'origine dello spazio delle coordinate dello stage (punto rosso) e quello dello spazio delle coordinate del simbolo (punto di registrazione contrassegnato dal mirino). Per sapere in quale spazio ci si trova attualmente, osserva la barra sotto la timeline di Flash IDE, come mostrato dall'immagine qui sotto.

(Sto usando Flash CS3, quindi la sua posizione potrebbe essere diversa per CS4 e CS5.) Quello che voglio sottolineare è l'esistenza di diversi spazi di coordinate e il fatto che tu abbia già familiarità con il loro utilizzo.


Passaggio 2: la logica

Ora c'è una buona ragione per questo. Possiamo usare uno spazio di coordinate come riferimento per cambiare l'altro spazio di coordinate. Potrebbe sembrare alieno, quindi ho incluso la presentazione Flash qui sotto per facilitare la mia spiegazione. Clicca e trascina le frecce rosse. Giocaci.

Sullo sfondo c'è una griglia blu e in primo piano una griglia rossa. Le frecce blu e rosse sono inizialmente allineate lungo l'asse x e y dello spazio delle coordinate Flash, il cui centro si è spostato al centro dello stage. La griglia blu è una griglia di riferimento; le griglie non cambieranno mentre interagisci con le frecce rosse. La griglia rossa, d'altra parte, può essere riorientata e ridimensionata trascinando le frecce rosse.

Nota che le frecce indicano anche un'importante proprietà di queste griglie. Indicano la nozione di un'unità di x e un'unità di y sulla rispettiva griglia. Ci sono due frecce rosse sulla griglia rossa. Ciascuno di essi indica la lunghezza di un'unità sull'asse xe l'asse y. Inoltre dettano l'orientamento dello spazio delle coordinate. Prendiamo la freccia rossa che punta lungo l'asse x e la estendiamo per il doppio della freccia originale (mostrata in blu). Osservare le seguenti immagini.

Vediamo che l'immagine (la scatola verde) disegnata sulla griglia rossa ora è allungata orizzontalmente, poiché questa griglia rossa su cui è disegnata è ora due volte più larga. Il punto che sto cercando di fare è piuttosto semplice: puoi usare uno spazio di coordinate come base per cambiare un altro spazio di coordinate.


Passaggio 3: spazio di coordinate affini

Quindi cos'è uno "spazio di coordinate affini"? Beh, sono sicuro che stai attento a osservare che questi spazi coordinati sono disegnati usando griglie parallele. Prendiamo ad esempio lo spazio affine rosso: non c'è alcuna garanzia che sia l'asse x che l'asse y siano sempre perpendicolari tra loro, ma state certi che comunque provate a modificare le frecce, non arriverete mai a questo caso come sotto.


Questo spazio di coordinate è non uno spazio di coordinate affini.

In effetti, gli assi X e Y di solito si riferiscono allo spazio delle coordinate cartesiane, come mostrato di seguito.

Notare che le griglie orizzontali e verticali sono perpendicolari l'una all'altra. Il cartesiano è un genere dello spazio di coordinate affini, ma possiamo trasformarlo in altri spazi affini come preferiamo. Le griglie orizzontali e verticali non devono necessariamente essere perpendicolari l'una all'altra.


Esempio di uno spazio di coordinate affini
Un altro esempio di spazio di coordinate affini

Passaggio 4: Trasformazioni affini

Come avrete intuito, le trasformazioni affini sono traslazione, ridimensionamento, riflessione, inclinazione e rotazione.


Spazio affine originale
Spazio affine in scala
Spazio affine riflesso
Spazio affine distorto
Spazio affine ruotato e ridimensionato

Inutile dire, proprietà fisiche come x, y, scaleX, scaleY e rotazione dipende dallo spazio. Quando effettuiamo chiamate a tali proprietà, stiamo effettivamente trasformando le coordinate affini.


Passaggio 5: Understanding Matrix

Spero che le immagini mostrate sopra siano abbastanza esplicite da guidare a casa l'idea. Questo perché per un programmatore che lavora con FlashDevelop, non vedremo quelle griglie che il Flash IDE visualizza comodamente per i progettisti. Tutti questi devono vivere nella tua testa.

Oltre a immaginare queste griglie, dobbiamo anche chiedere l'aiuto di Matrice classe. Quindi, avere una comprensione matematica delle matrici è importante, quindi dovremo rivedere le operazioni di matrice qui: addizione e moltiplicazione.


Passo 6: Significato geometrico dell'aggiunta della matrice

Operazioni a matrice convery significati geometricamente. In altre parole, puoi immaginare cosa significano su un grafico. Supponiamo di avere quattro punti nel nostro spazio di coordinate e vorremmo spostarli in una serie di nuove posizioni. Questo può essere fatto usando l'aggiunta della matrice. Guarda l'immagine qui sotto.

Come puoi vedere, stiamo effettivamente spostando l'intero spazio delle coordinate locali (griglie rosse) dove vengono disegnati questi quattro punti. La notazione per eseguire queste operazioni è come mostrato di seguito:

Possiamo anche vedere che questo spostamento può essere effettivamente rappresentato usando un vettore di (tx, ty). Cerchiamo di differenziare i vettori e i punti statici negli spazi delle coordinate con l'uso di parentesi e parentesi quadre. Li ho riscritti nell'immagine qui sotto.


Passaggio 7: implementazione di ActionScript

Ecco una semplice implementazione di addizione matrice. Controlla i commenti:

 public class Addition estende Sprite public function Addition () var m: Matrix = new Matrix (); // instantiate matrix m.tx = stage.stageWidth * 0.5; // shift in x m.ty = stage.stageHeight * 0.5; // shift in y var d: DottedBox = new DottedBox (); // crea l'immagine personalizzata (la casella punteggiata è una Sprite) addChild (d); d.transform.matrix = m; // applica la matrice al nostro grafico

Step 8: Significato geometrico della moltiplicazione della matrice

La moltiplicazione della matrice è un po 'più sofisticata dell'aggiunta alla matrice, ma il Prof Wildberger ha elegantemente suddiviso questa semplice interpretazione. Tenterò umilmente di reiterare la sua spiegazione. Per coloro che desiderano approfondire la comprensione dell'algebra lineare che porta a questo, dai un'occhiata alla serie di conferenze del professore.

Iniziamo affrontando il caso della matrice identitaria, I.

Dall'immagine sopra sappiamo che moltiplicando una matrice arbitraria, A, dalla matrice di identità, I, produrrà sempre A. Ecco un'analogia: 6 x 1 = 6; la matrice di identità è paragonata al numero 1 in quella moltiplicazione.

In alternativa, possiamo scrivere il risultato nel seguente formato vettoriale che semplificherà notevolmente la nostra interpretazione:

L'interpretazione geometrica di questa formula è mostrata nell'immagine qui sotto.

Dalla griglia cartesiana (griglia a sinistra), possiamo vedere che il punto blu si trova in (2, 1). Ora, se dovessimo trasformare questa griglia originale di xey in una nuova griglia (griglia destra) in base a una serie di vettori (sotto la griglia destra), il punto blu verrà riposizionato su (2, 1) sulla nuova griglia - ma quando mappiamo questo alla griglia originale, è lo stesso punto di prima.

Poiché stiamo trasformando la griglia originale in un'altra griglia che condivide gli stessi vettori per x e y, non vediamo alcuna differenza. In effetti, i cambiamenti di xey in questa trasformazione sono nulli. Questo è ciò che significava matrice identità, da un punto di vista geometrico.

Tuttavia, se proviamo a eseguire una mappatura utilizzando altre trasformazioni, vedremo alcune differenze. So che questo non è stato l'esempio più rivelatore per iniziare, quindi passiamo ad un altro esempio.


Passaggio 9: ridimensionamento lungo X

L'immagine sopra mostra un ridimensionamento dello spazio delle coordinate. Controlla il vettore di x nello spazio delle coordinate trasformate: un'unità dei conti x trasformati per due unità della x originale. Nello spazio delle coordinate trasformate, la coordinata del punto blu è ancora (2, 1). Tuttavia, se provi a mappare questa coordinata dalla griglia trasformata alla griglia originale, è (4, 1).

Questa intera idea è catturata dall'immagine qui sopra. Che ne dici della formula? Il risultato dovrebbe essere coerente; controlliamolo.

Sono sicuro che ricordi queste formule. Ora, ho aggiunto i loro rispettivi significati.

Ora per controllare il risultato numerico del nostro esempio di ridimensionamento.

  • Coordinata originale: (2, 1)
  • Vettore sull'asse x trasformato: (2, 0)
  • Vettore sull'asse Y trasformato: (0, 1)
  • Risultato previsto: (2 * 2 + 0 * 1, 0 * 2 + 1 * 1) = (4, 1)

Sono d'accordo l'uno con l'altro! Ora possiamo applicare felicemente questa idea ad altre trasformazioni. Ma prima di ciò, un'implementazione di ActionScript.


Passaggio 10: implementazione di ActionScript

Controlla l'implementazione di ActionScript (e il SWF risultante) di seguito. Si noti che una delle caselle sovrapposte viene allungata lungo x di una scala di 2. Ho evidenziato i valori importanti. Questi valori verranno ottimizzati nei passaggi successivi per rappresentare diverse trasformazioni.

 public class Multiplication estende Sprite public function Multiplication () var ref: DottedBox = new DottedBox (); // crea grafica di riferimento addChild (ref); ref.x = stage.stageWidth * 0.5; ref.y = stage.stageHeight * 0.5; var m: Matrix = new Matrix (); // instantiate matrix m.tx = stage.stageWidth * 0.5; // shift in x m.ty = stage.stageHeight * 0.5; // shift in y m.a = 2; m.c = 0; m.b = 0; m.d = 1; var d: DottedBox = new DottedBox (); // crea il grafico personalizzato addChild (d); d.transform.matrix = m // applica la matrice sul nostro grafico

Passaggio 11: ridimensionamento X e Y

Qui abbiamo ridimensionato la griglia di un fattore due lungo entrambi gli assi xey. Il punto blu è a (2, 1) nella griglia originale prima della trasformazione e (4, 2) nella griglia originale dopo la trasformazione. (Naturalmente, è ancora a (2, 1) nel nuovo griglia dopo la trasformazione.)

E per confermare il risultato numericamente ...

... si abbinano di nuovo! Per vederlo nell'implementazione di ActionScript, basta cambiare il valore di m.d da 1 a 2.

(Nota che la direzione di allungamento da y è verso il basso, non verso l'alto, perché y si incrementa verso il basso in Flash ma verso l'alto nello spazio normale delle coordinate cartesiane che ho usato nel diagramma.)


Step 12: Riflessione

Qui abbiamo riflesso la griglia lungo l'asse x utilizzando questi due vettori, quindi la posizione del punto blu nella griglia originale cambia da (2, 1) a (-2, 1). Il calcolo numerico è il seguente:

L'implementazione di ActionScript è la stessa di prima, ma utilizzando questi valori: m.a = -1, m.b = 0 per rappresentare il vettore per la trasformazione x e: m.c = 0 e m. d = 1 per rappresentare il vettore per la trasformazione y.

Quindi, che dire della riflessione simultanea su xey? Guarda l'immagine qui sotto.

Inoltre, calcolato numericamente nell'immagine sottostante.

Per l'implementazione di ActionScript ... beh, sono sicuro che conosci i valori da inserire nella matrice. m.a = -1, m.b = 0 rappresentare il vettore per la trasformazione x; m.c = 0 e m. d = -1 per rappresentare il vettore per la trasformazione y. Di seguito ho incluso il SWF finale.


Step 13: Inclinazione e tosatura

Inclinazione viene con un po 'di divertimento. Per il caso dell'immagine qui sotto, la griglia trasformata ha avuto il suo asse x riorientato e ridimensionato. Confronta le frecce rosse in entrambe le griglie di seguito: sono diverse, ma l'asse y rimane invariato.


inclinazione

Visivamente, sembra che la distorsione avvenga lungo la direzione y. Questo è vero perché il nostro asse x trasformato ora ha un componente y nel suo vettore.

Numericamente, questo è quello che succede ...

In termini di implementazione, ho elencato le modifiche qui sotto.

  • m.a = 2
  • m.b = 1
  • m.c = 0
  • m.d = 1

Sono sicuro che a questo punto ti piacerebbe provare le cose da solo, quindi vai avanti e modificalo

  • l'orientamento dell'asse y trasformato mantenendo l'asse x
  • l'orientamento di entrambi gli assi

Ho incluso l'output Flash per entrambi i casi come sotto. Per i lettori che desiderano un aiuto con questi valori, dai un'occhiata Multiplication_final.as nel download di origine.


Passaggio 14: rotazione

Considero la rotazione un sottoinsieme di inclinazione. L'unica differenza è che nella rotazione viene mantenuta la grandezza di un'unità di entrambi gli assi xey, così come la perpendicolarità tra i due assi.

ActionScript fornisce in realtà un metodo in Matrice classe, ruotare(), per fare questo. Ma passiamo attraverso questo comunque.

Ora non vogliamo modificare la grandezza di una lunghezza unitaria in xey dalla griglia originale; solo per cambiare l'orientamento di ciascuno. Possiamo usare la trigonometria per arrivare al risultato mostrato nell'immagine sopra. Dato un angolo di roazione, a, otterremo il risultato desiderato usando i vettori di (cos a, sin a) per l'asse xe (-sin a, cos a) per l'asse y. La magnitudine per ogni nuovo asse sarà comunque di una unità, ma ogni asse sarà ad un angolo di a, rispetto agli originali.

Per l'implementazione Actionscript, assumendo che l'angolo, a, sia di 45 gradi (ovvero radianti di 0,25 * Pi), basta modificare i valori della matrice come segue:

 var a: Number = 0.25 * Math.PI m.a = Math.cos (a); m.c = -1 * Math.sin (a); m.b = Math.sin (a); m.d = Math.cos (a);

La fonte completa può essere consultata in Multiplication_final.as.


Passaggio 15: Applicazione

Avere un'interpretazione vettoriale di una matrice 2x2 apre uno spazio per noi da esplorare. La sua applicazione nella manipolazione di bitmap (BitmapData, LineBitmapStyle, LineGradientStyle, ecc.) è molto diffuso, ma penso che lo salverò per un altro tutorial. Nel caso di questo articolo, tenteremo di distorcere il nostro sprite in fase di esecuzione in modo che sembri effettivamente in 3D.


Vista di un mondo isometrico pseudo-3D

Dall'immagine sopra possiamo vedere che, in un mondo con una vista isometrica, qualsiasi immagine "in piedi" mantiene inalterato il vettore dell'asse y mentre il vettore dell'asse x ruota. Si noti che un'unità di lunghezza per l'asse X e Y non cambia, in altre parole, non dovrebbe verificarsi alcun ridimensionamento in nessuno dei due assi, solo rotazione attorno all'asse x.

Ecco un esempio di questa idea in Flash. Fai clic in qualsiasi punto del palco e inizia a trascinare per vedere l'inclinazione del pesce. Rilascia per interrompere la tua interazione.

Ecco l'importante bit di Actionscript. Ho evidenziato le linee cruciali che gestiscono la rotazione dell'asse x. Puoi anche fare riferimento a FakeIso.as.

 private var f1: Fish, m: Matrix; private var disp: Point; private var axisX: Point, axisY: Point; funzione pubblica FakeIso () disp = new Point (stage.stageWidth * 0.5, stage.stageHeight * 0.5); m = new Matrix (); m.tx = disp.x; m.tà = disp.y; // sposta al centro dello stage f1 = new Fish (); addChild (f1); f1.transform.matrix = m; // applica la trasformazione a su pesce axisX = new Point (1, 0); // vector per x - axis axisY = new Point (0, 1); // vector per y - axis stage.addEventListener (MouseEvent.MOUSE_DOWN, start); // avvia l'interazione stage.addEventListener (MouseEvent.MOUSE_UP, end); // end interaction start della funzione privata (e: MouseEvent): void f1.addEventListener (Event.ENTER_FRAME, update);  funzione privata end (e: MouseEvent): void f1.removeEventListener (Event.ENTER_FRAME, update);  aggiornamento della funzione privata (e: Event): void axisX.setTo (mouseX - f1.x, mouseY - f1.y); // determina l'orientamento (ma anche la magnitudine cambia) axisX.normalize (1); // correzione della grandezza del vettore con nuovo orientamento su 1 unità apply2Matrix (); // applica la matrice sul pesce funzione privata apply2Matrix (): void m.setTo (axisX.x, axisX.y, axisY.x, axisY.y, disp.x, disp.y); f1.transform.matrix = m; 

Qui, ho usato la classe Point per la memorizzazione di vettori.


Passaggio 16: aggiungi il controllo della tastiera

In questo passaggio, tenteremo di aggiungere controlli da tastiera. La posizione del pesce verrà aggiornata in base alla sua velocità, velo. Definiremo anche i passaggi incrementali per la rotazione positiva (in senso orario) e negativa (antioraria).

 velo = new Point (1, 0); // velo sarà usato per definire l'asse xYYY = nuovo punto (0, 1); delta_positive = new Matrix (); delta_positive.rotate (Math.PI * 0.01); // rotazione positiva delta_negative = new Matrix (); delta_negative.rotate (Math.PI * -0.01); // rotazione negativa

Dopo aver premuto un tasto, velo ruoterà:

 funzione privata keyUp (e: KeyboardEvent): void if (e.keyCode == Keyboard.LEFT) velo = delta_negative.transformPoint (velo) // ruota velo in senso antiorario else if (e.keyCode == Keyboard.RIGHT ) velo = delta_positive.transformPoint (velo) // ruota velo in senso orario

Ora per ogni fotogramma, tenteremo di colorare il lato anteriore del pesce e di inclinare anche il pesce. Se la velocità, velo, ha una grandezza di più di 1 e lo applichiamo alla matrice del pesce, m, avremo anche un effetto di scala - quindi per eliminare questa possibilità, normalizzeremo la velocità e quindi applicheremo solo quella alla matrice del pesce.

 aggiornamento della funzione privata (e: Event): void var front_side: Boolean = velo.x> 0 // verifica il lato anteriore del fish if (front_side) f1.colorBody (0x002233,0.5) // colora il lato anteriore di fish else f1.colorBody (0xFFFFFF, 0.5) // bianco applicato al lato posteriore del pesce disp = disp.add (velo); // aggiorna lo spostamento corrente con velocity var velo_norm: Point = velo.clone (); // nel caso di velo> 0, è necessario ricalcolare 1 unità di lunghezza per x. velo_norm.normalize (1); // nota che l'asse x più di 1 eseguirà il ridimensionamento. Non vogliamo che per ora m.setTo (velo_norm.x, velo_norm.y, axisY.x, axisY.y, disp.x, disp.y); f1.transform.matrix = m; 

Passo 17: il tuo pesce

Fare clic sul palco, quindi premere i tasti freccia sinistra e destra per vedere rendere il pesce cambiare direzione.


Passaggio 18: un altro controllo della tastiera

Per rendere più interessanti le cose, permettiamo anche il controllo del vettore dell'asse y.

 funzione privata keyUp (e: KeyboardEvent): void if (e.keyCode == Keyboard.LEFT) velo = delta_negative.transformPoint (velo) else if (e.keyCode == Keyboard.RIGHT) velo = delta_positive.transformPoint (velo) if (e.keyCode == Keyboard.UP) axisY = delta_negative.transformPoint (axisY) else if (e.keyCode == Keyboard.DOWN) axisY = delta_positive.transformPoint (axisY)

Anche per determinare il lato anteriore del pesce, ora dobbiamo incorporare l'asse y. Ecco il codice per questo:

 var front_side: Boolean = velo.x * axisY.y> 0 if (front_side) f1.colorBody (0x002233,0.5) else f1.colorBody (0xFFFFFF, 0.5)

Step 19: Il tuo pesce non-così-normale

Bene, per alcuni il risultato del controllo di entrambi gli assi potrebbe rivelarsi un po 'confuso, ma il punto è che ora puoi inclinare il tuo pesce, tradurlo, rifletterlo e persino ruotarlo! Prova le combo di sopra + sinistra, su + destra, giù + sinistra, giù + destra.

Inoltre, vedi se riesci a mantenere il lato "anteriore" del pesce (il pesce sarà grigio). Suggerimento: tocca continuamente, poi a sinistra, poi in basso, quindi a destra. Stai facendo una rotazione!

Conclusione

Spero che la matematica a matrice sia una risorsa preziosa per i tuoi progetti dopo aver letto questo articolo. Spero di scrivere un po 'di più sulle applicazioni della matrice 2x2 in piccoli Quick Tips che escono da questo articolo, e via Matrix3D che è essenziale per le manipolazioni 3D. Grazie per la lettura, terima kasih.