Загрузка...
 
Печать

Библиотека NVIDIA OpenGL Helper Library (GLH)


Продолжение. Начало здесь: OpenGL - Библиотека GLUT

Библиотека GLUT разрабатывалась в суровые 90-е для языка C и не использует объектно-ориентированных фишек C++.1 Поэтому компания NVIDIA создала свою собственную объектно-ориентированную надстройку над OpenGL и GLUT, названную OpenGL Helper Library (GLH). В лучших традициях Open source ПО GLH распространяется в виде исходного кода. Поэтому её заголовочные файлы находятся в отдельном подкаталоге NVSDK\OpenGL\include\glh\ установленного NVIDIA SDK 5 и подключаются к Проекту обычной директивой #include.
О том, где взять и как установить NVIDIA SDK 5 читай здесь: OpenGL Уч.курс
Все классы GLH расположены в пространстве имён glh. Поэтому в начале листинга любого GLH-приложения обязательно добавляем директиву
using namespace glh

Классы GLH можно условно разделить на 3 группы:
  • Математические функции
  • Объектно-ориентированная надстройка над GLUT, основанная на интеракторах
  • Классы-обёртки, инкапсулирующие функции OpenGL



Математические функции (glh_linear.h)

  • На функциях данного мат. заголовка основаны почти все примеры NVIDIA SDK.
  • Подключается в Проект путём добавления директивы
    #include <glh_linear.h>
    и активизации пространства имён
    using namespace glh;

Классы для работы с векторами

Общим предком всех классов для работы с векторами является шаблонный класс (template class) vec:
template <int N, class T> class vec
{
	...
}

, где N - размерность векторов, T - тип его компонентов.
На основе этого шаблона в GLH определены 3 класса:
  • class vec2 : public vec<2, real>;
  • class vec3 : public vec<3, real>;
  • class vec4 : public vec<4, real>;
При этом тип real определён так:
#define GLH_REAL float
typedef GLH_REAL real;

...и является аналогом float.
Единственное различие между классами vec2, vec3, vec4 - в числе параметров конструктора.
У каждого из класов есть несколько коснтуркторов. У класса vec4 их аж 5 штук:
// Создаёт экземпяр класса на основе ссылки на массив чисел
vec4 (const real & t = real()) : vec<4,real>(t) {}
// Создаёт экземпяр класса на основе указателя на массив чисел
vec4 (const real & tp) : vec<4,real>(tp) {}
// Создаёт экземпяр класса на основе другого объекта
vec4 (const vec<4,real> & t) : vec<4,real>(t) {}
// Создаёт экземпяр класса на основе трёхмерного вектора и четвёртого компонента
vec4 (const vec<3,real> & t, real fourth)
{
	v[0] = t.v[0]; v[1] = t.v[1]; v[2] = t.v[2]; v[3] = fourth;
}
// Создаёт четырёхмерный вектор с заданными компонентами
vec4 (real x, real y, real z, real w)
{ v[0]=x; v[1]=y; v[2]=z; v[3]=w;}

Первые 3 конструктора используют конструкторы базового класса vec. Вот примеры создания векторов с помощью каждого из этих 5 конструкторов:
float values[4]={1,2,3,4};
float* ptr=&values[0];
vec3f v3dim(1,2,3);

vec4f v1(&values[0]);
vec4f v2(ptr);
vec4f v3(v2);
vec4f v4(v3dim, 4);
vec4f v5(1,2,3,4);

Шаблонный класс vec содержит ряд полезных методов:
Метод Описание
int size() const Возвращает размерность вектора.
T dot(const vec & rhs) const Вычисляет скалярное произведение векторов.
T lenght() const Вычисляет модуль длины вектора.
T square_norm() const Вычисляет квадрат модуля вектора.
void negate() Поворачивает вектор в противоположном направлении.
T normalize() Нормализует вектор.

Кроме того у каждого из потомков класса vec есть свои доп. методы. Например в классе vec3 есть метод cross, предназначенный для вычисления векторного произведения.
Класс vec4 перегружает практически все математические операторы C++, благодаря чему мы можем работать с векторами как с обычными числами.

Класс line


Работа с матрицами. Классы matrix4 и matrix4f

В библиотеке GLH_LINEAR есть всего 2 класса для работы с матрицами 4X4:
  • matrix4 (объявлен в пространстве имён GLH_REAL_NAMESPACE)
  • matrix4f (объявлен в пространстве имён GLH).
Класс matrix4 имеет 4 конструктора:
// Создаёт единичную матрицу
matrix4() {makeidentity();}
// Делает все элементы матрицы равными r
matrix4(real r) {set_value(r);}
// Берёт все элементы матрицы из одномерного массива на 16 элементов
matrix4(real*m) {set_value(m);}
// Создаёт матрицу и присваивает её элементам соответствующие значения
matrix4 (real a00, real a01, real a02, real a03,
		real a10, real a11, real a12, real a13,
		real a20, real a21, real a22, real a23,
		real a30, real a31, real a32, real a33)
{
	element(0,0) = a00;
	element(0,1) = a01;
	element(0,2) = a02;
	element(0,3) = a03;

	element(1,0) = a10;
	element(1,1) = a11;
	element(1,2) = a12;
	element(1,3) = a13;

	element(2,0) = a20;
	element(2,1) = a21;
	element(2,2) = a22;
	element(2,3) = a23;

	element(3,0) = a30;
	element(3,1) = a31;
	element(3,2) = a32;
	element(3,3) = a33;
}

Рассмотрим пример, показывающий использование всех четырёх конструкторов на практике:
float modelview[16]
// Получаем матрицу модели в одномерный массив
glGetFloatv(GL_MODELVIEW_MATRIX, &modelview[16]);

// Создаёт единичную матрицу
matrix4f m1;
// Создаёт матрицу с элементами равными 5
matrix4f m2(5);
// Создаёт матрицу, равную матрице модели
matrix4f m3(&modelview[0]);
// Создаёт матрицу
/*
	1 2 3 4
	5 6 7 8
	9 0 1 2
	3 4 5 6
*/
matrix4f m4(1, 2, 3, 4,
			5, 6, 7, 8,
			9, 0, 1, 2,
			3, 4, 5, 6);

Для доступа к элементам матрицы используется перегруженный оператор (), вызывающий метод elements:
real & operator () (int row, int col) {return element(row,col);}

Данный оператор даёт возможность работать с матрицей как с обычным массивом, где, напомним, нумерация строк и столбов начинается с нуля.
Функция get_value позволяет получить все значения матрицы в виде одномерного массива. Вот пример её применения:
void get_value(real * mp) const
{
	int c=0;
	for(int j=0; j<4; j++)
		for(int i=0; i<4; i++)
			mp[c++] = element(i,j);
}

