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