вторник, 21 августа 2012 г.

Управляемая сцена openGl

В прошлый раз я обещал управляемую сцену и сферу из примитивов -  Принимайте :-)

За подробностями реализации прошу пожаловать под кат.


Первое, что мы сделаем - Управляемую сцену. Охота, чтобы по нашему миру можно было путешествовать, как в компьютерных играх: с помощью мышки и клавиатуры WASD. Вспомним наш прошлый класс SceneGl 

class SceneGL : public QGLWidget


Он наследуется от QGLWidget, а QGLWidget наследуется от QWidget, QWidget может принимать и обрабатывать события клавиатуры и мыши. Воспользуемся этим - для этого дополним наш класс SceneGl такими функциями:


    void mousePressEvent(QMouseEvent* pe);
    void mouseMoveEvent(QMouseEvent* pe);
    void mouseReleaseEvent(QMouseEvent* pe);
    void wheelEvent(QWheelEvent* pe);
    void keyPressEvent(QKeyEvent* pe);

На данном этапе нам стоит отдалиться от кода и подумать о концепции необходимого нам функционала. Что мы хотим видеть в итоге?
  • Клавиши W,S - движение вперёд и назад соответственно;
  • Движение мыши, при зажатой левой кнопке - вращение камеры. 
  • Клавиши со стрелками - движение камеры.
  • Пробел - возврат на исходную позицию.
  • Esc - выход из программы.
  • Вращение колёсика мыши- изменение чувствительности скорости вращения к движению мыши.
Для обеспечения данного функционала мне понадобятся такие переменные:

    QPoint ptrMousePosition;


    GLfloat xRot;//поворот камеры относительно X
    GLfloat yRot;//поворот камеры относительно Y
    GLfloat zRot;//поворот камеры относительно Z
    GLfloat zTra;// перемещение камеры по оси Z( происходит перд поворотом)
    GLfloat nSca;// чувствительность мыши



Обрабатывать нажатие кнопки будем в функции keyPressEvent и там, с помощью case определять нажатую клавишу, и изменять параметры. Для удобоваримого чтения кода, и ради хорошего стиля( стиль - не пустой звук, так правда удобней), выделим на каждое изменение параметра по функции:



    void scale_plus();
    void scale_minus();
    void rotate_up();
    void rotate_down();
    void rotate_left();
    void rotate_right();
    void translate_down();
    void translate_up();
    void defaultScene();


Их названия говорят сами за себя. 

Заканчиваем с концепцией, пора перейти к реализации. Дабы не путать функциональный код и тривиальный код управления, я выделил отдельный файл controlescene.cpp.
Вот его содержимое:
#include "scenegl.h"
#include <QtGui>
#include <math.h>

void SceneGL::mousePressEvent(QMouseEvent* pe)
{
   ptrMousePosition = pe->pos();
}

void SceneGL::mouseReleaseEvent(QMouseEvent* pe)
{

}

void SceneGL::mouseMoveEvent(QMouseEvent* pe)
{

   xRot  = 180/nSca*(GLfloat)(pe->y()-ptrMousePosition.y())/height();
   zRot  = 180/nSca*(GLfloat)(pe->x()-ptrMousePosition.x())/width();

   ptrMousePosition = pe->pos();

   updateGL();
}

void SceneGL::wheelEvent(QWheelEvent* pe)
{
   if ((pe->delta())>0) scale_plus(); else if ((pe->delta())<0) scale_minus();

   updateGL();
}

void SceneGL::keyPressEvent(QKeyEvent* pe)
{
   switch (pe->key())
   {
      case Qt::Key_Plus:
         scale_plus();
      break;

      case Qt::Key_Equal:
         scale_plus();
      break;

      case Qt::Key_Minus:
         scale_minus();
      break;

      case Qt::Key_Up:
         rotate_up();
      break;

      case Qt::Key_Down:
         rotate_down();
      break;

      case Qt::Key_Left:
        rotate_left();
      break;

      case Qt::Key_Right:
         rotate_right();
      break;

      case Qt::Key_S:
         translate_down();
      break;

      case Qt::Key_W:
         translate_up();
      break;

      case Qt::Key_Space:
         defaultScene();
      break;

      case Qt::Key_Escape:
         this->close();
      break;
   }

   updateGL();
}

void SceneGL::scale_plus()
{
   nSca = nSca*1.1;
}

void SceneGL::scale_minus()
{
   nSca = nSca/1.1;
}

void SceneGL::rotate_up()
{
   xRot  = 1.0;
}

