Geometry in web applications II

Some years ago, I published a post related to apply geometry and in particular trigonometry in client-side web applications with high requeriments of animations and interaction. Today we are going to deepen a bit more in this topic studying a subject a little more advanced in the geometry world: rotation matrices.

Before starting, let‘s define what is a matrix. A matrix is nothing more than a two-dimensional set of numbers that represents a change (whether of a point, a vector or a plane) in a geometric space (specifically, Euclidean Space).

The use of matrices is one of the simplest ways to describe an algebraic transformation in geometric space and is very inexpensive in computational terms. That is why its use is so widespread in computer graphics generation. Every three-dimensional graphics rendering engine uses these algebraic operations in one way or another in its calculations. Although any transformation in Euclidean Space (be it translation, rotation, scaling, skew, etc.) could be described with a matrix, in this article we will focus only on rotation matrices.

Although it may seem intimidating at first, it is an easy topic to understand when we know all its aspects. To start, let‘s take a look at a two-dimensional rotation matrix:

This matrix will rotate a point by an angle θ with respect to the center of the coordinate system. If we wanted to rotate a point {x, y} in θ degrees, we only have to multiply the previous matrix by the vector made up of the values of said point:



With which we can determine that:

Let’s look at the following interactive example (vary the value of the angle)

In the same way that we can transform a point in a two-dimensional space, we can do it in a three-dimensional one [x, y, z], but in this case the rotation matrices will have three rows and three columns and we can apply the transformations in three different plans.

Let’s take a look at the rotation matrix needed to apply a rotation around the x axis:

In the same way as we did with the two-dimensional example, if we want to rotate a point {x, y, z} using the previous matrix, we must multiply it by the vector made up of these three points:




With which we can determine that:


Let’s proceed to create a practical example with HTML, CSS and JavaScript using CSS3 transformations.

HTML code
<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>
CSS code
.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" }

Now with JavaScript we will proceed to vary the CSS3 translate3d property of each ball element when the value of an input, relative to the value of the rotation angle, changes.

// Initial position of the 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 }
];

/* Function to rotate a point
** multiplying it by the rotation matrix in Xs
** point: object with coordinates x, y, z
** angle: angle in degrees
*/
const rotate = (point, angle) => {

    // Convert degrees to radians
    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', () => {

    // Input element to vary the rotation angle
    const angleInput = document.getElementById('angle');
    const balls = document.querySelectorAll('.ball');

    // Function to apply the calculated transformation to the elements
    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;
        });
    };

    // When the input to vary the angle changes
    angleInput.addEventListener('input', () => {
        render(+angleInput.value);
    });

    // By default render the graphic with 0 degrees
    render(0);

});
This will be the result (vary the value of the angle)

Let’s do the same exercise with the y-axis rotation matrix:

And let’s perform the corresponding calculations for this rotation matrix:






This will be the result (vary the value of the angle)

And finally let‘s do the same exercise with the z-axis rotation matrix:

And let‘s perform the corresponding calculations for this rotation matrix:






This will be the result (vary the value of the angle)

It is also possible to convert several rotation matrices into a single one, to do this you only need to multiply them, but it must be taken into account, that the order of the multiplications is important, because matrix multiplications are NOT commutative (you should only take a die and rotate it once to the left and once forward to realize that you won’t get the same result if you rotate it first forward and then to the left).

Let’s apply this to a practical example very similar to the previous ones, but this time we are going to use the transformations on the three axes so we will proceed to multiply the three corresponding matrices. The HTML and CSS code will be the same as we saw before, so we will not repeat them, let’s focus only on the JavaScript code.

JavaScript code
// Variables for the angles
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 }
];

// Utility to return the sin and cosin of an angle
const sincos = (angle) => {
    const rad = angle * Math.PI / 180;
    return {
        sin: Math.sin(rad),
        cos: Math.cos(rad)
    };
};

// Utility to return the x-axis rotation matrix for a certain angle
const rotateX = (angle) => {
    const sc = sincos(angle);
    return [
        [1,  0,         0     ],
        [0,  sc.cos,  - sc.sin],
        [0,  sc.sin,    sc.cos]
     ];
};

// Utility to return the y-axis rotation matrix for a certain angle
const rotateY = (angle) => {
    const sc = sincos(angle);
    return [
        [sc.cos,    0,  sc.sin],
        [0,         1,  0     ],
        [- sc.sin,  0,  sc.cos]
    ];
};

// Utility to return the z-axis rotation matrix for a certain angle
const rotateZ = (angle) => {
    const sc = sincos(angle);
    return [
        [sc.cos,  - sc.sin,  0],
        [sc.sin,    sc.cos,  0],
        [0,         0,       1]
    ];
};

// Utility to multiply two 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', () => {

    // Input elements to vary the rotation angles
    const angleInputX = document.getElementById('angle-x');
    const angleInputY = document.getElementById('angle-y');
    const angleInputZ = document.getElementById('angle-z');
    const balls = document.querySelectorAll('.ball');

    /**
     ** Function to build and multiply the three rotation matrices
     ** and apply the final transformation to the elements
     */ 
    const render = (angle) => {

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

            // Get the rotation matrices for each angle
            const matrixX = rotateX(angleX);
            const matrixY = rotateY(angleY);
            const matrixZ = rotateZ(angleZ);

            // Multiply the three rotation matrices
            const matrix = multiplyMatrix(
                multiplyMatrix(
                    matrixX,
                    matrixY
                ),
                matrixZ
            );
				
            // Transform each point with the resulted matrix
            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];
					
            // Apply the final transformation to the elements
            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();

});
This will be the result (vary the angle values)

I hope this article has been helpful to you. If you want to consult practical examples of the use of transformation matrices, I think you will find these two libraries that I maintain interesting: Isometric and Isometric-CSS.

(1 votes, average: 5.00 Out Of 5)
Share this post:

It is possible to insert code fragments between <pre></pre> tags and HTML or XML code between <xmp></xmp> tags.

Leave a Reply

Your email address will not be published. Required fields are marked *


The reCAPTCHA verification period has expired. Please reload the page.