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::scene::node::constructor::NodeConstructor;
25use crate::scene::particle_system::emitter::base::BaseEmitterBuilder;
26use crate::scene::particle_system::emitter::sphere::SphereEmitterBuilder;
27use crate::{
28    core::{
29        algebra::{Point3, Vector2, Vector3},
30        color::Color,
31        color_gradient::ColorGradient,
32        math::{aabb::AxisAlignedBoundingBox, TriangleDefinition},
33        pool::Handle,
34        reflect::prelude::*,
35        type_traits::prelude::*,
36        uuid::{uuid, Uuid},
37        value_as_u8_slice,
38        variable::InheritableVariable,
39        visitor::prelude::*,
40    },
41    material::{self, Material, MaterialResource},
42    rand::{prelude::StdRng, Error, RngCore, SeedableRng},
43    renderer::{self, bundle::RenderContext},
44    scene::{
45        base::{Base, BaseBuilder},
46        graph::Graph,
47        mesh::{buffer::VertexTrait, RenderPath},
48        node::{Node, NodeTrait, RdcControlFlow, UpdateContext},
49        particle_system::{
50            draw::Vertex,
51            emitter::{Emit, Emitter},
52            particle::Particle,
53        },
54    },
55};
56use fyrox_graph::constructor::ConstructorProvider;
57use fyrox_graph::BaseSceneGraph;
58use std::{
59    cmp::Ordering,
60    fmt::Debug,
61    ops::{Deref, DerefMut},
62};
63use strum_macros::{AsRefStr, EnumString, VariantNames};
64
65pub(crate) mod draw;
66pub mod emitter;
67pub mod particle;
68
69/// Pseudo-random numbers generator for particle systems.
70#[derive(Debug, Clone, Reflect)]
71pub struct ParticleSystemRng {
72    rng_seed: u64,
73
74    #[reflect(hidden)]
75    rng: StdRng,
76}
77
78impl Default for ParticleSystemRng {
79    fn default() -> Self {
80        Self::new(0xDEADBEEF)
81    }
82}
83
84impl ParticleSystemRng {
85    /// Creates new PRNG with a given seed. Fixed seed guarantees that particle system's behaviour will be
86    /// deterministic.
87    pub fn new(seed: u64) -> Self {
88        Self {
89            rng_seed: seed,
90            rng: StdRng::seed_from_u64(seed),
91        }
92    }
93
94    /// Resets the state of PRNG.
95    #[inline]
96    pub fn reset(&mut self) {
97        self.rng = StdRng::seed_from_u64(self.rng_seed);
98    }
99}
100
101impl RngCore for ParticleSystemRng {
102    #[inline]
103    fn next_u32(&mut self) -> u32 {
104        self.rng.next_u32()
105    }
106
107    #[inline]
108    fn next_u64(&mut self) -> u64 {
109        self.rng.next_u64()
110    }
111
112    #[inline]
113    fn fill_bytes(&mut self, dest: &mut [u8]) {
114        self.rng.fill_bytes(dest)
115    }
116
117    #[inline]
118    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
119        self.rng.try_fill_bytes(dest)
120    }
121}
122
123impl Visit for ParticleSystemRng {
124    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
125        let mut guard = visitor.enter_region(name)?;
126
127        self.rng_seed.visit("Seed", &mut guard)?;
128
129        // Re-initialize the RNG to keep determinism.
130        if guard.is_reading() {
131            self.rng = StdRng::seed_from_u64(self.rng_seed);
132        }
133
134        Ok(())
135    }
136}
137
138/// Particle system used to create visual effects that consists of many small parts,
139/// this can be smoke, fire, dust, sparks, etc. Particle system optimized to operate
140/// on many small parts, so it is much efficient to use particle system instead of
141/// separate scene nodes. Downside of particle system is that there almost no way
142/// to control separate particles, all particles controlled by parameters of particle
143/// emitters.
144///
145/// # Emitters
146///
147/// Particle system can contain multiple particle emitters, each emitter has its own
148/// set of properties and it defines law of change of particle parameters over time.
149///
150/// # Performance
151///
152/// In general particle system can be considered as heavy visual effect, but total impact
153/// on performance defined by amount of particles and amount of pixels they take to render.
154/// A rule of thumb will be to decrease amount of particles until effect will look good
155/// enough, alternatively amount of particles can be defined by some coefficient based on
156/// graphics quality settings.
157///
158/// # Example
159///
160/// Simple smoke effect can be create like so:
161///
162/// ```
163/// # use fyrox_impl::{
164/// #     asset::manager::ResourceManager,
165/// #     core::{
166/// #         algebra::Vector3,
167/// #         color::Color,
168/// #         color_gradient::{ColorGradient, GradientPoint},
169/// #         sstorage::ImmutableString,
170/// #     },
171/// #     material::{Material, MaterialProperty, MaterialResource},
172/// #     resource::texture::Texture,
173/// #     scene::{
174/// #         base::BaseBuilder,
175/// #         graph::Graph,
176/// #         particle_system::{
177/// #             emitter::base::BaseEmitterBuilder, emitter::sphere::SphereEmitterBuilder,
178/// #             ParticleSystemBuilder,
179/// #         },
180/// #         transform::TransformBuilder,
181/// #     },
182/// # };
183/// # use std::path::Path;
184/// #
185/// fn create_smoke(graph: &mut Graph, resource_manager: &mut ResourceManager, pos: Vector3<f32>) {
186///     let mut material = Material::standard_particle_system();
187///     material
188///         .bind("diffuseTexture", resource_manager.request::<Texture>(Path::new("data/particles/smoke_04.tga")));
189///
190///     ParticleSystemBuilder::new(
191///         BaseBuilder::new()
192///             .with_lifetime(5.0)
193///             .with_local_transform(TransformBuilder::new().with_local_position(pos).build()),
194///     )
195///     .with_acceleration(Vector3::new(0.0, 0.0, 0.0))
196///     .with_color_over_lifetime_gradient({
197///         let mut gradient = ColorGradient::new();
198///         gradient.add_point(GradientPoint::new(0.00, Color::from_rgba(150, 150, 150, 0)));
199///         gradient.add_point(GradientPoint::new(
200///             0.05,
201///             Color::from_rgba(150, 150, 150, 220),
202///         ));
203///         gradient.add_point(GradientPoint::new(
204///             0.85,
205///             Color::from_rgba(255, 255, 255, 180),
206///         ));
207///         gradient.add_point(GradientPoint::new(1.00, Color::from_rgba(255, 255, 255, 0)));
208///         gradient
209///     })
210///     .with_emitters(vec![SphereEmitterBuilder::new(
211///         BaseEmitterBuilder::new()
212///             .with_max_particles(100)
213///             .with_spawn_rate(50)
214///             .with_x_velocity_range(-0.01..0.01)
215///             .with_y_velocity_range(0.02..0.03)
216///             .with_z_velocity_range(-0.01..0.01),
217///     )
218///     .with_radius(0.01)
219///     .build()])
220///     .with_material(MaterialResource::new_ok(Default::default(), material))
221///     .build(graph);
222/// }
223/// ```
224#[derive(Debug, Clone, Reflect, ComponentProvider)]
225pub struct ParticleSystem {
226    base: Base,
227
228    /// List of emitters of the particle system.
229    pub emitters: InheritableVariable<Vec<Emitter>>,
230
231    #[reflect(setter = "set_material")]
232    material: InheritableVariable<MaterialResource>,
233
234    #[reflect(setter = "set_acceleration")]
235    acceleration: InheritableVariable<Vector3<f32>>,
236
237    #[reflect(setter = "set_color_over_lifetime_gradient")]
238    color_over_lifetime: InheritableVariable<ColorGradient>,
239
240    #[reflect(setter = "play")]
241    is_playing: InheritableVariable<bool>,
242
243    #[reflect(hidden)]
244    particles: Vec<Particle>,
245
246    #[reflect(hidden)]
247    free_particles: Vec<u32>,
248
249    #[reflect(
250        description = "The maximum distance (in meters) from an observer to the particle system at \
251        which the particle system remains visible. If the distance is larger, then the particle \
252        system will fade out and eventually will be excluded from the rendering. Use this value to \
253        tweak performance. Default is 30.0"
254    )]
255    visible_distance: InheritableVariable<f32>,
256
257    #[reflect(
258        description = "Defines a coordinate system for particles. Local coordinate space could \
259    be used for particles that must move with the particle system (sparks), world space - for \
260    particles that must be detached from the particle system (smoke trails)"
261    )]
262    coordinate_system: InheritableVariable<CoordinateSystem>,
263
264    rng: ParticleSystemRng,
265}
266
267/// Coordinate system for particles generated by a particle system.
268#[derive(
269    Default,
270    Copy,
271    Clone,
272    PartialOrd,
273    PartialEq,
274    Eq,
275    Ord,
276    Hash,
277    Debug,
278    Visit,
279    Reflect,
280    AsRefStr,
281    EnumString,
282    VariantNames,
283    TypeUuidProvider,
284)]
285#[type_uuid(id = "d19e13ec-03d5-4c88-b0b2-d161d1912632")]
286pub enum CoordinateSystem {
287    /// Local coordinate system moves particles together with the particle system itself. For example
288    /// if a particle system is moved, rotated, scaled, etc. then the particle will be moved, rotated,
289    /// scaled, etc. This option could be used create oriented particle systems, such as sparks. In
290    /// this case the sparks particle system should be designed to emit sparks along Z axis, and then
291    /// the actual particle system could be rotated to emit sparks into desired direction.
292    #[default]
293    Local,
294    /// World space coordinate system spawns the particles with the current world space transformation
295    /// matrix, and then it does not affect the particles anymore. The particles will be moved in world
296    /// space using the particle system parameters. This options could be used to create effects that
297    /// needs to be detached from the actual particle system. For example, it could be used to simulate
298    /// smoke trails.
299    World,
300}
301
302impl Visit for ParticleSystem {
303    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
304        let mut region = visitor.enter_region(name)?;
305
306        self.base.visit("Base", &mut region)?;
307        self.emitters.visit("Emitters", &mut region)?;
308        self.acceleration.visit("Acceleration", &mut region)?;
309        self.color_over_lifetime
310            .visit("ColorGradient", &mut region)?;
311        self.is_playing.visit("Enabled", &mut region)?;
312        self.particles.visit("Particles", &mut region)?;
313        self.free_particles.visit("FreeParticles", &mut region)?;
314        let _ = self.rng.visit("Rng", &mut region);
315        let _ = self.visible_distance.visit("VisibleDistance", &mut region);
316        let _ = self
317            .coordinate_system
318            .visit("CoordinateSystem", &mut region);
319
320        // Backward compatibility.
321        if region.is_reading() {
322            if let Some(material) = material::visit_old_texture_as_material(
323                &mut region,
324                Material::standard_particle_system,
325            ) {
326                self.material = material.into();
327            } else {
328                self.material.visit("Material", &mut region)?;
329            }
330        } else {
331            self.material.visit("Material", &mut region)?;
332        }
333
334        let mut soft_boundary_sharpness_factor = 100.0;
335        if soft_boundary_sharpness_factor
336            .visit("SoftBoundarySharpnessFactor", &mut region)
337            .is_ok()
338        {
339            self.material.data_ref().set_property(
340                "softBoundarySharpnessFactor",
341                soft_boundary_sharpness_factor,
342            );
343        }
344
345        Ok(())
346    }
347}
348
349impl Deref for ParticleSystem {
350    type Target = Base;
351
352    fn deref(&self) -> &Self::Target {
353        &self.base
354    }
355}
356
357impl DerefMut for ParticleSystem {
358    fn deref_mut(&mut self) -> &mut Self::Target {
359        &mut self.base
360    }
361}
362
363impl TypeUuidProvider for ParticleSystem {
364    fn type_uuid() -> Uuid {
365        uuid!("8b210eff-97a4-494f-ba7a-a581d3f4a442")
366    }
367}
368
369impl ParticleSystem {
370    const FADEOUT_MARGIN: f32 = 1.5;
371
372    /// Returns current acceleration for particles in particle system.
373    pub fn acceleration(&self) -> Vector3<f32> {
374        *self.acceleration
375    }
376
377    /// Set new acceleration that will be applied to all particles,
378    /// can be used to change "gravity" vector of particles.
379    pub fn set_acceleration(&mut self, accel: Vector3<f32>) -> Vector3<f32> {
380        self.acceleration.set_value_and_mark_modified(accel)
381    }
382
383    /// Sets new "color curve" that will evaluate color over lifetime.
384    pub fn set_color_over_lifetime_gradient(&mut self, gradient: ColorGradient) -> ColorGradient {
385        self.color_over_lifetime
386            .set_value_and_mark_modified(gradient)
387    }
388
389    /// Plays or pauses the particle system. Paused particle system remains in "frozen" state
390    /// until played again again. You can manually reset state of the system by calling [`Self::clear_particles`].
391    pub fn play(&mut self, is_playing: bool) -> bool {
392        self.is_playing.set_value_and_mark_modified(is_playing)
393    }
394
395    /// Returns current particle system status.
396    pub fn is_playing(&self) -> bool {
397        *self.is_playing
398    }
399
400    /// Replaces the particles in the particle system with pre-generated set. It could be useful
401    /// to create procedural particle effects; when particles cannot be pre-made.
402    pub fn set_particles(&mut self, particles: Vec<Particle>) {
403        self.free_particles.clear();
404        self.particles = particles;
405    }
406
407    /// Returns a reference to a slice to the current set of particles, generated by the particle system.
408    pub fn particles(&self) -> &[Particle] {
409        &self.particles
410    }
411
412    /// Removes all generated particles.
413    pub fn clear_particles(&mut self) {
414        self.particles.clear();
415        self.free_particles.clear();
416        for emitter in self.emitters.get_value_mut_silent().iter_mut() {
417            emitter.alive_particles = 0;
418            emitter.spawned_particles = 0;
419        }
420    }
421
422    /// Sets the new material for the particle system.
423    pub fn set_material(&mut self, material: MaterialResource) -> MaterialResource {
424        self.material.set_value_and_mark_modified(material)
425    }
426
427    /// Returns current material used by particle system.
428    pub fn texture(&self) -> MaterialResource {
429        (*self.material).clone()
430    }
431
432    /// Returns current material used by particle system by ref.
433    pub fn texture_ref(&self) -> &MaterialResource {
434        &self.material
435    }
436
437    fn tick(&mut self, dt: f32) {
438        for emitter in self.emitters.get_value_mut_silent().iter_mut() {
439            emitter.tick(dt);
440        }
441
442        let global_transform = self.global_transform();
443
444        for (i, emitter) in self.emitters.get_value_mut_silent().iter_mut().enumerate() {
445            for _ in 0..emitter.particles_to_spawn {
446                let mut particle = Particle {
447                    emitter_index: i as u32,
448                    ..Particle::default()
449                };
450                emitter.alive_particles += 1;
451                emitter.emit(&mut particle, &mut self.rng);
452                if *self.coordinate_system == CoordinateSystem::World {
453                    particle.position = global_transform
454                        .transform_point(&particle.position.into())
455                        .coords;
456                }
457                if let Some(free_index) = self.free_particles.pop() {
458                    self.particles[free_index as usize] = particle;
459                } else {
460                    self.particles.push(particle);
461                }
462            }
463        }
464
465        let acceleration_offset = self.acceleration.scale(dt * dt);
466
467        for (i, particle) in self.particles.iter_mut().enumerate() {
468            if particle.alive {
469                particle.lifetime += dt;
470                if particle.lifetime >= particle.initial_lifetime {
471                    self.free_particles.push(i as u32);
472                    if let Some(emitter) = self
473                        .emitters
474                        .get_value_mut_and_mark_modified()
475                        .get_mut(particle.emitter_index as usize)
476                    {
477                        emitter.alive_particles = emitter.alive_particles.saturating_sub(1);
478                    }
479                    particle.alive = false;
480                    particle.lifetime = particle.initial_lifetime;
481                } else {
482                    particle.velocity += acceleration_offset;
483                    particle.position += particle.velocity;
484                    particle.size += particle.size_modifier * dt;
485                    if particle.size < 0.0 {
486                        particle.size = 0.0;
487                    }
488                    particle.rotation += particle.rotation_speed * dt;
489
490                    let k = particle.lifetime / particle.initial_lifetime;
491                    particle.color = self.color_over_lifetime.get_color(k);
492                }
493            }
494        }
495    }
496
497    /// Simulates particle system for the given `time` with given time step (`dt`). `dt` is usually `1.0 / 60.0`.
498    pub fn rewind(&mut self, dt: f32, time: f32) {
499        assert!(dt > 0.0);
500
501        self.rng.reset();
502        self.clear_particles();
503
504        let mut t = 0.0;
505        while t < time {
506            self.tick(dt);
507            t += dt;
508        }
509    }
510
511    /// Sets the maximum distance (in meters) from an observer to the particle system at which the
512    /// particle system remains visible. If the distance is larger, then the particle system will
513    /// fade out and eventually will be excluded from the rendering. Use this value to tweak
514    /// performance. The larger the particle system, the larger this value should be. Default is 10.0.
515    pub fn set_visible_distance(&mut self, distance: f32) {
516        self.visible_distance.set_value_and_mark_modified(distance);
517    }
518
519    /// Returns current visible distance of the particle system. See [`Self::set_visible_distance`]
520    /// for more info.
521    pub fn visible_distance(&self) -> f32 {
522        *self.visible_distance
523    }
524
525    /// Sets a new coordinate system for the particles in the particle system. See [`CoordinateSystem`]
526    /// docs for more info.
527    pub fn set_coordinate_system(&mut self, coordinate_system: CoordinateSystem) {
528        self.coordinate_system
529            .set_value_and_mark_modified(coordinate_system);
530    }
531
532    /// Returns current coordinate system of the particle system.
533    pub fn coordinate_system(&self) -> CoordinateSystem {
534        *self.coordinate_system
535    }
536
537    fn is_distance_clipped(&self, point: &Vector3<f32>) -> bool {
538        point.metric_distance(&self.global_position())
539            > (*self.visible_distance + Self::FADEOUT_MARGIN)
540    }
541}
542
543impl Default for ParticleSystem {
544    fn default() -> Self {
545        ParticleSystemBuilder::new(BaseBuilder::new()).build_particle_system()
546    }
547}
548
549impl ConstructorProvider<Node, Graph> for ParticleSystem {
550    fn constructor() -> NodeConstructor {
551        NodeConstructor::new::<Self>().with_variant("Particle System", |_| {
552            ParticleSystemBuilder::new(BaseBuilder::new().with_name("ParticleSystem"))
553                .with_emitters(vec![SphereEmitterBuilder::new(
554                    BaseEmitterBuilder::new()
555                        .with_max_particles(100)
556                        .resurrect_particles(true),
557                )
558                .with_radius(1.0)
559                .build()])
560                .build_node()
561                .into()
562        })
563    }
564}
565
566impl NodeTrait for ParticleSystem {
567    fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
568        AxisAlignedBoundingBox::unit()
569    }
570
571    fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
572        self.local_bounding_box()
573            .transform(&self.global_transform())
574    }
575
576    fn id(&self) -> Uuid {
577        Self::type_uuid()
578    }
579
580    fn update(&mut self, context: &mut UpdateContext) {
581        let dt = context.dt;
582
583        if *self.is_playing {
584            self.tick(dt);
585        }
586    }
587
588    fn collect_render_data(&self, ctx: &mut RenderContext) -> RdcControlFlow {
589        if !self.should_be_rendered(ctx.frustum)
590            || self.is_distance_clipped(&ctx.observer_info.observer_position)
591        {
592            return RdcControlFlow::Continue;
593        }
594
595        if renderer::is_shadow_pass(ctx.render_pass_name) && !self.cast_shadows() {
596            return RdcControlFlow::Continue;
597        }
598
599        let distance_to_observer = ctx
600            .observer_info
601            .observer_position
602            .metric_distance(&self.global_position());
603
604        let particle_alpha_factor = if distance_to_observer >= self.visible_distance() {
605            1.0 - (distance_to_observer - self.visible_distance()) / Self::FADEOUT_MARGIN
606        } else {
607            1.0
608        };
609
610        let mut sorted_particles = Vec::new();
611        for (i, particle) in self.particles.iter().enumerate() {
612            if particle.alive {
613                let actual_position = particle.position + self.base.global_position();
614                particle
615                    .sqr_distance_to_camera
616                    .set((ctx.observer_info.observer_position - actual_position).norm_squared());
617                sorted_particles.push(i as u32);
618            }
619        }
620
621        let particles = &self.particles;
622
623        sorted_particles.sort_by(|a, b| {
624            let particle_a = particles.get(*a as usize).unwrap();
625            let particle_b = particles.get(*b as usize).unwrap();
626
627            // Reverse ordering because we want to sort back-to-front.
628            if particle_a.sqr_distance_to_camera < particle_b.sqr_distance_to_camera {
629                Ordering::Greater
630            } else if particle_a.sqr_distance_to_camera > particle_b.sqr_distance_to_camera {
631                Ordering::Less
632            } else {
633                Ordering::Equal
634            }
635        });
636
637        let global_transform = self.global_transform();
638        let sort_index = ctx.calculate_sorting_index(self.global_position());
639
640        ctx.storage.push_triangles(
641            Vertex::layout(),
642            &self.material,
643            RenderPath::Forward,
644            sort_index,
645            self.handle(),
646            &mut move |mut vertex_buffer, mut triangle_buffer| {
647                let vertices = sorted_particles.iter().flat_map(move |particle_index| {
648                    let particle = self.particles.get(*particle_index as usize).unwrap();
649
650                    let position = if *self.coordinate_system == CoordinateSystem::Local {
651                        global_transform
652                            .transform_point(&Point3::from(particle.position))
653                            .coords
654                    } else {
655                        particle.position
656                    };
657
658                    let alpha = (particle.color.a as f32 * particle_alpha_factor) as u8;
659                    let color = Color::from_rgba(
660                        particle.color.r,
661                        particle.color.g,
662                        particle.color.b,
663                        alpha,
664                    );
665
666                    [
667                        Vertex {
668                            position,
669                            tex_coord: Vector2::default(),
670                            size: particle.size,
671                            rotation: particle.rotation,
672                            color,
673                        },
674                        Vertex {
675                            position,
676                            tex_coord: Vector2::new(1.0, 0.0),
677                            size: particle.size,
678                            rotation: particle.rotation,
679                            color,
680                        },
681                        Vertex {
682                            position,
683                            tex_coord: Vector2::new(1.0, 1.0),
684                            size: particle.size,
685                            rotation: particle.rotation,
686                            color,
687                        },
688                        Vertex {
689                            position,
690                            tex_coord: Vector2::new(0.0, 1.0),
691                            size: particle.size,
692                            rotation: particle.rotation,
693                            color,
694                        },
695                    ]
696                });
697
698                let triangles = (0..sorted_particles.len()).flat_map(|i| {
699                    let base_index = (i * 4) as u32;
700
701                    [
702                        TriangleDefinition([base_index, base_index + 1, base_index + 2]),
703                        TriangleDefinition([base_index, base_index + 2, base_index + 3]),
704                    ]
705                });
706
707                let start_vertex_index = vertex_buffer.vertex_count();
708
709                for vertex in vertices {
710                    vertex_buffer
711                        .push_vertex_raw(value_as_u8_slice(&vertex))
712                        .unwrap();
713                }
714
715                triangle_buffer.push_triangles_iter_with_offset(start_vertex_index, triangles)
716            },
717        );
718
719        RdcControlFlow::Continue
720    }
721}
722
723/// Particle system builder allows you to construct particle system in declarative manner.
724/// This is typical implementation of Builder pattern.
725pub struct ParticleSystemBuilder {
726    base_builder: BaseBuilder,
727    emitters: Vec<Emitter>,
728    material: MaterialResource,
729    acceleration: Vector3<f32>,
730    particles: Vec<Particle>,
731    color_over_lifetime: ColorGradient,
732    is_playing: bool,
733    rng: ParticleSystemRng,
734    visible_distance: f32,
735    coordinate_system: CoordinateSystem,
736}
737
738impl ParticleSystemBuilder {
739    /// Creates new builder with default parameters.
740    pub fn new(base_builder: BaseBuilder) -> Self {
741        Self {
742            base_builder,
743            emitters: Default::default(),
744            material: MaterialResource::new_ok(
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        }
756    }
757
758    /// Sets desired emitters for particle system.
759    pub fn with_emitters(mut self, emitters: Vec<Emitter>) -> Self {
760        self.emitters = emitters;
761        self
762    }
763
764    /// Sets desired material for particle system.
765    pub fn with_material(mut self, material: MaterialResource) -> Self {
766        self.material = material;
767        self
768    }
769
770    /// Sets desired acceleration for particle system.
771    pub fn with_acceleration(mut self, acceleration: Vector3<f32>) -> Self {
772        self.acceleration = acceleration;
773        self
774    }
775
776    /// Sets the desired visible distance for the particle system. See [`ParticleSystem::set_visible_distance`]
777    /// for more info.
778    pub fn with_visible_distance(mut self, distance: f32) -> Self {
779        self.visible_distance = distance;
780        self
781    }
782
783    /// Sets color gradient over lifetime for particle system.
784    pub fn with_color_over_lifetime_gradient(mut self, color_over_lifetime: ColorGradient) -> Self {
785        self.color_over_lifetime = color_over_lifetime;
786        self
787    }
788
789    /// Sets an initial set of particles that not belongs to any emitter. This method
790    /// could be useful if you need a custom position/velocity/etc. of each particle.
791    pub fn with_particles(mut self, particles: Vec<Particle>) -> Self {
792        self.particles = particles;
793        self
794    }
795
796    /// Sets initial particle system state.
797    pub fn with_playing(mut self, enabled: bool) -> Self {
798        self.is_playing = enabled;
799        self
800    }
801
802    /// Sets desired pseudo-random numbers generator.
803    pub fn with_rng(mut self, rng: ParticleSystemRng) -> Self {
804        self.rng = rng;
805        self
806    }
807
808    /// Sets the desired coordinate system for particles.
809    pub fn with_coordinate_system(mut self, coordinate_system: CoordinateSystem) -> Self {
810        self.coordinate_system = coordinate_system;
811        self
812    }
813
814    fn build_particle_system(self) -> ParticleSystem {
815        ParticleSystem {
816            base: self.base_builder.build_base(),
817            particles: self.particles,
818            free_particles: Vec::new(),
819            emitters: self.emitters.into(),
820            material: self.material.into(),
821            acceleration: self.acceleration.into(),
822            color_over_lifetime: self.color_over_lifetime.into(),
823            is_playing: self.is_playing.into(),
824            rng: self.rng,
825            visible_distance: self.visible_distance.into(),
826            coordinate_system: self.coordinate_system.into(),
827        }
828    }
829
830    /// Creates new instance of particle system.
831    pub fn build_node(self) -> Node {
832        Node::new(self.build_particle_system())
833    }
834
835    /// Creates new instance of particle system and adds it to the graph.
836    pub fn build(self, graph: &mut Graph) -> Handle<Node> {
837        graph.add_node(self.build_node())
838    }
839}