Создание класса Камеры (C#, TaoFramework)

Блоговая публикация пользователя: OniKy Эта публикация была перенесена из личного блога пользователя в общие разделы уровок сайта.

Создание класса Камеры (C#, TaoFramework)

В этом уроке вы научитесь создавать класс "камера".

Для начала необходимо разобраться с командой библиотеки Glu - GluLookAt. В нашем случаи Glu.gluLookAt. Она принимает три набора аргументов, которые задают точку наблюдения, прицельную точку (точку, на которую направлена камера) и направление, которое следует считать верхним.

Уроки OpenGL различных тематик: Аргументы для Glu.gluLookAt Рисунок 1. Аргументы для Glu.gluLookAt.
Пример:

Код:
/*http://esate.ru, OniKy*/

          Glu.gluLookAt(Position.x, Position.y, Position.z, // Позиция самой камеры
                        View.x, View.y, View.z,             // Направление, куда мы смотрим
                        Up.x, Up.y, Up.z);                   // Верх камеры


Вроде бы все просто. Теперь создадим отдельный класс в нашем приложении - Camera. Сразу подключим пространства и создадим Vertex3D - структуру, где будут храниться координаты X, Y, Z векторов.

Код:
/*http://esate.ru, OniKy*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Tao.FreeGlut;
using Tao.OpenGl;
using Tao.Platform.Windows;

namespace Engine
{
    class Camera
    {
        private struct Vector3D
        {
            public float x, y, z;
        };
    }
}


Объявим вектора которые будем использовать:
Код:
/*http://esate.ru, OniKy*/

        private Vector3D mPos;   // Вектор позиции камеры
        private Vector3D mView;   // Направление, куда смотрит камера
        private Vector3D mUp;     // Вектор верхнего направления
        private Vector3D mStrafe; // Вектор для стрейфа (движения влево и вправо) камеры


Далее мы создадим метод, который будет нам возвращать перпендикулярный вектор от трех переданных векторов. Два любых вектора образуют плоскость, от которой мы и ищем перпендикуляр. Это все нам понадобится для того, чтобы мы смогли реализовать Стрейф.

Код:
/*http://esate.ru, OniKy*/

        private Vector3D Cross(Vector3D vV1, Vector3D vV2, Vector3D vVector2)
        {
            Vector3D vNormal;
            Vector3D vVector1;
            vVector1.x = vV1.x - vV2.x;
            vVector1.y = vV1.y - vV2.y;
            vVector1.z = vV1.z - vV2.z;

             // Если у нас есть 2 вектора (вектор взгляда и вертикальный вектор), 
             // У нас есть плоскость, от которой мы можем вычислить угол в 90 градусов.
             // Рассчет cross'a прост, но его сложно запомнить с первого раза. 
             // Значение X для вектора = (V1.y * V2.z) - (V1.z * V2.y)
            vNormal.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y));

            // Значение Y = (V1.z * V2.x) - (V1.x * V2.z)
            vNormal.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z));

            // Значение Z = (V1.x * V2.y) - (V1.y * V2.x)
            vNormal.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x));

             // *ВАЖНО* Вы не можете менять этот порядок, иначе ничего не будет работать.
             // Должно быть именно так, как здесь. Просто запомните, если вы ищите Х, вы не
             // используете значение X двух векторов, и то же самое для Y и Z. Заметьте,
             // вы рассчитываете значение из двух других осей и никогда из той же самой.

             // Итак, зачем всё это? Нам нужно найти ось, вокруг которой вращаться. Вращение камеры
             // влево и вправо простое - вертикальная ось всегда (0, 1, 0). 
             // Вращение камеры вверх и вниз отличается, так как оно происходит вне 
             // глобальных осей. Достаньте себе книгу по линейной алгебре, если у вас 
             // её ещё нет, она вам пригодится.

             // вернем результат.
            return vNormal;
        }


Далее создадим два метода который будут работать над векторами - Magnitude и Normalize.

Magnitude - возвращает величину вектора.

Код:
/*http://esate.ru, OniKy*/

        private float Magnitude(Vector3D vNormal)
        {
            // Это даст нам величину нашей нормали, 
            // т.е. длину вектора. Мы используем эту информацию для нормализации
            // вектора. Вот формула: magnitude = sqrt(V.x^2 + V.y^2 + V.z^2)   где V - вектор.

            return (float)Math.Sqrt((vNormal.x * vNormal.x) +
                    (vNormal.y * vNormal.y) +
                    (vNormal.z * vNormal.z));
        }


Normalize - возвращает нормализированный вектор, длинна которого равна единице, а это делает все рассчеты проще.

Код:
/*http://esate.ru, OniKy*/

