Страницы

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

Показаны сообщения с ярлыком графика. Показать все сообщения
Показаны сообщения с ярлыком графика. Показать все сообщения

понедельник, 13 апреля 2020 г.

Готовые наборы тайлсетов для игр

#c_sharp #xna #графика #разработка_игр #2d

                    
Доброго времени суток, Хэшкод.

В свободное от работы время начал потихоньку изучать xna. Подготовил двумерный изометрический
тайловый движок, даже нашел подходящий редактор под него, однако осталась одна проблема
- нашел очень мало нормальных тайлсетов. Художник и дизайнер из меня, прямо скажу,
аховый, так что вариант "нарисовать самому" я оставляю на самый крайний случай. При
попытке найти что-то в сети в основном получаю какой-то лохматый "ужос"... В общем
- не нашел.

Собственно у меня вопросы:


Есть ли сайты, на которых есть нормальные тайлсеты?
Есть ли нормальные редакторы для создания тайсетов? (можно конечно и в Paint-е(Gimp-е/Photoshop-е)
что-то наваять, но с моими навыками "рисования"... в общем, хотелось бы что-то более
специализированное)


Пример тайлсета, если кто не знает, что это такое:


[upd] Да, вопрос получился конечно не по программированию, но извиняюсь. Больше мне
спрашивать негде.
    


Ответы

Ответ 1



Вы когда нибудь пробовали собрать toolbar для оконного приложения с помощью сайта который представляет бесплатные иконки от разных людей? Ваш toolbar получится не ахти, так как все иконки будут нарисованы разными людьми в разном стиле и цветовой гамме. Даже если вы будете долго копаться на нескольких таких сайтах в поисках единого стиля, то наврятли получится что-то стоящее, т.к. совершенно не факт что вы найдете даже готовый набор полностью удовлетворяющий вашим нуждам. Тоже самое и с тайлсетом. Их все должен рисовать один человек в одном стиле по одному документу, на gamedev.ru и фриланс сайтах есть десятки людей которые с радостью возьмутся выполнить такой заказ по вполне адекватной цене(реальная рыночная цена тому что вы приводите в качестве примера не больше 100-300 рублей за полоску), да и качество будет вполне ничего, если вы конечно потрудитесь отобрать достойного кандидата. В качестве редактора можно использовать Photoshop и 3DSMax ну или Blender и GIMP если вы красноглазик. Специальный редактор для тайлсетов это плохая идея потому что непонятно чем собственно тайлсет отличается от обычной картинки. Самому рисовать их ни в коем случае нельзя, так как если у вас нет хотя бы пары лет опыта работы с графическими редакторами и хотя бы зачатков умений художника, то в лучшем случае у вас получится результат которым можно только пугать других людей. С другой стороны с попытки с 50ой может и получится что-нибудь хорошее, но тогда вы просто потеряете время. Правда есть и альтернативный вариант: http://lunar.lostgarden.com/labels/free%20game%20graphics.html http://hasgraphics.com/ http://cgtextures.com/ http://blogoscoped.com/archive/2006-08-08-n51.html http://www.freewebs.com/teh_pro/sprites.htm http://www.spriters-resource.com/ http://www.flyingyogi.com/fun/spritelib.html http://www.gamedev.net/topic/272386-sprites-sprites-and-more-sprites/ P.S. Собирать игры в качестве хобби это плохая идея, так как это требует очень много свободного времени и усилий. Нужно либо делать это нормально либо вообще не делать.

Ответ 2



Что касается сайтов с нормальными тайлсэтами - никогда не встречал, что касается редакторов - такие, безусловно, есть, но они никак не изменяют того факта, что нужно уметь рисовать, учитывая их более ограниченный функционал, большего смысла в них не вижу, разве что, обычно, удобно сразу рисовать сэт. Вообще, для того, что-бы научится хоть более менее нормально рисовать оные, великих навыков художника не требуется, все, чему нужно научится - базовым навыкам передачи светотени и объема. К тому-же, нет людей, которые не умеют рисовать, есть только те, которые не научились. Плюс ко всему, если рисовать не на бумаге, можно сделать, допустим, размерность 2к*2к px, при уменьшении которой, многие косяки изображения исчезнут, главное правильно передать объем и более менее правильно светотень. Даже если совсем не будет получатся - можно поискать начинающего художника и попросить нарисовать его, но, повторюсь - такое рисование не требует великих навыков, другой вопрос в том, достаточно ли у вас времени и желания?..

суббота, 11 апреля 2020 г.

Как превратить нарисованную от руки линию в красивый график?

#графика #математика

                    
Для своего диплома я хочу сделать компонент (если не получится, то фиг с ним:), который
при рисовании линии (я использую WPF InkCanvas) превращал бы эту линую в график. Ну
то есть выпрямлял посильнее. Не силен в математике, поэтому не знаю, что мне нужно
использовать для этого. В качестве входных данных имеется набор точек. Что можно применить?
    


Ответы

Ответ 1



Для этих целей уже есть готовый алгоритм Рамера-Дугласа-Пекера Он вроде применяется в векторных графических редакторах и картографии. И еще список алгоритмов делающих тоже самое: Visvalingam–Whyatt algorithm Reumann–Witkam algorithm Opheim simplification algorithm Lang simplification algorithm Затем полученную ломанную можно превратить в красивую кривую каким-нибудь алгоритмом интерполяции или использовать точки как опорные для построения кривых Безье.

Ответ 2



Решение в лоб: взять крайние точки исходной линии, найти равно распределенные точки "изломов". Получаем промежутки-условия. Потом для каждого такого промежутка ищем точки, которые в него попадают и находим простейшее среднеарифметическое значение для X и для Y. Получим усредненные значения на участках, соединим их точками - тот же график, но менее детализированный. Больше точек - больше изломов - картинка ближе к графику.

понедельник, 2 марта 2020 г.

Рисование графиков на PHP

#php #графика #график


Как можно рисовать графики в PHP коде? Мне надо, чтобы выглядело примерно так:


    


Ответы

Ответ 1



Здравствуйте! Очень увлёкся Вашим вопросом, и смог добиться результатов. Получилось похоже на ваш график. Вот код с комментариями:

среда, 26 февраля 2020 г.

Контур произвольного полигона

#2d #алгоритм #графика


Имеется векторный полигон в программе (в виде команд его рисования).
Задача - расширить этот полигон во все стороны на заданное расстояние. То есть, получить
его контур или окаёмку с учётом возможных внутренних коллизий. Интересует, собственно,
алгоритм сего действа. Желательно, не особо ресурсоёмкий. Либо просто литература, где
про это можно почитать.
Самое, что интересное:

обработка самопересечений в замкнутых областях (как внутренняя дырка в букве "я"
- заполнилась целиком).
закругления в углах. Как сделать без закруглений более-менее понятно (провести параллельные
линии на заданном расстоянии). Или концы отрезков с помощью безье соединить?..


Чёрным обозначена исходная область, а синим - итоговая (для различной толщины контура).
В крайнем случае, можно отталкиваться и от готового растра исходного полигона.    


Ответы

Ответ 1



Векторный случай Если предположить, что вы уже перешли к векторному формату, то: рассматриваете последовательно ребра полигона в одном направлении (например, против часовой стрелки). Это важно, т.к. повлияет на ориентацию направляющих векторов ребер полигона. Для каждого ребра вычисляете нормаль (это нормализованный вектор, перпендикулярный направляющему вектору ребра). В вашем случае также следует проследить, чтобы нормаль была направлена "от" полигона, а не "внутрь" него. умножаете каждую нормаль на коэффициент "расширения" k. прибавляете нормаль к начальной (u) и к конечной (v) вершинам ребра (вершины рассматриваем как векторы, исходящие из начала координат). Получаем "расширенные" вершины u' и v'. Эти вершины определяют начало и конец "расширенного" ребра. Кроме того, в итоге на каждую вершину полигона будет получено по две (в общем случае неколлиниарных) "расширенных" вершины u' и u'' (они будут вычислены для смежных ребер с общей вершиной u). Их можно использовать, чтобы сделать "закругление" (наиболее очевидный способ: строить промежуточные вершины поворотом u' к u'' малым угловым шагом). Нужно еще выявлять ситуации самопересечений и обрабатывать их. Боюсь соврать, но по-моему тут надо смотреть знаки смешанных произведений нормалей ребер и вектора (0,0,1). При выбранном направлении обхода полигона знак этого произведения должен меняться только для "вогнутых" пар ребер (образующих "невыпуклости" полигона). Кажется так.. Почитать можно что-то по аналитической геометрии и векторной арифметике. UPD: Как вариант можете еще глянуть эту книгу: Steven M. LaValle. Planning Algorithms. В ней охвачен широчайший спектр вопросов, в т.ч. и работа с полигонами. Растровый случай Думаю, практически в любом случае есть смысл заняться преобразованием в векторное представление. Для определения границ можно попробовать использовать алгоритм пересечения полигона отрезком, паралелльным какой-либо оси координат + читать цвета пикселей. Еще один вариант - как-то попробовать модифицировать алгоритм заливки полигона. Тут уже менее очевидно, надо думать или искать готовый векторизатор:)

понедельник, 24 февраля 2020 г.

Как в Java сделать фигуру полупрозрачной? Есть ли встроенные методы для этого?

#java #графика #graphics #awt


Как в Java сделать фигуру полупрозрачной? Есть ли встроенные методы для этого?
Например, рисую круг и заливаю его красным цветом. Потом поверх него рисую прямоугольник,
больший по размеру и заливаю его, допустим, синим цветом. Необходимо поставить для
прямоугольника определенный уровень полупрозрачности для того, чтобы был виден и круг
под ним.
Возможно ли это сделать?  
    


Ответы

Ответ 1



