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.
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ä:
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ä:
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
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
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
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.
Kun ajat ohjelman, ohjelman suoritus näyttää tältä:
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: 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