1use motion_canvas_rs::prelude::*;
2use std::time::Duration;
3
4const BG: Color = Color::rgb8(0x0f, 0x0f, 0x14);
6const FLOOR_COLOR: Color = Color::rgb8(0x2a, 0x2d, 0x3a);
7const ACCENT_RED: Color = Color::rgb8(0xe1, 0x32, 0x38);
8const ACCENT_BLUE: Color = Color::rgb8(0x68, 0xab, 0xdf);
9const ACCENT_YELLOW: Color = Color::rgb8(0xe6, 0xa7, 0x00);
10const ACCENT_TEAL: Color = Color::rgb8(0x20, 0xb2, 0xaa);
11const ACCENT_PURPLE: Color = Color::rgb8(0x9b, 0x59, 0xb6);
12const ACCENT_EMERALD: Color = Color::rgb8(0x25, 0xc2, 0x81);
13const TEXT_DIM: Color = Color::rgb8(0x88, 0x88, 0x99);
14const TEXT_BRIGHT: Color = Color::rgb8(0xe8, 0xe8, 0xf0);
15const WALL_COLOR: Color = Color::rgb8(0x3a, 0x3d, 0x4a);
16
17const W: u32 = 960;
18const H: u32 = 540;
19const CX: f32 = W as f32 / 2.0;
20const FADE: Duration = Duration::from_millis(300);
21
22fn make_floor(y: f32, width: f32, color: Color) -> StaticBodyNode {
25 StaticBodyNode::new(Box::new(
26 Rect::default()
27 .with_size(Vec2::new(width, 30.0))
28 .with_fill(color)
29 .with_radius(4.0),
30 ))
31 .with_position(Vec2::new(CX, y))
32 .with_shape(PhysicsShape::Cuboid(Vec2::new(width / 2.0, 15.0)))
33}
34
35fn make_wall(x: f32, y: f32, w: f32, h: f32) -> StaticBodyNode {
36 StaticBodyNode::new(Box::new(
37 Rect::default()
38 .with_size(Vec2::new(w, h))
39 .with_fill(WALL_COLOR)
40 .with_radius(2.0),
41 ))
42 .with_position(Vec2::new(x, y))
43 .with_shape(PhysicsShape::Cuboid(Vec2::new(w / 2.0, h / 2.0)))
44}
45
46fn make_title(text: &str) -> TextNode {
47 TextNode::default()
48 .with_text(text)
49 .with_position(Vec2::new(CX, 40.0))
50 .with_anchor(Vec2::ZERO)
51 .with_font_size(32.0)
52 .with_fill(TEXT_BRIGHT)
53 .with_font("JetBrains Mono")
54 .with_opacity(0.0)
55}
56
57fn make_subtitle(text: &str) -> TextNode {
58 TextNode::default()
59 .with_text(text)
60 .with_position(Vec2::new(CX, 75.0))
61 .with_anchor(Vec2::ZERO)
62 .with_font_size(16.0)
63 .with_fill(TEXT_DIM)
64 .with_font("JetBrains Mono")
65 .with_opacity(0.0)
66}
67
68fn make_label(text: &str, x: f32, y: f32, color: Color) -> TextNode {
69 TextNode::default()
70 .with_text(text)
71 .with_position(Vec2::new(x, y))
72 .with_anchor(Vec2::ZERO)
73 .with_font_size(14.0)
74 .with_fill(color)
75 .with_font("JetBrains Mono")
76 .with_opacity(0.0)
77}
78
79fn build_scene1() -> (PhysicsNode, TextNode, TextNode) {
84 let title = make_title("Gravity");
85 let subtitle = make_subtitle("A single box falls under gravity");
86
87 let mut p = PhysicsNode::new()
88 .with_timestep(1.0 / 60.0)
89 .with_opacity(0.0);
90
91 p.add_static(make_floor(460.0, 600.0, FLOOR_COLOR));
92 p.add_dynamic(
93 RigidBodyNode::new(Box::new(
94 Rect::default()
95 .with_size(Vec2::new(60.0, 60.0))
96 .with_fill(ACCENT_RED)
97 .with_radius(6.0),
98 ))
99 .with_position(Vec2::new(CX, 120.0))
100 .with_shape(PhysicsShape::Cuboid(Vec2::new(30.0, 30.0)))
101 .with_bounciness(0.3),
102 );
103
104 (p, title, subtitle)
105}
106
107fn build_scene2() -> (PhysicsNode, TextNode, TextNode, Vec<TextNode>) {
108 let title = make_title("Bounciness");
109 let subtitle = make_subtitle("Same ball, three values: 0.0 · 0.5 · 0.95");
110
111 let mut p = PhysicsNode::new().with_opacity(0.0);
112
113 p.add_static(make_floor(460.0, 800.0, FLOOR_COLOR));
114
115 let configs: [(f32, f32, Color, &str); 3] = [
116 (CX - 200.0, 0.0, ACCENT_RED, "0.0 (dead)"),
117 (CX, 0.5, ACCENT_YELLOW, "0.5 (rubber)"),
118 (CX + 200.0, 0.95, ACCENT_EMERALD, "0.95 (super ball)"),
119 ];
120
121 let mut labels = Vec::new();
122 for (x, bounce, color, label_text) in &configs {
123 p.add_dynamic(
124 RigidBodyNode::new(Box::new(
125 Circle::default().with_radius(25.0).with_fill(*color),
126 ))
127 .with_position(Vec2::new(*x, 100.0))
128 .with_shape(PhysicsShape::Ball(25.0))
129 .with_bounciness(*bounce),
130 );
131 labels.push(make_label(label_text, *x, 500.0, *color));
132 }
133
134 (p, title, subtitle, labels)
135}
136
137fn build_scene3() -> (PhysicsNode, TextNode, TextNode, Vec<TextNode>) {
138 let title = make_title("Shapes");
139 let subtitle = make_subtitle("Circles roll, rectangles tumble — shape matters");
140
141 let mut p = PhysicsNode::new().with_opacity(0.0);
142
143 p.add_static(make_floor(460.0, 800.0, FLOOR_COLOR));
144
145 p.add_static(
146 StaticBodyNode::new(Box::new(
147 Rect::default()
148 .with_size(Vec2::new(300.0, 16.0))
149 .with_fill(WALL_COLOR)
150 .with_radius(2.0),
151 ))
152 .with_position(Vec2::new(300.0, 320.0))
153 .with_rotation(0.25)
154 .with_shape(PhysicsShape::Cuboid(Vec2::new(150.0, 8.0))),
155 );
156 p.add_static(
157 StaticBodyNode::new(Box::new(
158 Rect::default()
159 .with_size(Vec2::new(300.0, 16.0))
160 .with_fill(WALL_COLOR)
161 .with_radius(2.0),
162 ))
163 .with_position(Vec2::new(660.0, 320.0))
164 .with_rotation(0.25)
165 .with_shape(PhysicsShape::Cuboid(Vec2::new(150.0, 8.0))),
166 );
167
168 p.add_dynamic(
169 RigidBodyNode::new(Box::new(
170 Circle::default().with_radius(25.0).with_fill(ACCENT_BLUE),
171 ))
172 .with_position(Vec2::new(220.0, 150.0))
173 .with_shape(PhysicsShape::Ball(25.0))
174 .with_bounciness(0.2),
175 );
176
177 p.add_dynamic(
178 RigidBodyNode::new(Box::new(
179 Rect::default()
180 .with_size(Vec2::new(50.0, 50.0))
181 .with_fill(ACCENT_PURPLE)
182 .with_radius(4.0),
183 ))
184 .with_position(Vec2::new(580.0, 150.0))
185 .with_shape(PhysicsShape::Cuboid(Vec2::new(25.0, 25.0)))
186 .with_bounciness(0.2),
187 );
188
189 let labels = vec![
190 make_label("Circle (rolls)", 250.0, 500.0, ACCENT_BLUE),
191 make_label("Rect (tumbles)", 610.0, 500.0, ACCENT_PURPLE),
192 ];
193
194 (p, title, subtitle, labels)
195}
196
197fn build_scene4() -> (PhysicsNode, TextNode, TextNode) {
198 let title = make_title("Stacking");
199 let subtitle = make_subtitle("Bodies interact — stacking, settling, and toppling");
200
201 let mut p = PhysicsNode::new().with_opacity(0.0);
202
203 p.add_static(make_floor(460.0, 500.0, FLOOR_COLOR));
204
205 let colors = [
206 ACCENT_RED,
207 ACCENT_BLUE,
208 ACCENT_YELLOW,
209 ACCENT_TEAL,
210 ACCENT_PURPLE,
211 ACCENT_EMERALD,
212 ];
213 let sizes = [
214 Vec2::new(80.0, 30.0),
215 Vec2::new(60.0, 40.0),
216 Vec2::new(70.0, 35.0),
217 Vec2::new(50.0, 50.0),
218 Vec2::new(65.0, 25.0),
219 Vec2::new(45.0, 45.0),
220 ];
221
222 for (i, (size, color)) in sizes.iter().zip(colors.iter()).enumerate() {
223 let x_offset = if i % 2 == 0 { 3.0 } else { -5.0 };
224 p.add_dynamic(
225 RigidBodyNode::new(Box::new(
226 Rect::default()
227 .with_size(*size)
228 .with_fill(*color)
229 .with_radius(4.0),
230 ))
231 .with_position(Vec2::new(CX + x_offset, 100.0 + i as f32 * 55.0))
232 .with_shape(PhysicsShape::Cuboid(*size * 0.5))
233 .with_bounciness(0.05),
234 );
235 }
236
237 (p, title, subtitle)
238}
239
240fn build_scene5() -> (
241 PhysicsNode,
242 TextNode,
243 TextNode,
244 Signal<f32>,
245 Vec<Box<dyn Node>>,
246) {
247 let title = make_title("Kinematic Containers");
248 let subtitle = make_subtitle("Have infinite mass, but can be moved and used for obstacles");
249
250 let mut p = PhysicsNode::new().with_opacity(0.0);
251
252 let time_var = Signal::new(0.0f32);
254
255 let floor = RigidBodyNode::new(Box::new(
257 Rect::default()
258 .with_size(Vec2::new(400.0, 30.0))
259 .with_fill(FLOOR_COLOR)
260 .with_radius(4.0),
261 ))
262 .with_position(Vec2::new(CX, 450.0))
263 .with_shape(PhysicsShape::Cuboid(Vec2::new(200.0, 15.0)))
264 .with_mode(PhysicsMode::Kinematic);
265
266 let left_wall = RigidBodyNode::new(Box::new(
267 Rect::default()
268 .with_size(Vec2::new(20.0, 220.0))
269 .with_fill(WALL_COLOR)
270 .with_radius(2.0),
271 ))
272 .with_position(Vec2::new(CX - 200.0, 350.0))
273 .with_shape(PhysicsShape::Cuboid(Vec2::new(10.0, 110.0)))
274 .with_mode(PhysicsMode::Kinematic);
275
276 let right_wall = RigidBodyNode::new(Box::new(
277 Rect::default()
278 .with_size(Vec2::new(20.0, 220.0))
279 .with_fill(WALL_COLOR)
280 .with_radius(2.0),
281 ))
282 .with_position(Vec2::new(CX + 200.0, 350.0))
283 .with_shape(PhysicsShape::Cuboid(Vec2::new(10.0, 110.0)))
284 .with_mode(PhysicsMode::Kinematic);
285
286 let floor_link = floor.position.bind(time_var.clone(), move |t| {
288 let dx = (t * 4.0).sin() * 70.0; let dy = (t * 6.0).cos() * 15.0; Vec2::new(CX + dx, 450.0 + dy)
291 });
292
293 let left_link = left_wall.position.bind(time_var.clone(), move |t| {
294 let dx = (t * 4.0).sin() * 70.0;
295 let dy = (t * 6.0).cos() * 15.0;
296 Vec2::new((CX - 200.0) + dx, 350.0 + dy)
297 });
298
299 let right_link = right_wall.position.bind(time_var.clone(), move |t| {
300 let dx = (t * 4.0).sin() * 70.0;
301 let dy = (t * 6.0).cos() * 15.0;
302 Vec2::new((CX + 200.0) + dx, 350.0 + dy)
303 });
304
305 p.add_dynamic(floor);
306 p.add_dynamic(left_wall);
307 p.add_dynamic(right_wall);
308
309 let ball_colors = [
310 ACCENT_RED,
311 ACCENT_BLUE,
312 ACCENT_YELLOW,
313 ACCENT_TEAL,
314 ACCENT_PURPLE,
315 ACCENT_EMERALD,
316 ];
317 let radii = [
318 12.0, 15.0, 10.0, 18.0, 13.0, 11.0, 14.0, 16.0, 9.0, 12.0, 15.0, 10.0,
319 ];
320
321 for (i, radius) in radii.iter().enumerate() {
322 p.add_dynamic(
323 RigidBodyNode::new(Box::new(
324 Circle::default()
325 .with_radius(*radius)
326 .with_fill(ball_colors[i % ball_colors.len()]),
327 ))
328 .with_position(Vec2::new(
329 CX - 120.0 + (i as f32 * 25.0),
330 80.0 + (i as f32 * 15.0),
331 ))
332 .with_shape(PhysicsShape::Ball(*radius))
333 .with_bounciness(0.4),
334 );
335 }
336
337 let links: Vec<Box<dyn Node>> = vec![
338 Box::new(floor_link),
339 Box::new(left_link),
340 Box::new(right_link),
341 ];
342 (p, title, subtitle, time_var, links)
343}
344
345fn build_scene6() -> (PhysicsNode, TextNode, TextNode) {
346 let title = make_title("Zero Gravity");
347 let subtitle = make_subtitle("gravity = (0, 0) — objects float, collisions still work");
348
349 let mut p = PhysicsNode::new()
350 .with_gravity(Vec2::new(0.0, 0.0))
351 .with_opacity(0.0);
352
353 p.add_static(make_wall(CX, 100.0, 800.0, 20.0));
354 p.add_static(make_wall(CX, 480.0, 800.0, 20.0));
355 p.add_static(make_wall(80.0, 290.0, 20.0, 400.0));
356 p.add_static(make_wall(880.0, 290.0, 20.0, 400.0));
357
358 let positions = [
359 (250.0, 200.0),
360 (480.0, 180.0),
361 (700.0, 250.0),
362 (350.0, 350.0),
363 (600.0, 380.0),
364 (200.0, 400.0),
365 ];
366 let velocities = [
367 Vec2::new(150.0, 80.0),
368 Vec2::new(-200.0, 100.0),
369 Vec2::new(-100.0, -150.0),
370 Vec2::new(180.0, -120.0),
371 Vec2::new(220.0, 180.0),
372 Vec2::new(-140.0, -200.0),
373 ];
374 let spins = [1.0, -1.5, 2.0, -0.8, 1.2, -2.5];
375 let colors = [
376 ACCENT_RED,
377 ACCENT_BLUE,
378 ACCENT_YELLOW,
379 ACCENT_TEAL,
380 ACCENT_PURPLE,
381 ACCENT_EMERALD,
382 ];
383
384 for (i, (((x, y), vel), spin)) in positions
385 .iter()
386 .zip(velocities.iter())
387 .zip(spins.iter())
388 .enumerate()
389 {
390 let color = colors[i % colors.len()];
391 if i % 2 == 0 {
392 p.add_dynamic(
393 RigidBodyNode::new(Box::new(
394 Circle::default().with_radius(22.0).with_fill(color),
395 ))
396 .with_position(Vec2::new(*x, *y))
397 .with_shape(PhysicsShape::Ball(22.0))
398 .with_bounciness(0.8)
399 .with_initial_velocity(*vel)
400 .with_initial_angular_velocity(*spin),
401 );
402 } else {
403 p.add_dynamic(
404 RigidBodyNode::new(Box::new(
405 Rect::default()
406 .with_size(Vec2::new(40.0, 40.0))
407 .with_fill(color)
408 .with_radius(4.0),
409 ))
410 .with_position(Vec2::new(*x, *y))
411 .with_shape(PhysicsShape::Cuboid(Vec2::new(20.0, 20.0)))
412 .with_bounciness(0.8)
413 .with_initial_velocity(*vel)
414 .with_initial_angular_velocity(*spin),
415 );
416 }
417 }
418
419 (p, title, subtitle)
420}
421
422fn build_scene7() -> (
423 PhysicsNode,
424 TextNode,
425 TextNode,
426 Vec<RigidBodyNode>,
427 Vec<TextNode>,
428 Vec<Box<dyn Node>>,
429) {
430 let title = make_title("Friction");
431 let subtitle = make_subtitle("Varying friction: 0.0 (ice) · 0.7 (medium) · 0.9 (rough)");
432
433 let mut p = PhysicsNode::new().with_opacity(0.0);
434
435 p.add_static(make_floor(460.0, 800.0, FLOOR_COLOR));
436
437 p.add_static(
438 StaticBodyNode::new(Box::new(
439 Rect::default()
440 .with_size(Vec2::new(180.0, 16.0))
441 .with_fill(WALL_COLOR)
442 .with_radius(2.0),
443 ))
444 .with_position(Vec2::new(240.0, 280.0))
445 .with_rotation(0.35)
446 .with_shape(PhysicsShape::Cuboid(Vec2::new(90.0, 8.0)))
447 .with_friction(0.0),
448 );
449
450 p.add_static(
451 StaticBodyNode::new(Box::new(
452 Rect::default()
453 .with_size(Vec2::new(180.0, 16.0))
454 .with_fill(WALL_COLOR)
455 .with_radius(2.0),
456 ))
457 .with_position(Vec2::new(480.0, 280.0))
458 .with_rotation(0.35)
459 .with_shape(PhysicsShape::Cuboid(Vec2::new(90.0, 8.0)))
460 .with_friction(0.7),
461 );
462
463 p.add_static(
464 StaticBodyNode::new(Box::new(
465 Rect::default()
466 .with_size(Vec2::new(180.0, 16.0))
467 .with_fill(WALL_COLOR)
468 .with_radius(2.0),
469 ))
470 .with_position(Vec2::new(720.0, 280.0))
471 .with_rotation(0.35)
472 .with_shape(PhysicsShape::Cuboid(Vec2::new(90.0, 8.0)))
473 .with_friction(0.9),
474 );
475
476 let cube_configs: [(f32, Color, f32, &str); 3] = [
478 (190.0, ACCENT_TEAL, 0.0, "Friction: 0.0"),
479 (430.0, ACCENT_YELLOW, 0.2, "Friction: 0.7"),
480 (670.0, ACCENT_RED, 0.9, "Friction: 0.9"),
481 ];
482
483 let mut cube_refs = Vec::new();
484 let mut labels = Vec::new();
485 let mut bindings: Vec<Box<dyn Node>> = Vec::new();
486
487 for (x, color, friction, label_text) in &cube_configs {
488 let cube = RigidBodyNode::new(Box::new(
489 Rect::default()
490 .with_size(Vec2::new(40.0, 40.0))
491 .with_fill(*color)
492 .with_radius(3.0),
493 ))
494 .with_position(Vec2::new(*x, 150.0))
495 .with_shape(PhysicsShape::Cuboid(Vec2::new(20.0, 20.0)))
496 .with_bounciness(0.1)
497 .with_friction(*friction);
498
499 let label = TextNode::default()
501 .with_text(label_text)
502 .with_font_size(14.0)
503 .with_fill(*color)
504 .with_font("JetBrains Mono")
505 .with_anchor(Vec2::ZERO)
506 .with_opacity(0.0);
507
508 let binding = label
510 .position
511 .bind(cube.position.clone(), |pos| Vec2::new(pos.x, pos.y - 35.0));
512
513 cube_refs.push(cube.clone());
514 labels.push(label);
515 bindings.push(Box::new(binding));
516
517 p.add_dynamic(cube);
518 }
519
520 (p, title, subtitle, cube_refs, labels, bindings)
521}
522
523fn build_scene8() -> (
524 PhysicsNode,
525 TextNode,
526 TextNode,
527 RigidBodyNode,
528 Vec<RigidBodyNode>,
529 TextNode,
530) {
531 let title = make_title("Mode Switching");
532 let subtitle =
533 make_subtitle("Disabled → Dynamic → Kinematic — seamless signal ↔ physics transitions");
534
535 let mut p = PhysicsNode::new().with_opacity(0.0);
536
537 p.add_static(make_floor(460.0, 800.0, FLOOR_COLOR));
538 p.add_static(make_wall(80.0, 350.0, 20.0, 300.0));
540 p.add_static(make_wall(880.0, 350.0, 20.0, 300.0));
541
542 let text_body = RigidBodyNode::new(Box::new(
544 TextNode::default()
545 .with_text("motion-canvas-rs")
546 .with_font_size(36.0)
547 .with_fill(ACCENT_BLUE)
548 .with_font("JetBrains Mono")
549 .with_anchor(Vec2::ZERO),
550 ))
551 .with_position(Vec2::new(CX, 270.0))
552 .with_shape(PhysicsShape::Cuboid(Vec2::new(170.0, 20.0)))
553 .with_bounciness(0.3)
554 .with_mode(PhysicsMode::Disabled);
555
556 let ball_colors = [ACCENT_RED, ACCENT_YELLOW, ACCENT_EMERALD, ACCENT_PURPLE];
558 let mut balls = Vec::new();
559 for i in 0..12 {
560 let color = ball_colors[(i % 4) as usize];
561 let ball = RigidBodyNode::new(Box::new(
562 Circle::default().with_radius(15.0).with_fill(color),
563 ))
564 .with_position(Vec2::new(CX - 90.0 + ((i % 4) as f32 * 60.0), -50.0))
565 .with_shape(PhysicsShape::Ball(15.0))
566 .with_bounciness(0.6)
567 .with_mode(PhysicsMode::Disabled);
568 balls.push(ball);
569 }
570
571 let status = TextNode::default()
573 .with_text("")
574 .with_position(Vec2::new(CX, 500.0))
575 .with_anchor(Vec2::ZERO)
576 .with_font_size(16.0)
577 .with_fill(TEXT_DIM)
578 .with_font("JetBrains Mono")
579 .with_opacity(0.0);
580
581 let text_ref = text_body.clone();
582 let ball_refs: Vec<RigidBodyNode> = balls.iter().map(|b| b.clone()).collect();
583
584 p.add_dynamic(text_body);
585 for ball in balls {
586 p.add_dynamic(ball);
587 }
588
589 (p, title, subtitle, text_ref, ball_refs, status)
590}
591
592fn build_scene9() -> (PhysicsNode, TextNode, TextNode) {
593 let title = make_title("Custom Colliders");
594 let subtitle = make_subtitle("Custom convex polygons falling through static pegs");
595
596 let mut p = PhysicsNode::new().with_opacity(0.0);
597
598 p.add_static(make_floor(490.0, 600.0, FLOOR_COLOR));
600
601 const NUM_ROWS: usize = 5;
603 let peg_radius = 8.0;
604
605 for r in 0..NUM_ROWS {
606 let y = 180.0 + r as f32 * 60.0;
607 let is_even = r % 2 == 0;
608 let num_pegs = if is_even { 5 } else { 6 };
609
610 for c in 0..num_pegs {
611 let offset_x = if is_even {
612 (c as f32 - 2.0) * 80.0
613 } else {
614 (c as f32 - 2.5) * 80.0
615 };
616 let x = CX + offset_x;
617
618 p.add_static(
619 StaticBodyNode::new(Box::new(
620 Circle::default()
621 .with_radius(peg_radius)
622 .with_fill(WALL_COLOR),
623 ))
624 .with_position(Vec2::new(x, y))
625 .with_shape(PhysicsShape::Ball(peg_radius))
626 .with_bounciness(0.6),
627 );
628 }
629 }
630
631 for i in 0..12 {
633 let x_offset = CX - 100.0 + (i as f32 * 18.0);
634 let y_offset = 80.0 - (i as f32 * 65.0);
635
636 let points = vec![
637 Vec2::new(0.0, -22.0),
638 Vec2::new(14.0, 0.0),
639 Vec2::new(0.0, 22.0),
640 Vec2::new(-14.0, 0.0),
641 ];
642
643 let diamond_visual = Polygon::default()
644 .with_points(points)
645 .with_fill(ACCENT_PURPLE);
646
647 let custom_col = PhysicsShape::Custom(std::sync::Arc::new(move || {
648 rapier2d::prelude::ColliderBuilder::convex_polyline(vec![
649 rapier2d::prelude::Point::new(0.0, -22.0),
650 rapier2d::prelude::Point::new(14.0, 0.0),
651 rapier2d::prelude::Point::new(0.0, 22.0),
652 rapier2d::prelude::Point::new(-14.0, 0.0),
653 ])
654 .expect("Invalid custom shape polygon")
655 }));
656
657 p.add_dynamic(
658 RigidBodyNode::new(Box::new(diamond_visual))
659 .with_position(Vec2::new(x_offset, y_offset))
660 .with_shape(custom_col)
661 .with_bounciness(0.5)
662 .with_friction(0.15),
663 );
664 }
665
666 (p, title, subtitle)
667}
668
669fn build_scene10() -> (PhysicsNode, TextNode, TextNode) {
670 let title = make_title("Final Example");
671 let subtitle = make_subtitle("100 shapes, high bounciness, one container");
672
673 let mut p = PhysicsNode::new().with_opacity(0.0);
674
675 p.add_static(make_floor(500.0, 700.0, FLOOR_COLOR));
676 p.add_static(make_wall(CX - 350.0, 350.0, 20.0, 320.0));
677 p.add_static(make_wall(CX + 350.0, 350.0, 20.0, 320.0));
678
679 let all_colors = [
680 ACCENT_RED,
681 ACCENT_BLUE,
682 ACCENT_YELLOW,
683 ACCENT_TEAL,
684 ACCENT_PURPLE,
685 ACCENT_EMERALD,
686 ];
687
688 for i in 0..100 {
689 let x = CX + (if i % 2 == 0 { 8.0 } else { -8.0 });
690 let y = -100.0 - (i as f32 * 140.0);
691 let color = all_colors[i % all_colors.len()];
692
693 if i % 3 == 0 {
694 let r = 24.0 + (i % 4) as f32 * 8.0;
695 p.add_dynamic(
696 RigidBodyNode::new(Box::new(Circle::default().with_radius(r).with_fill(color)))
697 .with_position(Vec2::new(x, y))
698 .with_shape(PhysicsShape::Ball(r))
699 .with_bounciness(0.85),
700 );
701 } else {
702 let s = 45.0 + (i % 3) as f32 * 12.0;
703 p.add_dynamic(
704 RigidBodyNode::new(Box::new(
705 Rect::default()
706 .with_size(Vec2::new(s, s))
707 .with_fill(color)
708 .with_radius(4.0),
709 ))
710 .with_position(Vec2::new(x, y))
711 .with_shape(PhysicsShape::Cuboid(Vec2::new(s / 2.0, s / 2.0)))
712 .with_bounciness(0.7),
713 );
714 }
715 }
716
717 (p, title, subtitle)
718}
719
720fn main() {
721 let mut project = Project::default()
722 .with_dimensions(W, H)
723 .with_title("Physics Demo")
724 .with_background(BG)
725 .close_on_finish();
726
727 let (p1, t1, s1) = build_scene1();
729 let (p2, t2, s2, labels2) = build_scene2();
730 let (p3, t3, s3, labels3) = build_scene3();
731 let (p4, t4, s4) = build_scene4();
732 let (p5, t5, s5, time5_var, links5) = build_scene5();
733 let (p6, t6, s6) = build_scene6();
734 let (p7, t7, s7, _cube_refs7, labels7, bindings7) = build_scene7();
735 let (p8, t8, s8, text8, balls8, status8) = build_scene8();
736 let (p9, t9, s9) = build_scene9();
737 let (p10, t10, s10) = build_scene10();
738
739 project.scene.add(&p1);
741 project.scene.add(&t1);
742 project.scene.add(&s1);
743
744 project.scene.add(&p2);
745 project.scene.add(&t2);
746 project.scene.add(&s2);
747 let labels2_c: Vec<_> = labels2
748 .iter()
749 .map(|l| {
750 project.scene.add(l);
751 l.clone()
752 })
753 .collect();
754
755 project.scene.add(&p3);
756 project.scene.add(&t3);
757 project.scene.add(&s3);
758 let labels3_c: Vec<_> = labels3
759 .iter()
760 .map(|l| {
761 project.scene.add(l);
762 l.clone()
763 })
764 .collect();
765
766 project.scene.add(&p4);
767 project.scene.add(&t4);
768 project.scene.add(&s4);
769
770 project.scene.add(&p5);
771 project.scene.add(&t5);
772 project.scene.add(&s5);
773
774 for link in links5 {
775 project.scene.add(link);
776 }
777
778 project.scene.add(&p6);
779 project.scene.add(&t6);
780 project.scene.add(&s6);
781
782 project.scene.add(&p7);
783 project.scene.add(&t7);
784 project.scene.add(&s7);
785 let labels7_c: Vec<_> = labels7
786 .iter()
787 .map(|l| {
788 project.scene.add(l);
789 l.clone()
790 })
791 .collect();
792 for binding in bindings7 {
793 project.scene.add(binding);
794 }
795
796 project.scene.add(&p8);
797 project.scene.add(&t8);
798 project.scene.add(&s8);
799 project.scene.add(&status8);
800
801 project.scene.add(&p9);
802 project.scene.add(&t9);
803 project.scene.add(&s9);
804
805 project.scene.add(&p10);
806 project.scene.add(&t10);
807 project.scene.add(&s10);
808
809 project.scene.video_timeline.add(chain![
811 t1.opacity.to(1.0, FADE),
813 wait!(1),
814 all![p1.opacity.to(1.0, FADE), s1.opacity.to(1.0, FADE),],
815 wait!(4.0),
816 all![
817 p1.opacity.to(0.0, FADE),
818 t1.opacity.to(0.0, FADE),
819 s1.opacity.to(0.0, FADE),
820 ],
821 t2.opacity.to(1.0, FADE),
823 wait!(1),
824 all![
825 p2.opacity.to(1.0, FADE),
826 s2.opacity.to(1.0, FADE),
827 labels2_c[0].opacity.to(1.0, FADE),
828 labels2_c[1].opacity.to(1.0, FADE),
829 labels2_c[2].opacity.to(1.0, FADE),
830 ],
831 wait!(5.0),
832 all![
833 p2.opacity.to(0.0, FADE),
834 s2.opacity.to(0.0, FADE),
835 t2.opacity.to(0.0, FADE),
836 labels2_c[0].opacity.to(0.0, FADE),
837 labels2_c[1].opacity.to(0.0, FADE),
838 labels2_c[2].opacity.to(0.0, FADE),
839 ],
840 t3.opacity.to(1.0, FADE),
842 wait!(1),
843 all![
844 p3.opacity.to(1.0, FADE),
845 s3.opacity.to(1.0, FADE),
846 labels3_c[0].opacity.to(1.0, FADE),
847 labels3_c[1].opacity.to(1.0, FADE),
848 ],
849 wait!(5.0),
850 all![
851 p3.opacity.to(0.0, FADE),
852 t3.opacity.to(0.0, FADE),
853 s3.opacity.to(0.0, FADE),
854 labels3_c[0].opacity.to(0.0, FADE),
855 labels3_c[1].opacity.to(0.0, FADE),
856 ],
857 t4.opacity.to(1.0, FADE),
859 wait!(1),
860 all![p4.opacity.to(1.0, FADE), s4.opacity.to(1.0, FADE),],
861 wait!(5.0),
862 all![
863 p4.opacity.to(0.0, FADE),
864 t4.opacity.to(0.0, FADE),
865 s4.opacity.to(0.0, FADE),
866 ],
867 t5.opacity.to(1.0, FADE),
869 wait!(1),
870 all![
871 p5.opacity.to(1.0, FADE),
872 s5.opacity.to(1.0, FADE),
873 time5_var.to(4.0, Duration::from_secs(4)),
874 ],
875 wait!(4.0),
876 all![
877 p5.opacity.to(0.0, FADE),
878 t5.opacity.to(0.0, FADE),
879 s5.opacity.to(0.0, FADE),
880 ],
881 t6.opacity.to(1.0, FADE),
883 wait!(1),
884 all![p6.opacity.to(1.0, FADE), s6.opacity.to(1.0, FADE),],
885 wait!(5.0),
886 all![
887 p6.opacity.to(0.0, FADE),
888 t6.opacity.to(0.0, FADE),
889 s6.opacity.to(0.0, FADE),
890 ],
891 t7.opacity.to(1.0, FADE),
893 wait!(1),
894 all![
895 p7.opacity.to(1.0, FADE),
896 s7.opacity.to(1.0, FADE),
897 labels7_c[0].opacity.to(1.0, FADE),
898 labels7_c[1].opacity.to(1.0, FADE),
899 labels7_c[2].opacity.to(1.0, FADE),
900 ],
901 wait!(3.0),
902 all![
903 p7.opacity.to(0.0, FADE),
904 t7.opacity.to(0.0, FADE),
905 s7.opacity.to(0.0, FADE),
906 labels7_c[0].opacity.to(0.0, FADE),
907 labels7_c[1].opacity.to(0.0, FADE),
908 labels7_c[2].opacity.to(0.0, FADE),
909 ],
910 t8.opacity.to(1.0, FADE),
912 wait!(1),
913 all![
914 p8.opacity.to(1.0, FADE),
915 s8.opacity.to(1.0, FADE),
916 status8.opacity.to(1.0, FADE),
917 ],
918 status8
920 .text
921 .to("mode: Disabled".to_string(), Duration::from_millis(1)),
922 wait!(0.5),
923 text8
925 .position
926 .to(Vec2::new(CX - 200.0, 270.0), Duration::from_millis(800)),
927 wait!(0.3),
928 text8
930 .position
931 .to(Vec2::new(CX + 200.0, 270.0), Duration::from_millis(800)),
932 wait!(0.3),
933 text8
935 .position
936 .to(Vec2::new(CX, 270.0), Duration::from_millis(600)),
937 wait!(1.0),
938 all![
940 balls8[0]
941 .mode
942 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
943 balls8[1]
944 .mode
945 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
946 balls8[2]
947 .mode
948 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
949 balls8[3]
950 .mode
951 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
952 ],
953 wait!(3.0),
954 status8
956 .text
957 .to("mode: Dynamic".to_string(), Duration::from_millis(1)),
958 text8
959 .mode
960 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
961 wait!(1.0),
962 all![
964 balls8[4]
965 .mode
966 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
967 balls8[5]
968 .mode
969 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
970 balls8[6]
971 .mode
972 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
973 balls8[7]
974 .mode
975 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
976 ],
977 wait!(3.0),
978 status8
980 .text
981 .to("mode: Kinematic".to_string(), Duration::from_millis(1)),
982 text8
983 .mode
984 .to(PhysicsMode::Kinematic, Duration::from_millis(1)),
985 text8
987 .position
988 .to(Vec2::new(CX, 300.0), Duration::from_millis(800)),
989 all![
991 balls8[8]
992 .mode
993 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
994 balls8[9]
995 .mode
996 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
997 balls8[10]
998 .mode
999 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
1000 balls8[11]
1001 .mode
1002 .to(PhysicsMode::Dynamic, Duration::from_millis(1)),
1003 ],
1004 wait!(2.0),
1005 text8
1007 .position
1008 .to(Vec2::new(CX - 100.0, 280.0), Duration::from_millis(600)),
1009 text8
1010 .position
1011 .to(Vec2::new(CX + 100.0, 320.0), Duration::from_millis(600)),
1012 text8
1013 .position
1014 .to(Vec2::new(CX, 300.0), Duration::from_millis(400)),
1015 wait!(2.0),
1016 all![
1017 p8.opacity.to(0.0, FADE),
1018 t8.opacity.to(0.0, FADE),
1019 s8.opacity.to(0.0, FADE),
1020 status8.opacity.to(0.0, FADE),
1021 ],
1022 t9.opacity.to(1.0, FADE),
1024 wait!(1),
1025 all![p9.opacity.to(1.0, FADE), s9.opacity.to(1.0, FADE),],
1026 wait!(6.0),
1027 all![
1028 p9.opacity.to(0.0, FADE),
1029 t9.opacity.to(0.0, FADE),
1030 s9.opacity.to(0.0, FADE),
1031 ],
1032 t10.opacity.to(1.0, FADE),
1034 wait!(1),
1035 all![p10.opacity.to(1.0, FADE), s10.opacity.to(1.0, FADE),],
1036 wait!(10.0),
1037 all![
1038 p10.opacity.to(0.0, FADE),
1039 t10.opacity.to(0.0, FADE),
1040 s10.opacity.to(0.0, FADE),
1041 ],
1042 wait!(1),
1043 ]);
1044
1045 project.show().expect("Failed to render");
1046}