3 d блок кубы ускоренный способ

3D на HTML и CSS: вращающийся куб

Создать 3D-объекты с помощью HTML и CSS совершенно не сложно. Другое дело, что возможности разметки не позволяют делать простым подходом сложные геометрические фигуры без использования дополнительных средств, вроде Canvas и SVG. Тем не менее, даже ограниченные возможности HTML-разметки позволяют делать достаточно красивые вещи. И в этой статье, на примере маленькой посадочной страницы, мы рассмотрим, как сделать 3D объект — куб, грани которого будут представлять собой блоки информации.

Пример посадочной страницы c 3D-элементами

Взгляните на страницу во фрейме, которую мы будем пытаться сделать. Обозначим ее, как аналогичную посадочной, потому что она имеет основные элементы таковой: навигацию и блоки информации. Сверху находятся ссылки, по клику на которые будет осуществляться переход на соответствующие грани куба посредством его вращения. Ниже, в качестве дополнения, добавлены еще пару ссылок, чтобы продемонстрировать приемы 3D в HTML и CSS. Поиграйтесь с HTML-кубом, а затем детально разберем, как это все работает.

Страница c вращающимся кубом, демонстрирующая возможности 3D в HTML и CSS.

И так, вот для начала весь код тела нашей посадочной страницы. Я исключил некоторые CSS-эффекты вроде теней, чтобы не осложнять разбор кода, но вы можете их рассмотреть самостоятельно, открыв исходный код данной посадочной страницы.

Навигация по такой посадочной странице была бы затруднительна без JavaScript, поэтому дополним ее несколькими строками простенького кода, который мы также подробно разберем ниже.

Что ж, теперь давайте шаг за шагом разберем, как устроена данная 3D-страница.

Как делается куб на HTML и CSS

Сделать куб на HTML и CSS благодаря возможностям CSS-функциям 3D трансформации очень просто. Всё, что представляется из себя данная геометрическая фигура — это развернутые на требуемые углы и смещенные в определенные стороны полигоны, то есть блочные элементы (div, article или подобное).

И вот мы добрались до самого интересного. Если мы одному дочернему блоку с помощью CSS-трансформации зададим поворот по оси X равным 90° и сместим этот блок на половину его размера по оси оказавшейся перпендикулярной этому блоку, то таким образом мы получим верхнюю грань куба. Аналогично этому, если мы уже развернем другой блок на -90° и так же сместим, то получим нижнюю грань. Фронтальная грань и так развернута как нужно, поэтому для нее потребуется только смещение. А вот для боковых сторон куба поворот нужно осуществлять уже по оси Y.

Таким образом должны получится подобные свойства для граней HTML куба:

Обратите внимание, что порядок перечисления CSS-функций трансформации важен. Каждая следующая трансформация выполняется относительно результата предыдущей функции. Поэтому не стоит удивляться, что смещение для всех граней куба в 3D-пространстве выполнено только по оси Z.

И вот, куб готов! И теперь таких можно сделать сколько угодно много на странице, просто копируя HTML и меняя лишь значения позиционирования копиям. И тут всё слишком просто, чтобы остановиться на этом. Поэтому пойдем дальше и займемся CSS-анимацией получившегося 3D HTML-объекта.

Вращающийся куб HTML / CSS

Анимировать куб еще проще. Для этого потребуется создать один ключевой кадр с промежуточным значением. Проще говоря, наш 3D-объект из своего исходного значения будет плавно переходить к промежуточному кадру и вновь возвращаться в свое исходное положение:

Затем, в класс .cube нужно добавить свойство анимации с указанием объявленных ключевых кадров:

animation: rotation 30s ease-in-out infinite;

Здесь длительность анимации 30 секунд, а функция времени установлена соответственно плавному началу и концу анимации. Используется бесконечное повторение.

Если вы хотите, чтобы анимация куба длилась без замедления и непрерывно, то нужно вместо промежуточного установить конечный 100% <. >ключевой кадр отличный от начального на n * угол полного оборота. То есть, например, если начальный угол по оси Y установлен равным -27° , то конечный ключевой кадр должен содержать угол равный -27° + 360° * n, где n, как вы уже догадались — количество оборотов. Кроме этого, понадобиться сменить временную функцию с ease-in-out на linear.

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

Создадим функцию selectEdge() , которая будет вращать куб на выбранные по ссылкам грани.
Внутри этой функции добавим две константы: элемент нашего 3D HTML-объекта и углы поворота куба соответствующие названиям якорей в ссылках и названиям сторон. Эти данные, для удобства дальнейшего использования, заключены в объект.

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

Поочередно в каждой итерации к элементу ссылки (elem) добавляется событие click, внутри функции обратного вызова описывается следующий сценарий:

Отменяется стандартное событие браузера по клику на ссылку (e) — e.preventDefault();

Получаем название грани куба из якоря ссылки let edgeName = e.target.hash.replace(/#/, »);

Назначаем новые свойства трансформации нашему кубу cube.style.transform =’perspective(…’ , где в свойства подставляются значения из объекта константы degs.

Завершив написание функции добавим ее вызов, который выполниться после загрузки страницы:

С JavaScript мы закончили, но остался еще один нюанс. Нам нужно добавить в класс .cube CSS-свойство перехода transition: all 1s ease; . Без него куб будет мгновенно перепрыгивать на новые свойства. Но не забудьте также убрать анимацию из этого класса, которую мы добавляли в предыдущей главе, иначе она будет мешать переходам по ссылкам.

Теперь вы можете совершенствовать и дополнять получившийся результат, чтобы получить действительно объемные и красивые веб-страницы.

В заключении хочу отметить, что разобранный код немного упрощен и отличается от кода посадочной страницы, которая представлена в начале статьи. Оттуда вырезаны пара функций JavaScript для демонстрации и некоторые стилистические эффекты CSS. Это сделано для того, чтобы не усложнять разбор кода для новичков излишними дополнениями. Но вы всегда можете открыть код в браузере и посмотреть как это устроено. А на этом всё. Желаю всем успехов!

Источник

3 d блок кубы ускоренный способ

В этой статье мы научимся рисовать простой 3D объект — куб. Также мы запрограммируем анимацию куба и виртуальную камеру, позволяющую взглянуть на куб глазами зрителя.

Содержание

Вершины куба

В чём OpenGL измеряет расстояния в трёхмерном мире? В 2D графике всё понятно: у нас есть пиксели. На трёхмерной проекции объекты одинакового размера могут дать разные проекции из-за перспективного искажения: чем дальше поверхность или линия, тем она меньше. Поэтому OpenGL измеряет расстояния в единицах измерения, не уточняя, в каких именно. Вы можете измерять в метрах, попугаях или в футах — на картинке это не имеет разницы, потому что исходные размеры не совпадают с проекциями.

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

Триангуляция куба

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

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

Теперь, глядя на иллюстрацию, можно перечислить все 12 треугольников, составляющих 6 граней.

Рисование в Immediate Mode

Для непосредственного рисования граней куба мы применим не самый производительный метод, который к тому же устарел в современных версиях OpenGL: рисование в блоке glBegin/glEnd, также известное как OpenGL Immediate Mode.

Настройка фиксированной камеры

В OpenGL при использовании фиксированного конвейера есть ровно две матрицы, относящихся к трансформациям точек и объектов:

  • GL_PROJECTION моделирует ортографическое или перспективное преобразование от трёхмерной усечённой пирамиды (т.е. от области видимости камеры) к трёхмерному кубу с длиной ребра, равной 2 (т.е. к нормализованному пространству).
  • GL_MODELVIEW сочетает в себе два преобразования: от локальных координат объекта к мировым координатам, а также от мировых координат к координатам камеры.

За рамками фиксированного конвейера можно использовать столько матриц, сколько захочется — но сейчас мы ограничены. Кроме того, нам приходится смешивать матрицы:

  • поведение камеры описывается как ортографическим или перспективным преобразованием, так и положением камеры в мировом пространстве, то есть для моделирования камеры нужны GL_PROJECTION и GL_MODELVIEW одновременно
  • c другой стороны, для трансформаций над телами — например, вращения куба с помощью умножения координат на матрицу — нам нужна матрица GL_MODELVIEW.

Обычно при программировании действуют так:

  • матрицу GL_PROJECTION обычно настраивают один раз для перспективного преобразования
  • матрицу GL_MODELVIEW постоянно модифицируют, когда локальная система координат очередного объекта не совпадает с мировой системой координат

Начнём настройку камеры с GL_MODELVIEW: зададим матрицу так, как будто бы камера смотрит с позиции eye на точку center , при этом направление “вверх” камеры задаёт вектор up :

Для перспективного преобразования достаточно создать матрицу с помощью функции glm::perspective . Она принимает на вход несколько удобных для программиста параметров преобразования: горизонтальный угол обзора камеры (англ. field of view), соотношение ширины и высоты (англ. aspect), а также две граничных координаты для отсечения слишком близких к камере и слишком далёких от камеры объектов. Для лучшего понимания взгляните на иллюстрацию:

Решение проблем невидимых поверхностей

При программном рисовании трёхмерного мира, состоящего из объемных тел с непрерывной поверхностью (англ. solid bodies), возникает вопрос: как не нарисовать невидимые поверхности? Например, на рисунке горы, покрытой лесом, мы не должны увидеть ни деревьев на противоположном к нам склоне, ни поверхности самого склона.

У художников для решения задачи есть свой алгоритм: сначала они рисуют композицию заднего фона, затем сверху покрывают средний фон, и, наконец, выводят передний фон:

Этот алгоритм так и называется — “алгоритм художника” (англ. painter’s algorithm). В компьютерной графике он иногда применим, но с модификацией: вместо деления объектов на три группы (задний фон, средний фон и передний фон) придётся отсортировать все объекты и вывести их в порядке приближения. К сожалению, не всегда объекты воможно отсортировать: это известно как “проблема художника” (англ. painter’s problem).

Для решения проблемы художника в OpenGL сделано следующее:

  • все поверхности представляются как базовые примитивы: в ранних версиях OpenGL это были точки, линии, треугольники и четырёхугольники, а в более поздних остались только треугольники
  • используется тест буфера глубины, т.е. модификация алгоритма художника, работающая с неделимыми фрагментами фигур
  • используется отсечение граней, развернувшихся невидимой стороной после умножения на матрицы GL_MODELVIEW и GL_PROJECTION

Тест буфера глубины

Буфер глубины OpenGL — это двумерная матрица дополнительных данных, где каждому пикселю соответствует одно значение float: глубина фрагмента примитива, оказавшегося ближе к пикселю, чем остальные фрагменты примитивов, проецируемые на тот же пиксель. Это позволяет реализовать попиксельный алгоритм художника: после приведения в нормализованное пространство каждая примитивная фигура будет разбита на фрагменты, для которых будет проведён тест глубины.

Фрагмент — это атомарная, то есть неделимая, часть фигуры. Если видеокарта не совершает сглаживание, то один фрагмент станет одним пикселем фигуры.

Тест глубины устроен следующим образом: если фрагмент в нормализованном пространтсве стал ближе, чем последнее значение буфера глубины, то мы выбираем его и обновляем значение в буфере глубины, иначе мы отбрасываем фрагмент. В псевдокоде способ выглядит так:

Как и любой другой буфер OpenGL, буфер глубины следует очищать. Для этого вызов glClear должен получать ещё один флаг GL_DEPTH_BUFFER_BIT :

Также нужно после создания контекста включить режим теста глубины командой glEnable:

Отсечение задних граней

OpenGL рассчитан на дополнительное отсечение невидимых поверхностей, построенное по принципу отсечения задних граней. По умолчанию включён режим, аналогичный вызову glFrontFace(GL_CCW) , и OpenGL делит примитивы на две группы:

  • те, вершины которых перечисляются против часовой стрелки (GL_CCW), становятся передними гранями (GL_FRONT)
  • те, вершины которых перечисляются по часовой стрелке (GL_CW), становятся задним гранями (GL_BACK)

Вызов glFrontFace(GL_CW) изменит классификацию на обратную: перечисление по часовой даст переднюю грань, перечисление против часовой даст заднюю.

Независимо от того, в каком порядке были заданы исходные вершины, если после всех преобразований грань объёмного тела повёрнута к нам лицевой стороной — порядок обхода сохранится, а если её перекроют другие грани — порядок обхода сменится на противоположный.

Режим отсечения граней можно включить командой glEnable(GL_CULL_FACE) , после чего можно выбрать способ отсечения: убирать задние грани (GL_BACK), передние грани (GL_FRONT) или оба вида граней (GL_FRONT_AND_BACK).

Соберём всю инициализацию состояния OpenGL в метод OnWindowInit, который будет вызываться один раз поле инициализации окна

Чтобы метод OnWindowInit был вызван своевременно, его можно объявить виртуальным в классе CAbstractWindow и вызывать в методе Show:

Диагностика проблем

При выводе трёхмерных тел встречается ряд типовых ошибок. Если что-то не работает, пройдитесь по следующему чеклисту:

  • вы не забыли вызвать glBegin/glEnd до и после вызова glColor/glVertex?
  • тело не выпадает из порта просмотра из-за матрицы GL_PROJECTION?
  • тело не обрезается дальней и ближней плоскостями отсечения из-за матрицы GL_PROJECTION?
  • в массиве индексов нумерация вершин начинается с нуля?
  • включён тест глубины и режим отсечения задних граней?
  • в массиве индексов вершины примитивов (треугольников и четырёхугольников) перечислены по часовой стрелке для внешнего наблюдателя?

Если всё нормально, то вы получите статичное изображение куба без возможности поменять положение камеры:

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

Трёхмерная система координат

В OpenGL используется правосторонняя система координат, в которых пользователь может задавать вершины примитивов, из которых состоят трехмерные объекты. Правосторонней система координат называется потому, что ее можно образовать при помощи большого, указательного и среднего пальцев правой руки, задающих направления координатных осей X, Y и Z соответственно.

Система координат задаётся точкой отсчёта и координатными осями, которые, в свою очередь, задают направления и длины трёх единичных векторов (1, 0, 0), (0, 1, 0) и (0, 0, 1). Как точка отсчёта, так и координатные оси могут меняться при переходе из одной системы координат в другую.

Например, представьте себе систему координат комнаты, где в качестве центра взята точка в геометрическом центре пола, а ось z указывает вверх, и расстояния измеряются в метрах. Тогда точки головы человека в комнате всегда будут иметь координату z, большую нуля, обычно в диапазоне (1.6; 1.8) . Если же перейти в другую систему отсчёта, где центром служит точка в геометрическом центре потолка, то голова человека в комнате будет иметь отрицательную координату z.

В любой трёхмерной сцене есть система координат, которую можно считать мировой системой коодинат. Это очень удобно: любая точка или вектор в мировой системе координат представляется однозначно. Благодаря этому, если у нас есть несколько локальных систем координат (например: комната, салон автомобиля, камера), и мы знаем способ преобразования из любой системы в мировые координаты, то мы можем спокойно перейти, например, от системы координат комнаты к системе координат камеры:

О локальных системах координат можно сказать следующее:

  • для перехода к мировой системе координат всегда есть аффинное преобразование, состоящее из некоторого числа перемещений, вращений и масштабирований
  • если точка отсчёта в разных координатах разная, это можно представить перемещением (англ. translate)
  • если координатные оси направлены в другие стороны, это можно представить вращением (англ. rotate)
  • если единицы измерения разные, например, метры в одной системе и километры в другой, это можно представить масштабированием (англ. scale)

Самый удивительный факт: любое элементарное трёхмерное преобразование, а также их комбинацию можно представить в виде матрицы 4×4! Чтобы понять, как это происходит, разберёмся с однородным представлением точек и векторов.

Единый тип данных для точек и векторов

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

Если использовать процедурную парадигму программирования, получится нечто такое:

Объектно-ориентированный подход предлагает такой вариант:

Оба подхода одинаково плохи: во-первых, мы в любом случае дублируем код. Во-вторых, на видеокартах с современной архитектурой любое ветвление или условное выполнение оборачивается огромным падением производительности, и желательно его избегать; в данных двух реализациях мы не сможем избежать ветвлений при выполнении трансформаций точек и векторов прямо на видеокарте.

Проблема решается с помощью математического приёма — однородного представления точек и векторов.

Однородное представление точек и векторов.

Давайте будем считать, что трёхмерная точка (x, y, z) хранится как четырёхкомпонентный вектор (x, y, z, 1) . А вектор хранится как (x, y, z, 0) . Всего лишь одно флаговое значение в конце позволяет избежать любых ветвлений в алгоритмах трансформации векторов и точек. Это происходит благодаря свойствам алгебры матриц.

Как известно, можно умножить матрицу на матрицу при условии, что ширина одной матрицы равна высоте другой (иначе операция просто недопустима). Для получения элемента с позицией i,j в новой матрице достаточно взять i-ю строку левой матрицы и j-й столбец правой матрицы. Вот пример:

Как ни странно, умножение 4-х компонентного вектора на матрицу 4×4 тоже возможно! Для этого достаточно считать 4-х компонентный вектор матрицей 4×1. После умножения получится новый 4-х компонентный вектор.

Ещё более удивительно, что любую комбинацию трёхмерных перемещений, поворотов, вращений (и не только их!) можно представить как всего лишь одну матрицу 4×4, называемую матрицей трёхмерной трансформации. При этом умножение матрицы на трёхмерную точки или вектор, записанный в однородном представлении, даёт новую точку или вектор именно так, как этого требуют правила преобразования точек и векторов. Никаких ветвлений, и никакой магии!

Класс CAnimatedCube

Давайте запомним несколько простых правил. Некоторые из них даже будут доказаны чуть ниже.

  • умножение матрицы преобразования на вектор или точку в однородном представлении даёт преобразованный вектор или точку
  • можно легко составить базовую матрицу, представляющих одно элементарное аффинное преобразование
  • умножение матрицы A на матрицу B даёт новую матрицу, которая описывает новую трансформацию, созданную путём применения трансформации B, а затем A (именно в таком порядке)
  • умножение матриц не коммутативно: вы не можете заменить A*B на B*A
  • инвертирование матрицы (например, с помощью детерминанта матрицы) даёт матрицу обратной трансформации, которая вернёт точку или вектор в исходное состояние — в идеальном мире. В дискретном мире компьютеров обратное преобразование может быть чуть-чуть неточным из-за особенностей представления типов float и double.

Для демонстрации этих правил расширим класс CIdentityCube из предыдущего урока. Новый класс будет называться CAnimatedCube, и он будет демонстрировать работу трёх базовых аффинных трансформаций. Смена и продвижение анимации будет выполняться в методе Update, вызываемом периодически снаружи.

С учётом перечисленных выше правил, мы можем написать метод Draw для анимированного куба. При этом вместо glLoadMatixf следует применить glMultMatrixf, чтобы вместо замены уже существующей трансформации всего лишь модифицировать её. Если мы заменим матрицу GL_MODELVIEW, камера будет работать некорректно.

Функции для работы с аффинными трансформациями (и не только!) можно найти в GML:

Единичная матрица

Единичная матрица (матрица идентичности, англ. identity matrix) задает преобразование, при котором точки и векторы остаются без изменений, отображаясь сами в себя. Посмотрите сами на перемножение этой матрицы и точки/вектора:

Если мы не хотим возвращать из метода GetAnimationTransform() какую-либо преобразующую трансформацию, мы можем просто вернуть единичную матрицу. Именно такую матрицу создаёт конструктор по умолчанию класса glm::mat4. Теперь мы можем заложить каркас метода GetAnimationTransform:

Матрица перемещения

Матрица перемещения воздействует на точку, но вектор сохраняет неизменным. Действует она так:

В GLM есть функция glm::translate, умножающая переданную матрицу на матрицу перемещения. Чтобы анимировать куб, будем вычислять смещение по оси Ox в каждый момент времени. После этого получение матрицы перемещения будет очень простым:

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

В процессе анимации от 0% до 100% куб должен один или несколько раз прыгнуть в сторону и затем вернуться обратно. Для этого воспользуемся делением с остатком, а также формулой расчёта расстояния на основе начальной скорости и противоположно направленного ускорения. Можно написать так:

Матрица масштабирования

Матрица масштабирования воздействует как на точку, так и на вектор, изменяя соответственно удалённость точки от начала координат и длину вектора. Действует так:

В GLM есть функция glm::scale, умножающая переданную матрицу на матрицу масштабирования, имеющую потенциально разные коэффициенты масштабирования для трёх разных компонентов вектора.

Давайте используем эту функцию, чтобы реализовать пульсирование куба — сжатие от нормальных размеров к нулевым и обратно:

Матрица поворота

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

Перечислим три матрицы поворота на угол “a” вокруг трёх осей системы координат (посмотреть, что получается при перемножении, вы можете самостоятельно):

В GLM есть функция glm::rotate, умножающая переданную матрицу на матрицу поворота вокруг переданного произвольного вектора оси на переданный угол. Как уже было сказано ранее, следует настроить GLM так, чтобы углы выдавались в радианах — иначе вы будете получать предупреждения об использовании устаревшего API. Проверьте, что в stdafx.h перед включением GLM объявлен макрос GLM_FORCE_RADIANS:

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

Вот так это будет выглядеть к концу урока:

Как создать камеру

В OpenGL в режиме версии 1.x есть две трансформирующих вершины матрицы: GL_MODELVIEW и GL_PROJECTION. Матрица GL_MODELVIEW объединяет к себе как переход от локальной системы координат к мировой (Model), так и переход от мировых координат к системе коодинат камеры (View). Класс CCamera будет возвращать только одну компоненту GL_MODELVIEW: матрицу вида, созданную функцией glm::lookAt.

Правила движения камеры будут следующими:

  • камера всегда смотрит на точку (0, 0, 0), вращается вокруг неё, приближается к ней или отдаляется
  • для вращения камеры служат клавиши “Влево” и “Вправо” либо “A” и “D” на клавиатуре
  • для приближения и отдаления служат клавиши “Вперёд” и “Назад” либо “W” и “S” на клавиатуре
  • камера не может приближаться ближе чем на 1.5f и не может отдаляться дальше чем на 30.f
  • камера не должна двигаться рывками, и даже при неравных интервалах перерисовки кадра движение должно оставаться плавным, т.е. зависит от deltaTime между кадрами

С учётом сказанного, спроектируем следующий интерфейс класса:

Методы Update, OnKeyDown, OnKeyUp должны вызываться извне — например, из класса окна. При этом методы обработки событий возвращают true, если событие было обработано, чтобы класс окна мог не рассылать это событие далее другим объектам.

Внутри класс хранит угол поворота камеры, отдаление от центра мира и подмножество клавиш, которые сейчас нажаты. Хранение подмножества нажатых клавиш позволяет легко устранить ряд непростых случаев:

  • пользователь нажал “Влево”, затем “Вправо”, потом отпустил “Влево”; после этого камера должна вращаться вправо
  • пользователь нажал “Влево” и “Вперёд”; после этого камера должна вращаться влево и при этом приближаться
  • пользователь нажал “Вперёд” и “Назад”; при этом камера может не двигаться или двигаться в одном приоритетном направлении — оба варианта хороши

Чтобы отслеживать нажатие только нужных клавиш, создадим функцию-предикат ShouldTrackKeyPressed:

Также подключим заголовок с функциями вращения вектора, введём вспомогательные константы и функции, позволяющие получить скорость поворота и скорость приближения (возможно, нулевые или отрицательные) на основе информации о нажатых клавишах:

После этого с небольшим применением линейной алгебры мы можем реализовать методы класса CCamera:

Изменения в CWindow

Теперь класс CWindow должен хранить три объекта:

Конструктор CCamera требует два аргумента, их можно задать следующим образом:

CWindow::CWindow() : m_camera(CAMERA_INITIAL_ROTATION, CAMERA_INITIAL_DISTANCE)

В методе OnUpdateWindow мы должны вызывать метод Update у всех трёх объектов системы:

В методе Draw немного схитрим: применим вызов glTranslate (вместо нормальной работы с функциями GLM), чтобы развести два куба в стороны:

Метод SetupView станет проще, потому что мы можем не вычислять матрицу GL_MODELVIEW, а получить её начальное (для кадра) значение у камеры.

Наконец, следует перегрузить методы OnKeyDown/OnKeyUp класса CAbstractInputControlWindow в классе CWindow:

Конец!

Теперь вы можете взять полный пример (github.com) или посмотреть, каким будет результат запуска (в виде статичного скриншота):

Чтобы сделать результат более наглядным, была сделана серия скриншотов, которые затем были объединены в gif с помощью GIMP:

Источник

Оцените статью
Юридический портал
Adblock
detector