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

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

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

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

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

Рисунок 5.
Нам потребуется объявить ряд переменных, для дальнейшей работы программы:
Код инициализации окна, настройки визуализации в OpenGL и старт таймера после завершения начальной настройки.
InitializeComponent();}
AnT.InitializeContexts();
// инициализация бибилиотеки 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;
Функция визуализации текста уже была рассмотрена нами ранее
// устанавливаем позицию вывода растровых символов}
// в переданных координатах 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);
Теперь переходим непосредственно к обработке нажатий мыши, таймера и построению сплайна.
Обработка события таймера
// обработка 'тика' таймера - вызов функции отрисовки}
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 будут решать две задачи: при перемещении курсора, интерактивная (т.е. еще не зафиксированная точка) будет создавать эффект создания сплайна на ходу. Но если установлен режим редактирования сплайна, то в том случае, если какая либо из точек захвачена, т.е. на нее наведен курсор и зажата левая клавиша мыши, то до тех пор пока пользователь не отпустит клавишу мыши, мы будем вносить изменения в координаты данной (захваченной) вершины.
Эта разность определяется как разница между последними сохраненными координатами в данном режиме и текущими координатами указателя. Таким образом, начав на вершину пользователь может передвинуть ее, при этом наблюдая в реальном времени как изменяются геометрия данного сплайна.
Обработка событий мыши:
// если установлен режим создания сплайна}
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;
// если мы находимся в режиме создания сплайна}
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++;
// если режим редактирования сплайна}
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 ;
// отмечаем что нет захваченной точки}
captured = -1;
Ну вот в принципе и все, теперь можно протестировать работу программы (рисунок 6).
Рисунок 6. Нарисованный сплайн.

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