# Transformaciones geométricas, materiales y módelos de iluminación utilizando p5.js
*Para hacer una revición más exhaustiva del tema puedes dirigirte a [este enlace](https://hackmd.io/@dasilvaca/visual_computing)*
## Transformaciones geométricas
Las transformaciones geométricas son operaciones que se aplican a los objetos para modificar su posición, orientación y escala. Estas transformaciones se aplican a los objetos en el orden en que se encuentran en el archivo de escena.
Las transformaciones geométricas se pueden aplicar a los objetos de dos maneras:
- **Transformaciones usando matrices de transformación**: se aplican a todos los objetos que se encuentran debajo de él en el archivo de escena. Para aplicar una transformación global, se debe utilizar la función `applyMatrix()`.
- **Transformaciones usando funciones de transformación propias de p5.js**: se aplican a todos los objetos que se encuentran debajo de él en el archivo de escena. Para aplicar una transformación global, se debe utilizar la función `translate()`, `rotate()` o `scale()`.
## Árbol de transformaciones (Push y Pop)
Las transformaciones se aplican a los objetos en el orden en que se encuentran en el archivo de escena. Esto da lugar a una jerarquía de transformaciones, donde las transformaciones se aplican de manera acumulativa.
Lo que ocurre además, es que cada una de las transformaciones, se aplican a un marco de referencia nuevo, relativo al mundo anterior. Esto da lugar a una jerarquía de marcos de referencia, donde cada marco de referencia se encuentra dentro del marco de referencia anterior.
Para ilustrar esto, se puede pensar en un árbol de transformaciones, donde cada nodo representa un marco de referencia, y cada arista representa una transformación.
Esto nos permite introducir un concepto importante: el concepto de `push()` y `pop()`. Estas funciones nos permiten crear un nuevo marco de referencia, y luego volver al marco de referencia anterior. Esto es útil para evitar que las transformaciones se acumulen de manera indefinida, y volver en el árbol de transformaciones, aplicando otras transformaciones nuevas sobre otros objetos.
### Push y Pop
Para crear un nuevo marco de referencia, se utiliza la función `push()`. Esta función crea un nuevo marco de referencia, y lo apila en el árbol de transformaciones.
Para volver al marco de referencia anterior, se utiliza la función `pop()`. Esta función desapila el marco de referencia actual, y vuelve al marco de referencia anterior.
En el siguiente tutorial, podemos ver algunos ejemplos de cómo se utilizan estas funciones:
<iframe width="750" height="394" src="https://www.youtube.com/embed/o9sgjuh-CBM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
De manera similar, se puede aplicar la matríz inversa de la transformación actual, para volver al marco de referencia anterior (Sin embargo, esa es una discusión que se sale de lo que deseamos tratar en este tutorial).
Se puede interpretar como que cada vez que deseemos insertar un nuevo nodo en el árbol de transformaciones, se debe utilizar la función `push()`. Y cada vez que deseemos volver al nodo anterior, se debe utilizar la función `pop()`. Y así podemos construir nuestro árbol de transformaciones.
Podemos introducir el concepto de árbol de transformaciones, utilizando el siguiente sketch:
```javascript
let moon_texture, earth_texture, stars_texture; //* Textures
let ligt_color, sunlight_direction; //* Light color and direction
let moon_orbit_speed, moon_rotation_speed, earth_rotation_speed; //* Sliders to control the speed of the objects
let set_earth, set_moon, set_UFO; //* Checkboxes to draw or not the objects
let space_rotationX = 0, space_rotationY = 0; //* Camera positions
let earth_radius = 200, moon_radius = earth_radius / 4, UFO_height = 10; //* Sizes of the objects
draw_axes = () => {
push();
stroke("red")
line(0, 0, 0, 20, 0, 0)
stroke("green")
line(0, 0, 0, 0, 20, 0)
stroke("blue")
line(0, 0, 0, 0, 0, 20)
pop();
}
draw_earth = () => {
push()
noStroke()
texture(earth_texture)
sphere(earth_radius)
pop()
}
draw_moon = () => {
push()
noStroke()
rotateY(frameCount * moon_rotation_speed.value())
texture(moon_texture)
sphere(moon_radius)
pop()
}
draw_UFO = () => {
push()
noStroke()
specularMaterial(0, 255, 0)
fill(0, 255, 0)
ellipsoid(2 * UFO_height, UFO_height, 2 * UFO_height)
translate(0, -UFO_height, 0)
specularMaterial(0, 100, 255)
fill(0, 0, 255)
sphere(UFO_height)
pop()
}
preload = () => {
moon_texture = loadImage('moon_texture.jpg');
earth_texture = loadImage('earth_texture.jpg');
stars_texture = loadImage('stars_texture.jpg');
}
function setup() {
createCanvas(400, 400, WEBGL);
angleMode(mode = "degrees")
frameRate(60);
set_earth = createCheckbox("draw earth", true)
set_earth.position(20, 40)
set_earth.style("color", "blue")
set_moon = createCheckbox("draw moon", true)
set_moon.position(20, 60)
set_moon.style("color", "blue")
set_UFO = createCheckbox("draw UFO", true)
set_UFO.position(20, 80)
set_UFO.style("color", "blue")
earth_rotation_speed = createSlider(0, 1, 0.1, 0.01)
earth_rotation_speed.position(20, 100)
moon_rotation_speed = createSlider(0, 1, 0.1, 0.01)
moon_rotation_speed.position(20, 120)
moon_orbit_speed = createSlider(-1, 1, 0.1, 0.01);
moon_orbit_speed.position(20, 140)
};
draw = () => {
background(0)
resizeCanvas(windowWidth - 20, windowHeight - 20); //* This make the canvas responsive
rotateX(space_rotationX); //* This emulates the camera rotation
rotateY(space_rotationY); //* This emulates the camera rotation
directionalLight(255, 255, 255, new p5.Vector(-0.5, 0, -0.5)); //* This is the sunlight. And it's set to be white light
push();
rotateY(frameCount * earth_rotation_speed.value()); // Make earth rotate into its own axis
push()
set_earth.checked() ? draw_earth() : draw_axes();
push()
rotateZ(30) //! This makes the UFO to dont spawn on North Pole
rotateX(30) //! This makes the UFO to dont spawn on North Pole
translate(0, -UFO_height - earth_radius, 0) //* Moves the next coordinate system at the earth border plus a bit of space
set_UFO.checked() ? draw_UFO() : draw_axes();
translate(earth_radius, 0, 0)
pop()
pop()
pop()
fill(255);
push()
rotateY(frameCount * moon_orbit_speed.value()); //* Makes moon to orbit around earth
translate(height / 2, 70, 0);
set_moon.checked() ? draw_moon() : draw_axes();
pop();
}
mouseDragged = () => {
space_rotationX -= mouseY / 1000;
space_rotationY += mouseX / 1000;
}
mouseClicked = () => {
console.log("mouse clicked")
};
```
Aquí tenemos varios elementos nuevos. Sin embargo, lo que nos interesa es el árbol de transformaciones que se genera. Este es el árbol de transformaciones que se genera:
```graphviz
digraph hierarchy {
nodesep=1.0 // increases the separation between nodes
node [color=Red,fontname=Courier,shape=box] //All nodes will this shape and colour
edge [color=Blue, style=dashed] //All the lines look like this
Mundo->{Tierra Luna}
Tierra->{OVNI}
}
```
Podemos observar la ejecución de [este sketch](https://dasilvaca.github.io/vc-sketches/axis.html) a continuación:
<iframe width="750" height="675" src="https://dasilvaca.github.io/vc-sketches/axis.html"></iframe>
## Modelos de iluminación
De manera nativa podemos encontrar tres modelos de iluminación en p5.js. Estos son:
- Ambient light (luz ambiental)
- Directional light (luz direccional)
- Point light (luz puntual)
Para cada una de ellas debemos tener en cuenta un parametro en común, este es el color de la luz. Corresponde a un vector de tres componentes que representan el color de la luz en el espacio RGB. Se puede tener más de una fuente de luz en un sketch si es necesario.
Podemos usar el [siguiente tutorial](https://www.youtube.com/watch?v=k2FguXvqp60) como referencia:
<iframe width="750" height="394" src="https://www.youtube.com/embed/k2FguXvqp60" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
### Luz ambiental
Es una luz aplicada a todo el canvas de manera uniforme. No tiene dirección ni posición. Es como si fuera una luz que ilumina todo el espacio. Para crearla debemos usar la función `ambientLight(r, g, b)` donde `r`, `g` y `b` son los componentes RGB del color de la luz. Puedes consultar [la referencia de la función aquí](https://p5js.org/reference/#/p5/ambientLight).
### Luz direccional
Es una luz que se extiende por el espacio indefinidamente con una dirección que definimos en un vector normalizado (Es decir, con valores entre -1 y 1). Para crearla debemos usar la función `directionalLight(r, g, b, normal)` donde `r`, `g` y `b` son los componentes RGB del color de la luz y `normal` es un vector normalizado (de la clase `p5.Vector`) que indica la dirección de la luz. Puedes consultar [la referencia de la función aquí](https://p5js.org/reference/#/p5/directionalLight).
### Luz puntual
A diferencia de las dos clases de luz anteriores, esta depende de la distancia respecto al foco de luz. Es decir, mientras más cerca estemos de la luz, más iluminados estaremos, y así mismo se comporta la luz respecto a la orientación de los objetos. Para crearla debemos usar la función `pointLight(r, g, b, x, y, z)` donde `r`, `g` y `b` son los componentes RGB del color de la luz y `x`, `y` y `z` son las coordenadas del foco de luz. Puedes consultar [la referencia de la función aquí](https://p5js.org/reference/#/p5/pointLight).
El [siguiente sketch](https://dasilvaca.github.io/vc-sketches/solar_system.html) muestra un ejemplo de luz puntual:
```javascript
let moon_texture;
let earth_texture;
let ligt_color;
let sunlight_position;
let stars_texture;
let moon_orbit_speed;
let moon_rotation_speed;
let earth_rotation_speed;
function axis() {
line(0, 20, 0 ,0 , 0)
stroke("red")
}
preload = () => {
moon_texture = loadImage('moon_texture.jpg');
earth_texture = loadImage('earth_texture.jpg');
stars_texture = loadImage('stars_texture.jpg');
}
function setup() {
createCanvas(400, 400, WEBGL);
frameRate(60);
earth_rotation_speed = createSlider(0,0.5)
moon_rotation_speed = createSlider(0,0.5);
moon_orbit_speed = createSlider(0, 0.5);
moon_orbit_speed.position( 20,20)
};
draw = () => {
background(0)
texture(stars_texture)
quad(-width, height, -height / 2, width, height, -height / 2, width, -height, -height / 2, -width, -height, -height / 2)
light_color = color(255, 255, 255)
resizeCanvas(windowWidth - 20, windowHeight - 20);
sunlight_position = new p5.Vector(-(width / 2), -(height / 2), -1)
pointLight(light_color, sunlight_position)
push()
rotateZ(0.3);
rotateY(frameCount * 0.01);
push()
noStroke();
fill(0);
texture(earth_texture);
sphere(height / 6);
pop()
stroke("white");
fill(255);
push()
translate(height / 2, 70, 0);
rotateX(frameCount * 0.015);
noStroke();
texture(moon_texture)
sphere(height / 13);
pop();
pop();
};
```
## Materiales
Bueno, ya definimos una luz, sin embargo, esto no es suficiente para poder observar colores en nuestro sketch. Para ello debemos definir un material. Un material es una clase que define las propiedades de un objeto en el espacio. Más específicamente, le atribuye propiedades sobre la reflexión de la luz a un objeto, asociado a su vez a un color. En p5.js podemos encontrar varios tipos de materiales, entre ellos:
- `ambientMaterial()` este no es realmente afectado por la luz. Sin embargo, se usa para hacer debug, y definir un color base para el objeto. Puedes consultar [la referencia de la función aquí](https://p5js.org/reference/#/p5/ambientMaterial).
- `normalMaterial(r,g,b)` este material es afectado por la luz. Sin embargo, no tiene brillo. Puedes consultar [la referencia de la función aquí](https://p5js.org/reference/#/p5/normalMaterial).
- `specularMaterial(r,g,b)` este material es afectado por la luz. Sin embargo, tiene brillo. Puedes consultar [la referencia de la función aquí](https://p5js.org/reference/#/p5/specularMaterial).
- `emissiveMaterial(r,g,b)` este material es afectado por la luz. Sin embargo, no tiene brillo. Puedes consultar [la referencia de la función aquí](https://p5js.org/reference/#/p5/emissiveMaterial).
Podemos usar el primer sketch para observar el comportamiento de los materiales.
## Texturas
Las texturas son imágenes (por lo general) que se pueden aplicar a un objeto en el espacio (es decir, lo envuelven). Para ello debemos usar la función `texture(img)` donde `img` es la imagen que queremos aplicar. Puedes consultar [la referencia de la función aquí](https://p5js.org/reference/#/p5/texture).
## Cámaras
Lo dejamos para la próxima clase. :3