[ CAPÍTULO 7 ]
Emergência
Causa e efeito

Uma das primeiras series de peças artísticas concebidas com o Processing foi fruto de um de seus fundadores, Casey Reas, que a batizou como Process . Cada uma dessas obras foi individualmente numerada com um código que representava a visualização de um processo diferente. Casey definiu um processo como um ambiente construído para elementos, que determina a maneira da qual suas relações e interações serão visualizadas. Por sua vez, elementos, ou agentes , são sistemas computacionais compostos por uma forma, como um círculo ou ponto, e um ou mais comportamentos, como se movimentar aleatoriamente pela janela de exibição. A intenção de Casey foi mostrar que o comportamento descrito individualmente pelos elementos era simples e desinteressante, mas a interação entre um número elevado de processos e elementos tornava a imagem final altamente imprevisível e única. A isso se dá o nome de emergência , um movimento inesperado para o além daquilo que você pudesse prever que regras simples seriam capazes de criar. Esta filosofia, de que a totalidade do resultado final é mais valorizado do que a contribuição individual, é um dos pontos centrais da linguagem Processing.

Figura 7.1 - Cardume de sardinhasa, cortesia do fotógrafo Mark Harris.
a Todos os direitos reservados pelo artista original.

Uma maneira de entender mais claramente o processo emergente, e como a ação local resulta em uma consequência global, é examinando modelos encontrados na natureza. Imagine um cardume composto por milhares de peixes que se movem como um grande e único ser. O cardume é o comportamento emergente que surge, não de uma coordenação central dos peixes, que neste caso são os agentes, mas sim das regras individuais que cada um deles segue. Podemos rapidamente imaginar que alguns dos objetivos de um peixe são a alimentação, a reprodução e a sobrevivência (escapar de predadores), todos esses tornam-se mais fáceis quando em um grupo. Simultaneamente, os peixes usam a suas percepções (audição, visão, olfato e paladar) para se manter perto de outros peixes, localizar alimentos próximos e evitar colisões. O cardume é formado pela interação de cada um dos elementos que o compõe.

Na computação existem diversas simulações clássicas que abraçam a ideia de emergência no sentido de que o pontual explode em complexidade. Algumas das mais comuns são o jogo da vida de John Conway, a colônia de formigas, a floresta de Reeves e os boids de Reynolds. Todos esses algoritmos são casos clássicos para ilustrar a emergência, seja visando imitar a natureza ou formando um processo puramente sintético. Infelizmente, a maioria dessas simulações pode ser encaixada em duas categorias indesejadas para aqueles iniciantes na computação, seja programador ou artista. Em um extremo elas são fáceis de programar, mas difíceis de se entender o propósito ou a origem do raciocínio, como por exemplo o jogo da vida de Conway. E no espectro oposto elas são fáceis de entender e difíceis de programar, como os boids de Reynolds que imitam a murmuração de pássaros, mas requerem um uso exacerbado de vetores, condicionais, repetições e otimizações locais. Todavia nem tudo está perdido, existe um terceiro método que iremos examinar, mais brando na programação e no entendimento, baseado em Process, figura 7.2, o trabalho citado de Casey Reas.

Figura 7.3 - Process 9 (A) b.
Figura 7.4 - Process 18 (B) b.
Figura - 7.2 - Representações gráficas de processos emergentes: Process. Cortesia do próprio artista, Casey Reas.
b Todos os direitos reservados pelo artista original.

7.1 Agente autônomo

Os principais autores da obra final são formalmente nomeados de agentes autônomos, reforçando a afirmação que eles agem localmente e de acordo com suas próprias intenções. Eles são constituídos por três[1] características principais:

  • Forma : É a representação física do agente. Normalmente é um ponto, linha, círculo ou qualquer outra figura, geométrica ou não.
  • Percepções : É o processo de reconhecimento e interpretação das informações que os agentes têm acesso para tomada de decisões. Por exemplo, um elemento pode verificar se está próximo de outro, ou se está contido dentro de uma região específica do espaço (como um círculo ou quadrado).
  • Comportamentos : São as ações que os agentes são capazes de expressar. Algumas delas podem ser se movimentar pelo espaço, mudar sua direção ou interferir com a trajetória de outros elementos. Aqui também estão contidos os objetivos globais dos agentes, tais como se movimentar aleatoriamente pela janela de exibição ou sempre se aproximar ou afastar do elemento mais próximo a ele.

Do ponto de vista da programação, um agente é composto por múltiplos componentes heterogêneos. Suas formas são representadas por atributos, como posição e tamanho, e suas percepções ou comportamentos particulares podem ser descritos através de funções internas. É interessante que essas características estejam organizadas e acessíveis por agente, tornando-o uma unidade complexa. Felizmente você estudou na seção 2.6 que as classes permitem satisfazer todas essas necessidades ao agrupar diferentes estruturas de programação e produzir objetos completamente individualizados e autocontidos.

Nesta seção você dará vida a uma série de agentes através das instanciações de uma classe geradora. Portanto vamos começar definindo, arbitrariamente, o que iremos programar. Suponha um elemento simplificado cujo único propósito é dar um passo em linha reta, com uma determinada direção, a cada instante de tempo. Simultaneamente ele deve ser capaz de perceber seu ambiente e reajustar sua direção para se manter dentro da janela de exibição. Com base nessa descrição podemos inferir que cada agente possuirá uma posição no espaço, um ângulo de movimentação e um tamanho de passo. Finalmente, iremos adicionar dois métodos de visualização que concederão forma ao agente, um para exibi-lo simplesmente como um ponto e o outro como uma reta, pois assim será possível acompanhar melhor sua trajetória ao longo do tempo. A classe abaixo, que descreve o agente, foi escrita com base nessas premissas e sua explicação é feita no próximo parágrafo.

Código 7.1 - Classe Agente -

class Agente {

// Atributos:
float posXA, posYA, posX, posY;
float angulo;
float passo;

// Construtor:
Agente(float _posX, float _posY, float _angulo, float _passo) {
posX = _posX;
posXA = posX;
posY = _posY;
posYA = posY;
angulo = _angulo;
passo = _passo;
}

// Métodos:

// Atualiza a posição espacial do agente:
void atualizar() {
// Grava a posição anterior:
posXA = posX;
posYA = posY;
// Atualiza a posição atual:
posX = posX + passo * cos(radians(angulo));
posY = posY + passo * sin(radians(angulo));
}

// Forma do agente - Ponto
void exibirComoPonto() {
stroke(0);
strokeWeight(2);
point(posX, posY);
}

// Forma do agente - Reta
void exibirComoReta() {
stroke(0);
strokeWeight(1);
line(posXA, posYA, posX, posY);
}
}

O construtor do agente recebe as informações essenciais para a definição mínima do mesmo: uma posição inicial na janela de saída, um ângulo inicial, que representa a direção de sua trajetória, e o tamanho do passo, que especifica o quanto efetivamente de espaço o agente irá percorrer ao se movimentar a cada quadro.

O deslocamento do agente é feito através da função atualizar() que recalcula sua posição atual de acordo com sua direção e tamanho de passo. O movimento de elementos na computação é derivado da física e formalizado através de vetores[2], mas este assunto é extenso e relativamente técnico, não sendo detalhado neste livro. Em vez disso existe uma maneira mais intuitiva de entender o movimento dos agentes usando as versáteis equações do círculo, 4.1, obtidas na seção de trigonometria. Para isso imagine o agente como sendo o ponto central de um círculo. O raio desse círculo é justamente a variável passo do agente, e a direção dele é o ângulo do arco, variável angulo. Desta forma, o ponto que está na borda do círculo é a posição futura do agente após ele dar um "passo", e é obtida usando as fórmulas citadas. Toda vez que a posição é atualizada, o ponto futuro se torna o ponto presente e o círculo é deslocado para esse novo ponto, permitindo obter a próxima posição da mesma maneira. As figuras 7.5 e 7.6 ilustram essa abordagem.

Figura 7.5 - Movimentação do agente no espaço.
Figura 7.6 - Movimentação do agente ao longo dos quadros.

As outras duas funções da classe Agente, exibirComoPonto() e exibirComoReta() , são responsáveis pelas suas formas. A exibição através de um ponto necessita apenas da posição atual (posX e posY), enquanto a que desenha uma reta necessita da posição atual e também da anterior (posXA e posYA). Não se esqueça de copiar todo o código 7.1 da classe para dentro do editor de texto do Processing.

Finalizada a análise da classe, podemos nos dedicar ao programa principal. Para popular o seu processo você pode criar um vetor do tipo Agente e adicionar um número qualquer de elementos a ele. Consequentemente o que resta escrever no código são as chamadas as rotinas para exibir cada um dos agentes e atualizar suas posições a cada quadro. Ambas devem ser feitas dentro da função draw() para que o movimento seja animado, veja o código abaixo:

Código 7.2 - Agentes autônomos na janela de exibição -

Agente[] ags;

void setup() {
size(300, 200);
background(255);

int numAgentes = 10;
ags = new Agente[numAgentes];

for (int i = 0; i < numAgentes; i++) {
// Cria e distribui os agentes aleatoriamente pela tela:
ags[i] = new Agente(random(width), random(height), random(360), 2);
}
}

void draw() {
for (int i = 0; i < ags.length; i++) {
// Para cada agente declarado, atualiza e exibe ele:
ags[i].atualizar();
ags[i].exibirComoReta();
}
}
Figura 7.7 - Movimentação de dez agentes.

Ao executar o código proposto você deve ter visto algo parecido com a figura 7.7 [3], confirmando que o agente está fazendo aquilo que você programou ele para fazer: andar em linha reta. Entretanto, perceba que o agente se locomove independentemente da janela de exibição e rapidamente sai do nosso campo de visão. Nossa próxima meta é impedir que isso aconteça.

Vamos começar entendendo melhor o espaço de restrição do agente que em nosso caso é a janela do Processing, ilustrada na figura 7.8. Lembre-se que é você quem define o tamanho da tela através da função size() e, portanto, possui exatamente as coordenadas e dimensões da mesma. Esses dados são suficientes para que você programe o agente de forma que ele detecte esses limites e não se mova além deles.

Figura 7.8 - Janela de exibição e seus limites.

Note que o agente está em um plano bidimensional e sua posição é definida por uma parcela horizontal e uma vertical, sendo possível saber se ele está dentro ou fora da janela usando suas coordenadas. Se a posição horizontal do agente for menor que zero ou maior que o comprimento da tela (width), ou então se sua posição vertical for menor que zero ou maior que a altura da tela (height), o agente estará fora da janela de exibição e do nosso campo de visão, veja a figura 7.9.

O próprio agente deve ser capaz de perceber o espaço em que se encontra, entender se sair dele e tomar as devidas ações para contornar esse evento. Ele pode, por exemplo, retornar para os limites da janela de exibição e alterar o ângulo da sua direção, de forma a evitar o movimento de saída da zona restrita. A maneira fisicamente correta de mudar o ângulo de um objeto qualquer quando ele atinge uma superfície é criando uma simetria de ângulo de incidência (φi<\sub>) e reflexão ($\phi_{r}$), mas para contribuir com a parcela gerativa de nossa obra iremos adicionar o caos e fazer com que esse ângulo varie aleatoriamente. Mesmo que tal fato não corresponda à realidade, ele faz parte do processo da experimentação e podemos criar as regras que quisermos. A figura 7.10 ilustra esse conceito.

Figura 7.9 - Posições fora do limite da janela.
Figura 7.11 - Segundo a Lei da Reflexão.
Figura 7.12 - Aleatoriamente.
Figura - 7.10 - Possíveis ângulos de reflexão.

Veja que você está adicionando mais um comportamento a esse agente, em que ele verifica se está fora da janela de exibição e, se estiver, retorna para uma posição dentro dela e depois altera sua direção. Isso corresponde a um novo método a ser adicionado na classe Agente, que pode ser visto no código a seguir:

Código 7.3 -

void detectaBordas() {
// Se o agente sair da região de limite, altera sua direção de movimento:
if (posX < 0 || posX > width || posY < 0 || posY > height) {
angulo = random(360);
}

// Se o agente sair da região de limite, ele volta para a mesma:
if (posX < 0) {
posX = 0;
}
else {
if (posX > width) {
posX = width;
}
}
if (posY < 0) {
posY = 0;
}
else {
if (posY > height) {
posY = height;
}
}
}

Agora, além de chamar as funções atualizar() e exibirComoReta() você tem que fazer com que o agente teste sua posição chamado o método detectaBordas().

Código 7.4 -

for(int i = 0; i < ags.length; i++) {
ags[i].detectaBordas();
ags[i].atualizar();
ags[i].exibirComoReta();
}

Ao executar o código após essas alterações, você verá que o agente se limita a ficar sempre dentro da janela de exibição, criando um padrão menos previsível, como o da figura 7.13. O seu agente autônomo está completo, possuindo uma forma de linha, e dotado de percepções e comportamentos, que o impele a sempre andar em linha reta e permanecer no campo de visão do programador.

Figura 7.13 - Agentes após as alterações nos seus comportamentos.

7.2 Processo

Muito bem, uma vez definidas as propriedades dos seus agentes, você pode focar na concepção de um processo. Isso naturalmente invocará uma pergunta fundamental:

Mas afinal, o que exatamente é o "processo" ?

Mesmo que possa parecer muito peculiar, você é livre para responder essa pergunta como quiser, não há uma resposta certa ou errada. O que de fato existe é a sua capacidade criativa de inventar um processo utilizando os agentes, com o propósito singular de explorar os diversos padrões emergentes provenientes das interações entre eles mesmos e o ambiente em que estão inseridos. Esta é a parte subjetiva da computação artística. Por exemplo, você pode desejar visualizar o que acontece quando você desenha um círculo cada vez que dois agentes colidem; ou então desenhar triângulos com vértices nas posições dos agentes sempre que eles se aproximarem uns dos outros; ou quem sabe fazer com que um agente absorva o outro e aumente de tamanho. As possibilidades são infinitas, assim como a quantidade e diversidade de imagens que cada processo é capaz de gerar.

Analisar com detalhes todos os cenários que podem ser idealizados como um processo é matéria para um livro por si só, portanto iremos dissecar apenas um estudo de caso.

Vamos começar com uma ideia simples e abandonar o desenho da trajetória dos agentes, optando por traçar uma conexão entre eles caso os mesmos se encontrem próximos. Perceba que neste exato ponto existe uma interface crucial entre o conceitual e o prático, entre o que você deseja fazer e como fazer isso. Você pode maximizar suas chances de sucesso nessa etapa, e em demais projetos, se focar em três aspectos centrais: estabelecer os dados que você possui, compreender como os mesmos podem ser usados para atingir seus objetivos e, se necessário, implementar novas funcionalidades. Esse fluxo de ação, para o processo de ligar os agentes através de retas, está ilustrado na figura 7.14.

Figura 7.15 - O objetivo final é conectar os agentes.
Figura 7.16 - Informações disponíveis a serem usadas.
Figura - 7.14 - Estratificação do processo para dois agentes.

Note que você tem acesso, ou pode encontrar, todas as variáveis que precisa, basta dividir o problema em etapas. Um último detalhe que deve ser esclarecido é que, apesar da figura 7.14 contemplar apenas dois agentes, na realidade existe um número qualquer de elementos simultaneamente próximos. Desta forma, para cada agente que estiver analisando, você deve comparar com todos os demais e calcular a distância entre eles. Se essa distância for menor que certo valor então você deverá traçar uma reta unindo eles, caso contrário não. Você pode cobrir todos esses casos se utilizar de uma estrutura de repetição aninhada, conforme visto nas seções anteriores. No código abaixo é mostrado como incrementar o loop de atualização dos agentes com uma nova funcionalidade que realiza as comparações de distância entre os agentes e os desenho das retas de conexão:

Código 7.5 -

for(int i = 0; i < ags.length; i++) {
ags[i].detectaBordas();
ags[i].atualizar();
ags[i].exibirComoReta();

float distMaxima = 50;
for(int j = i + 1; j < ags.length; j++) {
// Calcula a distância entre dois agentes:
float distEntreAgentes = dist(ags[i].posX,ags[i].posY,ags[j].posX,ags[j].posY);

// Se a distância entre os agentes for menor que 50 pixels,
// conectamos eles através de uma reta:
if(distEntreAgentes < distMaxima) {
line(ags[i].posX,ags[i].posY,ags[j].posX,ags[j].posY);
}
}
}

Essa solução se resume a ter um olhar puramente matemático, em que os agentes não passam de pontos num espaço bidimensional cuja distância é calculada através da fórmula da distância Euclidiana. No Processing, você pode encontrar a separação entre dois pontos quaisquer através da função dist() que recebe como argumentos as coordenadas x e y de cada um deles.

Fique atento a um outro fator importante, a segunda estrutura de repetição não começa do zero. Isto é feito para otimizar o desenho das linhas que conectam os agentes, evitando desenhar conexões sobrepostas que apenas deixariam sua simulação mais lenta. Tal ajuste permite que o programa se abstenha de desenhar linhas entre os mesmos agentes, por exemplo i = 1 e j = 1, e linhas entre agentes já conectados anteriormente, como i = 1 e j = 2 e, posteriormente, i = 2 e j = 1. Execute o código e veja a concepção visual, figura 7.17, do seu processo.

Figura 7.17 - Processo emergente através da interação entre agentes.

A figura não é representativa no livro uma vez que ela é estática, mas na animação você deve ter percebido que sempre que um ponto se aproxima de outro surge uma linha conectando os dois. Essa é a prova que o seu processo está tecnicamente e funcionalmente perfeito. Observe que não programamos um comportamento emergente em si, já que as decisões tomadas por um agente não sofrem influências dos demais. Todavia, esta é uma visualização emergente, com uma estética particular, que se manifesta progressivamente, fruto das relações imprevisíveis entre os agentes.

7.3 Extrapolação

Os agentes e o processo das seções anteriores naturalmente se fundem em um resultado emergente que é melhor observado ao longo do tempo em vez de instantaneamente. A sua representação visual é revelada quando aumentamos a escala da simulação, tanto referente ao número de elementos quanto ao tempo. Retorne à função setup() e altere os campos mostrados para os valores abaixo:

Código 7.6 -

size(700,200);
int numAgentes = 50;

aproveite e comente a exibição dos agentes por retas:

Código 7.7 -

// ags[i].exibirComoReta();

Desta forma o acúmulo de desenhos gera uma imagem cuja complexidade não está somente ligada ao número de agentes, mas também ao tempo total de execução. Ironicamente, se você permitir que essa simulação execute por muito tempo, irá obter uma imagem que decepciona, algo parecido com um borrão preto, tal como a figura 7.18. A etapa de formatação é mais uma vez essencial para resolver esse problema. O código que você escreveu até agora considera apenas a parte técnica dos agentes e do processo, na qual são usadas as cores e transparências padrões do Processing que estão limitadas à visualização básica das formas através de linhas pretas. Dependendo da complexidade, do número de elementos da simulação e como eles interagem entre si, o resultado não fará jus ao que está realmente acontecendo.

Figura 7.18 - Processo após executar 1000 frames.

Você já aprendeu a apreciar o poder de uma formatação bem pensada e poderá aplicar neste caso para agregar riqueza visual ao seu processo. O principal guia nesta etapa deve ser a criatividade do programador e artista, algo que novamente abre espaço para inúmeras possibilidades, além de ser muito difícil padronizar ou justificar tecnicamente. O que pode, e será feito, é identificar certos estilos de formatação e expor como programá-los usando o Processing.

O primeiro tipo de formatação, a dualidade, é um dos mais simples, e costuma funcionar relativamente bem em processos densos ou com muitos desenhos. Ela consiste em usar formatações que são opostas uma da outra para criar contrastes visuais. Por exemplo, desenhar linhas finas e grossas, pontos grandes e pequenos ou, neste caso em específico, a dualidade de cores. O preto e o branco são as cores extremas mais clássicas, mas você poderia escolher o azul e o vermelho, ou qualquer outra combinação contrastante. Você pode aplicá-la em seu código adicionando linhas que alterem a cor do desenho, internas à estrutura for aninhada responsável pelo desenho das conexões:

Código 7.8 -

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

float distEntreAgentes = dist(ags[i].posX,ags[i].posY,ags[j].posX,ags[j].posY);

if(distEntreAgentes < distMaxima) {
if(random(1) < 0.5) {
stroke(255);
}
else {
stroke(0);
}
line(ags[i].posX,ags[i].posY,ags[j].posX,ags[j].posY);
}
}

Atualmente nosso processo conta com um número relativamente elevado de agentes, determinando que seja usada uma proporção balanceada de cores para as conexões: metade das vezes ela deve ser preta e metade branca. Lembrando que se você quisesse uma maior proporção de linhas brancas (ou pretas), poderia alterar a condição que define a probabilidade desse evento acontecer.

Código 7.9 -

(random(1) < 0.50) // 50% das linhas serão brancas.
(random(1) < 0.60) // 60% das linhas serão brancas.
(random(1) < 0.95) // 95% das linhas serão brancas. etc.

O resultado dessa formatação pode ser visto na figura 7.19. Perceba como a visualização do processo mudou, sendo possível ver reminiscências da posição e trajetória dos agentes. Nada mal, mas você pode deixar a formatação ainda melhor se em vez de usar cores completamente sólidas, você optar por certo grau de transparência e suavidade. Para isso inclua o campo de alfa ao colorir os traços. O resultado é mostrado na figura 7.20.

Código 7.10 -

if(random(1) < 0.5) {
stroke(255,50);
}
else {
stroke(0,50);
}
Figura 7.19 - Dualidade de cores sem transparência.
Figura 7.20 - Dualidade de cores com transparência.

Agora, além do preto e do branco puros, existe o cinza, proveniente da combinação dessas cores. Isso reduz o contraste, mas adiciona uma maior profundidade visual de cores. Você ainda pode impor condições mais complexas de formatação. Por exemplo, quanto mais próximos dois agente estiverem, mais opaca deve ser a cor das linhas de conexão e, quanto mais longe, mais transparente. A função map() é novamente a protagonista para transformar a variável que definirá a formatação, que é a distância entre os elementos, em um valor de transparência.

Código 7.11 -

float alpha = map(distEntreAgentes,0,distMaxima,50,0);

if(random(1) < 0.5) {
stroke(255,alpha);
}
else {
stroke(0,alpha);
}

Ao escrever esse código, você está estabelecendo que a variável distEntreAgentes, que armazena a distância entre dois agentes e cujos valores podem estar entre 0 e distMaxima, agora será transformada em valores no intervalo 50 e 0 e passados para a variável alfa que representa a transparência da formatação. Execute o código modificado e sua janela de exibição se parecerá com a figura 7.21.

Figura 7.21 - Dualidade de cores com transparência em função da distância.

Formatações que não estão ligadas à forma também são válidas no ato de criar padrões visuais, em especial quando se trabalha com sistemas que possuem grandes números de elementos, sejam partículas ou um enxame de agentes autônomos. Uma delas é através do posicionamento inicial determinístico desses pontos no espaço. Em outras palavras, é você quem irá decidir onde os agentes começam em vez de delegar essa tarefa ao acaso. Esse raciocínio parece estar contra o princípio da arte gerativa, uma vez que a aleatoriedade e o ruído foram amplamente defendidos em seções anteriores devido a sua capacidade de emular o orgânico e criar imagens visualmente agradáveis. O grande dilema é que alguns processos podem ser fundamentalmente tão caóticos e carregados que dissolvem ou até mesmo eliminam o natural. Neste caso, possuir alguma figura familiar, como uma reta ou um círculo, contribui para atrair e direcionar o olhar, criando uma impressão de caos controlado, de união da forma e do processo, remetendo a padrões visualmente mais interessantes.

A segunda etapa da formatação de nosso processo será focada no posicionamento iniciais dos agentes. Uma distribuição homogênea de pontos ao longo de uma reta horizontal pode ser feita ao instanciar os elementos da classe Agente sempre com a mesma coordenada y. No entanto esta ação não é suficiente para diminuir o caos uma vez que o ângulo das direções dos agentes ainda será aleatório e, portanto, cabe a você também fixá-lo. Neste exemplo consideramos que o espaço de distribuição é uma reta completamente horizontal com uma angulação de 0°. Você pode criar um efeito dos agentes partindo dessa reta ao designar que eles tenham uma direção perpendicular a ela, o que implica que os ângulos dos agentes devem ser de +90° ou -90°, salvo alguns poucos graus aleatórios para não deixar o processo completamente mecânico. Compilando essa estratégia, você pode alterar a etapa de criação dos agentes no programa, atualizando a função setup() para o código mostrado abaixo. O processo final está ilustrado em 7.22.

Código 7.12 -

void setup() {
size(700, 200);
background(255);

int numAgentes = 100;
ags = new Agente[numAgentes];

for (int i = 0; i < numAgentes; i++) {
// 50% de chance do agente ir para cima ou para baixo.
float angulo;
if (random(1) < 0.5) {
angulo = -90 + random(-10, 10);
}
else {
angulo = 90 + random(-10, 10);
}
ags[i] = new Agente(random(width), height / 2, angulo, 1);
}
}
Figura 7.23 - Passo constante dos agentes.
Figura 7.24 - Passo variável dos agentes.
Figura - 7.22 - Distribuição inicial horizontal dos agentes.

Você também pode experimentar impor uma distribuição circular inicial dos agentes, figura 7.25, obtida através das fórmulas do círculo, equações 4.1. A aplicação simultânea de todas essas técnicas é capaz de combinar os agentes, o processo e a visualização efetiva em incontáveis imagens que possuem o mesmo núcleo gerador, mas diferentes execuções. Na sua forma mais pura, você acabou de criar uma obra artística viva, gerativa e diferente a cada vez que você pressionar o botão para iniciar a simulação.

Figura 7.26 - Passo e posicionamento homogêneos.
Figura 7.27 - Posicionamento em espiral.
Figura 7.28 - Passo aleatório.
Figura 7.29 - Passo e posicionamento aleatórios.
Figura - 7.25 - Distribuição inicial radial dos agentes.
Figura 7.30 - Distribuição circular com angulações desproporcionais.

7.4 Sumário

Neste capítulo foram explorados os conceitos de agentes autônomos e processos. Os agentes são uma estrutura de dados que emulam um ser responsivo dotado de forma, percepções e comportamentos. Eles tomam decisões locais baseadas em outros agentes ou de acordo com o próprio meio em que estão inseridos. Do ponto de vista técnico, você pôde perceber como as classes e objetos são importantes para modelar uma estrutura de dados complexa e unitária como é o caso dos agentes.

Em uma escala macro, as interações entre os agentes e o meio são capazes de gerar um resultado global sem coordenação central, mas organizado e único. A isso se dá o nome de emergência. Neste capítulo você aprendeu como programar, passo a passo, um sistema desse tipo. Você também acompanhou, com detalhes, como efetuar a transformação de ideias abstratas em interações concretas. Por último, você usou a seu favor o caos e as técnicas de formatação para estampar um estilo único ao seu processo.

Figura 7.31 - Agente.
Figura 7.32 - Processo.
Figura 7.33 - Renderização.
Figura - 7.34 - Etapas do processo 000164APR.
Figura 7.35 - Agente.
Figura 7.36 - Processo.
Figura 7.37 - Renderização.
Figura - 7.38 - Etapas do processo b74e10R.

 

 


[1] Em Process Reas define apenas duas características: forma e comportamento. No entanto iremos trabalhar com três, pois a separação entre percepções e comportamentos é mais natural.

[2] Os vetores citados aqui são aqueles descritos na geometria analítica.

[3] O processo ilustrado é o mesmo do código 7.2. Neste livro foram usadas cores apenas para evidenciar os agentes individualmente uma vez que a animação não é possível.