Skip navigation

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.

Anúncios

2 Comments

  1. alguns de vocês perguntaram:
    – como é possível definir pontos de atracção para o sistema de partículas?

    e eu mostrei-vos como era simples fazer isso, no update da partícula, fazer com que a força seja um vector na direcção do ponto de atracção de magnitude x. as diferenças são muito poucas. no update do Part:

    // calcular vector da pos até ao rato
    float dx = mouseX-px;
    float dy = mouseY-py;
    float len = sqrt(dx*dx+dy*dy);
    //normalizar a distância e multiplicar por uma força
    dx/=len; dy/=len;
    float force=1.6;//0.2;//10;//2.5;
    dx*=force; dy*=force;
    //adicionar estas componentes às forças
    fx += dx; fy += dy;
    //adicionar as forças às velocidades
    vx+=fx; vy+=fy;

    aquele valor de força tem vários valores. o passo mais importante é a normalização do vector, e depois multiplicamos por uma força, maior ou menor, e temos uma grande surpresa:)

  2. outra modificação interessante foi substituir as partículas por texto. isto implica umas modificações mínimas tb.

    no programa principal, definir uma fonte.

    PFont font;

    na função setup, adicionar:

    font = loadFont(“nomedafontecriada.vlw”,21);
    //ou
    //font = createFont(PFont.list()[1],21);
    textFont(font,21);

    como membro da classe da Partícula:

    String txt = “”;

    no construtor da Partícula, criar um novo construtor

    Part(float x, float y, String s){
    this(x,y);
    txt = s;
    }

    o draw da Part altera-se para:

    void draw(){
    fill(col,energy);
    text(txt,px,py);
    }

    e na classe e construtor do SysPart:

    String texto[] = { “Converts”, “laser”, “scanning”, “video”, “to”, “mathematical”,
    “surface”,”This”, “program”, “takes”, “as”, “input”, “a”, “.avi”,
    “rotation”, “o2f”, “an”, “object” };

    SysPart(int num, float x, float y){
    p = new Part[num];
    cx = x;
    cy = y;
    for(int i=0; i<p.length;i++){
    String txt = texto[(int)random(texto.length)];
    p[i] = new Part(x,y,txt);
    }
    }


One Trackback/Pingback

  1. By sessão 17 « O Som do Pensamento on 27 Maio 2009 at 11:54 am

    […] o próximo tópico foram as partículas e sistemas de partículas, onde elementos simples mantêm variáveis de posição, velocidade, aceleração. a aceleração será o vector que controla a magnitude da deslocação. é também o vector que define a totalidade das forças aplicadas nas partículas. os próximos passos são actualizar a velocidade com a aceleração, actualizar a posição com base na nova velocidade, desenhar o elemento na nova posição. (sessão 5) […]

Deixe uma Resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão / Alterar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Terminar Sessão / Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão / Alterar )

Google+ photo

Está a comentar usando a sua conta Google+ Terminar Sessão / Alterar )

Connecting to %s

%d bloggers like this: