Уроки OpenGL + C#.

Реализация программы для рисования B сплайна и его редактирования.

 

Разработка программы начинается с создания оболочки.

Создайте окно программы и разместите на ней элемент openglsimplecontrol , как показано на рисунке 1, после чего установите его размеры 500х500. Переименуйте данный объект, дав ему имя AnT.

Заготовка окна
Рисунок 1.

Справа от данного элемента разместите элемент comboBox, после чего в его свойствах установите значение параметра DropDownStyle = DropDownList. После этого выпадающие элементы перестанут быть доступными для редактирования. После этого, измените элементы Items как показано на рисунке 2.

Заполнение элемента listBox
Рисунок 2.


Так же не забудьте установить ссылки на используемые библиотеки Tao (рисунок 3).
Инициализация окна и OpenGl происходит, так же как и в предыдущих проектах, код приведен на всякий случай далее.

Подключение Tao.
Рисунок 3.

 

Для обработки событий мыши выделите объект AnT, перейдите к его свойствам, после чего перейдите к настройке событий и добавьте обработку событий мыши, как показано на рисунке 4.

События мыши.
Рисунок 4.


Для реализации визуализации будет использоваться таймер – после инициализации окна он будет генерировать событие, называемое тиком таймера, раз в 30 миллисекунд – добавьте элемент таймер, переименуйте экземпляр в RenderTimer и установите время тика 30 миллисекунд (как показано на рисунке 5), а так же добавьте ему событие для обработки тика.

Создание таймера.
Рисунок 5.

Нам потребуется объявить ряд переменных, для дальнейшей работы программы:


// массив в который будут заносится управляющие точки
private float [,] DrawingArray = new float [64, 2]; // количество точек
private int count_po int s = 0;

// размеры окна
double ScreenW, ScreenH;

// отношения сторон окна визуализации
// для корректного перевода координат мыши в координаты,
// принятые в программе

private float devX;
private float devY;

// вспомогательные переменные для построения линий от курсора мыши к координатным осям
float lineX, lineY;

// текущение координаты курсора мыши
float Mcoord_X = 0, Mcoord_Y = 0;


/*
* Состояние захвата вершины мышью (при редактированиее)
*/


int captured = -1; // -1 означает что нет захваченой, иначе - номер указывает на элемент массива, хранящий захваченную вершину


Код инициализации окна, настройки визуализации в OpenGL и старт таймера после завершения начальной настройки. 

Не забудьте создать непосредственно само событие загрузки формы.


public Form1()
{
InitializeComponent();
AnT.InitializeContexts();
}

private void Form1_Load( object sender, EventArgs e)
{
// инициализация бибилиотеки glut
Glut.glutInit();
// инициализация режима экрана
Glut.glutInitDisplayMode( Glut.GLUT_RGB | Glut.GLUT_DOUBLE);

// установка цвета очистки экрана (RGBA)
Gl.glClearColor(255, 255, 255, 1);

// установка порта вывода
Gl.glViewport(0, 0, AnT.Width, AnT.Height);

// активация проекционной матрицы
Gl.glMatrixMode( Gl.GL_PROJECTION);
// очистка матрицы
Gl.glLoadIdentity();

// определение параметров настройки проекции, в зависимости от размеров сторон элемента AnT.
if (( float )AnT.Width <= ( float )AnT.Height)
{
ScreenW = 500.0;
ScreenH = 500.0 * ( float )AnT.Height / ( float )AnT.Width;

Glu.gluOrtho2D(0.0, ScreenW, 0.0, ScreenH);
}
else
{
ScreenW = 500.0 * ( float )AnT.Width / ( float )AnT.Height;
ScreenH = 500.0;

Glu.gluOrtho2D(0.0, 500.0 * ( float )AnT.Width / ( float )AnT.Height, 0.0, 500.0);
}

// сохранение коэфицентов, которые нам необходимы для перевода координат указателя в оконной системе, в координаты
// принятые в нашей OpenGL сцене
devX = ( float )ScreenW / ( float )AnT.Width;
devY = ( float )ScreenH / ( float )AnT.Height;

// установка объектно-видовой матрицы
Gl.glMatrixMode( Gl.GL_MODELVIEW);
RenderTime.Start();

comboBox1.SelectedIndex = 0;
}


Функция визуализации текста уже была рассмотрена нами ранее


// фукнция визуализации текста
private void Pr int Text2D( float x, float y, string text)
{
// устанавливаем позицию вывода растровых символов
// в переданных координатах x и y.
Gl.glRasterPos2f(x, y); // в цикле foreach перебираем значения из массива text,
// который содержит значение строки для визуализации
foreach ( char char _for_draw in text)
{
// визуализируем символ c, с помощью функции glutBitmapCharacter, используя шрифт GLUT_BITMAP_9_BY_15.
Glut.glutBitmapCharacter( Glut.GLUT_BITMAP_8_BY_13, char _for_draw);
}
}

 


Теперь переходим непосредственно к обработке нажатий мыши, таймера и построению сплайна.
Обработка события таймера


private void RenderTime_Tick( object sender, EventArgs e)
{
// обработка 'тика' таймера - вызов функции отрисовки
Draw();
}

 


Функция отрисовки:

 


// функция отрисовки, вызываемая событием таймера
private void Draw()
{
// количество сегментов при расчете сплайна
int N = 30; // вспомогательные переменные для расчета сплайна
double X, Y;

// n = count_po int s+1 означает что мы берем все созданные контрольные
// точки + ту, которая следует за мышью, для создания интерактивности приложения
int eps = 4, i, j, n = count_po int s+1, first;
double xA, xB, xC, xD, yA, yB, yC, yD, t;
double a0, a1, a2, a3, b0, b1, b2, b3;

// очистка буфера цвета и буфера глубины
Gl.glClear( Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
Gl.glClearColor(255, 255, 255, 1);
// очищение текущей матрицы
Gl.glLoadIdentity();

// утснаовка черного цвета
Gl.glColor3f(0, 0, 0);

// помещаем состояние матрицы в стек матриц
Gl.glPushMatrix();

Gl.glPointSize(5.0f);
Gl.glBegin( Gl.GL_POINTS);

Gl.glVertex2d(0, 0);

Gl.glEnd();
Gl.glPointSize(1.0f);

PrintText2D(devX * Mcoord_X + 0.2f, ( float )ScreenH - devY * Mcoord_Y + 0.4f, "[ x: " + (devX * Mcoord_X).ToString() + " ; y: " + (( float )ScreenH - devY * Mcoord_Y).ToString() + "]");


// выполняем перемещение в прострастве по осям X и Y

// выполняем цикл по контрольным точкам
for (i = 0; i < n; i++)
{
// сохраняем координаты точки (более легкое представления кода)
X = DrawingArray[i, 0];
Y = DrawingArray[i, 1];

// если точка выделена (перетаскивается мышью)
if (i == captured)
{
// для ее отрисовки будут использоватся более толстые линии
Gl.glLineWidth(3.0f);
}

// начинаем отрисвку точки (квадрат)
Gl.glBegin( Gl.GL_LINE_LOOP);

Gl.glVertex2d(X - eps, Y - eps);
Gl.glVertex2d(X + eps, Y - eps);
Gl.glVertex2d(X + eps, Y + eps);
Gl.glVertex2d(X - eps, Y + eps);

Gl.glEnd();

// если была захваченная точка - необходимо вернуть толщину линий
if (i == captured)
{
// возвращаем прежнее значение
Gl.glLineWidth(1.0f);
}
}


// дополнительный цикл по всем контрольным точкам -
// подписываем их координаты и номер
for (i = 0; i < n; i++)
{
// координаты точки
X = DrawingArray[i, 0];
Y = DrawingArray[i, 1];
// выводим подпись рядом с точкой
PrintText2D(( float )(X - 20), ( float )(Y - 20), "P " + i.ToString() + ": " + X.ToString() + ", " + Y.ToString());
}

// начинает отрисовку кривой
Gl.glBegin( Gl.GL_LINE_STRIP);

// используем все точки -1 (т,к. алгоритм 'зацепит' i+1 точку
for (i = 1; i < n-1; i++)
{
// реализация представленного в теоретическом описании алгоритма для калькуляции сплайна
first = 1;
xA = DrawingArray[i - 1, 0];
xB = DrawingArray[i, 0];
xC = DrawingArray[i + 1, 0];
xD = DrawingArray[i + 2, 0];

yA = DrawingArray[i - 1, 1];
yB = DrawingArray[i, 1];
yC = DrawingArray[i + 1, 1];
yD = DrawingArray[i + 2, 1];

a3 = (-xA + 3 * (xB - xC) + xD) / 6.0;

a2 = (xA - 2 * xB + xC) / 2.0;

a1 = (xC - xA) / 2.0;

a0 = (xA + 4 * xB + xC) / 6.0;

b3 = (-yA + 3 * (yB - yC) + yD) / 6.0;

b2 = (yA - 2 * yB + yC) / 2.0;

b1 = (yC - yA) / 2.0;

b0 = (yA + 4 * yB + yC) / 6.0;

// отрисовка сегментов

for (j = 0; j <= N; j++)
{
// параметр t на отрезке от 0 до 1
t = ( double )j / ( double )N;

// генерация координат
X = (((a3 * t + a2) * t + a1) * t + a0);
Y = (((b3 * t + b2) * t + b1) * t + b0);

// и установка вершин
if (first == 1)
{
first = 0;
Gl.glVertex2d(X, Y);
}
else
Gl.glVertex2d(X, Y);
}
}
Gl.glEnd();


// завершаем рисование
Gl.glFlush();

// сигнал для обновление элемента реализующего визуализацию.
AnT.Invalidate();
 
}


Обработка события мыши , установленные для объекта AnT будут решать две задачи: при перемещении курсора, интерактивная (т.е. еще не зафиксированная точка) будет создавать эффект создания сплайна на ходу. Но если установлен режим редактирования сплайна, то в том случае, если какая либо из точек захвачена, т.е. на нее наведен курсор и зажата левая клавиша мыши, то до тех пор пока пользователь не отпустит клавишу мыши, мы будем вносить изменения в координаты данной (захваченной) вершины.

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

Обработка событий мыши:


// обработка движения мыши
private void AnT_MouseMove( object sender, Mouse EventArgs e)
{
// если установлен режим создания сплайна
if (comboBox1.SelectedIndex == 0)
{
// созраняем координаты мыши
Mcoord_X = e.X;
Mcoord_Y = e.Y; // вычисляем параметры для будующей дорисовке линий от указателя мыши к координатным осям.
lineX = devX * e.X;
lineY = ( float )(ScreenH - devY * e.Y);

// текущая (интерактивная точка, добавляемая к уже установленным - непрерывно изменяется от движения
// мыши и создает эффект интерактивности и наглядности приложения
DrawingArray[count_po int s, 0] = lineX;
DrawingArray[count_po int s, 1] = lineY;
}
else
{
// обычное протоколирование координат, для подсвечивания вершины в случае наведения
// созраняем координаты мыши
Mcoord_X = e.X;
Mcoord_Y = e.Y;

// вычисляем параметры для будующей дорисовке линий от указателя мыши к координатным осям.

float _lastX = lineX;
float _lastY = lineY;

lineX = devX * e.X;
lineY = ( float )(ScreenH - devY * e.Y);

// если точка захвачена (т.е. пользователь удерживает кнопку мыши.
if (captured != -1)
{
// то мы вносим разницу с последними координатами курсора
// другими словами перемещаем захваченную точку
DrawingArray[captured, 0] -= _lastX-lineX;
DrawingArray[captured, 1] -= _lastY-lineY;
}
}
}

// щелчок мыши
private void AnT_MouseClick( object sender, Mouse EventArgs e)
{
// если мы находимся в режиме создания сплайна
if (comboBox1.SelectedIndex == 0)
{
// забираем координаты мыши
Mcoord_X = e.X;
Mcoord_Y = e.Y;

// приводим к нужному нам формату, в соотвествии с настройками проекции
lineX = devX * e.X;
lineY = ( float )(ScreenH - devY * e.Y);

// создаем новую контрольную точку
DrawingArray[count_po int s, 0] = lineX;
DrawingArray[count_po int s, 1] = lineY;

// и увеличиваем значение счетчика контрольных точек
count_po int s++;
}
}

// если ОПУЩЕНА клавиша мыши
private void AnT_MouseDown( object sender, Mouse EventArgs e)
{
// если режим редактирования сплайна
if (comboBox1.SelectedIndex == 1)
{
// получаем и преобразовываем координаты нажатия
Mcoord_X = e.X;
Mcoord_Y = e.Y;

lineX = devX * e.X;
lineY = ( float )(ScreenH - devY * e.Y);

// проходим циклом по всем установленным контрольным точкам
for ( int ax = 0; ax < count_points; ax++)
{
// если точка попадает под курсор
if (lineX < DrawingArray[ax, 0] + 5 && lineX > DrawingArray[ax, 0] - 5 && lineY < DrawingArray[ax, 1] + 5 && lineY > DrawingArray[ax, 1] - 5)
{
// отмечаем ее как захваченную (записываем ее индекс в массив captured)
captured = ax;
// останавливаем цикл, мы нашли нужную точку
break ;
}
}
}
}

// если клавиша мыши поднята - пользователь отпустил клавишу и точка долна отсановится в перемещении
private void AnT_MouseUp( object sender, Mouse EventArgs e)
{
// отмечаем что нет захваченной точки
captured = -1;
}


Ну вот в принципе и все, теперь можно протестировать работу программы (рисунок 6).

 

Рисунок 6. Нарисованный сплайн.

Результат работы программы - реализация B-сплайнов на OpenGl.
Рисунок 6.

Скачать исходный код урока - сплайны, openGL

Обсуждение данного урока: Сплайны - реализация алгоритма на OpenGL.
Далее: 9.1 Теоретическое введение - трансформация графических объетов.