NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!
NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.

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.create­ImageData(wid­th, 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:

Mandelbrotova množina - JavaScript zdrojákoviště - Základní konstrukce jazyka

 

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

 

Všechny články v sekci
JavaScript zdrojákoviště - Základní konstrukce jazyka
Článek pro vás napsal Drahomír Hanák
Avatar
Uživatelské hodnocení:
7 hlasů
Autor v současné době studuje Informatiku. Zajímá se o programování, matematiku a grafiku.
Aktivity