В последнее время тут стали появляться статьи, связанные с движком OGRE, в т.ч. и пост по созданию простейшей программы, поэтому я решил не откладывать дело в долгий ящик и написать эту статью. Когда-то не так давно я уже писал небольшой поверхностный обзор библиотеки SDL, и здесь, в качестве продолжения, приведу конкретный пример использования этой библиотеки, а именно — как заставить её работать вместе с движком.
Для начала отвечу на вопрос — зачем это? OGRE и сам работает на большинстве популярных платформ, умеет создавать окошко для визуализации, а для обработки ввода пользователя многие уроки на оф. сайте движка рекомендуют использовать библиотеку OIS. Это действительно так, и раньше я использовал движок именно в связке с этой библиотекой, пока не пришлось писать под Linux. Тогда и возникли неприятные моменты. Окошко OGRE по непонятным мне причинам нередко отказывалось реагировать на перетаскивание мышью, часто изображение в нём мерцало (было подозрение на конфликт с Compiz или Emerald); ввод с помощью OIS не мог похвастаться приличной скоростью, и после орудования мышкой в течение нескольких секунд программу хотелось поскорее закрыть. Тогда я решил попробовать SDL в качестве альтернативы OIS. Библиотека справилась весьма успешно с возложенными на неё обязанностями — созданием окна и обработкой ввода, при этом показав приличную скорость работы.
Итак, нам понадобятся OGRE SDK и SDL SDK. Я не буду описывать процесс их установки, потому что в разных системах он разный, да и несложных уроков по этой теме немало. Перейдём сразу к делу. Первая задача — написать код программы, в которой созданием окна и обработкой ввода будет заниматься SDL, а OGRE будет лишь производить рендеринг в чужое для него окно. Постараемся сделать наш код переносимым хотя бы между Windows и Linux.
Начнём с того, что инициализируем SDL и создадим с её помощью окно:
Теперь надо настроить OGRE, установить для него визуализатор. Как закодить эту часть программы — сугубо индивидуальное предпочтение каждого. Здесь я оставил за собой право вручную загрузить лишь самые необходимые плагины и установить нужные параметры. Главное — не создавать окно средствами OGRE:
Самое интересное — говорим OGRE, что контекст OpenGL уже получен, и в него надо производить визуализацию:
Пора заканчивать с инициализацией и переходить к рабочей части:
При завершении программы не забываем убрать за собой:
Код получился достаточно коротким, половину места занимают комментарии. После компиляции и запуска программы появляется окошко с непонятным содержимым:

Это ожидаемый результат, потому что на данном этапе не создано ни одного viewport'а и ни одной камеры, из которой OGRE мог бы производить визуализацию. Поэтому всё, что отображается в окне — произвольные данные, находящиеся в видеопамяти.
Теперь было бы неплохо сделать небольшую удобную систему отлова событий и отсылки их слушателям. Сначала опишем интерфейсы слушателей:
Определив интерфейсы слушателей, можно написать и сам класс менеджера ввода:
Теперь перейдём к реализации класса. Основной интерес представляет метод capture():
Этот метод извлекает события из очереди и рассылает их нужным слушателям. Код легко адаптировать для прослушивания разных конкретных типов событий. На самом деле, узкоспециализированных слушателей можно создать довольно много, в данном случае я привёл лишь примеры слушателей мыши и клавиатуры.
Осталось совсем немного — реализовать методы регистрации слушателей. Тут всё просто, поэтому я приведу код реализации только для методов регистрации слушателей клавиатуры, остальные реализуются по аналогии:
Теперь у нас есть приличный менеджер ввода, не идеальный конечно, но дающий простор для фантазии в плане дальнейшего усовершенствования. С использованием менеджера ввода основной цикл программы можно переписать:
Для обработки событий нужно будет создать классы, реализующие соответствующие интерфейсы. Как минимум стоит отлавливать событие SDL_QUIT, чтобы корректно завершать программу :)
Надо сказать, что использование SDL я вовсе не считаю необходимым, но библиотека, тем не менее, является весьма приятной для использования. API, хотя и полностью написанный на Си, не уступает по удобству объектно-ориентированному интерфейсу OIS. Привязка к OGRE также не вносит каких-то особых неудобств. OGRE, в свою очередь, показал ещё раз свою работоспособность в условиях экстремальной эксплуатации :)
Интро
Для начала отвечу на вопрос — зачем это? OGRE и сам работает на большинстве популярных платформ, умеет создавать окошко для визуализации, а для обработки ввода пользователя многие уроки на оф. сайте движка рекомендуют использовать библиотеку OIS. Это действительно так, и раньше я использовал движок именно в связке с этой библиотекой, пока не пришлось писать под Linux. Тогда и возникли неприятные моменты. Окошко OGRE по непонятным мне причинам нередко отказывалось реагировать на перетаскивание мышью, часто изображение в нём мерцало (было подозрение на конфликт с Compiz или Emerald); ввод с помощью OIS не мог похвастаться приличной скоростью, и после орудования мышкой в течение нескольких секунд программу хотелось поскорее закрыть. Тогда я решил попробовать SDL в качестве альтернативы OIS. Библиотека справилась весьма успешно с возложенными на неё обязанностями — созданием окна и обработкой ввода, при этом показав приличную скорость работы.
Каркас программы
Итак, нам понадобятся OGRE SDK и SDL SDK. Я не буду описывать процесс их установки, потому что в разных системах он разный, да и несложных уроков по этой теме немало. Перейдём сразу к делу. Первая задача — написать код программы, в которой созданием окна и обработкой ввода будет заниматься SDL, а OGRE будет лишь производить рендеринг в чужое для него окно. Постараемся сделать наш код переносимым хотя бы между Windows и Linux.
Начнём с того, что инициализируем SDL и создадим с её помощью окно:
- #include <Ogre.h>
- #include <SDL.h>
- #ifdef _WINDOWS
- #include <SDL_syswm.h>
- #endif
-
- ...
-
- // Инициализация видео подсистемы SDL
- SDL_Init(SDL_INIT_VIDEO);
- // Центрировать создаваемое окно на экране
- SDL_putenv((char*)"SDL_VIDEO_CENTERED=true");
- // Установка видеорежима и одновременное создание окошка 800x600@16 с получением контекста OpenGL
- SDL_SetVideoMode(800, 600, 16, SDL_OPENGL);
- // Не оставляем окно безымянным
- SDL_WM_SetCaption("Demo window", NULL);
* This source code was highlighted with Source Code Highlighter.
Теперь надо настроить OGRE, установить для него визуализатор. Как закодить эту часть программы — сугубо индивидуальное предпочтение каждого. Здесь я оставил за собой право вручную загрузить лишь самые необходимые плагины и установить нужные параметры. Главное — не создавать окно средствами OGRE:
- // Начинаем инициализацию OGRE с создания объекта Root
- Ogre::Root *root = new Ogre::Root();
- // Загружаем плагин-визуализатор OpenGL из текущей папки (или из другого места)
- #ifdef _WINDOWS
- root->loadPlugin("./RenderSystem_GL.dll");
- #else
- root->loadPlugin("./RenderSystem_GL.so");
- #endif
- // Создаём сам визуализатор
- Ogre::RenderSystem *rs = root->getRenderSystemByName("OpenGL Rendering Subsystem");
- // ... делаем его текущим
- root->setRenderSystem(rs);
- // ... и настраиваем видео-режим
- rs->setConfigOption("Full Screen", "no");
- rs->setConfigOption("Video Mode", "800 x 600 16-bit colour");
- // Инициализируем OGRE, но без автоматического создания окна
- root->initialise(false);
* This source code was highlighted with Source Code Highlighter.
Самое интересное — говорим OGRE, что контекст OpenGL уже получен, и в него надо производить визуализацию:
- // Устанавливаем параметры создания окна OGRE.
- // Фактически никакого окна не создаётся. Мы просто указываем OGRE, куда
- // ему производить визуализацию
- Ogre::NameValuePairList params;
- #ifdef _WINDOWS
- SDL_SysWMinfo info;
- SDL_VERSION(&info.version);
- SDL_GetWMInfo(&info);
- params["externalWindowHandle"] = Ogre::StringConverter::toString(reinterpret_cast<size_t>(info.window));
- params["externalGLContent"] = Ogre::StringConverter::toString(reinterpret_cast<size_t>(info.hglrc));
- params["externalGLControl"] = Ogre::String("True");
- #else
- // Под Linux чуть проще
- params["currentGLContext"] = Ogre::String("True");
- #endif
-
- // Создание "виртуального" окна с выбранными параметрами
- Ogre::RenderWindow *wnd = root->createRenderWindow("Main window", 800, 600, false, ¶ms);
- wnd->setVisible(true);
* This source code was highlighted with Source Code Highlighter.
Пора заканчивать с инициализацией и переходить к рабочей части:
- // Сюда будем складывать очередное событие для обработки
- SDL_Event evt;
- // Да, цикл должен продолжаться
- bool running = true;
- // Основной цикл визуализации
- while (running)
- {
- // Читаем поступившие со времени предыдущего кадра системные события и события ввода
- while (SDL_PollEvent(&evt))
- {
- // Если юзер закрыл окно, прекращаем цикл
- if (evt.type == SDL_QUIT)
- running = false;
- }
- // Отрисовываем один кадр
- root->renderOneFrame();
-
- // Почему-то необходимость вызывать SDL_GL_SwapBuffers(); есть только в Windows.
- // В Linux прекрасно работает без этого и глючит, если это использовать.
- #ifdef _WINDOWS
- SDL_GL_SwapBuffers();
- #endif
- }
* This source code was highlighted with Source Code Highlighter.
При завершении программы не забываем убрать за собой:
- // Удаляем главный объект OGRE, освобождая тем самым все используемые движком ресурсы
- delete root;
- // Закрываем SDL
- SDL_Quit();
* This source code was highlighted with Source Code Highlighter.
Код получился достаточно коротким, половину места занимают комментарии. После компиляции и запуска программы появляется окошко с непонятным содержимым:

Это ожидаемый результат, потому что на данном этапе не создано ни одного viewport'а и ни одной камеры, из которой OGRE мог бы производить визуализацию. Поэтому всё, что отображается в окне — произвольные данные, находящиеся в видеопамяти.
Менеджер ввода
Теперь было бы неплохо сделать небольшую удобную систему отлова событий и отсылки их слушателям. Сначала опишем интерфейсы слушателей:
- // Input.h
-
- #include <list>
- #include <SDL.h>
-
- /// Интерфейс слушателей событий клавиатуры
- class KeyboardListener
- {
- public:
- virtual ~KeyboardListener() {}
- virtual void onKeyboardEvent(const SDL_KeyboardEvent &evt) = 0;
- };
-
- /// Интерфейс слушателей событий мыши
- class MouseListener
- {
- public:
- virtual ~MouseListener() {}
- virtual void onMouseMotion(const SDL_MouseMotionEvent &evt) = 0;
- virtual void onMouseBtn(const SDL_MouseButtonEvent &evt) = 0;
- };
-
- /// Интерфейс слушателей всех остальных типов событий
- class EventListener
- {
- public:
- virtual ~EventListener() {}
- virtual void onEvent(const SDL_Event &evt) = 0;
- };
* This source code was highlighted with Source Code Highlighter.
Определив интерфейсы слушателей, можно написать и сам класс менеджера ввода:
- class InputManager
- {
- private:
- typedef std::list<KeyboardListener*> KeyboardListenersList;
- typedef std::list<MouseListener*> MouseListenersList;
- typedef std::list<EventListener*> OtherEventsListenersList;
-
- private:
- KeyboardListenersList kb_listeners;
- MouseListenersList mouse_listeners;
- OtherEventsListenersList other_listeners;
-
- public:
- InputManager() {}
- ~InputManager() {}
-
- /// Захватывает и обрабатывает события
- void capture();
-
- /// Регистрирует слушателя событий клавиатуры
- void regKeyboardListener(KeyboardListener *l);
- /// Снимает регистрацию слушателя событий клавиатуры
- void unregKeyboardListener(KeyboardListener *l);
-
- /// Регистрирует слушателя событий мыши
- void regMouseListener(MouseListener *l);
- /// Снимает регистрацию слушателя событий мыши
- void unregMouseListener(MouseListener *l);
-
- /// Регистрирует слушателя событий общего типа
- void regEventListener(EventListener *l);
- /// Снимает регистрацию слушателя событий произвольного типа
- void unregEventListener(EventListener *l);
- };
* This source code was highlighted with Source Code Highlighter.
Теперь перейдём к реализации класса. Основной интерес представляет метод capture():
- SDL_Event evt;
- while (SDL_PollEvent(&evt))
- {
- switch (evt.type)
- {
- // Событие клавиатуры
- case SDL_KEYUP: case SDL_KEYDOWN:
- {
- KeyboardListenersList::const_iterator kb_it = kb_listeners.begin(), kb_end = kb_listeners.end();
- for (; kb_it != kb_end; ++kb_it)
- (*kb_it)->onKeyboardEvent(evt.key);
- break;
- }
- // Событие мыши
- case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: case SDL_MOUSEMOTION:
- {
- MouseListenersList::const_iterator m_it = mouse_listeners.begin(), m_end = mouse_listeners.end();
- for (; m_it != m_end; ++m_it)
- {
- if (evt.type == SDL_MOUSEBUTTONDOWN || evt.type == SDL_MOUSEBUTTONUP)
- (*m_it)->onMouseBtn(evt.button);
- if (evt.type == SDL_MOUSEMOTION)
- (*m_it)->onMouseMotion(evt.motion);
- }
- break;
- }
- // Все остальные события
- default:
- OtherEventsListenersList::const_iterator other_it = other_listeners.begin(), other_end = other_listeners.end();
- for (; other_it != other_end; ++other_it)
- (*other_it)->onEvent(evt);
- }
- }
* This source code was highlighted with Source Code Highlighter.
Этот метод извлекает события из очереди и рассылает их нужным слушателям. Код легко адаптировать для прослушивания разных конкретных типов событий. На самом деле, узкоспециализированных слушателей можно создать довольно много, в данном случае я привёл лишь примеры слушателей мыши и клавиатуры.
Осталось совсем немного — реализовать методы регистрации слушателей. Тут всё просто, поэтому я приведу код реализации только для методов регистрации слушателей клавиатуры, остальные реализуются по аналогии:
- void InputManager::regKeyboardListener(KeyboardListener *l)
- {
- if (l && std::find(kb_listeners.begin(), kb_listeners.end(), l) == kb_listeners.end())
- kb_listeners.push_back(l);
- }
-
- void InputManager::unregKeyboardListener(KeyboardListener *l)
- {
- if (l)
- kb_listeners.remove(l);
- }
* This source code was highlighted with Source Code Highlighter.
Теперь у нас есть приличный менеджер ввода, не идеальный конечно, но дающий простор для фантазии в плане дальнейшего усовершенствования. С использованием менеджера ввода основной цикл программы можно переписать:
- InputManager *imgr = new InputManager();
- while (!stop)
- {
- imgr->capture();
- root->renderOneFrame();
- ...
- }
- delete imgr;
* This source code was highlighted with Source Code Highlighter.
Для обработки событий нужно будет создать классы, реализующие соответствующие интерфейсы. Как минимум стоит отлавливать событие SDL_QUIT, чтобы корректно завершать программу :)
Напоследок
Надо сказать, что использование SDL я вовсе не считаю необходимым, но библиотека, тем не менее, является весьма приятной для использования. API, хотя и полностью написанный на Си, не уступает по удобству объектно-ориентированному интерфейсу OIS. Привязка к OGRE также не вносит каких-то особых неудобств. OGRE, в свою очередь, показал ещё раз свою работоспособность в условиях экстремальной эксплуатации :)