Skip navigation

a segunda sessão orbita em torno dos conceitos que assustam mais (mas que não há razões para tal: ), e que mais confusão provocam, e ainda que coisas mais engraçadas despoletam. estes conceitos são abordados já ao início, com exemplos simples, para que vão entrando mais facilmente, ainda vamos ter muito tempo ao longo do curso para regressarmos a estes temas trigonométricos.

 

começamos por rever as noções básicas de programação por objectos, da sessão passada, onde tínhamos a nossa classe Circulo, e com ela começamos a construir círculos no ecran que têm um comportamento reactivo ao rato.

 

hoje vamos ao fundo da questão do círculo, e bifurcar a aula em duas direcções: uma direcção vai construir uma máquina de desenhar, a outra direcção vai construir o primeiro mini-jogo do curso, que não é mais do que uma complexificação simples do sketch dos círculos que temos vindo a analisar. ambas estas direcções vão ter por base a conversão entre coordenadas polares e cartesianas, sendo que na máquina de desenhar vamos implementar escribas polares, e no mini-jogo, implementar encontrar distâncias entre círculos, comportamento base que nos pode dar a riqueza da interactividade.

 

primeiro vamos ver com calma como se constrói um círculo, a partir de linhas usando o comando line do processing (que recebe 4 parâmetros, x,y do primeiro ponto, x, y, do segundo ponto), o que envolve o entendimento sobre os dois sistemas de coordenadas mais usados em programação: o sistema cartesiano (onde cada ponto/vector é definido por magnitudes nos eixos ortonormados x/y) e o sistema polar (onde cada ponto/vector é definido com um ângulo e um raio). isto porque para encontrar as coordenadas dos pontos que pertencem ao círculo, é muito mais simples defini-lo no sistema polar, onde apenas temos de definir/incrementar o ângulo e o raio, para podermos ter movimento realmente circular, e posteriormente passar estes resultados para o sistema cartesiano, sistema base no qual estão definidos quase todos os comandos gráficos das linguagens de programação. o que está na base destas noções é o famoso teorema de pitágoras, concebido há mais de 2000 anos. vejam também esta página da wikipedia que é muito útil.

 

 circulo-1460

/*  circulo, som do pensamento

 

  este sketch procura mostrar como se constroiem círculos,

  e como convertemos entre coordenadas cartesianas e polares

 

  um ponto cartesiano (x,y) pode ser descrito como (alpha,raio) polar,

  e vice-versa, um ponto polar tem correspondentes no sistema cartesiano.

 

  //  CONVERSÃO POLAR->CARTESIANO

 

  tendo angulo e raio, converte-se para coords cartesianas usando sin e cos

 

  x = raio * cos (angulo);

  y = raio * sin (angulo);

 

 

  //  CONVERSÃO CARTESIANO->POLAR

 

  tendo x e y, converte-se para coordenadas polares usando o teorema de pitágoras

 

  raio = √ ( x^2 + y^2 ); // raiz quadrada da soma dos quadrados de x e y

  angulo = atan2 (y,x);

 

*/

 

 

assim, quando temos um raio e uma distância (a partir de um centro), estamos a falar dum ponto no sistema polar, podemos descobrir as coordenadas cartesianas desse ponto aplicando a fórmula:

 

  x = raio * cos (angulo) + centro.x;

  y = raio * sin (angulo)  + centro.y;

 

esta fórmula é uma desmultiplicação do teorema de pitágoras, onde as relações trigonométricas de seno e coseno (sin e cos) nos dão valores matemáticos precisos das magnitudes dos catetos adjacentes e opostos de um triângulo rectângulo em relação à hipotenusa, que é sempre o caso quando falamos de coordenadas cartesianas. (experimentem definir um ponto numa folha de papel e falar dele a partir de outro ponto: o resultado é a criação destes sistemas polares e cartesianos para podermos identificar com precisão a localização dos pontos).

 

quando temos coordenadas cartesianas e queremos descobrir as polares (ou o ângulo e a distância entre dois pontos), aplicamos as fórmulas:

 

raio = √ (x^2 + y^2);

angulo = atan2 (y,x);

 

 

a distância entre dois pontos vai ser um algorimo essencial para criar elementos interactivos e jogáveis, pois através da distância conseguimos calcular parâmetros como velocidade, ou colisão, que são muito úteis na criação de programas que envolvem e com os quais podemos brincar.

 