void SceneGL::rotate_down()
{
   xRot -= 1.0;
}

void SceneGL::rotate_left()
{
   zRot  = 1.0;
}

void SceneGL::rotate_right()
{
   zRot -= 1.0;
}

void SceneGL::translate_down()
{
   zTra -= 0.05;
}

void SceneGL::translate_up()
{
   zTra  = 0.05;
}

void SceneGL::defaultScene()
{
   xRot=0; yRot=0; zRot=0; zTra=0; nSca=1;
}





Перейдём к изменению scenegl.cpp.Добавьте в конструктор класса вызов функции обнуления переменных defaultScene(); и измените paintGL так:

void SceneGL::paintGL(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glTranslatef(0.0f, 0.0, -4.0f);
    glTranslatef(0.0f, 0.0, zTra);
    glRotatef(xRot, 1.0f, 0.0f, 0.0f);
    glRotatef(yRot, 0.0f, 1.0f, 0.0f);
    glRotatef(zRot, 0.0f, 0.0f, 1.0f);и
    ...
Теперь про перемещение. 
Обратите внимание на то, что сначала я перемещаюсь, а потом поворачиваю оси, тем самым я поворачиваю из вокруг уже смещённого положения. В пространстве порядок действий очень важен. На этом рисунке показаны обе последовательности и результаты, к которым они приводят.


Теперь я хочу поговорить про актёров на нашей сцене. Всё, что мы хотим нарисовать должно вызываться в функции repaint. Я хочу дать возможность помещать объекты на сцену динамически. Для этого я придумал такую технологию :
Я создаю абстрактный класс GLobject, в нём создаю виртуальную функцию paint, на сцене создаю вектор объектов типа GLobject и в функции repaint вызываю функцию paint для каждого объекта из моего вектора. Создать объект GLobject мы не можем(класс абстрактный),но мы можем от него наследовать и в классе-наследнике переопределить функцию paint, и в ней рисовать то, что хотим


Ближе к делу. Вот класс GLobject
class ObjectGL : public QObject
{
    Q_OBJECT
public:
    explicit ObjectGL(QObject *parent = 0);
    virtual void paint()=0;
};

Я хочу, чтоб объекты жили своей жизнью во время работы программы. Для этого я делаю дополнительный слой абстракции -  AliveObjectGL. В создадим переменную QTimer, и ещё одну виртуальную функцию-слот live(). Эта функция будет вызываться таймером, в ней потомки будут меня параметры характеризующие их состояние. Вот код классов GLobject и AliveObjectGL :
#ifndef OBJECTGL_H
#define OBJECTGL_H

#include <QGLWidget>
#include <GL/glu.h>
#include <QObject>
#include <QTimer>
class ObjectGL : public QObject
{
    Q_OBJECT
public:
    explicit ObjectGL(QObject *parent = 0);
    virtual void paint()=0;
};



class AliveObjectGL : public ObjectGL
{
    Q_OBJECT
public:
    QTimer *timer;
    explicit AliveObjectGL(QObject *parent = 0,int delay = 10);
    void setdelay(int delay);
private:
        int delay;
public slots:
     virtual  void live()=0;

};


#endif // OBJECTGL_H


И реализация:
#include "objectgl.h"

ObjectGL::ObjectGL(QObject *parent) :
    QObject(parent)
{
}

AliveObjectGL::AliveObjectGL(QObject *parent,int delay) :
    ObjectGL(parent)
{
    this->delay = delay;
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(live()));
    timer->start(delay);
}

void AliveObjectGL::setdelay(int delay)
{
    this->delay = delay;
    timer->stop();
    timer->start(delay);
}


В private SceneGL создаю вектор указателей  QVector<AliveObjectGL*> actors;

После прорисовки осей рисую все объекты.
void SceneGL::paintGL(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glTranslatef(0.0f, 0.0, -4.0f);
    glTranslatef(0.0f, 0.0, zTra);
    glRotatef(xRot, 1.0f, 0.0f, 0.0f);
    glRotatef(yRot, 0.0f, 1.0f, 0.0f);
    glRotatef(zRot, 0.0f, 0.0f, 1.0f);

    drawAxis();
    foreach (AliveObjectGL* actor, actors){
        glPushMatrix();
        actor->paint();
        glPopMatrix();
    }

}






Теперь создадим класс реального объекта. Я обещал сферу? Получайте:
#ifndef SPHEREGL_H
#define SPHEREGL_H
#include "objectgl.h"
#include "math.h"
class SphereGL : public AliveObjectGL
{
    Q_OBJECT
public:
    explicit SphereGL(QObject *parent = 0);


