Переименуйте данный объект, дав ему имя AnT.
Рисунок 1. Заготовка окна.
Справа от данного элемента разместите элемент comboBox, после чего в его свойствах установите значение параметра DropDownStyle = DropDownList. После этого выпадающие элементы перестанут быть доступными для редактирования. После этого измените элементы Items, как показано на рисунке 2.
Рисунок 2. Заполнение элемента listBox.
Также не забудьте установить ссылки на используемые библиотеки Tao (рис. 3).
Инициализация окна и OpenGl происходит так же, как и в предыдущих проектах. Код приведен на всякий случай чуть ниже.
Рисунок 3. Подключение Tao.
Для обработки событий мыши выделите объект AnT, перейдите к его свойствам, после чего перейдите к настройке событий и добавьте обработку событий мыши, как показано на рисунке 4.
Рисунок 4. События мыши.
Для реализации визуализации будет использоваться таймер. После инициализации окна он будет генерировать событие, называемое тиком таймера, раз в 30 миллисекунд. Добавьте элемент таймер, переименуйте экземпляр в RenderTimer и установите время тика 30 миллисекунд (как показано на рисунке 5), а также добавьте ему событие для обработки тика.
Рисунок 5. Создание таймера.
Нам потребуется объявить ряд переменных для дальнейшей работы программы:
/*http://esate.ru, Anvi*/
// массив, в который будут заносится управляющие точки
private float [,] DrawingArray = new float [64, 2]; // количество точек
private int count_points = 0;
private int max_point = 62;
// размеры окна
double ScreenW, ScreenH;
// отношения сторон окна визуализации
// для корректного перевода координат мыши в координаты
// принятые в программе
private float devX;
private float devY;
// вспомогательные переменные для построения линий от курсора мыши к координатным осям
float lineX, lineY;
// текущие координаты курсора мыши
float Mcoord_X = 0, Mcoord_Y = 0;
/*
* Состояние захвата вершины мышью (при редактировании)
*/
int captured = -1; // -1 означает, что нет захваченной, иначе - номер указывает на элемент массива, хранящий захваченную вершину
Код инициализации окна, настройки визуализации в OpenGL и старт таймера после завершения начальной настройки.
Не забудьте создать непосредственно само событие загрузки формы!
/*http://esate.ru, Anvi*/
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;
}
Функция визуализации текста уже была рассмотрена нами ранее.
/*http://esate.ru, Anvi*/
// функция визуализации текста
private void PrintText2D( 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);
}
}
Теперь переходим непосредственно к обработке нажатий мыши, таймера и построению сплайна.
Обработка события таймера:
/*http://esate.ru, Anvi*/
private void RenderTime_Tick( object sender, EventArgs e)
{
// обработка 'тика' таймера - вызов функции отрисовки
Draw();
}
Функция отрисовки:
/*http://esate.ru, Anvi*/
// функция отрисовки, вызываемая событием таймера
private void Draw()
{
// количество сегментов при расчете сплайна
int N = 30; // вспомогательные переменные для расчета сплайна
double X, Y;
// n = count_points+1 означает что мы берем все созданные контрольные
// точки + ту, которая следует за мышью, для создания интерактивности приложения
int eps = 4, i, j, n = count_points+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, будет решать две задачи: при перемещении курсора, интерактивная (т.е. еще не зафиксированная точка) будет создавать эффект создания сплайна на ходу. Но если установлен режим редактирования сплайна, то в том случае, если какая-либо из точек захвачена, т.е. на нее наведен курсор и зажата левая клавиша мыши, то до тех пор пока пользователь не отпустит клавишу мыши, мы будем вносить изменения в координаты данной (захваченной) вершины.
Эта разность определяется как разница между последними сохраненными координатами в данном режиме и текущими координатами указателя. Таким образом, нажав на вершину, пользователь может передвинуть ее, при этом наблюдая в реальном времени, как изменяются геометрия данного сплайна.
Обработка событий мыши:
/*http://esate.ru, Anvi*/
// обработка движения мыши
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_points, 0] = lineX;
DrawingArray[count_points, 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_points, 0] = lineX;
DrawingArray[count_points, 1] = lineY;
// и увеличиваем значение счетчика контрольных точек
count_points++;
}
}
// если ОПУЩЕНА клавиша мыши
private void AnT_MouseDown( object sender, Mouse EventArgs e)
{
if (count_points == max_point) // мы не должны выходить за границы объявленных массивов
return;
// если режим редактирования сплайна
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.