135 lines
6.6 KiB
JavaScript
135 lines
6.6 KiB
JavaScript
// © Головин Г.Г., Код с комментариями, 2023
|
||
'use strict';
|
||
// Класс Точка трёхмерного пространства содержит методы для поворотов на угол и для получения проекций
|
||
// на плоскость. При получении проекций, вычисляется расстояние от точки до центра проекции. Точка также
|
||
// содержит статический метод для сравнения двух проекций точек.
|
||
class Point {
|
||
// координаты точки
|
||
constructor(x,y,z) {
|
||
this.x=x;
|
||
this.y=y;
|
||
this.z=z;
|
||
}
|
||
// поворачиваем эту точку на угол (deg)
|
||
// по осям (x,y,z) относительно точки (t0)
|
||
rotate(deg, t0) {
|
||
// функции для получения синуса и косинуса угла в радианах
|
||
const sin = (deg) => Math.sin((Math.PI/180)*deg);
|
||
const cos = (deg) => Math.cos((Math.PI/180)*deg);
|
||
// получаем новые координаты точки по формулам
|
||
// матрицы поворота для трёхмерного пространства
|
||
let x,y,z;
|
||
// поворот по оси 'x'
|
||
y = t0.y+(this.y-t0.y)*cos(deg.x)-(this.z-t0.z)*sin(deg.x);
|
||
z = t0.z+(this.y-t0.y)*sin(deg.x)+(this.z-t0.z)*cos(deg.x);
|
||
this.y=y; this.z=z;
|
||
// поворот по оси 'y'
|
||
x = t0.x+(this.x-t0.x)*cos(deg.y)-(this.z-t0.z)*sin(deg.y);
|
||
z = t0.z+(this.x-t0.x)*sin(deg.y)+(this.z-t0.z)*cos(deg.y);
|
||
this.x=x; this.z=z;
|
||
// поворот по оси 'z'
|
||
x = t0.x+(this.x-t0.x)*cos(deg.z)-(this.y-t0.y)*sin(deg.z);
|
||
y = t0.y+(this.x-t0.x)*sin(deg.z)+(this.y-t0.y)*cos(deg.z);
|
||
this.x=x; this.y=y;
|
||
}
|
||
// получаем проекцию типа (type) с расстояния (d)
|
||
// на плоскость экрана наблюдателя (tv)
|
||
projection(type, tv, d) {
|
||
let proj = {};
|
||
// получаем проекцию по экспериментальным формулам
|
||
switch (type) {
|
||
case 'parallel': {
|
||
proj.x = this.x;
|
||
proj.y = this.y+(tv.y-this.z)/4;
|
||
break;
|
||
}
|
||
case 'perspective': {
|
||
proj.x = tv.x+d*(this.x-tv.x)/(this.z-tv.z+d);
|
||
proj.y = tv.y+d*(this.y-tv.y)/(this.z-tv.z+d);
|
||
break;
|
||
}
|
||
}
|
||
// вычисляем расстояние до центра проекции
|
||
proj.dist = Math.sqrt((this.x-tv.x)*(this.x-tv.x)
|
||
+(this.y-tv.y)*(this.y-tv.y)
|
||
+(this.z-tv.z+d)*(this.z-tv.z+d));
|
||
return proj;
|
||
}
|
||
// сравниваем две проекции точек (p1,p2),
|
||
// координаты (x,y) должны совпадать
|
||
static pEquals(p1, p2) {
|
||
return Math.abs(p1.x-p2.x)<0.0001
|
||
&& Math.abs(p1.y-p2.y)<0.0001;
|
||
}
|
||
};
|
||
// Класс Куб содержит коллекцию вершин класса Точка и массив граней. Каждая грань — это массив из 4 вершин,
|
||
// выходящих из одной точки и идущих по часовой стрелке. Куб содержит методы для поворота всех вершин на угол
|
||
// и для получения проекций всех граней на плоскость. При получении проекций, вычисляется наклон грани — это
|
||
// удалённость от плоскости проекции. Куб также содержит два статических метода для сравнения двух проекций
|
||
// граней: для определения равноудалённых граней от центра проекции и смежных стенок между соседними кубиками.
|
||
class Cube {
|
||
// левая верхняя ближняя координата и размер
|
||
constructor(x,y,z,size) {
|
||
// правая нижняя дальняя координата
|
||
let xs=x+size,ys=y+size,zs=z+size;
|
||
let v={ // вершины
|
||
t000: new Point(x,y,z), // верх
|
||
t001: new Point(x,y,zs), // верх
|
||
t010: new Point(x,ys,z), // низ
|
||
t011: new Point(x,ys,zs), // низ
|
||
t100: new Point(xs,y,z), // верх
|
||
t101: new Point(xs,y,zs), // верх
|
||
t110: new Point(xs,ys,z), // низ
|
||
t111: new Point(xs,ys,zs)};// низ
|
||
this.vertices=v;
|
||
this.faces=[ // грани
|
||
[v.t000,v.t100,v.t110,v.t010], // передняя
|
||
[v.t000,v.t010,v.t011,v.t001], // левая
|
||
[v.t000,v.t001,v.t101,v.t100], // верхняя
|
||
[v.t001,v.t011,v.t111,v.t101], // задняя
|
||
[v.t100,v.t101,v.t111,v.t110], // правая
|
||
[v.t010,v.t110,v.t111,v.t011]];// нижняя
|
||
}
|
||
// поворачиваем вершины куба на угол (deg)
|
||
// по осям (x,y,z) относительно точки (t0)
|
||
rotate(deg, t0) {
|
||
for (let vertex in this.vertices)
|
||
this.vertices[vertex].rotate(deg, t0);
|
||
}
|
||
// получаем проекции типа (type) с расстояния (d)
|
||
// на плоскость экрана наблюдателя (tv)
|
||
projection(type, tv, d) {
|
||
let proj = [];
|
||
for (let face of this.faces) {
|
||
// проекция грани, массив вершин
|
||
let p = [];
|
||
// кумулятивная удалённость вершин
|
||
p.dist = 0;
|
||
// обходим вершины грани
|
||
for (let vertex of face) {
|
||
// получаем проекции вершин
|
||
let proj = vertex.projection(type, tv, d);
|
||
// накапливаем удалённость вершин
|
||
p.dist+=proj.dist;
|
||
// добавляем в массив вершин
|
||
p.push(proj);
|
||
}
|
||
// вычисляем наклон грани, удалённость от плоскости проекции
|
||
p.clock = ((p[1].x-p[0].x)*(p[2].y-p[0].y)
|
||
-(p[1].y-p[0].y)*(p[2].x-p[0].x))<0;
|
||
proj.push(p);
|
||
}
|
||
return proj;
|
||
}
|
||
// сравниваем две проекции граней (f1,f2), вершины
|
||
// должны быть равноудалены от центра проекции
|
||
static pEquidistant(f1, f2) {
|
||
return Math.abs(f1.dist-f2.dist)<0.0001;
|
||
}
|
||
// сравниваем две проекции граней (f1,f2), координаты
|
||
// точек по главной диагонали (p0,p2) должны совпадать
|
||
static pAdjacent(f1, f2) {
|
||
return Point.pEquals(f1[0],f2[0])
|
||
&& Point.pEquals(f1[2],f2[2]);
|
||
}
|
||
};
|