Geometría en las aplicaciones web II

Hace algunos años atrás, escribí una entrada relacionada con aplicar la geometría y en particular la trigonometría en las aplicaciones web en el lado del cliente con altos requerimientos de animaciones e interactividad. Hoy vamos a profundizar algo más en este asunto estudiando un tema un poco más avanzado en el mundo de la geometría: las matrices de rotación.

Para empezar definamos qué es una matriz. Una matriz no es más que un conjunto bidimensional de números que representa un cambio (ya sea de un punto, un vector o un plano) en un espacio geométrico (específicamente, el Espacio Euclídeo).

El uso de matrices es una de las maneras más simples de describir una transformación algebráica en el espacio geométrico y es muy poco costosa en términos computacionales. Es por eso que su uso es tan extendido en la generación de gráficos por orenador. Todo programa de renderizado de gráficos en tres dimensiones usa de una u otra forma estas operaciones algebraicas en sus cálculos. Aunque cualquier transformación en el Espacio Euclídeo (ya sea traslación, rotación, escala, sesgo, etc.) se podría describir con una matriz, en este artículo nos centraremos solamente en las matrices de rotación.

Aunque parezca intimidante en un inicio, es un tema fácil de entender cuando conocemos todas sus aristas. Para comenzar, analicemos una matriz de rotación de dos dimensiones:

Esta matriz rotará un punto en un ángulo θ con respecto al centro del sistema de coordenadas. Si quisiéramos rotar un punto {x, y} en θ grados solo debemos multiplicar la anterior matriz por el vector conformado por los valores de dicho punto:



Con lo cual podemos determinar que:

Observemos el siguiente ejemplo interactivo (varía los valores del ángulo)

De la misma manera que podemos transformar un punto en un espacio de dos dimensiones, podemos hacerlo en un espacio tridimensional [x, y, z] lo que en este caso las matrices de rotación tendrán tres filas y tres columnas y podremos aplicar las transformaciones en tres planos diferentes.

Consultemos la matriz de rotación necesaria para aplicar una rotación alrededor del eje x:

De la misma manera que hicimos con el ejemplo en dos dimensiones, si deseamos rotar un punto {x, y, z} usando la anterior matriz, deberemos multiplicar la misma por el vector conformado por estos tres puntos:




Con lo cual podemos determinar que:


Procedamos a crear un ejemplo práctico con HTML, CSS y JavaScript valiéndonos de transformaciones CSS3.

Código HTML
<div class="scene">
    <div class="ball ball-1"></div>
    <div class="ball ball-2"></div>
    <div class="ball ball-3"></div>
    <div class="ball ball-4"></div>
</div>
Código CSS
.scene {
	left: 50%;
	position: absolute;
	perspective: 380px;
    perspective-origin: center -100px;
	top: 50%;
}

.ball {
	position: absolute;
	transform-origin: center center;
}

.ball::after {
	align-items: center;
	background-color: #E87A24;
	border-radius: 50%;
	color: #FFFFFF;
	content: "";
	display: flex;
	font-family: ArialMT;
	font-size: 12px;
	height: 30px;
	justify-content: center;
	transform: translate(-50%, -50%);
	width: 30px;
}

.ball-1::after { content: "1" }
.ball-2::after { content: "2" }
.ball-3::after { content: "3" }
.ball-4::after { content: "4" }

Ahora con JavaScript procederemos a variar la propiedad CSS3 translate3d de cada elemento ball cuando el valor de un input, relativo al valor del ángulo de rotación, cambie.

// Pocisión inicial de los elements
const POINTS = [
    { x: -100, y: 0, z:  100 },
    { x:  100, y: 0, z:  100 },	
    { x:  100, y: 0, z: -100 },
    { x: -100, y: 0, z: -100 }
];

/* Función de rotar un punto
** multiplicándolo por la matriz de rotación en las X
** point: objeto con coordenadas x, y, z
** angle: ángulo en grados
*/
const rotate = (point, angle) => {

    // Convertir grados a radianes
    const rad = angle * Math.PI / 180;
    const sin = Math.sin(rad);
    const cos = Math.cos(rad);

    const { x, y, z } = point;
    const x1 = x;
    const y1 = y * cos - z * sin;
    const z1 = y * sin + z * cos;

    return {
        x: x1,
        y: y1,
        z: z1
    };

};

document.addEventListener('DOMContentLoaded', () => {

    // Elemento input para variar el ángulo de rotación
    const angleInput = document.getElementById('angle');
    const balls = document.querySelectorAll('.ball');

    // Función de aplicar la transformaciones calculadas a los elementos
    const render = (angle) => {
        POINTS.forEach((point, index) => {
            const newPoint = rotate(point, angle);
            const ball = balls.item(index);
            const zIndex = Math.round(z + 100);
            ball.style.transform = `translate3d(${newPoint.x}px, ${newPoint.y}px, ${newPoint.z}px)`;
            ball.style.zIndex = zIndex;
        });
    };

    // Cuando el input de variar el ángulo cambie
    angleInput.addEventListener('input', () => {
        render(+angleInput.value);
    });

    // Por defecto renderear la escena con un ángulo 0
    render(0);

});
Este sería el resultado (varía los valores del ángulo)

Hagamos el mismo ejercicio con la matriz de rotación del eje y:

