Войти
ПрограммированиеСтатьиГрафика

Работа с расширениями OpenGL с использованием NVIDIA OpenGL SDK 5.1. (Часть 5)

Автор:

Интеракторы библиотеки OpenGL Helper Library
Создание шаблона простейшей GLH-программы
Интерактор консоли

Интеракторы библиотеки OpenGL Helper Library

При разработке относительно простых демонстрационных программ программисту постоянно приходится программировать одни и те же операции: реакцию на изменение размеров окна, вращение объекта мышью, выход из программы при нажатии клавиши ESC и т.д. Наиболее распространенный подход к этой проблеме - создание универсального шаблона и разработка приложений на основе этого шаблона. (Этот подход использовался при написании примеров статей этого цикла.) Недостаток данного подхода - "разбухание" программного кода, т.к. в главный модуль приходится включать большое количество строк кода, выполняющего тривиальные задачи - инициализацию OpenGL, организацию простого интерфейса с пользователями т.д. Причём этот вспомогательный код, как правило, оказывается примешан с основным кодом программы, что затрудняет его анализ. Использование библиотеки GLUT лишь немного смягчает эту проблему. К счастью, NVIDIA OpenGL SDK содержит объектную надстройку над GLUT - OpenGL Helper (GLH), которая позволяет автоматизировать многие часто встречающиеся операции .

Одна из причин появления этой библиотеки связана с тем, что в GLUT нельзя назначить одному событию несколько обработчиков. В принципе, создатели GLH могли бы заставить пользователя запоминать адрес предыдущего обработчика сообщения и вызывать его в начале/конце пользовательского обработчика сообщения (иными словами, производить суперклассирование (superclassing)). Но это сильно усложнило бы разработку программ и увеличило бы риск возникновения ошибок. Вместо этого создатели GLH решили изменить механизм обработки сообщений GLUT.