    float radius; /радиус сферы
    GLfloat rotz;   // её вращение вокгруг оси z ( меняется таймерм)
    int init();
    void paint();
    QColor*color;
public slots:
    void live();

};

#endif // SPHEREGL_H



А вот то, как она реализована.
#include "spheregl.h"
#include <QtCore>
SphereGL::SphereGL(QObject *parent) :
    AliveObjectGL(parent)
{
    init();
}

int SphereGL::init()
{
    rotz=0;radius = 1;
    color = new QColor(255,255,255,255);
}

void SphereGL::paint()
{

    glRotatef(20,1,0,0);
    glRotatef(rotz,0,0,1);
    float x,y,z;
    float X=-M_PI,Y=0;
    float Z=0;
    float accuracy = 0.1;
      glColor4f(color->redF(),color->greenF(),color->blueF(),color->alphaF());
    glBegin(GL_TRIANGLE_STRIP);
              while(X<M_PI)
              {
               while(Y<2*M_PI)
               {
               x=radius*cos(X)*cos(Y);
               y=radius*cos(X)*sin(Y);
               z=radius*sin(X);
              // qDebug()<<x<<y<<z;

               glVertex3f(x,y,z);

               x=radius*cos(X)*cos(Y);
               y=radius*cos(X accuracy)*sin(Y);
               z=radius*sin(X);
             //  qDebug()<<x<<y<<z;

               glVertex3f(x,y,z);

               x=radius*cos(X accuracy)*cos(Y);
               y=radius*cos(X)*sin(Y);
               z=radius*sin(X accuracy);
              // qDebug()<<x<<y<<z;

               glVertex3f(x,y,z);
               Y =accuracy;
                 }
                 Y=0;
                 X =accuracy;
               }
              glEnd();

}

void SphereGL::live()
{

    rotz =1.0f;
}


Это простая реализация параметрического уравнения сферы. Перед каждой точкой я добавил цвет по 3 координатам - это делает сферу разноцветной.

Теперь, при инициализации сцены добавим в наш вектор новую сферу. Как видите, она крутится, и мы можем мышкой перемещать её!



Это конечно интересно, но я хочу планету, а у планеты есть спутники, они движутся по орбитам. Давайте посмотрим на небо, как там обстоят дела: есть чёрная дыра, вокруг которой все движется, включая наше солнышко, вокруг солнышка движется планета, вокруг планеты луна и вокруг луны мы можем запустить спутник, и если кто-то выкинет мусор из окошка спутника, у этого спутника то-же появится спутник. Исходя из всего этого я постулирую: у каждого объекта может быть один или несколько спутников, которые движутся по эллиптической орбите вокруг него, и каждый объект может являться спутником для другого объекта, при этом объект не может стать своим собственным спутником. Самостоятельный объект я назову планетой,  а для организации спутников, я создам объект "Орбита", который не имеет права существовать сам по себе, он должен иметь планету, которая по ней движется и вокруг которого она движется. Любую планету можно прилепить к любой орбите, любую орбиту можно прилепить к любой планете, кроме случая, когда планета-центр и планета -спутник совпадают. Орбита должна характеризоваться параметрами эллипса, ещё, кроме вращения планеты по орбите я сделал вращение орбиты относительно планеты, этот случай не имеет места в реальности(на сколько я знаю), но это красиво и интересно.Для всего этого составим диаграмму классов:

Вот такое применение ООП( обожаю эту технологию). Внизу приведён код. Обратите внимание, что из главного окна вызывается прорисовка одной единственной планеты, а она в сою очередь вызывает прорисовки всех орбит которые на ней, а орбиты вызывают прорисовки планет, которые по этим орбитам крутятся. Красота да и только! После реализации всего этого у нас вот такой результат:




Код для запуска и просмотра реализации кольца, и движения по его траектории окружности можете посмотреть тут:
https://dl.dropbox.com/u/95314428/ForBlog/ControledScene.zip
Спасибо за внимание :-) В следующий раз хочу вам рассказать про замечательную функцию- gluLookAt. С её помощью мы сможем перемещаться по миру не смотря всё-время в центр координат, как это происходит в вышеприведённом видео.

Читать дальше......

суббота, 4 августа 2012 г.

Помещение актёров на сцену OpenGl Qt

