Космический корабль

3D модель космического корабля. Модель состоит из вершин, которые образуют полигоны. (Для удобства моделька Корабля была создана в Blender 3D для получения координат вершин Корабля и набора вершин для полигонов)

Вершины хранятся в памяти как объекты класса Vector3D:

class Vector3D{
 
    // Конструктор для создания нового обьекта типа Vector3D
    constructor(x, y, z){
        this.x = x;
        this.y = y;
        this.z = z;
    }
 
    // Функция класса для получения экранной проекции
    getProjection(obj, d){
        let x = this.x*d/(d+this.z+obj.origin.z);
	let y = this.y*d/(d+this.z+obj.origin.z);
 
	return new Point(x + obj.origin.x, y + obj.origin.y);
    }
 
    // Функция класса для изменения размера
    scale(scaleX, scaleY, scaleZ){
        this.x = scaleX * this.x;
        this.y = scaleY * this.y;
        this.z = scaleZ * this.z;
    }
 
    // Функция класса для перемещения обьекта
    move(obj, newX, newY, newZ){
        this.x = this.x + newX;
        this.y = this.y + newY;
        this.z = this.z + newZ;
    }
 
    // Функция класса для вращения обьекта
    // вызывает функции для вращения вокруг определенной оси XY, YZ, XZ
    rotate(origin, angleXY, angleYZ, angleXZ){        
        if (angleXY != 0) { this.rotateXY(origin, angleXY); }
        if (angleYZ != 0) { this.rotateYZ(origin, angleYZ); }
        if (angleXZ != 0) { this.rotateXZ(origin, angleXZ); }
    }
 
    rotateXY(origin, angle){
        const cos = Math.cos(angle);
        const sin = Math.sin(angle);
 
        const newX = (this.x - origin.x) * cos - (this.y - origin.y) * sin;
        const newY = (this.x - origin.x) * sin + (this.y - origin.y) * cos;
        const newZ = this.z;
 
        this.x = newX + origin.x;
        this.y = newY + origin.y;
        this.z = newZ;
    }
 
    rotateYZ(origin, angle){
        const cos = Math.cos(angle);
        const sin = Math.sin(angle);
 
        const newX = this.x
        const newY = (this.y - origin.y) * cos - (this.z -origin.z) * sin;
        const newZ = (this.y - origin.y) * sin + (this.z -origin.z) * cos;
 
        this.x = newX;
        this.y = newY + origin.y;
        this.z = newZ + origin.z;
    }
 
    rotateXZ(origin, angle){
        const cos = Math.cos(angle);
        const sin = Math.sin(angle);
 
        const newX = (this.x - origin.x) * cos - (this.z - origin.z) * sin;
        const newY = this.y;
        const newZ = (this.x - origin.x) * sin + (this.z - origin.z) * cos;
 
        this.x = newX + origin.x;
        this.y = newY;
        this.z = newZ + origin.z;
    }
}

Вершины хранятся в классе SpaceShip, который является дочерним для Mash. То есть SpaceShip наследует все переменные и методы класса Mash

// Родительский класс для всех именованных мешей (Куб, Сфера, Корабль)
class Mash{
 
    constructor(color='white'){
        this.origin = {
            x: 0,
            y: 0,
            z: 0
        };
 
        this.color = color;
 
        this.barycenter = 0;
 
        this.axisSet = [];
    }
 
    // Функция для расчета барицентра меша 
    calcBarycenter(){
        let sumX = 0;
        let sumY = 0;
        let sumZ = 0;
 
        this.vectorSet.forEach(V => {
            sumX += V.x;
            sumY += V.y;
            sumZ += V.z;
        });
 
        let len = this.vectorSet.length;
 
        this.barycenter = new Vector3D(sumX/len, sumY/len, sumZ/len);
    }
 
    // Функция для вращения меша 
    rotateMash(angleXY = 0, angleYZ = 0, angleXZ = 0, origin=this.barycenter){
        this.vectorSet.forEach(V => {
            V.rotate(origin, angleXY, angleYZ, angleXZ);
        });
 
        this.calcBarycenter();
    }
 
