Skip to main content

box2d_rust/body/
api.rs

1// Body public API from body.c, part 1: validity, transforms, velocities,
2// forces/impulses, and mass properties (b2Body_*).
3//
4// The C resolves the world from the id via the global registry (b2GetWorld /
5// b2GetWorldLocked); the Rust port takes `world` explicitly and the locked
6// guard becomes a debug assert + early return. B2_REC recording is not
7// ported.
8//
9// SPDX-FileCopyrightText: 2023 Erin Catto
10// SPDX-License-Identifier: MIT
11
12use super::{get_body_full_id, get_body_transform_quick, wake_body, BodySim, BodyState};
13use crate::body::body_flags;
14use crate::collision::MassData;
15use crate::constants::speculative_distance;
16use crate::core::NULL_INDEX;
17use crate::geometry::compute_fat_shape_aabb;
18use crate::id::BodyId;
19use crate::math_functions::{
20    aabb_contains, aabb_union, abs_float, add, cross_sv, inv_rotate_vector,
21    inv_transform_world_point, is_valid_float, is_valid_position, is_valid_rotation, is_valid_vec2,
22    length, length_squared, mul_sv, relative_angle, rotate_vector, round_down_float,
23    round_up_float, sub, sub_pos, transform_world_point, Aabb, Pos, Rot, Vec2, WorldTransform,
24    VEC2_ZERO,
25};
26use crate::solver_set::{AWAKE_SET, DISABLED_SET};
27use crate::types::BodyType;
28use crate::world::World;
29
30/// Body identifier validation. Can be used to detect orphaned ids. Provides
31/// validation for up to 64K allocations. (b2Body_IsValid — the world registry
32/// checks collapse to the world argument)
33pub fn body_is_valid(world: &World, id: BodyId) -> bool {
34    if id.index1 < 1 || (world.bodies.len() as i32) < id.index1 {
35        // invalid index
36        return false;
37    }
38
39    let body = &world.bodies[(id.index1 - 1) as usize];
40    if body.set_index == NULL_INDEX {
41        // this was freed
42        return false;
43    }
44
45    debug_assert!(body.local_index != NULL_INDEX);
46
47    if body.generation != id.generation {
48        // this id is orphaned
49        return false;
50    }
51
52    true
53}
54
55/// (b2Body_GetPosition)
56pub fn body_get_position(world: &World, body_id: BodyId) -> Pos {
57    let body_index = get_body_full_id(world, body_id);
58    get_body_transform_quick(world, &world.bodies[body_index as usize]).p
59}
60
61/// (b2Body_GetRotation)
62pub fn body_get_rotation(world: &World, body_id: BodyId) -> Rot {
63    let body_index = get_body_full_id(world, body_id);
64    get_body_transform_quick(world, &world.bodies[body_index as usize]).q
65}
66
67/// (b2Body_GetTransform)
68pub fn body_get_transform(world: &World, body_id: BodyId) -> WorldTransform {
69    let body_index = get_body_full_id(world, body_id);
70    get_body_transform_quick(world, &world.bodies[body_index as usize])
71}
72
73/// (b2Body_GetLocalPoint)
74pub fn body_get_local_point(world: &World, body_id: BodyId, world_point: Pos) -> Vec2 {
75    let transform = body_get_transform(world, body_id);
76    inv_transform_world_point(transform, world_point)
77}
78
79/// (b2Body_GetWorldPoint)
80pub fn body_get_world_point(world: &World, body_id: BodyId, local_point: Vec2) -> Pos {
81    let transform = body_get_transform(world, body_id);
82    transform_world_point(transform, local_point)
83}
84
85/// (b2Body_GetLocalVector)
86pub fn body_get_local_vector(world: &World, body_id: BodyId, world_vector: Vec2) -> Vec2 {
87    let transform = body_get_transform(world, body_id);
88    inv_rotate_vector(transform.q, world_vector)
89}
90
91/// (b2Body_GetWorldVector)
92pub fn body_get_world_vector(world: &World, body_id: BodyId, local_vector: Vec2) -> Vec2 {
93    let transform = body_get_transform(world, body_id);
94    rotate_vector(transform.q, local_vector)
95}
96
97/// (b2Body_SetTransform)
98pub fn body_set_transform(world: &mut World, body_id: BodyId, position: Pos, rotation: Rot) {
99    crate::recording::record_op(world, |rec, _| {
100        crate::recording::write_body_set_transform(rec, body_id, position, rotation)
101    });
102    debug_assert!(is_valid_position(position));
103    debug_assert!(is_valid_rotation(rotation));
104    debug_assert!(body_is_valid(world, body_id));
105    debug_assert!(!world.locked);
106
107    let body_index = get_body_full_id(world, body_id);
108    let (set_index, local_index) = {
109        let body = &world.bodies[body_index as usize];
110        (body.set_index, body.local_index)
111    };
112
113    let transform = {
114        let body_sim = &mut world.solver_sets[set_index as usize].body_sims[local_index as usize];
115        body_sim.transform.p = position;
116        body_sim.transform.q = rotation;
117        body_sim.center = transform_world_point(body_sim.transform, body_sim.local_center);
118
119        body_sim.rotation0 = body_sim.transform.q;
120        body_sim.center0 = body_sim.center;
121        body_sim.transform
122    };
123
124    let speculative_distance_ = speculative_distance();
125
126    let mut shape_id = world.bodies[body_index as usize].head_shape_id;
127    while shape_id != NULL_INDEX {
128        let (next_shape_id, moved) = {
129            let shape = &mut world.shapes[shape_id as usize];
130            let aabb = compute_fat_shape_aabb(&shape.geometry, transform, speculative_distance_);
131            shape.aabb = aabb;
132
133            let mut moved = None;
134            if !aabb_contains(shape.fat_aabb, aabb) {
135                let margin = shape.aabb_margin;
136                let fat_aabb = Aabb {
137                    lower_bound: Vec2 {
138                        x: aabb.lower_bound.x - margin,
139                        y: aabb.lower_bound.y - margin,
140                    },
141                    upper_bound: Vec2 {
142                        x: aabb.upper_bound.x + margin,
143                        y: aabb.upper_bound.y + margin,
144                    },
145                };
146                shape.fat_aabb = fat_aabb;
147
148                // The body could be disabled
149                if shape.proxy_key != NULL_INDEX {
150                    moved = Some((shape.proxy_key, fat_aabb));
151                }
152            }
153            (shape.next_shape_id, moved)
154        };
155
156        if let Some((proxy_key, fat_aabb)) = moved {
157            world.broad_phase.move_proxy(proxy_key, fat_aabb);
158        }
159
160        shape_id = next_shape_id;
161    }
162}
163
164/// (b2Body_GetLinearVelocity)
165pub fn body_get_linear_velocity(world: &World, body_id: BodyId) -> Vec2 {
166    let body_index = get_body_full_id(world, body_id);
167    let body = &world.bodies[body_index as usize];
168    if body.set_index == AWAKE_SET {
169        return world.solver_sets[AWAKE_SET as usize].body_states[body.local_index as usize]
170            .linear_velocity;
171    }
172    VEC2_ZERO
173}
174
175/// (b2Body_GetAngularVelocity)
176pub fn body_get_angular_velocity(world: &World, body_id: BodyId) -> f32 {
177    let body_index = get_body_full_id(world, body_id);
178    let body = &world.bodies[body_index as usize];
179    if body.set_index == AWAKE_SET {
180        return world.solver_sets[AWAKE_SET as usize].body_states[body.local_index as usize]
181            .angular_velocity;
182    }
183    0.0
184}
185
186/// (b2Body_SetLinearVelocity)
187pub fn body_set_linear_velocity(world: &mut World, body_id: BodyId, linear_velocity: Vec2) {
188    crate::recording::record_op(world, |rec, _| {
189        crate::recording::write_body_vec2(
190            rec,
191            crate::recording::OP_BODY_SET_LINEAR_VELOCITY,
192            body_id,
193            linear_velocity,
194        )
195    });
196    let body_index = get_body_full_id(world, body_id);
197
198    if world.bodies[body_index as usize].type_ == BodyType::Static {
199        return;
200    }
201
202    if length_squared(linear_velocity) > 0.0 {
203        wake_body(world, body_index);
204    }
205
206    let body = &world.bodies[body_index as usize];
207    if body.set_index != AWAKE_SET {
208        return;
209    }
210    let local_index = body.local_index;
211    world.solver_sets[AWAKE_SET as usize].body_states[local_index as usize].linear_velocity =
212        linear_velocity;
213}
214
215/// (b2Body_SetAngularVelocity)
216pub fn body_set_angular_velocity(world: &mut World, body_id: BodyId, angular_velocity: f32) {
217    crate::recording::record_op(world, |rec, _| {
218        crate::recording::write_body_f32(
219            rec,
220            crate::recording::OP_BODY_SET_ANGULAR_VELOCITY,
221            body_id,
222            angular_velocity,
223        )
224    });
225    let body_index = get_body_full_id(world, body_id);
226
227    {
228        let body = &world.bodies[body_index as usize];
229        if body.type_ == BodyType::Static || body.flags & body_flags::LOCK_ANGULAR_Z != 0 {
230            return;
231        }
232    }
233
234    if angular_velocity != 0.0 {
235        wake_body(world, body_index);
236    }
237
238    let body = &world.bodies[body_index as usize];
239    if body.set_index != AWAKE_SET {
240        return;
241    }
242    let local_index = body.local_index;
243    world.solver_sets[AWAKE_SET as usize].body_states[local_index as usize].angular_velocity =
244        angular_velocity;
245}
246
247/// (b2Body_SetTargetTransform)
248pub fn body_set_target_transform(
249    world: &mut World,
250    body_id: BodyId,
251    target: WorldTransform,
252    time_step: f32,
253    wake: bool,
254) {
255    crate::recording::record_op(world, |rec, _| {
256        crate::recording::write_body_set_target_transform(rec, body_id, target, time_step, wake)
257    });
258    let body_index = get_body_full_id(world, body_id);
259
260    {
261        let body = &world.bodies[body_index as usize];
262        if body.set_index == DISABLED_SET {
263            return;
264        }
265
266        if body.type_ == BodyType::Static || time_step <= 0.0 {
267            return;
268        }
269
270        if body.set_index != AWAKE_SET && !wake {
271            return;
272        }
273    }
274
275    let (sim_local_center, sim_center, sim_q, sim_max_extent) = {
276        let body = &world.bodies[body_index as usize];
277        let sim = &world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize];
278        (
279            sim.local_center,
280            sim.center,
281            sim.transform.q,
282            sim.max_extent,
283        )
284    };
285
286    // Compute linear velocity. The center difference is taken in world
287    // precision then demoted
288    let delta = sub_pos(transform_world_point(target, sim_local_center), sim_center);
289    let inv_time_step = 1.0 / time_step;
290    let linear_velocity = mul_sv(inv_time_step, delta);
291
292    // Compute angular velocity
293    let q1 = sim_q;
294    let q2 = target.q;
295    let delta_angle = relative_angle(q1, q2);
296    let angular_velocity = inv_time_step * delta_angle;
297
298    // Early out if the body is asleep already and the desired movement is
299    // small
300    if world.bodies[body_index as usize].set_index != AWAKE_SET {
301        let max_velocity = length(linear_velocity) + abs_float(angular_velocity) * sim_max_extent;
302
303        // Return if velocity would be sleepy
304        if max_velocity < world.bodies[body_index as usize].sleep_threshold {
305            return;
306        }
307
308        // Must wake for state to exist
309        wake_body(world, body_index);
310    }
311
312    debug_assert!(world.bodies[body_index as usize].set_index == AWAKE_SET);
313
314    let local_index = world.bodies[body_index as usize].local_index;
315    let state = &mut world.solver_sets[AWAKE_SET as usize].body_states[local_index as usize];
316    state.linear_velocity = linear_velocity;
317    state.angular_velocity = angular_velocity;
318}
319
320/// (b2Body_GetLocalPointVelocity)
321pub fn body_get_local_point_velocity(world: &World, body_id: BodyId, local_point: Vec2) -> Vec2 {
322    let body_index = get_body_full_id(world, body_id);
323    let body = &world.bodies[body_index as usize];
324    if body.set_index != AWAKE_SET {
325        return VEC2_ZERO;
326    }
327
328    let state: &BodyState =
329        &world.solver_sets[AWAKE_SET as usize].body_states[body.local_index as usize];
330    let body_sim: &BodySim =
331        &world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize];
332
333    let r = rotate_vector(
334        body_sim.transform.q,
335        sub(local_point, body_sim.local_center),
336    );
337    add(state.linear_velocity, cross_sv(state.angular_velocity, r))
338}
339
340/// (b2Body_GetWorldPointVelocity)
341pub fn body_get_world_point_velocity(world: &World, body_id: BodyId, world_point: Pos) -> Vec2 {
342    let body_index = get_body_full_id(world, body_id);
343    let body = &world.bodies[body_index as usize];
344    if body.set_index != AWAKE_SET {
345        return VEC2_ZERO;
346    }
347
348    let state: &BodyState =
349        &world.solver_sets[AWAKE_SET as usize].body_states[body.local_index as usize];
350    let body_sim: &BodySim =
351        &world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize];
352
353    let r = sub_pos(world_point, body_sim.center);
354    add(state.linear_velocity, cross_sv(state.angular_velocity, r))
355}
356
357/// (b2Body_GetType)
358pub fn body_get_type(world: &World, body_id: BodyId) -> BodyType {
359    let body_index = get_body_full_id(world, body_id);
360    world.bodies[body_index as usize].type_
361}
362
363/// (b2Body_ComputeAABB)
364pub fn body_compute_aabb(world: &World, body_id: BodyId) -> Aabb {
365    debug_assert!(!world.locked);
366
367    let body_index = get_body_full_id(world, body_id);
368    let body = &world.bodies[body_index as usize];
369    if body.head_shape_id == NULL_INDEX {
370        // No shapes, bracket the body origin so the box still contains the
371        // true position far away
372        let transform = get_body_transform_quick(world, body);
373        return Aabb {
374            lower_bound: Vec2 {
375                x: round_down_float(transform.p.x as f64),
376                y: round_down_float(transform.p.y as f64),
377            },
378            upper_bound: Vec2 {
379                x: round_up_float(transform.p.x as f64),
380                y: round_up_float(transform.p.y as f64),
381            },
382        };
383    }
384
385    let mut shape = &world.shapes[body.head_shape_id as usize];
386    let mut aabb = shape.aabb;
387    while shape.next_shape_id != NULL_INDEX {
388        shape = &world.shapes[shape.next_shape_id as usize];
389        aabb = aabb_union(aabb, shape.aabb);
390    }
391
392    aabb
393}
394
395/// (b2Body_GetMass)
396pub fn body_get_mass(world: &World, body_id: BodyId) -> f32 {
397    let body_index = get_body_full_id(world, body_id);
398    world.bodies[body_index as usize].mass
399}
400
401/// (b2Body_GetRotationalInertia)
402pub fn body_get_rotational_inertia(world: &World, body_id: BodyId) -> f32 {
403    let body_index = get_body_full_id(world, body_id);
404    world.bodies[body_index as usize].inertia
405}
406
407/// (b2Body_GetLocalCenter)
408pub fn body_get_local_center(world: &World, body_id: BodyId) -> Vec2 {
409    let body_index = get_body_full_id(world, body_id);
410    let body = &world.bodies[body_index as usize];
411    world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize].local_center
412}
413
414/// (b2Body_GetWorldCenter)
415pub fn body_get_world_center(world: &World, body_id: BodyId) -> Pos {
416    let body_index = get_body_full_id(world, body_id);
417    let body = &world.bodies[body_index as usize];
418    world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize].center
419}
420
421/// (b2Body_SetMassData)
422pub fn body_set_mass_data(world: &mut World, body_id: BodyId, mass_data: MassData) {
423    crate::recording::record_op(world, |rec, _| {
424        crate::recording::write_body_set_mass_data(rec, body_id, mass_data)
425    });
426    debug_assert!(is_valid_float(mass_data.mass) && mass_data.mass >= 0.0);
427    debug_assert!(
428        is_valid_float(mass_data.rotational_inertia) && mass_data.rotational_inertia >= 0.0
429    );
430    debug_assert!(is_valid_vec2(mass_data.center));
431    debug_assert!(!world.locked);
432    if world.locked {
433        return;
434    }
435
436    let body_index = get_body_full_id(world, body_id);
437    let (set_index, local_index) = {
438        let body = &mut world.bodies[body_index as usize];
439        body.mass = mass_data.mass;
440        body.inertia = mass_data.rotational_inertia;
441        (body.set_index, body.local_index)
442    };
443
444    let body_sim = &mut world.solver_sets[set_index as usize].body_sims[local_index as usize];
445    body_sim.local_center = mass_data.center;
446
447    let center = transform_world_point(body_sim.transform, mass_data.center);
448    body_sim.center = center;
449    body_sim.center0 = center;
450
451    body_sim.inv_mass = if mass_data.mass > 0.0 {
452        1.0 / mass_data.mass
453    } else {
454        0.0
455    };
456    body_sim.inv_inertia = if mass_data.rotational_inertia > 0.0 {
457        1.0 / mass_data.rotational_inertia
458    } else {
459        0.0
460    };
461}
462
463/// (b2Body_GetMassData)
464pub fn body_get_mass_data(world: &World, body_id: BodyId) -> MassData {
465    let body_index = get_body_full_id(world, body_id);
466    let body = &world.bodies[body_index as usize];
467    let body_sim = &world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize];
468    MassData {
469        mass: body.mass,
470        center: body_sim.local_center,
471        rotational_inertia: body.inertia,
472    }
473}
474
475/// (b2Body_ApplyMassFromShapes)
476pub fn body_apply_mass_from_shapes(world: &mut World, body_id: BodyId) {
477    crate::recording::record_op(world, |rec, _| {
478        crate::recording::write_body_marker(
479            rec,
480            crate::recording::OP_BODY_APPLY_MASS_FROM_SHAPES,
481            body_id,
482        )
483    });
484    debug_assert!(!world.locked);
485    if world.locked {
486        return;
487    }
488
489    let body_index = get_body_full_id(world, body_id);
490    super::update_body_mass_data(world, body_index);
491}
492
493/// (b2Body_SetLinearDamping)
494pub fn body_set_linear_damping(world: &mut World, body_id: BodyId, linear_damping: f32) {
495    crate::recording::record_op(world, |rec, _| {
496        crate::recording::write_body_f32(
497            rec,
498            crate::recording::OP_BODY_SET_LINEAR_DAMPING,
499            body_id,
500            linear_damping,
501        )
502    });
503    debug_assert!(is_valid_float(linear_damping) && linear_damping >= 0.0);
504    debug_assert!(!world.locked);
505    if world.locked {
506        return;
507    }
508
509    let body_index = get_body_full_id(world, body_id);
510    let body = &world.bodies[body_index as usize];
511    world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize]
512        .linear_damping = linear_damping;
513}
514
515/// (b2Body_GetLinearDamping)
516pub fn body_get_linear_damping(world: &World, body_id: BodyId) -> f32 {
517    let body_index = get_body_full_id(world, body_id);
518    let body = &world.bodies[body_index as usize];
519    world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize].linear_damping
520}
521
522/// (b2Body_SetAngularDamping)
523pub fn body_set_angular_damping(world: &mut World, body_id: BodyId, angular_damping: f32) {
524    crate::recording::record_op(world, |rec, _| {
525        crate::recording::write_body_f32(
526            rec,
527            crate::recording::OP_BODY_SET_ANGULAR_DAMPING,
528            body_id,
529            angular_damping,
530        )
531    });
532    debug_assert!(is_valid_float(angular_damping) && angular_damping >= 0.0);
533    debug_assert!(!world.locked);
534    if world.locked {
535        return;
536    }
537
538    let body_index = get_body_full_id(world, body_id);
539    let body = &world.bodies[body_index as usize];
540    world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize]
541        .angular_damping = angular_damping;
542}
543
544/// (b2Body_GetAngularDamping)
545pub fn body_get_angular_damping(world: &World, body_id: BodyId) -> f32 {
546    let body_index = get_body_full_id(world, body_id);
547    let body = &world.bodies[body_index as usize];
548    world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize].angular_damping
549}
550
551/// (b2Body_SetGravityScale)
552pub fn body_set_gravity_scale(world: &mut World, body_id: BodyId, gravity_scale: f32) {
553    crate::recording::record_op(world, |rec, _| {
554        crate::recording::write_body_f32(
555            rec,
556            crate::recording::OP_BODY_SET_GRAVITY_SCALE,
557            body_id,
558            gravity_scale,
559        )
560    });
561    debug_assert!(body_is_valid(world, body_id));
562    debug_assert!(is_valid_float(gravity_scale));
563    debug_assert!(!world.locked);
564    if world.locked {
565        return;
566    }
567
568    let body_index = get_body_full_id(world, body_id);
569    let body = &world.bodies[body_index as usize];
570    world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize].gravity_scale =
571        gravity_scale;
572}
573
574/// (b2Body_GetGravityScale)
575pub fn body_get_gravity_scale(world: &World, body_id: BodyId) -> f32 {
576    debug_assert!(body_is_valid(world, body_id));
577    let body_index = get_body_full_id(world, body_id);
578    let body = &world.bodies[body_index as usize];
579    world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize].gravity_scale
580}
581
582/// (b2Body_SetUserData)
583pub fn body_set_user_data(world: &mut World, body_id: BodyId, user_data: u64) {
584    let body_index = get_body_full_id(world, body_id);
585    world.bodies[body_index as usize].user_data = user_data;
586}
587
588/// (b2Body_GetUserData)
589pub fn body_get_user_data(world: &World, body_id: BodyId) -> u64 {
590    let body_index = get_body_full_id(world, body_id);
591    world.bodies[body_index as usize].user_data
592}
593
594/// (b2Body_SetName — takes &str; truncation matches the C strncpy to
595/// B2_NAME_LENGTH)
596pub fn body_set_name(world: &mut World, body_id: BodyId, name: &str) {
597    crate::recording::record_op(world, |rec, _| {
598        crate::recording::write_body_set_name(rec, body_id, name)
599    });
600    let body_index = get_body_full_id(world, body_id);
601    let body = &mut world.bodies[body_index as usize];
602    // C: strncpy into char[B2_NAME_LENGTH + 1]; truncate to the same limit
603    // (same as create_body).
604    body.name = name
605        .chars()
606        .take(crate::constants::NAME_LENGTH as usize)
607        .collect();
608}
609
610/// (b2Body_GetName)
611pub fn body_get_name(world: &World, body_id: BodyId) -> &str {
612    let body_index = get_body_full_id(world, body_id);
613    &world.bodies[body_index as usize].name
614}