Метод get_value самостоятельно не выделяет память для хранения элементов массива. Программер должен об этом позаботиться заранее.
Команда set_value присваивает значение матрице, хранящейся в одномерном массиве:
void set_value(real * mp)


Работа с кватернионами


Библиотека GLH_GLUT (glh_glut.h). Интеракторы

Заголовок glh_glut.h расположен по пути NVSDK\OpenGL\include\glh\.
  • Является объектной надстройкой над GLUT.
  • Решает проблему невозможности средствами GLUT назначить одному событию несколько обработчиков.
  • В её основе лежат т.н. интеракторы (interactors).
Интерактор - это класс, производный от класса glut_interactor. Он является переопределённым обработчиком некоей группы сообщений.
Интеракторы, используемые в OpenGL-приложении помещаются в коллекцию интеракторов:
std::list<glut_interactor *> interactors;

При наступлении события диспетчер сообщений GLUT просматривает список интеракторов и вызывает метод-обработчик, назначенный каждому из них:
void glut_display_function()
{
	propagate = true;
// Перебираем все интеракторы из списка, пока не достигнем конца
// или один из интеракторов не сделает переменную propagate равной false
for(std::list<glut_interactor *>::iterator
it=interactors.begin(); it != interactors.end() && propagate; it++)
// Вызываем метод display текущего интерактора
	(*it)->display();
}

Для того чтобы библиотека GLH назначила свои стандартные обработчики в качестве обработчиков событий GLUT, программа сперва должна вызвать функцию glut_helpers_initialize():
inline void glut_helpers_initialize()
{
	glutDisplayFunc(glut_display_function);
	glutIdleFunc(0);
	glutKeyboardFunc(glut_keyboard_function);
	glutMenuStatusFunc(glut_menu_status_function);
	glutMotionFunc(glut_motion_function);
	glutMouseFunc(glut_mouse_function);
	glutPassiveMotionFunc(glut_passive_motion_function);
	glutReshapeFunc(glut_reshape_function);
	glutSpecialFunc(glut_special_function);
	glutVisibilityFunc(glut_visibility_function);
}

Вызов самой функции glut_helpers_initialize() размещается внутри функции main() после операторов создания окна.

Класс glut_interactor

Рассмотрим структуру класса glut_interactor:
class glut_interactor
{
public:
	glut_interactor() {enabled = true;}

virtual	void	display() {} // Аналог обработчика glutDisplayFunc.
virtual	void	idle() {}	// Аналог обработчика glutIdleFunc().
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, является ли интерактор в данный момент активным (enabled = true) или неактивным (enabled = false). Если интерактор неактивен, то он игнорируется.
В составе GLH имеется несколько типовых интеракторов, решающих распространённые задачи:
  • создание матрицы проекции
  • её коррекция при изменении размеров окна (glut_perspective_reshaper)
  • Перемещение по сцене при помощи мыши (glut_simple_mouse_interactor) и др.
Это делает программу модульной: нужна камера - добавяем в коллекцию glut_perspective_reshaper, нужно проделывать манипуляции со сценой мышью - добавляем glut_simple_mouse_interactor.
GLH также поддерживает пользовательские обработчики событий, которые тоже являются интеракторами. В теории для этого надо создать класс производный от класса glut_interactor. Но на деле применяют более простой способ с использованием интерактора glut_callbacks. Вот пример его применения:
class glut_callbacks : public glut_interactor
{
pulic:
	glut_callbacks():
		display_function(0),
		idle_function(0)
//...
		visibility_function()	// Обязательно присутствует в конце этого списка колбэков (функций обратного вызова)
{}

// На каждый колбэк пишем объявление функции
virtual void display()
{if(display_function) display_function();}

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

//...

virtual void visibility(int v)
{if(visibility_function) visibility_function(v);}

// Указатели на пользовательские обработчики
void (* display_function) ();
void (* idle_function) ();
//...
void (* visibility_function) (int);

};

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

Заточим Проект GLUTTest01 под интеракторы

  • Стартуй MSVC++2010, если не сделал этого раньше.
  • Открой Проект GLUTTest01, созданный в предыдущей главе OpenGL - Библиотека GLUT.
  • Замени весь исходный код в Main.cpp на следующий:
Main.cpp
// Copyright by Gaidukov Serg Programming Inc. 1995-2002

#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#include <glh/glh_glut.h>

using namespace std;
using namespace glh;

int WinWidth=640;		// Ширина окна
int WinHeight=480;		// Высота окна

GLfloat  rx=0;			// Угол поворта сцены вокруг оси X
GLfloat  ry=0;			// Y
GLfloat  tx=0;			// Сдвиг по оси X
GLfloat	 ty=0;			// Y
GLfloat	 tz=-9;			// Z
GLint	 tt=0;			// Активная плоскось: 0 - XY, 1 - XZ

const string tstr[2]={"Translate XY", "Translate XZ"};

int mx,my;				// Координаты мыши
bool ldown=false;		// Нажата левая клавиша мыши?
bool rdown=false;		// Нажата правая клавиша мыши?

GLuint list0=0;

glut_callbacks cb;

void Init()				// Инициализация OpenGL
{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_COLOR_MATERIAL);

	glColor3f(0.1,0.7,0.2);
	glClearColor(0.5, 0.5, 0.75, 1);
	
	list0=glGenLists(1);
	
	glNewList(list0, GL_COMPILE);			//Создание дисплейного списка объекта (чайника)
		glutSolidTeapot(2);
	glEndList();
}

void Display()			// Обновление содержимого экрана
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glPushMatrix();
		glTranslatef(tx,ty,tz);		//Перемещение и поворт объекта
		glRotatef(rx,1,0,0);
		glRotatef(ry,0,1,0);
		
		glCallList(list0);			//Вывод объекта на экран
	glPopMatrix();

	glutSwapBuffers();
}

void Keyboard(unsigned char key,int x,int y)			//Обработка сообщений от клавиатуры
{
	switch (key)
	{
		case VK_ESCAPE:		//Если нажата клавиша ESC - выход
			if (list0)
				 glDeleteLists(list0,1);	//Удалить дисплейный список
			exit(0);
			break;
	}
}

void KeyboardSpecial(int key, int x, int y)
{
	switch (key)
	{
		case GLUT_KEY_F1:	//Если нажата клавиша F1
		{
			tt=(tt+1)%2;	//Смена плоскости перемещения
			glutSetWindowTitle(tstr[tt].c_str());	//Изменение заголовка
		}

	}
}

void Reshape(int Width,int Height)		//Обработка изменения размеров окна
{
	glViewport(0,0,Width,Height);
	WinWidth=Width;					//Запомнить новые размеры
	WinHeight=Height;
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45,GLdouble(WinWidth)/WinHeight,1,100);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	glutPostRedisplay();
}

void Mouse(int button, int state, int x, int y)		//Обработка щелчков мыши
{
	if (button==GLUT_LEFT_BUTTON)		//Левая кнопка
	{
		switch (state)
		{
			case GLUT_DOWN:		//Если нажата
				ldown=true;		//установить флаг
				mx=x;			//Запомнить координаты
				my=y;
				break;
			case GLUT_UP:
				ldown=false;
				break;
		}
	}
	if (button==GLUT_RIGHT_BUTTON)	//Правая кнопка
	{
		switch (state)
		{
			case GLUT_DOWN:
				rdown=true;
				mx=x;
				my=y;
				break;
			case GLUT_UP:
				rdown=false;
				break;
		}
	}
}

void MouseMotion(int x, int y)	//Перемешение мыши
{
	if (ldown)		// Левая кнопка
	{
		rx+=0.5*(y-my);	//Изменение угола поворота
		ry+=0.5*(x-mx);
		mx=x;
		my=y;
		glutPostRedisplay();	//Перерисовать экран
	}
	if (rdown)	//Правая
	{
		tx+=0.01*(x-mx);	//Перемещение вдоль активной плоскости
		if (tt)
			tz+=0.01*(y-my);
		else
			ty+=0.01*(my-y);
		mx=x;
		my=y;
		glutPostRedisplay();
	}
}

int main(int argc, char* argv[])
{
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(WinWidth,WinHeight);
	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH)-WinWidth)/2, 
		                   (glutGet(GLUT_SCREEN_HEIGHT)-WinHeight)/2);
	glutCreateWindow("GLUT Teapot");

	Init();

	glut_helpers_initialize();

	cb.display_function=Display;
	cb.keyboard_function=Keyboard;
	cb.special_function=KeyboardSpecial;
	cb.reshape_function=Reshape;
	cb.mouse_function=Mouse;
	cb.motion_function=MouseMotion;

	glut_add_interactor(&cb);
		
	glutMainLoop();

	return 0;
}


Прописываем путь к gl.h

Если скомпилировать его с текущими настройками Проекта, то MSVC++ выдаст ошибку "Не могу найти файл включения gl.h" или что-то в таком духе. На данный заголовок ссылается glh_glut.h, а сам файл gl.h является частью Windows SDK и расположен (по умолчанию) здесь: c:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include\gl\
Добавим данный путь в Свойствах Проекта:
  • В Обозревателе решений щёлкаем по названию только что созданного Проекта GLUTTest01.
  • Во всплывающем меню выбираем "Свойства".
Image
  • В появившемся окне установки свойств Проекта раскрываем древовидную структуру раздела "Свойства конфигурации", щёлкнув по чёрному треугольнику слева от надписи.
  • В раскрышемся списке выбери Каталоги VC++. В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас интересует только 1 строка Каталоги включения.
  • Щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
Image
  • В появившемся меню "Каталоги включения" у нас уже есть ранее добавленная строка "C:\NVSDK\OpenGL\include". Жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовку gl.h.
Image
В нашем случае это c:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include\gl. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода. Жмём "ОК".
Теперь в каталогах включения в верхнем поле у нас прописано 2 строки:
C:\NVSDK\OpenGL\include
C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include\gl

  • Сохрани Решение (Файл -> Сохранить все).
  • Скомпилируй Проект/Решение (F5).
При успешной компиляции появится окно программы с чайником. Если в области окна зажать ЛКМ и подвигать мышь в разные стороны чайник будет вращаться по осям X и Y, следуя движениям мыши. При зажатой ПКМ чайник будет двигаться вверх-вниз. При смене режима (F1) чайник будет то приближаться, то отдаляться.

Исходники Проекта GLUTTest01

Если где-то накосячил, то вот ссылка на исходники Проекта GLUTTest01: https://yadi.sk/d/k5te4Tw46rRciQ(external link) (25Кб)

Интеракторы GLH для решения повседневных задач

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

Интерактор glut_perspective_reshaper

  • Предназначен для автоматической генерации перспективной проекции.
  • Автоматически корректирует параметры видового окна и матрицу проекции при изменении размеров окна.
  • Имеет всего 1 конструктор:
glut_perspective_reshaper(float infovy = 60.f, float inzNear = .1f, float inzFar = 10.f)

Его параметры повторяют оные команды gluPerspective, за исключением того, что в glut_perspective_reshaper отсутствует параметр aspect, который наш интерактор рассчитывает автоматически, исходя из текущих параметров окна.
  • Обрабатывает одно событие - reshape:
class glut_perspective_reshaper : public glut_interactor
{
	public:
...
		void reshape(int w, int h)
		{
			width = w; height = h;
			if(enabled) apply();
		}

		void apply()
		{
			glViewport(0,0,width,height);
			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			apply_perspective();
			glMatrixMode(GL_MODELVIEW);
		}
		void apply_perspective()
		{
			...
		}
...
}

При вызове метода reshape в качестве параметров в него передаются текущие размеры окна.
Метод apply устанавливает параметры видового окна, делает текущей матрицу проекции и вызывает метод apply_perspective, умножающий текущую матрицу на матрицу проекции.
Рассмотрим реальный пример.
  • Стартуй MSVC++2010, если не сделал этого раньше.
  • Открой доработанный в предыдущем абзаце Проект GLUTTest01 (с добавленным gl.h). Впрочем, если читаешь данный урок с самого начала, он наверняка уже открыт в твоей IDE. На всякий случай вот ссылка на архив с Решением: https://yadi.sk/d/k5te4Tw46rRciQ(external link) (25Кб).
  • Замени весь исходный код в Main.cpp на следующий:
Main.cpp
// Copyright by Gaidukov Serg Programming Inc. 1995-2002

#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#include <glh/glh_glut.h>

using namespace std;
using namespace glh;

int WinWidth=640;		// Ширина окна
int WinHeight=480;		// Высота окна

GLfloat  rx=0;			// Угол поворта сцены вокруг оси X
GLfloat  ry=0;			// Y
GLfloat  tx=0;			// Сдвиг по оси X
GLfloat	 ty=0;			// Y
GLfloat	 tz=-9;			// Z
GLint	 tt=0;			// Активная плоскось: 0 - XY, 1 - XZ

const string tstr[2]={"Translate XY", "Translate XZ"};

int mx,my;				// Координаты мыши
bool ldown=false;		// Нажата левая клавиша мыши?
bool rdown=false;		// Нажата правая клавиша мыши?

GLuint list0=0;

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

void Init()				// Инициализация OpenGL
{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_COLOR_MATERIAL);

	glColor3f(0.1,0.7,0.2);
	glClearColor(0.5, 0.5, 0.75, 1);
	
	list0=glGenLists(1);
	
	glNewList(list0, GL_COMPILE);			//Создание дисплейного списка объекта (чайника)
		glutSolidTeapot(2);
	glEndList();
}

void Display()			// Обновление содержимого экрана
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glLoadIdentity();
	glTranslatef(tx,ty,tz);		//Перемещение и поворт объекта
	glRotatef(rx,1,0,0);
	glRotatef(ry,0,1,0);
		
	glCallList(list0);			//Вывод объекта на экран

	glutSwapBuffers();
}

void Keyboard(unsigned char key,int x,int y)			//Обработка сообщений от клавиатуры
{
	switch (key)
	{
		case VK_ESCAPE:		//Если нажата клавиша ESC - выход
			if (list0)
				 glDeleteLists(list0,1);	//Удалить дисплейный список
			exit(0);
			break;
	}
}

void KeyboardSpecial(int key, int x, int y)
{
	switch (key)
	{
		case GLUT_KEY_F1:	//Если нажата клавиша F1
		{
			tt=(tt+1)%2;	//Смена плоскости перемещения
			glutSetWindowTitle(tstr[tt].c_str());	//Изменение заголовка
		}

	}
}

void Mouse(int button, int state, int x, int y)		//Обработка щелчков мыши
{
	if (button==GLUT_LEFT_BUTTON)		//Левая кнопка
	{
		switch (state)
		{
			case GLUT_DOWN:		//Если нажата
				ldown=true;		//установить флаг
				mx=x;			//Запомнить координаты
				my=y;
				break;
			case GLUT_UP:
				ldown=false;
				break;
		}
	}
	if (button==GLUT_RIGHT_BUTTON)	//Правая кнопка
	{
		switch (state)
		{
			case GLUT_DOWN:
				rdown=true;
				mx=x;
				my=y;
				break;
			case GLUT_UP:
				rdown=false;
				break;
		}
	}
}

void MouseMotion(int x, int y)	//Перемешение мыши
{
	if (ldown)		// Левая кнопка
	{
		rx+=0.5*(y-my);	//Изменение угола поворота
		ry+=0.5*(x-mx);
		mx=x;
		my=y;
		glutPostRedisplay();	//Перерисовать экран
	}
	if (rdown)	//Правая
	{
		tx+=0.01*(x-mx);	//Перемещение вдоль активной плоскости
		if (tt)
			tz+=0.01*(y-my);
		else
			ty+=0.01*(my-y);
		mx=x;
		my=y;
		glutPostRedisplay();
	}
}

int main(int argc, char* argv[])
{
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(WinWidth,WinHeight);
	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH)-WinWidth)/2, 
		                   (glutGet(GLUT_SCREEN_HEIGHT)-WinHeight)/2);
	glutCreateWindow("GLUT Teapot");

	Init();

	glut_helpers_initialize();

	cb.display_function=Display;
	cb.keyboard_function=Keyboard;
	cb.special_function=KeyboardSpecial;
	cb.mouse_function=Mouse;
	cb.motion_function=MouseMotion;

	glut_add_interactor(&reshaper);
	glut_add_interactor(&cb);
		
	glutMainLoop();

	return 0;
}
}

  • Сохрани Решение (Файл -> Сохранить все).
  • Скомпилируй Проект/Решение (F5).

Интерактор glut_simple_interactor

  • Облегчает написание программ, определяющих перемещение мыши, нажатие кнопок, одновременно нажатие спецклавиш <Ctrl>, <Alt>, <Shift> и др.
Сначала программа создаёт объект класса glut_simple_interactor и затем присваивает значения одноимённому интерактору для указания того, какие события он должен обрабатывать. К примеру таким событием может быть перемещение мыши с зажатой клавишей <Ctrl>. После размещения настроенного интерактора в общем списке интеракторов программы он (интерактор) начнёт перехватывать события, на которые настроен. При наступлении события интерактор запоминает состояние иыши в данный момент, определяет отличия текущего состояния мыши от предыдущего (на сколько пикселов сдвинулся курсор мыши и т.д.), после чего связывает виртуальный метод update, который, в общем-то, ничего не делает (т.к. класс glut_simple_interactor яляется абстрактным, служит лишь для создания на его основе дочерних классов). Метод update фактически является пользовательским обработчиком события, на которое настроен интерактор.
  • На деле самостоятельно почти никода не используется, служа лишь предком для многих других интеракторов.
  • Пример конструктора класса glut_simple_interactor:
class glut_simple_interactor : public glut_interacor
{
public:
	glut_simple_interactor()
	{
// Активизироваться по левому щелчку мыши
		activate_on = GLUT_LEFT_BUTTON;
// Событие неактивно (левая кнопка мыши ещё не нажата)
		active = FALSE;
// Реагировать на клавиши-модификаторы
		use_modifiers = 0;
// Ширина и высота окна равны 0
		width = height = 0;
// Всё, что связано с координатами мыши равно 0
		x0 = y= x = y = dx = dy = 0; 
	}
...
//Поля
// Кнопка мыши, события от которой обрабатывает интерактор
	int activate_on;
// Использовать ли модификаторы (TRUE соответсвует "да")
	bool use_modifiers;
// Набор комбинация модификаторов, при которой будет активироваться интерактор
	int modifiers;
// Активен ли сейчас интерактор (нажата ли сейчас
// кнопка мыши + комбинаця модификаторов)
	bool active;
// Координаты мыши до перемещения
	int x0, y0;
// Координаты мыши после перемещения
	int x, y;
// Изменения координат мыши
	int dx, dy;
// Размеры окна
	int width, height;
}

Здесь конструктор класса glut_simple_interactor по умолчанию создаёт интерактор, реагирущий на нажатие левой кнопки мыши. Если при этом будет нажата хоть одна клавиша-модификатор, то событие будет проигнорировано.
  • Имеет ряд абстрактных методов, которые переопределяются его потомками:
// Умножает текущую матрицу на матрицу, рассчитанную интерактором
	virtual void apply_transform() = 0;
// Умножает текущую матрицу на обратную матрицу, рассчитанную интерактором
	virtual void apply_inverse_transform() = 0;
// Возвращает матрицу, рассчитанную интерактором
	virtual matrix4f get_transform() = 0;
// Пользовательский обработчик событий
	virtual void update() {}


Интерактор glut_rotate

  • Предназначен для облегчения написания программ, в которых поворот объектов (или сцены) осуществляется при помощи мыши.
