From 70afc5a7d871919776a64782e8b93404e6b0defd Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 15 Dec 2024 13:17:43 +0100 Subject: feat!: add raylib rendering --- playground/src/main.rs | 185 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 playground/src/main.rs (limited to 'playground/src/main.rs') diff --git a/playground/src/main.rs b/playground/src/main.rs new file mode 100644 index 0000000..1cb8198 --- /dev/null +++ b/playground/src/main.rs @@ -0,0 +1,185 @@ +use raylib::prelude::*; + +use physics::force::{drag::Drag, gravity::Gravity, spring::Spring}; +use physics::nalgebra::Point as PointBase; +use physics::particle_system::{Particle, ParticleSystem}; +use physics::algebra::{Point, Scalar, Vector, N}; +use physics::renderer::Camera; +use physics::solver::Solver; + +const SCALE: i32 = 5; +fn screen_space_to_raylib(p: PointBase, d: &RaylibDrawHandle) -> PointBase { + PointBase::::new( + d.get_screen_width() / 2 + p.x as i32 * SCALE, + d.get_screen_height() / 2 - p.y as i32 * SCALE, + ) +} +fn raylib_to_screen_space(p: PointBase, d: &RaylibDrawHandle) -> PointBase { + PointBase::::new( + ((p.x - d.get_screen_width() / 2) / SCALE) as Scalar, + ((p.y - d.get_screen_height() / 2) / -SCALE) as Scalar, + ) +} + +fn main() { + let dt = 0.0001; + let mut system = ParticleSystem { + particles: vec![ + Particle::new(Point::origin(), 4.0), + Particle::new(Point::new(-30.0, 0.0), 15.0), + Particle::new(Point::new(20.0, 0.0), 30.0), + Particle::new(Point::new(5.0, 20.0), 50.0), + Particle::new(Point::origin(), 25.0), + Particle::new(Point::new(50.0, 0.0), 10.0), + Particle::new(Point::new(-100.0, -100.0), 100.0), + ], + constraints: vec![], + forces: vec![ + Box::new(Gravity { + vector: Vector::y() * -9.8, + }), + Box::new(Drag { coefficient: 0.2 }), + Box::new(Spring { + particle_ids: [4, 2], + spring_constant: 0.75, + damping_constant: 0.1, + rest_length: 20.0, + }), + ], + t: 0.0, + }; + + system.add_anchor_constraint(0); + system.add_beam_constraint([0, 2]); + system.add_beam_constraint([1, 2]); + system.add_beam_constraint([1, 3]); + system.add_beam_constraint([2, 3]); + system.add_slider_constraint(5, Vector::x()); + system.add_beam_constraint([5, 4]); + + let mut selected_particle_id = None; + + let (mut rl, thread) = raylib::init() + .size(640, 480) + .title("Physics simulation") + .resizable() + .build(); + + while !rl.window_should_close() { + let mut d = rl.begin_drawing(&thread); + + d.clear_background(Color::WHITE); + d.draw_text( + format!( + "Time: {:03.3}\n\nFPS: {}\n\nKinetic energy: {:03.0}", + system.t, + d.get_fps(), + system.get_kinetic_energy() + ) + .as_str(), + 12, + 12, + 20, + Color::BLACK, + ); + + let camera = Camera::new(Point::origin(), Vector::y(), Vector::x()); + + match selected_particle_id { + Some(particle_id) => { + // let p: &Particle = &system.particles[particle_id]; + + let mouse_point = PointBase::::new(d.get_mouse_x(), d.get_mouse_y()); + let screen_space = raylib_to_screen_space(mouse_point, &d); + let world_mouse = camera.screen_space_to_world(screen_space); + + let mouse_particle_id = system.particles.len() - 1; + system.particles[mouse_particle_id].position = world_mouse; + + system.forces.push(Box::new(Spring { + particle_ids: [mouse_particle_id, particle_id], + spring_constant: 0.99, + damping_constant: 0.99, + rest_length: 1.0, + })); + } + None => (), + } + + for _ in 0..10 { + system.apply_forces(); + system.enforce_constraints(dt); + system.step(dt); + } + + match selected_particle_id { + Some(_) => { + system.forces.pop(); + } + None => (), + } + + if d.is_mouse_button_released(MouseButton::MOUSE_BUTTON_LEFT) { + selected_particle_id = None + } + + for (particle_id, particle) in system.particles.iter().enumerate() { + let position = + screen_space_to_raylib(camera.world_to_screen_space(particle.position), &d); + let radius = particle.mass.powf(1.0 / (N as f64)) as f32 * SCALE as f32; + + if d.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT) + && selected_particle_id.is_none() + { + let mouse_point = + PointBase::::new(d.get_mouse_x() as f32, d.get_mouse_y() as f32); + let position = PointBase::::new(position.x as f32, position.y as f32); + if (position - mouse_point).norm() < radius { + selected_particle_id = Some(particle_id) + } + } + + d.draw_circle( + position.x, + position.y, + radius, + if selected_particle_id.is_some_and(|v| v == particle_id) { + Color::RED + } else { + Color::BLACK + }, + ); + } + for c in &system.constraints { + let particle_ids = c.get_particles(); + if particle_ids.len() == 2 { + let a = screen_space_to_raylib(system.particles[particle_ids[0]].position, &d); + let b = screen_space_to_raylib(system.particles[particle_ids[1]].position, &d); + + d.draw_line(a.x, a.y, b.x, b.y, Color::GRAY); + } + } + + { + // Hard-coded spring + let particle_ids = [4, 2]; + let a = screen_space_to_raylib(system.particles[particle_ids[0]].position, &d); + let b = screen_space_to_raylib(system.particles[particle_ids[1]].position, &d); + d.draw_line(a.x, a.y, b.x, b.y, Color::GREEN); + } + { + // Hard-coded spring + match selected_particle_id { + Some(id) => { + let mouse_particle_id = system.particles.len() - 1; + let a = screen_space_to_raylib(system.particles[id].position, &d); + let b = + screen_space_to_raylib(system.particles[mouse_particle_id].position, &d); + + d.draw_line(a.x, a.y, b.x, b.y, Color::GREEN); + } + None => (), + } + } + } +} -- cgit v1.2.3