Дано:
Система координат
Куб вращается вокруг своего центра по всем трем осям.
Угол поворота заранее не известен.
Параллельная проекция считается по формуле:
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);
};
Ответ
Слова ничего не значат. Покажите мне код. (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;
}
Вся магия в том, что мы проверяем в том, как рисуются грани. Для этого, правда, пришлось поправить порядок задания вершин в самом начале, дабы каждая грань рисовалась по часовой стрелке. После проекции "векторным" произведением проверяется порядок спроецированных вершин, и если он оказался против часовой стрелки - этот набор вершин отбрасывается.
Бонусом на канвас выводится число граней, переданных в функцию отрисовки - чисто так, для верности расчётов.
Стоит ли говорить, что решена несколько иная задача, чем поставленная в вопросе, но код есть код :)