JavaScriptu & canvas - Mandelbrot množina
Mandelbrotova množina je jeden z najznámejších fraktálov. Jedná sa o fraktál ležiace v komplexnej rovine od [-2; -2] do [2; 2]. Vzorec pre jeho výpočet je Z = Z 2 + C. Z a C sú komplexné čísla, kde C je konštantná a jedná sa o pozíciu bodu, z ktorého fraktál počítame. Postup a podrobnejší opis je v článku Mandelbrot množina, ktorý odporúčam prečítať. Tak a my môžeme začítať programovať.
Vytvoríme si objekt MandelbrotovaMnozina
, do ktorého uložíme
pár premenných. /--code js var MandelbrotovaMnozina = function() {
// Soukromé proměnné
var platno = this.platno = $('#platno').get(0);
var kontext = platno.getContext('2d');
var width = platno.width,
height = platno.height;
// Vytvoříme si objekt imageData, kde budou data o jednotlivých
pixelech
this.imageData = kontext.createImageData(width, height);
this.maxiter = 20; } \--
Premenná platno
obsahuje element plátna. V
tomto prípade som použil jQuery, ale možno to urobiť aj cez klasický DOM a
to document.getElementById("platno")
. Ďalšia premenná je 2D
kontext na kreslenie a potom rozmery plátna. Základné práce s Canvas je
vysvetlená v článku Canvas aneb grafika
JavaScriptom. Zaujímavejšie sú ale verejné premenné. V premennej imaged
je uložený objekt imaged, ktorý vracia metóda kontext.createImageData
(width, height); Vytvoríme si tak pole pixelov na plátne. Nakoniec
maxiter
je premenná určujúca maximálny počet iterácií
(maximálny počet pokusov na vyskúšanie, či daný bod naozaj patrí do
množiny).
Metóda pre manipuláciu s pixelmi
JavaScript nemá priamo metódu, ktorá by vedela na užívateľsky
prijateľnej úrovni manipulovať s pixelmi na plátne, a preto si ju musíme
vytvoriť sami. Nazveme ju treba navstavPixel
, ešte než ju ale
vysvetlím, pokúsim sa definovať pixel. Pixel je nejaký bod na plátne. Tu je
tvorený súborom subpixelov, čo sú jednotlivé zložky farieb a alfa kanál.
Jeden pixel je tak v poli vyjadrený štyrmi číslami. Prvá je hodnota
červenej, druhý hodnota zelenej, tretí modrej a štvrtý je práve ten alfa
kanál (priehľadnosť). Toto je dôležité si uvedomiť. Ideme na to. /--code
js // Funkce nastaví barvu pro daný pixel this.nastavPixel = function(x, y, r,
g, b, a) {
// Zjistíme pozici v poli (pozici červeného subpixelu)
var index = (x + y * this.imageData.width) * 4;
// Hodnota červené bervy
this.imageData.data[index+0] = r;
// Hodnota zelené barvy
this.imageData.data[index+1] = g;
// Hodnota modré barvy
this.imageData.data[index+2] = b;
// Alfa kanál (průhlednost)
this.imageData.data[index+3] = a; }; \--
Vstup funkcia bude bod x, y, hodnota červenej, hodnota zelenej, hodnota modrej a alfa kanál. Index je pozícia červeného (prvého) subpixelu. Musíme si ho vypočítať, pretože máme každý pixel vyjadrený štyrmi číslami. Potom len nastavujeme jednotlivé zložky daného pixelu, ako sa sami môžete presvedčiť.
Metóda pre vykreslenie fraktálu
Funkcia bude mať tri vstupné parametre. Jednak prostrednej x komplexného
čísla, prostredná y komplexného čísla a potom priblíženia. Deklarujeme
tiež premenné potrebné k vykreslenie fraktálu. /--code js // Funkce
vykreslí Mandelbrotovu množinu this.vykresli = function( xCenter, yCenter,
zoom ) {
// Uloží zoom
$(platno).data('zoom', zoom);
// Vytvoří nová image data
this.imageData = kontext.createImageData(width, height);
// Potřebné proměnné
var count, zr, zi, zr2, zi2;
// Přiblížení
var zoom = zoom > 1 ? zoom*3 : zoom;
// Y, od kterého začneme
var minY = yCenter - (1.5/(zoom));
// Y, u kterého skončíme
var maxY = yCenter + (1.5/(zoom));
// X, u kterého začneme
var minX = xCenter - (1.5/(zoom));
// X, u kterého skončíme
var maxX = xCenter + (1.5/(zoom));
// Vypočteme jeden krok cyklu for X
var dx = (maxX - minX) / width;
// Vypočteme jeden krok cyklu for Y
var dy = (maxY - minY) / height;
// Budeme ukládat reálnou pozici pixelu na plátně
var rX = 0, rY = 0; } \--
Keď máme deklarované premenné, môžeme začať vykresľovať. Musíme prejsť všetky pixely a tak budeme potrebovať dva do seba vnorené cykly for (jeden pre X a jeden pre Y). V každom kroku vynulujeme naše komplexné číslo Z. Vzorec pre výpočet je potom nasledovné: Reálna časť = zr 2 - zi 2 Imaginárna časť: zi * zr + zi * zr = 2 * Zr * Zi
Aby sme sa v tom nestrácali, radšej si vytvoríme premenné druhej mocniny reálne a imaginárne časti komplexného čísla Z. Uložíme si ich do premenných ZR2, zi2. V cykle budeme toto počítať kým nebudeme mimo množinu (súčet druhej mocniny reálne a imaginárne časti bude väčší ako štyri) alebo presiahne maximálny počet pokusov (iterácií). Ak budeme mimo množinu, zafarbíme si pixel podľa počtu pokusov. Inak zafarbíme pixel čierno.
// Projdeme všechny pixely na plátně for(var y = minY; y < maxY; y += dy) { // Přičteme reálnou pozici Y rY++; // Jsme v novém řádku => vynulujeme widthCheck rX = 0; // Projdeme X a zároveň se ujistíme, že jsem stále na plátně for (var x = minX; x < maxX && rX < width; x += dx) { // Přičteme reálnou pozici X rX++; // Vynulujeme komplexní číslo Z a jeho druhou množinu zi = 0; zr = 0; zi2 = 0; zr2 = 0; // Nastavíme počet provedených iterací na 0 count = 0; // Obarvíme pixel na černo this.nastavPixel(rX, rY, 0, 0, 0, 255); // Pokud jsme v množině a nevyčerpali jsme možný počet pokusů while ((zr2 + zi2) < 4 && count < this.maxiter) { // Uložíme si druhou mocninu reálné části (alernativa: zr*zr) zr2 = Math.pow(zr, 2); // Uložíme si druhou mocninu imaginární části (alternativa: zi*zi) zi2 = Math.pow(zi, 2); // Vzorec: Z.i = 2 * Z.r * Z.i + posunY zi = 2*zr*zi + y; // Vzorec: Z.z^2 - Z.i^2 + posunX zr = zr2 - zi2 + x; // Přičteme počet provedených pokusů count++; } // Pokud jsme mimo množinu, obarvíme pixel na nějakou barvu podle počtu provedených pokusů if ((zr2 + zi2) > 4) //Obarvíme pixel podle počtu provedených pokusů this.nastavPixel(rX, rY, count * 12, count * 4, count * 3, 255); } } }
A je hotovo. Teraz už môžeme fraktál vykresliť a prípadne pridať
možnosť priblížiť fraktál po kliknutí na plátno. To by v jQuery mohlo
vyzerať napríklad /--code js var manMnozina = new MandelbrotovaMnozina();
manMnozina.vykresli(0, 1.5, 0.5); $(manMnozina.platno).click(function(e)
{
e = $.event.fix(e);
var left = e.pageX-$(this).offset().left,
top = e.pageY-$(this).offset().top,
zoom = $(this).data('zoom')+1;
manMnozina.vykresli(left/$(this).width(), top/$(this).height(), zoom); });
\--
Výsledok bude niečo podobné tomuto obrázku:
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 639x (35.93 kB)
Aplikácia je vrátane zdrojových kódov v jazyku JavaScript