Skip to main content

box2d_rust/world/
step.rs

1// The world step from physics_world.c: b2World_Step. The narrow phase
2// (b2Collide / b2CollideTask) lives in the sibling `collide` module.
3//
4// b2World_Step takes &mut World instead of a b2WorldId (no global world
5// registry). Profiling timers match C (`b2GetTicks` / profile fields).
6//
7// SPDX-FileCopyrightText: 2023 Erin Catto
8// SPDX-License-Identifier: MIT
9
10use super::collide::collide;
11use super::World;
12use crate::broad_phase::update_broad_phase_pairs;
13use crate::math_functions::{is_valid_float, min_float};
14use crate::sensor::overlap_sensors;
15use crate::solver::{make_soft, solve, StepContext};
16use crate::solver_set::STATIC_SET;
17use crate::timer::{get_milliseconds, get_ticks};
18use crate::types::BodyType;
19
20/// Simulate a world for one time step. (b2World_Step — takes &mut World; the
21/// C resolves the world from an id)
22pub fn world_step(world: &mut World, time_step: f32, sub_step_count: i32) {
23    debug_assert!(is_valid_float(time_step));
24    debug_assert!(0 < sub_step_count);
25
26    debug_assert!(!world.locked);
27    if world.locked {
28        return;
29    }
30
31    // (B2_REC(world, Step, ...))
32    if let Some(mut rec) = world.recording.take() {
33        let world_id = crate::id::WorldId {
34            index1: world.world_id + 1,
35            generation: world.generation,
36        };
37        crate::recording::write_step(&mut rec, world_id, time_step, sub_step_count);
38        world.recording = Some(rec);
39    }
40
41    // Prepare to capture events. Ensure user does not access stale data if
42    // there is an early return.
43    world.body_move_events.clear();
44    world.sensor_begin_events.clear();
45    world.contact_begin_events.clear();
46    world.contact_hit_events.clear();
47    world.joint_events.clear();
48
49    world.profile = super::Profile::default();
50
51    world.locked = true;
52
53    let step_ticks = get_ticks();
54
55    {
56        let static_shape_count = world.broad_phase.trees[BodyType::Static as usize].proxy_count();
57        let dynamic_shape_count = world.broad_phase.trees[BodyType::Dynamic as usize].proxy_count();
58        let static_body_count = world.solver_sets[STATIC_SET as usize].body_sims.len() as i32;
59        // this includes kinematic bodies
60        let total_body_count = world.body_id_pool.id_count();
61        let total_contact_count = world.contact_id_pool.id_count();
62
63        let c = &mut world.max_capacity;
64        c.static_shape_count =
65            crate::math_functions::max_int(c.static_shape_count, static_shape_count);
66        c.dynamic_shape_count =
67            crate::math_functions::max_int(c.dynamic_shape_count, dynamic_shape_count);
68        c.static_body_count =
69            crate::math_functions::max_int(c.static_body_count, static_body_count);
70        c.dynamic_body_count = crate::math_functions::max_int(
71            c.dynamic_body_count,
72            total_body_count - static_body_count,
73        );
74        c.contact_count = crate::math_functions::max_int(c.contact_count, total_contact_count);
75    }
76
77    // Update collision pairs and create contacts
78    {
79        let pair_ticks = get_ticks();
80        update_broad_phase_pairs(world);
81        world.profile.pairs = get_milliseconds(pair_ticks);
82    }
83
84    let sub_step_count = crate::math_functions::max_int(1, sub_step_count);
85    let mut context = StepContext {
86        dt: time_step,
87        sub_step_count,
88        ..StepContext::default()
89    };
90
91    if time_step > 0.0 {
92        context.inv_dt = 1.0 / time_step;
93        context.h = time_step / sub_step_count as f32;
94        context.inv_h = sub_step_count as f32 * context.inv_dt;
95    } else {
96        context.inv_dt = 0.0;
97        context.h = 0.0;
98        context.inv_h = 0.0;
99    }
100
101    world.inv_h = context.inv_h;
102    world.inv_dt = context.inv_dt;
103
104    // Hertz values get reduced for large time steps
105    let contact_hertz = min_float(world.contact_hertz, 0.125 * context.inv_h);
106    context.contact_softness = make_soft(contact_hertz, world.contact_damping_ratio, context.h);
107    context.static_softness =
108        make_soft(2.0 * contact_hertz, world.contact_damping_ratio, context.h);
109
110    context.restitution_threshold = world.restitution_threshold;
111    context.max_linear_velocity = world.max_linear_speed;
112    context.contact_speed = world.contact_speed;
113    context.enable_warm_starting = world.enable_warm_starting;
114
115    // Narrow phase: update contacts
116    {
117        let collide_ticks = get_ticks();
118        collide(world, &context);
119        world.profile.collide = get_milliseconds(collide_ticks);
120    }
121
122    // Integrate velocities, solve velocity constraints, and integrate
123    // positions.
124    if time_step > 0.0 {
125        let solve_ticks = get_ticks();
126        solve(world, &context);
127        world.profile.solve = get_milliseconds(solve_ticks);
128    }
129
130    // Update sensors
131    {
132        let sensor_ticks = get_ticks();
133        overlap_sensors(world);
134        world.profile.sensors = get_milliseconds(sensor_ticks);
135    }
136
137    world.profile.step = get_milliseconds(step_ticks);
138
139    world.locked = false;
140
141    // Swap end event array buffers
142    world.end_event_array_index = 1 - world.end_event_array_index;
143    world.sensor_end_events[world.end_event_array_index as usize].clear();
144    world.contact_end_events[world.end_event_array_index as usize].clear();
145
146    // Per-step StateHash + bounds growth for an active recording session.
147    crate::recording::record_step_end(world);
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use crate::body::{create_body, get_body_full_id, get_body_transform};
154    use crate::geometry::make_box;
155    use crate::math_functions::{abs_float, rot_get_angle, to_pos, Vec2};
156    use crate::shape::create_polygon_shape;
157    use crate::types::{default_body_def, default_shape_def, default_world_def};
158
159    // Port of test_world.c HelloWorld: a dynamic box falls onto a large
160    // static ground box and comes to rest at y ~= 1.
161    #[test]
162    fn hello_world() {
163        // Construct a world object, which will hold and simulate the rigid
164        // bodies.
165        let mut world_def = default_world_def();
166        world_def.gravity = Vec2 { x: 0.0, y: -10.0 };
167
168        let mut world = World::new(&world_def);
169
170        // Define the ground body.
171        let mut ground_body_def = default_body_def();
172        ground_body_def.position = to_pos(Vec2 { x: 0.0, y: -10.0 });
173
174        let ground_id = create_body(&mut world, &ground_body_def);
175
176        // Define the ground box shape. The extents are the half-widths of the
177        // box.
178        let ground_box = make_box(50.0, 10.0);
179
180        // Add the box shape to the ground body.
181        let ground_shape_def = default_shape_def();
182        create_polygon_shape(&mut world, ground_id, &ground_shape_def, &ground_box);
183
184        // Define the dynamic body. We set its position and call the body
185        // factory.
186        let mut body_def = default_body_def();
187        body_def.type_ = crate::types::BodyType::Dynamic;
188        body_def.position = to_pos(Vec2 { x: 0.0, y: 4.0 });
189
190        let body_id = create_body(&mut world, &body_def);
191        let body_index = get_body_full_id(&world, body_id);
192
193        // Define another box shape for our dynamic body.
194        let dynamic_box = make_box(1.0, 1.0);
195
196        // Define the dynamic body shape. Set the box density to be non-zero,
197        // so it will be dynamic. Override the default friction.
198        let mut shape_def = default_shape_def();
199        shape_def.density = 1.0;
200        shape_def.material.friction = 0.3;
201
202        create_polygon_shape(&mut world, body_id, &shape_def, &dynamic_box);
203
204        // Prepare for simulation. Typically we use a time step of 1/60 of a
205        // second (60Hz) and 4 sub-steps. This provides a high quality
206        // simulation in most game scenarios.
207        let time_step = 1.0 / 60.0;
208        let sub_step_count = 4;
209
210        // This is our little game loop.
211        for _ in 0..90 {
212            // Instruct the world to perform a single step of simulation. It is
213            // generally best to keep the time step and iterations fixed.
214            world_step(&mut world, time_step, sub_step_count);
215        }
216
217        let transform = get_body_transform(&world, body_index);
218        let position = transform.p;
219        let rotation = transform.q;
220
221        assert!(abs_float(position.x as f32) < 0.01);
222        assert!(abs_float(position.y as f32 - 1.00) < 0.01);
223        assert!(abs_float(rot_get_angle(rotation)) < 0.01);
224
225        // Profile timings are filled each step (timer.c was dropped earlier).
226        let profile = crate::world::world_get_profile(&world);
227        assert!(profile.step > 0.0);
228        assert!(profile.pairs >= 0.0);
229        assert!(profile.collide >= 0.0);
230        assert!(profile.solve >= 0.0);
231        assert!(profile.sensors >= 0.0);
232    }
233
234    // Port of test_world.c EmptyWorld: stepping an empty world does nothing.
235    #[test]
236    fn empty_world() {
237        let world_def = default_world_def();
238        let mut world = World::new(&world_def);
239
240        let time_step = 1.0 / 60.0;
241        let sub_step_count = 1;
242
243        for _ in 0..60 {
244            world_step(&mut world, time_step, sub_step_count);
245        }
246
247        // b2Solve counts the step before the awake-body early out
248        assert_eq!(world.step_index, 60);
249    }
250}