В основе библиотеки GLH лежит набор интеракторов (interactor's). Интерактор - это  класс, производный от класса glut_interactor. Фактически интерактор является предопределенным обработчиком некоторой группы сообщений. Интеракторы, используемые программой, помещаются в коллекцию интеракторов:

std::list<glut_interactor *> interactors;

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

Рассмотрим структура класса glut_interactor:

class glut_interactor
{
public:
  glut_interactor() { enabled = true; }

  virtual void display() {} //Аналог обработчика glutDispayFunc.
  virtual void idle() {}    //Далее - по аналогии
  virtual void keyboard(unsigned char key, int x, int y) {}
  virtual void menu_status(int status, int x, int y) {}
  virtual void motion(int x, int y) {}
  virtual void mouse(int button, int state, int x, int y) {}
  virtual void passive_motion(int x, int y) {}
  virtual void reshape(int w, int h) {}
  virtual void special(int  key, int x, int y) {}
  virtual void timer(int value) {}
  virtual void visibility(int v) {}

  virtual void enable()  { enabled = true; }
  virtual void disable() { enabled = false; }

  bool enabled;
};

Основу класса составляют 11 виртуальных функций-обработчиков событий GLUT. Свойство enabled указывает диспетчеру событий GLH, является ли интерактор в данный момент активным (true) или нет (false). В последнем случае интерактор игнорируется.

Читатель, наверное, уже заметил, что интерактор glut_interactor фактически представляет собой каркас программы, написанной на GLUT. Это позволят создавать классы, производные от glut_interactor, которые представляют собой законченную программу на GLUT.

В составе GLH имеется несколько типовых интеракторов, решающих распространенные задачи - создание матрицы проекции, ее коррекция при изменении размеров окна (glut_perspective_reshaper), перемещение по сцене при помощи мыши (glut_simple_mouse_interactor) и т.д. Это приводит к тому, что программа строится как бы из готовых "кирпичиков": добавили в коллекцию итератор glut_perspective_reshaper - у нас появилась камера, добавили еще и glut_perspective_reshaper - теперь сцену можно вращать, приближать и удалять при помощи мыши.

Пользовательские обработчики событий тоже представляют собой интеракторы. Но создавать класс, производный от glut_interactor, довольно утомительно, поэтому в составе GLH имеется интерактор glut_callbacks (для экономии места приведено лишь объявление полей/методов, которые касаются событий display и idle):

class glut_callbacks : public glut_interactor
{
public:
  void (* display_function) ();
  void (* idle_function) ();
//--------------------------

  glut_callbacks() :
    display_function(0),
    idle_function(0),
//--------------------------
  {}

  virtual void display()
  { if(display_function) display_function(); }

  virtual void idle()
  { if(idle_function) idle_function(); }
//--------------------------
}

Как видно, данный класс имеет набор полей с названием вида xxxx_function, которым можно присваивать адреса обработчиков событий GLUT. Этот класс позволяет быстро добавлять в готовые GLUT-программы поддержку интеракторов. Для этого необходимо выполнить следующие действия:
1.  Подключить заголовочные файлы GLH и добавить в коллекцию interaсtors необходимые интеракторы.
2.  Удалить из GLUT-программы весь код, который дублирует функции интеракторов.
3.  Добавить в программу интерактор glut_callbacks и связать его с обработчиками сообщений, которые остались от старой GLUT-программы.

Лучший способ разобраться с интеракторами - начать их использовать. Поэтому, рассмотрим небольшой пример (Ex01). Пусть нам нужно вывести на экран трехмерный объект (чайник) и дать пользователю возможность рассматривать его со всех сторон, приближать/удалять и т.д. Фактически, мы собираемся написать аналог программы Ex03 из статьи "GLUT - расширение OpenGL", но с использованием библиотеки GLH.

Для начала следует подключить библиотеку GLH, сделать текущим пространство имен glh и вставить в функцию main() вызов функции glut_helpers_initialize(), которая выполняет инициализацию библиотеки GLH:

#define GLH_EXT_SINGLE_FILE
#include <glh/glh_nveb.h> 
#include <glh/glh_extensions.h>
#include <glh/glh_obs.h>
#include <glh/glh_glut.h>

using namespace glh;

//-----------------
int main(int argc, char* argv[])
{
  glut_helpers_initialize();
  //------------
}

Теперь создадим интеракторы cb, mouse и reshaper.

glut_callbacks cb;
glut_simple_mouse_interactor mouse;
glut_perspective_reshaper reshaper(60, 0.1, 100);

Интерактор cb является пользовательским обработчиком событий Display (перерисовка окна) и Keyboard (нажатие клавиши на клавиатуре):

cb.keyboard_function=Keyboard;

cb.display_function=Display;

Сами обработчики можно взять из старой программы.

Интерактор reshaper предназначен для создания матрицы перспективной проекции и видового окна (Viewport) и их автоматической коррекции при изменении размеров окна. Параметры конструктора аналогичны параметрам команды gluProject (1-й, 3-й и 4-й параметры).

Интерактор mouse используется для перемещения по сцене с использованием мыши путем автоматической коррекции матрицы модели. Выбора типа интерфейса с пользователем осуществляется при помощи метода configure_buttons.

mouse.configure_buttons(1);

Единственный параметр метода задает тип интерфейса  (см. таблицу 1).

Значение, передаваемое
методу configure_buttons
 
Вращение объекта  Перемещение в
плоскости экрана
 
Перемещение
вдоль оси Z
Левая кнопка мыши  Shift + Левая кнопка мыши  Ctrl + Левая кнопка мыши
Левая кнопка мыши  Средняя кнопка мыши  Ctrl + Левая кнопка мыши
Левая кнопка мыши  Средняя кнопка мыши  Правая кнопка мыши

Таблица 1. Назначение кнопок мыши и клавиатуры интерактора glut_simple_mouse_interactor

Теперь следует настроить начальные параметры матрицы модели - сместить чайник вдоль оси z на -9 единиц. За смещения вдоль осей x, y и z отвечает подобъект dolly интерактора glut_simple_mouse_interactor, который содержит внутри себя массив смещений dolly. При создании объекта массив обнуляется, поэтому достаточно присвоить 3-му (с индексом 2) элементу массива значение -9:

mouse.dolly.dolly[2]=-9;

Интерактор glut_simple_mouse_interactor только рассчитывает матрицу сценарных преобразований. Для того чтобы применить ее к текущей сцене, необходимо в функции Dispay() умножать матрицу модели на матрицу glut_simple_mouse_interactor. Это можно сделать командой:

mouse.apply_transform();

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

glut_add_interactor(&mouse);
glut_add_interactor(&reshaper);
glut_add_interactor(&cb);

По умолчанию команда glut_add_interactor добавляет интерактор в конец списка интеракторов. Но,  передав значение false в качестве второго параметра, можно добавить интерактор в начало списка. Для удаления интерактора из списка интеракторов используется команда glut_remove_interactor.

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

Давайте немного изменим программу, сделав так, чтобы при повороте объекта мышью он продолжался вращать объект по инерции после отпускания кнопки мыши (пример Ex02). Для упрощения задачи предположим, что у пользователя включен режим вертикальной синхронизации . Из этого следует, что при достаточном быстродействии компьютера частота смены кадров (FPS) почти не меняется. Следовательно, мы можем просто запомнить угол последнего поворота объекта, (он будет примерно равен углу поворота между соседними кадрами) и вращать объект на этот угол в моменты бездействия программы (Idle), которые как раз возникают в паузах между кадрами. Кстати, этот подход используется в примерах NVIDIA OpenGL SDK.

Для начала создадим обработчик события Idle, свяжем его интерактором cb и включим генерацию события idle в момент простоя программы:

cb.idle_function=Idle;
//-----------
glut_idle(true);

Интерактор glut_simple_mouse_interactor имеет одну полезную особенность - он всегда запоминает угол и направление последнего поворота. Причем этот поворот можно повторить при помощи метода increment_rotation() подобъекта trackball():

void Idle()
{
  mouse.trackball.increment_rotation();
  glutPostRedisplay();
}

Мы рассмотрели основные вопросы программирования с использованием GLH, поэтому можно переходить к следующему разделу:

Страницы: 1 2 3 Следующая »

#расширения OpenGL, #NVIDIA, #OpenGL

28 марта 2003 (Обновление: 13 ноя. 2009)