1/jekyll_site/en/2023/01/11/spinning-cube-in-space.md
2023-12-17 08:21:20 +03:00

9.6 KiB

title description sections tags scripts styles canonical_url url_translated title_translated date lang
Spinning cube in space We consider the difference between parallel and perspective projection. Both are widely used in practice for various purposes. In the previous example, we...
Linear perspective
Rotation matrix
Experimental model
javascript
online
canvas
geometry
graphics
image
picture
square
cube
/js/classes-point-cube.js
/js/spinning-cube.js
/js/spinning-cube2.js
/css/pomodoro1.css
/en/2023/01/11/spinning-cube-in-space.html /ru/2023/01/10/spinning-cube-in-space.html Вращаем куб в пространстве 2023.01.11 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.

Complicated model, many cubes: [Spinning spatial cross]({{ '/en/2023/01/16/spinning-spatial-cross.html' | relative_url }}).

Parallel projection

Your browser does not support Canvas

Perspective projection

Your browser does not support Canvas

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.

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 change the position of the projection center onto the observer screen.

Your browser does not support Canvas

Rotation around axes:
Center onto observer screen:
150
150
60
Remoteness of projection center:
300

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.&\" %}

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}." %}

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)}." %}

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 a translucent 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.

Implementation in JavaScript

{% include classes-point-cube-en.md -%}

Create an object and draw two projections on the plane.

'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};
// 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));
}
// 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();
  }
}
// after loading the page, set the image refresh rate at 20 Hz
document.addEventListener('DOMContentLoaded',()=>setInterval(repaint,50));