particle_system.hpp 15.1 KB
Newer Older
1
2
#pragma once

3
4
#include <mirrage/renderer/model.hpp>

5
#include <mirrage/asset/asset_manager.hpp>
6
#include <mirrage/ecs/entity_manager.hpp>
7
#include <mirrage/graphic/texture.hpp>
8
#include <mirrage/utils/random.hpp>
9
#include <mirrage/utils/sf2_glm.hpp>
10
#include <mirrage/utils/small_vector.hpp>
11
12
13
14
15
#include <mirrage/utils/units.hpp>

#include <glm/vec3.hpp>
#include <sf2/sf2.hpp>

16
#include <memory>
17
18
#include <tuple>

19
20
21

namespace mirrage::renderer {

22
	struct Particle {
23
24
25
		glm::vec4 position; // xyz + ttl_left
		glm::vec4 velocity; // xyz + ttl_initial
		glm::uvec4
Florian Oetke's avatar
Florian Oetke committed
26
		        data; // last_feedback_buffer_index, seed, keyframe, floatBitsToUint(keyframe_interpolation_factor)
27
28
	};

29
30
31
32
	class Particle_script {
	  public:
		explicit Particle_script(vk::UniquePipeline pipeline) : _pipeline(std::move(pipeline)) {}

33
		void bind(vk::CommandBuffer) const;
34
35
36
37
38

	  private:
		vk::UniquePipeline _pipeline;
	};

Florian Oetke's avatar
Florian Oetke committed
39
40
	enum class Particle_blend_mode { solid, transparent, transparent_unlit };
	sf2_enumDef(Particle_blend_mode, solid, transparent, transparent_unlit);
41

42
43
	enum class Particle_geometry { billboard, mesh };
	sf2_enumDef(Particle_geometry, billboard, mesh);
44
45

	struct Particle_color {
46
47
48
49
		float hue        = 0.f;
		float saturation = 0.f;
		float value      = 0.f;
		float alpha      = 0.f;
50
	};
51
	sf2_structDef(Particle_color, hue, saturation, value, alpha);
52

53
	/// angle + axis rotation with the axis expressed in spherical coordinates
Florian Oetke's avatar
Florian Oetke committed
54
	/// elevation=0 and azimuth=0 is (0,0,1) and the base-plane is XZ
55
	struct Particle_rotation {
Florian Oetke's avatar
Florian Oetke committed
56
		float elevation = 0.f; //< 0=0°, 1= 90°
57
58
59
60
61
62
		float azimuth   = 0.f; //< 0=0°, 1=360°
		float angle     = 0.f; //< 0=0°, 1=360°
		float padding;
	};
	sf2_structDef(Particle_rotation, elevation, azimuth, angle);

Florian Oetke's avatar
Florian Oetke committed
63
64
65
66
67
68
69
70
	struct Particle_direction {
		float elevation          = 1.f;
		float azimuth            = 0.f;
		float velocity_elevation = 1.f;
		float velocity_azimuth   = 0.f;
	};
	sf2_structDef(Particle_direction, elevation, azimuth, velocity_elevation, velocity_azimuth);

71
72
73
74
	template <typename T>
	struct Random_value {
		T mean{};
		T stddev{};
75
	};
76
77
	sf2_structDef(Random_value<float>, mean, stddev);
	sf2_structDef(Random_value<glm::vec4>, mean, stddev);
78
	sf2_structDef(Random_value<Particle_rotation>, mean, stddev);
79
	sf2_structDef(Random_value<Particle_color>, mean, stddev);
Florian Oetke's avatar
Florian Oetke committed
80
	sf2_structDef(Random_value<Particle_direction>, mean, stddev);
81

82

83
84
	/// modify velocities of living particles
	/// e.g.
Florian Oetke's avatar
Florian Oetke committed
85
	/// gravity: {..., force=10, dir={0,-1,0}, decay=0}
86
87
88
89
	/// point: {position=? dir={0,0,0}, decay=2}
	/// flow: {position=? dir={1,0,0}, decay=2}
	struct Particle_effector_config {
		glm::quat rotation{1, 0, 0, 0};
90
		glm::vec3 position{0, 0, 0};
Florian Oetke's avatar
Florian Oetke committed
91
92
		bool      absolute = false;

93
94
95
96
		float     force = 0.f;
		glm::vec3 force_dir{0, 0, 0};
		float     distance_decay = 2.f;

Florian Oetke's avatar
Florian Oetke committed
97
98
		bool  scale_with_mass     = true;
		float negative_mass_scale = 0.f; //< 0: clamp mass to >=0; >0: inverte direction; 0.5: force/2
99
100
101
102
	};
	sf2_structDef(Particle_effector_config,
	              position,
	              rotation,
Florian Oetke's avatar
Florian Oetke committed
103
	              absolute,
104
105
106
	              force,
	              force_dir,
	              distance_decay,
Florian Oetke's avatar
Florian Oetke committed
107
108
	              scale_with_mass,
	              negative_mass_scale);
109

110
	struct Particle_keyframe {
Florian Oetke's avatar
Florian Oetke committed
111
112
113
114
		Random_value<Particle_color>    color     = {{1, 0, 1, 1}};
		Random_value<Particle_rotation> rotation  = {};
		Random_value<glm::vec4>         size      = {{1.f, 1.f, 1.f, 0.f}};
		glm::vec4                       clip_rect = {0, 0, 1, 1};
115
116
117
118
119
120