Можно или установить цвету прозрачность, или, для более сложных случаев, использовать AlphaComposite public class Alpha { static void initUi() { JFrame frame = new JFrame(); frame.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); JPanel colorWithAlpha = new JPanel() { @Override public void paintComponent( Graphics g ) { g.setColor( Color.WHITE ); g.fillRect( 0, 0, getWidth(), getHeight() ); g.setColor( Color.RED ); g.fillOval( 0, 0, getWidth(), getHeight() ); // rgba, a - альфа-канал, непрозрачность g.setColor( new Color(0, 0, 255, 128) ); g.fillRect( 0, getHeight()/3, getWidth(), getHeight()/3 ); } }; JPanel alphaComposite = new JPanel() { @Override public void paintComponent( Graphics g ) { Graphics2D g2d = (Graphics2D)g.create(); try { g2d.setColor( Color.WHITE ); g2d.fillRect( 0, 0, getWidth(), getHeight() ); g2d.setColor( Color.RED ); g2d.fillOval( 0, 0, getWidth(), getHeight() ); // композит SrcOver отображает исходное изображение (src, // прямоугольник), поверх целевого изображения, с заданной // в derive( float ) alpha AlphaComposite composite = AlphaComposite.SrcOver.derive( 0.5f ); g2d.setComposite( composite ); g2d.setColor( Color.BLUE ); g2d.fillRect( 0, getHeight()/3, getWidth(), getHeight()/3 ); } finally { g2d.dispose(); } } }; JPanel content = new JPanel( new GridLayout( 1, 2 ) ); content.add( colorWithAlpha ); content.add( alphaComposite ); frame.setContentPane( content ); frame.setSize( 800, 600 ); frame.setVisible( true ); } public static void main(String[] args) throws IOException { EventQueue.invokeLater( Alpha::initUi ); } } Обучалка от Oracle по композитам

суббота, 22 февраля 2020 г.

Как соединить две окружности прямой

#c_sharp #графика


Имеются две окружности одного радиуса R с центрами в точках x1, y1; x2, y2, с произвольным
расположением.

Вопрос: как провести прямую, которая соединяла бы эти окружности, но при этом не
пересекала их? 
Вариант с перерисовыванием окружностей поверх прямой не годится.
Прямую рисую вот так:

Brush red = new SolidBrush(Color.Red);
Pen redPen = new Pen(red, 2);         
gr.DrawLine(redPen, x1, y1, x2, y2);




    


Ответы

Ответ 1



У вас есть окружности с центрами в O1 = (x1, y1) и O2 = (x2, y2), радиуса r. Заведём вектор C, указывающий из центра первой окружности в центр второй: классическое "конечные минус начальные": C = O2 - O1 = (x2 - x1, y2 - y1). Но это смещение, лишённое "базовой точки". Для чего оно? Для того, чтобы вычислить смещение каждого из двух концов искомого отрезка относительно центров окружностей. Если присмотреться, видно, что смещения одинаковы, просто выполнены в прямо противоположные стороны. И направлены они параллельно (коллинеарно, в векторных терминах) C. У нас есть правильно направленный вектор, но неправильной длины. Но длину можно исправить, умножив вектор на число m = r / length(C). Получится вектор S = C * m = (C.x * m, C.y * m). O1 + S это одна точка отрезка, O2 - S другая.

Ответ 2



Я разобрался и нашел более простое решение: double theta = Math.Atan2(y2 - y1, x2 - x1); x1 = x1 + r * cos(theta) y1 = y1 + r * sin(theta) Вторая координата (x2, y2) находится аналогично, только радиус вычитается

Ответ 3



Алгоритм вычисления крайних точек следующий: int x1 = 0, y1 = 0, r1 = 1, x2 = 3, y2 = 4, r2 = 1; // получаем единичный вектор направленный от центра первой окружности ко второй Vector2 norm = Vector2.Normalize(new Vector2(x2-x1, y2-y1)); // удлиняем его на радиусы окружностей Vector2 v1 = Vector2.Multiply(r1, norm); // для конца линии умножаем на отрицательный скаляр - получаем вектор в обратном направлении Vector2 v2 = Vector2.Multiply(-r2, norm); // Искомые точки получаем простым суммированием: Console.WriteLine("(" + (x1+v1.X) +", "+ (y1+v1.Y)+") - (" + (x2+v2.X) +", "+ (y2+v2.Y)+")");

воскресенье, 16 февраля 2020 г.

Как соединить две окружности прямой

#c_sharp #графика


Имеются две окружности одного радиуса R с центрами в точках x1, y1; x2, y2, с произвольным
расположением.

Вопрос: как провести прямую, которая соединяла бы эти окружности, но при этом не
пересекала их? 
Вариант с перерисовыванием окружностей поверх прямой не годится.
Прямую рисую вот так:

Brush red = new SolidBrush(Color.Red);
Pen redPen = new Pen(red, 2);         
gr.DrawLine(redPen, x1, y1, x2, y2);




    


Ответы

Ответ 1



У вас есть окружности с центрами в O1 = (x1, y1) и O2 = (x2, y2), радиуса r. Заведём вектор C, указывающий из центра первой окружности в центр второй: классическое "конечные минус начальные": C = O2 - O1 = (x2 - x1, y2 - y1). Но это смещение, лишённое "базовой точки". Для чего оно? Для того, чтобы вычислить смещение каждого из двух концов искомого отрезка относительно центров окружностей. Если присмотреться, видно, что смещения одинаковы, просто выполнены в прямо противоположные стороны. И направлены они параллельно (коллинеарно, в векторных терминах) C. У нас есть правильно направленный вектор, но неправильной длины. Но длину можно исправить, умножив вектор на число m = r / length(C). Получится вектор S = C * m = (C.x * m, C.y * m). O1 + S это одна точка отрезка, O2 - S другая.

Ответ 2



Я разобрался и нашел более простое решение: double theta = Math.Atan2(y2 - y1, x2 - x1); x1 = x1 + r * cos(theta) y1 = y1 + r * sin(theta) Вторая координата (x2, y2) находится аналогично, только радиус вычитается

Ответ 3



Алгоритм вычисления крайних точек следующий: int x1 = 0, y1 = 0, r1 = 1, x2 = 3, y2 = 4, r2 = 1; // получаем единичный вектор направленный от центра первой окружности ко второй Vector2 norm = Vector2.Normalize(new Vector2(x2-x1, y2-y1)); // удлиняем его на радиусы окружностей Vector2 v1 = Vector2.Multiply(r1, norm); // для конца линии умножаем на отрицательный скаляр - получаем вектор в обратном направлении Vector2 v2 = Vector2.Multiply(-r2, norm); // Искомые точки получаем простым суммированием: Console.WriteLine("(" + (x1+v1.X) +", "+ (y1+v1.Y)+") - (" + (x2+v2.X) +", "+ (y2+v2.Y)+")");

понедельник, 10 февраля 2020 г.

Пересечение отрезка и эллипса

#java #графика #геометрия


Дан отрезок из двух точек А и В (у каждой точки есть координаты).

Дан эллипс (пока что задаю верхней левой точкой, длиной и шириной, но при необходимости
эти параметры можно заменить на любые другие). 

Задача: узнать пересекаются ли отрезок с эллипсом. Случай, при котором отрезок полностью
в эллипсе не удовлетворяет условие.

Помогите, пожалуйста, а то ничего в голову не приходит. Если возможно, то нужен код
в java, если нет - дайте решение, а код напишу сам.
    


Ответы

Ответ 1



UPD (исходя из комментария @AnT): Возможно такое расположение точек и эллипса, при котором обе точки будут лежать вне эллипса, но пересечение будет (одно, в случае, когда прямая, проходящая через заданные точки будет касательной к эллипсу и два в остальных случаях). Этот случай в данном решении не рассмотрен. В общем случае придется-таки решать систему уравнений. Вариант, с помощью которого не нужно решать уравнения (без использования численных методов): Пусть задан эллипс: (x-x0)^2/a^2 + (y-y0)^2/b^2 = 1 , где: (x0;y0) – координаты центра эллипса; a и b – длины большой и малой полуосей соответственно. и отрезок AB, где A(x1;y1) и B(x2;y2). Для того, чтобы отрезок AB пересекал заданный эллипс, необходимо и достаточно, чтобы была верна одна из систем неравенств: (x1-x0)^2/a^2 + (y1-y0)^2/b^2 < 1 (x2-x0)^2/a^2 + (y2-y0)^2/b^2 > 1 (случай, когда точка A лежит внутри эллипса, а B – вне его); (x1-x0)^2/a^2 + (y1-y0)^2/b^2 > 1 (x2-x0)^2/a^2 + (y2-y0)^2/b^2 < 1 (случай, когда точка A лежит вне эллипса, а B – внутри его). Если интересует случай с принадлежностью точек дуге эллипса, то в вышеприведенных системах неравенства будут нестрогие.

Ответ 2



Из геометрии- две линии пересекаются, если для системы уравнений, описывающей эти линии, существует решение создаем систему уравнений из 2х прямая = (x-xa)/(xb-xa)=(y-ya)/(yb-ya) уравнение эллипса необходимо выбирать максимально вам подходящее, после чего в обоих уравнениях выражаете y и решаете систему для канонического уравнения эллипса (оси эллипса совпадают с осями координат) (x^2/a^2+y^2/b^2=1) прямая y=((x-xa)*(yb-ya))/(xb-xa)+ya эллипс y=+-sqrt((1/b^2)-(x^2/a^2*b^2)) далее подставляем все известные данные и получаем или не получаем точку, если получили - есть пересечение, не получили = нет

Ответ 3



Любой эллипс является ни чем иным, как "растянутой" (или, если хотите, "сплюснутой") окружностью. Достаточно построить преобразование координат, которое превратит исходный эллипс в произвольную окружность ненулевого радиуса, и задача сведется к определению факта пересечения отрезка с окружностью. (Понятно, что такое преобразование координат переведет отрезок в отрезок.) Из приведенного условия можно неявно заключить, что речь идет не о произвольном эллипсе, а об эллипсе, оси которого параллельны осям координат. (Кстати, почему этого не сказано явно в условии?). В такой ситуации требуемое преобразование строится элементарно - это просто домножение одной из координат на коэффициент a/b - соотношение длин осей эллипса. Тем самым мы сводим задачу к задаче о пересечении отрезка и окружности. Задача пресечения отрезка с окружностью решается просто. Достаточно вычислить координаты концов хорды, "отсекаемой" прямой, содержащей наш отрезок (если таковая хорда существует). Положение концов отрезка относительно концов хорды сразу же скажет нам, пересекает ли наш отрезок данную окружность (и, если надо, даст нам координаты точек пересечения). Обратное преобразование координат даст нам координаты точек пересечения с исходным эллипсом (если они нас интересуют).

суббота, 8 февраля 2020 г.

Есть ли отличия между тесселяцией и триангуляцией?

#графика


И при триангуляции и при тесселяции происходит разбиение объектов на полигоны. Чем
они отличаются и для каких целей применяется каждая из них?    


Ответы

Ответ 1



Тесселяция является более общей техникой. Поверхность разбивается на мелкие многоугольники, как вот в этом примере в средней строке: Триангуляция разбивает строго на треугольники, где соседние граничат по общей стороне. Так что триангуляция тоже тесселяция, а тесселяция с верхнего рисунка не триангуляция: и то и другое применяется для показа поверхностей. Триангуляция применяется когда необходимо чтобы каждый кусочек лежал строго в одной плоскости. Треугольник всегда лежит в одной плоскости. Когда сеть разбивает поверхность, то кусочки с четырьмя и более вершинами не всегда плоские.

пятница, 7 февраля 2020 г.

Метод рендеринга Гуро, формулы

#алгоритм #графика


Попытался разобраться в методе Гуро, но не могу понять значения переменных в формулах,
искал в гугле уже, но всюду не приводится их смысл. Что бы не вырывать из контекста
формулы прикрепляю две страницы из учебника



Поясните пожалуйста что это за переменные. И что значит освещенность в данном контексте,
как это связать с цветом? Или есть какой-то учебник совсем для чайников ? 
    


Ответы

Ответ 1



Прежде чем вдаваться в физический смысл формул - необходимо разобраться в трёх компонентах света - ambient, diffuse и specular. по приведённым вами формулам: Ia - ambient - фоновая мощность света, иначе говоря этот тот свет который материал воспринимает без рассеивания и зеркального отражения. Является постоянной величиной и не зависит от позиции ни объекта ни источника света. Ka - ambient - показывает степень материала воспринимать фоновый свет. Kd - diffuse (рассеивающая) составляющая материала - показывает степень материала воспринимать рассеивающий свет - тот свет который в зависимости от ориентации (нормали) поверхности и направления света рассеивается во все стороны равномерно. (n, l) - скалярное произведение векторов n - нормали к касательной плоскости к поверхности и l - вектора идущего из источника света в произвольную точку на изображении - при cos(n, l) == 1 произведение Kd * (n, l) даст Kd что будет означать максимальную яркость диффузной компоненты материала в этой точке, и при cos(n, l) = 0 наоборот, максимальную темноту соответствующего пикселя (или точки). Ks - specular составляющая материала - свойство материала воспринимать зеркальный свет (например сталь отражает свет с неким блеском). (v, r)^p - скалярное произведение между вектором отражённым от вектора света и видовым вектором (вектор идущий из позиции камеры в точку), в степени коеффициента фонга (p) - чем больше эта степень тем острее будет отражаться зеркальный свет - и аналогично с диффузным источником света когда угол между этими векторами 0 тоесть cos 0 == 1 зеркальное освещение достигает своего максимума (иначе говоря видовой вектор совпадает с вектором отражающий свет) и минимума когда угол прямой или больше прямого cos pi/2 == 0. p.s. специально не привожу вариант cos(n, h)^p который в вашем учебнике, так как обычно зеркальное отражение света зависит от направления взгляда v и отражённого от света вектора r, а там не ясно что имеется ввиду под векторами n и h. С помощью формул 11.1 и 11.2 вычисляется конечный цвет пикселя как сумма всех составляющих - в первом случае с учётом только эмбиентной и диффузных компонент, во втором с учётом всех трёх. Важно заметить, что модель Гуро вычисляя значения цвета во всех вершина полигона и интерполируя по этим точкам остальные значения иногда может приводить к потере аккурантности всего изображения по сравнению с моделей фонга, который вычисляет конечный цвет во всех точках (пикселях) делая результат более аккуратным но засчёт значительно большого количества вычислений.

воскресенье, 26 января 2020 г.

OpenGL - “Поймать Мышь”

#cpp #opengl #графика


У меня есть код для преобразований координат мыши в OpenGL координаты.
И вроде отрисовывается и всё прекрасно, но при подведении мыши к краю,
центр квадрата (это полигон по координатам мыши) смещается в сторону.

Кто сталкивался с проблемой или у кого есть рабочий код по отлову координат в OpenGL?

ТОЛЬКО НЕ GLUT! 


Этого дела полно в Сети.
Пишу не под ним.


Ссылка на исходники

~Координаты мыши

GLvoid MouseCord(int mouse_x, int mouse_y, CVector3& p1)
{
// mouse_x, mouse_y  - оконные координаты курсора мыши.
// p1, p2            - возвращаемые параметры - концы селектирующего отрезка,
//                     лежащие соответственно на ближней и дальней плоскостях
//                     отсечения.
GLint    viewport[4];    // параметры viewport-a.
GLdouble projection[16]; // матрица проекции.
GLdouble modelview[16];  // видовая матрица.
GLdouble vx,vy,vz;       // координаты курсора мыши в системе координат viewport-a.
GLdouble wx,wy,wz;       // возвращаемые мировые координаты.

glGetIntegerv(GL_VIEWPORT,viewport);           // узнаём параметры viewport-a.
glGetDoublev(GL_PROJECTION_MATRIX,projection); // узнаём матрицу проекции.
glGetDoublev(GL_MODELVIEW_MATRIX,modelview);   // узнаём видовую матрицу.
// переводим оконные координаты курсора в систему координат viewport-a.
vx = mouse_x;
vy = viewport[3] - mouse_y - 1;
// вычисляем ближний конец селектирующего отрезка.
vz = -1;
gluUnProject(vx, vy, vz, modelview, projection, viewport, &wx, &wy, &wz);
p1.x = wx*100;
p1.y = wy*100;
p1.z = wz;
system("cls");
std::cout<<"p1.x: "<


Ответы

Ответ 1



К сожалению ваши исходники не доступны. Лучше бы добавили скриншоты проблемы. Вы можете попробовать ортогональную проекцию, может ли она. Если преобразовывать координаты мыши в 3Д координаты ОпенГЛ, то вы должны получить луч, а не точку, т.к. при разном значении vz координат мыши, вы получите разные координаты z в 3D. Тут есть 2 варианта решения: Вы переводите 2 точки координат мыши с разными vz и получив луч в 3Д пространстве, найти точку пересечения его с интересующей вас плоскостью. Это ваша конечная точка. Найти какое должно быть значение vz для конкретной точки в 3Д пространстве. Если ваша плоскость находится "лицом" к камере, то берёте любую точку на ней и проецируете её на экран. Затем берёте знамение Z этой точки спроецированной на экран и используете её как значение для vz.

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

Получение данных из “такой” карты высот

#алгоритм #графика


от есть такой кусок карты высот (8бит на канал) 



Как я догадался, эти резкие скачки отвечают за разные уровни высоты. То есть, где
где большая разница между светлым и голубым, там осуществляется переход между уровнями.
При этом участки светлее отвечают за меньшую высоту на своем уровне.

Значит попробовал я следующим образом: на каждой строке проверять разницу между значениями
красного канала на соседних пикселях. Взять к примеру константу max_dist = 180, и если
разница больше max_dist , тогда засчитывать переход на следующий уровень, то же самое
с переходом на предыдущий уровень при разнице меньше -max_dist.

Но это не  работает, только появляются полосы. Какие еще есть способы извлечь такие
данные?

UPDATE: Оказалось все еще интереснее)

Склеив все эти кусочки получил


    


Ответы

Ответ 1



Зеленый канал определяет абсолютную высоту, хотя поделен на несколько уровней(их нужно разделять, в данном примере первый уровень в голубых областях, второй в последующей красной с зеленым области, и последний в правом нижнем углу. Красный канал это остаток зеленого.По возвышениям 255 единиц из красного немного меньше чем 1 единица из зеленого. Отсюда можно вывести формулу Синий канал содержит только пару уровней возвышения зеленого (но их оказывается больше, поэтому он бесполезен, все равно нужно вручную отделять уровни) elevation = level*255 + green + red/256

среда, 22 января 2020 г.

Векторная или растровая графика для 2D игры?

#анимация #разработка_игр #графика #photoshop


Делаю 2D игру на движке Unity, определился, что анимация для персонажей будет скелетной
и реализовано с помощью расширения Anime2D, но вот где рисовать сами спрайты озадачился.
Будет это растровое изображение(использование photoshop), или векторное.

Поэтому вопросы:


В каких случаях используют векторную, а в каких растровую? (например
для персонажей рисуют векторную, а для фонов и декораций растровую)
И какие самые популярные и лучшие инструменты для этого
используются(например для растровой насколько я понял adobe
photoshop мастхев), а для векторной графики?
Есть разница в создании анимации при использовании растрового или
векторной графики?

    


Ответы

Ответ 1



Вообще юнити из коробки,на сколько мне извесно, не имеет возможности использовать векторную графику. А значит, предпочтительнее растовая, как поддерживаемая по дефолту. Если расчитываешь что будут играть на больших расширениях экрана и не на мобилах) Векторная графика в любом случае будет более требовательна к железу. Тем более если работать со сторонними ассетами, которые не факт что написаны нормально. Как ни странно -- любой векторный редактор, формат которого поддерживает выбранный ассет юнити. Скорее всего файлы адоб Илюстратора будут поддерживатся. Понятия не имею. Не пробовал

Нарисовать обводку эллипса

#алгоритм #графика #любой_язык


Как можно точно и быстро нарисовать обводку эллипса в канонической форме заданной
толщины? При этом используя только треугольники или отдельные пиксели. 

Сам-то эллипс можно, например, с помощью алгоритма Брезенхема или Midpoint circle,
а для обводки так сразу ничего не нашёл. Может быть, можно как-нибудь применить параметрические
уравнения обводки (двух её границ):



Здесь (x0, y0) точка на эллипсе, h — толщина обводки (больше 0 — внешняя, меньше
— внутренняя).

Обновление

Обводка эллипса — это область чёрного цвета на этом рисунке.



var a_canvas = document.getElementById("a");
var ctx = a_canvas.getContext("2d");

ctx.lineWidth = 20;
ctx.beginPath();
ctx.ellipse(100, 100, 50, 70, 0, 0, 2 * Math.PI);
ctx.closePath();
ctx.stroke();




  
  






    


Ответы

Ответ 1



Жирный эллипс - дело нехитрое. Берем неявно заданное уравнение эллипса: F= x^2/a^2 + y^2/b^2 + z^2/c^2 = R^2 Для этого уравнения берем вектор нормали: N{2x/a^2; 2y/b^2; 2z/c^2;} Нормируем N, путем деления на длину. В каждой точке X нашей области, где сидит наш эллипс, проверяем, что: F( X + e*N) * F(X - e*N) < 0 e - половина толщины обводки X - координаты текущей точки Если условие выполняется, рисуем черный пиксел

Ответ 2



Придумал такой алгоритм. Можно построить две кривые — границы обводки, и всё, что между ними, закрасить. Эти кривые — эквидистанты, они равноудалены от эллипса. Для построения кривых можно использовать Midpoint-алгоритм, подобно тому, как это делается для эллипса и круга. Правда, оптимизировать его аналогичным образом мне не удалось. Уравнение сразу двух кривых в форме f(x,y) = 0 я взял отсюда (теорема 3). Точки, где производная равна единице, можно найти, подставив точку эллипса, где производная единица, в уравнения в вопросе. Дополнительно, для внутренней эквидистанты, нужно найти точки пересечения функции и OY и начинать алгоритм не с (a-h, 0), а с одной из точек, как только две точки пересечения совпадут. Можно строить эквидистанты только для эллипсов с a >= b, иначе поворачивать построенные кривые. В этом коде строятся только границы обводки: const CHANNELS_PER_PIXEL = 4; function calcFunc(x, y, a, b, h) { var a1 = a * a + b * b - x * x - y * y + h * h; var a2 = a * a * b * b - b * b * x * x - a * a * y * y + h * h * (a * a + b * b); var a3 = h * h * a * a * b * b; var a312 = a3 / a1 / a2; return 1 - 4 * a2 / a1 / a1 - 4 * a1 * a3 / a2 / a2 + 18 * a312 - 27 * a312 * a312; } function drawOutline(x0, y0, a, b, h, canvas) { var imageWidth = canvas.width; var imageHeight = canvas.height; var context = canvas.getContext('2d'); var imageData = context.getImageData(0, 0, imageWidth, imageHeight); var pixelData = imageData.data; var makePixelIndexer = function (width) { return function (i, j) { var index = CHANNELS_PER_PIXEL * (j * width + i); return index; }; }; var pixelIndexer = makePixelIndexer(imageWidth); var drawPixel = function (x, y, col) { var r = (col >> 16) & 255; var g = (col >> 8) & 255; var b = col & 255; var idx = pixelIndexer(x, y); pixelData[idx] = r; pixelData[idx + 1] = g; pixelData[idx + 2] = b; pixelData[idx + 3] = 255; }; var swap = false; if(b > a){ swap = true; var t = a; a = b; b = t; } var draw4Pix = function(x, y, col){ if(swap) { drawPixel(x0 - y, y0 - x, col); drawPixel(x0 - y, y0 + x, col); drawPixel(x0 + y, y0 - x, col); drawPixel(x0 + y, y0 + x, col); } else { drawPixel(x0 - x, y0 - y, col); drawPixel(x0 - x, y0 + y, col); drawPixel(x0 + x, y0 - y, col); drawPixel(x0 + x, y0 + y, col); } }; var x = a + h; var y = 0; var lastX = a * a / Math.sqrt(a * a + b * b) + h / Math.sqrt(2); var prevX; while (true) { draw4Pix(x, y, 0xFF0000); y++; if (calcFunc(x - 0.5, y + 1, a, b, h) > 0) { x--; } if (x < lastX) { draw4Pix(x, y, 0xFF0000); prevX = x; break; } } if(prevX != 0) { x = 0; y = b + h; while (true) { draw4Pix(x, y, 0xFF0000); x++; if (calcFunc(x + 0.5, y - 1, a, b, h) > 0) { y--; } if (x >= prevX) { draw4Pix(x, y, 0xFF0000); break; } } } if(h < a && h < b){ lastX = a * a / Math.sqrt(a * a + b * b) - h / Math.sqrt(2); var root = (Math.sqrt(a*a - b*b) * Math.sqrt(b*b - h*h))/b; if(h > b*b / a){ x = Math.ceil(root); } else { x = a - h; } y = 0; if(x > lastX) { while (true) { draw4Pix(x, y, 0x0000FF); y++; if (calcFunc(x - 0.5, y + 1, a, b, h) < 0) { x--; } if (x < lastX) { draw4Pix(x, y, 0x0000FF); prevX = x; break; } } } else { prevX = Math.floor(root); draw4Pix(x, y); } if (prevX != 0) { x = 0; y = b - h; while (true) { draw4Pix(x, y, 0x0000FF); x++; if (calcFunc(x + 0.5, y - 1, a, b, -h) < 0) { y--; } if (x >= prevX) { draw4Pix(x, y, 0x0000FF); break; } } } } context.putImageData(imageData, 0, 0); } function ellipseAndOutline(x0, y0, a, b, h, drawEllipse, canv){ drawOutline(x0, y0, a, b, h, canv); if(drawEllipse) { var ctx = canv.getContext('2d'); ctx.beginPath(); ctx.lineWidth = 1; ctx.ellipse(x0, y0, a, b, 0, 0, Math.PI * 2); ctx.closePath(); ctx.stroke(); } } document.getElementById('btn').onclick = function () { var canv = document.getElementById('canv'); var ctx = canv.getContext('2d'); ctx.clearRect(0, 0, canv.width, canv.height); ellipseAndOutline( canv.width / 2, canv.height / 2, parseInt(document.getElementById('aInp').value), parseInt(document.getElementById('bInp').value), parseInt(document.getElementById('hInp').value), document.getElementById('drawEllCh').checked, canv); }
a b h эллипс


Необходимо быстро отображать битовую матрицу в WPF

#wpf #графика


Реализую игру Жизнь Конвея, возникла проблема в "быстром отображении"(под быстром
понимается каждые 20 милисеунд на форме(или на канвасе) рисовать около 60000 квадратов)
матриц каждого хода. Рисую обычным проходом по битовой матрице и "тупой от рисовкой"
на канвас(пример метода  ниже), но получается слайд шоу.Есть ли какие-то методы от
рисовки больших объемов данных в WPF?

public static void DrawOnCanvas(bool[,] matrix, Canvas canvas)
{
  int height = matrix.GetLength(0);
  int width = matrix.GetLength(1);
  //размеры матрицы
  for (int i = 0; i < width; i++)
  {
    for (int j = 0; j < height; j++)
    {
      Ellipse eleps = new Ellipse();
      //создаю элипс
      eleps.Height = 4;
      eleps.Width = 4;
      if (matrix[j , i] == true )
      {
          eleps.Fill = new SolidColorBrush(Colors.Red);
          Canvas.SetLeft(eleps, i * 5);
          Canvas.SetTop(eleps, j * 5);
          canvas.Children.Add(eleps);
          //добавляю его на канвас если в атрице 1
      }
    }
  }
}

    


Ответы

Ответ 1



Вот решение данной "проблемы ", я не знаю как считать fps в wpf приложениях но чисто на глаз матрица 256х256 отображается довольно шустро. public static void DrawOnImage(bool[,] outmatrix, Image img) { WriteableBitmap wb = new WriteableBitmap((int)img.Width, (int)img.Height, 1, 1, PixelFormats.Bgra32, null);//создаем типо битмап указывая ширину и высоту формат пиксилей и чето еще вроде как dpi Int32Rect rect = new Int32Rect(0, 0, (int)img.Width, (int)img.Height); byte[] pixels = new byte[(int)img.Width * (int)img.Height * wb.Format.BitsPerPixel / 8]; int alpha = 255; int red = 0; int green = 0; int blue = 0; int height = outmatrix.GetLength(0); int width = outmatrix.GetLength(1); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if ((outmatrix[j, i] == true)) { red = 255; } else { red = 0; } int pixelOffset = (i + j * wb.PixelWidth) * wb.Format.BitsPerPixel / 8; pixels[pixelOffset] = (byte)red; pixels[pixelOffset + 1] = (byte)green; pixels[pixelOffset + 2] = (byte)blue; pixels[pixelOffset + 3] = (byte)alpha; } int stride = (wb.PixelWidth * wb.Format.BitsPerPixel / 8); //шаги wb.WritePixels(rect, pixels, stride, 0); } img.Source = wb; }

четверг, 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



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