10.2 Формирование тел вращения - реализация алгоритма на OpenGl.

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

Переименуйте данный объект, дав ему имя AnT.
Уроки OpenGL + C#: Окно разрабатываемой программы Рисунок 1. Окно разрабатываемой программы.
Справа от данного элемента разместите элемент comboBox, после чего в его свойствах установите значение параметра DropDownStyle = DropDownList. После этого выпадающие элементы перестанут быть доступными для редактирования. Затем измените элементы Items, как показано на рисунке 2.
Уроки OpenGL + C#: Заполнение элементов ListBox Рисунок 2. Заполнение элементов ListBox.
Помимо этого установите элемент trackBar в окно формы. Перейдя к его свойствам, установите rientation равным Vertical. Максимальный диапазон установите равным 100.

Также не забудьте установить ссылки на используемые библиотеки Tao (рис. 3).
Уроки OpenGL + C#: Подключение необходимых библиотек Рисунок 3. Подключение необходимых библиотек.
Для реализации визуализации будет использоваться таймер – после инициализации окна он будет генерировать событие, называемое тиком таймера, раз в 30 миллисекунд. Добавьте элемент таймер, переименуйте экземпляр в RenderTimer и установите время тика 30 миллисекунд (как показано на рисунке 5), а также добавьте ему событие для обработки тика.
Обязательно включите таймер (свойство enabled должно быть установлено в true). Событие, которое будет выполняться при очередном тике таймера, будет вызывать функцию отрисовки сцены.
Уроки OpenGL + C#: Настройка таймера Рисунок 4. Настройка таймера.
Инициализация окна и OpenGl происходит так же, как и в предыдущих проектах.

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

/*http://esate.ru, Anvi*/

private int lastX, lastY; private float rot_1, rot_2;

private double[,] GeometricArray = new double[64,3];
private double[, ,] ResaultGeometric = new double[64, 64, 3];

private int init_mode = 0;

private int count_elements = 0;


private double Angle = 2*Math.PI / 64;
private int Iter = 64;

public Form1()
{

  InitializeComponent();
  AnT.InitializeContexts();

}


Как и раньше функция Form1_Load отвечает за инициализацию OpenGL. Но теперь здесь еще происходит построение массива геометрии тела, построенного вращением на основе заданного заранее массива GeometricArray. Form1_Load - это функция обработчик события Load для формы, на которой мы размещаем элементы управления.

Код этой функции максимально подробно комментирован:

/*http://esate.ru, Anvi*/