como primeiro exemplo, o sketch da aula p5_desenhar_circulo, vamos apenas desenhar um círculo no ecran, onde o círculo existe com duas variáveis float, o raio e o ângulo, e outras 4 variáveis armazenam as posições anteriores e actuais cartesianas: previousx, previousy, x, y.

 

float raio;

float angulo;

 

float ppx, ppy; //previous px, py

float px, py;

 

 

// inicializar o programa

 

void setup(){

 size(700,700);

 frameRate(30);

 background(100);   

  //inicializar tudo no centro

 ppx = px = width/2;

 ppy = py = height/2;

}

 

 

vamos agora na função draw usar uma das funções mais simples e recorrentes para transcrever valores de um intervalo de origem para um intervalo de destino, a função map, que mapeia um valor variável que existe dentro de determinado âmbito (o mouseX, dentro da tela, existe dentro do âmbito 0 a largura, e queremos que esses valores sejam mapeados linearmente para outro intervalo, -0.2 a 0.2, quantidades a incrementar ângulos, uma vez que os ângulos sao definidos em radianos, isto é, de 0 a 2π ou -π a π, ou seja, de 0. a 6.28 ou de -3.14 a 3.14). estas conversões são essenciais, pois se usarmos directamente os valores do mouseX, são demasiado elevados para a escala dos radianos — mouseX são inteiros que vão de 0 a 700, e ângulos vão de 0. a 6.28 –, e desenham outro tipo de coisa que não círculos, dão saltos muito grandes, comparados ao desenhar lento que vamos verificar com este sketch de processing.

 

o valor do ângulo vai ser então incrementado, ou no sentido dos ponteiros do relógio, ou no sentido inverso, dependendo se o rato está à esquerda ou à direita do centro do ecran. uma analogia física muito próxima corresponde a fixar um cordão num centro e ir esticando a linha mais para a esquerda ou mais para a direita, como os primeiros geómetras faziam círculos.

 

o valor do raio vai ser directamente mapeado em função do mouseY, em vez de acumulado como é o caso do ângulo.

 

// o resto do código do programa

 

void draw(){

 

 angulo = angulo +( map (mouseX, 0 , width, -0.2,0.2) );

 raio = map(mouseY,0,height,300.,0.);

 

 //guardar as posições anteriores

 ppx = px;

 ppy = py;

 //calcular as novas posições do circulo:

 // cos/sin(angulo) * raio + centro

 px = cos(angulo) * raio + width/2;

 py = sin(angulo) * raio + height/2;

 

 stroke(255,100);

 line (px,py,ppx,ppy);

 

}

 

 

neste programa mantemos a existência do círculo com as duas variáveis polares, e de seguida transcrevemos para 4 variáveis cartesianas, onde primeiro se guarda uma cópia dos valores anteriores de x e y (para podermos ter os valores anteriores), e de seguida se aplicam as fórmulas de conversão polar->cartesiana para descobrirmos a localização dos novos pontos cartesianos de um círculo.

 

 

1. máquinas de desenhar, parte 1

 

a primeira parte envolveu a criação de um sketch de processing que apenas desenhasse linhas para o ecran, sem nunca apagar as frames anteriores (a não ser que queiram), e que essas linhas apenas fossem desenhadas quando se prime o rato, e que a velocidade com que se desloca o rato também fosse tornada uma variável que influencia coisas no nosso programa. ora esta variável booleana, mousePressed, já existe a priori, (tal como mouseX, mouseY, e pmouseX, pmouseY) e pode ter os valores de verdade ou falso, consoante se carrega no rato ou não, o que a torna extremamente indicada para ser condição de controlo de um bloco de código a executar apenas se o rato estiver premido.

 

if (mousePressed) {

//executa-se apenas se o rato estiver pressionado

}

 

se quisermos executar código quando o rato não está premido temos de negar a variável boleana, o que se faz através do operador !, ficando if (!mousePressed), que se lê if not mouse pressed, e tem mesmo esse comportamento.

isto permite-nos acumular linhas no framebuffer, ou seja, desenhar numa tela digital, como num papel.

desenhar-0-0971 

através das restantes variáveis do rato que temos, coordenadas actuais e anteriores, conseguimos além de detectar em que ponto xy o rato se encontra, a velocidade relativa que o rato tem, subtraindo da coordenada anterior a coordenada actual. isto tem uma certa lógica, que tem a ver com a operação simples de encontrar as coordenadas de um vector entre dois pontos, que vamos estar a usar recorrentemente nos programas gráficos e que se define da seguinte forma: o vector de O a P corresponde a P menos O. isto em termos matemáticos, cada ponto tem uma coordenada x,y, corresponde à seguinte expressão:

 

vector_de_O_a_P.x = P.x – O.x;

vector_de_O_a_P.y = P.y – O.y;

 

a coordenada x do vector_de_O_a_P vai armazenar a diferença que existe entre as duas coordenadas x dos dois pontos a comparar, com sentidos positivos ou negativos, e a mesma coisa acontece com a componente y do vector. 

 

a distância que irá de O a P corresponde à magnitude deste vector, ou ao encontrar o raio que vai entre estes dois pontos, sendo que um deles é o centro, o outro o ponto periférico inserido no círculo, o que corresponde, como começamos a ver no início da aula, à conversão de coordenadas cartesianas para polares.

 

distância_de_O_a_P = √ (vector_de_O_a_P.x ^2  + vector_de_O_a_P.y ^2 );

 

se esta distância for grande, entre o rato anterior e o rato actual, significa que a velocidade do rato é grande, se a distância for pequena, significa que os pontos estão próximos.

 

importa ter tanto o vector de O a P, como a distância de O a P, porque o vector tem um sinal, + ou -, que servirá para indicar direcções a seguir, como vamos ver na construção do mini-jogo mais à frente, no ponto 2.

 

com estas informações base, coordenadas actuais e anteriores do rato, conseguimos detectar ângulos e distâncias entre pontos, que são parâmetros óptimos para utilizar na construção de agentes algorítmicos autónomos que nos auxiliam a generar (de generativo) desenhos, onde nós influenciamos e o programa também, criando uma relação quase simbiótica entre homem e máquina ( 🙂 num futuro longíquo, talvez….)

 
desenhar-0-5120
 

assim, começamos a criar as nossas primeiras máquinas de desenhar à base de rato. os primeiros sketches usam linhas, e depois criamos agentes autónomos que se nascem das coordenadas actuais do rato e evoluem em formas espiraladas, cujo comportamento vai decaindo em longo do tempo (através da variável energia), até que se encontra numa situação que já não desenha e já não consome recursos do cpu. a seccção mais importante dos Scriblers  que começamos a criar corresponde à execução da função draw, que é sempre chamada da draw principal do nosso programa, mas que só é executada quando a energia é superior a 0. o código é assim:

 

  void draw(){

    if(energy>0.){

      update();

      stroke(0, energy*10 + 20); 

      line(ppx,ppy, px,py);

    }

  }

 

e onde a função update faz um decaimento automático da energia para que a forma não fique sempre a desenhar ad infinitum na tela.

 

 

  void update(){

 

      energy -= 1;//0.5; 

 

      //update angle

       angle += side * curv;

 

      // diminuir o raio do angulo

      magnitude *= 0.9; // este parâmetro controla o decaimento do raio

 

      // novas coordenadas do circulo

     float  cx = cos(angle)*magnitude;

     float  cy = sin(angle)*magnitude;

 

 

      ppx = px;

      ppy = py;

 

      px = px + cx;

      py = py + cy;

 

 

 

  }

 

 

fizémos primeiro uma máquina com 1 escriva, e depois com N escrivas simultâneos, onde a lógica é sempre a mesma, seguimos programação com objectos, e criamos uma array com N objectos e todos eles podem estar activos simultaneamente na mesma frame, o que torna os resultados mais apetitosos e complexos.

 

em vez de termos 

 

Scribler escriva;

 

temos

 

Scribler escrivas[];

 

e na inicialização:

 

  escrivas = new Scribler[10];

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

    escrivas[i] = new Scribler();

 

no draw:

 

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

    escrivas[i].draw();

 

e na instância de novos escrivas automáticos:

 

    ///onde se activa o escriva..

    if(random(1)<0.2)

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

        if(escrivas[i].energy <= 0.){

          escrivas[i] = new Scribler(mouseX,mouseY,(mouseX-pmouseX),(mouseY-pmouseY),d);

          break; // get out of the for loop if this condition is true

        }

      }

 

 desenhar-2a-1826

