From f23706be40dea1cc455727bdc65689e72962d75f Mon Sep 17 00:00:00 2001 From: Ryan Bottriell Date: Thu, 7 Aug 2025 22:32:30 -0700 Subject: [PATCH] Add ColorByVelocityModifier This modifier sets the color of a particle on a gradient based on its linear velocity at any point in time. Great for having particles disappear as they slow down, for example. Signed-off-by: Ryan Bottriell --- src/modifier/output.rs | 80 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/modifier/output.rs b/src/modifier/output.rs index aeb9d2dc..92ddd3ba 100644 --- a/src/modifier/output.rs +++ b/src/modifier/output.rs @@ -366,6 +366,86 @@ impl RenderModifier for ColorOverLifetimeModifier { } } +/// A modifier modulating each particle's color based on its velocity using a +/// gradient curve. +/// +/// # Attributes +/// +/// This modifier requires the following particle attributes: +/// - [`Attribute::VELOCITY`] +#[derive(Debug, Default, Clone, PartialEq, Reflect, serde::Serialize, serde::Deserialize)] +pub struct ColorByVelocityModifier { + /// The color gradient defining the particle color based on its lifetime. + pub gradient: Gradient, + /// The color blend mode. + pub blend: ColorBlendMode, + /// The blend mask. + pub mask: ColorBlendMask, + /// the velocity range to use for the color gradient + pub velocity_range: std::ops::Range, +} + +impl ColorByVelocityModifier { + /// Create a new modifier from a given gradient. + pub fn new(gradient: Gradient) -> Self { + Self { + gradient, + blend: default(), + mask: default(), + velocity_range: 0.0..1.0, + } + } +} + +impl_mod_render!(ColorByVelocityModifier, &[Attribute::VELOCITY]); + +#[cfg_attr(feature = "serde", typetag::serde)] +impl RenderModifier for ColorByVelocityModifier { + fn apply_render( + &self, + _module: &mut Module, + context: &mut RenderContext, + ) -> Result<(), ExprError> { + let func_name = context.add_color_gradient(self.gradient.clone()); + context.render_extra += &format!( + r#"fn {0}(key: f32) -> vec4 {{ + {1} +}} + +"#, + func_name, + self.gradient.to_shader_code("key") + ); + + let op = self.blend.to_assign_operator(); + let col = format!( + "{func}(clamp(length(particle.{vel}), {min}, {max}) / {range});\n", + func = func_name, + vel = Attribute::VELOCITY.name(), + min = self.velocity_range.start.to_wgsl_string(), + max = self.velocity_range.end.to_wgsl_string(), + range = (self.velocity_range.end - self.velocity_range.start).to_wgsl_string() + ); + let s = if self.mask == ColorBlendMask::RGBA { + format!("color {op} {col};\n") + } else { + let mask = self.mask.to_components(); + format!("color.{mask} {op} ({col}).{mask};\n") + }; + + context.vertex_code += &s; + Ok(()) + } + + fn boxed_render_clone(&self) -> Box { + Box::new(self.clone()) + } + + fn as_modifier(&self) -> &dyn Modifier { + self + } +} + /// A modifier to set the size of all particles. /// /// This modifier assigns a _single_ size to all particles. That size can be