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::{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/// 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        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        // Backward compatibility.
320        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    /// Returns current acceleration for particles in particle system.
370    pub fn acceleration(&self) -> Vector3<f32> {
371        *self.acceleration
372    }
373
374    /// Set new acceleration that will be applied to all particles,
375    /// can be used to change "gravity" vector of particles.
376    pub fn set_acceleration(&mut self, accel: Vector3<f32>) -> Vector3<f32> {
377        self.acceleration.set_value_and_mark_modified(accel)
378    }
379
380    /// Sets new "color curve" that will evaluate color over lifetime.
381    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    /// Plays or pauses the particle system. Paused particle system remains in "frozen" state
387    /// until played again again. You can manually reset state of the system by calling [`Self::clear_particles`].
388    pub fn play(&mut self, is_playing: bool) -> bool {
389        self.is_playing.set_value_and_mark_modified(is_playing)
390    }
391
392    /// Returns current particle system status.
393    pub fn is_playing(&self) -> bool {
394        *self.is_playing
395    }
396
397    /// Replaces the particles in the particle system with pre-generated set. It could be useful
398    /// to create procedural particle effects; when particles cannot be pre-made.
399    pub fn set_particles(&mut self, particles: Vec<Particle>) {
400        self.free_particles.clear();
401        self.particles = particles;
402    }
403
404    /// Returns a reference to a slice to the current set of particles, generated by the particle system.
405    pub fn particles(&self) -> &[Particle] {
406        &self.particles
407    }
408
409    /// Removes all generated particles.
410    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    /// Sets the new material for the particle system.
420    pub fn set_material(&mut self, material: MaterialResource) -> MaterialResource {
421        self.material.set_value_and_mark_modified(material)
422    }
423
424    /// Returns current material used by particle system.
425    pub fn texture(&self) -> MaterialResource {
426        (*self.material).clone()
427    }
428
429    /// Returns current material used by particle system by ref.
430    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    /// Simulates particle system for the given `time` with given time step (`dt`). `dt` is usually `1.0 / 60.0`.
495    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    /// Sets the maximum distance (in meters) from an observer to the particle system at which the
509    /// particle system remains visible. If the distance is larger, then the particle system will
510    /// fade out and eventually will be excluded from the rendering. Use this value to tweak
511    /// performance. The larger the particle system, the larger this value should be. Default is 10.0.
512    pub fn set_visible_distance(&mut self, distance: f32) {
513        self.visible_distance.set_value_and_mark_modified(distance);
514    }
515
516    /// Returns current visible distance of the particle system. See [`Self::set_visible_distance`]
517    /// for more info.
518    pub fn visible_distance(&self) -> f32 {
519        *self.visible_distance
520    }
521
522    /// Sets a new coordinate system for the particles in the particle system. See [`CoordinateSystem`]
523    /// docs for more info.
524    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    /// Returns current coordinate system of the particle system.
530    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            // Reverse ordering because we want to sort back-to-front.
625            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
721/// Particle system builder allows you to construct particle system in declarative manner.
722/// This is typical implementation of Builder pattern.
723pub 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    /// Creates new builder with default parameters.
739    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    /// Sets desired emitters for particle system.
760    pub fn with_emitters(mut self, emitters: Vec<Emitter>) -> Self {
761        self.emitters = emitters;
762        self
763    }
764
765    /// Sets desired material for particle system.
766    pub fn with_material(mut self, material: MaterialResource) -> Self {
767        self.material = material;
768        self
769    }
770
771    /// Sets desired acceleration for particle system.
772    pub fn with_acceleration(mut self, acceleration: Vector3<f32>) -> Self {
773        self.acceleration = acceleration;
774        self
775    }
776
777    /// Sets the desired visible distance for the particle system. See [`ParticleSystem::set_visible_distance`]
778    /// for more info.
779    pub fn with_visible_distance(mut self, distance: f32) -> Self {
780        self.visible_distance = distance;
781        self
782    }
783
784    /// Sets color gradient over lifetime for particle system.
785    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    /// Sets an initial set of particles that not belongs to any emitter. This method
791    /// could be useful if you need a custom position/velocity/etc. of each particle.
792    pub fn with_particles(mut self, particles: Vec<Particle>) -> Self {
793        self.particles = particles;
794        self
795    }
796
797    /// Sets initial particle system state.
798    pub fn with_playing(mut self, enabled: bool) -> Self {
799        self.is_playing = enabled;
800        self
801    }
802
803    /// Sets desired pseudo-random numbers generator.
804    pub fn with_rng(mut self, rng: ParticleSystemRng) -> Self {
805        self.rng = rng;
806        self
807    }
808
809    /// Sets the desired coordinate system for particles.
810    pub fn with_coordinate_system(mut self, coordinate_system: CoordinateSystem) -> Self {
811        self.coordinate_system = coordinate_system;
812        self
813    }
814
815    /// Sets a margin value in which the distance fading will occur.
816    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    /// Creates new instance of particle system.
839    pub fn build_node(self) -> Node {
840        Node::new(self.build_particle_system())
841    }
842
843    /// Creates new instance of particle system and adds it to the graph.
844    pub fn build(self, graph: &mut Graph) -> Handle<Node> {
845        graph.add_node(self.build_node())
846    }
847}