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::{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, SceneGraph};
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 self.rng.visit("Rng", &mut region)?;
313 self.visible_distance
314 .visit("VisibleDistance", &mut region)?;
315 self.coordinate_system
316 .visit("CoordinateSystem", &mut region)?;
317 self.fadeout_margin.visit("FadeoutMargin", &mut region)?;
318 self.material.visit("Material", &mut region)?;
319
320 Ok(())
321 }
322}
323
324impl Deref for ParticleSystem {
325 type Target = Base;
326
327 fn deref(&self) -> &Self::Target {
328 &self.base
329 }
330}
331
332impl DerefMut for ParticleSystem {
333 fn deref_mut(&mut self) -> &mut Self::Target {
334 &mut self.base
335 }
336}
337
338impl TypeUuidProvider for ParticleSystem {
339 fn type_uuid() -> Uuid {
340 uuid!("8b210eff-97a4-494f-ba7a-a581d3f4a442")
341 }
342}
343
344impl ParticleSystem {
345 pub fn acceleration(&self) -> Vector3<f32> {
347 *self.acceleration
348 }
349
350 pub fn set_acceleration(&mut self, accel: Vector3<f32>) -> Vector3<f32> {
353 self.acceleration.set_value_and_mark_modified(accel)
354 }
355
356 pub fn set_color_over_lifetime_gradient(&mut self, gradient: ColorGradient) -> ColorGradient {
358 self.color_over_lifetime
359 .set_value_and_mark_modified(gradient)
360 }
361
362 pub fn play(&mut self, is_playing: bool) -> bool {
365 self.is_playing.set_value_and_mark_modified(is_playing)
366 }
367
368 pub fn is_playing(&self) -> bool {
370 *self.is_playing
371 }
372
373 pub fn set_particles(&mut self, particles: Vec<Particle>) {
376 self.free_particles.clear();
377 self.particles = particles;
378 }
379
380 pub fn particles(&self) -> &[Particle] {
382 &self.particles
383 }
384
385 pub fn clear_particles(&mut self) {
387 self.particles.clear();
388 self.free_particles.clear();
389 for emitter in self.emitters.get_value_mut_silent().iter_mut() {
390 emitter.alive_particles = 0;
391 emitter.spawned_particles = 0;
392 }
393 }
394
395 pub fn set_material(&mut self, material: MaterialResource) -> MaterialResource {
397 self.material.set_value_and_mark_modified(material)
398 }
399
400 pub fn texture(&self) -> MaterialResource {
402 (*self.material).clone()
403 }
404
405 pub fn texture_ref(&self) -> &MaterialResource {
407 &self.material
408 }
409
410 fn tick(&mut self, dt: f32) {
411 for emitter in self.emitters.get_value_mut_silent().iter_mut() {
412 emitter.tick(dt);
413 }
414
415 let global_transform = self.global_transform();
416
417 for (i, emitter) in self.emitters.get_value_mut_silent().iter_mut().enumerate() {
418 for _ in 0..emitter.particles_to_spawn {
419 let mut particle = Particle {
420 emitter_index: i as u32,
421 ..Particle::default()
422 };
423 emitter.alive_particles += 1;
424 emitter.emit(&mut particle, &mut self.rng);
425 if *self.coordinate_system == CoordinateSystem::World {
426 particle.position = global_transform
427 .transform_point(&particle.position.into())
428 .coords;
429 }
430 if let Some(free_index) = self.free_particles.pop() {
431 self.particles[free_index as usize] = particle;
432 } else {
433 self.particles.push(particle);
434 }
435 }
436 }
437
438 let acceleration_offset = self.acceleration.scale(dt * dt);
439
440 for (i, particle) in self.particles.iter_mut().enumerate() {
441 if particle.alive {
442 particle.lifetime += dt;
443 if particle.lifetime >= particle.initial_lifetime {
444 self.free_particles.push(i as u32);
445 if let Some(emitter) = self
446 .emitters
447 .get_value_mut_and_mark_modified()
448 .get_mut(particle.emitter_index as usize)
449 {
450 emitter.alive_particles = emitter.alive_particles.saturating_sub(1);
451 }
452 particle.alive = false;
453 particle.lifetime = particle.initial_lifetime;
454 } else {
455 particle.velocity += acceleration_offset;
456 particle.position += particle.velocity;
457 particle.size += particle.size_modifier * dt;
458 if particle.size < 0.0 {
459 particle.size = 0.0;
460 }
461 particle.rotation += particle.rotation_speed * dt;
462
463 let k = particle.lifetime / particle.initial_lifetime;
464 particle.color = self.color_over_lifetime.get_color(k);
465 }
466 }
467 }
468 }
469
470 pub fn rewind(&mut self, dt: f32, time: f32) {
472 assert!(dt > 0.0);
473
474 self.rng.reset();
475 self.clear_particles();
476
477 let mut t = 0.0;
478 while t < time {
479 self.tick(dt);
480 t += dt;
481 }
482 }
483
484 pub fn set_visible_distance(&mut self, distance: f32) {
489 self.visible_distance.set_value_and_mark_modified(distance);
490 }
491
492 pub fn visible_distance(&self) -> f32 {
495 *self.visible_distance
496 }
497
498 pub fn set_coordinate_system(&mut self, coordinate_system: CoordinateSystem) {
501 self.coordinate_system
502 .set_value_and_mark_modified(coordinate_system);
503 }
504
505 pub fn coordinate_system(&self) -> CoordinateSystem {
507 *self.coordinate_system
508 }
509
510 fn is_distance_clipped(&self, point: &Vector3<f32>) -> bool {
511 point.metric_distance(&self.global_position())
512 > (*self.visible_distance + *self.fadeout_margin)
513 }
514}
515
516impl Default for ParticleSystem {
517 fn default() -> Self {
518 ParticleSystemBuilder::new(BaseBuilder::new()).build_particle_system()
519 }
520}
521
522impl ConstructorProvider<Node, Graph> for ParticleSystem {
523 fn constructor() -> NodeConstructor {
524 NodeConstructor::new::<Self>().with_variant("Particle System", |_| {
525 ParticleSystemBuilder::new(BaseBuilder::new().with_name("ParticleSystem"))
526 .with_emitters(vec![SphereEmitterBuilder::new(
527 BaseEmitterBuilder::new()
528 .with_max_particles(100)
529 .resurrect_particles(true),
530 )
531 .with_radius(1.0)
532 .build()])
533 .build_node()
534 .into()
535 })
536 }
537}
538
539impl NodeTrait for ParticleSystem {
540 fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
541 AxisAlignedBoundingBox::unit()
542 }
543
544 fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
545 self.local_bounding_box()
546 .transform(&self.global_transform())
547 }
548
549 fn id(&self) -> Uuid {
550 Self::type_uuid()
551 }
552
553 fn update(&mut self, context: &mut UpdateContext) {
554 let dt = context.dt;
555
556 if *self.is_playing {
557 self.tick(dt);
558 }
559 }
560
561 fn collect_render_data(&self, ctx: &mut RenderContext) -> RdcControlFlow {
562 if !self.should_be_rendered(ctx.frustum, ctx.render_mask)
563 || self.is_distance_clipped(&ctx.observer_position.translation)
564 {
565 return RdcControlFlow::Continue;
566 }
567
568 if renderer::is_shadow_pass(ctx.render_pass_name) && !self.cast_shadows() {
569 return RdcControlFlow::Continue;
570 }
571
572 let distance_to_observer = ctx
573 .observer_position
574 .translation
575 .metric_distance(&self.global_position());
576
577 let particle_alpha_factor = if distance_to_observer >= self.visible_distance() {
578 1.0 - (distance_to_observer - self.visible_distance()) / *self.fadeout_margin
579 } else {
580 1.0
581 };
582
583 let mut sorted_particles = Vec::new();
584 for (i, particle) in self.particles.iter().enumerate() {
585 if particle.alive {
586 let actual_position = particle.position + self.base.global_position();
587 particle
588 .sqr_distance_to_camera
589 .set((ctx.observer_position.translation - actual_position).norm_squared());
590 sorted_particles.push(i as u32);
591 }
592 }
593
594 let particles = &self.particles;
595
596 sorted_particles.sort_by(|a, b| {
597 let particle_a = particles.get(*a as usize).unwrap();
598 let particle_b = particles.get(*b as usize).unwrap();
599
600 if particle_a.sqr_distance_to_camera < particle_b.sqr_distance_to_camera {
602 Ordering::Greater
603 } else if particle_a.sqr_distance_to_camera > particle_b.sqr_distance_to_camera {
604 Ordering::Less
605 } else {
606 Ordering::Equal
607 }
608 });
609
610 let global_transform = self.global_transform();
611 let sort_index = ctx.calculate_sorting_index(self.global_position());
612
613 ctx.storage.push_triangles(
614 ctx.dynamic_surface_cache,
615 Vertex::layout(),
616 &self.material,
617 RenderPath::Forward,
618 sort_index,
619 self.handle(),
620 &mut move |mut vertex_buffer, mut triangle_buffer| {
621 let vertices = sorted_particles.iter().flat_map(move |particle_index| {
622 let particle = self.particles.get(*particle_index as usize).unwrap();
623
624 let position = if *self.coordinate_system == CoordinateSystem::Local {
625 global_transform
626 .transform_point(&Point3::from(particle.position))
627 .coords
628 } else {
629 particle.position
630 };
631
632 let alpha = (particle.color.a as f32 * particle_alpha_factor) as u8;
633 let color = Color::from_rgba(
634 particle.color.r,
635 particle.color.g,
636 particle.color.b,
637 alpha,
638 );
639
640 [
641 Vertex {
642 position,
643 tex_coord: Vector2::default(),
644 size: particle.size,
645 rotation: particle.rotation,
646 color,
647 },
648 Vertex {
649 position,
650 tex_coord: Vector2::new(1.0, 0.0),
651 size: particle.size,
652 rotation: particle.rotation,
653 color,
654 },
655 Vertex {
656 position,
657 tex_coord: Vector2::new(1.0, 1.0),
658 size: particle.size,
659 rotation: particle.rotation,
660 color,
661 },
662 Vertex {
663 position,
664 tex_coord: Vector2::new(0.0, 1.0),
665 size: particle.size,
666 rotation: particle.rotation,
667 color,
668 },
669 ]
670 });
671
672 let triangles = (0..sorted_particles.len()).flat_map(|i| {
673 let base_index = (i * 4) as u32;
674
675 [
676 TriangleDefinition([base_index, base_index + 1, base_index + 2]),
677 TriangleDefinition([base_index, base_index + 2, base_index + 3]),
678 ]
679 });
680
681 let start_vertex_index = vertex_buffer.vertex_count();
682
683 for vertex in vertices {
684 vertex_buffer
685 .push_vertex_raw(value_as_u8_slice(&vertex))
686 .unwrap();
687 }
688
689 triangle_buffer.push_triangles_iter_with_offset(start_vertex_index, triangles)
690 },
691 );
692
693 RdcControlFlow::Continue
694 }
695}
696
697pub struct ParticleSystemBuilder {
700 base_builder: BaseBuilder,
701 emitters: Vec<Emitter>,
702 material: MaterialResource,
703 acceleration: Vector3<f32>,
704 particles: Vec<Particle>,
705 color_over_lifetime: ColorGradient,
706 is_playing: bool,
707 rng: ParticleSystemRng,
708 visible_distance: f32,
709 coordinate_system: CoordinateSystem,
710 fadeout_margin: f32,
711}
712
713impl ParticleSystemBuilder {
714 pub fn new(base_builder: BaseBuilder) -> Self {
716 Self {
717 base_builder,
718 emitters: Default::default(),
719 material: MaterialResource::new_ok(
720 Uuid::new_v4(),
721 Default::default(),
722 Material::standard_particle_system(),
723 ),
724 particles: Default::default(),
725 acceleration: Vector3::new(0.0, -9.81, 0.0),
726 color_over_lifetime: Default::default(),
727 is_playing: true,
728 rng: ParticleSystemRng::default(),
729 visible_distance: 30.0,
730 coordinate_system: Default::default(),
731 fadeout_margin: 1.5,
732 }
733 }
734
735 pub fn with_emitters(mut self, emitters: Vec<Emitter>) -> Self {
737 self.emitters = emitters;
738 self
739 }
740
741 pub fn with_material(mut self, material: MaterialResource) -> Self {
743 self.material = material;
744 self
745 }
746
747 pub fn with_acceleration(mut self, acceleration: Vector3<f32>) -> Self {
749 self.acceleration = acceleration;
750 self
751 }
752
753 pub fn with_visible_distance(mut self, distance: f32) -> Self {
756 self.visible_distance = distance;
757 self
758 }
759
760 pub fn with_color_over_lifetime_gradient(mut self, color_over_lifetime: ColorGradient) -> Self {
762 self.color_over_lifetime = color_over_lifetime;
763 self
764 }
765
766 pub fn with_particles(mut self, particles: Vec<Particle>) -> Self {
769 self.particles = particles;
770 self
771 }
772
773 pub fn with_playing(mut self, enabled: bool) -> Self {
775 self.is_playing = enabled;
776 self
777 }
778
779 pub fn with_rng(mut self, rng: ParticleSystemRng) -> Self {
781 self.rng = rng;
782 self
783 }
784
785 pub fn with_coordinate_system(mut self, coordinate_system: CoordinateSystem) -> Self {
787 self.coordinate_system = coordinate_system;
788 self
789 }
790
791 pub fn with_fadeout_margin(mut self, margin: f32) -> Self {
793 self.fadeout_margin = margin;
794 self
795 }
796
797 fn build_particle_system(self) -> ParticleSystem {
798 ParticleSystem {
799 base: self.base_builder.build_base(),
800 particles: self.particles,
801 free_particles: Vec::new(),
802 emitters: self.emitters.into(),
803 material: self.material.into(),
804 acceleration: self.acceleration.into(),
805 color_over_lifetime: self.color_over_lifetime.into(),
806 is_playing: self.is_playing.into(),
807 rng: self.rng,
808 visible_distance: self.visible_distance.into(),
809 coordinate_system: self.coordinate_system.into(),
810 fadeout_margin: self.fadeout_margin.into(),
811 }
812 }
813
814 pub fn build_node(self) -> Node {
816 Node::new(self.build_particle_system())
817 }
818
819 pub fn build(self, graph: &mut Graph) -> Handle<ParticleSystem> {
821 graph.add_node(self.build_node()).to_variant()
822 }
823}