Toistorakenteet

Viivan piirtäminen hankalasti

Kappaleen otsikko ei ala kovin motivoivasti, mutta noise-metodin avulla tapa käy järkeen. Jos maltat tämän kappaleen loppuun olet jo pitkällä generatiivisten tekniikoiden osalta.

Jos haluaisit piirtää suoran viivan Processingilla mieleesi tulee varmastikin suoraviivaisin ratkaisu: line-metodi.

line(0, height/2, width, height/2);

Entä jos haluaisit piirtää viivan yksi piste kerrallaan? Viivan piirtämiseen tästä ei ole hyötyä, mutta piste kerrallaan piirtäminen mahdollistaa pisteiden manipuloinnin esimerkiksi noise-metodin avulla. Tarmokasta näppäimistötyöskentelyä vaativa brute force -tekniikka näyttäisi ehkä tältä:

point(0, height/2);
point(1, height/2);
point(2, height/2);
point(3, height/2);
point(4, height/2);
...

Näin hankalaan tapaan ei onneksi tarvitse sortua. Apuun tulevat johdannossa mainitut toistorakenteet! Ajatuksena on toistaa koodinpätkää niin pitkään, kun jokin ehto täyttyy. Ehdot ilmaistaan samaan tapaan, kuin ehtolauseiden (if ja else) parissa. Tarkastele seuraavaa esimerkkiä:

int x = 0; //luo kokonaislukutyyppinen muuttuja x ja aseta arvoksi 0
while (x < width) { //toista koodilohkoa jos x on pienempi kuin width
  point(x, height/2); //piirrä piste
  x = x + 1; //kasvata muuttujan x arvoa yhdellä
}

Loistavaa, nyt olet taas lähtöpisteessä! Mutta mikä tässä tavassa oli vaivan arvoista? Ensiksi, pääsit tutustumaan while-looppiin, joka tulee osoittautumaan korvaamattomaksi työkaluksi ohjelmoinnissa. Toiseksi, nyt voit helposti lähteä manipuloimaan yksittäisiä pisteitä viivassa. Otetaan esimerkiksi matematiikasta tuttu sini-funktio. sin(x) palauttaa x-muuttujasta riippuen aina arvon välillä -1, 1. Kokeillaan muokata viiva-ohjelmassa piirrettävän pisteen y-koordinaattia sini-funktion arvoilla:

int x = 0; // luo kokonaislukutyyppinen muuttuja x ja aseta arvoksi 0
float sin_seed = 0;
while (x < width) { // toista koodilohkoa jos x on pienempi kuin width
  float y = sin(sin_seed) * 10; // muuttuja y saa arvoja välillä -10 - 10
  point(x, height/2 + y); // piirä piste
  x = x + 1; // kasvata muuttujan x arvoa yhdellä
  sin_seed = sin_seed + 0.1;
}

Toistorakenteet

Tässä kappaleessa jatketaan toistorakenteiden parissa ja esitellään Processingin noise-metodi. Käytit äskeisesssä kappaleessa while-looppia viivan ja sinikäyrän piirtämiseen. Tässä kappaleessa piirrät while-loopin ja noise-metodin avulla aaltoilevan käyrän.

Seuraavassa esimerkissä piirretään jokaisella draw-metodin kutsulla while-loopin avulla käyrä ikkunan poikki. Seuraavalla draw-metodin kutsulla piirretään käyrä uudestaan hieman eri muotoisena. Draw-metodia toistettaessa syntyy vaikutelma aaltoilevasta pinnasta.


While-looppi

While-looppi muistuttaa rakenteeltaan hyvin paljon if-lausetta. While-komennon jälkeen määritellään suluissa ehto, jonka on oltava tosi, jotta sitä seuraavan lohkon sisällä oleva koodi suoritetaan. Erona if-lauseeseen on se, että while-lauseen lohkon sisältöä suoritetaan niin kauan kunnes ehto ei enää päde. Siinä missä if-lauseen lohko suoritetaan kerran, while-lohko voidaan suorittaa vaikka kuinka monta kertaa - jopa ikuisesti!

Huom!
void draw(){

// while-looppia suoritetaan niin kauan, kuin ehto on tosi:
// koska 0 on aina 0, jatkuu suoritus tässä tapauksessa ikuisesti
  while (0 == 0){    
    rect(50, 100, 50, 50);
  }

  ellipse(200, 200, 30, 30);   // tätä komentoa ei koskaan päästä suorittamaan

}

Ylläoleva koodi on varoittava esimerkki siitä, mitä tapahtuu, kun while-loopin ehto on aina tosi. Kun yritämme ajaa ylläolevan ohjelman, IDE:n kääntäjä varoittaa meille, että ellipse-metodi on "unreachable code" eli ohjelma sisältää koodia, jota ei missään tilanteessa päästä suorittamaan. While-looppi siis jatkuu ikuisesti.

Noise-aalto

Aloitetaan aaltoanimaation kirjoittaminen piirtämällä jälleen while-loopin ja point-metodin avulla suora jana ikkunan poikki.

void setup(){
  size(500, 500);
}

void draw(){
  int x = 0;   // luodaan  kokonaislukumuuttuja x, asetetaan alkuarvoksi 0

  // While-looppia suoritetaan niin kauan kun ehto on tosi.
  // Tässä tapauksessa niin kauan kun x on pienempi kuin width eli 500
  while (x < width){                    
    point(x, 250);                   // piirretään piste kohtaan (x,250)
    x++;                             // kasvatetaan x:n arvoa yhdellä
                                     // x++ tarkoittaa samaa kuin x = x + 1
  }  

  // Kun while-looppi on käyty läpi, on x == 500.
  // Draw-metodin alussa muuttuja luodaan uudestaan ja asetetaan nollaan.

}

Olisi ollut helpompaa kirjoittaa ainoastaan line(0, 250, 500, 250), mutta työ on vaivan arvoista kun lisätään mukaan noise-metodi. Käytä seuraavaksi muuttujaa x noise-metodin parametrina ja käytä saatua arvona pisteen y-koordinaattina. Muuttuja x kasvaa jokaisella kierroksella yhdellä. Jos muuttujan arvo jaetaan 100:lla, saadaan noise-metodille sopiva 0.01 muutos jokaisella kierroksella.

Muista, että noise palauttaa aina arvon välillä 0-1. Jos haluat käyttää noisen antamaa arvoa esimerkiksi y-koordinaattina, tulee välillä 0-1 oleva arvo skaalata sopivalle välille. Skaalaus onnistuu mukavasti Processingin map-metodilla:

map(syöte, alkuperäinen minimi, alkuperäinen maksimi, uusi minimi, uusi maksimi)

Käytännössä map-metodin käyttö voisi näyttää tältä: (muuntaa välillä 0-1 olevan x:n arvon välille 200-400):

map(x, 0, 1, 200, 400)

void setup(){
  size(500, 500);
}

void draw(){
  float x = 0;   // luodaan  liukulukumuuttuja x, asetetaan alkuarvoksi 0

  // While-looppia suoritetaan niin kauan kun ehto on tosi.
  // Tässä tapauksessa niin kauan kun x on pienempi kuin width eli 500
  while (x < width){ 
    float y = map(noise(x/100), 0, 1, 150, 300); 
    // x/100 kasvaa 0.01 jokaisella kierroksella, mikä sopii hyvin noise-metodin parametriksi
    // skaalataan map-metodilla välillä 0-1 oleva arvo välille 150-300
                
    point(x, y);                   // piirretään piste kohtaan (x,y)
    x++;                           // kasvatetaan x:n arvoa yhdellä
                                   // x++ tarkoittaa samaa kuin x = x + 1
  }  
}

