Unravel Engine C++ Reference
Loading...
Searching...
No Matches
particle_emitter_component.cpp
Go to the documentation of this file.
3
11
12namespace unravel
13{
14
15
17{
18 // Register EmitterShape enum
19
20 entt::meta_factory<ps_soa::emitter_shape>{}
21 .type("EmitterShape"_hs)
23 entt::attribute{"name", "EmitterShape"},
24 entt::attribute{"pretty_name", "Emitter Shape"},
25 })
26 .data<ps_soa::emitter_shape::sphere>("Sphere"_hs)
28 entt::attribute{"name", "Sphere"},
29 entt::attribute{"pretty_name", "Sphere"},
30 })
31 .data<ps_soa::emitter_shape::hemisphere>("Hemisphere"_hs)
33 entt::attribute{"name", "Hemisphere"},
34 entt::attribute{"pretty_name", "Hemisphere"},
35 })
36 .data<ps_soa::emitter_shape::circle>("Circle"_hs)
38 entt::attribute{"name", "Circle"},
39 entt::attribute{"pretty_name", "Circle"},
40 })
41 .data<ps_soa::emitter_shape::box>("Box"_hs)
43 entt::attribute{"name", "Box"},
44 entt::attribute{"pretty_name", "Box"},
45 })
46 .data<ps_soa::emitter_shape::rect>("Rect"_hs)
48 entt::attribute{"name", "Rect"},
49 entt::attribute{"pretty_name", "Rectangle"},
50 });
51
52
53 entt::meta_factory<ps_soa::emitter_direction>{}
54 .type("EmitterDirection"_hs)
56 entt::attribute{"name", "EmitterDirection"},
57 entt::attribute{"pretty_name", "Emitter Direction"},
58 })
59 .data<ps_soa::emitter_direction::up>("Up"_hs)
61 entt::attribute{"name", "Up"},
62 entt::attribute{"pretty_name", "Up"},
63 })
64 .data<ps_soa::emitter_direction::outward>("Outward"_hs)
66 entt::attribute{"name", "Outward"},
67 entt::attribute{"pretty_name", "Outward"},
68 })
69 .data<ps_soa::emitter_direction::inward>("Inward"_hs)
71 entt::attribute{"name", "Inward"},
72 entt::attribute{"pretty_name", "Inward"},
73 });
74
75 entt::meta_factory<ps_soa::spawn_location>{}
76 .type("EmitterSpawnLocation"_hs)
78 entt::attribute{"name", "EmitterSpawnLocation"},
79 entt::attribute{"pretty_name", "Emitter Spawn Location"},
80 })
81 .data<ps_soa::spawn_location::inside>("Inside"_hs)
83 entt::attribute{"name", "Inside"},
84 entt::attribute{"pretty_name", "Inside"},
85 })
86 .data<ps_soa::spawn_location::surface>("Surface"_hs)
88 entt::attribute{"name", "Surface"},
89 entt::attribute{"pretty_name", "Surface"},
90 });
91
92 entt::meta_factory<bx::Easing::Enum>{}
93 .type("Easing"_hs)
95 entt::attribute{"name", "Easing"},
96 entt::attribute{"pretty_name", "Easing Function"},
97 })
98 .data<bx::Easing::Linear>("Linear"_hs)
100 entt::attribute{"name", "Linear"},
101 entt::attribute{"pretty_name", "Linear"},
102 })
103 .data<bx::Easing::Step>("Step"_hs)
105 entt::attribute{"name", "Step"},
106 entt::attribute{"pretty_name", "Step"},
107 })
108 .data<bx::Easing::SmoothStep>("SmoothStep"_hs)
110 entt::attribute{"name", "SmoothStep"},
111 entt::attribute{"pretty_name", "Smooth Step"},
112 })
113 .data<bx::Easing::InQuad>("InQuad"_hs)
115 entt::attribute{"name", "InQuad"},
116 entt::attribute{"pretty_name", "In Quad"},
117 })
118 .data<bx::Easing::OutQuad>("OutQuad"_hs)
120 entt::attribute{"name", "OutQuad"},
121 entt::attribute{"pretty_name", "Out Quad"},
122 })
123 .data<bx::Easing::InOutQuad>("InOutQuad"_hs)
125 entt::attribute{"name", "InOutQuad"},
126 entt::attribute{"pretty_name", "In-Out Quad"},
127 })
128 .data<bx::Easing::InCubic>("InCubic"_hs)
130 entt::attribute{"name", "InCubic"},
131 entt::attribute{"pretty_name", "In Cubic"},
132 })
133 .data<bx::Easing::OutCubic>("OutCubic"_hs)
135 entt::attribute{"name", "OutCubic"},
136 entt::attribute{"pretty_name", "Out Cubic"},
137 })
138 .data<bx::Easing::InOutCubic>("InOutCubic"_hs)
140 entt::attribute{"name", "InOutCubic"},
141 entt::attribute{"pretty_name", "In-Out Cubic"},
142 })
143 .data<bx::Easing::InSine>("InSine"_hs)
145 entt::attribute{"name", "InSine"},
146 entt::attribute{"pretty_name", "In Sine"},
147 })
148 .data<bx::Easing::OutSine>("OutSine"_hs)
150 entt::attribute{"name", "OutSine"},
151 entt::attribute{"pretty_name", "Out Sine"},
152 })
153 .data<bx::Easing::InOutSine>("InOutSine"_hs)
155 entt::attribute{"name", "InOutSine"},
156 entt::attribute{"pretty_name", "In-Out Sine"},
157 })
158 .data<bx::Easing::InExpo>("InExpo"_hs)
160 entt::attribute{"name", "InExpo"},
161 entt::attribute{"pretty_name", "In Expo"},
162 })
163 .data<bx::Easing::OutExpo>("OutExpo"_hs)
165 entt::attribute{"name", "OutExpo"},
166 entt::attribute{"pretty_name", "Out Expo"},
167 })
168 .data<bx::Easing::InOutExpo>("InOutExpo"_hs)
170 entt::attribute{"name", "InOutExpo"},
171 entt::attribute{"pretty_name", "In-Out Expo"},
172 })
173 .data<bx::Easing::InElastic>("InElastic"_hs)
175 entt::attribute{"name", "InElastic"},
176 entt::attribute{"pretty_name", "In Elastic"},
177 })
178 .data<bx::Easing::OutElastic>("OutElastic"_hs)
180 entt::attribute{"name", "OutElastic"},
181 entt::attribute{"pretty_name", "Out Elastic"},
182 })
183 .data<bx::Easing::InOutElastic>("InOutElastic"_hs)
185 entt::attribute{"name", "InOutElastic"},
186 entt::attribute{"pretty_name", "In-Out Elastic"},
187 })
188 .data<bx::Easing::InBack>("InBack"_hs)
190 entt::attribute{"name", "InBack"},
191 entt::attribute{"pretty_name", "In Back"},
192 })
193 .data<bx::Easing::OutBack>("OutBack"_hs)
195 entt::attribute{"name", "OutBack"},
196 entt::attribute{"pretty_name", "Out Back"},
197 })
198 .data<bx::Easing::InOutBack>("InOutBack"_hs)
200 entt::attribute{"name", "InOutBack"},
201 entt::attribute{"pretty_name", "In-Out Back"},
202 })
203 .data<bx::Easing::InBounce>("InBounce"_hs)
205 entt::attribute{"name", "InBounce"},
206 entt::attribute{"pretty_name", "In Bounce"},
207 })
208 .data<bx::Easing::OutBounce>("OutBounce"_hs)
210 entt::attribute{"name", "OutBounce"},
211 entt::attribute{"pretty_name", "Out Bounce"},
212 })
213 .data<bx::Easing::InOutBounce>("InOutBounce"_hs)
215 entt::attribute{"name", "InOutBounce"},
216 entt::attribute{"pretty_name", "In-Out Bounce"},
217 });
218
219 entt::meta_factory<ps_soa::simulation_space>{}
220 .type("SimulationSpace"_hs)
222 entt::attribute{"name", "SimulationSpace"},
223 entt::attribute{"pretty_name", "Simulation Space"},
224 })
225 .data<ps_soa::simulation_space::world>("World"_hs)
227 entt::attribute{"name", "World"},
228 entt::attribute{"pretty_name", "World"},
229 })
230 .data<ps_soa::simulation_space::local>("Local"_hs)
232 entt::attribute{"name", "Local"},
233 entt::attribute{"pretty_name", "Local"},
234 });
235
236 entt::meta_factory<ps_soa::texture_mode>{}
237 .type("TextureMode"_hs)
239 entt::attribute{"name", "TextureMode"},
240 entt::attribute{"pretty_name", "Texture Mode"},
241 })
242 .data<ps_soa::texture_mode::multi_channel>("MultiChannel"_hs)
244 entt::attribute{"name", "MultiChannel"},
245 entt::attribute{"pretty_name", "Multi Channel"},
246 })
247 .data<ps_soa::texture_mode::mask>("Mask"_hs)
249 entt::attribute{"name", "Mask"},
250 entt::attribute{"pretty_name", "Mask"},
251 });
252
253 entt::meta_factory<ps_soa::render_mode>{}
254 .type("RenderMode"_hs)
256 entt::attribute{"name", "RenderMode"},
257 entt::attribute{"pretty_name", "Render Mode"},
258 })
259 .data<ps_soa::render_mode::billboard>("Billboard"_hs)
261 entt::attribute{"name", "Billboard"},
262 entt::attribute{"pretty_name", "Billboard"},
263 })
264 .data<ps_soa::render_mode::horizontal_billboard>("HorizontalBillboard"_hs)
266 entt::attribute{"name", "HorizontalBillboard"},
267 entt::attribute{"pretty_name", "Horizontal Billboard"},
268 })
269 .data<ps_soa::render_mode::vertical_billboard>("VerticalBillboard"_hs)
271 entt::attribute{"name", "VerticalBillboard"},
272 entt::attribute{"pretty_name", "Vertical Billboard"},
273 });
274
275 entt::meta_factory<ps_soa::blend_mode>{}
276 .type("BlendMode"_hs)
278 entt::attribute{"name", "BlendMode"},
279 entt::attribute{"pretty_name", "Blend Mode"},
280 })
281 .data<ps_soa::blend_mode::normal>("Normal"_hs)
283 entt::attribute{"name", "Normal"},
284 entt::attribute{"pretty_name", "Normal"},
285 })
286 .data<ps_soa::blend_mode::additive>("Additive"_hs)
288 entt::attribute{"name", "Additive"},
289 entt::attribute{"pretty_name", "Additive"},
290 })
291 .data<ps_soa::blend_mode::multiply>("Multiply"_hs)
293 entt::attribute{"name", "Multiply"},
294 entt::attribute{"pretty_name", "Multiply"},
295 });
296
297 entt::meta_factory<ps_soa::particle_sim_backend>{}
298 .type("ParticleSimBackend"_hs)
300 entt::attribute{"name", "ParticleSimBackend"},
301 entt::attribute{"pretty_name", "Particle Sim Backend"},
302 })
303 .data<ps_soa::particle_sim_backend::cpu>("CPU"_hs)
305 entt::attribute{"name", "CPU"},
306 entt::attribute{"pretty_name", "CPU"},
307 })
308 .data<ps_soa::particle_sim_backend::gpu>("GPU"_hs)
310 entt::attribute{"name", "GPU"},
311 entt::attribute{"pretty_name", "GPU"},
312 });
313
314 entt::meta_factory<particle_emitter_component::culling_mode>{}
315 .type("ParticleCullingMode"_hs)
317 entt::attribute{"name", "ParticleCullingMode"},
318 entt::attribute{"pretty_name", "Particle Culling Mode"},
319 })
320 .data<particle_emitter_component::culling_mode::always_simulate>("always_simulate"_hs)
322 entt::attribute{"name", "always_simulate"},
323 entt::attribute{"pretty_name", "Always Simulate"},
324 })
325 .data<particle_emitter_component::culling_mode::renderer_based>("renderer_based"_hs)
327 entt::attribute{"name", "renderer_based"},
328 entt::attribute{"pretty_name", "Renderer Based"},
329 });
330
331 entt::meta_factory<particle_emitter_component>{}
332 .type("particle_emitter_component"_hs)
334 entt::attribute{"name", "particle_emitter_component"},
335 entt::attribute{"category", "RENDERING"},
336 entt::attribute{"pretty_name", "Particle Emitter"},
337 })
338 .func<&component_meta<particle_emitter_component>::exists>("component_exists"_hs)
339 .func<&component_meta<particle_emitter_component>::add>("component_add"_hs)
340 .func<&component_meta<particle_emitter_component>::remove>("component_remove"_hs)
341 .func<&component_meta<particle_emitter_component>::save>("component_save"_hs)
342 .func<&component_meta<particle_emitter_component>::load>("component_load"_hs)
343 // ── Top-level ──
345 .custom<entt::attributes>(entt::attributes{
346 entt::attribute{"name", "enabled"},
347 entt::attribute{"pretty_name", "Enabled"},
348 entt::attribute{"tooltip", "Controls whether the particle emitter actively spawns and updates particles. Disabled emitters stop emission but existing particles continue to animate."},
349 })
350 .data<&particle_emitter_component::set_culling_mode, &particle_emitter_component::get_culling_mode>("culling_mode"_hs)
352 entt::attribute{"name", "culling_mode"},
353 entt::attribute{"pretty_name", "Culling Mode"},
354 entt::attribute{"tooltip",
355 "Always Simulate: update even when not drawn. "
356 "Renderer Based: freeze spawn/sim when no camera drew the emitter last frame (same as animation)."},
357 })
358 .data<&particle_emitter_component::set_loop, &particle_emitter_component::is_loop>("loop"_hs)
360 entt::attribute{"name", "loop"},
361 entt::attribute{"pretty_name", "Loop"},
362 entt::attribute{"tooltip", "Controls whether the emitter loops continuously (true) or emits only once up to max particles (false). Non-looping emitters stop emitting after reaching max particles."},
363 })
364 .data<&particle_emitter_component::set_simulation_space, &particle_emitter_component::get_simulation_space>("simulation_space"_hs)
366 entt::attribute{"name", "simulation_space"},
367 entt::attribute{"pretty_name", "Simulation Space"},
368 entt::attribute{"tooltip", "Controls whether particles are simulated in world space or local space."},
369 })
370 .data<&particle_emitter_component::set_simulation_backend, &particle_emitter_component::get_simulation_backend>("simulation_backend"_hs)
372 entt::attribute{"name", "simulation_backend"},
373 entt::attribute{"pretty_name", "Simulation Backend"},
374 entt::attribute{"tooltip",
375 "CPU: batched CPU property update and instance gather (best for many mid-size emitters). "
376 "GPU: compute pack per emitter (best for a few very large emitters). Requires GPU pack shader."},
377 })
378 .data<&particle_emitter_component::set_max_particles, &particle_emitter_component::get_max_particles>("max_particles"_hs)
380 entt::attribute{"name", "max_particles"},
381 entt::attribute{"pretty_name", "Max Particles"},
382 entt::attribute{"tooltip", "Maximum number of particles that can exist simultaneously. Higher values allow more dense effects but impact performance."},
383 })
384 .data<nullptr, &particle_emitter_component::get_num_particles>("num_particles"_hs)
386 entt::attribute{"name", "num_particles"},
387 entt::attribute{"pretty_name", "Num Particles"},
388 entt::attribute{"tooltip", "Current number of active particles in the system (read-only). Updates in real-time as particles spawn and die."},
389 })
390 .data<nullptr, &particle_emitter_component::get_world_bounds>("world_bounds"_hs)
392 entt::attribute{"name", "world_bounds"},
393 entt::attribute{"pretty_name", "World Bounds"},
394 entt::attribute{"tooltip", "Bounding box containing all particles in world space. Used for culling and optimization (read-only)."},
395 })
396 .data<&particle_emitter_component::set_lifetime, &particle_emitter_component::get_lifetime>("lifetime"_hs)
398 entt::attribute{"name", "lifetime"},
399 entt::attribute{"pretty_name", "Lifetime"},
400 entt::attribute{"tooltip", "How long each particle lives in seconds. Longer lifetimes create more persistent effects."},
401 entt::attribute{"min", 0.0f},
402 entt::attribute{"step", 0.1f},
403 })
404
405 // ── Emission ──
406 .data<&particle_emitter_component::set_emission_rate, &particle_emitter_component::get_emission_rate>("emission_rate"_hs)
408 entt::attribute{"name", "emission_rate"},
409 entt::attribute{"pretty_name", "Emission Rate"},
410 entt::attribute{"tooltip", "Emission rate in particles per second. Higher values create denser particle effects. 0 = no emission."},
411 entt::attribute{"min", 0.0f},
412 entt::attribute{"group", "Emission"},
413 })
414 .data<&particle_emitter_component::set_emission_lifetime, &particle_emitter_component::get_emission_lifetime>("emission_lifetime"_hs)
416 entt::attribute{"name", "emission_lifetime"},
417 entt::attribute{"pretty_name", "Emission Lifetime"},
418 entt::attribute{"tooltip", "Duration of one complete emission cycle in seconds. Controls how long the emitter takes to spawn all particles before restarting the cycle."},
419 entt::attribute{"min", 0.0f},
420 entt::attribute{"step", 0.1f},
421 entt::attribute{"group", "Emission"},
422 })
423 .data<&particle_emitter_component::set_start_delay, &particle_emitter_component::get_start_delay>("start_delay"_hs)
425 entt::attribute{"name", "start_delay"},
426 entt::attribute{"pretty_name", "Start Delay"},
427 entt::attribute{"tooltip", "Delay in seconds before particle emission starts. Particles will begin spawning after this delay period."},
428 entt::attribute{"min", 0.0f},
429 entt::attribute{"step", 0.1f},
430 entt::attribute{"group", "Emission"},
431 })
432 .data<&particle_emitter_component::set_temporal_motion, &particle_emitter_component::get_temporal_motion>("temporal_motion"_hs)
434 entt::attribute{"name", "temporal_motion"},
435 entt::attribute{"pretty_name", "Temporal Motion"},
436 entt::attribute{"tooltip", "Controls temporal interpolation for moving emitters. 1.0 = full interpolation (smooth trails), 0.0 = no interpolation (discrete emission)."},
437 entt::attribute{"min", 0.0f},
438 entt::attribute{"max", 1.0f},
439 entt::attribute{"group", "Emission"},
440 })
441
442 // ── Emission Shape ──
443 .data<&particle_emitter_component::set_shape, &particle_emitter_component::get_shape>("shape"_hs)
445 entt::attribute{"name", "shape"},
446 entt::attribute{"pretty_name", "Emitter Shape"},
447 entt::attribute{"tooltip", "Geometric shape from which particles are spawned. Sphere = 3D ball, Hemisphere = half sphere, Circle = 2D ring, Disc = filled circle, Rect = rectangle."},
448 entt::attribute{"group", "Emission Shape"},
449 })
450 .data<&particle_emitter_component::set_direction, &particle_emitter_component::get_direction>("direction"_hs)
452 entt::attribute{"name", "direction"},
453 entt::attribute{"pretty_name", "Emitter Direction"},
454 entt::attribute{"tooltip", "Initial direction particles move when spawned. Up = particles move upward, Outward = particles move away from spawn position, Inward = particles move towards spawn position (only visible in 3D space)."},
455 entt::attribute{"group", "Emission Shape"},
456 })
457 .data<&particle_emitter_component::set_spawn_location, &particle_emitter_component::get_spawn_location>("spawn_location"_hs)
459 entt::attribute{"name", "spawn_location"},
460 entt::attribute{"pretty_name", "Spawn Location"},
461 entt::attribute{"tooltip", "Controls where particles spawn within the emission shape. Inside = particles spawn anywhere inside the shape volume/area, Surface = particles spawn only on the surface/perimeter of the shape."},
462 entt::attribute{"group", "Emission Shape"},
463 })
464 .data<&particle_emitter_component::set_emission_shape_position, &particle_emitter_component::get_emission_shape_position>("emission_shape_position"_hs)
466 entt::attribute{"name", "emission_shape_position"},
467 entt::attribute{"pretty_name", "Position Offset"},
468 entt::attribute{"tooltip", "Position offset of the emission shape relative to the emitter transform. Allows offsetting where particles spawn."},
469 entt::attribute{"group", "Emission Shape"},
470 })
471 .data<&particle_emitter_component::set_emission_shape_scale, &particle_emitter_component::get_emission_shape_scale>("emission_shape_scale"_hs)
473 entt::attribute{"name", "emission_shape_scale"},
474 entt::attribute{"pretty_name", "Scale"},
475 entt::attribute{"tooltip", "Scale of the emission shape. 1.0 = no scaling, 2.0 = double size, 0.5 = half size."},
476 entt::attribute{"group", "Emission Shape"},
477 })
478
479 // ── Motion over lifetime ──
480 .data<&particle_emitter_component::set_velocity_gradient, &particle_emitter_component::get_velocity_gradient>("velocity_gradient"_hs)
482 entt::attribute{"name", "velocity_gradient"},
483 entt::attribute{"pretty_name", "Velocity Gradient"},
484 entt::attribute{"tooltip", "Velocity range gradient over particle lifetime. Controls how particle speed changes from spawn to death."},
485 entt::attribute{"group", "Motion over lifetime"},
486 entt::attribute{"step", 0.05f},
487 })
488 .data<&particle_emitter_component::set_velocity_damping, &particle_emitter_component::get_velocity_damping>("velocity_damping"_hs)
490 entt::attribute{"name", "velocity_damping"},
491 entt::attribute{"pretty_name", "Velocity Damping"},
492 entt::attribute{"tooltip", "Reduces particle velocity over time. 0.0 = no damping (particles maintain speed), 1.0 = full damping (particles stop immediately)."},
493 entt::attribute{"min", 0.0f},
494 entt::attribute{"max", 1.0f},
495 entt::attribute{"step", 0.01f},
496 entt::attribute{"group", "Motion over lifetime"},
497 })
498 .data<&particle_emitter_component::set_position_easing, &particle_emitter_component::get_position_easing>("position_easing"_hs)
500 entt::attribute{"name", "position_easing"},
501 entt::attribute{"pretty_name", "Position Easing"},
502 entt::attribute{"tooltip", "Curve controlling how particles move from start to end position. Linear = constant speed, EaseIn = slow start, EaseOut = slow end."},
503 entt::attribute{"group", "Motion over lifetime"},
504 })
505
506 // ── Force over lifetime ──
507 .data<&particle_emitter_component::set_gravity_scale, &particle_emitter_component::get_gravity_scale>("gravity_scale"_hs)
509 entt::attribute{"name", "gravity_scale"},
510 entt::attribute{"pretty_name", "Gravity Scale"},
511 entt::attribute{"tooltip", "Multiplier for gravity effect on particles. 0.0 = no gravity, 1.0 = Earth-like gravity, negative values = upward force."},
512 entt::attribute{"step", 0.01f},
513 entt::attribute{"group", "Force over lifetime"},
514 })
515 .data<&particle_emitter_component::set_force_over_lifetime, &particle_emitter_component::get_force_over_lifetime>("force_over_lifetime"_hs)
517 entt::attribute{"name", "force_over_lifetime"},
518 entt::attribute{"pretty_name", "Force Over Lifetime"},
519 entt::attribute{"tooltip", "Additional force applied to particles throughout their lifetime. Use for wind, magnetism, or other environmental effects. Values are in world units."},
520 entt::attribute{"group", "Force over lifetime"},
521 })
522
523 // ── Size over lifetime ──
524 .data<&particle_emitter_component::set_scale_gradient, &particle_emitter_component::get_scale_gradient>("scale_gradient"_hs)
526 entt::attribute{"name", "scale_gradient"},
527 entt::attribute{"pretty_name", "Scale Gradient"},
528 entt::attribute{"tooltip", "Scale range gradient over particle lifetime. Controls how particle size changes from spawn to death."},
529 entt::attribute{"group", "Size over lifetime"},
530 })
531 .data<&particle_emitter_component::set_initial_scale_3d, &particle_emitter_component::get_initial_scale_3d>("initial_scale_3d"_hs)
533 entt::attribute{"name", "initial_scale_3d"},
534 entt::attribute{"pretty_name", "Initial 3D Scale"},
535 entt::attribute{"tooltip", "3D scale for particles. Allows creating rectangular particles (e.g., 2,1,1 for wide particles, 1,2,1 for tall particles). Default: 1,1,1 (square)."},
536 entt::attribute{"group", "Size over lifetime"},
537 })
538
539 // ── Size by Speed ──
540 .data<&particle_emitter_component::set_size_by_speed_range, &particle_emitter_component::get_size_by_speed_range>("size_by_speed_range"_hs)
542 entt::attribute{"name", "size_by_speed_range"},
543 entt::attribute{"pretty_name", "Size by Speed Range"},
544 entt::attribute{"tooltip", "Size multiplier range based on particle speed. Min = size at slow speed, Max = size at fast speed. Use values like 0.5-2.0 for dramatic effects."},
545 entt::attribute{"min", 0.1f},
546 entt::attribute{"max", 5.0f},
547 entt::attribute{"group", "Size by Speed"},
548 })
549 .data<&particle_emitter_component::set_size_by_speed_velocity_range, &particle_emitter_component::get_size_by_speed_velocity_range>("size_by_speed_velocity_range"_hs)
551 entt::attribute{"name", "size_by_speed_velocity_range"},
552 entt::attribute{"pretty_name", "Size by Speed Velocity Range"},
553 entt::attribute{"tooltip", "Velocity range for size mapping. Particles moving at min speed get min size, particles at max speed get max size."},
554 entt::attribute{"min", 0.0f},
555 entt::attribute{"max", 100.0f},
556 entt::attribute{"group", "Size by Speed"},
557 })
558
559 // ── Color over lifetime ──
560 .data<&particle_emitter_component::set_color_gradient, &particle_emitter_component::get_color_gradient>("color_gradient"_hs)
562 entt::attribute{"name", "color_gradient"},
563 entt::attribute{"pretty_name", "Color Gradient"},
564 entt::attribute{"tooltip", "Color gradient defining particle color over lifetime. Colors are interpolated smoothly based on gradient keyframes."},
565 entt::attribute{"group", "Color over lifetime"},
566 })
567 .data<&particle_emitter_component::set_opacity, &particle_emitter_component::get_opacity>("opacity"_hs)
569 entt::attribute{"name", "opacity"},
570 entt::attribute{"pretty_name", "Opacity"},
571 entt::attribute{"tooltip", "Global opacity for all particles regardless of lifetime. 0.0 = fully transparent, 1.0 = no change, values > 1.0 = enhanced opacity."},
572 entt::attribute{"min", 0.0f},
573 entt::attribute{"max", 1.0f},
574 entt::attribute{"step", 0.01f},
575 entt::attribute{"group", "Color over lifetime"},
576 })
577 .data<&particle_emitter_component::set_color_intensity, &particle_emitter_component::get_color_intensity>("color_intensity"_hs)
579 entt::attribute{"name", "color_intensity"},
580 entt::attribute{"pretty_name", "Color Intensity"},
581 entt::attribute{"tooltip", "HDR multiplier for particle color. Values above 1.0 make particles glow when bloom is enabled."},
582 entt::attribute{"min", 0.0f},
583 entt::attribute{"step", 0.1f},
584 entt::attribute{"max", 100.0f},
585 entt::attribute{"group", "Color over lifetime"},
586 })
587
588 // ── Color by Speed ──
589 .data<&particle_emitter_component::set_color_by_speed_gradient, &particle_emitter_component::get_color_by_speed_gradient>("color_by_speed_gradient"_hs)
591 entt::attribute{"name", "color_by_speed_gradient"},
592 entt::attribute{"pretty_name", "Color by Speed Gradient"},
593 entt::attribute{"tooltip", "Color gradient applied based on particle speed. Slow particles use colors from the start of the gradient, fast particles use colors from the end."},
594 entt::attribute{"group", "Color by Speed"},
595 })
596 .data<&particle_emitter_component::set_color_by_speed_velocity_range, &particle_emitter_component::get_color_by_speed_velocity_range>("color_by_speed_velocity_range"_hs)
598 entt::attribute{"name", "color_by_speed_velocity_range"},
599 entt::attribute{"pretty_name", "Color by Speed Velocity Range"},
600 entt::attribute{"tooltip", "Velocity range for color mapping. Particles moving at min speed get slow color, particles at max speed get fast color."},
601 entt::attribute{"min", 0.0f},
602 entt::attribute{"group", "Color by Speed"},
603 })
604
605 // ── Lifetime by Emitter Speed ──
606 .data<&particle_emitter_component::set_lifetime_by_emitter_speed_gradient, &particle_emitter_component::get_lifetime_by_emitter_speed_gradient>("lifetime_by_emitter_speed_gradient"_hs)
608 entt::attribute{"name", "lifetime_by_emitter_speed_gradient"},
609 entt::attribute{"pretty_name", "Lifetime by Emitter Speed Gradient"},
610 entt::attribute{"tooltip", "Lifetime multiplier gradient based on emitter movement speed. Allows particles to live longer/shorter based on how fast the emitter is moving."},
611 entt::attribute{"group", "Lifetime by Emitter Speed"},
612 entt::attribute{"min", 0.0f},
613 entt::attribute{"step", 0.01f},
614 })
615 .data<&particle_emitter_component::set_lifetime_by_emitter_speed_range, &particle_emitter_component::get_lifetime_by_emitter_speed_range>("lifetime_by_emitter_speed_range"_hs)
617 entt::attribute{"name", "lifetime_by_emitter_speed_range"},
618 entt::attribute{"pretty_name", "Lifetime by Emitter Speed Range"},
619 entt::attribute{"tooltip", "Emitter speed range for lifetime mapping. Emitters moving at min speed get slow gradient value, emitters at max speed get fast gradient value."},
620 entt::attribute{"min", 0.0f},
621 entt::attribute{"step", 0.01f},
622 entt::attribute{"group", "Lifetime by Emitter Speed"},
623 })
624
625 // ── Rendering ──
626 .data<&particle_emitter_component::set_render_mode, &particle_emitter_component::get_render_mode>("render_mode"_hs)
628 entt::attribute{"name", "render_mode"},
629 entt::attribute{"pretty_name", "Render Mode"},
630 entt::attribute{"tooltip", "Render orientation mode. Billboard = always face camera, Horizontal = rotate around Y axis only (stay horizontal), Vertical = stay vertical (perpendicular to ground)."},
631 entt::attribute{"group", "Rendering"},
632 })
633 .data<&particle_emitter_component::set_blend_mode, &particle_emitter_component::get_blend_mode>("blend_mode"_hs)
635 entt::attribute{"name", "blend_mode"},
636 entt::attribute{"pretty_name", "Blend Mode"},
637 entt::attribute{"tooltip", "How particles blend with the scene. Normal = alpha transparency, Additive = glowing/fire effects, Multiply = darkening/smoke."},
638 entt::attribute{"group", "Rendering"},
639 })
640 .data<&particle_emitter_component::set_align_to_direction, &particle_emitter_component::get_align_to_direction>("align_to_direction"_hs)
642 entt::attribute{"name", "align_to_direction"},
643 entt::attribute{"pretty_name", "Align To Direction"},
644 entt::attribute{"tooltip", "If enabled, particles rotate to align with their velocity direction. Useful for directional particles like arrows or sparks."},
645 entt::attribute{"group", "Rendering"},
646 })
647 .data<&particle_emitter_component::set_pivot, &particle_emitter_component::get_pivot>("pivot"_hs)
649 entt::attribute{"name", "pivot"},
650 entt::attribute{"pretty_name", "Pivot"},
651 entt::attribute{"tooltip", "Pivot point for particle rotation and positioning. (0,0) = bottom-left, (0.5,0.5) = center (default), (1,1) = top-right. Affects both rotation and where the particle is anchored."},
652 entt::attribute{"min", 0.0f},
653 entt::attribute{"max", 1.0f},
654 entt::attribute{"step", 0.05f},
655 entt::attribute{"group", "Rendering"},
656 })
657
658 // ── Texture ──
659 .data<&particle_emitter_component::set_texture, &particle_emitter_component::get_texture>("texture"_hs)
661 entt::attribute{"name", "texture"},
662 entt::attribute{"pretty_name", "Texture"},
663 entt::attribute{"tooltip", "Texture asset used to render each particle. Should be a square texture with alpha channel for best results. Common formats: smoke, fire, sparkle, etc."},
664 entt::attribute{"group", "Texture"},
665 })
666 .data<&particle_emitter_component::set_texture_mode, &particle_emitter_component::get_texture_mode>("texture_mode"_hs)
668 entt::attribute{"name", "texture_mode"},
669 entt::attribute{"pretty_name", "Texture Mode"},
670 entt::attribute{"tooltip", "Texture mode determines how the texture is interpreted. MultiChannel = standard RGBA texture, Mask = black/white mask where black = transparent, white = opaque (particle color used)."},
671 entt::attribute{"group", "Texture"},
672 })
673 .data<&particle_emitter_component::set_texture_sheet_tiles, &particle_emitter_component::get_texture_sheet_tiles>("texture_sheet_tiles"_hs)
675 entt::attribute{"name", "texture_sheet_tiles"},
676 entt::attribute{"pretty_name", "Texture Sheet Tiles"},
677 entt::attribute{"tooltip", "Number of tiles in the texture sheet grid (X columns, Y rows). For example, a 4x4 grid contains 16 animation frames. Set to 1x1 to disable."},
678 entt::attribute{"group", "Texture"},
679 entt::attribute{"step", 1.0f},
680 entt::attribute{"min", 1.0f},
681 })
682 .data<&particle_emitter_component::set_texture_sheet_cycles, &particle_emitter_component::get_texture_sheet_cycles>("texture_sheet_cycles"_hs)
684 entt::attribute{"name", "texture_sheet_cycles"},
685 entt::attribute{"pretty_name", "Animation Cycles"},
686 entt::attribute{"tooltip", "Number of times the animation loops over particle lifetime. 0 = disabled, 1 = play once, 2 = play twice, etc. Higher values make the animation play faster."},
687 entt::attribute{"min", 0.0f},
688 entt::attribute{"step", 1.0f},
689 entt::attribute{"group", "Texture"},
690 })
691 .data<&particle_emitter_component::set_texture_sheet_randomize, &particle_emitter_component::get_texture_sheet_randomize>("texture_sheet_randomize"_hs)
693 entt::attribute{"name", "texture_sheet_randomize"},
694 entt::attribute{"pretty_name", "Randomize Start Frame"},
695 entt::attribute{"tooltip", "When enabled, each particle starts at a random frame in the texture sheet animation instead of frame 0. Creates visual variety."},
696 entt::attribute{"group", "Texture"},
697 })
698
699 ;
700
701}
702
704{
705 try_save(ar, ser20::make_nvp("enabled", obj.is_enabled()));
706 try_save(ar, ser20::make_nvp("culling_mode", obj.get_culling_mode()));
707 try_save(ar, ser20::make_nvp("shape", obj.get_shape()));
708 try_save(ar, ser20::make_nvp("direction", obj.get_direction()));
709 try_save(ar, ser20::make_nvp("spawn_location", obj.get_spawn_location()));
710 try_save(ar, ser20::make_nvp("simulation_space", obj.get_simulation_space()));
711 try_save(ar, ser20::make_nvp("simulation_backend", obj.get_simulation_backend()));
712 try_save(ar, ser20::make_nvp("max_particles", obj.get_max_particles()));
713
714
715 // Emission properties
716 try_save(ar, ser20::make_nvp("emission_lifetime", obj.get_emission_lifetime()));
717 try_save(ar, ser20::make_nvp("emission_shape_position", obj.get_emission_shape_position()));
718 try_save(ar, ser20::make_nvp("emission_shape_scale", obj.get_emission_shape_scale()));
719 try_save(ar, ser20::make_nvp("gravity_scale", obj.get_gravity_scale()));
720 try_save(ar, ser20::make_nvp("emission_rate", obj.get_emission_rate()));
721 try_save(ar, ser20::make_nvp("temporal_motion", obj.get_temporal_motion()));
722 try_save(ar, ser20::make_nvp("velocity_damping", obj.get_velocity_damping()));
723 try_save(ar, ser20::make_nvp("force_over_lifetime", obj.get_force_over_lifetime()));
724 try_save(ar, ser20::make_nvp("size_by_speed_range", obj.get_size_by_speed_range()));
725 try_save(ar, ser20::make_nvp("size_by_speed_velocity_range", obj.get_size_by_speed_velocity_range()));
726 try_save(ar, ser20::make_nvp("color_by_speed_gradient", obj.get_color_by_speed_gradient()));
727 try_save(ar, ser20::make_nvp("color_by_speed_velocity_range", obj.get_color_by_speed_velocity_range()));
728 try_save(ar, ser20::make_nvp("lifetime_by_emitter_speed_gradient", obj.get_lifetime_by_emitter_speed_gradient()));
729 try_save(ar, ser20::make_nvp("lifetime_by_emitter_speed_range", obj.get_lifetime_by_emitter_speed_range()));
730
731 // Gradient properties
732 try_save(ar, ser20::make_nvp("lifetime", obj.get_lifetime()));
733 try_save(ar, ser20::make_nvp("velocity_gradient", obj.get_velocity_gradient()));
734 try_save(ar, ser20::make_nvp("scale_gradient", obj.get_scale_gradient()));
735 try_save(ar, ser20::make_nvp("initial_scale_3d", obj.get_initial_scale_3d()));
736 try_save(ar, ser20::make_nvp("opacity", obj.get_opacity()));
737 try_save(ar, ser20::make_nvp("color_intensity", obj.get_color_intensity()));
738
739 // Colors
740 try_save(ar, ser20::make_nvp("color_gradient", obj.get_color_gradient()));
741
742 // Easing functions (only position easing remains)
743 try_save(ar, ser20::make_nvp("position_easing", obj.get_position_easing()));
744
745 // Texture handle
746 try_save(ar, ser20::make_nvp("texture", obj.get_texture()));
747 try_save(ar, ser20::make_nvp("texture_mode", obj.get_texture_mode()));
748 try_save(ar, ser20::make_nvp("render_mode", obj.get_render_mode()));
749 try_save(ar, ser20::make_nvp("blend_mode", obj.get_blend_mode()));
750
751 // Texture sheet animation
752 try_save(ar, ser20::make_nvp("texture_sheet_tiles", obj.get_texture_sheet_tiles()));
753 try_save(ar, ser20::make_nvp("texture_sheet_cycles", obj.get_texture_sheet_cycles()));
754 try_save(ar, ser20::make_nvp("texture_sheet_randomize", obj.get_texture_sheet_randomize()));
755
756 // Loop control
757 try_save(ar, ser20::make_nvp("loop", obj.is_loop()));
758 try_save(ar, ser20::make_nvp("start_delay", obj.get_start_delay()));
759 try_save(ar, ser20::make_nvp("align_to_direction", obj.get_align_to_direction()));
760
761 // Pivot
762 try_save(ar, ser20::make_nvp("pivot", obj.get_pivot()));
763}
766
768{
769 bool enabled{true};
770 if(try_load(ar, ser20::make_nvp("enabled", enabled)))
771 {
772 obj.set_enabled(enabled);
773 }
774
776 if(try_load(ar, ser20::make_nvp("culling_mode", culling_mode)))
777 {
778 obj.set_culling_mode(culling_mode);
779 }
780
783 if(try_load(ar, ser20::make_nvp("shape", shape)))
784 {
785 obj.set_shape(shape);
786 }
787 if(try_load(ar, ser20::make_nvp("direction", direction)))
788 {
789 obj.set_direction(direction);
790 }
791
793 if(try_load(ar, ser20::make_nvp("spawn_location", spawn_location)))
794 {
795 obj.set_spawn_location(spawn_location);
796 }
797
799 if(try_load(ar, ser20::make_nvp("simulation_space", simulation_space)))
800 {
801 obj.set_simulation_space(simulation_space);
802 }
803
805 if(try_load(ar, ser20::make_nvp("simulation_backend", simulation_backend)))
806 {
807 obj.set_simulation_backend(simulation_backend);
808 }
809
810 uint32_t max_particles{1024};
811 if(try_load(ar, ser20::make_nvp("max_particles", max_particles)))
812 {
813 obj.set_max_particles(max_particles);
814 }
815
816 // Emission properties
817 std::chrono::duration<float> emission_lifetime{2.0f};
818 if(try_load(ar, ser20::make_nvp("emission_lifetime", emission_lifetime)))
819 {
820 obj.set_emission_lifetime(emission_lifetime);
821 }
822
823 math::vec3 emission_shape_position{0.0f, 0.0f, 0.0f};
824 if(try_load(ar, ser20::make_nvp("emission_shape_position", emission_shape_position)))
825 {
826 obj.set_emission_shape_position(emission_shape_position);
827 }
828
829 math::vec3 emission_shape_scale{1.0f, 1.0f, 1.0f};
830 if(try_load(ar, ser20::make_nvp("emission_shape_scale", emission_shape_scale)))
831 {
832 obj.set_emission_shape_scale(emission_shape_scale);
833 }
834
835 float gravity_scale{0.0f};
836 if(try_load(ar, ser20::make_nvp("gravity_scale", gravity_scale)))
837 {
838 obj.set_gravity_scale(gravity_scale);
839 }
840
841 float emission_rate{50.0f};
842 if(try_load(ar, ser20::make_nvp("emission_rate", emission_rate)))
843 {
844 obj.set_emission_rate(emission_rate);
845 }
846
847 float temporal_motion{1.0f};
848 if(try_load(ar, ser20::make_nvp("temporal_motion", temporal_motion)))
849 {
850 obj.set_temporal_motion(temporal_motion);
851 }
852
853 float velocity_damping{0.0f};
854 if(try_load(ar, ser20::make_nvp("velocity_damping", velocity_damping)))
855 {
856 obj.set_velocity_damping(velocity_damping);
857 }
858
859 math::vec3 force_over_lifetime{0.0f, 0.0f, 0.0f};
860 if(try_load(ar, ser20::make_nvp("force_over_lifetime", force_over_lifetime)))
861 {
862 obj.set_force_over_lifetime(force_over_lifetime);
863 }
864
865 frange_t size_by_speed_range{1.0f, 1.0f};
866 if(try_load(ar, ser20::make_nvp("size_by_speed_range", size_by_speed_range)))
867 {
868 obj.set_size_by_speed_range(size_by_speed_range);
869 }
870
871 frange_t size_by_speed_velocity_range{0.0f, 10.0f};
872 if(try_load(ar, ser20::make_nvp("size_by_speed_velocity_range", size_by_speed_velocity_range)))
873 {
874 obj.set_size_by_speed_velocity_range(size_by_speed_velocity_range);
875 }
876
877 math::gradient<math::color> color_by_speed_gradient;
878 if(try_load(ar, ser20::make_nvp("color_by_speed_gradient", color_by_speed_gradient)))
879 {
880 obj.set_color_by_speed_gradient(color_by_speed_gradient);
881 }
882 else
883 {
884 // Legacy loading: try to load old slow/fast colors and convert to gradient
885 math::color color_by_speed_slow_color{0xffffffff};
886 math::color color_by_speed_fast_color{0xffffffff};
887 bool has_slow = try_load(ar, ser20::make_nvp("color_by_speed_slow_color", color_by_speed_slow_color));
888 bool has_fast = try_load(ar, ser20::make_nvp("color_by_speed_fast_color", color_by_speed_fast_color));
889
890 if(has_slow || has_fast)
891 {
892 math::gradient<math::color> legacy_gradient;
893 legacy_gradient.add_point(color_by_speed_slow_color, 0.0f);
894 legacy_gradient.add_point(color_by_speed_fast_color, 1.0f);
895 obj.set_color_by_speed_gradient(legacy_gradient);
896 }
897 }
898
899 frange_t color_by_speed_velocity_range{0.0f, 10.0f};
900 if(try_load(ar, ser20::make_nvp("color_by_speed_velocity_range", color_by_speed_velocity_range)))
901 {
902 obj.set_color_by_speed_velocity_range(color_by_speed_velocity_range);
903 }
904
905 math::gradient<float> lifetime_by_emitter_speed_gradient;
906 if(try_load(ar, ser20::make_nvp("lifetime_by_emitter_speed_gradient", lifetime_by_emitter_speed_gradient)))
907 {
908 obj.set_lifetime_by_emitter_speed_gradient(lifetime_by_emitter_speed_gradient);
909 }
910
911 frange_t lifetime_by_emitter_speed_range{0.0f, 10.0f};
912 if(try_load(ar, ser20::make_nvp("lifetime_by_emitter_speed_range", lifetime_by_emitter_speed_range)))
913 {
914 obj.set_lifetime_by_emitter_speed_range(lifetime_by_emitter_speed_range);
915 }
916
917 // Gradient properties
918 std::chrono::duration<float> lifetime{1.0f};
919 if(try_load(ar, ser20::make_nvp("lifetime", lifetime)))
920 {
921 obj.set_lifetime(lifetime);
922 }
923
924 math::gradient<frange_t> velocity_gradient;
925 if(try_load(ar, ser20::make_nvp("velocity_gradient", velocity_gradient)))
926 {
927 obj.set_velocity_gradient(velocity_gradient);
928 }
929
930 math::gradient<frange_t> scale_gradient;
931 if(try_load(ar, ser20::make_nvp("scale_gradient", scale_gradient)))
932 {
933 obj.set_scale_gradient(scale_gradient);
934 }
935
936 math::vec3 initial_scale_3d{1.0f, 1.0f, 1.0f};
937 if(try_load(ar, ser20::make_nvp("initial_scale_3d", initial_scale_3d)))
938 {
939 obj.set_initial_scale_3d(initial_scale_3d);
940 }
941 // Backward compatibility: try loading old name
942 else if(try_load(ar, ser20::make_nvp("particle_scale_3d", initial_scale_3d)))
943 {
944 obj.set_initial_scale_3d(initial_scale_3d);
945 }
946
947
948 float opacity{1.0f};
949 if(try_load(ar, ser20::make_nvp("opacity", opacity)))
950 {
951 obj.set_opacity(opacity);
952 }
953
954 float color_intensity{1.0f};
955 if(try_load(ar, ser20::make_nvp("color_intensity", color_intensity)))
956 {
957 obj.set_color_intensity(color_intensity);
958 }
959
960 // Colors
961 // Try to load gradient first (new format)
962 math::gradient<math::color> color_gradient;
963 if(try_load(ar, ser20::make_nvp("color_gradient", color_gradient)))
964 {
965 obj.set_color_gradient(color_gradient);
966 }
967
968 // Easing functions (only position easing remains)
969 bx::Easing::Enum position_easing{};
970 if(try_load(ar, ser20::make_nvp("position_easing", position_easing)))
971 {
972 obj.set_position_easing(position_easing);
973 }
974
975 // Load old easing values for backward compatibility but ignore them
976 bx::Easing::Enum blend_easing{}, scale_easing{};
977 try_load(ar, ser20::make_nvp("blend_easing", blend_easing));
978 try_load(ar, ser20::make_nvp("scale_easing", scale_easing));
979
980 // Texture handle
982 if(try_load(ar, ser20::make_nvp("texture", texture)))
983 {
984 obj.set_texture(texture);
985 }
986
988 if(try_load(ar, ser20::make_nvp("texture_mode", texture_mode)))
989 {
990 obj.set_texture_mode(texture_mode);
991 }
992
994 if(try_load(ar, ser20::make_nvp("render_mode", render_mode)))
995 {
996 obj.set_render_mode(render_mode);
997 }
999 if(try_load(ar, ser20::make_nvp("blend_mode", blend_mode)))
1000 {
1001 obj.set_blend_mode(blend_mode);
1002 }
1003 // Backward compatibility: also check for old "billboard_mode" name
1005 if(try_load(ar, ser20::make_nvp("billboard_mode", billboard_mode)))
1006 {
1007 obj.set_render_mode(billboard_mode);
1008 }
1009
1010 // Texture sheet animation
1011 math::vec2 texture_sheet_tiles{1.0f, 1.0f};
1012 if(try_load(ar, ser20::make_nvp("texture_sheet_tiles", texture_sheet_tiles)))
1013 {
1014 obj.set_texture_sheet_tiles(texture_sheet_tiles);
1015 }
1016
1017 float texture_sheet_cycles{0.0f};
1018 if(try_load(ar, ser20::make_nvp("texture_sheet_cycles", texture_sheet_cycles)))
1019 {
1020 obj.set_texture_sheet_cycles(texture_sheet_cycles);
1021 }
1022
1023 bool texture_sheet_randomize{false};
1024 if(try_load(ar, ser20::make_nvp("texture_sheet_randomize", texture_sheet_randomize)))
1025 {
1026 obj.set_texture_sheet_randomize(texture_sheet_randomize);
1027 }
1028
1029 // Loop control
1030 bool loop{true}; // Default to true for backward compatibility
1031 if(try_load(ar, ser20::make_nvp("loop", loop)))
1032 {
1033 obj.set_loop(loop);
1034 }
1035
1036 std::chrono::duration<float> start_delay{0.0f}; // Default to 0 for backward compatibility
1037 if(try_load(ar, ser20::make_nvp("start_delay", start_delay)))
1038 {
1039 obj.set_start_delay(start_delay);
1040 }
1041
1042 bool align_to_direction{false}; // Default to false for backward compatibility
1043 if(try_load(ar, ser20::make_nvp("align_to_direction", align_to_direction)))
1044 {
1045 obj.set_align_to_direction(align_to_direction);
1046 }
1047
1048 math::vec2 pivot{0.5f, 0.5f}; // Default to center for backward compatibility
1049 if(try_load(ar, ser20::make_nvp("pivot", pivot)))
1050 {
1051 obj.set_pivot(pivot);
1052 }
1053}
1056
1057} // namespace unravel
auto add_point(const T &element, float progress) -> size_t
Definition gradient.hpp:8
Component that wraps the soa particle system emitter.
attributes::value_type attribute
Definition reflection.h:19
std::map< std::string, meta_any > attributes
Definition reflection.h:18
BinaryInputArchive iarchive_binary_t
simd::JSONOutputArchive oarchive_associative_t
BinaryOutputArchive oarchive_binary_t
simd::JSONInputArchive iarchive_associative_t
blend_mode
Blend mode. Ordinals match legacy BlendMode.
texture_mode
Texture interpretation. Ordinals match legacy TextureMode.
emitter_direction
Initial emission direction. Ordinals match legacy EmitterDirection.
particle_sim_backend
Selects CPU vs resident-GPU simulation backend.
emitter_shape
Emission volume shape. Ordinals match legacy EmitterShape for content compatibility.
simulation_space
Simulation space. Ordinals match legacy SimulationSpace.
spawn_location
Where particles spawn within the shape. Ordinals match legacy EmitterSpawnLocation.
render_mode
Billboard / facing mode. Ordinals match legacy RenderMode.
#define REFLECT(cls)
Definition reflection.h:149
#define SAVE_INSTANTIATE(cls, Archive)
#define LOAD(cls)
auto try_save(Archive &ar, ser20::NameValuePair< T > &&t, const hpp::source_location &loc=hpp::source_location::current()) -> bool
#define LOAD_INSTANTIATE(cls, Archive)
#define SAVE(cls)
auto try_load(Archive &ar, ser20::NameValuePair< T > &&t, const hpp::source_location &loc=hpp::source_location::current()) -> bool
Thread-safe handle to an asset.
bool enabled