В прошлый раз я обещал управляемую сцену и сферу из примитивов - Принимайте :-)
Обрабатывать нажатие кнопки будем в функции 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. С её помощью мы сможем перемещаться по миру не смотря всё-время в центр координат, как это происходит в вышеприведённом видео.