private void Form1_Load( object sender, EventArgs e)
{

  // инициализация Glut 
  Glut.glutInit();
  Glut.glutInitDisplayMode( Glut.GLUT_RGB | Glut.GLUT_DOUBLE | Glut.GLUT_DEPTH);

  // очистка окна 
  Gl.glClearColor(255, 255, 255, 1);

  // установка порта вывода в соответствии с размерами элемента anT 
  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();

  // настройка параметров OpenGL для визуализации 
  Gl.glEnable( Gl.GL_DEPTH_TEST);
  Gl.glEnable( Gl.GL_LIGHTING);
  Gl.glEnable( Gl.GL_LIGHT0);

  // количество элементов последовательности геометрии, на основе которых будет строится тело вращения 
  count_elements = 8;

  // непосредственное заполнение точек. 
  // после изменения данной геометрии мы сразу получим новое тело вращения. 
  GeometricArray[0,0] = 0;
  GeometricArray[0,1] = 0;
  GeometricArray[0,2] = 0;

  GeometricArray[1,0] = 0.7;
  GeometricArray[1,1] = 0;
  GeometricArray[1,2] = 1;

  GeometricArray[2, 0] = 1.3;
  GeometricArray[2, 1] = 0;
  GeometricArray[2, 2] = 2;

  GeometricArray[3,0] = 1.0;
  GeometricArray[3,1] = 0;
  GeometricArray[3,2] = 3;

  GeometricArray[4, 0] = 0.5;
  GeometricArray[4, 1] = 0;
  GeometricArray[4, 2] = 4;

  GeometricArray[5, 0] = 3;
  GeometricArray[5, 1] = 0;
  GeometricArray[5, 2] = 6;

  GeometricArray[6, 0] = 1;
  GeometricArray[6, 1] = 0;
  GeometricArray[6, 2] = 7;

  GeometricArray[7, 0] = 0;
  GeometricArray[7, 1] = 0;
  GeometricArray[7, 2] = 7.2f;

  // по умолчанию мы будем отрисовывать фигуру в режиме GL_POINTS 
  comboBox1.SelectedIndex = 0;

  // построение геометрии тела вращения 
  // принцип сводится к двум циклам: на основе первого перебираются 
  // вершины в геометрической последовательности, 
  // а второй использует параметр Iter и производит поворот последней линии геометрии вокруг центра тела вращения 
  // при этом используется заранее определенный угол angle, который определяется как 2*Pi / количество медиан объекта 
  // за счет выполнения этого алгоритма получается набор вершин, описывающих оболочку тела вращения. 
  // остается только соединить эти точки в режиме рисования примитивов для получения 
  // визуализированного объекта 

  // цикл по последовательности точек кривой, на основе которой будет построено тело вращения 
  for ( int ax = 0; ax < count_elements; ax++)
  { 
    // цикла по медианам объекта, заранее определенным в программе 
    for ( int bx = 0; bx < Iter; bx++)
    {
      // для всех (bx > 0) элементов алгоритма используется предыдущая построенная последовательность 
      // для ее поворота на установленный угол 
      if (bx > 0)
      {
        double new_x = ResaultGeometric[ax, bx - 1, 0] * Math.Cos(Angle) - ResaultGeometric[ax, bx - 1, 1] * Math.Sin(Angle);
        double new_y = ResaultGeometric[ax, bx - 1, 0] * Math.Sin(Angle) + ResaultGeometric[ax, bx - 1, 1] * Math.Cos(Angle);
        ResaultGeometric[ax, bx, 0] = new_x;
        ResaultGeometric[ax, bx, 1] = new_y;
        ResaultGeometric[ax, bx, 2] = GeometricArray[ax, 2];
      }
      else // для построения первой медианы мы используем начальную кривую, описывая ее нулевым значением угла поворота 
      {
        double new_x = GeometricArray[ax, 0] *Math.Cos(0) - GeometricArray[ax, 1] * Math.Sin(0);
        double new_y = GeometricArray[ax, 1] *Math.Sin(0) + GeometricArray[ax, 1] * Math.Cos(0);
        ResaultGeometric[ax, bx, 0] = new_x;
        ResaultGeometric[ax, bx, 1] = new_y;
        ResaultGeometric[ax, bx, 2] = GeometricArray[ax, 2];
      }
    }

  }

  // активация таймера 
  RenderTimer.Start();

}


Итак, геометрия объекта построена, остается обработать сообщение таймера для вызова функции отрисовки, а также реализовать непосредственно функцию Draw.

В функции Draw мы рассмотрим три вида визуализации, которые будут использованы в зависимости от установленного режима в элементе comboBox.

Визуализация с помощью точек – самая простая. Визуализация с помощью линий или полигонов – уже сложнее.

Постарайтесь максимально тщательно разобрать алгоритм, чтобы понять суть его работы.

/*http://esate.ru, Anvi*/

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