    // Функция для перемещения меша 
    moveMash(dX = 0, dY = 0, dZ = 0){
        this.origin.x += dX;
        this.origin.y += dY;
        this.origin.z += dZ;
 
        this.vectorSet.forEach(V => {
            V.move(this, dX, dY, dZ);
        });
 
        this.calcBarycenter();
    }
}
 
class SpaceShip extends Mash{
 
    constructor(factor, color){
 
        // Задание цвета для объекта класса SpaceShip, так как переменная для цвета определенна 
        // в родительском классе, нужно вызывать метод super(<переменная>);
        super(color);
 
        // Фактор определяющий размер корабля
        this.f = factor;
 
        this.vectorSet = [
            // Набор вершин. К примеру две вершины определяющие плоскость 
            //с координатами {(1,1,1),(1,-1,1),(-1,-1,1),(-1,1,1)}:
 
            new Vector3D( 1.00*factor,  1.00*factor,  1.00*factor),  // 1
            new Vector3D( 1.00*factor,  -1.00*factor, 1.00*factor),  // 2 
            new Vector3D( -1.00*factor,  -1.00*factor, 1.00*factor), // 3
            new Vector3D( -1.00*factor,  1.00*factor, 1.00*factor)   // 4
 
            // Реальный набор вершин корабля содержит 359 вершин
 
        ];
 
        this.sideSet = [
            // Набор полигонов заданные вершинами. 
            // К примеру четыре вершины определяющие полигон 
            //с координатами {(1,1,1),(1,-1,1),(-1,-1,1),(-1,1,1)}:
                [1, 2, 3, 4]
 
            // Реальный набор полигонов корабля содержит 360 полигонов
        ];
 
        this.calcBarycenter();
    }
}

В коде есть класс scene3D, который облегчает работу со сценой. Класс содержит методы для:

  • Создания именованного меша (makeCube, makeSphere, makeSpaceShip)
  • Отрисовки всех объектов сцены
  • Перемещения всей сцены, поворот всей сцены
  • Дополнительные средства отображения

Основная работа происходит в функции main()

function main() {
 
    // Высота и Ширина экрана 
    obj.width = window.innerWidth;
    obj.height = window.innerHeight;
 
    const W = obj.width;
    const H = obj.height;
 
    // Установка origin - обьект который задает центральную точку отрисовки 
    //(по умолчанию это всегда начало координат (0,0,0))
    // У обьекта Mash так же есть origin, это позволяет найти локальную 
    //центральную точку для меша относительно глобальной центральной точки
    const ORIGIN = {
        x: Math.round(W/2),
        y: Math.round(H/2),
        z: 0
    };
 
    ctx.fillStyle = 'black';
    ctx.fillRect(0,0,W,H);
 
    // Создание обьекта типа scene3D с аргументами origin=ORIGIN и дальность до наблюдателя d=500
    const SCENE = new scene3D(ORIGIN, 500);
 
    SCENE.isDrawOrigin = false;
    SCENE.isDrawBarycenter = false;
    SCENE.isDrawAxis = false;
    SCENE.onliVisible = false;
    SCENE.isFill = false;
 
    // Создание переменной a типа SpaceShip. Для создания использовался класс Scene3D и его обьект SCENE
    // Здесь SCENE.makeSpaceShip(
                                //<начальная координата X>, 
                                //<начальная координата Y>, 
                                //<начальная координата Z>, 
                                //<Фактор определяющий размер>, 
                                //<Цвет в виде строки>);
    let a = SCENE.makeSpaceShip(0, 0, 0, 50, 'red');
    SCENE.drawScene();
 
    SCENE.rotateScene(0, Math.PI/2, 0);
 
    // Функция play() реализующая вращение Корабля
    function play() {
        ctx.fillStyle = 'black';
        ctx.fillRect(0,0,W,H);
 
 
        a.rotateMash(0.01, 0.01, 0.01);
 
        SCENE.drawScene();
 
        requestAnimationFrame(play);
    }
 
    play();
 
}