spring_test/
main.rs

1mod physics_objects;
2
3use pg_sdl::prelude::*;
4use pg_sdl::vector2::Vec2;
5use pg_sdl::widgets::Widgets;
6use physics_objects::{apply_gravity, Mass, Motor, Rod, Spring};
7use std::collections::HashMap;
8
9/// PhysicsApp is a pyhsics engine app made to test any kind of 2D physics.
10pub struct PhysicsApp {
11    masses: Vec<Mass>,
12    rods: Vec<Rod>,
13    springs: Vec<Spring>,
14    motors: Vec<Motor>,
15}
16
17impl App for PhysicsApp {
18    fn update(&mut self, delta: f32, input: &Input, widgets: &mut Widgets) -> bool {
19        if widgets
20            .get_mut::<Button>("play")
21            .unwrap()
22            .state
23            .is_pressed()
24        {
25            if widgets.get::<Slider>("speed").unwrap().get_value() == 0.0 {
26                widgets.get_mut::<Slider>("speed").unwrap().set_value(1.0);
27                widgets
28                    .get_mut::<Button>("play")
29                    .unwrap()
30                    .set_text("Stop".to_string());
31            } else {
32                widgets.get_mut::<Slider>("speed").unwrap().set_value(0.0);
33                widgets
34                    .get_mut::<Button>("play")
35                    .unwrap()
36                    .set_text("Start".to_string());
37            }
38        }
39
40        if input.mouse.left_button.is_pressed() {
41            let mouse_position = Vec2::from(input.mouse.position);
42            self.masses.iter().enumerate().for_each(|(index, mass)| {
43                // TODO replace by take_while()
44                if mass.collide_point(mouse_position) {
45                    self.springs[0].change_end(index);
46                }
47            });
48        } else if input.mouse.left_button.is_released() {
49            self.springs[0].change_end(0);
50        }
51
52        apply_gravity(&mut self.masses);
53        self.springs.iter_mut().for_each(|spring| {
54            spring.apply_force(&mut self.masses);
55        });
56
57        let delta = delta * widgets.get_mut::<Slider>("speed").unwrap().get_value() * 10.0;
58
59        self.masses.iter_mut().for_each(|mass| mass.update(delta));
60        self.motors
61            .iter()
62            .for_each(|motor| motor.update(delta, &mut self.masses));
63
64        self.masses[0].position = Vec2::from(input.mouse.position);
65
66        // set mass 3 for constraining rod to its initial length
67        let delta_length = self.masses[3].position - self.masses[4].position;
68        self.masses[3].position =
69            self.masses[4].position + delta_length.normalized() * self.rods[0].length;
70
71        let delta_velocity = self.masses[3].velocity - self.masses[4].velocity;
72        let v1 = delta_velocity.dot(delta_length.perpendicular());
73        self.masses[3].velocity = self.masses[4].velocity + delta_length.perpendicular() * v1;
74
75        true
76    }
77
78    fn draw(&mut self, canvas: &mut Canvas<Window>, _text_drawer_: &mut TextDrawer) {
79        self.motors
80            .iter()
81            .for_each(|motor| motor.draw(canvas, &self.masses));
82        self.masses.iter().for_each(|mass| mass.draw(canvas));
83        self.rods
84            .iter()
85            .for_each(|rod| rod.draw(canvas, &self.masses));
86        self.springs
87            .iter()
88            .for_each(|spring| spring.draw(canvas, &self.masses));
89    }
90}
91
92fn main() {
93    let my_app = &mut PhysicsApp {
94        masses: Vec::from([
95            Mass::new(Vec2::new(0.0, 0.0), 0.0, 0.0, Colors::BLACK, true),
96            Mass::new(Vec2::new(600.0, 400.0), 1.0, 20.0, Colors::ORANGE, true),
97            Mass::new(Vec2::new(600.0, 450.0), 1.0, 15.0, Colors::ORANGE, true),
98            Mass::new(Vec2::new(600.0, 550.0), 5.0, 25.0, Colors::RED, false),
99            Mass::new(Vec2::new(800.0, 200.0), 1.0, 20.0, Colors::GREEN, true),
100            Mass::new(Vec2::new(800.0, 650.0), 1.0, 20.0, Colors::MAGENTA, false),
101        ]),
102        rods: Vec::from([
103            Rod::new(3, 4, 250.0, 10.0, Colors::BROWN),
104            Rod::new(3, 5, 50.0, 10.0, Colors::BROWN),
105        ]),
106        springs: Vec::from([
107            Spring::new(0, 0, 1.0, 0.5, 0.0, 20.0, Colors::WHITE),
108            Spring::new(2, 3, 0.5, 0.2, 150.0, 40.0, Colors::BEIGE),
109        ]),
110        motors: Vec::from([Motor::new(1, 2, 0.4, Colors::LIGHT_GREY)]),
111    };
112
113    let mut app: PgSdl = PgSdl::init("Spring test", 1200, 720, Some(60), true, Colors::SKY_BLUE);
114    app.add_widgets(HashMap::from([
115        (
116            "play",
117            Box::new(Button::new(
118                Colors::ORANGE,
119                rect!(300, 35, 120, 50),
120                Some(9),
121                Some(Text::new("Start".to_string(), 18, None)),
122            )) as Box<dyn Widget>,
123        ),
124        (
125            "speed",
126            Box::new(Slider::new(
127                Colors::ORANGE,
128                rect!(500, 50, 200, 30),
129                Some(20),
130                SliderType::Continuous {
131                    default_value: 0.0,
132                    display: Some(Box::new(|value| format!("{:.2}", value))),
133                },
134            )),
135        ),
136    ]));
137    app.run(my_app);
138}