Skip to main content

bevy_hanabi/
asset.rs

1#[cfg(feature = "serde")]
2use bevy::asset::{io::Reader, AssetLoader, LoadContext};
3#[cfg(feature = "serde")]
4use bevy::reflect::TypePath;
5use bevy::{
6    asset::{Asset, Assets, Handle},
7    log::trace,
8    math::{Vec2, Vec3},
9    platform::collections::HashSet,
10    prelude::{Component, Entity, FromWorld, Mesh, Plane3d, Resource, World},
11    reflect::Reflect,
12    utils::default,
13};
14use serde::{Deserialize, Serialize};
15#[cfg(feature = "serde")]
16use thiserror::Error;
17use wgpu::{BlendComponent, BlendFactor, BlendOperation, BlendState};
18
19use crate::{
20    modifier::{Modifier, RenderModifier},
21    BoxedModifier, ExprHandle, ModifierContext, Module, ParticleLayout, Property, PropertyLayout,
22    SimulationSpace, SpawnerSettings, TextureLayout,
23};
24
25/// Type of motion integration applied to the particles of a system.
26#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
27pub enum MotionIntegration {
28    /// No motion integration. The [`Attribute::POSITION`] of the particles
29    /// needs to be explicitly assigned by a modifier for the particles to move.
30    ///
31    /// [`Attribute::POSITION`]: crate::Attribute::POSITION
32    None,
33
34    /// Apply Euler motion integration each simulation update before all
35    /// modifiers are applied.
36    ///
37    /// Not to be confused with Bevy's `PreUpdate` phase. Here "update" refers
38    /// to the particle update on the GPU via a compute shader.
39    PreUpdate,
40
41    /// Apply Euler motion integration each simulation update after all
42    /// modifiers are applied. This is the default.
43    ///
44    /// Not to be confused with Bevy's `PostUpdate` phase. Here "update" refers
45    /// to the particle update on the GPU via a compute shader.
46    #[default]
47    PostUpdate,
48}
49
50/// Simulation condition for an effect.
51#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
52pub enum SimulationCondition {
53    /// Simulate the effect only when visible.
54    ///
55    /// The visibility is determined by the [`InheritedVisibility`] and the
56    /// [`ViewVisibility`] components, if present. The effect is assumed to be
57    /// visible if those components are absent.
58    ///
59    /// This is the default for all assets, and is the most performant option,
60    /// allowing to have many effects in the scene without the need to simulate
61    /// all of them if they're not visible.
62    ///
63    /// Note that any [`ParticleEffect`] spawned is always compiled into a
64    /// [`CompiledParticleEffect`], even when it's not visible and even when
65    /// that variant is selected. That means it consumes GPU resources (memory,
66    /// in particular).
67    ///
68    /// Note also that AABB culling is not currently available. Only boolean
69    /// ON/OFF visibility is used.
70    ///
71    /// [`Visibility`]: bevy::camera::visibility::Visibility
72    /// [`InheritedVisibility`]: bevy::camera::visibility::InheritedVisibility
73    /// [`ViewVisibility`]: bevy::camera::visibility::ViewVisibility
74    /// [`ParticleEffect`]: crate::ParticleEffect
75    /// [`CompiledParticleEffect`]: crate::CompiledParticleEffect
76    #[default]
77    WhenVisible,
78
79    /// Always simulate the effect, whether visible or not.
80    ///
81    /// For performance reasons, it's recommended to only simulate visible
82    /// particle effects (that is, use [`SimulationCondition::WhenVisible`]).
83    /// However occasionally it may be needed to continue the simulation
84    /// when the effect is not visible, to ensure some temporal continuity when
85    /// the effect is made visible again. This is an uncommon case, and you
86    /// should be aware of the performance implications of using this
87    /// condition, and only use it when strictly necessary.
88    ///
89    /// Any [`InheritedVisibility`] or [`ViewVisibility`] component is ignored.
90    ///
91    /// [`Visibility`]: bevy::camera::visibility::Visibility
92    /// [`InheritedVisibility`]: bevy::camera::visibility::InheritedVisibility
93    /// [`ViewVisibility`]: bevy::camera::visibility::ViewVisibility
94    Always,
95}
96
97/// Alpha mode for rendering an effect.
98///
99/// The alpha mode determines how the alpha value of a particle is used to
100/// render it. In general effects use semi-transparent particles. However, there
101/// are multiple alpha blending techniques available, producing different
102/// results.
103///
104/// This is very similar to the `bevy::prelude::AlphaMode` of the `bevy_pbr`
105/// crate, except that a different set of values is supported which reflects
106/// what this library currently supports.
107///
108/// The alpha mode only affects the render phase that particles are rendered
109/// into when rendering 3D views. For 2D views, all particle effects are
110/// rendered during the [`Transparent2d`] render phase.
111///
112/// [`Transparent2d`]: bevy::core_pipeline::core_2d::Transparent2d
113#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
114#[non_exhaustive]
115pub enum AlphaMode {
116    /// Render the effect with alpha blending.
117    ///
118    /// This is the most common mode for handling transparency. It uses the
119    /// "blend" or "over" formula, where the color of each particle fragment is
120    /// accumulated into the destination render target after being modulated by
121    /// its alpha value.
122    ///
123    /// ```txt
124    /// dst_color = src_color * (1 - particle_alpha) + particle_color * particle_alpha;
125    /// dst_alpha = src_alpha * (1 - particle_alpha) + particle_alpha
126    /// ```
127    ///
128    /// This is the default blending mode.
129    ///
130    /// For 3D views, effects with this mode are rendered during the
131    /// [`Transparent3d`] render phase.
132    ///
133    /// [`Transparent3d`]: bevy::core_pipeline::core_3d::Transparent3d
134    #[default]
135    Blend,
136
137    /// Similar to [`AlphaMode::Blend`], however assumes RGB channel values are
138    /// [premultiplied](https://en.wikipedia.org/wiki/Alpha_compositing#Straight_versus_premultiplied).
139    ///
140    /// For otherwise constant RGB values, behaves more like
141    /// [`AlphaMode::Blend`] for alpha values closer to 1.0, and more like
142    /// [`AlphaMode::Add`] for alpha values closer to 0.0.
143    ///
144    /// Can be used to avoid “border” or “outline” artifacts that can occur
145    /// when using plain alpha-blended textures.
146    Premultiply,
147
148    /// Combines the color of the fragments with the colors behind them in an
149    /// additive process, (i.e. like light) producing lighter results.
150    ///
151    /// Black produces no effect. Alpha values can be used to modulate the
152    /// result.
153    ///
154    /// Useful for effects like holograms, ghosts, lasers and other energy
155    /// beams.
156    Add,
157
158    /// Combines the color of the fragments with the colors behind them in a
159    /// multiplicative process, (i.e. like pigments) producing darker results.
160    ///
161    /// White produces no effect. Alpha values can be used to modulate the
162    /// result.
163    ///
164    /// Useful for effects like stained glass, window tint film and some colored
165    /// liquids.
166    Multiply,
167
168    /// Render the effect with alpha masking.
169    ///
170    /// With this mode, the final alpha value computed per particle fragment is
171    /// compared against the cutoff value stored in this enum. Any fragment
172    /// with a value under the cutoff is discarded, while any fragment with
173    /// a value equal or over the cutoff becomes fully opaque. The end result is
174    /// an opaque particle with a cutout shape.
175    ///
176    /// ```txt
177    /// if src_alpha >= cutoff {
178    ///     dst_color = particle_color;
179    ///     dst_alpha = 1;
180    /// } else {
181    ///     discard;
182    /// }
183    /// ```
184    ///
185    /// The assigned expression must yield a scalar floating-point value,
186    /// typically in the \[0:1\] range. This expression is assigned at the
187    /// beginning of the fragment shader to the special built-in `alpha_cutoff`
188    /// variable, which can be further accessed and modified by render
189    /// modifiers.
190    ///
191    /// The cutoff threshold comparison of the fragment's alpha value against
192    /// `alpha_cutoff` is performed as the last operation in the fragment
193    /// shader. This allows modifiers to affect the alpha value of the
194    /// particle before it's tested against the cutoff value stored in
195    /// `alpha_cutoff`.
196    ///
197    /// For 3D views, effects with this mode are rendered during the
198    /// [`AlphaMask3d`] render phase.
199    ///
200    /// [`AlphaMask3d`]: bevy::core_pipeline::core_3d::AlphaMask3d
201    Mask(ExprHandle),
202
203    /// Render the effect with no alpha, and update the depth buffer.
204    ///
205    /// Use this mode when every pixel covered by the particle's mesh is fully
206    /// opaque.
207    Opaque,
208}
209
210impl From<AlphaMode> for BlendState {
211    fn from(value: AlphaMode) -> Self {
212        match value {
213            AlphaMode::Blend => BlendState::ALPHA_BLENDING,
214            AlphaMode::Premultiply => BlendState::PREMULTIPLIED_ALPHA_BLENDING,
215            AlphaMode::Add => BlendState {
216                color: BlendComponent {
217                    src_factor: BlendFactor::SrcAlpha,
218                    dst_factor: BlendFactor::One,
219                    operation: BlendOperation::Add,
220                },
221                alpha: BlendComponent {
222                    src_factor: BlendFactor::Zero,
223                    dst_factor: BlendFactor::One,
224                    operation: BlendOperation::Add,
225                },
226            },
227            AlphaMode::Multiply => BlendState {
228                color: BlendComponent {
229                    src_factor: BlendFactor::Dst,
230                    dst_factor: BlendFactor::OneMinusSrcAlpha,
231                    operation: BlendOperation::Add,
232                },
233                alpha: BlendComponent::OVER,
234            },
235            _ => BlendState::ALPHA_BLENDING,
236        }
237    }
238}
239
240/// Default particle mesh, if not otherwise specified in [`EffectAsset::mesh`].
241///
242/// This defaults to a unit quad facing the Z axis.
243///
244/// [`EffectAsset`]: crate::EffectAsset
245#[derive(Debug, Clone, Resource)]
246pub struct DefaultMesh(pub Handle<Mesh>);
247
248impl FromWorld for DefaultMesh {
249    fn from_world(world: &mut World) -> Self {
250        let mut meshes = world.resource_mut::<Assets<Mesh>>();
251        let handle = meshes.add(Plane3d::new(Vec3::Z, Vec2::splat(0.5)));
252        trace!("Created DefaultMesh(Plane3d/Z): handle={handle:?}");
253        Self(handle)
254    }
255}
256
257/// Asset describing a visual effect.
258///
259/// An effect asset represents the description of an effect, intended to be
260/// authored during development and instantiated once or more during the
261/// application execution.
262///
263/// An actual effect instance can be spanwed with a [`ParticleEffect`]
264/// component which references the [`EffectAsset`].
265///
266/// [`ParticleEffect`]: crate::ParticleEffect
267/// [`EffectAsset`]: crate::EffectAsset
268#[derive(Asset, Default, Clone, Reflect)]
269#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
270#[reflect(from_reflect = false)]
271pub struct EffectAsset {
272    /// Display name of the effect.
273    ///
274    /// This has no internal use, and is mostly for the user to identify an
275    /// effect or for display in some tool UI. It's however used in serializing
276    /// the asset.
277    pub name: String,
278    /// Maximum number of concurrent particles.
279    ///
280    /// The capacity is the maximum number of particles that can be alive at the
281    /// same time. It determines the size of various GPU resources, most notably
282    /// the particle buffer itself. To prevent wasting GPU resources, users
283    /// should keep this quantity as close as possible to the maximum number of
284    /// particles they expect to render.
285    capacity: u32,
286    /// The CPU spawner for this effect.
287    pub spawner: SpawnerSettings,
288    /// For 2D rendering, the Z coordinate used as the sort key.
289    ///
290    /// This value is passed to the render pipeline and used when sorting
291    /// transparent items to render, to order them. As a result, effects
292    /// with different Z values cannot be batched together, which may
293    /// negatively affect performance.
294    ///
295    /// Ignored for 3D rendering.
296    pub z_layer_2d: f32,
297    /// Particle simulation space.
298    pub simulation_space: SimulationSpace,
299    /// Condition under which the effect is simulated.
300    pub simulation_condition: SimulationCondition,
301    /// Seed for the pseudo-random number generator.
302    ///
303    /// This value is used as the default value for all [`ParticleEffect`]
304    /// instances based on this asset. You can override this on a per-instance
305    /// basis by setting [`ParticleEffect::prng_seed`]. The resulting value
306    /// is uploaded to GPU and used for the various random expressions and
307    /// quantities computed in shaders.
308    ///
309    /// [`ParticleEffect`]: crate::ParticleEffect
310    /// [`ParticleEffect::prng_seed`]: crate::ParticleEffect::prng_seed
311    pub prng_seed: u32,
312    /// Init modifier defining the effect.
313    #[reflect(ignore)]
314    // TODO - Can't manage to implement FromReflect for BoxedModifier in a nice way yet
315    init_modifiers: Vec<BoxedModifier>,
316    /// update modifiers defining the effect.
317    #[reflect(ignore)]
318    // TODO - Can't manage to implement FromReflect for BoxedModifier in a nice way yet
319    update_modifiers: Vec<BoxedModifier>,
320    /// Render modifiers defining the effect.
321    #[reflect(ignore)]
322    // TODO - Can't manage to implement FromReflect for BoxedModifier in a nice way yet
323    render_modifiers: Vec<BoxedModifier>,
324    /// Type of motion integration applied to the particles of a system.
325    pub motion_integration: MotionIntegration,
326    /// Expression module for this effect.
327    module: Module,
328    /// Alpha mode.
329    pub alpha_mode: AlphaMode,
330    /// The mesh that each particle renders.
331    ///
332    /// If `None`, the effect uses the [`DefaultMesh`].
333    #[cfg_attr(feature = "serde", serde(skip))]
334    pub mesh: Option<Handle<Mesh>>,
335}
336
337impl EffectAsset {
338    /// Create a new effect asset.
339    ///
340    /// The effect assets requires 2 essential pieces:
341    /// - The capacity of the effect, which represents the maximum number of
342    ///   particles which can be stored and simulated at the same time for each
343    ///   group. Each group has its own capacity, in number of particles. All
344    ///   capacities must be non-zero and should be the smallest possible values
345    ///   which allow you to author the effect. These values directly impact the
346    ///   GPU memory consumption of the effect, which will allocate some buffers
347    ///   to store that many particles for as long as the effect exists. The
348    ///   capacities of an effect are immutable. See also [`capacity()`] for
349    ///   more details.
350    /// - The [`SpawnerSettings`], which defines when particles are emitted.
351    ///
352    /// Additionally, if any modifier added to this effect uses some [`Expr`] to
353    /// customize its behavior, then those [`Expr`] are stored into a [`Module`]
354    /// which should be passed to this method. If expressions are not used, just
355    /// pass an empty module [`Module::default()`].
356    ///
357    /// # Examples
358    ///
359    /// Create a new effect asset without any modifier. This effect doesn't
360    /// really do anything because _e.g._ the particles have a zero lifetime.
361    ///
362    /// ```
363    /// # use bevy_hanabi::*;
364    /// let spawner = SpawnerSettings::rate(5_f32.into()); // 5 particles per second
365    /// let module = Module::default();
366    /// let capacity = 1024; // max 1024 particles alive at any time
367    /// let effect = EffectAsset::new(capacity, spawner, module);
368    /// ```
369    ///
370    /// Create a new effect asset with a modifier holding an expression. The
371    /// expression is stored inside the [`Module`] transfered to the
372    /// [`EffectAsset`], which owns the module once created.
373    ///
374    /// ```
375    /// # use bevy_hanabi::*;
376    /// let spawner = SpawnerSettings::rate(5_f32.into()); // 5 particles per second
377    ///
378    /// let mut module = Module::default();
379    ///
380    /// // Create a modifier that initialized the particle lifetime to 10 seconds.
381    /// let lifetime = module.lit(10.); // literal value "10.0"
382    /// let init_lifetime = SetAttributeModifier::new(Attribute::LIFETIME, lifetime);
383    ///
384    /// let capacity = 1024; // max 1024 particles alive at any time
385    /// let effect = EffectAsset::new(capacity, spawner, module);
386    /// ```
387    ///
388    /// [`capacity()`]: self::EffectAsset::capacity
389    /// [`Expr`]: crate::graph::expr::Expr
390    pub fn new(capacity: u32, spawner: SpawnerSettings, module: Module) -> Self {
391        Self {
392            capacity,
393            spawner,
394            module,
395            ..default()
396        }
397    }
398
399    /// Get the capacity of the effect, in number of particles.
400    ///
401    /// This value represents the number of particles stored in GPU memory at
402    /// all times, even if unused, so you should try to minimize it.
403    /// However, the library cannot emit more particles than the effect
404    /// capacity. Whatever the spawner settings, if the number of particles
405    /// reaches the capacity, no new particle can be emitted. Choosing an
406    /// appropriate capacity for an effect is therefore a compromise between
407    /// more particles available for visuals and more GPU memory usage.
408    ///
409    /// Common values range from 256 or less for smaller effects, to several
410    /// hundreds of thousands for unique effects consuming a large portion of
411    /// the GPU memory budget. Hanabi has been tested with over a million
412    /// particles, however the performance will largely depend on the actual GPU
413    /// hardware and available memory, so authors are encouraged not to go too
414    /// crazy with the capacity.
415    ///
416    /// [`EffectSpawner`]: crate::EffectSpawner
417    pub fn capacity(&self) -> u32 {
418        self.capacity
419    }
420
421    /// Get the expression module storing all expressions in use by modifiers of
422    /// this effect.
423    pub fn module(&self) -> &Module {
424        &self.module
425    }
426
427    /// Set the effect name.
428    ///
429    /// The effect name is used when serializing the effect.
430    pub fn with_name(mut self, name: impl Into<String>) -> Self {
431        self.name = name.into();
432        self
433    }
434
435    /// Set the effect's simulation condition.
436    pub fn with_simulation_condition(mut self, simulation_condition: SimulationCondition) -> Self {
437        self.simulation_condition = simulation_condition;
438        self
439    }
440
441    /// Set the effect's simulation space.
442    pub fn with_simulation_space(mut self, simulation_space: SimulationSpace) -> Self {
443        self.simulation_space = simulation_space;
444        self
445    }
446
447    /// Set the alpha mode.
448    pub fn with_alpha_mode(mut self, alpha_mode: AlphaMode) -> Self {
449        self.alpha_mode = alpha_mode;
450        self
451    }
452
453    /// Set the effect's motion integration.
454    pub fn with_motion_integration(mut self, motion_integration: MotionIntegration) -> Self {
455        self.motion_integration = motion_integration;
456        self
457    }
458
459    /// Get the list of existing properties.
460    ///
461    /// This is a shortcut for `self.module().properties()`.
462    pub fn properties(&self) -> &[Property] {
463        self.module.properties()
464    }
465
466    /// Add an initialization modifier to the effect.
467    ///
468    /// Initialization modifiers apply to all particles that are spawned or
469    /// cloned.
470    ///
471    /// # Panics
472    ///
473    /// Panics if the modifier doesn't support the init context (that is,
474    /// `modifier.context()` returns a flag which doesn't include
475    /// [`ModifierContext::Init`]).
476    #[inline]
477    pub fn init<M>(mut self, modifier: M) -> Self
478    where
479        M: Modifier + Send + Sync,
480    {
481        assert!(modifier.context().contains(ModifierContext::Init));
482        self.init_modifiers.push(Box::new(modifier));
483        self
484    }
485
486    /// Add an update modifier to the effect.
487    ///
488    /// # Panics
489    ///
490    /// Panics if the modifier doesn't support the update context (that is,
491    /// `modifier.context()` returns a flag which doesn't include
492    /// [`ModifierContext::Update`]).
493    #[inline]
494    pub fn update<M>(mut self, modifier: M) -> Self
495    where
496        M: Modifier + Send + Sync,
497    {
498        assert!(modifier.context().contains(ModifierContext::Update));
499        self.update_modifiers.push(Box::new(modifier));
500        self
501    }
502
503    /// Add a [`BoxedModifier`] to the specific context.
504    ///
505    /// # Panics
506    ///
507    /// Panics if the context is [`ModifierContext::Render`]; use
508    /// [`add_render_modifier()`] instead.
509    ///
510    /// Panics if the input `context` contains more than one context (the
511    /// bitfield contains more than 1 bit set) or no context at all (zero bit
512    /// set).
513    ///
514    /// Panics if the modifier doesn't support the context specified (that is,
515    /// `modifier.context()` returns a flag which doesn't include `context`).
516    ///
517    /// [`BoxedModifier`]: crate::BoxedModifier
518    /// [`add_render_modifier()`]: crate::EffectAsset::add_render_modifier
519    pub fn add_modifier(mut self, context: ModifierContext, modifier: Box<dyn Modifier>) -> Self {
520        assert!(context == ModifierContext::Init || context == ModifierContext::Update);
521        assert!(modifier.context().contains(context));
522        if context == ModifierContext::Init {
523            self.init_modifiers.push(modifier);
524        } else {
525            self.update_modifiers.push(modifier);
526        }
527        self
528    }
529
530    /// Add a render modifier to the effect.
531    ///
532    /// # Panics
533    ///
534    /// Panics if the modifier doesn't support the render context (that is,
535    /// `modifier.context()` returns a flag which doesn't include
536    /// [`ModifierContext::Render`]).
537    #[inline]
538    pub fn render<M>(mut self, modifier: M) -> Self
539    where
540        M: RenderModifier + Send + Sync,
541    {
542        assert!(modifier.context().contains(ModifierContext::Render));
543        self.render_modifiers.push(Box::new(modifier));
544        self
545    }
546
547    /// Add a [`RenderModifier`] to the render context.
548    ///
549    /// # Panics
550    ///
551    /// Panics if the modifier doesn't support the render context (that is,
552    /// `modifier.context()` returns a flag which doesn't include
553    /// [`ModifierContext::Render`]).
554    pub fn add_render_modifier(mut self, modifier: Box<dyn RenderModifier>) -> Self {
555        assert!(modifier.context().contains(ModifierContext::Render));
556        self.render_modifiers.push(modifier.boxed_clone());
557        self
558    }
559
560    /// Get a list of all the modifiers of this effect.
561    pub fn modifiers(&self) -> impl Iterator<Item = &dyn Modifier> {
562        self.init_modifiers
563            .iter()
564            .map(|bm| &**bm)
565            .chain(self.update_modifiers.iter().map(|bm| &**bm))
566            .chain(self.render_modifiers.iter().map(|bm| &**bm))
567    }
568
569    /// Get a list of all the init modifiers of this effect.
570    ///
571    /// This is a filtered list of all modifiers, retaining only modifiers
572    /// executing in the [`ModifierContext::Init`] context.
573    ///
574    /// [`ModifierContext::Init`]: crate::ModifierContext::Init
575    pub fn init_modifiers(&self) -> impl Iterator<Item = &dyn Modifier> {
576        self.init_modifiers.iter().map(|bm| &**bm)
577    }
578
579    /// Get a list of all the update modifiers of this effect.
580    ///
581    /// This is a filtered list of all modifiers, retaining only modifiers
582    /// executing in the [`ModifierContext::Update`] context.
583    ///
584    /// [`ModifierContext::Update`]: crate::ModifierContext::Update
585    pub fn update_modifiers(&self) -> impl Iterator<Item = &dyn Modifier> {
586        self.update_modifiers.iter().map(|bm| &**bm)
587    }
588
589    /// Get a list of all the render modifiers of this effect.
590    ///
591    /// This is a filtered list of all modifiers, retaining only modifiers
592    /// executing in the [`ModifierContext::Render`] context.
593    ///
594    /// [`ModifierContext::Render`]: crate::ModifierContext::Render
595    pub fn render_modifiers(&self) -> impl Iterator<Item = &dyn RenderModifier> {
596        self.render_modifiers.iter().filter_map(|m| m.as_render())
597    }
598
599    /// Build the particle layout of the asset based on its modifiers.
600    ///
601    /// This method calculates the particle layout of the effect based on the
602    /// currently existing modifiers, and return it as a newly allocated
603    /// [`ParticleLayout`] object.
604    pub fn particle_layout(&self) -> ParticleLayout {
605        // Build the set of unique attributes required for all modifiers
606        let mut set = HashSet::new();
607        for modifier in self.modifiers() {
608            for &attr in modifier.attributes() {
609                set.insert(attr);
610            }
611        }
612
613        // Add all attributes used by expressions. Those are indirectly used by
614        // modifiers, but may not have been added directly yet.
615        self.module.gather_attributes(&mut set);
616
617        // Build the layout
618        let mut layout = ParticleLayout::new();
619        for attr in set {
620            layout = layout.append(attr);
621        }
622        layout.build()
623    }
624
625    /// Build the property layout of the asset based on its properties.
626    ///
627    /// This method calculates the property layout of the effect based on the
628    /// currently existing properties, and return it as a newly allocated
629    /// [`PropertyLayout`] object.
630    pub fn property_layout(&self) -> PropertyLayout {
631        PropertyLayout::new(self.properties().iter())
632    }
633
634    /// Get the texture layout of the module of this effect.
635    pub fn texture_layout(&self) -> TextureLayout {
636        self.module.texture_layout()
637    }
638
639    /// Sets the mesh that each particle will render.
640    pub fn mesh(mut self, mesh: Handle<Mesh>) -> Self {
641        self.mesh = Some(mesh);
642        self
643    }
644}
645
646/// Asset loader for [`EffectAsset`].
647///
648/// Effet assets take the `.effect` extension.
649#[cfg(feature = "serde")]
650#[derive(Default, TypePath)]
651pub struct EffectAssetLoader;
652
653/// Error for the [`EffectAssetLoader`] loading an [`EffectAsset`].
654#[cfg(feature = "serde")]
655#[derive(Error, Debug)]
656pub enum EffectAssetLoaderError {
657    /// I/O error reading the asset source.
658    #[error("An IO error occurred during loading of a particle effect")]
659    Io(#[from] std::io::Error),
660
661    /// Error during RON format parsing.
662    #[error("A RON format error occurred during loading of a particle effect")]
663    Ron(#[from] ron::error::SpannedError),
664}
665
666#[cfg(feature = "serde")]
667impl AssetLoader for EffectAssetLoader {
668    type Asset = EffectAsset;
669
670    type Settings = ();
671
672    type Error = EffectAssetLoaderError;
673
674    async fn load(
675        &self,
676        reader: &mut dyn Reader,
677        _settings: &Self::Settings,
678        _load_context: &mut LoadContext<'_>,
679    ) -> Result<Self::Asset, Self::Error> {
680        let mut bytes = Vec::new();
681        reader.read_to_end(&mut bytes).await?;
682        let custom_asset = ron::de::from_bytes::<EffectAsset>(&bytes)?;
683        Ok(custom_asset)
684    }
685
686    fn extensions(&self) -> &[&str] {
687        &["effect"]
688    }
689}
690
691/// Component defining the parent effect of the current effect.
692///
693/// This component is optional. When present, on the same entity as the
694/// [`ParticleEffect`], it defines the "parent effect" of that effect. The
695/// particles of the parent effect are accessible from the init pass of this
696/// effect, to allow the particles from the current effect to inherit some
697/// attributes (position, velocity, ...) from the parent particle which
698/// triggered its spawning via GPU spawn events.
699///
700/// Adding this component automatically makes the current particle effect
701/// instance use GPU spawn events emitted by its parent, and automatically makes
702/// the parent effect instance emits such events.
703///
704/// An effect has at most one parent, defined by this component, but a parent
705/// effect can have multiple children. For example, a parent effect can emit GPU
706/// spawn events continuously ([`EventEmitCondition::Always`]) to generate some
707/// kind of trail, and also emit GPU spawn events when its particles die
708/// ([`EventEmitCondition::OnDie`]) for any explosion-like effect.
709///
710/// [`ParticleEffect`]: crate::ParticleEffect
711/// [`EventEmitCondition::Always`]: crate::EventEmitCondition::Always
712/// [`EventEmitCondition::OnDie`]: crate::EventEmitCondition::OnDie
713#[derive(Debug, Clone, Copy, Component, Reflect)]
714pub struct EffectParent {
715    /// Entity of the parent effect.
716    pub entity: Entity,
717}
718
719impl EffectParent {
720    /// Create a new component with the given entity as parent.
721    pub fn new(parent: Entity) -> Self {
722        Self { entity: parent }
723    }
724}
725
726#[derive(Debug, Default, Clone, Copy, PartialEq, Reflect)]
727#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
728pub struct ParticleTrails {
729    pub spawn_period: f32,
730}
731
732#[cfg(test)]
733mod tests {
734    #[cfg(feature = "serde")]
735    use ron::ser::PrettyConfig;
736
737    use super::*;
738    use crate::*;
739
740    #[test]
741    fn add_modifiers() {
742        let mut m = Module::default();
743        let expr = m.lit(3.);
744
745        for modifier_context in [ModifierContext::Init, ModifierContext::Update] {
746            let effect = EffectAsset::default().add_modifier(
747                modifier_context,
748                Box::new(SetAttributeModifier::new(Attribute::POSITION, expr)),
749            );
750            assert_eq!(effect.modifiers().count(), 1);
751            let m = effect.modifiers().next().unwrap();
752            assert!(m.context().contains(modifier_context));
753        }
754
755        {
756            let effect = EffectAsset::default().add_render_modifier(Box::new(SetColorModifier {
757                color: CpuValue::Single(Vec4::ONE),
758                blend: ColorBlendMode::Overwrite,
759                mask: ColorBlendMask::RGBA,
760            }));
761            assert_eq!(effect.modifiers().count(), 1);
762            let m = effect.modifiers().next().unwrap();
763            assert!(m.context().contains(ModifierContext::Render));
764        }
765    }
766
767    #[test]
768    fn test_apply_modifiers() {
769        let mut module = Module::default();
770        let origin = module.lit(Vec3::ZERO);
771        let one = module.lit(1.);
772        let slot_zero = module.lit(0u32);
773        let init_age = SetAttributeModifier::new(Attribute::AGE, one);
774        let init_lifetime = SetAttributeModifier::new(Attribute::LIFETIME, one);
775        let init_pos_sphere = SetPositionSphereModifier {
776            center: module.lit(Vec3::ZERO),
777            radius: module.lit(1.),
778            dimension: ShapeDimension::Volume,
779        };
780        let init_vel_sphere = SetVelocitySphereModifier {
781            center: module.lit(Vec3::ZERO),
782            speed: module.lit(1.),
783        };
784
785        let mut effect = EffectAsset::new(4096, SpawnerSettings::rate(30.0.into()), module)
786            .init(init_pos_sphere)
787            .init(init_vel_sphere)
788            //.update(AccelModifier::default())
789            .update(LinearDragModifier::new(one))
790            .update(ConformToSphereModifier::new(origin, one, one, one, one))
791            .render(ParticleTextureModifier::new(slot_zero))
792            .render(ColorOverLifetimeModifier::default())
793            .render(SizeOverLifetimeModifier::default())
794            .render(OrientModifier::new(OrientMode::ParallelCameraDepthPlane))
795            .render(OrientModifier::new(OrientMode::FaceCameraPosition))
796            .render(OrientModifier::new(OrientMode::AlongVelocity));
797
798        assert_eq!(effect.capacity, 4096);
799
800        let module = &mut effect.module;
801        let property_layout = PropertyLayout::default();
802        let particle_layout = ParticleLayout::default();
803        let mut init_context =
804            ShaderWriter::new(ModifierContext::Init, &property_layout, &particle_layout);
805        assert!(init_pos_sphere.apply(module, &mut init_context).is_ok());
806        assert!(init_vel_sphere.apply(module, &mut init_context).is_ok());
807        assert!(init_age.apply(module, &mut init_context).is_ok());
808        assert!(init_lifetime.apply(module, &mut init_context).is_ok());
809        // assert_eq!(effect., init_context.init_code);
810
811        let accel_mod = AccelModifier::constant(module, Vec3::ONE);
812        let drag_mod = LinearDragModifier::constant(module, 3.5);
813        let property_layout = PropertyLayout::default();
814        let particle_layout = ParticleLayout::default();
815        let mut update_context =
816            ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
817        assert!(accel_mod.apply(module, &mut update_context).is_ok());
818        assert!(drag_mod.apply(module, &mut update_context).is_ok());
819        assert!(ConformToSphereModifier::new(origin, one, one, one, one)
820            .apply(module, &mut update_context)
821            .is_ok());
822        // assert_eq!(effect.update_layout, update_layout);
823
824        let property_layout = PropertyLayout::default();
825        let particle_layout = ParticleLayout::default();
826        let texture_layout = TextureLayout::default();
827        let mut render_context =
828            RenderContext::new(&property_layout, &particle_layout, &texture_layout);
829        ParticleTextureModifier::new(slot_zero)
830            .apply_render(module, &mut render_context)
831            .unwrap();
832        ColorOverLifetimeModifier::default()
833            .apply_render(module, &mut render_context)
834            .unwrap();
835        SizeOverLifetimeModifier::default()
836            .apply_render(module, &mut render_context)
837            .unwrap();
838        OrientModifier::new(OrientMode::ParallelCameraDepthPlane)
839            .apply_render(module, &mut render_context)
840            .unwrap();
841        OrientModifier::new(OrientMode::FaceCameraPosition)
842            .apply_render(module, &mut render_context)
843            .unwrap();
844        OrientModifier::new(OrientMode::AlongVelocity)
845            .apply_render(module, &mut render_context)
846            .unwrap();
847        // assert_eq!(effect.render_layout, render_layout);
848    }
849
850    #[cfg(feature = "serde")]
851    #[test]
852    fn test_serde_ron() {
853        let w = ExprWriter::new();
854
855        let pos = w.lit(Vec3::new(1.2, -3.45, 87.54485));
856        let x = w.lit(BVec2::new(false, true));
857        let _ = x + pos.clone();
858        let mod_pos = SetAttributeModifier::new(Attribute::POSITION, pos.expr());
859
860        let mut module = w.finish();
861        let prop = module.add_property("my_prop", Vec3::new(1.2, -2.3, 55.32).into());
862        let prop = module.prop(prop);
863        let _ = module.abs(prop);
864
865        let effect = EffectAsset {
866            name: "Effect".into(),
867            capacity: 4096,
868            spawner: SpawnerSettings::rate(30.0.into()),
869            module,
870            ..Default::default()
871        }
872        .init(mod_pos);
873
874        let s = ron::ser::to_string_pretty(&effect, PrettyConfig::new().new_line("\n".to_string()))
875            .unwrap();
876        eprintln!("{}", s);
877        assert_eq!(
878            s,
879            r#"(
880    name: "Effect",
881    capacity: 4096,
882    spawner: (
883        count: Single(30.0),
884        spawn_duration: Single(1.0),
885        period: Single(1.0),
886        cycle_count: 0,
887        starts_active: true,
888        emit_on_start: true,
889    ),
890    z_layer_2d: 0.0,
891    simulation_space: Global,
892    simulation_condition: WhenVisible,
893    prng_seed: 0,
894    init_modifiers: [
895        {
896            "SetAttributeModifier": (
897                attribute: "position",
898                value: 1,
899            ),
900        },
901    ],
902    update_modifiers: [],
903    render_modifiers: [],
904    motion_integration: PostUpdate,
905    module: (
906        expressions: [
907            Literal(Vector(Vec3((1.2, -3.45, 87.54485)))),
908            Literal(Vector(BVec2((false, true)))),
909            Binary(
910                op: Add,
911                left: 2,
912                right: 1,
913            ),
914            Property(1),
915            Unary(
916                op: Abs,
917                expr: 4,
918            ),
919        ],
920        properties: [
921            (
922                name: "my_prop",
923                default_value: Vector(Vec3((1.2, -2.3, 55.32))),
924            ),
925        ],
926        texture_layout: (
927            layout: [],
928        ),
929    ),
930    alpha_mode: Blend,
931)"#
932        );
933        let effect_serde: EffectAsset = ron::from_str(&s).unwrap();
934        assert_eq!(effect.name, effect_serde.name);
935        assert_eq!(effect.capacity, effect_serde.capacity);
936        assert_eq!(effect.spawner, effect_serde.spawner);
937        assert_eq!(effect.z_layer_2d, effect_serde.z_layer_2d);
938        assert_eq!(effect.simulation_space, effect_serde.simulation_space);
939        assert_eq!(
940            effect.simulation_condition,
941            effect_serde.simulation_condition
942        );
943        assert_eq!(effect.motion_integration, effect_serde.motion_integration);
944        assert_eq!(effect.module, effect_serde.module);
945        assert_eq!(effect.alpha_mode, effect_serde.alpha_mode);
946        assert_eq!(
947            effect.init_modifiers().count(),
948            effect_serde.init_modifiers().count()
949        );
950        assert_eq!(
951            effect.update_modifiers().count(),
952            effect_serde.update_modifiers().count()
953        );
954        assert_eq!(
955            effect.render_modifiers().count(),
956            effect_serde.render_modifiers().count()
957        );
958    }
959
960    #[test]
961    fn alpha_mode_blend_state() {
962        assert_eq!(BlendState::ALPHA_BLENDING, AlphaMode::Blend.into());
963        assert_eq!(
964            BlendState::PREMULTIPLIED_ALPHA_BLENDING,
965            AlphaMode::Premultiply.into()
966        );
967
968        let blend_state = BlendState {
969            color: BlendComponent {
970                src_factor: BlendFactor::SrcAlpha,
971                dst_factor: BlendFactor::One,
972                operation: BlendOperation::Add,
973            },
974            alpha: BlendComponent {
975                src_factor: BlendFactor::Zero,
976                dst_factor: BlendFactor::One,
977                operation: BlendOperation::Add,
978            },
979        };
980        assert_eq!(blend_state, AlphaMode::Add.into());
981
982        let blend_state = BlendState {
983            color: BlendComponent {
984                src_factor: BlendFactor::Dst,
985                dst_factor: BlendFactor::OneMinusSrcAlpha,
986                operation: BlendOperation::Add,
987            },
988            alpha: BlendComponent::OVER,
989        };
990        assert_eq!(blend_state, AlphaMode::Multiply.into());
991
992        let expr = Module::default().lit(0.5);
993        assert_eq!(BlendState::ALPHA_BLENDING, AlphaMode::Mask(expr).into());
994    }
995
996    // Regression test for #440
997    #[test]
998    fn transitive_attr() {
999        let mut m = Module::default();
1000        let age = m.attr(Attribute::F32_0);
1001        let modifier = SetAttributeModifier::new(Attribute::AGE, age);
1002        let asset = EffectAsset::new(32, SpawnerSettings::once(3.0.into()), m).init(modifier);
1003        let particle_layout = asset.particle_layout();
1004        assert!(particle_layout.contains(Attribute::AGE)); // direct
1005        assert!(particle_layout.contains(Attribute::F32_0)); // transitive
1006    }
1007}