os desenhos são criados dependendo da nossa intervenção mas também da intervenção do programa. durante a sessão lancei um exercício que correspondia a alterar as cores de fundo, os parâmetros da curvatura, e a homogeneização das cores de escrita das linhas, quer dos escrivas, quer da linha por nós desenhada com o rato. as imagens que têm nos posts que antecedem e sucedem este são alguns exemplos disso, e observem bem as grandes diferenças que existem entre maior parte dos desenhos feitos por vocês, todos eles têm o mesmo código base correspondente, mas depois cada um de vocês fez coisas diferentes, o que ilustra bem que receitas semelhantes levam a resultados muito distintos.

 

 

 

2. máquinas jogáveis, parte 1

 

esta segunda parte da aula foi em torno da criação de um mini-jogo, a partir do sketch dos circulos que vimos na primeira sessão.

 

o primeiro passo foi criar uma array de circulos_maus, e um circulo_eu, controlado pelo rato. depois alteramos a forma do movimento dos circulos maus, em vez de serem controlados com um filtro low pass, binarizamos o movimento, aplicacando movimento apenas no eixo x ou y, consoante a distância em relação ao circulo_eu seja maior ou menor. o código que define isso está dentro da class do circulo, na função go_mouse:

 

 

  void go_mouse(){

      // agora os circulos vão sempre ter com o do rato

      // mas movem apenas num eixo, onde a dist for maior

 

      // 1. calcular distâncias 

      float distx = mouseX – posx;

      float disty = mouseY – posy;

      boolean axis = ( abs(distx) > abs(disty) );

 

      if(axis==true) { // a distância do eixo x é maior, então anda ao longo do x

        float sign = ( distx > 0 ) ? 1. : -1. ; // ciclo if abreviado

        posx += sign * force;        

      } else {

        float sign = ( disty > 0 ) ? 1. : -1. ; // ciclo if abreviado

        posy = posy + sign * force;        

 

      }

 

  }

 

 

com o movimento mais interessante, faltava agora um passo em que temos de saber se os círculos se tocam ou não, e isso é realizado com uma detecção de colisão circulo-circulo simples, onde se a distância entre os centros do circulo forem inferiores à soma dos raios de cada círculo, então sabemos que eles se tocam. este é o passo fundamental, pois é a partir deste passo que depois criamos a mecânica do jogo, e que temos conhecimento binário se há círculos a tocarem no nosso círculo herói, e podemos construir toda a mecânica de jogo em torno deste conhecimento.

 

para detectar a distância entre dois pontos, temos a conversão coordenadas cartesianas->polares que começamos por analisar nesta sessão, e que nos vai acompanhar para muitas outras sessões. 

 

para que isto aconteça, bastou criar mais uns campos variáveis na nossa classe Circulo:

 

class Circulo {

 

  float posx, posy, rad;  

  float force; //uma variável para a força

 

  boolean touch=false; // a variável que determina se toca no heroi

  color cnotouch = color(82,247,195); // a cor de nao tocar

  color ctouch = color(222,247,57); // a cor de tocar

(…)

 

 

e alterar a função go_mouse(), para que altere as posições e verifique se a distância do circulo mau em questão está a tocar ou não no circulo_eu.

 

 

  void go_mouse(){

      //esta função tb calcula se os circulos tocam o principal…

 

    // agora os circulos vão sempre ter com o do rato

      // mas movem apenas num eixo, onde a dist for maior

 

      // 1. calcular distâncias 

      float distx = mouseX – posx;

      float disty = mouseY – posy;

      boolean axis = ( abs(distx) > abs(disty) );

 

 

      if(axis==true) { // a distância do eixo x é maior, então anda ao longo do x

        float sign = ( distx > 0 ) ? 1. : -1. ; // ciclo if abreviado

        posx += sign * force;        

      } else {

        float sign = ( disty > 0 ) ? 1. : -1. ; // ciclo if abreviado

        posy = posy + sign * force;        

 

      }

 

      // 2. colisão com o circulo principal

 

      float sumrad = rad/2.0f + circulo_eu.rad/2.0f;

      distx = circulo_eu.posx – posx;

      disty = circulo_eu.posy – posy;      

      float d = sqrt(distx*distx + disty*disty);  // calcular a distância entre os círculos

 

      if( d <= sumrad  ) 

          touch=true;

        else

          touch=false;      

 

  }

 

 

e usa-se agora o conhecimento da variável touch quando se desenha o círculo:

 

