116 lines
5.3 KiB
JavaScript
116 lines
5.3 KiB
JavaScript
// © Головин Г.Г., Код с комментариями, 2023
|
||
'use strict';
|
||
// матрицы-шаблоны для кубиков
|
||
const shape1 = [ // пространственный крест
|
||
[[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]]];
|
||
const shape2 = [ // крест-куб
|
||
[[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,1,0,0], [0,0,0,0,0], [1,0,0,0,1], [0,0,0,0,0], [0,0,1,0,0]],
|
||
[[1,1,1,1,1], [1,0,0,0,1], [1,0,0,0,1], [1,0,0,0,1], [1,1,1,1,1]],
|
||
[[0,0,1,0,0], [0,0,0,0,0], [1,0,0,0,1], [0,0,0,0,0], [0,0,1,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]]];
|
||
// размер кубика, количество кубиков в ряду, отступ
|
||
const size = 40, row = 5, gap = 50;
|
||
// массивы для кубиков
|
||
const cubes1 = [], cubes2 = [];
|
||
// обходим матрицы, заполняем массивы кубиками
|
||
for (let x=0; x<row; x++)
|
||
for (let y=0; y<row; y++)
|
||
for (let z=0; z<row; z++) {
|
||
if (shape1[x][y][z]==1)
|
||
cubes1.push(new Cube(x*size+gap,y*size+gap,z*size+gap,size));
|
||
if (shape2[x][y][z]==1)
|
||
cubes2.push(new Cube(x*size+gap,y*size+gap,z*size+gap,size));
|
||
}
|
||
// центр фигуры, вокруг него будем выполнять поворот
|
||
const t0 = new Point(150,150,150);
|
||
// удалённость центра проекции
|
||
const d = 300;
|
||
// положение экрана наблюдателя
|
||
const tv = new Point(150,150,125);
|
||
// угол поворота в градусах
|
||
const deg = {x:1,y:1,z:1};
|
||
|
||
// рисовать будем по две картинки для каждой фигуры
|
||
const canvas1 = document.getElementById('canvas1');
|
||
const canvas2 = document.getElementById('canvas2');
|
||
const canvas3 = document.getElementById('canvas3');
|
||
const canvas4 = document.getElementById('canvas4');
|
||
// обновление изображения
|
||
function repaint() {
|
||
// пространственный крест
|
||
processFigure(cubes1,canvas1,canvas2);
|
||
// крест-куб
|
||
processFigure(cubes2,canvas3,canvas4);
|
||
}
|
||
|
||
// поворачиваем фигуру и получаем проекции
|
||
function processFigure(cubes,cnv1,cnv2) {
|
||
// массивы проекций граней кубиков
|
||
let parallel = [], perspective = [];
|
||
// поворачиваем кубики и получаем проекции
|
||
for (let cube of cubes) {
|
||
cube.rotate(deg, t0);
|
||
parallel = parallel.concat(cube.projection('parallel',tv,d));
|
||
perspective = perspective.concat(cube.projection('perspective',tv,d));
|
||
}
|
||
// смежные стенки между соседними кубиками не рисуем
|
||
noAdjacent(parallel);
|
||
noAdjacent(perspective);
|
||
// сортируем грани разных кубиков по удалённости и внутри одного кубика по наклону
|
||
parallel.sort((a,b)=>Math.abs(b.dist-a.dist)>size ? b.dist-a.dist : b.clock-a.clock);
|
||
// сортируем грани по удалённости от центра проекции
|
||
perspective.sort((a,b)=>b.dist-a.dist);
|
||
// рисуем параллельную проекцию
|
||
drawFigure(cnv1, parallel);
|
||
// рисуем перспективную проекцию
|
||
drawFigure(cnv2, perspective);
|
||
}
|
||
|
||
// смежные стенки между соседними кубиками не рисуем
|
||
function noAdjacent(array) {
|
||
// сортируем грани по удалённости
|
||
array.sort((a,b) => b.dist-a.dist);
|
||
// удаляем смежные стенки между кубиками
|
||
for (let i=0, j=1; i<array.length-1; j=++i+1)
|
||
while (j<array.length && Cube.pEquidistant(array[i],array[j]))
|
||
if (Cube.pAdjacent(array[i],array[j])) {
|
||
array.splice(j,1);
|
||
array.splice(i,1);
|
||
i--; j=array.length;
|
||
} else j++;
|
||
}
|
||
|
||
// рисуем фигуру по точкам из массива
|
||
function drawFigure(canvas, proj, alpha=0.8) {
|
||
const context = canvas.getContext('2d');
|
||
// очищаем весь холст целиком
|
||
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 = 1.9;
|
||
context.lineJoin = 'round';
|
||
context.fillStyle = 'rgba(200,230,201,'+alpha+')';
|
||
context.strokeStyle = 'rgba(102,187,106,'+(0.2+alpha)+')';
|
||
context.fill();
|
||
context.stroke();
|
||
}
|
||
}
|
||
|
||
// после загрузки страницы, задаём частоту обновления изображения 20 Гц
|
||
document.addEventListener('DOMContentLoaded',()=>setInterval(repaint,50));
|