private Vector3D Normalize(Vector3D vVector)
        {
             // Вы спросите, для чего эта ф-я? Мы должны убедиться, что наш вектор нормализирован.
             // Вектор нормализирован - значит, его длинна равна 1. Например,
             // вектор (2, 0, 0) после нормализации будет (1, 0, 0).

             // Вычислим величину нормали
            float magnitude = Magnitude(vVector);

             // Теперь у нас есть величина, и мы можем разделить наш вектор на его величину.
             // Это сделает длинну вектора равной единице, так с ним будет легче работать.
            vVector.x = vVector.x / magnitude;
            vVector.y = vVector.y / magnitude;
            vVector.z = vVector.z / magnitude;

            return vVector;
        }

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

Код:
/*http://esate.ru, OniKy*/

        public void Position_Camera(float pos_x, float pos_y, float pos_z,
                float view_x, float view_y, float view_z,
                float up_x, float up_y, float up_z)
        {
            mPos.x = pos_x; // Позиция камеры
            mPos.y = pos_y; //
            mPos.z = pos_z; //
            mView.x = view_x; // Куда смотрит, т.е. взгляд
            mView.y = view_y; //
            mView.z = view_z; //
            mUp.x = up_x; // Вертикальный вектор камеры
            mUp.y = up_y; //
            mUp.z = up_z; //
        }

Теперь дадим возможность крутить камеру вокруг своей оси, как при виде от первого лица. Передавая в метод переменную speed, будем управлять силой поворота, а также направлением. Если положительная переменная, то вправо, а отрицательная, как вы уже догадались, налево.

Код:
/*http://esate.ru, OniKy*/

        public void Rotate_View(float speed)
        {
            Vector3D vVector; // Полчим вектор взгляда
            vVector.x = mView.x - mPos.x;
            vVector.y = mView.y - mPos.y;
            vVector.z = mView.z - mPos.z;

            

            mView.z = (float)(mPos.z + Math.Sin(speed) * vVector.x + Math.Cos(speed) * vVector.z);
            mView.x = (float)(mPos.x + Math.Cos(speed) * vVector.x - Math.Sin(speed) * vVector.z);
        }

Перейдем к созданию вида от третьего лица. Чтобы реализовать вращение камеры, мы будем использовать axis-angle вращение.

Помните, что формулы для расчета вращения не очень просты, но занимают немного кода. Axis-angle вращение позволяет нам вращать точку в пространстве вокруг нужной оси. Это значит, что мы можем взять нашу точку взгляда (m_vView) и вращать вокруг нашей позиции. Чтобы лучше понять следующие рассчеты, советую вам посмотреть детальное описание: http://astronomy.swin.edu.au/~pbourke/geometry/rotate/

Также это функция реализована, чтобы в переменную angle вы передавали угол в градусах.

Код:
/*http://esate.ru, OniKy*/

public void Rotate_Position(float angle, float x, float y, float z)
        {
            mPos.x = mPos.x - mView.x;
            mPos.y = mPos.y - mView.y;
            mPos.z = mPos.z - mView.z;

            Vector3D vVector = mPos;
            Vector3D AVector;

            float SinA = (float)Math.Sin(Math.PI * angle / 180.0);
            float CosA = (float)Math.Cos(Math.PI * angle / 180.0);

             // Найдем новую позицию X для вращаемой точки 
            AVector.x = (CosA + (1 - CosA) * x * x) * vVector.x;
            AVector.x += ((1 - CosA) * x * y - z * SinA) * vVector.y;
            AVector.x += ((1 - CosA) * x * z + y * SinA) * vVector.z;

             // Найдем позицию Y 
            AVector.y = ((1 - CosA) * x * y + z * SinA) * vVector.x;
            AVector.y += (CosA + (1 - CosA) * y * y) * vVector.y;
            AVector.y += ((1 - CosA) * y * z - x * SinA) * vVector.z;

             // И позицию Z 
            AVector.z = ((1 - CosA) * x * z - y * SinA) * vVector.x;
            AVector.z += ((1 - CosA) * y * z + x * SinA) * vVector.y;
            AVector.z += (CosA + (1 - CosA) * z * z) * vVector.z;

            mPos.x = mView.x + AVector.x;
            mPos.y = mView.y + AVector.y;
            mPos.z = mView.z + AVector.z;
        }

Займемся перемещением камеры. В первую очередь, как вы поняли, нам нужна возможность перемещать камеру вперед и назад в зависимости от того, куда смотрит камера. Следующий метод позволит нам это делать.

Код:
/*http://esate.ru, OniKy*/

        public void Move_Camera(float speed) // Задаем скорость
        {
            Vector3D vVector; // Получаем вектор взгляда
            vVector.x = mView.x - mPos.x;
            vVector.y = mView.y - mPos.y;
            vVector.z = mView.z - mPos.z;

            vVector.y = 0.0f; // Это запрещает камере подниматься вверх
            vVector = Normalize(vVector);

            mPos.x += vVector.x * speed;
            mPos.z += vVector.z * speed;
            mView.x += vVector.x * speed;
            mView.z += vVector.z * speed;
        }

