Fire.cpp 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /* Fire.cpp - a (classic) graphics demo for Serenity, by pd.
  2. * heavily based on the Fabien Sanglard's article:
  3. * http://fabiensanglard.net/doom_fire_psx/index.html
  4. *
  5. * Future directions:
  6. * [X] This does suggest the need for a palletized graphics surface. Thanks kling!
  7. * [X] alternate column updates, or vertical interlacing. this would certainly alter
  8. * the effect, but the update load would be halved.
  9. * [/] scaled blit
  10. * [ ] dithering?
  11. * [X] inlining rand()
  12. * [/] precalculating and recycling random data
  13. * [ ] rework/expand palette
  14. * [ ] switch to use tsc values for perf check
  15. * [ ] handle mouse events differently for smoother painting (queue)
  16. * [ ] handle fire bitmap edges better
  17. */
  18. #include <LibDraw/GraphicsBitmap.h>
  19. #include <LibDraw/PNGLoader.h>
  20. #include <LibGUI/GApplication.h>
  21. #include <LibGUI/GLabel.h>
  22. #include <LibGUI/GPainter.h>
  23. #include <LibGUI/GWidget.h>
  24. #include <LibGUI/GWindow.h>
  25. #include <stdio.h>
  26. #include <stdlib.h>
  27. #include <time.h>
  28. #define FIRE_WIDTH 320
  29. #define FIRE_HEIGHT 168
  30. #define FIRE_MAX 29
  31. static const Color s_palette[] = {
  32. Color(0x07, 0x07, 0x07), Color(0x1F, 0x07, 0x07), Color(0x2F, 0x0F, 0x07),
  33. Color(0x47, 0x0F, 0x07), Color(0x57, 0x17, 0x07), Color(0x67, 0x1F, 0x07),
  34. Color(0x77, 0x1F, 0x07), Color(0x9F, 0x2F, 0x07), Color(0xAF, 0x3F, 0x07),
  35. Color(0xBF, 0x47, 0x07), Color(0xC7, 0x47, 0x07), Color(0xDF, 0x4F, 0x07),
  36. Color(0xDF, 0x57, 0x07), Color(0xD7, 0x5F, 0x07), Color(0xD7, 0x5F, 0x07),
  37. Color(0xD7, 0x67, 0x0F), Color(0xCF, 0x6F, 0x0F), Color(0xCF, 0x7F, 0x0F),
  38. Color(0xCF, 0x87, 0x17), Color(0xC7, 0x87, 0x17), Color(0xC7, 0x8F, 0x17),
  39. Color(0xC7, 0x97, 0x1F), Color(0xBF, 0x9F, 0x1F), Color(0xBF, 0xA7, 0x27),
  40. Color(0xBF, 0xAF, 0x2F), Color(0xB7, 0xAF, 0x2F), Color(0xB7, 0xB7, 0x37),
  41. Color(0xCF, 0xCF, 0x6F), Color(0xEF, 0xEF, 0xC7), Color(0xFF, 0xFF, 0xFF)
  42. };
  43. /* Random functions...
  44. * These are from musl libc's prng/rand.c
  45. */
  46. static uint64_t seed;
  47. void my_srand(unsigned s)
  48. {
  49. seed = s - 1;
  50. }
  51. static int my_rand(void)
  52. {
  53. seed = 6364136223846793005ULL * seed + 1;
  54. return seed >> 33;
  55. }
  56. /*
  57. * Fire Widget
  58. */
  59. class Fire : public GWidget {
  60. C_OBJECT(Fire)
  61. public:
  62. virtual ~Fire() override;
  63. void set_stat_label(GLabel* l) { stats = l; };
  64. private:
  65. explicit Fire(GWidget* parent = nullptr);
  66. RefPtr<GraphicsBitmap> bitmap;
  67. GLabel* stats;
  68. virtual void paint_event(GPaintEvent&) override;
  69. virtual void timer_event(CTimerEvent&) override;
  70. virtual void mousedown_event(GMouseEvent& event) override;
  71. virtual void mousemove_event(GMouseEvent& event) override;
  72. virtual void mouseup_event(GMouseEvent& event) override;
  73. bool dragging;
  74. int timeAvg;
  75. int cycles;
  76. int phase;
  77. };
  78. Fire::Fire(GWidget* parent)
  79. : GWidget(parent)
  80. {
  81. bitmap = GraphicsBitmap::create(GraphicsBitmap::Format::Indexed8, { 320, 200 });
  82. /* Initialize fire palette */
  83. for (int i = 0; i < 30; i++)
  84. bitmap->set_palette_color(i, s_palette[i]);
  85. /* Set remaining entries to white */
  86. for (int i = 30; i < 256; i++)
  87. bitmap->set_palette_color(i, Color::White);
  88. dragging = false;
  89. timeAvg = 0;
  90. cycles = 0;
  91. phase = 0;
  92. my_srand(time(nullptr));
  93. stop_timer();
  94. start_timer(20);
  95. /* Draw fire "source" on bottom row of pixels */
  96. for (int i = 0; i < FIRE_WIDTH; i++)
  97. bitmap->bits(bitmap->height() - 1)[i] = FIRE_MAX;
  98. /* Set off initital paint event */
  99. //update();
  100. }
  101. Fire::~Fire()
  102. {
  103. }
  104. void Fire::paint_event(GPaintEvent& event)
  105. {
  106. CElapsedTimer timer;
  107. timer.start();
  108. GPainter painter(*this);
  109. painter.add_clip_rect(event.rect());
  110. /* Blit it! */
  111. painter.draw_scaled_bitmap(event.rect(), *bitmap, bitmap->rect());
  112. timeAvg += timer.elapsed();
  113. cycles++;
  114. }
  115. void Fire::timer_event(CTimerEvent&)
  116. {
  117. /* Update only even or odd columns per frame... */
  118. phase++;
  119. if (phase > 1)
  120. phase = 0;
  121. /* Paint our palettized buffer to screen */
  122. for (int px = 0 + phase; px < FIRE_WIDTH; px += 2) {
  123. for (int py = 1; py < 200; py++) {
  124. int rnd = my_rand() % 3;
  125. /* Calculate new pixel value, don't go below 0 */
  126. u8 nv = bitmap->bits(py)[px];
  127. if (nv > 0)
  128. nv -= (rnd & 1);
  129. /* ...sigh... */
  130. int epx = px + (1 - rnd);
  131. if (epx < 0)
  132. epx = 0;
  133. else if (epx > FIRE_WIDTH)
  134. epx = FIRE_WIDTH;
  135. bitmap->bits(py - 1)[epx] = nv;
  136. }
  137. }
  138. if ((cycles % 50) == 0) {
  139. dbgprintf("%d total cycles. finished 50 in %d ms, avg %d ms\n", cycles, timeAvg, timeAvg / 50);
  140. stats->set_text(String::format("%d ms", timeAvg / 50));
  141. timeAvg = 0;
  142. }
  143. update();
  144. }
  145. /*
  146. * Mouse handling events
  147. */
  148. void Fire::mousedown_event(GMouseEvent& event)
  149. {
  150. if (event.button() == GMouseButton::Left)
  151. dragging = true;
  152. return GWidget::mousedown_event(event);
  153. }
  154. /* FIXME: needs to account for the size of the window rect */
  155. void Fire::mousemove_event(GMouseEvent& event)
  156. {
  157. if (dragging) {
  158. if (event.y() >= 2 && event.y() < 398 && event.x() <= 638) {
  159. int ypos = event.y() / 2;
  160. int xpos = event.x() / 2;
  161. bitmap->bits(ypos - 1)[xpos] = FIRE_MAX + 5;
  162. bitmap->bits(ypos - 1)[xpos + 1] = FIRE_MAX + 5;
  163. bitmap->bits(ypos)[xpos] = FIRE_MAX + 5;
  164. bitmap->bits(ypos)[xpos + 1] = FIRE_MAX + 5;
  165. }
  166. }
  167. return GWidget::mousemove_event(event);
  168. }
  169. void Fire::mouseup_event(GMouseEvent& event)
  170. {
  171. if (event.button() == GMouseButton::Left)
  172. dragging = false;
  173. return GWidget::mouseup_event(event);
  174. }
  175. /*
  176. * Main
  177. */
  178. int main(int argc, char** argv)
  179. {
  180. GApplication app(argc, argv);
  181. auto window = GWindow::construct();
  182. window->set_double_buffering_enabled(false);
  183. window->set_title("Fire");
  184. window->set_resizable(false);
  185. window->set_rect(100, 100, 640, 400);
  186. auto fire = Fire::construct();
  187. window->set_main_widget(fire);
  188. auto time = GLabel::construct(fire);
  189. time->set_relative_rect({ 0, 4, 40, 10 });
  190. time->move_by({ window->width() - time->width(), 0 });
  191. time->set_foreground_color(Color::from_rgb(0x444444));
  192. fire->set_stat_label(time);
  193. window->show();
  194. window->set_icon(load_png("/res/icons/16x16/app-demo.png"));
  195. return app.exec();
  196. }