[Three.js] Basics parte I
Published: 2021-06-24 16:34:44

#Bienvenidos y bienvenidas!

La idea de este blog es ir volcando resúmenes de lo que voy aprendiendo en el curso de [Three.js de Bruno Simon] (https://threejs-journey.xyz/) para hacerlo un poco más accesible para todos y todas y que puedan aprender sobre animaciones. Mientras tanto, también pueden ver contenido *teórico* sobre animaciones en el [canal de youtube de la materia Técnicas de Gráficos por Computadora de la UTN FRBA] (https://www.youtube.com/user/tgcentregas) donde soy ayudante 😄

Ahora si, vamos a lo que nos incumbe

## Introducción

[Three.js] (https://threejs.org/) es una librería de javascript que se encuentra por encima de [WebGL] (https://developer.mozilla.org/es/docs/Web/API/WebGL_API) y es open source! Pueden ver el código [acá] (https://github.com/mrdoob/three.js)

También se puede usar con CSS y SVG pero no es lo que nos interesa por ahora

Acá se pueden ver paginas hechas con Three.js😍:
- https://bruno-simon.com
- https://go.pioneer.com/cornrevolution
- https://richardmattka.com
- https://lusion.co
- https://www.oculus.com/medal-of-honor/
- http://letsplay.ouigo.com
- https://zen.ly
- https://prior.co.jp/discover
- https://www.midwam.com
- https://heraclosgame.com
- https://chartogne-taillet.com
- https://live.vanmoof.com/site

### Bueno pero, que es WebGL exactamente?
Es una API de javascript que renderiza triángulos en un `<canvas>` de una manera increíblemente veloz, dado que usa la placa de video (la GPU) y hace operaciones en paralelo

La GPU dibuja triángulos, y para hacerlo, necesita saber dónde se encuentran posicionados. Esta información se encuentra en los shaders, que tienen la posición de los vertices y el color de los triángulos.

## Manos a la obra!

Lo primero que vamos a hacer es crear una escena bien básica con un cubo rojo en el centro.

Antes que nada, necesitamos descargarnos Three.js en nuestra compu: entran acá https://threejs.org/ y van a donde dice `download`. Se les va a descargar un zip, lo extraen y el archivo que vamos a usar es el que se llama `three.min.js`

Necesitamos crear 2 archivos: `index.html` y `script.js`. En el primero lo único que hacemos es lo siguiente:
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>03 - Basic Scene</title>
</head>
<body>
    <canvas class="webgl"></canvas>
    <script src="three.min.js"></script>
    <script src="script.js"></script>
</body>
</html>
```

Por ahora nada raro, simplemente ponemos en el body el canvas que les mencioné anteriormente y cargamos Three.js y el código javascript.

Ahora en `script.js` podemos usar el objeto `THREE` y acceder a los métodos y clases que nos provee la librería.

Si no me creen, escriban en `script.js`
```js
console.log(THREE);
```

Y pueden ver en la consola del navegador todo lo que tiene `THREE`

Para crear una escena necesitamos 4 elementos:
- Objetos
- Una escena que contiene los objetos
- Una cámara
- Un renderer

### Escena
Una escena es como un container. Ahí es donde vamos a poner todos los objetos que queremos mostrar en el browser, para luego renderizarlo.

Para instanciar una escena, el código es:
```js
const scene = new THREE.Scene();
```

###Objetos
En este ejemplo nuestro objeto va a ser un cubo rojo.

Para crear el cubo necesitamos un `mesh`. Un [Mesh] (https://threejs.org/docs/#api/en/objects/Mesh) es una combinación de una geometría (figura) y un material (cómo se ve)
Como queremos hacer un cubo rojo, vamos a usar [BoxGeometry] (https://threejs.org/docs/?q=boxgeo#api/en/geometries/BoxGeometry) para la geometría y [MeshBasicMaterial] (https://threejs.org/docs/?q=mesh#api/en/materials/MeshBasicMaterial) para el material.

```js
const geometry = new THREE.BoxGeometry(1, 1, 1);
```

Y para instanciar el material le pasamos como parámetro un objeto que tiene el color que queremos que tenga el material (se pueden pasar más pero en este caso solo nos interesa el color)

```js
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
```

Como pueden observar, le pasamos `0x` seguido del color en hexadecimal. El color se puede setear de muchas maneras: https://threejs.org/docs/?q=mesh#api/en/math/Color

Ahora sí podemos crear nuestro mesh:
```js
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(geometry, material);
```

Y lo agregamos a nuestra escena (muy importante! Si no no se va a ver)
```js
scene.add(mesh);
```

###Cámara

La cámara no es algo que nosotros vemos, sino que es el punto de vista desde donde se ve la escena. Podemos usar varias cámaras pero en general se usa una

Para setear la cámara, instanciamos una clase de [PerspectiveCamera] (https://threejs.org/docs/index.html#api/en/cameras/PerspectiveCamera) a la que le pasamos dos parámetros: el FOV (field of view) que es el ángulo de visión en grados, en este caso vamos a usar `75`. El segundo parámetro es el aspect ratio que es lo que el ancho del canvas / el alto.
Recuerden al final agregar la cámara a la escena.
```js
const sizes = {
    width: 800,
    height: 600
};

const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height);
scene.add(camera);
```

### Renderer
El renderer se encarga de hacer el render. Vamos a instanciar un [WebGLRenderer] (https://threejs.org/docs/index.html#api/en/renderers/WebGLRenderer) y le pasaremos como parámetro un objeto que tiene el canvas que pusimos en el HTML más arriba.
También tenemos que setearle el tamaño que va a ser el mismo que usamos para el aspect ratio 😉
```js
const canvas = document.querySelector('canvas.webgl');

const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(sizes.width, sizes.height);
```

Ahora llegó el momento de renderizar. Para eso, hacemos:
```js
renderer.render(scene, camera);
```

Si abren `index.html`, van a notar que solo se ve un cuadrado negro. Esto es porque la cámara está adentro del cubo. Para poder verlo, necesitamos alejar la cámara de este y para eso hacemos:
```js
camera.position.z = 3;
```

###AxesHelper

Algo que nos puede ayudar mucho a saber dónde están los ejes es la clase [AxesHelper] (https://threejs.org/docs/#api/en/helpers/AxesHelper)
Cuando lo agregamos a la escena, vamos a ver en color verde el eje `y` positivo, en color rojo el `x` positivo, y en color azul el eje `z` positivo

```js
const axesHelper = new THREE.AxesHelper(2);
scene.add(axesHelper);
```

El parámetro que se manda al instanciar esta clase, es el largo de los ejes (si queremos que se vean más grandes o más chicos)

##Transformaciones

Los [objetos] (https://threejs.org/docs/#api/en/core/Object3D) que usamos en las escenas tienen 4 atributos:
- `position` (para moverlo)
- `scale` (para cambiar el tamaño)
- `rotation` (para rotarlo)
- `quaternion` (para rotarlo - luego vamos a profundizar sobre esto)

### `position`

Tiene 3 propiedades esenciales, que son `x`, `y` y `z`.

Para mover un objeto:
- a la derecha => asignamos un valor > 0 en `x`
- a la izquierda => asignamos un valor < 0 en `x`
- hacia arriba => asignamos un valor > 0 en `y`
- hacia abajo => asignamos un valor < 0 en `y`
- hacia adelante (o sea hacia nosotros) => asignamos un valor > 0 en `z`
- hacia atrás (o sea alejado de nosotros) => asignamos un valor < 0 en `z`

Tengan en cuenta que tienen que mover al objeto *antes* de renderizarlo

Por ejemplo:
```js
mesh.position.x = 0.9;
mesh.position.y = -1;
mesh.position.z = 2;
```

`position` es una instancia de [Vector3] (https://threejs.org/docs/#api/en/math/Vector3), por lo tanto tiene los siguientes métodos heredados:
 ```js
console.log(mesh.position.length());
console.log(mesh.position.distanceTo(camera.position)); // distancia a otro Vector3
console.log(mesh.position.normalize()); // normaliza un vector, o sea, hace que su módulo (tamaño) sea 1
mesh.position.set(0.9, -1, 2); // setea los atributos x, y, z
```

###`scale`

Al igual que `position`, `scale` es una instancia de [Vector3] (https://threejs.org/docs/#api/en/math/Vector3). Se usa para setear en cuántas veces querés setear el tamaño del objeto. Por ejemplo, si ponemos 0.5, va a ser la mitad de chico; y si ponemos 2, va a ser el doble de grande.

```js
mesh.scale.x = 2;
mesh.scale.y = 0.25;
mesh.scale.z = 0.5;
```

PD: ojo! no usen números negativos.

###`rotation`

A diferencia de `position` y `scale`, `rotation` *no* es un Vector3, sino que es un [Euler] (https://threejs.org/docs/index.html#api/en/math/Euler). Según el eje que cambiemos, es sobre el eje que va a rotar el objeto. Por ejemplo, si cambiamos el `y`, va a rotar como una calesita.

Los valores de `x`, `y` y `z` están expresados en [radianes] (https://es.wikipedia.org/wiki/Radi%C3%A1n), esto implica que si queremos que rote media vuelta, tenemos que asignarle π. Esto en javascript se logra usando la constante `Math.PI`

Peeeero tenemos un problemilla: Esta clase de rotaciones nos puede traer problemas dependiendo en qué orden se aplican las rotaciones. Este problema se llama gimbal lock y nos saca un grado de libertad. Si quieren leer más sobre eso pueden leer una [explicación matemática] (http://www.math.umd.edu/~immortal/MATH431/lecturenotes/ch_gimballock.pdf). Para ayudarnos con este problema, vienen los quaterniones al rescate!

### `quaternion`

Como dijimos antes, los quaterniones se usan para las rotaciones. En esta parte del curso no profundiza sobre quaterniones así que no vamos a ahondar sobre el tema.

Tengan en cuenta que al actualizar el atributo de `rotation`, se actualizan los valores de `quaternion`


## Combinando transformaciones
	
Se pueden combinar la posición, la rotación (ya sea de Euler o quaternión) y la escala en cualquier tipo de orden. El resultado será el mismo

### Hi, I'm Mr. Meeseeks, look at me!

![Mr. Meeseeks] (https://i.pinimg.com/originals/a4/4c/b7/a44cb7324810ce345d14ff00c5d51fdb.jpg)

Todas las instancias de Object3D tienen un método que se llama `lookAt()` que recibe como argumento un Vector3. Podemos hacer, por ejemplo
```js
camera.lookAt(new THREE.Vector3(0, 1, -1));
```

## Grupos

Muchas veces vamos a querer mover un conjunto de objetos de la misma manera. Por ejemplo, estamos armando un auto, con las ruedas y puertas y queremos que sea más chico. Si quisiéramos achicar el auto, tendriámos que achicar cada parte por separado, o sea, un bajón 😔
Una solución a esto son los [Group] (https://threejs.org/docs/#api/en/objects/Group).

Para usarlo, lo instanciamos y lo agregamos a la escena para luego agregarle los objetos que querramos.

```js
const group = new THREE.Group();
group.scale.y = 2;
group.rotation.y = 0.2;
scene.add(group);

const cube1 = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1),
    new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
