Страницы

Поиск по вопросам

четверг, 9 января 2020 г.

Удаленность сторон куба от плоскости проекции

#javascript #алгоритм #графика #3d #геометрия


Дано:


Система координат
Куб вращается вокруг своего центра по всем трем осям.
Угол поворота заранее не известен.
Параллельная проекция считается по формуле:


  x`` = x;
  
  y`` = y + z / 4;

В единицу времени отображаются только 3 ближних к плоскости проекции грани.


Вопрос:

Как определить, какие из граней ближние?

Делаю таким образом: беру попарно грани правую и левую, переднюю и заднюю, верхнюю
и нижнюю, затем вычисляю дальнюю и ближнюю точки по оси (Z), составляю уравнение прямой,
беру вторую по удаленности точку по оси (Z), и подставляю в это уравнение - так определяю
наклон плоскости. Исходя из этого выбираю какая из 2х граней ближе к плоскости проекции.

Такой код не работает. 



'use strict';

    var figures;

    var figure_111 = {

        //╔═══╗
        //║   ║
        //╚═══╝

        figureType: 5,
        figurePositionY: 1,
        figurePositionX: 1,
        figurePositionZ: 1,
        shape: [
            [[5]]
        ],
        rotateZ: function () {
            this.shape = rotateZ(this.shape);
            this.figurePositionZ = changePosition(this.figurePositionZ);
        }
    };

    function initAnimation() {
        figures = [
            figure_111
        ];

        // Запускаем падение
        figureStartsFalling();
    }

    // Текущая фигура
    var currentFigure;

    // Размер фигуры
    var size = 30;

    // Объект на основании фигуры
    var fallingObject;

    // Цвет фигуры
    var figureColor;

    // Центр фигуры
    var t0;

    var canvas = document.getElementById('animation');
    var canvas_context = canvas.getContext('2d');

    // Сместим центр оси (Х) в середину поля
    canvas_context.translate(canvas.width / 2, 0);

    var doTenTimes;

    function figureStartsFalling() {
        doTenTimes = 30;
        // Переопределяем текущую фигуру
        currentFigure = figures[Math.floor(Math.random() * figures.length)];

        // Получим цвет текущей фигуры
        figureColor = 'rgb(0,200,0)';

        // Высота фигуры
        var figureHeight = currentFigure.shape.length;

        // Строим 3Д объект на основании фигуры
        fallingObject = [];
        var cube = [];
        var side = [];

        for (var y = currentFigure.shape.length - 1; y > -1; y--) {
            for (var x = 0; x < currentFigure.shape[0].length; x++) {
                for (var z = 0; z < currentFigure.shape[0][0].length; z++) {
                    // Для каждой клеточки создаем кубик с 5 гранями
                    cube = [];
                    if (currentFigure.shape[y][x][z] != 0) {
                        // Левая стенка
                        side = [
                            {x:x*size, y:y*size - figureHeight*size, z:z*size},
                            {x:x*size, y:y*size - figureHeight*size, z:z*size + size},
                            {x:x*size, y:y*size + size - figureHeight*size, z:z*size
+ size},
                            {x:x*size, y:y*size + size - figureHeight*size, z:z*size},
                            {m:'left'}
                        ];
                        // Центральная координата, нужна для расчета удаленности
по осям (X) и (Z)
                        side[4] = getSidecentralCoordinate(side);
                        // Если левее чтото есть, то не рисуем
                        if (x > 0
                            && currentFigure.shape[y][x - 1][z] != 0) {
                            side[5] = false;
                        }
                        cube.push(side);

                        // Правая стенка
                        side = [
                            {x:x*size + size, y:y*size - figureHeight*size, z:z*size},
                            {x:x*size + size, y:y*size - figureHeight*size, z:z*size
+ size},
                            {x:x*size + size, y:y*size + size - figureHeight*size,
z:z*size + size},
                            {x:x*size + size, y:y*size + size - figureHeight*size,
z:z*size},
                            {m:'right'}
                        ];
                        side[4] = getSidecentralCoordinate(side);
                        // Если правее чтото есть, то не рисуем
                        if (x + 1 < currentFigure.shape[0].length
                            && currentFigure.shape[y][x + 1][z] != 0) {
                            side[5] = false;
                        }
                        cube.push(side);

                        // Задняя стенка
                        side = [
                            {x:x*size, y:y*size - figureHeight*size, z:z*size},
                            {x:x*size + size, y:y*size - figureHeight*size, z:z*size},
                            {x:x*size + size, y:y*size + size - figureHeight*size,
z:z*size},
                            {x:x*size, y:y*size + size - figureHeight*size, z:z*size},
                            {m:'rear'}
                        ];
                        side[4] = getSidecentralCoordinate(side);
                        // Если сзади чтото есть, то не рисуем
                        if (z > 0
                            && currentFigure.shape[y][x][z - 1] != 0) {
                            side[5] = false;
                        }
                        cube.push(side);

                        // Передняя стенка
                        side = [
                            {x:x*size, y:y*size - figureHeight*size, z:z*size + size},
                            {x:x*size + size, y:y*size - figureHeight*size, z:z*size
+ size},
                            {x:x*size + size, y:y*size + size - figureHeight*size,
z:z*size + size},
                            {x:x*size, y:y*size + size - figureHeight*size, z:z*size
+ size},
                            {m:'front'}
                        ];
                        side[4] = getSidecentralCoordinate(side);
                        // Если впереди чтото есть, то не рисуем
                        if (z + 1 < currentFigure.shape[0][0].length
                            && currentFigure.shape[y][x][z + 1] != 0) {
                            side[5] = false;
                        }
                        cube.push(side);

                        // Верхняя стенка
                        side = [
                            {x:x*size, y:y*size - figureHeight*size, z:z*size},
                            {x:x*size, y:y*size - figureHeight*size, z:z*size + size},
                            {x:x*size + size, y:y*size - figureHeight*size, z:z*size
+ size},
                            {x:x*size + size, y:y*size - figureHeight*size, z:z*size},
                            {m:'top'}
                        ];
                        side[4] = getSidecentralCoordinate(side);
                        // Если сверху чтото есть, то не рисуем
                        if (y > 0
                            && currentFigure.shape[y - 1][x][z] != 0) {
                            side[5] = false;
                        }
                        cube.push(side);

                        // Нижняя стенка
                        side = [
                            {x:x*size, y:y*size + size - figureHeight*size, z:z*size},
                            {x:x*size, y:y*size + size - figureHeight*size, z:z*size
+ size},
                            {x:x*size + size, y:y*size + size - figureHeight*size,
z:z*size + size},
                            {x:x*size + size, y:y*size + size - figureHeight*size,
z:z*size},
                            {m:'bottom'}
                        ];
                        side[4] = getSidecentralCoordinate(side);
                        // Если снизу чтото есть, то не рисуем
                        if (y + 1 < currentFigure.shape.length
                            && currentFigure.shape[y + 1][x][z] != 0) {
                            side[5] = false;
                        }
                        cube.push(side);
                        fallingObject.push(cube);
                    }
                }
            }
        }

        // Определим центр фигуры
        t0 = {
            y:currentFigure.shape.length / 2 * size - figureHeight*size,
            x:currentFigure.shape[0].length / 2 * size,
            z:currentFigure.shape[0][0].length / 2 * size
        };

        // Запускаем падение
        setTimeout(figureFalls, 100);
    }

    // Угол поворота, не изменяется
    var deg = 1;

    // Текущая проекция
    var arrProj;

    var tempArr;

    function figureFalls() {

        // Сначала очистим экран - закрасим белым цветом
        clearScreen(canvas, canvas_context, 'rgb(255,255,255)');

        // Изменяем положение текущей фигуры
        for (var i = 0; i < fallingObject.length; i++) {
            for (var j = 0; j < 6; j++) {
                for (var k = 0; k < 4; k++) {
                    // Повернем фигуру на угол
                    fallingObject[i][j][k] = rotateOnDegreeY(t0, fallingObject[i][j][k]);
                    fallingObject[i][j][k] = rotateOnDegreeX(t0, fallingObject[i][j][k]);
                    fallingObject[i][j][k] = rotateOnDegreeZ(t0, fallingObject[i][j][k]);
                    // и опустим вниз на 1px
                    fallingObject[i][j][k].y += 1;
                }
                // Пересчитаем центральную координату плоскости
                fallingObject[i][j][4] = getSidecentralCoordinate(fallingObject[i][j]);
            }
            doTenTimes -= 1;
            // Пересчитаем центральную координату кубика
            fallingObject[i][6] = getCubecentralCoordinate(fallingObject[i]);
            // Если кубик ушел слишком далеко - удаляем кубик
            if (fallingObject[i][6].y > canvas.height) {
                fallingObject.splice(i, 1);
                i -=1;
            }
        }

        // Центр фигуры тоже опускается
        t0.y += 1;

        // Сортируем кубики по удаленности от начала координат по (X) и (Z)
        var exit = false;
        while (!exit) {
            exit = true;
            for (var i = 0; i < fallingObject.length - 1; i++) {
                if (fallingObject[i][6].z > fallingObject[i + 1][6].z) {
                    tempArr = fallingObject[i];
                    fallingObject[i] = fallingObject[i + 1];
                    fallingObject[i + 1] = tempArr;
                    exit = false;
                } else if (fallingObject[i][6].z == fallingObject[i + 1][6].z) {
                    if (fallingObject[i][6].x > fallingObject[i + 1][6].x) {
                        tempArr = fallingObject[i];
                        fallingObject[i] = fallingObject[i + 1];
                        fallingObject[i + 1] = tempArr;
                        exit = false;
                    }
                }
            }
        }

        // Отрисовываем фигуру
        for (var i = 0; i < fallingObject.length; i++) {
            // Получим параллельную проекцию
            arrProj = getParallelProjection(fallingObject[i]);
            // Нарисуем параллельную проекцию
            fillFigure(canvas_context, arrProj, figureColor);
        }

        // Если в видимой области еще есть кубики -
        // переходим на следующий шаг
        if (fallingObject.length > 0) {
            setTimeout(figureFalls, 100);
        }
        // иначе начинаем заново с новой фигурой
        else {
            setTimeout(figureStartsFalling, 100);
        }
    }

    // Получим центральную координату плоскости
    // для расчета удаленности от задней стенки
    function getSidecentralCoordinate(side) {

        var centralCoordinate = {x:0, y:0, z:0};

        for (var i = 0; i < 4; i++) {
            centralCoordinate.x += side[i].x;
            centralCoordinate.y += side[i].y;
            centralCoordinate.z += side[i].z;
        }

        centralCoordinate.x = centralCoordinate.x / 4;
        centralCoordinate.y = centralCoordinate.y / 4;
        centralCoordinate.z = centralCoordinate.z / 4;

        centralCoordinate.m = side[4].m;

        return centralCoordinate;
    }

    // Получим центральную координату кубика
    // для расчета удаленности от начала координат
    function getCubecentralCoordinate(side) {
        var centralCoordinate = {x:0, y:0, z:0};

        for (var i = 0; i < 6; i++) {
            centralCoordinate.x += side[i][4].x;
            centralCoordinate.y += side[i][4].y;
            centralCoordinate.z += side[i][4].z;
        }

        centralCoordinate.x = centralCoordinate.x / 6;
        centralCoordinate.y = centralCoordinate.y / 6;
        centralCoordinate.z = centralCoordinate.z / 6;

        return centralCoordinate;
    }

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

        var t_new = {};

        // Переводим угол поворота из градусов в радианы
        var 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;
    }

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

        var t_new = {};

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

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

        // Возвращаем полученное значение
        return t_new;
    }

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

        var t_new = {};

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

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

        // Возвращаем полученное значение
        return t_new;
    }

    // Закрашиваем весь экран определенным цветом
    function clearScreen(canvas, context, color) {
        context.fillStyle = color;

        context.beginPath();
        context.fillRect(- canvas.width / 2, 0, canvas.width, canvas.height);
        context.closePath();
        context.fill();
    }

    // Получаем параллельную проекцию кубика на плоскость экрана
    function getParallelProjection(arr) {

        var i, j, k;

        var arr_new = [];

        // Попарно работаем с гранями
        arr_new[0] = getSideParallelProjection(arr[0], arr[1], arr[6]); // Левая
и правая
        arr_new[1] = getSideParallelProjection(arr[2], arr[3], arr[6]); // Задняя
и передняя
        arr_new[2] = getSideParallelProjection(arr[4], arr[5], arr[6]); // Верхняя
и нижняя

        return arr_new;
    }

    // Параллельная проекция грани
    function getSideParallelProjection(side1, side2, cubeCenter) {

        // Сначала выясним какая пара нам попалась
        var centralProjection1 = getPointParallelProjection(side1[4]);
        var centralProjection2 = getPointParallelProjection(side2[4]);

        var coordinate = {
            x:Math.abs(side1[4].x - side2[4].x),
            y:Math.abs(side1[4].y - side2[4].y),
            z:Math.abs(side1[4].z - side2[4].z)};

        var i;

    // Найдем дальнюю (1) и ближнюю (2) от экрана точки
    // по оси (Z) и посчитаем их проекции
    var point1 = {z:10000},
        point2 = {z:-10000},
        point3 = {z:10000};

    for (i = 0; i < 4; i++) {
        if (point1.z > side1[i].z) {
            point1 = side1[i];
        }
        if (point1.z == side1[i].z
            && point1.x > side1[i].x) {
            point1 = side1[i];
        }
        if (point1.z == side1[i].z
            && point1.x == side1[i].x
            && point1.y < side1[i].y) {
            point1 = side1[i];
        }
        if (point2.z < side1[i].z) {
            point2 = side1[i];
        }
        if (point2.z == side1[i].z
            && point2.x < side1[i].x) {
            point2 = side1[i];
        }
        if (point2.z == side1[i].z
            && point2.x == side1[i].x
            && point2.y > side1[i].y) {
            point2 = side1[i];
        }
    }

    for (i = 0; i < 4; i++) {
        if (point3.z > side1[i].z
            && point1 != side1[i]) {
            point3 = side1[i];
        }
        if (point3.z == side1[i].z
            && point3.x > side1[i].x
            && point1 != side1[i]) {
            point3 = side1[i];
        }
        if (point3.z == side1[i].z
            && point3.x == side1[i].x
            && point3.y < side1[i].y
            && point1 != side1[i]) {
            point3 = side1[i];
        }
    }

	var direction = (point3.x - point1.x) / (point2.x - point1.x)
                            - (point3.y - point1.y) / (point2.y - point1.y);
        var projection = [];

        // (X) Правая и левая грани
        if (coordinate.x > coordinate.y && coordinate.x > coordinate.z) {

            point1 = getPointParallelProjection(point1);
            point2 = getPointParallelProjection(point2);
            point3 = getPointParallelProjection(point3);

            var direction = (point3.x - point1.x) / (point2.x - point1.x)
                - (point3.y - point1.y) / (point2.y - point1.y);

            var selected;

            if (point1.x >= point2.x) {
                if (centralProjection1.x >= centralProjection2.x) {
                    selected = side1;
                } else {
                    selected = side2;
                }
            } else {
                if (centralProjection1.x <= centralProjection2.x) {
                    selected = side1;
                } else {
                    selected = side2;
                }
            }

            if (selected.length < 6) {
                for (i = 0; i < 4; i++) {
                    projection[i] = getPointParallelProjection(selected[i]);
                }
                projection[4] = 'rgb(200,200,0)';
            }
        }

        // (Z) Передняя и задняя грани
        else if (coordinate.z > coordinate.x && coordinate.z > coordinate.y) {

            var selected;

            if (point1.x >= point2.x && direction < 0) {
                if (centralProjection1.x >= centralProjection2.x) {
                    selected = side1;
                } else {
                    selected = side2;
                }
            } else {
                if (centralProjection1.x <= centralProjection2.x) {
                    selected = side1;
                } else {
                    selected = side2;
                }
            }

            if (selected.length < 6) {
                for (i = 0; i < 4; i++) {
                    projection[i] = getPointParallelProjection(selected[i]);
                }
                projection[4] = 'rgb(200,0,0)';
            }
        }

        // (Y) Верхняя и нижняя грани
        else {

            var selected;

            if (point1.y <= point2.y) {
                if (centralProjection1.y <= centralProjection2.y) {
                    selected = side1;
                } else {
                    selected = side2;
                }
            } else {
                if (centralProjection1.y >= centralProjection2.y) {
                    selected = side1;
                } else {
                    selected = side2;
                }
            }

            if (selected.length < 6) {
                for (i = 0; i < 4; i++) {
                    projection[i] = getPointParallelProjection(selected[i]);
                }
                projection[4] = 'rgb(0,0,0)';
            }
        }

        return projection;
    }
    // Параллельная проекция точки
    function getPointParallelProjection(point) {
        return {
            x:point.x,
            y:point.y + point.z / 4};
//        return {
//            x:point.x + point.z * Math.cos(Math.PI * 3 / 4) / 2,
//            y:point.y + point.z * Math.sin(Math.PI * 3 / 4) / 2};
    }

    // Рисуем фигуру по точкам из массива
    function fillFigure(context, arr, color) {
        context.lineWidth = 2;
        context.strokeStyle = 'rgb(255,255,255)';
        context.fillStyle = color;

        for (var i = 0; i < arr.length; i++) {
            context.beginPath();
            for (var j = 0; j < arr[i].length; j++) {
                if (arr[i][4] != undefined) {
                    context.fillStyle = arr[i][4];
                } else {
                    context.fillStyle = color;
                }
                if (j == 0) {
                    context.moveTo(arr[i][j].x, arr[i][j].y);
                } else {
                    context.lineTo(arr[i][j].x, arr[i][j].y);
                }
            }
            context.closePath();
            context.fill();
            context.stroke();
        }
    }

    // Запускаем наш скрипт
    window.onload = function() {
        setTimeout(initAnimation, 200);
    };

    

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


