three_d/renderer/geometry/
particles.rs

1use super::BaseMesh;
2use crate::core::*;
3use crate::renderer::*;
4
5///
6/// Used for defining the attributes for each particle in a [ParticleSystem], for example its starting position and velocity.
7///
8/// Each list of attributes must contain the same number of elements as the number of particles.
9///
10#[derive(Clone, Debug, Default)]
11pub struct Particles {
12    /// Initial positions of each particle in world coordinates.
13    pub start_positions: Vec<Vec3>,
14    /// Initial velocities of each particle defined in the world coordinate system.
15    pub start_velocities: Vec<Vec3>,
16    /// The texture transform applied to the uv coordinates of each particle.
17    pub texture_transforms: Option<Vec<Mat3>>,
18    /// A custom color for each particle.
19    pub colors: Option<Vec<Srgba>>,
20}
21
22impl Particles {
23    ///
24    /// Returns an error if the particle data is not valid.
25    ///
26    pub fn validate(&self) -> Result<(), RendererError> {
27        let instance_count = self.count();
28        let buffer_check = |length: Option<usize>, name: &str| -> Result<(), RendererError> {
29            if let Some(length) = length {
30                if length < instance_count as usize {
31                    Err(RendererError::InvalidBufferLength(
32                        name.to_string(),
33                        instance_count as usize,
34                        length,
35                    ))?;
36                }
37            }
38            Ok(())
39        };
40
41        buffer_check(
42            self.texture_transforms.as_ref().map(|b| b.len()),
43            "texture transforms",
44        )?;
45        buffer_check(self.colors.as_ref().map(|b| b.len()), "colors")?;
46        buffer_check(Some(self.start_positions.len()), "start_positions")?;
47        buffer_check(Some(self.start_velocities.len()), "start_velocities")?;
48
49        Ok(())
50    }
51
52    /// Returns the number of particles.
53    pub fn count(&self) -> u32 {
54        self.start_positions.len() as u32
55    }
56}
57
58///
59/// Particle system that can be used to simulate effects such as fireworks, fire, smoke or water particles.
60///
61/// All particles are initialised with [Particles::start_positions] and [Particles::start_velocities] and a global [ParticleSystem::acceleration].
62/// Then, when time passes, their position is updated based on
63///
64/// ```no_rust
65/// new_position = start_position + start_velocity * time + 0.5 * acceleration * time * time
66/// ```
67///
68/// The particles will only move if the [ParticleSystem::animate] is called every frame.
69///
70pub struct ParticleSystem {
71    context: Context,
72    base_mesh: BaseMesh,
73    start_position: InstanceBuffer<Vec3>,
74    start_velocity: InstanceBuffer<Vec3>,
75    tex_transform: Option<(InstanceBuffer<Vec3>, InstanceBuffer<Vec3>)>,
76    instance_color: Option<InstanceBuffer<Vec4>>,
77    /// The acceleration applied to all particles defined in the world coordinate system.
78    pub acceleration: Vec3,
79    instance_count: u32,
80    transformation: Mat4,
81    time: f32,
82}
83
84impl ParticleSystem {
85    ///
86    /// Creates a new particle system with the given geometry and the given attributes for each particle.
87    /// The acceleration is applied to all particles defined in the world coordinate system.
88    ///
89    pub fn new(
90        context: &Context,
91        particles: &Particles,
92        acceleration: Vec3,
93        cpu_mesh: &CpuMesh,
94    ) -> Self {
95        #[cfg(debug_assertions)]
96        cpu_mesh.validate().expect("invalid cpu mesh");
97
98        let mut particles_system = Self {
99            context: context.clone(),
100            base_mesh: BaseMesh::new(context, cpu_mesh),
101            acceleration,
102            instance_count: 0,
103            transformation: Mat4::identity(),
104            time: 0.0,
105            start_position: InstanceBuffer::<Vec3>::new(context),
106            start_velocity: InstanceBuffer::<Vec3>::new(context),
107            tex_transform: None,
108            instance_color: None,
109        };
110        particles_system.set_particles(particles);
111        particles_system
112    }
113
114    ///
115    /// Returns local to world transformation applied to the particle geometry before its position is updated as described in [ParticleSystem].
116    ///
117    pub fn transformation(&self) -> Mat4 {
118        self.transformation
119    }
120
121    ///
122    /// Set the local to world transformation applied to the particle geometry before its position is updated as described in [ParticleSystem].
123    ///
124    pub fn set_transformation(&mut self, transformation: Mat4) {
125        self.transformation = transformation;
126    }
127
128    ///
129    /// Set the particles attributes.
130    ///
131    pub fn set_particles(&mut self, particles: &Particles) {
132        #[cfg(debug_assertions)]
133        particles.validate().expect("invalid particles");
134        self.instance_count = particles.count();
135
136        self.start_position =
137            InstanceBuffer::new_with_data(&self.context, &particles.start_positions);
138        self.start_velocity =
139            InstanceBuffer::new_with_data(&self.context, &particles.start_velocities);
140        self.tex_transform = particles
141            .texture_transforms
142            .as_ref()
143            .map(|texture_transforms| {
144                let mut instance_tex_transform1 = Vec::new();
145                let mut instance_tex_transform2 = Vec::new();
146                for texture_transform in texture_transforms.iter() {
147                    instance_tex_transform1.push(vec3(
148                        texture_transform.x.x,
149                        texture_transform.y.x,
150                        texture_transform.z.x,
151                    ));
152                    instance_tex_transform2.push(vec3(
153                        texture_transform.x.y,
154                        texture_transform.y.y,
155                        texture_transform.z.y,
156                    ));
157                }
158                (
159                    InstanceBuffer::new_with_data(&self.context, &instance_tex_transform1),
160                    InstanceBuffer::new_with_data(&self.context, &instance_tex_transform2),
161                )
162            });
163        self.instance_color = particles.colors.as_ref().map(|instance_colors| {
164            InstanceBuffer::new_with_data(
165                &self.context,
166                &instance_colors
167                    .iter()
168                    .map(|c| c.to_linear_srgb())
169                    .collect::<Vec<_>>(),
170            )
171        });
172    }
173}
174
175impl<'a> IntoIterator for &'a ParticleSystem {
176    type Item = &'a dyn Geometry;
177    type IntoIter = std::iter::Once<&'a dyn Geometry>;
178
179    fn into_iter(self) -> Self::IntoIter {
180        std::iter::once(self)
181    }
182}
183
184impl Geometry for ParticleSystem {
185    fn id(&self) -> GeometryId {
186        GeometryId::ParticleSystem(
187            self.base_mesh.normals.is_some(),
188            self.base_mesh.tangents.is_some(),
189            self.base_mesh.uvs.is_some(),
190            self.base_mesh.colors.is_some(),
191            self.instance_color.is_some(),
192            self.tex_transform.is_some(),
193        )
194    }
195
196    fn vertex_shader_source(&self) -> String {
197        format!(
198            "#define PARTICLES\n{}{}{}",
199            if self.instance_color.is_some() {
200                "#define USE_INSTANCE_COLORS\n"
201            } else {
202                ""
203            },
204            if self.tex_transform.is_some() {
205                "#define USE_INSTANCE_TEXTURE_TRANSFORMATION\n"
206            } else {
207                ""
208            },
209            self.base_mesh.vertex_shader_source()
210        )
211    }
212
213    fn draw(&self, viewer: &dyn Viewer, program: &Program, render_states: RenderStates) {
214        if let Some(inverse) = self.transformation.invert() {
215            program.use_uniform_if_required("normalMatrix", inverse.transpose());
216        } else {
217            // determinant is float zero
218            return;
219        }
220        program.use_uniform("viewProjection", viewer.projection() * viewer.view());
221        program.use_uniform("modelMatrix", self.transformation);
222        program.use_uniform("acceleration", self.acceleration);
223        program.use_uniform("time", self.time);
224
225        program.use_instance_attribute("start_position", &self.start_position);
226        program.use_instance_attribute("start_velocity", &self.start_velocity);
227
228        if program.requires_attribute("tex_transform_row1") {
229            if let Some((row1, row2)) = &self.tex_transform {
230                program.use_instance_attribute("tex_transform_row1", row1);
231                program.use_instance_attribute("tex_transform_row2", row2);
232            }
233        }
234
235        if program.requires_attribute("instance_color") {
236            if let Some(color) = &self.instance_color {
237                program.use_instance_attribute("instance_color", color);
238            }
239        }
240
241        self.base_mesh
242            .draw_instanced(program, render_states, viewer, self.instance_count);
243    }
244
245    fn aabb(&self) -> AxisAlignedBoundingBox {
246        AxisAlignedBoundingBox::INFINITE
247    }
248
249    fn render_with_material(
250        &self,
251        material: &dyn Material,
252        viewer: &dyn Viewer,
253        lights: &[&dyn Light],
254    ) {
255        render_with_material(&self.context, viewer, &self, material, lights)
256    }
257
258    fn render_with_effect(
259        &self,
260        material: &dyn Effect,
261        viewer: &dyn Viewer,
262        lights: &[&dyn Light],
263        color_texture: Option<ColorTexture>,
264        depth_texture: Option<DepthTexture>,
265    ) {
266        render_with_effect(
267            &self.context,
268            viewer,
269            self,
270            material,
271            lights,
272            color_texture,
273            depth_texture,
274        )
275    }
276
277    fn animate(&mut self, time: f32) {
278        self.time = time;
279    }
280}