cube1.position.x = -1.5;
group.add(cube1);

const cube2 = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1),
    new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
cube2.position.x = 0;
group.add(cube2);

const cube3 = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1),
    new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
cube3.position.x = 1.5;
group.add(cube3);
```

Tengan en cuenta que `group` hereda de `Object3D`, por lo tanto, tiene los atributos y clases que mencioné más arriba.

## Animaciones

Cada vez que hacemos `renderer.render(...)` es como sacarle una "foto" a la escena. Las animaciones no son nada más ni nada menos que muchas fotos consecutivas de la escena, como si fuera un stop-motion.

La pantalla que uno ve corre a determinados FPS (frames per second), que en general suele ser 60FPS.

### `requestAnimationFrame`

`window.requestAnimationFrame()` es un método que recibe como argumento una función que ejecutará en el próximo render. En el 99% de los casos vamos a usar esta función de manera recursiva ya que queremos que nuestra función que anima a los objetos se ejecute todo el tiempo
```js
const loop = () => {
    mesh.rotation.y += 0.01;
    renderer.render(scene, camera);
    window.requestAnimationFrame(loop);
};

loop();
```

El código de arriba crea una función y la guarda en la variable `loop`. Adentro de la función se invoca a `window.requestAnimationFrame` que hace justamente lo que queremos: ejecuta `loop` en el próximo render. No se olviden de llamar `loop` por primera vez porque sino no se ejecuta nuestra función y no podremos ver nuestras bellas animaciones.

###Pero tenemos un problema:
Si yo corro este código en una computadora con mejor GPU y en otra con peor, vamos a ver las animaciones a distintas velocidades porque tendremos distintos FPS.

La solución? Que la animación (en este caso, la rotación) dependa del tiempo entre cada render.

```js
let time = Date.now();

