Two variants of the same figure. Spatial cross in parallel projection. Rotation of three-dimensional objects on all three axes at once.The algorithm is universal and very simple: take three-dimensional matrix and build the cubes by its points. Join the cubes in a single figure and start a rotation. The matrix may be different - the essence remains the same. For this example I took two variants of the spatial cross.1. Matrix - spatial cross (y, x, z)2. Launch the loop through the matrix (three-dimensional array), which builds cubes for each non-zero point.3. Build each cube as array of 6 faces. For example, take the left face. If there is another cube to the left of it, then don't draw this face, in order to avoid intermediate faces inside the figure - the corresponding face of another cube don't draw too. All other faces are build similar way.Now we have three-dimensional object, which consist of cubes, which consist of faces which consist of points. Start the rotation - now at each step first rotate the figure by the angle, then make two-dimensional projection and in the end draw the projection on the screen. ~~Don't draw three-dimensional hologram.~~4. Launch the loop through all points of which the object consists and turn each of them by an angle on each axis. For example, take the rotation on axis (Y). Rotation on axis (X) and (Z) performs similarly.6. Work with projection. Draw each projection of the face of the figure by the points of which it consists.There were major steps of the algorithm. I hope it became clear how it works.

Spatial cross

Cross-cube

JavaScript

// Figure let figure = { shape: [ [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]], [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]], [[0,0,1,0,0], [0,0,1,0,0], [1,1,1,1,1], [0,0,1,0,0], [0,0,1,0,0]], [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]], [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]] ] }; // Center of the figure. // We'll make a turn around it let t0 = { y:figure.shape.length / 2 * size, x:figure.shape[0].length / 2 * size, z:figure.shape[0][0].length / 2 * size };

JavaScript

for (let y = 0; y < figure.shape.length; y++) { for (let x = 0; x < figure.shape[0].length; x++) { for (let z = 0; z < figure.shape[0][0].length; z++) { ... // Build a cube as array of 6 faces ... } } }

JavaScript

// Size of the cube let size = 30; // Left face face = [ {x:x*size, y:y*size, z:z*size}, {x:x*size, y:y*size, z:z*size + size}, {x:x*size, y:y*size + size, z:z*size + size}, {x:x*size, y:y*size + size, z:z*size}, ]; // Central coordinate, // it is needed to calculate the distance from axes (X) and (Z) face[4] = getFaceCentralCoordinate(face); // If there is something to the left, then do not draw this face if (x > 0 && figure.shape[y][x - 1][z] === 1) { face[5] = {display:false}; }

JavaScript

// Turn point t(x,y,z) by an angle (deg) on axis (Y) // relative to the point t0(x,y,z) function rotateByDegreeY(t0, t) { let t_new = {}; // Translate the angle of rotation from degrees to radians let rad = (Math.PI / 180) * deg; // Calculate coordinates of new point according to formula t_new.x = t0.x + (t.x - t0.x) * Math.cos(rad) - (t.z - t0.z) * Math.sin(rad); t_new.y = t.y; t_new.z = t0.z + (t.x - t0.x) * Math.sin(rad) + (t.z - t0.z) * Math.cos(rad); // Return the resulting value return t_new; }

Now the projection of the resulting figure needs to be made. There are only two variants: parallel projection and perspective projection. The difference between them we discussed in a previous article: Rotate the cube in space. For the current example, I chose a parallel projection.

5. Build a parallel projection for each point of the figure.JavaScript

// Parallel projection of a point function getPointParallelProjection(point) { return { x:point.x, y:point.y + point.z / 4}; }

JavaScript

// Draw a figure through points from an array function fillFigure(canvas_context, arr) { canvas_context.lineWidth = 2; canvas_context.strokeStyle = 'rgba(250,250,100,0.3)'; for (let i = 0; i < arr.length; i++) { if (arr[i].length < 5) { canvas_context.beginPath(); if (i < 3) { canvas_context.fillStyle = 'rgba(0,200,0,0.9)'; } else { canvas_context.fillStyle = 'rgba(0,200,0,0.5)'; } for (let j = 0; j < arr[i].length; j++) { if (j === 0) { canvas_context.moveTo(arr[i][j].x, arr[i][j].y); } else { canvas_context.lineTo(arr[i][j].x, arr[i][j].y); } } canvas_context.closePath(); canvas_context.fill(); canvas_context.stroke(); } } }