9.2 Алгоритмы геометрических преобразований - реализация на OpenGl.

Необходимые знания:
Вам могут понадобится следующие статьи:
Создайте окно программы и разместите на ней элемент openglsimplecontrol, как показано на рисунке 1, после чего установите его размеры 500х500.

Переименуйте данный объект, дав ему имя AnT.
Уроки OpenGL + C#: Заготовка окна программы Рисунок 1. Заготовка окна программы.
Справа от данного элемента разместите элемент comboBox, после чего в его свойствах установите значение параметра DropDownStyle = DropDownList. После этого выпадающие элементы перестанут быть доступными для редактирования. Затем измените элементы Items, как показано на рисунке 2.
Уроки OpenGL + C#: Заполнение элементов ListBox Рисунок 2. Заполнение элементов ListBox.
Также не забудьте установить ссылки на используемые библиотеки Tao (рис. 3).
Уроки OpenGL + C#: Ссылки на используемые библиотеки Рисунок 3. Ссылки на используемые библиотеки.
Для обработки событий клавиатуры выделите объект AnT, перейдите к его свойствам, после чего перейдите к настройке событий и добавьте обработку событий мыши, как показано на рисунке 4.
Уроки OpenGL + C#: Опрос клавиатуры Рисунок 4. Опрос клавиатуры.
Для реализации визуализации будет использоваться таймер – после инициализации окна он будет генерировать событие, называемое тиком таймера, раз в 30 миллисекунд. Добавьте элемент таймер, переименуйте экземпляр в RenderTimer и установите время тика 30 миллисекунд (как показано на рисунке 5), а также добавьте ему функцию обработки события таймера.
Уроки OpenGL + C#: Настройка таймера Рисунок 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 + C#: Результат работы программы Рисунок 6. Результат работы программы.
Данный метод отлично демонстрирует математические основы геометрических преобразований. Его недостатком является преобразование геометрии самого объекта.

При использовании функций OpenGL в этом нет необходимости – нам достаточно сохранять в стек матриц текущее состояние, после чего производить преобразования с видовой матрицей, отрисовывать объект и возвращать сохраненную матрицу.

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

Нет доступа к просмотру комментариев.

^