const loop = () => {
    const currentTime = Date.now();
    const deltaTime = currentTime - time;
    time = currentTime;

    mesh.rotation.y += 0.01 * deltaTime;

    renderer.render(scene, camera);
    window.requestAnimationFrame(loop);
}

loop();
```

#### Solución de Three.js
Three.js tiene una clase que se llama `Clock` que hace por nosotros el código escrito anteriormente mediante la función `getElapsedTime()`, que nos retorna cuántos segundos pasaron desde que se instanció la clase `Clock`.

```js
const clock = new THREE.Clock()

const loop = () => {
    const elapsedTime = clock.getElapsedTime();

    mesh.rotation.y = elapsedTime;

    renderer.render(scene, camera);
    window.requestAnimationFrame(loop);
}

loop();
```

##Volviendo a las animaciones

Acá es donde uno se pone creativo: podemos usar, por ejemplo, la función seno que oscila entre el -1 y 1 para hacer que nuestro cubo se mueva en el eje `y` entre -1 y 1

```js
const clock = new THREE.Clock()

const loop = () => {
    const elapsedTime = clock.getElapsedTime();

    mesh.rotation.y = Math.sin(elapsedTime);

    renderer.render(scene, camera);
    window.requestAnimationFrame(loop);
}

loop();
```

Y si queremos que el cubo haga la trayectoria de un círculo

```js
const clock = new THREE.Clock()

