Ohjelmointi ja taide, osa 2: Rekursio

Ohjelmoinnin, matematiikan ja taiteen yhdistäminen mahdollistaa projektit, joissa hyödynnetään tekniikkaa luovalla tavalla. Julkaisemme aiheesta kolmiosaisen juttusarjan. Sarjan toisessa artikkelissa tutustumme P5js-ohjelmointikielen aliohjelmiin ja rekursioon taiteen tekemisessä.

Ensimmäinen osa Geometriset kuviot (Dimensio 7.7.2022)

Kolmas osa Taidetta laskukaavoilla (Dimensio 4.8.2022)

Aliohjelma eli funktio

P5js-ohjelmointikielessä on kaksi perusfunktiota: setup- ja draw-funktiot. Setup-funktio suoritetaan kerran ohjelman suorituksen aikana, ja siksi setup-funktioon laitetaan sellaiset komennot, joita tarvitaan suorittaa vain kerran. Yksi tällainen komento on esimerkiksi ikkunan eli koordinaatiston koko, joka asetetaan komennolla createCanvas(leveys,korkeus); ja se aina laitetaan setup-funktioon. Draw-funktio on ikuisessa silmukassa, jota voisimme nimittää myös pääohjelmaksi. Jätämme tällä kertaa draw-funktion kokonaan pois ja teemme ohjelman, jossa setup-funktio kutsuu toista funktiota eli aliohjelmaa. Muita funktioita eli aliohjelmia suoritetaan ainoastaan silloin kun sitä kutsutaan joko setup- tai draw-funktiosta.

Tehdään seuraava ohjelma:

function setup () {
     createCanvas(600,600);     // Ikkunan koko
     background(128);           // Harmaa taustaväri
     fill(255,0,0);             // Punainen täyttöväri
     suorakulmio(100,50,300,200);  // Kutsutaan aliohjelmaa
     suorakulmio(200,300,250,150); // Kutsutaan aliohjelmaa
}

function suorakulmio(x, y, leveys, korkeus) { // Aliohjelma alkaa
     rect(x,y,leveys,korkeus);  // Piirrä suorakulmio
}                               // Aliohjelma loppuu

Loimme tässä oman funktion eli aliohjelman nimeltään suorakulmio. Aliohjelma alkaa sanalla function, jonka jälkeen annetaan ainutkertainen aliohjelman nimi, joka tässä on siis suorakulmio. Aliohjelmaan voimme välittää tietoa parametrien avulla. Tämä tieto tallennetaan muuttujiin aliohjelmassa ja nämä aliohjelman muuttujat ovat x, y, leveys ja korkeus. Aliohjelman sisällä voit käyttää grafiikkakomentoja aivan samalla tavalla kuin pääohjelmassakin, ja siksi aliohjelmaan kaarisulkujen sisälle on kirjoitettu komento rect, joka piirtää suorakulmion.

Suorakulmio piirretään ainoastaan silloin, kun aliohjelmaa kutsutaan joko setup- tai draw-funktioista. Tässä esimerkissä aliohjelmaa suorakulmio kutsutaan setup-lohkossa kaksi kertaa, joten ohjelma piirtää kaksi suorakulmiota. Aliohjelman kutsussa sulkujen sisällä on lukuarvot, jotka määräävät suorakulmion nurkkapisteen (x,y), leveyden ja korkeuden. Kun ajat ohjelman, niin lopputulos näyttää seuraavanlaiselta.

Kaksi punaista suorakulmiota harmaalla pohjalla.
Kuva 1: Kuvassa on esitetty suorakulmio-aliohjelman suoritus, kun sitä kutsutaan kaksi kertaa setup-funktiosta. Aliohjelman idea mahdollistaa omien suomenkielisten grafiikkakomentojen tekemisen.

Kehitellään ideaa eteenpäin ja laitetaan aliohjelmakutsu for-silmukan sisälle: tällöin aliohjelmaa kutsutaan niin monta kertaa kuin for-silmukassa on toistoja. Muutamme ohjelman muotoon:

function setup () {
     createCanvas(600,600); // Ikkunan koko
     background(128);       // Harmaa taustaväri
     for (var a = 0; a < 1000; a++) {  // 1000 toistoa
        fill(random(255), random(255), random(255), random(255));
        // Asetetaan satunnainen väri         
        suorakulmio(random(width), random(height),50,50);
        // Kutsutaan aliohjelmaa
     }                                // for silmukka loppuu
}

