#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); };
Ответы
Ответ 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); }; Релевантный кусок: // Получаем параллельную проекцию кубика на плоскость экрана 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); };Ответ 3
В чем собственно сложность? Преобразуете все вершины Вычисляете центры граней Берете три ближних Или даже еще проще: Берете центры граней Преобразуете центры граней согласно проекции Берете три ближних
Комментариев нет:
Отправить комментарий