odin-blend2d

Odin bindings to Blend2D
Log | Files | Refs | README | LICENSE

bl_demo_particles.cpp (7247B)


      1 #include <stdlib.h>
      2 #include <cmath>
      3 #include <vector>
      4 
      5 #include "bl_qt_headers.h"
      6 #include "bl_qt_canvas.h"
      7 
      8 struct Particle {
      9   BLPoint p;
     10   BLPoint v;
     11   int age;
     12   int category;
     13 };
     14 
     15 class MainWindow : public QWidget {
     16   Q_OBJECT
     17 
     18 public:
     19   QTimer _timer;
     20   QComboBox _renderer_select;
     21   QCheckBox _limit_fps_check;
     22   QCheckBox _colors_check_box;
     23   QSlider _count_slider;
     24   QSlider _rotation_slider;
     25   QBLCanvas _canvas;
     26 
     27   BLRandom _rnd;
     28   std::vector<Particle> _particles;
     29 
     30   bool _animate = true;
     31   int maxAge = 650;
     32   double radius_scale = 6;
     33 
     34   enum { kCategoryCount = 8 };
     35   BLRgba32 colors[kCategoryCount] = {
     36     BLRgba32(0xFF4F00FF),
     37     BLRgba32(0xFFFF004F),
     38     BLRgba32(0xFFFF7F00),
     39     BLRgba32(0xFFFF3F9F),
     40     BLRgba32(0xFF7F4FFF),
     41     BLRgba32(0xFFFF9F3F),
     42     BLRgba32(0xFFFFFF00),
     43     BLRgba32(0xFFAF3F00)
     44   };
     45 
     46   MainWindow() {
     47     QVBoxLayout* vBox = new QVBoxLayout();
     48     vBox->setContentsMargins(0, 0, 0, 0);
     49     vBox->setSpacing(0);
     50 
     51     QGridLayout* grid = new QGridLayout();
     52     grid->setContentsMargins(5, 5, 5, 5);
     53     grid->setSpacing(5);
     54 
     55     QBLCanvas::init_renderer_select_box(&_renderer_select);
     56     _limit_fps_check.setText(QLatin1String("Limit FPS"));
     57     _colors_check_box.setText(QLatin1String("Colors"));
     58 
     59     _count_slider.setMinimum(0);
     60     _count_slider.setMaximum(5000);
     61     _count_slider.setValue(500);
     62     _count_slider.setOrientation(Qt::Horizontal);
     63 
     64     _rotation_slider.setMinimum(0);
     65     _rotation_slider.setMaximum(1000);
     66     _rotation_slider.setValue(100);
     67     _rotation_slider.setOrientation(Qt::Horizontal);
     68 
     69     connect(&_renderer_select, SIGNAL(activated(int)), SLOT(onRendererChanged(int)));
     70     connect(&_limit_fps_check, SIGNAL(stateChanged(int)), SLOT(onLimitFpsChanged(int)));
     71 
     72     grid->addWidget(new QLabel("Renderer:"), 0, 0);
     73     grid->addWidget(&_renderer_select, 0, 1);
     74     grid->addWidget(&_colors_check_box, 0, 2);
     75     grid->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding), 0, 3);
     76     grid->addWidget(&_limit_fps_check, 0, 4, Qt::AlignRight);
     77 
     78     grid->addWidget(new QLabel("Count:"), 1, 0, Qt::AlignRight);
     79     grid->addWidget(&_count_slider, 1, 1, 1, 5);
     80 
     81     grid->addWidget(new QLabel("Rotation:"), 2, 0, Qt::AlignRight);
     82     grid->addWidget(&_rotation_slider, 2, 1, 1, 5);
     83 
     84     _canvas.on_render_blend2d = std::bind(&MainWindow::on_render_blend2d, this, std::placeholders::_1);
     85     _canvas.on_render_qt = std::bind(&MainWindow::on_render_qt, this, std::placeholders::_1);
     86 
     87     vBox->addItem(grid);
     88     vBox->addWidget(&_canvas);
     89     setLayout(vBox);
     90 
     91     connect(&_timer, SIGNAL(timeout()), this, SLOT(onTimer()));
     92     connect(new QShortcut(QKeySequence(Qt::Key_P), this), SIGNAL(activated()), SLOT(onToggleAnimate()));
     93 
     94     onInit();
     95   }
     96 
     97   void showEvent(QShowEvent* event) override { _timer.start(); }
     98   void hideEvent(QHideEvent* event) override { _timer.stop(); }
     99   void keyPressEvent(QKeyEvent* event) override {}
    100 
    101   void onInit() {
    102     _rnd.reset(1234);
    103     _limit_fps_check.setChecked(true);
    104     _updateTitle();
    105   }
    106 
    107   Q_SLOT void onToggleAnimate() { _animate = !_animate; }
    108   Q_SLOT void onRendererChanged(int index) { _canvas.set_renderer_type(_renderer_select.itemData(index).toInt()); }
    109   Q_SLOT void onLimitFpsChanged(int value) { _timer.setInterval(value ? 1000 / 120 : 0); }
    110 
    111   Q_SLOT void onTimer() {
    112     if (_animate) {
    113       size_t i = 0;
    114       size_t j = 0;
    115       size_t count = _particles.size();
    116 
    117       double rot = double(_rotation_slider.value()) * 0.02 / 1000;
    118       double PI = 3.14159265359;
    119       BLMatrix2D m = BLMatrix2D::make_rotation(rot);
    120 
    121       while (i < count) {
    122         Particle& p = _particles[i++];
    123         p.p += p.v;
    124         p.v = m.map_point(p.v);
    125         if (++p.age >= maxAge)
    126           continue;
    127         _particles[j++] = p;
    128       }
    129       _particles.resize(j);
    130 
    131       size_t maxParticles = size_t(_count_slider.value());
    132       size_t n = size_t(_rnd.next_double() * maxParticles / 60 + 0.95);
    133 
    134       for (i = 0; i < n; i++) {
    135         if (_particles.size() >= maxParticles)
    136           break;
    137 
    138         double angle = _rnd.next_double() * PI * 2.0;
    139         double speed = bl_max(_rnd.next_double() * 2.0, 0.05);
    140         double aSin = std::sin(angle);
    141         double aCos = std::cos(angle);
    142 
    143         Particle part;
    144         part.p.reset();
    145         part.v.reset(aCos * speed, aSin * speed);
    146         part.age = int(bl_min(_rnd.next_double(), 0.5) * maxAge);
    147         part.category = int(_rnd.next_double() * kCategoryCount);
    148         _particles.push_back(part);
    149       }
    150     }
    151 
    152     _canvas.update_canvas(true);
    153     _updateTitle();
    154   }
    155 
    156   void on_render_blend2d(BLContext& ctx) noexcept {
    157     ctx.fill_all(BLRgba32(0xFF000000u));
    158 
    159     double cx = _canvas.image_width() / 2;
    160     double cy = _canvas.image_height() / 2;
    161 
    162     if (_colors_check_box.isChecked()) {
    163       BLPath paths[kCategoryCount];
    164 
    165       for (Particle& part : _particles) {
    166         paths[part.category].add_circle(BLCircle(cx + part.p.x, cy + part.p.y, double(maxAge - part.age) / double(maxAge) * radius_scale));
    167       }
    168 
    169       ctx.set_comp_op(BL_COMP_OP_PLUS);
    170       for (size_t i = 0; i < kCategoryCount; i++) {
    171         ctx.fill_path(paths[i], colors[i]);
    172       }
    173     }
    174     else {
    175       BLPath path;
    176       for (Particle& part : _particles) {
    177         path.add_circle(BLCircle(cx + part.p.x, cy + part.p.y, double(maxAge - part.age) / double(maxAge) * radius_scale));
    178       }
    179       ctx.fill_path(path, BLRgba32(0xFFFFFFFFu));
    180     }
    181   }
    182 
    183   void on_render_qt(QPainter& ctx) noexcept {
    184     ctx.fillRect(0, 0, _canvas.image_width(), _canvas.image_height(), QColor(0, 0, 0));
    185     ctx.setRenderHint(QPainter::Antialiasing, true);
    186 
    187     double cx = _canvas.image_width() / 2;
    188     double cy = _canvas.image_height() / 2;
    189 
    190     if (_colors_check_box.isChecked()) {
    191       QPainterPath paths[kCategoryCount];
    192 
    193       for (Particle& part : _particles) {
    194         double r = double(maxAge - part.age) / double(maxAge) * radius_scale;
    195         double d = r * 2.0;
    196         paths[part.category].addEllipse(cx + part.p.x - r, cy + part.p.y - r, d, d);
    197       }
    198 
    199       ctx.setCompositionMode(QPainter::CompositionMode_Plus);
    200       for (size_t i = 0; i < kCategoryCount; i++) {
    201         paths[i].setFillRule(Qt::WindingFill);
    202         ctx.fillPath(paths[i], QBrush(bl_rgba_to_qcolor(colors[i])));
    203       }
    204     }
    205     else {
    206       QPainterPath p;
    207       p.setFillRule(Qt::WindingFill);
    208 
    209       for (Particle& part : _particles) {
    210         double r = double(maxAge - part.age) / double(maxAge) * radius_scale;
    211         double d = r * 2.0;
    212         p.addEllipse(cx + part.p.x - r, cy + part.p.y - r, d, d);
    213       }
    214 
    215       ctx.fillPath(p, QBrush(QColor(qRgb(255, 255, 255))));
    216     }
    217   }
    218 
    219   void _updateTitle() {
    220     char buf[256];
    221     snprintf(buf, 256, "Particles [%dx%d] [Count=%d] [RenderTime=%.2fms FPS=%.1f]",
    222       _canvas.image_width(),
    223       _canvas.image_height(),
    224       int(_particles.size()),
    225       _canvas.average_render_time(),
    226       _canvas.fps());
    227 
    228     QString title = QString::fromUtf8(buf);
    229     if (title != windowTitle())
    230       setWindowTitle(title);
    231   }
    232 };
    233 
    234 int main(int argc, char *argv[]) {
    235   QApplication app(argc, argv);
    236   MainWindow win;
    237 
    238   win.setMinimumSize(QSize(400, 320));
    239   win.resize(QSize(580, 520));
    240   win.show();
    241 
    242   return app.exec();
    243 }
    244 
    245 #include "bl_demo_particles.moc"