		float time      = 0;
		float base_mass = 1;
		float density   = 0;
		float drag      = 0.f;
	};
Florian Oetke's avatar
Florian Oetke committed
121
122
	sf2_structDef(Particle_keyframe, color, rotation, size, clip_rect, time, base_mass, density, drag);
	static_assert(sizeof(Particle_keyframe) == sizeof(float) * (4 * 3 * 2 + 4 + 4),
123
	              "Particle_keyframe contains padding");
124

125
126
	/// describes how living particles are updated and drawn
	struct Particle_type_config {
127
		util::small_vector<Particle_keyframe, 3> keyframes;
128

129
130
131
132
133
134
135
136
137
138
		bool color_normal_distribution_h    = false;
		bool color_normal_distribution_s    = false;
		bool color_normal_distribution_v    = false;
		bool color_normal_distribution_a    = false;
		bool rotation_normal_distribution_x = false;
		bool rotation_normal_distribution_y = false;
		bool rotation_normal_distribution_z = false;
		bool size_normal_distribution_x     = false;
		bool size_normal_distribution_y     = false;
		bool size_normal_distribution_z     = false;
139

Florian Oetke's avatar
Florian Oetke committed
140
141
142
		bool  rotate_with_velocity = false;
		bool  symmetric_scaling    = false; //< also use x size for y and z
		float loop_keyframe_time   = 0.f;
143

144
145
146
		Particle_blend_mode blend    = Particle_blend_mode::transparent;
		Particle_geometry   geometry = Particle_geometry::billboard;

147
148
149
150
		float update_range = -1.f;
		float draw_range   = -1.f;
		bool  shadowcaster = false;

151
152
153
154
155
156
		std::string            material_id;
		renderer::Material_ptr material;

		std::string                 model_id;
		asset::Ptr<renderer::Model> model;

157
		std::string                 update_script_id;
158
		asset::Ptr<Particle_script> update_script;
159
	};
160
	sf2_structDef(Particle_type_config,
161
162
163
164
165
166
167
168
169
170
171
172
	              keyframes,
	              color_normal_distribution_h,
	              color_normal_distribution_s,
	              color_normal_distribution_v,
	              color_normal_distribution_a,
	              rotation_normal_distribution_x,
	              rotation_normal_distribution_y,
	              rotation_normal_distribution_z,
	              size_normal_distribution_x,
	              size_normal_distribution_y,
	              size_normal_distribution_z,
	              rotate_with_velocity,
173
	              symmetric_scaling,
Florian Oetke's avatar
Florian Oetke committed
174
	              loop_keyframe_time,
175
	              blend,
176
	              geometry,
177
178
179
	              update_range,
	              draw_range,
	              shadowcaster,
180
181
182
183
	              material_id,
	              model_id,
	              update_script_id);

184
185
186

