Skip navigation

Monthly Archives: Fevereiro 2009

Anúncios

a sessão de hoje anda em torno de 4 tópicos: revisão dos sistemas de partículas; leitura, scrubbing e gravação de filmes; introdução ao 3d e introdução ao fft.

1. sistemas de partículas

revimos os sistemas de partículas através de 3 novos sketchs. 

o primeiro sketch desenha apenas se o rato está pressionado. em vez de termos as funções mouseDragged() e mousePressed(), temos agora na função draw() principal um gate if (mousePressed) {…} que subsistui essas duas funções. as partículas decaiem e não são automaticamente re-instanciadas, por isso é que fazemos quando o rato é pressionado:

 

  if(mousePressed){

    sp.setPosForce(mouseX,mouseY,pmouseX,pmouseY);

    sp.reIginite();  

  }

 

o setPosForce vai calcular a força e passar o centro para dentro do SysPart, e quando as partículas morrem, esses valores são transmitidos às novas partículas através da função reIgnite do sistema de partículas. por isso é que conseguimos ter partículas com forças e velocidades diferentes, enquanto mexemos o rato na tela, umas têem uma força e um centro, e outras, outras variáveis.

syspartd01-000163

syspartd01-000228

para o segundo sketch, em vez de termos a posição activa do rato apenas, fomos criar uma class Centro que o rato controla, mas que também evolui sozinha através de movimento browniano. ainda vamos olhar com mais detalhe para o movimento browniano, para já fica o seu funcionamento imediato:

 

    // pos = lastpos + random(-offset,offset);

 

assim, em vez de este sistema de partículas ficar estático no mesmo sítio, ele move-se pelo ecran, se alguma coordenada sai do ecran, volta para o centro.

 

  void bounds(){

    if(x<0||x>width||y<0||y>height) {

     x = width/2;

     y = height/2; 

    }

  }

 

a função bounds é chamada automaticamente da update da classe Centro

 

  void update( float _x, float _y){

    px = x; 

    py = y; //primeiro copiar valores anteriores

    x = _x; 

    y = _y; //depois actualizar

    bounds();

  }

 

e a função que implementa o gerador browniano:

 

  void dwell(){

    // pos = lastpos + random(-offset,offset);

    update( (random(-xamount,xamount)+x), (random(-yamount,yamount)+y)  ); 

  }

 