Huomaat, että ohjelma generoi erilaisen käyrän jokaisella kerralla kun ohjelma ajetaan. Käyrä pysyy kuitenkin paikoillaan. Kun ohjelma on käynnissä, käyrä piirretään yhä uudestaan ja uudestaan draw-metodissa. Noise-metodia kutsutaan aina samoilla arvoilla, joten käyrä piirtyy samanlaisena. Tehdään seuraavaksi viimeinen vaihe ja lisätään noise-metodin parametreihin hieman variaatiota jokaisella draw-metodin kutsulla.

Jokaisella draw-metodin kutsulla x saa arvot välillä 0-500. Kun x jaetaan sadalla, noise-metodia kutsutaan samoilla arvoilla välillä 0-5, 0.01 välein. Jos jokaisella draw-metodin kutsulla halutaan piirtää hieman erilainen kuvaaja, tulee noise-metodia kutsua myös eri arvoilla. Tämä onnistuu kätevästi antamalla noise-metodille toinen parametri x:n lisäksi! Eli:

noise(ensimmäinen_parametri, toinen_parametri), tai: noise(x,y)

Jos toista parametria kasvatetaan hieman draw-metodin lopussa, saadaan aikaiseksi hieman erilainen kuvaaja jokaisella draw-metodin kierrokselta ja lopputulos näyttää aaltoilevalta:

float y = 0;   // luodaan  liukulukumuuttuja y, asetetaan alkuarvoksi 0

void setup(){
  size(500, 500);
}

void draw(){
  background(255);
  float x = 0;   // luodaan  liukulukumuuttuja x, asetetaan alkuarvoksi 0

  // While-looppia suoritetaan niin kauan kun ehto on tosi.
  // Tässä tapauksessa niin kauan kun x on pienempi kuin width eli 500
  while (x < width){ 
    float n = noise(x/100, y); 
    // kutsutaan noise-metodia kahdella parametrilla. Ensimmäinen parametri saa arvoja väillä 0-5, 0.01 välein.
    // Toinen parametri pysyy samana, kunnes sitä draw-metodin lopussa kasvatetaan hieman.
  
    float y = map(n, 0, 1, 150, 300); // skaalataan map-metodilla välillä 0-1 oleva arvo välille 150-300
                
    point(x, y);                   // piirretään piste kohtaan (x,y)
    x++;                           // kasvatetaan x:n arvoa yhdellä
  }  

  y += 0.02; // Kasvatetaan y:n arvoa hieman
}

Loistavaa! Voisit kokeilla seuraavaksi esimerkiksi taustan häivyttämistä draw-metodin alussa background-metodin sijaan ja hakea inspiraatiota loppuprojektia varten.

Seuraavassa kappaleessa käsitellään for-looppia, joka toiminnaltaan on while-looppia vastaava, mutta suoraviivaistaa rakenteen syntaksia. Tee seuraavaksi alla olevat tehtävät while-loopin toiminnasta ja siirry seuraavaan lukuun.


int i = 0;
while (i<10){
  print("Tämä tulostuu kymmenen kertaa");
}
Miksi alla oleva koodi ei tulosta kymmentä kertaa tekstiä?

For-looppi

For-looppi on while-looppia näppärämpi tapa toistaa asioita. Se koostuu täsmälleen samoista palasista: laskurimuuttujan luomisesta, ehdosta ja laskurin kasvatuksesta. Vain syntaksi on eri. Alla on esimerkki while- ja for-loopeista, jotka molemmat tekevät saman asian:

int i = 0;
while (i < 10){    // while-looppi suoritetaan kymmenen kertaa
  rect(10*i, 10*i, 10*i, 10*i);
  i++;
}
for (int i = 0; i < 10; i++){    // for-looppi suoritetaan kymmenen kertaa
  rect(10*i, 10*i, 10*i, 10*i);
}

Sisäkkäiset for-loopit

Koska edellisessä kappaleessa tehtiin onnistuneesti aaltoileva animaatio while-loopin ja noisen avulla, jatketaan tässä kappaleessa tutulla linjalla – mutta tehdään samalla vaivalla ainakin parikymmentä aaltoilevaa kuvaajaa!