const loop = () => {
    const elapsedTime = clock.getElapsedTime();

    mesh.rotation.y = Math.sin(elapsedTime);
    mesh.position.x = Math.cos(elapsedTime);

    renderer.render(scene, camera);
    window.requestAnimationFrame(loop);
}

loop();
```
Ta-da!

### Usando GSAP
[GSAP] (https://greensock.com/gsap/) es una librería, por lo tanto tenemos que agregarla a nuestro proyecto. Al igual que la mayoría de las librerías, su objetivo es simplificarnos las cosas, con ella es más simple generar animaciones

```js
gsap.to(mesh.position, { duration: 1, delay: 1, x: 2 });

const loop = () => {
    renderer.render(scene, camera);
    window.requestAnimationFrame(loop);
}

loop();
```

Acá le decimos a `gsap` que cambie la *posición* del mesh, que se mueva 2 unidades en el eje x, que tarde un segundo en empezar y que dure un segundo

PD: ojo! A `gsap` le pasamos la `position`, no todo el `mesh`

![That's all folks] (https://cdn.hinative.com/attached_images/481981/9c7d491417c87206f1cefcfd47670972fb2ff202/large.jpg?1562691680)

That's all folks! Si llegaron hasta acá, gracias por leer🥰
A medida que vaya viendo los otros módulos voy a ir subiendo los resúmenes acá.
Mientras tanto, pueden verme en [Twitch] (https://www.twitch.tv/daiuszw) o leerme en [Twitter] (https://twitter.com/uhhdai)

Hasta la próxima!!