Atramentová krivka

Nadviažeme voľne na tutoriál Lomená čiara a vyrobíme si nekonečnú farebnú krivku. Ale namiesto ostrých zlomov bude mať plynulý priebeh. A doplníme jednoduchý efekt na rozmazávanie obrazovky:

Základný program

Začneme so štandardným skečom so setup() a draw(), nič špeciálne.

Lomenú čiaru predtým sme vyrábali so segmentou na seba nadviazaných. Každý segment bola jedna krátka úsečka. Tu budeme na seba nadväzovať krátke krivky.

Príkazy beginShape() a endShape()

Aby sme mohli kresliť krivku, už nám nebude stačiť obyčajný príkaz line() ale naučíme sa univerzálnejší postup, ktorý vie nakresliť aj tvary z viacerými vrcholmi alebo zaoblené krivky.

Vyskúšajte tento skeč:

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

void draw()
{
  beginShape();
    vertex(100,100);
    vertex(300,200);
    vertex(50,350);
  endShape();  
}

Vykreslí to trojuholník. Skúste za tretí príkaz vertex() pridať ďalší príkaz vertex() s nejakými svojimi súradnicami. Vykreslí sa štvoruholník. Skúste pridať ďalší vertex. Vykreslí sa päťuholník.

Takto vieme, okrem iného, vykresliť ľubovoľný n-uholník. Každý jeho vrchol má svoj príkaz vertex().

Pozrite si aj dokumentáciu ku kresleniu tvarov cez príkaz beginShape().

Bézierova krivka

Cez príkazy beginShape() a endShape() vieme kresliť aj zaoblenú krivku, takzvanú Bézierovu. Tá pozostáva zo 4 bodov. V prvom bode začína, druhý a tretí bod určujú jej zaoblenie, vo štvrtom bode končí:

Vyrobíme si v skeči súradnice pre 4 body abcd a len tak na skúšku priraďme im nejaké súradnice:

float ax,ay, bx,by, cx,cy, dx,dy;

void setup()
{
  size(400,400); 
  ax = 50;
  ay = 200;
  bx = 100;
  by = 100;
  cx = 250;
  cy = 50;
  dx = 350;
  dy = 300;
}

Takto potom vykreslíme medzi týmito 4 bodmi krivku:

void draw()
{
  beginShape();
    vertex(ax,ay);
    bezierVertex(bx, by, cx, cy, dx, dy);
  endShape(); 
}

Prvý vertex() určuje, odkiaľ krivka začína (bod a). A súradnice ďalších 3 bodov sú potom v nasledujúcom príkaze bezierVertex(). Program sa dá čitať tak, že začni v bode ax,ay a potom urob krivku cez body bx, by, cx, cy až do bodu dx, dy.

Náhodné súradnice

Ak by sme dali krivke úplne náhodné súradnice, tak bude dosť veľká divoká. Vyskúšajte v setup() namiesto konkrétnych čísel priradiť x-ovým súradniciam random(width) a y-ovým súradniciam random(height).

My chceme, aby naša veľká krivka bola vyskladaná s malých segmentov. Takže upravíme súradnice tak, aby bod B bol v blízkom okolí bodu A, bod C v blízkom okolí bodu B a bod D v blízkom okolí bodu C. V akom blízkom? Na to si vyrobíme premennú v (ako vzdialenosť):

float ax,ay, bx,by, cx,cy, dx,dy;
float v = 30;

Generovanie náhodnej pozície v okolí inej pozície sme už robili pri Lomenej čiare, tu je to podobné. Začneme v strede obrazovky a ďalšie súradnice generujeme v okolí tých predošlých:

void setup()
{
  size(400,400); 
  
  ax = width/2;
  ay = height/2;
  
  bx = random(ax - v, ax + v);
  by = random(ay - v, ay + v);
  
  cx = random(bx - v, bx + v);
  cy = random(by - v, by + v);
  
  dx = random(cx - v, cx + v);
  dy = random(cy - v, cy + v);
}

Nová krivka je už krátka a dobre sa nám tak budú skladať segmenty:

Krivka nakreslená cez beginShape() a endShape() obsahuje aj výplň. Tú my nepotrebujeme, tak v setup() vypnite výplň príkazom noFill().

Nadväzovanie

Ak budeme chcieť kresliť ďalšie a ďalšie segmenty, tak budeme potrebovať, aby nový segment začínal tam, kde skončil predošlý. Takže po vykreslení si ax,ay nastavíme na dx,dy a ostatné súradnice vygenerujeme znova náhodne:

void draw()
{
  beginShape();
    vertex(ax,ay);
    bezierVertex(bx, by, cx, cy, dx, dy);
  endShape(); 
  
  ax = dx;
  ay = dy;
  
  bx = random(ax - v, ax + v);
  by = random(ay - v, ay + v);
  cx = random(bx - v, bx + v);
  cy = random(by - v, by + v);
  dx = random(cx - v, cx + v);
  dy = random(cy - v, cy + v);    
}

Hladké nadväzovanie

Jednotlivé segmenty na seba náhodne nadväzujú, ale sú dosť kostrbaté. To preto, že na seba nenadväzujú hladko. Nový začiatok A je síce umiestnený tam, kde bol starý konec C, ale inak je krivka inak zaoblená. Takto to vyzerá zväčšené. Šípka označuje nadpojenie nového segmentu na starý.

Aby bolo napojenie hladké, musíme prispôsobiť nie len nový začiatok A starému koncu D, ale aj nové zaoblenie B starému koncu. Konkrétne takto:

void draw()
{
  beginShape();
    vertex(ax,ay);
    bezierVertex(bx, by, cx, cy, dx, dy);
  endShape(); 
  
  ax = dx;
  ay = dy;
  
  bx = ax + dx - cx;
  by = ay + dy - cy;
  
  cx = random(bx - v, bx + v);
  cy = random(by - v, by + v);
  dx = random(cx - v, cx + v);
  dy = random(cy - v, cy + v);   
}

Prečo vzorec na bx a by vyzerá práve takto, je teraz nad rámec vysvetľovania. Nie je to zložité, ale bude na to čas v budúcom semestri.

Nová krivka je už o poznanie hladšia:

Kozmetická úprava kódu:

Ak si všimnete, tak náhodné pozície C a D generujeme dvakrát úplne rovnako. Raz na konci v setup() a druhý raz na konci v draw(). Je to zbytočné a postačí, keď ich budeme generovať iba raz, a to na začiatku draw():

float ax,ay, bx,by, cx,cy, dx,dy;
float v = 30;

void setup()
{
  size(400,400); 
  noFill();
  
  ax = width/2;
  ay = height/2;
  
  bx = random(ax - v, ax + v);
  by = random(ay - v, ay + v);
}

void draw()
{    
  cx = random(bx - v, bx + v);
  cy = random(by - v, by + v);
  
  dx = random(cx - v, cx + v);
  dy = random(cy - v, cy + v);
  
  beginShape();
    vertex(ax,ay);
    bezierVertex(bx, by, cx, cy, dx, dy);
  endShape(); 
  
  ax = dx;
  ay = dy;
  
  bx = ax + dx - cx;
  by = ay + dy - cy;  
}

Zaloopovanie krivky

Keď nám vylezie krivka von z okna, chceme ju vrátiť naspäť na opačnom okraji obrazovky. Pri lomenej čiare, stačilo prehodiť na opačný koniec obrazovky jeden bod. Tu máme ale krivku, tak budeme prehadzovať na opačný koniec dva body: A aj B.

Vyrobíme si na to podprogram zaloopuj(), aby sme nemali už moc komplikovaný draw():

void zaloopuj()
{
  if (ax > width)
  {
    ax = ax - width;
    bx = bx - width;
  }  
  if (ax < 0)
  { 
    ax = ax + width;
    bx = bx + width;
  }
  if (ay > height)
  {
    ay = ay - height;
    by = by - height;
  }
  if (ay < 0)
  {
    ay = ay + height;
    by = by + height;
  }  
}

A podprogram potom zavoláme na konci draw():

void draw()
{    
  cx = random(bx - v, bx + v);
  cy = random(by - v, by + v);
  
  dx = random(cx - v, cx + v);
  dy = random(cy - v, cy + v);
    
  beginShape();
    vertex(ax,ay);
    bezierVertex(bx, by, cx, cy, dx, dy);
  endShape(); 
    
  ax = dx;
  ay = dy;
  
  bx = ax + dx - cx;
  by = ay + dy - cy;  
    
  zaloopuj();
}

Farba

Geometriu máme vyriešenú, poďme dekorovať. Nastavme farebný režim na HSB a pozadie na čierne:

void setup()
{
  size(400,400); 
  noFill();
  colorMode(HSB);
  background(0);
  
  ax = width/2;
  ay = height/2;
  
  bx = random(ax - v, ax + v);
  by = random(ay - v, ay + v);
}

A v draw() budeme pred každým vykreslením postupne posúvať farebný tón. V minulosti by sme si vyrobili časovú premennú t a zvyšovali ju na konci draw() o nejakú hodnotu. Ale ukážeme si iný spôsob.

Processing nám v systémovej premennej frameCount hovorí, koľký krát sa práve vykresľuje draw(). Koľký snímok v poradí sa práve vykresľuje. Preto frameCount. Na začiatku je v tejto premennej uložená hodnota 1 a po každom draw() sa automaticky zvyšuje o 1.

