--- title: Вращаем куб в пространстве description: Рассмотрим разницу между параллельной и перспективной проекцией. Обе широко используются на практике для различных целей. В предыдущем примере мы вращали... sections: [Линейная перспектива,Матрица поворота,Экспериментальная модель] tags: [javascript,онлайн,canvas,геометрия,графика,изображение,картинка,квадрат,куб,3д,трёхмерный] scripts: [/js/classes-point-cube.js,/js/spinning-cube.js,/js/spinning-cube2.js] styles: [/css/pomodoro1.css] canonical_url: /ru/2023/01/10/spinning-cube-in-space.html url_translated: /en/2023/01/11/spinning-cube-in-space.html title_translated: Spinning cube in space date: 2023.01.10 --- Рассмотрим разницу между параллельной и перспективной проекцией. Обе широко используются на практике для различных целей. В предыдущем примере мы [вращали квадрат на плоскости]({{ '/ru/2023/01/05/spinning-square-on-plane.html' | relative_url }}) — переходим в трёхмерное пространство. Теперь, чтобы отобразить на плоскости экрана поворот трёхмерного объекта, нужно сначала создать *математическую модель* трёхмерного объекта, повернуть её на угол, срисовать с неё проекцию и отобразить на экране уже проекцию. Для наглядности будем использовать декартову систему координат. Усложнённая модель, много кубиков: [Вращаем пространственный крест]({{ '/ru/2023/01/15/spinning-spatial-cross.html' | relative_url }}).
Параллельная проекция

Холст для отображения результатов вычислений

Перспективная проекция

Холст для отображения результатов вычислений

*Параллельная проекция* — центр проекции бесконечно удалён от плоскости экрана наблюдателя, размеры предметов выглядят одинаковыми. *Перспективная проекция* — параллельные линии сходятся в центре перспективы, предметы выглядят уменьшающимися вдалеке. {% include heading.html text="Экспериментальная модель" hash="experimental-model" %} Размер куба 200, размер холста 300, начало координат находится в верхнем левом углу. Центр фигуры в середине холста. Ось `X` направлена вправо, ось `Y` направлена вниз, ось `Z` направлена вдаль. Выполняется поворот последовательно по всем трём осям: сначала по оси `X`, затем по оси `Y` и затем по оси `Z`. Настройками модели можно управлять, например можно отключать лишнее вращение по осям и двигать центральную точку проекции на экране наблюдателя.

Холст для отображения результатов вычислений

Вращение по осям:
Центр на экране наблюдателя:
150
150
60
Удалённость центра проекции:
300
{% include heading.html text="Поворот точки в пространстве" hash="point-rotation-in-space" %} Рассчитываем новые координаты точки по формулам матрицы поворота для трёхмерного пространства. Поворачиваем точку `t` относительно точки `t0` — получаем точку `t'`. *Поворот по оси `X`.* {% 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.&\\" %} *Поворот по оси `Y`.* {% 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.&\\" %} *Поворот по оси `Z`.* {% 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="Проекция точки" hash="point-projection" %} Экспериментальные формулы с возможностью смещения центра проекции `d0` на экране наблюдателя `tv`. Отображаем точку пространства `t` на плоскость экрана — получаем точку `t'`. *Параллельная проекция.* {% 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.&\\" %} *Перспективная проекция.* {% 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).&\\" %} *Расстояние от точки до центра проекции.* {% 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="Сортировка граней" hash="face-sorting" %} При создании кубика, вершины каждой грани задаём по часовой стрелке. При получении проекции, подставляем в уравнение прямой три подряд идущие вершины, чтобы определить наклон грани и удалённость её от плоскости проекции. *Уравнение прямой, проходящей через две точки.* {% 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="Описание алгоритма" hash="algorithm-description" %} Сначала обходим вершины куба и поворачиваем их на угол относительно центральной точки. Затем обходим грани куба и получаем проекции входящих в них вершин. После этого сортируем проекции граней по удалённости. Затем рисуем проекции на плоскости — соединяем точки линиями. Рисуем почти прозрачным цветом сперва дальние грани и поверх них ближние, чтобы сквозь ближние грани было видно дальние. На каждом шаге отображения фигуры повторяем сортировку граней по удалённости, так как с изменением угла поворота, координаты смещаются, и ближние грани становятся дальними. {% include heading.html text="Реализация на JavaScript" hash="implementation-in-javascript" %} {% include classes-point-cube-ru.md -%} Создаём объект и рисуем две проекции на плоскости. ```js 'use strict'; // рисовать будем сразу две картинки, // объект будет один, а проекций будет много const canvas1 = document.getElementById('canvas1'); const canvas2 = document.getElementById('canvas2'); // создаём объект const cube = new Cube(50,50,50,200); // центр фигуры, вокруг него будем выполнять поворот const t0 = new Point(150,150,150); // удалённость центра проекции const d = 300; // положение экрана наблюдателя const tv = new Point(150,150,80); // угол поворота в градусах const deg = {x:0,y:1,z:0}; ``` ```js // поворот фигуры и обновление изображения function repaint() { cube.rotate(deg, t0); // рисуем параллельную проекцию drawFigure(canvas1, cube.projection('parallel', tv)); // рисуем перспективную проекцию drawFigure(canvas2, cube.projection('perspective', tv, d)); } ``` ```js // рисуем фигуру по точкам из массива function drawFigure(canvas, proj) { let context = canvas.getContext('2d'); // сортируем грани по их наклону proj.sort((a,b) => b.clock-a.clock); // очищаем весь холст целиком context.clearRect(0, 0, canvas.width, canvas.height); // обходим массив граней куба for (let i = 0; i < proj.length; i++) { // обходим массив точек и соединяем их линиями 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(); // рисуем грань куба вместе с рёбрами context.lineWidth = 2.2; context.lineJoin = 'round'; context.fillStyle = '#fff9'; context.strokeStyle = '#222'; context.fill(); context.stroke(); } } ``` ```js // после загрузки страницы, задаём частоту обновления изображения 20 Гц document.addEventListener('DOMContentLoaded',()=>setInterval(repaint,50)); ```