В прошлый раз я обещал управляемую сцену и сферу из примитивов - Принимайте :-)
Обрабатывать нажатие кнопки будем в функции keyPressEvent и там, с помощью case определять нажатую клавишу, и изменять параметры. Для удобоваримого чтения кода, и ради хорошего стиля( стиль - не пустой звук, так правда удобней), выделим на каждое изменение параметра по функции:
Теперь я хочу поговорить про актёров на нашей сцене. Всё, что мы хотим нарисовать должно вызываться в функции repaint. Я хочу дать возможность помещать объекты на сцену динамически. Для этого я придумал такую технологию :
Я создаю абстрактный класс GLobject, в нём создаю виртуальную функцию paint, на сцене создаю вектор объектов типа GLobject и в функции repaint вызываю функцию paint для каждого объекта из моего вектора. Создать объект GLobject мы не можем(класс абстрактный),но мы можем от него наследовать и в классе-наследнике переопределить функцию paint, и в ней рисовать то, что хотим
Ближе к делу. Вот класс GLobject
Я хочу, чтоб объекты жили своей жизнью во время работы программы. Для этого я делаю дополнительный слой абстракции - AliveObjectGL. В создадим переменную QTimer, и ещё одну виртуальную функцию-слот live(). Эта функция будет вызываться таймером, в ней потомки будут меня параметры характеризующие их состояние. Вот код классов GLobject и AliveObjectGL :
И реализация:
В private SceneGL создаю вектор указателей QVector<AliveObjectGL*> actors;
После прорисовки осей рисую все объекты.
Теперь создадим класс реального объекта. Я обещал сферу? Получайте:
А вот то, как она реализована.
Это простая реализация параметрического уравнения сферы. Перед каждой точкой я добавил цвет по 3 координатам - это делает сферу разноцветной.
Теперь, при инициализации сцены добавим в наш вектор новую сферу. Как видите, она крутится, и мы можем мышкой перемещать её!
За подробностями реализации прошу пожаловать под кат.
Первое, что мы сделаем - Управляемую сцену. Охота, чтобы по нашему миру можно было путешествовать, как в компьютерных играх: с помощью мышки и клавиатуры 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. С её помощью мы сможем перемещаться по миру не смотря всё-время в центр координат, как это происходит в вышеприведённом видео.
Спасибо за статейку )
ОтветитьУдалитьОбновите свой исходники, за статьи спасибо.
ОтветитьУдалитьСсылка на исходники устарела.
ОтветитьУдалитьПожалуйста, выложите их снова
Простите. Дроп бокс меня надул, а я на него рассчитывал. Скоро востановлю.
УдалитьЛучше яндекс диск заведите))
Удалитьссылка устарела
ОтветитьУдалитьобновите ссылку, пожалуйста!!!
ОтветитьУдалить