Skip to main content

arcane_engine/physics/
sleep.rs

1use super::types::{BodyType, Contact, RigidBody};
2
3// Velocity threshold must exceed gravity*dt to handle resting contact oscillation.
4// Bodies in resting contact briefly reach gravity-level speed each frame (during integration,
5// before the collision solver zeroes it). A threshold of 8.0 covers gravity*dt up to ~480.
6const SLEEP_VELOCITY_THRESHOLD: f32 = 8.0;
7const SLEEP_ANGULAR_THRESHOLD: f32 = 0.5;
8const SLEEP_TIME_THRESHOLD: f32 = 0.3;
9
10/// Island-based sleep: groups of connected dynamic bodies sleep/wake together.
11/// This prevents cascading wake in stacks where individual bodies sleep at different
12/// times, corrupting the warm start cache and causing endless blinking.
13pub fn update_sleep(
14    bodies: &mut [Option<RigidBody>],
15    contacts: &[Contact],
16    dt: f32,
17) {
18    let len = bodies.len();
19    if len == 0 {
20        return;
21    }
22
23    // Build islands using union-find on contacts.
24    // Each dynamic body starts as its own island.
25    let mut parent: Vec<usize> = (0..len).collect();
26
27    // Union-find helpers (inline for simplicity)
28    fn find(parent: &mut [usize], mut x: usize) -> usize {
29        while parent[x] != x {
30            parent[x] = parent[parent[x]]; // path compression
31            x = parent[x];
32        }
33        x
34    }
35    fn union(parent: &mut [usize], a: usize, b: usize) {
36        let ra = find(parent, a);
37        let rb = find(parent, b);
38        if ra != rb {
39            parent[rb] = ra;
40        }
41    }
42
43    // Merge dynamic bodies that share contacts into the same island
44    for contact in contacts {
45        let a = contact.body_a as usize;
46        let b = contact.body_b as usize;
47        if a >= len || b >= len {
48            continue;
49        }
50        let a_dynamic = bodies[a]
51            .as_ref()
52            .map_or(false, |b| b.body_type == BodyType::Dynamic);
53        let b_dynamic = bodies[b]
54            .as_ref()
55            .map_or(false, |b| b.body_type == BodyType::Dynamic);
56
57        if a_dynamic && b_dynamic {
58            union(&mut parent, a, b);
59        }
60    }
61
62    // Collect islands: map root → list of dynamic body indices
63    let mut island_map: std::collections::HashMap<usize, Vec<usize>> =
64        std::collections::HashMap::new();
65    for i in 0..len {
66        let is_dynamic = bodies[i]
67            .as_ref()
68            .map_or(false, |b| b.body_type == BodyType::Dynamic);
69        if !is_dynamic {
70            continue;
71        }
72        let root = find(&mut parent, i);
73        island_map.entry(root).or_default().push(i);
74    }
75
76    let threshold_sq = SLEEP_VELOCITY_THRESHOLD * SLEEP_VELOCITY_THRESHOLD;
77
78    // Process each island
79    for (_root, members) in &island_map {
80        // Check if ALL bodies in the island are below velocity threshold
81        let all_below = members.iter().all(|&i| {
82            let b = bodies[i].as_ref().unwrap();
83            let speed_sq = b.vx * b.vx + b.vy * b.vy;
84            speed_sq < threshold_sq && b.angular_velocity.abs() < SLEEP_ANGULAR_THRESHOLD
85        });
86
87        if all_below {
88            // Increment all timers. Find the minimum timer in the island.
89            let mut min_timer = f32::MAX;
90            for &i in members {
91                let b = bodies[i].as_mut().unwrap();
92                b.sleep_timer += dt;
93                min_timer = min_timer.min(b.sleep_timer);
94            }
95
96            // Only sleep the entire island when ALL members have been still long enough
97            if min_timer > SLEEP_TIME_THRESHOLD {
98                for &i in members {
99                    let b = bodies[i].as_mut().unwrap();
100                    b.sleeping = true;
101                    b.vx = 0.0;
102                    b.vy = 0.0;
103                    b.angular_velocity = 0.0;
104                }
105            }
106        } else {
107            // Any body above threshold → wake and reset the entire island
108            for &i in members {
109                let b = bodies[i].as_mut().unwrap();
110                b.sleep_timer = 0.0;
111                b.sleeping = false;
112            }
113        }
114    }
115}