Skip to main content

physics/
physics.rs

1use sge::prelude::*;
2
3const BOUNDS_SIZE: Vec2 = Vec2::new(1000.0, 1000.0);
4const BOUNDS_THICKNESS: f32 = 50.0;
5const FORCE_RADIUS: f32 = 250.0;
6const FORCE_STRENGTH: f32 = 100.0;
7
8#[derive(Clone, Copy, PartialEq)]
9enum ShapeType {
10    Circle,
11    Square,
12}
13
14impl ShapeType {
15    fn from_index(i: usize) -> Self {
16        match i % 2 {
17            0 => Self::Circle,
18            _ => Self::Square,
19        }
20    }
21
22    fn bounds(&self) -> Bounds {
23        match self {
24            Self::Circle => Bounds::Circle(15.0),
25            Self::Square => Bounds::Rect(Vec2::splat(30.0)),
26        }
27    }
28
29    fn draw(&self, pos: Vec2, color: Color, rotation: f32) {
30        match self {
31            Self::Circle => draw_circle(pos, 15.0, color),
32            Self::Square => draw_square_rotation(pos - Vec2::splat(15.0), 30.0, color, rotation),
33        }
34    }
35}
36
37fn speed_color(speed: f32) -> Color {
38    Color::from_oklch(
39        0.8,
40        0.1 + (speed / 100.0).clamp(0.0, 0.1),
41        142.94 - (speed / 5.0).clamp(0.0, 142.94 - 26.17),
42    )
43}
44
45#[main("Physics Showcase")]
46fn main() {
47    let mut world = PhysicsWorld::new();
48
49    let wall_rects = [
50        (
51            Vec2::new(BOUNDS_THICKNESS * 0.5, BOUNDS_SIZE.y * 0.5),
52            Vec2::new(BOUNDS_THICKNESS, BOUNDS_SIZE.y),
53        ),
54        (
55            Vec2::new(BOUNDS_SIZE.x * 0.5, BOUNDS_THICKNESS * 0.5),
56            Vec2::new(BOUNDS_SIZE.x, BOUNDS_THICKNESS),
57        ),
58        (
59            Vec2::new(BOUNDS_SIZE.x * 0.5, BOUNDS_SIZE.y - BOUNDS_THICKNESS * 0.5),
60            Vec2::new(BOUNDS_SIZE.x, BOUNDS_THICKNESS),
61        ),
62        (
63            Vec2::new(BOUNDS_SIZE.x - BOUNDS_THICKNESS * 0.5, BOUNDS_SIZE.y * 0.5),
64            Vec2::new(BOUNDS_THICKNESS, BOUNDS_SIZE.y),
65        ),
66    ];
67    for (pos, size) in wall_rects {
68        world.create_fixed(Bounds::Rect(size)).with_position(pos);
69    }
70
71    let ramp_pos = Vec2::new(BOUNDS_SIZE.x * 0.5, BOUNDS_SIZE.y * 0.5 + 200.0);
72    world
73        .create_fixed(Bounds::Triangle(
74            Vec2::new(-120.0, 40.0),
75            Vec2::new(120.0, 40.0),
76            Vec2::new(-120.0, -40.0),
77        ))
78        .with_position(ramp_pos);
79
80    let sensor_pos = Vec2::new(BOUNDS_SIZE.x * 0.75, BOUNDS_SIZE.y * 0.25);
81    let sensor = world
82        .create_fixed_with(Bounds::Circle(80.0), ColliderConfig::default().sensor(true))
83        .with_position(sensor_pos);
84
85    let mut objects: Vec<(ObjectRef, ShapeType)> = Vec::new();
86
87    for i in 0..50 {
88        let pos = Vec2::new(
89            rand::<f32>() * (BOUNDS_SIZE.x - BOUNDS_THICKNESS * 2.0) + BOUNDS_THICKNESS,
90            rand::<f32>() * (BOUNDS_SIZE.y * 0.6) + BOUNDS_THICKNESS,
91        );
92        let velocity = Vec2::new(rand::<f32>() * 500.0 - 250.0, rand::<f32>() * 500.0 - 250.0);
93        let shape_type = ShapeType::from_index(i);
94
95        let collider = world
96            .create_dynamic(shape_type.bounds())
97            .with_ccd()
98            .with_position(pos);
99
100        let mut collider = collider;
101        collider.set_velocity(velocity);
102        objects.push((collider, shape_type));
103    }
104
105    let mut show_colliders = false;
106
107    set_cursor_visible(false);
108
109    loop {
110        world.update();
111        clear_screen(Color::NEUTRAL_900);
112
113        draw_rect(
114            Vec2::ZERO,
115            Vec2::new(BOUNDS_THICKNESS, BOUNDS_SIZE.y),
116            Color::NEUTRAL_800,
117        );
118        draw_rect(
119            Vec2::ZERO,
120            Vec2::new(BOUNDS_SIZE.x, BOUNDS_THICKNESS),
121            Color::NEUTRAL_800,
122        );
123        draw_rect(
124            Vec2::new(0.0, BOUNDS_SIZE.y - BOUNDS_THICKNESS),
125            Vec2::new(BOUNDS_SIZE.x, BOUNDS_THICKNESS),
126            Color::NEUTRAL_800,
127        );
128        draw_rect(
129            Vec2::new(BOUNDS_SIZE.x - BOUNDS_THICKNESS, 0.0),
130            Vec2::new(BOUNDS_THICKNESS, BOUNDS_SIZE.y),
131            Color::NEUTRAL_800,
132        );
133
134        {
135            use ui::prelude::*;
136            let ui = Fit::new(Fill::new(
137                Color::NEUTRAL_800,
138                Padding::all(
139                    50.0,
140                    Col::new([
141                        Text::title_nowrap("Physics Showcase"),
142                        Text::mono(format!("Objects: {}", objects.len())),
143                        Text::mono(format!("FPS: {:.2}", avg_fps())),
144                        Text::h2("Controls"),
145                        Text::body("• Left Click: Spawn object"),
146                        Text::body("• Right Click (hold): Apply force"),
147                        Text::body("• D: Toggle collider debug"),
148                        Text::h2("Shapes"),
149                        Text::body("Circle, Square, Rect, Capsule (Y/X),"),
150                        Text::body("Triangle, Hexagon, Star (compound)"),
151                    ]),
152                ),
153            ));
154            ui::draw_ui(ui, vec2(0.0, BOUNDS_SIZE.y - BOUNDS_THICKNESS));
155        }
156
157        draw_tri(
158            ramp_pos + Vec2::new(-120.0, 40.0),
159            ramp_pos + Vec2::new(120.0, 40.0),
160            ramp_pos + Vec2::new(-120.0, -40.0),
161            Color::NEUTRAL_700,
162        );
163
164        let sensor_active = sensor.is_colliding();
165        let sensor_fill = if sensor_active {
166            Color::CYAN_500.with_alpha(0.25)
167        } else {
168            Color::CYAN_900.with_alpha(0.15)
169        };
170        let sensor_outline = if sensor_active {
171            Color::CYAN_400
172        } else {
173            Color::CYAN_700
174        };
175
176        draw_circle_with_outline(sensor_pos, 80.0, sensor_fill, sensor_outline, 2.5);
177        if sensor_active {
178            draw_circle_outline(sensor_pos, 90.0, Color::CYAN_300.with_alpha(0.4), 1.0);
179        }
180
181        for (collider, shape_type) in &objects {
182            let pos = collider.get_position();
183            let speed = collider.get_velocity().length();
184            let rot = collider.get_rotation();
185            shape_type.draw(pos, speed_color(speed), rot);
186        }
187
188        if show_colliders {
189            world.draw_colliders();
190        }
191
192        if let Some(cursor_pos) = cursor() {
193            draw_circle_with_outline(cursor_pos, 10.0, Color::CYAN_400, Color::WHITE, 3.0);
194
195            if mouse_held(MouseButton::Right) {
196                for (collider, _) in &mut objects {
197                    let pos = collider.get_position();
198                    let to_cursor = cursor_pos - pos;
199                    let distance = to_cursor.length();
200                    if distance < FORCE_RADIUS && distance > 0.0 {
201                        let strength =
202                            (1.0 - distance.powi(2) / FORCE_RADIUS.powi(2)) * FORCE_STRENGTH;
203                        collider.add_velocity(to_cursor.normalize() * strength);
204                    }
205                }
206
207                draw_sdf(
208                    Sdf::circle(cursor_pos, FORCE_RADIUS)
209                        .with_fill(
210                            Color::CYAN_500.with_alpha(0.2),
211                            Color::CYAN_500.with_alpha(0.15),
212                            0.0,
213                            1.0,
214                            SdfFill::RadialGradient,
215                        )
216                        .with_stroke(3.0, Color::WHITE, SdfStroke::Inside),
217                );
218            }
219
220            if mouse_pressed(MouseButton::Left) {
221                let velocity =
222                    Vec2::new(rand::<f32>() * 200.0 - 100.0, rand::<f32>() * 200.0 - 100.0);
223                let shape_type = ShapeType::from_index(objects.len());
224                let mut collider = world.create_dynamic(shape_type.bounds()).with_ccd();
225                collider.set_position(cursor_pos);
226                collider.set_velocity(velocity);
227                objects.push((collider, shape_type));
228            }
229        }
230
231        if key_pressed(KeyCode::KeyD) {
232            show_colliders = !show_colliders;
233        }
234
235        if should_quit() {
236            break;
237        }
238
239        next_frame().await;
240    }
241}