BY-0

Введение

Когда-то давно я написал небольшой примерчик использования библиотеки GLUT, в котором демонстрировалось физически корректное столкновение двух твёрдых шариков. В феврале 2010г он был размещён на моей странице на codepad.org. Теперь же я решил сделать из него что-то более интересное и использовать в качестве учебного пособия.

Шарики

Изначально код рассчитан на N шариков. При этом почему-то было сделано так, что столкновения обрабатываются только между первыми двумя. Впрочем, это несложно исправить.

Вначале генерируется случайная «целевая» точка столкновения внутри окна (функция new_circles). N шариков со случайными массами и радиусами расставляются в случайных позициях со скоростями, направленными в точку столкновения, по величине равными четверти расстояния от центра шарика до точки столкновения. Таким образом, шарики должны столкнуться примерно через 4 секунды.

За рисование (рендеринг) отвечает функция repaint. Собственно же движения и столкновения шариков рассчитываются в функции frame_move. Время на момент расчёта последнего кадра хранится в глобальной переменной t. В случае, если на момент вызова frame_move() прошло больше 0 микросекунд, происходит изменение положения шариков соответственно их скоростям и прошедшему времени. Если центры шариков 0 и 1 находятся друг от друга не дальше суммы радиусов этих шариков, то регистрируется столкновение и скорости шариков меняются соответственно. Код под комментарием «improve distance» производит «разведение» шариков, чтобы они перестали касаться. Работа этого кода может приводить к «туннелированию» шариков, если результирующие скорости оказались близки к параллельным.

Функция handle_key реализует реакцию на нажатие пользователем клавиш, когда активно окно, где происходит рисование шариков (под MS Visual Studio данный файл следует компилировать как консольное приложение, соответственно будет два окна: консоль и окно с графикой, правда, чтобы его скомпилировать и успешно запустить, нужно раздобыть библиотеку GLUT в составе glut.h, glut32.lib и glut32.dll). Нажатие на enter или пробел создаёт новый набор шариков, нажатие на escape (код 27 в ASCII) или q приводит к выходу из программы.

Функция main производит инициализацию GLUT, состояния контекста OpenGL, создаёт аппроксимацию сферы (единичного радиуса, 12 вершин на «круг» по долготе, 4 таких круга по широте), регистрирует (передаёт библиотеке указатели на эти функции) функции-обработчики событий (callbacks; под комментарием «register callbacks» ). Вызов glutMainLoop() запускает цикл-обработчик событий, из которого, собственно, и вызываются зарегистрированные нами функции.

Окна

Зачем нам нужен GLUT? Что до OpenGL, то это низкоуровневый графический API, обращающийся непосредственно к драйверу GPU. В его «компетенции» не входит создание окон GUI или, тем более, организация взаимодействия с пользователем (реакция на события клавиатуры и мыши). Всё это, а также инициализация виртуального устройства OpenGL (создание контекста OpenGL) реализуется операционной системой. Если же хочется обеспечить независимость от ОС и/или простоту кода (что важно для маленьких приложений), имеет смысл воспользоваться сторонними библиотеками-обёртками, предоставляющими указанный функционал. Например, библиотекой GLUT.

Библиотека GLUT имеет давно устоявшийся (в настоящее время не развивается), достаточно простой в использовании (самый простой?) интерфейс на языке C (можно использовать в программах на разных языках программирования). Конечно, есть другие библиотеки, с помощью которых можно сделать то же самое и что-нибудь ещё, как маленькие и простые (например, SFML), так и огромные, предоставляющие широкий функционал для создания серьёзных GUI приложений (например, Qt).

Новый вариант

Итак, старый вариант был переправлен.

Для сравнения старого и нового вариантов удобно использовать какой-нибудь вариант утилиты diff (у Microsoft есть своя реализация — WinDiff, которая может присутствовать в составе Visual Studio/Windows SDK; мне же больше нравится WinMerge).

В качестве реализации GLUT было решено взять более современную (чем оригинальная) open-source библиотеку freeglut (тем более, что есть сборка под Visual C++). Вместо микросекундного времени boost::date_time, будем использовать обычный миллисекундный std::clock.

Макрос GLUTCALLBACK нам теперь без надобности (был на случай особого соглашения вызова для функций-обработчиков, например, __stdcall).

Посмотрим Ball::paint vs circle::paint. Стек матриц не используется, поэтому перед установкой матрицы для шара, просто заменяем текущую матрицу на единичную. Далее, было:

glScaled(r, r, r);
glTranslated(p.x / r, p.y / r, 0.0);

стало

glTranslated(p.x, p.y, 0.0);
glScaled(r, r, r);

Указанные функции домножают текущую матрицу на матрицу соответствующего преобразования слева. Поэтому (новый вариант), сначала промасштабируем единичную сферу, сделав из неё сферу радиуса r, потом переместим её из начала координат в позицию (p.x, p.y), а не наоборот, как было в старом варианте (поэтому там деление на r).

Появилась новая функция Ball::move, которая осуществляет расчёт сдвига шара в соответствии с заданным ускорением a. Ускорение считается постоянным в течении указанного промежутка времени time_step. Метод первого порядка, его точность низкая, но вполне соответствует обычной «игровой физике». Итак, сначала нужно записать в a сумму ускорений, порождаемых силами, действующими на шар, потом вызвать move.

Сила здесь будет одна — похожее на гравитацию (прямо пропорциональное массе шара и константе G, обратно пропорциональное квадрату расстояния) притяжение к центру экрана. Вычислением ускорения, порождаемого этой силой, занимается функция applyCentralForce.

Функция frame_move переименована в frameMove и теперь рассчитывает столкновения для всех пар шариков. Кроме того, вместо того, чтобы «разводить» шарики, «зашедшие» внутрь друг друга, фиксация столкновения производится только в случае сближения центров шариков. Шарики могут оказаться внутри друг друга (что особенно заметно из-за того, что при случайном выборе позиции факт «попадания» внутрь не проверяется), зато нет «туннелирования».

Добавлен обработчик события изменения размеров окна рендеринга reshape, именно там устанавливается матрица проекции, которая в старом варианте просто не использовалась. Текущие размеры окна хранятся в глобальных переменных width и height.

С фактом наличия глобальных переменных пока решено смириться.

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: