JavaScript & canvas - Mandelbrotova množina
Mandelbrotova množina je jeden z nejznámějších fraktálů. Jedná se o fraktál ležící v komplexní rovině od [-2;-2] do [2;2]. Vzorec pro jeho výpočet je Z = Z2 + C. Z a C jsou komplexní čísla, kde C je konstantní a jedná se o pozici bodu, ze kterého fraktál počítáme. Postup a podrobnější popis je v článku Mandelbrotova množina, který doporučuji přečíst. Tak a my můžeme začíst programovat.
Vytvoříme si objekt MandelbrotovaMnozina
, do kterého
uložíme pár proměnných.
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; }
Proměnná platno
obsahuje element plátna. V tomto případě
jsem použil jQuery, ale lze to udělat i přes klasický DOM a to
document.getElementById("platno")
. Další proměnná je 2D kontext
na kreslení a pak rozměry plátna. Základní práce s canvasem je
vysvětlená v článku Canvas aneb grafika
JavaScriptem. Zajímavější jsou ale veřejné proměnné. V proměnné
imageData je uložen objekt ImageData, který vrací metoda
kontext.createImageData(width, height); Vytvoříme si tak pole pixelů na
plátně. Nakonec maxiter
je proměnná určující maximální
počet iterací (maximální počet pokusů na vyzkoušení, jestli daný bod
opravdu patří do množiny).
Metoda pro manipulaci s pixely
JavaScript nemá přímo metodu, která by uměla na uživatelsky
přijatelné úrovni manipulovat s pixely na plátně, a proto si ji musíme
vytvořit sami. Nazveme ji třeba navstavPixel
, ještě než ji ale
vysvětlím, pokusím se definovat pixel. Pixel je nějaký bod na plátně. Zde
je tvořen souborem subpixelů, což jsou jednotlivé složky barev a alfa
kanál. Jeden pixel je tak v poli vyjádřen čtyřmi čísli. První je hodnota
červené, druhý hodnota zelené, třetí modré a čtvrtý je právě ten alfa
kanál (průhlednost). Tohle je důležité si uvědomit. Jdeme na to.
// 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 funkce bude bod x, y, hodnota červené, hodnota zelené, hodnota modré a alfa kanál. Index je pozice červeného (prvního) subpixelu. Musíme si ho vypočítat, jelikož máme každý pixel vyjádřený čtyřmi čísly. Pak jen nastavujeme jednotlivé složky daného pixelu, jak se sami můžete přesvědčit.
Metoda pro vykreslení fraktálu
Funkce bude mít tři vstupní parametry. Jednak prostřední x komplexního čísla, prostřední y komplexního čísla a pak přiblížení. Deklarujeme také proměnné potřebné k vykreslení fraktálu.
// 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; }
Když máme deklarované proměnné, můžeme začít vykreslovat. Musíme projít všechny pixely a tak budeme potřebovat dva do sebe vnořené cykly for (jeden pro X a jeden pro Y). V každém kroku vynulujeme naše komplexní číslo Z. Vzorec pro výpočet je pak následující: Reálná část = zr2 - zi2 Imaginární část: zi * zr + zi * zr = 2 * Zr * Zi
Abychom se v tom neztráceli, raději si vytvoříme proměnné druhé mocniny reálné a imaginární části komplexního čísla Z. Uložíme si je do proměnných zr2, zi2. V cyklu budeme toto počítat dokud nebudeme mimo množinu (součet druhé mocniny reálné a imaginární části bude větší než čtyři) nebo přesáhneme maximální počet pokusů (iterací). Pokud budeme mimo množinu, obarvíme si pixel podle počtu pokusů. Jinak obarvíme pixel černě.
// 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. Teď už můžeme fraktál vykreslit a popřípadě přidat možnost přiblížit fraktál po kliknutí na plátno. To by v jQuery mohlo vypadat třeba
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ýsledek bude něco podobného tomuto obrázku:
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 638x (35.93 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript