Código bonito é uma alegria para escrever, mas é difícil compartilhar essa alegria com outros programadores, sem mencionar com os não-programadores. No meu tempo livre entre o meu dia de trabalho e tempo de família eu tenho brincado com a idéia de um poema de programação usando o elemento de tela para desenhar no navegador. Há uma infinidade de termos lá fora para descrever experimentos visuais no computador, como dev art, sketch de código, demo e arte interativa, mas , no final das contas, decidi por um poema de programação para descrever esse processo. A ideia por trás de um poema é uma peça polida de prosa que é facilmente compartilhável, concisa e estética. Não é uma ideia incompleta em um caderno de esboços, mas uma peça coesa é apresentada ao espectador para sua diversão. Um poema não é uma ferramenta, mas existe para evocar uma emoção.
Para meu próprio prazer, tenho lido livros sobre matemática, computação, física e biologia. Eu aprendi muito rapidamente que quando eu divagar sobre uma idéia, isso aborrece as pessoas rapidamente. Visualmente, posso pegar algumas dessas idéias que considero fascinantes e rapidamente dar a qualquer um a sensação de admiração, mesmo que não entendam a teoria por trás do código e dos conceitos que a impulsionam. Você não precisa lidar com nenhuma filosofia ou matemática difícil para escrever um poema de programação, apenas um desejo de ver algo ao vivo e respirar na tela.
O código e os exemplos que eu apresento abaixo irão dar um passo em frente na compreensão de como realmente realizar esse processo rápido e altamente satisfatório. Se você gostaria de seguir junto com o código que você pode baixe os arquivos de origem aqui.
O principal truque ao criar um poema é mantê-lo leve e simples. Não gaste três meses fazendo uma demonstração muito legal. Em vez disso, crie 10 poemas que evoluem uma ideia. Escreva um código experimental que seja excitante e não tenha medo de falhar.
Para uma rápida visão geral, a tela é essencialmente um elemento de imagem bitmap 2d que vive no DOM que pode ser desenhado para. O desenho pode ser feito usando um contexto 2d ou um contexto WebGL. O contexto é o objeto JavaScript que você usa para obter acesso às ferramentas de desenho. Os eventos JavaScript que estão disponíveis para a tela são muito barebones, ao contrário dos disponíveis para o SVG. Qualquer evento que é acionado é para o elemento como um todo, não qualquer coisa desenhada na tela, como um elemento de imagem normal. Aqui está um exemplo básico de tela:
var canvas = document.getElementById('example-canvas');var context = canvas.getContext('2d');//Draw a blue rectanglecontext.fillStyle = '#91C0FF';context.fillRect(100, // x100, // y400, // width200 // height);//Draw some textcontext.fillStyle = '#333';context.font = "18px Helvetica, Arial";context.textAlign = 'center';context.fillText("The wonderful world of canvas", // text300, // x200 // y);
É bastante simples de começar. A única coisa que pode ser um pouco confusa é que o contexto deve ser configurado com as configurações fillStyle, lineWidth, font e strokeStyle antes que a chamada de desenho real seja usada. É fácil esquecer de atualizar ou redefinir essas configurações e obter alguns resultados indesejados.
O primeiro exemplo foi executado apenas uma vez e desenhou uma imagem estática na tela. Tudo bem, mas quando realmente fica divertido é quando é atualizado a 60 quadros por segundo. Os navegadores modernos têm a função interna requestAnimationFrame que sincroniza o código de desenho personalizado para os ciclos de desenho do navegador. Isso ajuda em termos de eficiência e suavidade. O alvo de uma visualização deve ser o código que cantarola a 60 quadros por segundo.
(Uma nota sobre o suporte: há alguns polyfills simples disponíveis se você precisar dar suporte a navegadores mais antigos).
var canvas = document.getElementById('example-canvas');var context = canvas.getContext('2d');var counter = 0;var rectWidth = 40;var rectHeight = 40;var xMovement;//Place rectangle in the middle of the screenvar y = ( canvas.height / 2 ) - ( rectHeight / 2 );context.fillStyle = '#91C0FF';function draw() {//There are smarter ways to increment time, but this is for demonstration purposescounter++;//Cool math below. More explanation in the text following the code.xMovement = Math.sin(counter / 25) * canvas.width * 0.4 + canvas.width / 2 - rectWidth / 2;//Clear the previous drawing resultscontext.clearRect(0, 0, canvas.width, canvas.height);//Actually draw on the canvascontext.fillRect(xMovement,y,rectWidth,rectHeight);//Request once a new animation frame is available to call this function againrequestAnimationFrame( draw );}draw();
Agora vou reescrever minha fórmula do exemplo de código anterior como uma versão mais detalhada e mais fácil de ler.
var a = 1 / 25, //Make the oscillation happen a lot slowerx = counter, //Move along the graph a little bit each time draw() is calledb = 0, //No need to adjust the graph up or downc = width * 0.4, //Make the oscillation as wide as a little less than half the canvasd = canvas.width / 2 - rectWidth / 2; //Tweak the position of the rectangle to be centeredxMovement = Math.sin( a * x + b ) * c + d;
Se você quiser brincar com o código até o momento, sugiro adicionar algum movimento na direção y. Tente mudar os valores na função sin, ou mude para outro tipo de função para brincar e ver o que acontece.
Além de dirigir o movimento com a matemática, tire um momento para imaginar o que você pode fazer com diferentes dispositivos de entrada do usuário para mover um quadrado ao redor de uma página. Existem todos os tipos de opções disponíveis no navegador, incluindo o microfone, webcam, mouse, teclado e gamepad. Opções adicionais baseadas em plugins estão disponíveis com algo como o Leap Motion ou o Kinect. Usando WebSockets e um servidor, você pode conectar uma visualização a um hardware caseiro. Conecte um microfone à Web Audio API e dirija seus pixels com som. Você pode até mesmo construir um sensor de movimento de uma webcam e assustar uma escola de peixes virtuais (ok, eu fiz o último em Flash cinco anos atrás).
Então agora que você tem a sua grande ideia, vamos voltar para mais alguns exemplos. Um quadrado é chato, vamos subir a aposta. Primeiramente, vamos criar uma função quadrada que pode fazer muito. Nós vamos chamá-lo de um ponto. Uma coisa que ajuda quando se trabalha com objetos em movimento é usar vetores ao invés de variáveis x e y separadas. Nestas amostras de código eu obtive a classe Vector.js de três.js. É fácil de usar imediatamente com vector.x e vector.y, mas também tem vários métodos úteis para trabalhar com eles. Dê uma olhada os docs para um mergulho mais profundo.
O código deste exemplo fica um pouco mais complexo porque está interagindo com objetos, mas valerá a pena. Confira o código de exemplo para ver um novo objeto Scene que gerencia os fundamentos do desenho para a tela. Nossa nova classe Dot terá uma alça para esta cena para acessar quaisquer variáveis como o contexto da tela que será necessário.
function Dot( x, y, scene ) {var speed = 0.5;this.color = '#000000';this.size = 10;this.position = new THREE.Vector2(x,y);this.direction = new THREE.Vector2(speed * Math.random() - speed / 2,speed * Math.random() - speed / 2);this.scene = scene;}
Para começar, o construtor do ponto configura a configuração de seu comportamento e define algumas variáveis a serem usadas. Novamente, isso está usando a classe vetorial three.js. Ao renderizar a 60fps, é importante pré-inicializar seus objetos e não criar novos enquanto estiver animando. Isso consome sua memória disponível e pode deixar sua visualização instável. Além disso, observe como o ponto é passado uma cópia da cena por referência. Isso mantém as coisas limpas.
Dot.prototype = {update : function() {...},draw : function() {...}}
Todo o restante do código será definido no objeto de protótipo do ponto, de modo que cada novo ponto criado tenha acesso a esses métodos. Eu irei função por função na explicação.
update : function( dt ) {this.updatePosition( dt );this.draw( dt );},
Estou separando meu código de desenho do meu código de atualização. Isso torna muito mais fácil manter e ajustar seu objeto, da mesma forma que o padrão MVC separa o controle e a lógica de visualização. A variável dt é a mudança no tempo em milissegundos desde a última chamada de atualização. O nome é legal e curto e vem de derivados de cálculo (não se assuste). O que isso faz é separar seu movimento da velocidade da taxa de quadros. Dessa forma, você não obtém lentidão no estilo NES quando as coisas ficam muito complicadas. Seu movimento derrubará quadros se estiver trabalhando duro, mas permanecerá na mesma velocidade.
updatePosition : function() {//This is a little trick to create a variable outside of the render loop//It's expensive to allocate memory inside of the loop.//The variable is only accessible to the function below.var moveDistance = new THREE.Vector2();//This is the actual functionreturn function( dt ) {moveDistance.copy( this.direction );moveDistance.multiplyScalar( dt );this.position.add( moveDistance );//Keep the dot on the screenthis.position.x = (this.position.x + this.scene.canvas.width) % this.scene.canvas.width;this.position.y = (this.position.y + this.scene.canvas.height) % this.scene.canvas.height;}}(), //Note that this function is immediately executed and returns a different function
Esta função é um pouco estranha em sua estrutura, mas útil para visualizações. É muito caro alocar memória em uma função. A variável moveDistance é definida uma vez e reutilizada sempre que a função é chamada.
Esse vetor é usado apenas para ajudar a calcular a nova posição, mas não é usado fora da função. Esta é a primeira matemática vetor que está sendo usada. Neste momento o vetor de direção está sendo multiplicado contra a mudança no tempo, então adicionado à posição. No final, há uma pequena ação de módulo para manter o ponto na tela.
draw : function(dt) {//Get a short variable name for conveniencevar ctx = this.scene.context;ctx.beginPath();ctx.fillStyle = this.color;ctx.fillRect(this.position.x, this.position.y, this.size, this.size);}
Finalmente as coisas fáceis. Obtenha uma cópia do contexto do objeto de cena e desenhe um retângulo (ou o que você quiser). Retângulos são provavelmente a coisa mais rápida que você pode desenhar na tela.
Neste ponto eu adiciono um novo Dot chamando this.dot = new Dot (x, y, isto) no construtor de cena principal, e então no método de atualização de cena eu adiciono um this.dot.update (dt) e existe um ponto de zoom ao redor da tela. (Confira o código fonte para o código completo no contexto.)
Agora, na cena, em vez de criar e atualizar um ponto , criamos e atualizamos o DotManager . Vamos criar 5000 pontos para começar.
function Scene() {...this.dotManager = new DotManager(5000, this);...};Scene.prototype = {...update : function( dt ) {this.dotManager.update( dt );}...};
É um pouco confuso em uma linha, então aqui está quebrado como a função do pecado de antes.
var a = 1 / 500, //Make the oscillation happen a lot slowerx = this.scene.currTime, //Move along the graph a little bit each time draw() is calledb = this.position.x / this.scene.canvas.width * 4, //No need to adjust the graph up or downc = 20, //Make the oscillation as wide as a little less than half the canvasd = 0; //Tweak the position of the rectangle to be centeredxMovement = Math.sin( a * x + b ) * c + d;
Obtendo groovy ...
Mais um pequeno ajuste. Monocromático é um pouco monótono, então vamos adicionar um pouco de cor.
var hue = this.position.x / this.scene.canvas.width * 360;this.color = Utils.hslToFillStyle(hue, 50, 50, 0.5);
Esse objeto simples encapsula a lógica das atualizações do mouse do resto da cena. Apenas atualiza o vetor de posição em um movimento do mouse. O restante dos objetos pode, então, fazer uma amostra do vetor de posição do mouse, se eles tiverem passado por uma referência ao objeto. Uma ressalva que estou ignorando aqui é se a largura da tela não é uma para uma com as dimensões em pixels do DOM, ou seja, uma tela redimensionada responsavelmente ou uma tela de densidade de pixels (retina) maior ou se a tela não estiver localizada na tela canto superior esquerdo. As coordenadas do mouse precisarão ser ajustadas de acordo.
var Scene = function() {...this.mouse = new Mouse( this );...};
A única coisa que restou para o mouse foi criar o objeto mouse dentro da cena. Agora que temos um mouse, vamos atrair os pontos para ele.
function Dot( x, y, scene ) {...this.attractSpeed = 1000 * Math.random() + 500;this.attractDistance = (150 * Math.random()) + 180;...}
Eu adicionei alguns valores escalares ao ponto para que cada um se comporte um pouco diferente na simulação para dar um pouco de realismo. Brinque com esses valores para obter uma sensação diferente. Agora, para atrair o método do mouse. É um pouco longo com os comentários.
attractMouse : function() {//Again, create some private variables for this methodvar vectorToMouse = new THREE.Vector2(),vectorToMove = new THREE.Vector2();//This is the actual public methodreturn function(dt) {var distanceToMouse, distanceToMove;//Get a vector that represents the x and y distance from the dot to the mouse//Check out the three.js documentation for more information on how these vectors workvectorToMouse.copy( this.scene.mouse.position ).sub( this.position );//Get the distance to the mouse from the vectordistanceToMouse = vectorToMouse.length();//Use the individual scalar values for the dot to adjust the distance movedmoveLength = dt * (this.attractDistance - distanceToMouse) / this.attractSpeed;//Only move the dot if it's being attractedif( moveLength > 0 ) {//Resize the vector to the mouse to the desired move lengthvectorToMove.copy( vectorToMouse ).divideScalar( distanceToMouse ).multiplyScalar( moveLength );//Go ahead and add it to the current position now, rather than in the draw callthis.position.add(vectorToMove);}};}()
Esse método pode ser um pouco confuso se você não estiver atualizado sobre sua matemática vetorial. Os vetores podem ser muito visuais e podem ajudar se você desenhar alguns rabiscos em um pedaço de papel manchado de café. Em inglês claro, essa função está obtendo a distância entre o mouse e o ponto. É então mover o ponto um pouco mais perto do ponto baseado em quão próximo já está do ponto e da quantidade de tempo decorrido. Ele faz isso descobrindo a distância a ser movida (um número escalar normal) e multiplicando isso pelo vetor normalizado (um vetor com comprimento 1) do ponto apontando para o mouse. Ok, essa última sentença não era necessariamente simples, mas é um começo.