agora, a nova mecânica, no programa principal(reparem que o rato actualiza as coordenadas do centro, e as variáveis para a função setPosForce são os campos do centro:

 

SysPart sp;

Centro centro;

  sp = new SysPart(100,width/2,height/2); //num parts, centerx, centery 

  centro = new Centro(mouseX,mouseY);

na draw:

  sp.update();

  sp.draw(); 

 

  if(mousePressed){

    centro.update(mouseX,mouseY);

  }

    centro.dwell();

//    centro.draw();

    sp.setPosForce(centro.x,centro.y,centro.px,centro.py);

    sp.reIginite();  

 

 

syspartd2-000082

syspartd2-000453

 

 

1 sistema de partículas com 100 partículas e 1 centro = 15%cpu. depois introduzimos o OPENGL, passamos para o sketch 3, onde em vez de 1 temos 10 sistemas de partículas e 10 centros, e em vez de 150% cpu, ficámos com 11%. alguns comandos gráficos são agora corridos no hardware da placa gráfica e libertam o cpu, o OPENGL torna as coisas mesmo rápidas (ainda mais num puro modo opengl)
syspart3-000405

syspart3-000520

syspart3-000656

 

 

 

2. leitura, scrubbing e gravação de filmes quicktime

 o  próximo tópico foi a introdução de leitura, scrubbing e gravação de filmes quicktime. para tal, temos de importar a biblioteca video do processing, que vai ser a mesma para ler imagens de câmaras:

 

import processing.video.*;

 

para ler filmes do disco,  declaramos objectos do tipo Movie, que se constroem com myMovie = new Movie(this, “station.mov”); e logo a seguir colocamo-los em loop() com a declaração myMovie.loop(); 

 

quando o filme está pronto para ler a próxima frame, o processing envia uma função callback que se chama movieEvent (um pouco como o mouseDragged), e é nessa função que devemos ler a próxima frame através de myMovie.read(); o sketch que vos passei tem um bug, mas é para perceberem que o filme é lido como se fosse nessa lógica. é necessário comentar o myMovie.read() na função draw, e descomentar o callback anterior, para que as coisas operem com normalidade.

 

depois, o myMovie funciona como uma PImage normal. pode ser representado no ecran com a função image() que recebe o nome da variável e posx e posy, e opcionalmente, tamanho x e tamanho y. se quiserem por o filme a fullscreen tem de ser image(myMovie,0,0,width,height); como está, a imagem está interactiva à posição do rato, apenas deslocando-se face às posições mouseX e Y.

 

no sketch a seguir fomos aprender a fazer scrubbing aos videos, com o rato. só precisamos de uma float global que indique a duração do filme, e assignamos o valor a esta float quando construímos o filme:

 

Movie myMovie;

float movieDur;

void setup(){

  …

  myMovie = new Movie(this, “station.mov”);

  myMovie.loop();

  movieDur = myMovie.duration(); //get the duration

no draw, normalizamos a posição do rato, assegurando que nunca sai dos limites:

   float pos = constrain( map(mouseX,0,width,0.,1.), 0.,1.);

logo a seguir, mulltimos este valor pela duração em segundos do filme, e mandamos o filme ir para esse sítio:

  pos = pos * movieDur;  

  myMovie.jump(pos);

 

a função jump dos objectos Movie é a que permite efectuar o scrubbing. salta para a posição temporal com n segundos. para que os filmes saltem bem para as posições, devem estar comprimidos com codecs que não introduzem compressões temporais, como os divx, ou h.264, devemos comprimir os nossos filmes fonte com o codec Photo-JPEG, que não introduz keyframes, e mantem uma qualidade fotográfica das imagens. neste codec podemos saltar imediatamente para as posições dos filmes, enquanto que outros codecs podem engonhar, e atrasar o programa.

video-000075

para a gravação de filmes quicktime, declaramos um objecto do tipo MovieMaker, que construímos apenas quando queremos começar a gravar (eu fiz isso com a tecla ‘g’ e a variável booleana gravar inicialmente a falsa):

 

  if (key == ‘g’) {

    if(!gravar){

      mm = new MovieMaker(this, width, height, “video.mov”, 30, 

      MovieMaker.JPEG, MovieMaker.HIGH);

      gravar = true;

    } 

    else 

      mm.finish();

 

este método permite gravar qualquer tipo de sketch processing, nas dimensões actuais da nossa tela digital. quando se constroi o objecto mm, este fica pronto a gravar frames, mas isso só acontece quando chamamos a função mm.addFrame(); no final da função draw(), para gravar os conteúdos que desenhamos. quando queremos terminar a gravação, chamamos a função mm.finish()

video_1

3. introdução ao 3d.

 

o processing vem com dois renderers gráficos que permitem desenhar coisas em 3d. o P3D e o OPENGL. o P3D funciona em software, enquanto que o OPENGL envia parte das coisas para a placa gráfica.

 

estes modos de renderização são activados na função size, passando como argumento o nome do renderizador que queremos usar. para o opengl temos de importa a library.

import processing.opengl.*;

agora, além das coordenadas x e y, vamos ter um novo eixo cartesiano, o z, que define a distância perpendicular à tela, na direcção negativa para dentro da tela, e positiva para fora da tela. usar este novo eixo é muito simples, e uma extensão mínima comparado com o eixo x y que temos vindo a utilizar. é preciso é ter sempre presente o sistema de coordenadas do processing, onde o ponto(0,0,0) vai ser no canto superior esquerdo da tela, o x cresce para a direita, o y cresce para baixo, e o z cresce na nossa direcção. x negativos vão para a esquerda, y negativos vão para cima, e z negativos para dentro da tela.

 

fomos fazer um remake dum clássico, um campo de estrelas com velocidade variável. para manter uma variável do tipo estrela, precisamos apenas de 4 floats: x, y, z  e s. xyz vão ser a posição no sistema 3d, s vai ser uma velocidade local de cada estrela. cada x, y e s são instanciados e mantêem-se estáticos. o z é que vai variando, vindo lá do fundo e aproximando-se com a soma da velocidade local de cada estrela, com a velocidade global.

 

as estrelas são linhas, e a dimensão da linha é tanto maior quanto é a sua velocidade. o comando que desenha linhas, como já vimos, é o line, e agora, em 3d, em vez de receber 4 parâmetros, dois pontos xy, vai receber 6 parâmetros, 2 pontos xyz. 

 

a função principal da estrela é a render que desenha e actualiza as coordenadas z de cada estrela.

 

  void render(){

    if(z>500)

      zz();

    else

      line(x,y,z,x,y, z – speed*s);

    z+=(s+speed);

  }

 

se o z for maior que 500, chamamos a função zz(), que repõe o z para o fundo da tela, até no máximo à posição -10000.

starfield-000087

starfield-001150

depois, evoluímos este sketch para representar uns planos na imagem, e tornar o sketch reactivo ao som, como temos vindo a fazer nas sessões anteriores. aqui o som varia a velocidade de todas as estrelas de maneira igual.

starfieldsom-000106

starfieldsom-001079

 

4. introdução ao fft

fast fourrier transform: é uma transformação que fazemos ao som onde conseguimos ter acesso à magnitude do contéudo espectral do som, as frequências distribuem-se por bandas de análise, e cada banda vai ter um valor fraccionário que corresponde à magnitude do som em tempo-real nessa região do espectro sonoro. em vez de um valor único, vamos ter tantos valores como bandas de frequência da nossa análise de fft, tipicamente 256, 512, ou 1024 bandas.

analisamos o sketch exemplo da minim que faz fft, e nele vimos que cada linha do espectro linear representado é uma linha vertical e sobe desde o fundo da tela até determinada altura, sendo que essa altura é uma escala da banda actual. aqui está a parte do program que calcula o fft e acede ao valor de cada banda e desenha as linhas:

  fft.forward(jingle.mix);

  for(int i = 0; i < fft.specSize(); i++){

     line(i, height, i, height – fft.getBand(i)*4);

  }

 

fft-000461

 

fft-000040

vamos usar agora este mecanismo na construção de um sketch 3d das estrelas, onde cada estrela reage a frequências diferentes do som em tempo real. para tal, umas modificações mínimas na class start para que cada estrela fique linkada a uma banda fft, e cada função render lê o valor dessa banda e faz variar a velocidade da estrela de acordo com essa velocidade. para que tal aconteça:

 

no construtor:

    fftband = (int) random(fft.specSize());

no render:

    local_speed = fft.getBand(fftband) * 100.;

    z+=(s+local_speed);

 

 

algumas imagens, notem como apenas algumas das estrelas reagem ao som nessas frequências…

starfieldfft-000208

starfieldfft-001031

finalmente, modificámos a class dos planos para permitir inserir uma imagem, e em vez de termos cores nos planos, vamos ter um png carregado para uma array de imagens e escolhido aleatoriamente para cada plano.

starfieldimg-000187

starfieldimg-001132

o exercício de hoje era modificar os sketches da aula e gravar um video dessa interacção. aqui ficam alguns stills dos vossos trabalhos (no post de exemplos sessão 6). até para a semana.

sistemas de partículas. são dos primeiros algoritmos que implementam técnicas físicas na modelação computacional de vários elementos simultaneamente; um dos mais importantes avanços gráficos, onde através de um algoritmo se tentam modelar fenómenos complexos como nuvens, neve, fumo, quedas de água, explosões, etc. os sistemas de partículas são ainda técnicas de programação base sobre as quais se construiram, por exemplo, os algoritmos de boids, enxames de partículas que se movem de acordo com parâmetros mais complexos. os sistemas de partículas foram usados pela primeira vez no filme star trek 2, em 1982, e há um paper de William Reeves de 1983 que descreve essa técnica.

 

nos sistemas de partículas aglomeram-se várias partículas individuais, com características únicas, entre as quais posições, velocidades e forças, e estas partículas são coligidas num único objecto ou classe, que será um gestor das várias partículas. cada sistema de partículas tem regras únicas, de acordo com o efeito desejado, mas o comportamento base é sempre o mesmo: as partículas individuais são gerados num centro ou numa região, com posições e velocidades iniciais distintas. com o passar de cada frame, todas as partículas recebem mais ou menos a mesma quantidade de força, quer seja a gravidade, ou outras forças em jogo. estas forças vão modificando as velocidades das partículas, e as velocidades por sua vez vão modificando as posições. este mecanismo corresponde a uma integração de um sistema de partículas pelo método de Euler, onde as novas posições são iguais às posições anteriores mais as velocidades, as velocidades actuais são iguais às velocidades anteriores mais as forças (há outros métodos comoVerlet,  Runge-Kutta, cada um com as suas especificidades, vamos ficar no de Euler nos próximos tempos).

 

cada partícula terá então em traços largos a seguinte composição:

 

class Part {

  float px, py; //pos 

  float vx, vy; //vel

  float fx, fy; //força

  float energia; 

(…)

}

 

e será inicializada ou na mesma posição ou em posições diferentes, e as velocidades iniciais serão aleatórias, ou a apontar para determinadas regiões. as forças, essas serão mais estáveis, como a gravidade, manterão uma acção constante nas velocidades (mas também pode variar bastante, para se deslocar para um ponto de atracção móvel, por exemplo)

 

o construtor da partícula será algo como:

 

   Part(float centrox, float centroy) {

     px = centrox ;

     py = centroy ;

     vx = random(-10,10);

     vy = random(-10,10);     

   }

 

 

a função update de cada partícula calcula as novas posições com base na integração do sistema das posições e velocidades:

 

 void update(){

   // actualizar as velocidades a partir das forças

   vx = vx + fx;

   vy = vy + fy;

   vy = vy + gravidade;

   // fricção

   vx = vx * friccao;

   vy = vy * friccao;

   // actualizar as posições a partir das velocidades

    px = px + vx;

    py = py + vy;

   // actualizar a energia

   energia = energia – 0.5;

  }

 

num sistema de partículas contínuo, logo que a energia de uma partícula é menor que zero, ela é reiniciada na posição central do sistema de partículas, com novas velocidades e com a energia reposta no máximo. num sistema de partículas em que elas morrem mesmo, será necessária uma acção exterior para voltar a despoletar o sistema em novas posições.

 

syspart1-000184

 

syspart1-0005001

 

normalização de vectores

antes de abordar os exemplos, vimos a operação da normalização de um vector com componentes x e y. é uma operação fundamental para aplicar forças uniformes em qualquer direcção. primeiro calculamos um vector aleatório, depois normalizamos, para ter a certeza que o comprimento desse vector é de 1., e a seguir podemos aplicar o valor de força a cada um dos componentes do vector. assim conseguimos controlar a magnitude dos vectores, e gerar vectores com a magnitude que quisermos, em qualquer direcção. isto vai permitir aplicar as tais forças variáveis em qualquer vector, em qualquer direcção.

para normalizar um vector, basta dividir as componentes nas várias dimensões pelo comprimento actual do vector. assim temos a certeza que o comprimento será de 1.

float vx = random(-20,20);

float vy = random(-20,20);

float len = sqrt (vx*vx+vy*vy); // o comprimento do vector aleatório

vx = vx / len;

vy /= len; // a seguir a esta linha o vector vx, vy está normalizado

vx*=force;

vy*=force; // aplicamos uma força de tamanho ‘force’ ao vector.

 

 

exemplos dos sistemas de partículas


como criamos uma classe que define as particulas, e outra que define o sistema de partículas, a manipulação das várias partículas é muito simplificada no programa principal. 

declaramos um único objecto ‘sp’ do tipo SysPart. na função setup chamamos o construtor do SysPart com 3 argumentos: num de partículas, centro x e centro y.

a função draw chama a função update e draw do objecto sp. basta estas funções, pois o código está todo encapsulado nas classes SysPart e Part. temos apenas mais uma função que permite alterar o centro do sistema de partículas, que chamamos quando carregamos no rato.

 

/*

    sistemas de partículas // o som do pensamento // 2009

*/

 

SysPart sp;

 

void setup(){

 

 size (700,700);

  frameRate(30);

 

 sp = new SysPart(100,width/2,height/2); //num parts, centerx, centery 

 

}

 

 

void draw(){

 

 background(0);

 sp.update();

 sp.draw(); 

 

}

 

void mousePressed(){

 

  sp.setPos(mouseX,mouseY);

 

}

 

 

a classe Part vai representar uma única partícula, e apresenta a seguinte estrutura:

 

class Part{

 

  float px,py, ppx,ppy; // posições actuais e anteriores

  float energy,energy_dec;

  float rad;

 

  float vx,vy; //vel

  float f; //friction

 

  float gravity; //gravidade, força no eixo dos y

  color col;

 

 

o construtor das partículas vai iniciar as posições todas no mesmo local e vai colocar velocidades aleatórias como velocidades iniciais.

 

 Part(float x,float y){

    px = ppx = x;

    py = ppy = y;

    energy = 255.f; 

    energy_dec = random(5,10);

    rad = random(10,50);

    //make_rnd_velocity(50.);

    vx = random(-50,50);

    vy = random(-50,50);

    f = 0.9;

    gravity = 0.9;

    col = color( 232, random(100,152), 40); //reddishes

  }

 

 

as funções principais update e draw, respectivamente integram o sistema, actualizando as novas posições e velocidades a partir das forças no sistema, e a função draw desenha as posições actuais das partículas. será a função update responsável pelo comportamento da partícula.

 

  void update(){

    //store pos

    ppx = px;

    ppy = py;

   //update velocity

   vy = vy + gravity ;

   //vel = vel * friction

   vx = vx * f;

   vy = vy * f;

   // position = pos + vel

   px = px + vx;

   py = py + vy;

   // energy

   energy = energy – energy_dec;

  }

 

  void draw(){

    stroke(col, (int)energy);

    line(ppx,ppy,px,py);

  }

 

 

estas são as funções principais que manipulam os campos variáveis de uma única partícula. agora resta-nos agrupar várias partículas numa outra class, que será a classe do sistema de partículas, SysPart.

 

class SysPart{

  Part  p[]; // a array de partículas

  float cx,cy; // o centro

 

  SysPart(int num, float x, float y){

    p = new Part[num];

    cx = x; 

    cy = y;

    for(int i=0; i<p.length;i++){

      p[i] = new Part(x,y); 

    }

  } 

 

a classe SysPart tem uma array de objectos Part chamada p, e duas floats para representar o centro. quando chamamos o contrutor do objecto, com numero de partículas e posições x e y, o número de partículas vai servir para iniciar a array de partículas, declarando a array de tamanho igual ao número que passamos, e inicializando cada Part com o x e y que passamos como centro. lembre-se que o construtor da Part recebe x e y.

 

logo a seguir, temos as funções gestoras de todas as partículas, a função update e a draw. a update vai chamar a função update de cada uma das partículas e se a energia de cada partícula é inferior a zero, vai recolocá-la no centro, com novas energias e velocidades, dependendo de uma força que é cíclica com a frameCount.

 

  void update(){

    for(int i = 0; i < p.length; i++) {

      p[i].update();

 

      if(p[i].energy < 0){

        p[i].setPos(cx,cy); //novo centro

        p[i].energy = 255;  //energia a 255 de novo

        float force = ( (frameCount*0.1) % 100);

        p[i].make_rnd_normalized_velocity(random(force));

      } 

    }

  }

 

a função draw limita-se a chamar a draw de cada partícula individual:

 

  void draw(){

    for(int i = 0; i < p.length; i++) 

      p[i].draw();

  }

 

 

com este esquema de organização relativamente simples, onde definimos o comportamento individual de cada partícula numa classe que se chama Part, e criamos uma nova classe que agrega N partículas individuais num SysPart, conseguimos representar e controlar várias partículas individuais com comportamentos únicos mas que são regradas da mesma maneira para cada sistema de partículas.

 

o que falta neste sketch é a introdução de mais forças nos sistemas de partículas, e foi isso que fomos fazer no sketch sistemas_de_particulas02. o programa principal mantém-se essencialmente igual, o que muda são as classes Part e SysPart. na classe Part inserimos  float fx,fy; //força nos eixos no construtor e na função update:

 

   //update velocity

   vy = vy + gravity ;

   vx = vx + fx;

   vy = vy + fy;

 

além de usarmos a gravidade, usamos também estes valores de força para alterar os valores da velocidade. inserimos ainda uma nova função na Part, que se chama setPosForce, e que perimitirá colocar a nova posição e nova força nos campos membros da partícula. tudo o resto se mantém igual. na classe SysPart, inserimos também float fx,fy; // uma força nos membros do objecto, e uma nova função que recebe mouseX e mouseY e o previousMouse também para calcular a força a partir de diferença destes parâmetros.

 

  void setPosForce(float x, float y, float px, float py){

   cx = x;

   cy = y; 

   fx = (x – px)*0.1;

   fy = (y – py)*0.1;

  }

 

quando as partículas morrem e são reinstanciadas, é nesta altura que passamos os novos valores de força para dentro de cada elemento Part, fazendo uma curta alteração na função update do SysPart:

 

     if(p[i].energy < 0){

        p[i].setPosForce(cx,cy,fx,fy); //novo centro, nova força

 

e no programa principal vamos actualizar o valor das forças sempre que carregarmos com o rato, através da função mousePressed:

 

void mousePressed(){

  sp.setPosForce(mouseX,mouseY,pmouseX,pmouseY);  

}

 

fica tudo ligado, e será a velocidade actual do rato que gerará particulas com determindas forças.

 

syspart3-000662

syspart3-0007011

syspart3-001196

 

o sketch número 3 dos sistemas de partículas introduz duas novas noções, a primeira é a de desenhar continuamente com os sistemas de partículas, e a segunda é a de gerar as partículas em torno do centro, não exactamente do centro, mas com uns valores variáveis em torno do centro.

 

para tal basta adicionar a função mouseDragged no sketch principal, para termos este comportamento:

 

void mouseDragged(){

  sp.setPosForce(mouseX,mouseY,pmouseX,pmouseY);  

}

 

e na class SysPart definimos uma variável cdev que definirá um valor aleatório para x e y quando chamamos o contrutor da partícula.

 

  SysPart(int num, float x, float y){

    p = new Part[num];

    cx = x; 

    cy = y;

    for(int i=0; i<p.length;i++){

      p[i] = new Part(x+ random(-cdev,cdev),y+ random(-cdev,cdev)); 

    }

  } 

 

syspart3-000088

 

o sketch número 4 dá um salto, onde, em vez de usarmos um único sistema de partículas, vamos usar uma array de sistemas de partículas. alteramos principalmente o programa principal, onde agora cada sistema de partículas vai ter uma cor, para mais facilmente distinguirmos qual é o sistema de que estamos a falar. para tal, cria-se uma array de cores, onde passamos uma cor para o construtor dos SysPart, que por sua vez passa essa mesma cor para cada uma das partículas que lhe corresponde.

 

syspart4-000190

/*

    sistemas de partículas // o som do pensamento // 2009

 agora com forças nos dois eixos, + a gravidade 

 e com cores diferentes, uma array de sistemas de partículas

 */

 

SysPart sp[];

int head=0;

color cores[] = { #E86528, #08FFF9, #0885FF, #4408FF, #FF087B ,

                  #40FF08, #08FFD4, #51607C, #B7DCE3, #C6FFC1};

 

 

void setup(){

 

  size (700,700);

  frameRate(30);

 

  sp = new SysPart[10];

  for(int i=0; i < sp.length; i++){    

    color c = cores[(int)random(cores.length)]; // escolher uma cor aleatória da array de cores    

    sp[i] = new SysPart((int)random(50,200),width/2,height/2, c); //num parts, centerx, centery, cor

  }

 

}

 

 

void draw(){  

  // background(0);

  noStroke(); 

  fill(0,100);

  rect(0,0,width,height);

 

  for(int i=0; i < sp.length; i++) {

    sp[i].update();

    sp[i].draw(); 

  }

 

}

 

void mousePressed(){

  head= (head+1)%sp.length;

  sp[head].setPosForce(mouseX,mouseY,pmouseX,pmouseY);  

}

void mouseDragged(){

  sp[head].setPosForce(mouseX,mouseY,pmouseX,pmouseY);  

}

 

syspart4-000350

syspart4-0009271

 

este sketch mostra-nos que um rato não chega para controlar tantos sistemas de particulas simultaneamente e criar um comportamento interessante. nos últimos sketches fomos alterar o comportamento de cada sistema para que as partículas quando morrem não sejam automaticamente re-instanciadas. isso faz-se comentando o auto-re-instanciar das partículas na função update do SysPart, ficando apenas a função update a chamar o update de cada Part, que é executado apenas se a energia for superior a 0.. e criamos uma nova função que se chama reIgnite, que vai fazer o que a função update fazia, isto é, se a energia de alguma partícula for inferior a 0., então repomos essa partícula com um valor de energia máximo perto do centro do sistema de partículas, com velocidades iniciais aleatórias.

 

  void reIgnite(){

    for(int i = 0; i < p.length; i++) {

      if(p[i].energy < 0){

        p[i].energy = 255;

        p[i].setPosForce(cx+ random(-cdev,cdev),cy+ random(-cdev,cdev),fx,fy);

        float force = (abs(fx)+abs(fy)) * 10.;

        p[i].make_rnd_normalized_velocity(random(force));

      }

    }    

  }

 

e de seguida introduzimos uma interacção à base de intensidade sonora, como temos estado a fazer nos programas das sessões anteriores. se o valor actual de som passa o treshold de som mínimo, então criamos um novo sistema de partículas na tela com forças dependendo do som. primeiro temos de começar a cadeia de som e analisar o valor de som na função draw, e se esse valor for superior ao minsound, chamamos uma função que criar um novo sistema de partículas:

 

  float rms = in.mix.level();

//  println(rms); //escrever os valores de som

 

  if(rms > MINSOUND){ //criar um novo sistema

     call_new_sys(rms);

   }

 

 

calibrem bem o sistema. o valor de MINSOUND e MAXSOUND serão fundamentais para afinar o comportamento do fogo de artíficio. esta função call_new_sys, que recebe o valor de rms, vai emitir sistemas com mais ou menos força, dependendo desse valor que entra como argumento. e aqui vemos a importância da normalização do vector força, que passamos para dentro do sistema de partículas. a força vai ser multiplicada por um valor de inten  que corresponde ao mapeamento do rms entre um min/max para valores de destino de magnitudes destes vectores.

 

 

void call_new_sys(float fff){

    head= (head+1)%sp.length;

    sp[head].setPos(random(200,width-200),random(200,height-200));

    //vector aleatório normalizado de força

    float fx = random(-1,1);  

    float fy = random(-1,1);

    float len = sqrt(fx*fx+fy*fy);

    fx/=len; fy/=len;

    // multiplicar o vector por intensidade sonora

    float inten = map (fff, MINSOUND,MAXSOUND, 0.05,10.);

    fx*=inten; fy*=inten;

    sp[head].fx = fx; sp[head].fy = fy;

 

    sp[head].reIgnite();   

 

}

 

 

syspart5s-000272

syspart5s-000549

syspart5s-000940

syspart5s-001042

 

realizámos um exercício livre, que tinha como objectivo meter as mãos na massa dos sistemas de partículas, onde havia umas direcções para alterarem o comportamento das partículas e das primitivas gráficas, juntando elementos que temos vindo a analisar em sessões anteriores. vários exemplos dos vossos exercícios mesmo aqui no post de imagens da sessão 5. sistemas de partículas são muito divertidos,  e permitem modelar fenómenos mais complexos, são usados um pouco em todo o lado nos mundos gráficos computacionais, pois são muito ricos visualmente.

 

temos estado a analisar comandos gráficos à base das primitivas 2d que vêem com o processing: rect, ellipse, line, point, triangle... hoje introduzimos o paradigma beginShape() / vertex() / endShape(), bem como as transformações às matrizes geométricas, dadas pelas funções translate, scale, rotate e finalmente o par pushMatrix e popMatrix, que permite encapsular os comandos geométricos.

 

estas noções  permitem criar comandos gráficos mais avançados, agrupamos vértices definidos em coordenadas locais (o zero vai ser muito importante, pois é o ponto em torno do qual se roda e que é movido…), e temos várias maneiras de unir os vértices; a mesma data pode ser unida através de pontos, linhas, tiras de linhas, quads, tiras de quads, triangles, triangle_strip, triangle_fan, etc. estas funções e parâmetros são inspiradas no sistema gráfico OPENGL. (o opengl vai ser o sistema gráfico 2d/3d mais avançado, vejam a referência no redbook http://fly.cc.fer.hr/~unreal/theredbook/ ).mantém-se um estado de transformações geométricas, com o mesmo sistema de vértices definidos, e roda-se, move-se, escala-se, desenha-se de diferentes formas este sistema de vértices. 

 

os parâmetros que podem ser enviados à função beginShape correspondem aos parâmetros de opengl que podem ser enviados à função glBegin, sem o prefixo GL_*. comparem esta imagem (http://fly.cc.fer.hr/~unreal/theredbook/figures/fig2-6.gif ) que ilustra todos os parâmetros à função glBegin com a referência do comando beginShape do processing.

 

assim, criamos uma forma gráfica ao usar a função beginShape, seguida de listas de vértices, e fechamos a forma com a função endShape. antes dos comandos da forma, podemos aplicar transformações geométricas (translate, rotate, scale) que modificam o sistema de coordenadas e representam a forma em determinada posição dada pela função translate, e rodam-na x radianos, transformação dada pela função rotate.

 

o pushMatrix e popMatrix são sempre usados aos pares, armazenando informações sobre as transformações geométricas e comandos gráficos de formas através do beginShape / vertex / closeShape, e, mais importante, fazendo um reset às transformações geométricas. 

 

há um estado geométrico mantido na tela, que lembra sempre o sistema de coordenadas em vigor. esse estado pode ser alterado através das transformações geométricas, como o translate, rotate e scale. se aplicamos o translate, movemos o centro do sistema de coordenadas para os parâmetros enviados através da função. se aplicamos o rotate, o sistema geométrico é todo ele rodado, e todos os vértices que se encontrarem nesse nível das transformações são rodados x radianos. o par push/popMatrix permite lembrar o estado anterior, e criar um novo estado, repondo a origem no sítio habitual. quando fazemos pushMatrix (o equivalente glPushMatrix();) estamos a dizer ao sistema gráfico para criar um novo nível de transformações geométricas, onde agora logo a seguir ao pushMatrix, esse nivel está iniciado a zeros, nas coordenadas originais. fazemos translates e rotates, desenhamos a forma, e logo a seguir aplicamos um popMatrix(), para regressar ao estado gráfico anterior. é muito importante que os níveis abertos com o pushMatrix sejam fechados com o popMatrix, para regressar a cabeça gráfica ao estado geométrico anterior.

 

como exemplo, pegámos no sketch do triângulo de som que vimos na sessão anterior, e mudamos a maneira de criar a geometria gráfica. na sessão anterior, mantemos a existência do triângulo apenas com as variáveis centro px e py e uma variável para o ângulo. calculamos de seguida as coordenadas dos pontos que fazem parte do triângulo estando a determinado raio e ângulo do ponto central. 

relembro aqui o código que faz isso:

 

 // um triângulo são 3 pontos

  // neste caso, cada ponto está à mesma distância do centro

 

  float pt1x,pt1y;

  float pt2x,pt2y;

  float pt3x,pt3y;

  float a; // um angulo

 

  rad = map (audio_energy, 0. , 0.2 , 50, 250);

  angulo += 0.01;

 

  a = HALF_PI + angulo;

  pt1x = cos(a)*rad + px; 

  pt1y = sin(a)*rad + py; 

 

  a = PI + PI/4 + angulo;

  pt2x = cos(a)*rad + px; 

  pt2y = sin(a)*rad + py; 

 

  a = TWO_PI – PI/4 + angulo;

  pt3x = cos(a)*rad + px; 

  pt3y = sin(a)*rad + py; 

 

 fill(255,100);

 stroke(0,150);

 

 triangle(pt1x,pt1y, pt2x,pt2y, pt3x, pt3y);

 

 

hoje vamos desenhar um triângulo, recorrendo ao novo paradigma gráfico que aqui introduzimos, e que vai simplificar bastante a criação de novas formas. 

 

para tal, primeiro temos de pensar que queremos rodar o triângulo em torno do centro, e não de um dos vértices. isto implica que o nosso sistema de coordenadas local da forma gráfica a desenhar tem de ficar com a origem(o ponto 0,0) exactamente no sítio em que queremo que o triângulo rode.

 

assim, vamos definir os vértices do triângulo em função deste ponto original. o ponto A estará na posição 0X e -radY, pois o eixo dos y no processing cresce para baixo. o ponto B estará na posição rad/2, rad, ou seja, metade do raio para a direita da origem, e raio para baixo no sentido y. o ponto C vai para a posição -rad/2, e rad para baixo. isto faz um sistema de vértices definido desta maneira:

 

 

    beginShape();

    vertex(0, -rad);

    vertex(rad/2, rad);

    vertex(-rad/2, rad);

    endShape(CLOSE);    

 

depois, completamos o nosso draw do triângulo, ao inserir as transformações geométricas que aplicamos, translacção para as coordenadas do centro px e py do triangulo e rotação angulo radianos. finalmente, temos de encapsular estas transformações geométricas dentro de pares push/pop, para permitir que outras formas que sejam desenhadas a seguir tenham a origem da tela como referência, e não estados geométricos anteriores deixados por outras formas gráficas.

 

 

  void draw(){

 

    pushMatrix();

    translate(px,py);

    rotate(angulo);

 

    beginShape();

    vertex(0, -rad);

    vertex(rad/2, rad);

    vertex(-rad/2, rad);

    endShape(CLOSE);    

 

    popMatrix();

 

  }

 

estas modificações são importantes, simplificam, e permitem outros níveis de complexidade, e estão mais próximas de comandos gráficos tri-dimensionais do que os comandos de primitivas gráficas que vimos anteriormente.

 

 

1. máquinas de desenhar (à base de som), parte 3

 

com a mudança de sistema geométrico, começamos a complexificar introduzindo máquinas de desenhar à base de som. como na sessão anterior, vamos ter um valor único e global (que pode ser acedido em qualquer parte do nosso sketch processing) que se chama audio_energy e que corresponde ao rms dos samples de som nos buffers capturados em tempo-real pelos microfones com um filtro lowpass para suavizar as transições.

 

primeiro 10 triângulos a reagirem de maneira igual ao som, depois 50 triângulos já com respostas diferentes, depois 100 triângulos a andarem com orientações dependentes da presença sonora.

 

os triângulos que reagem de maneira igual, têm os mesmos valores no construtor, e as funções que mapeiam os valores de audio têm os mesmos máximos e mínimos de destino. varia apenas o valor de velocidade que faz com que uns andem mais depressa e outros mais devagar.

sndtri1-002066

os triângulos que têm respostas distintas ao mesmo valor de som, têem novas variáveis que determinam estas diferenças nas respostas. nos campos da classe, adicionamos variáveis que servem como mínimos e máximos de variação de raio, variação de ângulo e variação de velocidade. o construtor instancia valores aleatórios para cada um desses valores. e esses valores são aplicados nos destinos das funções que mapeiam a audio_energy para o raio, o angulo e velocidade actual do triângulo em questão. como todos os triângulos têm valores distintos, as respostas ao mesmo parâmetro vão ser diferentes. além disso, há uns triângulos pretos que vão apagando o que os triângulos brancos acumulam na frame.

sndtri1-007122

sndtri1-011252

sndtri1-018892

os 100 triângulos a andarem com direcções e velocidades diferentes, vêem do mini-jogo à base de som que desenvolvemos na sessão anterior, onde se o som passar um parâmetro mínimo, andamos em frente na direcção angulo actual. se o som estiver próximo de silêncio, o triângulo altera a direcção e fica no mesmo sítio.

sndtri4-002573

sndtri4-0027751

 

aprendemos também a correr os sketches em fullscreen, com o modo present, e colocando o tamanho da tela com as dimensões do ecrã, passando as variáveis screen.width e screen.height como parâmetros para a função size.  

sndtri5-001231

sndtri5-190374

 

as imagens exemplos da sessão 4 são exemplos gráficos que vocês desenvolveram na aula, criando máquinas de desenhar à base de som, usando este novo sistema gráfico.

 

finalmente, aplicamos o último comportamento dos triângulos, substituindo a forma gráfica por imagens de homens e mulheres vistos de cima, criando uma multidão que se desloca em função do som. a multidão é a classe dos triângulos onde agora o comando gráfico são imagens com máscaras.

 

 

  void draw(){

 

    pushMatrix();

 

    translate(px,py);

    rotate(angulo);

 

    tint(255,150);

    image(img, -rad/2, -rad/2, rad, rad);

 

    popMatrix();

 

  }

 

 

sndhumans-000733

sndhumans-0021071

na última parte da aula ainda vimos o princípio de detecção de colisão, onde se as distâncias entre os triângulos ou os humanos eram inferiores a 100px, desenhamos uma linha entre eles. isso está desenvolvido na função principal que desenha todos os triângulos aqui:

 

   // renderizar todos os triangulos

 

   for(int i=0; i<tris.length;i++){

     tris[i].update();

     tris[i].draw(); 

 

     // check distance & line if < thresh

      for(int j=i+1; j<tris.length;j++){

       float d = abs(tris[i].px-tris[j].px) + abs(tris[i].py-tris[j].py); 

       if(d < 100){

        stroke(tris[i].cfill,12);

        line(tris[i].px,tris[i].py,tris[j].px,tris[j].py);

       }

      }

  }

 

sndtri5-018676

 

mesmo no fim, vimos alguns sketches simples que tentam abstrair a questão de encontrar o ângulo de qualquer ponto do espaço para o ponto representado pelo rato, e vimos os sketches setas.

 

o comportamento destes é sempre representado pela função update, onde vamos encontrar o ângulo entre dois pontos através da função atan2, como já vimos quando falámos de sistemas polares e sistemas cartesianos.

 

void updateSeta(){

  //ver o angulo da seta até ao rato (rato – seta)

  float dx = mouseX – px; 

  float dy = mouseY – py; 

  ang = atan2(dy,dx);

}

 

 

a função que desenha a seta é baseada neste novo paradigma gráfico que hoje introduzimos, e desenhamos uma linha horizontal, e depois mais duas linhas, de maneira a fazer uma seta a apontar para este (0 graus corresponde a oriente).

 

void desenhaSeta(){

  pushMatrix();

  translate(px,py);    //move a posição para o centro px, py

  rotate(ang);         //roda na pos actual ang radianos

  stroke(255);         // cor a branco

  rect(0,0,2,2);      // quadrado no centro da seta (reparem q a pos é 0,0 pois já estamos no centro)

  line(-len/2,0,len/2,0);  //corpo da seta

  line(len/3,10,len/2,0);  //braço1 da seta

  line(len/3,-10,len/2,0); //braço2 da seta

  popMatrix();

}

 

 

começamos com uma seta, depois N setas em cena, e finalmente as setas dispostas numa matriz todas a apontar para o rato.

setas1-00226

setas1-01358

 

com estas noções, concluímos a introdução ao novo sistema gráfico, que tanto permite que se trabalhe em 2d, como 3d, e já temos as ferramentas para começar a fazer sketches gráficos mais avançados.