1use crate::rand::Error;
25use crate::{
26 core::{
27 algebra::{Point3, Vector2, Vector3},
28 color::Color,
29 color_gradient::ColorGradient,
30 math::{aabb::AxisAlignedBoundingBox, TriangleDefinition},
31 pool::Handle,
32 reflect::prelude::*,
33 type_traits::prelude::*,
34 uuid::{uuid, Uuid},
35 value_as_u8_slice,
36 variable::InheritableVariable,
37 visitor::prelude::*,
38 },
39 material::{self, Material, MaterialResource},
40 rand::{prelude::StdRng, RngCore, SeedableRng},
41 renderer::{self, bundle::RenderContext},
42 scene::{
43 base::{Base, BaseBuilder},
44 graph::Graph,
45 mesh::{buffer::VertexTrait, RenderPath},
46 node::{constructor::NodeConstructor, Node, NodeTrait, RdcControlFlow, UpdateContext},
47 particle_system::{
48 draw::Vertex,
49 emitter::{base::BaseEmitterBuilder, sphere::SphereEmitterBuilder, Emit, Emitter},
50 particle::Particle,
51 },
52 },
53};
54use fyrox_graph::{constructor::ConstructorProvider, BaseSceneGraph};
55use std::{
56 cmp::Ordering,
57 fmt::Debug,
58 ops::{Deref, DerefMut},
59};
60use strum_macros::{AsRefStr, EnumString, VariantNames};
61
62pub(crate) mod draw;
63pub mod emitter;
64pub mod particle;
65
66#[derive(Debug, Clone, Reflect)]
68pub struct ParticleSystemRng {
69 rng_seed: u64,
70
71 #[reflect(hidden)]
72 rng: StdRng,
73}
74
75impl Default for ParticleSystemRng {
76 fn default() -> Self {
77 Self::new(0xDEADBEEF)
78 }
79}
80
81impl ParticleSystemRng {
82 pub fn new(seed: u64) -> Self {
85 Self {
86 rng_seed: seed,
87 rng: StdRng::seed_from_u64(seed),
88 }
89 }
90
91 #[inline]
93 pub fn reset(&mut self) {
94 self.rng = StdRng::seed_from_u64(self.rng_seed);
95 }
96}
97
98impl RngCore for ParticleSystemRng {
99 #[inline]
100 fn next_u32(&mut self) -> u32 {
101 self.rng.next_u32()
102 }
103
104 #[inline]
105 fn next_u64(&mut self) -> u64 {
106 self.rng.next_u64()
107 }
108
109 #[inline]
110 fn fill_bytes(&mut self, dest: &mut [u8]) {
111 self.rng.fill_bytes(dest)
112 }
113
114 #[inline]
115 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
116 self.rng.try_fill_bytes(dest)
117 }
118}
119
120impl Visit for ParticleSystemRng {
121 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
122 let mut guard = visitor.enter_region(name)?;
123
124 self.rng_seed.visit("Seed", &mut guard)?;
125
126 if guard.is_reading() {
128 self.rng = StdRng::seed_from_u64(self.rng_seed);
129 }
130
131 Ok(())
132 }
133}
134
135#[derive(Debug, Clone, Reflect, ComponentProvider)]
222#[reflect(derived_type = "Node")]
223pub struct ParticleSystem {
224 base: Base,
225
226 pub emitters: InheritableVariable<Vec<Emitter>>,
228
229 #[reflect(setter = "set_material")]
230 material: InheritableVariable<MaterialResource>,
231
232 #[reflect(setter = "set_acceleration")]
233 acceleration: InheritableVariable<Vector3<f32>>,
234
235 #[reflect(setter = "set_color_over_lifetime_gradient")]
236 color_over_lifetime: InheritableVariable<ColorGradient>,
237
238 #[reflect(setter = "play")]
239 is_playing: InheritableVariable<bool>,
240
241 #[reflect(hidden)]
242 particles: Vec<Particle>,
243
244 #[reflect(hidden)]
245 free_particles: Vec<u32>,
246
247 visible_distance: InheritableVariable<f32>,
252
253 coordinate_system: InheritableVariable<CoordinateSystem>,
257
258 #[reflect(min_value = 0.0)]
260 fadeout_margin: InheritableVariable<f32>,
261
262 rng: ParticleSystemRng,
263}
264
265#[derive(
267 Default,
268 Copy,
269 Clone,
270 PartialOrd,
271 PartialEq,
272 Eq,
273 Ord,
274 Hash,
275 Debug,
276 Visit,
277 Reflect,
278 AsRefStr,
279 EnumString,
280 VariantNames,
281 TypeUuidProvider,
282)]
283#[type_uuid(id = "d19e13ec-03d5-4c88-b0b2-d161d1912632")]
284pub enum CoordinateSystem {
285 #[default]
291 Local,
292 World,
298}
299
300impl Visit for ParticleSystem {
301 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
302 let mut region = visitor.enter_region(name)?;
303
304 self.base.visit("Base", &mut region)?;
305 self.emitters.visit("Emitters", &mut region)?;
306 self.acceleration.visit("Acceleration", &mut region)?;
307 self.color_over_lifetime
308 .visit("ColorGradient", &mut region)?;
309 self.is_playing.visit("Enabled", &mut region)?;
310 self.particles.visit("Particles", &mut region)?;
311 self.free_particles.visit("FreeParticles", &mut region)?;
312 let _ = self.rng.visit("Rng", &mut region);
313 let _ = self.visible_distance.visit("VisibleDistance", &mut region);
314 let _ = self
315 .coordinate_system
316 .visit("CoordinateSystem", &mut region);
317 let _ = self.fadeout_margin.visit("FadeoutMargin", &mut region);
318
319 if region.is_reading() {
321 if let Some(material) = material::visit_old_texture_as_material(
322 &mut region,
323 Material::standard_particle_system,
324 ) {
325 self.material = material.into();
326 } else {
327 self.material.visit("Material", &mut region)?;
328 }
329 } else {
330 self.material.visit("Material", &mut region)?;
331 }
332
333 let mut soft_boundary_sharpness_factor = 100.0;
334 if soft_boundary_sharpness_factor
335 .visit("SoftBoundarySharpnessFactor", &mut region)
336 .is_ok()
337 {
338 self.material.data_ref().set_property(
339 "softBoundarySharpnessFactor",
340 soft_boundary_sharpness_factor,
341 );
342 }
343
344 Ok(())
345 }
346}
347
348impl Deref for ParticleSystem {
349 type Target = Base;
350
351 fn deref(&self) -> &Self::Target {
352 &self.base
353 }
354}
355
356impl DerefMut for ParticleSystem {
357 fn deref_mut(&mut self) -> &mut Self::Target {
358 &mut self.base
359 }
360}
361
362impl TypeUuidProvider for ParticleSystem {
363 fn type_uuid() -> Uuid {
364 uuid!("8b210eff-97a4-494f-ba7a-a581d3f4a442")
365 }
366}
367
368impl ParticleSystem {
369 pub fn acceleration(&self) -> Vector3<f32> {
371 *self.acceleration
372 }
373
374 pub fn set_acceleration(&mut self, accel: Vector3<f32>) -> Vector3<f32> {
377 self.acceleration.set_value_and_mark_modified(accel)
378 }
379
380 pub fn set_color_over_lifetime_gradient(&mut self, gradient: ColorGradient) -> ColorGradient {
382 self.color_over_lifetime
383 .set_value_and_mark_modified(gradient)
384 }
385
386 pub fn play(&mut self, is_playing: bool) -> bool {
389 self.is_playing.set_value_and_mark_modified(is_playing)
390 }
391
392 pub fn is_playing(&self) -> bool {
394 *self.is_playing
395 }
396
397 pub fn set_particles(&mut self, particles: Vec<Particle>) {
400 self.free_particles.clear();
401 self.particles = particles;
402 }
403
404 pub fn particles(&self) -> &[Particle] {
406 &self.particles
407 }
408
409 pub fn clear_particles(&mut self) {
411 self.particles.clear();
412 self.free_particles.clear();
413 for emitter in self.emitters.get_value_mut_silent().iter_mut() {
414 emitter.alive_particles = 0;
415 emitter.spawned_particles = 0;
416 }
417 }
418
419 pub fn set_material(&mut self, material: MaterialResource) -> MaterialResource {
421 self.material.set_value_and_mark_modified(material)
422 }
423
424 pub fn texture(&self) -> MaterialResource {
426 (*self.material).clone()
427 }
428
429 pub fn texture_ref(&self) -> &MaterialResource {
431 &self.material
432 }
433
434 fn tick(&mut self, dt: f32) {
435 for emitter in self.emitters.get_value_mut_silent().iter_mut() {
436 emitter.tick(dt);
437 }
438
439 let global_transform = self.global_transform();
440
441 for (i, emitter) in self.emitters.get_value_mut_silent().iter_mut().enumerate() {
442 for _ in 0..emitter.particles_to_spawn {
443 let mut particle = Particle {
444 emitter_index: i as u32,
445 ..Particle::default()
446 };
447 emitter.alive_particles += 1;
448 emitter.emit(&mut particle, &mut self.rng);
449 if *self.coordinate_system == CoordinateSystem::World {
450 particle.position = global_transform
451 .transform_point(&particle.position.into())
452 .coords;
453 }
454 if let Some(free_index) = self.free_particles.pop() {
455 self.particles[free_index as usize] = particle;
456 } else {
457 self.particles.push(particle);
458 }
459 }
460 }
461
462 let acceleration_offset = self.acceleration.scale(dt * dt);
463
464 for (i, particle) in self.particles.iter_mut().enumerate() {
465 if particle.alive {
466 particle.lifetime += dt;
467 if particle.lifetime >= particle.initial_lifetime {
468 self.free_particles.push(i as u32);
469 if let Some(emitter) = self
470 .emitters
471 .get_value_mut_and_mark_modified()
472 .get_mut(particle.emitter_index as usize)
473 {
474 emitter.alive_particles = emitter.alive_particles.saturating_sub(1);
475 }
476 particle.alive = false;
477 particle.lifetime = particle.initial_lifetime;
478 } else {
479 particle.velocity += acceleration_offset;
480 particle.position += particle.velocity;
481 particle.size += particle.size_modifier * dt;
482 if particle.size < 0.0 {
483 particle.size = 0.0;
484 }
485 particle.rotation += particle.rotation_speed * dt;
486
487 let k = particle.lifetime / particle.initial_lifetime;
488 particle.color = self.color_over_lifetime.get_color(k);
489 }
490 }
491 }
492 }
493
494 pub fn rewind(&mut self, dt: f32, time: f32) {
496 assert!(dt > 0.0);
497
498 self.rng.reset();
499 self.clear_particles();
500
501 let mut t = 0.0;
502 while t < time {
503 self.tick(dt);
504 t += dt;
505 }
506 }
507
508 pub fn set_visible_distance(&mut self, distance: f32) {
513 self.visible_distance.set_value_and_mark_modified(distance);
514 }
515
516 pub fn visible_distance(&self) -> f32 {
519 *self.visible_distance
520 }
521
522 pub fn set_coordinate_system(&mut self, coordinate_system: CoordinateSystem) {
525 self.coordinate_system
526 .set_value_and_mark_modified(coordinate_system);
527 }
528
529 pub fn coordinate_system(&self) -> CoordinateSystem {
531 *self.coordinate_system
532 }
533
534 fn is_distance_clipped(&self, point: &Vector3<f32>) -> bool {
535 point.metric_distance(&self.global_position())
536 > (*self.visible_distance + *self.fadeout_margin)
537 }
538}
539
540impl Default for ParticleSystem {
541 fn default() -> Self {
542 ParticleSystemBuilder::new(BaseBuilder::new()).build_particle_system()
543 }
544}
545
546impl ConstructorProvider<Node, Graph> for ParticleSystem {
547 fn constructor() -> NodeConstructor {
548 NodeConstructor::new::<Self>().with_variant("Particle System", |_| {
549 ParticleSystemBuilder::new(BaseBuilder::new().with_name("ParticleSystem"))
550 .with_emitters(vec![SphereEmitterBuilder::new(
551 BaseEmitterBuilder::new()
552 .with_max_particles(100)
553 .resurrect_particles(true),
554 )
555 .with_radius(1.0)
556 .build()])
557 .build_node()
558 .into()
559 })
560 }
561}
562
563impl NodeTrait for ParticleSystem {
564 fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
565 AxisAlignedBoundingBox::unit()
566 }
567
568 fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
569 self.local_bounding_box()
570 .transform(&self.global_transform())
571 }
572
573 fn id(&self) -> Uuid {
574 Self::type_uuid()
575 }
576
577 fn update(&mut self, context: &mut UpdateContext) {
578 let dt = context.dt;
579
580 if *self.is_playing {
581 self.tick(dt);
582 }
583 }
584
585 fn collect_render_data(&self, ctx: &mut RenderContext) -> RdcControlFlow {
586 if !self.should_be_rendered(ctx.frustum, ctx.render_mask)
587 || self.is_distance_clipped(&ctx.observer_position.translation)
588 {
589 return RdcControlFlow::Continue;
590 }
591
592 if renderer::is_shadow_pass(ctx.render_pass_name) && !self.cast_shadows() {
593 return RdcControlFlow::Continue;
594 }
595
596 let distance_to_observer = ctx
597 .observer_position
598 .translation
599 .metric_distance(&self.global_position());
600
601 let particle_alpha_factor = if distance_to_observer >= self.visible_distance() {
602 1.0 - (distance_to_observer - self.visible_distance()) / *self.fadeout_margin
603 } else {
604 1.0
605 };
606
607 let mut sorted_particles = Vec::new();
608 for (i, particle) in self.particles.iter().enumerate() {
609 if particle.alive {
610 let actual_position = particle.position + self.base.global_position();
611 particle
612 .sqr_distance_to_camera
613 .set((ctx.observer_position.translation - actual_position).norm_squared());
614 sorted_particles.push(i as u32);
615 }
616 }
617
618 let particles = &self.particles;
619
620 sorted_particles.sort_by(|a, b| {
621 let particle_a = particles.get(*a as usize).unwrap();
622 let particle_b = particles.get(*b as usize).unwrap();
623
624 if particle_a.sqr_distance_to_camera < particle_b.sqr_distance_to_camera {
626 Ordering::Greater
627 } else if particle_a.sqr_distance_to_camera > particle_b.sqr_distance_to_camera {
628 Ordering::Less
629 } else {
630 Ordering::Equal
631 }
632 });
633
634 let global_transform = self.global_transform();
635 let sort_index = ctx.calculate_sorting_index(self.global_position());
636
637 ctx.storage.push_triangles(
638 ctx.dynamic_surface_cache,
639 Vertex::layout(),
640 &self.material,
641 RenderPath::Forward,
642 sort_index,
643 self.handle(),
644 &mut move |mut vertex_buffer, mut triangle_buffer| {
645 let vertices = sorted_particles.iter().flat_map(move |particle_index| {
646 let particle = self.particles.get(*particle_index as usize).unwrap();
647
648 let position = if *self.coordinate_system == CoordinateSystem::Local {
649 global_transform
650 .transform_point(&Point3::from(particle.position))
651 .coords
652 } else {
653 particle.position
654 };
655
656 let alpha = (particle.color.a as f32 * particle_alpha_factor) as u8;
657 let color = Color::from_rgba(
658 particle.color.r,
659 particle.color.g,
660 particle.color.b,
661 alpha,
662 );
663
664 [
665 Vertex {
666 position,
667 tex_coord: Vector2::default(),
668 size: particle.size,
669 rotation: particle.rotation,
670 color,
671 },
672 Vertex {
673 position,
674 tex_coord: Vector2::new(1.0, 0.0),
675 size: particle.size,
676 rotation: particle.rotation,
677 color,
678 },
679 Vertex {
680 position,
681 tex_coord: Vector2::new(1.0, 1.0),
682 size: particle.size,
683 rotation: particle.rotation,
684 color,
685 },
686 Vertex {
687 position,
688 tex_coord: Vector2::new(0.0, 1.0),
689 size: particle.size,
690 rotation: particle.rotation,
691 color,
692 },
693 ]
694 });
695
696 let triangles = (0..sorted_particles.len()).flat_map(|i| {
697 let base_index = (i * 4) as u32;
698
699 [
700 TriangleDefinition([base_index, base_index + 1, base_index + 2]),
701 TriangleDefinition([base_index, base_index + 2, base_index + 3]),
702 ]
703 });
704
705 let start_vertex_index = vertex_buffer.vertex_count();
706
707 for vertex in vertices {
708 vertex_buffer
709 .push_vertex_raw(value_as_u8_slice(&vertex))
710 .unwrap();
711 }
712
713 triangle_buffer.push_triangles_iter_with_offset(start_vertex_index, triangles)
714 },
715 );
716
717 RdcControlFlow::Continue
718 }
719}
720
721pub struct ParticleSystemBuilder {
724 base_builder: BaseBuilder,
725 emitters: Vec<Emitter>,
726 material: MaterialResource,
727 acceleration: Vector3<f32>,
728 particles: Vec<Particle>,
729 color_over_lifetime: ColorGradient,
730 is_playing: bool,
731 rng: ParticleSystemRng,
732 visible_distance: f32,
733 coordinate_system: CoordinateSystem,
734 fadeout_margin: f32,
735}
736
737impl ParticleSystemBuilder {
738 pub fn new(base_builder: BaseBuilder) -> Self {
740 Self {
741 base_builder,
742 emitters: Default::default(),
743 material: MaterialResource::new_ok(
744 Uuid::new_v4(),
745 Default::default(),
746 Material::standard_particle_system(),
747 ),
748 particles: Default::default(),
749 acceleration: Vector3::new(0.0, -9.81, 0.0),
750 color_over_lifetime: Default::default(),
751 is_playing: true,
752 rng: ParticleSystemRng::default(),
753 visible_distance: 30.0,
754 coordinate_system: Default::default(),
755 fadeout_margin: 1.5,
756 }
757 }
758
759 pub fn with_emitters(mut self, emitters: Vec<Emitter>) -> Self {
761 self.emitters = emitters;
762 self
763 }
764
765 pub fn with_material(mut self, material: MaterialResource) -> Self {
767 self.material = material;
768 self
769 }
770
771 pub fn with_acceleration(mut self, acceleration: Vector3<f32>) -> Self {
773 self.acceleration = acceleration;
774 self
775 }
776
777 pub fn with_visible_distance(mut self, distance: f32) -> Self {
780 self.visible_distance = distance;
781 self
782 }
783
784 pub fn with_color_over_lifetime_gradient(mut self, color_over_lifetime: ColorGradient) -> Self {
786 self.color_over_lifetime = color_over_lifetime;
787 self
788 }
789
790 pub fn with_particles(mut self, particles: Vec<Particle>) -> Self {
793 self.particles = particles;
794 self
795 }
796
797 pub fn with_playing(mut self, enabled: bool) -> Self {
799 self.is_playing = enabled;
800 self
801 }
802
803 pub fn with_rng(mut self, rng: ParticleSystemRng) -> Self {
805 self.rng = rng;
806 self
807 }
808
809 pub fn with_coordinate_system(mut self, coordinate_system: CoordinateSystem) -> Self {
811 self.coordinate_system = coordinate_system;
812 self
813 }
814
815 pub fn with_fadeout_margin(mut self, margin: f32) -> Self {
817 self.fadeout_margin = margin;
818 self
819 }
820
821 fn build_particle_system(self) -> ParticleSystem {
822 ParticleSystem {
823 base: self.base_builder.build_base(),
824 particles: self.particles,
825 free_particles: Vec::new(),
826 emitters: self.emitters.into(),
827 material: self.material.into(),
828 acceleration: self.acceleration.into(),
829 color_over_lifetime: self.color_over_lifetime.into(),
830 is_playing: self.is_playing.into(),
831 rng: self.rng,
832 visible_distance: self.visible_distance.into(),
833 coordinate_system: self.coordinate_system.into(),
834 fadeout_margin: self.fadeout_margin.into(),
835 }
836 }
837
838 pub fn build_node(self) -> Node {
840 Node::new(self.build_particle_system())
841 }
842
843 pub fn build(self, graph: &mut Graph) -> Handle<Node> {
845 graph.add_node(self.build_node())
846 }
847}