// функция отрисовки сцены 
private void Draw()
{ 
  // два параметра, которые мы будем использовать для непрерывного вращения сцены вокруг 2 координатных осей 
  rot_1++;
  rot_2++; // очистка буфера цвета и буфера глубины 
  Gl.glClear( Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
  Gl.glClearColor(255, 255, 255, 1);
  // очищение текущей матрицы 
  Gl.glLoadIdentity();

  // установка положения камеры (наблюдателя). Как видно из кода, 
  // дополнительно на положение наблюдателя по оси Z влияет значение, 
  // установленное в ползунке, доступном для пользователя. 

  // таким образом, при перемещении ползунка, наблюдатель будет отдаляться или приближаться к объекту наблюдения 
  Gl.glTranslated(0, 0, -7 -trackBar1.Value);
  // 2 поворота (углы rot_1 и rot_2) 
  Gl.glRotated(rot_1, 1, 0, 0);
  Gl.glRotated(rot_2, 0, 1, 0);

  // устанавливаем размер точек, равный 5 
  Gl.glPointSize(5.0f);

  // условие switch определяет установленный режим отображения, на основе выбранного пункта элемента 
  // comboBox, установленного в форме программы 
  switch (comboBox1.SelectedIndex)
  { 
    case 0: // отображение в виде точек 
    {
      // режим вывода геометрии - точки 
      Gl.glBegin( Gl.GL_POINTS);

      // выводим всю ранее просчитанную геометрию объекта 
      for ( int ax = 0; ax < count_elements; ax++)
      {
        for ( int bx = 0; bx < Iter; bx++)
        {
          // отрисовка точки 
          Gl.glVertex3d(ResaultGeometric[ax, bx, 0], ResaultGeometric[ax, bx, 1], ResaultGeometric[ax, bx, 2]);
        }
      }
    
      // завершаем режим рисования 
      Gl.glEnd();
      break ;
    }
    case 1: // отображение объекта в сеточном режиме, используя режим GL_LINES_STRIP 
    {

      // устанавливаем режим отрисовки линиями (последовательность линий) 
      Gl.glBegin( Gl.GL_LINE_STRIP);
      for ( int ax = 0; ax < count_elements; ax++)
      {
        for ( int bx = 0; bx < Iter; bx++)
        {

          Gl.glVertex3d(ResaultGeometric[ax, bx, 0], ResaultGeometric[ax, bx, 1], ResaultGeometric[ax, bx, 2]);
          Gl.glVertex3d(ResaultGeometric[ax + 1, bx, 0], ResaultGeometric[ax + 1, bx, 1], ResaultGeometric[ax + 1, bx, 2]);

          if (bx + 1 < Iter - 1)
          {
            Gl.glVertex3d(ResaultGeometric[ax + 1, bx + 1, 0], ResaultGeometric[ax + 1, bx + 1, 1], ResaultGeometric[ax + 1, bx + 1, 2]);
          }
          else
          {
            Gl.glVertex3d(ResaultGeometric[ax + 1, 0, 0], ResaultGeometric[ax + 1, 0, 1], ResaultGeometric[ax + 1, 0, 2]);
          }

        }
      }

      Gl.glEnd();
      break ;

    }
    case 2: // отрисовка оболочки с расчетом нормалей для корректного затенения граней объекта 
    {

      Gl.glBegin( Gl.GL_QUADS); // режим отрисовки полигонов состоящих из 4 вершин 
      for ( int ax = 0; ax < count_elements; ax++)
      {
        for ( int bx = 0; bx < Iter; bx++)
        {
          // вспомогательные переменные для более наглядного использования кода при расчете нормалей 
          double x1 = 0, x2 = 0, x3 = 0, x4 = 0, y1 = 0, y2 = 0, y3 = 0, y4 = 0, z1 = 0, z2 = 0, z3 = 0, z4 = 0;

          // первая вершина 
          x1 = ResaultGeometric[ax, bx, 0];
          y1 = ResaultGeometric[ax, bx, 1];
          z1 = ResaultGeometric[ax, bx, 2];

          if (ax + 1 < count_elements) // если текущий ax не последний 
          {

            // берем следующую точку последовательности 
            x2 = ResaultGeometric[ax + 1, bx, 0];
            y2 = ResaultGeometric[ax + 1, bx, 1];
            z2 = ResaultGeometric[ax + 1, bx, 2];

            if (bx + 1 < Iter - 1) // если текущий bx не последний 
            {
              // берем следующую точку последовательности и следующий медиан 
              x3 = ResaultGeometric[ax + 1, bx + 1, 0];
              y3 = ResaultGeometric[ax + 1, bx + 1, 1];
              z3 = ResaultGeometric[ax + 1, bx + 1, 2];

              // точка, соответствующая по номеру, только на соседнем медиане 
              x4 = ResaultGeometric[ax, bx + 1, 0];
              y4 = ResaultGeometric[ax, bx + 1, 1];
              z4 = ResaultGeometric[ax, bx + 1, 2];
            }
            else
            {
              // если это последний медиан, то в качестве след. мы берем начальный (замыкаем геометрию фигуры) 
              x3 = ResaultGeometric[ax + 1, 0, 0];
              y3 = ResaultGeometric[ax + 1, 0, 1];
              z3 = ResaultGeometric[ax + 1, 0, 2];

              x4 = ResaultGeometric[ax, 0, 0];
              y4 = ResaultGeometric[ax, 0, 1];
              z4 = ResaultGeometric[ax, 0, 2];
            }

          }
          else // данный элемент ax последний, следовательно, мы будем использовать начальный (нулевой) вместо данного ax 
          {
            // следующей точкой будет нулевая ax 
            x2 = ResaultGeometric[0, bx, 0];
            y2 = ResaultGeometric[0, bx, 1];
            z2 = ResaultGeometric[0, bx, 2];

            if (bx + 1 < Iter - 1)
            {
              x3 = ResaultGeometric[0, bx + 1, 0];
              y3 = ResaultGeometric[0, bx + 1, 1];
              z3 = ResaultGeometric[0, bx + 1, 2];

              x4 = ResaultGeometric[ax, bx + 1, 0];
              y4 = ResaultGeometric[ax, bx + 1, 1];
              z4 = ResaultGeometric[ax, bx + 1, 2];
            }
            else
            {
              x3 = ResaultGeometric[0, 0, 0];
              y3 = ResaultGeometric[0, 0, 1];
              z3 = ResaultGeometric[0, 0, 2];

              x4 = ResaultGeometric[ax, 0, 0];
              y4 = ResaultGeometric[ax, 0, 1];
              z4 = ResaultGeometric[ax, 0, 2];
            }

          }


          // переменные для расчета нормали 
          double n1 = 0, n2 = 0, n3 = 0;

          // нормаль будем рассчитывать как векторное произведение граней полигона 
          // для нулевого элемента нормаль мы будем считать немного по-другому. 

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

          if (ax == 0) // при расчете нормали для ax мы будем использовать точки 1,2,3 
          {
            n1 = (y2 - y1) * (z3 - z1) - (y3 - y1) * (z2 - z1);
            n2 = (z2 - z1) * (x3 - x1) - (z3 - z1) * (x2 - x1);
            n3 = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1);
          }
          else // для остальных - 1,3,4 
          {
            n1 = (y4 - y3) * (z1 - z3) - (y1 - y3) * (z4 - z3);
            n2 = (z4 - z3) * (x1 - x3) - (z1 - z3) * (x4 - x3);
            n3 = (x4 - x3) * (y1 - y3) - (x1 - x3) * (y4 - y3);
          }

          // если не включен режим GL_NORMILIZE, то мы должны в обязательном порядке 
          // произвести нормализацию вектора нормали перед тем как передать информацию о нормали 
          double n5 = (double)Math.Sqrt(n1 * n1 + n2 * n2 + n3 * n3);
          n1 /= (n5 + 0.01);
          n2 /= (n5 + 0.01);
          n3 /= (n5 + 0.01);

          // передаем информацию о нормали 
          Gl.glNormal3d(-n1, -n2, -n3);

          // передаем 4 вершины для отрисовки полигона 
          Gl.glVertex3d(x1, y1, z1);
          Gl.glVertex3d(x2, y2, z2);
          Gl.glVertex3d(x3, y3, z3);
          Gl.glVertex3d(x4, y4, z4);

        }



        }

        // завершаем выбранный режим рисования полигонов 
        Gl.glEnd();
        break ;

    }

  }

  // возвращаем сохраненную матрицу 
  Gl.glPopMatrix();

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

  // обновляем элемент AnT 
  AnT.Invalidate();

}


На рисунках 5, 6, 7 представлены результаты работы программы: вращающееся тело с различными режимами отрисовки геометрии.
Уроки OpenGL + C#: Пример построения объекта в режиме точек Рисунок 5. Пример построения объекта в режиме точек.
Уроки OpenGL + C#: Пример построения объекта в режиме линий Рисунок 6. Пример построения объекта в режиме линий.
Уроки OpenGL + C#: Пример построения объекта в режиме полигонов Рисунок 7. Пример построения объекта в режиме полигонов.
В следующей главе мы рассмотрим создание 3D-объектов, реализация которых уже имеется в библиотеке GLUT, а также функции OpenGL для изменения их положения в пространстве и масштабировании без изменений в структуре геометрии объектов.
Прикрепленные файлы для скачивания:

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

^