Компьютерная графика, мультимедиа и игры на Visual C#

       

Изображение и управление поверхностями в трехмерном пространстве


Листинг 37.1. Код выше и в теле метода Form1_Load.

//Вводим функцию для изображаемой поверхности z = f(x, y):

double f1;

double f(double x, double y)

{

    //Параметры поверхности z = f(x, y)для эллипсоида:

    double a = 1.03;

    double b = 1.02;

    double c = 1.01;

    //Уравнение поверхности z = f(x, y) в виде эллипсоида:

    f1 = Math.Sqrt(c * c * (200 - x * x / (a * a) –

                                          y * y / (b * b)));

    return f1;



}

//Точка наблюдения:

myClassPoint3D myEye = new myClassPoint3D();

//Концы числового интервала области задания поверхности:

const int x_max = 20;

const int y_max = 20;

const int x_min = -10;

const int y_min = -10;

//Массив узловых точек:

myClassPoint3D[,] Points = new myClassPoint3D[x_max + 1, y_max + 1];

//Точки на осях координат:

myClassPoint3D[] Axes = new myClassPoint3D[4];

//Загрузка данных и их инициализация:

private void Form1_Load(object sender, EventArgs e)

{

    //Задаем координаты точки наблюдения:

    myEye = new myClassPoint3D(40, 20, 20);

      //Объявляем координаты точки:

      double x, y, z; int i, j;

      //В каждом (i,j)-м узле плоскости x,y

      //рассчитываем координату z точки пов-ти z = f(x, y):

      for (i = 0; i <= x_max; i++)

      {

            x = i + x_min;

            for (j = 0; j <= y_max; j++)

            {

                  y = j + y_min;

                  //Координата z точек пов-ти z = f(x, y):

                  z = f(x, y);

                  Points[i, j] = new myClassPoint3D(x, y, z);

            }

      }

      //Инициализация осей (axes) координат:

      Axes[0] = new myClassPoint3D(0, 0, 0);

      Axes[1] = new myClassPoint3D(30, 0, 0);

      Axes[2] = new myClassPoint3D(0, 30, 0);

      Axes[3] = new myClassPoint3D(0, 0, 30);

}

Чтобы мы могли рисовать заданную поверхность и оси координат внутри графического элемента PictureBox, в панели Properties (для этого элемента) на вкладке Events дважды щелкаем по имени события Paint. Появляется файл Form1.cs с шаблоном (метода pictureBox1_Paint), который после записи кода принимает такой вид.


