diff options
| author | eug-vs <eugene@eug-vs.xyz> | 2024-12-09 06:38:12 +0100 | 
|---|---|---|
| committer | eug-vs <eugene@eug-vs.xyz> | 2024-12-09 06:38:12 +0100 | 
| commit | ac0e2abb003300bd4d414ae9ee5e97c36c0663a2 (patch) | |
| tree | 997cec9567ed6af44594b3d3d383ca0add848008 /src/main.rs | |
| download | particle-physics-ac0e2abb003300bd4d414ae9ee5e97c36c0663a2.tar.gz | |
feat: create basic particle-spring system
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 167 | 
1 files changed, 167 insertions, 0 deletions
| diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ab2e15f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,167 @@ +use std::io::Write; +use std::{fs::File, path::PathBuf}; +use cgmath::{Angle, ElementWise, InnerSpace, Matrix3, MetricSpace, Rad, Vector3, Zero}; + +type Vector = Vector3<f32>; + +#[derive(Debug, Clone, Copy)] +struct Particle { +    position: Vector, +    position_old: Vector, +    // velocity: Vector, +    acceleration: Vector, +    mass: f32, +    fixed: bool, +} + +impl Particle { +    pub fn new(position: Vector, mass: f32, fixed: bool) -> Self { +        Self { +            position, +            position_old: position, +            acceleration: Vector::zero(), +            mass, +            fixed, +        } + +    } + +    fn apply_force(&mut self, force: Vector) { +        self.acceleration += force / self.mass; +    } + +    fn tick(&mut self, dt: f32) { +        if !self.fixed { +            // Verlet method +            let velocity = self.position - self.position_old; +            self.position_old = self.position; +            self.position += velocity + self.acceleration * dt * dt; +        } + +        self.acceleration = Vector::zero(); +    } +} + +#[derive(Debug, Clone, Copy)] +struct Spring { +    rest_length: f32, +    stiffness: f32, +    particle_ids: [usize; 2], +} + +#[derive(Debug, Clone)] +struct Simulation { +    particles: Vec<Particle>, +    springs: Vec<Spring>, +} + +impl Simulation { +    fn tick(&mut self, dt: f32) { +        for spring in self.springs.iter() { +            let [a, b] = spring.particle_ids; +            let dist_vec = self.particles[b].position - self.particles[a].position; +            let force_magnitude = spring.stiffness * (dist_vec.magnitude() - spring.rest_length); +            let normalized = dist_vec.normalize(); + +            self.particles[a].apply_force(normalized.mul_element_wise(force_magnitude)); +            self.particles[b].apply_force(normalized.mul_element_wise(force_magnitude * -1.0)); +        } +        for p in &mut self.particles { +            p.tick(dt); +        } +    } +} + + +struct PPM { +    prefix: PathBuf, +    width: usize, +    height: usize, +    // pixels_per_unit: usize, +} + +impl PPM { +    fn render_particles(&self, particles: &Vec<Particle>) -> String { +        let mut s = format!("P3\n{} {}\n255\n", self.width, self.height); +        let white = "255 255 255 "; +        let black = "0 0 0 "; + +        for pixel_row in 0..self.height { +            for pixel_col in 0..self.width { +                let point = Vector::new(pixel_col as f32, 0.0, (pixel_row as f32) * -1.0) + Vector::new(self.width as f32 / -2.0, 0.0, self.height as f32 / 2.0); +                let color = match particles.iter().any(|p| p.position.distance(point) <= p.mass / 2.0) { +                    true => black, +                    false => white, +                }; +                s += color; +            } +        } +        s +    } + +    fn save_frame(&self, particles: &Vec<Particle>, time: f32) { +        let file_name = format!("frame-{:08.3}", time); +        let path = self.prefix.join(file_name); +        let mut file = File::create(path).unwrap(); +        let data = self.render_particles(particles); +        file.write(data.as_bytes()).unwrap(); +    } +} + + +fn main() { +    let rotation_matrix = Matrix3::from_axis_angle(Vector::unit_y(), -Rad::full_turn() / 12.0); + +    let mut simulation = Simulation { +        particles: vec![ +            Particle::new(Vector::zero(), 5.0, true), +            Particle::new(rotation_matrix * Vector::unit_x() * 40.0, 30.0, false), +            Particle::new(rotation_matrix * Vector::unit_x() * 40.0 + rotation_matrix * rotation_matrix * Vector::unit_x() * 50.0, 15.0, false) +        ], +        springs: vec![ +            Spring { +                particle_ids: [0, 1], +                rest_length: 40.0, +                stiffness: 400.0, +            }, +            Spring { +                particle_ids: [1, 2], +                rest_length: 50.0, +                stiffness: 5.0, +            }, +        ], +    }; + + +    let ppm = PPM { +        prefix: PathBuf::from("./out"), +        width: 150, +        height: 800, +    }; + +    let gravity = Vector::new(0.0, 0.0, -9.8); + +    let time_step = 0.01; +    let mut tick = 1; +    let mut time = 0.0; +    while time < 130.0 { +        { +            let p = &mut simulation.particles[0]; +            p.apply_force(gravity * -p.mass); +        } +        let mut all_particles = Vec::new(); +        for p in &mut simulation.particles { +            p.apply_force(gravity * p.mass); +        } +        simulation.tick(time_step); +        all_particles.append(&mut simulation.particles.clone()); + +        if tick % 10 == 0 { +            ppm.save_frame(&all_particles, time); +        } + + +        time += time_step; +        tick +=1; +    } +} | 
