cherry.png

Вращаем пространственный крест

19.12.2017
checkmark3

HTML5, Canvas

Два варианта одной и той же фигуры. Пространственный крест в параллельной проекции. Вращение объемных объектов по всем трем осям сразу.

Ваш браузер не поддерживает Canvas

Пространственный крест

Ваш браузер не поддерживает Canvas

Крест-куб

Алгоритм универсальный и очень простой: берем трехмерную матрицу, из каждой точки которой строим кубик. Кубики собираем в единую фигуру, и запускаем вращение. Матрица может быть любой - суть от этого не меняется. Для данного примера я взял два варианта пространственного креста.
1. Матрица - пространственный крест (y, x, z)

JavaScript

// Фигура
let figure = {
    shape: [
        [[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]]
    ]
};

// Центр фигуры
let t0 = {
    y:figure.shape.length / 2 * size,
    x:figure.shape[0].length / 2 * size,
    z:figure.shape[0][0].length / 2 * size
};
2. Далее запускаем цикл по матрице (трехмерный массив), в котором строим кубики для каждой ненулевой точки.

JavaScript

for (let y = 0; y < figure.shape.length; y++) {
    for (let x = 0; x < figure.shape[0].length; x++) {
        for (let z = 0; z < figure.shape[0][0].length; z++) {
            ...
            // Строим кубик как массив из 6 граней
            ...
        }
    }
}
3. Каждый кубик строим как массив из 6 граней. Для примера возьмем левую стенку. Если левее нее есть другой кубик, тогда эту грань рисовать не будем, чтобы не было промежуточных стенок внутри фигуры - соответствующую грань другого кубика тоже не рисуем. Все остальные грани строим по аналогии.

JavaScript

// Размер кубика
let size = 30;

// Левая стенка
face = [
    {x:x*size, y:y*size, z:z*size},
    {x:x*size, y:y*size, z:z*size + size},
    {x:x*size, y:y*size + size, z:z*size + size},
    {x:x*size, y:y*size + size, z:z*size},
];

// Центральная координата, нужна для расчета удаленности по осям (X) и (Z)
face[4] = getFaceCentralCoordinate(face);

// Если левее чтото есть, то не рисуем
if (x > 0 && figure.shape[y][x - 1][z] === 1) {
    face[5] = {display:false};
}
Теперь мы имеем трехмерный объект, состоящий из кубиков, которые в свою очередь состоят из граней, а грани - из точек. Запускаем вращение - теперь на каждом шаге будем сперва поворачивать фигуру на угол, потом снимать с нее двухмерную проекцию и в конце отрисовывать на экране уже проекцию.
Трехмерную голограмму отрисовывать не будем.
4. Запускаем цикл по всем точкам, из которых состоит объект, и поворачиваем каждую из них на угол по каждой из осей. Для примера приведу поворот по оси (Y). Аналогично осуществляется поворот по осям (X) и (Z).

JavaScript

// Поворачиваем точку t(x,y,z) на угол (deg) по оси (Y)
// относительно точки t0(x,y,z)
function rotateOnDegreeY(t0, t) {

    let t_new = {};

    // Переводим угол поворота из градусов в радианы
    let rad = (Math.PI / 180) * deg;

    // Рассчитываем координаты новой точки по формуле
    t_new.x = t0.x + (t.x - t0.x) * Math.cos(rad) - (t.z - t0.z) * Math.sin(rad);
    t_new.y = t.y;
    t_new.z = t0.z + (t.x - t0.x) * Math.sin(rad) + (t.z - t0.z) * Math.cos(rad);

    // Возвращаем полученное значение
    return t_new;
}
Теперь с получившейся фигуры нужно снять проекцию. Вариантов есть всего два: параллельная проекция и перспективная проекция. Разницу между ними мы рассматривали в предыдущей статье:Вращаем куб в пространствеДля текущего примера я выбрал параллельную проекцию.
5. Для каждой точки фигуры строим параллельную проекцию.

JavaScript

// Параллельная проекция точки
function getPointParallelProjection(point) {
    return {
        x:point.x,
        y:point.y + point.z / 4};
}
6. Работаем с проекцией. Каждую грань проекции фигуры рисуем по составляющим ее точкам.

JavaScript

// Рисуем фигуру по точкам из массива
function fillFigure(canvas_context, arr) {
    canvas_context.lineWidth = 2;
    canvas_context.strokeStyle = 'rgba(250,250,100,0.3)';

    for (let i = 0; i < arr.length; i++) {
        if (arr[i].length < 5) {
            canvas_context.beginPath();
            if (i < 3) {
                canvas_context.fillStyle = 'rgba(0,200,0,0.9)';
            } else {
                canvas_context.fillStyle = 'rgba(0,200,0,0.5)';
            }
            for (let j = 0; j < arr[i].length; j++) {
                if (j === 0) {
                    canvas_context.moveTo(arr[i][j].x, arr[i][j].y);
                } else {
                    canvas_context.lineTo(arr[i][j].x, arr[i][j].y);
                }
            }
            canvas_context.closePath();
            canvas_context.fill();
            canvas_context.stroke();
        }
    }
}
Вот основные шаги алгоритма. Надеюсь, стало понятно, как он работает. Весь код целиком можно посмотреть здесь:Смотреть код

facebookvkontaktetwitterodnoklassnikimailrulivejournal

Комментарии

O0O0O0O0
Комментатор
01.01.1970 03:00 (MSK)
Комментариев пока нет.. Вы можете стать первым..