Листинг 37.2. Методо pictureBox1_Paint для рисования поверхности.
private void pictureBox1_Paint(object sender,
PaintEventArgs e)
{
    // Масштабируем все графические объекты на PictureBox1.
    //Коэффициенты масштабирования:
    float M_1 = 29; float M_2 = 31;
    e.Graphics.ScaleTransform(pictureBox1.Width / M_1,
    -pictureBox1.Height / M_2, MatrixOrder.Append);
    float M_3 = 2; float M_4 = 2;
    e.Graphics.TranslateTransform(pictureBox1.Width / M_3,
    pictureBox1.Height / M_4, MatrixOrder.Append);
    //Задавая M_1, M_2, M_3, M_4 другие значения,
    //мы будем смещать поверхность по отношению к осям x,y,z.
    //Матрица преобразования (transformation matrix):
    myClassMatrix3D tr = CalculateTransformation();
    //Применяем матрицу преобразования к точкам:
    for (int x = 0; x <= x_max; x++)
        for (int y = 0; y <= y_max; y++)
            Points[x, y].Transformation(tr);
     //Объявляем индексы массива myArrayVCsharp[i, j]:
    int i, j;
    //Задаем границы индексов массива myArrayVCsharp[i, j]:
    int N_x = 2000; int N_y = 1;
    //Задаем массив myArrayVCsharp[i, j]переменных Single,
    //когда i = 0,1,2,3,...,N_x; j = 0,1,2,3,...,N_y:
    float[,] myArrayVCsharp = new float[N_x + 1, N_y + 1];
    //Первая, вторая и третья границы массива, разделяющие
    //линии поверхности, параллельные xz, yz, и оси:
    int N_1_myArrayCsharp = 0, N_2_myArrayCsharp = 0,
    N_3_myArrayCsharp = 0;
    //Рассчитываем элементы массива myArrayVCsharp[i, j]
    //для рисования линий поверхности,
    //параллельных плоскости координат xz:
    float x1, y1, x2, y2;
    Pen myPen = new Pen(Color.Black, 0);
    i = -2;//Задаем до циклов.
    for (int x = 0; x <= x_max; x++)
      {
            x2 =
            Convert.ToSingle(Points[x, 0].trans_coord[0]);
            y2 =
            Convert.ToSingle(Points[x, 0].trans_coord[1]);
            for (int y = 1; y <= y_max; y++)
            {
                  x1 = x2; y1 = y2;


                  x2 =
               Convert.ToSingle(Points[x, y].trans_coord[0]);
                  y2 =
               Convert.ToSingle(Points[x, y].trans_coord[1]);
                  //Можно рисовать линии поверхности и здесь:
            //e.Graphics.DrawLine(myPen, x1, y1, x2, y2);
            //Записываем в массив координат:
            i = i + 2;
            myArrayVCsharp[i, 0] = x1;
            myArrayVCsharp[i, 1] = y1;
            myArrayVCsharp[i + 1, 0] = x2;
            myArrayVCsharp[i + 1, 1] = y2;
            N_1_myArrayCsharp = i + 1; //1-я гран. в массиве.
            }
      }
    //Рассчитываем элементы массива myArrayVCsharp[i, j]
    //для рисования линий поверхности,
    //параллельных плоскости координат yz:
    i = N_1_myArrayCsharp - 1;
    for (int y = 0; y <= y_max; y++)
      {
            x2 =
            Convert.ToSingle(Points[0, y].trans_coord[0]);
            y2 =
            Convert.ToSingle(Points[0, y].trans_coord[1]);
            for (int x = 1; x <= x_max; x++)
        {
                  x1 = x2; y1 = y2;
                  x2 =
            Convert.ToSingle(Points[x, y].trans_coord[0]);
                  y2 =
            Convert.ToSingle(Points[x, y].trans_coord[1]);
                  //Можно рисовать линии поверхности и здесь:
                  //e.Graphics.DrawLine(myPen,x1,y1, x2, y2);
                  //Записываем в массив координат:
            i = i + 2;
            myArrayVCsharp[i, 0] = x1;
            myArrayVCsharp[i, 1] = y1;
            myArrayVCsharp[i + 1, 0] = x2;
            myArrayVCsharp[i + 1, 1] = y2;
            N_2_myArrayCsharp = i + 1; //2-я гран. в массиве.
        }
    }
    //Преобразовываем оси(axes):
    for (int k = 0; k <= 3; k++)
        Axes[k].Transformation(tr);
    // Рассчитываем элементы массива для рисования осей:
    Pen myPenAxes = new Pen(Color.Red, 0);
    i = N_2_myArrayCsharp - 1;
    x1 = Convert.ToSingle(Axes[0].trans_coord[0]);


    y1 = Convert.ToSingle(Axes[0].trans_coord[1]);
    for (int k = 1; k <= 3; k++)
      {
        x2 = Convert.ToSingle(Axes[k].trans_coord[0]);
        y2 = Convert.ToSingle(Axes[k].trans_coord[1]);
        // Можно рисовать оси координат и здесь:
        //e.Graphics.DrawLine(myPenAxes, x1, y1, x2, y2);
        //Записываем в массив координат:
        i = i + 2;
        myArrayVCsharp[i, 0] = x1;
        myArrayVCsharp[i, 1] = y1;
        myArrayVCsharp[i + 1, 0] = x2;
        myArrayVCsharp[i + 1, 1] = y2;
        N_3_myArrayCsharp = i + 1; //Число чисел в массиве.
      }
    //Рисуем при помощи массива
    //координат myArrayVCsharp[2000, 1].
    //Рисуем линии, параллельные плоскости xz:
    i = -2;
    for (int x = 0; x <= x_max; x++)
      {
        for (int y = 1; y <= y_max; y++)
            {
            i = i + 2;
            x1 = myArrayVCsharp[i, 0];
            y1 = myArrayVCsharp[i, 1];
            x2 = myArrayVCsharp[i + 1, 0];
            y2 = myArrayVCsharp[i + 1, 1];
            e.Graphics.DrawLine(myPen, x1, y1, x2, y2);
            }
      }
    //Рисуем линии, параллельные плоскости yz:
    i = N_1_myArrayCsharp - 1;
    for (int y = 0; y <= y_max; y++)
      {
            for (int x = 1; x <= x_max; x++)
            {
            i = i + 2;
            x1 = myArrayVCsharp[i, 0];
            y1 = myArrayVCsharp[i, 1];
            x2 = myArrayVCsharp[i + 1, 0];
            y2 = myArrayVCsharp[i + 1, 1];
            e.Graphics.DrawLine(myPen, x1, y1, x2, y2);
            }
      }
    //Рисуем три оси координат:
    i = N_2_myArrayCsharp - 1;
    Pen myPen2 = new Pen(Color.Red, 0);
    for (int k = 1; k <= 3; k++)
      {
        i = i + 2;
        x1 = myArrayVCsharp[i, 0];
        y1 = myArrayVCsharp[i, 1];
        x2 = myArrayVCsharp[i + 1, 0];
        y2 = myArrayVCsharp[i + 1, 1];
        e.Graphics.DrawLine(myPen2, x1, y1, x2, y2);
      }
    //Записываем массив myArrayVCsharp[2000, 2] в файл.


    // Создаем объект sw класса StreamWriter
    //для записи в файл по адресу D:\MyDocs\MyTest.txt.
    //Файл автоматически “опустошается”:
    StreamWriter sw =
            new StreamWriter("D:\\MyDocs\\MyTest.txt");
    //Каждый элемент myArrayVCsharp[i, j] записываем в файл
    //в виде отдельной строки при помощи процедуры WriteLine:
    for (i = 0; i <= N_x; i++)
            for (j = 0; j <= N_y; j++)
                sw.WriteLine(myArrayVCsharp[i, j]);
    sw.Close();
}//Конец метода pictureBox1_Paint.
Далее, чтобы мы могли управлять (например, вращать) объектами при помощи нажатия клавиш, в панели Properties (для элемента PictureBox) на вкладке Events мы должны дважды щелкнуть по имени события ProcessCmdKey, если это имя там имеется. Если же такого имени там нет, то мы полностью переписываем (или копируем с прилагаемого диска) нижеследующий метод.
Листинг 37.3. Метод ProcessCmdKey для вращения поверхности.
protected override bool ProcessCmdKey(
      ref System.Windows.Forms.Message msg,
      System.Windows.Forms.Keys keyData)
{
      //Задаем шаг поворота поверхности:
      const double delta_theta = Math.PI / 32;
      const double delta_phi = Math.PI / 16;
      //Вычисляем сферич-е коорд-ты (spherical coordinates)
      //точки наблюдения:
      double theta = Math.Atan2(myEye.orig_coord[1],
      myEye.orig_coord[0]);
      double r1 = Math.Sqrt(myEye.orig_coord[0] *
                                         myEye.orig_coord[0]
      + myEye.orig_coord[1] * myEye.orig_coord[1]);
      double r2 = Math.Sqrt(myEye.orig_coord[0] *
                                         myEye.orig_coord[0]
      + myEye.orig_coord[1] * myEye.orig_coord[1] +
        myEye.orig_coord[2] * myEye.orig_coord[2]);
      double phi = Math.Atan2(myEye.orig_coord[2], r1);
      //Корректируем углы phi и theta:
      switch (keyData)
      {
            case Keys.Left:
                  theta = theta - delta_theta;
                  break;


            case Keys.Up:
                  phi = phi - delta_phi;
                  if (phi < -Math.PI / 2) phi = -Math.PI / 2;
                  break;
            case Keys.Right:
                  theta = theta + delta_theta;
                  break;
            case Keys.Down:
                  phi = phi + delta_phi;
                  if (phi > Math.PI / 2) phi = Math.PI / 2;
                  break;
      }
      //Изменяем координаты точки наблюдения:
      myEye.orig_coord[0] = r1 * Math.Cos(theta);
      myEye.orig_coord[1] = r1 * Math.Sin(theta);
      myEye.orig_coord[2] = r2 * Math.Sin(phi);
      //Перерисовываем изображение внутри PictureBox1:
      pictureBox1.Invalidate();
      return true;
}
Ниже записываем следующий метод.
Листинг 37.4. Метод CalculateTransformation.
// Метод для вычисления матрицы преобразования
//для текущей точки наблюдения:
myClassMatrix3D CalculateTransformation()
{
      //Поворачиваем вокруг оси z,
      //чтобы точка наблюдения оказалась в плоскости y-z:
      myClassMatrix3D transformation1 =
                 myClassMatrix3D.GetZRotPointToYZ(myEye);
      //Поворачиваем вокруг оси x,
      //чтобы точка наблюдения оказалась на оси z:
      myClassMatrix3D transformation2 =
                 myClassMatrix3D.GetXRotPointToZ(myEye);
      //Проецируем по оси z, игнорируя координату z.
      //Для этого умножаем матрицы преобразования:
      return (transformation1.TimesMatrix(transformation2));
}
Ниже этого автоматически сгенерированного класса Form1:  
public partial class Form1 : System.Windows.Forms.Form
{
}
вводим два новых класса с методами преобразования систем координат.
Листинг 37.5. Два новых класса.
//Вводим класс с методами преобразования систем координат
//в трехмерном пространстве:
public class myClassPoint3D
{
      //Массив из 4-х элементов для первоначальных координат
      //(original coordinates); нулевой индекс используем:
      public double[] orig_coord = new double[4];


