sph_plugin/
lib.rs

1use bevy::prelude::*;
2use bevy::time::common_conditions::on_timer;
3use std::time::Duration;
4
5mod components;
6mod math;
7mod resources;
8mod systems;
9
10pub use components::*;
11pub use math::*;
12pub use resources::*;
13pub use systems::*;
14
15use std::f32::consts::PI;
16
17/// Physics timestep configuration
18#[derive(Resource, Clone, Debug, Copy)]
19pub struct TimeConfig {
20    /// Physics update rate in Hz
21    pub physics_rate: f32,
22    /// Derived fixed timestep in seconds
23    pub fixed_timestep: f32,
24}
25
26impl Default for TimeConfig {
27    fn default() -> Self {
28        Self {
29            physics_rate: 240.0,
30            fixed_timestep: 1.0 / 240.0,
31        }
32    }
33}
34
35/// Wind tunnel and domain configuration
36#[derive(Resource, Clone, Debug, Copy)]
37pub struct DomainConfig {
38    /// Size of the simulation domain [mm]
39    pub box_size: Vec2,
40    /// Free stream velocity [m/s]
41    pub freestream_velocity: Vec2,
42    /// Gravity vector [m/s²]
43    pub gravity: Vec2,
44}
45
46impl Default for DomainConfig {
47    fn default() -> Self {
48        Self {
49            box_size: Vec2::new(200.0, 200.0),
50            freestream_velocity: Vec2::new(50.0, 0.0),
51            gravity: Vec2::new(0.0, 0.0),
52        }
53    }
54}
55
56/// Particle-specific configuration
57#[derive(Resource, Clone, Debug, Copy)]
58pub struct ParticleConfig {
59    /// Particle radius [mm]
60    pub radius: f32,
61    /// Initial number of particles
62    pub initial_count: usize,
63    /// Number of particles to spawn per update
64    pub spawn_rate: usize,
65    /// Grid cell size for spatial partitioning
66    pub grid_cell_size: f32,
67    /// Particle mass [kg]
68    pub mass: f32,
69}
70
71impl ParticleConfig {
72    pub fn new(radius: f32, initial_count: usize, spawn_rate: usize, rest_density: f32) -> Self {
73        Self {
74            radius,
75            initial_count,
76            spawn_rate,
77            grid_cell_size: radius * 4.0,
78            mass: rest_density * (4.0 / 3.0) * PI * radius.powi(3),
79        }
80    }
81}
82
83impl Default for ParticleConfig {
84    fn default() -> Self {
85        Self::new(0.5, 600, 50, 1.225)
86    }
87}
88
89/// SPH fluid properties configuration
90#[derive(Resource, Clone, Debug, Copy)]
91pub struct FluidConfig {
92    /// Rest density of the fluid [kg/m³]
93    pub rest_density: f32,
94    /// Gas constant for the fluid [J/(kg·K)]
95    pub gas_constant: f32,
96    /// Smoothing length relative to particle radius
97    pub smoothing_length: f32,
98    /// Dynamic viscosity coefficient [Pa·s]
99    pub viscosity: f32,
100}
101
102impl FluidConfig {
103    pub fn new(particle_radius: f32) -> Self {
104        Self {
105            rest_density: 1.225,
106            gas_constant: 287.05,
107            smoothing_length: particle_radius * 16.0,
108            viscosity: 1.81e-5,
109        }
110    }
111}
112
113impl Default for FluidConfig {
114    fn default() -> Self {
115        Self::new(0.5)
116    }
117}
118
119/// Visualization and rendering configuration
120#[derive(Resource, Clone, Debug, Copy)]
121pub struct RenderConfig {
122    /// Scale factor for rendering
123    pub render_scale: f32,
124    /// Camera zoom limits
125    pub min_zoom: f32,
126    pub max_zoom: f32,
127    /// Camera pan speed
128    pub pan_speed: f32,
129}
130
131impl Default for RenderConfig {
132    fn default() -> Self {
133        Self {
134            render_scale: 4.0,
135            min_zoom: 0.1,
136            max_zoom: 10.0,
137            pan_speed: 150.0,
138        }
139    }
140}
141
142/// Airfoil configuration
143#[derive(Resource, Clone, Debug, Copy)]
144pub struct AirfoilConfig {
145    /// Angle of attack [degrees]
146    pub aoa: f32,
147    /// Chord length [mm]
148    pub chord_length: f32,
149    /// Maximum thickness ratio
150    pub thickness_ratio: f32,
151    /// Position in the domain
152    pub position: Vec2,
153}
154
155impl Default for AirfoilConfig {
156    fn default() -> Self {
157        Self {
158            aoa: 5.0,
159            chord_length: 50.0,
160            thickness_ratio: 0.12,
161            position: Vec2::ZERO,
162        }
163    }
164}
165
166/// Combined simulation configuration
167#[derive(Resource, Clone, Debug, Copy)]
168pub struct SPHConfig {
169    pub time: TimeConfig,
170    pub domain: DomainConfig,
171    pub particle: ParticleConfig,
172    pub fluid: FluidConfig,
173    pub render: RenderConfig,
174    pub airfoil: AirfoilConfig,
175}
176
177impl Default for SPHConfig {
178    fn default() -> Self {
179        Self {
180            time: TimeConfig::default(),
181            domain: DomainConfig::default(),
182            particle: ParticleConfig::default(),
183            fluid: FluidConfig::default(),
184            render: RenderConfig::default(),
185            airfoil: AirfoilConfig::default(),
186        }
187    }
188}
189
190pub struct SPHPlugin {
191    pub config: SPHConfig,
192}
193
194impl Default for SPHPlugin {
195    fn default() -> Self {
196        Self {
197            config: SPHConfig::default(),
198        }
199    }
200}
201
202impl Plugin for SPHPlugin {
203    fn build(&self, app: &mut App) {
204        // Insert configuration and derived constants
205        app.insert_resource(self.config)
206            .insert_resource(ParticlePool::new(self.config.particle.initial_count))
207            .insert_resource(SpatialGrid::new(
208                self.config.particle.grid_cell_size,
209                self.config.particle.radius,
210            ))
211            .init_resource::<SimulationStats>()
212            // Setup systems
213            .add_systems(Startup, setup)
214            // Fixed update systems
215            .add_systems(
216                FixedUpdate,
217                (
218                    update_spatial_grid,
219                    compute_density_pressure,
220                    apply_sph_forces,
221                )
222                    .chain()
223                    .run_if(on_timer(Duration::from_secs_f32(
224                        self.config.time.fixed_timestep,
225                    ))),
226            )
227            .add_systems(Update, (spawn_particles, print_fps, pan_camera))
228            .init_resource::<VisualizationMode>()
229            .add_systems(
230                Update,
231                (handle_visualization_toggle, update_particle_colors),
232            );
233    }
234}
235
236// Re-export everything needed for the public API
237pub mod prelude {
238    pub use crate::{components::*, math::*, resources::*, SPHConfig, SPHPlugin};
239}