/

HTML / CSS / JavaScript Tutorial

Example: Animation of Spinning 3d Regular Polyhedra

[this page | pdf | back links]

In this page we illustrate how an animation involving spinning polyhedra can be created using HTML, CSS and JavaScript, and we explain the coding involved, which is set out below.

 

HTML

 

The first and last few lines involve HTML and create a canvas element, onto which the spinning polyhedra will be drawn. The canvas element is itself contained in a hyperlink, as the spinning polyhedra are mainly used in the Nematrian website to point to its HTML, CSS and JavaScript Tutorial.

 

Start of Script

 

The first part of the script, links the canvas element to a variable named x2, sets the canvas element size and creates a context applicable the canvas. Although canvas contexts that render 3d images directly do exist, we use here the more widely supported 2d context and include our own algorithms for projecting 3d objects onto a 2d plane.

 

The next part of the script defines a series of opening parameters, such as which shapes we will draw, in what order, their colouring, where they are positioned on the canvas, how big they are, how fast each is rotating, around what axis and their initial angle through which they have been rotated from the specimen layouts defined later in the code. In the colouring, the “rgba” define the red / green / blue colours of the faces and edges and the extent to which they will be transparent. We also set some shape independent parameters, e.g. the overall x and y axes of the plane onto which we will project the resulting images and the frame rate specifying how frequently we redraw the picture on the canvas.

 

The shapes used are the five regular polyhedra known from antiquity. Each shape is defined by the cartesian coordinates of its vertices and by arrays that then indicate for each edge which two vertices its joins and for each face which vertices form the perimeter of the face. The initial orientation of the shape is set in a manner that simplifies the exact locations of the vertices relative to the origin. The shapes are embedded in the functions named regularTetrahedron, …

 

The script then executes the function animate3dShapeOnCanvas, which contains all the necessary elements to create the moving images.

 

Script functions

 

The function animate3dShapeOnCanvas identifies the number of shapes being drawn and initialises (in dynamicRotAngles) the angles to which each shape has been rotated around its axis when the animation starts. It draws the initial configuration of the shapes on the canvas by calling the projectionIncrement function which itself clears the canvas, resets the line path being drawn on the canvas and then projects each shape in turn onto the canvas, using a generic project3dShapeOnCanvas function which is designed to handle any suitably specified shape. It also updates the dynamicRotAngles so that the next time the shape is plotted it will be rotated slightly. The animate3dShapeOnCanvas function then uses the setInterval method to set the animation running. The setInterval tells JavaScript to repeatedly call a specified function (here the same projectionIncrement function that was used to draw the shapes initially) every set number of milliseconds (the number here being the frame rate defined earlier).

 

The remainder of the code is more mathematical in nature and is designed to support the project3dShapeOnCanvas function. For example, it includes some generic vector and matrix functions designed to facilitate rotating a shape and then projecting it onto a plane.

 

EXAMPLE:


HTML USED IN THIS EXAMPLE:
<!DOCTYPE html>
<html> <!-- Copyright (c) Nematrian Limited 2018 -->
<head></head>
<body>
<a href="HTMLCSSJSTutorial">
    <canvas id="SmallSpinningRegularPolyhedra">
    HTML / CSS / JavaScript Tutorial
    </canvas>
</a>

<script>
var x2 = document.getElementById("SmallSpinningRegularPolyhedra");
x2.setAttribute("width","250");
x2.setAttribute("height","52");
x2.setAttribute("style","border: 1px solid black");
var c2 = x2.getContext("2d");

var shapes = [regularTetrahedron(),
    regularCube(),
    regularOctahedron(),
    regularDodecahedron(),
    regularIcosahedron()];
var faceFillStyles = ["rgba(0,127,0,.5)", 
    "rgba(127,0,0,.5)",
    "rgba(0,0,127,.5)",
    "rgba(0,80,127,.5)",
    "rgba(100,160,80,.5)"];
var edgeStrokeStyles = ["rgba(0,0,0,.5)",
    "rgba(0,0,0,.5)",
    "rgba(0,0,0,.5)",
    "rgba(0,0,0,.5)",
    "rgba(0,0,0,.5)"];
var rotAxes = [[1, 0, .7],
    [1, 1, 0.3],
    [0, 1, .6],
    [1, 1, .6],
    [1, 0, .7]];
var initialRotAngles = [0, 0, 0, 0, 0];
var xOrigins = [25, 75, 125, 175, 225];
var yOrigins = [35, 35, 35, 35, 35];
var scalings = [8, 8, 12, 8, 8];
var rotSpeeds = [0.1, .3, .11, .12, .16]; // in seconds
var xAxis = [1, 0, 0];
var yAxis = [0, 1, 0];
var c = 0;
var frameRate = 50; // in milliseconds

animate3dShapeOnCanvas(x2, c2, shapes, faceFillStyles, edgeStrokeStyles,
        rotAxes, initialRotAngles, xOrigins, yOrigins, scalings,
        rotSpeeds, xAxis, yAxis, c, frameRate)

function animate3dShapeOnCanvas(canvas, ctx, shapes, faceFillStyles, edgeStrokeStyles,
        rotAxes, initialRotAngles, xOrigins, yOrigins, scalings,
        rotSpeeds, xAxis, yAxis, c, frameRate) {
    var projectionID = null; // The setInterval() ID value for animation
    var i;
    var noShapes = shapes.length;
    var dynamicRotAngles = new Array;
    for (i = 0; i < noShapes; i++) {
        dynamicRotAngles[i] = initialRotAngles[i];
    }

    projectionIncrement();
    projectionID = setInterval(projectionIncrement, frameRate);

    function projectionIncrement() {
        ctx.fillStyle = "white";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        var i;
        for (i = 0; i < noShapes; i++) {
            dynamicRotAngles[i] += rotSpeeds[i] * 2 * Math.PI * frameRate / 1000;
            ctx.beginPath();
            project3dShapeOnCanvas(ctx, shapes[i], xAxis, yAxis, c,
                faceFillStyles[i], edgeStrokeStyles[i],
                rotAxes[i], dynamicRotAngles[i],
                xOrigins[i], yOrigins[i], scalings[i]);
        }
        ctx.fillStyle = "maroon";
        ctx.font = "16px Arial";
        ctx.fillText("HTML / CSS / JavaScript Tutorial", 8, 16);
    }
}

function project3dShapeOnCanvas(ctx, shape, xAxis, yAxis, c,
        faceFillStyle, edgeStrokeStyle,
        rotAxis, rotAngle, xOrigin, yOrigin, scaling) {

    var unitisedRotAxis = unitiseVector(rotAxis);
    var rot = rotationMatrix(unitisedRotAxis, rotAngle);
    var n = unitiseVector(vectorCrossProduct(xAxis, yAxis));
    //n is unit vector perpendicular to plane formed by xAxis and yAxis
    var i, j, x, y, a, ax, ay, az;
    var rotatedPoint, rx, ry, rz;

    var noVertices = shape[0].length;
    var noEdges = shape[1].length;
    var noFaces = shape[2].length;

    var rotatedVertices = new Array;

    for (i = 0; i < noVertices; i++) {
        a = shape[0][i];
        ax = a[0];
        ay = a[1];
        az = a[2];
        rx = rot[0][0] * ax + rot[0][1] * ay + rot[0][2] * az;
        ry = rot[1][0] * ax + rot[1][1] * ay + rot[1][2] * az;
        rz = rot[2][0] * ax + rot[2][1] * ay + rot[2][2] * az;
        rotatedPoint = [rx, ry, rz];
        rotatedVertices[i] = rotatedPoint
    }

    var projectedPoints = new Array;
    for (i = 0; i < noVertices; i++) {
        a = rotatedVertices[i];
        projectedPoints[i] = projectPointOntoPlane(a, n, c);
    }

    var startPointIndex, endPointIndex, startPoint, endPoint;
    var noFaceVertices;

    //draw faces
    ctx.fillStyle = faceFillStyle;
    for (i = 0; i < noFaces; i++) {
        noFaceVertices = shape[2][i].length;
        startPointIndex = shape[2][i][0];
        startPoint = projectedPoints[startPointIndex];
        x = vectorDotProduct(xAxis, startPoint);
        y = vectorDotProduct(yAxis, startPoint);
        ctx.beginPath();
        ctx.moveTo(xOrigin + x * scaling, yOrigin + y * scaling);
        for (j = 1; j < noFaceVertices; j++) {
            endPointIndex = shape[2][i][j];
            endPoint = projectedPoints[endPointIndex];
            x = vectorDotProduct(xAxis, endPoint);
            y = vectorDotProduct(yAxis, endPoint);
            ctx.lineTo(xOrigin + x * scaling, yOrigin + y * scaling);
        }
        ctx.fill();
    }

    // draw edges
    ctx.strokeStyle = edgeStrokeStyle;
    for (i = 0; i < noEdges; i++) {
        startPointIndex = shape[1][i][0];
        endPointIndex = shape[1][i][1];
        startPoint = projectedPoints[startPointIndex];
        endPoint = projectedPoints[endPointIndex];
        x = vectorDotProduct(xAxis, startPoint);
        y = vectorDotProduct(yAxis, startPoint);
        ctx.beginPath();
        ctx.moveTo(xOrigin + x * scaling, yOrigin + y * scaling);
        x = vectorDotProduct(xAxis, endPoint);
        y = vectorDotProduct(yAxis, endPoint);
        ctx.lineTo(xOrigin + x * scaling, yOrigin + y * scaling);
        ctx.stroke();
    }
}

function regularTetrahedron() {
    var vertices = [[1, 1, 1], [1, -1, -1], [-1, 1, -1], [-1, -1, 1]];
    var edges = [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]];
    var faces = [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]];
    var ans = [vertices, edges, faces];
    return ans;
}

function regularCube() {
    var vertices = [[1, 1, 1], [1, 1, -1], [1, -1, 1], [1, -1, -1],
                [-1, 1, 1], [-1, 1, -1], [-1, -1, 1], [-1, -1, -1]];
    var edges = [[0, 1], [0, 2], [0, 4], [1, 3], [1, 5],
                [2, 3], [2, 6], [3, 7], [4, 5], [4, 6], [5, 7], [6, 7]];
    var faces = [[0, 1, 3, 2], [0, 1, 5, 4], [1, 5, 7, 3],
                [0, 2, 6, 4], [4, 5, 7, 6], [2, 3, 7, 6]];
    var ans = [vertices, edges, faces];
    return ans;
}

function regularOctahedron() {
    var vertices = [[1, 0, 0], [-1, 0, 0], [0, 1, 0], [0, -1, 0],
                [0, 0, 1], [0, 0, -1]];
    var edges = [[0, 2], [2, 1], [1, 3], [3, 0],
                [4, 0], [4, 2], [4, 1], [4, 3],
                [5, 0], [5, 2], [5, 1], [5, 3]];
    var faces = [[0, 2, 4], [0, 3, 4], [1, 2, 4], [1, 3, 4],
                [0, 2, 5], [0, 3, 5], [1, 2, 5], [1, 3, 5]];
    var ans = [vertices, edges, faces];
    return ans;
}

function regularDodecahedron() {
    var phi1 = (1 + Math.sqrt(5)) / 2;
    var phi2 = 1/phi1;
    var vertices = [[1, 1, 1], [1, 1, -1], [1, -1, 1], [1, -1, -1],
                [-1, 1, 1], [-1, 1, -1], [-1, -1, 1], [-1, -1, -1],
                [0, phi1, phi2], [0, phi1, -phi2], [0, -phi1, phi2], [0, -phi1, -phi2],
                [phi2, 0, phi1], [-phi2, 0, phi1], [phi2, 0, -phi1], [-phi2, 0, -phi1],
                [phi1, phi2, 0], [phi1, -phi2, 0], [-phi1, phi2, 0], [-phi1, -phi2, 0]];
    var edges = [[0, 12], [0, 8], [0, 16], [1, 9], [1, 14], [1, 16],
                [2, 10], [2, 12], [2, 17], [3, 11], [3, 14], [3, 17],
                [4, 8], [4, 13], [4, 18], [5, 9], [5, 15], [5, 18],
                [6, 10], [6, 13], [6, 19], [7, 11], [7, 15], [7, 19],
                [8, 9], [10, 11], [12, 13], [14, 15], [16, 17], [18, 19]];
    var faces = [[0, 12, 2, 17, 16], [0, 8, 4, 13, 12], [0, 8, 9, 1, 16],
                [1, 14, 3, 17, 16], [1, 9, 5, 15, 14], [2, 10, 11, 3, 17],
                [2, 12, 13, 6, 10], [3, 11, 7, 15, 14], [4, 13, 6, 19, 18],
                [4, 8, 9, 5, 18], [5, 15, 7, 19, 18],[6,10,11,7,19]];
    var ans = [vertices, edges, faces];
    return ans;
}