При перемещении мыши с нажатой левой кнопкой по оси x интерактор считает, что он хочет осуществить поворот вокруг оси x. Если же мышь двигается вдоль оси y, поворот осуществляется вокруг оси y.
  • Является наследником интерактора glut_simple_interactor.
  • Его структура:
class glut_rotate : public glut_simple_interactor
{
public:
// Конструктор
	glut_rotate()
	{
	// Начальные углы поворота равны 0
	rotate_x = rotate_y = 0;
	// Чувствительность мыши 1
	scale = 1;
	}
// Обработчик события перемещения мыши
	void update()
	{
	// Изменяем угол поворота на расстояние, которое прошла мышь (с учётом чувствительности)
	rotate_x += dx * scale;
	rotate_y += dy * scale;
	glutPostRedisplay();
	}
// Умножаем текущую матрицу на матрицу поворота.
// Иными словами, выполняем поворот вокруг осей x и y
	void apply_transform()
	{
	glRotatef(rotate_x, 0, 1, 0);
	glRotatef(rotate_y, -1, 0, 0);
	}

// Аналогичным образом переопределяем остальные методы класса
// glut_simple_interactor

	void apply_inverse_transform()
	{
	glRotatef(-rotate_y, -1, 0, 0);
	glRotatef(-rotate_x, 0, 1, 0);
	}

	matrix4f get_transform()
	{
	rotationf rx(to_radians(-rotate_x), 0, 1, 0);
	rotationf ry(to_radians(-rotate_y), -1, 0, 0);
	matrix4f mx, my;
	rx.get_value(mx);
	rx.get_value(my);
	return my*mx;
	}
// Углы поворота вокруг осей x и y и чувствительность мыши
// (по умолчанию 1)
	float rotate_x, rotate_y, scale;
};


Рассмотрим реальный пример.
  • Стартуй MSVC++2010, если не сделал этого раньше.
  • Открой доработанный в предыдущем абзаце Проект GLUTTest01 (с добавленным gl.h). Впрочем, если читаешь данный урок с самого начала, он наверняка уже открыт в твоей IDE. На всякий случай вот ссылка на архив с Решением: https://yadi.sk/d/k5te4Tw46rRciQ(external link) (25Кб).
  • Замени весь исходный код в Main.cpp на следующий:
Main.cpp
// Copyright by Gaidukov Serg Programming Inc. 1995-2002

#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#include <glh/glh_glut.h>

using namespace std;
using namespace glh;

int WinWidth=640;		// Ширина окна
int WinHeight=480;		// Высота окна

GLfloat  rx=0;			// Угол поворта сцены вокруг оси X
GLfloat  ry=0;			// Y
GLfloat  tx=0;			// Сдвиг по оси X
GLfloat	 ty=0;			// Y
GLfloat	 tz=-9;			// Z
GLint	 tt=0;			// Активная плоскось: 0 - XY, 1 - XZ

const string tstr[2]={"Translate XY", "Translate XZ"};

int mx,my;				// Координаты мыши
bool rdown=false;		// Нажата правая клавиша мыши?

GLuint list0=0;

glut_perspective_reshaper reshaper(60, 0.1, 100);
glut_rotate mouse_rotate;
glut_callbacks cb;

void Init()				// Инициализация OpenGL
{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_COLOR_MATERIAL);

	glColor3f(0.1,0.7,0.2);
	glClearColor(0.5, 0.5, 0.75, 1);
	
	list0=glGenLists(1);
	
	glNewList(list0, GL_COMPILE);			//Создание дисплейного списка объекта (чайника)
		glutSolidTeapot(2);
	glEndList();
}

void Display()			// Обновление содержимого экрана
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glLoadIdentity();
	glTranslatef(tx,ty,tz);
	mouse_rotate.apply_transform();
		
	glCallList(list0);			//Вывод объекта на экран

	glutSwapBuffers();
}

void Keyboard(unsigned char key,int x,int y)			//Обработка сообщений от клавиатуры
{
	switch (key)
	{
		case VK_ESCAPE:		//Если нажата клавиша ESC - выход
			if (list0)
				 glDeleteLists(list0,1);	//Удалить дисплейный список
			exit(0);
			break;
	}
}

void KeyboardSpecial(int key, int x, int y)
{
	switch (key)
	{
		case GLUT_KEY_F1:	//Если нажата клавиша F1
		{
			tt=(tt+1)%2;	//Смена плоскости перемещения
			glutSetWindowTitle(tstr[tt].c_str());	//Изменение заголовка
		}

	}
}

void Mouse(int button, int state, int x, int y)		//Обработка щелчков мыши
{
	if (button==GLUT_RIGHT_BUTTON)	//Правая кнопка
	{
		switch (state)
		{
			case GLUT_DOWN:
				rdown=true;
				mx=x;
				my=y;
				break;
			case GLUT_UP:
				rdown=false;
				break;
		}
	}
}

void MouseMotion(int x, int y)	//Перемешение мыши
{
	if (rdown)	//Правая
	{
		tx+=0.01*(x-mx);	//Перемещение вдоль активной плоскости
		if (tt)
			tz+=0.01*(y-my);
		else
			ty+=0.01*(my-y);
		mx=x;
		my=y;
		glutPostRedisplay();
	}
}

int main(int argc, char* argv[])
{
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(WinWidth,WinHeight);
	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH)-WinWidth)/2, 
		                   (glutGet(GLUT_SCREEN_HEIGHT)-WinHeight)/2);
	glutCreateWindow("GLUT Teapot");

	Init();

	glut_helpers_initialize();

	mouse_rotate.scale=0.3;

	cb.display_function=Display;
	cb.keyboard_function=Keyboard;
	cb.special_function=KeyboardSpecial;
	cb.mouse_function=Mouse;
	cb.motion_function=MouseMotion;

	glut_add_interactor(&reshaper);
	glut_add_interactor(&mouse_rotate);
	glut_add_interactor(&cb);
		
	glutMainLoop();

	return 0;
}

  • Сохрани Решение (Файл -> Сохранить все).
  • Скомпилируй Проект/Решение (F5).


Рис. 1 Проекция на плоскость точки на поверхности шара трекбола. Здесь и далее изображения взяты с https://www.cs.unm.edu/~angel/CS433.S05/LECTURES/AngelCG15.pdf
Рис. 1 Проекция на плоскость точки на поверхности шара трекбола. Здесь и далее изображения взяты с https://www.cs.unm.edu/~angel/CS433.S05/LECTURES/AngelCG15.pdf

Рис. 2 Определение плоскости поворота трекбола.
Рис. 2 Определение плоскости поворота трекбола.

Рис. 3 Определение угла поворота трекбола.
Рис. 3 Определение угла поворота трекбола.


