1use motion_core::{Duration, Random, Time, ValidationError};
43use std::sync::{Arc, OnceLock};
44
45mod batch;
46mod collision;
47mod components;
48mod curves;
49mod events;
50mod extended;
51mod forces;
52mod lazy;
53mod neighbors;
54pub mod presets;
55mod render;
56mod simulation;
57mod sprites;
58mod style;
59#[cfg(test)]
60mod tests;
61mod trajectory;
62
63pub use batch::ParticleBatch;
64pub use collision::{
65 CircleCollider, CollisionResponse, PolygonCollider, SdfCollider, SegmentCollider,
66 SignedDistanceGrid,
67};
68pub use components::{
69 Attractor, BounceBox, BurstEmitter, ColorOverLifetime, DiskEmitter, Drag, Gravity,
70 ImageDensity, ImageEmitter, PathEmitter, PointEmitter, RectEmitter, Seek, SizeOverLifetime,
71 SurfaceEmitter, Vortex, Wind,
72};
73pub use curves::{
74 ColorCurve, ColorField, FieldCoordinate, IntensityOverLifetime, ParticleCurve, Temperature,
75};
76pub use events::{Inherit, ParticleEvent, ParticleEventKind, SubEmitter};
77pub use forces::{
78 Boids, CurlNoise, FlowField, FlowSource, ForceFalloff, ParticleCollisions, RadialForce, Spring,
79};
80pub use simulation::{
81 ParticleSimulation, ParticleSimulationBuilder, SimulationLimits, SimulationStats,
82};
83pub use sprites::{ProceduralSprite, SpriteAtlas, SpriteSheet, SpriteSheetMode};
84pub use style::{
85 BokehShape, ParticleBlend, ParticleShape, ParticleStyle, SpeedStretch, StyleOptions,
86 TrailOptions,
87};
88pub use trajectory::{Ballistic, Converge, NoiseDrift, Orbit, ParticleTrajectory, TrajectoryModel};
89
90use motion_core::instances::MAX_INSTANCE_COUNT;
91
92const MAX_PARTICLE_SIZE: f32 = 4096.0;
94
95const MAX_LIFETIME_SECONDS: f32 = 3600.0;
97
98#[repr(C)]
100#[derive(Clone, Copy, Debug, PartialEq)]
101#[cfg_attr(feature = "gpu", derive(bytemuck::Pod, bytemuck::Zeroable))]
102pub struct Particle {
103 pub position: [f32; 2],
105 pub velocity: [f32; 2],
107 pub color: [f32; 4],
109 pub size: f32,
111 pub rotation: f32,
113 pub age: f32,
115 pub lifetime: f32,
117 pub index: u64,
119 pub custom: [f32; 4],
121}
122
123impl Default for Particle {
124 fn default() -> Self {
125 Self {
126 position: [0.0; 2],
127 velocity: [0.0; 2],
128 color: [1.0; 4],
129 size: 1.0,
130 rotation: 0.0,
131 age: 0.0,
132 lifetime: 1.0,
133 index: 0,
134 custom: [0.0; 4],
135 }
136 }
137}
138
139impl Particle {
140 pub(crate) fn validate(&self) -> Result<(), ValidationError> {
141 let finite = |values: &[f32]| values.iter().all(|value| value.is_finite());
144 if !(finite(&self.position)
145 && finite(&self.velocity)
146 && finite(&self.color)
147 && finite(&self.custom)
148 && finite(&[self.size, self.rotation, self.age, self.lifetime]))
149 || self.size < 0.0
150 || self.size > MAX_PARTICLE_SIZE
151 || self.age < 0.0
152 || self.lifetime <= 0.0
153 || self.lifetime > MAX_LIFETIME_SECONDS
154 || !(0.0..=1.0).contains(&self.color[3])
155 || (self.color[3] == 0.0 && self.color[..3].iter().any(|value| *value != 0.0))
156 {
157 return Err(invalid(
158 "state",
159 "Invalid finite particle geometry, lifetime or premultiplied color",
160 ));
161 }
162 Ok(())
163 }
164}
165
166pub trait ParticleEmitter: Send + Sync {
170 fn accepts_event(&self, _: &ParticleEvent) -> bool {
172 true
173 }
174
175 fn emit(
177 &self,
178 step: u64,
179 time: Time,
180 random: &mut Random,
181 output: &mut Vec<Particle>,
182 ) -> Result<(), ValidationError>;
183}
184
185pub trait ParticleModifier: Send + Sync {
187 fn apply(
189 &self,
190 particle: &mut Particle,
191 step_seconds: f32,
192 time: Time,
193 random: &mut Random,
194 ) -> Result<(), ValidationError>;
195
196 fn resets_color(&self) -> bool {
199 false
200 }
201
202 fn work(&self) -> u64 {
204 1
205 }
206
207 fn apply_event(
209 &self,
210 particle: &mut Particle,
211 step_seconds: f32,
212 time: Time,
213 random: &mut Random,
214 ) -> Result<bool, ValidationError> {
215 self.apply(particle, step_seconds, time, random)?;
216 Ok(false)
217 }
218}
219
220pub trait ParticleGroupModifier: Send + Sync {
222 fn apply(
224 &self,
225 particles: &mut [Particle],
226 step: f32,
227 time: Time,
228 max_work: u64,
229 ) -> Result<u64, ValidationError>;
230
231 fn collisions(&self) -> bool {
233 false
234 }
235}
236
237fn invalid(field: &str, message: &str) -> ValidationError {
238 ValidationError::invalid("particles", field, message)
239}
240
241fn limit(message: &str) -> ValidationError {
242 ValidationError::limit("particles", "budget", message)
243}