Здравствуйте
Этот пост особенный - ведь прошло уже два месяца с момента публикации первой записи.
Как и обещалось, сегодня я презентую демо версию игры (тут я немного слукавил - это больше похоже на техническую демку движка).
Игра называется Dangerous (что как бы намекает). Если вы ещё не посмотрели на эффект, обязательно сделайте это (для просмотра настоятельно рекомендуется Google Chrome), ведь в этом движке нашли применение почти все no-canvas технологии, про которые я уже успел написать в блоге.
No Canvas
Stars Sky - обычные div-ы размером 1x1 px.
Planets - мой метод рисования кругов без канваса™
Polygons - http://www.uselesspickles.com/triangles/demo.html (не позволяет рисовать треугольники с освещением по гуро или текстурами, но зато генерит очень мало DOM элементов).
Cross - прицел нарисован так же, как и звёздное небо, обычными дивами.
JavaScript
Изначально я хотел использовать jQuery для доступа к DOM элементам, но решил попробовать Yet Another cSS selector (или сокращённо - yass).
В результате оказалось, что ни jQuery, ни yass мне на самом деле не нужны :), но в yass я нашёл управление зависимостями файлов искаропки, так что решил оставить.
Честно говоря сделано оно несколько странно, но чтобы не изобретать ненужный для моих целей велосипед, я решил пока оставить yass.
В очередной раз я использую кастомную функцию random, чтобы была возможность на лету генерировать целые вселенные (как в оригинальной Elite).
Так же, в этот раз я решил изменить своим принципам, и добавлять методы в объекты через прототипы (обычно я добавляю методы в конструкторе, что даёт возможность использовать "множественное наследование").
Math
Теперь о вещах, ни с канвой, ни с её отсутствием, ни даже с javascript никак не связанных.
Поговорим о математике.
Matrix
Повороты и движение выполняются с помощью привычной матрицы 4x3 (обычно используется матрица 4x4, но так как последний ряд всегда равен [0 0 0 1], его можно опустить).
Попробуем вывести объект.
1) Объект надо повернуть (матрица OBJROT)
2) Перенести в координаты где он находится (матрица OBJMOV)
3) Перенести относительно камеры (матрица CAMMOV)
4) Повернуть относительно камеры (матрица CAMROT)
Даже если шаги 2 и 3 слить и сделать без матриц, всё равно потребуется 18 умножений на точку.
Значит для 20 объектов о 6-ти вершинах каждый (а именно столько таких объектов используется в качестве "космического мусора") потребуется 20*6*18 = 2160 умножений.
Однако, можно заметить, что
(CAMROT * (CAMMOV * (OBJMOV * (OBJROT * point)))) =
(CAMROT * (CAMMOV * (OBJMOV * OBJROT))) * point =
((CAMROT * CAMMOV) * (OBJMOV * OBJROT)) * point
Итого требуется:
1) Единоразово выполнить умножение 2х матриц камеры (36 умножений)
2) Для каждого объекта выполнить умножение 3х матриц (72 умножения)
3) Для каждой точки объекта выполнить всего 9 умножений
Учитывая что на втором шаге одни из умножаемых мартиц - это матрица движения на матрицу поворота, его можно оптимизировать, выкинув ненужные умножения (в результате получим 36 умножений на 2м шаге).
Для тех же объектов потребуется выполнить всего 36 + ((36 + 9*6) * 20) = 1836 умножений, то есть на 324 умножения меньше. Причём с ростом числа объектов и числа вершин в них, разница будет расти.
Quaternion
Для управления кораблём задействуем ту же систему, что и в Elite - корабль можно вращать (roll) и поднимать/опускать (pitch).
Если пробовать его крутить по обычным осям (X/Y, X/Z, Y/Z) или даже по всем трём (что явно лишнее), через пару секунд управления становится непонятно что вообще происходит, и как нажатые кнопки влияют на ориентацию корабля в пространстве.
Чтобы избежать таких проблем, использоют кватернионы (что это?, вики). Так как мне совершенно не нравилась перспектива преобразования кватерниона в матрицу поворота самостоятельно (тем более что раньше я такого никогда не делал), на просторах интернета была найдена Oolite - open source реализация elite-подобной игры на Objective C.
Оттуда, кроме формул для матрицы поворота, я получил ещё и базисные вектора для нового пространства (или не пространства? в универе я совсем забивал на матан), которые помогут сделать движение вперёд/назад.
Hacks
Как сделать чтоб звёздное небо крутилось и двигалось вместе с планетами и объектами, но чтоб не генерить его на всю вселенную?
Сначала подвинем его относительно камеры, затем вернём вылетевшие точки внутрь блока и только потом повернём их.
Code
Несколько комментариев к коду (скачайте его прямо сейчас) в стиле javadoc:
main - загрузчик
common.* - разные полезняшки, привычные рубисту
dg - игровой менеджер. клей для остальных частей
dg.rotatematrix - матрица поворота и операции над ней
dg.quat - квартернион и операции над ним
dg.camera - по сути прокси к RotateMatrix и Quat
dg.viewport - явный представитель антипаттерна God Object - отсекает невидимые точки, расчитывает коеффициенты для проецирования, рисует точки, круги и треугольники
dg.point - точка в 3D либо вершина объекта
dg.objectslist - позволяет производить операции над списком объектов (точки, планеты либо полигональные объекты)
dg.starssky - звёздное небо
dg.planet - планеты и связанные с ними формулы
dg.planetslist - прокси к objectslist + некоторые кастомные функции
dg.object3d - полигональные объекты
dg.objects3dlist - операции над списком 3D объектов
P.S.
Радио "Космический блюз" желает вам удачных полётов и передаёт сводку последних новостей: на некоторое время (небольшое) изменяется переодичность постов - вместо двух постов в месяц (аки одного в две недели) будет один пост в месяц. У меня внезапно появились срочные и долговременные дела, и дабы не писать шило вместо нормальных постов я решил увеличить промежутки межну ними.
Ещё раз - удачных полётов.
Сайт русской поддержки Oolite http://www.roolite.org
ReplyDelete