В прошлом посте я рассказал про создание OpenGl окна. Сегодня я расскажу , как туда поместить объект. Сначала создадим окно , как описано тут http://alexkutsan.blogspot.com/2012/04/opengl-qt.html .

Всё не так сложно.
Изменять я буду  функцию paintGL(). В ней происходит отрисовка.Вызывается она всякий раз, когда окно перерисовывается. Мы нарисуем  8 угольник. Определим его вершины:


Да, мы можем заставить компьютер считать все эти косинусы и синусы, но зачем? Компьютер будет раскладывать в ряд каждое выражение. Лучше попросим любого 7-миклассника посчитать нам эти значения. Он нам с первого взгляда всё выведет (стыдно, да?)


Ближе к коду.


void SceneGL::paintGL(){
  glClear(GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  glBegin(GL_POLYGON);
      glVertex3f( 0.5f, 1.0f,0.0f);
      glVertex3f(1.0f,0.5f,  0.0f);
      glVertex3f( 1.0f,-0.5f,0.0f);

      glVertex3f(0.5f,-1.0f,0.0f);
      glVertex3f(-0.5f,-1.0f,0.0f);
      glVertex3f(-1.0f,-0.5f,0.0f);

      glVertex3f(-1.0f,0.5f,0.0f);
      glVertex3f(-0.5f,1.0f,0.0f);
   glEnd();
}



Немного описания.

Мы работаем уже в матричной модели. Изменяем ведь саму модель - glMatrixMode(GL_MODELVIEW);

Мы очищаем буфер цветов -  glClear(GL_COLOR_BUFFER_BIT); , ведь у нас появляются здесь цвета( пока-что только белый)

Загружаем единичную матрицу -  glLoadIdentity(); . Никаких трансформаций над матрицей мы не делаем, поэтому этот шаг не обязателен,но делать его принято. Попробуйте вставить
...
glRotated(30,0,0,1);
glBegin(GL_POLYGON);
...
Вы  увидите, что матрица повернётся относительно единичной. Теперь уберите  glLoadIdentity();. Видите? Матрица поворачивается всякий раз при перерисовке окна относительно прошлого её состояния. Это потому, что функция glRotated(30,0,0,1); не поворачивает объект, это мы с вами думаем, что она поворачивает объект. glRotated умножает текущую матрицу на матрицу поворота, параметры которой мы задаём.

Всё серо и грустно.
Давайте добавим радости - добавим цвета.
Перед каждой точкой будем задавать цвет функцией glColor3f. Её параметры - RGB (0.0f-1.0f);
  glBegin(GL_POLYGON);
       glColor3f(1,0,0);
      glVertex3f( 0.5f, 1.0f,-1.0f);
       glColor3f(0,1,0);
      glVertex3f(1.0f,0.5f,  -1.0f);
       glColor3f(0,0,1);
      glVertex3f( 1.0f,-0.5f,-1.0f);

       glColor3f(1,0,1);
      glVertex3f(0.5f,-1.0f,-1.0f);
       glColor3f(1,1,0);
      glVertex3f(-0.5f,-1.0f,-1.0f);
       glColor3f(1,1,1);
      glVertex3f(-1.0f,-0.5f,-1.0f);

       glColor3f(0.5,0.5,0.5);
      glVertex3f(-1.0f,0.5f,-1.0f);
       glColor3f(0,0.5,1);
      glVertex3f(-0.5f,1.0f,-1.0f);
      glEnd();



 
И результат:



Хочу, чтоб всё вертелось!
Когда мы смотрим на объект, мы смотрим на него сверху.
Для простоты , сейчас мы повертим его относительно оси Z.
Если вы посмотрите код выше, то увидите, что наш 6-угольник находился в плоскости Z на уровне  -  -1. Давайте это изменим, и поставим его на уровень 0. И воспользуемся функцией смещения glTranslated(0,0,-3); Тем самым, мы отведём от глаз шестиугольник на 3 шага.

Для того, чтоб повернуть этот шестиугольник , используется функция   glRotatef. Она принимает 4 параметра:
angel - угол на который нужно повернуть объект.
x - поворачивать относительно Ох?
y - поворачивать относительно Оу?
z - поворачивать относительно Oz?

Давайте создадим private переменную  int rot; И создадим public slot void update();
Внутри update напишем.
void SceneGL::update()
{
    rot++;
    updateGL();
}

Теперь, изменим код initializeGL
void SceneGL::initializeGL(){
    glClearColor(0.0f,0.0f,0.0f,1.0f);
    glEnable(GL_DEPTH_TEST);
    QTimer *timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    timer->start(10);
    
}

Как видите, мы запускаем таймер, и через каждые 10 миллисекунд будет выполняться update(), тем самым увеличивая переменную rot и перерисовывая сцену. Стоит изменить функцию paintGL
void SceneGL::paintGL(){
    glClear(GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslated(0,0,-4);
    glRotatef(rot,0,0,1);
    ...
После загрузки единичной матрицы я поворачиваю сцену на rot градусов, а rot постоянно меняется таймером. Запускайте, любуйтесь.

Теперь, давайте повертим наш шести угольник не только относительно Z, а ещё относительно X и Y. Для этого изменим glRotatef(rot,0,0,1); на glRotatef(rot,1,1,1);. Для большей наглядности нарисуем Оси координат. Создайте private функцию

void SceneGL::drawAxis()
{
    glLineWidth(3.0f);
    // до вызова команды ширина равна 9 пикселю по умолчанию
    // устанавливаем цвет последующих примитивов
    glColor4f(1.00f, 0.00f, 0.00f, 1.0f);
    // ось x кvoid Scene3D::drawAxis()
    {
        glLineWidth(3.0f);
        // до вызова команды ширина равна 9 пикселю по умолчанию

        // устанавливаем цвет последующих примитивов
        glColor4f(1.00f, 0.00f, 0.00f, 1.0f);
        // ось x красного цвета
        glBegin(GL_LINES); // построение линии
        glVertex3f( 9.0f,  0.0f,  0.0f); // первая точка
        glVertex3f(-9.0f,  0.0f,  0.0f); // вторая точка
        glEnd();

        QColor halfGreen(0, 128, 0, 255);
        qglColor(halfGreen);
        glBegin(GL_LINES);
        // ось y зеленого цвета
        glVertex3f( 0.0f,  9.0f,  0.0f);
        glVertex3f( 0.0f, -9.0f,  0.0f);

        glColor4f(0.00f, 0.00f, 1.00f, 9.0f);
        // ось z синего цвета
        glVertex3f( 0.0f,  0.0f,  9.0f);
        glVertex3f( 0.0f,  0.0f, -9.0f);
        glEnd();
    }

}

Теперь изменим функцию paintGL вот так:
    ...
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslated(0,0,-4);
    glRotatef(30,1,1,0);
    drawAxis();
    glRotatef(rot,1,1,1);
    ...
и функцию initializeGL
void SceneGL::initializeGL(){
    glClearColor(0.0f,0.0f,0.0f,1.0f);
    glEnable(GL_DEPTH_TEST);
    QTimer *timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    timer->start(10);

}
Обратите внимание на glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); и glEnable(GL_DEPTH_TEST);. Я включил тест глубины, если его не включить, то openGl не будет учитывать дальность объектов. Виден будет объект, которые нарисуется первым. С включённым тестом глубины будет виден тот объект, который ближе к наблюдателю(к нам). Ещё я поместил сюда второй glRotatef, который поворачивает сцену на 30 градусов по X и Y - это даст нам удобный угол просмотра. После этого я рисую оси и поворачиваю сцену на rot градусов по всем осям, и рисую 6-угольник. Благодоря этой последовательности прорисовки, оси у меня остаются всегда в 30 градусах, а объект крутится.
Итог получается вот такой:
 
 В следующий раз я расскажу как сделать управляемую сцену, чтоб мы в ней могли перемещаться динамически. Ещё я хочу рассказать, как с помощью примитивов нарисовать сферу. На сегодня всё.Спасибо всем, кто это прочёл. Исходники можно получить здесь : Arctors.zip
Читать дальше......

среда, 1 августа 2012 г.

Gnome3 Linux Mint

Что-то мне надоели постоянные проблемы с системой. Охота сидеть и писать код. Решил поставить Linux Mint,но меня ждали плохие новости. Как известно, в Mint используется оконное окружение - mate. Мне оно не нравится. Я хочу gnome3. При установке пакетов gdm, gnome и gnome-shell, исталятор валится с указанием на существование файла /usr/share/pixmaps/nobody.png. Его удаление не помогает. Информации об этом в интернете нету.Оказалось, что Mintовский mdm(mint display manager)- это будто-бы gdm, но mdm.Для корректной работы gdm, mdm надо удалить. Удаляете и ставите спокойно gdm и gnome. Лично мне - нравится mdm. Я удалил mdm, поставил gdm,gnome,gnome-shell, удалил gdm, поставил mdm. Всё... Работает как надо :-)
Читать дальше......