par_particle_life/simulation/
mod.rs

1//! Simulation module containing core physics and data structures.
2
3mod boundary;
4mod game_of_life;
5mod particle;
6mod physics;
7mod spatial_hash;
8
9pub use boundary::BoundaryMode;
10pub use game_of_life::GameOfLife;
11pub use particle::{
12    InteractionMatrix, Particle, ParticlePosType, ParticlePosTypeHalf, ParticleVel,
13    ParticleVelHalf, RadiusMatrix,
14};
15pub use physics::{PhysicsEngine, advance_particles, compute_forces_cpu};
16pub use spatial_hash::SpatialHash;
17
18use serde::{Deserialize, Serialize};
19
20/// Configuration for the particle life simulation.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct SimulationConfig {
23    /// Number of particles in the simulation (16 - 1,048,576).
24    pub num_particles: u32,
25
26    /// Number of particle types/species (1 - 16).
27    pub num_types: u32,
28
29    /// Force scaling factor (0.1 - 10.0). Higher values reduce force magnitude.
30    pub force_factor: f32,
31
32    /// Friction coefficient (0.0 - 1.0). Applied each frame to slow particles.
33    pub friction: f32,
34
35    /// Repulsion strength at close range (0.01 - 4.0).
36    pub repel_strength: f32,
37
38    /// Maximum velocity magnitude. Particles are clamped to this speed.
39    pub max_velocity: f32,
40
41    /// Boundary handling mode.
42    pub boundary_mode: BoundaryMode,
43
44    /// Wall repulsion strength for Repel boundary mode (0.0 - 100.0).
45    pub wall_repel_strength: f32,
46
47    /// Number of mirror copies for MirrorWrap mode (5 or 9).
48    pub mirror_wrap_count: u32,
49
50    /// World size in pixels.
51    pub world_size: glam::Vec2,
52
53    /// Enable 3D simulation with depth.
54    pub enable_3d: bool,
55
56    /// Maximum depth for 3D mode.
57    pub depth_limit: f32,
58
59    /// Particle render size in pixels.
60    pub particle_size: f32,
61
62    /// Enable glow effect on particles.
63    pub enable_glow: bool,
64
65    /// Glow effect intensity (0.0 - 2.0).
66    pub glow_intensity: f32,
67
68    /// Glow effect size multiplier.
69    pub glow_size: f32,
70
71    /// Glow falloff steepness (1.0 - 4.0). Higher = sharper edge.
72    pub glow_steepness: f32,
73
74    /// Use spatial hashing for force calculation optimization.
75    pub use_spatial_hash: bool,
76
77    /// Spatial hash cell size. Should be >= max interaction radius.
78    pub spatial_hash_cell_size: f32,
79
80    /// Maximum number of particles in a single bin before force scaling occurs.
81    #[serde(default = "default_max_bin_density")]
82    pub max_bin_density: f32,
83
84    /// Maximum neighbors to check per particle (0 = unlimited).
85    /// Setting a budget prevents slowdown when particles cluster heavily.
86    #[serde(default)]
87    pub neighbor_budget: u32,
88
89    /// Background color [r, g, b] in 0.0-1.0 range.
90    pub background_color: [f32; 3],
91}
92
93/// Default value for max_bin_density (used by serde).
94fn default_max_bin_density() -> f32 {
95    5000.0
96}
97
98impl Default for SimulationConfig {
99    fn default() -> Self {
100        Self {
101            num_particles: 64_000,
102            num_types: 7,
103            force_factor: 1.0,
104            friction: 0.3,
105            repel_strength: 3.0, // Increased to discourage clustering
106            max_velocity: 500.0,
107            boundary_mode: BoundaryMode::Wrap,
108            wall_repel_strength: 100.0,
109            mirror_wrap_count: 5,
110            world_size: glam::Vec2::new(1920.0, 1080.0),
111            enable_3d: false,
112            depth_limit: 420.0,
113            particle_size: 0.5,
114            enable_glow: true,
115            glow_intensity: 0.35,
116            glow_size: 4.0,
117            glow_steepness: 2.0,
118            // Spatial hash enabled for debugging
119            use_spatial_hash: true,
120            spatial_hash_cell_size: 64.0,
121            background_color: [0.0, 0.0, 0.0], // Black
122            max_bin_density: 5000.0,
123            neighbor_budget: 0, // 0 = unlimited (default), set non-zero to cap iterations in dense clusters
124        }
125    }
126}
127
128impl SimulationConfig {
129    /// Create a configuration suitable for GPU rendering.
130    pub fn gpu_defaults() -> Self {
131        Self::default()
132    }
133
134    /// Validate the configuration and return errors if invalid.
135    pub fn validate(&self) -> Result<(), String> {
136        if self.num_particles == 0 {
137            return Err("num_particles must be greater than 0".to_string());
138        }
139        if self.num_types == 0 || self.num_types > 16 {
140            return Err("num_types must be between 1 and 16".to_string());
141        }
142        if self.force_factor <= 0.0 {
143            return Err("force_factor must be positive".to_string());
144        }
145        if !(0.0..=1.0).contains(&self.friction) {
146            return Err("friction must be between 0.0 and 1.0".to_string());
147        }
148        if self.repel_strength < 0.0 {
149            return Err("repel_strength must be non-negative".to_string());
150        }
151        if self.world_size.x <= 0.0 || self.world_size.y <= 0.0 {
152            return Err("world_size must have positive dimensions".to_string());
153        }
154        Ok(())
155    }
156}