Интерактор glut_trackball

В интеракторе glut_rotate поворот сцены задаётся при помощи двух углов поворота вокруг осей x и y (т.н. Эйлеровы углы). Причём сначала поворот осуществляется вокруг оси x, а затем вокруг y. Данный подход имеет 1 существенный недостаток: после поворота вокруг оси x изменяется ориентация других осей (y и z), в результате чего ось второго поворота зависит от угла поворота вокруг оси x. В результате неподготовленный пользователь после нескольких минут работы с программой может просто запутаться в осях координат. Данная проблема общеизвестна под названием Gimbal lock(external link).
Одним из её решений является моделирование виртуального трекбола. Для этого необходимо определить соответствие между положением точки на поверхности шара трекбола и позицией указателя мыши, которая передвигается на плоскость. Один из возможных вариантов такого соответствия изображён на Рис.1.
Если предположить, что необходимая нам точка на поверхности шара трекбола имеет положительную координату y, то мы сможем определить однозначное соответствие между проекцией точки на поверхность шара трекбола и самой точкой на поверхности шара. Другими словами, мы сможем по положению указателя мыши на экране восстановить трехмерную точку на поверхности трекбола. А зная две точки на поверхности трекбола мы сможем определить направление и угол поворота трекбола.
Ось поворота можно найти как n = p1 x p2, а угол поворота определяется соотношением... (См. Рис. 3) Здесь p1 и p2 - векторы, проведённые из начала координат к точкам p1 и p2. Зная ось и угол поворота, мы сможем легко повренуть сцену.
Так вот. Для эмуляции виртуального трекбола в библиотеке GLH используется интерактор glut_trackball, который является наследником класса glut_simple_interactor.

Рассмотрим реальный пример.
  • Стартуй MSVC++2010, если не сделал этого раньше.
  • Открой доработанный в предыдущем абзаце Проект GLUTTest01 (с добавленным gl.h). Впрочем, если читаешь данный урок с самого начала, он наверняка уже открыт в твоей IDE. На всякий случай вот ссылка на архив с Решением: https://yadi.sk/d/k5te4Tw46rRciQ(external link) (25Кб).
  • Замени весь исходный код в Main.cpp на следующий:
Main.cpp
// Copyright by Gaidukov Serg Programming Inc. 1995-2002

#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#include <glh/glh_glut.h>

using namespace std;
using namespace glh;

int WinWidth=640;		// Ширина окна
int WinHeight=480;		// Высота окна

GLfloat  rx=0;			// Угол поворта сцены вокруг оси X
GLfloat  ry=0;			// Y
GLfloat  tx=0;			// Сдвиг по оси X
GLfloat	 ty=0;			// Y
GLfloat	 tz=-9;			// Z
GLint	 tt=0;			// Активная плоскось: 0 - XY, 1 - XZ

const string tstr[2]={"Translate XY", "Translate XZ"};

int mx,my;				// Координаты мыши
bool rdown=false;		// Нажата правая клавиша мыши?

GLuint list0=0;

glut_perspective_reshaper reshaper(60, 0.1, 100);
glut_trackball trackball;
glut_callbacks cb;

void Init()				// Инициализация OpenGL
{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_COLOR_MATERIAL);

	glColor3f(0.1,0.7,0.2);
	glClearColor(0.5, 0.5, 0.75, 1);
	
	list0=glGenLists(1);
	
	glNewList(list0, GL_COMPILE);			//Создание дисплейного списка объекта (чайника)
		glutSolidTeapot(2);
	glEndList();
}

void Display()			// Обновление содержимого экрана
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glLoadIdentity();
	glTranslatef(tx,ty,tz);
	trackball.apply_transform();
		
	glCallList(list0);			//Вывод объекта на экран

	glutSwapBuffers();
}

void Keyboard(unsigned char key,int x,int y)			//Обработка сообщений от клавиатуры
{
	switch (key)
	{
		case VK_ESCAPE:		//Если нажата клавиша ESC - выход
			if (list0)
				 glDeleteLists(list0,1);	//Удалить дисплейный список
			exit(0);
			break;
	}
}

void KeyboardSpecial(int key, int x, int y)
{
	switch (key)
	{
		case GLUT_KEY_F1:	//Если нажата клавиша F1
		{
			tt=(tt+1)%2;	//Смена плоскости перемещения
			glutSetWindowTitle(tstr[tt].c_str());	//Изменение заголовка
		}

	}
}

void Mouse(int button, int state, int x, int y)		//Обработка щелчков мыши
{
	if (button==GLUT_RIGHT_BUTTON)	//Правая кнопка
	{
		switch (state)
		{
			case GLUT_DOWN:
				rdown=true;
				mx=x;
				my=y;
				break;
			case GLUT_UP:
				rdown=false;
				break;
		}
	}
}

void MouseMotion(int x, int y)	//Перемешение мыши
{
	if (rdown)	//Правая
	{
		tx+=0.01*(x-mx);	//Перемещение вдоль активной плоскости
		if (tt)
			tz+=0.01*(y-my);
		else
			ty+=0.01*(my-y);
		mx=x;
		my=y;
		glutPostRedisplay();
	}
}

int main(int argc, char* argv[])
{
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(WinWidth,WinHeight);
	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH)-WinWidth)/2, 
		                   (glutGet(GLUT_SCREEN_HEIGHT)-WinHeight)/2);
	glutCreateWindow("GLUT Teapot");

	Init();

	glut_helpers_initialize();
	
	cb.display_function=Display;
	cb.keyboard_function=Keyboard;
	cb.special_function=KeyboardSpecial;
	cb.mouse_function=Mouse;
	cb.motion_function=MouseMotion;

	glut_add_interactor(&reshaper);
	glut_add_interactor(&trackball);
	glut_add_interactor(&cb);
		
	glutMainLoop();

	return 0;
}

  • Сохрани Решение (Файл -> Сохранить все).
  • Скомпилируй Проект/Решение (F5).

Интеракторы glut_pan и glut_dolly

  • Применяются для создания виртуальной камеры, перемещающейся по сцене.
  • glut_pan реализует перемещение виртуальной камеры вдоль осей x и y.
  • glut_dolly реализует перемещение виртуальной камеры вдоль оси z.

Рассмотрим реальный пример.
  • Стартуй MSVC++2010, если не сделал этого раньше.
  • Открой доработанный в предыдущем абзаце Проект GLUTTest01 (с добавленным gl.h). Впрочем, если читаешь данный урок с самого начала, он наверняка уже открыт в твоей IDE. На всякий случай вот ссылка на архив с Решением: https://yadi.sk/d/k5te4Tw46rRciQ(external link) (25Кб).
  • Замени весь исходный код в Main.cpp на следующий:
Main.cpp
// Copyright by Gaidukov Serg Programming Inc. 1995-2002

#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#include <glh/glh_glut.h>

using namespace std;
using namespace glh;

int WinWidth=640;		// Ширина окна
int WinHeight=480;		// Высота окна

GLuint list0=0;

glut_perspective_reshaper reshaper(60, 0.1, 100);
glut_trackball trackball;
glut_pan pan;
glut_dolly dolly;
glut_callbacks cb;

void Init()				// Инициализация OpenGL
{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_COLOR_MATERIAL);

	glColor3f(0.1,0.7,0.2);
	glClearColor(0.5, 0.5, 0.75, 1);
	
	list0=glGenLists(1);
	
	glNewList(list0, GL_COMPILE);			//Создание дисплейного списка объекта (чайника)
		glutSolidTeapot(2);
	glEndList();
}

void Display()			// Обновление содержимого экрана
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glPushMatrix();
		pan.apply_transform();
		dolly.apply_transform();
		trackball.apply_transform();
		
		glCallList(list0);			//Вывод объекта на экран
	glPopMatrix();

	glutSwapBuffers();
}

void Keyboard(unsigned char key,int x,int y)			//Обработка сообщений от клавиатуры
{
	switch (key)
	{
		case VK_ESCAPE:		//Если нажата клавиша ESC - выход
			if (list0)
				 glDeleteLists(list0,1);	//Удалить дисплейный список
			exit(0);
			break;
	}
}

int main(int argc, char* argv[])
{
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(WinWidth,WinHeight);
	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH)-WinWidth)/2, 
		                   (glutGet(GLUT_SCREEN_HEIGHT)-WinHeight)/2);
	glutCreateWindow("GLUT Teapot");

	Init();

	glut_helpers_initialize();
	
	dolly.dolly[2]=-9;
	dolly.modifiers=GLUT_ACTIVE_CTRL;
	pan.modifiers=GLUT_ACTIVE_SHIFT;
	
	cb.display_function=Display;
	cb.keyboard_function=Keyboard;

	glut_add_interactor(&reshaper);
	glut_add_interactor(&pan);
	glut_add_interactor(&dolly);
	glut_add_interactor(&trackball);
	glut_add_interactor(&cb);
		
	glutMainLoop();

	return 0;
}

  • Сохрани Решение (Файл -> Сохранить все).
  • Скомпилируй Проект/Решение (F5).
Перенос вдоль осей x и y производится при задатом <Ctrl>, а вдоль оси z - при зажатом <Shift>.

Интерактор glut_simple_mouse_interactor

  • Объединяет в себе 3 модификатора: glut_trackball, glut_pan и glut_dolly.
Рассмотрим реальный пример.
  • Стартуй MSVC++2010, если не сделал этого раньше.
  • Открой доработанный в предыдущем абзаце Проект GLUTTest01 (с добавленным gl.h). Впрочем, если читаешь данный урок с самого начала, он наверняка уже открыт в твоей IDE. На всякий случай вот ссылка на архив с Решением: https://yadi.sk/d/k5te4Tw46rRciQ(external link) (25Кб).
  • Замени весь исходный код в Main.cpp на следующий:
Main.cpp
// Copyright by Gaidukov Serg Programming Inc. 1995-2002

#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#include <glh/glh_glut.h>

using namespace std;
using namespace glh;

int WinWidth=640;		// Ширина окна
int WinHeight=480;		// Высота окна

GLuint list0=0;

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

void Init()				// Инициализация OpenGL
{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_COLOR_MATERIAL);

	glColor3f(0.1,0.7,0.2);
	glClearColor(0.5, 0.5, 0.75, 1);
	
	list0=glGenLists(1);
	
	glNewList(list0, GL_COMPILE);			//Создание дисплейного списка объекта (чайника)
		glutSolidTeapot(2);
	glEndList();
}

void Display()			// Обновление содержимого экрана
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glLoadIdentity();
	user.apply_transform();		
		
	glCallList(list0);			//Вывод объекта на экран

	glutSwapBuffers();
}

void Keyboard(unsigned char key,int x,int y)			//Обработка сообщений от клавиатуры
{
	switch (key)
	{
		case VK_ESCAPE:		//Если нажата клавиша ESC - выход
			if (list0)
				 glDeleteLists(list0,1);	//Удалить дисплейный список
			exit(0);
			break;
	}
}

int main(int argc, char* argv[])
{
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(WinWidth,WinHeight);
	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH)-WinWidth)/2, 
		                   (glutGet(GLUT_SCREEN_HEIGHT)-WinHeight)/2);
	glutCreateWindow("GLUT Teapot");

	Init();

	glut_helpers_initialize();
	
	user.configure_buttons(1);
	user.dolly.dolly[2]=-9;
	
	cb.display_function=Display;
	cb.keyboard_function=Keyboard;

	glut_add_interactor(&reshaper);
	glut_add_interactor(&user);
	glut_add_interactor(&cb);
		
	glutMainLoop();

	return 0;
}

  • Сохрани Решение (Файл -> Сохранить все).
  • Скомпилируй Проект/Решение (F5).

Как видно, интерактор glut_simple_mouse_interactor объединяет интеракторы glut_trackball, glut_pan и glut_dolly в единый интерактор с общим конструктором, матрицей преобразования и т.д.
Рассмотрим конструктор glut_simple_mouse_interactor:
glut_simple_mouse_interactor(int num_buttons_to_use=3)
{
 configure_buttons(num_buttons_to_use);
 camera_mode = false;
}

Конструктор передаёт свой ед. параметр методу num_buttons_to_use и устанавливает "классический режим камеры". Метод configure_buttons устанавливает вид интерфейса с пользователем и может принимать значения 1, 2 и 3:
Значение, перед. методу configure_buttons Вращение объекта Перемещение в плоскости экрана Перемещение вдоль оси Z
1 Левая кнопка мыши Shift + левая кнопка мыши Ctrl + левая кнопка мыши
2 Левая кнопка мыши Средняя кнопка мыши Ctrl + левая кнопка мыши
3 Левая кнопка мыши Средняя кнопка мыши Правая кнопка мыши


Функции glut_timer и glut_idle

  • glut_timer - функция таймера.
  • glut_idle - функция бездействия.
