Ejemplos básicos en WebGL y Processing

A continuación son mostrados, además del caso requerido en el planteamiento inicial acerca de un entorno diferente a WebGL (en este caso, Processing) he agregado también un segundo caso en WebGL. Uno de los objetivos perseguidos en este ejercicio es hacer funcionar cada uno de estos casos de manera offline (y sin el uso de servidores web en localhost, para facilitarle las cosas a un usuario no técnico). Como se vera en las líneas siguientes, esto constituye un desafío interesante porque el guardado a disco duro que usualmente realizan los navegadores web sólo tienen en cuenta los archivos referenciados en los HTMLs, y no tienen en cuenta los recursos llamados desde archivos JS o CSS.

Para poder establecer una comparación entre WebGL y Processing, y puesto que no tengo experiencia manejando código en WebGL (aunque sí he tenido un poco más de práctica con OpenGL y esperaría encontrar alguna semejanza en su derivado dirigido a la Web), me vi en la necesidad de abordar este segundo caso que no estaba contemplado en el planteamiento.

Caso 1: WebGL
WebGL - Apreciación de código
Caso 2: Processing
Processing - Apreciación de código


Caso 1: WebGL
http://webglsamples.org/aquarium/aquarium.html

Visualización de un acuario con peces moviéndose, y cámara rotando circularmente alrededor. Contiene opciones para cambiar una variedad de parámetros como número de peces, mostrar/ocultar elementos, y atributos para cambiar detalles como coloración, campo de visión, distancia de la cámara, tamaño y velocidad de objetos, etc.

Desde la URL original, corre corectamente en Chrome (v. 63)






















Y también desde Firefox (v. 51)

















A la hora de intentar almacenarlo, no todas las imágenes y recursos han sido descargados, por lo que funciona incorrectamente: 
































Se utilizó un add-on de Firefox llamado Tamper Data que, entre otras cosas, permite obtener la lista de todos los archivos que fueron invocados en la carga de la página (no solo los referenciados en los HTML):
























Utilizando wget se pudo descargar versiones originales de los archivos (los navegadores suelen alterar los HTML, modificándoles los paths de cada sub-archivo invocado) y conservando el árbol de directorios original. No obstante, tiene el mismo problema de los browsers de descargar sólo lo referenciado en tags de HTML.



































A partir de la lista obtenida con Tamper Data, son descargados uno a uno los archivos que hacen falta. En el ejemplo a continuación, se aprecia que en WebGL los modelos de cada objeto tridimensional se encuentran en un formato derivado de JSON:
































Se observa en este ejemplo de WebGL que elementos tales como texturas, mapas difusos o de normales son manejados en formato PNG. La carga de todos ellos se realiza desde el código en Javascript, no desde el HTML:



























Luego de descargar los recursos restantes y de realizar unos pocos ajustes en el HTML, la ejecución offline no pudo realizarse con Chrome debido a restricciones de seguridad en dicho browser relacionadas con prohibir la carga de recursos de origen cruzado:




















Firefox sí permitió la ejecución offline del ejemplo en WebGL:
























WebGL - Apreciación de código

El código de control principal del programa se encuentra distribuido entre los archivos aquarium.html y aquarium.js. En el html hay bloques al interior de varios tags <script> con contenido declarado como "text/something-not-javascript". Al estar así, sigue una estructura similar a la de JS pero utilizan primitivas que no son de JS. El contenido de estos bloques guarda una cierta semejanza con la ejecución secuencial de OpenGL.


Por otra parte, en aquarium.js observo, además de una típica declaración de variables, método principal y métodos auxiliares, la declaración de varias variables en donde se referencian los bloques de scripts del html, algo que sugiere un manejo del código como si fueran datos.



































Caso 2: Processing
https://www.openprocessing.org/sketch/492096

Haciendo click y/o dragging con el mouse se van creando círculos cuyo movimiento se comporta de manera similar a las visualizaciones "Force-Directed Network", pero con una mayor inercia y elasticidad:






































La página OpenProcessing permite descargar una versión mínima de este ejemplo (archivo ZIP que contiene 1 HTML y 1 JS), que permitió la ejecución offline tanto en Chrome como en Firefox:


Processing - Apreciación de código

El esquema de un programa en Processing es el mismo que he podido observar también en Unity y algunas otras librerías en C para gráficos interactivos:
  • Una función de inicialización, que corre 1 vez al inicio
  • Una función de actualización, que es iterada indefinidamente hasta la finalización del programa
  • Varios métodos manejando eventos de entrada (mouse, teclado, etc)
  • Declaraciones de variables

mySketch.js


var mass = [];

var positionX = [];

var positionY = [];

var velocityX = [];

var velocityY = [];



/////////////////////////////////////////////////////////////////////////////////////////////////////



function setup() {
createCanvas(windowWidth, windowHeight);
noStroke();
fill(64, 255, 255, 192);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////

function draw() {
background(32);
for (var particleA = 0; particleA < mass.length; particleA++) {
var accelerationX = 0, accelerationY = 0;
for (var particleB = 0; particleB < mass.length; particleB++) {
if (particleA != particleB) {
var distanceX = positionX[particleB] - positionX[particleA];
var distanceY = positionY[particleB] - positionY[particleA];

var distance = sqrt(distanceX * distanceX + distanceY * distanceY);
if (distance < 1) distance = 1;

var force = (distance - 320) * mass[particleB] / distance;
accelerationX += force * distanceX;
accelerationY += force * distanceY;
}
}
velocityX[particleA] = velocityX[particleA] * 0.99 + accelerationX * mass[particleA];
velocityY[particleA] = velocityY[particleA] * 0.99 + accelerationY * mass[particleA];
}
for (var particle = 0; particle < mass.length; particle++) {
positionX[particle] += velocityX[particle];
positionY[particle] += velocityY[particle];
ellipse(positionX[particle], positionY[particle], mass[particle] * 1000, mass[particle] * 1000);
}
}

/////////////////////////////////////////////////////////////////////////////////////////////////////

function addNewParticle() {
mass.push(random(0.003, 0.03));
positionX.push(mouseX);
positionY.push(mouseY);
velocityX.push(0);
velocityY.push(0);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////

function mouseClicked() {
addNewParticle();
}

/////////////////////////////////////////////////////////////////////////////////////////////////////

function mouseDragged() {
addNewParticle();
}

Los parámetros de cada partícula (masa, posición y velocidad) son inicializados y almacenados en arrays separados. En el método de actualización se varían posición y velocidad relacionando a "todos contra todos", (n-1)*n iteraciones.

En la línea 31: var force = (distance - 320) * mass[particleB] / distance; (cuya fórmula hubiera podido ser simplificada a  var force = mass[particleB] * (1 - (320/distance));) se observa un funcionamiento en el que la fuerza (de lo que depende posteriormente la velocidad y aceleración) es proporcional a la masa, y la distancia puede actuar a favor o en contra de la atracción dependiendo de una constante arbitraria de 320.

Finalmente, en la línea ellipse(positionX[particle], positionY[particle], mass[particle] * 1000, mass[particle] * 1000); se dibuja cada círculo del array con un tamaño dependiente de su masa, y con la posición calculada en la iteración anterior.


Comentarios

Entradas populares de este blog

Proyecto Final - Entrega 3

Animación