MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
fireworks.cpp
Go to the documentation of this file.
1#include "mxvk/argz.hpp"
2#include "mxvk/mxvk.hpp"
5
6#include <algorithm>
7#include <cmath>
8#include <cstdlib>
9#include <format>
10#include <iostream>
11#include <random>
12#include <string>
13#include <vector>
14
15#include <glm/ext/matrix_clip_space.hpp>
16#include <glm/ext/matrix_transform.hpp>
17#include <glm/glm.hpp>
18#include <glm/gtc/random.hpp>
19
20namespace {
21
22 constexpr std::size_t PARTICLES_PER_EXPLOSION = 250;
23 constexpr std::size_t MAX_EXPLOSIONS = 256;
25 constexpr float MIN_LIFETIME = 0.5f;
26 constexpr float MAX_LIFETIME = 2.0f;
27 constexpr float PARTICLE_SPEED = 1.0f;
28 constexpr float PARTICLE_SIZE = 25.0f;
29 constexpr float SCENE_REFERENCE_ASPECT = 16.0f / 9.0f;
30 constexpr float VERTICAL_FIELD_OF_VIEW_DEGREES = 45.0f;
31
32 struct Particle {
33 glm::vec3 position{};
34 glm::vec3 velocity{};
35 glm::vec4 color{};
36 float lifetime = 0.0f;
37 float initial_lifetime = 1.0f;
38 };
39
40 struct Explosion {
41 std::vector<Particle> particles{};
42 };
43
44 [[nodiscard]] float random_float(float min, float max) {
45 static std::random_device rd;
46 static std::default_random_engine engine(rd());
47 std::uniform_real_distribution<float> dist(min, max);
48 return dist(engine);
49 }
50
51} // namespace
52
53namespace example {
54
56 public:
57 FireworksWindow(const std::string &data_root, int width, int height, bool fullscreen, bool enable_vsync) : mxvk::VK_Window("Fireworks - [3D Particle Effect]", width, height, fullscreen, MXVK_VALIDATION, enable_vsync), data_root(data_root), vertices(MAX_PARTICLES) {
58 setClearColor(0.0f, 0.0f, 0.0f, 1.0f);
59 setFont(data_root + "/font.ttf", 24);
60 last_update_time = SDL_GetTicks();
61 }
62
63 ~FireworksWindow() override {
64 if (device != VK_NULL_HANDLE) {
65 vkDeviceWaitIdle(device);
66 }
67 point_batch.cleanup();
68 }
69
70 void event(SDL_Event &e) override {
71 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
72 exit();
73 } else if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_SPACE) {
74 trigger_random_explosion();
75 }
76 }
77
78 void proc() override {
79 if (!has_active_particles()) {
80 printText("Explosion - Press Space to Trigger", 25, 25, SDL_Color{255, 64, 64, 255});
81 }
82 }
83
84 void onSwapchainRecreated() override { point_batch.resize(this); }
85
86 void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override {
87 if (!ensure_resources()) {
88 return;
89 }
90
91 const Uint32 current_time = SDL_GetTicks();
92 float delta_time = static_cast<float>(current_time - last_update_time) / 1000.0f;
93 last_update_time = current_time;
94 delta_time = std::min(delta_time, 0.1f);
95
96 update_particles(delta_time);
97 write_vertices();
98
99 point_batch.upload_vertices(vertices.data(), active_vertices);
100 point_batch.update_mvp(image_index, make_mvp());
101 point_batch.render(cmd, image_index);
102 }
103
104 private:
105 bool ensure_resources() {
106 if (point_batch.loaded()) {
107 return true;
108 }
109 if (device == VK_NULL_HANDLE || command_pool == VK_NULL_HANDLE || getSwapchainImageCount() == 0U || getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
110 return false;
111 }
112
113 point_batch.load(this, data_root + "/star.png", data_root + "/fireworks.vert.spv", data_root + "/fireworks.frag.spv", vertices.size());
114 point_batch.set_additive_blending(true);
115 point_batch.set_depth_test_enabled(false);
116 point_batch.set_depth_write_enabled(false);
117 last_update_time = SDL_GetTicks();
118 return true;
119 }
120
121 void trigger_random_explosion() { trigger_explosion(glm::vec3(random_float(-3.0f, 3.0f), random_float(-3.0f, 3.0f), 0.0f)); }
122
123 void trigger_explosion(const glm::vec3 &origin) {
124 if (explosions.size() >= MAX_EXPLOSIONS) {
125 return;
126 }
127
128 Explosion explosion;
129 explosion.particles.resize(PARTICLES_PER_EXPLOSION);
130 for (Particle &particle : explosion.particles) {
131 reset_particle(particle, origin);
132 }
133 explosions.push_back(std::move(explosion));
134 }
135
136 void reset_particle(Particle &particle, const glm::vec3 &origin) const {
137 particle.position = origin;
138 particle.velocity = glm::sphericalRand(PARTICLE_SPEED);
139 particle.lifetime = random_float(MIN_LIFETIME, MAX_LIFETIME);
140 particle.initial_lifetime = particle.lifetime;
141 particle.color = glm::vec4(random_float(0.2f, 1.0f), random_float(0.2f, 1.0f), random_float(0.2f, 1.0f), 1.0f);
142 }
143
144 void update_particles(float delta_time) {
145 for (Explosion &explosion : explosions) {
146 for (Particle &particle : explosion.particles) {
147 if (particle.lifetime <= 0.0f) {
148 continue;
149 }
150 particle.lifetime -= delta_time;
151 particle.position += particle.velocity * delta_time;
152 particle.color.a = particle.lifetime;
153 }
154 }
155
156 std::erase_if(explosions, [](const Explosion &explosion) { return std::ranges::none_of(explosion.particles, [](const Particle &particle) { return particle.lifetime > 0.0f; }); });
157 }
158
159 [[nodiscard]] bool has_active_particles() const {
160 return std::ranges::any_of(explosions, [](const Explosion &explosion) { return std::ranges::any_of(explosion.particles, [](const Particle &particle) { return particle.lifetime > 0.0f; }); });
161 }
162
163 void write_vertices() {
164 active_vertices = 0;
165 for (const Explosion &explosion : explosions) {
166 for (const Particle &particle : explosion.particles) {
167 if (particle.lifetime <= 0.0f || active_vertices >= vertices.size()) {
168 continue;
169 }
170
171 mxvk::PointSpriteVertex &vertex = vertices[active_vertices++];
172 vertex.position[0] = particle.position.x;
173 vertex.position[1] = particle.position.y;
174 vertex.position[2] = particle.position.z;
175 vertex.size = PARTICLE_SIZE;
176 vertex.color[0] = particle.color.r;
177 vertex.color[1] = particle.color.g;
178 vertex.color[2] = particle.color.b;
179 vertex.color[3] = particle.color.a;
180 }
181 }
182 }
183
184 [[nodiscard]] glm::mat4 make_mvp() const {
185 const VkExtent2D extent = getSwapchainExtent();
186 const float aspect = extent.height > 0U ? static_cast<float>(extent.width) / static_cast<float>(extent.height) : SCENE_REFERENCE_ASPECT;
187
188 float vertical_field_of_view = glm::radians(VERTICAL_FIELD_OF_VIEW_DEGREES);
189 if (aspect < SCENE_REFERENCE_ASPECT) {
190 const float reference_half_width = std::tan(vertical_field_of_view * 0.5f) * SCENE_REFERENCE_ASPECT;
191 vertical_field_of_view = 2.0f * std::atan(reference_half_width / aspect);
192 }
193
194 glm::mat4 projection = glm::perspective(vertical_field_of_view, aspect, 0.1f, 100.0f);
195 projection[1][1] *= -1.0f;
196 const glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 5.0f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
197 const glm::mat4 model(1.0f);
198 return projection * view * model;
199 }
200
201 std::string data_root{};
202 std::vector<Explosion> explosions{};
203 std::vector<mxvk::PointSpriteVertex> vertices{};
204 mxvk::VK_PointSpriteBatch point_batch{};
205 std::size_t active_vertices = 0;
206 Uint32 last_update_time = 0;
207 };
208
209} // namespace example
210
211int main(int argc, char **argv) {
212 try {
213 const Arguments args = proc_args(argc, argv);
214 const std::string root = args.path.empty() ? std::string(FIREWORKS_ASSET_DIR) : args.path;
215 example::FireworksWindow window(root + "/data", args.width, args.height, args.fullscreen, args.enable_vsync);
216 window.loop();
217 } catch (mxvk::Exception &e) {
218 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
219 return EXIT_FAILURE;
220 } catch (ArgException<std::string> &e) {
221 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
222 return EXIT_FAILURE;
223 }
224
225 return EXIT_SUCCESS;
226}
Lightweight, header-only, template command-line argument parser.
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
Definition argz.hpp:854
Exception thrown by Argz::proc() on unrecognised or malformed options.
Definition argz.hpp:169
FireworksWindow(const std::string &data_root, int width, int height, bool fullscreen, bool enable_vsync)
Definition fireworks.cpp:57
void event(SDL_Event &e) override
Handle one SDL event.
Definition fireworks.cpp:70
void proc() override
Execute one processing/update step.
Definition fireworks.cpp:78
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
Definition fireworks.cpp:84
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override
Optional hook for derived classes to record extra draw commands.
Definition fireworks.cpp:86
std::string text() const
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:39
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:222
void loop()
Run the main event/render loop.
Definition mxvk.cpp:651
VkDevice device
Definition mxvk.hpp:606
size_t getSwapchainImageCount() const noexcept
Get the number of swapchain images currently allocated.
Definition mxvk.hpp:239
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
Definition mxvk.cpp:637
void exit()
Request loop termination.
Definition mxvk.cpp:1378
VkCommandPool command_pool
Definition mxvk.hpp:627
VK_Window()=default
Construct an empty window object.
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
Definition mxvk.cpp:3872
void printText(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for rendering during the current frame.
Definition mxvk.cpp:3919
VkFormat getSwapchainFormat() const noexcept
Get the swapchain color format.
Definition mxvk.hpp:213
#define MXVK_VALIDATION
Definition mxvk.hpp:28
Reusable point-sprite renderer for particle and starfield effects.
constexpr float SCENE_REFERENCE_ASPECT
Definition bluesky.cpp:57
constexpr std::size_t PARTICLES_PER_EXPLOSION
Definition fireworks.cpp:22
float random_float(float min, float max)
Definition fireworks.cpp:44
constexpr std::size_t MAX_PARTICLES
Definition fireworks.cpp:24
constexpr std::size_t MAX_EXPLOSIONS
Definition fireworks.cpp:23
constexpr float VERTICAL_FIELD_OF_VIEW_DEGREES
Definition fireworks.cpp:30
int main()
Definition main.py:165
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
float random_float(float min_value, float max_value)
Generates a uniformly distributed floating-point value.
constexpr int MAX_PARTICLES
Definition space.cpp:30
Plain data structure returned by proc_args() with all common libmx2 CLI options.
Definition argz.hpp:718
bool fullscreen
Whether fullscreen mode was requested.
Definition argz.hpp:724
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:738
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:721
std::string path
Asset root; proc_args() defaults it to the executable directory.
Definition argz.hpp:723
int width
Viewport width in pixels (default: 1280).
Definition argz.hpp:720
float y
Definition space.cpp:76
int lifetime
Definition space.cpp:78
float x
Definition space.cpp:76
float color[4]
RGBA tint color passed to shader location 2.
float size
Rasterized point size in pixels, passed to shader location 1.
float position[3]
Vertex position passed to shader location 0.