Geïnspireerd door Prof Wildberger in zijn collegereeksen over lineaire algebra, ben ik van plan zijn wiskundige ideeën met Flash te implementeren. We zullen niet ingaan op de wiskundige manipulatie van matrices door lineaire algebra: alleen door vectoren. Dit inzicht, hoewel het de elegantie van lineaire algebra verdunt, is genoeg om ons in een aantal interessante mogelijkheden van 2x2 matrixmanipulatie te brengen. We zullen het in het bijzonder gebruiken om tijdens runtime verschillende afschuif-, scheef-, flipping- en schalingseffecten toe te passen op afbeeldingen.
Laten we eens kijken naar het uiteindelijke resultaat waar we naartoe zullen werken. Druk op de vier richtingstoetsen - omhoog, omlaag, links, rechts - om enkele effecten te zien die we kunnen bereiken met affiene transformaties.
Als je alleen de linker en rechter pijltjestoetsen gebruikt, lijkt de vis rond te zwemmen in een pseudo-3D isometrische ruimte.
Afbeeldingen worden getekend op coördinatenruimten. Dus om ze te manipuleren, vooral om afbeeldingen te vertalen, roteren, schalen, weer te geven en scheef te stellen, is het essentieel dat we coördinaatruimten begrijpen. Over het algemeen gebruiken we niet slechts één, maar meerdere coördinaatruimten in een enkel project - dit geldt niet alleen voor ontwerpers die de Flash IDE gebruiken, maar ook voor programmeurs die ActionScript schrijven.
In Flash IDE gebeurt dit telkens wanneer u uw tekeningen converteert naar MovieClip-symbolen: elk symbool heeft zijn eigen oorsprong.
De afbeelding hierboven toont de oorsprong van de coördinatenruimte van de stage (rode stip) en die van de coördinatenruimte van het symbool (registratiepunt gemarkeerd met een draadkruis). Als u wilt weten in welke ruimte u zich momenteel bevindt, bekijkt u de balk onder de tijdlijn van Flash IDE, zoals wordt weergegeven in de onderstaande afbeelding.
(Ik gebruik Flash CS3, dus de locatie kan verschillen voor CS4 en CS5.) Wat ik wil benadrukken, is het bestaan van verschillende coördinatenruimten en het feit dat u al bekend bent met het gebruik ervan.
Nu is daar een goede reden voor. We kunnen een coördinatenruimte gebruiken als referentie om de andere coördinaatruimte te wijzigen. Dit klinkt misschien alien, dus ik heb de onderstaande Flash-presentatie toegevoegd om mijn uitleg te vergemakkelijken. Klik en sleep de rode pijlen. Speel ermee.
Op de achtergrond is een blauw raster en op de voorgrond een rood raster. De blauwe en rode pijlen worden initieel uitgelijnd langs de x- en y-as van de Flash-coördinaatruimte, waarvan het middelpunt I naar het midden van het werkvlak is verschoven. Het blauwe raster is een referentieraster; de rasterlijnen veranderen niet terwijl je de rode pijlen gebruikt. Het rode raster kan echter worden georiënteerd en geschaald door de rode pijlen te slepen.
Merk op dat de pijlen ook een belangrijke eigenschap van deze rasters aangeven. Ze geven het begrip van een eenheid van x en een eenheid van y op hun respectievelijke raster aan. Er zijn twee rode pijlen op het rode raster. Elk van deze geeft de lengte van één eenheid op de x-as en de y-as aan. Ze dicteren ook de oriëntatie van de coördinatenruimte. Laten we de rode pijl langs de x-as houden en uitbreiden tot twee keer zo lang als de originele pijl (blauw weergegeven). Bekijk de volgende afbeeldingen.
We zien dat het beeld (het groene kader) dat op het rode raster is getekend nu horizontaal wordt uitgerekt, omdat dit rode raster waarop het wordt getrokken nu twee keer zo breed is. Het punt dat ik probeer te maken is vrij eenvoudig: je kunt één coördinatenruimte gebruiken als basis om een andere coördinaatruimte te veranderen.
Dus wat is een "affine coordinate space"? Wel, ik ben er zeker van dat je voorzichtig genoeg bent om te zien dat deze coördinaatruimten getekend worden met parallelle roosters. Laten we bijvoorbeeld de rode affiene-ruimte nemen: er is geen garantie dat zowel de x-as als de y-as altijd loodrecht op elkaar staan, maar u kunt er zeker van zijn dat hoe u de pijlen ook probeert aan te passen, u nooit in een dergelijk geval terechtkomt zoals hieronder.
In feite verwijzen x- en y-assen meestal naar de cartesiaanse coördinatenruimte, zoals hieronder weergegeven.
Merk op dat de horizontale en verticale roosters loodrecht op elkaar staan. Cartesiaanse is een type van affine coördinatenruimte, maar we kunnen het transformeren naar andere affiene ruimten zoals we dat willen. De horizontale en verticale roosters hoeven niet noodzakelijk loodrecht op elkaar te staan.
Zoals je misschien al geraden had, zijn de affiene transformaties vertaling, schaalvergroting, reflectie, rotatie en rotatie.
Onnodig te zeggen, fysieke eigenschappen zoals x, y, scaleX, scaleY
en omwenteling
afhankelijk van de ruimte. Wanneer we naar die eigenschappen bellen, transformeren we eigenlijk affiene coördinaten.
Ik hoop dat de afbeeldingen hierboven duidelijk genoeg zijn om het idee naar huis te brengen. Dit komt omdat voor een programmeur die met FlashDevelop werkt, we niet de rasters zullen zien die de Flash IDE geschikt maakt voor ontwerpers. Al deze moeten in je hoofd leven.
Naast het voorstellen van deze rasters, moeten we ook de hulp inroepen van Matrix
klasse. Het is daarom belangrijk om een wiskundig begrip van matrices te hebben, dus we zullen hier de bewerkingen van de matrix herzien: optellen en vermenigvuldigen.
Matrixbewerkingen convergeren betekenissen geometrisch. Met andere woorden, je kunt je voorstellen wat ze in een grafiek betekenen. Laten we aannemen dat we vier punten in onze coördinatenruimte hebben en ze willen verplaatsen naar een reeks nieuwe locaties. Dit kan worden gedaan met behulp van matrixtoevoeging. Bekijk de afbeelding hieronder.
Zoals je ziet, verplaatsen we eigenlijk de hele lokale coördinatenruimte (rode rasters) waar deze vier punten worden getekend. De notatie voor het uitvoeren van deze bewerkingen is zoals hieronder weergegeven:
We kunnen ook zien dat deze verschuiving daadwerkelijk kan worden weergegeven met een vector van (tx, ty). Laten we vectoren en statische punten in coördinaatruimten onderscheiden door ons gebruik van haakjes en vierkante haken. Ik heb ze in de onderstaande afbeelding herschreven.
Hier is een eenvoudige implementatie van matrixtoevoeging. Bekijk de reacties:
public class Addition breidt uit public function Addition () var m: Matrix = nieuwe 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 (); // maak de aangepaste afbeelding (gestippeld vak is een Sprite) addChild (d); d.transform.matrix = m; // pas de matrix toe op onze afbeelding
Matrixvermenigvuldiging is iets geavanceerder dan matrixtoevoeging, maar Prof Wildberger heeft het op elegante wijze onderverdeeld in deze eenvoudige interpretatie. Ik zal nederig proberen zijn uitleg te herhalen. Voor degenen die graag dieper willen ingaan op het begrip van lineaire algebra dat hiertoe leidt, bekijk de lezingenserie van de professor.
Laten we beginnen met het aanpakken van de casus van de identiteitsmatrix, I.
Uit de afbeelding hierboven weten we dat vermenigvuldiging van een willekeurige matrix, A, door de identiteitsmatrix, I, altijd A zal produceren. Hier is een analogie: 6 x 1 = 6; de identiteitsmatrix wordt vergeleken met het getal 1 in die vermenigvuldiging.
Als alternatief kunnen we het resultaat in het volgende vectorformaat schrijven, wat onze interpretatie aanzienlijk zal vereenvoudigen:
De geometrische interpretatie van deze formule wordt getoond in de onderstaande afbeelding.
Vanuit het Cartesiaanse raster (linker raster) kunnen we zien dat het blauwe punt zich bevindt op (2, 1). Als we nu dit oorspronkelijke raster van x en y naar een nieuw raster (rechter raster) volgens een reeks vectoren (onder het juiste raster) zouden transformeren, wordt het blauwe punt verplaatst naar (2, 1) in het nieuwe raster. - maar wanneer we dit terugbrengen naar het oorspronkelijke raster, is het hetzelfde punt als voorheen.
Omdat we het originele raster transformeren naar een ander raster met dezelfde vectoren voor x en y, zien we geen verschil. In feite zijn de veranderingen van x en y in deze transformatie nul. Dit is wat het betekende identiteitsmatrix, vanuit een geometrisch oogpunt.
Als we echter proberen een toewijzing uit te voeren met andere transformaties, zullen we enig verschil zien. Ik weet dat dit niet het meest onthullende voorbeeld was om mee te beginnen, dus laten we naar een ander voorbeeld gaan.
Afbeelding hierboven toont een schaal van de coördinaatruimte. Bekijk de vector van x in de getransformeerde coördinaatruimte: één eenheid van de getransformeerde x is verantwoordelijk voor twee eenheden van de originele x. Op de getransformeerde coördinaatruimte is de coördinaat van het blauwe punt nog steeds (2, 1). Als u echter probeert deze coördinaat van het getransformeerde raster naar het oorspronkelijke raster te mappen, is het (4, 1).
Dit hele idee wordt vastgelegd door de afbeelding hierboven. Hoe zit het met de formule? Het resultaat moet consistent zijn; laten we het bekijken.
Ik weet zeker dat je je deze formules herinnert. Nu heb ik hun respectieve betekenissen toegevoegd.
Om nu het numerieke resultaat van ons schalingsvoorbeeld te bekijken.
Ze zijn het met elkaar eens! Nu kunnen we dit idee graag toepassen op andere transformaties. Maar daarvoor een ActionScript-implementatie.
Bekijk de ActionScript-implementatie (en de resulterende SWF) hieronder. Merk op dat een van de overlappende vakken langs x wordt uitgerekt met een schaal van 2. Ik heb de belangrijke waarden gemarkeerd. Deze waarden worden in de latere stappen aangepast om verschillende transformaties weer te geven.
Public Class Multiplication breidt Sprite-functie vermenigvuldiging () var-ref: DottedBox = nieuwe DottedBox (); uit; // maak referentie grafische addChild (ref); ref.x = stage.stageWidth * 0.5; ref.y = stage.stageHeight * 0.5; var m: Matrix = nieuwe matrix (); // instantiate matrix m.tx = stage.stageWidth * 0.5; // shift in x m.ty = stage.stageHeight * 0.5; // verschuiving in y m.a = 2; m.c = 0; m.b = 0; m.d = 1; var d: DottedBox = new DottedBox (); // maak de aangepaste grafische addChild (d); d.transform.matrix = m // pas de matrix toe op onze afbeelding
Hier hebben we het raster geschaald met een factor twee langs zowel de x- als de y-as. Het blauwe punt bevindt zich op (2, 1) in het oorspronkelijke raster vóór de transformatie en (4, 2) in het oorspronkelijke raster na de transformatie. (Het staat natuurlijk nog steeds op (2, 1) in de nieuwe raster na de transformatie.)
En om het resultaat numeriek te bevestigen ...
... ze komen opnieuw overeen! Als u dit in de ActionScript-implementatie wilt zien, hoeft u alleen de waarde van te wijzigen m.d
van 1 tot 2.
(Merk op dat de richting van rek vanaf y naar beneden is, niet naar boven, omdat y in Flash naar beneden toe omhoog gaat in de normale cartesiaanse coördinaatruimte die ik in het diagram heb gebruikt.)
Hier hebben we het raster langs de x-as gereflecteerd met behulp van deze twee vectoren, dus de positie van het blauwe punt in het oorspronkelijke raster verandert van (2, 1) in (-2, 1). De numerieke berekening is als volgt:
De ActionScript-implementatie is hetzelfde als hiervoor, maar in plaats hiervan worden deze waarden gebruikt: m.a = -1, m.b = 0
om de vector voor de x-transformatie te vertegenwoordigen, en: m.c = 0 en m. d = 1
om de vector voor de y-transformatie te vertegenwoordigen.
Vervolgens, hoe zit het met simultaan reflecteren op x en y? Bekijk de afbeelding hieronder.
Ook numeriek berekend in onderstaande afbeelding.
Voor de ActionScript-implementatie ... nou, ik weet zeker dat je de waarden kent die in de matrix moeten worden gezet. m.a = -1, m.b = 0
om de vector voor de x-transformatie weer te geven; m.c = 0 en m. d = -1
om de vector voor de y-transformatie te vertegenwoordigen. Ik heb de laatste SWF hieronder opgenomen.
Skewing komt met een beetje plezier. Voor het geval van de afbeelding hieronder is het getransformeerde raster zijn x-as opnieuw georiënteerd en geschaald. Vergelijk de rode pijlen in beide onderstaande rasters: ze zijn verschillend, maar de y-as blijft ongewijzigd.
Visueel lijkt het erop dat vervorming langs de y-richting plaatsvindt. Dit is waar omdat onze getransformeerde x-as nu een y-component in zijn vector heeft.
Numeriek is dit wat er gebeurt ...
In termen van implementatie heb ik de tweaks hieronder opgesomd.
m.a = 2
m.b = 1
m.c = 0
m.d = 1
Ik ben er zeker van dat je op dit moment dingen zelf wilt uitproberen, dus ga je gang en doe je best
Ik heb de Flash-uitvoer voor beide gevallen meegeleverd, zoals hieronder. Voor lezers die hulp nodig hebben bij deze waarden, ga je naar Multiplication_final.as
in de brondownload.
Ik beschouw rotatie als een subset van scheeftrekken. Het enige verschil is dat bij rotatie de grootte van een eenheid van zowel de x- als de y-as wordt gehandhaafd, evenals de loodrechtheid tussen de twee assen..
ActionScript biedt eigenlijk een methode in de Matrix
klasse, draaien()
, om dit te doen. Maar laten we dit toch doornemen.
Nu willen we de grootte van een eenheidslengte in x en y van het oorspronkelijke raster niet wijzigen; gewoon om de oriëntatie van elk te veranderen. We kunnen gebruik maken van trigonometrie om te komen tot het resultaat dat wordt weergegeven in de bovenstaande afbeelding. Gegeven een hoek van roatie, a, krijgen we het gewenste resultaat door vectoren van (cos a, sin a) voor x-as en (-sin a cos a) voor y-as te gebruiken. De grootte voor elke nieuwe as is nog steeds één eenheid, maar elke as staat onder een hoek ten opzichte van de originelen.
Voor Actionscript-implementatie, ervan uitgaande dat de hoek, a, 45 graden is (dat wil zeggen 0,25 * Pi-radialen), hoeft u alleen de matrixwaarden aan te passen aan het volgende:
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);
Er kan naar de volledige bron worden verwezen Multiplication_final.as
.
Het hebben van een vectorinterpretatie van een 2x2 matrix opent ruimte voor ons om te verkennen. De toepassing ervan bij het manipuleren van bitmaps (BitmapData, LineBitmapStyle, LineGradientStyle
, etc.) is wijdverspreid - maar ik denk dat ik dat zal bewaren voor een andere tutorial. In het geval van dit artikel zullen we proberen onze sprite tijdens runtime scheef te trekken, zodat het lijkt alsof het in 3D wordt omgezet..
Uit de bovenstaande afbeelding kunnen we zien dat in een wereld met een isometrische weergave elke afbeelding die "rechtop staat" zijn y-asvector onveranderd houdt terwijl de x-asvector roteert. Merk op dat een lengte-eenheid voor de x- en y-as niet verandert - met andere woorden, geen schaalvergroting moet in beide assen plaatsvinden, alleen rotatie rond de x-as.
Hier is een voorbeeld van dit idee in Flash. Klik ergens op het podium en begin te slepen om de vis scheef te zien. Laat los om uw interactie te stoppen.
Dit is het belangrijke deel van Actionscript. Ik heb de cruciale lijnen gemarkeerd die de rotatie van de x-as behandelen. U kunt ook verwijzen naar FakeIso.as
.
privé var f1: Vis, m: Matrix; private var disp: Point; private var axisX: Point, axisY: Point; publieke functie FakeIso () disp = new Point (stage.stageWidth * 0.5, stage.stageHeight * 0.5); m = nieuwe matrix (); m.tx = disp.x; m.ty = disp.y; // verplaatsen naar het midden van fase f1 = nieuwe Fish (); addChild (f1); f1.transform.matrix = m; // transformatie toepassen op visasX = nieuw punt (1, 0); // vector voor x - as asY = nieuw punt (0, 1); // vector voor y - axis stage.addEventListener (MouseEvent.MOUSE_DOWN, start); // start interactie stage.addEventListener (MouseEvent.MOUSE_UP, einde); // einde interactie start privéfunctie (e: MouseEvent): void f1.addEventListener (Event.ENTER_FRAME, update); private function end (e: MouseEvent): void f1.removeEventListener (Event.ENTER_FRAME, update); update van persoonlijke functie (e: Event): void axisX.setTo (mouseX - f1.x, mouseY - f1.y); // bepaal de oriëntatie (maar ook de magnitude is gewijzigd) axisX.normalize (1); // fix magnitude of vector met nieuwe oriëntatie op 1 eenheid apply2Matrix (); // pas matrix toe op vissen private function apply2Matrix (): void m.setTo (axisX.x, axisX.y, axisY.x, axisY.y, disp.x, disp.y); f1.transform.matrix = m;
Hier heb ik de Point-klasse gebruikt voor het opslaan van vectoren.
In deze stap proberen we toetsenbordbesturingselementen toe te voegen. De locatie van de vis wordt bijgewerkt op basis van de snelheid, velo
. We zullen ook incrementele stappen definiëren voor positieve rotatie (met de klok mee) en negatieve (tegen de klok in) rotatie.
velo = nieuw punt (1, 0); // velo wordt gebruikt om x-as asY = nieuw punt (0, 1) te definiëren; delta_positive = nieuwe matrix (); delta_positive.rotate (Math.PI * 0,01); // positieve rotatie delta-negatief = nieuwe matrix (); delta_negative.rotate (Math.PI * -0.01); // negatieve rotatie
Bij een toetsdruk, velo
zal roteren:
private functie keyUp (e: KeyboardEvent): void if (e.keyCode == Keyboard.LEFT) velo = delta_negative.transformPoint (velo) // draai velo tegen de klok in else if (e.keyCode == Keyboard.RIGHT ) velo = delta_positive.transformPoint (velo) // draai velo met de klok mee
Nu proberen we voor elk frame de voorkant van de vis te kleuren en de vis ook scheef te draaien. Als de snelheid, velo
, heeft een magnitude van meer dan 1 en we passen het toe op de matrix van de vis, m
, we krijgen ook een scaling-effect - dus om deze mogelijkheid te elimineren, zullen we de snelheid normaliseren en dan alleen toepassen op de matrix van de vis.
update van persoonlijke functie (e: Event): void var front_side: Boolean = velo.x> 0 // controleren op de voorkant van vis if (front_side) f1.colorBody (0x002233,0.5) // kleur de voorzijde van vissen anders f1.colorBody (0xFFFFFF, 0.5) // wit toegepast op de achterkant van de vis disp = disp.add (velo); // update huidige verplaatsing met velocity var velo norm: Point = velo.clone (); // in het geval velo> 0, moeten we 1 eenheid van lengte herberekenen voor x. velo_norm.normalize (1); // merk op dat de x-as meer dan 1 schaalt. We willen dat voorlopig niet m.setTo (velo_norm.x, velo_norm.y, axisY.x, axisY.y, disp.x, disp.y); f1.transform.matrix = m;
Klik op het podium en druk vervolgens op de linker en rechter pijltjestoetsen om te zien hoe de vis van richting verandert.
Om de zaken wat op te fleuren, laten we ook de controle over de y-asvector toestaan.
persoonlijke functietoetsUp (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)
Om ook de voorkant van de vis te bepalen, moeten we nu de y-as opnemen. Hier is de code voor:
var front_side: Boolean = velo.x * axisY.y> 0 if (front_side) f1.colorBody (0x002233,0.5) else f1.colorBody (0xFFFFFF, 0.5)
Nou, voor sommigen kan het resultaat van het beheersen van beide assen een beetje verwarrend blijken te zijn, maar het punt is dat je nu je vis kunt scheeftrekken, vertalen, weerspiegelen en zelfs roteren! Probeer de combo's van omhoog + links, omhoog + rechts, omlaag + links, omlaag + rechts.
Controleer ook of u de "voorkant" -kant van de vis kunt behouden (vissen worden grijs weergegeven). Hint: Tik continu omhoog, dan links, dan omlaag, dan rechts. Je maakt een rotatie!
Ik hoop dat je matrix math een waardevolle aanwinst voor je projecten vindt na het lezen van dit artikel. Ik hoop een beetje meer te schrijven over toepassingen van 2x2 matrix in kleine Quick Tips die uit dit artikel en verder gaan Matrix3D
wat essentieel is voor 3D-manipulaties. Bedankt voor het lezen, terima kasih.