Az ötödik rész előtt lazításképpen írjunk gyorsan egy Mandelbrot-halmaz rajzoló algoritmust. A Mandelbrot halmaz azon pontok halmaza a komplex számsíkon, amelyekre a z(i) = z(i-1)*z(i-1) + c rekurzív függvény a nullához konvergál. Ezeket a pontokat simán feketével jelöljük, viszont a nem a halmazba tartozó környező pontokat aszerint színezzük, hogy milyen gyorsan tartanak a végtelenbe. Magyarul iszonyú dizájnos dióbeleket lehet vele rajzolni, és a végtelenségig tudunk zoomolni benne.
Egy komplex szám valós és képzetes részből áll z = ( r , i ). Összeadásnál külön kell összeadni őket, a szorzásuk meg szimpla vektoriális szorzás: z1 * z2 = ( r1 , i1 )*( r2 , i2 ) = ( r1*r2 – i1*i2 , r1*i2 + r2*i1 ). Ez alapján már gyerekjáték megírni a generátorunkat.
Nagy jóság, hogy az AS3-as virtual machine már igen gyorsan számol, és pörgeti a for ciklusokat, és az AS2-vel ellenétben nem kell időzítenünk a számolást, egy for ciklusból ledarálja az egészet pár másodperc alatt.
Hozzunk létre egy új projectet MandelbrotSet néven, és gépeljünk:
//begin // // package { // // // import flash.display.Sprite; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.StageScaleMode; // // // public class MandelbrotSet extends Sprite { // // // private var mbCNT:Bitmap; private var mbBMP:BitmapData; // private var maxCycles:int = 128; // meddig pörgessünk egy adott pontot // private var LEFT:Number = -2; // a komplex számsík valós minimuma private var RIGHT:Number = 1; // a komplex számsík valós maximuma private var BOTTOM:Number = -1; // a komplex számsík imaginárius minimuma private var TOP:Number = 1; // a komlex számsík imaginárius maximuma // private var WTH:int = 550; // a rajzterünk szélessége private var HTH:int = 380; // a rajztér magassága // private var XSTEPPING:Number = ( RIGHT - LEFT ) / WTH; // a valós rész lépésköze private var YSTEPPING:Number = ( TOP - BOTTOM ) / HTH; // az imaginárius rész lépésköze // //az alábbiakat fölvehetnénk lokális változóknak is, de a nagy számú memóriafoglalás lassítja a futást // private var zr:Number; //a változó valós változónk private var zi:Number; // a változó imaginárius változónk private var cr:Number; // a fix pontunk valós része private var ci:Number; // a fix pontunk imaginárius része private var zrsq:Number; //zr négyzete private var zisq:Number; //zi négyzete
Definiáltuk a konstansokat, és a munkaváltozókat, ahol lehetett, ott Number helyett int-et használunk, ugyanis akkor nem kell a procinak lebegőpontos számításokat végeznie, tehát szárnyal. Kell egy BitmapData is, amit egy Bitmap osztály fog tartalmazni.
// // // public function MandelbrotSet( ) { // stage.scaleMode = StageScaleMode.NO_SCALE; // mbBMP = new BitmapData( WTH , HTH , false , 0x000000 ); mbCNT = new Bitmap( mbBMP ); addChild( mbCNT ); // //forral sokkal gyorsabb, mint do while-al // for ( var a:int = 0 ; a < WTH ; ++a ) for ( var b:int = 0 ; b < HTH ; ++b ) calcPoint( a , b ); // }
A konstruktorban fölrántjuk a Bitmap-ünket, és a rajzterünk összes koordinátájára kiszámoljuk a neki megfelelő komplex síkbeli pontra vonatkoztatott függvény értékének megfelelő színt.
// // // private function calcPoint( xp:int , yp:int ):void { // zr = 0; //a komplex számnunk valós része zi = 0; //a komplex számunk imaginárius része cr = LEFT + XSTEPPING * xp; //az aktuálisan vizsgált pontunk valós része ci = BOTTOM + YSTEPPING * yp; //az aktuálisan vizsgált pontunk imaginárius része // zrsq = 0; zisq = 0; // for ( var a:int = 0 ; a < maxCycles ; ++a ) { // zi = zr * zi * 2 + ci; zr = zrsq - zisq + cr; // zrsq = zr * zr; zisq = zi * zi; // if ( zrsq + zisq > 4 ) // a végtelenbe tart { // var color:Number = a << 16 | ( a + 50 ) << 8 | a ; mbBMP.setPixel( xp , yp , color ); return; // } // } // mbBMP.setPixel( xp , yp , 0x000000 ); // } // // // } // // // } // // //end
Magát a számolást pedig ez a függvény végzi, ha a rekurzívan generált komplex számunk abszolút értéke ( ami tulajdonképpen a valós és imaginárius oldalhosszúságú derékszögű háromszög átlója ) nagyobb 2-nél, akkor elhagyja az origótól számított 2 sugarú kör területét, és a függvény a végtelenbe tart.
Két kérdés merülhet fel: egyrész honnan jön a zi = zr*zi*2 + ci, és az alatta található sor? A megoldás egyszerű: kézbe kell venni a programozó két legjobb barátját, a papírt és ceruzát, és levezetni a z(i) = z(i-1)z(i-1) + c egyenlőséget a komplex és imaginárius együtthatókkal kb így:
z(2) = z(1)*z(1) + c
(zr2,zi2) = (zr1,zi1)(zr1,zi1) + (cr,ci);
(zr2,zi2) = ( zr1*zr1 - zi1*zi1 , zr1*zi1 + zr1*zi1 ) + ( cr , ci );
zr2 = zr1*zr1 - zi1*zi1 + cr;
zi2 = zr1*zi1 + zr1*zi1 + ci = 2*zr1*zi1 + ci;
|zi| > 2 -> a komplex számunk abszolút értékének az origóközpontú 2 sugarú körben kell maradnia
|zi| = sqrt( zr*zr + zi*zi ) > 2;
A másik ami fölmerülhet: mi a csuda ez a sor:
a << 16 | ( a + 50 ) << 8 | a ;
A válasz szintén egyszerű: egy szimpla, maximum 128 nagyságú számból kellett generálnom valahogy egy 24 bites színkódot, amit zöldesnek szerettem volna, tehát fogtam magam, a 128 alatti számot bitszinten odébbtoltam 16-al, akkor ő fölcsúszott a "piros" ( 2(25) - 2 (16) ) tartományba, aztán hozzáadtam 50-et, hogy ő legyen a domináns szín, és fölcsúsztattam a "zöld" tartományba ( 2(15) - 2 (8) ), és meghagytam a "kék" tartományban is( 2(7) - 2(0) ), a három helyiértéket pedig bitszintű logikai "vagy"-gyal kapcsoltam össze.
Ha van 9-es playered, megnézheted a cuccot itt.
Összehasonlításképpen így nézett ki a dolog AS2-ben.
/hmm, basszus, ez gyorsabb mint gondoltam, lehet hogy érdemes lenne kicsit butítani a felbontáson és real-time zoomot nyomatni, klikk ide :) /
Játszogassatok el a kóddal, kifagyasztani a gépet úgy lehet, hogy a maxCycles-t magasra állítod, zoomolni meg úgy tudsz, hogy a LEFT,RIGHT,TOP,BOTTOM konstansokat a kívánt számsíkrészre húzod össze.
Jó matekozást :)
MilGra
A cikkhez kapcsolódó hozzászólásokat a fórum "Actionscript 3" topicjában várjuk.