	struct Particle_emitter_spawn {
		float particles_per_second = 10.f;
187
		float stddev               = 0.f;
188
189
		float time                 = -1.f;
	};
190
	sf2_structDef(Particle_emitter_spawn, particles_per_second, stddev, time);
191
192
193
194
195
196
197

	// describes how new particles are created
	struct Particle_emitter_config {
		Random_value<float> ttl = {1.f};

		Random_value<float> velocity = {1.f};

198
199
200
		glm::vec4 size = {0.f, 1.f, 0.f, 0.f}; // min_radius, max_radius, <ignored>, <ignored>

		bool independent_direction = false; // initial velocity direction is independent of spawn position
Florian Oetke's avatar
Florian Oetke committed
201
202
203
		Random_value<Particle_direction> direction                     = {Particle_direction{},
                                                      Particle_direction{1.f, 0.5f, 1.f, 0.5f}};
		bool                             direction_normal_distribution = false;
204

205
206
207
		float parent_velocity = 0.f;

		glm::vec3 offset{0, 0, 0};
208
209
		glm::quat rotation{1, 0, 0, 0};

210
211
212
		util::small_vector<Particle_emitter_spawn, 4> spawn;
		bool                                          spawn_loop = true;

213
214
215
216
217
		std::string                 emit_script_id;
		asset::Ptr<Particle_script> emit_script;

		std::string                      type_id;
		asset::Ptr<Particle_type_config> type;
218
	};
219
220
221
222
223
	sf2_structDef(Particle_emitter_config,
	              spawn,
	              spawn_loop,
	              ttl,
	              velocity,
224
225
226
227
	              size,
	              independent_direction,
	              direction,
	              direction_normal_distribution,
228
229
230
231
232
	              parent_velocity,
	              offset,
	              rotation,
	              emit_script_id,
	              type_id);
233
234

	struct Particle_system_config {
235
236
		util::small_vector<Particle_emitter_config, 1> emitters;
		std::vector<Particle_effector_config>          effectors;
237
	};
238
	sf2_structDef(Particle_system_config, emitters, effectors);
239
240


241
242
243
244
245
	class Particle_emitter_gpu_data {
	  public:
		auto valid() const noexcept { return _live_rev && *_live_rev == _rev; }
		void set(const std::uint64_t* rev,
		         vk::Buffer,
246
		         vk::DescriptorSet,
247
248
249
250
		         std::int32_t  offset,
		         std::int32_t  count,
		         std::uint32_t feedback_idx);

251
252
253
		void next_uniforms(vk::DescriptorSet s) { _next_uniforms = s; }
		auto next_uniforms() const noexcept { return _next_uniforms; }

254
255
256
		auto batch_able() const noexcept { return _batch_able; }
		void batch_able(bool b) noexcept { _batch_able = b; }

257
258
	  private:
		vk::Buffer           _buffer;
259
260
		vk::DescriptorSet    _uniforms;
		vk::DescriptorSet    _next_uniforms;
261
262
263
264
265
		const std::uint64_t* _live_rev     = nullptr;
		std::uint64_t        _rev          = 0;
		std::int32_t         _offset       = 0;
		std::int32_t         _count        = 0;
		std::uint32_t        _feedback_idx = 0;
266
		bool                 _batch_able   = true;
267
268
269
270

