MXVK Vulkan Framework 0.33.1
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)
58 : mxvk::VK_Window("Fireworks - [3D Particle Effect]", width, height, fullscreen, MXVK_VALIDATION, enable_vsync),
59 data_root(data_root),
60 vertices(MAX_PARTICLES) {
61 setClearColor(0.0f, 0.0f, 0.0f, 1.0f);
62 setFont(data_root + "/font.ttf", 24);
63 last_update_time = SDL_GetTicks();
64 }
65
66 ~FireworksWindow() override {
67 if (device != VK_NULL_HANDLE) {
68 vkDeviceWaitIdle(device);
69 }
70 point_batch.cleanup();
71 }
72
73 void event(SDL_Event &e) override {
74 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
75 exit();
76 } else if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_SPACE) {
77 trigger_random_explosion();
78 }
79 }
80
81 void proc() override {
82 if (!has_active_particles()) {
83 printText("Explosion - Press Space to Trigger", 25, 25, SDL_Color{255, 64, 64, 255});
84 }
85 }
86
87 void onSwapchainRecreated() override {
88 point_batch.resize(this);
89 }
90
91 void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override {
92 if (!ensure_resources()) {
93 return;
94 }
95
96 const Uint32 current_time = SDL_GetTicks();
97 float delta_time = static_cast<float>(current_time - last_update_time) / 1000.0f;
98 last_update_time = current_time;
99 delta_time = std::min(delta_time, 0.1f);
100
101 update_particles(delta_time);
102 write_vertices();
103
104 point_batch.upload_vertices(vertices.data(), active_vertices);
105 point_batch.update_mvp(image_index, make_mvp());
106 point_batch.render(cmd, image_index);
107 }
108
109 private:
110 bool ensure_resources() {
111 if (point_batch.loaded()) {
112 return true;
113 }
114 if (device == VK_NULL_HANDLE || command_pool == VK_NULL_HANDLE || getSwapchainImageCount() == 0U || getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
115 return false;
116 }
117
118 point_batch.load(
119 this,
120 data_root + "/star.png",
121 data_root + "/fireworks.vert.spv",
122 data_root + "/fireworks.frag.spv",
123 vertices.size());
124 point_batch.set_additive_blending(true);
125 point_batch.set_depth_test_enabled(false);
126 point_batch.set_depth_write_enabled(false);
127 last_update_time = SDL_GetTicks();
128 return true;
129 }
130
131 void trigger_random_explosion() {
132 trigger_explosion(glm::vec3(random_float(-3.0f, 3.0f), random_float(-3.0f, 3.0f), 0.0f));
133 }
134
135 void trigger_explosion(const glm::vec3 &origin) {
136 if (explosions.size() >= MAX_EXPLOSIONS) {
137 return;
138 }
139
140 Explosion explosion;
141 explosion.particles.resize(PARTICLES_PER_EXPLOSION);
142 for (Particle &particle : explosion.particles) {
143 reset_particle(particle, origin);
144 }
145 explosions.push_back(std::move(explosion));
146 }
147
148 void reset_particle(Particle &particle, const glm::vec3 &origin) const {
149 particle.position = origin;
150 particle.velocity = glm::sphericalRand(PARTICLE_SPEED);
151 particle.lifetime = random_float(MIN_LIFETIME, MAX_LIFETIME);
152 particle.initial_lifetime = particle.lifetime;
153 particle.color = glm::vec4(random_float(0.2f, 1.0f), random_float(0.2f, 1.0f), random_float(0.2f, 1.0f), 1.0f);
154 }
155
156 void update_particles(float delta_time) {
157 for (Explosion &explosion : explosions) {
158 for (Particle &particle : explosion.particles) {
159 if (particle.lifetime <= 0.0f) {
160 continue;
161 }
162 particle.lifetime -= delta_time;
163 particle.position += particle.velocity * delta_time;
164 particle.color.a = particle.lifetime;
165 }
166 }
167
168 std::erase_if(explosions, [](const Explosion &explosion) {
169 return std::ranges::none_of(explosion.particles, [](const Particle &particle) {
170 return particle.lifetime > 0.0f;
171 });
172 });
173 }
174
175 [[nodiscard]] bool has_active_particles() const {
176 return std::ranges::any_of(explosions, [](const Explosion &explosion) {
177 return std::ranges::any_of(explosion.particles, [](const Particle &particle) {
178 return particle.lifetime > 0.0f;
179 });
180 });
181 }
182
183 void write_vertices() {
184 active_vertices = 0;
185 for (const Explosion &explosion : explosions) {
186 for (const Particle &particle : explosion.particles) {
187 if (particle.lifetime <= 0.0f || active_vertices >= vertices.size()) {
188 continue;
189 }
190
191 mxvk::PointSpriteVertex &vertex = vertices[active_vertices++];
192 vertex.position[0] = particle.position.x;
193 vertex.position[1] = particle.position.y;
194 vertex.position[2] = particle.position.z;
195 vertex.size = PARTICLE_SIZE;
196 vertex.color[0] = particle.color.r;
197 vertex.color[1] = particle.color.g;
198 vertex.color[2] = particle.color.b;
199 vertex.color[3] = particle.color.a;
200 }
201 }
202 }
203
204 [[nodiscard]] glm::mat4 make_mvp() const {
205 const VkExtent2D extent = getSwapchainExtent();
206 const float aspect = extent.height > 0U ? static_cast<float>(extent.width) / static_cast<float>(extent.height) : SCENE_REFERENCE_ASPECT;
207
208 float vertical_field_of_view = glm::radians(VERTICAL_FIELD_OF_VIEW_DEGREES);
209 if (aspect < SCENE_REFERENCE_ASPECT) {
210 const float reference_half_width = std::tan(vertical_field_of_view * 0.5f) * SCENE_REFERENCE_ASPECT;
211 vertical_field_of_view = 2.0f * std::atan(reference_half_width / aspect);
212 }
213
214 glm::mat4 projection = glm::perspective(vertical_field_of_view, aspect, 0.1f, 100.0f);
215 projection[1][1] *= -1.0f;
216 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));
217 const glm::mat4 model(1.0f);
218 return projection * view * model;
219 }
220
221 std::string data_root{};
222 std::vector<Explosion> explosions{};
223 std::vector<mxvk::PointSpriteVertex> vertices{};
224 mxvk::VK_PointSpriteBatch point_batch{};
225 std::size_t active_vertices = 0;
226 Uint32 last_update_time = 0;
227 };
228
229} // namespace example
230
231int main(int argc, char **argv) {
232 try {
233 const Arguments args = proc_args(argc, argv);
234 const std::string root = args.path.empty() ? std::string(FIREWORKS_ASSET_DIR) : args.path;
235 example::FireworksWindow window(root + "/data", args.width, args.height, args.fullscreen, args.enable_vsync);
236 window.loop();
237 } catch (mxvk::Exception &e) {
238 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
239 return EXIT_FAILURE;
240 } catch (ArgException<std::string> &e) {
241 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
242 return EXIT_FAILURE;
243 }
244
245 return EXIT_SUCCESS;
246}
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:872
Exception thrown by Argz::proc() on unrecognised or malformed options.
Definition argz.hpp:178
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:73
void proc() override
Execute one processing/update step.
Definition fireworks.cpp:81
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
Definition fireworks.cpp:87
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override
Optional hook for derived classes to record extra draw commands.
Definition fireworks.cpp:91
std::string text() const
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:38
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:210
void loop()
Run the main event/render loop.
Definition mxvk.cpp:627
VkDevice device
Definition mxvk.hpp:596
size_t getSwapchainImageCount() const noexcept
Get the number of swapchain images currently allocated.
Definition mxvk.hpp:227
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
Definition mxvk.cpp:613
void exit()
Request loop termination.
Definition mxvk.cpp:1429
VkCommandPool command_pool
Definition mxvk.hpp:616
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:4002
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:4051
VkFormat getSwapchainFormat() const noexcept
Get the swapchain color format.
Definition mxvk.hpp:207
int main(int argc, char **argv)
Definition main.cpp:173
#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
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:730
bool fullscreen
Whether fullscreen mode was requested.
Definition argz.hpp:736
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:750
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:733
std::string path
Asset root; proc_args() defaults it to the executable directory.
Definition argz.hpp:735
int width
Viewport width in pixels (default: 1280).
Definition argz.hpp:732
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.