function suorakulmio(x, y, leveys, korkeus) {
     rect(x,y,leveys,korkeus);
}

Lähde: https://editor.p5js.org/riekkinen/sketches/y7G9n52vP

Komento random(255) arpoo satunnaisen desimaaliluvun väliltä 0…255. Kun tämä komento laitetaan fill(R,G,B,A) komennon sisälle värisävyn (R=red, G=green, B=blue) ja läpinäkyvyyden A=alpha-arvo paikalle, saamme asetettua satunnaisen värisävyn.

Komento random(width) arpoo satunnaisesti desimaaliluvun väliltä 0…600, koska ikkunan leveys on 600 px. Vastaavasti komento random(height) arpoo satunnaisesti desimaaliluvun väliltä 0…600, koska ikkunan korkeus on 600 px. Ikkunan korkeus ja leveys on asetettu createCanvas(leveys, korkeus); komennossa. Kun komennot fill(R,G,B,A) ja aliohjelman kutsu ovat for-silmukan sisällä, niin ohjelman suoritus näyttää tältä:

Eri värisiä neliöitä osittain päällekäin.
Kuva 2: Kun ajat ohjelman, ohjelma piirtää satunnaisella värillä 1000 kappaletta 50×50 kokoisia neliöitä satunnaiseen paikkaan.

Rekursio

Rekursio tarkoittaa, että aliohjelma kutsuu itseään. Tehdään aluksi aliohjelma, joka piirtää satunnaisella värillä ympyrän. Tässä ympyrän keskipiste on (300,300) ja säde 200.

function setup() {
     createCanvas(600, 600);   // Ikkunan koko 600 x 600
     background(128);          // Taustaväri harmaa
     ympyra(300, 300, 200);    // Kutsutaan aliohjelmaa 
}

function ympyra(x, y, r) {     // Aliohjelma alkaa
     fill(random(255),random(255),random(255));
                               // Asetetaan satunnainen väri
     ellipse(x, y, 2*r, 2*r);  // Piirretään ympyrä
}                              // Aliohjelma päättyy

Kun ajat ohjelma, tulos voi näyttää esimerkiksi tältä:

Sininen ympyrä harmaalla taustalla.
Kuva 3: Piirretään satunnaisella värillä ympyrä, jonka keskipiste on x = 300, y = 300 ja säde r = 200.

Nyt kun kirjoitamme aliohjelman sisälle aliohjelman nimen uudestaan, aliohjelma kutsuu itseään. Tämä johtaisi ikuiseen silmukkaan ja ohjelman hyytymiseen, siksi aliohjelmakutsun ympärille pitää laittaa if-lause, joka lopettaa kutsumisen, kun haluttu ehto täyttyy. Kirjoitamme rekursion muotoon: ympyra(x, y, 0.8*r); Tämä tarkoittaa, että jokaisen uuden ympyrän keskipiste on kokoajan sama, mutta säde on 20 % pienempi eli 0,8-kertainen kuin edellisen ympyrän säde. Toistoa jatketaan niin kauan, kun säde on suurempi kuin 2 px. Kokonainen ohjelma näyttää nyt tältä:

function setup() {
     createCanvas(600, 600); // Ikkunan koko
     background(128);        // Taustaväri harmaa
     ympyra(300, 300, 300);  // Kutsutaan aliohjelmaa
}

function ympyra(x, y, r) {    // Aliohjelma alkaa
     fill(random(255),random(255),random(255));
     // Aseta satunnainen väri
     ellipse(x, y, 2*r, 2*r); 
     // Piirrä ympyrä, joka keskipiste on (x,y) ja halkaisija 2r
     if (r > 2) {              // Jos r on suurempi kuin 2, niin 
          ympyra(x, y, 0.8*r); // aliohjelma kutsuu itseään
     }                         // Lopeta if-lause
}                              // Aliohjelma loppuu

Lähde: https://editor.p5js.org/riekkinen/sketches/Ao888chL-

Koska setup-lohkossa tehdään aliohjelmakutsu muodossa ympyra(300, 300, 300); ensimmäinen ympyrä piirretään satunnaisella värillä paikkaan x = 300, y = 300 ja sen säde on 300. Seuraavat ympyrät piirretään aliohjelmakutsujen avulla siten, että jokainen uusi ympyrä 20 % pienempi kuin edellinen. Aliohjelma kutsuu itseään niin kauan kun säde on suurempi kuin 2. Jos säde on 2 tai pienempi, niin aliohjelmakutsuja ei enää tehdä. Kun suoritat ohjelman, niin lopputulos voisi näyttää esimerkiksi tältä.