		friend class Particle_emitter;
	};

271
272
	class Particle_emitter {
	  public:
273
		explicit Particle_emitter(const Particle_emitter_config& cfg) : _cfg(&cfg) {}
274

275
276
		void active(bool b) noexcept { _active = b; }
		auto active() const noexcept { return _active; }
277
278
279
280
281
282
283
284
		void reset_time()
		{
			_time_accumulator   = 0;
			_spawn_idx          = 0;
			_spawn_entry_timer  = 0;
			_particles_to_spawn = 0;
			_last_timestep      = 0;
		}
285

286
		void position(glm::vec3 p) noexcept { _position = p; }
287
288
		auto position() const noexcept { return _position; }

289
		void rotation(glm::quat r) noexcept { _rotation = r; }
290
291
		auto rotation() const noexcept { return _rotation; }

292
293
294
		void absolute(bool b) noexcept { _absolute = b; }
		auto absolute() const noexcept { return _absolute; }

295
		void incr_time(float dt);
296
		auto spawn(util::default_rand&) -> std::int32_t;
297
		void override_spawn(std::int32_t spawn) { _particles_to_spawn = spawn; }
298
299
300
301
302
303
304
305
306

		auto drawable() const noexcept { return _gpu_data && _gpu_data->valid(); }
		auto particle_offset() const noexcept { return drawable() ? _gpu_data->_offset : 0; }
		auto particle_count() const noexcept { return drawable() ? _gpu_data->_count : 0; }
		auto particle_feedback_idx() const noexcept
		{
			return drawable() ? util::just(_gpu_data->_feedback_idx) : util::nothing;
		}
		auto particle_buffer() const noexcept { return drawable() ? _gpu_data->_buffer : vk::Buffer{}; }
307
308
309
310
		auto particle_uniforms() const noexcept
		{
			return drawable() ? _gpu_data->_uniforms : vk::DescriptorSet{};
		}
311
312
313
314
		auto particles_to_spawn() const noexcept { return _particles_to_spawn; }
		auto last_timestep() const noexcept { return _last_timestep; }

		auto gpu_data() -> std::shared_ptr<Particle_emitter_gpu_data>;
315

316
		auto cfg() const noexcept -> auto& { return *_cfg; }
317
318

	  private:
319
		const Particle_emitter_config* _cfg;
320
321

		// TODO: userdata?
322
		bool      _active = true;
323
324
		glm::vec3 _position{0, 0, 0};
		glm::quat _rotation{1, 0, 0, 0};
325
		bool      _absolute = false;
326

327
328
329
330
331
332
333
334
335
		float       _time_accumulator  = 0.f;
		std::size_t _spawn_idx         = 0;
		float       _spawn_entry_timer = 0;

		std::int32_t _particles_to_spawn = 0;
		float        _last_timestep      = 0;

		// shared_ptr because its update after the async compute tasks finished
		std::shared_ptr<Particle_emitter_gpu_data> _gpu_data;
336
337
	};

338
	class Particle_system : private std::enable_shared_from_this<Particle_system> {
339
	  public:
340
341
		using Emitter_list  = util::small_vector<Particle_emitter, 1>;
		using Effector_list = std::vector<Particle_effector_config>;
342

343
344
345
346
		Particle_system() = default;
		Particle_system(asset::Ptr<Particle_system_config> cfg,
		                glm::vec3                          position = {0, 0, 0},
		                glm::quat                          rotation = {1, 0, 0, 0});
347

348
		auto cfg() const noexcept { return _cfg; }
349
		auto cfg_aid() const { return _cfg ? util::just(_cfg.aid()) : util::nothing; }
350

351
352
353
354
355
		auto emitters() noexcept -> auto&
		{
			_check_reload();
			return _emitters;
		}
356

357
358
359
360
361
		auto effectors() noexcept -> auto&
		{
			_check_reload();
			return _effectors;
		}
362

363
		void position(glm::vec3 p) noexcept { _position = p; }
364
365
		auto position() const noexcept { return _position; }

366
		void rotation(glm::quat r) noexcept { _rotation = r; }
367
368
		auto rotation() const noexcept { return _rotation; }

369
370
371
372
373
374
375
		auto emitter_position(const Particle_emitter& e) const noexcept
		{
			return e.absolute() ? e.position() : _position + e.position();
		}

		auto emitter_rotation(const Particle_emitter& e) const noexcept
		{
376
			return e.absolute() ? e.rotation() : glm::normalize(_rotation * e.rotation());
377
378
		}

379
	  private:
380
381
		friend class Particle_pass;

382
383
384
385
		asset::Ptr<Particle_system_config> _cfg;
		bool                               _loaded = false;
		Emitter_list                       _emitters;
		Effector_list                      _effectors;
386

387
		glm::vec3 _position{0, 0, 0};
388
		glm::vec3 _last_position{0, 0, 0};
389
		glm::quat _rotation{1, 0, 0, 0};
390
391

		void _check_reload();
392
	};
393
394
395
396
397
398
399
400
401
402
403


	class Particle_system_comp : public ecs::Component<Particle_system_comp> {
	  public:
		static constexpr const char* name() { return "Particle_system"; }
		friend void                  load_component(ecs::Deserializer& state, Particle_system_comp&);
		friend void                  save_component(ecs::Serializer& state, const Particle_system_comp&);

		Particle_system_comp() = default;
		Particle_system_comp(ecs::Entity_handle owner, ecs::Entity_manager& em) : Component(owner, em) {}

404
405
406
		Particle_system particle_system;
	};

407
	class Particle_effector_comp : public ecs::Component<Particle_effector_comp> {
408
409
410
411
412
413
414
415
416
	  public:
		static constexpr const char* name() { return "Particle_effector"; }
		friend void                  load_component(ecs::Deserializer& state, Particle_effector_comp&);
		friend void                  save_component(ecs::Serializer& state, const Particle_effector_comp&);

		Particle_effector_comp() = default;
		Particle_effector_comp(ecs::Entity_handle owner, ecs::Entity_manager& em) : Component(owner, em) {}

		Particle_effector_config effector;
417
418
	};

419
420
421
422
423
424
425
426
427

	extern auto create_particle_shared_desc_set_layout(graphic::Device&) -> vk::UniqueDescriptorSetLayout;

	extern auto create_particle_script_pipeline_layout(graphic::Device&        device,
	                                                   vk::DescriptorSetLayout shared_desc_set,
	                                                   vk::DescriptorSetLayout storage_buffer,
	                                                   vk::DescriptorSetLayout uniform_buffer)
	        -> vk::UniquePipelineLayout;

428
} // namespace mirrage::renderer
429
430
431
432
433
434

namespace mirrage::asset {

	template <>
	struct Loader<renderer::Particle_script> {
	  public:
435
436
437
		Loader(graphic::Device&        device,
		       vk::DescriptorSetLayout storage_buffer,
		       vk::DescriptorSetLayout uniform_buffer);
438
439
440
441

		auto              load(istream in) -> renderer::Particle_script;
		[[noreturn]] void save(ostream, const renderer::Particle_script&)
		{
442
			MIRRAGE_FAIL("Save of Particle_script is not supported!");
443
444
445
		}

	  private:
446
447
448
		graphic::Device&              _device;
		vk::UniqueDescriptorSetLayout _shared_desc_set;
		vk::UniquePipelineLayout      _layout;
449
450
451
452
453
	};

	template <>
	struct Loader<renderer::Particle_system_config> {
	  public:
454
455
		auto              load(istream in) -> async::task<renderer::Particle_system_config>;
		[[noreturn]] void save(ostream, const renderer::Particle_system_config&)
456
		{
457
458
459
460
461
462
463
			MIRRAGE_FAIL("Save of Particle_system_config is not supported!");
		}
	};

	template <>
	struct Loader<renderer::Particle_type_config> {
	  public:
464
465
		auto              load(istream in) -> async::task<renderer::Particle_type_config>;
		[[noreturn]] void save(ostream, const renderer::Particle_type_config&)
466
467
		{
			MIRRAGE_FAIL("Save of Particle_type_config is not supported!");
468
469
470
471
		}
	};

} // namespace mirrage::asset