function regularIcosahedron() {
    var phi = (1 + Math.sqrt(5)) / 2;
    var vertices = [[0, 1, phi], [0, 1, -phi], [0, -1, phi], [0, -1, -phi],
                [1, phi, 0], [1, -phi, 0], [-1, phi, 0], [-1, -phi, 0],
                [phi, 0, 1], [phi, 0, -1], [-phi, 0, 1], [-phi, 0, -1]];
    var edges = [[0, 2], [0, 4], [0, 6], [0, 8], [0, 10],
                [1, 3], [1, 4], [1, 6], [1, 9], [1, 11],
                [2, 5], [2, 7], [2, 8], [2, 10],
                [3, 5], [3, 7], [3, 9], [3, 11],
                [4, 6], [4, 8], [4, 9], [5, 7], [5, 8], [5, 9],
                [6, 10], [6, 11], [7, 10], [7, 11], [8, 9], [10, 11]];
    var faces = [[0, 2, 8], [0, 2, 10], [0, 4, 6], [0, 4, 8], [0, 6, 10],
                [1, 3, 9], [1, 3, 11], [1, 4, 6], [1, 4, 9], [1, 6, 11],
                [2, 5, 7], [2, 5, 8], [2, 7, 10],
                [3, 5, 7], [3, 5, 9], [3, 7, 11],
                [4, 8, 9], [5, 8, 9], [6, 10, 11], [7, 10, 11]];
    var ans = [vertices, edges, faces];
    return ans;
}

function projectPointOntoPlane(a, n, c) {
    // a is the point we want to project
    // plane is defined by normal direction n (3-vector) and c = x.n
    // where x are points on plane and "." is the dot product
    var ans = [0, 0, 0];
    var d;
    d = a[0] * n[0] + a[1] * n[1] + a[2] * n[2] - c;
    ans[0] = a[0] - d * n[0];
    ans[1] = a[1] - d * n[1];
    ans[2] = a[2] - d * n[2];
    return ans;
}

function rotationMatrix(u, theta) {
    //u is the axis of rotation (unit vector)
    //theta is the amount of rotation around axis
    //returned is a 3 x 3 matrix representing transformation
    var ans = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
    var c = Math.cos(theta);
    var c1 = 1 - c;
    var s = Math.sin(theta);
    var ux = u[0];
    var uy = u[1];
    var uz = u[2];
    var uxs = ux * s;
    var uys = uy * s;
    var uzs = uz * s;
    var uxc1 = ux * c1;
    var uxuyc1 = uy * uxc1;
    var uxuzc1 = uz * uxc1;
    var uyuzc1 = uy * uz * c1;
    ans[0][0] = c + ux * uxc1;
    ans[0][1] = uxuyc1 - uzs;
    ans[0][2] = uxuzc1 + uys;
    ans[1][0] = uxuyc1 + uzs;
    ans[1][1] = c + uy * uy * c1;
    ans[1][2] = uyuzc1 - uxs;
    ans[2][0] = uxuzc1 - uys;
    ans[2][1] = uyuzc1 + uxs;
    ans[2][2] = c + uz * uz * c1;
    return ans;
}

function unitiseVector(u){
    //returns a vector in same direction
    //but of unit length
    var ux = u[0];
    var uy = u[1];
    var uz = u[2];
    var s = ux * ux + uy * uy + uz * uz;
    var s = Math.sqrt(s);
    var ans = [ux / s, uy / s, uz / s];
    return ans;
}

function vectorCrossProduct(u, v) {
    var ux = u[0];
    var uy = u[1];
    var uz = u[2];
    var vx = v[0];
    var vy = v[1];
    var vz = v[2];
    var ans = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
    return ans;
}

function vectorDotProduct(u, v) {
    var ans = u[0] * v[0] + u[1] * v[1] + u[2] * v[2];
    return ans;
}

</script>

</body>
</html>


NAVIGATION LINKS
Contents | Prev


Desktop view | Switch to Mobile