Skip to main content

fyrox_impl/scene/particle_system/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Contains all structures and methods to create and manage particle systems. See [`ParticleSystem`] docs for more
22//! info and usage examples.
23
24use 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/// Pseudo-random numbers generator for particle systems.
67#[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    /// Creates new PRNG with a given seed. Fixed seed guarantees that particle system's behaviour will be
83    /// deterministic.
84    pub fn new(seed: u64) -> Self {
85        Self {
86            rng_seed: seed,
87            rng: StdRng::seed_from_u64(seed),
88        }
89    }
90
91    /// Resets the state of PRNG.
92    #[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        // Re-initialize the RNG to keep determinism.
127        if guard.is_reading() {
128            self.rng = StdRng::seed_from_u64(self.rng_seed);
129        }
130
131        Ok(())
132    }
133}
134
135/// Particle system used to create visual effects that consists of many small parts,
136/// this can be smoke, fire, dust, sparks, etc. Particle system optimized to operate
137/// on many small parts, so it is much efficient to use particle system instead of
138/// separate scene nodes. Downside of particle system is that there almost no way
139/// to control separate particles, all particles controlled by parameters of particle
140/// emitters.
141///
142/// # Emitters
143///
144/// Particle system can contain multiple particle emitters, each emitter has its own
145/// set of properties and it defines law of change of particle parameters over time.
146///
147/// # Performance
148///
149/// In general particle system can be considered as heavy visual effect, but total impact
150/// on performance defined by amount of particles and amount of pixels they take to render.
151/// A rule of thumb will be to decrease amount of particles until effect will look good
152/// enough, alternatively amount of particles can be defined by some coefficient based on
153/// graphics quality settings.
154///
155/// # Example
156///
157/// Simple smoke effect can be create like so:
158///
159/// ```
160/// # use fyrox_impl::{
161/// #     asset::manager::ResourceManager,
162/// #     core::{
163/// #         algebra::Vector3,
164/// #         color::Color,
165/// #         color_gradient::{ColorGradient, GradientPoint},
166/// #         sstorage::ImmutableString,
167/// #     },
168/// #     material::{Material, MaterialProperty, MaterialResource},
169/// #     resource::texture::Texture,
170/// #     scene::{
171/// #         base::BaseBuilder,
172/// #         graph::Graph,
173/// #         particle_system::{
174/// #             emitter::base::BaseEmitterBuilder, emitter::sphere::SphereEmitterBuilder,
175/// #             ParticleSystemBuilder,
176/// #         },
177/// #         transform::TransformBuilder,
178/// #     },
179/// # };
180/// # use std::path::Path;
181/// #
182/// fn create_smoke(graph: &mut Graph, resource_manager: &mut ResourceManager, pos: Vector3<f32>) {
183///     let mut material = Material::standard_particle_system();
184///     material
185///         .bind("diffuseTexture", resource_manager.request::<Texture>(Path::new("data/particles/smoke_04.tga")));
186///
187///     ParticleSystemBuilder::new(
188///         BaseBuilder::new()
189///             .with_lifetime(5.0)
190///             .with_local_transform(TransformBuilder::new().with_local_position(pos).build()),
191///     )
192///     .with_acceleration(Vector3::new(0.0, 0.0, 0.0))
193///     .with_color_over_lifetime_gradient({
194///         let mut gradient = ColorGradient::new();
195///         gradient.add_point(GradientPoint::new(0.00, Color::from_rgba(150, 150, 150, 0)));
196///         gradient.add_point(GradientPoint::new(
197///             0.05,
198///             Color::from_rgba(150, 150, 150, 220),
199///         ));
200///         gradient.add_point(GradientPoint::new(
201///             0.85,
202///             Color::from_rgba(255, 255, 255, 180),
203///         ));
204///         gradient.add_point(GradientPoint::new(1.00, Color::from_rgba(255, 255, 255, 0)));
205///         gradient
206///     })
207///     .with_emitters(vec![SphereEmitterBuilder::new(
208///         BaseEmitterBuilder::new()
209///             .with_max_particles(100)
210///             .with_spawn_rate(50)
211///             .with_x_velocity_range(-0.01..0.01)
212///             .with_y_velocity_range(0.02..0.03)
213///             .with_z_velocity_range(-0.01..0.01),
214///     )
215///     .with_radius(0.01)
216///     .build()])
217///     .with_material(MaterialResource::new_embedded(material))
218///     .build(graph);
219/// }
220/// ```
221#[derive(Debug, Clone, Reflect, ComponentProvider)]
222#[reflect(derived_type = "Node")]
223pub struct ParticleSystem {
224    base: Base,
225
226    /// List of emitters of the particle system.
227    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    /// The maximum distance (in meters) from an observer to the particle system at which the
248    /// particle system remains visible. If the distance is larger, then the particle system will
249    /// fade out and eventually will be excluded from the rendering. Use this value to tweak
250    /// performance. Default is 30.0
251    visible_distance: InheritableVariable<f32>,
252
253    /// Defines a coordinate system for particles. Local coordinate space could be used for particles
254    /// that must move with the particle system (sparks), world space - for particles that must be
255    /// detached from the particle system (smoke trails)
256    coordinate_system: InheritableVariable<CoordinateSystem>,
257
258    /// A margin value in which the distance fading will occur.
259    #[reflect(min_value = 0.0)]
260    fadeout_margin: InheritableVariable<f32>,
261
262    rng: ParticleSystemRng,
263}
264
265/// Coordinate system for particles generated by a particle system.
266#[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    /// Local coordinate system moves particles together with the particle system itself. For example
286    /// if a particle system is moved, rotated, scaled, etc. then the particle will be moved, rotated,
287    /// scaled, etc. This option could be used create oriented particle systems, such as sparks. In
288    /// this case the sparks particle system should be designed to emit sparks along Z axis, and then
289    /// the actual particle system could be rotated to emit sparks into desired direction.
290    #[default]
291    Local,
292    /// World space coordinate system spawns the particles with the current world space transformation
293    /// matrix, and then it does not affect the particles anymore. The particles will be moved in world
294    /// space using the particle system parameters. This options could be used to create effects that
295    /// needs to be detached from the actual particle system. For example, it could be used to simulate
296    /// smoke trails.
297    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    /// Returns current acceleration for particles in particle system.
346    pub fn acceleration(&self) -> Vector3<f32> {
347        *self.acceleration
348    }
349
350    /// Set new acceleration that will be applied to all particles,
351    /// can be used to change "gravity" vector of particles.
352    pub fn set_acceleration(&mut self, accel: Vector3<f32>) -> Vector3<f32> {
353        self.acceleration.set_value_and_mark_modified(accel)
354    }
355
356    /// Sets new "color curve" that will evaluate color over lifetime.
357    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    /// Plays or pauses the particle system. Paused particle system remains in "frozen" state
363    /// until played again again. You can manually reset state of the system by calling [`Self::clear_particles`].
364    pub fn play(&mut self, is_playing: bool) -> bool {
365        self.is_playing.set_value_and_mark_modified(is_playing)
366    }
367
368    /// Returns current particle system status.
369    pub fn is_playing(&self) -> bool {
370        *self.is_playing
371    }
372
373    /// Replaces the particles in the particle system with pre-generated set. It could be useful
374    /// to create procedural particle effects; when particles cannot be pre-made.
375    pub fn set_particles(&mut self, particles: Vec<Particle>) {
376        self.free_particles.clear();
377        self.particles = particles;
378    }
379
380    /// Returns a reference to a slice to the current set of particles, generated by the particle system.
381    pub fn particles(&self) -> &[Particle] {
382        &self.particles
383    }
384
385    /// Removes all generated particles.
386    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    /// Sets the new material for the particle system.
396    pub fn set_material(&mut self, material: MaterialResource) -> MaterialResource {
397        self.material.set_value_and_mark_modified(material)
398    }
399
400    /// Returns current material used by particle system.
401    pub fn texture(&self) -> MaterialResource {
402        (*self.material).clone()
403    }
404
405    /// Returns current material used by particle system by ref.
406    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    /// Simulates particle system for the given `time` with given time step (`dt`). `dt` is usually `1.0 / 60.0`.
471    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    /// Sets the maximum distance (in meters) from an observer to the particle system at which the
485    /// particle system remains visible. If the distance is larger, then the particle system will
486    /// fade out and eventually will be excluded from the rendering. Use this value to tweak
487    /// performance. The larger the particle system, the larger this value should be. Default is 10.0.
488    pub fn set_visible_distance(&mut self, distance: f32) {
489        self.visible_distance.set_value_and_mark_modified(distance);
490    }
491
492    /// Returns current visible distance of the particle system. See [`Self::set_visible_distance`]
493    /// for more info.
494    pub fn visible_distance(&self) -> f32 {
495        *self.visible_distance
496    }
497
498    /// Sets a new coordinate system for the particles in the particle system. See [`CoordinateSystem`]
499    /// docs for more info.
500    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    /// Returns current coordinate system of the particle system.
506    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            // Reverse ordering because we want to sort back-to-front.
601            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
697/// Particle system builder allows you to construct particle system in declarative manner.
698/// This is typical implementation of Builder pattern.
699pub 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    /// Creates new builder with default parameters.
715    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    /// Sets desired emitters for particle system.
736    pub fn with_emitters(mut self, emitters: Vec<Emitter>) -> Self {
737        self.emitters = emitters;
738        self
739    }
740
741    /// Sets desired material for particle system.
742    pub fn with_material(mut self, material: MaterialResource) -> Self {
743        self.material = material;
744        self
745    }
746
747    /// Sets desired acceleration for particle system.
748    pub fn with_acceleration(mut self, acceleration: Vector3<f32>) -> Self {
749        self.acceleration = acceleration;
750        self
751    }
752
753    /// Sets the desired visible distance for the particle system. See [`ParticleSystem::set_visible_distance`]
754    /// for more info.
755    pub fn with_visible_distance(mut self, distance: f32) -> Self {
756        self.visible_distance = distance;
757        self
758    }
759
760    /// Sets color gradient over lifetime for particle system.
761    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    /// Sets an initial set of particles that not belongs to any emitter. This method
767    /// could be useful if you need a custom position/velocity/etc. of each particle.
768    pub fn with_particles(mut self, particles: Vec<Particle>) -> Self {
769        self.particles = particles;
770        self
771    }
772
773    /// Sets initial particle system state.
774    pub fn with_playing(mut self, enabled: bool) -> Self {
775        self.is_playing = enabled;
776        self
777    }
778
779    /// Sets desired pseudo-random numbers generator.
780    pub fn with_rng(mut self, rng: ParticleSystemRng) -> Self {
781        self.rng = rng;
782        self
783    }
784
785    /// Sets the desired coordinate system for particles.
786    pub fn with_coordinate_system(mut self, coordinate_system: CoordinateSystem) -> Self {
787        self.coordinate_system = coordinate_system;
788        self
789    }
790
791    /// Sets a margin value in which the distance fading will occur.
792    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    /// Creates new instance of particle system.
815    pub fn build_node(self) -> Node {
816        Node::new(self.build_particle_system())
817    }
818
819    /// Creates new instance of particle system and adds it to the graph.
820    pub fn build(self, graph: &mut Graph) -> Handle<ParticleSystem> {
821        graph.add_node(self.build_node()).to_variant()
822    }
823}