Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27/* Fire.cpp - a (classic) graphics demo for Serenity, by pd.
28 * heavily based on the Fabien Sanglard's article:
29 * http://fabiensanglard.net/doom_fire_psx/index.html
30 *
31 * Future directions:
32 * [X] This does suggest the need for a palletized graphics surface. Thanks kling!
33 * [X] alternate column updates, or vertical interlacing. this would certainly alter
34 * the effect, but the update load would be halved.
35 * [/] scaled blit
36 * [ ] dithering?
37 * [X] inlining rand()
38 * [/] precalculating and recycling random data
39 * [ ] rework/expand palette
40 * [ ] switch to use tsc values for perf check
41 * [ ] handle mouse events differently for smoother painting (queue)
42 * [ ] handle fire bitmap edges better
43*/
44
45#include <LibCore/ElapsedTimer.h>
46#include <LibGUI/Application.h>
47#include <LibGUI/Label.h>
48#include <LibGUI/Painter.h>
49#include <LibGUI/Widget.h>
50#include <LibGUI/Window.h>
51#include <LibGfx/Bitmap.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <time.h>
55
56#define FIRE_WIDTH 320
57#define FIRE_HEIGHT 168
58#define FIRE_MAX 29
59
60static const Color s_palette[] = {
61 Color(0x07, 0x07, 0x07), Color(0x1F, 0x07, 0x07), Color(0x2F, 0x0F, 0x07),
62 Color(0x47, 0x0F, 0x07), Color(0x57, 0x17, 0x07), Color(0x67, 0x1F, 0x07),
63 Color(0x77, 0x1F, 0x07), Color(0x9F, 0x2F, 0x07), Color(0xAF, 0x3F, 0x07),
64 Color(0xBF, 0x47, 0x07), Color(0xC7, 0x47, 0x07), Color(0xDF, 0x4F, 0x07),
65 Color(0xDF, 0x57, 0x07), Color(0xD7, 0x5F, 0x07), Color(0xD7, 0x5F, 0x07),
66 Color(0xD7, 0x67, 0x0F), Color(0xCF, 0x6F, 0x0F), Color(0xCF, 0x7F, 0x0F),
67 Color(0xCF, 0x87, 0x17), Color(0xC7, 0x87, 0x17), Color(0xC7, 0x8F, 0x17),
68 Color(0xC7, 0x97, 0x1F), Color(0xBF, 0x9F, 0x1F), Color(0xBF, 0xA7, 0x27),
69 Color(0xBF, 0xAF, 0x2F), Color(0xB7, 0xAF, 0x2F), Color(0xB7, 0xB7, 0x37),
70 Color(0xCF, 0xCF, 0x6F), Color(0xEF, 0xEF, 0xC7), Color(0xFF, 0xFF, 0xFF)
71};
72
73/* Random functions...
74 * These are from musl libc's prng/rand.c
75*/
76static uint64_t seed;
77
78void my_srand(unsigned s)
79{
80 seed = s - 1;
81}
82
83static int my_rand(void)
84{
85 seed = 6364136223846793005ULL * seed + 1;
86 return seed >> 33;
87}
88
89/*
90 * Fire Widget
91*/
92class Fire : public GUI::Widget {
93 C_OBJECT(Fire)
94public:
95 virtual ~Fire() override;
96 void set_stat_label(RefPtr<GUI::Label> l) { stats = l; };
97
98private:
99 Fire();
100 RefPtr<Gfx::Bitmap> bitmap;
101 RefPtr<GUI::Label> stats;
102
103 virtual void paint_event(GUI::PaintEvent&) override;
104 virtual void timer_event(Core::TimerEvent&) override;
105 virtual void mousedown_event(GUI::MouseEvent& event) override;
106 virtual void mousemove_event(GUI::MouseEvent& event) override;
107 virtual void mouseup_event(GUI::MouseEvent& event) override;
108
109 bool dragging;
110 int timeAvg;
111 int cycles;
112 int phase;
113};
114
115Fire::Fire()
116{
117 bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::Indexed8, { 320, 200 });
118
119 /* Initialize fire palette */
120 for (int i = 0; i < 30; i++)
121 bitmap->set_palette_color(i, s_palette[i]);
122
123 /* Set remaining entries to white */
124 for (int i = 30; i < 256; i++)
125 bitmap->set_palette_color(i, Color::White);
126
127 dragging = false;
128 timeAvg = 0;
129 cycles = 0;
130 phase = 0;
131
132 my_srand(time(nullptr));
133 stop_timer();
134 start_timer(20);
135
136 /* Draw fire "source" on bottom row of pixels */
137 for (int i = 0; i < FIRE_WIDTH; i++)
138 bitmap->bits(bitmap->height() - 1)[i] = FIRE_MAX;
139
140 /* Set off initital paint event */
141 //update();
142}
143
144Fire::~Fire()
145{
146}
147
148void Fire::paint_event(GUI::PaintEvent& event)
149{
150 Core::ElapsedTimer timer;
151 timer.start();
152
153 GUI::Painter painter(*this);
154 painter.add_clip_rect(event.rect());
155
156 /* Blit it! */
157 painter.draw_scaled_bitmap(event.rect(), *bitmap, bitmap->rect());
158
159 timeAvg += timer.elapsed();
160 cycles++;
161}
162
163void Fire::timer_event(Core::TimerEvent&)
164{
165 /* Update only even or odd columns per frame... */
166 phase++;
167 if (phase > 1)
168 phase = 0;
169
170 /* Paint our palettized buffer to screen */
171 for (int px = 0 + phase; px < FIRE_WIDTH; px += 2) {
172 for (int py = 1; py < 200; py++) {
173 int rnd = my_rand() % 3;
174
175 /* Calculate new pixel value, don't go below 0 */
176 u8 nv = bitmap->bits(py)[px];
177 if (nv > 0)
178 nv -= (rnd & 1);
179
180 /* ...sigh... */
181 int epx = px + (1 - rnd);
182 if (epx < 0)
183 epx = 0;
184 else if (epx > FIRE_WIDTH)
185 epx = FIRE_WIDTH;
186
187 bitmap->bits(py - 1)[epx] = nv;
188 }
189 }
190
191 if ((cycles % 50) == 0) {
192 dbgprintf("%d total cycles. finished 50 in %d ms, avg %d ms\n", cycles, timeAvg, timeAvg / 50);
193 stats->set_text(String::format("%d ms", timeAvg / 50));
194 timeAvg = 0;
195 }
196
197 update();
198}
199
200/*
201 * Mouse handling events
202*/
203void Fire::mousedown_event(GUI::MouseEvent& event)
204{
205 if (event.button() == GUI::MouseButton::Left)
206 dragging = true;
207
208 return GUI::Widget::mousedown_event(event);
209}
210
211/* FIXME: needs to account for the size of the window rect */
212void Fire::mousemove_event(GUI::MouseEvent& event)
213{
214 if (dragging) {
215 if (event.y() >= 2 && event.y() < 398 && event.x() <= 638) {
216 int ypos = event.y() / 2;
217 int xpos = event.x() / 2;
218 bitmap->bits(ypos - 1)[xpos] = FIRE_MAX + 5;
219 bitmap->bits(ypos - 1)[xpos + 1] = FIRE_MAX + 5;
220 bitmap->bits(ypos)[xpos] = FIRE_MAX + 5;
221 bitmap->bits(ypos)[xpos + 1] = FIRE_MAX + 5;
222 }
223 }
224
225 return GUI::Widget::mousemove_event(event);
226}
227
228void Fire::mouseup_event(GUI::MouseEvent& event)
229{
230 if (event.button() == GUI::MouseButton::Left)
231 dragging = false;
232
233 return GUI::Widget::mouseup_event(event);
234}
235
236/*
237 * Main
238*/
239int main(int argc, char** argv)
240{
241 GUI::Application app(argc, argv);
242
243 auto window = GUI::Window::construct();
244 window->set_double_buffering_enabled(false);
245 window->set_title("Fire");
246 window->set_resizable(false);
247 window->set_rect(100, 100, 640, 400);
248
249 auto fire = Fire::construct();
250 window->set_main_widget(fire);
251
252 auto time = fire->add<GUI::Label>();
253 time->set_relative_rect({ 0, 4, 40, 10 });
254 time->move_by({ window->width() - time->width(), 0 });
255 time->set_foreground_color(Color::from_rgb(0x444444));
256 fire->set_stat_label(time);
257
258 window->show();
259 window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-demo.png"));
260
261 return app.exec();
262}