Как говорилось ранее, объектная надстройка над GLUT использует свои собственные обработчики событий GLUT. При возникновении события обработчик GLUT перебирает интеракторы из списка интеракторов и вызывает соответствующий метод-обработчик. С большинством событий такой механизм работает вполне нормально. Но есть 2 исключения: события таймера glutTimerFunc и бездействия программы glutIdleFunc.
При программировании таймера программа должна самостоятельно указать не только интервал, через который работает таймер, но и обработчик события от таймера. Этим обработчиком всегда является библиотека GLH, что противоречит самой идеологии объектной надстройки над GLUT, т.к. пользовательская программа не должна непосредственно работать со стандартными обработчиками библиотеки GLH. Компромиссным решением стала функция glut_timer:
inline void glut_timer (int msec, int value)
{
  glutTimerFunc(msec, glut_timer_function, value);
}

Как видно, функция принимает интервал, через который сработает таймер и идентификатор таймера, после чего устанавливает в качестве обработчика таймера собственную функцию-обработчик. Конечно, можно обойтись без данной функции, но тогда есть вероятнось, что наша программа перестанет работать с будущими версиями NVIDIA OpenGL SDK.

Рассмотрим реальный пример, где чайник вращается с использованием таймера.
  • Стартуй MSVC++2010, если не сделал этого раньше.
  • Открой доработанный в предыдущем абзаце Проект GLUTTest01 (с добавленным gl.h). Впрочем, если читаешь данный урок с самого начала, он наверняка уже открыт в твоей IDE. На всякий случай вот ссылка на архив с Решением: https://yadi.sk/d/k5te4Tw46rRciQ(external link) (25Кб).
  • Замени весь исходный код в Main.cpp на следующий:
Main.cpp
// Copyright by Gaidukov Serg Programming Inc. 1995-2002

#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#include <glh/glh_glut.h>

using namespace std;
using namespace glh;

int WinWidth=640;		// Ширина окна
int WinHeight=480;		// Высота окна

GLuint list0=0;

GLfloat rr=0;

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

void Init()				// Инициализация OpenGL
{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_COLOR_MATERIAL);

	glColor3f(0.1,0.7,0.2);
	glClearColor(0.5, 0.5, 0.75, 1);
	
	list0=glGenLists(1);
	
	glNewList(list0, GL_COMPILE);			//Создание дисплейного списка объекта (чайника)
		glutSolidTeapot(2);
	glEndList();
}

void Display()			// Обновление содержимого экрана
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glLoadIdentity();
	user.apply_transform();		
	glRotatef(rr, 0, 1, 0);
		
	glCallList(list0);			//Вывод объекта на экран

	glutSwapBuffers();
}

void onExit()
{
	glDeleteLists(list0,1);
}

void Timer(int Value)
{
	rr+=2;
	if (rr>360)
		rr-=360;

	glut_timer(25, 1);
	glutPostRedisplay();
}

int main(int argc, char* argv[])
{
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(WinWidth,WinHeight);
	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH)-WinWidth)/2, 
		                   (glutGet(GLUT_SCREEN_HEIGHT)-WinHeight)/2);
	glutCreateWindow("GLUT Teapot");

	Init();

	glut_helpers_initialize();
	
	user.configure_buttons(1);
	user.dolly.dolly[2]=-9;
	
	cb.display_function=Display;
	cb.keyboard_function=glut_exit_on_escape;
	cb.timer_function=Timer;

	glut_add_interactor(&reshaper);
	glut_add_interactor(&user);
	glut_add_interactor(&cb);

	atexit(onExit);

	Timer(1);
		
	glutMainLoop();

	return 0;
}

  • Сохрани Решение (Файл -> Сохранить все).
  • Скомпилируй Проект/Решение (F5).

С обработчиком бездействия программы (Idle) ситуация схожая: для включения и включения обработки события idle используется функция glut_idle:
inline void glut_idle (bool do_idle)
{
 glutIdleFunc(do_idle ? glut_idle_function : 0);
}

Функция glut_idle принимает 1 параметр типа bool. Если он равен true, то обработка события idle включена. Если false - то выключена.

Рассмотрим реальный пример.
  • Стартуй MSVC++2010, если не сделал этого раньше.
  • Открой доработанный в предыдущем абзаце Проект GLUTTest01 (с добавленным gl.h). Впрочем, если читаешь данный урок с самого начала, он наверняка уже открыт в твоей IDE. На всякий случай вот ссылка на архив с Решением: https://yadi.sk/d/k5te4Tw46rRciQ(external link) (25Кб).
  • Замени весь исходный код в Main.cpp на следующий:
Main.cpp
// Copyright by Gaidukov Serg Programming Inc. 1995-2002

#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#include <glh/glh_glut.h>

using namespace std;
using namespace glh;

int WinWidth=640;		// Ширина окна
int WinHeight=480;		// Высота окна

GLuint list0=0;

GLfloat rr=0;
float OldTick;

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

void Init()				// Инициализация OpenGL
{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_COLOR_MATERIAL);

	glColor3f(0.1,0.7,0.2);
	glClearColor(0.5, 0.5, 0.75, 1);
	
	list0=glGenLists(1);
	
	glNewList(list0, GL_COMPILE);			//Создание дисплейного списка объекта (чайника)
		glutSolidTeapot(2);
	glEndList();
}

void Display()			// Обновление содержимого экрана
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glLoadIdentity();
	user.apply_transform();		
	glRotatef(rr, 0, 1, 0);
		
	glCallList(list0);			//Вывод объекта на экран

	glutSwapBuffers();
}

void onExit()
{
	glDeleteLists(list0,1);
}

void Idle()
{
	rr+=(GetTickCount()-OldTick)*0.1;
	OldTick=GetTickCount();	
	if (rr>360)
		rr-=360;

	glutPostRedisplay();
}

int main(int argc, char* argv[])
{
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(WinWidth,WinHeight);
	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH)-WinWidth)/2, 
		                   (glutGet(GLUT_SCREEN_HEIGHT)-WinHeight)/2);
	glutCreateWindow("GLUT Teapot");

	Init();

	glut_helpers_initialize();
	
	user.configure_buttons(1);
	user.dolly.dolly[2]=-9;
	
	cb.display_function=Display;
	cb.keyboard_function=glut_exit_on_escape;
	cb.idle_function=Idle;

	glut_add_interactor(&reshaper);
	glut_add_interactor(&user);
	glut_add_interactor(&cb);

	atexit(onExit);

	OldTick=GetTickCount();
	glut_idle(true);
		
	glutMainLoop();

	return 0;
}

  • Сохрани Решение (Файл -> Сохранить все).
  • Скомпилируй Проект/Решение (F5).

Источники:


1. Гайдуков С. OpenGL. Профессиональное программирование трехмерной графики на C++. - БВХ-Петербург. 2004


Contributors to this page: slymentat .
Последнее изменение страницы Понедельник 14 / Октябрь, 2019 08:52:35 MSK автор slymentat.

Помочь проекту

Яндекс-деньги: 410011791055108