      //Массив для преобразованных координат:
      public double[] trans_coord = new double[4];
      //Создаем неинициализирующий конструктор этого класса:
      public myClassPoint3D()
      {
      }
      //Создаем инициализирующий конструктор:
      public myClassPoint3D( double x, double y, double z)
      {
        orig_coord[0] = x;
        orig_coord[1] = y;
        orig_coord[2] = z;
            double myScale;
            myScale = 1; //Масштаб.
            orig_coord[3] = myScale;
      }
      //Матрица преобразования (transformation matrix):
      bool normalize = true;
      public void Transformation(myClassMatrix3D matrix)
      {
            double value = 0;
            myClassPoint3D result = new myClassPoint3D();
            int i, j;
            for (i = 0; i <= 3; i++)
            {
            value = 0;
            for (j = 0; j <= 3; j++)
              value = value + orig_coord[j] * matrix.M[j, i];
              trans_coord[i] = value;
            }
        //Повторно нормализуем точку:
        if (normalize == true)
            {
            //После выхода из цикла value = trans_coord[4]:
            trans_coord[0] = trans_coord[0] / value;
            trans_coord[1] = trans_coord[1] / value;
            trans_coord[2] = trans_coord[2] / value;
            trans_coord[3] = 1;
            }
      }
} //Конец класса public class myClassPoint3D.
//Вводим класс с методами преобразования координат точки
//в трехмерном пространстве:
public class myClassMatrix3D
{
      //Матрица (matrix) в виде массива размера 4x4:
      public double[,] M = new double[4, 4];
      //Создаем неинициализирующий конструктор этого класса:
      public myClassMatrix3D()
      {
      }
      //Создаем конструктор, инициализирующий матрицу:
      public myClassMatrix3D(
      double m00, double m01, double m02, double m03,
      double m10, double m11, double m12, double m13,
      double m20, double m21, double m22, double m23,


      double m30, double m31, double m32, double m33)
      {
            M[0, 0] = m00;    M[0, 1] = m01;
            M[0, 2] = m02;    M[0, 3] = m03;
            M[1, 0] = m10;    M[1, 1] = m11;
            M[1, 2] = m12;    M[1, 3] = m13;
            M[2, 0] = m20;    M[2, 1] = m21;
            M[2, 2] = m22;    M[2, 3] = m23;
            M[3, 0] = m30;    M[3, 1] = m31;
            M[3, 2] = m32;    M[3, 3] = m33;
      }
      //Метод для умножения матрицы на матрицу справа:
      public myClassMatrix3D TimesMatrix(
            myClassMatrix3D right_matrix)
      {
            myClassMatrix3D result = new myClassMatrix3D();
            double value; int i, j, k;
            for (i = 0; i <= 3; i++)
            {
                  for (j = 0; j <= 3; j++)
                  {
                        value = 0;
                        for (k = 0; k <= 3; k++)
                        {
                             value = value + M[i, k] *
                                   right_matrix.M[k, j];
                        }
                        result.M[i, j] = value;
                  }
            }
            return result;
      }
      //Метод для поворота вокруг оси z
      //до точки в y-z плоскости:
      public static myClassMatrix3D GetZRotPointToYZ(
            myClassPoint3D pt)
      {
            double R = Math.Sqrt(
                  pt.orig_coord[0] * pt.orig_coord[0] +
                  pt.orig_coord[1] * pt.orig_coord[1]);
            double stheta = pt.orig_coord[0] / R;
            double ctheta = pt.orig_coord[1] / R;
            return new myClassMatrix3D(
            ctheta, stheta, 0, 0,
            -stheta, ctheta, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1);
      }
      //Метод для поворота вокруг оси x до точки на оси z:
      public static myClassMatrix3D
      GetXRotPointToZ(myClassPoint3D pt)
      {
            double R1 = Math.Sqrt(
                  pt.orig_coord[0] * pt.orig_coord[0] +
                  pt.orig_coord[1] * pt.orig_coord[1]);
            double R2 = Math.Sqrt(
                  pt.orig_coord[0] * pt.orig_coord[0] +
                  pt.orig_coord[1] * pt.orig_coord[1] +
                  pt.orig_coord[2] * pt.orig_coord[2]);
            double sphi = -R1 / R1;
            double cphi = -pt.orig_coord[2] / R2;
            return new myClassMatrix3D(
            1, 0, 0, 0,
            0, cphi, sphi, 0,
            0, -sphi, cphi, 0,
            0, 0, 0, 1);
      }
} //Конец класса class myClassMatrix3D.

Содержание раздела