  void draw(){

 

    stroke(0);

    if(touch)

      fill(ctouch);

    else

      fill(cnotouch);

 

    ellipse(posx,posy,rad,rad);

 

  }

 

 

conseguindo agora saber quando os circulos maus tocam o circulo_eu, podemos criar o mini-jogo. o objectivo será fugir dos maus x tempo, até que o score chegue a 1000. se chegar a 1000, ganhámos, se formos tocando nos círculos maus, o score vai diminuido, e se passar -100 negativos, perdemos.

 

para criar esta mecânica, basta inserir mais umas variáveis no programa principal:

 

///

int score=0; // o score em torno do qual a mecânica é construida

PFont font; // a fonte usada para desenhar informações no ecran

float eficiencia=0.0f; // um valor de eficiencia (que pode ser traduzido em perfeito, bom, razoável, etc)

boolean pausa = false; // a variável que controla se se joga ou se se está no modo que informa que ganhámos ou perdemos

int timereset=180; //3*60 // uma variável que controla o tempo que se está no modo de pausa

 

 

e a mecânica é criada com os seguintes passos:

 

o loop principal do programa agora é precedido da bolean se não está em pausa, executa o jogo, se está em pausa, diz se ganhámos ou perdemos. e a análise contínua do valor do score é que controla a passagem para o modo de pausa, onde se o score é > 1000 ou <-100, passamos para o modo de pausa. saímos do modo de pausa ao fim de x frames, e recomeçamos o jogo com outros parâmetros.

 

aqui fica o loop principal do jogo que traduz estas informações e umas imagens.

 

void draw(){

 

  //  background (58,95,83);

 

  if(!pausa) {

 

    fill (58,95,83  ,  25);

    noStroke();

    rect(0,0,width,height);

 

 

 

    circulo_eu.setPos(mouseX,mouseY);

    circulo_eu.draw();

 

    for(int i=0; i<NUM_CIRCULOS; i++) { 

      circulos_maus[i].go_mouse();

      circulos_maus[i].draw(); 

    }

 

 

    if(frameCount%200==0){

      for(int i=0; i<NUM_CIRCULOS; i++) { 

        circulos_maus[i].reset();

      }

 

    }

 

 

 

    //tratar do score

    score++;

 

    for(int i=0; i<NUM_CIRCULOS; i++) { 

      if (circulos_maus[i].touch)

        score = score – 5;

    }

 

    textFont(font,12);

    fill(230);

    text(“score “+score, 1, height-10);

 

    //entrar no modo de pausa

    if(score > 1000 || score < -100) {

      pausa = true;

      timereset = frameCount + 180;

      eficiencia = (float)score / (float) frameCount;

    }

 

 

  } 

  else { // if pausa (significa ou gameover ou parabéns

 

    boolean win = (score > 1000);

    textFont(font,25);

    fill(230);

 

     text(“circulos attack!!”,50,50); 

 

    if(win) {

      text(“PARABÉNS! “+eficiencia,50,200); 

    } 

    else {

      text(“oooh! “+eficiencia,50,200); 

    }

 

    //reset do jogo

    if(frameCount>timereset){

      pausa = false;

      score = 0;

      frameCount = 0;

      if(win)      

        NUM_CIRCULOS += 5; //HAHAHAHA

      else

        NUM_CIRCULOS –;

 

       if (NUM_CIRCULOS < 2)

         NUM_CIRCULOS = 10;

 

      circulos_maus = new Circulo[NUM_CIRCULOS];

      for(int i=0; i<NUM_CIRCULOS; i++) { 

        circulos_maus[i] = new Circulo();

      }

 

    }

 

  }

 

 

}

 

circulos-attack4-0211

circulos-attack4-0461

circulos-attack4-1074

circulos-attack4-1438

 

até para a semana. vamos continuar a abordar máquinas de desenhar e a criação deste género de ambientes jogáveis, até que consigam entender bem como se inter-manipulam variáveis, e como podemos ir apanhar parâmetros e transcrevê-los para outros universos.

Anúncios

2 Trackbacks/Pingbacks

  1. By sessão 8 « O Som do Pensamento on 12 Mar 2009 at 3:57 pm

    […] parte da sessão de hoje, abordámos o sistema esférico. é igual ao polar, que já vimos na segunda sessão, só que agora introduzimos a terceira dimensão z, e passamos as coordenadas cartesianas (x,y,z) […]

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

    […] primeiro as noções iniciais de programação exploradas em máquinas de desenhar, onde a posição do rato serve como foco para o lançamento de novos objectos de classes programadas por nós. o sistema de coordenadas cartesiano e o sistema de coordenadas polar. (sessão 2) […]

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: