Forstå Affine Transformations With Matrix Mathematics

Inspirert av Prof. Wildberger i sin forelesningsserie om lineær algebra, har jeg tenkt å implementere sine matematiske ideer med Flash. Vi skal ikke dype inn i matematisk manipulering av matriser via lineær algebra: bare gjennom vektorer. Denne forståelsen, selv om det fortynner elegansen av lineær algebra, er nok til å lansere oss inn i noen interessante muligheter for 2x2 matriseprofilering. Spesielt vil vi bruke den til å bruke ulike skjærings-, skjev-, flippings- og skaleringseffekter til bilder ved kjøring.


Endelig resultatforhåndsvisning

La oss se på det endelige resultatet vi skal jobbe for. Trykk på de fire retningstastene - opp, ned, venstre, høyre - for å se noen effekter vi kan oppnå med affine transformasjoner.

Hvis du bare bruker venstre og høyre piltast, synes fisken å svømme rundt i et pseudo-3D isometrisk rom.


Trinn 1: Forskjellige koordineringsrom

Grafikk trekkes på koordinatrom. Så for å manipulere dem, spesielt for å oversette, rotere, skalere, reflektere og skrå grafikk, er det viktig at vi forstår koordinatrom. Vi bruker vanligvis ikke bare en, men flere koordinatrom i et enkelt prosjekt - dette gjelder ikke bare for designere som bruker Flash IDE, men også for programmerere som skriver ActionScript.

I Flash IDE skjer dette når du konverterer tegningene dine til MovieClip-symboler: hvert symbol har sin egen opprinnelse.

Bildet over viser opprinnelsen til scenen 'koordinatplass (rød prikk) og symbolet på symbolets koordinatrom (registreringspunkt merket med krysshår). For å vite hvilken plass du er i for øyeblikket, observere linjen under tidslinjen til Flash IDE som vist på bildet nedenfor.

(Jeg bruker Flash CS3, så plasseringen kan variere for CS4 og CS5.) Det jeg vil understreke er eksistensen av forskjellige koordinatrom, og det faktum at du allerede er kjent med å bruke dem.


Trinn 2: Begrunnelsen

Nå er det en god grunn til dette. Vi kan bruke en koordinatplass som en referanse for å endre den andre koordinasjonsplassen. Dette kan høres fremmed, så jeg har tatt med Flash presentasjonen nedenfor for å legge til rette for min forklaring. Klikk og dra de røde pilene. Spill rundt med det.

I bakgrunnen er et blått rutenett, og i forgrunnen er det et rødt rutenett. De blå og røde pilene er i utgangspunktet justert langs x- og y-aksen til Flash-koordinatplassen, hvis senter jeg har skiftet til midten av scenen. Det blå rutenettet er et referansegitter; Rutenettet vil ikke forandres når du samhandler med de røde pilene. Det røde rutenettet, derimot, kan omorienteres og skaleres ved å dra de røde pilene.

Merk at pilene også angir en viktig egenskap for disse nettene. De indikerer begrepet en enhet x og en enhet på y på deres respektive rutenett. Det er to røde piler på det røde rutenettet. Hver av dem angir lengden på en enhet på x-aksen og y-aksen. De dikterer også retningen for koordinatrommet. La oss ta den røde pilen som peker langs x-aksen, og utvide den til å være dobbelt så lang som den opprinnelige pilen (vist i blått). Følg følgende bilder.

Vi ser at bildet (den grønne boksen) trukket på det røde rutenettet nå strekkes horisontalt, på grunn av at dette røde rutenettet er trukket på, er nå dobbelt så bredt. Poenget jeg prøver å lage er ganske enkelt: Du kan bruke en koordinatplass som grunnlag for å endre et annet koordinatrom.


Trinn 3: Affine Koordinering Space

Så hva er et "affine koordinatrom"? Vel, jeg er sikker på at du er forsiktig nok til å observere at disse koordinatene blir tegnet ved hjelp av parallelle grid. La oss ta den røde affine plass for eksempel: det er ingen garanti for at både x-aksen og y-aksen er alltid vinkelrett på hverandre, men vær sikker på at du forsøker å justere pilene, men du kommer aldri til å komme til en slik sak som Nedenfor.


Denne koordinaterommet er ikke en affine koordinatplass.

Faktisk refererer x- og y-akser vanligvis til kartesisk koordinatrom, som vist nedenfor.

Legg merke til at de horisontale og vertikale rutenettene er vinkelrett på hverandre. Cartesian er en type av affine koordinatrom, men vi kan forvandle det til andre affine mellomrom som vi foretrekker. De horisontale og vertikale rutenettene trenger ikke nødvendigvis å være vinkelrett på hverandre.


Eksempel på et affine koordinatrom
Et annet eksempel på en affine koordinatplass

Trinn 4: Affinere transformasjoner

Som du kanskje har gjettet, er affine-transformasjonene oversettelse, skalering, refleksjon, skeving og rotasjon.


Original affine plass
Skalert affine plass
Reflektert affine plass
Skewed affine plass
Rotert og skalert affine plass

Unødvendig å si, fysiske egenskaper som x, y, scaleX, scaleY og rotasjon avhenger av plassen. Når vi ringer til disse egenskapene, forvandler vi faktisk affine koordinater.


Trinn 5: Forstå Matrix

Jeg håper bildene vist ovenfor er eksplisitte nok til å drive hjem ideen. Dette skyldes at for en programmerer som arbeider med FlashDevelop, ser vi ikke disse nettene som Flash IDE beleilig viser for designere. Alle disse må leve i hodet ditt.

Bortsett fra å tenke på disse nettene, må vi også få hjelp av Matrise klasse. Det er derfor viktig å ha matematisk forståelse av matriser, så vi skal revidere operasjonen av matrisen her: tillegg og multiplikasjon.


Trinn 6: Geometrisk betydning av matrise tillegg

Matrix operasjoner convery betyr geometrisk. Med andre ord kan du se hva de betyr på en graf. La oss anta at vi har fire poeng i vårt koordinatrom og ønsker å skifte dem til et sett med nye steder. Dette kan gjøres ved hjelp av matrise tillegg. Sjekk ut bildet nedenfor.

Som du kan se, skifter vi faktisk hele det lokale koordinatområdet (røde grid) hvor disse fire punktene er tegnet. Notasjonen for å utføre disse operasjonene er som vist nedenfor:

Vi kan også se at dette skiftet faktisk kan representeres ved hjelp av en vektor av (tx, ty). La oss skille vektorer og statiske punkter i koordinatrom ved bruk av parenteser og firkantede parenteser. Jeg har omskrevet dem i bildet nedenfor.


Trinn 7: Implementering av ActionScript

Her er en enkel implementering av matrise tillegg. Sjekk ut kommentarene:

 offentlig klasse Addition utvider Sprite public function Addition () var m: Matrix = ny Matrix (); // instantiate matrise m.tx = stage.stageWidth * 0,5; // skift i x m.ty = scene.stageHeight * 0,5; // skift i y var d: DottedBox = new DottedBox (); // lage den egendefinerte grafikken (stiplet boks er et Sprite) addChild (d); d.transform.matrix = m; // bruk matrisen til grafikken vår

Trinn 8: Geometrisk Betydning av Matrix Multiplikasjon

Matriksmultiplikasjon er noe mer sofistikert enn matrise tillegg, men Prof Wildberger har elegant brutt det ned til denne enkle tolkningen. Jeg skal ydmykt forsøke å gjenta sin forklaring. For de som ønsker å dykke dypere inn i forståelsen av lineær algebra som fører til dette, sjekk professorens forelesningsserie.

La oss starte med å takle saken av identitetsmatrisen, jeg.

Fra bildet ovenfor vet vi at multipliserer en vilkårlig matrise, A, ved identitetsmatrisen, jeg, vil alltid produsere A. Her er en analogi: 6 x 1 = 6; identitetsmatrisen er lik nummer 1 i den multiplikasjonen.

Alternativt kan vi skrive resultatet i følgende vektorformat som i stor grad forenkler vår tolkning:

Den geometriske tolkningen av denne formelen er vist i bildet nedenfor.

Fra kartesisk rutenettet (venstre rutenett) kan vi se det blå punktet ligger på (2, 1). Nå, hvis vi skulle transformere dette originale rutenettet med x og y til et nytt rutenettet (høyre rutenettet) i henhold til et sett med vektorer (under høyre rutenettet), vil det blåpunktet flyttes til (2, 1) på det nye rutenettet - men når vi kartlegger dette tilbake til det opprinnelige rutenettet, er det samme punkt som før.

Fordi vi forvandler det opprinnelige rutenettet til et annet rutenett som deler de samme vektorene for x og y, ser vi ingen forskjell. Faktisk er endringene av x og y i denne transformasjonen null. Dette er hva det betydde med identitetsmatrise, fra et geometrisk synspunkt.

Men hvis vi prøver å utføre en kartlegging ved hjelp av andre transformasjoner, ser vi noe forskjell. Jeg vet at dette ikke var det mest avslørende eksemplet til å begynne med, så la oss gå videre til et annet eksempel.


Trinn 9: Skalering langs X

Bildet ovenfor viser en skalering av koordinatplassen. Sjekk ut vektoren av x i transformert koordinatrom: en enhet av den transformerte x står for to enheter av den opprinnelige x. På transformert koordinatrom er koordinaten til det blå punktet fortsatt (2, 1). Men hvis du prøver å kartlegge denne koordinaten fra det transformerte rutenettet til det opprinnelige rutenettet, er det (4, 1).

Hele ideen er fanget av bildet ovenfor. Hva med formelen? Resultatet skal være konsekvent; la oss sjekke det ut.

Jeg er sikker på at du husker disse formlene. Nå har jeg lagt til sine respektive betydninger.

Nå for å sjekke ut det numeriske resultatet av vårt skaleringseksempel.

  • Original koordinat: (2, 1)
  • Vektor på transformert x-akse: (2, 0)
  • Vektor på transformert y-akse: (0, 1)
  • Forventet resultat: (2 * 2 + 0 * 1, 0 * 2 + 1 * 1) = (4, 1)

De er enige med hverandre! Nå kan vi gjerne bruke denne ideen til andre transformasjoner. Men før det, en ActionScript-implementering.


Trinn 10: Implementering av ActionScript

Se gjennom ActionScript-implementeringen (og den resulterende SWF-en) nedenfor. Legg merke til at en av de overlappende boksene strekkes langs x med en skala på 2. Jeg har fremhevet de viktige verdiene. Disse verdiene vil bli tweaked i de senere trinnene for å representere forskjellige transformasjoner.

 offentlig klasse Multiplikasjon utvider Sprite offentlig funksjon Multiplikasjon () var ref: DottedBox = new DottedBox (); // lage referansegrafikk addChild (ref); ref.x = scene.stageWidth * 0,5; ref.y = scene.stageHeight * 0,5; var m: Matrise = ny Matrix (); // instantiate matrise m.tx = stage.stageWidth * 0,5; // skift i x m.ty = scene.stageHeight * 0,5; // skift i y m.a = 2; m.c = 0; m.b = 0; m.d = 1; var d: DottedBox = ny DottedBox (); // lage den egendefinerte grafiske addChild (d); d.transform.matrix = m // bruk matrisen på grafikken vår

Trinn 11: Skalering X og Y

Her har vi skalert rutenettet med en faktor på to langs både x- og y-aksene. Blåpunktet er på (2, 1) i det opprinnelige rutenettet før transformasjonen, og (4, 2) i det opprinnelige rutenettet etter transformasjonen. (Selvfølgelig er det fortsatt på (2, 1) i ny nett etter transformasjonen.)

Og for å bekrefte resultatet numerisk ...

... de matcher igjen! For å se dette i ActionScript-implementeringen, bare endre verdien av m.d fra 1 til 2.

(Merk at strekningsretningen fra y er nedover, ikke oppover, fordi y øker nedover i Flash, men oppover i det normale kartesiske koordinatområdet som jeg brukte i diagrammet.)


Trinn 12: Refleksjon

Her har vi reflektert rutenettet langs x-aksen ved hjelp av disse to vektorene, slik at plasseringen av det blå punktet i det opprinnelige ruten endres fra (2, 1) til (-2, 1). Nummerberegningen er som følger:

ActionScript-implementeringen er den samme som før, men i stedet for disse verdiene: m.a = -1, m.b = 0 å representere vektoren for x-transformasjonen, og: m.c = 0 og m. d = 1 å representere vektoren for y-transformasjonen.

Neste, hvordan reflekterer du samtidig på x og y? Sjekk ut bildet nedenfor.

Også numerisk beregnet i bildet nedenfor.

For ActionScript-implementeringen ... vel, jeg er sikker på at du vet hvilke verdier som skal legges inn i matrisen. m.a = -1, m.b = 0 å representere vektoren for x-transformasjonen; m.c = 0 og m. d = -1 å representere vektoren for y-transformasjonen. Jeg har tatt med den endelige SWF nedenfor.


Trinn 13: Skewing og skjæring

