Go back to Richel Bilderbeek's homepage.

Go back to Richel Bilderbeek's C++ page.

 

 

 

 

 

(C++) QtExample8

 

QtQt CreatorLubuntuUbuntu

 

This Qt example shows how implement a simple 2D SIR simulation, like this screenshot (png).

 

Technical facts

 

Application type(s)

Operating system(s) or programming environment(s)

IDE(s):

Project type:

C++ standard:

Compiler(s):

Libraries used:

 

 

 

 

 

Qt project file: ./CppQtExample8/CppQtExample8.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

 

 

 

 

 

./CppQtExample8/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/timer.hpp>
#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 SirSprite : public QGraphicsPixmapItem
{
  enum State { susceptible, infected, resistant };
  SirSprite(const int width, const int height, const bool is_infected = false)
    : angle(GetRandomUniform() * 2.0 * boost::math::constants::pi<double>()), //Random direction
      speed(5.0),
      state(is_infected ? infected : susceptible),
      maxx(0),
      maxy(0)
  {
    QImage i(width,height,QImage::Format_ARGB32);
    this->setPixmap(this->pixmap().fromImage(i));
    setState(is_infected ? infected : susceptible);
  }
  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)
      {
        SirSprite * const other = dynamic_cast<SirSprite*>(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));
        //Infection?
        if (this->state == infected && other->state == susceptible)
        {
          other->setState(infected);
        }
        if (other->state == infected && this->state == susceptible)
        {
          this->setState(infected);
        }
      }
    }
    //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;
      }
      //Resistance?
      if (state == infected && timer.elapsed() > 5.0) setState(resistant);
    }
  }
  void setRect(const int width, const int height)
  {
    maxx = static_cast<double>(width - this->pixmap().width() );
    maxy = static_cast<double>(height - this->pixmap().height());
  }
  QColor getStateColor() const
  {
    switch (state)
    {
      case susceptible: return QColor(0,0,255);
      case infected : return QColor(255,0,0);
      case resistant : return QColor(0,255,0);
    }
    assert(!"Should not get here");
    return QColor(0,0,0);
  }
  void setStateColor()
  {
    const int width = this->pixmap().width();
    const int height = this->pixmap().height();
    QImage i(width,height,QImage::Format_ARGB32);
    const QColor transparency_color = QColor(0,0,0,255);
    const QColor state_color = getStateColor();
    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)
        {
          i.setPixel(x,y,state_color.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 setState(const State any_state)
  {
    state = any_state;
    setStateColor();
    timer.restart();
  }
  double angle;
  double speed;
  private:
  State state;
  boost::timer timer;
  double maxx;
  double maxy;
};

int main(int argc, char *argv[])
{
  QApplication a(argc, argv);
  QGraphicsScene s;
  QGraphicsView v(&s);

  ChangingBackground background(512,512);
  s.addItem(&background);

  std::vector<boost::shared_ptr<SirSprite> > sprites;
  //Add multiple sprites
  {
    const int n_sprites = 20;
    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 pi = boost::math::constants::pi<double>();
    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<SirSprite> sprite(new SirSprite(32,32, i == 0 ? true : false));
      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(100);

  return a.exec();
}

 

 

 

 

 

Go back to Richel Bilderbeek's C++ page.

Go back to Richel Bilderbeek's homepage.

 

Valid XHTML 1.0 Strict

This page has been created by the tool CodeToHtml