--- title: Spinning cube in space description: We consider the difference between parallel and perspective projection. Both are widely used in practice for various purposes. In the previous example, we... sections: [Linear perspective,Rotation matrix,Experimental model] tags: [javascript,online,canvas,geometry,graphics,image,picture,square,cube,3d,three-dimensional] scripts: [/js/classes-point-cube.js,/js/spinning-cube.js,/js/spinning-cube2.js] styles: [/css/pomodoro1.css] canonical_url: /en/2023/01/11/spinning-cube-in-space.html url_translated: /ru/2023/01/10/spinning-cube-in-space.html title_translated: Вращаем куб в пространстве date: 2023.01.11 lang: en --- We consider the difference between parallel and perspective projection. Both are widely used in practice for various purposes. In the previous example, we [rotated square on plane]({{ '/en/2023/01/06/spinning-square-on-plane.html' | relative_url }}) — we pass into three-dimensional space. Now, to display the rotation of a three-dimensional object on the screen plane, we first need to create a *mathematical model* of a three-dimensional object, rotate it by an angle, draw a projection from it and display already the projection on the screen. For clarity, we will use the cartesian coordinate system. Complicated model, many cubes: [Spinning spatial cross]({{ '/en/2023/01/16/spinning-spatial-cross.html' | relative_url }}).
Parallel projection

Canvas for displaying computations results

Perspective projection

Canvas for displaying computations results

*Parallel projection* — the projection center is infinitely distant from the plane of the observer screen, dimensions of the objects look the same. *Perspective projection* — parallel lines converge in the center of the perspective, objects appear to shrink in the distance. {% include heading.html text="Experimental model" hash="experimental-model" %} Cube size 200, canvas size 300, origin of coordinates is in the upper left corner. The center of the figure is in the middle of the canvas. The `X` axis is directed to the right, the `Y` axis is directed downwards, the `Z` axis is directed to the distance. The rotation is performed sequentially around all three axes: first around the `X` axis, then around the `Y` axis and then around the `Z` axis. Model settings can be controlled, for example, you can switch off redundant rotation around the axes and move the central point of the projection onto the observer screen.

Canvas for displaying computations results

Rotation around axes:
Center onto observer screen:
150
150
60
Remoteness of projection center:
300
{% include heading.html text="Point rotation in space" hash="point-rotation-in-space" %} We calculate the new coordinates of the point using the formulas of the rotation matrix for three-dimensional space. We rotate the point `t` relative to the point `t0` — we get the point `t'`. *Rotation along `X` axis.* {% include image_svg.html src="/img/column-vector3dx.svg" style="width: 242.619pt; height: 59.0768pt;" alt="&x'=x,&\\&y'=y_0+(y-y_0)cos\varphi-(z-z_0)sin\varphi,&\\&z'=z_0+(y-y_0)sin\varphi+(z-z_0)cos\varphi.&\\" %} *Rotation along `Y` axis.* {% include image_svg.html src="/img/column-vector3dy.svg" style="width: 246.251pt; height: 59.0768pt;" alt="&x'=x_0+(x-x_0)cos\varphi-(z-z_0)sin\varphi,&\\&y'=y,&\\&z'=z_0+(x-x_0)sin\varphi+(z-z_0)cos\varphi.&\\" %} *Rotation along `Z` axis.* {% include image_svg.html src="/img/column-vector3dz.svg" style="width: 246.793pt; height: 55.4753pt;" alt="&x'=x_0+(x-x_0)cos\varphi-(y-y_0)sin\varphi,&\\&y'=y_0+(x-x_0)sin\varphi+(y-y_0)cos\varphi,&\\&z'=z.&\\" %} {% include heading.html text="Point projection" hash="point-projection" %} Experimental formulas with the possibility of shifting the projection center `d0` on the observer screen `tv`. We map the point of space `t` to the plane of the screen — we get the point `t'`. *Parallel projection.* {% include image_svg.html src="/img/oblique-projection.svg" style="width: 123.97pt; height: 37.2836pt;" alt="&x'=x,&\\&y'=y+(y_v-z)/4.&\\" %} *Perspective projection.* {% include image_svg.html src="/img/central-projection.svg" style="width: 231.924pt; height: 37.2836pt;" alt="&x'=x_v+d_0\cdot(x-x_v)/(z-z_v+d_0),&\\&y'=y_v+d_0\cdot(y-y_v)/(z-z_v+d_0).&\\" %} *Distance from the point to the projection center.* {% include image_svg.html src="/img/euclidean-distance.svg" style="width: 319.911pt; height: 17.9328pt;" alt="d(t,d_0)=\sqrt{(x-x_v)^2+(y-y_v)^2+(z-z_v+d_0)^2}." %} {% include heading.html text="Face sorting" hash="face-sorting" %} When creating a cube, we set the vertices of each face clockwise. When obtaining a projection, we substitute three consecutive vertices into the equation of a line, to determine the tilt of the face and its remoteness from the projection plane. *Equation of the line, that passes through two points.* {% include image_svg.html src="/img/linear-equation.svg" style="width: 137.171pt; height: 35.3194pt;" alt="{(x-x_1)\over(y-y_1)}={(x_2-x_1)\over(y_2-y_1)}." %} {% include heading.html text="Algorithm description" hash="algorithm-description" %} First, we bypass the vertices of the cube and rotate them by an angle relative to the center point. Then we bypass the faces of the cube and get projections of the vertices included in them. After that, we sort the projections of the faces by remoteness. Then we draw projections on the plane — we link the points with lines. We draw with an almost transparent color first the far faces and atop them the near ones, so that the far faces can be seen through the near ones. At each step of displaying the figure, we repeat the sorting of the faces by remoteness, since with a change in the angle of rotation, the coordinates shift, and the near faces become far. {% include heading.html text="Implementation in JavaScript" hash="implementation-in-javascript" %} {% include classes-point-cube-en.md -%} Create an object and draw two projections on the plane. ```js 'use strict'; // we will draw two pictures at once, there will be // one object, and there will be many projections const canvas1 = document.getElementById('canvas1'); const canvas2 = document.getElementById('canvas2'); // create an object const cube = new Cube(50,50,50,200); // figure center, we'll perform a rotation around it const t0 = new Point(150,150,150); // remoteness of the projection center const d = 300; // observer screen position const tv = new Point(150,150,80); // rotation angle in degrees const deg = {x:0,y:1,z:0}; ``` ```js // figure rotation and image refresh function repaint() { cube.rotate(deg, t0); // draw parallel projection drawFigure(canvas1, cube.projection('parallel', tv)); // draw perspective projection drawFigure(canvas2, cube.projection('perspective', tv, d)); } ``` ```js // draw a figure by points from an array function drawFigure(canvas, proj) { let context = canvas.getContext('2d'); // sort the faces by their tilt proj.sort((a,b) => b.clock-a.clock); // clear the entire canvas context.clearRect(0, 0, canvas.width, canvas.height); // bypass the array of cube faces for (let i = 0; i < proj.length; i++) { // bypass the array of points and link them with lines context.beginPath(); for (let j = 0; j < proj[i].length; j++) { if (j == 0) { context.moveTo(proj[i][j].x, proj[i][j].y); } else { context.lineTo(proj[i][j].x, proj[i][j].y); } } context.closePath(); // draw the face of the cube along with the edges context.lineWidth = 2.2; context.lineJoin = 'round'; context.fillStyle = '#fff9'; context.strokeStyle = '#222'; context.fill(); context.stroke(); } } ``` ```js // after loading the page, set the image refresh rate at 20 Hz document.addEventListener('DOMContentLoaded',()=>setInterval(repaint,50)); ```