Skip to main content

box3d_rust/world/
step.rs

1// b3World_Step from physics_world.c, split out of world/mod.rs to keep
2// modules focused.
3//
4// SPDX-FileCopyrightText: 2025 Erin Catto
5// SPDX-License-Identifier: MIT
6
7use crate::world::{Profile, World};
8
9impl World {
10    /// Advance the world simulation by one time step. (b3World_Step)
11    pub fn step(&mut self, time_step: f32, sub_step_count: i32) {
12        use crate::broad_phase::update_broad_phase_pairs;
13        use crate::math_functions::{is_valid_float, max_int, min_float};
14        use crate::solver::{make_soft, solve, StepContext};
15
16        debug_assert!(is_valid_float(time_step) && time_step >= 0.0);
17        debug_assert!(!self.locked);
18        if self.locked {
19            return;
20        }
21
22        crate::recording::capture::rec(self, |rec, wid| {
23            rec.write_step(wid, time_step, sub_step_count);
24        });
25
26        self.locked = true;
27
28        self.body_move_events.clear();
29        self.sensor_begin_events.clear();
30        self.contact_begin_events.clear();
31        self.contact_hit_events.clear();
32        self.joint_events.clear();
33        self.profile = Profile::default();
34
35        {
36            let c = &mut self.max_capacity;
37            c.static_shape_count = max_int(
38                c.static_shape_count,
39                self.broad_phase.trees[crate::types::BodyType::Static as usize].proxy_count(),
40            );
41            c.dynamic_shape_count = max_int(
42                c.dynamic_shape_count,
43                self.broad_phase.trees[crate::types::BodyType::Dynamic as usize].proxy_count(),
44            );
45            let static_body_count = self.solver_sets[crate::solver_set::STATIC_SET as usize]
46                .body_sims
47                .len() as i32;
48            c.static_body_count = max_int(c.static_body_count, static_body_count);
49            let total_body_count = self.body_id_pool.id_count();
50            c.dynamic_body_count =
51                max_int(c.dynamic_body_count, total_body_count - static_body_count);
52            c.contact_count = max_int(c.contact_count, self.contact_id_pool.id_count());
53        }
54
55        update_broad_phase_pairs(self);
56
57        let sub_steps = max_int(1, sub_step_count);
58        let mut context = StepContext {
59            dt: time_step,
60            sub_step_count: sub_steps,
61            ..StepContext::default()
62        };
63
64        if time_step > 0.0 {
65            context.inv_dt = 1.0 / time_step;
66            context.h = time_step / sub_steps as f32;
67            context.inv_h = sub_steps as f32 * context.inv_dt;
68        } else {
69            context.inv_dt = 0.0;
70            context.h = 0.0;
71            context.inv_h = 0.0;
72        }
73
74        self.inv_dt = context.inv_dt;
75        self.inv_h = context.inv_h;
76
77        // Hertz values get reduced for large time steps
78        let contact_hertz = min_float(self.contact_hertz, 0.125 * context.inv_h);
79        context.contact_softness = make_soft(contact_hertz, self.contact_damping_ratio, context.h);
80        context.static_softness = make_soft(
81            2.0 * contact_hertz,
82            0.5 * self.contact_damping_ratio,
83            context.h,
84        );
85        context.restitution_threshold = self.restitution_threshold;
86        context.max_linear_velocity = self.max_linear_speed;
87        context.contact_speed = self.contact_speed;
88        context.enable_warm_starting = self.enable_warm_starting;
89
90        crate::contact::collide(self, time_step);
91
92        if time_step > 0.0 {
93            solve(self, &context);
94        }
95
96        // Update sensors (after solve; C physics_world.c runs b3OverlapSensors here)
97        crate::sensor::overlap_sensors(self);
98
99        self.step_index = self.step_index.wrapping_add(1);
100        self.validate_solver_sets();
101
102        // Swap end event array buffers
103        self.end_event_array_index = 1 - self.end_event_array_index;
104        self.sensor_end_events[self.end_event_array_index as usize].clear();
105        self.contact_end_events[self.end_event_array_index as usize].clear();
106        self.locked = false;
107
108        if self.recording.is_some() {
109            use crate::math_functions::aabb_union;
110            use crate::recording::hash::hash_world_state;
111            use crate::recording::session::world_public_id;
112            use crate::types::BODY_TYPE_COUNT;
113
114            let hash = hash_world_state(self);
115            let wid = world_public_id(self);
116            crate::recording::with_recording(self, |rec| {
117                rec.write_state_hash(wid, hash);
118            });
119
120            let mut world_bounds = crate::math_functions::Aabb::default();
121            let mut have_bounds = false;
122            for i in 0..BODY_TYPE_COUNT {
123                let tree = &self.broad_phase.trees[i];
124                if tree.proxy_count() == 0 {
125                    continue;
126                }
127                let bounds = tree.root_bounds();
128                world_bounds = if have_bounds {
129                    aabb_union(world_bounds, bounds)
130                } else {
131                    bounds
132                };
133                have_bounds = true;
134            }
135            if have_bounds {
136                crate::recording::with_recording(self, |rec| {
137                    rec.accumulate_bounds(world_bounds);
138                });
139            }
140        }
141    }
142}