Ответы

Ответ 1



Слова ничего не значат. Покажите мне код. (c) Линус Торвальдс Challenge accepted. Фиддл 'use strict'; var figures; var figure_111 = { //╔═══╗ //║ ║ //╚═══╝ figureType: 5, figurePositionY: 1, figurePositionX: 1, figurePositionZ: 1, shape: [ [[5]] ], rotateZ: function () { this.shape = rotateZ(this.shape); this.figurePositionZ = changePosition(this.figurePositionZ); } }; function initAnimation() { figures = [ figure_111 ]; // Запускаем падение figureStartsFalling(); } // Текущая фигура var currentFigure; // Размер фигуры var size = 70; // Объект на основании фигуры var fallingObject; // Цвет фигуры var figureColor; // Центр фигуры var t0; var canvas = document.getElementById('animation'); var canvas_context = canvas.getContext('2d'); // Сместим центр оси (Х) в середину поля canvas_context.translate(canvas.width / 2, 0); var doTenTimes; function figureStartsFalling() { doTenTimes = 30; // Переопределяем текущую фигуру currentFigure = figures[Math.floor(Math.random() * figures.length)]; // Получим цвет текущей фигуры figureColor = 'rgb(0,200,0)'; // Высота фигуры var figureHeight = currentFigure.shape.length; // Строим 3Д объект на основании фигуры fallingObject = []; var cube = []; var side = []; for (var y = currentFigure.shape.length - 1; y > -1; y--) { for (var x = 0; x < currentFigure.shape[0].length; x++) { for (var z = 0; z < currentFigure.shape[0][0].length; z++) { // Для каждой клеточки создаем кубик с 5 гранями cube = []; if (currentFigure.shape[y][x][z] != 0) { // Левая стенка side = [ {x:x*size, y:y*size - figureHeight*size, z:z*size}, {x:x*size, y:y*size - figureHeight*size, z:z*size + size}, {x:x*size, y:y*size + size - figureHeight*size, z:z*size + size}, {x:x*size, y:y*size + size - figureHeight*size, z:z*size}, {m:'left'} ]; // Центральная координата, нужна для расчета удаленности по осям (X) и (Z) side[4] = getSidecentralCoordinate(side); // Если левее чтото есть, то не рисуем if (x > 0 && currentFigure.shape[y][x - 1][z] != 0) { side[5] = false; } cube.push(side); // Правая стенка side = [ {x:x*size + size, y:y*size - figureHeight*size, z:z*size}, {x:x*size + size, y:y*size + size - figureHeight*size, z:z*size}, {x:x*size + size, y:y*size + size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size - figureHeight*size, z:z*size + size}, {m:'right'} ]; side[4] = getSidecentralCoordinate(side); // Если правее чтото есть, то не рисуем if (x + 1 < currentFigure.shape[0].length && currentFigure.shape[y][x + 1][z] != 0) { side[5] = false; } cube.push(side); // Задняя стенка side = [ {x:x*size, y:y*size - figureHeight*size, z:z*size}, {x:x*size, y:y*size + size - figureHeight*size, z:z*size}, {x:x*size + size, y:y*size + size - figureHeight*size, z:z*size}, {x:x*size + size, y:y*size - figureHeight*size, z:z*size}, {m:'rear'} ]; side[4] = getSidecentralCoordinate(side); // Если сзади чтото есть, то не рисуем if (z > 0 && currentFigure.shape[y][x][z - 1] != 0) { side[5] = false; } cube.push(side); // Передняя стенка side = [ {x:x*size, y:y*size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size + size - figureHeight*size, z:z*size + size}, {x:x*size, y:y*size + size - figureHeight*size, z:z*size + size}, {m:'front'} ]; side[4] = getSidecentralCoordinate(side); // Если впереди чтото есть, то не рисуем if (z + 1 < currentFigure.shape[0][0].length && currentFigure.shape[y][x][z + 1] != 0) { side[5] = false; } cube.push(side); // Верхняя стенка side = [ {x:x*size, y:y*size - figureHeight*size, z:z*size}, {x:x*size + size, y:y*size - figureHeight*size, z:z*size}, {x:x*size + size, y:y*size - figureHeight*size, z:z*size + size}, {x:x*size, y:y*size - figureHeight*size, z:z*size + size}, {m:'top'} ]; side[4] = getSidecentralCoordinate(side); // Если сверху чтото есть, то не рисуем if (y > 0 && currentFigure.shape[y - 1][x][z] != 0) { side[5] = false; } cube.push(side); // Нижняя стенка side = [ {x:x*size, y:y*size + size - figureHeight*size, z:z*size}, {x:x*size, y:y*size + size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size + size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size + size - figureHeight*size, z:z*size}, {m:'bottom'} ]; side[4] = getSidecentralCoordinate(side); // Если снизу чтото есть, то не рисуем if (y + 1 < currentFigure.shape.length && currentFigure.shape[y + 1][x][z] != 0) { side[5] = false; } cube.push(side); fallingObject.push(cube); } } } } // Определим центр фигуры t0 = { y:currentFigure.shape.length / 2 * size - figureHeight*size, x:currentFigure.shape[0].length / 2 * size, z:currentFigure.shape[0][0].length / 2 * size }; // Запускаем падение setTimeout(figureFalls, 100); } // Угол поворота, не изменяется var deg = 1; // Текущая проекция var arrProj; var tempArr; function figureFalls() { // Сначала очистим экран - закрасим белым цветом clearScreen(canvas, canvas_context, 'rgb(255,255,255)'); // Изменяем положение текущей фигуры for (var i = 0; i < fallingObject.length; i++) { for (var j = 0; j < 6; j++) { for (var k = 0; k < 4; k++) { // Повернем фигуру на угол fallingObject[i][j][k] = rotateOnDegreeY(t0, fallingObject[i][j][k]); fallingObject[i][j][k] = rotateOnDegreeX(t0, fallingObject[i][j][k]); fallingObject[i][j][k] = rotateOnDegreeZ(t0, fallingObject[i][j][k]); // и опустим вниз на 1px fallingObject[i][j][k].y += 1; } // Пересчитаем центральную координату плоскости fallingObject[i][j][4] = getSidecentralCoordinate(fallingObject[i][j]); } doTenTimes -= 1; // Пересчитаем центральную координату кубика fallingObject[i][6] = getCubecentralCoordinate(fallingObject[i]); // Если кубик ушел слишком далеко - удаляем кубик if (fallingObject[i][6].y > canvas.height) { fallingObject.splice(i, 1); i -=1; } } // Центр фигуры тоже опускается t0.y += 1; // Сортируем кубики по удаленности от начала координат по (X) и (Z) var exit = false; while (!exit) { exit = true; for (var i = 0; i < fallingObject.length - 1; i++) { if (fallingObject[i][6].z > fallingObject[i + 1][6].z) { tempArr = fallingObject[i]; fallingObject[i] = fallingObject[i + 1]; fallingObject[i + 1] = tempArr; exit = false; } else if (fallingObject[i][6].z == fallingObject[i + 1][6].z) { if (fallingObject[i][6].x > fallingObject[i + 1][6].x) { tempArr = fallingObject[i]; fallingObject[i] = fallingObject[i + 1]; fallingObject[i + 1] = tempArr; exit = false; } } } } // Отрисовываем фигуру for (var i = 0; i < fallingObject.length; i++) { // Получим параллельную проекцию arrProj = getParallelProjection(fallingObject[i]); // Нарисуем параллельную проекцию fillFigure(canvas_context, arrProj, figureColor); } // Если в видимой области еще есть кубики - // переходим на следующий шаг if (fallingObject.length > 0) { setTimeout(figureFalls, 100); } // иначе начинаем заново с новой фигурой else { setTimeout(figureStartsFalling, 100); } } // Получим центральную координату плоскости // для расчета удаленности от задней стенки function getSidecentralCoordinate(side) { var centralCoordinate = {x:0, y:0, z:0}; for (var i = 0; i < 4; i++) { centralCoordinate.x += side[i].x; centralCoordinate.y += side[i].y; centralCoordinate.z += side[i].z; } centralCoordinate.x = centralCoordinate.x / 4; centralCoordinate.y = centralCoordinate.y / 4; centralCoordinate.z = centralCoordinate.z / 4; centralCoordinate.m = side[4].m; return centralCoordinate; } // Получим центральную координату кубика // для расчета удаленности от начала координат function getCubecentralCoordinate(side) { var centralCoordinate = {x:0, y:0, z:0}; for (var i = 0; i < 6; i++) { centralCoordinate.x += side[i][4].x; centralCoordinate.y += side[i][4].y; centralCoordinate.z += side[i][4].z; } centralCoordinate.x = centralCoordinate.x / 6; centralCoordinate.y = centralCoordinate.y / 6; centralCoordinate.z = centralCoordinate.z / 6; return centralCoordinate; } // Поворачиваем точку t(x,y,z) на угол (deg) по оси (Y) // относительно точки t0(x,y,z) function rotateOnDegreeY(t0, t) { var t_new = {}; // Переводим угол поворота из градусов в радианы var 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; } // Поворачиваем точку t(x,y,z) на угол (deg) по оси (X) // относительно точки t0(x,y,z) function rotateOnDegreeX(t0, t) { var t_new = {}; // Переводим угол поворота из градусов в радианы var rad = (Math.PI / 180) * deg; // Рассчитываем координаты новой точки по формуле t_new.x = t.x; t_new.y = t0.y + (t.y - t0.y) * Math.cos(rad) - (t.z - t0.z) * Math.sin(rad); t_new.z = t0.z + (t.y - t0.y) * Math.sin(rad) + (t.z - t0.z) * Math.cos(rad); // Возвращаем полученное значение return t_new; } // Поворачиваем точку t(x,y,z) на угол (deg) по оси (Z) // относительно точки t0(x,y,z) function rotateOnDegreeZ(t0, t) { var t_new = {}; // Переводим угол поворота из градусов в радианы var rad = (Math.PI / 180) * deg; // Рассчитываем координаты новой точки по формуле t_new.x = t0.x + (t.x - t0.x) * Math.cos(rad) - (t.y - t0.y) * Math.sin(rad); t_new.y = t0.y + (t.x - t0.x) * Math.sin(rad) + (t.y - t0.y) * Math.cos(rad); t_new.z = t.z; // Возвращаем полученное значение return t_new; } // Закрашиваем весь экран определенным цветом function clearScreen(canvas, context, color) { context.fillStyle = color; context.beginPath(); context.fillRect(- canvas.width / 2, 0, canvas.width, canvas.height); context.closePath(); context.fill(); } // Получаем параллельную проекцию кубика на плоскость экрана function getParallelProjection(arr) { var i, j, k; var projected = []; var projection; for (i = 0; i < 6; i++) { projection = []; if (arr[i].length < 6) { for (j = 0; j < 4; j++) { projection[j] = getPointParallelProjection(arr[i][j]); } } projected[i] = projection; } return projected.filter(isClockwise); } function isClockwise(point_array) { if (point_array.length < 3) return false; var p0 = point_array[0]; var p1 = point_array[1]; var p2 = point_array[2]; return (p1.x-p0.x)*(p2.y-p0.y) - (p2.x-p0.x)*(p1.y-p0.y) > 0; } // Параллельная проекция точки function getPointParallelProjection(point) { return { x:point.x, y:point.y + point.z / 4}; } // Рисуем фигуру по точкам из массива function fillFigure(context, arr, color) { context.lineWidth = 2; context.strokeStyle = 'rgb(255,255,255)'; context.fillStyle = color; for (var i = 0; i < arr.length; i++) { context.beginPath(); for (var j = 0; j < arr[i].length; j++) { if (arr[i][4] != undefined) { context.fillStyle = arr[i][4]; } else { context.fillStyle = color; } if (j == 0) { context.moveTo(arr[i][j].x, arr[i][j].y); } else { context.lineTo(arr[i][j].x, arr[i][j].y); } } context.closePath(); context.fill(); context.stroke(); } context.font = '12px'; context.fillText('Рисуется сторон: ' + arr.length, -120, 20); } // Запускаем наш скрипт window.onload = function() { setTimeout(initAnimation, 200); };

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

Релевантный кусок: // Получаем параллельную проекцию кубика на плоскость экрана function getParallelProjection(arr) { var i, j, k; var projected = []; var projection; for (i = 0; i < 6; i++) { projection = []; if (arr[i].length < 6) { for (j = 0; j < 4; j++) { projection[j] = getPointParallelProjection(arr[i][j]); } } projected[i] = projection; } return projected.filter(isClockwise); } function isClockwise(point_array) { if (point_array.length < 3) return false; var p0 = point_array[0]; var p1 = point_array[1]; var p2 = point_array[2]; return (p1.x-p0.x)*(p2.y-p0.y) - (p2.x-p0.x)*(p1.y-p0.y) > 0; } Вся магия в том, что мы проверяем в том, как рисуются грани. Для этого, правда, пришлось поправить порядок задания вершин в самом начале, дабы каждая грань рисовалась по часовой стрелке. После проекции "векторным" произведением проверяется порядок спроецированных вершин, и если он оказался против часовой стрелки - этот набор вершин отбрасывается. Бонусом на канвас выводится число граней, переданных в функцию отрисовки - чисто так, для верности расчётов. Стоит ли говорить, что решена несколько иная задача, чем поставленная в вопросе, но код есть код :)

Ответ 2



Второй способ: подсказано пользователем Дмитрий Чистик. Берем ближнюю вершину и отображаем 3 отходящих от нее грани. Лагов меньше, чем в первом случае, но все равно они есть. Такой код тоже не работает. 'use strict'; // Массив с фигурами var figures; var figure_111 = { //╔═══╗ //║ ║ //╚═══╝ figureType: 5, figurePositionY: 1, figurePositionX: 1, figurePositionZ: 1, shape: [ [[5]] ], rotateZ: function () { this.shape = rotateZ(this.shape); this.figurePositionZ = changePosition(this.figurePositionZ); } }; // Инициализация нашего скрипта // эту функцию надо будет вставить // в переменную window.onload function initAnimation() { figures = [ figure_111 ]; // Запускаем падение figureStartsFalling(); } // Текущая фигура var currentFigure; // Размер фигуры var size = 30; // Объект на основании фигуры var fallingObject; // Цвет фигуры var figureColor; // Центр фигуры var t0; var canvas = document.getElementById('animation'); var canvas_context = canvas.getContext('2d'); // Сместим центр оси (Х) в середину поля canvas_context.translate(canvas.width / 2, 0); var doTenTimes; function figureStartsFalling() { doTenTimes = 30; // Переопределяем текущую фигуру currentFigure = figures[Math.floor(Math.random() * figures.length)]; // Получим цвет текущей фигуры figureColor = 'rgb(0,200,0)'; // Высота фигуры var figureHeight = currentFigure.shape.length; // Строим 3Д объект на основании фигуры fallingObject = []; var cube = []; var side = []; for (var y = currentFigure.shape.length - 1; y > -1; y--) { for (var x = 0; x < currentFigure.shape[0].length; x++) { for (var z = 0; z < currentFigure.shape[0][0].length; z++) { // Для каждой клеточки создаем кубик с 5 гранями cube = []; if (currentFigure.shape[y][x][z] != 0) { // Левая стенка side = [ {x:x*size, y:y*size - figureHeight*size, z:z*size}, {x:x*size, y:y*size - figureHeight*size, z:z*size + size}, {x:x*size, y:y*size + size - figureHeight*size, z:z*size + size}, {x:x*size, y:y*size + size - figureHeight*size, z:z*size}, {m:'left'} ]; // Центральная координата, нужна для расчета удаленности по осям (X) и (Z) side[4] = getSidecentralCoordinate(side); // Если левее чтото есть, то не рисуем if (x > 0 && currentFigure.shape[y][x - 1][z] != 0) { side[5] = false; } cube.push(side); // Правая стенка side = [ {x:x*size + size, y:y*size - figureHeight*size, z:z*size}, {x:x*size + size, y:y*size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size + size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size + size - figureHeight*size, z:z*size}, {m:'right'} ]; side[4] = getSidecentralCoordinate(side); // Если правее чтото есть, то не рисуем if (x + 1 < currentFigure.shape[0].length && currentFigure.shape[y][x + 1][z] != 0) { side[5] = false; } cube.push(side); // Задняя стенка side = [ {x:x*size, y:y*size - figureHeight*size, z:z*size}, {x:x*size + size, y:y*size - figureHeight*size, z:z*size}, {x:x*size + size, y:y*size + size - figureHeight*size, z:z*size}, {x:x*size, y:y*size + size - figureHeight*size, z:z*size}, {m:'rear'} ]; side[4] = getSidecentralCoordinate(side); // Если сзади чтото есть, то не рисуем if (z > 0 && currentFigure.shape[y][x][z - 1] != 0) { side[5] = false; } cube.push(side); // Передняя стенка side = [ {x:x*size, y:y*size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size + size - figureHeight*size, z:z*size + size}, {x:x*size, y:y*size + size - figureHeight*size, z:z*size + size}, {m:'front'} ]; side[4] = getSidecentralCoordinate(side); // Если впереди чтото есть, то не рисуем if (z + 1 < currentFigure.shape[0][0].length && currentFigure.shape[y][x][z + 1] != 0) { side[5] = false; } cube.push(side); // Верхняя стенка side = [ {x:x*size, y:y*size - figureHeight*size, z:z*size}, {x:x*size, y:y*size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size - figureHeight*size, z:z*size}, {m:'top'} ]; side[4] = getSidecentralCoordinate(side); // Если сверху чтото есть, то не рисуем if (y > 0 && currentFigure.shape[y - 1][x][z] != 0) { side[5] = false; } cube.push(side); // Нижняя стенка side = [ {x:x*size, y:y*size + size - figureHeight*size, z:z*size}, {x:x*size, y:y*size + size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size + size - figureHeight*size, z:z*size + size}, {x:x*size + size, y:y*size + size - figureHeight*size, z:z*size}, {m:'bottom'} ]; side[4] = getSidecentralCoordinate(side); // Если снизу чтото есть, то не рисуем if (y + 1 < currentFigure.shape.length && currentFigure.shape[y + 1][x][z] != 0) { side[5] = false; } cube.push(side); fallingObject.push(cube); } } } } // Определим центр фигуры t0 = { y:currentFigure.shape.length / 2 * size - figureHeight*size, x:currentFigure.shape[0].length / 2 * size, z:currentFigure.shape[0][0].length / 2 * size }; // Запускаем падение setTimeout(figureFalls, 100); } // Угол поворота, не изменяется var deg = 1; // Текущая проекция var arrProj; var tempArr; function figureFalls() { // Сначала очистим экран - закрасим белым цветом clearScreen(canvas, canvas_context, 'rgb(255,255,255)'); // Изменяем положение текущей фигуры for (var i = 0; i < fallingObject.length; i++) { for (var j = 0; j < 6; j++) { for (var k = 0; k < 4; k++) { // Повернем фигуру на угол fallingObject[i][j][k] = rotateOnDegreeY(t0, fallingObject[i][j][k]); fallingObject[i][j][k] = rotateOnDegreeX(t0, fallingObject[i][j][k]); fallingObject[i][j][k] = rotateOnDegreeZ(t0, fallingObject[i][j][k]); // и опустим вниз на 1px fallingObject[i][j][k].y += 1; } // Пересчитаем центральную координату плоскости fallingObject[i][j][4] = getSidecentralCoordinate(fallingObject[i][j]); } doTenTimes -= 1; // Пересчитаем центральную координату кубика fallingObject[i][6] = getCubecentralCoordinate(fallingObject[i]); // Если кубик ушел слишком далеко - удаляем кубик if (fallingObject[i][6].y > canvas.height) { fallingObject.splice(i, 1); i -=1; } } // Центр фигуры тоже опускается t0.y += 1; // Сортируем кубики по удаленности от начала координат по (X) и (Z) var exit = false; while (!exit) { exit = true; for (var i = 0; i < fallingObject.length - 1; i++) { if (fallingObject[i][6].z > fallingObject[i + 1][6].z) { tempArr = fallingObject[i]; fallingObject[i] = fallingObject[i + 1]; fallingObject[i + 1] = tempArr; exit = false; } else if (fallingObject[i][6].z == fallingObject[i + 1][6].z) { if (fallingObject[i][6].x > fallingObject[i + 1][6].x) { tempArr = fallingObject[i]; fallingObject[i] = fallingObject[i + 1]; fallingObject[i + 1] = tempArr; exit = false; } } } } // Отрисовываем фигуру for (var i = 0; i < fallingObject.length; i++) { // Получим параллельную проекцию arrProj = getParallelProjection(fallingObject[i]); // Нарисуем параллельную проекцию fillFigure(canvas_context, arrProj, figureColor); } // Если в видимой области еще есть кубики - // переходим на следующий шаг if (fallingObject.length > 0) { setTimeout(figureFalls, 100); } // иначе начинаем заново с новой фигурой else { setTimeout(figureStartsFalling, 100); } } // Получим центральную координату плоскости // для расчета удаленности от задней стенки function getSidecentralCoordinate(side) { var centralCoordinate = {x:0, y:0, z:0}; for (var i = 0; i < 4; i++) { centralCoordinate.x += side[i].x; centralCoordinate.y += side[i].y; centralCoordinate.z += side[i].z; } centralCoordinate.x = centralCoordinate.x / 4; centralCoordinate.y = centralCoordinate.y / 4; centralCoordinate.z = centralCoordinate.z / 4; centralCoordinate.m = side[4].m; return centralCoordinate; } // Получим центральную координату кубика // для расчета удаленности от начала координат function getCubecentralCoordinate(side) { var centralCoordinate = {x:0, y:0, z:0}; for (var i = 0; i < 6; i++) { centralCoordinate.x += side[i][4].x; centralCoordinate.y += side[i][4].y; centralCoordinate.z += side[i][4].z; } centralCoordinate.x = centralCoordinate.x / 6; centralCoordinate.y = centralCoordinate.y / 6; centralCoordinate.z = centralCoordinate.z / 6; return centralCoordinate; } // Поворачиваем точку t(x,y,z) на угол (deg) по оси (Y) // относительно точки t0(x,y,z) function rotateOnDegreeY(t0, t) { var t_new = {}; // Переводим угол поворота из градусов в радианы var 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; } // Поворачиваем точку t(x,y,z) на угол (deg) по оси (X) // относительно точки t0(x,y,z) function rotateOnDegreeX(t0, t) { var t_new = {}; // Переводим угол поворота из градусов в радианы var rad = (Math.PI / 180) * deg; // Рассчитываем координаты новой точки по формуле t_new.x = t.x; t_new.y = t0.y + (t.y - t0.y) * Math.cos(rad) - (t.z - t0.z) * Math.sin(rad); t_new.z = t0.z + (t.y - t0.y) * Math.sin(rad) + (t.z - t0.z) * Math.cos(rad); // Возвращаем полученное значение return t_new; } // Поворачиваем точку t(x,y,z) на угол (deg) по оси (Z) // относительно точки t0(x,y,z) function rotateOnDegreeZ(t0, t) { var t_new = {}; // Переводим угол поворота из градусов в радианы var rad = (Math.PI / 180) * deg; // Рассчитываем координаты новой точки по формуле t_new.x = t0.x + (t.x - t0.x) * Math.cos(rad) - (t.y - t0.y) * Math.sin(rad); t_new.y = t0.y + (t.x - t0.x) * Math.sin(rad) + (t.y - t0.y) * Math.cos(rad); t_new.z = t.z; // Возвращаем полученное значение return t_new; } // Закрашиваем весь экран определенным цветом function clearScreen(canvas, context, color) { context.fillStyle = color; context.beginPath(); context.fillRect(- canvas.width / 2, 0, canvas.width, canvas.height); context.closePath(); context.fill(); } // Получаем параллельную проекцию кубика на плоскость экрана function getParallelProjection(arr) { var i, j, k; var arr_new = []; // Найдем ближнюю по оси (Z) вершину var point = {x:-10000, y:10000, z:-10000}; for (i = 0; i < 6; i++) { for (j = 0; j < 4; j++) { if (arr[i][j].z > point.z) { point = arr[i][j]; } if (arr[i][j].z == point.z && arr[i][j].x > point.x) { point = arr[i][j]; } if (arr[i][j].z == point.z && arr[i][j].x == point.x && arr[i][j].y < point.y) { point = arr[i][j]; } } } // Упорядочим грани по удаленности от ближней вершины var exit, temp; do { exit = true; for (i = 0; i < 5; i++) { if (findDistance(point, arr[i][4]) > findDistance(point, arr[i + 1][4])) { temp = arr[i]; arr[i] = arr[i + 1]; arr[i + 1] = temp; exit = false; } } } while (!exit); var projection; for (i = 0; i < 3; i++) { projection = []; if (arr[i].length < 6) { for (j = 0; j < 4; j++) { projection[j] = getPointParallelProjection(arr[i][j]); } } arr_new[i] = projection; } return arr_new; } // Находим расстояние между двумя точками в пространстве function findDistance(point1, point2) { return Math.sqrt( Math.pow((point2.x - point1.x), 2) + Math.pow((point2.y - point1.y), 2) + Math.pow((point2.z - point1.z), 2) ) } // Параллельная проекция точки function getPointParallelProjection(point) { return { x:point.x, y:point.y + point.z / 4}; } // Рисуем фигуру по точкам из массива function fillFigure(context, arr, color) { context.lineWidth = 2; context.strokeStyle = 'rgb(255,255,255)'; context.fillStyle = color; for (var i = 0; i < arr.length; i++) { context.beginPath(); for (var j = 0; j < arr[i].length; j++) { if (arr[i][4] != undefined) { context.fillStyle = arr[i][4]; } else { context.fillStyle = color; } if (j == 0) { context.moveTo(arr[i][j].x, arr[i][j].y); } else { context.lineTo(arr[i][j].x, arr[i][j].y); } } context.closePath(); context.fill(); context.stroke(); } } // Запускаем наш скрипт window.onload = function() { setTimeout(initAnimation, 200); };

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



Ответ 3



В чем собственно сложность? Преобразуете все вершины Вычисляете центры граней Берете три ближних Или даже еще проще: Берете центры граней Преобразуете центры граней согласно проекции Берете три ближних

Комментариев нет:

Отправить комментарий