Переименуйте данный объект, дав ему имя AnT.

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


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

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

Нам потребуется объявить ряд переменных для дальнейшей работы программы.
Инициализация окна и OpenGl происходит так же, как и в предыдущих проектах:
/*http://esate.ru, Anvi*/
public Form1()
{
InitializeComponent();
// инициализация для работы с openGL
AnT.InitializeContexts();
}
// массив вершин создаваемого геометрического объекта
private float[,] GeomObject = new float[32, 3];
// счетчик его вершин
private int count_elements = 0;
// событие загрузки формы окна
private void Form1_Load( object sender, EventArgs e)
{
// инициализация OpenGL, много раз комментированная ранее
// инициализация библиотеки 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();
Glu.gluPerspective(45, (float)AnT.Width / (float)AnT.Height, 0.1, 200);
Gl.glMatrixMode( Gl.GL_MODELVIEW);
Gl.glLoadIdentity();
Gl.glEnable( Gl.GL_DEPTH_TEST);
// пирамида для визуализации (4 точки)
GeomObject[0, 0] = -0.7f;
GeomObject[0, 1] = 0;
GeomObject[0, 2] = 0;
GeomObject[1, 0] = 0.7f;
GeomObject[1, 1] = 0;
GeomObject[1, 2] = 0;
GeomObject[2, 0] = 0.0f;
GeomObject[2, 1] = 0;
GeomObject[2, 2] = 1.0f;
GeomObject[3, 0] = 0;
GeomObject[3, 1] = 0.7f;
GeomObject[3, 2] = 0.3f;
// количество вершин рассматриваемого геометрического объекта
count_elements = 4;
// устанавливаем ось X по умолчанию
comboBox1.SelectedIndex = 0;
// начало визуализации (активируем таймер)
RenderTimer.Start();
}
Обработка события таймера:
/*http://esate.ru, Anvi*/
private void RenderTime_Tick( object sender, EventArgs e)
{
// обработка "тика" таймера - вызов функции отрисовки
Draw();
}
Функция отрисовки:
/*http://esate.ru, Anvi*/
// функция отрисовки
private void Draw()
{
// очистка буфера цвета и буфера глубины
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.glTranslated(0, 0, -7);
// поворачиваем ее на 15 градусов
Gl.glRotated(15, 1, 1, 0);
// помещаем состояние матрицы в стек матриц
Gl.glPushMatrix();
// начинаем отрисовку объекта
Gl.glBegin( Gl.GL_LINE_LOOP);
// геометрические данные мы берем из массива GeomObject
// рисуем основание с помощью зацикленной линии
Gl.glVertex3d(GeomObject[0, 0], GeomObject[0, 1], GeomObject[0, 2]);
Gl.glVertex3d(GeomObject[1, 0], GeomObject[1, 1], GeomObject[1, 2]);
Gl.glVertex3d(GeomObject[2, 0], GeomObject[2, 1], GeomObject[2, 2]);
// завершаем отрисовку примитивов
Gl.glEnd();
// рисуем линии от вершин основания к вершине пирамиды
Gl.glBegin( Gl.GL_LINES);
Gl.glVertex3d(GeomObject[0, 0], GeomObject[0, 1], GeomObject[0, 2]);
Gl.glVertex3d(GeomObject[3, 0], GeomObject[3, 1], GeomObject[3, 2]);
Gl.glVertex3d(GeomObject[1, 0], GeomObject[1, 1], GeomObject[1, 2]);
Gl.glVertex3d(GeomObject[3, 0], GeomObject[3, 1], GeomObject[3, 2]);
Gl.glVertex3d(GeomObject[2, 0], GeomObject[2, 1], GeomObject[2, 2]);
Gl.glVertex3d(GeomObject[3, 0], GeomObject[3, 1], GeomObject[3, 2]);
// завершаем отрисовку примитивов
Gl.glEnd();
// возвращаем состояние матрицы
Gl.glPopMatrix();
// возвращаем состояние матрицы
Gl.glPopMatrix();
// отрисовываем геометрию
Gl.glFlush();
// обновляем состояние элемента
AnT.Invalidate();
}
Что же касается самих преобразований, то они реализованы с помощью 3-х функций, которые будут активированы при обработке соответствующих клавиш.
W / S реализуют перемещение по указанной оси.
A / D – вращение вокруг указанной оси.
Z / X – масштабирование.
Обработка данных клавиш клавиатуры и обработка события элемента comboBox при изменении его значения для исключения перехвата им нажатия клавиш:
/*http://esate.ru, Anvi*/
// обработка событий от клавиатуры - нажатие (клавиша зажата!)
private void AnT_KeyDown( object sender, Key EventArgs e)
{
// Z и X отвечают за масштабирование
if(e.KeyCode == Keys.Z)
{
// вызов функции, в которой мы реализуем масштабирование - передаем коэффициент масштабирования и выбранную ось в окне программы
CreateZoom(1.05f, comboBox1.SelectedIndex);
}
if(e.KeyCode == Keys.X)
{
// вызов функции, в которой мы реализуем масштабирование - передаем коэффициент масштабирования и выбранную ось в окне программы
CreateZoom(0.95f, comboBox1.SelectedIndex);
}
// W и S отвечают за перенос
if(e.KeyCode == Keys.W)
{
// вызов функции, в которой мы реализуем перенос - передаем значение перемещения и выбранную ось в окне программы
CreateTranslate(0.05f, comboBox1.SelectedIndex);
}
if(e.KeyCode == Keys.S)
{
// вызов функции, в которой мы реализуем перенос - передаем значение перемещения и выбранную ось в окне программы
CreateTranslate(-0.05f, comboBox1.SelectedIndex);
} // A и D отвечают за поворот
if(e.KeyCode == Keys.A)
{
// вызов функции, в которой мы реализуем поворот - передаем значение для поворота и выбранную ось
CreateRotate(0.05f, comboBox1.SelectedIndex);
}
if(e.KeyCode == Keys.D)
{
CreateRotate(-0.05f, comboBox1.SelectedIndex);
}
}
// дополнительное событие для элемента comboBox - если произошло
// изменение его значения - установить фокус в элемент AnT
// чтобы избежать перехват события нажатия клавиши данным элементом
private void comboBox1_SelectedIndexChanged( object sender, EventArgs e)
{
// устанавливаем фокус в AnT
AnT.Focus();
}
Ну и самое главное – собственно реализация поворота, переноса и масштабирования:
/*http://esate.ru, Anvi*/
// функция масштабирования
private void CreateZoom( float coef, int os)
{
// создаем матрицу
float[,] Zoom3D = new float[3, 3]; Zoom3D[0, 0] = 1;
Zoom3D[1, 0] = 0;
Zoom3D[2, 0] = 0;
Zoom3D[0, 1] = 0;
Zoom3D[1, 1] = 1;
Zoom3D[2, 1] = 0;
Zoom3D[0, 2] = 0;
Zoom3D[1, 2] = 0;
Zoom3D[2, 2] = 1;
// устанавливаем коэффициент масштабирования для необходимой (выбранной и переданной в качестве параметра) оси
Zoom3D[os, os] = coef;
// вызываем функцию для выполнения умножения матриц, представляющих собой координаты вершин геометрического объекта
// на созданную в данной функции матрицу
multiply(GeomObject, Zoom3D);
}
// перенос
private void CreateTranslate( float translate, int os)
{
// в виду простоты данного алгоритма, мы упростили его обработку -
// достаточно прибавить изменение (перенос) в координатах объекта по выбранной и переданной оси
for ( int ax = 0; ax < count_elements; ax++)
{
// обновление координат (для выбранной оси)
GeomObject[ax, os] += translate;
}
}
// реализация поворота
private void CreateRotate( float angle, int os)
{
// массив, который будет содержать матрицу
float[,] Rotate3D = new float[3, 3];
// в зависимости от оси, матрицы будут кардинально различаться,
// поэтому создаем необходимую матрицу в зависимости от оси, используя switch
switch (os)
{
case 0: // вокруг оси Х
{
Rotate3D[0, 0] = 1;
Rotate3D[1, 0] = 0;
Rotate3D[2, 0] = 0;
Rotate3D[0, 1] = 0;
Rotate3D[1, 1] = (float)Math.Cos(angle);
Rotate3D[2, 1] = (float)-Math.Sin(angle);
Rotate3D[0, 2] = 0;
Rotate3D[1, 2] = (float)Math.Sin(angle);
Rotate3D[2, 2] = (float)Math.Cos(angle);
break;
}
case 1: // вокруг оси Y
{
Rotate3D[0, 0] = (float)Math.Cos(angle);
Rotate3D[1, 0] = 0;
Rotate3D[2, 0] = (float)Math.Sin(angle);
Rotate3D[0, 1] = 0;
Rotate3D[1, 1] = 1;
Rotate3D[2, 1] = 0;
Rotate3D[0, 2] = (float)-Math.Sin(angle);
Rotate3D[1, 2] = 0;
Rotate3D[2, 2] = (float)Math.Cos(angle);
break;
}
case 2: // вокруг оси Z
{
Rotate3D[0, 0] = (float)Math.Cos(angle);
Rotate3D[1, 0] = (float)-Math.Sin(angle);
Rotate3D[2, 0] = 0;
Rotate3D[0, 1] = (float)Math.Sin(angle);
Rotate3D[1, 1] = (float)Math.Cos(angle);
Rotate3D[2, 1] = 0;
Rotate3D[0, 2] = 0;
Rotate3D[1, 2] = 0;
Rotate3D[2, 2] = 1;
break;
}
}
// вызываем функцию для выполнения умножения матриц, представляющих собой координаты вершин геометрического объекта
// на созданную в данной функции матрицу
multiply(GeomObject, Rotate3D);
}
// функция умножения матриц
private void multiply(float[,] obj, float[,] matrix)
{
// временные переменные
float res_1, res_2, res_3;
// проходим циклом по всем координатам (представляющие собой матрицу A [x,y,z])
// и умножаем каждую матрицу на матрицу B (переданную)
// результат сразу заносим в массив геометрии
for ( int ax = 0; ax < count_elements; ax++)
{
res_1 = (obj[ax, 0] * matrix[0, 0] + obj[ax, 1] * matrix[0, 1] + obj[ax, 2] * matrix[0, 2]);
res_2 = (obj[ax, 0] * matrix[1, 0] + obj[ax, 1] * matrix[1, 1] + obj[ax, 2] * matrix[1, 2]);
res_3 = (obj[ax, 0] * matrix[2, 0] + obj[ax, 1] * matrix[2, 1] + obj[ax, 2] * matrix[2, 2]);
obj[ax, 0] = res_1;
obj[ax, 1] = res_2;
obj[ax, 2] = res_3;
}
}
Результат работы программы вы можете видеть на рисунке 6. Псевдо-трехмерный объект, управляемый нажатиями кнопок W,S,A,D,Z,X и установкой оси в окне формы.

Данный метод отлично демонстрирует математические основы геометрических преобразований. Его недостатком является преобразование геометрии самого объекта.
При использовании функций OpenGL в этом нет необходимости – нам достаточно сохранять в стек матриц текущее состояние, после чего производить преобразования с видовой матрицей, отрисовывать объект и возвращать сохраненную матрицу.
Для реализации самого перемещения, поворота и масштабирования нам будет достаточно использовать функции glTranslate**, glRotate**, glScale**. Мы рассмотрим это в следующих главах.
Комментарии (10):