Sisäkkäisiä, erivärisiä ympyröitä.
Kuva 4: Rekursio eli aliohjelma kutsuu itseään, kunnes säde on pienempi kuin 2.

Fraktaalipuu

Sovelletaan rekursioideaa käytäntöön eli piirretään fraktaalipuu. Fraktaalissa toistetaan tiettyä kuviota periaatteessa loputtomiin eli kuviota voisi myös skaalata loputtomiin. Fraktaali on jatkuva, mutta ei derivoituva funktio. Käytännössä ohjelmaan kuitenkin tehdään lopetusehto, jotta ohjelman suoritus ei hyydy.  Tehdään ohjelma, joka piirtää oksan, jossa on kaksi haaraa.

function setup() {
  createCanvas(600, 600);// Ikkunan koko 600 x 600
  angleMode(DEGREES);    // Ottaa käyttöön kulma-asteet 360°
  background(0);         // Taustaväri musta
  stroke(255);           // Reunaviivan väri valkoinen
  strokeWeight(2);       // Reunaviivan paksuus 2
  translate(300, 550);   // Siirrä origo 300 px oikealle ja 550
                         // px alas lukien ikkunan vasemmasta ylänurkasta
  oksa(180,60,0.65);     // Kutsutaan aliohjelmaa
}

function oksa( pituus, kulma, kerroin) {
   line(0,0,0, -pituus);  // Piirretään suora viiva ylöspäin
   translate(0, -pituus); // Siirretään origo viivan loppuun
   if (pituus > 2)   {    // jos viiva on suurempi kuin 2
       push();            // Ota koordinaatisto muistiin
       rotate(kulma);     // Käännä kulman verran oikealle
       oksa(pituus*kerroin, kulma, kerroin);
       // Kutsutaan aliohjelmaa
       pop();             // Palautetaan koordinaatisto muistista
       push();            // Otetaan koordinaatisto muistiin
       rotate(-kulma);    // Käännä kulman verran vasemmalle
       oksa(pituus*kerroin, kulma, kerroin);
       // Kutsutaan aliohjelmaa
       pop();             // Palautetaan koordinaatisto muistista
    }                     // Lopetetaan if-lause
}                         // Lopetetaan aliohjelma

Lähde: https://editor.p5js.org/riekkinen/sketches/cpT6jsnIh

Tässä ohjelmassa on kaksi aliohjelmakutsua aliohjelman sisällä. Tämä siksi, että ensimmäisellä kutsulla piirretään oikeanpuoleista oksan haaraa ja toisella kutsulla vasemmanpuoleista. Aliohjelma piirtää oikeastaan vain yhden janan komennolla: line(0, 0, 0, -pituus); Tässä janan alkupiste on (0,0) ja loppupiste on (0, -pituus). Muistamme, että x-koordinaatit kasvavat oikealle ja y-koordinaatit alaspäin, niin tässä viiva piirretään ylöspäin, koska viivan loppupiste on negatiivinen. Jos suoritat aliohjelman ilman if -lausetta, niin aliohjelma piirtää vain yhden janan.

Komennolla translate(0, -pituus) siirrämme janan piirtämisen jälkeen origon paikan aina janan loppupisteeseen. Kun aliohjelmassa kutsutaan aliohjelmaa uudestaan komennolla oksa(pituus*kerroin, kulma, kerroin); niin kutsu on laitettava push(); pop(); rakenteen sisälle, jotta origo saadaan palautettua alkuperäiseen paikkaan toista kutsua varten. Komento push() ottaa sen hetkisen origon paikan muistiin ja komento pop() palauttaa muistissa olleen koordinaatiston takaisin. Tämä rakenne tarvitaan siksi, että rotate(kulma) -komennossa pyöritämme koko koordinaatistoa joko oikealle tai vasemmalle ja siksi koordinaatisto pitää aina välillä palauttaa takaisin alkuperäiseen arvoonsa.

Pääohjelmassa aliohjelmaa kutsutaan komennolla: oksa(180, 60, 0.65). Tämä tarkoittaa sitä, että ensimmäisen oksan pituus on 180 px. Jokainen seuraava oksa piirretään 60 asteen kulmaan, joko oikealle tai vasemmalle. Jokainen uusi oksa on pituudeltaan 0,65-kertainen eli on pienentynyt 35 %. Jos tämä kerroin on lähellä ykköstä, niin ohjelma hyytyy helposti, koska toistoja tulee silloin liikaa. Katsotaan ohjelman suoritus kaksilla eri arvoilla:

Kaksi valkoista fraktaalipuuta mustalla taustalla.
Kuva 5: Kuvassa on esitetty ohjelman suoritus eri arvoilla, kun aliohjelmaa oksa kutsutaan setup-funktiossa.

Sierpinski

Vuonna 1915 puolalainen matemaatikko Wacław Sierpiński konstruoi kolmion, jossa jokaisen kolmion sisälle muodostettiin kolme uutta kolmiota. Näin muodostettua fraktaalia nimitetään Sierpińskin kolmioksi. Käytännössä tämä tarkoittaa, että aliohjelma kutsuu itseään rekursiivisesti kolme kertaa. Tehdään tästä kokonainen ohjelma.

function setup() {
  createCanvas(800, 800);  // Ikkunan koko 800 x 800
  background(0);           // Taustaväri musta
  noStroke();              // Ei reunaviivaa
  sierpinski(0, 700, 400, 0, 800, 700, 6); 
                           // Kutsutaan aliohjelmaa
}

function sierpinski(x1, y1, x2, y2, x3, y3, n) {
  if ( n > 0 ) {  // Jos n on suurempi kuin nolla
    fill(random(255),random(255),random(255));
    // Aseta satunnainen väri
    triangle(x1, y1, x2, y2, x3, y3); // Piirrä kolmio, jonka
    // nurkkapisteet ovat (x1, y1), (x2, y2) ja (x3, y3)
    var h1 = (x1+x2)/2.0;
    var w1 = (y1+y2)/2.0;
    var h2 = (x2+x3)/2.0;
    var w2 = (y2+y3)/2.0;
    var h3 = (x3+x1)/2.0;
    var w3 = (y3+y1)/2.0;
    // Kutsutaan aliohjelmaa kolme kertaa:
    sierpinski(x1, y1, h1, w1, h3, w3, n-1);
    sierpinski(h1, w1, x2, y2, h2, w2, n-1);
    sierpinski(h3, w3, h2, w2, x3, y3, n-1);
  }
}

Lähde: https://editor.p5js.org/riekkinen/sketches/QfqD8vuJP

Setup-funktiossa aliohjelmaa kutsutaan komennolla sierpinski(0, 700, 400, 0, 800, 700, 6). Tässä kolmion nurkkapisteet ovat (0, 700), (400, 0) ja (800, 700). Viimeinen luku kertoo toistojen määrän, eli toistamme aliohjelmaa 6 kertaa. Aliohjelmassa ohjelma kutsuu itseään kolme kertaa ja täten piirtää kolme uutta kolmiota. Sitä varten aliohjelman pitää laskea kolmion sivun pituuden puolittajan arvo eli käytännössä lasketaan janan keskipisteen paikka ennen kuin aliohjelmia aletaan kutsua. Alla oleva kuva havainnollistaa asiaa.

Havainnekuva kolmiosta.
Kuva 6. Kuva havainnollistaa ohjelmassa käytettyjen muuttujien merkityksen. Esimerkiksi piste (h1, w1) on janan (x1, y1) ja (x2, y2) keskipiste. Näiden muuttujien avulla tehdään rekursiokutsut.

Kun ajat ohjelman, ohjelman suoritus näyttää tältä:

Värikäs Sierpienskin kolmio mustalla pohjalla.
Kuva 7: Sierpińskin kolmio, kun toistoja on kuusi kappaletta.

Lähteet ja lisälinkit

Ohjelmointiympäristö: https://editor.p5js.org/

MAOL ry:n julkaisemia ohjeita: https://maol.fi/materiaalit/taidetta-ohjelmoimalla/

Englanninkielisiä ohjeita: https://p5js.org/

Sierpińskin kolmio: https://fi.wikipedia.org/wiki/Sierpi%C5%84skin_kolmio

Lue myös Värit, Sierpinski ja Pascal (Dimensio-lehti, 10.12.2020)

sekä: Integraalipäiväjutun ohjelmointihaaste (Dimensio-lehti, 7.9.2021)


Tilaa Dimension uutiskirje – saat sähköpostiisi aina kuunvaihteessa koosteen tuoreimmista artikkeleista

Kirjoittaja