Skip to main content

box2d_rust/world/
api.rs

1// The b2World_* public API from physics_world.c: validity, bounds, events,
2// enable flags, tuning setters, counters, callbacks, explosions, and the
3// static-tree rebuild. World queries (overlap/cast) live in query.rs.
4//
5// Omitted relative to C, all consequences of the registry-less, serial port:
6// - b2CreateWorld/b2DestroyWorld map to World::new and drop.
7// - b2World_SetWorkerCount / GetWorkerCount: the solver is serial; the world
8//   always has exactly one worker context.
9// - b2World_StartRecording / StopRecording: the recording subsystem is not
10//   ported yet.
11// - b2World_Draw / b2World_DumpMemoryStats: debug drawing and allocation
12//   tracking are deferred.
13//
14// SPDX-FileCopyrightText: 2023 Erin Catto
15// SPDX-License-Identifier: MIT
16
17use super::{default_friction_callback, default_restitution_callback, World};
18use super::{CustomFilterFcn, PreSolveFcn, Profile};
19use crate::aabb::offset_aabb;
20use crate::body::{get_body_transform_quick, wake_body};
21use crate::constants::GRAPH_COLOR_COUNT;
22use crate::distance::{make_proxy, shape_distance, DistanceInput, SimplexCache};
23use crate::events::{BodyMoveEvent, ContactEvents, JointEvent, SensorEvents};
24use crate::math_functions::{
25    aabb_union, clamp_float, cross, inv_transform_world_point, is_valid_float, is_valid_position,
26    left_perp, length_squared, max_int, mul_add, mul_sv, normalize, rotate_vector, sub, Aabb, Pos,
27    Vec2, TRANSFORM_IDENTITY,
28};
29use crate::shape::{get_shape_centroid, get_shape_projected_perimeter, make_shape_distance_proxy};
30use crate::solver_set::{AWAKE_SET, FIRST_SLEEPING_SET};
31use crate::types::{
32    BodyType, Capacity, Counters, ExplosionDef, FrictionCallback, RestitutionCallback,
33};
34
35/// World id validity. (b2World_IsValid)
36///
37/// C validates the id against the global world registry; the registry-less
38/// Rust port owns the `World`, so a reachable world is valid unless it has
39/// been torn down.
40pub fn world_is_valid(world: &World) -> bool {
41    world.in_use
42}
43
44/// Compute the bounds of all shapes in the world. Returns the bounds and
45/// whether the world had any proxies at all. (b2ComputeWorldBounds)
46pub fn compute_world_bounds(world: &World) -> (Aabb, bool) {
47    let mut world_bounds = Aabb::default();
48    let mut have_bounds = false;
49
50    for tree in world.broad_phase.trees.iter() {
51        if tree.proxy_count() == 0 {
52            continue;
53        }
54
55        let tree_bounds = tree.root_bounds();
56        world_bounds = if have_bounds {
57            aabb_union(world_bounds, tree_bounds)
58        } else {
59            tree_bounds
60        };
61        have_bounds = true;
62    }
63
64    (world_bounds, have_bounds)
65}
66
67/// Get the bounds of all shapes in the world. Returns a zero AABB for an
68/// empty world. (b2World_GetBounds)
69pub fn world_get_bounds(world: &World) -> Aabb {
70    debug_assert!(!world.locked);
71    if world.locked {
72        return Aabb::default();
73    }
74
75    let (bounds, _) = compute_world_bounds(world);
76    bounds
77}
78
79/// Get the body events for the current time step. The event data is transient.
80/// Do not store a reference to this data. (b2World_GetBodyEvents)
81pub fn world_get_body_events(world: &World) -> &[BodyMoveEvent] {
82    debug_assert!(!world.locked);
83    if world.locked {
84        return &[];
85    }
86
87    &world.body_move_events
88}
89
90/// Get sensor events for the current time step. The event data is transient.
91/// Do not store a reference to this data. (b2World_GetSensorEvents)
92pub fn world_get_sensor_events(world: &World) -> SensorEvents<'_> {
93    debug_assert!(!world.locked);
94    if world.locked {
95        return SensorEvents {
96            begin_events: &[],
97            end_events: &[],
98        };
99    }
100
101    // Careful to use previous buffer
102    let end_event_array_index = 1 - world.end_event_array_index;
103
104    SensorEvents {
105        begin_events: &world.sensor_begin_events,
106        end_events: &world.sensor_end_events[end_event_array_index as usize],
107    }
108}
109
110/// Get contact events for this current time step. The event data is transient.
111/// Do not store a reference to this data. (b2World_GetContactEvents)
112pub fn world_get_contact_events(world: &World) -> ContactEvents<'_> {
113    debug_assert!(!world.locked);
114    if world.locked {
115        return ContactEvents {
116            begin_events: &[],
117            end_events: &[],
118            hit_events: &[],
119        };
120    }
121
122    // Careful to use previous buffer
123    let end_event_array_index = 1 - world.end_event_array_index;
124
125    ContactEvents {
126        begin_events: &world.contact_begin_events,
127        end_events: &world.contact_end_events[end_event_array_index as usize],
128        hit_events: &world.contact_hit_events,
129    }
130}
131
132/// Get the joint events for the current time step. The event data is
133/// transient. Do not store a reference to this data. (b2World_GetJointEvents)
134pub fn world_get_joint_events(world: &World) -> &[JointEvent] {
135    debug_assert!(!world.locked);
136    if world.locked {
137        return &[];
138    }
139
140    &world.joint_events
141}
142
143/// Enable/disable sleep. If your application does not need sleeping, you can
144/// gain some performance by disabling sleep completely at the world level.
145/// (b2World_EnableSleeping)
146pub fn world_enable_sleeping(world: &mut World, flag: bool) {
147    debug_assert!(!world.locked);
148    if world.locked {
149        return;
150    }
151
152    crate::recording::record_op(world, |rec, id| {
153        crate::recording::write_world_bool(
154            rec,
155            crate::recording::OP_WORLD_ENABLE_SLEEPING,
156            id,
157            flag,
158        )
159    });
160
161    if flag == world.enable_sleep {
162        return;
163    }
164
165    world.enable_sleep = flag;
166
167    if !flag {
168        let set_count = world.solver_sets.len();
169        for i in (FIRST_SLEEPING_SET as usize)..set_count {
170            if !world.solver_sets[i].body_sims.is_empty() {
171                crate::solver_set::wake_solver_set(world, i as i32);
172            }
173        }
174    }
175}
176
177/// Is body sleeping enabled? (b2World_IsSleepingEnabled)
178pub fn world_is_sleeping_enabled(world: &World) -> bool {
179    world.enable_sleep
180}
181
182/// Enable/disable constraint warm starting. Advanced feature for testing.
183/// Disabling warm starting greatly reduces stability and provides no
184/// performance gain. (b2World_EnableWarmStarting)
185pub fn world_enable_warm_starting(world: &mut World, flag: bool) {
186    debug_assert!(!world.locked);
187    if world.locked {
188        return;
189    }
190
191    crate::recording::record_op(world, |rec, id| {
192        crate::recording::write_world_bool(
193            rec,
194            crate::recording::OP_WORLD_ENABLE_WARM_STARTING,
195            id,
196            flag,
197        )
198    });
199
200    world.enable_warm_starting = flag;
201}
202
203/// Is constraint warm starting enabled? (b2World_IsWarmStartingEnabled)
204pub fn world_is_warm_starting_enabled(world: &World) -> bool {
205    world.enable_warm_starting
206}
207
208/// Get the number of awake bodies. (b2World_GetAwakeBodyCount)
209pub fn world_get_awake_body_count(world: &World) -> i32 {
210    world.solver_sets[AWAKE_SET as usize].body_sims.len() as i32
211}
212
213/// Enable/disable continuous collision between dynamic and static bodies.
214/// Generally you should keep continuous collision enabled to prevent fast
215/// moving objects from going through static objects. The performance gain
216/// from disabling continuous collision is minor. (b2World_EnableContinuous)
217pub fn world_enable_continuous(world: &mut World, flag: bool) {
218    debug_assert!(!world.locked);
219    if world.locked {
220        return;
221    }
222
223    crate::recording::record_op(world, |rec, id| {
224        crate::recording::write_world_bool(
225            rec,
226            crate::recording::OP_WORLD_ENABLE_CONTINUOUS,
227            id,
228            flag,
229        )
230    });
231
232    world.enable_continuous = flag;
233}
234
235/// Is continuous collision enabled? (b2World_IsContinuousEnabled)
236pub fn world_is_continuous_enabled(world: &World) -> bool {
237    world.enable_continuous
238}
239
240/// Adjust the restitution threshold. It is recommended not to make this value
241/// very small because it will prevent bodies from sleeping. Usually in meters
242/// per second. (b2World_SetRestitutionThreshold)
243pub fn world_set_restitution_threshold(world: &mut World, value: f32) {
244    debug_assert!(!world.locked);
245    if world.locked {
246        return;
247    }
248
249    crate::recording::record_op(world, |rec, id| {
250        crate::recording::write_world_f32(
251            rec,
252            crate::recording::OP_WORLD_SET_RESTITUTION_THRESHOLD,
253            id,
254            value,
255        )
256    });
257
258    world.restitution_threshold = clamp_float(value, 0.0, f32::MAX);
259}
260
261/// Get the the restitution speed threshold. Usually in meters per second.
262/// (b2World_GetRestitutionThreshold)
263pub fn world_get_restitution_threshold(world: &World) -> f32 {
264    world.restitution_threshold
265}
266
267/// Adjust the hit event threshold. This controls the collision speed needed
268/// to generate a b2ContactHitEvent. Usually in meters per second.
269/// (b2World_SetHitEventThreshold)
270pub fn world_set_hit_event_threshold(world: &mut World, value: f32) {
271    debug_assert!(!world.locked);
272    if world.locked {
273        return;
274    }
275
276    crate::recording::record_op(world, |rec, id| {
277        crate::recording::write_world_f32(
278            rec,
279            crate::recording::OP_WORLD_SET_HIT_EVENT_THRESHOLD,
280            id,
281            value,
282        )
283    });
284
285    world.hit_event_threshold = clamp_float(value, 0.0, f32::MAX);
286}
287
288/// Get the the hit event speed threshold. Usually in meters per second.
289/// (b2World_GetHitEventThreshold)
290pub fn world_get_hit_event_threshold(world: &World) -> f32 {
291    world.hit_event_threshold
292}
293
294/// Adjust contact tuning parameters: hertz, damping ratio, and push speed.
295/// Advanced feature for testing. (b2World_SetContactTuning)
296pub fn world_set_contact_tuning(
297    world: &mut World,
298    hertz: f32,
299    damping_ratio: f32,
300    push_speed: f32,
301) {
302    debug_assert!(!world.locked);
303    if world.locked {
304        return;
305    }
306
307    crate::recording::record_op(world, |rec, id| {
308        crate::recording::write_world_set_contact_tuning(rec, id, hertz, damping_ratio, push_speed)
309    });
310
311    world.contact_hertz = clamp_float(hertz, 0.0, f32::MAX);
312    world.contact_damping_ratio = clamp_float(damping_ratio, 0.0, f32::MAX);
313    world.contact_speed = clamp_float(push_speed, 0.0, f32::MAX);
314}
315
316/// Set the contact recycle distance. Contacts this close to a recently
317/// destroyed contact reuse the old impulses for warm starting.
318/// (b2World_SetContactRecycleDistance)
319pub fn world_set_contact_recycle_distance(world: &mut World, recycle_distance: f32) {
320    debug_assert!(!world.locked);
321    if world.locked {
322        return;
323    }
324
325    crate::recording::record_op(world, |rec, id| {
326        crate::recording::write_world_f32(
327            rec,
328            crate::recording::OP_WORLD_SET_CONTACT_RECYCLE_DISTANCE,
329            id,
330            recycle_distance,
331        )
332    });
333
334    world.contact_recycle_distance = clamp_float(recycle_distance, 0.0, f32::MAX);
335}
336
337/// Get the contact recycle distance. (b2World_GetContactRecycleDistance)
338pub fn world_get_contact_recycle_distance(world: &World) -> f32 {
339    world.contact_recycle_distance
340}
341
342/// Set the maximum linear speed. Usually in m/s.
343/// (b2World_SetMaximumLinearSpeed)
344pub fn world_set_maximum_linear_speed(world: &mut World, maximum_linear_speed: f32) {
345    debug_assert!(is_valid_float(maximum_linear_speed) && maximum_linear_speed > 0.0);
346
347    debug_assert!(!world.locked);
348    if world.locked {
349        return;
350    }
351
352    crate::recording::record_op(world, |rec, id| {
353        crate::recording::write_world_f32(
354            rec,
355            crate::recording::OP_WORLD_SET_MAXIMUM_LINEAR_SPEED,
356            id,
357            maximum_linear_speed,
358        )
359    });
360
361    world.max_linear_speed = maximum_linear_speed;
362}
363
364/// Get the maximum linear speed. Usually in m/s.
365/// (b2World_GetMaximumLinearSpeed)
366pub fn world_get_maximum_linear_speed(world: &World) -> f32 {
367    world.max_linear_speed
368}
369
370/// Get the current world performance profile. (b2World_GetProfile)
371pub fn world_get_profile(world: &World) -> Profile {
372    world.profile
373}
374
375/// Get world counters and sizes. (b2World_GetCounters)
376pub fn world_get_counters(world: &World) -> Counters {
377    let mut s = Counters {
378        body_count: world.body_id_pool.id_count(),
379        shape_count: world.shape_id_pool.id_count(),
380        contact_count: world.contact_id_pool.id_count(),
381        joint_count: world.joint_id_pool.id_count(),
382        island_count: world.island_id_pool.id_count(),
383        ..Default::default()
384    };
385
386    let static_tree = &world.broad_phase.trees[BodyType::Static as usize];
387    s.static_tree_height = static_tree.height();
388
389    let dynamic_tree = &world.broad_phase.trees[BodyType::Dynamic as usize];
390    let kinematic_tree = &world.broad_phase.trees[BodyType::Kinematic as usize];
391    s.tree_height = max_int(dynamic_tree.height(), kinematic_tree.height());
392
393    // stack_used, byte_count, and task_count stay zero: no arena allocator,
394    // no global allocation tracking, no task system in the serial port.
395
396    for i in 0..world.worker_count as usize {
397        s.recycled_contact_count += world.task_contexts[i].recycled_contact_count;
398    }
399
400    for i in 0..GRAPH_COLOR_COUNT as usize {
401        let color = &world.constraint_graph.colors[i];
402        s.color_counts[i] = (color.contact_sims.len() + color.joint_sims.len()) as i32;
403        s.awake_contact_count += color.contact_sims.len() as i32;
404    }
405    s.awake_contact_count += world.solver_sets[AWAKE_SET as usize].contact_sims.len() as i32;
406
407    s
408}
409
410/// Get the maximum capacity the world has reached. (b2World_GetMaxCapacity)
411pub fn world_get_max_capacity(world: &World) -> Capacity {
412    world.max_capacity
413}
414
415/// Set the user data pointer. (b2World_SetUserData)
416pub fn world_set_user_data(world: &mut World, user_data: u64) {
417    world.user_data = user_data;
418}
419
420/// Get the user data pointer. (b2World_GetUserData)
421pub fn world_get_user_data(world: &World) -> u64 {
422    world.user_data
423}
424
425/// Register the friction callback. This is optional. Passing `None` restores
426/// the default mixing rule. (b2World_SetFrictionCallback)
427pub fn world_set_friction_callback(world: &mut World, callback: Option<FrictionCallback>) {
428    if world.locked {
429        return;
430    }
431
432    world.friction_callback = Some(callback.unwrap_or(default_friction_callback));
433}
434
435/// Register the restitution callback. This is optional. Passing `None`
436/// restores the default mixing rule. (b2World_SetRestitutionCallback)
437pub fn world_set_restitution_callback(world: &mut World, callback: Option<RestitutionCallback>) {
438    if world.locked {
439        return;
440    }
441
442    world.restitution_callback = Some(callback.unwrap_or(default_restitution_callback));
443}
444
445/// Register the custom filter callback. This is optional.
446/// (b2World_SetCustomFilterCallback)
447pub fn world_set_custom_filter_callback(
448    world: &mut World,
449    fcn: Option<CustomFilterFcn>,
450    context: u64,
451) {
452    world.custom_filter_fcn = fcn;
453    world.custom_filter_context = context;
454}
455
456/// Register the pre-solve callback. This is optional.
457/// (b2World_SetPreSolveCallback)
458pub fn world_set_pre_solve_callback(world: &mut World, fcn: Option<PreSolveFcn>, context: u64) {
459    world.pre_solve_fcn = fcn;
460    world.pre_solve_context = context;
461}
462
463/// Set the gravity vector for the entire world. Box2D has no concept of an up
464/// direction and this is left as a decision for the application.
465/// (b2World_SetGravity)
466pub fn world_set_gravity(world: &mut World, gravity: Vec2) {
467    crate::recording::record_op(world, |rec, id| {
468        crate::recording::write_world_set_gravity(rec, id, gravity)
469    });
470    world.gravity = gravity;
471}
472
473/// Get the gravity vector. (b2World_GetGravity)
474pub fn world_get_gravity(world: &World) -> Vec2 {
475    world.gravity
476}
477
478/// Apply a radial explosion. Explosions are modeled as a force, not as a
479/// collision event. (b2World_Explode + static ExplosionCallback)
480pub fn world_explode(world: &mut World, explosion_def: &ExplosionDef) {
481    let mask_bits = explosion_def.mask_bits;
482    let position = explosion_def.position;
483    let radius = explosion_def.radius;
484    let falloff = explosion_def.falloff;
485    let impulse_per_length = explosion_def.impulse_per_length;
486
487    debug_assert!(is_valid_position(position));
488    debug_assert!(is_valid_float(radius) && radius >= 0.0);
489    debug_assert!(is_valid_float(falloff) && falloff >= 0.0);
490    debug_assert!(is_valid_float(impulse_per_length));
491
492    debug_assert!(!world.locked);
493    if world.locked {
494        return;
495    }
496
497    crate::recording::record_op(world, |rec, id| {
498        crate::recording::write_world_explode(rec, id, explosion_def)
499    });
500
501    // The broad-phase tree is float, so translate a local query box out to
502    // world with outward rounding
503    let extent = radius + falloff;
504    let local_box = Aabb {
505        lower_bound: Vec2 {
506            x: -extent,
507            y: -extent,
508        },
509        upper_bound: Vec2 {
510            x: extent,
511            y: extent,
512        },
513    };
514    let aabb = offset_aabb(local_box, position);
515
516    // C applies impulses inside the tree traversal (ExplosionCallback), but
517    // waking a body needs &mut World while the tree is borrowed. Collect the
518    // candidate shape ids in traversal order first, then apply; waking a body
519    // moves its sim between solver sets without touching any transform or
520    // broad-phase proxy, so the two-phase form is behaviorally identical.
521    let mut shape_ids: Vec<i32> = Vec::new();
522    world.broad_phase.trees[BodyType::Dynamic as usize].query(aabb, mask_bits, |_, user_data| {
523        shape_ids.push(user_data as i32);
524        true
525    });
526
527    for shape_id in shape_ids {
528        explosion_callback(
529            world,
530            shape_id,
531            position,
532            radius,
533            falloff,
534            impulse_per_length,
535        );
536    }
537}
538
539/// Apply the explosion impulse to one candidate shape.
540/// (static ExplosionCallback)
541fn explosion_callback(
542    world: &mut World,
543    shape_id: i32,
544    position: Pos,
545    radius: f32,
546    falloff: f32,
547    impulse_per_length: f32,
548) {
549    let shape = &world.shapes[shape_id as usize];
550    let body_id = shape.body_id;
551    let body = &world.bodies[body_id as usize];
552    debug_assert!(body.type_ == BodyType::Dynamic);
553
554    let xf = get_body_transform_quick(world, body);
555
556    // Re-center the explosion into the shape local frame so distance and
557    // direction stay precise far from the origin. Everything below runs in
558    // that near-origin frame.
559    let local_position = inv_transform_world_point(xf, position);
560
561    let input = DistanceInput {
562        proxy_a: make_shape_distance_proxy(shape),
563        proxy_b: make_proxy(&[local_position], 0.0),
564        transform: TRANSFORM_IDENTITY,
565        use_radii: true,
566    };
567
568    let mut cache = SimplexCache::default();
569    let output = shape_distance(&input, &mut cache, None);
570
571    if output.distance > radius + falloff {
572        return;
573    }
574
575    // All shape-derived values are computed before waking the body; C computes
576    // them after b2WakeBody, but waking does not modify the shape or transform.
577    let mut closest_point = output.point_a;
578    if output.distance == 0.0 {
579        closest_point = get_shape_centroid(shape);
580    }
581
582    let mut direction = sub(closest_point, local_position);
583    if length_squared(direction) > 100.0 * f32::EPSILON * f32::EPSILON {
584        direction = normalize(direction);
585    } else {
586        direction = Vec2 { x: 1.0, y: 0.0 };
587    }
588
589    let local_line = left_perp(direction);
590    let perimeter = get_shape_projected_perimeter(shape, local_line);
591    let mut scale = 1.0;
592    if output.distance > radius && falloff > 0.0 {
593        scale = clamp_float((radius + falloff - output.distance) / falloff, 0.0, 1.0);
594    }
595
596    let magnitude = impulse_per_length * perimeter * scale;
597    let impulse = mul_sv(magnitude, rotate_vector(xf.q, direction));
598
599    wake_body(world, body_id);
600
601    let body = &world.bodies[body_id as usize];
602    if body.set_index != AWAKE_SET {
603        return;
604    }
605
606    let local_index = body.local_index;
607    let set = &mut world.solver_sets[AWAKE_SET as usize];
608    let (inv_mass, inv_inertia, local_center) = {
609        let body_sim = &set.body_sims[local_index as usize];
610        (
611            body_sim.inv_mass,
612            body_sim.inv_inertia,
613            body_sim.local_center,
614        )
615    };
616    let state = &mut set.body_states[local_index as usize];
617    state.linear_velocity = mul_add(state.linear_velocity, inv_mass, impulse);
618
619    // Lever arm from the center of mass to the closest point, rotated to world
620    let r = rotate_vector(xf.q, sub(closest_point, local_center));
621    state.angular_velocity += inv_inertia * cross(r, impulse);
622}
623
624/// This is for internal testing. (b2World_RebuildStaticTree)
625pub fn world_rebuild_static_tree(world: &mut World) {
626    debug_assert!(!world.locked);
627    if world.locked {
628        return;
629    }
630
631    crate::recording::record_op(world, |rec, id| {
632        crate::recording::write_world_marker(
633            rec,
634            crate::recording::OP_WORLD_REBUILD_STATIC_TREE,
635            id,
636        )
637    });
638
639    world.broad_phase.trees[BodyType::Static as usize].rebuild(true);
640}
641
642/// This is for internal testing. (b2World_EnableSpeculative)
643pub fn world_enable_speculative(world: &mut World, flag: bool) {
644    crate::recording::record_op(world, |rec, id| {
645        crate::recording::write_world_bool(
646            rec,
647            crate::recording::OP_WORLD_ENABLE_SPECULATIVE,
648            id,
649            flag,
650        )
651    });
652    world.enable_speculative = flag;
653}