(C++) QtExample7
February 24, 2017 · View on GitHub
(C++) QtExample7




This Qt example shows how to let customly-drawn and partially transparent sprites move and collide over a changing background in 2D, like this screenshot (png).
Technical facts
Operating system(s) or programming environment(s)
Lubuntu 15.04 (vivid)
Qt Creator 3.1.1
- G++ 4.9.2
Libraries used:
Qt project file: ./CppQtExample7/CppQtExample7.pro
exists(../../DesktopApplication.pri) { include(../../DesktopApplication.pri) } !exists(../../DesktopApplication.pri) { QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = app CONFIG(debug, debug|release) { message(Debug mode) } CONFIG(release, debug|release) { message(Release mode) DEFINES += NDEBUG NTRACE_BILDERBIKKEL } QMAKE_CXXFLAGS += -std=c++11 -Wall -Wextra unix { QMAKE_CXXFLAGS += -Werror } } exists(../../Libraries/Boost.pri) { include(../../Libraries/Boost.pri) } !exists(../../Libraries/Boost.pri) { win32 { INCLUDEPATH += \ ../../../Projects/Libraries/boost_1_55_0 } } SOURCES += main.cpp
./CppQtExample7/main.cpp
#include <cassert> #include <cmath> #include <iostream> #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Weffc++" #pragma GCC diagnostic ignored "-Wunused-local-typedefs" #include <boost/shared_ptr.hpp> #include <boost/static_assert.hpp> #include <boost/math/constants/constants.hpp> #include <QApplication> #include <QBitmap> #include <QGraphicsPixmapItem> #include <QGraphicsScene> #include <QGraphicsView> #include <QTimer> #pragma GCC diagnostic pop BOOST_STATIC_ASSERT(sizeof(qreal)==sizeof(double) && "Assume use of double is equivalent of qreal"); ///Obtain the angle in radians between two deltas ///12 o'clock is 0.0 * pi /// 3 o'clock is 0.5 * pi /// 6 o'clock is 1.0 * pi /// 9 o'clock is 1.5 * pi //From www.richelbilderbeek.nl/CppGetAngle.htm double GetAngle(const double dx, const double dy) { const double pi = boost::math::constants::pi<double>(); return pi - (std::atan2(dx,dy)); } //From http://www.richelbilderbeek.nl/CppDoPerfectElasticCollision.htm void DoPerfectElasticCollision( const double angleCollision, double& angle1, double& speed1, double& angle2, double& speed2) { const double pi = boost::math::constants::pi<double>(); //The length of the impulse of player 1 (assumes both players have equal mass!) const double A = speed1; //The length of the impulse of player 2 (assumes both players have equal mass!) const double E = speed2; //The angles between the two globes const double c = angleCollision; //The angle between c and the impulse direction of player 1 const double a = c - angle1; //The angle between c and the impulse direction of player 2 const double b = c + pi - angle2; //Seperate the impulses to their impulses paralel and othoganal the angle of collision //The length of the impulse of player 1 parallel to the collision const double B = A * std::cos(a); //The length of the impulse of player 1 orthogonal to the collision const double C = A * std::sin(a); //The length of the impulse of player 2 parallel to the collision const double F = E * std::cos(b); //The length of the impulse of player 2 orthogonal to the collision const double G = E * std::sin(b); //Seperate the impulses in X and Y directions const double BdX = B * std::sin(c + (0.0 * pi)); const double BdY = B * -std::cos(c + (0.0 * pi)); const double CdX = C * std::sin(c + (1.5 * pi)); const double CdY = C * -std::cos(c + (1.5 * pi)); const double FdX = F * std::sin(c + (1.0 * pi)); const double FdY = F * -std::cos(c + (1.0 * pi)); const double GdX = G * std::sin(c + (0.5 * pi)); const double GdY = G * -std::cos(c + (0.5 * pi)); //The resulting impulses //The resulting impulse of player 1 in the X direction const double DdX = CdX + FdX; //The resulting impulse of player 1 in the Y direction const double DdY = CdY + FdY; //The resulting impulse of player 2 in the X direction const double HdX = BdX + GdX; //The resulting impulse of player 2 in the Y direction const double HdY = BdY + GdY; //Write the final results angle1 = GetAngle(DdX, DdY); angle2 = GetAngle(HdX, HdY); speed1 = std::sqrt( (DdX * DdX) + (DdY * DdY) ); //Pythagoras speed2 = std::sqrt( (HdX * HdX) + (HdY * HdY) ); //Pythagoras } //From http://www.richelbilderbeek.nl/CppGetRandomUniform.htm double GetRandomUniform() { return static_cast<double>(std::rand())/static_cast<double>(RAND_MAX); } struct ChangingBackground : public QGraphicsPixmapItem { ChangingBackground(const int width, const int height) : z(0) { QPixmap m(width,height); this->setPixmap(m); } void advance(int) { QImage i = this->pixmap().toImage(); const int width = i.width(); const int height = i.height(); for (int y=0;y!=height;++y) { for (int x=0;x!=width;++x) { QPoint pos(x,y); QColor c((x+z)%256,(y+z)%256,(x+y+z)%256); i.setPixel(pos,c.rgb()); } } this->setPixmap(this->pixmap().fromImage(i)); ++z; } private: int z; }; struct TransparentSprite : public QGraphicsPixmapItem { TransparentSprite( const int width, const int height, const int r = 255, const int g = 255, const int b = 255) : angle(GetRandomUniform() * 2.0 * boost::math::constants::pi<double>()), //Random direction speed(2.0), maxx(0), maxy(0) { QImage i(width,height,QImage::Format_ARGB32); const QColor transparency_color = QColor(0,0,0,255); const double r_real = static_cast<double>(r); const double g_real = static_cast<double>(g); const double b_real = static_cast<double>(b); const double midx = static_cast<double>(width ) / 2.0; const double midy = static_cast<double>(height) / 2.0; const double ray = std::min(midx,midy); for (int y=0;y!=height;++y) { const double y_real = static_cast<double>(y); const double dy = midy - y_real; const double dy2 = dy * dy; for (int x=0;x!=width;++x) { const double x_real = static_cast<double>(x); const double dx = midx - x_real; const double dx2 = dx * dx; const double dist = std::sqrt(dx2 + dy2); if (dist < ray) { const QColor c( (1.0 - (dist / ray)) * r_real, (1.0 - (dist / ray)) * g_real, (1.0 - (dist / ray)) * b_real ); i.setPixel(x,y,c.rgb()); } else { i.setPixel(x,y,transparency_color.rgb()); } } } this->setPixmap(this->pixmap().fromImage(i)); //Add transparancy QPixmap pixmap = this->pixmap(); const QBitmap mask = pixmap.createMaskFromColor(transparency_color); pixmap.setMask(mask); this->setPixmap(pixmap); } void advance(int phase) { if (phase == 0) { //Bounce against others QList<QGraphicsItem*> others = this->collidingItems(); const int n_others = others.size(); for (int i=0; i!=n_others; ++i) { TransparentSprite * const other = dynamic_cast<TransparentSprite*>(others[i]); if (!other) continue; if (other == this) continue; //Ensure checking is only done once per colliding pair if (this->zValue() < other->zValue()) continue; //Relative between players 1 and 2 const double dx_between = other->x() - this->x(); const double dy_between = other->y() - this->y(); const double angle_between = GetAngle(dx_between,dy_between); //For this player const double this_dx = std::cos(this->angle) * this->speed; const double this_dy = -std::sin(this->angle) * this->speed; double this_angle = GetAngle( this_dx, this_dy); double this_speed = std::sqrt((this_dy * this_dy) + (this_dx * this_dx)); //For other player const double other_dx = std::cos(other->angle) * other->speed; const double other_dy = -std::sin(other->angle) * other->speed; double other_angle = GetAngle( other_dx, other_dy); double other_speed = std::sqrt((other_dy * other_dy) + (other_dx * other_dx)); DoPerfectElasticCollision( angle_between, this_angle, this_speed, other_angle, other_speed ); this->angle = this_angle; this->speed = this_speed; other->angle = other_angle; other->speed = other_speed; //Let them move once setX(x() + (std::sin(angle) * speed)); setY(y() - (std::cos(angle) * speed)); other->setX(other->x() + (std::sin(other->angle) * other->speed)); other->setY(other->y() - (std::cos(other->angle) * other->speed)); } } //Bounce against the edges else if (phase == 1) { while (1) { const double pi = boost::math::constants::pi<double>(); setX(x() + (std::sin(angle) * speed)); setY(y() - (std::cos(angle) * speed)); if (x() < 0.0) { setX(x()+1); angle = (0.0*pi) + ((0.0*pi) - angle); continue; } if (y() < 0.0) { setY(y()+1); angle = (0.5*pi) + ((0.5*pi) - angle); continue; } if (x() > maxx) { setX(x()-1); angle = (1.0*pi) + ((1.0*pi) - angle); continue; } if (y() > maxy) { setY(y()-1); angle = (1.5*pi) + ((1.5*pi) - angle); continue; } break; } } } void setRect(const int width, const int height) { maxx = static_cast<double>(width - this->pixmap().width() ); maxy = static_cast<double>(height - this->pixmap().height()); } double angle; double speed; private: double maxx; double maxy; }; int main(int argc, char *argv[]) { QApplication a(argc, argv); QGraphicsScene s; QGraphicsView v(&s); ChangingBackground background(256,256); s.addItem(&background); std::vector<boost::shared_ptr<TransparentSprite> > sprites; //Add multiple sprites { const double pi = boost::math::constants::pi<double>(); const int n_sprites = 10; const double midx = background.pixmap().width() / 2.0; const double midy = background.pixmap().height() / 2.0; const double ray = std::min(midx,midy) * 0.8; const double d_angle = 2.0 * pi / static_cast<double>(n_sprites); double angle = 0.0; for (int i=0; i!=n_sprites; ++i) { boost::shared_ptr<TransparentSprite> sprite( new TransparentSprite( 32, 32, 128 + (std::rand() % 128), 128 + (std::rand() % 128), 128 + (std::rand() % 128) ) ); const double x = midx + (std::sin(angle) * ray) - (sprite->pixmap().width() / 2); const double y = midy - (std::cos(angle) * ray) - (sprite->pixmap().height() / 2); sprite->setX(x); sprite->setY(y); sprite->setRect(background.pixmap().width(),background.pixmap().height()); s.addItem(sprite.get()); sprites.push_back(sprite); angle+=d_angle; } } v.show(); boost::shared_ptr<QTimer> timer(new QTimer(&s)); timer->connect(timer.get(), SIGNAL(timeout()), &s, SLOT(advance())); timer->start(50); return a.exec(); }