Aloitetaan piirtämällä paikallaan pysyvä noise-käyrä for loopilla:

void setup(){
  size(500, 500);
}

void draw(){
  // For loop käy läpi arvot välillä 0-500)
  for (float x = 0; x < width; x++){ 
    float y = map(noise(x/100), 0, 1, 0, 100); // skaalataan map-metodilla välillä 0-1 oleva arvo välille 0-100
    point(x, y);                                 // piirretään piste kohtaan (x,y)
  }  
}

Tämä on jo tuttua edellisen kappaleen perusteella. Ainoa ero on for-rakenne while-rakenteen sijaan.

Nyt ohjelma piirtää yhden käyrän. Jos haluaisit piirtää vaikkapa 20 käyrää, voisit kopioida for loopin 20 kertaa peräkkäin. Pidemmän päälle saman koodin kopiointi käy kuitenkin vaivalloiseksi ja arvojen muuttaminen jokaisesta kahdestakymmenestä toistorakenteesta olisi työlästä ja virhealtista. Onneksi voit luoda toisen toistorakenteen edellisen ympärille! Katso seuraavaa esimerkkiä:

void setup(){
  size(500, 500);
}

void draw(){
  // row käy läpi arvot 0, 20, 40, 60, ..., 460, 480
  for (float row = 0; row < width; row += 20){ 
    // For loop käy läpi arvot välillä 0-500. Sisempi for-loop käydään läpi 25 kertaa
    for (float x = 0; x < width; x++){ 
      float y = map(noise(x/100), 0, 1, 0, 100); // skaalataan map-metodilla välillä 0-1 oleva arvo välille 0-100
      point(x, row + y);                         // piirretään piste kohtaan (x,y)
    }  
  }
}

Sisempi for-loop toistetaan nyt 25 kertaa, eli ruudulle piirretään yhteensä 25 kuvaajaa. Kaikki kuvaajat ovat ikävästi samanlaisia, joten käytetään row-muuttujaa noise-metodin toisena parametrina:

void setup(){
  size(500, 500);
}

void draw(){
  // row käy läpi arvot 0, 20, 40, 60, ..., 460, 480
  for (float row = 0; row < width; row += 20){ 
    // For loop käy läpi arvot välillä 0-500. Sisempi for-loop käydään läpi 25 kertaa
    for (float x = 0; x < width; x++){ 
      float y = map(noise(x/100, row/100), 0, 1, 0, 100); // skaalataan map-metodilla välillä 0-1 oleva arvo välille 0-100
      point(x, row + y);                         // piirretään piste kohtaan (x,y)
    }  
  }
}

Hienoa, mutta kuva pysyy edelleen paikallaan. Lisätään noise-muuttujalle vielä kolmas parametri ja käytetään samaa kikkaa kuin edellisessä kappaleessa animaation tuottamiseksi. Luodaan muuttuja z, käytetään z-muuttujaa noise-metodin kolmantena parametrina ja kasvatetaan muuttujan arvoa hieman draw-metodin lopussa.

float z = 0;

void setup(){
  size(500, 500);
}

void draw(){
  background(255);
  // row käy läpi arvot 0, 20, 40, 60, ..., 460, 480
  for (float row = 0; row < width; row += 20){ 
    // For loop käy läpi arvot välillä 0-500. Sisempi for-loop käydään läpi 25 kertaa
    for (float x = 0; x < width; x++){ 
      float y = map(noise(x/100, row/100, z), 0, 1, 0, 100); // skaalataan map-metodilla välillä 0-1 oleva arvo välille 0-100
      point(x, row + y);                         // piirretään piste kohtaan (x,y)
    }  
  }
  z += 0.02;
}

Loistavaa! Voit kokeilla erilaisia värimaailmoja sekä eri arvoja noise-metodille.


for (int i=6; i>3; i--){
  print(" i ="+i+", ");
} 
Mitä ohjelma tulostaa ruudulle?