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"