Skip to main content

box2d_rust/world/
step.rs

1// The world step from physics_world.c: b2CollideTask/b2Collide (narrow
2// phase + contact state transitions) and b2World_Step.
3//
4// The serial port gathers contact locations instead of the C's arena array
5// of ContactSim pointers, and processes them in the same order: graph colors
6// 0..GRAPH_COLOR_COUNT (overflow included) then the awake set's non-touching
7// contacts. b2World_Step takes &mut World instead of a b2WorldId (no global
8// world registry). Profiling timers match C (`b2GetTicks` / profile fields).
9//
10// SPDX-FileCopyrightText: 2023 Erin Catto
11// SPDX-License-Identifier: MIT
12
13use super::World;
14use crate::broad_phase::update_broad_phase_pairs;
15use crate::constants::{speculative_distance, GRAPH_COLOR_COUNT};
16use crate::contact::{contact_flags, update_contact, Contact, ContactSim, ContactUpdateContext};
17use crate::core::NULL_INDEX;
18use crate::events::{ContactBeginTouchEvent, ContactEndTouchEvent};
19use crate::id::{ContactId, ShapeId};
20use crate::math_functions::{
21    aabb_overlaps, abs_float, distance, dot, inv_mul_rot, inv_mul_world_transforms, invert_rot,
22    is_valid_float, max_float, min_float, mul_rot, rotate_vector, sub, sub_pos, Rot,
23};
24use crate::sensor::overlap_sensors;
25use crate::solver::{make_soft, solve, StepContext};
26use crate::solver_set::{AWAKE_SET, FIRST_SLEEPING_SET, STATIC_SET};
27use crate::timer::{get_milliseconds, get_ticks};
28use crate::types::BodyType;
29
30/// (static inline b2RelativeCos)
31fn relative_cos(a: Rot, b: Rot) -> f32 {
32    a.c * b.c + a.s * b.s
33}
34
35/// Location of a contact sim during the narrow phase: a graph color index or
36/// NULL_INDEX for the awake set's non-touching array, plus the local index.
37/// (The C gathers b2ContactSim pointers; pointers are unstable in Rust.)
38#[derive(Clone, Copy)]
39struct ContactSimLocation {
40    color_index: i32,
41    local_index: i32,
42}
43
44fn get_located_sim(world: &World, location: ContactSimLocation) -> ContactSim {
45    if location.color_index != NULL_INDEX {
46        world.constraint_graph.colors[location.color_index as usize].contact_sims
47            [location.local_index as usize]
48    } else {
49        world.solver_sets[AWAKE_SET as usize].contact_sims[location.local_index as usize]
50    }
51}
52
53fn put_located_sim(world: &mut World, location: ContactSimLocation, sim: ContactSim) {
54    if location.color_index != NULL_INDEX {
55        world.constraint_graph.colors[location.color_index as usize].contact_sims
56            [location.local_index as usize] = sim;
57    } else {
58        world.solver_sets[AWAKE_SET as usize].contact_sims[location.local_index as usize] = sim;
59    }
60}
61
62/// Update one contact in the narrow phase. (the b2CollideTask loop body)
63fn collide_contact(
64    world: &mut World,
65    location: ContactSimLocation,
66    update_context: &ContactUpdateContext,
67) {
68    let recycle_distance = world.contact_recycle_distance;
69    let speculative_distance_ = speculative_distance();
70    let recycle_distance_non_touching = min_float(recycle_distance, speculative_distance_);
71
72    let mut contact_sim = get_located_sim(world, location);
73
74    let contact_id = contact_sim.contact_id;
75
76    // Do proxies still overlap?
77    let overlap = {
78        let shape_a = &world.shapes[contact_sim.shape_id_a as usize];
79        let shape_b = &world.shapes[contact_sim.shape_id_b as usize];
80        aabb_overlaps(shape_a.fat_aabb, shape_b.fat_aabb)
81    };
82
83    if !overlap {
84        contact_sim.sim_flags |= contact_flags::SIM_DISJOINT;
85        contact_sim.sim_flags &= !contact_flags::SIM_TOUCHING;
86        put_located_sim(world, location, contact_sim);
87        world.task_contexts[0]
88            .contact_state_bit_set
89            .set_bit(contact_id as u32);
90        return;
91    }
92
93    let was_touching = contact_sim.sim_flags & contact_flags::SIM_TOUCHING != 0;
94
95    // Update contact respecting shape/body order (A,B)
96    let (shape_body_id_a, shape_body_id_b) = {
97        let shape_a = &world.shapes[contact_sim.shape_id_a as usize];
98        let shape_b = &world.shapes[contact_sim.shape_id_b as usize];
99        (shape_a.body_id, shape_b.body_id)
100    };
101
102    let body_a = &world.bodies[shape_body_id_a as usize];
103    let body_b = &world.bodies[shape_body_id_b as usize];
104    let body_sim_a =
105        world.solver_sets[body_a.set_index as usize].body_sims[body_a.local_index as usize];
106    let body_sim_b =
107        world.solver_sets[body_b.set_index as usize].body_sims[body_b.local_index as usize];
108    let transform_a = body_sim_a.transform;
109    let transform_b = body_sim_b.transform;
110
111    // These may not be skipped by relative transform check below
112    contact_sim.body_sim_index_a = if body_a.set_index == AWAKE_SET {
113        body_a.local_index
114    } else {
115        NULL_INDEX
116    };
117    contact_sim.inv_mass_a = body_sim_a.inv_mass;
118    contact_sim.inv_i_a = body_sim_a.inv_inertia;
119
120    contact_sim.body_sim_index_b = if body_b.set_index == AWAKE_SET {
121        body_b.local_index
122    } else {
123        NULL_INDEX
124    };
125    contact_sim.inv_mass_b = body_sim_b.inv_mass;
126    contact_sim.inv_i_b = body_sim_b.inv_inertia;
127
128    let type_a = body_a.type_;
129    let type_b = body_b.type_;
130
131    // Contact recycling optimization. Please cite this code if you use this
132    // optimization. This is inspired by persistent contact manifolds used in
133    // some physics engines, such as PhysX. However, this allows larger
134    // relative motion and has fewer tuning parameters (just one).
135    if recycle_distance > 0.0
136        && contact_sim.sim_flags & contact_flags::SIM_RELATIVE_TRANSFORM_VALID != 0
137        && contact_sim.sim_flags & contact_flags::RECYCLE != 0
138    {
139        let cached_q_a = contact_sim.cached_rotation_a;
140        let cached_q_b = contact_sim.cached_rotation_b;
141        let xfc = contact_sim.cached_relative_pose;
142        let xf = inv_mul_world_transforms(transform_a, transform_b);
143
144        let cos_a = relative_cos(transform_a.q, cached_q_a);
145        let cos_b = relative_cos(transform_b.q, cached_q_b);
146        let min_cos = min_float(cos_a, cos_b);
147
148        let max_extent_a = if type_a == BodyType::Static {
149            0.0
150        } else {
151            body_sim_a.max_extent
152        };
153        let max_extent_b = if type_b == BodyType::Static {
154            0.0
155        } else {
156            body_sim_b.max_extent
157        };
158        let max_extent = max_float(max_extent_a, max_extent_b);
159        let distance_ = distance(xf.p, xfc.p);
160        let qr = inv_mul_rot(xf.q, xfc.q);
161
162        // This metric is used for fast bodies and sleeping. It comes from
163        // conservative advancement. Note that qr.s == sin(theta) ~= theta for
164        // small angles. Need a tighter tolerance for non-touching shapes so
165        // that contacts are not missed.
166        let tolerance = if was_touching {
167            recycle_distance
168        } else {
169            recycle_distance_non_touching
170        };
171
172        if min_cos > crate::constants::CONTACT_RECYCLE_COS_ANGLE
173            && distance_ + max_extent * abs_float(qr.s) < tolerance
174        {
175            let dq_a = mul_rot(transform_a.q, invert_rot(cached_q_a));
176            let dq_b = mul_rot(transform_b.q, invert_rot(cached_q_b));
177            let normal = contact_sim.manifold.normal;
178
179            // Minimize round-off
180            let dc = sub_pos(body_sim_b.center, body_sim_a.center);
181
182            for i in 0..contact_sim.manifold.point_count as usize {
183                // Keep anchors but update separation, same as sub-stepping.
184                // This eliminates jitter.
185                let mp = &mut contact_sim.manifold.points[i];
186                let r_a = rotate_vector(dq_a, mp.anchor_a);
187                let r_b = rotate_vector(dq_b, mp.anchor_b);
188                let dp = crate::math_functions::add(dc, sub(r_b, r_a));
189                mp.separation = mp.base_separation + dot(dp, normal);
190                mp.persisted = true;
191            }
192
193            world.task_contexts[0].recycled_contact_count += 1;
194
195            // Contact is recycled. This also skips updating other aspects of
196            // the contact such as material parameters.
197            put_located_sim(world, location, contact_sim);
198            return;
199        }
200    }
201
202    // Caching for contact recycling.
203    contact_sim.cached_rotation_a = transform_a.q;
204    contact_sim.cached_rotation_b = transform_b.q;
205    contact_sim.cached_relative_pose = inv_mul_world_transforms(transform_a, transform_b);
206    contact_sim.sim_flags |= contact_flags::SIM_RELATIVE_TRANSFORM_VALID;
207
208    let center_offset_a = rotate_vector(transform_a.q, body_sim_a.local_center);
209    let center_offset_b = rotate_vector(transform_b.q, body_sim_b.local_center);
210
211    // This updates solid contacts
212    let touching = {
213        let shape_a = &world.shapes[contact_sim.shape_id_a as usize];
214        let shape_b = &world.shapes[contact_sim.shape_id_b as usize];
215        update_contact(
216            update_context,
217            &mut contact_sim,
218            shape_a,
219            transform_a,
220            center_offset_a,
221            shape_b,
222            transform_b,
223            center_offset_b,
224        )
225    };
226
227    // State changes that affect island connectivity. Also affects contact
228    // events.
229    if touching && !was_touching {
230        contact_sim.sim_flags |= contact_flags::SIM_STARTED_TOUCHING;
231        world.task_contexts[0]
232            .contact_state_bit_set
233            .set_bit(contact_id as u32);
234    } else if !touching && was_touching {
235        contact_sim.sim_flags |= contact_flags::SIM_STOPPED_TOUCHING;
236        world.task_contexts[0]
237            .contact_state_bit_set
238            .set_bit(contact_id as u32);
239    }
240
241    for i in 0..contact_sim.manifold.point_count as usize {
242        let mp = &mut contact_sim.manifold.points[i];
243        mp.base_separation = mp.separation;
244    }
245
246    put_located_sim(world, location, contact_sim);
247}
248
249/// (static b2AddNonTouchingContact)
250fn add_non_touching_contact(world: &mut World, contact_id: i32, contact_sim: ContactSim) {
251    debug_assert!(world.contacts[contact_id as usize].set_index == AWAKE_SET);
252    let local_index = world.solver_sets[AWAKE_SET as usize].contact_sims.len() as i32;
253    {
254        let contact = &mut world.contacts[contact_id as usize];
255        contact.color_index = NULL_INDEX;
256        contact.local_index = local_index;
257    }
258    world.solver_sets[AWAKE_SET as usize]
259        .contact_sims
260        .push(contact_sim);
261}
262
263/// (static b2RemoveNonTouchingContact)
264fn remove_non_touching_contact(world: &mut World, set_index: i32, local_index: i32) {
265    let set = &mut world.solver_sets[set_index as usize];
266    let moved_index = set.contact_sims.len() as i32 - 1;
267    set.contact_sims.swap_remove(local_index as usize);
268    if moved_index != local_index {
269        let moved_contact_id =
270            world.solver_sets[set_index as usize].contact_sims[local_index as usize].contact_id;
271        let moved_contact = &mut world.contacts[moved_contact_id as usize];
272        debug_assert!(moved_contact.set_index == set_index);
273        debug_assert!(moved_contact.local_index == moved_index);
274        debug_assert!(moved_contact.color_index == NULL_INDEX);
275        moved_contact.local_index = local_index;
276    }
277}
278
279/// Narrow-phase collision. (b2Collide)
280fn collide(world: &mut World, _context: &StepContext) {
281    // gather contacts into a single array for easier iteration; the order is
282    // graph colors then the awake set, matching the C gather
283    let mut locations: Vec<ContactSimLocation> = Vec::new();
284    for color_index in 0..GRAPH_COLOR_COUNT {
285        let count = world.constraint_graph.colors[color_index as usize]
286            .contact_sims
287            .len();
288        for local_index in 0..count as i32 {
289            locations.push(ContactSimLocation {
290                color_index,
291                local_index,
292            });
293        }
294    }
295    {
296        let count = world.solver_sets[AWAKE_SET as usize].contact_sims.len();
297        for local_index in 0..count as i32 {
298            locations.push(ContactSimLocation {
299                color_index: NULL_INDEX,
300                local_index,
301            });
302        }
303    }
304
305    if locations.is_empty() {
306        return;
307    }
308
309    // Contact bit set on ids because contact locations are unstable as they
310    // move between touching and not touching.
311    let contact_id_capacity = world.contact_id_pool.id_capacity();
312    {
313        let task_context = &mut world.task_contexts[0];
314        task_context
315            .contact_state_bit_set
316            .set_bit_count_and_clear(contact_id_capacity as u32);
317        task_context.recycled_contact_count = 0;
318    }
319
320    // (b2CollideTask over the whole range on one worker)
321    let update_context = ContactUpdateContext::new(world);
322    for &location in &locations {
323        collide_contact(world, location, &update_context);
324    }
325
326    // Serially update contact state. Process contact state changes, iterating
327    // over set bits.
328    let end_event_array_index = world.end_event_array_index as usize;
329    let world_id = world.world_id;
330
331    let block_count = world.task_contexts[0].contact_state_bit_set.block_count();
332    for k in 0..block_count {
333        let mut bits = world.task_contexts[0].contact_state_bit_set.block(k);
334        while bits != 0 {
335            let ctz = bits.trailing_zeros();
336            let contact_id = (64 * k + ctz) as i32;
337
338            let (color_index, local_index, flags, generation, shape_id_a, shape_id_b) = {
339                let contact: &Contact = &world.contacts[contact_id as usize];
340                debug_assert!(contact.set_index == AWAKE_SET);
341                (
342                    contact.color_index,
343                    contact.local_index,
344                    contact.flags,
345                    contact.generation,
346                    contact.shape_id_a,
347                    contact.shape_id_b,
348                )
349            };
350
351            let sim_flags = if color_index != NULL_INDEX {
352                // contact lives in constraint graph
353                debug_assert!((0..GRAPH_COLOR_COUNT).contains(&color_index));
354                world.constraint_graph.colors[color_index as usize].contact_sims
355                    [local_index as usize]
356                    .sim_flags
357            } else {
358                world.solver_sets[AWAKE_SET as usize].contact_sims[local_index as usize].sim_flags
359            };
360
361            let (shape_id_a_full, shape_id_b_full) = {
362                let shape_a = &world.shapes[shape_id_a as usize];
363                let shape_b = &world.shapes[shape_id_b as usize];
364                (
365                    ShapeId {
366                        index1: shape_a.id + 1,
367                        world0: world_id,
368                        generation: shape_a.generation,
369                    },
370                    ShapeId {
371                        index1: shape_b.id + 1,
372                        world0: world_id,
373                        generation: shape_b.generation,
374                    },
375                )
376            };
377            let contact_full_id = ContactId {
378                index1: contact_id + 1,
379                world0: world_id,
380                padding: 0,
381                generation,
382            };
383
384            if sim_flags & contact_flags::SIM_DISJOINT != 0 {
385                // Bounding boxes no longer overlap
386                crate::contact::destroy_contact(world, contact_id, false);
387            } else if sim_flags & contact_flags::SIM_STARTED_TOUCHING != 0 {
388                debug_assert!(world.contacts[contact_id as usize].island_id == NULL_INDEX);
389
390                if flags & contact_flags::ENABLE_CONTACT_EVENTS != 0 {
391                    world.contact_begin_events.push(ContactBeginTouchEvent {
392                        shape_id_a: shape_id_a_full,
393                        shape_id_b: shape_id_b_full,
394                        contact_id: contact_full_id,
395                    });
396                }
397
398                debug_assert!(color_index == NULL_INDEX);
399                debug_assert!(
400                    world.solver_sets[AWAKE_SET as usize].contact_sims[local_index as usize]
401                        .manifold
402                        .point_count
403                        > 0
404                );
405
406                // Link first because this wakes colliding bodies and ensures
407                // the body sims are in the correct place.
408                world.contacts[contact_id as usize].flags |= contact_flags::TOUCHING;
409                crate::island::link_contact(world, contact_id);
410
411                // Make sure these didn't change
412                debug_assert!(world.contacts[contact_id as usize].color_index == NULL_INDEX);
413                debug_assert!(world.contacts[contact_id as usize].local_index == local_index);
414
415                // Refresh the contact sim after the awake set may have grown
416                let mut contact_sim =
417                    world.solver_sets[AWAKE_SET as usize].contact_sims[local_index as usize];
418                contact_sim.sim_flags &= !contact_flags::SIM_STARTED_TOUCHING;
419
420                // Add first for memcpy
421                crate::constraint_graph::add_contact_to_graph(world, contact_sim, contact_id);
422
423                // This destroys the contact sim in the awake set
424                remove_non_touching_contact(world, AWAKE_SET, local_index);
425            } else if sim_flags & contact_flags::SIM_STOPPED_TOUCHING != 0 {
426                debug_assert!(color_index != NULL_INDEX);
427                let contact_sim = {
428                    let sim = &mut world.constraint_graph.colors[color_index as usize].contact_sims
429                        [local_index as usize];
430                    sim.sim_flags &= !contact_flags::SIM_STOPPED_TOUCHING;
431                    *sim
432                };
433                world.contacts[contact_id as usize].flags &= !contact_flags::TOUCHING;
434
435                if flags & contact_flags::ENABLE_CONTACT_EVENTS != 0 {
436                    world.contact_end_events[end_event_array_index].push(ContactEndTouchEvent {
437                        shape_id_a: shape_id_a_full,
438                        shape_id_b: shape_id_b_full,
439                        contact_id: contact_full_id,
440                    });
441                }
442
443                debug_assert!(contact_sim.manifold.point_count == 0);
444
445                crate::island::unlink_contact(world, contact_id);
446                let body_id_a = world.contacts[contact_id as usize].edges[0].body_id;
447                let body_id_b = world.contacts[contact_id as usize].edges[1].body_id;
448
449                // Add first for memcpy
450                add_non_touching_contact(world, contact_id, contact_sim);
451                crate::constraint_graph::remove_contact_from_graph(
452                    world,
453                    body_id_a,
454                    body_id_b,
455                    color_index,
456                    local_index,
457                );
458            }
459
460            // Clear the smallest set bit
461            bits &= bits - 1;
462        }
463    }
464
465    world.validate_solver_sets();
466    world.validate_contacts();
467}
468
469impl World {
470    /// (b2ValidateContacts — C compiles the body under B2_ENABLE_VALIDATION;
471    /// here the whole check runs in debug builds only)
472    pub fn validate_contacts(&self) {
473        if !cfg!(debug_assertions) {
474            return;
475        }
476
477        let contact_count = self.contacts.len() as i32;
478        debug_assert!(contact_count == self.contact_id_pool.id_capacity());
479        let mut allocated_contact_count = 0;
480
481        for contact_index in 0..contact_count {
482            let contact = &self.contacts[contact_index as usize];
483            if contact.contact_id == NULL_INDEX {
484                continue;
485            }
486
487            debug_assert!(contact.contact_id == contact_index);
488
489            allocated_contact_count += 1;
490
491            let touching = contact.flags & contact_flags::TOUCHING != 0;
492
493            let set_id = contact.set_index;
494
495            if set_id == AWAKE_SET {
496                if touching {
497                    debug_assert!(
498                        0 <= contact.color_index && contact.color_index < GRAPH_COLOR_COUNT
499                    );
500                } else {
501                    debug_assert!(contact.color_index == NULL_INDEX);
502                }
503            } else if set_id >= FIRST_SLEEPING_SET {
504                // Only touching contacts allowed in a sleeping set
505                debug_assert!(touching);
506            } else {
507                // Sleeping and non-touching contacts belong in the disabled set
508                debug_assert!(!touching && set_id == crate::solver_set::DISABLED_SET);
509            }
510
511            let contact_sim = if set_id == AWAKE_SET && contact.color_index != NULL_INDEX {
512                &self.constraint_graph.colors[contact.color_index as usize].contact_sims
513                    [contact.local_index as usize]
514            } else {
515                &self.solver_sets[set_id as usize].contact_sims[contact.local_index as usize]
516            };
517            debug_assert!(contact_sim.contact_id == contact_index);
518            debug_assert!(contact_sim.body_id_a == contact.edges[0].body_id);
519            debug_assert!(contact_sim.body_id_b == contact.edges[1].body_id);
520
521            let sim_touching = contact_sim.sim_flags & contact_flags::SIM_TOUCHING != 0;
522            debug_assert!(touching == sim_touching);
523
524            debug_assert!(
525                0 <= contact_sim.manifold.point_count && contact_sim.manifold.point_count <= 2
526            );
527        }
528
529        let contact_id_count = self.contact_id_pool.id_count();
530        debug_assert!(allocated_contact_count == contact_id_count);
531        let _ = allocated_contact_count;
532    }
533}
534
535/// Simulate a world for one time step. (b2World_Step — takes &mut World; the
536/// C resolves the world from an id)
537pub fn world_step(world: &mut World, time_step: f32, sub_step_count: i32) {
538    debug_assert!(is_valid_float(time_step));
539    debug_assert!(0 < sub_step_count);
540
541    debug_assert!(!world.locked);
542    if world.locked {
543        return;
544    }
545
546    // (B2_REC(world, Step, ...))
547    if let Some(mut rec) = world.recording.take() {
548        let world_id = crate::id::WorldId {
549            index1: world.world_id + 1,
550            generation: world.generation,
551        };
552        crate::recording::write_step(&mut rec, world_id, time_step, sub_step_count);
553        world.recording = Some(rec);
554    }
555
556    // Prepare to capture events. Ensure user does not access stale data if
557    // there is an early return.
558    world.body_move_events.clear();
559    world.sensor_begin_events.clear();
560    world.contact_begin_events.clear();
561    world.contact_hit_events.clear();
562    world.joint_events.clear();
563
564    world.profile = super::Profile::default();
565
566    world.locked = true;
567
568    let step_ticks = get_ticks();
569
570    {
571        let static_shape_count = world.broad_phase.trees[BodyType::Static as usize].proxy_count();
572        let dynamic_shape_count = world.broad_phase.trees[BodyType::Dynamic as usize].proxy_count();
573        let static_body_count = world.solver_sets[STATIC_SET as usize].body_sims.len() as i32;
574        // this includes kinematic bodies
575        let total_body_count = world.body_id_pool.id_count();
576        let total_contact_count = world.contact_id_pool.id_count();
577
578        let c = &mut world.max_capacity;
579        c.static_shape_count =
580            crate::math_functions::max_int(c.static_shape_count, static_shape_count);
581        c.dynamic_shape_count =
582            crate::math_functions::max_int(c.dynamic_shape_count, dynamic_shape_count);
583        c.static_body_count =
584            crate::math_functions::max_int(c.static_body_count, static_body_count);
585        c.dynamic_body_count = crate::math_functions::max_int(
586            c.dynamic_body_count,
587            total_body_count - static_body_count,
588        );
589        c.contact_count = crate::math_functions::max_int(c.contact_count, total_contact_count);
590    }
591
592    // Update collision pairs and create contacts
593    {
594        let pair_ticks = get_ticks();
595        update_broad_phase_pairs(world);
596        world.profile.pairs = get_milliseconds(pair_ticks);
597    }
598
599    let sub_step_count = crate::math_functions::max_int(1, sub_step_count);
600    let mut context = StepContext {
601        dt: time_step,
602        sub_step_count,
603        ..StepContext::default()
604    };
605
606    if time_step > 0.0 {
607        context.inv_dt = 1.0 / time_step;
608        context.h = time_step / sub_step_count as f32;
609        context.inv_h = sub_step_count as f32 * context.inv_dt;
610    } else {
611        context.inv_dt = 0.0;
612        context.h = 0.0;
613        context.inv_h = 0.0;
614    }
615
616    world.inv_h = context.inv_h;
617    world.inv_dt = context.inv_dt;
618
619    // Hertz values get reduced for large time steps
620    let contact_hertz = min_float(world.contact_hertz, 0.125 * context.inv_h);
621    context.contact_softness = make_soft(contact_hertz, world.contact_damping_ratio, context.h);
622    context.static_softness =
623        make_soft(2.0 * contact_hertz, world.contact_damping_ratio, context.h);
624
625    context.restitution_threshold = world.restitution_threshold;
626    context.max_linear_velocity = world.max_linear_speed;
627    context.contact_speed = world.contact_speed;
628    context.enable_warm_starting = world.enable_warm_starting;
629
630    // Narrow phase: update contacts
631    {
632        let collide_ticks = get_ticks();
633        collide(world, &context);
634        world.profile.collide = get_milliseconds(collide_ticks);
635    }
636
637    // Integrate velocities, solve velocity constraints, and integrate
638    // positions.
639    if time_step > 0.0 {
640        let solve_ticks = get_ticks();
641        solve(world, &context);
642        world.profile.solve = get_milliseconds(solve_ticks);
643    }
644
645    // Update sensors
646    {
647        let sensor_ticks = get_ticks();
648        overlap_sensors(world);
649        world.profile.sensors = get_milliseconds(sensor_ticks);
650    }
651
652    world.profile.step = get_milliseconds(step_ticks);
653
654    world.locked = false;
655
656    // Swap end event array buffers
657    world.end_event_array_index = 1 - world.end_event_array_index;
658    world.sensor_end_events[world.end_event_array_index as usize].clear();
659    world.contact_end_events[world.end_event_array_index as usize].clear();
660
661    // Per-step StateHash + bounds growth for an active recording session.
662    crate::recording::record_step_end(world);
663}
664
665#[cfg(test)]
666mod tests {
667    use super::*;
668    use crate::body::{create_body, get_body_full_id, get_body_transform};
669    use crate::geometry::make_box;
670    use crate::math_functions::{rot_get_angle, to_pos, Vec2};
671    use crate::shape::create_polygon_shape;
672    use crate::types::{default_body_def, default_shape_def, default_world_def};
673
674    // Port of test_world.c HelloWorld: a dynamic box falls onto a large
675    // static ground box and comes to rest at y ~= 1.
676    #[test]
677    fn hello_world() {
678        // Construct a world object, which will hold and simulate the rigid
679        // bodies.
680        let mut world_def = default_world_def();
681        world_def.gravity = Vec2 { x: 0.0, y: -10.0 };
682
683        let mut world = World::new(&world_def);
684
685        // Define the ground body.
686        let mut ground_body_def = default_body_def();
687        ground_body_def.position = to_pos(Vec2 { x: 0.0, y: -10.0 });
688
689        let ground_id = create_body(&mut world, &ground_body_def);
690
691        // Define the ground box shape. The extents are the half-widths of the
692        // box.
693        let ground_box = make_box(50.0, 10.0);
694
695        // Add the box shape to the ground body.
696        let ground_shape_def = default_shape_def();
697        create_polygon_shape(&mut world, ground_id, &ground_shape_def, &ground_box);
698
699        // Define the dynamic body. We set its position and call the body
700        // factory.
701        let mut body_def = default_body_def();
702        body_def.type_ = crate::types::BodyType::Dynamic;
703        body_def.position = to_pos(Vec2 { x: 0.0, y: 4.0 });
704
705        let body_id = create_body(&mut world, &body_def);
706        let body_index = get_body_full_id(&world, body_id);
707
708        // Define another box shape for our dynamic body.
709        let dynamic_box = make_box(1.0, 1.0);
710
711        // Define the dynamic body shape. Set the box density to be non-zero,
712        // so it will be dynamic. Override the default friction.
713        let mut shape_def = default_shape_def();
714        shape_def.density = 1.0;
715        shape_def.material.friction = 0.3;
716
717        create_polygon_shape(&mut world, body_id, &shape_def, &dynamic_box);
718
719        // Prepare for simulation. Typically we use a time step of 1/60 of a
720        // second (60Hz) and 4 sub-steps. This provides a high quality
721        // simulation in most game scenarios.
722        let time_step = 1.0 / 60.0;
723        let sub_step_count = 4;
724
725        // This is our little game loop.
726        for _ in 0..90 {
727            // Instruct the world to perform a single step of simulation. It is
728            // generally best to keep the time step and iterations fixed.
729            world_step(&mut world, time_step, sub_step_count);
730        }
731
732        let transform = get_body_transform(&world, body_index);
733        let position = transform.p;
734        let rotation = transform.q;
735
736        assert!(abs_float(position.x as f32) < 0.01);
737        assert!(abs_float(position.y as f32 - 1.00) < 0.01);
738        assert!(abs_float(rot_get_angle(rotation)) < 0.01);
739
740        // Profile timings are filled each step (timer.c was dropped earlier).
741        let profile = crate::world::world_get_profile(&world);
742        assert!(profile.step > 0.0);
743        assert!(profile.pairs >= 0.0);
744        assert!(profile.collide >= 0.0);
745        assert!(profile.solve >= 0.0);
746        assert!(profile.sensors >= 0.0);
747    }
748
749    // Port of test_world.c EmptyWorld: stepping an empty world does nothing.
750    #[test]
751    fn empty_world() {
752        let world_def = default_world_def();
753        let mut world = World::new(&world_def);
754
755        let time_step = 1.0 / 60.0;
756        let sub_step_count = 1;
757
758        for _ in 0..60 {
759            world_step(&mut world, time_step, sub_step_count);
760        }
761
762        // b2Solve counts the step before the awake-body early out
763        assert_eq!(world.step_index, 60);
764    }
765}