Сразу напишем Стрейф:

Код:
/*http://esate.ru, OniKy*/

        public void Strafe(float speed)
        {
             // добавим вектор стрейфа к позиции
            mPos.x += mStrafe.x * speed;
            mPos.z += mStrafe.z * speed;

             // Добавим теперь к взгляду
            mView.x += mStrafe.x * speed;
            mView.z += mStrafe.z * speed;
        }

Но для стрейфа нужно вычислять перпендикуляр каждый момент времени. Для этого напишем следующий метод:

Код:
/*http://esate.ru, OniKy*/

        public void update()
        {
            Vector3D vCross = Cross(mView, mPos, mUp);

             // Нормализуем вектор стрейфа
            mStrafe = Normalize(vCross);
        }

Теперь добавим возможность камеры смотреть вверх и вниз.

Код:
/*http://esate.ru, OniKy*/

        public void upDown(float speed)
        {
            mPos.y += speed;
        }

Нужен метод, который будет обновлять взгляд и позицию камеры:

Код:
/*http://esate.ru, OniKy*/

        public void Look()
        {
            Glu.gluLookAt(mPos.x, mPos.y, mPos.z, // Ранее упомянутая команда 
                          mView.x, mView.y, mView.z,
                          mUp.x, mUp.y, mUp.z);
        }

Итак, на всякий случай допишем методы, с помощью которых можно получать позиции в пространстве как и камеры, так и взгляда:

Код:
/*http://esate.ru, OniKy*/

        public double getPosX() // Возвращает позицию камеры по Х
        {
            return mPos.x;
        }

        public double getPosY() // Возвращает позицию камеры по Y
        {
            return mPos.y;
        }

        public double getPosZ() // Возвращает позицию камеры по Z
        {
            return mPos.z;
        }

        public double getViewX() // Возвращает позицию взгляда по Х
        {
            return mView.x;
        }

        public double getViewY() // Возвращает позицию взгляда по Y
        {
            return mView.y;
        }

        public double getViewZ() // Возвращает позицию взгляда по Z
        {
            return mView.z;
        }

Ну вот и все. Класс написан и может быть использован.

Как же его применять?

Создадим новый проект. Объявим нужные нам библиотеки и пространства имен, добавим SimpleOpenGlControl и переименуем его в AnT. Также добавим Timer. Сразу объявим новый наш класс:

Код:
/*http://esate.ru, OniKy*/

Camera cam = new Camera();

Напишем метод инициализации OpenGl:

Код:
/*http://esate.ru, OniKy*/

private void InitGL()
        {
            Glut.glutInit();
            Glut.glutInitDisplayMode(Glut.GLUT_RGB | Glut.GLUT_DOUBLE | Glut.GLUT_DEPTH);

            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);
            Gl.glEnable(Gl.GL_LIGHTING);
            Gl.glEnable(Gl.GL_LIGHT0);

            Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
            Gl.glHint(Gl.GL_LINE_SMOOTH_HINT, Gl.GL_NICEST);
            Gl.glEnable(Gl.GL_BLEND);
            Gl.glEnable(Gl.GL_LINE_SMOOTH);
            Gl.glLineWidth(1.0f);

            cam.Position_Camera(0, 6, -15, 0, 3, 0, 0, 1, 0); // Вот тут в инициализации
// укажем начальную позицию камеры, взгляда и вертикального вектора.
        }

В загрузку формы кинем нашу инициализацию:

Код:
/*http://esate.ru, OniKy*/

        private void Form1_Load_1(object sender, EventArgs e)
        {
            InitGL();
        }

Также напишем метод рисования:

Код:
/*http://esate.ru, OniKy*/

private void Draw()
        {
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

            Gl.glLoadIdentity();
            Gl.glColor3i(255, 0, 0);

            cam.Look(); // Обновляем взгляд камеры

            Gl.glPushMatrix();

               DrawGrid(30, 1); // Нарисуем сетку

            Gl.glPopMatrix();
         
            Gl.glFlush();

            AnT.Invalidate();
        }

Метод рисования Сетки:

Код:
/*http://esate.ru, OniKy*/

 private void DrawGrid(int x, float quad_size)
        {
             // x - количество или длина сетки, quad_size - размер клетки
            Gl.glPushMatrix(); // Рисуем оси координат, цвет объявлен в самом начале
            Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_AMBIENT_AND_DIFFUSE, MatrixColorOX);
            Gl.glTranslated((-x * 2) / 2, 0, 0);
            Gl.glRotated(90, 0, 1, 0);
            Glut.glutSolidCylinder(0.02, x * 2, 12, 12);
            Gl.glPopMatrix();

            Gl.glPushMatrix();
            Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_AMBIENT_AND_DIFFUSE, MatrixColorOZ);
            Gl.glTranslated(0, 0, (-x * 2) / 2);
            Glut.glutSolidCylinder(0.02, x * 2, 12, 12);
            Gl.glPopMatrix();

            Gl.glPushMatrix();
            Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_AMBIENT_AND_DIFFUSE, MatrixColorOY);
            Gl.glTranslated(0, x / 2, 0);
            Gl.glRotated(90, 1, 0, 0);
            Glut.glutSolidCylinder(0.02, x, 12, 12);
            Gl.glPopMatrix();

            Gl.glBegin(Gl.GL_LINES);

            Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_AMBIENT_AND_DIFFUSE, MatrixOXOYColor);

             // Рисуем сетку 1х1 вдоль осей
            for (float i = -x; i <= x; i += 1)
            {
                Gl.glBegin(Gl.GL_LINES);
                // Ось Х
                Gl.glVertex3f(-x * quad_size, 0, i * quad_size);
                Gl.glVertex3f(x * quad_size, 0, i * quad_size);

                // Ось Z
                Gl.glVertex3f(i * quad_size, 0, -x * quad_size);
                Gl.glVertex3f(i * quad_size, 0, x * quad_size);
                Gl.glEnd();
            }
        }

Напишем метод, который будет считывать мышку:

Код:
/*http://esate.ru, OniKy*/

private void AnT_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
                mouseRotate = true; // Если нажата левая кнопка мыши

            if (e.Button == MouseButtons.Middle)
                mouseMove = true; // Если нажата средняя кнопка мыши

            myMouseYcoord = e.X; // Передаем в нашу глобальную переменную позицию мыши по Х
            myMouseXcoord = e.Y;
        }

        private void AnT_MouseUp(object sender, MouseEventArgs e)
        {
            mouseRotate = false; 
            mouseMove = false;
        }

        private void AnT_MouseMove(object sender, MouseEventArgs e)
        {
            myMouseXcoordVar = e.Y;
            myMouseYcoordVar = e.X;
        }

И конечно нам надо обработать мышку, вот и сам метод:

Код:
/*http://esate.ru, OniKy*/

private void mouse_Events()
        {
            if (mouseRotate == true) // Если нажата левая кнопка мыши
            {
                AnT.Cursor = System.Windows.Forms.Cursors.SizeAll; //меняем указатель
         
                cam.Rotate_Position((float)(myMouseYcoordVar - myMouseYcoord), 0, 1, 0); // крутим камеру, в моем случае вид у нее от третьего лица

                rot_cam_X = rot_cam_X + (myMouseXcoordVar - myMouseXcoord);
                if ((rot_cam_X > -40) && (rot_cam_X < 40))
                    cam.upDown(((float)(myMouseXcoordVar - myMouseXcoord)) / 10);

                myMouseYcoord = myMouseYcoordVar;
                myMouseXcoord = myMouseXcoordVar;
            }
            else
            {
                if (mouseMove == true)
                {
                    AnT.Cursor = System.Windows.Forms.Cursors.SizeAll;

                    cam.Move_Camera((float)(myMouseXcoordVar - myMouseXcoord) / 50);
                    cam.Strafe(-((float)(myMouseYcoordVar - myMouseYcoord) / 50));

                    myMouseYcoord = myMouseYcoordVar;
                    myMouseXcoord = myMouseXcoordVar;

                }
                else
                {
                    AnT.Cursor = System.Windows.Forms.Cursors.Default; // возвращаем курсор
                };
            };
        }

И дописываем в таймер.

Код:
/*http://esate.ru, OniKy*/

                mouse_Events();
                cam.update();
                Draw();

Вот и все. На данном примере мы сделали возможность перемещаться по сцене, как в редакторе моделей 3D Studio Max. Этот метод можно использовать и в играх (стратегиях).

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

^
Регистрация
Регистрируясь, вы принимаете правила сайта. Если вы не получили код подтв. регистрации - не забудьте проверить папку спам.
Логин*
Email*
Пароль*
Подтверждение пароля*
 
Логин*
Код*
 
×
Восстановление пароля
Пожалуйста, заполните поля, после чего вы получите код подтверждения на ваш Email. Если код не пришел в течении нескольких минут - проверьте папку спам.
Логин

или Email
Логин*
Код подтверждения*
Новый пароль*
Подтверждение пароля*
×
Авторизация
  • Используйте вашу учетную запись на Facebook.com для входа на сайт.
  • Используйте вашу учетную запись VKontakte для входа на сайт.
  • Используйте вашу учетную запись Google для входа на сайт.
Авторизуйтесь с помощью соц. сети или с помощью аккаунта на сайте:
×