Použijeme ju teda na nastavenie farby pred vykreslením segmentu krivky:

  stroke(frameCount, 255, 255);  
  beginShape();
    vertex(ax,ay);
    bezierVertex(bx, by, cx, cy, dx, dy);
  endShape(); 

Akurát, že po istom čase je už krivka len červená, lebo nám frameCount vyliezol nad hodnotu 255. Tak ho po dosiahnutí 256 zrazíme naspäť na nulu pomocou %:

  stroke(frameCount % 256, 255, 255);  

Hrúbka čiary

Zaujímavý efekt ako pri písaní štetcom docielime, ak budeme náhodne meniť hrúbku čiary pre každý segment:

  stroke(frameCount % 256, 255, 255); 
  strokeWeight(random(2,10));
  beginShape();
    vertex(ax,ay);
    bezierVertex(bx, by, cx, cy, dx, dy);
  endShape();

Obrazový efekt cez image()

Na záver ešte dorobíme vizuálny efekt, kedy sa vykreslený obraz postupne zväčšuje a tým aj rozostruje. Toto vyrobíme tak, že na konci každého draw() si uložíme do premennej celý obsah obrazovky a na začiatku ďalšieho draw() ho vykreslíme trochu zväčšený a trochu posunutý.

Vyrobíme si na to premennú špeciálneho typu PImage, do ktorej sa dá uložiť obrázok.

float ax,ay, bx,by, cx,cy, dx,dy;
float v = 30;
PImage img;

Do premenných typu int alebo float sa ukladajú čísla. Do premennej typu PImage sa ukladá obrázok.

Na konci draw() si do premennej img uložíme obsah obrazovky jednoduchým príkazom get():

void draw()
{    
  cx = random(bx - v, bx + v);
  cy = random(by - v, by + v);
  
  dx = random(cx - v, cx + v);
  dy = random(cy - v, cy + v);

  stroke(frameCount % 256, 255, 255); 
  strokeWeight(random(2,10));
  beginShape();
    vertex(ax,ay);
    bezierVertex(bx, by, cx, cy, dx, dy);
  endShape(); 
    
  ax = dx;
  ay = dy;
  
  bx = ax + dx - cx;
  by = ay + dy - cy;  
    
  zaloopuj();
  
  img = get();
}

Na začiatku draw() vykreslíme obrázok uložený v premennej img príkazom image(). Akurát, v úplne prvom draw() ešte nie je v img nič uložené, tak počítač by sa hneval. Aby sa to nedialo, tak si to ošetríme podmienkou, že img sa vykresľuje iba, ak už som v neskoršom frame ako v prvom. Využijeme na to frameCount, ktorý si pamätá, v ktorom frame teraz sme.

void draw()
{    
  if (frameCount > 1)
  {
    image(img, 0, 0);
  }
  
  cx = random(bx - v, bx + v);
  cy = random(by - v, by + v);
  
  dx = random(cx - v, cx + v);
  dy = random(cy - v, cy + v);

  stroke(frameCount % 256, 255, 255); 
  strokeWeight(random(2,10));
  beginShape();
    vertex(ax,ay);
    bezierVertex(bx, by, cx, cy, dx, dy);
  endShape(); 
    
  ax = dx;
  ay = dy;
  
  bx = ax + dx - cx;
  by = ay + dy - cy;  
    
  zaloopuj();
  
  img = get();
}

Príkaz image() má niekoľko parametrov. Prvý je obrázok, ktorý sa má vykresliť. Druhý a tretí parameter sú súradnice, odkiaľ sa má obrázok začať vykresľovať. Dali sme tam zatiaľ 0,0 a preto sa nič nedeje. Obrázok na konci zosnímame z obrazovky a na začiatku ho vykreslíme do ľavého horného rohu obrazovky. Presne na to isté miesto, odkiaľ sme ho zobrali. Preto sa nič viditeľné nedeje.

Skúste upraviť volanie príkazu image() takto:

    image(img, 1, 1);

Obrázok bude ubiehať doprava dole:

Ak chceme, aby sa postupne zväčšoval, tak urobíme dve veci:

  • Začneme ho vykresľovať nie od 1,1 ale od -1,-1, takže bude začínať trochu mimo obrazovky vľavo hore

  • A vykreslíme ho väčší o 2 pixely než je obrazovka, takže bude končiť trochu mimo obrazovky vpravo dole.

Na nastavenie veľkosti vykresľovaného obrázka slúži tretí a štvrtý parameter v image():

    image(img, -1, -1, width+2, height+2);

Toto je už efekt zväčšovania a s tým aj spojené rozostrenie, lebo obrázok je pixelový a pri jeho zväčšovaní dochádza k strate kvality:

Hotový skeč

Tu si môžete stiahnuť finálny skeč:

Last updated