Y realicemos los cálculos correspondientes para esta matriz de rotación:






Este sería el resultado (varía los valores del ángulo)

Y por último hagamos el mismo ejercicio con la matriz de rotación del eje z:

Y realicemos los cálculos correspondientes para esta matriz de rotación:






Este sería el resultado (varía los valores del ángulo)

También es posible convertir varias matrices de rotación en una sola, para ello solo se necesita mutiplicarlas, pero se debe tener en cuenta que el orden en el que se multiplican las matrices es importante, porque las multiplicaciones de matrices NO son conmutativas (sólo debes tomar un dado y rotarlo una vez hacia a la izquierda y otra vez hacia adelante para darte cuenta que no obtendrás lo mismo si lo rotas primero hacia adelante y después hacia la izquierda).

Apliquemos esto a un ejemplo práctico muy parecido a los anteriores, pero esta vez vamos a utilizar las transformaciones en los tres ejes por lo que procederemos a multiplicar las tres matrices correspondientes. El código HTML y el código CSS serán los mismos que vimos anteriormente así que no los repetiremos, centrémonos solo en el código JavaScript.

Código JavaScript
// Variables para los ángulos
let angleX = 0;
let angleY = 0;
let angleZ = 0;

const POINTS = [
    { x: -100, y: 0, z:  100 },
    { x:  100, y: 0, z:  100 },	
    { x:  100, y: 0, z: -100 },
    { x: -100, y: 0, z: -100 }
];

// Utilidad para devolver el seno y el coseno de un ángulo
const sincos = (angle) => {
    const rad = angle * Math.PI / 180;
    return {
        sin: Math.sin(rad),
        cos: Math.cos(rad)
    };
};

// Utilidad para devolver la matriz de rotación de un ángulo en el eje de las X
const rotateX = (angle) => {
    const sc = sincos(angle);
    return [
        [1,  0,         0     ],
        [0,  sc.cos,  - sc.sin],
        [0,  sc.sin,    sc.cos]
     ];
};

// Utilidad para devolver la matriz de rotación de un ángulo en el eje de las Y
const rotateY = (angle) => {
    const sc = sincos(angle);
    return [
        [sc.cos,    0,  sc.sin],
        [0,         1,  0     ],
        [- sc.sin,  0,  sc.cos]
    ];
};

// Utilidad para devolver la matriz de rotación de un ángulo en el eje de las Z
const rotateZ = (angle) => {
    const sc = sincos(angle);
    return [
        [sc.cos,  - sc.sin,  0],
        [sc.sin,    sc.cos,  0],
        [0,         0,       1]
    ];
};

// Utilidad para multiplicar dos matrices
const multiplyMatrix = (m1, m2) => (
    m1.map((row, i) => (
        m2[0].map((_, j) =>
            row.reduce((acc, _, n) =>
                acc + m1[i][n] * m2[n][j],
                0
            )
        )
    )
));

document.addEventListener('DOMContentLoaded', () => {

    // Elementos input para variar los ángulos de rotación
    const angleInputX = document.getElementById('angle-x');
    const angleInputY = document.getElementById('angle-y');
    const angleInputZ = document.getElementById('angle-z');
    const balls = document.querySelectorAll('.ball');

    /**
     ** Función para multiplicar todas las matrices de rotación
     ** y aplicar la matriz resultante a los puntos
     */ 
    const render = (angle) => {

        POINTS.forEach((point, index) => {

            // Obtener las matrcices de rotación
            const matrixX = rotateX(angleX);
            const matrixY = rotateY(angleY);
            const matrixZ = rotateZ(angleZ);

            // Multiplicar todas las matrices de rotación
            const matrix = multiplyMatrix(
                multiplyMatrix(
                    matrixX,
                    matrixY
                ),
                matrixZ
            );
				
            // Transformar cada punto con la matriz resultante		
            const x = point.x * matrix[0][0] + point.y * matrix[0][1] + point.z * matrix[0][2];
            const y = point.x * matrix[1][0] + point.y * matrix[1][1] + point.z * matrix[1][2];
            const z = point.x * matrix[2][0] + point.y * matrix[2][1] + point.z * matrix[2][2];
					
            // Aplicar la transformación a los elementos	
            const ball = balls.item(index);
            const zIndex = Math.round(z + 100);
            ball.style.transform = `translate3d(${x}px, ${y}px, ${z}px)`;
            ball.style.zIndex = `${zIndex}`;

        });

    };

    angleInputX.addEventListener('input', () => {
        angleX = +angleInputX.value;
        render();
    });

    angleInputY.addEventListener('input', () => {
        angleY = +angleInputY.value;
        render();
    });

    angleInputZ.addEventListener('input', () => {
        angleZ = +angleInputZ.value;
        render();
    });

    render();

});
Este sería el resultado (varía los valores de los ángulos)

Espero que te haya sido de ayuda este artículo. Si deseas consultar ejemplos prácticos del uso de las matrices de transformación, creo que estas dos librerías que mantengo te resultarán interesantes: Isometric e Isometric-CSS.

(2 votos, promedio: 3,50 de 5)
Comparte este artículo:

Puedes situar fragmentos de código dentro de etiquetas <pre></pre> y código HTML o XML entre etiquetas <xmp></xmp>.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *


El periodo de verificación de reCAPTCHA ha caducado. Por favor, recarga la página.