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}