Skewing kommer med litt moro. For tilfelle av bildet nedenfor har det transformerte rutenettet fått sin x-akse omdirigert og skalert. Sammenlign de røde pilene i begge rutenettene nedenfor: De er forskjellige, men y-aksen forblir uendret.


forvrenger

Visuelt virker det som forvrengning skjer langs y-retningen. Dette er sant fordi vår transformerte x-akse nå har en y-komponent i sin vektor.

Numerisk er dette hva som skjer ...

Når det gjelder implementering, har jeg oppført tweaksene nedenfor.

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

Jeg er sikker på at du nå vil prøve ut ting selv, så fortsett og tweak

  • orienteringen av transformert y-akse mens du opprettholder x-aksen
  • orienteringen av begge aksene helt

Jeg har tatt med Flash-utgangen for begge tilfeller som nedenfor. For lesere som vil ha litt hjelp med disse verdiene, sjekk ut Multiplication_final.as i kilden nedlasting.


Trinn 14: Rotasjon

Jeg vurderer å rotere en delmengde av skeving. Den eneste forskjellen er at i rotasjon holdes størrelsen på en enhet av både x og y-aksen, som det er vinkelrettigheten mellom de to aksene.

ActionScript gir faktisk en metode i Matrise klasse, rotere(), å gjøre dette. Men la oss gå gjennom dette uansett.

Nå ønsker vi ikke å endre størrelsen på en enhetslengde i x og y fra det opprinnelige rutenettet; bare for å endre orienteringen til hver. Vi kan bruke trigonometri for å komme frem til resultatet som vises i bildet ovenfor. Gitt en rotasjonsvinkel, a, vil vi få det ønskede resultatet ved å bruke vektorer av (cos a, synd a) for x-akse og (-in a, cos a) for y-akse. Størrelsen for hver ny akse vil fortsatt være en enhet, men hver akse vil være i en vinkel på a, sammenlignet med originalene.

For ActionScript implementering, forutsatt at vinkelen, a, er 45 grader (det vil si 0,25 * Pi radianer), just juster matrisen verdiene til følgende:

 var a: Nummer = 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);

Full kilde kan henvises til i Multiplication_final.as.


Trinn 15: Søknad

Å ha en vektorfortolkning av en 2x2-matrise åpner opp plass for oss å utforske. Dens applikasjon i manipulering av bitmaps (BitmapData, LineBitmapStyle, LineGradientStyle, etc.) er utbredt - men jeg tror jeg vil lagre det for en annen opplæring. I tilfelle av denne artikkelen skal vi forsøke å skape vår sprite i kjøretid slik at det ser ut til at det faktisk flipper i 3D.


Visning av en pseudo-3D isometrisk verden

Fra bildet ovenfor kan vi se at i en verden med en isometrisk visning holder grafikken som står "stående" sin y-aksevektor uendret mens x-aksens vektor roterer. Merk at en lengdeenhet for x- og y-aksen ikke endres - med andre ord, ingen skalering skal skje i hver akse, bare rotasjon rundt x-aksen.

Her er et eksempel på denne ideen i Flash. Klikk hvor som helst på scenen og begynn å dra rundt for å se fisken skje. Slett for å stoppe samspillet ditt.

Her er den viktige biten ActionScript. Jeg har markert de avgjørende linjene som håndterer x-aksens rotasjon. Du kan også referere til FakeIso.as.

 privat var f1: fisk, m: matrise; privat var disp: punkt; private var axisX: Punkt, akseJ: Punkt; offentlig funksjon FakeIso () disp = new Point (stadium.stageWidth * 0.5, stage.stageHeight * 0.5); m = ny matrise (); m.tx = disp.x; m.ty = disp.y; // flyttes til midten av scenen f1 = ny fisk (); addChild (f1); f1.transform.matrix = m; // bruk transformasjon til på fisk aksel = nytt punkt (1, 0); // vektor for x-akse axisY = nytt punkt (0, 1); // vektor for y-akse scene.addEventListener (MouseEvent.MOUSE_DOWN, start); // start interaksjon stage.addEventListener (MouseEvent.MOUSE_UP, slutt); // end-samhandling privat funksjonstart (e: MouseEvent): void f1.addEventListener (Event.ENTER_FRAME, oppdatering);  privatfunksjonens slutt (e: MouseEvent): void f1.removeEventListener (Event.ENTER_FRAME, oppdatering);  privat funksjon oppdatering (e: Event): void axisX.setTo (mouseX - f1.x, mouseY - f1.y); // bestem orientering (men størrelsen endret også) axisX.normalize (1); // fikse størrelsen på vektoren med ny retning til 1 enhet apply2Matrix (); // bruk matrise på fisk privat funksjon apply2Matrix (): void m.setTo (axisX.x, axisX.y, axisY.x, axisY.y, disp.x, disp.y); f1.transform.matrix = m; 

Her har jeg brukt Point-klassen til å lagre vektorer.


Trinn 16: Legg til tastaturkontroll

I dette trinnet skal vi forsøke å legge til tastaturkontroller. Fiskens plassering vil oppdatere i henhold til hastigheten, velo. Vi vil definere trinnvise trinn for positiv (med klokken) rotasjon og negativ (mot klokken) rotasjon også.

 velo = nytt punkt (1, 0); // velo vil bli brukt til å definere x-akse axisY = nytt punkt (0, 1); delta_positive = ny matrise (); delta_positive.rotate (Math.PI * 0,01); // positiv rotasjon delta_negative = ny matrise (); delta_negative.rotate (Math.PI * -0.01); // negativ rotasjon

Ved et tastetrykk, velo vil rotere:

 privat funksjon keyUp (e: KeyboardEvent): void if (e.keyCode == Keyboard.LEFT) velo = delta_negative.transformPoint (velo) // roter velo mot urviseren annet hvis (e.keyCode == Tastatur.RIGHT ) velo = delta_positive.transformPoint (velo) // roter velo med klokka

Nå for hver ramme skal vi forsøke å fargelegge forsiden av fisken, og skrå fisken også. Hvis hastigheten, velo, har en størrelsesorden på mer enn 1 og vi bruker den til fiskens matrise, m, vi vil også få en skaleringseffekt - så for å eliminere denne muligheten skal vi normalisere hastigheten og deretter bare bruke den til fiskens matrise.

 privat funksjon oppdatering (e: Begivenhet): void var front_side: Boolean = velo.x> 0 // sjekker forsiden av fisken hvis (front_side) f1.colorBody (0x002233,0.5) // farger forsiden av fisk annet f1.colorBody (0xFFFFFF, 0,5) // hvitt på baksiden av fisken disp = disp.add (velo); // oppdater gjeldende forskyvning med hastighet var velo_norm: Punkt = velo.clone (); // i tilfelle velo> 0, må vi omberegne 1 lengde for x. velo_norm.normalize (1); // merk at x-aksen mer enn 1 skal utføre skalering. Vi vil ikke ha det for nå m.setTo (velo_norm.x, velo_norm.y, axisY.x, axisY.y, disp.x, disp.y); f1.transform.matrix = m; 

Trinn 17: Din fisk

Klikk på scenen, trykk deretter på venstre og høyre piltast for å se få fisken til å endre retning.


Trinn 18: En annen tastaturkontroll

For å krydre opp ting, la oss tillate kontrollen med y-aksen vektoren også.

 privat funksjon keyUp (e: KeyboardEvent): void hvis (e.keyCode == Keyboard.LEFT) velo = delta_negative.transformPoint (velo) annet hvis (e.keyCode == Keyboard.RIGHT) velo = delta_positive.transformPoint (velg) hvis (e.keyCode == Keyboard.UP) axisY = delta_negative.transformPoint (axisY) annet hvis (e.keyCode == Keyboard.DOWN) axisY = delta_positive.transformPoint (axisY)

Også for å bestemme forsiden av fisken, må vi nå innlemme y-aksen. Her er koden for det:

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

Trinn 19: Din No-So-Regular Fish

Vel, for noen kan resultatet av å kontrollere begge akser vise seg å være litt forvirrende, men poenget er at du nå kan skjevre fisken din, oversette den, reflektere den, og til og med rotere den! Prøv kombinasjonene av opp + venstre, opp + høyre, ned + venstre, ned + høyre.

Se også om du kan opprettholde "forsiden" siden av fisken (fisken blir grå). Tips: Trykk opp kontinuerlig, deretter til venstre, deretter ned, og deretter til høyre. Du gjør en rotasjon!

Konklusjon

Jeg håper du finner matrisematikk en verdifull ressurs for prosjektene dine etter å ha lest denne artikkelen. Jeg håper å skrive litt mer på applikasjoner av 2x2 matrise i små raske tips forgrening ut av denne artikkelen, og videre Matrix3d som er viktig for 3D-manipulasjoner. Takk for å lese, terima kasih.