El código hermoso es una alegría para escribir, pero es difícil compartir esa alegría con otros programadores, por no mencionar los que no son programadores. En mi tiempo libre entre mi trabajo diario y mi tiempo familiar, he estado jugando con la idea de un poema de programación usando el elemento canvas para dibujar en el navegador. Hay una multitud de términos para describir experimentos visuales en la computadora como desarrollo de arte, bosquejo de código, demostración y arte interactivo, pero al final me decidí a programar poemas para describir este proceso. La idea detrás de un poema es una pieza de prosa pulida que se puede compartir fácilmente, conciso y estético. No es una idea a medio terminar en un cuaderno de bocetos, sino una pieza cohesiva presentada al espectador para su disfrute. Un poema no es una herramienta, sino que existe para evocar una emoción.

Para mi propio disfrute, he estado leyendo libros sobre matemáticas, computación, física y biología. Aprendí muy rápido que cuando uso una idea, aburre a la gente bastante rápido. Visualmente puedo tomar algunas de estas ideas que me parecen fascinantes, y rápidamente le doy a alguien una sensación de asombro, incluso si no entienden la teoría detrás del código y los conceptos que lo impulsan. No necesita manejar una filosofía o matemática difícil para escribir un poema de programación, solo el deseo de ver algo en vivo y respirar en la pantalla.

El código y los ejemplos que he reunido a continuación iniciarán una comprensión de cómo llevar a cabo este proceso rápido y altamente satisfactorio. Si desea seguir el código, puede descarga los archivos fuente aquí.

El truco principal cuando se crea un poema es mantenerlo ligero y simple. No pases tres meses construyendo una demo realmente genial. En cambio, crea 10 poemas que desarrollen una idea. Escriba código experimental que sea emocionante y no tenga miedo de fallar.

Introducción al lienzo

Para una visión general rápida, el lienzo es esencialmente un elemento de imagen de mapa de bits 2d que vive en DOM que se puede dibujar. El dibujo se puede hacer usando un contexto 2d o un contexto WebGL. El contexto es el objeto JavaScript que usa para acceder a las herramientas de dibujo. Los eventos de JavaScript que están disponibles para el lienzo son muy simples, a diferencia de los disponibles para SVG. Cualquier evento que se desencadena es para el elemento como un todo, no como algo dibujado en el lienzo, como un elemento de imagen normal. Aquí hay un ejemplo básico de lienzo:

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);

Es bastante sencillo comenzar. Lo único que podría ser un poco confuso es que el contexto debe configurarse con las configuraciones como fillStyle, lineWidth, font y strokeStyle antes de que se use la llamada real. Es fácil olvidarse de actualizar o restablecer esas configuraciones y obtener algunos resultados no deseados.

Hacer que las cosas avancen

El primer ejemplo solo se ejecutó una vez y dibujó una imagen estática en el lienzo. Está bien, pero cuando realmente se divierte es cuando se actualiza a 60 cuadros por segundo. Los navegadores modernos tienen incorporada la función requestAnimationFrame que sincroniza el código de dibujo personalizado con los ciclos de dibujo del navegador. Esto ayuda en términos de eficiencia y suavidad. El objetivo de una visualización debe ser un código que vibre a 60 cuadros por segundo.

(Una nota sobre soporte: hay algunos polyfills simples disponibles si necesita admitir navegadores más antiguos).

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();

Es bueno que haya un poco más de estructura interna en el código, pero realmente no hace nada mucho más interesante. Ahí es donde entra un bucle. En el objeto escena crearemos un nuevo objeto DotManager . Es útil recopilar esta funcionalidad en un objeto separado, ya que es más fácil y más limpio razonar a medida que se agrega más y más complejidad a la simulación.

var DotManager = function( numberOfDots, scene ) {this.dots = [];this.numberOfDots = numberOfDots;this.scene = scene;for(var i=0; i < numberOfDots; i++) {this.dots.push( new Dot(Math.random() * this.canvas.width,Math.random() * this.canvas.height,this.scene));}};DotManager.prototype = {update : function( dt ) {for(var i=0; i < this.numberOfDots; i++) {this.dots[i].update( dt );}}};

Ahora en la escena, en lugar de crear y actualizar un punto , creamos y actualizamos el DotManager . Crearemos 5000 puntos para comenzar.

function Scene() {...this.dotManager = new DotManager(5000, this);...};Scene.prototype = {...update : function( dt ) {this.dotManager.update( dt );}...};

Para cada punto nuevo creado, tome su posición inicial y establezca su tono en el lugar donde se encuentra a lo largo del ancho del lienzo. La función Utils.hslToFillStyle es una pequeña función auxiliar que agregué para transformar algunas variables de entrada en la cadena fillStyle con formato correcto . Ya las cosas se ven más emocionantes. Los puntos eventualmente se fusionarán y perderán su efecto arco iris después de que tengan tiempo de dispersarse. De nuevo, este es un ejemplo de imágenes visuales con un poco de matemática o entradas variables. Realmente disfruto haciendo colores con el modelo de color HSL con arte generativo en lugar de RGB debido a la facilidad de uso. RGB es un poco abstracto.

Interacción del usuario usando un mouse

No ha habido ninguna interacción real del usuario hasta este punto.

var Mouse = function( scene ) {this.scene = scene;this.position = new THREE.Vector2(-10000, -10000);$(window).mousemove( this.onMouseMove.bind(this) );};Mouse.prototype = {onMouseMove : function(e) {if(typeof(e.pageX) == "number") {this.position.x = e.pageX;this.position.y = e.pageY;} else {this.position.x = -100000;this.position.y = -100000;}}};

Este objeto simple encapsula la lógica de las actualizaciones del mouse del resto de la escena. Solo actualiza el vector de posición en un movimiento de mouse. El resto de los objetos pueden tomar muestras del vector de posición del mouse si se les pasa una referencia al objeto. Una advertencia que ignoro aquí es si el ancho del lienzo no es uno a uno con las dimensiones en píxeles del DOM, es decir, un lienzo de tamaño reajustable o un lienzo de mayor densidad de píxeles (retina) o si el lienzo no está ubicado en el arriba a la izquierda. Las coordenadas del mouse deberán ajustarse en consecuencia.

var Scene = function() {...this.mouse = new Mouse( this );...};

Lo único que le quedaba al mouse era crear el objeto del mouse dentro de la escena. Ahora que tenemos un mouse, atraigamos los puntos.

function Dot( x, y, scene ) {...this.attractSpeed = 1000 * Math.random() + 500;this.attractDistance = (150 * Math.random()) + 180;...}

Agregué algunos valores escalares al punto para que cada uno se comporte un poco diferente en la simulación para darle un poco de realismo. Juega con estos valores para obtener una sensación diferente. Ahora vamos al método del mouse de atracción. Es un poco largo con los comentarios.

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);}};}()

Este método podría ser un poco confuso si no está actualizado en su matemática vectorial. Los vectores pueden ser muy visuales y pueden ayudar si dibuja algunos garabatos en un trozo de papel manchado de café. En lenguaje simple, esta función está obteniendo la distancia entre el mouse y el punto. Luego, acerca el punto al punto en función de lo cerca que esté del punto y la cantidad de tiempo que ha transcurrido. Hace esto calculando la distancia para moverse (un número escalar normal), y luego multiplicando eso por el vector normalizado (un vector con longitud 1) del punto que apunta al mouse. Ok, esa última frase no era necesariamente simple, pero es un comienzo.